------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
反射的基石:Class类
Java程序中的个Java类都属于同一类事物,描述这类事物的Java类名就是Class。
对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?
人----->Person
Java类------->Class
对比提问:Person类代表人,它的实例对象就是张三、李四这样的一个个具体的人,Class类代表Java类,它的各个实例对象又分别对应什么呢?
对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同
的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?
Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性。至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不
同的属性值。Java程序中的各个Java类,它们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与小写的class
关键字区别。Class类描述了哪方面的信息呢?类的名字,类的访问属性,类所属的包名,字段名称列表,方法名称的列表等等。学习放射,首先要明白Class
这个类。
反射
反射就是把Java类中的各种成分映射成相应的Java类。例如,一个Java类中用一个Class类的对象来表示一个类中的组成部分:成员变量,方法,构造方法,包等等信息页用一
个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示Java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方
法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Constructor、Package等等。
一个类中的每个成员都可以用相应的反射API的一个实例对象来表。
反射获取字节码的三种方式:
1, 类名.Class,例如:System.class
2, 对象.getClass(),例如,newDate().getClass(
3, Class.forName(“类名”),例如,Class.forName(“java.util.Date”);反射主要用这种,可以不用知道类型的名字
九个预定义的Class实例对象:八个基本类型加上void
总之:只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…
IsPrimitive判断指定的Class对象是否表示一个基本类型
IsArray判定此Class对象是否表示一个数组类
练习:
package Reflect;
public class RefDemo {
public static void main(String[] args) throws ClassNotFoundException {
String str = "dfjfl";
//获得String字节码的三种方式
Class cls1 = str.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");//参数为完整的类名 Class的静态方法
//三个字节码对应的是同一个字节码 两个true
System.out.println(cls1 == cls2);
System.out.println(cls1 == cls3);
//判断是否为基本类型
System.out.println(cls1.isPrimitive());
System.out.println(int.class.isPrimitive());
System.out.println(int.class == Integer.class);
//Integer。TYPE代表它所包装的基本类型的字节码
System.out.println(int.class == Integer.TYPE);
//判断是否为一个数组类型
System.out.println(int[].class.isArray());
}
}
打印结果:
true
true
false
true
false
Constructor
Constructor类代表某个类中的一个构造方法
得到某个类所有的构造方法:
例子:Constructor[] constructors = Class.forName(“java.lang.String”).getConstructors();
得到某一个构造方法:
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”));
调用获得的方法时要用到上面相同类型的实例对象
Class.newInstance()方法:
例子:String obj =(String)Class.forName(“java.lang.String”).newInstance():
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
练习:
package Reflect;
import java.lang.reflect.*;
public class ConstructorDemo {
public static void main(String[] args)throws Exception {
//获取String接受StringBuffer类型参数的构造函数
Constructor constructor = String.class.getConstructor(StringBuffer.class);
//用获取的构造函数创造新的实例对象
String str = (String)constructor.newInstance(new StringBuffer("ffe"));
//用Class类的newInstance方法穿件实例对象
String str1 = (String)Class.forName("java.lang.String").newInstance();
System.out.println(str);
System.out.println(str1.length());
}
}
打印结果:
ffe
0
FIeld
Field类代表某个类中的一个成员变量
常用方法:
Field getField(String s); 返回一个Filed对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
Field getDeclaredField(String s); 返回一个Field对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
setAccessible(ture);将此对象的accseeible标志设置为指示的布尔值。值为true指示反射的对象在使用时应该取消 Java语言访问检查。值为 flase 则 指示 反射的对象应该实施 Java 语言访问检查。
set(Object obj, Object value);//将指定对象变量上此Field对象表示的字段设置为指定的新值。
Object get(Object obj);//返回指定对象上Field表示的字段的值。
练习:
import java.lang.reflect.Field;
public class RefDemo2 {
public static void main(String[] args)throws Exception {
//创建Person对象
Person person = new Person("li",23);
//获取name字段
Field field = person.getClass().getField("name");
//获取person对象上的name字段值
String name = (String) field.get(person);
System.out.println(name);
//获取该类的全部字段 包括私有 获取私有的age字段
Field field1 = person.getClass().getDeclaredField("age");
//取消访问检查
field1.setAccessible(true);
//获取person上的age属性值
int age = (int)field1.get(person);
System.out.println(age);
}
}
class Person {
public String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
}
打印结果:
li
23
Method
package Reflect;
/*练习: 将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的'b'替换为'a'
* */
import java.lang.reflect.Field;
public class RefDemo3 {
public static void main(String[] args) throws Exception {
//创建Person对象
Person person = new Person();
//调用方法
changeStr(person);
//输出
System.out.println(person);
}
public static void changeStr(Object obj)throws Exception{
//获得对象的所有字段
Field[] fileds = obj.getClass().getFields();
//遍历字段数组
for(Field field : fileds){
//判断字段是否为String类型
if(field.getType() == String.class){
//获取字段值
String oldStr = (String) field.get(obj);
//字符替换
String newStr = oldStr.replace('b', 'a');
//将替换后的字段赋值给对象的字段
field.set(obj, newStr);
}
}
}
}
class Person {
public String name = "djl";
private int age;
public String str1 = "ball";
public String str2 = "bascateball";
public String str3 = "itcast";
public Person(){}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String toString(){
return str1+":"+str2+":"+str3;
}
<span style="font-size:18px;">}
</span>
打印结果:
aall:aascateaall:itcast
Method
Methodl类代表某个类中的一个成员方法
相关方法:
Method getMethod(String name, Class<?>... parameterTypes) ; 返回一个 Method 对象,它反映此Class对象所表示的类或接口的指定公共成员方法。
Method[] getMethods();返回一个包含某些Method
对象的数组,这些对象反映此Class
对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那
些的类或接口)的公共 member 方法。
Object invoke(Object obj, Object... args); 对带有指定参数的指定对象调用由此Method
对象表示的底层方法。
Method getDeclaredMethod(String name, Class<?>... parameterTypes);返回一个Method
对象,该对象反映此Class
对象所表示的类或接口的指定已声明方法。
注:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法
练习:
package Reflect;
import java.lang.reflect.*;
public class RefDemo4 {
public static void main(String[] args)throws Exception {
//给定字符串str
String str = "ksfk";
//通过反射获取charAt方法
Method method = String.class.getMethod("charAt", int.class);
//执行charAt方法
char c = (char)method.invoke(str, 1);
System.out.println(c);
}
}
打印结果: s
JDK1.4和JDK1.5的invoke方法的区别:
JDK1.5: public Object invoke(Object obj, Object... args);
JDK1.4: public Object invoke(Object obj, Object[] args),即按JDK1.4的语法需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应别调用方法中的一个参
数,所以,调用charAt()方法的代码也可以用JDK1.4改写为charAt(str, new Object[]{1})的形式。
写一个程序,这个程序能够根据用户提供的类名,去执行该类的main方法:
问题:启动Java程序的main方法的参数是一个字符串数组,即public static void main(String args[]),通过反射方式来调用这个main方法时如何为invoke方法传递参数呢?按
JDK1.5的语法,整个数组是一个参数,而按JDK1.4的语法,数组中的每个元素都对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底
按照哪种语法进行处理呢?JDK1.5肯定要兼容JDK1.4。所以,在给main方法传递参数时,不能使用代码 mainMethod.invoke(null,new String[](“xxx“),javac只把它
当做1.4的语法进行理解,而不把它当做jdk1.5的语法理解,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,new Object[]{newString[]{“djfl“}});
mainMethod.invoke(null (Object) newString[]{“djfl“});编译器会做特殊处理,编译时不把参数当做数组看待,也就不会把数组打散成若干参数了。
示例代码:
package Reflect;
import java.lang.reflect.Method;
public class Demo {
public static void main (String args[])throws Exception{
//传统方式
Ref.main(new String[]{"123","jdfl"});
String startingClassName = args[0];
Method methodMain = Class.forName(startingClassName).getMethod("main", String[].class);
//第一种解决方案: 强制转换为超类Object,不用拆包
methodMain.invoke(null, (Object)new String[]{"jlsdfj","jld"});
//第二种结局方案:将数组打包,编译器拆包后就是一个String[]类型的整体
methodMain.invoke(null,new Object[]{new String[]{"jlsdfj","jld"}});
}
}
class Ref{
public static void main(String args[]){
for(String str : args){
System.out.println(str);
}
}
}
打印结果:
123
jdfl
jlsdfj
jld
jlsdfj
jld
数组的反射
数组的反射:具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当作Object类型使用。
Array完成对数组的反射操作。
练习代码:
package Reflect;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Arrays;
public class RedDemo5 {
public static void main(String[] args) {
int[] arr1 ={1,2,3,4,5,6,7};
int[] arr2 = new int[3];
String [] arr3 = new String[2];
int[][] arr4 = new int [3][2];
//相同维度,相同类型的数组具有相同的Class对象
System.out.println(arr1.getClass() == arr2.getClass());
//得到数组父类的类型名字
System.out.println(arr1.getClass().getSuperclass().getName());
//数组可以赋值给一个Object对象
Object obj1 = arr1;
//非基本类型的数组可以看做Object数组
Object[] obj2 = arr4;
System.out.println(Arrays.toString(obj2));
System.out.println(Arrays.asList(arr3));
print(arr1);
}
public static void print(Object obj){
//判断传入的是否维数组
if(obj.getClass().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);
}
}
}
输出结果:
true
java.lang.Object
[[I@3be4d6ef, [I@2cdb03a1, [I@5ecb5608]
[null, null]
1
2
3
4
5
6
7
反射的作用-------->实现框架功能
框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门和空调,我做的房子就是框架,用户要使用我的框架,把门窗插入进我提供的框架中。框架与工具类具有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题
我咋写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢,我写的框架程序要怎样能调用到你以后写的类(门窗)呢?
因为在写程序时无法知道被调用的类名,所以,在程序中无法直接new某个类的示例对象了,而要用反射方式来做。
综合案例:先直接用new语句创建ArrayList和HashSet的示例对象,演示用Eclipse自动生成ReflectPoint类的equals和hashCode()方法,比较两个集合的运行结果差异。
然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果的差异。
练习代码:
配置文件名称为 Properties.properties 存入的数据为:className = java.util.HashSet 或者 className = java.util.ArrayList
package Reflect;
import java.util.*;
import java.io.*;
public class RefDemo6 {
public static void main(String[] args)throws Exception {
//创建和配置文件相关联的流
InputStream in = new FileInputStream("Properties.properties");
//创建Properties对象
Properties properties = new Properties();
//加载流
properties.load(in);
in.close();
//获取类名
String className = properties.getProperty("className");
//加载并创建类的实例对象
Collection<RefTest> c =(Collection) Class.forName(className).newInstance();
RefTest re1 = new RefTest(1,2);
RefTest re2 = new RefTest(1,3);
RefTest re3 = new RefTest(1,4);
RefTest re4 = new RefTest(1,2);
//往集合中添加元素
c.add(re1);
c.add(re2);
c.add(re3);
c.add(re4);
//打印集合的长度
System.out.println(c.size());
}
}
class RefTest{
int x ;
int y;
//构造函数
public RefTest(int x, int y) {
super();
this.x = x;
this.y = y;
}
//复写hashCode
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
//复写equals
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RefTest other = (RefTest) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
打印结果: 当配置文件中的数据为:className = java.util.HashSet 时 打印结果为: 3
当配置文件中的数据位: className = java.util.ArrayList 时打印结果为: 4