时间:2018-07-21
作者:魏文应
一、对象的多态性
public class Person {
public void eat(){
System.out.println("人吃饭");
}
public void animal(){
System.out.println("人是一种动物");
}
}
public class Man extends Person{
public void eat(){
System.out.println("男人吃饭比较多!");
}
public void entertain(){
System.out.println("男人请客。");
}
}
public class TestPerson {
public static void main(String[] args){
Person p = new Man();
}
}
看上面代码,你会发现,一般我们是 Person p = new Person();
去创建一个对象实例,但这里使用的是 Person p = new Man();
。这里,就使用了 对象的多态性。这样,就可以使用子类中,子类重写的那些方法了。什么意思呢?看下面例子:
Person p = new Man();
p.eat(); // System.out.println("男人吃饭比较多!");
这时,使用父类 Person 的 引用p ,p指向 子类Man的一个对象实体,p.eat() 实际执行 的是 子类Man中的eat()方法,这就被叫做 虚拟方法调用 。下面调用是 错误的:
Person p = new Man();
p.entertain(); // 这样使用是错误的,不能使用entertain()方法
因为父类Person 并没有entertain这个方法,这也说明了,这种情况下,如果要 使用子类的方法,只能使用子类中重写父类的方法(比如,Man中有eat()方法,要使用Man中的eat(),父类Person也必须要有一个叫做eat()的方法)。下面调用是 正确的:
Person p = new Man();
p.animal();
虽然子类没有 animal() 方法,但父类Person是有这个方法的,所以可以使用。直观的表现就是: 父类Person中的所有方法都可以使用,也只能使用这些方法。只是有些方法被子类重写了,执行的时候,跑去执行子类的方法去了。 这里说的父类的所有方法,包括父类从其它类继承来的方法。
子类对象的多态性使用的前提
其实上面讲的那么多,实际上是和 程序有编译状态和运行状态 有关,对于下面语句:
Person p = new Man();
在使用 p 这个引用时,编译器 只会检查 Person p
有哪些属性和方法,它 不管 new Man()
有哪些方法和属性,只要 Man 是 Person 的子类就行了。
- 要有类的继承关系,比如上面的 Person 和 Man 就有继承关系。
- 要有子类对父类方法的重写,比如上面的子类Man中,对父类Person eat() 方法的重写(这不是必须的,但如果不这样,
Person p = new Man();
和Person p = new Person ();
的效果就是一样的了, 多态性的作用没有很好的体现出来)。
多态性的用途
假设我们有下面三个类,三个类都有 eat() 方法:
public class Person {
public void eat(){
System.out.println("人吃饭");
}
}
public class Man extends Person{
public void eat(){
System.out.println("男人吃饭比较多!");
}
}
public class Man extends Person{
public void eat(){
System.out.println("女人吃饭比较文雅!");
}
}
public class TestPerson {
public static void main(String[] args){
TestPerson t = new TestPerson();
Person m = new Man();
t.eat(m); // 使用的是Man 类的eat()方法
Person w = new Woman();
t.eat(w); // 使用的是Woman 类的eat()方法
Person p = new Person();
t.eat(p); // 使用的是Person 类的eat()方法
}
public void eat(Person p){
p.eat();
}
public void eat(Man m){
m.eat();
}
public void eat(Woman w){
w.eat();
}
}
上面 TestPerson 类中的三个 eat() 方法之间构成方法重载。如果除了 Person、Man、Woman
,还有更多的方法需要去重载呢?这样就会太多方法重载了,写起来也很麻烦。因此:
public class TestPerson {
public static void main(String[] args){
TestPerson t = new TestPerson();
Person m = new Man();
t.eat(m); // 使用的是Man 类的eat()方法
Person w = new Woman();
t.eat(w); // 使用的是Woman 类的eat()方法
Person p = new Person();
t.eat(p); // 使用的是Person 类的eat()方法
}
public void eat(Person p){
p.eat();
}
}
上面代码中,只需 eat(Person p)
,就可以替代 eat(Man m)
、eat(Person p)
、eat(Woman w)
这三个方法。如果Person子类更多,替代的就越多。这样就比较方便了,这就是对象的多态性的用途之一。
对象的多态性是对于方法来说的, 属性是没有多态性的。比如下面示例:
public class Person{
int id = 1001;
}
public class Man extends Person{
int id = 1002;
}
public class TestPerson {
public static void main(String[] args){
Person p = new Man();
System.out.println(p.id); // 这时打印出的是1001,是Person类的。
}
}
说明,属性是没有多态性的。
二、类的强制类型转换:向下转型
子类继承父类的属性和方法(虽然private属性和方法子类无法直接使用),可以认为父类是子类的子集。所以,下面这个,子类赋值给父类,容量大的赋值给容量小的,称为 向上转型 :
Person p = new Man();
容量小的赋值给容量大的,称为 向下转型:
Person p = new Man();
Man m = (Man)p; // 向下转型
m.entertain(); // 子类非重写的方法,也可以使用了。
转型时,我们要注意,创建的对象实体是否有相应的方法:
public class Person {
}
public class Man extends Person{
}
public class Woman extends Person{
public void shopping(){
System.out.println("女人天生爱购物。");
}
}
public class TestPerson {
public static void main(String[] args){
Person p = new Man();
Woman w = (Woman)p;
w.shopping(); // 这里执行时会报错,因为Man没有shopping()方法。
}
}
执行上面代码时,就会 抛出异常,因为 new Man()
时,根本就没有 shopping()方法 在方法区被创建。编译时语法没有错误,运行时出错了:
Exception in thread "main" java.lang.ClassCastException: my.wwy.java1.Man cannot be cast to my.wwy.java1.Woman
at my.wwy.java1.TestPerson.main(TestPerson.java:33)
三、instanceof 关键字
格式:
对象 instanceof 类
比如:
Man m = new Man();
if(m instanceof Man){
System.out.println("这是一个Man类");
}
这就是:判断对象 m 是否是 类Man 的一个实例。 是的话,返回true;不是的话,返回flase。instanceof 判断时,如果是父类,也会返回true :
public class Man extends Person{
}
Man m = new Man();
if(m instanceof Man){ // 返回true
System.out.println("这是一个Man类");
}
if(m instanceof Person){ // 这个同样返回true
System.out.println("这是一个Person类");
}
上面的 m instanceof Man
和 m instanceof Person
判断都返回 true
。想想也是,Man 继承了 Person ,也就继承了Person 的方法和属性,也算是 Person 类的一个实例。
四、Object 类及其 equals() 方法
equals() 方法在Object类中,其源代码是这样的:
public boolean equals(Object obj) {
return (this == obj);
}
说明其输入参数是 类的对象。比较的是两个引用变量的 地址值 。比如下:
class Person{
}
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1.equals(p2)); // false, 比较了p1的值是否和p2值相等
上面的p1和p2值不一样,返回false。
String str1 = new String("AA");
String str2 = new String("AA");
System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2)); // true
上面的 String 类,重写了Object类的 equals() 方法,变成比较的是内容是否一样,而不是地址。
五、String 类在内存中存储
String 类的内容,是存储在常量池中的:
String str1 = "AA";
String str2 = "AA";
String str3 = new String("AA");
System.out.println(str1 == str2); // true
System.out.println(str1.equals(str2)); // true
System.out.println(str1 == str3); // false
System.out.println(str1.equals(str3)); // true
- string类在内存中存储
以 String str1 = "AA"
这种方式创建String对象时,引用变量 str1 直接保存的是常量池中的 AA
字符串的地址。如果以 String str3 = new String("AA")
方式new出来的String变量,str3 保存了内存堆的String地址,而内存堆中同样保存 "AA" 字符串在常量池中的位置,而不是直接在内存堆中 new 出 String 字符串 “AA” 。如果 “AA” 被修改为 “AB” 呢?那么就重新指向处理池中的 “AB”。如果常量池没有 “AB”,则常量池自动创建 “AB” 字符串。
六、toString() 方法的使用
比如,我们有下面代码:
class Person {
}
Person p1 = new Person();
System.out.println(p1.toString()); // my.wwy.java.Person@311e170c
System.out.println(p1); // my.wwy.java.Person@311e170c
你发现上面打印的信息一样,打印的都是引用变量的值,也就是 内存地址值。toString() 方法在JDK中源代码如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
在我们的代码中,在定义类时,应该显式地 重写 toString() 方法,使得调用toString()类时,能够 打印类的属性信息 :
public String toString(){
return "Person: " + "name:" + name + ";" + "age:" + age;
}
当然,也可以使用 Eclipse 提供的,自动生成 toString() 方法。将光标放在需要创建 toString()方法的类的内部,依次选择 Source -> Generate toString() -> OK 即可:
- 自动生成toString()方法
自动生成的内容,大致如下:
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
这样,当我们每次调用 toString() 方法时,返回的是 当前类的属性信息,而不是 引用变量的值:
public class Person {
String name = new String("魏文应");
int age = 12;
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class TestToString {
public static void main(String[] args) {
Person p1 = new Person();
System.out.println(p1.toString()); // Person [name=魏文应, age=12]
System.out.println(p1); // Person [name=魏文应, age=12]
}
}
很多类都对Object类中的toString()方法进行了重写,比如String类、Data类、File类、包装类等。比如下面的String:
public class TestToString {
public static void main(String[] args) {
String str = "AA";
System.out.println(str.toString()); // 打印结果为:AA
}
}
上面的 toString() 方法,是打印 str 对应的字符串的值,而不是str保存的内存地址值。下面是JDK的 String类中的toString() 方法源代码:
public String toString() {
return this;
}
网友评论