17透彻分析反射的基础
/*
反射的基石-->Class
如何获取字节码,有几种方法?
字节码:类的创建首先要把字节码加载到内存当中,再用这个字节码去复制出一个一个对象。
每一个字节码就是一个类的实例对象。
Java程序中的各个java类属于同一个类事物,描述这类事物的java类名就是Class。
对比提问:众多的人用一个什么类表示?众多的java类用一个什么类表示?
人-->Person
java类-->Class
对比提问:Person类代表人,它的实例对象就是张三,李四这样一个个具体的人Class类代表java类,
它的各个实例对象又分别对应什么呢?
对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类字节码,不同的
类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象
来表示,这些对象显然具有相同的类型,这个类型是什么呢?
如何得到各个字节码对应的实例对象(Class类型)
类名.class,例如,System.class
对象.getClass(),例如,new Date().getClass()
Class.forName("类名"),例如,Class.forName("java.util.Date").//传入字符串包名。
九个预定义Class实例对象:
参看Class.isPrimitive方法的帮助
int.class==Integer.Type
数组类型的Class实例对象
Class.isArray()
总之,只要是源程序中出现的类型,都有各自的Class实例对象,例如int[],void...
*/
isPrimitive public boolean isPrimitive()判定指定的 Class 对象是否表示一个基本类型。 有九种预定义的 Class 对象,表示八个基本类型和 void。这些类对象由 Java 虚拟机创建,与其表示的基本类型同名,即 boolean、byte、char、short、int、long、float 和 double。 这些对象仅能通过下列声明为 public static final 的变量访问,也是使此方法返回 true 的仅有的几个 Class 对象。 Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYP |
代码:
String str1 = "abc";
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
System.out.println("cls1==cls2:"+(cls1==cls2));
System.out.println("cls1==cls3:"+(cls1==cls3));
System.out.println("cls1.isPrimitive()::"+cls1.isPrimitive());
System.out.println("int.class.isPrimitive()::"+int.class.isPrimitive());
System.out.println("int.class == Integer.class::"+(int.class == Integer.class));
//type常量代表这个包装类型包装的基本类型的字节码。
System.out.println("int.class == Integer.TYPE::"+(int.class == Integer.TYPE));
//数组不是原始类型。
System.out.println("int[].class.isPrimitive()::"+(int[].class.isPrimitive()));
System.out.println("int[].class.isArray()::"+(int[].class.isArray()));
结果:
cls1==cls2:true
cls1==cls3:truecls1.isPrimitive()::falseint.class.isPrimitive()::trueint.class == Integer.class::falseint.class == Integer.TYPE::trueint[].class.isPrimitive()::falseint[].class.isArray()::true
--------------------------------------------------
18 理解反射的概念
简而言之:反射就是把java类中的各种成分映射成相应的java类
反射就是把java类中的各种成分映射成相应的java类。例如,一个java类中用一个Class类的对象来表示,
一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的java类表示,就像汽车是
一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的class类显然要提供一系列的方法,
来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,他们
-------------------------------------------
19 构造方法的反射应用
Constructor类
Constructor类代表某个类中的一个构造方法
得到某个类所有的构造方法:
例子:Constructor[] constructor =
Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
//通过构造方法的参数类型来确定获取的是哪一个构造方法
例子1:Constructor constructor =
Class.forName("java.lang.String").getConstructor(StringBuffer.class);
例子2:Constructor constructor2 = ReflectPoint.class.getConstructor(
Integer.TYPE,Integer.TYPE)//基本数据类型用法
ReflectPoint rp3 =(ReflectPoint)constructor2.newInstance(2,2)
获得方法时要用到类型
创建实例对象:
通常方式:String str = new String(new StringBuffer("abc"));
反射方式:String str = (String) constructor.newInstance(new StringBuffer("abc"));
//通过字节码获取constructor构造方法类。
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
//通过constructor创建一个string类,此处有类型提升,需要强转。
//String.class.newInstance()可以通过class类直接创建实例
String conStr = (String)constructor1.newInstance(new StringBuffer("abc"));
System.out.println(conStr);
获取构造方法并创建person类:
Person p = (Person)Person.class.getConstructor(
String.class,/*int.class*/Integer.TYPE).newInstance(new String("zhangsan"),13);
----------------------------------------------------------------
20 成员变量的反射
Field类
Field类代表某个类中的一个成员变量
演示eclipse自动生成java类的构造方法
问题:得到的Field对象是对应到的类上面的饿成员变量,还是对应到对象上的
成员变量?类只有一个,二该类的实例对象又多个,如果是与对象关联,哪
关联的是哪个对象呢?所以字段fieldX代表的是x的定义,而不是具体的X变量。
-----------------------------------------------------------------------------------
21 成员变量反射的综合案例
Field 类
//此类用于反射案例,无实际作用。
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "ReflectPoint [str1=" + str1 + ", str2=" + str2 + ", str3="
+ str3 +",x="+x+",y="+y+"]";
}
package cn.itcast.day1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class ReflectTest2 {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
ReflectPoint pt1 = new ReflectPoint(3,7);
//fieldY代表的是Y变量而不是具体的y的值
Field fieldY = pt1.getClass().getField("y");
System.out.println(fieldY.get(pt1));
//getDeclaredField()获取所有该类对象的成员无论是否私有,该x的是为prviate私有
Field fieldX = pt1.getClass().getDeclaredField("x");
//如果要获取私有的值必须先设为setAccessible为ture才可以获取值。
叫暴力反射
fieldX.setAccessible(true);
System.out.println(fieldX.get(pt1));
//设定pt1对象fieldY,y的新值为9
fieldY.set(pt1, 9);
System.out.println(pt1);
changeStringValue(pt1);
System.out.println(pt1);
}
//用反射原理获取某个类的String成员,并更改成员的内容将'b'替换成a
private static void changeStringValue(Object obj) throws Exception {
//将某个类的所有成员用Field[] 数组存起来
Field[] fields= obj.getClass().getFields();
//通过for遍历每一个成员
for(Field field : fields){
//field.getType()判断该成员是否为String类型
if(field.getType()==String.class){
//获取旧值存入oldValue内
String oldValue = (String)field.get(obj);
//新字符串的a替换旧字符串内所有的b
String newValue = oldValue.replace('b', 'a');
//将对象内成员的值设定为新字符串
field.set(obj,newValue);
}
}
}
}
-----------------------------------------------------------------------------
22 成员方法的反射
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对象对应的是一个静态方法!
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改写为char.invoke("str",new Object[]{1})形式。
String str1 = "abc";
Method methodCharAt = String.class.getMethod("charAt", int.class);
System.out.println(methodCharAt.invoke(str1, 1));
---------------------------------------
23 用反射方式执行某个类中的main方法
目标:
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
用普通方式调完后,大家要明白为什么要用反射方式去调啊?
问题:
启动java程序的main方法的参数是一个字符串数组,即public static void main(String[] args)
通过反射方式来调用这个main方法时,如果为invoke方法传递参数呢?按jdk1.5的语法,整个数
组是一个参数,而按jdk1.4的语法数组中的每个元素对应一个参数,当把一个字符串数组作为参
数传递给invoke方法时javac会到底按照那种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,
会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。
所以在给main方法传递参数时,不能使用代码
mainMthod.invoke(null.new String[]{"xxxx"}),
javac只把他当做jdk1.4的语法进行理解,而不把它当做jdk1.5的语法解释,因此会出现参数类型不对的问题。
解决办法:
mainMthod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMthod.invoke(null,(Object)new String[]{"xxx"});编译器会做特殊处理,编译时不把参数当做数组看待,
也就不会讲数组打散成若干个参数了。
package cn.itcast.day1;
import java.lang.reflect.Method;
public class ReflectTest3 {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
//传递一个字符串给当前类运行,参数为,cn.itcast.day1.TestArguments
String startingClassName = args[0];
//用反射获取TestArguments类的main方法。
Method mainMethod =Class.forName(startingClassName).getMethod("main", String[].class);
//用反射原理调用这个main方法;此处的有两种方式.
//方式一:数组的父类就是Object[]。此处将String数组变成Object一个数组元素。
mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
//方式二:此处通过强转的方式,告诉编译器当前数组就是一个Object数组,让编译器把数组当做是一个整体处理。
//mainMethod.invoke(null,(Object)new String[]{"111","222","333"});
}
}
//创建一个有main函数的类。
class TestArguments{
public static void main(String[] args){
for(String arg : args){
System.out.println("arg"+arg);
}
}
}
--------------------------------------------------------
24
数组与Object的关系及反射类型
数组与Object的关系及反射类型
|-具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
|-代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
|-基本类型的一维数组可以被当做Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以
当作Object类型使用,又可以当作Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异:
而String[]可以被当做Object[]类型,所以是1.4版本方法 | 1.4版本 asList(Object[] obj) ,1.5版本 asList(T...a),因为int[]不可以转换成Object[],所以是1.5版本的方法,接收的是对象,打印哈希码,
package cn.itcast.day1;
import java.lang.reflect.Array;
import java.util.Arrays;
public class ReflectTest24 {
/**数组
* @param args
*/
public static void main(String[] args) {
// 数组与Object的关系及其反射类型
int[] a1 = new int[]{'1','2'};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[]{"1","3","b"};
System.out.println(a1.getClass()== a2.getClass());
//System.out.println(a1.getClass()== a3.getClass());
//System.out.println(a1.getClass()== a2.getClass());
System.out.println(a1.getClass().getName());
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a4));
Object aObj1 = a1;//父类都是 Object。
Object aObj2 = a4;
//Object[] aObj3 =a1;//编译无法通过,因为a1是int的基本数据类型他不是一个object。
Object[] aObj4 = a3;
Object[] aObj5 = a4;
/*
for(int x=0;x<Array.getLength(a4)-1;x++){
System.out.println((Array.get(a1,x)));
}*/
printObject(a4);
printObject(a1);
}
//打印数组元素。
public static void printObject(Object obj){
Class clazz = obj.getClass();
if(clazz.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);
}
}
}
------------------------------------------------------------
25
数组的反射的应用
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的
一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异。int显示的哈希值,string可以打印里面的元素。
Array工具类用于完成对数组的反射操作。
思考题:怎么得到数组中的元素类型?
//用反射的方式,打印数组元素。
public static void printObject(Object obj){
Class clazz = obj.getClass();
if(clazz.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);
}
}
26 ArrayList_Hashset的比较及Hashcode分析
提示:
1,通常来说,一个类的两个实例对象用equals()方法比较的结果相等时,它们的哈希码必须相等,
但反之则不成立,即equals方法比较结果不相等的对象可以有相同的哈希码,或者哈希码相同的
两个对象的equals方法比较的结果可以不等,例如字符串“bb”和“Aa“的equals方法比较结果
肯定不相等,但它们的hashCode方法返回值却相等。
2,当一个对象呗存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,
否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了。这种情况下,即使在
contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,
这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
-----------------------------------------------------------------------------
27
框架的概念及用反射技术开发框架的原理
框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户
需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具
类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎么能调用你以后写
类(门窗)呢?
因为在写程序时无法知道要被调用的类名,所以在程序中无法直接new 某个类的实例对象了,
而要用反射方式来做。
综合案例
1 先直接用new语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成
ReflectPoint类的equals和hashcode方法,比较两个集合的运行结果差异。
2 然后改为采用配置文件加反射的方式创建ArrayList和HashCode的实例对象,
比较观察运行结果差异
3 引入了eclipse对资源文件的管理方式的讲解。
//只需通过更改配置文件就可以更改,创建的集合比如:ArrayList和HashSet
public static void main(String[] args) throws Exception {
//创建流读取配置文件信息
InputStream ips = new FileInputStream("config.properties");
//创建properties类。
Properties props = new Properties();
//将文件读取到props里
props.load(ips);
//获取className对应的java.util.ArrayList
String className = props.getProperty("className");
//利用反射原理,建立集合类
Collection collections = (Collection)Class.forName(className).newInstance();
//Collection collections = new HashSet();
ReflectPoint rp1 =new ReflectPoint(3,3);
ReflectPoint rp2 =new ReflectPoint(5,5);
ReflectPoint rp3 =new ReflectPoint(3,3);
collections.add(rp1);
collections.add(rp2);
collections.add(rp3);
collections.add(rp1);
System.out.println(collections.size());
}
28 用类加载器的方式管理资源和配置文件
文件夹位置D:\MyEclipse 8.5\javaenhance\src\cn\itcast\day1
配置文件如果放在.java目录下面,myeclipse会自动将他复制到class path下面就是编译过的.class文件夹下面。
开发当中配置文件都放在class path下面。
//通过类加载器ArrayListHashsetDemo.class.
getClassLoader()方法获取配置文件的信息。cn前面如果添加/那就是代表根目录javaenhance。
方式1:
InputStream ips = ArrayListHashsetDemo.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
方式2:
//a如果类和配置文件在同一个文件夹,相对路径是默认在一起的。
//b如果用的是绝对路径就要写清楚当前的路径和配置文件所在文件夹。"/cn/itcast/day1/resources/config.properties"
InputStream ips = ArrayListHashsetDemo.class.getResourceAsStream("config.properties");
不管是相对还是绝对他们内部都是调用getClassLoader()
总结:
Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。 Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。 虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。 基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。 每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。 一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象。 一、如何得到Class的对象呢?有三种方法可以的获取: 1、调用Object类的getClass()方法来得到Class对象,这也是最常见的产生Class对象的方法。例如: MyObject x; Class c1 = x.getClass(); 2、使用Class类的中静态forName()方法获得与字符串对应的Class对象。例如: Class c2=Class.forName("MyObject"),Employee必须是接口或者类的名字。 3、获取Class类型对象的第三个方法非常简单。如果T是一个Java类型,那么T.class就代表了匹配的类对象。例如 Class cl1 = Manager.class; Class cl2 = int.class; Class cl3 = Double[].class; 注意:Class对象实际上描述的只是类型,而这类型未必是类或者接口。例如上面的int.class是一个Class类型的对象。由于历史原因,数组类型的getName方法会返回奇怪的名字。 二、Class类的常用方法 1、getName() 一个Class对象描述了一个特定类的属性,Class类中最常用的方法getName以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 2、newInstance() Class还有一个有用的方法可以为类创建一个实例,这个方法叫做newInstance()。例如: x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。 3、getClassLoader() 返回该类的类加载器。 4、getComponentType() 返回表示数组组件类型的 Class。 5、getSuperclass() 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。 6、isArray() 判定此 Class 对象是否表示一个数组类。 三、Class的一些使用技巧 1、forName和newInstance结合起来使用,可以根据存储在字符串中的类名创建对象。例如 Object obj = Class.forName(s).newInstance(); 2、虚拟机为每种类型管理一个独一无二的Class对象。因此可以使用==操作符来比较类对象。例如: if(e.getClass() == Employee.class) |