反射 reflect

反射 reflect,第1张

关于反射

反射是Java程序开发语言的特征之一,它允许运行中的java程序对自身进行检查,并能直接 *** 作程序的内部属性和方法

优点:
运行期类型的判断,动态加载类,代码灵活度高;

缺点:
性能低,安全性被破坏
常用api

主要通过以下类实现反射

Field
Constructor
Method
Class
Object
获取Class对象

方式一
通过Object类 getClass() 获取反射对象

Book book = new Book();
Class bookClass = book.getClass();

System.out.println("完全限定名:" + bookClass.toString()); 
System.out.println("类的包名:" + bookClass.getPackage()); // 包名
System.out.println("类的包名+类名:" + bookClass.getName());
System.out.println("类名: " + bookClass.getSimpleName());

方式二
通过类名访问class关键字属性,获取该类型的Class对象

Class bookClass = Book.class;

方式三
通过Class类forName() 静态方法获取,需传入类的完全限定名 (包名.类名)

Class bookClass = Class.forName("reflect.Book");//包名.类名
通过Class对象调用对应类的构造方法

调用无参构造方法

OObject obj=bookClass.newInstance();//通过反射创建对象

获得有参构造方法并调用
通过Class对象 getDeclaredConstructor (构造方法参数 . class)获得构造方法的构造器对象

通过构造器对象 . newInstance(传入构造方法参数) 调用构造方法

对于private私有构造方法的调用必须使用构造器对象 . setAccessible(true)权限设置为true 使权限检查机制关闭

Constructor constuctor = bookClass.getDeclaredConstructor(String.class, String.class);//传入参数类型.class
constuctor.setAccessible(true);//权限检查机制关闭
Object obj = constuctor.newInstance("张碧晨", "华晨宇");//调用有参构造

获得所有参构造方法并打印
把所有构造方法对象放入构造器数组中

Constructor[] constructros = bookClass.getDeclaredConstructors();
获取当前Class类型中成员变量并赋值

获得所有成员变量

Field[] fields = bookClass.getDeclaredFields();
for (Field f : fields) {
	System.out.println(f);
	System.out.println("访问修饰符:" + f.getModifiers());
	System.out.println("类型:" + f.getType());
	System.out.println("名称:" + f.getName());
	System.out.println();
}

按照成员变量的名称获取成员变量Field对象并赋值

对于private私有成员变量通过
Field对象 . setAccessible(true)
把权限设置为true使权限检查机制关闭
Field descField = bookClass.getDeclaredField("desc");//传入成员变量名字
descField.set(bookClass, "这个是desc成员变量");//传入类对象和赋值的内容

//获取私有成员变量
Field bookNameField = bookClass.getDeclaredField("bookName");//传入成员变量名字
bookNameField.setAccessible(true);//私有成员变量
bookNameField.set(bookClass, "这是bookName成员变量");//传入类对象和赋值的内容
获取当前Class类型中成员方法并调用

获得所有成员方法

Method[] methods = bookClass.getDeclaredMethods();
for (Method m : methods) {
	System.out.println(m);
	System.out.println("访问范围:" + (m.getModifiers() == Modifier.PUBLIC ? "公有" : "其它"));
	System.out.println("返回类型:" + m.getReturnType());
	System.out.println("方法名称:" + m.getName());
}

获得指定成员方法(指定参数类型)并调用

对于private私有成员方法通过
Method 对象 . setAccessible(true)
把权限设置为true使权限检查机制关闭
Method methodDosth2 = bookClass.getDeclaredMethod("dosth2", String.class, double.class);//传入方法名和方法参数.class
methodDosth2.setAccessible(true);
methodDosth2.invoke(bookClass, "平凡的世界", 75.6);//调用方法,传入类类对象和方法参数

注意

getMethod() 经过重载,如果不需要传递参数,例如 getter 方法,那么直接传递方法的 String 字符串即可
Method method = clazz.getMethod("getSid");

如果需要传递参数,比如 setter 方法,那么在后面需要传递参数的类
需要注意的是: 这里没有包装类的自动转换,必须对应,Integer.class 不能写成 int.class,反之亦然
Method method = clazz.getMethod("setSid",int.class);
获取一个类继承的父类和实现的接口(单继承多实现)
Class arrayListClass = ArrayList.class;//获取ArrayList类的Class对象

Class superClass = arrayListClass.getSuperclass();// 获取直接父类,单继承
System.out.println("ArrayList的父类: " + superClass);

Class[] interfaces = arrayListClass.getInterfaces();
System.out.println("ArrayList实现的接口:");//获取接口,多实现
for(Class interfacex : interfaces) {
	System.out.println(interfacex);
}
getDeclaredConstructor() 与 getConstructor()
getDeclaredConstructor(Class<?>... parameterTypes) 
返回制定参数类型的 所有构造器 ,包括public的和非public的,当然也包括private的
返回结果没有参数类型的过滤

getConstructor(Class<?>... parameterTypes)
只返回制定参数类型访问权限是 public 的构造器。
返回结果同样没有参数类型的过滤
getMethods()/getFileds() 和 getDeclaredMethods()/getDeclaredFields()

获取的方法是无序的

getDeclaredMethods()/getDeclaredFields()
获取本类中 仅自己定义 的所有方法,包括私有的(privateprotected、默认以及public)的方法
不包括继承的方法,包括它所实现接口的方法

getMethods()/getFileds()
获取 本类 以及 父类或者父接口 中所有的公共方法(public修饰符修饰的)
getGenericParameterTypes() 和 getParameterTypes()

获取方法形参类型

getGenericParameterTypes:返回Type类型的数组 Type[].
getParameterTypes:返回Class类型的数组: Class<?>[].
关于 Type 接口

Type是一个高级接口

具体的说明来看一段网上的解释:

Type 是 所有类型的高级公共接口,当然也是Class的父类 
它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。

先来看一下 Type 的用法:

type是一种表示编程语言中的所有类型的类超级接口
如:int、Integer、String 这都是表示一编程语言的类型,而其中的 int.class、Integer.class、String.class 它们表示的是类型的实例

反射 Class c = Integer.class,Class相当于表示类型的类, 而 Integer.class 则是一种名为整形类型的类型实例

type 与 class 一样,不过 type是一种比Class 表示范围还要广的超级接口,它表示Java语言中类型的所有接口

