反射(java)

反射机制就是在程序“运行状态”时:对任意一个类,我们通过反射都能够知道这个类的所有属性和方法;对于任意一个对象,我们同样也都能够调用它的任意一个方法和属性。

目录

一、反射的入口

二、反射可以拿到什么

2.1获取所有实现的接口

2.2获取所有的父类

2.3获取所有的构造方法

2.4获取方法

2.5获取属性

2.6获取对象实例

三、利用反射获取对象实例,并操作对象

3.1操作属性

3.2操作方法

3.3操作构造方法

四、补充

4.1动态加载类

4.2越过泛型检查


一、反射的入口

想要使用反射,我们必须获取到某一个类的Class对象,java中一共有三种方式可以获取到某个类的Class对象:

  1. Class.forName(类的全限定名);
  2. 类名.class;
  3. 对象.getClass();(getClass()方法是Object类中的一个native方法)

下面我们结合代码实例具体的看一下反射的用法。先创建一个接口和一个实现类,并在实现类里面赋予一些成员属性和方法。

接口1:

public interface MyInterface {
    void interfaceMethod();
}

接口2:

public interface MyInterface2 {
    void interfaceMethod2();
}

实现类: 

public class Person  implements MyInterface,MyInterface2{
    private int id;
    private String name;
    private int age;
    public String gender;

    @Override
    public void interfaceMethod() {
        System.out.println("interface Method......");
    }

    @Override
    public void interfaceMethod2() {
        System.out.println("interface2 Method......");
    }

    //准备一个静态方法
    public static void staticMehtod(){
        System.out.println(" static method......");
    }

    //准备一个公有方法
    public void publicMethod(){
        System.out.println("public Method......");
    }

    //准备一个私有方法
    private void privateMethod(){
        System.out.println("private Method......");
    }

    //无参构造
    public Person() {

    }
    //一参构造
    public Person(int id) {
        this.id = id;
    }
    //三餐构造
    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    
    //私有构造
    private Person(String name){
        this.name = name;
    }

