您好,我是湘王,这是我的51CTO博客,欢迎您来,欢迎您再来~


Java是一种纯面向对象的编程语言,类和对象是它的两个基本语言特性,学过Java的人都应该知道。但它还有另外两个比较容易搞混淆的特性:抽象类和接口。

我尝试着结合自身的经验,把这个问题搞清楚。受个人经验能力所限,出现纰漏在所难免,欢迎来辩。

先看一张表(《Java编程思想》中列出的关键特性):

Java中的抽象类与接口(1)_java


拿一个实际的问题举例:

所有的生物都有“吃东西”和“繁衍后代”这样的共性接口,哺乳类动物具有胎生、肺呼吸、体温恒定等共性,这就可以用抽象类来表示,这样就不必与生物这个基类紧耦合。代码表示如下:

/**
* 生物
*
*/
public interface Bios {
/**
* 吸收能量
*/
public void energy();

/**
* 繁衍
*/
public Object multiply();
}


/**
* 动物
*
*/
public interface Animal extends Bios {
/**
* 移动
*/
public void move();

/**
* 吃东西
*/
public void eat();
}


/**
* 哺乳纲
*
*/
public abstract class Mammal implements Animal {
/**
* 体温恒定
*/
protected static double temperature = 37;

/**
* 胎生
*/
public abstract Object vivipation();

/**
* 肺呼吸
*/
public abstract void pulmonary();
}


/**
* 灵长类
*
*/
public abstract class Primate extends Mammal {
/**
* 偶蹄,蹄均呈双数
*/
protected static boolean evenHoof = true;

/**
* 大脑发达,会思考
*/
public abstract void thinking();
}



/**
* 人类
* 生物 -> 动物 -> 哺乳纲 -> 灵长目
*
*/
public class Person extends Primate {
@Override
public void move() {
// TODO Auto-generated method stub
}

@Override
public void eat() {
// TODO Auto-generated method stub
}

@Override
public void energy() {
// TODO Auto-generated method stub
}

@Override
public Object multiply() {
// TODO Auto-generated method stub
return null;
}

@Override
public void thinking() {
// TODO Auto-generated method stub
}

@Override
public Object vivipation() {
// TODO Auto-generated method stub
return null;
}

@Override
public void pulmonary() {
// TODO Auto-generated method stub
}
}


可以看到,「人类」继承了「哺乳纲」和「灵长类」的属性,同时也需要实现「生物」、「动物」接口和「哺乳纲」、「灵长类」的抽象方法。

至于抽象类和接口的区别,很多技术博客都写过,比如有些博主写得很好,比如门和报警器的例子、鸟和飞机的例子。不过在我看来,这都是从语言本身的特性来说明两者的区别,如果换一种例子、换一个场景,恐怕又会懵圈——好像什么都说了,又好像什么都没说。

与其将语法解释的很明白,倒不如把它用明白,这其实就是新手和高手之间的最大区别:新手总想弄懂每一招每一式,但高手更注重肌肉记忆所产生的反馈循环,什么意思呢?

来看看好的实践是怎么用抽象类和接口的吧。

Java中的抽象类与接口(1)_抽象类_02



这是Java I/O中的类继承结构图,这只是一部分,但用说明问题已经足够了。

可以看到,AbstractList是一个抽象类,实现了List接口,而List接口又继承自Collection接口。

抽象类AbstractList的部分定义是:

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

......

public abstract E get(int index);

......

}


接口List的部分定义是:

public interface List<E> extends Collection<E> {

......

E get(int index);

......

}


同样可以很清楚地看到,抽象类和接口中都定义了E get(int index)这个方法,这是为啥?而且这个方法还是抽象类唯一的方法,为了这个方法,单独定义一个抽象类。因为作用不同。

接口中的E get(int index)是一种获取列表元素的操作,List这个接口集中了所有对列表可能的操作。

确切地来说,抽象类的更像是一种「模板」,规定了它的子类需要干的工作;而接口更像是一种「约束」,只要实现了接口的类,都要实现接口的方法。而且这种约束是可以不断往下传递的——只要父类有了这种约束,那么子类也全都得遵守这种约束。如果这个约束被修改了,那么遵守它的类也全都要跟着修改。

所有的集合都要能够迭代、可增加、删除和获取元素,这就是一种约束,所以以接口形式表现。而不同类型的集合又有不同类型的方法,有些方法不需要工程师自己实现,仅仅可以作为默认的「模板」提供。而模板,是不需要实例化的(也最好禁止实例化,所以它只能是抽象的,不能具体化。抽象类本身也是可以没有抽象方法的)。

所以,多看一些优秀的源码,如JDK的源码、Spring的源码,多揣摩作者的设计意图,看多了,也就慢慢会了。




感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~