继承
- 子类继承父类,那么意味着子类继承了父类的所有非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之前的话 如果注重父类提供的默认实现方法功能应该使用抽象类 而不是接口。