JAVA——反射

一、获取类对象

类对象概念(不是类的对象): 所有的类,都存在一个类对象,这个类对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法。

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

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

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

1.1获取类对象

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

在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样的。
注: 准确的讲是一个ClassLoader下,一种类,只会有一个类对象存在。通常一个JVM下,只会有一个ClassLoader。

1.2获取类对象的时候,会导致类属性被初始化

为Hero增加一个静态属性,并且在静态初始化块里进行初始化,

static String copyright;
//静态初始化块初始化
static {
    System.out.println("初始化 copyright");
    copyright = "版权由Riot Games公司所有";
}

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

1.3在静态方法上加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的“类对象”,然后通过类对象获取“构造器对象”
通过构造器对象创建一个对象

2.1创建一个对象

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

2.2通过配置文件获取对象

首先准备一个文本文件:hero.config。 在这个文件中保存类的全名称,可以是charactor.APHero 或者是charactor.ADHero

接着设计一个方法叫做:

public static Hero getHero()

在这个方法中,读取*hero.config的数据,取出其中的类名*,根据类名实例化出对象,然后返回对象。

Hero h = getHero();
System.out.println(h);

通过打印h,可以发现,当配置文件里的内容发生变化的时候,就会得到不同的对象

源代码不需要发生任何变化,只需要修改配置文件,就可以导致程序的逻辑发生变化, 这是一种基于配置的编程思想

Spring框架中的IOC和DI的底层就是基于这样的机制实现的。

public class TestReflection {
    public static void main(String[] args) throws InterruptedException {
        Hero h = getHero();
        System.out.println(h);
    }

    public static Hero getHero() {
         //配置文件
        File f = new File("E:/project/j2se/hero.config");

        try (FileReader fr = new FileReader(f)) {
            String className = null;
            char[] all = new char[(int) f.length()];
            fr.read(all);
            className = new String(all);
            Class clazz=Class.forName(className);
            Constructor c= clazz.getConstructor();
            Hero h= (Hero) c.newInstance();
            return h;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }
}

三、访问属性

通过反射机制修改对象的属性

为了访问属性,把name修改为public。
对于private修饰的成员,需要使用setAccessible(true)才能访问和修改。

3.1通过反射修改属性的值

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

3.2getField和getDeclaredField的区别

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

四、调用方法

通过反射机制,调用一个对象的方法

4.1调用方法

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

public class TestReflection {

    public static void main(String[] args) {
        Hero h = new Hero();

        try {
            // 获取这个名字叫做setName,参数类型是String的方法,用Method接收
            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,所以在这里举两个例子,来演示一下反射的一种实际运用。

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

public class Service1 {

    public void doService1(){
        System.out.println("业务方法1");
    }
}

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框架的最基本的原理,只是它做的更丰富,安全,健壮。

sping.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);

    }
}

猜你喜欢

转载自blog.csdn.net/Jae_Peng/article/details/79961242