最近在看《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(被序列化)。
标记接口定义的类型是由标记类的实例实现的;标记注解则没有定义这样的类型。在开发阶段或者编译阶段就会提示出需要的类型。
标记接口和标记注解都各自有用处。如果要定义一个任何新方法都不会与之关联的类型,标记接口就是最好的选择。如果想要标记程序元素而非类和接口,考虑到为了可能要给标记添加更多的信息,或者标记要适合于已经广泛使用了注解类型的框架,那么标记注解就是正确的选择。