一、什么是反射
1、编译和运行
在了解什么是Java反射机制前,先聊聊Java的编译和运行。
还记得第一次使用记事本编写第一个Java程序的场景吗?通过命令窗口,使用javac命令编译一个.java文件,生成一个.class文件,再使用java命令运行.class文件。
在编译期,jvm会去检查.java文件中使用的类、类的属性和方法、类的对象等信息,如果都能找到对应的信息,则编译通过,否则会编译报错或抛出异常信息。
在运行期,会根据.class文件信息,处理其中的代码逻辑,如果遇到程序运行错误会抛出异常信息。
下面我们使用一个小例子来说明编译和运行。新建一个Animal类,根据传入的参数不同,创建不同的对象,执行不同的方法。
public class Animal{
public static void main(String[] args) {
if("Cat".equals(args[0])){
Cat cat = new Cat();
cat.eat();
}
if("Dog".equals(args[0])){
Dog dog = new Dog();
dog.eat();
}
}
}
使用cmd命令编译时,会报错,找不到Cat类和Dog类。
E:\Java项目实战>javac Animal.java
Animal.java:6: 错误: 找不到符号
Cat cat = new Cat();
^
符号: 类 Cat
位置: 类 Animal
Animal.java:6: 错误: 找不到符号
Cat cat = new Cat();
^
符号: 类 Cat
位置: 类 Animal
Animal.java:11: 错误: 找不到符号
Dog dog = new Dog();
^
符号: 类 Dog
位置: 类 Animal
Animal.java:11: 错误: 找不到符号
Dog dog = new Dog();
^
符号: 类 Dog
位置: 类 Animal
4 个错误
这时我们新建一个Cat类,编译Cat类后,再次编译Animal类,又报了两个错误,找不到Dog类。
Animal.java:11: 错误: 找不到符号
Dog dog = new Dog();
^
符号: 类 Dog
位置: 类 Animal
Animal.java:11: 错误: 找不到符号
Dog dog = new Dog();
^
符号: 类 Dog
位置: 类 Animal
如果我们想要使用猫类,而不管狗类是否存在,那该怎么做?那就是绕过编译期,在运行期动态执行猫类的方法。怎样绕过编译期呢?这就需要使用Java的反射机制了。
2、反射的概念
反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法。这种动态获取信息以及动态调用对象方法的功能,我们称之为Java语言的反射机制。
继续上面遗留的问题,在不新建一个Dog.java文件时,怎样调到Cat类的eat方法?
首先,我们要根据传入的参数动态加载类,然后创建类的实例对象,最后执行类的方法。因为你无法确定传入的参数是猫类还是狗类,就需要定义一个超类或接口,让猫类继承超类或实现接口,重载或重写eat()方法,在创建类实例时,只用创建超类或接口的实例即可。
public class AnimalBetter {
public static void main(String[] args) {
try {
Class c1 = Class.forName(args[0]);
IAnimal animal = (IAnimal)c1.newInstance();
animal.eat();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里我们选用的是接口
public interface IAnimal {
public void eat();
}
猫类的实现
public class Cat implements IAnimal {
public void eat() {
System.out.println("Cat is eatting");
}
}
二、反射的优缺点
使用反射,可以在代码改动最小的情况下有效扩展功能。就像上面的小例子,如果哪天我们想使用老虎类进行eat动作,我们还需要改动AnimalBetter类的代码吗?答案是不需要,只需要新建一个老虎类,实现IAnimal的接口,重写eat方法,就可以了,对其他既有代码无需做任何改动。
使用反射,可以动态的创建类实例和使用类的方法,可以分析类文件,可以访问类的私有属性或方法,对于一些需要在运行时处理的事情都可以交给反射实现。
既然反射这么好,为什么不在项目中大范围的使用?其实,反射也是有弊端的。反射包括了一些动态类型,jvm无法对这些代码进行优化,实际操作效率低于非反射操作,在那些经常被执行的代码或对性能要求较高的模块尽量少使用反射。反射在程序有安全限制的环境中是无法使用的。反射可以访问私有的属性或方法,可能会破坏封装性,产生意料之外的结果。
三、如何使用反射
1、使用过程
首先要获取操作的类的Class对象,然后通过Class类中的方法获取和查看该类中的方法和属性,最后使用反射API来操作这些信息。
2、获取要操作的类的Class对象
有三种方式获取类的Class对象
(1)已经知道该类的类名
Class c2 = Animal.class;
实际上,任何一个类都有一个隐含的静态成员变量class,并且任何一个类都是Class类的实例对象,这个对象我们称为该类的类类型。
(2)已经知道该类的对象,通过getClass方法获取
Animal animal = new Animal();
Class c3 = animal.getClass();
不管是上面的c2还是这里的c3,都是Animal类的类类型,一个类只可能是Class类的一个实例对象或一个类类型。
(3)使用Class类中的forName()静态方法
Class c4 = Class.forName("Animal"); //forName(String str)中参数为类的全路径(包名+类名)
3、获取类的属性和方法
我们将其编写为一个工具类,如下:
/**
* 获取类的属性和方法
* @author 小川94
* @date 2018年6月24日
*/
public class ClassUtil {
/**
* 获取类的成员变量信息
* @param obj 要操作的类
*/
public static void getClassFieldInfo(Object obj){
// 获取要操作的类的Class对象
Class c = obj.getClass();
// 获取该类的成员变量信息
// field类是java.lang.reflect包下的类,封装了关于类成员变量的操作
Field[] fs = c.getFields(); // 获取的是所有的public的成员变量的信息
Field[] fs2 = c.getDeclaredFields(); // 获取的是该类自己声明的成员变量的信息
// 使用foreach遍历成员变量
for (Field field : fs2) {
// 得到成员变量的类型的类类型
Class fieldType = field.getType();
// 获取类类型的名称
String typeName = fieldType.getName();
// 获取成员变量的名称
String fieldName = field.getName();
System.out.println(typeName + " " + fieldName);
}
}
/**
* 获取类的方法信息
* @param obj 要操作的类
*/
public static void getClassMethodInfo(Object obj){
// 获取要操作的类的Class对象
Class c = obj.getClass();
// 获取要操作的类的名称
System.out.println("类的名称是:" + c.getName());
// 获取该类的方法信息
// Method类是java.lang.reflect包下的类,封装了关于类方法的操作
Method[] ms = c.getMethods(); //获取的是所有的public的函数,包括从父类继承而来的
Method[] ms2 = c.getDeclaredMethods(); // 获取的是所有该类自己声明的方法,不限访问权限
// 遍历方法
for (Method method : ms2) {
// 得到方法的返回值类型的类类型
Class returnType = method.getReturnType();
System.out.print(returnType.getName() + " ");
// 获取方法名
System.out.print(method.getName() + " (");
// 获取参数类型,得到的是参数列表的类型的类类型
Class[] para = method.getParameterTypes();
for (Class class1 : para) {
System.out.print(class1.getName() + ",");
}
System.out.println(")");
}
}
/**
* 获取类的构造方法
* @param obj 要操作的类
*/
public static void getClassConstructorMethodInfo(Object obj){
// 获取要操作的类的Class对象
Class c = obj.getClass();
// 获取该类的构造方法信息
// constructor类是java.lang.reflect包下的类,封装了关于类的构造方法的操作
Constructor[] cs = c.getConstructors(); // 获取所有的public的构造函数
Constructor[] cs2 = c.getDeclaredConstructors(); //得到所有的构造函数
for (Constructor constructor : cs2) {
System.out.print(constructor.getName()+"(");
// 获取构造函数的参数列表,得到的是参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}
}
编写测试类,如下:
public class Test {
public static void main(String[] args) {
System.out.println("----- 成员变量信息 -----");
ClassUtil.getClassFieldInfo(new Integer(2));
System.out.println("----- 构造方法信息 -----");
ClassUtil.getClassConstructorMethodInfo(new Integer(2));
System.out.println("----- 方法信息 -----");
ClassUtil.getClassMethodInfo(new Integer(2));
}
}
测试结果如下,大家可以将测试结果与Integer这个类做比较。
----- 成员变量信息 -----
int MIN_VALUE
int MAX_VALUE
java.lang.Class TYPE
[C digits
[C DigitTens
[C DigitOnes
[I sizeTable
int value
int SIZE
int BYTES
long serialVersionUID
----- 构造方法信息 -----
java.lang.Integer(int,)
java.lang.Integer(java.lang.String,)
----- 方法信息 -----
类的名称是:java.lang.Integer
int numberOfLeadingZeros (int,)
int numberOfTrailingZeros (int,)
int bitCount (int,)
boolean equals (java.lang.Object,)
java.lang.String toString (int,int,)
java.lang.String toString ()
java.lang.String toString (int,)
int hashCode (int,)
int hashCode ()
int min (int,int,)
int max (int,int,)
int reverseBytes (int,)
int compareTo (java.lang.Integer,)
int compareTo (java.lang.Object,)
byte byteValue ()
short shortValue ()
int intValue ()
long longValue ()
float floatValue ()
double doubleValue ()
java.lang.Integer valueOf (java.lang.String,int,)
java.lang.Integer valueOf (int,)
java.lang.Integer valueOf (java.lang.String,)
java.lang.String toHexString (int,)
int compare (int,int,)
java.lang.Integer decode (java.lang.String,)
void getChars (int,int,[C,)
int reverse (int,)
int stringSize (int,)
int sum (int,int,)
int parseInt (java.lang.String,)
int parseInt (java.lang.String,int,)
long toUnsignedLong (int,)
int compareUnsigned (int,int,)
int divideUnsigned (int,int,)
int formatUnsignedInt (int,int,[C,int,int,)
java.lang.Integer getInteger (java.lang.String,java.lang.Integer,)
java.lang.Integer getInteger (java.lang.String,int,)
java.lang.Integer getInteger (java.lang.String,)
int highestOneBit (int,)
int lowestOneBit (int,)
int parseUnsignedInt (java.lang.String,)
int parseUnsignedInt (java.lang.String,int,)
int remainderUnsigned (int,int,)
int rotateLeft (int,int,)
int rotateRight (int,int,)
int signum (int,)
java.lang.String toBinaryString (int,)
java.lang.String toOctalString (int,)
java.lang.String toUnsignedString (int,)
java.lang.String toUnsignedString (int,int,)
java.lang.String toUnsignedString0 (int,int,)
4、使用反射API来操作类
public class MethodDemo {
public static void main(String[] args) {
// 第一种写法
try {
// 获取要操作的类的Class对象
Class c = Class.forName("sessionone.Foo");
// 获取print方法
Method m = c.getMethod("print", String.class);
// 第二种写法,new Class[]{}里面可以接多个参数,也可以为空
Method m2 = c.getMethod("print", new Class[]{String.class});
// 第三种写法,获取该类自己声明的方法
Method m3 = c.getDeclaredMethod("print", String.class);
// 执行方法
m3.invoke(c.newInstance(), "小川94");
} catch (Exception e) {
e.printStackTrace();
}
// 第二种写法
Foo foo = new Foo();
foo.print("小川94");
// 第三种写法
try {
Class c2 = Class.forName("sessionone.Foo");
Foo foo2 = (Foo) c2.newInstance();
foo2.print("小川94");
} catch (Exception e) {
e.printStackTrace();
}
// 第四种写法
try {
Foo foo3 = new Foo();
Class c3 = Class.forName("sessionone.Foo");
Method method = c3.getDeclaredMethod("print", String.class);
method.invoke(foo3, "小川94");
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Foo{
public void print(String name){
System.out.println("Hello," + name);
}
}
四、反射的实际应用
1、在JDBC中的使用
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
在装载驱动时,使用了反射,在getConnection的源代码中,使用了反射,整个JDBC都和反射有关联。
2、在集合泛型中的使用
ArrayList list = new ArrayList();
ArrayList<String> list2 = new ArrayList<String>();
System.out.println(list.equals(list2)); //true
Class c = list.getClass();
System.out.println("list的类类型名称:"+c.getName());
Class c2 = list2.getClass();
System.out.println("list2的类类型名称:"+c2.getName());
System.out.println(c == c2); //true
// c1==c2结果返回true,说明编译之后集合的泛型是去泛型化的
// Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了
// list2.add(20); 这样写,是会编译报错
try {
System.out.println("list2中有"+list2.size()+"个元素");
Method m = c2.getMethod("add", Object.class);
m.invoke(list2, 20);
System.out.println("list2中有"+list2.size()+"个元素");
System.out.println(list2.toString()); //[20],已经将类型是整型的数添加到list2中
} catch (Exception e) {
e.printStackTrace();
}
集合中的泛型只是为了防止编译错误,在运行时会将泛型全部清除。
3、在Servlet中的使用
在之前的servlet系列文章中,使用了反射来完成了一个Servlet处理多个请求,具体可以看那篇文章的详细描述。
4、在框架中的使用
在主流框架中都有反射的影子,通过配置、注解完成一些需要静态加载类的方法转为使用反射来动态加载类,具体的会在讲解到框架时细讲。
Java反射机制小结和实际操作的更多相关文章
-
有关java反射机制 小结
111111111111 Class<?> cls = Class.forName("包名.类名"); Object o = cls.newInstance(); 22 ...
-
Java反射机制能够获取的信息,与应用
一.什么是Java反射机制? [1]反射机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法: [2]对于任意一个对象,都能够调用它的任意一个属性和方法: 像这种动态获取类的信息以及动 ...
-
Java反射机制demo(六)—获得并操作一个类的属性
Java反射机制demo(六)—获得并操作一个类的属性 获得并操作一个类的属性?! 不可思议啊,一个类的属性一般都是私有成员变量啊,private修饰符啊! 但是毫无疑问,这些东西在Java的反射机制 ...
-
Java进阶(六)Java反射机制可恶问题NoSuchFieldException
作为一种重要特性,Java反射机制在很多地方会用到.在此做一小结,供朋友们参考. 首先从一个问题开始着手. 可恶的问题又来了,NoSuchFieldException,如下图所示: 完全不知道这个qu ...
-
第28章 java反射机制
java反射机制 1.类加载机制 1.1.jvm和类 运行Java程序:java 带有main方法的类名 之后java会启动jvm,并加载字节码(字节码就是一个类在内存空间的状态) 当调用java命令 ...
-
java基础知识(十一)java反射机制(上)
java.lang.Class类详解 java Class类详解 一.class类 Class类是java语言定义的特定类的实现,在java中每个类都有一个相应的Class对象,以便java程序运行时 ...
-
java基础知识(十一)java反射机制(下)
1.什么是反射机制? java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用他的属性和方法,这种动态获取属性和方法的功能称为java的反射机制. ...
-
Java反射机制专题
·Java Reflection Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方 ...
-
java反射机制深入详解
java反射机制深入详解 转自:http://www.cnblogs.com/hxsyl/archive/2013/03/23/2977593.html 一.概念 反射就是把Java的各种成分映射成 ...
随机推荐
-
WPF文章资源库
MUHAMMAD SHUJAAT SIDDIQI
-
CRC校验(转)
CRC即循环冗余校验码(Cyclic Redundancy Check[1] ):是数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以任意选定.循环冗余检查(CRC)是一种数据 ...
-
poj 1007 (nyoj 160) DNA Sorting
点击打开链接 DNA Sorting Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 75164 Accepted: 30 ...
-
【Android界面实现】使用Canvas对象实现“刮刮乐”效果
在淘宝.京东等电商举办活动的时候,常常能够看到在移动client推出的各种刮奖活动,而这样的活动也受到了非常多人的喜爱.从client的体验来说,这样的效果应该是通过网页来实现的,那么,我们使用And ...
-
关于子线程更新UI
大家都了解的子线程不能更新UI,所以普通青年比方我,遇到耗时操作用到线程时.不得不立刻想到了用handler传递来解决UI更细的问题. 普通青年的做法: 方案:使用Thread+handler方式,h ...
- JVM之对象的创建简要流程
-
04 SimpleAdapter
<span style="font-size:18px;">package com.fmyboke; import java.util.ArrayList; impor ...
-
[T-ARA][내가 너무 아파][我很痛]
歌词来源:http://music.163.com/#/song?id=5402882 作曲 : 新沙洞老虎/崔圭成 [作曲 : 新沙洞老虎/崔圭成] 作词 : 新沙洞老虎/崔圭成 [作词 : 新沙洞 ...
-
Android布局属性说明
Android布局LinearLayout注意设置属性android:orientation属性,否则有的组件可能无法显示. 该属性不设置时默认为horizontal.此时第一个控件的宽度若设置成“f ...
-
ios开发之--PDF文件生成
写项目的时候,碰到一个需求,就是在手机端根据指定的文件内容生成PDF文件,并可以保存到手机上,因为以前只是听说过,没有真正的去了解过这个需求,通过查阅资料,可以实现这个功能,话不多说,代码如下: -( ...