黑马程序员——反射

黑马程序员--反射

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

一、概述

1、反射机制
它是Java成为动态语言的一个关键特性,在运行中的Java程序对自身进行检查或者说是自省(Introspection),并
能直接操作程序的内部属性。这个机制允许程序在执行时可以通过反射APIs取得任何一个已知名称的类的信息,并能
在执行时改变类的成员变量(fields)的内容或执行方法(methods)。
2、应用
当我们对一个已经投入使用的应用程序进行维护、升级时,通常会提供一个配置文件,来供以后实现此程序的类进行功能扩展。
对外提供配置文件,让后期出现的子类直接将类名配置到配置文件中。该应用程序直接读取配置文件中的内容,并查找和给定
名称相同的类文件。操作步骤:a 加载这个类;b 创建该类对象;c 调用该类内容。
当应用程序使用的类不明确时,通过让使用者将具体子类存储到配置文件,用反射机制获取类中内容。
优点:反射机制大大提高了程序的扩展性。
二、反射的相关对象
1、概述
反射:就是把Java类中的各种成分映射成相应的Java类。如:一个Java类中用一个Class类的对象来表示;一个类中组成部分:成员变量、方法、构造方法
、包等信息也用相应的Java类来表示。像汽车是一个类,汽车中的发动机、变速箱等也是一个个类。表示Java类的Class类显然要
提供一系列的方法,以获取其中的变量、方法、构造函数、修饰符、包等信息。这些信息就是用相应类的实例对象来表示,
它们是Filed、Method、Contructor、Package等。
简单说就是:动态获取类中信息,就是Java反射。它用来对指定名称的字节码文件进行加载并获取其中的内容并调用。
反射使程序代码能够访问装载到JVM中的类的内部信息,主要包括获取已装载类的字段、方法和构造函数的信息,允许编写处理类
的代码,这些类是在程序运行时临时确定的,而非源代码中事先选定。

2、反射的基石--Class类
2.1 所有的类文件都有共同属性,将这些类文件的共性内容封装成一个类,这个类就是Class(描述字节码文件的对象)。
Class类中包含的属性有:field(字段)、method(方法)、construction(构造函数)。
而field中有修饰符、类型、变量名等复杂的描述内容,因此也可以将字段封装成一个对象。用来获取类中field的内容,这个
对象的描述叫Field。同理方法和构造函数也被封装成对象Method、Constructor。要想对一个类进行内容的获取,必须先获取该
字节码文件的对象。该对象是Class类型。
2.2 Class类描述了哪些方面的信息呢?
包含:类的名字;类的访问属性;类所属的包名;字段名称的列表、方法名的列表等。
2.3 Person类代表人,它的实例对象就是张三、李四这样一个个具体的人,Class类代表Java类,它的实例对象对应什么?
Class类的实例对象对应的是各个类在内存中的字节码(class)。如:Person类的字节码,ArrayList类的字节码等。
一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码不同,
所以它们在内存中的内容是不同的。这一个个的空间可分别用一个个对象来表示。同时,这些对象显然具有相同的类型---Class类型。
PS:
1 字节码(Byte-code):是一种包含执行程序、由一系列操作代码/数据对组成的二进制文件。在Java中就是class文件。
2 Class和class的区别:Class是所有Java类的总称;class是Java类的实例对象。
2.4 获取Class对象的三种方式:
a 类名.class; 如:System.class;缺点:需要明确具体的类及其对象,还要调用getClass方法。
b 对象.getClass(); 如:new Date().getClass();缺点:需求明确具体的类。
c Class.forName("类名"); 如:Class.forName("java.util.Date");

