2015年7月31日 星期五

Java作業練習01:學生成績排名

完成 Student 類別,依總分由大排到小,總分相同比英文,英文相同比數學,數學相同比名字,名字相同則亂數決定

Student 類別
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Student implements Comparable<Student> {

    String name;
    int eng;
    int math;
    int sum;

    public Student(String name, int eng, int math) {
        this.name = name;
        this.eng = eng;
        this.math = math;
        this.sum = eng + math;
    }

    @Override
    public String toString() {
        return name + " " + eng + " " + math + " " + sum;
    }

    @Override
    public int compareTo(Student o) {
        if (this.sum > o.sum) {
            return -1;
        } else if (this.sum == o.sum && this.eng > o.eng) {
            return -1;
        } else if (this.eng == o.eng && this.math > o.math) {
            return -1;
        } else if (this.math == o.math && this.name.compareTo(o.name)<0) { //名字字串比對
            return -1;
        } else if (this.name.compareTo(o.name)== 0){
            return (int) Math.random()*10;
        }return 1;
    }
}

StudentTest 主程式
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class StudentTest {

    public static void main(String[] args) {
       Set<Student> set =new TreeSet<>();
       set.add(new Student("Tom", 100, 90)); //190
       set.add(new Student("Amy", 95, 100)); //195
       set.add(new Student("Joe", 95, 95)); //190
       set.add(new Student("Lee", 100, 90)); //190
       
       //依總分由大排到小,總分相同比英文,英文相同比數學,數學相同比名字,名字相同則亂數決定 
       System.out.println("成績排行");
       int rank=0;
       for(Student stu:set){
           rank++;
           System.out.println(rank+". "+stu.toString());
       }

    }

}

顯示結果


code
Student
StudentTest

Java上課練習:Object類別與集合_自動排序物件的集合

TreeSet:自動排序物件的集合

新增一個 TreeSetTest 主程式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.*;

public class TreeSetTest {

    public static void main(String[] args) {
        //Set介面,不重複
        //TreeSet,不重複,有排序性
        Set<Integer> set = new TreeSet<>();//隨時可以將TreeSet更換為其他實作介面,只要是Set有的
        /*
         宣告 參考變數(set) 使用 介面(Set)而不是使用TreeSet,方便將來更換不同的集合,讓程式將來修改時更容易
         Set介面可實作的類別很多,TreeSet是其中一個,之後如果修改,可以將TreeSet修改為HashSet
         下面的程式碼都不需要異動
         Set<Integer>指泛型,只要是Integer的類型都可以加入
         */
        set.add(10);
        set.add(5);
        set.add(7);
        set.add(3);

        Set<String> set2 = new TreeSet<>();
        set2.add("C");
        set2.add("A");
        set2.add("F");
        set2.add("B");

        System.out.println(set);
        System.out.println(set2);
    }

}

顯示結果


若將 Animal物件放入TreeSet
//嘗試將 Animal 物件放入 TreeSet
        Animal a = new Dog();
        a.setName("小白");

        Animal b = new Cat();
        b.setName("小花");

        Animal c = new Bird();
        c.setName("小飛");

        Set<Animal> set3 = new TreeSet<>();
        set3.add(a);
        set3.add(b);
        set3.add(c);

若要將動物也使用TreeSet會出現錯誤,因為動物無法排序,
將 a 加入 TreeSet ,轉型失敗, a 所操作的 Dog 物件
無法轉成 java.lang.Comparable 型別
所以會出現下面的結果


TreeSet 只接受具有 Comparable 特性的物件
所以要在 Animal 插入介面 Comparable<Animal>,並且加入自定的規則
abstract public class Animal implements 有攻擊能力的, Comparable<Animal> { // 抽象類別

