2014年12月9日 星期二

【Android】CheckBox的「帽子把戲」(如何自製「單選選項」)

這篇算是白癡教學。只要把基礎讀完的人應該都可以順利看完。

如果(讀者)要準備製作的是一張從頭到尾都手工打造、每個選項都有自己獨特的ID和對應資料的選單,這篇文章講的技巧沒什麼意義。

但這裡講的是「ListView這類用緩存記憶體大量複製出來的UI」,而且操作的是JSONArray這類的資料時,可能會需要的技巧(或說是心得會比較恰當)。

另外...如果不知道什麼是CheckBox的人也沒有必要繼續浪費時間看下去。



說明一下題目。

雖然Android就有預設「單選群組」給大家使用,但「萬一不夠用」,或「某些特性跟自己的需求相違背」時,該怎麼辦?

我沒辦法解釋或說明「那會是什麼樣的狀況」,那是碰到了就知道的狀況.......

一般來說CheckBox是用來製作「多選選項」、或「勾選式True/False」的元件。(有一種「滑動式切換True/False」的元件。)

如果每個CheckBox都是獨一無二的,則只要「輸出結果」時將全部的CheckBox都集合起來,用迴圈判讀它的Check狀態就好,但如果是用迴圈/緩存記憶體大量複製的,則無法這樣做,資料的操作必須要在按下「CheckBox」當下的瞬間進行修改。

再說明清楚一點:

假設功能要求這裡有個長整數(Integer)的矩陣A,每「格」矩陣對應內有二或多個以上CheckBox的CheckBox群組,CheckBox群組內的CheckBox依序對應「1」、「2」....以此類推的值,(CheckBox1被勾選時,某數值為1,CheckBox2被勾選時,某數值為2,.......某數值不可能同時又是1、又是2,所以CheckBox1和CheckBox2必須為單選項,)被選取的CheckBox則會把自己對應的數值寫入群組對應的矩陣格內。

如果矩陣A的長度永遠固定,那就表示CheckBox群組的數目也永遠固定,所以只要用固定格式的介面即可。最終要輸出答案時,只要把所有CheckBox群組用固定的順序判讀即可。

但如果矩陣A的長度會隨機變化,那狀況就很複雜了.........(先不提選項有多少種,光是題目數目不一定,這就是個「菜鳥的挑戰」了。)

以ListView為例,如果用全緩存的方式管理生成畫面,會發生「當這個選項滑出畫面外時,系統會把這個選項的介面圖形回收掉,」(不懂技術的人肯定完全不懂這是怎麼回事。意思就是說「這個欄位如果滑出畫面外,可能會被系統消除掉、以節省記憶體......,當它划回營幕時,系統再把它重新生出來。」)所以如果用「判讀介面上CheckBox選項狀態」的方式來決定答案值,會發生取不到CheckBox的狀況。

碰到這種案例時,就不能「讀取CheckBox的選項狀態」來決定數值,必須要在選項被圈選的當下就決定數值.......這就要使用介面「OnCheckedChangedListener」。

這只是狀況之一,隨機決定長度的資料難度完全不同。




首先,下面這算是個用匿名函數式去宣告的一個OnCheckedChangeListener。(怕有人沒用過,所以說明一下。CompoundButton buttonView是指設定了這個Listener的CheckBox,所以即使多個CheckBox設定同一個Listener,Listener也不會弄錯,──這是種設計函數的基本技巧。boolean isChecked則是前面這個buttonView的選取狀態,true表示被勾選,false則是無勾選、或勾選取消。)

OnCheckedChangeListener l1 = new OnCheckedChangeListener() {
  
  @Override
  public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
   // TODO Auto-generated method stub
   
  }
 };
請不要用這個方式製作接下來的OnCheckedChangeListener!

請正規的實作一個類別後再宣告。


結果大概如下!這個類別預設的是操作JSONObject。
class CheckListenr implements OnCheckedChangeListener{
  ...//省略建構子
  
  JSONObject js;
  CompoundButton lastView = null;

  @Override
  public void onCheckedChanged(CompoundButton buttonView,
    boolean isChecked) {
   // TODO Auto-generated method stub
                        //!!!!0#  將這個CheckBox對應的值取出
   int tag = (Integer) buttonView.getTag();
   if(!isChecked){//!!!!A#
    ....
    ....
                                //!!!!3#  
                                lastView = null;
   }
   else{
    if(lastView != null){
                                        //!!!!2#  將上一次選取的CheckBox取消勾選
     lastView.setOnCheckedChangeListener(null);
     lastView.setChecked(false);
     lastView.setOnCheckedChangeListener(this);
                                        //!!!!2#End
    }
                                //!!!!1#  將這次勾選的選項設為「上一次勾選的項目」
    lastView = buttonView;
    ...
                                ...
   }
  }
  
 }

假設眼前有三個CheckBox,分別是CheckA、CheckB、CheckC,則將它們對應的數值設為Tag,譬如CheckA為A、CheckB為B、CheckC為C,──可以看到「!!!!0#」下面對應的那行。(我這裡的做法是用isChecked為true時填入這個值,如果為false則填入統一的「X」。各位也可以選擇用設定兩個Tag,isChecked為true時取Tag1,false則取Tag2。這是很基本的變化技巧.......不是講給熟手聽得。)

「!!!!1#」、「!!!!2#」和「!!!!3#」就是將這些CheckBox做成單選項的關鍵。

「!!!!A#」的地方將整個邏輯切割開來,分為「勾選」和「取消勾選」......不難懂(吧)。

如果是第一次勾選這個群組(提醒:群組是指都設定了這個Listener、並且被指定要修改同一個數值的CheckBox)、或群組內的勾選被取消的狀況時,「!!!!2#」區段的程式碼並不會被執行,有CheckBox被勾選後、使用者又決定改勾選別的CheckBox時,「!!!!2#」就會被呼叫。

這地方是我稱這個技巧為「帽子把戲」的原因。注意看我將「上一次勾選的CheckBox取消勾選(設定為false)」前後,個做了一次「將Listener設為Null」和「將這個Listener設定給CheckBox」。

這有可能造成無窮迴圈,或至少會造成「無法將選項正確的反勾選」。因為如果CheckBox有設定CheckChangedListener時改變它的勾選狀態,則這個Listener會被呼叫執行,然後「取消勾選後」,也就是「!!!!A#」這個地方如果為false的區段會被執行,包含「!!!!3#」。

一般情況下這其實不會有問題!可是......我無法精確說明何時會是問題,因為「取消勾選(群組內的任何一個選項)」跟「被觸發設為false」兩者是不一樣的!(簡單來說,如果「取消勾選」時對應的動作如果在「被觸發設為false」時不可以被執行會出錯時,這就是我這裡再講的狀況。)

所以就假設「萬一出問題好了」,單純的將「上一次勾選的CheckBox」設為「false」會導致邏輯判斷出錯,解法非常簡單,再執行「被觸發設為false」前,將Listener解除就好。

2014年9月30日 星期二

【Java】for(;;)和for(:)的效能差異

【我測效能的方式一向是土法煉鋼。】



宣告一個長度在三千以上的矩陣,然後用兩種不同的for迴圈來讀取、操作內容,效能會差距多少?

注意!矩陣長度一定至少要在三千以上,一萬是比較保險的。(對矩陣個別元素的操作或運算內容也別太複雜。)

注意!還可以在for(:)的迴圈內容加個長整數,然後讓長整數一起跟著遞增,讓兩個迴圈的差異變得比較小。

答案是........




一千倍!

for(:)迴圈的速度是for(;;)迴圈的一千倍以上!




但了解技術內容的人都知道for(:)迴圈其實是將矩陣轉換成Iterator,然後用這個物件操作內容。

而且這個內容只是矩陣元素取值出來操作而已!它只是宣告一個變數,然後這個變數的值會是當下正在操作的矩陣元素的值。不論對這個變數做怎樣的操作,矩陣元素的值都不會有變化。

理論上來說,for(:)反而應該要比較慢才對,因為多了個轉換手續。

所以矩陣的長度要夠長,才會看得出差異。如果太短,這個轉換手續造成的效能差異會讓for(:)迴圈看起來比較慢。




如果有在for(:)迴圈內加長整數遞增,效能依然是一千倍!

顯然。遞增運算絕對不是效能差異的關鍵。(這很理所當然。)兩個迴圈的差異,完全在於Iterator的效能。

但差在哪裡呢?操作記憶體的效率?........傳統的for(;;)迴圈操作矩陣可能沒有位址紀錄器,要重覆的讀取迴圈內設置的長整數,還要判斷長整數的執行範圍........不過這是我隨便猜想啦!

如果在換個方法來測試這兩個迴圈的差異,例如也在傳統的for(;;)迴圈中加上一個區域變數,然後用這個區域變數取值後再對這個區域變數做運算操作,效能會差多少呢?




根本沒差!

或許關鍵根本不再Iterator,而是「變化矩陣元素內容」就是會這麼慢。






一般來說長度會到這個等級的矩陣......都是Stream吐出來的基本型別資料串。(自己寫的物件搞到三千多個?...超級資料庫?)

2014年9月17日 星期三

【Android/Java】ListView中使用ArrayAdapter搭配自己設計的物件

一般官方範例只會說到如何使用ArrayAdapter呈現String資料串。

感覺上大家會以為「ArrayAdapter只是把程式中寫在泛型資料格式指定的資料物件塞到XML Layout中的TextView上」。

但其實ArrayAdapter呼叫的是資料物件的「toString()」這個函數。


也就是說:設計師只要把「自己設定的物件」的「toString()」重新複寫就可以丟進ArrayAdapter了!

但剩下的資料呢?自己設定的物件中一定還有其他資料要顯示在ListView中,否則就不需要自定義物件了......

問這個問題的人去跪算盤!

複寫getView(int position, View convertView, ViewParent parent)這個函式就好啦!

