Java Tutorial

(slide ver)

https://hackmd.io/@speedcubing/java

You should read this slide along w/ this following files:

講師介紹

Contact info:

  • Discord: speedcubing
  • IG: speedcubing.top
  • Email: cubing@speedcubing.top
  • GitHub: TheSpeedCubing

總之以前在 成電 教過同樣的課。

Java Introduction

Java?

咖啡? 咖啡杯?

  • 簡單
  • 跨平台
  • 物件導向 (OOP)
  • 程式清晰易懂
  • 垃圾蒐集機制 (GC)

缺點?

程式字元數可能有點多

單字都很長?

類別名稱可以取成

InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState

這麼長?!

應用?

Web Backend -> Spring Web

Android Application -> Android Studio

Large Game Development -> Minecraft

Java如何運作?

JDK/JRE/JVM?

  • JDK
    Java Development Kit,
    包含編譯器、一些編譯工具、Library、JRE
    主要目的為開發、把程式碼編譯為bytecode

  • JRE
    Java Runtime Environment,
    Java的執行環境,有了這個就可以執行Java程式

  • JVM
    Java Virtual Machine,
    執行Java程式的時候, 會建立一個JVM,
    把byte code轉成machine code並且執行

編譯 執行

Java程式

JDK

bytecode

 

JRE

JVM

machine code

可以移植到其他有JRE的環境 ->

建立JVM

單獨一個.java編譯出.class

很多個.java可以編譯出.jar (一大包.class)

只有Java能給JVM跑?

Kotlin, Scala, Groovy...

JDK

bytecode

 

JRE

JVM

machine code

可以移植到其他有JRE的環境 ->

建立JVM

甚至連Python都有社群支援

開發環境

IDE

  • Intellij IDEA -> 首選

  • Eclipse -> 您還好嗎?

  • Visual Studio Code

不要再VS Code了 好嗎?

Text Editor

  • Notepad++  -> 這傢伙主張台獨
  • Notepad
  • Visual Studio Code
  • vi
  • nano
  • Microsoft Word, nah, not this type of "editor"

能修改檔案的都ok。

Package

重要但是暫時不會碰到

簡單講, 就是組織class/interface的結構,
本質上就是目錄(資料夾)。
妥善的存放class/interface在適當的package能保持組織性。

照這個範例, 我們的專案目錄(java/)有

 

java/
- example/
  - main/
    - Main.java
  - test/
    - Test.java
package example.test;

public class Test {

}

如果說想要在Test中使用不在同個package的Class

需要import <classpath>

 

package example.test;

import example.main.Main;

public class Test {
    // Main...
}

可以使用*來一次import一個package內所有的Class

 

package example.test;

import example.main.*;

public class Test {
    // Main...
}

但大家都亂取, 會不會衝突? 會不會找不到別人的程式?

會的。 假設有兩份example.test.Test.java

第二個會無法載入。

怎麼搞?

使用 倒反域名.projectname

如果有人在ttussc.com寫了一個叫做Service的專案

把程式碼都放在 com.ttussc.service 裡面即可

(就是資料夾com/ttussc/service/)

(package 請全部小寫, 求求你。)

com/
- ttussc/
  - service/
    - Main.java
    - utils/
      - Util.java

org/
- github/
  - paperspigot/
    - Main.java
    - utils/
      - Util.java
      - PushBasedHopper.java

看吧? 這樣兩個Main.java就不會衝突了。

Hello World

 

我們這裡先不討論外層的class, 先專注於Main Class中的內容

建立一個.java檔, 當做主要的(入口)。
我這裡檔名想取成Main.java
所以寫一個Main class

public class Main {
    
}

接下來寫程式入口方法。(main)
如果說, 你是直接執行一個.class, 裡面應該只會寫一個入口方法。

 

(如果你是一個jar, 裡面可能有多個入口方法, jar內部會有一個/META-INF/MANIFEST.MF file, 裡面有寫Main class究竟是哪一個。)

不是很重要

public class Main {
   public static void main(String[] args) {
      System.out.print("hello, world");  // 輸出字串"hello, world"
   }
}
  • public (之後會講)

  • static (之後會講)

  • void (無回傳值) (之後會講)

  • main(String[] args)
    有一個字串陣列參數 代表command line args。

編譯/執行

假設檔名叫做Main.java

 

1. 編譯成bytecode

javac Main.java

2. 執行在Main.class裡面的入口函式:

java Main

2. 執行某個.jar

java -jar xxx.jar

Command Line Args

java Main a b c

假設你執行Java程式時, 後面有附帶參數

 

或是

 
 
java -jar xxx.jar a b c

參數會在入口方法的String[]中。

public class Main {
   public static void main(String[] args) {
      for(String s : args) {
          System.out.println(s); // a b c
      }
   }
}

Basic introduction

of String

字串系統有字串池在維護
你永遠不知道你兩個內容一樣的字串不是同個物件, 使用==比較物件是沒意義的。(之後會講)

 

因此請使用.equals()

 

 

String s = "hello";
String s2 = "world";

boolean b = s.equals(s2);

System.out.println(b);

String Pool

建立字串

String s = "123";

 (可能會去字串池找也是"123"的字串物件)

 (強迫建立新字串物件, 沒必要)

String s = new String("456");

字串物件除了用變數, 也可以用" "表示。
所以你今天在路上, 不管看到
             或是 


s 跟 "abcdef" 都是物件。

 

都可以對他呼叫方法, 像是.equals()

String s = ...;
"abcdef"
String s = "hello";

"abcdef".equals(s);

s.equals("anotherString");

s.equals("s2");

字串長度: String#length()

字串取第i個自元: String#charAt(int)

String s = "hello";

int i = s.length(); // 5

char c = s.charAt(1); // 'e'

String Contactenation

如果是字串+字串, 會組成字串。

"abc" + 1

"abc1"
"abc" + "def" 

"abcdef"

如果是字串+基本類別:

"abc" + 1 + 1

"abc11"
"abc" + (1 + 1)

"abc2"

Basic I/O

System class

System class內有兩個field,
in out
分別是輸入/輸出流物件

輸出

System.out.print

有兩個方法可用:
System.out.print(obj); -> 輸出obj
System.out.println(obj); -> 輸出obj, 換行

引數可以是任何資料型別。

System.out.print("Hello ");
System.out.println("World");

"Hello World\n"

輸入

Scanner

建立一個Scanner物件, 內含很多種輸入方法。

Scanner sc = new Scanner(System.in);

sc.nextInt() // int
sc.nextByte() // byte
sc.nextLong() // long
sc.nextShort() // short
sc.nextBoolean() // boolean
sc.nextFloat() // float
sc.nextDouble() // double
sc.nextLine() // 輸入一整行的"字串"
sc.next() // 輸入"字串", 以空格區分

java.util.Scanner

輸入

緩衝Reader + 輸入流Reader

一次讀一整行, 執行速率較快。

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

String s = reader.nextLine();

不是很重要

來寫個Lab吧

嘗試輸入一行字串, 把它輸出出來。

使用Scanner與System.out.println

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String s = scanner.next();
        System.out.println(s);
    }
}

Syntax/Naming

除了風格問題,也會影響

可讀性、可維護性、可擴展性

請用心編寫優雅的程式。

Comments

雖然現在可能提倡 "程式碼即為註解"

意即: 無須註解, 程式碼清晰到能直接描述。

 

但是很難做到。

註解寫法:

// 這是一行註解

/*
  這是一堆註解
  這是一堆註解
  這是一堆註解
*/

建議: 請在//後面多一個空格。

建議: 請不要在/*或*/的同一行撰寫註解。

// 輸出Hello World
System.out.println("Hello World");

如果覺得在一行程式後面寫註解影響可讀性, 可以往上移

System.out.println("Hello World"); // 輸出Hello World

如果註解想要描述多行程式,

註解前一行, 註解的程式結尾請留空白

int i = 1;

// 輸出Hello World
System.out.print("Hello ");
System.out.println("World");

int j = 2;

Naming

Class, Interface: 大駝峰

public class NetworkManager { 
}

Method: 小駝峰

方法名如果有動詞, 應該要放一個單字

public static void run() {
}
public static void getName() {
}
public class User {
    int id;
    String name;
}

non static final var: 小駝峰

static final var: 大寫/底線

static final MAX_VALUE = 2147483647;

Declare Variables

告訴compiler你要用name去refer一個資料
寫法:

type name;

 

  • 不可使用關鍵字
  • 開頭只能用A-Z, a-z, _, $
  • 其他只能用A-Z, a-z, _, $, 0-9
  • 沒有長度上限

變數命名規則

(Java編譯期間自己建立的變數會以$開頭, 因此儘量不要使用$)

Three Types of Variables

  • 在class內, Method外, 叫做 "member variable" ("成員變數"), "field"
  • 在Method裡面的, 叫做local variable
  • 宣告Method中寫的, 叫做參數(parameter)
class Main {
    int i; // field
    
    public static int add(int a, int b) { // parameter
        int result = a + b; // local variable
        return result;
    }
}
  • member variable (field)
    如果沒有寫初始值, 會直接使用該原始型別的初始值。
    但這不是一個好的程式風格。

  • 初始值
    • Primitive Type: default value
    • Reference Type: null
  • local variable
    不會有初始值。
    存取未初始化的局部變數會有CE。

  • 初始值

    • Reference Type: not initialized
    • Primitive Type: not initialized
public class Main {
    
    int i; // 0
    Object o; // null  
    
    // you should always write the initial value.
    int j = 0;
    Object obj = null; 
    
    public static void main(String[] args) {
        int i; // not initialized
        Object o; // not initialized
    }
}

Primitive Data Type

Type Size(bit) Limit/Details Default Value (Field)
byte 8 -128 ~ 127 0
short 16 -32768 ~ 32767 0
int 32 -2147483648 ~ 2147483647 0
long 64 -2^63 ~ 2^63-1 0L
float 32 (1+8+23) (see IEEE754) 0.0F
double 64 (1+11+52) (see IEEE754) 0.0D
char 16 0 ~ 65535 \u0000
boolean 1 true / false false

byte, short, int, long 皆為 2's complement integer.

 

float, double 為 IEEE 754 floating point.

 

boolean 只有 true, false.

 

char 是 Unicode 字元.

 

Primitive Data Type只有值的概念, 沒有參考, 指標。

 

宣告Primitive變數的時候,

會使用一個約4bytes大小 + (符合該型別大小->不一定) 的記憶體空間。

int i; // 宣告i變數, 值等於初始值
i = 3; // 把i設為3
int i = 3;

範例:

數字寫法

整數

10000; // int
2147483650L; // long
2147483650; // invalid

int i = 10000; // 10000 int
int i2 = 2147483650L; // invalid
int i3 = 2147483650; // invalid

long l = 10000; // 10000 long
long l2 = 2147483650L; // 2147483650 long
long l2 = 2147483650; // invalid

如果整數L/l結尾, 則為long, 否則為int. (建議使用大寫)

整數

// number 26 in decimal
int decVal = 26;

// number 26 in octal
int octVal = 032;

// number 26 in hexadecimal
int hexVal = 0x1a;
int hexVal2 = 0x1A;

// number 26 in binary
int binVal = 0b11010;

使用不同數字系統來表示數值

浮點數

123.4 // double
1.234e2 // 科學記號
1.234f // float

如果以以F/f結尾, 則為float, 否則為double,

並且可選擇以D/d結尾. (建議使用大寫)
也可使用E/e來表示科學記號

其他範例

int j, k, l; // 可以一次宣告多個
int n = -30, o = 1000; // 可以一次宣告多個

char c = '\u2001';
char c2 = 'a';
char c3 = 492;
  
boolean b = true;
boolean b2 = false;

底線

可以在數字之間任意新增底線_, 除了:

數字頭尾

int i = _123; // invalid
int i2 = 1_23; // valid
int i3 = 123_; // invalid
int i4 = 0x_123; // invalid

數字頭尾

float f1 = 3_.1415F; // invalid
float f2 = 3._1415F; // invalid

F/L/D之前

插在radix前/中/後

int i4 = _0x52; // invalid
int i5 = 0_x52; // invalid
int i6 = 0x_52; // invalid
long l = 9999_L; // invalid

不是很重要

Type Casting

byte b = 1;
char c = 2;
short s = 3;
int i = 4;
long l = 5;
float f = 6;
double d = 7;

Implicit Casting

long l = i;
float f = i;

值不會受影響的都可以隱式轉型, 像是

int to long

int to float

byte b = 1;
char c = 2;
short s = 3;
int i = 4;
long l = 5;
float f = 6;
double d = 7;

Explicit Casting

int i = (int) l;
int i2 = (int) f;

值會受影響的, 要顯示轉型, 像是

long to int

float to int

byte b = 1;
char c = 2;
short s = 3;
int i = 4;
long l = 5;
float f = 6;
double d = 7;

轉為浮點數

整數都可以隱式轉為浮點數

float可以隱式轉為double

double要顯式轉為float

double d = f;
float f = (float) d;
byte b = 1;
char c = 2;
short s = 3;
int i = 4;
long l = 5;
float f = 6;
double d = 7;

轉為整數

浮點數都要顯式轉為整數

如果是一個較大的型別要轉為較小的型別,

需要顯式轉換

long i = (long) d;
float f = (float) d;

Operators

Assignment Operator

int i = 1;
i = i + 1;

把右邊的值assign到左邊的變數

對於類別來說, 讓左邊的變數參考右邊的物件

Object o = new Object();

Arithmetic Operators

+

-

*

/

%

加法 (也可用於字串相加)

減法

乘法

除法

取餘數 (remainder)

Unary Operators

+

-

++

--

!

表示正值 (不寫也是正值)

把一個expression"負"

數值+1

數值-1

反轉boolean

Unary Operators

int i = +1;
int j = 1;

int k = -2;
k = -k;
 
i++;
  
--j;

boolean b = true;
boolean c = !b;

Unary Operators

int i = 10, j=10;
System.out.println(i++); // 10
System.out.println(++j); // 11
System.out.println(i); // 11
System.out.println(j); // 11

i++ 等同於 i+=1 , return the original value(回傳原始值)
++i 等同於 i+=1 , return the updated value(回傳原始值+1)

Equality and Relational Operators

==

!=

>

<

>=

<=

(works for boolean aswell, but you shouldn't do b1 == b2,

use conditional operator instead ...)

Conditional Operators

&&

||

(for boolean operand)

boolean t = true;
boolean f = false;
boolean b1 = t && f; // false
boolean b2 = t || f; // true

Conditional Operators

在 || , 如果其中一個成立, 後面的邏輯皆不會判斷

public class Main {
   public static void main(String[] args) {
       if(a() || b()) {
           System.out.println("hello, world");
       }
   }
       
   public static boolean a() {
       System.out.println("a");
       return true;
   }
       
   public static boolean b() {
       System.out.println("b"); // 不會執行
       return true;
   }
}

Bitwise and Bit Shift Operators

&

|

^

~

<<

>>

>>>

Bitwise AND

Bitwise OR

Bitwise XOR (exclusive OR)

Bitwise NOT

Bitwise Left Shift

Bitwise Right Shift (會考慮正負號的bit)

Bitwise Right Shift

Priority

Unary Operators ++ -- + - !

Type Casting

Arithmetic Operators * / %

Arithmetic Operators + -

Relational Operators <  <=  > >=

Equality Operators == !=

AND &&

OR ||

Assignment Operator = *= /= %= += -=

同級的, 先後會以左到右

說實在的, 還是建議多打一點括號沒關係,

程式可讀性比較重要, 不要太依賴先後順序

x + y / 100 // 不建議
x + (y / 100) // 建議
還有, 在運算符之間打一點空白,
是個好習慣。
// 不建議
int i=x+(y/100);
if(i>0){
    // ...
}

// 建議
int i = x + (y / 100);
if (i > 0) {
    // ...
}

Expression

是一個以變數, 運算子, method invocations組成的

int cadence = 0;
anArray[0] = 100;
System.out.println("Element 1 at index 0: " + anArray[0]);

int result = 1 + 2; // 這裡有兩個expression !
if (value1 == value2)

System.out.println("value1 == value2");

int length = "Hello".length(); // 這裡有兩個expression !

Statements

很像是自然語言的一個句子。
一個statement夠成一個完整的執行單元。

aValue = 8933.234;

aValue++;

System.out.println("Hello World!");

new Object();

Blocks

是一個內有{0,}個statement的statement

{
    System.out.println("Hello World!");
    int i = 0;
}

區塊內變數名不能重複

// 錯誤
{
   int a;
   {
       int a;
   }
}

if, else

顧名思義,if 就是如果,就

if (boolean) 
  //statement

範例:

int a = 1;
if (a == 1)
   System.out.print("hello world");

前篇說過, Block也算是一個statement

int a = 1;
if (a == 1) {
   System.out.print("hello world");
   System.out.print("hello world!!!");
}

如果要在 if 後面在新增更多種情況可以用 else if

int a = 5487;
if (a > 10000) {
    System.out.print("too expensive");
} else if(a > 5000) {
    System.out.print("deal"); // 會執行這行
}

if 或是 else if 都不成立, 可以接 else

int a = 3000;
if (a > 10000) {
    System.out.print("too expensive");
} else if (a > 5000) {
    System.out.print("deal"); 
} else {
    System.out.print("cheapest is the dearest"); // 會執行這行
}

不能在非Block的敘述內宣告變數。
 

if (a > 10000)
    int i = 1; // not allowed

if (a > 10000)
    Object o = new Object(); // not allowed

一律不建議省略大括號

int a = 1;
if (a == 1)
    System.out.print("Hello World");
System.out.println("End of this section");
int a = 1;
if (a == 1) {
    System.out.print("Hello World");
}
System.out.println("End of this section");

switch, case, yield

  • 資料可以是: byte, short, int, char, String, enum
  • 只要case符合, 就開始執行該case下寫的所有statement, 直到break
  • 如果想要or的效果, 放兩行case即可。
  • default是最後都不符合的case

範例:

int i = 1;
switch (i) {
    case 0:
        System.out.println("0");
    case 1:
        System.out.println("1");
    case 2:
    case 3:
        System.out.println("2 3");
    default:
        System.out.println("default");
}
1
2 3
default

可以拿來作成類似if else的形式(每個casebreak)

int i = 1;
switch (i) {
  case 0:
    System.out.println("0");
    break;
  case 1:
    System.out.println("1"); // 1
    break;
  case 2:
  case 3:
    System.out.println("2 or 3");
    break;
  default:
    System.out.println("default");
}
1

到 Java12+ , 可以使用 ->

  • -> :  不能混用
  • -> 後寫一個statement
int i = 1;
switch (i) {
  case 0 ->
    System.out.println("0");
  case 1 ->
    System.out.println("1");
  case 2, 3 ->
    System.out.println("2 or 3");
  default ->
    System.out.println("default");
}
1

switch 可以回傳值

int i = 1;
String result;
switch(i) {
    case 0:
        result = "zero";
        break;
    case 1:
        result = "one";
        break;
    default:
        result = "not-sure";
}
int i = 1;
String result = switch(i) {
    case 0 -> "zero";
    case 1 -> "one";
    default -> "not-sure";
};

可寫成

Enhanced Switch

如果你除了return值, 還想要做一些其他的敘述,
就可以用yield

int i = 1;
String result = switch(i) {
    case 0:
        yield "zero";
    case 1:
        yield "one";
    default:
        System.out.println("why?");
        yield "not-sure";
};
int i = 1;
String result = switch(i) {
    case 0 ->
        yield "zero";
    case 1 ->
        yield "one";
    default -> {
        System.out.println("why?");
        yield "not-sure";
    }
};

Array

注意, 宣告變數只是表示該變數的型別

不代表有建立Array, 跟其他變數宣告一樣。

int[] arr;
int arr2[]; // 不建議

宣告Array變數

建立一個有10個int的陣列,

並assign到arr變數

arr = new int[10];

建立Array

int[] arr = new int[3];
// 此時arr = {0,0,0}

String[] arr2 = new String[3];
// 此時arr2 = {null,null,null}

建立Array

如果是基本型別, 內值會是初始值

如果是參考型別, 內值會是null

int[] arr = {1, 2, 3, 4, 5}; 
String names = {"Olympic", "Titanic", "Britannic"};

arr = {6, 7, 8, 9, 10}; // not allowed

初始值建立Array

{}只有在初始值時能使用

請養成在,後留space的好習慣。

int[] arr = new int[4];

//設值

// 把arr的第一項設為122
// {122, 0, 0, 0}
arr[0] = 122; 

// 把arr的第一項設為97
// {122, 97, 0, 0}
arr[1] = 97;

// 取值
int k = arr[0];
System.out.println(k); // 122

取值, 設值

相信大家都知道C的Array Index是從0開始, Java也是。

值的記憶體位置會連續

int[] arr = new int[4];

System.out.println(arr.length); // 4

陣列長度

arr.length
int[][] arr;
int[][] arr2 = new int[3][3];
int[][] arr3 = {{1, 2, 3}, {4, 5, 6}};

System.out.println(arr3.length); // 2
System.out.println(arr3[0].length); // 3

多維Array

while

如果敘述成立->執行, 直到條件不成立為止

while (boolean)
    // statement
int i = 0;
while (i < 5) {
    System.out.print(i + " ");
    i++;
}
0 1 2 3 4

範例

int i = 0;
while (i < 5) {
    System.out.print(i + " ");
    i++;
}
0 1 2 3 4
int[] arr = {0, 1, 2, 3, 4};
int i = 0;
while (i < arr.length) {
    System.out.println(arr[i]);
    i++;
}
0 1 2 3 4

do while

執行->如果敘述成立->執行->.., 直到條件不成立為止

do {
    // statements
} while (boolean);
int i = 5;
do {
    System.out.print(i + " ");
    i--;
} while (i > 0);
5 4 3 2 1

for

 

直接看:

for (初始化; boolean; 每完成一次後執行的動作) {
    ...
}

直接看:

for (初始化; boolean; 每完成一次後執行的動作)
	// statement
for (initialization; termination; increment) 
    // statement

一樣, 請養成在;後面留space的好習慣。

範例

for (int i = 0; i < 5; i++) {
     System.out.println(i);
}
int[] arr = {0, 1, 2, 3, 4};
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}
0 1 2 3 4
0 1 2 3 4

初始化的變數離開for就沒了。

for 小括號內的三個部分不一定要寫

int i = 0;
for (; i < 5; i++) {
     System.out.println(i);
}
// 0 1 2 3 4
int i = 0;
for (; i < 5;) {
    System.out.println(arr[i]);
    i++;
}

如果寫到這樣, 不如去用 while

無限執行的迴圈

for (;;) {
    System.out.println("infinity !!!");
}
while (true) {
    System.out.println("infinity !!!");
}

個人覺得 while 好過 for

你覺得只是為了迭代陣列

要宣告i變數, 設立終止條件, 還要i++ 很麻煩?

int[] arr = {0, 1, 2, 3, 4};
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

Enhanced Switch

for (DataType element : array) {
    // do something with the element
}
int arr[5] = {0, 1, 2, 3, 4};
for (int e : arr) {
    System.out.println(e);
}
int[] arr = {0, 1, 2, 3, 4};
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

對比起來, 是不是好讀很多?

普通寫法

使用Enhanced for

let's write some... codes?

ok so, you should know how to

  • read input from the standard input stream
  • use String
  • use the for loop

2

只能乖乖使用for loop

-

________________

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String s = scanner.next();
        for(int i = 0; i < s.length(); i += 2) {
            System.out.println(s.charAt(i));
        }
    }
}

Class

我想看看一個解釋...

假設你在寫一個程式

你有很多二維的點座標

所以你寫了一堆

int x1, y1, x2, y2, x3, y3;

可以說是物件藍圖

class內部的non-static-member決定了物件的樣子。

物件  是類別實例

宣告Class

class MyClass { // class body, not-a-block
    /*
      這裡可以宣告
      - 成員變數(member variable) (field)
      - 建構子(constructor)
      - 方法(method)
      這些都是class的成員。
    */
}

前面寫的public static void main(String[] args)

也是屬於你前面寫的Main class的成員。

宣告成員變數(Field)

class Point {
    public int x;
    public int y;
}

由三個部分組成.

  • {0,} 個修飾符, 如access modifiers
  • Data Type
  • Name

基於封裝精神, 把Field設為private是很常見的行為,
只寫有需要調用的方法, 像是getter, setter會使程式碼更好維護。

Class應該只能公開必須公開的成員。

//---------- in Point.java ----------
class Point {
    private int x;
    private int y;
    
    //getter & setter
    public int getX() {
        return x;
    }
    
    public int getY() {
        return y;
    }
    
    public void setX(int newVal) {
        x = newVal;
    }
    
    public void setY(int newVal) {
        y = newVal;
    }
}
//---------- in Main.java ----------
public class Main {
    public static void main(String[] args) {
        Point p = new Point();
        p.setX(100);
        p.setY(200);
        
        System.out.println(p.getX() + " " + p.getY()); // 100 200 
    }
}

因此這裡把Field設為private

並且只公開一些必要的方法

Object

我們有class, 也就是類別(物件藍圖)

那要怎麼建立物件?

new ClassName()

如下面的範例,我建立了一個"Point"的類別
並且用new建立Point object

class Point {
    public int x;
    public int y;
}

public class Main {
    public static void main(String[] args) {
        new Point(); //建立了object
    }
}

Point

Point

p1

Point

Point

p2

這個孤兒之後會被回收

new Point();
Point p1 = new Point();
Point p2 = new Point();

stack

ref

NOT BE USED

比較物件

a == b

比較兩者是不是同一個物件

a.equals(b)

如果類別有@Override equals(Object o),

那就是用他寫的方法, 否則預設是==

像是String就是

Objects.equals(a, b)

等同於

(a == b) || (a != null && a.equals(b))

適用於: 想要用equals(), 但a可能是null的時候

使用物件內的Field

在class內使用class的field

fieldName

可以直接用名子存取

在class外使用class的field

objectReference.fieldName

範例

class Point {
    public int x;
    public int y;

    public void print() {
       // 可以直接用fieldName
        System.out.println("(" + x + ", " + y + ")"); 
    }
}

public class Main {
    public static void main(String[] args) {
        Point p1 = new Point();
        
        // objectReference.fieldname
        p1.x = 100;
        p1.y = 200;
        int x = p1.x;
        int y = p1.y;
        
        System.out.println("(" + x + ", " + y + ")");
    }
}

參考

(Primitive Type, Object)

(物件)變數其實一個參考, 它會參考(refer to, ->) 物件。

很像是指標指向物件的意思。

 

但口頭上不會一直說p參考某物件

通常直接說p是物件就好了

Point p = new Point(); // p 參考 new出來的物件

System.out.println(p.x + " " + p.y); // 0 0

Point p2 = p; // p2 參考 p 參考的物件

p2.x = 200; // 把 p2 參考的物件的x改掉

System.out.println(p.x + " " + p.y); // 200 0 
Point p = new Point(); // p 參考 new出來的物件

System.out.println(p.x + " " + p.y); // 0 0

Point p2 = p; // p2 參考 p 參考的物件

p2.x = 200; // 把 p2 參考的物件的x改掉

System.out.println(p.x + " " + p.y); // 200 0 

Point

p

Point

p2

Point p = new Point();
Point p2 = p;

stack

基本類型因為只有值的概念

所以不會改變原本的值

 

int i = 1; // i 值是 1

System.out.println(i); // 1

int j = i; // j 值是 i 的值

j = 30; // j 值是 30

System.out.println(i); // 1

Access Modifiers

default private  protected public 
same class
same package subclass
same package non-subclass
diff package subclass
diff package
non-subclass

N

N

N

N

N

N

N

Y

Y

Y

Y

Y

Y

Y

Y

Y

Y

Y

Y

Y

簡單來講

  • public 給任何地方使用
  • private 只給自己class
  • protected 只給自己/自己的子class, 同package
  • default 只給自己, 同package

Method

modifiers return-type method-name(parameter-list) { // method body
    
}

宣告Method

像是之前寫過的入口方法

modifiers return-type method-name(parameter-list) {
   
}

public static void main(String[] args) {
   
}

void代表無回傳值

參數名不可重複

public static void f(int i, double i) {

}

// not allowed

如果無parameter, 空白即可

public static void printHello() {
    System.out.println("Hello World!");
}

Method Signature

Method Name & Parameter Types 用來唯一標示一個方法


一個class內不能有同樣Method-Signature的方法。

public static int sum(int i, int j) {
    return i + j;
}

範例

sum(int, int)

method signature:

可知, return-type / modifier不同

不代表Method Signature不同

