Java构造函数

构造函数在创建对象时用来初始化对象。 它与类具有相同的名称,并且在语法上与方法类似。但是,构造函数没有返回值类型。

通常,使用构造函数为类定义的实例变量提供初始值,或执行创建完全形成的对象所需的其他启动过程。

所有类都有构造函数,无论是否定义了构造函数,因为Java会自动提供一个默认构造函数,将所有成员变量初始化为零。 但是,一旦定义了自己的构造函数,就不再使用默认构造函数。

语法

以下是构造函数的语法 -

class ClassName {
   ClassName() {
   }
}

每当使用new关键字创建类的实例时,都会调用构造函数并返回类的对象。 由于构造函数只能将对象返回给类,它是由java运行时隐式完成的,不应该向它添加返回类型。

如果将返回类型添加到构造函数,那么它将成为类的方法。 这是java运行时区分普通方法和构造函数的方式。 假设在Employee类中有以下代码。

import java.io.*;

public class Employee {

    public Employee() {
        System.out.println("Employee构造函数");
    }


    public Employee Employee() {
        System.out.println("Employee一般方法");
        return new Employee();
    }
}

这里第一个是构造函数,因为它没有返回类型和返回语句。 第二个是一个常规方法,我们再次调用第一个构造函数来获取Employee实例并返回它。建议不要将方法名称与类名相同,因为会容易造成混淆。

1. Java中构造函数的类型

java中有三种类型的构造函数。

  • 默认构造函数
  • 无参数构造函数
  • 参数化构造函数

下面将通过示例程序来学习这些构造函数类型。

1.1. Java中的默认构造函数

不需要始终在类代码中提供构造函数实现。如果不提供构造函数,那么java提供默认的构造函数实现供我们使用。 下面来看一个使用默认构造函数的简单程序,它没有显式定义构造函数。

package com.zaixian.constructor;

public class Car {

    public static void main(String[] args) {
        Car c = new Car();
    }
}
  • 默认构造函数的唯一作用是初始化对象并将其返回给调用代码。
  • 默认构造函数始终没有参数,只有在没有定义现有构造函数的情况下由java编译器提供。
  • 大多数情况下,可以使用默认构造函数本身,因为可以通过getter/setter方法访问和初始化其他属性。

1.2. 无参构造函数

没有任何参数的构造函数称为无参构造函数。 这就像覆盖默认构造函数并用于执行一些预初始化的东西,例如:检查资源,网络连接,日志记录等。通过以下代码浏览一下java中的无参构造函数。

package com.zaixian.constructor;

public class Car {
    // 无参构造函数
    public Car() {
        System.out.println("No-Args Constructor");
    }
    public static void main(String[] args) {
        Car d = new Car();
    }
}

当调用new new()时,将调用无参构造函数。执行上面代码,程序在控制台输出结果如下:

No-Args Constructor

1.3. 参数化构造函数

带参数的构造函数称为参数化构造函数。下面来看看java中参数化构造函数的例子。

package com.zaixian.constructor;

public class Language {

    private String name;

    public Language(String n) {
        System.out.println("Parameterized Constructor");
        this.name = n;
    }

    public String getName() {
        return name;
    }

    public static void main(String[] args) {
        Language lang = new Language("Java");
        System.out.println(lang.getName());
    }

}

执行上面代码,程序在控制台输出结果如下:

Parameterized Constructor
Java

2. 在Java中构造函数重载

当有多个构造函数时,它就是java中的构造函数重载。通过下面示例代码来了解java程序中构造函数重载。

package com.zaixian.constructor;

public class Data {

    private String name;
    private int id;

    //no-args constructor
    public Data() {
        this.name = "Default Name";
    }
    //one parameter constructor
    public Data(String n) {
        this.name = n;
    }
    //two parameter constructor
    public Data(String n, int i) {
        this.name = n;
        this.id = i;
    }

    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "ID="+id+", Name="+name;
    }
    public static void main(String[] args) {
        Data d = new Data();
        System.out.println(d);

        d = new Data("Java");
        System.out.println(d);

        d = new Data("Maxsu", 25);
        System.out.println(d);

    }

}

3. Java中的私有构造函数

不能将abstractfinalstaticsynchronized关键字与构造函数一起使用。 但是,可以使用访问修饰符来控制类对象的实例化。 使用public访问和default访问仍然没有问题,但是将构造函数设为私有的用途是什么? 在这种情况下,任何其他类都将无法创建该类的实例。

如果想要实现单例设计模式,构造函数是private。 由于java自动提供默认构造函数,因此必须显式创建构造函数并将其保持为private。 客户端类提供了实用程序静态方法来获取类的实例。 下面给出了Data类的私有构造函数的示例。

// private 构造函数
private Data() {
    // 单例模式实现的空构造函数
    // 可以在类的getInstance()方法中使用代码
}

4. Java构造函数链接

当构造函数调用同一个类的另一个构造函数时,它被称为构造函数链接。需要使用this关键字来调用该类的另一个构造函数。有时它用于设置类变量的一些默认值。

请注意,另一个构造函数调用应该是代码块中的第一个语句。 此外,不应该有一个会产生无限循环的递归调用。下面来看看java程序中构造函数链接的一个例子。


package com.zaixian.constructor;

public class Employee {

    private int id;
    private String name;

    public Employee() {
        this("Maxsu", 1999);
        System.out.println("Default Employee Created");
    }

    public Employee(int i) {
        this("Maxsu", i);
        System.out.println("Employee Created with Default Name");
    }
    public Employee(String s, int i) {
        this.id = i;
        this.name = s;
        System.out.println("Employee Created");
    }
    public static void main(String[] args) {

        Employee emp = new Employee();
        System.out.println(emp);
        Employee emp1 = new Employee(10);
        System.out.println(emp1);
        Employee emp2 = new Employee("zaixian", 20);
        System.out.println(emp2);
    }

    @Override
    public String toString() {
        return "ID = "+id+", Name = "+name;
    }
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

上代码代码中,已经重写了toString()方法来打印一些有关Employee对象的信息。 以下是上面程序产生的输出 -

Employee Created
Default Employee Created
ID = 1999, Name = Maxsu
Employee Created
Employee Created with Default Name
ID = 10, Name = Maxsu
Employee Created
ID = 20, Name = zaixian

从另一个构造函数调用一个构造函数,这个构造函数被称为构造函数链接过程。

5. Java超类构造函数

有时一个类是从超类继承的,在这种情况下,如果必须调用超类构造函数,那么可以使用super关键字。

请注意,super造函数调用应该是子类构造函数中的第一个语句。 此外,在实例化子类构造函数时,java首先初始化超类,然后初始化子类。 因此,如果未显式调用超类构造函数,则java运行时将调用defaultno-args构造函数。下面将通过一些示例程序来理解这些概念。
假设有两个类,如下所示。

Person类源代码 -


package com.zaixian.constructor;

public class Person {

    private int age;

    public Person() {
        System.out.println("Person Created");
    }

    public Person(int i) {
        this.age = i;
        System.out.println("Person Created with Age = " + i);
    }

}

Student类源代码 -

package com.zaixian.constructor;

public class Student extends Person {

    private String name;

    public Student() {
        System.out.println("Student Created");
    }

    public Student(int i, String n) {
        super(i); // 超类构造函数调用
        this.name = n;
        System.out.println("Student Created with name = " + n);
    }

}

现在,如果创建一个Student对象,如下所示:

Student st = new Student();

那将输出什么结果? 上面代码的输出将是:

Person Created
Student Created

所以调用转到了Student类的no-args构造函数,因为在第一个语句中没有super调用,所以调用了Person类的no-args或默认构造函数。如果使用Student类的参数化构造函数作为 -

Student st = new Student(1999,"Maxsu");

那么输出将是:

Person Created with Age = 1999
Student Created with name = Maxsu

这里输出很明显,因为我们显式调用了超类构造函数,所以java不需要从no-args构造函数这边做任何额外的工作。

6. Java拷贝构造函数

Java拷贝构造函数将相同类的对象作为参数,并创建它的副本。有时需要另一个对象的副本来进行一些处理。 可以通过以下方式做到这一点:

  • 实现克隆。
  • 提供用于对象深拷贝的方法。
  • 实现一个复制构造函数。

现在来看看如何编写复制构造函数,假设有一个类:Fruits,代码如下。


package com.zaixian.constructor;

import java.util.ArrayList;
import java.util.List;

public class Fruits {

    private List<String> fruitsList;

    public List<String> getFruitsList() {
        return fruitsList;
    }

    public void setFruitsList(List<String> fruitsList) {
        this.fruitsList = fruitsList;
    }

    public Fruits(List<String> fl) {
        this.fruitsList = fl;
    }

    public Fruits(Fruits fr) {
        List<String> fl = new ArrayList<>();
        for (String f : fr.getFruitsList()) {
            fl.add(f);
        }
        this.fruitsList = fl;
    }
}

请注意,Fruits(Fruits fr)执行深拷贝以返回对象的副本。下面通过一个测试程序,了解为什么拷贝构造函数比拷贝对象更好。


package com.zaixian.constructor;

import java.util.ArrayList;
import java.util.List;

public class ConstructorTest {

    public static void main(String[] args) {
        List<String> fl = new ArrayList<>();
        fl.add("Mango");
        fl.add("Orange");

        Fruits fr = new Fruits(fl);

        System.out.println(fr.getFruitsList());

        Fruits fr = fr;
        fr.getFruitsList().add("Apple");

        System.out.println(fr.getFruitsList());

        fr = new Fruits(fr);
        fr.getFruitsList().add("Banana");
        System.out.println(fr.getFruitsList());
        System.out.println(fr.getFruitsList());

    }

}

执行上面查询语句,得到以下结果:


[Mango, Orange]
[Mango, Orange, Apple]
[Mango, Orange, Apple]
[Mango, Orange, Apple, Banana]

请注意,当使用复制构造函数时,原始对象和它的副本彼此无关,并且其中一个中的任何修改都不会反映到其他对象中。


上一篇: java中方法重载和方法重写的区别 下一篇:无