天天看点

黑马程序员——反射机制

------- android培训、java培训、期待与您交流! ----------

1、反射的概念

反射的引入:

Object obj = new Student();

              若程序运行时接收到外部传入的一个对象,该对象的编译类型是Object,但程序又需要调用该对象运行类型的方法:

              1.若编译和运行类型都知道,使用 instanceof判断后,强转。

              2.编译时根本无法预知该对象属于什么类,程序只能依靠运行时信息来发现对象的真实信息,这时就必须使用反射了。

              3.要是想得到对象真正的类型,就得使用反射。

什么是反射机制?  

        简单的来说,反射机制指的是程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。

反射机制的优点与缺点:  

        为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念,  

       静态编译:在编译时确定类型,绑定对象,即通过。  

       动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。  

       一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发。

它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

Class类和Class类实例

Java程序中的各个Java类属于同一类事物,描述这类事物的Java类就是Class类。

对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?

人 Person

Java类  Class

对比提问: Person类代表人,它的实例对象就是张三,李四这样一个个具体的人,Class类代表Java类,它的各个实例对象又分别对应什么呢?

对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等;

一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的;

用类来描述对象,类:描述数据的结构

用元数据来描述Class,MetaData(元数据):描述数据结构的结构;

反射就是得到元数据的行为;

备注:一个类在虚拟机中只有一份字节码;

2、获得Class对象

如何得到各个字节码对应的实例对象?

每个类被加载后,系统会为该类生成对应的Class对象,通过Class对象可以访问到JVM中的这个类,

3种方式:

1、调用某个类的class属性获取Class对象,如Date.class会返回Date类对应的Class对象(其实就是得到一个类的一份字节码文件);

2、使用Class类的forName(String className)静态方法,className表示全限定名;如String的全限定名:java.lang.String;

3、调用某个对象的getClass()方法。该方法属于Object类;

Class<?>clz = new Date().getClass();

Eg:

package junereflect624;

public class ClassDemo1 {

    public static void main(String[] args) throws Exception {

        //获得Class对象的方法(三种)

        //一:调用属性

        Class<String> c =String.class;

        System.out.println(c);//打印结果:classjava.lang.String                String.class就表示JVM中一份表示String类的字节码

        Class<String> c2 =String.class;

        System.out.println(c == c2);//true都是String类的字节码       一个类在虚拟机中只有一份字节码;

        //二:使用forName()方法

        //Class cla =Class.forName("String");//ERROR,

        Class<String> cla=                                                         (Class<String>)Class.forName("java.lang.String");//必须用上全限定名,否则报错

        System.out.println(c == cla);//true

        //三:利用对象调用Object的getClass方法;

        Class c3 = new String().getClass();

        System.out.println(c == c3);//ture

    }

}

我的总结:获取Class对象最常用的是利用属性的方法!

3、九个预定义Class对象

基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void通过class属性也表示为 Class 对象;

Class类中boolean isPrimitive() :判定指定的 Class 对象是否表示一个基本类型。

包装类和Void类的静态TYPE字段;

Integer.TYPE == int.class ;         

Integer.class == int.class;             

 数组类型的Class实例对象:

Class<String[]> clz = String[].class;

数组的Class对象如何比较是否相等? 数组的维数和数组的类型;

Class类中 boolean isArray() :判定此 Class 对象是否表示一个数组类型。

package junereflect624;

public class PreClassDemo2 {

    public static void main(String[] args) {

        Class<?> in = int.class;

        System.out.println(in);//int

        Class<?> in2 =Integer.class;

        //包装类都有一个常量TYPE,用来表示其基本数据类型的字节码

        Class<?> in3 =Integer.TYPE;

        System.out.println(in2);//class java.lang.Integer

        System.out.println(in3);//int

        System.out.println(in3 == in);//true包装类都有一个常量TYPE,用来表示其基本数据类型的字节码,所以这里会相等!

        System.out.println(in3 == in2);//false

        Class<String[]> s= String [].class;

        Class<int[]> i = int [].class;

        //System.out.println(i==s);//编译根本就通过不了,一个是int,一个是String

    }

    //这两个自定义的方法是可以的,一个int,一个Integer//包装类与基本数据类型的字节码是不一样的

    public void show(int i){}

    public void show(Integer i){}

}

4、利用Class获取类的属性信息

package junereflect624;

import java.lang.reflect.Modifier;

class A { 

}

interface B{

}

interface C{

}

