IT虾米网

Java 反射机制应用实践详解

admin 2018年06月15日 编程语言 420 0

引言

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(); 
    } 
}
发布评论

分享到:

IT虾米网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

从代码层读懂 Java HashMap 的实现原理详解
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。