其实,我们在Java中,提倡用的是接口而不是继承,很多书上都说“别滥用继承”,那么什么是接口呢,他和继承,多态有什么关系?
何谓接口
Java是一门面向对象的语言,它尝试着把所有问题都抽象为一个实例,也就是一个对象。Java中的类就是一种”类”,可以看做是一个种类。既然是对象,我们可以把它抽象为一个人,而我们在前面说的继承就是子类继承父类,可以看做我们继承了父母的基因,而它完全继承了父类的方法。而接口相当于“行为”,我们可以设计一个类名为people的类,然后给它定义一个接口(行为),我们称这个类拥有这个行为,或这个类操作这个接口,这样的话,在逻辑上面会很好理解,当我们写一些程序时,是非常方便我们理解的。
接口定义行为
我们在Java中使用interface关键字来进行定义接口,假设我们现在有一个类people,现在我们定义一个接口,表示人拥有游泳这个行为:
public interface Swimmer{
public abstract void swim();
}
- 接口可以定义行为但不定义操作,我们用abstract来标示,而且一定是public。对象如果想拥有Swimmer定义的行为,就必须操作Swimmer接口。
- 在接口中,所有的方法都必须是public abstract的,这个一般是不用写的,编译程序的时候会自动加上。
现在我们让人拥有Swimmer行为:
public abstract class People implements Swimmer{
protected String name;
public People(String name){
this.name = name;
}
public String getName(){
return name;
}
@Override
public abstract void swim();
}
我们可以看到,类要操作接口时,需要implements关键字。操作某接口时,有两种处理方式,一种是操作接口中定义的方法,二是再度将该方法标示为abstract。
行为的多态
我们要对行为使用多态语法,和继承时使用多态语法是一样的,都是通过“扮演”来判断这个行为是否是合法的多态语法。例如:
Swimmer swimmer = new Shark();
当我们判断的时候,是从右边向左边读的,右边是不是有左边的行为,或者是右边对象是不是操作了左边的接口。
那好,我们再来看一个问题:
Swimmer swimmer = new Shark();
Shark shark = swimmer;
这个会不会编译正确呢,答案是不会的,第一句话表示鲨鱼拥有Swimmer这个行为是正确的,在第二行由于swimmer是Swimmer类型,编译程序就会想有Swimmer行为的对象是不是Shark呢,答案当然是不一定了,还有可能是People实例。我们可以做如下的修改:
Swimmer swimmer = new Shark();
Shark shark = (Shark) swimmer;
对于第二行的语义就是我现在告诉编译程序,swimmer这个行为就是Shark拥有的,你不要再报错了,所以它就会让你编译通过,但是由这个可能引发的后果就要自己承担了。
我们再来看一个:
Swimmer swimmer = new Shark();
Fish fish = (Fish) swimmer;
如果在第二行没有让swimmer扮演Fish的话,这个写法就是错误的,它参考的是鲨鱼,鲨鱼继承自鱼,你现在让swimmer强行扮演Fish也是可以通过的,不会报错,但是如果你这样做的话那么就GG了:
Swimmer swimmer = new Submarine();
Fish fish = (Fish) swimmer;
这个程序在执行时期是会出错的,因为我们让swimmer参考的是Submarine,现在在第二行又让它扮演鱼类,这肯定是不正确。
解决需求变化
- 在Java中,类可以操作两个以上的类,也就是拥有两种以上的行为,类也可以同时继承某个类,并操作某些接口。
- 在Java中,接口可以继承自另一个接口,也就是继承父接口的行为,再在子接口中额外设计行为,我们可以用这个方法来扩展我们的程序功能。
接口语法细节
接口的默认
- 在刚才已经说过了,我们在接口中定义的没有操作的行为都是public abstract,这是系统默认的。
- 在接口中我们还可以定义枚举常数,对于枚举常数我会在以后说明,现在就了解一下这个概念就可以了。
- 在接口中我们也只能定义public static final 的枚举常数,如果我们在接口中只写了int STOP = 0;这样的语句,编译程序会自动展开为public static final。
匿名内部类
在Java中,我们有时会有临时继承某个类或操作某个接口并建立实例的需求,由于这些类或接口只使用一次,并不需要为这些类命名,所以我们可以使用匿名内部类来解决这个需求。它的基本语法:
new 父类() | 接口(){
//类本体操作
};
比如:
Object o = new Object(){
@Override
public String toString(){
return "语法示范";
}
};
在JDK8出现之前,如果要在匿名内部类中存取局部变量,该局部变量必须是final,要了解为什么,就需要涉及一些底层知识,我们知道,在Java中局部变量的生命周期是比对象要短的,当一个方法调用完之后就会返回对象,局部变量的生命周期就结束了。要解决这个问题,在Java中使用传值,也就是在匿名内部类的实例中,建立新的变量参考原来的对象。例如:
int ai[] = {10, 20};
Object obj = new Object(ai){
public String toString(){
return ...; //使用局部变量,方法之后生命周期结束
}
final int x[];
{
x = ai; //传值
}
}
所以我们也不能改变x的参考,为此程序才强制你要在局部变量加上final。