private long sum(int i, int j) {
    return i + j;
}

範例

sum(int, int)

method signature:

public static int sum(int i, int j) {
    return i + j;
}

Overloading

如果有多個方法使用同個名子, 但是Method Signature不同, 即為Overloading。

public class Printer {
    public static void print(int i) {
        // ...
    }
    public static void print(double f) {
        // ...
    }
    public static void print(String s) {
        // ...
    }
}

註: 請妥善使用, 否則程式碼可讀性有機會變差。

VarArgs

如果最後一個Parameter是Array
或是只有一個Array Parameter
可以用Type... 代替

public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        double d = 1.0;
    
        f(d, arr);
        // f(d, 1, 2, 3, 4, 5); // not allowed
        
        g(d, arr);
        g(d, 1, 2, 3, 4, 5);
    }
    
    static void f(double d, int[] arr) {
        //...
    }
    
    static void g(double d, int... arr) {
        //...
    }
}

這很常見嗎?

public static void printf(String format, Object... args) {
    // ...
}

有的, 像是printf

public static void printf(String format, Object... args) {
    // ...
}
printf("%d %d\n", 100, 200);

使用物件內的方法

在class內使用class的method

methodName(argumentList);

可以直接用名子存取

在class外使用class的method

objectReference.methodName(argumentList);
class Point {
    public int x;
    public int y;
    
    public void set(int nextX, int nextY) {
        x = nextX;
        y = nextY;
    }
    
    public void setAndPrint(int nextX, int nextY) {
        set(nextX, nextY); // methodName(argumentList);
        print(); // methodName(argumentList);
    }
    
    public void print() {
        System.out.println("(" + x + ", " + y + ")");
    }
}

public class Main {
    public static void main(String[] args) {
        Point p = new Point();
        
        // objectReference.methodName(argumentList);
        p.print(); // (0, 0)
        p.set(300,400);
        p.print(); // (300, 400)
        
        p.setAndPrint(500, 600) // (500, 600)
    } 
}

傳入Primitive Data Type Arguments

傳入參數時是被passed by value
也就是一個value的copy

public class PassPrimitiveByValue {

    public static void main(String[] args) {
           
        int x = 3;
           
        // invoke passMethod() with 
        // x as argument
        passMethod(x);
           
        // print x to see if its 
        // value has changed
        System.out.println(x); // 3
           
    }
        
    // change parameter in passMethod()
    public static void passMethod(int p) {
        p = 10;
    }
}

傳入Reference Data Type Arguments

參考類型, 也是passed by value
參數會直接是參考的copy

class IntWrapper {
    int i;
}
public class PassReferenceByValue {

    public static void main(String[] args) {
           
        IntWrapper x = new IntWrapper();
        intWrapper.i = 3;
        // invoke passMethod() with 
        // x as argument
        passMethod(x);
           
        // print x to see if its 
        // value has changed
        System.out.println(x.i); // 10
           
    }
        
    // change parameter in passMethod()
    public static void passMethod(IntWrapper p) {
        p.i = 10; // 此時 p 跟 x 參考的物件一樣
    }
}

Point

x

IntWrapper

p