講精準一點來說,ArrayAdapter在產生實體(new)的時候,會需要指令XML Layout跟這個Layout中的一個TextView的id。(如果不指定會預設為android.R.id.text1。)複寫的getView中如果寫了super.getView,就不用另外執行這個toString動作。(記得把super.getView傳出的View設為回傳值...記得getView這個函數要設回傳值。)




這個方法可以用在幾乎所有會用到Adapter的地方。

而且資料動態變更(執行notify函數)時,絕對會成功,因為用的是ArrayAdapter的方法。(我實作BaseAdapter產生的物件幾乎沒有成功過。)



話說......Java物件預設的函式到底還有那些?又有哪些功能?......

複寫他們會有些什麼副作用嗎?一般來說「toString()」是回傳物件的class資訊,就這樣複寫掉會有什麼影響呢?.......

有機會再補充啦。

2014年8月27日 星期三

【Java】interface內的static參數(型別為物件,而不是基本數值型別)

挺奇妙的...

在interface內的參數會被強制宣告為final型態,因此boolean/byte/short/int/char/long/float/double/String這些基本型態的參數一旦寫在裡面,就會被「鎖死」不能再進行修改。

但如果這個參數直接是個類別(A)的實作物件(A),雖然不能修改物件(A)、不能重新宣告或指定新的物件(A),但物件(A)內的參數是可以修改的。

最明顯的例子就是就是宣告ArrayList<T>。

實作這個介面的物件依舊可以動態的、彈性的增減ArrayList<T>的內容。



更奇妙的是可以將這個ArrayList宣告為static屬性。

在多個不同(幾乎沒有相似性)的類別中實作帶ArrayList的介面後,修改、增加ArrayList的內容,然後看看內容組成,(只要看數量就夠了,)會發現ArrayList確實只被產生一次。(它是個綁在介面下的static參數,而不是綁在類別中。)



Android中,Service和Activity的關係一直困擾著我的地方是:Activity要如何對Service內的參數做出修正?或引用Service的屬性和函數?

直接掛上ServiceConnection當然是必要動作,但如果是Activity下所屬的Dialog呢?或是設計者將功能獨立成一個物件,為了能夠在多個Activity中使用,所以傳入參數只使用的基本Context?(好爛的例子......但大家應該懂我真正想表達的,其實就是一個問題:「萬一真的不行,萬一真的有必要另闢途徑,萬一我懶到不想這麼做、專案已經複雜到新增一個傳遞參數大家就會因為要修改舊程式碼而爆炸........有沒有別的選擇?」)

將參數打包成一個物件,然後用interface(I)包裝起來再丟給Service去實作/擴充...這樣一來凡是實作/擴充這個interface(I)的類別都可以享有Service下的參數讀取、甚至修改的權限。



我馬上想到的第一個想法就是設計一個ArrayList<Runnable>,任何被丟進去的Runnable都會被Service執行......當然要做基本的數量控制!......這樣就有簡單的「任務控制器」了。.......這概念大家都稱為「任務控制器」吧?

2014年8月22日 星期五

Android的LayoutInflater真是博大精深啊!!!

public View inflate(int resource, ViewGroup root)

這個Inflater裡的函數非常有趣!

「ViewGroup root」帶Null或帶參數...對「int resource」解析的結果會完全不同!



剛剛同事花了半天才發現如果帶「ViewGroup root」,其實回傳的是這個「ViewGroup root」。

一般情況下這沒什麼,但如果「int resource」最外層的ViewGroup是個設計者自行設計的Layout類別,結果Inflater會忽略這個類別,然後只把「int resource」內的ChildView解析出來、加入「ViewGroup root」中。



會不會有點繞口?



詳細一點說明,如果設計者自行設計了一個LinearLayoutA,並且將它用在XML中,而且是包在檔案最外層。

如果帶了個「ViewGroup root」給Inflater,則Inflater會跳過這個LinearLayoutA的XML不去解析。



如果真是這樣,可以做個實驗.......

XML檔最外層是個FrameLayout,但「ViewGroup root」卻是個LinearLayout。

則用一個「FrameLayout frameLayout」參數去接收Inflater的回傳時,肯定會出錯............



等等做實驗!



(十五分鐘後........)

沒錯!實驗結果完全如此!

2014年8月18日 星期一

【Android】決定Dialog的大小和位置

今天的工作內容是「設計一個Dialog,它彈出的位置會隨著呼叫它的Button位置不同,而跟著不同,例如按鈕如果在整個螢幕偏右側,則視窗要在左側,反之則是在右側。」

問題最後算是初步解決了。以下是心得。



簡單來說,先獲得Dialog下的Window物件,然後再用Window物件產生WindowManager。

([Dialog物件].getWindow()可以獲得Window物件。[Window物件].getWindowManager可以獲得.......別問我怎麼獲得Dialog物件!拒絕回答!)

有了WindowManager,就可以進一步獲得這個Dialog的LayoutParams。

(這個LayoutParams可以說是Dialog的LayoutParams嗎?...這個Params的物件路徑明明白白是歸屬在WindowManager下,所以它到底是.........一點都不重要!)



一般的View元件在有LayoutParams後,決定View的大小位置,就不是什麼難事了!

但.........

不管設了什麼值給這個Params,一旦Dialog執行了show()之後,什麼都有可能亂掉!

例如我碰到的情況是「系統會將寬度強制設回-2」。

(系統會強制用預設的-1/-2覆蓋掉我們給予的值!──似乎不是所有人都會有這個困擾!為何只有某些人會碰到、或基本的程序會節外生枝,........抱歉!還在研究中。)

解決方法在於不使用LayoutParams設定位置與大小,而是使用WindowManager的setLayout(int wid, int height)。

(能不能用同樣的方法指定位置?......晚點試驗一下。)


2014年6月28日 星期六

Stream、Port、Ping、Socket

不知道有沒有人試過在瀏覽器的網址輸入欄中輸入「file:///」後面接上某檔案的路徑,然後按下「Enter」,看看會發生什麼事?........

很多檔案其實都可以用這種方式開啟。



因為這就是Stream與Port的概念。



在電腦中,中央對外溝通是藉由Port。

主機板要跟網路溝通?用Port。

主機板要跟列表機溝通?用Port。

主機板要跟硬碟要資料?用Port。

幾乎每個Port都有特定的工作,(或只能執行特定的工作,或是先佔先贏,程式大多會衝突,很多時候就是因為搶Port、或是對Port的設定沒有彈性。)

打入網址,瀏覽器會從Port編號:8080去跟外界網路溝通;如果打入檔案路徑,可能也是從Port編號:8080,但也可能不是,(我不想探究!Java耶!細問這麼多?)

重點是對電腦的中央來說,這都只是資料藉由Stream傳入或傳出而已,數據機跟網路怎麼處理從中央傳入的資料?中央才不管!(或許可以想像哪天真的把網址傳給硬碟,會發生怎樣有趣的事情呢?硬碟會變成變型金剛?)



但真正的問題是:Stream本身只管檢查自己是否可以傳出或傳入資料,能否建立Stream,這完全不是Stream自己可以控管的。

所以有File類別來檢查「檔案路徑是否存在」,所以有各種硬體管理類別來檢查「特定硬體是否存在」。

Socket就是用來檢查「指定的連線IP是否存在、是否可以進行連線」的類別。

網路連線的第一步就是「檢查IP是否正常、正確、可以連線?我在CMD模式下打入[Ping]+[ IP],會收到怎樣的結果?」,但Stream本身都不具備這些功能,這就是我們需要Socket的原因。

所以要清楚意識到Socket並不是用來進行資料傳輸溝通的主體!Stream才是!

雖然沒有Socket就沒有Stream,沒有Stream、Socket只是好看的工具,但這兩者並沒有絕對的依存關係。(.........講是這樣講,但我也不知道怎麼樣不靠Socket建立網路連線。)



學習使用Java Socket,大部分的新手都是死在這個認知上。

能夠清楚、易懂傳達這個認知的文章.......對不起!沒看過。(我也是別人明明白白講給我聽、甚至試範給我看後,才搞懂這一切。)

三張圖,讓人看懂Java Socket真正的用法

Socket的結構、特性......不難查到相關資料跟說明,例如怎樣設定它的Port、怎樣跟特定IP建立聯結、怎樣檢查連線是否成功........

但是實際上會用Socket建立TCP/IP或UDP連線的人很少,要做網路功能,URL和J2EE幾乎把這塊領域吃光光........

怎麼回事?



因為Oracle官方的教學文件本身就是塊爛帳!

要看清楚這塊爛帳,除非老師上課有講清楚、教學文寫者有意識到,不然讀的人最後都會撞死在「為什麼連線建好了卻不能用」、「為什麼連線的效率差到根本無法運作」、「為什麼最後會徹底失去建立Socket和Stream的能力」......上。

把「網際網路通訊原理」丟一邊,Java設計師/工程師其實需要理解到的本來就只有「這樣就可以跟遠端裝置進行通訊」這個概念就好,(而且這不正是「Socket的精神」嗎?)但是Oracle的官方範例僅止於「一條龍式的示範可以丟入怎樣的參數來建立Socekt,建立之後可以優先使用哪些參數或得哪些回傳物件........」但是對於真正重要的Stream使用就幾乎支字未提。

而且我可以保證同樣都是Stream,在網路跟單純的檔案管理上頭,這兩種Sream的使用與操作經驗完全不同。(我現在會這樣說,十年後還是這樣說。「這是一樣的東西啊!」那完全是老鳥的嘴砲與傲慢。)

不管是Oracle,或是網路上、課堂上無數的範例與教學,根本沒想過要讓人搞懂「Java怎麼建立連線環境」的全貌,都是不停丟出片段的知識而已。

