目录
一、什么是抽象类
抽象类在实现多态中的意义
二、接口是什么
通过接口实现多态
三、抽象类和接口的区别
各位铁汁们大家好呀!
今天让我们继续学习java,看看java中的抽象类和接口到底是什么?
我们之前学过什么是类,那么抽象类是不是也是类的一种呢?
听名字就感觉好抽象呀!说对了,他就是抽象的,不是具体的。在类中没有包含足够的信息来描绘一个具体的对象,这样的类称为抽象类。
来看一个抽象类的例子
-
// 抽象类和抽象方法需要被 abstract 关键字修饰
-
abstract
class
Shape {
-
// 抽象类中的方法一般要求都是抽象方法,抽象方法没有方法体
-
abstract
void
draw
();
-
}
大家觉得这个抽象类是不是什么也没干,他唯一的方法draw()还是空的。
像这样的类是不是就没有包含足够的信息来描绘一个具体的对象,自然也就不能实例化对象了。不信你看:
那既然一个类不能实例化,那这种抽象类存在的意义是什么呀?别急,存在即合理,听我慢慢道来。
抽象类存在的一个最大意义就是被继承,当被继承后就可以利用抽象类实现多态。
来看一段代码
-
// 抽象类和抽象方法需要被 abstract 关键字修饰
-
abstract
class
Shape {
-
// 抽象类中的方法一般要求都是抽象方法,抽象方法没有方法体
-
abstract
void
draw
();
-
}
-
// 当一个普通类继承一个抽象类后,这个普通类必须重写抽象类中的方法
-
class
Cycle
extends
Shape {
-
@Override
-
void
draw
() {
// 重写抽象类中的draw方法
-
System.out.println(
"画一个圆圈");
-
}
-
}
-
-
public
class
Test4 {
-
public
static
void
main
(String[] args) {
-
//Shape shape = new Shape(); 抽象类虽然不能直接实例化
-
// 但可以把一个普通类对象传给一个抽象类的引用呀,即父类引用指向子类对象
-
Shape
shape
=
new
Cycle();
// 这称作:向上转型
-
-
/*Cycle cycle = new Cycle();
-
Shape shape = cycle // 这是向上转型的另一种写法
-
*/
-
shape.draw();
// 通过父类引用调用被子类重写的方法
-
}
-
}
运行之后你就会发现神奇的一幕:
大家在看完了代码可能会有很多疑问,别急咱们一个一个的说,
什么是向上转型:一句话总结就是“父类引用指向子类对象”
向上转型后的变化
向上转型的作用
这样的话就我们上面的代码就可以理解了
看来,我们可以通过子类对抽象类的继承和重写,抽象类还真有点用呀!
但这和多态有什么关系呢,抽象类用起来这么麻烦,我还不如直接用普通类,也能达到这样的效果,还不用再写一个子类呢?
那行,你再看看下面的代码,你就知道抽象类在实现多态时的好处了。
-
abstract
class
Shape {
-
public
abstract
void
draw
();
// 抽象方法不能里有具体的语句
-
}
-
// 当一个普通类继承一个抽象类的时候,再这个子类中必须重写抽象类中的抽象方法
-
class
Cycle
extends
Shape {
-
@Override
// 如果不重写会报错,但如果继承的是普通类则不会报错,用抽象类更安全
-
public
void
draw
() {
-
System.out.println(
"画一个圆圈");
-
}
-
}
-
class
Flower
extends
Shape {
// 不同的子类对父类的draw方法进行了不同的重写
-
@Override
-
public
void
draw
() {
-
System.out.println(
"画一朵花");
-
}
-
}
-
class
Square
extends
Shape {
-
@Override
-
public
void
draw
() {
-
System.out.println(
"画一个正方形");
-
}
-
}
-
-
public
class
Test4 {
-
public
static
void
main
(String[] args) {
-
Cycle
cycle
=
new
Cycle();
// 子类引用cycle
-
Flower
flower
=
new
Flower();
// 子类引用flower
-
Square
square
=
new
Square();
-
-
// 数组的类型是Shape,即数组中每一个元素都是一个父类引用
-
// 在这个过程其实也发生了向上转型,对抽象类中的方法进行了重写
-
Shape[] shapes = {cycle, flower, square};
// 父类引用引用不同的子类对象
-
for (
int
i
=
0; i < shapes.length; i++) {
-
Shape
shape
= shapes[i];
// 父类引用shape指向—>当前所对应的子类对象
-
-
shape.draw();
// 通过父类引用调用子类重写的draw方法
-
}
-
-
}
-
}
调用同一个方法竟然打印出了不同的结果,这难道就是所谓的多态
是不是有点懵,下面我们来解释一下
-
/
/ 对上面的代码补充一下
-
/
/ 可能你对 Shape[] shapes
= {cycle, flower, square};不太理解
-
/
/ 但上面的代码就相当于
-
-
Shape[] shapes
1
= new Shape[
3];
/
/ 有三个不同的子类对象呀!数组大小为
3
-
-
/
/ (将指向-
>子类对象)的子类引用赋值给父类对象,不就相当于该夫类引用指向-
>所对应的子类对象吗
-
/
/这是向上转型的另一种写法,应为前面已经实例化了子类对象 Cycle cycle
= new Cycle();
-
shapes
1[
0]
= cycle;
/
/ 如果前面没实例化子类对象,就要写成shape
1[
0]
= new Cycle
-
shapes
1[
1]
= flower;
-
shapes
1[
2]
= square;
对于多态来说,他有这三个要素
回头再看一下我们的代码,是不是就刚好符合了多态的三要素。
当我们的父类引用指向不同的子类对象时,当我们调用同一个draw方法时却输出了不同的结果。(其实就是该方法再子类中被重写成了不同形式)这就叫做多态 。
嘻嘻,其实只要只要结合着例子来看,多态也没那么难理解呀
那为啥一定要用抽象类呢?我一个普通类继承普通类来实现多态不可以吗
当然可以,但不太安全有风险;
但如果是抽象类的话,就不一样了
从这我们也可以看出,当用抽象类的时候,编译器自动就对我们是否重写进行了校验,而充分利用编译器的校验, 在实际开发中是非常有意义的 。所以说抽象类还是有用的,嘻嘻
好了,相信到这里你对抽象类也有了一个大概的认识,下面我们来简单做一下总结
哈哈,上面的8条总结是不是臭长臭长的,大家也不用专门去记(用的多了,自然就记住了)
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(Interface)。
接口是Java中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。
如何定义一个接口呢?下面我们来看一个栗子
-
//接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口
-
-
public interface 接口名称{
-
// 定义变量
-
int
a
=
10;
// 接口当中的成员变量默认都是public static final
-
-
// 抽象方法
-
public
abstract
void
method1
();
// public abstract 是固定搭配,可以不写
-
void
method2
();
// 接口当中的成员方法默认都是public abstract, 更推荐用第二种来定义方法
-
}
可以看到接口和类其实还是有很多相似点:
接口中也包含抽象方法,所以也不能直接实例化接口,那么我们怎么用接口呢?
哈哈,很简单,我们再用一个普通类实现这个接口不就行了吗,不同的是抽象类是被子类来继承而实现的,而接口与类之间则是用关键字implements来实现。
就像普通类实现实现抽象类一样,一个类实现某个接口则必须实现该接口中的抽象方法,否则该类必须被定义为抽象类。
铁汁们!刚才我们是用抽象类来实现多态,那么现在我们可以尝试用接口来实现多态,嘻嘻
-
接口可以看成是一种特殊的类,只能用 interface 关键字修饰
-
interface
IShape {
-
int
a
=
10; 接口当中的成员变量默认都是
public
static
final
-
int
b
=
23;
-
void
draw
(); 接口当中的成员方法一般只能是抽象方法,默认是
public
abstract(JDK1
.8以前)
-
-
default
void
show
() {
-
System.out.println(
"接口中的其他方法");
//接口中的其他方法也可以实现,但要用default修饰
-
}
-
public
static
void
test
() {
-
System.out.println(
"这是接口当中的一个静态的方法");
-
}
-
}
-
-
// 一个普通的类要想实现接口,可以用implement,
-
//因为接口也是抽象方法的,所以实现接口的这个类也要重写抽象方法
-
class
Cycle
implements
IShape {
-
-
@Override
-
public
void
draw
() {
-
System.out.println(
"画一个圆圈");
-
}
-
}
-
class
Square
implements
IShape {
-
@Override
-
public
void
draw
() {
-
System.out.println(
"画一个正方形");
-
}
-
}
-
class
Flower
implements
IShape {
-
-
@Override
-
public
void
draw
() {
-
System.out.println(
"画一朵花");
-
}
-
}
-
public
class
Test4 {
-
public
static
void
main
(String[] args) {
-
// IShape iShape = new IShape(); 接口也不能直接实例化
-
Cycle
cycle
=
new
Cycle();
-
Square
square
=
new
Square();
-
Flower
flower
=
new
Flower();
-
// 这里的IShape接口就相当与抽象类中父类,接口类型也是一种引用类型
-
-
IShape[] iShapes = {cycle, square, flower};
// 这个过程其实就发生了向上转型
-
-
for (IShape iShape : iShapes) {
// 增强型的for—each循环,也可以写成普通的for循环形式
-
iShape.draw();
// 通过重写实现了多态
-
}
-
}
-
}
-
引用变量cycle和square都赋值给了Shape类型的引用变量shape,
-
但当执行shape.draw()时,java虚拟机到底要调用谁重写的的draw方法,
-
就看此时接口引用的是那个对象的,是shape的、还是cycle的
看一下运行结果
看完代码你可能有点晕,但没关系。一般接口咱也不这么用,直接使用抽象类不就好了(我只是演示一下用接口也能实现多态)
下面我们来总结一下Java中接口的几个主要特点
那么接口一般用在什么地方呢?
下面就让我们来看看接口的正确用法:帮助java实现“ 多继承 ”
-
由于 Java 中单继承的特性,导致一个类只能继承一个类,但是可以实现一个或多个接口,此时可以使用接口。
-
class
Animal {
-
String name;
// 不能使用private,后面的子类也要用
-
-
public
Animal
(String name) {
// 父类的自定义的构造方法
-
this.name = name;
-
}
-
}
-
interface
IFlying {
// 自定义多种接口
-
void
fly
();
-
}
-
interface
IRunning {
-
void
run
();
-
}
-
interface
ISwimming {
-
void
swimming
();
-
}
-
// 小鸭子,不仅会跑,还会游泳、飞行
-
一个类继承父类,并实现多个接口,间接的解决java中不能多继承的问题
-
class
Duck
extends
Animal
implements
IRunning, ISwimming, IFlying {
-
-
public
Duck
(String name) {
// 子类构造方法
-
super(name);
// 必须在子类构造方法的第一行
-
// 在给实现子类的构造方法前,先要用super()调用实现父类的构造方法,比较先有父后有子呀!
-
// 因为父类自己定义了构造方法,编译器不会自动给给子类构造方法中添加super();来实现父类的构造方法,需要我们自己实现
-
}
-
// 对接口中的抽象方法进行重写
-
@Override
-
public
void
fly
() {
-
System.out.println(
this.name +
"正在用翅膀飞");
-
}
-
-
@Override
-
public
void
run
() {
-
System.out.println(
this.name +
"正在用两条腿跑");
-
}
-
-
@Override
-
public
void
swimming
() {
-
System.out.println(
this.name +
"正在漂在水上");
-
}
-
-
}
-
-
public class 接口的使用 {
// 不用学我用中文名作为类名,我只是为演示方便
-
public
static
void
main
(String[] args) {
-
Duck
duck
=
new
Duck(
"第一个小鸭子");
// 实例化鸭子对象
-
duck.fly();
// 通过引用 变量名.方法名 输出重写后的方法
-
duck.run();
-
duck.swimming();
-
}
-
}
-
有人可能会说干嘛用接口,我直接在父类Animal中实现fly、run、swimming这些属性,
-
然后不同的动物子类再继承父类这些方法不行吗?
-
-
但问题是,鸭子会fly、swimming,那猫会飞和游泳吗?你再写个其他动物的子类是不是就不行了
-
而用接口呢?我们只是把这种飞、游泳的行为给抽象出来了,
-
-
只要一个子类有这种行为,他就可以实现相对应的接口,接口是更加灵活的
-
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多个接口。
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 ,能实现接口的类和该接口并不一定有is_a的关系,只要该类有这个接口的特性就行
猫是一种动物, 具有会跑的特性.
青蛙也是一种动物, 既能跑, 也能游泳
鸭子也是一种动物, 既能跑, 也能游, 还能飞
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型,只要这个类有有这个特性就好。
举个栗子
-
class
Robot
implements
IRunning {
-
private String name;
-
public
Robot
(String name) {
-
this.name = name;
-
}
-
// 对run方法进行重写
-
@Override
-
public
void
run
() {
-
System.out.println(
"机器人" +
this.name +
"正在跑");
-
}
-
}
-
public
class
Test4 {
-
public
static
void
main
(String[] args) {
-
Robot
robot1
=
new
Robot(
"图图");
-
robot1.run();
-
}
-
}
-
// 执行结果
-
机器人图图正在跑
只要能跑就行,管他是机器人还是动物呢,接口是不是很灵活呀!
同时在实际的开发过程中,一般来说,一个类是一个Java文件,一个接口也是一个Java文件。大家也要在平时就养成这个好习惯呀!
Java中接口和抽象类的定义语法分别为interface与abstract关键字。
相同点
都不能被实例化 ,接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点
- 抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;接口中的方法默认使用public修饰
- 接口成员变量默认为public static final,必须赋初值,不能被修改。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;
- 实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
- 接口强调特定功能的实现,而抽象类强调所属关系。
- 抽象类可以包含方法、构造方法,方法可以实现,但是构造方法不能用于实例化,主要用途是被子类调用。接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体
哈哈,不知道现在你对抽象类和接口的认识是不是又上升了一个高度呢?嘻嘻,我们下篇博客再见了,下篇让我们瞅瞅Java中常用的接口有哪些
每天进步一点点,铁汁们,一起加油