构造函数在创建对象时用来初始化对象。 它与类具有相同的名称,并且在语法上与方法类似。但是,构造函数没有返回值类型。
通常,使用构造函数为类定义的实例变量提供初始值,或执行创建完全形成的对象所需的其他启动过程。
所有类都有构造函数,无论是否定义了构造函数,因为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.example.constructor; public class Car { public static void main(String[] args) { Car c = new Car(); } }
默认构造函数的唯一作用是初始化对象并将其返回给调用代码。默认构造函数始终没有参数,只有在没有定义现有构造函数的情况下由java编译器提供。大多数情况下,可以使用默认构造函数本身,因为可以通过getter/setter方法访问和初始化其他属性。
1.2. 无参构造函数
没有任何参数的构造函数称为无参构造函数。 这就像覆盖默认构造函数并用于执行一些预初始化的东西,例如:检查资源,网络连接,日志记录等。通过以下代码浏览一下java中的无参构造函数。
package com.example.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.example.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
2. 在Java中构造函数重载
当有多个构造函数时,它就是java中的构造函数重载。通过下面示例代码来了解java程序中构造函数重载。
package com.example.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("Tom", 25); System.out.println(d); } }
3. Java中的私有构造函数
不能将abstract,final,static和synchronized关键字与构造函数一起使用。 但是,可以使用访问修饰符来控制类对象的实例化。 使用public访问和default访问仍然没有问题,但是将构造函数设为私有的用途是什么? 在这种情况下,任何其他类都将无法创建该类的实例。
如果想要实现单例设计模式,构造函数是private。 由于java自动提供默认构造函数,因此必须显式创建构造函数并将其保持为private。 客户端类提供了实用程序静态方法来获取类的实例。 下面给出了Data类的私有构造函数的示例。
// private 构造函数 private Data() { // 单例模式实现的空构造函数 // 可以在类的getInstance()方法中使用代码 }
4. Java构造函数链接
当构造函数调用同一个类的另一个构造函数时,它被称为构造函数链接。需要使用this关键字来调用该类的另一个构造函数。有时它用于设置类变量的一些默认值。
请注意,另一个构造函数调用应该是代码块中的第一个语句。 此外,不应该有一个会产生无限循环的递归调用。下面来看看java程序中构造函数链接的一个例子。
package com.example.constructor; public class Employee { private int id; private String name; public Employee() { this("Tom", 1999); System.out.println("Default Employee Created"); } public Employee(int i) { this("Tom", 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("example", 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 = Tom Employee Created Employee Created with Default Name ID = 10, Name = Tom Employee Created ID = 20, Name = TOM
从另一个构造函数调用一个构造函数,这个构造函数被称为构造函数链接过程。
5. Java超类构造函数
有时一个类是从超类继承的,在这种情况下,如果必须调用超类构造函数,那么可以使用super关键字。
请注意,super造函数调用应该是子类构造函数中的第一个语句。 此外,在实例化子类构造函数时,java首先初始化超类,然后初始化子类。 因此,如果未显式调用超类构造函数,则java运行时将调用default或no-args构造函数。下面将通过一些示例程序来理解这些概念。假设有两个类,如下所示。
Person类源代码 -
package com.example.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.example.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,"Tom");
那么输出将是:
Person Created with Age = 1999 Student Created with name = Tom
这里输出很明显,因为我们显式调用了超类构造函数,所以java不需要从no-args构造函数这边做任何额外的工作。
6. Java拷贝构造函数
Java拷贝构造函数将相同类的对象作为参数,并创建它的副本。有时需要另一个对象的副本来进行一些处理。 可以通过以下方式做到这一点:
实现克隆。提供用于对象深拷贝的方法。实现一个复制构造函数。
现在来看看如何编写复制构造函数,假设有一个类:Fruits,代码如下。
package com.example.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.example.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]
请注意,当使用复制构造函数时,原始对象和它的副本彼此无关,并且其中一个中的任何修改都不会反映到其他对象中。