黑马程序员 知识点总结-Java反射

时间:2023-02-17 08:43:39


----------------------Android培训Java培训、期待与您交流! ----------------------


【反射的基石-Class类】


      用于描述每个类在内存中的“字节码”这一事物。一个类被加载到内存中后,占用一片


 内存空间,这个空间里的内容即为字节码。字节码包含的信息有:类名、类的访问权限、


 类所属的包名、字段名称列表、方法名称列表等。


      Class类的实例表示正在运行的 Java应用程序中的类和接口。枚举是一种类,注释是


 一种接口。每个数组属于被映射为 Class对象的一个类,所有具有相同元素类型和维数


 的数组都共享该Class对象。


      Class没有公共构造方法。Class对象是在加载类时由 Java虚拟机以及通过调用类加


 载器中的defineClass方法自动构造的。


      如何得到各个字节码实力对象呢?


  1. 类名.class System.class应用前提:知道这个类的类名

  2. 对象.getClass() newDate().getClass应用前提:有这个对象或其引用

  3. Class.forName("类名")Class.forName("java.util.System")应用前提:知道类的全路径名

    九个预定义Class实例对象

           基本的 Java类型(booleanbytecharshortintlongfloat double)和关

      键字 void也表示为 Class对象。与基本类型相对应的基本类型包装类的静态成员

      TYPE返回的是对应的基本类型Class实例。可用Class对象的isPrimitive()判断自身

      是否是一个基本类型Class实例。(例如,void.class int.classboolean.class)

    数组类型的Class实例对象

           数组也有对应的字节码对象,即Class实例对象,可用isArray()方法判断自身是否

      是一个数组类型的Class实例。(例如,int[].class)

    总之,只要在源程序中出现的类型,都会有各自的Class实例对象。

    【反射】

           反射就是把Java类中的各种成分映射成相应的Java类。一个Java类可用一个Class

      的实例来表示,类中的组成部分:成员变量,方法,构造方法,包等信息也可以用一个个

      Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个类。表示

      Java类的Class类提供了一系列的方法,来获得其中的变量,方法,构造方法,修饰符,

      包等信息,这些信息用相应类的实例对象来表示,它们是FiledMethodContructor

      Package等。

           构造方法的反射应用:

                  Constructor类是代表类中构造方法的类


  • 得到某个类所有的构造方法:Class实例对象的getConstructors()

    public Constructor<?>[] getConstructors()throwsSecurityException

    如:获取String类中的所有构造方法

    Constructor[] constructors

    = Class.forName(“java.lang.String”).getConstructors();

  • 得到某个构造方法:Class实例对象的getConstructor()

    public Constructor<T> getConstructor(Class<?>...parameterTypes)

    throws NoSuchMethodException,SecurityException

    如:获取String类中参数为StringBuffer类型的构造方法

    Constructor constructor

            =Class.forName(“java.lang.String”).getConstructor(“StringBuffer.class”);

                         获取指定构造方法时,要指定参数

  • 创建实例对象:Constructor类中的newInstance()      

    public T newInstance(Object...initargs)

            throws InstantiationException,IllegalAccessException,

    IllegalArgumentException,InvocationTargetException

    通常方式:Stringstr = new String(new StringBuffer(“abc”));

    反射方式:Stringstr = (String)constructor.newInstance(new StringBuffer(“abc”));

    Constructor实例对象的newInstance方法中的参数应为获得该实例对象的getConstructor方法中的参数的实例对象

  • Class类中的newInstance()方法

    public T newInstance()throwsInstantiationException,IllegalAccessException

    创建此Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的new表达式实例化该类。如果该类尚未初始化,则初始化这个类。

    如:

            String str = (String)Class.forName(“java.lang.String”).newInstance();

    该方法用到了缓存机制来保存默认构造方法的实例对象

    该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象

           成员变量的反射应用:

                  Field类是代表类中成员变量的类

                         Field提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射

                         的字段可能是一个类(静态)字段或实例字段。该Field类的实例对象代表的

                         不是具体对象上的变量,而是类上的变量,若要获取某个对象上的变量的值,

                         则需要时定具体对象。

  • 获取某个类中的共有成员变量:

    public Field getField(String name)

    throwsNoSuchFieldException,SecurityException

    返回一个 Field 对象,它反映此Class对象所表示的类或接口的指定公

    共成员字段。name 参数是一个String,用于指定所需字段的简称。

                         publicField[]getFields()throws SecurityException

                                返回一个包含某些Field对象的数组,这些对象反映此Class 对象所表

                                示的类或接口的所有可访问公共字段。返回数组中的元素没有排序,也没

                                有任何特定的顺序。如果类或接口没有可访问的公共字段,或者表示一个

                                数组类、一个基本类型或 void,则此方法返回长度为 0的数组。

                         publicField getDeclaredField(String name)

    throwsNoSuchFieldException,SecurityException

                                返回一个Field对象,该对象反映此 Class 对象所表示的类或接口的指

                                定已声明字段。name参数是一个String,它指定所需字段的简称。

                         publicField[] getDeclaredFields()throws SecurityException

                                返回Field对象的一个数组,这些对象反映此Class对象所表示的类或

                                接口所声明的所有字段。包括公共、保护、默认(包)访问和私有字段,

                                但不包括继承的字段。返回数组中的元素没有排序,也没有任何特定的顺

                                序。如果该类或接口不声明任何字段,或者此Class对象表示一个基本

                                类型、一个数组类或 void,则此方法返回一个长度为 0的数组。

  • 使用反射获取具体对象中的某个变量的值:

    public Object get(Objectobj)

    throws IllegalArgumentException,IllegalAccessException

    返回指定对象上此 Field 表示的字段的值。

                         获取指定对象上的此Field表示的各种基本数据类型数据:

                                public基本数据类型 get+基本数据(Object coj)

                         publicClass<?>getType()

                                返回一个Class对象,它标识了此 Field 对象所表示字段的声明类型。

                         publicvoid setAccessible(boolean flag)throwsSecurityException

                                设置该Field实例对象是否是可访问的,若为ture,则表示可访问。

                         例:ReflectPoint类中有共有成员变量x和私有成员变量y,其通过反射获取

                                ReflectPoint实例对象中的两个变量的值


		ReflectPoint pt = new ReflectPoint(3,5);
		Field fieldX = pt.getClass().getField("x");
		System.out.println(fieldX.get(pt));
		Field fieldY = pt.getClass().getDeclaredField("y");
		fieldY.setAccessible(true);
		System.out.println(fieldY.get(pt));

  • 使用反射设置就提对象中某个变量的值:

    public void set(Objectobj,Object value)

    throws IllegalArgumentException,IllegalAccessException

           将指定对象变量上此Field对象表示的字段设置为指定的新值。

    设置指定对象上此Field表示的各种基本数据类型数据:

           public 基本数据类型 set+基本数据类型(Objectobj,Object value)

                         练习:将任意一个对象中所有String类型的成员变量所对应的字符串内容中的

                                 “b”改为”a”

