第8章:面向对象编程(中级部分)

8.1 包

8.1.1 看一个应用场景

现在有两个程序员共同开发一个java项目,程序员xiaoming希望定义一个类取名Dog,程序员xiaoqiang也想定义一个类也叫Dog。两个程序员为此还吵架了,该怎么办呢?

8.1.2 包的三大作用

  1. 区别相同名字的类
  2. 当类很多时,可以很好的管理类
  3. 控制访问范围

8.1.3 包基本语法

eg:package com.aimoyudett;

说明:

  1. package 关键字,表示打包
  2. com.aimoyudett:表示包名

8.1.4 包的本质分析(原理)

包的本质 实际上就是创建不同的文件夹/目录来保存类文件。

在这里插入图片描述

8.1.5 快速入门

针对开头所说的场景,我们将用包解决他们的矛盾

在这里插入图片描述

运行结果:

在这里插入图片描述

8.1.6 包的命名

8.1.6.1 命名规则

只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字

eg:
demo.class.exec1 //错误 class是关键字
ermo.12a // 错误 12a是数字开头
demo.ab12.oa // 对

8.1.6.2 命名规范

一般是小写字母 + 小圆点 ** com.公司名.项目名.业务模块名**

比如:com.aimoyudett.oa.model;
举例:
com.sina.crm.user; //用户模块
com.sina.crm.order; //订单模块
com.sina.crm.utils; //工具类

8.1.7 常用的包

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

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

8.1.8 如何引入包

语法: import 包名;

我们引入一个包的主要目的是要使用该包下的类,比如import java.util.Scanner;就只是引入一个类Scanner;import java.util.*;表示将java.util包下的所有都引入。

package com.aimoyudett;

import java.util.Arrays;

//注意:
//爱摸鱼的TT~建议:我们需要使用到哪个类,就导入哪个类即可,不建议使用 *导入
// import java.util.Scanner;
// 表示只会引入 java.util 包下的 Scanner
// import java.util.*;
// 表示将 java.util 包下的所有类都引入(导入)

public class Import01 {

    public static void main(String[] args) {
        //使用系统提供 Arrays 完成 数组排序
        int[] arr = {-1,2,0,12,1,6};
        //比如对其进行排序
        //传统方法是,自己编写排序(冒泡)
        //系统是提供了相关的类,可以方便完成 Arrays
        Arrays.sort(arr);
        //输出排序结果
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }
    }
}

8.1.9 注意事项和使用细节

  1. package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package;
  2. import指令 位置放在package的下面,在类的定义前面,可以有多句且没有顺序要求。

8.2 访问修饰符

8.2.1 基本介绍

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

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

8.2.2 4种访问修饰符的访问范围

在这里插入图片描述

8.2.3 使用的注意事项

  1. 修饰符可以用来修饰类中的属性成员方法以及
  2. 只有默认的和public才能修饰类!!!并且遵循上述访问权限的特点;
  3. 因为没有学习继承,因此,关于在子类中的访问权限,我们讲完子类后,再回头讲解;
  4. 成员方法的访问规则和属性完全一样。

8.3 面向对象编程—封装

8.3.1 基本介绍

面向对象编程有三大特征:封装、继承、多态。

8.3.2 封装介绍

封装(encapsulate)就是把抽象出的数据【属性】和对数据的操作【方法】封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作【方法】,才能对数据进行操作。

8.3.3 封装的理解和好处

  1. 隐藏实现细节:方法(连接数据库)<— 调用(传入参数…)
  2. 可以对数据进行验证,保证安全合理。

8.3.4 封装的实现步骤(三步)

  1. 将属性进行私有化private【不能直接修改属性】
  2. 提供一个公共的**(public)set方法**,用于对属性判断并赋值
public void setXXX(类型 参数名){ //XXX表示某个属性
    //加入数据验证的业务逻辑
    属性 = 参数名;
}
  1. 提供一个公共的**(public)get方法**,用于获取属性的值
