java总结(之四)--类型信息

运行时类型信息使得我们可以在程序运行时发现和使用类型信息。
java中主要有两种方式可以在运行时识别对象和类的信息。

  1. 传统的RTTI,它假定我们在编译时已经知道了所有的类型。编译器在编译时打开和检查.class文件(也就是我们可以用”普通“方式调用对象的所有方法)。

  2. 反射机制,它允许我们在运行时发现和使用类的信息。.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。

    下面分别介绍以上两种方式

RTTI(Runtime-Type Information )

RTTI是在运行时,识别一个对象的类型,但是在编译器已经确保类型的正确性。在编译时,编译器必须知道所有要通过RTTI来处理的类,才能利用这些信息处理事情。

  1. 类型信息在运行时是如何表示的?
    主要由Class对象的特殊对象完成的,它包含了与类有关的信息。事实上,Class对象就是用来创建类的所有的”常用“对象的。java使用Class对象来执行RTTI,即使你正执行的是类似转换这样的操作。每个类都有一个Class对象,每当编写并且编译了一个新类,就会产生一个Class对象。为了生成这个类的对象,运行这个程序的JVM将使用被称为”类加载器“的子系统。

  2. 什么是类加载器?
    类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括Java API类,他们通常是从本地磁盘加载的,在这条链中不需要添加额外的类加载器,但是如果有需要的可以有一种方式可以挂接额外的类加载器。

  3. 类加载器的作用?
    所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这也证明类构造器也是类的静态成员,当使用new关键字创建对象时也会被当做静态成员的引用。
    类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包括不良的java代码。一旦某各类的Class对象被载入内存,他就会被用来创建这个类的所有对象。

  4. 使用Class.forName()来进行类加载
    Class.forName()是取得Class对象引用的一种方法,返回的是一个Class对象的引用。它的作用是:如果一个类还没有被加载就加载它。

class Candy{
    static {System.out.println("loading candy");}
} 
class Gum{
    public Gum(){}
    static {System.out.println("loading gum");}
}

public class LoadTest {
    public static void main(String[] args) {
        System.out.println("inside main");
        new Candy();
        System.out.println("after loading candy");
        try {
            Class.forName("container.Gum");//注意在forName要使用权限定名
        } catch (ClassNotFoundException e) {
            System.out.println("can not find gum");
        }
        System.out.println("after class.forname()");
    }
}
  1. getClass()方式获取Class对象
    如果我们已经拥有了一个感兴趣的类型的对象就可以通过调用getClass()方法获取Class对象。
    注意,newInstance()实现”虚拟构造器“的一种途径,虚拟构造器允许我们声明:”不知道确切类型,但无论如何要正确的而创建自己“
interface HasBetteris{}
interface Waterproof{}
interface Shoots{}

class Toys{
    public Toys() {}
    Toys(int i){}
}
class FancyToy extends Toys 
implements HasBetteris,Waterproof,Shoots{
    FancyToy(){super(1);}
}

public class GetClassTest {
    public static void main(String[] args) {
        Class c = null;
        try {
            c =Class.forName("container.FancyToy");
        } catch (ClassNotFoundException e) {
            System.out.println("can not find FancyToy");
        }
        System.out.println(c);

        for (Class face : c.getInterfaces()) {
            System.out.println(face);
        }
        Class up = c.getSuperclass();
        Object object = null;
        try {
            object = up.newInstance();//一定要有默认构造器
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(object.getClass());
    }
}
output:
class container.FancyToy
interface container.HasBetteris
interface container.Waterproof
interface container.Shoots
class container.Toys

6.java的另一种生成Class对象的引用:类字面常量。即FancyToy.class
这样做不仅简单而且更安全,他在编译时就会受到检查因此不需要放到try/catch中。它不仅可以用在普通类,也可以用在接口、数组以及基本数据类型。另外对于基本数据的包装器类还有一个标准字段TYPE,该字段是一个引用指向对应用的基本数据类型的Class对象。
注意,当使用.class来创建对Class的引用时,不会自动的初始化该Class对象。初始化被延迟到了对静态方法或非静态域进行首次引用时才执行。为了使用类而做的准备工作实际包括三个步骤:

  1. 加载,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找)从这些字节码中创建一个Class对象。
  2. 链接,在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用。
  3. 初始化,如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。

7.类型转换前先做检查

主要由(1)RTTI确保类型转换的正确性,如果执行了一个错误的类型转换就会抛出异常;(2)代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。(3)还有就是关键字instanceof关键字,它返回一个布尔值告诉我们对象是不是某个特定类型的实例。instanceof有限制:只可以与其命名类型进行比较而不能与Class对象比较

if (x instanceof Dog)
((Dog)x).bark()

  1. instanceof与Class的等价性
class Base{}
class Derived extends Base{}

public class Test {
    static void test(Object obj){
        System.out.println( obj.getClass());
        System.out.println((obj instanceof Base));
        System.out.println(obj instanceof Derived);
        System.out.println(Base.class.isInstance(obj));
        System.out.println(Derived.class.isInstance(obj));
        System.out.println(obj.getClass() == Base.class);
        System.out.println(obj.getClass() == Derived.class);
        System.out.println(obj.getClass().equals(Base.class));
        System.out.println(obj.getClass().equals(Derived.class));
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        test(new Base());
        test(new Derived());
    }
}
output:
class container.Base
true
false
true
false
true
false
true
false
class container.Derived
true
true
true

从以上结果可以看出instanceof和isInstance()生成的结果完全一样,equals和==也一样,但是instanceof保持了类型的概念,它指的是”你是这个类吗,或者你是这个类的派生类吗“ 而如果用==比较实际的Class对象,就没有考虑继承,它只是是或者不是这个类。

  1. 反射:运行时的类信息
    反射提供了一种机制—-用来检查可用的方法,并返回方法名。当我们想在运行时获取跨网络的远程平台类的信息的能力,反射机制提供了这种能力。
    Class类和java.lang.reflect类库一起对反射的概念进行了支持,该类库包括Field、Method、以及Constructor类,这些类型的对象时由JVM在运行时创建的,用以表示未知类里对应的成员。这样就可以用Constructor创建对象,用get、set方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法等,这样匿名对象的类信息就能在运行时完全被确定下来,而在编译时不需要知道任何事情。
    通过反射,一般可以访问private等权限的的方法或者域
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值