抽象类
什么是抽象类?
抽象类是特殊的类,只是不能被实例化;除此以外,具有类的其他特性;重要的是抽象类可以包括抽象方法,这是普通类所不能的。抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须覆盖它们。另外,抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖,如果不覆盖,则其派生类必须覆盖它们。
如何定义抽象类?
抽象类和抽象方法需要被 abstract 关键字修饰
抽象类中的方法一般要求都是抽象方法,抽象方法没有方法体
举个例子:
/**
* @author gf
* @date 2023/2/23
*/
// 1.抽象类和抽象方法需要被 abstract 关键字修饰
abstract class Car {
// 2.抽象类中的方法一般要求都是抽象方法,抽象方法没有方法体
abstract void makeCar();
}
抽象类存在的意义?
众所周知,抽象类不能被实例化,那么抽象类存在的意义是什么呢?
抽象类存在的一个最大意义就是被继承,当被继承后就可以利用抽象类实现多态。
先看一段代码:
/**
* @author gf
* @date 2023/2/23
*/
// 抽象类和抽象方法需要被 abstract 关键字修饰
abstract class Car {
// 抽象类中的方法一般要求都是抽象方法,抽象方法没有方法体
abstract void makeCar();
}
class BWM extends Car{
// 重写抽象类中的draw方法
@Override
void makeCar() {
System.out.println("制造宝马");
}
}
public class CatTest {
public static void main(String[] args) {
// 抽象类虽然不能直接实例化,但可以把一个普通类对象传给一个抽象类的引用,即父类引用指向子类对象
Car bwm = new BWM();
// 通过父类引用调用被子类重写的方法
bwm.makeCar();
}
}
运行结果:
疑问1:父类引用指向子类对象后发生了什么?
关于方法:父类引用可以 调用子类和父类公用的方法(如果子类重写了父类的方法,则调用子类方法)但子类特有的方法无法调用。
关于属性: 父类引用可以调用父类的属性,不可以调用子类的属性
疑问2:这样做的好处是什么?
减少一些重复性的代码
对象实例化的时候可以根据不同需求实例化不同的对象
抽象类实现多态
再看一段代码:
// 抽象类和抽象方法需要被 abstract 关键字修饰
abstract class Car {
// 抽象类中的方法一般要求都是抽象方法,抽象方法没有方法体
abstract void makeCar();
}
class BWM extends Car{
// 重写抽象类中的draw方法
@Override
void makeCar() {
System.out.println("宝马:即使你把我拆得七零八落,我依然是位美人");
}
}
class Tesla extends Car{
// 重写抽象类中的draw方法
@Override
void makeCar() {
System.out.println("特斯拉:我要做电车界的老大");
}
}
class BYD extends Car{
// 重写抽象类中的draw方法
@Override
void makeCar() {
System.out.println("比亚迪:干掉特斯拉,我们就是电车界的老大");
}
}
public class CatTest {
public static void main(String[] args) {
// 抽象类虽然不能直接实例化,但可以把一个普通类对象传给一个抽象类的引用,即父类引用指向子类对象
Car bwm = new BWM();
Car tesla = new Tesla();
Car byd = new BYD();
// 通过父类引用调用被子类重写的方法
bwm.makeCar();
tesla.makeCar();
byd.makeCar();
}
}
运行结果:
多态的好处这里就不赘叙了
疑问:抽象类实现多态和普通类实现多态有什么区别?
抽象类的子类必须重写父类的所有方法,而普通类则不用
抽象类的特点
信到这里对抽象类也有了一个大概的认识,下面我们来简单做一下总结
使用abstract修饰的类或方法,就抽象类或者抽象方法
抽象类是不能具体的描述一个对象,不能用抽象类直接实例化对象
抽象类里面的成员变量和成员方法,都是和普通类一样的,只不过就是不能进行实例化了
当一个普通类继承这个抽象类后,那么这个普通类必须重写抽象类当中的所有的抽象方法(我们之前说过抽象类是不具体的,没有包含足够的信息来描述一个对象,所以我们需要把他补充完整)
但当一个抽象类A继承了抽象类B,这是抽象类A就可以不重写抽象类B当中的抽象方法
final不能修饰抽象类和抽象方法(因为抽象类存在的最大意义就是被继承,而被final修饰的不能被继承,final和抽象,他们两个是天敌)
抽象方法不能被private修饰(抽象方法一般都是要被重写的,你被private修饰了,还怎么重写)
抽象类当中不一定有抽象方法,但如果一个类中有抽象方法,那么这个类一定是抽象类。
接口
如何定义接口
接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口
口当中的成员变量默认都是public static final
接口当中的成员方法默认都是public abstract
例子:
public interface 接口名称{
// 定义变量,接口当中的成员变量默认都是public static final
int a = 10;
// 抽象方法
public abstract void method1(); // public abstract 是固定搭配,可以不写
void method2(); // 接口当中的成员方法默认都是public abstract, 更推荐用第二种来定义方法
}
疑问:那么接口一般用在什么地方呢?
一般情况下,实现类和它的抽象类之前具有 "is-a" 的关系,但是如果我们想达到同样的目的,但是又不存在这种关系时,使用接口。
由于 Java 中单继承的特性,导致一个类只能继承一个类,但是可以实现一个或多个接口,此时可以使用接口。
通过接口实现多态
代码如下:
/**
* @author gf
* @date 2023/2/23
*/
interface Car {
void makeCar();
}
class BWM implements Car{
@Override
public void makeCar() {
System.out.println("宝马:即使你把我拆得七零八落,我依然是位美人");
}
}
class Tesla implements Car{
@Override
public void makeCar() {
System.out.println("特斯拉:我要做电车界的老大");
}
}
class BYD implements Car{
@Override
public void makeCar() {
System.out.println("比亚迪:干掉特斯拉,我们就是电车界的老大");
}
}
public class CatTest {
public static void main(String[] args) {
Car bwm = new BWM();
Car tesla = new Tesla();
Car byd = new BYD();
bwm.makeCar();
tesla.makeCar();
byd.makeCar();
}
}
运行结果:
帮java实现多继承
由于 Java 中单继承的特性,导致一个类只能继承一个类,但是可以实现一个或多个接口,此时可以使用接口。
代码例子:
class Animal {
String name;
// 父类的自定义的构造方法
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 CatTest {
public static void main(String[] args) {
Duck duck = new Duck("小黄鸭"); // 实例化鸭子对象
duck.fly(); // 通过引用 变量名.方法名 输出重写后的方法
duck.run();
duck.swimming();
}
}
疑问: 有人可能会说干嘛用接口,我直接在父类Animal中实现fly、run、swimming这些属性,
然后不同的动物子类再继承父类这些方法不行吗?
但问题是,鸭子会fly、swimming,那猫会飞和游泳吗?你再写个其他动物的子类是不是就不行了
而用接口呢?我们只是把这种飞、游泳的行为给抽象出来了,只要一个子类有这种行为,他就可以实现相对应的接口,接口是更加灵活的。
抽象类与接口的区别
通过以上对抽象类和接口的描述,我们总结一下抽象类和接口的区别
抽象类要被子类继承,接口要被类实现
接口只能做方法声明,抽象类中可以作方法声明,也可以做方法实现
接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量
接口是设计的结果,抽象类是重构的结果
抽象类和接口都是用来抽象具体对象的,但是接口的抽象级别最高
抽象类可以有具体的方法和属性,接口只能有抽象方法和不可变常量
抽象类主要用来抽象类别,接口主要用来抽象功能