很多时候,java程序运行中,我们需要在运行时了解类的信息,得到类的实例,并且进而继续得到类的方法,构造函数,权限,变量以及其他信息。这时候我们需要用到一门技术,java反射
反射说白了,就是把我们的一些文件,一些字符串,一些地址上具体的配置信息,能够把他们动态的在运行期实例化,并且我们能够操作这些实例。这就是反射。
以下几段话是我学习Android的思考,只是想学习反射的同学可以直接跳到Class的使用
我学习Android开发的时候,我就在想,我们在layout文件下写的xml布局文件,他是文本文件,是一些配置信息吧。既然万事万物皆对象,那我们的手机界面应该是一个类。
xml布局文件有LinearLayout布局,那是不是有一个LinearLayout类?我在xml布局文件那里配置
android:layout_width="match_parent"
android:layout_height="match_parent"
就能让这个LinearLayout的宽高匹配父布局。那么,如果真的有LinearLayout类的话,会不会有个方法类似于setWidth()让我设置宽高?事实上,有LinearLayout类,提供了setLayoutParams()方法让我们设置布局参数,当然这个方法能设置宽高
那,我们在xml布局文件写的配置信息,在手机界面上能够显示那么丰富的效果,有按钮,有文本,有列表。这是怎么做到的?其实,这就是用到反射,把xml布局文件写的配置信息,动态的实例化,比如Button按钮,TextView文本,在程序运行时刻全都动态实例化。如果没有反射,我们就无法使用(调用)xml布局文件,无法使用到xml布局文件的话,那Android系统怎么加载丰富多彩的界面?
同样的,AndroidManifest文件里配置了很多Activity,都是通过反射来实例化。如果没有反射,也无法使用AndroidManifest里的配置信息。当然也就没有了Android框架了。
Java反射那么重要,我们需要理解了才能够去使用他。那我们就来学习Java反射这门技术
在面向对象的世界里,万事万物皆对象。
在java的世界里,有两样东西不是面向对象的。基本的数据类型和静态(static)的东西。
比如,int a = 5,他不是面向对象,但是他有包装类(Integer)或者说是封装类,弥补了他。Int a = 5;是直接在栈空间中分配内存。而Integer a = new Integer(5),对象在堆内存中,引用变量a在栈内存。像这样的普通数据类型还好几个,比如boolean与Boolean,char和Character,double与Double等等,他们都不是面向对象
还有是就是java静态的东西,他不属于某个对象,是属于类的
现在,除去以上不是面向对象的。
那么我们写的类,类是不是对象呢?类是谁的对象呢?类是哪个类的对象呢?
我们写的每一个类它也是对象,类是对象,类是java.lang.Class类的实例对象。
我写的一个Student类,他是不是对象,是一个对象。谁的对象?Class类的实例对象
public class Student {
}
注意,我们上面的程序class,c是小写,是关键字。在声明java类时使用。而我们说的java.lang.Class类的C是大写,他是一个类,因为我们定义一个类,类名的首字母是大写的呀。也可以这么理解:
现在有一个类,他的名字就叫Class。java.lang.Class类
Class类能不能new出实例对象呢?这个对象到底如何表示呢?
我们按住ctrl+鼠标左键,追踪Class这个类,发现他的构造方法是私有的,还给出了注释,只有java虚拟机才能创建Class对象,如下所示:
/*
* Constructor. Only the Java Virtual Machine creates Class
* objects.
*/
private Class() {}
既然不能通过调用构造方法,new出实例,那么Class类的实例对象如何表示呢?
接下来我们来学习Class类的使用
我们打开eclipse先新建一个Student类
public class Student {
public void introduce(){
System.out.println("我是一名大三学生");
}
}
在新建一个demo,我们很简单的将Student类的实例对象表示出来
public class ClassDemo {
public static void main(String[] args){
//student1表示Student的实例对象
Student student1 = new Student();
}
}
那么我们说到,Student这个类,也是一个实例对象,是Class类的实例对象
——》紧接上面提到的问题:Class类的实例对象如何表示呢?
任何一个类都是Class类的实例对象(这句话可以心里念三遍)。这个实例对象有三种表示方式:
第一种表示方式 —>通过类名调用静态成员变量class。 类名.class
Class class1 = Student.class;
这实际在告诉我们任何一个类都有一个隐含的静态成员变量class
第二种表示方式 —> 已经知道该类的对象,通过调用getClass方法。object.getClass()
Class class2 = student1.getClass();
第三种表达方式 —> Class类调用静态方法forName(“类的全称”)。
Class.forName(“类的全称”)
Class class3 = null;
try {
class3 = Class.forName("com.xyh.reflect.Student");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
万一我们找不到这个类呢?这种方法会抛出ClassNotFoundException异常
好,现在我们已经将Class类的实例对象表示出来了。
class1, class2,class3 表示了Student类的类类型(class type)。
这里的三种表示方式,类名.class,object.getClass(),Class.forName(“类的全称”),是指哪个类调用.class返回的就是哪个类的类类型,哪个对象调用getClass()返回的就是这个对象所属类的类类型,Class.forName()同理
我是这么理解类类型的,类类型是某个类的另一种表示方式,另一种类型。他们大有不同,却又息息相关。(这有点绕口)
下面我们会学到用类类型来创建对象,如:通过class1对象调用newInstance()方法,需要进行一下强转
Student student2 = (Student) class1.newInstance();
这种通过类类型创建Student类的实例对象,我们也可以通过new Student()来创建对象,两种方式都可以创建对象嘛!所以我把Student的类类型理解成了Student类的另一种表示方式,
类也是对象,是Class类的实例对象
这个Class类的对象,我们称为该类的类类型
我们看到下面这行代码
System.out.println(class1 == class2);
输出:true
结论:不管class1 ,class2都代表了Student类的类类型,一个类只可能是Class类的一个实例对象,也就是唯一,只有一个
我们完全可以通过类的类类型创建该类的实例对象 —>
比如,可以通过class1,class2,class3创建Student类的实例对象
try {
//前提是需要有无参数的构造方法
Student student2 = (Student) class1.newInstance();
//student2调用方法验证一下
student2.introduce();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
输出:true
我是一名大三学生
| 以下才是核心知识点 |
| 以下才是核心知识点 |
| 以下才是核心知识点 |
Class.forName(“类的全称”)
不仅表示了类的类类型,还代表了动态加载类。那么,什么是动态加载类,什么是静态加载类
编译时刻加载类是静态加载类,运行时刻加载类是动态加载类
我一开始写Java程序,是在Notepad++写的,写好后后通过命令行输入javac 类名.java编译,通过java 类名,执行程序。好怀念,后来我使用了eclipse写java,我就在想,为什么这些IDE能够做到:我一写好程序,点击运行就ok,他一定做了很多功能的封装,(比如我一点击运行,eclipse就执行了javac和java命令吧),不管怎样。IDE方便了开发者,而且他检错很好,比如下图
我现在Office类需要使用Word类的实例对象,但是我并没有定义这个Word类
如果我们使用Notepad++就没有那么好了,并没有提示。不过却能让我们更好的理解 动态加载类 这个知识点
好,我们通过Notepad++写一段程序回忆下
public class Office {
public static void main(String[] args){
//new创建对象是静态加载类,在编译时刻就需要加载所有的可能使用到的类
if("Word".equals(args[0])){
Word word = new Word();
word.open();
}
if("Excel".equals(args[0])){
Excel excel = new Excel();
excel.open();
}
}
}
我们知道这样编译肯定会报错的。因为我们现在没有声明Word类和Excel类。此时,我们不禁会想,Word一定用到吗?Excel一定用到吗?
那我们现在声明一个Word类
public class Word{
public void open(){
System.out.println("Word...Open");
}
}
现在只报两个错了,这个程序有什么问题? Word类已经有了,我现在就想用Word,这个程序能跑吗?不能,因为程序一直会报找不到Excel类的错误,如果现在有一百个类,有一个类找不到或者出问题,全部都用不了。而且一百个类,每一个都还需要用if语句去判断
思路:我们这段程序实现的是静态加载。是在编译时加载。
而我们希望写的程序是可以扩展的,我想要用到哪个类就加载哪个类,即在程序运行时刻动态加载。接着我们新建一个OfficeBetter类如下
public class OfficeBetter{
public static void main(String[] args){
try{
Class class1 = Class.forName(args[0]);
Word word = (Word)class1.newInstance();
word.open();
}catch(Exception e){
e.printStackTrace();
}
}
}
Word word = (Word)class1.newInstance();
word.open();
如果我传入的是Excel,PPT呢?那么就无法强转了。还是说我用到哪个类了,在来OfficeBetter这里修改强转的类型??这也太麻烦了吧。所以我们应该提供一个标准,新建一个OfficeAble接口
public interface OfficeAble{
public void open();
}
以后我们想用到哪个类,就让他实现这个接口就行了。优化后的代码如下
public class Word implements OfficeAble{
public void open(){
System.out.println("Word...Open");
}
}
public class OfficeBetter{
public static void main(String[] args){
try{
Class class1 = Class.forName(args[0]);
OfficeAble officeAble = null;
officeAble = (OfficeAble)class1.newInstance();
officeAble.open();
}catch(Exception e){
e.printStackTrace();
}
}
}
如果我们想用到Excel类,那就新建Excel类实现OfficeAble接口
public class Excel implements OfficeAble{
public void open(){
System.out.println("Excel...Open");
}
}
呐,我想用到哪个类,我就写一个类去实现OfficeAble这个标准就行了,如图PPT类我是没有去写的,传进去参数才会报错。而那个OfficeBetter程序我更本不用去修改它
功能性的类一般使用动态加载
总结:到这里,我们已经学习了Class类的使用,和动态加载类的知识点,我们需要明白什么是类类型(class type)。
以上的内容我们只是通过类类型来创建该类的实例对象—>类类型.newInstance()
但是,类类型还能够做很多很多事情。
要获取类的信息,首先要获取类的类类型,(class type)
我们通过eclipse的代码提示,可以看到,类类型很多get方法
在这里我们可以封装一个工具类,去获取类的信息,就叫ClassUtil吧
package com.xyh.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 获取类的信息的工具类
*
*/
public class ClassUtil {
/**
* 以后在任意情况下,我们想获取一个类的信息,首先要获取到这个类的类类型
*/
/**
*
* 打印类的成员函数信息
* @param object
*/
public static void printMethodMessage(Object object){
//获取到该类的类类型
Class class1 = object.getClass();//Object类是一切类的父类,传递的是哪个子类的对象,class1就是该子类的类类型
//获取类的名称
System.out.println("类的名称是:"+class1.getName());
/**
* 方法也是对象,Method类的对象
* 一个成员方法就是一个Method对象
* getMethod()方法获取的是所有的public函数,包括父类继承而来的
* getDeclaredMethods()获取的是所有该类自己声明的方法,不问访问权限
*
*/
Method[] methods = class1.getMethods();
for(int i = 0 ;i //得到方法的返回值类型的类类型
Class returnType = methods[i].getReturnType();
System.out.print(returnType.getName()+" ");
//得到方法的名称
System.out.print(methods[i].getName()+"(");
//获取参数类型 ->得到的是参数列表的类型的类类型
Class[] paramTypes = methods[i].getParameterTypes();
for (Class class2 : paramTypes) {
System.out.print(class2.getName()+",");
}
System.out.println(")");
}
}
/**
* 打印类的成员变量的信息
* @param object
*/
public static void printFieldMessage(Object object) {
/**
* 成员变量也是对象
* java.lang.reflect.Field
* Field类封装了关于成员变量的操作
* getFields()方法获取的是所有的public的成员变量的信息
* getDeckaredFields()获取的是该类自己声明的成员变量的信息
*/
//Field[] fields = class1.getFields();
Class class1 = object.getClass();
Field[] fields = class1.getDeclaredFields();
for(Field field :fields){
//得到成员变量的类型的类类型
Class fieldType = field.getType();
String typeName = fieldType.getName();
//得到成员变量的名称
String fieldName = field.getName();
System.out.println(typeName+" "+fieldName);
}
}
/**
* 获取对象的构造函数的信息
* @param object
*/
public static void printConstructorMessage(Object object){
Class class1 = object.getClass();
/**
* 构造函数也是对象,
* java.lang.Constructor中封装了构造函数的信息
* getConstructors()获取所有的public的构造函数
* getDeclaredConstructors()获取所有的构造函数
*
*/
Constructor[] constructors = class1.getDeclaredConstructors();
for(Constructor constructor : constructors){
System.out.print(constructor.getName()+"(");
//获取构造函数的参数列表->得到的是参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();
for (Class class2 : paramTypes) {
System.out.print(class2.getName()+",");
}
System.out.println(")");
}
}
}
这里直接贴出了代码,注释也写的比较详细。还需要补充的是,ClassUtil工具类是欢迎大家去丰满他的,现在只能打印一些类的信息,但是我们开头说到的,反射可以在运行时实例化,获取到类的信息,并能操作这些实例,既然是操作,我们可以看到下图:
如果我们能得到Constructor构造函数对象,他有很多方法,是可以做很多事的。其他成员函数,成员变量亦同理。
最后一个知识点。
方法的反射操作是用method对象来进行方法调用。一般是这样的
method.invoke(对象,参数列表)
我们写一个Computer类
public class Computer {
public void plus(int a,int b){
System.out.println(a+b);
}
public void plus(String a,String b){
System.out.println(a.toUpperCase()+","+b.toLowerCase());
}
}
目标:我们要通过方法的反射操作来获取到plus方法
新建一个MethodDemo类,
package com.xyh.reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodDemo {
public static void main(String[] args){
/**
* 方法是类的信息,要获取一个方法,就是获取类的信息,获取类的信息首先要获取这个类的类类型
*
*/
Computer computer = new Computer();
Class class1 = computer.getClass();
/**
* 获取方法,名称和参数列表来决定
* getMethod获取的是public的方法
* getDelcaredMethod获取自己声明的方法
*/
try {
Method method = class1.getMethod("plus",new Class[]{int.class,int.class});
//方法的反射操作是用method对象来进行方法调用,和computer.plus(10, 20)调用的效果是一样的
/**
* 其实可以这么理解,这个plus现在变成了方法对象了,那么就是computer这个对象操作plus这个对象
* 同样,我们也可以反过来操作,用plus这个方法所代表的对象method,来操作computer这个对象
*
* 用plus这个方法对象。那么plus方法对象是谁?
* 是method。来操作computer
*/
Object object = method.invoke(computer, new Object[]{10,20});
//Object object = method.invoke(computer, 10,20);
Method method2 = class1.getMethod("plus", String.class,String.class);
method2.invoke(computer,"hello","world");
/**
* 无参数
* Method method3 = class1.getMethod("plus");
* method3.invoke(computer);
*/
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
最后需要补充的是,Class的getMethod方法和getMethods方法是不一样的
后者getMethods我们在ClassUtil工具类里用到过,返回的是一个数组,需要遍历每一个方法对象。
而getMethod方法,第一个参数传的是方法名,第二个参数是参数列表,是一个数组
方法名称和方法的参数列表才能唯一决定某个方法