2016年12月25日 星期日

楊輝三角形

楊輝三角形,又稱賈憲三角形、帕斯卡三角形、海亞姆三角形
書中楊輝說明是引自賈憲的《釋鎖算術》 維基連結
參考一下它的排列方式和算法

現在把它先寫成如下圖的程式:
此例為7層,只要把它順時針轉45度就一樣了
(1,1)表示第一層第一個;(5,3)表示第5層第3個
先寫個框框吧!

※第一階段:座標

public static void main(String[] args) {
    pascal(7);
}
    
public static void pascal(final int layer) {
    int rightCoordinate = layer;
    for (int i = 1; i <= layer; i++) {
        for (int j = 1; j <= rightCoordinate; j++) {
            System.out.print("(" + i + "," + j + ") ");
        }
        System.out.println();
        rightCoordinate--;
    }
}

※外層迴圈表示第幾層,內層迴圈表示要印出的座標,要和上圖的座標一樣才表示正確的

※因為第一層要印7個,第二層印6個,每次減1,而一開始印的7個剛好和要印幾層一樣,所以宣告了rightCoordinate變數


※第二階段:左/上 兩層

private static void pascal(final int layer) {
    int rightCoordinate = layer;
    for (int i = 1; i <= layer; i++) {
        for (int j = 1; j <= rightCoordinate; j++) {
            // System.out.print("(" + i + "," + j + ") ");
            System.out.print(coordinate(i, j) + " ");
        }
        System.out.println();
        rightCoordinate--;
    }
}
    
private static int coordinate(int left, int right) {
    if (left == 1 || right == 1) {
        return 1;
    }
    if (left == 2 || right == 2) {
        return left > right ? left : right;
    }
    return 0;
}

※根據上圖會發現,座標有1的,結果就是1;座標有2的,結果是比較大的那一個,如(2,4)或(4,2),結果都是4
但如果有1也有2,如(2,1)和(1,2),結果是1,所以if有1的判斷式要放在前面

※其他暫時回傳0


※第三階段:完成

private static int coordinate(int left, int right) {
    if (left == 1 || right == 1) return 1;
    if (left == 2 || right == 2) return left > right ? left : right;
    return coordinate(left - 1, right) + coordinate(left, right - 1);
}

※(4,3)的結果是10,是由(4,2)和(3,3)算來的,所以是左-1和右-1相加而得
(4,2)的結果是由(4,1)和(3,2)算來的,所以是左-1和右-1相加而得
(3,3)的結果是由(3,2)和(2,3)算來的,所以是左-1和右-1相加而得
其他也都是一樣,所以公式是「左-1加右-1」
所以將這個公式呼叫自己,也就是遞迴,答案即為所求

※此時想將排版改成和楊輝三角形時,也就是左邊要補空格,是不可行的,以5層為例,會變成如下的樣子
    1 1 1 1 1
   1 2 3 4
  1 3 6
 1 4
1
所以一開始的圖是不夠正確的,將它改成如下的樣子



這樣子加空格才是可行的


public static void main(String[] args) {
    pascal(7);
}
    
private static void pascal(final int layer) {
    int space = layer - 1;
    for (int i = 1; i <= layer; i++) {
        getSpace(space--);
        for (int j = 1; j <= i; j++) {
            // System.out.print("(" + i + "," + j + ") ");
            System.out.print(coordinate(i, j) + "|");
        }
        System.out.println();
    }
}
    
private static int coordinate(int left, int right) {
    if (right == 1 || left == right) {
        return 1;
    }
    return coordinate(left - 1, right - 1) + coordinate(left - 1, right);
}
    
private static void getSpace(int s) {
    for (int space = s; space > 0; space--)
        System.out.print(" ");
}

※不熟的一樣要用剛剛的三階段比較好了解,但此時的做法又不太一樣了
會回傳1的要改成右邊是1或者左右座標一樣的,看圖即可知道

※遞迴也要改,本來是左邊和上面的加總,要改成上面和上面的左邊加總,一樣看圖即可知道
所以公式變成「左-1加上左右-1」,將公式放在遞迴的位置上即可

※此時可以排版了,宣告的space變數即為排版用的,但太多層時,因位數字的關係,如幾百幾千,就是要佔3、4個位置,和一開始的佔1、2個位置有差,所以不好排版,可能還要再想一下才會好看




※陣列寫法

上面的座標,當然也是可以用陣列來實現,會變得更加簡單,但因為陣列都是從0開始,所以再將圖改成以下的樣子


改成都是從0開始


public static void main(String[] args) {
    pascal(10);
}
    
private static void pascal(final int layer) {
    int l = layer - 1;
    int[][] lr = new int[l][l];
    
    for (int i = 0; i < lr.length; i++) {
        for (int j = 0; j <= i; j++) {
            if (j == 0 || i == j) {
                lr[i][j] = 1;
            } else {
                lr[i][j] = lr[i - 1][j - 1] + lr[i - 1][j];
            }
            System.out.print(lr[i][j] + "|");
        }
        System.out.println();
    }
}

※layer-1是因為從0開始,0~9就是10層了,所以-1

※由於一開始是0,所以一開始進迴圈不能減1,所以直接在裡面判斷,判斷的方法和上面介紹的一樣,看圖說故事

※還沒有排版,直接使用上面寫的getSpace方法,但因為陣列的關係,所以還得再減1,如下:

public static void main(String[] args) {
    pascal(10);
}
    
private static void pascal(final int layer) {
    int l = layer - 1;
    int space = l - 1;
    int[][] lr = new int[l][l];
    
    for (int i = 0; i < lr.length; i++) {
        getSpace(space--);
        for (int j = 0; j <= i; j++) {
            if (j == 0 || i == j) {
                lr[i][j] = 1;
            } else {
                lr[i][j] = lr[i - 1][j - 1] + lr[i - 1][j];
            }
            System.out.print(lr[i][j] + "|");
        }
        System.out.println();
    }
}
    
private static void getSpace(int s) {
    for (int space = s; space > 0; space--)
        System.out.print(" ");
}

2016年12月24日 星期六

slice、substring、substr 差別


let data = '0123456789';
    
console.log('一個參數');
console.log(data.slice(4)); // 456789
console.log(data.substring(4)); // 456789
console.log(data.substr(4)); // 456789
console.log('---------------');
console.log('二個參數-正數');
console.log(data.slice(1, 4)); // 123-->從index 1到index4,但最後的不算,所以是1~3
console.log(data.substring(1, 4)); // 123-->同slice
console.log(data.substr(1, 4)); // 1234-->從index 1開始取4個
console.log('---------------');
console.log('二個參數-負數小~大');
console.log(data.slice(-2, 4)); // 空-->不支援,但也不報錯
console.log(data.substring(-2, 4)); // 0123-->等同(0, 4),也就是不支援負的
console.log(data.substr(-2, 4)); // 89-->index 9也等於-1,所以從8開始取4個,但只取到兩個就結束了
console.log('---------------');
console.log('二個參數-負數大~小');
console.log(data.slice(4, -2)); // 4567-->從index 4開始,-2表示最後兩個不算
console.log(data.substring(4, -2)); // 0123-->等同(-2, 4),但因為不支援負的,所以又等同(0, 4)
console.log(data.substr(4, -2)); // 空-->不支援,但也不報錯

※結果:
一個參數
456789
456789
456789
---------------
二個參數-正數
123
123
1234
---------------
二個參數-負數小~大

0123
89
---------------
二個參數-負數大~小
4567
0123



常考的面試題:九九乘法

首先一般都是用for迴圈,使用兩個迴圈,都是從1跑到9,變數當然不同,然後九九乘法就出來了,但如果只要顯示其中一個角,就要在上面判斷了,如下:

※橫式

for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= 9; j++) {
        // 右上
        /*
            if (i > j) {
                sevenSpace();
                continue;
            }
        */
    
        // 左下,但使用for (int j = 1; j <= i; j++)效能較好
        if (j > i) {
            continue;
        }
    
        // 左上
        /*
            if (i + j == 11) {
                break;
            }
        */
        
        // 右下
        /*
            if (i + j < 10) {
                sevenSpace();
                continue;
            }
        */
    
        System.out.printf("%dx%d=%2d|", i, j, i * j);
    }
    System.out.println("");
}
    
private static void sevenSpace() {
    for (int k = 1; k <= 7; k++) {
        System.out.print(" ");
    }
}




※直式

就是3x3格式,先寫最右邊的1到9會,然後顯示時再調整一下會比較好寫

