今天要學的是 Input and Output
會結合之前學過的例子來學習
之前學的文字冒險遊戲,將導入存檔讀檔功能
之前的冒險遊戲代碼:
Main.java
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;public class Main {
private static Map<Integer, Location> locations = new HashMap<Integer, Location>(); public static void main(String[] args) {
// Change the program to allow players to type full words, or phrases, then move to the
// correct location based upon their input.
// The player should be able to type commands such as "Go West", "run South", or just "East"
// and the program will move to the appropriate location if there is one. As at present, an
// attempt to move in an invalid direction should print a message and remain in the same place.
//
// Single letter commands (N, W, S, E, Q) should still be available. Scanner scanner = new Scanner(System.in); Map<String, Integer> tempExit = new HashMap<String, Integer>();
locations.put(0, new Location(0, "You are sitting in front of a computer learning Java",null)); tempExit = new HashMap<String, Integer>();
tempExit.put("W", 2);
tempExit.put("E", 3);
tempExit.put("S", 4);
tempExit.put("N", 5);
locations.put(1, new Location(1, "You are standing at the end of a road before a small brick building",tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("N", 5);
locations.put(2, new Location(2, "You are at the top of a hill",tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("W", 1);
locations.put(3, new Location(3, "You are inside a building, a well house for a small spring",tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("N", 1);
tempExit.put("W", 2);
locations.put(4, new Location(4, "You are in a valley beside a stream",tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("S", 1);
tempExit.put("W", 2);
locations.put(5, new Location(5, "You are in the forest",tempExit)); Map<String, String> vocabulary = new HashMap<String, String>();
vocabulary.put("QUIT", "Q");
vocabulary.put("NORTH", "N");
vocabulary.put("SOUTH", "S");
vocabulary.put("WEST", "W");
vocabulary.put("EAST", "E");
int loc = 1;
while(true) {
System.out.println(locations.get(loc).getDescription());
tempExit.remove("S"); if(loc == 0) {
break;
} Map<String, Integer> exits = locations.get(loc).getExits();
System.out.print("Available exits are ");
for(String exit: exits.keySet()) {
System.out.print(exit + ", ");
}
System.out.println(); String direction = scanner.nextLine().toUpperCase();
if(direction.length() > 1) {
String[] words = direction.split(" ");
for(String word: words) {
if(vocabulary.containsKey(word)) {
direction = vocabulary.get(word);
break;
}
}
} if(exits.containsKey(direction)) {
loc = exits.get(direction); } else {
System.out.println("You cannot go in that direction");
}
} }
}
Location.java
import java.util.HashMap;
import java.util.Map;
public class Location {
private final int locationID;
private final String description;
private final Map<String, Integer> exits; public Location(int locationID, String description, Map<String, Integer> exits) {
this.locationID = locationID;
this.description = description;
if(exits != null) {
this.exits = new HashMap<String, Integer>(exits);
} else {
this.exits = new HashMap<String, Integer>();
}
this.exits.put("Q", 0);
}// public void addExit(String direction, int location) {
// exits.put(direction, location);
// } public int getLocationID() {
return locationID;
} public String getDescription() {
return description;
} public Map<String, Integer> getExits() {
return new HashMap<String, Integer>(exits);
}
}
我們將把上面的代碼做優化,把 Main 裡用的 locations
private static Map<Integer, Location> locations = new HashMap<Integer, Location>();
獨立拆成一個 Class,改到 Locations.java 裡面去
Main.java
package introduceIo;import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;public class Main {
private static Locations locations = new Locations(); public static void main(String[] args) {
// Change the program to allow players to type full words, or phrases, then move to the
// correct location based upon their input.
// The player should be able to type commands such as "Go West", "run South", or just "East"
// and the program will move to the appropriate location if there is one. As at present, an
// attempt to move in an invalid direction should print a message and remain in the same place.
//
// Single letter commands (N, W, S, E, Q) should still be available. Scanner scanner = new Scanner(System.in); Map<String, String> vocabulary = new HashMap<String, String>();
vocabulary.put("QUIT", "Q");
vocabulary.put("NORTH", "N");
vocabulary.put("SOUTH", "S");
vocabulary.put("WEST", "W");
vocabulary.put("EAST", "E");
int loc = 1;
while(true) {
System.out.println(locations.get(loc).getDescription()); if(loc == 0) {
break;
} Map<String, Integer> exits = locations.get(loc).getExits();
System.out.print("Available exits are ");
for(String exit: exits.keySet()) {
System.out.print(exit + ", ");
}
System.out.println(); String direction = scanner.nextLine().toUpperCase();
if(direction.length() > 1) {
String[] words = direction.split(" ");
for(String word: words) {
if(vocabulary.containsKey(word)) {
direction = vocabulary.get(word);
break;
}
}
} if(exits.containsKey(direction)) {
loc = exits.get(direction); } else {
System.out.println("You cannot go in that direction");
}
} }
}
Locations.java
package introduceIo;import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;public class Locations implements Map<Integer, Location> {
private static Map<Integer, Location> locations = new HashMap<Integer, Location>(); static {
Map<String, Integer> tempExit = new HashMap<String, Integer>();
locations.put(0, new Location(0, "You are sitting in front of a computer learning Java",null)); tempExit = new HashMap<String, Integer>();
tempExit.put("W", 2);
tempExit.put("E", 3);
tempExit.put("S", 4);
tempExit.put("N", 5);
locations.put(1, new Location(1, "You are standing at the end of a road before a small brick building",tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("N", 5);
locations.put(2, new Location(2, "You are at the top of a hill",tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("W", 1);
locations.put(3, new Location(3, "You are inside a building, a well house for a small spring",tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("N", 1);
tempExit.put("W", 2);
locations.put(4, new Location(4, "You are in a valley beside a stream",tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("S", 1);
tempExit.put("W", 2);
locations.put(5, new Location(5, "You are in the forest",tempExit)); } @Override
public int size() {
return locations.size();
} @Override
public boolean isEmpty() {
return locations.isEmpty();
} @Override
public boolean containsKey(Object key) {
return locations.containsKey(key);
} @Override
public boolean containsValue(Object value) {
return locations.containsValue(value);
} @Override
public Location get(Object key) {
return locations.get(key);
} @Override
public Location put(Integer key, Location value) {
return locations.put(key, value);
} @Override
public Location remove(Object key) {
return locations.remove(key);
} @Override
public void putAll(Map<? extends Integer, ? extends Location> m) { } @Override
public void clear() {
locations.clear();
} @Override
public Set<Integer> keySet() {
return locations.keySet();
} @Override
public Collection<Location> values() {
return locations.values();
} @Override
public Set<Entry<Integer, Location>> entrySet() {
return locations.entrySet();
}
}
到目前為止功能方面還是一樣的
只是這樣改動增加了功能的拓展性
像我們現在可以在 Locations 裡面
新增存檔跟讀取的功能
Locations.java
package introduceIo;import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;public class Locations implements Map<Integer, Location> {
private static Map<Integer, Location> locations = new HashMap<Integer, Location>(); public static void main(String[] args) {
FileWriter file;
try {
file = new FileWriter("src/introduceIo/locations.txt");
for (Location location : locations.values()) {
file.write(location.getLocationID() + ", " + location.getDescription() + "\n");
}
file.close();
} catch (IOException e) {
System.out.println("In catch block");
e.printStackTrace();
}
} static {
Map<String, Integer> tempExit = new HashMap<String, Integer>();
locations.put(0, new Location(0, "You are sitting in front of a computer learning Java", null)); tempExit = new HashMap<String, Integer>();
tempExit.put("W", 2);
tempExit.put("E", 3);
tempExit.put("S", 4);
tempExit.put("N", 5);
locations.put(1, new Location(1, "You are standing at the end of a road before a small brick building", tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("N", 5);
locations.put(2, new Location(2, "You are at the top of a hill", tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("W", 1);
locations.put(3, new Location(3, "You are inside a building, a well house for a small spring", tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("N", 1);
tempExit.put("W", 2);
locations.put(4, new Location(4, "You are in a valley beside a stream", tempExit)); tempExit = new HashMap<String, Integer>();
tempExit.put("S", 1);
tempExit.put("W", 2);
locations.put(5, new Location(5, "You are in the forest", tempExit)); } @Override
public int size() {
return locations.size();
} @Override
public boolean isEmpty() {
return locations.isEmpty();
} @Override
public boolean containsKey(Object key) {
return locations.containsKey(key);
} @Override
public boolean containsValue(Object value) {
return locations.containsValue(value);
} @Override
public Location get(Object key) {
return locations.get(key);
} @Override
public Location put(Integer key, Location value) {
return locations.put(key, value);
} @Override
public Location remove(Object key) {
return locations.remove(key);
} @Override
public void putAll(Map<? extends Integer, ? extends Location> m) { } @Override
public void clear() {
locations.clear();
} @Override
public Set<Integer> keySet() {
return locations.keySet();
} @Override
public Collection<Location> values() {
return locations.values();
} @Override
public Set<Entry<Integer, Location>> entrySet() {
return locations.entrySet();
}
}
在 Main 裡新增了存檔功能,其他地方都沒改
但不能這樣寫,Java 會直接提示錯誤
Unhandled exception: java.io.IOException
這個屬於 checked exception
發現有問題時直接紅字,連編譯執行代碼都不能
平常執行後的報錯則是 — unchecked exception
As we know that there are two types of exception checked and unchecked. Checked exception (compile time) force you to handle them, if you don’t handle them then the program will not compile.
On the other hand unchecked exception (Runtime) doesn’t get checked during compilation. Throws keyword is used for handling checked exceptions . By using throws we can declare multiple exceptions in one go.
public static void main(String[] args) {
FileWriter file = null;
try {
file = new FileWriter("src/introduceIo/locations.txt");
for (Location location : locations.values()) {
file.write(location.getLocationID() + ", " + location.getDescription() + "\n");
}
file.close();
} catch (IOException e) {
System.out.println("In catch block");
e.printStackTrace();
}
}
這樣改之後就能順利執行了
可以去驗證,是否有了一個 locations.txt 檔案
0, You are sitting in front of a computer learning Java
1, You are standing at the end of a road before a small brick building
2, You are at the top of a hill
3, You are inside a building, a well house for a small spring
4, You are in a valley beside a stream
5, You are in the forest
額外補充:
e.printStackTrace();System.out.println(e);
同樣是打印錯誤訊息,這兩個差在哪裡呢?
上面那個就會顯示平常報錯的樣子
java.io.FileNotFoundException: introduceIo/locations.txt (No such file or directory)
at java.base/java.io.FileOutputStream.open0(Native Method)
at java.base/java.io.FileOutputStream.open(FileOutputStream.java:298)
at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:237)
at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:126)
at java.base/java.io.FileWriter.<init>(FileWriter.java:66)
at introduceIo.Locations.main(Locations.java:16)
Process finished with exit code 0
下面那個只會顯示最終錯誤
In catch block
java.io.FileNotFoundException: introduceIo/locations.txt (No such file or directory)
Process finished with exit code 0
回到程序,但這樣寫還是有問題
如果走在 try
裡面的 for loop 裡面的 file.write()
時
若發生錯誤就會直接進到 catch
那就不會執行到後續的 file.close()
而這步是一定要執行的
public static void main(String[] args) {
FileWriter file = null;
try {
file = new FileWriter("src/introduceIo/locations.txt");
for (Location location : locations.values()) {
file.write(location.getLocationID() + ", " + location.getDescription() + "\n");
}
} catch (IOException e) {
System.out.println("In catch block");
e.printStackTrace();
}finally {
System.out.println("In finally block");
file.close();
}
}
寫進 finally
的意思就是不管怎要,最後都要執行
但這樣寫還是有問題
會提示 Unhandled exception: java.io.IOException
public static void main(String[] args) {
FileWriter file = null;
try {
file = new FileWriter("src/introduceIo/locations.txt");
for (Location location : locations.values()) {
file.write(location.getLocationID() + ", " + location.getDescription() + "\n");
}
} catch (IOException e) {
System.out.println("In catch block");
e.printStackTrace();
} finally {
System.out.println("In finally block");
try {
if (file != null) {
System.out.println("file close");
file.close();
}else{
System.out.println("file not close");
}
} catch (IOException e) {
System.out.println("In second catch block");
e.printStackTrace();
}
}
}
在裡面再加一個 try
跟 catch
,還做 if
判斷,這樣就穩了
執行結果:
In finally block
file close
我有把產生的文檔清空做測試,還是一樣順利寫入
另外一種測試
file = new FileWriter("sdf$%@$^@$^&sfd/locations.txt");
把路徑亂改,讓寫入失敗
執行結果:
In catch block
In finally block
file not close
java.io.FileNotFoundException: sdf%@^@$^&sfd/locations.txt (No such file or directory)
at java.base/java.io.FileOutputStream.open0(Native Method)
at java.base/java.io.FileOutputStream.open(FileOutputStream.java:298)
at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:237)
at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:126)
at java.base/java.io.FileWriter.<init>(FileWriter.java:66)
at introduceIo.Locations.main(Locations.java:16)
白字順序可能不同
進入第一層 catch
之外,finally
最後也有順利執行
若是想進入到第二層 catch 可以去創一個同名資料夾,就可以了
可是若寫讀存文件時,都要寫那麼多行也太累了
public static void main(String[] args) throws IOException {
FileWriter file = null;
try {
file = new FileWriter("src/introduceIo/locations.txt");
for (Location location : locations.values()) {
file.write(location.getLocationID() + ", " + location.getDescription() + "\n");
}
} finally {
System.out.println("In finally block");
if (file != null) {
System.out.println("file close");
file.close();
} else {
System.out.println("file not close");
}
}
}
我們還可以簡化成這樣
public static void main(String[] args) throws IOException {
FileWriter file = null;
try {
file = new FileWriter("src/introduceIo/locations.txt");
for (Location location : locations.values()) {
file.write(location.getLocationID() + ", " + location.getDescription() + "\n");
throw new IOException("test exception thrown while writing");
}
} finally {
System.out.println("In finally block");
if (file != null) {
System.out.println("file close");
file.close();
} else {
System.out.println("file not close");
}
}
}
在 for loop 第一次寫入後
拋出一個異常做測試
輸出結果:
In finally block
file close
Exception in thread “main” java.io.IOException: test exception thrown while writing
at introduceIo.Locations.main(Locations.java:19)
若執行前把 locations.txt 的內容刪掉會發現
只寫入了一行
0, You are sitting in front of a computer learning Java
此結果有符合預期
Tim 特別提醒 throw 異常,是開發測試時使用的方法,確認 try catch finally 是否正常工作
而在實際上線的業務代碼是不能被放入的,開發測試確認正常後,記得就要刪掉
不刪的話,這個 throw 拋異常後,程序就崩潰了
public static void main(String[] args) throws IOException {
try (FileWriter file = new FileWriter("src/introduceIo/locations.txt")) {
for (Location location : locations.values()) {
file.write(location.getLocationID() + ", " + location.getDescription() + "\n");
}
}
}
更開心的是,從 Java SE7 開始,還能簡化到如此程度
不管是正常運作或拋出異常,都會自動關閉 file,不用另外寫了,看起來更舒服
補充:The try-with-resources Statement
public static void main(String[] args) throws IOException {
try (FileWriter file = new FileWriter("src/introduceIo/locations.txt");
FileWriter dirFile = new FileWriter("src/introduceIo/directions.txt")) {
for (Location location : locations.values()) {
file.write(location.getLocationID() + ", " + location.getDescription() + "\n");
for (String direction : location.getExits().keySet()) {
dirFile.write(location.getLocationID() + "," + direction + "," + location.getExits().get(direction) + "\n");
}
} }
}
接著繼續創方向文檔,把所有 direction 也寫進去
之後就可以去寫讀檔了
補充:
java.util.HashMap.keySet()方法實例
java.util.HashMap.values()方法實例