对面向对象的一点感悟

知识点

  • 多态
  • 内部类(主要是匿名内部类书写及其理解)
  • 对面向对象难点感悟
  • 你真的理解接口的回调吗

一、多态

多态顾名思义“一种形式多种状态”说了等于白说。其实啥叫多态呢?或许可以这样说:子类对象持有父类或者接口类型的引用,在方法调用期间动态的改变。接下来便是我对多态的一点理解。。。

1、发生条件

1、存在继承
2、存在方法重写
3、父类/接口引用子类对象
ps:只讨论方法,成员变量不存在多态。

2、多态误区

1、成员字段的调用不存在多态
2、父类方法A为private,子类也有方法A(无论任何访问权限),父类引用调用时访问不了父类方法A的(编译期报错),而且此时也访问不到子类的方法A。(结合变量引用类型理解)
3、父类的static方法对子类是‘隐藏’的(参考如下)

父类的静态方法能否被子类重写?
不能:
1、静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"(即使父类的静态成员和方法不是private的,子类中也有一样的静态方法,父类对象引用也调用不了,发生不了多态。所以父类的静态方法对子类来说是不能重写的这叫做隐藏)。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
2、重写指的是根据运行时对象的类型来决定调用哪个方法,而不是根据编译时的类型。

3、变量的引用类型

java的引用变量有两个类型:
1、编译时类型:由声明该变量时使用的类型决定
2、运行时类型:实际赋值给该变量的对象决定

  Animal animal = new Fish();

如上:
1、Animal animal为编译时类型,这时animal 只能调用Animal 类中可访问的方法字段。
2、Animal animal = new Fish,在运行期间animal 变为了Fish类型,这时调用重载方法时就发生了多态,即调用了子类的方法。

4、构造方法的调用树

当用户使用调用一个子类的构造时,系统总会先调用父类的构造。而且首先调用的是java.lang.Object类的构造(默认调无参数的)

在这里插入图片描述

如上,总会先执行顶级父类的构造,在接着往下执行(如果父类中含有构造调用其他参数构造,则这些构造都会一次执行,最终往下初始化直到子类的构造。)

5、多态情况下的方法调用树

在这里插入图片描述

6、感悟

1、类的初始化时(构造)自上而下初始化。
2、父类或接口引用调用方法时,自下而上调用。直到查找到Object类。
3、父类或者接口引用只能调用自身或者子类中覆盖的方法,否则调用时编译期报错。(通过反射可以跳过编译期检测)

7、模拟一道题

传送门:
答案:本文章末

二、你真的理解接口的回调吗

1、准备
public abstract class Animal {
    
    
    abstract void run();
}
-------------------------------------------
public class People extends Animal {
    
    
    @Override
    public void run() {
    
    
        System.out.println("我是people");
    }
}
----------------------------------------
public class Person extends People {
    
    

}
2、栗子
package test1.interfacedemo;

/**
 * Create by SunnyDay on 2020/02/18 
 * ps:今天回顾整理与此
 */