IntWrapper x = new IntWrapper();
passMethod(x);
// ...
void passMethod(IntWrapper p) {

stack

passMethod start

passMethod end

傳入Reference Data Type Arguments

但還是不能直接更改原本的參考變數x

class IntWrapper {
    int i;
}
public class PassReferenceByValue {

    public static void main(String[] args) {
           
        IntWrapper x = new IntWrapper();
        intWrapper.i = 3;
        // invoke passMethod() with 
        // x as argument
        passMethod(x);
           
        // print x to see if its 
        // value has changed
        System.out.println(x.i); // 3
           
    }
        
    // change parameter in passMethod()
    public static void passMethod(IntWrapper p) {
        p = new IntWrapper(); // 無法改變 x 的參考
    }
}

Point

x

IntWrapper

p

IntWrapper x = new IntWrapper();
passMethod(x);
// ...
void passMethod(IntWrapper p) {

Point

IntWrapper

p = new IntWrapper();

x參考的IntWrapper還是不變。

stack

passMethod start

passMethod end

Constructor

Constructor 不屬於 Method !

一個class會有constructor, 在建立object時被呼叫。

  • 必須與class同名
  • 沒有return-type
  • 可以寫多個constructor, 但參數不能一樣

如果class沒有任何constructor,
implict constructor就會存在。無參數,無body

class Point {
    public int x;
    public int y;
}
class Point {
    public int x;
    public int y;
    
    // implict constructor
    public Point() { 
    }
}

it's actually...

我們來寫一些constructor...

class Point {
    public int x;
    public int y;
    
    public Point() {
        x = -1;
        y = -1;
    }
    
    public Point(int startX, int startY) {
        x = startX;
        y = startY;
    }
}

public class Main {
    public static void main(String[] args) {
        Point p = new Point();
        System.out.println(p.x + " " + p.y); // -1 -1
        
        Point p = new Point(100, 200);
        System.out.println(p.x + " " + p.y); // 100 200
    }
}

this

this指的是目前的物件

this不能放在static方法中 (之後會講)

為什麼我們在這個class裡面了, 還要用目前的物件?

class Point {
    public int x;
    public int y;
    
    public Point(int startX, int startY) {
        // 明明這裡可以直接存取到目前物件的成員?
        x = startX;
        y = startY;
    }
}

public class Main {
    public static void main(String[] args) {
        Point p = new Point(100, 200);
    }
}

有了this, 你可以這樣寫

class Point {
    public int x;
    public int y;
    
    public Point(int x, int y) {
        /*
          this就是指現在這個物件
          也就是 剛剛new出來的物件
        */
        this.x = x; 
        this.y = y; 
    }
    
    public void print() {
        System.out.println(this.x + " " + this.y);
    }
}

public class Main {
    public static void main(String[] args) {
        Point p = new Point(100, 200);
        p.print();
    }
}

或是說想使用這個物件, 像是用於呼叫方法的參數

class Point {
    public int x;
    public int y;
    
    public Point(int x, int y) {
        this.x = x; 
        this.y = y; 
    }
    
    public void reset() {
        /*
          this就是指現在這個物件
          也就是 剛剛new出來的物件
        */
        OtherClass.reset(this);
    }
}

class OtherClass {
    public static void reset(Point p) {
        p.x = 0;
        p.y = 0;
    }
}

public class Main {
    public static void main(String[] args) {
        Point p = new Point(100, 200);
        p.reset();
        p.print();
    }
}

在Constructor使用this

你可以在呼叫建構子時, 呼叫另一個建構子。
這叫做"explicit constructor invocation"

class Point {
    public int x;
    public int y;
 
    //不如直接呼叫下面的建構子, 更優雅。
    public Point() {
        /*
        this.x = -1;
        this.y = -1;
        */
        this(-1, -1);
    }
    
    public Point(int x, int x) {
        this.x = x;
        this.y = y;
    }
}

public class Main {
    public static void main(String[] args) {
        Point p1 = new Point();
        System.out.println(p1.x + " " + p1.y); // -1 -1
        
        Point p2 = new Point(100, 200);
        System.out.println(p2.x + " " + p2.y); // 100 200
    }
}
class Point {
    private int x;
    private int y;
    
    public Point(int x, int x) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() {
        return this.x;
    }
    
    public void setX(int x) {
        this.x = x;
    } 
    
    public int getY() {
        return this.x;
    }
    
    public void setY(int y) {
        this.y = y;
    }
}

最後, 把field 設為private

只把我要提供的四個方法public

class Circle {
    private Point p;
    private int y;
    
    public Circle(int x, int y, int r) {
        this.p = new Point(x, y);
        this.r = r;
    }
    
    public int getX() {
        return p.x;
    }
    
    public void setX(int x) {
        p.setX(x);
    } 
    
    public int getY() {
        return this.x;
    }
    
    public void setY(int y) {
        this.y = y;
    }
    
    public int getR() {
        return this.r;
    }
    
    public void setR(int r) {
        this.r = r;
    }
}
class Rectangle {
    private Point p1;
    private Point p2;
    
    public Rectangle(Point p1, Point p2) {
        this.p1 = p1;
        this.p2 = p2;
    }
    
    public Point getP1() {
        return p1;
    }
    
    public Point getP2() {
        return p2;
    } 
    
    public void setP1() {
        this.p1 = p1;
    }
    
    public void setP2() {
        this.p2 = p2;
    } 
}

Static

static field

當你建立物件的時候, 像是之前的Point
每個Point物件都有自己的x, y field的值。

有時候, 你希望每個物件都共享同一個field
此時, 把Field設為static即可。

也可稱為Class Variable,

因為對於一個static field而言,

不管class有多少個實例, 他永遠只有一個值,

那就是與class有關連, 與物件沒有關聯。

因此, 不需要物件就可以存取static field。
non static field反之。

Example: 每個Point共享一個pointCount值

class Point {
    public static int pointCount = 0;
    private int x;
    private int y;
    // ...
}
class Point {
    public static int pointCount = 0;
    
    private int x;
    private int y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
        // 在class內可以直接使用staticFieldName
        pointCount++;
    }
    
    void something() {
        int pointCount = -1;
        
        // 區域變數衝突
        int count = Point.pointCount;
    }
    
    // ...
}
public class Main {
    public static void main(String[] args) {
        // 正常在Class外使用
        int count = Point.pointCount; 
        
        // 強烈不建議
        Point p = new Point();
        int count = p.pointCount;  
    }
}
Class.staticFieldName
staticFieldName
objectReference.staticFieldName
Class.staticFieldName

static method

不需要物件就可以呼叫static method。

內部不能存取this


non static method反之。

class Test {
    public static int k = 3;
    
    public static void print() {
        System.out.println("Hello");
    }
    
    public static void test() {
        print(); // 在Class內使用
    }
}
public class Main {
    public static void main(String[] args) {
        Test.print(); // 在Class外使用
        
        Test t = new Test();
        t.print(); // 強烈不建議    
    }
}
ClassName.methodName(args)
methodName(args)
objectReference.methodName(args)

static method 內 不能呼叫non-static方法, 不能寫this

non-static method 內 能呼叫static方法

Constant

static final 用於定義常數。

static final double PI = 3.141592653589793;
static final int MAX_VALUE = 2147483647;

Extends

如果
Child extends Parent

 

此時

Child 會有 Parent 的成員。
Parent Child 的"superclass" (父類別)
Child 為 Parent 的"subclass" (子類別)


一個class只能同時繼承一個class

但是可以A繼承B , B繼承C, 這樣C會有A,B的成員

Constructor

class Printer {
    public Printer() {
        System.out.println("init Printer");
    }
    public void print(String s) {
        System.out.println(s);
    }
}

class MyPrinter extends Printer {
    public MyPrinter() {
        // super();
        System.out.println("init MyPrinter");
    }
    public void myPrint(String s) {
        System.out.println("[A] " + s);
    }
}
public class Main {
    public static void main(String[] args){
        // child class
        MyPrinter p = new MyPrinter();
        // init Printer
        // init MyPrinter
        p.myPrint("hello"); // [A] hello
        p.print("hello"); // hello
        
        // parent class
        Printer pa = new Printer();
        // init Printer
        pa.print("hello"); // hello
    }
 }
public class Main {
    public static void main(String[] args){
        MyPrinter p = new MyPrinter();
        Printer p2 = new Printer();
        
        // 這樣也可以, 但是會沒辦法用MyPrinter內的方法
        // 要先轉成MyPrinter
        Printer p3 = new MyPrinter();
        // p3.print(); // not allowed
    }
 }

java.lang.Object

Object class 是整個Class繼承樹的根,

也就是說

每個類別都會是Object的子類別。

 

如果你的class沒有繼承任何類別,

那該class會繼承Object

因此每個物件都可以有

toString(), equals(), hashCode()等方法
這些全部都是從java.lang.Object繼承來的

// 建立Copy
protected Object clone() throws CloneNotSupportedException

// 預設會是 ==
public boolean equals(Object obj)
    
// 被GC呼叫時, 查看參考用
protected void finalize() throws Throwable

// 取得hashcode
public int hashCode()
    
// 取得String表示
public String toString()

可以把變數Child轉成Parent, Parent轉成Child, (instance是同一個)

public class Main {
    public static void main(String[] args) {
        MyPrinter p = new MyPrinter();
        
        Printer p2 = p; //upcasting
        System.out.println(p2 == p); //true
        
        Printer p3 = new MyPrinter(); 
        MyPrinter p4 = (MyPrinter) p3;
        System.out.println(p3 == p4); //true
        
        // Not castable
        Printer p5 = new Printer();
        MyPrinter p6 = (MyPrinter) p5; // ClassCastException
    }
}

Instanceof

可以來判斷一個instance是不是屬於某一個class/interface(child也行)的instance

public class Main {
    public static void main(String[] args){
        Printer s = new Printer();
        MyPrinter v = new MyPrinter();
        boolean b = s instanceof Printer;
        boolean b2 = s instanceof MyPrinter;
        boolean b3 = v instanceof Printer;
        boolean b4 = v instanceof MyPrinter;
        System.out.println(b); //true
        System.out.println(b2); //false
        System.out.println(b3); //true
        System.out.println(b4); //true
    }
}

@Override

子類別在繼承父類別時,

會繼承父類別的Method(non-static non-final)

如果想要在子類別中,改寫從父類別繼承下來的方法:

 

直接寫一個一樣的方法
並且可以加一個@Override Annotation (強烈建議寫@Override)

class Printer {
    public void print(String s) {
        System.out.println(s);
    }
}

class MyPrinter extends Printer {
    
    @Override
    public void print(String s) {
        System.out.println("[A] " + s);
    }
}

public class Main {
    public static void main(String[] args) {
        // child class
        MyPrinter p = new MyPrinter();
        p.print("hello"); // [A] hello
        
        // parent class
        Printer p2 = new Printer();
        p2.print("hello"); // hello
    }
}

Super

如果子類別override print()方法,

super.print()可以使用父類別原有的print()方法。
 

super 用來呼叫父類別的方法/建構子

class Printer {
    public void print(String s) {
        System.out.println(s);
    }
}

class MyPrinter extends Printer {
    
    @Override
    public void print(String s) {
        System.out.println("[A] " + s);
    }
    
    public void parentPrint(String s) {
        super.print(s);
    }
}

public class Main {
    public static void main(String[] args) {
        // child class
        MyPrinter p = new MyPrinter();
        p.print("hello"); // [A] hello
        p.parentPrint("hello"); // hello
        
        // parent class
        Printer p2 = new Printer();
        p2.print("hello"); // hello
    }
}

constructor param issue

假設父類別的constructor

  • 沒有() constructor
  • 每個constructor都需要parameter

我們需要用super()

在子類別手動呼叫父類別的constructor

 
class Printer {
    
    private int version;
    
    public Printer(int version) {
        this.version = version;
        System.out.println(ersion);
    }
    
    public void print(String s) {
        System.out.println(s);
    }
    
}

class MyPrinter extends Printer {
    
    public MyPrinter() {
        super(0); // 一定要手動呼叫, 因為預設的super()不能用了
    }
    
    public MyPrinter(int version) {
        super(version); // 一定要手動呼叫, 因為預設的super()不能用了
    }
    
    public void myPrint(String s) {
        System.out.println("[A] " + s);
    }
}

Final

public static void main(String[] args) {
  
  final int a = 1;


  // a = 2 // final 變數無法修改

  // 不能宣告無初始值的final變數
  // final double b;
  // final Ship s;

  final Point p = new Point(100, 200);
  p.setX(200); // 你還是可以呼叫成員方法
  // p = new Point(300, 400); // final變數無法修改
}

final local variable

必需對該變數定義初始值, 不得更改數值

public class Main {
    public static void main(String[] args) {
        int i = 1;
        test(i);
    }
    
    public static int test(final int i) {
       i = 3; // not allowed
       System.out.println(i);
    }
}

Final Parameter
該parameter在Method中無法更改.

public class Main {
	static final int MAX_HEIGHT = 1080;
    
    public static void main(String[] args) {
        // ...
    }
}

如果是static final field, 請盡量用全大寫,底線命名

如果是non-static field

初始值可以從constructor提供 但一樣未來無法更改數值

class Point {
    private final int x;
    private final int y;
 
    public Point() {
        this(-1, -1);
    }
    
    public Point(int x, int x) {
        this.x = x;
        this.y = y;
    }
    
    /*
    public void setX(int x, int y) {
        this.x = x;
        this.y = y;
    }
    */
}
public class Main {
    public static void main(String[] args) {
        Point p2 = new Point(100, 200);
        // p2.y = 300; // now allowed
    }
}

Interface

一台印表機,可能都有"列印"的功能。
作為印表機的使用者,你不需要知道底層是如何運作的,只要功能能用就行了。
這是因為所有的印表機都遵守了一個共同的「介面規範」

確保所有印表機都具備這些基本功能。

這樣一來,無論你用哪台印表機,都能用相同的方式來操作,而不需要學習不同的控制方法。


interface(介面) 可以說是一個Method Set template
但是你仍然算是宣告一個參考資料型態
你還是可以到處使用interface Name。

class 能 implement 多個interface
Interface 能 extend 多個 Interface

private method

  • 無法被子類別實作的輔助方法

Members of Interface

static method

  • Access Modifier: default 會變為 public

variable

  • 預設是public static final
  • 不能是private / protected

Members of Interface

interface TestInterface {
    int i = 1; // 預設會 public static final
    
    private void helperMethod() { // private method
        
    }
    
    static void hello() { // static method
        System.out.println("Hello");
    }
}

class Main {
    public static void main(String[] args) {
        int i = TestInterface.i;
        TestInterface.hello(); 
    }
}

是一個無Method Body的方法

  • Method Body, ; 改寫
  • 在Interface中 不能被private/protected修飾
  • default 會變為 public
  • 必須在abstract class/interface裡面
  • interface中, 無須以abstract修飾

Abstract Method

interface IPrinter {
    void print(String s);
}
interface IPrinter {
    void print(String s);
}

class TypeA implements IPrinter {
    @Override
    public void print(String s) {
        System.out.println("[A] " + s);
    }
}

class TypeB implements IPrinter {
    @Override
    public void print(String s) {
        System.out.println("[B] " + s);
    }
}

是一個無Method Body的方法

  • 有預設的實作
  • 可以被子類別實作
  • 不能被 static/final/private/protected修飾

Default Method

interface IPrinter {
    default void print(String s) { 
        System.out.println(s);
    }
}

Default Method

interface IPrinter {
    default void print(String s) { 
        System.out.println(s);
    }
}

class TypeA implements IPrinter {
    @Override
    public void print(String s) {
        System.out.println("[MyPrinter]" + s);
    }
}

class TypeB implements IPrinter {
    // it use the Printer's default print(String s)
}

Default Method

public class Main {
    public static void main(String[] args) {
        Printer a = new TypeA();
        a.print("test"); // [A] test
        
        Printer b = new TypeB();
        b.print("test"); // test
    }
}
public class Main {
    public static void main(String[] args) {
        Printer a = new TypeA();
        TypeB b = new TypeB();
        
        printHelloWorld(a);
        printHelloWorld(b);
    }
    
    
    public static void printHelloWorld(Printer c) {
        c.print("Hello World");
    }
}

因為a, b 都是 Printer,

所以可以丟進Printer引數中

正因為如此, 如果底層把印表機的邏輯寫好,

把底層的getPrinter寫好,

 

在比較上層的程式碼, 我們可以不在乎印表機是誰。

透過介面提供的方法操作。

讓程式碼與實作的各種(印表機)類別解耦。

class PrinterManager {
    private static Printer typeB = new TypeB();
    private static Printer typeA = new TypeA();
    public static Printer getPrinter() {
        // 這裡可能會找一台沒人在用的Printer之類的
        while(...) {
            if (...) return typeB; 
            else if (...) return typeA;
        }
    }
}


public class Main {
    public static void main(String[] args) {
        Printer a = PrinterManager.getPrinter();
        a.print("hello"); // 用就對了
    }
}

關於繼承...

假設interface A extends interface B

如果有個class C implements A

等於class C implements A, B

(Interface 能 extend 多個 Interface)

interface Movable {
    void move(int x, int y);
}

interface Runnable extends Movable {
    void run(int speed);
}

class Human implements {
    // move
    // run
}

關於繼承...

class 能 implement 多個interface

interface Movable {
    void move(int x, int y);
}

interface Runnable {
    void run(int speed);
}

class Human implements Movable, Runnable {
    // move
    // run
}

Abstract Class

被abstract修飾的class無法直接被實例化, 但可以被繼承。
內可含abstract methods

如果有個子類別繼承某個abstract methods
子類別通常會實作所有父類別的abstract methods,
如果沒有, 子類別也需是abstract class

abstract class Printer {
    
    abstract void print(String s);

    public void TestOutput() {
        print("testing printer...");
    }
}

class TypeA extends Printer {
    
    @Override
    public void print(String s) {
        System.otu.println("[A] " + s);
    }
}

public class Main {
    public static void main(String[] args){
        Printer p = new TypeA();
        p.print("hello"); // [A] hello
        p.TestOutput(); // [A] testing printer...
    }
}

Abstract vs Interface

  • Abstract Class
    • 你的父子類別通常很有關係
    • 你希望有些成員不是public
    • 你想宣告non-static或是non-final fields
  • Interface
    • 你的interface與class可以關係不大
    • 你想指定某些class的行為, 但不在乎他是怎麼實作的
    • 你想使用多重implement

Anonymous Class

前面我們寫過一些interface, abstract class,

並且有個子類別繼承他們,


但是每次我們要建立一種介面實作,

或是抽象類別的子類,

就要建立一個class,

 

有時候只是希望一次性使用, 就會很麻煩。

所以我們可以不直接寫子類別,

直接new 一個interface/abstract class,
並且要實作方法。

new InterfaceOrAbstractClass(constructor param) {declaration};
class Printer {
    public void print(String s) {
        System.out.println(s);
    }
}

public class Main {
    public static void main(String[] args) {
        Printer v = new Printer() {
            @Override
            public void print(String s) {
                System.out.print("Class : " + s);
            }
        };
        v.print("Hello"); // Class : Hello
    }
}
interface IPrinter {
    void print(String s);
}

public class Main {
    public static void main(String[] args) {
        IPrinter v2 = new IPrinter() {
            @Override
            public void print(String s) {
                System.out.print("Interface : " + s);
            }
        };
        v2.print("Hello"); // Interface : Hello
    }
}
abstract class PrinterAbstract {
    public abstract void print(String s);
}

public class Main {
    public static void main(String[] args) {
        PrinterAbstract v3 = new PrinterAbstract() {
            @Override
            public void print(String s) {
                System.out.print("Abstract Class : " + s);
            }
        };
        v3.print("Hello"); // Abstract Class : Hello
    }
}

總之class, abstract class, interface寫法都一樣。

Lambda

如果Interface 的non-static方法只有一個
我們可以把傳統的Interface Anoymous Class改寫成:

(param) -> statement
 new InterfaceName() {
     @Override
     public void methodName(param... ) {
         // do something
     }
 };
(param) -> {
    // do something
}
interface MathFunction {
    void run(int x,int y);
}

public class Main {
    public static void main(String[] args) {

        // 原本寫法
        MathFunction sum = new MathFunction() {
            @Override
            public void run(int x, int y) {
                System.out.println(x + y);
            }
        };
        sum.run(10, 20); // 30
        
        MathFunction multiply = (x, y) -> {
            int result = x * y;
            System.out.println(result);
        };
        multiply.run(10, 20); // 200
        
        MathFunction subtract = (x, y) -> System.out.println(x - y);
        subtract.run(20, 10); // 10
    }
}

interface MathFunction {
    void run(int x);
}

public class Main {
    public static void main(String[] args) {
        MathFunction pow = x -> System.out.println(x * x);
        pow.run(20); // 400
    }
}

如果param只有一個的話可以省去小括號

Lambda with return value

(param) -> <return value>
(param) -> {
    ...
    return ...;
}
 new InterfaceName() {
     @Override
     public Type methodName(param... ) {
         return ...;
     }
 };
interface MathFunction {
    int run(int x,int y);
}

public class Main {
    public static void main(String[] args) {
        MathFunction sum = new MathFunction() {
            @Override
            public int run(int x,int y) {
                return x + y;
            }
        };
        System.out.println(sum.run(10,20))); //30
        
        MathFunction multiply = (x,y) -> {
            int result = x * y;
            return result;
        };
        System.out.println(multiply.run(10,20)); //200
        
        //x * y is the return value itself.
        MathFunction subtract = (x,y) -> x - y;
        System.out.println(subtract.run(20, 10)); // 10
    }
}