public 数据类型 getXXX(){ // 权限判断,XXX某个属性
    return xx;

8.3.5 快速入门案例

请大家看一个小程序,不能随便查看人的年龄、工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认年龄,必须在1-120,,年龄和工资不能直接查看,name的长度在2-8字符之间。

package com.aimoyudett.encap;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-07 23:04
 * @Modified By:
 */
public class Encapsulate {

    public static void main(String[] args) {

        //如果要使用快捷键 alt+r, 需要先配置主类
        //第一次,我们使用鼠标点击形式运算程序,后面就可以用
        Person person = new Person();
        person.setName("爱摸鱼的TT~");
        person.setAge(123);
        person.setSalary(18000);
        System.out.println(person.info());
    }
}
class Person{
    /*请大家看一个小程序,不能随便查看人的年龄、工资等隐私,并对设置的年龄进行合理的验证。
    年龄合理就设置,否则给默认年龄,必须在1-120,年龄和工资不能直接查看,name的长度在2-8字符之间。
    */
    String name;
    private int age;
    private double salary;

    //自己写 setXxx 和 getXxx 太慢,我们使用快捷键
    //然后根据要求来完善我们的代码.
    public void setName(String name){
        //加入对数据的校验,相当于增加了业务逻辑
        if(name.length() >= 2 && name.length() <= 8) {
            this.name = name;
        }else{
            System.out.println("名字的长度不对,需要(2-8)个字符,默认名字");
            this.name = "无名氏";
        }
    }
    public String getName(){
        return name;
    }

    public void setAge(int age){
        //加入对数据的校验,相当于增加了业务逻辑
        if(age >= 1 && age <= 120) {
            this.age = age;
        }else{
            System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄 18 ");
            this.age = 18;
        }
    }
    public int getAge(){
        return age;
    }

    public void setSalary(double salary){
        this.salary = salary;
    }
    public double getSalary(){
        return salary;
    }
    //写一个方法,返回属性信息
    public String info(){
        return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
    }
}

运行结果:

在这里插入图片描述

8.3.6 将构造器和 setXxx 结合

我们将上面代码增加构造器,

package com.aimoyudett.encap;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-07 23:04
 * @Modified By:
 */
public class Encapsulate {

    public static void main(String[] args) {

        Person person1 = new Person("爱摸鱼的TT~", 123, 18000);
        System.out.println(person1.info());
    }
}
class Person{
    /*请大家看一个小程序,不能随便查看人的年龄、工资等隐私,并对设置的年龄进行合理的验证。
    年龄合理就设置,否则给默认年龄,必须在1-120,年龄和工资不能直接查看,name的长度在2-8字符之间。
    */
    String name;
    private int age;
    private double salary;

    //构造方法
    public Person(){

    }
    public Person(String name, int age, double salary){
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    //自己写 setXxx 和 getXxx 太慢,我们使用快捷键
    //然后根据要求来完善我们的代码.
    public void setName(String name){
        //加入对数据的校验,相当于增加了业务逻辑
        if(name.length() >= 2 && name.length() <= 8) {
            this.name = name;
        }else{
            System.out.println("名字的长度不对,需要(2-8)个字符,默认名字");
            this.name = "无名氏";
        }
    }
    public String getName(){
        return name;
    }

    public void setAge(int age){
        //加入对数据的校验,相当于增加了业务逻辑
        if(age >= 1 && age <= 120) {
            this.age = age;
        }else{
            System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄 18 ");
            this.age = 18;
        }
    }
    public int getAge(){
        return age;
    }

    public void setSalary(double salary){
        this.salary = salary;
    }
    public double getSalary(){
        return salary;
    }
    //写一个方法,返回属性信息
    public String info(){
        return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
    }
}

运行结果:

在这里插入图片描述

从上面运行结果看出,age超出了规定范围,但是setAge方法不起作用,不会提示信息。那这种问题要咋整呢?

需要在有参构造方法中修改代码,将setXXX(xx) 写入!

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

修改后运行的结果:

在这里插入图片描述

综合代码:

package com.aimoyudett.encap;

public class Encapsulate {

    public static void main(String[] args) {

        //如果要使用快捷键 alt+r, 需要先配置主类
        //第一次,我们使用鼠标点击形式运算程序,后面就可以用
        /*Person person = new Person();
        person.setName("爱摸鱼的TT~");
        person.setAge(123);
        person.setSalary(18000);
        System.out.println(person.info());*/

        //如果我们自己使用构造器指定属性
        Person person1 = new Person("爱摸鱼的TT~", 123, 18000);
        System.out.println(person1.info());
    }
}
class Person{
    /*请大家看一个小程序,不能随便查看人的年龄、工资等隐私,并对设置的年龄进行合理的验证。
    年龄合理就设置,否则给默认年龄,必须在1-120,年龄和工资不能直接查看,name的长度在2-8字符之间。
    */
    String name;
    private int age;
    private double salary;

    //构造方法
    public Person(){

    }
    public Person(String name, int age, double salary){
//        this.name = name;
//        this.age = age;
//        this.salary = salary;
        //我们可以将 set 方法写在构造器中,这样仍然可以验证
        this.setName(name);
        this.setAge(age);
        this.setSalary(salary);
    }
    //自己写 setXxx 和 getXxx 太慢,我们使用快捷键
    //然后根据要求来完善我们的代码.
    public void setName(String name){
        //加入对数据的校验,相当于增加了业务逻辑
        if(name.length() >= 2 && name.length() <= 8) {
            this.name = name;
        }else{
            System.out.println("名字的长度不对,需要(2-8)个字符,默认名字");
            this.name = "无名氏";
        }
    }
    public String getName(){
        return name;
    }

    public void setAge(int age){
        //加入对数据的校验,相当于增加了业务逻辑
        if(age >= 1 && age <= 120) {
            this.age = age;
        }else{
            System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄 18 ");
            this.age = 18;
        }
    }
    public int getAge(){
        return age;
    }

    public void setSalary(double salary){
        this.salary = salary;
    }
    public double getSalary(){
        return salary;
    }
    //写一个方法,返回属性信息
    public String info(){
        return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
    }
}

8.3.7 巩固练习

创建程序,在其中定义两个类:Account 和 AccountTest类体会Java的封装性。

  1. Account类要求具有属性:姓名(长度为2位3位或4位)、余额(必须大于20)、密码(必须是六位),如果不满足,则给出提示信息,并给默认值(程序员自己定)
  2. 通过setXXX的方法给Account的属性赋值
  3. 在AccountTest中测试
package com.aimoyudett.encap;

public class Account {
    /*1. Account类要求具有属性:姓名(长度为2位3位或4位)、余额(必须大于20)、密码(必须是六位),
        如果不满足,则给出提示信息,并给默认值(程序员自己定)
      2. 通过setXXX的方法给Account的属性赋值
      3. 在AccountTest中测试
      */
    private String name;
    private double money;
    private String passWord;

    //构造方法
    public Account(){}

    public Account(String name, double money, String passWord){
        this.setName(name);
        this.setMoney(money);
        this.setPassWord(passWord);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        if(name.length() >= 2 && name.length() <= 4) {
            this.name = name;
        }else{
            System.out.println("名字的长度不对,需要(2-4)位字符,默认名字");
            this.name = "爱摸鱼的TT~";
        }
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        if(money >= 20.0) {
            this.money = money;
        }else{
            System.out.println("金额不满足大于20元,默认金额");
            this.money = 20;
        }
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        if(passWord.length() == 6) {
            this.passWord = passWord;
        }else{
            System.out.println("密码输入有误,不是六位数,默认密码");
            this.passWord = "000000";
        }
    }

    public void showInfo(){
        System.out.println("账号信息 name=" + name + " 余额=" + money + " 密码" + passWord);
    }
}

public class AccountTest {
    public static void main(String[] args) {
        Account account = new Account();
        account.setName("Tom");
        account.setMoney(12008);
        account.setPassWord("123456");

        //调用构方
        //Account account = new Account("Tom", 12008, "123456");

        //输出信息
        account.showInfo();
    }
}

运行结果:

在这里插入图片描述

8.4 面向对象编程—继承

8.4.1 为什么需要继承

还是用问题引入主题,我们编写两个类,一个是Pupil类(小学生),一个是Graduate(大学生毕业生),问题:两个类的属性和方法有很多是相同的,怎么办?

在这里插入图片描述

看上面代码,很明显很多代码是重复性了。那怎么解决代码复用性呢? ===> 继承

8.4.2 继承基本介绍和示意图

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

在这里插入图片描述

8.4.3 继承的基本语法

class 子类 extends 父类{
}
  1. 子类就会自动拥有父类定义的属性和方法
  2. 父类又叫 超类、基类
  3. 子类又叫 派生类。

8.4.4 继承给编程带来的便利

  1. 代码的复用性提高了
    2) 代码的扩展性和维护性提高了

8.4.5 继承的深入讨论/细节问题

  1. 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问

在这里插入图片描述

运行结果:
在这里插入图片描述

  1. 子类必须调用父类的构造器, 完成父类的初始化

在这里插入图片描述


运行结果:

在这里插入图片描述

  1. a、当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,b、如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过(怎么理解。)

a、在这里插入图片描述

b、在这里插入图片描述

  1. 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
  2. super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
  3. super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器【super() 是调用父类、this() 是调用本类】
  4. java 所有类都是 Object 类的子类, Object 是所有类的基类

在这里插入图片描述

  1. 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
  2. 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制
    1. 思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】

在这里插入图片描述

  1. 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系

8.4.6 继承的本质分析(重要!!!)

我们看一个案例分析当子类继承父类,创建子类对象时,内存中到底发生了什么?

爱摸鱼的TT~提示:当子类对象创建好后,其实就是建立(子父类)查找的关系

在这里插入图片描述


以上就是继承的内存布局分析图,相信大家都能理解掌握,接下来就有个问题:如果要访问里面属性、方法,该怎么访问?

这时请大家注意,要按照查找关系来返回信息

  • (1) 首先看子类是否有该属性
  • (2) 如果子类有这个属性,并且可以访问,则返回信息
  • (3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息…)
  • (4) 如果父类没有就按照(3)的规则,继续找上级父类,直到Object…
  • (5) 如果有该属性,但是不能直接访问,修饰符时private,则需要通过父类提供公共的方法去访问
package com.aimoyudett.extend_.import_;

/**
 * 讲解继承的本质
 */
public class ExtendsTheory {
    public static void main(String[] args) {
        Son son = new Son();//内存的布局
        
        System.out.println(son.name);//返回就是大头儿子
        //System.out.println(son.age);//返回的就是39
        //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 = "大头儿子";
}

咦,有同学会问 如果父类的age属性是被private修饰的,但是爷类的age属性不是私有的,那在内存 堆中不存在私有属性?答案是照样存在私有属性。还有访问时是直接跳过父类,来访问爷类的公有age属性?答案肯定是错的,它一直会直接访问父类age,如果没有公有的方法去访问,编译就会报错!!!

8.4.7 巩固练习

  1. 案例1

在这里插入图片描述

  1. 案例2

在这里插入图片描述

  1. 案例3

编写 Computer 类,包含 CPU、内存、硬盘等属性,getDetails 方法用于返回 Computer 的详细信息
编写 PC 子类,继承 Computer 类,添加特有属性【品牌 brand】
编写 NotePad 子类,继承 Computer 类,添加特有属性【color】
编写 Test 类,在 main 方法中创建 PC 和 NotePad 对象,分别给对象中特有的属性赋值,以及从 Computer 类继承的属性赋值,并使用方法并打印输出信息

  • Computer类
//编写Computer类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
public class Computer {
    private String cpu;
    private int memory;
    private int disk;
    public Computer(String cpu, int memory, int disk) {
        this.cpu = cpu;
        this.memory = memory;
        this.disk = disk;
    }
    //返回Computer信息
    public String getDetails() {
        return "cpu=" + cpu + " memory=" + memory + " disk=" + disk;
    }

    public String getCpu() {
        return cpu;
    }
    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public int getMemory() {
        return memory;
    }
    public void setMemory(int memory) {
        this.memory = memory;
    }

    public int getDisk() {
        return disk;
    }
    public void setDisk(int disk) {
        this.disk = disk;
    }
}
  • PC类
//编写PC子类,继承Computer类,添加特有属性【品牌brand】
public class PC extends Computer{
    private String brand;
    //这里IDEA 根据继承的规则,自动把构造器的调用写好
    //这里也体现: 继承设计的基本思想,父类的构造器完成父类属性初始化
    //子类的构造器完成子类属性初始化
    public PC(String cpu, int memory, int disk, String brand) {
        super(cpu, memory, disk);
        this.brand = brand;
    }
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public void printInfo() {
        System.out.print("PC信息:");
//      System.out.println(getCpu() + getMemory() + getDisk());
        //调用父类的getDetails方法,得到相关属性信息..
        System.out.println(getDetails() + " brand=" + brand);
    }
}
  • 测试方法
public class ExtendsExercise03 {
    public static void main(String[] args) {
        PC pc = new PC("intel", 16, 500, "IBM");
        pc.printInfo();
    }
}
/*
编写Computer类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
编写PC子类,继承Computer类,添加特有属性【品牌brand】
编写NotePad子类,继承Computer类,添加特有属性【color】 //同学们自己写。
编写Test类,在main方法中创建PC和NotePad对象,分别给对象中特有的属性赋值,
以及从Computer类继承的属性赋值,并使用方法并打印输出信息
 */

运行结果:
在这里插入图片描述

8.5 super 关键字

8.5.1 基本介绍

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

8.5.2 基本语法

  1. 访问父类的属性/方法,但不能访问父类的private属性/方法【super.属性名/方法】
  2. 访问父类的构造器(这点前面用过)【super(参数列表);只能放在构造器的第一句,只能出现一句!】

8.5.3 super关键字的注意事项和细节

  1. 调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类属性由子类初始化)

  2. 当子类中有何父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果!【好好理解,难度不大】

分以下情况:

  • 访问方法
    • 找xxx方法时(xxx() 和 this.xxx()),顺序是:
      • (1)先找本类,如果有,则调用
      • (2)如果没有,则找父类(如果有,并可以调用,则调用)
      • (3)如果父类没有,则继续找父类的父类,整个规则都是一样的,直到 Object类
      • 提示:如果查找方法的过程中,找到了,但是不能访问, 则报错, cannot access
        如果查找方法的过程中,没有找到,则提示方法不存在
    • 找xxx方法(super.xxx()) 的顺序是直接查找父类,其他的规则一样

在这里插入图片描述

  • 访问属性
    • xx属性 和 this.xx 查找的规则是
      • (1) 先找本类,如果有,则调用
      • (2) 如果没有,则找父类(如果有,并可以调用,则调用)
      • (3) 如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object类
      • 提示:如果查找属性的过程中,找到了,但是不能访问, 则报错, cannot access
        如果查找属性的过程中,没有找到,则提示属性不存在
    • 找xx属性 (super.xx) 的顺序是直接查找父类属性,其他的规则一样

在这里插入图片描述

  1. super的访问不限于直接父类,如果爷爷类和本类中又同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。A->B->C,当然也需要遵守访问权限的相关规则。

8.5.4 super 和 this 的比较

在这里插入图片描述

8.6 方法重写/覆盖(override)

8.6.1 基本介绍

简单来说:方法覆盖(重写)就是子类有一个方法,和父类(可以不是直接父类,爷爷类等都行)的某个方法的名称返回类型参数一样,那么我们就说子类的这个方法覆盖了父类的方法。

8.6.2 注意事项和使用细节

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

  1. 子类的方法的形参列表方法名称 要和父类方法的形参列表、方法名称完全一致。
  2. 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类。比如:父类 返回类型是Objet,子类的返回类型是String。
父类:
public Object getInfo(){
}

子类:
public String getInfo(){
}
  1. 子类方法不能缩小父类方法的访问权限【public > protected > 默认 > private】
父类:
protected void getInfo(){
}

子类:
public void getInfo(){
}

8.6.3 方法重写和重载比较

在这里插入图片描述

8.6.4 巩固练习

    1. 编写一个 Person 类,包括属性/private(name、age),构造器、方法 say(返回自我介绍的字符串)。
  1. 编写一个 Student 类,继承 Person 类,增加 id、score 属性/private,以及构造器,定义 say 方法(返回自我介绍的信息)。
    3) 在 main 中,分别创建 Person 和 Student 对象,调用 say 方法输出自我介绍
  • Person类
