你好!今天我们考虑一个非常重要的话题,它与我们的对象有关。毫不夸张地说,我们可以说您每天都会在现实生活中使用这个话题!我们正在谈论 Java 构造函数。这可能是您第一次听到这个术语,但实际上您已经使用过构造函数。您只是没有意识到这一点 :) 我们稍后会说服自己。

构造函数到底是什么,为什么需要它们?

让我们考虑两个例子。

public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car bugatti = new Car();
       bugatti.model = "Bugatti Veyron";
       bugatti.maxSpeed = 378;

   }
}
我们创建了我们的汽车,并设置了它的模型和最大速度。但是Car对象在实际项目中显然不会有 2 个字段。例如,它可能有 16 个字段!

public class Car {

   String model;// model
   int maxSpeed;// maximum speed
   int wheels;// wheel width
   double engineVolume;// engine volume
   String color;// color
   int productionYear;// production year
   String ownerFirstName;// first name of owner
   String ownerLastName;// last name of owner
   long price;// price
   boolean isNew;// flag indicating whether car is new
   int seatsInTheCar;// number of seats in the car
   String cabinMaterial;// interior material
   boolean insurance;// flag indicating whether car is insured
   String manufacturerCountry;// manufacturer country
   int trunkVolume;// size of the trunk
   int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.productionYear = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.seatsInTheCar = 2;
       bugatti.maxSpeed = 378;
       bugatti.model = "Bugatti Veyron";

   }

}
我们创建了一个新的Car对象。有一个问题:我们有 16 个字段,但我们只初始化了 12 个!现在查看代码并尝试找到我们忘记的字段!没那么容易吧?在这种情况下,程序员很容易犯错误,无法初始化某些字段。结果,该程序将表现不正确:

public class Car {

   String model;// model
   int maxSpeed;// maximum speed
   int wheels;// wheel width
   double engineVolume;// engine volume
   String color;// color
   int productionYear;// production year
   String ownerFirstName;// first name of owner
   String ownerLastName;// last name of owner
   long price;// price
   boolean isNew;// flag indicating whether car is new
   int seatsInTheCar;// number of seats in the car
   String cabinMaterial;// interior material
   boolean insurance;// flag indicating whether car is insured
   String manufacturerCountry;// manufacturer country
   int trunkVolume;// size of the trunk
   int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.productionYear = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.seatsInTheCar = 2;
       bugatti.maxSpeed = 378;
       bugatti.model = "Bugatti Veyron";

       System.out.println("Model: Bugatti Veyron. Engine volume: " + bugatti.engineVolume + ". Trunk volume: " + bugatti.trunkVolume + ". Cabin material: " + bugatti.cabinMaterial +
       ". Wheel width: " + bugatti.wheels + ". Purchased in 2018 by Mr. " + bugatti.ownerLastName);

   }

}
控制台输出: 型号:布加迪威龙。发动机容积:6.3。行李箱容积:0。客舱材料:无。轮宽:0。空先生于 2018 年购买 您的买家,为这辆车放弃了 200 万美元,显然不喜欢被称为“空先生”!但严重的是,最重要的是我们的程序错误地创建了一个对象:一辆车轮宽度为 0(即根本没有车轮)的汽车,一个丢失的行李箱,一个由未知材料制成的小屋,最重要的是,一个未定义的所有者. 你只能想象程序运行时怎么会出现这样的错误!我们需要以某种方式避免这种情况。我们需要限制我们的程序:在创建新Car时对象,我们希望始终指定字段,例如模型和最大速度。否则,我们要阻止对象的创建。构造函数可以轻松处理此任务。他们得名是有原因的。构造函数创建了一种每个新对象都必须匹配的类“骨架”。为了方便起见,让我们回到具有两个字段的Car类的更简单版本。考虑到我们的要求,Car类的构造函数将如下所示:

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}

// And creating an object now looks like this:

public static void main(String[] args) {
   Car bugatti = new Car("Bugatti Veyron", 378);
}
注意构造函数是如何声明的。它类似于常规方法,但没有返回类型。此外,构造函数指定以大写字母开头的类名 ( Car )。此外,构造函数与对您来说是新的关键字一起使用:this。关键字this用于指示特定对象。构造函数中的代码

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}
几乎 可以逐字解释:“这辆车的模型(我们现在正在创建的)是传递给构造函数的模型参数。这辆车(我们正在创建的)的 maxSpeed 是传递给建设者。” 这就是发生的事情:

public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car("Bugatti Veyron", 378);
       System.out.println(bugatti.model);
       System.out.println(bugatti.maxSpeed);
   }

}
控制台输出: Bugatti Veyron 378 构造函数正确分配了所需的值。您可能已经注意到构造函数与普通方法非常相似!就是这样。构造函数实际上是一种方法,但具有特定的功能 :) 就像方法一样,我们将参数传递给构造函数。就像调用方法一样,调用构造函数将不起作用,除非您指定它们:

public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car(); // Error!
   }
  
}
您可以看到构造函数完成了我们试图实现的目标。现在你不能创造没有速度或模型的汽车!构造函数和方法之间的相似性并没有就此结束。就像方法一样,构造函数可以被重载。假设您家里有 2 只宠物猫。你得到了其中一只小猫。但是第二棵是你从街上拿来的,当时它已经长大了,你不知道它到底有多大。在这种情况下,我们希望我们的程序能够创建两种猫:有名字和年龄的猫(第一只猫)和只有名字的猫(第二只猫)。为此,我们将重载构造函数:

public class Cat {

   String name;
   int age;

