7-9 复用,多态及接口

2019-05-14  本文已影响0人  Teemo_fca4
继承
名称屏蔽

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

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关键字
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);
  }
}
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方法相同名称 相同参数等的方法,子类并未重写这个方法 而是仅仅生成了一个新的方法
继承与初始化
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之前的话 如果注重父类提供的默认实现方法功能应该使用抽象类 而不是接口。

上一篇 下一篇

猜你喜欢

热点阅读