java之多态(面向对象编程)


java中的多态与C++中的类似。

一、向上转型及向下转型

1、向上转型
向上转型指的是将子类对象赋给父类引用(父类引用引用子类对象),例如实现:

class Animal{
    protected String name;
    public Animal(String name){
        this.name = name;
    }
    public void eat(){
        System.out.println(name+"eat");
    }
    public void sleep(){
        System.out.println(name+"sleep");
    }
}

class Dog extends Animal{
    public Dog(String name){
        super(name);
    }
    public void func(){
        System.out.println(this.name + "在游戏");
    }
}

class Cat extends Animal{
    public Cat(String name){
        super(name);
    }

    public void func(){
        System.out.println(this.name + "在猫砂中");
    }

}
public class TestDemo1 {
    public static void main(String[] args) {
        Dog dog = new Dog("哈士奇");
        Animal dog2 = dog;
        dog2.eat();
        dog2.sleep();
        dog2.func();//无法访问子类的方法或者属性,只能访问父类自己的方法或者属性
    }
}

当然也可以直接:

        Animal dog = new Dog("哈士奇");
        dog.sleep();
        dog.eat();

注意:向上转型之后,通过父类的引用只能访问父类自己的方法或者属性,因此dog2.func()是无法实现的,会抛异常。
向上转型发生的时机:
(1)直接赋值:上面已经示范
(2)传参,例如:

class Animal{
    protected String name;
    public Animal(String name){
        this.name = name;
    }
    public void eat(){
        System.out.println(name+"eat");
    }
    public void sleep(){
        System.out.println(name+"sleep");
    }
}

class Dog extends Animal{
    public Dog(String name){
        super(name);
    }
    public void func(){
        System.out.println(this.name + "在游戏");
    }
}

class Cat extends Animal{
    public Cat(String name){
        super(name);
    }

    public void func(){
        System.out.println(this.name + "在猫砂中");
    }

}
public class TestDemo1 {
    public static void func(Animal animal){
        animal.eat();
        animal.sleep();
    }
    public static void main(String[] args) {
        Animal dog = new Dog("哈士奇");
        func(dog);
    }
}

结果为:
在这里插入图片描述
(3)返回值,例如:

class Animal{
    protected String name;
    public Animal(String name){
        this.name = name;
    }
    public void eat(){
        System.out.println(name+"eat");
    }
    public void sleep(){
        System.out.println(name+"sleep");
    }
}

class Dog extends Animal{
    public Dog(String name){
        super(name);
    }
    public void func(){
        System.out.println(this.name + "在游戏");
    }
}

class Cat extends Animal{
    public Cat(String name){
        super(name);
    }

    public void func(){
        System.out.println(this.name + "在猫砂中");
    }

}
public class TestDemo1 {
    public static Animal func(){

        Dog dog = new Dog("哈士奇");
        return dog;
    }
    public static void main(String[] args) {
        Animal animal = func();
        animal.eat();
        animal.sleep();
    }
}

结果也为:
在这里插入图片描述
2、向下转型
向下转型就是父类对象转为子类对象,上面向上转型是子类对象转为父类对象,那么父类引用是没有办法访问子类的方法和属性的,我们如果想要dog访问dog类的func方法,可以使用向下转型,例如:

class Animal{
    protected String name;
    public Animal(String name){
        this.name = name;
    }
    public void eat(){
        System.out.println(name+"eat");
    }
    public void sleep(){
        System.out.println(name+"sleep");
    }
}

class Dog extends Animal{
    public Dog(String name){
        super(name);
    }
    public void func(){
        System.out.println(this.name + "在游戏");
    }
}

class Cat extends Animal{
    public Cat(String name){
        super(name);
    }

}
public class TestDemo1 {
    public static Animal func(){

        Dog dog = new Dog("哈士奇");
        return dog;
    }
    public static void main(String[] args) {
        Animal animal = new Dog("哈士奇");
        Dog dog = (Dog)animal;
        dog.func();
    }
}

结果为:
在这里插入图片描述
但是向下转型是非常不安全的,例如:

  public static void main(String[] args) {
        Animal animal = new Dog("哈士奇");
        Cat cat = (Cat)animal;
        cat.func();
    }

运行就会抛异常:
在这里插入图片描述
因为animal本质上引用的是一个Dog对象,但是却将其转为

