Fork me on GitHub

第28章 java反射机制

java反射机制

1.类加载机制

1.1.jvm和类

运行Java程序:java 带有main方法的类名
之后java会启动jvm,并加载字节码(字节码就是一个类在内存空间的状态)

当调用java命令来运行某一个java程序,该命令将会启动一个JVM进程,同一个JVM中的所有线程,变量都处于同一个进程中,共享该JVM的内存区域
当出现一下情况的时候,JVM会退出:
1.程序正常执行结束
2.使用System.exit(0)方法
3.出现异常时,没有捕获异常,此时中断执行
4.平台强制结束JVM进程

JVM进程一旦结束,该进程中内存中的数据就会被丢弃

1.2.类的加载

类加载的流程图:

加载-->连接(验证-->准备-->解析)-->初始化-->使用-->卸载

当程序主动使用某一个类的时候,如果该类还没有被加载到内存中,则系统就会通过加载,连接,初始化三个步骤来对该类进行初始化操作
1.类的加载:
类加载时指将类的class文件(字节码文件)载入内存中,并为之创建一个java.lang.Class对象,我们称之为字节码对象
类的加载过程是有类加载器来完成的,类加载器通常有JVM提供,我们称之为系统加载器,我们也可以集成ClassLoader类来提供自定义加载器
不同的类加载器可以实现加载本地字节码文件,jar包中的字节码,通过网络加载字节码
2.类的连接:
当类被加载到内存之后,系统为之生产一个对应的Class对象,接着把来二进制数据合并到JRE(运行环境)中
1.验证:检测被加载的类是否有真确的内部结构
2.准备:负责为类的static变量分配内存,并设置默认值
3.解析:把类的二进制数据中的符号引用替换为直接引用
3.类的初始化:
在此阶段,JVM负责对类进行初始化,主要就是对satic变量进行初始化
类的初始化过程为:
1.如果该类还未被加载和连接,则先加载并连接该类
2.如果该类的直接父类还未被加载,则先初始化其父类
3.如果类中有初始化语句(静态代码块),则系统一次执行这些初始化语句

符号引用:
符号引用是一个字符串,他给出了被引用的内容的名字并且可能会包含一些其他关于这个引用项的信息--这些信息必须足以唯一的标识一个类,方法,字段

2.什么是反射

具体例子,有一个Student类,用这个类创建了三个实例,那么这三个实例表示对象,是Student类的对象实例,但是在一个java程序中,会有很对像Student这样的类,而java又说一切皆是对象,那么,我们有如果标识一个java程序中不同的类呢?
这就需要反射。
反射就是抽象出在运行的这个java程序中的类的成员信息(构造器,方法,字段,内部类,接口,父类等等),其实就是对java程序中很多但是不相同类的一个抽象。那么这个类就是java.lang.Class类,用来表示不同的java类
可以想象为所有java程序中的类就是这个java.lang.Class的一个对象
反射就是在运行期间,动态的取获取某一个类的成员信息,并把类中的每一个成员都描述成一个新的类。主要包含

Class: 表示素有的类
Constructor: 表示所有的构造器
Method:表示的所有的方法
Filed:表示所有的字段

3.Class类和Class实例

Class类:用来描述类或者接口的类型,描述类的类
Class类的实例:在JVM中的一份份字节码,Class实例表示在JVM中的类或者接口,枚举是一种特殊的类,注解是一种特殊的接口

例如:当程序第一次使用某一个java.util.Date类的时候,就会把该类的字节码对象加载进JVM,并创建一个Class对象
此时的Class对象就表示java.util.Date的字节码
Class类可以表示N个类的字节码对象,那么问题是:到底怎么区分Class类此时表示的哪一个字节码呢?为了解决这个问题,Class的设计者提供了泛型-->Class

3.1.获取Class实例三种方式

如何创建Class对象,如何来表示一个字节码对象呢?
有三种方式,但是最常用的是

