写在前面:博主是一只经过实战开发历练后投身培训事业的“小山猪”,昵称取自动画片《狮子王》中的“彭彭”,总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域,如今终有小成,愿将昔日所获与大家交流一二,希望对学习路上的你有所助益。同时,博主也想通过此次尝试打造一个完善的技术图书馆,任何与文章技术点有关的异常、错误、注意事项均会在末尾列出,欢迎大家通过各种方式提供素材。
本文关键字:对象、初始化、实例化、构造器、构造方法
明确了类与对象的关系后,我们知道:类只是一个定义的结构,用来表述我们想要描述的事物,即具备哪些属性(成员变量),可以产生哪些行为(方法)。那么具体行为的发生,也就是方法的调用要靠对象来完成,同时属性值也要附着在对象上才有意义。创建对象的过程被叫做类的实例化,或者称为对象的初始化,在这个过程中需要使用的就是new关键字和类的构造器。
对于相关概念还不清楚的同学请进传送门:Java中的基本操作单元 - 类和对象
没错,他们都是同一个意思。
构造器本身更像一种方法,因此定义的格式与方法类似,可以区别着进行记忆。构造器同样被定义在class的大括号内,构造器的定义格式如下:
public class className{
// 构造器定义开始
[权限修饰符] 类名(参数列表){
// 代码部分
}
// 构造器定义结束
}
从以上的结构我们看到,构造器的声明格式与方法类似:
那么,在这个构造方法当中我们都应该写些什么呢?还是从构造器的作用入手,既然他的作用是初始化一个对象,那么对象在初始化时最需要做的就是对属性赋值,所以如果有需要我们会在调用时传入某些属性的初始值,或者在对象初始化时执行某些代码,帮助我们判断对象初始化的状态。
public class Student{
public Student(){
System.out.println("学生对象初始化成功");
// 其他代码
}
}
public class Test{
public static void main(String[] args){
Student student = new Student();
// 执行效果 -> 输出:学生对象初始化成功
}
}
对于创建对象时为属性赋值的用法将在构造器的重载中演示。
在刚刚开始学习面向对象部分时,可能都会存在一个疑问,之前定义的class都没有定义构造器呀,不是一样可以通过new来创建实例吗?这是因为当一个类被定义后,如果没有手动的创建任何的构造方法,会默认提供一个空的构造器,供初始化使用,这个过程是编译时完成的。
public class Person{
}
我们对Person类进行编译,得到Person.class文件,然后我们对class文件进行反编译,就可以看到已经出现了一个空的构造器:
Java程序在执行时,加载的都是.class文件,并且所生成的.class文件与我们定义的.java文件一般都是存在差异的。所以这就能够解释,为什么明明我们在.java文件中没有定义构造器,但是在创建对象时却可以使用new调用到。
隐式构造器还有一个特点,就是如果我们已经手动创建了一个无参的构造器,或者一个有参的构造器,那么在编译时就不会生成无参构造器了。
public class Person{
public Person(String name){
System.out.println(name);
}
}
此时,由于我们已经手动指定了一个构造器了,所以在编译时就不会再产生默认的无参构造器了,只会有自己手动定义的构造器:
那么,大家应该也注意到了一个问题,既然用new创建对象时是调用的构造器,那么现在我们自己定义了一个有参数的构造器,那么就会使得我们最常使用的new Person()这种实例化的代码报错,因为此时类中已经没有无参构造器可供调用了,也可以认为无参的构造器被覆盖了,必须要传入一个参数才能初始化对象。
public class Test{
public static void main(String[] args){
Person person = new Person();
// 编译不通过,已经无法调用无参构造器来初始化对象
}
}
那么如果我们还是想用这个无参构造器来创建对象该怎么办呢?没错,手动声明一下就好了,里面不需要写任何内容:
public class Person{
// 无参构造器
public Person(){}
// 有参构造器,可以接收一个参数
public Person(String name){
System.out.println(name);
}
}
我们来看一下效果,很明显,将会同时存在两个构造器,我们在使用new进行对象初始化的时候可以根据需要来使用。
`
public class Test{
public static void main(String[] args){
Person person1 = new Person();
// 编译通过,执行后person1被成功实例化,无输出
Person person2 = new Person("小明");
// 编译通过,执行后person2被成功实例化,输出:小明
}
}
从上面的例子我们已经可以看到,一个类结构中可以存在多个构造器,用于在有不同需要时被调用。而且由于构造器本身的主要作用是用于为类的属性赋初始值,所以在构造器中我们会指定一些参数,用于被调用时传入,为当前类的属性赋值。
public class Person{
// 无参构造器
public Person(){}
// 两参构造器,可以给name和age属性赋值
public Person(String name,int age){
this.name = name;
this.age = age;
}
// 三参构造器,可以给name、age和job属性赋值
public Person(String name,int age,String job){
this.name = name;
this.age = age;
this.job = job;
}
public String name;
public int age;
public String job;
}
在上面的代码中我们可以看到有三个构造器,名称相同,只有参数列表不同,这种关系被称为重载,在方法中也有类似的概念。可以看到构造器中存在部分代码,且都是赋值语句。
this可以指代当前对象,使用this可以调用出直接在类下定义的成员(变量和方法),其中一个最主要的作用就是可以区分同名的变量。我们在进行变量命名时,一直强调见名知意,那么问题就来了:在类中定义的成员变量名称已经确定了,而构造器中传入的参数就是为了给这些属性赋值的,那么参数的名称是不是应该和类成员变量一样才更能表达意思呢?如果这样的话就造成了参数列表中的变量名称与类成员变量的名称同名,这时就可以通过this来区分。
明确了this的用法,我们再来看构造器中的内容就很好理解了,将传入的参数赋值给当前对象的类成员变量,具体的调用过程我们看下面的例子。
src
└──edu
└──sandtower
└──bean
│ Person.java
└──test
│ Test.java
以上为实体类与测试类所在的目录结构,Person实体类所在包:edu.sandtower.bean,Test测试类所在包:edu.sandtower.test,则代码如下:
package edu.sandtower.bean;
public class Person{
// 无参构造器
public Person(){}
// 三参构造器,可以给name、age和job属性赋值
public Person(String name,int age,String job){
this.name = name;
this.age = age;
this.job = job;
}
public String name;
public int age;
public String job;
}
package edu.sandtower.test;
// 导包操作:指明需要使用的Person类的所在位置
import edu.sandtower.bean.Person;
public class Test{
public static void main(String[] args){
Person person1 = new Person();
// person1被成功实例化,各属性无初始值,可以手动赋值
person1.name = "小张";
person1.age = 26;
person1.job = "Linux运维工程师";
Person person2 = new Person("小李",25,"Java开发工程师");
// person2被成功实例化,并具有如下初始值
// name:小李
// age:25
// job:Java开发工程师
// 输出进行验证
System.out.println("name:" + person2.name);
System.out.println("age:" + person2.age);
System.out.println("job:" + person2.job);
}
}
在进行对象的初始化时,可以根据需要取得一个空的对象(如:person1)后手动赋值,也可以通过有参构造器直接对属性赋值(如:person2),避免逐一赋值的麻烦。