//编写一个Person类,包括属性/private(name、age),构造器、方法say(返回自我介绍的字符串)
public class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String say() {
        return "name=" + name + " age=" + 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;
    }
}
  • Student类
//编写一个Student类,继承Person类,增加id、score属性/private,以及构造器,定义say方法(返回自我介绍的信息)。
public class Student extends Person{
    private int id;
    private double score;

    public Student(String name, int age, int id, double score) {
        super(name, age);//这里会调用父类构造器
        this.id = id;
        this.score = score;
    }
    //say
    public String say() { //这里体现super的一个好处,代码复用.
        return super.say() + " id=" + id + " score=" + score;
    }
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}
  • Test类
public class Test {
    public static void main(String[] args) {
        //在main中,分别创建Person和Student对象,调用say方法输出自我介绍
        Person person = new Person("爱摸鱼的TT~", 18);
        System.out.println(person.say());

        Student student = new Student("Jack", 20, 123456, 99.8);
        System.out.println(student.say());
    }
}

运行结果:

在这里插入图片描述

8.7 面向对象编程—多态

8.7.1 先看一个问题

请编写一个程序,Master类中有一个feed(喂食)方法,可以完成主人给动物喂食物的信息。

在这里插入图片描述

  • 使用传统的方法来解决(private属性)

代码编写:

//动物类
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;
    }
}

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

public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
}
//食物类
public class Food {
    private String name;

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

    public String getName() {
        return name;
    }

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

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

public class Bone extends Food {
    public Bone(String name) {
        super(name);
    }
}
//主人
public class Master {
    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(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());
    }
}
//测试类
public class Poly01 {
    public static void main(String[] args) {

        Master tom = new Master("汤姆");
        Dog dog = new Dog("大黄~");
        Bone bone = new Bone("大棒骨~");
        tom.feed(dog, bone);

        Cat cat = new Cat("小花猫~");
        Fish fish = new Fish("黄花鱼~");
        System.out.println("===========-------");
        tom.feed(cat, fish);
    }
}

运行结果:

在这里插入图片描述

从上面看,传统的方法带来的问题是什么呢?代码的复用性不高,而且不利于代码维护。
那如何解决呢?引出我们要讲解的多态。

8.7.2 多态基本介绍

什么是多态呢?从字面意思理解就是多种状态。

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

8.7.3 多态的具体体现

多态的具体体现主要就两种:第一种是方法的多态,第二种是对象的多态。下面就用代码举例说明。

  1. 方法的多态

重写和重载就体现多态

在这里插入图片描述

  1. 对象的多态 **(核心,困难,重点) **

爱摸鱼的TT~重要的几句话(记住):
(1)一个对象的编译类型和运行类型可以不一致;

//解读:父类的引用指向子类的对象【animal引用类型 指向 dog对象】
//  animal编译类型是Animal,运行类型是Dog
Animal animal = new Dog();

(2)编译类型在定义对象时,就确定了,不能改变;
(3)运行类型是可以变化的;

// animal的运行类型变成了Cat,编译类型仍然是Animal
animal = new Cat();

(4)编译类型看定义时 =号的左边,运行类型看 =号的右边。

看看代码理解下:

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

public class Cat extends Animal {

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

public class Dog extends Animal {

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

//测试类
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(); //小猫喵喵叫
    }
}

运行结果:

在这里插入图片描述

总结:父类的引用(编译类型)指向子类的对象(运行类型),在运行时,它是以运行类型为主。

因此,使用多态的机制来解决主人喂食物的问题,走代码。

在这里插入图片描述

将Master类的上面代码进行修改为父类的引用指向子类的对象方式进行编码

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

8.7.4 多态注意事项和细节讨论

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

  2. 多态的向上转型:

    1. 本质:父类的引用指向子类的对象;
    2. 语法:父类类型 引用名 = new 子类类型( );
    3. 特点:编译类型看左边,运行类型看右边
      1. 可以调用父类中的所有成员(需遵守访问权限)
      2. 不能调用子类中特有成员(属性和方法);【因为在编译阶段,能调用哪些成员(属性和方法),是由编译类型来决定的】
      3. 最终运行效果看子类(运行类型)的具体实现。即调用方法时,按照从子类(运行类型)开始查找方法
        ,然后调用,规则我前面讲的方法调用规则一致【super知识那块】。
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,你好");
    }
}

public class Cat extends Animal {
    public void eat(){//方法重写
        System.out.println("猫吃鱼");
    }
    public void catchMouse(){//Cat特有方法
        System.out.println("猫抓老鼠");
    }
}
//测试类
public class PolyDetail {
    public static void main(String[] args) {

        //向上转型: 父类的引用指向了子类的对象
        //语法:父类类型引用名 = new 子类类型();
        Animal animal = new Cat();
        //Object obj = new Cat();//可以吗? 可以 Object 也是 Cat的父类

        //向上转型调用方法的规则如下:
        //(1)可以调用父类中的所有成员(需遵守访问权限)
        //(2)但是不能调用子类的特有的成员
        //(#)因为在编译阶段,能调用哪些成员(属性和方法),是由编译类型来决定的
        //animal.catchMouse();错误

        //(3)最终运行效果看子类(运行类型)的具体实现, 即调用方法时,按照从子类(运行类型)开始查找方法
        //,然后调用,规则我前面我们讲的方法调用规则一致【super知识那块】。
        animal.eat();//猫吃鱼..
        animal.run();//跑
        animal.show();//hello,你好
        animal.sleep();//睡

        System.out.println("ok~~");
    }
}
  1. 多态的向下转型:
    1. 语法:子类类型 引用名 = (子类类型)父类引用;
    2. 只能强转父类的引用,不能强转父类的对象;
    3. 要求父类的引用必须指向的是当前目标类型的对象;
    4. 当向下转型后,可以调用子类类型中所有的成员。
public class PolyDetail {
    public static void main(String[] args) {

        //向上转型: 父类的引用指向了子类的对象
        //语法:父类类型引用名 = new 子类类型();
        Animal animal = new Cat();
        //Object obj = new Cat();//可以吗? 可以 Object 也是 Cat的父类

        //爱摸鱼的TT~希望,可以调用Cat的 catchMouse方法
        //多态的向下转型
        //(1)语法:子类类型 引用名 =(子类类型)父类引用;
        //问一个问题? cat 的编译类型 Cat,运行类型也是 Cat
        Cat cat = (Cat) animal;
        //(3)当向下转型后,可以调用子类类型中所有的成员。
        cat.catchMouse();//猫抓老鼠
        //(2)要求父类的引用必须指向的是当前目标类型的对象
        Dog dog = (Dog) animal; //可以吗? 不可以,不能将指向猫的动物变成狗的引用!!!

        System.out.println("ok~~");
    }
}
  1. 属性没有重写之说!属性的值看编译类型
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;//属性
}
  1. instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
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 {}//子类

8.7.5 巩固练习

请说出下面的每条语句,哪些是正确的,哪些是错误的,为什么?

  1. 案例1
public class PolyExercise01{
    public static void main(String[] args){
        double d = 13.4; //ok
        long l = (long)d; //ok
        System.out,println(l); //13
        int i = 5; //ok
        boolean b = (boolean)i; //不对,boolean -> int
        Object obj = "Hello"; //可以,向上转型
        String objStr = (String)obj; //可以,向下转型
        Object objPri = new Integer(5); //可以,向上转型
        String str = (String)objPri; //错误ClassCastException,指向Integer的父类引用,转成String
        Integer str1= (Integer_objPri; //可以,向下转型
  1. 案例2

在这里插入图片描述

8.7.6 Java 的动态绑定机制(非常非常重要!)

Java 重要特性: 动态绑定机制

首先,我们看看下面这个代码,输出什么结果:
0.png在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

不难得出结果,结合我们上所讲的知识:

最终运行效果看子类(运行类型)的具体实现。即调用方法时,按照从子类(运行类型)开始查找方法
,然后调用,规则我前面讲的方法调用规则一致【super知识那块】。

所以,运行结果是 40 和 30。

那我将子类的sum( ) 方法给注释了,就直接调用父类的sum( )/sum1( )方法,但getI( )方法父子类都有,那该调用谁的呢?这时输出结果又有啥变化呢?这里就涉及到动态绑定机制。

Java的动态绑定机制

  1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定;
  2. 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
public class DynamicBinding {
    public static void main(String[] args) {
        //a 的编译类型 A, 运行类型 B
        A a = new B();//向上转型
        System.out.println(a.sum());//? 30
        System.out.println(a.sum1());//? 20
    }
}

class A {//父类
    public int i = 10;
    //动态绑定机制
    public int sum() {//父类sum()
        return getI() + 10;//20+10
    }
    public int sum1() {//父类sum1()
        return i + 10;//10+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;
//    }
}

8.7.7 多态的应用

1. 多态数组

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

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

Person类:

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

    public Person(String name, int age) {
        this.name = name;
        this.age = 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 String say(){//返回名字和年龄
        return name + "\t" + age;
    }
}

Student类:

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;
    }

    //重写父类say()
    public String say(){
        return "学生 " + super.say() + " score = " + score;
    }
}

Teacher类:

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;
    }

