您的位置:首页 > 职场人生

黑马程序员—JAVA高新技术之反射

2014-06-14 19:37 363 查看
反射
一、反射的基石

  反射的基石是Class类。Java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class.

1.1 Class类的分析

  在程序运行时调用类的时候,首先将这个类在硬盘上的二进制代码加载到内存中,才可以用这个类创建对象,也就是说先将类的字节码加载到内存,再用这个字节码创建对象。每个类的字节码就是一个Class类的实例对象。

1.2得到字节码对应的实例对象(Class类型)有三种方式:

  1. 类名.class 例:PersonDemo.class

  2.  对象.getClass() 例:new Person().getClass();

  3.  Class.forName(“完整类名”),

例:Class.forName(“java.util.Data”);

  Class.forName方法的作用是返回字节码,返回方式有两种:

  第一种,如果字节码已经被加载进内存,虚拟机缓存了,那么就可以直接用全类名的方式得到字节码Class.forName(String className)。

  第二种,如果class文件没有被载加过,也就是说虚拟机里还没有该字节码,就要指定文件名、加载器,来得到字节码(用加载器加载,把那份字节码缓存起来,通过该方法返回字节码)Class.forName(String name, boolean initialize, ClassLoader loader)

1.3九个预定义的Class实例对象

   任何类型都是Class的实例对象。

  八个基本数据类型都是Class类的实例对象,void类也是Class类的实例对象,Class cls = void.class;  

  方法isPrimitive()用于判断指定的Class对象是否表示一个基本类型(原始类型),有八个基本类型和关键字void。基本类型和void的Class实例对象都可用对应的包装类型的常量值TYPE来获取。

   例:Integer.TYPE表示所包装的基本类型int的Class实例对象。

    Class类中有个方法isArray()用于判断Class实例是不是数组类型。

  总之,只要是在源程序中出现的类型,都有各自的Class实例对象。

 

二、反射的概念

  反射就是把java类中的各种成分映射成相应的java类,例如:一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。

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

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

三、 Constructor类

Constructor类代表某个类中的一个构造方法。

import java.lang.reflect.*;

public class ConstructorTest {

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

// 返回类里面的所有构造方法

Constructor[] constructors = String.class.getConstructors();

//用参数类型来指定某一个构造方法,从而返回该构造方法对象

Constructor con = String.class.getConstructor(StringBuilder.class);

//用得到的有参构造方法进行new对象,用的什么参数类型获取的构造方法,

//在new对象的时候,就一定要用相应的实际参数类型传值。

String str = (String)con.newInstance(new StringBuilder("abc"));//该方法返回的是Object所以一定要强转

System.out.println(str.charAt(2));

//这个方法也是用于建对象,只是通过无参构造方法建的。

String.class.newInstance();

}

}

Class类里面的newInstance()方法就是在用无参的构造方法创建一个对象。

四、Field类

  Field类代表某个类中的一个成员变量,也就是字节码里的一个变量,不代表一个对象上的变量。所以用get()方法获取变量值时要指定是哪个对象。

4.1成员变量的反射

  当成员变量私有化时,通过Class对象调用getDeclaredField方法传入指定的成员变量字符串,返回Field对象,并通过Field对象调用setAccessible(true)方法设为可访问性,就可用get方法获取到私有的成员变量,也称暴力反射。

public class ReflectPoint {

public int x;

private int y;

public ReflectPoint(int x, int y) {

super();

this.x = x;

this.y = y;

}

}

import java.lang.reflect.*;

public class ReflectDemo

{

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

ReflectPoint rp = new ReflectPoint(5,9);

Field fieldX = rp.getClass().getField("x");

System.out.println(fieldX.get(rp));

Field fieldY = rp.getClass().getDeclaredField("y");

fieldY.setAccessible(true);

System.out.println(fieldY.get(rp));

}

}

4.2获取一个类中所有String类型的成员变量,并替换指定字符

import java.lang.reflect.*;

public class ReflexDemo {

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

Student s = new Student("zhangsan","beijing",23);
refField(s);
System.out.println(s.name+":"+s.add+":"+s.age);

}

static void refField(Object o) throws Exception{
Field[] field = o.getClass().get
cc19
Fields();
for(Field f : field){
if(f.getType() == String.class){
String oldValue = (String)f.get(o);
String newValue = oldValue.replaceAll("zhangsan", "lisi");
f.set(o, newValue);

}
}
}

}

class Student{
public String name,add;
public int age;

public Student(String name,String add,int age){
this.name = name;
this.add = add;
this.age = age;
}

}

五、Method类

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

5.1成员方法的反射

  1、得到成员方法对象:Class对象.getMethod(“方法名”,参数类型.class);

  方法有几个参数类型就要写几个对应的.class。

  2、用反射方式调用方法:Method对象.invoke(对象,实际参数);

  如果传递给Method对象的invoke()方法的第一个参数为null,该Method对象对应的是一个静态方法,因为静态方法不需要用对象调用,是跟着类走的。

  3、JDK1.4和JDK1.5的invoke方法的区别:

  JDK1.5:public Object invoke(Object obj,Object。。。args)支持可变参数

  JDK1.4:public Object invoke(Object obj,Object[] args)需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,用JDK1.4写为charAt.invoke(“str”,new Object[]{1})

例:

import java.lang.reflect.*;

public class ReflectDemo{

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

String str = new String("abcde");

Method method = String.class.getMethod("charAt", int.class);//得到method对象

System.out.println(method.invoke(str, 2));

}

}

5.2用反射方式执行某个类中的main方法

  需求:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。

  分析:启动java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,怎样给invoke方法传递参数呢?按JDK1.5的语法,整个数组是一个参数,而按1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会按1.4的语法处理(因为新版本要兼容老版本),即把数组打散成为若干个单独的参数,所以给main方法传递参数时,不能写mainMethod.invoke(null,new String[]{“abc”,”def”});

解决办法有两种:

  1、mainMethod.invoke(null,new Object[]{new String[]{“xxx”}});这种方式相当于Object数组里存了一个String数组类型的元素,这样编译器就会认为是一个参数。

  2、mainMethod.invoke(null.(Object)new String[]{“xxx”});这种方式编译时,相当于是一个Object对象,编译器就不会把参数当作数组看待,也就不会把数组里的元素打散成若干个参数。

具体代码如下:

import java.lang.reflect.*;

public class ReflectDemo{

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

String startClassName = args[0];

Method mainMethod = Class.forName(startClassName).getMethod("main", String[].class);

mainMethod.invoke(null, (Object)new String[]{"123"});

}

}

class TestArguments{

public static void main(String[] args){

for(String str:args){

System.out.println(str);

}  

}

}

  注:要在运行ReflectTest的时候给主函数传值,打开Run Configurations窗口,并在Arguments选项卡的Program arguments栏里输入cn.itcast.day1.TestArguments 表示TestArguments字节码传给ReflectTestr 的main函数,那么在运行ReflectTest的时候就会同时加载得到TestArguments字节码文件,就相当于。ReflectTest主函数的args[0],那么就可通过Class.forName方法得到传递进来的字节码文件对象。

六、数组的反射

6.1数组与Object的关系及其反射类型

  1、只要数组类型相同,维度相同,就是同一份字节码对象。

  2、只要是引用数据类型(比如数组类型)的超类都是Object,所以可以通过getSuperclass方法获取到超类的Class对象。

  3、如果数组是一维的,数组里的元素类型又是基本数据类型,那么就不能写成Object[] obj = a1;只能写成Object obj = a1;

例:

int[] a1 = new int[3];

int[] a2 = new int[5];

int[] [] a3 = new int[3];

Stirng[] a4 = new String[3];

System.out.println(a1.getClass()==a2.getClass());//结果为true

System.out.println(a1.getClass().getSuperclass().getName());//结果为Object

Object a1Obj = a1;//正确

Object[] a1Obj = a1;//错误

Object[] a3Obj = a3;//正确,一维数组是数组类型,属于Object子类,Object[]相当于有一个Object数组,里面存的是Object

Objcet[] a4Obj = a4;//正确,因为String是类类型,属于Object子类

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

  从反射的角度理解:虚拟机运行时会先根据JDK1.4的方法处理Arrays.asList(Object[] a),接收的是Object类型的数组,当传入的是String[],就代表String数组里的每个元素都是一个参数并存到集合里,所以可以全部打印出元素。这时int[]传进来它代表的一个Object而不是Object[],不符合1.4的参数类型就处理不了,就会按1.5的方法处理,Arrays.asList(T…a)是接收的Object对象,就把int[]当作一个整体处理了,就相当于是一个参数,所以打印的是地址值。

  个人理解:Arrays.asList()返回的是一个list集合,而集合里只能存对象的引用,不能存基本数据类型int,所以打印list就是一个地址值,而String[]里存的是String类型的元素,每个元素都所属Object子类,打印list就可看到每个元素的值。

 

6.2、数组的反射应用

Array类是用于对数组反射的类。

例:用反射方式做:只要传进来的是数组就打印里面的元素,不是数组就直接打印对象。

public class ReflectArray{

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

String[] str = new String[]{"ab","cde","w"};

Object obj = null;

printObject(str);

printObject("sss");

}

public static void printObject(Object obj){

if(obj.getClass().isArray()){

int len = Array.getLength(obj);

for(int x=0;x<len;x++){

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

}

}

else{

System.out.println(obj);

}

}  

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: