封装,继承,多态是面向对象的三个基本特征。
封装,通过将细节“私有化”,把接口和实现分离开来。
继承,允许将对象为它自己本身的类型或它的父类来加以处理。
多态,则是消除类型之间的耦合关系。
public enum Note{
MIDDLE_C,C_SHARP,B_FLAT;
}
class Instrument(){
public void play(Note n){
System.out.println("Instrument.play()");
}
}
public class Wind extends Instrument(){
public void play(Note n){
System.out.println("wind.play()"+n);
}
}
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);
}
}
//wind.play() MIDDLE_C
在Music.java中的tune()方法中,它接受一个Instrument引用,但是输出的结果可以却是Instrment类的子类调用的play方法。那么编译器是如何知道Instrument引用到底是指向的哪一个对象呢?
实际上,编译器并不知道Instrument引用指向哪一个对象。我们需要了解一下绑定这个概念。
方法调用绑定将一个方法的调用和方法所属的类相关联被称作绑定,简单说就是JVM知道调用哪一个类的方法。如果在程序职系那个前进行绑定,就叫前期绑定。解决上面代码疑惑的办法就是后期绑定,也叫动态绑定或运行时绑定,含义就是在运行时根据对象的类型进行绑定。
Java中除了static方法和final方法之外,其它都所有的方法都是后期绑定。
所以将方法声明为final,可以有效地关闭动态绑定,告诉编译器不要对其进行动态绑定。所以最好根据设计来绝对是否使用final而不是出于提高性能。
有一个Circle类继承于Shape类,并且重写了父类的draw()
Shape shape = new Circle()
这样写是正确的,因为通过继承,Circle就是一种Shape,编译器认可这句话,所以就不会产生错误信息。
shape.draw()
由于多态(后期绑定),编译器还是会正确调用Circle.draw()方法。
再次强调,Java中除了static方法和final方法之外,其它都所有的方法都是后期绑定。
缺陷 覆盖私有方法public class PrivateOverride {
private void f(){
System.out.println("private f()");
}
public static void main(String[] args) {
PrivateOverride privateOverride = new Derived();
privateOverride.f();
}
}
class Derived extends PrivateOverride{
public void f(){
System.out.println("public f()");
}
}
// private f()
由于private方法被自动认为是final方法,而且对子类是屏蔽的。因此Derived.f()是一个全新的方法,并不是对父类的覆盖重载。
final关键字,告诉编译器不要对这个方法进行动态绑定,所以在运行前就已经绑定了父类。
域与静态方法**我们需要记住,只有普通的方法调用可以是多态的。**直接访问某个域,这个访问就在编译器进行解析。
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());// 0 1
Sub sub = new Sub();
System.out.println("sub.field="+sub.field+",sub.getfield()="+sub.getField()+",sub.getSuperField()"+sub.getSuperField());// 1 1 0
}
}
当Sub对象转型为Super 引用时,任何域访问操作都将由编译器解析,因此不是多态。
在本例中,为Super.field 和Sub.field 分配了不同存储空间。这样,Sub实际上有两个名称为field的域,它自己的和从Super得到的,如果想要得到Super.field必须指明super.field。
虽然这是一个容易混淆的问题,但是实践中从来不会发生,通常情况下所有的域都会设成private,并且不在父类和子类中的域赋予相同名字。
如果某个方法是静态的,它的行为就不具有多态性。
构造器内部的多态方法的行为class Glyph{
void draw(){
System.out.println("Glyph.draw()");
}
Glyph(){
System.out.println("Glyph before draw()");
draw();
System.out.println("Glyph after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius = 1;
RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph.draw(), radius= "+radius);
}
@Override
void draw() {
System.out.println("RoundGlyph.draw(),radius="+radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
/*
Glyph before draw()
RoundGlyph.draw(),radius=0
Glyph after draw()
RoundGlyph.draw(), radius= 5
RoundGlyph.draw(),radius=0 这句话就是在构造器内部调用正在构造的对象的某个动态绑定方法出现的问题。
这段代码的初始化过程是这样的:
- 在任何事情发生之前,将分配给对象的存储空间初始化成二进制的零。创建RoundGlyph对象时先初始化父类Glyph调用父类的无参构造器调用了被覆盖后的draw()(要在调用子类构造器之前调用)因为第一步的原因,此时radius=0;按照声明顺序调用成员的初始化方法。调用子类的构造器主体。
因此,在编写构造器时,有一条有效的准则:“用尽可能简单的方法使对象进入正常的状态,避免调用其它的方法”。
在构造器内唯一能安全调用的那些方法时父类中的final方法。
协变返回类型协变返回类型,表示在子类中的被覆盖的方法可以返回父类方法的返回类型的某一个子类型
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);
}
}
/*
Grain
Wheat



