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

黑马程序员——Java之反射

2015-12-18 19:36 691 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

内容提要:

反射的基石——Class类

Class类中的方法
通过Class对象获取类实例

Constructor类

Field类

Method类

Jdk1.5和Jdk1.4的区别

数组的反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。

反射技术可以对类进行解剖。

反射就是把Java类中的各种成分映射成相应的Java类。例如一个Java类用一个Class类的对象来表示,一个类中的组成部分:成员变量、构造方法、成员方法、包等信息也用一个个的Java类来表示。就像汽车是一个类,汽车中的发动机、变速箱等等也是一个个的类。表示Java类的Class类显然要提供一系列的方法,来获得其中的变量、方法、构造方法等信息,这些信息就是用相应类的实例对象来表示,他们是Field、Method、Constructor、Package等等,把他们表示成反射API类的一个个实例对象。

反射的基石——Class类
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class(大写的C),要注意与小写的class关键字相区别。
Class类描述了哪些方面的信息?有类名字、类的访问属性、类所属于的包名、字段名称的列表、方法名称的列表等等。比如:人(实例对象)- - ->Person类;Java类
- - ->Class类,也就是说Class- - - >代表的是一类事物。所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就是Class,用来描述字节码文件的对象。

Person类代表的人这类事物,有比如张三、李四这样的实例对象;而Class类代表着Java中涉及到的类,他的实例对象是什么呢?1.对应的是内存中的字节码;2.一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以他们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示。
平常使用的类实例对象,需要JVM先将类的字节码加载到内存中去,再使用这个字节码去创建对象。每一个类在内存中仅仅有一份字节码;不同字节码是不同的类。加载XX.class文件进内存时就被封装成了对象,该对象就是字节码文件对象。
要想对一个类进行内容的获取,必须要先获取该字节码文件的对象,该对象属于Class类型。

对于Class类的实例对象,不能使用new Class()得到一个类的字节码。有三种方式得到一个Class实例对象:
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String s1 = "abc";
Class cls1 = s1.getClass(); // 使用对象得到类字节码
Class cls2 = String.class; // 类型名.class,即代表类字节码文件

// 在反射中最常用的一种方式
Class cls3 = Class.forName("java.lang.String"); // Class类的静态方法

System.out.println(cls2.getTypeName());
System.out.println(cls2.isPrimitive()); // String不是基本类型
System.out.println(cls1 == cls2); // 判断是否是同一份字节码文件
System.out.println(cls1 == cls3);
System.out.println(cls2 == cls3);

System.out.println(int.class.isPrimitive()); // int是基本类型
System.out.println(int.class == Integer.class); // 两者不是同一份字节码
System.out.println(Integer.TYPE); // Integer中的字段TYPE代表的是其基本类型字节码
System.out.println(int.class == Integer.TYPE);
System.out.println(int[].class.isPrimitive()); // 数组不是基本类型
System.out.println(int[].class.isArray()); // 判断是否是数组类型

// 获取当前类所在的包
System.out.println(new ReflectionDemo().getClass().getPackage());
}
}

代码分析:代码使用三种方式得到相应类的字节码文件:String.class,并演示了Class类的相关方法。

综上所述,可以总结获取类型字节码文件的三种方法:

方法一、通过对象的getClass()方法进行获取,但每次都需要具体的类和该类的对象,以及调用getClass方法。

方法二、任何数据类型都具备一个静态的属性class,这个属性直接获取到该类型对应的Class对象,但需要使用具体的类。

方法三、通过类名直接获取,但必须是全类名。其中Class.forName()的作用:返回字节码,有两种方式返回:其一是这份字节码曾经被加载过,已经存在于JVM中,直接返回即可;另一种是JVM不存在这份字节码,则用类加载器去加载并缓冲。
需要注意的是:八个基本数据类型,也对应了Class字节码对象。特别的,void.class也对应一个基本类型的Class字节码对象。
(比如int[])任何类型都可以用Class对象来表示,都对应着一份字节码文件。
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如:int[],void…
Class类中的方法
static Class forName(String className):返回与给定字符串名的类或接口的相关联的Class对象。

Field getField(String name):返回一个Field对象,他表示此Class对象所代表的类或接口的指定公共成员字段

Field[] getFields():返回包含某些Field对象的数组,表示所代表类中的成员字段。

Method getMethod(String name, Class... parameterTypes):返回一个Method对象,他表示的是此Class对象所代表的类的指定公共成员方法

