译者翻译的时候有些奇怪,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虚拟机)使用了“类加载器”的子系统。
补充一下百度的定义:
在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。最后一句是重点。
所有的类都是在第一次使用时动态加载到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.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被初始化了,打印语句也打出来了。
这一章是我理解的不足的地方,后面还有反射和动态代理,这样回到了我原来的目的,重温前面的,学懂之前不懂的。