前言

本文主要介绍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中,数组是协变的:

        Number[] numbers = new Integer[10];  // right
Integer[] integers = new Number[10]; // wrong

泛型则是不变的

        List<Object> numbers = new ArrayList<Integer>();  // wrong
List<Integer> numbers = new ArrayList<Object>(); // wrong
List<Integer> numbers = new ArrayList<Integer>(); // right

但是泛型可以通过通配符号?来实现协变和逆变。

泛型协变

 List<? extends Object> numbers = new ArrayList<Integer>();  // right

泛型逆变

 List<? super Integer> numbers = new ArrayList<Object>();  // right

换句话说,extends确定了泛型的上界,而super确定了泛型的下界。

而在方法的参数和返回值上,传入的参数应该是参数的子类或者本身,而返回的参数应该是父类或者本身。

static Number method(Number num) {
return 1;
} Object result = method(new Integer(2)); //correct
Number result = method(new Object()); //error
Integer result = method(new Integer(2)); //error

在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) { ... }
}

代理

先看如下代码

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方法打印一句话。如果只有两个类,这难不倒你,尽管忘代码里添加就可以了,但是如果有一百甚至一千个这样的类呢?这就需要用到代理了。

静态代理

我们可以新建一个这样的代理类

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方法。

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 {

    /**
* @param args
*/
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自动绑定,类似代码如下

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的功能。

第一步

先新建一个注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Bind {
int value();
}

Target本身也是一个注解,是用来标明我们新建的注解可以使用在哪些地方的。而Retention这个注解是用来标明注解的生命周期,声明成RetentionPolicy.RUNTIME则表用该注解在运行时也会一直保留。

第二步

通过反射获取对应值

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是在编译时期生成相应代码的,运行时性能几乎不会有影响。

参考

Java中的逆变与协变

第六节:协变和逆变

Difference between <? super T> and <? extends T> in Java

Java 注解

Annotation

java的几个特性的更多相关文章

  1. 第9章 Java类的三大特性之一:继承

    1.什么是继承 子类继承父类就是对父类的扩展,继承时会自动拥有父类所拥有的处private之外的所有成员作用:增加代码复用语法格式: class 子类名 extends 父类名{…………}第9章 Ja ...

  2. 谈谈Java面向对象的三大特性

    Java面向对象的三大特性就是指封装.继承.多态了. 一.封装: 概念:封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式. (举例:笔记本电脑就是一个封装体,Java语言中最小的封装体就是函数 ...

  3. java 对象的this使用 java方法中参数传递特性 方法的递归

    一.this关键字,使用的情形,以及如何使用. 1.使用的情形 类中的方法体中使用this  --初始化该对象 类的构造器中使用this --引用,调用该方法的对象 2.不写this,调用 只要方法或 ...

  4. [转] Java 8的新特性

    简介 毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库.工具和JVM等方面的十多个新特性.在本文中我们将学习这些新特性,并用实际的例子 ...

  5. 在Android项目中启用Java 8的部分特性--Lambda & Method References

    Android N发布时同时发布了一个新的编译工具Jack(AS2.1+支持),基于Jack我们可以使用Java 8 的部分特性,在低版本机器上能使用的更少,同时Jack也有诸多不完善,工具链的改变难 ...

  6. JAVA基础——面向对象三大特性:封装、继承、多态

    JAVA面向对象三大特性详解 一.封装 1.概念: 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问. 2.好处: 只能通过规定的方法访问数据. ...

  7. Java中的继承性特性

    继承性是java中的第二特性之一.而继承性最为关键的地方为:代码重用性的问题,利用继承性可以从已有的类中继续派生出新的子类,也可以利用子类扩展出更多的操作功能. 继承性的实现代码为:class 子类 ...

  8. Java 8的新特性—终极版

    作者:杜琪[译] 原文链接:http://www.jianshu.com/p/5b800057f2d8 1. 简介 毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本 ...

  9. 这是一篇关于:以时间表的形式来介绍Java如何演变至今,以及Java版本的一些特性的分享

    这是一篇关于:以时间表的形式来介绍Java如何演变至今,以及Java版本的一些特性的分享: Java版本,功能和历史 原文[英]:https://javapapers.com/core-java/ja ...

  10. java的3大特性

    java的3大特性 1.继承: * 继承是从已有类得到继承信息创建新类的过程. * 提供继承信息的类被称为父类(超类.基类):得到继承信息的类被称为子类(派生类). * 继承让变化中的软件系统有定的延 ...

随机推荐

  1. 团队冲刺Alpha(七)

    目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:翟丹丹 组员7:何家伟 组员8:政演 组员9:黄鸿杰 组员10:刘一好 组员11:何宇恒 展示 ...

  2. iOS runLoop 理解

    目录 概述 run loop modes 一.概述 run loop叫事件处理循环,就是循环地接受各种各样的事件.run loop是oc用来管理线程里异步事件的工具.一个线程通过run loop可以监 ...

  3. [poj] 2318 TOYS || 判断点在多边形内

    原题 给出一个矩形玩具箱和其中隔板的位置,求每个玩具在第几个隔间内(保证没有在线上的玩具) 将玩具按x轴排序,记录当前隔板的编号,每次判断是否需要右移(左移)隔板(因为是有序的,所以移动次数左右不厚超 ...

  4. python下载链接内容

    下面代码下载京东注册码,可接收参数num dir 可以将连接构造成其它网址,比如移动联通网上营业厅的验证码都是固定网址+13位时间戳的结构. #!/usr/bin/python #code utf-8 ...

  5. 基于WEB的机器人远程控制

    1.前进后退左转右转控制: 2.视频传输,为了保证视频的流畅性,选择相机支持格式中图像最小,帧率最低的:并对视频进行处理,将15帧处理成5帧,从而降低传输数据量: 3.地图显示及导航控制: 地图在三维 ...

  6. ACdream 1029 前缀和

    Multiplication Time Limit: 2000/1000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Others) Subm ...

  7. hibernate用注解的方式实现orm

    hibernate 有两种方式实现把一张表映射成一个对象,一种是配置文件的方式,一种是注解的方式.这里用hibernate提供的注解的方式实现一个对象和一张表之间的对应. 思路: 首先在hiberna ...

  8. 【ZOJ4062】Plants vs. Zombies(二分)

    题意:有n个植物排成一排,标号为1-n,每株植物有自己的生长速度ai,每对植物浇一次水,该株植物就长高ai, 现在机器人从第0个格子出发,每次走一步,不能停留,每一步浇一次水,总共可以走m步,问最矮的 ...

  9. poj 2079 Triangle (二维凸包旋转卡壳)

    Triangle Time Limit: 3000MS   Memory Limit: 30000KB   64bit IO Format: %I64d & %I64u Submit Stat ...

  10. [9018_1963][IOI_1998]Picture

    题目描述 N(N<5000) 张矩形的海报,照片和其他同样形状的图片贴在墙上.它们的边都是垂直的或水平的.每个矩形可以部分或者全部覆盖其他矩形.所有的矩形组成的集合的轮廓称为周长.写一个程序计算 ...