相信很多的Android開發者和我一樣,當初學習Android開發時,對Java的學習並不是非常深入;大致瞭解了類和物件是怎麼回事,對多執行緒及網路程式設計有了一個簡單的瞭解之後,便投入到了Android開發中;感覺當時瞭解的東西就夠用了,一些比較偏的點,遇到了在網上找一下就能解決問題了,總的來說不影響日常工作。
但是,隨著時間的流逝,慢慢感覺自己遇到了瓶頸,基礎的東西都會了;嘗試去學習一些進階的東西,卻發現非常的難;由於不瞭解註解和反射,第一次使用Retrofit框架的時候,完全就是一臉懵逼,搞不懂@是幹什麼用的;嘗試去解讀Glide的原始碼,由於缺乏對泛型及設計模式的瞭解,連Glide底層的網路請求時在哪裡實現都找不到;基礎不牢,寫程式碼總是挖坑……。
總之應了那句話,出來混總是要還的。想在這條道上長遠的走下去,曾經欠下的東西都得補回來。所以,這段時間對惡補了一寫Java基礎,總結了一些之前理解有偏差或錯誤的點,在這裡權當筆記記錄一下,之後又新的心得體會會持續更新。
基礎
基礎資料型別的範圍
型別 | 位數 | 值域 |
---|---|---|
boolean | JVM 決定 | true/false |
char | 16bit | 0~65535 |
byte | 8bit | -128~127 |
short | 16bit | -32768~32767 |
int | 32bit | -2147483648~2147483648 |
long | 64bit | 很大 |
float | 32bit | 範圍可變 |
double | 64bit | 範圍可變 |
引用變數
People man=new People();
People women=new People();
People boy=man;複製程式碼
man,women,boy應該稱為引用變數,它儲存的是存取物件的方法;引用變數並不是物件的容器,而是類似指向物件的指標。
以上賦值程式碼表達的意思,一個People型別的變數man引用到堆上建立的一個People物件。
"==" 和 "equals"
== 兩個引用變數是否引用到堆上的同一個物件。或者是基礎資料型別(int,long等)的變數是否相等。
equals 兩個物件的內容是否一樣
關於"=" 產生的一個低階bug。
之前寫程式碼的時候,就關於變數賦值產生過一個很經(di)典(ji)的bug。這裡來分享一下。背景很簡單,就是做RecyclerView的下拉重新整理,程式碼如下,很簡單。
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
private List<String> datas = new ArrayList<>();
private MyAdaptetr mMyAdaptetr;
SwipeRefreshLayout mSwipeRefreshLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeLayout);
mSwipeRefreshLayout.setOnRefreshListener(this);
RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
datas = getData();
mMyAdaptetr = new MyAdaptetr(datas);
mRecyclerView.setAdapter(mMyAdaptetr);
}
private List<String> getData() {
List<String> datas = new ArrayList<>();
for (int i = 0; i < 100; i++) {
datas.add("item " + i);
}
return datas;
}
@Override
public void onRefresh() {
datas.clear();
datas = getData();
mMyAdaptetr.notifyDataSetChanged();
mSwipeRefreshLayout.setRefreshing(false);
}
}
private class MyAdaptetr extends RecyclerView.Adapter<MyAdaptetr.MyHolder> {
private List<String> datas;
public MyAdaptetr(List<String> datas) {
this.datas = datas;
}
@Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new MyHolder(mView);
}
@Override
public void onBindViewHolder(MyHolder holder, int position) {
holder.text.setText(datas.get(position));
}
@Override
public int getItemCount() {
return datas.size();
}
class MyHolder extends RecyclerView.ViewHolder {
TextView text;
public MyHolder(View itemView) {
super(itemView);
text = (TextView) itemView.findViewById(R.id.text);
}
}
}複製程式碼
實際場景程式碼要比這複雜很多,這裡為了說明問題,寫了一個簡易的demo,但所要表達的問題一致,onRefresh裡的程式碼是有問題的,你發現了嗎?
乍一看,這程式碼貌似沒問題,但是執行下拉重新整理後,列表直接被清空了,一條資料都顯示不出來。記得當初為了找原因,對MyAdapter各種修改,甚至懷疑自己是不是發現了一個Android系統的bug。完全沒有去考慮datas=getData()這行程式碼的意義。直到後來打斷點發現,mMyAdaptetr.notifyDataSetChanged() 執行後,再次去Adapter的onBindViewHolder方法中檢視時,居然發現的列表的size變成了0。這下可是完全懵逼了。
後來做了如下修改,問題得以解決:
@Override
public void onRefresh() {
datas.clear();
//datas = getData();
datas.addAll(getData());
mMyAdaptetr.notifyDataSetChanged();
mSwipeRefreshLayout.setRefreshing(false);
}複製程式碼
當時,雖然把問題解決了,但是非常的不理解。為什麼第一執行datas=getData()時就可以,第二次執行就不行了呢?datas=getData() 和 datas.addAll(getData())有區別嗎?不都是把新建的列表賦給datas嗎?
結合上面關於引用變數的賦值解釋,這個問題就很容易理解了。
- 在onCreate()方法中,第一次執行datas=getData()時,在記憶體中建立了一段資料空間,同時datas這個變數指向了這段空間。
- datas.clear(),清空了datas所指向的這段內容空間的資料。
- datas=getData(),讓datas 指向了一段新的記憶體空間
- datas.addAll(getData()) 向datas第一次指向的內容空間,重新填充資料。
而當我們建立MyAdapter物件時,由於MyAdapter只會執行一次
public MyAdaptetr(List<String> datas) {
this.datas = datas;
}複製程式碼
因此,MyAdapter內部的datas指向的永遠都是我們第一次建立的那塊儲存區域。
到這裡,我們就很容易理解這個bug的本質了。
"=", 不是的賦值這麼簡單 !
多型
應用變數的型別可以是實際物件型別的父類
//Man 繼承自People類
People mPeople=new Man();複製程式碼
方法的覆蓋,引數,返回值型別,均不能改變,存取許可權不能降低
方法的過載,引數不同,返回值型別,存取許可權可以改變,與多型無關。
People mPeople=new People();
Object o=mPeople;
int code=o.hashCode();
o.toString();
o.eat();複製程式碼
編譯器是根據引用型別來判斷有哪些method可以呼叫,而不是根據引用所指向的物件型別來判斷。因此,上述o.eat()將 無法執行,即便People類這個方法,但是對於Object來說是未知的。
Java關鍵字
this 和 super
this 關鍵字是類內部當中對自己的一個引用,可以方便類中方法訪問自己的屬性
要從子類呼叫父類的方法可以使用super關鍵字來引用
super.onResume()複製程式碼
建構函式之this() 和 super()
使用this()來從某個建構函式呼叫同一個類的另外一個建構函式。this()只能用在建構函式中,並且必須是第一行被執行的語句。
super() 用來呼叫父類的建構函式,必須是第一個被執行的語句,因此,super()和this() 不能共存。
以後自定義View的時候,建構函式該怎麼寫,終於有譜了。
abstract
被abstract標記的類稱為抽象類,無法被例項化;
抽象類任然可以作為引用變數的型別。
被abstract標記的方法被宣告時沒有內容,以分號結束;非抽象子類必須實現此方法。
如果有一個類當中有任意一個抽象的方法,那麼這個類也必須是抽象的;當然這個抽象類當中,同時可以包括其他非抽象的方法。
如果要限制一個類被例項化,除了使用abstract標記為抽象類之外,還可以將其建構函式標記為private。
static
被static標記的方法,靜態方法,可以不需要具體的物件,可直接由類呼叫。
靜態方法不能呼叫非靜態的變數,因為他無法得知是那個例項變數。同理可得,靜態方法不>能呼叫非靜態的方法。
靜態變數是共享的,同一類的所有例項共享一份靜態變數,它會在該類的任何靜態方法 執行之前就初始化,沒有被賦值時,會被設定為該變數所屬的預設值。
Math.random();
Math.min(1,2);複製程式碼
因此,帶有靜態方法的類,一般來說可以是抽象的,不必要被初始化;但不是必須的。
final
static final double PI=3.1415925複製程式碼
final 型別的靜態變數為常量
final 型別的變數一旦被賦值就不能再更改
final 型別的方法不能被覆蓋
final 型別的類不能被繼承
synchronized
防止兩個執行緒同時進入同一物件的同一個方法
public class TestSync implements Runnable {
private static final int COUNT = 500000;
private int count;
@Override
public void run() {
for (int i = 0; i < COUNT; i++) {
increment();
System.err.println("count=" + count);
}
}
private synchronized void increment() {
count = count + 1;
}
public static void main(String[] args) {
TestSync mRunnable = new TestSync();
Thread a = new Thread(mRunnable);
Thread b = new Thread(mRunnable);
a.start();
b.start();
}
}複製程式碼
上面的程式碼中,當a,b 兩個執行緒同時開始執行run方法時,在缺少synchronized的情況下,兩個執行緒將由虛擬機器的排程器控制執行,因此當a執行緒完成count+1時,還沒來得及賦值操作,就被切換到了b執行緒,b執行緒再次執行count+1操作時,就會丟掉a完成的工作,最終會導致結果不正確,兩個執行緒內各自經歷COUNT次迴圈後,並沒有使最終的值達到COUNT*2 的結果。
只有increment()方法被synchronized修飾後,就可以保證在每次在a執行緒完整了執行完了increment方法後,b執行緒才可以執行該方法,反之亦然。這樣就可以保證最終的執行結果的正確性
需要注意的是,synchronized(鎖)並不是加在方法上,而是配在物件上。某個物件都有一把“鎖”和一把“鑰匙”存在,大部分時候並沒有實際意義,只有對方法使用synchronized(同步化)之後,鎖才會變得有意義。當同時有多個執行緒需要執行同步化方法時,只有取得當前物件鎖的鑰匙的執行緒才能進入該同步化方法。其他執行緒只有在獲得鑰匙的執行緒完整的執行完畢同步化方法時,才會獲得鑰匙從而進入同步化方法,否則,就只能等待。因此,使用synchronized會消耗額外的資源,會使方法執行變慢,因此要謹慎使用。同時,使用不當會導致死鎖問題的產生。
當物件有多個同步化的方法時,鎖和鑰匙還是隻有一把。獲得鑰匙的某個執行緒進入到該物件的同步化方式時,其他執行緒也無法進入該物件其他的同步化方法。此時,唯一能做的事情就是等待。
未完待續。。。。
關於Kotlin
Google I/O 大會之後Kotlin很火,那麼我們是否意味著在Android開發中他會取代Java呢?
Google’s Java-centric Android mobile development platform is adding the Kotlin language as an officially supported development language, and will include it in the Android Studio 3.0 IDE.
這段話應該說的很清楚了。