Method[] getMethods():返回一个包含某些Method对象的数组,是所代表的类中的公共成员方法。
通过Class对象获取类实例

步骤:

1.查找并加载指定名字的字节码文件进内存,并封装成Class对象;

2.通过Class对象的newInstance方法创建该Class对应的类实例;

3.调用newInstance()方法会去使用该类的空参数构造函数进行初始化。

public class Test {

public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
// 获取Person类的Class对象
String className = "java1218.Person";
Class clazz = Class.forName(className);
// 通过newInstance方法获取类的无参构造函数实例
Person p = (Person) clazz.newInstance();
}

}

class Person {
private String name;
public int age;

Person() {
System.out.println("Person is running");
}

Person(String name, int age) {
this.age = age;
this.name = name;
}

public String toString() {
return name + "::" + age;
}
}

反射就是把Java类中的各种成分映射成相应的Java类。
一个Java类用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示。
表示Java类的Class类显然要提供一系列的方法来获取其中的变量、方法、构造方法、修饰符、包等等信息,这些信息就是用相应类的实例对象来表示。比如Field、Method、Constructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。
Constructor类

如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化,这是该怎么办?这是就不能使用Class类的newInstance方法了。既然要通过指定的构造函数进行对象的初始化,就必须先获取这个类的构造函数——Constructor。

Constructor类代表某个类的构造方法类,里面包含了该类的所有构造方法。
import java.lang.reflect.Constructor;
public class ReflectionDemo2 {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String s = "abc";
Class cls = s.getClass(); // 使用对象得到类字节码

// 获得String类的所有构造方法
Constructor[] constructors = cls.getConstructors(); // 还有另外两种方式
// 获得String类的具有一个StringBuffer参数的构造方法
Constructor constructor = cls.getConstructor(StringBuffer.class);
// 调用构造方法对象的newInstance方法创建对象
String str = (String) constructor.newInstance(new StringBuffer("abd"));// 返回的是Object对象
// 以上语句和new String(new StringBuffer("abc"));相同
System.out.println(str.charAt(1));

// 方法二:使用Class类的静态方法,先得到默认(不带参数)构造方法,再创建对象
String obj = (String) Class.forName("java.lang.String").newInstance();
}
}

代码分析:获得的类字节码后,通过getConstructors()方法获得了该类的相关构造方法,并将结果存储在Constructor类的数组中。代码还演示了使用得到的构造器创建对象。
获取构造方法:

其一、得到这个类的所有构造方法;

其二、获取某一个构造方法。

public class Test {

public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
// 获取Person类的Class对象
String className = "java1218.Person";
Class clazz = Class.forName(className);
// 通过newInstance方法获取类的无参构造函数实例
Person p1 = (Person) clazz.newInstance(); //只能是无参构造器

//获取指定构造函数的类实例
Constructor con = clazz.getConstructor(String.class, int.class);
//反射方式:通过构造函数类实例的newInstance方法,创建对应类对象
Person p2 = (Person) con.newInstance("lisi", 30);
System.out.println(p2.toString());

//普通方式,调用默认构造方法
Person p3 = new Person();
Person p4 = new Person("zhangsan",26);
}

}


创建实例对象的两种方法比较:

其一、通常方式;其二、反射方法。

需要注意的是:创建实例时newInstance()方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致;newInstance()构造出一个实例对象,每调用一次就构造一个对象;利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。
一个类有多个构造方法,用什么方式区分清楚得到的方法?根据参数个数和类型来区分。需要指出的是:获得方法时要指出参数的类型,调用获得的方法时要用到与之相同类型的实例对象。newInstance()的两种方式,其中Class类的静态方法有缓存的动作,导致程序性能下降。

反射会导致程序性能下降。
Filed类

Field类代表某个类的成员变量类。
Field getDeclaredField(String s):获取该类中任意成员变量,包括私有的;

setAccessible(true):如果是私有字段,要先将该私有字段进行取消权限检查的能力,也称暴力反射。

set(Object obj, Object value):将指定对象上此Field对象表示的字段设置为指定的值。

Object get(Object obj):返回指定对象上File表示的字段的值。

import java.lang.reflect.Field;
public class ReflectionDemo3 {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Demo d = new Demo("zhangfeng", 26);
// 此处的field指的是一个变量,并不和某个对象绑定
Field field_public = d.getClass().getField("name");
// 取出d对象上的该字段的值
System.out.println(field_public.get(d));

// Field field_private = d.getClass().getField("age"); //只能得到可见的
Field field_private = d.getClass().getDeclaredField("age"); // 能得到不可见的
field_private.setAccessible(true); // 暴力反射
System.out.println(field_private.get(d)); // private修饰的字段,不可见
}
}

class Demo {
public String name;
private int age;

public Demo(String name, int age) {
super();
this.name = name;
this.age = age;
}
}

代码分析:由Demo类的class文件,通过getField()方法获得了该类的名为name的字段。但是突然想到了一个问题:和Constructor比较,构造器能够得到构造器的数组,字段为什么就不行?通过查看API,可见:使用getFields()方法即可获得全部可见的字段。
通过反射机制,可以替换一个对象的字段内容。
/*
* 需求:通过反射将对象中的字段内容进行替换
* */

import java.lang.reflect.Field;

public class ReflectionDemo4 {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
DemoTest dt = new DemoTest();
changeField(dt);
System.out.println(dt);
}

public static void changeField(Object obj) throws Exception {
Field[] fields = obj.getClass().getFields();
for (Field field : fields) {
//输出字段信息
System.out.println(field);
/* 测试结果
* public java.lang.String java1218.DemoTest.str1
* public java.lang.String java1218.DemoTest.str2
* */

if (field.getType() == String.class) {
String oldValue = (String) field.get(obj); //获取obj对象的该字段内容
String newValue = oldValue.replace('h', 'w');
field.set(obj, newValue);
}
/* 测试结果
* ww-->ww*/
}
}
}

class DemoTest {
//私有成员变量
private int x;
private int y;

//公有成员变量
public String str1 = "hh-->";
public String str2 = "ww";

public DemoTest() {
}

public DemoTest(int x, int y) {
super();
this.x = x;
this.y = y;
}

public String toString() // System.out会自动调用toString()方法
{
return str1 + str2;
}
}

代码分析:通过反射,获取了指定字段,并将其替换为新内容。

Method类

Method类代表某个类的成员方法类。调用某个对象上的方法,要先得到方法,再针对某个对象调用。
Method[] getMethods():只能获取公共和父类中的方法;

Method[] getDeclaredMethods():获取本类中的方法,包括私有的;

Method getMethod("方法名",参数.class(如果是空参数,可以写null));
Object invoke(Object obj,参数):方法的调用。如果方法是静态的,invoke方法中的对象参数obj可以为null。

import java.lang.reflect.Method;
public class ReflectionDemo5 {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String str = "zhangfeng";

// 获取String类上的名为charAt方法,该方法有一个int类型的参数
Method methodcharAt = String.class.getMethod("charAt", int.class);
// 通过方法对象invoke引发方法执行,作用在str对象上
System.out.println(methodcharAt.invoke(str, 2));
System.out.println(methodcharAt.invoke(str, new Object[] { 2 })); // 按照jdk1.4的写法传入数组

Method methodMain = TestArgument.class.getMethod("main", String[].class); // main方法需要的是String[]类型
// 如果invoke时,对象位置的参数为null时,表明该方法是类的静态方法
// methodMain.invoke(null,new String[]{"111","222","333"});
// //会拆开这个字符串数组,变成三个参数
methodMain.invoke(null, new Object[] { new String[] { "111","222","333" } }); // 将字符串数组打包,变成一个参数
}
}

class TestArgument {
public static void main(String[] args) {
for (String str : args)
System.out.println(str);
}
}

代码分析:这个程序能够根据用户提供的类名,去执行该类中的main()方法。

练习:

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

public class ReflectTest {

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

/*
*笔记本电脑使用usb设备
*/
NoteBook computer=new NoteBook();
computer.run();

//关联配置文件
File file=new File("usb.properties");
FileInputStream fis=new FileInputStream(file);
//将配置文件信息缓存到集合中
Properties ps=new Properties();
ps.load(fis);

System.out.println(ps.size());

for(int x=1;x<=ps.size();x++){
String className=ps.getProperty("usb"+x);//获取配置文件中类名
Class clazz=Class.forName(className);//获取类的Class对象
USB usb=(USB)clazz.newInstance();//得到类实例
computer.useUSB(usb);//开始使用

}
fis.close();//关流
}
}

//USB 接口
interface USB {
void open();
void close();
}
//笔记本电脑
class NoteBook {
public void run(){
System.out.println("NoteBook is run");
}

public void useUSB(USB usb){
if(usb!=null){
usb.open();
usb.close();
}
}
}
//鼠标
class MouseUSB implements USB {
@Override
public void open() {
System.out.println("Mouse is use");
}
@Override
public void close() {
System.out.println("Mouse is close");
}
}
//键盘
class KeyUSB implements USB {
@Override
public void open() {
System.out.println("Key is use");
}
@Override
public void close() {
System.out.println("Key is close");
}
}

JDK1.4与JDK1.5的区别
Jdk1.5:public Object invoke(Object obj, Object … args); 可变参;Jdk1.4:public Object invoke(Object obj, Object[] args); 将一个数组作为参数传递给invoke方法,数组中的每个元素分别对应被调用方法中的一个参数。
对接受数组参数的成员方法进行反射:按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,会按jdk1.4的语法进行处理(兼容性考虑),即把数组打散称为若干个单独的参数。
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

每个数组的父类,都是Object类型。
基本数据类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本数据类型的一维数组,既可以被当做Object类型使用,又能被当做Object[]类型使用。
启动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"});
这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。

import java.util.Arrays;
public class ReflectionDemo6 {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr1 = new int[] { 1, 2, 3 };
int[] arr2 = new int[3];
int[][] arr3 = new int[4][4];
String[] arr4 = new String[] { "as", "dd" };
String[] arr5 = new String[3];

System.out.println(arr1.getClass() == arr2.getClass()); // 是相同的字节码文件
System.out.println(arr4.getClass() == arr5.getClass()); // 是相同的字节码文件

System.out.println(arr1.getClass().getName()); // [I:int类型的一维数组
System.out.println(arr3.getClass().getName()); // [[I:int类型的二维数组
System.out.println(arr4.getClass().getName()); // [java.lang.String

// 输出的都是java.lang.Object
System.out.println(arr1.getClass().getSuperclass().getName()); //java.lang.Object
System.out.println(arr3.getClass().getSuperclass().getName()); //java.lang.Object
System.out.println(arr4.getClass().getSuperclass().getName()); //java.lang.Object

Object obj1 = arr1;
Object obj2 = arr4; // java.lang.Object
Object[] obj3 = arr3; // [java.lang.Object
Object[] obj4 = arr4;
// Object[] obj5 = arr2; //can`t convert from int[] to object[]

// 测试
System.out.println(Arrays.asList(arr1)); // 输出的是hashCode
System.out.println(Arrays.asList(arr4)); // 打印内容
}
}

代码分析:计算结果输出了各种类型class文件类型。
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

Object[]与String[]没有父子关系,Object与String有父子关系,所以new Object[]{"abc","bbb"}不能强制转化为new
String[]{"abc","bbb"}。

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

数组的反射
数组反射的应用:
/*
* 数组反射:
* Array类专门用于数组反射的工具类
* */
import java.lang.reflect.Array;

public class ReflectionDemo7 {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr1 = new int[] { 1, 2, 3 };
int[] arr2 = new int[3];
int[][] arr3 = new int[4][4];
String[] arr4 = new String[] { "as", "dd" };
String[] arr5 = new String[3];

Object obj1 = arr1;
printObject(obj1); // 1 2 3
System.out.println("<--------------->");
Object obj2 = arr2;
printObject(obj2); // 0 0 0
System.out.println("<--------------->");
Object obj3 = arr3;
printObject(obj3); // [I@106d69c[I@52e922[I@25154f[I@10dea4e
System.out.println("<--------------->");
Object obj4 = arr4;
printObject(obj4); // as dd
System.out.println("<--------------->");
printObject("xyz"); // 直接打印对象 xyz
}

// 数组的反射
private static void printObject(Object obj) {
Class cl = obj.getClass();

if (cl.isArray()) {
int len = Array.getLength(obj); // 获取数组长度
for (int index = 0; index < len; index++)
System.out.print(Array.get(obj, index)); // 获取该数组指定位置的元素
} else
System.out.println(obj);
}
}

代码分析:只能得到数组元素的类型,不能得到声明数组的类型。
反射的作用就是实现框架的功能,在程序中无法直接new某个类的实例对象了,需要反射来实现。
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;

import java1205.FileInputStream;

public class ReflectionTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
InputStream ips = new FileInputStream("config.properties"); // 类名在配置文件中,未出现在源文件中
Properties props = new Properties();
props.load(ips);
ips.close();

String className = props.getProperty("className");
Collection collection = (Collection) Class.forName(className).newInstance();

/* 下面就可以通过config.properties中定义的className使用该类 */
}
}
代码分析:由于无法获知运行类的类名,需要通过反射来实现获取。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: