@throws
標簽在文檔中說明違反參數值限制時拋出的異常。/**
* Returns a BigInteger whose value is {@code (this mod m}). This method
* differs from {@code remainder} in that it always returns a
* <i>non-negative</i> BigInteger.
*
* @param m the modulus.
* @return {@code this mod m}
* @throws ArithmeticException {@code m} ≤ 0
* @see #remainder
*/
public BigInteger mod(BigInteger m) {
if (m.signum <= 0)
throw new ArithmeticException("BigInteger: modulus not positive");
BigInteger result = this.remainder(m);
return (result.signum >= 0 ? result : result.add(m));
}
private static void sort(long array[],int offset,int length){
//assert 斷言
assert array!=null;
assert offset>=0&&offset<=array.length;
assert length>=0&&length<= array.length-offset;
}
sort(null,0,0);
//Exception in thread "main" java.lang.AssertionError
斷言默認是關閉的
Java編譯中啟用斷言:-enableassertions
,簡寫為-ea
IDEA啟用斷言:Run-->Edit Configuration-->VM Options-->添加-ea
保護性地設計程序(假設類的客戶端會盡其所能破壞類的約束條件)
//下面的類聲稱可以表示一段不可變的時間周期
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 getStart(){
return start;
}
public Date getEnd() {
return end;
}
}
上面那個Period類表面上看是不可變類,並且加了約束條件:周期的起始時間不能再結束時間之後。但由於Date類是可變的,很容易就違反這個約束條件。如下。
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.set(2008,Calendar.JULY,8);
Date start = calendar.getTime();
calendar.set(2016,Calendar.JULY,3);
Date end = calendar.getTime();
Period period = new Period(start,end);
start.setYear(2017);//修改了Period類
Date start1 = period.getStart();
System.out.println(start1.getYear());//2017
}
為避免類的實例的內部信息受到攻擊,可對構造器中的每個可變參數進行保護性拷貝。
public Period(Date start,Date end){
// if(start.compareTo(end)>0)
// throw new IllegalArgumentException(start+"after"+end);
// this.start = start;
// this.end = end;
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if(this.start.compareTo(this.end)>0)
throw new IllegalArgumentException(start+"after"+end);
}
雖然替換構造方法能避免上述的攻擊,但仍然可以改變Period實例。它的訪問方法提供了對其可變內部成員的訪問能力。
period.getEnd().setYear(1998);//修改了Period實例
對於後一種攻擊,只需修改方法,使它返回可變內部域的保護性拷貝即可:
public Date getStart(){
return new Date(start.getTime());
}
public Date getEnd() {
return new Date(end.getTime());
}
參數的保護性拷貝:
下面的例子就是使用可變對象作為Map的鍵,導致Map的約束條件被破壞。
Calendar calendar = Calendar.getInstance();
calendar.set(2008,Calendar.JULY,8);
Date start = calendar.getTime();
Map<Date,String> map = new HashMap<>();
map.put(start,start.getYear()+"");
start.setYear(1999);
String s = map.get(start);
System.out.println(s);
//修改後,使用Date.getTime()返回的long值作為Map內部時間的表示方法
Map<Long,String> newMap = new HashMap<>();
newMap.put(start.getTime(),start.getYear()+"");
結論:
最好使用不可變對象作為對象內部的組件。
類具有從客戶端得到或返回客戶端的可變組件,類就必須保護性地拷貝這些組件。
拷貝的成本受到限制,且類信任它的客戶端不會不恰當地修改組件,就可在文檔中指明客戶端的職責是不得修改受到影響的組件。
可參看Google Java Style中的方法命名標准
目標是4個參數,或者更少。 相同類型的長參數序列格外有害。 容易弄錯順序,但程序仍可以編譯和運行。
縮短過長的參數列表的三種辦法:
- 1.分解成多個方法
- 2.創建輔助類
- 3.Builder模式
對於參數類型,優先使用接口而不是類。
methodA(Map<K,V> map);
//methodA(HashMap<K,V> map);
對於boolean參數,優先使用兩個元素的枚舉類型。
public enum TemperatureScale{
F,C
}
Thermometer.newInstance(TemperatureScale.C);
public class CollectionClassifier {
public static String classify(Set<?> set){
return "Set";
}
public static String classify(List<?> list){
return "List";
}
public static String classify(Collection<?> collection){
return "Unknown Collection";
}
public static void main(String[] args) {
Collection<?> [] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String,Object>().values()
};
for(Collection collection:collections){
System.out.println(classify(collection));
}
}
}
//
//Unknown Collection
//Unknown Collection
//Unknown Collection
classify方法被重載(overloaded)了,而調用哪個重載方法是在編譯時做出決定的。for循環中的三次循環,參數的編譯時類型都是相同的:Collection<?>
。即使每次循環的運行時類型都是不同的。
重載方法(overloaded method)的選擇是靜態的,被覆蓋的方法(overridden method)的選擇是動態的。調用哪個被覆蓋的方法是在運行時做出決定的。
public class Overriding {
public static void main(String[] args) {
Wine [] wines = {
new Wine(),
new SparklingWine(),
new Champagne()
};
for(Wine wine:wines){
System.out.println(wine.name());
}
}
}
class Wine{
String name(){
return "wine";
}
}
class SparklingWine extends Wine{
@Override
String name() {
return "sparkling wine";
}
}
class Champagne extends SparklingWine{
@Override
String name() {
return "champagne";
}
}
//output:
//wine
//sparkling wine
//champagne