7-9 复用,多态及接口

继承
  • 子类继承父类,那么意味着子类继承了父类的所有非private的属性和方法。
  • 子类在构造器初始化的时候,构造器的第一行会被隐式的调用父类的无参构造器,如果父类没有无参构造器,那么需要在子类构造器super显式调用父类有参构造器。
名称屏蔽

如果父类的某个方法被子类重载,子类不会屏蔽父类方法中的任何重载方法。

package com.test.reusing;

import static net.mindview.util.Print.*;

class Homer {
  char doh(char c) {
    print("doh(char)");
    return 'd';
  }
  float doh(float f) {
    print("doh(float)");
    return 1.0f;
  }
}

class Milhouse {}

class Bart extends Homer {

  void doh(Milhouse m) {
    print("doh(Milhouse)");
  }
}

public class Hide {
  public static void main(String[] args) {
    Bart b = new Bart();
    b.doh(1);
    b.doh('x');
    b.doh(1.0f);
    b.doh(new Milhouse());
  }
} /* Output:
doh(float)
doh(char)
doh(float)
doh(Milhouse)
*///:~
final关键字
  • final 修饰基本类型数据时 变量数值恒定不变,修饰引用类型 则该引用无法指向新的对象。
  • 空白final变量:被声明但是未被立即初始化(构造器内初始化)。
  • final 修饰方法参数变量, 意味着方法内无法更改参数值或者参数引用所指向的对象
package com.test.reusing;

class Poppet {
  private int i;
  Poppet(int ii) { i = ii; }
}

public class BlankFinal {
  private final int i = 0; // Initialized final
  private final int j; // Blank final
  private final Poppet p; // Blank final reference
  // Blank finals MUST be initialized in the constructor:
  public BlankFinal() {
    j = 1; // Initialize blank final
    p = new Poppet(1); // Initialize blank final reference
  }
  public BlankFinal(int x) {
    j = x; // Initialize blank final
    p = new Poppet(x); // Initialize blank final reference
  }
  public static void main(String[] args) {
    new BlankFinal();
    new BlankFinal(47);
  }
}
  • final 修饰方法时,子类无法重写该方法(其实所有private方法都被隐式指定为final的 然而这也无任何意义,因为子类是继承不到父类的final数据的)。
package com.test.reusing;
import static net.mindview.util.Print.*;

class WithFinals {
  // Identical to "private" alone:
  private final void f() { print("WithFinals.f()"); }
  // Also automatically "final":
  private void g() { print("WithFinals.g()"); }
}

class OverridingPrivate extends WithFinals {
  private final void f() {
    print("OverridingPrivate.f()");
  }
  private void g() {
    print("OverridingPrivate.g()");
  }
}

class OverridingPrivate2 extends OverridingPrivate {
  public final void f() {
    print("OverridingPrivate2.f()");
  }
  public void g() {
    print("OverridingPrivate2.g()");
  }
}

public class FinalOverridingIllusion {
  public static void main(String[] args) {
    OverridingPrivate2 op2 = new OverridingPrivate2();
    op2.f();
    op2.g();
    // You can upcast:
    OverridingPrivate op = op2;
    // But you can't call the methods:
    //! op.f();
    //! op.g();
    // Same here:
    WithFinals wf = op2;
    //! wf.f();
    //! wf.g();
  }
}
//父类的private方法对于子类来说是隐藏的,如果子类有和该private方法相同名称 相同参数等的方法,子类并未重写这个方法 而是仅仅生成了一个新的方法
  • final修饰类时 表示该类不能被继承。
继承与初始化
package com.test.reusing;
// The full process of initialization.
import static net.mindview.util.Print.*;

class Insect {
  private int i = 9;
  protected int j;
  Insect() {
    print("i = " + i + ", j = " + j);
    j = 39;
  }
  private static int x1 = printInit("static Insect.x1 initialized");
  static int printInit(String s) {
    print(s);
    return 47;
  }
}

public class Beetle extends Insect {
  private int k = printInit("Beetle.k initialized");
  public Beetle() {
    print("k = " + k);
    print("j = " + j);
  }
  private static int x2 = printInit("static Beetle.x2 initialized");
  public static void main(String[] args) {
    print("Beetle constructor");
    Beetle b = new Beetle();
  }
} 

//1 执行Beetle的main方法时,因为属性的初始化顺序要优先于方法 所以先执行  private static int x2 = printInit("static Beetle.x2 initialized");这一句代码,printInit位于Insect类中,同理需先初始化x1变量 因此输出如下
// static Insect.x1 initialized
// static Beetle.x2 initialized
//2 然后执行main方法 ,初始化Beetle,需要先初始化Insect, 初始化Insect时 先初始化变量,再执行构造器,输出如下 执行构造器前j=0,执行后j=39
// Beetle constructor
// i = 9, j = 0
//3 初始化Beetle,先初始化k(因为x1已经初始化过了,因此此时无需再初始化了) k=47,
//k = 47
//j = 39
多态

多态的本质是动态方法调用,也就是运行时判断对象类型 从而根据运行时的类型来调用方法。

构成多态的三个必要条件
1 要有继承
2 要有重写
3 父类引用指向子类对象
使用多态时的注意:当父类的方法是private修饰时,当子类看起来重写父类方法时,不会产生动态方法调用。这时编译期也不会报错,也不会达到我们的预期。所以尽量使用@Override注解 它可以提示我们方法重写失败,从而避免错误。

