面向对象——多态

来源:互联网 发布:软件项目考核指标 编辑:程序博客网 时间:2024/04/30 22:06

多态

多态定义

多态:可以理解为事物存在的多种体现形态。
例如:

人:男人,女人动物:猫,狗

猫这个对象对应的类型是猫类型:猫 x = new 猫();,同时猫也是动物中的一种,也可以把猫称为动物:动物 x = new 猫();。动物是猫和狗等具体事物中抽取出来的父类型。
本文从以下几个方面介绍多态:

  1. 多态的体现——父类的引用指向了自己的子类对象。即父类的引用也可以接收自己的子类对象
  2. 多态的前提——必须是类与类之间有关系,要么继承,要么实现。通常还有一个前提:存在覆盖
  3. 多态的好处——多态的出现大大的提高了程序的扩展性
  4. 多态的弊端——提高了扩展性,但是只能使用父类的引用访问父类中的成员,不能预先使用子类,因为那时子类还没存在
  5. 多态的应用
  6. 多态的出现在代码中的特点(多态使用的注意事项)

以动物:猫,狗,猪为例说之。

abstract class Animal {    public abstract void eat();}class Cat extends Animal {    public void eat() {        System.out.println("吃鱼");    }    public void catchMouse() {        System.out.println("抓老鼠");    }}class Dog extends Animal {    public void eat() {        System.out.println("吃骨头");    }    public void kanJia() {        System.out.println("看家");    }}class Pig extends Animal {    public void eat() {        System.out.println("饲料");    }    public void gongDi() {        System.out.println("拱地");    }}

那么以下代码:

Animal a = new Cat(); // 类型提升,向上转型a.eat();

如果想要调用猫的特有方法时,如何操作?——强制将父类的引用转成子类类型,向下转型,即:

Cat c = (Cat)a;c.catchMouse();

千万不要出现这样的操作,就是将父类对象转成子类类型,即:

Animal a = new Animal();Cat c = (Cat)a;

我们能转换的是父类引用指向了自己的子类对象时,该引用可以被提升,也可以被强制转换。多态自始至终都是子类对象在做着变化。

instanceof关键字的使用

当我们使用向下转型时,确实是可以带来好处,即可以使用子类的特有功能。但是也会带来弊端——必须得面对具体的子类型。这即是说向下转型有风险,容易发生ClassCastException,只要转换类型和对象类型不匹配就会发生,想要安全,必须要进行判断,判断一个对象是否匹配某一个类型,这时我们就需要使用一个新的关键字——instanceof了,instanceof的用法为:对象 instanceof 类型。我们举例说明:

class DuoTaiDemo2 {    public static void main(String[] args) {        function(new Dog());            function(new Cat());    }    public static void function(Animal a) {        a.eat();        if(a instanceof Cat) {            Cat c = (Cat)a;            c.catchMouse();        } else if(a instanceof Dog) {            Dog d = (Dog)a;            d.kanJia();        }       }}

