方法
一、概述
方法,也称函数,如果想要重复一段或者多段代码块的使用,可以将这些代码封装成一个方法,方法具体表现为某种行为,使用方法可以提高代码的复用性。
二、方法的种类
1.构造方法
构造方法的概念
1.构造器用于创建对象,方法名必须与类名相同,不需要定义返回值类型。
2.构造方法分为有参构造和无参构造,有参构造带参数,无参构造不带参数。
3.一个类默认会自带一个无参构造方法,如果手动定义了有参构造,则会覆盖默认的无参构造。
Text1 t1 = new Text1();//引用无参构造函数创建对象
Text1 t2 = new Text1("张三",18);//引用有参构造函数创建对象
2.静态方法
2.1. 关键字static修饰的方法称为静态方法,也叫类方法,静态方法不属于对象,而是属于类,随着类的加载而加载,优先于构造方法执行。
**2.2.**静态方法可以直接使用类名.方法直接调用,如果静态方法位于本类中可以直接省略类名直接调用
2.3. 在静态方法中,可以访问静态方法,可以引用类变量(被static修饰的变量),不能访问非静态方法与变量,不能使用super和this关键字
3.非静态方法
3.1是不含有static关键字修饰的普通方法,又称为实例方法,成员方法。属于对象的,不属于类的。(成员属性,成员方法是属于对象的,必须通过new关键字创建对象后,再通过对象调用)。
3.2在非静态方法中,即可以调用非静态方法,也可以调用静态方法,可以引用类变量和成员变量,可以使用关键字super和this
public class Text3 {
int age = 18;//成员变量
static String name = "迪丽热巴";//类变量
public static void main(String[] args) {
Text3 t1 = new Text3();
method();//本类中调用静态方法,直接省略类名
t1.method1();//调用成员方法
}
//静态方法
static void method() {
System.out.println("静态方法");
//访问属性
System.out.println(name);
//System.out.println(age);//报错,静态不能访问非静态
//t1.method();//报错
}
//非静态方法,也叫成员方法
void method1() {
System.out.println("非静态方法1");
method();//既可以调用静态方法
new Text3().method2();//又可以调用非静态方法
System.out.println(name);//既可以访问静态属性
System.out.println(age);//又可以访问非静态属性
}
void method2() {
System.out.println("非静态方法2");
}
}
这段代码定义了一个名为 Text3
的类,并在其中展示了静态方法和非静态方法的使用。以下是代码的详细解释:
-
成员变量和类变量:
int age = 18;
是一个非静态成员变量(实例变量),每个Text3
类的实例都有自己的age
值。static String name = "迪丽热巴";
是一个静态类变量(类变量),所有Text3
类的实例共享这个name
变量。
-
main
方法:- 创建了
Text3
类的一个实例t1
。 - 调用静态方法
method()
,不需要对象实例,可以直接通过类名调用。 - 调用非静态方法
method1()
,需要通过对象实例t1
调用。
- 创建了
-
静态方法
method
:- 静态方法可以被类名直接调用,不需要创建类的实例。
- 在静态方法中,可以访问静态变量,如
name
,但不能直接访问非静态变量,如age
。 - 静态方法不能直接访问非静态方法,因为非静态方法依赖于类的实例。
-
非静态方法
method1
:- 非静态方法需要通过对象实例调用。
- 在非静态方法中,可以访问静态变量和非静态变量,也可以调用静态方法和非静态方法。
method1
方法中调用了静态方法method
和另一个非静态方法method2
。
-
非静态方法
method2
:- 这个方法简单地打印出 “非静态方法2”。
输出:
当运行这段代码时,输出将如下所示:
静态方法
迪丽热巴
非静态方法1
非静态方法2
迪丽热巴
18
解释:
- 首先,调用
method()
,打印 “静态方法” 和类变量name
的值。 - 然后,调用
method1()
,打印 “非静态方法1”,接着创建一个新的Text3
对象并调用method2()
,打印 “非静态方法2”。 - 在
method1()
中,打印静态变量name
和非静态变量age
的值。
注意:
- 静态方法属于类本身,而非静态方法属于类的实例。
- 静态方法中不能直接访问非静态变量或调用非静态方法,因为它们需要一个对象实例。
- 非静态方法可以访问类的任何变量和调用任何方法,无论是静态的还是非静态的。
4.抽象方法
关键字abstract修饰的方法称为抽象方法,抽象方法必须定义在抽象类(abstract修饰的类称为抽象类)中,抽象类中既有抽象方法,也有非抽象方法,但是抽象方法必须定义在抽象类中,并且抽象方法没有方法体
public class Text3 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.cry();
}
}
// 定义一个抽象类
abstract class Animal {
String name;
int age;
// 抽象方法
public abstract void cry(); // 不确定动物怎么叫的。定义成抽象方法,来解决父类方法的不确定性。
//抽象方法在父类中不能实现,所以没有方法体。但在后续在继承时,要具体实现此方法。
}
// 抽象类可以被继承
// 当继承的父类是抽象类时,需要将抽象类中的所有抽象方法全部实现。
class Cat extends Animal {
// 实现父类的cry抽象方法
public void cry() {
System.out.println("猫叫:苗苗苗");
}
}
注:静态方法和非静态方法的区别(生命周期不同)
静态方法的生命周期跟相应的类一样长,静态方法和静态变量会随着类的定义而被分配和装载入内存中。一直到线程结束,静态属性和方法才会被销毁。(也就是静态方法属于类)
非静态方法的生命周期和类的实例化对象一样长,只有当类实例化了一个对象,非静态方法才会被创建,而当这个对象被销毁时,非静态方法也马上被销毁。(也就是非静态方法属于对象)
三、下面是构造方法与普通方法的一些关键区别:
构造方法的特点:
- 名称:构造方法的名称必须与类名完全相同,而普通方法可以自由命名。
- 返回类型:构造方法没有返回类型声明,甚至没有
void
类型,而普通方法可以有不同的返回类型,包括void
。 - 调用方式:构造方法是在创建对象时自动调用的,通过
new
关键字来创建对象,而普通方法需要通过已创建的对象或类名(如果是静态方法)显式调用。 - 初始化职责:构造方法主要用于初始化对象的状态,如分配内存、设置默认值等。普通方法则用于实现类的具体功能,执行特定的任务。
- 重载:构造方法可以被重载,即一个类可以定义多个构造方法,通过不同的参数列表来实现构造方法的重载。
- 修饰符:构造方法不能被
static
、final
、synchronized
、abstract
和native
修饰,而普通方法可以使用这些修饰符。 - 默认构造方法:如果一个类没有显式定义构造方法,Java编译器会自动为该类提供一个无参构造方法。但如果一个类已经定义了构造方法,则Java编译器不会为其再生成默认的无参构造方法。
普通方法的特点:
- 名称:普通方法的名称可以自由定义,只要符合编程语言的规则。
- 返回类型:普通方法可以有各种返回类型,包括
void
(表示没有返回值)。 - 主要用途:普通方法用于执行特定的功能或计算,可以修改对象状态或者返回计算结果。
- 调用方式:普通方法需要通过已创建的对象或类名(如果是静态方法)显式调用。
示例
假设有一个名为Person
的类,下面是一个构造方法和普通方法的例子:
public class Person {
private String name;
private int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 普通方法
public void introduce() {
System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
}
}
在这个例子中,Person
类有两个属性:name
和age
。构造方法接受这两个参数,并将它们赋值给相应的成员变量。introduce
方法是一个普通方法,它输出一个字符串介绍这个人的信息。
结论
构造方法与普通方法的主要区别在于它们的用途和调用方式。构造方法专注于对象的初始化,并在对象创建时自动调用,而普通方法则用于执行业务逻辑,并需要显式调用。理解这些区别有助于更有效地编写和维护面向对象的程序。
四、构造方法和普通方法分别可以调用哪种方法
构造方法和普通方法在Java中都可以调用其他的方法,但它们之间的调用方式有一些限制和规范。
构造方法可以调用的方法:
-
其他构造方法:构造方法内部可以调用同一个类中的其他构造方法,这种调用必须发生在构造方法的第一行,并且使用
this
关键字加上参数列表来指定要调用的构造方法。这种调用方式通常用于代码复用。public class Example { public Example() { // 调用带有参数的构造方法 this("default"); } public Example(String value) { // 初始化逻辑 } }
-
普通方法:构造方法也可以调用类中的任何普通方法,但需要注意的是,由于构造方法在对象创建时调用,因此在调用普通方法时,对象的状态可能尚未完全初始化。
public class Example { private int value; public Example() { setValue(10); printValue(); } private void setValue(int newValue) { value = newValue; } public void printValue() { System.out.println(value); } }
普通方法可以调用的方法:
-
其他普通方法:普通方法可以调用类内的其他普通方法,这种方式非常灵活,可以根据需要在任何地方调用。
public class Example { public void methodA() { methodB(); } private void methodB() { // 实现细节 } }
-
构造方法:普通方法不能直接调用构造方法,因为构造方法是在对象创建时由JVM自动调用的,而普通方法是在对象创建之后才调用的。
// 错误示范: public class Example { public void someMethod() { new Example(); // 这实际上是在创建一个新的对象 } }
特殊情况:
- 静态方法:不论是构造方法还是普通方法,都可以调用类中的静态方法,因为静态方法并不依赖于任何特定对象的存在。
public class Example { public static void staticMethod() { // 实现细节 } public Example() { staticMethod(); } public void instanceMethod() { staticMethod(); } }
总之,构造方法主要用于初始化对象,而普通方法则用于实现对象的行为。构造方法可以调用其他构造方法或普通方法来辅助初始化过程,而普通方法则主要用于实现业务逻辑,并且可以调用类中的其他普通方法或静态方法。需要注意的是,构造方法不应直接被普通方法调用,因为构造方法是在对象创建时由系统自动调用的。
五、方法的声明
1.组成
访问权限符 返回值 方法名(参数列表){
方法体
}
public viod method(){
System.out.println(“Hello World!!!”);
}
2.访问权限符
表示该方法被访问的权限,主要有以下几种
public
(1)定义:public是公共的,被public所修饰的成员可以在任何类中都能被访问到。
(2)作用域:public能来修饰类,在一个java源文件中只能有一个类被声明为public,而且一旦有一个类为public,那这个java源文件的文件名就必须要和这个被public所修饰的类的类名相同,否则编译不能通过。public用来修饰类中成员(变量和方法),被public所修饰的成员可以在任何类中都能被访问到。通过操作该类的对象能随意访问public成员。public在类的继承上的体现,被public所修饰的成员能被所有的子类继承下来。
protected
(1)定义:protected是受保护的,受到该类所在的包所保护。
(2)作用域:被protected所修饰的成员会被位于同一package中的所有类访问到。同时,被protected所修饰的成员也能被该类的所有子类继承下来。(注意:这里是指同一个package或者不同的package中的子类都能访问)
default
(1)定义:default是默认,缺省的,即在成员的前面不写任何的访问修饰符的时候,默认就是友好的。所谓友好的,是对同一package的类友好。
(2)作用域:同一package中的所有类都能访问。被friendly所修饰的成员只能被该类所在同一个package中的子类所继承下来。(也就是说只有在同一个package中的子类才能访问到父类中friendly修饰的成员)
private
(1)定义:private是私有的,即只能在当前类中被访问到,它的作用域最小。
(2)作用域:private可以修饰数据成员,构造方法,方法成员,不能修饰类(此处指外部类,不考虑内部类)。被private修饰的成员,只能在定义它们的类中使用,在其他类中不能调用。
以下是四种主要的访问修饰符及其对应的访问范围:
访问修饰符 | 描述 | 示例代码 |
---|---|---|
public | 最宽松的访问级别,允许任何类访问该成员。 | public class MyClass { public void method() {} } |
protected | 成员可以被同一个包内的类访问,也可以被不同包中的子类访问。 | protected class MyClass { protected void method() {} } |
默认(无修饰符) | 成员仅能被同一个包内的类访问,这种权限也称为包级权限。 | class MyClass { void method() {} } |
private | 最严格的访问级别,成员只能被其所在的类访问。 | private class MyClass { private void method() {} } |
访问修饰符 | 同一类中 | 同一包 | 不同包中的非子类 | 不同包中的子类 | 同包的子类 |
---|---|---|---|---|---|
public | 是 | 是 | 是 | 是 | 是 |
protected | 是 | 是 | 否 | 是 | 是 |
默认 | 是 | 是 | 否 | 否 | 是 |
private | 是 | 否 | 否 | 否 | 否 |
这里,“同一文件”意味着成员可以被定义在同一源文件中的其他类访问;“同一包”指的是成员可以被位于同一个包中的类访问;“不同包中的非子类”是指那些不在同一包内的类,且不是继承关系;而“不同包中的子类”则是指位于不同包内的类,但是子类继承了拥有成员的那个类。
请注意,这里提到的“同一文件”实际上并不是标准的术语,但它有助于理解在同一个源文件中定义的内部类或嵌套类如何访问外部类的成员。对于private
成员来说,即使在同一文件中,也只有包含该成员的类能够访问到它。
3.返回值:
表示方法(函数)要返回的数据类型,int ,double,String等等·,void表示无返回值
4.方法名:
方法的名称,避免使用到java关键字,并且首字母小写,命名时使用驼峰命名法
骆驼式命名法(Camel-Case)又称驼峰式命名法,是电脑程式编写时的一套命名规则(惯例)。正如它的名称CamelCase所表示的那样,是指混合使用大小写字母来构成变量和函数的名字。程序员们为了自己的代码能更容易的在同行之间交流,所以多采取统一的可读性比较好的命名方式。
5.参数列表:
方法声明时可以定义参数列表,调用时需要传入实参
5.1 形参
也叫形式参数,方法声明时候的参数称为形参,如add(int a)
5.2 实参
也叫实际参数,方法调用时传入的参数,如add(1)
public class Text {
public static void main(String[] args) {
method("字符参数");//括号中为实参
}
private static void method(String str) {//括号中为形参
System.out.println(str);//将传入的参数打印输出
}
}
5.3 可变参数
1.当参数的个数不定时,可以使用可变参数
2.可变参数的写法:int…a 前面为参数类型,中间加三个点,后面为参数名
3.使用规则:
-
可变参数只能位于参数列表的最后一个
-
可变参数有且只有一个
4.可变参数的原理为数组,传入参数时,根据传入的参数自动生成匹配的数组类型,数组长度等于传入的参数个数,数组元素为传入的实参
public class Text2 {
public static void main(String[] args) {
int sum = addSum(1,2,3,4,5,6,7,8,9);
System.out.println(sum);
}
private static int addSum(int ...a) {
int add = 0;
//将数组元素(即传入的参数)求和
for (int i = 0; i < a.length; i++) {
add += a[i];
}
return add;
}
}
六、方法的声明与调用
public class Text3 {
//定义4种方法
private static void method1() {
System.out.println("无参无返回值的方法");
}
private static int method2() {
System.out.println("无参有返回值的方法");
return 1;
}
private static int method3(String str) {
System.out.println("有参有返回值的方法");
return 1;
}
private static void method4(String str) {
System.out.println("有参无返回值的方法");
}
public static void main(String[] args) {
//调用方法
method1();
method2();
method3("我");
method4("你");
}
}
对象的创建与构造方法的应用
1.对象是通过构造函数创建的,默认通过调用无参构造方法创建。
2.在Java中,创建对象有两种方式:先创建对象再赋值和边创建边赋值,后者通过有参构造方法实现。
3.有参构造方法可以通过形参接收外部传入的值,并赋给对象的属性。 4.在构造方法中使用this关键字可以引用当前对象的属性或调用其他构造方法。
this关键字的用法
1.this关键字用于指代当前类的实例化对象,可以用于访问对象的属性和方法。
2.在构造方法中,this可以用来调用其他构造方法,但只能在构造方法中使用。
3.this还可以用于区分成员变量和形式参数,避免命名冲突。
构造函数中的错误写法及解决方式
1.在构造函数中直接调用无参构造函数会导致死循环,引发内存溢出错误。
2.正确的做法是根据需要选择调用有参或无参构造函数,避免无限递归。
七、JVM内存结构划分
在JVM(Java Virtual Machine)的内存结构中,堆(Heap)和栈(Stack,通常是指虚拟机栈VM Stack和本地方法栈Native Method Stack的统称,但在这里我们主要讨论虚拟机栈)是两个非常重要的区域,它们在功能、存储内容、生命周期以及管理方式上都有显著的不同。
堆(Heap)
-
描述:堆是JVM中用于存储对象实例和数组的内存区域。它是JVM管理的最大一块内存区域,也是垃圾收集器管理的主要区域。
-
特性:
- 堆是线程共享的,多个线程可以访问同一个堆中的对象。
- 堆的大小在JVM启动时确定,并可以通过JVM启动参数(如-Xms和-Xmx)进行调整。
- 堆被划分为新生代(Young Generation)和老年代(Old Generation),新生代又被进一步划分为Eden区、From Survivor区(S0)和To Survivor区(S1)。
- 堆内存中的对象由垃圾收集器进行回收,当堆内存不足时,会触发垃圾收集。
栈(Stack,主要指虚拟机栈VM Stack)
-
描述:栈是每个线程私有的内存区域,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。当一个方法被调用时,JVM会在这个线程的栈中创建一个栈帧(Stack Frame)来存储这个方法调用的相关信息。
-
特性:
- 栈是线程私有的,每个线程都有自己独立的栈空间。
- 栈的大小在JVM启动时确定,但栈的大小限制通常不是由JVM直接设置的,而是由操作系统和JVM的具体实现决定的。
- 栈是后进先出(LIFO)的数据结构,当一个方法被调用时,它的栈帧被压入栈顶;当方法执行完成后,它的栈帧被弹出栈顶。
- 如果栈的深度超过了JVM所允许的最大深度,就会抛出
StackOverflowError
异常;如果栈在尝试扩展时无法申请到足够的内存空间,就会抛出OutOfMemoryError
异常。
堆和栈在JVM内存结构中扮演着不同的角色:
- 堆用于存储对象实例和数组,是垃圾收集器管理的主要区域,是线程共享的。
- 栈用于存储局部变量表、操作数栈、动态链接、方法出口等信息,是线程私有的,用于支持方法的调用和执行。
理解堆和栈的区别对于深入理解JVM的内存管理和优化Java程序的性能至关重要。
八、方法的重载
方法重载
方法重载指的是在同一个类中定义多个具有相同名称但参数列表不同的方法。这里的“参数列表”指的是方法参数的个数、类型以及参数的顺序。方法的返回类型和访问修饰符并不能作为判断方法是否重载的标准 。
方法重载的条件
要构成方法重载,必须满足以下条件:
- 方法名相同。
- 参数列表不同,即参数的个数、类型或顺序至少有一项不同。
- 方法的返回类型可以相同也可以不同,但这不是判断重载的关键条件 。
方法重载的目的
方法重载的目的在于提高代码的可读性和可用性,同时也增强了代码的灵活性。通过使用相同的方法名,可以使得代码更加清晰易懂,并且易于维护 。
方法重载的示例
以下是几个方法重载的示例:
public class MathUtil {
// 重载方法disp,分别接受char和int类型的参数
public static void disp(char ch) {
System.out.println("字符:" + ch);
}
public static void disp(int num) {
System.out.println("整数:" + num);
}
// 重载方法add,处理不同类型的加法运算
public static int add(int a, int b) {
return a + b;
}
public static double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
MathUtil.disp('A'); // 调用disp(char ch)
MathUtil.disp(1); // 调用disp(int num)
int sumInt = MathUtil.add(2, 3); // 调用add(int a, int b)
double sumDouble = MathUtil.add(2.5, 3.5); // 调用add(double a, double b)
System.out.println("整数相加的结果是:" + sumInt);
System.out.println("浮点数相加的结果是:" + sumDouble);
}
}
在这个示例中,disp
和 add
方法都进行了重载,通过不同的参数类型或个数实现了不同的功能。当调用这些方法时,Java编译器会根据传递的实际参数来选择合适的方法执行 。
方法重载的注意事项
虽然方法重载提供了便利,但也需要注意以下几点:
- 方法的返回类型不能作为重载的依据。
- 仅仅是返回类型不同不足以构成方法的重载。
- 参数列表的差异决定了方法的重载。
结论
方法重载是Java语言中一种重要的特性,它允许程序员在同一个类中定义多个同名但参数列表不同的方法,从而提高代码的可读性和灵活性。正确使用方法重载可以帮助编写更清晰、更易于维护的代码。
九、方法重写
方法重写的基本概念
- 定义:当子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表时,我们说子类中的方法覆盖(overriding)了父类中的方法。
- 目标:子类可以改变继承自父类的方法的行为,以适应子类特有的需求。
- 好处:提高了代码的灵活性和可扩展性,同时也支持了多态性。
方法重写的规则
- 参数列表必须完全与被重写方法的相同;
- 返回类型必须完全与被重写方法的返回类型相同;
- 访问权限不能比父类中被重写的方法的访问权限更低;
- 声明为final的方法不能被重写;
- 声明为static的方法不能被重写,但是能够被再次声明;
- 构造方法不能被重写;
- 子类和父类在同一个包中,那么子类可以重写父类所有除了声明为private和final的方法;
- 如果不能继承一个方法,则不能重写这个方;
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法;
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重 写方法声明的更广泛的强制性异常,反之则可以。
注意事项
- 私有方法:私有方法不能被重写(父类私有成员子类是不能继承的)。
- 静态方法:父类中的静态方法不能被重写,如果子类中有相同的方法,它并不是重写了父类的方法,而是将父类同名的方法隐藏了起来。
- 使用
@Override
注解:建议在子类中重写父类的方法时加上@Override
注解,这可以提高代码的可读性,并且可以在编译阶段检查重写是否正确。
示例代码
以下是一个简单的示例,展示了方法重写的基本用法:
class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
// 重写父类的方法
@Override
public void eat() {
System.out.println("Dog is eating");
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 输出 "Dog is eating"
// 多态性演示
Animal myDog = new Dog();
myDog.eat(); // 输出 "Dog is eating"
}
}
在这个例子中,Dog
类继承了Animal
类,并重写了eat()
方法。当我们通过Dog
类型的引用调用eat()
方法时,输出的是"Dog is eating",这体现了方法重写的多态性特征。
方法重写是Java中实现多态的一种方式,通过这种方法,可以使程序更加灵活,并且能够有效地扩展父类的功能,而不改变父类的实现。
注、方法重写与方法重载区别
- 方法重载:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
- 方法重写:子类可以有一个与父类签名完全相同的方法,即子类可以重写父类的方法。
十、在Java中,什么时候用重载,什么时候用重写?
(1)重载是多态的集中体现,在类中,要以统一的方式处理不同类型数据的时候,可以用重载。
(2)重写的使用是建立在继承关系上的,子类在继承父类的基础上,增加新的功能,可以用重写。
(3)简单总结:
1.重载是多样性,重写是增强剂;
2.目的是提高程序的多样性和健壮性,以适配不同场景使用时,使用重载进行扩展;
3.目的是在不修改原方法及源代码的基础上对方法进行扩展或增强时,使用重写;