Course III notes
throw 代表了一个处理 exception 的过程。该过程包括:
一个 InputMismatch 的例子:
Exception in thread "main" java.util.InputMismatchException
// using throwFor as an operator to hand off the ecxcption to JVM
at java.base/java.util.Scanner.throwFor(Scanner.java:939)
// where the exception actually made
at java.base/java.util.Scanner.next(Scanner.java:1594)
// tracking calls which caused the exception
at java.base/java.util.Scanner.nextInt(Scanner.java:2258)
at java.base/java.util.Scanner.nextInt(Scanner.java:2212)
// where exception started
at FahrenheitToCelsiusExceptions.main(FahrenheitToCelsiusExceptions.java:6)
expection 的 throw 过程通常会在终端显示。这个过程被称为 call stack trace。这个过程展示了一系列的 method 导致了异常被抛出。stack 体现了这些 methods 的调用流程。
通常来说,JVM 会存储当前正在执行的 method 的状态:
main()
是第一个压栈,最后一个出栈的。
Java 使用 try
和 catch
语句处理异常:
try {
statement(s)
}
catch (ExceptionType identifier)
catch (ExceptionType identifier) {
handleStatement(s)
}
如果进入了 catch
阶段,程序就不会再回到 try
阶段了。
try {
statement(s);
} catch (ExceptionType1 identifier) {
statement(s);
} catch (ExceptionType2 identifier) {
statement(s);
}
nextLine()
清理
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int fahrenheit = 0;
boolean isValid = false;
while(!isValid) {
try {
System.out.print("Enter a Fahrenheit value: ");
fahrenheit = input.nextInt();
isValid = true;
}
catch (InputMismatchException e) {
input.nextLine();
System.out.println("Your input is not an int type.");
}
}
double celsius = (5.0/9) * (fahrenheit - 32);
System.out.printf("Fahrenheit: %d\n", fahrenheit);
System.out.printf("Celsius: %.1f\n", celsius);
}
每一个 catching 的分支完毕之后,都不会再继续执行其他的 catch。因此,error catching 分类的顺序是非常重要的。有两点需要注意:
Exception
)在前,那么所有的 error 都会被该分支捕获,其他的分支永远不会被唤起。(Java 无法通过编译)0.0
的时候抛出 InputMismatchException
而不是 ArithmeticException
。getMessage()
和 getStackTrace()
获取更多异常的额外信息。
try {
//...
}
catch (InputMismatchException ime) {
input.nextLine();
System.out.println("Your input is not an int type.");
}
catch (ArithmeticException ae) {
input.nextLine();
System.out.println(ae.getMessage());
System.out.println(ae.getStackTrace());
}
catch (Exception e) {
input.nextLine();
System.out.println(e.getMessage());
}
如果希望将多个异常归于一类,使用 |
运算符并列多个 exception:
catch (InputMismatchException | ArithmeticException cbe) {
input.nextLine();
System.out.println("Invaild Input");
}
finally
修饰的 block 处于 try-catch block 的最后,代表无论是否出现异常都会执行的代码段。通常可以放一些必要的清理代码:
try {
//....
}
catch (Exception e) {
System.out.println(e.getMessage());
}
finally {
input.nextLine();
}
Java 中使用文件作为 Input 和 output 需要 4 个 package:
io.File
,这个 package 提供 File
class。该类型对象接收一个 String 类型的路径用于读 / 写文件。io.FileNotFoundException
,这个 pakeage 提供文件名不存在时的异常对象。util.Scanner
,提供读取对象util.PrintWriter
,提供写对象
Scanner
和 PrintWriter
的初始值推荐为 null
。该值可以作为关闭流的条件:在没有文件路径的情况下,这些对象的默认是 null
;这种情况下不需要关闭流。
//fileScan is an Scanner object, filePrint is an PrintWriter object
if(fileScan != null) {
fileScan.close();
}
if(filePrint != null) {
filePrint.close();
简单的实现文件:filetest.java
在有 File
类型的 Object 参与的程序中,FileNotFoundException
不可省略。但也无需为其指定 catch 分支;只需要声明(导入) FileNotFoundException
即可:
import java.io.FileNotFoundException;
exception 分为 checked 和 unchecked 两种类型。checked exception 指必须做特别处理的 exception,比如上面的例子。unchecked exception 通常指某些多变的,非常难以管理的 excpetion。这种 expetion 不会留具体的处理方式。
File
对象Scanner
对象读取该 File
对象的内容Scanner
的 method 对文件进行读取(比如 nextLine
)
String inputFileName = "text.txt"
File fileIn = new File(inputFileName);
Scanner fileScan = new Scanner(fileIn);
File
对象PrintWriter
对象读取该 File
对象的内容print
将内容写入到文件中
File fileOut = new File(keyword + "In" + inputFileName);
filePrint = new PrintWriter(fileOut);
filePrint.printf("Line %d contains %s\n",lineNum, keyword);
String.contians(keyword)
:检查 String 中是包含 keyword
关键字我们可以对 exception 进行更加的特例化:只需要根据需求继承对应的 exception 类即可,比如:
public class DivideByZeroException extends ArithmeticException{
public DivideByZeroException() {
super("Divide by zero.");
}
}
这里的 super(“Divide by zero.”)
调用了父类的构造函数,String 参数会作为 getMessage()
的返回值。
与 try-catch 不同,throw
允许我们手动引发异常:
fahrenheit = input.nextInt();
if (fahrenheit == 0) {
throw new DivideByZeroException();
}
引发异常后,throw
会先在同 method
的内容中寻找对应的的 error handing (catch)。如果需要我们实现,写法与 try-catch 一致:
catch (DivideByZeroException de) {
System.out.println("invalid number.");
}
throw
通常用于处理一些我们明确知道的异常,比如上述的除数为 0
的情况。try-catch 更像是一种防患于未然的机制,用于保证程序不会因潜在的问题导致崩溃。
Delimited Files,又称 CSV,是一种可以保证格式(persistent)的,用于存储结构性数据的文本文件。比如下面的数组:
public static void main(String[] args) {
Wolf[] pack = {
new Wolf(17.1, 2),
new Wolf(3, 10),
new Wolf(9.2, 7),
new Wolf(9.1, 8),
};
如果直接打印到终端,那么结果是:
[Rank 2, Size 17.1, Rank 10, Size 3.0, Rank 7, Size 9.2, Rank 8, Size 9.1]
可以看到的是,终端打印有太多的局限:
CSV 文件可以解决该问题。 CSV 文件是一种文本文件,其内部不同的数据使用逗号隔开。比如上面例子中的数据,写成 CSV 的格式:
2,17.1
10,3.0
7,9.2
8,9.1
输出 CSV 只要以上述的格式,使用 PrintWriter
对象将其输出到文本中即可:
File fileOut = new File("SortedWolves.csv");
PrintWriter filePrint = new PrintWriter(fileOut);
filePrint.println(wolf.getRank() + "," + wolf.getSize());
JAVA 中提供了 String.split(char)
方法用于按 char
分割字符串。返回的结果是一个包含了各个分割部分的 String[]
。因此,如果将分割符设置为逗号,那么就可以从 CSV
文件中提取数据:
double[] allWeights = new double[10];
fileScan = new Scanner(fileIn);
String line = null;
while (fileScan.hasNextLine()) {
line = fileScan.nextLine();
tokens = line.split(",");
allWeights[index] = Double.parseDouble(tokens[1]);
index++;
}
需要注意,得到的是 String[]
,某些场景需要转换为指定格式的数据。
split()
支持正则表达式(Regular Expressions)。下面是一个使用大写字母作为间隔的分割实例:
public static void main(String []args) {
String chant = "Java Is The Best!";
String[] tokens = chant.split("[A-Z]+");
for (String x : tokens){
System.out.println(x);
}
}
Scanner 可以像处理 stream 一样处理 String(也就是其成员 method 都可以用于处理 String)。我们可以使用 useDelimiter()
method 来指定间隔符,然后使用 next
系列 method 来读取每一个部分:
fileScan = new Scanner(fileIn);
String line = null;
while (fileScan.hasNextLine()) {
line = fileScan.nextLine();
wolfScan = new Scanner(line);
wolfScan.useDelimiter(",");
wolfScan.nextInt(); //consume unused rank token
allWeights[index] = wolfScan.nextDouble();
index++;
}
List Java 中以 Interface 的形式出现。不同类型的数据都会基于该 Interface 设计 class。LIst 的几个基本方法:
add(E e)
:添加元素clear()
:清空 Listcontains(Object o)
:查询是否包含某个元素remove(index)
: 删除指定位置的元素size()
:返回 List 大小elementData
的 array instance; 类型为 Object[] elementData
,以及一系列的配套 methods。10
(10 个值为 null 的连续地址)。arraylist 用 Capacity 来描述长度。
//generic creation, 10 capacity
ArrayList playlist = new ArrayList();
//generic creation, 5 capacity
ArrayList playlist = new ArrayList(5);
//type sepcified creataion
ArrayList<elementType> aList = new ArrayList<elementType>(initialCapacity);
//type sepcified creataion (after java 7). type on righthandside can be omited.
ArrayList<String> playlist = new ArrayList<>(5);
指定类型的声明方法只限制了可以调用的 mothod;也就是说,其声明类型依然是 Object[]
,而实例(对象)类型才是被指定的类型。
ArrayList<String> playList = new ArrayList<>();
//adding element to the end of the list(push_back())
playList.add("Humpty Dumpty");
//remove
playList.remove("Humpty Dumpty");
//print, will call toString()
System.out.println(playList.toString());
Arraylist 定义的类型是 Object[]
。如果需要在 arraylist 中使用 primtive type,那么需要使用 Java 中对应的 Object(match wrapper type)。比如 int
对应 Integer
Object。这个把 primitive type 自动转化为 对应的 wrapper type 的过程被称为 AutoBoxing。
public static void main(String[] args) {
ArrayList<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
for(int num : intList) {
System.out.println(num);
}
}
Java 中自带 GenericLinkedList
类。初始化方法:
GenericLinkedList<type> newList = new GenericLinkedList<>(iniParamterList);
在 Java 中,如果声明类型与对象类型不一致,在调用对象类型中的 method 时,需要进行 casting 确保调用的对象和方法的一致性:
//class Bin info for demo
private Object content;
public Bin(Object content) {
this.content = content;
}
public Object getContent() {
return content;
}
//instantiate
Bin car = new Car("yuhina", "spark", 2037));
//call
((Car).car).getContent();
实际上在 Java 中,我们可以通过对 Bin
类进行类型特化来替代每次调用都要 casting 的步骤。写法如下:
//simiar to template in C++
//T can be replaced with any meanful name
Class className<T>
之后所有与 generic 相关的 name, 返回值就都可以用 T
修饰了:
public class Bin<T> {
private Object content;
public Bin(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
//instantiate, a specific type needed
Bin<Car> car = new Car("yuhina", "spark", 2037));
//call, casting is nolonger needed.
car.getContent();
如果希望一个 generic class 特化多个类型,可以直接在 typename list 里面添加:
className<T1,T2,...Tn>
在通用类的内部可以使用声明类型使指定的变量和方法一致:
public class Bin<X, Y> {
private X content1;
private Y content2;
public X getContent1() {
return content1;
}
public void setContent(Y content2) {
this.content2 = content2;
}
//intansiate
Bin<Car, Dog> = new Bin(Car("Yuhina", "Spark", 2037), Dog("puppy"));
//T must be the subclass type of the Insert class
Bin<T extends Insert>
Bin<T extends Comparable>
Bin<T extends Insert & Comparable & Groomable>
//T is an Comparable type(can call compareTo())
public class Compartment<T extends Comparable> {
private T content1;
private T content2;
public Compartment(T content1, T content2) {
this.content1 = content1;
this.content2 = content2;
}
public boolean Greater() {
return content1.compareTo(content2) >= 0 ? true : false;
}
public static void main(String[] args) {
Compartment<String> test = new Compartment<>("Hello", "Hello");
System.out.println(test.Greater());
}
}
ArrayList 的几个劣势:
private class Node<E>
{
E data;
//Node<E> type is an reference(address)
Node<E> next;
Node(E data, Node<E> next) {
this.data = data;
this.next = next;
}
}
初始化:
//empty list
Node<E> mylist = new Node<E>(newData, null);
链表需要同时管理两个指针:
因此,head 的初始化为首元素的地址。如果值为 null,则链表不存在元素。因此,链表的初始化可以写成下面的形式:
//head has default value null
//right hand side equals new Node<E>(newData, null);
//after init, head points to the first Node
head = new Node<E>(newData, head);
该方式会在现有的 head 之前添加元素:
很容易看出来, addToFrount()
方法无论是初始化还是添加节点到现有链表,其逻辑都如下:
head = new Node<E>(newData, head);
遍历需要使用到 current
指针:
current
的初始值与 head 相同currrent
的值会更新为 next
中的地址,也就是下一个节点的地址current
值为 null
时,表示当前链表已经没有元素,遍历结束
Node current = head;
while(current != null)
{
//do sth
current = current.next;
}
该方式中,新建的节点永远都处于链表的最后一位。
null
,因为每次添加的元素都会成为链表最后一个元素current
遍历链表,直到遍历到最后一位节点,//new date will always be the end of the list
Node<E> node = new Node<E> (newData, null);
//init current
Node<E> current = head;
//if there is only one node in the list, then point head to the node
if (head == null) {
head = node;
}
//else, trverse to the end of the list
else {
while(current.next != null) {
current = current.next;
}
//point the last node to the new created node to make it as the newest last one.
current.next = node;
}
注意前置循环添加元素和后置循环添加元素的两个循环条件不一样:
链表中的打印需要遍历整个链表。默认从开头开始打印。
public String toString()
{
String result = "";
Node<E> current = head;
while (current != null) {
result = result + current.data.toString() + "\n";
current = current.next;
}
return result;
}
链表中的遍历需要维护 current 指针。该循环的三个必要条件:
false
equal()
)是否存在匹配关键词的元素。有则返回 true
。false
。
public boolean contains(E key)
{
if(isEmpty()) {
return false;
}
boolean isFound = false;
Node<E> current = head;
while(current != null) {
if (key.equals(current.data)) {
isFound = true;
break;
}
}
return isFound;
}
删除节点前都需要考虑链表为空的情况。
由于 Java 的 GC 机制,删除的节点本身资源的释放会自动进行。我们只需要重新将 head 指向第二个节点即可。具体步骤:
public E removeFromFront()
{
if(isEmpty()) {
return null;
}
else {
E removedData = head.data;
head = head.next;
return removedData;
}
}
由于需要删除最后一个节点,removeFromRear()
需要对倒数第二个节点进行操作。又因为访问链表尾部元素必须通过遍历进行,如果以倒数第二个节点为循环结束点,循环条件需要从:
while(current.next != null)
更改为:
while(current.next.next != null)
因为上述这个条件是默认链表中至少存在 2 个节点的;因此必须对链表中只存在一个节点的情况进行单独处理。因此,整个 removeFromRear
的逻辑为:
head.next = null
时:public E removeFromRear()
{
E removedData;
if(isEmpty()) {
return null;
}
else if (head.next == null) {
removedData = head.data;
head = null;
}
else {
Node<E> current = head;
while(current.next.next != null)
{
current = current.next;
}
removedData = current.next.data;
current.next = null;
}
return removedData;
}
ArrayList 有数据访问上的优势:
递归本身可以想象成堆栈的过程。递归调用一次就等于 push 一次,栈顶的是递归的 base case。
IllegalArgumentException
控制输入范围(如果不满足条件,则抛出该异常)
使用 JavaFX 的类会继承自 javafx.application.Application
。该类中定义了一个抽象入口函数 start()
。需要使用 javaFX 的类都需要实现该函数(可以认为是 JavaFX 里的 main()
)
public class JavaFXTest extends Application {
public void start(Stage mainStage) {
//....
}
}
Events 代表了用户的操作。JavaFX 中的 Event 继承自是 ActionEvent :
如果需要处理 Events,需要实例化对应类型的 Events,比如鼠标点击按钮:
btn.setOnAction(new CustomEventHandler());
Events 的初始化需要 EventHandler
类型的实例来处理用户执行完操作以后到底会发生什么。为了将 Events 与 Handler 联系起来,JavaFX规定:
handle()
函数EventHandler
实现。EventHandler
包含了抽象函数 handle()
EventHandler
的特化类型(点击鼠标的类型是 ActionEvent
)
比如为点击按钮设置一个名为 CustomEventHandler
的类:
//CustomEventHandler implements EventHandler
//click button action has type ActionEvent
private class CustomEventHandler implements EventHandler<ActionEvent> {
//rewrite handle
//handle parameter has type T to ensure the parameter macting the event type
public void handle(ActionEvent event) {
System.out.println("Hello Wolrd!");
}
}
当 CustomEventHandler
定义完毕后,我们将其传递给了 setOnActrion()
成员方法。该步骤相当于我们将其注册为 Event Listener,用于反馈结果给点击按钮的行为。
EventHandler
必须是 private
类型,并且是内部类(Inner class)Panes 为整个 Stage 提供布局。其扮演了一个组件容器的角色:
StackPane root = new StackPane();
之后创建的组件会叠加到之前创建的组件上面。
实例化 Pane 之后,我们就可以将之间创建的组件通过 getChildren()
成员放入该容器中了:
//adding button element to the container
root.getChildren().add(btn);
Pane 与组件之间的关系是包含(contains)关系,可以用 Scene Graphs 的方式来描述(图中的粗线代表包含):
leaf node
EventHandler 可以用 inner class 的方式实现:
EventHandler<ActionEvent> CustomEventHandler = new EventHandler<>() {
public void handle(ActionEvent event) {
System.out.println("Helloworld");
};
};
//notice CustomEventHandler is not a cstr. it is an reference name now.
btn.setOnAction(CustomEventHandler);
<code>
如果只使用一次,可以写成匿名的形式直接作为 ''setOnAction()'' 的参数:
<code js>
btn.setOnAction(
new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
System.out.println("hello world");
};
}
);
上述的 EventHandler 还可以使用 Lambda 表达式进一步的改进:
btn.setOnAction(event -> System.out.println("helloWorld"));
handle()
可以省略,因为 EventHandler 中只有一个函数ActionEvent
也可以省略,Java 可以根据 EventHandler 自动推断类型handle()
的定义之后一行,连大括号都可以省去
ComboBox<String> pickScaleFrom = new ComboBox<>();
pickScaleFrom.getItems().addAll("Fahrenheit", "Celsius");
pickScaleFrom.getSelectionModel().selectFirst();
pickScaleTo.getSelectionModel().selectLast();
String scaleFrom = pickScaleFrom.getValue();
result.setText(String.format("%.2f", conversionResult));
abel from = new Label("From:");
TextField userInput = new TextField();
//userInput is an TextField instance
userInput.getCharacters().toString();
//define
Alert a = new Alert(AlertType.ERROR);
//title
a.setTitle("Error");
//error message
a.setHeaderText("Invaild Temperature");
//details
a.setContentText("That is not a valid tempature");
//keeping the dialog box show up, pause the program
a.showAndWait();
//keep the element in the same row
HBox input = new HBox();
//
VBox scales = new VBox();
input.setAlignment(Pos.CENTER);
scales.setSpacing(10);
root.getChildren().addAll(input, scales, convertButton, result);