(不知道多少初入門的Java設計師死在這上頭,最後只能一把鼻涕一把眼淚的強迫自己接受J2EE的強暴、放棄自己滿腦子本來可以用J2SE實現的應用與設計。真是殺死人的絕境、與欲哭無淚的哀愁啊!)



第一步,建立Socket。
不管是用直接指定IP的方式,或用ServerSocket。
總之,想去「取得」一個Socket。 


 從第一步中獲得的Socket產生兩個InputStream和OutputStream。
可能是Thread中產生兩個全域變數Stream;
或是直接產生兩個Thread裡面帶有Stream變數。

將兩個Stream丟給不同的Thread(不是負責生成Socket的同一個Thread),來進行資料的讀寫操作。

這裡頭牽涉到一些細節,例如負責生成Socket的Thread要怎麼管理已經生成的Socket?

提示:絕對不是馬上關掉它。Stream一樣也不用馬上關閉,除非在測試中發現機器沒辦法同時負荷那麼多Stream(原理...待查中...)否則Stream做好,除非要結束連線......建議:就放著吧。(不然連線的效能拉不起來。)



為何要獨立一個Thread來管理Socket生成?

當機器要能夠具備跟多個對象溝通的能力時,Socket絕對不會只有一組,可能最後要建立多組,特別是使用ServerSocket時,但問題是ServerSocket的avaliable()指令會霸佔住整個Thread,所以對於已經建立好的Socket,如果要保有繼續操作它們的能力,就要用另外獨立的Thread來操作,另外Socket要有建立流程、自然也要有關閉流程,一個Thread中怎麼同時管理多組Socket、操作多組InputStream/OutputStream?(神人會說「有何難?不就幾百行程式碼而已!」........想當那種神人,我就不會開這個部落格、寫這些文章分享這些經驗了。)

領悟力好的人會理解這點,根本不需要我這三張圖,.......但問題是領悟力好的人有多少呢?況且說這些人領悟力好...這些人又有多少有能力自己從底層去寫自己的Socket物件來管理網路連線?........還不提我看到一家規模中上等級的公司開出「Java工程師,懂Socket使用,會管理TCP/IP連線......」的徵人啟事,薪水45K(起跳),結果過了一個月還沒人應徵.......

2014年6月26日 星期四

Android的背景Service連續啟動、或前景Activity連續啟動,對程式中的Thread(或其他東西)造成的影響

[2017/8/24]

現在發現其實Android有些更簡單的機制可以處理這種狀況...

ActivityLifecycleCallBacks...這東西的使用機制比較複雜。

或是LifecycleObserver...但這東西我沒實用過,我習慣使用LifecycleCallbacks



 [2017/8/24 !!]
==========================================================

寫一個Activity或Service,然後在onCreate()中加入Log訊息,會發現當「程式/APP」大到某個程度時,會經常(其實是變成例行公事)一個Activity或Service連續開啟(執行「onCreate()」)兩次。

雖然第一次開啟的Activity/Service最後都會關掉(都會立刻轉到「onDestroy()」),但是程式整體結構會變得很難掌控。

開啟不是開啟,關閉不是關閉,如果有別的程序──例如等一下會講到的執行緒──要去判斷/讀取這些Activity/Service內的參數時,可能就糗了......

再講明白一點:Thread啟動、將Activity/Service當作傳入引數要求Thread判斷裡頭的參數......結果不到一秒鐘的時間,Activity/Service已經被關閉,但Thread才剛完成「生成」和「啟動」而已,結果進入執行緒的任務中要開始工作時要去判讀Activity/Service的參數......當機!



有些人會認為應該要在執行緒中加入極為精密、精準的判斷邏輯,避免掉這樣的錯誤發生,「這才是程式設計。」

我不否認「程式設計本來就是門苦工」,產品使用者不曉得在智慧型手機上按一下螢幕、或在電腦上點一下滑鼠,這個「按」與「點」,本身就包含了無數行程式碼。(像寫這個的工程師們致敬。)

但這不代表我們應該只認可「苦工」,而放棄、或否定尋找其它秘訣的機會。

(而且:最苦工的方法想也知道會讓Thread效率變差。)


其實解決方法很簡單,(這部落格講的是Java/Android,所以這些經驗主要應用在Java上,)基本上已經摸過一定程度的Java工程師都知道在Thread中的迴圈要用一個外面可以控制的參數A來決定Thread是否要繼續執行。

這個參數A可能是個Boolean值,程式進入onCreate,則Boolean轉為True,程式執行onClose,則Boolean轉為False。

但僅僅是這樣還不夠解決問題。(像如果用Activity或Service本身的物件是否還存在、是否為Null來做判斷.......抱歉!沒成功過。)

可是真的要解決問題也非常簡單......

Thread有run()函數,這個函數內的程式碼可以被無限執行、並同時排程。

在run()的最開頭,只要用排程的方式先讓它「休息0.5秒」,等到「休息0.5」秒結束後,真正要值行任務內容時,參數A已經變成了False,這個Thread自然也會跳入關閉狀態、然後乖乖等著被回收,不會干染下一個Activity/Service啟動並產生真正穩定的Thread。



應該不會有人會問「0.5秒太短了,不管用,該怎麼辦?」........



================================================

這篇文講的東西很碎,但我以為Java本來就是這樣的東西。它的學習門檻很低,應用門檻也很低,要用它的人、可以用它的地方其實都很多。

但是它的門檻低,後面卻有一階比一階高的樓梯,沒有多種其他層面知識的人跳進來學Java,其實很快就會遭遇障礙。

把程式設計的基礎,例如迴圈和判斷式的使用、物件的設計等很基本的東西講的淺白易懂,然後接下來馬上轉為「基礎教完了,要開始把你們當成專業級的對待,」然後下一個教程、範例、或項目(隨便怎麼稱呼),馬上開始用「我只講這麼多,剩下的要自己理解自己推敲」的態度........所以Java程式設計師能夠活過「初階」的很少,特別是這種語言標榜著所有人都能學。

孔子講「舉一隅不以三隅反者,吾未往也。」這是舉一反三的典故。

聽起來很了不起、很霸氣,但如果今天的「教學」都用這種態度,那小學生學會九九乘法表的一到三之後,豈不是要自己歸納出剩下的四五六七八九的訣竅?

整體的知識水平要提升、科技要進步......這種態度(期望丟出去的教學能夠有最大話的回收)就有嚴重的問題。

所以我開這個部落格、分享這些很碎很碎的技巧。

除了因為還是會有人需要用到,也是為了讓那些沒辦法第一步就達到舉一反三的人可以達到有能力舉一反三。

Thread要更新畫面,除了使用Handler以外,還可以考慮BroadCastReceiver.......(內無程式碼,因為不需要)

有人注意到BroadCastReceiver也可以像Handler一般接受從主線程以外發出的「更新畫面」指令嗎?

註冊一個特定畫面專用的BroadCastReceiver,(或是你有別的辦法可以產生動態、適用所有你想要的畫面的Receiver,)然後改成使用sendBroadcast(Intent intent),來接收........

public abstract void onReceive (Context context, Intent intent)

在Receiver內的這個函式中的Context就可以獲得發出這個Intent的Activity。

也就是說,用Context(強制形變一下)來執行....findViewById(int id)

就可以輕鬆的更新任何想要更新的畫面了........

效率是不太好啦!(但也不會很糟。)



為何要想出這招,而不是滿足於使用Handler就好?因為用Handler更新畫面,在測試程式中看起來很完美,但實務上經常碰到的問題是:

變化比較頻繁的功能可能會發生「Handler」送出後,但是Activity卻已經關閉。

這時候APP絕對死當!




一般來說,大家都會把Handler寫成個別Activity下的子類別、巢狀類別,所以裡面的Activity都很「專一」,但也因為專一,所以大家可以寫個很獨特的偵測方式來決定Handler真的被主執行緒執行後,要不要真的執行畫面更新的動作......

多麻煩啊!(把Activity變成Handler的參數,然後每呼叫到一次就先執行、檢查一下「act != null」.......這樣的設計合理嗎?見人見智。)

改用BroadCastReceiver,只要解除註冊,就不用再煩惱任何問題,如果有問題就是「寫錯內容」,而不是「發送Handler的程序需要調整」。

而且在Android下,只要確認View元件的ID碼正確、內容也正確,其實BroadCastReceiver可以作的事情、可以更新的畫面幾乎是無限多。



(稱這為「小秘訣」...好像有點沒價值。到網路上找,好像沒什麼人想到這樣做。)

2014年6月18日 星期三

【Android】EditText && Editable && Selection

會研究到這兩個東西,是因為在作網路連線功能時,要讓使用者自行輸入IP來決定連線對象。

IPv4機制需要四組0~255的數字。(現在知道了,那其實是四個Byte。)所以需要四個EditText輸入框。

但問題是.......

當我在第一組數字輸入完後,我希望跳到下一組數字的輸入框......

容易!

EditText下的getText()可以獲得一個Editable物件。這個物件長的很像StringBuffer,雖然不是,但用法概念很像。



問題是如果我要用「刪除鍵(BackSpace←)」刪除文字,同時具有「跳回上一個EditText輸入框」的功能...結果也不難,但問題是跳過去後,游標會停留在框內的文字開頭。

例如框內如果有「100」,游標會出現在「1」之前,但我希望它直接出現在最後一個「0」的後面。

這個時候就可以用Selection來決定游標要停留在哪裡。



其餘的函式具體功能還沒機會嘗試研究,但主要使用的是setSelection(...)。

先將EditText.getText()獲得的Editable物件當成引數傳入。(Editable其實是Spannable的子類別。)接著決定「位置」,──如果傳入「頭」「尾」,則會把「頭」「尾」之間的文字「選」起來,功能就類似滑鼠的拖曳。



其實是很簡單的一件事,但很實用。

2014年6月15日 星期日

ArrayList.add()的兩種方式的效能差異

