第1章--抽象与接口
1.1 抽象
An abstract class can be created without abstract methods, the purpose of doing this is to prevent instantiating the class.
A class with any abstract method must be abstract.
1.2 数据与表现分离
separation of the logical layer and the graphical interface.
责任驱动:whose responsibility?
1.3 接口
接口所有的方法都是没有方法体的,而且都是public abstract,即使你没有这样声明。
接口中的所有成员变量都是public static final的变量,并且必须有定义初始化,因为所有变量都必须在编译的时候有确定值。
Java不允许多继承(一个类有两个父类),但是允许一个类实现多个接口,也允许一个接口从多个接口得到继承,但是不允许接口从类继承。
接口--任何需要传入传出的一定是接口而不是具体的类
pro: 适合多人同时写一个大程序
con: 代码量膨胀起来很快
第2章--异常
2.1 异常的捕捉
// Question: // how is the ArrayIndexOutOfBoundsException thrown by f() ? public class ExceptionThrown { public static void f() { int[] a = new int[10]; a[10] = 10; System.out.println("hello"); } public static void g() { f(); } public static void h() { int i = 10; if (i < 100) { g(); } } public static void k() { try { g(); } catch (NullPointerException e) { System.out.println("k()"); } } public static void main (String[] args) { try { k(); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Exception Caught");
System.out.println(e);
e.printStackTrace(); } } }
捕捉异常后 可以处理异常,也可再次抛出
catch(Exception e) {
...
throw e;
}
finally{} -- execute finally block after try/catch.
2.2 异常的抛出和声明
若函数可能抛出异常,就必须在函数头部声明 throws ...
若调用一个声明了会抛出异常的函数,这需要try/catch来解决异常或重新抛出异常。
所有异常都是Exception类的子类。
捕捉异常时可以catch到异常的子类。
多句catch语句时会按照先后顺序匹配,若匹配到则不会继续匹配。
若同时有两句catch捕捉子类和父类异常,
注意"Unreachable catch block for child Exception" if child exception is put after the catch parent exception.
当覆盖一个函数的时候,子类的函数声明抛出的异常不能比被覆盖的父类函数声明抛出的异常多。
因为有可能拿着子类的对象当成父类的对象来看待: upper cast
public class A { public void f() throws AException {} } public class A1 extends A { public void f() {} // works ok public void f() throws AException {} // works ok public void f() throws AException, BException {} // Error: Exception BException is not compatible with throws clause in A.f() }
在子类的构造函数中,必须声明父类可能抛出的全部异常。
因为子类的构造器会自动调用父类的构造器。
第3章--IO
3.1 流
处理输入输出的方法:stream -- unidirectional; one-dimensional.
java.io: Package
InputStream & OutputStream -- abstract class in java.io
InputStream:
read(); read(byte b[]); read(byte[], int off, int len); --stream of bytes
int available(); mark(); reset(); markSupported(); close();
public class InputStreamTest { public static void main(String[] args) { System.out.println("Ready to read in: "); byte[] buffer = new byte[1024]; try { int len = System.in.read(buffer); // how many bytes read in String s = new String(buffer, 0 ,len); // start at 0, with length len System.out.println("read in " + len + " bytes."); System.out.println(s); System.out.println("length of s: " + s.length()); } catch (IOException e) { e.printStackTrace(); } } }
when the input stream contains Chinese characters, the value of len would be different from the value of s.length()
OutputStream:
write(); write(int b); write(byte b[]); write(byte b[], int off, int len);
flush()--; close();
File stream 文件流:
FileInputStream & FileOutputStream -- 实际工程中较少使用:常用在内存数据或通信数据上建立的流;具体的文件读写有更专业的类如配置文件和日志文件.
public static void main(String[] args) { System.out.println("prompt"); byte[] buffer = new byte[10]; for(int i = 0; i < buffer.length; i++) { buffer[i] = (byte) i; } try { FileOutputStream out = new FileOutputStream("a.dat"); out.write(buffer); // a file called a.dat will be created of size 10 bytes } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
流过滤器:
以一个介质流对象为基础,层层构建过滤器流,最终形成的流对象能在数据的输入输出过程中,逐层使用过滤器流的方法来读写数据。
public static void main(String[] args) { System.out.println("prompt"); // data stream: input/output the primitive types value DataOutputStream out; try { out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("a.dat"))); // nested stream -- bytes from FileOutputStream to BufferedOutputStream, to DataOutputStream // int i = 0xcafebabe; // hex int i = 123456; // what is written in the file is binary out.writeInt(i); out.close(); DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("a.dat"))); int j = in.readInt(); in.close(); System.out.println(j); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
3.2 文本流
The class we use previously (Data stream) is only available for dealing with data of primitive types (binary data).
What if we wanna handle text? -- Reader & Writer.
public static void main(String[] args) { System.out.println("prompt"); try { PrintWriter out = new PrintWriter ( new BufferedWriter( new OutputStreamWriter( new FileOutputStream("a.txt")))); // FileOutputStream: bytes // OutputStreamWriter: the bridge between stream and writer int i = 123456; out.println(i); out.close(); // BufferedReader BufferedReader in = new BufferedReader( new InputStreamReader( new FileInputStream("src/ReaderWriter.java"))); String line; while ((line = in.readLine()) != null) { // read in line by line System.out.println(line); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
Other classes:
LineNumberReader -- is able to get the line number and switch between lines
FileReader -- is the child class of InputStreamReader.
FileReader(File file);/ FileReader(String fileName); -- create a reader on a file directly.
文件编码方式的影响:
国标GB码:2bytes一个汉字
Unicode编码:所有平台上通用。
UTF-8:如果全部是英文字符,一个byte就能表示一个英文字符;如果有汉字(对他们来说不常见),这需要(可能)3个字节表示
若:上述程序读入的是UTF-8编码的文件,则会输出乱码--FileInputStream处理二进制数据后交给InputStreamReader处理文本(二进制转换为文本:default方法:系统默认方式)
程序改进--在InputStreamReader中给定处理方式new InputStreamReader(new FileInputStream("utf8.txt"), "utf8"));
(创建使用给定字符集的InputStreamReader: InputStreamReader(InputStream in, Charset cs);
创建使用给定字符集解码器的InputStreamReader: InputStreamReader(InputStream in, CharsetDecoder dec);
创建使用指定字符集的InputStreamReader: InputStreamReader(InputStream in, String charsetName);)
面对繁多的输入输出类,如何选择呢?
是二进制数据?-- InputStream
否 -- 表达的是文本? -- Reader (readLine...)
表达的是基本数据类型? -- Scanner(nextInt extDouble...)
3.3 流的应用 (instances)
network socket
public static void main(String[] args) { try { // construct connection Socket socket = new Socket(InetAddress.getByName("localhost"), 12345); PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( socket.getOutputStream()))); out.println("Hello"); // write Hello out.flush(); // kinda like refresh // Read in BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream())); String line; line = in.readLine(); System.out.println(line); out.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } }
在terminal中打开nc -l 12345端口后,运行程序。
read()函数是阻塞的,在读到所需的内容之前会停下来等(基于read()的函数也是如此,如nextInt(), readLine())
常用单独的线程来做socket读入的等待,或使用nio的channel选择机制
对于socket,可以设置SO时间:setSoTimeout(int timeOut);
对象串行化:将类整个写到流里去 (Interface Serialization)
ObjectInputStream & ObjectOutputStream
public static void main(String[] args) { Student s1 = new Student("John", 18, 5); System.out.println("s1" + s1); try { ObjectOutputStream out; out = new ObjectOutputStream ( new FileOutputStream("obj.dat")); out.writeObject(s1); out.close(); } catch (IOException e) { e.printStackTrace(); } ObjectInputStream in; try { in = new ObjectInputStream( new FileInputStream("obj.dat")); Student s2 = (Student)in.readObject(); System.out.println("s2" + s2); in.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
第4章--设计原则
4.1 城堡游戏(例子)
package castle; public class Room { public String description; public Room northExit; public Room southExit; public Room eastExit; public Room westExit; public Room(String description) { this.description = description; } public void setExits(Room north, Room east, Room south, Room west) { if(north != null) northExit = north; if(east != null) eastExit = east; if(south != null) southExit = south; if(west != null) westExit = west; } @Override public String toString() { return description; } }
package castle; import java.util.Scanner; public class Game { private Room currentRoom; public Game() { createRooms(); } private void createRooms() { Room outside, lobby, pub, study, bedroom; // 制造房间 outside = new Room("城堡外"); lobby = new Room("大堂"); pub = new Room("小酒吧"); study = new Room("书房"); bedroom = new Room("卧室"); // 初始化房间的出口 outside.setExits(null, lobby, study, pub); lobby.setExits(null, null, null, outside); pub.setExits(null, outside, null, null); study.setExits(outside, bedroom, null, null); bedroom.setExits(null, null, null, study); currentRoom = outside; // 从城堡门外开始 } private void printWelcome() { System.out.println(); System.out.println("欢迎来到城堡!"); System.out.println("这是一个超级无聊的游戏。"); System.out.println("如果需要帮助,请输入 'help' 。"); System.out.println(); System.out.println("现在你在" + currentRoom); System.out.print("出口有:"); if(currentRoom.northExit != null) System.out.print("north "); if(currentRoom.eastExit != null) System.out.print("east "); if(currentRoom.southExit != null) System.out.print("south "); if(currentRoom.westExit != null) System.out.print("west "); System.out.println(); } // 以下为用户命令 private void printHelp() { System.out.print("迷路了吗?你可以做的命令有:go bye help"); System.out.println("如: go east"); } private void goRoom(String direction) { Room nextRoom = null; if(direction.equals("north")) { nextRoom = currentRoom.northExit; } if(direction.equals("east")) { nextRoom = currentRoom.eastExit; } if(direction.equals("south")) { nextRoom = currentRoom.southExit; } if(direction.equals("west")) { nextRoom = currentRoom.westExit; } if (nextRoom == null) { System.out.println("那里没有门!"); } else { currentRoom = nextRoom; System.out.println("你在" + currentRoom); System.out.print("出口有: "); if(currentRoom.northExit != null) System.out.print("north "); if(currentRoom.eastExit != null) System.out.print("east "); if(currentRoom.southExit != null) System.out.print("south "); if(currentRoom.westExit != null) System.out.print("west "); System.out.println(); } } public static void main(String[] args) { Scanner in = new Scanner(System.in); Game game = new Game(); game.printWelcome(); while ( true ) { String line = in.nextLine(); String[] words = line.split(" "); if ( words[0].equals("help") ) { game.printHelp(); } else if (words[0].equals("go") ) { game.goRoom(words[1]); } else if ( words[0].equals("bye") ) { break; } } System.out.println("感谢您的光临。再见!"); in.close(); } }
4.2 消除代码复制
duplication -- bad for maintenance.
solution--using methods; inheritance.
System.out.println("你在" + currentRoom); System.out.print("出口有: "); if(currentRoom.northExit != null) System.out.print("north "); if(currentRoom.eastExit != null) System.out.print("east "); if(currentRoom.southExit != null) System.out.print("south "); if(currentRoom.westExit != null) System.out.print("west "); System.out.println();
duplications in printWelcome() and goRoom();
4.3 封装
encapsulation.
good design: low coupling & high cohesion.
for code modification & code reuse.
Lower coupling by using encapsulation
bad design: variables in Room set as public
solution: private visualization should be used
but how to access these variables? add getters? Not an OOP solution.
-- coupling: what does these exits used for?
We can make Room class tell Game about its exits, rather than figure it out in Game class.
getExitDesc() in Room
public String getExitSDesc() { StringBuffer sb = new StringBuffer(); if(northExit != null) sb.append("north "); if(eastExit != null) sb.append("east "); if(southExit != null) sb.append("south "); if(westExit != null) sb.append("west "); return sb.toString();
}
the reason why not using String s = ""; s+="north ";... -- immutable -> new and replace -> to much cost.
an efficient way to do this: StringBuffer.
-- coupling: same for move operation in Game.goRoom()
public Room moveToNextRoom(String direction) { Room nextRoom = null; if(direction.equals("north")) { nextRoom = northExit; } if(direction.equals("east")) { nextRoom = eastExit; } if(direction.equals("south")) { nextRoom = southExit; } if(direction.equals("west")) { nextRoom = westExit; } return nextRoom; }
4.4 可扩展性
可扩展性的意思就是代码的某些部分不需要经过修改就能适应将来可能的变化。
add functions: go up/down:
用容器来实现灵活性--原本: 用成员变量来表示Room的方向,增减方向需要改变代码。
solution: 用hash table来表示方向,那么方向就不是“硬编码”的了。
four private Room variables should not be created in Room class directly -- changing to use HashMap<String, Room> instead.
adjust the method setExits();
public void setExit(String dir, Room room) { exits.put(dir, room); }
change the initialization of the exits to:
outside.setExit("east", lobby); outside.setExit("south", study); outside.setExit("west", pub); lobby.setExit("west", outside); pub.setExit("east", outside); study.setExit("north", outside); study.setExit("east", bedroom); bedroom.setExit("west", study); // extended exits lobby.setExit("up", pub); pub.setExit("down", lobby);
4.5 框架加数据
进一步加强可拓展性:从程序中识别出框架和数据,以代码实现框架,将部分功能以数据的方式加载。
上一小节中的exits改动就是框架的使用。
还有一个地方也可以使用到这个理论来改良代码质量:读取指令的部分
while ( true ) { String line = in.nextLine(); String[] words = line.split(" "); if ( words[0].equals("help") ) { game.printHelp(); } else if (words[0].equals("go") ) { game.goRoom(words[1]); } else if (words[0].equals("bye") ) { break; } readInCmd(); }
while loop中的String-method pair可不可以也用HashMap的框架表示呢?
借助子类class中的override method来实现。
public void play() {
Scanner in = new Scanner(System.in);
while ( true ) {
String line = in.nextLine();
String[] words = line.split(" ");
CmdHandler handler = handlers.get(words[0]);
if (handler != null) {
handler.cmdHandling();
if (handler instanceof ByeHandler) {
break;
} else if (handler instanceof GoHandler) {
if (words.length == 2) {
((GoHandler)handler).cmdHandling(words[1]);
} else {
System.out.println("Invalid Command (Usage: go dir)
");
}
}
} else {
System.out.println("Invalid Command
");
}
}
in.close();
}
public class CmdHandler {
public void cmdHandling() {
}
}
public class GoHandler extends CmdHandler{
private Game game;
public GoHandler(Game game) {
this.game = game;
}
public void cmdHandling(String dir) {
game.goRoom(dir);
}
}
public class HelpHandler extends CmdHandler{
public void cmdHandling() {
System.out.println("迷路了吗?你可以做的命令有:go bye help");
System.out.println("如: go east");
}
}
public class ByeHandler extends CmdHandler{
public void cmdHandling() {
System.out.println("感谢您的光临。再见!");
}
}
之后,若是想要加入新的cmd,添加Handler的子类即可。
第5章--设计模式
5.1 注入控制反转
GUI(Graphic User Interface):
部件:创建GUI的独立部分,比如按钮、菜单、菜单项、选择条等
布局:如何在屏幕上放置组件 -- 通过layout manager布局管理器实现
容器:可以在容器frame中放置部件(需要指定位置BorderLayout)(容器本身也是部件)
事件处理:用来响应用户的输入。用户激活一个部件,系统就会产生一个事件,应用程序就可以收到关于这个时间的通知(调用一个方法)。
BorderLayout:NORTH/ SOUTH/ EAST/ WEST/ CENTER. (default-CENTER) (overwrite if the position is the same)
Swing:event handling处理用户输入。
动作事件:点击按钮或选中菜单项
鼠标事件:点击或移动鼠标
窗口事件:框架被关闭或最小化
等等。
Q: How does a JButton object invoke the methods in my class?
A: One way can be done on our own: have a class MyButton extends JButton and override the doPressed() method to invoke myMethod.
A: What Swing does: 注入反转(反转控制)
JButton class; ActionListener interface;
JButton.addActionListener()/JButton.removeActionListener().
ActionListener.actionPerformed();
implement actionPerformed()
when click the JButton, check any actionListener related
JButton knows its registered ActionListener
invoke ActionListener.actionPerformed();
// add button JButton btnStep = new JButton("下一步"); frame.add(btnStep, BorderLayout.NORTH); // which component, and where btnStep.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { step(); frame.repaint(); } });
上例中:ActionListener作为一个内部类(一个类定义在另一个类的内部,从而成为外部类的一个成员,具有和成员变量成员方法相同的性质)
内部类的最重要的特点就是能够访问外部类的所有成员(包括私有成员);当外部是函数时,只能访问那个函数里的final变量(?)。
此外:ActionListener也是一个匿名类(在new对象的时候给出的类的定义),匿名类可以继承某类,也可以实现某接口
5.2 MVC设计模式
JTable: separating the data and representation.
public static void main(String[] args) { JFrame frame = new JFrame(); JTable table = new JTable(new CurriculumData()); // JTable cannot display all of its content JScrollPane panel = new JScrollPane(table); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(panel); frame.setSize(400, 400); frame.setVisible(true); }
CurriculumData is a class that stores the data
import javax.swing.event.TableModelListener; import javax.swing.table.TableModel; public class CurriculumData implements TableModel { private String[] title = { "周一","周二","周三","周四","周五","周六","周日" }; private String[][] curriculum = new String[8][7]; @Override public int getRowCount() { return 8; } @Override public int getColumnCount() { return 7; } @Override public String getColumnName(int columnIndex) { // the name of each column return title[columnIndex]; } @Override public Class<?> getColumnClass(int columnIndex) { // the data type of each column return String.class; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } @Override public Object getValueAt(int rowIndex, int columnIndex) { // get the data value at a particular cell return curriculum[rowIndex][columnIndex]; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { // set the data value at a particular cell curriculum[rowIndex][columnIndex] = (String) aValue; } @Override public void addTableModelListener(TableModelListener l) { // TODO Auto-generated method stub } @Override public void removeTableModelListener(TableModelListener l) { // TODO Auto-generated method stub } }
what happens inside is: There is a Table Model object between JTable and JTableData as the interface.
|
|/
MVC (Model, View, Control)
模型:保存和维护数据,提供接口让外部(Control)修改数据,notify表现(View)刷新呈现的数据
表现:从模型获得数据,根据数据画出表现
控制:从用户得到输入,根据输入调用Model的接口来调整数据
NB: Control和View没有直接的联系
好处:每一个部分的实现都很单纯,比如View只需知道每次全部重画一遍,无需在意修改的细节
上例中:Model (TableModel); View&Control (JTable) -- 常见MVC实现方式,将View&Control合并(都是显示部分)