instanceof 关键字的使用
编译时类型(声明类型)与运行时类型(实例类型)
编译时类型(声明类型):
- 编译时类型是指变量声明时所指定的类型,或者说是变量的静态类型。
- 这个类型在代码编译时就已经确定,编译器根据这个类型来进行类型检查和类型推断。
- 编译时类型决定了该变量可以调用哪些方法和访问哪些成员变量。
运行时类型(实例类型):
- 运行时类型是指对象在内存中实际的类型,或者说对象的实际类。
- 这个类型是在程序运行时动态确定的,可以通过
instanceof
进行检查。- 运行时类型决定了该对象真正所拥有的属性和方法。
Person o3 = new Student();//那这个是什么类型
Person o3 = new Student();
这一行代码涉及的是 多态 的概念,具体来说,它是父类引用指向子类对象。解释:
- 声明类型:
Person
是o3
的声明类型,意味着编译时o3
被视为Person
类型。- 实例化类型:
new Student()
创建了一个Student
类型的对象。虽然o3
是Person
类型的引用,但它指向的是Student
类型的对象。关键点:
- 声明类型(编译时类型):
o3
的声明类型是Person
。这意味着o3
只能调用Person
类中定义的方法(包括父类Person
中继承过来的方法),不能直接调用Student
类中特有的方法(除非强制类型转换)。- 实例类型(运行时类型):
o3
实际上引用的是new Student()
创建的对象,new Student()
创建的对象的实际类型是Student
。这意味着在运行时,o3
指向的对象是Student
类型,而Student
是Person
的子类,因此可以通过instanceof
进行判断。
instanceof
的工作原理
instanceof
是一个用于判断对象是否是某个类的实例,或者是否是该类子类的实例的运算符。其工作原理依赖于 编译时类型 和 运行时类型。
instanceof
判断规则:
- 左侧对象(变量)的类型:左侧对象的 编译时类型 和 运行时类型 都会影响
instanceof
判断。如果编译时类型与目标类类型不兼容,编译时就会报错。- 右侧类(目标类型)的类型:如果左侧对象的 运行时类型 是目标类或目标类的子类,则返回
true
,否则返回false
。总结:
- 编译时类型:是变量声明时所给定的类型,决定了变量可以调用哪些方法,进行哪些操作。
- 运行时类型:是对象在内存中实际的类型,决定了对象可以访问哪些成员。
instanceof
的工作原理
- 在编译时检查左边的类型与右边的类是否兼容。
- 在运行时检查左边对象的实际类型(运行时类型)是否是右边类的实例或子类实例。
CAL 总结一句话!:instanceof通过不通过看声明类型(编译时类型)是否兼容,返回值看实例类型(运行时类型)是不是calssname 的实例或者子类的实例
父类 Person
package com.oop.demo06;public class Person {public Person() {} }
子类 Student
package com.oop.demo06;public class Student extends Person{public Student() {} }
子类Teacher
package com.oop.demo06;public class Teacher extends Person {String name;public Teacher() {}public Teacher(String name) {this.name = name;} }
启动类Application
package com.oop.demo06; //此例主要讨论 instanceof 关键字的使用 /* * 声明时类型(编译时类型) 左边的 * 实例类型(运行时类型) 右边的 * 使用格式:object instanceof ClassName * 所以说 instanceof 编译通过不通过 是看声明类型(编译时类型)是否兼容(是彼此,或者直接继承就是彼此) * 返回值是true还是false 是看实例类型(运行时类型)是不是 calssname 的实例或者子类的实例 * 是就返回ture 不是就返回false * */ import sun.rmi.transport.ObjectTable;public class Application {public static void main(String[] args) {// Object > Person > Student// Object > Person > Teacher// Object > String Ctrl +H 打开结构树Student o1 = new Student();System.out.println(o1 instanceof Student); // true//System.out.println(o1 instanceof Teacher); // 编译错误,o1 是 Student 类型,不能转换为 Teacher//System.out.println(o1 instanceof String); // 编译错误,o1 是 Student 类型,不能转换为 StringSystem.out.println(o1 instanceof Person); // trueSystem.out.println(o1 instanceof Object); // trueSystem.out.println("============");//Teacher o2 = new Student();编译不通过Teacher o2 = new Teacher();//System.out.println(o2 instanceof Student); // 编译错误,o2 是 Teacher 类型,不能转换为 Student//System.out.println(o2 instanceof String); // 编译错误,o2 是 Teacher 类型,不能转换为 StringSystem.out.println(o2 instanceof Teacher); // trueSystem.out.println(o2 instanceof Person); // trueSystem.out.println(o2 instanceof Object); // trueSystem.out.println("============");Person o3 = new Student();//System.out.println(o3 instanceof String); // 编译错误,o3 编译时是 Person 类型,不能转换为 StringSystem.out.println(o3 instanceof Student); // true o3运行时时 Student()的实例 可由Student实例化得到System.out.println(o3 instanceof Teacher); // falseSystem.out.println(o3 instanceof Person); // true o3运行时时 Student()的实例 可由Person子类Student实例化得到System.out.println(o3 instanceof Object); // trueo3运行时时 Student()的实例 可由Obeject的子(Person)->子(Student)类实例化得到System.out.println("============");Person o4 = new Teacher();//System.out.println(o4 instanceof String); // 编译错误,o4 编译是是 Person 类型,不能转换为 StringSystem.out.println(o4 instanceof Student); // falseSystem.out.println(o4 instanceof Teacher); // trueSystem.out.println(o4 instanceof Person); // trueSystem.out.println(o4 instanceof Object); // trueSystem.out.println("============");Object o5 = new Student();System.out.println(o5 instanceof String); // false o5 是obeject类型System.out.println(o5 instanceof Student); // trueSystem.out.println(o5 instanceof Teacher); // falseSystem.out.println(o5 instanceof Person); // true} }
对象类型转换
1. 小转大:子类转换为父类(向上转型)
这是允许的,因为每个子类实例都可以视为父类的实例,子类自动符合父类的结构。所以子类对象可以赋值给父类引用,或者直接转换成父类类型。
// 小转大:子类转父类
Son s1 = new Son();
Father f1 = s1; // 隐式转换(向上转型),这是合法的
f1.run(); // 调用父类的run方法// 或者强制转换
((Father)s1).run(); // 显式转换,调用父类的run方法
解释:
- 子类对象可以赋值给父类引用:
Father f1 = s1;
这样可以直接用子类对象的父类部分。 - 强制转换
((Father)s1).run();
也是合法的,因为s1
实际上指向的是Son
类型的对象。
2. 大转小:父类转换为子类(向下转型)
这是不推荐直接做的,必须确保对象在运行时确实是子类的实例。否则,编译时即使允许,也会导致运行时抛出 ClassCastException
。
2.1 编译时父类,运行时子类:可以强制转换
如果声明时是父类引用,但运行时对象类型是子类,那么你可以通过强制转换将父类引用转换成子类类型,调用子类的特有方法。
Father f2 = new Son(); // 编译时是父类引用,运行时是子类实例
f2.run(); // 调用父类方法,OK((Son)f2).study(); // 强制转换,调用子类方法
解释:
- 这里,
f2
在编译时是Father
类型,但它实际上是指向Son
类型的对象(多态)。因此,你可以强制转换f2
为Son
类型,调用Son
特有的方法study()
。 - 需要注意的是,在进行强制转换之前,你应该确保
f2
实际上指向的是Son
类型的对象。否则会抛出ClassCastException
。
2.2 编译时父类,运行时也是父类:不可以强制转换
如果编译时是父类引用,且运行时对象也是父类类型,那么你不能强制转换为子类类型,转换会失败。
Father f3 = new Father(); // 编译时和运行时都是父类
((Son)f3).study(); // 强制转换会抛出 ClassCastException
解释:
- 这里,
f3
实际上是Father
类型的对象,不能强制转换为Son
类型。这种情况下,会抛出ClassCastException
,因为Father
类和Son
类没有直接的关系(Father
并不是Son
的父类)。
总结:
- 小转大:子类转父类(向上转型)
- 允许:子类对象可以赋值给父类引用。
- 调用父类的方法,不会调用子类特有的方法。
- 大转小:父类转子类(向下转型)
- 如果编译时是父类引用,运行时是子类对象,则可以通过强制转换调用子类的方法(多态情况下可以转换)。
- 如果编译时是父类引用,运行时也是父类对象,则无法进行转换,强转会抛出
ClassCastException
。
ClassCastException
是 运行时异常(Runtime Exception)。异常类别
在 Java 中,异常大致分为两类:
- 检查型异常(Checked Exception)
- 这些异常是在编译时被检查的,程序必须显式处理这些异常(通过
try-catch
或throws
声明)。- 例如:
IOException
、SQLException
等。- 非检查型异常(Unchecked Exception)
- 这些异常是在运行时发生的,编译器不会强制要求处理这些异常(但可以选择处理)。
RuntimeException
是所有非检查型异常的父类。- 例如:
NullPointerException
、ArrayIndexOutOfBoundsException
、ClassCastException
等。
ClassCastException
的具体含义
ClassCastException
是 Java 中的 运行时异常(Unchecked Exception),通常在你尝试将一个对象强制转换为不兼容的类型时抛出。它表示你在运行时尝试进行不合法的类型转换。举个例子:
Object obj = new String("Hello"); Integer num = (Integer) obj; // 这里会抛出 ClassCastException
在这个例子中,
obj
被声明为Object
类型,但它实际上指向的是一个String
对象。你尝试将其强制转换为Integer
类型时,Java 会抛出ClassCastException
,因为String
和Integer
是不兼容的类型。为什么是运行时异常?
ClassCastException
是 运行时异常,意味着编译器不会检查类型转换是否安全,直到程序运行时才会发现转换的类型不兼容。如果你在编译时尝试转换为不兼容的类型,编译器不会报错,而是等到程序运行时抛出这个异常。总结:
ClassCastException
是运行时异常,继承自RuntimeException
。- 它表示你在运行时尝试进行不合法的类型转换,通常是强制转换时出现的错误。
instanceof
无法直接判断两个类型之间是否可以相互转换,只能用来判断一个对象能否安全地转换为某个类型
class Person {public void run() {System.out.println("Person is running");}
}class Student extends Person {public void study() {System.out.println("Student is studying");}
}class Teacher extends Person {public void teach() {System.out.println("Teacher is teaching");}
}public class Test {public static void main(String[] args) {Person p1 = new Student(); // p1 是一个 Student 类型的对象Person p2 = new Teacher(); // p2 是一个 Teacher 类型的对象Person p3 = new Person(); // p3 是一个 Person 类型的对象// 判断 p1 是否可以转换为 Studentif (p1 instanceof Student) {Student s = (Student) p1; // 安全转换,因为 p1 实际上是 Student 类型s.study(); // 调用子类特有方法}// 判断 p2 是否可以转换为 Studentif (p2 instanceof Student) {Student s = (Student) p2; // 这个不会执行,因为 p2 实际上是 Teacher 类型s.study();}// 判断 p3 是否可以转换为 Studentif (p3 instanceof Student) {Student s = (Student) p3; // 这个不会执行,因为 p3 实际上是 Person 类型s.study();}}
}
转化部分 CAL总结:
1.小转大 子类转换成父类 随便转 run 是父类Father 中独有的方法
Son s1 = new Son(); Father f1 =s1; f1.run(); //或者直接 如第四行 ((Father)s1).run();
2.大转小就比较麻烦了 ,主要还是看对象运行时的类型
2.1 如果编译时父类 运行时是子类,那么可以强转 (多态) 但是还是可能出现问题
ClassCastException
2.2 如果编译时父类 运行时还是父类 这就不可以强转
多态的关键在于:编译时的类型决定了引用变量的可用方法(即只能访问父类或实现接口中声明的方法),而运行时的类型决定了实际执行哪个方法。
父类 Person
package com.oop.demo07;public class Person {public Person() {}public void run(){System.out.println("Running Person");}//public void study(){//}//重写之后启动类中的第11行可以使用了,不报错 也就是说父类对象可以是用子类重写的方法}
子类 Student
package com.oop.demo07;public class Student extends Person{public Student() {}public void study(){System.out.println("Study");} }
启动类Application
package com.oop.demo07;public class Application {public static void main(String[] args) {Student s1 = new Student();s1.run();s1.study();//((Person)(s1)).study();//已经转成父类,可能就有部分子类方法丢失了((Person)(s1)).run();Person p1 =s1; //子类转换成父类 直接转p1.run(); //第10行和第11行效果同第9行 只是新建了一个父类Persond实例p1System.out.println("========================");Person s2 = new Student();s2.run();//s2.study();//父类不能用子类独有的方法,需要重写在父类中声明一样的方法//如果不重写方法就通过强制转换,将父类对象转换成子类对象,调用子类中的方法((Student)s2).study();//将编译时父类Person(声明时)的S2 转换成子类Stendent类((Student)s2).run();//转换成子类 同样可以继承父类的run()方法System.out.println("========================");Person s3 = new Person();s3.run();//子类可直接转换成父类 小转大 但是大转小 父类转子类//需要多态声明 主要看运行是对象的类型 像 26行27行// 这样直接定义父类 (声明时(编译)是父类,运行时(实例类型)也是父类)将父类强转成子类是不可以的//((Student)s3).study(); //报错//((Student)s3).run();//报错} }
输出结果:
Running Person Study Running Person Running Person ======================== Running Person Study Running Person ======================== Running Person