下面有兩個迴圈.......(limit2是1000,但其實可以隨便寫。)

for(int k = 0 ; k < limit2 ; k++){
  for(int l = 0 ; l < limit2 ; l++){
    Integer irt = new Integer(l);
    list1.add(irt);
  }
  list1.clear();
}

for(int k = 0 ; k < limit2 ; k++){
  for(int l = 0 ; l < limit2 ; l++){
    list1.add(new Integer(l));
  }
  list1.clear();
}
看出差異了嗎?

下面的效能大概比上面的效能快了兩成左右。

「宣告一塊記憶體位址」然後再「產生一塊實作」,佔去的效能要比「不宣告」多兩成左右。

(所以造成效能差異的關鍵並不在ArrayList本身。)



沒什麼意義。

因為我把迴圈擴大(外頭在加兩層迴圈,)總共執行了1000 * 1000 * 1000次的『「宣告一塊記憶體位址」然後再「產生一塊實作」』,和1000 * 1000 * 1000次的『「不宣告」直接「產生一塊實作」』,這樣才看的出有兩成左右的效能差異。

應該很少有人沒事會去在Java上操作這麼大的ArrayList吧!(即使「有事」也不會作,因為記憶體會掛掉。大家會想別的辦法達到同樣的效果。)

2014年4月20日 星期日

讀 【Why OO Sucks by Joe Armstrong】 ... 這傢伙不是真的懂物件導向(This guy don't know much about OO.)

原文出處似乎已經不可考,附上的聯結內的文章其實也不是原文。

這位Armstrong先生列出了四點「物件導向很爛的原因」。

有些我無法反駁,因為著墨不深、經驗不多,但我明顯感受到不正確的地方,就直接用力的「炮」了!

1.Data structure and functions should not be bound together.
2.Everything has to be an object.
3.In a OOPL data type definitions are spread out all over the place.
4.Objects have private state.

.......仔細看了一眼,基本上每一點都有得「炮」。

或許是因為他的觀點是集中在C++的物件導向,而不是Java的物件導向,所以他對物件導向的觀點很狹隘、想像力更是偏執。(因為跟C++工程師一起合作的經驗很多,所以對他們有這樣的偏見。)\



第一點就大錯特錯!

資料結構跟資料處理功能本來就可以分開!

There is no reason to bound the data structure and functions in one object.

不知道怎麼分開,就表示根本沒搞懂物件導向的極限!

If you don't know how to do it in different object, you don't know OO at all.



第二點...我不懂有什麼好抱怨的,可是我也不覺得對此反感有什麼「不對」。



第三點是最離譜的地方。

因為這點的好壞很主觀!

就好像C++的指標操作也引來很多人詬病一樣!但除此以外的物件導向語言(似乎)多有直接變換取直/取記憶體的功能。(在C/C++...連矩陣的操作都可以用指標亂入,某個「指標參數」的遞增並不是遞增這個指標的值,而是指「從指標01遞增到指標02」。──雖然程式碼和語法上有「特徵」,但整體結構卻沒有。)





第四點......不太懂!(難道我英文這麼差?)沒人強制設計者宣告任何private參數!

這又是個不懂OO的人會有的偏見。

private的功能和意義是避免特定資料備不受控制的修改,而不是把這個資料的型態藏起來!getter和setter就是這個概念的延伸物!

2014年4月17日 星期四

【Android】UsbManager 的 requestPermission() (UsbHost元件的使用心得2)

在Google官方的Android API中的範例教學裡,有一段如和動態的使用程式碼設定/註冊BroadCastReciever去偵測「Usb插上/拔除」的「事件」。(就是告訴你的程式Usb裝置是否還插在手機上。)

這段程式碼如下........

UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
...
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);
mPermissionIntent是什麼?要做什麼用的?

下面的文字說明中有說,是要給requestPermission使用。

UsbDevice device;
...
mUsbManager.requestPermission(device, mPermissionIntent);
問題是...何時要使用?



APP啟動後,(或是在使用過程中,)發生了「拔除再插上(不一定是同一部Usb裝置)」時,會發現UsbManager雖然可以正常取得UsbDevice,但這個UsbDevice卻沒有「權限/Permission」。

這個時候就要使用requestPermission。



聽起來好像是很簡單的事情。但是如果是走範例中「XML設定」的人,就很有可能會跳過這一整段程式,而誤以為「XML設定會幫使用者取得穩定可靠的Permission」。

但其實並不是。

至少,如果希望自己寫的APP有「拔除後不用退出就可以切換到下一台Usb裝置」的功能,就需要使用requestPermission()。



另外一個要注意的地方是requestPerssion()可以多次重複進行。每做一次、畫面就會彈出一個徵詢使用者「是否要讓這個裝置連線嗎」的視窗。

一般來說,會因為沒有Permission而出錯的時機,是在「取得UsbConnection」時。

也就是說即使沒有Permission,UsbManager還是可以正常取得UsbDevice。

但是(讀上一篇)取得UsbConnection後,就要按時/定時把它關閉/釋放,如果資料傳輸、溝通的量很大、或次數很頻繁,取得UsbConnection的動作勢必要寫在迴圈/Thread中。

也就是說使用者根本沒有辦法監控每一次的取得動作。

如果迴圈/Thread不停的跑回來查詢UsbDevice的Permission,在使用者按下那個「確認」以前,程式都會一直彈出詢問視窗。

所以?

自己各憑本事想個機制去「只問一次」啦!



最笨的方式就是在迴圈/Thread後面寫個無窮迴圈去「等使用者選擇『同意連線』」,因為...如果使用者不小心點到「不同意」時就GG了!

但這也開啟了另一個問題就是「如果只問一次,但是使用者不小心回答了『不同意』,要怎麼再次建立Usb連線?

...........

這其實不是問題。

1.拔掉再插上就好。

2.在Menu/選單中增加一個「偵測連線」選項,......目的是什麼?如果懂了應該就知道該寫怎樣的功能了......

2014年4月9日 星期三

【Android】hardware/usb (UsbHost元件的使用心得)

雖然我只有使用了UsbHost系列的功能,UsbAccessory的部分還沒用過,但經驗相信是差不多,而且UsbHost部分的元件複雜多了。

要在這裡附上我使用的程式做範例是不太可能的,我只能盡量做到引用官方API,然後每個細節補充



要在Android上使用USB、或稱為OTG,需要使用Android建立在  android.hardware.usb  這個package下的一系列元件,還有在APP的AndroidManifest檔中寫下類似下面這樣的設定:

<manifest ...>
    <uses-feature android:name="android.hardware.usb.host" />
    <uses-sdk android:minSdkVersion="12" />
    ...
    <application>
        <activity ...>
            ...
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>

            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
        </activity>
    </application>
</manifest>
(希望進來看到這篇文的人看得懂怎麼修改Manifest,因為我討厭寫基本教學。)

特別注意一下:
android:resource="@xml/device_filter"
這行是要另外搭配一個XML檔,(在res目錄下直接新增一個目錄「xml」,然後在裡頭新增檔案,──有興趣的人也可以取不同的名字,只要不會造成混亂就好。)檔案的名稱就取為device_filter:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
</resources>
要特別注意的是這個範例有點「過於」詳盡,其實vendor-id/product-id/class/subclass/protocol....並不全是必須的。(像下面這段也是可以的。這些XML檔都是直接引用自官方API的教學內容。)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="1234" product-id="5678" />
</resources>

這些屬性/數值是每一台Usb裝置的「身分證」,我不是這方面的專家,無法非常精準正確的講述其中的細節,但是基本上......同一家公司生產的同一系列商品,基本上都有相同的vendor-id和product-id。

設定這個device-filter和Manifest,這樣裝置插上手機後,系統才會自動偵測「要不要啟動你的APP」。(也有不自動偵測,改走別的路徑的方式,就是自己設定BroadCastReciever去獲得裝置連線的Permission,但這方面的使用經驗還不多,改天再講吧。)

如果你/妳的APP要針對特定Usb裝置,就盡量把device_filter定義的詳細一點。如果不針對特定程式,就乾脆什麼都不要寫。


Android的Usb連線溝通方式,是以UsbDevice類別為基礎。每一個類別的物件代表一台Usb裝置。

獲得UsbDevice物件的方式,最簡單的就是......
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
但是這個方法在官網中的說明很模糊、很「曖昧」,像最基本的...這個intent是什麼?怎麼獲取?(根據後來的BroadCastReciever範例,這個Intent根本不是在Activity中獲得。)

所以我並沒有用它。



我用的是下面這段方法...(幾乎是官方API照抄。)

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
... //Android
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while(deviceIterator.hasNext()){
    UsbDevice device = deviceIterator.next()
    //your code
}
但其實這段的作法是有很多彈性的...deviceList中的Key值其實就是UsbDevice的DeviceName。這段程式碼中獲得UsbDevice後,可以將DeviceName存起來,然後下次跑到這段時,直接使用DeviceName去獲得UsbDevice,就不用跑while迴圈了。

官方API教學程式碼如下。注意最後一行程式碼中的那個「"deviceName"」,這個地方並不是要使用者輸入「"deviceName"」,而是要輸入上一面的DeviceName!

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...  HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice device = deviceList.get("deviceName");


菜鳥、初學Android的人可能會被API這種寫法給害死,就傻傻的一直輸入「"deviceName"」,然後好奇為何HashMap永遠要不到資料。因為直覺沒有敏銳到發現「我幹嘛在一個HashMap的get()中輸入deviceName這個字?想也知道這地方的內容是彈性的!

雖然Key值可以直接請HashMap吐給你/妳,可是用OTG時,是沒有adb功能的,(因為Usb接頭接著Usb裝置而不是電腦,)又某種原因,wifi-adb根本靠不住,(官方推薦改用wifi輸出Log資訊到電腦上,)所以吐出來的Key值...作夢想想就好。(或是自己在自己的APP中設置背景資訊的輸出功能,等到開發完畢在關閉這個功能就好。)

