知识点
- 多态
- 内部类(主要是匿名内部类书写及其理解)
- 对面向对象难点感悟
- 你真的理解接口的回调吗
一、多态
多态顾名思义“一种形式多种状态”说了等于白说。其实啥叫多态呢?或许可以这样说:子类对象持有父类或者接口类型的引用,在方法调用期间动态的改变。接下来便是我对多态的一点理解。。。
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();
}
}
}