前言
本文主要介绍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中,数组是协变的:
| 12
 
 | Number[] numbers = new Integer[10];  Integer[] integers = new Number[10];
 
 | 
泛型则是不变的
| 12
 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确定了泛型的下界。
而在方法的参数和返回值上,传入的参数应该是参数的子类或者本身,而返回的参数应该是父类或者本身。
| 12
 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) { ... }
 }
 
 | 
代理
先看如下代码
| 12
 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方法打印一句话。如果只有两个类,这难不倒你,尽管忘代码里添加就可以了,但是如果有一百甚至一千个这样的类呢?这就需要用到代理了。
静态代理
我们可以新建一个这样的代理类
| 12
 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方法。
| 12
 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自动绑定,类似代码如下
| 12
 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的功能。
第一步
先新建一个注解
| 12
 3
 4
 5
 
 | @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)
 public @interface Bind {
 int value();
 }
 
 | 
Target本身也是一个注解,是用来标明我们新建的注解可以使用在哪些地方的。而Retention这个注解是用来标明注解的生命周期,声明成RetentionPolicy.RUNTIME则表用该注解在运行时也会一直保留。
第二步
通过反射获取对应值
| 12
 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是在编译时期生成相应代码的,运行时性能几乎不会有影响。
参考