//获取字节码对象:Class对象
public class ClassInstanceDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //需求:获取java.util.Date类的字节码对象

        //方式1:使用class属性,每个对象都有class属性
        Class<java.util.Date> clazz = java.util.Date.class;
        //方式2:通过对象的getClass方法来获取,getClass是Object类中的方法
        java.util.Date date = new java.util.Date();
        Class<?> clazz2 = date.getClass();
        //方式3:通过Class类中的静态方法forName(String className)
        Class<?> clazz3 = Class.forName("java.util.Date");//此处必须全限定名,并用双引号引起来

        //打印测试
        System.out.println(clazz);
        System.out.println(clazz2);
        System.out.println(clazz3);
    }
}

注意:同一个类在JVM中只存在一份字节码对象,也就是说上述clazz = clazz2 = clazz3

3.2.九大内置的Class实例

上面的案例中讲述了三种获取Class对象的方式,但是基本数据类型不能表示为对象,所有不能使用getClass的方式。并且基本类型没有类名的概念,也不能使用Class.forName的方式。那么如何表示基本数据类型的字节码对象呢?
答案是所有的基本数据类型都有一个class属性Class clz = 数据类型.class
java中内置的有九大Class实例,该九大包括基本类型的八类和void,使用的时候不用在Class后面添加泛型
使用方法如下:

public class DataTypeClassInstanceDemo {
    public static void main(String[] args) {
        //8大数据类型Class实例
        Class intClass = int.class;
        Class byteClass = byte.class;
        Class shortClass = short.class;
        Class longClass = long.class;
        Class floatClass = float.class;
        Class doubleClass = double.class;
        Class charClass = char.class;
        Class booleanClass = boolean.class;
        //void字节码对象
        Class voidClass = void.class;

        //Integer.class和int.class是不同的
        System.out.println(Integer.class==int.class);//false
        System.out.println(intClass);
        System.out.println(booleanClass);
        System.out.println(voidClass);

        //在8大数据类型的包装类中,都有一个常量TYPE,用于返回该包装类的字节码对象
        System.out.println(Integer.TYPE==int.class);//true
    }
}

3.3.数组的Class实例

数组也是一个对象,所有可以使用上面方法中的第一种和第二种,但是数组不是一个类,所以不能使用第三种。
数组的Class实例:数组是引用数据类型,其实也就是对象
如何来表示数组的Class对象呢?
方式1:数组类型.class;
方式2:数组对象.getClass();
注意:所有具有相同的维数和相同元素类型的数组共享同一份字节码对象,和元素没有关系
示例:

//数组字节码对象
public class ArrayClassInstanceDemo {
    public static void main(String[] args) {

        int[] arr1 = {1,2,3};
        //方式1:数组类型.class;
        Class arr1Class2 = int[].class;
        //方式2:数组对象.getClass();
        Class arr1Class = arr1.getClass();


        //arr1Class和arr2Class是同一份字节码
        System.out.println(arr1Class==arr1Class2);//true

        //所有具有相同的维数和相同元素类型的数组共享同一份字节码对象,和元素没有关系
        int[] arr2 = {5,6,7,8,9};
        System.out.println(arr1.getClass()==arr2.getClass());//true
    }
}

Object和Class的对象:
Class:描述所有的类型,所以Class类中应具有所有类型的相同方法
Object:描述所有对象,所在在Object类中应该具有所有对象的共同方法

4.操作构造器

4.1.获取构造器

需求:通过反射来获取某一个类的构造器
步骤:
1.获取该类的字节码对象
2.从该字节码对象中取找需要获取的构造器

Class获取构造器常用方法有两大类,一类是获取或有的构造器,并返回一个构造器数组;一类是获指定的构造器。每一类中又有两种不同的方法,一种是只获取public,一种是不受修饰符限定,获取所有存在的构造器
1.获取指定单个的构造器
Constructor getConstructor(Class... parameterTypes) 返回一个符合参数的public的构造器,参数是构造器要接收的参数的类型 Constructor getDeclaredConstructor(Class... parameterTypes)
返回一个符合参数的构造器,参数是构造器要接收的参数的类型,这种方法可以获取privite的,不受修饰符限制

2.获取全部的构造器
Constructor[] getConstructors() 返回一个包含所有public的构造器数组,必须是public的 Constructor[] getDeclaredConstructors()
返回一个包含所有构造器的数组,不受修饰符限制

示例:

//一个User类
class User{
    public User(){
        System.out.println("我是无参构造器");
    }
    public User(String name){
        System.out.println("我的构造器是:"+name);
    }
    private User(String name, int age){
        System.out.println("我是两个参数的构造器");
    }

}


//测试获取构造器的方法
public class GetConstruactorDemo {
    public static void main(String[] args) throws Exception {
        getOne();
        getAll();
    }

    
    //获取指定的单个构造器
    private static void getOne() throws Exception {
        //1.获取构造器所在类的字节对象
        Class<User> clz = User.class;
        //2.获取clz对象中的所有构造器
        //需求1:获取 public User()构造器
        Constructor<User> con = clz.getConstructor();
        System.out.println(con);//public rocco.User()
        //需求2:获取 public User(String name)构造器
        con = clz.getConstructor(String.class);
        System.out.println(con);//public rocco.User(java.lang.String)
        //需求3:获取 public User(String name, int age)构造器
        con = clz.getDeclaredConstructor(String.class,int.class);
        System.out.println(con);//private rocco.User(java.lang.String,int)
    }

    //获取所有构造器
    private static void getAll() {
        //1.获取构造器所在类的字节对象
        Class<User> clz = User.class;
        //2.获取clz对象中的所有构造器
        //需求1:获取全部public的构造器
        Constructor<?>[] cs = clz.getConstructors();
        System.out.println(cs.length);//2
        for (Constructor<?> c :cs){
            System.out.println(c);//只包含public的构造器
        }
        //需求2:获取全部的构造器,不受修饰符限制
        cs = clz.getDeclaredConstructors();
        System.out.println(cs.length);//3
        for (Constructor<?> c : cs){
            System.out.println(c);//包含所有构造器
        }

    }
}

4.2.调用构造器创建对象

由于在java框架中,提供给我们的数据大多是字符串的,不不能通过new来创建一个对象,所以,我们利用反射类创建对象。
上面我们获取到构造器的目的是利用构造器来创建一个对象,那么如何利用上面的构造器来创建对象呢?
步骤是:
1.找到构造器所在类的字节码对象
2.获取构造器对象
3.使用反射,创建对象

注意:

1.如果一个类中的构造器是外界可以直接访问的,同时没有参数,那么可以直接使用Class类中的newInstance方法创建对象。public Object newInstance()相当于new 类名
2.访问私有成员的时候,必须先设置可访问:对象.setAccessible(true);

代码示例:

//一个User类
class User{
    public User(){
        System.out.println("我是无参构造器");
    }
    public User(String name){
        System.out.println("我的构造器是:"+name);
    }
    private User(String name, int age){
        System.out.println("我是两个参数的构造器");
    }

}


//测试获取构造器的方法
public class GetConstruactorDemo {
    public static void main(String[] args) throws Exception {
        getOne();
    }
    
    //获取指定的单个构造器
    private static void getOne() throws Exception {
        //1.获取构造器所在类的字节对象
        Class<User> clz = User.class;
        //2.获取clz对象中的所有构造器
        //需求1:创建无参构造器
        //传统方式
        new User();//我是无参构造器
        //反射方法,调用newInstance方法
        Constructor<User> con = clz.getConstructor();
        User user = con.newInstance();//我是无参构造器

        //需求2:创建一个参数的构造器
        con = clz.getConstructor(String.class);
        con.newInstance("rocco");//我的构造器是:rocco

        //需求3:创建一个privited的构造器
        con = clz.getDeclaredConstructor(String.class,int.class);
        //设置当前privited构造器可以访问
        con.setAccessible(true);
        con.newInstance("rocco", 20);//我是两个参数的构造器
    }
}

5.操作方法

5.1.获取类中的方法

获取全部方法
1.getMethods():获取包括自身和继承过来的所有的public方法
2.getDeclaredMethods()方法获取自身所有的方法(不包括继承的,和访问权限无关)

获取指定方法
getMethod(String name, Class<?>... parameterTypes)
返回一个指定的方法,第一个参数是方法名称,第二个参数是方法接收的参数

示例代码:

class User{
    public void doWork(){}
    public void doWork(String name){}
    private String sayHello(String name, int age){
        return name+", "+age;
    }

}

//使用反射获取类中的方法
public class MethodDemo {
    public static void main(String[] args) throws Exception {
//        getAll();
        getOne();
    }



