一、什么是反射

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. go基础系列:结构struct

    Go语言不是一门面向对象的语言,没有对象和继承,也没有面向对象的多态.重写相关特性. Go所拥有的是数据结构,它可以关联方法.Go也支持简单但高效的组合(Composition),请搜索面向对象和组合 ...

  2. Runtime详解(下)

    Runtime应用 1.Runtime 交换方法 应用场景:当第三方框架或者系统原生方法功能不能满足我们的时候,我们可以在保持系统原有功能的基础上,添加额外的功能. 需求:加载一张图片直接用系统的[U ...

  3. VB.NET获取系统特殊目录

    For Each x In GetType(System.Environment.SpecialFolder).GetEnumValues Debug.Print("{0} {1}" ...

  4. 从零开始学安全(十三)●SQL server 2008 R2 安装

    安装过程1.下载并解压 sql_server_2008_r2_enterprise 点击 setup . 2.打开后如图,点击左侧的 安装 ,再点击右边的 全新安装或向现有安装添加功能. 3.安装支持 ...

  5. T-SQL :编程理论 (一)

    SQL代表结构化查询语言,是一种在关系数据库系统中查询和管理数据的标准语言.sql语句也有几个类别,包括定义语言(DDL),数据操作语言(DML),数据控制语言(DCL). DDL包括create,a ...

  6. 49.Linux-wpa_cli使用之WIFI开启,扫描热点,连接热点,断开热点,WIFI关闭(49)

    本章学习内容: 1.WIFI如何开启 2.扫描热点 3.连接热点 4. 断开热点 5.关闭WIFI 本节使用的是wpa_supplicant工具,它主要包含wpa_supplicant(命令行模式)与 ...

  7. Yii2基本概念之——事件(Event)

    说起事件(event),我们可是一点都不陌生.现实生活当中的事件无处不在,比如你发了一条微博,触发了一条事件,导致关注你的人收到了一条消息,看到你发的内容:比如你通过支付宝买东西,付了款,触发一个事件 ...

  8. 谈谈 final finally finalize 区别

    声明 本篇所涉及的提问,正文的知识点,全都来自于杨晓峰的<Java核心技术36讲>,当然,我并不会全文照搬过来,毕竟这是付费的课程,应该会涉及到侵权之类的问题. 所以,本篇正文中的知识点, ...

  9. POJ 2478Farey Sequence

    Farey Sequence Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 17744   Accepted: 7109 D ...

  10. AOJ1370: Hidden Anagrams(hash)

    题意 题目链接 Sol 直接对出现的次数hash即可,复杂度\(O(26n^2)\) 一开始没判长度条件疯狂wa #include<bits/stdc++.h> //#define int ...