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

黑马程序员—Java基础加强(反射)

2014-04-16 16:17 453 查看
------- android培训java培训、期待与您交流! ----------

 

 

反射:

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

比如说从多的人我们用Person类来表示,众多的动物我们用Animal类来表示,那么众多的java类也应该用一个类来表示,这个类就是Class。

Class的实例对应着各个java类在内存中的字节码,例如,Person类的字节码,Animal类的字节码。

字节码:一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的

记住:一个类在虚拟机中只有一份字节码。

那么如何获取Class对象呢?

有三种方式:

1、调用某个类的class属性获取Class对象,如Date.class会返回Date类对应的Class对象(其实就是得到一个类的一份字节码文件);Class
 clazz = Date.class;

2、使用Class类的forName(StringclassName)静态方法,className表示全限定名;如String的全限定名:java.lang.String;

Class clazz = Class.forName("java.lang.String");

3、调用某个对象的getClass()方法。该方法属于Object类;

Class<?> clazz = new Date().getClass();

 

九个预定义Class对象

 

基本的 Java 类型(boolean、byte、char、short、int、long、float
和 double)和关键字 void通过class属性也表示为 Class
对象;

Class类中boolean isPrimitive() :判定指定的 Class
对象是否表示一个基本类型。

包装类和Void类的静态TYPE字段;

Integer.TYPE == int.class ;        

Integer.class == int.class;        

 数组类型的Class实例对象:

Class<String[]> clz = String[].class;

数组的Class对象如何比较是否相等?
数组的维数和数组的类型;

Class类中boolean isArray() :判定此 Class 对象是否表示一个数组类型。

 

反射:



 

 

为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念:

静态编译:在编译时确定类型,绑定对象,即通过。

动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

反射机制的优点:就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发。大大的增强了程序的扩展性。

反射机制的缺点:是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

 

反射的基本步骤:

1、获得Class对象,就是获取到指定的名称的字节码文件对象。

2、实例化对象,获得类的属性、方法或构造函数。

3、访问属性、调用方法、调用构造函数创建对象。

 

反射的用法:

1)、需要获得java类的各个组成部分,首先需要获得类的Class对象。

 

2)、反射类的成员方法:

Method类用于描述类中的方法:

Method getMethod(String name, Class<?> ...parameterTypes)


返回该Class对象表示类和其父类的指定的public方法;

示例:Class clazz = Person.class;

     Methodmethod = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});

     method.invoke();

 

Method[] getMethods(): 


返回该Class对象表示类和其父类的所有public方法;

Method getDeclaredMethod(String name, Class<?>...parameterTypes)


返回该Class对象表示类的指定的方法。和访问权限无关,但不包括继承的方法;

Method[] getDeclaredMethods():
获得类所有的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法

    

 

 

3)、反射类的构造函数:

Constructor类用于描述类中的构造方法:

Constructor<T> getConstructor(Class<?>...parameterTypes)


返回该Class对象表示类的指定的public构造方法;

例如:Constructor con = clazz.getConstructor(new Class[]{paramClazz1,paramClazz2,...})

     con.newInstance(params...)

 

 

Constructor<?>[] getConstructors()

返回该Class对象表示类的所有public构造方法;

Constructor<T>getDeclaredConstructor(Class<?>... parameterTypes)

返回该Class对象表示类的指定的构造方法,和访问权限无关;

Constructor<?>[] getDeclaredConstructors()


返回该Class对象表示类的所有构造方法,和访问权限无关;

 

    

 

4)、反射类的属性:

     Fieldfield = clazz.getField(fieldName);

     field.setAccessible(true);

     field.setObject(value);

 

代码示例:

先创建一个需要被反射的类ReflectPoint:

//导入Date类
import java.util.Date;
//需要被反射的类ReflectPoint
public class ReflectPoint {

//私有的成员Dete类的对象birthday
private Date birthday = new Date();
//私有成员属性x
private int x;
//公有的成员属性y
public int y;
//一系列公有的成员属性
public String str1 = "bass";
public String str2 = "basketball";
public String str3 = "itheima";

//构造函数
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}

//getX方法,获取x属性的值
public int getX() {
return x;
}
//setX方法,设置x属性的值
public void setX(int x) {
this.x = x;
}
//getY方法,设置y属性的值
public int getY() {
return y;
}
//setY,设置y属性的值
public void setY(int y) {
this.y = y;
}
//getBirthday方法,获取birthday对象
public Date getBirthday() {
return birthday;
}
//setBirthday方法,设置birthday
public void setBirthday(Date birthday) {
this.birthday = birthday;
}

//重写toString方法
public String toString() {
return "ReflectPoint [x=" + x + ", y=" + y + ", str1=" + str1
+ ", str2=" + str2 + ", str3=" + str3 + "]";
}

//重写hashCode方法
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}

//重写equals方法
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;
}

}


 

然后对ReflectPoint类进行反射操作以及反射的一些常见的用法代码示例:

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;

public class ReflectTest
{
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException
{
//创建ReflectPoint类的一个对象,向构造函数中传入参数
ReflectPoint rep1 = new ReflectPoint(3, 5);
//获取ReflectPoint类字节码的 y 字段属性的对象
Field fieldY = rep1.getClass().getField("y");
//fieldY的值是多少呢?是5吗?错!他只是ReflectPoint类字节码的 y 这个属性的对象,而不是具体的某个ReflectPoint的对象的y属性
//fieldY不是对象身上的变量,而是类上的 y 字段属性。要用它来获取具体对象的 y 字段的值。
//获取rep1这个具体对象的 y字段的值
System.out.println(fieldY.get(rep1));

//获取ReflectPoint类字节码的 x 字段属性的对象,因为x是private修饰的变量,
//所以要用到getDeclaredField方法,该方法可以获取到类中定义的private修饰的成员。
Field fieldX = rep1.getClass().getDeclaredField("x");
//设置ReflectPoint类中的 x 属性是可以被访问的
fieldX.setAccessible(true);
//打印具体的ReflectPoint的对象rep1的具体 x 字段的值,
//fieldX只是ReflectPoint类字节码的 x 属性对象,通过get方法传入具体的ReflectPoint类的对象来获取具体对象的 x 字段的值
System.out.println(fieldX.get(rep1));

String str1 = "abc";

//获取String的字节码的三种方法
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");

System.out.println(cls1 == cls2);//true
System.out.println(cls1 == cls3);//true

System.out.println(cls1.isPrimitive());//查看String是否是基本类型 false
System.out.println(int.class.isPrimitive());//查看int是否是基本类型 true
System.out.println(int.class == Integer.class);//查看int和Integer字节码是否相同 false
System.out.println(int.class == Integer.TYPE);//true
System.out.println(int[].class.isPrimitive());//int[]数组为引用类型所以为false
System.out.println(int[].class.isArray());//判断int[]数组是否为Array数组

//获取String类的字节码对象,通过字节码对象调用getConstructor获取String带StringBuilder的构造函数对象
Constructor con1 = String.class.getConstructor(StringBuilder.class);

//new String(new StringBuilder("abc"))
//获取String类的字节码对象,通过字节码对象调用getConstructor获取String带StringBuilder的构造函数对象
Constructor con2 = String.class.getConstructor(StringBuilder.class);
//通过构造函数对象调用newInstance方法,传入StringBuilder对象。实例化出一个带StringBuilder对象参数的String对象。
String str2 = (String) con2.newInstance(new StringBuilder("abc"));
//打印输出str2对象中2角标的元素
System.out.println(str2.charAt(2));

//Class类也有newInstance()方法,该方法只能使用类的空参数构造函数获取一个类的对象。
String str3 = String.class.newInstance();

//调用替换字符串的反射功能
changeStringValue(rep1);
//打印替换后的结果
System.out.println(rep1);

//str1.charAt(1);
//用反射的方式得到String的字节码里面的方法
Method methodCharAt = String.class.getMethod("charAt", int.class);
//再用这个方法来作用于指定的对象,并传入指定的参数
char ch  = (char) methodCharAt.invoke(str1, 2);//如果是invoke(null, 2)那么调用的是静态方法,因为静态方法不需要具体的对象
//打印该方法作用于指定对象和参数的返回值
System.out.println(ch);

//JDK1.4语法,new Object[]{2}这是一个数组,数组里面装了一个int型的值,并且值为2,那么数组的length为1。可以理解为 new int[]{2}
//其实就是这个参数就是一个int型的 2
char ch2 = (char) methodCharAt.invoke(str1, new Object[]{2});
//打印该方法作用于指定对象和参数的返回值
System.out.println(ch2);

//mainTest.main(new String[]{"itcast", "itheima", "ZOL"});
//在执行本类的main方法时,运行 java ReflectTest 传入一个参数,该参数是要被执行的其他的类的字符串表现形式
String startingClassName = args[0];
//获取那个需要运行的其他类的字节码,并通过getMethod方法获取那个类的main方法
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
//执行需要运行那个类的main方法,mian方法是静态的,所以不需要对象,故对象参数的位置为null。后面传入一个字符串参数给main方法
//用new Object[]{} 数组包住String数组是因为main函数会对传入的String数组进行拆包,被它拆包之后就成为了3个参数而不是一个数组
//而用new Object[]{} 会给String数组再加一层包装,是main函数在拆包后发现,传入的的确是一个数组
//		mainMethod.invoke(null, new Object[]{new String[]{"itcast", "itheima", "ZOL"}});
//这是告诉main函数,就把传入的参数当成是Object,让它不要拆包
mainMethod.invoke(null, (Object)new String[]{"itcast", "itheima", "ZOL"});

int[] i1 = new int[]{3, 5, 6};//int型数组
int[] i2 = new int[5];//int型数组
int[][] i3 = new int[2][3];//int型二维数组
String[] s1 = new String[]{"itcast", "itheima", "ZOL"};//String类型数组
System.out.println(i1.getClass() == i2.getClass());//比较i1和i2字节码,true
//		System.out.println(i1.getClass() == i3.getClass());//比较i1和i3字节码,由于维数不同,所以不相同
//		System.out.println(i1.getClass() == s1.getClass());//比较i1和s1字节码,由于不是同一类型数组,所以不相同
System.out.println(i1.getClass().getName());//获取i1所在类的字节码,通过字节码获取类名称并打印
System.out.println(i1.getClass().getSuperclass().getName());//获取i1所在类的字节码,再通过该字节码获取其父类的字节码,再获取父类名称并打印
System.out.println(i3.getClass().getSuperclass().getName());//获取i3所在类的字节码,再通过该字节码获取其父类的字节码,再获取父类名称并打印
System.out.println(s1.getClass().getSuperclass().getName());//获取s1所在类的字节码,再通过该字节码获取其父类的字节码,再获取父类名称并打印

Object obji1 = i1;//i1是引用型,是Object类型,所以可以转换
Object obji3 = i3;//i3也是引用型,是Object类型,所以可以转换
Object objs1 = s1;//s1也是引用类型,是Object类型,所以可以转换
//		Object[] obj1 = i1;//i1是引用型,但里面是int型,转换成Object数组,里面却不是Object类型,所以不可以转换
Object[] obj2 = i3;//i3是二维数组,转换成Object数组后,里面还是一维数组,还是Object,所以可以转换
Object[] obj3 = s1;//s1是数组,转换成Object数组后,里面是String类型,String类型还是Object类型,所以可以转换

//这里不能转换成功,public static <T> List<T> asList(Object[] a),这个(Object[] a)表示某个引用类型的数组
//int数组不能转换成Object数组,所以会走这个asList(T... a),那么传入的int数组就变成一个参数了,也就不能转换里面的int整数了
System.out.println(Arrays.asList(i1));
//这里能够转换成功,public static <T> List<T> asList(Object[] a),这个(Object[] a)表示某个引用类型的数组
//如果是String类型数组的话,就可以转换成Object数组,因为String数组里面还是String,属于引用型。就可以转换数组里面的元素了,那么就可以转换成功
System.out.println(Arrays.asList(s1));

//调用打印对象功能,int型数组i1
printObject(i1);
//调用打印对象功能,字符串
printObject("itheima");
}

//打印对象功能
private static void printObject(Object obj) {
//获取obj对象的字节码对象
Class clas = obj.getClass();
//判断obj是否为数组
if (clas.isArray()){
//如果obj是数组就获取数组的长度
int len = Array.getLength(obj);
//遍历数组
for (int i = 0; i < len; i++){
//打印数组
System.out.println(Array.get(obj, i));
}
}else{
//如果不是数组就直接打印
System.out.println(obj);
}
}

//替换字符串的反射功能,需要调用者传入一个对象
private static void changeStringValue(Object obj) throws IllegalArgumentException, IllegalAccessException {
// TODO Auto-generated method stub
//获取obj对象所在的类的字节码对象,字节码对象又获取了该类中定义的字段,并存入了字段数组
Field[] fields = obj.getClass().getFields();

//遍历该类中所有的字段
for (Field field : fields){
//			if (field.getType().equals(String.class)){
//比较字节码应该用 == 号比较,因为字节码只有一份,而又是同一份字节码。虽然用equals也可以比较,但是语义不准确。
//如果字段类型是String的话,执行if里面的代码
if (field.getType() == String.class){
//获取该类的obj对象的该字段的具体值,用oldValue记录
String oldValue = (String) field.get(obj);
//把oldValue中的具体值字符串中的 b 都替换成 r,并用新的变量newValue来记录
String newValue = oldValue.replace('b', 'r');
//设置该obj对象的该字段的被替换后的值
field.set(obj, newValue);
}
}
}
}

//mainTest类
class mainTest {
public static void main(String[] args){
//遍历调用main方法时传入的String数组
for (String arg : args){
//打印String数组中的每个元素
System.out.println(arg);
}
}
}


------- android培训java培训、期待与您交流! ----------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: