5.6 接口


抽象类是从多个类中抽象出来的模板,如果将这种抽象更彻底,则可以提炼出一种更加特殊的"抽象类"——接口(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单继承的不足。

posted @ 2020-03-05 21:09  小新和风间  阅读(136)  评论(0编辑  收藏  举报