文章目录
1 静态 VS 动态语言
- 动态语言是一类在运行时可以改变其结构的语言,例如可以引进新的函数、对象、甚至代码;已有的函数可以被删除或进行结构上的变化。通俗来说是在运行时代码可以根据某些条件改变自身结构。主要有:Object-C、JavaScript、PHP、Python等
- 静态语言与动态语言相对,其运行时结构不可变,如Java、C、C++
- Java不是动态语言,但可称为“准动态语言”,即Java具有一定的动态性。可以利用反射机制获得类似动态语言的特性
2 Java反射
-
反射(Reflection)是Java被视为“准动态语言”的关键,反射机制允许程序在执行期间借助反射API取得任何类的内部信息,并能直接操作任意对象的内部属性和方法
-
加载完类之后,在堆内存的方法区中就产生了一个
Class
类型的对象(一个类只有一个Class
对象),这个对象就包含了完整的类的结构信息。这个对象就像一面镜子。透过这个镜子看到类的结构,故称为反射 -
反射可以实现动态创建对象和编译的功能,使程序具有很大的灵活性
-
反射对性能会有影响,反射是一种解释操作,相当于告诉JVM程序员希望做什么,然后JVM满足程序员的要求。这种操作总是慢于直接执行相同的操作
-
反射相关的主要API:
java.lang.Class
:代表一个类java.lang.reflect.Method
:代表类的方法java.lang.reflect.Field
:代表类的成员变量java.lang.reflect.Constructor
:代表类的构造器- ……
3 Class
类
-
Class
对象只能由系统创建对象 -
一个加载的类在JVM中只会有一个
Class
实例 -
一个
Class
对象对应一个加载到JVM中的.class文件 -
每个类的实例都会记得自己是由哪个
Class
实例所生成 -
通过
Class
可以完整地得到一个类中所有被加载的结构 -
Class
类是反射的根源,针对任何想要动态加载、运行的类,必须先获得相应的Class
对象 -
Class
类的常用方法:方法名 功能说明 static Class forName(String name)
返回指定类名name的 Class
对象Object newInstance()
调用默认构造函数,返回 Class
对象的一个实例getName()
返回此 Class
对象所表示的实体(类,接口,数组类或void
)的名称Class getSuperClass()
返回当前 Class
对象父类的Class
对象Class[] getInterfaces()
获取当前 Class
对象的接口ClassLoader getClassLoader()
返回该类的类加载器 Constructor[] getConstructors()
返回一个包含 Constructor
对象的数组Method getMethod(String name, Class<?>... T)
返回一个 Method
对象Field[] getDeclaredFields()
返回 Field
对象的一个数组
4 获取Class
类的实例
-
若已知具体的类,通过类的
class
属性获取,此方法最为安全可靠,程序性能最高Class clazz = Person.class;
-
已知某个类的实例,调用该实例的
getClass()
方法获取Class
对象Class clazz = person.getClass();
-
已知一个类的全类名,且该类在类路径下,可通过
Class
类的静态方法forName()
获取,可能会抛出ClassNotFoundException
异常Class clazz = Class.forName("com.kayne.Person");
-
内置基本数据类型可以直接用
类名.Type
-
使用
ClassLoader
-
public class TestGetClass { public static void main(String[] args) throws ClassNotFoundException { Animal animal = new Dog(); System.out.println("这个动物是:" + animal.name); // 通过对象获得 Class c1 = animal.getClass(); // forName()获得 Class c2 = Class.forName("com.kayne.Animal"); // 通过类名.class Class c3 = Dog.class; // 基本类型的包装类可以由Type属性获得Class对象 Class c4 = Integer.TYPE; // 获得父类类型 Class c5 = c1.getSuperclass(); } } class Animal { String name; } class Dog extends Animal { public Dog() { this.name = "狗"; } }
5 所有类型的Class
对象
外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类;接口;数组;枚举;注解;基本数据类型和void
都有Class
对象
public static void main(String[] args) {
System.out.println(Object.class); // 类
System.out.println(Comparable.class); // 接口
System.out.println(String[].class); // 一维数组
System.out.println(int[][].class); // 二维数组
System.out.println(Override.class); // 注解
System.out.println(ElementType.class); // 枚举
System.out.println(Integer.class); // 基本数据类型
System.out.println(void.class); // void
System.out.println(Class.class); // Class
}
输出结果为:
class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
class java.lang.Integer
void
class java.lang.Class
6 类加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过一下三个步骤对类进行初始化
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后生成一个代表这个类的
java.lang.Class
对象 - 链接:将Java类的二进制代码合并到JVM的运行状态之中
- 验证:确保加载的类信息符合JVM规范,没有安全问题
- 准备:正式为类变量(
static
)分配内存并设置类变量默认初始值,这些内存都在方法区中分配 - 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
- 初始化:
- 执行类构造器
<clinit>()
方法的过程、类构造器<clinit>()
方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器构造类信息,不是构造该类对象) - 初始化一个类时,如果其父类还没有进行初始化,需要先初始化其父类
- 虚拟机会保证一个类的
<clinit>()
方法在多线程环境中被正确加锁和同步
- 执行类构造器
7 类初始化
- 类的主动引用(一定会发生类的初始化)
- JVM启动时,先初始化
main()
方法所在的类 new
一个类的对象- 调用类的静态成员(除了
final
常量)和静态方法 - 使用
java.lang.reflect
包的方法对类进行反射调用 - 初始化一个类,如果其父类没有被初始化,则先初始化父类
- JVM启动时,先初始化
- 类的被动引用(不会发生类的初始化)
- 访问一个静态域,只有真正声明这个域的类才会被初始化。如当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就已经存入调用类的常量池中了)
8 类加载器
-
类缓存:标准的JavaSE类加载器可以按要求查找类,一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些
Class
对象 -
类加载器是用来把类装载进内存的,JVM规范定义了如下类型的类加载器
- 引导类加载器(Bootstrap ClassLoader):使用C++编写,是JVM自带的类加载器,负责Java平台核心库,用于装载核心类库(rt.jar)。该加载器无法直接获取
- 拓展类加载器(Extension ClassLoader):负责将jre/lib/ext目录下的jar包或
-D java.ext.dirs
指定目录下的jar包装入工作库 - 系统类加载器(System ClassLoader):负责将
java -classpath
或-D java.class.path
指定的目录下的类与jar包装入工作库,是最常用的加载器
-
public class TestLoader { public static void main(String[] args) { // 获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); // sun.misc.Launcher$AppClassLoader@18b4aac2 // 获取系统类加载器的父类加载器:拓展类加载器 ClassLoader extensionClassLoader = systemClassLoader.getParent(); System.out.println(extensionClassLoader); // sun.misc.Launcher$ExtClassLoader@1b6d3586 // 获取拓展类加载器的父类加载器:启动类加载器 ClassLoader bootstrapClassLoader = extensionClassLoader.getParent(); System.out.println(bootstrapClassLoader); // null // 启动类加载器无法直接读取 } }
-
类加载器除了实现类的加载动作外,还可以和一个类本身共同确定其在JVM中的唯一性。两个类是否“相等”的前提是这两个类是由同一个类加载器加载,否则即使两个类来源于同一个.class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类就必定不相等
9 双亲委派机制
-
工作原理:
- 当某个类加载器需要加载某个.class文件时,它并不会自己先加载,而是把这个任务委托给它的上级类加载器
- 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器
- 如果父类加载器可以完成类加载任务,就成功返回,否则子加载器尝试自己去加载
- 如果加载任务最终无法被类加载器加载,则抛出异常
-
双亲委派机制可以保证JVM提供的核心类不被篡改,从而保证类执行安全。例如
String
类,无论哪个加载器要加载这个类,由于双亲委派机制最终都会由最顶层的启动类加载器来加载,保证了String
类在不同的类加载器环境下都是同一个类 -
双亲委派可以防止重复加载同一个类。双亲委派中向上询问的过程中,如果加载过就不会再加载了
10 获取类的运行时结构
public class TestGetStructure {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
User user = new User();
Class<?> clazz = user.getClass();
// 获得类名
System.out.println(clazz.getName()); // 包名 + 类名
System.out.println(clazz.getSimpleName()); // 类名
// 获得类的属性
Field[] fields = clazz.getFields(); // 只能获得public属性
for (Field field : fields) {
System.out.println(field);
}
fields = clazz.getDeclaredFields(); // 可以获得类的所有属性
for (Field field : fields) {
System.out.println(field);
}
// 获得指定属性的值
Field name = clazz.getDeclaredField("name");
System.out.println(name);
// 获得类的方法
Method[] methods = clazz.getMethods(); // 获得本类及其父类的全部public方法
for (Method method : methods) {
System.out.println(method);
}
methods = clazz.getDeclaredMethods(); // 获得本类的所有方法
for (Method method : methods) {
System.out.println(method);
}
// 获得指定方法
// 参数为函数名和参数类型的Class
Method getName = clazz.getDeclaredMethod("getName", null);
Method setName = clazz.getDeclaredMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
// 获得类的构造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
constructors = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
// 获得指定构造器
// 参数为构造器参数类型的Class
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class, int.class);
System.out.println(constructor);
}
}
// 实体类,pojo
class User {
private String name;
private int id;
private int age;
public User() {}
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
private void test() {}
}
11 动态创建和操作对象
-
通过
Class
对象的newInstance()
方法可以创建类的对象,其中类必须有一个无参构造器,且类的构造器访问权限需要足够public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Class<?> clazz = Class.forName("User"); // 构造一个对象 User user = (User) clazz.newInstance(); // 本质是调用了类的无参构造器 System.out.println(user); }
-
如果没有无参构造器,需要在操作时明确调用类中构造器,并将参数传递进去
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Class<?> clazz = Class.forName("User"); // 明确获取有参构造器 Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class, int.class); // 输入有参构造器的参数创建对象 User user = (User) constructor.newInstance("Kayne", 001, 18); System.out.println(user); }
-
通过反射可以调用方法,并通过
Object invoke(Object obj, Object... args)
方法激活对象的方法public static void main(String[] args) throws Exception { Class<?> clazz = Class.forName("User"); User user = (User) clazz.newInstance(); // 通过反射获取一个方法 Method setName = clazz.getDeclaredMethod("setName", String.class); // invoke(对象, 方法的参数) setName.invoke(user, "Kayne"); System.out.println(user); }
Object
对应原方法的返回值,若原方法无返回值,则返回null- 若原方法为静态方法,此时形参
Object obj
可为null - 若原方法形参列表为空,则
Object... args
为null - 若原方法声明为
private
,在调用invoke()
方法前,需要显式调用方法对象的setAccessible(true)
方法
-
通过反射操作属性,默认不能操作
private
修饰的属性,但可以通过setAccessible()
方法设置可访问性,设置为true表示可访问(相当于关闭程序安全监测)public static void main(String[] args) throws Exception { Class<?> clazz = Class.forName("User"); User user = (User) clazz.newInstance(); Field name = clazz.getDeclaredField("name"); // 不能直接操作私有属性,需要关闭程序安全监测 name.setAccessible(true); name.set(user, "Kayne"); System.out.println(user.getName()); }
-
setAccessible()
方法:Method
、Field
和Constructor
对象都有setAccessible()
方法,参数默认为false- 参数为true时指示反射对象在使用时取消Java语言访问检查
- 该方法可以使得原本无法访问的私有成员也可以访问
- 该方法可以提高反射的效率,如果代码中必须用反射,请设置为true
12 反射性能分析
public class PerformanceAnalysis {
// 普通方式调用
public void test01() {
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式调用10亿次:" + (endTime - startTime) + "ms");
}
// 反射方式调用
public void test02(boolean flag) throws Exception {
Class<?> clazz = Class.forName("com.kayne.SE.User");
User user = (User) clazz.newInstance();
Method getName = clazz.getDeclaredMethod("getName");
long startTime = System.currentTimeMillis();
getName.setAccessible(flag);
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user);
}
long endTime = System.currentTimeMillis();
if (!flag) {
System.out.print("反射方式调用10亿次, 开启访问检查:");
} else {
System.out.print("反射方式调用10亿次, 关闭访问检查:");
}
System.out.println((endTime - startTime) + "ms");
}
public static void main(String[] args) throws Exception {
PerformanceAnalysis pa = new PerformanceAnalysis();
pa.test01();;
pa.test02(false);
pa.test02(true);
}
}
输出结果:
普通方式调用10亿次:2ms
反射方式调用10亿次, 开启访问检查:1845ms
反射方式调用10亿次, 关闭访问检查:1032ms
可以发现,发射方式调用效率大大低于普通方式调用。如果一定要使用反射,关闭访问检查可以提升效率
13 反射操作泛型
-
Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题。一旦编译完成,所有和泛型有关的类型全部擦除
-
为了通过反射操作泛型,Java新增了
ParameterizedType
,GenericArrayType
,TypeVariable
和WildcardType
几种类型来代表不能被归一化到Class
类中但又和原始类型齐名的类型ParameterizedType
:表示一种参数化类型(泛型),例如Collection<String>
GenericArrayType
:表示一种元素类型是参数化类型或类型变量的数组类型TypeVariable
:是各种类型变量的公共父接口WildcardType
:代表一种通配符类型表达式
-
public class ReflectGeneric { public void test01(Map<String, User> map, List<User> list) { System.out.println("test01"); } public Map<String, User> test02() { System.out.println("test02"); return null; } public static void main(String[] args) throws NoSuchMethodException { Method test01 = ReflectGeneric.class.getDeclaredMethod("test01", Map.class, List.class); Method test02 = ReflectGeneric.class.getDeclaredMethod("test02"); // 获取参数类型 Type[] genericParameterTypes = test01.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes) { System.out.println("#" + genericParameterType); // 如果是泛型 if (genericParameterType instanceof ParameterizedType) { // 获取真实类型 Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument); } } } System.out.println("-----------------------"); // 获取返回值类型 Type genericReturnType = test02.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.kayne.SE.User> class java.lang.String class com.kayne.SE.User #java.util.List<com.kayne.SE.User> class com.kayne.SE.User ----------------------- #java.util.Map<java.lang.String, com.kayne.SE.User> class java.lang.String class com.kayne.SE.User
14 ORM
-
ORM指Object Relationship Mapping,对象关系映射
-
其中类和表结构对应,属性和字段对应,对象和记录对应
15 反射操作注解
public class ReflectAnnotation {
public static void main(String[] args) throws NoSuchFieldException {
Class<?> clazz = Student.class;
// 通过反射获取注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 获得注解的value值
Table table = clazz.getAnnotation(Table.class);
System.out.println(table.value());
// 获得类指定注解
Field field = clazz.getDeclaredField("name");
Attribute annotation = field.getAnnotation(Attribute.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@Table("db_student")
class Student {
@Attribute(columnName = "db_id", type = "int", length = 10)
private int id;
@Attribute(columnName = "db_name", type = "varchar", length = 3)
private String name;
@Attribute(columnName = "db_age", type = "int", length = 10)
private int age;
public Student() {}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
// 类注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
String value();
}
// 属性注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Attribute {
String columnName();
String type();
int length();
}
输出结果:
@com.kayne.SE.Table(value=db_student)
db_student
db_name
varchar
3