import java.lang.reflect.Field;

class ReflectString {
	public String s1 = "base";
	private String s2 = "basketball";
	private String s3 = "helloworld";
}

class Test {
	public static void main(String[] args)throws Exception {
		ReflectString str = new ReflectString();
		// 获取成员变量数组
		Field[] fields = str.getClass().getDeclaredFields();
		// 遍历数组
		for (Field field : fields) {
			// 元素可方法
			field.setAccessible(true);
			// 判断元素类型是否为String
			if (field.getType() == String.class) {
				String oldValue = (String) field.get(str);
				String newValue = oldValue.replace('b', 'a');
				field.set(str, newValue);
			}
		}
	}
}

      成员方法的反射应用:


             Method类是代表类中成员方法的类


  • 得到某个类中的成员方法

    public Method getMethod(Stringname,Class<?>... parameterTypes)

    throws NoSuchMethodException,SecurityException

    返回一个 Method 对象,它反映此Class对象所表示的类或接口的指定公共成员方法。name参数是一个String,用于指定所需方法的简称。parameterTypes参数是按声明顺序标识该方法形参类型的Class 对象的一个数组。如果 parameterTypes null,则按空数组处理。

                         publicMethod[] getMethods()throwsSecurityException

                                返回一个包含某些Method对象的数组,这些对象反映此Class 对象所

                                表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承

    的那些的类或接口)的公共 member 方法。数组类返回从 Object 类继承

    的所有(公共)member 方法。返回数组中的元素没有排序,也没有任何

    特定的顺序。如果此 Class 对象表示没有公共成员方法的类或接口,或

    者表示一个基本类型或 void,则此方法返回长度为 0的数组。

  • 调用此Method对象代表的底层方法:Method类中的invoke()方法

    public Object invoke(Object obj,Object... args)throws

          IllegalAccessException,IllegalArgumentException,InvocationTargetException

                                对带有指定参数的指定对象调用由此Method对象表示的底层方法。个

                                别参数被自动解包,以便与基本形参相匹配,基本参数和引用参数都随需

                                服从方法调用转换。如果底层方法是静态的,那么可以忽略指定的obj

                                参数,写为 null。如果底层方法所需的形参数为 0,则所提供的args

                                组长度可以为 0 null

                  练习:用反射方式执行某个类中的main方法

                         目标:写一个程序可以根据用户提供的类名,去执行该类中的main方法

                    代码:

// 要执行的main方法所在的类
class TestArguements {
	public static void main(String[] args) {
		for (String arg : args) {
			System.out.println(arg);
		}
	}
}

主程序中的代码:


		String className = args[0];
		// 根据类名称获取Class对象,
		// 再使用Class类中的getMethod方法获取main方法
		Method main = Class.forName(className)
				.getMethod("main", String[].class);
		// 调用main代表的底层方法
		main.invoke(null, new Object[] { new String[] { "java", "hello java",
				"hello world" } });
		main.invoke(null, (Object) new String[] { "java", "hello java",
				"hello world" });

在运行该程序时,右击-->Run As-->Run Configurations...-->点击arguments-->Program arguments栏中填入要运行类的名称


                    问题:


                           启动Java程序的main方法的参数是一个字符串数组,当通过反射调用该


方法时,按找jdk 1.5的语法,整个数组是一个参数,而按照jdk 1.4的语法,


数组中的每个元素都对应一个参数。当把一个字符串数组传递给invoke


法时,由于jdk 1.5兼容jdk 1.4,所以会按照jdk 1.4语法进行处理,,即把


数组打散成若干个单独的参数,所以会出现参数类型不匹配。


                    解决办法:


  1. 将字符串数组最为元素封装到Object类型数组中,这样在按照jdk 1.4语法解析时,拆分Object类型数组得到的就是字符串数组

    main.invoke(null,newObject[]{

    new String[]{"java","hello java","hello world"}});

  2. 在字符串数组前面加上’(Object)’,这样编译器会做特殊处理,编译器不会把参数当作数组看待,也就不会把数组打散成若干个参数

    main.invoke(null, (Object)newString[]{"java","hello java","hello world"});


      数组的反射:


  1. 具有相同维数和元素类型的数组属于同一种类型,即具有相同的Class实例对象

  2. 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class对象

  3. 基本数据类型的一维数组可以被当作Object类型使用,但不能被当作Object类型数组使用,因为Object类型数组中存放的是Object类型元素,而基本数据类型一维数组中存放的是基本数据类型数据,基本数据类型数据不能转化为Object类型;非基本数据类型一维数组,既可以被当作Object类型使用,亦可以被当作Object类型数组使用

    如:

		// 定义整型一维,二维数组,字符串数组
		int[] a1 = new int[3];
		int[] a2 = new int[4];
		int[][] a3 = new int[2][3];
		String[] a4 = new String[3];
		// 获取四个数组的Class实例对象
		Class cls1 = a1.getClass();
		Class cls2 = a2.getClass();
		Class cls3 = a3.getClass();
		Class cls4 = a4.getClass();
		// 判断四个数组的Class实例对象是否相同
		System.out.println(cls1 == cls2);// true
		System.out.println(cls1 == cls3);// false
		System.out.println(cls1 == cls4);// false
		System.out.println(cls3 == cls4);// false
		// 不合法,因为a1中的元素为int型,不能被当作Object
		// Object[] o1 = a1;
		// 由于a3是整型二维数组,即每个元素为整形一维数组,
		// 整形一维数组可以被当作Object
		Object[] o3 = a3;
		Object[] o4 = a4;

数组反射的应用:


Array工具类(java.lang.reflect)用于对数组的反射操作


  1. Array类中的获取方法

    获取指定数组对象的指定索引的值:

    public static Object get(Object array,int index)throws IllegalArgumentException,

    ArrayIndexOutOfBoundsException

                         获取各种基本数据类型数据:

                         public static基本数据类型 get+基本数据类型(Object array,int index)

                         获取数组的长度:

                         public static intgetLength(Object array)throwsIllegalArgumentException

  2. Array类中的设置方法:

    设置指定数组对象的指定索引的值:

    public static void set(Object array,int index,Object value)

    throwsIllegalArgumentException,ArrayIndexOutOfBoundsException

                         设置各种基本数据类型数据:

                         public static基本数据类型

    set+基本数据类型(Object array,int index,基本数据类型value)

           反射的作用:实现框架功能

                  框架

                  框架要解决的核心问题

                        框架要解决的核心问题就是解决如何调用用户提供的类,因为框架无法得知要

                   调用的类的名称,无法直接new对象。解决办法是利用反射方实现。

                 综合案例:

                         采用配置文件加反射创建ArrayListHashSet的实例对象

                         配置文件用于存储要创建对象的类的名称,假如放在工程的根目录中

    如:className=java.util.ArrayList

    主函数代码:

		InputStream is = null;// 定义输入流变量,读取配置文件信息
		String className = null;// 定义变量接收要创建实例的类名
		Collection col = null;// 定义集合变量
		try {
			// 实例化输入流对象
			is = new FileInputStream("config.properties");
			// 创建Properties对象,使用该对象的load方法读取流中的属性信息
			Properties prop = new Properties();
			prop.load(is);
			// 根据键获取值
			className = prop.getProperty("className");
		} catch (IOException e) {
			throw new RuntimeException("读取配置文件失败!");
		} finally {
			try {
				if (is != null)
					is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		try {
			// 利用反射创建className类的实例
			col = (Collection) Class.forName(className).newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 添加元素
		ReflectPoint pt1 = new ReflectPoint(3, 3);
		ReflectPoint pt2 = new ReflectPoint(5, 5);
		ReflectPoint pt3 = new ReflectPoint(3, 3);
		col.add(pt1);
		col.add(pt2);
		col.add(pt3);
		col.add(pt1);
		// 打印集合大小
		System.out.println(col.size());

             管理资源和配置文件:


             eclipse对资源文件的管理方式:


             eclipse会自动把资源文件复制到指定classpath目录中的某个位置,比如说,若资


             源文件在src目录下,则eclipse会自动把资源文件复制到bin目录中,若资源文件


             在某个包中,则eclipse会自动把资源文件复制到bin目录中相应的包中


             用类加载器加载配置文件:publicInputStreamgetResourceAsStream(String name)


             //类加载器加载配置文件,是从classpath路径中找的,若程序是用eclipse编写


             //的,则会从bin目录中查找


             is = ReflectTest.class.getClassLoader()


.getResourceAsStream("cn/itcast/day1/config.properties");


             Class对象加载配置文件:publicInputStreamgetResourceAsStream(String name)


             //Class实例对象加载配置文件,是从classpath路径中找的,若程序是用eclipse


             写的,则会根据类当前在src中的相对位置,在bin目录对应的相对位置查找


             is =ReflectTest.class.getResourceAsStream("resources/config.properties");


      内省à了解JavaBean


             内省对应的英文单词是IntroSpector,它只要用于对JavaBean进行操作;JavaBean


       是一种特殊的Java类,其中的某些方法符合某些命名规则。如果一个Java类中一些


      方法符合这种命名规则,则可以把其当作JavaBean来使用


             JavaBean


                    JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方


              法主要用于访问私有的字段,且方法名符合某种命名规则。


                    如果要在两个模块之间传递多个数据,可以将这些数据封装到一个JavaBean


              中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO),这些


              信息在类中用私有字段存储,如果读取或设置这些字段的值,则需要通过一些相


              应的方法来访问。JavaBean中属性是根据其中的settergetter方法来确定的,


              而不是根据其中的成员变量。JavaBean中,去掉settergetter方法的前缀set


              get,剩余的部分就是属性名。格式:如果剩余部分的第二个字母是小写的,


              则把剩余部分的首字母改成小写。如:


                    setAge()属性名àage


                    isLast()属性名àlast


                    getCPU()属性名àCPU


                    总之一个类被当作JavaBean使用时,JavaBean的属性是根据方法名推出来的,


              它根本看不到java类内部的成员。


                    一个符合JavaBean特点的类也可以当作普通类来使用,但把它当作JavaBean


              用肯定会带来一些额外的好处:


  1. JavaEE开发中,经常要使用JavaBean。很多环境就要求按JavaBean方式进行操作。

  2. JDK中提供了对JavaBean进行操作的一些API,这套API就成为内省。用内省操作JavaBean比用普通类的方式更方便。

    内省综合案例:

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class IntroSpectorTest {
	public static void main(String[] args) {
		ReflectPoint pt1 = new ReflectPoint(3, 5);
		// 定义属性名变量
		String propertyName = "x";
		Object value = 7;
		// 调用方法,设置属性的值和获取属性的值
		setProperty(pt1, propertyName, value);
		getProperty(pt1, propertyName);
	}

	// 自定义方法,获取属性值
	private static void getProperty(Object pt1, String propertyName) {
		try {
			// 创建属性描述符对象,指定JavaBean对象和属性名
			PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1
					.getClass());
			// getReadMethod()获取属性的get方法
			Method methodGetX = pd.getReadMethod();
			// 调用属性get方法,获得属性值
			Object retValue = methodGetX.invoke(pt1);
			System.out.println(retValue);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 自定义方法,设置属性值
	private static void setProperty(Object pt1, String propertyName,
			Object value) {
		try {
			// 创建属性描述符对象,指定JavaBean对象和属性名
			PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1
					.getClass());
			// getWriteMethod()获取属性的set方法
			Method methodSetX = pd.getWriteMethod();
			// 调用属性set方法,设置属性值
			methodSetX.invoke(pt1, value);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 自定义方法,获取属性值(对JavaBean的复杂内省操作)
	private static void getProperty_1(Object pt1, String propertyName) {
		Object retValue = null;
		try {
			// 利用内省类的getBeanInfo方法获取BeanInfo子类对象
			BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());
			// 获取属性描述符数组
			PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
			// 遍历数组,查找指定属性名
			for (PropertyDescriptor pd : pds) {
				if (pd.getName().equals(propertyName)) {
					// getReadMethod()获取属性的get方法
					Method methodGetX = pd.getReadMethod();
					// 调用属性get方法,获得属性值
					retValue = methodGetX.invoke(pt1);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println(retValue);
	}
}

             PropertyDescriptor:属性描述符,初始化时指定JavaBean实例对象和要操作的属性


                    |--getWriteMethod():返回要操作属性的setter方法


                    |--getReadMethod():返回要操作属性的getter方法


             IntroSpector:该为通过工具学习有关受目标Java Bean支持的属性、事件和方法的


                                  知识提供了一个标准方法。


                    |--getBeanInfo()  Java Bean上进行内省,了解其所有属性、公开的方法和


                                                事件。返回描述目标 bean BeanInfo对象。


             BeanInfo:接口,提供了有关Java Bean的方法、属性、事件等显式信息。


                    |--getPropertyDescriptors():获取属性描述集合


             使用BeanUtils工具包操作JavaBean


             BeanUtils类中的静态方法:


             |--publicstatic java.lang.StringgetProperty(java.lang.Object bean,


 java.lang.String name)


                    获取指定JavaBean对象的指定属性值 beanJavaBean对象name:属性名


             |--publicstatic voidsetProperty(java.lang.Object bean,


                              java.lang.String name, java.lang.Object value)


                    设置指定JavaBean对象的指定属性值 value:值


             |--public static voidcopyProperties(java.lang.Object dest,java.lang.Object orig)


                    JavaBean对象orig中的属性赋值给JavaBean对象dest


             BeanUtils工具包还支持属性的级联操作

		ReflectPoint pt1 = new ReflectPoint(3, 5);
		// 定义属性名变量
		String propertyName = "x";
		try {
			// 使用BeanUtils工具包的方法设置属性x的值
			BeanUtils.setProperty(pt1, propertyName, "9");
			// 使用BeanUtils工具包的方法获取属性x的值
			System.out.println(BeanUtils.getProperty(pt1, propertyName));
			// birthday类型是Date型,该类型数据有属性time的setter和getter方法
			// BeanUtils工具包支持属性的级联操作
			BeanUtils.setProperty(pt1, "birthday.time", "2013");
			System.out.println(BeanUtils.getProperty(pt1, "birthday.time"));
		} catch (Exception e) {
			e.printStackTrace();
		}









----------------------Android培训Java培训、期待与您交流! ----------------------