Java之反射机制(详解)

Java之反射机制

一、反射的定义

1、Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

2、Java反射是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时通过Java Reflection API取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。

3、Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。换言之,Java可以加载一个运行时才得知名称的class,获得其完整结构。

二、反射的原理

下图是类的正常加载过程、反射原理与class对象:

Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
在这里插入图片描述

三、JDK中提供的Java Reflection API简介

Java反射相关的API在包java.lang.reflect中,JDK 1.6.0的reflect包如下图

类摘要
AccessibleObjectAccessibleObject 类是 Field、Method 和 Constructor 对象的基类,它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力 。
ArrayArray 类提供了动态创建和访问 Java 数组的方法。
ConstructorConstructor 提供关于类的单个构造方法的信息以及对它的访问权限。
Field提供有关类或接口的单个字段的信息,以及对它的动态访问权限。
MethodMethod 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。
ModifierModifier 类提供了 static 方法和常量,对类和成员访问修饰符进行解码。
ProxyProxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
ReflectPermission反射操作的 Permission 类。
接口摘要
AnnotatedElement表示目前正在此 VM 中运行的程序的一个已注释元素。
GenericArrayTypeGenericArrayType 表示一种数组类型,其组件类型为参数化类型或类型变量。
GenericDeclaration声明类型变量的所有实体的公共接口。
InvocationHandlerInvocationHandler 是代理实例的调用处理程序 实现的接口。
Member成员是一种接口,反映有关单个成员(字段或方法)或构造方法的标识信息。
ParameterizedTypeParameterizedType 表示参数化类型,如 Collection。
TypeType 是 Java 编程语言中所有类型的公共高级接口。
TypeVariableTypeVariable 是各种类型变量的公共高级接口。
WildcardTypeWildcardType 表示一个通配符类型表达式,如 ?、? extends Number 或 ? super Integer。

四、关于JDK中自带的类加载器:(聊一聊,不需要掌握,知道当然最好!)

1、什么是类加载器?

专门负责加载类的命令/工具。
ClassLoader

2、JDK中自带了3个类加载器

(1)启动类加载器:rt.jar

