反射 :reflection
程序的一种内省机制
程序可以在运行期间动态的创建对象,获取对象类型,调用对象行为
内省机制在java和.net语言中有,在早期的C,C++,delphi,vb这些语言都没有内省机制。
//方法的可变参数
public void playDisc(Class... cl) {
}
反编译后,可变参数的本质就是数组
public transient void playDisc(Class aclass[])
{
}
Class
Class类的类表示正在运行的Java应用程序中的类和接口
1 类的加载概述和加载时机
A:类的加载概述
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载连接初始化散步来实现对这个类进行初始化
加载:就是指将class文件读入内存,并为之创建一个class对象,任何类被使用时系统都会建立一个class对象。
连接:(三步:验证、准备、解析)
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用
初始化 就是以前常用的初始化步骤(创建一个对象时字节码文件加载进来的过程
B加载时机
创建类的实例
访问类的静态变量,或者为静态变量赋值
调用类的静态方法
使用反射方式来强制创建某个类或接口对应的java.lang,class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类
2 类加载器的概述和分类
A类加载器的概述
负责将。class文件加载到内存中并为之生成对应的class对象。类加载机制可以不用关心,但是此处的了解有利于更好地理解程序的运行
B类加载器的分类
Bootstrap classloader 根类加载器
Extension classloader 扩展类加载器
system classloader 系统类加载器
C类加载器的作用
Bootstrap classloader 根类加载器
也被称为引导类加载器,负责Java核心类的加载
比如system,string类等,在JDK中JRE放入lib目录下rt.jar文件中
Extension classloader 扩展类加载器
负责JRE的扩展目录中jar包的加载
在JDK中的JRE的lib目录下ext目录
system classloader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
3 反射概述
A 反射概述
java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用他的任意一个方法和属性
这种动态获取的信息以及动态调用对象的方法和功能称为java语言的反射机制。
想要解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法,所以要先获取到每一个字节码文件对应的class类型的对象。
B 获取对象类型的方法:(三种)
1:类名.class 静态属性class,锁对象
Class aa = Dog.class; //通过xxx.class的方式获取数据类型
2:对象.getclass(); Object类的个体class()方法,判断两个对象是否在同一个字节码文件
Dog dog = new Dog("旺财");
Class bb = dog.getClass();//在程序运行期,动态湖区
System.out.println(bb);
3:class.forname()需要抛出异常; class类中静态方法forname(),读取配置文件 可以只改配置文件获取不同的.class文件
Class.forname(“oracle.jdbc.driver.OracleDriver”);
//作用1、检查驱动是否存在
2. 通过静态代码块,给静态变量defaultDriver赋值并做了初始化操作
3. 在DriverManager.getConnection中调用前面的静态变量
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connection = DriverManager.getConnection("xxx","aa","123456");
//1. 检查数据类型是否存在
2. 通过静态代码块给静态变量初始化 -----静态变量存储与JVM的方法区
3. ClassLoader ,把数据类型加载到JVM的方法区
4. 返回Class对象
C 三种获取对象类型的方式实例
public class Test1_reflect { //三种获取class的方式实例
/**
*
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException {
Class clazz1= Class.forName("bean.Person");
Class clazz2= Person.class;
Person p=new Person();
Class clazz3=p.getClass();
System.out.println(clazz1==clazz2);
System.out.println(clazz2==clazz3);
}
}
4 Class,forname()读取配置文件举例
榨汁机:
public class Test2_reflect {
/*
* 用forname获取对象,在使用时只需要更改配置文件即可以完成操作
*/
public static void main(String[] args) throws Exception {
/*没用到反射
* Juicer j=new Juicer(); //购买榨汁机
* j.run(new Apple()); //榨汁机中放苹果
* j.run(new Orange()); //榨汁机中放橘子*/
//用反射和配置文件
BufferedReader br=new BufferedReader(new FileReader("config.properties"));
Class clazz =Class.forName(br.readLine());
Fruit f= (Fruit) clazz.newInstance(); //父类引用指向子类对象,水果的引用指向了苹果对象
//clazz.newInstance();根据配置文件创建了一个Object类型的对象,使用时需要强转
Juicer j=new Juicer();
j.run(f);
}
}
interface Fruit{
public void squeeze();
}
class Apple implements Fruit{
public void squeeze() {
System.out.println("榨出一杯苹果汁");
}
}
class Orange implements Fruit{
public void squeeze() {
System.out.println("榨出一杯橘子汁");
}
}
class Juicer{
public void run(Fruit f) {
f.squeeze();
}
/*public void run(Apple a) {
a.squeeze();
}
public void run(Orange o) {
o.squeeze();
}*/
}
5 通过反射获取带参构造方法并使用
constructor
Class类的newinstance()方法时使用该类无参的构造函数和创建对象,如果一个类没有无参构造函数,就不能这样创建了,可以调用Class类的getConstructor(String。class,int.class)方法获取一个指定的构造函数然后再调用Constructor类的newInstan(“张三”20)方法创建对象。
public class Test3_reflect {
/*
* Class类的newinstance()方法时使用该类无参的构造函数和创建对象,如果一个类没有无参构造函数,就不能这样创建了,
* 可以调用Class类的getConstructor(String。class,int.class)方法获取一个指定的构造函数
* 然后再调用Constructor类的newInstan(“张三”20)方法创建对象。
*/
public static void main(String[] args) throws Exception {
Class clazz=Class.forName("bean.Person");
//Person p=(Person) clazz.newInstance(); 通过无参构造创建对象
//System.out.println(p);
//void.class
Constructor c=clazz.getConstructor(String.class,int.class); //反射阶段操作字节码,给的是字节码(获取有参构造)
Person p =(Person) c.newInstance("张三",23); //通过有参构造创建对象
System.out.println(p);
}
}
6 通过反射获取成员变量并使用
Field
Class.getFiled(String)方法可以获取类中指定字段(可见的),如果是私有的可以用getDeclaedField("name")方法获取,通过set(obj,"李四")方法可以设置指定对象上该字段的值,如果是私有的需要先调用setAccessible(ture)设置访问权限,用获取的指定字段调用get(obj)可以获取指定对象中该字段的值
public class Test4_reflect {
/**
* Class.getFiled(String)方法可以获取类中指定字段(可见的),如果是私有的可以用getDeclaedField("name")方法获取,
* 通过set(obj,"李四")方法可以设置指定对象上该字段的值,如果是私有的需要先调用setAccessible(ture)设置访问权限,
* 用获取的指定字段调用get(obj)可以获取指定对象中该字段的值
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Class clazz=Class.forName("bean.Person");
Constructor c=clazz.getConstructor(String.class,int.class); //反射阶段操作字节码,给的是字节码(获取有参构造)
Person p =(Person) c.newInstance("张三",23); //通过有参构造创建对象
//Field f=clazz.getField("name");//获取name字段
//f.set(p, "李四"); //修改P的姓名的值
Field f=clazz.getDeclaredField("name");//暴力反射获取字段(可以获取私有的字段)
f.setAccessible(true); //去除私有权限
f.set(p, "李四");
System.out.println(p);
}
}
7 通过反射获取方法并使用
Method
Class.getMethod(String,Class...)和Class。getDeclaredMethod(String,Class...)方法可以获取类中的指定方法,调用invoke(Object,Object...)可以调用该方法,Class。getMethod(“eat")invoke(obj)Class.getMethod("eat",int.class)invoke(obj,10)
public class Test5_Method {
/**
* Class.getMethod(String,Class...)和Class。getDeclaredMethod(String,Class...)方法可以获取类中的指定方法,
* 调用invoke(Object,Object...)可以调用该方法,
* Class。getMethod(“eat")invoke(obj)Class.getMethod("eat",int.class)invoke(obj,10)
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Class clazz=Class.forName("bean.Person");
Constructor c=clazz.getConstructor(String.class,int.class); //反射阶段操作字节码,给的是字节码(获取有参构造)
Person p =(Person) c.newInstance("张三",23); //通过有参构造创建对象
Method m=clazz.getMethod("eat"); //获取无参eat方法
m.invoke(p); //执行无参方法
Method m1=clazz.getMethod("eat",int.class); //获取有参eat方法
m1.invoke(p,10); //执行有参方法
}
}
8 通过反射越过泛型检查
A案例演示
ArrayList的一个对象,在这个集合中添加一个字符串数据。
public class Test1 {
/**
* @param args
* ArrayList的一个对象,在这个集合中添加一个字符串数据
* 泛型只在编译期有效,在运行期会被擦除掉
* @throws Exception
*/
public static void main(String[] args) throws Exception {
ArrayList list=new ArrayList<>();
list.add(111);
list.add(222);
Class clazz =Class.forName("java.util.ArrayList"); //获取字节码对象
Method m=clazz.getMethod("add", Object.class);
m.invoke(list, "abc");
System.out.println(list); //泛型被擦除掉了所添加进去的内容是新的类型
}
}
9 通过反射写一个通用的设置某个对象的某个属性为指定的值
A案例演示
public void setProperty(object obj,String propertyName,Object value){},此方法可将obj对象中名为propertyName的属性值设置为value。
public class Test2 {
/**
* public void setProperty(object obj,String propertyName,Object value){},
* 此方法可将obj对象中名为propertyName的属性值设置为value。
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Student s=new Student("张三",23);
System.out.println(s);
Tools t=new Tools();
t.setProperty(s, "name", "李四");
System.out.println(s);
}
}
class Student{
private String name;
private int age;
public Student() {
super();
}
public Student(String name,int age) {
super();
this.name=name;
this.age=age;
}
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 [name=" + name + ", age=" + age + "]";
}
}
import java.lang.reflect.Field;
public class Tools {
public void setProperty(Object obj,String propertyName,Object value) throws Exception{
Class clazz =obj.getClass(); //获取字节码对象
Field f=clazz.getDeclaredField(propertyName); //暴力反射获取字段
f.setAccessible(true); //去除权限
f.set(obj, value);
}
}
练习:
已知一个类定义如下
package cn;
public class DenoCLass{
public void run(){
system.out.println("welcome to china!);
}
}
(1)写一个Properties格式配置文件,配置类的完整名称
(2)写一个程序,读取这个Properties配置文件,获得类的完整名称并加载这个类,用反射的方式运行run方法。
作业:
自定义日志系统,在系统运行目录按日期建立文件夹和日志文件(Error文件单独存储)
IO流,反射