java基础加强_反射


反射的基石:Class类

Class类概述:
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。 Class类中提供了大量操作字节码文件的方法。
Class类代表Java类,它的各个实例对象又分别对应对应各个类在内存中的字节码,
例如,Person类的字节码,ArrayList类的字节码,等等。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class类型

我的理解:其实Class类就是用来操作.class文件的,将.class文件封装成对象,使用Class类中的方法进行操作


获取字节码对应的实例对象的三种方法:
1.类名.class,例如,System.class  通常用于获取已知类或者类型的.class文件
2.对象.getClass(),例如,new Date().getClass()    通常用于获取自定义对象的.class文件
|--java.lang.Object
|--class<?> getClass()  返回object的运行时类
3.Class.forName("类名"),例如,Class.forName("java.util.Date")  通常用于获取API文档中的.class文件
|--java.lang.Class
|--static Class<?> forName(String className)  返回与带有给定字符串名的类或接口相关联的 Class 对象。


九个预定义Class实例对象:
分别是:
8个基本数据 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。 
他们的内部都封装了一个TYPE字段,这九个类的包装类对象调用TYPE字段后都会返回一个Class类
static Class<Byte> TYPE 
          表示基本类型 byte 的 Class 实例。
 

代码示例:
package com.learn;

public class ReflectionDemo
{

	/**
	 * @param args
	 * @throws ClassNotFoundException 
	 */
	public static void main(String[] args) throws ClassNotFoundException
	{
		// TODO Auto-generated method stub
		//获取字节码文件的第一种方法
		Class cls1 = Thread.class;
		//获取字节码文件的第二种方法
		Class cls2 = new Thread().getClass();
		//获取字节码文件的第三种方法
		Class cls3 = Class.forName("java.lang.Thread");
		//判断他们是否是同一个字节码文件
		System.out.println(cls1 == cls2);
		System.out.println(cls2 ==cls1);
		//判断该字节码文件是否是基本数据类型的字节码文件
		System.out.println(long.class.isPrimitive());
		//判断是否是同一个字节码文件
		System.out.println(int.class == Integer.TYPE);
		//返回值为false
		System.out.println(int.class == Integer.class);
		//void是9个预定义实例对象中的其中一个
		System.out.println(void.class.isPrimitive());
		//判断字节码文件是否为数组类型
		System.out.println(int[].class.isArray());
	}

}

总结:
只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…

反射

概念:
反射就是把Java类中的各种成分映射成相应的java类。


概述:

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

继承体系
java.lang.reflect
|--java.lang.reflect.AccessibleObject
|--java.lang.reflect.Field
|--java.lang.reflect.Method
|--java.lang.reflect.Constructor


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

java.lang.reflect.Contructor 用于操作字节码文件中构造函数的类

得到某个类所有的构造方法:
例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
//获得方法时要用到类型
创建实例对象:
1.java.lang.reflect.Constructor中的 newInstance()方法
通常方式:String str = new String(new StringBuffer("abc"));
反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));
//调用获得的方法时要用到上面相同类型的实例对象

2.java.lang.Class中的newInstance()方法
Class.newInstance()方法:
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。

代码示例:
package com.learn;

import java.lang.reflect.Constructor;

public class ReflectDemo2
{

	/**
	 * @param args
	 * @throws Exception 
	 * @throws SecurityException 
	 */
	public static void main(String[] args) throws SecurityException, Exception
	{
		// TODO Auto-generated method stub
		//遍历Thread类中的所有构造函数
		//获取类中的所有构造方法,存入集合中
		Constructor[] constructors = Class.forName("java.lang.Thread").getConstructors();
		for(Constructor constructor : constructors)
		{
			System.out.println(constructor);
		}
		//获取一个指定参数类型的构造函数
		//方法一:costructor.newInstance(Type ... args);
		Constructor constructor1 = Class.forName("java.lang.String").getConstructor();
		String str = (String)constructor1.newInstance();
		System.out.println(str.length());
		//方法二:Class.newInstance();
		String str1 = (String)Class.forName("java.lang.String").newInstance();
		System.out.println(str.length());
	}

}

