深入剖析 Java 构造器调用及类的初始化顺序

时间:2022-08-25 00:06:11

深入剖析 Java 构造器调用及类的初始化顺序

Java 中的构造函数或称为构造器,其实就是一段代码,是在创建对象的实例并为该对象分配内存时调用该代码块。它是一种用于初始化对象的特殊方法。在声明构造函数时使用访问修饰符也是允许的。

掌握构造函数是有效学习 Java 的重要组成部分。因此,本篇文章就来谈谈创建Java构造器的有关规则、应用以及初始化情况,以全面的理解和掌握 Java 构造函数和相关情况。

1、构造器规则

编写Java构造器必须遵循的规则有:

  • Java 构造函数不得具有显式返回类型;
  • 它不能是抽象的(abstract)、最终的(final)、静态的(static)或同步的(synchronized);
  • 构造函数名称必须与属于其类的名称相同;
  • 构造器调用构造器的第一行原则。

2、构造器类型

Java中有两种构造函数:

1)默认构造函数或无参数构造函数

Java 默认构造函数没有参数。这就是为什么它也被称为无参数构造函数的原因。 Java 默认构造函数的一般语法是:

  1. (){
  2. //必要的初始化化代码
  3. }

需要知道的是,如果 Java 类中没有显式定义构造函数,那么 Java 编译器会自动为该类创建一个默认构造函数。根据对象的类型,默认构造函数为对象提供默认值或说默认初始化。

使用由 javac(java编译器) 自动创建的默认构造函数的缺点就是之后程序员无法设置或改变对象属性的初始值。例如:

  1. package com.learning;
  2. /**
  3. *
  4. * @author Solo Cui
  5. */
  6. public class ConstructorDemo {
  7. ConstructorDemo(){//无参构造器
  8. System.out.println("成功执行构造器,完成对象的创建。");
  9. }
  10. public static void main(String[] args) {
  11. ConstructorDemo cd = new ConstructorDemo() ;
  12. }
  13. }

运行输出结果如下:

成功执行构造器,完成对象的创建。

2)参数化构造函数

任何带有参数(一个或以上)的 Java 构造函数都称为参数化构造函数。尽管参数化构造函数通常用于为不同的 Java 对象提供不同的值,但它也可以为不同的 Java 对象提供相同的值,比如0或null。示例如下:

  1. package com.learning;
  2. /**
  3. * 参数化构造器示例
  4. *
  5. * @author Solo Cui
  6. */
  7. public class ParametersConstructor {
  8. int id;
  9. String name;
  10. ParametersConstructor(String n,int i ) {
  11. name = n;
  12. id = i;
  13. }
  14. void display(){
  15. System.out.println(""+id+" "+name);
  16. }
  17. public static void main(String args[]) {
  18. ParametersConstructor s1 = new ParametersConstructor("Solo",666);
  19. ParametersConstructor s2 = new ParametersConstructor("Cui", 999);
  20. s1.display();
  21. s2.display();
  22. }
  23. }

运行输出结果如下:

  1. 666 Solo
  2. 999 Cui

3、构造器调用

构造器除了创建对象时的常规调用,在同一类内,构造器也可调用其他构造器。这里分两种情况,即调用父类构造器和调用当前子类构造器。示例如下:

1)this形式调用

  1. package com.learning;
  2. /**
  3. * 多个构造器的类,this形式调用其他构造器
  4. * @author Administrator
  5. */
  6. public class MyParent {
  7. MyParent(){
  8. System.out.println("无参构造器进行初始化处理……");
  9. }
  10. MyParent(String hello){
  11. this();
  12. System.out.println("打个招呼:"+hello);
  13. }
  14. public static void main(String[] args) {
  15. new MyParent("You're Great!");
  16. }
  17. }

这里一定要注意,调用同一类内的其他构造器的this()必须是是构造器内的第一行,是否有参数,则根据调用的构造器实际情况决定。

2)super形式调用

此种调用是针对类继承关系的子类调用父情况。代码示例如下:

  1. package com.learning;
  2. /**
  3. * 继承MyParent类,调用父类
  4. * @author Administrator
  5. */
  6. public class MyChild extends MyParent{
  7. String name ="default";
  8. MyChild(){
  9. //super():编译器会隐式调用父类的无参构造器
  10. System.out.println("My Name is "+name);
  11. }
  12. MyChild(String n){
  13. super("大秦帝国");//显式调用父类构造器
  14. name = n ;
  15. }
  16. void showName(){
  17. System.out.println("Name is "+name);
  18. }
  19. public static void main(String[] args) {
  20. MyChild child = new MyChild();
  21. child.showName();
  22. MyChild child2 = new MyChild("Solo");
  23. child2.showName();
  24. }
  25. }

请注意:在子类未调用父类构造器时,则编译器会隐式的调用父类无参构造器的,即super();若子类显式调用父类的构造器,则必须是在构造器的第一行,super内的参数根据需要传入即可。还有一点需要注意,即this同类构造器调用和super父子类调用不能同时出现在同一个构造器内。

4、构造器重载

与 方法一样, Java类中的构造函数可以重载。构造函数重载,是用相同的构造函名但具有不同的参数列表。所有这些构造器各自执行不同的任务。

Java 编译器通过列表中参数的总数及其类型来区分重载的构造函数。以下代码片段演示了 Java 中的构造函数重载:

  1. package com.learning;
  2. /**
  3. * 构造器重载示例
  4. *
  5. * @author Solo Cui
  6. */
  7. public class OverloadConstructor {
  8. int id;
  9. String name;
  10. int age;
  11. OverloadConstructor(int i, String n) {
  12. id = i;
  13. name = n;
  14. }
  15. OverloadConstructor(int i, String n, int a) {
  16. id = i;
  17. name = n;
  18. age = a;
  19. }
  20. void display(){
  21. System.out.println("id="+id+";name="+name+";age="+age);
  22. }
  23. public static void main(String args[]) {
  24. OverloadConstructor s1 = new OverloadConstructor(333, "Solo");
  25. OverloadConstructor s2 = new OverloadConstructor(666, "Cui", 25);
  26. s1.display();
  27. s2.display();
  28. }
  29. }

运行输出结果如下:

  1. id=333;name=Solo;age=0
  2. id=666;name=Cui;age=25

5、构造器与方法

简单来讲,Java 方法是一段具有特定名称的代码。在可访问的范围内,只需使用方法名称即可在程序中的任何时间点调用它。你也可以理解为对数据进行操作,然后返回值(某特定在或void)的子程序。

Java 构造函数是一种特殊类型的方法。两者在许多方面相似,但并不完全相同。以下是 Java 构造函数和 Java 方法之间一些最重要的区别:

  • 调用——子类通过super()隐式调用父类构造函数,而方法必须显式调用;同类内有多个构造器时,某个构造器调用另一个构造器时,使用this()形式,this后括号有无参数根据调用的构造器参数决定;而方法调用则是通过this+点(.)+方法名;
  • 编译器——Java编译器从不提供 Java 默认方法。但是,如果 Java 类中未定义构造函数,则 Java 编译器会提供默认构造函数;
  • 命名约定——Java构造函数的名称必须与类的名称相同。但是方法的名称可能与包含它的类的名称相同,也可能不同
  • 调用次数——Java 构造函数只在对象创建期间被调用一次。而Java 方法可以根据需要多次调用;
  • 返回类型——Java 方法必须具有返回类型,但对于构造函数则不需要返回类型;
  • 用法——方法用于公开 Java 对象的行为,而构造函数用于初始化相同对象的状态。

6、复制构造函数

尽管 Java 中没有提供复制构造函数,但可以将值从一个 Java 对象复制到另一个对象,就像在 C++ 中使用复制构造函数一样。

除了使用构造函数将值从一个对象复制到另一个对象外,还可以通过以下方式完成:

将一个对象的值分配给另一个对象;

或者

通过使用 Object 类的 clone() 方法。

以下程序演示了使用 Java 构造函数将值从一个 Java 对象复制到另一个对象:

  1. package com.learning;
  2. /**
  3. * 复制当前类的对象
  4. *
  5. * @author Solo Cui
  6. */
  7. public class SimpleCopy {
  8. int id;
  9. String name;
  10. SimpleCopy(int i, String n) {
  11. id = i;
  12. name = n;
  13. }
  14. SimpleCopy(SimpleCopy s) {
  15. id = s.id;
  16. name = s.name;
  17. }
  18. void show() {
  19. System.out.println("id=" + id + ";name=" + name);
  20. }
  21. public static void main(String[] args) {
  22. SimpleCopy s1 = new SimpleCopy(138, "Solo");
  23. SimpleCopy s2 = new SimpleCopy(s1);
  24. s1.show();
  25. s2.show();
  26. }
  27. }

7、类的初始化

