《Effective Java》阅读笔记(二)

最近在看《Effective Java》这本书,顺便就记录一些笔记,记录一下书中的一些知识点以及对知识点的总结。一般情况会记录所有的知识点,但是知识点太过简单或者无归纳点总结的就不做详细记录。

五、泛型

java1.5增加了泛型;本章会用到的一些术语:

术语示例
参数化的类型List<String>
实际参数化类型String
泛型List<E>
形式类型参数E
无限制通配符类型List<?>
原生态类型List
有限制类型参数<E extends Number>
递归类型限制<E extends Comparable<E>>
有限制通配符类型List<? extends Number>
泛型方法static <E> List<E> asList(E[] a)
类型令牌String.class

23、请不要在新代码中使用原生态类型

java1.4之前的集合都使用的是原生态类型,eg:List list;造成安全性问题,且表示不明确。在java1.5之后,如果不确定或者不关心实际的参数类型,可以使用通配符?,也可以使用Object当做参数化类型。但就是不要使用原生态类型。

24、消除非受检警告

使用泛型编程时,会遇到许多编译器警告:非受检强制转换警告、非受检方法调用警告、非受检普通数组创建警告、非受检转换警告。如果无法消除警告同时又能证明引起警告的代码是类型安全的,就可以使用类似SuppressWarings(“unchecked”)来解除警告。SuppressWarings可以用在任何力度的级别中,应该始终在尽可能的范围内使用SuppressWarings注解,可以具体到一个局部变量。例如下例:

SuppressWarings("unchecked")//这样的是不正确的
public T[] copy(……){
     return (T[]) Arrays.copy(……);
}

应该定义一个局部变量,来保持返回值,然后加注解

public T[] copy(……){
     SuppressWarings("unchecked")
     T[] result = (T[]) Arrays.copy(……);
     return result ;
}

25、列表优于数组

1、数组是协变的,如果Sub为Super的子类,那么Sub[]就是Super[]的子类型。而泛型是不可变的,不同的泛型E、T就是完全不同的两个类型。
2、数组是具体化的,在运行时才知道并检查他们的元素类型约束。而泛型是在编译时需要检测他们的类型信息(保证是同一类型),编译完之后在运行时,就丢弃或擦除他们的元素类型信息。在编程过程中,遇到泛型数组时应该用列表代替数组。

26、优先考虑泛型

27、优先考虑泛型方法

28、利用有限制通配符来提升API的灵活性

要记住基本原则:producer-extends,consumer-super。而所有的Comparable和comparator都是消费者。

public static <T extentds Comparable<? super T>> T max(List<? extends T> list){
    Iterator<? extends T> = list.iterator();
    T result = i.next();
    while(i.hasNext()){
        T t = i.next();
        if(t.compareTo(result) > 0)
            result = t;
    }
    return result;
};

理解:producer就是提供数据的,consumer是处理数据的,比如上例中,list就是提供数据,所以使用extends,Comparable是用来compareTo,处理数据,所以使用super。

29、优先考虑类型安全的异构容器

//类型安全的异构容器(每个键都可以有一个不同的参数化类型)。eg:(Map并不能保证键和值之间的类型关系,即不能保证每个值的类型都与键的类型相同)
public class Favorites {
    private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();

    /**
     保证key和value之间的类型关系是一致的
    */
   public <T> void putFavorites(Class<T> type,T instance) {
        if (type == null)
            throw new NullPointerException("Type is null");
        favorites.put(type, type.cast(instance));
    }

    public <T> T getFavorites(Class<T> type) {
        return type.cast(favorites.get(type));
    }