    //重写父类的say()
    public String say(){
        return "老师 " + super.say() + " salary = " + salary;
    }
}

测试类:

public class PolyArray {
    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("mary", 18, 100);
        persons[2] = new Student("smith", 19, 30.1);
        persons[3] = new Teacher("scott", 30, 20000);
        persons[4] = new Teacher("king", 50, 25000);

        //循环遍历多态数组,调用say
        for (int i = 0; i < persons.length; i++) {
            //爱摸鱼的TT~提示: person[i] 编译类型是 Person ,运行类型是根据实际情况有JVM来判断
            System.out.println(persons[i].say());//动态绑定机制
        }
    }
}

运行结果:

在这里插入图片描述

应用实例升级:如何调用子类特有的方法,比如Teacher 有一个 teach , Student 有一个 study怎么调用?

Student类多添加一个方法:

在这里插入图片描述

Teacher类多添加一个方法:

在这里插入图片描述

测试类:

public class PolyArray {
    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("mary", 18, 100);
        persons[2] = new Student("smith", 19, 30.1);
        persons[3] = new Teacher("scott", 30, 20000);
        persons[4] = new Teacher("king", 50, 25000);

        //循环遍历多态数组,调用say
        for (int i = 0; i < persons.length; i++) {
            //爱摸鱼的TT~提示: person[i] 编译类型是 Person ,运行类型是根据实际情况有JVM来判断
            System.out.println(persons[i].say());//动态绑定机制
            //这里大家聪明. 使用 类型判断 + 向下转型.
            if(persons[i]  instanceof  Student) {//判断person[i] 的运行类型是不是Student
                Student student = (Student)persons[i];//向下转型
                student.study();
                //小伙伴也可以使用一条语句 ((Student)persons[i]).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("你的类型有误, 请自己检查...");
            }
        }
    }
}

运行结果:

在这里插入图片描述

2. 多态参数

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

应用实例1:前面的主任喂动物【自己回头看看】
应用实例2:定义员工类Employee,包含姓名和月工资【private】,以及计算年工资getAnnual的方法。普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法。

测试类中添加一个showEmpAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法【e.getAnnual( )】

测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法

Employee类:

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

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    //得到年工资的方法
    public double getAnnual() {
        return 12 * salary;
    }

    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;
    }
}

Manger类:

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;
    }
    public void manage() {
        System.out.println("经理 " + getName() + " is managing");
    }
    //重写获取年薪方法
    @Override
    public double getAnnual() {
        return super.getAnnual() + bonus;
    }
}

Worker类:

public class Worker extends Employee {
    public Worker(String name, double salary) {
        super(name, salary);
    }
    public void work() {
        System.out.println("普通员工 " + getName() + " is working");
    }

    @Override
    public double getAnnual() { //因为普通员工没有其它收入,则直接调用父类方法
        return super.getAnnual();
    }
}

测试类:

public class PloyParameter {
    public static void main(String[] args) {
        Worker tom = new Worker("tom", 2500);
        Manager milan = new Manager("milan", 5000, 200000);
        PloyParameter ployParameter = new PloyParameter();
        ployParameter.showEmpAnnual(tom);
        ployParameter.showEmpAnnual(milan);

        ployParameter.testWork(tom);
        ployParameter.testWork(milan);

    }

    //showEmpAnnual(Employee e)
    //实现获取任何员工对象的年工资,并在main方法中调用该方法 [e.getAnnual()]
    public void showEmpAnnual(Employee e) {
        System.out.println(e.getAnnual());//动态绑定机制.
    }
    //添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法
    public void testWork(Employee e) {
        if(e instanceof  Worker) {
            ((Worker) e).work();//有向下转型操作
        } else if(e instanceof Manager) {
            ((Manager) e).manage();//有向下转型操作
        } else {
            System.out.println("不做处理...");
        }
    }
}

运行结果:

在这里插入图片描述

8.7.8 小结

什么是多态、多态的具体体现有哪些?(可举例说明)

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

  1. 方法多态
    1. 重载体现多态
    2. 重写体现多态
  2. 对象多态
    1. 对象的编译类型和运行类型可以不一致,编译类型在定义时,就确定,不能变化
    2. 对象的运行类型是可以变化的,可以通过getClass()方法来查看运行类型
    3. 编译类型看定义时=号的左边,运行类型看定义时=号的右边
  3. 举例说明:

在这里插入图片描述

Java的动态绑定机制是什么?

  1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定;
  2. 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。

8.8 Object 类详解

我们可以查看下jdk的帮助文档,看看Object类有哪些内容,要掌握什么

在这里插入图片描述

8.8.1 equals 方法

我们在前面也有用到过equals方法判断相等的情况,当时也没有大致说它的内涵,现在这里就详解下,并且需要掌握equals 和 == 二者的区别【面试题】

== 是一个比较运算符

  1. ==:既可以判断基本类型,又可以判断引用类型;
  2. ==:如果判断基本类型,判断的是值是否相等。比如int i = 10; double d = 10.0;//二者值是相等的
  3. ==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象

在这里插入图片描述

equals方法

  1. equals:是Object类中对的方法,只能判断引用类型【要学会看jdk源码】
  2. 默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如:Integer、String【看看String 和 Integer的equals源代码】

接下来带大家一起分析源码【怎么查看源码?在配置好的前提,把光标放在XXX方法,直接输入ctrl+b】

  • Object类的equals方法:比较对象地址是否相同
//即Object 的equals 方法默认就是比较对象地址是否相同
//也就是判断两个对象是不是同一个对象.
public boolean equals(Object obj) {
       return (this == obj);
}
  • String类的 equals方法:比较两个字符串值是否相同
//把Object的equals方法重写了,变成了比较两个字符串值是否相同
public boolean equals(Object anObject) {
        if (this == anObject) {//如果是同一个对象
            return true;//返回true【因为都是指向同一个对象,地址相同,那内容肯定相同啦】
        }
        if (anObject instanceof String) {//判断类型
            String anotherString = (String)anObject;//向下转型
            int n = value.length;
            if (n == anotherString.value.length) {//如果长度相同,那就继续判断里面内容
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {//然后一个一个的比较字符
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;//如果两个字符串的所有字符都相等,则返回true
            }
        }
        return false;//如果比较的不是字符串,则直接返回false
}
  • Integer类的equals方法:判断两个值是否相同
//从源码可以看到 Integer 也重写了Object的equals方法,
//变成了判断两个值是否相同
public boolean equals(Object obj) {
       if (obj instanceof Integer) {
           //(Integer)obj 就是向下转型,转换成Integer对象
             return value == ((Integer)obj).intValue();
       }
       return false;
}

因此,从上面源码分析,容易得出:Object类是基类,它的equals方法是直接比较两个对像(对象地址)是否相等,而String、Integer等类是重写了Object类的equals方法,故它们equals方法是判断两个值是否相等。但是呢,我们自定义person类等自定义类,是不会自动重写Object类中equals方法的,所以在没有重写的前提还是比较两个对象地址是否相等,重写之后则比较两个值是否相等。

下面就做几道题巩固知识点

Integer integer1 = new Integer(1000);
Integer integer2 = new Integer(1000);
System.out.println(integer1 == integer2);//false
System.out.println(integer1.equals(integer2));//true

String str1 = new String("爱摸鱼的TT~");
String str2 = new String("爱摸鱼的TT~");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true

Person person1 = new Person("爱摸鱼的TT~");
Person person2 = new Person("爱摸鱼的TT~");
System.out.println(person1 == person2);//false
System.out.println(person1.equals(person2));//true

8.8.2 如何重写 equals 方法

应用实例: 判断两个 Person 对象的内容是否相等,如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false。 【这题目很明显,就是要重写equals方法来比较两个对象内容是否相等】

步骤:

  1. 判断比较的两个对象是否为同一个对象
  2. 两个对象的类型判断
    1. 对象进行向下转型【因为需要得到子类的 各个属性】
public class EqualsExercise01 {
    public static void main(String[] args) {
        Person person1 = new Person("爱摸鱼的TT~", 10, '男');
        Person person2 = new Person("爱摸鱼的TT~", 20, '男');

        System.out.println(person1.equals(person2));//重写之后就是真
    }
}
//判断两个Person对象的内容是否相等,
//如果两个Person对象的各个属性值都一样,则返回true,反之false
class Person{ //extends Object
    private String name;
    private int age;
    private char gender;

    //重写Object 的 equals方法
    public boolean equals(Object obj) {
        //判断如果比较的两个对象是同一个对象,则直接返回true
        if(this == obj) {
            return true;
        }
        //类型判断
        if(obj instanceof  Person) {//是Person,我们才比较

            //进行 向下转型, 因为我需要得到obj的 各个属性
            Person p = (Person)obj;
            return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
        }
        //如果不是Person ,则直接返回false
        return false;
    }

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

    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 char getGender() {
        return gender;
    }
    public void setGender(char gender) {
        this.gender = gender;
    }
}

8.8.3 == 和 equals 的区别

名称概念是否可以用于基本数据类型是否可以用于引用类型
==比较运算符可以,判断值是否相等可以,判断两个对象是否相等
equalsObject类的方法,Java类都可以使用equals不可以可以,默认是判断两个对象是否相等,但是子类往往重写该方法,比较对象的属性是否相等,比如String、Integer

8.8.4 巩固练习:equals 和 ==

  1. 案例1:判断分析输出语句的结果
public class EqualsExercise02 {
    public static void main(String[] args) {

        Person_ p1 = new Person_();
        p1.name = "爱摸鱼的TT~";

        Person_ p2 = new Person_();
        p2.name = "爱摸鱼的TT~";

        System.out.println(p1==p2); //False
        System.out.println(p1.name .equals( p2.name));//T 因为p1.name是字符串,字符串是重写了equals方法
        System.out.println(p1.equals(p2));//False 而p1是person类,是没有自动重写equals方法

        String s1 = new String("爱摸鱼的TT~");
        String s2 = new String("爱摸鱼的TT~");
        System.out.println(s1.equals(s2));//T
        System.out.println(s1==s2); //F
    }
}
class Person_{//类
    public String name;
}
  1. 案例2:判断分析输出语句的结果
int it = 65; 
float fl = 65.0f; 
System.out.println(6565.0f 是否相等?” + (it == fl));//T 

char ch1 =A; 
char ch2 = 12; 
System.out.println(65 和‘A’是否相等?” + (it == ch1));//T 
System.out.println(12 和 ch2 是否相等?” + (12 == ch2));//T 
String str1 = new String("hello"); 
String str2 = new String("hello"); 
System.out.println("str1 和 str2 是否相等?"+ (str1 == str2)); //F 
System.out.println(“str1 是否 equals str2?”+(str1.equals(str2)));//T 
System.out.println(“hello” == new java.sql.Date()); //编译错误

8.8.5 hashCode 方法

在这里插入图片描述

爱摸鱼的TT~的 6 个小结: 【记住!】
1) 提高具有哈希结构的容器的效率!
2) 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
3) 两个引用,如果指向的是不同对象,则哈希值是不一样的。【不是绝对的,如果在很大很大范围内,那就可能发生碰撞】
4) 哈希值主要根据地址号来的!不能完全将哈希值等价于地址。【注意这里的哈希值不是真正的内部地址,在Java中是无法得到真正的内部地址,因为它是运行在JVM中】
5) 案例演示[测试:A obj1 = new A(); A obj2 = new A(); A obj3 = obj1]
6) 后面在集合中 hashCode 如果需要的话,也会重写,在讲解集合时,爱摸鱼的TT~在说如何重写 hashCode()

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

        AA aa = new AA();
        AA aa2 = new AA();
        AA aa3 = aa;
        System.out.println("aa.hashCode()=" + aa.hashCode());//aa.hashCode()=460141958
        System.out.println("aa2.hashCode()=" + aa2.hashCode());//aa2.hashCode()=1163157884
        System.out.println("aa3.hashCode()=" + aa3.hashCode());//aa3.hashCode()=460141958
    }
}
class AA {}

