接口很多人都会写,也明白它用在什么地方,会产生哪些效果。特别是很多人一提到接口,就脱口而出规范、设计模式,那么对于接口又了解多少呢?本章试着从java中接口的定义和使用出发,谈谈java中接口具有哪些特性。
一、接口
首先,了解接口interface就必须和类class区分开,为什么这么说呢?因为接口和类是两个概念,但接口又和类具有一定的关系。有人不禁会问,怎么说呢?类class是从java.lang.Object类派生而来,但接口interface并不是从某一个特定接口派生而来,两个interface可能没有任何交集,但两个class一定存在交集。接口不是Object的子类,但接口却隐士申明了Object中的所有可继承方法,类似于Object子类继承了它的所有可继承方法,这里申明的方法都是abstract形式,也即只有申明,没有方法体block。
其次,接口的申明形式如下:
[annotation] [modifier] interface identitor [extends interfacelist] {
[public | static | ] [FieldConstant];
[public | abstract | ] [AbstractMethod];
[public | static | ] [MemberType];
}
其中,”[ ]”表示可有可无,而[ a | b ]表示从a或b中选择一个。annotation表示接口的注解;modifier表示接口的修饰符,正如前面所说top interface只能是public, nested interface可是static,在class中还可以是protected或private;identitor表示该接口的名称,一般取名时前缀为”I” + interfaceName 或者 interfaceName + “able”;interfacelist表示继承父接口列表,如Comparable, Serializable,Iterator等;FieldConstant表示接口中的字段;AbstractMethod表示接口中的abstract方法;MemeberType表示接口中的成员,如class,interface。
二、interface种类
java将接口分为两种:normal interface,annotation type。normal这个单词不用解释,既然有两种,那么他们肯定存在差异,也具有一定的共性。下面分别谈谈这两种interface。
三、normal interface
通用接口又可以分为外层接口top interface、内嵌接口nested interface,也即是作为一个单独的接口,还是被嵌入于类中、接口中的接口。它们的接口体body定义相同,唯一的区别在于修饰符的差异。nested interface的修饰符可以是static、public、protected或private(这两者只能是作为class中的内嵌接口才能修饰,而作为top interface中的内嵌接口进行修饰,会编译报错)。
1、FieldConstant常量字段
在接口中定义的字段都被修饰符public static final默认进行了修饰,虽然可以显示的添加public、static或final,但是建议不要添加,因为这是多余的行为,可以添加只是说明字段的修饰符。此处,还需注意一个问题,那就是常量的初始化问题,因为class中的常量可以在定义的时候进行初始化,也可以在构造函数中进程初始化,那么问题是接口中的常量何时进行初始化?因为不能显示的调用接口的初始化,所以常见在定义的时候就必须初始化,否则编译器会抛出初始化Error。其次,接口中的FieldConstant能否被子类继承?可以,如果class实现了interface,那么该接口的实例引用可以引用该接口中的常量字段,如果subinterface继承了该接口,那么subinterface继承了该接口的所有常量字段。
public class Test implements ITest {
public static void main(String... args) {
ITest test = new Test();
//通过实例引用接口ITest的常量字段
System.out.println(test.strTest); //hello world
//通过接口引用常量字段
System.out.println(ITest.strTest); //hello world
//通过子接口引用常量字段
System.out.println(ISubTest1.strTest); // hello world
}
}
public interface ITest {
//字段常量在定义的时候必须进行初始化
String strTest = "hello world";
}
public interface ISubTest1 extends ITest {}
提问:接口中的常量字段能否hide父接口中相同名称的字段?肯定可以!
2、AbstractMethod抽象方法
接口中定义的方法没有方法体,并且修饰符为public abstract默认进行修饰,也就是可以给接口中的方法添加这两种修饰符,但是建议不添加,完全是画蛇添足,理由和常量字段一样。既然是方法,那么就必然涉及到方法的重写、重载以及实现问题。
public interface ITest {
public abstract testMethod();
//可以不用添加public abstract修饰符,编译器默认添加
test();
}
- 重写override
在class中重写,子类通过重写父类的方法来改变通过该方法名进行引用时,所具有的行为。那么,interface的方法重写是什么样的呢?除了方法体,方法重写的签名和返回值都和class中方法的重写要求相同。
public interface ITest {
//定义一个待重写的方法
Object test() throws IOException, ClassCastException;
String test2();
}
public interface ISubTest extends ITest {
//第一种,返回值类型是重写方法的子类型
String test() throws IOException, ClassCastException;
//第二种,异常子句重写
Object test() throws IOException;
String test() throws CLassCastException;
String test() throws EOFException;
//第三种,正常重写
Object test();
String test(Object obj) throws IOException;
}
public class Test implements ISubTest {
//实现子接口的方法,如果子接口没有重写父接口的方法,则必须实现父接口的方法。也即,实现子接口申明的、继承的方法。
//第一种,返回值类型是重写方法的子类型
String test() throws IOException, ClassCastException {
return null;
}
//第二种,异常子句重写
Object test() throws IOException {
return null;
}
String test() throws CLassCastException {
return null;
}
String test() throws EOFException {
return null;
}
//第三种,正常重写
Object test() {
return null;
}
String test(Object obj) throws IOException {
return null;
}
//实现子接口继承的方法
String test2(){
return null;
}
}
- 重载overload
重载相对于重写来说没那么复杂,它所要求的是方法的签名不同,所谓方法签名是指方法的返回值和方法的参数组合,如String test(Object obj)的签名signatrue为(Object)String。但是,这里并不能根据返回值的不同来判定重载,而在JVM的底层中是根据signature来进行实现,只是编译器进行了过滤处理。
public interface ITest {
//原始方法
String test();
//第一种,参数个数不同
String test(String str);
//第二种,参数的类型不同
String test(int a);
//第三种,参数的类型和个数不同
String test(String str1, String str2);
}
- 方法的实现
接口中方法的实现都必须反映到实现类中,同时允许class通过继承类实现接口interface。此外,class实现implements接口,那么该class必须实现接口中的所有abstract方法(后面会说JDK8的新特性)。
public class Test {
//父类中定义了test1, 记得实现时修饰符的全限必须是public
public void test1() {
}
}
class A extends Test implements ITest1, ITest2 {
//实现ITest2接口方法,记得实现时修饰符的全限必须是public
public String test2() {
return null;
}
}
interface ITest1 {
//方法被A的父类Test实现
void test1();
}
interface ITest2 {
//方法被A实现
String test2();
}
提问:接口中字段和方法是否可以具有相同名称?肯定可以!
3、MemberType成员类型
class中可以申明内部类innerClass、内部接口innerInterface和内部枚举innerEnum,接口也同样可以,此时对于innerClass、innerInterface成员类型的默认修饰符为public static,那么在定义时可以显示的指定这两个修饰符,但不建议。但innerEnum是内部枚举类型,此时它的默认修饰符为public static final,也就是说在接口中定义的枚举都是常量枚举。
public interface ITest {
//修饰符默认为public static
class NestedClass {
private int a;
NestedClass (int a) {
this.a = a;
}
}
//默认修饰符为 public static
interface INestedInterface {
}
//默认修饰符为public static final
enum NestedEnum {
RED, YELLOW,GREEN;
}
}
提问:接口中的字段是否可以和成员类型具有相同的名字?可以;接口中的方法能否具有和成员类型相同的名字呢?可以。
4、内嵌接口NestedInterface
接口可以作为top interface的内嵌成员,也可以作为class中的内嵌成员。无论作为class还是interface的内嵌成员,其中接口体的定义内如如前所述,但是作为class的成员时,接口申明的修饰符还可以是protected 、private或者static。
四、接口中的defaut方法
考虑这样一种场景,某个已发布的jar中的某个接口需要集成一个新的功能函数,那么此时需要往接口中添加一个abstract方法,而所有implements这个接口的class都必须实现这个abstract方法,如果采用一个接口适配器来实现程序,也就需要在适配器类中进行实现即可,其他情况是否就很麻烦?JDK8中在接口中引入了default方法,它是一个接口实例方法,可以用来继承。那么default方法和接口的abstract方法有什么相同点和特性?具体体现在重写、重载和实现。
public interface ITest {
//abstract抽象方法
void test();
//default默认方法
default void testDefault() {
System.out.println("hello world");
}
//@complile-error默认方法不能作为abstract方法的具体实现
/*default void test() {
}*/
}
- 重写
方法重写的方式就四种,default方法和普通的abstract方法没有区别,都必须满足下列四种情况之一:1、具有和父接口方法相同的返回值、名称和抛出异常;2、具有和父接口方法相同名称、抛出异常,但返回值类型是子类型;3、具有和父接口方法相同的名称、返回值,但抛出异常兼容(子异常类型);4、具有和父接口方法相同的名称,但返回值和抛出异常兼容。
但default方法不能重写abstract方法,它只能重写父类的default方法,同时default方法不能具有和abstract方法相同的名称;
public interface ITest {
//抽象方法
Object test();
//默认方法
default Object testDefault() {
System.out.println("hello world");
}
}
public interface ISubTest extends ITest {
//@compile-error编译错误
//默认方法不能作为abstract方法的重写方法
/*default Object test() {
System.out.println("test");
}*/
//默认方法可以重写默认方法
default String testDefault() {
System.out.prinln("hello world");
}
}