第十四章类型信息

java在运行时识别对象和类信息主要有2中方式:
1.传统的RTTI(Run-Time Type Indentification) ,他假定在编译时已经知道所有的类型.
2.java反射,允许在运行时发现和使用类信息.

RTTI :主要解决传统意义上的多态向上转型问题.事实上List<> 容器保存多态对象时,对于容器来说他始终是当成Object对象保存,在读取容器的数据时在转成基类.运行时就是多态的需要解决的问题.

14.2 Class对象

class 是表示类信息的对象.运行class对象的系统成为类加载器.
类加载器子系统包含一个原生类加载器,但他只加载可信类.包括java API类,通常他是从本地加载的.当程序第一次创建其静态成员引用时,就会加载这个类.从此说名类的构造器是静态方法,new 操作符会被认为创建对静态成员的引用.

Class.forName("com.xxx.className");//使用的是全限定名
class.newInstance(),方法的类必须还有默认构造器.

14.2字面常量

例如 Test.class. 字面常量更简洁,更安全,高效.不需要进行异常检车.普通类,接口,和数组,基本类型都可以使用字面常量.包装器类还定义了特殊的字段.TYPE,表示字面常量.

采用.class 创建对象的引用时候不会自动初始化该class对象.为了实用类准备工作实际上有经过3个阶段

1.加载:类加载器执行,创建一个class对象
2.链接:验证类中的字节码,为静态域分配存储空间,如果有必要会创建对其他类的引用.
3.初始化:初始化静态域,被延迟到静态变量,静态域被首次引用时.如果没有调用 new XXX(),其构造函数不会执行.

package tinking_in_java.runTimeTypeInfo;
/**
 * Created by leon on 17-12-17.
 */
class Initable {
    static final int a = 47;
    static {
        int j = 100;
        System.out.println("static block");
    }
    public Initable() {
        System.out.println("construction Initable");
    }
}
class Initable2 extends Initable {
    public static final int b = 100;
    static {
        System.out.println("Initable2");
    }
    public Initable2() {
        System.out.println("construction Initable2");
    }
}
public class rttiTest {
    public static void main(String[] args) {
        Class initable2 = Initable2.class;
        Class innn = int.class;
        try {
            Class inn2 = Class.forName("tinking_in_java.runTimeTypeInfo.Initable2");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        int c = Initable2.b;
    }
}
//output----
static block
Initable2
Process finished with exit code 0

14.2.2 泛型class引用

在泛型中,如果需要有继承关系 需要采用extends 关键字,不能直接用基类名字.
例如 :

Class intNumber=int.class; //这样是不正确的,虽然Integer 是继承Number,
                                   //但是Integer class 不是继承 Number Class .
Class intNumber=int.class;//这样才是合法的

14.3 类型检测

instanceof :只能比较是否属于 某class
isAssignableFrom()判断是否是超类,或者同类

14.5反射:运行时的类信息

package tinking_in_java.runTimeTypeInfo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * Created by leon on 17-12-17.
 */
interface Interface {
    void dosomething();
    void dosomethingEles(String arg);
}
class RealObject implements Interface {
    @Override
    public void dosomething() {
        System.out.println("real object");
    }
    @Override
    public void dosomethingEles(String arg) {
        System.out.println("real object" + arg);
    }
}
class DynamicHandle implements InvocationHandler {
    private Object proxyed;
    public DynamicHandle(Object proxyed) {
        this.proxyed = proxyed;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (args != null)
            for (Object arg : args)
                System.out.println("" + arg);
        if (method != null) {
            System.out.println(method.toString());
        }
        //如果返回null 则表示拦截方法,如果返回method.invoke() 表示不拦截
        return null;//method.invoke(proxyed, args);
    }
}
public class SimpleDynamicProxy {
    public static void cusume(Interface intf) {
        intf.dosomething();
        intf.dosomethingEles("booooo");
    }
    public static void main(String[] args) {
        RealObject realObject = new RealObject();
//        cusume(realObject);
        Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),
                new Class[]{Interface.class},
                new DynamicHandle(realObject));
        cusume(proxy);
    }
}
//output----
public abstract void tinking_in_java.runTimeTypeInfo.Interface.dosomething()
booooo
public abstract void tinking_in_java.runTimeTypeInfo.Interface.dosomethingEles(java.lang.String)

