======Exceptions, Data Structures, Recursion & GUIs======
//Course III notes//
----
====Exceptions and File I/O====
===Eexception 的定义===
* Exception 是一种 Object
* Exception 代表了某种出现在 **runtime** 时期的错误事件(exceptional event)。该事件会打断正常的程序流程。
==Exception 的常见例子==
* NullPointerException
* ArrayIndexOutOfBoundsException
* StringIndexOutOfBoundsException
* ArithmeticException
* ClassCastException
* IllegalArgumentException
==什么是 thrown exception==
//throw// 代表了一个处理 exception 的过程。该过程包括:
* 针对错误创建一个 exception object
* 将该对象递交给 JVM 进行处理,看是否有方法可以处理该异常
一个 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)
===The call stack===
expection 的 throw 过程通常会在终端显示。这个过程被称为 //call stack trace//。这个过程展示了一系列的 method 导致了异常被抛出。//stack// 体现了这些 methods 的调用流程。
\\ \\
通常来说,JVM 会存储当前正在执行的 method 的状态:
* 在其调用另一个 method 之前,JVM 会存储其 local variables 和调用 method 的入口(referenece)
* 整个信息存储的数据结构就是一个 stack
* 当调用 Method 时,如果需要在当前 method 中调用其他 method:
* 首先将当前 method 的信息存储压栈(push),再进行第二个 method 的调用
* 如果当前 method 成功完成执行,则信息出栈 (pop)
* 可见 ''main()'' 是第一个压栈,最后一个出栈的。
{{ :cs:programming:java:courses:gtx_cs1311x:call_stack.svg.svg?400 |}}
===The Throwable Hierarchy===
//Exception// 类型存在以下继承关系:\\ \\
{{ :cs:programming:java:courses:gtx_cs1311x:throwable_hierarchy.jpg |}}
* 最顶部的是 ''Throwable'' 类
* ''Error'' 代表了**无法恢复**的错误类型(比如内存不足)
* ''Exception'' 代表了**可以处理**的错误类型
===Handling Exceptions===
Java 使用 ''try'' 和 ''catch'' 语句处理异常:
* 需要处理(catch) 的代码段应该放与 try block 中:
try {
statement(s)
}
* 定义 exception 的类型:
catch (ExceptionType identifier)
* 对该类型的 exception 的处理方法放置于 catch block 中:
catch (ExceptionType identifier) {
handleStatement(s)
}
如果进入了 ''catch'' 阶段,程序就不会再回到 ''try'' 阶段了。
==Multiple Catch Blocks==
try {
statement(s);
} catch (ExceptionType1 identifier) {
statement(s);
} catch (ExceptionType2 identifier) {
statement(s);
}
==Exception Controlled Loops==
* 对 try-catch block 使用循环反复检测时,需要将执行语句置于循环之外
* 如果是检查输入,每次检查之后都需要对 inputstream 使用 ''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);
}
==Exception 的优势==
* 分离错误处理逻辑
* 可以将错误进行分类处理
==Handling Multiple Exception==
每一个 catching 的分支完毕之后,都不会再继续执行其他的 catch。因此,error catching **分类的顺序**是非常重要的。有两点需要注意:
* 更具体的 error catching 放在更前面:如果是泛用的异常(比如 ''Exception'')在前,那么所有的 error 都会被该分支捕获,其他的分支永远不会被唤起。(Java 无法通过编译)
* 根据需求对 catching 分支排序。比如类型不匹配与除 0 两种处理,如果将类型不匹配(int)置于前面,那么输入 ''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());
}
==Combined Catch Blocks==
如果希望将多个异常归于一类,使用 ''|'' 运算符并列多个 exception:
catch (InputMismatchException | ArithmeticException cbe) {
input.nextLine();
System.out.println("Invaild Input");
}
==The finally Block==
''finally'' 修饰的 block 处于 try-catch block 的最后,代表无论是否出现异常都会执行的代码段。通常可以放一些必要的清理代码:
try {
//....
}
catch (Exception e) {
System.out.println(e.getMessage());
}
finally {
input.nextLine();
}
====File I/O====
===Input & Output 的使用===
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();
简单的实现文件:{{ :cs:programming:java:courses:gtx_cs1311x:filetest.java |}}
==FileNotFoundException==
在有 ''File'' 类型的 Object 参与的程序中,''FileNotFoundException'' 不可省略。但也无需为其指定 catch 分支;只需要**声明**(导入) ''FileNotFoundException'' 即可:
import java.io.FileNotFoundException;
exception 分为 checked 和 unchecked 两种类型。checked exception 指必须做特别处理的 exception,比如上面的例子。unchecked exception 通常指某些多变的,非常难以管理的 excpetion。这种 expetion 不会留具体的处理方式。
==input==
- 以具体路径为参数实例化 ''File'' 对象
- 使用 ''Scanner'' 对象读取该 ''File'' 对象的内容
- 使用 ''Scanner'' 的 method 对文件进行读取(比如 ''nextLine'')
String inputFileName = "text.txt"
File fileIn = new File(inputFileName);
Scanner fileScan = new Scanner(fileIn);
==output==
- 同样需要使用路径实例化的 ''File'' 对象
- 使用 ''PrintWriter'' 对象读取该 ''File'' 对象的内容
- 使用 各种 ''print'' 将内容写入到文件中
File fileOut = new File(keyword + "In" + inputFileName);
filePrint = new PrintWriter(fileOut);
filePrint.printf("Line %d contains %s\n",lineNum, keyword);
==重要的 method==
* ''String.contians(keyword)'':检查 String 中是包含 ''keyword'' 关键字
===自定义 exception===
我们可以对 exception 进行更加的特例化:只需要根据需求**继承**对应的 exception 类即可,比如:
public class DivideByZeroException extends ArithmeticException{
public DivideByZeroException() {
super("Divide by zero.");
}
}
这里的 ''super("Divide by zero.")'' 调用了父类的构造函数,String 参数会作为 ''getMessage()'' 的返回值。
==使用 throw operator==
与 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===
==什么是 Delimited Files==
//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==
输出 CSV 只要以上述的格式,使用 ''PrintWriter'' 对象将其输出到文本中即可:
File fileOut = new File("SortedWolves.csv");
PrintWriter filePrint = new PrintWriter(fileOut);
filePrint.println(wolf.getRank() + "," + wolf.getSize());
==处理 CSV==
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[]'',某些场景需要转换为指定格式的数据。
==Regular Expressions and split()==
''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 处理 CSV==
//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++;
}
====Lists====
//List// Java 中以 Interface 的形式出现。不同类型的数据都会基于该 Interface 设计 class。//LIst// 的几个基本方法:
* ''add(E e)'':添加元素
* ''clear()'' :清空 //List//
* ''contains(Object o)'' :查询是否包含某个元素
* ''remove(index)'' : 删除指定位置的元素
* ''size()'':返回 //List// 大小
===ArrayList===
* an implementation of the List interface with an array as the underlying data structure for storing items
* Arraylist 声明了一个 名为 ''elementData'' 的 array instance; 类型为 ''Object[] elementData'',以及一系列的配套 methods。
* 如果 array 空间不够,会创建一个新的 Object array
==Creating an ArrayList==
* 创建时不需要指定 type(//Raw type//)
* 当 parameter 提供的时候,arraylist 会转换为对应的版本类型(//parameterized type//)
* 未指定长度的 arraylist 长度为 ''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 aList = new ArrayList(initialCapacity);
//type sepcified creataion (after java 7). type on righthandside can be omited.
ArrayList playlist = new ArrayList<>(5);
指定类型的声明方法只限制了可以调用的 mothod;也就是说,其声明类型依然是 ''Object[]'',而实例(对象)类型才是被指定的类型。
* [[https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html|arraylist doc]]
* 其他实现的 interface: //Serializable, Cloneable, Iterable, Collection, List, RandomAccess//
===ArrayList 的相关操作==
ArrayList 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());
==Autoboxing==
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 intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
for(int num : intList) {
System.out.println(num);
}
}
====LinkedLists====
Java 中自带 ''GenericLinkedList'' 类。初始化方法:
GenericLinkedList newList = new GenericLinkedList<>(iniParamterList);
===Generic Classes===
在 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
之后所有与 generic 相关的 name, 返回值就都可以用 ''T'' 修饰了:
public class Bin {
private Object content;
public Bin(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
//instantiate, a specific type needed
Bin car = new Car("yuhina", "spark", 2037));
//call, casting is nolonger needed.
car.getContent();
==包含多种类型的 generic class==
如果希望一个 generic class 特化多个类型,可以直接在 typename list 里面添加:
className
在通用类的内部可以使用声明类型使指定的变量和方法一致:
public class Bin {
private X content1;
private Y content2;
public X getContent1() {
return content1;
}
public void setContent(Y content2) {
this.content2 = content2;
}
//intansiate
Bin = new Bin(Car("Yuhina", "Spark", 2037), Dog("puppy"));
==其他 type parameter 的用法==
* 限制 T 的类型必须是某个类的子类:
//T must be the subclass type of the Insert class
Bin
* 限制 T 的类型必须是某个 interface 的实现:
Bin
* 以上限制可以叠加:
Bin
==实例:结合使用 type parameter 和 Comparable==
//T is an Comparable type(can call compareTo())
public class Compartment {
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 test = new Compartment<>("Hello", "Hello");
System.out.println(test.Greater());
}
}
===LinkedList 的原理===
ArrayList 的几个劣势:
* 初始化需要占指定空间
* 插入非尾部元素需要移动其他元素
==LinkedList 的基础元素==
* Node:
* 存储数据
* 存储指向下一个元素的地址
private class Node
{
E data;
//Node type is an reference(address)
Node next;
Node(E data, Node next) {
this.data = data;
this.next = next;
}
}
初始化:
//empty list
Node mylist = new Node(newData, null);
==head 与 current==
链表需要同时管理两个指针:
* head 指向**链表**的起始元素地址
* current 指向**当前**元素所在的地址
因此,head 的初始化为首元素的地址。如果值为 null,则链表不存在元素。因此,链表的初始化可以写成下面的形式:
//head has default value null
//right hand side equals new Node(newData, null);
//after init, head points to the first Node
head = new Node(newData, head);
==addToFront()==
该方式会在现有的 head 之前添加元素:
- 创建新节点:将该节点指向 head
- 更新 head:让 head 指向刚被创建的新节点
{{ :cs:programming:java:courses:gtx_cs1311x:add_to_front.svg?230 |}}\\ \\
很容易看出来, ''addToFrount()'' 方法无论是初始化还是添加节点到现有链表,其逻辑都如下:
head = new Node(newData, head);
==front traversal==
遍历需要使用到 ''current'' 指针:
- 遍历从 head 开始:''current'' 的初始值与 head 相同
- 循环中,''currrent'' 的值会更新为 ''next'' 中的地址,也就是下一个节点的地址
- 当 ''current'' 值为 ''null'' 时,表示当前链表已经没有元素,遍历结束
Node current = head;
while(current != null)
{
//do sth
current = current.next;
}
==addToRear()==
该方式中,新建的节点永远都处于链表的最后一位。
- 初始化节点:节点的 next 初始值为 ''null'',因为每次添加的元素都会成为链表最后一个元素
- 如果链表只有一个节点,那么当前的节点就是 head 的位置
- 否则,使用 ''current'' 遍历链表,直到遍历到最后一位节点,
- 修改之前的节点指向地址为之前初始化的节点。
{{ :cs:programming:java:courses:gtx_cs1311x:add_to_rear.svg?500 |}}
注意循环的条件:''current.next != null''
//new date will always be the end of the list
Node node = new Node (newData, null);
//init current
Node 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;
}
注意前置循环添加元素和后置循环添加元素的两个循环条件不一样:
* 前置需要验证的是当前节点是否为空
* 后置需要验证的是当前节点指向的节点是否为空,即是否为最后一位元素
==toString()==
链表中的打印需要遍历整个链表。默认从开头开始打印。
public String toString()
{
String result = "";
Node current = head;
while (current != null) {
result = result + current.data.toString() + "\n";
current = current.next;
}
return result;
}
链表中的遍历需要维护 current 指针。该循环的三个必要条件:
* current 从 head 处开始
* current 停止的条件是 current 值不会 null
* current 的迭代方式是让其指向下一个节点的地址
==contains()==
* 如果链表为空,直接返回 ''false''
* 如果不为空,则遍历查询比较(使用 ''equal()'')是否存在匹配关键词的元素。有则返回 ''true''。
* 搜索完毕没找到返回 ''false''。
public boolean contains(E key)
{
if(isEmpty()) {
return false;
}
boolean isFound = false;
Node current = head;
while(current != null) {
if (key.equals(current.data)) {
isFound = true;
break;
}
}
return isFound;
}
==removeFromFront()==
删除节点前都需要考虑链表为空的情况。
由于 Java 的 GC 机制,删除的节点本身资源的释放会自动进行。我们只需要重新将 head 指向第二个节点即可。具体步骤:
* 检测当前链表是否为空,如为空返回 null
* 获取被删除节点中的数据
* 重新定向 head
* 返回被删除节点的数据
public E removeFromFront()
{
if(isEmpty()) {
return null;
}
else {
E removedData = head.data;
head = head.next;
return removedData;
}
}
==removeFromRear()==
由于需要删除最后一个节点,''removeFromRear()'' 需要对倒数第二个节点进行操作。又因为访问链表尾部元素必须通过遍历进行,如果以倒数第二个节点为循环结束点,循环条件需要从:
while(current.next != null)
更改为:
while(current.next.next != null)
因为上述这个条件是默认链表中至少存在 2 个节点的;因此必须对链表中只存在一个节点的情况进行单独处理。因此,整个 ''removeFromRear'' 的逻辑为:
- 如果链表为空,返回 full
- 如果链表长度为 1,也就是 ''head.next = null'' 时:
- 缓存节点的数据
- 将 head 设为 null (代表空链表)
- 返回缓存数据
- 如果链表长度大于 1:
- 遍历到倒数第二个节点
- 缓存最后一个节点的数据
- 将倒数第二个节点的指向设为 null,使之成为最后一个节点
{{ :cs:programming:java:courses:gtx_cs1311x:removefromrear.svg?350 |}}
public E removeFromRear()
{
E removedData;
if(isEmpty()) {
return null;
}
else if (head.next == null) {
removedData = head.data;
head = null;
}
else {
Node current = head;
while(current.next.next != null)
{
current = current.next;
}
removedData = current.next.data;
current.next = null;
}
return removedData;
}
==Linked Lists vs. ArrayLists==
ArrayList 有数据访问上的优势:
* random access 非常快(通过下标即可)
* 链表需要通过遍历才能进行访问,最差情况为 $O(n)$
====Recursion====
* method 可以调用其本身
* mehod 应该具有某种机制作为终止条件,允许返回
递归本身可以想象成堆栈的过程。递归调用一次就等于 push 一次,栈顶的是递归的 base case。
==递归正确运行的三个条件==
* 终止条件(base call):不需要进行递归调用就可以进行 return 的语句
* 递减步骤(reduction step):使递归调用朝着终止条件的方向进行
* 递归调用(recursive call)
==可能用于改进的手段==
* 对输入范围进行额外的限定
* 使用 ''IllegalArgumentException'' 控制输入范围(如果不满足条件,则抛出该异常)
====JavaFX====
* //Stage//: window
* //Scene//: 内容
{{ :cs:programming:java:courses:gtx_cs1311x:javafx_base_flow.svg?400 |}}
===JavaFX essentials===
==入口函数的定义==
使用 JavaFX 的类会继承自 ''javafx.application.Application''。该类中定义了一个**抽象**入口函数 ''start()''。需要使用 javaFX 的类都需要实现该函数(可以认为是 JavaFX 里的 ''main()'')
public class JavaFXTest extends Application {
public void start(Stage mainStage) {
//....
}
}
==结构总览==
* //Stage//:当前的窗口
* //Scene//:由窗口中的元素以及其布局组成
==Events Handling==
//Events// 代表了用户的操作。JavaFX 中的 //Event// 继承自是 //ActionEvent// :
\\ \\
{{ :cs:programming:java:courses:gtx_cs1311x:javafx_events.jpg?400 |}}
\\ \\
如果需要处理 //Events//,需要实例化对应类型的 //Events//,比如鼠标点击按钮:
btn.setOnAction(new CustomEventHandler());
//Events// 的初始化需要 ''EventHandler'' 类型的实例来处理用户执行完操作以后到底会发生什么。为了将 //Events// 与 //Handler// 联系起来,JavaFX规定:
* //Handler// 中必须定义一个 ''handle()'' 函数
* 通过继承接口 ''EventHandler'' 实现。''EventHandler'' 包含了抽象函数 ''handle()''
* 用户需要根据 //Event// 的类型决定 ''EventHandler'' 的特化类型(点击鼠标的类型是 ''ActionEvent'')
比如为点击按钮设置一个名为 ''CustomEventHandler'' 的类:
//CustomEventHandler implements EventHandler
//click button action has type ActionEvent
private class CustomEventHandler implements EventHandler {
//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 & Scene Graphs==
Panes 为整个 //Stage// 提供布局。其扮演了一个组件容器的角色:
StackPane root = new StackPane();
之后创建的组件会叠加到之前创建的组件**上面**。
实例化 Pane 之后,我们就可以将之间创建的组件通过 ''getChildren()'' 成员放入该容器中了:
//adding button element to the container
root.getChildren().add(btn);
Pane 与组件之间的关系是**包含(//contains//)**关系,可以用 //Scene Graphs// 的方式来描述(图中的粗线代表包含):
\\ \\
{{ :cs:programming:java:courses:gtx_cs1311x:scene_graph.jpg |}}
\\
其他可选的 layout 和组件有:
\\ \\
{{ :cs:programming:java:courses:gtx_cs1311x:elements.jpg?400 |}}
\\ \\
* //Layout// 类别中的类都是 ''StackPane'' 的子类,代表了不同的布局方式。如果没有合适的,可以自己通过继承 ''Pane'' 来自定义布局。参考:[[https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/StackPane.html|文档]]
* 元素组件被称为 ''leaf node''
* //Layout// 本身不必是根部节点,也可以是**分支**节点(嵌套布局)
[[https://docs.oracle.com/javafx/2/layout/builtin_layouts.htm|JavaFX Layouts Example]]
===Anonymous Inner Classes===
EventHandler 可以用 inner class 的方式实现:
EventHandler 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);
如果只使用一次,可以写成匿名的形式直接作为 ''setOnAction()'' 的参数:
btn.setOnAction(
new EventHandler() {
public void handle(ActionEvent event) {
System.out.println("hello world");
};
}
);
===Lambda Expressions===
上述的 EventHandler 还可以使用 Lambda 表达式进一步的改进:
btn.setOnAction(event -> System.out.println("helloWorld"));
* ''handle()'' 可以省略,因为 EventHandler 中只有一个函数
* ''ActionEvent'' 也可以省略,Java 可以根据 EventHandler 自动推断类型
* 如果 ''handle()'' 的定义之后一行,连大括号都可以省去
===Addtional Operations===
{{ :cs:programming:java:courses:gtx_cs1311x:temperatureconvertergui.java |Demo Code}}
==ComboBox==
* 定义 ComboBox
ComboBox pickScaleFrom = new ComboBox<>();
* 添加选项到 Combobox
pickScaleFrom.getItems().addAll("Fahrenheit", "Celsius");
* 设定默认选项
pickScaleFrom.getSelectionModel().selectFirst();
pickScaleTo.getSelectionModel().selectLast();
* 从 ComboBox 中获取选项值
String scaleFrom = pickScaleFrom.getValue();
* 打印值到 Label 区域
result.setText(String.format("%.2f", conversionResult));
==Label==
* 创建 Label
abel from = new Label("From:");
==TextField==
* 创建 TextField
TextField userInput = new TextField();
* 获取 TextField 中的内容(in string format)
//userInput is an TextField instance
userInput.getCharacters().toString();
==Error dialog box==
{{ :cs:programming:java:courses:gtx_cs1311x:errorbox.jpg?300 |}}
//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();
==Layout==
* 创建 Layout
//keep the element in the same row
HBox input = new HBox();
//
VBox scales = new VBox();
* 设置对齐方式:
input.setAlignment(Pos.CENTER);
* 设置间距
scales.setSpacing(10);
* Layout 的嵌套
root.getChildren().addAll(input, scales, convertButton, result);