(因為Manifest中加了intent-filter,所以不需要經由BroadCastReciever去獲得Permission,因此我這裡不說明官方API中那段的使用方式。)



接下來是直接跟Usb機器「溝通」的部分。

跟機器「溝通」實在是個很模糊的概念,因為最終都是「送出Byte陣列資料」。

譬如...這裡有段Byte矩陣要送進特殊的隨身碟中存起來,只要矩陣開頭和結尾的Byte值是「100,101」,隨身碟就會把(「100,101」捨棄後)中間的值存起來。

(或是個喇叭,會把中間的值轉成音效,然後發出噪音。)

搞不懂的人可能要去研究一下網路傳輸或I/O原理,或就單純的自己用Java架個簡單的測試伺服器,然後練習用手機跟這個伺服器溝通看看。

就先......假設看到這裡的人都懂怎麼作,(遙遠的未來,或許我也來寫寫I/O相關的心得文吧。)



這段程式碼就是傳輸和接收資料的方法。(注意!Android的Usb元件能夠傳送和接收的資料有長度限制。上限是16K。)
private Byte[] bytes //要傳輸的資料  特別注意的是這個bytes的長度不能超過16K 也就是 16 * 1024  因為這是它每次能傳輸的資料量極限 
private static int TIMEOUT = 0; //每次傳輸的等待時間 (如果Usb機器超過這個時間沒回傳  就視為傳輸失敗)
private boolean forceClaim = true;
...
UsbInterface intf = device.getInterface(0);
UsbEndpoint endpoint = intf.getEndpoint(0);//又是個API害死人的例子
UsbDeviceConnection connection = mUsbManager.openDevice(device); 
connection.claimInterface(intf, forceClaim);
connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
最後一行connection.bulkTransfer中,這個用法跟使用File元件讀取檔案的使用方法很像。(所以細節我也不說明了。)

關鍵在於每個UsbInterface中都會有兩組UsbEndpoint,一組為傳輸、一組為接收。(哪組在先、哪組在後,似乎並沒有絕對的標準和規則。所以我在上面的程式碼中補註明了「又是個API害死人的例子」。)

UsbConstants(這是一個UsbHost的元件)中可以看到相關的參數UsbConstants.USB_DIR_IN/UsbConstants.USB_DIR_OUT。

去判讀每組Endpoint的Direction,(使用指令getDirection(),可以得到參數,判讀參數是IN或OUT,就可以知道Endpoint的屬性和功能。)把傳輸的丟進去就會傳輸資料給Usb機器,把接收的丟進去就會從Usb機器接收資料。



最後,官方文件輕輕帶過、但困擾了我整整一個月的部分.......

UsbDeviceConnection和UsbInterface元件使用完,要進行「釋放」和「關閉」的動作。

為何要特別強調?

因為這段方式沒有「標準作法」!

一般人會以為「我不再需要使用Usb功能了,就把它關閉,」.......錯!這些元件雖然基本上是ParceLable介面的實作,(跟Bundle一樣,)但其實它們都混合了JNI,所以經常資料傳輸到一半,這兩個元件(其中一個?或兩個同時發生?)會變成「Null」!(我的解釋是「它自己把自己回收掉了。)

碰到這種情況時,就要重作一次獲取的動作。

可是隨著這種情況發生、多次獲取後,經常會連獲取都失敗!(UsbManager的openDevice指令回傳Null?UsbDevice的getInterface也回傳Null?)

因為UsbManager用一組「很奇特」的HashMap來存放這些元件的資料,這組HashMap功能異常(資料不見?資料錯誤?存放資料總數超過上限?)就會回傳Null。(可以用IDE打開UsbManager,但對於理解HashMap會異常的原因還是沒有幫助,而且修改SDK的程式碼對未來APP在實機上的運作其實沒什麼幫助。)

所以不能等到整個功能都用完了才去作釋放動作,必須要定時的釋放。

「每次傳輸/接收完,都釋放一次?」......還是錯!

這樣只是把「獲取失敗」發生的時間往後推遲而以。(我清清楚楚的記得...從不釋放,程式會在第540~550之間轉為「獲取失敗」。每次釋放,程式會在第1050~1100之間轉為「獲取失敗」。非常穩定、且準時的兩組數字。)



我的設定是「傳輸+接收」的次數「每十次」就進行一次釋放、再獲取的動作。

但這個數字是我碰運氣、靠靈感隨便決定的,(當然也在平版實機上進行了無數次的測試。)

如果你/妳要使用Android的Usb連線功能,而且不是用在最常見、制式標準的隨身碟這類儲存工具上,最後這個步驟記得多花點時間測試。

2014年4月1日 星期二

【Android】Handler.post(Runnable run) 讓這個Runnable跑久一點

標題是個Android的API。

簡單來說,Handler可以允許使用者輸入一個Runnable,並執行這個Runnable的內容。

所以.......用Handler來啟動Thread/Runnable不是也不錯嗎?



如果你曾經有過這個念頭,表示你跟我一樣蠢。

因為Handler會導向主線程,所以Handler啟動了這個Runnable後,MainThread/UIThread就被占住了。(有趣的是,如果在Runnable中直接寫入操作UI的相關指令/程式碼,系統不會擋下這個Runnable。但直接使用Thread來啟動這個Runnable,第一時間就被系統攔阻下來。)

這個APP不會對使用者的任何操作產生反應,連關都關不掉,最後是等系統通知「此APP無回應」,才順利關閉。



========================================



反向思考一下,其實Runnable並不等於Thread!

它只是種接口,而且不是Thread獨佔使用。

Handler.post這個用法應該不是另外開啟一個Thread,而是將Runnable的內容差入Main Thread中執行。

能幹嘛........參透中,真的不能理解這樣做跟實作一個有獨特功能的Handler的差異。終究只是今日一個無聊實驗的無聊結果。


2014年3月12日 星期三

【Java】位移運算...消失的位元...請不要強制讀取它!否則.....活不過七天

坦白說,在Java之下用位元運算的方式來處理「乘以二」這件事情,感覺不太出有明顯的效能差異。

(我用百億次的方式累積差異,但還是沒有明顯的差。)

但就算不討論效能上的差異,位元運算是很多工業用或商業用程式設計技巧的基礎,只是一直被JAVA(的標準教程)給忽略了。

原因或許不難理解,因為除非為了特殊理由,──例如加密──位元運算的意義在JAVA中很雞肋。



【以下開始為無厘頭的內容,有點像是我個人的自言自語。不建議點進來的人認真看待。反正知道Java中用位移方式做「乘以二」並沒有塊到哪裡去就好,願意自己做實驗更好。】

所以「講」一些很基本的東西好了...(這其實是筆記,並不是教材,路過看到的人...見笑了。)

byte值的上下限是-128~127。

問題是如果將127在加一,會如何呢?



位元運算的基礎原理就是在二進位的表示法中,將所有的1和0左右移動。

例如在二進位中的110其實是6,如果乘以二變成12,二進位表示法是1100。

它只是往左移動一位,並補上一個零。

如果是基數,例如7,二進位表示法111,除以二則是往右移位,變成011,但零可以省去,變成11。

結果...如果是乘法,或是往左位移,除了數字增加以外,還會增大這個數值得「單位」

例如本來是Integer長整數,往左移動幾次後就變成了long...



但有個問題是...如果將一個負數做往左或往右位移呢?

正數位移最後會變成0,但是負數卻會變成....-1。(1再往右位一,一樣會變成零。)



這就是上面要問將127在加一會如何的道理。

假設127是01111111,加一後會變成10000000,也就是-128。

不要懷疑!

10000000是負128。

也就是說00000001+11111111會得到00000000....(1消失了)......也就是零。

1加多少會是零?...當然是-1啊!

所以......-128不停的往右位移...不是補0,因為它從10000000到最後變成11111111。



(驚恐!!!!!!!)

除非編譯器的程式有特別判斷,這是否可以表示...往右位移其實是將「第一個位元繁殖」。

所以第一個位元若為0(同時表示這是個正數),所以往右位移後補上的是0,若為1(同時表示這是個負數),所以往右位移後補上的是1。

那往左位移呢?

除非程式又設定為「一律補零」,否則這是否表示其實在最右邊的位元之外,還有一個位元,只是設計者/使用者/人類永遠無法讀取修改它?



就好像七夜怪談中的錄影帶一樣......強制讀取的後果,蒼蠅會跑出來。

2014年3月10日 星期一

【Android】Activity中的Thread

一堆範例寫到Android中的Thread控制,都會寫一堆「怎麼把Thread關閉」的程式碼。

但其實...因為在Android中,有「Activity的生命週期」這件事,還有所謂的「APP之間的界線」,所以大家變得不太敢用一些理所當然的方法去操作Thread。



其實Thread並不會介意是哪個Activity在呼叫它、操縱它、管理它,就好像Thread根本不會介意設計者把操縱的程式寫在Activity之外。

把Thread的屬性設定為「public static」,然後大膽的關掉啟動它的Activity、並切換到另一個Activity。



程式意外的繼續順暢運作!

如果程式運作不順暢,絕對不是因為這樣切換Activity,更不是因為忘了把Thread關掉。



但...當你/妳確定這次按下「返回」後,APP的每個Activity都會退出執行狀態,那還是確保每個ˊThread都關掉吧!



講得很籠統?...連個程式碼都沒有,只有Thread/Activity/publi/static.....就別要求太多了。

(思考一下Thread設計的方式,思考一下Thread對各個Activity的意義。

如果是我,我會在Thread中留個Handler做為常數,讓各Activity不定時丟個Handler進去讓Thread來觸發/send......

如果是我,我會寫個統一的資料物件,讓各Activity去指揮Thread上網路接收資料,把資料轉入物件中,再讓Activity取用......

Google的Android API中,有些東西是寶,有些東西只有參考價值,但也有很多根本是渣。)

