Java面向对象之包、访问修饰符、封装及继承及多态

1、包

1.1、需求引出

在这里插入图片描述

1.2、包的三大作用
  1. 区分相同名字的类
  2. 当类很多时,可以很好的管理类【看Java API文档】
  3. 控制访问范围
1.3、基本语法
package com.helloedu;

说明:
1package: 关键字, 表示打包
2、com.helloedu: 表示包名
1.4、包的本质分析

在这里插入图片描述
在这里插入图片描述

1.5、包的命名

只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
demo.class.exec1 // 错误 class是关键字
demo.12a // 错误 12a是数字开头
demo.ab12.oa // 对
命名规范:
一般是小写字母+小圆点
例如:com.xxx.oa.model;
com.xxx.oa.controller;
举例:
com.sina.crm.user; // 用户模块
com.sina.crm.order; // 订单模块
com.sina.crm.utils; // 工具类

1.6、常用的包

一个包下,包含很多的类,java 中常用的包有:

  1. java.lang.* // lang 包是基本包,默认引入,不需要再引入
  2. java.util.* // util 包,系统提供的工具包,工具类,使用 Scanner
  3. java.net.* // 网络包,网络开发
  4. java.awt.* // 是做 java 的界面开发,GUI
1.7、如何引入包

在这里插入图片描述

// 一个类中最多只有一句 package
package com.use_;

// 建议: 我们需要使用到哪个类, 就导入哪个类即可, 不建议使用 * 导入
// import 指令位置放在 package 的下面, 在类定义前面, 可以有多句且没有顺序要求
import java.util.Arrays;  // 表示只会引入 java.util 包下的 Arrays
import java.util.*;  // 表示将 java.util 包下的所有类都引入(导入)

public class Sort_ {
    public static void main(String[] args) {
        int[] arrList = {1, -2, 99, -105, 82, 30, -3, 0, 5, 29};
        // 系统提供了相关的类, 可以方便完成排序 (Arrays 类)
        Arrays.sort(arrList);
        for (int i = 0; i < arrList.length; i++) {
            System.out.print(arrList[i] + "\t");
        }
    }
}
1.8、注意事项和使用细节

在这里插入图片描述

2、访问修饰符

2.1、定义

java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):

  1. 公开级别:用 public 修饰,对外公开
  2. 受保护级别:用 protected 修饰,对子类和同一个包中的类公开
  3. 默认级别:没有修饰符号,向同一个包的类公开
  4. 私有级别:用 private 修饰,只有类本身可以访问,不对外公开
2.2、4 种访问修饰符的访问范围

在这里插入图片描述

2.3、使用的注意事项
  1. 修饰符可以用来修饰类中的属性,成员方法及类
  2. 只有默认的和public才能修饰类,并且遵循上述访问权限的特点
  3. 成员方法的访问规则和属性完全一样
2.4、代码实现

在这里插入图片描述
TestClass.java

package access;

import access1.TestClass3;
import access1.TestClass4;

public class TestClass {
    public int n1 = 10;
    protected int n2 = 20;
    int n3 = 30;
    private int n4 = 40;

    public void m1() {
        System.out.println("TestClass中 public m1 方法");
    }

    protected void m2() {
        System.out.println("TestClass中 protected m2 方法");
    }

    void m3() {
        System.out.println("TestClass中 默认 m3 方法");
    }

    private void m4() {
        System.out.println("TestClass中 private m4 方法");
    }