总结:
异常会发生在两个状态下
1.编译时,JVM编译源文件时,检查等号左边的变量定义
2.运行时,执行文件时,检查等号右边的部分

java.lang.reflect.Field 用于操作字节码文件中的定义的变量的类

Field类的特点:
当我们获取指定对象中的Field类时,如果想操作其中的一个Field或者一类Field,那么要指定这个Field所属的对象

代码示例:
package com.learn;

import java.lang.reflect.Field;

public class ReflectDemo3
{

	/**
	 * @param args
	 * @throws Exception 
	 * 
	 */
	public static void main(String[] args) throws Exception
	{
		Demo3 d = new Demo3(6,5);
		//获取私有的成员变量
		Field fieldX = Demo3.class.getDeclaredField("x");
		//暴力反射,强制获取私有的成员变量
		fieldX.setAccessible(true);
		System.out.println(fieldX.get(d));
		Field fieldY = Demo3.class.getField("y");
		System.out.println(fieldX.get(d));
	}

}

public class Demo3
{
	private int x = 9;
	public int y = 9;
	public Demo3(int x, int y)
	{
		super();
		this.x = x;
		this.y = y;
	}
	
}

总结:
getField(String str)方法只可以获取字节码中权限修饰符为public的field;

暴力反射:
程序中隐藏的field,通常是不想让别人使用的,才会如此定义。此时我们如果想获取隐藏的field,需使用getDeclaredField()方法获取。然后对此field进行暴力反射:setAccessible(true),即可使用get方法获取此field的值

练习:将字符串中的字符‘b’换成'a'
package com.learn;

import java.lang.reflect.Field;

public class FieldTest
{
	/**
	 * @param args
	 * @param Demo 
	 */
	public static void main(String[] args) throws Exception
	{

		// 创建Demo1类的对象
		Demo1 d = new Demo1("abc","aabb","cctv");
		//将对象传入替换字符的方法中
		replaceChar(d);
		System.out.println(d);
	}
	
	private static void replaceChar(Object obj) throws Exception
	{
		//获取对象中所有的Field 
		Field[] fields = obj.getClass().getFields();
		//遍历集合中的field
		for(Field field : fields)
		{
			//判断该field是否是String类型
			if(field.getType() == String.class)
			{
				//获取field在指定对象中所对应的字段
				String oldstr = (String) field.get(obj);
				//替换字段中的字符
				String newstr = oldstr.replace('b','a');
				//将新的字段添加进对象对应的字段中
				field.set(obj, newstr);
			}
		}
		
	}

}

public class Demo1
{
		public String str1 ;
		public String str2 ;
		public String str3 ;
		public Demo1(String str1, String str2, String str3)
		{
			super();
			this.str1 = str1;
			this.str2 = str2;
			this.str3 = str3;
		}
		public String toString()
		{
			return str1+"::"+str2+"::"+str3;
		}
}

程序中用到的方法:
java.lang.Class
|--Field getField(String name):获取字节码文件中包含指定未私有变量的Field对象(只能获取未被隐藏的field)
|--Field[] getFields():获取字节码文件中所有的Field对象
|--Field getDeclaredField:获取字节码文件中变量的Field对象(无论是否隐藏都可以获取,当获取的field是隐藏成员变量时,需进行暴力反射)
|--Field[] getDeclaredFields:获取字节码文件中的所有变量
java.lang.reflect.Field
|--Object get(Object obj):返回指定对象上字段的值
|--Class getType():返回Field对象中field的数据类型
|--void set(Object obj, Object value) :将指定对象变量上此 Field 对象表示的字段设置为指定的新值。           
java.lang.reflect
|--void AccessibleObject(boolean flag):对隐藏的field进行暴力反射