    @Override //定義Animal的大小判定,以下以字串名字長度為判斷標準
    public int compareTo(Animal o) {
        return this.name.length()-o.name.length(); 
        /*
        可將下面判斷方式簡化,若要由大排到小,將運算結果前面加上負號即可
        return -(this.name.length()-o.name.length());
        */
        
//        if (this.name.length() > o.name.length()) {
//            return 0;
//        } else if (this.name.length() > o.name.length()) {
//            return -1;
//        } else {
//            return 0;
//        }
        /*
        返回:負整數、零或正整數,根據此物件是小於、等於還是大於指定物件。
        */
    }

}

再次執行TreeSet
        //嘗試將 Animal 物件放入 TreeSet
        Animal a = new Dog();
        a.setName("小白A");

        Animal b = new Cat();
        b.setName("小花");

        Animal c = new Bird();
        c.setName("小飛");
        
        //判斷大小compareTo
//        System.out.println("");
//        int result = a.compareTo(b);
//        System.out.println("a.compareTo(b) = " + result);
//        result=b.compareTo(c);
//        System.out.println("b.compareTo(c) = "+result);

        Set<Animal> set3 = new TreeSet<>();
        set3.add(a);
        set3.add(b);
        set3.add(c);
        System.out.println(set3);
        /*
         若要將動物也使用TreeSet會出現錯誤,因為動物無法排序
         Dog cannot be cast to java.lang.Comparable
         將 a 加入 TreeSet ,轉型失敗, a 所操作的 Dog 物件 無法轉成 java.lang.Comparable 型別
         TreeSet 只接受具有 Comparable 特性的物件
         所以要在Animal插入介面Comparable<Animal>,並且加入自定的規則
         */
        /*
        當加入Comparable<Animal>後,WHY小飛還是沒有出現
        因為 b.compareTo(c) 的結果為 0,表示 b 與 c 相等,TreeSet 不重複的特性,所以 c(小飛) 不會放進 TreeSet 裡
        ( TreeSet 使用 compareTo() 判斷相等性 ,HashSet 使用 equals() 判斷相等性)
        */

顯示結果


為什麼 小飛 不在 TreeSet 裡 ?
因為 b.compareTo(c) 的結果為 0,表示 b 與 c 相等,TreeSet 不重複的特性,所以 c(小飛) 不會放進 TreeSet 裡
( TreeSet 使用 compareTo() 判斷相等性 ,HashSet 使用 equals() 判斷相等性)

最後整個 Animal 的關聯


code
Animal
TreeSetTest

Java上課練習:Object類別與集合_ArrayList 與 HashSet 集合

ArrayList 與 HashSet 集合

新增一個 HashSetTest 主程式

宣告參考變數使用介面,方便將來更換不同的集合,讓程式將來修改時更容易;
例如,LinkedList 也實作了 List 介面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.util.*; //java.unti套件裡所有的類別


public class HashSetTest {

    public static void main(String[] args) {
        
        Animal a=new Dog();
        a.setName("小白");
        
        Animal b=new Cat();
        b.setName("小花");
        
        Animal c=new Bird();
        c.setName("小飛");
        
        Animal d=new Dog();
        d.setName("小白");
        
        //List介面,有順序姓,可重複
        List<Animal> list=new ArrayList<>(); //隨時可以將ArrayList更換為其他實作介面,只要是List有的
        /*
        宣告 參考變數(list) 使用 介面(List)而不是使用ArrayList,方便將來更換不同的集合,讓程式將來修改時更容易
        List介面可實作的類別很多,ArrayList是其中一個,之後如果修改,可以將ArrayList修改為LinkedList
        下面的程式碼都不需要異動
        List<Animal>指泛型,只要是Animal類別的都可以加入
        */
        list.add(a);
        list.add(b);
        list.add(c);
        list.add(d);
        
        //Set介面,無順序姓,不重複
        Set set=new HashSet(); //同List說明
        set.add(a);
        set.add(b);
        set.add(c);
        set.add(d);
        /*
        若Animal沒有覆寫equals,set集合會出現兩個Dog小白
        因為 Object 的 equals() 使用 == 判斷相等,只有是同一個物件的情況下才會相等
        所以當沒有覆寫equals時,是兩個不同的Dog小白
        */
        
        System.out.println(list);
        System.out.println(set);
        //若要出現的訊息不是顯示雜訊碼,需要再Animal加入toString

    }

}