    //获取User类中所有方法
    private static void getAll() {
        Class clz = User.class;
        //Method[] getMethods():获取包括自身和继承过来的所有的public方法
        Method[] ms = clz.getMethods();
        System.out.println(ms.length);//11
        for (Method m : ms){
            System.out.println(m);
        }

        //getDeclaredMethods()方法获取自身所有的方法(不包括继承的,和访问权限无关)
        ms = clz.getDeclaredMethods();
        System.out.println(ms.length);//11
        for (Method m : ms){
            System.out.println(m);
        }
    }

    //获取User类中的指定方法,根据方法的名称和参数来确定
    private static void getOne() throws Exception {
        Class clz = User.class;
        //需求:获取doWork()
        Method m = clz.getMethod("doWork");
        System.out.println(m);//public void method.User.doWork()
        //需求:获取public void doWork(String name)方法
        m = clz.getMethod("doWork", String.class);
        System.out.println(m);//public void method.User.doWork(java.lang.String)
        //需求:获取private String sayHello(String name, int age)方法
        m = clz.getDeclaredMethod("sayHello", String.class, int.class);
        System.out.println(m);//private java.lang.String method.User.sayHello(java.lang.String,int)

    }
}

5.2.调用类中的方法

前面已经找到要用的方法,在这里我们可以使用这个方法了
步骤:
1.获取方法所在类的字节码对象
2.获取方法对象
3.使用反射调用方法
调用一个方法使用invoke(Object obj, Object……args)方法来调用当前Method所表示的方法
接收参数:
obj:表示调用方法底层所属对象,即须有一个对象,才能使用对象的方法
args:表示调用方法是传递的实际参数
返回:返回底层方法返回的结果

调用私有方法
在调用私有方法之前:应该设置该方法为可访问的
示例代码:

class User{
    public void doWork(){
        System.out.println("User.doWork");
    }
    public void doWork(String name){
        System.out.println("User.doWork "+name);
    }
    private void sayHello(String name, int age){
        System.out.println("User.sayHello "+name+" "+age);
    }

}

//使用反射调用类中的方法
public class MethodDemo {
    public static void main(String[] args) throws Exception {
        Class clz = User.class;
        //需求1:调用doWork()
        Method m = clz.getMethod("doWork");
        m.invoke(clz.newInstance());//User.doWork

        //需求2:调用public void doWork(String name)方法
        m = clz.getMethod("doWork", String.class);
        m.invoke(clz.newInstance(),"rocco");//User.doWork rocco

        //需求3:调用private String sayHello(String name, int age)方法
        m = clz.getDeclaredMethod("sayHello", String.class, int.class);
        //设置可访问私有的成员
        m.setAccessible(true);
        m.invoke(clz.newInstance(),"rocco",20);//User.sayHello rocco 20

    }
}

5.3.使用反射调用静态方法和数组参数

调用静态方法
静态方法不属于任何对象,静态方法属于类本身,反射调用静态方法的时候,把invoke()的第一个参数设置为null就可以了

使用反射调用数组参数(可变参数)
使用反射调用数组参数时,王道是在调用方法的时候把实际参数统统作为Object数组的元素即可
Method对象.invoke(方法底层所属对象, new Object[]{所有实参}),此方法通用,在如果是基本数据类型,会自己解包,可以不用这么写,但是如果这样写了也是可以的。

6.操作字段

对字段的操作跟上面的构造器和方法是一样的,获取的时候分为获取所有的字段和指定的字段。获取的时候也会分为public和所有的。用的不多,所以这里不再举例,可以查阅Class的JDK

7.其他API细节

public class OtherAPIDemo {
    public static void main(String[] args) {
        //获取这个类的修饰符
        Class clz = OtherAPIDemo.class;
        int i = clz.getModifiers();//获取修饰符,返回的是整型
        String s = Modifier.toString(i);//将整型转化为对应的修饰符
        System.out.println(s);

        //类的名称
        System.out.println(clz.getName());//method.OtherAPIDemo
        System.out.println(clz.getSimpleName());//OtherAPIDemo

        //操作包
        System.out.println(clz.getPackage());//package method
        System.out.println(clz.getPackage().getName());//method
    }
}
posted @ 2016-12-09 15:59  洋葱源码  阅读(569)  评论(0编辑  收藏  举报