反射

能够分析类能力的程序称为反射(reflective),代码的这种能力称为"自省"。反射机制的功能极其强大,反射机制可以用来:

  • 在运行时分析类的能力
  • 在运行时查看对象,例如,编写一个toString方法供所有类使用
  • 实现通用的数组操作代码
  • 利用Method对象,这个对象很像C++中的函数指针

Class类

在程序运行期间,Java运行时系统为所有对象维护一个被称为运行时的类型标识。虚拟机利用运行时类型信息选择相应的方法执行。

然而,可以通过专门的Java类访问这些信息。保存这些信息的类被称为Class,这个名字很容易让人混淆。Object类中的getClass()方法将会返回一个Class类型的实例。最常用的Class方法是getName,获取类的名字。

Employee e;
...
Class cl = e.getClass();
String name = cl.getName();
复制代码

如果类在一个包里,包的名字也作为类名的一部分:

Random generator = new Random();
Class cl = generator.getClass();
String name = cl.getName(); // name is set to "java.util.Random"
复制代码

还可以调用静态方法forName获得类名对应的Class对象。

String className = "java.util.Random";
Class cl = Class.forName(className);
复制代码

获得Class类对象的第三种方法,如果T是任意的Java类型(或void关键字),T.class将代表匹配的类对象。

Class cl1 = Random.class;  //if you import java.util.*
Class cl2 = int.class;
Class cl3 = Double[].class;
复制代码

注意:Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如,int不是类,但int.class是一个Class类型的对象。

虚拟机为每个类型管理一个Class对象。因此,可以利用==运算符实现两个类对象比较的操作。例如:

if(e.getClass() == Employee.class) ...
复制代码

还有一个很有用的方法newInstance(),可以用来动态创建一个类的实例,例如,

e.getClass().newInstance();
复制代码

创建了一个与e具有相同类类型的实例。newInstance方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器,就会抛出一个异常。(注意:要求我们除非强制要求创建对象是必须实现带参数的,否则一般会在实现带参构造函数的同时,实现无参默认构造函数。)

将forName与newInstance配合起来使用,可以根据存储在字符串中的类名创建一个对象。

String s = "java.util.Random";
Object m = Class.forName(s).newInstance();
复制代码

注意:在JDK9以后newInstance() 废弃,@Deprecated(since="9"),替换为,

clazz.getDeclaredConstructor().newInstance()
String employeeClassStr = "EmploySalary.Employee";
//如果构造函数有参数
Object m = Class.forName(employeeClassStr).getDeclaredConstructor(String.class, double.class).newInstance("abc", 1.2);
复制代码

反射的用处之一在于:可以不在编译期用import导入需要的类或在maven中写下需要的jar包,而在运行期通过类名创建需要的类,如下:

package RefectTest;
//没有import EmploySalary包
public class ReflectionT1 {
public static void main(String[] args) {
String className = "EmploySalary.EmployeeTest"; //不在RefectTest包中
try {
Object o = Class.forName(className).getConstructor().newInstance();
System.out.println(o.getClass().getName());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
复制代码

利用反射分析类的能力

反射机制最重要的内容——检查类的结构。

在java.lang.reflect包中有三个类Field、Method、Constructor分别用于描述类的域、方法和构造器。

Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。Class类的getDeclareFields、getDeclareMethods和getDeclaredConstuctors方法分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。另外,还可以利用Modifier.toString方法将修饰符打印出来。

在运行时使用反射分析对象

如果知道想要查看的域名和类型,查看指定的域是一件很容易的事情。而利用反射机制可以查看编译时还不清楚的对象域。

Employee harry = new Employee("Harry Hack", 562);
Class cl = harry.getClass();
Field f = cl.getDeclaredField("name");
f.setAccessible(true); //由于name是私有的,如果没有这条语句,将抛IllegalAccessException
Object v = f.get(harry);
System.out.println(v.getClass().getName()); //输出java.lang.String
System.out.println(v.toString()); //输出Harry Hack
f.set(harry, "Jackie"); //将obj对象的f域设置为新值
v = f.get(harry);
System.out.println(v.toString()); //输出Jackie
复制代码

反射机制的默认行为受限于Java的访问控制。然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,需要调用Field、Method或Constructor对象的setAccessible方法。

setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Construct类的公共超类。这个特性是为调试、持久存储和相似机制提供的。

使用反射编写泛型数组代码

java.lang.reflect包中的Array类允许动态地创建数组。

Arrays的copyOf方法,可以用于扩展已经填满的数组

Employee[] a = new Employee[100];
...
//array is full
a = Arrays.copyOf(a, 2 * a.length);
复制代码

如何编写一个通用的方法呢?正好能够将Employee[]数组转换成Object[]数组。

public static Object[] badCopyOf(Object[] a, int newLength){
Object[] newArray = new Object[newLength];
System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
return newArray;
}
复制代码

然而,在实际使用结果数组时会遇到一个问题。这段代码返回的数组类型是对象数组(Object[])类型,这是由于使用下面这行代码创建的数组:

new Object[newLength]
复制代码

一个对象数组不能转换成雇员数组(Employee[])。如果这样做,运行时Java将会产生ClassCastException异常。前面已经看到,Java数组会记住每个元素的类型,即创建数组时new表达式中使用的元素类型。将一个Employee[]临时地转换成Object[]数组,然后再将它转换回来是可以的,但一个从开始就是Object[]的数组却永远无法转换成Employee[]数组。

使用java.lang.reflect包中的Array类,可以实现这个我们的目的。

public static Object goodCopyOf(Object a, int newLength) {
Class cl = a.getClass();
if(!cl.isArray())
return null;
Class componentType = cl.getComponentType();
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}
复制代码

这个goodCopyOf方法可以用来扩展任意类型的数组,而不仅是对象数组。

int[] a = {1,3,4};
a = (int[]) goodCopyOf(a, 10);
复制代码

为了实现上述操作,应该将goodCopyOf参数声明为Object类型,而不要声明为对象型数组(Object[])。整型数组类型int[]可以被转换成Object,但不能转换成对象数组

调用任意方法

在C和C++中,可以从函数指针执行任意函数。从表面上看,Java没有提供方法指针,即将一个方法的存储地址传给另外一个方法,以便第二个方法能够随后调用它。事实上,Java设计者曾说过:方法指针是很危险的,并且常常带来隐患。他们认为Java提供的接口(interface)是一种更好的解决方案。然而,反射机制允许你调用任意方法。
为了能够看到方法指针的工作过程,先回忆一下利用Field类的get方法查看对象域的过程。与之类似,在Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是:

Object invoke(Object, Object ... args)
复制代码

对于静态方法,第一个参数可以被忽略,即可以将它设置为null。

假设m1代表Employee类的getName方法,m2代表Employee的getSalary方法。

String n = (String)m1.invoke(harry);
double s = (Double)m1.invoke(harry);
复制代码

如何获得Method对象?

Method getMethod(String name, Class ... parameterTypes)
复制代码

下面说明了如何获得Emplopyee类的getName方法和raiseSalary方法的方法指针。

Method m1 = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("raiseSalary", double.class);
复制代码

建议仅在必要的时候才使用Method对象,而最好使用接口以及Java SE 8中的lambda表达式。建议Java开发者不要使用Method对象的回调功能。使用接口进行回调会使得代码执行速度更快,更易维护。

注解

注解是那些插入到源代码中使用其他工具可以对其进行处理的标签。这些工具可以在源码层次上进行操作,或者可以处理编译器在其中放置了注解的类文件。

注解不会改变程序的编译方式。Java编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。

为了能够受益于注解,你需要选择一个处理工具,然后向你的处理工具可以理解的代码中插入注解,之后运用该处理工具处理代码。

注解的使用范围:

  • 附属文件的自动生成,例如部署描述符或bean信息类
  • 测试、日志、事务语义等代码的自动生成

每个注解都必须通过一个注解接口进行定义。这些接口中的方法与注解中的元素相对应。例如,JUnit的注解Test可以用下面这个接口定义:

    @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test
{
long timeout() default OL;
}
复制代码

@interface声明创建了一个真正的Java接口。处理注解的工具将接收那些实现了这个注解接口的对象。这类工具可以调用timeout方法来检索某个特定Test注解的timeout元素。

注解Target和Retention是元注解。它们注解了Test注解,即将Test注解标识成一个只能运用到方法上的注解,并且当类文件载入到虚拟机的时候,仍可以保留下来。

注解接口中的元素声明实际上是方法声明。一个注解接口的方法不能有任何参数和任何throws语句,并且它们也不能是泛型的。注意:一个注解元素永远不能设置为null,甚至不允许其默认值为null。这样在实际应用用会相当不方便。你必须使用其他的默认值,例如""或者Void.class

标准注解

用于编译的注解

Java SE在java.lang.annotation和javax.annotation包中定义了大量的注解接口。其中四个是元注解,用于描述注解接口的行为属性,其他的三个是规则接口,可以用它们来注解你的源代码中的项。

@Deprecated注解可以被添加到任何不再鼓励使用的项上。所以,当你使用一个已过时的项时,编译器将会发出警告。这个注解与Java文档标签@deprecated具有同等功效。

@SuppressWarning注解会告知编译器阻止特殊类型的警告信息,例如,

@SuppressWarning("unchecked")
复制代码

用于管理资源的注解

@PostConstruct和@PreDestroy注解用于控制对象生命周期的环境中,例如Web容器和应用服务器。标记了这些方法应该在对象被构建之后,或者在对象被移除之前,紧接着调用。

@Resource注解用于资源注入。在Web应用中,可以像下面这样引用数据源:

@Resource(name="jdbc/mydb")
private DataSource source; //当这个过程域对象被创建时,容器会“注入”一个对该数据源的引用。
复制代码

@Generated注解的目的是供代码生成工具来使用。任何生成的源代码都可以被注解,从而与程序员提供的代码区分开。

元注解

@Target 元注解可以应用与一个注解,以限制该注解可以应用到哪些项上

@Rentention元注解用于指定一条注解应该保留多长时间。

@Documented 注解为像Javadoc这样的归档工具提供了一些提示。

@Inherited元注解只能应用于对类的注解。如果一个类具有继承注解,那么它的所有子类都自动具有同样的注解。

代理

利用代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。

转载于:https://juejin.im/post/5b6ada37f265da0faf71f197

Java反射与注解的更多相关文章

  1. Java反射,注解,以及动态代理

    Java反射,注解,以及动态代理 基础  最近在准备实习面试,被学长问到了Java反射,注解和动态代理的内容,发现有点自己有点懵,这几天查了很多资料,就来说下自己的理解吧[如有错误,望指正] Java ...

  2. java 反射,注解,泛型,内省(高级知识点)

     Java反射 1.Java反射是Java被视为动态(或准动态)语言的一个关键性质.这个机制允许程序在运行时透过Reflection APIs    取得任何一个已知名称的class的内部信息, 包括 ...

  3. java反射获取注解并拼接sql语句

    先建两个注解 分别为 Table 和 Column package com.hk.test; import java.lang.annotation.ElementType; import java. ...

  4. 【转】JAVA反射与注解

    转载自:https://www.daidingkang.cc/2017/07/18/java-reflection-annotations/ 前言 现在在我们构建自己或公司的项目中,或多或少都会依赖几 ...

  5. JavaSE学习总结(十五)—— Java反射与注解

    一.静态语言与动态语言 静态类型语言:是指在编译时变量的数据类型即可确定的语言,多数静态类型语言要求在使用变量之前必须声明数据类型,某些具有类型推导能力的现代语言可能能够部分减轻这个要求.强类型 动态 ...

  6. java反射与注解结合使用(根据传入对象输出查询sql)

    我们在项目开发中有很多地方使用到了注解,关于注解的定义与创建小伙伴可以参考我的文章<java注解>.有任何问题的小伙伴们可以在评论区指出哦,欢迎各位大佬指出问题. 今天我要说的是使用注解与 ...

  7. Java反射及注解

    一.反射 1.动态语言:是指程序在运行是可以改变其结构:新的函数可以引进,已有的函数可以被删除等结构上的变化.比如常见的JavaScript就是动态语言,除此以外Python等也属于动态语言,而C.C ...

  8. Java反射和注解

    反射:http://blog.csdn.net/liujiahan629629/article/details/18013523 注解:http://www.cnblogs.com/peida/arc ...

  9. Java 反射、注解

    1. 泛型 基本用法.泛型擦除.泛型类/泛型方法/泛型接口.泛型关键字.反射泛型! a. 概述 泛型是JDK1.5以后才有的, 可以在编译时期进行类型检查,且可以避免频繁类型转化! // 运行时期异常 ...

随机推荐

  1. 解决MySQL workbench的resultgird不出现的问题

    23:01:37 2019-08-08 鸽了好久 终于开始看怎么使用MySQL workbench了 首先就是遇到了result gird不显示的问题 搜了 重启是一种办法(这方法...至少能用) 其 ...

  2. Java Interger小知识

    Integer装箱与拆箱 装箱: Integer i = 10; 相当于:Integer i = Integer.valueOf(10); 拆箱: Integer i = 10; //装箱 int t ...

  3. 存储机制 cookie session jwt token

    cookieCookie的诞生 由于HTTP协议是无状态的,而服务器端的业务必须是要有状态的.Cookie诞生的最初目的是为了存储web中的状态信息,以方便服务器端使用.比如判断用户是否是第一次访问网 ...

  4. 【Java】 语言基础习题汇总 [1] 基础概念到数组

    1 JDK JRE JVM 三种之间的关系,以及JDK JRE 包含的主要结构有哪些? JDK = JRE + 开发工具 javac.exe java.exe javadoc.exe等等 JRE = ...

  5. 【DataBase】更改root根用户密码 和 SQLyog安装

    更改root根用户密码 和 SQLyog安装 无密码登录MySQL mysql -u root -p 修改密码与更新加密规则 ALTER USER 'root'@'localhost' IDENTIF ...

  6. WinForm:API

    一.WndProc:  主要用在拦截并处理系统消息和自定义消息 比如:windows程序会产生很多消息,比如你单击鼠标,移动窗口都会产生消息.这个函数就是默认的消息处理函数.你可以重载这个函数来制定自 ...

  7. Python语法详解

    python语法解析 目录 python语法解析 一.顺序结构 二.分支结构 2.1 if 的基本语法 2.2 if 的基本应用 三.循环结构 3.1 while 语法 3.1.1 语法结束条件 3. ...

  8. 提高万恶的KPI,切忌要避开这六个低效的编程习惯

    作者:程序员小跃 Slogan:当你的才华还无法撑起你的野心时,那应该静下心来好好学习 上次的翻译,引起了很大的反响,大家都想知道自己和高级工程师的差距,看了我的文章,是不是都在默默地做着比较呢?如果 ...

  9. 数据挖掘入门系列教程(九)之基于sklearn的SVM使用

    目录 介绍 基于SVM对MINIST数据集进行分类 使用SVM SVM分析垃圾邮件 加载数据集 分词 构建词云 构建数据集 进行训练 交叉验证 炼丹术 总结 参考 介绍 在上一篇博客:数据挖掘入门系列 ...

  10. ppt和pptx转图片完整代码,解决2003版和2007版中文乱码问题

    引入所需依赖,注意poi版本,新版本不支持,最好使用和我一样的版本. <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --& ...