修改 AnimaltoString(),使其文字顯示為 物件類別.物件名稱=指定的名字
    @Override
    public String toString() {
        return this.getClass().getName() + "{name=" + name + '}';
        //顯示 物件類別.物件名稱=指定的名字
    }

顯示結果如下


如果 Animal 沒有覆寫 equals(),Set 集合會出現兩個 Dog小白
因為 Object 的 equals() 使用 == 判斷相等,只有同一個物件的情況下才會相等 ( 現在是兩個不同的小白物件 )


************************************************************************************
HashSet使用雜湊碼可以提高搜尋效率

************************************************************************************
Set 的應用
增加一個水果字串,並且使用HashSet(),將水果字串放入
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import java.util.*;

//Set的應用
public class HashSeDemo {

    public static void main(String[] args) {
        String[] fruit = {"蘋果", "香蕉", "鳳梨", "芭樂", "蘋果", "香蕉"};//建立一組水果字串
        Set set = new HashSet();//建立物件
        for (String s : fruit) { //將水果字串一個一個放入迴圈中判斷
            set.add(s); //將水果放入set中,刪除重複
        }
        System.out.println("一共有"+set.size()+"種水果");
        System.out.println(set);//列出水果
    }

}
顯示結果


code
Animal
HashSetTest
HashSetDemo

Java上課練習:Object類別與集合

12_Object類別與集合

根據練習10_抽象類別 與 介面 完成code



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
interface 有攻擊能力的 {
    void attack(); // 自動補上 public abstract
}

 interface 會飛的 {
    void fly();  // 自動補上 public abstract
}


abstract public class Animal implements 有攻擊能力的 { // 抽象類別
    private String name; //封裝,隱藏資訊>setter&getter
    abstract public void attack(); // 抽象方法

//setter&getter
    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }
    
    public void print(){
        System.out.println("我的類別是" + this.getClass().getName() + ",名字叫" + name);
    }
    
}


class Dog extends Animal {
    @Override
    public void attack() { // 實作
        System.out.println("用牙齒咬");
    }
}

class Cat extends Animal {
    @Override
    public void attack() { // 實作
        System.out.println("用貓爪");
    }
}

// Bird 繼承 Animal 實作 會飛的 介面
class Bird extends Animal implements 會飛的 {
    @Override
    public void attack() { // 實作
        System.out.println("用頭撞");
    }
    @Override
    public void fly() {
        System.out.println("飛起來");
    }
}

// Airplane 自動繼承 Object 實作 會飛的 介面
class Airplane implements 會飛的 {
    @Override
    public void fly() {
        System.out.println("飛機起飛");
    }
}

class Superman implements 有攻擊能力的, 會飛的 {
    @Override
    public void attack() {
        System.out.println("超人 雷射攻擊");
    }
    @Override 
    public void fly() {
        System.out.println("超人 超音速飛行");
    }
}

其中 print()是繼承Object,取得該類別的名稱 getClass().getName()
public void print(){
        System.out.println("我的類別是" + this.getClass().getName() + ",名字叫" + name);
    }

新增一個AnimailTest主程式執行
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class AnimalTest {

    public static void main(String[] args) {
        Animal a;
        a = new Dog();
        a.setName("小白");
        a.print();
        a.attack();
    }
}

顯示結果

AnimailTest主程式中加入Object 類別 toString()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AnimalTest {

    public static void main(String[] args) {
        Animal a;
        a = new Dog();
        a.setName("小白");
        a.print();
        a.attack();
        
        System.out.println("");
        System.out.println(a.getName()+"字串長度="+a.getName().length());
        
        System.out.println("");
        System.out.println(a.toString());//toString()從 Object 繼承
        System.out.println(a);//效果同上
        
        System.out.println("");
        System.out.println("基本資料="+a.toString());
        System.out.println("基本資料="+a);
               
    }

}

