重点解析:
一、反射:JAVA反射机制是在运行状态中,对于任意一个类 (class文件),都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
简单总结:动态的获取信息的过程就是java反射,可以理解为是对类的解剖。
怎么对类进行解剖呢?必须先拥有字节码文件对象。
反射的基础:Class
1、所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就叫Class(描述字节码文件的对象)。
简单记忆:对类文件共性内容的抽取,就是Class。
Class类中就包含属性有field(字段)、method(方法)、construction(构造函数)。
而field中有修饰符、类型、变量名等复杂的描述内容,因此也可以将字段封装称为一个对象。用来获取类中field的内容,这个对象的描述叫Field。同理方法和构造函数也被封装成对象Method、Constructor。要想对一个类进行内容的获取,必须要先获取该字节码文件的对象。该对象是Class类型。
Class类描述的信息:类的名字,类的访问属性,类所属于的包名,字段名称的列表,方法名称的列表等。每一个字节码就是class的实例对象。如:classcls=Data.class;
拓展:什么叫字节码?源程序的二进制代码,当加载类时,把二进制文件加载到内存中。
2、Class和class的区别
1)class:描述一类事物的共性抽取。
2)Class:类文件共性内容的抽取。
3.如何获取字节码文件对象?
三种方式:
方式一:Object类中的getClass()方法的。
想要用这种方式,必须要明确具体的类,并创建对象。
格式:对象.getClass()
如:Class clazz=new Student().getClass();//Student是一个类名
方式二:任何数据类型都具备一个静态的属性.class来获取其对应的Class对象。
相对简单,但是还是要明确用到类中的静态成员。
格式:类名.Class
如:Class clazz=Student.class;//Student是一个类名
方式三:只要通过给定的类的 字符串名称就可以获取该类,更为扩展。
可是用Class类中的方法完成。
方法就是forName.
这种方式只要有名称即可,更为方便,扩展性更强。
格式: Class.forName(“类名”)
如:Class clazz=Class.forName("包名.Student");//Student是一个类名
举例:
<span style="font-size:14px;">package day06;
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
String str ="hahh";
//获取字节码对象的三种方式,获取的字节码对象是一种引用类型。
Class cls1=str.getClass();
Class cls2=String.class;
Class cls3=Class.forName("java.lang.String");
System.out.println(cls1==cls2);
System.out.println(cls1==cls3);
System.out.println(cls1.isPrimitive());
System.out.println(int.class.isPrimitive());
//引用类型的字节码文件和基础类型的字节码文件不相同,只要是在源程序中出现的类型都有各自的Class实例对象,
System.out.println(int.class==Integer.class);
//Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,
//所以和int.class是相等的。基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示
System.out.println(int.class==Integer.TYPE);
System.out.println(int[].class.isArray());
}
}
</span>
结果:
true
true
false
true
false
true
true
4.九种预定义的Class
(1)包括八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class。
(2)Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的。基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示
注意:只要是在源程序中出现的类型都有各自的Class实例对象,如int[].class。基本数据类型的字节码文件和包装类的自己吗文件不 同
5.Class类中的方法参照API
6. 通过Class对象获取类的实例对象
步骤:
a. 获取类文件对象
b.使用newInstance()使该类的空参的构造函数初始化
举例:
<span style="font-size:14px;">package day06;
import java.lang.reflect.Constructor;
import java.util.jar.Attributes.Name;
//定义person类
class Person
{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public Person() {
super();
}
}
public class ConstructorDemo {
public static void main(String[] args) throws Exception {
//获取perosn类的class对象
Class cls1=Class.forName("day06.Person");
//用Class类的newInstance方法创建实例对象
Person p = (Person) cls1.newInstance();
System.out.println(p.toString());
}
}
</span>
二、构造函数的反射应用
Constructor类
1、概述
如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化。这时怎么办呢?这时就不能使用Class类中的newInstance方法了。既然要通过指定的构造函数进行对象的初始化。就必须先获取这个构造函数——Constructor。Constructor代表某个类的构造方法。
2、获取构造方法:
1)得到这个类的所有构造方法:如得到上面示例中Person类的所有构造方法
Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();
2)获取某一个构造方法:
Constructor con=Person.class.getConstructor(String.class,int.class);
3、创建实例对象:
1)通常方式:Person p = new Person("wangwu",10);
2)反射方式:Person p= (Person)con.newInstance("wangwu",10);
注:
1、创建实例时newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致。
2、newInstance():构造出一个实例对象,每调用一次就构造一个对象。 这里使用了缓存机制。
3、利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。
举例:
<span style="font-size:14px;">package day06;
import java.lang.reflect.Constructor;
import java.nio.Buffer;
public class ConstructorDemo2 {
public static void main(String[] args) throws Exception {
//获取指定单数的构造函数
Constructor con=String.class.getConstructor(StringBuffer.class);
//使用newInstance方法实例化对象,这里stringbuffer也要new一个对象
String str1=(String) con.newInstance(new StringBuffer("abc"));
System.out.println(str1.length());
System.out.println(str1.charAt(2));
}
}
</span>
<span style="font-size:14px;">package day06;
import java.lang.reflect.Constructor;
import java.util.jar.Attributes.Name;
//定义person类
class Person
{
private String name;
private int age;
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
}
public class ConstructorDemo {
public static void main(String[] args) throws Exception {
//获取perosn类的class对象
Class cls1=Class.forName("day06.Person");
//获取指定的构造函数的类实例
Constructor con = cls1.getConstructor(String.class,int.class);
//用Class类的newInstance方法穿件实例对象
Person p =(Person)con.newInstance("zhangsan",10);
System.out.println(p.toString());
}
}
</span>
三、成员变量的反射
1.Field类:Field类代表某个类中一个成员变量
方法
Field getField(String s);//只能获取公有和父类中公有
Field getDeclaredField(String s);//获取该类中已声明的成员变量,包括私有
setAccessible(ture);
//如果是私有字段,要先将该私有字段进行取消权限检查的能力。也称暴力访问。
set(Object obj, Object value);//将指定对象变量上此Field对象表示的字段设置为指定的新值。
Object get(Object obj);//返回指定对象上Field表示的字段的值。
举例:<span style="font-size:14px;">package day06;
import java.io.ObjectInputStream.GetField;
import java.lang.reflect.Field;
//定义点
public class ReflectPoint {
private int x;
public int y;
public String str1="abc";
public String str2="base";
public String str3="kab";
public ReflectPoint(int x,int y)
{
super();
this.x=x;
this.y=y;
}
public String toString()
{
return str1+":"+str2+":"+str3;
}
//定义一个改变成员变量值的方法
public static void changeStringValue(Object obj) throws Exception
{
Field[] fields = obj.getClass().getFields();
for(Field field :fields)
{
if(field.getType()==String.class)
{
String oldValue=(String) field.get(obj);
String newValue=oldValue.replace('b','c');
field.set(obj, newValue);
}
}
}
public static void main(String[] args) throws Exception {
//建立一个点对象
ReflectPoint pt1=new ReflectPoint(3,5);
//获取成员变量y
Field fieldY=pt1.getClass().getField("y");//这时的fieldY不是5,还没有制定对象,这时的field只是类上的。
//指定对象,获取对象上的y
System.out.println(fieldY.get(pt1));
//获取私有成员变量x,用
Field fieldX=pt1.getClass().getDeclaredField("x");
//暴力反射
fieldX.setAccessible(true);
System.out.println(fieldX.get(pt1));
changeStringValue(pt1);
System.out.println(pt1);
}
}
</span>
2.Method类代表某个类中的一个成员方法
方法:
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对象对应的是一个静态方法
举例:<span style="font-size:14px;">package day06;
import java.lang.reflect.Method;
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
//定义字符串;
String str = "hahh";
//获取charAt方法,通过反射
Method method=String.class.getMethod("charAt", int.class);
//指定对象,执行charAt方法,并打印
System.out.println(method.invoke(str, 1));
}
}
</span>
3.
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“});编译器会做特殊处理,编译时不把参数当做数组看待,也就不会把数组打散成若干参数了。
举例:<span style="font-size:14px;">package day06;
import java.lang.reflect.Method;
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
//定义字符串;
String str = "hahh";
//获取charAt方法,通过反射
Method method=String.class.getMethod("charAt", int.class);
//指定对象,执行charAt方法,并打印
System.out.println(method.invoke(str, 1));
//按1.4版本执行
System.out.println(method.invoke(str, new Object[]{2}));
}
}
</span>
<span style="font-size:14px;">package day06;
import java.lang.reflect.Method;
import java.util.Arrays;
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
/* // 定义字符串;
String str = "hahh";
// 获取charAt方法,通过反射
Method method = String.class.getMethod("charAt", int.class);
// 指定对象,执行charAt方法,并打印
System.out.println(method.invoke(str, 1));
// 按1.4版本执行
System.out.println(method.invoke(str, new Object[] { 2 }));*/
//TestArguements.main(new String[] { "111", "222", "333" });
// 之所以用反射是因为不知道类的名字,所以定义传进来的第一个参数为类的名字
String startingClassName=args[0];
//System.out.println(args[0]);
Method mainMethod =Class.forName(startingClassName).getMethod("main", String[].class);
//方法一:转化为Object,不用拆包了
mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
//将数组拆包,拆开后当做是一个参数
//mainMethod.invoke(null, new Object[] {new String[]{"111","222","333"}});
}
}
class TestArguements {
public static void main(String[] args) {
System.out.println(Arrays.toString(args));
}
}</span>
四、数组的反射
数组的反射:具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当作Object[]类型使用。
举例:
<span style="font-size:14px;">package day06;
import java.lang.reflect.Array;
import java.util.Arrays;
public class ReflectDemo4 {
public static void main(String[] args) {
int[] a1={1,2,3};
int[] a2=new int[3];
int[][] a3=new int[2][3];
String[] a4 = new String[]{"a","b","c"};
//相同维度、形同元素类型的数组属于同一类型
System.out.println(a1.getClass()==a2.getClass());
System.out.println(a1.getClass().equals(a3.getClass()));
System.out.println(a1.getClass().equals(a4.getClass()));
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());
Object obj1=a1;
Object obj2=a4;这样可以,因为String数组中的元素属于Object
//Object[] obj3=a1;//这样是不行的,因为a1中的元素是int类型,基本数据类型不是Object
/* Arrays.asList()方法处理int[]和String[]时的差异。
*打印Arrays.asList(a1);还是跟直接打印a1是一样的
打印Arrays.asList(a4);就会把a3的元素打印出来。
这是因为此方法在JDK1.4版本中,接收的Object类型的数组,
而a3可以作为Object数组传入。但是a1不可以作为Object数组传入,所以只能按照JDK1.5版本来处理。
在JDK1.5版本中,传入的是一个可变参数,所以a1就被当作是一个object,也就是一个参数,
而不是数组传入,所以打印的结果还是跟直接打印a1一样。
*/
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a4));
printObject(a1);
printObject(a4);
printObject("xyz");
}
private static void printObject(Object obj) {
//判断是否是数组
if(obj.getClass().isArray())
{
for(int i =0;i<Array.getLength(obj);i++)
{
System.out.println(Array.get(obj, i));
}
}
else
System.out.println(obj);
}
}
</span>
五、反射的作用--------->实现框架功能
1.框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门和空调,我做的房子就是框架,用户要使用我的框架,把门窗插入进我提供的框架中。框架与工具类具有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
2.框架要解决的核心问题
我咋写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢,我写的框架程序要怎样能调用到你以后写的类(门窗)呢?
因为在写程序时无法知道被调用的类名,所以,在程序中无法直接new某个类的示例对象了,而要用反射方式来做。
3.综合案例:先直接用new语句创建ArrayList和HashSet的示例对象,演示用Eclipse自动生成ReflectPoint类的equals和hashCode()方法,比较两个集合的运行结果差异。
然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果的差异。
举例:
<span style="font-size:14px;">package day06;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Properties;
public class ReflectDemo5 {
public static void main(String[] args) throws Exception {
//创建相关文件的流
InputStream in = new FileInputStream("config.properties");
//创建properties对象
Properties properties = new Properties();
//加载流
properties.load(in);
in.close();
// 获取类名
String className=properties.getProperty("className");
// 加载并创建类的实例对象
Collection collection = (Collection) Class.forName(className).newInstance();
Point pt1=new Point(1, 2);
Point pt2=new Point(3, 4);
Point pt3=new Point(5, 6);
Point pt4=new Point(1, 2);
collection.add(pt1);
collection.add(pt2);
collection.add(pt3);
collection.add(pt4);
System.out.println(collection.size());
}
}
class Point
{
int x;
int y;
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
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;
}
}
</span>
打印结果: 当配置文件中的配置为:className = java.util.HashSet 时结果为: 3
当配置文件中的配置为: className = java.util.ArrayList 时结果为: 4
注意:建立配置文件应该在工程名字上建立,否则找不到配置文件。