反射
获取类对象
类对象概念: 所有的类,都存在一个类对象,这个类对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法。
步骤1:什么是类对象
在理解类对象之前,先说我们熟悉的对象之间的区别:
garen和teemo都是Hero对象,他们的区别在于,各自有不同的名称,血量,伤害值。
然后说说类之间的区别
Hero和Item都是类,他们的区别在于有不同的方法,不同的属性。
类对象,就是用于描述这种类,都有什么属性,什么方法的
步骤2:获取类对象
获取类对象有3种方式
\1. Class.forName
\2. Hero.class
\3. new Hero().getClass()
在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样的。
注: 准确的讲是一个ClassLoader下,一种类,只会有一个类对象存在。通常一个JVM下,只会有一个ClassLoader。因为还没有引入ClassLoader概念, 所以暂时不展开了。
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();
}
}
}
步骤3:获取类对象的时候,会导致类属性被初始化
为Hero增加一个静态属性,并且在静态初始化块里进行初始化,参考 类属性初始化。
static String copyright;
static {
System.out.println("初始化 copyright");
copyright = "版权由Riot Games公司所有";
}
无论什么途径获取类对象,都会导致静态属性被初始化,而且只会执行一次。(除了直接使用 Class c = Hero.class 这种方式,这种方式不会导致静态属性被初始化)
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();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
创建对象
与传统的通过new 来获取对象的方式不同
反射机制,会先拿到Hero的“类对象”,然后通过类对象获取“构造器对象”
再通过构造器对象创建一个对象
步骤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();
}
}
}
通过配置文件获取对象
首先准备一个文本文件:hero.config。 在这个文件中保存类的全名称,可以是charactor.APHero 或者是charactor.ADHero
接着设计一个方法叫做:
public static Hero getHero()
在这个方法中,读取hero.config的数据,取出其中的类名,根据类名实例化出对象,然后返回对象。
package charactor;
public class APHero extends Hero {
public void magicAttack() {
System.out.println("进行魔法攻击");
}
}
package charactor;
public class ADHero extends Hero {
public void physicAttack() {
System.out.println("进行物理攻击");
}
}
Hero h = getHero();
System.out.println(h);
通过打印h,可以发现,当配置文件里的内容发生变化的时候,就会得到不同的对象。
源代码不需要发生任何变化,只需要修改配置文件,就可以导致程序的逻辑发生变化, 这是一种基于配置的编程思想。
Spring框架中的IOC和DI的底层就是基于这样的机制实现的。
package reflection;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import charactor.Hero;
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;
}
}
}
访问属性
通过反射机制修改对象的属性
步骤1:Hero.java
为了访问属性,把name修改为public。
对于private修饰的成员,需要使用setAccessible(true)才能访问和修改。不在此知识点讨论。
package charactor;
public class Hero {
public String name;
public float hp;
public int damage;
public int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Hero(){
}
public Hero(String string) {
name =string;
}
@Override
public String toString() {
return "Hero [name=" + name + "]";
}
public boolean isDead() {
// TODO Auto-generated method stub
return false;
}
public void attackHero(Hero h2) {
System.out.println(this.name+ " 正在攻击 " + h2.getName());
}
}
步骤2:TestRelection
通过反射修改属性的值
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();
}
}
}
步骤3:getField和getDeclaredField的区别
getField和getDeclaredField的区别
这两个方法都是用于获取字段
getField 只能获取public的,包括从父类继承来的字段。
getDeclaredField 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true)
调用方法
通过反射机制,调用一个对象的方法
步骤1:调用方法
首先为Hero的name属性,增加setter和getter
通过反射机制调用Hero的setName
package charactor;
public class Hero {
public String name;
public float hp;
public int damage;
public int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Hero(){
}
public Hero(String string) {
name =string;
}
@Override
public String toString() {
return "Hero [name=" + name + "]";
}
public boolean isDead() {
// TODO Auto-generated method stub
return false;
}
public void attackHero(Hero h2) {
// TODO Auto-generated method stub
}
}
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,所以在这里举两个例子,来演示一下反射的一种实际运用。
步骤1:业务类
首先准备两个业务类,这两个业务类很简单,就是各自都有一个业务方法,分别打印不同的字符串
package reflection;
public class Service1 {
public void doService1(){
System.out.println("业务方法1");
}
}
package reflection;
public class Service2 {
public void doService2(){
System.out.println("业务方法2");
}
}
步骤2:非反射的方式
当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果
package reflection;
public class Test {
public static void main(String[] args) {
new Service1().doService1();
}
}
package reflection;
public class Test {
public static void main(String[] args) {
// new Service1().doService1();
new Service2().doService2();
}
}
步骤3:反射方式
使用反射方式,首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。 里面存放的是类的名称,和要调用的方法名。
在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。
当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件spring.txt,再运行即可。
这也是Spring框架的最基本的原理,只是它做的更丰富,安全,健壮。
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);
}
}
浙公网安备 33010602011771号