八.接口和内隐类
接口和内隐类为你系统中的对象提供更为方便的组织和控制方式。
**接口:**interface更胜于抽象类,因为我们能够借以撰写出可被向上转型为多个基本类型的class,从而达到像c++一样的多重继承的变形。
interface可以内含数据成员,但是这些数据成员会自动变为static和final。是的,interface所提供的只是形式,不含实现的细目。
interface所陈述的是:“所有实现出本接口的classes,看起来都应该像这样。”
方法:interface中的方法都是public。所以当你实现某个接口时,必须将继承自该接口的所有方法都声明为public!
代码演示:
package test;
public class Test {
static void t(CanFight x){x.fight();}
static void u(CanSwim x){x.swim();}
static void v(CanFly x){x.fly();}
static void w(ActionCharacter x){x.fight();}
public static void main(String[] args) {
Hero h = new Hero();
t(h);
u(h);
v(h);
w(h);
}
}
interface CanFight{
void fight();
}
interface CanSwim{
void swim();
}
interface CanFly{
void fly();
}
class ActionCharacter{
public void fight(){System.out.println("action fight");}
}
class Hero extends ActionCharacter implements CanFight,CanFly,CanSwim{
public void swim(){System.out.println("swim");};//实现
public void fly(){System.out.println("fly");};//实现
}
注意,interface CanFight及class ActionCharacter中的fight()的标记式是一致的(这样Hero通过继承ActionCharacter的方法,相当于实现了CanFight接口未实现的方法)
接口存在意义?
1.能够被向上转型为多个基本性别
2.让客户端程序员无法产生其对象,并因此确保这只是一个接口。
interface同时赋予你抽象类和接口的好处,因此你的base class可以不带任何函数定义或任何成员变量,那么应该优先考虑用interface。
扩充interface:利用继承,可以轻易将新的函数加至interface中,也可以通过继承extends将多个interface结合为一个新的interface。
产生产量群:因为interface中的所有数据成员都会自动成为static和final,所以能够方便的产生常量群。
定义于interface中的数据成员都是static和final,但不能是blank finals。但可以被非常量表达式初始化。如:
public interface Rand{
int rint = (int)(Math.random()*10);
}
嵌套的interface:
package test;
import test.A.DImp2;
class A{
interface B{//default
void f();
}
public class BImp implements B{//public
public void f(){}
}
private class BImp2 implements B{//private
public void f(){}
}
public interface C{//public
void f();
}
class CImp implements C{//default
public void f(){}
}
private class CImp2 implements C{//private
public void f(){}
}
private interface D{//private
void f();
}
private class DImp implements D{//private
public void f(){}
}
public class DImp2 implements D{//public
public void f(){}
}
public D getD(){
return new DImp2();
}
private D dRef;
public void receiveD(D d){
dRef = d;
dRef.f();
}
}
interface E{
interface G{
void f();
}
//多余的public
public interface H{
void f();
}
void g();
//错误:成员只能是public
//!private interface I{}
}
public class Test1 {
public class BImp implements A.B{
public void f(){}
}
class CImp implements A.C{
public void f(){}
}
//不能实现一个private interface,除非在interface被定义的class中
//!class DImp implements A.D{
//public void f(){}
//}
class EImp implements E{
public void g(){}
}
class EGImp implements E.G{
public void f(){}
}
class EImp2 implements E{
public void g(){}
class EG implements E.G{
public void f(){}
}
}
public static void main(String[] args) {
A a = new A();
//A.D not visible
//!A.D ad = a.getD();
A.DImp2 di2 = (DImp2) a.getD();//向下转型
a.receiveD(a.getD());
}
}
说明:将interface嵌套在class中,相当直觉。在其中的interface可被定义为正常的可视性。
interface E告诉我们,interface可彼此相互嵌套。但是”所有interface的元素都必须为public“这一条会被严格执行。注意,当你实现某个interface时,你无须实现其中任何嵌套的interface。此外,private interface无法再其所定义的class之外被使用。
内隐类
将某个class的定义置于另一个class定义之中是可行的,这就是所谓的内隐类(inner class)。注意,内隐类和所谓的组合(composition)是截然不同的两回事。
内隐类的典型做法是,外围class有一个函数,可以传回一个reference指向inner class。代码:
package test;
public class Parcel2 {
//内隐类
class Contents{
private int i = 11;
public int value(){return i;}
}
class Destination{
private String label;
Destination(String whereTo){
label = whereTo;
}
String readLabel(){
return label;
}
}
public Destination to(String s){
return new Destination(s);
}
public Contents cont(){
return new Contents();
}
public void ship(String dest){
Contents c = cont();
Destination d = to(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("mianyang");
Parcel2 q = new Parcel2();
//给内隐类分配reference
Parcel2.Contents c = q.cont();
Parcel2.Destination d = q.to("tianjin");
Parcel2.Destination d2 = new Parcel2().new Destination("chengdu");
System.out.println(d.readLabel());
System.out.println(d2.readLabel());
}
}
输出:
mianyang
tianjin
chengdu
内隐类和向上转型
当你开始向上转型至base class,尤其是转型为interface,就能凸显inner class的好处(注意:从某个”实现出interface I“的inner class对象身上产生一个reference指向I,本质上和”向上转型至base class“是一样的),这是因为inner class(也就是I的实现者)可以在接下来的情境中完全不被看见,而且不为任何人所用,这么一来我们就很方便能够”隐藏实现细目“。你所得到的只是”指向base class或interface“的一个reference。
一般的(non-inner)classes无法被声明为private或者protected,只能是public或friendly。
inner class的隐晦使用方式
你可以将inner class置于函数之内或者甚至置于程序范畴(scopes)之内。你这样做可能有两个理由:
1.你想实现某种interface,使你得以产生并返回某个reference。
2.你正在解决某个复杂问题,而你希望在解决方案中设计某个class,又不希望这个class被外界所用。
在任意程序范畴嵌套inner class:
package test;
public class Parcel5 {
//在任意函数范畴(scope)内嵌套inner class、
private void internalTracking(boolean b){
if(b){
class TrackingSlip{
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip(){
return id;
}
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
System.out.println(s);
}
//超出scope,不能够被使用
//!TrackingSlip ts1 = new TrackingSlip("slip");
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
p.internalTracking(true);
}
}
注意:class TrackingSlip被嵌套在if语句中,并不意味这该class会随着条件的成立才被产生,事实上,他会和其他classes一起被编译出来。不过它在它所处的范畴之外就不能被使用。
与外围class的连接关系
截止目前,inner class看起来似乎是一种用于名称隐藏和程序代码组织的体制。这并不能完全让人信服,它还有另外一个作用。当你建立一个inner class时,其对象便拥有了与其制造者–那个外围(enclosing)对象–之间的一种连接关系。所以它可以访问外围对象的所有成员而无需添加任何饰词。此外,inner class也可以访问enclosing class的所有元素。
package test;
interface Selector{
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] obs;
private int next = 0;
public Sequence(int size){
obs = new Object[size];
}
public void add(Object o){
if(nextprivate class SSelector implements Selector{
int i = 0;
public boolean end(){
return i == obs.length;
}
public Object current(){
return obs[i];
}
public void next(){
if(ipublic Selector getSelector(){
return new SSelector();
}
public static void main(String[] args) {
Sequence s = new Sequence(10);
for (int i = 0; i < 10; i++)
s.add(Integer.toString(i));
Selector s1 = s.getSelector();
while(!s1.end()){
System.out.println(s1.current());
s1.next();
}
}
}
sequence只是一个大小固定的Object array,以class形式加以包装。要想取得sequence中的每个对象,有个名为selector的interface,可以让你执行相关操作。由于selector是个interface,所以任何class都可以以自己的形式来实现此一interface,而很多函数都可以接受此一interface作为引数,藉以产生一般化的程序代码。
乍看之下,SSelector只不过是个inner class,但是注意到其函数中都用到了obs,并不是SSelector的一部分,而是外围class的private成员变量。这样就带来了很大的便利。
静态内隐类
如果你不需要inner class对象和enclosing class对象之间的连接关系,你可以将inner class声明为static。一般的inner class(也就是non-static inner class)会自动记录一个reference指向enclosing class的某个对象,而后者也就是此inner class对象的制造者。但是一旦你将inner class声明为static,上述说法不成立。static inner class意味着:
1.产生其对象时,并不需要同时存在一个enclosing class对象。即不需要s.getSelector()中的s
2.你无法在static inner class对象中访问enclosing class对象。
另一方面:non-static inner class内的所有数据和函数都只能位于class的外层(此语只是一种形容,没有实际的技术意义),所以它不能够拥有任何static data,static fields,static inner class。然而static inner class可以拥有这些东西。
package test1;
interface Contents{
public int value();
}
interface Destination{
public String readLabel();
}
public class Parcel10 {
private static class pContents implements Contents{
private int i = 11;
public int value(){return i;}
}
protected static class pDestination implements Destination{
private String label;
private pDestination(String whereTo){
label = whereTo;
}
public String readLabel(){
return label;
}
//static class可以包含static elements
public static void f(){}
static int x = 10;
static class AnotherLevel{
public static void f(){}
static int x = 10;
}
}
public static Destination dest(String s){
return new pDestination(s);
}
public static Contents cont(){
return new pContents();
}
public static void main(String[] args) {
Contents c = cont();
Destination d = dest("tianjin");
}
}
main函数中完全不需要Parcel10对象,它只要采用一般用来选择static成员的语法,调用”传回reference(指向Contents)和reference(指向Destination)“的函数即可。
一般而言,你不能将任何程序代码置于interface内,但static inner class却可以是interface中的一部分,这是因为class既然被声明为static,那也就不会破坏interface的规则–static inner class只不过是被置于interface的命名空间中罢了。
non-static inner class之中对于外围class对象的连接关系,是通过一个特殊的this reference形成。
注意两点:
1.在需要产生一个reference指向outer class对象时,命名方式便是在outer class名称之后紧接一个句号,然后再接this。举例来说,class Sequence.SSelector内的任何函数都可以通过Sequence.this来产生一个reference指向Sequence。产生出来的reference会自动被设定正确性别。这样,编译期即可得知确切型别并加以检查,所以不会有执行期的额外负担。(inner class内部产生outer class对象)
2.要产生inner class对象,就需要先产生一个outer class对象,否则无法得到该对象。因此,除非你已经拥有了一个outer class对象,否则便无法产生其inner class对象。这是因为inner class对象会被暗中连接到某个outer class对象上,后者即该inner class对象的制造者。但是对于static inner class,那就不需要一个reference指向outer class对象。(outer class中产生inner class对象)
继承inner class:
由于inner class的构造函数必须连接到一个reference指向outer class对象身上(应该是编译器自动完成),所以当你继承inner class时,事情变得复杂些。问题出在“指向outer calss对象”的那个神秘reference必须被初始化,但derived class之内不存有可连接的缺省对象。这个问题的答案是,使用专用语法,明确产生该关联性。
代码如下:
package test1;
class WithInner{
public WithInner(){
System.out.println("withinner");
}
//内部类
class Inner{
public Inner(){
System.out.println("inner");
}
}
}
public class InheritInner extends WithInner.Inner{
//不能够被编译
//!public InheritInner() {
//}
InheritInner(WithInner wi){
wi.super();
System.out.println("inheritinner end");
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
结果如下:
withinner
inner
inheritinner end
InheritInner继承的是inner class而非outer class,但是当编译至构造函数时,default构造函数有问题;而且你也不能够只是传入一个reference指向outer object,你还必须在构造函数中使用一下语法:
enclosingClassReference.super();这么一来便能提供所需的reference,而程序也能顺利编译下去。
如果删除:
InheritInner(WithInner wi){
wi.super();
System.out.println("inheritinner end");
}
在new InheritInner(wi);处会报错,显示:The constructor InheritInner(WithInner) is undefined。
为什么需要inner class?
一般来说,inner class会继承某个class或实现某个interface,而且inner class内的程序代码会操作其outer class对象。可以这样说,inner class所提供的其实是针对outer class的某种窗口。
有个问题直指inner class的核心:如果我只需要”指向某个interface“的reference,为什么我不直接让outer class实现该interface呢?答案是,如果这么做能符合你的需求,你确实应该这么做。那么,”由inner class实现interface“和”由outer class实现interface“两者之间的区别究竟在哪儿?答案是后者将无法总是享受到interface的便利性–有时候你得下探实现细目。关于inner class的存在,最信服的理由就是:
每个inner class都能够各自继承某一实现类。因此,inner class不受限于outer class是否已继承自某一实现类。
从另一个角度看,它是多重继承问题的完美解决方案。interface能够解决其中一部分问题,但inner class才能实际允许你继承自多个实现类。如果你拥有的不是interface,而是抽象或实在的class,你就必须使用inner class来解决”多重继承“的问题。
通过inner class,你可以拥有下列几个额外性质:
1.inner class可以拥有多分实体(instance),每个实体都拥有专属的状态信息,而这些信息和outer class对象的信息是相互独立的。
2.在单一outer class内你可以拥有多个inner class,每个都实现相同的interface,或以不同方式继承同一个class。(???)
3.产生inner class对象的时间点,不见得必须和产生outer class对象同时。
4.outer class和inner class之间不存在is-a的关系,inner class就是一个独立的个体。
举个例子,如果Sequence.java不使用inner class,那么你就得宣称”Sequence是个Selector“,而且对特定某个Sequence而言,你只能拥有单一的Selector。另外,如果你希望拥有第二个函数,getRSelector(),令它产生一个“回头走”的Selector,那么你必须采用inner class,才能有如此弹性。
Closures(终结)和callbacks(回调)
所谓closure是一种可被调用的对象,他会记录一些信息,这些信息来自它的产生地所在的程序范畴(scope)。在callback机制底下,某个对象被赋予一些信息,这些信息允许该对象在稍后某个时间点上调用原先的对象。让inner class提供closure功能,是完美解决方案,比起指针来说,不但更具弹性,而且安全许多。代码示例如下:
package test1;
//用inner class实现callbacks
interface Incrementable{
void increment();
}
//简单实现接口
class Callee1 implements Incrementable{
private int i = 0;
public void increment(){
i++;
System.out.println(i);
}
}
class MyIncrement{
public void increment(){
System.out.println("other operation");
}
public static void f(MyIncrement mi){
mi.increment();
}
}
//如果你的class要以其他方式实现increment(),你应该用inner class
class Callee2 extends MyIncrement{
private int i =0;
private void incr(){
i++;
System.out.println(i);
}
//inner class
private class Closure implements Incrementable{
public void increment(){
incr();
}
}
Incrementable getCallbacksReference(){
return new Closure();
}
}
class Caller{
private Incrementable callbackReference;
Caller(Incrementable cbh){
callbackReference = cbh;
}
void go(){
callbackReference.increment();
}
}
public class Callbacks {
public static void main(String[] args) {
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbacksReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
for (int i = 0; i < 10; i++) {
caller1.go();
}
}
}
结果:
other operation
1
2
1
2
3
4
5
6
7
8
9
10
11
12
就程序代码而言,Callee1无疑是较简单的方法,Callee2继承自MyIncrement,后者拥有另一个不同的increment(),这个函数会执行一些动作,而这些动作和Incrementable interface预期应该要做的事毫无关联。当MyIncrement被Callee2继承,你无法重写increment()以为Incrementable所用。所以你得利用inner class另行提供一份独立的实现码。请注意,当你撰写inner class时,你并不会将任何东西加入outer class的接口,或修改该接口。
inner class很单纯地藉由“实现Incrementable”来提供与Callee2之间的关联。这个关联很安全。
Caller于其构造函数中接受Incrementable reference作为引数,而且在某段时间之后,它会使用这个reference来“回头调用”Callee class。
callback的价值在于其弹性–你可以在执行时期动态决定究竟调用哪个函数。