Java 反射
1. 反射的基石→Class类【C大写,是一个叫Class的类】
java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性值是什么,则是有这个类的实力对象来确定的,不同的实例对象有不同的属性值。
Person p1=new Person();
Date 、Math等类。
字节码:一个类建立对象的一堆二进制代码(放进内存前)
方式①: p1.getClass();
方式②: Class.forName(“java.lang.String”);
方式③: Class cls1=Date.class; //字节码1
Class cls2=Person.class; //字节码2
三个得到方法,①:对已建立、放到内存中,只需得到就可以。即:对象.getClass();
②:先用类去加载,加载进来后,再返回字节码。即:Class.forName(“类名”);
③:类名.class
注:反射多用方法②
九个预定义的Class实例对象:8个基本类型+void(参看Class.isPrimitive方法帮助)
Int.class==Integer.TYPE【TYPE包装基本类型字节码】;
Int.class!=Integer.class
同理:还有Byte.TYPE;Boolean.TYPE;Void.TYPE.
注:对字节码比较用”==”;
eg:
String str1=”abc”;
Class cls1=str1.getClass();
Class cls2=String.class;
Class cls3=Class.forName(“java.lang.String”);
//cls1\cls2\cls3三者相同
System.out.println(cls1==cls2); //true
System.out.println(cls1==cls3); //true
System.out.println(int.class.isPrimitive()); //true
数组类型的class实例对象
Class.isArray();
总之,只要在程序中出现的类型,都有各自的Class实例对象,例如:int[],void
2. 反射的概念
一句话总结:反射就是把Java类中的各种成分映射成相应的java类。
例如:一个java类用一个Class类的对象来表示,一个类中的组成部分:成员变量、方法、构造方法、包等等信息也用一个个的java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量、方法、构造函数、修饰类、包等信息,这些信息就使用相应类的实例对象来表示,它们是Field、Method、Constructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。
即强调:参数类型用什么来表示——用Class对象!
3. 构造方法的反射应用
Constructor类(代表某个类中的一个构造方法)
得到某个类所有的构造方法:(JDK1.5后因为可变参数,可以获得好几个构造方法,只要在()里放进去想获得参数(构造方法)就可以。
eg:Constructor[] constructors=Class.forName(“java.lang.String”).getConstructors();
得到某一个构造方法:
eg:比如选择StringBuffer这个构造函数
① :Constructor constructor=
Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
创建实例对象:
通常方式:String str=new String(new StringBuffer(“abc”));
反射方式:②:String str=(String)constructor.newInstance(new StringBuffer(“abc”));
因为newInstance返回的是Object类型,所以要转型。
注:①和②的参数要匹配才能行,即①获得方法是要用到类型;②调用获得的方法时要用到上面相同类型的实例对象。
Class.newInstance()方法:
eg:String obj=(String)Class.forName(“java.lang.String”).newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
注:反射会导致程序性能下降。
4. Field类(成员变量的反射)
Field类代表某个类中的一个成员变量
filedX代表的是X定义,而不是具体的X变量。
注:“Alt+Shift+s为eclipse的快捷键”Generate Constructor using Fields,可生成构造方法。
eg:
ReflectPoint pt1=new ReflectPoint(3,5);
Field fieldY=pt1.getClass().getField(“y”);
//fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值。
System.out.println(fieldY。get(pt1));
Field fieldX=pt1.getClass().getDeclaredField(“x”); //因为x为私有,强取。
fieldX.setAccessible(true); //设置可访问
System.out.println(fieldX.get(pt1));
public c lass ReflectPoint{
private int x;
public int y;
public ReflectPoint(int x,int y){
super();
this.x=x;
this.y=y;
}
}
练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的“b”改成“a”。
public c lass ReflectPoint{
public String str1=”ball”;
public String str2=”basketball”;
public String str3=”itcast”;
}
private static void changeStringValue(Object obj)throw Exception{
Field[] fields=obj.getClass().getFields();
for(Field field:fields){
//if(field.getType().equals(String.class)){ //不推荐这么写
if(field.getType()==String.class){ //用“==”号,因为是同一个字节码(都是String)
String oldValue=(String)field.get(obj);
String newValue=oldValue.replace(‘b’,’a’);
field.set(obj,newValue); //对象身上的还没改,所以set一下。
}
}
changeStringValue(pt1);
System.out.println(pt1);
5. Method类(成员方法的反射)
Method类代表某个类中的一个成员方法。
得到类中的某一个方法:
eg:
Method charAt=
Class.forName(“java.lang.String”).getMethod(“charAt”,int.class); //()里写方法名
和参数类型,因为有的方法有重载。
调用方法:
通常方法:System.out.println(str.charAt(1));
反射方法:System.out.println(charAr.invoke(str,1));
如果传递给Method对象的invoke()方法的的第一个参数为null,该Method对象对应的是一个静态方法。
jdk1.4和1.5的invoke方法的区别:
jdk1.5:public Object invoke(Object obj,Object…args)
jdk1.4:public Object invoke(Object obj,Object[] args),既按1.4的语法,需要一个数组作为参数传递给invoke时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用jdk1.4改写为charAt.invoke(“str”,new Object[]{1})形式。
对接收数组参数的成员方法进行反射
用反射方法执行某个类中的main方法
目标:
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的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”}); 编译器会做特殊处理,编译时不把参数当作数组看待,也就不会将数组打散成若干个参数了。
eg:
//TestArguments.main(new String[]{“111”,”222”,”333”});//普通方法
//反射方法如下:
String StartingClassName=args[0];
Method mainMethod=Class.forName(StartingClassName).getmethod(“main”,String[].class);
mainMethod.invoke(null,(Object)new String[]{“111”,”222”,”333”});
class TestArguments{
public static void main(String[] args){
for(String arg:args){
System.out.println(arg);
}
}
}
运行时,为了让“args[0]”编译通过,要把调用哪个类的完整名字进行如下操作:
在eclipse下,右键本代码→run as→open run dialog→Arguments→复制类名到Program arguments下→apply【等效于在命令行:java TestLei类名(此刻传的参数)】
在eclipse下,箭头指到类名,按下F2键,可现实完成的类名(包名前缀)
6. 数组与Object的关系及其反射类型
数组的反射
具有相同维数和元素类型的数组属于同一个类型。即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当做Object[]类型使用(因基本类型不是Object);非基本类型的一维数组,即可以当做Object类型使用,又可以当做Object[]类型用。
Arrays.asList()方法处理int[]和String[]时的差异
Array工具类用于完成读一数组的反射操作。
eg:
int[] a1=new int[3];
int[] a2=new int[4];
int[] a3=new int[2][3];
String[] a4=new String[3];
System.out.println(a1.getClass()==a2.getClass()); //true
System.out.println(a1.getClass()==a4.getClass()); //false
System.out.println(a1.getClass()==a3.getClass()); //false
System.out.println(a1.getClass().getName()); //[I
System.out.println(a1.getClass().getSuperclass().getName()); //java.lang.Object
System.out.println(a4.getClass().getSuperClass().getName()); //java.lang.Object
Object aObj1=a1;
Object aObj2=a4;
//Object[] aObj3=a1; //【×】
Object[] aObj4=a3; //a3唯一个一维数组的数组。
Object[] aObj5=a4; //a4这个数组装的是String,String是Object。
/*
如果把上述a1和a4修改一下。进行如下操作
*/
int[] a1=new int[]{1,2,3};
String[] a4=new String[]{“a”,”b”,”c”};
System.out.println(a1); //[I@1cfb549
System.out.println(a4); //[Ljava.lang.String;@186d4c1
System.out.println(Arrays.asList(a1)); //[[I@1cfb549]
System.out.println(Arrays.asList(a4)); //[a,b,c]
注:因为jdk1.4中,asList方法参数(Object[] a)
jdk1.5中 asList方法参数(T…..a)(可变参数)
1.5兼容1.4所以当Arrays.asList(a1)过来时,不能解释为Object[],按1.5方式处理为1个参数;而String[]符合了Object[]
数组的反射应用
(接上)
printObject(a4);
printObject(“xyz”);
/*
结果为:
a
b
c
xyz
*/
private static void printObject(Object obj){
Class clazz=obj.getClass();
if(clazz.isArray()){
int len=Array.getLength(obj);
for(int i=0;i<len;i++){
System.out.println(Array.get(obj,i));
}
}else{
System.out.println(obj);
}
}
思考:如何得到数组中的元素类型?——————无法得到;
只能得到每一个元素类型,不可能得到数组类型。
eg:Object[] a=new Object[]{“a”,1};
a[0].getClass().getName();
但 int[] a=new int[3]; a无法判断前面为int。
ArrayList_HashSet的比较及HashCode分析
反射的作用→实现框架功能
框架与框架要解决的核心问题
房子卖给用户住,由用户自己安装门窗和空调,作的房子就是框架,用户要使用我的框架,把门窗插入进我提供的框架中。【框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。】
框架要解决的核心问题
我在写框架时,用户可能还在上小学,还不会写程序。写的框架程序怎样能调用到你以后写的类呢?因为在写程序时无法知道要被调用的类名。所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。
综合案例
先直接用new语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成ReflectPoint类的equals和hashCode方法,比较两个集合的运行结果差异。
然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观摩运行结果差异。
eg:
public class ReflectTest{
public static void main(String[] args){
Collection collections=new HashSet();
ReflectPiont pt1=new ReflectPoint(3,3);
ReflectPoint pt2=new ReflectPoint(5,5);
ReflectPoint pt3=new ReflectPoint(3,3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
//pt1.y=7;
//collections.remove(pt1); //输出结果还是2,原因见下。
System.out.printyln(collections.size());
}
//重写之前Reflection类中的hashCode()与equals()方法【在eclipse可自动//生成。方法:右键→Source→Generate hashCode() and equals(),勾选x,y→ok】
@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;
final ReflecPoint other=(ReflectPoint)obj;
if(x!=other.x)
return false;
if(y!=other.y)
return false;
return true;
}
}
注:当一个对象被放进HashSet集合中后。就不能修改对象参数与计算哈希值的字段了,否则导致与最初存进去的对象的哈希值不同。造成:内存泄露。
如果不复写hashCode与equals()上面结果是3;如果只写equals()结果可能是3也可能是2.
如果不重写hashCode,可能就不能单靠equals()来确定相等,因为,在按内存算时,同样的参数可能被放到不同的hashCode算法的字段区域,所以结果不确定。【如果不存到hashSet等hash算法中,不用覆写hashCode,只覆写equals()】
框架的概念及用反射技术开发框架的原理
利用反射完成上例中HashSet与ArrayList
方法:在本包中,建立一个配置文件(到时候直接配置文件就好)
在本包名上:右键→New→File→Filename:config.properties→确定
点击这个文件→下面Source→(写内容)className=java.util.ArrayList
然后在原程序上修改【尽量面向接口和父类编程】所以用InputStream
InputStream ips=new FileInputStream(“Config.properties);
Properties props=new Properties();
props.load(ips);
ips.close();
String className=props.getProperty(“className”);
Collection collections=(Collection)Class.forName(className).newInstance();
ReflectionPoint pt1=…….;
ReflectionPoint pt2=…….;
……(后面参考上面程序)
注:Properties对象等HashMap,装的是key、value。但此HashMap高级的是,可以把自己内存的键值对存到硬盘中,可以在初始化时,把键值对加载进来。
同时养成好习惯关资源。此时为关闭调用的系统资源,并非ips,防止内存泄露。