Java编程思想(十四) —— 类型信息RTTI(1)

译者翻译的时候有些奇怪,Runtime type information (RTTI) allows you to discover and use type information while a program is running。

运行时类型信息(原来的翻译没有括号这里面的内容,Runtime type information,简称RTTI,个人觉得这样注释比较好)可以让你在程序运行的时候发现和使用类型信息。后面直接出现RTTI让人疑惑。

1)为什么需要RTTI

之前的多态的例子中:

public class EveryTV {
    public static void tvshow(TV tv){
        tv.show();
    }

    public static void main(String[] args) {
        tvshow(new LeTV());
        tvshow(new MiTV());
        tvshow(new SanTV());
    }
}

将各种TV的子类转型为TV,这是RTTI的一种使用形式,运行时类型信息,所有类型的转换都是在运行时进行正确的检查。即在运行时,识别一个对象的类型。

2)Class对象

Java使用Class对象来执行其RTTI,类是程序的一部分,每个类都有一个Class对象,其实每编写和编译一个新类,就会产生一个Class对象,其实这个对象时被保存在同名的.class文件中的。生成这个类对象,其实是JVM(Java虚拟机)使用了“类加载器”的子系统。

所有的类都是在第一次使用时动态加载到JVM,当程序创建第一个对类的静态成员的引用时就会加载这个类,这样说的话,构造器是类的静态方法,虽然没有static修饰,因为new的新对象就是类的静态成员的引用。

动态加载使能行为(感觉好怪),英文原话:Dynamic loading enables behavior that is difficult or impossible toduplicate in a statically loaded language like C++.

动态加载允许的行为在C++这样的静态加载语言中是很难或者根本不可能复制的(这样翻译好一些)。

类加载器首先检查这个类的Class对象是否已经加载。未加载则根据类名查找.class文件。Class对象被载入内存,就被用来创建这个类的所有对象

package son;

class First{
    static{
        System.out.println("first load");
    }
}

class Second{
    static{
        System.out.println("second load");
    }
}
public class TestClass {
    public static void main(String[] args) {
        System.out.println("main");
        new First();
        System.out.println("first after");
        try {
            Class.forName("son.Second");
        } catch (ClassNotFoundException e) {
            System.out.println("not found");
        }
        System.out.println("second after");
        System.out.println("end");
    }
}
result:
main
first load
first after
second load
second after
end

这次特地加上包名,因为越发觉得奇怪,没有包名的时候class是找不到的。提前用了Second.class.getName(),类名为son.Second。果真一试可以了。

根据static方法里面的语句可以知道类的加载顺序,Class.forName("Second")

所有Class对象属于Class类。

static Class<?> forName(String className)

Returns the Class object associated with the class or interface with the given string name.

真阳就拿到提供名字的Class对象。由于包存在的缘故,没有写包名的时候找不到Second对象,所以是not found。

Class.forName()能获得对所需的Class对象的引用,而不需要我们去持有该类型对象。如果已经有了对象,可以通过getClass()获取Class引用。

书上例子有一个问题(main语句重复)。

package son;

public interface Fire {}
public interface Water {}

class Gun{
    Gun(){
        System.out.println("init");
    }
}

public class DeathGun extends Gun implements Water,Fire{
    static void printInfo(Class c){
        System.out.println("Class name: "+ c.getName()+
                                         "Interface? "+c.isInterface()+
                                         "\nsimplename "+c.getSimpleName()+
                                         "  canonicalname "+c.getCanonicalName());
    }
    public static void main(String[] args) {
        Class c = null;
        try {
            c = Class.forName("son.DeathGun");
        } catch (ClassNotFoundException e) {
            System.out.println("not found");
            System.exit(1);
        }
        printInfo(c);
        for(Class cc : c.getInterfaces()){
            printInfo(cc);
        }

        Class father = c.getSuperclass();
        Object o = null;
        try {
             o = father.newInstance();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        printInfo(o.getClass());
    }
}

forName正如前面所提到要有包名,整个称为canonicalname,规范类名,译者翻译为全限定名。getSimpleName()拿到的为类名,isIterface()是否为接口。getInterfaces返回Class对象。

先停一停,现在有人有点乱了,重理思路,Class,class,类,对象,会不会搞得乱七八糟了。class是关键字,定义一个类的,class就是类,对象则是一个类的实例,而Class是类中的一种,为什么要Class呢,因为他可以干一些厉害的工作——如反射。

(以下内容会有重复)前面提到的RTTI,其实是由叫做Class对象的特殊对象完成,因为Class是一个类,但又区别于整个的class,Class包含类的相关信息。class A,如果我们编写了并且编译了A,那么就会产生Class对象,存放在.class文件中。创建静态成员引用的时候会加载类,静态成员是类和多个对象拥有的属性或者方法,即可以用类名+静态成员名的方式调用,应为new对象的时候会加载类,这就说明了构造器也是静态方法。Class.forName会返回一个Class对象的引用,如果类未加载就进行加载。

继续,newInstance确实像网友所说的类似工厂模式,特地在构造器写了一个输出,newInstance会打印出来,father只是Class的引用,编译期不具备进一步的类型信息,new了之后的Object引用其实指的就是Gun对象。

3)类字面常量

呵呵,之前弄晕我了,你会发现class,getClass(),.class,Class这些看起来好像,看看之前重理的思路,会好理解。

这是生成对Class对象引用的另外一种方法:

A.class;

简单安全,编译器检查,可以应用于接口,数组和基本类型,基本类型的包装类还有一个标准字段TYPE,也是一个引用,指向对应的Class对象。

int.class 等价于 Integer.TYPE。

但是这个并不会初始化Class对象。所以不会像forName那样会打印类中的static方法。

