初识对象
面向过程思想
- 步骤清晰简单,第一步做什么,第二部做什么...
- 面向过程适合处理一些较为简单的问题
面向对象思想
- 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索
- 面向对象适合处理复杂的问题,适合处理需要多人协作的问题
特征:封装、继承、多态
过程:找对象、建立对象、使用对象、维护对象的关系
本质:以类的方式组织代码,以对象的组织(封装)数据
应用:对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,需要使用面向对象来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。
类与对象的关系
类 是对某一类事物的共同描述,但并不能代表某一个具体事物,是 引用数据类型
对象 是现实世界中的具体事物
对象由对象属性和方法组成,它们共同定义了对象的特征和行为。
对象属性指的是对象所包含的数据,用于描述对象的状态和特征。
在Java中,类通过class关键字定义,对象通过new操作符创建
二者关系
类 是抽取了同类对象的共同属性(成员变量)和行为(成员方法)所定义的对象的"模板"
对象 是根据类的"模板"所创建的类的实例
创建与初始化
示例
Java
public class MyClass { //定义一个公共类MyClass
private int number; // 定义一个私有整数型实例变量number
public MyClass(int num) { // 定义一个公共构造方法
this.number = num; //使用this来引用当前对象的number实例变量,并将传入参数num的值赋给它
//构造方法 MyClass(int num) 被调用来初始化对象的 number 属性
}
public void displayNumber() {
System.out.println("Number: " + number);
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass(10); // 创建MyClass对象,并传递参数10给构造方法MyClass
obj.displayNumber(); // 调用displayNumber方法来显示number的值
}
}
- 创建对象
一旦类被定义,就可以在程序中通过使用 new 关键字来实例化该类,创建对象。
语法格式
Java
ClassName objectName = new ClassName();
//ClassName是 要创建实例对象的 类的名称,objectName是创建的实例对象的名称
- 内存分配
在使用 new 关键字创建对象时,Java 在内存中为对象分配空间。对象通常被存储在堆内存中,而引用变量(如上面的 obj
)则存储在栈内存中。栈内存中的引用变量指向堆内存中相应对象的地址。
- 构造方法的调用
在对象实例化的过程中,必须要调用构造方法(构造器)来完成对象的初始化工作,可以设置对象的初始状态或执行其他必要的操作。在上面的例子中,构造方法 MyClass(int num)
被调用来初始化对象的 number
属性。
拓展
在IntelliJ IDEA中,可以使用以下快捷键来快速生成构造方法:
- 按下
Alt
+Insert
打开Generate菜单 - 选择Constructor选项并按回车键
- 在弹出的窗口中选择需要包含的字段,然后点击"OK"(有参)或"NO"(无参)
构造器
主要作用
在创建一个对象时进行初始化操作。通常用于设置对象的初始状态,包括初始化对象的成员变量、分配内存或执行其他必要的初始化任务。
构造方法的语法格式
Java
public class ClassName {
public ClassName(parameters) { // 构造方法,类名即方法名
// 构造方法的实现,不写返回类型
}
}
默认构造方法
如果一个类没有定义任何构造方法,编译器会自动为该类生成一个默认构造方法。这个默认构造方法没有参数 、不执行任何操作 。当创建一个新的对象,且没有自定义构造方法时,Java虚拟机会自动调用这个默认构造方法(作用是为了方便程序员快速创建一个该类的对象,而不需要手动编写构造方法)
默认构造方法的语法
Java
public class ClassName {
public ClassName() { // 默认构造方法
// 不执行任何操作
}
}
带参数的构造方法
调用一个带参数的构造方法时,编译器会检查传入的参数类型是否与带参构造器的参数列表中指定的所需参数相匹配。
如果参数类型不匹配,编译器会尝试进行类型转换以匹配传入的参数与构造器参数类型。 如果无法进行有效的类型转换,将会导致编译错误。
例如,如果构造器的参数列表中指定一个 int 的参数,但调用构造器时传入了一个 String 类型的参数,那么编译器将会报错:无法将字符串转换为整数。
因此
- 在使用带参数的构造器时,确保传入的参数类型与带参构造器的参数列表中指定的所需参数相匹配。
- 如果在类中定义了带参数的构造器,编译器将不再自动生成无参构造器。当需要创建一个没有传入参数的对象时,必须显式地定义一个无参构造器以供调用(除非通过反射)
关键字 this
this 关键字是一个特殊的引用,指向当前实例。当在一个构造方法或方法中引用一个实例变量时,需要使用 this 关键字来区分实例变量和局部变量
语法格式
Java
this.实例变量 = 参数
语义:将参数值赋给类的实例变量,而不是方法内部的局部变量
在一个方法(如构造方法)内部,允许定义与 类变量 同名的局部变量(如此,在方法内部使用该变量名时将指向局部变量而不是类变量)。所以,如果直接使用变量名(如number = num;
),Java会认为这是在引用方法内部的局部变量,而不是类的实例变量
为了避免这种混淆,Java提供了this
关键字。当使用this.number = num;
时,这意味着声明:"我要将这个num值赋给类的实例变量number,而不是方法内部的局部变量number"
为了进一步说明,示例:
java
public class MyClass {
private int number;
public MyClass(int number) {
// ↓这里没有使用this关键字,Java会认为这在引用方法内部的局部变量
// number = num; // 这行会导致编译错误,因为此语句中number是方法内部的局部变量
this.number = num; // 正确的方式,将参数的值赋给实例变量
}
}
为对象属性赋值
为对象属性提供值通常可以通过以下几种方式实现:
- 创建属性时直接赋值
Java
public class Person {
private String name = "Jie";
private int age = 19;
}
- 在构造方法中赋值:在对象的构造函数中,可以为对象的属性提供初始值。
java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = "Jie";
this.age = 19;
}
}
- 调用构造方法赋值:创建对象时,调用构造方法接受两个参数来为对象的属性赋值
Java
Person Demo = new Person("Jie", 19);
- 使用setter方法 :如果不希望在构造函数中为所有属性赋值,或者需要在对象创建后更改属性的值,你可以使用setter方法。
2.1. 在创建属性的类中使用setter方法为属性赋值,示例:
Java
public class Person {
private String name;
private int age;
// Setter方法用于设置name属性的值
public void setName(String newName) {
this.name = newName;
}
// Setter方法用于设置age属性的值
public void setAge(int newAge) {
this.age = newAge;
}
}
//setName和setAge就是两个setter方法,它们分别用于设置name属性和age属性的值
2.2. 在其他类中使用setter方法为对象的属性赋值,示例:
Java
public class Main {
public static void main(String[] args) {
Person person1 = new Person();
// 使用setter方法为属性赋值
person1.setName("Jie");
person1.setAge(19);
}
}
内存分析
代码
Java
//定义将实例化的类
public class Pet {
public String name = "小黑"; //默认 null
public int age = 4; //默认 0
//无参构造
public Pet(String name, int age) {
this.age = age;
this.name = name;
}
public void shout(){
System.out.println("叫了一声");
}
}
Java
//定义测试类,创建调用对象
public class Application {
public static void main(String[] args) {
Pet dog = new Pet("旺财",3);
dog.shout();
}
}
内存图解
- 加载含main类的字节码文件:因为main() 函数是程序的唯一入口,因此,包含main函数的Application类的字节码文件会优先加载到方法区。
- main函数进栈
- 执行main中代码:开始执行main方法的第一行代码,从右向左执行,首先便遇到了new关键字,因为new关键字后面出现了Pet类,表示要实例化的是Pet类,而Pet类的字节码文件还没有加载到方法区,JVM 不认识Pet,因此下一步是将Pet类的字节码文件加载到方法区(假设Pet类成员方法在方法区中的地址值为0x0003)
- new关键字开始:Pet类的字节码文件加载到方法区之后,new关键字会在堆内存开辟空间给新的Pet类对象,假设Pet类对象在内存中的地址值为0x0001。
- 在堆中开辟空间:根据Pet类的字节码文件,在堆中分配的这块内存空间会被分成三部分,分别是属性值name,age,和Pet类的成员方法,Pet类的成员方法部分其实是成员方法在方法区中的地址值,将来如果对象调用成员方法,可以通过这个地址值找到方法区中的成员方法。
- 默认初始化:对 对象的属性值进行第一次初始化:默认初始化。对应于Pet类中,name是String类型默认值null;age是int类型默认值0。
- 显式初始化:可以在定义Pet类时,对name和age分别赋了初始值,进行第二次初始化:显式初始化。(如果没有在类中对属性赋初始值,则没有这一步)
- 构造器初始化:创建一个对象,需要构造器来进行构造器初始化,如果调用的是Pet类的带参构造器,那么要进行第三次初始化 : 构造器初始化。即将属性值更改为调用带参构造时传入的形参。
- 常量池:这里的字符串常量"旺财",在常量池中,有自己的地址,调用模式类似于成员方法。name属性这里其实保存的是"旺财"在常量池中的地址值
- new关键字结束:最后,把对象的地址值返回给dog引用就可以了
再创建一个对象Pet cat = new Pet();