本文主要是介绍第十四章 类型信息RTTI Class instanceof isInstance,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1.RTTI(运行时识别一个对象的类型)
- 动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
- 运行时类型信息使你能够在程序运行时发现和使用(比如对象的具体类)类型信息。
- RTTI主要有两种形式
- 传统的RTTI一种是在编译时知道了所有的类型信息,这属于静态绑定。
- 另一个种是“反射机制”,允许我们在运行时发现和使用类(对象的具体类)的信息。这属于动态绑定(多态、运行时绑定)。
- 面向对象的基本目的是:让代码只操纵对基类的引用,这样即使(从shape)派生出一个新类来也不会对原来的代码产生影响。
注意:这个例子中的Shape接口中动态绑定了draw()方法,目的是让客户端程序员使用泛化的Shape引用来调用draw()方法。
draw()方法在所有派生类都会被覆盖,并且由于它是动态绑定的,所以即使通过泛化的Shape引用来调用,也能产生正确的行为。这就是多态。 - 如果某个对象出现在字符串表达式中,其toString()方法会被自动调用。
/*** 一个泛化的Shape 由个别到普通的过程*/ abstract class Shape{void draw(){System.out.println(this + ".draw()");}abstract public String toString(); } /*** 个别特殊的类型*/ class Circle extends Shape{//toString()方法public String toString() {return "Circle";} } class Square extends Shape {public String toString() {return "Square";} } class Triangle extends Shape{public String toString() {return "Triangle";} }public class Shapes {public static void main(String[] args) {//在List中放入一个泛化的Shape类型,List<Shape> shapeList = Arrays.asList(new Circle(),new Square(), new Triangle());for(Shape shape : shapeList)/*** 因为泛化的Shape动态绑定了draw()方法,其子类型也会覆盖此方法,也可以产生正确的行为。* 多态,运行时识别对象的类型,调用相应方法*/shape.draw();} } /** Circle.draw() Square.draw() Triangle.draw()*/
- List中放入对象会把Shape对象的具体类型丢失。也就是说进入的都是Shape
- 当从List中取出元素时:这种容器——实际上会将所有的事物都当做Object持有——然后自动将结果转型为Shape.
在Java中,所有的类型转换都是在运行时进行正确检查的。RTTI:在运行时,识别一个对象的类型(然后在将泛化的类型引用转化为具体的类型)。 - 这个例子中:首先 Objiet转为Shape,因为只知道List<Shape>保存的都是Shape,在编译时由容器和java泛型系统来强制确保这一点;而在运行时,有类型转换操作来确保这一点。
- 接下来就是多态机制,Shape对象执行什么样的代码,是由引用所指向的具体对象所决定的,可以识别是因为多态。
-
通常,你希望大部分代码尽可能少的了解对象的具体类型,而只与一个泛化类型表示。这样的代码更容易写,读,切更便于维护;设计也更容易实现、理解和改变。
-
所以“多态”是面向对象的基本目标。
2.Class 对象
- 当我们运行一个程序时,这个程序的所有类并不是一次都全部向虚拟机加载完成的,而是当程序运行到那一块使用到那个类(比如声明了一个对象或调用了一个静态的东西)时才会将这个类加载到jvm虚拟机当中去,在这个时候,对应的类也会生成一个Class的对象,它包含了这个类的一切信息(比你想象的要多)。
- 类型信息在运行时有Class对象来表示,它包含了与类相关的有关信息。事实上,Class对象就是用来创建类的所有“常规”对象的。Java使用Class对象来执行其RTTI,即使正在执行的是类似转型的操作。
- 类加载器子系统实际上可以包含一条类加载器链,但只有一个原生类加载器;它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括Java API类,它们通常从本地磁盘加载。通常不需要添加额外的类加载器,但如果有特殊需求(如从网络下载的类),那么可以挂接额外的类加载器。
- 类加载的过程:所有类都是在对其第一次使用时(static初始化是在类加载的时候进行的),动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个也证明构造器也是类的静态方法,即使构造器之前没有使用static关键字。因此,使用new操作符创建类的新对象也会被当做对类的静态成员的引用。
因此:Java程序在运行之前并非被完全加载,而是在其各个部分必需时才会被加载的。 - 类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时(实际上是字节码文件加载入内存),他们会接受验证,并且不包含不良代码。
特别注意:一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。
class Candy{static {System.out.println("Loading Candy");} } class Gum{static {System.out.println("Loading Gum");} } class Cookie{static {System.out.println("Loading Cookie");} } public class SweetShop {public static void main(String[] args) {System.out.println("inside main");new Candy();//如果没有.class文件,会自动生成一个同名的.class文件。System.out.println("After creating Candy");try{/*** Class对象和其他对象一样,我们可以获取并操作它的引用(类加载器)* Class的静态成员,得到这个名字的类,返回一个Class对象的引用* * Class.forName("Gum")方法如果找不到要加载的类(.class文件)就会抛出异常*/Class.forName("Gum");}catch (ClassNotFoundException e) {//找不到类Gum,因为它还没有被第一次加载过(也就是说这个类的静态成员一次也没有引用过)System.out.println("Couldn't find Gum");}} }
- 无论何时,只要在运行时使用类型信息,就必须首先获得对恰当Class对象的引用。使用Class.forName()就可以实现。如果你有一个类的引用,可以通过getClass()获得该对象实际类型的Class引用。
interface HasBatteries{} interface Waterproof{} interface Shoots{}class Toy{Toy() {}Toy(int i){} } class FancyToy extends Toy implements HasBatteries , Waterproof, Shoots{FancyToy() {super(1);} } public class ToyTest {static void printInfo(Class cc){System.out.println("类名: " + cc.getName() + " 是否接口? [" + cc.isInterface() + "]");System.out.println("简单类名: " + cc.getSimpleName());System.out.println("规范名: " + cc.getCanonicalName());System.out.println();}public static void main(String[] args) {Class c = null;try {c = Class.forName("com.yue.rtti.FancyToy");//获取一个Class对象} catch (ClassNotFoundException e) {System.out.println("Can't find FancyToy");System.exit(1);}printInfo(c);//第一次输出信息//getInterfaces会返回此Class对象所表示的类实现的所有接口 是数组形式 Class[]System.out.println("以下是此类的全部接口");for(Class face : c.getInterfaces())printInfo(face);//逐条打印System.out.println("以上是此类的全部的接口");Class up = c.getSuperclass();//获取到此类的超类 即父类 基类Object obj = null;try {obj = up.newInstance();//使用这个类的Class对象创建一个具体的对象,并赋予一个obj引用} catch (InstantiationException e) {System.out.println("Cannot instantite");System.exit(1);} catch (IllegalAccessException e) {System.out.println("Cannot access");System.exit(1);}printInfo(obj.getClass());//打印} } /** 类名: com.yue.rtti.FancyToy 是否接口? [false] 简单类名: FancyToy 规范名: com.yue.rtti.FancyToy以下是此类的全部接口 类名: com.yue.rtti.HasBatteries 是否接口? [true] 简单类名: HasBatteries 规范名: com.yue.rtti.HasBatteries类名: com.yue.rtti.Waterproof 是否接口? [true] 简单类名: Waterproof 规范名: com.yue.rtti.Waterproof类名: com.yue.rtti.Shoots 是否接口? [true] 简单类名: Shoots 规范名: com.yue.rtti.Shoots以上是此类的全部的接口 类名: com.yue.rtti.Toy 是否接口? [false] 简单类名: Toy 规范名: com.yue.rtti.Toy */
2.1.类字面常量.class
- FancyToy.class这样也可以获得一个class对象。这样做简单安全,因为它在编译时就会受到检查。
并且根除了forName()方法的调用,所以更高效。 - 类字面常量不仅可以应用于普通类,也可应用于接口、数组以及基本数据类型。
- 对于基本类型的包装器类,还有一个TYPE的标准字段。
- 当使用.class来创建对Class对象的引用时,不会自动初始化该Class对象。为了使用类而做的准备工作实际包含三个步骤,而.class不会走带三个步骤“初始化”
加载:由类加载器执行。查找字节码文件中的字节码(.class文件中)并从这些字节码中创建一个Class对象。链接:在链接阶段将验证类中的字节码,为静态域分配存储空间(内存),如果必需,将解析这个类创建的对其他类的所有引用。初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。【初始化存储空间】
初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非常数静态域进行首次引用时才执行:
class Initable{static final int saticFinal = 47;static final int staticFinal2 = //注意这种情况也会被初始化的,虽然是final,但具有不确定性,非常数静态域。ClassInitialization.rand.nextInt(1000);static {System.out.println("Initializing Initable");} } class Initable2{static int staticNonFinal = 147;//非常数静态域static{System.out.println("Initializing Initable2");} } class Initable3{static int staticNonFinal = 74;static {System.out.println("Initializing Initable3");} } //有继承超类的情况,情况不特殊也不会被初始化 class Initable4 extends Initable3{static final int saticFinal = 123;static {System.out.println("Initializing Initable4");} } public class ClassInitialization {public static Random rand = new Random(47);public static void main(String[] args) throws ClassNotFoundException {//得到一个Class对象 这样不会先对其初始化,而是把初始化延迟到了第一次使用其静态域时。Class initable = Initable.class;System.out.println("After creating Initable ref");System.out.println(Initable.saticFinal);//仅仅是编译器常量还不足以保证不初始化System.out.println(Initable.staticFinal2);//直接使用一个类的静态域(他不是final的) 其他静态域也会被初始化System.out.println(Initable2.staticNonFinal);//另一种方式获取Class对象 这样会首先对其初始化Class initable3 = Class.forName("com.yue.rtti.Initable3");System.out.println("After creating Initable3 ref");System.out.println(Initable3.staticNonFinal);System.out.println("继承超类的情况:"+Initable4.saticFinal);} } /** After creating Initable ref 47 Initializing Initable 258 Initializing Initable2 147 Initializing Initable3 After creating Initable3 ref 74 继承超类的情况:123*/
- 初始化有效地实现了尽可能的“惰性”。.class语法获得对
类的引用不会引发初始化,为了产生Class引用,Class.forName()立即进入初始化。
- 如果一个static final值是“编译器常量”(有可能不是 如Initable.staticFinal2),这个值不需对Initable类进行初始化就可以被获取。
- 如果一个static域不是final的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个类分配存储空间)和初始化(初始化存储空间)。
2.2.泛化的Class引用(有特殊到一般的过程)
- Class引用总是指向某个Class对象(包含一个类的所有信息,通过类加载器加载.class文件中的字节码而创建)的。
可以使用这个引用来创建类的实例(对象),并包含可作用域这些实例的所有方法代码。还包括该类的静态成员,因
此,Class引用表示的就是它所指向的对象(Class类的一个对象)的确切类型(具体类型)。 - 但是使用泛型语法进行限定还可以使它的类型更具体。如下:
public class GenericClassReferences {public static void main(String[] args) {Class intClass = int.class;//普通的Class引用Class<Integer> genericIntClass = int.class;//使用泛型限定的Class引用,只能指向指定的引用的Class对象 // genericIntClass = double.class;//这里会报错,因为使用了泛型进行限定//普通的类引用不会产生警告信息,可以指向任何其他Class对象intClass = double.class;//使用通配符来指定此泛型的限定范围 Class<? extends Number> genericNumberClass = int.class;} }
- 通过使用泛型语法,可以让编译器强制执行额外的类型检查。使用通配符可以稍微放松一些这种限制
通配符?表示“任何事物”。
Class<?>优于平凡的Class,即便他们是等价的,Class<?>表示使用了一个非具体的类引用,就选择了非具体的版本。Class<?> intClass = int.class;
- 限定为某种类型或该类型的任何子类型,使用?与extends关键字相结合,创建一个范围。
//使用通配符来指定此泛型的限定范围 Class<? extends Number> genericNumberClass = int.class;
- 向Class引用添加泛型语法仅仅是为了提供编译期类型检查,使用普通的Class引用,如果犯了错误,直到运行时才会发现,显得很不方便。
当你将泛型语法用于Class对象时,newInstance()将返回改对象的确切类型,而不仅仅是基本的object(普通的Class引用)class CountedInteger {private static long counter;private final long id = counter++;public String toString() {return Long.toString(id);} }public class FilledList<T> {public static void main(String[] args) {// CountedInteger类必须有一个无参构造器Class<CountedInteger> type = CountedInteger.class;try {//这个类必须假设与它一同工作的任何类型都有一个某人的构造器(无参构造器),否则抛出异常CountedInteger obj = type.newInstance();} catch (Exception e) {throw new RuntimeException(e);}} }
//class Toy{ // Toy() {} // Toy(int i){} //} //class FancyToy extends Toy implements //HasBatteries , Waterproof, Shoots{ // FancyToy() {super(1);} //} public class GenericToyTest {public static void main(String[] args) throws Exception {Class<FancyToy> ftClass = FancyToy.class;FancyToy fancyToy = ftClass.newInstance(); // Class<Toy> up2 = ftClass.getSuperclass();//这样编译器会报错//?代表某个类 他是FancyToy的超类,而不会接受Class<Toy> up2这样的声明Class<? super FancyToy> up = ftClass.getSuperclass();Object obj = up.newInstance();} }
2.3新的转型语法
class Building {} class House extends Building {} public class ClassCasts {public static void main(String[] args) {Building b = new House();Class<House> houseType = House.class;//先获得目标转型的Class引用House h = houseType.cast(b);//使用cast进行转型的语法h = (House) b;//直接转型的语法} }
- cast()方法转型比普通的转型语法多了很多额外的工作,它对于普通转型语法不能转型的情况非常有用
- Class.asSubclass;允许将一个类对象(Class)转型为更加具体的类型。
3.类型转型前先做检查
- RTTI形式
- 传统的类型转换,如“(Shape)”,由RTTI确保类型转换的正确性,如果执行错误的类型转换,就会抛出一个ClassCastException异常
- 类型安全的向下转型(强制类型转换),类层次结构图,由Shape转化成Circle是向下的,所以是向下转型(显式的,因为编译器不知道你要转换成什么类型)。
- instanceof:检查对象是否从属于某个类
if (h instanceof Building) {h = (House) b;}
3.1动态的instanceof[Class.isInstance()]
- Class.isInstance()方法提供了一种动态地测试对象的途径。检查某个对象是否从属于某个类。
class Building {} class House extends Building {} public class ClassCasts {public static void main(String[] args) {Building b = new House();//这里引用的实际对象是一个House,它在运行时会识别这个类型RTTIClass<House> houseType = House.class;Class<Building> buildType = Building.class;House h = houseType.cast(b);//使用cast进行转型的语法h = (House) b;//直接转型的语法if (h instanceof Building) {h = (House) b;System.out.println("转型");}if (buildType.isInstance(b)) {//可以改写成这样System.out.println(buildType.isInstance(b));}} }
3.2递归计数法
+++这是一种方法,根据不同情况会有不同的实现。
4.注册工厂
这个东西会在设计模式中说。
5.Instanceof与Class的等价性
- 在查询类型信息的时候,以instanceof的形式(instanceof的形式或isInstance()的形式)与直接比较Class对象有一个很重要的差别。
class Base{} class Derived extends Base{} public class FamilyVsExactType {static void test(Object x){System.out.println("测试x的类型 " + x.getClass());System.out.println("x instanceof Base: " + (x instanceof Base));System.out.println("x instanceof Derived: " + (x instanceof Derived));System.out.println("Base.isInstance(x): " + Base.class.isInstance(x));System.out.println("Derived.isInstance(x): " + Derived.class.isInstance(x));System.out.println("下面的输出与上面对比");System.out.println("x.getClass() == Base.class: " + (x.getClass() == Base.class));System.out.println("x.getClass() == Derived.class: " +(x.getClass() == Derived.class));System.out.println("x.getClass().equals(Base.class): " + (x.getClass().equals(Base.class)));System.out.println("x.getClass().equals(Derived.class): " + (x.getClass().equals(Derived.class)));System.out.println("++++++++++++++++++++++++++++++++++++++++++++++");}public static void main(String[] args) {test(new Base());test(new Derived());} } /** 测试x的类型 class com.yue.rtti.Base x instanceof Base: true x instanceof Derived: false Base.isInstance(x): true Derived.isInstance(x): false 下面的输出与上面对比 x.getClass() == Base.class: true x.getClass() == Derived.class: false x.getClass().equals(Base.class): true x.getClass().equals(Derived.class): false ++++++++++++++++++++++++++++++++++++++++++++++ 测试x的类型 class com.yue.rtti.Derived x instanceof Base: true x instanceof Derived: true Base.isInstance(x): true Derived.isInstance(x): true 下面的输出与上面对比 x.getClass() == Base.class: false x.getClass() == Derived.class: true x.getClass().equals(Base.class): false x.getClass().equals(Derived.class): true ++++++++++++++++++++++++++++++++++++++++++++++*/
这篇关于第十四章 类型信息RTTI Class instanceof isInstance的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!