java的几个特性

前言

本文主要介绍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

时间: 2024-08-07 04:33:48

java的几个特性的相关文章

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

JAVA面向对象三大特性详解 一.封装 1.概念: 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问. 2.好处: 只能通过规定的方法访问数据.     隐藏类的实例细节,方便修改和实现. 3.封装的实现步骤 需要注意:对封装的属性不一定要通过get/set方法,其他方法也可以对封装的属性进行操作.当然最好使用get/set方法,比较标准. A.访问修饰符 从表格可以看出从上到下封装性越来越差. B.this关键字 1.this关键字代表当前

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

一.this关键字,使用的情形,以及如何使用. 1.使用的情形 类中的方法体中使用this  --初始化该对象 类的构造器中使用this --引用,调用该方法的对象 2.不写this,调用 只要方法或者构造器中  不存在成员变量与局部变量同名的情况,可直接不写this 否则方法中或构造器中使用的就是局部变量 3.static 静态方法不能调用this,不能调用任何非static修饰的成员变量 或者方法 二.java方法中  参数传递特性 1.基本数据类型--实际是新增变量,并赋值而已   不过代

Atitit..jdk&#160;java&#160;各版本新特性&#160;1.0&#160;1.1&#160;1.2&#160;1.3&#160;1.4&#160;1.5(5.0)&#160;1.6(6.0)&#160;7.0&#160;8.0&#160;9.0&#160;attilax&#160;大总结

Atitit..jdk java 各版本新特性 1.0 1.1 1.2 1.3 1.4 1.5(5.0) 1.6(6.0) 7.0 8.0 9.0 attilax 大总结 1.1. Java的编年史2 1.2. Java版本:JDK 1.02 1.3. Java版本:JDK 1.13 1.4. Java版本:JDK 1.2 (Java 2)4 1.4.1. 1999年5 1.4.2. 2000年5 1.5. Java版本:JDK 1.35 1.5.1. 2001年6 1.5.2. 2002年7

《深入理解Java虚拟机 JVM高级特性...》核心笔记

深入理解Java虚拟机 JVM高级特性与最佳实践(第二版) 核心笔记 JAVA 环境: JAVA虚拟机高级特性: 一:java内存区域与内存异常 一):运行数据区     1:程序计数器(Program Counter Register),也称"PC寄存器" A:用来指示需要执行哪条指令的.(在汇编语言中,CPU在得到指令之后,程序计数器便自动加1或者根据                    转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令.) B:由于在JVM中,多线程

Java SE 6 新特性: HTTP 增强--转

概述 Java 语言从诞生的那天起,就非常注重网络编程方面的应用.随着互联网应用的飞速发展,Java 的基础类库也不断地对网络相关的 API 进行加强和扩展.在 Java SE 6 当中,围绕着 HTTP 协议出现了很多实用的新特性:NTLM 认证提供了一种 Window 平台下较为安全的认证机制:JDK 当中提供了一个轻量级的 HTTP 服务器:提供了较为完善的 HTTP Cookie 管理功能:更为实用的 NetworkInterface:DNS 域名的国际化支持等等. NTLM 认证 不可

Java 8的新特性—终极版

声明:本文翻译自Java 8 Features Tutorial – The ULTIMATE Guide,翻译过程中发现并发编程网已经有同学翻译过了:Java 8 特性 – 终极手册,我还是坚持自己翻译了一版(写作驱动学习,加深印象),有些地方参考了该同学的. Java 8 前言: Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级.在Java Code Geeks上已经有很多介绍Java 8新特性的文章,例如Playing with Java 8 – Lambdas

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

1.什么是继承 子类继承父类就是对父类的扩展,继承时会自动拥有父类所拥有的处private之外的所有成员作用:增加代码复用语法格式: class 子类名 extends 父类名{----}第9章 Java类的三大特性之一:继承例子: 1 //测试类,创建子类对象,可以直接调用属性和方法 2 public class testDog { 3 public static void main(String[] args) { 4 Dog dog = new Dog(); 5 dog.age=10; 6

Java SE 6 新特性: JMX 与系统管理

Java SE 6 新特性: JMX 与系统管理 2006 年底,Sun 公司发布了 Java Standard Edition 6(Java SE 6)的最终正式版,代号 Mustang(野马).跟 Tiger(Java SE 5)相比,Mustang 在性能方面有了不错的提升.与 Tiger 在 API 库方面的大幅度加强相比,虽然 Mustang 在 API 库方面的新特性显得不太多,但是也提供了许多实用和方便的功能:在脚本,WebService,XML,编译器 API,数据库,JMX,网

Java中的继承性特性

继承性是java中的第二特性之一.而继承性最为关键的地方为:代码重用性的问题,利用继承性可以从已有的类中继续派生出新的子类,也可以利用子类扩展出更多的操作功能. 继承性的实现代码为:class 子类  extends  父类{ } 有以下3点说明: 1.对于extends而言,需要不断的开展开来,但是为了理解上的方便,这些统一称之为:继承: 2.子类又称之为派生类: 3.父类又称之为超类(Super class): 以下代码为子类继承父类的属性及方法 class Person{ private

Java SE 6 新特性: 编译器 API

新 API 功能简介 JDK 6 提供了在运行时调用编译器的 API,后面我们将假设把此 API 应用在 JSP 技术中.在传统的 JSP 技术中,服务器处理 JSP 通常需要进行下面 6 个步骤: 分析 JSP 代码: 生成 Java 代码: 将 Java 代码写入存储器: 启动另外一个进程并运行编译器编译 Java 代码: 将类文件写入存储器: 服务器读入类文件并运行: 但如果采用运行时编译,可以同时简化步骤 4 和 5,节约新进程的开销和写入存储器的输出开销,提高系统效率.实际上,在 JD