黑马程序员--高新技术之反射

本文深入探讨了Java反射机制的基础概念、实现方法及其在实际编程中的应用案例,包括类的加载、反射构造方法、字段访问和方法调用。通过示例代码展示了如何使用反射动态创建对象、访问成员变量和调用方法,强调了反射机制在动态编程、配置管理和扩展性方面的优势,同时也指出了其对性能的影响。总结了反射的优缺点,并鼓励开发者在适当情况下合理使用反射,以提高软件系统的灵活性和可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

------- android培训java培训、期待与您交流! ----------


一、关于反射需要了解的一些基本概念
1、java类:Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,
 至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。

2、类的生命周期
在一个类编译完成之后,下一步就需要开始使用类,如果要使用一个类,肯定离不开JVM。在程序执行中JVM通过装载,链接,初始化这3个步骤完成。
类的装载是通过类加载器完成的,加载器将.class文件的二进制文件装入JVM的方法区,并且在堆区创建描述这个类的java.lang.Class对象。用来封装数据。
但是同一个类只会被类装载器装载一次。链接就是把二进制数据组装为可以运行的状态。
 
链接分为校验,准备,解析这3个阶段
校验一般用来确认此二进制文件是否适合当前的JVM(版本),
准备就是为静态成员分配内存空间,。并设置默认值
解析指的是转换常量池中的代码作为直接引用的过程,直到所有的符号引用都可以被运行程序使用(建立完整的对应关系)
完成之后,类型也就完成了初始化,初始化之后类的对象就可以正常使用了,直到一个对象不再使用之后,将被垃圾回收。释放空间。
当没有任何引用指向Class对象时就会被卸载,结束类的生命周期。

3、反射:由java类的基本概念,我们可以知道,java程序中各个java类属于同一类事物,而描述这类事物的java类名就是Class。
说白了,反射就是把java类中的各个成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:
成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。
表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,
它们是Field、Method、Contructor、Package等等。 


4、内存中的思考:一个类被加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同类的字节码是不同的,所以他们在内存中
的内容也是不同的,这一个个的空间分别用一个个的对象来表示,这些对象具有相同的类型,这个类型就是Class.这些字节码只被装载一次。


二、对于反射的具体实现
1、在进入反射的代码使用时,我们需要先了解一下,jdk为反射提供了那些类与方法
在jdk中,主要由一下类来实现Java的反射机制,这些类都位于java.lang.reflect包中
Class类:代表一个类(类的模板)
Field类:代表类的成员变量(即类的属性)
Method类:代表类的方法。
Constructor类:代表类的构造方法
Arrays类:动态创建数组,以及访问数组元素的静态方法(数组的排序sort(),数组中的最大值getMax()等)
其中Class类是Reflaction API的核心类。

类中的方法和和具体的作用
a.获得一个类的类模板
Class clazz = 对象名.getClass();
Class clazz = Class.forName(包名+类名);这是我们经常用到的,比如说在jdbc中获得某个数据库的驱动器的类
Class clazz = 类名.class(方法中以Class类型作为参数时,经常用到) 
b.根据类模板获取类的信息:
Field类 ---clazz.getFiled(String pname)  获得指定属性(公共字段)
Filed类----clazz.getDeclearedFiled(String pname) 获得指定属性(所有权限字段)
Filed[]----clazz.getFileds(String pname)  获得所有属性(公共字段)
Filed[]----clazz.getDeclearedFileds(String pname) 获得所有属性(所有权限字段)
通过以上方法获得的Field的类的对象是属于类的即clazz,而不是这个类所代表的的具体的对象的属性,
如果要利用反射获得对象的属性,需要用到Filed类提供的public Object get(Object obj)的方法,
返回指定对象上此 Field 表示的字段的值(obj就是要得到属性的对象)。如果该值是一个基本类型值,则自动将其包装在一个对象中。 
c.获取类的方法:
Method类----clazz.getMethod(StringmethodName,class[] params)  获得指定方法(公共方法)
Method类----clazz.getDeclearedMethod(StringmethodName,class[] params)  获得指定方法(所有方法)
Method[]----clazz.getMethods(StringmethodName,class[] params)  获得所有方法(公共方法)
Method[]----clazz.getDeclearedMethods(StringmethodName,class[] params)  获得所有方法(所有权限方法)


通过以上方法获得类模板的方法并不包括类的构造方法,上面只是获得方法,如果要执行某个对象的方法,
则要用到Method类中的public Object invoke(Object obj,Object... args),在使用这个方法时有两点要注意:
1。如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。 
2.如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。 
d.获取类的构造方法:
Constructors类---getConstructor(Class<?>... parameterTypes)  获得指定构造方法(公共方法)
Constructors[]---public Constructor<?>[] getConstructors() 获取所有类的构造方法(公共的)
Constructors类---public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Class 对象所表示的类或接口的指定构造方法
Constructors[]---public Constructor<?>[] getDeclaredConstructors() 获取所有类的构造方法(公共、保护、默认(包)访问和私有构造方法)
通过以上方法可以获得类的所有的构造方法,如果要创建类的具体的实例,则需要用到Constructors类中的public T newInstance(Object... initargs),
注意点:如果底层构造方法所需形参数为 0,则所提供的 initargs 数组的长度可能为 0 或 null。 
2、介绍完所有的类和方法,下面就是演示案例了

