49. 必要时进行防御性拷贝

假如客户端会想方设法的破坏类的安全性,防御性拷贝就是任客户端风吹雨打,我自屹然不动的功夫

 public final class Period {	private final Date start;	private final Date end;	public Period(Date start,Date end) {		if(start.compareTo(end) > 0){			throw new IllegalArgumentException(start + " after " + end);
		}		this.start = start;		this.end = end;
	}	
	public Date start(){		return start;
	}	
	public Date end(){		return end;
	}	//remainder omitted}复制代码

上面这个例子时间是不可改变的。然而Date类本身是可变的

Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
end.setYear(78);
System.out.println(period.end());							
复制代码

我们可以修改一下构造函数

 // 有了新的构造方法后,前面的攻击将不会对 Period 实例产生影响public Period(Date start, Date end) {	this.start = new Date(start.getTime());	this.end = new Date(end.getTime());	if (this.start.compareTo(this.end) > 0)		throw new IllegalArgumentException(this.start + " after " + this.end);
}复制代码

虽然替换构造方法成功地抵御了先前的攻击,但是仍然可以对 Period 实例进行修改,因为它的访问器提供了 对其可变内部结构的访问

// Second attack on the internals of a Period instanceDate start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // Modifies internals of p复制代码

为了抵御第二次攻击,只需修改访问器以返回可变内部字属性的防御性拷贝:

public Date start() {return new Date(start.getTime());
}public Date end() {return new Date(end.getTime());
}复制代码

总之,如果一个类有从它的客户端获取或返回的可变组件,那么这个类必须防御性地拷贝这些组件。如果拷贝的成本太高,并且类信任它的客户端不会不适当地修改组件,则可以用文档替换防御性拷贝,该文档概述了客户端不得修改受影响组件的责任

竞争条件(Race Condtions)

由上面不安全的类会引法竞争条件,一般分为二种漏洞

  • Time of Check Versus Time of Use (TOCTOU)

应用运行的过程中,在某个操作之前,比如写文件,都会检查一下文件是否存在,在检查与真正的写入之间的间隔就是一个可以被利用的Race Condition,恶意软件可以将用户检查的文件替换成自己的文件,这样数据就泄露了。

  • Signal Handling

处理信号的过程中,是随时可以被另一个信号的处理打断的,如果在处理一个信号的过程中另一个信号到来,那么这个过程会被马上中断,这样,系统就会处于一种未知的状态。

50.仔细设计方法签名

  • 仔细选择方法的名称,像fangfa1,fangfa2这种名字就不应该出现了
  • 方法太多也不好,特别是在接口中
  • 避免过长的参数列表,一般是四个或者四个以下

避免过长参数的技术有三种,一种是将一个方法分解为多个方法,每一个方法只需要参数的一个子集,另外一种是创立辅助类来保存参数数组,这些辅助类通常是静态成员类,最后一种是,建议从对象构造到方法采用builder模式

  • 对于参数的类型,优先选择接口而不是类
  • 与布尔型参数相比,优先使用两个元素枚举类型

51. 明智审慎地使用重载

重载(overloaded)方法之间的选择是静态的,而重写(overridden)方法之间的选择是动态的。方法重载不能通过基于运行时类型自动调度到适当的方法,文中例子

public class CollectionClassifier {	public static String classify(Set<?> s) {		return "Set";
	}	public static String classify(List<?> lst) {		return "List";
	}	public static String classify(Collection<?> c) {		return "Unknown Collection";
	}	public static void main(String[] args) {
		Collection<?>[] collections = { new HashSet<String>(), new ArrayList<BigInteger>(),				new HashMap<String, String>().values() };		for (Collection<?> c : collections)
			System.out.println(classify(c));
	}
}// 程序输出三次Unknown Collection,实际我们想输出打印 Set,然后是 List 和 Unknown Collection 字符串复制代码

修改这个程序之后

public static String classify(Collection<?> c) {     return c instanceof Set ? "Set" :
     c instanceof List ? "List" : "Unknown Collection";
}复制代码

一个安全和保守的策略是永远不要导出两个具有相同参数数量的重载,例如,考虑 ObjectOutputStream 类。对于每个基本类型和几个引用类型,它都有其 write 方法的变体。这些变体都有不同的名称,例如 writeBoolean(boolean) 、 writeInt(int) 和 writeLong(long) ,而不是重载 write 方法。与重载相比,这种命名模式的另一个好处是,可以为 read 方法提供相应的名称,例如readBoolean() 、 readInt() 和 readLong() 。 ObjectInputStream 类实际上提供了这样的读取方法。

52. 明智审慎地使用可变参数

可变参数就是可以接收0个或者多个指定类型的参数,通常用...表示

// 接收一系列int类型并且返回它们的和static int sum(int... args) {int sum = 0;for (int arg : args)
sum += arg;return sum;
}复制代码

53. 返回空的数组或集合,不要返回 null

如果方法返回null,客户端必须包含特殊情况代码来处理 null 返回的可能性,除非我们可以证明 null 返回是不可能的,如果客户端忽略可能会造成NullPointerException的可能

54. 明智审慎地返回 Optional

Optional英文表示可选择的,随意的,在java程序设计中是jdk1.8提出的一个表示不可变类的容器,不是空的包含值被称为存在(present)

可能在某一些情况下不能返回 T ,但是可以声明为返回一个 Optional 。这允许该方法返回一个空结果,表明不能返回有效的结果。返回 Optional 的方法比抛出异常的方法更灵活、更容易使用,而且比返回 null 的方法更不容易出错。

