05_面向对象基础篇_02-构造方法、匿名对象、对象比较、this关键字
本章章节
> 5.1 面向对象程序设计的基本概念
> 5.2类与对象
> 5.3类的封装性
> 5.4在类内部调用方法
> 5.5引用数据类型的传递
> 5.6构造方法
> 5.7匿名对象
> 5.8对象的比较
> 5.9 this 关键字的使用
> 5.10 static 关键字的使用
> 5.11构造方法的私有
> 5.12对象数组的使用
> 5.13 Java基本数据类型的包装类
> 5.14 String类的常见方法
> 5.15 Java文档注释
.本章摘要:
5.6 构造方法
在前面的Java程序里,我们将属性封装之后,需要提供公共的getXxx和setXxx函数集来初始化变量和获取变量。利用构造方法也可以实现给新创建的对象的各个成员属性赋初值。构造方法可视为一种特殊的方法,它的定义方式与普通方法类似,其语法如下所示:
<修饰符> class 类名称 { 访问权限 类名称(类型1 参数1,类型2 参数2,…) { 程序语句; … // 构造方法没有返回值 } }
在使用构造方法的时候请注意以下几点:
1、构造方法的名称与类的名称相同
2、构造方法没有返回值
3、构造方法可以重载
如上所述,构造方法除了没有返回值,且名称必须与类的名称相同之外,它的调用时机也与一般的方法不同。一般的方法是在需要时才调用,而构造方法则是在创建对象时,便自动调用,并执行构造方法的内容。因此,构造方法无需在程序中直接调用,而是在对象产生时自动执行。
基于上述构造方法的特性,可利用它来对对象的数据成员做初始化的赋值。所谓初始化就是为对象的赋初值。
下面的程序TestConstruct.java说明了构造方法的使用。
范例:TestConstruct.java
class Person { public Person() // Person类的构造方法 { System.out.println("public Person()"); } } public class TestConstruct { public static void main(String[] args) { Person p = new Person(); } }
输出结果:
public Person()
程序说明:
1、程序1~7行声明了一个Person类,此类中只有一个Person的构造方法。
2、程序3~6行声明了一个Person类的构造方法,此方法只含有一个输出语句。
3、程序第12行实例化一个Person类的对象p,此时会自动调用Person中的无参构造方法,即在屏幕上打印信息。
从程序中不难发现,在类中声明的构造方法,会在实例化对象时自动调用。你可能会问,在之前的程序中用同样的方法来产生对象,但是在类中并没有声明任何构造方法,而程序不也一样正常运行了吗?
实际上,在执行javac编译java程序的时候,如果在程序中没有明确声明构造方法的话,系统会自动为类加入一个无参的且什么都不做的构造方法。类似于下面代码:
public Person(){}
所以,之前所使用的程序虽然没有明确的声明构造方法,也是可以正常运行的。但是需要注意的是,一旦用户自己写了构造函数之后,系统就不会再提供默认的构造函数。
5.6.1 构造方法的重载
在Java里,不仅普通方法可以重载,构造方法也可以重载。只要构造方法的参数个数不同,或是类型不同或顺序不同,便可定义多个名称相同的构造方法。这种做法在java中是常见的,请看下面的程序。
范例:TestConstruct1.java
class Person { private String name; private int age;
public Person(String n, int a) { name = n; age = a; System.out.println("public Person(String n, int a)"); } public String talk() { return "我是:"+name+",今年:"+age+"岁"; } } public class TestConstruct1 { public static void main(String[] args) { Person p = new Person("张三", 25); System.out.println(p.talk()); //Person p2 = new Person();//加入此行会报错,因为没有无参的构造函数 //System.out.println(p2.talk()); } }
输出结果:
public Person(String n, int a)
我是:张三,今年:25岁
程序说明:
1、程序1~15行声明了一个Person类,里面有name与age两个私有属性,和一个talk()方法。
2、程序第5~10行,在Person类中声明一个含有两个参数的构造方法,此构造方法用于将传入的值赋给Person类的属性。
3、程序第20行调用Person类中有的含有两个参数的构造方法“new Person("张三",25)”。
4、程序第21行调用Person类中的talk()方法,打印信息。
从本程序可以发现,构造方法的基本作用就是为类中的属性初始化的,在程序产生类的实例对象时,将需要的参数由构造方法传入,之后再由构造方法为其内部的属性进行初始化。这是在一般开发中经常使用的技巧。但是如果加入22、23行的代码就会报错。因为在java程序中只要明确的声明了构造方法,则默认的构造方法将不会被自动生成。而要解决这一问题,只需要简单的修改一下Person类就可以了,可以在Person类中明确地声明一个无参的且什么都不做的构造方法。
范例:TestConstruct2.java
class Person { private String name; private int age; public Person() { } public Person(String n, int a) { name = n; age = a; System.out.println("public Person(String n,int a)"); } public String talk() { return "我是:" + name + ",今年:" + age + "岁"; } } public class TestConstruct2 { public static void main(String[] args) { Person p = new Person(); System.out.println(p.talk()); } }
可以看见,在程序的第5、6行声明了一个无参的且什么都不做的构造方法,此时再编译程序的话,就可以正常编译而不会出现错误了。
5.7 匿名对象
“匿名对象”,顾名思义,就是没有名字的对象。也可以简单的理解为只使用一次的对象,即没有任何一个具体的对象名称引用它。请看下面的范例:
范例:TestNoName.java
class Person { private String name = "张三"; private int age = 25; public String talk() { return "我是:" + name + ",今年:" + age + "岁"; } } public class TestNoName { public static void main(String[] args) { System.out.println(new Person().talk()); } }
输出结果:
我是:张三,今年:25岁
程序说明:
1、程序1~9行声明了一个Person类,里面有name与age两个私有属性,并分别赋了初值。
2、在程序第14行,声明了一个Person匿名对象,调用Person类中的talk()方法。可以发现用new Person()声明的对象并没有赋给任何一个Person类对象的引用,所以此对象只使用了一次,之后就会被Java的垃圾收集器回收。
5.8 对象的比较
Java程序中测试两个变量是否相等有两种方式,一种是利用==运算符,另一种是利用equals方法。
当使用==来判断两个变量是否相等时,如果两个变量是基本数据类型的变量,且都是数值类型,只要两个变量的值相等,就会返回布尔值true。但对于两个引用类型的变量,他们必须指向同一个对象时,才会返回布尔值true。否则,返回false。
equals方法是Object类(所有类的父类)提供的一个实例方法,所有引用变量都可以调用该方法判断是否与其他变量相等。Object类提供的equals方法没有太大意义,默认的equals方法,当两个引用变量指向一个对象才会返回true,跟“==”类似。如果希望采用自定义的相等标准,可以采用重写equals方法来实现。
先以String类为例,String类中已经重写了equals方法。“==”操作符用于比较两个String对象的内存地址值是否相等,equals()方法用于比较两个String对象的内容是否一致。
范例:TestEquals.java
public class TestEquals { public static void main(String[] args) { String str1 = new String("java"); String str2 = new String("java"); String str3 = str2; if (str1 == str2) System.out.println("str1 == str2"); else System.out.println("str1 != str2"); if (str2 == str3) System.out.println("str2 == str3"); else System.out.println("str2 != str3"); } }
输出结果:
str1 != str2
str2 == str3
由程序的输出结果可以发现,str1不等于str2,str1与str2的内容完全一样,为什么会不等于呢?在程序的第5和第6行分别实例化了String类的两个对象,此时,这两个对象指向不同的内存空间,所以它们的内存地址是不一样的。这个时候程序中是用的“==”比较,比较的是内存地址值,所以输出str1!=str2。程序第7行,将str2的引用赋给str3,这个时候就相当于str3也指向了str2的引用,此时,这两个对象指向的是同一内存地址,所以比较值的结果是str2==str3。
那该如何去比较里面的内容呢?这就需要采用另外一种方式——“equals”,请看下面的程序,下面的程序TestEquals1.java修改自程序TestEquals.java,如下所示:
范例:TestEquals1.java
public class TestEquals1 { public static void main(String[] args) { String str1 = new String("java"); String str2 = new String("java"); String str3 = str2; if (str1.equals(str2)) System.out.println("str1 equals str2"); else System.out.println("str1 not equals str2"); if (str2.equals(str3)) System.out.println("str2 equals str3"); else System.out.println("str2 note equals str3"); } }
输出结果:
str1 equals str2
str2 equals str3
这个时候可以发现,在程序中将比较的方式换成了equals,而且调用equals()方法的是String类的对象,所以可以知道equals是String类中的方法。在这里一定要记住:“==”是比较内存地址值的,“equals”是比较内容的。
小提示:
你可能会问,下面两种String对象的声明方式到底有什么不同?
String str1 = new String("java");
String str2 = "java";
下面先来看一个范例:
public class StringDemo { public static void main(String[] args) { String str1 = "java"; String str2 = new String("java"); String str3 = "java"; System.out.println("str1 == str2 ? --- > " + (str1 == str2)); System.out.println("str1 == str3 ? --- > " + (str1 == str3)); System.out.println("str3 == str2 ? --- > " + (str3 == str2)); } }
输出结果:
str1 == str2 ? --- > false
str1 == str3 ? --- > true
str3 == str2 ? --- > false
由程序输出结果可以发现,str1与str3相等,这是为什么呢?还记得上面刚提到过“==”是用来比较内存地址值的。现在str1与str3相等,则证明str1与str3是指向同一个内存空间的。可以用图5-11来说明:
图5-11 String对象的声明与使用
由图中可以看出“java”这个字符串在内存中开辟的一个空间,而str1与str3又同时指向同一内存空间,所以即使str1与str3虽然是分两次声明的,但最终却都指向了同一内存空间(可以把它理解为C语言的文字常量区)。而str2是用new关键字来开辟的空间,所以单独占有自己的一个内存空间。
另外,需要注意的是,如果用new关键字开辟String对象的内存空间的话,则实际上就开辟了两个内存空间,如图5-12所示:
图5-12 String对象内容的改变
由图 5-12(A)中不难发现,如果要改变str1的值,则会先断开原有的对象引用,再开辟新的对象,之后再指向新的对象空间。
用图 5-12(B)的方法也可以实现改变String对象的声明的操作,可以发现,用new String("java")方式实例化 String 对象时,实际上是开辟了两个内存空间,所以一般在开发上都采用直接赋值的方式,即:String str1 = "java"。
再来看看普通类的实例:
范例:TestCompare.java
class Person { String name; int age;
Person(String name, int age) { this.name = name; this.age = age; } public boolean equals(Object obj) //可以试着将这个函数注释掉 { if (obj instanceof Person)
{ Person p = (Person) obj; if (this.name.equals(p.name) && this.age == p.age) return true; } return false; } } public class Test { public static void main(String[] args) { Person p1 = new Person("张三", 30); Person p2 = new Person("张三", 30); //直接==,比较的是地址 System.out.println(p1 == p2 ? "相等,是同一人!" : "不相等,不是同一人!"); //调用自己复写的equals方法,比较的是内容 System.out.println(p1.equals(p2) ? "相等,是同一人!" : "不相等,不是同一人!"); } }
输出结果:
相等,是同一人!
实例中我们复写了Object类的equals方法,此时利用p1.equals(p2)调用的是自己本身的equals方法,按照自己定义的比较规则来比较两个对象是否相同。
5.9 this 关键字的使用
在整个java的面向对象程序设计中,this是一个比较难理解的关键字,在前面调用类内部方法时,曾经提到过this。this强调对象本身,那么什么是对象本身呢?其实就是当前对象本身,而所谓的当前对象就是指调用类中方法或属性的那个对象。即“哪个对象调用的,则this就具备谁的身份”。
this 关键字有以下作用:
·通过this操作类中的属性和普通方法;
·使用this调用本类的构造方法;
·this表示当前对象;
先来看一下下面的程序片段:
范例:Person.java
class Person { private String name; private int age;
public Person(String name,int age) { name = name; age = age; } }
看上面的程序可以发现会有些迷惑,程序的本意是通过构造方法为name和age进行初始化的,但是在构造方法中声明的两个参数的名称也同样是name和age,此时执行name=name,没能完成赋值,只是将形参变量自己赋给了自己。没能修改到类中的成员变量。为了避免这种混淆的出现,可以采用this这种方式,请看修改后的代码:
范例:Person.java
class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } }
Person.java这段代码与之前的不同之处在于在第7、8行分别加上了this关键字。还记得之前说过的this表示当前对象吗?那么此时的this.name和this.age就分别代表类中的name与age属性,这个时候再完成赋值操作的话,就可以清楚的知道谁赋值给谁了。完整程序代码如下:
范例:TestJavaThis.java
class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String talk() { return "我是:" + name + ",今年:" + age + "岁"; } } public class TestJavaThis { public static void main(String[] args) { Person p = new Person("张三", 25); System.out.println(p.talk()); } }
输出结果:
我是:张三,今年:25岁
程序说明:
1、程序第1~14行声明了一个名为Person的类。
2、第5~9行声明Person类的一个构造方法,此构造方法的作用是为类中的属性赋初值。
5.9.1 用 this 调用构造方法
如果在程序中想用某一构造方法调用另一构造方法,可以用this来实现,具体的调用形式如下:
this();
在使用 this 关键字调用其他构造方法的时候,有以下几点限制;
·this()调用构造方法的语句只能放在构造方法的首行(非构造方法不可以使用)。
·至少有一个构造方法是不用 this调用的。
范例:TestJavaThis1.java
class Person { String name; int age;
public Person() { System.out.println("1. public Person()"); } public Person(String name, int age) { // 调用本类中无参构造方法 this(); this.name = name; this.age = age; System.out.println("2. public Person(String name,int age)"); } } public class TestJavaThis1 { public static void main(String[] args) { new Person("张三", 25); } }
输出结果:
1. public Person()
2. public Person(String name,int age)
程序说明:
1、程序1~17行,声明一个名为Person的类,类中声明了一个无参和一个有两个参数的构造方法。
2、程序第12行使用this()调用本类中的无参构造方法。
3、程序第22行声明一个Person类的匿名对象,调用了有参的构造方法。
由程序TestJavaThis1.java可以发现,在第22行虽然调用了Person中有两个参数的构造方法,但是由于程序第12行,使用了this()调用本类中的无参构造方法,所以程序先去执行Person中无参构造方法,之后再去继续执行其他的构造方法。
注意:
如果我把this()调用无参构造方法的位置任意调换,那不就可以在任何时候、任何方法里面都可以调用构造方法了么?实际上这样理解是错误的。构造方法是在实例化一对象时被自动调用的,也就是说在类中的所有方法里,只有构造方法是被优先调用的,所以使用this调用构造方法必须也只能放在构造方法的第一行,下面的程序就是一个错误的程序:
class Person { String name; int age; public Person() { System.out.println("1. public Person()"); } public Person(String name, int age) { this.name = name; this.age = age; // 用this调用构造方法,此时不是放在构造方法的首行,错 this(); System.out.println("2. public Person(String name,int age)"); } public String talk() { // 普通函数调用this,错 this(); System.out.println("我是:" + name + ",今年:" + age + "岁"); } } public class TestJavaThis1 { public static void main(String[] args) { new Person("张三", 25); } }
由此可见,对this()的调用必须是构造函数中的第一个语句。
感谢阅读。如果感觉此章对您有帮助,却又不想白瞟