2.5 预定义的Class实例对象
2.5.1 包括:八个基本数据类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void;
2.5.2 基本数据类型的字节码都可以拥与之对应的包装类中的TYPE常量表示。
如:基本数据类型int,它对应的包装类是Integer类,所以:int.class == Integer.TYPE。
2.5.3 只要是在源程序中出现的类型,都有各自的Class实例对象。如:int[].class、void.class。
数组类型的Class实例对象,可以用Class.isArray()方法判断是否为数组类型。
2.6 Class类中的常见方法:
2.6.1 static Class forName(String className):返回与给定字符串名的类、接口相关联的Class对象;
2.6.2 Class getClass():返回的是Object运行时的类;
2.6.3 Constructor getConstructor():返回此Class对象所表示的类的指定公共构造方法;
2.6.4 Field getField(String name):返回此Class对象所代表的类或接口的指定的公共成员字段;
2.6.5 Field[] getFields():返回包含类中成员字段的数组;
2.6.6 Method getMethod(String name,Class... parameterTypes):返回此Class对象所代表的类的指定公共成员方法;
2.6.7 Method[] getMethods():返回包含类中公共成员方法的数组;
2.6.8 String getName():返回此Class对象字符串形式的实体名称;
2.6.9 String getSuperclass():返回此Class对象的父类名称;
2.6.10 boolean isArray():判断此Class对象是否表示一个数组;
2.6.11 boolean isPrimitive():判断指定的Class对象是否是一个基本类型;
2.6.12 T newInstance():创建此Class对象所表示的类的一个新对象。

  1. package cn.itheima;  
  2. public class Person  
  3. {  
  4.     private String name;  
  5.     public int age;  
  6.     public Person()  
  7.     {  
  8.         System.out.println("Person is run");  
  9.     }  
  10.     public Person(String name,int age)  
  11.     {  
  12.         this.age = age;  
  13.         this.name = name;  
  14.     }  
  15.     public String toString()  
  16.     {  
  17.         return name+":"+age;  
  18.     }  
  19. }  
  20. class  CreateClassDemo  
  21. {  
  22.     public static void main(String[] args)   
  23.     {  
  24.         createPersonClass();  
  25.     }  
  26.     //通过Class对象创建类实例方法   
  27.     public static void createPersonClass() throws Exception  
  28.     {  
  29.         //获取Person类的Class对象   
  30.         String className = "cn.itheima.Person";  
  31.         Class clazz = Class.forName(className);  
  32.   
  33.         //通过newInstance方法获取类的无参构造函数实例   
  34.         Person p = (Person)clazz.newInstance();  
  35.     }  
  36. }  
package cn.itheima;
public class Person
{
	private String name;
	public int age;
	public Person()
	{
		System.out.println("Person is run");
	}
	public Person(String name,int age)
	{
		this.age = age;
		this.name = name;
	}
	public String toString()
	{
		return name+":"+age;
	}
}
class  CreateClassDemo
{
	public static void main(String[] args) 
	{
		createPersonClass();
	}
	//通过Class对象创建类实例方法
	public static void createPersonClass() throws Exception
	{
		//获取Person类的Class对象
		String className = "cn.itheima.Person";
		Class clazz = Class.forName(className);

		//通过newInstance方法获取类的无参构造函数实例
		Person p = (Person)clazz.newInstance();
	}
}

3、Constructor类
3.1 它代表某个类中的一个构造方法。适用于类中缺少空参数构造函数或需指定构造函数的初始化。
3.2 获取构造方法:
a 获取某一个构造方法:
Constructor constructor = 
           Class.forName("java.lang.String").getConstructor(String.class,int.class);
b 获取类中所有构造方法:
Constructor[] constructors =
           Class.forName("java.lang.String").getConstructors();