8.8.6 toString 方法

在这里插入图片描述

  1. 基本介绍

默认返回:全类名【指包名+类名】+@+哈希值的十六进制,【查看 Object 的 toString 方法】

子类往往重写 toString 方法,用于返回对象的属性信息

//Object的toString() 源码
//(1)getClass().getName() 类的全类名(包名+类名 )
//(2)Integer.toHexString(hashCode()) 将对象的hashCode值转成16进制字符串
public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

举例:没有重写toString方法的运行结果:

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

        Monster monster = new Monster("小妖怪", "巡山的", 1000);
        System.out.println(monster.toString() + " hashcode=" + monster.hashCode());
    }
}

class Monster {
    private String name;
    private String job;
    private double sal;

    public Monster(String name, String job, double sal) {
        this.name = name;
        this.job = job;
        this.sal = sal;
    }
}

运行结果:在这里插入图片描述

  1. 重写 toString 方法,目的是打印对象或拼接对象时,都会自动调用该对象的 toString 形式;
public class ToString_ {
    public static void main(String[] args) {

        Monster monster = new Monster("小妖怪", "巡山的", 1000);
        System.out.println(monster.toString() + " hashcode=" + monster.hashCode());
    }
}

class Monster {
    private String name;
    private String job;
    private double sal;

    public Monster(String name, String job, double sal) {
        this.name = name;
        this.job = job;
        this.sal = sal;
    }

    //重写toString方法, 输出对象的属性
    //使用快捷键即可 alt+ c -> toString
    @Override
    public String toString() { //重写后,一般是把对象的属性值输出,当然程序员也可以自己定制
        return "Monster{" +
                "name='" + name + '\'' +
                ", job='" + job + '\'' +
                ", sal=" + sal +
                '}';
    }
}

运行结果:在这里插入图片描述

  1. 当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调用monster.toString()
//补充代码
System.out.println("==当直接输出一个对象时,toString 方法会被默认的调用==");
System.out.println(monster); //等价 monster.toString()

运行结果:在这里插入图片描述

8.8.7 finalize 方法

在这里插入图片描述

    1. 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作【什么是资源?比如数据库的连接就是资源、又如对象在调用时打开文件,也是一种资源】
    1. 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。
    1. 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法,后面会讲解), 也可以通过** System.gc() **主动触发垃圾回收机制,测试:Car [name]

爱摸鱼的TT~提示: 我们在实际开发中,几乎不会运用 finalize , 所以更多就是为了应付面试。

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

        Car bmw = new Car("宝马");
        //这时 car对象就是一个垃圾,垃圾回收器就会回收(销毁)对象, 在销毁对象前,会调用该对象的finalize方法
        //,程序员就可以在 finalize中,写自己的业务逻辑代码(比如释放资源:数据库连接,或者打开文件..)
        //,如果程序员不重写 finalize,那么就会调用 Object类的 finalize, 即默认处理
        //,如果程序员重写了finalize, 就可以实现自己的逻辑
        bmw = null;
        System.gc();//主动调用垃圾回收器

        System.out.println("程序退出了....");
    }
}
class Car {
    private String name;
    //属性, 资源。。
    public Car(String name) {
        this.name = name;
    }
    //重写finalize
    @Override
    protected void finalize() throws Throwable {
        System.out.println("我们销毁 汽车" + name );
        System.out.println("释放了某些资源...");//模拟而已
    }
}

运行结果:在这里插入图片描述

8.9 IDEA断点调试(debug)【较为重要】

1. 一个实际需求

  1. 在开发中,新手程序员在查找错误时,这时老程序员就会温馨提示,可以用断定调试,一步一步的看源码执行的过程,从而发现错误所在。
  2. 重要提醒:在断点调试过程中,是运行状态,是以对象的运行类型来执行的。
A extends B; B b = new A(); b.xx();

2. 断点调试介绍

  1. 断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后你可以一步一步往下调试,调试过程中可以看看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。进行分析从而找到这个bug。
  2. 断点调试时程序员必须掌握的技能。
  3. 断点调试也能帮助我们查看java底层源代码的执行过程,提高程序员的Java水平。

3. 断点调试的快捷键

  • F7(跳入):跳入方法内
  • F8(跳过) :逐行执行代码
  • shift+F8(跳出): 跳出方法
  • F9(resume,执行到下一个断点)

在这里插入图片描述

4. 断点调试应用案例

1)案例1:看一下变量的变化情况等

public class Debug01 {
    public static void main(String[] args) {
        //演示逐行执行代码
        int sum = 0;
        for (int i = 0; i < 5; i++) {
            sum += i;
            System.out.println("i=" + i);
            System.out.println("sum=" + i);
        }
        System.out.println("退出for....");
    }
}

运行debug F8快捷键操作

在这里插入图片描述

2)看一下数组越界的异常

public class Debug02 {
    public static void main(String[] args) {
        int[] arr = {1, 10, -1};
        for (int i = 0; i <= arr.length; i++) {
            System.out.println(arr[i]);
        }
        System.out.println("退出for");
    }
}

在这里插入图片描述

3)演示如何追源码,看看 java 设计者是怎么实现的。(提高编程思想)。
小技巧:将光标放在某个变量上,可以看到最新的数据。【F7跳入】
在这里插入图片描述

//现在我们分析sort方法的源码
public class Debug03 {
    public static void main(String[] args) {
        int[] arr = {1, -1, 10, -20 , 100};
        //我们看看Arrays.sort方法底层实现.->Debug
        Arrays.sort(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }
    }
}

采用快捷键F7 和 shif + F8
在这里插入图片描述

好了,大致就这样查看方法的源码,但是有些同学按了快捷键不起作用,那该怎么办呢?需要自己设置好相关配置,具体如下:

点击Setting --> Build,Execution,Deployment --> Debugger --> Stepping
把Do not step into the classes中的java.*,javax.*取消勾选,其他的随意

在这里插入图片描述

4)演示如何直接执行到下一个断点 F9 resume。
爱摸鱼的TT~小技巧: 断点可以在 debug 过程中,动态的下断点

//演示执行到下一个断点,同时支持动态的下断点.
public class Debug04 {
    public static void main(String[] args) {

        int[] arr = {1, -1, 10, -20 , 100};
        //我们看看Arrays.sort方法底层实现.->Debug
        Arrays.sort(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println("hello100");
        System.out.println("hello200");
        System.out.println("hello300");
        System.out.println("hello400");
        System.out.println("hello500");
        System.out.println("hello600");
        System.out.println("hello700");
    }
}

在这里插入图片描述

巩固习题

断点调试需要多加练习,慢慢熟悉它,之后会经常要用到的,像研究算法,底层东西时就会采用断点调试来理解代码。
在这里插入图片描述

8.10 项目-零钱通

8.10.1 项目开发流程说明

8.10.2 项目需求说明

使用 Java 开发 零钱通项目 , 可以完成收益入账,消费,查看明细,退出系统等功能。

8.10.3 项目的界面

在这里插入图片描述

化繁为简:

    1. 先完成显示菜单,并可以选择
    1. 完成零钱通明细.
    1. 完成收益入账
    1. 消费
    1. 退出

8.10.4 项目代码实现

编写文件 SmallChangeSys.java 完成基本功能 (过程编程)

爱摸鱼的TT~提示:先使用过程编程,后面改成 OOP 版本,请小伙伴体会 OOP 编程带来的好处

package smallchange;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-09 16:22
 * @Modified By:
 */
public class SmallChangeSys {

    //化繁为简
    //1. 先定义显示菜单,并可以选择菜单,给出相应信息
    //2. 完成零钱通明细
    //3. 完成收益入账
    //4. 完成消费
    //5. 退出
    //下面是改进代码
    //6. 用户输入4退出时,给出提示“你确定要退出吗?y/n”,必须输入正确的y/n否则循环输入指令,知道输入y或者n。
    //7. 在收益入账和消费时,判断金额是否合理,并给出相应的提示。

    public static void main(String[] args) {
        //定义相关变量
        boolean loop = true;
        Scanner myscanner = new Scanner(System.in);
        String key = "";

        //2. 完成零钱通明细
        //爱摸鱼的TT~思路:
        //(1)可以把收益入账和消费,保存到数组
        //(2)可以使用对象
        //(3)简单的话可以使用String拼接
        String details = "--------------零钱通明细--------------";

        //3. 完成收益入账
        //完成功能驱动程序员增加新的变量和代码
        //爱摸鱼的TT~思路,定义新的变量
        double money = 0;
        double balance = 0; // 余额
        Date date = null; // date 是java.util.Date类型,表示日期
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); // 可以用于日期格式化的对象

        //4. 消费
        //定义新变量,记录保存消费的原因
        String note = "";

        //1. 先定义显示菜单,并可以选择菜单,给出相应信息
        do{
            System.out.println("\n=============零钱通菜单=============");
            System.out.println("\t\t\t1 零钱通明细");
            System.out.println("\t\t\t2 收益入账");
            System.out.println("\t\t\t3 消    费");
            System.out.println("\t\t\t4 退    出");

            System.out.println("请选择(1-4)");
            key = myscanner.next();

            switch(key){
                case "1":
                    System.out.println(details);
                    break;
                case "2":
                    System.out.println("收益入账金额:");
                    money = myscanner.nextDouble();
                    //money的值范围应该校验
                    //爱摸鱼的TT~思路
                    //编程思想:就是在一系列判断时,找不符合条件即可【过关斩将 校验方式】
                    //找出不正确的金额条件,然后给出提示,就直接break
                    if(money <= 0){
                        System.out.println("收益入账金额 需要 大于 0");
                        break;
                    }
                    date = new Date();
                    balance += money;
                    //拼接收益入账信息到“details”
                    details += "\n收益入账\t+" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
                    break;
                case "3":
                    System.out.println("消费金额:");
                    money = myscanner.nextDouble();
                    //money的值范围应该校验
                    //找出金额不正确的情况
                    //过关斩将 校验方式
                    if(money <= 0 || money > balance){
                        System.out.println("你的消费金额 应该在 0-" + balance);
                        break;
                    }
                    System.out.println("消费说明:");
                    note = myscanner.next();
                    balance -= money;
                    //拼接消费信息到“details”
                    details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
                    break;
                case "4":
                    //用户输入4退出时,给出提示“你确定要退出吗?y/n”,必须输入正确的y/n否则循环输入指令,知道输入y或者n。
                    //爱摸鱼的TT~思路分析:
                    //(1)定义一个变量 choice,接收用户的收入
                    //(2)使用while循环 + break,来处理接收到的输入时 y 或者 n
                    //(3)退出while后,在判断choice是y还是n,就可以决定是否退出
                    //(4)建议一段代码,完成一个小功能,尽量不要混在一起

                    String choice = "";
                    while(true){//要求用户输入y/n,否则就继续循环
                        System.out.println("你确定要退出吗?y/n");
                        choice = myscanner.next();
                        if("y".equals(choice) || "n".equals(choice)){
                            break;
                        }
                        //第二种方案:一个while循环含有两个功能
//                        if("y".equals(choice)){
//                            loop = false;
//                            break;
//                        }else if("n".equals(choice)){
//                            break;
//                        }
                    }
                    //当用户退出while,进行判断是y 还是n
                    if(choice.equals("y")){
                        loop = false;
                    }
                    break;
                default:
                    System.out.println("选择有误,请重新选择");
            }
        }while(loop);

        System.out.println("\n--------退出了零钱通项目--------");

    }
}

8.10.5 项目代码实现改进

  1. 用户输入4退出时,给出提示“你确定要退出吗?y/n”,必须输入正确的y/n否则循环输入指令,知道输入y或者n。
  2. 在收益入账和消费时,判断金额是否合理,并给出相应的提示。

以上两个代码改进在8.10.4中实现了,大家需要认真分析解读里面的注释!!!

  1. 将面向过程的代码修改成面向对象的,编写SmallChangeSysOOP.java类,并使用SmallChangeSysApp.java完成测试。

在这里插入图片描述

  • SmallChangeSysOOP类
package smallchange.oop;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/**
 * 该类是完成零钱通的各个功能的类
 * 使用OOP(面向对象编程)
 * 将各个功能对应一个方法.
 */
public class SmallChangeSysOOP {

    //属性..
    //定义相关变量
    boolean loop = true;
    Scanner myscanner = new Scanner(System.in);
    String key = "";

    //2. 完成零钱通明细
    //爱摸鱼的TT~思路:
    //(1)可以把收益入账和消费,保存到数组
    //(2)可以使用对象
    //(3)简单的话可以使用String拼接
    String details = "--------------零钱通明细--------------";

    //3. 完成收益入账
    //完成功能驱动程序员增加新的变量和代码
    //爱摸鱼的TT~思路,定义新的变量
    double money = 0;
    double balance = 0; // 余额
    Date date = null; // date 是java.util.Date类型,表示日期
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); // 可以用于日期格式化的对象

    //4. 消费
    //定义新变量,记录保存消费的原因
    String note = "";

    //先完成显示菜单,并可以选择
    public void mainMenu() {
        //1. 先定义显示菜单,并可以选择菜单,给出相应信息
        do{
            System.out.println("\n=============零钱通菜单(OOP)=============");
            System.out.println("\t\t\t1 零钱通明细");
            System.out.println("\t\t\t2 收益入账");
            System.out.println("\t\t\t3 消    费");
            System.out.println("\t\t\t4 退    出");

            System.out.println("请选择(1-4)");
            key = myscanner.next();

            //使用switch 分支控制
            switch (key) {
                case "1":
                    this.detail();
                    break;
                case "2":
                    this.income();
                    break;
                case "3":
                    this.pay();
                   break;
                case "4":
                    this.exit();
                    break;
                default:
                    System.out.println("选择有误,请重新选择");
            }

        } while (loop);

        System.out.println("\n--------退出了零钱通项目--------");
    }

    //完成零钱通明细
    public void detail() {
        System.out.println(details);
    }
    //完成收益入账
    public void income() {
        System.out.println("收益入账金额:");
        money = myscanner.nextDouble();
        //money的值范围应该校验
        //爱摸鱼的TT~思路
        //编程思想:就是在一系列判断时,找不符合条件即可【过关斩将 校验方式】
        //找出不正确的金额条件,然后给出提示,就直接break
        if(money <= 0){
            System.out.println("收益入账金额 需要 大于 0");
            return;//
        }
        date = new Date();
        balance += money;
        //拼接收益入账信息到“details”
        details += "\n收益入账\t+" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
    }
    //消费
    public void pay() {
        System.out.println("消费金额:");
        money = myscanner.nextDouble();
        //money的值范围应该校验
        //找出金额不正确的情况
        //过关斩将 校验方式
        if(money <= 0 || money > balance){
            System.out.println("你的消费金额 应该在 0-" + balance);
            return;
        }
        System.out.println("消费说明:");
        note = myscanner.next();
        balance -= money;
        //拼接消费信息到“details”
        details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
    }

    //退出
    public void exit() {
        //用户输入4退出时,给出提示“你确定要退出吗?y/n”,必须输入正确的y/n否则循环输入指令,知道输入y或者n。
        //爱摸鱼的TT~思路分析:
        //(1)定义一个变量 choice,接收用户的收入
        //(2)使用while循环 + break,来处理接收到的输入时 y 或者 n
        //(3)退出while后,在判断choice是y还是n,就可以决定是否退出
        //(4)建议一段代码,完成一个小功能,尽量不要混在一起

        String choice = "";
        while(true){//要求用户输入y/n,否则就继续循环
            System.out.println("你确定要退出吗?y/n");
            choice = myscanner.next();
            if("y".equals(choice) || "n".equals(choice)){
                break;
            }
            //第二种方案:一个while循环含有两个功能
//                        if("y".equals(choice)){
//                            loop = false;
//                            break;
//                        }else if("n".equals(choice)){
//                            break;
//                        }
        }
        //当用户退出while,进行判断是y 还是n
        if(choice.equals("y")){
            loop = false;
        }
    }
}
  • SmallChangeSysApp类
package smallchange.oop;

/**
 * 这里我们直接调用SmallChangeSysOOP 对象,显示主菜单即可
 */
public class SmallChangeSysApp {

    public static void main(String[] args) {
        new SmallChangeSysOOP().mainMenu();
    }
}

到此…

