日后的框架开发工作中,反射的用处是非常大的,如何通过Class对象获取类的字节码文件,以及用配置文件的形式将数据运用框架得以发挥,以便高效的开发和熟练的掌握?今天,我们同时来掌握java的重点:反射技术。
*Class
Class的定义:
java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值,java程序中的各个java类,它们是否属于同一个事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与小写的class关键字区别,Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属的包名,字段名称的列表,方法名称的列表,等等。学习反射,首先要明白Class这个类
java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class
得到类的字节码的三种方法:
Date 第一种:Class cls1 = Date.class 类名.class
Person p1 = new Person ( ) ; 第二种: p1.getClass ( ) ;对象.getClass( );
java.lang.String 第三种:Class.forName ( " java.lang.String " ) ;Class.forName( “ 类名 ” );
九个预定义的Class示例对象:
int.class == Integer.TYPE
void.class
数组类型的Class实例对象 :
Class.isArray ( )
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如 int [ ] , void ...
Class理解代码如下:
package cn.itheima.day1;
public class ReflectTest {
public static void main(String[] args) throws Exception {
String str1 = "abc";
//获取字节码文件的三种方式
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1 == cls2); //打印结果为true,证明三个字节码文件为同一个对象
System.out.println(cls2 == cls3);
System.out.println(cls1.isPrimitive()); //是否为基本类型,结果为false
System.out.println(int.class.isPrimitive()); //结果为true,是基本类型
System.out.println(int.class == Integer.class); //结果为false,Integer类并非基本类型
System.out.println(int.class == Integer.TYPE); //Integer.TYPE 代表所包装的基本类型的字节码 结果为true
System.out.println(int[].class.isPrimitive()); //结果为false,数组也是一种类型,非基本类型
System.out.println(int[].class.isArray()); //结果为true,是数组类型
}
}
*反射
反射的概念:
反射就是把java类中各种成分映射成相应的java类。
反射就是把java类中的各种成分映射成相应的java类,例如,一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个小小的java类来表示。表示java类的Class类显然要提供一系列的方法,来获取其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的示例对象来表示,他们是Field、Method、Contructor、Package等等
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
-Constructor类
Constructor类代表某个类中的一个构造方法
得到某个类的所有构造方法:
例子:Constructor [ ] cons= Class.forName( " java.lang.String " ).getConstructors();
得到某个构造方法:
例子:Constructor cons = Class.forName( " StringBuffer " ).getConstructor();
创建实例对象:
通常方式:
Stringstr = new String( new StringBuffer( " abc " ) ) ;
反射方式:
Constructor con = String.class.getConstructor ( StringBuffer.class ) ;
String str = ( String ) Constructor.newInstance ( new StringBuffer( " abc " ) )
Class.newInstance( ) 方法:
例子:String obj = (String)Class.forName( " java.lang.String " ).new Instance( );
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码怎么写的呢?用到了缓存机制来保存默认方法的实例对象。
-Field类
Field类代表某个类中的一个成员变量
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?
类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?所以字段fieldX代表的是x的定义,而不是具体的x变量
Field类和Constructor类的理解代码如下:
package cn.itheima.day1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class ReflectTest {
public static void main(String[] args) throws Exception {
//new String(new StringBuffer("abc"))
//得到构造方法
Constructor con1 = String.class.getConstructor(StringBuffer.class);
//通过构造方法创建新实例
String str2 = (String)con1.newInstance(/*"abc"*/new StringBuffer("abc"));
System.out.println(str2.charAt(2));
ReflectPoint pt1 = new ReflectPoint(3,5); //创建对象
Field fieldY = pt1.getClass().getField("y"); //通过反射获取对象
// fieldY的值是多少? 是5?错!fieldY不是对象身上的变量,而是类上的,要用它去取某个对象对应的值
System.out.println(fieldY.get(pt1)); //将fieldY与pt1相关联,打印结果为5
Field fieldX = pt1.getClass().getDeclaredField("x"); //获取不可见(私有)的变量
fieldX.setAccessible(true); //设置可见,称为暴力反射
System.out.println(fieldX.get(pt1)); //打印结果为3
//练习:将ReflectPoint类中的字符串变量中含有"b"的字母改为"a"
changeStringValue(pt1);
System.out.println(pt1);
}
public static void changeStringValue(Object obj) throws IllegalArgumentException, IllegalAccessException {
Field[] fields = obj.getClass().getFields();
for(Field field:fields){
//field.getType().equals(String.class)
if(field.getType() == String.class){ //同一份字节码,可以用==表示
String oldValue = (String) field.get(obj);
String newValue = oldValue.replace("b", "a");
field.set(obj, newValue);//设置对象修改为新字符
}
}
}
}
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对象对应的是一个静态方法!静态方法不需要对象调用
目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的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 " } ) ;编译器会做特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干参数了。
Method理解代码以及问题答案如下:
package cn.itheima.day1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectTest {
public static void main(String[] args) throws Exception {
String str1 = "abc";
//str1.charAt(1);
Method methodCharAt = String.class.getMethod("charAt",int.class );
System.out.println(methodCharAt.invoke(str1, 1)); //调用此方法
System.out.println(methodCharAt.invoke(str1, new Object[](2))); //使用数组,表示Object数组中只有一个对象2,用于1.4版本
//普通方式调用已有类中的main方法
TestArguments.main(new String[]("111","222","333"));
//反射方法调用已有类中的main方法
String staringClassName = args[0];
Method mainMethod = Class.forName(staringClassName).getMethod("main", String[].class);
mainMethod.invoke(null, new Object[](new String[]("111","222","333"))); //调用main方法,数组的父类是Object
}
class TestArguments{
public static void main(String[] args) {
for(String arg:args){
System.out.println(arg);
}
}
}
}
具有相同维数和元素类型的数组属于同一个类型,即有相同Class实例对象
代表数组的Class实例对象的getSuperClass( )方法返回的父类为Object类对应的Class
基本类型的以为数组可以被当作Object类型使用,不能当作Object[ ] 类型使用,非基本类型的一位数组,既可以当做Object类型使用,又可以当作Object[ ] 类型使用。
Arrays.asList( ) 方法处理int [ ] 和String [ ] 时的差异
数组反射的理解代码如下:
package cn.itheima.day1;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
public class ReflectTest {
public static void main(String[] args) throws Exception {
int[] a1 = new int[]{1,2,3};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[]{"a","b","c"};
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 表示int类型数组
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,编译报错
Object[] aObj4 = a3;
Object[] aObj5 = a4;
System.out.println(a1); //打印结果为int数组类型哈希码
System.out.println(a4); //打印结果为String数组类型的哈希码
System.out.println(Arrays.asList(a1)); //打印结果为int数组类型哈希码,int为基本数据类型
System.out.println(Arrays.asList(a4)); //打印结果为[a,b,c]
PrintObject(a4);
PrintObject("xyz");
}
public 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);
}
}
}
先直接用new语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成ReflectPoint类的equals和hashcode方法,比较两个运行结果的差异。
理解代码如下:
package cn.itheima.day1;
import java.util.Collection;
import java.util.HashSet;
public class ReflectTest2 {
public static void main(String[] args) {
Collection coll = new HashSet();
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPoint pt3 = new ReflectPoint(3,3);
coll.add(pt1);
coll.add(pt2);
coll.add(pt3);//需要将HashSet的哈希值进行覆盖,pt3对象才无法存入
coll.add(pt1);
pt1.y = 7; //修改完pt1的值之后,哈希码就变化了
coll.remove(pt1); //所以此动作未完成,打印结果为2,专业术语称为内存泄漏
System.out.println(coll.size());
}
}
class ReflectPoint {
private int x;
public int y;
public String str1 = "ball";
public String str2 = "basketball";
public String str3 = "itheima";
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;
}
@Override
public String toString() {
return str1+":"+str2+":"+str3;
}
}
本示例中也讲解了内存泄漏的原因,因为pt1使用过后进行了修改,哈希码也同样变化,再进行移除操作的时候便没有完成此动作。
反射的作用->实现框架的功能
框架与框架要解决的核心问题:
我要做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中,框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题:
我在写框架(房子)时,用户可能还不会写程序,我写的框架程序怎么样调用到以后写出来的类(门窗)呢?
因为在写程序时无法知道要被调用的类名,所以在程序中无法new出来某个类的实例对象,而要用反射的方式来做。
利用反射方法制作小小的框架,理解代码如下:
package cn.itheima.day1;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
public class ReflectTest2 {
public static void main(String[] args) throws Exception{
//加载properties文件
InputStream ips = new FileInputStream("config.properties");
Properties props = new Properties();
//将Properties对象和文件相关联
props.load(ips);
//关闭流资源,防止系统内存泄漏
ips.close();
//获取配置文件的键所对应的类名
String className = props.getProperty("className");
//通过反射的方法创建实例对象
Collection coll = (Collection)Class.forName(className).newInstance();
//通过一般的方法创建对象
// Collection coll = new HashSet();
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPoint pt3 = new ReflectPoint(3,3);
coll.add(pt1);
coll.add(pt2);
coll.add(pt3);
coll.add(pt1);
System.out.println(coll.size());
}
}
class ReflectPoint {
private int x;
public int y;
public String str1 = "ball";
public String str2 = "basketball";
public String str3 = "itheima";
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;
}
@Override
public String toString() {
return str1+":"+str2+":"+str3;
}
}