OOP特征二:继承性

继承性的好处

  1. 减少代码冗余,提高了代码的复用性;
  2. 便于功能的扩展;
  3. 为之后多态性的使用,提供了前提。

继承性的格式:class A extends B{}

  1. A:子类、派生类、subclass

  2. B:父类、超类、基类、superclass

  3. 体现:一旦子类A继承父类B后,子类A就获取了父类B中所有的属性和方法。

  4. 特别地:父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构,只是因为封装性的影响,使得子类不能直接调用父类的结构而已。

  5. 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。子类和父类的关系,不同于子集和集合的关系。extends:延展,扩展。

Java中关于继承性的规定

  1. 一个类可以被多个子类继承;
  2. Java中类的单继承性:一个类只能有一个父类;
  3. 子类是相对概念;
  4. 子类直接继承的父类称为直接父类,间接继承的父类称为间接父类;
  5. 子类继承父类以后就获取了直接父类以及所有间接父类声明的属性和方法。

java.lang.Object类的理解

  1. 如果没有显式地声明一个类的父类的话,则此类继承于java.lang.Object类;
  2. 所有的Java类(除java.lang.Object类之外都直接或间接的继承于java.lang.Object类;
  3. 意味着,所有的Java类具java.lang.Object类声明的功能。

练习

  1. 定义一个学生类Student,它继承自Person类。

    Person
    name:String
    sex:char
    age:int
    +Person(name:String sex:char age:int)
    +toString():String

    ^

    |

    Student
    number:long
    math:int
    english:int
    computer:int
    +Student(n:String s:char a:int k:long m:int e:int c:int)
    +aver():double
    +max():int
    +min():int
    +toString():String

    (1) Person类

    package top.triabin._01;

    public class Person {
    String name;
    char sex;
    int age;

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

    public void setName(String name) {
    this.name = name;
    }
    public String getName() {
    return this.name;
    }
    public void setSex(char sex) {
    this.sex = sex;
    }
    public char getSex() {
    return this.sex;
    }
    public void setAge(int age) {
    this.age = age;
    }
    public int getAge() {
    return this.age;
    }
    @Override
    public String toString() {
    return "name: " + this.name + "\nsex: " + this.sex + "\nage: " + this.age;
    }
    }
package top.triabin._01;

public class Student extends Person {
long number;
int math;
int english;
int computer;

public Student(String n,char s,int a,long k,int m,int e,int c) {
super(n,s,a);
this.number = k;
this.math = m;
this.english = e;
this.computer = c;
}

public long getNumber() {
return number;
}
public void setNumber(long number) {
this.number = number;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getEnglish() {
return english;
}
public void setEnglish(int english) {
this.english = english;
}
public int getComputer() {
return computer;
}
public void setComputer(int computer) {
this.computer = computer;
}
public double aver() {
return (this.math + this.english + this.computer) / 3;
}
public int max() {
return (((this.math > this.english)? this.math : this.english) > this.computer)? ((this.math > this.english)? this.math : this.english) : this.computer;
}
public int min() {
int minValue = (this.math < this.english)? this.math : this.english;
minValue = (minValue < this.computer)? minValue : this.computer;
return minValue;
}
@Override
public String toString() {
return "name: " + this.name + "\nsex: " + this.sex + "\nage: " + this.age + "\nnumber: " + this.number + "\nmath=" + this.math + "\nEnglish=" + this.english +"\ncomputer=" + this.computer;
}
}
  1. (1)定义一个ManKind类,包括

    • 成员变量int sex和int salary;
    • 方法void mainOrWoman():根据sex的值显式“man“(sex==1)或者“woman”(sex==0);
    • 方法void employeed():根据salary的值显示“nojob”(salary==0)或者“job”(salary==1)。
    package top.trisbin.inheritanceex2;

    public class ManKind {
    private int sex;//性别
    private int salary;//工作状


    public ManKind() {
    //空参构造器
    }
    public ManKind(int sex, int salary) {
    this.sex = sex;
    this.salary = salary;
    }


    public int getSex() {
    return sex;
    }
    public void setSex(int sex) {
    this.sex = sex;
    }
    public int getSalary() {
    return salary;
    }
    public void setSalary(int salary) {
    this.salary = salary;
    }

    public void manOrWoman() {
    if(sex == 0) {
    System.out.println("woman");
    }else if(sex == 1) {
    System.out.println("man");
    }
    }
    public void employeed() {
    // if(salary == 0) {
    // System.out.println("no job");
    // }else if(salary == 1) {
    // System.out.println("job");
    // }//或者
    System.out.println((salary == 0)? "no job" : "job");
    }
    }

    (2)定义类Kids继承ManKind,并包括

    • 成员变量int yearsOld;
    • 方法printAge()打印yearsOld的值。
    package top.trisbin.inheritanceex2;

    public class Kids extends ManKind{
    private int yearsOld;


    public Kids() {
    //空参构造器
    }
    public Kids(int sex,int salary,int yearsOld) {
    setSex(sex);
    setSalary(salary);
    this.yearsOld = yearsOld;
    }


    public int getYearsOld() {
    return yearsOld;
    }
    public void setYearsOld(int yearsOld) {
    this.yearsOld = yearsOld;
    }
    public void printAge() {
    System.out.println("I am " + yearsOld + " years old.");
    }
    }

    (3)定义类KidsTest,在类的main方法中实例化Kids的对象someKid,用该对象访问其父类的成员变量及方法。

    package top.trisbin.inheritanceex2;

    public class KidsTest {
    public static void main(String[] args) {
    Kids someKid = new Kids(1,0,18);
    someKid.manOrWoman();
    someKid.employeed();
    someKid.printAge();
    }
    }
  2. 根据下图实现类。在CylinderTest类中创建Cylinder类的对象,设置圆柱的底面半径个高,并输出圆柱的体积。

    Circle(圆)
    -redius:double
    Circle():构造器,将radius初始化为1
    +setRadius(double radius):void
    +getRadius():double
    +findArea():double 计算圆的面积

    ^

    |

    Cylinder(圆柱)
    -length:double
    Cylinder():构造器,将length属性初始化为1
    +setLength(double length):void
    +getLength():double
    +findVolueme():double 计算圆柱体积

    ①Circle类

    package top.trisbin.inheritanceex3;

    public class Circle {
    private double radius;


    public Circle() {
    this.radius = 1;
    }


    public void setRadius(double radius) {
    this.radius = radius;
    }
    public double getRadius() {
    return radius;
    }
    public double findArea() {
    return Math.PI * radius * radius;
    }
    }

    ②Cylinder类、

    package top.trisbin.inheritanceex3;

    public class Cylinder extends Circle{
    private double length;


    public Cylinder() {
    this.length = 1;
    }


    public void setLength(double length) {
    this.length = length;
    }
    public double getLength() {
    return length;
    }
    public double findVolueme() {
    return findArea() * length;
    }
    }

    ③CylinderTest测试类

    package top.trisbin.inheritanceex3;

    public class CylinderTest {
    public static void main(String[] args) {
    Cylinder cy = new Cylinder();

    cy.setRadius(7);
    cy.setLength(5);
    double area = cy.findArea();
    double volueme = cy.findVolueme();

    System.out.println("此圆柱底面积 = " + area);
    System.out.println("此圆柱体积 = " + volueme);
    }
    }
    • 运行

Debug(调试)

如何调试程序

  1. System.out.println();

  2. Eclipse - Debug

    • 设置断点(可设置多个断点)
    • debug as java application
    • 常用操作
    操作 作用
    step into 跳入(F5) 进入当前所调用的方法中
    step over 跳过(F6) 执行完当前语句进入下一行
    step return 跳回(F7) 执行完当前所在方法,进入下一行
    drop to frame 回到当前行所在方法的第一行
    resume 恢复 执行完当前所在断点的所有代码,进入下一个断点,如果没有就结束
    Terminate 终止 停止JVM,后面的程序不会再执行

方法的重写(override/overwrite)

概念及使用

  1. 重写:在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在执行程序时,子类的方法将覆盖父类的方法。

  2. 应用:重写以后,当创建子类对象以后,通过子类对象调用父类中的同名参数的方法时,实际执行的是子类重写父类的方法。

  3. 重写规定:

    方法的声明:权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{

    ​ //方法体

    ​ }

    约定俗成:子类中的叫重写的方法,父类中的叫被重写的方法

    • 重写与被重写方法名以及形参列表相同
    • 重写重写方法的权限修饰符必须不小于被重写方法的权限修饰符
      • 特殊情况:子类不能重写父类中权限为private的方法
    • 返回值类型:
      • 父类被重写方法的返回值类型是void,则子类重写方法的返回值类型只能是void;
      • 父类被重写方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类;
      • 父类被重写方法的返回值类型是基本数据类型(比如double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double);
    • 子类重新写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。(具体放到异常处理再讲)
  • 子类和父类中同名同参数的要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
package top.triabin.override;
/**
*
* @Description 方法重写练习--父类
* @author DawnLee
* @version 1.0
* @data 2020-8-14 15:19:55
*
*/
public class Person {
String name;
int age;

public Person() {
//空参构造器
}
public Person(String name,int age) {
this.name = name;
this.age = age;
}

public void eat() {
System.out.println("吃饭");
}
public void walk(int distance) {
System.out.println("走路,走的距离是" + distance + "公里。");
}
}
package top.triabin.override;
/**
*
* @Description 方法重写练习--子类
* @author DawnLee
* @version 1.0
* @data 2020-8-14 15:24:49
*
*/
public class Student extends Person {
String major;

public Student() {

}
public Student(String major) {
this.major = major;
}

public void study() {
System.out.println("学习。专业是:" + major);
}
//对父类中的eat()方法重写
public void eat() {
System.out.println("学生应该多吃有营养的事食物");
}
}
package top.triabin.override;
/**
*
* @Description 方法重写练习--测试类
* @author DawnLee
* @version 1.0
* @data 2020-8-14 15:28:22
*
*/
public class PersonTest {
public static void main(String[] args) {
Student s = new Student("计算机科学与技术");
s.eat();
s.walk(10);
s.study();

Person p1 = new Person();
p1.eat();
}
}

面试题:区分方法的重载与重写 高频题

答:

① 二者概念:

② 重载和重写的具体规则:

③ 重载不表现为多态性,重写表现为多态性。

练习

  1. 如果现在父类的一个方法定义成private访问权限,在子类中将此方法声明为default访问权限,那么这样还叫重写吗?

    NO

  2. 修改上一节OOP特征二:继承性练习题第2题中定义的类Kids,在Kids中重新定义employeed()方法,覆盖父类ManKind中定义的employeed()方法,输出“Kids should study and no job.”

    //直接在子类Kids中重写该类如下:
    @Override
    public void employeed(){
    System.out.println("Kids should study and no job.")
    }

四种访问权限修饰符

  1. Java权限修饰符public、protected、 (缺省)、 private置于类的成员定义前,用来限定对象对该类成员的访问权限。

    修饰符 类内部 同一个包 不同包的子类 同一个工程
    private Yes
    (缺省) Yes Yes
    protected Yes Yes Yes
    public Yes Yes Yes Yes

    对于class的权限修饰只能用public和default(缺省)。

    • public类可以在任意地方被访问

    • default类只可以被同一个包内部的类访问

  2. 访问控制举例

    创建一个Order类:

    package top.triabin.fourpermissionmodifiers;
    /**
    *
    * @Description 体会4种不同的权限修饰
    * @author DawnLee
    * @version 1.0
    * @data 2020-8-14 18:23:20
    *
    */
    public class Order {
    @SuppressWarnings("unused")
    private int orderPrivate;
    int orderDefault;
    protected int orderProtected;
    public int orderPublic;

    @SuppressWarnings("unused")
    private void methodPrivate() {
    orderPrivate = 1;
    orderDefault = 1;
    orderProtected = 1;
    orderPublic = 1;
    }
    void methodDefault() {
    orderPrivate = 2;
    orderDefault = 2;
    orderProtected = 2;
    orderPublic = 2;
    }
    protected void methodProtected() {
    orderPrivate = 3;
    orderDefault = 3;
    orderProtected = 3;
    orderPublic = 3;
    }
    public void methodPublic() {
    orderPrivate = 4;
    orderDefault = 4;
    orderProtected = 4;
    orderPublic = 4;
    }
    }

    ①同一个包中的其他类,不可以调用Order类中私有的属性、方法

    package top.triabin.fourpermissionmodifiers;
    /**
    *
    * @Description 4种不同的权限修饰测试类
    * @author DawnLee
    * @version 1.0
    * @data 2020-8-14 18:30:38
    *
    */
    public class OrderTest {
    public static void main(String[] args) {
    Order order = new Order();

    order.orderDefault = 2;
    order.orderProtected = 3;
    order.orderPublic = 4;

    order.methodDefault();
    order.methodProtected();
    order.methodPublic();

    // 同一个包中的其他类,不可以调用Order类中私有的属性、方法
    // order.orderPrivate = 1;
    // order.methodPrivate();
    }
    }

    ②在不同包的子类中,不能调用Order类中声明为private和缺省权限的属性、方法

    package top.triabin.fourpermissionmodifiers.suborder;

    import top.triabin.fourpermissionmodifiers.Order;

    public class SubOrder extends Order {
    public void method() {
    orderProtected = 1;
    orderPublic = 2;

    methodProtected();
    methodPublic();

    // 在不同包的子类中,不能调用Order类中声明为private和缺省权限的属性、方法。
    // orderDefault = 3;
    // orderPrivate = 4;
    //
    // methodDefault();
    // methodPrivate();
    }
    }

    ③不同包下的普通类(非子类)要使用Order类,不可以调用声明为private、缺省、protected权限的属性、方法

    package top.triabin.fourpermissionmodifiers.suborder;

    import top.triabin.fourpermissionmodifiers.Order;

    public class OrderTest {
    public static void main(String[] args) {
    Order order = new Order();
    order.orderPublic = 1;
    order.methodPublic();

    // 不同包下的普通类(非子类)要使用Order类,不可以调用声明为private、缺省、protected权限的属性、方法。
    // order.orderPrivate = 2;
    // order.orderDefault = 3;
    // order.orderProtected = 4;
    //
    // order.methodPrivate();
    // order.methodDefault();
    // order.methodProtected();
    }
    }

关键字:super

super关键字的使用

  1. super理解为:父类

  2. super可以用来调用:属性、方法、构造器;

  3. super的使用:调用属性和方法

    ①我们可以在子类的方法或构造器中,通过使用“super.属性”或“super.方法”的方式,显示的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略“super.”;

    ②特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须是显示的使用“super.属性”的方式,表明调用的是父类中声明的属性;

    ③特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须是显示的使用“super.方法”的方式,表明调用的是父类中被重写的方法。

  4. super的使用:调用构造器

    ①我们可以在子类的构造器中显式的使用“super(形参列表)”的方式,调用父类中声明的构造器;

    ②“super(形参列表)”的使用,必须声明在子类构造器的首行;

    ③我们在类的构造器中,针对于“this(形参列表)”或“super(形参列表)”只能二选一,不能同时出现

    ④在子类构造器的首行,没有显示的声明“this(形参列表)”或“super(形参列表)”,则默认调用父类中空参的构造器:super();

    ⑤在类的多个构造器中,至少有一个类的构造器中是用来了“super(形参列表)”,调用父类中的构造器。

super关键字举例

  1. 父类Person

    package top.triabin.keywordsuper;

    public class Person {
    String name;
    int age;
    int id = 1001;//身份证号

    public Person() {
    System.out.println("在构造器的首行没有显示的声明“this(形参列表)”或“super(形参列表)”,则默认调用父类中空参的构造器:super()。");
    }
    public Person(String name) {
    this.name = name;
    }
    public Person(String name,int age) {
    this.name = name;
    this.age = age;
    }

    public void eat() {
    System.out.println("人:吃饭");
    }
    public void walk() {
    System.out.println("人:走路");
    }
    }
  2. 子类Student

    package top.triabin.keywordsuper;

    public class Student extends Person {
    String major;
    int id = 1002;//学号

    public Student() {

    }
    public Student(String major) {
    this.major = major;
    }
    public Student(String name,int age,String major) {
    // this.name = name;
    // this.age = age;
    super(name,age);//调用父类中形参列表相同的构造器
    this.major = major;
    }

    @Override
    public void eat() {
    System.out.println("学生:多吃有营养的食物");
    }
    public void study() {
    System.out.println("学生:学习知识");
    this.eat();
    super.eat();
    }
    public void show() {
    System.out.println("name = " + this.name + ",age = " + super.age);
    System.out.println("id = " + this.id);
    System.out.println("id = " + super.id);
    }
    }
  3. 测试类SuperTest

    package top.triabin.keywordsuper;
    /**
    *
    * @Description super关键字的使用
    * @author DawnLee
    * @version 1.0
    * @data 2020-8-14 22:10:13
    *
    */
    public class SuperTest {
    public static void main(String[] args) {
    Student s = new Student();
    s.show();

    System.out.println();

    s.study();

    Student s1 = new Student("Triabin",18,"IT");
    s1.show();

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

    @SuppressWarnings("unused")
    Student s2 = new Student();
    }
    }
    • 运行结果

子类对象实例化过程

  1. 从结果上来看:(继承性)
    • 子类继承父类以后,就获取了父类中声明的属性和方法;
    • 创建的子类的对象,在堆空间中,就会加载所有父类中声明的属性。
  2. 从过程上来看:
    • 但我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类结构,所以才可以看到内存中有父类中的结构,子类对象才可以调用。
  • 明确:虽然创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即为new的子类对象。

实验:类的继承&super

  1. 写一个名为Account的类模拟账户。该类的属性和方法如下图所示。该类包括的属性: 账号id,余额 balance,年利率annualInterestRate;包含的方法:访问器方法(getter和setter方法),返回月利率的方法getMonthlyInterest(),取款方法withdraw(),存款方法deposit()。

    Account
    private int id
    private double balance
    private double annualInterestRate
    public Account(int id,double balance,double annualInterestRate)
    public int getId()
    public double getBalance()
    public double getAnnualInterestRate()
    public void setId( int id)
    public void setBalance(double balance)
    public void setAnnualInterestRate(double annualInterestRate)
    public double getMonthlyInterest()
    public void withdraw (double amount)
    public void deposit (double amount)
package experimentsuperinheritance;
/**
*
* @Description 继承性&super实验一--Account类
* 写一个名为 Account 的类模拟账户。该类的属性和方法如下图所示。该类包括的属性:
* 账号 id,余额 balance,年利率 annualInterestRate;包含的方法:访问器方法(getter 和
* setter 方法),返回月利率的方法 getMonthlyInterest(),取款方法 withdraw(),存款方法
* deposit()。
* @author DawnLee
* @version 1.0
* @data 2020-8-15 23:29:57
*
*/
public class Account {
private int id;//账号
private double balance;//余额
private double annualInterestRate;//年利率

public Account(int id,double balance,double annualInterestRate) {
super();
this.id = id;
this.balance = balance;
this.annualInterestRate = annualInterestRate;
}

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public double getAnnualInterestRate() {
return annualInterestRate;
}
public void setAnnualInterestRate(double annualInterestRate) {
this.annualInterestRate = annualInterestRate;
}
//返回月利率
public double getMonthlyInterest() {
return annualInterestRate / 12;
}
//取款
public void withdraw(double amount){
if(amount > balance) {
System.out.println("余额不足!\n您的账户余额为:" + balance);
}else if(amount > 0 && amount < balance) {
balance -= amount;
System.out.println("取款成功!\n您的账户余额为:" + balance);
}else {
System.out.println("取款金额不能为负!");
}
}
//存款
public void deposit(double amount) {
if(amount > 0) {
balance += amount;
System.out.println("成功存入金额:" + amount + "\n您的账户余额为:" + balance);
}
}
}

写一个用户程序测试 Account 类。在用户程序中,创建一个账号为 1122、余额为 20000、 年利率 4.5%的 Account 对象。使用 withdraw 方法提款 30000 元,并打印余额。 再使用 withdraw 方法提款 2500 元,使用 deposit 方法存款 3000 元,然后打印余额和月利 率。

package experimentsuperinheritance;
/**
*
* @Description 继承性&super实验一--测试类1
* @author DawnLee
* @version 1.0
* @data 2020-8-15 23:48:46
*
*/
public class AccountTest {
public static void main(String[] args) {
Account account = new Account(1122,20000,4.5);
System.out.println("账号:" + account.getId());
System.out.println("余额:" + account.getBalance());
System.out.println("年利率:" + account.getAnnualInterestRate() + "%");
System.out.println();

account.withdraw(30000);
System.out.println();

account.withdraw(2500);
System.out.println();

account.deposit(3000);
System.out.println("月利率为:" + account.getMonthlyInterest() + "%");
}
}

运行结果

  1. 如下:

    package experimentsuperinheritance;
    /**
    *
    * @Description 继承性&super实验一--CheckAccount类
    * 创建 Account 类的一个子类 CheckAccount 代表可透支的账户,该账户中定义一个
    * 属性overdraft 代表可透支限额。在 CheckAccount 类中重写 withdraw 方法,其算法如下:
    * 如果(取款金额<账户余额),
    * 可直接取款
    * 如果(取款金额>账户余额),
    * 计算需要透支的额度
    * 判断可透支额 overdraft 是否足够支付本次透支需要,如果可以
    * 将账户余额修改为 0,冲减可透支金额
    * 如果不可以
    * 提示用户超过可透支额的限额
    * @author DawnLee
    * @version
    * @data 2020-8-16 15:22:42
    *
    */
    public class CheckAccount extends Account {
    private double overdraft;

    public CheckAccount(int id,double balance,double annualInterestRate,double overdraft) {
    super(id,balance,annualInterestRate);
    this.overdraft = overdraft;
    }

    @Override
    public void withdraw(double amount) {
    if(getBalance() >= amount && amount > 0) {//余额足够消费
    setBalance(getBalance() - amount);//或
    // super.withdraw(amount);
    System.out.println("取款成功!\n您的账户余额为:" + getBalance() + "\n您的账户可透支额度为:" + overdraft);
    }else if(overdraft >= amount - getBalance()) {//透支额度+余额足够消费
    overdraft -= (amount - getBalance());
    setBalance(0);//或
    // super.withdraw(getBalance());
    System.out.println("透支取款成功!\n您的账户余额为:" + getBalance() + "\n您的账户可透支额度为:" + overdraft);
    }else {
    System.out.println("超过可透支额度!\n您的账户余额为:" + getBalance() + "\n您的账户可透支额度为:" + overdraft);
    }
    }
    }
    package experimentsuperinheritance;
    /**
    *
    * @Description 继承性&super实验一--测试类2
    * 写一个用户程序测试 CheckAccount 类。在用户程序中,创建一个账号为 1122、余
    额为 20000、年利率 4.5%,可透支限额为 5000 元的 CheckAccount 对象。
    使用 withdraw 方法提款 5000 元,并打印账户余额和可透支额。
    再使用 withdraw 方法提款 18000 元,并打印账户余额和可透支额。
    再使用 withdraw 方法提款 3000 元,并打印账户余额和可透支额。
    * @author DawnLee
    * @version 1.0
    * @data 2020-8-16 15:43:21
    *
    */
    public class CheckAccountTest {
    public static void main(String[] args) {
    CheckAccount account = new CheckAccount(1122,20000,4.5,5000);

    account.withdraw(5000);
    System.out.println();

    account.withdraw(18000);
    System.out.println();

    account.withdraw(3000);
    }
    }

    运行结果

OOP特征三:多态性

基本概念和规则

  1. 理解多态性:可以理解为事物的多种形态。

  2. 何为多态:

    • 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类引用)
  3. 多态的使用:虚拟方法调用

    有了对象的多态性以后,我们在编译器期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。

    总结(Person p2 = new Man();):编译,看左边(Person);运行,看右边(Man())。

  4. 多态性使用前提:①要有类的继承关系;②要有方法的重写。

  5. 对象的多态性,只适用于方法,不适用于属性。(编译和运行都看左边)

应用举例

举例一:基本概念的使用

  1. Person类

    package top.triabin.polymorphism;
    /*
    *
    */
    public class Person {
    String name;
    int age;

    int id = 1001;

    public void eat() {
    System.out.println("吃饭");
    }
    public void walk() {
    System.out.println("走路");
    }
    }
  2. Man类

    package top.triabin.polymorphism;

    public class Man extends Person {
    boolean isSmoking;

    int id = 1002;

    public void earnMoney() {
    System.out.println("男人负责赚钱养家");
    }
    public void eat() {
    System.out.println("多吃肉,长肌肉");
    }
    public void walk() {
    System.out.println("霸气地走路");
    }
    }
  3. Woman类

    package top.triabin.polymorphism;

    public class Woman extends Person {
    boolean isBeauty;

    public void goShopping() {
    System.out.println("购物");
    }
    public void eat() {
    System.out.println("减肥");
    }
    public void walk() {
    System.out.println("优雅");
    }
    }
  4. PersonTest类

    package top.triabin.polymorphism;
    /*
    * 面向对象特征之三:多态性
    *
    * 1.理解多态性:可以理解为事物的多种形态
    * 2.何为多态:
    * 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类引用)
    * 3.多态的使用:虚拟方法调用
    * 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
    * 总结(Person p2 = new Man();):编译,看左边(Person);运行,看右边(Man())。
    * 4.多态性使用前提:①要有类的继承关系;②要有方法的重写。
    *
    * 5.对象的多态性,只适用于方法,不适用于属性。(编译和运行都看左边)
    *
    */
    public class PersonTest {
    public static void main(String[] args) {
    Person p1 = new Person();
    p1.eat();

    Man man = new Man();
    man.eat();
    man.age = 18;
    man.earnMoney();

    System.out.println("\n*****************************");
    //对象的多态性:父类的引用指向子类的对象
    Person p2 = new Man();
    // Person p3 = new Woman();
    // 多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法---虚拟方法调用
    p2.eat();
    p2.walk();

    // p2.earnMoney();

    System.out.println();
    System.out.println(p2.id);//1001 对象的多态性,只适用于方法,不适用于属性。
    }
    }
    • 运行

应用举例二:虚拟方法调用

  1. AnimalTest类

    package top.triabin.polymorphism;
    //多态性的使用举例一:
    public class AnimalTest {
    public static void main(String[] args) {
    AnimalTest test = new AnimalTest();

    test.func(new Dog());

    test.func(new Cat());
    }

    public void func(Animal animal) {//Animal animal = new Dog();
    animal.eat();
    animal.shout();
    }
    }


    class Animal{

    public void eat() {
    System.out.println("动物:进食");
    }
    public void shout() {
    System.out.println("动物:叫");
    }
    }


    class Dog extends Animal {
    public void eat() {
    System.out.println("狗吃骨头");
    }
    public void shout() {
    System.out.println("汪!汪!汪!");
    }
    }


    class Cat extends Animal {
    public void eat() {
    System.out.println("猫吃鱼");
    }
    public void shout() {
    System.out.println("喵!喵!喵!");
    }
    }

从编译和运行的角度看重载与重写

  • 重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数列表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。

    所以,对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”

  • 而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”

引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”

instanceof操作符

  • 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。如何才能调用子类特有的属性和方法?
  1. 向下转型:使用强制类型转换符 例 Man m1 = (Man)p2;

  2. 使用强转时,可能出现ClassCastException异常。引入instanceof关键字:

  3. instanceof关键字的使用:

    • a instanceof A:判断对象a是否是类A的实例。如果是,返回true,如果不是返回false。
    • 使用场景:为了避免在向下转型时出现ClassCastException异常,我们在向下转型前,先进行instanceof判断,一旦返回true,就进行向下转型;如果返回false,不进行向下转型。
    • 如果a instanceof A返回true,则a instanceof B也返回true,那么B是A的父类。
  4. 应用举例:将上面应用举例一中的PersonTest类修改如下:

    package top.triabin.polymorphism;
    /*
    * 面向对象特征之三:多态性
    *
    * 1.理解多态性:可以理解为事物的多种形态
    * 2.何为多态:
    * 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类引用)
    * 3.多态的使用:虚拟方法调用
    * 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
    * 总结(Person p2 = new Man();):编译,看左边(Person);运行,看右边(Man())。
    * 4.多态性使用前提:①要有类的继承关系;②要有方法的重写。
    *
    * 5.对象的多态性,只适用于方法,不适用于属性。(编译和运行都看左边)
    *
    */
    public class PersonTest {
    public static void main(String[] args) {
    Person p1 = new Person();
    p1.eat();

    Man man = new Man();
    man.eat();
    man.age = 18;
    man.earnMoney();

    System.out.println("\n*****************************");
    //对象的多态性:父类的引用指向子类的对象
    Person p2 = new Man();
    // Person p3 = new Woman();
    // 多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法---虚拟方法调用
    p2.eat();
    p2.walk();

    // p2.earnMoney();//无法调用子类所特有的方法,编译时认为p2是一个Person类,Person中无earnMoney方法。
    // p2.isSmoking = true;

    System.out.println();
    System.out.println(p2.id);//1001--对象的多态性,只适用于方法,不适用于属性。

    System.out.println();
    System.out.println("***************instanceof关键字的使用*****************");
    // 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类
    // 类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。

    // 如何才能调用子类特有的属性和方法?
    Man m1 = (Man)p2;//向下转型:使用强制类型转换符。
    m1.earnMoney();
    m1.isSmoking = true;

    // 使用强转时,可能出现ClassCastException异常。
    // Woman w1 = (Woman)p1;
    // w1.goShopping();
    /* instanceof关键字的使用:
    *
    * a instanceof A:判断对象a是否是类A的实例。如果是,返回true,如果不是返回false。
    *
    * 使用情景:为了避免在向下转型时出现ClassCastException异常,我们在向下转型前,先进
    * 行instanceof判断,一旦返回true,就进行向下转型;如果返回false,不进行向下转型。
    *
    * 如果a instanceof A返回true,则a instanceof B也返回true,那么B是A的父类。
    */
    if(p2 instanceof Woman) {
    Woman w1 = (Woman)p2;
    w1.goShopping();
    System.out.println("强转成功");
    }else {
    System.out.println("p2不是类Woman的实例,强转失败!");
    }
    if(p2 instanceof Man) {
    System.out.println("p2是类Man的实例,可以强转!");
    }else {
    System.out.println("p2不是类Man的实例,强转失败!");
    }

    System.out.println();
    if(p2 instanceof Person) {
    System.out.println("p2是类Person的实例!");
    }
    if(p2 instanceof Object) {
    System.out.println("p2也是类Object的实例!");
    }

    System.out.println();
    // 练习:
    //问题一:编译时通过,运行时不通过
    // Person p3 = new Woman();
    // Man m3 = (Man)p3;
    //举例一:
    // Person p3 = new Woman();
    // Man m3 = (Man)p3;
    //举例二:
    // Person p4 = new Person();
    // Man m4 = (Man)p4;

    //问题二:编译通过,运行也通过
    // Object obj = new Woman();
    // Person p = (Person)obj;

    //问题三:编译不通过
    // Man m5 = new Woman();
    }
    }
    • 运行结果

练习

  1. 继承成员变量和继承方法的区别

    package top.triabin.polymorphism;
    /*
    * 1.若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可
    * 能把父类里的方法转移到子类中。编译看左边,运行看右边
    * 2.对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例
    * 变量依然不可能覆盖父类中定义的实例变量。编译运行都看左边
    *
    */
    class Base {
    int count = 10;
    public void display() {
    System.out.println(this.count);
    }
    }
    class Sub extends Base {
    int count = 20;
    public void display() {
    System.out.println(this.count);
    }
    }

    public class Exercise1FieldMethodTest {
    public static void main(String[] args) {
    Sub s = new Sub();
    System.out.println(s.count);//20
    s.display();//20
    Base b = s;//赋地址
    System.out.println(b == s);//true
    System.out.println(b.count);//10
    b.display();//20
    }
    }
    • 运行
  2. class Person{
        protected String name = "person";
        protected int age = 50;
        public String getInfo() {
            return "Name:" + name + "\nage:" + age;
        }
    }
    <!--hexoPostRenderEscape:<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">&#96;&#96;&#96;java</span><br><span class="line">class Student extends Person&#123;</span><br><span class="line">    protected String school &#x3D; &quot;pku&quot;;</span><br><span class="line">    public String getInfo() &#123;</span><br><span class="line">        return &quot;Name:&quot; + name + &quot;\nage:&quot; + age + &quot;\nschool:&quot; + school;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>:hexoPostRenderEscape-->
    
    <!--hexoPostRenderEscape:<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Graduate</span> <span class="keyword">extends</span> <span class="title">Student</span></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> String major = <span class="string">&quot;IT&quot;</span>;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getInfo</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Name:&quot;</span> + name + <span class="string">&quot;\nage:&quot;</span> + age + <span class="string">&quot;\nschool:&quot;</span> + school + <span class="string">&quot;\nmajor:&quot;</span> + major;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>:hexoPostRenderEscape-->
    
    建立InstanceTest类,在类中定义方法method(Person e);在method中:
    
    ①根据**e**的类型调用相应类的**getInfo()**方法。
    
    ②根据**e**的类型执行:
    
    * 如果**e**为**Person**类的对象,输出:
    
      “a person”
    
    * 如果e为Student类的对象,输出:
    
      “a student”
    
      “a person”
    
    * 如果e为Graduate类的对象,输出:
    
      “a graduated student”
    
      “a student”
    
      “a person”
    
    <!--hexoPostRenderEscape:<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> top.triabin.polymorphism;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">     * 建立InstanceTest类,在类中定义方法method(Person e);在method中:</span></span><br><span class="line"><span class="comment">    ①根据e的类型调用相应类的getInfo()方法。</span></span><br><span class="line"><span class="comment">    ②根据e的类型执行:</span></span><br><span class="line"><span class="comment">        如果e为Person类的对象,输出:</span></span><br><span class="line"><span class="comment">      “a person”</span></span><br><span class="line"><span class="comment">        如果e为Student类的对象,输出:</span></span><br><span class="line"><span class="comment">      “a student”</span></span><br><span class="line"><span class="comment">      “a person”</span></span><br><span class="line"><span class="comment">        如果e为Graduate类的对象,输出:</span></span><br><span class="line"><span class="comment">      “a graduated student”</span></span><br><span class="line"><span class="comment">      “a student”</span></span><br><span class="line"><span class="comment">      “a person”</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Exercise2InstanceTest</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        Exercise2InstanceTest test = <span class="keyword">new</span> Exercise2InstanceTest();</span><br><span class="line">        Person p1 = <span class="keyword">new</span> Person();</span><br><span class="line">        Person p2 = <span class="keyword">new</span> Student();</span><br><span class="line">        Person p3 = <span class="keyword">new</span> Graduate();</span><br><span class="line">        </span><br><span class="line">        test.method(p1);</span><br><span class="line">        System.out.println();</span><br><span class="line">        test.method(p2);</span><br><span class="line">        System.out.println();</span><br><span class="line">        test.method(p3);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">method</span><span class="params">(Person e)</span> </span>&#123;</span><br><span class="line">        System.out.println(e.getInfo());</span><br><span class="line">        <span class="keyword">if</span>(e <span class="keyword">instanceof</span> Graduate) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;a graduated student&quot;</span>);</span><br><span class="line">            System.out.println(<span class="string">&quot;a student&quot;</span>);</span><br><span class="line">            System.out.println(<span class="string">&quot;a person&quot;</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span> <span class="keyword">if</span>(e <span class="keyword">instanceof</span> Student) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;a student&quot;</span>);</span><br><span class="line">            System.out.println(<span class="string">&quot;a person&quot;</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;a person&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>:hexoPostRenderEscape-->
    
    * 运行
    
    <img src="https://gitee.com/Dawn_Lee/blogimg/raw/master/20200817162902.png" align="left"/>
  3. 定义三个类,父类GeometricObject代表几何形状,子类Circle代表圆,MyRecangle代表矩形。定义一个测试类GeometricTest,编写equalsArea方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术),编写displayGeometricObject方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)。

    • 父类GeometricObject
    GeometricObject
    #color:String
    #weight:double
    #GeometricObject(color:String,weight:double)
    属性的getter和setter方法
    +findArea():double
    package top.triabin.polymorphismexercise3;
    /*
    * 父类GeometricObject
    */
    public class GeometricObject {
    protected String color;
    protected double weight;

    public GeometricObject() {
    //空
    }
    protected GeometricObject(String color,double weight) {
    this.color = color;
    this.weight = weight;
    }

    public String getColor() {
    return color;
    }
    public void setColor(String color) {
    this.color = color;
    }
    public double getWeight() {
    return weight;
    }
    public void setWeight(double weight) {
    this.weight = weight;
    }
    public double findArea() {
    return findArea();
    }
    }
    • GeometricObject的子类Circle
    Circle
    -radius:double
    +Circle(radius:double,color:String,weight:double)
    radius属性的setter和getter方法
    +findArea():double //计算圆的面积
    package top.triabin.polymorphismexercise3;

    public class Circle extends GeometricObject {
    private double radius;

    public Circle() {}
    public Circle(double radius,String color,double weight) {
    super(color,weight);
    this.radius = radius;
    }

    public double getRadius() {
    return radius;
    }
    public void setRadius(double radius) {
    this.radius = radius;
    }
    public double findArea() {
    return Math.PI * radius * radius;
    }
    }
    • GeometricObject的子类MyRectangle
    MyRectangle
    -width:double
    -height:double
    +MyRectangle(width:double,height:double,color:String,weight:double)
    属性的getter和setter方法
    +findArea():double //计算面积
    package top.triabin.polymorphismexercise3;

    public class MyRectangle extends GeometricObject {
    private double width;
    private double height;

    public MyRectangle() {}
    public MyRectangle(double width,double height,String color,double weight) {
    super(color,weight);
    this.width = width;
    this.height = height;
    }

    public double getWidth() {
    return width;
    }
    public void setWidth(double width) {
    this.width = width;
    }
    public double getHeight() {
    return height;
    }
    public void setHeight(double height) {
    this.height = height;
    }
    public double findArea() {
    return width * height;
    }
    }
    • 测试类GeometricTest
    package top.triabin.polymorphismexercise3;

    public class GeometricTest {
    public static void main(String[] args) {
    GeometricTest test = new GeometricTest();

    GeometricObject g1 = new Circle(5,"red",10);
    System.out.print("g1");
    test.displayGeometricObject(g1);

    GeometricObject g2 = new MyRectangle(Math.PI,25,"green",20);
    System.out.print("g2");
    test.displayGeometricObject(g2);

    GeometricObject g3 = new MyRectangle(5,10,"draw",25);
    System.out.print("g3");
    test.displayGeometricObject(g3);

    if(test.equalsArea(g1,g2)) {
    System.out.println("g1和g2面积相等") ;
    }else {
    System.out.println("g1和g2面积不相等") ;
    }
    System.out.println("g2和g3面积相等 => " + test.equalsArea(g2, g3));
    }
    public boolean equalsArea(GeometricObject g1,GeometricObject g2) {
    return g1.findArea() == g2.findArea();
    }
    public void displayGeometricObject(GeometricObject g) {
    System.out.println("的面积为:" + g.findArea());
    }
    }
    • 运行结果
  4. **面试题:**多态是编译时行为还是运行时行为?如何证明?

    证明见InterviewTest.java

  5. 拓展问题:InterviewTest1.java

Object类的使用

java.lang.Object类

  1. Object类是所有Java类的根父类。

  2. 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类。

  3. Object类中的功能(属性、方法)具有通用性。

    属性:无

    方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait()、notify()、notifyAll()

  4. Object类只声明了一个空参的构造器。

  • 面试题:final、finally、finalize的区别?
package top.triabin.classobject;
/*
* java.lang.Object类
* 1. Object类是所有Java类的根父类。
* 2. 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类。
* 3. Object类中的功能(属性、方法)具有通用性。
* 属性:无
* 方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait()、notify()、notifyAll()
*
* 4. Object类只声明了一个空参的构造器。
*
*
* 面试题:final、finally、finalize的区别
*/
public class ObjectTest {
public static void main(String[] args) {
Order order = new Order();
System.out.println(order.getClass().getSuperclass());
}
}

class Order{

}

Object类中的主要结构

NO. 方法名称 类型 描述
1 public Object() 构造 构造器
2 public boolean equals(Object obj) 普通 对象比较
3 public int hashCode() 普通 取得Hash码
4 public String toString() 普通 对象打印时调用

equals()方法

面试题:==和equals()的区别?

  1. 回顾==的使用:

    ==:运算符

    ①可以使用在基本数据类型变量和引用数据类型变量中;

    ② 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同) 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一 个对象实体。

  2. equals()方法的使用:

    ①是一个方法,而非运算符;

    ②只能适用于引用数据类型;

    ③Object类中equals()的定义:

    public boolean equals(Object obj) {
    return (this == obj);
    }

    说明:Object类中equals()和==的作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。

    ④像String、Date、File、包装类等都重写了Object类中的equals()方法,重写之后,比较的不再是两个对象的“实体内容”是否相同;

    ⑤通常情况下,我们自定义的类如果使用equals()方法的话,也通常是比较两个对象的“实体内容”是否相同。那么我们就需要对Object类中的equals()方法进行重写。

    重写原则:比较两个对象的实体内容(即:name和age)是否相同。

  3. 代码实例

    • Customer类
    package top.triabin.classobject;

    public class Customer {
    private String name;
    private int age;

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

    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;
    }
    // 重写原则:比较两个对象的实体内容(即:name和age)是否相同
    // 手动实现equals()的重写:
    // @Override
    // public boolean equals(Object obj) {
    // if(this == obj) {
    // return true;
    // }
    // if(obj instanceof Customer) {
    // Customer cust = (Customer)obj;
    // //比较两个对象的每个属性是否都相同
    // return this.age == cust.age && this.name.equals(cust.name);
    // }
    // return false;
    // }
    // 自动生成equals()的重写:=> Alt+Shift+S / Source => Generate hashCode() and equals()
    @Override
    public boolean equals(Object obj) {
    if (this == obj)
    return true;
    if (obj == null)
    return false;
    if (getClass() != obj.getClass())
    return false;
    Customer other = (Customer) obj;
    if (age != other.age)
    return false;
    if (name == null) {
    if (other.name != null)
    return false;
    } else if (!name.equals(other.name))
    return false;
    return true;
    }
    }
    • EqualsTest类
    package top.triabin.classobject;

    import java.sql.Date;

    /*
    * 面试题:==和equals()的区别?
    *
    * 一、回顾 == 的使用:
    * ==:运算符
    * 1. 可以使用在基本数据类型变量和引用数据类型变量中;
    * 2. 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
    * 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一
    * 个对象实体。
    *
    * 二、equals()方法的使用:
    * 1. 是一个方法,而非运算符;
    * 2. 只能适用于引用数据类型;
    * 3. Object类中equals()的定义:
    * public boolean equals(Object obj) {
    return (this == obj);
    }
    * 说明:Object类中equals()和==的作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
    *
    * 4. 像String、Date、File、包装类等都重写了Object类中的equals()方法,重写之后,比较的不再是两个对象的“实体内容”
    * 是否相同。
    * 5. 通常情况下,我们自定义的类如果使用equals()方法的话,也通常是比较两个对象的“实体内容”是否相同。那么我们就
    * 需要对Object类中的equals()方法进行重写。
    * 重写原则:比较两个对象的实体内容(即:name和age)是否相同。
    */
    public class EqualsTest {
    public static void main(String[] args) {
    // 基本数据类型
    int i = 10;
    int j = 10;
    double d = 10.0;
    System.out.println(i == j);//true
    System.out.println(i == d);//true

    // boolean b = true;
    // System.out.println(i == b);//报错

    char c = 10;
    System.out.println(i == c);//true
    char c1 = 'A';
    char c2 = 65;
    System.out.println(c1 == c2);//true

    // 引用数据类型
    Customer cust1 = new Customer("Tom",21);
    Customer cust2 = new Customer("Tom",21);
    System.out.println(cust1 == cust2);//false

    String str1 = new String("Triabin");
    String str2 = new String("Triabin");
    System.out.println(str1 == str2);//false

    System.out.println("**********equals()方法的使用**********");
    System.out.println(cust1.equals(cust2));//false => Object类中的equals()方法,比较的是地址,修改相应的equals()方法后变为true,详情见Customer类
    System.out.println(str1.equals(str2));//true => String类中的equals()方法,比较的是“实体内容”,而非地址

    Date date1 = new Date(32432525324L);
    Date date2 = new Date(32432525324L);
    System.out.println(date1.equals(date2));//true => Date类中的equals()方法,比较的是“实体内容”,而非地址
    }
    }
    • 运行结果
  4. 重写equals()方法的原则:

    • 对称性:如果x.equals(y)返回的是“true”,那么y.equals(x)也应该返回是“true”。

    • 自反性:x.equals(x)必须返回是“true”。

    • 传递性:如果x.equals(y)返回是“true”,而且x.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

    • 一致性:如果x.equals(y)返回是”true“,只要x和y内容一直不变,不管重复x.equals(y)多少次,返回都是“true”。

    • 任何情况下,x.equals(null),永远返回是“false”;

      x.equals(和x不同类型的对象)永远返回是“false”。

课后练习

  1. 编写Order类,有int型的orderId,String型的orderName,相应的getter()和setter()方法,两个参数的构造器,重写父类的equals()方法:public boolean equals(Object obj),并判断测试类中创建的两个对象是否相等。

    package top.triabin.objectequalsexercise1;
    /**
    *
    * @Description equals()方法课后练习一:
    * 编写Order类,有int型的orderId,String型的orderName,相应的getter()和setter()方法,两个参数的
    * 构造器,重写父类的equals()方法:public boolean equals(Object obj),并判断测试类中创建的两个对
    * 象是否相等。
    * @author DawnLee
    * @version 1.0
    * @data 2020-8-17 22:17:20
    *
    */
    public class Order {
    private int orderId;
    private String orderName;

    public Order(int orderId, String orderName) {
    super();
    this.orderId = orderId;
    this.orderName = orderName;
    }

    public int getOrderId() {
    return orderId;
    }
    public void setOrderId(int orderId) {
    this.orderId = orderId;
    }
    public String getOrderName() {
    return orderName;
    }
    public void setOrderName(String orderName) {
    this.orderName = orderName;
    }
    @Override
    public boolean equals(Object obj) {
    if(this == obj) {
    return true;
    }
    if(obj instanceof Order) {
    Order order = (Order)obj;
    return this.orderId == order.getOrderId() && this.orderName.equals(order.getOrderName());
    }
    return false;
    }
    }
    package top.triabin.objectequalsexercise1;
    /**
    *
    * @Description equals()方法课后练习一:测试类
    * @author DawnLee
    * @version 1.0
    * @data 2020-8-17 22:34:45
    *
    */
    public class OrderTest {
    public static void main(String[] args) {
    Order order1 = new Order(1001,"AA");
    Order order2 = new Order(1001,"BB");
    Order order3 = new Order(1001,"AA");

    System.out.println(order1.equals(order2));
    System.out.println(order1.equals(order3));
    }
    }
    • 运行结果
  2. 请根据以下代码自行定义能满足需要的MyDate类,在MyDate类中覆盖equals方法,使其判断当两个MyDate型对象的年月日都相同时,结果为true,否则为false。

    package top.triabin.objectequalsexercise2;
    /**
    *
    * @Description equals()方法课后练习二:
    * 请根据以下代码自行定义能满足需要的MyDate类,在MyDate类中覆盖equals方法,使其判断当两个
    * MyDate型对象的年月日都相同时,结果为true,否则为false。
    * @author DawnLee
    * @version 1.0
    * @data 2020-8-17 22:42:38
    *
    */
    public class EqualsTest {
    public static void main(String[] args) {
    MyDate m1 = new MyDate(14,3,1976);
    MyDate m2 = new MyDate(14,3,1976);
    if(m1 == m2) {
    System.out.println("m1==m2");
    }else {
    System.out.println("m1!=m2");//m1 != m2
    }
    if(m1.equals(m2)) {
    System.out.println("m1 is equal to m2");// m1 is equal to m2
    }else {
    System.out.println("m1 is not equal to m2");
    }
    }
    }

    class MyDate{
    private int year;
    private int month;
    private int day;
    public MyDate( int month, int day,int year) {
    super();
    this.month = month;
    this.day = day;
    this.year = year;
    }
    public int getYear() {
    return year;
    }
    public void setYear(int year) {
    this.year = year;
    }
    public int getMonth() {
    return month;
    }
    public void setMonth(int month) {
    this.month = month;
    }
    public int getDay() {
    return day;
    }
    public void setDay(int day) {
    this.day = day;
    }
    @Override
    public boolean equals(Object obj) {
    if(this == obj) {
    return true;
    }
    if(obj instanceof MyDate) {
    MyDate date = (MyDate)obj;
    return this.month == date.getMonth() && this.day == date.getDay() && this.year == date.getYear();
    }
    return false;
    }
    }
    • 运行结果

