JavaRush /Java 博客 /Random-ZH /Java 中的构造函数

Java 中的构造函数

已在 Random-ZH 群组中发布
你好!今天我们将讨论一个与我们的对象有关的非常重要的主题。在这里,我们可以毫不夸张地说,你每天都会在实际工作中运用到这些知识!我们将讨论构造函数。您可能是第一次听到这个术语,但实际上您可能使用过构造函数,只是您自己没有注意到:) 我们稍后会看到这一点。

Java 中的构造函数是什么以及为什么需要它?

让我们看两个例子。
public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

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

   }
}
我们创建了我们的汽车并设置了它的模型和最大速度。然而,在实际项目中, Car对象显然会有超过 2 个字段。例如,16 个字段!
public class Car {

   String model;//model
   int maxSpeed;//max speed
   int wheels;// disk width
   double engineVolume;//engine capacity
   String color;//color
   int yearOfIssue;//year of issue
   String ownerFirstName;//Owner's name
   String ownerLastName;//owner's last name
   long price;//price
   boolean isNew;//new or not
   int placesInTheSalon;//number of seats in the cabin
   String salonMaterial;// interior material
   boolean insurance;//is it insured
   String manufacturerCountry;//manufacturer country
   int trunkVolume;// trunk volume
   int accelerationTo100km;//acceleration 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.yearOfIssue = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.placesInTheSalon = 2;
       bugatti.maxSpeed = 407;
       bugatti.model = "Bugatti Veyron";

   }

}
我们创建了一个新的Car对象。一个问题:我们有 16 个字段,但我们只初始化了 12 个!现在尝试使用代码来查找我们忘记的那些!没那么容易,对吧?在这种情况下,程序员很容易犯错误并跳过某些字段的初始化。结果,程序行为将变得错误:
public class Car {

   String model;//model
   int maxSpeed;//max speed
   int wheels;// disk width
   double engineVolume;//engine capacity
   String color;//color
   int yearOfIssue;//year of issue
   String ownerFirstName;//Owner's name
   String ownerLastName;//owner's last name
   long price;//price
   boolean isNew;//new or not
   int placesInTheSalon;//number of seats in the cabin
   String salonMaterial;// interior material
   boolean insurance;//is it insured
   String manufacturerCountry;//manufacturer country
   int trunkVolume;// trunk volume
   int accelerationTo100km;//acceleration 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.yearOfIssue = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.placesInTheSalon = 2;
       bugatti.maxSpeed = 407;
       bugatti.model = "Bugatti Veyron";

       System.out.println("Model Bugatti Veyron. Engine size - " + bugatti.engineVolume + ", trunk - " + bugatti.trunkVolume + ", salon is made of" + bugatti.salonMaterial +
       ", disc width - " + bugatti.wheels + ". Was acquired in 2018 by Mr. " + bugatti.ownerLastName);

   }

}
控制台输出:
布加迪威龙模型。发动机排量 - 6.3,后备箱 - 0,内饰由 null 制成,轮圈宽度 - 0。由 null 先生于 2018 年购买
你的买家花了 200 万美元买了一辆车,显然不喜欢被称为“空先生”!但说真的,最后我们的程序最终得到了一个错误创建的对象 - 一辆轮辋宽度为 0(即根本没有轮辋)的汽车,一个丢失的行李箱,由未知材料制成的内饰,甚至属于未知的人。可以想象,在程序运行时怎么会出现这样的错误!我们需要以某种方式避免这种情况。我们需要我们的程序有一个限制:例如,当创建一个新的车辆对象时,必须始终为其指定模型和最大速度。否则,不允许创建对象。构造函数可以轻松应对这项任务。他们的名字是有原因的。构造函数创建类的一种“骨架”,类的每个新对象都必须与其相对应。为了方便起见,让我们回到具有两个字段的Car类的简单版本。根据我们的要求, Car类的构造函数将如下所示:
public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}
创建一个对象现在看起来像这样:
public static void main(String[] args) {
   Car bugatti = new Car("Bugatti Veyron", 407);
}
注意构造函数是如何创建的。它与常规方法类似,但没有返回类型。在这种情况下,类名在构造函数中指示,也使用大写字母。在我们的例子中 -汽车。此外,构造函数使用 new-to-you 关键字this。“this”在英语中的意思是“这个,这个”。这个词指的是一个特定的对象。构造函数中的代码:
public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = 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", 407);
       System.out.println(bugatti.model);
       System.out.println(bugatti.maxSpeed);
   }

}
控制台输出:
布加迪威龙 407
构造函数成功分配了所需的值。您可能已经注意到构造函数与常规方法非常相似!就是这样:构造函数是一个方法,只是有点具体:) 就像在方法中一样,我们将参数传递给构造函数。就像调用方法一样,如果不指定构造函数,调用构造函数将不起作用:
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!
   }

}
你看,设计师做到了我们想要实现的目标。现在,没有速度或没有模型就无法制造汽车!构造函数和方法之间的相似之处还不止于此。就像方法一样,构造函数可以重载。想象一下你家里有两只猫。你把其中一只当成小猫了,当你把第二只成年后从街上带回家时,你不知道他到底有多大。这意味着我们的程序应该能够创建两种类型的猫 - 第一只猫有名字和年龄,第二只猫只有名字。为此,我们将重载构造函数:
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 barsik = new Cat("Barsik", 5);
       Cat streetCatNamedBob = new Cat("Bob");
   }

}
对于带有参数“name”和“age”的原始构造函数,我们添加了另一个仅带有名称的构造函数。我们在前面的课程中以相同的方式重载了方法。现在我们可以成功创建两个版本的猫了:) 为什么需要构造函数? - 2你还记得在讲座开始时我们说过你已经使用过构造函数,但你只是没有注意到它吗?这是真实的。事实上,Java 中的每个类都有一个所谓的默认构造函数。它没有任何参数,但每次创建任何类的任何对象时都会触发它。
public class Cat {