    public static void main(String[] args) {
        Favorites favorites = new Favorites();
        favorites.putFavorites(String.class, "Effective Java");
        favorites.putFavorites(Integer.class, 30306);
        favorites.putFavorites(Class.class, Favorites.class);
        String[] strArr = {"J", "a", "v", "a"};
        favorites.putFavorites(String[].class, strArr); // 不能用在不可具体化的类型中,比如List<String>
        //favorites.putFavorites(String.class, "Hel, World"); // key必须不一样,否则会覆盖。
        String str = favorites.getFavorites(String.class);
        int favoriteInteger = favorites.getFavorites(Integer.class);
        Class<?> favoriteClass = favorites.getFavorites(Class.class);
        String[] arr = favorites.getFavorites(String[].class);
        for (String s : arr)
            System.out.print(s);
        System.out.println();
        System.out.printf("%s %d %s%n", str, favoriteInteger, favoriteClass);
    }
}

这样就能保证put和get的都是同一种类型。

六、枚举和注解

30、用enum代替int常量

java1.5之前经常会使用int,string来表示枚举,但是安全性和性能方面都没有保证。1.5之后推出了enum,就可以妥善的解决那些问题,而且枚举类型还允许添加任意的方法和域,并实现任意的接口。
下面就是一个比较全面的枚举的使用:

public class EnumTest{
    public interface Operation{//接口,让枚举来实现
        double apply(double x,double y);
    }
    public enum Operations implements Operation{
        PLUS("+")  {
            @Override
            public double apply(double x, double y) {
                return x + y;
            }
            @Override
            void printSelf() {
                System.out.println("+");
            }},
        MINUS("-")  {
            @Override
            public double apply(double x, double y) {
                return x - y;
            }
            @Override
            void printSelf() {
                System.out.println("-");
            }},
        TIMES("*")  {
            @Override
            public double apply(double x, double y) {
                return x * y;
            }            
            @Override
            void printSelf() {
                System.out.println("*");
            }},
        DIDIDE("/")/*构造函数,需要传入操作符*/{
            @Override
            public double apply(double x, double y) {
                return x / y;
            }
            @Override
            void printSelf() {
                System.out.println("/");
            }};

        private final String symhol;
        Operations(String symhol) {/*构造函数,需要传入操作符*/
            this.symhol = symhol;
        }
        @Override
        public String toString(){ return symhol;}
        abstract void printSelf();//定义一个abstract方法,让具体的枚举对象去实现
    }
    public static void main(String[] args) {
        Operations o = Operations.PLUS;
        double result = o.apply(12.0, 133.0);
        System.out.println(result);
        o.printSelf();
    }
}

一般来说,枚举会优先使用comparable而非int常量。与int常量相比,枚举在装载和初始化枚举时会有空间和时间的成本,在一些资源受限制的设备上需要注意。

31、用实例域代替序数

枚举都有一个ordinal方法,用来返回没一个枚举常量在类型中index,但是尽量避免使用ordinal方法来处理逻辑,因为枚举常量的顺序调整之后,它的ordinal返回值也会跟着变化,就会造成不必要的错误。可以通过构造函数传递一个int值给枚举常量,然后通过一个共有方法来返回该int值,这样不管如何调整枚举的顺序,返回的int值是不变的。

public enum DotaV{
    FIRSTBLOOD(1), DOUBLEKILL(2),
    TRIPLEKILL(3), ULTRAKILL(4),
    KILLINGSPREE(5), DOMINATING(6),
    MEGAKILL(7), UNSTOPABLE(8),
    WHICKEDSICK(9), MONSTERKILL(10),
    GODLIKE(11), HOLYSHIT(12),
    RAMPAGE(13);
    private final int value;
    DotaV(int value) {
        this.value = value;
    }
    public int getValue(){return value;};
}

32、使用EnumSet代替位域
33、使用EnumMap代替序数序列
34、使用接口模拟可伸缩的枚举

注解知识补充

