2014年3月31日 星期一

常業詐欺犯

關於服貿, 我有一點想法.

昨天總裁出來講話了, 說開放對我們有利. 但不好意思, 不是所有的開放都有利. 身為 9A 總裁大概沒人敢批評吧. 但我認為其功勞就是將匯率把穩舵而已. 在連續獲得 9A 佳評之時, 也是台灣經濟江河日下的期間, 不是嗎? 9A 就是神嗎? 想想李遠哲的桂冠吧! 還很亮是嗎?

昨天, 朱立倫也講話了. 大概是看到林飛帆出場時, 群眾高呼選總統吧! 話語權被這個名不見經傳的小夥子拿走了, 大阿哥情何以堪啊!

這一夥人, 當年在台上信誓旦旦說, 我們準備好了. 喔. 準備好大漲特漲. 房價漲, 物價漲, 什麼都漲. 誰去看看那些社會底層的人民過什麼樣的生活.

這一夥人, 當年誇下海口保證 633, 原來是張遠期芭樂票, 尋咱人民開心的呢.

ECFA 說是 A 擱發, 沒錯, 財閥裙帶大發特發, 榮歸故里驕其妻女呢.

經濟總設計師, 設計到哪裡去了呢?

七年來, 這夥人給我的印象是, 一群常業詐欺犯, 真想打 165 報案呢.

Java 複習筆記 : 日期與時間

今天要繼續寫常用類別庫的日期部分時, 原本是想照搬 Javascript 版本的 get_date_time(), 改寫為 getDateTime() 就可以了, 因為 Java 裡面的 java.util.Date 跟 Javascript 裡處理日期時間的 Date 物件用法幾乎一模一樣, 改寫起來應該很輕鬆啦, 但編譯時卻發現如下錯誤 :

Note: JT.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

檢查 API 才發現, 原來 java.util.Date 的方法大部分早已經被廢棄 (deprecated), 參見 :

http://docs.oracle.com/javase/7/docs/api/java/util/Date.html

一. java.util.Date類別 :

Date 物件僅剩下列四個是常用的方法 :
  1. after(Date when) : 檢查日期物件時間是否在本物件後 (true/false)   
  2. before(Date when) : 檢查日期物件時間是否在本物件前 (true/false)   
  3. getTime() : 傳回自 1970/1/1 以來之毫秒數
  4. setTime(int ms) : 將時間設定為 1970/1/1 後之毫秒數
  5. toString() : 把 Date 物件轉成字串
例如 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    Date d=new Date();                    //建立目前時間之 Date 物件
    System.out.println(d.getTime());   //輸出 1396313616224
    System.out.println(System.currentTimeMillis());  //輸出 1396313616224
    System.out.println(d.toString());   //輸出 Tue Apr 01 08:53:36 CST 2014
    Date d1=new Date(1);     //建立時間戳記為 1ms 之 Date 物件 (自 1970/1/1)
    System.out.println(d1.before(d));  //輸出 true
    System.out.println(d.before(d1));  //輸出 false
    System.out.println(d1.after(d));     //輸出 false
    System.out.println(d.after(d1));     //輸出 true
    d1.setTime(4396267507921L);   //須為長整數
    System.out.println(d.after(d1));     //輸出 false
    }
  }

要取得自 1970/1/1 以來的毫秒數也可以用 System.currentTimeMillis() 方法, 事實上, 在 new Date() 時, 也是使用此方法取得系統時間的.

Date 類別其他以前常用的 getYear(), getDate() 等都被廢棄了, 我離開 Java 太久了, 竟然一無所知. 現在要改用 java.util.Calendar 類別, 其 API 如下 :

http://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html

二. java.util.Calendar 類別 : 

使用 Calender 時只要呼叫其靜態方法 getInstance() 就會傳回一個 Calendar 物件 :

Calendar c=Calendar.getInstance();

要取得年月日時分秒資訊可直接擷取其欄位, 傳回值均為整數, 我們可將其放入陣列 :

    int[] a={c.get(Calendar.YEAR),
                 c.get(Calendar.MONTH),
                 c.get(Calendar.DAY_OF_MONTH),
                 c.get(Calendar.HOUR_OF_DAY),
                 c.get(Calendar.MINUTE),
                 c.get(Calendar.SECOND)
                 };

注意這裡 DAY_OF_MONTH 之值為 0~11, 與 Javascript 的 Date 物件之 getMonth() 一樣, 所以必須加一轉成我們習慣的 1~12 月. 現在是 3 月 31 日, 下面的範例卻顯示 2 月 31 日 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    Calendar c=Calendar.getInstance();
    int[] a={c.get(Calendar.YEAR),
             c.get(Calendar.MONTH),
             c.get(Calendar.DAY_OF_MONTH),
             c.get(Calendar.HOUR_OF_DAY),
             c.get(Calendar.MINUTE),
             c.get(Calendar.SECOND)
             };
    for (int i:a) {System.out.print(i + " ");}  //輸出 2014 2 31 16 6 33
    }
  }

如果要指定日期時間, 則要呼叫 set() 方法,

set(int year, int month, int date, int hourOfDay, int minute, int second)

注意, 其中的月份也是要傳入實際月份減一才對.

我的常用類別庫對日期時間的要求其時很簡單, 最常用的是傳回目前的日期時間字串, 以便寫入資料庫或 log 檔中, 慣用格式為 "2014-03-31 13:24:41".  這可以用下列靜態方法達成 :

import java.util.Calendar;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getDateTime());  //輸出 2014-03-31 16:15:45
    System.out.println(getDateTime(2014,3,31,0,59,59)); //輸出 2014-03-31 00:59:59
    }
  public static String getDateTime() {  //無參數=傳回現在時間
    Calendar c=Calendar.getInstance();
    return getYMDHMS(c);
    }
  public static String getDateTime(int Y, int M, int D, int H, int m, int S) { //指定時間
    Calendar c=Calendar.getInstance();
    c.set(Y, --M, D, H, m, S);  //傳進來的實際月份要減 1
    return getYMDHMS(c);
    }
  public static String getYMDHMS(Calendar c) { //輸出格式製作
    int[] a={c.get(Calendar.YEAR),
             c.get(Calendar.MONTH),
             c.get(Calendar.DAY_OF_MONTH),
             c.get(Calendar.HOUR_OF_DAY),
             c.get(Calendar.MINUTE),
             c.get(Calendar.SECOND)
             };
    StringBuffer sb=new StringBuffer();
    sb.append(a[0]);
    if (a[1]<9) {sb.append("-0" + (a[1] + 1));}   //加 1 才會得到實際月份
    else {sb.append("-" + (a[1] + 1));}
    if (a[2]<10) {sb.append("-0" + (a[2]));}
    else {sb.append("-" + (a[2]));}
    if (a[3]<10) {sb.append(" 0" + (a[3]));}
    else {sb.append(" " + (a[3]));}
    if (a[4]<10) {sb.append(":0" + a[4]);}
    else {sb.append(":" + a[4]);}
    if (a[5]<10) {sb.append(":0" + a[5]);}
    else {sb.append(":" + a[5]);}
    return sb.toString();
    }
}

可見對於小於 10 之值前端補 0, 就會傳回我要的標準格式. 注意, 上面的 getDateTime() 方法已經針對月份做加 1 處理過了, 因此呼叫時要傳入實際的月份, 不須再加 1 了.

Calendar 也跟 Date 一樣有一個 getTime() 方法, 但它會傳回一個 Date 物件, 而非自 1970/1/1 的毫秒數. Calendar 類別中相同功能的方法為 getTimeInMillis(). 同樣的, Calendar 也有一個 setTime() 方法, 但其傳入參數不是一個長整數, 而是一個 Date 物件 :

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    Calendar c=Calendar.getInstance();
    Date d=c.getTime();  //傳回 Date 物件
    System.out.println(d.getTime());             //輸出 1396267507921
    System.out.println(c.getTimeInMillis());    //輸出 1396267507921
    d.setTime(1396277507921L);  //傳入長整數
    c.setTime(d);  //傳入 Date 物件
    int[] a={c.get(Calendar.YEAR),
             c.get(Calendar.MONTH),
             c.get(Calendar.DAY_OF_MONTH),
             c.get(Calendar.HOUR_OF_DAY),
             c.get(Calendar.MINUTE),
             c.get(Calendar.SECOND)
             };      
    for (int i:a) {System.out.print(i + " ");}  //輸出 2014 2 31 22 51 47
    }
  }       

Calendar 類別也有 before() 與 after() 用來比較兩個 Calendar 物件的時間先後, 例如

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    Date d=new Date();
    Calendar c1=Calendar.getInstance();
    Calendar c2=Calendar.getInstance();
    d.setTime(1);     //設定時戳為 1ms
    c1.setTime(d);
    d.setTime(2);     //設定時戳為 2ms
    c2.setTime(d);
    System.out.println(c1.before(c2));  //輸出 true
    System.out.println(c1.after(c2));     //輸出 false
    }
  }       

Calendar 類別還有一個常用的應用是求指定日期是星期幾. 這個必須用到 Calendar.DAY_OF_WEEK 這個欄位, 其值為 0 (Sunday) ~ 6 (Saturday), 相對應 Calendar.SUNDAY~Calendar.SATURDAY. 透過判別 Calendar.DAY_OF_WEEK 之值, 即傳回 "星期日" ~ "星期六" 字串, 如下列範例所示 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getDayOfWeek("2014-04-01"));  //輸出 星期二
    }
  public static int[] parseDate(String date) {
    String[] d=date.split("-");
    int[] dt=new int[3];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    return dt;
    }
  public static String getDayOfWeek(String date) {
    int[] d=parseDate(date);
    Calendar c=Calendar.getInstance();
    c.set(d[0],d[1]-1,d[2]);  //這裡務必減 1
    String w="";
    switch(c.get(Calendar.DAY_OF_WEEK)) {
      case Calendar.SUNDAY :    {w="星期日";break;}
      case Calendar.MONDAY :    {w="星期一";break;}
      case Calendar.TUESDAY :   {w="星期二";break;}
      case Calendar.WEDNESDAY : {w="星期三";break;}
      case Calendar.THURSDAY :  {w="星期四";break;}
      case Calendar.FRIDAY :    {w="星期五";break;}
      case Calendar.SATURDAY :  {w="星期六";break;}
      }
    return w;
    }
  }


