抽象类的产生:
当编写一个类时,我们往往会为该类定义一些方法,这些方法是用来描述该类的功能具体实现方式,那么这些方法都有具体的方法体。
但是有的时候,某个父类只是知道子类应该包含怎么样的方法,但是无法准确知道子类如何实现这些方法。比如一个图形类应该有一个求周长的方法,但是不同的图形求周长的算法不一样。那该怎么办呢?
分析事物时,发现了共性内容,就出现向上抽取。会有这样一种特殊情况,就是方法功能声明相同,但方法功能主体不同。那么这时也可以抽取,但只抽取方法声明,不抽取方法主体。那么此方法就是一个抽象方法。
宠物店的案例中,猫、狗、鸭子都能叫,因此将叫声的方法主体抽象到父类中,声明为抽象方法
1.抽象类
1.1.抽象类和抽象方法的定义
抽象类:使用abstract 关键字修饰的类叫做抽象类
abstract class 类名 {
}
抽象类和普通类的区别:
1.抽象类需要修饰符abstract修饰,普通方法不允许
2.普通类可以实例化,抽象类不能实例化
抽象方法:使用abstract修饰的方法叫抽象方法,抽象方法不允许有方法体。
访问修饰符 abstract 返回类型 方法名 ();
抽象方法和普通方法的区别:
1.抽象方法需要修饰符abstract修饰,普通方法不允许
2.抽象方法没有方法体,普通方法有方法体
注:
● 子类如果不是抽象类,则子类必须重写抽象类中的全部抽象类方法
1.2.抽象类的规则
抽象类专门用于继承关系,在继承关系中充当父类。
(1)抽象类不允许被实例化,既不让new
为什么呢?抽象类不是一个完整的类,因为抽象类中可能有抽象方法,而抽象方法没有方法体,没有方法体的方法是半成品,所以不允许实例化。
(2)抽象类中可以有属性,普通方法,构造方法,main方法
public abstract class Shape {
//抽象类中可以定义属性
double param1;
double param2;
static final double PI = 3.14;
//抽象类中可以定义普通方法
public void sayHello(){ }
//抽象类中可以定义抽象方法
public abstract double daC();
//抽象类中可以定义main方法
public static void main(String[] args) {
}
}
(3)如果一个类中包含抽象方法,那么这个类必须是抽象类
public abstract class Shape {
//抽象方法
public abstract double daC();
}
错误的情况示例:
(4)抽象类中可以没有抽象方法
public abstract class Shape {
//抽象类中可以没有抽象方法
}
(5)父类可以通过抽象方法要求子类实现抽象方法。
子类继承抽象父类后,子类从父类继承了抽象方法,由于此时子类中包含了抽象方法,所以子类也必须是抽象类。抽象类不允许实例化,导致无法创建子类对象。若要能够实例化子类对象,就必须保证子类不是抽象类。只要子类实现了重写从父类继承的抽象方法,那么子类中就没有抽象方法了,就可以实例化了。
代码示例:
abstract class Shape {
public static final Double PI = 3.1415926;
//定义抽象方法
public abstract double calcC(double n1,double n2);
}
class Rectangle extends Shape{
//实现抽象方法
@Override
public double calcC(double n1, double n2) {
return (n1 + n2) * 2;
}
}
class Circle extends Shape{
//实现抽象方法
@Override
public double calcC(double n1, double n2) {
return 2 * Shape.PI * n1;
}
}
public class TestAbs{
public static void main(String[] args) {
//实例化Rectangle对象
Shape shape1 = new Rectangle();
double c1 = shape1.calcC(3, 4);
System.out.println(c1);
//实例化Circle对象
shape1 = new Circle();
double c2 = shape2.calcC(3, 4);
System.out.println(c2);
}
}
1.3.谁不与abstract共存
- private:私有的方法子类是无法继承到的,也不存在覆盖,而abstract和private一起使用修饰方法,abstract既要子类去实现这个方法,而private修饰子类根本无法得到父类这个方法。互相矛盾。
- final:final修饰的类不允许被继承,与abstract相悖。
- static:static是静态,只有一份,用于共享。abstract是为了让子类重写,是多份,用于子类私有。
1.4.抽象类的优势
抽象类中己经实现的方法可以被其子类使用,使代码可以被复用,同时提供了抽象方法,保证了子类具有自身的独特性。
1.5.抽象类的局限性
在有些应用场合,仅仅使用抽象类和抽象方法会有一定的局限性。下面通过“宠物店”来进一步分析、认识这种局限性,并学会使用接口来改进设计。
宠物店中,猫是喵喵的叫,猎豹也是喵喵的叫,狗是汪汪的叫,鸭子是嘎嘎的叫,如果还有宠物鱼,干脆不叫,因此在类图中设计的结果如下。
此时,使用抽象类就会出现以下问题:
第一,叫的方法不再通用,因为有不叫的宠物
第二,子类继承宠物抽象类之后,写出来的叫的方法可能会出现代码重复的情况,如猎豹和猫都是“喵喵”叫,这时候,就不再符合代码复用的要求。
对于第一个叫的方法不再通用问题,最自然的想法就是将叫这个方法变为抽象方法,然后由其子类去实现,这样做虽然解決了第一个问题,但是会造成代码冗余的问题,如这里的猎豹和猫的叫方法也会一样,也就是第二个问题更加突出。要解决上述问题,最理想的方式就是使用接口。
2.接口
2.1.接口与定义规范
接口是interface,相当于抽象类,在继承关系中充当父类的角色,在接口中通过定义抽象方法来指定规范,子类去实现接口,要实现接口中的所有抽象方法。
接口可以多实现。也就实现了子类的多继承问题。
与定义类的class不同,接口定义时需要使用interface关键字。
接口定义语法格式:
public interface 接口名 {
抽象方法1;
抽象方法2;
抽象方法3;
}
定义接口:
public interface IShout {
}
定义接口就是定义规范,接口中的抽象方法就是具体的规范。
2.2.实现类与遵循规范
接口实现语法格式:
class 类 implements 接口 {
重写接口中方法
}
在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类需要重写该抽象方法,完成具体的逻辑。
实现接口:
interface IShout extends IFly{//extends Object 接口不是类,所以没有默认继承Object
//接口没有最高层,类的最高层是Object
//接口中不允许有普通方法,所以接口中的方法都是抽象的
/*public void sayHello(){
}*/
//接口不是类,所以没有构造
/*
public IShout(){
}
*/
//定义常量
String TYPE="动物";
//定义抽象方法
void shout();
}
class Rabbit implements IShout{
//实现shout()方法
@Override
public void shout() {
}
//实现fly()方法
@Override
public void fly() {
}
}
实现接口就是遵循接口的规范。
2.3.接口的多实现
了解了接口的特点后,那么想想为什么要定义接口,使用抽象类描述也没有问题,接口到底有啥用呢?
接口最重要的体现:解决多继承的弊端。将多继承这种机制在java中通过多实现完成了。
interface Fu1{
void show1();//接口定义规范show1()
}
interface Fu2{
void show2();//接口定义规范show2()
}
class Zi implements Fu1,Fu2 {//多实现。同时实现多个接口。
public void show1(){}//子类实现show1()规范
public void show2(){}//子类实现show2()规范
}
怎么解决多继承的弊端呢?
- 弊端:多继承时,当多个父类中有相同功能时,子类调用会产生不确定性。
- 其实核心原因就是在于多继承父类中功能有主体,而导致调用运行时,不确定运行哪个主体内容。
为什么多实现能解决了呢?
- 因为接口中的功能都没有方法体,由子类来明确。
2.4. 类继承类同时实现接口
- 接口和类之间可以通过实现(implements)产生关系
- 类与类之间可以通过继承(extends)产生关系
- 当一个类已经继承了一个父类,它又需要扩展额外的功能,这时接口就派上用场了。
- 子类通过继承父类扩展功能,通过继承扩展的功能都是子类应该具备的基础功能。如果子类想要继续扩展其他类中的功能呢?这时通过实现接口来完成。
class Fu {
public void show(){}
}
interface Inter {
pulbic abstract void show1();
}
interface Outer {
pulbic abstract void show2();
}
class Zi extends Fu implements Inter,Outer {
public void show1() {
}
public void show2() {
}
}
接口的出现避免了单继承的局限性。父类中定义的事物的基本功能。接口中定义的事物的扩展功能。
2.5.接口的规定
- 接口中可以定义变量,但是变量必须有固定的修饰符修饰,public static final 所以接口中的变量也称之为常量,其值不能改变。
- 接口中可以定义方法,方法也有固定的修饰符,public abstract
- 接口不可以new,不可以实例化。
- 子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类。
- 接口不能继承类
- 接口可以继承接口
- 接口没有最高层,类的最高层是Object
- 接口中不允许有普通方法,所以接口中的方法都是抽象(除了default方法和static方法)
- 接口不是类,所以没有构造
2.6.接口的继承
学习类的时候,知道类与类之间可以通过继承产生关系,接口和类之间可以通过实现产生关系,那么接口与接口之间会有什么关系。
多个接口之间可以使用extends进行继承。
interface Fu1{
void show();
}
interface Fu2{
void show1();
}
interface Fu3{
void show2();
}
interface Zi extends Fu1,Fu2,Fu3{
void show3();
}
在开发中如果多个接口中存在相同方法,这时若有个类实现了这些接口,那么就要实现接口中的方法,由于接口中的方法是抽象方法,子类实现后也不会发生调用的不确定性。
2.7. JDK1.8中接口的新特性
2.7.1.default方法
JDK1.8中可以定义默认方法,默认方法是由default关键字修饰的
默认方法必须有方法实现,也可以被重写。
interface IShout {
//定义默认方法,有方法实现,子类可以直接使用,也可以重写
public default void method(){
System.out.println(" interface default method ");
}
}
public class Rabbit implements IShout{
@Override
public void method() {
System.out.println(" rabbit method is running. ");
}
public static void main(String[] args) {
Rabbit rabbit = new Rabbit();
rabbit.method();
}
}
2.7.2.static方法
interface IShout {
//定义抽象方法,有方法实现,可以直接使用接口名调用
public static void method(){
System.out.println(" interface static method ");
}
}
public class Rabbit implements IShout{
public static void main(String[] args) {
IShout.method();
}
}
接口中的static方法由接口名调用,static方法只有一份