在上一个小章节介绍了有关“类”和对象的初步认知,最主要的关键是:什么是面向对象和面向过程?如何定义一个类?怎么创建一个对象?如何访问对象的属性?
目录
一、对象的初始化
1、如何初始化对象
通过基本的学习,我们知道在Java中方法内定义的局部变量必须要进行初始化,否则会出现编译错误!
public class TestDemo {
public static void main(String[] args) {
int a; //变量a没有进行初始化
System.out.println(a);
}
}
//会报出编译错误:Variable 'a' might not have been initialized
通过上述代码知道,局部变量一定要初始化,只要进行初始化就不会报出未初始化的错误!那定义一个类内的成员变量是否也需要初始化呢?
(1)我们在实例化一个对象后,可以通过引用变量名访问对象的属性来进行初始化。 以下面代码为例:
class Person{
public String name;
public int age;
public String sex;
public void show(){
System.out.println(this.name+"的年龄为"+this.age);
}
}
public class TestDemo {
public static void main(String[] args) {
Person person=new Person(); //实例化对象
//初始化
person.name="李华";
person.age=13;
person.sex="boy";
//打印
person.show();
}
}
//运行结果:李华的年龄为13
(2)我们也可以在类中定义一个成员方法,通过调用这个成员方法将实参传过去进行初始化。
class Person{
public String name;
public int age;
public String sex;
public void set(String name,int age,String sex){
this.name=name;
this.age=age;
this.sex=sex;
}
public void show(){
System.out.println(this.name+"的年龄为"+this.age);
}
}
public class TestDemo {
public static void main(String[] args) {
Person person=new Person();
person.set("李华",13,"boy");
person.show();
}
}
//运行结果:李华的年龄为13
🤔但是如果我们不对实例化出来一个对象进行初始化,直接调用show()方法会发什么事,事能正常运行还是会像没初始化局部变量一样报编译错误呢?
class Person{
public String name;
public int age;
public String sex;
public void show(){
System.out.println(this.name+"的年龄为"+this.age);
}
}
public class TestDemo {
public static void main(String[] args) {
Person person=new Person();
person.show();
}
}
运行结果:
通过上述代码,发现即使成员变量不进行初始化,代码还是可以正常运行,只不过打印出来的是对应基本数据类型的默认值。
对于类中成员变量的初始化可以分为两种,分别是就地初始化和默认初始化。
2、就地初始化
当我们在类中声明成员变量时,直接给成员变量赋值,就称其为“就地初始化”!
class Person{
public String name="LiHua";
public int age=13;
public String sex="boy";
public void show(){
System.out.println(this.name+"的年龄为"+this.age+"性别为:"+this.sex);
}
}
public class TestDemo {
public static void main(String[] args) {
Person person=new Person();
person.show();
}
}
//运行结果:LiHua的年龄为13性别为:boy
【注意】:代码编译完成后,编译器会将所有给成员初始化的这些语句添加到各个构造函数中.
3、默认初始化
当我们声明成员变量后,后续并未对其进行初始化,或实例化对象后的赋值,该成员变量会被编译器默认为0值。
当实例化一个对象时,在Java虚拟机层面中,会为这个对象在堆上分配内存空间。对象空间被申请好之后,对象中包含的成员已经设置好了初始值。如:
数据类型 默认值 int 0 short 0 long 0L float 0.0f double 0.0 boolean false
char '\u0000' byte 0 class Person{ public String name; public int age; public String sex; public void show(){ System.out.println(this.name+"的年龄为"+this.age+"性别为:"+this.sex); } } public class TestDemo { public static void main(String[] args) { Person person=new Person(); person.show(); } }
![]()
二、构造方法
1、什么是构造方法?
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且 在整个对象的生命周期内只调用一次。
public class Date { public int year; public int month; public int day; // 构造方法: // 名字与类名相同,没有返回值类型,设置为void也不行 // 一般情况下使用public修饰 // 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次 public Date(int year, int month, int day){ this.year = year; this.month = month; this.day = day; System.out.println("Date(int,int,int)方法被调用了"); } public void printDate(){ System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { // 此处创建了一个Date类型的对象,并没有显式调用构造方法 Date d = new Date(2022,4,5); // 输出Date(int,int,int)方法被调用了 d.printDate(); // 2022-4-5 } }
【注意】:构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间。
在实例化对象时,编译器自动调用new关键词后面的方法Date(),也就构造方法,但类中的成员变量总是得先进行初始化后才进行构造方法的调用。
2、构造方法的特性
1. 名字必须与类名相同
2. 没有返回值类型,设置为void也不行
3. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
4. 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
重载:方法名、没有返回值类型,参数列表不同的构造方法会构成 “方法的重载” !
实例化对象时,会根据构造方法传过去参数列表的不同,调用相对应的构造方法。构造方法不止一个,是可以有多个的!
【注意】:方法名一定得相同;返回值类型不做要求,参数列表(顺序,类型,数量上的不同)一定得存在差异,否则就不是重载,而是重写了。
重写后续在有关多态的章节会介绍。
class Person{ public String name; public int age; public String sex; public Person(){ System.out.println("无参构造方法"); } public Person(String name,int age){ this.name=name; this.age=age; System.out.println("带两个参数的构造方法"); } } public class TestDemo { public static void main(String[] args) { Person person=new Person(); Person person1=new Person("LiHua",15); } } //运行结果;无参构造方法 // 带两个参数的构造方法
上述两个构造方法,分别时不带参数,和带两个参数的构造方法,两个方法之间构成了方法的重载!
我可以利用构造方法来对当前的对象的成员进行初始化。
5、如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。
在我们刚接触类和对象时,实例化一个对象,并没有自己创建一个构造方法,可为什么程序还是能正常运行?
这是因为,当我们用户没有手动定义一个构造方法(显示定义 ),编译器会自动生成一个隐式的构造方法(无参的构造方法)调用.
public Person(){ }
【注意】:一旦我们定义了一个构造方法后,编译器就不再为我们自动提供一个默认的不带参数的构造方法。
public class Date { public int year; public int month; public int day; public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public void printDate(){ System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { // 如果编译器会生成,则生成的构造方法一定是无参的 // 则此处创建对象是可以通过编译的 // 但实际情况是:编译期报错 Date d = new Date(); d.printDate(); } } /* Error:(26, 18) java: 无法将类 extend01.Date中的构造器 Date应用到给定类型; 需要: int,int,int 找到: 没有参数 原因: 实际参数列表和形式参数列表长度不同 */
此处虽然有自己定义一个构造方法,但这个构造方法是带三个参数的,在实例化对象时并没有传三个参数过去,是无参的。而我们自己定义后,编译器是不会再自动提供默认的无参构造方法,所以该代码才报错。正确的做法,是再添加一个无参的构造方法,或在实例化对象的时候传三个参数给构造方法。
6、构造方法中,可以通过this调用其他构造方法来简化代码 !
借一个构造方法,内部再调用另外一个构造方法。
class Person{ public String name; public int age; public String sex; public Person(){ this("LiHua",17); System.out.println("无参构造方法"); } public Person(String name,int age){ this.name=name; this.age=age; System.out.println("带两个参数的构造方法"); System.out.println(this.name+"年龄为:"+this.age); } } public class TestDemo { public static void main(String[] args) { Person person=new Person(); } }
总是先执行完this()调用的构造方法,再回到原本的构造方法进行执行。
【注意】:
1、在构造方法中使用this()调用其他的构造方法,this()调用语句一定要位于该构造方法内的第一行,因为this()一定得首先运行
2、因为this()只能放在第一行优先执行,所以一个构造方法里面只能调用一个this(),否则它们都想优先执行,没法都在第一行。
3、不能使用this()调用构造方法形成----环
【构造方法之间的循环反复调用,永无止境】
public Date(){ this(1900,1,1); } public Date(int year, int month, int day) { this(); } /* 无参构造器调用三个参数的构造器,而三个参数构造器有调用无参的构造器,形成构造器的递归调用 编译报错:Error:(19, 12) java: 递归构造器调用 */
7. 绝大多数情况下使用public来修饰,特殊场景下会被private修饰(后序讲单例模式时会遇到)
三、new关键字
对于实例化一个对象的语句,该语句不单单只是一个简单的语句,,在JVM层面需要做好多事情,下面简单介绍下:
1. 检测对象对应的类是否加载了,如果没有加载则加载;
由.java文件编译成.class文件时,一个字节码文件对应一个类,当用到类的时候要加载它。
2. 为对象分配内存空间;
3. 处理并发安全问题 比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突。
(当有多线程时,为同一个对象分配一块内存,就会出现并发安全问题。为了避免发生冲突,你不能每一个线程在分配内存时,都分配到同一个地址,多个对象没法放进去同一个地方。)
四、总结
在本章节中介绍了对象的初始化和构造方法。
最主要的要点是:
💥1、【构造方法的本质】当我们实例化一个对象的时侯,一定会进行两步操作:为对象分配内存、调用合适的构造方法。
2、构造方法是一个没有返回值,类名相同,参数列表不同的多个方法。
3、当写一个类时,编译器会帮我们默认生成一个不带参数的构造方法;但只要我们用户自己定义了一个构造方法,编译器就不会再默认生成一个。(一个类中至少会有一个构造方法,即使用户没有定义)。
4、可以通过this()调用构造方法,但只能在构造方法中使用(在同一个构造方法内也就只能使用一次 )。而且必须放在构造方法内第一行。
5、this()不能形成“环”。