反射基础

p.s: 本文需要读者对反射机制的API有一定程度的了解,如果之前没有接触过的话,建议先看一下官方文档的Quick Start

在应用反射机制之前,首先我们先来看一下如何获取一个对象对应的反射类Class,在Java中我们有三种方法可以获取一个对象的反射类。

通过getClass方法

在Java中,每一个Object都有一个getClass()方法,通过getClass方法我们可以获取到这个对象对应的反射类:

1
2
String s = "ziwenxie";
Class<?> c = s.getClass();

通过forName方法

我们也可以调用Class类的静态方法forName()

1
Class<?> c = Class.forName("java.lang.String");

使用.class

或者我们也可以直接使用.class

1
Class<?> c = String.class;

获取类型信息

在文章开头我们就提到反射的一大好处就是可以允许我们在运行期间获取对象的类型信息,下面我们通过一个例子来具体看一下。

首先我们在typeinfo.interfacea包下面新建一个接口A

1
2
package typeinfo.interfacea;
public interface A { void f(); }

接着我们在typeinfo.packageaccess包下面新建一个接口C,接口C继承自接口A,并且我们还另外创建了几个用于测试的方法,注意下面几个方法的权限都是不同的。

1
2
3
4
5
6
7
8
9
10
11
12
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()方法就可以触发对象的相关方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
    }
}

从输出结果我们可以看出来,不管是publicdefaultprotect还是pricate方法,通过反射类我们都可以自由调用。当然这里我们只是为了显示反射的强大威力,在实际开发中这种技巧还是不提倡。

1
2
3
4
5
6
public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()

上面我们只是测试了Method对象,感兴趣的读者在熟悉了反射的API之后,不妨测试一下Filed,这里我们就不重复了。

与注解相结合

在单元测试框架比如Junit中反射机制也得到了广泛的应用,即通过注解的方式。下面我们简单地来了解一下如何通过反射机制来获取相关方法的注解信息,比如说我们有下面这样一个业务场景,当用户在修改自己密码的时候,为了保证密码的安全性,我们要求用户的新密码要满足一些条件,比如说至少要包含一个非数字字符,不能与以前的密码相同之类的条件等。

1
2
3
4
5
6
7
import java.lang.annotation.*
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserCase {
    public int id();
    public String description() default "no description";
}

下面是我们检测密码的工具类的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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对象的指定注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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: 关于泛型擦除:我在上一篇文章里面有详细解释,感兴趣的朋友可以看一看。

为了实现我们上面的例子,我们先来定义几个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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类继承自IndividualIndividual类的的实现稍微复杂一点,我们实现了Comparable接口,重新自定义了类的比较规则,如果不是很明白的话,也没有关系,我们已经将它抽象出来了,所以不理解实现原理也没有关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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()是等价的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
    }
}

接下来我们来实现上面这一个抽象类,解释一下下面的代码,在下面的代码中,我们声明了两个集合类,allTypestypes,其中allTypes中包含了我们呢上面所声明的所有类,但是我们具体的类型实际上只有两种即MuttEgypianMau,所以我们真正需要new出来的宠物只是types中所包含的类型,以后我们通过调用getTypes()便可以得到types中所包含的所有类型。

1
2
3
4
5
6
7
8
9
10
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()顾名思义就是得到某个反射类的父类了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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();
    }
}

References

