ITEEDU

Java Gossip: 接口(interface)型态

表面上看来,接口有点像是完全没有任何方法被实作的抽象类别,但实际上两者在语义与应用上是有差别的。「继承某抽象类别的类别必定是该类别的一个子类」,由于同属一个类型,只要父类别中也有定义同名方法,您就可以透过父类别型态来操作子类实例中被重新定义的方法,也就是透过父类别型态进行多型操作,但「实作某接口的类别并不被归属于哪一类」,一个对象上可以实作多个接口。

考虑您有一个方法doRequest(),您事先并无法知道什么型态的对象会被传进来,或者是这个方法可以接受任何类型的对象,您想要操作对象上的某个特 定方法,例如doSomething()方法,问题是传进来的对象是任意的,除非您定义一个抽象类别并宣告doSomething()抽象方法,然后让所 有的类别都继承这个抽象类别,否则的话您的doRequest()方法似乎无法实作出来,实际上这么作也没有价值。

接口的目的在定义一组可操作的方法,实作某接口的类别必须实作该接口所定义的所有方法,只要对象有实作某个接口,就可以透过该接口来操作对象上对应的方法,无论该对象实际上属于哪一个类别,像上一段所述及的问题,就要靠要接口来解决。

接口的宣告是使用"interface"关键词,宣告方式如下:

interface 接口名称 {
	传回型态 方法(参数列);
	传回型态 方法(参数列);
	// ....
}

一 个宣告界面的例子如下:

IRequest.java
public interface IRequest {
	public void execute();
}

接口的权限都是"public",所以即使不指定public仍是预设为public,例如下例与上例是相同的:

IRequest.java
interface IRequest {
	void execute();
} 

接口预设都是abstract,有无加abstract都一样,以下的宣告作用也是相同:

IRequest.java
public abstract interface IRequest {
	public abstract void execute();
} 

当定义类别时,可以使用"implements"关键词来一并指定要实作哪个接口,接口中所有定义的方法都要实作,例如:

HelloRequest.java
public class HelloRequest implements IRequest {
	private String name;
	public HelloRequest(String name) {
		this.name = name;
	}
	public void execute() {
		System.out.printf("Hello! %s!%n", name);
	}
}
WelcomeRequest.java
public class WelcomeRequest implements IRequest {
	private String place;
	public WelcomeRequest(String place) {
		this.place = place;
	}
	public void execute() {
		System.out.printf("Welcome to %s!%n", place);
	}
}

由于接口中的方法预设都是public,所以实作接口的类别中,方法必须宣告为public,否则无法通过编译。

来写一个测试程序:

Test.java
public class Test {
	public static void main(String[] args) {
		for(int i = 0; i < 10; i++) {
			int n = (int) (Math.random() * 10) % 2;
			switch (n) {
				case 0:
				doRequest(
				new HelloRequest("caterpillar"));
				break;
				case 1:
				doRequest(new WelcomeRequest("PmWiki"));
			}
		}
	}
	public static void doRequest(IRequest request) {
		request.execute();
	}
}

在这个程序中,即使doRequest()并不知道传入的对象是哪一种类别的实例,但它只要知道这个对象的操作接口就可以正确的执行请求,这是界面实作的一个实际应用,也是很常见到的一种应用。

在C++中可以使用多重继承,但在Java中只能单一继承,也就是一次只能扩充一个类别,Java使用interface来达到某些多重继承的目的,您可 以一次实作多个接口,就像是同时继承多个抽象类别(实际上这是C++中多重继承的一个实际运用方式),实作多个接口的方式如下:

public class 类别名称 implements 接口1, 接口2, 接口3 { 
	// 界面实作 
}

当您实作多个接口时,记得您必须实作每一个接口中所定义的方法,由于您实作了多个接口,所以要操作对象时,必要时您必须作「接口转换」,如此程序才能知道如何正确的操作对象,例如假设someObject实作了ISomeInterface1与ISomeInterface2两个接口,则我们可以如下对对象进行接口转换与操作:

ISomeInterface1 obj1 = (ISomeInterface1) someObject;
obj1.doSomeMethodOfISomeInterface1();


ISomeInterface2 obj2 = (ISomeInterface2) someObject;
obj2.doSomeMethodOfISomeInterface2();

  当 抽象类别 中的所有方法都是抽象方法时,它的作用就与接口有些类似(像在C++中,并没有区分抽象类别与接口),但记得在Java中只允许单一继承,所以您不能同时 继承多个抽象方法。

事实上在Java里,抽象类别中并不会全是抽象方法,这么使用并不适当,在Java中区分抽象类别与接口,其在语义上是有所不同的,例如抽象类别中允许您 先实作某些方法,而保留一些抽象方法不实作,其应用场合之一是像 Template Method 模式 中介绍的接口定义中的方法则是完全不实作,它只定义方法名称,接口常用于规范统一的操作界面,其应用场合之一是像 Command 模式 中介绍的,接口定义一组协议,所有实作它的类别都必须遵守的协议,接口可以保证实作它的类别一定会实作所定义的方法。

接口也可以进行继承的动作,同样也是使用"extends"关键词,例如:

public interface 名称 extends 接口1, 接口2 { 
	// ... 
} 

不同于类别的是,接口可以同时继承多个接口,这也可以达到类似C++中多重继承的功能,而实作子接口的类别必须将所有在父接口和子接口中所定义的方法实做出来。

  • 多型(Polymorphism)操作的应用太多了,从 设 计模式 开始学习会是个不错的选择。
  • 在宣告接口的名称时,一个常见的惯例是在名称前加上 'I' ,这明显表示这是一个接口。
  • 我喜欢用接口转换,而不是用转型(Cast),为对象转换一个操作接口,而不是将对象转型。