• 反射笔记


     

    目录

    类的生命周期:

    类的主动使用被动使用和类的初始化时机:

    反射的使用:

    Class.forName()读取配置文件举例

    通过反射获取构造方法并使用

    通过反射获取成员变量并使用

    通过反射获取方法并使用

    通过反射越过泛型检查

    通过反射写一个通用的方法,设置某个对象的某个属性为指定的值

    小练习:

    动态代理的概述和实现


    关于反射的更详细讲解可以看这位博主https://blog.csdn.net/sinat_38259539/article/details/71799078

     

    类的生命周期:


          在一个类编译完成之后,下一步就需要开始使用类,如果要使用一个类,肯定离不开JVM。在程序执行中JVM通过加载,链连接,初始化这3个步骤完成。 
    类加载器工作机制: 
        类的加载是通过类加载器完成的,加载器将.class文件的二进制文件装入JVM的方法区,并且在堆区创建描述这个类的java.lang.Class对象。用来封装数据。 但是同一个类只会被类加载器加载一次 
    1:加载:指将类的class文件读入内存,并为之创建一个java.lang.Class对象,即当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
    2:连接就是把二进制数据组装为可以运行的状态。 

        连接分为验证,准备,解析这3个阶段 


        验证:一般用来确认此二进制文件是否适合当前的JVM(版本),验证阶段用于检验被夹在的类是否有正确的内部结构,并和其他类协调一致。
        准备:类准备阶段负责为类变量分配内存,并设置默认的初始值。
        解析:指的是转换常量池中的代码作为直接引用的过程,直到所有的符号引用都可以被运行程序使用(建立完整的对应关系) 


    3:初始化:在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化。

             JVM最先初始化的总是java.lang.Object类。当程序主动使用任何一个类时,系统会保证该类以及所有父类(包括直接父类和间接父类)都会被初始化。

        类初始化的时机:

    1. 创建类的实例。为某个类创建实例的方式包括:使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例。

    2.调用某个类的类方法(静态方法)。

    3.访问某个类或接口的类变量,或为该类变量赋值。

    4.使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。例如Class.forName("Person"),如果系统还未初始化Person类,则这行代码会导致该Person类被初始化,并返回Person类对应的java.lang.Class对象。

    5.初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化。

    6.直接使用java.exe命令来运行某个主类。当运行某个主类时,程序会先初始化该主类。

    class Base {
        static int num = 1;
        
        static {
            System.out.println("Base " + num);
        }
    }
    public class Main {
        public static void main(String[] args) throws ClassNotFoundException {
            // 不会初始化静态块
            Class<?> class1 = Base.class;
            System.out.println("------");
            // 会初始化
            Class<?> class2 = Class.forName("my.Base");
            // 会初始化,先注释上面可看到效果
            /*Base p = new Base();
            Class<?> class3 = p.getClass();*/
        }
    }

    运行结果:

    ------
    Base 1


    使用功能”.class”来创建Class对象的引用时,不会自动初始化该Class对象,使用forName()会自动初始化该Class对象。

            对于一个final型的类变量,如果该类变量的值在编译时就可以确定下来,那么这个类变量相当于“宏变量”。java编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使程序使用该类变量,也不会导致该类的初始化。如下:

    class MyTest{
        static {
            System.out.println("MyTest静态初始化块");
        }
        static final String compileConstant = "执行我无需初始化MyTest类";
    }
    
    public class Main {
        public static void main(String[] args) {
            System.out.println(MyTest.compileConstant);
        }
    }

    运行结果:

    执行我无需初始化MyTest类

            当某个类变量(静态变量)使用了final修饰,而且它的值可以在编译时就确定下来,那么程序其他地方使用该类变量时,实际上并没有使用该类变量,而是相当于使用了常量。

            反之,如果final修饰的类变量的值不能在编译时确定下来,则必须等到运行时才可以确定该类变量的值,如果通过该类来访问它的类变量,则会导致该类被初始化。

    比如刚刚的代码改为static final String compileConstant = System.currentTimeMillis() + "";

            因为上面定义的compileConstant类变量的值必须在运行时才可以确定,所以这里必须保留对MyTest类的类变量的引用,这行代码就变成了使用MyTest的类变量,这将导致MyTest类被初始化。

    类的主动使用被动使用和类的初始化时机:

            当使用ClassLoader类的loadClass()方法来加载这个类时,该方法只是加载该类,并不会执行该类的初始化。使用Class的forName()静态方法才会导致强制初始化该类。


           类加载器工作由ClassLoader及其子类负责,ClassLoader是一个重要的java运行时系统组件,负责在运行时查找和加载Class字节码文件。 
    java在运行时会产生三个ClassLoader:根加载器、ExtClassLoader(扩展类加载器)、AppClassLoader(系统类加载器)。 
            其中根加载器不是ClassLoader的子类,它由C++编写,根加载器负责加载jre的核心类库。 
    ExtClassLoader(扩展类加载器)、AppClassLoader(系统类加载器)是ClassLoader的子类,AppClassLoader 是ExtClassLoader 的子类; 
    ExtClassLoader负责加载jre扩展目录ext中的jar包;AppClassLoader负责加载ClassPath路径下的类包。 
    默认情况,使用AppClassLoader加载应用程序的类。

    public class demo {
       public static void main(String[] args) {
            ClassLoader loader=Thread.currentThread().getContextClassLoader();
            System. out.println("current loader=" +loader);
            System. out.println("parent loader=" +loader.getParent());
            System. out.println("grandParent loader="+loader.getParent().getParent());
      }
    }
    

    结果: 
    current loader=sun.misc.Launcher$AppClassLoader@addbf1 
    parent loader=sun.misc.Launcher$ExtClassLoader@42e816 
    grandParent loader=null

    全盘负责委托机制: 
        指当一个ClassLoader装载一个类的时候,除非显示的使用另一个ClassLoader,该类依赖及引用的类也有这个ClassLoader;“委托机制”指先委托父加载器寻找目标类,只有在找不到的情况下,才从自己的类路径中查找并加载目标类。 
    当没有任何引用指向Class对象时就会被卸载,结束类的生命周期 
    java.lang 
    类 Class<T> 
    Java程序中的各个Java类属于同一类事物,描述这类事物的java类名就是Class; 
    Class类无构造方法; 
    Class类所表示的对象是 对应的一些字节码(*.class); 
    对于加载到jvm的字节码,就表示一个Class类的对象; 

    对于字节码(Class类的对象) 
        1:对应的类已经加载到jvm中,直接得到该类的字节码 
        2:未加载到jvm中,就先加载然后得到。 
    获得一个类的字节码对应的实例对象(Class类实例) 
        1:类名.class 
        2:有对象 对象.getClass(); 
        3: static Class<?>forName(String className) 
    返回与带有给定字符串名的类或接口相关联的 Class 对象。 
    ps:做反射的时候常用第三种方法。 
    基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。

    反射的使用:

    三种方式获取的是同一字节码对象。

    package my.reflect;
    
    import my.bean.Person;
    
    public class Demo1_Reflect {
        public static void main(String[] args) throws ClassNotFoundException {
            // 常被用来读取配置文件
            Class<?> class1 = Class.forName("my.bean.Person"); // 第一种Class.forName获取
            // 常被用来当做锁对象
            Class<?> class2 = Person.class; // 第二种字节码对象获取
            Person p = new Person();
            // 常被用来判断是否是同一字节码文件
            Class<?> class3 = p.getClass(); // 第三种通过对象反射获取
            System.out.println(class1 == class2);
            System.out.println(class3 == class2);
        }
    }
    

    运行结果:

    true

    true

    javabean文件

    package my.bean;
    
    public class Person {
        private int age;
        private String name;
    
        public Person() {
            super();
            // TODO Auto-generated constructor stub
        }
    
        public Person(int age, String name) {
            super();
            this.age = age;
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        @Override
        public String toString() {
            return "Person [age=" + age + ", name=" + name + "]";
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + age;
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            return result;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Person other = (Person) obj;
            if (age != other.age)
                return false;
            if (name == null) {
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))
                return false;
            return true;
        }
    
        public void eat() {
            System.out.println("今天吃饱了");
        }
    
        private void eat(int num) {
            System.out.println("今天吃饱了" + num + "顿");
        }
    }

    Class.forName()读取配置文件举例

    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    
    public class Demo2_Reflect {
        public static void main(String[] args) throws ClassNotFoundException, IOException, InstantiationException, ReflectiveOperationException {
            Juicer j = new Juicer();
            // j.run(new Apple());
            // j.run(new Orange());
            BufferedReader br = new BufferedReader(new FileReader("config.properties")); // 配置文件写什么类这边就读的什么类
            Class<?> class1 = Class.forName(br.readLine()); // 获取字节码对象
            // 得到字节码对象的对象,比如Apple.class,那么下面这句相当于new Apple()
            Fruit f = (Fruit) class1.newInstance(); // 不要强转具体类型,也不要用具体类型接收,这样只需要改配置文件就好
            j.run(f);
        }
    }
    
    interface Fruit{
        public void squeeze();
    }
    
    class Apple implements Fruit{
        @Override
        public void squeeze() {
            System.out.println("榨出一杯苹果汁"); 
        }
    }
    
    class Orange implements Fruit{
        @Override
        public void squeeze() {
            System.out.println("榨出一杯橘子汁"); 
        }
    }
    
    class Juicer {
        public void run(Fruit f) {
            f.squeeze();
        }
    }
    

    config.properties(在当前项目文件夹下)

    my.reflect.Orange

    运行结果:

    榨出一杯橘子汁

    当然只需要改动config.properties内容为my.reflect.Apple

    就会运行得到 榨出一杯苹果汁

    通过反射获取构造方法并使用

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    import my.bean.Person;
    
    public class Demo3_Constructor {
        /**
         * Class类的newInstance()方法是使用该类的无参构造函数创建对象,如果一个类没有无参的构造函数,就不能这样创建了。
         * 可以调用Class类的getConstructor(String.class, int.class)方法获取一个指定的构造函数然后再调用
         * Constructor类的newInstance("张三", 20)方法创建对象
         * 
         * @param args
         * @throws InstantiationException
         * @throws IllegalAccessException
         * @throws ClassNotFoundException
         * @throws SecurityException
         * @throws NoSuchMethodException
         * @throws InvocationTargetException
         * @throws IllegalArgumentException
         */
        public static void main(String[] args)
                throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException,
                SecurityException, IllegalArgumentException, InvocationTargetException {
            Class<?> class1 = Class.forName("my.bean.Person");
            //Constructor<?> c1 = class1.getConstructor();
            // 获取public空参构造器可以直接由class1对象直接newInstance(),不必获取Constructor对象来获取
            Person p1 = (Person) class1.newInstance();
            System.out.println(p1);
            
            /*Constructor<?> c = class1.getDeclaredConstructor(int.class, String.class);
            System.out.println("Constructor对象:" + c);
            c.setAccessible(true);*/
            Constructor<?> c = class1.getConstructor(int.class, String.class);
            System.out.println("Constructor对象:" + c);
            Person p = (Person) c.newInstance(23, "张三");
            
            System.out.println(p);
        }
    }
    

    运行结果:

    Person [age=0, name=null]
    Constructor对象:public my.bean.Person(int,java.lang.String)
    Person [age=23, name=张三]
     

    获取public空参构造器可以直接由class1对象直接newInstance(),不必获取Constructor对象来获取

    注意:Class类里面的newInstance()是没有参数的,只能调用无参构造方法,如果需要调用有参构造方法,必须先获取构造器的对象。Constructor的newInstance方法如下:

    public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException

    这个Constructor<?>对象newInstance()调用空构造方法的对象。newInstance(23, "张三")这里拿到了有参数的构造方法对象,打印p调用toString();

    如果构造方法是private的怎么办呢?也能拿到constructor对象吗?

    其实是可以的

    Constructor<?> c = class1.getDeclaredConstructor(int.class, String.class);
    c.setAccessible(true);

    暴力获取即可。这种暴力获取constructor对象。Method对象,Field对象都是一样的方法。

    通过反射获取成员变量并使用

    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    
    import my.bean.Person;
    
    public class Demo4_Field {
        /**
         * Field
         * Class.getField(String)方法可以获取类中的指定字段(可见的前提下),如果是私有的可以用getDeclaredField("name")方法,
         * 通过set(obj, "李四")方法可以设置指定对象上该字段的值,如果是私有的需要先调用setAccessible(true)设置访问权限,
         * 用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值
         * @param args
         * @throws ClassNotFoundException
         * @throws NoSuchMethodException
         * @throws SecurityException
         * @throws InstantiationException
         * @throws IllegalAccessException
         * @throws IllegalArgumentException
         * @throws InvocationTargetException
         * @throws NoSuchFieldException
         */
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
            Class<?> class1 = Class.forName("my.bean.Person");
            Constructor<?> c = class1.getConstructor(int.class, String.class);
            Person p = (Person) c.newInstance(23, "张三");
    /*        // 因为name字段private,所以需要暴力获取
            Field f = class1.getField("name");
            f.set(p, "李四");// 这样抛异常NoSuchMethodException*/
            Field f = class1.getDeclaredField("age");
            System.out.println("Field对象:" + f);
            // 将此对象的accessible标志设置为指示的布尔值。 true的值表示反射对象应该在使用时抑制Java语言访问检查。
            // false的值表示反映的对象应该强制执行Java语言访问检查。
            f.setAccessible(true);
            f.set(p, 24);
            System.out.println(p);
            
        }
    }
    

    运行结果:

    Field对象:private int my.bean.Person.age
    Person [age=24, name=张三]
     


    这里暴力拿到age对象,修改为24.

    f.set(p, 24)//表示f字段由p对象调用,相当于p.f = 24,暴力修改,无视f字段是否为private修饰。

    和刚刚暴力获取constructor对象一样,都是先class对象调用getDeclaredxxx然后再将相应的对象调用setAccessible()方法为true

    这个setAccessible()是AccessibleObject类的方法。

          AccessibleObject类是Field,Method和Constructor对象的基类。 它提供了将反射对象标记为在使用它时抑制默认Java语言访问控制检查的功能。 当使用Fields,Methods或Constructors来设置或获取字段,调用方法,或创建和初始化新的类实例时,会执行访问检查(对于public,默认(包)访问,受保护和私有成员)。

    套路如下:

    Field f = class1.getDeclaredField("age");
            // 将此对象的accessible标志设置为指示的布尔值。 true的值表示反射对象应该在使用时抑制Java语言访问检查。
            // false的值表示反映的对象应该强制执行Java语言访问检查。
    f.setAccessible(true);

    通过反射获取方法并使用

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import my.bean.Person;
    
    public class Demo5_Method {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException,
                InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            Class<?> class1 = Class.forName("my.bean.Person"); // 可以写到配置文件再读取
            Constructor<?> c = class1.getConstructor(int.class, String.class);
            System.out.println("constructor对象:" + c);
            Person p = (Person) c.newInstance(23, "张三");
            Method m = class1.getMethod("eat");
            System.out.println("Method对象:" + m);
            Object o = m.invoke(p);
            System.out.println(o);
            
            Method mm = class1.getDeclaredMethod("eat", int.class);
            System.out.println("Method对象:" + mm);
            mm.setAccessible(true);
            Object o1 = mm.invoke(p, 3);
            System.out.println(o1);
        }
    }
    

    运行结果:

    constructor对象:public my.bean.Person(int,java.lang.String)
    Method对象:public void my.bean.Person.eat()
    今天吃饱了
    null
    Method对象:private void my.bean.Person.eat(int)
    今天吃饱了3顿
    null
     

    这个m.invoke(p)意思就是m方法(eat方法)由p对象调用,调用的是无参的那个m方法。

    mm.invoke(p, 3)意思就是mm方法(有参的eat方法)由p对象调用,调用的是有参数的mm方法,传入参数3

    public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

    Method类里面的invoke方法第一个参数是从底层方法被调用的对象

    第二个参数是可变形参,用于方法调用的参数,Person类里面eat()有2个重载方法

    如果底层方法是静态的,则指定的obj参数将被忽略。 它可能为null。

    如果底层方法所需的形式参数的数量为0,则提供的args数组的长度为0或为空。

    如果底层方法是一个实例方法,它将使用动态方法查找来调用,如“Java语言规范”第二版,第15.12.4.4节所述; 特别是将会发生基于目标对象的运行时类型的覆盖。

    如果底层方法是静态的,则如果尚未初始化该方法,那么声明该方法的类将被初始化。

    如果方法正常完成,则返回的值将返回给调用者; 如果值具有原始类型,则首先将其适当地包装在对象中。 但是,如果该值具有基本类型的数组的类型,则该数组的元素不会包含在对象中; 换句话说,返回一个原始类型的数组。 

    如果底层方法返回类型为void,则调用返回null。

    public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

           这个方法官方意思大致是name参数是一个String它指定了所需方法的简单名称。parameterTypes参数是以声明顺序标识方法的形式参数类型的对象的数组。 如果parameterTypesnull ,它被视为一个空数组。

    也就是第一个参数是方法名,第二个参数是方法的字节码对象,如果是int类型参数就传入int.class,如果是String就传入String.class,如果传入Object.class,调用的时候就可以传入任何类型参数,那么接着看下面的例子吧。

    通过反射越过泛型检查

    反射练习:

         ArrayList<Integer>的一个对象,在这个集合中添加一个字符串数据,如何实现呢?
         泛型是在编译时期检查的,但是在运行期会被擦出掉

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Test {
        /**
         * 案例演示 
         * ArrayList<Integer>的一个对象,在这个集合中添加一个字符串数据,如何实现呢?
         * 泛型是在编译时期检查的,但是在运行期会被擦出掉
         * 
         * @param args
         * @throws ClassNotFoundException
         * @throws SecurityException
         * @throws NoSuchMethodException
         * @throws InvocationTargetException
         * @throws IllegalArgumentException
         * @throws IllegalAccessException
         */
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException,
                IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            List<Integer> list = new ArrayList<Integer>();
            list.add(111);
            list.add(222);
            list.add(333);
            list.add(444);
            // 拿到字节码对象
            Class<?> class1 = Class.forName("java.util.ArrayList");
            // 拿到Method
            // add是泛型参数,相当于有很多重载参数,这里相当于选取形参为Object类对象的add方法
            Method m = class1.getMethod("add", Object.class);
            System.out.println("Method对象:" + m);
            Object o = m.invoke(list, "abc"); // 返回方法的返回值,add添加成功,返回true
    
            System.out.println(o);
            System.out.println(list);
        }
    }

    运行结果:

    Method对象:public boolean java.util.ArrayList.add(java.lang.Object)
    true
    [111, 222, 333, 444, abc]

    成功的把字符串“abc”添加到了ArrayList<Integer>集合中。

    println()的所有重载方法,除了boolean,char,char[],double,float,int,String,long基本类型类型外,当传入的参数是其他类型时,哪怕是引用类型Integer,Double,集合类型等...都会被当作是Object类型从而调用println(Object)方法。

    所以以下代码会出现类转换异常

    List<String> list = new ArrayList<String>();
            list.add("111");
            list.add("222");
            list.add("333");
            list.add("444");
            // 拿到字节码对象
            Class<?> class1 = Class.forName("java.util.ArrayList");
            // 拿到Method
            // add是泛型参数,相当于有很多重载参数,这里相当于选取形参为Object类对象的add方法
            Method m = class1.getMethod("add", Object.class);
            Object o = m.invoke(list, 123); // 返回方法的返回值,add添加成功,返回true
            System.out.println(list.get(4)); // 鼠标移动上去发现println的参数是String,但是这里是Integer类型的123

    看到源码   

    public void println(String x) {
            synchronized (this) {
                print(x);
                newLine();
            }
        }

    相当于String x = 123;(应该是“123”而不是123)

    所以出错 java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

    通过反射写一个通用的方法,设置某个对象的某个属性为指定的值

    package my.test;
    
    import java.lang.reflect.Field;
    
    public class Test2 {
        // 此方法可将obj对象中名为propertyName的属性的值设置为value
        public void setProperty(Object obj, String propertyName, Object value) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
            Class<?> class1 = obj.getClass();   // 获取该对象的字节码对象
            Field f = class1.getDeclaredField(propertyName);    // 暴力获取该字段的名字
            f.setAccessible(true);           // 去除权限
            f.set(obj, value); // 不管obj对象有无setXXX方法都可以强制修改
        }
    }
    
    package my.test;
    
    public class Test3 {
        public static void main(String[] args)
                throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
            Student s = new Student();
            Test2 t = new Test2();
            System.out.println(s);
            // 给某个对象的属性修改值
            t.setProperty(s, "name", "李四");
            t.setProperty(s, "age", 24);
            System.out.println(s);
        }
    }
    
    class Student {
        private String name;
        public int age;
    
        public String toString() {
            return name + " ," + age;
        }
    }

    运行结果:

    null ,0
    李四 ,24

    f.set(obj, value); // 相当于设置obj对象的f字段为value值,即obj.f = value;

    Field类里面的set方法:
    public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException

    将指定对象参数上的此Field对象表示的字段设置为指定的新值。 如果基础字段具有原始类型,则新值将自动展开。

    操作进行如下:

    如果基础字段是静态的,则忽略obj参数; 它可能为null。

    否则底层字段是一个实例字段。 如果指定的对象参数为空,则该方法将抛出一个NullPointerException 。 如果指定的对象参数不是声明底层字段的类或接口的实例,则该方法将抛出一个IllegalArgumentException 。

    如果这个Field对象正在执行Java语言访问控制,并且底层字段是无法访问的,则该方法将抛出一个IllegalAccessException 。

    如果底层字段是final,该方法将抛出一个IllegalAccessException ,除非setAccessible(true)已成功为该Field对象,该字段是非静态的。 以这种方式设置最终字段只有在反序列化或重建具有空白最终字段的类的实例时才有意义,才能使其可用于程序其他部分的访问。 在任何其他情况下使用可能会产生不可预测的影响,包括程序的其他部分继续使用此字段的原始值的情况。

    如果底层字段是原始类型,则尝试将新值转换为原始类型的值的解包转换。 如果此尝试失败,该方法将抛出一个IllegalArgumentException 。

    如果在可能展开后,新值不能通过标识或扩展转换转换为底层字段的类型,则该方法将抛出一个IllegalArgumentException 。

    如果底层字段是静态的,那么声明该字段的类如果尚未被初始化,则会被初始化。

    该字段设置为可能展开和扩展的新值。

    如果字段隐藏在obj类型中,则该字段的值根据前面的规则设置。

    参数

    obj - 其字段应被修改的对象

    value - 修改了 obj的新值

    小练习:

         写一个properties格式的配置文件,配置类的完整名称

         写一个程序,读取这个properties配置文件,获得类的完整名称并加载这个类,用反射的方式去运行这个类的方法。

    package my.test;
    
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    
    public class Test4 {
        public static void main(String[] args)
                throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {
            // 创建流对象,关联配置文件
            BufferedReader br = new BufferedReader(new FileReader("abc.properties"));
            // 将配置文件的类名返回,并获取到字节码对象
            Class<?> class1 = Class.forName(br.readLine());
            br.close();
            // 通过字节码对象创建对象
            DemoClass dc = (DemoClass) class1.newInstance();
            dc.a();
        }
    }
    package my.test;
    
    public class DemoClass {
        public void a() {
            System.out.println("hello world");
        }
    }
    

    abc.properties 

    my.test.DemoClass

    运行结果:

    hello world
     

    动态代理的概述和实现

    关于什么是动态代理,定义及其讲解请参考以下两位博主的文章。

    https://www.cnblogs.com/xiaoluo501395377/p/3383130.html

    https://www.cnblogs.com/gonjan-blog/p/6685611.html

    笔记如下:

    User.java

    public interface User {
        public void add();
        public void delete();
    }

    UserImp.java

    public class UserImp implements User {
        @Override
        public void add() {
            System.out.println("添加功能");
        }
        @Override
        public void delete() {
            System.out.println("删除功能");
        }
    }

    MyInvocationHandler.java

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * 代码写完,修改源码不好 其他的代码也需要添加比如权限校验和日志记录,手动修改不方便 写成一个通用的动态代理
     * 
     * @author lcy
     *
     */
    public class MyInvocationHandler implements InvocationHandler {
        private Object target;
    
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("权限校验");
            Object obj = method.invoke(target, args);
            System.out.println("日志记录");
            return obj;
        }
    }
    

    Test.java

    import java.lang.reflect.Proxy;
    
    public class Test {
        public static void main(String[] args) {
            UserImp ui = new UserImp();
            ui.add();
            ui.delete();
            System.out.println("=============代理之前==============");
            MyInvocationHandler m = new MyInvocationHandler(ui);
            Class<?> class1 = ui.getClass();
            User u = (User)Proxy.newProxyInstance(class1.getClassLoader(), class1.getInterfaces(), m);
            u.add();
            u.delete();
        }
    }

    =================================Talk is cheap, show me the code===============================

    CSDN博客地址:https://blog.csdn.net/qq_34115899
  • 相关阅读:
    jQuery Mobile动态刷新页面样式
    IE10下阿里旺旺无法快速登录解决办法
    JS复制内容到剪贴板: 兼容IE、Firefox、Chrome、Safari所有浏览器【转】
    python sftp ftp 造轮子,实现多个方法
    synergy ubuntu18.04 windows10
    爬虫之js破解 非常详细
    scrapy的useragent与代理ip
    Xpath的string(.)用法
    selenium cookie 登录
    scrapy爬取迅雷电影天堂最新电影ed2k
  • 原文地址:https://www.cnblogs.com/lcy0515/p/10807886.html
Copyright © 2020-2023  润新知