Method Reference

  • static method
    • ClassName::staticMethodName
  • non-static method
    • object::nonstaticMethodName
  • constructor
    • ClassName::new

如果Lambda

直接把參數呼叫另一個方法

可以用該方法的Method Reference代替。​

class Methods {
    public static int sum(int x, int y) {
        return x + y;
    }
}

如果說有這個sum方法存在 (不管是誰寫的)

(x, y) -> Methods.sum(x, y)

那此時我可以把

(x, y) -> x + y

改成

那此時就是直接把參數呼叫另一個方法

所以可以改寫成

Methods::sum(x, y);
class Test {
    public int sum(int x, int y) {
        return x + y;
    }
}

如果是非靜態方法

Test obj = // ...

(x, y) -> obj.sum(x, y)

obj::sum
(x, y) -> new Point(x, y);

如果是呼叫建構子

Point::new

可以寫成

Exception

當在方法執行時出了錯, 可能會生成一個Throwable物件, 包含錯誤資訊

丟擲例外之後,

 

會嘗試在Call Stack(呼叫方法堆疊)

逆序尋找一個能處理Throwable的throwable handler。


如果找到能處理的, 就好了。
如果沒找到, 程序會結束。

Throwable分成兩種:

  • Error: 比較不太會發生
    • 像是OutOfMemoryError,StackOverflowError
  • Exception: 在程式中比較常遇到
    • 像是NullPointerException,IOException

