Java 抽象类与接口
Java 抽象类
在上文我们的多态的讲解中,我们父类设定了一些方法,设定的主要目的是让子类继承父类去覆写那些方法,来展示不同的结果。换句话说,我们不关心父类方法里的具体实现,反正会被子类的方法覆写,那么我们就可以让父类更抽象一下,抽象到只有方法的声明,而没有方法体。我们管这种方法叫做抽象方法,管包含抽象方法的类叫做抽象类。
抽象类的特点
抽象类里只要含有一个或者一个以上的抽象方法就是抽象类,但如果一个抽象方法都没有,那这个抽象类也没有建立的意义。抽象方法是具有如下格式的方法
[public] abstract 返回类型 方法名(参数列表);
抽象方法是没有方法体的 ,所以方法名写完就会加;
表示方法声明结束,抽象法方法体用abstract
修饰,表示这是一个抽象的方法,访问修饰符只能用public或者是protected,或者是默认访问权限,就是不能用private,因为根本无法去继承
同样,抽象类的格式如下:
[public] abstract class 类名{
[public] abstract 返回类型 方法名(参数列表);
}
抽象类的是前面加abstract
修饰,表示这个是一个抽象类,访问修饰符只能用public或者是protected,或者是默认访问权限,不能用private的原因和上面一样。
抽象类创造的意义是将方法的声明与方法的实现分隔开,从而实现多态。那么他就具有如下的特点:
- 抽象类不能被实例化,也就是说不能直接创建一个抽象类的对象,但抽象类是可以有构造函数的,如果是有参构造函数,则子类要去显示调用它。
- 抽象类是用来被继承的,方法是要被覆写的,如果子类继承了抽象的父类,则需要覆写父类的抽象方法,如果没有覆写,则子类必须也要定义为抽象类。
- abstract是不能与private static,final 修饰符一起使用来修饰方法。
这里我们解释一下第三点,abstract不能与private一起用,因为private修饰的方法和类,都是无法再类之外被访问到。也就没有继承的可能性。abstract不能和static一起用,是因为abstract的作用是实现多态,而实现多态则依赖于继承和覆写。static修饰的方法虽能被子类所继承,但是我们修改了继承后的方法时,这个就不能算作是覆写,而是父类的方法被隐藏掉了,只有通过
父类名.方法名
的形式显示调用它,这个实现不了多态。从另一个角度来看,静态的方法是编译的时候就确定了,无法实现后期绑定,也就不存在运行时在决定方法调用的可能。所以static修饰的方法是可以被继承,但无法实现多态,自然也就不能和abstract
一起使用。
abstract不能和final一起使用的原因和上面一样,final修饰的方法无法被继承,自然也谈不上多态,所以abstract无法和final一起用。
抽象类举例
我们把上文多态的例子,继续修改,抽象化。我们把Animal的代码改成如下样子。
public abstract class Animal {
abstract void run();
}
我们Dog,Cat类的代码不需要改变。
public class Dog extends Animal{
@Override
public void run() {
System.out.println("狗在奔跑");
}
}
public class Cat extends Animal{
@Override
public void run() {
System.out.println("猫在奔跑");
}
}
其他Print类,和Test类的代码也保持不变,代码如下:
public class Print {
public void print(Animal animal) {
animal.run();
}
}
public class Test {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
new Print().print(dog);
new Print().print(cat);
}
}
我们可以看出抽象类和之前普通类,在其他地方的改动基本是没有的,只是将方法变得抽象,意义上更加明确。
抽象总结
创建抽象类和抽象方法是很有意义的,他能让我们设计类的时候使类的含义更加明确,通过抽象的思想,让类变得更加通用,声明一系列的方法来告诉用户打算怎么去使用它。
Java 接口
接口可以看出一种协议,一种要求,我们继承他来知道我们可以做什么,具体怎么做则取决与我们。比如KFC就是一个接口,我们一看他就知道里面有买汉堡,炸鸡,可乐,但是具体的味道和服务又会根据不同的店,有不同的样子,这就是接口的意义。所以我们这里也可以看出接口里的方法也应该是抽象的。
接口的特点
Java中接口的写法如下:
[public] interface InterfaceName {
成员变量
方法声明
}
接口区别于类,他不是用class来修饰,他用特定的interface
关键字,访问修饰符与class一致,可以用public或者缺省。里面只有抽象方法和成员变量两种内容。成员变量会默认添加public static final
意为成员变量归类所有,访问权限是最大的,但是不能别继承和修改。这也看出接口是因为不能被实例化,才会这样约定的,接口的成员变量不允许为空,在定义的时候就要赋值给他。 而接口的方法则默认为抽象方法,默认添加public abstract
和抽象方法一样,只能写方法声明。
不同于我们用extends
去继承一个类,我们用implements
来表示实现这个接口。
class ClassName implements Interface1{
}
接口作为一个特殊的存在,是有一些他的独特的地方的。
- 一个类是可以实现多个接口的,这在一定程度上实现了Java的多继承。
- 接口是不能被实例化,不同于抽象类,接口的内部只能由成员变量和抽象方法,是不可以存在静态变量块,以及构造器的。
- 我们是可以声明一个接口类型的变量,但是只能引用一个实现了这个接口的类。
- 同抽象方法,实现了接口的类必须实现接口的所有方法,否则就会变成抽象类。
接口举例
看过抽象的例子,我们可能想,我们把Animal从抽象换成接口,不就实现了一个接口的例子嘛,其他地方基本也不用去改动。但这显然是错的。我们并不能去说Animal是一个接口,我们上面说了,接口是一种协议,规定我们能做什么,而不是一个事物的抽象。从这里我们也能看出接口和抽象的不同,抽象更多的是一种重构而产生的东西,我们先有dog,cat类,然后才会把他们共性的东西提取出来,放到一个更通用,更抽象的父类Animal中,而我们发现Animal不需要管run方法是怎么实现的,所以我们将run方法设定为抽象的方法,从而将Animal类设为抽象类,等待去=继承者来实现run。这是一种从下而上的设计思想。但是接口不是,接口一开始就是设定好的,我们根据设定好的接口从上往下去写。接口先规定好了有什么方法,然后我们再去具体实现他。这是一种从上而下的设计思想。
所以,我们不能将Animal设为接口,但是我们可以将Print设为接口,他规定我们有一个print()方法,代码如下:
public interface Print {
void print(Object obj);
}
那我们就可以写一个新的类去实现这个Print接口。代码如下:
public class SimplePrint implements Print{
@Override
public void print(Object obj) {
((Animal)obj).run();
}
}
除了Test类以外,其他地方都不需要改变。我们Test类代码如下:
public class Test {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
Print print = new SimplePrint();
print.print(dog);
print.print(cat);
}
}
接口总结
接口和抽象虽然都是通过抽象的方法来提供我们实现多态的方式,但是他们却是两个不同的设计思想。这里关于接口的讲解比较简单,关于接口自身的继承,接口内部包含其他接口,以及利用接口来实现回调等等留在以后的文章专门来说。这里主要是通过对比来了解抽象和接口。