天天看点

【JAVA面向对象】认识反射机制(二)

三、反射机制操作

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

先来创建一个用户类用来测试:

package com.bean;
public class User {
    public Integer id;
    private String username;
    private String password;
    public User() {
    }
    /**
     * 使用private修饰有参构造,当前构造只能在本类中使用
     * @param username
     * @param password
     */
    private User(String username,String password){
        this.username = username;
        this.password = password;
    }
    public User(Integer id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }
    public Integer getId()  {return id;}
    public void setId(Integer id)  {this.id = id;}
    public String getUsername() {return username;}
    public void setUsername(String username) {this.username = username;}
    public String getPassword() {return password;}
    public void setPassword(String password) {this.password = password;}
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
    private String show(){
        System.out.println("id : "+getId()+" , username : "+getUsername()+" , password : "+ getPassword());
        return "helloworld";
    }
}

           

下面看一下怎样通过反射获取构造器:

package com.demo;
import java.lang.reflect.Constructor;
/**
 * 反射操作构造方法
 */
public class Demo06 {
    public static void main(String[] args) throws Exception {
        //获取User类对应的Class对象
        Class<?> clazz = Class.forName("com.bean.User");
        //获取无参构造方法对象,c1存储的是一个构造方法对象,
        Constructor<?> c1 = clazz.getConstructor();
        //使用无参创建User类对象
        Object obj1 = c1.newInstance();
        System.out.println(obj1);
        System.out.println("---------------");
        //获取User类对应的有参构造方法对象           分别填入参数对应的.class对象
        Constructor<?> c2 = clazz.getConstructor(Integer.class, String.class, String.class);
        //使用有参创建User对象
        Object obj2 = c2.newInstance(1, "张三", "root");
        System.out.println(obj2);
        System.out.println("---------------");
        //获取User(String username,String password)构造方法对象
        //Constructor<?> c3 = clazz.getConstructor(String.class, String.class);
        //使用私有有参创建User对象
        //Object obj3 = c3.newInstance("张国静", "20000101");
        //System.out.println(obj3);
        //此时运行会报错,原因是此处要获取的构造方法是私有的,无法获取,
        //可以改变setAccessible设置实现暴力反射
        Constructor<?> c3 = clazz.getDeclaredConstructor(String.class, String.class);
        //暴力反射,让私有构造器对象可以被外部访问(一般不推荐使用)
        c3.setAccessible(true);
        Object obj3 = c3.newInstance("张国静", "20000101");
        System.out.println(obj3);
    }
}
           

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

package com.demo;
import com.bean.User;
import java.lang.reflect.Field;
/**
 * 反射操作成员变量
 */
public class Demo07 {
    public static void main(String[] args) throws Exception {
        //获取User类的Class对象
        Class<User> clazz = User.class;
        User user = clazz.newInstance();
        //操作public修饰的成员变量
        Field idField = clazz.getField("id");
        //设置该成员变量值
        //set方法有两个参数obj、value
        //obj:需要设置的对象
        //value:需要设置的值
        //给user对象的id属性设置值为250
        idField.set(user,250);
        System.out.println(user);
        //获取该成员变量值
        Object idValue = idField.get(user);
        System.out.println(idValue);
        //操作非public修饰的成员变量
        Field usernameField = clazz.getDeclaredField("username");
        usernameField.setAccessible(true);//暴力反射
        usernameField.set(user,"小明");
        System.out.println(user);
        Object usernameValue = usernameField.get(user);
        System.out.println(usernameValue);
    }
}
           

3.3 通过反射获取成员方法并使用

package com.demo;
import com.bean.User;
import java.lang.reflect.Method;
/**
 * 反射操作成员方法
 */
public class Demo08 {
    public static void main(String[] args) throws Exception {
        //获取User类的Class对象
        Class<User> clazz = User.class;
        User user = clazz.newInstance();
        //获取public修饰的成员方法  有两个参数,方法名、该方法参数的class对象
        Method method01 = clazz.getMethod("setPassword", String.class);
        //使用方法对象,invoke方法有两个参数 obj、args
        //obj:哪个对象在执行该方法
        //args:方法执行时所需的参数值
        method01.invoke(user,"123456");
        System.out.println(user);
        //操作非public修饰的成员方法
        Method method02 = clazz.getDeclaredMethod("show");
        method02.setAccessible(true);
        Object result = method02.invoke(user);
        System.out.println(result);
    }
}
           

四、反射机制应用

4.1 反射结合工厂模式

我们举个摘水果的例子来解释:

首先创建水果父类:

package com.bean;
/**
 * 水果类
 */
public class Fruit {
    private String fruitName;
    public Fruit() {
    }
    public Fruit(String fruitName) {
        this.fruitName = fruitName;
    }
    public String getFruitName() {
        return fruitName;
    }
    public void setFruitName(String fruitName) {
        this.fruitName = fruitName;
    }
    @Override
    public String toString() {
        return "Fruit{" +
                "fruitName='" + fruitName + '\'' +
                '}';
    }
}
           

再创建两个水果子类:苹果和香蕉

package com.bean;
/**
 * 苹果
 */
public class Apple extends Fruit{
    private int weight;
    public Apple() {
    }
    public Apple(String fruitName , int weight) {
        super(fruitName);
        this.weight = weight;
    }
}
           
package com.bean;
/**
 * 香蕉
 */
public class Banana extends Fruit{
    private int weight;
    public Banana() {
    }
    public Banana(String fruitName,int weight) {
        super(fruitName);
        this.weight = weight;
    }
}
           

来看看下边的测试:

package com.demo;
import com.bean.Apple;
import com.bean.Banana;
public class Demo02 {
    public static void main(String[] args) {
        //获取一个苹果
        Apple apple = new Apple("红富士",10);
        //获取一个香蕉
        Banana banana = new Banana("黄金大香蕉",10);
    }
}

           

这种方式的缺点是什么?

我们发现这种操作方式的类之间耦合性太高,这仅仅是两个水果,如果是很多水果那么就需要依次new对象!!

那么有什么解决方法呢?

下面来看一种改进的方法:添加水果工厂

package com.factory;
import com.bean.Apple;
import com.bean.Banana;
/**
 * 水果工厂
 * 工厂模式:降低耦合
 */
public class FruitFactory1 {
    public static Apple getApple(){
        return new Apple("红富士",10);
    }
    public static Banana getBanana(){
        return new Banana("东亚小香蕉",10);
    }
}

           

再来看看测试:

package com.demo;
import com.bean.Apple;
import com.bean.Banana;
import com.factory.FruitFactory1;
public class Demo03 {
    public static void main(String[] args) {
        //获取一个苹果
        Apple apple = FruitFactory1.getApple();
        //获取一个香蕉
        Banana banana = FruitFactory1.getBanana();
    }
}
           

这样做发现和之前的demo02例子的性质差不多,仍然存在问题!需要在工厂类中添加很多方法(一种水果对应一个)!

再看一种改进的方式:结合多态

package com.factory;
import com.bean.Apple;
import com.bean.Banana;
import com.bean.Fruit;
/**
 * 水果工厂
 * 工厂模式:降低耦合
 * 结合使用多态:可扩展性强
 */
public class FruitFactory2 {
    public static Fruit getFruit(String fruitName){
        if ("apple".equals(fruitName)) {
            return new Apple("红富士",10);
        } else if ("banana".equals(fruitName)) {
            return new Banana("南亚小香蕉",10);
        }
        return null;
    }
}
           

测试类:

package com.demo;
import com.bean.Apple;
import com.bean.Banana;
import com.factory.FruitFactory2;
public class Demo04 {
    public static void main(String[] args) {
        //获取一个苹果
        Apple apple = (Apple) FruitFactory2.getFruit("apple");
        //获取一个香蕉
        Banana banana = (Banana) FruitFactory2.getFruit("banana");
    }

}

           

此时我们发现如果有很多水果,在工厂二中必须要有许多的if-else判断,所以还可以进行改进,用反射机制结合工厂模式:

package com.factory;
import com.bean.Fruit;
/**
 * 水果工厂
 * 工厂模式:降低耦合
 * 结合使用多态:可扩展性强
 * 反射机制:解决if..else过多的问题
 */