二、多态(运行时绑定或者动态绑定)

1、重写
(1)方法名相同
(2)返回值相同
(3)参数列表相同
(4)不同的类—>继承关系
例如:

class Animal{
    protected String name;
    public Animal(String name){
        this.name = name;
    }
    public void eat(){
        System.out.println("Animal():eat()");
    }
    public void sleep(){
        System.out.println("Animal():sleep()");
    }
}

class Dog extends Animal{
    public Dog(String name){
        super(name);
    }
    public void eat(){
        System.out.println("Dog():eat()");
    }

}

class Cat extends Animal{
    public Cat(String name){
        super(name);
    }

    public void sleep(){
        System.out.println("Cat():sleep()");
    }

}
public class TestDemo1 {
    public static void main(String[] args) {
        Animal dog = new Dog("哈士奇");
        dog.eat();
        Animal cat = new Cat("喵喵");
        cat.sleep();
    }
}

我们想要调用Dog自己的eat方法和Cat自己的sleep方法,此时在Dog类中对Animal类的eat方法进行重写,运行结果为:
在这里插入图片描述
这种情况就是多态,体现在父类的引用引用子类的对象,同时通过父类引用调用同名的覆盖方法,就会发生运行时绑定,也就是动态绑定(多态)。
重写的注意事项
(1)需要重写的方法不能被final修饰,因为final修饰的方法密封方法,不可以修改。
(2)被重写的方法访问修饰限定符不能是private的(原因在继承篇已经讲过)。
(3)被重写的方法子类的访问修饰限定符要大于父类的访问修饰限定符。
(4)static方法不能被重写。
注意
在构造方法中调用重写方法,例如:

class B{
    public B(){
        func();
    }
    public void func(){
        System.out.println("B::func()");
    }
}
class D extends B{
    private int count = 1;
    @Override
    public void func() {
        System.out.println("D::func()" + count);
    }
}

public class TestDemo2 {
    public static void main(String[] args) {

        D d = new D();
    }
}

父类B中的构造方法调用了func()方法,此时的运行结果为:
在这里插入图片描述
发现调用了子类D重写的func()方法,且count为0,这是因为在创建子类D的对象时,会调用父类B的构造方法,可以发现父类B的构造方法调用的是子类重写的func()方法,但是此时D对象本身还没有构造,因此count还没有初始化,因此为0。
总结
在继承的条件下,子类重写父类的同名方法,父类的引用引用子类的对象,调用同名方法调用的是子类的同名方法。
使用多态的好处是:
(1)类调用者对类的使用成本降低,能够让类的调用者不用知道这个类的类型,只需要知道这个对象具有某个方法;
(2)降低代码的圈复杂度,避免使用大量的if-else,例如实现代码:

class Shape{
    public void draw(){

    }
}
class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("○");
    }
}
class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("□");
    }
}
class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("♣");
    }
}
public class TestDemo {
    public static void drawMap(Shape shape){
        shape.draw();
    }
    public static void main(String[] args) {

        Shape shape1 = new Flower();
        Shape shape2 = new Cycle();
        Shape shape3 = new Rect();
        //多态作为参数的情况
        drawMap(shape1);
        drawMap(shape2);
        drawMap(shape3);
    }
}

代码实现打印多个图形的功能,使用多态避免了使用多个if-else语句,降低了圈复杂度。
(3)可扩展能力更强,例如上述代码如果我们要多增加一种新的形状,使用多态只需要添加一个子类,对draw方法进行重写即可,代码改动成本比较低。

三、抽象类

在多态总结中,打印多个图形的例子,父类Shape的draw方法好都是由它的子类的draw方法好像并没有做什么实际的工作,打印图形主要是由它的子类的draw方法来完成的,对于这种没有实际工作的方法,可以将其设计为一个抽象方法,包含抽象方法的类称为抽象类,即:

abstract class Shape{
    abstract public void draw();
}

在draw方法前加上abstract关键字表示它是一个抽象方法,抽象方法没有方法体({}),包含抽象方法的类,必须加上abstract关键字,表示它是一个抽象类。
注意
(1)抽象类不能实例化,例如:

Shape shape = new Shape();

显示编译出错,提醒Shape是抽象的,无法实例化。
(2)抽象方法不能是private的,例如:

abstract class Shape{
    abstract private void draw();
}

显示编译出错,提醒非法的修饰符组合:abstr和private;
(3)抽象类主要是用来被继承的:注意如果一个类继承了抽象类,这个类中一定要实现抽象类中的抽象方法,例如:
在这里插入图片描述
发现Rect类继承了抽象类Shape,但是出现了错误,因为没有重写Shape类中的抽象方法draw,此时应该为:

abstract class Shape {
    abstract public void draw();
    public int age;//定义字段
    //定义方法
    void func() {
        System.out.println("func");
    }
}
class Rect extends Shape {
    public void draw(){
        System.out.println("□");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Shape shape = new Rect();
        shape.func();
    }
}

此时的运行结果就是:
在这里插入图片描述
抽象类的最大意义就是为了被继承,它本身不能被实例化,要想使用,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法,它相当于多了一重编译器的检验,实际工作不应该由父类完成,而应由子类完成,如果此时不小心误用成父类了,使用普通类编译器不会报错,但是父类是抽象类就会在实例化的时候提示错误,尽早发现问题。
因此抽象类或者抽象方法一定不能被final修饰。

四、接口

接口是抽象类的更进一步,抽象类中可以包含非抽象方法和字段,而接口中包含的方法都是抽象方法,字段只能包含静态常量。
例如在打印图形的例子中,父类Shape没有包含其他非抽象方法,可以设计为一个接口,例如:

interface Shape {
	//省略abstract和public
    void draw();
}
class Rect implements Shape {
    public void draw(){
        System.out.println("□");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Shape shape = new Rect();
        shape.draw();
    }
}

(1)使用interface定义一个接口;
(2)接口中的方法一定是抽象方法,因此可以省略abstract;
(3)接口中的方法一定是public,因此可以省略public;
(4)子类Rect使用implements继承接口,此时表达的含义是“实现”而不是“扩展”;
(5)在调用时同样可以创建一个接口的引用,对应一个子类的实例;
(6)接口不能被单独实例化
(7)接口中其实可以有具体实现的方法,这个方法被default修饰(jdk1.8中加入的);
(8)接口中的成员变量一定是静态常量,即:

interface Shape {
    void draw();
    public static final int num = 0;//静态常量
}

注意:
在这里插入图片描述
这个出现了错误,发现Rect中的draw()无法实现Shape中的draw(),正在尝试分配更低的访问权限; 以前为public,也就是说,子类中重写的抽象方法draw必须是public的,即:
在这里插入图片描述
(9)接口是为了解决单继承的问题出现的,有时候需要一个类同时集成自多个父类,java中只支持单继承,一个类只能extends一个父类,但是可以同时实现多个接口,达到多继承类似的作用,例如实现一个动物类:

class Animal{
    protected String name;
    public Animal(String name){
        this.name = name;
    }
}
//会飞的
interface IFlying{
    void Fly();
}
//会跑的
interface IRunning{
    void Run();
}
//会在水里游的
interface ISwimming{
    void Swim();
}
//创建几个具体的动物
//狗是会跑的
class Dog extends Animal implements IRunning{
    public Dog(String name){
        super(name);
    }

    @Override
    public void Run() {
        System.out.println(this.name + "Run()");
    }
}
//鸟是会飞的
class Bird extends Animal implements IFlying{
    public Bird(String name){
        super(name);
    }

    @Override
    public void Fly() {
        System.out.println(this.name +"Fly()");
    }
}
//鱼会游
class Fish extends Animal implements ISwimming{
    public Fish(String name){
        super(name);
    }

    @Override
    public void Swim() {
        System.out.println(this.name +"Swim()");
    }
}
//青蛙是会跑也会在水里游的
class Frog extends Animal implements IRunning,ISwimming{
    public Frog(String name){
        super(name);
    }

    @Override
    public void Run() {
        System.out.println(this.name +"Run()");
    }

    @Override
    public void Swim() {
        System.out.println(this.name +"Swim()");
    }
}
//鸭子既会跑,也会飞,也会游
class Buck extends Animal implements IRunning,IFlying,ISwimming{
    Buck(String name){
        super(name);
    }

    @Override
    public void Run() {
        System.out.println(this.name +"Run()");
    }

