美文网首页Effective Java
第37条:用标记接口定义类型

第37条:用标记接口定义类型

作者: NekoJiang | 来源:发表于2017-06-28 22:56 被阅读0次

定义

标记接口(marker interface):没有包含方法声明的接口,而只是指明一个类实现了具有某种属性的接口。例如,Serializable接口。
标记注解(marker annotation):特殊类型的注解,其中不包含成员。标记注解的唯一目的就是标记声明。例如,@Override。

优缺点对比

标记接口的优点:

  • 标记接口定义的类型是由北标记类的实例实现的;标记接口则没有定义的类型;
    这个类型允许你在编译时捕捉在使用标记注解的情况下要到运行时才能捕捉到的错误 .
    比如:就Serializable标记接口而言,如果他的参数没有实现该接口,那么ObjectOutputStream.writeObject(Object, obj)方法将会失败。令人不解的是ObjectOutputStream API的创建者在声明write方法的时候并没有利用Serializable接口。因此如果将没有实现Serializable的对象上调用ObjectOutputStream.write,只会在运行时失败。
pic2.jpg pic1.jpg
  • 标记接口可以更加精确的进行锁定。
    如果注解类型利用@Target(ElementType.TYPE)声明,它就可以被应用到任何类或者接口,假设有一个标记只是适用于特殊的接口实现,但它却可以被应用到类,如果定义成一个标记接口,就可以用它将唯一的接口扩展成适用的接口。

Set接口就可以说是这种有限制的标记接口。他只是用与Collection子类型,但是他不会添加除了Collection定义之外的方法。一般情况下,不把它当作标记接口,因为他改进了几个Collection的方法的契约,比如add、equals和hashCode。但是很容易想象只适用于某种特殊接口的子类型的标记接口,他没有改进接口的任何方法的契约。这种标记接口可以描述整个对象的某个约束条件,或者表明实例能够利用其他某个类的方法进行处理(就像Serializable接口表明实例可以通过ObjectOutputStream进行处理一样)。

声明set注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Set {

}
@Set
public class HashSet {
}

set接口:

public interface Set<E> extends Collection<E> {
    // Query Operations
/** @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified
     *         element
     * @throws UnsupportedOperationException if the <tt>add</tt> operation
     *         is not supported by this set
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this set
     * @throws NullPointerException if the specified element is null and this
     *         set does not permit null elements
     * @throws IllegalArgumentException if some property of the specified element
     *         prevents it from being added to this set
     */
    boolean add(E e);
}
public interface Collection<E> extends Iterable<E> {
/** @param e element whose presence in this collection is to be ensured
     * @return <tt>true</tt> if this collection changed as a result of the
     *         call
     * @throws UnsupportedOperationException if the <tt>add</tt> operation
     *         is not supported by this collection
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this collection
     * @throws NullPointerException if the specified element is null and this
     *         collection does not permit null elements
     * @throws IllegalArgumentException if some property of the element
     *         prevents it from being added to this collection
     * @throws IllegalStateException if the element cannot be added at this
     *         time due to insertion restrictions
     */
    boolean add(E e);
}

标记注解的优点

  • 标记注解可以通过默认的方式添加一个或者多个注解类型元素 , 给已被实用的注解类型添加更多地信息 。随着时间的推移,简单的编辑注解类型可以演变成更加丰富的注解类型。这种演变对于编辑接口而言则是不可能的,因为他通常不可能在实现接口之后再给他添加方法。
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * Created by Jiang Meiwei on 2017/6/27.
 */

@Retention(RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptinTest{
Class<? extends Exception> value();
}
package com.thunisoft.yilian.lafx.server.ajsc;

/**
 * Created by Jiang Meiwei on 2017/6/28.
 */
public class Sample {
    
    @ExceptinTest(ArithmeticException.class)
    public static void m1(){
        int i = 0;
        i = i/i;
    }
    @ExceptinTest(ArithmeticException.class)
    public static void m2(){
        int[] a = new int[0];
        int i = a[1];
    }

    @ExceptinTest(ArithmeticException.class)
    public static void m3(){
    }
}
package com.thunisoft.yilian.lafx.server.ajsc;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by Jiang Meiwei on 2017/6/28.
 */
public class RunTests {
    public static void main(String[] args) throws ClassNotFoundException{
        int tests = 0;
        int passed = 0;
        Class testClass = Class.forName("com.thunisoft.yilian.lafx.server.ajsc.Sample");
        for (Method m: testClass.getDeclaredMethods()) {
            if(m.isAnnotationPresent(ExceptinTest.class)){
                tests++;
                try {
                    m.invoke(null);
                }catch (InvocationTargetException wrappedEx) {
                    Throwable exc = wrappedEx.getCause();
                    Class<? extends Exception> excType = m.getAnnotation(ExceptinTest.class).value();
                    if(excType.isInstance(exc)){
                        passed++;
                    } else {
                        System.out.printf("Test %s failed: exppected %s, got %s%n", m, excType.getName(), exc);
                        
                    }
                } catch(Exception exc){
                    System.out.printf("Test %s failed: on exception%n", m);
                }
            }
        }

    }
}

输出结果:

pic3.jpg

做了如下更改:

pic4.jpg
  • 标记注解是更大注解机制的一部分 , 这意味这它在那些支持注解作为编程元素之一的框架中同样具有一致性

标记注解和标记接口的选择

  • 如果标记是应用到程序元素而不是类或者接口,就必须用注解;
  • 如果标记只应用给类和接口,那么就标记接口优先于标记注解;
  • 如果要定义一个任何新方法都不会与之关联的类型,标记接口是最好的选择;
  • 如果想要标记程序元素而非类和接口,考虑到未来可能要给标记添加更多的信息忙活着标记更适合于已经广泛使用了注解类型的框架,那么就该使用标记注解。

总结:从某种意义上说,本条目与第19条中“如果不想定义类型就不要使用接口”的说法相反。本条目接近的意思是说:如果想要定义类型,一定要使用接口

相关文章

网友评论

    本文标题:第37条:用标记接口定义类型

    本文链接:https://www.haomeiwen.com/subject/rjoccxtx.html