1. 构造器
1.1 简介
忘记初始化是程序产生错误的重要原因之一。Java通过提供构造器来确保每个对象都会得到初始化。
构造器的名称与类的名称相同:
class Dog{
Dog() {//构造器名Dog与类名相同
System.out.println("成功调用了构造器Dog()");
}
}
public class Consructor {
public static void main(String[] args) {
Dog dog_a = new Dog();
}
}
创建对象时通过使用new Dog()为对象分配存储空间,调用构造器来确保这个对象已经被初始化了。
构造器没有返回值,这与void不同,构造器本身不会返回任何东西。
1.2 默认构造器
如果在你写的类中没有构造器,那么编译器会自动帮你创建一个默认的构造器:
class Bird{}
public class DefaultConstructor{
public static void main(String[] args){
Bird b = new Bird();
}
}
尽管在上面的Bird类中我们没有写构造器,但是我们依然可以调用Bird(),说明编译器为我们自动创建了一个默认的构造器。
默认的构造器没有参数。
如果你已经在类中写了一个自定义的构造器,那么编译器就不会再自动创建默认构造器:
class Bird{
Bird(int i){ }
}
public class DefaultConstructor{
public static void main(String[] args){
//Bird b = new Bird();此行会报错
Bird b = new Bird(1);
}
}
1.3 自定义构造器
为了指定如何创建对象(比如想要在调用构造器时有一条输出语句,或者给类中的域赋值),你可以使用自定义的构造器:
class Dog{
int age;
String name;
Dog(int i,String s){
age = i;
name = s;
}
}
public class Consructor {
public static void main(String[] args) {
// TODO Auto-generated method stub
Dog dog = new Dog(2, "bool");
}
}
2. this关键字
2.1 在方法内部使用this
在调用方法的时候,编译器是怎么知道是哪个对象在调用方法呢?我们可以这样理解,在调用方法时,编译器隐含地传入了另一个参数(隐式参数)——调用该方法的引用:
A a = new A();
a.print(i);//可以理解为A.print(a,i),但是写代码时并不能这么写,这里只是为了理解
//事实上,a.print(i)前面的a就说明了这个隐式参数。
而当我们需要在方法内部调用当前对象的引用时,就要用到this关键字:
class People{
void eat(Food food) {
Food maked_food = food.getFood();
System.out.println("Nice!");
}
}
class Cook{
static Food makeFood(Food food) {
return food;
}
}
class Food{
Food getFood() {
return Cook.makeFood(this);//this表示当前调用这个getFood()方法的Food类对象
}
}
public class ThisKeyWord {
public static void main(String[] args) {
new People().eat(new Food());
}
}
Food需要调用Cook中的makeFood方法,并把自身传递给外部方法,此时就需要用到this。
当需要返回该对象的引用时,也可以使用this:
public class Leaf {
int i = 0;
Leaf increment() {
i++;
return this;//this表示当前调用increment()方法的Leaf对象
}
void print() {
System.out.println("i = " + i);
}
public static void main(String[] args) {
Leaf x = new Leaf();
x.increment().increment().increment().print();
}
}//代码引自《Java编程思想》
由于increment方法返回的是this,也就是一个Leaf类的对象,即x.increment()等价于一个x,因此可以x.increment().increment().increment().print()。
2.2 在构造器中使用this
我们可以在构造器中使用this添加了参数的this来调用其它构造器:
class Word{
int length;
char initials;
Word() {
System.out.println("a new word");
}
Word(int length,char initials){
this();
//! this(2);会报错:Constructor call must be the first statement in a constructor
this.length = length;
this.initials = initials;
//这里展示了this的另一种用法:由于参数s的名称和数据成员s的名字相同,
//所以会产生歧义。使用this.s来代表数据成员就能解决这个问题。
}
Word(int i) {
System.out.println("a new word whose length is"+ i);
}
void print() {
//! this();会报错:Constructor call must be the first statement in a constructor
System.out.println("the length of this word is "+ this.length+
",the initials is "+this.initials);//这里的this是前面讲的在一般方法中使用this
}
}
public class ThisKeyWord2 {
public static void main(String[] args) {
Word word = new Word(3,'i');
word.print();
//输出为:
//a new word
//the length of this word is 3,the initials is i
}
}
从上面的代码可以看到,通过使用“this + () + ()内可能存在的参数”这样一个形式,可以首先在构造器内调用一次含对应参数的其它构造器。
同时应注意以下几点:
- 在调用构造器时必须将调用的this语句放在最前面。
- 只能在构造器内调用一次其他构造器(这条与上条其实是相关联的,如果存在两个调用构造器语句的话,必然有一个不可能放在最前面,这与第一条违背)。
- 只能在构造器内调用构造器,在一般方法里面不能调用构造器。
由这三条再来看上面代码中的报错语句:Constructor call must be the first statement in a constructor,“the first statement”要求了第一、二条,“in a constructor”要求了第三条。
3. 方法重载
3.1 方法签名
方法名和参数列表被称为方法签名,它们唯一地标识了一个方法。
3.2 方法重载
从前面构造器地例子可以看到,我们在Word类当中使用了好几个构造器,由于构造器的名称一定与类名相同,也就是说有好几个名称都为Word的构造器。这说明Java必须要允许在一个类中有一些方法具有相同的方法名,那么怎么区分这些方法名相同的方法呢?
答案是:每个重载的方法都必须有一个独一无二的参数类型列表。
就比如上面的Word类:
Word() {
System.out.println("a new word");
}
Word(int length,char initials){
this();
this.length = length;
this.initials = initials;
}
Word(int i) {
System.out.println("a new word whose length is"+ i);
}
我们有三个Word构造器,而它们的参数列表是不同的,一个为空,一个为int和char,一个为int。因此,通过参数列表的不同,我们得以区分重载的方法。
事实上,参数列表的顺序不同也可以区分重载的方法,但是一般不推荐这么使用:
void ol(int a,char b) {}
void ol(char b,int a) {}
//这样的两个方法也构成重载
但是方法的返回值不允许被用来区分重载的方法:
//下面的代码会报错:
//int ol(int a,char b) {return a;}
//! void ol(int a,char b) {}
//因为有时候方法的返回值并不一定会被使用,你可能会这样使用一个方法:ol()
//那么这个方法到底是int还是void类型的呢?
3.3 涉及基本类型的重载
由于基本类型能自动地从较小的类型转换成较大的类型,因此可能会造成混淆:
public class OverLoad {
static void sh(short a) {
System.out.println("short:"+a);
}
static void sh(int a) {
System.out.println("int:"+a);
}
public static void main(String[] args) {
short b = 5;
sh(5);//结果为int:5,如果你把static void sh(int a)这个方法注释掉,本行就会报错
sh((short)5);//结果为short:5
sh(b);//结果为short:5
}
}
当你输入一个常量5时,其默认是int类型的,因此,如果你想要把一个常量当作比int类型小的类型(short,byte)来作为形式参数,你可能需要用到强制类型转换或者直接输入一个等于5的short类型变量。
如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会被提升:
public class OverLoad {
static void sh(short a) {
System.out.println("short:"+a);
}
static void sh(double a) {
System.out.println("double:"+a);
}
public static void main(String[] args) {
sh(5);//输出为double:5.0
//输入的int类型的5因为找不到参数类型为int的sh()方法
//因此会按类型大小的顺序(long,float,double)逐个提升直到找到有该类型的sh()方法
sh((byte)5);//输出为short:5,道理与上面一样,由于没有byte sh(),所以提升到short
}
}
char类型是个例外,如果无法找到恰好接受char参数的方法,它会被直接提升为int类型:
public class OverLoad {
static void sh(short a) {
System.out.println("short:"+a);
}
static void sh(int a) {
System.out.println("int:"+a);
}
public static void main(String[] args) {
sh('a');//输出为int:97.这是由于char类型的'a'被直接转换成了int。
}
}
如果你想要将一个大范围的数据类型的实际参数传入小范围数据类型的形式参数,则必须进行强制类型转换否则会报错:
public class OverLoad {
static void sh(short a) {
System.out.println("short:"+a);
}
static void sh(int a) {
System.out.println("int:"+a);
}
public static void main(String[] args) {
//! sh(5.0);代码报错。5.0是double类型的参数,要想传入成更小的int或short,必须进行类型转换
sh((int)5.0);
sh((short)5.0);
}
}