public class FruitFactory {
    public static Fruit getFruit(String fruitName) throws Exception {
        //Class.forName(全类名).newInstance,可以根据全类名,获得类,再new对象(newInstance方法)
        return (Fruit) Class.forName(fruitName).newInstance();
    }
}

           

测试类:

package com.demo;
import com.bean.Fruit;
import com.factory.FruitFactory;
public class Demo05 {
    public static void main(String[] args) throws Exception {
        //获取苹果
        Fruit fruit1 = FruitFactory.getFruit("com.bean.Apple");
        //获取香蕉
        Fruit fruit2 = FruitFactory.getFruit("com.bean.Banana");
    }
}
           

存在的问题:

​ com.bean.Apple对应的对象

​ 将以上"Apple"字符串写到java代码中,合适吗?

​ 显然不合适!!!

​ 如果全类名发生改变了,那你就必须修改java源代码,必须要重新部署项目!

​ "Apple"字符串和java程序的耦合性非常高!!

​ "Apple"字符串不能放到java代码中,而应该放置到配置文件中!!

​ 这样秩序修改配置文件即可,不需操作源码!!

​ 解耦!降低耦合!!!

4.2 反射结合配置文件

创建一个配置文件如bean.properties,在文件中填写:

bean01=com.bean.Apple
bean02=com.bean.Banana
           
package com.factory;
import com.bean.Fruit;
import java.io.InputStream;
import java.util.Properties;
/**
 * 水果工厂
 * 结合配置文件
 */
public class FruitFactoryPro {
    private static final Properties properties = new Properties();
    public static Fruit getFruit() throws Exception {
        try {
            //适用类自身自带的流
            InputStream is = FruitFactoryPro.class.getClassLoader().getResourceAsStream("com/factory/bean.properties");
            properties.load(is);//通过流,将配置信息的内容分割成键值对
            return (Fruit)Class.forName(properties.getProperty("bean01")).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

}
           

测试类:

package com.demo;
import com.bean.Fruit;
import com.factory.FruitFactory;
import com.factory.FruitFactoryPro;
public class Demo05 {
    public static void main(String[] args) throws Exception {
        ///获取苹果
        Fruit fruit1 = FruitFactoryPro.getFruit();
    }
}
//此处是简易版,可以通过外部操作(如网页web控制等)来确定调用配置文件的哪个内容
           

4.3反射越过泛型检查

  • 案例演示:
    • 创建ArrayList的一个对象,在这个集合中添加一个字符串数据,如何实现呢?
package com.demo;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class Demo09 {
    public static void main(String[] args) throws Exception {
        //创建List集合对象
        List<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);
        //泛型只在编译期有效!!!
        //反射越过泛型检查
        //反射可以在程序运行时,动态地调用List中的add方法去添加元素
        Class<? extends List> clazz = list.getClass();
        Method add = clazz.getDeclaredMethod("add", Object.class);
        add.setAccessible(true);
        Object result = add.invoke(list, "hello generic type !");
        System.out.println(list);
        System.out.println(result);
    }
}
           

4.4 反射通用方法

package com.demo;
import com.bean.User;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
 * 反射案例
 * 给指定对象的指定属性设置指定值
 */
public class Demo10 {
    public static void main(String[] args) throws Exception {
        User user = new User();
        setValue(user,"id" , 123);
        System.out.println(user);
    }
    /**
     * 给指定对象的指定属性设置指定值
     * @param obj : 指定对象
     * @param fieldName : 指定属性
     * @param value : 指定值
     */
    public static void setValue(Object obj , String fieldName , Object value) throws Exception {
        Class<?> clazz = obj.getClass();
        //方法名规范:如果只有一个单词,所有的字母全都小写。如果有多个单词,从第二个单词开始,首字母大写!!!
        //变量名规范: 如果只有一个单词,所有的字母全都小写。如果有多个单词,从第二个单词开始,首字母大写!!!
        //username setUsername
        //根据属性名称获取对应的set方法名称
        //String methodName = "set" + "U" + "sername";
        String methodName = "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
        //获取变量
        Field field = clazz.getDeclaredField(fieldName);
        //获取变量的数据类型
        Class<?> fieldType = field.getType();
        //获取到该变量的set方法对象
        Method method = clazz.getMethod(methodName, fieldType);
        //执行set方法
        method.invoke(obj , value);
    }
}