抽象类 接口 多态

Posted createtable

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了抽象类 接口 多态相关的知识,希望对你有一定的参考价值。

一 抽象类(abstract)

抽象类的概念
只抽取了很多类的方法的声明,方法声明用abstract修饰。
一个类如果有抽象方法,那么这个类必须是抽象类。
抽象类里边可以没有抽象方法,如果这么做只有一个目的:不让你创建这个类的对象。
抽象类不能被实例化,不能创建对象。
如果一个类继承抽象类,那么,它要么重写抽象类中的所有抽象方法,要么本身也是抽象类。

抽象类的成员
成员变量:可以是常量,也可以是变量。子类可以直接继承抽象类中的成员变量。
成员方法:可以是抽象的,也可以是非抽象的。抽象方法在子类中必须要被实现。普通方法可以被子类直接继承使用。
构造方法:抽象类不能被实例化创建对象,但抽象类是class,那么它就有构造方法,可以给子类实例化使用。

抽象关键字abstract不可以和哪些关键字共存?
private:私有的,外部直接无法访问。
static:static修饰后抽象方法就可以通过类名调用,但是这样是没有意义的。
final:final修饰的方法不能被重写,所以它和abstract冲突。

二 接口(interface)

接口的概念
接口是功能的集合,可看做是一种数据类型,一种只包含了功能声明的特殊类,是比抽象类更为抽象的”类”。
接口只描述所应该具备的方法,没有具体实现,具体实现由接口的实现类(相当于接口的子类)来完成。
当一个类中所有的方法都是抽象的时候,没必要定义为抽象类,定义为接口即可。

接口的作用
1 扩展了功能,将功能的定义与实现分离,优化程序设计,降低了耦合性,即设备与设备之间实现了解耦。
2 解决了java中只能单继承的问题。接口与接口可以是单继承,也可以是多继承。 extends

Java多继承会出现的问题
多继承时,当多个父类中有相同功能时,子类调用会产生不确定性,不确定运行父类中哪个功能主体。
接口多实现时,接口中的功能都没有方法体,由子类来明确,避免了多继承的问题。

接口的成员
接口只有成员变量和成员方法。
成员变量:必须是常量, 默认修饰符 public static final
成员方法:必须是公共访问的抽象方法,默认修饰符 public abstract
为便于阅读,建议手动加上修饰符。

定义格式
public interface 接口名 {
  抽象方法1;
  抽象方法2;
  抽象方法3;
}

接口的实现类
在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类需要重写该抽象方法,完成具体的逻辑。
子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类。

类实现接口的格式
class 类 implements 接口 {
  重写接口中方法
}

继承+实现接口
父类中定义的事物的基本功能,接口中定义的事物的扩展功能。子类通过继承父类来扩展功能,如果想继续扩展其他类中的功能,需要通过实现接口来完成。

接口和抽象类异同
相同点:
都位于继承的顶端,用于被其他类实现或继承;
都不能直接实例化对象;
都包含抽象方法,其子类都必须覆写这些抽象方法;
区别:
抽象类只能单继承;接口可以多实现。
抽象类为部分方法提供实现,避免子类重复实现这些方法,提高代码重用性;接口只能包含抽象方法;
抽象类是这个事物体系结构中的共性内容, 继承体系是is..a关系
接口是这个事物中的扩展功能,继承体系是like..a关系
包含成员区别
二者的选用:
优先选用接口,尽量少用抽象类;
需要定义子类的行为,又要为子类提供共性功能时才选用抽象类;

三 多态

多态的概念
Java作为面向对象的语言,可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
Java中多态的代码体现在一个子类对象(实现类对象)既可以给这个子类引用变量赋值,又可以给这个子类的父类(接口)变量赋值。如一个Student对象既可以赋值给一个Student类型的引用,也可以赋值给一个Person类型的引用。
最终多态体现为父类引用变量可以指向子类对象。

使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。

方法重载(静态多态)
方法重写(动态多态,对象多态)

多态的前提
A:类与类(或接口)要有继承(或实现)关系。
B:一定要有方法的重写。
C:一定要有父类或者接口的引用指向子类的对象。
多态思想:可以指挥同一类型的一批对象做事情,多态的出现让我们复杂的问题简单化了。

类、抽象类、接口的多态调用
类的多态定义格式:
父类的引用变量指向子类对象
父类类型 变量名 = new 子类类型();
变量名.方法名();

class Fu {}
class Zi extends Fu {}
//类的多态使用
Fu f = new Zi();

抽象类多态定义格式
抽象类 变量名 = new 抽象类子类();

abstract class Fu {
  public abstract void method();
}
class Zi extends Fu {
  public void method(){
    System.out.println(“重写父类抽象方法”);
  }
}
//类的多态使用
Fu fu= new Zi();

接口多态定义的格式
接口 变量名 = new 接口实现类();

interface Fu {
  public abstract void method();
}
class Zi implements Fu {
  public void method(){
    System.out.println(“重写接口抽象方法”);
  }
}
//接口的多态使用
Fu fu = new Zi();

多态成员方法的变化
多态出现后会导致子父类中的成员变量有微弱的变化

class Fu {
    int num = 4;
}
class Zi extends Fu {
    int num = 5;
}
class Demo {
    public static void main(String[] args) {
        Fu f = new Zi();
        System.out.println(f.num);
        Zi z = new Zi();
        System.out.println(z.num);
    }
}         

>>>
4
5

多态成员变量
如果子父类中出现同名成员变量,多态调用该变量时:
编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。
运行时期:也是调用引用型变量所属的类中的成员变量。
简单记: 编译运行看左边。

