什么是接口

接口定义行为

比如我们想要开发一个海洋乐园游戏,当中所有东西都会游泳。

谈到会游的东西,第一个想到的就是鱼。

我们刚才学了继承。

也许可以定义Fish类中有个swim()的行为。

package base.interf.OceanWorld.v1;

public abstract class Fish  {
    protected String name;
    public Fish(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }

    public abstract void swim();
}

由于实际上每种鱼游泳方式不同,所以将swim()定义为abstract,因此Fish也是abstract。

接着定义小丑鱼继承鱼

package base.interf.OceanWorld.v1;

public class Anemonefish extends Fish {
   public Anemonefish(String name) {
       super(name);
   }
   
    @Override
    public void swim() {
        System.out.printf("小丑魚 %s 游泳%n", name);
    }
}

我们还可以定义鲨鱼Shark类继承Fish、食人鱼Piranha继承Fish。

package base.interf.OceanWorld.v1;

public class Shark extends Fish {
   public Shark(String name) {
       super(name);
   }
   
    @Override
    public void swim() {
        System.out.printf("鯊魚 %s 游泳%n", name);
    }
}

package base.interf.OceanWorld.v1;

public class Piranha extends Fish {
   public Piranha(String name) {
       super(name);
   }
   
    @Override
    public void swim() {
        System.out.printf("食人魚 %s 游泳%n", name);
    }    
}

然后,需求变了。老板说话了。

为什么都是鱼?人也会游泳啊!怎么没有?

于是你就再定义Human类继承Fish。

package base.interf.OceanWorld.v1;

public class Human extends Fish {

	public Human(String name) {
		super(name);
	}

	@Override
	public void swim() {
		System.out.printf("人 %s 游泳%n", name);
	}

}

等一下,Human继承Fish?不会觉得很奇怪吗?

就目前为止,程序也可以执行,继承会有是一种(is-a)的关系,

所以Anemonefish是一种Fish,Shark是一种Fish,Piranha是一种Fish,如果你让Human继承Fish,那Human是一种Fish?

image-20190511074934236

重新想一下需求,想要的是所有东西”都会“游泳”,而不是“某种东西”都会“游泳”。

“所有东西”都会“游泳”,代表了“游泳”这个“行为”可以被所有东西拥有,而不是“某种”东西专属。

对于“定义行为”,在Java中可以使用interface关键字定义。

package base.interf.OceanWorld.v2;

public interface Swimmer {
    public void swim();
}

Fish类

package base.interf.OceanWorld.v2;

public abstract class Fish implements Swimmer {
    protected String name;
    public Fish(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    @Override
    public abstract void swim();
}

Anemonefish

package base.interf.OceanWorld.v2;

public class Anemonefish extends Fish {
   public Anemonefish(String name) {
       super(name);
   }
   
    @Override
    public void swim() {
        System.out.printf("小丑魚 %s 游泳%n", name);
    }
}

Human

package base.interf.OceanWorld.v2;

public class Human implements Swimmer {
    private String name;
    
    public Human(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    @Override
    public void swim() {
        System.out.printf("人 %s 游泳%n", name);
    }
    
}

最终的类图

image-20190511075514175

以Java的语意来说,继承会有“是一种”关系,操作接口则表示“拥有行为”,但不会有“是一种”的关系。Human与Submarine操作了Swimmer,所以都拥有Swimmer定义的行为,但它们没有继承Fish,所以它们不是一种鱼,这样的架构比较合理也较有弹性,可以应付一定程度的需求变化。

行为的多态

和继承的多态一样。我们直接来看代码。

package base.interf.OceanWorld.v3;

public class Ocean {
    public static void doSwim(Swimmer swimmer) {
        swimmer.swim();
    }
    
    public static void main(String[] args) {
        doSwim(new Anemonefish("小鱼儿"));
        doSwim(new Shark("沙克"));
        doSwim(new Human("贾维斯"));
        doSwim(new Submarine("唐山一号"));
    }
}

解决需求变化

今天老板突发奇想,想把海洋乐园变为海空乐园,有的东西会游泳,有的东西会飞,有的东西会游也会飞。

仔细想想,有的东西会飞,但不限于某种东西才有“飞”这个行为。

有了前面的经验,我们可以使用interface定义了Flyer接口。

package base.interf.OceanWorld.v4;

public interface Flyer {
    public abstract void fly();
}

假设有台海上飞机具有飞行的行为,也可以在海面上航行,可以定义Seaplane操作Swimmer与Flyer接口

package base.interf.OceanWorld.v4;

public class Seaplane implements Swimmer, Flyer {
    private String name;
    
    public Seaplane(String name) {
        this.name = name;
    }
    
    @Override
    public void fly() {
        System.out.printf("海上飞机 %s 在飞%n", name);
    }

