方法
1. 方法的作用
-
- 使程序变得更简短而清晰。
-
- 有利于程序维护。
-
- 可以提高程序开发的效率。
-
- 提高了代码的重用性
2. 方法的定义
方法的命名规则
- 1.方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerson。
- 2.下划线可能出现在 JUnit 测试方法名称中用以分隔名称的逻辑组件。一个典型的模式是:test
_ ,例如 testPop_emptyStack。
什么是方法
Java方法是语句的集合,它们在一起执行一个功能。
-
方法是解决一类问题的步骤的有序组合
-
方法包含于类或对象中
-
方法在程序中被创建,在其他地方被引用
方法的定义
一般情况下,定义一个方法包含以下语法:
修饰符 返回值类型 方法名(参数类型 参数名){ ... 方法体 ... return 返回值; }
方法包含一个方法头和一个方法体。下面是一个方法的所有部分:
-
修饰符:修饰符,这是可选的,告诉编译器如何调用该方法。定义了该方法的访问类型。
-
返回值类型 :方法可能会返回值。returnValueType 是方法返回值的数据类型。有些方法执行所需的操作,但没有返回值。在这种情况下,returnValueType 是关键字void。
-
方法名:是方法的实际名称。方法名和参数表共同构成方法签名。
-
参数类型:参数像是一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数。
-
方法体:方法体包含具体的语句,定义该方法的功能。
如:
public static int age(int birthday){...}
参数可以有多个:
static float interest(float principal, int year){...}
注意: 在一些其它语言中方法指过程和函数。一个返回非void类型返回值的方法称为函数;一个返回void类型返回值的方法叫做过程。
成员方法
Java中的成员方法(也称为实例方法)是定义在类中的方法,它们与类的实例(对象)相关联。这意味着成员方法必须通过类的实例来调用,并且它们可以访问和修改该实例的字段(变量)。成员方法通常用于执行与对象状态相关的操作。
成员方法的特点:
- 实例调用:成员方法不能通过类名直接调用,必须通过类的实例(对象)来调用。
- 访问实例变量:成员方法可以访问和修改其所属类的实例变量(非静态变量)。
- 隐式
this
引用:在成员方法内部,可以通过this
关键字来引用当前对象的实例。this
关键字是隐式可用的,但在某些情况下(如当局部变量与实例变量同名时)需要显式使用。 - 构造器中的调用:成员方法可以在构造器中被调用,但需要注意调用顺序和潜在的问题(如循环调用)。
- 重写(Override):在子类中,可以重写继承自父类的成员方法,以提供特定于子类的实现。
成员方法的声明和调用:
声明成员方法:
public class MyClass {
// 实例变量
int myVar = 0;
// 成员方法
public void myMethod() {
// 访问实例变量
System.out.println("实例变量的值: " + myVar);
// 调用另一个成员方法
anotherMethod();
}
// 另一个成员方法
public void anotherMethod() {
System.out.println("这是另一个成员方法");
}
}
调用成员方法:
public class Test {
public static void main(String[] args) {
// 创建MyClass的实例
MyClass obj = new MyClass();
// 通过实例调用成员方法
obj.myMethod(); // 输出实例变量的值和另一个成员方法的输出
}
}
注意事项:
- 在成员方法中,不能直接访问类的静态变量和静态方法,除非通过类名来引用它们。但是,静态方法可以访问类的实例变量和实例方法,但必须通过类的实例来调用这些实例成员。
- 成员方法可以有访问修饰符(如
public
、protected
、private
和默认(包)访问),这些修饰符决定了方法可以被哪些类访问。 - 成员方法可以有参数,也可以没有参数。它们还可以返回一个值(通过返回类型指定),或者声明为
void
类型,表示不返回任何值。 - 在Java中,每个对象都独立地拥有其字段的副本,但所有对象共享同一个类定义中的方法。这意味着,当你通过不同对象调用同一个方法时,虽然它们各自操作的是不同的字段副本,但它们执行的是相同的代码逻辑。
静态方法
在Java中,静态方法(也称为类方法)是属于类本身而不是类的任何特定实例的方法。这意味着你可以在不创建类的实例的情况下调用静态方法。静态方法使用static
关键字进行声明。
静态方法的特点:
- 非实例化调用:你可以直接使用类名来调用静态方法,而不需要创建类的实例。
- 访问静态变量:静态方法可以直接访问类的静态变量和其他静态方法。如果静态方法需要访问类的实例变量或实例方法,它必须通过类的实例来调用。
- 隐藏实例状态:静态方法通常用于执行不依赖于对象状态的操作。例如,工具类中的方法常常声明为静态的,因为它们的目的是执行一些与特定实例无关的任务。
- 内存分配:静态方法在程序启动时就被加载到内存中,而实例方法是在创建类的实例时才被加载。
静态方法的声明和调用:
声明静态方法:
public class MyClass {
public static void myStaticMethod() {
System.out.println("这是一个静态方法");
}
}
调用静态方法:
// 使用类名直接调用静态方法
MyClass.myStaticMethod();
// 假设创建了MyClass的实例,依然可以使用类名调用静态方法
MyClass obj = new MyClass();
obj.myStaticMethod(); // 这通常不推荐,因为它可能误导读者以为这是实例方法
注意事项:
-
静态方法不能直接访问类的实例变量或实例方法,除非通过类的实例来调用。
-
在静态上下文中(如静态方法中),不能使用
this
关键字,因为this
代表当前对象实例,而静态方法是属于类的,不是任何特定实例的。 -
静态方法不能被重写(override),但它们可以被隐藏(hide)。如果你在一个子类中声明了一个与父类中静态方法同名且同参数列表的静态方法,这不是重写父类方法,而是隐藏了父类的静态方法。这是因为静态方法是与类关联的,而不是与类的实例关联的。
-
静态方法常用于实现工具类中的方法,这些工具类提供的功能不依赖于类的任何实例状态。
3. 方法的实参和形参
在编程中,理解方法的实参(实际参数)和形参(形式参数)以及它们之间的传递方式是非常重要的。这涉及到如何在方法调用时传递数据,以及这些数据在方法内部是如何被处理的。主要的概念包括值传递(Pass by Value)和引用传递(Pass by Reference),但需要注意的是,Java中所有的对象引用都是按值传递的,这可能会导致一些混淆。
实参(Actual Parameters)
实参是方法调用时实际传递给方法的参数。它们是表达式,在方法调用时求值,并将结果传递给对应的形参。
形参(Formal Parameters)
形参是方法定义时列出的参数,它们用于接收方法调用时传递的实际参数的值。
值传递(Pass by Value)
在值传递中,方法接收的是调用者提供的参数值的副本。因此,在方法内部对形参所做的任何修改都不会影响到传递给它的实参。这适用于基本数据类型(如int、float、char等)的传递。
public class Test {
public static void main(String[] args) {
int num = 10;
changeValue(num);
System.out.println("num after change: " + num); // 输出10,因为num是按值传递的
}
public static void changeValue(int number) {
number = 20; // 修改的是number的副本,对num无影响
}
}
引用传递(Pass by Reference)
引用传递的概念通常与对象相关,但实际上在Java中并不真正存在引用传递,因为所有的对象引用都是按值传递的。这里的“值”是指对象引用的值(即对象的内存地址)。当对象作为参数传递给方法时,传递的是对象引用的副本,而不是对象本身。但是,由于这个副本仍然指向原始对象,因此通过该副本可以访问和修改原始对象的状态。
public class Test {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.value = 10;
changeRefValue(obj);
System.out.println("obj.value after change: " + obj.value); // 输出20,因为obj的引用是按值传递的,但这个值指向了同一个对象
}
public static void changeRefValue(MyClass object) {
object.value = 20; // 修改的是object引用的对象的状态
}
}
class MyClass {
int value;
}
在上面的例子中,虽然obj
的引用是按值传递的,但这个值(即对象的内存地址)在changeRefValue
方法中仍然指向了原始对象。因此,通过object
引用可以修改原始对象obj
的状态。
总结:
- Java中的基本数据类型是按值传递的。
- Java中的对象引用也是按值传递的,但这个“值”是对象的内存地址。因此,通过对象引用的副本可以访问和修改原始对象的状态。
- 需要注意的是,虽然有时会说Java支持“引用传递”,但这实际上是一种简化的说法,更准确的描述是“对象引用是按值传递的”。
4. JVM内存结构划分
JVM(Java Virtual Machine)内存结构是Java虚拟机运行时数据区的结构,主要包括以下几个部分:堆(Heap)、方法区(Method Area)、虚拟机栈(VM Stack)、程序计数器(Program Counter Register)和本地方法栈(Native Method Stack)。以下是对这些部分的详细划分和解释:
1. 堆(Heap)
- 定义与功能:堆是JVM中最大的一块内存区域,用于存储对象实例和数组。堆被所有线程共享,是垃圾收集器管理的主要区域,因此也被称为“GC堆”。
- 内存划分:堆可以细分为年轻代(Young Generation)和老年代(Old Generation)。在JDK 8及之前,年轻代还包含永久代(PermGen space),但在JDK 8及之后,永久代被元空间(Metaspace)所取代。年轻代通常又包括Eden区、From Survivor区(或称为S0区)和To Survivor区(或称为S1区),这三个区域的比例默认为8:1:1。
- 内存配置:堆的大小可以通过
-Xms
(初始堆大小)和-Xmx
(最大堆大小)两个参数来配置。年轻代与老年代的比例可以通过-XX:NewRatio
参数来调整,默认为2(即年轻代占1/3,老年代占2/3)。Eden区与Survivor区的比例可以通过-XX:SurvivorRatio
参数来调整,默认为8。
2. 方法区(Method Area)
- 定义与功能:方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它是线程共享的区域,在JDK 8之前被称为永久代(PermGen space),但在JDK 8及之后被元空间(Metaspace)所取代。
- 内存配置:元空间的大小可以通过
-XX:MetaspaceSize
(初始大小)和-XX:MaxMetaspaceSize
(最大大小)两个参数来配置。默认情况下,-XX:MaxMetaspaceSize
的值为-1,表示不限制元空间的大小。
3. 虚拟机栈(VM Stack)
- 定义与功能:虚拟机栈是线程私有的,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被执行时都会同时创建一个栈帧(Stack Frame),用于存储该方法的局部变量等信息。
- 异常处理:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出
StackOverflowError
异常;如果虚拟机栈可以动态扩展但无法申请到足够的内存,将抛出OutOfMemoryError
异常。
4. 程序计数器(Program Counter Register)
- 定义与功能:程序计数器是一块较小的内存空间,用于记录当前线程所执行的字节码的行号指示器。它是线程私有的,用于记录当前线程执行到哪一条指令。
- 特点:程序计数器是唯一一个在Java虚拟机规范中没有规定任何
OutOfMemoryError
情况的区域。
5. 本地方法栈(Native Method Stack)
- 定义与功能:本地方法栈与虚拟机栈类似,但它是为虚拟机使用到的Native方法服务。Native方法一般是用其他语言(如C、C++或汇编语言)编写的,并且被编译为基于本机硬件和操作系统的程序。
- 异常处理:与虚拟机栈一样,本地方法栈区域也会抛出
StackOverflowError
和OutOfMemoryError
异常。
综上所述,JVM内存结构划分主要包括堆、方法区、虚拟机栈、程序计数器和本地方法栈五个部分。这些部分共同协作,支持Java程序的运行和管理。
5. 方法的重载
方法重载(Overloading)是面向对象编程中一个概念,指同一个类中或者在父类和子类中有多个同名方法,但这些方法的参数类型、参数个数或者类型的顺序不同。方法重载可以让一个方法名支持多种使用方式。
在Java中,方法重载的规则如下:
- 方法名必须相同。
- 参数列表必须不同(参数类型、参数个数或者参数顺序)。
- 方法的返回类型可以相同也可以不同。
- 仅返回类型不同不足以区分两个重载方法。
下面是一个Java中方法重载的例子:
public class MethodOverloading {
// 方法重载:不同的参数类型
public void test(int value) {
System.out.println("Int parameter: " + value);
}
public void test(double value) {
System.out.println("Double parameter: " + value);
}
public void test(String value) {
System.out.println("String parameter: " + value);
}
// 方法重载:不同的参数个数
public void test(int value1, int value2) {
System.out.println("Int parameters: " + value1 + ", " + value2);
}
// 方法重载:不同的参数顺序
public void test(String value, int value2) {
System.out.println("String parameter: " + value + ", Int parameter: " + value2);
}
public static void main(String[] args) {
MethodOverloading overloading = new MethodOverloading();
overloading.test(10);
overloading.test(5.5);
overloading.test("Hello World!");
overloading.test(10, 20);
overloading.test("Hello ", 100);
}
}
在这个例子中,test
方法被重载了五次,每次使用不同的参数类型或参数个数。在 main
方法中,我们通过调用 test
方法并传递不同类型的参数来测试重载