多态特点

  • 在多态中,成员变量的特点:当子父类中出现同名变量时,多态调用时,只看调用该成员变量的引用所属的类中的成员变量。简单说:无论编译还是运行,都看等号(=)的左边(引用型变量所属的类)
    明白了在多态中成员变量的特点之后,试着看以下代码的运行结果:

    class Fu{    int num = 5;    void show()    {        System.out.println("num = " + this.num); // 打印num = 5    }}class Zi extends Fu{    int num = 6;}class DuoTaiTest2 {    public static void main(String[] args)     {        Fu f = new Zi();        f.show();    }}

    很显然打印的是num = 5,为什么会这样呢?一图以蔽之:
    这里写图片描述

  • 在多态中成员函数(非静态)的特点:出现一模一样函数时,多态调用,在编译时期,参阅引用型变量所属的类中是否有调用的方法,如果有,编译通过,如果没有编译失败。在运行时期,参阅对象所属的类中是否有调用的方法。简单总结就是:成员函数(非静态)在多态调用时,编译看左边,运行看右边
    在一些专业书上也有这样的表述:成员方法动态绑定到当前对象上
  • 在多态中,静态成员函数(或者静态成员变量)的特点:出现一模一样函数时,多态调用,无论编译和运行,都参考左边(引用型变量所属的类)。其实我们要知道,真正调用静态方法是不需要对象的,直接类名调用。因为静态方法绑定到类上,所以这种情况更多用于面试中。

多态应用

例1,基础班学生:学习,睡觉;高级班学生:学习,睡觉。可以将这两类事物进行抽取。

abstract class Student {    public abstract void study();    public void sleep() {        System.out.println("躺着睡");    }}// 工具类class DoStudent {    public void doSomething(Student stu) {        stu.study();        stu.sleep();    }}class BaseStudent extends Student {    public void study() {        System.out.println("base study");    }    public void sleep() {        System.out.println("坐着睡");    }}class AdvStudent extends Student {    public void study() {        System.out.println("adv study");    }}class DuoTaiDemo {    public static void main(String[] args) {        DoStudent ds = new DoStudent();        ds.doSomething(new BaseStudent());        ds.doSomething(new AdvStudent());    }}

例2,需求:电脑运行示例,电脑运行基于主板。

// 接口定义规则interface PCI {    public void open();    public void close();}class MainBoard {    public void run() {        System.out.println("mainboard run");    }    public void usePCI(PCI p) { // PCI p = new NetCard(); // 接口型引用指向自己的子类对象        if(p != null) {            p.open();            p.close();        }    }}class NetCard implements PCI {    public void open() {        System.out.println("netcard open");    }    public void close() {        System.out.println("netcard close");    }}class SoundCard implements PCI {    public void open() {        System.out.println("soundcard open");    }    public void close() {        System.out.println("soundcard close");    }}class DuoTaiDemo {    public static void main(String[] args) {        MainBoard mb = new MainBoard();        mb.run();        mb.usePCI(null);        mb.usePCI(new NetCard());        mb.usePCI(new SoundCard());    }}

示意图:
这里写图片描述
例3,数据库的操作。数据是:用户信息。

  1. 连接数据库(JDBC Hibernate)
  2. 操作数据库(CRUD)——C creat R read U update D delete
  3. 关闭数据库连接
interface UserInfoDao {    public void add(User user);    public void delete(User user);}class UserInfoByJDBC implements UserInfoDao {    public void add(User user) {        1、JDBC连接数据库        2、使用sql添加语句添加数据        3、关闭连接    }    public void delete(User user) {        1、JDBC连接数据库        2、使用sql删除语句删除数据        3、关闭连接    }}class UserInfoByHibernate implements UserInfoDao {    public void add(User user) {        1、Hibernate连接数据库        2、使用sql添加语句添加数据        3、关闭连接    }    public void delete(User user) {        1、Hibernate连接数据库        2、使用sql删除语句删除数据        3、关闭连接    }}class DBOperate {    public static void main(String[] args) {        UserInfoDao ui = new UserInfoByHibernate();        ui.add(user);        ui.delete(user);    }}

示意图如下:
这里写图片描述

Object类

Object是所有对象的直接或者间接父类,传说中的上帝。该类中定义的肯定是所有对象都具备的功能。
Object类中已经提供了对对象是否相同的比较方法,如果自定义类中也有比较相同的功能,没有必要重新定义。只要沿袭父类中的功能,建立自己特有比较内容即可,这就是覆盖。
例,假如有一个Person类,其代码为:

class Person extends Object{    private int age;    private String name;    Person(String name, int age)    {        this.name = name;        this.age = age;    }}

现在我们的需求是:定义一个方法,判断两个Person对象是否是同一个,判断的依据是根据姓名和年龄,如果姓名和年龄都相同,视为同一个人。
我的分析:不用再自定义方法判断对象是否相同了,因为在Object父类中,已经定义了这样的方法,直接使用就可以了,但是判断的内容是根据Person的特点定义的,那就需要保留父类的功能声明,定义子类功能的特有内容,使用覆盖。所以要在Person类中加入如下equals方法:

public boolean equals(Object obj) // Object obj = p2;--->Object obj = new Person();{    // 提高点效率。如果两个引用指向了同一个对象,就不用再转换并比较内容了,直接判断地址就哦了。    if (this == obj)        return true;    // obj.age是错误的,因为Object中没有age属性,    // 想要使用子类对象的特有属性或行为,必须对其进行向下转型,并且需要进行类型判断    if (!(obj instanceof Person))    {        // return false;         throw new ClassCastException("类型错误");    }    Person p = (Person)obj;    // 如果判断姓名字符串是否相同,不要用==,字符串本身是一个对象,所以要使用String类的equals方法    return this.name.equals(p.name) && this.age == p.age;}

有时候,我们还需要重写Object类的toString()方法,建立Person对象特有的字符串表现形式。查询API帮助文档,我们可以发现:Object类的toString方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:getClass().getName() + '@' + Integer.toHexString(hashCode())。如果我们不在Person类中重写该方法,运行以下程序:

class ObjectDemo {    public static void main(String[] args)     {        Person p1 = new Person("lisi", 21);        Person p2 = new Person("mazi", 25);        System.out.println(p1); // Person@139a55,打印对象时,默认调用toString()方法        System.out.println(p1.toString());    }}

则会输出Person@139a55,而且在打印对象时,默认调用了toString()方法

  • 顺便说一下,如果要我们自己来弄,输出Person@139a55这样的东西,又该怎么做呢?
    这时,我们就要接触一点点反射的知识了,关于反射的知识以后会另开一篇专讲。A.classB.class这些class文件都有名称,这些文件内都有构造函数,一般方法,java中用Class来描述这些class文件,可通过getName()获取名称。所以以下代码:

    Person p1 = new Person("lisi", 21);Class c = p1.getClass();System.out.println(c.getName()); // Person

    会输出Person
    现在我们就可以自己来编写了,代码如下:

    Person p1 = new Person("lisi", 21);Class c = p1.getClass();System.out.println(c.getName()+"@@"+Integer.toHexString(p1.hashCode())); 

    这时就会输出Person@@139a55这样的东西了。

说完toString()方法,我们须在Person类中重写该方法,所以应在Person类中添加如下toString()方法:

/**建立Person对象特有的字符串表现形式,只要覆盖toString方法即可*/public String toString(){    return "Person[name = " + this.name +", age = " + this.age + "]";}

这样,测试类的代码可这样写为:

class ObjectDemo {    public static void main(String[] args)     {        Person p1 = new Person("lisi", 21);        Person p2 = new Person("mazi", 25);        System.out.println(p1.equals(p2)); // 判断的是对象的内容,用equals。        System.out.println(p1 == p2); // 判断的是对象的地址    }}

内部类

将一个类定义在另一个类的里面,对里面那个类就称为内部类(内置类,嵌套类)。例如,A类要直接访问B类中的成员时,可以将A类直接定义到B类中,作为B类的内部类存在。
内部类的访问规则:

  • 内部类可以直接访问外部类中的成员,包括私有。之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类中的引用,格式:外部类名.this。用一个例子来验证:

    class Outer{    int num = 2;    class Inner    {        int num = 3;        void show()        {            int num = 4;            System.out.println("show..." + Outer.this.num);        }    }    public void method()    {        new Inner().show();    }}class InnerClassDemo2 {    public static void main(String[] args)     {        new Outer().method();    }}
  • 外部类要想访问内部类,只能创建内部类的对象来访问。

访问格式:

  • 非静态,非私有的内部类访问方式
    当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中,可以直接建立内部类对象。格式为:外部类名.内部类名 变量名 = 外部类对象.内部类对象;,例,Outer.Inner in = new Outer().new Inner();示例代码如下:

    class Outer{    private int num = 4;    class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static    {        void show()        {            System.out.println(num);        }    }}class InnerClassDemo{    public static void main(String[] args)     {        Outer.Inner in = new Outer().new Inner();        in.show();    }}

    而且在非静态内部类中只允许定义静态的常量,存于常量池中,不能定义其他静态成员。所以,以下代码编译是没有任何问题的:

    class Outer{    private int num = 4;    class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static    {        static final int count = 5; // 在非静态内部类中只允许定义静态的常量,存于常量池中,不能定义其他静态成员        void show()        {            System.out.println(num);        }    }}class InnerClassDemo{    public static void main(String[] args)     {        Outer.Inner in = new Outer().new Inner();        in.show();    }}
  • 静态,非私有的内部类访问方式,访问非静态成员
    当内部类在成员位置上时,就可以被成员修饰符修饰,比如,private将内部类在外部类中进行封装,static内部类就具备static的特性。当内部类被static修饰后,只能直接访问外部类中的static成员,出现了访问局限
    在外部其他类中,如何直接访问静态内部类非静态成员呢?答案是格式为new Outer.Inner().function();。示例代码如下:

    class Outer{    private static int num = 4;    class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static    {        void show()        {            System.out.println(num);        }    }    static class Inner2 // 静态内部类,相当于一个外部类    {        void show2()        {            System.out.println("show2..." + num);        }    }}class InnerClassDemo{    public static void main(String[] args)     {        Outer.Inner2 in = new Outer.Inner2();        in.show2();    }}
  • 静态,非私有的内部类访问方式,访问静态成员
    在外部其他类中,如何直接访问静态内部类静态成员呢?答案是格式为Outer.Inner.function();。示例代码如下:

    class Outer{    private static int num = 4;    class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static    {        void show()        {            System.out.println(num);        }    }    static class Inner2 // 静态内部类,相当于一个外部类    {        void show2()        {            System.out.println("show2..." + num);        }        static void show3()        {            System.out.println("show3..." + num);        }    }}class InnerClassDemo{    public static void main(String[] args)     {        Outer.Inner2.show3();    }}

注意:当内部类中定义了静态成员,该内部类必须是static的。当外部类中的静态方法访问内部类时,内部类也必须是static的。

class Outer {    private static int x = 3;    static class Inner { // 静态内部类        static void function() { // 当内部类中定义了静态成员,该内部类必须是static的            System.out.println("inner::::"+x); // 当内部类被static修饰后,只能直接访问外部类中的static成员        }    }    static class Inner2 {        void show() {            System.out.println("inner2 show");        }    }    public static void method() {        new Inner2().show(); // 当外部类中的静态方法访问内部类时,内部类也必须是static的    }}

内部类定义在局部时

  1. 不可以被成员修饰符修饰,因为privatestatic不能修饰局部成员。
  2. 可以直接访问外部类中的成员,因为还持有外部类中的引用。但是不可以访问它所在的局部中的变量,只能访问被final修饰的局部变量,主要原因是生命周期不同。注意:java8没这个区别了,但是被final修饰的变量是一个常量,只能被赋值一次,所以一经存在就不得更改。
    例,以下是java8的运行环境。

    class Outer {    int x = 3;    void method(int a) {        // a++; // 从内部类引用的本地变量必须是最终变量或实际上的最终变量        int y = 4;        class Inner {            void function() {                System.out.println(a);            }        }        new Inner().function();    }}class InnerClassDemo {    public static void main(String[] args) {        Outer out = new Outer();        out.method(7);        out.method(8);    }}

为了能说明局部内部类只能访问被final修饰的局部变量,而且其主要原因是生命周期不同这一点,我们举例验证。

class Outer{    private int num = 4;    Object obj;    public void method()    {        /*final*/ int x = 5;        class Inner extends Object // Inner本身继承Object        {            // 覆盖Object类中的toString()方法            public String toString()            {                System.out.println("x = " + 5);                System.out.println("show..." + num);                return "Inner...abc";            }        }        // 创建内部类的对象        Inner in = new Inner();        // 将内部类对象的地址赋值给obj        obj = in;    }    public void function()    {        // 打印obj指向的对象的字符串表现形式        System.out.println(obj.toString());    }}class InnerClassDemo2 {    public static void main(String[] args)     {        new Outer().method();    }}

以上例子在JVM内存中的示意图大概是这样的:
这里写图片描述

匿名内部类

  1. 匿名内部类其实就是内部类的简写格式。
  2. 定义匿名内部类的前提:内部类必须是继承一个类或者实现接口。
  3. 匿名内部类的格式:new 父类或者接口() {定义子类的内容}
  4. 匿名内部类其实就是一个匿名子类对象,而且这个对象有点胖。可以理解为带内容的对象。
  5. 匿名内部类中定义的方法最后不要超过3个。
    例,

    abstract class AbsDemo {    abstract void show();}class Outer {    int x = 3;    public void function() {        AbsDemo d = new AbsDemo() {            int num = 9;            void show() {                System.out.println("num==="+num);            }            void abc() {                System.out.println("haha");            }        };        d.show();        // d.abc(); // 编译失败,因为父类中没有这个方法    }}

练习一:补全代码,通过匿名内部类。

interface Inter {    void method();}class Test {    // 补足代码。通过匿名内部类}class InnerClassTest {    public static void main(String[] args) {        Test.function().method();    }}

解:

interface Inter {    void method();}class Test {    // 补足代码。通过匿名内部类    static Inter function() {        return new Inter() {            public void method() {                System.out.println("Inter method");            }        };    }}class InnerClassTest {    public static void main(String[] args) {        // Test.function():Test类中有一个静态的方法function        // .method():function这个方法运算后的结果是一个对象,而且是一个Inter类型的对象,        // 因为只有是Inter类型的对象,才可以调用method()        Test.function().method();    }}

面试时可能遇到的一个小问题(有关匿名内部类的),如果没有一个类继承或一个接口实现,还能使用匿名内部类吗?答案是可以的。

class InnerTest {    public static void main(String[] args) {        new Object() { // new Object() {}是Object类的子类对象            public void function() {                System.out.println("hello");            }        }.function();    }}
0 0
原创粉丝点击