Java反射篇
动态语言与静态语言的区别?
动态语言:在运行时代码可以根据某些条件改变自身结构。举例:JavaScript, PHP, Python。
静态语言:运行时结构不可变的语言就是静态语言。举例:Java, C, C++。
说明:由于反射机制的存在,Java可以称为“准动态语言”。
1.反射的定义
Java 反射,就是在运行状态中:
获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等;
获取任意对象的属性,并且能改变对象的属性;
调用任意对象的方法;
判断任意一个对象所属的类;
实例化任意一个类的对象。
2.反射的优缺点
优点:
可以实现动态创建对象和编译,体现出很大的灵活性
缺点:
对性能有影响。
3.Class类的创建方式
- 通过对象的getClass()。
- 类名.class。
- Class.forName("")。
package com.zx.reflection;
//测试Class类的创建方式有哪些
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("这个人是:"+person.name);
//方式一:通过对象获得
Class c1 = person.getClass();
System.out.println(c1.hashCode());
//方式二:Class.forName()
Class c2 = Class.forName("src下的包名.Student");//类似JDBC的加载驱动
System.out.println(c2.hashCode());
//方式三:类名.class
Class c3 = Student.class;
System.out.println(c3.hashCode());
}
}
class Person{
public String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '}';
}
}
class Student extends Person{
public Student(){
this.name = "学生";
}
}
class Teacher extends Person{
public Teacher(){
this.name = "老师";
}
}
注意:
c1, c2, c3的hashCode()都一样,说明加载的类在JVM中只有一个Class实例。
4.Class对象适用的类型
创建形式 | 类型 |
---|---|
Object.class | Object类 |
Comparable.class | 接口 |
String[].class | 一维数组 |
int[][].class | 二维数组 |
Override.class | 注解 |
ElementType.class | 枚举 |
Integer.class | 基本数据类型 |
void.class | void |
Class.class | Class本身 |
5.类的加载过程
类的加载-->类的链接-->类的初始化
类的加载:
将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成。
类的链接:
将类的二进制数据合并到JRE中。
类的初始化:
JVM负责对类进行初始化。
6.类的初始化的条件
类的主动引用一定会发生类的初始化:
- 虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员和静态方法
- 对类进行反射调用
- 若父类没被初始化,会先初始化父类
类的被动引用不会发生类的初始化:
- 通过子类调父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化
/**
* 测试类什么时候初始化
*/
public class Test {
static {
System.out.println("Main被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//主动引用 优先级:Main>父类>子类
//Son son = new Son();
//反射 Main>父类>子类
//Class c1 = Class.forName("包名.Son");
//子类调父类静态变量,子类不会被加载
//System.out.println(Son.b);
//只有Main类被加载
//Son[] arrays = new Son[5];
//只有Main类被加载,子类中final修饰的常量被调用子类不会被加载
// System.out.println(Son.M);
}
}
class Father{
static int b = 2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
m = 300;
}
static int m = 100;
static final int M = 1;
}
7.如何获得要加载类的信息
- 首先创建一个Class对象,用上面提到的三种方式的一种。假如该对象为c1。
- 用c1调方法。
方法 | 说明 |
---|---|
c1.getName() | 获得包名+类名 |
c1.getSimpleName() | 获得类名 |
c1.getFields() | 获得public修饰的属性 |
c1.getDeclaredFields() | 获得所有属性(public,private…) |
c1.getDeclaredField("") | 获得指定属性的value |
c1.getMethods() | 获得本类和父类被public修饰的方法 |
c1.getDeclaredMethods() | 获得本类的所有方法包括私有 |
c1.getMethod(“方法名”,参数类型 ) | 获得指定的方法 |
c1.newInstance() | 获取实例(本质是调用了无参构造器) |
8.执行效率比较
public class Test{
public static void main(String[] args) throws Exception{
test01();
test02();
test03();
}
public static void test01(){
User user = new User();
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long end = System.currentTimeMillis();
System.out.println("普通方法:"+(end-begin)+"ms");
}
public static void test02() throws Exception {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user, null);//激活(执行)该方法
}
long end = System.currentTimeMillis();
System.out.println("反射方式:"+(end-begin)+"ms");
}
public static void test03() throws Exception {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
getName.setAccessible(true);//关闭访问安全检查
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user, null);//激活(执行)该方法
}
long end = System.currentTimeMillis();
System.out.println("反射方式(关闭访问安全检查):"+(end-begin)+"ms");
}
}
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 +
'}';
}
}
结论:
普通方法 > 反射(关闭Java访问安全检查) > 反射
setAccessible(true)
: 不论是属性还是方法都有该方法,关闭Java访问安全检查,提高反射效率
9.Java内置的三个加载器
- 引导类加载器
- 扩展类加载器
- 应用程序加载器
双亲委派机制:
通过以上三个加载器类之间的互相调用来实现的。
双亲委派机制大致内容:
在加载区域类三找要加载的类,优先级为应用程序加载器 > 扩展类加载器 > 引导类加载器,若均没找到,在类路径下三找要加载的类,优先级为引导类加载器 > 扩展类加载器 > 应用程序加载器,若均没找到,则报异常。
优点:可以防止核心API被篡改;避免类被重复加载。
如何打破:通过自定义加载器,重写ClassLoader类中的loadClass方法来打破双亲委派机制。
10.反射操作泛型
//通过反射操作泛型
public class Test {
public static void main(String[] args) throws Exception {
Method method = Test.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.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);
}
}
}
method = Test.class.getMethod("test02", null);
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
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;
}
}
11.反射操作注解
//反射操作注解
public class Test {
public static void main(String[] args) throws Exception{
Class c1 = Class.forName("包名.Student");
//通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的value的值
Tabletest tabletest = (Tabletest)c1.getAnnotation(Tabletest.class);
String value = tabletest.value();
System.out.println(value);
//获得类指定字段的注解的value的值
Field f = c1.getDeclaredField("name");
Filedtest annotation = f.getAnnotation(Filedtest.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@Tabletest("db_student")
class Student{
@Filedtest(columnName = "db_id",type = "int",length = 10)
private int id;
@Filedtest(columnName = "db_age",type = "int",length = 10)
private int age;
@Filedtest(columnName = "db_name",type = "varchar",length = 3)
private String name;
public Student() {
}
public Student(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 "Student{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Tabletest{
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Filedtest{
String columnName();
String type();
int length();
}