博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java的几个特性
阅读量:4702 次
发布时间:2019-06-10

本文共 5242 字,大约阅读时间需要 17 分钟。

前言

本文主要介绍java语言的三个特性:类型协变和逆变,动态代理和静态代理,注解。

协变和逆变

借用的博文,逆变与协变用来描述类型转换(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 numbers = new ArrayList
(); // wrong List
numbers = new ArrayList
(); // wrong List
numbers = new ArrayList
(); // right

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

泛型协变

List
numbers = new ArrayList
(); // right

泛型逆变

List
numbers = new ArrayList(); // right

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

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

static Number method(Number num) {    return 1;}Object result = method(new Integer(2)); //correctNumber result = method(new Object()); //errorInteger 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是在编译时期生成相应代码的,运行时性能几乎不会有影响。

参考

转载于:https://www.cnblogs.com/jasonkent27/p/5931559.html

你可能感兴趣的文章
C# 两个datatable中的数据快速比较返回交集或差集
查看>>
关于oracle样例数据库emp、dept、salgrade的mysql脚本复杂查询分析
查看>>
adb shell am 的用法
查看>>
iOS10 UI教程视图和子视图的可见性
查看>>
FindChildControl与FindComponent
查看>>
中国城市json
查看>>
android下载手动下载Android SDK
查看>>
C++学习:任意合法状态下汉诺塔的移动(原创)
查看>>
leetcode133 - Clone Graph - medium
查看>>
一点小基础
查看>>
UNET学习笔记2 - 高级API(HLAPI)
查看>>
"ORA-00942: 表或视图不存在 "的原因和解决方法[转]
查看>>
Oauth支持的5类 grant_type 及说明
查看>>
C#中用DateTime的ParseExact方法解析日期时间(excel中使用系统默认的日期格式)
查看>>
W3100SM-S 短信猫代码发送 上
查看>>
Linux IO模式及 select、poll、epoll详解
查看>>
Log4j知识汇总
查看>>
C# winform 使用DsoFramer 创建 显示office 文档
查看>>
找工作的一些感悟——前端小菜的成长
查看>>
C#委托和事件的应用Observer模式实例
查看>>