三. DateFormat 與 SimpleDateFormat 類別 :

這兩個類別與上面的 Date 跟 Calendar 源頭不同, 它們並非來自 java.util 類別庫, 而是源自 java.text.Format 類別, SimpleDateFormat 則是 DateFormat 的子類別.

java.lang.Object
    |__ java.text.Format
                |__ java.text.DateFormat
                           |__ java.text.SimpleDateFormat

這兩個類別主要是用來轉換日期字串與 Date 物件, 剖析日期字串可能因為格式錯誤而拋出例外, 所以使用 DateFormat 與 SimpleDateFormat 這兩個類別時, 必須匯入 ParseException 類別 :

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;

或者乾脆 :

import java.text.*;

# http://docs.oracle.com/javase/7/docs/api/java/text/DateFormat.html
# http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

DateFormat 類別有四個欄位用來設定日期字串的顯示格式 (style) :

 欄位 說明
 DateFormat.SHORT (3) "3/31/14" 2014 年 3 月 31 日 (美式)
 DateFormat.MEDIUM (2) "Mar 31, 2014" 
 DateFormat.LONG (1) "March 31, 2014"
 DateFormat.SHORT (0) "Monday, March 31, 2014"

此四種格式是用來設定日期物件的顯示格式. 跟 Calendar 類別一樣, 要取得 DateFormat 物件可以直接呼叫類別方法 getDateTimeInstance() :

DateFormat df=DateFormat.getDateTimeInstance();

沒有傳入參數的話, 傳回的 DateFormat 物件會以預設的日期與時間格式與區域 (Locale) 來進行格式化. 呼叫 format() 方法即傳回值其格式化字串, 如下所示 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    Date d=new Date();
    DateFormat df=DateFormat.getDateTimeInstance();  //取得預設格式化物件
    System.out.println(df.format(d));      //輸出 2014/4/1
    }
  }

也可以傳入日期與時間格式參數 :

getDateTimeInstance(int dateStyle, int timeStyle) 

這樣就會依照指定格式顯示, 這裡為了簡單起見, 傳入整數代碼 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    Date d=new Date();
    DateFormat df_short=DateFormat.getDateTimeInstance(3,3);      //SHORT 格式
    DateFormat df_medium=DateFormat.getDateTimeInstance(2,2);  //MEDIUM 格式
    DateFormat df_long=DateFormat.getDateTimeInstance(1,1);       //LONG 格式
    DateFormat df_full=DateFormat.getDateTimeInstance(0,0);         //FULL 格式
    System.out.println(df_short.format(d));  //輸出 2014/4/1 上午 10:18
    System.out.println(df_medium.format(d));  //輸出 2014/4/1 上午 10:18:06
    System.out.println(df_long.format(d));  //輸出  2014年4月1日 上午10時18分06秒
    System.out.println(df_full.format(d));   //輸出 2014年4月1日 星期二 上午10時18分06秒 TST
    }
  }

可見預設是顯示本地的區域語言 (Locale), 如果要顯示英文, 則要傳入第三參數 locale :

getDateTimeInstance(int dateStyle, int timeStyle, Local locale) 

這就需要用到 java.util.Local 類別來建立一個 Locale 物件 :

Locale locale=new Locale("en", "US");

第一個參數為語言, 第二個參數是國家 :

Locale(String language, String country)

台灣的話, language 傳入 "zh", country 傳入 "zh_TW". 如下表所示  :

語言 Language Country
 美式英文 en US
 英式英文 en en_GB
 加式英文 en en_CA
 繁體中文 zh zh_TW
 日文 ja ja_JP
 韓文 ko ko_KR
 簡體中文 zh zh_CN
 德文 de de_DE
 義大利文 it it_IT
 法文 fr fr_FR
 加式法文 fr fr_CA

http://docs.oracle.com/javase/7/docs/api/java/util/Locale.html 

下列範例設定為英文格式輸出 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    Date d=new Date();
    Locale locale=new Locale("en", "US");
    DateFormat df_short=DateFormat.getDateTimeInstance(3,3,locale);
    DateFormat df_medium=DateFormat.getDateTimeInstance(2,2,locale);
    DateFormat df_long=DateFormat.getDateTimeInstance(1,1,locale);
    DateFormat df_full=DateFormat.getDateTimeInstance(0,0,locale);
    System.out.println(df_short.format(d));   //輸出 4/1/14 11:05 AM
    System.out.println(df_medium.format(d));  //輸出 Apr 1, 2014 11:05:48 AM
    System.out.println(df_long.format(d));   //輸出 April 1, 2014 11:05:48 AM CST
    System.out.println(df_full.format(d)); //輸出 Tuesday, April 1, 2014 11:05:48 AM CST
    }
  }

可見傳入美國的 Locale 後就全部洋化了. 以上是把 Date 物件格式化為字串輸出, 反過來要把日期時間字串轉成 Date 物件該怎麼做? 這可以呼叫 parse() 方法來剖析字串, 此方法會傳回一個 Date 物件 :

Date parse(String source)

但日期時間字串格式不見得正確, 可能會拋出例外, 因此 parse() 必須放在 try catch 區塊裡面, 如下例所示 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    Date d=new Date();
    Locale locale=new Locale("en", "US");
    DateFormat df_short=DateFormat.getDateTimeInstance(3,3,locale);
    DateFormat df_medium=DateFormat.getDateTimeInstance(2,2,locale);
    DateFormat df_long=DateFormat.getDateTimeInstance(1,1,locale);
    DateFormat df_full=DateFormat.getDateTimeInstance(0,0,locale);
    try {d=df_medium.parse("Apr 1, 2014 11:05:48 AM");}
    catch (ParseException e) {e.printStackTrace();}
    System.out.println(df_short.format(d));  //輸出 4/1/14 11:05 AM
    System.out.println(df_medium.format(d)); //輸出 Apr 1, 2014 11:05:48 AM
    System.out.println(df_long.format(d));    //輸出 April 1, 2014 11:05:48 AM CST
    System.out.println(df_full.format(d));  //輸出 Tuesday, April 1, 2014 11:05:48 AM CST
    }
  }

注意, 傳入的日期時間字串, 格式必須符合 DateFormat 之設定, 否則執行時期會拋出例外. 由於 DateFormat 只提供了四種格式, 因此無法產生我需要的 "YYYY-MM-DD HH:mm:SS" 格式, 這可以用 DateFormat 的子類別 SimpleDateFormat 來解決.

SimpleDateFormat 跟 DateFormat 一樣用在 Date 物件與格式字串的互轉, 但 SimpleDateFormat 定義了一些特定的格式字元, 可藉著格式字元來產生所需要的輸出. 參考 :

http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

與 DateFormat 不同的是, 建立一個 SimpleDateFormat 物件是必須呼叫其建構子 :

SimpleDateFormat sdf=new SimpleDateFormat();

此類別的 toPattern() 方法會傳回目前的格式字串, 而 applyPattern() 方法則可以傳入格式字串以套用新的格式, 如下例所示 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    Date d=new Date();
    SimpleDateFormat sdf=new SimpleDateFormat();
    System.out.println(sdf.toPattern());  //輸出 yyyy/M/d a h:mm
    System.out.println(sdf.format(d));    //輸出 2014/4/1 下午 1:44
    sdf.applyPattern("yyyy-MM-dd HH:mm:ss");  //套用新格式
    System.out.println(sdf.format(d));  //輸出 2014-04-01 13:44:36
    }
  }

可見, 如果建立 SimpleDateFormat 物件時沒有指定格式, 則預設的格式字串為 "yyyy/M/d a h:mm". 也可以在建立物件時就指定格式字串與 Locale 物件 :

SimpleDateFormat(String pattern, Locale locale)

如下範例所示 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    Date d=new Date();
    Locale locale=new Locale("it", "it_IT");  //義大利語
    String pattern="yyyy.MM.dd G 'at' HH:mm:ss z";
    SimpleDateFormat sdf=new SimpleDateFormat(pattern,locale);  //指定格式與區域語言
    System.out.println(sdf.toPattern()); //輸出 yyyy.MM.dd G 'at' HH:mm:ss z
    System.out.println(sdf.format(d)); //輸出 2014.04.01 dopo Cristo at 14:51:27 CST
    }
  }

輸出格式中的 G (Era designator 年代) 就顯示義大利文的 "dopo Cristo" (西元 : 主後).

既然 SimpleDateFormat 是 DateFormat 的子類別, 當然可以使用 format() 與 parse() 方法來互轉 Date 物件與日期時間字串, 如下列範例所示 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    Date d=new Date();
    System.out.println(d.toString());  //輸出 Tue Apr 01 15:04:12 CST 2014
    String pattern="yyyy-MM-dd HH:mm:ss";
    SimpleDateFormat sdf=new SimpleDateFormat(pattern);
    System.out.println(sdf.format(d)); //輸出 2014-04-01 15:04:12
    try {d=sdf.parse("2013-12-25 23:59:59");}
    catch (ParseException e) {e.printStackTrace();}
    System.out.println(d.toString()); //輸出 Wed Dec 25 23:59:59 CST 2013
    }
  }

