(1)“封装”通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”把接口和实现分离开来。
(2)多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要它们都是从同一基类导出而来的。这种区别是根据方法行为的不同而表示出来的,虽然这些方法都可以通过一个基类来调用。
(1)将一个方法调用同一个方法主体关联起来被称为绑定。若在程序执行前进行绑定(如果有的话,由编译器连接程序实现),叫做前期绑定。
(2)与之对应的是后期绑定的概念:就是在运行时根据对象的类型进行绑定
(3)Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定
(4)final方法可以调用生成更有效的代码,但是对系统的整体性能不会有太大的提高,所有建议根据设计来判断何时需要用final进行修饰
(5)静态的方法被导出类覆盖时向上转型不会正确找到导出类的方法,而是找到基类的方法,这是因为静态的方法属于前期绑定,所有对于含有静态方法的类继承时,要谨慎设计,如以下例子:
public class MyTest1 { public int i = 11; public static void f(){ System.out.println("This is the MyTest1 static mothod f"); } public void g(){ System.out.println("This is the MyTest1 mothod g"); } }
public class MyTest2 extends MyTest1{ public int i = 12; // @Override //方法f编译错误,由于f是静态方法,继承后是两个域的方法,不属于覆盖 public static void f(){ System.out.println("This is the MyTest2 static mothod f"); } @Override public void g(){ System.out.println("This is the MyTest2 mothod g"); } public void h(){ System.out.println("This is the MyTest2 mothod h"); } public static void main(String[] args){ MyTest1 t = new MyTest2(); // t.h(); //编译错误,方法丢失,基类中无此方法 t.g(); t.f(); System.out.println(t.i); //默认绑定基类的i,不受导出类影响(没有方法绑定机制,或者都理解为“前绑定”) } }
运行结果:
This is the MyTest2 mothod g This is the MyTest1 static mothod f 11
(6)可扩展性:只与基类接口通信,这样的程序是可扩展的,因为可以从通用的基类继承出新的数据类型,从而新添加一些功能。那些操纵基类接口的方法不需要任何改动就可以应用于新类
(7)只有非private方法才可以被覆盖,确切的说,在导出类中,对于基类中的private方法,最好采用不同的名字(如果加上@overrider,会报编译错误,因为不存在覆盖的说法)
(8)你通常会将所有的域都设置成private,因此不能直接访问它们,其副作用是只能调用方法来访问。另外,你可能不会对基类中的域和导出类中的域赋予相同的名字,因为这种做法容易令人混淆。
(9)变量不存在向方法的覆盖绑定机制。(可以理解为都是“前绑定”)
(1)它们实际上是static方法,只不过该static声明是隐式的
(2)构造器具有一项特殊任务:检查对象是否被正确地构造。导出类只能访问它自己的成员,不能访问基类中的成员(基类成员通常是private类型)。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。(尽管用super来调用变量也是可以的,但这会大大增加维护难度,违反定律)
(3)以下说明继承类调用的顺序
(31)依次加载类的静态变量与方法(遇extends关键字时会加载基类)
(32)调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最底层的导出类。
(33)按声明顺序调用成员的初始化方法。
(34)调用导出类构造器的主体
实例:
public class MyTest3 extends PortableLunch{ public MyTest3(){ System.out.println("MyTest3()"); } public static void main(String[] args){ MyTest3 t1 = new MyTest3(); System.out.println("---------"); MyTest3 t2 = new MyTest3(); } } class Meal{ private Bread b = new Bread(); private int i = Meal.f(); //不会加载 Meal(){ System.out.println("Meal()"); } private static int f(){ System.out.println("static class Meal"); return 11; } } class Bread{ Bread(){ System.out.println("Bread()"); } } class Cheese{ Cheese(){ System.out.println("Cheese()"); } } class Lettuce{ Lettuce(){ System.out.println("Lettuce()"); } } class Lunch extends Meal{ private Cheese c = new Cheese(); private static int i = Lunch.f(); //会加载(静态的变量只会加载一次) Lunch(){ System.out.println("Lunch()"); } private static int f(){ System.out.println("static class Lunch"); return 12; } } class PortableLunch extends Lunch{ private Lettuce l = new Lettuce(); private static int i = PortableLunch.f(); //会加载(静态的变量只会加载一次) PortableLunch(){ System.out.println("PortableLunch()"); } private static int f(){ System.out.println("static class PortableLunch"); return 13; } } //继承过程中会调用基类的构造器,如果为私用则编译错误,找不到编译器 //class Soup{ // private Soup(){ // // } //} // //class Sup extends Soup{ // //}
运行结果:
static class Lunch static class PortableLunch Bread() static class Meal Meal() Cheese() Lunch() Lettuce() PortableLunch() MyTest3() --------- Bread() static class Meal Meal() Cheese() Lunch() Lettuce() PortableLunch() MyTest3()
(4)构造器内部的多态行为(”Java设计缺陷“)
看下面例子:
public class MyTest4{ public static void main(String[] args){ new RoundGlyph(5); } } 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.RoundGlyph(),radius = "+ radius); } void draw(){ System.out.println("RoundGlyph.draw(),radius = "+radius); } }
运行结果:
Glyph() before draw() RoundGlyph.draw(),radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(),radius = 5
Glyph.draw()方法设计为要被覆盖,但是我们看到redius的值为0
(41)因此,编写构造器时有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法”。在构造器内唯一能够安全调用的那些方法是基类中的final方法(也适用于private方法,它们自动属于final方法)。这些方法不能被覆盖,因此也就不会出现上述令人惊讶的问题。你可能无法总是能够遵循这条准则,但是应该朝着它努力。
(1)由于向上转型(在继承层次中向上移动)会丢失具体的基类信息,所以我们就想,通过向下转型-也就是在继承层次中向下移动-应该能够获取类型信息。然而,我们知道向上转型是安全的,因为基类不会具有大于导出类的接口。因此,我们通过基类接口发送的消息保证都能被接受。但是对于向下转型,例如,我们无法知道一个“几何形状”它确实就是一个“圆”,它可以是一个三角形、正方形或其他一些类型。
向上转型:安全,如果不能向上转型(编译器报错)
向下转型:不安全,通过强制转型,如果不能向下转型(编译器不会报错,运行时会抛出类转换异常,通常先用 instnceof 来判断基类的引用是否能转化成导出类,在程序上进行简单保护