Checked, Unchecked

 

  • Checked: 必須顯式處理的Exception
    • 在方法內要用try-catch處利, 否則要寫throws
  • Unchecked: 反之
    • RuntimeException 與 Error
  • Throwable:
    • Exception:
      • IOException
      • RuntimeException (Unchecked)
        • NullPointerException
    • Error: (Unchecked)
      • NoClassDefFoundError
      • OutOfMemoryError

try-catch

try {
    // code
} catch (ExceptionType ex) {
    
} catch (ExceptionType ex) {
    
}

使用一個Exception Handler catch 多種Exception

try {
    // code
} catch (IOException | SQLException ex) {
    
} catch (OtherException ex) {
   
}

try block結束之後會執行finally block

try {
    // code
} catch (ExceptionType ex) {
    
} catch (ExceptionType ex) {
    
} finally {
    
}
public static void main(String[] args) {
    String s = "hello";
    try {
        int i = Integer.parseInt(s);
        System.out.println(i);
    } catch (NumberFormatException e) {
        e.printStackTrace();
    }
}

字串轉為整數

public static void main(String[] args) {
    String s = ...;
    try {
        int i = Integer.parseInt(s);
        System.out.println(i);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

throws

如果method內可能有exception會出現,

但是你不想要在此method處理,
可以使用 throws Exception1, Exception2 ... 

把可能出現的例外丟給上層call stack處理。

 

如果Method內有未處理的checked exception,

則必須寫throws

public static void main(String[] args) {
    try {
        connect("https://api.speedcubing.top");
    } catch (IOException ex) {
        //
    }
}

public static void connect(String url) throws IOException {
    new URL(url).openConnection();
}
public static void main(String[] args) {
    try {
        connect("https://api.speedcubing.top");
    } catch (IOException ex) {
        //
    }
}

public static void connect(String url) throws IOException {
    new URL(url).openConnection();
}

Custom Exception


找一個Exception, 然後extends他, 完事。

 

  • extends Exeption來製作Checked Exception
  • extends RuntimeException來製作Unchecked Exception
class NumberOutofRangeException extends RuntimeException {
    
}

class NumberChecker {
    public static void check(int i, int low, int high) {
        if(i < low || high < i) {
            throw new NumberOutofRangeException();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        int i = 20;
        NumberChecker.check(i, 100, 200);
        System.out.println(i);
    }
}

try-with-resources

resource是指程式執行完後需要關閉的物件,

會實作java.lang.AutoCloseable

try (Statement statement = con.createStatement()) {
    ResultSet rs = statement.executeQuery(query);
}


statement物件離開try block後會被釋放。

Java Tutorial

By speedcubing

Java Tutorial

  • 120