c 创建实例对象:
一般方式:Person p = new Person("lisi",30);
反射方式:Person p = (Person)constructor.newInstance("lisi",30);
PS:
1 创建实例时newInstance方法中的参数列表必须与获取构造方法中的getConstructor的参数列表一致;
2 newInstance():构造一个实例对象,每调用一次就构造一个对象;
3 利用Constructor类来创建类实例的优点:可指定构造函数;而Class类只能利用无参构造函数创建类实例对象;

  1. //通过Constructor对象创建类实例   
  2. public static void createPersonClass_2() throws Exception  
  3. {  
  4.     String className = "cn.itheima.Person";  
  5.     Class clazz = Class.forName(className);  
  6.   
  7.     //获取指定构造函数的类实例   
  8.     Constructor con = clazz.getConstructor(String.class,int.class);  
  9.     Person p = (Person)con.newInstance("lisi",30);  
  10.   
  11.     System.out.println(p.toString);  
  12. }  
	//通过Constructor对象创建类实例
	public static void createPersonClass_2() throws Exception
	{
		String className = "cn.itheima.Person";
		Class clazz = Class.forName(className);

		//获取指定构造函数的类实例
		Constructor con = clazz.getConstructor(String.class,int.class);
		Person p = (Person)con.newInstance("lisi",30);

		System.out.println(p.toString);
	}
4、Field类
4.1 Field类:代表某个类中的一个成员变量。
4.2 常见方法:
Field getField(String s):只能获取公有和父类中公有成员变量;
Field getDeclaredField(String s):获取该类中任意成员变量,包括私有;
setAccessible(ture):暴力访问:如果是私有字段,要先将该私有字段进行取消权限检查的能力;
set(Object obj, Object value):将指定对象变量上此Field对象表示的字段设置为指定的新值;
Object get(Object obj):返回指定对象上Field表示的字段的值。
  1. public static void getPersonField() throws Exception  
  2. {  
  3.     //给变量赋值必须先有对象   
  4.     Class clazz = Class.forName("cn.itheima.Person");  
  5.   
  6.     Person p = (Person)clazz.newInstance();  
  7.   
  8.     //获取所有的成员变量   
  9.     Field[] fs = clazz.getFields();  
  10.     for(Field f : fs)  
  11.     {  
  12.         System.out.println(f);  
  13.     }  
  14.   
  15.     //获取指定的成员变量   
  16.     Field fa = clazz.getField("age");  
  17.   
  18.     Field fn = clazz.getDeclaredField("name");  
  19.   
  20.     //显示改变后的值   
  21.      fa.set(p,20);  
  22.      System.out.println(fa.get(p));  
  23.   
  24.      //暴力访问   
  25.      fn.setAccessible(true);  
  26.      fn.set(p,"zhangsan");  
  27.      System.out.println(fn.get(p));  
  28. }  
	public static void getPersonField() throws Exception
	{
		//给变量赋值必须先有对象
		Class clazz = Class.forName("cn.itheima.Person");

		Person p = (Person)clazz.newInstance();

		//获取所有的成员变量
		Field[] fs = clazz.getFields();
		for(Field f : fs)
		{
			System.out.println(f);
		}

		//获取指定的成员变量
		Field fa = clazz.getField("age");

		Field fn = clazz.getDeclaredField("name");

		//显示改变后的值
		 fa.set(p,20);
		 System.out.println(fa.get(p));

		 //暴力访问
		 fn.setAccessible(true);
		 fn.set(p,"zhangsan");
		 System.out.println(fn.get(p));
	}
5、Method类
Method类:代表某个类中的一个成员方法。
5.1 特点:专家模式:谁调用这个数据,谁就是调用它的专家。
总结:变量使用方法:方法本身知道如何实现执行的过程,即"方法对象"调用方法,才执行了方法的各个细节。
5.2 常见方法:
Method[] getMethods():只获取公共和父类中的方法。
Method[] getDeclaredMethods():获取本类中包含私有。
Method getMethod("方法名",参数.class(如果是空参可以写null));
Object invoke(Object obj ,参数):调用方法。如果方法是静态,invoke方法中的对象参数可以为null。
如:
一般方式:System.out.println(str.charAt(1));
反射方式:System.out.println(charAt.invoke(str,1));若第一个参数为null,代表Method方法对应的是一个静态方法。
  1. //获取Person类中的方法   
  2. public static void getPersonMethod() throws Exception  
  3. {  
  4.     Class clazz = Class.forName("cn.itheima.Person");  
  5.     Person p = (Person)clazz.newInstance();   
  6.       
  7.     //获取单个方法   
  8.     Method me = clazz.getMethod("toString",null);  
  9.   
  10.     Object returnValue = me.invoke(p,null);  
  11.     System.out.println(returnValue);  
  12.   
  13.     //获取所有方法   
  14.     Method[] mes = clazz.getMethods();  
  15.     for(Method me : mes)  
  16.     {  
  17.         System.out.println(me);  
  18.     }  
  19. }  
	//获取Person类中的方法
	public static void getPersonMethod() throws Exception
	{
		Class clazz = Class.forName("cn.itheima.Person");
		Person p = (Person)clazz.newInstance();	
		
		//获取单个方法
		Method me = clazz.getMethod("toString",null);

		Object returnValue = me.invoke(p,null);
		System.out.println(returnValue);

		//获取所有方法
		Method[] mes = clazz.getMethods();
		for(Method me : mes)
		{
			System.out.println(me);
		}
	}
5.3 jdk1.4和jdk1.5的invoke方法的区别:
jdk1.5:public Object invoke(Object obj,Object...args)
jdk1.4:public Object invoke(Object obj,Object...[] args),
即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,
所以,调用charAt方法的代码也可以用jdk1.4改写为:charAt.invoke("str",new Object[]{1})形式。

5.4 用反射方式执行类中的main方法:
5.4.1 写一个能够根据用户提供的类名,去执行该类中的main方法的程序。可以用普通方式和反射方式,为什么要用反射去调用呢?
答:一般方式调用方法必须有明确的对象;反射不用。
5.4.2 启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用
这个main方法时,如何为invoke方法传递参数呢?
答:按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数
传递给invoke方法时,Jdk1.5因为要兼容jdk1.4,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给
main方法传递参数时,不能使用代码:mainMethod.invoke(null,new String[]{"xxx"}),javac只能把它当作jdk1.4的语法进行理解,
因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"});
mainMethod.invoke(null,(Object)new String[]{"xxx"});,此时编译器会做特殊处理,
编译时不把参数当作数组看待,也就不会将数组打散成若干参数了。

5.6 数组的反射
5.6.1 数组的反射:具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
5.6.2 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
5.6.3 Object与String有父子关系;Object[]与String[]没有父子关系;所以Object x ="abc";能
强制转换成String x ="abc";new Object[]{"aaa","bb"}不能强制转换成new String[]{"aaa","bb"}; 
5.6.4 如何得到某个数组中的某个元素的类型,
例:
     int a = new int[3];//我想通过反射知道a的具体类型,这个无法办到。
     Object[] obj=new Object[]{"ABC",1};//因为反射后为Object[],它的类型是任意的。
无法得到某个数组的具体类型,只能得到其中某个元素的类型,
如:
     Obj[0].getClass().getName();//得到的是java.lang.String。
