1.接口的基本概念
在 Java 中,被关键字 interface 修饰的“类”是接口。
接口的定义如下:
interface 接口名称{
全局常量;
抽象方法;
}
2. 接口的使用
接口的使用分两步:创建接口
实现接口
2.1 创建接口
//源码
public interface Move {
void move();
}
2.2 实现接口
//源码
public class Animal implements Move {
@Override
public void move() {
System.out.println("Animal move");
}
}
public class Human implements Move{
@Override
public void move() {
System.out.println("Human move");
}
}
public class Car implements Move {
@Override
public void move() {
System.out.println("Car move");
}
}
public class MoveTest {
public static void main(String[] args) {
Move [] move = {new Animal(), new Human(), new Car()};
for (Move m : move) {
m.move();
}
}
}
//执行结果
Animal move
Human move
Car move
3. 接口存在的意义
接口存在的意义主要有两点:禁止直接为其实例化对象
打破单继承局限(实现伪多重继承)
3.1 禁止直接为其实例化对象
在这个点上,相比于抽象类,Java 对接口的限制更加严格了,因为接口连构造方法都没有,所以,根本不可能为其实例化对象。
//源码
public interface Move {
//此时编译器会提示 Interfaces cannot have constructors 错误信息
public Move(){}
void move();
}
3.2 打破单继承局限(实现伪多重继承)
由于 Java 中允许多实现,所以,一个类在实现了多个接口之后,就可以上转型为多个接口,即打破单继承局限。
//源码
public interface Fly {
void fly();
}
public interface Fight {
void fight();
}
public class SuperMan implements Fly,Fight{
@Override
public void fight() {
System.out.println("SuperMan fight");
}
@Override
public void fly() {
System.out.println("SuperMan fly");
}
}
public class MultiImplementsTest {
public static void main(String[] args) {
SuperMan sm = new SuperMan();
fly(sm);
fight(sm);
}
private static void fly(Fly f){
f.fly();
}
private static void fight(Fight f){
f.fight();
}
}
//执行结果
SuperMan fly
SuperMan fight
由于 SuperMan 实现了 Fly 和 Fight 接口,所以 SuperMan 可以上转型为 Fly 接口,也可以上转型为 Fight 接口,即“多重继承?”。
4. 接口中易混淆的概念
4.1 接口中有构造方法吗?
接口中没有构造方法。详情请参考《3.1 禁止直接为其实例化对象》。
4.2 接口可以继承普通类吗?接口可以继承抽象类吗?
接口不可以继承普通类。
//源码
public class Animal {
}
//此时开发工具会提示 The type Animal cannot be a superinterface of Fly; a superinterface must be an interface 错误信息
public interface Fly extends Animal{
void fly();
}
接口不可以继承抽象类。
//源码
public abstract class Airplane {
}
//此时开发工具会提示 The type Airplane cannot be a superinterface of Fly; a superinterface must be an interface 错误信息
public interface Fly extends Airplane{
void fly();
}
其实这很好理解,因为接口中只能定义静态常量和抽象方法,无论普通类还是抽象类都没有如此严格的要求,因此接口既不能继承普通类也不能继承抽象类。
4.3 当实现类的父类中的方法和接口中的方法一样时,会出现什么情况?
4.3.1 正常情况下的类继承
在 Java 中,一个类的子类将继承父类的所有用 public 和 protected 关键字修饰的方法和属性。
//源码
public class Animal {
public void eat(){
System.out.println("Animal eat");
}
}
public class Tiger extends Animal{
}
public class TigerTest {
public static void main(String[] args) {
Tiger tiger = new Tiger();
tiger.eat();
}
}
//执行结果
Animal eat
4.3.2 正常情况下的接口实现
在 Java 中,一个类实现了某个接口,就要实现该接口中所有的方法。
//源码
public interface Fly {
void fly();
}
public class Eagle implements Fly {
@Override
public void fly() {
System.out.println("Eagle fly");
}
}
public class EagleTest {
public static void main(String[] args) {
Eagle eagle = new Eagle();
eagle.fly();
}
}
//执行结果
Eagle fly
4.3.3 实现类的父类中的方法和接口中的方法不一样
//源码
public class Animal {
public void eat(){
System.out.println("Animal eat");
}
}
public interface Fly {
void fly();
}
public class Vulture extends Animal implements Fly {
@Override
public void fly() {
System.out.println("Vulture fly");
}
}
public class VultureTest {
public static void main(String[] args) {
Vulture vulture = new Vulture();
vulture.eat();
vulture.fly();
}
}
//执行结果
Animal eat
Vulture fly
4.3.4 实现类的父类中的方法和接口中的方法一样
其实通常情况下这种事是不会发生的,除非某个程序员想自找麻烦。但如果是为了搞清接口的概念,那这很值得一试。正如《Thiking in Java》的作者 Bruce Eckel 所说:I generally find that once you know about a feature, you often discover places where it is useful.
方法名确定之后,就剩下方法的参数列表和返回值类型,接下来,对各种情况进行分析:参数相同,返回值相同
参数相同,返回值不同
参数不同,返回值相同
参数不同,返回值不同
Ps:此处讨论的前提是方法名称一样。
4.3.4.1 参数相同,返回值相同
//源码
public class Animal {
public void hunt(){
System.out.println("Animal hunt");
}
}
public interface Hunt {
void hunt();
}
public class Eagle extends Animal implements Hunt{
}
public class EagleTest {
public static void main(String[] args) {
Eagle eagle = new Eagle();
eagle.hunt();
}
}
//执行结果
Animal hunt
结论:当实现类的父类中的方法的签名和返回值跟接口中的方法的签名和返回值完全一样时,此时子类可以不同显式实现接口中的方法。如果此时,实现类没有显式实现接口中的,那么将调用父类中的方法。
4.3.4.2 参数相同,返回值不同
//源码
public class Animal {
public void hunt(){
System.out.println("Animal hunt");
}
}
public interface Hunt {
String hunt();
}
//此时,如果不实现接口中的方法,开发工具会提示 The return types are incompatible for the inherited methods Hunt.hunt(), Animal.hunt() 错误信息
public class Eagle extends Animal implements Hunt{
//此时开发工具会提示 The return type is incompatible with Animal.hunt() 错误信息
public String hunt(){
return "";
}
}
结论:当实现类的父类中的方法的签名跟接口中的方法的签名一样,而返回值不一样时,实现类定义不成功,即根本不存在这样的(实现)类。
4.3.4.3 参数不同,返回值相同
//源码
public class Animal {
public void hunt(){
System.out.println("Animal hunt");
}
}
public interface Hunt {
void hunt(String place);
}
public class Eagle extends Animal implements Hunt{
@Override
public void hunt(String place) {
System.out.println("Eagles hunt on the " + place);
}
}
public class EagleTest {
public static void main(String[] args) {
Eagle eagle = new Eagle();
eagle.hunt();
eagle.hunt("grasslands");
}
}
//执行结果
Animal hunt
Eagles hunt on the grasslands
结论:当实现类的父类中的方法的参数跟接口中的方法的参数不一样,而返回值一样时,需要在实现类中重新实现接口中的方法。
4.3.4.4 参数不同,返回值不同
//源码
public class Animal {
public void hunt(){
System.out.println("Animal hunt");
}
}
public interface Hunt {
String hunt(String place);
}
public class Eagle extends Animal implements Hunt{
@Override
public String hunt(String place) {
System.out.println("Eagles hunt on the " + place);
return place;
}
}
public class EagleTest {
public static void main(String[] args) {
Eagle eagle = new Eagle();
eagle.hunt();
System.out.println(eagle.hunt("grasslands"));
}
}
//执行结果
Animal hunt
Eagles hunt on the grasslands
grasslands
结论:当实现类的父类中的方法的参数和返回值跟接口中的方法的参数和返回值均不一样时,需要在实现类中重新实现接口中的方法。
之所以上面的概念没有搞清是因为对类的定义理解的不够透彻:
class 类名称 extends 父类名称 implements 接口名称{
属性;
方法;
}
从上面的定义中可以知道:子类是先继承父类后实现接口。《4.3.4.1 参数相同,返回值相同》的代码也证实了这一点,因为如果是先实现后继承,那么在《4.3.4.1 参数相同,返回值相同》的 Eagle 类中就需要实现接口 Hunt 的方法,而此处未实现,开发工具也没有报错,那说明,在实现之前已经定义好了,而此时在 Eagle 类中实际上是未显式定义接口 Hunt 中的方法的,因此可以确定推论子类是先继承父类后实现接口是正确的。
明白了子类是先继承父类后实现接口之后,上面的结论也就很好理解了,比如《4.3.4.2 参数相同,返回值不同》的结论:当实现类的父类中的方法的签名跟接口中的方法的签名一样,而返回值不一样时,实现类定义不成功,即根本不存在这样的(实现)类。
因为实现类是先继承父类后实现接口的,所以,当实现类继承了父类之后,相当于已经定义了一个方法签名与接口中方法签名一样的方法。此时二者唯一的不同就是返回值不一样,而返回值不一样并不能区分两个方法,即不满足方法重载的条件(方法名相同,参数类型或个数不同),故不能定义那样的类。同样的道理,这也是为什么《4.3.4.3 参数不同,返回值相同》和《4.3.4.4 参数不同,返回值不同》需要在 Eagle 类中重新定义(实现) hunt 方法的原因——Eagle 类并未实现 Hunt 接口中的方法,它所拥有的只不过是 Animal 类中一个与 Hunt 接口中的方法互为重载关系的方法而已。
4.4 普通类中可以定义接口吗?
可以。
//源码
public class Animal {
interface Climb{
}
}
4.5 接口中可以定义接口吗?
可以。
//源码
public interface Hunt {
interface Kill{
}
}
4.6 如何使用普通类中定义的 private 接口?
//源码
public class Animal {
private Climb mClimb;
public interface Hunt{
void hunt();
}
private interface Climb{
void climb();
}
public class ClimbImpl implements Climb{
@Override
public void climb() {
System.out.println("ClimbImpl climb");
}
}
public Climb getClimb(){
return new ClimbImpl();
}
public void setClimb(Climb climb){
this.mClimb = climb;
mClimb.climb();
}
}
public class Eagle implements Animal.Hunt {
@Override
public void hunt() {
System.out.println("Eagle hunt");
}
}
//此时开发工具会提示 The type Animal.Climb is not visible 错误
public class Tortoise implements Animal.Climb {
}
public class AnimalTest {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.toString());
Animal.Hunt hunt = new Eagle();
hunt.hunt();
//Climb cannot be resolved to a type
//Climb climb = animal.getClimb();
animal.setClimb(animal.getClimb());
}
}
//执行结果
com.smart.www.define_interface_in_class_one.Animal@7852e922
Eagle hunt
ClimbImpl climb
最后的 animal.setClimb(animal.getClimb()) 方法虽然有点突兀,但这里主要想表达的意思是:确实可以在外面用类内部定义的 private 接口。
5. 接口实际应用
接口的使用场景非常多,我们不可能把所有的情况都分析一遍的。接下来,我们就从设计模式的维度来分析如何在实际的开发中使用接口。
工厂方法模式是一种常用的创建型模式,它能很好地将对象的创建和对象的使用分离,并且添加新类型的产品对象不会影响原有的代码。接下来,我们分析下如何在工厂方法设计模式中应用接口。
工厂方法模式的定义如下:在工厂父类中定义一个创建产品对象的接口,让子类负责生产具体的产品对象。(Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclassed.)
工厂方法模式的结构图如下:
由上面的结构图可知,工厂方法模式一共包含四部分:Product(抽象产品类)
抽象产品类是定义产品的接口,是工厂方法模式创建的产品对象的“父类”。ConcreteProduct(具体产品类)
具体产品类是抽象产品类的实现类,它实现了抽象产品类,与具体工厂类一一对应。Factory(抽象工厂类)
抽象工厂类是定义创建产品类的接口。ConcreteFactory(具体工厂类)
具体工厂类是抽象工厂类的实现类,它实现了抽象工厂类,用于生产具体的产品,与具体产品类一一对应。
接下来,我们就根据工厂方法模式创建一个能生产手机的工厂,结构图如下:
以下是具体代码实现:
//源码
public interface Phone {
void call();
}
public class IPhone implements Phone {
@Override
public void call() {
System.out.println("IPhone call");
}
}
public class XiaoMi implements Phone {
@Override
public void call() {
System.out.println("XiaoMi call");
}
}
public class MeiZu implements Phone {
@Override
public void call() {
System.out.println("MeiZu call");
}
}
public interface PhoneFactory {
Phone producePhone();
}
public class IPhoneFactory implements PhoneFactory {
@Override
public Phone producePhone() {
return new IPhone();
}
}
public class XiaoMiFactory implements PhoneFactory {
@Override
public Phone producePhone() {
return new XiaoMi();
}
}
public class MeiZuFactory implements PhoneFactory {
@Override
public Phone producePhone() {
return new MeiZu();
}
}
public class PhoneFactoryTest {
public static void main(String[] args) {
PhoneFactory phoneFactory = new XiaoMiFactory();
Phone phone = phoneFactory.producePhone();
phone.call();
}
}
//执行结果
XiaoMi call
想要添加新的产品类也十分方便,只用创建新的产品类和新的工厂即可,对原有代码没有任何影响,如为该工厂添加创建华为手机的方法:
//源码
public class HuaWei implements Phone {
@Override
public void call() {
System.out.println("HuaWei call");
}
}
public class HuaWeiFactory implements PhoneFactory {
@Override
public Phone producePhone() {
return new HuaWei();
}
}
public class PhoneFactoryTest {
public static void main(String[] args) {
PhoneFactory phoneFactory = new HuaWeiFactory();
Phone phone = phoneFactory.producePhone();
phone.call();
}
}
//执行结果
HuaWei call
参考文档《Java 开发实战经典》
《Thinking in Java》
《设计模式》