    @Override
    public void swim() {
        System.out.printf("海上飞机 %s 航行海面%n", name);
    }
}

在Java中,类可以操作两个以上的类,也就是拥有两种以上的行为。

例如,Seaplane就同时拥有Swimmer与Flyer的行为。

如果是会游也会飞的飞鱼呢?飞鱼是一种鱼,可以继承Fish类,飞鱼会飞,可以操作Flyer接

package base.interf.OceanWorld.v4;

public class FlyingFish extends Fish implements Flyer {
    public FlyingFish(String name) {
        super(name);
    }
    
    @Override
    public void swim() {
        System.out.println("飞魚游泳");
    }

    @Override
    public void fly() {
        System.out.println("飞魚会飞");
    }
    
}

在Java中,类可以同时继承某个类,并操作某些接口。例如,FlyingFish是一种鱼,也拥有Flyer的行为。如果现在要让所有会游的东西游泳。我们可以这么来写

package base.interf.OceanWorld.v4;

public class Ocean {
	public static void doSwim(Swimmer swimmer) {
		swimmer.swim();
	}

	public static void main(String[] args) {
		doSwim(new Anemonefish("小鱼儿"));
		doSwim(new Shark("沙克"));
		doSwim(new Human("贾维斯"));
		doSwim(new Submarine("唐山一号"));

		doSwim(new Seaplane("空軍零號"));
		doSwim(new FlyingFish("甚平"));
	}
}

最后我们的程序如下

image-20190511081151522

然而,

老板又开口了,

  • 并非所有的人都会游泳,所以不再让Human操作Swimmer
  • 只有游泳选手会游泳,游泳选手是一种人,并拥有Swimmer的行为
  • 有的飞机只会飞,所以设计一个Airplane类作为Seaplane的父类,Airplane操作Flyer接口
  • Seaplane会在海上航行,所以在继承Airplane之后,必须操作Swimmer接口
  • 直升机就只会飞
  • 水里的话,将浅海游泳与深海潜行分开好了

那后我们又开始修改代码。

image-20190511082330297

接口语法细节

接口的默认
  • Java中,使用interface来定义抽象的行为外观,方法要声明为public abstract,无须且不能有方法定义。

    interface Action{
    	public abstract void excute();
    }
    
  • 接口中的方法一定是公开且抽象,且不能有操作,没有别的写法了。为了方便,也可以省略public abstract。

    interface Action{
    	void excute();
    }
    

    那么问题来了,下面代码的执行结果如何

    interface Action{
    	void excute();
    }
    
    class SomeAction implements Action{
    
    	@Override
    	void excute() {
    		System.out.println("一些操作");
    
    	}
    
    }
    public class MainAction {
    	public static void main(String[] args) {
    		SomeAction someAction = new SomeAction();
    		someAction.excute();
    	}
    }
    

    在interface中,可以定义常数。

    interface Action {
    	public static final int TEST_NUMBER = 123;
    
    	void excute();
    }
    
    // 使用
    Action.TEST_NUMBER
    
  • Java8以后,可以定义default方法。

    default void excutedefault() {
    		System.out.println("默认的一些操作");
    }
    
  • 类可以操作两个以上的接口,如果有两个接口都定义了某方法,而操作两个接口的类会怎样吗?

package com.ripjava.interfaces;

interface Some {
	void excetue();

	void doSome();
}

interface Other {
	void excetue();

	void doOther();
}

public class Service implements Some, Other {

	@Override
	public void doOther() {
		System.out.println("doOther");

	}

	@Override
	public void excetue() {
		System.out.println("excetue");

	}

	@Override
	public void doSome() {
		System.out.println("doSome");

	}

	public static void main(String[] args) {
		Some some = new Service();
		some.excetue();
		Other other = new Service();
		other.excetue();
	}

}

在设计上,思考一下:Some与Other定义的execute()是否表示不同的行为?

  • 如果表示不同的行为,那么Service在操作时,应该有不同的方法操作,那么Some与Other的execute()方法就得在名称上有所不同,Service在操作时才可以有两个不同的方法操作。

  • 如果表示相同的行为,那可以定义一个父接口,在当中定义execute()方法,而Some与Other继承该接口,各自定义自己的doSome()与doOther()方法.

    interface SAction {
    	void excetue();
    }
    
    interface SSome extends SAction{
    
    	void doSome();
    }
    
    interface SOther extends SAction{
    	void excetue();
    
    	void doOther();
    }
    
匿名类

在将集合排序的时候和大家说。

使用enum枚举常数

从JDK5之后新增了enum语法,可用于定义枚举常数。直接来看例子:

public enum Action {
    STOP, RIGHT, LEFT, UP, DOWN
}

那么如何使用这个Action呢?可以用它来声明类型。

public class Game {
    public static void play(Action action) {
        switch(action) {
            case STOP:
                System.out.println("停止");
                break;
            case RIGHT:
                System.out.println("向右");
                break;
            case LEFT:
                System.out.println("向左");
                break;
            case UP:
                System.out.println("向上");
                break;
            case DOWN:
                System.out.println("向下");
                break;
        }
    }
    public static void main(String[] args) {
        play(Action.RIGHT);
        play(Action.UP);
    }    
}

这个是enum的基本用法,以后如果有机会的话,会给大家讲一下,高级的用法。