從Android到Java

IAM四十二發表於2017-05-30

相信很多的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.

這段話應該說的很清楚了。

相關文章