java.lang.reflect.Method 用于操作字节码文件中的定义的方法的类

获取类中的方法:
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1)); 
注意:
如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法!

代码示例:
package com.learn;

import java.lang.reflect.Method;

public class ReflctMethod
{
	public static void main(String[] args) throws Exception
	{
		Demo1 d = new Demo1("qwer","tyui","asdf");
		Method toString = Demo1.class.getMethod("toString");//空参数的方法,只要传入方法名,没参数就不用传入参数类型
		System.out.println(toString.invoke(d));//第一个传入的Object是调用方法的对象,第二个传入的是具体的参数,没有就不传
	}
}

public class Demo1
{
		public String str1 ;
		public String str2 ;
		public String str3 ;
		public Demo1(String str1, String str2, String str3)
		{
			super();
			this.str1 = str1;
			this.str2 = str2;
			this.str3 = str3;
		}
		public String toString()
		{
			return str1+"::"+str2+"::"+str3;
		}
}

总结:
当获取的方法是一个空参数的方法时,getMethod(String name,Class DataType)方法的第二个参数不用传入,在调用获取的方法时,
invoke(Object obj,Object... args)第二个参数不用传值,也不能写为null。

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})形式。

jdk 1.4版

package com.learn;

import java.lang.reflect.Method;

public class ReflctMethod
{
	public static void main(String[] args) throws Exception
	{
		Demo1 d = new Demo1("qwer","tyui","asdf");
		//如果有参数,那么此时的参数类型的字节码文件是Object[].class
		Method toString = Demo1.class.getMethod("toString");
		//空参数的方法,所以数组中的参数列表中没有元素
		System.out.println(toString.invoke(d,new Object[]{}));
	}
}

public class Demo1
{
		public String str1 ;
		public String str2 ;
		public String str3 ;
		public Demo1(String str1, String str2, String str3)
		{
			super();
			this.str1 = str1;
			this.str2 = str2;
			this.str3 = str3;
		}
		public String toString()
		{
			return str1+"::"+str2+"::"+str3;
		}
}

用反射方式执行某个类中的main方法
程序目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
回顾主函数:
main方法的格式是固定的,public static void main(String[] args)  注意:JDK1.5后出现的可变参数:String[]... args
从主函数格式中我们知道
函数名:main
参数类型:String[]
参数个数:0
长度:0
package com.learn;

import java.lang.reflect.Method;

public class ReflectMain
{

	public static void main(String[] args) throws Exception
	{

		String str = args[0];
		Method mainMethod = Class.forName(str).getMethod("main", String[].class);
		mainMethod.invoke(null, (Object)new String[]{});
	}
}
运行以上程序发现,将会报角标越界异常
这是因为我们没有向数组中传入参数

解决方法:
Run As--> 点击Run Configurations -->点击Arguments选项卡-->输入需要执行的main方法所在类全名即可
此时String[]数组的长度变为1,args[0] = 传入的类全名
以上程序其实就是:Method mainMethod = Class.forName(类全名).getMethod("main",String[].class);

传入参数后,还会报非法参数异常(IllegalArgumentsException)
原因:
启动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实例对象的特点:
1.具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
2.代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

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

Arrays.asList()方法处理int[]和String[]时的差异。
示例:
int[] arr = new int[]{1,2,3};
String[] arr1 = new String[]{"a","b","c"};
Object obj = Arrays.asList(arr);
Object obj1 = Arrays.asList(arr1);
System.out.println(arr);
System.out.println(arr1);

