反射机制作用:
通过java语言中的反射机制可以操作字节码文件(可以读和修改字节码文件)
通过反射机制可以操作代码片段。
反射机制的相关类在java.lang.reflect.*包下
反射机制相关的重要的类:
java.lang.Class 代表整个字节码,代表一个类型,代表整个类
java.lang.reflect.Method 代表字节码中的方法字节码,代表类中的方法
java.lang.reflect.Constructor 代表字节码中的构造方法字节码,代表类中的构造方法
java.lang.reflect.Field代表字节码中的属性字节码,代表类中的成员变量
public class User{
//Field
int no;
//Constructor
public User(int no){this.no=no}
//Method
public int getNo(){
return no;
}
}
/*
* 操作一个类的字节码,需要获得这个类的字节码。获取java.lang.Class实例
* 有三种方式
* 1.Class.forName()
* 1)静态方法
* 2)方法的参数是一个字符串
* 3)完整类名必须带有包名,java.lang不能省略
* */
public class Test01 {
public static void main(String[] args) {
Class c1=null;
try {
c1=Class.forName("java.lang.String");//c1代表string.class文件,c1代表string类型
Class c2=Class.forName("java.lang.Date");//c2代表Date类型
Class c3=Class.forName("java.lang.Integer");
Class c4=Class.forName("java.lang.System");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//方式2.java中任何一个对象都有一个方法getClass()
String s="abc";
Class x=s.getClass();//x代表string.class字节码文件,x代表string类型
System.out.println(c1==x);//true(==判断的是对象的内存地址,这两个变量中保存的内存地址是一样的,都指向方法区中的字节码)
//方式3.java语言中任何一种类型,包括基本数据类型,都有.class属性。
Class y=String.class;//y代表string类型
Class k= Date.class;//k代表Date类型
}
}
public class Test01 {
public static void main(String[] args) {
try {
//通过反射机制获取Class,通过Class来实例化对象
Class c=Class.forName("reflect.User");//c代表User类型
//newInstance()这个方法会调用User这个类的无参构造方法,完成对象的创建。
Object obj= c.newInstance();
System.out.println(obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/*验证反射机制的灵活性
* java代码写一遍,在不改变java源代码的基础上,可以做到不同对象的实例化(properties文件里的对象类型随便改,反射代码不用变),非常灵活。
* 符合OCP开闭原则,对扩展开放,对修改关闭
* */
public class Test01 {
public static void main(String[] args) throws Exception {
//通过IO流读取classinfo.properties文件
FileReader reader=new FileReader("JavaBase/classinfo.properties");
//创建属性类对象Map
Properties pro=new Properties();//key values都是string
//加载
pro.load(reader);
//关闭流
reader.close();
//通过key获取value
String className=pro.getProperty("className");
//System.out.println(className);//结果:reflect.User
//通过反射机制实例化对象
Class c=Class.forName(className);
Object obj=c.newInstance();
System.out.println(obj);
}
}
/*
* 研究Class.forName()发生了什么
* 如果只希望一个类的静态代码块执行,其他代码一律不执行。
* 可以使用Class.forName("完整类名");
* 这个方法的执行会导致类加载,静态代码块执行
* */
public class Test01 {
public static void main(String[] args) {
try {
//Class.forName()这个方法的执行会导致:类加载
Class.forName("reflect.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MyClass{
//静态代码块在类加载时执行且只执行一次
static{
System.out.println("MyClass类的静态代码块执行了");
}
}
/*
研究文件路径
该文件必须在类路径下(类路径:在src下的就是在类路径下)
src是类的根路径
* */
public class Test01 {
public static void main(String[] args) throws Exception {
/*
* Thread.currentThread()当前线程对象
*getContextClassLoader()是线程对象的方法,可以获取到当前线程的类加载器对象
* getResource()[获取资源]这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
* */
String path=Thread.currentThread().getContextClassLoader().getResource("classinfo.properties").getPath();
//采用以上方式可以拿到一个文件的绝对路径
///C:/Users/user/IdeaProjects/test/out/production/JavaBase/classinfo.properties
System.out.println(path);
}
}
public class Test01 {
public static void main(String[] args) throws Exception {
//获取文件绝对路径
//方式1
/* String path = Thread.currentThread().getContextClassLoader()
.getResource("classinfo.properties").getPath();
FileReader reader=new FileReader(path);*/
//方式2 直接以流的形式传送(不需要直接路径)
InputStream reader=Thread.currentThread().getContextClassLoader().getResourceAsStream("classinfo.properties");
Properties pro=new Properties();
pro.load(reader);
reader.close();
//通过key 获取value
String className=pro.getProperty("className");
System.out.println(className);
}
}
/*
* java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
* 使用以下方式的时候,属性配置文件必须放在类路径下
* */
public class Test01 {
public static void main(String[] args) {
//资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下,文件扩展名必须是properties
//并且在写路径的时候,路径后面的扩展名不能写
ResourceBundle bundle=ResourceBundle.getBundle("classinfo");
String className=bundle.getString("className");
System.out.println(className);
}
}
关于JDK中自带的类加载器
什么是类加载器?
启动类加载器
扩展类加载器
应用类加载器
假设有这样一段代码:
string s=“abc”;
代码在开始执行之前,会将所需要类全部加载到JVM中。
通过类加载器加载,看到以上代码类加载器会找string.class文件,找到就加载,那么是怎么进行加载的呢?
首先通过“启动类加载器”加载。
注意:启动类加载器加载JDK最核心的类库
如果通过“启动类加载器”加载不到的时候
会通过“扩展类加载器”加载。
如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载。
注意:应用类加载器专门加载:classpath中的jar包(class文件。)
java中为了保证类加载的安全,使用双亲委派机制。
优先从启动类加载器中加载,称为<父>
父中无法加载到,再从扩展类加载器中加载,
称为<母>。双亲委派,如果都加载不到,才会考虑从应用加载器中加载。
/*反射Student类中所有Field*/
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
//获取整个类
Class studentClass=Class.forName("reflect.Student");
String className=studentClass.getName();
System.out.println("完整类名"+className);
String simpleName=studentClass.getSimpleName();
System.out.println("简类名"+simpleName);
//获取类中所有public修饰的Field
Field[] fields=studentClass.getFields();
System.out.println(fields.length);//
//取出这个field
Field f=fields[0];
//取出这个field的名字
String fieldName=f.getName();
System.out.println(fieldName);
//获取所有的Field
Field[] fs=studentClass.getDeclaredFields();
System.out.println(fs.length);//4
//遍历
for(Field field:fs){
//获取属性修饰符列表
int i=field.getModifiers();
System.out.println(i);//返回的修饰符是一个数字,每个数字是修饰符的代号
//可将代号转换成字符串
String modifierString= Modifier.toString(i);
System.out.println(modifierString);
//获取属性的类型
Class fieldType=field.getType();
//完整类型名
// String fName=fieldType.getName();
//简单类型名
String fName=fieldType.getSimpleName();
System.out.println(fName);
//获取属性的名字
System.out.println(field.getName());
}
}
}
//反射属性Field
public class Student {
//Field翻译为字段,就是属性/成员
//4个Field分别采用了不同的访问控制权限修饰符
public int no;//field对象
private String name;
protected int age;
boolean sex;
}
/*通过反射机制,反编译一个类的属性Field*/
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
//创建这个是为了拼接字符串
StringBuilder s=new StringBuilder();
Class studentClass=Class.forName("java.lang.Thread");
s.append(Modifier.toString(studentClass.getModifiers())+" class "+studentClass.getSimpleName()+"{\n");
Field[] fields=studentClass.getDeclaredFields();
for(Field field:fields){
s.append("\t");
s.append(Modifier.toString(field.getModifiers()));
s.append(" ");
s.append(field.getType().getSimpleName());
s.append(" ");
s.append(field.getName());
s.append(";\n");
}
s.append(")");
System.out.println(s);
}
}
/*
* 怎样通过反射机制访问一个java对象的属性?
* 给属性赋值set
* 获取属性的值get
* */
public class Test01 {
public static void main(String[] args) throws Exception {
//不使用反射机制访问对象的属性
Student s=new Student();
//给对象赋值
s.no=111;
//读属性值
//两个要素
System.out.println(s.no);
//使用反射机制访问对象的属性(set get)
Class studentClass=Class.forName("reflect.Student");
Object obj=studentClass.newInstance();//obj就是student对象
//获取no属性(根据属性的名称来获取field)
Field noField=studentClass.getDeclaredField("no");
//给obj对象(Student对象)的no属性赋值
/*
* 三要素:对象 属性 值
* */
noField.set(obj,22);
//读取属性的值
//两个要素:获取obj对象的no属性的值
noField.get(obj);
/*
* 如果想要访问私有属性,可以使用打破封装的方式
* */
Field nameField=studentClass.getDeclaredField("name");
//打破封装(这就是反射机制的缺点,会给不法分子留下机会)
nameField.setAccessible(true);
//给name属性赋值
nameField.set(obj,"jackson");
//获取name属性的值
System.out.println(nameField.get(obj));
}
}
/*
* 可变长度参数
* 语法是:类型...
1.可变长度参数要求参数个数:0~N个
2.可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有一个
3.可变长度参数可以当做一个数组来看待
*/
public class Test01 {
public static void main(String[] args) throws Exception {
m();
m(10);
m3("abc","def","ghjm");
String[] strs={"asfd","dsfcdv"};
//也可以传一个数组
m3(strs);
}
public static void m(int...args){
System.out.println("执行m方法");
}
//必须在最后,只能有一个
public static void m2(int a,String...args){}
public static void m3(String... args){
//args有length属性,说明args是一个数组
//可将可变长度参数视为一个数组
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
/*
* 反射Method
* */
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
//获取类
Class userServiceClass=Class.forName("reflect.UserService");
//获取所有的Method(包括私有)
Method[] methods=userServiceClass.getDeclaredMethods();
System.out.println(methods.length);//2
//遍历Method
for(Method method:methods){
//获取修饰符列表
System.out.println(Modifier.toString(method.getModifiers()));
//获取方法的返回值类型
System.out.println(method.getReturnType().getSimpleName());
//获取方法名
System.out.println(method.getName());
//方法的修饰符列表(一个方法的参数可能会有多个
Class[] parameterTypes=method.getParameterTypes();
for(Class parameterType:parameterTypes){
System.out.println(parameterType.getSimpleName());
}
}
}
}
/*
* 用户业务类
* */
public class UserService {
/***
* 登录方法
* @param name 用户名
* @param password 密码
* @return TRUE表示登陆成功,FALSE表示登陆失败
*/
public boolean login(String name,String password){
if("admin".equals(name)&&"123".equals(password)){
return true;
}
return false;
}
/**
* 退出登录的方法
*/
public void logout(){
System.out.println("系统已安全退出");
}
}
/*
* 通过反射机制调用一个对象的方法
* 反射机制让代码更具通用性。
* */
public class Test01 {
public static void main(String[] args) throws Exception {
Class userServiceClass=Class.forName("reflect.UserService");
//不用反射机制,调用方法
//创建对象
UserService userService=new UserService();
//调用方法
boolean loginSuccess=userService.login("admin","123");
System.out.println(loginSuccess?"登陆成功":"登陆失败");
/*******************************************************/
//创建对象
Object obj=userServiceClass.newInstance();
//获取Method
Method loginMethod=userServiceClass.getDeclaredMethod("login",String.class,String.class);
//调用方法(要素:对象 方法名 实参列表 返回值)
//invoke-----调用
/**
* loginMethod 方法
* obj 对象
* "admin","123"实参
* retValue返回值
*/
Object retValue=loginMethod.invoke(obj,"admin","123");
System.out.println(retValue);
}
}