1.方法重写(覆盖)(非常重要)
又被称为方法覆盖,override
覆盖才是继承的精髓和终极奥义
记住:重写是方法的重写,和属性无关
覆盖:通过使用和父类方法签名一样,而且返回值也必须一样的方法,可以让子类覆盖掉父类的方法
如果方法签名一样,但返回值不同,会报错
1 | public class B { |
test方法去掉static修饰
1 | public class B { |
静态方法是类的方法,而非静态方法是对象的方法。
在demo1中,b是A new出来的对象,所以调用A的方法test。
重写的关键字只能是public,不能是static或者private,子类继承父类才有重写
重写:(IDEA快捷键:Alt + Insert:override
)
- 需要有继承关系,子类重写父类的方法!
- 方法名必须相同
- 参数列表必须相同
- 修饰符:范围可以扩大但不能缩小,
public > porotected > default > private
- 抛出的异常:范围可以缩小但不能扩大,例:
ClassNotFoundException(小)-->Exception(大)
-为什么需要重写?
-父类的功能,子类不一定需要或不一定满足!
-什么方法不能重写?
- static方法:它属于类,不属于实例(对象)
- final 常量
- private方法
2.多态(重要)
多态注意事项:
- 多态是方法的多态,属性没有多态
- 必须是父类与子类间的继承关系
- 存在条件:继承关系,方法需要重写,父类引用指向子类对象!
- 一个Person类既可以引用Person类型的对象,也可以引用Person类的任何一个子类的对象
- 可以调用哪些方法,取决于引用类型是什么,具体调用哪个方法,取决于对象所属的类是什么
- 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法
1 | // 对于同一方法run有不同的行为 |
当父类的引用指向子类的实例,只能通过父类的引用,像父类一样操作子类的对象,也就是说左边“名”的类型,决定了能执行哪些操作(调用哪些方法…)。
总结:无论一个方法是使用哪个引用被调用的,”它都是在实际的对象上执行的”。执行的任何一个方法,都是这个对象所属的类的方法。如果没有,就去父类找,再没有,就去父类的父类找,依次寻找,直到找到。那么自然的,即使是在继承自父类的代码里,去调用一个方法,也是先从子类开始,一层层继承关系的找。这也是Java选择单继承的重要原因之一。在多继承的情况下,如果使用不当,多态可能会非常复杂,以至于使用的代价超过其带来的好处。
在这里我再总结一下这三个模式。
1)父类引用指向子类对象,可以调用只在父类中的方法(继承) 2)父类引用指向子类对象,可以调用子类覆盖了父类的方法(覆盖,多态) 3)父类引用指向子类对象,在1)和2)这两种情况下。如果这个方法的代码中又调用了别的方法,那么还是会遵循这个规则。举个例子,如果父类中有m1,m2两个方法。子类覆盖了m2方法。那么如果调用m1,则m1中调用的m2会是子类中定义的m2。
- 如何体现动态多态的“动态”二字?
3.instanceof和类型转换
instanceof
instanceof 是 Java 的一个二元操作符,类似于 ==,>,< 等操作符。
它的作用是测试它左边的对象是否是它右边的类(或子类)的实例,返回 boolean 的数据类型。
1 | public class Application { |
System.out.println(x instanceof Y);
能不能编译(无关对错)通过取决于x指向的类型是不是与Y有继承关系。
如果引用是null,肯定返回false。
“is-a”规则
用来判断是否应该将数据设计为继承关系,它指出子类的每个对象也是父类的对象。
例如:将Student类设计为Person类的子类,因为“Student is a Person”。
类型转换
- 父类引用指向子类的对象,不能反过来
- 把子类转换为父类,向上转型
- 把父类转换为子类,向下转型,强制转换,只能在继承关系下进行强制转换
- 方便方法的调用,减少重复代码
- 在将父类强制类型转换成子类之前,应该使用instanceof进行检查
- 一般情况下,最好尽量少使用强制转换和instanceof
- 父类若转换为子类,父类引用必须指向的是一个子类对象。实质上是将一个指向子类对象的父类引用改为了子类引用
1 | // 注:Person是Student的父类 |
所谓强制转型(转自CSDN)
你没有改变对象,只是改变了引用而已
父类引用不能访问子类方法
所以当你用父类引用指向子类对象时,不能访问子类的方法
而当你转回子类引用指向子类对象时,又可以访问子类方法了
记住,这个过程中对象一直没变过,一直都是那个子类对象,变的只是引用
4.static关键字
静态方法里面只能调用静态的东西,而main函数也是静态的。
1 | public class Person { |
小例子
1 | // 静态导入包 |
5.抽象类(不是重点)
1 | // abstract 抽象类 |
1.不能new这个抽象类,只能靠子类去实现它
2.抽象类中可以写普通方法
3.抽象方法必须在抽象类中
6.接口(重要)
Java通过接口来实现C++中多继承的效果,一个类可以实现多个接口。接口与接口之间可以多继承。
接口是一种特殊的抽象类,它的所有方法都是抽象方法(除了default方法),里面全部由全局常量和公共的抽象方法组成。
接口中的所有属性都会自动声明为final static
。没有变量,都是常量。
接口和类是两个并列的结构。
抽象类与接口的区别:
abstract class | interface | |
---|---|---|
继承 | 只能extends一个class | 可以implements多个interface |
字段 | 可以定义实例字段 | 不能定义实例字段 |
抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
非抽象方法 | 可以定义非抽象方法 | 可以定义default方法 |
实现类可以不必覆写default
方法。default
方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default
方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
1 | // 类可以通过implements实现接口 |
接口作用
- 约束
- 定义一些方法,让不同的人实现
- 接口中方法默认用
public abstract
修饰 - 接口中属性默认用
public static final
修饰,也即静态常量,必须初始化 - 接口不能被实例化,接口中没有构造方法
- 接口中定义的静态方法只能通过接口来调用
- 通过实现类的对象,可以调用接口的default方法
- 通过implements可以实现多个接口
- 必须要重写接口中的方法
7.N种内部类
小例子1:局部内部类。
1 | class Outer { |
上述定义的Outer
是一个普通类,而Inner
是一个Inner Class,它与普通类有个最大的不同,就是Inner Class的实例不能单独存在,必须依附于一个Outer Class的实例。
这是因为Inner Class除了有一个this
指向它自己,还隐含地持有一个Outer Class实例,可以用Outer.this
访问这个实例。所以,实例化一个Inner Class不能脱离Outer实例。
Inner Class和普通Class相比,除了能引用Outer实例外,还有一个额外的“特权”,就是可以修改Outer Class的private
字段,因为Inner Class的作用域在Outer Class内部,所以能访问Outer Class的private
字段和方法。
观察Java编译器编译后的.class
文件可以发现,Outer
类被编译为Outer.class
,而Inner
类被编译为Outer$Inner.class
。
小例子2:
1 | public class Test(){ |
小例子3:匿名内部类。
1 | public class Main { |
观察asyncHello()
方法,我们在方法内部实例化了一个Runnable
。Runnable
本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable
接口的匿名类,并且通过new
实例化该匿名类,然后转型为Runnable
。在定义匿名类的时候就必须实例化它,定义匿名类的写法如下:
1 | Runnable r = new Runnable() { |
匿名类可以继承自接口,也可以继承自一个普通类。