使用类前的准备工作:

(1)加载,由类加载器,查找字节码,并从字节码中创建对象。

(2)链接,验证字节码,为静态域分配空间。

(3)初始化,具有超类的话对其初始化,执行静态初始化器和静态初始化块。

初始化被延迟了,延迟到了静态方法,自然包括之前说的构造器或者是非常数静态域进行首次引用才初始化。

package son;
class A{
    static final int show= 1;
    static{
        System.out.println("init a");
    }
}
class B{
    static int showb= 1;
    static{
        System.out.println("init b");
    }
}
class C{
    static int showc= 1;
    static{
        System.out.println("init c");
    }
}
public class ClassInitialization {
    public static void main(String[] args) {
        Class InitA = A.class;
        System.out.println("after a");
        System.out.println(A.show);
        Class InitB = B.class;
        System.out.println("after b");
        System.out.println(B.showb);
        try {
            Class InintC = Class.forName("son.C");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
result:
after a
1
after b
init b
1
init c

之前没看到这里的时候,自己就先尝试Class InitA = A.class;没有打印static中的语句,就以为自己理解错了概念,原来真的没错,真的不会先初始化。

为了产生Class的引用,Class.forName()就立即初始化。但是.class的初始化是惰性的。static final是编译期常量,不需要A类进行初始化,而B类,没有final,读取域的时候,先进行链接,进行存储空间分配和初始化。所以B被初始化了,打印语句也打出来了。

这一章是我理解的不足的地方,后面还有反射和动态代理,这样回到了我原来的目的,重温前面的,学懂之前不懂的。

时间: 2024-08-02 12:00:36

Java编程思想(十四) —— 类型信息RTTI(1)的相关文章

Java编程思想(十五) —— 类型信息之反射

讲完.class,Class之后,继续. 1)泛化的Class引用 Class也可以加入泛型,加入之后会进行类型检查. 贴一下书上原话,Class<?>优于Class,虽然他们是等价的,Class<?>的好处是碰巧或疏忽使用了一个非具体的类引用.我搞不懂这个所谓非具体是什么? 后面弄懂了,其实<?>作为通配符,就是未知的,直接写结论的话不能写个具体类型吧,作者的意思其实就是说加了泛型的Class就是选择了非具体的版本. 加入泛型的原因是提供编译期间的类型检查,操作失误的

Java编程思想(十八) —— 再谈反射

在Java编程思想(十五) -- 类型信息之反射和Java编程思想(十六) -- 联系JVM再谈Class,书上只用了3页就讲完了,还有讲了那么多Class的东西,接下来要从反射中怎么用,自己结合API和其他资料再写多一些. 示例:Test.java public class Test { public Test() {     }      public Test(int i) {         System.out.println(i);     } private void pri()

Java编程思想第四版读书笔记——第十三章 字符串

Java编程思想第四版读书笔记--第十三章 字符串 字符串的操作是计算机程序设计中最常见的行为. 关键词: StringBuilder ,StringBuffer,toString(),format转换,正则表达式, 1.不可变String String对象时不可变的.每当把String对象作为方法的参数时,都会复制一份引用.(其实就是对函数中参数列表中参数的操作不会影响外面的原参数) 如下: import static net.mindview.util.Print.*; public cla

『Java编程思想-第四版』第二章:一切都是对象

Java编程思想-第四版学习总结,此为第二章:一切都是对象. package com.w3cjava.second; @SuppressWarnings("all") public class Second { /** * Java编程思想(第四版) * 第2章 一切都是对象 * @param args */ public static void main(String[] args) { /** * 2.1 用引用操作对象 * 遥控器(引用)操作电视机(对象),改变音量,改变频道 *

12.JAVA编程思想——集合的类型

12.JAVA编程思想--集合的类型 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51100510 标准Java 1.0 和1.1 库配套提供了非常少的一系列集合类.但对于自己的大多数编程要求,它们基本上都能胜任.Java 1.2 提供的是一套重新设计过的大型集合库. 1      Vector Vector 的用法很简单,大多数时候只需用addElement()插入对象,用elementAt()一次提取一个对象,并用el

java编程思想第四版中net.mindview.util包下载,及源码简单导入使用

在java编程思想第四版中需要使用net.mindview.util包,大家可以直接到http://www.mindviewinc.com/TIJ4/CodeInstructions.html 去下载,并按照所在页面的操作进行操作.当然也可以直接我下载下面的链接,下载的直接是JAR包,可以直接导入并使用: net.mindview.util包:百度网盘:点击下载  密码: ggpi java编程思想第四版源码:百度网盘:点击下载  密码: ur3e 下面我简单的介绍一下源码在Eclipse中的导

Java编程思想(十六) —— 联系JVM再谈Class

编程思想这个专栏停了好久了,主要是把精力放在了其他知识上,现在继续补上. 前面两篇写到RTTI和简单的反射介绍,先回顾一下: RTTI,运行时类型信息,多态的应用,类型转换其实是发生在运行期间. Class对象: 编程思想讲到的定义,Java使用Class对象来执行其RTTI,类是程序的一部分,每个类都有一个Class对象,其实每编写和编译一个新类,就会产生一个Class对象,其实这个对象时被保存在同名的.class文件中的.生成这个类对象,其实是JVM(Java虚拟机)使用了"类加载器&quo

Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十四)之Type Information

Runtime type information (RTTI) allow you to discover and use type information while a program is running This take two forms: 1. "traditional" RTTI, which assumes that you have all the types available at compile time, 2. the reflection mechanis

Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十二)之Error Handling with Exceptions

The ideal time to catch an error is at compile time, before you even try to run the program. However, not all errors can be detected at compile time. To create a robust system, each component must be robust. By providing a consistent error-reporting