输出结果:
[[I@b6e39f]   因为基本类型的一维数组是Object类型,所以asList()方法将arr数组当做一个对象处理
[a, b, c]          而String类型的数组即可作为Object类型也可当做Object[]类型使用,String内部的元素都是对象,输出的是对象的集合

Array工具类用于完成对数组的反射操作。
package com.learn;

import java.lang.reflect.Array;

public class ReflectArray
{

	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		// TODO Auto-generated method stub
		String[] str = new String[]{"a","b","c"}; 
		PrintObject(str);
	}

	private static void PrintObject(Object obj)
	{
		// 有两种情况,一种是obj是数组类型,一种就是普通对象
		Class cls = obj.getClass();
		if(cls.isArray())
		{
			for(int x = 0;x<Array.getLength(obj);x++)
			{
				System.out.println(Array.get(obj, x));
			}
		}else
		{
			System.out.println(obj);
		}
		
	}

}

用到的的方法
java.lang.Class
|--isArray():判断Class对象是否是数组类型
java.lang.reflect.Array
|--static int getLength(Object Array):返回指定数组的长度
|--static object get(Object array,int index):返回指定数组角标为的值

ArrayList和HashCode的比较及HashCode分析

实例分析:
ReflectPoint类
package com.learn;

public class ReflectPoint
{
	private int x;
	private 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;
	}
	
}
测试代码:
package com.learn;

import java.util.ArrayList;
import java.util.Collection;

public class ReflectTest
{

	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		// TODO Auto-generated method stub
		Collection<ReflectPoint> list = new ArrayList<ReflectPoint>();
		ReflectPoint rp = new ReflectPoint(1,2);
		ReflectPoint rp1 = new ReflectPoint(2,3);
		ReflectPoint rp2 = new ReflectPoint(3,4);
		ReflectPoint rp3 = new ReflectPoint(3,4);
		list.add(rp);
		list.add(rp1);
		list.add(rp2);
		list.add(rp3);
		System.out.println(list.size());
	}

}

测试代码中创建的是ArrayList集合,该集合是有序可重复的,所以该集合中无论存储多少元素,无论是否相同都可以存入,此时的size()为4;
如果将ArrayList集合改为HashSet集合,因为HashSet集合存取时无序且不可重复,他们通过hashCode()和equals()方法来保证元素的唯一性,先比较对象的hash值,然后用equals()方法判断对象中的内容。最后的结果为3

注意:
当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值也会改变,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。ArrayList集合没有这方面的问题

框架的概念及用反射技术开发框架的原理

框架与框架要解决的核心问题

我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。


框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以 后写的类(门窗)呢? 因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接 new 某个类的实 例对象了,而要用反射方式来做。

模拟框架示例:
配置文件config.properties中的内容:
classname = java.util.ArrayList

示例代码:
package com.learn;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;

public class ReflectTest2
{

	/**
	 * @param args
	 * 创建一个加载任意集合的框架
	 */
	public static void main(String[] args) throws Exception
	{
		//获取文件
		InputStream is = new FileInputStream("config.properties");
		//创建空列表对象
		Properties p = new Properties();
		//加载配置文件
		p.load(is);
		//关闭流资源
		is.close();
		//获取键对应的值
		String classname = p.getProperty("classname");
		//使用反射创建配置文件中要建立的对象
		Collection collection = (Collection)Class.forName(classname).newInstance();
		collection.add(new ReflectPoint(1,2));
		collection.add(new ReflectPoint(1,2));
		collection.add(new ReflectPoint(1,2));
		collection.add(new ReflectPoint(1,2));
		System.out.println(collection.size());
	}

}

示例代码就是一个小型的框架,我们只需要改变config.properties文件中classname的值,即可使用这个框架来为我们工作,非常的方便。上面的程序我们可以将任意集合类作为classname的值

加载配置文件的三种方式:
1,采用类加载器进行加载,使用相对路径的方式 
InputStream is=ReflectTest.class.getClassLoader(). getResourceAsStream("com/itheima/day01/config.properties");

2,利用Class方式进行加载,使用相对路径的方式 
InputStream is = ReflectTest.class.getResourceAsStream("config.properties"); 

3,利用Class方式进行加载,使用绝对路径的方式
InputStream is = ReflectTest.class.getResourceAsStream("com/itheima/day01/config.properties");






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值