5.6.5 Array工具类用于完成对数组的反射操作。
Array.getLength(Object obj):获取数组的长度;
Array.get(Object obj,int x):获取数组中的元素;
5.6.6 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;
非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
5.7 HashCode的分析
5.7.1 HashCode():一种元素查找方式。它将集合分为若干存储区域,每个对象可以计算出一个哈希码,可将哈希码分组,每组
分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的区域。
5.7.2 HashSet类:采用哈希算法存取对象的集合。它内部采用对某个数字n进行取余的方式,对哈希码进行分组和划分对象的存储区域。
Object类中定义了一个hashCode()方法来返回每个Java对象的哈希码,当从HashSet集合中查找某个对象时,Java系统首先
调用对象的hashCode()方法获得该对象的哈希码,然后根据哈希码找到对应的存储区域,最后取出该存储区域内的每个元素
与该对象进行equals方法比较,这样不用遍历集合中的所有元素就可以得到结论。因此HashSet类有较好的检索功能,但是其
存储效率较低,因为要通过分区存储。
提示:1,通常一个类的两个实例对象用equals()方法比较的结果相等时,他们的哈希码也必须相等,但反之则不成立。
即:equals方法比较结果不相等的对象可以有相同的哈希码,或哈希码相同的两个对象equals方法比较的结果可以不等。
2,当一个对象被存储进HashSet集合中后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值
与最初存储进HashSet集合中的哈希值就不相同了。这时,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合
中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。

  1. package cn.itheima;  
  2.   
  3. public class ReflectPoint {  
  4.     private int x;  
  5.     public int y;  
  6.       
  7.     public ReflectPoint(int x, int y) {  
  8.         super();  
  9.         this.x = x;  
  10.         this.y = y;  
  11.     }  
  12.     @Override  
  13.     public int hashCode() {  
  14.         final int prime = 31;  
  15.         int result = 1;  
  16.         result = prime * result + x;  
  17.         result = prime * result + y;  
  18.         return result;  
  19.     }  
  20.     @Override  
  21.     public boolean equals(Object obj) {  
  22.         if (this == obj)  
  23.             return true;  
  24.         if (obj == null)  
  25.             return false;  
  26.         if (getClass() != obj.getClass())  
  27.             return false;  
  28.         ReflectPoint other = (ReflectPoint) obj;  
  29.         if (x != other.x)  
  30.             return false;  
  31.         if (y != other.y)  
  32.             return false;  
  33.         return true;  
  34.     }     
  35. }  
package cn.itheima;

public class ReflectPoint {
	private int x;
	public int y;
	
	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ReflectPoint other = (ReflectPoint) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}	
}

三、反射的作用

反射的作用--->实现框架功能
1、框架与框架要解决的核心问题
我做房子卖个用户,用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入我提供的框架中。
框架与工具类有区别:工具类被用户的类调用,而框架则是调用用户提供的类。
2、框架要解决的核心问题
因为写框架时无法知道要被调用的类名,在程序中无法直接new某个类的实例对象,所以,要借助反射。
3、应用

  1. package cn.itheima;  
  2.   
  3. import java.io.FileInputStream;  
  4. import java.io.InputStream;  
  5. import java.util.ArrayList;  
  6. import java.util.Collection;  
  7. import java.util.HashSet;  
  8. import java.util.Properties;  
  9.   
  10. public class ReflectTest2 {  
  11.       
  12.     public static void main(String[] args)throws Exception  
  13.     {  
  14.         //导入未知文件,这个文件由用户自己定义   
  15.         InputStream ips = new FileInputStream("config.properties");  
  16.         Properties props = new Properties();  
  17.         props.load(ips);  
  18.         ips.close();//关闭流对象,对象ips由虚拟机关闭。    
  19.           
  20.         //获取类名   
  21.         String className = props.getProperty("className");  
  22.         Collection collections = (Collection)Class.forName(className).newInstance();  
  23.           
  24.         //Collection collections = new ArrayList();//可重复   
  25.         //Collection collections = new HashSet();//不可重复   
  26.         ReflectPoint pt1 = new ReflectPoint(3,3);  
  27.         ReflectPoint pt2 = new ReflectPoint(5,4);  
  28.           
  29.         //pt1和pt3有两个独立的内存地址,哈希值不同。但当equals覆盖后,哈希值相同。   
  30.         ReflectPoint pt3 = new ReflectPoint(3,3);  
  31.         collections.add(pt1);  
  32.         collections.add(pt2);  
  33.         collections.add(pt3);  
  34.         collections.add(pt1);  
  35.           
  36.         //pt1.y = 7;   
  37.         //collections.remove(pt1);//此时的哈希值就不是原始值,所以删除不了原来的pt1.   
  38.           
  39.         System.out.println(collections.size());  
  40.     }  
  41. }  
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值