java的动态代理缺陷,只能针对实现接口类的代理做修改.不支持对class 进行代理.如果需要针对class代理,则需要采用cglib,CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑

类加载过程:

第十四章类型信息_第1张图片
image.png

加载,验证,准备,初始化,卸载 这几部分的顺序是按部就班的开始.
但是解析的顺序是不确定的,java为了支持动态绑定机制,有时候可以在初始化之后在进行.
1.什么时候开始加载? jvm规范并没有严格要求,有虚拟机具体实现决定.
2.初始化有严格要求:
(1) new,getstatic, putstatic 或者invokestatic 4条指令是.会触发初始化.(但是读写 static final 静态常量池中字段不会触发初始化).
(2) 使用java.lang.refelect包的方法进行反射调用时,如果没有初始化会触发初始化.
(3) 当初始化一个类时,发现其父类还没有初始化,会先触发器父类初始化.
(4) 当虚拟机启动是,用户需要执行主类(包含main()的那个类),会初始化这个主类.
(5) 当使用jdk动态语言支持时,如果一个java.lang.invoke.methodHandle实例最后结果返回是REF_getStatic,REF_putStatic,REF_invokeStaic句柄时,当句柄所对应的类没有初始化,会初始化.

对于这5中情况的触发称为主动引用,虚拟机规范中定义”有且只有”,处理这5种情况之外的所有引用,都不会触发初始化,成为被动引用.

接口加载过程和类加载过程稍有些不同.接口也有初始化过程,和类初始化的区别在于上述5点的第三点.接口在初始化时,并不会把父接口全部初始化,只有在用到父接口的时候才会(把父接口)初始化.

类的加载过程:

(一) 加载(class loading) :
1.采用权限定符(com.xxx.xxx)读取class文件
 2.将静态存储结构转换成方法区的运行时数据结构
 3.在内存中生成java.lang.Class对象,作为方法区这个类各种数据的访问入口.
虚拟机的这3点要求并不是很严格,所以在加载阶段可以有很多灵活的方法:例如
 a)从zip,jar,war,ear,apk等包读取
 b)从网络中读取 applet
c)运行时计算生成,动态代理技术.Proxy 类就是通过ProxyGenerator.generateProxyClass 生成$Proxy形式的代理类.
数组类和一般类的加载不太一样.
(二) 验证:
  1)文件验证(验证文件的格式是否符合jvm规范要求)
   2)元数据验证(保证类,方法,字段符合java规范)
   3)字节码验证(保证代码数据流和控制流,包括类型转化保证不会危害虚拟机)
   4)符号引用验证(验证类引用其他类,方法,字段的可见性)这个过程不是一定必须,如果不需要验证符号可以设置 -Xverify:none
(三) 准备:
正式为类变量分配内存,并初始值阶段.这里进行内存分配的仅仅是static 变量,而不包括实例变量,实例变量会在初始化时候随对象一起分配到java堆中.这里说的初始阶段通常是初始为0 的."通常情况"指的是不是静态常量,仅仅是静态变量.如果是静态常量在准备阶段就会设置常量值.
例如: static int i=100;//准备阶段 时 i=0
static final int I =100;//准备阶段时 i=100,这个是静态常量
(四) 解析:
主要将符号引用解析成直接引用的过程,转化成具体的机器对应的执行地址.
(五) 初始化:
执行类构造器()方法过程,()实例构造器, 这两个是不同的,类构造器中,静态域,变量是按java代码中的排列顺序赋值.静态语句块只能访问在他之前定义的变量,在静态语句块之后的静态变量可以赋值,但无法访问.

  public class Test{
  static{
        i=0;//给变量赋值可以正常编译通过
        System.out.print(i);//这句编译器会提示"非法向前引用"
         }
    static int i=1;
 }

你可能感兴趣的:(第十四章类型信息)