public class BaseDemo3 extends A implements B,C{

    //内部类

    public class C{}

    public interface D{}

    public static void main(String[] args) {

        //类可以,接口也可以

        Class<BaseDemo3> c= BaseDemo3.class;

        System.out.println(c);//class junereflect624.BaseDemo3

        //得到包名

        System.out.println(c.getPackage());//package junereflect624

        //得到全限定名

        System.out.println(c.getName());//junereflect624.BaseDemo3

        //得到类的简称

        System.out.println(c.getSimpleName());//BaseDemo3

        //得到父类

        System.out.println(c.getSuperclass().getSimpleName());//A,先获取父类,再获取父类的简称

        //得到接口

        System.out.println(c.getInterfaces());//[Ljava.lang.Class;@1b60280

        Class[] arr =c.getInterfaces();

        for (Class cla : arr) {

            System.out.println(cla);//interfacejunereflect624.B   interfacejunereflect624.C

        }

        //获得public修饰的类

        Class[] cl =c.getClasses();

        System.out.println(cl.length);//在内部类没有加上public修饰的时候长度为0,加上就是2(获取的是公共的)

        for (Class class1 : cl) {

            System.out.println(class1);

        }

        //获得修饰符

        int i = c.getModifiers();

        System.out.println(i);//常量值1表示public

        System.out.println(Modifier.toString(i));//直接打印出public

    }

}

5、Class中得到构造方法Constructor、方法Method、字段Field

常用方法:

Constructor类用于描述类中的构造方法:

Constructor<T>getConstructor(Class<?>... parameterTypes)

返回该Class对象表示类的指定的public构造方法;

Constructor<?>[] getConstructors()

返回该Class对象表示类的所有public构造方法;

Constructor<T>getDeclaredConstructor(Class<?>... parameterTypes)

返回该Class对象表示类的指定的构造方法,和访问权限无关;

Constructor<?>[]getDeclaredConstructors()

返回该Class对象表示类的所有构造方法,和访问权限无关;

Method类用于描述类中的方法:

Method getMethod(String name,Class<?> ... parameterTypes)

返回该Class对象表示类和其父类的指定的public方法;

Method[] getMethods(): 

返回该Class对象表示类和其父类的所有public方法;

Method getDeclaredMethod(String name,Class<?>... parameterTypes)

返回该Class对象表示类的指定的方法。和访问权限无关,但不包括继承的方法;

Method[] getDeclaredMethods(): 获得类所有的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法;

Eg:

package junereflect624;

import java.lang.reflect.Constructor;

class Emp{

    private String name;

    private int age;

    private Emp() {

    }

    Emp(String name){

    }

    public Emp(String name,int age){

    }

}

public class ConstructorDemo4 {

    public static void main(String[] args) throws Exception {

        //得到所有的构造器(先得到类)

        Class<Emp> c =Emp.class;

        Constructor[] con= c.getConstructors();//前面的修饰符必须是public才可以在这个方法下获取到

        for (Constructor cons : con) {

            System.out.println("c.getConstructors()"+cons);//如果上面的某构造器public去掉,则显示不出

        }

        //得到指定的构造器,也是必须public

        Constructor c1 =c.getConstructor(String.class,int.class);

        System.out.println(c1);//public junereflect624.Emp(java.lang.String,int)

System.out.println("====================================");

    //现在想获得不受public影响的,getDeclaredConstructors(),暴力反射

        con =c.getDeclaredConstructors();

        for (Constructor cons : con) {

System.out.println("c.getDeclaredConstructors()=="+cons);//此时不受修饰符的影响

        }

    }

}

package junereflect624;

importjava.lang.annotation.Annotation;

importjava.lang.reflect.Field;

import java.lang.reflect.Method;

class AB{

    protected String name;

    protected String id;

}

@Deprecated

public class MethodDemo5extends AB{

    void show(){}

    public void say(){}

    private int age;

    public char c;

    private boolean b;

    public static void main(String[] args) throws Exception {

        Class<MethodDemo5>c = MethodDemo5.class;

        //获取所有的(包含父类的方法)public修饰的方法

        Method[] m =c.getMethods();

        for (Method method : m){

            System.out.println(method);

        }

        //总结:4个方法,获取全部,获取特定;不受修饰符影响的全部,不受修饰符影响的特定;(前两个都还是受限制的)

        //获取指定的方法

        Method me =c.getMethod("main", String[].class);

        System.out.println("main"+me);//main public static voidjunereflect624.MethodDemo5.main(java.lang.String[]) throws java.lang.Exception

        //访问所有方法,不受访问权限影响

        m =c.getDeclaredMethods();

        for (Method method : m){

            System.out.println("不受影响的:"+method);

        }

        me =c.getDeclaredMethod("show");

        System.out.println(me);//voidjunereflect624.MethodDemo.show()

        me =c.getMethod("toString");

        System.out.println(me);//publicjava.lang.String java.lang.Object.toString()

        //得到字段

        Field[] f =c.getFields();

        for (Field field : f){//只得到了public的

            System.out.println("字段"+field);

        }

        //特定字段

        Field fi =c.getField("c");//""里面是名称

        System.out.println(fi);//publicchar junereflect624.MethodDemo.c

        //得到不受限定名限定的全部字段

        f =c.getDeclaredFields();

        for (Field field : f){//得到不受修饰符限定的字段,但是只对当前类有效

            System.out.println("全部字段:"+field);

        }

        //注释  Annotation

         Annotation[] a = c.getAnnotations();

         System.out.println(a.length);

         for (Annotation annotation : a) {

            System.out.println(annotation);

        }

         //特定注解

         Deprecated d =c.getAnnotation(Deprecated.class);

         System.out.println(d);

    }

}

获取当前对象的字段:

package july78javaEnhance;

import java.lang.reflect.Field;

class Stu{

    public String name;

    public String sex;

    public int age;

    public Stu(String name, Stringsex, int age) {

        super();

        this.name = name;

        this.sex = sex;

        this.age = age;

    }

}

public class ReflectDemo6 {

    public static void main(String[] args) throws Exception {

        Stu s = new Stu("刘昭", "男", 12);

        Class<Stu> c =Stu.class;

        Field f = c.getField("name");

        System.out.println(f.get(s));////从哪个对象身上取!此时显示刘昭!

//  修改对象的值

    }

}

我的总结:对于方法,字段,构造方法之类用类获取记住四个:获取全部,获取特定,暴力获取全部,暴力获取特定!

6、利用反射创建对象

创建对象:

1、使用Class对象的newInstance()方法创建该Class对象的实例,此时该Class对象必须要有无参数的构造方法。

2、使用Class对象获取指定的Constructor对象,再调用Constructor的newInstance()方法创建对象类的实例,此时可以选择使用某个构造方法。如果这个构造方法被私有化起来,那么必须先申请访问,将可以访问设置为true;

Eg:

最简单的:

package junereflect624;

class User{

    public String toString() {

        return "User对象创建成功!";

    }

}

public class NewInstanceDemo6 {

    public static void main(String[] args) throws Exception {

        //传统方式创建对象

         System.out.println(new User());

         //使用反射的方式

         Class<User> c = User.class;

         User u = c.newInstance();(直接newInstance的话必须保证默认的构造方法正常存在,也就是没有被私有化!这是前提条件)

         System.out.println(u);

    }

}

复杂点的:更强大的第二种:

使用指定构造方法来创建对象:

获取该类的Class对象。

利用Class对象的getConstructor()方法来获取指定的构造方法。

调用Constructor的newInstance()方法创建对象。

AccessibleObject对象的setAccessible(boolean flag)方法,当flag为true的时候,就会忽略访问权限(可访问私有的成员)。

其子类有Field, Method, Constructor;

若要访问对象private的成员?

在调用之前使用setAccessible(true),

       Xxx x = getDeclaredXxxx();//才能得到私有的类字段.

总结步骤:

1.       获取该类的Class对象。

2.       利用Class对象的getConstructor()方法来获取指定的构造方法。

3.       申请访问(设置为可访问)

4. 调用Constructor(构造方法)的newInstance()方法创建对象。

例子

package junereflect624;

import java.lang.reflect.Constructor;

class Per{

    private String name;

    private int age;

    private Per(){   

    }

    private Per(String name){

    }

    public String toString() {

        return "对象!!!";

    }

}

public class NewInstanceDemo7 {

    public static void main(String[] args) throws Exception {

        Class<Per> c =Per.class;

        //System.out.println(c.newInstance());;//证明利用无参的可以

        先获得需要被调用的构造器(private 修饰的构造方法)

        Constructor<Per>con = c.getDeclaredConstructor();//调用默认的,什么都不要写

        System.out.println(con);//private junereflect624.Per()

        //现在只需要执行这个构造器,

        //私有的成员是受保护的,不能直接访问

        //若要访问私有的成员,得先申请一下

        con.setAccessible(true);//允许访问

        Per p =con.newInstance();//成功,通过私有的受保护的构造方法创建了对象

        System.out.println("无参构造方法"+p);

        con =c.getDeclaredConstructor(String.class);

        System.out.println(con);//private junereflect624.Per(java.lang.String)

        con.setAccessible(true);//允许访问

        p = con.newInstance("liuzhao");//成功,通过私有的受保护的构造方法创建了对象

        System.out.println("String构造方法"+p);

    }

}

备注:对于此时的话,单例模式就不再安全了!反射可破之!!

验证:对于枚举而言,反射依然没有办法重新创建对象

对于枚举,安全!

package junereflect624;

import java.lang.reflect.Constructor;

enum Color{

    RED,BLUE,GREEN;

    private Color(){

    }

}

public class EnumDemo8 {

    public static void main(String[] args) throws Exception {

        Class<Color> c =Color.class;

        Constructor<Color>con = c.getDeclaredConstructor();//(错误在这一行发生,就是说对枚举而言这种方法连构造器都获得不了,)编译可以通过,但是运行就通不过了!

        Color co = (Color)con.newInstance();

        System.out.println(co);//失败,证明对枚举而言不行,所以枚举的单例模式更加安全

        System.out.println(c.isEnum());//true是枚举

    }

}

7、使用反射调用方法

每个Method的对象对应一个具体的底层方法。获得Method对象后,程序可以使用Method里面的invoke方法来执行该底层方法。

Objectinvoke(Object obj,Object ... args):obj表示调用底层方法的对象,后面的args表示传递的实际参数。

如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为null,想想为什么?

如果底层方法所需的形参个数为 0,则所提供的 args 数组长度可以为 0 或 null。

不写,null,或 newObject[]{}

若底层方法返回的是数组类型,invoke方法返回的不是底层方法的值,而是底层方法的返回类型;

package junereflect624;

import java.lang.reflect.Method;

class Dept{

    public String show(String name){//用反射的方法来调用正常的方法

        return name+",您好!";

    }

    private void privateshow(){//用反射来实现对私有化方法的调用

        System.out.println("privateshow");

    }

    public static void staticshow(){

        System.out.println("staticshow");

    }

}

public class InvokeDemo9 {

    public static void main(String[] args) throws Exception {

        //想要通过反射来调用Dept中的方法

        Class<Dept> c =Dept.class;

        Method m = c.getMethod("show", String.class);

        Object o =m.invoke(c.newInstance(), "刘昭");

        System.out.println(o);

        //私有化的方法

        m = c.getDeclaredMethod("privateshow");//无参方法

        m.setAccessible(true);

        o =m.invoke(c.newInstance());

        //静态方法的调用

        m = c.getMethod("staticshow");

        m.invoke(null);//staticshow为静态方法,不需创建对象,所以这里会是null

    }

}

打印

刘昭,您好!

privateshow

staticshow

8、使用反射调用可变参数方法

使用反射操作对象-调用可变参数方法

要把可变参数都当做是其对应的数组类型参数;

如 show(XX... is)作为show(XX[] is)调用;

若可变参数元素类型是引用类型:

JDK内部接收到参数之后,会自动拆包取出参数再分配给该底层方法,为此我们需要把这个数组实参先包装成一个Object对象或把实际参数作为一个Object一维数组的元素再传递。

若可变参数元素类型是基本类型:

JDK内部接收到参数之后,不会拆包,所以可以不必再封装.不过封装了也不会错.所以建议,不管基本类型还是引用类型都使用Object[]封装一层,保证无误.

例子

package junereflect624;

//可变参数的方法调用

import java.lang.reflect.Method;

class VaryMethod{

    public static void show(int ...args){

        System.out.println("基本数据类型传递过来了!");

    }

    public static void show(String ...args){

        System.out.println("引用数据类型传递过来了!");

    }

}

public class InvokeVaryDemo10 {

    public static void main(String[] args) throws Exception{

        Class<VaryMethod>c = VaryMethod.class;

        Method m = c.getMethod("show",int[].class);

        m.invoke(null,new int[]{1,2,3});

        m = c.getMethod("show",String[].class);

        //m.invoke(null,newString[]{"A","B","C"});//ERROR

        m.invoke(null,(Object)new String[]{"A","B","C"});//YES,强转为Object类型

        m.invoke(null,new Object[]{new String[]{"A","B","C"}});//推荐写法

    }

}

9、使用反射操作字段

Field提供两组方法操作字段:

xxxgetXxx(Object obj):获取obj对象该Field的字段值,此处的xxx表示8个基本数据类型。若该字段的类型是引用数据类型则使用,Object get(Object obj);

voidsetXxx(Object obj,xxx val):将obj对象的该Field字段设置成val值,此处的xxx表示8个基本数据类型。若该字段的类型是引用数据类型则使用,void set(Object obj, Object value);

package junereflect624;

//获取字符,并且赋值,然后再取出来(对应的去查看api,比如这个是Field,别的比如Constructor,Method)

步骤:

1.获取类

2.获取字段

3.赋值(set(c.newInstance(),””));{如果为私有的话设置可接受}

import java.lang.reflect.Field;

class Cat{

    private String name;

    public int age;

    private String color;

}

public class FieldDemo12 {

    public static void main(String[] args) throws Exception {

        Class<Cat> clz =Cat.class;

        Field[] f =clz.getDeclaredFields();

        for (Field field : f) {

            System.out.println(field);

        }

        Field fi =clz.getDeclaredField("name");

        System.out.println(fi);

        System.out.println(fi.getName());//name

        //核心开始

        Cat c = clz.newInstance();

        fi.setAccessible(true);

        fi.set(c, "刘昭");//赋值成功

        Object o = fi.get(c);

        System.out.println(o);//取出成功

        fi =clz.getDeclaredField("age");

        fi.setAccessible(true);

        fi.set(c, 21);

        int i = fi.getInt(c);//左边的接受类型已经写成了int,右边的返回类型就也必须是int

        System.out.println(i);//获取成功

    }

}

打印

private java.lang.Stringjunereflect624.Cat.name

public intjunereflect624.Cat.age

private java.lang.Stringjunereflect624.Cat.color

private java.lang.Stringjunereflect624.Cat.name

name

刘昭

21

10、反射和泛型-反射来获取泛型信息

通过指定对应的Class对象,程序可以获得该类里面所有的Field,不管该Field使用private 方法public。获得Field对象后都可以使用getType()来获取其类型。

Class<?>type = f.getType();//获得字段的类型

但此方法只对普通Field有效,若该Field有泛型修饰,则不能准确得到该Field的泛型参数,如Map<String,Integer>;

为了获得指定Field的泛型类型,我们采用:

TypegType = f.getGenericType();得到泛型类型

然后将Type对象强转为ParameterizedType,其表示增加泛型后的类型

TypegetRawType()//返回被泛型限制的类型;

Type[]  getActualTypeArguments()//返回泛型参数类型;

利用反射来获取泛型的类型(泛型信息)

步骤:

1.获取当前类

2.获取目标字段

3.获取包含泛型类型的类型 getGenericType()

4.强转至子类ParameterizedType 因为Type没有任何对应的方法

5.获得泛型真正的类型 getActualTypeArguments()

例子

package junereflect624;

import java.lang.reflect.Field;

importjava.lang.reflect.ParameterizedType;

import java.lang.reflect.Type;

import java.util.HashMap;

import java.util.Map;

public class GetGenericTypeDemo14 {

    Map<String,Integer>map = new HashMap<String,Integer>();

    publicstatic void main(String[] args) throws Exception {

        Class c = GetGenericTypeDemo14.class;

        Field f =c.getDeclaredField("map");

        System.out.println(f);

        System.out.println(f.getName());//map

        // Class<?> getType()  返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。

        Class cl = f.getType();

        System.out.println("获得其类型:"+cl);

//获得其类型:interface java.util.Map

        Type t = f.getGenericType();//包含泛型的类型

        System.out.println(t);

//java.util.Map<java.lang.String, java.lang.Integer>

        ParameterizedType pt =(ParameterizedType)t;//强转到其子类

        t = pt.getRawType();//类型的类或接口

        System.out.println(t);

        Type[] ts = pt.getActualTypeArguments();

        for (Type type : ts) {

            System.out.println(type);

        }

    }

}

打印:

java.util.Mapjunereflect624.GetGenericTypeDemo14.map

map

获得其类型:interfacejava.util.Map

java.util.Map<java.lang.String,java.lang.Integer>

interface java.util.Map

class java.lang.String

class java.lang.Integer

我的总结:多查找api,参考api中方法使用的限制,比如是否静态、返回值类型等。

------- android培训、java培训、期待与您交流! ----------