三、反射机制操作
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);
}
}