第十四章 类型信息RTTI Class instanceof isInstance

2023-12-24 07:08

本文主要是介绍第十四章 类型信息RTTI Class instanceof isInstance,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.RTTI(运行时识别一个对象的类型)

  • 动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法
  • 运行时类型信息使你能够在程序运行时发现和使用(比如对象的具体类)类型信息
  • RTTI主要有两种形式
    1. 传统的RTTI一种是在编译时知道了所有的类型信息,这属于静态绑定。
    2. 另一个种是“反射机制”,允许我们在运行时发现和使用类(对象的具体类)的信息。这属于动态绑定(多态、运行时绑定)。
  • 面向对象的基本目的是:让代码只操纵对基类的引用,这样即使(从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()*/
    
    1. List中放入对象会把Shape对象的具体类型丢失。也就是说进入的都是Shape
    2. 当从List中取出元素时:这种容器——实际上会将所有的事物都当做Object持有——然后自动将结果转型为Shape.
      在Java中,所有的类型转换都是在运行时进行正确检查的。RTTI:在运行时,识别一个对象的类型(然后在将泛化的类型引用转化为具体的类型)。
    3. 这个例子中:首先 Objiet转为Shape,因为只知道List<Shape>保存的都是Shape,在编译时由容器和java泛型系统来强制确保这一点;而在运行时,有类型转换操作来确保这一点。
    4. 接下来就是多态机制,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不会走带三个步骤“初始化”
    1. 加载:由类加载器执行。查找字节码文件中的字节码(.class文件中)并从这些字节码中创建一个Class对象。
    2. 链接:在链接阶段将验证类中的字节码,为静态域分配存储空间(内存),如果必需,将解析这个类创建的对其他类的所有引用
    3. 初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。【初始化存储空间】
      初始化被延迟到了对静态方法构造器隐式的是静态的)或者非常数静态域进行首次引用时才执行:
      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*/ 
      
    4. 初始化有效地实现了尽可能的“惰性”。.class语法获得对的引用不会引发初始化,为了产生Class引用,Class.forName()立即进入初始化。
    5. 如果一个static final值是“编译器常量”(有可能不是 如Initable.staticFinal2),这个值不需对Initable类进行初始化就可以被获取。
    6. 如果一个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<?> intClass = int.class;
    Class<?>优于平凡的Class,即便他们是等价的,Class<?>表示使用了一个非具体的类引用,就选择了非具体的版本。
  • 限定为某种类型或该类型的任何子类型,使用?与extends关键字相结合,创建一个范围。
    //使用通配符来指定此泛型的限定范围 Class<? extends Number> genericNumberClass = int.class;
  • 向Class引用添加泛型语法仅仅是为了提供编译期类型检查,使用普通的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对象时,newInstance()将返回改对象的确切类型,而不仅仅是基本的object(普通的Class引用)
  • //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形式
    1. 传统的类型转换,如“(Shape)”,由RTTI确保类型转换的正确性,如果执行错误的类型转换,就会抛出一个ClassCastException异常
    2. 类型安全的向下转型(强制类型转换),类层次结构图,由Shape转化成Circle是向下的,所以是向下转型(显式的,因为编译器不知道你要转换成什么类型)。
    3. 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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/530947

相关文章

类型信息:反射-Class

在说反射前提一个概念:RTTI(在运行时,识别一个对象的类型) public class Shapes {public static void main(String[] args) {List<Shape> shapes = Arrays.asList(new Circle(), new Square(), new Triangle());for (Shape shape : shapes

react笔记 8-17 属性绑定 class绑定 引入图片 循环遍历

1、绑定属性 constructor(){super()this.state={name:"张三",title:'我是一个title'}}render() {return (<div><div>aaaaaaa{this.state.name}<div title={this.state.title}>我是一个title</div></div></div>)} 绑定属性直接使用花括号{}   注

泛型参Class、Class、Class的对比区别

1.原文链接 泛型参Class、Class、Class的对比区别 https://blog.csdn.net/jitianxia68/article/details/73610606 <? extends T>和<? super T> https://www.cnblogs.com/drizzlewithwind/p/6100164.html   2.具体内容: 泛型参数Class、

【JavaScript】如何模拟一个instanceof

首先看一下instanceof的用法。 a instanceof B 判断的是,a是否为B的实例,即a的原型链上是否存在B的构造函数。 function Person(name) {this.name = name}const p = new Person('mike')p instanceof Person // true 这里的 p 是 Person 构造出来的实例对象。 p._

c++通用模板类(template class)定义实现详细介绍

有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:class Compare_int { public : Compare(int a,int b) { x=a; y=b; } int max( ) { return (x>y)?x:y; } int min( ) { return (x&... 有时,有两个或多个类,其功能是相同的,仅仅是数

Python方法:__init__,__new__,__class__的使用详解

转自:https://blog.csdn.net/qq_26442553/article/details/82464682 因为python中所有类默认继承object类。而object类提供了了很多原始的内建属性和方法,所以用户自定义的类在Python中也会继承这些内建属性。可以使用dir()函数可以查看,虽然python提供了很多内建属性但实际开发中常用的不多。而很多系统提供的内建属性实际

SpringBoot启动报错Failed to determine a suitable driver class

两种解决办法 1.在Application类上加 ` @EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class}) package com.example.demo3;import org.springframework.boot.SpringApplication;import org.springframewo

easyswoole not controller class match

not controller class match composer.json 注册 App 这个名称空间了吗?执行过 composer dump-autoload 了吗?存在 Index 控制器,但是文件大小写、路径都对了吗? task socket listen fail 注意,在部分环境下,例如 win10 的 docker 环境中,不可把虚拟机共享目录作为 EasySwoole 的 T

JavaBug系列- Failed to load driver class com.mysql.cj.jdbc.Driver in either of HikariConfig class load

JavaBug系列之Mysql驱动问题 Java医生一、关于错误信息二、如何解决问题 Java医生 本系列记录常见Bug,以及诊断过程和原因 Java/一对一零基础辅导/企业项目一对一辅导/日常Bug解决/代码讲解/毕业设计等 V:study_51ctofx 一、关于错误信息 APPLICATION FAILED TO START Description: Fai

【上】java获取requestMapping上所有注解功能实现及取匿名注释类的值及 class com.sun.proxy.$Proxy140 转换出错

java获取requestMapping上所有注解功能实现及取匿名注释类的值及 class com.sun.proxy.$Proxy140 转换出错 1,多人相当然以为类似对象一样直接强转下就可以,结果迎来的是class com.sun.proxy.$Proxy140转换出错【想法很勇敢,现实很骨感】 //Class<A> operatorMappingAnnotationType// 错误