最後, 我們可以利用 SimpleDateFormate 來改良上面用 Calendar 類別寫的 getDateTime() 方法 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getDateTime());
    }
  public static String getDateTime() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(new Date());
    }
  }

實在是太簡單了, 不須再去處理年月日時分秒了, 難怪取名為 "Simple" !

參考 :

# Java Calendar取得年(year)、月(month)、日(day)
# Java Gossip: 使用 Calendar
# 深入理解Java:SimpleDateFormat安全的时间格式化
# JAVA的日期時間取得 
# Java Gossip: 使用 Date、DateFormat
# Java 获得指定日期是一年中的第几天

2014年3月28日 星期五

最後的詔書

今天在維基看到這篇, 讓我想起以前看過的末代皇帝溥儀自傳, 但其中對隆裕太后著墨不多, 畢竟當年隆裕太后抱著他登基時, 他才不過三歲, 第二年隆裕太后就死了, 哪會留下多少印象呢?

宣統帝退位詔書

此乃中國最後的太后-隆裕, 於 1912 年 2 月 12 日, 以太后身分所頒布的清帝遜位詔書, 這是終結自西元前 221 年秦將王賁率軍攻入齊都臨淄, 秦始皇統一六國, 締造 2130 餘年封建帝制以來的最後一篇詔書, 具有劃時代的意義.

隆裕太后名靜芬, 係慈禧弟弟, 都統桂祥的長女, 也就是要叫慈禧姑媽. 而光緒帝載湉則是醇親王奕譞 (ㄒㄩㄢ) 之子, 母親為慈禧之妹, 故光緒要叫慈禧阿姨, 因此隆裕與光緒實為親表姊弟 (舅舅的女兒), 長光緒 3 歲. 慈禧為了有身邊人可以就近監視光緒帝, 因此一手安排了光緒與隆裕的大婚. 隆裕進宮時 20 歲, 立為皇后, 當時光緒 17 歲.

隆裕雖然貴為皇后, 為後宮之長, 但可能因為長相平庸, 不為光緒所喜, 光緒喜歡的是珍妃. 或者光緒知道她是阿姨派來監視他的, 有所猜忌也說不定. 總之, 帝后感情不睦, 故無所出. 所以當光緒暴斃於中南海瀛台之後, 慈禧遺命, 由光緒的弟弟, 第二代醇親王載灃之幼子溥儀入繼大統, 亦即當了伯父光緒, 伯母隆裕的兒子.

隆裕太后頒布退位詔書後, 因鬱鬱寡歡, 第二年 (1913) 即病逝於紫禁城西六宮, 享年 45 歲, 國府以國喪之禮為其治喪, 袁世凱下令降半旗三日致哀, 文武官員服孝 27 日.

綜觀隆裕一生, 雖感情婚姻不遂, 不得皇上寵愛, 但是在曠古大變局中, 不忍萬民塗炭, 明智果斷決定清帝退位, 促成共和, 難怪孫中山讚其為 "女中堯舜".

詳見 "隆裕太后:孫中山眼中的"女中堯舜" 非亡國罪人".

參考資料 :

# 隆裕太后
# 光緒皇帝
盤點清朝28位皇后-隆裕
滿清 隆裕太后 讓位 103年,又見 甲午!
袁世凱為何要納隆裕太后為妾?


2014年3月27日 星期四

全民中檢

整理書包資料時找到一張姐姐小學畢業前, 我到學校參加講座所發的 "CWT 全民中檢" 資料, 藏在書包中三年了 ... 都沒時間整理.

# CWT 全民中檢

昨天因為上司要我找一張舊資料, 我翻箱倒櫃順便整理我的公務櫃, 今天繼續整理. 不整理的話, 檢索時就要花很多時間, 沒效率.


2014年3月26日 星期三

Java 複習筆記 : 陣列

陣列是程式員最常用的資料結構, Java 的陣列屬於傳統的固定式陣列, 亦即陣列元素資料類型必需相同, 而且元素個數必須先宣告才能使用, 不像 Javascript 等動態語言之陣列允許異質性資料, 且長度不需先宣告即可使用. 當然, Java 陣列也不支援關聯性陣列, Java 是利用 Map 來處理此種資料型態. 以下整理了 Java 陣列的使用方法.

http://docs.oracle.com/javase/7/docs/api/

一. 陣列的宣告與賦值 :

Java 陣列的主要特性如下 :
  1. 宣告一個變數名稱即可儲存大量相同資料型態之資料
  2. 陣列元素存放在記憶體中的連續空間, 其值可以重複.
  3. 以變數名稱與索引 (0 起始), 配合迴圈可方便存取元素
  4. Java 陣列是物件型態, 其元素可以是原始資料型態, 字串, 或物件 (必須全部一致)
  5. Java 陣列之元素個數宣告之後即固定, 即陣列長度不可變更. 
  6. Java 陣列長度可由 length 屬性取得, 而同樣是物件的字串, 卻用方法 length()

陣列變數的宣告有兩種方式 :

int a[];
int[] a;

亦即中括號可以在資料型別後面, 也可以在變數名稱後面. 宣告陣列變數後, 編譯器是在程式的 stack 記憶體區中配置一個儲存陣列參考的記憶體位址, 其預設值為 null, 表示沒有指向任何陣列實體. 這時如果去讀取陣列長度屬性, 編譯時將出現 "error: variable a might not have been initialized" 錯誤訊息 (還沒有實體化) :

int a[];
//System.out.println(a.length); //無法通過編譯



接下來需用 new 指令要求編譯器配置連續的記憶體以儲存陣列實體 (即元素) :

a=new int[3];

編譯器看到 new 就會在 heap 記憶體區配置連續 3 個記憶體位址來存放陣列元素, 然後把第一個位址 (陣列頭) 填入 stack 記憶體區中, 儲存陣列變數參考的位址, 這就是陣列的參考位址. 5k4


這個 new 就是陣列實體化 (也就是建立陣列物件), 陣列實體是放在 heap 區, 而其參考是放在 stack 區. 陣列實體化後就可以讀取到物件的 length 屬性了 :

int a[];
a=new int[3];
System.out.println(a.length);  //輸出 3


上面這兩個分解動作也可以合而為一, 可以用 for 迴圈拜訪陣列元素 :

int a[]=new int[3];
for (int i=0; i<a.length; i++) {
  System.out.print(a[i]);   //輸出 000
  }

但是要注意, 存取陣列元素時不可超過索引上限, 雖然可以通過編譯, 但是會出現執行時期錯誤 :  "java.lang.ArrayIndexOutOfBoundsException".

上面配置完記憶體後, 陣列元素都已有值 0, 這是因為 int 的預設值為 0 之故. 因為存放在 heap 區的變數都會有預設值, 因此雖然陣列元素都還沒有初始化 (賦值), 但編譯器會先給予預設值, 如果元素是物件, 其預設值為 null, 如果是原始資料, 預設值如下所示 :


最後是將陣列元素初始化 (賦值), 亦即將元素值填入 heap 記憶體中 :

a[0]=100;
a[1]=101;
a[2]=103;


這上面三個分解動作 (宣告, 實體化, 初始化) 也可以一氣呵成 :

int a[]=new int[]{100,101,103};

亦即, 陣列元素是用大括號列舉. 特別注意, 這裏大括號前的那個中括號裡面不可再填入元素個數, 否則編譯會出現 "; expected", "{ not a statement" 等錯誤 :

//int a[]=new int[3]{100,101,103}; //編譯失敗 :

因為 JDK 看到 int[3] 後就準備在 heap 配置記憶體, 預期後面應該要有一個分號, 但是卻出現大括號之故. 編譯器會根據大括弧裡面的元素個數來配置記憶體數目.

或者可以乾脆省略 new int[] :

int a[]={100,101,103};
for (int i=0; i<a.length; i++) {
  System.out.print(a[i]);   //輸出 100101103
  }

也可以使用 for each 方式拜訪 :

int a[]={100,101,103};
for (int i : a) {  //i in a 之意
  System.out.print(i);   //輸出 100101103
  }

二維陣列的宣告則是用兩個中括號表示, 同樣有兩種方式 :

int a[][];
int[][] a;

同樣地, 它也是告訴編譯器, 在 stack 記憶體區配置一個存放陣列參考的記憶體 :

由於還沒有實體化, 故其值 (參考) 為 null.

二維陣列宣告中的第一個中括弧代表列, 第二個中括弧代表行, 當以 new 實體化時, 我們必須指定列數與行數, 例如 :

a=new int[3][2]; 


編譯器會在 heap 記憶體區配置 "列x行" 個記憶體以存放全部元素, 這些元素會分成 "列" 組, 每一組都是連續空間, 用來存放各列元素. 然後會在 heap 區再配置 "列" 個連續空間, 以儲存每一列第一個元素的參考位址, 最後再把這個列陣列的第一個元素參考填入陣列變數 a 中即完成全部記憶體配置. 因此二維陣列的第一維 (列陣列) 存放的其實是個一維的參考陣列, 第二維 (行陣列) 才是存放元素的陣列, 如下列範例所示 :

int[][] a=new int[3][2];
for (int i=0; i<a.length; i++) {
  System.out.println(a[i]);  
  }

輸出結果 :

