草庐IT

Java中的多态

火木火木 2023-05-05 原文

一、多态定义及存在条件

1.1 多态的定义

多态是什么?

  • 多态是同一个行为具有不同的表现形式或形态的能力
  • 同一方法可以根据发送对象的不同而采用不同的行为方式

例如:打印机分为黑白打印机和彩色打印机,在黑白打印机情况下打出来为黑白,在彩色打印机情况下打印出来为彩色

 多态就是事物的多种形态,一个对象在不同条件下所表现的不同形式

1.2 多态的存在条件

多态存在的三个必要条件

  1. 继承或实现:在多态中必须存在有继承或实现关系的子类和父类
  2. 方法的重写:子类对父类中的某些方法进行重新定义(重写,使用@Override注解进行重写)
  3. 基类引用指向派生类对象,即父类引用指向子类对象,父类类型:指子类对象继承的父类类型,或实现的父接口类型

1.3 多态的格式

  • 父类类型  变量名 = new 子类类型();
  • 然后通过 变量名.方法名()调用在子类中重写的方法
  • 多态体现为父类引用变量可以指向子类对象:定义了一个父类类型的引用,指向新建的子类类型的对象,由于子类是继承他的父类的,所以父类类型的引用是可以指向子类类型的对象的

1.4多态中的成员特点

  • 多态成员变量:编译运行看左边

此处举例Animal是父类,Dog是子类

  • Animal dog = new Dog();   //Animal是引用类型,Dog是实际类型
  • System.out.println(dog.age) //dog的引用类型是Animal,所以取到的是父类Animal中的值,说白了dog是属于Animal类,Animal中变量的值是多少就通过对象就取得多少

父类Animal:

//父类
public class Animal {

    public int age = 11;
    
}

子类Dog:

//子类
public class Dog extends Animal {

    public int age = 33;
    
}

启动项:

public class DemoApplication {

    public static void main(String[] args) {

        //父类类型 对象 = new 子类类型()
        Animal dog = new Dog();
        System.out.println(dog.age);
    }
}

控制台打印输出:父类中定义的age 

11  
  • 多态成员方法:编译看左边,运行看右边

此处举例Animal是父类,Dog是子类

  • Animal  dog  =  new Dog();  //Animal是引用类型,Dog是实际类型
  • dog.eat();     //变量dog的实际类型是Dog,即是由Dog 这个实际类型new出来的,因此dog.eat() 调用的应该是子类Dog中重写的方法

父类Animal:

//父类
public class Animal {



    public void eat() {
        System.out.println("午餐吃狗粮");
    }

}

子类Dog:

//子类
public class Dog extends Animal {
    
    @Override
    public void eat() {
        System.out.println("晚餐吃狗粮");
    }
}

启动项:

public class DemoApplication {

    public static void main(String[] args) {

        //父类类型 对象 = new 子类类型()
        Animal dog = new Dog();

        dog.eat();
    }
}

控制台打印输出:调用的是子类中重写的方法

晚餐吃狗粮

1.5 重写方法的快捷键

在子类Dog中右键选择Generate

 选择Override Methods然后点击生成

 1.6 多态的特点

  1. 多态情况下,子类和父类存在同名的成员变量时,访问的时父类的成员变量
  2. 多态情况下,子父类存在同名的非静态成员方法时,访问的是子类中重写的方法
  3. 多态情况下,子父类存在同名的静态成员变量成员方法时,访问的是父类的成员函数
  4. 多态情况下,不能访问子类独由的方法

对于子类独有的方法,父类无法访问,

父类Animal保持不变:

//父类
public class Animal {
    
    public void eat() {
        System.out.println("午餐吃狗粮");
    }

}

子类Dog:增加子类读有的方法walk()

//子类
public class Dog extends Animal {

    
    public void walk(){
        System.out.println("子类独有的方法");
    }
    
    
    @Override
    public void eat() {
        System.out.println("晚餐吃狗粮");
    }
}

启动项:walk()方法爆红,即编译报错,

根据多态成员方法中编译看左边,运行看右边的原理

Animal  dog  =  new Dog();

可知 左边的Animal引用类型中没有walk()这个方法,故编译不通过,编译爆红


public class DemoApplication {

    public static void main(String[] args) {

        //父类类型 对象 = new 子类类型()
        Animal dog = new Dog();

        dog.eat();  //访问的是子类中重写的方法


        dog.walk();  //walk方法爆红,即编译报错,编译看左边,dog类的实例对象Animal没有walk()这个方法,所以编译报错


    }
}

那么想要直接访问子类独有的方法,该如何解决呢,由此引出了引用类型转换

二、引用类型转换

2.1 为什么需要引用类型转换

  • 上面的例子说明了,在多态情况下,使用Animal引用类型构建出来的对象dog无法访问子类Dog所独有的方法walk();强行调用时方法会爆红,编译出错,即我们所说的编译看左边,运行看右边
  • 而且我们在多态情况下调用方法时,首先会检查等式左边的引用类型(父类)中是否有该方法存在,如果父类中没有该方法,则编译器直接报错,也就代表着,父类无法调用子类独有的方法
  • 既然编译都出错了,更别说运行了,这也是多态所造成的,因此如果我们想要调用子类的方法,必须做到向下转型

2.2 向上转型(自动转换)

先了解什么向上转型(儿子变父亲)

向上转型:

 多态本身是子类向父类向上转换(自动转换)的过程,这个过程是默许的,当父类引用指向一个子类对象时,就是向上转型

父类引用指向子类对象:

Animal dog  = new Dog()

左边的Animal是引用类型,而dog是由右边的Dog实例对象new出来的,在上面这个等式中,左边的引用Animal指向了子类的对象dog,原本是子类对象的dog完成了向上转型

对于父类和子类的关系,直接用图来描述

 Animal父类是大范围的类型,而Cat和Dog类均属于动物类的子类,所以对于子类这种范围小的,我们可以自动转型给父类的变量,儿子向上转型,父亲是唯一的,因此是自动转换

使用格式:

父类类型  变量名  = new 子类类型();

Animal       dog     = new  Dog()

通过由实例变量Dog类new出来的变量dog作为中介,使得引用变量Animal有所指向,从而完成了向上转型

相当于是

Animal       dog     = (Animal) new  Dog()

2.3 向下转型(父亲变儿子,需要强制转换)

向上转型是一个子类变成父类的过程,下面介绍向下转型

向下转型:

向下转型是父类向子类转换的过程,这个过程需要强制转换(父亲变儿子肯定是需要条件的),一个可以将父类对象转换为子类对象,可以使用强制类型转换的格式,这便是向下转型

继续拿图说话

 

对于Dog、Cat这些子类来说,他们只是父类Animal的一部分,而对于父类来说。他拥有更多的子类 牛、羊等所以一旦父类要转换成子类,就必须指定要变成哪个子类,必须有指向性,所以向下转型才是强制转换

使用格式:

向上转型

父类类型  变量名  = new 子类类型();

Animal       dog     = new  Dog()

向下转型

子类类型 子类变量名 = (子类类型) 父类变量名

Dog dog1 = (Dog) dog;

dog1.walk; //此时可以使用子类独有的方法了

代码示例如下:

父类Animal:

//父类
public class Animal {

    public void eat() {
        System.out.println("午餐吃狗粮");
    }

}

子类Dog:包含有子类独有的方法walk()

//子类
public class Dog extends Animal {


    public void walk(){
        System.out.println("子类独有的方法");
    }


    @Override
    public void eat() {
        System.out.println("晚餐吃狗粮");
    }
}

启动项:

通过

Dog dog1 = (Dog) dog;完成向下转型

再利用向下转型成功的子类对象dog1调用子类中独有的方法walk()


public class DemoApplication {

    public static void main(String[] args) {

        //父类类型 对象 = new 子类类型()
        Animal dog = new Dog();
        //向下转型
        //子类类型 子类变量名 = (子类类型) 父类变量名
        Dog dog1 = (Dog) dog;

        dog.eat();  //访问的是子类中重写的方法

        //通过向下转型的子类对象调用子类独有的方法
        dog1.walk();  //walk方法爆红,即编译报错,编译看左边,dog类的实例对象Animal没有walk()这个方法,所以编译报错


    }
}

控制台打印输出:

晚餐吃狗粮
子类独有的方法

所以对于多态中,无法使用子类特有的方法也通过向下转型,将父类类型强制转换为某个子类类型后,再进行方法的调用

2.4 向下转型的问题

虽然可以通过向下转型可以调用子类独有的方法,但也会产生下面的问题

增加一个子类Cat类,

Cat类中有其独有的方法sleep()


//Cat类通过extends关键字继承父类Animal
public class Cat extends Animal {



    public void sleep(){
        System.out.println("Cat类独有的方法");
    }

    @Override
    public void eat() {
        System.out.println("晚餐吃猫粮");
    }
}

        

父类Animal:

//父类
public class Animal {

    public void eat() {
        System.out.println("午餐吃狗粮");
    }

}

子类Dog类:

//子类
public class Dog extends Animal {


    public void walk(){
        System.out.println("Dog类独有的方法");
    }


    @Override
    public void eat() {
        System.out.println("晚餐吃狗粮");
    }
}

启动项:

public class DemoApplication {

    public static void main(String[] args) {

        //向上转型
        //父类类型 对象 = new 子类类型()
        Animal cat = new Cat();
        //向下转型
        //子类类型 子类变量名 = (子类类型) 父类变量名
        Dog dog1 = (Dog) cat;


        //通过向下转型的子类对象调用子类独有的方法
        dog1.walk();  //walk方法爆红,即编译报错,编译看左边,dog类的实例对象Animal没有walk()这个方法,所以编译报错

    }
}

控制台打印输出:爆出异常ClassCastException,即类型转换异常

 分析:为什么会爆出类型转换异常

  • 因为 在启动项中,向上转型的过程,Animal cat = new Cat(); cat对象是由子类Cat构造出来的
  • 而向下转型的过程中 Dog dog1 = (Dog) cat; 却将其变成了Dog 类的对象,
  • Dog类和Cat类都是Animal类的儿子类 ,
  • 上面的步骤中的第二步将子类Cat的对象cat变成了兄弟类的对象dog,这就不是向下转型了,因此会报类型转换异常

那么如何避免这种异常呢 ,就需要使用instanceof关键字

2.5 instanceof关键字详解

Java为我们提供了一个关键字instanceof,它可以帮助我们避免出现ClassCastException这样的异常,

格式:

变量名  instanceof   数据类型

解释:

  • 如果变量属于该数据类型或者其子类型,返回true
  • 如果变量不属于该数据类或者其子类型,返回false

直接拿启动项来进行说明,

代码示例如下:


public class DemoApplication {

    public static void main(String[] args) {

        //向上转型
        //父类类型 对象 = new 子类类型()
        Animal animal = new Cat();


        //向下转型
        //子类类型 子类变量名 = (子类类型) 父类变量名
        if ( animal instanceof Cat){
            Cat cat = (Cat) animal;
            cat.sleep();
        }else if(animal instanceof Dog){
            Dog dog = (Dog) animal;
            dog.walk();
        }

    }
}

在进行了向上转型之后,在向下转型的过程中利用if语句来进行判断,而判断条件正是向上转型产生的变量与子类之间的关系

控制台打印输出:

Cat类独有的方法

有关Java中的多态的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  8. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  9. ruby - rspec 需要 .rspec 文件中的 spec_helper - 2

    我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只

  10. ruby-on-rails - active_admin 目录中的常量警告重新声明 - 2

    我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA

随机推荐