    @Override
    public void Fly() {
        System.out.println(this.name +"Fly()");
    }

    @Override
    public void Swim() {
        System.out.println(this.name +"Swim()");
    }
}
public class TestDemo {

    public static void main(String[] args) {
         IRunning animal1 = new Dog("wangwang");
         animal1.Run();
         IFlying animal2 = new Bird("haha");
         animal2.Fly();
         ISwimming animal3 = new Fish("Tom");
         animal3.Swim();
         IRunning animal4 = new Frog("Jerri");
         animal4.Run();
         ISwimming animal5 = new Frog("Jim");
         animal5.Swim();
         ISwimming animal6 = new Buck("gaga");
         animal6.Swim();
         IRunning animal7 = new Buck("lala");
         animal7.Run();
         IFlying animal8 = new Buck("jiejie");
         animal8.Fly();

    }
}

它的编译运行结果为:
在这里插入图片描述
也就是一个类继承一个父类,同时实现多种接口,继承表达的含义时is-a语义,而接口表达的含义时含有某种特性,上述表达的就是:
狗是一种动物,具有会跑的特性;
青蛙是一种动物,既可以跑,也可以游;
鸭子是一种动物,既能跑,也能飞,也可以游;
这样的设计就是体现多态的好处,让程序员不用关心类型,有了接口之后,类使用者不用关心具体类型,只需要关心某个类是都具有某种能力,例如实现一个方法:

public class TestDemo {

    public static void Walk(IRunning irunning){
        System.out.println("走路");
        irunning.Run();
    }
    public static void main(String[] args) {
         IRunning animal1 = new Dog("wangwang");
         /*animal1.Run();*/
         Walk(animal1);

    }
}

在这个walk方法内部,并不关注是哪种动物,只要参数是会跑的就可以,甚至参数不是动物,例如:

class Robot implements IRunning{

    private String name;
    public Robot(String name){
        this.name = name;
    }
    @Override
    public void Run() {
        System.out.println(this.name + "正在用轮子跑");
    }
}
public class TestDemo {

    public static void Walk(IRunning animal){
        System.out.println("走路");
        animal.Run();
    }
    public static void main(String[] args) {
         /*IRunning animal1 = new Dog("wangwang");
         *//*animal1.Run();*//*
         Walk(animal1);*/
        IRunning robot = new Robot("celin");
        Walk(robot);

    }
}

此时运行结果为:
在这里插入图片描述
Comparable接口
例如实现一个学生类,创建一个对象数组,对学生的成绩进行排序:

class Student{
    private String name;
    private int score;
    public Student (String name,int score){
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "[" + this.name + ":" + this.score + "]";
    }
}
public class TestDemo {
   public static void main(String[] args) {
       //创建一个对象数组
       Student[] students = new Student[] {
               new Student("张三", 90),
               new Student("李四", 86),
               new Student("王五", 91),
               new Student("赵六", 50),
       };
       Arrays.sort(students);
       System.out.println(Arrays.toString(students));
   }
}

此时运行就会抛异常,可见使用现成的sort方法是不可以的,比较对象的大小需要额外来指定,我们的Student类来实现Comparable接口,并实现其中的compareTo方法,例如:

class Student implements Comparable{
    private String name;
    private int score;
    public Student (String name,int score){
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "[" + this.name + ":" + this.score + "]";
    }

    @Override
    public int compareTo(Object o) {
        Student s = (Student) o;
        if(this.score > s.score){
            return -1;
        } else if(this.score < s.score){
            return 1;
        } else {
            return 0;
        }
    }
}
public class TestDemo {
   public static void main(String[] args) {
       //创建一个对象数组
       Student[] students = new Student[] {
               new Student("张三", 90),
               new Student("李四", 86),
               new Student("王五", 91),
               new Student("赵六", 50),
       };
       Arrays.sort(students);
       System.out.println(Arrays.toString(students));
   }
}

注意此时我们如果Student类实现Comparable< Student> 接口,此时的compareTo的参数就是Student类型的。
sort方法中会自动调用compareTo方法,它的参数是Object,其实传入的是Student对象,compareTo方法的对象大小关系为:
如果当前对象应排在参数对象之前, 返回小于 0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回0
此时的运行结果就是:
在这里插入图片描述
注意对于sort方法来讲,传入的对象都要是可比较的,要具有compareTo能力。
接口间的继承
接口可以继承一个接口,达到复用的作用,例如:

interface IRunning {
    void run();
}
interface ISwimming {
    void swim();
}
// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
    private String name;

    public Frog(String name){
        this.name = name;
    }
    @Override
    public void run(){
        System.out.println(this.name + "正在跑");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在游");
    }
}
public class TestDemo {
  public static void main(String[] args) {
      IRunning frog1 = new Frog("haha");
      frog1.run();
      ISwimming frog2 = new Frog("heihei");
      frog2.swim();
  }
}

此时的运行结果就是:
在这里插入图片描述
接口间的继承就相当于将多个接口合并在一起。
Clonable接口
Object类中存在一个clone方法,调用这个方法可以创建一个对象的拷贝,要合法使用clone接口,就要先实现Clonable接口,否则会跑出CloneNotSupportedException异常,例如实现一个Person类,要进行克隆这个人,实现:

class Person implements Cloneable{
    private String name;
    public Person clone(){
        Person p = null;
        try{
            p = (Person)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return p;
    }
}
public class TestDemo {
  public static void main(String[] args) {

      Person p1 = new Person();
      Person p2 = p1.clone();
      System.out.println(p1==p2);
  }
}

此时运行结果是false,当然我们可以直接写成:

class Person implements Cloneable{
    private String name;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class TestDemo {
  public static void main(String[] args) throws CloneNotSupportedException{
      Person p1 = new Person();
      Person p2 = (Person)p1.clone();//一定要转为Person类型,因此此时实现的clone方法返回的是Object类型
      System.out.println(p1.age);
      System.out.println(p2.age);
      //修改
      System.out.println("修改后");
      p2.age = 12;
      System.out.println(p1.age);
      System.out.println(p2.age);
  }
}

此时运行结果是:
在这里插入图片描述
可以发现p2是p1的克隆,改变p2的age并不会改变p1的age,也就是说通过 clone 拷贝出的 p2对象只是拷贝了 p1自身, 而没有拷贝p1内部包含的对象,也就是clone方法是一种浅拷贝。
又比如:

class Money{
    double money = 12.6;
}
class Person implements Cloneable{
    protected int age;
    Money m = new Money();//创建一个Money对象

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class TestDemo {
	public static void main(String[] args) throws CloneNotSupportedException{

      Person p1 = new Person();
      Person p2 = (Person)p1.clone();
      System.out.println(p1.m.money);
      System.out.println(p2.m.money);
      //修改
      System.out.println("修改后");
      p2.m.money = 99.9;
      System.out.println(p1.m.money);
      System.out.println(p2.m.money);
  }
}

此时的运行结果为:
在这里插入图片描述
可以发现p2克隆p1,对p2的m引用指向的money进行改变,p1的m引用指向的money也改变了,因为在Person类中创建m引用,创建p1对象,p2对象克隆p1,也会克隆p1中的m引用,p2中的m引用与p1中的m引用指向的内容是同一个,因此改变p2中m引用指向的money,p1中m引用指向的money也会改变。
如果也想将money实现浅拷贝,也就是改变p2中m引用指向的money不会改变p1中m引用指向的money,此时可以这样实现:

class Money implements Cloneable{
    double money = 12.6;

    //要将money的内容也克隆一份,首先先在Money实现克隆
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Person implements Cloneable{
    protected int age;
    Money m = new Money();//创建一个Money对象

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //这里实现先将p指向的内容克隆一份,然后将当前p对象指向的m引用的money克隆一份
        Person p = (Person) super.clone();
        p.m = (Money) this.m.clone();
        return p;
    }
}
public class TestDemo {
  public static void main(String[] args) throws CloneNotSupportedException{

      Person p1 = new Person();
      Person p2 = (Person)p1.clone();
      System.out.println(p1.m.money);
      System.out.println(p2.m.money);
      //修改
      System.out.println("修改后");
      p2.m.money = 99.9;
      System.out.println(p1.m.money);
      System.out.println(p2.m.money);
  }
}

此时运行结果就达到了我们想要的效果,如图:
在这里插入图片描述
改变p2的m引用指向的money不会改变p1的m引用指向的money,实现了m对象的浅拷贝。
总结
抽象类和接口的区别:
核心区别:是:抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值