文章目录
一、引言
面向对象的三大特征:封装、继承、多态。从一定的角度上看,封装和继承几乎是为多态准备的。多态在Java技术里有很重要的地位,多态是Java学习过程中的一个重点,也是难点,多态的概念很抽象也很难理解。本文是在学习B站和CSDN中有关多态的介绍的基础上,进行了一些学习记录和个人总结。
本文主要分为三部分,第一部分是多态的概述,主要包括多态的概念、现实理解、成员访问特点、instanceof关键字和多态转型等内容;第二部分是为多态地存在条件和实现方法进行介绍,在这部分内容中,我通过几个简单的例子对多态的实现方法进行了介绍;第三部分内容主要是java中多态的意义。
二、多态概述
1.1概念
基类对象访问派生类重写的方法
多态是除继承和封装外面向对象编程的有一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
概念一:一种行为(方法)不同的子类有不同的实现
概念二:程序在运行期间动态的创建对象,然后调用方法
以形状和图形举例对概念二进行说明:
1.引例:一个父类(形状),多个子类(圆形,方形,三角形),用户输入1创建圆形,输入2创建方形,输入3创建三角形。最后调用形状分绘画方法。
2.步骤:
- 定义父类,定义成员方法draw()
- 定义子类,覆盖父类的成员方法
- 定义一个形状工厂类,根据用户输入的数字创建对应的形状
- 调用形状的绘画方法
3.具体代码:
//创建Shape01类
public class Shape01 {
/**
* 父类只定义做什么,如何做(绘画)全部下沉到子类去实现,此时父类定义的draw()是一个哑巴方法
*/
public void draw(){}
}
//创建Circle类
class Circle extends Shape{
public void draw(){
System.out.println("使用圆规绘画圆形.........");
}
}
//创建Square类
class Square extends Shape{
public void draw(){
System.out.println("使用橡皮尺绘画方形");
}
}
//创建Triangle类
class Triangle extends Shape{
public void draw(){
System.out.println("使用三角板绘画三角形");
}
}
public class Shape01factory {
/**
* 创建形状:只向外界暴露接口(方法),如何创建产品(形状)向外界隐藏了
* @param index 客户(外界)输入的数字
* @return 返回对应的产品(形状)
*/
public static Shape createShape(int index){
Shape sh = null;
switch (index){
case 1:
sh = new Circle();
break;
case 2:
sh = new Square();
break;
case 3:
sh = new Triangle();
break;
default:
return null;
}
return sh;
}
}
1.2多态现实理解
现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学李四既是学生也是人,即出现两种形态。再比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。
Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
1.3多态中成员访问的特点
- 成员变量:编译看父类,运行看父类
- 成员方法:编译看父类,运行看子类
1.4instanceof关键字
Instanceof关键字的作用是用来判断某个对象是否属于某种类型数据(注意:返回类型为布尔值)
1.5Object类
Object是所有类型的根(父类),如果某个类没有指定父类,那么Object类将是该类的父类。Object类型类似于一个班上的“班主任”,所有类型类似于班上所有的学生。
场景:定义一个Student类型,有姓名、年龄、ID(学生编号),每个属性有对应的get/set方法,由于它继承了Object类型,所以可以调用父类的toString()方法
public class student04 {
private String stuName;
private int stuAge;
private int id;
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public int getStuAge() {
return stuAge;
}
public void setStuAge(int stuAge) {
this.stuAge = stuAge;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"stuName='" + stuName + '\'' +
", stuAge=" + stuAge +
", id=" + id +
'}';
}
}
public class student04Test {
public static void main(String[] args) {
student04 tom = new student04();
tom.setId(1010101);
tom.setStuAge(19);
tom.setStuName("Tom");
// 调用toString()方法,返回Student类型表示的字符串(将Student类型转换为字符串)
// 打印结果:com.whsxt.day5.object.Student@4554617c
// 缺点:显示的字符串不友好
// 不友好的原因:子类此时调用了父类的toString()方法,但是没有覆写父类的toString()
/**
* public String toString() {
* return getClass().getName() + "@" + Integer.toHexString(hashCode());
* }
* 以上就是Object类型toString()方法的源码
* getClass() 获取Student.class的字节码对象
* getName()获取字节码对象的名称(包名称+类名称) com.whsxt.day5.object.Student
* Integer.toHexString(hashCode()) 以十六进制的方式显示Student类型对象的哈希值
* 如何解决不友好的打印?Student类型需要覆写Object类型的toString()方法
*/
String result =tom.toString();
System.out.println(result);
}
}
//运行结果:
Student{stuName='Tom', stuAge=19, id=1010101}
1.6多态的转型
- 向上转型
使用格式:父类类型 变量名=new 子类类型();
适用场景:当不需要面对子类类型时,通过提高扩展性,或者;使用父类的功能就能完成相应的操作。 - 向下转型
使用格式:子类类型 变量名=(子类类型)父类类型的变量;
适用场景:当要使用子类特有功能时。
二、多态的定义及实现
2.1多态存在的三个必要条件
2.1.1必须有继承
继承,父类和子类的一种关系,父类在上,子类在下,所以,继承也是一种垂直性的关系,通常用父类来定义共性行为,子类一旦继承了父类,就可以使用父类的行为,最终达到代码的复用。继承关键字是extends,一个子类最多只能有一个父类。
2.1.2有父类的引用指向子类的对象(向上转型)
(1)基本类型的向上转型
低bit类型自动转换为高bit类型。
public class test1{
public static void main(String[] args) {
long num = no;
int num1=1,num2=2;
long result = add(num1,num2);
System.out.println(result);
}
public static long add(long first,long second){
return first+second;
}
}
在本例中num1和num2都是int类型,他们作为实参传递给形参first和second,此时int类型的num自动向上转型为long类型,所以我们可以得出结论:向上转型是安全的。
(2)引用类型的向上转型
某个类型可以看做他自己也可以当做他的父类型。例如:圆形是一个形状,那么,圆形可以看做是圆形也可以看做是形状,但是反过来不行,形状不能看做是圆形,那么可以理解为圆形可以向上转型为形状)。
- 引用类型的赋值向上转型
public class Shape {
}
class Circle extends Shape {
}
public class Shapetest {
public static void main(String[] args) {
// long num = 10;
/**
* new Circle()创建了圆形对象Circle
* 此时Circle可以当做圆形对象,也可以当做形状对象(Shape),圆形是一个形状
* 此时我定义的sh,编译期是Shape类型,运行期是指向Circle()类型的对象
* 引用类型有两种状态:
* 1 编译期状态: 引用指向父类型(Shape)
* 2 运行期状态: 引用指向子类型(Circle)
* 编译看左边类型,运行看右边类型
*/
System.out.println("start");
Shape sh = new Circle();
System.out.println(sh);
System.out.println("end");
}
}
- 引用类型的参数向上转型
形参:通常是高类型(父类) Shape
实参:通常是低类型(子类) Circle
public class Shape {
}
class Circle extends Shape {
}
//在纸上绘画形状
public class Paper {
/**
* 绘画形状
* @param sh 编译期sh是父类Shape,运行期sh可以是一个子类的对象
*
*/
public static void doDraw(Shape sh){
System.out.println(sh);
}
public static void main(String[] args) {
// 创建子类对象
Circle circle = new Circle();
doDraw(circle);
}
}
- 返回类型的向上转型
public class Shape {
}
class Circle extends Shape {
}
//形状工厂负责创建形状
public class Shapefactory {
/**
* 将返回类型向上转型
* 编译期:返回Shape,运行期由于创建了Circle对象,它是Shape的子类,所以向上转型为Shape
* 此时 Circle可以当做圆形类型的对象,也可以当做形状类型的对象,圆形是一个形状
* @return 形状
*/
public static Shape create(){
// Shape sh = new Circle();
return new Circle();
}
// public static Shape create(){
// // 创建圆形对象
// Circle circle = new Circle();
// // 返回圆形对象
// return circle;
// }
}
public class Shapetset {
public static void main(String[] args) {
Shape shape = Shapefactory.create();
System.out.println(shape);
}
}
2.1.3必须有子类重写父类的方法
以圆形(子类)和形状(父类)来举例说明子类重写父类的方法:
public class Shape02 {
public void draw(){}
}
//Circle类
//圆形继承形状,具有绘画的行为
class Circle extends Shape02{
public void draw(){
System.out.println("使用圆规绘画圆形........");
}
}
//Square类
// 正方形继承形状,具有绘画的行为
class Square extends Shape02{
public void draw(){
System.out.println("使用橡皮尺绘画正方形");
}
}
/**
* 测试形状:
* 1 创建圆形
* 2 创建正方形
* 3 调用子类的绘画行为
*
*/
public class Shape02Test {
public static void main(String[] args) {
Shape02 shape = new Circle();
// 向上转型:编译sh是Shape类型,运行期指向了Square类型的对象
Shape02 sh = new Square();
// 绘画圆形
shape.draw();
// 绘画正方形
// 编译期draw()方法是父类Shape的成员方法,运行期是子类Square的成员方法
// 此时子类覆写了父类的draw()方法
/**
* draw()行为(方法):在不同的子类有不同的实现,此时子类覆写父类的方法了
*/
sh.draw();
}
}
2.2多态实现的方式
2.2.1用方法重写实现多态
(1)方法重写概述
子类跟据需求对从父类继承的方法进行重新编写,重写时,可以用super.方法的方式来保留父类的方法,构造方法不能被重写。
(2)方法重写规则
- 方法名相同
- 参数列表相同
- 返回值类型相同或者是父类返回值类型的子类
- 访问权限不能严于父类
- 父类的静态方法不能被子类覆盖为非静态方法,父类的非静态方法不能被子类覆盖为静态方法
- 子类可以定义与父类同名的静态方法,以便在子类中隐藏父类的静态方法(注意:静态方法中无法使用super)
- 父类的私有方法不能被子类覆盖
- 不能抛出比父类方法更多的异常
(3)用方法重写实现多态案例
1.引例:苹果手机类有功能-发短信(速度一般);三星手机类有功能-发信息(速度快);小米手机类有功能-发短信(速度慢);建立人类:数据-名字;功能-用手机发短信(发短信过程:①人拿着手机编写短信②手机发短信③人放下手机。其中,手机可以是三星,苹果,小米)。
需求:(1)测试类中提供一个方法,根据参数三星、苹果、小米返回对应的手机对象。(2)熊大用三星手机发短信,熊二用苹果手机发短信。
2.具体代码:
public class test {
public static void main(String[] args) {
cellPhone p1=getcellphone("三星");
Person c1=new Person("熊大");
c1.usecellPhoneMessage(p1);
cellPhone p2=getcellphone("苹果");
Person c2=new Person("熊二");
c2.usecellPhoneMessage(p2);
}
public static cellPhone getcellphone(String type){
if("三星".equals(type)){
return new SanXingPhone();
}
if("苹果".equals(type)){
return new ApplePhone();
}
if("小米".equals(type)){
return new XiaoMiPhone();
}
return null;
}
}
class cellPhone{
public void sendMessgage(){
System.out.println("手机可以发短信,速度不知道如何");
}
}
class ApplePhone extends cellPhone{
public void sendMessgage(){
System.out.println("苹果手机发短信,速度一般");
}
}
class SanXingPhone extends cellPhone{
public void sendMessgage(){
System.out.println("三星手机发短信,速度快");
}
}
class XiaoMiPhone extends cellPhone{
public void sendMessgage(){
System.out.println("小米手机发短信,速度慢");
}
}
class Person{
String name;
public Person(String name){
super();
this.name=name;
}
public void usecellPhoneMessage(cellPhone ce){
System.out.println(this.name+"拿着手机发短信");
ce.sendMessgage();
System.out.println(this.name+"放下手机");
}
}
//运行结果:
熊大放下手机
熊二拿着手机发短信
苹果手机发短信,速度一般
熊二放下手机
2.2.2用接口实现多态
(1)接口概念
对象之间交换数据的一个协议。接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用。Java中的接口更多的体现在对行为的抽象。
(2)接口的现实理解
生活中的接口,插排。插排定义了一系列的规则:两眼和三眼插口,每个插口都应一个宽度和高度,定义了转动行为。电风扇,空调等电器如果想转动就必须实现插排定义的规则(插口的宽度和高度)。插排只定义了规则,他并不关心到底是电视机还是电风扇插在里面
(3)接口的特点
- 接口用关键字interface修饰
- 类实现接口用implements表示 接口不能实例化
- 接口如何实例化呢?参照多态的方式,通过实现类对象实例化,这叫接口多态。
- 接口的子类,要么重写接口中的所有抽象方法,要么子类也是抽象类
(4)接口的成员特点
- 成员特点:成员变量只能是常量,默认修饰符为:public static final
- 构造方法:没有,因为接口主要是扩展功能的,而没有具体存在
- 成员方法: 只能是抽象方法,默认修饰符:public abstract
(5)接口的语法
Java中使用interface关键字定义接口
public interface <接口名称>{
常量
方法声明
}
(6)用接口实现多态的案例
1.引例:分析笔记本类,实现笔记本使用USB鼠标,USB键盘,USB接口,打开设备功能,关闭设备功能。笔记本类,包括开机功能、关机功能、使用USB设备功能;鼠标类,实现USB接口,具备点击的方法;键盘类,实现USB接口,具备敲击的方法。
2.具体代码:
package jiekou;
public class Computer {
public void powerOn() {
System.out.println("笔记本电脑开机");
}
public void powerOff() {
System.out.println("笔记本电脑关机");
}
//使用USB设备的方法,使用接口作为方法的参数
public void useDevice(USB usb) {
usb.open();//打开设备
if (usb instanceof Mouse) {//一定要先判断
Mouse mouse = (Mouse) usb;//向下转型
mouse.click();
} else if (usb instanceof Keyboard) {//先判断
Keyboard keyboard = (Keyboard) usb;//向下转型
keyboard.type();
}
usb.close();//关闭设备
}
}
public class DemoMain {
public static void main(String[] args) {
//创建一个笔记本类
Computer computer = new Computer();
computer.powerOn();//笔记本开机
//准备一个鼠标类,供电脑使用
//Mouse mouse = new Mouse();
//首先进行向上转型:先当做USB
//左边是接口,右边是实现类
USB usb = new Mouse();
//参数是usb类型,我正好传递进去的就是usb鼠标
computer.useDevice(usb);
//创建一个USB键盘
Keyboard keyboard = new Keyboard();
//方法参数是USB类型,传递进去的是实现类对象
computer.useDevice(keyboard);//正确写法
//使用子类对象,匿名对象,也可以
// computer.useDevice(new Keyboard());//也是正确写法!
computer.powerOff();//笔记本关机
System.out.println("=============");
method(30.0);//正确写法,double -- > double
method(30); //正确写法,int -- > double
int a = 30;
method(a); //正确写法,int -- > double
}
public static void method(double num) {
System.out.println(num);
}
}
//这句话的意思是:
//键盘就是一个USB设备
public class Keyboard implements USB{
@Override
public void open() {
System.out.println("打开键盘");
}
@Override
public void close() {
System.out.println("关闭键盘");
}
public void type() {
System.out.println("键盘输入");
}
}
//这句话的意思是:
//鼠标就是一个USB设备
public class Mouse implements USB{
@Override
public void open() {
System.out.println("打开鼠标");
}
@Override
public void close() {
System.out.println("关闭鼠标");
}
public void click() {
System.out.println("点击鼠标");
}
}
public interface USB {
public abstract void open(); //打开设备
public abstract void close(); //关闭设备
}
//运行结果:
=============
30.0
30.0
30.0
2.2.3用抽象类实现
(1)抽象类的概念
在java中不能使用new关键词创建对象的类叫做抽象类,例如:形状(shape)。
(2)抽象类的特点
- 定义抽象类的关键字:abstract。例如:public abstract shape{}或者abstract public shape{}
- 抽象类主要定于定义共性的行为,或者说定义不变的行为。例如,每个形状都具备绘画的功能,绘画在抽象类中表示不变的行为。
具体如何画下沉到子类。所以抽象类(shape)通常作为具体类(circle)的父类,具体类作为子类去覆写抽象中的抽象方法。 - 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 抽象类不能实例化,抽象类如何实例化呢?参照多态的方式,通过子类对象实例化,这叫抽象类多态
package absrtracts;
public class shapetest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//编译错误:抽象类不能实例化
new shape();
}
}
- 抽象类的子类要么重写抽象类中的所有抽象方法,要么是抽象类
- 场景:定义一个抽象类Shape
/** abstract是Java的一个关键字也是一个修饰符,可以修饰类和方法,此时Shape表示一个抽象类
**/
abstract public class
{
/*
形状的绘画方法是一个哑巴方法,只有方法定义没有方法实现
抽象类通常把所有的哑巴方法定义为抽象方法,让子类去覆写
*/
abstract public void draw();
}
(3)抽象类的成员特点
- 成员的特点:成员变量既可以是变量也可以是常量
- 构造方法:有参构造;无参构造
- 成员方法:抽象方法;普通方法
(4)用抽象类实现多态案例
1.引例:圆形和矩形都有一个共同的父类—形状,但形状却是抽象的,我们无法计算它的面积和周长,所以必须写成抽象类。而圆形和矩形,它们计算面积和周长的方法也是不同的。这样,我们就必须对形状这个抽象类中,计算面积和周长的方法进行重写。
2.具体代码:
public abstract class Shape {
public int width;//几何图形的长
public int height;//几何图像的宽
public Shape(int width,int height) {
this.width=width;
this.height=height;
}
public abstract double area();//定义抽象方法,计算面积
}
//定义一个Square类
class Square extends Shape {
public Square(int width,int height){
super(width,height);
}
//重写父类中的抽象方法,实现计算正方向面积的功能
public double area(){
return width*height;
}
}
//定义一个Triangle类
class Triangle extends Shape {
public Triangle(int width,int height){
super(width,height);
}
//重写父类中的抽象方法,实现计算三角形面积的功能
public double area(){
return 0.5*width*height;
}
}
public class ShapeTest {
public static void main(String[] args) {
Shape square=new Square(5,4);//创建正方形类对象
System.out.println("正方形的面积为:"+square.area());
Triangle triangle= new Triangle(2,5);//创建三角形类对象
System.out.println("三角形的面积为:"+triangle.area());
}
}
//运行结果:
正方形的面积为:20.0
三角形的面积为:5.0
三、java中多态的意义
3.1使用多态的好处
- 可替换性。多态对已存在代码具有可替换性,。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
- 可扩充性。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
- 接口性。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。
- 灵活性。它在应用中体现了灵活多样的操作,提高了使用效率。
- 简化性。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
3.2多态的作用
- 多态用于形参类型的时候,可以接收更多类型的数据
- 多态用于返回值类型的时候,可以返回更多类型的数据
3.3多态的弊端
不能使用子类特有的属性和行为。
参考文献
https://www.bilibili.com/video/BV1N5411n7rq?from=search&seid=12434283770858841850
https://www.bilibili.com/video/BV1Lo4y1m7r8?from=search&seid=12434283770858841850
https://blog.csdn.net/qq_44392499/article/details/114481630
https://blog.csdn.net/weixin_30795127/article/details/97976614
https://blog.csdn.net/AI_drag0n/article/details/84891509
https://blog.csdn.net/qq_41679818/article/details/90523285
https://blog.csdn.net/NYfor2017/article/details/104704516/
https://blog.csdn.net/weixin_40244153/article/details/82348763
https://blog.csdn.net/qq_19782019/article/details/79788326
https://www.bilibili.com/video/BV19o4y1d7Ru?from=search&seid=17728006898209242763