面向对象程序设计语言的四个基本特点:抽象 继承 多态 封装
多态的作用:松耦合 高内聚 紧封装
向上转型
// Instrument 类
enum Note{ MIDDLE_C,C_SHARP,B_FLAT }
public class Instrument{
public void play(Note note){ System.out.println("Instrument.play()"); }
}
class Wind extends Instrument {
@Override
public void play(Note n){ System.out.println("Wind.play()"); }
}
class Stringed extends Instrument{
@Override
public void play(Note note) { System.out.println("Stringed.play()"); }
}
// Music 类
public class Music {
public static void tune(Instrument i){ i.play(Note.MIDDLE_C ); }
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute);
// 只要是乐器 都要继承乐器类 tune 方法接收基类的类型 这就是多态 提高了程序的扩展性
// 如果 tune方法 不用基类作为参数 每新写一个乐器类 就要重新写一个 tune方法
Stringed guZheng = new Stringed();
tune(guZheng);
}
}
运行结果:
虽然接收的是基类的类型 但是具体执行的是其子类的方法
方法调用绑定 将一个方法的调用与其方法的主体关联起来
1. 前期绑定 如果编译之前就关联起来了
static修饰的方法 final修饰的方法 私有方法 都属于前期绑定 其他的为后期绑定
2. 后期绑定 在运行的时候根据对象的类型进行绑定 (又叫 动态绑定 或 运行时绑定)
上面的例子 当tune()方法接收参数为父类时 传给tune方法的对象为子类对象 编译器是不知道的传的子类具体是哪一个 但是还是正确的调用了子类的方法 这就用到了方法绑定
public class PrivateOverride {
private void f(){ System.out.println("private f()"); }
private void g(){ System.out.println("private g()"); }
public static void main(String[] args) {
PrivateOverride p = new Derived();
System.out.println("不加强制转换");
p.f();
p.g();
}
}
class Derived extends PrivateOverride{ public void f() { System.out.println("public f()"); } }
结果:
不加强制转换 :被private修饰的f方法是静态绑定的在编译期与类绑定一起 当调用的时候会调用父类的f方法 就如调用g方法一样
加上强制转换 :把父类的引用强制转换为子类对象 并强制调用子类f方法
关于作用域的访问
class Father{
public int s = 0;
public int getS() { return s; }
}
class Son extends Father{
public int s = 1;
public int getS() { return s; }
public int getFatherS() { return super.s;}
}
public class Main {
public static void main(String[] args) {
Father f = new Son();
System.out.println("Father f = new Son() -》 s:"+f.s+", getS:"+f.getS());
Son s = new Son();
System.out.println("Son s = new Son() -》 s:"+s.s+", getS:"+s.getS()+", getFatherS:"+s.getFatherS());
}
}
输出结果:
任何域的访问操作都是由编译器解析的因此不是多态的 方法是多态的 当我们声明的类型是基类而实际创建的是子类 通过引用访问域的时候 因为域不是多态因此拿到的值是基类的值,而方法是多态的是后期绑定的因此通过引用访问的是子类的方法。
即你声明的是基类的访问的成员变量就是基类的 你声明的是子类的访问的成员变量就是子类的
静态方法也是如此
构造器多态 构造器是特殊的方法 但不具备多态性
构造器的执行顺序 先执行父类的构造器 每个构造器执行之前它的实例成员都要进行实例化 然后才执行构造器
对象销毁刚好相反
class Glyph{
void draw(){ System.out.println("Glyph.draw()"); }
Glyph(){
System.out.println("Glyph() 之前");
draw();
System.out.println("Glyph() 之后");
}
}
class RoundGlyph extends Glyph{
private int i = 1;
RoundGlyph(int r){
i = r;
System.out.println("RoundGlyph.RoundGlyph(int r) i="+i);
}
@Override
void draw(){ System.out.println("RoundGlyph.draw() i :"+i); }
}
public class Main{
public static void main(String[] args) {
new RoundGlyph(2);
}
}
运行结果
由结果看 在父类的构造方法中 本想调用父类的draw方法 结果却调用了子类的方法 这是由于多态的原因 因为子类又重写了父类的方法 当创建子类对象时 方法属于后期绑定 导致子类把父类的方法覆盖了 当程序运行时调用的就是父类的方法即使写在父类的构造器中 然而父类的方法又不是我们想要的方法 即使我们在子类进行了i的初始化赋值 但是却没有赋值之前就发生调用
由上面总结 :当我们编写构造器时应尽量避免在构造器中调用方法在一些初始化的工作 如果非要使用这种调用又想要安全就把这个类定义为final的
协变返回类型 子类重写父类方法时,返回类型可以是基类方法返回类型的子类。
class Father {
public String toString(){ return "Father()";}
}
class Son extends Father {
public String toString(){ return "Son()";}
}
class CreatFather {
public Father creatObject(){ return new Father();}
}
class CreatSon extends CreatFather {
//覆盖父类的方法 返回值为父类方法返回值的子类类型
public Son creatObject(){ return new Son();}
}
public class Main{
public static void main(String[] args) {
CreatFather cf = new CreatFather();
Father f = cf.creatObject();
System.out.println(f);
CreatSon cs = new CreatSon();
f = cs.creatObject();
System.out.println(f);
}
}
输出结果