在实践中了解Java反射机制应用
作者:ziwenxie 发布时间:2021-06-29 02:40:14
引言
Java反射机制是一个非常强大的功能,在很多大型项目比如Spring, Mybatis都可以看见反射的身影。通过反射机制我们可以在运行期间获取对象的类型信息,利用这一特性我们可以实现工厂模式和代理模式等设计模式,同时也可以解决Java泛型擦除等令人苦恼的问题。本文我们就从实际应用的角度出发,来应用一下Java的反射机制。
反射基础
p.s: 本文需要读者对反射机制的API有一定程度的了解,如果之前没有接触过的话,建议先看一下官方文档的Quick Start。
在应用反射机制之前,首先我们先来看一下如何获取一个对象对应的反射类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()
应用实践
我们有下面这样一个业务场景,我们有一个泛型集合类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();
}
}
来源:http://www.codeceo.com/article/java-reflection.html


猜你喜欢
- Parallel类是对线程的抽象,提供数据与任务的并行性。类定义了静态方法For和ForEach,使用多个任务来完成多个作业。Paralle
- ObjectMapper 忽略字段大小写核心代码:ObjectMapper mapper = new ObjectMapper();mapp
- 前言最近看了一下 Android 上的图表控件,去年做过一款应用也已上架了,也用到了图表控件,但是只是按照官方 demo 集成了,并没有过多
- Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同
- 前言RecyclerView几乎在每个app里面都有被使用,但凡使用了列表就会采用分页加载进行数据请求和加载。android 官方也推出了分
- 前言日常开发中,缓存是解决数据库压力的一种方案,通常用于频繁查询的数据,例如新闻中的热点新闻,本文记录springboot中使用cache缓
- 本文实例为大家分享了java实现超市管理系统的具体代码,供大家参考,具体内容如下实现功能使用选择结构,循环结构,数组的知识实现一个超市管理系
- Map接口存储特点以键(key)值(value)对的形式存储键无序、无下标、元素不可重复值无序、无下标、元素可以重复常用实现类HashMap
- Java中常用关键字:与数据类型相关(10)与流程控制相关(13)if: 表示条件判断,一般用法if(关系表达式),后跟else或
- Java 7的这个新特性改变了警告的对象。构建这些类型毕竟有破坏类型安全的风险,这总得有人知道。但 API 的用户对此是无能为力的,不管do
- 纸上得来终觉浅,觉知此事要躬行。楔子本文适合:对Spring Security有一点了解或者跑过简单demo但是对整体运行流程不明白的同学,
- 本文实例讲述了C#实现文件断点续传下载的方法。分享给大家供大家参考。具体实现方法如下:using System;using System.D
- springcloud多个消费端重复定义feign clientspring cloud消费端调用服务提供者,有两种方式rest+ribbo
- Java时间格式转换大全import java.text.*;import java.util.Calendar;public class
- 需求读200+的CSV/EXCEL文件,按文件名称存到不同数据库前期准备环境maven + jdk8 + mysql代码展示pom文件<
- for循环和foreach循环其实可以算得上是从属关系的,即foreach循环是可以转化成for循环,但是for循环不一定能转换成forea
- Java读取Properties文件的方法总结  
- application.ymlspring: datasource: username: root password
- 1. 背景Java Persistence with Hibernate 在12.2.1小节使用如下例子描述 n+1查询问题:List<
- 目录前言一、内存优化策略二、具体优化的点1.避免内存泄漏2.Bitmap等大对象的优化策略(1) 优化Bitmap分辨率(2) 优化单个像素