顯示結果


顯示結果中顯示的 Dog@19a32e0 為雜訊碼(hashcode),若要顯示正常字串,要在 Animal 類別中覆寫 toString( )

    @Override
    public String toString() {
        return "Animal{" + "name=" + name + '}';
    }

顯示結果

*************************************************************************************
物件的相等性
AnimailTest主程式中加入新的 Animal b=new Dog()
        //物件相等性
        System.out.println("");
        Animal b=new Dog();
        b.setName("小白");
        b.print();
        a.print();
        System.out.println("a==b is "+(a==b)); //兩個是否參考相同物件,是否共用同一個物件
        /*
        a新增一個物件Dog,b也新增一個物件Dog,所以a和b不是共用一個物件
        */
        System.out.println("a.equals(b) is "+a.equals(b)); //兩個物件,是否相等(比對欄位值)
        /*
        equals是比對Object內欄位是否 a==b,若沒有Override,則兩者沒有差異
        所以要覆寫Object的equals,至於要比對那些欄位才算兩個Object相等,由設計者決定
        例如,兩個員工是否相等,比對員工編號即可,不須比對電話、地址等欄位
        以此為例,在Animal中Override equals後,則a.equals(b)會判斷名字是否相同,若相同即回傳true
        若將b改為Cat,則a.equals(b),雖然名字一樣,但類別不一樣,所以回傳false
        */

== 兩個是否參考相同物件,是否共用同一個物件
equals 兩個物件,是否相等(比對欄位值)

equals是比對Object內欄位是否 a==b,若沒有Override,則兩者沒有差異
所以要覆寫Object的equals,至於要比對那些欄位才算兩個Object相等,由設計者決定
例如,兩個員工是否相等,比對員工編號即可,不須比對電話、地址等欄位
以此為例,在Animal中Override equals後,則a.equals(b)會判斷名字是否相同,若相同即回傳true
若將b改為Cat,則a.equals(b),雖然名字一樣,但類別不一樣,所以回傳false

Animal 類別中覆寫 equals( )
//通常覆寫equals會一併覆寫hashCode(雜訊碼)
    @Override
    public int hashCode() {
        int hash = 7;
        hash = 23 * hash + Objects.hashCode(this.name);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        //a的equals和b比對,obj=b
        if (obj == null) { //判斷obj是否有物件
            return false;
        }
        if (getClass() != obj.getClass()) { //判斷a物件類別和b的物件類別是否相同,以此例要判斷是否為Dog類別(a)
            return false;
        }
        final Animal other = (Animal) obj; //將obj轉換同a的物件(Animal)
        if (!Objects.equals(this.name, other.name)) { //判斷a的物件名字和b的物件名字是否相同
            return false;
        }
        return true;
    }
通常覆寫equals()會一併覆寫hashCode()(雜訊碼)

在執行AnimailTest主程式會顯示以下結果

code
Animal
AnimalTest

2015年7月30日 星期四

Java上課練習:物件的序列化與反序列化

物件的序列化與反序列化


物件必須實作 Serializable 介面 ( 介面中沒有任何抽象方法需要實作 )

將 Student 類別 加入 Serializable 介面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.Serializable;


public class Student implements Serializable{
    private String name;
    private int eng;
    private int math;

    public Student(String name) throws StudentException{
        if(name.length()<2){
            throw new StudentException("名字不能少於兩個字");
        }
        this.name = name;
    }

    public Student(String name, int eng, int math) {
        this(name);//呼叫另一個建構子
        this.eng = eng;
        this.math = math;
    }

    @Override
    public String toString() {
        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math + '}';
    }


    }