public static <E extends Comparable<E>> E max(Collection<E> c) {		if (c.isEmpty())			throw new IllegalArgumentException("Empty collection");
		E result = null;		for (E e : c)			if (result == null || e.compareTo(result) > 0)
				result = Objects.requireNonNull(e);		return result;
	}复制代码

上面的例子是如果集合为空就抛出异常,根据以前港的,我们可以替换为Optional<E>

public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {		if (c.isEmpty())			return Optional.empty();
		E result = null;		for (E e : c)			if (result == null || e.compareTo(result) > 0)
				result = Objects.requireNonNull(e);		return Optional.of(result);
	}复制代码

Optional.empty() 返回一个空的 Optional, Optional.of(value) 返回一个包含给定非 null 值的Optional。 将 null 传递给 Optional.of(value) 是一个编程错误,这个方法会抛出一个异常

注意事项
  • 永远不要通过返回 Optional 的方法返回一个空值
  • 容器类型,包括集合、映射、Stream、数组和Optional,不应该封装在 Optional 中
  • 永远不应该返回装箱的基本类型的 Optional。
使用Optional的时机

如果可能无法返回结果,并且在没有返回结果,客户端还必须执行特殊处理的情况下,则应声明返回 Optional 的方法(返回Optional  并非没有成本。 Optional 是必须分配和初始化的对象,从 Optional 中读取值需要额外的迂回。)

55. 为所有已公开的 API 元素编写文档注释

Java 编程环境使用 Javadoc 实用程序简化了这一任务。 Javadoc 使用特殊格式的文档注释 (通常称为 doc 注 释),从源代码自动生成 API 文档。

现在多的是Swagger自动生成文档,maven依赖

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.6.1</version>    
</dependency>    

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.6.1</version>
</dependency>    
复制代码

56. 最小化局部变量的作用域

通过最小化局部变量的作用域,可以提高代码的可读性和可维护性,并降低出错的可能性

使用方式

用于最小化局部变量作用域的最强大的技术是再首次使用的地方声明它。就是说在那里使用就在那里声明

几乎每个局部变量声明都应该包含一个初始化器

如果循环终止后不需要循环变量的内容,那么优先选择 for 循环而不是 while 循环。

57. for-each 循环优于传统 for 循环

for-each 循环 (官方称为“增强的 for 语句”) 通过隐藏迭代器或索引变量来消除混乱和出错的机会

不能使用for-each的时候
  • 有损过滤(Destructive filtering)——如果需要遍历集合,并删除指定选元素,则需要使用显式迭代器,以便可以调用其 remove 方法。 通常可以使用在 Java 8 中添加的 Collection 类中的 removeIf 方法,来避免显式遍历。
  • 转换——如果需要遍历一个列表或数组并替换其元素的部分或全部值,那么需要列表迭代器或数组索引来替换元素的值。
  • 并行迭代——如果需要并行地遍历多个集合,那么需要显式地控制迭代器或索引变量,以便所有迭代器或索引变量都可以同步进行 (正如上面错误的 card 和 dice 示例中无意中演示的那样)。

58. 了解并使用库

通过使用标准库,你可以利用编写它的专家的知识和以前使用它的人的经验。

在每个主要版本中,都会向库中添加许多特性,了解这些新增特性是值得的。

ThreadLocalRandom

JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。ThreadLocalRandom不是直接用new实例化,而是第一次使用其静态方法current()

ThreadLocalRandom t=ThreadLocalRandom.current();

System.out.println(t.nextInt(50));//随机生成0~50的随机数,不包括50

System.out.println(t.nextInt(30, 50));//随机生成30~50的随机数,不包括50复制代码

Random:产生一个伪随机数(通过相同的种子,产生的随机数是相同的

59. 若需要精确答案就应避免使用 float 和 double 类

float 和 double 类型主要用于科学计算和工程计算。它们执行二进制浮点运算,该算法经过精心设计,能够在很大范围内快速提供精确的近似值。但是,它们不能提供准确的结果,也不应该在需要精确结果的地方使用。float 和double 类型特别不适合进行货币计算,因为不可能将 0.1(或 10 的任意负次幂)精确地表示为 float 或 double。

一般来说货币运算使用bigdecimal,int,long来计算,但是使用bigdecimal存在两个问题,一是计算与原始的算术类型相比很不方便,最后就是速度慢,但是,它可以完全控制舍入,当执行需要舍入的操作时,可以从八种舍入模式中进行选择

60. 基本数据类型优于包装类

Java 有一个由两部分组成的类型系统,包括基本类型(如 int、double 和 boolean)和引用类型(如 String 和List)。每个基本类型都有一个对应的引用类型,称为包装类型。

基本类型和包装类型的区别

  • 基本类型只知道它们的值,包装类型有和他的值不同的标识
  • 包装类型可以包含null,基本类型不行
  • 基本类型比包装类型更节省时间和空间

不能将==用于包装类型

// 下面这个程序会报错,修复这个问题非常简单,只需将 i 声明为 int 而不是 Integer。public class Unbelievable {  static Integer i;  public static void main(String[] args) {     if (i == 42)
      System.out.println("Unbelievable");
   }
}复制代码

它在计算表达式 i==42 时抛出NullPointerException,这里出现异常的原因是:i是 Integer,而不是 int 数,而且像所有非常量对象引用字段一样,它的初值为 null。当程序计算表达式 i==42 时,它是在比较 Integer 与 int。在操作中混合使用基本类型和包装类型时,包装类型就会自动拆箱,如果一个空对象自动拆箱的话就会报空指针异常

啥时候使用包装类

  • 第一个是作为集合中的元素、键和值。不能将基本类型放在集合中
  • 用包装类型作为类型参数
  • 反射方法调用时,必须使用包装类型
未完待续。。。。