1、概述
注解可以定义到方法上,类上,一个注解相当与一个类,就相当于实例了一个对象,加上了注解,就相当于加了一个标志。
常用的注解:

  • @Override:表示重新父类的方法,
    这个也可以判断是否覆盖的父类方法,在方法前面加上此语句,如果提示的错误,那么你不是覆盖的父类的方法,要是提示的没有错误,那么就是覆盖的父类的方法。
  • @SuppressWarnings(”deprecation”):取消编译器的警告(例如你使用的方法过时了)
  • @Deprecated:在方法的最上边也上此语句,表示此方法过时,了,或者使用在类上面
    2、自定义注解
    (1)格式
    权限 @interface 注解名称 { }
    步骤:
    定义注解类—>定义应用注解类的类—>对应用注解类的类进行反射的类(这个类可以另外定义,也可以是在应用注解类中进行测试)
    (2)声明周期
    格式:例如:@Retention(RetentionPolicy.CLASS)
    在自定一的注解类上定义周期,@Retention(参数类型) 参数类型是RetentionPolicy
    RetentionPolicy.CLASS:类文件上,运行时虚拟机不保留注解
    RetentionPolicy.RUNTIME:类文件上,运行时虚拟就保留注解
    RetentionPolicy.SOURCE:源文件上,丢弃注解
    SuppressWarnings和Override是RetentionPolicy.SOURCE,
    Deprecated是在RetentionPolicy.RUNTIME,要向运行时调用定义的一样,那么必须是RetentionPolicy.RUNTIME,
    默认的都是RetentionPolicy.CLASS:
    3、指定目标
    格式:例如:方法上@Target(ElementType.METHOD)
    定义的注解可以注解什么成员。如果不声明此注解,那么就是可以放到任何程序的元素上。可以是包,接口,参数,方法,局部变量,字段…等。

35、注解优于命名模式

命名模式的缺点

  • 文字拼写容易出错
  • 无法确保它们只用于相应的程序元素上
  • 没有提供将参数值与程序元素关联起来的好方法
    注解

  • 可以作为标记注解,例如Junit中使用@Test来标记测试方法

  • 和反射结合起来使用,编写一些框架,例如基于Sqlite的轻量级orm框架

下面是一个本人的一个实际项目中用到的代码片段

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

定义列名注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
    public String value();
}

定义表名注解

@Table(DBHelper.Table_Area)
public class Area implements Serializable{
    public String getOrderby() {
        return orderby;
    }
    public void setOrderby(String orderby) {
        this.orderby = orderby;
    }
    @Column(DBHelper.Table_Column_name)
    protected String name;
    @Column(DBHelper.Table_Column_host)
    protected int host;
……
}
应用到数据对象类中,类上面加Table注解,用于后面查询数据根据类名就可以反射出表名,数据域上面加有Column注解,用于查询出数据之后,可以利用反射根据class反射出定义的数据域,然后对应到列名,利用反射将数据封装起来等。
private void fillInstance(Cursor query, T t) {
    Field[] fields = t.getClass().getDeclaredFields();
    for(Field item:fields)
    {
        item.setAccessible(true);
        String key="";
        Column column = item.getAnnotation(Column.class);
        if(column!=null)
        {
            key=column.value();
            int columnIndex=query.getColumnIndex(key);
            String value=query.getString(columnIndex);
            Object content = null;
            try {
                int parseInteger = Integer.parseInt(value);
                content = parseInteger;
            } catch (NumberFormatException e) {
                content = value;
            }
            try {
                item.set(t,content);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

一个利用注解和反射封装查询数据的例子,代码写的大致方向是对的,但比较粗糙,明白意思即可。
36、坚持使用Override注解
可以避免不必要的麻烦,比如方法写错
37、用标记接口定义类型
标记接口是没有包含方法声明的接口,而只是致命一个类实现了具有某种属性的接口,例如Serializable,表示实现这个接口,可以被写到ObjectOutputStream(被序列化)。
标记接口定义的类型是由标记类的实例实现的;标记注解则没有定义这样的类型。在开发阶段或者编译阶段就会提示出需要的类型。
标记接口和标记注解都各自有用处。如果要定义一个任何新方法都不会与之关联的类型,标记接口就是最好的选择。如果想要标记程序元素而非类和接口,考虑到为了可能要给标记添加更多的信息,或者标记要适合于已经广泛使用了注解类型的框架,那么标记注解就是正确的选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值