將 Student 物件 序列化,儲存檔案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class ObjectOutputStreamTest {

    public static void main(String[] args) {
        Student st=new Student("Tom",100,99);
        try(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("C:/Users/Administrator/Desktop/MyJava/student.obj"))){
            oos.writeObject(st);
            System.out.println("Student物件儲存成功");
        }catch(IOException e){
            System.out.println(e);
        }

    }

}

將 Student 物件 反序列化,讀取檔案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectInputStreamTest {

    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:/Users/Administrator/Desktop/MyJava/student.obj"))) {
            Student st = (Student) ois.readObject(); //Object轉型回Student
            System.out.println(st.toString());
        } catch (IOException e) {
            System.out.println(e);
        } catch (ClassNotFoundException e) {
            System.out.println(e);
        }

    }

}

若修改Student類別,新增 avg 欄位,計算平均,執行 ObjectInputStreamTest,會出現版號不一致的問題
注意:每個類別都有自己的版本編號
修改後的類別,編譯器自動產生的版本編號, 與先前序列化物件的版本編號不一致,導致反序列化失敗

如何避免版號不一致的情況? 在程式自行指定版號

修改Student類別,加入版號

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.io.Serializable;


public class Student implements Serializable{
    private static final long serialVersionUID = 1L; //自行指定版號
    private String name;
    private int eng;
    private int math;

    public Student(String name) throws StudentException{
        if(name.length()<2){
            throw new StudentException("名字不能少於兩個字");
        }
        this.name = name;
    }

    public Student(String name, int eng, int math) {
        this(name);//呼叫另一個建構子
        this.eng = eng;
        this.math = math;
    }

    @Override
    public String toString() {
        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math + '}';
    }
    }

刪除先前儲存的Student物件
重新執行序列化 ObjectOutputStreamTest,產生新的學生物件 (沒有平均成績)
此時修改學生類別,加入平均的程式碼

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.Serializable;


public class Student implements Serializable{ //實作序列化界面
    private static final long serialVersionUID = 1L; //自行指定版號
    private String name;
    private int eng;
    private int math;
    private float avg;
    
    public Student(String name) throws StudentException{
        if(name.length()<2){
            throw new StudentException("名字不能少於兩個字");
        }
        this.name = name;
    }

    public Student(String name, int eng, int math) {
        this(name);//呼叫另一個建構子
        this.eng = eng;
        this.math = math;
        this.avg=(eng+math)/2.0f;
    }

    @Override
    public String toString() {
        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math + ", avg=" + avg + '}';
//        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math  + '}';
    }
    }
執行反序列化 ObjectInputStreamTest,讀取先前儲存的學生物件
反序列化成功,但由於先前儲存的學生物件沒有平均,反序列化自動將 avg 設為 0

再度執行序列化 ObjectOutputStreamTest,現在儲存的學生有平均
執行反序列化 ObjectInputStreamTest,即可成功讀取平均成績

因平均不需要設定欄位,可以直接計算,所以使用 transient 宣告,並自定反序列化行為,在 Studnet 類別撰寫自定 readObject( ),反序列化物件時,系統會自動執行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;


public class Student implements Serializable{ //實作序列化界面
    private static final long serialVersionUID = 1L; //自行指定版號
    private String name;
    private int eng;
    private int math;
    transient private float avg;//宣告transient,不需要反序列化
    
    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException{
        stream.defaultReadObject();//執行反序列化
        this.avg=(eng+math)/2.0f;
    }

    public Student(String name) throws StudentException{
        if(name.length()<2){
            throw new StudentException("名字不能少於兩個字");
        }
        this.name = name;
    }

    public Student(String name, int eng, int math) {
        this(name);//呼叫另一個建構子
        this.eng = eng;
        this.math = math;
//        this.avg=(eng+math)/2.0f;
    }

    @Override
    public String toString() {
        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math + ", avg=" + avg + '}';
//        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math  + '}';
    }
    }