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<Number> intNumber=int.class; //这样是不正确的,虽然Integer 是继承Number,
//但是Integer class 不是继承 Number Class .
Class<? extends Number> 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采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑
类加载过程:
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,这个是静态常量
(四) 解析:
主要将符号引用解析成直接引用的过程,转化成具体的机器对应的执行地址.
(五) 初始化:
执行类构造器<clinit>()方法过程,<init>()实例构造器, <clinit> 和<init>这两个是不同的,类构造器中,静态域,变量是按java代码中的排列顺序赋值.静态语句块只能访问在他之前定义的变量,在静态语句块之后的静态变量可以赋值,但无法访问.
public class Test{
static{
i=0;//给变量赋值可以正常编译通过
System.out.print(i);//这句编译器会提示"非法向前引用"
}
static int i=1;
}
网友评论