1、RTTI(运行时类型识别)
是什么?
RTTI即运行时类型识别,意思跟名字一样,在运行时识别某个引用所指向对象的确切类型。
为什么需要?
《编程思想》中的例子:有一个Shape类,它有几个子类Circle、Square和Triangle。假如创建一个Shape类型的ArrayList数组,那么这个数组是可以用Shape的子类来填充的,但是当你取出数组中的元素时,你只知道这是一个Shape的引用,它到底指向的是Square类还是Circle类你不知道,而使用RTTI则可以查询Shape引用指向的对象的确切类型。
怎么实现?
java怎么让我们在运行时识别类和对象的信息:一是编译是就知道的类型信息,二是通过反射机制在运行时发现和使用类的信息
2、Class对象
是什么?
Class对象是保存了与类有关信息的特殊对象,它用来创建类的对象。每当编译了一个新类就会产生一个保存在一个同名的.class文件中的Class对象。(感觉要去看看虚拟机的知识)
用来干什么?
比如要生成Shape类的对象,那么类加载器首先检查Shape类的Class对象是否加载,若没有加载就会根据类名(Shape)查找.class文件,并将Class对象载入内存,然后Class对象就用来创建Shape类对象。(自己的理解,欢迎大佬指正)
3、Class类中一些方法的使用:
(1)Class.forName(),使用Class.forName()获得Class对象引用时会导致类的加载
package rtti;
class A{
static{
System.out.println("A");
}
}
class Test {
public static void main(String[] args){
try{
Class.forName("rtti.A");//此时类A被加载了
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
//output:
//A
(2)getName()、getSimepleName()、getCanonicalName()和isInterface()
getName():返回虚拟机里面class的表示
getSimepleName():返回简单的类名表示
getCanonicalName():返回比getName()简洁的类名表示,大部分情况下与getName()返回的值相同
package psl.rtti.study;
class A{
class InnerA{
}
}
class B{
}
public class Test3 {
public static void main(String[] args) {
Class a = A.class;
Class innera = A.InnerA.class;
Class b = B.class;
System.out.println("a.getName():"+a.getName());
System.out.println("a.getSimpleName():"+a.getSimpleName());
System.out.println("a.getCanonicalName():"+a.getCanonicalName());
System.out.println("innera.getName():"+innera.getName());
System.out.println("innera.getSimpleName():"+innera.getSimpleName());
System.out.println("innera.getCanonicalName():"+innera.getCanonicalName());
System.out.println("b.getName():"+b.getName());
System.out.println("b.getSimpleName():"+b.getSimpleName());
System.out.println("b.getCanonicalName():"+b.getCanonicalName());
}
}
/*output:
*a.getName():psl.rtti.study.A
*a.getSimpleName():A
*a.getCanonicalName():psl.rtti.study.A
*innera.getName():psl.rtti.study.A$InnerA
*innera.getSimpleName():InnerA
*innera.getCanonicalName():psl.rtti.study.A.InnerA
*b.getName():psl.rtti.study.B
*b.getSimpleName():B
*b.getCanonicalName():psl.rtti.study.B
*/
(3)getSuperClass()以及newInstance()
使用非泛化的Class的newInstance()方法返回的是Object对象,但是该引用指向的是对应的类对象。使用newInstance()来创建类时必须要有默认的构造器。
使用泛化的Class时:
Class<? extends A> 此时可以确定newInstance()产生的类为A的子类,所以可以将其返回值赋值给A的引用
Class<? super A>此时使用newIntance()依然返回Object的引用
Class<A>此时确切的知道类型为A,newInstance()返回的为A类型
class A1 {
}
class B1 extends A1{
}
public class Test {
public static void main(String[] args) throws Exception{
Class<B1> cb1= B1.class;
B1 b1 = cb1.newInstance();//返回的为B1类型
System.out.println(b1);
Class c = cb1.getSuperclass();
//A1 a = c.newInstance(); //Error
Object obj1 = c.newInstance(); //这里实际返回Object引用,但指向A1对象
System.out.println(obj1);
Class<? super B1> csb1 = cb1.getSuperclass();//这儿只知道是B1的超类,但具体不知道
Object obj2 =csb1.newInstance(); //所以返回Object引用,指向的A1对象
Class<? extends A1> cea1 = B1.class;//这里保证是A1的导出类
A1 a2 = cea1.newInstance();//所以可以用A1
}
}
//output:
//psl.rtti.study.B1@15db9742
//psl.rtti.study.A1@6d06d69c
4、类字面常量(A.class)
当使用类字面常量来创建对Class对象的引用时,并不会初始化类。
为了使用类而做的准备包含三步(以后看虚拟机时再深入理解)
(1)加载。由类加载器执行,通常在classpath所指定的路径中查找字节码,并从这些字节码中创建一个Class对象
(2)链接。验证类中的字节码,为静态域分配存储空间,如果需要的话,解析这个类创建的对其他类的引用
(3)初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块
使用类的“编译期常量”也不需要对类进行初始化
class A{
static final int sfa = 10;
static final double sa =Math.random();
static {
System.out.println("A初始化了");
}
}
class B{
static int b = 20;
static {
System.out.println("B初始化了");
}
}
public class Test{
public static void main(String[] args){
Class a = A.class;
System.out.println("取得A的Class对象引用之后");//没有引起类的初始化
System.out.println("A.sfa="+A.sfa);//由于sfa为编译期常量,也没有引起类的初始化
System.out.println("A.sa="+A.sa);//虽然sa是static与final的但是依然引起类的初始化
System.out.println("B.b="+B.b);
}
}
//output:
//取得A的class对象引用之后
//A.sfa=10
//A初始化了
//A.sa=0.6363871958687979
//B初始化了
//B.b=20
5、泛化的Class引用
java SE5后Class可以使用泛型,从而可以限制Class引用所指向的Class对象的类型
通配符“?”可以取消这一限制,也可以使用通配符限定某一范围
Class Test{
public static void main(String[] args){
Class c1 = int.class;
Class<Integer> c2 = Integer.class;//或者 = int.class
Class<?> c3 = int.class;
Class<? extends Number> c4 =int.class;
c1 = Double.class;//c1没有做限定,所以可以
//c2 = Double.class; c2限制为Integer的Class对象,所以编译错误
c3 = Double.class;//c3使用的通配符,所以也可以
c4 = Double.class;//Double是Number的子类,所以也可以
}
}