[I@175d6ab
[I@160a26f
[I@1484a05

此即第一維所存放的列陣列參考. 若此時去拜訪陣列元素, 如下例所示 :

int[][] a=new int[3][2];
for (int i=0; i<a.length; i++) {
  for (int j=0; j<a[i].length; j++) {
    System.out.print(a[i][j]);
    }
  System.out.println("");
  }

輸出結果 :

00
00
00

可見由於 heap 中的變數都有預設值, 而 int 的預設值為 0, 因此全部元素值均為 0. 注意 a.length 表示陣列的列數, 而 a[i].length 則為其行數.

二維陣列的初始化可以一個元素一個元素賦值 :

int a[0][0]=100;
int a[0][1]=101;
.....

也可以用雙重大括弧比較簡潔 :

int a[][]=new int[][]{{100,101},{99,103},{78,117}};

同樣要注意, 這裡中括號裡不可填列數與行數, 或者乾脆省略 new int[][] :

int a[][]={{100,101},{99,103},{78,117}};

如下列範例所示 :

int[][] a={{100,101},{99,103},{78,117}};
for (int i=0; i<a.length; i++) {
  for (int j=0; j<a[i].length; j++) {
    System.out.print(a[i][j] + " ");
    }
  System.out.println("");
  }
輸出結果 :

100 101
99 103
78 117


二. 一維陣列的排序搜尋 :

# http://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html

Java 的 java.util.Arrays 類別提供幾個靜態方法來處理一維陣列 (不能用於二維陣列). 其中最常用的是 sort() 與 binarySearch(), 可對一維陣列進行排序與搜尋. 要使用此功能可以先匯入此類別 :

import java.util.Arrays;

然後就可以使用 Arrays 了, 使用 sort() 方法只要傳入待排序陣列即可 :

Arrays.sort(a);

也可以不先匯入, 直接使用 java.util.Arrays :

java.util.Arrays(a);

如下範例所示 :

int a[]={103,101,102};
java.util.Arrays.sort(a);
for (int i : a) {  //i in a 之意
  System.out.print(i + " ");   //輸出 101 102 103
  }

可見 sort() 方法會對陣列元素進行升階排序. 降階排序該怎麼做呢? 可以利用 sort() 另一個多載方法, 傳入一個 Comparator 物件 ;

sort(T[] a, Comparator c)

降序的 Comparator 物件可以利用 java.util.Collections 類別的靜態方法 reverseOrder() 來取得, 因此我們需要再匯入 Collections 類別或者乾脆指定整個 java.util 路徑即可 :

import java.util.*;

關於 Comparator 與 Collections 類別可參考 :

# http://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html
# http://docs.oracle.com/javase/7/docs/api/java/util/Collections.html

但是要注意, 這個降序用的 sort() 方法只能傳入物件型別的陣列, 不能用於原始型別陣列, 因此下列用法是錯的, 無法編譯成功 :

int a[]={103,101,102};  //原始型別陣列
//java.util.Arrays.sort(a, java.util.Collections.reverseOrder());  //編譯失敗

必須先將原始型別陣列利用包裹類別把原始型態陣列轉成物件陣列才行 :

Integer[] aInt=new Integer[a.length];  //宣告 a 陣列的包裹陣列
for (int i : a) { 
  aInt[i]=new Integer(a[i]);  //建立與原始陣列內容相同之包裹陣列
  }

參考 :

# Sorting Arrays

這樣就可以呼叫降序的 sort() 方法了, 如下列範例所示 :

import java.util.*;
public class test {
  public static void main(String argv[]) {
    int a[]={103,101,102};
    Integer[] aInt=new Integer[a.length];
    for (int i=0; i<a.length; i++) {
      aInt[i]=new Integer(a[i]);
      }
    Arrays.sort(aInt, Collections.reverseOrder());
    for (Integer i : aInt) { 
      System.out.print(i.intValue() + " ");   //輸出 103 102 101
      }
    }
  }

如果是字串陣列那就不需要如此麻煩了, 因為字串本身就是物件型態, 可以直接使用, 例如 :

import java.util.*;
public class test {
  public static void main(String argv[]) {
    String str[]={"one","two","three"};
    Arrays.sort(str, Collections.reverseOrder());
    for (String s : str) { 
      System.out.print(s + " ");   //輸出 two three one
      }
    }
  }

可見英文字串的排序是依照字母一個一個比較來定先後的. 排序在英文與數字不成問題, 但在中文就頭大了, 例如下列升序排列 :

String str[]={"渡邊淳二","渡邊淳三","渡邊淳一"};
Arrays.sort(str);
for (String s : str) { 
   System.out.print(s + " ");   //輸出 渡邊淳一 渡邊淳三 渡邊淳二
   }

而降序排列卻是 :

String str[]={"渡邊淳二","渡邊淳三","渡邊淳一"};
Arrays.sort(str, Collections.reverseOrder());
for (String s : str) { 
   System.out.print(s + " ");   //輸出 渡邊淳二 渡邊淳三 渡邊淳一
   }

原以為會照筆畫順序, 看起來不是, 也無法理解其排序的邏輯. 關於中文排序, 參見 :

# java 中文排序

此文實作了一個 Comparator 介面的類別來替中文做升序排序 :

public class Asc implements Comparator {
  public int compare(Object arg0, Object arg1) {     
     VLG_ItemMaster m1=(VLG_ItemMaster)arg0;          
     VLG_ItemMaster m2=(VLG_ItemMaster)arg1;     
     Collator collator=Collator.getInstance(Locale.TRADITIONAL_CHINESE);
     return collator.compare(m1.getItemDesc(),m2.getItemDesc());
     }
  }


其次來看搜尋, 也就是找看看陣列中是否有指定之元素, java.util.Arrays 的 binarySearch() 方法採用二元搜尋法, 它有兩個參數, 第一個是目標陣列, 第二個參數是要搜尋的標的, 它會傳回一個整數, 如果有找到, 會傳回該元素的索引, 否則傳回一個負數, 其值為標的在此陣列內的排序索引取負數再減 1. 為了提高搜尋效能, 呼叫 binarySearch() 之前最好先呼叫 sort() 進行排序.

static int binarySearch(array, key)

如下列範例所示 :

int a[]={105,101,103};
java.util.Arrays.sort(a);     //排序後是 {101,103,105}
System.out.print(java.util.Arrays.binarySearch(a, 101));   //輸出 0 
System.out.print(java.util.Arrays.binarySearch(a, 102));   //輸出 -2
System.out.print(java.util.Arrays.binarySearch(a, 100));   //輸出 -1
System.out.print(java.util.Arrays.binarySearch(a, 104));   //輸出 -4

上例中, 100 找不到, 它在排序後的陣列中會排在索引 0, 因此回傳 -1; 104 也找不到, 若將其排入目標陣列, 其索引將會是 3, 故傳回 -4. 所以有沒有找到可以簡單地以傳回值的正負數判斷.

如果沒有先呼叫 sort(), 如下所示, 傳回的索引將會不同 :

int a[]={105,101,103};
System.out.print(java.util.Arrays.binarySearch(a, 101));   //輸出 1 
System.out.print(java.util.Arrays.binarySearch(a, 102));   //輸出 -3
System.out.print(java.util.Arrays.binarySearch(a, 100));   //輸出 -1
System.out.print(java.util.Arrays.binarySearch(a, 104));   //輸出 -4

可見蒐尋 101 傳回其索引 1 沒錯, 但 102 傳回 -3, 搜尋 100 傳回 -1, 搜尋 104 傳回 -4 就不容易理解了. 總之, 如果需要用到其傳回值, 搜尋前請先排序; 如果只是要判斷陣列是否含有某元素, 則不需要先排序, 直接搜尋即可.

三. 可變長度陣列 ArrayList :

# http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html

Java 陣列的長度是固定的, 一旦宣告之後就不能改變. 但很多時候我們無法預期陣列可能的元素個數, 例如陣列就不適合用來儲存文字檔內的每一列字串, 因為檔案會有幾列無法預期的, 這時就要改用 java.util.ArrayList 類別, 這是 Java 集合的一種 (List 串列), 此類別實做了 List 介面, 其特性與陣列大致相同, 可視為可變長度的陣列 :
  1. 有序 (以索引存取)
  2. 元素可以重複
  3. 一個變數名稱可以存取大量同性質資料
但是有兩點不同 :
  1. ArrayList 長度可以增減
  2. ArrayList 的元素全部是物件, 即使存入原始資料, 也會 Autoboxing 為物件.
ArrayList 預設配置 10 個元素的記憶體空間, 當空間不足時會自動擴充容量, 也可以隨時呼叫 trimToSize() 方法來去除多餘的容量, 使其剛好符合元素數量, 以釋放多餘之記憶體. 為了方便, 使用 ArrayList 最好先匯入 java.util 類別庫 :

import java.util.*; 

ArrayList 類別支援泛型設計, 因此宣告一個 ArrayList 物件時要指定其型別 :

ArrayList<String> list=new ArrayList<String>();

例如下列為一個元素均為字串的 ArrayList :

ArrayList<String> list=new ArrayList<String>();

其常用方法如下表所示 :

 方法說明
 add(E element)  將元素加到串列尾端
 add(int index, E element) 將元素加到指定索引位置, 該位置後面的元素全部往後移
 get(int index) 傳回指定索引位置之元素值
 set(int index, E element) 以新值取代指定索引位置之元素
 remove(int index) 移除指定索引位置之元素
 remove(Object o) 移除指定元素
 size() 傳回 ArrayList 元素個數 (與容量無關)
 indexOf(Object o) 從前端搜尋串列中是否有指定元素, 有傳回其索引, 否則傳回 -1
 lastIndexOf(Object o) 從尾端搜尋串列中是否有指定元素, 有傳回其索引, 否則傳回 -1
 contains(Object o) 搜尋串列是否有指定之元素, 有傳回 true, 否則 false
 isEmpty() 串列無元素傳回 true, 否則 false
 toArray() 將全部元素以陣列傳回
 trimToSize() 去除串列預留空間, 使其剛好裝得下全部元素


拜訪 ArrayList 的元素跟拜訪陣列一樣, 用 for 迴圈或 for each 迴圈 :

for (String e : list) {
  System.out.println(e);
  }

如下面範例所示 :

import java.util.*;
public class test {
  public static void main(String[] args) {
    ArrayList list=new ArrayList();
    System.out.println(list.size());    //輸出 0 (空的, 尚無元素)
    System.out.println(list.isEmpty()); //輸出 true (空的)
    list.add("楊過");                   //加入第一個元素
    System.out.println(list.size());    //輸出 1
    //list.add(2,"小龍女");             //編譯錯誤, 超出索引上限
    list.add(1,"小龍女");               //加入第二個元素
    System.out.println(list.size());    //輸出 2
    list.add(1,"郭襄");                 //加入第三個元素於索引 1
    System.out.println(list.get(1));    //輸出 郭襄
    System.out.println(list.get(2));    //輸出 小龍女
    list.set(1,"完顏萍");               //更改索引 1 之值
    System.out.println(list.get(1));    //輸出 完顏萍
    System.out.println(list.indexOf("小龍女"));   //輸出 2
    System.out.println(list.lastIndexOf("楊過")); //輸出 0
    System.out.println(list.contains("完顏萍"));  //輸出 true
    System.out.println(list.contains("黃蓉"));    //輸出 false
    List sList=list.subList(1,3);         //傳回子串列
    String[] a=sList.toArray(new String[sList.size()]); //串列轉字串
    for (String e : a) {System.out.print(e + " ");}    //輸出 完顏萍 小龍女
    System.out.println("");
    list.remove("完顏萍");                             //刪除元素
    for (String e : list) {System.out.print(e + " ");} //輸出 楊過 小龍女
    System.out.println("");
    list.remove(1);                                    //刪除索引 1 之元素
    for (String e : list) {System.out.print(e + " ");} //輸出 小龍女
    }
  }


# String array to arraylist

下面這個 readLines() 靜態方法用來讀取文字檔, 然後拆成列字串陣列傳回 :

  public static String[] readLines(String file) {
    ArrayList<String> list=new ArrayList<String>();
    try {
      BufferedReader br=new BufferedReader(new FileReader(file));
      String line=null;
      while ((line=br.readLine()) != null) {list.add(line);}
      br.close();
      }
    catch (IOException e) {System.out.println(e);}
    list.trimToSize();
    return list.toArray(new String[list.size()]);
    }


參考資料 :

stack vs heap:執行時期儲存兩大要角

Facebook 帳號被盜

昨晚電腦關機前, 想說很久沒上 facebook 了, 結果登入時發現竟然顯示我的密碼不對, 而且說我剛改過資料, 名字被改成 "郭遠宏", 驚覺帳號可能被盜, 於是搜尋解決之道, 在網路上找了幾個建議方法如下 :

# Facebook 取回被盜的帳號

但是我連線到檢舉網頁 :

https://www.facebook.com/hacked

照程序走, 跟上面說的好像不太一樣 :









到這一步竟然說 email 不符, 剛剛第二步不是顯示我正確的 facebook 帳號 (gmail) 嗎? 怎麼這回卻說 email 不符呢? 我猜可能跟第 6 張圖顯示的 s*******@gmail.com 有關, 駭客可能改了認證用的 Gmail 帳號, 但保留了顯示用的帳號, 

按返回又回到第二張圖了, 無言, facebook 給我的感覺就是網頁設計很清爽, 但操作邏輯很混亂. 後來找到申訴 e-mail : 

https://www.facebook.com/help/contact/328029570569950

還要上傳身分證給他們, 然後耐心等待審核.


2014年3月25日 星期二

Java 複習筆記 : 變數與記憶體

一個 Java 程式中宣告的變數有四種, 配置於記憶體中的位置與時機不同, 存活時間也不同. 一般作業系統將程式載入記憶體準備執行時, 通常為此程式配置三種不同的儲存區域, 即程式碼區, Global 區 (儲存靜態變數與函式), 與動態區 (儲存程式碼執行時的動態資料). Java 的記憶體配置區域如下圖所示 :

 

靜態變數與方法在編譯完成後即存在 global 區 (靜態區), stack 區存放一般方法, 以及執行時期方法所用到之參數與區域變數; 而 heap 區則存放執行時期的實體物件與實體變數.

Java 的變數依照其存活時間長短分成下列四種 :

1. 類別變數 : 

又稱為靜態變數, 因為它必須在類別成員區宣告為 static (也只能在類別成員區有 static), 例如 :

static int count=0;

其特性如下 :
  1. 存活時間最長, 編譯時即配置記憶體存放於 global 區 (即靜態區, 靜態方法也在此區), 程式執行期間均可存取. 
  2. 直接用類別名稱存取, 即 : 類別名稱.變數名稱, 且為該類別全部實體所共用 (即共同變數).
  3. 可以加存取修飾子 : public, protected, private.
  4. 有預設值, 如果沒有初始化, 編譯器也會給予預設值如下 :



2. 實體變數 :

又稱為物件變數, 或物件的屬性, 係類別成員中沒有 static宣告之變數. 其特性如下 :

  1. 存活時間第二長, 於物件建立時產生, 存放於 heap 區, 只要物件未消滅皆可存取.
  2. 必須以物件實體名稱存取, 即 : 物件名稱.變數名稱, 同類別之不同實體有各自屬性 (不共用).
  3. 可以加存取修飾子 : public, protected, private.
  4. 有預設值, 如果沒有初始化, 編譯器也會給予預設值, 與類別變數相同.
3. 方法變數 :

此為方法中的小括弧 (參數) 以及大括弧 (內容) 中所用到的變數, 是兩種區域變數之一 (另一種為區塊變數). 其特性如下 :
  1. 存活期間最短, 僅在該方法被呼叫時存在, 配置於 statck 區 (LIFO), 方法執行完畢即消滅, 還給作業系統. 
  2. 只能在方法內部直接存取, 故不可以加任何存取修飾子 (即外部無法存取).
  3. 無預設值, 存取前必須先初始化 (賦值), 否則會編譯失敗. 方法之參數因為是外部傳入 Literal (相當於初始化), 因此可以直接使用.

4. 區塊變數 :

即 if, switch, for, while, do-while 等流程控制語法大括弧內所定義之變數, 是兩種區域變數之一, 其特性如下 :
  1. 存活期間最短, 僅在進入該區塊執行時才存在, 配置於 statck 區 (FILO), 離開該區塊時即消滅, 還給作業系統.
  2. 只能在區塊內部存取, 故不可以加任何存取修飾子 (區塊外部無法存取).
  3. 無預設值, 存取前必須先初始化 (賦值), 否則會編譯失敗. 

總之, 區域變數 (方法變數, 區塊變數) 是放在 stack 區, 執行時期動態生滅; 靜態變數放在 global 區, 編譯成功時即存在, 程式結束時消滅; 實體變數放在 heap 區, 隨實體存亡而生滅.

2014年3月23日 星期日

合約不能亂簽

幾年前表妹想學電腦, 到某知名電腦補習班用信用卡繳了三萬多塊後, 才上兩堂課就因為排班時間不合請假, 後來因為時間很難喬, 想乾脆退費不上了, 結果補習班說表妹翹課只能退部分學費 (好像 3 成), 她說有電話請假啊, 但補習班說沒有接到, 我陪她去跟店經理談判退費事宜, 本來想說豈有此理, 才上兩堂課竟然只能拿回一點錢, 要幫她討回公道, 結果是 ... 乖乖吞下去.

為什麼? 明明不合理, 為什麼要吞下去? 原因出在我們自己. 店經理還沒聽完我滔滔不絕, 一副得理不饒人據理力爭的說詞, 不疾不徐拿出當初表妹來上課時所簽的同意書, 哈, 裡面就白紙黑字寫得明明白白, 開課後幾日內要退課只能退還學費幾分之幾, 當初在櫃檯刷信用卡簽約時, 小姐就有說明, 我問表妹有嗎? 表妹說沒有, 只是說這合約書請先看一下再簽名, 我先幫妳刷卡. 呵呵, 問題就在這裡, 店經理堅持櫃檯小姐不可能沒講, 表妹堅持沒聽到小姐有說明, 這下變成羅生門, 當然結局就是 ... 吞下去, 因為表妹在上面簽了名啊.

這社會的遊戲就是這樣, 設計遊戲的人早就想到各種可能的問題, 而在遊戲規則上設好了種種保護措施保護自己, 我們沒有仔細看合約就簽名, 表示我們放棄保護自己的權利, 等到有糾紛時, 一切按合約走, 能不吞下去? 怪只能怪我們自己欠缺法律風險意識. 所以, 合約不能亂簽.

現在鬧得沸沸揚揚的服貿也是這樣, 偷偷摸摸的簽, 還不想讓你仔細看合約內容, 被抓包了又擺出一副教父訓斥我們甚麼都不懂, 還在那邊瞎反的高傲態度. 那個管交通出身的經濟部長更是好笑, 說這服貿如果不過的話, 韓國最高興. 南韓在高興啥? 在馬幫惡搞幾年之下, 台灣經濟江河日下, 南韓早就已經不把台灣視為對手了, 你說南韓高興啥?

那個滿洲正黃旗出身的新秘書長更離譜, 一下飛機就說, 美國參眾兩院絕對不會容許佔領國會這樣的暴力事件發生, 言下之意, 是要 CIA, FBI, 還是國土安全部出手嗎? 30 秒通過議案存查屬於什麼暴力? 馬式暴力? 離開美國前應該拜會參眾兩院議長, 請教一下如果民主黨 30 秒通過議案會怎樣 ?

這些怪現象說明 : 這裡面一定有鬼.

Java 複習筆記 : 資料型態與語法

此處整理 Java 資料型態與語法方面一些較容易搞混與忽略的觀念.

http://docs.oracle.com/javase/7/docs/api/

一. include 與 import 的差異 :

Java 程式頭常會用 import 指令匯入所需之類別或類別庫 (用 .*), 這似乎與 C/C++ 的 imclude 很像, 功能上都是匯入 library, 但其實兩者不同. C/C++ 的 include 會在編譯時把指定的函式全部匯入程式裡, 但 Java 的 import 並非將類別匯入程式中, 只是告訴編譯器程式中會用到的類別的路徑而已. 當使用 * 匯入函式庫時, 程式中用不到的類別並不會造成程式膨脹或執行效能下降, 因為 JVM 是在執行時期才會把實際使用到的類別內容匯入.

要注意的是, 位於相同目錄下的其他類別不需用 import 指令匯入, 直接使用即可, 因為編譯器會自動在目前目錄底下搜尋使用到的其他類別, 搜尋不到時才到 import 指令區去找該類別的套件位置.

二.  變數與資料型態 :

Java 是物件導向語言, 主要的資料型態當然是物件, 字串與陣列都是物件, 但為了增進執行效率也提供了 8 種原始資料型態, 這使得 Java 不能像 Small Talk 語言那樣稱為 100% 的物件導向語言. Java 的資料類型如下圖所示 :




Java 的資料型別可以說只有兩類, 一個是原始資料型態, 一是參考型態. 原始型態又可分為數值 與布林兩大類 (字元可以視為數值型態). 而參考類型則包括字串, 陣列, 與物件.

這裡首先要特別注意的是字元, 整數, 與實數的常數表示法 :

1. 字元與字串常數表示法 :

字串常數要用雙引號括起來, 字元常值要用單引號 :

char ch='K';             //以字面值表示字元
char ch='\101';         //以 8 進位表示字元
char ch='\u0041';     //以 unicode 表示字元
String str="string";   //字串要用雙引號

字元除了用字面值 (Literal)  'A' 表示以外, 也可以使用 8 進位或 16 進位 unicode 字元編碼表示, 因為 Java 的字元本身就是用 Unicode 儲存的. 用 16 位元 unicode 字元編碼表示時, 其值為 '\u0000' 至 '\uffff' (0 至 65535), 只要將字元宣告為 int 類型, 就可顯示其整數編碼值了 :

char ch1='A';
char ch2='台';
char ch3='\u0041';
char ch4='\101';
int i1=ch1;  //自動轉型為 int
int i2=ch2;  //自動轉型為 int
int i3=ch3;  //自動轉型為 int
int i4=ch4;  //自動轉型為 int
System.out.println(ch1);   //輸出 A
System.out.println(ch2);   //輸出 台
System.out.println(ch3);   //輸出 A
System.out.println(ch4);   //輸出 A
System.out.println(i1);      //輸出 65
System.out.println(i2);      //輸出 21488
System.out.println(i3);      //輸出 65
System.out.println(i4);      //輸出 65

2. 整數常數表示法 :

Java 的整數有 byte, short, int, 與 long 四種, 整數常數預設型態是 int, 如果數值較大, 可以在最後面加上 l 或 L 表示用長整數儲存, 例如 123L.

整數常數原本只有三種表示法, 即 8, 10, 16 進位表示法, Java 7 新增了 2 進位表示法. 除了十進位表示法以外, 其他三種表示法都需要前置字元, 八進位的前置字元為 0, 十六進位為 0x 或 0X, 二進位則為 0b 或 0B :

int i1=15;
int i2=017;
int i3=0xf;
int i4=0b1111;         //二進制表示法 : Java 7 才有支援
int i5=9_000_210;   //以底線代表千位數分段 (Java 7 才有支援)
System.out.println(i1);   //輸出 15
System.out.println(i2);   //輸出 15
System.out.println(i3);   //輸出 15
System.out.println(i4);   //輸出 15
System.out.println(i5);   //輸出 9000210

注意, 這裡的 ox/0X 與 0b/0B 是 Java 兩個不分大小寫的地方之一. 其次, 不論是 2/8/10/16 哪一種進位表示法, 整數常數預設型態是 int (例如上面的例子最後面都沒有 l 或 L, 所以預設都以 int 存放), 但也可以在數值最後面加上 l 或 L 表示要用 long 型態儲存 (8 個 bytes), 例如 :

long i1=15L;
long i2=15l;

這是 Java 兩個不分大小寫的地方之二. 這樣就會出現一個問題, 當為一個變數賦值時, 就須考慮變數與常數值長度的匹配, 例如 :

int i1=15L;

常數 15 是用 long 儲存, 但變數 i1 卻宣告為 int, 這在編譯時就會出現 "error: possible loss of precision" 的錯誤, 表示這樣的賦值可能會造成數值失去精確度. 這必須使用強制轉型才能通過編譯 :

int i1=(int)15L;

強制轉型是告訴編譯器, 我們接受可能之精確度損失, 這樣就能通過編譯了.

另外, 在 Java 7 以後開始支援商用的千位分段符號, 但用底線取代逗號.

3. 實數常值表示法 :

Java 的實數有兩個 : float 與 double, 實數常數有浮點數與科學表示法兩種表示法, 在數值末尾可以加 f/F 或 d/D 指定儲存型態, 若未指定, 則預設型態為 double :

float f1=123.45f;
float f2=1.2345e4F;
double d1=123.45;  //預設為 double
double d2=123.45d;
double d3=1.2345e4D;

上面的 d1 值末尾未指定型態, 故預設為 double, 變數 d1 也須宣告為 double, 若上例中的 d1 變數宣告為 float, 則編譯時將出現 "loss of precision" 錯誤, 必須宣告為 double, 或用 float 強制轉型才能通過編譯.

其次 Java 的實數還有位數限制, 須注意這會影響數值精確度, 小數位數部分, float 最多是 7 位數, double 最多 17 位數, 位數超過時會被截掉,  float 在剪掉尾巴時會四捨五入, 但 double 則直接捨去, 如下所示 :

public class test {
  public static void main(String[] args) {  
    float f1=0.123456789F;
    float f2=0.1234567890e2F;
    float f3=123456789F;
    double d1=0.1234567890123456D;
    double d2=0.1234567890123456789e2D;
    double d3=1234567890123456789D;
    System.out.println(f1);    //輸出 0.12345679    (8 位數不含 0)
    System.out.println(f2);    //輸出 12.345679      (8 位數)
    System.out.println(f3);    //輸出 1.23456792E8 (9 位數)
    System.out.println(d1);   //輸出 0.1234567890123456  (17 位數不含 0)
    System.out.println(d2);   //輸出 12.345678901234567  (17 位數)
    System.out.println(d3);   //輸出 1.23456789012345677E18 (18 位數)
    }
  }

另外, 實數都會以科學表示法儲存, 因此上例中 f1 會先轉成 1.23456789E-1, 因為 float 只能儲存 7 位小數, 所以會四捨五入為 1.2345679E-1, 最後顯示為 0.12345679. 同樣地, f2 先轉成 1.234567890E1, 小數部分四捨五入成 7 位數 1.2345679E1, 最後轉回 12.345679 :

f2 : 0.1234567890E2 -> 1.234567890E1 -> 1.2345679E-1 -> 12.345679
f3 : 123456789 -> 1.23456789E8 -> 1.2345679E8 -> 1.23456792E8

f3 會先轉成 1.23456789E8, 但因為 float 只能儲存 7 位小數, 因此第 8 位的 9 會四捨五入變成 1.2345679E8, 但因為小數第 8 位會有誤差, 最後得到之值為 1.23456792E8. 而變數 d3 會先轉成 1.234567890123456789E18, 因為 double 只能存 17 位小數, 所以會直接刪掉最後面的 9 變成 1.23456789012345678E18, 同樣因為第 18 位小數的誤差, 最後得到 1.23456789012345677E18.

關於浮點數精確度問題, 參考 : java中的float和double的精度问题

4. 成員變數的預設值 :

類別的成員若為原始型態變數, 即使宣告時未初始化, JDK 編譯器仍會自動賦予預設值如下, 故可通過編譯 :



public class test {
  static byte by;
  static short s;
  static int i;
  static long l;
  static float f;
  static double d;
  static char c;
  static boolean bo;
  public static void main(String[] args) {
    System.out.println(by);  //輸出 0
    System.out.println(s);    //輸出 0
    System.out.println(i);     //輸出 0
    System.out.println(l);     //輸出 0
    System.out.println(f);     //輸出 0.0
    System.out.println(d);    //輸出 0.0
    System.out.println(c);    //輸出
    System.out.println(bo);  //輸出 false
    }
  }

但區域變數就不會自動給予預設值了, 方法中的區域變數一定要初始化才能使用, 否則無法通過編譯, 出現 "variable xxx might not have been initialized" 錯誤.

至於 String 與陣列均屬於物件, 若類別的成員變數為物件, 宣告後未初始化, 其預設值為 null.

5. 原始資料類型的包裹類別 :  

雖然原始資料型態運算較快速, 但只能做單純的運算, 對於較複雜之計算必須呼叫方法, 而只有類別才有方法, 因此 Java 在 java.lang 類別庫中為 8 種基本類型定義了包裹類別 (wrapper class), 可以將原始型態包裝成類別 :


由於八個原始型態中有六個為數值型態, 因此包裹類別繼承了 java.lang.Number 為父類別. 呼叫其建構子即可建立包裹物件, 可傳入對應之原始資料型態數值字串 :

Byte b=new Byte((byte)127);
Byte b=new Byte("127");

注意, 因為整數常值預設為 int 型態, 因此必須強制轉型為 byte, 否則編譯時會出現 "no suitable constructor found for Byte(int)" 錯誤. 其他包裹類別也是如此. 包裹類別提供了幾個有用的方法.

首先是 xxxValue() 方法, 此方法可將包裹物件的值轉成指定之數值類原始型態值傳回, 這個方法不支援 char 與 boolean, 只支援下列六種原始數值型態 : byte, short, int, long, float, 與 double :

Double -> byte, short, int, long, float
Float ->  byte, short, int, long, double
Long -> byte, short, int, float, double
Integer -> byte, short, long, float, double
Short -> byte, int, long, float, double

例如 :

Byte b=new Byte((byte)127);             //必須強制轉型
Short s=new Short("32767");
Integer i=new Integer(12345);             //不須強制轉型
Float f=new Float(1234.56789);
Double d=new Double("23.59123e10");
System.out.println(b.doubleValue());    //輸出 127.0
System.out.println(s.floatValue());        //輸出 32767.0
System.out.println(f.intValue());            //輸出 1234 (有誤差)
System.out.println(d.floatValue());        //輸出 2.35912298e11 (有誤差)

注意傳入原始型態常數時可能需要強制轉型, 而且浮點數轉型時可能會有誤差出現.

第二個常用方法是 parseXxx() 方法, 此方法會剖析傳入之字串, 傳回指定之原始資料型態值, 支援除了 char 以外的 7 種原始資料型態 :

byte parseByte(String str)
short parseShort(String str)
int parseInt(String str)
long parseLong(String str)
boolean parseBoolean(String str)
float parseFloat(String str)
double parseDouble(String str)

例如 :

byte b=Byte.parseByte("127");
int i=Integer.parseInt("123");
float f=Float.parseFloat("123.456789");
boolean b1=Boolean.parseBoolean("true");
boolean b2=Boolean.parseBoolean("True");
boolean b3=Boolean.parseBoolean("OK");
System.out.println(b);     //輸出 127
System.out.println(i);      //輸出 123
System.out.println(f);      //輸出 123.456789
System.out.println(b1);   //輸出 true
System.out.println(b2);   //輸出 true
System.out.println(b3);   //輸出 false

注意, 數值字串之值不可超過值域, 例如上例中的 byte 若傳入 "128", 雖可通過編譯, 但執行時會出現 "java.lang.NumberFormatException: Value out of range." 錯誤.

第三個常用方法是 equals() 與 compareTo(), 這跟 String 類別一樣, 用來比較兩個包裹物件之值. 兩者的差別只是傳回值不同, equals() 傳回 true/false, 而 compareTo() 則傳回數值, 相等傳回 0, 本物件較大傳回正數, 較小傳回負數, 例如 :

Integer i1=new Integer(127);
Integer i2=new Integer(126);
System.out.println(i1.equals(i2));              //輸出 false
System.out.println(i1.compareTo(i2));      //輸出 1

另外, Float 與 Double 提供了 isNaN() 與 isInfinite() 用來判斷是否為數值, 以及是否為無限大 (即除以 0 情況), 此為類別方法, 可以直接呼叫, 傳入計算式 :

System.out.println(Double.isInfinite(1.0/0.0));     //輸出 true (除以 0 為無限大)
System.out.println(Double.isNaN(1.0/0.0));       //輸出 false (無限大仍為數值)
System.out.println(Double.isNaN(0.0/0.0));       //輸出 true (0/0 為非數值)

也可以用包裹物件方法呼叫 :

Double d=new Double(1.0/0.0);
System.out.println(d.isInfinite());    //輸出 true
System.out.println(d.isNaN());       //輸出 false

但是 Float 只能使用物件方法呼叫, 不能呼叫類別方法 (雖然 API 中說可以) :

Float f=new Float(1.0/0.0);
System.out.println(f.isInfinite());     //輸出 true
System.out.println(f.isNaN());       //輸出 false

下列呼叫無法編譯成功, 出現 "no suitable method found for isInfinite(double)" 錯誤訊息 :

//System.out.println(Float.isInfinite(1.0/0.0));  
//System.out.println(Float.isNaN(1.0/0.0));    

除了 Character 外, 其他 7 個包裹類別都定義了兩個屬性 MIN_VALUE 與 MAX_VALUE 是用來紀錄各資料類型的值域 :

System.out.println(java.lang.Byte.MIN_VALUE);      //輸出 -128
System.out.println(java.lang.Byte.MAX_VALUE);     //輸出 127
System.out.println(java.lang.Short.MIN_VALUE);     //輸出 -32768
System.out.println(java.lang.Short.MAX_VALUE);    //輸出 32767
System.out.println(java.lang.Integer.MIN_VALUE);   //輸出 -2147483648
System.out.println(java.lang.Integer.MAX_VALUE);  //輸出 2147483647
System.out.println(java.lang.Long.MIN_VALUE);      //輸出 -9223372036854775808
System.out.println(java.lang.Long.MAX_VALUE);     //輸出 922337203685477580
System.out.println((int)java.lang.Character.MIN_VALUE);    //輸出 0
System.out.println((int)java.lang.Character.MAX_VALUE);    //輸出 65535
System.out.println(java.lang.Float.MIN_VALUE);      //輸出 1.4E-45
System.out.println(java.lang.Float.MAX_VALUE);     //輸出 3.4028235E38
System.out.println(java.lang.Double.MIN_VALUE);   //輸出 4.9E-324
System.out.println(java.lang.Double.MAX_VALUE);  //輸出 1.7976931348623157E308

注意, 因為字元是以 unicode 儲存, 因此要強制轉型為 int 才會轉成字元編碼, 否則會顯示空字串與問號.

6. 原始資料型態的轉型問題 :

當使用 "=" 運算子為變數賦值時, 等號兩邊的資料型態必須相同, 亦即, 等號左邊變數宣告的型態, 必須與等號右邊常數的型態相同, 否則可能出現 "type imcompatible" 或 "loss of precision" 的錯誤. 例如 :

byte b=true;

就會出現如下錯誤訊息 :

test.java:3: error: incompatible types
byte b=true;
        ^
  required: byte
  found:    boolean
1 error

因為變數 b 是 byte 整數, 而 true 是布林值, 兩者不匹配. 再者, 例如 :

int i=20.1;

會出現如下編譯錯誤 :

test.java:3: error: possible loss of precision
int i=20.1;
      ^
  required: int
  found:    double
1 error

因為變數 i 是整數, 而 20.1 預設是 double, 所以會失去精確度. 精確性錯誤可以透過強制轉型解決, 這是告訴編譯器, 我們知道並容許失去精確性, 這樣就能編譯成功了 :

int i=(int)20.1;  //將 double 強制轉型為 int

但是型態不匹配問題卻不是強制轉型能解決的 (無解), 因為那是無法轉換的, 例如 :

byte b=(byte) true;  //錯誤的強制轉換

這會出現下列錯誤訊息 :

test.java:3: error: inconvertible types
byte b=(byte)true;
             ^
  required: byte
  found:    boolean
1 error

可見強制轉型也是有限制的, boolean 型態無法跟任何數值型態互轉, 只有數值 (整數, 實數, 字元) 型態之間才能互轉, 而且只有在精確度大變小 (narrowing) 時才需要強制轉型, 而精確度小變大 (widening) 時不需要強制轉型, 編譯器會進行自動轉型 (auto-casting).下列是大變小必須強制轉型的例子 :

byte b=(byte)65;
short s=(short)123;
int i=(int)123L;
long l=(long)1.23F;
float f=(float)1.23D;
char c1=(char)65L;
char c2=(char)s;
char c3=(char)b;

byte b1=(byte)c3;
System.out.println(b);   //輸出 65
System.out.println(s);   //輸出 123
System.out.println(i);    //輸出 123
System.out.println(l);    //輸出 1
System.out.println(f);    //輸出 1.23
System.out.println(c1);  //輸出 A
System.out.println(c2);  //輸出 {
System.out.println(c3);  //輸出 A
System.out.println(b1);  //輸出 65

注意, 上例中的 float 轉 long, 雖然 long 是 8 bytes, 而 float 僅 4 bytes, 但是 float 精確度比 long 大, 因此一定要強制轉換才能通過編譯. 其次, char 佔 2 bytes (與 short 一樣), 雖然比 byte 大, 但是 char 沒有負數, 因此 short 與 byte 轉成 char 也是必須強制轉換, 事實上, 所有數值類型轉成 char 都必須強制轉換. 但是 char 常數轉成其他數值卻都會自動轉型 (因為 char 沒有負數) :

char c='A';
byte b='A';   //用 b=c 不行
short s='A';  //用 b=c 不行
int i=c;
long l=c;
float f=c;
double d=c
System.out.println(b);  //輸出 65
System.out.println(s);  //輸出 65
System.out.println(c);  //輸出 A
System.out.println(i);   //輸出 65
System.out.println(l);   //輸出 65
System.out.println(f);   //輸出 65.0
System.out.println(d);  //輸出 65.0

可見常數字元皆會自動轉型為其他任何數值型態, 但轉成 byte 與 short 時不可使用字元變數, 必須強制轉換, 用字元常數才會自動轉換. 除了轉成 byte 與 short 外, char 轉成其他類型不論常數或變數均可.

以下是會自動轉型的兩種情況 :
  1. byte -> short -> int -> long -> float -> double
  2. int -> byte, short, char (僅在值域內時會自動轉型)
第一項的自動轉型範例 :

byte b=127;  //int 轉 byte
short s=b;     //byte 轉 short
int i=s;          //short 轉 int
long l=i;        //int 轉 long
float f=l;       //long 轉 float
double d=f;  //float 轉 double
System.out.println(b);   //輸出 127
System.out.println(s);   //輸出 127
System.out.println(i);    //輸出 127
System.out.println(l);    //輸出 127
System.out.println(f);    //輸出 127.0
System.out.println(d);   //輸出 127.0

上面第二項 int 轉成 byte, short, char 是精確度由大變小, 照理是不會自動轉型, 但因為 int 是整數常數之預設型態, 因此只要常數是在值域之內, 會自動轉型, 不需強制轉型, 例如 :

byte b1=127;                //值域內自動轉型
byte b2=(byte)128;        //值域外需強制轉型
short s1=32767;            //值域內自動轉型
short s2=(short)32768;  //值域外需強制轉型
System.out.println(b1);   //輸出 127
System.out.println(b2);   //輸出 -128
System.out.println(s1);   //輸出 32767
System.out.println(s2);   //輸出 -32768

但是實數就不是這樣了, 都必須強制轉型. 實數常數預設型態為 double, 即使其值在值域之內, 仍然不會自動轉型 :

float f1=(float)1234.5678;        //必須強制轉型
float f2=(float)1.2345678e3;    //必須強制轉型
System.out.println(f1);              //輸出 1234.5678
System.out.println(f2);              //輸出 1234.5678

7. 宣告適當資料型態 :

宣告原始型態變數時要選擇適當資料型態, 否則會浪費記憶體空間, 甚至影響計算結果, 導致不正確的輸出, 例如數值運算有精確度問題 :

int i=1/2;
float f=10/3;
double d=10.0/3;
System.out.println(i);    //輸出 0
System.out.println(f);    //輸出 3.0
System.out.println(d);   //輸出 3.3333333333333335

可見兩個整數相除, 結果也是只取整數, 所以 1/2 得到 0, 而不是 0.5.


2014年3月22日 星期六

Java 複習筆記 : 檔案處理

資料處理最常用的技巧就是讀取檔案與輸出檔案, 例如處理過程要記錄在 log 檔. 當然最方便是紀錄在資料庫中, 很容易搜尋, 不過像 ACCESS 2003 有 2G 的限制, 如果沒有特別的搜尋需求, 其實以檔案輸出就可以了.

Java 對於檔案是以 IO 串流方式處理, 所以必須匯入 java.io.* 類別庫 :

import java.io.*;

IO 類別庫實作了許多檔案與印表機輸入與輸出之類別, 可以處理文字檔與二進位檔 (以 byte 為單位). 其次, 所有 IO 操作指令都可能拋出例外, 因此必須放在 try catch 區塊中 :

try { //IO }
catch (IOException e) {System.out.println(e);}

一. 讀取文字檔 : 

文字檔部份主要用到 FileReader 與 FileWriter 兩個類別, 如果要指定字元集或檔案寫入方式 (append/replace), 就會使用 FileInputStream 與 FileOutputStream 類別, 這些類別都是以字元 char 為處理單位. 但是對於純文字資料處理而言, 通常會採用緩衝區來處理檔案讀寫, 以減少磁碟存取頻率加快處理速度, 這就要用到 BufferedReader 與 BufferedWriter 類別.

java.lang.Object
        |-------- java.io.Reader ---------------------- java,io.BufferedReader
        |-------- java.io.InputStreamReader ------- java.io.FileReader
        |-------- java.io.Writer ----------------------- java,io.BufferedWriter
        |-------- java.io.InputStreamWriter -------- java.io.FileWriter

如果不需指定字元集, 只要將 FileReader/FileWrtiter 物件傳給 BufferedReader/BufferedWriter 做緩衝處理, 直接使用 BufferedReader 與 BufferedWriter 來讀寫檔案即可 :

FileReader fr=new FileReader("C:\\test.txt");
BufferedReader br=new BufferedReader(fr);

然後再用 BufferedReader 的 readLine() 方法來讀取檔案中的每一行字串, 當讀完最後一行時, 再讀取就會傳回 null, 因此可用迴圈來讀取全部檔案內容 :

String line;
while ((line=br.readLine()) != null) {
  System.out.print(line);
  }

我們先準備一個文字檔 test.txt 內容如下 :


1.裘千尺
2.小龍女
3.楊過
4.公孫綠萼
5.郭靖

然後用 BufferedReader 來讀取, 如下範例 1 所示 :

測試範例 1 :  http://mybidrobot.allalla.com/javatest/file1.zip  [ 看原始碼] 

import java.io.*;
public class file1 {
  public static void main(String[] args) {
    try {
      FileReader fr=new FileReader("test.txt");
      BufferedReader br=new BufferedReader(fr);
      String line;
      while ((line=br.readLine()) != null) {
        System.out.print(line);
        }
      }
    catch (IOException e) {System.out.println(e);}
    }
  }

結果顯示 :

H:\Java\JT>java file1
1.裘千尺2.小龍女3.楊過4.公孫綠萼5.郭靖

注意, readLine() 會刪除每行末尾的跳行符號 (即 "\r\n") 後再傳回, 因此檔案中原本的五列資料用 print 輸出時全部擠在一列, 如果用 println 就會輸出五列了.

也可以用 BufferedReader 繼承自 Reader 類別的 read() 方法來讀取, 但此方法是一次讀取一個字元, 讀到檔尾時會傳回 -1, 但其傳回值型態是 int, 亦即字元的整數編碼, 因此顯示時必須用 (char) 來強制轉型才會顯示原始檔案內容, 如下列範例 2 所示 :

測試範例 2 :  http://mybidrobot.allalla.com/javatest/file2.zip  [ 看原始碼] 

import java.io.*;
public class file2 {
  public static void main(String[] args) {
    try {
      FileReader fr=new FileReader("test.txt");
      BufferedReader br=new BufferedReader(fr);
      int ch;
      while ((ch=br.read()) != -1) {
        System.out.print((char)ch);
        }
      }
    catch (IOException e) {System.out.println(e);}
    }
  }

這樣就不會像 readLine() 那樣會自動刪除列尾的跳行字元了, 即使用 print 也會輸出完整內容 :

H:\Java\JT>java file2
1.裘千尺
2.小龍女
3.楊過
4.公孫綠萼
5.郭靖

如果沒有強制轉型為 char, 就會輸出編碼值 :

H:\Java\JT>java file2
4946350322131523610131050462356740845228991310514626954369421310524620844234033216033852131053463710138742




FileWriter fw=New FileWriter("C:\\test.txt");
BufferedWriter br=new BufferedWriter(fw);

~未完待續~



2014年3月20日 星期四

服貿之我見

立法院破天荒竟然停擺, 而始作傭者, Our President Nine Percent (九趴總統) 說, 國會事務, 國會自理. 果真是哈佛牌不沾鍋啊.

服貿最大的問題在哪裡? 在於黑箱作業, 違反民主核心價值. 回到政治學第一章, 政府的最重要職責是啥? 合理分配利益而已, 就這麼簡單. 在國際村的今天, 不開放市場行不通, 這大家都了解, 你 President Nine 不用特別強調.

問題一, 開放後哪些產業會大發, 那些產業會吃虧, 政府作何補救?

問題二, 開放哪些項目, 衝擊為何, 立法院不能檢視討論嗎?

問題三, 開放的項目真的不影響國家安全?

這些都是影響人民巨大利益的東西, 竟然立下軍令狀, 務必要過! 連逐條檢視的機會都沒有, 然後讓權益受損的產業乖乖吞下去? 當年台灣是怎樣從農業邁向工業的? 政府是用股票來補償地主的權益損失啊! 如今都成了鉅富. 這就是合理的利益分配啊. 現在吞下去就是唯一選項嗎?

今天新聞說, 經濟部表示, 服貿若過, 薪水上看 52K, 你看, 這就是標準的騙子業務員典範, 只說那些虛無飄渺, 都不知道錢從哪裡來的甜頭, 全然不提壞處, 這就是反對的唯一理由. 你想想看, 這天下豈有好處無限, 壞處全無的好康事?

正常的政府要把好壞攤出來, 利弊得失權衡之後, 取利多者, 補償受害者, 這就是合理分配利益. 這就是歷史發展的核心原則. 我相信服貿絕對是有利的一面, 但這利是歸全民還是少數巨商大戶? 巨商得利再來炒高地皮? 拒絕審查那表示這張利益分配表是見不得人的, 不能攤開來.

一看到前面有警察路邊臨檢, 就嚇得抄叉路遁逃的, 你知道是哪些人嗎?

讀王莽有感

近日讀西漢末年故事, 看王莽如何從一位禮賢下士, 萬人推崇的儒林賢者, 變成篡漢自立, 萬古罪人的逆臣賊子, 這種反差真是難以想像.

據維基百科所述, 王莽是中國歷史上第一個篡位者, 歷來史家評其為偽君子, 僅胡適等認為他是一個改革者, 政治家, 稱頌其為 "王莽是中國第一位社會主義者 ".  我覺得錢穆的評論比較深入 : "王莽是一個事事復古, 脫離現實的政治家. 王莽的政治, 完全是一種書生的政治. .... 不達政情, 又無賢輔, 徒以文字議論政治.", 當政者宜深思.

古來改革者均無好下場, 失敗的因素很複雜, 不可一概而論. 改革本身沒甚麼不對, 而是改革者的掌握度不夠, 有時也是時也運也命也. 像張居正那樣審時度勢等待時機成熟再進行改革者, 死後也不免遭到清算. 王莽的下場, 維基是這麼寫 : "地皇四年,綠林軍攻入長安,王莽在混亂中為商人杜吳所殺,校尉公賓斬其首,懸於宛市之中。新朝滅亡。頭顱後來被各代收藏,直到295年晉惠帝時,洛陽武庫大火,王莽之頭焚毀。".

http://zh.wikipedia.org/wiki/%E7%8E%8B%E8%8E%BD
http://zh.wikipedia.org/wiki/%E7%8E%8B%E6%94%BF%E5%90%9B