toString方法

  1. 当我们输出一个对象的引用时,实际上就是调用当前对象的toString();

  2. Object类中toString()的定义:

    public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
  3. 像String、Date、File、包装类等都重写了Object类中的toString()方法,使得在调用对象的toString()方法时,返回“实体内容”信息;

  4. 自定义类也可以重写toString()方法,当调用此方法时,返回对象的“实体内容”。

  5. 代码实例:在equals()代码实例的包下创建类ToStringTest类

    package top.triabin.classobject;

    import java.sql.Date;

    /*
    * Object类中toString()的使用
    *
    * 1. 当我们输出一个对象的引用时,实际上就是调用当前对象的toString();
    *
    * 2. Object类中toString()的定义:
    * public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    *
    * 3. 像String、Date、File、包装类等都重写了Object类中的toString()方法,使得在调用对象的toString()方法
    * 时,返回“实体内容”信息;
    * 4. 自定义类也可以重写toString()方法,当调用此方法时,返回对象的“实体内容”。
    */
    public class ToStringTest {
    public static void main(String[] args) {
    Customer cust1 = new Customer("Triabin",22);
    System.out.println(cust1.toString());//top.triabin.classobject.Customer@15db9742 重写toString()方法后:Customer[name = Triabin,age = 22]
    System.out.println(cust1);//top.triabin.classobject.Customer@15db9742 重写toString()方法后:Customer[name = Triabin,age = 22]

    String str = new String("MM");
    System.out.println(str);//MM

    Date date = new Date(4534534534543L);
    System.out.println(date.toString());//2113-09-11
    }
    }
    • 在Customer类中重写toString()方法:
    //    手动实现toString()方法的重写:
    // @Override
    // public String toString() {
    // return "Customer[name = " + name + ",age = " + age + "]";
    // }

    // 自动实现:
    @Override
    public String toString() {
    return "Customer [name=" + name + ", age=" + age + "]";
    }
    • 运行结果

