Java的动态性
反射机制
• 动态编译
• 动态执行javascript代码
• 动态字节码操作
动态语言
动态语言
– 程序运行时,可以改变程序结构或变量类型。典型的语言:
• Python、ruby、javascript等。
• 如下javascript代码:
• C, C++, JAVA不是动态语言,JAVA可以称之为“准动态语言”。但是JAVA有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
• JAVA的动态性让编程的时候更加灵活
反射机制
– 反射机制指的是可以于运行时加载、探知、使用编译期间完全未知的类。
– 程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个已加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;
# 反射的核心代码之一,注意 Class 的 C 是大写
Class c = Class.forName("包名.类名");
– 加载完类之后,在堆内存中,就产生了一个 Class 类型的对象(一个
类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。
我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过
这个镜子看到类的结构,所以,我们形象的称之为:反射。
java.lang.Class
类十分特殊,用来表示java中类型(class/interface/enum/annotation/primitive type/void
)本身。
– Class 类的对象包含了某个被加载类的结构。一个被加载的类对应一个 Class 对象。
– 当一个 class 被加载,或当加载器(class loader)的 defineClass() 被JVM调用,JVM 便自动产生一个Class 对象。
• Class类是Reflection的根源。
– 针对任何您想动态加载、运行的类,唯有先获得相应的Class 对象
Class类的对象如何获取
• 运用 getClass()
• 运用 Class.forName()(最常被使用)
• 运用 .class 语法
下面重点看下关于 Class 的源码注释
# Class 的源码注释翻译
Instances of the class Class represent classes and interfaces in a running Java application.
类class的实例表示运行中的Java应用程序中的类和接口。
An enum is a kind of class and an annotation is a kind of interface.
枚举是一种类,注释是一种接口。
Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions.
每个数组还属于一个类,这个类反映为一个类对象,由具有相同元素类型和维数的所有数组共享。
The primitive Java types (boolean, byte, char, short, int, long, float, and double), and the keyword void are alsorepresented as Class objects.
原始Java类型(布尔型、字节型、char型、short型、int型、long型、float型和double型)和关键字void也被表示为类对象。
测试用例:
package com.bigdataBC.reflect.bean;
public class User {
private int id;
private int age;
private String uname;
// 这里省略set、get方法,读者使用请自行添加
public User(int id, int age, String uname) {
super();
this.id = id;
this.age = age;
this.uname = uname;
}
public User() {
}
}
示例代码 demo1.java
package com.bigdataBC.reflect;
/**
* 测试 各种类型(class,interface,enum,annotation,primitive type,void)对应的java.lang.Class对象的获取方式
* @author bboy枫亭
*
*/
@SuppressWarnings("all")
public class demo1 {
public static void main(String[] args) {
String path="com.bigdataBC.reflect.bean.User";
try {
Class clazz = Class.forName(path);
// 对象是用来表示或封装一些数据。一个类被加载后,JVM会创建一个对应该类的Class对象,类的整个结构信息会放到对应的Class对象中。
// 这个Class对象就像一面镜子一样,通过这面镜子可以看到对应类的全部信息
System.out.println(clazz.hashCode());
/**
* 一个类值对应一个Class对象
*/
Class clazz2 = Class.forName(path);
System.out.println(clazz2.hashCode());
Class strClazz = String.class;
Class strClazz2 = path.getClass();
System.out.println(strClazz == strClazz2);
Class intClazz = int.class;
/**
* 关于数组,注释中是这样说的:
* Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions.
* 也就是说和长度无关,和维度有关
*/
int[] arr01 = new int[10];
int[] arr02 = new int[30];
int[][] arr03 = new int[30][3];
int[][] arr04 = new int[30][10];
System.out.println(arr01.getClass().hashCode());
System.out.println(arr02.getClass().hashCode());
System.out.println(arr03.getClass().hashCode());
System.out.println(arr04.getClass().hashCode());
double[] arr05 = new double[10];
System.out.println(arr05.getClass().hashCode());
} catch (Exception e) {
// TODO: handle exception
}
}
}
运行结果:
反射机制的常见作用
• 动态加载类、动态获取类的信息(属性、方法、构造器)
• 动态构造对象
• 动态调用类和对象的任意方法、构造器
• 动态调用和处理属性
• 获取泛型信息
• 处理注解
示例代码 demo2.java
package com.bigdataBC.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 应用反射API,获取类的信息(类的名字、属性、方法、构造器等)
* @author bboy枫亭
*/
public class demo2 {
public static void main(String[] args) {
String path = "com.bigdataBC.reflect.bean.User";
try {
Class clazz = Class.forName(path);
// 获取类的名字
System.out.println(clazz.getName()); //获得包名.类名 com.bigdataBC.reflect.bean.User
System.out.println(clazz.getSimpleName()); //获得类名 User
System.out.println("=================================================================================");
// 获得属性
// Field[] fields = clazz.getFields(); // 只能获得public的field
Field[] fields = clazz.getDeclaredFields(); // 获得所有的field
Field f = clazz.getDeclaredField("uname"); //获得单个field
System.out.println(fields.length);
for (Field temp:fields) {
System.out.println("属性:"+temp);
}
System.out.println("=================================================================================");
// 获得方法
Method[] methods = clazz.getDeclaredMethods(); //获取所有方法
//获取单个方法
Method m = clazz.getDeclaredMethod("getUname", null);
Method m2 = clazz.getDeclaredMethod("setUname", String.class); //如果方法有参,则必须传递参数类型,比如有重载情况的话,不区分参数类型就无法区分是要找哪个方法。
for (Method ms:methods) {
System.out.println("方法:"+ms);
}
System.out.println("=================================================================================");
// 获得构造器
Constructor[] cs = clazz.getDeclaredConstructors(); //获取所有构造器
for (Constructor c:cs) {
System.out.println(c);
}
// 获取单个构造器
Constructor c1 = clazz.getDeclaredConstructor(null); //获取无参构造器
Constructor c2 = clazz.getDeclaredConstructor(int.class,int.class,String.class);
} catch (Exception e) {
// TODO: handle exception
}
}
}
运行结果:
示例代码 demo3.java
package com.bigdataBC.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.bigdataBC.reflect.bean.User;
/**
* 通过反射API动态地操作:构造器、方法、属性
* @author bboy枫亭
*/
public class demo3 {
public static void main(String[] args) {
String path = "com.bigdataBC.reflect.bean.User";
try {
Class<User> clazz = (Class<User>)Class.forName(path);
// 通过反射API调用构造方法,构造对象
User u = clazz.newInstance(); //其实是调用了User的无参构造方法,如果没有无参构造方法,就会报初始化异常的错误。这也就是为什么Javabean一定要有无参构造方法。
System.out.println(u);
//指定相关构造器进行传参调用
Constructor<User> c = clazz.getDeclaredConstructor(int.class,int.class,String.class);
User u2 = c.newInstance(002,18,"枫亭");
System.out.println(u2.getUname());
System.out.println("=================================================================================");
// 通过反射API动态调用普通方法
User u3 = clazz.newInstance();
// 下面这两行代码相当于:u3.setUname("张三");
Method method = clazz.getDeclaredMethod("setUname", String.class);
method.invoke(u3, "张三");
// 这样的好处:方法和参数都是变量(动态的),方便调用不同的方法进行传参。
System.out.println(u3.getUname());
System.out.println("=================================================================================");
// 通过反射API动态操作属性
User u4 = clazz.newInstance();
Field f = clazz.getDeclaredField("uname");
// 由于User里面设置三个属性都是私有的,所以要加下面这行代码,否则报异常无法运行。
f.setAccessible(true); //设为true代表这个属性不需要做安全检查了,可以直接访问
f.set(u4, "李四"); //通过反射直接写属性
System.out.println(u4.getUname());
System.out.println(f.get(u4)); //通过反射直接读属性
} catch (Exception e) {
// TODO: handle exception
}
}
}
运行结果:
反射机制性能问题
- setAccessible
– 启用和禁用访问安全检查的开关,值为 true 则指示反射的对象在使用时应该取消 Java 语
言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。并不是为true
就能访问为false就不能访问。
– 禁止安全检查,可以提高反射的运行速度。
• 可以考虑使用:cglib/javaassist字节码操作
测试代码 demo4.java
package com.bigdataBC.reflect;
import java.lang.reflect.Method;
import com.bigdataBC.reflect.bean.User;
/**
* 通过跳过安全检查,提高反射效率
* 三种执行方法的效率差异比较
*/
public class demo4 {
public static void test01(){
User u = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
u.getUname();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方法调用,执行10亿次,耗时:"+(endTime-startTime)+"ms");
}
public static void test02() throws Exception{
User u = new User();
Class clazz = u.getClass();
Method m = clazz.getDeclaredMethod("getUname", null);
// m.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
m.invoke(u, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射动态方法调用,执行10亿次,耗时:"+(endTime-startTime)+"ms");
}
public static void test03() throws Exception{
User u = new User();
Class clazz = u.getClass();
Method m = clazz.getDeclaredMethod("getUname", null);
m.setAccessible(true); //不需要执行访问安全检查
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000L; i++) {
m.invoke(u, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射动态方法调用,跳过安全检查,执行10亿次,耗时:"+(endTime-startTime)+"ms");
}
public static void main(String[] args) throws Exception {
test01();
test02();
test03();
}
}
运行结果:
反射操作泛型(Generic)
Java采用泛型擦除的机制来引入泛型。
Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。
但是,一旦编译完成,所有的和泛型有关的类型全部擦除。
• 为了通过反射操作这些类型以迎合实际开发的需要,Java就新增了ParameterizedType,GenericArrayType,TypeVariable 和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
• ParameterizedType: 表示一种参数化的类型,比如Collection
• GenericArrayType: 表示一种元素类型是参数化类型或者类型变量的数组类型
• TypeVariable: 是各种类型变量的公共父接口
• WildcardType: 代表一种通配符类型表达式,比如?, ? extends Number, ? super Integer【wildcard是一个单词:就是“通配符”】
示例代码 demo5.java
package com.bigdataBC.reflect;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import com.bigdataBC.reflect.bean.User;
/**
* 通过反射获取泛型信息
*/
public class demo5 {
public void test01(Map<String,User> map,List<User> list){
System.out.println("demo5.test01()");
}
public Map<Integer,User> test02(){
System.out.println("demo5.test02()");
return null;
}
public static void main(String[] args) {
try {
//获得指定方法参数泛型信息
Method m = demo5.class.getMethod("test01", Map.class,List.class);
Type[] t = m.getGenericParameterTypes();
for (Type paramType : t) {
System.out.println("#"+paramType);
if(paramType instanceof ParameterizedType){
// 如果它是一个参数化类型,则将它强制转型为带泛型的参数类型,然后.getActualTypeArguments()获得真正的参数类型
Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
for (Type genericType : genericTypes) {
System.out.println("泛型类型:"+genericType);
}
}
}
//获得指定方法返回值泛型信息
Method m2 = demo5.class.getMethod("test02", null);
Type returnType = m2.getGenericReturnType();
if(returnType instanceof ParameterizedType){
Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type genericType : genericTypes) {
System.out.println("返回值,泛型类型:"+genericType);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
反射操作注解(Annotation)
可以通过反射API:getAnnotations, getAnnotation获得相关的注解信息
//获得类的所有有效注解
Annotation[] annotations=clazz.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a);
}
//获得类的指定的注解
SxtTable st = (SxtTable) clazz.getAnnotation(SxtTable.class);
System.out.println(st.value());
//获得类的属性的注解
Field f = clazz.getDeclaredField("studentName");
SxtField sxtField = f.getAnnotation(SxtField.class);
System.out.println(sxtField.columnName()+"-- "+sxtField.type()+"--"+sxtField.length());
示例代码:
先自定义两个注解:
MyField.java
package com.bigdataBC.annotation.reflect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
String columnName();
String type();
int length();
}
MyTable.java
package com.bigdataBC.annotation.reflect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTable {
String value();
}
Student.java 使用两个注解
package com.bigdataBC.annotation.reflect;
@MyTable("tb_student")
public class Student {
@MyField(columnName="studentName",type="varchar",length=10)
String studentName;
@MyField(columnName="id",type="int",length=10)
int id;
@MyField(columnName="age",type="int",length=3)
int age;
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
测试类 demo.java
package com.bigdataBC.annotation.reflect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
/**
* 使用反射读取注解的信息,模拟处理注解信息的流程
* @author 18322
*
*/
public class demo {
public static void main(String[] args) {
try {
Class clazz = Class.forName("com.bigdataBC.annotation.reflect.Student");
//获得类的所有有效注解 @com.bigdataBC.annotation.reflect.MyTable(value=tb_student)
Annotation[] annotations=clazz.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a);
}
//获得类的指定的注解 tb_student
MyTable t = (MyTable) clazz.getAnnotation(MyTable.class);
System.out.println(t.value());
//获得类的属性的注解 studentName--varchar--10
Field f = clazz.getDeclaredField("studentName");
MyField myField = f.getAnnotation(MyField.class);
System.out.println(myField.columnName()+"--"+myField.type()+"--"+myField.length());
//根据获得的表名、字段的信息,拼出DDL语句,然后,使用JDBC执行这个SQL,在数据库中生成相关的表
} catch (Exception e) {
// TODO: handle exception
}
}
}
运行结果: