美文网首页
7-9 复用,多态及接口

7-9 复用,多态及接口

作者: Teemo_fca4 | 来源:发表于2019-05-14 11:26 被阅读0次
继承
  • 子类继承父类,那么意味着子类继承了父类的所有非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之前的话 如果注重父类提供的默认实现方法功能应该使用抽象类 而不是接口。

相关文章

  • 7-9 复用,多态及接口

    继承 子类继承父类,那么意味着子类继承了父类的所有非private的属性和方法。 子类在构造器初始化的时候,构造器...

  • jvm结构&运行机制&多态实现

    浅析Java虚拟机结构与机制 浅谈多态机制的意义及实现 多态:编译时多态(重载)、运行时多态(继承父类、实现接口)...

  • 接口和抽象类该如何取舍

    抽象类和接口的区别在于使用动机。使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态性。 “面向接口编程”中...

  • 抽象类 vs 接口

    抽象类和接口的区别在于使用动机。使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态性。

  • Spring的学习

    接口和抽象类? 抽象类和接口的区别在于使用动机。使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态性。1....

  • 面向对象:论封装的重要性

    面向对象的基本概念包括对象、类、抽象、封装、继承、多态、接口、消息、组件、复用和模式等。 封装 访问权限的控制常被...

  • 接口 , 抽象 类一些想法

    接口可以利用多态 , 建立中间层 , 隔离变化 抽象类 可以消除重复代码 , 起到复用和隔离变化 的作用 (有单继...

  • Java基础-day12-接口

    接口 1. 接口 1.1 接口使用操作 2. 多态 2.1 动物园 2.2 USB接口 2.3 多态总结

  • 编程模式·小结

    常见编程模式小结 继承 & 组合复用代码,两者都可以继承有多态效果,满足“里氏代换原则”设计上最好用单继承多接口(...

  • 《Java8学习笔记》读书笔记(八)

    第7章 接口与多态 学习目标 使用接口定义 了解接口的多态操作 利用接口枚举常数 利用enum枚举常数 7.1 ...

网友评论

      本文标题:7-9 复用,多态及接口

      本文链接:https://www.haomeiwen.com/subject/ecwjaqtx.html