课后练习

  1. 定义两个类,父类GeometricObject代表几何形状,子类Circle代表圆形。

    GeometricObject
    protected String color
    protected double weight
    protected GeometricObject()
    protected GeometricObject(String color,double weight)
    属性的getter和setter方法

    空参构造器protected GeometricObject()初始化对象的color属性为“white”,weight属性为1.0。

    package top.triabin.objecttostringexercise1;

    public class GeometricObject {
    protected String color;
    protected double weight;

    protected GeometricObject() {
    color = "white";
    weight = 1.0;
    }
    protected GeometricObject(String color,double weight) {
    this.color = color;
    this.weight = weight;
    }

    public String getColor() {
    return color;
    }
    public void setColor(String color) {
    this.color = color;
    }
    public double getWeight() {
    return weight;
    }
    public void setWeight(double weight) {
    this.weight = weight;
    }
    }
    Circle
    private double radius
    public Circle()
    public Circle(double radius)
    public Circle(double radius,String color,double weight)
    radius属性的getter和setter方法
    public double findArea():计算圆的面积
    public boolean equals(Object obj)
    public String toString()

①public Circle():初始化对象的color属性为“white”,weight属性为1.0,radius属性为1.0;

②public Circle(double radius):初始化对象的color属性为“white”,weight属性为1.0,radius属性根据参数构造器确定;

