zoukankan      html  css  js  c++  java
  • 说说JAVA的反射机制

    首先要明确什么是类对象

    在理解类对象之前,先说我们熟悉的对象之间的区别:
    garen和teemo都是Hero对象,他们的区别在于,各自有不同的名称,血量,伤害值。

    然后说说类之间的区别
    Hero和Item都是类,他们的区别在于有不同的方法,不同的属性。

    类对象,就是用于描述这种类,都有什么属性,什么方法的
     
     

    获取类对象:

    获取类对象有3种方式
    1. Class.forName
    2. Hero.class
    3. new Hero().getClass()

    在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样的。

    : 准确的讲是一个ClassLoader下,一种类,只会有一个类对象存在。通常一个JVM下,只会有一个ClassLoader。因为还没有引入ClassLoader概念, 所以暂时不展开了。

    package charactor;
     
    public class Hero {
        public String name;
        public float hp;
        public int damage;
        public int id;
     
        static String copyright;
     
        static {
            System.out.println("初始化 copyright");
            copyright = "版权由Riot Games公司所有";
        }
     
    }
    package reflection;
     
    import charactor.Hero;
     
    public class TestReflection {
     
        public static void main(String[] args) {
                String className = "charactor.Hero";
                try {
                    Class pClass1=Class.forName(className);
                    Class pClass2=Hero.class;
                    Class pClass3=new Hero().getClass();
                    System.out.println(pClass1==pClass2);
                    System.out.println(pClass1==pClass3);
                } catch (ClassNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
        }
    }
    

    获取类对象的时候,会导致类属性被初始化(代码同上)

    无论什么途径获取类对象,都会导致静态属性被初始化,而且只会执行一次。(除了直接使用 Class c = Hero.class 这种方式,这种方式不会导致静态属性被初始化)

     

    在静态方法上加synchronized,同步对象是什么?

    当synchronized修饰静态方法的时候, 同步对象就是这个类的类对象。

    如代码中的例子,当第一个线程进入method1的时候,需要占用TestReflection.class才能执行。

    第二个线程进入method2的时候进不去,只有等第一个线程释放了对TestReflection.class的占用,才能够执行。 反推过来,第二个线程也是需要占用TestReflection.class。 那么TestReflection.class就是method2的同步对象。

    换句话说,静态方法被修饰为synchronized的时候,其同步对象就是当前类的类对象。

    package reflection;
     
    public class TestReflection {
        public static void main(String[] args) throws InterruptedException {
            Thread t1= new Thread(){
                public void run(){
                    //调用method1
                    TestReflection.method1();
                }
            };
            t1.setName("第一个线程");
            t1.start();
             
            //保证第一个线程先调用method1
            Thread.sleep(1000);
             
            Thread t2= new Thread(){
                public void run(){
                    //调用method2
                    TestReflection.method2();
                }
            };
            t2.setName("第二个线程");
            t2.start();
        }
     
        public static void method1() {
     
            synchronized (TestReflection.class) {
                // 对于method1而言,同步对象是TestReflection.class,只有占用TestReflection.class才可以执行到这里
                System.out.println(Thread.currentThread().getName() + " 进入了method1方法");
                try {
                    System.out.println("运行5秒");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
        }
     
        public static synchronized void method2() {
            // 对于mehotd2而言,必然有个同步对象,通过观察发现,当某个线程在method1中,占用了TestReflection.class之后
            // 就无法进入method2,推断出,method2的同步对象,就是TestReflection.class
            System.out.println(Thread.currentThread().getName() + " 进入了method2方法");
            try {
                System.out.println("运行5秒");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
     
        }
    }
    

    通过反射机制创建一个对象

    与传统的通过new 来获取对象的方式不同 
    反射机制,会先拿到Hero的“类对象”,然后通过类对象获取“构造器对象” 
    再通过构造器对象创建一个对象

     见代码:

    package reflection;
    import java.lang.reflect.Constructor;
    import charactor.Hero;
    public class TestReflection {
      
        public static void main(String[] args) {
            //传统的使用new的方式创建对象
            Hero h1 =new Hero();
            h1.name = "teemo";
            System.out.println(h1);
              
            try {
                //使用反射的方式创建对象
                String className = "charactor.Hero";
                //类对象
                Class pClass=Class.forName(className);
                //构造器
                Constructor c= pClass.getConstructor();
                //通过构造器实例化
                Hero h2= (Hero) c.newInstance();
                h2.name="gareen";
                System.out.println(h2);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    

     

     通过反射修改属性的值

    package reflection;
     
    import java.lang.reflect.Field;
     
    import charactor.Hero;
      
    public class TestReflection {
      
        public static void main(String[] args) {
                Hero h =new Hero();
                //使用传统方式修改name的值为garen
                h.name = "garen";
                try {
                    //获取类Hero的名字叫做name的字段
                    Field f1= h.getClass().getDeclaredField("name");
                    //修改这个字段的值
                    f1.set(h, "teemo");
                    //打印被修改后的值
                    System.out.println(h.name);
                     
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
        }
    }
    

      

     getField和getDeclaredField的区别

    getField和getDeclaredField的区别
    这两个方法都是用于获取字段
    getField 只能获取public的,包括从父类继承来的字段。
    getDeclaredField 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))

     调用方法

    首先为Hero的name属性,增加setter和getter
    通过反射机制调用Hero的setName

    package reflection;
     
    import java.lang.reflect.Method;
     
    import charactor.Hero;
     
    public class TestReflection {
     
        public static void main(String[] args) {
            Hero h = new Hero();
     
            try {
                // 获取这个名字叫做setName,参数类型是String的方法
                Method m = h.getClass().getMethod("setName", String.class);
                // 对h对象,调用这个方法
                m.invoke(h, "盖伦");
                // 使用传统的方式,调用getName方法
                System.out.println(h.getName());
     
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
     
        }
    }
    

      

     反射有啥用?

    反射非常强大,但是学习了之后,会不知道该如何使用,反而觉得还不如直接调用方法来的直接和方便。 

    通常来说,需要在学习了Spring 的依赖注入,反转控制之后,才会对反射有更好的理解,但是刚学到这里的同学,不一定接触了Spring,所以在这里举两个例子,来演示一下反射的一种实际运用。

    首先准备两个业务类,这两个业务类很简单,就是各自都有一个业务方法,分别打印不同的字符串

    package reflection;
     
    public class Service2 {
     
        public void doService2(){
            System.out.println("业务方法2");
        }
    }
    

    当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果

    package reflection;
     
    public class Test {
     
        public static void main(String[] args) {
    //      new Service1().doService1();
            new Service2().doService2();
        }
    }
    

      

    使用反射方式,首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。 里面存放的是类的名称,和要调用的方法名。
    在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。

    当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件spring.txt,再运行即可。

    这也是Spring框架的最基本的原理,只是它做的更丰富,安全,健壮。  

    spring.txt:

    class=reflection.Service1
    method=doService1
    
    package reflection;
     
    import java.io.File;
    import java.io.FileInputStream;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    import java.util.Properties;
     
    public class Test {
     
        @SuppressWarnings({ "rawtypes", "unchecked" })
        public static void main(String[] args) throws Exception {
     
            //从spring.txt中获取类名称和方法名称
            File springConfigFile = new File("e:\project\j2se\src\spring.txt");
            Properties springConfig= new Properties();
            springConfig.load(new FileInputStream(springConfigFile));
            String className = (String) springConfig.get("class");
            String methodName = (String) springConfig.get("method");
             
            //根据类名称获取类对象
            Class clazz = Class.forName(className);
            //根据方法名称,获取方法对象
            Method m = clazz.getMethod(methodName);
            //获取构造器
            Constructor c = clazz.getConstructor();
            //根据构造器,实例化出对象
            Object service = c.newInstance();
            //调用对象的指定方法
            m.invoke(service);
             
        }
    }
    

      

      

  • 相关阅读:
    Nginx配置文件nginx.conf中文详解
    Linux安装nginx
    熊猫TV游戏直播教程-OBS篇
    Mac下MySQL卸载方法
    sphinx 1.10-实时索引 api
    freebsd 国内相当快的ports源地址
    Springboot框架中如何读取位于resource资源中的properties配置文件,并将配置文件中的键对应的值赋值到目标bean中?
    分析Jedis源码实现操作非关系型数据库Redis
    分析线程池源码测试线程池
    socket简单示例实现从服务器拷贝文件到客户端
  • 原文地址:https://www.cnblogs.com/ttty/p/11362011.html
Copyright © 2011-2022 走看看