动态性
Java本质为静态语言,而不是动态语言。动态语言显著的特点是在程序运行时,可以改变程序结构或变量类型,典型的动态语言有Python、ruby、javascript等。Java不是动态语言,但Java具有一定的动态性,表现在以下几个方面:
l 反射机制;
l 动态字节码操作;
l 动态编译;
l 执行其他脚本代码;
下面我们将介绍反射机制和动态字节码操作等内容。
1、 反射机制
反射机制指的是可以在运行时加载、探知,使用编译期间完全未知的类。我们知道,当类加载完毕后,在堆内存中,就产生了一个Class类型的对象(一个类只有一个Class对象)。这个对象包含了完整的类结构信息,通过该对象就可以获知类的结构信息,如类的方法、属性等。
所以通过一个类的Class对象,我们就可以动态的创建实例、调用方法、访问属性等。当一个类被加载时,JVM便自动产生了一个Class对象,Class对象是反射的根源,使用反射机制的前提是获取类的Class对象。Class的描述如下:
Instances of the class Class represent classes and interfaces in a running Javaapplication. An enum is a kind of class and an annotation is a kind ofinterface. Every array also belongs to a class that is reflected as a Class object that is shared byall arrays with the same element type and number of dimensions. The primitiveJava types (boolean, byte, char, short, int, long, float, and double), and the keyword void are also represented as Class objects.
Class has no public constructor. Instead Class objects are constructedautomatically by the Java Virtual Machine as classes are loaded and by calls tothe defineClass method in the classloader.
反射机制常见的操作有:
l 动态加载类、动态获取类的信息,如构造器、方法、属性等;
l 动态构造对象;
l 动态调用类和对象的任意方法(公有和私有)、构造器等;
l 访问任意属性(公有和私有),在访问私有属性时,需要设置setAccessible属性为真,这时候告诉JVM不做安全检查,可以直接反问,而且由于不做安全检查,提高了效率;
l 处理注解;
l 获取泛型信息;
使用反射机制最大的问题就是性能问题,若大量使用反射则性能会大幅下降,如下:
public class Main
{
public static void main(String[] args)
{
test1();
test2();
test3();
}
public static void test1()
{
long startTime=System.currentTimeMillis();
String string=new String();
for (long i = 0; i < 1000000000L; i++)
{
string.trim();
}
long endTime=System.currentTimeMillis();
System.out.println("普通方法调用10亿次耗时:"+(endTime-startTime));
}
public static void test2()
{
try
{
long startTime=System.currentTimeMillis();
Class<String> clazz=String.class;
String string=clazz.newInstance();
Method method=clazz.getMethod("trim",null);
for (long i = 0; i < 1000000000L; i++)
{
method.invoke(string, null);
}
long endTime=System.currentTimeMillis();
System.out.println("反射方法调用10亿次耗时:"+(endTime-startTime));
}
catch (Exception e)
{
}
}
public static void test3()
{
try
{
long startTime=System.currentTimeMillis();
Class<String> clazz=String.class;
String string=clazz.newInstance();
Method method=clazz.getMethod("trim",null);
method.setAccessible(true);
for (long i = 0; i < 1000000000L; i++)
{
method.invoke(string, null);
}
long endTime=System.currentTimeMillis();
System.out.println("反射方法调用10亿次,不做安全检查耗时:"+(endTime-startTime));
}
catch (Exception e)
{
}
}
}
普通方法调用10亿次耗时:1271
反射方法调用10亿次耗时:2912
反射方法调用10亿次,不做安全检查耗时:2164
为了提高性能,可以采用Javassist、cglib等进行字节码操作。
2、 字节码操作
运行时操作字节码,比反射开销下,效率高,可以实现如下功能:
l 动态生成新的类;
l 动态改变某个类的结构,如添加、删除、修改属性或方法等;
常见的字节码操作类库有:
l BCEL
l ASM
l CGLIB
l Javassist
3、 代理
在Java中代理分为静态代理和动态代理两种类型。静态代理就是显示源码创建一个类的代理,当代理较多时,需要写大量的代理类源码;而动态代理是动态创建一个类的代理类,如可以通过JDK动态生成代理类,也可以采用字节码操作动态创建代理类。通过JDK提供的Proxy、InvocationHandler来动态创建代理类的步骤如下:
1) 定义接口
2) 提供实现类
3) 实现InvocationHandler
/**
* 定义接口
* */
public interface UserDao
{
public void add();
}
/**
* 实现类
* */
public class UserDaoImpl implements UserDao
{
public void add()
{
System.out.println("adduser");
}
}
/**
* InvocationHandler
* */
public class ProxyFactory implements InvocationHandler
{
private Object real;
public ProxyFactory(Object real)
{
this.real=real;
}
/**
* 通过Proxy来返回代理对象
* */
publicObject getProxyObject()
{
returnProxy.newProxyInstance(ClassLoader.getSystemClassLoader(),real.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
System.out.println("logging..........");
Object result=method.invoke(real, args);
return result;
}
}