文章目录
反射
Java不是动态语言,但Java可称之为“准动态语言”。即Java有一定的动态性,可利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活,同时也增加了不安全性。
1、Reflection 反射概念
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存中就产生了一个class类型的对象(一个类只有一个class对象),这个对象就包含了完整的类的结构信息。可通过这个给对象看到类的结构。这个对象就如一面镜子,透过这个镜子看到类的结构,所以形象称之为:反射。
优点:
- 可实现动态创建对象和编译,体现出很大的灵活性
- 可以解耦,提高程序的可扩展性
缺点:
- 对性能有影响。
反射相关的主要API
java.lang.Class
代表一个类java.lang.reflect.Method
代表类的方法java.lang.reflect.Field
代表类对成员变量java.lang.reflect.Constructor
代表类的构造器
比如:
IDEA软件就用了反射机制,当使用一个类,调用方法时,IDEA会弹出很多方法的提示,这些方法都是通过反射机制实现的,会去找到该类通过class类对象封装了的Method对象,找到反射结果的方法。
2、Class类 JDK 1.1
对象照镜子后可以得到信息:某个类的属性、方法、构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构的有关信息。
- Class本身也是一个类
- Class对象只能由系统建立
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
常用方法
static ClassforName(String name)
返回指定类名name的Class对象Object newInstance()
调用缺省构造函数,返回Class对象的一个实例getName()
返回此Class对象所表示的实体(类,接口,数组类或void)的名称Class getSuperClass()
返回当前Class对象的父类的Class对象Class[] getinterface()
获取当前Class对象的接口ClassLoader getClassLoader()
返回该类的类加载器Constructor[] getConstructors()
返回该类的构造器Method[] getMethods()
返回一个方法类对象数组,获得本类以及父类的所有方法,只有publicMethod[] getDeclaredMethods()
返回一个方法类对象数组,获得本类的所有方法
Method getMethod(String name, Class... T)
返回指定的方法类对象Field[] getDeclaredFields()
返回类变量对象的一个数组,可找到全部属性Field[] getFields()
只能找到public属性
获取Class类实例的方法
-
若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。
Class clazz = Preson.class
-
已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class clazz = person.getClass()
-
已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException异常
Classs clazz = Class.forName(“全路径”)
-
Java中基本内置对象的包装类都有一个Type属性,可通Type属性获得Class对象
Class clazz = Integer.TYPE
-
利用ClassLoader方式获取Class对象
public class GetClassObj {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println(person.name);
// 1.
Class c1 = Student.class;
System.out.println(c1.hashCode());
// 2.
Class c2 = person.getClass();
System.out.println(c2.hashCode());
// 3.
Class c3 = Class.forName("com.lfjava.refleciton.Student");
System.out.println(c3.hashCode());
// 4.
Class c4 = Integer.TYPE;
System.out.println(c4.hashCode());
// 获取父类的Class对象实例
Class c5 = c1.getSuperclass();
System.out.println(c5.hashCode());
System.out.println(c5);
}
}
class Person{
public String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
}
class Student extends Person{
public Student() {
this.name = "我是学生";
}
}
class Teacher extends Person{
public Teacher() {
this.name = "我是老师";
}
}
我是学生
2133927002
2133927002
2133927002
1836019240
325040804
class com.lfjava.refleciton.Person
有Class对象的类型
- class:外部类、成员(成员内部类,静态内部类)、局部内部类、匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解
- primitive type:基本数据类型
- void
public class GetClassObj {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = Override.class;
Class c4 = int[].class;
Class c5 = int[][].class;
Class c6 = void.class;
Class c7 = ElementType.class;
Class c8 = Integer.class;
Class c9 = Class.class;
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
int[] num1 = new int[10];
int[] num2 = new int[100];
System.out.println(num1.getClass().hashCode());
System.out.println(num2.getClass().hashCode());
}
}
class java.lang.Object
interface java.lang.Comparable
interface java.lang.Override
class [I
class [[I
void
class java.lang.annotation.ElementType
class java.lang.Integer
class java.lang.Class
2133927002
2133927002
3、Class类生成的内存分析
在执行程序时,会先将类里的基本数据全部加载在方法区中,然后生成对应的Class类,每一个类都对应了一个Class对象,这个过程时类加载器做的。为方便new实例化时,能够通过Class类反射机制去找到对应的数据。当在new了一个对象时,就会去找该类的Class对象,通过Class对象反射机制找到该类的所有数据,进行链接(对该类的验证,准备,解析),变量什么都先赋默认值,然后开始初始化,JVM通过调用
<clinit>(){
System.out.println("静态代码块");
m = 300;
m = 100;
}
方法,将静态代码合并在一块,进行初始化。然后再调用构造器。
public class Test05{
public static void main(String[] args){
A a = new A();
System.out.println(a.m); // 输出 100
}
}
class A{
static{
System.out.println("静态代码块");
m = 300
}
static int m = 100;
public A(){
System.out.println("构造器");
}
}
注意:Class类,是通过类加载器加载到内存中时产生的。
该图就是分析的将一个Person类编译成class字节码文件,通过java的类加载器,加载该类,通过class类对象对该字节码进行封装成各个组成部分,成为其他对象。
类加载器加载过程
1、类的加载
加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,
也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
2、类的链接
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。
1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。
2)准备:类准备阶段负责为类的静态变量分配内存,并设置默认初始值。
3)解析:将类的二进制数据中的符号引用(在编译时,有些通过字符变量赋值的,因为不知道该变量指的是哪个直接指,所有把它解析成特殊的符号)替换成直接引用。
3、类的初始化
初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,
其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样
的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准
备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值
0,即a=0,然后到解析,到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。
4、类的初始化时刻
public class ClassInit {
static {
System.out.println("Main被加载,初始化...");
}
public static void main(String[] args) throws ClassNotFoundException {
// 类被初始化
// new Son();
// System.out.println(Son.b);
// System.out.println(Son.a);
// Class<?> aClass = Class.forName("com.lfjava.refleciton.Son"); // 反射
// 类不会初始化
// System.out.println(Son.B);
Son[] sons = new Son[10]; // 只是数组在内存分配空间,并不会使Son类初始化
}
}
class Father{
static {
System.out.println("Father被加载,初始化...");
}
static int a = 1;
}
class Son extends Father{
static {
System.out.println("Son被加载,初始化...");
}
static int b = 2;
static final int B = 3; // 类被加载的时候常量就已经在常量池中了
}
对应输出:
// 类被初始化
Main被加载,初始化...
Father被加载,初始化...
Son被加载,初始化...
Main被加载,初始化...
Father被加载,初始化...
Son被加载,初始化...
2
Main被加载,初始化...
Father被加载,初始化...
1
Main被加载,初始化...
Father被加载,初始化...
Son被加载,初始化...
// 类不被初始化
Main被加载,初始化...
3
Main被加载,初始化...
5、类加载器
类加载的作用:
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后再堆中生成一个代表这个类的java.lang.Class
对象,作为方法区中类数据的访问入口。
类缓存:
标准的JavaSE类加载器可按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
System.out.println(ClassLoaderTest.class.getClassLoader());
// 获取自己写的类,是通过用户加载器加载的
System.out.println(Class.forName("java.lang.Math").getClassLoader());
}
}
// 输出:
// sun.misc.Launcher$AppClassLoader@18b4aac2 用户加载器
// sun.misc.Launcher$ExtClasssLoader@7f31245a 扩展加载器
// null 根加载器(是Java的核心,用C/C++编写,该类加载器是无法直接获取的)
// sun.misc.Launcher$AppClassLoader@18b4aac2
// null
双亲委派机制:
定义了一个类,在类加载的时候,先去用户类加载器看有没有该类,没有就去扩展类加载器查找,如还没有就去根类加载器去查找。首先使用自己的包。
6、获取运行时类的对象,使用方法
public class GetClass {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class<?> c1 = Class.forName("com.lfjava.refleciton.User");
// 通过反射机制获取 类名
System.out.println(c1.getName());
System.out.println(c1.getSimpleName());
System.out.println("------------------------------------------------------");
// 通过反射机制获取 字段
Field[] fields = c1.getFields(); // 只能获取public
for(Field f : fields){
System.out.println("# "+f);
}
Field[] dFields = c1.getDeclaredFields();
for(Field f : dFields){
System.out.println("$ "+f);
}
System.out.println(c1.getField("sex"));
System.out.println(c1.getDeclaredField("name"));
System.out.println("------------------------------------------------------");
// 通过反射机制获取 方法
Method[] methods = c1.getMethods(); // 只能获取public
for(Method m: methods){
System.out.println("# "+m);
}
Method[] dMethods = c1.getDeclaredMethods();
for (Method m : dMethods) {
System.out.println("$ "+m);
}
System.out.println(c1.getDeclaredMethod("getAge", null));
System.out.println("------------------------------------------------------");
// 通过反射机制获取 构造器
Constructor[] constructors = c1.getConstructors(); // 只能获取public
for (Constructor c : constructors) {
System.out.println("# "+c);
}
Constructor[] dConstructors = c1.getDeclaredConstructors();
for (Constructor c : dConstructors) {
System.out.println("$ "+c);
}
System.out.println(c1.getDeclaredConstructor(String.class, String.class));
}
}
class User{
private String name;
private String age;
public String sex;
public User(String name, String age) {
this.name = name;
this.age = age;
}
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
private void method(){
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
输出:
com.lfjava.refleciton.User
User
------------------------------------------------------
# public java.lang.String com.lfjava.refleciton.User.sex
$ private java.lang.String com.lfjava.refleciton.User.name
$ private java.lang.String com.lfjava.refleciton.User.age
$ public java.lang.String com.lfjava.refleciton.User.sex
public java.lang.String com.lfjava.refleciton.User.sex
private java.lang.String com.lfjava.refleciton.User.name
------------------------------------------------------
# public java.lang.String com.lfjava.refleciton.User.toString()
# public java.lang.String com.lfjava.refleciton.User.getName()
# public void com.lfjava.refleciton.User.setName(java.lang.String)
# public void com.lfjava.refleciton.User.setAge(java.lang.String)
# public java.lang.String com.lfjava.refleciton.User.getAge()
# public final void java.lang.Object.wait() throws java.lang.InterruptedException
# public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
# public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
# public boolean java.lang.Object.equals(java.lang.Object)
# public native int java.lang.Object.hashCode()
# public final native java.lang.Class java.lang.Object.getClass()
# public final native void java.lang.Object.notify()
# public final native void java.lang.Object.notifyAll()
$ public java.lang.String com.lfjava.refleciton.User.toString()
$ public java.lang.String com.lfjava.refleciton.User.getName()
$ public void com.lfjava.refleciton.User.setName(java.lang.String)
$ private void com.lfjava.refleciton.User.method()
$ public void com.lfjava.refleciton.User.setAge(java.lang.String)
$ public java.lang.String com.lfjava.refleciton.User.getAge()
public java.lang.String com.lfjava.refleciton.User.getAge()
------------------------------------------------------
# public com.lfjava.refleciton.User(java.lang.String,java.lang.String)
# public com.lfjava.refleciton.User()
$ public com.lfjava.refleciton.User(java.lang.String,java.lang.String)
$ public com.lfjava.refleciton.User()
public com.lfjava.refleciton.User(java.lang.String,java.lang.String)
7、反射机制创建对象,设置属性
首先获取类的Class对象,然后通过调用newInstance()
创建该类的实例对象。
通过获取到类的set方法,通过invoke()
方法,可以设置值
setAccessible(true)
将安全监测机制关闭,可访问private的属性或成员
public class UseClass {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class clazz = Class.forName("com.lfjava.refleciton.User");
// 如同: User user = new User();
// User user = (User)clazz.newInstance();
// 通过反射 创建一个User实例,无参构造器
Constructor constructorNull = clazz.getConstructor(null);
User user1 = (User)constructorNull.newInstance(null);
System.out.println(user1.toString());
// 通过反射 创建一个User实例,有参构造器
Constructor constructor = clazz.getConstructor(String.class, String.class);
User user2 = (User)constructor.newInstance("heroC","18");
System.out.println(user2.toString());
// 通过反射 获取setName方法,用于在user2实例上
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user2,"张还行");
System.out.println(user2.toString());
// 通过反射 获取属性age,调用setAccessible(true)将安全监测机制关闭,不然没有权限直接操作属性
// 修改user2实例上的age
Field name = clazz.getDeclaredField("age");
name.setAccessible(true);
name.set(user2,"20");
System.out.println(user2.toString());
}
}
输出:
User{name='null', age='null'}
User{name='heroC', age='18'}
User{name='张还行', age='18'}
User{name='张还行', age='20'}
8、普通方式与反射机制 性能 对比
public class ReflectionFun {
public static void test01(){
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000_000_000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方法调用getName方法10亿次 "+(endTime-startTime) + "ms");
}
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class clazz = user.getClass();
Method getName = clazz.getDeclaredMethod("getName", null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000_000_000; i++) {
getName.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射机制调用getName方法10亿次 "+(endTime-startTime) + "ms");
}
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class clazz = user.getClass();
Method getName = clazz.getDeclaredMethod("getName", null);
getName.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000_000_000; i++) {
getName.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射机制关闭安全监测调用getName方法10亿次 "+(endTime-startTime) + "ms");
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test01(); // 普通方法
test02(); // 反射机制
test03(); // 反射机制 关闭监测
}
}
输出结果:
普通方法调用getName方法10亿次 4ms
反射机制调用getName方法10亿次 4359ms
反射机制关闭安全监测调用getName方法10亿次 1794ms
可见,反射机制使用的性能很低很低。在反射机制中,关闭安全监测能够提高一些性能。
9、反射获取泛型
public class GenericParamsType {
public static void test(Map<String, User> map, List<User> list){
System.out.println("test");
}
public static Map<String,User> test01(){
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = GenericParamsType.class.getMethod("test", Map.class, List.class);
// 获取方法的 泛型参数类型,返回一个Type数组
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("*******************************");
System.out.println(genericParameterType); // 打印方法中每个参数的泛型类型
System.out.println("-------------------------------");
// genericParameterType 与 ParameterizedType 有关系,就执行
// getActualTypeArguments()是ParameterizedType的方法
if(genericParameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument); // 将每个参数的真实类型点取出来
}
}
}
System.out.println();
method = GenericParamsType.class.getMethod("test01",null);
// 获取 泛型返回值类型
Type genericReturnType = method.getGenericReturnType();
System.out.println(genericReturnType); // 打印返回值类型
if(genericReturnType instanceof ParameterizedType){
// 获取返回值类型的每个参数类型
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument); // 得到具体的返回值类型
}
}
}
}
输出:
*******************************
java.util.Map<java.lang.String, com.lfjava.refleciton.User>
-------------------------------
class java.lang.String
class com.lfjava.refleciton.User
*******************************
java.util.List<com.lfjava.refleciton.User>
-------------------------------
class com.lfjava.refleciton.User
java.util.Map<java.lang.String, com.lfjava.refleciton.User>
class java.lang.String
class com.lfjava.refleciton.User
10、反射获取注解信息
getAnnotations()
获取注解
getAnnotation(String s)
获取指定的注解
public class ReflectionAnnotation {
public static void main(String[] args) throws NoSuchFieldException {
// 获取Table类的注解 (作用在类上的注解)
Annotation[] annotations = Table.class.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 获取Table类上指定的注解,并获取参数值
ClassAnnotation annotation = Table.class.getAnnotation(ClassAnnotation.class);
System.out.println(annotation.value());
// 获取Table类中的指定的属性上的指定的注解,并获取注解中的参数值
Field id = Table.class.getDeclaredField("id");
FieldAnnotation idAnnotation = id.getAnnotation(FieldAnnotation.class);
System.out.println(idAnnotation);
System.out.println(idAnnotation.columnName());
System.out.println(idAnnotation.type());
System.out.println(idAnnotation.length());
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ClassAnnotation{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldAnnotation{
String columnName();
String type();
int length();
}
@ClassAnnotation("db_student")
class Table{
@FieldAnnotation(columnName = "id", type = "int", length = 30)
private int id;
@FieldAnnotation(columnName = "age", type = "int", length = 30)
private int age;
@FieldAnnotation(columnName = "name", type = "varchar", length = 3)
private String name;
public Table() {
}
public Table(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
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;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Table{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
输出:
@com.lfjava.refleciton.ClassAnnotation(value=db_student)
db_student
@com.lfjava.refleciton.FieldAnnotation(columnName=id, type=int, length=30)
id
int
30
通过操作注解,如果是将类生成一张数据表,也可以通过反射获取注解,获取注解中的参数信息,再通过sql语句,生成数据库的表。
通过操作注解,如果是将数据库表中的数据注入到类中的属性中,就可以通过反射获取注解来完成。
ORM:Object relationship Mapping (对象关系映射)