Java构造方法及类的初始化
Posted 愿荣
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java构造方法及类的初始化相关的知识,希望对你有一定的参考价值。
活动地址:CSDN21天学习挑战赛
✨博客主页: XIN-XIANG荣
✨系列专栏:【Java SE】
✨一句短话: 难在坚持,贵在坚持,成在坚持!
文章目录
一. 利用构造方法给对象初始化
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(2021, 6, 9);
// 输出Date(int,int,int)方法被调用了
d.printDate(); // 2021-6-9
2. 构造方法的特性
- 名字必须与类名相同
- 没有返回值类型,设置为void也不行
- 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次
- 绝大多数情况下使用public来修饰,特殊场景下会被private修饰
- 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法); 下面两个构造方法:名字相同,参数列表不同,因此构成了方法重载
public class Date
public int year;
public int month;
public int day;
// 无参构造方法
public Date()
this.year = 1900;
this.month = 1;
this.day = 1;
// 带有三个参数的构造方法
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();
- 如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的; 一旦用户定义,编译器则不再生成;下面代码中,没有定义任何构造方法,编译器会默认生成一个不带参数的构造方法。
public class Date
public int year;
public int month;
public int day;
public void printDate()
System.out.println(year + "-" + month + "-" + day);
public static void main(String[] args)
Date d = new Date();
d.printDate();
- 构造方法中,可以通过this调用其他构造方法来简化代码
【注意事项】
- 构造方法中,通过this(…)去调用其他构造方法,这条语句必须是构造方法中第一条语句
- 多个构造方法不可以互相调用(不能形成环), 会形成构造器的递归调用,但却没有调用的结束条件
public class Date
public int year;
public int month;
public int day;
// 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复
// 此处可以在无参构造方法中通过this调用带有三个参数的构造方法
// 但是this(2022,8,16);必须是构造方法中第一条语句
public Date()
//System.out.println(year); 注释取消掉,编译会失败
this(2022, 8, 16);
//this.year = 1900;
//this.month = 1;
//this.day = 1;
// 带有三个参数的构造方法
public Date(int year, int month, int day)
this.year = year;
this.month = month;
this.day = day;
3. 子类构造方法
在继承基础上,子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,
原因在于:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父类和子类, 肯定是先有父再有子,所以在构造子类对象时候 ,子类构造方法中先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再完成子类自己的构造,将子类自己新增加的成员初始化完整 。
【注意事项】
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法
public class Base
public Base()
System.out.println("Base()");
public class Derived extends Base
public Derived()
// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次
System.out.println("Derived()");
public class Test
public static void main(String[] args)
Derived d = new Derived();
- 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
public class Animal
public String name;
public int age;
public Animal(String name, int age)
this.name = name;
this.age = age;
System.out.println("Animal(String , int )");
public class Dog extends Animal
//傻狗 是狗的属性
public boolean silly;
public Dog(String name,int age,boolean silly)
//1. 先帮助父类部分初始化 必须放到第一行
super(name,age);
this.silly = silly;
System.out.println("Dog(String ,int ,boolean )");
public static void main(String[] args)
Animal animal2 = new Dog("金毛",6,false);
- 在子类构造方法中,super(…)调用父类构造时,必须是子类构造方法中第一条语句。
- super(…)只能在子类构造方法中出现一次,由与this(…)调用时也要在第一条语句,所以super(…)不能和this(…)同时出现,也就是是说子类构造方法中不能使用this(…)
4. 避免在构造方法中调用重写的方法
一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func
class B
public B()
// do nothing
func();
public void func()
System.out.println("B.func()");
class D extends B
private int num = 1;
@Override
public void func()
System.out.println("D.func() " + num);
public class Main
public static void main(String[] args)
D d = new D();
执行结果:
- 构造 D 对象的同时, 会调用 B 的构造方法.
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, num 处在未初始化的状态, 值为 0;如果具备多态性,num的值应该是1.
- 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
【结论】:
“用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.
二. 对象的默认初始化
在Java方法内部定义一个局部变量时,用户必须要将其赋值或者初始化,否则会编译失败;
但对象中的字段(成员变量),用户不需要将其初始化就可直接访问使用,这里其原因在于new对象时,jvm会给出字段的默认初始化。
下面是new对象是时,jvm层面执行的概述:
- 检测对象对应的类是否加载了,如果没有加载则加载
- 为对象分配内存空间
- 处理并发安全问题
比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突 - 初始化所分配的空间
即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值
数据类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0f |
double | 0.0 |
char | /u0000 |
boolean | false |
reference (引用类型) | null |
- 设置对象头信息(关于对象内存模型后面会介绍)
- 调用构造方法,给对象中各个成员赋值
三. 就地初始化对象
在声明成员变量时,就直接给出了初始值。
代码编译完成后,编译器会将所有给成员初始化的这些语句添加到各个构造方法中
public class Date
public int year = 1900;
public int month = 1;
public int day = 1;
public Date()
public Date(int year, int month, int day)
public static void main(String[] args)
Date d1 = new Date(2022,8,16);
Date d2 = new Date();
四. 类的初始化顺序
1. 普通类(没有继承关系)
- 静态部分(静态变量、常量,静态代码块)
- 在类加载阶段执行,类中存在多个静态部分时,会按顺序执行
- 静态代码块只会执行一次,且静态的变量、常量等只会创建一份
- 非静态部分(实例变量、常量、实例代码块)
- 当有对象创建时才会执行,按顺序执行
- 最后执行构造方法,当有对象创建时才会执行
代码演示:
class Person
public String name;
public int age;
public Organ organ = new Organ();
public Person(String name, int age)
this.name = name;
this.age = age;
System.out.println("构造方法执行");
System.out.println("实例代码块执行");
static
System.out.println("静态代码块执行");
class Organ
//...
public Organ()
System.out.println("实例变量::organ");
public class TestDemo
public static void main(String[] args)
Person person1 = new Person("xin",21);
System.out.println("==============");
Person person2 = new Person("xinxin",20);
执行结果:
2. 派生类( 有继承关系)
- 静态部分(静态变量、常量,静态代码块)
- 父类静态代码块优先于子类静态代码块执行,且是最早执行
- 只有第一次实例化子类对象时,父类和子类的静态部分会执行; 之后再实例化子类对象时,父类和子类的静态部分都不会再执行
- 父类非静态部分(实例变量、常量、实例代码块)和父类构造方法
- 子类非静态部分(实例变量、常量、实例代码块)和子类构造方法
class Person
public String name;
public int age;
public Person(String name, int age)
this.name = name;
this.age = age;
System.out.println("Person:构造方法执行");
System.out.println("Person:实例代码块执行");
static
System.out.println("Person:静态代码块执行");
class Student extends Person
public Student(String name,int age)
super(name,age);
System.out.println("Student:构造方法执行");
System.out.println("Student:实例代码块执行");
static
System.out.println("Student:静态代码块执行");
public class TestDemo4
public static void main(String[] args)
Student student1 = new Student("张三",19);
System.out.println("===========================");
Student student2 = new Student("gaobo",20);
public static void main1(String[] args)
Person person1 = new Person("bit",10);
System.out.println("============================");
Person person2 = new Person("gaobo",20);
执行结果:
Java中类的构造方法
constructor;构造函数。
在创建对象的时候,对象成员可以由构造函数方法进行初始化。
new对象时,都是用构造方法进行实例化的;
例如;Test test = new Test("a");
//Test("a");其中这个就是构造函数,“a”为构造方法的形参;
构造方法的方法名必须与类名一样。
构造方法没有返回类型,也不能定义为void,在方法名前面不声明方法类型。
构造方法不能作用是完成对象的初始化工作,他能够把定义对象时的参数传递给对象的域。
构造方法不能由编程人员调用,而要系统调用。
构造方法可以重载,以参数的个数,类型,或排序顺序区分。
具体用法,代码实现。
1;单个构造函数方法;’
2;多个构造函数方法
(例子为;带参数与不带参数)
3;关于继承类的构造方法的调用;
先看事例;
我第一次看到结果的时候好惊讶的,怎么都调用了哈。看了知识点才知道。
在Subtine的主方法中只调用子类构造方法,实例化子类对象并且在子类构造方法中,没有调用父类的构造方法的任何语句。但是在实例化对象时,它相应的调用了父类构造方法,在结果中还可以看到调用构造方法的顺序,首先是顶级,再继续往下直达本身类。也就是说实例化子类的时候,要首先实例化父类对象,然后在实例化子类对象,所以在子类构造方法调用父类构造方法前,父类已经实例化了。
拓展一下。
package text_4_1; public class Parent { Parent(int a){//就是将无参数构造方法改成有参数的。 System.out.println("调用父类Parent构造方法" + a); } } package text_4_1; public class Subparent extends Parent{ Subparent(){ System.out.println("调用子类Subparent的构造方法"); } } package text_4_1; public class Subtine extends Subparent{ Subtine(){ System.out.println("调用子类Subtine构造方法"); } } package text_4_1; public class Main { public static void main(String[] args){ Subtine subtine = new Subtine(); } }
这样的代码将会报错;//就是将父类Parent类无参数构造方法改成有参数的。
Implicit super constructor Parent() is undefined. Must explicitly invoke another constructor
//隐式的超级构造函数()是未定义的。必须显式地调用另一个构造函数
意思就是在子类调用构造函数时,必须先调用父类构造函数,因为无参数的构造函数,编译器会自动调用,也就是第一种情况不会报错。但是有参数的,就必须要利用Super的方法来调用。如果不进行调用那么将会报错。
将Subparent类改成。
package text_4_1;
public class Subparent extends Parent{
Subparent(){
super(5);//调用了其父类就不会报错了。
System.out.println("调用子类Subparent的构造方法");
}
}
//备注一下,调用父类构造方法就是;super();//就ok了。
还补充一点,关于构造方法的可扩展性;
查看过源码的应该都发现过jdk的编码人员会在一个类中建立多个构造方法;然而他的作用就是提高可扩展性;
这么多构造方法,他们之间并且还有联系;通过this()来调用自己类的其他构造方法,来减少代码的复制,增大程序的可读性;
至于怎么来进行封装;举个例子;
构造方法的可扩展性就表示在这里;
可以多多个构造方法;根据不同的参数决定调用哪个;带来很大的可扩展性
刚试了一下notepad++;然而直接爆出中文乱码,搞了好久才搞清楚;
原因是;国标码和ANSI之间的不兼容;
因为刚学,有什么不足的后期补上。
望路过的大神,指点一二。
以上是关于Java构造方法及类的初始化的主要内容,如果未能解决你的问题,请参考以下文章
java静态资源(静态方法,静态属性)是程序一运行就加载到jvm中,还是当被调用的时候才进行加载呢?