第8章-多态
Think in java 读书笔记
pikzas
2019.07.25
第八章 多态
知识点
1.什么是多态
继承自同一个父类的两个子类对同一方法的不同实现
2.多态的实现基础
JAVA实现多态的基础依赖于后期绑定(动态绑定,运行时绑定),就是一个方法的调用者,只有在运行的时候才知道具体回去调用哪个方法。如果在编译时就知道,那就是前期绑定。
JAVA中除了static,final,private修饰的方法都是动态绑定的。
3.多态的应用
接口处使用父类,实际使用的时候是具体的类。也就是如果要求一个对象执行一个方法,不是单单看这个对象是用什么引用来持有的,而应该看该对象具体是怎么new 出来的。
例如 输出的是man run。
class Human{
public void run(){
System.out.println("human run");
}
}
class Man extends Human{
public void run(){
System.out.println("man run");
}
}
class Demo{
public static void main(String[] args){
Human human = new Man();
human.run(); // 输出的结果是human run
}
}
4.覆盖(override)的正确姿势
示例执行结果是Sup f(),并不是期待中的Sub f().仔细看会发现,父类中的f()是private修饰的,
而子类的是public修饰的。而private方法是不存在override的,也就是说,
当前这种写法中的f()方法并没有发生override,也就不可能有多态的效果。
但是编译器不会报错。应为这时候,编译器认为父子类中的f()是两个不相干的方法。
此时可以在子类f()上加入@override,来表明我们就是想要覆盖父类方法。
但是介于父类方法是私有的,此时会提示编译错误。
public class Sup {
private void f(){
System.out.println("Sup f()");
}
public static void main(String[] args) {
Sup sup = new Sub();
sup.f(); // 输出Sup f(),因为此时并没有发生重写,所以调用的时候。不会调用子类方法。所以建议此时子父类不要用一个方法名,最好区别开。
}
}
public class Sub extends Sup {
public void f(){
System.out.println("Sub f()");
}
public static void main(String[] args) {
Sup sup = new Sub();
sup.f(); // 此时会提示编译错误,因为Sup里的f()方法是私有的,这里没有访问权限,除非用Sub su = new Sub();的方式创建Sub
}
}
5.多态对于属性的影响
多态针对的是方法的调用,所以其对于属性是不存在多态性的
public class Test {
public static void main(String[] args) {
Parent parent = new Child();
System.out.println("parent's field "+ parent.field +" parent's getField " + parent.getField() );
Child child = new Child();
System.out.println("child's field "+ child.field +" child's getField " + child.getField() + " child's parent's field "+ child.getParentField() );
}
}
public class Parent {
public int field = 0;
public int getField(){
return field;
}
}
public class Child extends Parent {
public int field = 100;
public int getField(){
return field;
}
public int getParentField(){
return super.field;
}
}
输出
parent's field 0 parent's getField 100
child's field 100 child's getField 100 child's parent's field 0
// 方法的调用遵循多态,但是属性的获取,只看当前是用什么引用去持有该对象。因为域的访问是由编译器解析的,不存在多态性。
6.多态对于静态方法的影响
静态方法是属于类的方法,与具体类无关,不存在动态绑定。
public class Test {
public static void main(String[] args) {
StaticSuper sup = new StaticSub();
sup.method();
sup.staticGet();
}
}
public class StaticSuper {
public static void staticGet(){
System.out.println("Static Super");
}
public void method(){
System.out.println("super method");
}
}
public class StaticSub extends StaticSuper {
@Override
public void method() {
System.out.println("Sub method");
}
public static void staticGet(){
System.out.println("Static Sub");
}
}
输出:
Sub method //父类方法被子类覆盖,此处为多态
Static Super //对象的持有引用为父类,就会调用父类的静态方法
7.多态对构造器的影响
构造器是隐式的static方法,所以不存在多态
7.1.多继承结构中初始化的调用顺序
基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐层向上链接,保证每个基类的构造器都能得到调用。
初始化顺序为
- 具体类中的静态属性和方法
- 调用基类的构造器,自上而下。
- 初始化类的属性和方法
- 调用导出类的构造方法
所以可简单的总结一下,以Parent p = new Child();为例
- p.xxMethod(); 如果Child有对Parent做正确的Override,那么遵循多态的规则,调用子类的xxMethod,如果不存在多态重写的情况,调用Parent的xxMethod。
- p.xxMethod(); 中如果xxMethod为静态方法,那么也是调用Parent中的xxMethod。
- 而对p.yyParam,则是看等号前面是用哪个对象来装载新创建的对象的,则取调用它的yyParam。
8.手动销毁对象的原则
对于有多层次的复杂对象,应该遵循,销毁对象的顺序和初始化的相反,对于字段则和声明顺序相反
8.1.如果多个对象实例共享一个对象,并要求手动销毁对象的时候,这时候就应该引入引用计数
例子
public class Share {
private int refcount = 0;
private static long counter = 0 ;
private final long id = counter++;
public Share(){
System.out.println("creating " + this);
}
public void addRef(){
refcount++;
}
protected void dispose(){
if(--refcount==0){
System.out.println("disposing "+ this);
}
}
public String toString(){
return "Share " + id;
}
}
public class Composing {
private Share share;
private static long counter = 0;
private final long id = counter++;
public Composing(Share share){
System.out.println("Creating "+this);
this.share = share;
this.share.addRef();
}
protected void dispose(){
System.out.println("disposing "+this);
share.dispose();
}
public String toString(){
return "Composing "+id;
}
}
public class Test {
public static void main(String[] args) {
Share share = new Share();
Composing[] cs = new Composing[10];
for(int i = 0; i < 10 ;i++){
cs[i] = new Composing(share);
}
for (Composing c:cs ) {
c.dispose();
}
}
}
输出为
creating Share 0
Creating Composing 0
Creating Composing 1
Creating Composing 2
Creating Composing 3
Creating Composing 4
Creating Composing 5
Creating Composing 6
Creating Composing 7
Creating Composing 8
Creating Composing 9
disposing Composing 0
disposing Composing 1
disposing Composing 2
disposing Composing 3
disposing Composing 4
disposing Composing 5
disposing Composing 6
disposing Composing 7
disposing Composing 8
disposing Composing 9
disposing Share 0
9.构造器内部调用多态方法
如过父类中存在一个方法在子类中被覆盖,但是此方法同时也在基类构造器中被调用,那么就会产生不恰当的调用。
所以编写构造器的时候,尽力避免书写复杂的调用,尽力调用那些不能被覆盖的方法(private final static的方法)
实例
public class Test {
public static void main(String[] args) {
new Extra(333);
}
}
public class Base {
public void f(){
System.out.println("base f");
}
public Base(){
System.out.println("Base start");
f();
System.out.println("Base end");
}
}
public class Extra extends Base {
private int x = 123;
public void f(){
System.out.println("Extra f "+x);
}
public Extra(int i){
System.out.println("Extra start");
this.x = i;
System.out.println("Extra end");
}
}
输出
Base start
Extra f 0
Base end
Extra start
Extra end
Base构造器调用的居然是子类的方法,而且变量i既不是123,也不是输入的333.这是因为这时候子类只对属性进行了初始化为0的动作,然后就被父类调用了。所以会出现这个问题。
10.协变返回类型
被覆盖的子类中的方法的返回值可以是父类中的子类型
11.继承的使用原则
用继承表达行为间的差异,用字段表达状态上的变化
public class Actor {
public void act(){}
}
public class HappyActor extends Actor {
@Override
public void act() {
System.out.println("i am happy");
}
}
public class SadActor extends Actor {
@Override
public void act() {
System.out.println("i am sad");
}
}
public class Stage {
private Actor actor = new HappyActor();
public void change(){
actor = new SadActor();
}
public void show(){
actor.act();
}
}
public class Test {
public static void main(String[] args) {
Stage stage = new Stage();
stage.show();
stage.change();
stage.show();
}
}
输出
i am happy
i am sad