③public boolean equals(Object obj):重写equals()方法,比较两个圆的半径是否相等,如相等,返回true;

④public String toString():重写toString()方法,输出圆的半径。

package top.triabin.objecttostringexercise1;

public class Circle extends GeometricObject {
private double radius;

public Circle() {
super();
radius = 1.0;
}
public Circle(double radius) {
super();
this.radius = radius;
}
public Circle(double radius,String color,double weight) {
super(color,weight);
this.radius = radius;
}

public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public double findArea() {
return Math.PI * radius * radius;
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj instanceof Circle) {
Circle c = (Circle)obj;
return this.radius == c.radius;
}
return false;
}
@Override
public String toString() {
return "Circle[radius = " + this.radius + "]";
}
}

写一个测试类,创建两个Circle对象,判断其颜色是否相等;利用equals()方法判断其半径是否相等;利用toString()方法输出其半径。

package top.triabin.objecttostringexercise1;

public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle(10,"red",1.0);
System.out.println(c1.toString());
Circle c2 = new Circle(10,"bule",1.0);
System.out.println(c2.toString());
System.out.println("c1和c2颜色相等 => " + c1.getColor().equals(c2.getColor()));
System.out.println("c1和c2半径相等 => " + c1.equals(c2));
}
}
  • 运行结果

