接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。
抽象类和抽象方法
我们为了解决某个问题建立了一个类,这个类定义了方法但是没有具体的方法体,我们建立这个类的目的是想它的让导出类来做具体的实现,这个类只是建立了一个通用接口,让不同的子类用不同的方式表示此接口。通用接口建立起一种基本形式,以此表示所有导出类的共同部分。这样的通用接口另一种叫法是“抽象基类”,简称“抽象类”。
我们建立起这种类创建了这种类的对象,向这个对象发送消息,发现没有什么意义。因为这个类中定义方法没有方法体,我们拿到这个类的对像什么也不能做,这个对象对我们来说没有任何意义,所以我们不想得到这样的对象,甚至希望不能创建这样的对象。
为此,Java提供了一个叫抽象方法的机制,这种方法不是完成的;仅有声明而没有方法体。抽象方法的声明采用的语法:
abstract void f();
包含抽象方法的类叫抽象类。如果一个类包含了一个或多个抽象方法,该类必须被限定为抽象的。类被定义为抽象类,那么这个类是不完整的。如果我们试图创建一个抽象类的对象,由于为抽象类创建对象是不安全的,所以编译器会报错。
如果从一个抽象类继承,并想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法的定义。如果不这样做,那么导出类便也是抽象类,且编译器将会强制我们中abstract关键字类限定这个类。
类被定义为抽象类,但这个类可以没有抽象方法。定义一个没有抽象方法的抽象类,可能是不想能够创建这个类的对象。
接口
abstract关键字允许人们在类中创建一个或多个没有任何定义的方法——提供了接口部分。interface关键字使抽象的概念更向前迈进了一步。interface这个关键字产生一个完全抽象的类,它根本没有提供任何具体的实现。它允许创建者确定方法名、参数列表、和返回类型,但没有任何方法体。
接口表示:“所有实现了该特定接口的类看起来都像这样”。因此,任何使用某些特定接口的代码都知道可以调用该接口的哪些方法,而且仅需要知道这些。
interface不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型,类实现某种类似多重继变种的特性。
定义一个接口只需把class换成interface即可。接口种定义的方法可以显示的声明为public的,但即使你不这么做,它们也是public的。当要实现一个接口时(使用implements关键字),在接口中定义的方法必须被定义为public的,否则它们只能得到默认的包的访问权限,这样就违反了继承的规则(导出类中重写的方法的权限不比基类的更窄),所以编译器不让通过。接口中可以定义字段(域),这些域都是static fianl的,但是定义的域不能是“空白fi nal”,但是可以被非常量表达式初始化。域不是接口的一部分,它们的值被存储在该接口的静态存储区域内。
完全解耦
只要一个方法操作的是类而不是接口,那么你只能使用这类及其导出类。如果你想要将这个方法应用于不在此继承结构中的某个类,那么该怎么办呢?多态的作用是来消除类型之间的耦合关系。在继承结构中,可以将导出类型的对象的引用向上转型为基类型的引用。一个类实现了特定的接口,那么实现类看起来像这个接口。可以将实现类的对象的引用向上转型为接口类型的引用。所以上面的问题可以可以使用接口来解决。一个接口可以有若干实现类,而这些实现类这间可以没有什么关系。
Java 中的多重继承
Java中规定,类只能是单根继承,即一个类只能有一个父类。接口不仅仅只是一种更纯粹形式的抽象类,它的目标比这要高。因为接口时根本没有任何具体实现的——也就是说,没有任何接口相关的存储,因此也就无法阻止多个接口的组合。Java中可以去继承一个类,实现多个接口,这个导出类最终看起来有点像“它是一个a和一个b以及一个c”。例:
class X extends A implements B ,C{
...
}
class S implements B ,C{
...
}
导出类可以向上转型为基类或每一个接口,即能够上转型为多个基类,这也是使用接口的核心原因。
通过继承来扩展接口
通过继承,可以很容易的在接口中添加新的方法声明,还可以通过继承在新接口中组合数个接口。例:
interface A{
void a();
}
interface B extends A{
void b();
}
interface C extends A , B{
void c();
}
此语法只能适用于接口。但是这样可能带来一个问题,如果在组合接口是,接口中出现名字冲突怎么办?例:
interface L1{
void f();
}
interface L2{
int f(int i);
}
interface L3{
int f();
}
class C1 implements L1,L2{
public void f(){
}
public int f(int i){
}
}
// class C2 implements L1,L3{
// public void f(){
// }
// public int f(int i){
// }
//}
// class L4 extends L1,L3{
//}
当方法实现、覆盖、重写就会很容易产生混淆。而且重载方法仅通过返回类型是区分不开的。所以上面被注释调的代码是编译不通过的。
在打算组合不同接口中使用相同的方法名通常会造成代码可读性的混乱,所以尽量避免这样情况。