======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);