Tips:单元测试(JUnit)

package top.triabin.junit;

import java.sql.Date;

import org.junit.Test;

/*
* Java中的JUnit单元测试
*
* 1. 选中当前工程 -> 右键 -> build path -> add libraries -> JUnit 4 -> 下一步
* 2. 创建一个Java类进行单元测试
* 此时的Java类要求:① 此类是public的; ② 此类提供公共的无参的构造器
* 3. 此类中声明单元测试方法
* 此时的单元测试方法:方法的权限是public,没有返回值,没有形参
* 4. 此单元测试方法上需要声明注释:@Test,并在单元测试类中导入:import org.junit.Test;
* 5. 声明好单元测试方法以后,就可以在方法体内测试相关代码;
* 6. 写完代码以后,左键双击单元测试方法名,右键:run as -> JUnit Test
*
* 说明:
* 1. 如果执行结果没有异常:绿条
* 2. 如果执行结果出现异常:红条
*/
public class JUnitTest {
int num = 10;

@Test
public void testEquals() {
String s1 = "MM";
String s2 = "MM";
System.out.println(s1.equals(s2));

//ClassCastException的异常
// Object obj = new String("GG");
// Date date = (Date)obj;

System.out.println(num);
show();
}

public void show() {
num = 20;
System.out.println("show()...");
}

@Test
public void testToString() {
String s2 = "MM";
System.out.println(s2.toString());
}
}
  • testEquals():

Tips:开发过程中可直接在代码文本编辑器中写@Test,然后利用软件自带的修正功能直接完成add libraries和导入等操作。

