类
类
CAMELLIA!!! note 目录
类
一、类的定义
在计算机编程中,类(Class)是一种抽象数据类型(ADT),它是面向对象编程(OOP)的基本概念之一。类是对现实世界中对象的抽象,它定义了对象的属性(成员变量)和行为(成员方法)。
类的定义通常包括以下几个要素:
类名(Class Name):类的名称用于标识该类,在代码中可以通过类名来引用该类。类名通常使用大驼峰命名法(Pascal Case)。
成员变量(Member Variables):也称为属性或字段(Fields),用于描述类的状态或特征。成员变量可以是各种数据类型(如整数、浮点数、字符串等),它们代表了对象的各种属性。在类的定义中,成员变量通常以变量名和数据类型的形式列出。
成员方法(Member Methods):也称为函数或操作(Methods),用于描述类的行为或功能。成员方法定义了对象可以执行的操作,它们可以操作对象的状态,并且可以被外部代码调用以执行特定的任务。在类的定义中,成员方法通常以方法名、参数列表和返回类型的形式列出。
构造方法(Constructor):是一种特殊类型的成员方法,用于在创建对象时初始化对象的状态。构造方法的名称与类名相同,并且通常没有返回类型。在Java等编程语言中,通过调用构造方法可以创建类的实例。
访问修饰符(Access Modifiers):用于控制类的成员对外部代码的可见性和访问权限。常见的访问修饰符包括public、protected、private等。
一个简单的类定义示例(使用Java语言)如下所示:
1 | public class MyClass { |
二、类对象的创建和使用
创建和使用类对象是面向对象编程中的基本操作,它们使我们能够使用类定义的属性和方法来操作对象。
以下是创建和使用类对象的一般步骤:
- 类定义:首先,我们需要定义一个类,其中包括类的属性(成员变量)和方法(成员方法)。
- 对象实例化:在程序中,通过使用类的构造方法来创建类的实例(对象)。构造方法会初始化对象的状态,并返回一个指向该对象的引用。
- 访问成员变量:一旦对象被创建,我们可以使用点操作符(
.
)来访问对象的成员变量,并为其赋值或获取值。 - 调用成员方法:同样,我们也可以使用点操作符来调用对象的成员方法,并向方法传递参数(如果需要)。
以下是一个简单的示例,演示了如何创建类对象并使用它:
1 | public class MyClass { |
三、JVM内存分析
1 | package com.camellia.oop01; |
1 | package com.camellia.oop01; |
从此图可以看出,开始将所有类的字节码存储到元空间当中,当对象被创建时就在堆内存中开辟一个空间,用于存储对象和实例变量等。然后通过引用实现对对象的一系列操作。
其中对象属性等的改变都发生在堆内存中,引用只不过保存了它的地址(这和C++中的指针很像)。
四、实例变量和实例方法的访问
- 实例变量要想访问,必须先new对象。通过引用来访问实例变量。
- 实例变量是不能通过类名直接访问的。
- 我们通常描述一个对象的行为动作时,不加static。 没有添加static的方法,被叫做:实例方法。(对象方法)
- 空指针异常:一个空引用访问实例相关的,都会出现空指针异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Pet {
String name; // 实例变量
// 出生日期
String birth;
// 性别
char sex;
// 方法:行为动作
// 吃
public void eat(){ // 实例方法
System.out.println("宠物在吃东西");
}
// 跑
public void run(){
System.out.println("宠物在跑步");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class PetTest02 {
public static void main(String[] args) {
// 创建宠物对象
Pet dog = new Pet();
// 给属性赋值
dog.name = "小黑";
dog.birth = "2012-10-11";
dog.sex = '雄';
// 读取属性的值
System.out.println("狗狗的名字:" + dog.name);
System.out.println("狗狗的生日:" + dog.birth);
System.out.println("狗狗的性别:" + dog.sex);
dog = null;
// 注意:引用一旦为null,表示引用不再指向对象了。但是通过引用访问name属性,编译可以通过。
// 运行时会出现异常:空指针异常。NullPointerException。这是一个非常著名的异常。
// 为什么会出现空指针异常?因为运行的时候会找真正的对象,如果对象不存在了,就会出现这个异常。
//System.out.println("狗狗的名字:" + dog.name);
// 会出现空指针异常。
dog.eat();
// 会出现空指针异常。
//dog.run();
}
}
//java.lang.NullPointerException如果没有任何引用指向对象,该对象最终会被当做垃圾被GC回收。
五、方法调用时传递参数
5.1、方法调用时传递基本数据类型
1 | package com.camellia.oop04; |
5.2、方法调用时传递引用数据类型
1 | package com.camellia.oop04; |
1 | package com.camellia.oop04; |
这个和基本数据类型原理相同。首先明确引用数据类型中应用存储的是User的地址,所以add(User u);实质上是将main中的引用u里存储的值(就是new User();的地址)复制一份给add方法。
* 六、封装
概念: 封装是面向对象编程中的一个重要概念,它指的是将数据和操作数据的方法捆绑在一起,并限制对数据的访问。
封装的目的是隐藏对象的内部细节,只向外界暴露必要的接口,以防止外部代码直接访问对象的内部状态,从而提高代码的安全性和可维护性。
如何实现封装:
数据隐藏(Data Hiding): 封装通过将对象的数据隐藏起来即属性私有化,只允许通过对象的方法来访问和修改数据,从而防止外部直接访问对象的内部状态。
访问控制(Access Control): 通常,封装会将对象的属性设置为私有(private),只允许通过公共(public)方法来访问和修改这些属性。
1 | package com.camellia.oop07; |
1 | package com.camellia.oop07; |
* 七、构造方法
7.1、构造方法的基本知识点
1、构造方法的作用
对象的创建: 构造方法通过调用完成对象的创建。当使用 new 关键字实例化一个对象时,构造方法被调用,对象在内存中被创建并分配空间。
对象的初始化: 构造方法用于给对象的所有属性赋值,即对象的初始化。它确保对象在创建后处于一个合适的状态,属性被赋予初始值,以便对象可以正常运行。
2、定义构造方法的方式
1 | [修饰符列表] 构造方法名(形参列表) { |
注意事项:
- 构造方法名必须和类名一致,以便编译器能够识别并与类关联。
- 构造方法不需要提供返回值类型,因为它的主要目的是创建对象,而不是返回值。
- 如果提供了返回值类型,则该方法不再是构造方法,而是普通方法,不能用于对象的创建。
3、构造方法怎么调用呢?
- 使用new运算符来调用。
- 语法:new 构造方法名(实参);
- 注意:构造方法最终执行结束之后,会自动将创建的对象的内存地址返回。但构造方法体中不需要提供“return 值;”这样的语句。
4、构造方法相关注意事项
- 在Java语言中,如果一个类没有显式定义构造方法,系统会默认提供一个无参数的构造方法。这个构造方法通常称为缺省构造器。
- 如果一个类显式定义了构造方法,系统则不再提供缺省构造器。因此,为了对象创建更加方便,建议手动编写一个无参数的构造方法。
- 在Java中,一个类可以定义多个构造方法,并且这些构造方法自动构成了方法的重载。这意味着可以根据不同的参数列表调用不同的构造方法来创建对象。
- 构造方法中给属性赋值是对象第一次创建时属性的初始值。然而,单独定义set方法给属性赋值的好处在于后期可以灵活地修改属性的值。这种方式允许在对象创建后,根据需要修改对象的属性,从而增加了对象的灵活性和可维护性。
*5、构造方法的执行原理
构造方法的执行包括两个重要的阶段:
- 第一阶段:对象的创建
- 第二阶段:对象的初始化
对象在什么时候创建的?
- 当使用
new
关键字实例化一个对象时,在堆内存中直接开辟空间。这个过程中,会给对象的所有属性赋默认值,完成对象的创建。这一过程发生在构造方法体执行之前。
- 当使用
对象初始化在什么时候完成的?
- 构造方法体开始执行时,标志着对象的初始化过程开始。在构造方法体中,可以对对象的属性进行赋值等初始化操作。构造方法体执行完毕,表示对象初始化完毕。此时,对象处于可用状态,可以被程序进一步操作和调用。
6、构造代码块
语法格式:
- 构造代码块的语法格式为一对大括号
{}
,没有参数列表。
- 构造代码块的语法格式为一对大括号
执行时机及次数:
- 每次在使用
new
关键字创建对象时,构造代码块都会被执行。 - **构造代码块是在构造方法执行之前执行的**,因此在对象的初始化过程中,构造代码块是首先被执行的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35public class Example {
private int x;
private int y;
// 构造代码块
{
System.out.println("构造代码块被执行");
x = 5;
y = 10;
}
// 构造方法
public Example() {
System.out.println("构造方法被调用");
}
// 获取x的值
public int getX() {
return x;
}
// 获取y的值
public int getY() {
return y;
}
public static void main(String[] args) {
// 创建对象
Example obj = new Example();
// 输出属性值
System.out.println("x 的值为:" + obj.getX());
System.out.println("y 的值为:" + obj.getY());
}
}
- 每次在使用
[!NOTE]
注意:和静态代码块做区分。
7、构造代码块的作用
构造代码块可以用于将对象初始化时共享的代码抽取出来,实现代码的复用。具体而言:
- 如果所有的构造方法在最开始的时候有相同的一部分代码,可以将这部分代码放入构造代码块中。
- 构造代码块会在每次对象创建时都执行,确保共享的代码被执行,并且避免了代码重复。
这样,通过构造代码块,可以提高代码的可维护性和可读性,减少代码冗余,提高代码复用性。
*八、this关键字
this 本质上是一个引用。this 中保存的是当前对象的内存地址。
在Java中,this
是一个关键字,用于引用当前对象的实例。它通常用于区分实例变量和方法参数之间的命名冲突,或者在一个类的方法内部调用同一个类的另一个方法。下面详细解释 this
的几个常见用途:
区分实例变量和方法参数:当方法参数的名称与实例变量的名称相同时,使用
this
来引用当前对象的实例变量。这样可以明确指示要访问的是实例变量而不是方法参数。1
2
3
4
5
6
7public class MyClass {
private int value;
public void setValue(int value) { //这个形参与属性value相同,若不加this则根据Java中的就近原则,方法中的value都是形参value。
this.value = value; // 使用 this 引用实例变量
}
}这里
this.value
指的是当前对象的value
实例变量,而value
是方法的参数。在构造器中调用另一个构造器:可以使用
this
调用同一个类的另一个构造器,且只能出现在第一行。这种方法通常被称为构造器重载。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class MyClass {
private int value;
private String name;
public MyClass() {
//new MyClass(10,"giaogiao"); 这么会创建一个新对象
this(10,"giaogiao");// 调用另一个构造器
/*这个的目的是什么?
当要求类在初始化时,给它赋指定的默认值。
EG:this.value=10;
this.name="giaogiao";
这段代码其实是重复的。而通过this(10,"giaogiao");获取当前对象,调用MyClass(int value,String name)构造器以简化开发。
*/
}
public MyClass(int value,String name) {
this.value = value;
this.name=name;
}
}在这个例子中,无参构造器调用了带参构造器,以避免重复代码。
传递当前对象的引用:可以将当前对象的引用传递给其他方法,这在某些情况下很有用。
1 | package com.camellia.oop6; |
1 | package com.camellia.oop6; |
在Java中,当对象调用自己的方法时,方法体内的 this 关键字会引用该对象的实例。
在方法被调用时,Java虚拟机会隐式地将当前对象的引用传递给方法,以便方法能够访问对象的成员变量和方法。
因此,在普通方法中通过 this 关键字引用的就是调用该方法的当前对象。
九、static关键字
在Java中,使用static关键字声明的成员(变量、方法、代码块)是类级别的,而不是与类的每个实例相关联的。
因此,它们可以通过类名直接访问,而无需创建类的实例。
9.1、静态变量存储图
1、没使用静态变量时的存储图
1 | package com.camellia.oop7; |
1 | package com.camellia.oop7; |
2、使用静态变量时的存储图
1 | package com.camellia.oop8; |
1 | package com.camellia.oop8; |
9.2、Java中静态变量和方法的访问,以及静态变量不能使用this关键字
- 静态变量和方法建议使用
类名.
调用。虽然用引用.
也可以,但是实质还是通过类来调用,而且这样容易和实例变量和方法的访问相混淆。 - 静态方法不能使用 this 关键字是因为 this 关键字代表当前对象的实例,而静态方法是与类相关联的,不依赖于任何特定的实例。所以无法直接访问实例变量和方法。
9.3、静态代码块
静态代码块是使用 static
关键字声明的代码块,**它在类被加载时执行,并且只执行一次。**
静态代码块通常用于在类加载时进行初始化操作,例如初始化静态变量或执行静态方法。它们的执行顺序是在类加载时按照代码顺序执行。
[!NOTE]
注意:和构造代码块做区分。
静态代码块的特点包括:
使用
static
关键字声明:静态代码块使用static
关键字进行声明,以标识它们是与类相关联的,而不是与类的实例相关联的。在类加载时执行:静态代码块在类被加载时执行,并且只执行一次。类的加载是指当 JVM 第一次加载类时发生的操作,通常在首次创建类的实例之前。
仅执行一次:静态代码块只会在类加载时执行一次,即使没有创建类的实例也会执行。
示例:
1 | public class StaticTest01 { |
十、无参构造方法、构造代码块和静态代码块的执行顺序
- 静态代码块
- 构造代码块
- 无参构造方法
1 |
|