Java 反射机制应用实践
反射基础
p.s: 本文需要读者对反射机制的API有一定程度的了解,如果之前没有接触过的话,建议先看一下官方文档的Quick Start(https://docs.oracle.com/javase/tutorial/reflect/)。
在应用反射机制之前,首先我们先来看一下如何获取一个对象对应的反射类Class,在Java中我们有三种方法可以获取一个对象的反射类。
通过getClass方法
在Java中,每一个Object都有一个getClass()方法,通过getClass方法我们可以获取到这个对象对应的反射类:
String s = "ziwenxie";
Class<?> c = s.getClass();
通过forName方法
我们也可以调用Class类的静态方法forName():
Class<?> c = Class.forName("java.lang.String");
使用.class
或者我们也可以直接使用.class:
Class<?> c = String.class;
获取类型信息
在文章开头我们就提到反射的一大好处就是可以允许我们在运行期间获取对象的类型信息,下面我们通过一个例子来具体看一下。
首先我们在typeinfo.interfacea包下面新建一个接口A:
package typeinfo.interfacea;
public interface A { void f(); }
接着我们在typeinfo.packageaccess包下面新建一个接口C,接口C继承自接口A,并且我们还另外创建了几个用于测试的方法,注意下面几个方法的权限都是不同的。
package typeinfo.packageaccess;
import typeinfo.interfacea.A;
class C implements A {
public void f() { System.out.println("public C.f()"); }
public void g() { System.out.println("public C.g()"); }
protected void v () { System.out.println("protected C.v()"); }
void u() { System.out.println("package C.u()"); }
private void w() { System.out.println("private C.w()"); }
}
public class HiddenC {
public static A makeA() { return new C(); }
}
在callHiddenMethod()方法中我们用到了几个新的API,其中getDeclaredMethod()根据方法名用于获取Class类指代对象自己声明的某个方法,然后我们通过调用invoke()方法就可以触发对象的相关方法:
package typeinfo;
import typeinfo.interfacea.A;
import typeinfo.packageaccess.HiddenC;
import java.lang.reflect.Method;
public class HiddenImplementation {
public static void main(String[] args) throws Exception {
A a = HiddenC.makeA();
a.f();
System.out.println(a.getClass().getName());
// Oops! Reflection still allows us to call g():
callHiddenMethod(a, "g");
// And even methods that are less accessible!
callHiddenMethod(a, "u");
callHiddenMethod(a, "v");
callHiddenMethod(a, "w");
}
static void callHiddenMethod(Object a, String methodName) throws Exception {
Method g = a.getClass().getDeclaredMethod(methodName);
g.setAccessible(true);
g.invoke(a);
}
}
从输出结果我们可以看出来,不管是public,default,protect还是pricate方法,通过反射类我们都可以自由调用。当然这里我们只是为了显示反射的强大威力,在实际开发中这种技巧还是不提倡。
public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()
上面我们只是测试了Method对象,感兴趣的读者在熟悉了反射的API之后,不妨测试一下Filed,这里我们就不重复了。
与注解相结合
在单元测试框架比如Junit中反射机制也得到了广泛的应用,即通过注解的方式。下面我们简单地来了解一下如何通过反射机制来获取相关方法的注解信息,比如说我们有下面这样一个业务场景,当用户在修改自己密码的时候,为了保证密码的安全性,我们要求用户的新密码要满足一些条件,比如说至少要包含一个非数字字符,不能与以前的密码相同之类的条件等。
import java.lang.annotation.*
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserCase {
public int id();
public String description() default "no description";
}
下面是我们检测密码的工具类的实现:
public class PasswordUtils {
@UserCase(id=47, description="Password must contain at least one numeric")
public boolean validatePassword(String password) {
return (password.matches("\\w*\\d\\w*"));
}
@UserCase(id=48)
public String encryptPassword(String password) {
return new StringBuilder(password).reverse().toString();
}
@UserCase(id=49, description="New passwords can't equal previously used ones")
public boolean checkForNewPassword(List<String> prevPasswords, String password) {
return !prevPasswords.contains(password);
}
}
利用反射我们可以写出更加清晰的测试代码,其中getDeclaredMethods()方法可以获取相关对象自己声明的相关方法,而getAnnotation()则可以获取Method对象的指定注解。
public class UseCaseTracker {
public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
for(Method m : cl.getDeclaredMethods()) {
UseCase uc = m.getAnnotation(UseCase.class);
if(uc != null) {
System.out.println("Found Use Case: " + uc.id() + " " + uc.description());
useCases.remove(new Integer(uc.id()));
}
}
for(int i : useCases) {
System.out.println("Warning: Missing use case-" + i);
}
}
public static void main(String[] args) {
List<Integer> useCases = new ArrayList<Integer>();
Collections.addAll(useCases, 47, 48, 49, 50);
trackUseCases(userCases, PasswordUtils.class);
}
}
解决泛型擦除
现在有下面这样一个业务场景,我们有一个泛型集合类List<Class<? extends Pet>>,我们需要统计出这个集合类中每种具体的Pet有多少个。由于Java的泛型擦除,注意类似List<? extends Pet>的做法肯定是不行的,因为编译器做了静态类型检查之后,到了运行期间JVM会将集合中的对象都视为Pet,但是并不会知道Pet代表的究竟是Cat还是Dog,所以到了运行期间对象的类型信息其实全部丢失了。p.s: 关于泛型擦除:我在上一篇文章里面有详细解释,感兴趣的朋友可以看一看。
为了实现我们上面的例子,我们先来定义几个类:
public class Pet extends Individual {
public Pet(String name) { super(name); }
public Pet() { super(); }
}
public class Cat extends Pet {
public Cat(String name) { super(name); }
public Cat() { super(); }
}
public class Dog extends Pet {
public Dog(String name) { super(name); }
public Dog() { super(); }
}
public class EgyptianMau extends Cat {
public EgyptianMau(String name) { super(name); }
public EgyptianMau() { super(); }
}
public class Mutt extends Dog {
public Mutt(String name) { super(name); }
public Mutt() { super(); }
}
上面的Pet类继承自Individual,Individual类的的实现稍微复杂一点,我们实现了Comparable接口,重新自定义了类的比较规则,如果不是很明白的话,也没有关系,我们已经将它抽象出来了,所以不理解实现原理也没有关系。
public class Individual implements Comparable<Individual> {
private static long counter = 0;
private final long id = counter++;
private String name; // name is optional
public Individual(String name) { this.name = name; }
public Individual() {}
public String toString() {
return getClass().getSimpleName() + (name == null ? "" : " " + name);
}
public long id() { return id; }
public boolean equals(Object o) {
return o instanceof Individual && id == ((Individual)o).id;
}
public int hashCode() {
int result = 17;
if (name != null) {
result = 37 * result + name.hashCode();
}
result = 37 * result + (int) id;
return result;
}
public int compareTo(Individual arg) {
// Compare by class name first:
String first = getClass().getSimpleName();
String argFirst = arg.getClass().getSimpleName();
int firstCompare = first.compareTo(argFirst);
if (firstCompare != 0) {
return firstCompare;
}
if (name != null && arg.name != null) {
int secendCompare = name.compareTo(arg.name);
if (secendCompare != 0) {
return secendCompare;
}
}
return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
}
}
下面创建了一个抽象类PetCreator,以后我们通过调用arrayList()方法便可以直接获取相关Pet类的集合。这里使用到了我们上面没有提及的newInstance()方法,它会返回Class类所真正指代的类的实例,这是什么意思呢?比如说声明new Dog().getClass().newInstance()和直接new Dog()是等价的。
public abstract class PetCreator {
private Random rand = new Random(47);
// The List of the different getTypes of Pet to create:
public abstract List<Class<? extends Pet>> getTypes();
public Pet randomPet() {
// Create one random Pet
int n = rand.nextInt(getTypes().size());
try {
return getTypes().get(n).newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public Pet[] createArray(int size) {
Pet[] result = new Pet[size];
for (int i = 0; i < size; i++) {
result[i] = randomPet();
}
return result;
}
public ArrayList<Pet> arrayList(int size) {
ArrayList<Pet> result = new ArrayList<Pet>();
Collections.addAll(result, createArray(size));
return result;
}
}
接下来我们来实现上面这一个抽象类,解释一下下面的代码,在下面的代码中,我们声明了两个集合类,allTypes和types,其中allTypes中包含了我们呢上面所声明的所有类,但是我们具体的类型实际上只有两种即Mutt和EgypianMau,所以我们真正需要new出来的宠物只是types中所包含的类型,以后我们通过调用getTypes()便可以得到types中所包含的所有类型。
public class LiteralPetCreator extends PetCreator {
@SuppressWarnings("unchecked")
public static final List<Class<? extends Pet>> allTypes = Collections.unmodifiableList(
Arrays.asList(Pet.class, Dog.class, Cat.class, Mutt.class, EgyptianMau.class));
private static final List<Class<? extends Pet>> types = allTypes.subList(
allTypes.indexOf(Mutt.class), allTypes.size());
public List<Class<? extends Pet>> getTypes() {
return types;
}
}
总体的逻辑已经完成了,最后我们实现用来统计集合中相关Pet类个数的TypeCounter类。解释一下isAssignalbeFrom()方法,它可以判断一个反射类是某个反射类的子类或者间接子类。而getSuperclass()顾名思义就是得到某个反射类的父类了。
public class TypeCounter extends HashMap<Class<?>, Integer> {
private Class<?> baseType;
public TypeCounter(Class<?> baseType) {
this.baseType = baseType;
}
public void count(Object obj) {
Class<?> type = obj.getClass();
if (!baseType.isAssignableFrom(type)) {
throw new RuntimeException(
obj + " incorrect type " + type + ", should be type or subtype of " + baseType);
}
countClass(type);
}
private void countClass(Class<?> type) {
Integer quantity = get(type);
put(type, quantity == null ? 1 : quantity + 1);
Class<?> superClass = type.getSuperclass();
if (superClass != null && baseType.isAssignableFrom(superClass)) {
countClass(superClass);
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("{");
for (Map.Entry<Class<?>, Integer> pair : entrySet()) {
result.append(pair.getKey().getSimpleName());
result.append("=");
result.append(pair.getValue());
result.append(", ");
}
result.delete(result.length() - 2, result.length());
result.append("} ");
return result.toString();
}
}
Java 反射机制应用实践的更多相关文章
- Java反射机制应用实践
反射基础 在应用反射机制之前,首先我们先来看一下如何获取一个对象对应的反射类Class,在Java中我们有三种方法可以获取一个对象的反射类. 通过getClass方法 在Java中,每一个Object ...
- 一个例子让你了解Java反射机制
本文来自:blog.csdn.net/ljphhj JAVA反射机制: 通俗地说,反射机制就是可以把一个类,类的成员(函数,属性),当成一个对象来操作,希望读者能理解,也就是说,类,类的成员,我们在运 ...
- (转)个例子让你了解Java反射机制
个例子让你了解Java反射机制 原文地址:http://blog.csdn.net/ljphhj/article/details/12858767 JAVA反射机制: 通俗地说,反射机制就是可 ...
- (转)JAVA反射机制理解
JAVA反射机制: 通俗地说,反射机制就是可以把一个类,类的成员(函数,属性),当成一个对象来操作,希望读者能理解,也就是说,类,类的成员,我们在运行的时候还可以动态地去操作他们. 理论的东东太多也没 ...
- 浅谈java反射机制
目录 什么是反射 初探 初始化 类 构造函数 属性 方法 总结 思考 什么是反射 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意 ...
- 一文带你了解Java反射机制
想要获取更多文章可以访问我的博客 - 代码无止境. 上周上班的时候解决一个需求,需要将一批数据导出到Excel.本来公司的中间件组已经封装好了使用POI生成Excel的工具方法,但是无奈产品的需求里面 ...
- 第28章 java反射机制
java反射机制 1.类加载机制 1.1.jvm和类 运行Java程序:java 带有main方法的类名 之后java会启动jvm,并加载字节码(字节码就是一个类在内存空间的状态) 当调用java命令 ...
- Java反射机制
Java反射机制 一:什么事反射机制 简单地说,就是程序运行时能够通过反射的到类的所有信息,只需要获得类名,方法名,属性名. 二:为什么要用反射: 静态编译:在编译时确定类型,绑定对象,即通过 ...
- java基础知识(十一)java反射机制(上)
java.lang.Class类详解 java Class类详解 一.class类 Class类是java语言定义的特定类的实现,在java中每个类都有一个相应的Class对象,以便java程序运行时 ...
随机推荐
- javascript之冒泡排序
Array.prototype.bubble = function () { var temp; //第1轮比较求第一,第二轮求第二,以此类推(i<=数组成员的个数); for (var i = ...
- (4.11)DBCC 常用命令
三.DBCC状态.验证.其他语句 跟踪标识全集:https://www.cnblogs.com/lyhabc/articles/4092507.html 状态语句DBCC INPUTBUFFER 显示 ...
- 《Python 数据分析》笔记——数据的检索、加工与存储
数据的检索.加工与存储 1.利用Numpy和pandas对CSV文件进行写操作 对CSV文件进行写操作,numpy的savetxt()函数是与loadtxt()相对应的一个函数,他能以诸如CSV之类的 ...
- 玩转type类型(牛逼克拉斯 )
一.前言 一说起type()方法肯定有很多人都知道这不就是查看某个对象类型的嘛,其实不然,他还有更牛逼的应用------创建类 >>> type(1) <class 'int' ...
- mysql库安装
如果缺少<mysql/mysql.h> 先安装mysql,然后apt-get install libmysqlclient-dev即可
- token的生成和应用
token的生成和应用 接口特点汇总: 1.因为是非开放性的,所以所有的接口都是封闭的,只对公司内部的产品有效: 2.因为是非开放性的,所以OAuth那套协议是行不通的,因为没有中间用户的授权过程: ...
- JSP笔记01——尝试
JSP ————> servlet 我的第1个Java Web应用程序——index.jsp 我的第2个Java Web应用程序——welcome-file 我的第3个Java Web应用程序— ...
- Django---media静态文件的配置&全局变量
media 静态文件配置 static 静态文件多用于存放用于渲染前端页面的相关数据,media用于存放客户上传或其他的文件 setting.py 中加入路径 MEDIA_ROOT = ( os.pa ...
- 在vim下按ctrl+s后界面卡住
用惯了window编辑器的我们,在使用linux vim编辑器时会不会遇到这个问题:在编辑时总是会不小心按下Ctrl+S,然后整个终端都没有反应了?其实在Linux下 Ctrl+S是有特殊的用途的,不 ...
- Python的operator.itemgetter函数和sorted函数
写这篇文章的目的是之前在<机器学习实战>用Python3实现KNN算法时用到的几个函数不太懂, 地址: 1- https://github.com/hitergelei/Self-Lear ...