前言:
上篇文章中我们已经简单的对java中面向对象的基本思想有所了解了。初步了解了,什么是面向对象,什么是类,什么是对象以及如何简单的对对象进行访问初始化。但是在上篇文章的最后,我们在“如何更加方便快捷的对对象进行初始化”这一点上留下了些许疑问。接下来我们就一起来“填坑”,具体了解一下如何方便快捷地用构造器对对象进行初始化吧!
必备知识点:
在我们正式来介绍什么是构造器之前,我们还需要做一些准备工作。我们得先学习一下方法重载相关的知识。
方法的签名
一个方法的方法名称和参数列表所组成的整体被称作为方法的签名。
class A
{
void f(String str){}
}
像上面代码中的:
- f: 方法名称
- (String str):参数列表
由方法名称和参数列表所组成的f(String str)这一个整体就称为方法的签名。
注意:方法的签名不包含返回类型哦!!
方法的重载
基本概念
在同一个类中,有多个同名的方法,但是方法参数列表互不相同的一种情况。(即方法名称相同,方法的签名却不同)
如:
class A
{
void f(String str)
{
System.out.println("我的参数是字符串");
}
void f(int a)
{
System.out.println("我的参数是整型数字");
}
}
上面代码中具有2个同名的f方法,但是他们的参数列表不相同(因此方法签名也不相同):这种情况也就是方法的重载。当我们调用方法时,编译器编译时会根据我们所传入的实参来跟形参进行自动匹配,然后选择相对应的方法进行执行。
那么我们来写个TestDemo来测试一下:
class TestDemo
{
pubulic static void main(String[] args)
{
A a = new A();//先实例化一个A类的对象a
a.f("aaaaaa");//对类的成员进行访问,下同
a.f(12);
}
}
那么屏幕将会先后输出:
我的参数是字符串
我的参数是整型数字
这2行字符串。
但是我们如果稍微将A类进行修改:
class A
{
int f(int str)
{
System.out.println("我的参数是字符串");
}
void f(int a)
{
System.out.println("我的参数是整型数字");
}
}
这时候编译器就会报错了。
大概就是说f方法已经存在,不能再这样写了。
为什么会这样呢?不是方法参数列表不同吗?而且方法的返回类型也不同啊!
首先出现这种问题有好几个原因:
- 在编译器眼里,在方法同名的情况下,它们只认参数列表参数类型,并不会管你的参数列表的参数名称
- 编译器其实并没有那么“智能”,它无法根据方法的返回值来选择要执行的方法。再通俗点来说,在方法同名,参数列表相同的情况下,它无法根据返回值类型来选择要执行的方法,它不知道要执行哪个方法,当然报错啦。所以说要实现方法重载,首先必须得保证的你的方法签名不同。
很多初学者都很容易在这里犯错,这是他们没有真正理解方法重载的概念导致的。所以我才在上面介绍方法签名的时候强调要注意:方法签名跟方法返回类型无关。
构造器
好了,前期的铺垫工作已经做的差不多了,可以说这篇文章的重点内容了——构造器。
构造器是用来对类中的成员变量进行初始化赋值的一种特殊的函数。
如:
class Student
{
String name;
int id;
Student(String n,int i)
{
this.name=n;
this.id=i;
}
}
以上代码中的Student方法就是一个构造器,编译器通常在我们new对象时,为对象分配实际空间时会对它自动进行调用进行初始化。
在测试类中的初始化代码如下:
Student zs=new Student("张三",10086);
这就相当于我们把之前:
zs.name="张三";
zs.id=10086;
这两步初始化赋值操作放在构造对象的括号里,在初始化建立对象的同时进行了赋值操作。
也就是说这一行代码可以等于原来的:
class Student
{
String name;
int id;
}
class Test
{
public static void main(String[] args)
{
Student zs=new Student();//1
zs.name="张三";//2
zs.id=10086;//3
}
}
有数字标记的这三行代码。
它们都是起到了对对象属性进行初始化的作用。
怎么样?是不是觉得代码简单方便(偷懒)了很多?
这时候有人可能会有疑问了?为什么在我们原来没有写构造方法的时候,它也能自动构造一个默认的对象呢?就像上面被标记为1的代码一样,不也是调用了Student()构造方法吗?
是这样的,当我们没有手动给类写上构造方法时,编译器会自动给该类加上一个默认构造方法,以实现一个“空皮囊”对象的构造,但是它什么也不会做,所以说类中的那些属性值仍是编译器给的默认的值(引用类型变量默认值是null,int型默认值为0,double型默认值为0.0)。
但是需要注意的是:当我们已经有手动写上了个构造方法(时,编译器将不会再给提供默认构造方法。故此时我们再用最初的new Student()去定义对象的话,编译器将会报错(大意就是说找不到相应的构造方法)。如
class Student
{
String name;
int id;
Student(String n,int i)
{
name=n;
id=i;
}
}
class Test
{
public static void main(String[] args)
{
Student zs=new Student();//会报错
}
}
此时再用Student zs=new Student();进行实例化对象时,编译器将会报错。
那么再思考一下,构造函数中的参数列表情况
Student(String n,int i)
{
name=n;
id=i;
}
按照我们这部分写的来看,无非是很好的实现了我们对属性的赋值操作。但是从另外一个角度来看,我们是不是无法直接的根据这个形参列表看出我们要传递参数的含义呢?那么这个参数列表是不是写的不够直观呢?我们可不可以将参数列表里的形参名字写成name和id呢?这样我们就可以方便的了解我们要传的参数的实际含义了。
我们先来写写看吧!
Student(String name,int id)
{
name=name;//1
id=id;//2
}
同样的,我们来加点东西测试一下试试看结果:
class Student
{
String name;
int id;
Student(String name,int id)
{
name=name;//1
id=id;//2
}
void sayHi()
{
System.out.println("我的名字是"+name+"我的学号是:"+id);
}
}
class Test
{
public static void main(String[] args)
{
Student zs=new Student("张三",10086);
zs.sayHi();
}
}
等等?这玩意是什么鬼?为啥还是默认值???我们不是已经按照要求初始化了吗?
是这样,两个同名的变量,编译器讲究一个”就近原则“,故把2个同名的变量都当作局部变量来处理了,也就是说,并没有真正成功地赋值给我们类的成员变量!!换句人话来说,就是“把自己赋值给自己!”。显然,这无法实现我们给类的成员变量赋值的需求。
this关键字的使用
因此我们引入了this这个关键字。以上的代码我们也稍微修改一下。
Student(String name,int id)
{
this.name=name;
this.id=id;
}
此时再进行测试运行,就可以得到我们想要的结果了:
在这里,我们需要对this关键字在该处的用法解释一下:
this在这指代了调用该成员的对象,可以理解为zs.name=name;(这样比较好理解)
这样做的话,就可以精准无误的定位到要赋值的成员变量啦!
那么我们再拓展说明一下:
其实在类中调用成员变量的完整写法都是:this.属性名。
就拿sayHi()方法来举例吧!它的完整写法其实是:
void sayHi()
{
System.out.println("我的名字是"+this.name+"我的学号是:"+this.id);
}
我们可以写成:
void sayHi()
{
System.out.println("我的名字是"+name+"我的学号是:"+id);
}
这种写法,不过是在没有重名冲突的情况下,编译器可以帮我们实现的简写操作罢了。实际上对于我们写的这种简写的写法,编译器编译的时候还是会自动帮我们补成完整版的。
同理this.关键字还可以用在调用方法上,如this.sayHi()。但是由于用的很少,而且原理也跟this.成员变量名的原理一样,我就不多赘述了。
this关键字的一点点拓展
this关键字除了用this.成员名调用该对象的成员属性之外,还可以用this()调用成员构造方法呢!
那么我们来举个例子,还是上面的代码,不过我们要加点东西。
class Student
{
String name;
int id;
Student(String name)
{
this(name,3);
}
Student(String name,int id)
{
this.name=name;
this.id=id;
}
void sayHi()
{
System.out.println("我的名字是"+name+"我的学号是:"+id);
}
}
class Test
{
public static void main(String[] args)
{
Student zs=new Student("张三");
zs.sayHi();
}
}
在这个例子中,我用函数重载又加了个只有一个参数的构造方法。
然后我在实例化对象的时候也只传了一个参数,所以编译器将自动调用那个只有一个参数的构造方法(只初始化一个属性)。
按照我们上面解释的内容来说,我后面调用sayHi方法输出的应该是:
我的名字是张三我的学号是:0
但是实际的运行结果却是:
结果显示,2个属性都被初始化了。这是为什么呢?
仔细观察,
Student(String name)
{
this(name,3);
}
调用一个参数的构造方法时,多了一句:this(name,3);语句
看过结果的我们很容易就能明白,就是这个语句调用了我们2个参数的构造方法,也就是说最后还是用的2个参数的构造方法进行的初始化。