    public static void main(String[] args) {
        // 在同类中测试
        // 结论: 所有属性和方法都能正常访问
        TestClass testClass = new TestClass();
        System.out.println(testClass.n1);
        System.out.println(testClass.n2);
        System.out.println(testClass.n3);
        System.out.println(testClass.n4);
        testClass.m1();
        testClass.m2();
        testClass.m3();
        testClass.m4();
        System.out.println("=====");

        // 在同一个包中, 不同的类中测试 测试 TestClass1 类
        // 结论: private 属性和方法不能正常访问
        TestClass1 testClass1 = new TestClass1();
        System.out.println(testClass1.h1);
        System.out.println(testClass1.h2);
        System.out.println(testClass1.h3);
        // System.out.println(testClass1.h4);  // 报错
        testClass1.q1();
        testClass1.q2();
        testClass1.q3();
        // testClass1.q4();  // 报错
        System.out.println("=====");

        // 在同一个包中, 子类继承父类 测试 测试 TestClass2 类
        // 结论: private 属性和方法不能正常访问
        TestClass2 testClass2 = new TestClass2();
        System.out.println(testClass2.h1);
        System.out.println(testClass2.h2);
        System.out.println(testClass2.h3);
        // System.out.println(testClass2.h4);  // 报错
        testClass2.q1();
        testClass2.q2();
        testClass2.q3();
        // testClass2.q4();  // 报错
        System.out.println("=====");

        // 在不同的包中, 子类继承父类 测试 测试 TestClass3 类
        // 结论: 默认和private 属性和方法不能正常访问
        TestClass3 testClass3 = new TestClass3();
        System.out.println(testClass3.n1);
        System.out.println(testClass3.n2);
        // System.out.println(testClass3.n3);  // 错误
        // System.out.println(testClass3.n4);  // 错误
        testClass3.m1();
        testClass3.m2();
        // testClass3.m3();  // 错误
        // testClass3.m4();  // 错误
        System.out.println("=====");

        // 在不同的包中, 测试 TestClass4 类
        // 结论: protected、默认private 属性和方法不能正常访问, 只有公开类的属性和方法可以正常访问
        TestClass4 testClass4 = new TestClass4();
        System.out.println(testClass4.v1);
        // System.out.println(testClass4.v2);  // 报错
        // System.out.println(testClass4.v3);  // 报错
        // System.out.println(testClass4.v4);  // 报错
        testClass4.c1();
        // testClass4.c2();  // 报错
        // testClass4.c3();  // 报错
        // testClass4.c4();  // 报错
    }
}

TestClass1.java

package access;

// 在同一个包中, 不同的类中测试
public class TestClass1 {
    public int h1 = 100;
    protected int h2 = 200;
    int h3 = 300;
    private int h4 = 400;

    public void q1() {
        System.out.println("TestClass1中 public q1 方法");
    }

    protected void q2() {
        System.out.println("TestClass1中 protected q2 方法");
    }

    void q3() {
        System.out.println("TestClass1中 默认 q3 方法");
    }

    private void q4() {
        System.out.println("TestClass1中 private q4 方法");
    }
}

TestClass2.java

package access;

public class TestClass2 extends TestClass1{
}

TestClass3.java

package access1;

import access.TestClass;

public class TestClass3 extends TestClass {
}

TestClass4.java

package access1;

public class TestClass4 {
    public int v1 = 1000;
    protected int v2 = 2000;
    int v3 = 3000;
    private int v4 = 4000;

    public void c1() {
        System.out.println("TestClass4中 public c1 方法");
    }

    protected void c2() {
        System.out.println("TestClass4中 protected c2 方法");
    }

    void c3() {
        System.out.println("TestClass4中 默认 c3 方法");
    }

    private void c4() {
        System.out.println("TestClass4中 private c4 方法");
    }
}

3、面向对象编程三大特征

封装、继承和多态

3.1、封装
3.1.1、定义

在这里插入图片描述

3.1.2、封装的优势

在这里插入图片描述

3.1.3、封装的实现步骤

在这里插入图片描述

public class Encapsulation {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("张三");
        person.setAge(150);  // 年龄150不在范围内, 用默认值 18
        person.setSalary(1000.98);
        System.out.println(person.info());
    }
}

// 不能随便查看人的年龄, 工资等隐私, 并对设置的年龄进行合理的验证
// 年龄合理就设置, 否则给默认年龄 18, 年龄必须在 1-120, 工资不能直接查看, name 的长度在 2-6 字符之间
class Person {
    public String name;
    private int age;
    private double salary;

    public Person() {

    }

    public Person(String name, int age, double salary) {
        // 我们可以将 set 方法写在构造器中, 这样仍然可以验证
        setName(name);
        setAge(age);
        setSalary(salary);
    }

    //自己写 setXxx 和 getXxx 太慢, 我们使用快捷键
    // 然后根据要求来完善我们的代码
    // 加入对数据的校验, 相当于增加了业务逻辑
    public String getName() {
        return name;
    }

    public void setName(String name) {
        if (name.length() >= 2 && name.length() <= 6) {
            this.name = name;
        } else {
            System.out.println("名字的长度不对, 需要(2-6)个字符, 默认名字");
            this.name = "无名";
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {  // 如果是合理范围
        if (age >= 1 && age <= 120) {
            this.age = age;
        } else {
            System.out.println("你设置年龄不对, 需要在 (1-120), 给默认年龄 18");
            this.age = 18;  // 给一个默认年龄
        }
    }

    public double getSalary() {
        // 可以这里增加对当前对象的权限判断
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    // 写一个方法, 返回属性信息
    public String info() {
        return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
    }
}
3.1.4、将构造器和 setXxx 结合
public Person(String name, int age, double salary) {
    // 我们可以将 set 方法写在构造器中, 这样仍然可以验证
    setName(name);
    setAge(age);
    setSalary(salary);
}
3.2、继承
3.2.1、定义

继承可以解决代码复用, 让我们的编程更加靠近人类思维, 当多个类存在相同的属性(变量)和方法时, 可以从这些类中抽象出父类, 在父类中定义这些相同的属性和方法, 所有的子类不需要重新定义这些属性和方法, 只需要通过 extends 来 声明继承父类即可
在这里插入图片描述

3.2.2、定义继承的基本语法

在这里插入图片描述

3.2.3、代码实现

Extends.java

package com.jm;

public class Extends {
    public static void main(String[] args) {
        Pupil pupil = new Pupil();
        pupil.name = "张三";
        pupil.age = 18;
        pupil.setScore(95);
        pupil.testing();
        pupil.showInfo();
        System.out.println("=====");
        Graduate graduate = new Graduate();
        graduate.name = "李四";
        graduate.age = 20;
        graduate.setScore(100);
        graduate.testing();
        graduate.showInfo();

    }
}

// 父类, 是 Pupil 和 Graduate 的父类
class Student {
    // 共有属性
    public String name;
    public int age;
    private double score;//成绩

    // 共有的方法
    public void setScore(double score) {
        this.score = score;
    }

    public void showInfo() {
        System.out.println("学生名 " + name + " 年龄 " + age + " 成绩 " + score);
    }
}

Pupil.java

package com.jm;

public class Pupil extends Student {  // 子类Pupil 继承父类Student 
    public void testing() {
        System.out.println("小学生 " + name + " 正在考小学数学..");
    }
}

Graduate.java

package com.jm;

public class Graduate extends Student {  // 子类Graduate 继承父类Student 
    public void testing() {
        System.out.println("大学生 " + name + " 正在考大学数学..");
    }
}
3.2.4、继承给编程带来的便利
  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了
3.2.5、继承的细节问题
  1. 子类继承了所有的属性和方法, 非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问, 要通过父类提供公共的方法去访问
  2. 子类必须调用父类的构造器, 完成父类的初始化
  3. 当创建子类对象时, 不管使用子类的哪个构造器, 默认情况下总会去调用父类的无参构造器, 如果父类没有提供无参构造器, 则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作, 否则, 编译不会通过
  4. 如果希望指定去调用父类的某个构造器, 则显式的调用一下:super(参数列表)
  5. super 在使用时, 必须放在构造器第一行(super 只能在构造器中使用)
  6. super() 和 this() 都只能放在构造器第一行, 因此这两个方法不能共存在一个构造器
  7. java 所有类都是 Object 类的子类, Object 是所有类的基类.
  8. 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
  9. 子类最多只能继承一个父类(指直接继承), 即 java 中是单继承机制
  10. 不能滥用继承, 子类和父类之间必须满足 is-a 的逻辑关系

ExtendsDetail.java

package com.jm;

public class ExtendsDetail {
    public static void main(String[] args) {
        System.out.println("===第 1 个对象====");
        Sub sub = new Sub();  // 创建了子类对象 sub
        /* 输出分析:
        1 构造器 TopBase() 被调用...
        2 父类 Base(String name, int age)构造器被调用....
        3 子类 Sub()构造器被调用....
        */

        System.out.println("===第 2 个对象====");
        Sub sub2 = new Sub("jack");  // 创建了子类对象 sub2
        /*
        1 构造器 TopBase() 被调用...
        2 父类 Base(String name, int age)构造器被调用....
        3 子类 Sub(String name)构造器被调用....
        */


        System.out.println("===第 3 对象====");
        Sub sub3 = new Sub("king", 10);  // 创建了子类对象 sub2
        sub.sayOk();
        /*
        1 构造器 TopBase() 被调用...
        2 父类 Base(String name, int age)构造器被调用....
        3 子类 Sub(String name, int age)构造器被调用....
        4 100  200  300
        5 test100
        6 test200
        7 test300
        8 n4=400
        9 test400
        */
    }
}

TopBase.java

package com.jm;

public class TopBase {
    public TopBase() {
        super();  // Object 的无参构造器
        System.out.println("构造器 TopBase() 被调用...");
    }
}

Base.java

package com.jm;

public class Base extends TopBase {  //父类
    // 4 个属性
    public int n1 = 100;
    protected int n2 = 200;
    int n3 = 300;
    private int n4 = 400;

    public Base() {
        // 无参构造器
        System.out.println("父类 Base()构造器被调用....");
    }

    public Base(String name, int age) {  // 有参构造器
        // 默认 super()
        System.out.println("父类 Base(String name, int age)构造器被调用....");
    }

    public Base(String name) {  // 有参构造器
        System.out.println("父类 Base(String name)构造器被调用....");
    }

    //父类提供一个 public 的方法, 返回了 n4
    public int getN4() {
        return n4;
    }

    public void test100() {
        System.out.println("test100");
    }

    protected void test200() {
        System.out.println("test200");
    }

    void test300() {
        System.out.println("test300");
    }

    private void test400() {
        System.out.println("test400");
    }

    // call
    public void callTest400() {
        test400();
    }
}

Sub.java

package com.jm;

// 输入 ctrl + H 可以看到类的继承关系
public class Sub extends Base {  // 子类
    public Sub(String name, int age) {
        // 1. 调用父类的无参构造器, 如下或者什么都不写, 默认就是调用super()
        // super();  // 父类的无参构造器
        // 2. 调用父类的 Base(String name) 构造器
        // super("tom");
        // 3. 调用父类的 Base(String name, int age) 构造器
        super("king", 20);
        // 细节: super 在使用时, 必须放在构造器第一行
        // 细节: super() 和 this() 都只能放在构造器第一行, 因此这两个方法不能共存在一个构造器
        // this() 不能再使用了
        System.out.println("子类 Sub(String name, int age)构造器被调用....");
    }

    public Sub() {  // 无参构造器
        // super();  // 默认调用父类的无参构造器
        super("smith", 10);
        System.out.println("子类 Sub()构造器被调用....");
    }

    // 当创建子类对象时, 不管使用子类的哪个构造器, 默认情况下总会去调用父类的无参构造器
    public Sub(String name) {
        super("tom", 30);
        // do nothing...
        System.out.println("子类 Sub(String name)构造器被调用....");
    }

    public void sayOk() {
        // 子类方法
        // 非私有的属性和方法可以在子类直接访问
        // 但是私有属性和方法不能在子类直接访问
        System.out.println(n1 + " " + n2 + " " + n3);
        test100();
        test200();
        test300();
        // test400();  错误
        // 要通过父类提供公共的方法去访问
        System.out.println("n4=" + getN4());
        callTest400();
    }
}
3.2.6、继承的本质分析

当子类对象创建好后,建立查找的关系

ExtendsTheory.java

package com.jm;

public class ExtendsTheory {
    public static void main(String[] args) {
        Son son = new Son();  // 内存的布局
        // 要按照查找关系来返回信息
        // (1) 首先看子类是否有该属性
        // (2) 如果子类有这个属性, 并且可以访问, 则返回信息
        // (3) 如果子类没有这个属性, 就看父类有没有这个属性(如果父类有该属性, 并且可以访问, 就返回信息..)
        // (4) 如果父类没有就按照(3)的规则, 继续找上级父类, 直到 Object...
        System.out.println(son.name);  // 返回就是大头儿子
        // System.out.println(son.age);  // 报错
        System.out.println(son.getAge());  // 返回的就是 39
        System.out.println(son.hobby);  // 返回的就是旅游
    }
}

class GrandPa {  // 爷类
    String name = "大头爷爷";
    String hobby = "旅游";
}

class Father extends GrandPa {  //父类
    String name = "大头爸爸";
    private int age = 39;

    public int getAge() {
        return age;
    }
}

class Son extends Father {  //子类
    String name = "大头儿子";
}

子类创建的内存布局
在这里插入图片描述

3.2.7、super 关键字

super 代表父类的引用,用于访问父类的属性、方法、构造器

3.2.7.1、基本语法

在这里插入图片描述
Base.java

package _super;

public class Base {  // 父类是Object
    public int n1 = 999;
    public int age = 111;
    public int n4 = 20;

    public void cal() {
        System.out.println("Base 类的 cal() 方法...");
    }

    public void eat() {
        System.out.println("Base 类的 eat().....");
    }
}

A.java

package _super;


public class A extends Base {
    // 4 个属性
    // public int n1 = 100;
    protected int n2 = 200;
    int n3 = 300;
    private int n4 = 400;

    public A() {
    }

    public A(String name) {
    }

    public A(String name, int age) {
    }

    // public void cal() {
    //    System.out.println("A 类的 cal() 方法...");
    // }

    public void test100() {
    }

    protected void test200() {
    }

    void test300() {
    }

    private void test400() {
    }
}

B.java

package _super;

public class B extends A {
    public int n1 = 888;
    protected int n10 = 200;

    // 编写测试方法
    public void test() {
        // super 的访问不限于直接父类, 如果爷爷类和本类中有同名的成员, 也可以使用 super 去访问爷爷类的成员;
        // 如果多个基类(上级类)中都有同名的成员, 使用 super 访问遵循就近原则 A->B->C
        System.out.println("super.n1=" + super.n1);
        super.cal();
    }

    // 访问父类的属性, 但不能访问父类的 private 属性
    public void hi() {
        System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
    }

    public void cal() {
        System.out.println("B 类的 cal() 方法...");
    }

    public void sum() {
        System.out.println("B 类的 sum()");
        // 希望调用父类-A 的 cal 方法
        // 这时, 因为子类 B 没有 cal 方法, 因此我可以使用下面三种方式
        // 找 cal 方法时(cal() 和 this.cal()),顺序是:
        // (1) 先找本类, 如果有, 则调用
        // (2) 如果没有, 则找父类(如果有, 并可以调用, 则调用)
        // (3)如果父类没有, 则继续找父类的父类, 整个规则, 就是一样的, 直到 Object 类
        // 提示: 如果查找方法的过程中, 找到了, 但是不能访问, 则报错, cannot access
        // 如果查找方法的过程中, 没有找到, 则提示方法不存在
        cal();
        this.cal();  // 等价 cal
        // 找 cal 方法(super.call()) 的顺序是直接查找父类,其他的规则一样
        // super.cal();
        // 演示访问属性的规则
        // n1 和 this.n1 查找的规则是:
        // (1) 先找本类, 如果有, 则调用
        // (2) 如果没有, 则找父类(如果有, 并可以调用, 则调用)
        // (3) 如果父类没有, 则继续找父类的父类, 整个规则, 就是一样的, 直到 Object 类
        // 提示: 如果查找属性的过程中, 找到了, 但是不能访问, 则报错, cannot access
        // 如果查找属性的过程中, 没有找到, 则提示属性不存在
        System.out.println(n1);
        System.out.println(this.n1);
        // 找 n1 (super.n1) 的顺序是直接查找父类属性,其他的规则一样
        System.out.println(super.n1);
        // System.out.println(super.n4);  // 父类中n4是private, 爷爷类中是public, 也不会向上继续查找, 直接报错
        // System.out.println(super.n10);  // n10在本类中, 调用了super 不会从本类查找, 直接报错
    }

    // 访问父类的方法,不能访问父类的 private 方法 super.方法名(参数列表);
    public void ok() {
        super.test100();
        super.test200();
        super.test300();
        // super.test400();  // 不能访问父类 private 方法
    }

    // 访问父类的构造器(这点前面用过): super(参数列表);只能放在构造器的第一句, 只能出现一句!
    public B() {
        // super();
        // super("jack", 10);
        super("jack");
    }
}

Super01.java

package _super;

public class Super01 {
    public static void main(String[] args) {
        B b = new B();  // 子类对象
        b.sum();
        /*
        1 B 类的 sum()
        2 B 类的 cal() 方法...
        3 B 类的 cal() 方法...
        4 888
        5 888
        6 999
        */

        b.test();
        /*
        1 super.n1=999
        2 Base 类的 cal() 方法...
        */
    }
}
3.2.7.2、super 细节

在这里插入图片描述

3.2.7.3、super 和 this 的比较

在这里插入图片描述

3.2.8、方法重写/覆盖(override)

在这里插入图片描述
Animal.java

package override_;

public class Animal {
    public void cry() {
        System.out.println("动物叫唤..");
    }

    public Object m1() {
        return null;
    }

    public String m2() {
        return null;
    }

    public AAA m3() {
        return null;
    }

    protected void eat() {
    }
}

Dog.java

package override_;

public class Dog extends Animal {
    // 1. 因为 Dog 是 Animal 子类
    // 2. Dog 的 cry 方法和 Animal 的 cry 定义形式一样(名称、返回类型、参数)
    // 3. 这时我们就说 Dog 的 cry 方法, 重写了 Animal 的 cry 方法
    public void cry() {
        System.out.println("小狗汪汪叫..");
    }

    // 子类方法的返回类型和父类方法返回类型一样, 或者是父类返回类型的子类
    // 比如 父类 返回类型是 Object, 子类方法返回类型是 String
    public String m1() {
        return null;
    }

    // 这里 Object 不是 String 的子类, 因此编译错误
    // public Object m2() {
    //     return null;
    // }

    public BBB m3() {
        return null;
    }

    // 子类方法不能缩小父类方法的访问权限
    // public > protected > 默认 > private
    public void eat() {
    }
}

class AAA {
}

class BBB extends AAA {
}

Override01.java

package override_;

public class Override01 {
    public static void main(String[] args) {
        // 方法重写的情况
        Dog dog = new Dog();
        dog.cry();  // ctrl+b
    }
}
3.2.8.1、注意事项和使用细节

方法重写也叫方法覆盖,需要满足下面的条件

在这里插入图片描述

3.2.8.2、重写和重载的比较

在这里插入图片描述

3.3、多态

在这里插入图片描述

3.3.1、传统方法与多态方法

在这里插入图片描述
Animal.java

package poly_;

public class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Dog.java

package poly_;

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

Cat.java

package poly_;

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

Food.java

package poly_;

public class Food {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Food(String name) {
        this.name = name;
    }
}

Bone.java

package poly_;

public class Bone extends Food{
    public Bone(String name) {
        super(name);
    }
}

Fish.java

package poly_;

public class Fish extends Food {

    public Fish(String name) {
        super(name);
    }
}

Master.java

package poly_;

public class Master {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Master(String name) {
        this.name = name;
    }

    // 传统方法
    public void feed(Dog dog, Bone bone) {
        System.out.println("主人" + name + " 正在给 " + dog.getName() + " 吃 " + bone.getName());
    }

    public void feed(Cat cat, Fish fish) {
        System.out.println("主人" + name + " 正在给 " + cat.getName() + " 吃 " + fish.getName());
    }

    // 多态方法
    // 使用多态机制, 可以统一的管理主人喂食的问题
    // animal 编译类型是 Animal, 可以指向(接收) Animal 子类的对象
    // food 编译类型是 Food, 可以指向(接收) Food 子类的对
    public void feed(Animal animal, Food food) {
        System.out.println("主人" + name + " 正在给 " + animal.getName() + " 吃 " + food.getName());
    }

    public static void main(String[] args) {
        // 传统方法
        Master master = new Master("张三");
        Dog dog = new Dog("大黄狗");
        Bone bone = new Bone("大棒骨");
        master.feed(dog, bone);
        Cat cat = new Cat("小花猫");
        Fish fish = new Fish("黄花鱼");
        master.feed(cat, fish);

        // 多态方式
        Master master1 = new Master("李四");
        Dog dog1 = new Dog("二哈");
        Bone bone1 = new Bone("小排骨");
        master1.feed(dog1, bone1);
        Cat cat1 = new Cat("大脸猫");
        Fish fish1 = new Fish("金枪鱼");
        master1.feed(cat1, fish1);
    }
}
3.3.2、定义

方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的

3.3.3、多态的具体体现
3.3.3.1、方法的多态

重写和重载就体现多态

package PloyMethod;

public class PloyMethod {
    public static void main(String[] args) {
        // 方法重载体现多态
        A a = new A();
        // 这里我们传入不同的参数, 就会调用不同 sum 方法, 就体现多态
        System.out.println(a.sum(10, 20));
        System.out.println(a.sum(10, 20, 30));
        // 方法重写体现多态
        B b = new B();
        a.say();
        b.say();
    }
}

class B {  // 父类
    public void say() {
        System.out.println("B say() 方法被调用...");
    }
}

class A extends B {  // 子类

    public int sum(int n1, int n2) {  // 和下面 sum 构成重载
        return n1 + n2;
    }

    public int sum(int n1, int n2, int n3) {
        return n1 + n2 + n3;
    }

    public void say() {
        System.out.println("A say() 方法被调用...");
    }
}
3.3.3.2、对象的多态
  1. 一个对象的编译类型和运行类型可以不一致
  2. 编译类型在定义对象时就确定了, 不能改变
  3. 运行类型是可以变化的
  4. 编译类型看定义时 = 号的左边, 运行类型看 = 号的右边
Animal animal = new Dog();  【animal编译类型是Animal, 运行类型是Dog】
animal = new Cat();  【animal的运行类型变成了Cat, 编译类型仍然是Animal

Animal.java

package PloyMethod;

public class Animal {
    public void cry() {
        System.out.println("Animal cry() 动物在叫....");
    }
}

Cat.java

package PloyMethod;

public class Cat extends Animal {
    public void cry() {
        System.out.println("Cat cry() 小猫喵喵叫...");
    }
}

Dog.java

package PloyMethod;

public class Dog extends Animal {
    public void cry() {
        System.out.println("Dog cry() 小狗汪汪叫...");
    }
}

PolyObject.java

package PloyMethod;

public class PolyObject {
    public static void main(String[] args) {
        // 体验对象多态特点
        // animal 编译类型就是 Animal, 运行类型 Dog
        Animal animal = new Dog();
        // 因为运行时, 执行到改行时, animal 运行类型是 Dog, 所以 cry 就是 Dog 的 cry
        animal.cry();  // 小狗汪汪叫
        // animal 编译类型 Animal, 运行类型就是 Cat
        animal = new Cat();
        animal.cry();  // 小猫喵喵叫
    }
}
3.3.4、多态注意事项和细节讨论

多态的前提是:两个对象(类)存在继承关系

3.3.4.1、多态的向上转型

在这里插入图片描述

3.3.4.2、多态的向下转型

在这里插入图片描述

3.3.4.3、代码实现

Animal.java

package PolyDetail;

public class Animal {
    String name = "动物";
    int age = 10;

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

    public void run() {
        System.out.println("跑");
    }

    public void eat() {
        System.out.println("吃");
    }

    public void show() {
        System.out.println("hello, 你好");
    }
}

Cat.java

package PolyDetail;

public class Cat extends Animal {
    public void eat() {  // 方法重写
        System.out.println("猫吃鱼");
    }

    public void catchMouse() {  // Cat 特有方法
        System.out.println("猫抓老鼠");
    }
}

PolyDetail.java

package PolyDetail;

public class PolyDetail {
    public static void main(String[] args) {
        // 向上转型: 父类的引用指向了子类的对象
        // 语法: 父类类型引用名 = new 子类类型();
        Animal animal = new Cat();
        Object object = new Cat();  // 可以正常访问, Object 也是 Cat 的父类
        // 向上转型调用方法的规则如下:
        // (1) 可以调用父类中的所有成员(需遵守访问权限)
        // (2) 但是不能调用子类的特有的成员
        // (3) 因为在编译阶段, 能调用哪些成员, 是由编译类型来决定的
        // animal.catchMouse();  // 报错
        // (4) 最终运行效果看子类(运行类型)的具体实现, 即调用方法时按照从子类(运行类型)开始查找方法, 调用规则与方法调用规则一致
        animal.eat();  // 猫吃鱼..
        animal.run();  // 跑
        animal.show();  // hello, 你好
        animal.sleep();  // 睡

        // 可以调用 Cat 的 catchMouse 方法
        // 多态的向下转型
        // (1) 语法: 子类类型 引用名 = (子类类型)父类引用
        Cat cat = (Cat) animal;  // cat 的编译类型 Cat, 运行类型是 Cat
        cat.catchMouse();  // 猫抓老鼠
        // (2) 要求父类的引用必须指向的是当前目标类型的对象
        // Dog dog = (Dog) animal;  // 报错
    }
}

属性没有重写之说!属性的值看编译类型

package PolyDetail;

public class PolyDetail02 {
    public static void main(String[] args) {
        // 属性没有重写之说!属性的值看编译类型
        Base base = new Sub();  // 向上转型
        System.out.println(base.count);  // 看编译类型 10
        Sub sub = new Sub();
        System.out.println(sub.count);  // 20
    }
}

class Base {  // 父类
    int count = 10;  // 属性
}

class Sub extends Base {  // 子类
    int count = 20;  // 属性
}

instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型

package PolyDetail;

public class PolyDetail03 {
    public static void main(String[] args) {
        BB bb = new BB();
        System.out.println(bb instanceof BB);  // true
        System.out.println(bb instanceof AA);  // true
        // aa 编译类型 AA, 运行类型是 BB
        // BB 是 AA 子类
        AA aa = new BB();
        System.out.println(aa instanceof AA);  // true
        System.out.println(aa instanceof BB);  // true
        Object obj = new Object();
        System.out.println(obj instanceof AA);  // false
        String str = "hello";
        // System.out.println(str instanceof AA);  // 报错
        System.out.println(str instanceof Object);  // true
    }
}

// 父类
class AA {
}

//子类
class BB extends AA {
}
3.3.5、java 的动态绑定机制

在这里插入图片描述

package DynamicBinding;

public class DynamicBinding {
    public static void main(String[] args) {
        // a 的编译类型 A, 运行类型 B
        A a = new B();  // 向上转型
        System.out.println(a.sum());  // 40
        System.out.println(a.sum1());  // 30
    }
}

class A {  // 父类
    public int i = 10;

    //动态绑定机制:
    public int sum() {  // 父类 sum()
        return getI() + 10;
    }

    public int sum1() {  // 父类 sum1()
        return i + 10;
    }

    public int getI() {  // 父类 getI
        return i;
    }
}

class B extends A {  // 子类
    public int i = 20;

    public int sum() {
        return i + 20;
    }

    public int getI() {  // 子类 getI()
        return i;
    }

    public int sum1() {
        return i + 10;
    }
}
3.3.6、多态的应用
3.3.6.1、多态数组

数组的定义类型为父类类型,里面保存的实际元素类型为子类类型

需求

现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象say 方法
应用实例升级:如何调用子类特有的方法,Teacher 有一个 teach , Student 有一个 study方法

Person.java

package polyarr_;

public class Person {  // 父类
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String say() {  // 返回名字和年龄
        return name + "\t" + age;
    }
}

Student.java

package polyarr_;

public class Student extends Person {
    private double score;

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public String say() {
        return "学生 " + super.say() + " score=" + score;
    }

    // 特有的方法
    public void study() {
        System.out.println("学生 " + getName() + " 正在学 java...");
    }

}

Teacher.java

package polyarr_;

public class Teacher extends Person {
    private double salary;

    public Teacher(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String say() {
        return "老师 " + super.say() + " salary=" + salary;
    }

    public void teach() {
        System.out.println("老师 " + getName() + " 正在讲 java 课程...");
    }
}

PloyArray.java

package polyarr_;

public class PloyArray {
    public static void main(String[] args) {
        // 要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中, 并调用每个对象 say 方法
        Person[] persons = new Person[5];
        persons[0] = new Person("jack", 20);
        persons[1] = new Student("tom", 18, 95);
        persons[2] = new Student("lily", 21, 78);
        persons[3] = new Teacher("smith", 50, 9500.2);
        persons[4] = new Teacher("king", 37, 10000.6);

        // 循环遍历多态数组, 调用 say
        for (int i = 0; i < persons.length; i++) {
            // person[i] 编译类型是 Person, 运行类型是是根据实际情况有 JVM 来判断
            System.out.println(persons[i].say());  // 动态绑定机制
            // 输出各自的方法, 使用 类型判断 + 向下转型
            if (persons[i] instanceof Student) {
                Student student = (Student) persons[i];
                student.study();
            } else if (persons[i] instanceof Teacher) {
                Teacher teacher = (Teacher) persons[i];
                teacher.teach();
            } else if (persons[i] instanceof Person) {
                System.out.println("你的类型有误, 请自己检查...");
            } else {
                System.out.println("你的类型有误, 请自己检查...");
            }
        }
    }
}
3.3.6.2、多态参数

方法定义的形参类型为父类类型, 实参类型允许为子类类型

在这里插入图片描述
Employee.java

package polyparameter_;

public class Employee {
    private String name;
    private double salary;

    public String getAnnual() {
        return name + "的工资是" + salary * 12;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
}

Worker.java

package polyparameter_;

public class Worker extends Employee {
    public Worker(String name, double salary) {
        super(name, salary);
    }

    @Override
    public String getAnnual() {
        return super.getAnnual();
    }

    public void work() {
        System.out.println("员工" + super.getName() + "正在工作...");
    }
}

Manager.java

package polyparameter_;

public class Manager extends Employee {
    private double bonus;

    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    @Override
    public String getAnnual() {
        return super.getAnnual() + ", 奖金是" + bonus + ", 合计是" + (super.getSalary() * 12 + bonus);
    }

    public void manage() {
        System.out.println("管理" + super.getName() + "正在管理...");
    }
}

TestEmployee.java

package polyparameter_;

public class TestEmployee {
    public static void main(String[] args) {
        Worker worker = new Worker("张三", 3000);
        Manager manager = new Manager("李四", 28000, 1500);
        TestEmployee testEmployee = new TestEmployee();
        testEmployee.showEmpAnnual(worker);
        testEmployee.showEmpAnnual(manager);
    }

    public void showEmpAnnual(Employee e) {
        System.out.println(e.getAnnual());
        // 区分普通员工和经理
        if (e instanceof Manager) {
            Manager manager = (Manager) e;
            manager.manage();
            System.out.println(manager.getBonus());
        } else if (e instanceof Worker) {
            Worker worker = (Worker) e;
            worker.work();
        } else {
            System.out.println("未知错误...");
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值