Java接口
Java接口有点像Java类,但是Java接口只能包含方法签名和字段。 Java接口并非旨在包含方法的实现,而仅包含方法的签名(名称,参数和异常)。但是,可以在Java接口中提供方法的默认实现,以使接口的实现更易于实现接口的类。
我们可以使用Java中的接口来实现多态。在本文的后面,我将回到多态。
Java接口示例
这是一个简单的Java接口示例:
public interface MyInterface { public String hello = "Hello"; public void sayHello(); }
如我们所见,接口是使用Javainterface
关键字声明的。就像类一样,可以将Java接口声明为public或者包范围(无访问修饰符)。
上面的接口示例包含一个变量和一种方法。可以直接从界面访问变量,如下所示:
System.out.println(MyInterface.hello);
如我们所见,从接口访问变量与访问类中的静态变量非常相似。
但是,该方法需要由某个类实现,然后才能访问它。下一节将说明如何完成此操作。
实现接口
在真正使用接口之前,必须在某些Java类中实现该接口。这是一个实现上述" MyInterface"接口的类:
public class MyInterfaceImpl implements MyInterface { public void sayHello() { System.out.println(MyInterface.hello); } }
注意上面的类声明的implements MyInterface
部分。这向Java编译器发出信号,表明MyInterfaceImpl类实现了MyInterface接口。
实现接口的类必须实现接口中声明的所有方法。这些方法必须具有与接口中声明的完全相同的签名(名称+参数)。该类不需要实现(声明)接口的变量。只有方法。
接口实例
一旦Java类实现了Java接口,我们就可以将该类的实例用作该接口的实例。这是一个例子:
MyInterface myInterface = new MyInterfaceImpl(); myInterface.sayHello();
请注意,如何将变量声明为接口类型" MyInterface",而创建的对象是类型" MyInterfaceImpl"。 Java允许这样做,因为类MyInterfaceImpl实现了MyInterface接口。然后,我们可以将MyInterfaceImpl类的实例引用为MyInterface接口的实例。
我们不能单独创建Java接口的实例。我们必须始终创建实现该接口的某些类的实例,并将该实例引用为该接口的实例。
实现多个接口
一个Java类可以实现多个Java接口。在这种情况下,该类必须实现在所有实现的接口中声明的所有方法。这是一个例子:
public class MyInterfaceImpl implements MyInterface, MyOtherInterface { public void sayHello() { System.out.println("Hello"); } public void sayGoodbye() { System.out.println("Goodbye"); } }
该类实现了两个接口,分别称为" MyInterface"和" MyOtherInterface"。我们在关键字关键字" implements"之后列出要实现的接口名称。
如果接口与实现类不在同一包中,则还需要导入接口。就像Java类一样,使用" import"指令导入Java接口。例如:
import com.Hyman.package1.MyInterface; import com.Hyman.package2.MyOtherInterface; public class MyInterfaceImpl implements MyInterface, MyOtherInterface { ... }
这是上述类实现的两个Java接口:
public interface MyInterface { public void sayHello(); }
public interface MyOtherInterface { public void sayGoodbye(); }
如我们所见,每个接口都包含一个方法。这些方法由MyInterfaceImpl类实现。
重叠方法签名
如果一个Java类实现多个Java接口,则存其中某些接口可能包含具有相同签名(名称+参数)的方法的风险。由于Java类只能在具有给定签名的方法上实现一次,因此可能会导致一些问题。
Java规范没有对此问题提供任何解决方案。由我们决定在这种情况下该怎么做。
哪些Java类型可以实现接口?
以下Java类型可以实现接口:
- Java类
- Java抽象类
- Java嵌套类
- Java枚举
- Java动态代理
接口变量
Java接口可以包含变量和常量。但是,通常没有必要在接口中放置变量。在某些情况下,在接口中定义常量是有意义的。尤其是如果那些常量要由实现接口的类使用,例如在计算中,或者作为界面中某些方法的参数。但是,我的建议是,如果可以,请避免在Java接口中放置变量。
接口中的所有变量都是公共的,即使我们在变量声明中省略了" public"关键字。
接口方法
Java接口可以包含一个或者多个方法声明。如前所述,接口无法为这些方法指定任何实现。由实现接口的类来指定实现。
接口中的所有方法都是公共的,即使我们在方法声明中省略了" public"关键字。
接口默认方法
在Java 8之前,Java接口不能包含方法的实现,而只能包含方法签名。但是,当API需要向其接口之一添加方法时,这会导致一些问题。如果API仅将方法添加到所需的接口,则实现该接口的所有类都必须实现该新方法。如果所有实现类都位于API内,则可以。但是,如果某些实现类位于API的客户端代码(使用API的代码)中,则该代码将中断。
让我用一个例子来说明。查看此界面,并想象它是例如许多应用程序在内部使用的开源API:
public interface ResourceLoader { Resource load(String resourcePath); }
现在,假设一个项目使用此API并实现了如下所示的ResourceLoader
接口:
public class FileLoader implements ResourceLoader { public Resource load(String resourcePath) { // in here is the implementation + // a return statement. } }
如果API的开发人员想向" ResourceLoader"接口添加另一个方法,则当该项目升级到API的新版本时," FileLoader"类将被破坏。
为了缓解此Java接口演变问题,Java 8已将接口默认方法的概念添加到Java接口中。接口默认方法可以包含该方法的默认实现。然后,实现该接口但不包含默认接口实现的类将自动获取默认方法实现。
我们可以使用default
关键字将接口中的方法标记为默认方法。这是向ResourceLoader
接口添加默认方法的示例:
public interface ResourceLoader { Resource load(String resourcePath); default Resource load(Path resourcePath) { // provide default implementation to load // resource from a Path and return the content // in a Resource object. } }
本示例添加了默认方法load(Path)
。该示例省略了实际的实现(在方法主体内部),因为这并不是很有趣。重要的是如何声明接口默认方法。
类可以通过显式地实现默认方法来覆盖默认方法的实现,就像在实现Java接口时通常所做的那样。类中的任何实现都优先于接口默认方法实现。
接口静态方法
Java接口可以具有静态方法。 Java接口中的静态方法必须具有实现。这是Java接口中静态方法的示例:
public interface MyInterface { public static void print(String text){ System.out.print(text); } }
在接口中调用静态方法的外观和工作方式类似于在类中调用静态方法。这是从上面的MyInterface接口调用静态print()方法的示例:
MyInterface.print("Hello static method!");
当我们要使用某些实用程序方法时,接口中的静态方法会很有用,这些方法自然适合与相同职责相关的接口。例如,"车辆"接口可以具有" printVehicle(车辆v)"静态方法。
接口和继承
一个Java接口有可能从另一个Java接口继承,就像类可以从其他类继承一样。我们可以使用extends
关键字指定继承。这是一个简单的接口继承示例:
public interface MySuperInterface { public void saiHello(); }
public interface MySubInterface extends MySuperInterface { public void sayGoodbye(); }
接口MySubInterface扩展了接口MySuperInterface。这意味着MySubInterface继承了MySuperInterface的所有字段和方法。这意味着,如果一个类实现了MySubInterface,则该类必须实现在MySubInterface和MySuperInterface中定义的所有方法。
如果以某种方式在设计中需要,可以在子接口中定义与超级接口中定义的方法具有相同签名(名称+参数)的方法。
与类不同,接口实际上可以从多个超级接口继承。通过列出所有要继承的接口的名称(以逗号分隔)来指定该名称。实现从多个接口继承的接口的类必须实现该接口及其超级接口的所有方法。
这是从多个接口继承的Java接口的示例:
public interface MySubInterface extends SuperInterface1, SuperInterface2 { public void sayItAll(); }
与实现多个接口时一样,当多个超级接口具有具有相同签名(名称+参数)的方法时,也没有规则来处理情况。
继承和默认方法
接口默认方法给接口继承规则增加了一点复杂性。尽管类即使接口包含具有相同签名的方法通常也可以实现多个接口,但是如果这些方法中的一个或者多个是默认方法,则不可能实现。换句话说,如果两个接口包含相同的方法签名(名称+参数),并且其中一个接口将该方法声明为默认方法,则类无法自动实现两个接口。
如果一个接口扩展(继承自)多个接口,并且这些接口中的一个或者多个包含具有相同签名的方法,并且其中一个超接口将重叠方法声明为默认方法,则情况相同。
在以上两种情况下,Java编译器都要求实现接口的类显式实现导致问题的方法。这样,毫无疑问,该类将具有哪种实现。该类中的实现优先于任何默认实现。
接口与多态
Java接口是一种实现多态的方法。多态是一个需要一些实践和思想才能掌握的概念。基本上,多态性意味着类(对象)的实例可以被当作不同类型使用。在这里,类型是指类或者接口。
上面的类是模型的所有部分,代表具有字段和方法的不同类型的车辆和驾驶员。这些类负责从现实生活中对这些实体进行建模。
现在想象我们需要能够将这些对象存储在数据库中,并将它们序列化为XML,JSON或者其他格式。我们希望对每个操作使用单一方法来实现,可在每个"汽车","卡车"或者"车辆"对象上使用。一个store()方法,一个serializeToXML()方法和一个serializeToJSON()方法。
请暂时忘记,将这种函数作为方法直接在对象上实现可能会导致混乱的类层次结构。试想一下,这就是我们想要实现操作的方式。
我们将这三种方法放在上图中的什么位置,以便在所有类上都可以访问它们?
解决此问题的一种方法是为" Vehicle"和" Driver"类创建一个通用的超类,该类具有存储和序列化方法。但是,这将导致概念混乱。类层次结构将不再为车辆和驾驶员建模,而是与应用程序中使用的存储和序列化机制相关联。
更好的解决方案是使用存储和序列化方法创建一些接口,并让类实现这些接口。以下是此类接口的示例:
public interface Storable { public void store(); }
public interface Serializable { public void serializeToXML(Writer writer); public void serializeToJSON(Writer writer); }
当每个类实现这两个接口及其方法时,可以通过将对象强制转换为接口类型的实例来访问这些接口的方法。我们无需确切知道给定对象属于什么类,只要知道它实现了什么接口即可。这是一个例子:
Car car = new Car(); Storable storable = (Storable) car; storable.store(); Serializable serializable = (Serializable) car; serializable.serializeToXML (new FileWriter("car.xml")); serializable.serializeToJSON(new FileWriter("car.json"));
如我们现在可能想象的那样,接口提供了比继承更干净的方法来在类中实现交叉函数。
通用接口
通用Java接口是可以输入的接口,这意味着它可以专门用于与特定类型(例如接口或者类)一起使用。让我首先创建一个包含单个方法的简单Java接口:
public interface MyProducer() { public Object produce(); }
该接口表示一个接口,其中包含一个称为" produce()"的方法,该方法可以产生一个对象。由于Produce()的返回值是Object,因此它可以返回任何Java对象。
这是一个实现MyProducer接口的类:
public class CarProducer implements MyProducer{ public Object produce() { return new Car(); } }
上面的类CarProducer实现了MyProducer接口。每次调用时,produce()方法的实现都会返回一个新的Car对象。这是使用CarProducer
类的样子:
MyProducer carProducer = new CarProducer(); Car car = (Car) carProducer.produce();
注意从carProducer.produce()方法调用返回的对象必须强制转换为Car实例,因为producer方法的返回类型为Object。使用Java泛型,我们可以键入MyProducer接口,以便指定使用它时生成的对象的类型。首先是MyProducer接口的通用版本:
public interface MyProducer <T>{ public T produce(); }
现在,当我在CarProducer类中实现MyProducer接口时,我也必须包括通用类型声明,如下所示:
public class CarProducer<T> implements MyProducer<T>{ @Override public T produce() { return (T) new Car(); } }
现在,当创建CarProducer
时,我可以指定其通用接口类型,如下所示:
MyProducer<Car> myCarProducer = new CarProducer<Car>(); Car produce = myCarProducer.produce();
如我们所见,由于CarProducer实例的泛型类型设置为Car,因此不再强制转换从produce()方法返回的对象,因为MyProducer中的原始方法声明接口说明,此方法返回的类型与泛型使用时指定的类型相同。
但是现在实际上可以为CarProducer实例指定另一种泛型类型,而不是它的从produce()方法实现中实际返回的类型。如果向上滚动,则无论创建时为它指定的通用类型如何,CarProducer.produce()实现都会返回Car对象。因此,以下声明是可能的,但在执行时将返回ClassCastException:
MyProducer<String> myStringProducer = new CarProducer<String>(); String produce1 = myStringProducer.produce();
相反,在实现接口时,我们可以在CarProducer类中锁定MyProducer接口的泛型类型。这是在实现通用接口时指定通用类型的示例:
public class CarProducer implements MyProducer<Car>{ @Override public Car produce() { return new Car(); } }
现在,我们无法在使用CarProducer时指定其通用类型。它已经被键入到Car
。这是使用" CarProducer"的样子:
MyProducer<Car> myCarProducer = new CarProducer(); Car produce = myCarProducer.produce();
如我们所见,由于CarProducer实现将其声明为Car实例,因此仍无需强制转换由produce()返回的对象。
我的Java泛型教程中更详细地介绍了Java泛型。
函数接口
从Java 8开始,引入了一个新的概念,称为函数接口。简而言之,函数接口是具有单个未实现方法(非默认,非静态方法)的接口。我已经在Java函数接口教程中解释了函数接口,该教程是Java函数编程教程的一部分。 。
函数接口通常旨在由Java Lambda表达式实现。