(2)扩展类加载器:ext/*.jar

(3)应用类加载器:classpath

3、假设有这样一段代码:
String s = “abc”;

代码在开始执行之前,会将所需要类全部加载到JVM当中。
通过类加载器加载,看到以上代码类加载器会找String.class
文件,找到就加载,那么是怎么进行加载的呢?

(1)首先通过“启动类加载器”加载。

注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar

rt.jar中都是JDK最核心的类库。

(2)如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。

注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar

(3)如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载。

注意:应用类加载器专门加载:classpath中的类。

4、java中为了保证类加载的安全,使用了双亲委派机制。优先从启动类加载器中加载,这个称为“父”。“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

四、反射机制常用的类

Java.lang.Class;

Java.lang.reflect.Constructor;

Java.lang.reflect.Field;

Java.lang.reflect.Method;

Java.lang.reflect.Modifier;

五、反射的基本使用

1、反射机制获取类的三种方法

(1)通过java.lang包下Class类的静态方法:forName(String className)(最常用)

(2)java中任何一个对象都有一个方法:getClass()

(3)任何数据类型(包括基本数据类型和引用数据类型)都有一个“静态”的class属性

import java.util.Date;

public class ReflectTest01 {
    public static void main(String[] args) {
    
    	//第一种方式获取Class对象,Class.forName("完整类名必须带包名")
        Class c1=null;
        Class c2=null;
        try {
            c1=Class.forName("java.lang.String");
            c2=Class.forName("java.util.Date");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
		
		//第二种方式获取Class对象
        String s="abc";
        Class x1=s.getClass();
        System.out.println(x1==c1);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个Class对象
        
        Date time=new Date();//这一new 产生一个Date对象,一个Class对象
        Class x2=time.getClass();
        System.out.println(x2==c2);
		
		//第三种方式获取Class对象
        Class z1=String.class;
        Class z2=Date.class;
        System.out.println(z1==x1);//判断三种方式是否获取的是同一个Class对象
        System.out.println(z2==x2);

    }
}

结果:在运行期间,一个类,只有一个Class对象产生,所以打印结果都是true。

注意:如果你只想让一个类的“静态代码块”执行的话,你可以怎么做?
		Class.forName("该类的类名");
		这样类就加载,类加载的时候,静态代码块执行!!!!
		在这里,对该方法的返回值不感兴趣,主要是为了使用“类加载”这个动作。

2、创建对象,通过反射来生成对象主要有两种方法:

(1)获取到Class后,使用Class对象的newInstance()方法来创建对象

Class c=Class.forName("com.java.bean.User");
//newInstance()这个方法调用的是User这个类的无参构造方法,完成对象的创建
//这个方法的调用必须保证无参构造是存在的
Object obj=c.newInstance();
System.out.println(obj);      

(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对象,这种方法可以用指定的构造器创建类的实例。

//1、获取Vip的Class对象
Class vipClass=Class.forName("com.java.service.Vip");
           
//2、通过Class对象获取指定的Constructor构造器对象

//(1)获取无参数构造方法
Constructor con=vipClass.getDeclaredConstructor();
Object obj=con.newInstance();
System.out.println(obj);

//(2)调用有参数构造方法
Constructor con2=vipClass.getDeclaredConstructor(int.class,String.class,String.class);
Object newObj=con.newInstance(110,"Tom","2020-1-1");
System.out.println(newObj);

通过读取属性类属性来创建对象

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class ReflectTest03 {
    public static void main(String[] args) {
        try {
            FileReader reader=new FileReader("reflect/classinfo.properties");
            //创建属性类对象Map
            Properties pro=new Properties();
            //加载
            pro.load(reader);
            //关闭流
            reader.close();
            //通过key获取value
            String className=pro.getProperty("className");
            //System.out.println(className);

            //通过反射机制实例化对象
            Class c=Class.forName(className);
            Object obj=c.newInstance();
            System.out.println(obj);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

资源绑定器
IO + Properties,怎么快速绑定属性资源文件?

只能绑定xxx.properties文件,并且这个文件必须在类路径下,文件扩展名也必须为properties,并且在写路径时,路径后面的扩展名不能写。

ResourceBundle bundle=ResourceBundle.getBundle("classinfo");
String className=bundle.getString("className");
System.out.println(className);

获取一个文件的绝对路径(前提:文件需在类路径下)

String path = Thread.currentThread().getContextClassLoader()
						  .getResource("写相对路径,但是这个相对路径从src出发开始找").getPath();	

String path = Thread.currentThread().getContextClassLoader()
						  .getResource("abc").getPath();	//必须保证src下有abc文件。

String path = Thread.currentThread().getContextClassLoader()
						  .getResource("a/db").getPath();	//必须保证src下有a目录,a目录下有db文件。
		
String path = Thread.currentThread().getContextClassLoader()
						  .getResource("com/bjpowernode/test.properties").getPath();	
						  //必须保证src下有com目录,com目录下有bjpowernode目录。
						  //bjpowernode目录下有test.properties文件。
						  
//Thread.currentThread() 当前线程对象
//getContextClassloader() 是线程对象的方法,可以获取当前线程的类加载器对象
//getResource() 这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源

这种方式是为了获取一个文件的绝对路径。(通用方式,不会受到环境移植的影响。)
但是该文件要求放在类路径下,换句话说:也就是放到src下面。
src下是类的根路径。

直接以流的形式返回

InputStream in=Thread.currentThread().getContextClassloader().getResourceAsStream("src下的路径");

3、获取类的Fields

可以通过反射机制获到某个类的某个属性,然后改变对应于这个类的某个实例的属性值。Java 的Class类提供了几个方法获取类的属性。

FieldgetField(String name) 返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
Field[]getFields() 返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
FieldgetDeclaredField(String name) 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
Field[]getDeclaredFields() 返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。
intgetModifiers() 返回此类或接口以整数编码的 Java 语言修饰符。
StringgetName() 以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
StringgetSimpleName() 返回源代码中给出的底层类的简称。
package com.java.bean;
//反射属性Field
public class Student {
    //Field翻译为字段,其实就是属性/成员
    public int no;
    private String name;
    protected int age;
    boolean sex;
}
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/*
反射Student类中的所有的Field
 */