8.11 本章巩固练习

  1. 定义一个Person类(name,age,job),初始化Person对象数组,有3个Person对象,并按照age从大到小进行排序,提示:使用冒泡排序。
package com.homework;

/**
 * @Author: 爱摸鱼的TT~
 * @Description: 1. 定义一个Person类(name,age,job),初始化Person对象数组,有3个Person对象,并按照age从大到小进行排序,
 *               提示:使用冒泡排序。
 * @Date Created in 2021-12-13 19:57
 * @Modified By:
 */
public class Homework01 {
    public static void main(String[] args) {
        //初始化Person 对象数组,有3个person对象
        Person[] persons = new Person[3];
        persons[0] = new Person("爱摸鱼的TT", 18, "在校生");
        persons[1] = new Person("芳芳", 21, "实习生");
        persons[2] = new Person("小明", 16, "电子厂");

        //输出当前对象数组
        for(int i = 0; i < persons.length; i++){
            System.out.println(persons[i]);
        }

        //使用冒泡排序
        Person temp = null;//临时变量,用于交换
        for(int i = 0; i < persons.length - 1; i++){//外层循环
            for(int j = 0; j < persons.length - 1 -i; j++){//内层循环
                //并按照 age 从 大到 小进行排序, 如果前面的人的age < 后面人的年龄,就交换                
                if(persons[j].getAge() < persons[j + 1].getAge()){
                    temp = persons[j];
                    persons[j] = persons[j + 1];
                    persons[j + 1] = temp;
                }
                //要求按照名字的长度从小到大 
                //if(persons[j].getName().length() > persons[j+1].getName().length()){}
            }
        }

        System.out.println("排序后的效果");
        for(int i = 0; i < persons.length; i++){
            System.out.println(persons[i]);
        }
    }
}
class Person{
    private String name;
    private int age;
    private String job;

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

    }

    //setter 和 getter
    public void setName(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }

    public int getAge() {
        return age;
    }

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

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", job='" + job + '\'' +
                '}';
    }
}
  1. 编写老师类
    1. 要求有属性“姓名name”,“年龄age”,“职称post”,“基本工资salary”
    2. 编写业务方法,introduce( ),实现输出一个教师信息。
    3. 编写教师类的三个子类:教授类(Professor)、副教授类、讲师类。工资级别分别为教授1.3、副教授为1.2、讲师为1.1,在三个子类里面都重写父类的introduce()方法。
    4. 定义并初始化一个老师对象,调用业务方法,实现对象基本信息的后台打印。
package com.homework;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:   a. 要求有属性“姓名name”,“年龄age”,“职称post”,“基本工资salary”
 *                 b. 编写业务方法,introduce( ),实现输出一个教师信息。
 * @Date Created in 2021-12-13 20:26
 * @Modified By:
 */
public class Teacher {
    private String name;
    private int age;
    private String post;
    private double salary;
    //这里我们在增加一个工资级别
    private double grade;

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

    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 String getPost() {
        return post;
    }

    public void setPost(String post) {
        this.post = post;
    }

    public double getSalary() {
        return salary;
    }

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

    public double getGrade() {
        return grade;
    }

    public void setGrade(double grade) {
        this.grade = grade;
    }

    public void introduce(){
        System.out.println("name: " + name + ", age: " + age + ", post: " + post + ", salary: " + salary + ", grade: " + grade);
    }
}

class Professor extends Teacher{
    //特有属性...
    public Professor(String name, int age, String post, double salary, double grade) {
        super(name, age, post, salary, grade);
    }

    @Override
    public void introduce() {
        System.out.println(" 这是教授的信息 ");
        super.introduce();
    }
}

测试类:

package com.homework;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-13 20:25
 * @Modified By:
 */
public class Homework02 {
    public static void main(String[] args) {
        Professor professor = new Professor("爱摸鱼的TT~", 30, "高级职称", 30000, 1.3);
        professor.introduce();
    }
}
  1. 通过继承实现员工工资核算打印功能
    1. 父类:员工类(Employee)
    2. 子类:部门经理类(Manager)、普通员工(Worker)
      1. 部门经理工资 = 1000 + 单日工资天数等级(1.2) => 奖金 + 基本工资
      2. 普通员工工资 = 单日工资天数等级(1.0) => 基本工资
      3. 员工属性:姓名,单日工资,工作天数
      4. 员工方法(打印工资)
      5. 普通员工及部门经理都是员工子类,需要重写打印工资方法
      6. 定义并初始化普通员工对象,调用打印工资方法输出工资,定义并初始化部门经理对象,调用工资方法输出工资
package com.homework;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-13 20:40
 * @Modified By:
 */
public class Employee {
    //属性
    //员工属性:姓名,单日工资,工作天数
    private String name;
    private double daySal;
    private int dayWork;
    //分析出还有一个属性等级
    private double grade;

    public Employee(String name, double daySal, int dayWork, double grade) {
        this.name = name;
        this.daySal = daySal;
        this.dayWork = dayWork;
        this.grade = grade;
    }

    public Employee() {
    }

    public String getName() {
        return name;
    }

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

    public double getDaySal() {
        return daySal;
    }

    public void setDaySal(double daySal) {
        this.daySal = daySal;
    }

    public int getDayWork() {
        return dayWork;
    }

    public void setDayWork(int dayWork) {
        this.dayWork = dayWork;
    }

    public double getGrade() {
        return grade;
    }

    public void setGrade(double grade) {
        this.grade = grade;
    }

    //打印工资方法
    public void printSalary(){
        System.out.println(name + " 工资是 " + daySal * dayWork * grade);
    }
}

class Manager extends Employee{
    //特有属性
    private double bonus;
    //创建Manager对象时,奖金是多少并不是确定的,因为老师在构造器中,不给bonus
    //可以通过setBonus
    public Manager(String name, double daySal, int dayWork, double grade) {
        super(name, daySal, dayWork, grade);
    }

    //方法:重写父类的 printSal
    @Override
    public void printSalary() {
        //因为经理的工资计算方式和Employee不一样,所以我们重写
        System.out.println("经理 " + getName() + " 工资是 "
                + (bonus + getDaySal() * getDayWork() * getGrade()));
    }

    public double getBonus() {
        return bonus;
    }

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

class Worker extends Employee{
    //分析普通员工没有特有的属性
    public Worker(String name, double daySal, int dayWork, double grade) {
        super(name, daySal, dayWork, grade);
    }

    //重写printSal
    //因为普通员工和Employee输出工资情况一样,所以直接调用父类的printSalary()
    @Override
    public void printSalary() {
        System.out.print("普通员工 ");//自己的输出信息
        super.printSalary();//调用父类的方法,复用代码
    }
}

测试类:

package com.homework;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-13 20:36
 * @Modified By:
 */
public class Homework03 {
    public static void main(String[] args) {
        Manager manage = new Manager("刘备", 100, 20, 1.2);
        //设置奖金
        manage.setBonus(3000);
        //打印经理的工资情况
        manage.printSalary();

        Worker worker = new Worker("关羽",50, 10, 1.0);
        worker.printSalary();
    }
}
  1. 设计父类一员工类,子类:工人类(Worker)、农民类(Peasant)、教师类(Teacher)、科学家类(Scientist)、服务生类(Waiter)
    1. 其中工人、农民、服务生只有基本工资sal
    2. 教师除基工资外,还有课酬(元/天)classDay,classSal
    3. 科学家除基本工资外,还有年终奖bonus
    4. 编写一个测试类,将各种类型的员工的全年工资打印出来
//父类
public class Employee {
    //属性
    private String name;
    private double sal;
    //分析有一个带薪的月份,不固定
    private int salMonth = 12;

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

    public String getName() {
        return name;
    }

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

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    public int getSalMonth() {
        return salMonth;
    }

    public void setSalMonth(int salMonth) {
        this.salMonth = salMonth;
    }

    //打印全年工资
    public void printSal(){
        System.out.println(name + "年工资是" + (sal * salMonth));
    }
}

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

    @Override
    public void printSal() {
        System.out.print("工人 ");
        super.printSal(); //使用父类的printSal()
    }
}

class Peasant extends Employee{
    public Peasant(String name, double sal) {
        super(name, sal);
    }

    @Override
    public void printSal() {
        System.out.print("农民 ");
        super.printSal();
    }
}
...
class Teacher extends Employee{
    //特有属性
    private int classDays;//一年上课的次数
    private double classSal;//课时费

    public Teacher(String name, double sal) {
        super(name, sal);
    }

    public int getClassDays() {
        return classDays;
    }

    public void setClassDays(int classDays) {
        this.classDays = classDays;
    }

    public double getClassSal() {
        return classSal;
    }

    public void setClassSal(double classSal) {
        this.classSal = classSal;
    }

    @Override
    public void printSal() {
        System.out.print("老师 ");
        System.out.println(getName() + "年工资是 "
                + (getSal() * getSalMonth() + classDays * classSal));
    }
}

class Scientist extends Employee{
    //特有属性
    private double bonus;

    public Scientist(String name, double sal) {
        super(name, sal);
    }

    public double getBonus() {
        return bonus;
    }

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

    @Override
    public void printSal() {
        System.out.print("科学家 ");
        System.out.println(getName() + "年工资是 "
                + (getSal() * getSalMonth() + bonus));
    }
}

测试类:

package com.homework.homework04;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-13 22:13
 * @Modified By:
 */
