多态性又叫动态绑定、推迟绑定或运行期绑定。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
上溯造型:
interface Animal{ //动物接口
void play(){};
}
class Dog implements Animal{ //狗类
void play(){
System.out.println("Dog");
}
}
class Cat implements Animal{ //猫类
void play(){
System.out.println("Cat");
}
}
public class Test{
void play(Animal a){ //参数为动物接口
a.play();
}
public static void main(String[] args){
Dog dog = new Dog();
Cat cat = new Cat();
play(dog); //传入狗类
play(cat); //传入猫类
}
}
为什么要用上溯造型?那上面的代码为例,如果不用上溯造型,则Test类中要写两个play()方法,参数分别为狗类和猫类。如果衍生类增加,play()方法的个数也要增加。使用上溯造型可以不管衍生类,只需要跟基础类打交道,省下很大的工作量。
深入理解动态绑定:Test类的play()方法接收一个Animal句柄,那么它是怎么知道这个句柄是指向Dog还是Cat的呢?这必须在运行期间才能确定。所以在运行之前进行的绑定叫做“早期绑定”,在运行期进行的绑定叫做“动态绑定”。Java中方法的绑定都是动态绑定,除非一个方法被声明为final.
下面这条语句成立也是因为动态绑定:
Animal a = new Dog();
下溯造型:
上溯造型会丢失具体的类型信息,所以为了获取具体的类型信息可以使用 “下溯造型”。然而,上溯造型肯定 是安全的;基础类不可能再拥有一个比衍生类更大的接口。因此,我们通过基础类接口发送的每一条消息都 肯定能够接收到。但在进行下溯造型的时候并不一定安全。
class Useful {
public void f() {}
}
class MoreUseful extends Useful {
public void f() {}
public void g() {}
}
public class RTTI {
public static void main(String[] args) {
Useful[] x = {
new Useful(),
new MoreUseful() //上溯造型
};
((MoreUseful)x[0]).g(); //下溯造型报错 Exception thrown
((MoreUseful)x[1]).g(); //下溯造型成功
}
}
如上代码所示,衍生类上溯造型会丢失数据,但再下溯造型回来后这些数据还存在。上述代码也表示下溯造型并不一定是安全的。
抽象类和接口:
- 抽象类(abstract)可以含有一个或多个抽象方法(只有方法的定义而没有方法的实现),也可以不含有抽象方法。如果想从一个抽象类继承,而且新类要实例化对象,则必须实现抽象类的所有抽象方法。否则,新类也是抽象类。
- 接口(interface)可以理解为纯抽象类。它完全禁止方法的实现。接口中的数据成员(字段)默认为static和final, 成员函数(方法)默认为public。所以在实现一个接口时,来自接口的方法必须也是public。
实现按一个接口要使用 implements(实现)关键字,其他操作和继承非常类似。
Java中的“多重继承”可以靠接口实现。因为Java中类只能单一继承而接口可以多继承。
如果一个新类同时要继承一个基础类和多个接口,基础类必须写在前面。
class Student extends People implements Eat, Sleep{
//...
}
接口扩展extends关键字:
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
使用接口的原因:
- 能上溯造型至多个基础类
- 防止其他人制作这个类的对象(和抽象类相同)
接口 or 抽象类?
若使用接口,我们可以同时获得抽象类以及接口的好处。所以假如想创建的基础类没有任何方法定义或者成员变量,那么就选择接口。事实上,如果事先知道某种东西会成为基础类,那么第 一个选择就是把它变成一个接口。只有在必须使用方法定义或者成员变量的时候,才应考虑采用抽象类。
内部类:
每个内部类都可以独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了一个(接口的)实现,对内部类都没有影响。
如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得更加完整。接口解决了部分问题,而内部类有效地实现了”多重继承“。也就是说,内部类允许继承多个非接口类型(类或抽象类)。
定义在一个类内部的类被称为内部类。内部类拥有对封装类所有元素的访问权限,因为内部类的对象默认持有创建它的那个封装类的一个对象的句柄。
若想在除外部类非 static 方法内部之外的任何地方生成内部类的一个对象,必须将那个对象的类型设为“外部类名.内部类名”,而且创建内部类实例必须先有一个外部类实例。
public class School{
class Student{ //内部类
int name;
}
public Student make(){
return new Student();
}
public static void main(String[] args){
School sh = new School();
School.Student st = sh.make(); //生成内部类对象
}
}
内部类的特点:
- 数据隐藏
- 能够访问外围对象的所有成员。
.this和 .new:
如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟原点+this。
如果想创建某个内部类对象,必须在new表达式中提供对其他外部类的引用,这需要.new语法,例如:
public static void main(String[] args){
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
}
内部类和上溯造型:
当我们准备上溯造型到一个基础类(特别是到一个接口)的时候,内部类就开始发挥其关键作用。这是由于内部类随后可完全进入不可见或不可用状态——对任何人都将如此。所以我们可以非常方便地隐藏实施细节。我们得到的全部回报就是一个基础类或者接口的句柄,而且甚至有可能不知道准确的类型。
interface Destination { //接口
String readLabel();
}
public class Parcel3 {
protected class PDestination implements Destination { //内部类
public String readLabel() { System.out.println("readLabel"); }
}
public Destination dest() {
return new PDestination();
}
}
class Test {
public static void main(String[] args) {
Parcel3 p = new Parcel3();
Destination d = p.dest(); //上溯造型
}
}
定义在方法和作用域中的内部类:
上面的情况是使用内部类的经典情况,但内部类还有其他的用法:
- 在一个方法内定义的类
- 在方法的一个作用域内定义的类
- 一个匿名类,用于实现一个接口
- 一个匿名类,用于扩展拥有非默认构建器的一个类
- 一个匿名类,用于执行字段初始化
- 一个匿名类,通过实例初始化进行构建(匿名内部类不可拥有构建器)
采用方法或作用域中的内部类有两个原因:
- 我们准备实现某种形式的接口,使自己能创建和返回一个句柄
- 要解决一个复杂的问题,并希望创建一个类,用来辅助自己的程序方案。同时不愿意把它公开
从内部类(非static)继承:
由于内部类构建器必须同封装类对象的一个句柄联系到一起(见上面生成内部类对象的代码),所以从一个内部类继承的时候,情况会稍微变得有些复杂。这儿的问题是封装类的“秘密”句柄必须获得初始化,而且在衍生类中不再有一个默认的对象可以连接。解决这个问题的办法是采用一种特殊的语法,明确建立这种关联:
class WithInner {
class Inner {}
}
public class InheritInner extends WithInner.Inner { //继承内部类
//! InheritInner() {} // 不会通过编译,因为继承的内部类的封装类没有显式初始化
InheritInner(WithInner wi) {
wi.super(); //保证封装类得到初始化
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
匿名类:
public class Parcel6 {
public Contents cont() {
return new Contents() { //匿名类
private int i = 11;
public int value() { return i; }
}; //必须用分号
}
//main()方法
}
上面代码包含一个匿名类--它没有名字。这种奇怪的语法要表达的意思是:“创建从 Contents 衍生出来的匿名类的一个对象”。由 new 表达式返回的 句柄会自动上溯造型成一个Contents 句柄。上述匿名类代码等同于:
class MyContents extends Contents {
private int i = 11;
public int value() { return i; }
}
return new MyContents();
由于没有名字,所以匿名类没有构造器,所以我们也不能直接生成一个匿名类的对象,但我们可以通过包含匿名类的方法来实现:
public class Par {
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Par p = new Par();
Destination d = p.dest("Tanzania");
}
}
分析上述代码,我们可以通过对dest()方法的调用来生成匿名类对象,而且该方法可以认为是匿名类的构建方法,当然它的功能是有限的:因为无法重载,所以“构建器”只能有一个。
嵌套类:
嵌套类即 static 内部类,必须记住内部类的对象默认持有创建它的那个封装类的一个对象的句柄。然而,假如我们说一个内部类是static 的,这种说法却是不成立的。嵌套类意味着:
- 为创建一个 static 内部类的对象,我们不需要一个外部类对象
- 不能从嵌套类的对象中访问非静态的外围类对象
嵌套内部类和普通内部类还有一个区别。普通内部类不能含有static数据和static字段,也不能包含嵌套类。但嵌套类可以包含这些东西。
- 静态内部类才可以声明静态方法
- 静态方法不可以使用非静态变量
- 抽象方法不可以有函数体
针对第一条“为创建一个 static 内部类的对象而不需要一个外部类对象”,那么可将所有东西都设为static。为了能正常 工作,同时也必须将内部类设为static:
abstract class Con {
abstract public int value();
}
public class Per{
private static class PCon extends Con{ //静态内部类
private int i = 11;
public int value() { return i; }
}
public static Con cont() { //静态方法
return new PCon();
}
public static void main(String[] args) {
Con c = cont(); //不需要创建封装类
}
}
static内部类可以成为接口的一部分----因为类是“静态”的,所以它不会违反接口的规则(static 内部类只位于接口的命名空间内部):
interface IInterface {
static class Inner {
int i, j, k;
void f() {}
}
}
生成内部类对象:
普通内部类--除非已拥有外部类的一个对象,否则不可能创建内部类的一个对象:
public class Par {
class Contents { //内部类
private int i = 11;
public int value() { return i; }
}
public static void main(String[] args) {
Par p = new Par(); //需要外部类
Par.Contents c = p.new Contents(); //创建内部类
}
}
嵌套类--不需要指向外部类对象的一个句柄:
public class Par {
static class Contents { //静态内部类
private int i = 11;
public int value() { return i; }
}
public static void main(String[] args) {
//不需要封装类(外部类)
Contents c = new Contents(); //创建内部类
}
}