public class ReflectTest05 {
    public static void main(String[] args) {
        try {
            //获取整个类
            Class studentClass=Class.forName("com.java.bean.Student");
                                
            //获取所有的Field
            Field[] fs=studentClass.getDeclaredFields();
         
            //遍历
            for(Field field:fs){
                //获取属性的修饰符列表             
                String modifierString= Modifier.toString(field.getModifiers());
                System.out.println(modifierString);

                //获取属性类型
                Class fieldType=field.getType();
                String fName=fieldType.getSimpleName();
                System.out.println(fName);
                
                //获取属性名字
                System.out.println(field.getName());
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

import java.lang.reflect.Field;

public class ReflectTest06 {
    public static void main(String[] args) {
        //使用反射机制,访问一个对象的属性
        try {
            Class studentClass=Class.forName("com.java.bean.Student");
            Object obj=studentClass.newInstance();

            //获取no属性
            Field noField=studentClass.getDeclaredField("no");
            noField.set(obj,111);
            System.out.println(noField.get(obj));

            //可以访问私有的属性吗? 可以
            Field nameField=studentClass.getDeclaredField("name");

            //打破封装(打破封装,可能会给不法分子机会)
            //这样设置完之后,在外部也是可以访问private
            nameField.setAccessible(true);

            nameField.set(obj,"zhangsan");
            System.out.println(nameField.get(obj));

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

//完成任意一个类属性的反编译
public class ReflectTest07 {
    public static void main(String[] args) {
        try {
            //创建这个是为了拼接字符串
            StringBuilder s=new StringBuilder();
            
            Class dateClass=Class.forName("java.util.Date");
            s.append(Modifier.toString(dateClass.getModifiers())+" class "+dateClass.getSimpleName()+" {\n");

            Field[] fields=dateClass.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);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

4、获取类的Method

通过反射机制得到某个类的某个方法,然后调用对应于这个类的某个实例的方法

package com.java.service;

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("系统已经安全退出!");
    }
}

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class ReflectTest08 {
    public static void main(String[] args) {
        try {
            //获取类
            Class userServiceClass=Class.forName("com.java.service.UserService");

            //获取所有的Method(包括私有的!)
            Method[] methods=userServiceClass.getDeclaredMethods();
            //System.out.println(methods.length);

            //遍历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());
                }

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class ReflectTest09 {
    public static void main(String[] args) {
        try {
            StringBuilder s=new StringBuilder();
            Class userServiceClass= Class.forName("com.java.service.UserService");
            s.append(Modifier.toString(userServiceClass.getModifiers())+" class "+userServiceClass.getSimpleName()+"{\n");

            Method[] methods=userServiceClass.getDeclaredMethods();
            for(Method method:methods){
                s.append("\t");
                s.append(Modifier.toString(method.getModifiers()));
                s.append(" ");
                s.append(method.getReturnType().getSimpleName());
                s.append(" ");
                s.append(method.getName());
                s.append("(");
                
                //参数列表
                Class[] parameterTypes=method.getParameterTypes();
                for(Class parameterType:parameterTypes){
                    s.append(parameterType.getSimpleName());
                    s.append(",");
                }
                if(parameterTypes.length>0){
                    s.deleteCharAt(s.length()-1);
                }
                
                s.append("){}\n");
            }
            s.append("}");
            System.out.println(s);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectTest10 {
    public static void main(String[] args) {
        try {
            Class userServiceClass=Class.forName("com.java.service.UserService");
            
            //创建对象
            Object obj=userServiceClass.newInstance();
            
            //获取方法
            Method logoutMethod=userServiceClass.getDeclaredMethod("logout");
            Method loginMethod=userServiceClass.getDeclaredMethod("login",String.class,String.class);
            
            //调用方法
            Object retValue=loginMethod.invoke(obj,"admin","123");
            System.out.println(retValue);

            logoutMethod.invoke(obj);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

5、获取类的Constructor

通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例

package com.java.service;

public class Vip {
    int no;
    String name;
    String birth;
    char sex;

    public Vip() {
    }

    public Vip(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public Vip(int no, String name, String birth) {
        this.no = no;
        this.name = name;
        this.birth = birth;
    }

    public Vip(int no, String name, String birth, char sex) {
        this.no = no;
        this.name = name;
        this.birth = birth;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Vip{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", birth='" + birth + '\'' +
                ", sex=" + sex +
                '}';
    }
    
}

import java.lang.reflect.Constructor;

//使用反射机制调用构造方法
public class ReflectTest11 {
    public static void main(String[] args) throws Exception {   
        //使用反射机制创建对象
        Class vipClass=Class.forName("com.java.service.Vip");
        
        //调用无参数构造方法
        Object obj=vipClass.newInstance();
        System.out.println(obj);

        //调用有参数构造方法
        //第一步:先获取这个有参数构造方法
        Constructor con=vipClass.getDeclaredConstructor(int.class,String.class,String.class,char.class);
        //第二步:调用构造方法new对象
        Object newObj=con.newInstance(110,"Tom","2020-1-1",'男');
        System.out.println(newObj);

        //获取无参数构造方法
        Constructor con2=vipClass.getDeclaredConstructor();
        Object obj2=con2.newInstance();
        System.out.println(obj2);
    }
}

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

public class ReflectTest12 {
    public static void main(String[] args) throws Exception {
        StringBuilder s=new StringBuilder();
        Class vipClass=Class.forName("com.java.service.Vip");
        s.append(Modifier.toString(vipClass.getModifiers()));
        s.append(" class ");
        s.append(vipClass.getSimpleName());
        s.append("{\n");

        //拼接构造方法
        Constructor[] constructors=vipClass.getDeclaredConstructors();
        for(Constructor constructor:constructors){
            //public Vip(int no, String name, String birth, boolean sex) {
            s.append("\t");
            s.append(Modifier.toString(constructor.getModifiers()));
            s.append(" ");
            s.append(vipClass.getSimpleName());
            s.append("(");

            //拼接参数
            Class[] parameterTypes=constructor.getParameterTypes();
            for(Class parameterType:parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }

            //删除最后下标上的字符
            if(parameterTypes.length>0){
                s.deleteCharAt(s.length()-1);
            }
            s.append("){}\n");
        }

        s.append("}");
        System.out.println(s);
    }
}

public class ReflectTest13 {
    public static void main(String[] args) throws Exception {
        Class stringClass=Class.forName("java.lang.String");

        //获取String的父类
        Class superClass=stringClass.getSuperclass();
        System.out.println(superClass.getName());

        //获取String类所有实现的接口
        Class[] interfaces=stringClass.getInterfaces();
        for(Class in:interfaces){
            System.out.println(in.getName());
        }
    }
}

六、反射的优缺点:

1、优点:在运行时获得类的各种内容,再进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便地创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

2、缺点:(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;

(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

古藤老人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值