public class Homework04 {
    public static void main(String[] args) {
        Worker worker = new Worker("小明", 10000);
        worker.setSalMonth(10);
        worker.printSal();

        Peasant peasant = new Peasant("smith", 20000);
        peasant.setSalMonth(8);
        peasant.printSal();

        Waiter waiter = new Waiter("John", 8000);
        waiter.setSalMonth(12);
        waiter.printSal();

        //老师测试
        Teacher teacher = new Teacher("爱摸鱼的TT~", 2000);
        //老师有课时费
        teacher.setClassDays(360);
        teacher.setClassSal(1000);
        teacher.printSal();

        //科学家
        Scientist scientist = new Scientist("钟南山", 20000);
        scientist.setBonus(2000000);
        scientist.printSal();
    }
}
  1. 假定Frand、Father和Son在同一个包,问:父类和子类中通过this和super都可以调用哪些属性和方法?
class Grand{//超类
    String name = "AA";
    private int age = 100;
    public void g1(){}
}

class Father extends Grand{//父类
    String id = "001";
    private double score;
    //super可以访问哪些成员(属性和方法)?
    super.name;super.g1();
    //this可以访问哪些成员?
    this.id;this.score;this.f1();this.name;this.g1();
}

class Son extends Father{//子类
    String name = "BB";
    public void g1(){}
    private void show(){
    	//super可以访问哪些成员?
        super.id;super.f1();super.name;super.g1();
        //this可以访问哪些成员?
        this.name;this.g1();this.show();this.id;this.f1();
    }
  1. 现有Person类,里面又方法run、eat,Student类继承了Person类,并重写了run方法,自定义了study方法,试写出对象向上转型和向下转型的代码,并写出各自都可以调用哪些方法,并写出方法输出什么?
class Person{//父类
    public void run(){
        System.out.println("person run");
    }
    public void eat(){
        System.out.println("person eat");
    }
}

calss Student extends Person{//子类
    public void run(){
        System,out.println("studnet run");
    }
    public void study(){
        System.out.println("studnet study...");
    }
}

答案:
//向上转型:父类的引用指向子类对象
Person p = new Studnet();
p.run();//student run
p.eat();//person eat
//向下转型:把指向子类对象的父类引用,转成指向子类对象的子类引用
Student s = (Student)p;

  1. 编写Doctor类(name、age、job、gender、sal),相应的getter和setter方法,5个参数的构造方法,重写父类(Object)的equals()方法,并判断测试类中的创建的两个对象相等。相等就是判断属性是否相同。
public class Doctor {
    //属性
    //{name, age, job, gender, sal}
    private String name;
    private int age;
    private String job;
    private char gender;
    private double sal;
    //5个参数的构造器

    public Doctor(String name, int age, String job, char gender, double sal) {
        this.name = name;
        this.age = age;
        this.job = job;
        this.gender = gender;
        this.sal = sal;
    }

    //方法
    //相应的getter()和setter()方法

    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 String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    //重写父类(Object)的equals()方法:public boolean equals(Object obj),并判断测试类中创建的两个对象是否相等。相等就是判断属性是否相同
    public boolean equals(Object obj) {
        //判断两个比较对象是否相同
        if (this == obj) {
            return true;
        }
        //判断obj 是否是 Doctor类型或其子类
        //过关斩将 校验方式
        if (!(obj instanceof Doctor)) { //不是的话
            return false;
        }

        //向下转型, 因为obj的运行类型是Doctor或者其子类型
        Doctor doctor = (Doctor)obj;
        return this.name.equals(doctor.name) && this.age == doctor.age &&
                this.gender == doctor.gender && this.job.equals(doctor.job) && this.sal == doctor.sal;
    }
}

测试类:

public class Homework10 {
    public static void main(String[] args) {
        //测试
        Doctor doctor1 = new Doctor("jack", 20, "牙科医生", '男', 20000);
        Doctor doctor2 = new Doctor("jack", 20, "牙科医生", '男', 20000);

        System.out.println(doctor1.equals(doctor2));//T
    }
}
  1. (综合题)案例题目描述

(1)做一个Student类,Student类有名称(name),性别(sex),年龄(age)学号(stu_id),做合理封装,通过构造器在创建对象时将4个属性赋值。
(2)写一个Teacher类,Teacher类有名称(name),性别(sex),年龄(age),工龄(work_age),做合理的封装,通过构造器在创建时将4个属性赋值。
(3)抽取一个父类Person类,将共同和方法放到Person类。
(4)学生需要又学习的方法(study),在方法里写上“我承诺,我会好好学习。”
(5)教师需要有教学的方法(teach),在方法里写上“我承诺,我会认真教学。”
(6)学生和教师都有玩的方法(Play),学生玩的是足球,老师玩的是象棋,此方法是返回字符串的,分别返回“xx爱玩足球”和“xx爱玩象棋”(其中xx分别代表学生和老师的姓名)。因为玩的方法名称都一样,所以要求此方法定义在父类中,子类实现重写。
应当分析出,我们打印信息的方法,printlnfo()【先完成到第6题】
【Person类】

package com.homework.Homework08;

import javax.lang.model.element.NestingKind;

/**
 * @Author: 爱摸鱼的TT~
 * @Description: 抽取一个父类Person类,将共同属性和方法放到Person类
 * @Date Created in 2021-12-14 8:26
 * @Modified By:
 */
public class Person {
    private String name;
    private char sex;
    private int age;

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

    public Person() {
    }

    public String getName() {
        return name;
    }

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

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

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

    //编写一个play 方法, 把共有的输出内容写到父类
    public String play(){
        return name + "爱玩";
    }

    public String printInfo(){
        return "姓名:" + name + "\n" + "年龄:" + age + "\n" + "性别:" + sex + "\n";
    }

    @Override
    public String toString() {
        return printInfo();
    }
}

【Student类】

package com.homework.Homework08;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-14 8:34
 * @Modified By:
 */
public class Student extends Person {
    private String stu_id;

    public Student(String name, int age, char sex, String stu_id) {
        super(name, age, sex);
        this.stu_id = stu_id;
    }

    public String getStu_id() {
        return stu_id;
    }

    public void setStu_id(String stu_id) {
        this.stu_id = stu_id;
    }

    public String study(){
        return "我承诺,我会好好学习。";
    }
    @Override
    public String play() {
        return super.play() + "足球。";
    }

    @Override
    public String printInfo() {
        System.out.println("学生的信息:");
        return super.printInfo() + "学号:" + stu_id + "\n" + study() + "\n" + play();
    }

    @Override
    public String toString() {
        return printInfo();
    }
}

【Teacher类】

package com.homework.Homework08;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-14 8:41
 * @Modified By:
 */
public class Teacher extends Person{
    private int work_age;

    public Teacher(String name, int age, char sex, int work_age) {
        super(name, age, sex);
        this.work_age = work_age;
    }

    public int getWork_age() {
        return work_age;
    }

    public void setWork_age(int work_age) {
        this.work_age = work_age;
    }

    public String teach(){
        return "我承诺,我会认真教学。";
    }

    @Override
    public String play() {
        return super.play() + "象棋。";
    }

    @Override
    public String printInfo() {
        System.out.println("老师的信息");
        return super.printInfo() + "工龄:" + work_age + "\n" + teach() + "\n" + play();
    }

    @Override
    public String toString() {
        return printInfo();
    }
}

【测试类】

package com.homework.Homework08;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-14 8:44
 * @Modified By:
 */
public class Homework08 {
    public static void main(String[] args) {
        Student stu = new Student("小张",18,'男',"1912406030236");
        System.out.println(stu.printInfo());

        System.out.println("--------------------------------");

        Teacher teacher = new Teacher("爱摸鱼的TT~", 24, '男', 1);
        System.out.println(teacher.printInfo());
    }
}

在这里插入图片描述

(7)定义多态数组,里面保存2个学生和2个教师,要求按照年龄从高到底排序。
(8)定义方法,形参为Person类型,功能:调用学生的study或教师的teach。
【最后两题好好分析题意,难度不大】

package com.homework.Homework08;

/**
 * @Author: 爱摸鱼的TT~
 * @Description:
 * @Date Created in 2021-12-14 8:44
 * @Modified By:
 */
public class Homework08 {
    public static void main(String[] args) {

        //定义多态数组,里面保存2个学生和2个教师,要求按年龄从高到低排序
        Person[] persons = new Person[4];
        persons[0] = new Student("jack", 10, '男', "0001");
        persons[1] = new Student("mary", 20,'女',  "0002");
        persons[2] = new Teacher("smith", 36,'男',  5);
        persons[3] = new Teacher("scott", 26,'男',  1);

        Homework08 homework08 = new Homework08();
        homework08.bubbleSort(persons);

        //输出排序后的数组
        System.out.println("---排序后的数组-----");
        for(int i = 0; i < persons.length; i++) {
            System.out.println(persons[i]);//persons[i].toString()
        }

        //遍历数组,调用test方法
        System.out.println("=======================");
        for (int i = 0; i < persons.length; i++) {//遍历多态数组
            homework08.test(persons[i]);
        }
    }
    //(7)方法,完成年龄从高到底排序
    public void bubbleSort(Person[] persons){
        Person temp = null;
        //冒泡排序法
        for(int i = 0; i < persons.length - 1; i++){
            for(int j = 0; j < persons.length - 1 - i; j++){
                //判断条件, 注意这里的条件可以根据需要,变化
                if(persons[j].getAge() < persons[j+1].getAge()){
                    temp = persons[j];
                    persons[j] = persons[j+1];
                    persons[j+1] = temp;
                }
            }
        }
    }

    //(8)定义方法,形参为Person类型,功能:调用学生的study或教师的teach方法
    //分析这里会使用到向下转型和类型判断
    public void test(Person p) {
        if(p instanceof Student) {//p 的运行类型如果是Student
            System.out.println(((Student) p).study());
        } else if(p instanceof  Teacher) {
            System.out.println(((Teacher) p).teach());
        } else {
            System.out.println("do nothing...");
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涛涛同学debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值