前言
本文主要介绍java语言的三个特性:类型协变和逆变,动态代理和静态代理,注解。
协变和逆变
借用Treant的博文,逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:
- 如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类);
- f(⋅)是逆变(contravariant)的,当A≤B时,有f(B)≤f(A)成立;
- f(⋅)是协变(covariant)的,当A≤B时, 有f(A)≤f(B)成立;
- f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。
通俗地讲,逆变使得转换后类型变宽(父类转子类),协变使得转换后类型变窄(子类转父类)。
在Java中,数组是协变的:
1 2
| Number[] numbers = new Integer[10]; Integer[] integers = new Number[10];
|
泛型则是不变的
1 2 3
| List<Object> numbers = new ArrayList<Integer>(); List<Integer> numbers = new ArrayList<Object>(); List<Integer> numbers = new ArrayList<Integer>();
|
但是泛型可以通过通配符号?来实现协变和逆变。
泛型协变
1
| List<? extends Object> numbers = new ArrayList<Integer>();
|
泛型逆变
1
| List<? super Integer> numbers = new ArrayList<Object>();
|
换句话说,extends确定了泛型的上界,而super确定了泛型的下界。
而在方法的参数和返回值上,传入的参数应该是参数的子类或者本身,而返回的参数应该是父类或者本身。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| static Number method(Number num) { return 1; } Object result = method(new Integer(2)); Number result = method(new Object()); Integer result = method(new Integer(2)); 在Java 1.4中,子类覆盖(override)父类方法时,形参与返回值的类型必须与父类保持一致:
class Super { Number method(Number n) { ... } } class Sub extends Super { @Override Number method(Number n) { ... } } 从Java 1.5开始,子类覆盖父类方法时允许协变返回更为具体的类型:
class Super { Number method(Number n) { ... } } class Sub extends Super { @Override Integer method(Number n) { ... } }
|
代理
先看如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public interface IFruit { void eat(); } public class Apple implements IFruit { @Override public void eat() { System.out.println("You are eating Apple!"); } } public class Orange implements IFruit { @Override public void eat() { System.out.println("You are eating Orange!"); } }
|
有个IFruit接口,分别有两个类实现了IFruit接口,有一天产品经理过来和你说需求变更了,现在需要在每个IFruit的实现类的eat方法打印一句话。如果只有两个类,这难不倒你,尽管忘代码里添加就可以了,但是如果有一百甚至一千个这样的类呢?这就需要用到代理了。
静态代理
我们可以新建一个这样的代理类
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class StaticProxy implements IFruit { private IFruit mOrig ; public StaticProxy(IFruit orig) { mOrig = orig ; } @Override public void eat() { mOrig.eat(); System.out.println("add one line!"); } }
|
然后再调用Proxy类的eat()方法,同样能达到目的。问题又来了,如果我不仅修改IFruit,还修改其他的接口比如IAnimal、IRobot等接口,而且这样的接口也同样有成百上千个呢?这就需要用到java的动态代理了
动态代理
java动态代理需要实现InvocationHandler接口,原来类的所有方法的调用前都会调用DynamicProxy.invoke方法。
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
| public class DynamicProxy implements InvocationHandler { private Object mOrig ; public DynamicProxy(Object orig) { mOrig = orig ; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(mOrig, args); System.out.println("add one line!"); return result; } } public class MainTest {
public static void main(String[] args) { Apple apple = new Apple(); ((IFruit)dynamicProxy(apple)).eat(); staticProxy(apple).eat(); } private static IFruit staticProxy(IFruit fruit) { return new StaticProxy(fruit); } private static Object dynamicProxy(Object fruit) { return Proxy.newProxyInstance( fruit.getClass().getClassLoader(), fruit.getClass().getInterfaces(), new DynamicProxy(fruit) ); } }
|
在invoke方法中,我们可以执行原来的方法(eat),当然也能加入自己的代码逻辑。通过代码我们可以看到,动态代理类无需实现IFruit接口,这样的好处是可以节省很多的代码。
小结
动态代理和静态代理功能上并无差别。动态代理只是做了进一步的封装。使用代理模式可以增强原来方法的功能,通过代理类的Proxy方法可以轻松修改原来的代码逻辑,结合反射可以达到更改某些系统API的目的,Android插件开发中,DroidPlugin可以说是把这种思想运用到了极致。
注解
Java中的注解(Annotation),也称元数据,JDK1.5引入,主要用来对类、变量、方法、方法参数等进行注释说明。
Java中主要有如下四个类型的注解
- @Documented 表示该注解可以包含在javadoc中
- @Retention 标明注解的声明周期(源码、class文件、运行时)
- @Target 注解可以使用在哪些地方(方法、类、变量等)
- @Inherited – 是否允许子类继承该注解
注解除了对变量、方法等进行说明,还能结合反射完成更强大的功能。
Android中经常使用findViewById来查找一些控件,Butternife可以通过注解的方式注入代码使得View和id自动绑定,类似代码如下
1 2 3 4 5 6 7 8 9 10 11
| public class MainActivity extends AppCompatActivity { @Bind(R.id.text_view) private TextView tv ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); InjectHelper.inject(this); tv.setText("hello boys!"); } }
|
我们也可以通过使用注解和反射的方式完成类似buffernife的功能。
第一步
先新建一个注解
1 2 3 4 5
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Bind { int value(); }
|
Target本身也是一个注解,是用来标明我们新建的注解可以使用在哪些地方的。而Retention这个注解是用来标明注解的生命周期,声明成RetentionPolicy.RUNTIME则表用该注解在运行时也会一直保留。
第二步
通过反射获取对应值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class InjectHelper { public static void inject(Activity acitvity) { try { Field[] fields = acitvity.getClass().getDeclaredFields(); for (Field field : fields) { Bind bind = field.getAnnotation(Bind.class); if (bind != null) { int id = bind.value(); View v = acitvity.findViewById(id); field.setAccessible(true); field.set(acitvity, v); } } } catch (Exception e) { e.printStackTrace(); } } }
|
这样就完成了相应注入操作,也能达到和butternife相应的功能。但是代码中用到了反射,效率会比butternife慢,因为butternife是在编译时期生成相应代码的,运行时性能几乎不会有影响。
参考