属性和静态方法不能使用多态

当父类引用指向子类对象的时候,任何属性的解析都是由编译器解析,而不是运行时确定的 因此属性不能体现多态

package com.test.polymorphism.shape1;

class Super {
  public int field = 0;
  public int getField() { return field; }
}

class Sub extends Super {
  public int field = 1;
  public int getField() { return field; }
  public int getSuperField() { return super.field; }
}

public class FieldAccess {
  public static void main(String[] args) {
    Super sup = new Sub(); // 向上转型
    System.out.println("sup.field = " + sup.field + ", sup.getField() = " + sup.getField());
    Sub sub = new Sub();
    System.out.println("sub.field = " + sub.field + ", sub.getField() = " +sub.getField() +", sub.getSuperField() = " + sub.getSuperField());
  }
} /* Output:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
*///:~

上面的例子中Sub实际上包含两个field,一个是自己的 一个是父类的。
一般来说我们都会将属性私有化(private),另外子类和父类属性一般不会设置同名。

public class StaticSuper {
    public static void main(String[] args) {
        Sup s = new Son();
        s.staticGet();
        s.commomGet();
    }
}

class Sup{
    public static void staticGet() {
        System.out.println("父类静态方法");
    }
    
    public  void commomGet() {
        System.out.println("父类普通方法");
    }
}

class Son extends Sup{
    public static void staticGet() {
        System.out.println("子类静态方法");
    }
    
    public  void commomGet() {
        System.out.println("子类普通方法");
    }
}
// 输出
//父类静态方法
//父类普通方法
构造器内部的多态
package com.test.polymorphism;
import static net.mindview.util.Print.*;

class Glyph {
  void draw() {
      print("父类draw方法");
}
  Glyph() {
    print("父类初始化前");
    draw();
    print("父类初始化后");
  }
}   

class RoundGlyph extends Glyph {
  private int radius = 1;
  RoundGlyph(int r) {
    radius = r;
    print("子类构造器方法中, radius = " + radius);
  }
  void draw() {
    print("子类draw方法中, radius = " + radius);
  }
}   

public class PolyConstructors {
  public static void main(String[] args) {
    new RoundGlyph(5);
  }
} /* Output:
父类初始化前
子类draw方法中, radius = 0
父类初始化后
子类构造器方法中, radius = 5
*///:~

这里我们在父类调用draw方法,但是却调用了子类的draw方法,这正是多态所期望的,但是radius=0,而不是初始值1。这是因为真实初始化时的顺序是如下的,
1 在任何事物反生之前,将分配对象的存储空间初始化成0
2 调用父类的构造器,期间需要调用重写后的draw方法,此时由于第一步的缘故 radius=0,
3 按顺序调用子类的成员初始化方法
正因为上面的方法调用,产生了让我们意料之外的结果(因为我们从来没有让radius=0),因此为了避免上述意外,我们最好不要在构造器内调用其他方法,此外我们可以在构造器内安全的调用final和private方法 这些方法不能被重写,因此也就不会出现上面的问题了。

返回协变类型
class Grain {
  public String toString() { return "Grain"; }
}

class Wheat extends Grain {
  public String toString() { return "Wheat"; }
}

class Mill {
  Grain process() { return new Grain(); }
}

class WheatMill extends Mill {
  Wheat process() { return new Wheat(); }
}

public class CovariantReturn {
  public static void main(String[] args) {
    Mill m = new Mill();
    Grain g = m.process();
    System.out.println(g);
    m = new WheatMill();
    g = m.process();
    System.out.println(g);
  }
} /* Output:
Grain
Wheat
*///:~

子类重写父类方法时,返回值可以是父类方法返回值的子类,这种类型称为协变类型。

接口与抽象类

什么时候使用接口?什么时候使用抽象类?
假设我们目前需要设计猫科动物,所有的猫科动物可以跑,然后设计老虎 老虎游泳。然后设计豹 豹可以爬树。最后假设我们要设计豹虎兽 这个豹虎兽既可以爬树也可以游泳。
接口可以如下设计

interface Cat{
    void canRun();
}

interface Tiger extends Cat{
    void canSwim();
}

interface Leopard extends Cat{
    void canClimbTree();
}

class LeopardAndTiger  implements Tiger,Leopard{
    @Override
    public void canRun() {
        
    }

    @Override
    public void canClimbTree() {
        
    }

    @Override
    public void canSwim() {
        
    }
}

由于类只能单继承 所以如果Cat ,Tiger ,Leopard 被设计为抽象类时LeopardAndTiger类是无法起作用的。
此外由于jdk8之后的接口方法可以有默认方法实现,接口相比抽象类就更有优势了。
总结:JDK8下 由于方法可以有默认实现 因此会导致以前原本需要用抽象类类设计倒向用接口来设计了,但是抽象类还是有一点自己的用武之地的,因为抽象类允许非final属性,允许方法是public,private和protected的 ,所以 如果你关心属性或方法是否是private,protected,non-static或final的,那么考虑抽象类,如果关心的是java中的多继承,那么用接口吧。8之前的话 如果注重父类提供的默认实现方法功能应该使用抽象类 而不是接口。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355