包装类的使用

  1. Java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征。

  2. 掌握:基本数据类型、包装类、String类型之间的相互转换。

    package top.triabin.classwrapper;
    /*
    * 包装类的使用
    * 1. Java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
    * 2. 掌握:基本数据类型、包装类、String三者之间的相互转换
    *
    */
    import org.junit.Test;

    public class WrapperTest {

    //基本数据类型 --> 包装类 => 调用包装类的构造器
    @Test
    public void test1() {
    int num1 = 10;
    // System.out.println(num1.toString());
    Integer in1 = new Integer(num1);
    System.out.println(in1.toString());

    //报异常:java.lang.NumberFormatException: For input string: "123abc"
    // Integer in2 = new Integer("123abc");
    // System.out.println(in2.toString());

    Float f1 = new Float(12.3);
    Float f2 = new Float("12.3");
    System.out.println(f1);
    System.out.println(f2);

    @SuppressWarnings("unused")
    Boolean b1 = new Boolean(true);
    Boolean b2 = new Boolean("TrUe");
    System.out.println(b2);//true
    Boolean b3 = new Boolean("true123");
    System.out.println(b3);//false

    Order order = new Order();
    System.out.println(order.isMale);//false
    System.out.println(order.isFemale);//null
    }

    //包装类 --> 基本数据类型 => 调用包装类的xxxValue()
    @Test
    public void test2() {
    Integer in1 = new Integer(12);
    int i1 = in1.intValue();
    System.out.println(i1 + 1);

    Float f1 = new Float(12.3);
    float f2 = f1.floatValue();
    System.out.println(f2 + 1);
    }


    /*
    * JDK 5.0新特性:自动装箱与自动拆箱
    *
    */
    @Test
    public void test3() {
    // int num1 = 10;
    // //基本数据类型 --> 包装类对象
    // method(num1);//10 Object obj = num1

    //自动装箱:
    int num2 = 10;
    Integer in1 = num2;//自动拆箱,其他数据类型类似

    //自动拆箱
    System.out.println(in1.toString());
    @SuppressWarnings("unused")
    int num3 = in1;//自动拆箱
    }
    public void method(Object obj) {
    System.out.println(obj);
    }

    //基本数据类型、包装类 --> String类型 =>调用String重载的valueOf(Xxx xxx)
    @Test
    public void test4() {
    int num1 = 10;
    //方式一:连接运算
    @SuppressWarnings("unused")
    String str1 = num1 + "";

    //方式二:调用String重载的valueOf(Xxx xxx)
    float f1 = 12.3f;
    String str2 = String.valueOf(f1);//"12.3"
    System.out.println(str2);
    Double d1 = new Double(12.4);
    String str3 = String.valueOf(d1);
    System.out.println(str3);
    }

    //String类型 --> 基本数据类型、包装类 => 调用包装类的parseXxx(String s)
    @Test
    public void test5() {
    String str1 = "123";
    //错误情况:
    // int num1 = (int)str1;
    // Integer in1 = (Integer)str1;

    int num2 = Integer.parseInt(str1);
    System.out.println(num2 + 1);

    String str2 = "true";
    boolean b1 = Boolean.parseBoolean(str2);
    System.out.println(b1);
    // String str2 = "true123";
    // boolean b1 = Boolean.parseBoolean(str2);
    // System.out.println(b1);//false

    }
    }

    class Order{
    boolean isMale;
    Boolean isFemale;
    }
    • test1:
    • test2
    • test3

      10

    • test4

    • test5

课后练习

  1. 面试题:如下两个代码输出的结果相同吗?各是什么?

    Object o1 = true ? new Integer(1) : new Double(2.0);
    System.out.println(o1);//1.0
    //题解:三元运算符在编译时需要保证前后数据类型一致,所以前面的int型的1倍自动类型提升为double型的1.0

    ```java
    Object o2;
    if (true)
    o2 = new Integer(1);
    else
    o2 = new Double(2.0);
    System.out.println(o2);//1
    public void method1(){
    Integer i = new Integer(1);
    Integer j = new Integer(1);
    System.out.println(i == j);//false 两个对象,比地址

    //Integer内部定义了一个IntegerCache结构,IntegerCache中定义了Integer[],保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在-128~127内时,可以之际使用数组中的元素,不用再去new了。目的:提高效率
    Integer m = 1;
    Integer n = 1;
    System.out.println(m == n);//true

    Integer x = 128;//范围超出-128~127,相当于new了一个对象,下面比较的是地址值
    Integer y = 128;
    System.out.println(x == y);//false
    }
  2. 利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。

    • 提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类java.util.Vector可以根据需要动态绳伸缩;
    • 创建Vector对象:Vector v = new Vector();
    • 给向量添加元素:v.addElement(Object obj);//obj必须是对象
    • 取出向量中的元素:Object obj = v.elementAt(0);
      • 注意第一个元素的下标是0,返回值是Object类型的。
    • 计算向量长度:v.size();
    • 若与最高分相差10分内:A等;20分内:B等;30分内:C等;其他:D等。
    package top.triabin.classwrapper;
    import java.util.Scanner;
    import java.util.Vector;

    /*
    * 课后练习2
    */
    public class ScoreTest {
    public static void main(String[] args) {
    Scanner scan = new Scanner(System.in);
    Vector<Integer> v = new Vector<Integer>();
    int maxScore = 0;
    for(;;) {
    System.out.print("请输入学生成绩(以负数代表输入结束):");
    int score = scan.nextInt();
    if(score < 0) {
    scan.close();
    break;
    }
    if(score > 100) {
    System.out.println("输入数据非法,请重新输入!");
    continue;
    }
    //jdk 5.0之前:
    // Integer inScore = new Integer(score);
    // v.addElement(inScore);
    //jdk 5.0之后:
    v.addElement(score);//自动装箱
    if(maxScore < score) {
    maxScore = score;
    }
    }
    char level;
    for(int i =0;i < v.size();i ++) {
    Object obj = v.elementAt(i);
    //jdk 5.0之前:
    // Integer inScore = (Integer)obj;
    // int score = inScore.intValue();
    //jdk 5.0之后:
    int score = (int)obj;

    if(maxScore - score <= 10) {
    level = 'A';
    }else if(maxScore - score <= 20) {
    level = 'B';
    }else if(maxScore - score <= 30) {
    level = 'C';
    }else {
    level = 'D';
    }
    System.out.println("student-" + i + " score is " + score + ", leval is " + level);
    }
    }
    }

章节练习题及面试题

继承性

  1. java类是否可以多继承,怎么实现多继承?

    答:java没有多继承,但可以通过接口的形式来达到多继承的目地。

  2. 继承练习

    定义类A和类B如下:

    class A {
    int a = 1;
    double d = 2.0;

    void show() {
    System.out.println("Class A: a=" + a + "\td=" + d);
    }
    }

    class B extends A {
    float a = 3.0f;
    String d = "Java program.";

    void show() {
    super.show();
    System.out.println("Class B: a=" + a + "\td=" + d);
    }
    }
    • 若在应用程序的main方法中有以下语句:

      A a = new A();

      a.show();

      则输出的结果如何?

      答:Class A:a=1 d=2.0

    • 若在应用程序的main方法中定义类B的对象b:

      A b = new B();

      b.show();

      则输出结果如何?

      答:Class A:a=1 d=2.0

      ​ Class B:a=3.0 d=Java program

  3. 重载(overload)和重写(override,有的书也叫做“覆盖”)的区别?

    答:方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被”屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。

  4. Overload的方法是否可以改变返回值类型?

    答:Overload的方法可以改变返回值类型。

  5. 写出错误答案错误的原因。

    class Demo{
    int show(int a,int b){return 0;}
    }

    下面哪些函数可以存在于Demo的子类中?

    A.public int show(int a,int b){return 0;}//可以,覆盖

    B.private int show(int a,int b){return 0;}//不可以,权限不够

    C.private int show(int a,long b){return 0;}//可以,和父类不是一个函数。没有覆盖,相当于重载

    D.public short show(int a,int b){return 0;}//不可以,因为该函数不可以和给定函数出现在同一类中,或者子父类中

    E.static int show(int a,int b){return 0;}//不可以,静态只能覆盖静态。

  6. 写出程序的结果。

    class Super {
    public int get() {
    return 4;
    }
    }

    class Demo15 extends Super {
    public long get() {
    return 5;
    }

    public static void main(String[] args) {
    Super s = new Demo15();
    System.out.println(s.get());
    }
    }

    答:编译失败;原因:因为子类父类中的get方法没有覆盖。但是子类调用的时候不能明确返回值是什么类型,所以这样的函数不可以存在字符类中。

  7. 按要求实现下列问题:实现一个名为Person的类和它的子类Employee,Employee有两个子类Faculty和Staff。具体要求如下:

    ① Person类中的属性有:姓名name(String类型),地址address(String类型),电话号码telephone(String类型);

    ② Employee类中的属性有:办公室office(String类型),工资wage(double类型),受雇日期hiredate(String类型);

    ③ Faculty类中的属性有:学位degree(String类型),级别level(String类型);

    ④ Staff类中的属性有:职务称号duty(STring类型);

    ⑤现有对象Person p1 = new Faculty()和Person p2 = new Staff(),请分别为p1的属性赋值“本科”和Staff类的duty属性赋值“职员”。

       

Object类的使用说明

包装类的使用

super关键字

多态性