抽象类
-
概述
-
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的。类是对 对象的抽象,而抽象类就是对类的抽象。用来捕捉子类的通用特性 ,用来创建继承层级里子类的模板。
-
抽象类和普通类的区别是不能实例化和可以包含抽象方法(如果没有抽象方法就没有存在的意义)
-
由于抽象类不能实例化对象,所以抽象类必须继承后,才能使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。(子类必须重写父类的抽象方法,除非也是抽象类)
-
-
为什么要用抽象类?
普通类里定义的方法,子类也可以覆盖重写,为什么要定义成抽象类呢?
当然是可以把抽象类都写成非抽象类的。但是现实中有些父类中的方法体确实没有必要写,因为各个子类中的这个方法肯定会有不同 (比如Dog 这个类的 eat 方法),所以没有必要在父类里写。而写成抽象类,这样别人看到你的代码,或你看到别人的代码,你就会注意抽象方法,而知道这个方法是在子类中实现的,所以,有个 提示作用 。
总结:
作用1:架构明确,清晰直观
作用2:提示作用,重点关注 -
abstract 关键字
用 abstract 修饰的类为抽象类,方法则为抽象方法。抽象方法不写 { } 括号。//抽象类 abstract class Dog { public abstract void sleep(); public abstract void eat(); } //具体类 public class Poodle extends Dog{ @Override public void eat() { System.out.println("Poodle吃罐头"); } @Override public void sleep() { System.out.println("Poodle睡床"); } } ---*--- 演示类
抽象类和普通类的区别是可以有抽象方法,所以这里仅用抽象方法演示。将Dog定义为抽象类,有sleep和eat两个抽象方法。具体的狗Poodle(贵宾犬),再去实现这两个方法
-
总结
-
抽象类不能被实例化
-
有抽象方法的类必定是抽象类。
-
抽象类中的抽象方法只是声明,不包含方法体。
-
构造方法,类方法(用 static 修饰的方法)、final方法、私有方法,不能声明为抽象方法。
-
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
-
接口
-
概述
官方解释:Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
个人理解:接口可以理解为一种特殊的抽象类,其只能有公共的静态常量和公共的抽象方法(JDK 8之前)。接口是用来弥补Java类只能单继承,和在不影响原有继承体系情况下针对行为的扩展。比如飞行这个行为,鸟类可以实现,飞机类也可以实现。 -
定义与实现 interface & implements
//飞接口 interface Fly{ void fly(); } //抽象鸟类 abstract class Bird{ abstract void eat(); abstract void sleep(); } //猫头鹰类 class Owl extends Bird implements Fly{ @Override void eat() { System.out.println("猫头鹰吃老鼠"); } @Override void sleep() { System.out.println("猫头鹰睡树上"); } @Override public void fly() { System.out.println("猫头鹰式飞行"); } } ---*--- 演示类
这里主要说一下为什么不把fly方法写到Bird类中。因为有一些鸟类是不会飞行的,但是它们至少是需要吃饭和睡觉的。所以这里我把eat和sleep方法写到了Bird抽象类,把fly写到一个接口。
-
接口的特性
1.接口都是abstract隐性修饰的
2.接口没有构造方法,所以也就不能实例化(通过子类实例化)
3.接口中只有静态常量(所有变量都以 public static final 隐性修饰)
4.接口中的都是公共抽象方法(方法都以 public abstract 隐性修饰),在JDK8 之前
5.接口可以多继承,而且一个类可以实现多个接口
区别
先来举一个简单的例子:
狗都具有 eat() 、sleep() 方法,我们分别通过抽象类和接口定义这个抽象概念
//通过抽象类定义
public abstract class Dog {
public abstract void eat();
public abstract void sleep();
}
//通过接口定义
public interface Dog {
public abstract void eat();
public abstract void sleep();
}
但是我们现在如果需要让狗拥有一项特殊的技能——钻火圈 DrillFireCircle(),如何增加这个行为呢?
思考:
- 将钻火圈方法与前面两个方法一同写入抽象类中,但是这样的话,但凡继承这个抽象类狗都具有了钻火圈技能,明显不合适
- 将钻火圈方法与前面两个方法一同写入接口中,当需要使用钻火圈功能的时候,就必须实现 接口中的eat() 、sleep() 方法(重写该接口中所有的方法)显然也不合适
那么该如何解决呢 ? 我们可以仔细想一想,eat和sleep都是狗本身所应该具有的一种行为,而钻火圈这种行为则是后天训练出来的,只能算是对狗类的一种附加或者延伸, 两者不应该在同一个范畴内,所以我们考虑将这个单独的行为,独立的设计一个接口,其中包含DrillFireCircle()方法, Dog设计为一个抽象类, 其中又包括eat() 、sleep() 方法.
一个SpecialDog即可继承Dog类并且实现DrillFireCircle()接口
下面给出代码:
//定义接口,含有钻火圈方法
public interface DrillFireCircle() {
public abstract void drillFireCircle();
}
//定义抽象类狗类
public abstract class Dog {
public abstract void eat();
public abstract void sleep();
}
//继承抽象类且实现接口
class SpecialDog extends Dog implements drillFireCircle {
public void eat() {
//....
}
public void sleep() {
//....
}
public void drillFireCircle() () {
//....
}
}
- 总结
继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如狗是否能钻火圈,能则可以实现这个接口,不能就不实现这个接口。