【effective Java读书笔记】方法(一)
前言:
依然是从安全性的角度讲述方法:主要讲述两点:一、有效性检查的两种方式:主动抛出异常,断言。二、保护性拷贝的意义和使用
第38条:检查参数的有效性
一、违反参数值限制主动抛出异常:
代码一:
public class Sample {
public BigInteger mod(BigInteger m) {
if (m.signum() <= 0) {
throw new ArithmeticException("Modulus <= 0:" + m);
}
return null;
}
}
代码一执行类:
public class Test {
@org.junit.Test
public void test() {
Sample sample = new Sample();
sample.mod(new BigInteger("-1"));
}
}
结果:运行时异常。
java.lang.ArithmeticException: Modulus <= 0:-1
at com.function.Sample.mod(Sample.java:8)
at com.function.Test.test(Test.java:12)
二、断言检查有效性(-ea运行参数):
不了解断言的小伙伴看这里代码二:
public class Sample2 {
public static void sort(long a[],int offset,int length){
assert a!=null;
assert offset >=0 && offset<= a.length;
assert length >=0 && length<=a.length-offset;
System.out.println("success exec");
}
}
代码二执行类:
public class Test {
@org.junit.Test
public void test2(){
long[] a = new long[5];
for (int i = 0; i < a.length; i++) {
a[i]=(long) (i/1.0);
}
Sample2.sort(a, -1, 3);
}
}
-ea命令执行结果:
Exception in thread "main" java.lang.AssertionError
at com.function.Sample2.sort(Sample2.java:6)
at com.function.Test.main(Test.java:28)
提示这一行代码有问题,因为offset = -1,不满足断言。assert offset >=0 && offset<= a.length;
注意:以上两种限制判断方法,均写在方法开头处。第39条:必要时进行保护性拷贝:
为什么要写保护性拷贝?保护性拷贝是什么?
第一个问题:面对客户的不良行为仍然能保持健壮性的类,需要采用保护性拷贝。
第二个问题,保护性拷贝是什么,我们先看一个举例如下:
代码三:
public 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;
}
@Override
public String toString() {
return "Period [start=" + start + ", end=" + end + "]";
}
}
代码三的执行方法:
@org.junit.Test
public void test3(){
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
System.out.println(p.toString());
end.setYear(78);
System.out.println(p.toString());
}
执行结果:
Period [start=Sun Jun 18 22:59:06 CST 2017, end=Sun Jun 18 22:59:06 CST 2017]
Period [start=Sun Jun 18 22:59:06 CST 2017, end=Sun Jun 18 22:59:06 CST 1978]
说好的约束呢?第二行结果显示,开始时间大于结束时间,因为被引用型变量修改了原始对象。那么此处需有保护性拷贝出场了。为避免内部信息受到攻击,对构造器每个参数进行保护性拷贝。修改后代码如下:
public class Period2 {
private final Date start;
private final Date end;
public Period2(Date start, Date end) {
//先保护性拷贝
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
//再检查有效性,避免多线程问题
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + "after" + end);
}
}
public Date start() {
return start;
}
public Date end() {
return end;
}
@Override
public String toString() {
return "Period [start=" + start + ", end=" + end + "]";
}
}
代码执行类
@org.junit.Test
public void test4(){
Date start = new Date();
Date end = new Date();
Period2 p = new Period2(start, end);
System.out.println(p.toString());
end.setYear(78);
System.out.println(p.toString());
}
执行结果:
Period [start=Sun Jun 18 23:39:06 CST 2017, end=Sun Jun 18 23:39:06 CST 2017]
Period [start=Sun Jun 18 23:39:06 CST 2017, end=Sun Jun 18 23:39:06 CST 2017]
这回虽然修改了引用型变量的值,但是一段时间的Period对象不变。依然受到开始时间《结束时间的约束。完美?nonono!差点忘了,还可以通过这种方式攻击,看代码:@org.junit.Test
public void test5(){
Date start = new Date();
Date end = new Date();
Period2 p = new Period2(start, end);
System.out.println(p.toString());
end.setYear(78);
p.end().setYear(78);
System.out.println(p.toString());
}
倒数第二句新增代码:
p.end().setYear(78);
由于提供了访问这个对象的引用,那么便能访问到保护性拷贝后的对象,对它进行操作,得到下面的结果:Period [start=Sun Jun 18 23:44:07 CST 2017, end=Sun Jun 18 23:44:07 CST 2017]
Period [start=Sun Jun 18 23:44:07 CST 2017, end=Sun Jun 18 23:44:07 CST 1978]
当然,这样的结果我们不能忍,修改如下代码即可:public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
现在我们的一段时间的Period对象才能算的上是安全,完美!
这个问题告诉我们,当一个类中包含引用型变量的时候一定要认真考虑清楚它的作用,然后考虑是否需要进行保护性拷贝处理,使得它安全可靠。