2014年2月18日 星期二

【Java】Make your object work better/fast...???

 That is my first English Blog post. And ,if you like to know, I never spend a day in school of Computer Science. So..... everything in this blog are amateur.
What is the different between those class???

If I use a loop to run a "new()" ... like new Test1(), or new Test2()... 100000 times, there are how much time they took to finish the loop.

Test1 :100 millisecond.
Test2:12000 millisecond.
Test3:34000 millisecond.

Obviously, more field the object has, more time it take to "new."

Is there more? Am I satisfied my own curiosity?

No! There is one more.


....... Interesting. They take the same time to finish the loop.

So the field make different, but method do nothing.



Wait!!!  There are more story about what happen when I use a infinity loop to test how many Test1/Test2/Test3 can a ArralyList<Test0> carry.......

Good night... it is 12 pm in Taiwan.

【Java】這樣設計物件有差喔!

差在哪裡?

我同樣用迴圈的方式放大其中的差異...在迴圈中執行「宣告動作」,──簡單來說,就是用類別 new 一個物件出來啦!

Test1執行的結果為100毫秒(大概的平均值)。

Test2執行的結果卻要12000毫秒。

Test3執行的結果更是增加到36000毫秒。

所以......愛護你/妳的程式、增加你/妳程式的效能,請從減少不必要的參數開始。

但萬一真的有必要,要使用「很多判斷式」.......把Test1/Test2/Test3改造成下列型式,然後參數規格都修改成一樣........


結果執行速度就差異不大...頂多1~2%,而且浮動性很高,極可能是「記憶體回收」或是其他干擾造成的。

重點在於:宣告階段影響程式效能的關鍵在「參數多寡」。



對!我聽到馬上有人質疑:反正一個物件宣告完後就擺在哪,何必計較宣告動作的效能呢?

今天假設是要做遊戲,比如某個關卡會有無限的「飛彈」、「小怪物」產生,「飛彈」不停被擊落、又不停射出,「小怪物」不停被打死、又不停被召喚........

當然可以把飛彈/怪物本身「被擊落與否」、「死亡與否」用純粹參數代替,(飛彈被擊落?重回原點等待發射,怪物死亡?重回原點等待召喚。)

如果採用這個做法,那確實沒有考量這點差異的必要。



但思考一下這個效能差異的由來......

雖然上面的附圖沒有截載,但關鍵在於記憶體。

記憶體配置?記憶體寫入?.......不管是哪一點,如果換個迴圈跑法,只是單純的做個ArrayList,然後一直塞物件進去、看記憶體何時爆掉........

Test3最快,Test2大約要Test3的三倍,Test1則是Test2的兩倍多一些......

還沒有機會測試使用了介面後的結果,但想必是相同的,因為使用介面根本不會有太多影響,介面(似乎)是類別的一部份,只會存在類別中,而不在物件存於記憶體的「標記/區段」上。

2014年2月11日 星期二

【JAVA】if...else if...else...判斷式,怎樣寫效能才會好?(3) 拿switch做比較

承續上一篇......我的「無聊神經」一旦開始運轉好像停不下來。

但是這次改用switch做比較。


我把這個迴圈用個函數包起來,a就是函數的傳入引數。

另外有一個函數在同樣的迴圈中寫入了「if...else...」,如下圖:




這兩個迴圈/函數執行的方式是......

run為switch,run1則是「if...else...」。為何要這樣做?run(1)和run(2)執行的結果不列入比較,原因看上一篇。剩下的.....做實驗當然要有充分的數據。

以下是結果...a是run函數輸出的,b則是run1......


先聲明:要使用switch或if來進行條件判斷,效能並不是優先考量......很多時候根本沒得選,因為switch只能判斷數值。(這算是「判斷」嗎?感覺跟分類沒兩樣。)

所以雖然效能明顯快了兩成,但其實只是作為一種比較而已。

2014年2月10日 星期一

【JAVA】if...else if...else...判斷式,怎樣寫效能才會好?(2)

上一篇,寫完後發現應該有個更客觀、更有準確的方法可以來測試,所以我先重新設計了作為基準/比對的「if...else...」,如下圖:




但跟上一篇不同的地方是我將它做成了一個函數...並且用以下的方法執行:


並且得到結果:

注意到了嗎...第1/2次的測試結果跟接下來的3/4/5/6差異過大。不管程式重複執行再多次也一樣。(這挺可怕的...雖然差異只是1~2%左右。)

不過這是插曲,接下來是我修改run()這個函數的內容的方法......


先聲明,用註解、或測底刪除程式,結果沒有明顯差異。測試結果如下圖.......




「if...else...」確實會明顯造成執行效能的差異。

這個測試法還有得搞.........(下一篇)

2014年2月7日 星期五

【JAVA】if...else if...else...判斷式,怎樣寫效能才會好?

「if」怎麼使用?

寫這個未免也太娘太小家子氣太學生口味了!(沒有瞧不起人的意思,感謝網路上無數的教學網站和教學文,但...你們還有需要加強的地方。)

參考一下我以前測試「效能」的方法,現在用來測下面這兩種「if」寫法在執行上會有多少效能差異......

IF1


IF2

上圖稱為IF1,下圖稱為IF2。因為我用個三層式的for迴圈來進行測試,所以會有i/j/k三個數值出現。

c1/c2的型態為long。看看他們被遞增的if條件式......是完全一樣的!也就是說如果c1被遞增、c2也會被遞增。唯一的差異就在於if...else的執行順序而已。

就理論上來說,「if」會被逐一執行,也就是說如果第一行if的條件吻合,則剩下的if都不會被執行,即使有條件更嚴謹、更精確、更穩和的。也就是說,理論上IF1執行起來會比較慢。

但另一個真正的問題是......要做if判斷,會需要耗掉多少資源。(沒辦法精準測量,只能取個概念。)

測試結果的彈性變化差異很大。



i/j/k的遞增極限都是1200,──k遞增到1200時會歸零,然後把j遞增1,j遞增到1200時也會歸零,然後把i遞增1......直到i也是1200,這個迴圈宣告結束。

limit1的值越小,IF1被執行到最後一行的機率就越高,理論上來說消耗的資源也越高。

當我把limit1定在400時,IF1執行完花了1769millisecond,IF2只花了836。

但當我把limit1定在800時,IF1執行完花了372millisecond,IF2只花了252。

當我再把limit1定在1000時,IF1執行完花了101millisecond,IF2只花了79。

最後但當我把limit1定在1200時,IF1執行完花了27millisecond,IF2只花了16。



這結果挺詭異的...我沒預想到,其實應該只要專注在小於800的數字就好。我擔心IF的條件太少會失去意義,但顯然我的IF條件太多...但又不夠多!(所以當limit1設在1200時,兩者的執行效率沒辦法達到一樣,因為我沒有精確的把所有i/j/k的狀況列入。)

可是結果肯定有差.......

如果寫了一長串的「if...else...」判斷式,然後又把常用的都擺在最後頭,程式的效能差異可以到達兩倍之多。(還沒有嘗試增加判斷式的難度會有何差別,那雖然不難.....但細節很多。)



這不單單只是效能測試而已!

程式碼光是簡潔還不夠,判斷式光是正確還不夠.......因為我們沒辦法預料這自己寫的判斷式會碰到什麼樣的資料/數據。我們沒辦法精準的保證「自己最前頭的判斷條件一定會被優先執行」。



所以會有使用函數並搭配return來控制函數執行的需要。不過這是改天再寫的了。