测试
import java.util.List;
public class SampleClass {
    private String sampleField;
    private List<Integer> ids;
    public String getSampleField() {
        return sampleField;
    }
    public void setSampleField(String sampleField) {
        this.sampleField = sampleField;
    }
    public List<Integer> getIds() {
        return ids;
    }
    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }
}
public static void main(String[] args) {
  Class sampleClassClass = SampleClass.class;
  Method[] methods = sampleClassClass.getMethods();
  
  for (Method method : methods) {
      System.out.println("------------------" +method.getName());
      Type[] genericParameterTypes =  method.getGenericParameterTypes();
      Class<?>[] parameterTypes = method.getParameterTypes();
      
      for(Class parameterType: parameterTypes){
          System.out.println(parameterType + "====" + parameterType.getName());
      }
      for (int i = 0; i < genericParameterTypes.length; i++) {
          System.out.println(genericParameterTypes[i] + "=====" + genericParameterTypes[i].getTypeName());
      }
}

String类型,这两个方法返回的结果是一样的
List<Integer> ,getParameterTypes 只返回了类型,泛型没有返回;
而getGenericParameterTypes返回的是完整的泛型。
如果方法参数不是参数化类型(泛型),那么 getParameterTypes 和 getGenericParameterTypes 返回的结果是一样的 
如果方法参数是泛型,这时就有区别了,getGenericParameterTypes 会返回 完整的信息 ,而 getParameterTypes 只会返回参数类型,参数化类型无法得到 

获取参数化类型

// 将类型向参数化类型转换
ParameterizedType t = (ParameterizedType)genericParameterTypes[0];
// 可以得到参数化类型的参数实例
t.getActualTypeArguments()[0];
其他用法 通过类class文件,拿到该文件里面的资源

TestClass.class文件(被编译后)所在的classes文件夹下,有prop.properties属性文件,如何拿到这个文件的绝对路径?如何把该文件加载到流中

Class clazz = TestClass.class;
ClassLoader classLoader = clazz.getClassLoader();
URL url = classLoader.getResource("prop.properties");
String path = url.getPath();
InputStream ins = new FileInputStream(path);

链式写法为:
String path = TestClass.class.getClassLoader().getResource("prop.properties").getPath();
InputStream ins = new FileInputStream(path);

或者直接将资源加载成流
InputStream ins = TestClass.class.getClassLoader().getSourceAsStream("prop.properties");
用该class文件,创建一个对象
Class xx = TestClass.class;

Object obj = xx.newInstance();//无参构造方法

TestClass tc = (TestClass)obj;//转换为该类的对象
Class xx = TestClass.class;

Constructor cst = xx.getConstructor();//无参构造方法
Object obj = cst.newInstance();//这个也不能有参数

TestClass tc = (TestClass)obj;//转换为该类的对象

上面只能使用无参的构造方法来创建无参默认的对象,如果想创建带参的对象

Class xx = TestClass.class;

Constructor cst = xx.getConstructor(String.class,int.class);//指定参数类型的带参构造方法
Object obj = cst.newInstance("李四",18);//顺便指定好实参

TestClass tc = (TestClass)obj;//转换为该类的对象
给类设置属性后,再用它执行方法
@Slf4j
public class ExecuteApiUtil {

    public static final String jwt = Util.getJwt();
    public static final String basePath = "https://xxxx";

    public static Object setBasePathForApi(Class<?> api) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Method setApiClient = api.getMethod("setApiClient", ApiClient.class);
        ApiClient apiClient = new ApiClient();
        apiClient.setBasePath(basePath);
        apiClient.setVerifyingSsl(false);
        Object instance = api.newInstance();
        setApiClient.invoke(instance, apiClient);
        return instance;
    }

    public static void executeApi(Class<? extends BaseControllerApi> apiClass, String methodName, Object... args) {
        try {
            Class<?> api = Class.forName(apiClass.getName());
            Method[] methods = api.getDeclaredMethods();
            Method method = Optional.ofNullable(filterMethod(methods, methodName, args)).orElseThrow(NoSuchMethodException::new);
            method.invoke(setBasePathForApi(api), args);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Method filterMethod(Method[] methods, String methodName, Object... args) {
        for (Method m : methods) {
            String name = m.getName();
            if (name.equals(methodName)) {
                Parameter[] parameters = m.getParameters();
                List<Class<?>> parameterTypes = Lists.newArrayList(m.getParameterTypes());
                List<Class<?>> argsList = Arrays.stream(args).collect(Collectors.toList()).stream().map(arg -> arg.getClass()).collect(Collectors.toList());
                if (parameters.length == args.length && parameterTypes.equals(argsList)) {
                    return m;
                }
            }
        }
        return null;
    }
}
java反射获取方法的参数名

从JDK8开始,反射类添加了Parameter,通过Parameter类,我们可以从.class编译后的文件中获取方法上参数名。

获取参数名的方法:Parameter.getName()

public static List<String> getParameterNameJava8(Class clazz, String methodName) {
	List<String> paramterList = new ArrayList<>();
	Method[] methods = clazz.getDeclaredMethods();
	for (Method method : methods) {
		if (methodName.equals(method.getName())) {
			//直接通过method就能拿到所有的参数
			Parameter[] params = method.getParameters();
			for (Parameter parameter : params) {
                // 获取当参数的名称
				paramterList.add(parameter.getName());
			}
		}
	}
	return paramterList;
}
开启javac编译.class文件保留参数名

默认情况下,JDK8的javac编译后的.class文件是不保留方法的参数名参数名是以arg0,arg1,arg3…表示。

如果要保留参数名,需要给 javac 添加参数 -parameters 开启

Maven配置开启parameters

pom.xml的编译插件中增加参数配置-parameters

<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.3version>
<configuration>
    <source>1.8source>
    <target>1.8target>
    <compilerArgs>
        <arg>-parametersarg>
    compilerArgs>
configuration>
plugin>
Idea配置开启parameters

注意
Object res = method.invoke(instance, args);

Object 对应原方法的返回值,若原方法无返回值,此时 method.invoke() 返回null

method.invoke(null,args);

原方法若为静态方法,此时形参 Object obj可为null

method.invoke(instance,null);

没参数,可以不传 或者 传 null 

若原方法形参列表为空,则Object[] args为null

Method method = Optional.ofNullable(filterMethod(methods, methodName, args)).orElseThrow(NoSuchMethodException::new);
method.setAccessible(false);
method.invoke(instance, args);

若原方法声明为private, 则需要在调用此invoke()方法前显式调用方法对象的setAccessible(true)方法,将可访问private的方法

new 的实例引用的对象是同一个

都为true,内存地址是一样的。在程序的运行中,Personclass文件只被加载了一次。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/919801.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存