• 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单继承的不足。

  • 相关阅读:
    自定义控件其实很简单5/12
    自定义控件其实很简单1/3
    自定义控件其实很简单1/4
    自定义控件其实很简单1/6
    ListView addfooter 没显示 footer的原因(之一)
    android 版本和 api level 的对应关系
    derby入门
    OA系统与ERP的区别
    Windows下的定时任务
    linux学习笔记——安装问题
  • 原文地址:https://www.cnblogs.com/weststar/p/12416940.html
Copyright © 2020-2023  润新知