系列文章目录
Java 注解与反射 01 —— 注解
Java 注解与反射 02 —— 反射
文章目录
静态 vs 动态
动态语言:是一类在运行时可以改变其结构的语言,例如新的函数、对象,甚至引进代码。通俗点说,就是在运行的时候代码可以根据某些条件改变自己结构。
主要的动态语言:Object-C、C#、JavaScript、PHP、Python等。
静态语言:运行时不能改变自身结构的语言,例如 Java、C、C++。
虽然 Java 不是动态语言,但 Java 可以称之为“准动态语言”。也就是说,Java 其实也有一定的动态性,而这就要依靠反射机制。
Java Reflection
Reflection(反射)的 API 允许 Java 程序在执行期间取得任何类的内部信息(类名、接口、方法、字段等)。其实质就是我们创建对象的逆过程,我们通过一个class 来创建一个对象,而反射则是通过对象能够获取一个Class。
注意,一个类只有一个Class,即便多个对象获取到的也是同一个Class。


反射优点:可以实现动态创建对象和编译,体现很大灵活性。
反射缺点:对性能有影响。使用反射基本上是一种解释操作,这类操作总是慢于直接执行的同类操作。也就是说正常情况下,我们还是通过new 得到对象,而不应该通过反射去获取。
反射相关的主要API:
-
java.lang.Class:代表一个类
-
java.lang.reflect.Method:代表类的方法
-
java.lang.reflect.Field:代表类的成员变量
-
java.lang.reflect.Constructor:代表类的构造器
Class 类
对于每个类而言,JRE都为其保留了一个不变的Class类型对象。一个Class对象包含了特定某个结构的有关信息。
- Class 本身也是一个类
- Class 对象只能由系统创建,我们只能获取
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是加载到JVM中的一个.class文件
- 每个类的实例都记得自己是由哪个Class实例生成的
- 通过Class可以完整得到一个类中所有被加载的结构
- Class类是Reflection的根源,针对任何想动态加载、运行的类,都要先获得Class对象
Class常用方法
方法名 | 功能说明 |
---|---|
static ClassforName(String name) | 返回指定类名的Class对象 |
Object newInstance() | 调用缺省构造函数,返回Class对象一个实例 |
getName() | 返回此Class对象锁表示的实体名称 |
Class getSuperClass() | 返回当前Class对象父类的Class对象 |
Class[] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Method getMethod(String name, Class… T) | 返回一个Method对象,此对象的形参类型为paramType |
Field[] getDeclareFields() | 返回Field对象的一个数组 |
获取Class类对象
-
已知具体的类,通过该类的class属性获取。最安全可靠的方法。
Class c1 = Person.class;
-
已知某个类实例,通过该实例的getClass()获取。[getClass()是Object类的方法]
Class c2 = person.getClass();
-
已知类的全类名[即包名也知道],通过Class类静态方法forName()获取。
Class c3 = Class.forName("demo01.Person");
-
内置基本数据类型可直接用类名.Type,例如Integer.Type
-
还可用ClassLoader,后面详细说明。
哪些类型可以获取Class对象?
- class:外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类
- interface:接口
- [ ]:数组,不论长度都是一个Class
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
Java 内存分析
堆:
1. 存放new的对象和数组。
2. 可以被所有的线程共享,不会存放别的对象的引用。
栈:
1. 存放基本变量类型(会包含这个基本类型的具体数值)。
2. 引用对象的变量(会存放这个引用在堆里面的具体地址)。
方法区(特殊的堆):
1. 可以被所有的线程共享。
2. 包含了所有的class和static变量、static方法,同时包含常量池、代码...
类的初始化
当程序主动使用某个类时,若该类还未被加载到内存中,则系统通过下面三个步骤对该类进行初始化。
-
加载:将.class文件内容加载到内存,并将里面的静态数据转换成方法区的运行时数据,然后生成一个代表该类的java.lang.Class对象。
-
链接:将Java类的二进制代码合并到JVM的运行状态之中。
- 验证:确保加载的类的信息符合JVM规范。
- 准备:为类变量(有static关键字)分配内存并设置类变量默认初始值。这些变量的内存将在方法区里进行分配。
- 解析:把虚拟机常量池内的符号引用(变量名)替换为直接引用(地址)。
-
初始化:
- 执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。[注意:类构造器是构造类的信息,不是构造该类对象的构造器]
- 当初始化一个类时,若该类的父类没有初始化,则先对父类进行初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确枷锁和同步。
代码更加直观:
package lessen09_AnnotationAndReflection;
//测试类初始化过程
public class Test {
static {
System.out.println("main的静态代码块");
}
public static void main(String[] args) {
/*
初始化过程:
1. 加载—— .class文件加载到内存,把静态变量、方法放到方法区,再生成Class对象。
2. 链接—— 检查类的信息规范,设置static变量默认值,这里是 m = 0;
3. 初始化—— 合并代码
<clinit>(){
System.out.println("A类静态代码块初始化");
m = 300;
m = 100;
}
最后结果,100
*/
A a = new A();
System.out.println(A.m);
}
}
class A{
static {
System.out.println("A类静态代码块初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A类的无参构造方法");
}
}
什么时候发生类的初始化
类的主动引用(一定发生类的初始化)
- 虚拟机启动时,先初始化main()所在类
- new 一个类对象时
- 调用类的静态成员(除了final常量)和静态方法时
- 使用java.lang.reflect包的方法对类进行反射调用时
- 当初始化一个类时,其父类未初始化,先初始化其父类
类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。例如,当通过子类引用父类的静态变量,不会导致子类初始化。
- 通过数组定义类引用,不会触发此类的初始化。
- 引用常量不会触发初始化。常量在链接阶段就存入调用类的常量池中了。
类加载器
类加载器作用:将.class文件内容加载到内存,并将里面的静态数据转换成方法区的运行时数据,然后在堆中生成一个代表该类的java.lang.Class对象,作为方法区中类数据访问的入口。
类缓存:一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
类加载分为:
- 引导类加载器:由C++编写,负责Java核心库(rt.jar包),无法直接获取。
- 扩展类加载器:负责jre/lib/ext目录下的jar包装入工作库。
- 系统类加载器: 把我们指定的项目下的jar包装入工作库,最常用的加载器。
package lessen09_AnnotationAndReflection;
//测试获取加载器
public class Test03 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类加载器的父类加载器————>扩展类加载器
ClassLoader loaderParent = systemClassLoader.getParent();
System.out.println(loaderParent);
//获取扩展类加载器的父类加载器————>引导类加载器(根),无法获取,返回null
ClassLoader parent = loaderParent.getParent();
System.out.println(parent);
//测试当前类是哪个加载器加载的
ClassLoader c1 = Class.forName("lessen09_AnnotationAndReflection.Test03").getClassLoader();
System.out.println(c1);
ClassLoader c2 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(c2);
}
}
获取类的运行时结构
package lessen09_AnnotationAndReflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
//测试获得类的运行时结构
public class Test04 {
public static void main(String[] args) throws Exception {
T t = new T(1, 2);
//1. 获取类名
Class c = t.getClass();
c.getName();
c.getSimpleName();
//2. 获得类的属性
Field[] fields = c.getFields();//获得所有public属性
fields = c.getDeclaredFields();//获得所有属性
Field field = c.getDeclaredField("a");//获得类的指定属性
//3. 获得类的方法
Method[] methods = c.getMethods();//获得本类及其父类的所有public方法
methods = c.getDeclaredMethods();//获得本类的方法
Method setA = c.getMethod("setA", int.class);//获得指定public方法
Method test = c.getDeclaredMethod("test", int.class, int.class);
//同理还有获得构造方法...
}
}
class T{
private int a;
public int b;
public T(){
}
public T(int a, int b){
this.a = a;
this.b = b;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
private void test(int a, int b){
}
}
动态创建对象、执行方法
Student类:
class Student{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
private void test(){
System.out.println("这是个私有方法");
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
真正运行的代码:
//动态创建对象、执行方法
public class TestReflection03 {
public static void main(String[] args) throws Exception {
//1. 获取Class对象
Class c1 = Student.class;
//2. 通过Class创建Student对象
// 1. 利用无参构造方法,若没有无参构造方法会抛出异常
Student s1 = (Student)c1.newInstance();//这里返回的是Object类型
// 2. 通过Class 获取有参构造方法再创建Student对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class);
Student s2 = (Student) constructor.newInstance("李华", 18);
//3. 通过反射调用普通方法
Method setName = c1.getDeclaredMethod("setName", String.class);//获取一个方法对象
setName.invoke(s1, "小红");//激活该方法,放入一个Student对象以及方法参数
//4. 通过反射设置属性
Field age = c1.getDeclaredField("age");//获取一个属性对象
//由于该属性是私有,必须先允许访问。
//和getDeclaredField、getField无关,这两个方法只是获取到属性对象,操作需要单独开启访问。【它会关闭对权限的检查】
age.setAccessible(true);
age.set(s1, 20);//设置该属性,需放入一个Student对象以及属性值
System.out.println("学生1:"+s1);
System.out.println("学生2:"+s2);
}
}
结果:
学生1:Student{
name='小红', age=20}
学生2:Student{
name='李华', age=18}
上面的setAccessible()不仅是访问私有属性,同时还能提高反射操作的效率,下面进行一个性能分析。
package lessen09_AnnotationAndReflection;
import java.lang.reflect.Method;
import java.sql.Struct;
//性能分析
public class TestAnalyze {
public static void test01(){
Student student = new Student();
long startTime = System.currentTimeMillis();
for (int r = 0; r < 1000000000; r++) {
student.getAge();
}
long endTime = System.currentTimeMillis();
System.out.println("正常执行10亿次:"+(endTime - startTime)+"ms");
}
public static void test02() throws Exception {
Class c = Student.class;
Student student = (Student) c.newInstance();
Method getAge = c.getMethod("getAge", null);
long startTime = System.currentTimeMillis();
for (int r = 0; r < 1000000000; r++) {
getAge.invoke(student);
}
long endTime = System.currentTimeMillis();
System.out.println("通过反射执行10亿次:"+(endTime - startTime)+"ms");
}
public static void test03() throws Exception {
Class c = Student.class;
Student student = (Student) c.newInstance();
Method getAge = c.getMethod("getAge", null);
getAge.setAccessible(true);//关闭检测,即使该方法是Public,关闭后性能也会提升
long startTime = System.currentTimeMillis();
for (int r = 0; r < 1000000000; r++) {
getAge.invoke(student);
}
long endTime = System.currentTimeMillis();
System.out.println("setAccessible执行10亿次:"+(endTime - startTime)+"ms");
}
public static void main(String[] args) throws Exception {
test01();
test02();
test03();
}
}
结果:
正常执行10亿次:3ms
通过反射执行10亿次:2362ms
setAccessible执行10亿次:1564ms
可以明显看出,当需要进行大量反射操作时,使用setAccessible(true)可以提升性能。
练习:ORM
ORM(Object Relation Mapping)即对象关系映射,我们通过注解和反射完成类与表结构的映射关系。学习注解和反射便于后面对数据库、框架的学习。这里用一个例子来获取注解信息。
自定义注解:
//类名的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyType{
String value();//类名
}
//属性的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface MyField{
String columnName();//列名
String type();//类型
int length();//长度
}
Bird类:
@MyType("db_birds")
class Bird{
@MyField(columnName = "db_name", type = "varchar", length = 3)
private String name;
@MyField(columnName = "db_age", type = "int", length = 2)
private int age;
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
}
主函数:
/*
练习:Object Relation Mapping 对象关系映射
利用注解和反射完成类和表结构的映射关系
*/
public class TestORM {
public static void main(String[] args) throws Exception {
//通过反射获取Class对象
Class c1 = Class.forName("lessen09_AnnotationAndReflection.Bird");
//通过反射获取类全部注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得类注解的value值,直接通过类的Class对象获取
MyType type = (MyType)c1.getAnnotation(MyType.class);
System.out.println(type.value());
//获得类属性注解的value值,先获取到Field对象,再获取注解
Field name = c1.getDeclaredField("name");//注意,name是private,getField只能获取public
MyField myField = name.getAnnotation(MyField.class);
System.out.println(myField.columnName());
System.out.println(myField.type());
System.out.println(myField.length());
}
}
结果:
@lessen09_AnnotationAndReflection.MyType(value=db_birds)
db_birds
db_name
varchar
3
通过提取注解,方便后面对数据库进行操作。