抽象类是从多个类中抽象出来的模板,如果将这种抽象更彻底,则可以提炼出一种更加特殊的"抽象类"——接口(interface)。Java 9对接口进行改造,允许在接口中定义默认方法和类方法,接口方法和类方法都可以提供方法实现。Java 9为接口增加了一种私有方法,私有方法可提供方法实现。
一、接口的概念
接口定义了一种规范,接口定义了某一批类所需要遵循的规范,接口不关心这些类内部状态数据,也不关心这些类里方法的是实现细节,它只规定了这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。
二、Java 9定义接口
定义接口使用interface关键定义接口,接口定义的基本语法:
[修饰符] interface 接口名 extends 父接口1,父接口2...
{
//常量定义(接口中的成员变量默认使用static final)
//多个抽象方法定义
//零到多个内部类、接口、枚举
//零到多个私有方法、默认方法或类方法定义...
}
说明:
1、接口修饰符可以是public或省略,如果省略public修饰符访问控制符,则默认包访问权限。
2、修饰符:public。接口是彻底抽象,所以不能用final;接口已经够抽象了,因此不需要abstract修饰。
3、接口名等同于类名。一般推荐使用形容词。田间able就是形容词。
4、一个接口可以有多个直接父类,但接口只能继承接口,不能继承类。
5、Java 8以上版本才允许在接口中定义默认方法、类方法。
6、因为接口里不能包含构造器,所以也就不能包含初始化块(初始化块会还原到构造器中)。接口里可以包含成员变量(只能是静态常量)、方法(抽象实例方法、类方法、默认方法、私有方法)、内部类(包括接口、枚举)。
2.1 定义接口中成员变量
对于接口的静态常量,他们与接口相关,系统为自动为这些成员变量增加static和final修饰符。也就是说,在接口中定义成员变量时,不管是否使用public static final 修饰符,接口中的成员变量总是使用这三个修饰符来修饰。因为接口中没有构造器和初始化块,因此接口里定义的成员变量只能在定义时由程序员显示指定默认值。**
接口中定义的成员变量如下两行代码完全结果一样:
int MAX_SIZE=20;
public static final MAX_SIZE=20;
2.2 接口无构造器和初始化块
我们已经知道抽象类获得了抽象方法,但是失去了创建子类的能力。而接口是更加抽象的类,也就不能创建实例,因此接口中无构造器。接口中的成员变量,在定义时就必须指定默认值,所以初始化块存在也就没有任何意义。
2.3定义接口中方法
接口中定义的方法只能是抽象方法、类方法、默认方法、私有方法(类方法或实例方法),因此如果不是定义默认方法、类方或私有方法,系统将自动为普通方法增加abstract修饰符;定义接口里的普通方法时不管是否使用public abstract修饰符,接口里的普通方法总是使用public abstract修饰。接口里的普通方法不能有方法体;但类方法、默认方法、私有方法都必须有方法体必须有方法实现。
1、抽象方法
接口是更加抽象的类,因此接口中一定存在着抽象方法。在接口中定义的方法如果没有指定修饰符abstract,且不含方法体,则该方法是抽象方法系统会默认在前面加上public abstract修饰符。下面两行代码的效果是一样的:
public interface A
{
void test();//这是一个抽象方法
//public abstract void main();//与上面的语句等价
}
2、类方法
接口中存在着类方法。类方法需要使用static修饰符,如没有在定义方法时加上public,系统会默认为static方法加上public修饰符。
public interface StaticMed
{
static void test()
{
System.out.println("这是一个类方法");
}
}
E:Java第六章 面向对象(下)6.6 接口>javap -c StaticMed
Compiled from "StaticMed.java"
public interface StaticMed {
public static void test();
Code:
0: getstatic #1 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #2 // String 这是一个类方法
5: invokevirtual #3 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
3、默认方法
接口中定义默认方法,必须使用default修饰,因为为了告诉编译这不是一个抽象方法。默认方法总是使用public修饰。可以理解为类中的实例方法。
interface DefaultMed
{
default void test()
{
System.out.println("这是一个默认方法");
}
}
4、私有方法
Java 9 增加了私有方法。Java 8允许在接口中定义默认方法和类方法,当两个默认方法(或类方法)中包含一段相同的实现逻辑时,程序将考虑将这段实现逻辑抽取成工具方法,而且该工具方法不希望被接口的实现类调用,应该隐藏起来,故必须使用private修饰。
例如下面程序中的默认方法fly()和run()都包含了预备阶段的实现逻辑,这与程序要求减少代码重复不符。
interface PrivateMed
{
default void fly()
{
System.out.println("预备阶段阶段:3,2,1!go");
System.out.println("起飞...");
}
default void run()
{
System.out.println("预备阶段阶段:3,2,1!go");
System.out.println("飞迸前进...");
}
}
我们现在将这段重复逻辑抽取为工具方法,只提供给接口内部的默认方法(类方法不能调用—— 错误: 无法从静态上下文中引用非静态 方法 ready())使用。
public interface PrivateMed
{
private void ready()
{
System.out.println("预备阶段阶段:3,2,1!go");
}
default void fly()
{
ready();
System.out.println("起飞...");
}
default void run()
{
ready();
System.out.println("飞迸前进...");
}
}
2.4 定义接口中的内部类、内部接口、内部枚举
接口里的内部类、内部接口、内部枚举都会默认使用public static两个修饰符,不管是否指定这两个修饰符,系统将会自动使用public static对它修饰。
总结图:
定义接口举例:
package lee;
public interface Output
{
//定义接口中的成员变量只能是常量
int MAX_CACHE_LINE=50;//等价于public static final int MAX_SIZE=50;
//普通方法
//接口里定义普通方法只能是public抽象方法
void out();
//等价于public abstract void out();
void getData(String msg);
//默认方法 需要使用default修饰
default void print(String...msgs)
{
for(var msg:msgs)
{
System.out.println(msg);
}
}
default void test()
{
System.out.println("默认的test()方法");
}
//类方法 需要使用static修饰
static String staticTest()
{
return "接口中的类方法";
}
//定义私有方法 需要private修饰
private void foo()
{
System.out.println("foo私有方法");
}
//私有静态方法
private static void bar()
{
System.out.println("bar私有静态方法");
}
}
该Output接口里包含一个成员变量MAX_CACHE_LINE。还定义了两个普通方法:表示获取数据的getData()方法和表示输出的out()方法。这就定了了Output接口的规范:某个类只能获取数据,并可以将数据输出,它就是一个输出设备,至于设备的实现细节,暂不关心。
接口里的成员默认使用public static final修饰,因此即使在不同包下,也可以通过接口来访问接口里的成员变量。
package yeeku;
public class OutputFieldTest
{
public static void main(String[] args)
{
System.out.println(lee.Output.MAX_CACHE_LINE);
//下面语句将出错,因为是final修饰
//lee.Output.MAX_CACHE_LINE=20;
//使用接口调用方法
System.out.println(lee.Output.staticTest());
}
}
三、接口的继承
接口完全支持多继承,即一个接口可以有多个直接父接口。和类继承相似,子接口扩展某个父接口,将会获得接口里定义的所有抽象方法、常量。
一个接口继承多个父接口时,多个父接口在extends关键字之后,多个父接口之间以英文逗号(,)隔开,下面定义了三个接口,第三个接口继承了前面两个接口
interface InterfaceA
{
int PROP_A=5;//默认使用public static final修饰
void testA();//默认使用public abstract修饰
}
interface InterfaceB
{
int PROP_B=6;//默认使用public static final修饰
void testB();//默认使用public abstract修饰
}
interface InterfaceC extends InterfaceA,InterfaceB
{
int PROP_C=7;
void testC();
}
public class InterfaceExtendsTest
{
public static void main(String[] args)
{
System.out.println(InterfaceC.PROP_A);//输出5
System.out.println(InterfaceC.PROP_B);//输出6
System.out.println(InterfaceC.PROP_C);//输出7
}
}
上面程序中,接口InterfaceC接口继承了InterfaceA和InterfaceB,所以InterfaceC中获得了它们的常量。
四、使用接口
接口不能创建实例,当接口可用于声明引用类型变量。当使用接口来声明引用类型的变量时,这个引用类型变量必须引用到其实现类的对象。
接口的主要用途:
1、定义变量,也可以进行强制类型转换。
2、调用接口中定义的常量
2、被其他类实现
一个类可以实现一个或多个接口,继承使用extends关键字,实现则使用implements关键字。因为一个类可以实现多接口,这也是Java为单继承灵活性不足所做的补充。类实现的语法格式:
[修饰符] class 类名 extends 父类 implements 接口1,接口2...
{
//类体部分
}
 ; ;实现接口和继承父类相似,一样可以获得接口里定义的常量、方法(包括抽象方法、默认方法)。
 ; ;一个类实现某个接口时,这个类必须实现接口中所定义的全部抽象方法;否则,该类将保留从父类那里继承的抽象方法,该类也就必须定义为抽象类。
import lee.Output;
//定义一个Product接口
interface Product
{
int getProduceTime();
}
//让printer类实现Output和Product接口
public class Printer implements Output,Product
{
private String[] printData=new String[MAX_CACHE_LINE];
//用于记录当前需打印的作业数
private int dataNum=0;
@ Override
public void out()
{
//只要还有作业,就继续打印
while(dataNum>0)
{
System.out.println("打印机打印:"+printData[0]);
//把作业队列整体前移一位,并将身下的作业数减1
System.arraycopy(printData,1,printData,0,--dataNum);
}
}
@Override
public void getData(String msg)
{
if (dataNum>MAX_CACHE_LINE)
{
System.out.println("输出队列已满,添加失败");
}
else
{
//把打印的数据添加到队列里,已保存的数据数量+1
printData[dataNum++]=msg;
}
}
@Override
public int getProduceTime()
{
return 45;
}
public static void main(String[] args)//重写方法访问权限需要更大
{
//创建一个printer对象,当成Output使用
Output o=new Printer();//向上转换
o.getData("轻量级Java EE");
o.getData("疯狂Java讲义");
o.out();
//调用Output接口中的默认方法
o.print("hello","world","firend","!");
o.test();
//创建一个Printer对象,当成product使用
Product p=new Printer();
System.out.println(p.getProduceTime());
}
}
从上面的程序可以看出,Printer类实现了Output接口和Product接口,因此Printer对象即可以直接赋给Output变量,也可以直接赋给Product变量。这就是Java提供的模拟多继承。
注:实现接口方法时,需要重写父接口中所有的父类接口中的所有抽象方法,必须使用public修饰符,因为默认接口中的抽象方法使用public abstract修饰。重写方法满足两同两小一大:方法名相同,形参类列表相同,返回值类型相同或更小,抛出的异常类型相同或更小,一大访问权限相同或更大。
五、接口和抽象类
接口和抽象类很像,具有如下特征:
1、接口和抽象了都不可以被实例化,它们位于继承树的顶端,用于被其他实现和继承
2、抽象类和接口都可以包含抽象方法,继承抽象类的普通子类和实现接口都必须实现这些抽象方法。
二者的设计目的不同:
1、接口作为系统与外界交互的窗口,接口体现的是一种规范。对于接口实现着而言,接口规定了实现者必须向外提供哪些服务(以方法的形式实现);对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务。当在一个程序中使用接口时,接口是多个模块之间耦合标准;当在多个程序中使用接口时,接口是多个程序之间通信标准。
2、抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。抽象类作为多个子类的抽象父类,可以当作系统实现的中间产品,这个产品已经实现了系统部分功能,但这个产品依然不能当成最终产品,必须有进一步完善。
接口和抽象类的用法上区别:
★接口里只能包含抽象方法、静态方法、默认方法、私有方法,不能为普通方法提供方法实现;抽象类可以包含普通方法。
★接口里只能定义静态常量,不能定义普通成员;抽象类里既可以包含实例成员变量,也可以包含静态常量。
★接口里不包含构造器;抽象类可以包含构造器,但抽象类里的构造器并不是用于创建对象,而是让子类调用这些构造器来完成属于抽象类的初始化操作。
★接口里不能包含初始化块;但抽象类完全可以。
★一个类只能有一个直接父类,包括抽象类;但一个类可以实现对多个接口,通过接口弥补Java单继承的不足。