美文网首页
第4章:Java语言高级类特性2:多态性

第4章:Java语言高级类特性2:多态性

作者: 秋的懵懂 | 来源:发表于2018-07-25 10:49 被阅读0次

时间: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 Manm 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;
}

相关文章

网友评论

      本文标题:第4章:Java语言高级类特性2:多态性

      本文链接:https://www.haomeiwen.com/subject/xaghmftx.html