java第三次博客

一、引言

  面向对象的三大特征:封装、继承、多态。从一定的角度上看,封装和继承几乎是为多态准备的。多态在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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值