目录
一个需求引出反射
请看下面问题
1. 根据配置文件re.properties指定信息,创建 Cat对象并调用方法hi
classfullpath = com.hspedu.Cat
method = hi
思考:不使用反射你能做到吗
2.这样的需求在学习框架是特别多,即通过外部文件配置,在不修改源码的情况下,来控制程序,应符合设计模式的ocp原则(开闭原则:不修改源码,扩容功能)
反射入门
我们尝试做一做上边问题-->先对反射有个大致了解在看下边反射的细节
1.创建re.properties配置文件
classfullpath = com.hspedu.Cat
method = hi
2.创建需要的Cat类
public class Cat {
public String name = "招财猫";
public int age =10;
public Cat() {
}
public Cat(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void hi(){
System.out.println("hi"+name);
}
}
3.根据配置文件re.properties指定信息,创建 Cat对象并调用方法hi
传统方法:可以创建 Cat对象并调用方法hi但没有通过配置文件
Cat cat = new Cat();
cat.hi();
反射方法
public static void main(String[] args) throws Exception {
//1.使用properties类,可以读写配置文件
Properties properties = new Properties();
//load()读取配置文件键值对内容 FileInputStream字节流的读取
properties.load(new FileInputStream("src\\re.properties"));
//get(键):获取配置文件键对应的值
String classfullpath = properties.get("classfullpath").toString();
//classfullpath拿到了com.hspedu.Cat
String methodName = properties.get("method").toString();//hi
System.out.println("classfullpath "+classfullpath);//com.hspedu.Cat
System.out.println("method "+methodName);//hi
//2.创建对象,此时传统的方法肯定不行
//new classfullpath();classfullpath是String类型,不是类的全路径,new后便不可能跟一个字符串而是应该跟一个类名
// Cat cat2 = new com.hspedu.Cat();//一样原理
//3.用反射解决
//(1)加载类:返回class类对象,得到类的全路径而不是字符串
Class cls = Class.forName(classfullpath);
//通过cls得到你加载的类com.hspedu.Cat的对象实例
//newInstance()创建对象
Object o = cls.newInstance();
//通过cls得到你加载的类com.hspdu.Cat 的 methodName 的方法
//即在反射中可以把方法视为对象。
Method method1 = cls.getMethod(methodName);
//通过method1调用方法:即通过方法对象来实现调用方法
//.invoke()方法,用来执行某个的对象的目标方法。
method1.invoke(o);
//若有两个具体方法,在不修改源码的情况下获取另一个方法,只需修改配置文件里相应键所对应的值
//比如Cat类里还有一个具体方法cry只需将re.properties中method = hi改成method = cry即可
//而传统方法// Cat cat = new Cat();
// //cat.hi(); ----》cat.cry();此时已修改了源码
}
此时,大家可能对反射还是很模糊,没关系下边会对反射内容一一讲解
正片
反射原理图
反射机制
Java反射机制可以完成
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
Java反射相关的主要类
- java.lang.Class:代表一个类。Class对象表示摸个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法
-
//java.lang.reflect.Field:代表类的成员变量,Field对象表示的是某个类的成员变量 //得到age字段 //getFiled不能得到私有属性 Field nameFiled = cls.getField("age"); System.out.println(nameFiled.get(o));//传统写法:对象.成员变量 fanshe:成员变量.get(对象) //java.lang.reflect.Constructor:代表类的构造方法, Constructor对象表示构造器 Constructor constructor = cls.getConstructor(); System.out.println(constructor);//返回无参构造器public com.hspedu.Cat() //构造器是干嘛的:帮我们都在对象 //constructor.toString()所以就可以调用他的很多方法了 Constructor constructor2 = cls.getConstructor(String.class);//String.class:String类class对象 System.out.println(constructor2);//public com.hspedu.Cat(java.lang.String)
反射优点和缺点
优点:可以动态地创建和使用对象(也是框架底层的核心),使用灵活,没有反射机制,框架技术就失去底层支撑
缺点:使用反射基本是解释之星,对执行速度有影响
反射优化:关闭访问检查提高效率
Method和Filed、Constructor都有setAccessible()方法
setAccessible()方法作用是启动和禁用访问安全检查的开关
参数为true时表示反射对象在使用时取消访问检查,提高反射效率
案例:查看传统方法和反射调用的效率对比
public class Reflection1 {
public static void main(String[] args) throws Exception {
m1();
m2();
m3();
}
public static void m1(){
Cat cat = new Cat();
long start = System.currentTimeMillis();
for(int i=0;i<900000000;i++){
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("传统时间差"+(end-start));
}
//反射
public static void m2() throws Exception {
Class cls = Class.forName("com.hspedu.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
long start = System.currentTimeMillis();
for(int i=0;i<900000000;i++){
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射时间差"+(end-start));
}
//使用setAccessible(true),取消访问检查优化反射效率
public static void m3() throws Exception {
Class cls = Class.forName("com.hspedu.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
hi.setAccessible(true);//在反射调用方法时,取消访问检查
long start = System.currentTimeMillis();
for(int i=0;i<900000000;i++){
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射优化时间差"+(end-start));
}
}
Class类分析
Class01.java com.hspedu.reflection.class
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,而是系统创建的
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由那个Class实例所生成
- 通过Class可以完整地得到一个类的完整结构,通过一系列的API
- Class对象是存放在堆的
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据
Class常用方法
- Class.forName(类名):获得Class类对应的Class对象
- getClass():输出cls运行类型(也就是Class的类)
- getPackage().getName():获得包名
- getName():获得全类名
- newInstance():创建实例
- getField():获得属性
- set(实例对象,值):给属性赋值
getFields():获得所有属性
看具体代码
public class Class02 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classAllPath ="com.hspedu.Cat";
// 1.获取到car类对应的Class对象
// <?>表示不确定的java类型
Class<?> cls = Class.forName(classAllPath);
System.out.println(cls);//显示cls对象,是哪个类的Class对象 com.hspedu.Car
System.out.println(cls.getClass());//输出cls运行类型(也就是Class的类) java.lang.Class
// 得到包名
System.out.println(cls.getPackage().getName());//com.hspedu
// 得到全类名
System.out.println(cls.getName());//com.hspedu.Cat
// 通过cls创建实例
Cat car = (Cat) cls.newInstance();
System.out.println(car);//car.tostring:Cat{name='招财猫', age=10}
// 通过反射获取属性
Field age = cls.getField("age");//不能传private属性的值
System.out.println(age.get(car));//10
// 通过反射给属性赋值
age.set(car,11);
System.out.println(age.get(car));//11
// 希望获得所有属性
Field [] fields = cls.getFields();
for(Field f:fields){
System.out.println(f.getName());//name age 不能拿到private属性的值
}
}
获得Class类对象的六种方式
1.已知全类名,且该类在类路径下,可使用静态方法forName()
2.已知具体的类,通过类的Class获取,该方式最为安全可靠,程序性能最高
Class cls2 = 类名.class
3.对象.getClass():应用场景有对象实例
4.通过类加载器得到Class对象
5.基本数据(int char boolean float byte long short )按如下方式获得class对象
6. 基本数据类型对应的包装类,可以通过.TYPE得到class类对象
看具体代码
public class GetClass {
public static void main(String[] args) throws ClassNotFoundException {
// 1.Class.forName:
String classAllPath = "com.hspedu.Cat";
Class<?> cls1 = Class.forName(classAllPath);//通过读取配置文件获取
System.out.println(cls1);
// 2.类名.class 应用场景:用于参数传递
Class cls2 = Cat.class;
System.out.println(cls2);
// 3.对象.getClass():应用场景有对象实例
Cat cat = new Cat();
Class cls3 = cat.getClass();
System.out.println(cls3);
// 通过类加载器得到Class对象
// 先得到类加载器Cat
ClassLoader classLoader = cat.getClass().getClassLoader();
// 通过类加载器得到class对象
Class cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4 );
// 基本数据(int char boolean float byte long short )按如下方式获得class对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
System.out.println(integerClass);//int
// 基本数据类型对应的包装类,可以通过.TYPE得到class类对象
Class<Integer> type = Integer.TYPE;
System.out.println(type);//int
}
}
那些类型有Class对象
动态加载和静态加载
该程序报错
静态加载:Dog这个类不一定会用到(此时还未编写Dog类),但静态加载不管那么多,在编译时会加载这个类并进行语法校验
Person类因为是动态,所以编译时会通过只有在执行下边代码要用到这个类时检测到没有编写person类会报错
类加载五个阶段流程图
类加载五个阶段分析
二进制字节流加载到内存:在将某个类的字节码啊二进制数据加载到方法区,同时生成一个class类对象
验证:对文件进行安全校验,比如文件格式是否正确 源数据验证是否正确 字节码是否正确 符号引用是否OK
准备:对静态变量分配内存,并完成默认初始化
解析 虚拟机将会把常量池中的符号引用替换为直接引用。Jvm机完成进入内存不再靠符号链接而是靠地址链接
初始化:真正的执行在类中去定义的java代码,如静态代码块,静态代码赋值
public class Classload1 {
public static void main(String[] args) {
/*
* 1.加载B类,并生成B的class对象
* 2.连接num=0;
* 3.初始化阶段
* 依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句
* 收集 clinit(){
* System.out.println("B 静态代码块被执行");
num = 300;
num = 100;}
* 合并num =100;
* 4."B() 构造器被执行
*
* */
// new B();
System.out.println(B.num);//B 静态代码块被执行 100
//不执行构造器应为那个没有new构造对象,若new B();输出B 静态代码块被执行 B() 构造器被执行 100 "
}
}
class B{
static{
System.out.println("B 静态代码块被执行");
num = 300;
}
static int num = 100;
public B(){
System.out.println("B() 构造器被执行");
}
}
获取类的结构信息(方法)
所以说反射还是很强大的那一单拿到类的对象可以得到类的所有信息
通过反射创建对象
方式一:调用类中的public修饰符的无参构造器
方式二:调用类中的指定构造器
Class类对应的相关方法
- newInstance():调用类中的无参构造器,获取对应的类对象
- getConstructor():根据参数列表,获取对应的public构造器对象
- getDecalaredConstructor():根据参数列表,获取对应的构造器对象
Constructor类相关方法
setAccessible(true):爆破(访问非public属性,方法都要使用爆破)
nweInstance():调用构造器
看具体案例理解运用
反射爆破创建实例
public class ReflecCreteInstance {
public static void main(String[] args) throws Exception {
// 1.获取到User的class对象
Class cls2 =User.class;
// 2.通过public的无参构造器创建实例
Object o = cls2.newInstance();
System.out.println(o);
// 3.通过public的有参构造器创建实例(有参构造器的参数类型)
Constructor <?>constructor = cls2.getConstructor(String.class);
Object jmm = constructor.newInstance("jmm");
System.out.println(jmm);
// 4.通过非public的有参构造器创建实例
// 先得到私有的构造器的对象
Constructor<?> declaredConstructor = cls2.getDeclaredConstructor(int.class, String.class);
//爆破,使用反射可以访问private构造器、方法、属性
declaredConstructor.setAccessible(true);
Object user2 = declaredConstructor.newInstance(100,"张三丰");
System.out.println(user2);
}
}
class User{
private int age;
private String name;
public User(){
}
public User(String name){
this.name = name
}
public User(int age,String name){
this.age = age;
this.name = name;
}
@Override
public String toString(){
return"user[age="+age+",name="+name+"]";
}
}
反射爆破操作属性
public class ReflecAccessProperty {
public static void main(String[] args) throws Exception {
// 得到student类对应的Class对象
Class cls = Student.class;
// 创建对象
Object o = cls .newInstance();//o的运行类型就是student
// 属于反射得到age属性对象
Field age = cls .getField("age");
age.set(o,88);//通过反射来操作属性
System.out.println(o);
// 使用反射操作name属性
Field name = cls.getDeclaredField("name");
// name属性是私有的且是静态的所以是静态的所以要进行爆破
name.setAccessible(true);
name.set(o,"zhangsan");
// name.set(null,"zhangsan");应为是静态的属性,是属于所有对象的,在类加载时就已存在所以可以写成null
System.out.println(o);
}
}
class Student{
public int age;
private static String name;
public Student(){
}
@Override
public String toString(){
return "Student[age="+age+",name="+name+"]";
}
}
反射爆破操作方法
public class ReflecAccessMethod {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//得到Boss类对应的Class对象
Class cls = Boss.class;
// 创建一个对象
Object o = cls.newInstance();
// 调用public的hi方法
Method hi = cls.getMethod("hi",String.class);
// 调用
hi.invoke(o,"1");
//调用private static方法
// 得到say方法对象
Method say = cls.getDeclaredMethod("say", int.class,String.class, char.class);
// 爆破
say.setAccessible(true);
// 应为该方法返回字符串所以这样输出
System.out.println(say.invoke(o,100,"张三",'男'));
// System.out.println(say.invoke(null,100,"张三",'男'));应为say方法是静态的
// 返回值:在反射中,如果方法有返回值,统一返回Object,但是他的运行类型和方法定义的返回类型一直
Object e = say.invoke(null,300,"w",'n');
System.out.println("e的运算类型"+e.getClass()); //String
}
}
class Boss{
public int age;
private static String name;
public Boss(){
}
private static String say(int n, String s,char c){
return n+" "+s+" "+c;
}
public void hi(String s){
System.out.println("hi" +s);
}
}