java反射基础知识(四)反射应用实践的更多相关文章

  1. Java的基础知识四

    一.Java 流(Stream).文件(File)和IO Java.io 包几乎包含了所有操作输入.输出需要的类.所有这些流类代表了输入源和输出目标. Java.io 包中的流支持很多种格式,比如:基 ...

  2. C#基础知识回顾-- 反射(3)

    C#基础知识回顾-- 反射(3)   获取Type对象的构造函数: 前一篇因为篇幅问题因为篇幅太短被移除首页,反射这一块还有一篇“怎样在程序集中使用反射”, 其他没有什么可以写的了,前两篇主要是铺垫, ...

  3. C#基础知识回顾-- 反射(1)

    C#基础知识回顾-- 反射(1)   反射(reflection)是一种允许用户获得类型信息的C#特性.术语“反射”源自于它的工作方式: Type对象映射它所代表的底层对象.对Type对象进行查询可以 ...

  4. 什么才是java的基础知识?

    近日里,很多人邀请我回答各种j2ee开发的初级问题,我无一都强调java初学者要先扎实自己的基础知识,那什么才是java的基础知识?又怎么样才算掌握了java的基础知识呢?这个问题还真值得仔细思考. ...

  5. java必备基础知识(一)

    学习的一点建议: 每一门语言的学习都要从基础知识开始,学习是一个过程,"万丈高楼平地起",没有一个好的地基,想必再豪华的高楼大厦终究有一天会倒塌.因此,我们学习知识也要打牢根基,厚 ...

  6. JAVA相关基础知识

    JAVA相关基础知识 1.面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择其中的一部分, ...

  7. Java 多线程——基础知识

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  8. Java语言基础(四)

    Java语言基础(四) 一.基本数据类型(8) byte  8位  使用较少,一般用于网络传输: -128-+127 short  16位 不常用 -32768-+32767 int  32位 常用  ...

  9. java部分基础知识整理----百度脑图版

    近期发现,通过百度脑图可以很好的归纳总结和整理知识点,本着学习和复习的目的,梳理了一下java部分的知识点,不定期更新,若有不恰之处,请指正,谢谢! 脑图链接如下:java部分基础知识整理----百度 ...

  10. Python基础知识(四)

    Python基础知识(四) 一丶列表 定义格式: 是一个容器,由 [ ]表示,元素与元素之间用逗号隔开. 如:name=["张三","李四"] 作用: 存储任意 ...

随机推荐

  1. Android控件——7种形式的Android Dialog使用举例(转载)

    在Android开发中,我们经常会需要在Android界面上弹出一些对话框,比如询问用户或者让用户选择.这些功能我们叫它Android Dialog对话框,在我们使用Android的过程中,我归纳了一 ...

  2. css 禅意花园 笔记

    1.  FOUC(Flash Of Unstyled Content) 现象( 在某些情况下,IE加载网页时会出现短暂的CSS样式失效. a: 只发生在Windows上的 IE (5.0版本以上) b ...

  3. Ubuntu14.04中安装Sublime_Text_3

    Sublime Text 简介 Sublime Text 是一款流行的文本编辑器软件,有点类似于TextMate,跨平台,可运行在Linux.Windows和Mac OS X.也是许多程序员喜欢使用的 ...

  4. 第二百零四节,jQuery EasyUI,Dialog(对话框)组件

    jQuery EasyUI,Dialog(对话框)组件 学习要点: 1.加载方式 2.属性列表 3.事件列表 4.方法列表 本节课重点了解EasyUI中Dialog(窗口)组件的使用方法,这个组件依赖 ...

  5. weblogic配置oracle数据源

    在weblogic配置oracle数据源还是挺简单的,网上也有很多关于这方面的文章,写给自己也写给能够得到帮助的人吧.weblogic新建域那些的就不说了哈.点击startWebLogic文件,会弹出 ...

  6. VS本地调试oracle报错解决方法

    同事的项目,SVN下载下来以后一直报错,后来确认一下 1本地要安装oracle 2代码用的是64位的,所以本地安装也要64位的oracle 3VS调试用的IIS Express也要是64位的,激活方法 ...

  7. Docker mysql 连接 “The server requested authentication method unknown to the clien”错误

    查了下,出现这个错误的原因是从mysql 5.6开始,mysql密码加密算法更改了. 我装的mysql 8.* ,那么有两种解决方法: mysql 版本选择 <= 5.6 修改密码 docker ...

  8. 如何正确使用Google搜索

    “” 双引号表示完全匹配,结果中必须出现与搜索文本完全相同的内容 A -B 搜索包含A但不包含B的结果(请注意A后面的空格不能省略) filetype 搜索对应类型的文件.例如:中国防火墙 filet ...

  9. SurvivalShooter学习笔记(六.玩家生命)

    需求: 玩家有初始生命: 被敌人攻击后:掉血,播放受击音效,红屏(用UI图片做)闪烁提示,UI面板刷新生命 直至死亡:死亡播放死亡音效,游戏结束: 1.变量: 玩家生命 public int star ...

  10. 用pypy运行ryu

    最近看到pypy可以提高python的运行速率到很变态的境地,加之现在ryu发现拓扑的能力有限,不能满足实验要求,所以想将其试着在pypy上运行 部署pypy在virtualenv,在学python初 ...