java反射机制
一.概念
自己理解:java反射机制可通过类的全路径名对类进行动态加载实例化,获取类信息及类里面的成员变量,方法,改造方法等;加载完类之后,在堆中就产生了一个Clas类型的对象(一个类只有一个对象),这个对象包含了类的完整结构信息,通过这个对象可以得到类的结构(方法,构造方法,属性,结构)。
网上解释:
(1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
(2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
二.使用
1.在运行时判断任意一个对象所属的类
2.在运行时构造任意一个类的对象
3.在运行时得到任意一个类所具有的成员变量和方法
4.在运行时调用任意一个对象的成员变量和方法
5.生成动态代理
//1.使用Properties
Properties properties = new Properties();
properties.load(new BufferedInputStream(new FileInputStream("src/main/resources/test.properties")));
String classPath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();
System.out.println(classPath);
System.out.println(methodName);
//2.使用反射机制解决
//(1)加载类,返回class类型的对象cls
Class cls = Class.forName(classPath);
//1.9之后不能用了,因为只能调用无参构造,没办法构
Object objPerson1 =instanceObj.newInstance();
//1.9之后采用这个来实例化对象
//(2)通过cls得到你加载的类的对象实例
Object o=cls.getDeclaredConstructor().newInstance();
System.out.println("o的运行类型=" + o.getClass());
//(3)通过cls得到你加载的类里面的方法
//即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4)通过method1调用方法:即通过方法对象来实现调用方法
method1.invoke(o);//传统方法,对象.方法(),反射机制:方法.invoke(对象)
Field nameField= cls.getField("age");
System.out.println("nameField"+nameField.get(o));
Constructor constructors = cls.getConstructor();
System.out.println("constructors"+constructors);
三.反射相关类
java.lang.refiect
java.lang.Class:代表一个类
java.lang.refiect.Method:代表类的方法
java.lang.refiect.Field:代表类的成员变量
java.lang.refiect.Constructors:代表类的构造方法
//根据类路劲获取类信息
Class clazz = Class.forName("com.example.content.test.Student");
//实例化对象
Object o = clazz.getConstructor().newInstance();
//获取方法
Method hi = clazz.getMethod("hi");
//获取构造方法
Constructor constructor = clazz.getConstructor();
//获取属性(属性需要是public的才可以,不是会提示Exception in thread "main" java.lang.NoSuchFieldException)
Field nameField = clazz.getField("name");
System.out.println("获取方法="+hi);
System.out.println("获取构造方法="+constructor);
System.out.println("获取属性="+nameField);
结果:
获取方法=public void com.example.content.test.Student.hi()
获取构造方法=public com.example.content.test.Student()
获取属性=public java.lang.String com.example.content.test.Student.name
四.反射的优点和缺点
缺点:耗时,效率慢
public static void main(String[] args) throws Exception {
m1();
m2();
m3();
}
//创痛方法调用hi
public static void m1() {
Student student = new Student();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
student.hi();
}
long end = System.currentTimeMillis();
System.out.println("传统方法耗时" + (end - start));
}
//创痛方法调用hi
public static void m2() throws Exception {
Class clazz = Class.forName("com.example.content.test.Student");
Object cls = clazz.getConstructor().newInstance();
Method method = clazz.getMethod("hi");
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
method.invoke(cls);
}
long end = System.currentTimeMillis();
System.out.println("反射方法耗时" + (end - start));
}
//创痛方法调用hi
public static void m3() throws Exception {
Class clazz = Class.forName("com.example.content.test.Student");
Object cls = clazz.getConstructor().newInstance();
Method method = clazz.getMethod("hi");
method.setAccessible(true);
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
method.invoke(cls);
}
long end = System.currentTimeMillis();
System.out.println("反射优化方法耗时" + (end - start));
}
输出:
传统方法耗时3
反射方法耗时52
反射优化方法耗时20
反射调用优化-关闭访问监测(能提升一些,一定程度上优化性能)
具体方法:AccessibleObject
Method method = clazz.getMethod("hi"); method.setAccessible(true);
五.Class类分析
1.class也是类,继承object类
2.创建类
(1)传统方式:
/**
* public Class<?> loadClass(String name) throws ClassNotFoundException {
* return loadClass(name, false);
* }
*/
Student student=new Student();
(2)java反射:
Class aClass = Class.forName(“com.example.content.test.Student”);
对于某个类的class类对象,在内存中只存在一份,因此类只加载一次
(3)每个类的实例都知道他是由哪个class实例所生成
(4)通过class对象可以完整地得到一个类的完整结构,通过一系列api
(5)class对象放在堆空间
(6)类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法名,变量等等)
五.Class类的常用方法
//通过类路径获取类
Class aClass = Class.forName("com.example.content.test.Student");
System.out.println(aClass);//显示cla对象,是哪个类的Class对象
System.out.println(aClass.getClass());//运行类型
//得到包名
Package aPackage = aClass.getPackage();
System.out.println(aPackage);
//得到全类名
String className = aClass.getName();
System.out.println(className);
//创建对象实例
Student student = (Student) aClass.getConstructor().newInstance();
System.out.println(student);
//得到属性
Field nameField = aClass.getField("name");
System.out.println(nameField.get(student));
//设置属性
nameField.set(student, "萌萌猫");
System.out.println(nameField.get(student));
//得到所有属性
Field[] fields = aClass.getFields();
System.out.println(fields);
六.获取类对象的几种方式:
(1)Class aClass = Class.forName(“com.example.content.test.Student”);
前提:已知类的全路径,且该类在类路径下,通过此方法获取
应用场景:多用于配置文件,读取类全路径,加载类
(2) Class aClass1 = Student.class();
前提:已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高
应用场景:多用于参数传递,比如通过反射得到对应构造器对象
(3) Class aClass1 = student.getClass();
//通过类文件
Class aClass = Class.forName("com.example.content.test.Student");
//类名.class
Class student=Student.class;
//类名.class
Student student1=new Student();
Class aClass1=student1.getClass();
//类加载器
ClassLoader classLoader = student.getClassLoader();//得到类加载器
Class<?> aClass2 = classLoader.loadClass("com.example.content.test.Student");//通过类加载器得到class对象
System.out.println("aClass="+aClass);
System.out.println("student="+student);
System.out.println("aClass1="+aClass1);
System.out.println("aClass2="+aClass2);
运行结果:
aClass=class com.example.content.test.Student
student=class com.example.content.test.Student
aClass1=class com.example.content.test.Student
aClass2=class com.example.content.test.Student
六.Java中类变量(静态变量)和实例变量区别
成员变量:把类内、方法体外定义的变量称为成员变量。
Java中的成员变量分为两种:
一是没有static修饰的,这些成员变量是对象中的成员,称为实例变量。
二是有static修饰的,称为类变量(静态变量)。
类变量和实例变量的区别是:
类变量在内存中只存一份,在程序运行时,系统只为类变量分配一次内存,只进行一次的初始化。在加载类的过程中完成类变量的内存分配。
类变量可以通过类名访问。
实例变量是属于对象中的成员,每创建一个类的对象,就会为实例变量分配一次内存,实例变量在内存中有多个拷贝,分别属于不同对象,他们之间互不影响。
示例:
public class scope {
static int a;
int b;
public static void main(String[] args) {
// TODO 自动生成的方法存根
a++;
scope s1 = new scope();
s1.a++;
s1.b++;
scope s2 = new scope();
s2.a++;
s2.b++;
scope.a++;
System.out.println("a="+a);
System.out.println("s1.a="+s1.a);
System.out.println("s2.a="+s2.a);
System.out.println("s1.b="+s1.b);
System.out.println("s2.b="+s2.b);
}
}
运行结果为:
a=4
s1.a=4
s2.a=4
s1.b=1
s2.b=1
a是类变量,b是实例变量。a在内存中只有一份,a可以通过类名访问,也可以通过对象名访问。无论哪种访问形式都是对同一个a进行操作,所以连续加1后,a的值为4.
而对于b,每次创建一个对象,内存中就有一份b的空间,在这里对象s1和s2中分别有一个变量b,对他们操作需要通过不同的对象名,所以对他们的操作互不影响。在分别加1后,都为1。
七.类加载:
(1) 加载过程:加载-连接(验证,准备,解析)-初始化
验证:(验证代码是否jvm的规范,文件格式是否正确,元数据验证,字节码验证,符号引用验证)
准备(为类中定义的变量,默认初始化并分配空间,即静态变量分配内存并设置变量的初始值,通常情况为该数据类型的“零值”。)
代码示例:
class a{
//属性-成员变量-字段 类加载的链接阶段-准备 属性是如何处理
//1.a是实例尚需经,不是静态变量,因此在准备阶段,不会分配内存
//2.b是静态变量,在此会分配内存并赋该数据类型上午默认初始化值0,而不是1
//3.c是static final 是常量,是不可改变的,一旦赋值就不会发生变化,此处赋值1
public int a=1;
//会为该属性分配内存,并赋初始化值0,1放在初始化中
public static int b=1; //b=0
public final static int c=1;// c=1
}
解析(解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,此过程伴随着第二阶段验证中符号引用验证,放到jre中,类处于可运行的状态);
初始化(真正执行类中定义的java程序代码)
代码示例:
(2)类加载的两种形式
静态加载:依赖性太强,编译时加载相关的类,如果没有则报错
动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,依赖性不强
应用实例:
public static void main(String[] args) throws Exception {
Scanner scanner=new Scanner(System.in);
String key = scanner.next();
switch (key){
case "1":
Student student=new Student();//静态加载(该类必须存在,不然此处声明时会报错,无法成功编译)
student.hi();
break;
case "2":
//动态加载(当程序执行到这里的时候才会去检测并编译,不会影响编译结果)
Class aClass = Class.forName("com.example.content.test.app");
Object o = aClass.getConstructor().newInstance();
Method method = aClass.getMethod("hi");
method.invoke(o);
default:
System.out.println("************");
}
}
运行结果:
2
Exception in thread "main" java.lang.ClassNotFoundException: com.example.content.test.app
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:315)
at com.example.content.test.Refiection06.main(Refiection06.java:27)
1
Process finished with exit code 0
总结:
动态加载:程序在运行时调用相应方法,即使其他方法是错误的,程序依旧会执行。通过动态加载可以让程序的可延长行大大提升,对以后的维护和扩展有重要意义。
静态加载:程序在编译时执行。在执行过程中加载所有可能执行到的程序。在这种加载方式下,只要加载中一个方法出错,程序就不能运行。我们一般写程序默认的是静态加载。
八.通过反射创建对象
1.方式一:调用类中的public修饰的无参构造器
2.方式二:调用类中的指定构造器
public class Refiection09 {
public static void main(String[] args) throws Exception {
//1.先获取user类的class对象
Class aClass = Class.forName("com.example.content.test.user");
//2.通过public的无参构造器创建实例
Object o = aClass.getConstructor().newInstance();
System.out.println(o);
//3.通过public的有参构造器创建实例
Object o1 = aClass.getConstructor(String.class).newInstance("李四");
System.out.println(o1);
//4.通过非public的有参构造器创建实例
/* Exception in thread "main" java.lang.NoSuchMethodException: com.example.content.test.user.<init>(int, java.lang.String)*/
//Object o3 = aClass.getConstructor(int.class, String.class).newInstance(5, "王麻子");由于该方法是私有构造方法,会报上面的异常
Constructor declaredConstructor = aClass.getDeclaredConstructor(int.class, String.class);//他可以获取所有构造方法
System.out.println(declaredConstructor);
declaredConstructor.setAccessible(true);//暴破【暴力破解】,使用反射可以访问private构造器
Object o3 = declaredConstructor.newInstance(5, "王麻子");
System.out.println(o3);
}
}
class user {
private int age = 1;
private String name = "张三";
public user() {
}
public user(String name) {
this.name = name;
}
private user(int age, String name) {
this.age = age;
tuser[age=1,name=张三}
user[age=1,name=李四}
private com.example.content.test.user(int,java.lang.String)
user[age=5,name=王麻子}his.name = name;
}
public String toString() {
return "user[age=" + age + ",name=" + name + "}";
}
}
运行结果:
user[age=1,name=张三}
user[age=1,name=李四}
private com.example.content.test.empoless(int,java.lang.String)
user[age=5,name=王麻子}
九.通过反射操作属性和方法
代码举例:
public class Refiection10 {
public static void main(String[] args) throws Exception {
//1.先获取user类的class对象
Class aClass = Class.forName("com.example.content.test.User");
//2.通过public的无参构造器创建实例
Object o = aClass.getDeclaredConstructor().newInstance();
System.out.println(o);
/* Exception in thread "main" java.lang.NoSuchFieldException: age
at java.base/java.lang.Class.getField(Class.java:1999)
at com.example.content.test.Refiection10.main(Refiection10.java:19)*/
// Field age = aClass.getField("age");
// 注意:当属性为私有的时候,需要暴破,如果只是普通属性,可以直接getField
Field age = aClass.getDeclaredField("age");
age.setAccessible(true);//获取私有属性字段之后还需要进行暴力破解,设置取消访问检查
age.set(o, 15);
Field name = aClass.getDeclaredField("name");
name.setAccessible(true);
name.set(o, "小明");
/*Exception in thread "main" java.lang.NoSuchMethodException: com.example.content.test.User.a()
at java.base/java.lang.Class.getMethod(Class.java:2108)
at com.example.content.test.Refiection10.main(Refiection10.java:34)*/
Method a = aClass.getMethod("a", int.class);//此处需要指定参数类型
a.invoke(o, 11);
Method b = aClass.getDeclaredMethod("b", int.class);//此处需要指定参数类型
b.setAccessible(true);
b.invoke(o, 22);
Method c = aClass.getDeclaredMethod("c", int.class);//此处需要指定参数类型
c.setAccessible(true);
//静态方法的两种调用方式
c.invoke(o,33);
c.invoke(null,33);
System.out.println(o);
}
}
class User {
private int age;
private String name;
public User() {
}
public User(int age) {
this.age = age;
}
public User(String name) {
this.name = name;
}
private User(int age, String name) {
this.age = age;
this.name = name;
}
public String a(int a) {
System.out.println("调用公共方法" + a);
return "调用公共方法" + a;
}
private String b(int b) {
System.out.println("调用公共方法" + b);
return "调用私有方法" + b;
}
private static String c(int c) {
System.out.println("调用公共方法" + c);
return "调用私有静态方法" + c;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
运行结果:
User{age=0, name='null'}
调用公共方法11
调用公共方法22
调用公共方法33
调用公共方法33
User{age=15, name='小明'}
九.课后练习
代码:
public class TestRefiect {
public static void main(String[] args) throws Exception {
//1.获取类路径
Class<?> aClass = Class.forName("com.example.content.test.PrivateTest");
//2.运行实例化
Object o = aClass.getDeclaredConstructor().newInstance();
//3.获取属性
Field name = aClass.getDeclaredField("name");
name.setAccessible(true);//需要暴破
//4.获取方法
Method nameMethod = aClass.getMethod("getName");
//执行方法
nameMethod.invoke(o);
//4.获取属性值
System.out.println(name.get(o));
//5.设置值
name.set(o, "kitty");
nameMethod.invoke(o);
System.out.println(name.get(o));
}
}
class PrivateTest {
private String name = "hellokitty";
public String getName() {
System.out.println("方法执行打印=:" + name);
return name;
}
}
运行结果:
方法执行打印=:hellokitty
hellokitty
方法执行打印=:kitty
kitty
package com.example.demo.test;/**
* @Auther: 12855
* @Date: 2022/3/21 14:30
* @Description:
*/
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @Auther: 12855
* @Date: 2022/3/21 14:30
* @Description:
*/
public class Study {
public static void main(String[] args) throws Exception {
Class aClass = Class.forName("java.io.File");
Constructor[] declaredConstructors = aClass.getDeclaredConstructors();
for (int i = 0; i < declaredConstructors.length; i++) {
System.out.println("declaredConstructor=" + declaredConstructors[i]);
}
//获取实例对象
//得到指定的构造器
Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class);
//设置参数
String path = "d:\\aa.txt";
//获取实例对象
Object o = declaredConstructor.newInstance(path);
//找出对应方法
Method createNewFile = aClass.getMethod("createNewFile");
//执行方法
createNewFile.invoke(o);
File file = new File("d:\\bb.txt");
file.createNewFile();
}
}
运行结果:
declaredConstructor=public java.io.File(java.lang.String)
declaredConstructor=public java.io.File(java.lang.String,java.lang.String)
declaredConstructor=public java.io.File(java.io.File,java.lang.String)
declaredConstructor=public java.io.File(java.net.URI)
declaredConstructor=private java.io.File(java.lang.String,java.io.File)
declaredConstructor=private java.io.File(java.lang.String,int)
九.注解
元注解:@Target,Retention,@Document,@Inherited
@Target() :描述注解的使用范围
@Retention:就是我们需要告诉编译器我们需要在什么级别保存该注释信息,用于描述注解的生命周期。
@Document:
@Inherited
常用的内置注解:
@Override:修辞方法,表示打算重写超类中的方法声明。
@Deprecated:这个注释可以修辞方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为他很危险或有更好的选择。
@SuperWarnings:这个注解主要是用来抑制警告信息的,我们在写程序时,可能会报很多黄线的警告,但是不影响运行,我们就可以用这个注解来抑制隐藏它。与前俩个注解不同的是我们必须给注解参数才能正确使用他。