   // For the first cat
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   // For the second cat
   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5);
       Cat streetCatNamedBob = new Cat("Bob");
   }

}
除了带有“name”和“age”参数的原始构造函数之外,我们还添加了一个只有一个 name 参数的构造函数。与我们在之前课程中重载方法的方式完全相同。现在我们可以创建两种猫 :)
为什么我们需要构造函数? - 2
还记得我们在课程开始时说过您已经在不知不觉中使用过构造函数了吗?我们是认真的。事实上,Java 中的每个类都有所谓的默认构造函数。它不接受任何参数,但每次您创建任何类的任何对象时都会调用它。

public class Cat {

   public static void main(String[] args) {

       Cat smudge = new Cat(); // The default constructor is invoked here
   }
}
乍一看,它是看不见的。我们创建了一个对象,那又如何呢?构造函数在这里做什么?为了看到它,让我们显式地为Cat类编写一个空的构造函数。我们将在其中显示一些短语。如果显示该短语,则调用了构造函数。

public class Cat {

   public Cat() {
       System.out.println("A cat has been created!");
   }

   public static void main(String[] args) {

       Cat smudge = new Cat(); // The default constructor is invoked here
   }
}
控制台输出: 已创建一只猫! 有确认!默认构造函数总是不可见地存在于您的类中。但是你还需要知道一件事。一旦创建带参数的构造函数,默认构造函数就会从类中删除。事实上,我们已经在上面看到了这方面的证据。它在这段代码中:

public class Cat {

   String name;
   int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat(); //Error!
   }
}
我们不能创建没有名字和年龄的猫,因为我们声明了一个带有字符串整数参数的Cat构造函数。这导致默认构造函数立即从类中消失。所以一定要记住,如果你的类中需要多个构造函数,包括一个无参构造函数,你必须单独声明它。例如,假设我们正在为兽医诊所创建一个程序。我们诊所希望做好事,帮助那些名字和年龄不详的无家可归的小猫。那么我们的代码应该是这样的:

public class Cat {

   String name;
   int age;

   // For cats with owners
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   // For street cats
   public Cat() {
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5);
       Cat streetCat = new Cat();
   }
}
现在我们已经编写了显式默认构造函数,我们可以创建两种类型的猫 :) 与任何方法一样,传递给构造函数的参数顺序非常重要。让我们在构造函数中 交换nameage参数。

public class Cat {

   String name;
   int age;

   public Cat(int age, String name) {
       this.name = name;
       this.age = age;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 10); // Error!
   }
}
一个错误!构造函数明确规定,创建Cat对象时,必须依次传递一个数字和一个字符串。所以,我们的代码不起作用。一定要记住这一点,并在声明自己的类时牢记这一点:

public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}

public Cat(int age, String name) {
   this.age = age;
   this.name = name;
}
这是两个完全不同的构造函数!如果我们要用一句话来回答“为什么我需要构造函数?”这个问题,我们可能会说,“确保对象始终具有有效状态”。当您使用构造函数时,您的所有变量都将被正确初始化。您的程序不会有任何速度为 0 的汽车或任何其他“无效”对象。它们的主要好处是对程序员而言。如果您手动初始化字段(在创建对象之后),您很可能会遗漏某些内容并引入错误。但这不会发生在构造函数中:如果您未能传递所有必需的参数或传递了错误类型的参数,编译器将立即注册一个错误。我们还必须单独说你不应该把你的程序' 构造函数中的逻辑。这就是方法的用途。方法是您应该定义所有必需功能的地方。让我们看看为什么向构造函数添加逻辑不是一个好主意:

public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Our car factory is called " + this.name);
   System.out.println("It was founded " + this.age + " years ago" );
   System.out.println("Since that time, it has produced " + this.carsCount +  " cars");
   System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}

   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Ford", 115 , 50000000);
   }
}
我们有一个描述汽车工厂的CarFactory类。在构造函数中,我们初始化所有字段并包含一些逻辑:我们显示有关工厂的一些信息。好像这没什么不好的。该程序运行良好。控制台输出: 我们的汽车厂叫福特,它成立于115年前,从那时起,它生产了5000万辆汽车平均每年生产434782辆汽车 但我们实际上埋下了一个延时地雷。而且这种代码很容易导致错误。假设现在我们谈论的不是福特,而是一家名为“Amigo Motors”的新工厂,它成立不到一年,已经生产了 1000 辆汽车:

public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Our car factor is called " + this.name);
   System.out.println("It was founded " + this.age + " years ago" );
   System.out.println("Since that time, it has produced " + this.carsCount +  " cars");
   System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}


   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Amigo Motors", 0 , 1000);
   }
}
控制台输出: 我们的汽车工厂名为 Amigo Motors Exception in thread "main" java.lang.ArithmeticException: / by zero 它成立于 0 年前,从那时起,它已经 在 CarFactory 生产了 1000 辆汽车。 (CarFactory.java:15) 在 CarFactory.main(CarFactory.java:23) 进程完成,退出代码为 1 繁荣!该程序以某种无法理解的错误结束。你能试着猜出原因吗?问题出在我们放入构造函数的逻辑中。更具体地说,这一行:

System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
在这里,您正在执行计算并将生产的汽车数量除以工厂的年龄。由于我们的工厂是新工厂(即 0 岁),我们除以 0,这在数学上是做不到的。因此,程序因错误而终止。

我们应该做什么?

将所有逻辑放在一个单独的方法中。我们称它为printFactoryInfo()。您可以将CarFactory对象作为参数传递给它。您可以将所有逻辑放在那里,同时处理潜在的错误(比如我们涉及零年的错误)。每个人都有自己的。需要构造函数来设置有效的对象状态。我们有业务逻辑的方法。不要将一个与另一个混在一起。 为了巩固您所学的知识,我们建议您观看我们的 Java 课程中的视频课程