package cn.yusheng.reflaction
/*
*这是反射main方法所用到的类
*/
public class Test {
	public static void main(String[] args) {
		
		System.out.println("我是Test类的main()方法");
		
	}

}


package cn.yusheng.reflaction

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		//得到Class实例对象的三种形式
		Class cls1 = String.class;
		Class cls2 = new String().getClass();
		Class cls3 = Class.forName("java.lang.String");
		System.out.println(cls1.getName()+"--"+cls2.getName()+"--"+cls3.getName());
		//从打印结果可以看出三者都获得了java.lang.String的类模板
		
		//判定指定的 Class 对象是否表示一个基本类型。 
		System.out.println(Integer.TYPE == int.class);
		//打印结果为true,说明两者的.class文件是同一个
		
		//反射构造方法Stri.ng:String(StringBuffer buffer) 
		Class cls4 = Class.forName("java.lang.String");
		Constructor cons = cls4.getConstructor(StringBuffer.class);
		//利用String类的模板通过反射所获的构造方法创建String类的实例对象
		String str = (String) cons.newInstance(new StringBuffer("我是通过反射出的构造方法创建的String实例"));
		//因为只有当运行时才知道创建的实例对象的类型,而在编译期间是不知道的
		//所以在此处要加强转
		System.out.println(str);
		
		
		//用Clss类提供的newInstance()方法创建Stringh实例,但底层还是通过反射出类模板的构造方法去创建
		//实例的,只是利用的黑丝默认的无参数构造方法
		Class cls5 = Class.forName("java.lang.String");
		String str2 = (String) cls5.newInstance();
		
		//反射字段
		ReflectPoint r1 = new ReflectPoint(1, 2);
		Class cls6 = Class.forName("ReflectPoint");
//		Field x = cls6.getField("x");//类或接口的指定公共成员字段,访问私有的属性时会把错
		Field x = cls6.getDeclaredField("x");
		x.setAccessible(true);//暴力反射
		int x1 = (Integer)x.get(r1);
		System.out.println(x1);
		
		//反射String类的charAt(int index)方法并执行
		String str3 = "hello world";
		Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
		//第一个参数是方法的名字,第二个参数是方法的参数,jdk5支持可变参数
		System.out.println("hello world中的第2个字符是:"+charAt.invoke(str3, 1));
		
		//反射执行MainClass的main的方法
		Class cls7 = Class.forName("Test");
		Method main = cls7.getMethod("main", String[].class);
		main.invoke(cls7, (Object)new String[]{"12","13"});
		/*
		 * 在反射main方法时出现的问题:启动Java程序的main方法的参数是一个字符串数组,
		 * 即public static void main(String[] args),通过反射方式来调用这个main方法时,
		 * 如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,
		 * 数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,
		 * javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,
		 * 即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,
		 * 不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,
		 * 而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
		 * 
		 * 解决方法:mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
					mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,
					编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了

		 */
	}
}


//定义一个做实验所需要的类,
class ReflectPoint{
	private int x;
	public int y;
	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}
	
}

动态创建和访问数组
java.lang.Array 类提供了动态创建和访问数组元素的各种静态方法
例程ArrayTester1 类的main()方法创建了一个长度为10 的字符串数组,接着把索引位置为5 的
元素设为“hello”,然后再读取索引位置为5 的元素的值


代码实现:
public class ArrayTester1 {
    public static void main(String args[]) throws Exception {
        Class classType = Class.forName("java.lang.String");
        // 创建一个长度为10的字符串数组
        Object array = Array.newInstance(classType, 10);
        // 把索引位置为5的元素设为"hello"
        Array.set(array, 5, "hello");
        // 获得索引位置为5的元素的值
        String s = (String) Array.get(array, 5);
        System.out.println(s);
    }

}

三、总结与思考
反射的优缺点:
为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念,
    静态编译:在编译时确定类型,绑定对象,即通过。
    动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多
    态的应用,有以降低类之间的藕合性。
    一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中
    它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编
    译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如
    这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能
    的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功
    能。
       它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它
    满足我们的要求。这类操作总是慢于只直接执行相同的操作。


写到这,关于反射的知识算是总节的差不多了,还好这次写博客用了2个小时,算是有点进步吧。其实对于javaSE的
东西我以前都是看过的,如今只是复习,进度算是蛮快的,但这样边写博客边复习,却让我发现自己还有好多的知识点
掌握的不牢固,还需要加深,不过还有时间,慢慢来,静下心来,赶不上第一批录取,就力争第二批吧,只要我努力,
我相信一定会通过的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值