(但如果是我,我會用物件的多型性來取代這一長串判斷式。這有機會再寫的了。



點這是看續篇.......(我怎麼這麼無聊,連這種東西都寫續篇。)

2014年1月22日 星期三

[Java/多型性]多型性和強制轉換類別... (試過才知道)

真的稍微搞懂Java物件導向的人,都知道多型性是啥。

「參數型別宣告為父類別,則可以存入所有子類別。」.......講得很潦草,但大概是那樣。

可是這個特性具體來說會造成什麼影響?

子類別A強制轉型為子類別B時,會發生什麼事?

要測試多型性,自然要寫物件。以下有A(只是測試,就別花心思去取名了,)和A1/A2。

接著是初步測試用的程式......


可能有些人會理解、可能有些人不會,甚至有些人直接笑我蠢,但我真的想知道參數和函數對型別強制轉換的反應有何差異....

如果A2的test()內的傳入參數型別指定為A,因為多型性的理由,不用考慮一定都會順利執行。

但如果指定為A2,然後用A或A1強制轉型呢?



父類別似乎無法順利強制轉型為子類別,...不僅僅只是會損失資料而已,重新叫出了error訊息,訊息告訴我「無法如此轉型」。

顯然類別之間的型別轉換跟基礎數值的型別轉換不同,不是只會損失資料而已,而是根本上就限制了使用方式。

所以......所謂的「多型性」並不是種物件導向的特性,而是種人為操作/設計出來的規則。

不知道有沒有人能驗證這樣的解釋?



這次的實驗感覺很基礎很簡單很沒意義,但我不想知道怎樣可以成功,不想知道怎樣可以搞出「讓世人讚嘆的多型性應用」,但我對「怎樣操作多型性會失敗」很有興趣。就.......「浪費時間」吧!

2014年1月17日 星期五

[Java/Android]選取Spinner的內容同時改變Button的功能 (我會怎麼用純物件的方式寫這個功能)

(接續上一篇)還是這個功能/介面,資料處理方式可能是「各種加密方法」或「將資料傳向特定網站或資料庫」。

用「進化」的觀點來看,結構化程式設計技巧確實比物件化程式設計技巧更基礎。(前者在60~70年代出現並成熟、被發揚光大,後者雖然同時期出現,但Java卻是90年代的東西了。)

不用物件導向技巧還是可以在物件導向語言裡寫出可以運作良好的程式,只是...「維護」和「擴充」(特別是要由第三者來維護跟擴充)就很讓人「蛋疼」了。(女工程式請不要介意...)




我會先設計一個這樣的介面Interface(不是UI介面)。

看名稱應該就知道,這是擴充後要給Spinner/下拉彈出式選單作為內容的介面。

我擴充這個介面產生一個新類別,並在getText()中設定「return "功能一"」,如果用這個類別宣告實作物件,並將物件塞給Spinner的SpinnerAdapter,則選單中就會有「功能一」這個選項。以此類推...要多少有多少。(中間有一些設定SpinnerAdapter的細節,如果不知道自己推敲應變,那我這段描述會變得很怪。或者你知道使用其他Adapter來取代......)

然後再設計一個OnItemSelectedListener,──給Spinner使用的。這個OnItemSelectedListener具有「偵測」使用者選取了哪個Spinner選單內容的能力。(看到第22行的程式碼,那是個函數,裡頭有個「int position」,這就是「使用者選擇的參數」,──或者說是「選單中的第幾個選項」。)

Button b1就是「送出」鈕。藉著將這個Listener設定給Spinner,程式會即時、動態(使用者每選擇一次選單內容)的變換這個按鈕的OnClickListener。(list就是用來「裝」選單內容的地方/物件。)

(上面這個程式範例有嚴重的錯誤就是「它是OnItemClickListener」。但Spinner雖然保有這個屬性,但卻無法使用,執行Spinner的setOnItemClickListener......還沒成功過。幸運的是,除了名稱不一樣以外,它和OnItemSelectedListener結構與傳入參數都一模一樣。)



如下圖,這是Activity,可以看到我一共產生了三個選項,分別是「Test1」、「Test2」、「Test3」。(恕我省略HsuSpinnerAdapter的內容。因為就是個繼承自SpinnerAdapter的類別)



這支程式只會很可笑的把「Test1」、「Test2」、「Test3」作成小框框內的文字顯示在畫面上而已。但確實是「三個不同的功能」。



如果想要,可以自己設計獨特的SpinnerItem,然後放進list中,未來要擴充功能就會變得很簡單。

「難道不用物件導向技巧會變得很難寫嗎?」

這樣假設吧......今天有個「Test1.5」要放進「Test1」和「Test2」之間,那可能只要在ArrayList<String>的 「Test1」和「Test2」之間放進「Test1.5」,就會出現這個選項。

問題是「送出」按鈕的OnCLickLitener中,要將所有判斷式的position值重新修改,......其實也不難,但是如果又出現了「Test1.4」、「Test1.3」、「Test2.1」.......無限增加中,只要這個程式還活著、專案管理和企劃人員腦筋動個不停,這種事情就會發生。

當OnClickListener的程式碼長達八九百行、甚至一千五百行時,不停重複修改一支Class就不是讓人很愉快的事情了。

所以為何不在程式剛開始、專案剛起頭時,想好一個將來方便擴充的機制呢?

(文章寫到這裡,我又想到一個修改SpinnerItem的方法,將來就算要直接新增一個選單,──也就是一共會有兩個選單──也可以輕鬆擴充、修改的設計,......物件導向就是這麼迷人。)



上面的那個問題好像有點廢話,但問題是「有些工程師會認為像我這樣寫程式才是廢話。」

因為「物件架構」不是一翻兩瞪眼的東西,如果架構不可行,理論上有時後看不出來,要有具體程式碼才能驗證,而且驗證一次不夠,可能要兩三次。

第一次驗證失敗後,已經有一個功能和三千行程式碼,第二個功能進行設計完成時,要進行第二次驗證,結果又失敗,這時已經有六千行程式碼,結果這六千行幾乎都要打掉重寫,重第一個功能開始驗證......重複這樣的過程,直到確定架構可以重複運作在...「所有功能上」。

可能五六個星期已經過去了。

但是......直接抓起規格書,開始用土法煉鋼的「一個功能就一個物件/Activity」這樣寫,可能兩個禮拜就把初步規格書上所有的功能都完成了。

當這些工程師可以不斷一次又一次超前進度、快速精準完成專案時,「物件導向」就變成了「Java語言不方便的特性」而已。去精熟物件架構、去專研怎樣設計物件架構、累積設計物件架構的經驗.......只是一場笑話罷了。




這就是JAVA設計師的絕境與哀愁啊!物件導向其實好學!只要認真寫教學文、教學範例。甚至可以用Library重新包裝所有JAVA基礎程式指令,例如更精簡、更快速的Socket建立方式。寫JAVA?哪那麼難?

但是一堆人把JAVA當成單純的結構式語言、當成C/C++工程師找工作時的代替品,認為「要精通很多指令」(或說是「同一個指令要有很多次使用的經驗」)、「要能閱讀並掌握大量程式碼」、「要能快速、用直線思考的方式實現規格書的需求」......才是工程師之道。

2014年1月16日 星期四

[Java/Android]選取Spinner的內容同時改變Button的功能 (為何程式設計師流動率高?)

上面(假設)是個Android功能的UI,資料處理方式可能是「各種加密方法」或「將資料傳向特定網站或資料庫」。

EditText和Spinner的使用及設定方式,我不想花太多力氣詳述,(我講概念/心得/天馬行空亂撞的結果,不講別人寫死的技術規格。)

一般來說,程式寫法很多、怎樣寫都行,但我覺得最糟糕的寫法就是用匿名/無實體的類別搭配ArrayList<String>來作為SPinner的Adapter。



首先在會使用到這個介面的Activity中設定一個參數int p,同時設定一個OnItemClickListener,在這個介面的功能onItemClick(AdapterView<?> parent, View view, int position, long id)中,int position參數可以傳給p( p = position )。

「送出」按鈕的OnClickListener則是會判讀參數p的值,如果是0,就表示使用者選擇了方法一,如果是1,就表示使用者選擇了方法二...以此類推,然後在各個判別條件後寫入真正的「方法一/方法二........」的程式碼。



而且,這些Adapter/OnItemClickListener/OnClickListener都是用匿名/無實體的類別來設計。

程式碼全都擠在一支Activity的程式中。 (對這些人來說,Java的「物件導向」只是寫Java程式不得不應付的「規則」和「語法」罷了!類似「每支程式都是用class這串字當開頭」之類的限制。他們很擅長應用別人寫好的物件、把那些物件當成基礎指令套件一樣使用,他們知道怎麼把程式結構化,但他們並沒有真的在思考怎麼把程式物件化。)



我碰到一堆人把「這樣才是成熟的工程師寫程式該有的風格」當成聖經。

即使這個功能將來如果要新增Spinner中的「方法」好達到擴充功能的目的,結果接手的人找不到「方法」寫在什麼位置,而導致整個程式只能無條件要打掉重寫........(一個功能只有一個輸入框、一個彈出選項、兩顆按鈕的功能不太可能發生這種「接手的人找不到舊功能程式碼寫在什麼地方」的窘境,所以大家假設一下這個功能有三個輸入框,同時有兩個彈出選向來交叉決定「功能」,還有三個按鈕可以選取,同時這個功能要能應付至少六種客製化版本........)

怎辦?「換個人來寫程式。」

他們永遠不會發現「自己家的Java工程師流動率非常高,特別是菜鳥陣亡率奇高無比!」他們只會慢慢的歸納出一個結論:真的會寫程式的人好少。



(下一篇......)

2014年1月15日 星期三

[Java]用介面Interface使用「用介面Interface訂出來的參數架構」

 (接續上一篇「用介面Interface設計參數架構」...)

有點饒口的標題。

我相信(因為這也曾經困擾我)一個程式語言的語法好學習,迴圈、判斷式、運算子、函式定義、物件宣告......可是怎麼把這些「規矩」組合成「功能」,就很難學......

因為鮮少有人在教。

如果在網路上、在論壇上,明確的講出自己想要的功能,其實得到的往往是連「模糊」都稱不上的答案。當然不能怪那些程式老鳥們這種態度,畢竟「又沒錢拿」,如果菜鳥們、出學者們都乖乖掏個幾千、甚至幾萬塊去走進補習班和短期課程,學程式設計肯定會好混許多。



可是學習方式應該有很多種。從「規矩」到「具體功能」之間,明顯有個難以跨越的門檻,沒理由要讓「任大家自生自滅,可以自己領悟的人才有資格進入程式設計殿堂」變成放諸四海、天下人接要遵守的規矩。

所以我用我的風格寫這些文章,或許......有人看過這些文章後可以有比讀其他文章、鑽研被公開分享的範例更有效率的學會程式設計。



接續上一篇「用介面Interface設計參數架構」...

(別告訴我你不懂何謂參數......我還沒準備好寫那麼基礎的東西。)

用「介面設計參數」這話直覺上違反Java特性,因為寫在介面中的參數會變成final形態,是不能被修改的。

可是 (請注意接下來這段話特指物件導向,所以看完立刻以為「你挑出了明顯的錯誤和例外」的人請去重讀物件導向) ...「物件導向的特性讓參數和函式在記憶體中都只是個位址,差別在於參數的位置可能導向另一個存放數值的記憶體區塊,函數的位置則是導向存放一連串的運算式、指令、判斷式、迴圈的記憶體區塊, 共通點在於它們都是個「另一段記憶體區段的索引」罷了,(主要的差異在於:函數存的索引值不能改,可是參數可以。另外就是程式碼的寫法不同。)

所以.......「為何不直接把函數拿來當參數用?反正在多型性的程式世界中,編譯器才不會好奇為何設計者設計的父類別沒有參數只有函數。」

但這都是 「用介面Interface設計參數架構」中的內容。



這篇講的其實是「多形性」到底怎麼應用來寫程式。或是說「用多型性寫程式」到底是會寫出什麼樣的程式。

這個介面只有定義一個函式。而這個函式要求傳入一個屬性為ValueInterface的參數。

為何只定義一個函數?

這是因為「大家習慣用IDE編輯器來編寫Java」,而「IDE編輯器(這裡是指Eclipse)讀取註解時有些特性」。

下圖是個「實作」這個介面的程式/類別/物件...(我又迷失在專業術語中了。)

先不看函數中的程式碼,(此為物件導向寫作法的好習慣,)注意一下開頭的註解(第36到39行的程式碼),這個註解會在任何寫了Operate1這個物件名稱的地方跳出來。(只要把滑鼠游標一到上面,就會跳出這段程式碼。

像下圖...可以看到游標一到Operate1上,就自動彈出一個小視窗,顯示註解內容。

有趣的問題是ops這個矩陣中,不管是ops[0]、ops[1]、或ops[2],都有operate()這個函數。

可是如果把註解寫在Operate1的operate()函數上,游標移到接下來的程式碼中出現的operate函數時,什麼都不會顯示。
因為...這個operate()是介面ValueOperatorInterface的operate(),不是Operate1的,而我在介面中沒有寫任何註解,自然不會顯示。

詳細一點說明為何只設計一個函數:當這個物件只有一個函數時,你寫在建構子上的註解自然會是它唯一的功能說明,閱讀性理論上會好一點。可是我工作的地方,大部分的工程師都還是習慣結構導向寫法,物件導向...頂多就是「記得寫個建構子」,多型性?好像沒啥人真得在活用。



基於多型性的理由,我不會考慮直接用Operate1當成參數的型別/屬性,(但是這樣設計程式沒什麼錯,更沒什麼問題,就是有點可惜罷了。而且如果要這樣作,就沒有必要寫介面了。)

但說到所謂的多型性...我可以隨意組合ops矩陣的內容,而且都會成立、都可以精準運作。(除果出錯,絕對是程式出錯,而不是「多型性」這個原則有例外。)

有了多型性,我設計好了各種不同的Operator類別,然後在各種不同的程式中組合它們......我就可以快速擴建出各種不同功能的程式。




一般會反駁我這種寫法、認為這樣很多餘的人,都會認為...「與其我設計三種Operator類別,不如我在ValueInterface中額外增加三個函數,(甚至直接就用物件寫,不需要經過設計ValueInterface的步驟,)然後把本來要寫在三種Operator中的程式碼般到三個函數中。」(參考一下我平常測試的方法...這次不附上測試方法了。不過,如果覺得這樣測試根本不對......根本不重要啦!原因...繼續看下去。)

所以我一開始要解釋參數與函數的差異......

我原始的想法是認為......一個只有兩個函數(一個讀取參數、一個寫入參數)的介面會在記憶體中佔去兩個單位的記憶體,一個有五個函數的介面自然就是在記憶體中佔去五個單位的記憶體。如果用矩陣大量操作這個ValueInterface,例如說「現在要1000個ValueInterface」,想想看「2*1000」和「5*1000」,會有多少差距?

但後來發現......其實不會有差。設計兩個類別,都只有一個參數,然後甲類別有兩個函數,乙類別有五個,分別用一個ArrayList無限量去「產生、並收納」,結果會發現...我在Android模擬器下分別去執行,會發現大概都在89300000次的執行上下時,會出現「OutOfMemory」的錯誤。

反過來,如果將乙類別的函數全砍掉,改成八個參數,(參數的結構不詳述,)結果執行次數一口氣就掉到14300000左右而已。

簡單來說,我對於在物件導向機制下Java應用記憶體的方式假想試錯誤的,「函數並不會隨著類別宣告為實體物件而增加佔去的記憶體空間,只有參數會。」不管類別A被產生了幾個物件實體,函數都導向/共用同一個記憶體區塊。

(所以我上面說「測試方法對不對」並不重要......仔細研究了一下物件導向的機制,確實如此。)

我並不能夠說服自己:這樣寫,程式的效能和記憶體的使用效率比較高。──並不會!但我還是繼續這樣寫程式。



為何要這樣寫程式?因為......

專案方便擴充!提高程式碼利用度!

如果不盡可能徹底使用物件導向,一個程式未來如果要擴充功能,難度明顯會高許多。

上面提到的「與其我設計三種Operator類別,不如我在ValueInterface中額外增加三個函數,(甚至直接就用物件寫,不需要經過設計ValueInterface的步驟,)然後把本來要寫在三種Operator中的程式碼般到三個函數中。」假設一支程式經過無數次的改寫擴充後,已經從三個函數增加到十三個函數,每個函數的平均工能從三十行程式碼增加到六十行程式碼......然後今天又要再增加兩個新函數,然後有至少五個舊函數的功能要修改........請便吧!看到這種程式碼,我每次都有罷工的衝動。

把要新增加的功能額外獨立成一支程式/類別,然後再要擴充的地方增加一個參數。像下圖,我只要新設計一個實作 ValueOperatorInterface的類別,然後在switch中找個地方插入它即可......

雖然這樣沒有徹底作到「舊程式永遠不會膨脹」,但是修改的難度低、幅度永遠不會過大,而且程式的邏輯/模式永遠不會變化太大,不會因為逐次擴充/修改/維護,而失去原樣。

這個範例使用的是switch指令。假想一下今天使用的是「if...else...」然後每一段「...」裡頭都有數百行程式碼,第一次拿到程式碼、又要再上頭擴充功能的人,可能就會迷失在追蹤各種if條件值中,──可能很難搞懂自己新增加的if條件為何沒有順利發揮作用,到底是哪個if、或哪個if中的if把程式的執行判斷權拿走了。(「執行判斷權」?我又發明了一個莫名其妙的名詞。)

當那「數百行程式碼」被縮減成一個物件和它的函數時,if就很好追蹤了。(就像這個switch......天哪!比這個switch還好用。)



另外,如果不盡可能徹底使用物件導向卻又牽涉到「版本控管」......先假定物件導向跟專案管理無關,一般的專案管理就只能期望專案管理軟體夠強、或專案團隊製定的專案管理流程夠嚴謹.......好笑的是:JAVA物件導向的精神之一就包含了解決上述問題。

使用Java撰寫程式不善用物件導向特性,卻期望「專案管理」軟體代勞......我不知道這叫什麼,只是發現這想法/態度/策略挺普遍的。

有機會在介紹一個更明確的「多型性」寫作法。

2014年1月13日 星期一

[Java]用介面Interface「設計參數架構」

標題所謂的「參數架構」是指擁有/繼承自同一個父類別的所有子類別們,都會有相似的參數型態。

如果類別A有個屬性為開放(public)的參數X,那繼承類別A的類別B和類別C都會有參數X。

如果我在程式中宣告了一個參數Y,這個Y的種類為類別A,(這個時候或許應該稱Y為物件Y,或實作Y,.......這方面的名詞我一向很不講究,)那我不管塞進了類別B或類別C,後續的程式都可以在參數Y中另外獲得參數X。(當要讀取或修改「Y.X」時不會發生錯誤。)

或許可以稱這樣的目的為「設計參數架構」。



學Java都會學物件導向。

教學範本大多從Object的extends開始教起,其次才會講到怎麼自己設計介面/接口。

結果......大家最後都直接習慣設計Object、擴充自己寫的(或別人寫的)Object,然後沒啥人在用介面/接口。

我個人用介面的頻率算挺高的...事實上,我覺得寫Java程式就是先訂出Interface,並且已Interface為程式邏輯/流程設計和運作的基礎/核心。

「但是Interface有很多限制,寫在裡頭的參數都會被強制轉成final形態,如此一來不管是開放(public)、私用(private)、限制(protected),都沒有意義了。」

(用前面參數Y作範例來解釋的意思是:如果把參數X1寫在介面D中,則繼承/擴充介面D的類別E和類別F也會有參數X1,但這個X1是個「常數」,只能讀取,對它作修改......不會出錯,但不會有意義,X1的值會永遠保持在設計者寫在程式碼中的值,如果我明確寫了X1=0,不管我後來寫了什麼程式碼,X1就是0。)

不懂我在描述的狀況是什麼?或不曾有過類似的疑惑和需要?

反正.......

介面/接口/Interface也是可以拿來幫參數訂架構的。

這是一個介面。(public interface後面的ValueInterface是這個 介面/接口/Interface的名稱......)

像這樣......  我就可以用a()這個函式/接口來產生一個「參數a」了......

如果要把「參數a」傳給「參數b」,我就用「b = a();」。

如果要把「參數a」加上「參數b」,我就用「a( b );」。

像以下的Value0...



 注意到我想要達成的效果了嗎?可能還不夠明顯......

總之,凡是介面ValueInterface的子類別,都會有參數a。

至於參數a(第91行)的「真面目」,可能可以是(x+y),參數a(第94行)則是(y = i)。

感覺上沒什麼不同,只是想證明「也可以用介面形式來達到設計參數」。這些範例並不是非常有意義、更不用提效率與價值了。



這樣作具體來說有任何好處嗎?

Java的設計是「一個物件只能繼承一個物件」,如果類別甲繼承自類別乙,雖然它會完整的繼承類別乙的數值結構,但如果它同時也需要類別丙的數值結構呢?

如果類別丙的數值結構是在介面之下用我這裡所寫的形式,這就不會是問題了。

一連串的類別可以任意搭配一系列的介面......這裡可以有也是用來「設計參數架構」的介面丙一、丙二、丙三.......

可以用丙一和丙二搭配出類別一,用丙一和丙三搭配出類別二。不管是類別一或類別二,都可以塞進屬性為丙一的參數中。

「能」這樣設計程式不就是物件導向的特性嗎?作為全物件導向語言之一,使用Java卻不善用這種特性與思維......何必呢?它的「可攜性」可沒那麼強大。