在Java中类初始化可以分为两类,即静态初始化和非静态初始化。当创建java对象时,程序总是依次调用每个父类的非静态初始化块、父类构造器(总是从Object开始——Java中的始祖类)执行初始化,最后再调用当前(子)类的非静态初始化块、构造器执行初始化。通过示例演示如下,下面我们来开看其初始化的顺序:

1)父类XParent:

  1. package com.learning;
  2. /**
  3. * 类初始化:父类
  4. *
  5. * @author Administrator
  6. */
  7. public class XParent {
  8. static {
  9. System.out.println("XParent:父类【静态】初始化块");
  10. }
  11. {
  12. System.out.println("XParent:父类【非静态】初始化块");
  13. }
  14. public XParent() {
  15. System.out.println("XParent:父类无参构造器");
  16. }
  17. public XParent(String name) {
  18. System.out.println("XParent:父类含参构造器:name=" + name);
  19. }
  20. }

2)子类XChild:

  1. package com.learning;
  2. /**
  3. * 子类XChild初始化
  4. *
  5. * @author Solo cui
  6. */
  7. public class XChild extends XParent {
  8. static {
  9. System.out.println("XChild:子类静态初始化块");
  10. }
  11. {
  12. System.out.println("XChild:子类非静态初始化块");
  13. }
  14. public XChild() {
  15. this("Solo");
  16. System.out.println("XChild:子类无参构造器");
  17. }
  18. public XChild(String name) {
  19. super(name);
  20. System.out.println("XChild:子类含参构造器: name=" + name);
  21. }
  22. public static void main(String[] args) {
  23. XChild c1 = new XChild("Tomy");
  24. XChild c2 = new XChild();
  25. }
  26. }

运行子类输出结果为:

  1. XParent:父类【静态】初始化块
  2. XChild:子类静态初始化块
  3. XParent:父类【非静态】初始化块
  4. XParent:父类含参构造器:name=Tomy
  5. XChild:子类非静态初始化块
  6. XChild:子类含参构造器: name=Tomy
  7. XParent:父类【非静态】初始化块
  8. XParent:父类含参构造器:name=Solo
  9. XChild:子类非静态初始化块
  10. XChild:子类含参构造器: name=Solo
  11. XChild:子类无参构造器

所以,无论新实例化多少个对象,该类的所有父类以及自身的静态初始化块只执行一次,而且是最先执行的初始化,称作类的初始化。之后的初始化会依次执行父类的非静态初始化块、父类的构造器和子类的非静态初始化块、子类的构造器来完成初始化称为对象初始化;在子类的构造器中可以通过super来显式调用父类的构造器,可以通过this来调用该类重载的其他构造器,而具体调用哪个构造器决定于调用时的参数类型。

8、关于构造器的FAQs

以下是一些关于 Java 构造函数的最常见问题。

问:构造函数是否有返回值?

答:虽然不能在 Java 构造函数中使用返回类型,但它确实返回一个值。 Java 构造函数返回当前类实例的引用。

问:Java 中的构造函数链是什么?

答:构造函数链接是在 Java 编程语言中从其他构造函数调用构造函数的技术。 this() 方法用于调用同一个类构造函数,而 super() 方法用于调用超类构造函数。

问:说说在构造器调用构造器时,this和super的区别

答:super和this的调用只能在构造器中,而且都必须作为构造器中的第一行,因此super和this不会同时出现在同一个构造器中。

问:在Java中可以从超类构造函数中调用子类构造函数吗?

答:不行。

问:Java 有析构函数吗?

答:Java 没有析构函数,因为它是垃圾自回收语言。在 Java 中无法预测对象何时会被销毁。

问:Java 构造函数可以执行除初始化之外的哪些任务?

答:Java 构造函数可以执行任何可以使用方法执行的操作。使用 Java 构造函数执行的一些最流行的任务是:

调用方法;

对象创建;

开始一个线程等。

问:Java 中何时需要构造函数重载?

A:构造函数重载在 Java 中通常用于需要以多种不同方式初始化 Java 对象的情况。

问:如果为 Java 构造函数添加返回类型会发生什么?

答:具有返回类型的 Java 构造函数将被视为典型的 Java 方法。

最后

这就是 Java 构造函数的全部内容。掌握好如何有效地使用构造函数是Java编程的必备核心技能之一。根据具体学习情况多加练习和酌情使用吧。

原文链接:https://www.toutiao.com/i7048643028608631334/