public class MainTest {
    
    
    public static void main(String[] args) {
    
    
        // 接口回调栗子1:
        test(new Animal() {
    
    
            @Override
            void run() {
    
    
                System.out.println("接口回调栗子1:发送消息");
            }
        });
        /**
         * 栗子1解析:
         * 这种为常见的栗子:直接 new 接口或抽象类,实现要实现的方法。
         * 代码原理(相当于如下):
         *  Animal animal =  new Animal{
         *             @Override
         *             void run() {
         *                 System.out.println("接口回调栗子1:发送消息");
         *             }}
         *   1、代码左面Animal animal  就是test(Animal animal)
         *   2、代码右面相当于创建个Animal的子类,实现了run方法。
         *   3、test(Animal animal) 方法被调用时传入了个 Animal 子类对象
         *   4、这时开始执行 test()中的 animal.run(),根据多态机制。多态调用,去子类中寻找。
         *   5、由于我们传入的为new Animal{xxx}所以在这里寻找重写的方法加以调用
         * */

        // 接口回调栗子2:
        test(new Person() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("接口回调栗子2:发送消息");
            }
        });
        /**
         * 栗子2解析:
         * 这种为常见的栗子:直接 new 接口或抽象类,实现要实现的方法。
         * 代码原理(相当于如下):
         *  Animal animal =  new Person{
         *             @Override
         *             void run() {
         *                 System.out.println("接口回调栗子2:发送消息");
         *             }}
         *   1、代码左面Animal animal  就是test(Animal animal)
         *   2、代码右面相当于创建个Person的子类,实现了run方法。
         *   3、test(Animal animal) 方法被调用时传入了个 Animal 子类对象(Person间接继承的Animal)
         *   4、这时开始执行 test()中的 animal.run(),根据多态机制。多态调用,去子类中寻找。
         *   5、由于我们传入的为new Person{xxx}所以在这里寻找重写的方法加以调用
         * */

        // 拓展栗子:
        /**
         * 拓展栗子解析:
         * 这种为常见的栗子:直接 new 接口或抽象类,实现要实现的方法。
         * 代码原理(相当于如下):
         *  Animal animal =  new Person(){
         *      
         *  }
         *
         *   1、代码左面Animal animal  就是test(Animal animal)
         *   2、代码右面相当于创建个Person的子类,实现了run方法。
         *   3、test(Animal animal) 方法被调用时传入了个 Animal 子类对象(Person间接继承的Animal)
         *   4、这时开始执行 test()中的 animal.run(),根据多态机制。多态调用,去子类中寻找重写的方法。
         *   5、由于我们传入的为new Person{}这个Person子类中没有重写所以,代用从父类中继承的run方法
         *   6、去Person类中寻找,一看没有。
         *   7、去People类中寻找,找到啦,调用。
         * */
        test(new Person(){
    
    });
    }

     /**
      * MainTest类的test方法
      * @param animal    超类接口引用
      * */
    public static void test(Animal animal) {
    
    
        animal.run();
        System.out.println("收到消息!!!");
    }
}


logcat:
接口回调栗子1:发送消息
收到消息!!!

接口回调栗子2:发送消息
收到消息!!!

我是people
收到消息!!!

具体参考以下加以理解:
1、多台机制
2、多态调用时方法调用机制
3、匿名内部类使用

二、内部类

1、知识点

在这里插入图片描述

2、代码

传送门

2、匿名内部类的理解
/**
 * Created by sunnyDay on 2019/9/26 17:11
 * 匿名内部类栗子
 */
public class Test {
    
    
    public static void main(String[] args) {
    
    

         //1、 new 抽象类
        new Animal(){
    
    
            @Override
            void run() {
    
    
                System.out.println("1");
            }
        }.run();

        //2、 new 接口
        new MyInterface(){
    
    
            @Override
            public void doSome() {
    
    
                System.out.println("接口的实现");
            }
        }.doSome();

        //3、 new 普通类
       new Fish(){
    
    
           @Override
           void run() {
    
    
               System.out.println("2");
           }
       }.run();


       // demo
       class Person extends Animal{
    
    //继承方式
           @Override
           void run() {
    
    
               System.out.println("人可以走可以跑");
           }
       }

       Person person  = new Person();
    }
}

书写方式:
1、new 抽象类构造器 实现抽象方法(直接带方法体,方法体内实现)
2 、new 接口 ,实现接口的方法(直接带方法体,方法体内实现)
3、new 某一个普通类的构造器,后跟方法体。
以上三种方式和 继承(如上demo)或者实现接口创建子类的区别:
1、 两种类都是局部内部类, 只是匿名内部类属于“特殊的局部内部类”
2、 局部内部类有类名,匿名内部类没有。

参考文章:搞懂 JAVA 内部类

三、对面向对象难点的感悟

1、难点
  • 各种内部类的知识点。

特别是匿名内部类的new抽象类,new接口形式的理解。

  • 多态思想

    结合子类父类、各个成员、方法、代码块(动静)初始化顺序理解
    结合多态的概念理解
    结合构造器的调用树理解
    实战结合接口的回调理解

付:答案
/**
 * Created by sunnyDay on 2019/9/20 16:03
 */
public class test {
    
    
    public static void main(String[] args) {
    
    
        Animal animal = new Fish();

        animal.test();
        animal.run(); // 访问成功,编译期间访问父类方法,发生多态,运行期间调用子类方法。
       // animal.te(); // 只会访问父类的,且访问失败(私有权限)
       //  animal.Bubble(); // 这里输出啥?-> // 调用不了 (对多态理解:编译器调用。即只能调用编译期间的成员,在运行期动态改变)

        try {
    
    

            Class clazz = animal.getClass();
            Method method = clazz.getMethod("Bubble", null);
            method.invoke(animal, null);   // 这里输出啥?-> // 反射 运行时 动态调用(相对上面跳过了编译期间的检测)

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_38350635/article/details/101204524
今日推荐