zoukankan      html  css  js  c++  java
  • java反射

    为什么要使用反射?

    Object obj = new Student();

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

    1.如果编译和运行时的类型都知道,使用instance of 判断后,强转。

    2.编译时根本无法预知该对象属于什么类,只有依靠运行时才能知道对象的类型,就要依靠反射了。

    静态编译:在编译时确定类型。

    动态编译:在运行时确定类型。降低类之间的耦合。

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

    编译的时候无法确定类型,只有到运行的时候才能确定类型

    字节码:一个类被类加载器加载到内存中,占用一片存储空间,空间里面的内容就是类的字节码,不同的类的字节码不同

    Class类的实例就是字节码:

    一个类在虚拟机中只有一份字节码

    获得Class对象

     

    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();

    public static void main(String[] args) throws Exception {
            //获得Class对象的方法(三种)
            //一:调用属性
            Class<String> c = String.class;
            System.out.println(c);//打印结果:class java.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类中boolean isPrimitive() :判定指定的 Class 对象是否表示一个基本类型。

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

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

     

    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获取类的属性信息

     

    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
            
            //得到父类
            /**
             * Class<? super T> getSuperclass() 此处super表示下限
                      返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。 
             */
            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);//interface junereflect624.B   interface junereflect624.C
            }
            
            //获得public修饰的类
            /**
             * Class<?>[] getClasses() 
                         返回一个包含某些 Class 对象的数组,这些对象表示属于此 Class 对象所表示的类的成员的所有公共类和接口。 (如果内部类前面没有加上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
        }
    }

    5Class中得到构造方法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(): 获得类所有的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法;

     

    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<?>[] getConstructors() 
                      返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。 
             */
            Constructor[] con = c.getConstructors();//前面的修饰符必须是public才可以在这个方法下获取到
            for (Constructor cons : con) {
                System.out.println("c.getConstructors()"+cons);//如果上面的某构造器public去掉,则显示不出
                /**打印
                    public junereflect624.Emp(java.lang.String,int)
                 */
            }
            
            //得到指定的构造器,也是必须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);//此时不受修饰符的影响
                /**打印
                 *  public junereflect624.Emp()
                    public junereflect624.Emp(java.lang.String)
                    public junereflect624.Emp(java.lang.String,int)
                 */
            }
        }
    }

    获得方法和字段

    class AB{
        protected String name;
        protected String id;
    }
    @Deprecated
    public class MethodDemo5 extends 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 void junereflect624.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);//void junereflect624.MethodDemo.show()
    
            me = c.getMethod("toString");
            System.out.println(me);//public java.lang.String java.lang.Object.toString()
            
            /**
             * Method[] getDeclaredMethods() 
                      返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,
                      包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法,只可以对当前类有效
             */
            /*me = c.getDeclaredMethod("toString");//ERROR,c.getDeclaredMethod()不能得到继承的方法
            System.out.println(me);//public java.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);//public char junereflect624.MethodDemo.c
            
            //得到不受限定名限定的全部字段
            f = c.getDeclaredFields();
            for (Field field : f) {//得到不受修饰符限定的字段,但是只对当前类有效
                System.out.println("全部字段:"+field);
                /**
                 *  全部字段:private int junereflect624.MethodDemo.age
                    全部字段:public char junereflect624.MethodDemo.c
                    全部字段:private boolean junereflect624.MethodDemo.b
                 */
            }
            //注释  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);
        }
    }

    获取当前对象的字段:

     

    class Stu{
        public String name;
        public String sex;
        public int age;
        
        public Stu(String name, String sex, 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));////从哪个对象身上取!此时显示刘昭!
    //    修改对象的值
    /**
    Field f = c.getField("name");
            f.set(s,"章泽天");
    System.out.println(f.get(s));//从哪个对象身上取!//此时显示章泽天
    */
        }
    }

    6、利用反射创建对象

    创建对象:

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

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

    class User{
        /*private 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()方法来获取指定的构造方法。

    调用ConstructornewInstance()方法创建对象。

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

    其子类有Field, Method, Constructor;

    若要访问对象private的成员?

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

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

    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 = c.getDeclaredConstructor(String.class);获取指定的构造方法
            System.out.println(con);//private junereflect624.Per(java.lang.String)*/        
            //现在只需要执行这个构造器,
            /**
             *  T newInstance(Object... initargs) 
                     使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。 
             */
            
            //私有的成员是受保护的,不能直接访问
            //若要访问私有的成员,得先申请一下
            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);
        }
    }

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

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

    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方法来执行该底层方法。

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

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

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

    不写,null,或 new Object[]{}

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

    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 {
            
    /*    传统方式:
    String name = new Dept().show("刘昭");
            System.out.println(name);*/
                
    /**
             * Method getMethod(String name, Class<?>... parameterTypes) 
                      返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指            定公共成员方法。 
                name - 方法名
                parameterTypes - 参数列表 
             */
            //想要通过反射来调用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
        }
    }

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

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

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

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

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

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

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

    class VaryMethod{
        public static void show(int ...args){
            System.out.println("基本数据类型传递过来了!");
        }
        /*public static void show(int[] args){//这是一样的
        }*/
        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,new String[]{"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提供两组方法操作字段:

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

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

     

    步骤:

     

    1.获取类

     

    2.获取字段

     

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

     

    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
            
            //核心开始
            /**
             *  void set(Object obj, Object value) 
    将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 
             */
            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);//获取成功
        }
    }

     

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

     

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

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

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

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

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

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

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

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

     

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

    步骤:

    1.获取当前类

    2.获取目标字段

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

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

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

     

     

     

    public class GetGenericTypeDemo14 {
        Map<String,Integer> map = new HashMap<String,Integer>();
        
        public static 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 getGenericType() 返回一个 Type 对象,它表示此 Field 对象所表示字段的声明类型。 
             *  Type是Class的接口;
             */
            Type t = f.getGenericType();//包含泛型的类型
            System.out.println(t);
    //java.util.Map<java.lang.String, java.lang.Integer>
            
    
            /**
             * Type这个类里面没有任何的方法,所以需要调用子类的方法,那么大的类型转到小的类型,需要强转!
             */
            ParameterizedType pt = (ParameterizedType)t;//强转到其子类
            /**
             *  Type[] getActualTypeArguments() 
                          返回表示此类型实际类型参数的 Type对象的数组。 
                 Type getOwnerType() 
                          返回 Type 对象,表示此类型是其成员之一的类型。 
                 Type getRawType() 
                          返回 Type 对象,表示声明此类型的类或接口。 
             */
            
            t = pt.getRawType();//类型的类或接口
            System.out.println(t);
            
            Type[] ts = pt.getActualTypeArguments();
            for (Type type : ts) {
                System.out.println(type);
                /**
                 *  class java.lang.String
                    class java.lang.Integer
                 */
            }
        }
    }

     

     

  • 相关阅读:
    线程安全 ---Struts1与Struts2
    sql servset 有表,没表备份数据。
    group by 使用及体会 和having用法
    js 去掉输入的空格
    struts + json + ajax +级联 例子
    js 级联 思路
    return break continue 的用法和作用
    eclise 怎么集成 spket 。及spket 与 sdk.jsb3管理起来
    logback基本用法
    log4j:特定类的日志输出到指定的日志文件中
  • 原文地址:https://www.cnblogs.com/cherryhimi/p/4075362.html
Copyright © 2011-2022 走看看