   public static void main(String[] args) {

       Cat barsik = new Cat(); //this is where the default constructor worked
   }
}
乍一看这并不明显。好了,我们创建了一个对象并创建了它,设计师的工作在哪里呢?为了看到这一点,让我们亲手为Cat类编写一个空的构造函数,并在其中将一些短语打印到控制台。如果显示出来,那么构造函数已经工作了。
public class Cat {

   public Cat() {
       System.out.println("Created a cat!");
   }

   public static void main(String[] args) {

       Cat barsik = new Cat(); //this is where the default constructor worked
   }
}
控制台输出:
他们创造了一只猫!
这是确认!默认构造函数始终不可见地存在于您的类中。但您还需要了解它的另一项功能。当您创建带有参数的构造函数时,默认构造函数将从类中消失。这一点的证明,其实我们在上面已经看到了。在此代码中:
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 barsik = new Cat(); //error!
   }
}
我们无法创建没有名字和年龄的猫,因为我们为Cat定义了一个构造函数:字符串 + 数字。此后,默认构造函数立即从类中消失。因此,请务必记住:如果您的类中需要多个构造函数(包括一个空构造函数),则需要单独创建它。例如,我们正在为兽医诊所创建一个程序。我们的诊所希望做好事并帮助无家可归的猫,我们不知道它们的名字或年龄。那么我们的代码应该是这样的:
public class Cat {

   String name;
   int age;

   //for domestic cats
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   //for street cats
   public Cat() {
   }

   public static void main(String[] args) {

       Cat barsik = new Cat("Barsik", 5);
       Cat streetCat = new Cat();
   }
}
现在我们已经显式地编写了默认构造函数,我们可以创建两种类型的猫:) 对于构造函数(对于任何方法),参数的顺序非常重要。让我们在构造函数中交换名称和年龄参数。
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 barsik = new Cat("Barsik", 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("She was founded" + this.age + " years ago" );
   System.out.println("During this time it was produced" + this.carsCount +  "cars");
   System.out.println("On average she produces" + (this.carsCount/this.age) + "cars per year");
}

   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Ford", 115 , 50000000);
   }
}
我们有一个CarFactory类,它描述了一个生产汽车的工厂。在构造函数内,我们初始化所有字段并将逻辑放置在这里:我们向控制台显示有关工厂的一些信息。看起来这并没有什么问题,程序运行得很完美。控制台输出:
我们的汽车厂叫福特,成立已有115年了,这段时间生产了50,000,000辆汽车,平均每年生产434,782辆汽车。
但事实上,我们已经埋下了定时炸弹。而且这样的代码很容易导致错误。让我们想象一下,现在我们谈论的不是福特,而是新工厂“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 factory is called" + this.name);
   System.out.println("She was founded" + this.age + " years ago" );
   System.out.println("During this time it was produced" + this.carsCount +  "cars");
   System.out.println("On average she produces" + (this.carsCount/this.age) + "cars per year");
}


   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Amigo Motors", 0 , 1000);
   }
}
控制台输出:
我们的汽车工厂名为 Amigo Motors 异常,位于线程“main” java.lang.ArithmeticException: / by Zero 它成立于 0 年前 在此期间,它在 CarFactory.<init>(CarFactory.java:15) 生产了 1000 辆汽车CarFactory.main(CarFactory.java:23) 进程完成,退出代码 1</init>
我们到了!程序因一些奇怪的错误而结束。你会试着猜猜原因是什么吗?原因是我们放置在构造函数中的逻辑。具体来说,在这一行中:
System.out.println("On average she produces" + (this.carsCount/this.age) + "cars per year");
这里我们进行计算,并将生产的汽车数量除以工厂的年龄。而且由于我们的工厂是新的(即0年),所以结果除以0,这在数学中是禁止的。结果,程序因错误而终止。我们应该做什么?将所有逻辑移至单独的方法中并调用它,例如printFactoryInfo()您可以将CarFactory对象作为参数传递给它。您还可以将所有逻辑放在那里,同时处理可能的错误,就像我们零年的错误一样。每个人都有自己的。需要构造函数来正确设置对象的状态。对于业务逻辑,我们有方法。你不应该将其中一种与另一种混合。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION