一、什么是反射

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反射机制小结和实际操作的更多相关文章

  1. 有关java反射机制 小结

    111111111111 Class<?> cls = Class.forName("包名.类名"); Object o = cls.newInstance(); 22 ...

  2. Java反射机制能够获取的信息,与应用

    一.什么是Java反射机制? [1]反射机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法: [2]对于任意一个对象,都能够调用它的任意一个属性和方法: 像这种动态获取类的信息以及动 ...

  3. Java反射机制demo(六)—获得并操作一个类的属性

    Java反射机制demo(六)—获得并操作一个类的属性 获得并操作一个类的属性?! 不可思议啊,一个类的属性一般都是私有成员变量啊,private修饰符啊! 但是毫无疑问,这些东西在Java的反射机制 ...

  4. Java进阶(六)Java反射机制可恶问题NoSuchFieldException

    作为一种重要特性,Java反射机制在很多地方会用到.在此做一小结,供朋友们参考. 首先从一个问题开始着手. 可恶的问题又来了,NoSuchFieldException,如下图所示: 完全不知道这个qu ...

  5. 第28章 java反射机制

    java反射机制 1.类加载机制 1.1.jvm和类 运行Java程序:java 带有main方法的类名 之后java会启动jvm,并加载字节码(字节码就是一个类在内存空间的状态) 当调用java命令 ...

  6. java基础知识(十一)java反射机制(上)

    java.lang.Class类详解 java Class类详解 一.class类 Class类是java语言定义的特定类的实现,在java中每个类都有一个相应的Class对象,以便java程序运行时 ...

  7. java基础知识(十一)java反射机制(下)

    1.什么是反射机制? java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用他的属性和方法,这种动态获取属性和方法的功能称为java的反射机制. ...

  8. Java反射机制专题

    ·Java Reflection Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方 ...

  9. java反射机制深入详解

    java反射机制深入详解  转自:http://www.cnblogs.com/hxsyl/archive/2013/03/23/2977593.html 一.概念 反射就是把Java的各种成分映射成 ...

随机推荐

  1. 伪指令 ADR 与 LDR 的区别

    指令简介: adr r0, _start 得到的是 _start 的当前执行位置,由 pc+offset 决定 ldr r0, =_start 得到的是绝对的地址,链接时决定 程序示例: ldr r0 ...

  2. centos7部署DNS-1

    文章索引: 一.服务相关介绍 二.实验:搭建正向主DNS服务器 三.实验:搭建反向解析服务器 四.实验:泛域名解析,如wwww.baidu.com也可以正常访问 环境 服务器 节点名称 IP地址 dn ...

  3. Spring Cloud Finchley版中Consul多实例注册的问题处理

    由于Spring Cloud对Etcd的支持一直没能从孵化器中出来,所以目前来说大多用户还在使用Eureka和Consul,之前又因为Eureka 2.0不在开源的消息,外加一些博眼球的标题党媒体使得 ...

  4. Python爬虫之使用Fiddler+Postman+Python的requests模块爬取各国国旗

    介绍   本篇博客将会介绍一个Python爬虫,用来爬取各个国家的国旗,主要的目标是为了展示如何在Python的requests模块中使用POST方法来爬取网页内容.   为了知道POST方法所需要传 ...

  5. [angularjs] angularjs系列笔记(四)控制器

    Scope作用域 Scope作用域是应用在HTML视图和Js控制器之间的纽带 Scope是一个对象,有可用的属性和方法 根作用域 所有的应用都有一个$rootScope,它可以作用在ng-app指令包 ...

  6. devtools进行热部署

    热部署的形式这里只介绍一种devtools devtools可以实现页面热部署(即页面修改后会立即生效,这个可以直接在application.properties文件中配置spring.thymele ...

  7. js 数据类型具体分析

            复习 点运算符 xxx.sss xxx是对象  sss是属性和方法.任何数据类型都是拥有属性和方法的.字符串 String var st=“hello world”.字符串的定义    ...

  8. PHP常用函数归类【持续整理中......】

    一.PHP基础语法   变量,常量     严格区分大小写,但内置结构或关键字无所谓(echo)     命名:不能以数字,空格,.来开头,但是可以有汉字,eg:$变量="aa"; ...

  9. Bootstrap 按钮颜色

    上述按钮CSS规则 .btn { display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-w ...

  10. iphone屏幕镜像怎么用 手机投屏电脑

    手机看视频有的时候总会感觉到累,屏幕太小看的不够爽又或者用手一直拿着手机看累得慌.我就就喜欢看电视因为电视屏幕大看的爽,而且现在很多手机视频都可以往电视上投影视频,那么iphone屏幕镜像怎么用? 使 ...