    //get和set方法

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

测试类:

//通过反射获取类
public class RelectDemo01 {
    public static void main(String[] args) {
        //1.Class.forName(类的全限定名)  一般推介使用此方式
        try {
            Class<?> personClass = Class.forName("com.shhxbean.test.reflect.Person");
            System.out.println(personClass);
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }

        //2.类名.class
        Class<Person> personClass2 = Person.class;
        System.out.println(personClass2);

        //3.对象.getClass()
        Person person = new Person();
        Class<? extends Person> personClass3 = person.getClass();
        System.out.println(personClass3);
    }
}

输出:

class com.shhxbean.test.reflect.Person
class com.shhxbean.test.reflect.Person
class com.shhxbean.test.reflect.Person

二、反射可以拿到什么

其实一个类或对象,只要你里面有的,通过反射都可以拿到,不管你是实现了多个接口,继承了某个类,里面有什么属性、什么方法,在反射面前一切都是虚无......下面我们具体展开看一看:

2.1获取所有实现的接口

上面的代码实例中我们我们让Person实现了两个接口,现在通过反射拿到这两个接口,关键方法就是getInterfaces();

public class RelectDemo02 {
    public static void main(String[] args) {
        //反射入口
        Class<?> personClass = null;
        try {
            personClass = Class.forName("com.shhxbean.test.reflect.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //获取Person实现的所有接口
        Class<?>[] interfaces = personClass.getInterfaces();
        for(Class<?> inter : interfaces){
            System.out.println(inter);
        }
    }
}

输出:

interface com.shhxbean.test.reflect.MyInterface
interface com.shhxbean.test.reflect.MyInterface2

2.2获取所有的父类

Person类没有显式的继承某个父类,但它有默认的父类Object,我们通过反射也可以拿到,关键方法getSuperClass();

public class RelectDemo03 {
    public static void main(String[] args) {
        //反射入口
        Class<?> personClass = null;
        try {
            personClass = Class.forName("com.shhxbean.test.reflect.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //获取Person的父类
        Class<?> superClass = personClass.getSuperclass();
        System.out.println(superClass);
    }
}

输出:

class java.lang.Object

2.3获取所有的构造方法

在Person类中定义了三个构造方法,我们通过反射来拿到他们,关键字getConstractors();这个也是获取的public修饰的构造方法,如果构造方法是private修饰的,这种方式是拿不到的,那如何拿private的,getDeclaredConstractors()即可。

public class RelectDemo04 {
    public static void main(String[] args) {
        //反射入口
        Class<?> personClass = null;
        try {
            personClass = Class.forName("com.shhxbean.test.reflect.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //获取Person的构造方法
        Constructor<?>[] constructors = personClass.getConstructors();
        for(Constructor<?> constructor :constructors){
            System.out.println(constructor);
        }
    }
}

输出:

public com.shhxbean.test.reflect.Person()
public com.shhxbean.test.reflect.Person(int)
public com.shhxbean.test.reflect.Person(int,java.lang.String,int)

2.4获取方法

这里获取方法又分为两种情况:

  1. 获取所有的公共方法(本类及其父类、接口中所有的public修改的方法),关键字getMethods();
  2. 只获取当前类中所有的方法(包括public、private修饰的所有方法),关键字getDeclaredMethods();
public class RelectDemo05 {
    public static void main(String[] args) {
        //反射入口
        Class<?> personClass = null;
        try {
            personClass = Class.forName("com.shhxbean.test.reflect.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //情况一:获取所有公共的方法(包括父类、接口中所有public的方法)
        System.out.println("情况一:获取所有公共的方法:");
        Method[] methods = personClass.getMethods();
        for(Method method : methods){
            System.out.println("情况一:" + method);
        }

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

        //情况二:只获取当前类中所有方法(包括public\private)
        System.out.println("情况一:获取所有公共的方法:");
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for(Method declaredMethod : declaredMethods){
            System.out.println("情况二:" + declaredMethod);
        }

    }
}

输出:

情况一:获取所有公共的方法:
情况一:public java.lang.String com.shhxbean.test.reflect.Person.getName()
情况一:public int com.shhxbean.test.reflect.Person.getId()
情况一:public void com.shhxbean.test.reflect.Person.setName(java.lang.String)
情况一:public void com.shhxbean.test.reflect.Person.interfaceMethod()
情况一:public void com.shhxbean.test.reflect.Person.interfaceMethod2()
情况一:public void com.shhxbean.test.reflect.Person.setId(int)
情况一:public int com.shhxbean.test.reflect.Person.getAge()
情况一:public void com.shhxbean.test.reflect.Person.setAge(int)
情况一:public static void com.shhxbean.test.reflect.Person.staticMehtod()
情况一:public void com.shhxbean.test.reflect.Person.publicMethod()
情况一:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
情况一:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
情况一:public final void java.lang.Object.wait() throws java.lang.InterruptedException
情况一:public boolean java.lang.Object.equals(java.lang.Object)
情况一:public java.lang.String java.lang.Object.toString()
情况一:public native int java.lang.Object.hashCode()
情况一:public final native java.lang.Class java.lang.Object.getClass()
情况一:public final native void java.lang.Object.notify()
情况一:public final native void java.lang.Object.notifyAll()
==================================
情况一:获取所有公共的方法:
情况二:public java.lang.String com.shhxbean.test.reflect.Person.getName()
情况二:public int com.shhxbean.test.reflect.Person.getId()
情况二:public void com.shhxbean.test.reflect.Person.setName(java.lang.String)
情况二:public void com.shhxbean.test.reflect.Person.interfaceMethod()
情况二:public void com.shhxbean.test.reflect.Person.interfaceMethod2()
情况二:public void com.shhxbean.test.reflect.Person.setId(int)
情况二:public int com.shhxbean.test.reflect.Person.getAge()
情况二:public void com.shhxbean.test.reflect.Person.setAge(int)
情况二:public static void com.shhxbean.test.reflect.Person.staticMehtod()
情况二:public void com.shhxbean.test.reflect.Person.publicMethod()
情况二:private void com.shhxbean.test.reflect.Person.privateMethod()

通过上面的输出结果我们可以看到情况一拿到了很多方法,既有类本身的public方法,也有其父类Object中的wait()、equals()等方法,还有接口中的方法。而情况二中只拿到了类本身中的方法,包括public和private修饰的方法。

2.5获取属性

属性的获取和上面方法的获取类似,也是分两种情况:

  1. 获取所有的公共属性,关键字getFields();
  2. 获取所有属性,关键字getDeclaredFields();
public class RelectDemo06 {
    public static void main(String[] args) {
        //反射入口
        Class<?> personClass = null;
        try {
            personClass = Class.forName("com.shhxbean.test.reflect.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        情况一:获取所有公共的属性(包括父类、接口中所有public的属性)
        System.out.println("情况一:获取所有公共的属性:");
        Field[] fields = personClass.getFields();
        for(Field field :fields){
            System.out.println("情况一:" + field);
        }

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

        //情况二:只获取当前类中所有属性(包括public\private)
        System.out.println("情况二:获取所有属性:");
        Field[] declaredfields = personClass.getDeclaredFields();
        for(Field declaredfield : declaredfields){
            System.out.println("情况二:" + declaredfield);
        }
    }
}

输出:

情况一:获取所有公共的属性:
情况一:public java.lang.String com.shhxbean.test.reflect.Person.gender
==================================
情况二:获取所有属性:
情况二:private int com.shhxbean.test.reflect.Person.id
情况二:private java.lang.String com.shhxbean.test.reflect.Person.name
情况二:private int com.shhxbean.test.reflect.Person.age
情况二:public java.lang.String com.shhxbean.test.reflect.Person.gender

2.6获取对象实例

上面列举的是通过反射拿到了一个类中的一些方法、属性等,我们也可以通过反射来创建某个类(或接口)的实例对象,关键字newInstance();

public class RelectDemo07 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        //反射入口
        Class<?> personClass = null;
        try {
            personClass = Class.forName("com.shhxbean.test.reflect.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        Object instance = personClass.newInstance();
        Person person = (Person) instance;
        //通过实例对象调用方法
        person.interfaceMethod();
    }
}

输出:

interface Method......

三、利用反射获取对象实例,并操作对象

上面的内容只是大体的罗列了一下通过反射可以拿到的一些东西,现在我们具体的通过反射机制创建一个实例对象,并对其做一些特定的操作。

3.1操作属性

public class RelectDemo08 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        //反射入口
        Class<?> personClass = null;
        try {
            personClass = Class.forName("com.shhxbean.test.reflect.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //操作属性
        try {
            //利用反射机制获取实例对象
            Person person = (Person)personClass.newInstance();
            //getDeclaredField()中的参数就是属性的名字
            Field idField = personClass.getDeclaredField("id");
            //因为id属性是private的,所以需要通过setAccessible(true)屏蔽掉private限制
            idField.setAccessible(true);
            //第一个参数是对象名,第二个参数是要给此属性赋的值
            idField.set(person,1);
            System.out.println("id=" + person.getId());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

    }
}

输出:

id=1

这里面需要注意的就三点:

  1. getDeclaredField(参数)方法,我们在上面讲的getDeclaredFields()方法没有参数,它拿到的是类中的所有属性,返回的也是一个Field数组,而getDeclaredField(参数)方法中需要传递参数,这个参数就是属性名,通过这个方法可以拿到指定的属性,返回值就是一个Field,而不是数组;
  2. setAccessible()方法,如果类中的某个属性/方法是private修饰的,我们需要调用Field/Method.setAccessible(true)才能不受限制的给其赋值或调用;
  3. set()方法的两个参数,第一个是对象引用,第二个是你要赋的具体的值。

3.2操作方法

public class RelectDemo09 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        //反射入口
        Class<?> personClass = null;
        try {
            personClass = Class.forName("com.shhxbean.test.reflect.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //操作方法
        try {
            //利用反射机制获取实例对象
            Person person = (Person)personClass.newInstance();
            //getDeclaredMethod()中的参数:第一个是方法名,第二个是方法的参数类型
            Method method = personClass.getDeclaredMethod("privateMethod",null);
            //因为privateMethod方法是private的,所以需要通过setAccessible(true)屏蔽掉private限制
            method.setAccessible(true);
            //第一个参数是对象名,第二个是要给privateMethod方法传递的参数
            method.invoke(person,null);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

输出:

private Method......

操作方法同上面的操作属性类似,这里需要注意的有以下几点:

  1. getDeclaredMethod(参数1,参数2)方法拿到的是某个具体的方法,第一个参数是你要获取方法的方法名,第二个是这个方法的参数类型。比如我们person类中的privateMethod()这个方法,它的方法名就是privateMethod,并且它没有参数,所以我们第二个参数就直接写null。如果它是privateMethod(String s)这种类型,那么它的参数类型是String,这个时候getDeclaredMethod方法就改写成getDeclaredMethod("privateMethod",String.class)。
  2. 属性的赋值是通过set方法,而方法的调用则是通过invoke()方法

3.3操作构造方法

public class RelectDemo10 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        //反射入口
        Class<?> personClass = null;
        try {
            personClass = Class.forName("com.shhxbean.test.reflect.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //操作构造方法
        try {
            //利用反射机制获取实例对象
            Person person = (Person)personClass.newInstance();
            //getConstructor中的参数是构造函数的参数类型
            //在反射中,根据类型获取方法时:基本类型(int char...)和包装类(Integer  Character...)是不同的类型
            //如果是私有的构造方法通过getDeclaredConstructor即可获取,其他的不变
            Constructor<?> constractor = personClass.getConstructor(int.class);
            System.out.println(constractor);
            //通过构造方法创建对象,因为我们在上面是通过personClass.getConstructor(int.class)
            //拿到的构造方法(有参构造),所以通过它新建对象的时候也必须传递一个参数,否则会报错
            Person person2 = (Person) constractor.newInstance(16);
            System.out.println("person2=" + person2);

            //获取私有构造
            Constructor<?> constructor2 = personClass.getDeclaredConstructor(String.class);
            constructor2.setAccessible(true);
            //通过私有构造创建对象
            Person person3 = (Person) constructor2.newInstance("周杰伦");
            System.out.println("person3=" + person3);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

四、补充

4.1动态加载类

有时候在程序运行之前我们不知道要调用那个类的那个方法,只有在运行的时候才知道。这个时候反射就会发挥出他的价值。我们可以把类名、方法名等配置在一个文件中,通过读取文件来让程序取执行不同类的不同方法,进而避免修改程序文件,而只是根据实际情况修改配置文件即可。假如我们现在有两个类Student和Teacher,如下:

public class Student {
    public void study(){
        System.out.println("I am studying...");
    }
}
public class Teacher {
    public void teach(){
        System.out.println("I am teaching...");
    }
}

现在我们再在工程目录下创建一个专门的配置文件class.txt(名字随便起),里面专门配置类和方法名,如下:

                                      

 

内容如下:

classname=com.shhxbean.test.reflect.Teacher
methodname=teach

下面我们写个demo演示一下:

public class ReflectDemo11 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //动态加载类名和方法
        Properties properties = new Properties();
        properties.load(new FileReader("class.txt"));
        String classname = properties.getProperty("classname");
        String methodname = properties.getProperty("methodname");

        try {
            Class<?> aClass = Class.forName(classname);
            //getMethod()、invoke()为可变参数类型...
            Method method = aClass.getMethod(methodname);
            method.invoke(aClass.newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出:

I am teaching...

可以看到我们通过Properties读取了class.txt文件的内容,拿到方法名和类名,最后通过反射实现了对其的调用。这时如果我们想要调用Student类的study方法,只要在class.txt文件中把classname和methodname做相应的修改即可,而不需要修改程序。

4.2越过泛型检查

我们知道,假如在一个List里面有泛型限定,那么你只能向这个List里面添加指定类型的元素,其他元素是添加不进去的,但是通过反射就可以绕过泛型检查,想添加啥添加啥,如下demo:

public class ReflectDemo12 {
    public static void main(String[] args) throws Exception {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        //因为有泛型的约束,所以下面的这行代码在编译期就会报错,它不允许你往list里面放String
        //类型的元素
        //list.add("a");

        //通过反射绕过泛型检查
        Class<? > listClass = list.getClass();
        //利用反射调用add方法,绕过泛型检查,Object.class表示了可以往进添加任何类型的元素
        Method method = listClass.getMethod("add", Object.class);
        method.invoke(list,"aaaaa");
        System.out.println(list);

    }
}

输出:

[1, 2, 3, aaaaa]

不过需要提醒的是虽然可以通过反射机制访问private等访问修饰符不允许访问的属性/方法,也可以忽略掉泛型的约束,但在实际开发中不建议这样做,因为可能会造成程序的混乱。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值