额外知识点
代码块
定义
在类中只有一对大括号,大括号内无任何代码,如图所示。
注意
一个类中可以有多个代码块,且代码块中可以写代码
代码块分类
普通代码块
定义:普通代码块是一个没有名称的代码块,通常包含一些需要执行的语句。它可以出现在类的方法中,也可以出现在if、for、while等语句中
注意: 普通代码块无法接收外界的参数(即无法传参)(构造器可以传参)
静态代码块
定义:静态代码块是用static关键字定义的一段代码块,它在类加载时会被执行一次,且只会被执行一次。它通常被用来初始化静态变量(即初始化静态属性)或执行一些静态的代码。
两种代码块的区别
以代码运行截图来说明,相关解释在注释中
两种代码块的特点
普通代码块
- 只在创建对象时会被调用,且按照定义的顺序依次调用,且普通代码块的调用早于构造器的调用,如图所示;
- 可以访问类的成员变量和方法;
- 与静态代码块不同,普通代码块不能定义静态变量。
静态代码块 - 只会被执行一次,且在实体类加载时被执行。(即静态代码块随着类的加载按照定义的顺序依次调用)如图所示;
- 不能访问非静态的成员变量和方法;
- 可以有多个静态代码块,它们的执行顺序与定义顺序一致。
两种代码块执行顺序
以实例代码为例
- Pet父类代码
package com.atguigu.day06;
public class Pet {
private String petName;
private int petAge;
private String petGender;
//Constructor
public Pet(){
System.out.println("父类Pet无参构造器被调用");
}
public Pet(String petName, int petAge, String petGender){
this.petName = petName;
this.petAge = petAge;
this.petGender = petGender;
}
//get/set方法
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public int getPetAge() {
return petAge;
}
public void setPetAge(int petAge) {
this.petAge = petAge;
}
public String getPetGender() {
return petGender;
}
public void setPetGender(String petGender) {
this.petGender = petGender;
}
//静态代码块
static {
System.out.println("Pet父类静态代码块1");
}
//普通代码块
{
System.out.println("Pet父类普通代码块1");
}
//静态代码块
static {
System.out.println("Pet父类静态代码块2");
}
//普通代码块
{
System.out.println("Pet父类普通代码块2");
}
public void playGame(){
System.out.println("和主人玩耍");
}
}
- 子类Dog代码
package com.atguigu.day06;
public class Dog extends Pet{
private String color;
//Constructor
public Dog() {
System.out.println("子类Dog无参构造器被调用");
}
public Dog(String petName, int petAge, String petGender, String color){
super(petName, petAge, petGender);
this.color = color;
}
//get/set方法
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
//普通代码块
{
System.out.println("Dog子类普通代码块1");
}
{
System.out.println("Dog子类普通代码块2");
}
//静态代码块
static {
System.out.println("Dog子类静态代码块1");
}
static {
System.out.println("Dog子类静态代码块2");
}
}
- 测试类代码
package com.atguigu.day06;
public class TestDog {
public static void main(String[] args) {
Pet pet = new Dog();
pet.setPetName("小黄");
pet.setPetAge(5);
pet.setPetGender("公");
pet.playGame();
}
}
- 运行截图如下:
综上,可得出代码块的运行顺序:
父类静态代码块—>子类静态代码块—>父类普通代码块—>父类构造器—>子类普通代码块—>子类构造器
属性赋值
例子代码
- Pet代码
- Cat代码
通过setxxx()方法进行赋值
- 父类Pet类
package at.guigu.codeblock;
public class Pet {
private String name;
private int age;
//构造器
public Pet() {
System.out.println("父类无参构造器被调用");
}
public Pet(String name, int age) {
System.out.println("父类有参构造器被调用");
this.name = name;
this.age = age;
}
//get/set方法
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 void play() {
System.out.println("和主人玩");
}
}
- 子类Dog类
package at.guigu.codeblock;
//测试:不通过子类构造器是否可以给父类中的属性赋值
public class Dog extends Pet{
private String size;
public Dog() {
//自动有个super()来调用父类的无参构造器
}
public Dog(String size) {
this.size = size;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
}
- 测试类
package at.guigu.codeblock;
public class TestDog {
public static void main(String[] args) {
Dog dog = new Dog();
dog.setName("狗蛋");
dog.setAge(5);
dog.setSize("大狗");
}
}
在属性声明的后面直接进行赋值
此处不对此进行例子解释
通过构造器给普通属性赋值
假设Cat有一个父类没有,而Cat有的独有的属性;如上图Cat代码图所示。此时可通过构造器给其进行赋值
通过无参构造器对其进行赋值
此时Cat代码如图一所示,TestCat代码如图二所示,运行结果如图三所示
通过有参构造器对其进行赋值
此时Cat代码如图一所示,TestCat代码如图二所示,运行结果如图三所示
注意:
有参构造器对其进行赋值时,由运行结果截图可看出getInfo方法并未将猫的颜色打印出来,所以此时需要重写getInfo方法,如图所示
问题
- Cat的独有属性color的访问修饰符是private,则在测试类中为什么可以访问color并给其进行赋值?
答:在Java中,通过构造器可以给访问修饰符为private的属性进行赋值。这是因为构造器是在类内部执行的,它可以访问类中的所有成员变量和方法,包括那些被声明为private的属性。在测试类中,可以通过创建类的实例来调用构造器并给private属性赋值。但是,由于private属性是只能在类内部访问的,因此在测试类中不能直接访问这些属性。通常情况下,可以使用类的公共方法来间接地访问和修改这些private属性的值。
通过代码块给普通属性和静态属性进行赋值
- 单个类:先对静态属性进行赋值然后在按顺序执行静态代码块。调用对象时先对普通属性进行赋值在按顺序调用执行普通代码块
- 父子类(先父类再子类,先静态再普通):先对父类的静态属性进行赋值然后在按顺序调用执行父类的静态代码块,执行结束后,开始对子类的静态属性进行赋值然后在按顺序调用子类的静态代码块。调用对象时先对父类的普通属性进行赋值然后在按顺序调用执行普通代码块,结束后开始对子类的普通属性进行赋值然后在按顺序调用子类的普通代码块,最后调用构造器。
详见例题截图,如下 - 父类
- 子类
- 测试类及运行结果
Object根父类
Object类特点
- 任何一个java类都隐式继承Object类,要么是Object类的直接子类,要么是Object类的间接子类。说白了Object类是所有java类的总父类
- Object类位于java包下的lang包中,任何java类都默认导入lang包
Object类中的若干个方法
toString()
方法签名: public String toString()
特点
- 任何对象被放进println中进行打印时或者被放到字符串上进行拼接时,其默认调用的都是toString()方法。
- 默认情况下,
toString()
方法返回的是“对象的运行时类型@对象的hashCode值的十六进制形式
”,通过调用getClass().getName()
获取对象所属的完整类名及位置,调用hashCode()
获取对象的哈希码,并将其转换为十六进制字符串,最后将它们拼接起来返回。如图所示
问题:
- 以上截图示例中,父类中未写toString原因:
Object类是所有java类的子类,toString()方法是Object类中的方法,所以可以不用写在Student类中直接在测试类中调用- 在测试类中用System.out.println(stu);而不是用System.out.println(stu.toString());的原因:
子类在使用Object类中的方法时可以不用对象名.方法名调用,直接使用即可。
- 该方法只是Object类的默认实现,可以被子类重写以提供更有意义的字符串表示形式。如图所示
注意事项
- 通常情况下,建议重写toString()方法
- 若我们直接System.out.println(对象),则会自动调用这个对象的toString()
getClass()
定义
getClass()方法返回一个Class对象,该对象表示当前对象的运行时类型(即获取对象的运行时类型)。Class对象包含有关类的信息,例如类的名称、包、父类、实现的接口、字段、方法等。
语法
public final Class<?> getClass()
该方法不接受任何参数,它是一个非常简单的方法,只需返回调用对象的运行时类型的Class对象。
实例
-
getClass()
-
getClass().getName()
equals()方法
Object类中的equals()方法
Object类中equals()方法的含义
public boolean equals(Object obj) {
return (this == obj);
}
- equals()方法是用来比较两个对象是否相等的方法,其默认实现是比较两个对象的引用是否相等(即比较两个对象是否是同一个对象)。也就是说比较两个对象的地址是否相同(此时equals()方法和 “==”的效果是一样的)。如图所示
注意:
(1)Object类中equals()方法的含义不能满足实际需求,因为通常我们需要比较对象的内容是否相等。所以通常需要对equals()方法进行重写
(2)为了比较对象的内容是否相等而重写equals()方法时,还需要对hashCode方法进行重写,以确保相等的对象具有相同的哈希码。
问题 - 比较两个对象的内容是否相等时为什么要对hashCode()方法进行重写?
(1)java中的hashCode()方法会根据对象的内存地址计算出一个哈希值,以此来帮助快速查找到对象的位置。
(2)尽可能的减少哈希冲突(即不同的对象具有相同的哈希值)的概率(注意:不能保证完全避免哈希冲突的发生) - 能不能说两个对象的内容相同时其哈希值一定相同?
不能,如果两个对象的内容一致时,只能说它们的哈希值应该尽量相同,并不能保证一定的相同,因为哈希值的范围是有限的,而对象的内容可能非常复杂,因此可能存在不同的对象具有相同的哈希值(即哈希冲突)。而且哈希码是不唯一的。 - 能把不能说哈希值相同的两个对象的内容是相同的?
(1)这是不能够保证的,因为哈希值的计算过程是通过将对象的各个属性计算得出的,不同的对象也有可能得到相同的哈希值(即哈希冲突)
(2)当然,如果两个对象的哈希值不同,则一定可以确定它们的内容肯定不同,但如果它们的哈希值相同,则需要进一步利用equals()方法来确定它们的内容是否相同
重写equals()方法步骤
- Step1:右键–>Generate–>equals() and hashCode()
- Step2:遇到的所有对勾都打上–>然后一路Next
- Step3:Create
重写后的equals()代码及运行结果如下:
Objects类中的equals()方法
源码:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
Objects类中的equals方法与Object类中的equals方法的作用相同,不过其是对Object类中的equals方法的优化,即Objects类中的equals方法可以处理空引用,避免了NullPointerException异常的发生。此外,Objects类中的equals方法还可以比较数组类型的对象。如果两个对象都是null,则Objects.equals方法返回true;如果其中一个对象为null,则返回false;否则,返回两个对象的equals方法的比较结果。
String类中的equals()方法
String类对Object类中的equals方法进行了重写。在String类中,equals方法被覆盖,以比较两个String对象的内容而不是引用。
String类并没有直接重写Objects类中的equals方法。 Objects.equals方法在比较时会调用对象的equals方法,而String类已经在自己的equals方法中处理了对象内容的比较。所以,对于String类型,Objects.equals方法和String类的equals方法在比较时都是比较对象的内容。
- 作用
比较两个字符串的内容是否相等(可详见大数据—javase基础—day2中的额外知识点)
问题
- 系统什么时候用Object类中的equals()方法,什么时候用String类中的equals()方法?
在Java中,字符串是一种特殊的对象类型,它可以使用equals方法来比较两个字符串是否相等。这是因为String类已经重写了Object类中的equals方法,实现了字符串内容的比较逻辑。
当你调用equals方法时,会根据对象的类型来决定使用哪个equals方法。如果你调用的是一个Object类型的对象的equals方法,则会使用Object类中的equals方法,它比较的是对象的引用是否相等。如果你调用的是一个String类型的对象的equals方法,则会使用String类中重写的equals方法,它比较的是字符串的内容是否相等,而不是比较它们的引用是否相等。因此,当你需要比较字符串内容是否相等时,应该使用String类中的equals方法。系统会自动进行判断。
- ==
主要用于比较两个对象的引用是否相等,或者比较两个基本数据类型的值是否相等
wait()方法
- 定义
wait()方法为普通方法(即Object类中定义的实例方法,因此它们需要通过对象实例来调用),使当前线程进入等待状态,直到其他线程调用相同对象上的notify()或notifyAll()方法来唤醒它。在调用wait()方法之前,线程必须先获得对象的锁。该方法有几个可重载的版本,可以指定等待的时间限制。
注意: wait方法会释放锁对象和cpu wait()
无参版本的wait()方法
将当前线程置于等待状态,直到其他线程调用相同对象上的notify()或notifyAll()方法唤醒它。wait(long timeout)
带有一个timeout参数的wait()方法
当前线程将进入等待状态,并持续等待指定的时间,以毫秒为单位。如果在等待时间结束之前,其他线程调用相同对象上的notify()
或notifyAll()
方法来唤醒等待的线程,那么线程将被立即唤醒,不需要等待指定的时间。
如果在等待时间结束之前没有其他线程调用相同对象上的唤醒方法,那么等待的线程将自动被唤醒,并且从等待状态转换为可运行状态。
需要注意的是,当线程被唤醒时,它还需要重新获得对象的锁才能继续执行。因此,在使用wait(long timeout)
方法时,需要在同步块或同步方法中调用,确保线程在正确的同步环境下等待和唤醒。
timeout
参数表示等待的时间限制,以毫秒为单位。wait(long timeout, int nanos)
带有两个参数timeout和nanos的wait()方法
将当前线程置于等待状态,并持续等待指定的时间如果在指定的时间内,其他线程调用相同对象上的notify()
或notifyAll()
方法来唤醒等待的线程,那么线程将被立即唤醒,不需要等待整个指定的时间。
如果在指定的时间内没有其他线程调用相同对象上的唤醒方法,那么等待的线程将自动被唤醒,并且从等待状态转换为可运行状态。
需要注意的是,当线程被唤醒时,它还需要重新获得对象的锁才能继续执行。因此,在使用wait(long timeout, int nanos)
方法时,需要在同步块或同步方法中调用,确保线程在正确的同步环境下等待和唤醒。
timeout
参数表示等待的时间限制,以毫秒为单位。
nanos
参数表示额外的纳秒时间,范围为0到999999。- 注意
(1)当线程调用wait()方法后,它会立即释放当前持有的锁对象(这一点与Thread类中的sleep()方法刚好相反)并释放CPU,允许其他线程获得该锁对象并执行相关的同步操作。
(2)为了调用wait()方法,线程必须先获得对象的锁。如果在没有获得锁的情况下调用wait()方法,会抛出IllegalMonitorStateException异常。因此,在使用wait()方法时,通常会在同步块或同步方法中调用,以确保线程在正确的同步环境下进行等待和唤醒。
notify()方法
notify()方法为普通方法(即Object类中定义的实例方法,因此它们需要通过对象实例来调用),用于唤醒在相同对象上调用wait()方法而进入等待状态的线程中的一个线程。注意,它只会唤醒其中一个线程,但具体唤醒哪个线程是不确定的。如果有多个线程在等待状态,系统将随机选择其中一个线程唤醒,并使其进入可运行状态。
注意:
(1)调用notify()方法的线程不会立即释放锁对象并将锁对象给唤醒的线程,也不会立即释放CPU。调用notify()方法只是唤醒一个等待在相同对象上的线程,并使其进入可运行状态。
(2)唤醒的线程只有在调用notify()方法的线程释放了锁对象之后,才能获得锁对象并继续执行。这意味着调用notify()方法的线程执行完同步块或同步方法后,才会释放锁对象,并允许被唤醒的线程获取锁对象并执行相关操作。(即在多线程环境中,调用notify()方法仅仅是唤醒一个等待线程,但并不会立即释放锁对象或将锁对象给唤醒的线程。唤醒的线程需要等待调用notify()方法的线程释放锁对象之后,才能获取锁对象并继续执行。)
notifyAll()方法
- 定义
notifyAll()方法为普通方法(即Object类中定义的实例方法,因此它们需要通过对象实例来调用),用于唤醒在相同对象上调用wait()方法而进入等待状态的所有线程。被唤醒的线程将从等待状态转换为可运行状态,等待获取锁后继续执行。注意,notifyAll()会唤醒所有等待的线程,而不仅仅是一个线程。
这个方法在多线程编程中非常有用,特别是在需要唤醒多个等待线程的情况下。通过调用notifyAll(),可以唤醒所有等待该对象锁的线程,使它们有机会继续执行。
需要注意的是,调用notifyAll()方法的前提是当前线程已经获得对象的锁。否则,会抛出IllegalMonitorStateException异常。通常,notifyAll()方法被放置在同步代码块或同步方法中,以确保线程在正确的同步环境下调用该方法。
注意: wait()方法、notify()方法、notifyAll()方法可详见day11等待唤醒机制的内容知识点总结归纳
final关键字
final意义:表示最终的,不可更改
final修饰
final修饰类
作用
表示这个类不能被继承,没有子类。即该类属于太监类
final修饰方法
作用
表示这个方法不能被子类重写
final关键字修饰的方法不能被重写。final修饰的方法表示该方法不能被子类重写或覆盖。如果在子类中尝试重写final方法,编译器将会报错。这是因为final方法在父类中已经是最终实现,不允许再进行修改。
final修饰变量(成员变量或局部变量)
作用
final修饰某个变量,表示该变量的值就不能被修改,即该变量成为了一个常量
要点
- final修饰成员变量时有两种赋值方式
(1)在声明属性的同时直接赋值,否则会报错。图一为错误示范;图二为正确示范
(2)在类的构造器中为final属性赋值,如代码所示
public class Student {
final String name;
public Student () {
name = "张三";
}
}
- final修饰局部变量时
可以不在初始化时赋值,在初始化之后赋值是被允许的,如图一;但是赋值之后不能更改其值,否则会下划线标红报错,如图二
以上两种区别的原因
属性在声明时就已经有了默认初始值,如果不在声明属性的同时对其进行赋值,则会认定该属性的值就是其默认初始值
多态
多态实现的三种方式
- 普通父类+子类实现多态
- 抽象父类+子类实现多态(详见javase基础day7)
- 接口+实现类实现多态(详见javase基础day7)
举例说明
创建四个类,分别是Pet、Dog、Cat、Master。其中Pet中定义了一个play方法(输出内容为和主人玩),且Dog和Cat对play方法进行了重写(输出内容分别为玩飞盘和玩儿毛线球)。而Master类为主人类,主要是为了和宠物玩,其代码如下:
创建主人的测试类TestMaster用来测试和宠物玩,其代码及运行结果如图
注意: 当Master类中的playWithPet方法的引用数据类型为Dog类,此时测试类中的Dog dog = new Dog()属于本态(即本类的引用指向本类的对象)
那么问题来了: 有的人可能不止一个宠物,可能有猫、狗、鸟等多个宠物;在上述Master实体类中定义的playWithPet方法的参数是引用数据类型Dog类,只能传入Dog类的对象。则如果你有其他的宠物(比如猫),你要怎么传入Master类中与猫玩耍
鉴于以上问题即可引入多态
由于狗、猫都属于宠物,所以可以在Mater类中playWithPet方法的参数可以直接是引用数据类型Pet类(Pet类是Dog类和Cat类的父类)
原因:
多态中任何一个需要父类引用数据类型的位置都可以传递一个子类对象
此时Master类的代码如图一所示,TestMater方法一如图二所示,方法二如图三所示
注意:
当Master类中的playWithPet
方法的引用数据类型为Pet
类(父类),此时测试类中的Pet pet = new Dog()
或者Dog dog = new Dog()
属于父类的引用指向子类的对象
多态定义及实现步骤
什么是多态
任何一个需要父类引用的位置都可以传递一个子类对象
实现多态的三个关键步骤
- 必须要有继承,必须要有父子类
- 父类必须定义方法(原因:这样在Master类中引用才能调的出来),子类必须重写方法(原因:多态就是要不同的子类体现不同的特征)
- 父类的引用指向子类的对象
问题
- 本态和多态哪个优先级高?
答:本态的优先级更高,能匹配本态时优先匹配本态;没本态时匹配多态
多态的形式和体现
多态的引用
父类类型 变量名 = 子类对象
多态引用的表现
- 编译时类型与运行时类型不一样,编译时看“父类”,运行时看“子类”
原因
解释:Pet类和它的子类Dog和Cat。Pet类中有一个play()方法,而Dog和Cat类分别重写了play()方法。那么当通过Petl类型的引用调用play()方法时,编译器只能检查Pet类中是否有play()方法,但无法确定实际调用的是Dog类中的play()方法还是Cat类中的play()方法。而在运行时期,当Pet类型的引用指向Dog类的对象时,实际上调用的是Dog类中重写后的play()方法;当Pet类型的引用指向Cat类的对象时,实际上调用的是Cat类中重写后的play()方法。这就是多态在编译时看“父类”,运行时看“子类”的原理。
多态引用的缺点
- 编译时只能调用父类声明的方法,不能调用子类扩展的方法。(也可以说是在多态状态下,只能看见父类中定义的方法,子类定义的独有的方法无法看见)
举例说明
在Dog类中创建一个work方法(输出“和主任一起工作”),而父类Pet中没有,此时你用父类的引用来调用子类Dog中的work方法时则会标红报错,如图所示
此时如果你不想其出错,则你需要在父类中也写一个work方法才可。
应用多态解决问题
声明变量时父类类型,变量赋值是子类对象
原因
1.方法的形参是父类类型,调用方法的实参是子类对象
2.实例变量声明父类类型,实际存储的是子类对象。(即父类的引用指向子类的对象)
多态数组—了解
数组元素是父类类型,元素对象是子类对象
代码实现实例
- Pet2父类代码
package com.atguigu.day06;
public class Pet2 {
private String petName;
private int petAge;
private String petGender;
public Pet2(){
}
public Pet2(String petName, int petAge, String petGender) {
this.petName = petName;
this.petAge = petAge;
this.petGender = petGender;
}
//get/set方法
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public int getPetAge() {
return petAge;
}
public void setPetAge(int petAge) {
this.petAge = petAge;
}
public String getPetGender() {
return petGender;
}
public void setPetGender(String petGender) {
this.petGender = petGender;
}
//玩耍
public Pet2 playGame(){
System.out.println("和主人玩耍");
return null;
}
}
- Dog2子类代码
package com.atguigu.day06;
public class Dog2 extends Pet2{
private String color;
public Dog2(){
}
public Dog2(String petName, int petAge, String petGender, String color){
super(petName, petAge, petGender);
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public Dog2 playGame(){
System.out.println("和主人玩丢飞盘");
return null;
}
}
- 多态数组代码
/*
与Pet2、Dog2为一体用来测试多态数组
*/
package com.atguigu.day06;
public class ManyArray {
public Pet2 sale(String type){
switch(type) {
case "Dog2" :
return new Dog2();
}
return null;
}
}
- 测试类代码
package com.atguigu.day06;
public class TestManyArray {
public static void main(String[] args) {
ManyArray ma = new ManyArray();
Pet2 pet2 = ma.sale("Dog2");
pet2.setPetName("小黄");
pet2.setPetAge(5);
pet2.setPetGender("公");
/*
由于编译时看等号左侧的对象是否有对应的方法,此处父类pet2中没有setcolor方法
属于子类Dog2独有的,所以需要先将其进行向下转型,然后在设置color的参数
*/
Dog2 dog2 = (Dog2) pet2;
dog2.setColor("黄色");
pet2.playGame();
}
}
向上转型和向下转型
向上转型(自动完成)
定义
子类的对象被当作父类引用,比如:Pet pet = new Dog()
解释
引用调用方法时必须看引用数据类型,方法运行时体现对象的方法特征。这就更清晰的解释了刚刚调用子类Dog中的work方法报错的原因,即Pet pet = new Dog()
的引用数据类型为Pet类,而Pet类中没有work方法,所以报错
注意:
Pet pet = new Dog()
等号左侧叫做编译时类型,等号右侧叫做运行时类型。能调用什么方法看编译时类型,方法运行体现的特点是运行时类型的特点
向下转型(手动完成)
格式
(子类类型)父类类型
原理
将父类的引用强制转换成子类的引用,并将其赋给子类的引用。
注意
可以解决父类没有而子类独有的方法被调用出错的问题,例如:之前在Dog类中创建了一个其独有的类work,该work类在父类Pet中没有,所以在测试类中调用该方法时出现了报错,解决办法一如图所示:
方法二:也可以在Master类中解决问题,如下图所示
instanceof 运算符
在上述例子的解决方法二中,由于Master类中的playWithPet
方法内有Dog dog = (Dog)pet;``和
dog.work();所以在测试类中只对“父类的引用指向Dog类的对象引用
”有效;而对“父类的引用指向Cat类的对象
”无效。所以若指向Cat类对象则会报错,如图所示
上述图二也可以是
原因:
1.Master类中的playWithPet方法内有Dog dog = (Dog)pet
2.Cat类中没有work方法,该方法是Dog类中独有的方法
解决方案就是利用instanceof 运算符来做一个判断
A instanceof B
含义
- 其中A一定是一个对象,B一定是一个类
- 若左侧的这个对象是右侧这个类的对象或右侧这个类的子类对象,则返回True,反之则返回False
正确如图所示
注意:
在进行向下转型之前,必须使用 instanceof 运算符来检查对象是否是所需的类型,以避免ClassCastException(类型转换异常) 异常的发生。
A instanceof B that
含义
若左侧的这个对象A是右侧这个类的对象或右侧这个类的子类对象,则用that来指代A这个对象,详见在比较两个对象的内容是否相等时重写后的equals方法
if(A instanceof B that)等同于
if(A instanceof B) {
B that = (B)A;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof StudentTwo that)) return false;
return getAge() == that.getAge() && Objects.equals(getName(), that.getName()) && Objects.equals(getGender(), that.getGender());
/*
o instanceof StudentTwo that
判断o这个对象是否是右侧这个类的对象或右侧这个类的子类的对象,若是则用that代替o这个对象
由于字符串不是基本数据类型,为对象类型,所以为了比较对象是否相等则用了Object.equals()方法
==主要用于比较两个对象的引用是否相等,或者比较两个基本数据类型的值是否相等
getAge()用来获取的是调用equals方法所属对象的数据
that.getAge()用来获取的是equals方法的参数 o 的数据
*/
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge(), getGender());
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof StudentTwo that)) return false;
return getAge() == that.getAge() && Objects.equals(getName(), that.getName()) && Objects.equals(getGender(), that.getGender());
}
等同于
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof StudentTwo)) return false;
else if {
StudentTwo that = (StudentTwo)o;
return getAge() == that.getAge() && Objects.equals(getName(), that.getName()) && Objects.equals(getGender(), that.getGender());
}
}
成员变量(属性)没有多态性,只有普通方法有多态性。如下