class Fu {
    int num = 4;
    void show()    {
        System.out.println("Fu show num...");
    }
}
class Zi extends Fu {
    int num = 5;
    void show()    {
        System.out.println("Zi show num...");
    }
}
class Demo {
    public static void main(String[] args)     {
        Fu f = new Zi();
        f.show();
        System.out.println(f.num);
        
        Zi z = new Zi();
        z.show();
        System.out.println(z.num);
    }
}

>>>
Zi show num...
4
Zi show num...
5

多态成员方法
编译时期:参考引用变量所属的类,如果没有类中调用的方法,编译失败。
运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法。
简而言之:编译看左边,运行看右边。

Fu f = new Zi();
成员变量: 编译和运行都看Fu。
非静态方法:编译看Fu,运行看Zi。
静态方法: 编译和运行都看Fu。

举例:超人的例子:
person :走路();
SuperMan:走路();fly();

Person p = new SuperMan();//超人没变身之前就是普通人一个,只能调用Person里的方法
//在运行的时候发现有SuperMan这个子类继承了他,会去看里面是否有和你调用Person里相同的方法
//如果有运行就执行子类重写的方法(成员函数的特性,覆盖)
p.走路();

SuperMan sm= (SuperMan)p;//变身超人
sm.走路();
sm.fly();

总结:无论是向上转型还是向下转型,变化的都是子类对象,绝对不能把父类对象强转为子类类型

instanceof关键字
instanceof关键字用来判断对象是否属于某种数据类型。如学生的对象属于学生类,学生的对象也属于人类
格式:boolean b = 对象 instanceof 数据类型;

Person p1 = new Student();
boolean flag = p1 instanceof Student; //flag结果为true
boolean flag2 = p2 instanceof Teacher; //flag结果为false

多态转型
向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。
使用格式:
父类类型 变量名 = new 子类类型();
Person p = new Student();

向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。如果是直接创建父类对象,是无法向下转型的!
使用格式:
子类类型 变量名 = (子类类型) 父类类型的变量;
Student stu = (Student) p; //变量p实际上指向Student对象

多态的好处和弊端

当父类的引用指向子类对象时,就发生了向上转型,即把子类类型对象转成了父类类型。
向上转型的好处是隐藏了子类类型,提高了代码的扩展性。
弊端是只能使用父类共性的内容,而无法使用子类特有功能,功能有限制。

看如下代码

//描述动物类,并抽取共性eat方法
abstract class Animal {
    abstract void eat();
}
     
// 描述狗类,继承动物类,重写eat方法,增加lookHome方法
class Dog extends Animal {
    void eat() {
        System.out.println("啃骨头");
    }

    void lookHome() {
        System.out.println("看家");
    }
}
    
// 描述猫类,继承动物类,重写eat方法,增加catchMouse方法
class Cat extends Animal {
    void eat() {
        System.out.println("吃鱼");
    }

    void catchMouse() {
        System.out.println("抓老鼠");
    }
}
    
public class Test {
    public static void main(String[] args) {
        Animal a = new Dog(); //多态形式,创建一个狗对象
        a.eat(); // 调用对象中的方法,会执行狗类中的eat方法
        // a.lookHome();//使用Dog类特有的方法,需要向下转型,不能直接使用
        
        // 为了使用狗类的lookHome方法,需要向下转型
        // 向下转型过程中,可能会发生类型转换的错误,即ClassCastException异常
        // 那么,在转之前需要做健壮性判断 
        if( !a instanceof Dog){ // 判断当前对象是否是Dog类型
                System.out.println("类型不匹配,不能转换"); 
                return; 
        } 
        Dog d = (Dog) a; //向下转型
        d.lookHome();//调用狗类的lookHome方法
    }
}

什么时候使用向上转型:
当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作,这时就可以使用向上转型。
Animal a = new Dog();
a.eat();
什么时候使用向下转型:
当要使用子类特有功能时,就需要使用向下转型。
Dog d = (Dog) a; //向下转型
d.lookHome();//调用狗类的lookHome方法
弊端:需要面对具体的子类对象;向下转型时容易发生ClassCastException类型转换异常,转换前必须用instanceof做类型判断。


多态举例:毕老师和毕姥爷的故事

class 毕姥爷 {
    void 讲课() {
        System.out.println("政治");
    }

    void 钓鱼() {
        System.out.println("钓鱼");
    }
}

// 毕老师继承了毕姥爷,就有拥有了毕姥爷的讲课和钓鱼的功能,
// 但毕老师和毕姥爷的讲课内容不一样,因此毕老师要覆盖毕姥爷的讲课功能
class 毕老师 extends 毕姥爷 {
    void 讲课() {
        System.out.println("Java");
    }

    void 看电影() {
        System.out.println("看电影");
    }
}

public class Test {
    public static void main(String[] args) {
        // 多态形式
        毕姥爷 a = new 毕老师(); // 向上转型
        a.讲课(); //这里表象是毕姥爷,其实真正讲课的仍然是毕老师,因此调用的也是毕老师的讲课功能
        a.钓鱼();//这里表象是毕姥爷,但对象其实是毕老师,而毕老师继承了毕姥爷,即毕老师也具有钓鱼功能

        // 当要调用毕老师特有的看电影功能时,就必须进行类型转换
        毕老师 b = (毕老师) a; // 向下转型
        b.看电影();
    }
}

 

以上是关于抽象类 接口 多态的主要内容,如果未能解决你的问题,请参考以下文章

多态抽象类魔术方法

接口与抽象类 多态

面向对象_多态_抽象类_接口

抽象类,接口类,封装,多态

多态抽象类与接口

Java语法包 继承 多态 抽象类 接口