Know Why Never do.html
先知道基本編程基本規則,才知道何時能夠打破java
[TOC]程序員
靜態工廠提交代碼的可讀性,而且讓調用者沒必要爲選擇什麼參數構造器煩惱編程
靜態工廠可以避免建立多的重複對象設計模式
靜態工廠可以返回那些你自定義該對象的子對象(好比private的,更靈活)數組
public class Service{
private Service(){};//Prevents instantiation
private static final Map<String,Provider> providers =
new ConcurrentHashMap<String,Provider>();
//Provider registraion API
public static void registerDefaultProvider(Provider p){
registerProvider(name,p);
}
public static void registerProvider(String name,Provider p){
providers.put(name,p);
}
//Provider unregistration API
public static void unreginsterProviders(){
providers.remove();//不穿參數所有清空
}
public static void unreginsterSelectProvider(String name){
providers.remove(name);//移除某個
}
//Service asscess API
public static Service newInstance(){
return newInstance(DEFAULT_PROVIDER_NAME);
}
public static Service new Instance(String name){
Provider p = providers.get(name);
if(p==null) throw Exception;
return p.newService();
}
}
複製代碼
靜態工廠在參數化類型實例時更加簡潔瀏覽器
Map<String,List<String>> m = new HashMap<String,List<String>();
==>結合泛型
Map<String,List<String>> m = HashMap.newInstance();
public static <K,V> HashMap<K,V> newInstance(){
return new HashMap<K,V>;
}
複製代碼
常見靜態工廠方法舉例緩存
ValueOf ---返回實例和其參數具備相同值,類型轉換方法
of --- valueOf 簡寫
getInstance --- 惟一的實例
newInstance --- 返回不一樣的實例
getType --- 返回對象類型
newType --- 返回不一樣的對象類型
複製代碼
建立帶參數實例選擇 | 優勢 | 缺點 |
---|---|---|
重疊構造器 | 適合<=3參數肯定,如自定義view,開銷低 | 參數過多時,可讀性差,混亂和出錯 |
JavaBean模式 | 可讀性好,多參不容易出錯 | 出現不一致狀態,違法類的不可變原則,線程不安全 |
Builder模式 | 適合>3,可讀性強,靈活,參數約束,可擴展 | 額外開銷,重複代碼 |
//使用重疊構造器
mNativeAdLoader = new NativeAdLoader(mContext,unitId,"ab:123",超時時間);
//定義重疊構造器
public class NativeAdLoader{
public NativeAdLoader(Context context,String unitId){
this(context,unitId,"");
}
public NativeAdLoader(Context context,String UnitId,String stragegy){
this(context,unitId,stragegy,0)
}
public NativeAdLoader(Context context,String unitId,String stragegy,long timeout){
this.context = context;
this.unitId = unitId;
this.stragegy = stragegy;
this.timeout = timeout;
}
}
複製代碼
//使用JavaBean
mNativeAdLoader = new NativeAdLoader();
mNativeAdLoader.setContext(mContext);
mNativeAdLoader.setUnitId(unitId);
mNativeAdLoader.setStrategy("ab123");
mNatvieAdLoader.setTimeout(超時時間);
//定義JavaBean
public class NativeAdLoader{
public NativeAdLoader(){}
public void setContext(Context context){
this.mContext = context;
}
public void setUnitId(String unitId){
this.unitId = unitId;
}
public void setStrategy(String strategy){
this.strategy = strategy;
}
public void setTimeout(long timeout){
this.timeout = timeout;
}
}
複製代碼
//使用構鍵器
mNativeAdLoader = new NativeAdLoader.Builder(mContext,"unitId")
.forNativeAdSourcesByStrategy("ab:123",超時時間)
.build();
//定義構建器
public class NativeAdLoader{
public static class Builder{
//必要參數
protected Context mContext;
private String mUnitId;
//可選參數
private String mStrategy;
private long mTimeout public Builder(Context context,String unitId){
this.mContext = context;
this.mUnitId = unitId;
}
public Builder forNativeAdSourcesByStrategy(String strategy,long timeout){
this.mStrategy = strategy;
this.mTimeout = timeout;
return this;
}
public NativeAdLoader build(){
return new NativeAdLoader(this)
}
}
public NativeAdLoader(Builder builder){
this.context = builder.context;
this.unitID = builder.unitID;
this.strategy = builder.strategy;
this.timout = builder.timeout;
}
}
複製代碼
常規化標準單例寫法安全
//Singleton with static factory
public class AdsManager{
//private(私有不可直接引用) + static(方便靜態方法) + final(最終對象)+ 類加載時實例化
private static final AdsManager INSTANCE = new AdsManager();
private AdsManager(){};//私有化
public static AdsManager getInstance(){//getInstance 代表該對象的單例性
return INSTANCE;
}
//Remainder ommitted
}
複製代碼
解決多線程不安全問題(懶加載)性能優化
public class AdsManager{
private static transient AdsManager mInstance;//瞬時
private AdsManager(){};
public static AdsManager getInstance(){
if(mInstance == null){
synchronize(AdsManager.class){
if(mInstance ==null){
mInstance = new AdsManager();
}
}
}
return mInstance;
}
}
複製代碼
完美枚舉寫法(java 1.5上)
public enum AdsManager{
INSTANCE;
}
//簡潔可讀,防止屢次實例化,提供序列化機制,沒法反射攻擊
複製代碼
//工具類或者不想要被實例化的類
public class UtilsClass{
//將默認的構造器私有化並直接拋異常
private void UtilsClass(){
throw new AssertionError();
}
//Remainder omitted
}
複製代碼
避免屢次建立同一個對象
String s = new String("123"); ==> String s = "123";
//Date,Calendar,TimeZone 只建立一次
Class Person{
private final Date birthDate;
private static final Date BOOM_START;
private static final Date BOOM_END;
//靜態代碼塊寫法,類加載僅執行一次
static {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(...);
BOOM_START = gmtCal.getTime();
getCal.set(...);
BOOM_END = gmtCal.geTime();
}
//壞處若是該方法不用,就會依然建立以上對象
public boolean isBabyBoomer(){
return birthDate.compareTo(BOOM_START)>=0&&
birthDate.compareTo(BOOM_END)<0;
}
}
複製代碼
避免無心識的自動裝箱,優先使用基本數據類型
public static void main(String[] args){
Long sum = 0L;//申明時特別注意,避免使用裝箱
for(long i=0;i<Integer.MAX_VALUE;i++){
sum += i;
}
System.out.println(sum);
}
複製代碼
到底重用仍是建立附加對象考慮安全,風格,性能,清晰,簡潔,功能,不要一律而論
內存泄漏的常見來源-過時對象
//棧實現類
public class Stack {
private Object[] elements;
private int size=0;
private static final int DEFAULT_INITIAL_CAPACITY=16;
public Stack(){
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size++] = e;
}
public Object pop(){
if(size==0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;//eliminate obsolete reference
return result;
}
private void ensureCapacity(){
if(elements.length == size){
elements = Arrays.copyOf(elements,2*size+1);
}
}
}
//什麼是過時引用,活動部分,非活動部分?
//垃圾回收機制會自動回收非活動部份內存,而程序員要作的就是將該過時引用對象告訴垃圾,這是非活動部分。
複製代碼
內存泄漏的常見來源-緩存
1.WeakHashMap
表明弱引用緩存(緩存項過時自動刪除,緩存項生命週期由該鍵的外部對象決定時)
2.不容易肯定緩存的生命週期,緩存隨着時間推移變得愈來愈沒價值,定時清除Timer
或ScheduledThreadPoolExecutor
3.緩存添加新條目時清理,LinkedHashMap.removeEldestEntry
4.更復雜直接使用java.lang.ref???
內存泄漏的常見來源-監聽器或其餘回調
1.沒有顯式的取消註冊
2.不肯定何時取消,只保存回調的弱引用,常見保持成WeakHashMap中的鍵
//io流,connection,DB等
try{
//do something
}catch(Exception){
}finally{//must do
.close
.cancel
.terminate();
}
複製代碼
等價關係
自反性(reflexive) x!=null&&x.equals(x) ==>true
對稱性(symmetric) x!=null&&y!=null && x.equals(y) ==>y.equals(x)
傳遞性(transitive)x,y,z!=null && x.equals(y)&&y.equals(z) ==>x.equals(z)
一致性(consistent)x,y!=null &&x,y not change ==>x.equals(y) always true/false
非null性 (nevernull)x!=null && x.equals(null) ==>false
複製代碼
原理篇(略)
高質量equals訣竅
== 代替 equals ,是時候下降成本了
使用instanceof操做檢查類型
參數轉換類型(默認進行了instanceof測試)
這樣寫(先比較最有可能不同的)
Double.compare Arrays.equals
field == null? o.field == null : field.equals(o.field);
若是field 和 o.field 是相同對象的引用,更快
field==o.field || (field!=null&&field.equals(o.field))
複製代碼
寫完equals方法自問本身,是否對稱,傳遞,一致
覆蓋equals總要覆蓋hashCode
不要將equals申明的Object替換成其餘類型
//沒有覆蓋Object.equals
public boolean equals(MyCleass o){
...
}
Override 註解好處就會告訴你,編你不過
複製代碼
爲何非要這樣?
1.不這樣違反Object.hashCode的通用約定(若是兩個對象的equals方法是比較相等的,那麼調用這兩個對象任意一個hashCode方法必須產生相同的整數結果)
2.不這樣致使該類沒法結合基於散列的集合正常工做,好比HashMap、HashSet、HashTable
class PhoneNumber{
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(...){
//igore construct
}
@Override public boolean equals(Object O){
//igore equals
}
//Broken - NO HashCode Method
}
Map<PhoneNumber,String> m = new HashMap<PhoneNumber,String>();
m.put(new PhoneNumber(123,456,789),"lizhaoxiong");
==>m.get(new PhoneNumber(110,456,789)) 會返回null ***
==>m.get(new PhoneNumber(123,456,789)) 會返回"lizhaoixong"嗎?待test
若是一個對象的屬性值發生變化,那麼這個對象的hashCode值還相同嗎?test
複製代碼
解決方案
@Override
public int hashCode(){
int result = 17;
result = 31*result + areaCode;
result = 31*result + prefix;
result = 31*result + lineNumber;
resutl result;
}
思路:
隨便找個int 數;
不一樣域轉換int:
boolean(f?1:0)| byte,short,int (int)f |(int)(f^(f>>>32))
|Float.floatToIntBits(f)|Double.doubleToLongBits(f)+Float.floatToIntBits(f)|
|對象,遞歸調用equals和hashCode。|數組,Arrays.hashCode,遞歸每一個元素hashCode再組合排列
公式:result = 31*result+c;
1.爲何是31?
移位和減法代替乘法,JVM優化性能(31*i = (i<<5)-i)
2.直接result int的常亮值的壞處?
線性時間、平方級時間?降級了,散列表 ==> 鏈表
3.怎麼使用延遲初始化?
if(result=0)才進行計算,上面代碼
4.什麼是散列函數?
5.String、Integer、Date的hashCode方法返回的是什麼確切值?這樣的壞處?
複製代碼
爲何非要這麼作?
1.雖然這不是強制的,但這是規範。
2.若是不覆蓋試着比較下 類名@16進制散列 VS 自定義(簡潔、信息豐富、易於閱讀)
3.toString應該返回值得關注的信息 或 診斷信息
4.toString 要麼返回統一規範化格式信息 要麼你註釋說明好
提供一種無需調用構造器就能夠建立對象思路
拷貝的精確含義取決於該對象的類
要把這個搞懂,必須造成一個專題,就目前而言沒有多大意義
compareTo 和 equals 區別?
1.compareTo沒有在Object聲明
2.compareTo 是Comparable接口惟一方法,實現該接口比較對象數組Array.sort(a)
比較返回的是int類型值
3.當比較對象類型不一樣時直接拋出ClassCastException不進行比較,equals返回false
4.Comparable接口是參數化的,comparable方法是靜態類型,沒必要進行類型轉化和類型檢查,有問題直接沒法編譯,若是參數null,直接NullPointException
5.CompareTo方法中域的比較是順序比較而不是等同性比較。
啥時候用該接口?(collection imp)
因爲Java平臺的全部值類都實現該接口,當你在編寫一個值類須要明顯的內在排序關係,好比字母、數值、時間等,堅定考慮實現該接口
public interface Comparable<T>{
int compareTo(T t);
}
複製代碼
自定義Comparator專門用於你自定的排序
int 比較><= ,float和double用Double.compare/Float.compare
多個關鍵域比較,從最關鍵的開始,逐步進行到全部重要域,出現非0結果結束返回該結果
public int compareTo(People p){
if(inner < p.inner) return -1;
if(inner > p.inner) return 1;
if(out < p.out) return -1;
if(out > p.out) return 1;
...
return 0;
}
//肯定差值不大於INTERGER.MAX_VALUE 2^31 -1
public int compareTo(People p){
int diffInner = inner -p.inner;
if(diffInner!=0) return diffInner;
int diffOut = out - p.out;
if(diffOut!=0) return diffOut;
return 0;
}
複製代碼
儘量地下降可訪問性
Android 和 java 的公有類的成員變量編寫究竟是追求簡潔仍是安全
class Point {
public int x;
public int y;
}
VS
class Point {
private int x;
private int y;
public Point (int x,int y){
this.x = x;
this.y = y;
}
public void setX(int x){
this.x = x;
}
public void setY(int y){
this.y = y;
}
public int getX(){return this.x}
public int getY(){return this.y}
}
//構建時約束條件
public Point (int x, int y){
if(x<0&&y<0){
throw new IllegalArgumentException("x,y" + x + y)
}
this.x = x;
this.y = y;
}
複製代碼
如何把類變成不可變類?(參照String、BigInteger源碼)
不對外提供修改對象狀態的任何方法
保證類不會被擴展,好比聲明final類和private構造器(valueof)
聲明域都是final
聲明域都是private
使用函數對操做數運算並不修改它
public Complex add(Complex c){
return new Complex(x+c*x,y+c*y)
}
複製代碼
爲何要使用不可變類?
相比可變類的複雜空間和不肯定性,不可變穩定可靠
不可變對象本質是線程安全的,不要求同步
//爲了鼓勵多使用,應該這樣寫
public static final Complex ZERO = new Complex(0,0);
public static final Complex ONE = new Complex(1,0);
==>近一步單例化,提升內存佔用
複製代碼
複製代碼
對象能夠被自由共享(剔除拷貝構造器)
內部信息共享
Building blocks
就算是可變類,也要讓其可變性最小(好比TimerTask狀態只有執行和取消)
惟一缺點:建立這種對象代價可能很高,爲提升性能使用可變配套類
可變配套類
String 的可變配套類是StringBuilder和淘汰的StringBuffer
BigInteger 是BitSet
複製代碼
Why?
複合/轉發?
想要給現有類增長一個功能/特色,在新類中增長一個私有域,它引用現有類的一個實例,使現有類成爲新類的一個組件,這樣的設計叫作複合(composition)
新類的每一個方法均可以調用被包含的現有類的實例對應的方法,並返回它的結果,稱爲轉發(forwarding)
在轉發的基礎上,返回結果時作定製化處理,這就是裝飾/包裝(wrapper)
複製代碼
```java Class A { public void methodA(){}; public void methodB(){}; }
Class BWrapper { private A a; public BWrapper(A a){this.a=a};
public void wrapA(){
a.methodA();
}
public void wrapB(){
a.methodB();
}
public void wrapC(){
a.methodA();
a.methodB();
}
複製代碼
}
包裝類不適合回調框架(SELF問題)
慎用繼承!
用繼承前問問本身,二者 is-a的關係嗎?每一個B確實也是A嗎?不能肯定就不應擴展A。那麼就讓B應該包含A的一個私有實例,而且暴露較小、簡單的API,A本質不是B的一部分,只是它的實現細節。
(Java 違反這一原則的,棧Stack不是向量vector,屬性列表Properties不是散列表Hashtable)
#### 要麼爲繼承而設計提供文檔說明,要麼禁止繼承
爲何寫基類的要比寫實現的值錢?
好的API文檔應該描述給定方法<u>作了什麼工做</u>,而不是描述<u>如何作到的</u>
使用此類的方法要注意什麼,應該幹什麼,不能幹什麼
類必須經過某種形式提供適當的鉤子(hook)
編寫基於繼承的基類後必定要編寫子類進行測試
構造器決不能調用可被覆蓋的方法
對於並不是爲了安全子類化而設計的類要禁止子類化,有幾種方法
1. 聲明類爲final
2. 構造器私有化或者包級私有並增長公有靜態工廠代替構造器(單例)
3. 利用包裝類模式代替繼承實現更多功能
#### 接口優於抽象類
**區別**
- 抽象類可包含實現,接口不容許
- 抽象類依賴繼承進行子類實現,因爲Java單繼承原則喪失靈活性
- 接口靈活,擴展性強,而且非層次。高手用繼承,多用接口好
**包裝類設計模式體現着點**
**多接口模擬多重繼承**
**骨架實現**(先設計公有接口,可能抽象的基類簡單的公有實現,具體子類實現)
= 簡單實現+抽象類
**公有接口設計切記**:多多測試接口,否則一旦上線發行,就很差修改了
#### 接口只用於定義類型
**常量接口模式**
實現細節泄漏API、可能後期無價值、污染代碼(反面教程ObjectStreamConstants)
**有固定常量的需求**
1. 枚舉類型
2. 不可實例化的工具類(大量引用類名)
3. 靜態導入
#### 類層次優於標籤類
#### 函數對象表示策略
函數指針
策略模式
元素排序策略:經過傳遞不一樣比較器函數,就能夠得到各類不一樣的排列順序
函數對象:Java沒有提供函數指針,使用對象引用實現一樣功能,這種對象的方法是對其餘對象的操做,
且一個類導出一個方法,它的實例實際等同於指向該方法的指針,這樣的實例叫作函數對象。
#### 優先考慮靜態成員類
**嵌套類有四種:**
**靜態成員類(非內部類)**:
特色— 理解爲剛好聲明到類裏面的普通類,可訪問外圍類的成員,聲明私有也可供外圍類內部使用
場景— 公有輔助類,像枚舉,像Calculator.Operation.MINUS
**非靜態成員類(內部類)**:
特色— 每一個實例都包含一個額外指向外圍對象的引用,非靜態成員類強依賴外圍類,並在發行版本中不可由非靜轉靜
場景— Iterator、HashMap的Entry
**匿名類(內部類)**:
特色— 沒有名字,不是外圍類成員,聲明時實例化,不可Instanceof,不可擴展,必須簡短
場景— 動態建立函數對象,匿名的Sort方法的Comparator、建立過程對象Runnable、Thread、TimeTask
、靜態工程內部
**局部類(內部類)**:
特色— 使用少見
場景— 任何能夠聲明局部變量的地方
經典問題:靜態內部類和非靜態內部類的區別?
## 泛型
#### 請不要在新代碼中使用原生態類型
泛型的做用 — 在編譯時期告知是否插入類型錯誤的對象,而非再運行時期在報錯,泛型檢查。
泛型定義 — 聲明一個多個類型參數的類或者接口,java1.5版本開始支撐,例如List<String>。
原生態類型 — 不帶任何類型參數的泛型類型,List<E> 原生態時List,只是爲了和引入泛型以前的遺留代碼兼容
```java
//未使用泛型
private final Collection students = ...;
students.add(new Teather());
for(Iterator i = students.iterator;i.hasNext();){
Student s = (Student) i.next();//Throws ClassCastException
}
//使用泛型
private final Collection<Student> students = ...;
students.add(new Teather()); // 提早Error
//for-each 和for
for(Student s: students){}
for(Iterator<Student> i = students.iterator();i.hasNext();){
Student s = i.next();//No cast necessary
}
複製代碼
泛型和原生區別 — 泛型檢查
泛型子類化 — List 是原生態類型List的子類型,而不是參數化類型List 的子類型
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(44));
String s = strings.get(0);
//編譯經過,可是收到警告
private static void unsafeAdd(List list, Object o){
list.add(o);
}
//沒法編譯經過,List<String> 不是List<Object>的子類
private static void unsafeAdd(List<Object> list,Object o){
list.add(o);
}
複製代碼
無限制通配符類型 — Set<?> 不肯定和不關心實際參數類型
無限制通配符類型Set<?>和原生態類型Set之間有什麼區別?
安全的!不是什麼都能插入Set<?>,須要聲明 Extends ?
何時使用原生態(泛型信息能夠在運行時被擦除) — 1.類文字 2.和instanceof有關
static void newElements(Set s1,Set s2){
for(Object o1 : s1){
if(s2.contains(o1)){...}
}
}
//會報錯
static void newElements(Set<?> s1,Set<?> s2){
for(Object o1 : s1){
if(s2.contains(o1)){...}
}
}
//利用泛型來使用instanceof
if(0 instanceof Set){
Set<?> m = (Set<?>)o;
}
複製代碼
非受檢警告 — 強制轉化、方法調用、普通數組、轉化警告
簡單 ==>多用泛型,便可消除
難的 ==>@SuppressWarnings("unchecked") //必定要使用確認註釋緣由
數組和泛型的區別?
爲何List不是List的超類也不是子類?
由於類型信息已經被擦除了。
爲何第一是列表第二是數組?
錯誤發現原則— 能在編譯時發現就不要放到運行時發現,泛型列表(擦除—編譯時檢查類型信息,運行時擦除類型信息)優先於數組(運行時檢查元素類型約束),這個就像地鐵安檢同樣,你進入地鐵前對你進行檢查(泛型列表),而當你已經進入地鐵後,再來人對你進行檢查(數組),危險極大。
如何學習編寫泛型呢?
《具體查看Stack源碼》
elements = new E[DEFAULT_INITIAL_CAPACITY];
==>
@suppressWarnings("unChecked")
elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY];
E result = elements[--size];
==>
E result = (E)elements[--size]
==>
@SuppressWarnings("unCkecked")
E result = (E)elements[--size];
總之須要你確認沒有安全問題
class DelayQueue<E extends Delayed> implements BlockingQueue<E>;
E被稱之爲有限制的類型參數
每一個類型都是它的子類型
複製代碼
public static <E> Set<E> union(Set<E> s1,Set<E> s2){
Set<E> result = new HashSet<E>(s1);
result.addAll(s2);
return result;
}
//消除代碼chenyu
Map<String,List<String>> ana = new HashMap<String,List<String>>();
==>
Map<String,List<String>> ana = newHashMap();
public static <K,V> HashMap<K,V> newHashMap(){
return new HashMap<K,V>();
}
//遞歸類型限制Comparable接口
public interface Comarable<T>{
int compareTo(T o);
}
//根據元素的天然順序計算列表的最大值
public static <T extends Comparable<T>> T max(List<T> list){
Iterator<T> i = list.iterator();
T result = i.next();
while(i.hasNext()){
T t = i.next();
if(t.compareTo(result)>0){
result = t;
}
}
return result;
}
複製代碼
泛型參數類型特色?
提升靈活性的兩種方式?
PECS(producer-extends,consummer-super)
合理的選擇嚴格的參數類型、生產者參數、消費者參數,包裝類型安全和靈活性
public class Stack<E> {
public void push(E e);
public E pop();
}
+ pushAll
+ popAll
//子類沒法push進去,因爲Integer是Numble的子類,可是Integer插入不了聲明爲Numble的
public void pushAll(Iterable<E> src){
for(E e: src){
push(e);
}
}
有限制的通配符類型==>
public void pushAll(Iterable<? extends E> src){
for(E e: src){
push(e);
}
}
//若是聲明E是Object,因爲Object是Numble的父類,可是Numble卻沒法調用Object的
public void popAll(Collection<E> dst){
while(!isEmpty()){
dst.add(pop());
}
}
有限制的通配符類型==>
public void popAll(Connection<? super E> dst){
while(!isEmpty()){
dst.add(pop())
}
}
//升級eg1:
static <E> E reduce(List<? extends E> list ,Function<E> f);
//升級eg2:返回類型仍然爲Set<E> ,不要用通配符類型做爲返回類型,除非很是強調靈活性
public static <E> Set<E> union(Set<? extends E> s1,Set<? extends E> s2) //升級eg3: public static <T extends Comparable<? super T>> T max(List<? extends T> list) //注意類型匹配: max(List<? extends T> list){
Iterator<? extends T> i = list.iterator();
}
//無限制的類型參數,不能將元素放回剛剛從中取出的列表
public static void swap(List<?> list,int i,int j){
list.set(i,list.set(j,list.get(i)));
}
==>
調用swapHelper(List<E> list,int i,int j)
複製代碼
泛型使用場景
集合(Set、Map、List)、單元素容器(ThreadLocal、AtomicReference)
如何定製鍵類型呢?
Map<Class,Object> favorites = new HashMap,Object>()
爲何?
1.多數組最好轉化爲Map枚舉集合+泛型設計!
2.EnumMap內部使用了經過序數索引的數組,並隱藏了實現細節
3.數組最大的問題是沒法保證序數和數組索引之間的關係
4.嵌套的EnumMap關係(142頁)EnumMap<..., EnumMap<...>>。
5.使用Enum.ordinal是傻叉
//將全部花園的花根據種類打印出來
//1. 打印的map、key是type、vlaue是花對象集合(定義)
//2. 遍歷原有集合(type),重寫put
//3. == 遍歷花園獲得花對象
Map<Flower.Type, Set<Flower>> flowersByType =
new EnumMap<Flower.Type, Set<Flower>>(Flower.Type.class);
for(Flower.Type t: Flower.Type.values()) { --values方法能夠這樣嗎?返回的是key對應的對象嗎?
flowersByType.put(t,new HashSet<Flower>());
}
for(Flower f:garden){
flowersByType.get(f.type).add(f);
}
System.out.println(flowerByType);
複製代碼
ordinal方法返回每一個常量類型的數字位置(序數)
該方法被設計給EnumSet和EnumMap使用,請徹底避免自行使用ordinal方法
// 錯誤事例
public enum Ensemble{
ONE,TWE,THTREE...;
public int numberOfDay(){
return ordinal()+1;
}
}
// 正確事例
public enum Ensemble{
ONE(1),TWE(2),THREE(3)...;
private int num;
Ensemble(int day){num = day}
public int numOfDay(){return num;}
}
複製代碼
爲何?
// bad code
public class Text{
public static final int STYLE_BOLD = 1 << 0; //1
public static final int STYLE_ITALIC = 1 << 1; //2
public void applyStyles(int styles){...}
==> text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
}
//nice code
public class Text{
public enum Style{BOLD,ITALIC... }
public void applyStyles(Set<Style> styles){...}
==> text.applyStyles(EnumSet.of(Style.BOLD,Style.ITALIC));
}
複製代碼
1.基本數據類型的枚舉如何經過擴展枚舉實現呢
2.經過Collection<? Extends Operation> 這個有限制通配符類型
3.雖然沒法編寫可擴展枚舉類型,但能夠經過編寫接口即實現該接口實現進行模擬
命名模式的3個缺陷:
註解類型:
@Retention(RetentionPolicy.RUNTIME) -- 運行時保留
@Target(ElementType.METHOD) -- 做用域,在方法中才合理
public @interface Test {}
//Retention(生命範圍,源代碼,class,runtime)
//Documented,Inherited,Target(做用範圍,方法,屬性,構造方法等)
複製代碼
防止因爲重載或者或者無心識地覆蓋產生的覆蓋(有覆蓋需求就加上吧)
究竟是使用標記接口仍是標記註解?
參考:Serializable標記接口(被序列化接口)
如何處理參數和返回值?
如何設計方法簽名?
如何爲方法編寫文檔?
--專一可用性、健壯性、靈活性
1.錯誤發生前檢查參數有效性,下降調試工做難度
2.使用@throws標籤(tag)標明違反參數值限制會拋出的異常,如ArithmeticException
IllegalArgumentException、IndexOutOfBoundsException、NullPointerException
3.使用斷言作有效性檢查
4.只要有效性檢查有一次失敗,那麼你爲此作的努力即可以連本帶利的償還
/** * @throws NullPointerException if object is null */
public static <T> T requireNonNull(final T object, final String message) {
if (object == null) {
throw new NullPointerException(message);
}
return object;
}
複製代碼
不要使用Clone進行保護性拷貝
保護性拷貝是在檢查參數的有效性前進行的
Date類事可變的,把它交給如Date.getTime()返回long進行時間表示
重載是在編譯時期決定的 -- 重載方法當方法名稱相同,在調用時容易形成調此非此。
如何避免胡亂使用重載 -- 永遠不要使用兩個具備相同參數數目的重載方法。
在建立多個功能類似的,僅僅傳入參數類型不一樣的方法時,儘量經過方法命名區分而非參數類型區分。
編寫API時,請不要讓調用程序員感到混淆的,調用不明確,避免重載的致使難以發現的錯誤。
覆蓋是運行時期的,覆蓋更加安全。-- so 避免編譯時的類型影響
public static String classify(Collection<?> c){
return c instanceof Set ? "Set" :
c instanceof List ? "List" : "Unknow Collection"
}
複製代碼
當咱們編寫方法,明確了參數的類型可是不肯定參數的數量的時候,就用可變參數
可變參數的實現本質sum(int... args) 就是sum(int [] args)
==>先建立一個數組,數組的大小爲在調用方法傳遞的參數數量
//可變參數解決無參數問題
public static int min(int firstArg,int... args){
int min = firstArg;
for(int arg: args){
min = arg < min?arg:min;
}
return min;
}
複製代碼
可變參數是爲printf而設計,最主要的做用體如今printf和反射機制
Arrays.asList ==> Arrays.toString()
複製代碼
從性能角度,可變參數每次的調用都會致使一次數組的分配和初始化,so 靈活性 VS 性能
每次都得爲空指針考慮,因此請儘可能不要返回null,除非你是有意識的
永遠不會分配零長度數組的代碼寫法
//返回零長度的數組
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
public Cheese[] getCheeses[]{
return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}
//返回空集合
return Collections.emptyList();
//具體參考Collections的EmptyList系列的幾個內部類
複製代碼
How to Write Doc Comments
爲每一個被導出的類、接口、構造器、方法、域聲明增長文檔註釋
方法的文檔註釋:
,而且由標籤開頭的文字不用句點結束
{@code index>=0}
概要描述(Summary Description)-- 文檔註釋的第一句話,對你寫的API一句話描述體現你的總結能力了
更多說明:
討論局部變量的處理、控制結構、類庫的用法、各類數據類型的用法以及reflection 和 native method的用法
及優化和命名慣例
Why?
遍歷集合和遍歷數組for循環是比while循環更好,可是因爲迭代器和索引變量的存在很容易出錯
使用for-each循環徹底隱藏迭代器和索引變量,適合集合和數組
性能上稍有優點,它對數組的索引邊界值只是計算一次
嵌套遍歷時,內外數量不一致會拋出NoSuchElementException,嵌套循環可能須要在第一層循環內把遍歷的值記錄下來,而for-each完美的處理了嵌套的問題
advise!
若是要編寫類型表示一組元素,即便不讓其實現Collection,也要讓其實現Iterable,這樣就可使用for-each
沒法使用for-each的三種狀況!
過濾 -- 要刪除指定元素調用remove方法(修改)
替換 — 遍歷列表或數組時,要取代替換元素時 (修改)
平行迭代
Random三個缺點 ==> 僞隨機數生成器 Random.nextInt(int) ,So
advise!
一種辦法是複雜的使用BigDecimal,不方便較慢,要本身使用十進制小數點(貨幣)
還有一種辦法,是使用int(簡單)或者long(複雜)
Java分爲基本數據類型和引用類型,每一個基本數據類型都有一個引用類型
區別?
對於引用數據類型使用==操做符老是錯誤的
請把能聲明成基本數據類型變量,避免反覆的裝箱和拆箱,致使性能降低
何時使用引用類型呢?
不適合使用字符串的場景:
當是簡單輸出或者顯示使用字符串鏈接 +
但當拼接的量級別變大,或者使用for循環進行拼接請使用StringBuilder代替String
多使用StringBuilder的append方法
更通常的講,若是有合適的接口類型存在,那麼參數、返回值、變量和域都應該使用接口類型聲明
養成接口做爲類型的好習慣吧,使你的程序更靈活吧
Vector<Subscriber> subscribers = new Vector<Subscriber>();
==> good code :
List<Subscriber> subscribers = new Vector<Subscriber>();
好比ThreadLocal類的以前的HashMap 要換成 IdentityHashMap那不是一句話的事,並且不用擔憂影響
複製代碼
若是對象的類是基於類的框架(abstract class),就應當用相關的基類來引用這個對象,好比TimerTask
反射提供了經過程序訪問類的成員名稱、域類型、方法等信息的能力
反射的設計初衷:
爲了基於組件的應用建立工具設計,這類工具須要裝載類。普通應用程序在運行時不該該以反射方式訪問對象。目前反射機制使用場景:瀏覽器、對象監視器、代碼分析工具、解釋型的內嵌式系統、RPC系統。現實app中當編譯時沒法獲取到的類或者過期方法等
反射的缺點:
使用注意:當你編寫程序與編譯時的未知類一塊兒工做時
本地方法的三種用途:訪問特定平臺的機制,註冊表和文件鎖,訪問遺留代碼和數據能力,提供系統性能
使用本地方法提升性能時不值得提倡的,原來是可行的如今隨着JVM的塊和Java平臺完善,好比BigInteger
本地語言不是安全的,可能須要編寫膠合代碼,而且單調乏味和難以閱讀
本地代碼中的一個Bug可能讓你痛不欲生
名人名言:
不少計算機上的過失都被歸咎於效率(沒有必要達到的效率)
計較效率上的小小的得失,不成熟的優化纔是一切問題的根源
在優化方面咱們應該遵照兩條規則:第一,不要進行優化 第二,沒有絕對清晰的優化方案以前,請不要進行優化
advise!
API的設計對性能的影響是很是實際的,使用哪些性能優化過的API特別重要,保證接口的靈活性也特別重要
不要費力的編寫快速的程序,盡力編寫好的程序,速度天然而然就隨之而來
藉助性能方面的工具是不錯的手段
字面慣例的例子
標示符類型 | 例子 |
---|---|
包 | com.xiaoyu.util, org.xiaoyu.dao.impl |
類或者接口 | HttpServlet, AsyncTask(單詞首字母大寫) |
方法或者域 | toString, equal, isUpperCase(首單詞首字母小寫,以後單詞首字母大寫) |
常量域 | IP_ADDR(所有大寫,單詞之間加下劃線) |
局部變量 | stuNumber,mString(與方法命名相似) |
類型參數 | T,E,V,K等等 |
語法命名慣例
標示符類型 | 例子 |
---|---|
類 | 名稱或名稱短語,Timer,BufferedWriter,ChessPiece |
接口 | Collection,Comparator,Runnable,Iterable,Accessible |
註解 | BindingAnnotation,Inject,ImplementedBy,Singleton |
方法或者域 | 動詞或動詞短語,append或drawImage,返回boolean is開頭 isEmpty,isEnabled 方法返回非boolean對象的函數或屬性:speed(),color(),getTime(),getA+setA 轉換對象類型:toType,toString,toArray 返回view:asList 返回和調用對象同值基本類型方法:typeValue、intValue 靜態工廠的經常使用名稱:valueOf、getInstance、newInstance、getType、newType |
發揮異常優勢,提升程序的可讀性、可靠性和可維護性,介紹下異常的指導原則
異常的錯誤做用:
so,異常應該只用於異常的狀況,永遠不該該用於正常的控制流
「狀態測試方法」 「可識別的返回值」
Java 程序設計提供三種可拋出結構(throwable)
永遠不要定義Exception的子類,只會困擾API的用戶
catch 塊老是具備斷言失敗的特徵
標準意味着可被重用
可被重用異常排行榜
最讓人困惑的異常莫過於拋出的異常和執行任務沒有明顯的聯繫,每每由底層抽象拋出的異常?!
如何避免?— 異常轉譯
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
//異常鏈
try{
... //Use lower-level bidding
}catch(LowerLevelException cause){
throw new HigherLevelException(cause);
}
複製代碼
固然也不要濫用,能夠給低層傳遞參數前檢查高層方法的參數有效性,從而避免底層方法拋出異常
而若是沒法避免底層異常,讓高層繞開這些異常,隔離!並記錄下來
若是失敗的情形不容易重現,想要經過系統日誌分析會很是困難
so,編寫代碼時把細節打印出來
固然也不要刻意追求,錯就是錯了,另外API文檔應當清楚的指名對象將會處於什麼狀態
不考慮後果直接try catch是極其不負責任的行爲,起碼你解決不了,輸出些日誌出來也行
Synchronized 同一時刻,只有一個線程能夠執行某一個方法或者代碼塊
互斥的理解:當一個對象唄一個線程修改時,能夠阻止另外一個線程觀察到對象內部不一致的狀態(狀態一致性)
boolean域的讀寫操做是原子的,讓第一個線程輪詢(開始false),經過第二個線程改變(false)代表第一個線程終止本身了
i++不是原子操做
不共享可變數據或者共享不可變數據以及非要共享可變數據就必須執行同步
public class StopThread{
private static boolean stopRequested;
private static synchronized void requestStop(){
stopRequested = true;
}
private static synchronized boolean stopRequested(){
return stopRequested;
}
main(){
Thread backgroudThread = new Thread(
new Runnable(){
public void run(){
int i = 0;
while(!stopRequested())
i++;
}
}
);
backgroundThread.start();
TimeUnit.SECOND.sleep(1);
requestStop();
}
}
or ==>
private static volatile boolean stopRequested;//就不須要寫同步方法了
複製代碼
過分同步可能會致使性能下降、死鎖、不肯定行爲
死鎖緣由:它企圖鎖定某某,但它沒法得到該鎖
分析StringBuffer內部同步問題
分拆鎖、分離鎖、非阻塞併發控制
//代替new thread 的Executor Framework 三步曲
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(runnable);
executor.shutdown();
//輕載的服務器,夠用不須要設置啥
Executors.newCachedThreadPool
//大負載產品,固定線程數目的線程池
Executors.newFixedThreadPool
//最大限度的使用
ThreadPoolExecutor
複製代碼
Executor Famework 所作的工做就是執行
Collections Framework 所作的工做就是彙集
使用ScheduledThreadPoolExecutor代替Timer,更靈活。Timer只用一個線程,面對長期任務影響準確性,一旦線程拋出未被捕獲的異常,timer就會中止。而executor支持多個線程,並可以優雅的從拋出的未受檢異常任務中恢復。
應該用更高級的併發工具來代替 wait和notify(手動太困難)
併發java.util.concurrent:Executor Framework、Concurrent Collection、Synchronizer
併發集合:ConcurrentMap、BlockingQueue(生產者和消費者隊列)
同步器:CountDownLatch/CyclicBarrier—倒計時鎖存器、Semaphore
System.nanoTime更加準確更加精確,它不受系統的時鐘的調整影響(System.currentTimeMills)
線程安全的級別:
私有鎖,把鎖對象封裝在它所同步的對象中(private + final)適用於無條件線程安全
總之,有條件的線程安全必須寫文檔指名了「哪一個方法調用序列須要外部同步,而且得到哪把鎖」
雙層檢查 :減小延遲初始化的性能開銷,再加上volatile
不健壯、不可移植性
把堆棧軌跡定向一個特定域應用程序的日誌中Thread.setUncaughtExceptionHandler
將一個對象編碼成一個字節流,即序列化;相反,即爲反序列化。
序列化代理模式
一個類被序列化的直接開銷很是低,可是後期的長期開銷確是實實在在的,一旦一個類被髮布,大大下降這個類實現的靈活性。
舊版原本序列化一個類,在用新版本反序列化,每每會致使程序的失敗。
UID — 序列化版本serialVersionUID,若是你沒有聲明顯式的序列化UID,那麼一旦類發生改變,那麼自動生成的UID也會發生變化,兼容性遭到破壞,運行時致使InvalidClassException異常
反序列化做爲隱藏的構造器,具有和其餘構造器相同特色,很容易出現Bug和安全漏洞
隨着類的新版本發佈,相關測試負擔加劇(確保序列化和反序列化過程成功)
哪些類須要序列化(Date BigInteger、Throwable—異常從服務隊傳客戶端、Component—GUI的發送保持恢復、HttpServlet—回話狀態可緩存、存在目的爲了融於這種類型的框架)
哪些類不該該實現序列化(thread pool、爲繼承設計的類+用戶接口—爲擴展存在的,否則會揹負沉重負擔)
ReadObjectNoData
內部類不因該是實現序列化
transient表示的變量默認不序列化(瞬時),writeObject和readObject表示具體的序列化形式
@serial標籤
StringList
readObject方法至關於公有構造器,如同其餘構造器同樣須要檢查參數有效性,必要時對參數進行保護性拷貝
一個Singleton若是實現Serializable的話,就再也不是單例了,除非在readResolve作文章
當不能在客戶端擴展類編寫readObject或者writeObject時候,考慮使用序列化代理模式
瞭解規則,看到背後,打破規則,創造新規則