反射

反射的基石-Class类

1、java代码的阶段:一段java代码在程序运行期间会经历三个阶段: source(.java文件)-->class(.class文件)-->runtime

如同我们用Person类来描述人一样,我们用Class类来描述java中的.class文件,即字节码文件。Java中的类都是描述一类事物的共性特征,他们都具有不同的属性,而这些属性的取值在不同的对象中各不相同。,Class类也是如此,Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表、方法名称的列表,等等。

每一个java类或者说每一个.class文件都是Class类的一个实例对象。它描述了该类有什么方法,有什么字段,及其访问属性。

2、Class类代表Java类,它的各个实例对象又分别对应什么呢?

对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,他们就是Class类的实例对象。

 3、如何得到各个字节码对应的实例对象( Class类型)

类名.class,例如,System.class

对象.getClass(),例如,new Date().getClass()

Class.forName("类名"),例如,Class.forName("java.util.Date");

/ 1. 根据给定的类名来获得  用于类加载
String classname = "cn.itcast.reflect.Person";	// 来自配置文件
Class clazz = Class.forName(classname);	// 此对象代表Person.class

// 2. 如果拿到了对象,不知道是什么类型   用于获得对象的类型
Object obj = new Person();
Class clazz1 = obj.getClass();	// 获得对象具体的类型
// 3. 如果是明确地获得某个类的Class对象  主要用于传参
Class clazz2 = Person.class;	
// 在java中所有的类型都会对应一个Class对象 int Integer
Class intClazz = int.class;
Class intarrClazz = int[].class;
Class voidClazz = void.class;
String s = "abc";
Class cls1 = Class.forName("java.lang.String");
Class cls2 = s.getClass();
System.out.println(cls1==cls2);
		
//int是基本类型,Integer是包装类,两个不是同一个类型
System.out.println(int.class == Integer.class);
		
//判断该字节码是否是基本类型的字节码
System.out.println(cls1.isPrimitive());

//Integer中的TYPE是Integer所包装的基本对象的字节码
System.out.println(int.class==Integer.TYPE);
		
		
//任何类型都是Class的实例对象,int[]对应的class不是原始类型
//应该是数组类型。
System.out.println(Integer[].class.isPrimitive());
System.out.println(Integer[].class.isArray());

4、9个预定义的Class实例对象:8个基本类型和void。

反射:反射就是将java类中的各个成分映射成相应的java类。一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。

1、构造方法的反射:

 Constructor类代表反射所得到的的类中的构造方法。

//得到类的所有构造方法
		Constructor []constructors1 = String.class.getDeclaredConstructors();
		//得到某一个构造方法,通过参数的不同来得到不同的构造方法
		//可变参数,构造方法有几个参数就传几个相应类型的class对象
		Constructor c= String.class.getConstructor(parameterTypes);
		//得到构造方法后可以得到该构造方法的各种属性,包括访问权限,参数列表等,
		//还可以创建对象,创建时参数要匹配,否则出现类型不匹配错误
		String str = (String)c.newInstance(initargs);//编译器不知知道该构造方法
		//是String的构造方法,一定要加上强制类型转换


如何查看源代码:

找到JDK所在目录,并打开src.zip,就可以查看源代码了。

Class.newInstance()方法:

例子:String obj = (String)Class.forName("java.lang.String").newInstance();

如果要使用默认的无参构造方法,就不用反射得到构造方法,直接使用该方法

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

2、成员变量的反射

Field类代表反射所得到的的类中的成员变量

ReflectPoint point = new ReflectPoint(1,7);
//得到字段y
	Field y = Class.forName("cn.itcast.corejava.ReflectPoint").getField("y");
	System.out.println(y.get(point));
	//Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");只能得到可见的
    //返回NoSuchFieldException运行时异常
//成员变量x在RefelectPoint是私有的,所以外界看不到
//强制得到私有成员变量
	Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x");
   //私有成员的Accessable属性默认为false
	x.setAccessible(true);
	System.out.println(x.get(point));

作业:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。

public class ReflectPiont {
	public String name;
	private String other;
	public ReflectPiont(String name, String other) {
		super();
		this.name = name;
		this.other = other;
		}
	public String getName() 
	{
		return name;
	}
	public void setName(String name)
	{
		this.name = name;
	}
	public String getOther()
	{
		return other;
	}
	public void setOther(String other)
	{
		this.other = other;
	}
	public String toString()
	{
		return name+":"+other;
	}
}

public static void changeStrValue(Object obj)throws Exception{
	Field[] fields = obj.getClass().getDeclaredFields();
	for(Field field :fields)
		{
			if(field.getType()==String.class)
			{
				String oldValue = (String)field.get(obj);
				String newVlaue = oldValue.replace('b', 'a');
				field.set(obj, newVlaue);
			}
		}
	}

3、成员方法的反射

Method类代表某个类中的一个成员方法

得到类中的某一个方法:

例子:Method charAt =Class.forName("java.lang.String").getMethod("charAt",int.class);

getMethod(String name,Class parameterTypes)第一个参数指定想要得到的方法的名称,第二个参数是可变参数,指定想要的方法的参数。

调用方法:

通常方式:System.out.println(str.charAt(1));

反射方式:System.out.println(charAt.invoke(str, 1));

如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!

jdk1.4和jdk1.5的invoke方法的区别:

Jdk1.5:public Objectinvoke(Object obj,Object... args)

Jdk1.4:public Objectinvoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为charAt.invoke(“str”, new Object[]{1})形式

 

mainMethod.invoke(null,new String[]{“xxx”})出现IllegalArguementException

原因:按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[]{newString[]{"xxx"}});

mainMethod.invoke(null,(Object)newString[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了

4、数组的反射

Java.lang.reflect.Array用于数组的反射

l  具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

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

l  基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

Arrays.asList()方法处理int[]和String[]时的差异:

int[] a1= new int[]{1,2,3};
String [] a2 = new String[]{"a","b","c"};
		
//int[]数组不能转换成Object[],按照JDk5的语法处理,把整个数组当成
//一个Object来处理,放入到list中
System.out.println(Arrays.asList(a1));
		
//String[]数组能够转化为Object[],按照JDk1.4的语法处理,把String[]
//中的每一个元素当成一个Object来处理,放入到list中。
//打印时会打印其中的内容
System.out.println(Arrays.asList(a2));

数组反射的应用:打印对象,如果给的是数组,打印其中的每一个元素,如果是对象,则直接打印对象。

public static void printObj(Object obj)

   {

      Class clazz = obj.getClass();

      

      //判断是否是数组

      if(clazz.isArray())

      {

         //反射得到数组的长度

         int len = Array.getLength(obj);

         for(int i = 0;i<len;i++)

         {

            //反射得到数组中第i个元素并打印

            System.out.println(Array.get(obj, i));

         }

      }else

      {

         System.out.println(obj);

      }

   }

hashCode()的作用

在java中hashCode()一般用于底层用哈希结构实现的存储结构中,比如:hashSet,hashMap。

为了提高效率,在这种存储结构中用哈希值来决定元素的存储位置,hashCode()就是通过某种算法来返回一个对象的哈希值。例如:在hashSet中通过哈希值和equals()方法来保证集合中元素的唯一性。在对象存入集合之前,程序会调用hashCode()得到其哈希值并判断集合中是否已经存在哈希值与其相同的对象,如果不存在,则该对象会被加入到集合中,如果不存在,程序会调用equals()方法来判断集合中是否存在与其比较结果为true的对象,如果不存在,则该对象会被加入到集合中,如果存在,则该对象不会被加入到集合中。一般来说,

如果两个对象的equals()方法相同,则应该让其hashcode()也相等。

注意: 如果一个元素已经被存入hash集合中,就不应该修改这个对象中那些参与哈希值运算的字段了,否则对象修改后的哈希值与存进哈希集合中的时候不一样,即使在contains方法使用该对象的当前引用作为参数去集合中检索对象,也将返回找不到对象的结果,导致无法从集合中删除当前对象,从而造成内存泄露。

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

到底框架是什么?  框架就是将开发中大量重复的代码集中起来写个通用的程序框架就是用反射来实现的,      框架需要现在的类调用将来写的类

框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。

框架是将来的程序员调用的,框架不能实现完整的功能,框架只是一些一些通用的代码;

框架要依赖将来写的类来运行.

现在写的类要调用将来写的类,我们先针对接口进行调用,将来的类需要实现接口,那么方法就固定了    

但是将来写的类的类名我们无法获知,这时就需要调用者通过配置文件告诉框架具体的类名

应用实例:

由于我们的程序要用到以后才会出现的类(位置类),所以我们无法现在就建立类的对象,只有用反射让程序能得到将要用的类,而这个类的名字由后来用程序的人用配置文件的方式告诉程序。

1、  建立一个配置文件config.properties,存放配置信息。

2、  程序启动先加载配置信息,会根据得到的类名来建立类的对象,然后应用。

import java.io.*;
import java.util.*;
public class ReflectTest2 {

	public static void main(String[] args) throws Exception{
		InputStream is = new FileInputStream("config.properties");
		
		/* Properties类是hashMap的子类,储存的对象也是键值对,但是对hashMap进行
		 * 了功能扩展,它可以直接从流中得到键值对,也可以将键值对直接写入到流中
		 */
		Properties properties = new Properties();
		properties.load(is);//从流中加载键值对
		is.close();//关闭is所关联的系统资源
		
		/*调用getProperty(String key)得到所需要的类名
		 */
	String className =properties.getProperty("className");

		//用反射方法创建对象
	Collection collections =(Collection)Class.forName(className).newInstance();
	}

}

用类加载器的方式管理资源和配置文件

Java程序运行时,如果需要加载配置文件,就需要得到配置文件的路径,如果该路径用相对路径表示,则程序只会在当前工作目录下寻找该文件,也就是在当前目录下寻找是否存在该路径表示的文件。因此,一般用绝对路径表示配置文件所在的路径。

要得到完整的目录,就需要运算,用系统的getRealPath()方法来得到当前工程的目录,然后再和配置文件的相对路径相连接,就得到配置文件的完整目录。

类加载器

类加载器能够加载.class文件,也可加载普通的文件。

类名.class.getClassLoader().getResourceAsStream(String name)  该方法会在classpath下寻找指定文件并加载该文件 name为文件的绝对路径或者相对于该工程的相对路径

缺点:这种方式只能从classpath指定的目录读,但是不能存文件。

同样:类名.class.getClassLoader().getResourceAsStream(String name)

name为文件的绝对路径或者相对于该工程的相对路径

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值