int x = 1;
for (int k = 1; k <= 3; k++) {
    for (int j = 1; j <= 9; j++) {
        for (int i = x; i < x + 3; i++) {
            // System.out.println(i + " " + j);
            System.out.printf("%dx%d=%2d|", i, j, i * j);
        }
        System.out.println();
    }
    x = x + 3;
    System.out.println("---------------------");
}




※一個迴圈

for (int i = 1, j = 1; j <= 9; j++) {
    System.out.printf("%dx%d=%2d|", i, j, i * j);
    if (j == 9) {
        System.out.println();
        i++;
        j = 0;
        if (i == 10) {
            break;
        }
    }
}




2016年12月23日 星期五

內省

內省的包是 java.beans,就是要針對java bean使用的

※getPropertyDescriptors

利用setter/getter其中之一來達到抓取的目的,但有以下幾點要注意
就算沒有給屬性(所以屬性的修飾子是什麼無所謂),
.setter:public void set開頭就抓的到
.getter:public而且回傳值不是void,且沒有參數,就算回傳的是個null也可抓的到
set/get開頭後面一定要有,不能剛好是set/get


package inheritate;
    
public class Son extends Dad {
    private int son;
    private int son2;
    private String son3;
    
    // setter/getter...
}
------------------------------
package inheritate;
    
public class Dad extends Father {
    private String dad;
    
    // setter/getter...
}
------------------------------
package inheritate;
    
public abstract class Father implements GrandFather {
    private String father;
    
    // setter/getter...
}
------------------------------
package inheritate;
    
public interface GrandFather {
    String grandFather = "g";
    
    default String getGrandFather() {
        return grandFather;
    }
}

※雖然最後是介面,也有屬性,但內省是不會理他的,因為介面本來就沒有什麼setter/getter


※測試

Son son = new Son();
son.setSon(1);
son.setDad("d1");
son.setFather("d2");
    
/*
Son s;
try {
    Class<?> clazz = Class.forName("inheritate.Son");
    try {
        s = (Son) clazz.newInstance();
        s.setSon(2);
        s.setDad("s1");
        s.setFather("s2");
    } catch (InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }
} catch (ClassNotFoundException e1) {
    e1.printStackTrace();
}
*/
    
BeanInfo bi = null;
try {
    bi = Introspector.getBeanInfo(son.getClass());
} catch (IntrospectionException e) {
    e.printStackTrace();
}
    
PropertyDescriptor[] pds = bi.getPropertyDescriptors();
System.out.println("pds長度=" + pds.length + System.getProperty("line.separator"));
    
for (PropertyDescriptor p : pds) {
    System.out.println("name=" + p.getName());
    System.out.println("propertyType=" + p.getPropertyType());
    Method rm = p.getReadMethod();
    try {
        System.out.println("invoke=" + rm.invoke(son));
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
        e.printStackTrace();
    }
    System.out.println();
}

※結果:
pds長度=6

name=class
propertyType=class java.lang.Class
invoke=class inheritate.Son

name=dad
propertyType=class java.lang.String
invoke=d1

name=father
propertyType=class java.lang.String
invoke=d2

name=son1
propertyType=int
invoke=1

name=son2
propertyType=int
invoke=0

name=son3
propertyType=class java.lang.String
invoke=null

※Son有三個屬性,而Dad、Father各有一個屬性,所以會有5個,但看結果會多一個class的屬性

※getBeanInfo有四個建構子,其中的兩個參數,有給class和int的
.class表示在哪個class就停止搜尋下去的意思,停止的class不會搜尋
所以以上面的結果,如果不想要Class,就可以在getBeanInfo增加第二個參數Object.class就不會出現class屬性了,又如果我給Father.class,那結果就是4個,因為Son繼承Dad,Father不會搜尋

.int有三種,都是static的
USE_ALL_BEANINFO:如果第二個參給這個值,它會連到三個參數的建構子,所以第二個肯定是null,這時等同一個參數的建構子
IGNORE_IMMEDIATE_BEANINFO:待補充
IGNORE_ALL_BEANINFO:待補充

※實體可不同,如下

BeanInfo bi = null;
try {
    bi = Introspector.getBeanInfo(Son.class, Dad.class);
} catch (IntrospectionException e) {
    e.printStackTrace();
}
    
PropertyDescriptor[] pds = bi.getPropertyDescriptors();
System.out.println("pds長度=" + pds.length + System.getProperty("line.separator"));
    
Son son = new Son();
for (PropertyDescriptor p : pds) {
    Method wm = p.getWriteMethod();
    Method rm = p.getReadMethod();
    
    if (p.getName().equals("son3")) {
        try {
            wm.invoke(son, "xxx");
            System.out.println("invoke=" + rm.invoke(son));
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println();
    } else if (p.getName().equals("son1")) {
        try {
            wm.invoke(son, 2);
            System.out.println("invoke=" + rm.invoke(son));
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

※Son的實體沒有放在getBeanInfo裡面,但還是OK的

※此例塞son3和son1屬性,其他就不塞,所以不塞的該是null就是null;該是0就是0

※getWriteMethod對應setter方法;getReadMethod對應getter方法,如果其中一個沒有,那對應的方法就會報錯

※由於屬性和setter/getter的名稱是可以不匹配的,所以java bean如下的設定也是可以的
package inheritate;
    
public class Son extends Dad {
    private int s;
    private int s2;
    private String s3;
    
    public int getSon() {
        return s;
    }
    
    public void setSon(int son1) {
        this.s = son1;
    }
    
    public int getSon2() {
        return s2;
    }
    
    public void setSon2(int son2) {
        this.s2 = son2;
    }
    
    public String getSon3() {
        return s3;
    }
    
    public void setSon3(String son3) {
        this.s3 = son3;
    }
}




※PropertyDescriptor

少了一個s,這時屬性修飾子依然無所謂,但setter/getter都一定要有

※測試

try {
    PropertyDescriptor pd = new PropertyDescriptor("son4", Son.class);
    Method m1 = pd.getWriteMethod();
    Method m2 = pd.getReadMethod();
    Son son = new Son();
    try {
        m1.invoke(son, "xxx"); // 呼叫setSon4
        System.out.println(m2.invoke(son)); // 呼叫getSon4
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
        e.printStackTrace();
    }
} catch (IntrospectionException e) {
    e.printStackTrace();
}

※直接利用PropertyDescriptor的建構子即可達到目的,第二個參數放Class,第一個參數放第二個參數裡的屬性名稱

※屬性和setter/getter的名稱是仍然是不匹配的,所以java bean如下的設定也是可以的
package inheritate;
    
public class Son extends Dad {
    private String son1;
    
    public String getSon4() {
        return "ooo";
    }
    
    public void setSon4(String son4) {
        this.son1 = son4;
    }
}





※getMethodDescriptors

BeanInfo bi = null;
try {
    bi = Introspector.getBeanInfo(Son.class, Dad.class);
} catch (IntrospectionException e) {
    e.printStackTrace();
}
MethodDescriptor[] mds = bi.getMethodDescriptors();
System.out.println("mds長度=" + mds.length + System.getProperty("line.separator"));

for (MethodDescriptor m : mds) {
    System.out.println("name=" + m.getName());
    System.out.println("value=" + m.getValue(m.getName()));
    if ("setSon3".equals(m.getName())) {
        m.setValue(m.getName(), "xxx");
        System.out.println("value=" + m.getValue(m.getName()));
    }
    
    Enumeration<String> attrs = m.attributeNames();
    while(attrs.hasMoreElements()){
        System.out.println(attrs.nextElement());
    }
    System.out.println();
}





※公用

apache提供了一個方便的工具,但要下載


<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.3</version>
</dependency>
    



※測試

public static void main(String[] args) throws Exception {
    Son son = new Son();
    BeanUtils.setProperty(son, "son", "1");
    BeanUtils.setProperty(son, "son2", "2");
    BeanUtils.setProperty(son, "son3", "xxx");
    
    System.out.println(BeanUtils.getProperty(son, "son"));
    System.out.println(BeanUtils.getProperty(son, "son2"));
    System.out.println(BeanUtils.getProperty(son, "son3"));
    
    Son s = new Son();
    PropertyUtils.setProperty(s, "son", 3);
    PropertyUtils.setProperty(s, "son2", 4);
    PropertyUtils.setProperty(s, "son3", "ooo");
    
    System.out.println(PropertyUtils.getProperty(s, "son"));
    System.out.println(PropertyUtils.getProperty(s, "son2"));
    System.out.println(PropertyUtils.getProperty(s, "son3"));
}

※BeanUtils在set時,可以全部都用字串塞值,但PropertyUtils一定要塞對的型態,否則會報錯

※還是要依照java bean才有辦法塞/取值

2016年12月17日 星期六

代理、工廠模式 (反射五)

※代理

package inheritate;
    
public interface GrandFather {
    public String say(String data);
}
------------------------------
package inheritate;
    
public class Son implements GrandFather {
    @Override
    public String say(String data) {
        return data;
    }
}


※InvocationHandler

public class MyInvocationHandler implements InvocationHandler {
    private Object obj;
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(obj, args);
    }
    
    public Object proxy(Object obj) {
        this.obj = obj;
        Class<?> objClass = obj.getClass();
        return Proxy.newProxyInstance(objClass.getClassLoader(), objClass.getInterfaces(), this);
    }
}


※測試

MyInvocationHandler mih = new MyInvocationHandler();
GrandFather gf = (GrandFather) mih.proxy(new Son());
System.out.println(gf.say("xxx"));

※利用InvocationHandler即可完成代理的功能



※工廠模式

package inheritate;
    
public interface GrandFather {
    public String say(String data);
}
------------------------------
package inheritate;
    
public class Son implements GrandFather {
    @Override
    public String say(String data) {
        return data + " 1";
    }
}
------------------------------
package inheritate;
    
public class SecondSon implements GrandFather {
    @Override
    public String say(String data) {
        return data + " 2";
    }
}


※工廠類

package inheritate;
    
public class ReflecFactory {
    public static Object getInstance(String className) {
        try {
            return Class.forName(className).newInstance();
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}



※測試

GrandFather gf1 = (GrandFather) ReflecFactory.getInstance("inheritate.Son");
System.out.println(gf1.say("xxx"));
    
GrandFather gf2 = (GrandFather) ReflecFactory.getInstance("inheritate.SecondSon");
System.out.println(gf2.say("ooo"));

※不管有幾個子類和其他的父子類,工廠類別都不用動

為屬性、方法設值取值(反射四)

※屬性設值取值

package inheritate;
    
public class Son {
    public String s1;
    private String s2;
}
------------------------------
public static void main(String[] args) throws Exception {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    Field s1 = clazz.getField("s1");
    Object s1Obj = clazz.newInstance();
    s1.set(s1Obj, "xxx");
    System.out.println(s1.get(s1Obj));
    
    // Field s2 = clazz.getField("s2");
    Field s2 = clazz.getDeclaredField("s2");
    s2.setAccessible(true);
    Object s2Obj = clazz.newInstance();
    s2.set(s2Obj, "ooo");
    System.out.println(s2.get(s2Obj));
}

※修飾子不是public的,有可能會報「NoSuchFieldException」的錯,得看測試類在放在哪而定

※想取得上面報的錯的值,可用setAccessible,但得用DeclaredField才行,不然還是會報錯,因為DeclaredField本來就是取自己class的東西



※AccessibleObject.setAccessible

Field和Method到最後都會繼承AccessibleObject類別,所以setAccessible都可以用
setAccessible是個overloadding,有分一個參數和兩個參數,兩個參數是static的
一個參數上個例子已有,這裡講的是兩個參數的


package inheritate;
    
public class Son {
    private String s0;
    private String s1;
    private String s2;
    private String s3;
    private String s4;
}
------------------------------
public static void main(String[] args) throws Exception {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    Field[] fields = clazz.getDeclaredFields();
    Object obj = clazz.newInstance();
    for (int i = 0; i < fields.length; i++) {
        fields[i].setAccessible(true);
        fields[i].set(obj, "s" + i);
        System.out.println("field name=" + fields[i].getName());
        System.out.println("field value=" + fields[i].get(obj));
        System.out.println();
    }
}

※這個例子還是和上個例子一樣,跑迴圈時,一個一個變成true


Class<?> clazz = Class.forName("inheritate.Son");
    
Field[] fields = clazz.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
Object obj = clazz.newInstance();
for (int i = 0; i < fields.length; i++) {
    // f.setAccessible(fields, true);
    fields[i].set(obj, "s" + i);
    System.out.println("field name=" + fields[i].getName());
    System.out.println("field value=" + fields[i].get(obj));
    System.out.println();
}

※使用靜態方法可以一次將屬性全部變成true

※註解那行也是可以,但浪費一些資源,而且會有警告,因為它是static


※方法設值取值

package inheritate;
    
public class Son {
    public String sayHello() {
        return "hello";
    }
    
    public String say(String data) {
        return data;
    }
    
    private String privateTest() {
        return "private method";
    }
}
------------------------------
public static void main(String[] args) throws Exception {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    // 呼叫無參方法
    Method m1 = clazz.getMethod("sayHello");
    String r1 = (String) m1.invoke(clazz.newInstance());
    System.out.println(r1);
    
    // 呼叫有參方法
    Method m2 = clazz.getMethod("say", String.class);
    String r2 = (String) m2.invoke(clazz.newInstance(), "Hello Reflection");
    System.out.println(r2);
    
    // 取得private方法
    // Method m3 = clazz.getMethod("privateTest");
    Method m3 = clazz.getDeclaredMethod("privateTest");
    m3.setAccessible(true);
    String r3 = (String) m3.invoke(clazz.newInstance());
    System.out.println(r3);
}

※修飾子不是public的,有可能會報「NoSuchMethodException」的錯,得看測試類在放在哪而定

※想取得上面報的錯的值,可用setAccessible,但得用DeclaredMethod才行,不然還是會報錯,因為DeclaredField本來就是取自己class的東西

※invoke就是呼叫方法了,裡面至少要放實體,如果沒有回傳值也不會報錯,會回傳null



※setter/getter

package inheritate;
    
public class Son {
    private int id;
    
    public int getId() {
        return id;
    }
    
    public void setId(int id) {
        this.id = id;
    }
}
------------------------------
public static void main(String[] args) throws Exception {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    Object obj = clazz.newInstance();
    Class<?> cz = obj.getClass();
    
    Method m1 = cz.getMethod("setId", int.class);
    m1.invoke(obj, 1);
    
    Method m2 = cz.getMethod("getId");
    Object getResult = m2.invoke(obj);
    System.out.println(getResult);
}

※使用newInstance取得實體,然後再取得Class,這個Class可以讓setter/getter使用



※setter/getter公用方法

public static void main(String[] args) throws Exception {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    Object obj = clazz.newInstance();
    Class<?> cz = obj.getClass();
    
    setXxx(cz, obj, "id", int.class, 1);
    int rtn = (int) getXxx(cz, obj, "id");
    System.out.println(rtn);
}
    
public static <T> void setXxx(Class<?> sourceClass, Object instance, String methodName, Class<T> parameterType, T value) throws Exception {
    Method m = sourceClass.getMethod("set" + firstUpperCase(methodName), parameterType);
    m.invoke(instance, value);
}
    
public static String firstUpperCase(String methodName) {
    return methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
}
    
public static Object getXxx(Class<?> sourceClass, Object instance, String methodName) throws Exception {
    Method m = sourceClass.getMethod("get" + firstUpperCase(methodName));
    return m.invoke(instance);
}

方法、介面、父類 (反射三)

※取得全部方法

package inheritate;
    
public interface GrandFather {
    void gf1();
    
    static String gf2(String p) {
        return "";
    }
    
    default String gf3(Integer p) {
        return "";
    }
}
------------------------------
package inheritate;
    
public abstract class Father implements GrandFather {
    public abstract void f1();
    
    public static String f2(String p) {
        return "";
    }
    
    public String f3(Integer p) {
        return "";
    }
}
------------------------------
package inheritate;
    
public class Son extends Father {
    @Override
    public void gf1() {
        System.out.println("");
    }
    
    static String s1(String p) {
        return "";
    }
    
    @Override
    public void f1() {}
}


※測試

try {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    System.out.println("Methods:");
    for (Method m : clazz.getMethods()) {
        System.out.println(m);
    }
    
    System.out.println(System.getProperty("line.separator") + "DeclaredMethods:");
    for (Method m : clazz.getDeclaredMethods()) {
        System.out.println(m);
    }
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

※getDeclaredMethods,只會叫出Class.forName裡面定義的方法,不管修飾子是什麼

※getMethods只會叫出修飾子是public的方法
但要注意屬性的繼承是有作用的,抽象方法並不會印出來
介面不寫修飾子,預設是public abstract,所以一定是public,寫private還會編譯錯誤

※注意介面的static不會印出來(default可以),有可能是java8的bug,因為java8才可以寫static方法在裡面,又或者本來就是這樣的設計

※方法常用方法

package inheritate;
    
public class Son extends Father {
    private String a;
    private int b;
    
    public String getA() {
        return a;
    }
    
    public void setA(String a) {
        this.a = a;
    }
    
    public int getB() throws RuntimeException {
        return b;
    }
    
    public void setB(int b) {
        this.b = b;
    }
    
    @Override
    public void gf1() {
        System.out.println("");
    }
    
    static String s1(String p) {
        return "";
    }
    
    @Override
    public void f1() {}
}


※測試

try {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    for (Method m : clazz.getDeclaredMethods()) {
        System.out.println("m:" + m);
        System.out.println("修飾子(數字):" + m.getModifiers());
        System.out.println("修飾子(中文):" + Modifier.toString(m.getModifiers()));
        System.out.println("包.方法名:" + m.getName());
        System.out.println("有幾個參數:" + m.getParameterCount());
        // System.out.println("有幾個參數:" + m.getParameterTypes().length);
        System.out.println("回傳型態:" + m.getReturnType());
        System.out.println("宣告的類別:" + m.getDeclaringClass());// Son
        System.out.println("方法的類別:" + m.getClass());// Method
    
        for(Class<?> exc:m.getExceptionTypes()){
            System.out.println("例外:" + exc);
        }
    
        for (Class<?> p : m.getParameterTypes()) {
            System.out.println("參數名稱:" + p.getCanonicalName());
        }
        System.out.println();
    }
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}




※取得全部介面

public interface GrandFather {}
public interface Dad {}
public class Son implements Dad, GrandFather {}
--------------------
try {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    System.out.println(clazz.getInterfaces().length);
    for (Class<?> cz : clazz.getInterfaces()) {
        System.out.println(cz.toGenericString());
        System.out.println(cz.toString());
        System.out.println(cz.getPackage());
        System.out.println(cz.getCanonicalName());
        System.out.println(cz.getName());
        System.out.println(cz.getSimpleName());
        System.out.println();
        cz.getFields();
        cz.getMethods();
    }
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

※如果Son有繼承父類是取不到的,只能取得介面,且是Son的直接介面
假設Son只有實作Dad,而Dad繼承GrandFather,只會取得Dad而已


※取得父類

public abstract class Father {}
public class Son extends Father {}
--------------------
try {
    Class<?> clazz = Class.forName("inheritate.Son");
    Class<?> cz = clazz.getSuperclass();
    System.out.println(cz.toGenericString());
    System.out.println(cz.toString());
    System.out.println(cz.getPackage());
    System.out.println(cz.getCanonicalName());
    System.out.println(cz.getName());
    System.out.println(cz.getSimpleName());
    System.out.println();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

※如果Son有實作介面是取不到的,只能取得父類,且是Son的直接父類,沒有就是Object
假設Father還有繼承父類或實作類別都取不到

※getSuperclass並沒有length方法,因為父類只有一個,至少有Object,如繼承了,則Object就取不到了

2016年12月14日 星期三

建構子、屬性 (反射二)

※取得全部建構子

package inheritate;
    
public abstract class Father implements GrandFather {
    Father(){}
    Father(String s){}
}
------------------------------
package inheritate;
    
public class Son extends Father {
    Son() {}
    
    public Son(String s) {}
    
    Son(int i, String s) {}
}

※GrandFather是介面,因介面是沒有建構子的,所以不貼出來

※測試

try {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    System.out.println("Constructors:");
    for (Constructor<?> c : clazz.getConstructors()) {
        System.out.println(c);
    }
    
    System.out.println(System.getProperty("line.separator") + "DeclaredConstructors:");
    for (Constructor<?> c : clazz.getDeclaredConstructors()) {
        System.out.println(c);
    }
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

※getDeclaredConstructors,會叫出Class.forName裡面定義的建構子,不管修飾子是什麼

※getConstructors只會叫出修飾子是public的建構子
但要注意繼承的父類並不會出現,也就是Father抽象類別(沒抽象也是一樣)沒有作用



※建構子常用方法

package inheritate;
    
public class Son extends Father {
    private int i;
    private String s;
    
    // setter/getter...
    
    Son() {}
    
    private Son(String s) {}
    
    protected Son(int i, String s) {
        this.i = i;
        this.s = s;
    }
    
    public Son(String s, int i) {
        this.s = s;
        this.i = i;
    }
}
--------------------
package inheritate;
    
public class Father {
    public Father() {}
    
    Father(String s) {}
}


※測試

try {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    System.out.println("幾個建構子:" + clazz.getDeclaredConstructors().length);
    System.out.println();
    for (Constructor<?> c : clazz.getDeclaredConstructors()) {
        System.out.println("c:" + c);
        System.out.println("修飾子(數字):" + c.getModifiers());
        System.out.println("修飾子(中文):" + Modifier.toString(c.getModifiers()));
        System.out.println("包.類名:" + c.getName());
        System.out.println("有幾個參數:" + c.getParameterCount());
        // System.out.println("有幾個參數:" + c.getParameterTypes().length);
    
        for (Class<?> p : c.getParameterTypes()) {
            System.out.println("參數名稱:" + p.getCanonicalName());
        }
        System.out.println();
    }
    
    // 塞值
    Constructor<?> c1 = clazz.getDeclaredConstructors()[0];
    try {
        Son s1 = (Son) c1.newInstance("xxx", 1);
        System.out.println(s1.getI());
        System.out.println(s1.getS());
    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
            | InvocationTargetException e) {
        e.printStackTrace();
    }
    
    // 有可能出現「Class reflection.Test can not access a member of class inheritate.Son with modifiers "protected"」,看測試類有沒有在同個package或者是否是Son的子類而定
    Constructor<?> c2 = clazz.getDeclaredConstructors()[1];
    try {
        Son s2 = (Son) c2.newInstance(2, "ooo");
        System.out.println(s2.getI());
        System.out.println(s2.getS());
    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
            | InvocationTargetException e) {
        e.printStackTrace();
    }
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

※getName、getCanonicalName很像,以下是差別


※getName、getCanonicalName差別

package inheritate;
    
public class Son {
    public static class Grandson {}
}
--------------------
package inheritate;
    
public interface GrandFather {
    public static String gf2(String p) {
        return "";
    }
    
    default String gf3(Integer p) {
        return "";
    }
}


※測試

// 內部類別
Class<?> sg = Son.Grandson.class;
System.out.println(sg.getName());//inheritate.Son$Grandson
System.out.println(sg.getCanonicalName());//inheritate.Son.Grandson
System.out.println();
    
// 陣列
Integer[] iArray1 = {};
int[] iArray2 = {};
System.out.println(iArray1.getClass().getName());//[Ljava.lang.Integer;
System.out.println(iArray1.getClass().getCanonicalName());//java.lang.Integer[]
System.out.println();
System.out.println(iArray2.getClass().getName());//[I
System.out.println(iArray2.getClass().getCanonicalName());//int[]
System.out.println();
    
// 介面、類別
System.out.println(new GrandFather(){}.getClass().getName());//Test$1
System.out.println(new GrandFather(){}.getClass().getCanonicalName());//null

※總之getCanonicalName比較好閱讀


※取得全部屬性

package inheritate;
    
public interface GrandFather {
    String gf1 = "";
    String gf2 = "";
}
------------------------------
package inheritate;
    
public abstract class Father implements GrandFather {
    public String f1 = "";
    String gf2 = "";
    public static String sf = "";
}
------------------------------
package inheritate;
    
public class Son extends Father {
    public String son = "";
    static String sson = "";
}


※測試

try {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    System.out.println("Fields:");
    for (Field f : clazz.getFields()) {
        System.out.println(f);
    }
    
    System.out.println(System.getProperty("line.separator") + "DeclaredFields:");
    for (Field f : clazz.getDeclaredFields()) {
        System.out.println(f);
    }
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

※getDeclaredFields,只會叫出Class.forName裡面定義的屬性,不管修飾子是什麼

※getFields只會叫出修飾子是public的屬性
但要注意屬性的繼承是有作用的,不管是class、interface,只要是public都會印出來
介面的屬性不寫修飾子,預設就是public static final,所以一定是public,寫private、protected會編譯錯誤


※屬性常用方法

package inheritate;
    
public class Son extends Father {
    public String son = "";
    public static String sson = "";
}
------------------------------
package inheritate;
    
public abstract class Father implements GrandFather {
    public String f1 = "";
    String gf2 = "";
    public static String sf = "";
}
------------------------------
package inheritate;
    
public interface GrandFather {
    String gf1 = "";
    String gf2 = "";
}


※測試

try {
    Class<?> clazz = Class.forName("inheritate.Son");
    
    System.out.println(System.getProperty("line.separator") + "DeclaredFields:");
    for (Field f : clazz.getDeclaredFields()) {
        int modifiers = f.getModifiers();
        System.out.println("修飾子(數字):" + modifiers);
        System.out.println("修飾子(中文):" + Modifier.toString(modifiers));
    
        Class<?> type = f.getType();
        System.out.println("type:" + type);
        System.out.println("屬性類型:" + type.getCanonicalName());
        System.out.println("屬性名稱:" + f.getName());
        System.out.println("宣告的類別:" + f.getDeclaringClass());// Son
        System.out.println("欄位的類別:" + f.getClass());// Field
    }
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

2016年12月13日 星期二

日期、Base64 (java8 七)

※多執行緒用 SimpleDateFormat 有問題

DateFormat df = new SimpleDateFormat("yyyyMMdd HHmmss");
ExecutorService es = Executors.newFixedThreadPool(5);
    
Stream.iterate(0, i -> ++i).limit(1000).forEach(i -> {
    es.execute(() -> {
        try {
            // System.out.println(df.parse("20101112 235958"));
    
            // 要用時再 new 可解決,但浪費記憶體
            System.out.println(new SimpleDateFormat("yyyyMMdd HHmmss").parse("20101112 235958"));
    
            // synchronized 也可解決,但高併發時會阻塞
    
            // 用 ThreadLocal 也可解決
            System.out.println(ThreadLocalDateFormat.convertDate("20101112 235958"));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    });
});
    
es.shutdown();
    
    
class ThreadLocalDateFormat {
    private static ThreadLocal<DateFormat> tl = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmmss"));
    
    static Date convertDate(String ymdhms) throws ParseException {
        return tl.get().parse(ymdhms);
    }
}


※日期

java.time開頭的5個package都是java8新增的
LocalDate、LocalTime、LocalDateTime看要不要有年月日或時分秒用的


※LocalDate

LocalDate today = LocalDate.now();
System.out.println("現在日期=" + today);
    
System.out.println("下星期=" + today.plus(1, ChronoUnit.WEEKS));
System.out.println("上一個月=" + today.plus(-1, ChronoUnit.MONTHS));
System.out.println("明年=" + today.plus(1, ChronoUnit.YEARS));
System.out.println("下一個十年" + today.plus(1, ChronoUnit.DECADES));

※ChronoUnit還可以加100年、1000年…等

※ChronoUnit.YEARS
ChronoUnit.WEEKS
ChronoUnit.MONTHS
ChronoUnit.DAYS
ChronoUnit.HOURS
ChronoUnit.MINUTES
ChronoUnit.SECONDS
ChronoUnit.MILLIS
ChronoUnit.NANOS
時、分、秒、毫秒、毫微秒要使用 LocalDateTime



※日期相差

LocalDate date1 = LocalDate.now();
System.out.println("date1=" + date1);
    
LocalDate date2 = date1.plus(1, ChronoUnit.MONTHS);
System.out.println("date2=" + date2);
    
// 右邊減左邊
Period period = Period.between(date1, date2);
System.out.println("相差=" + period);// P1M
    
LocalTime time1 = LocalTime.now();
System.out.println("time1=" + time1);
    
Duration twoHours = Duration.ofHours(-2);
LocalTime time2 = time1.plus(twoHours);
System.out.println("time2=" + time2);
    
// 右邊減左邊
Duration duration = Duration.between(time1, time2);
System.out.println("Duration: " + duration);// PT-2H

※開頭的P和PT固定會有,可以再 getXXX 取得數字,但時分秒是分開的,要小心


※上/下 一個日期

LocalDate today = LocalDate.now();
System.out.println("現在日期=" + today);
    
LocalDate nextTuesday = today.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
System.out.println("下個星期二=" + nextTuesday);
    
// 這個月的第一天
LocalDate one = LocalDate.of(today.getYear(), today.getMonth(), 1);
LocalDate two = one.with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY));
System.out.println("下一個星期六(如果1號也是星期六,那就是1號)=" + two);
    
LocalDate three = two.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println("下一個星期五=" + three);

※要注意next和nextOrSame的差別


※LocalDateTime

LocalDateTime currentTime = LocalDateTime.now();
System.out.println("現在日期時間=" + currentTime);// 2016-12-13T17:43:00.427
    
System.out.println("年月日=" + currentTime.toLocalDate());// 2016-12-13
System.out.println("年=" + currentTime.getYear());// 2016
System.out.println("1月1日到現在是第幾天=" + currentTime.getDayOfYear());// 348
System.out.println("英文月=" + currentTime.getMonth());// DECEMBER
System.out.println("月=" + currentTime.getMonthValue());// 12
System.out.println("x月1日到現在是第幾天=" + currentTime.getDayOfMonth());// 13
System.out.println("星期=" + currentTime.getDayOfWeek());// TUESDAY
System.out.println("時=" + currentTime.getHour());// 17
System.out.println("分=" + currentTime.getMinute());// 43
System.out.println("秒=" + currentTime.getSecond());// 0
System.out.println("毫秒=" + currentTime.getNano());// 427000000
System.out.println("getChronology=" + currentTime.getChronology());// ISO
    
LocalDateTime defineymd = currentTime.withDayOfMonth(10).withYear(2012);
// 沒with的就以現在時間為主
System.out.println("自訂日期: " + defineymd);// 2012-12-10T17:43:00.427
System.out.println("年月日: " + LocalDate.of(2014, Month.DECEMBER, 12));// 2014-12-12
System.out.println("時分: " + LocalTime.of(22, 15));// 22:15
System.out.println("時分秒: " + LocalTime.parse("20:15:30"));// 20:15:30

※LocalDateTime 相關的 class,預設已經加上時區了,但不包括日光節約時間,
台灣日光節約日期可參考這裡,1975/4/1 - 1975/9/30 是其中一組,
所可以用 1975/3/31 23:59:59 加 1 秒會變成凌晨 1 點
1975/9/30 加 1 秒,會變成 1975-09-30T23:00+08:00[Asia/Taipei],
再扣一小時會變成 1975-09-30T23:00+09:00[Asia/Taipei],
也就是在日光節約時間裡是 UTC+9 

※預設時區的名字,可用 ZoneId.systemDefault() 替看


※ZonedDateTime和轉換

// ZoneId.getAvailableZoneIds().forEach(System.out::println);
ZonedDateTime d = ZonedDateTime.parse("2007-12-03T10:15:30+05:30[Asia/Karachi]");
System.out.println("d=" + d);
    
ZoneId id = ZoneId.of("Europe/Paris"); // ZoneId.of(ZoneId.SHORT_IDS.get("ECT"));
System.out.println("ZoneId=" + id);
    
System.out.println("================================");
ZoneId zoneId = ZoneId.systemDefault();
System.out.println("zoneId=" + zoneId);
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(
                LocalDateTime.of(2021, 2, 20, 10, 0, 0),
                zoneId)
System.out.println(zonedDateTime2.getOffset().getTotalSeconds() / 60 / 60); // 取得 UTC 差幾小時
    
Date today = new Date();
System.out.println("現在時間=" + today);
    
Instant dateInstant = today.toInstant();
    
// Date轉LocalDateTime
LocalDateTime localDateTime = LocalDateTime.ofInstant(dateInstant, zoneId);
System.out.println("LocalDateTime=" + localDateTime);
    
// Date轉ZonedDateTime
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(dateInstant, zoneId);
System.out.println("ZonedDateTime=" + zonedDateTime);
    
// LocalDateTime轉Date
Instant instant = localDateTime.toInstant(ZoneOffset.ofHours(8));
Date date = Date.from(instant);
System.out.println(date);
    
// ZonedDateTime轉Date
System.out.println(Date.from(zonedDateTime.toInstant()));
    
System.out.println("================================");
Calendar calendar = Calendar.getInstance();
Instant calendarInstant = calendar.toInstant();
    
// Calendar轉LocalDateTime
LocalDateTime localDateTime2 = LocalDateTime.ofInstant(calendarInstant, zoneId);
System.out.println("LocalDateTime2=" + localDateTime2);
    
// Calendar轉ZonedDateTime
ZonedDateTime zonedDateTime2 = ZonedDateTime.ofInstant(calendarInstant, zoneId);
System.out.println("ZonedDateTime2=" + zonedDateTime2);
OffsetDateTime offsetDateTime2 = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.of("-5"));

※ZonedDateTime比LocalDateTime多了ZoneOffset和ZoneId

※ZoneOffset就是+08:00這種東東;而parse裡面的[]就是ZoneId,可以使用ZoneId.systemDefault()取得

※ZoneOffset.ofHours(8)裡的8,上一行印出ZonedDateTime時,可以知道

※Date和Calendar在1.8也新增了toInstant方法,可以和新的日期做轉換使用

※Date有from可以轉成ZonedDateTime和LocalDateTime;Calendar沒看到有轉換成Instant的方法,只好和以前一樣,轉換成Date了

※OffsetDateTime 並沒有考慮日光節約時間;而 ZonedDateTime 有考慮到

※GMT 是用地球公轉來表示;UTC 是用銫原子去做,是人為的,但因為地球公轉有變慢的趨勢,所以有做加 1 秒的動作,會變成 23:59:60,時間越久,GMT 和 UTC 會越差越多,我測的 java 是在 windows 裡,沒有測出這個 

這裡可以查詢時區和日光節約時區的資訊


※有時間的字串轉換

時區要寫在 DateTimeFormatter 的 withZone 方法
final String DATE = "2018-09-18T19:13:00Z"; // Z 就是 zone 的意思
ZonedDateTime z0 = ZonedDateTime.parse(DATE);
ZonedDateTime z1 = ZonedDateTime.parse(DATE, DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(ZoneId.of("Asia/Taipei")));
ZonedDateTime z2 = ZonedDateTime.parse(DATE, DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(ZoneId.of("America/Indiana/Indianapolis")));
    
2018-09-18T19:13Z // 沒有 zone 資訊
2018-09-18T19:13+08:00[Asia/Taipei]  // +8小時後的時間
2018-09-18T19:13-04:00[America/Indiana/Indianapolis] // -4小時後的時間



※DateTimeFormatter 的 X

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss XXX");
ZonedDateTime tmpTimestamp = ZonedDateTime.parse("2018-07-31 12:13:14 +03:00", formatter);

偏移量X和x:它將根據圖案字母的數量來格式化偏移量。
除非分鐘非零,否則一個字母僅輸出小時,例如“+01”,在這種情況下,分鐘也會輸出,例如“+0130”。
兩個字母輸出小時和分鐘,不帶冒號,例如'+0130'。
三個字母輸出小時和分鐘,並帶有冒號,例如'+01:30'。
四個字母輸出小時和分鐘,可選秒,不帶冒號,例如“+013015”。
五個字母輸出小時和分鐘,可選秒,以冒號表示,例如“+01:30:15”。
六個或更多字母會引發IllegalArgumentException。
當要輸出的偏移量為零時,模式字母“X”(大寫)將輸出“Z”,而模式字母“x”(小寫)將輸出“+00”,“+ 0000”或“+00” :00'。

或者,您可以使用五個字母(XXXXX),也可以使用ZZZ或ZZZZZ代替XXX或XXXXX。


※Base64 

java.util包裡有三個Base64開頭的class,也都是java8新增的


try {
    // BASIC Encode
    String encode = Base64.getEncoder().encodeToString("xxx".getBytes("UTF-8"));
    System.out.println("encode=" + encode);
    
    // Decode
    byte[] decode = Base64.getDecoder().decode(encode);
    System.out.println("basic decode=" + new String(decode, "UTF-8"));
    
    System.out.println();
    // URL/Filename safe Encode
    String urlEncode = Base64.getUrlEncoder().encodeToString("http://www.google.com".getBytes("UTF-8"));
    System.out.println("urlEncode=" + urlEncode);
    
    // Decode
    System.out.println("url decode=" + new String(Base64.getDecoder().decode(urlEncode), "UTF-8"));
    
    System.out.println();
    // MIME Encode
    byte[] mimeBytes = new String("text/html").getBytes("UTF-8");
    String mimeEncode = Base64.getMimeEncoder().encodeToString(mimeBytes);
    System.out.println("mimeEncode=" + mimeEncode);
    
    // Decode
    System.out.println("mime decode=" + new String(Base64.getDecoder().decode(mimeEncode), "UTF-8"));
} catch (UnsupportedEncodingException e) {
    System.out.println("Error :" + e.getMessage());
}



Optional、Nashorn (java8 六)

此類是想消除null用的


※Optional

※of和ofNullable

final String str = null;
// of
// 下行java.lang.NullPointerException
Optional<String> optional1 = Optional.of(str);
System.out.println("opt1=" + optional1.get());
    
// ofNullable
Optional<String> optional2 = Optional.ofNullable(str);
// 下行java.util.NoSuchElementException: No value present
System.out.println("opt2=" + optional2.get());

※of:如果是null,不用再呼叫方法就會直接拋空指針例外

※ofNullable:如果是null,會回傳Optional.empty,如果再呼叫方法會拋NoSuchElementException 例外


※判斷

public class Test {
    public static void main(String[] args) {
        final String str = null;
    
        // if判斷
        Optional<String> optional = Optional.ofNullable(str);
        if (optional.isPresent()) {
            System.out.println(optional.get());
        } else {
            System.out.println(Optional.empty());// Optional[?] | Optional.empty
        }
    
        // 使用三元運算子判斷
        System.out.println(optional.isPresent() ? Optional.of(str) : Optional.empty());
    
        // 不用判斷
        System.out.println(optional.orElse("default value"));
    
        // 做完事再回傳
        String rtn = optional.orElseGet(() -> {
            System.out.println("do something");
            return Optional.empty().toString();
        });
        System.out.println("rtn=" + rtn);
    
        // 直接拋例外
        optional.orElseThrow(() -> new XxxException("null->exception"));
    }
}
    
class XxxException extends RuntimeException {
    public XxxException() {
        super();
    }
    
    public XxxException(String message) {
        super(message);
        System.out.println("message=" + message);
    }
}




※Nashorn

參考資料

和javascript溝通的工具jjs,也是java8新增的
在命令提示字元打上jjs -h可以看到說明

我在d槽新增一個叫jjsTest.js的檔案,內容如下:
let a = 1;
let b = 2;
print(a+b);



上圖可以看到有5個紅框,第一個紅框確定內容

第二個紅框出錯了,因為它不認識let關鍵字,因為這是es6才有的

第三個紅框加上--language=es6就認識let了

第四個紅框--後面加上要傳進去的參數,但--後面要有空格,所以出錯了

第五個紅框使用arguments.join將傳進去的參數用「, 」隔開了

離開時要打quit()



※test.java

ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine nashorn = sem.getEngineByName("nashorn");
String xxx = "Hello nashorn";
try {
    nashorn.eval("print('" + xxx + "')");
    System.out.println((Integer) nashorn.eval("10 + 2"));
} catch (ScriptException e) {
    System.out.println("message=" + e.getMessage());
}

※EngineByName的字串一定要是nashorn


※jjsTest.js

var arrayList = Java.type('java.util.ArrayList');
    
function lookList() {
    var list = new arrayList;
    list.add(1);
    list.add(2);
    print(list);
}
lookList();

※Java.type就類似import

※不能加泛型

※在命令提示字元打上jjs jjsTest.js可看到結果

2016年12月11日 星期日

Class、塞值 (反射一)

※Class取得方式

System.out.println("1.使用Object物件的getClass()");
Class<? extends BigDecimal> c1 = BigDecimal.ZERO.getClass();
System.out.println(c1 + System.getProperty("line.separator"));
    
System.out.println("2.使用.class");
Class<Integer> c2a = int.class;
Class<Integer> c2b = Integer.class;
System.out.println(c2a);
System.out.println(c2b + System.getProperty("line.separator"));
    
System.out.println("3.使用Class.forName()");
try {
    Class<?> c3 = Class.forName("java.util.List");
    System.out.println(c3 + System.getProperty("line.separator"));
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
    
System.out.println("4.Wrapper類別才有的TYPE");
Class<Long> c4 = Long.TYPE;
System.out.println(c4);

※結果:
1.使用Object物件的getClass()
class java.math.BigDecimal

2.使用.class
int
class java.lang.Integer

3.使用Class.forName()
interface java.util.List

4.Wrapper類別才有的TYPE
long

※第2種泛型裡只能是Wrapper類別,但都能分清楚

※第4種取得的是小寫的


※塞值的四種方式

package xxx.ooo;
    
public class JavaBean {
    private int id;
    private String name;
    
    // setter/getter...
}

※隨便寫一隻java bean


※四種方式
try {
    System.out.println("原本的方式");
    JavaBean jb1 = new JavaBean();
    jb1.setId(1);
    jb1.setName("George");
    System.out.println(jb1.getId());
    System.out.println(jb1.getName());
    
    System.out.println(System.getProperty("line.separator") + "Java8的lambda方式");
    Supplier<JavaBean> sup = () -> {
        return new JavaBean();
    };
    JavaBean jb2 = sup.get();
    jb2.setId(2);
    jb2.setName("Mary");
    System.out.println(jb2.getId());
    System.out.println(jb2.getName());
    
    System.out.println(System.getProperty("line.separator") + "Java8的引用方式");
    Supplier<JavaBean> supp = JavaBean::new;
    JavaBean jb3 = supp.get();
    jb3.setId(3);
    jb3.setName("借錢");
    System.out.println(jb3.getId());
    System.out.println(jb3.getName());
    
    System.out.println(System.getProperty("line.separator") + "反射的方式");
    Class<?> c3 = Class.forName("xxx.ooo.JavaBean");
    JavaBean jb4 = (JavaBean) c3.newInstance();
    jb4.setId(4);
    jb4.setName("免還");
    System.out.println(jb4.getId());
    System.out.println(jb4.getName());
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

※可以很順利的塞值,但反射的方式看不出來有用預設建構子,其實是有用到的,如在JavaBean增加一個建構子,如下:


public class JavaBean {
    private int id;
    private String name;
    
    public JavaBean(int id, String name) {
        this.id = id;
        this.name = name;
    }
    
    // setter/getter...
}

※此時預設建構子就會消失,這時反射的方式在編譯時不會報錯,執行時會報「java.lang.NoSuchMethodException: xxx.ooo.JavaBean.<init>()」的錯,所以大部分寫java bean時都會要求一定要有預設建構子

※雖然沒有預設建構子,但反射還是能做到塞值的功能,如下:


try {
    System.out.println("原本的方式");
    JavaBean jb1 = new JavaBean(1, "George");
    System.out.println(jb1.getId());
    System.out.println(jb1.getName());
    
    System.out.println(System.getProperty("line.separator") + "Java8的lambda方式");
    BiFunction<Integer, String, JavaBean> bf = (i, s) -> {
        return new JavaBean(i, s);
    };
    JavaBean jb2 = bf.apply(2, "Mary");
    System.out.println(jb2.getId());
    System.out.println(jb2.getName());
    
    System.out.println(System.getProperty("line.separator") + "Java8的引用方式");
    BiFunction<Integer, String, JavaBean> bif = JavaBean::new;
    JavaBean jb3 = bif.apply(3, "借錢");
    System.out.println(jb3.getId());
    System.out.println(jb3.getName());
    
    System.out.println(System.getProperty("line.separator") + "反射的方式");
    Class<?> c3 = Class.forName("xxx.ooo.JavaBean");
    // JavaBean jb4 = (JavaBean) c3.newInstance();
    // jb4.setId(4);
    // jb4.setName("免還");
    
    Constructor<?>[] conArray = c3.getConstructors();
    JavaBean jb4 = null;
    try {
        jb4 = (JavaBean) conArray[0].newInstance(4, "免還");
    } catch (IllegalArgumentException | InvocationTargetException e) {
        e.printStackTrace();
    }
    System.out.println(jb4.getId());
    System.out.println(jb4.getName());
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

※反射的方式要利用getConstructors()的方法就可以做到塞值



※不用 set 的塞值

這種方法可以在迴圈判斷方法名時加數字

Animal animal = new Animal();

Stream.of(animal.getClass().getDeclaredMethods()).forEach(m -> {
if (m.getName().equals("setName")) {
try {
m.invoke(animal, "monkey");
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
});
System.out.println(animal.getName());

2016年12月10日 星期六

package、集合 (java8 四)

Java8提供了一個java.util.function的package,有了這個就不用自己寫interface了,主要有以下四個:

介面名稱              方法名
Function<T, R>       R apply(T t)
Consumer<T>         void accept(T t)
Supplier<T>            T get()
Predicate<T>           boolean test(T t)
Function的T為參數,R為回傳值,看方法名就可知道

Function:(功能),像客製化的產品,什麼都可以,所以可接收參數也可回傳結果
Consumer:(消費者),消費者只負責接收產品,所以只接收參數,不回傳結果
Supplier:(提供者),提供者只負責生產,所以不接收參數,只回傳結果
Predicate:(正則)表達式,和 Function 一樣,但回傳布林

其他:
UnaryOperaor<T>:一個參數,回傳的類型和傳參類型一樣
BinaryOperator<T, T>:和 UnaryOperaor 一樣,只不過有兩個參數
※只要有 Operator 就表示傳參類型和回傳值的類型是一樣的

BiXxx:Bi 開頭的就是兩個參數的意思
IntXxx:參數為 int,類似的還有 LongXxx、DoubleXxx
ToIntXxx:回傳 int 的意思,類似的還有 ToLongXxx、ToDoubleXxx
IntToXxx:上兩個的合體,參數為 int,回傳 Xxx,類似的還有 LongToXxx、DoubleToXxx

※package

// Function
Function<String, Integer> funa = (i) -> {
    return Integer.parseInt(i) + 1;
};
System.out.println("Function:");
System.out.println(funa.apply("100"));
    
Function<String, Integer> funb = Integer::parseInt;
System.out.println(funb.apply("100") + 1);
    
// Consumer
Consumer<String> cona = (s) -> {
    System.out.println(s);
};
System.out.println(System.getProperty("line.separator") + "Consumer:");
cona.accept("Hello World!");
    
Consumer<String> conb = System.out::println;
conb.accept("Hello World!");
    
// Supplier
Supplier<Properties> supa = () -> {
    return System.getProperties();
};
System.out.println(System.getProperty("line.separator") + "Supplier:");
System.out.println(supa.get());
    
Supplier<Properties> supb = System::getProperties;
System.out.println(supb.get());
    
// Predicate
String str = "12345";
Predicate<String> prea = (regExp) -> {
    return str.matches(regExp);
};
System.out.println(System.getProperty("line.separator") + "Predicate:");
System.out.println(prea.test("\\d{5}"));
    
Predicate<String> preb = str::matches;
System.out.println(preb.test("\\d{5}"));
    
System.out.println();
// 有更多參數時,就要到API去找,如Bi有二的意思,表示傳兩個參數
BiFunction<String, String, String> bfuna = (s1, s2) -> {
    return "hello".replaceAll(s1, s2);
};
System.out.println(bfuna.apply("l", "_"));
    
BiFunction<String, String, String> bfunb = "hello"::replaceAll;
System.out.println(bfunb.apply("l", "_"));




※集合

※迭代 

java8的Iterable<T>多一個default的forEach(Consumer<? super T> action)方法

public class Java8Test {
    static List<Integer> l = new ArrayList<>();
    static {
        l.add(1);
        l.add(5);
        l.add(3);
        l.add(3);
        l.add(3);
    }
    
    public static void main(String[] args) {
        System.out.println("迭代");
        for (Integer i : l)
            System.out.println(i);
    
        System.out.println();
        l.forEach(i -> System.out.println(i));
    
        System.out.println();
        System.out.println("長度");
        System.out.println(l.size());
    
        Stream<Integer> stream = l.stream();
        System.out.println(stream.count());
    
        System.out.println();
        System.out.println("List");
        System.out.println(l);
        Stream<List<Integer>> s = Stream.of(l);
        s.forEach(System.out::println);
    }
}




※包含

static List<String> animal = new ArrayList<>();
static {
    animal.add("elephant");
    animal.add("dog");
    animal.add("pig");
    animal.add("rat");
    animal.add("tiger");
    animal.add("cat");
}
    
public static void main(String[] args) {
    Predicate<String> pred = (String str) -> {
        return str.contains("a");
    };
    animal.forEach(xxx -> {
        if (pred.test(xxx)) {
            System.out.println("have a is:" + xxx);
        }
    });
}

※將集合抓出來然後判斷正則,還可以寫一個公用的方法,如下:


public static void main(String[] args) {
    find(animal, (String str) -> {
        return str.contains("a");
    });
}
    
public static void find(List<String> data, Predicate<String> pred) {
    data.forEach(xxx -> {
        if (pred.test(xxx)) {
            System.out.println("have a is:" + xxx);
        }
    });
    
    // data.stream().filter(pred).forEach(System.out::println);
    
    // data.stream().filter(pred).collect(Collectors.toList()).forEach(System.out::println);
}

※注解是利用Stream,可以有更精簡的寫法,下一章會說明

方法引用、泛型 (java8 三)

lambda、方法引用都無法使用泛型方法,可以定義(因為可以覆寫),但無法用 lambda 和方法引用的寫法去調用

方法引用(Method Reference)的符號是「::」,如果方法體已經寫好了,就可以使用
它的功能和上一章的 lambda類似,但沒辦法加工,而 lambda 裡面還可以寫引用

※方法引用

import java.util.Arrays;
import java.io.PrintStream;
    
@FunctionalInterface
interface I1 {
    void fi(String s);
}
    
@FunctionalInterface
interface I2 {
    void fi(int... i);
}
    
@FunctionalInterface
interface I3 {
    public String fi(int p);
}
    
@FunctionalInterface
interface I4 {
    void fi(PrintStream out, String s);
}
    
public class Java8Test {
    public static void main(String[] args) {
        I1 i = (c) -> System.out.println(c);
        i.fi("lambda");
    
        I1 i1 = System.out::println;
        i1.fi("引用1");
    
        ((I1) System.out::println).fi("引用2");
    
        int[] iArray = new int[] { 3, 1, 52, 4 };
    
        System.out.println("before:" + Arrays.toString(iArray));
        // java7(含)之前的做法
        // Arrays.sort(iArray);
    
        // 使用標準的lambda
        I2 i2a = (x) -> Arrays.sort(x);
        i2a.fi(iArray);
    
        // 使用引用
        // I2 i2b = Arrays::sort;
        // i2b.fi(iArray);
        System.out.println("after:" + Arrays.toString(iArray));
    
        // 普通方法(和上面的System.out::println一樣的意思)
        String str = "12345";
        I3 i3a = (x) -> {
            return str.substring(x);
        };
        System.out.println(i3a.fi(2));
    
        I3 i3b = new String("12345")::substring;
        System.out.println(i3b.fi(2));
    
        // 參數是物件
        I4 i4a = (x, o) -> x.println(o);
        i4a.fi(System.out, "i4a");
    
        I4 i4b = PrintStream::println;
        i4b.fi(System.out, "i4b");
    }
}

※使用引用也是可以強制轉換後寫成一行

※靜態方法可以將「.」變成「::」,然後後面的括號都不寫

※普通方法先宣告好或用建構子寫在::的左邊

※要試無參數和無回傳值,可用 Map 的 clear


※引用加泛型

P表示參數;R表示回傳值

import java.util.Arrays;
import java.io.PrintStream;
    
@FunctionalInterface
interface I1<P> {
    void fi(P s);
}
    
@FunctionalInterface
interface I2<P> {
    void fi(P i);
}
    
@FunctionalInterface
interface I3<P, R> {
    public R fi(P p);
}
    
@FunctionalInterface
interface I4<P1, P2> {
    void fi(P1 p1, P2 p2);
}
    
public class Java8Test {
    public static void main(String[] args) {
        I1<String> i = (c) -> System.out.println(c);
        i.fi("lambda");
    
        I1<String> i1 = System.out::println;
        i1.fi("引用1");
    
        ((I1<String>) System.out::println).fi("引用2");
    
        int[] iArray = new int[] { 3, 1, 52, 4 };
    
        System.out.println("before:" + Arrays.toString(iArray));
        // java7(含)之前的做法
        // Arrays.sort(iArray);
    
        // 使用標準的lambda
        // I2<int[]> i2a = (x) -> Arrays.sort(x);
        // i2a.fi(iArray);
    
        // 使用引用
        I2<int[]> i2b = Arrays::sort;
        i2b.fi(iArray);
        System.out.println("after:" + Arrays.toString(iArray));
    
        // 普通方法(和上面的System.out::println一樣的意思)
        String str = "12345";
        I3<Integer, String> i3a = (x) -> {
            return new String("12345").substring(x);
        };
        System.out.println(i3a.fi(2));
    
        I3<Integer, String> i3b = str::substring;
        System.out.println(i3b.fi(2));
    
        // 參數是物件
        I4<PrintStream, String> i4a = (x, o) -> x.println(o);
        i4a.fi(System.out, "i4a");
    
        I4<PrintStream, String> i4b = PrintStream::println;
        i4b.fi(System.out, "i4b");
    }
}




※建構子

@FunctionalInterface
interface I1 {
    ConstructorTest fi();
}
    
@FunctionalInterface
interface I2 {
    ConstructorTest fi(String s);
}
    
class ConstructorTest {
    ConstructorTest() {
        System.out.println("無參");
    }
    
    ConstructorTest(String s) {
        System.out.println("有參" + s);
    }
}
    
public class Java8Test {
    public static void main(String[] args) {
        I1 i1a = () -> new ConstructorTest();
        i1a.fi();
    
        I1 i1b = ConstructorTest::new;
        i1b.fi();
    
        I2 i2a = (x) -> new ConstructorTest(x);
        i2a.fi("xxx");
    
        I2 i2b = ConstructorTest::new;
        i2b.fi("xxx");
    }
}




※建構子加泛型

@FunctionalInterface
interface I1<R extends ConstructorTest> {
    R fi();
}
    
@FunctionalInterface
interface I2<R extends ConstructorTest> {
    R fi(String s);
}
    
class ConstructorTest {
    ConstructorTest() {
        System.out.println("無參");
    }
    
    ConstructorTest(String s) {
        System.out.println("有參" + s);
    }
}
    
public class Java8Test {
    public static void main(String[] args) {
        I1<ConstructorTest> i1a = () -> new ConstructorTest();
        System.out.println(i1a.fi());
    
        I1<ConstructorTest> i1b = ConstructorTest::new;
        System.out.println(i1b.fi());
    
        I2<ConstructorTest> i2a = (x) -> new ConstructorTest(x);
        System.out.println(i2a.fi("xxx"));
    
        I2<ConstructorTest> i2b = ConstructorTest::new;
        System.out.println(i2b.fi("xxx"));
    }
}

※塞值可以參考 反射(一) 塞值的部分


※加泛型注意事項

interface 裡的 方法,不可以用泛型方法,否則使用時會編譯錯誤 Target method is generic,就不能用 lambda 了,只能用匿名類別了

例:
@FunctionalInterface
interface Animal {
    int get(Monkey m); // 如果在此宣告泛型方法,這裡可以編譯,但 main 方法裡的 a -> 1 會報錯
}

@Data
@AllArgsConstructor
class Monkey {
    private int id;
    private String name;

    public static void main(String[] args) {
        Animal b = a -> 1;
        b.get(new Monkey(999, "monkey"));
    }
}


※包含::方法名

總共有四種引用方式
1.建構子,很簡單,看了就會
2.靜態,一樣看了就會
3.因為靜態可以直接去「.」,不是靜態就只好用 new 的方式,然後一樣接「::」
4.不是靜態,但看起來像靜態的方式,這種方式,第一個參數一定要傳「::」左邊的物件才行


Function<Integer, String> i3b = new String("0123456789")::substring;
System.out.println(i3b.apply(4));
    
Function<char[], String> i3ba = String::copyValueOf;
System.out.println(i3ba.apply(new char[] { 'a', 'b', 'c', 'd', 'e' }));
    
BiFunction<String, Integer, String> i4 = String::substring;
System.out.println(i4.apply("0123456789", 4));
    
Supplier<J8Test> con1 = J8Test::new;
con1.get();
    
Function<String, J8Test> con2 = J8Test::new;
con2.apply("oo");

※想法要先寫引用再想方法名怎麼寫,會比較好理解

官方網站的例子比較好理解了吧!

※範例
@FunctionalInterface
public interface Ixxx {
    int method(Xxx x, String s, int i);
    
    public static void main(String[] args) {
        // 引用
        Ixxx x = Xxx::xxxMethod;
        System.out.println(x.method(new Xxx(9), "5", 1));
    
        // Lambda
        Ixxx o = (xxx, y, z) -> xxx.xxxMethod(y, z);
        System.out.println(o.method(new Xxx(9), "5", 1));
    }
}
--------------------
public class Xxx {
    private int a;
    public Xxx(int a){
        this.a = a;
    }
    
    public int xxxMethod(String s, int i){
        return this.a + Integer.parseInt(s) + i;
    }
}

※注意 Ixxx 的 method 方法和  Xxx 的 xxxMethod 裡的參數有何不同

※另外一種記法是以 Lambda 寫出來後,如上的 (xxx, y, z) -> xxx.xxxMethod(y, z)
第一個參數去點方法,而這個方法的參數是第二個之後的參數,且是有照順序的,此時可用方法引用

※1.8 有在 Integer、Long、Float、Double 增加 靜態的 sum、max、min,這些用在方法引用還不錯