反射是个近乎无敌的工具。
但使用过度会遭人嫌弃。
按CodeReview的话说,叫
“同学,你这有点过度设计啊”。
翻译过来叫
“整NM这么复杂有毛病啊。”
当你开心写完代码但得到这样的评价是会有点小尴尬。但我们站在读代码的角度,看到一串复杂玩意,确实很烦的。
然而身为一个geek,即使可能遭人嫌弃,我们也要继续去作死!
但作死也要讲究原则,否则就真死了。为了不被CodeReview的大佬嫌弃,在使用反射进行花式操作时,遵循下面几个原则。
不被嫌弃的第一步就是尽量不做无用功。如果使用反射技巧能让你的代码在使用上有下列优点的话,就值得去做。
第二点是维持简洁。整个类最好很短,功能单一。这样后人接手你的代码时,心理压力会小很多。
第三点也和后期维护有关,当别人需要增加功能时,而又不想用反射了,你需要提供可修改的途径。比如使用接口,比如用protected代替private。
第三点和面向对象经典的开闭原则是相违背的,放在IDEA里,你用protected它就会提醒你改成private,很烦。但完美的开闭有时过于理想了,完美开闭的前提是已有良好的类开闭设计,但在你所在的公司,可能接口变得比业务还多。为了让后人能够增加功能而不是直接copy,你需要提供一定的开放。
这里给出个当时业务中我遇到的例子,就很适合用反射去解决。
先简单提下反射的特性,你可以这样去理解:
代码字面上包含的类信息,就是反射能获取的信息。
了解这点后,你就能判断何时能用反射去秀操作了。
我遇到的需求是这样:
我们有个泛型接口,我们叫它Runner<T,R>它接收一个T返回一个R,这个T和R都可以被序列化成字符串。
我们的需求分为两步,
第一步,外部发来Runner的名字,然后从所有实现类找到需要的Runner。
第二步,用这个Runner去接收外部发来的字符串,将他反序列化为T后,然后返回R序列化后的字符串。
用户给个Runner name->实例化的Runner
所以重点就是,我们需要知道T和R的类型。
但是 Java的泛型是会类型擦除的,在运行时并不知道是T还是R 。
一个简单想法就是,把T和R放在实现类中,比如直接用个
//简单方案,直接增加2个方法 interface Runner<T,R>{ ... Type GetInputType(); Type GetOuputType(); }
但这样每个实现类都要自己去实现这两个方法了,会有一堆多余的代码,而且得一个一个手动复制粘贴。还要注意修改时保持一致性。很麻烦不是吗。
所以这时候就可以用反射了。
我们回想一下,反射是所有代码字面上包含的类信息,比如下面这个Runner, 它明明白白的写了string和string,所以我们是能通过反射拿到那两个string的 。
public class StringStringRunner implements Runner<String,String> { @Override public String execute(String s) { return s+"!"; } }
换句话说,只有有了那个类名,我就能知道它的T和R。
剩下就是反射的具体实现了,这一部分纯粹是基本功我就不多说了。边写边google就是,我自己写的时候也不是都记得的,每次查或者copy以前的代码。
关键你只需要知道这里能用反射,然后发挥你的想象力,再靠你的基本功去实现它。
/** * 泛型类型工厂 * <br> 这个工厂在产生泛型实例的同时,会给出该实例对应泛型接口的参数类型。 */ public class RunnerFactory implements AbstRunnerFactory { /** * 工厂的生产单元 */ StringMapRunner stringMapRunner = new StringMapRunner(); StringStringRunner stringStringRunner = new StringStringRunner(); MapStringRunner mapStringRunner = new MapStringRunner(); public Result create(String name) throws Exception { for (Field field : this.getClass().getDeclaredFields()) { if(field.getName().equals(name)){ for (Type anInterface : field.getType().getGenericInterfaces()) { ParameterizedType anInterface1 = (ParameterizedType) anInterface; if(anInterface1.getRawType().equals(Runner.class)){ Type[] types = anInterface1.getActualTypeArguments(); return new Result( types[0], types[1], (Runner)field.getType().newInstance() ); } } } } throw new Exception("NoFoundThisMember "+name); } @Data @AllArgsConstructor public static class Result{ Type inputType; Type outputType; Runner runner; } }
使用起来也很方便,是我自己比较满意的一个方案。
public static void main(String[] args) throws Exception { //创建Runner工厂 RunnerFactory runnerFactory = new RunnerFactory(); //从工厂创建mapStringRunner实例 Result result = runnerFactory.create("mapStringRunner"); Runner runner = result.runner; //输出 java.util.Map<java.lang.String, java.lang.String> System.out.println(result.inputType); //输出 class java.lang.String System.out.println(result.outputType); //将字符串转换成Runner实例需要的Object并执行 String inputJson = "{/"name/":/"Tom/"}"; Object input = JSONObject.parseObject(inputJson, result.inputType); Object res = runner.execute(input); //输出 String System.out.println(res.getClass().getSimpleName()); //输出 {name=Tom} System.out.println(res); }
但最后,我们团队还是选择了方案1,就是GetInpuType()那套方案。。。