作业一 第一题 题目 计算多项式1!+2!+3!…+n!,当多项式之和超过10000时停止,并输出累加之和以及n的值。
分析 将多项式记为$\sum\limits_{i=1}^{n}i!$ ,那么显然程序需要两部分
对$i$求其阶乘$i!$的部分,在factorial方法中完成
对$i!$进行循环求和的部分,在main方法中完成
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class First { public static void main (String[] args) { int sum = 0 ; int n = 1 ; for (int i = 1 ; sum <= 10000 ; i++) { sum += factorial(i); n = i; } System.out.println("sum=" + sum); System.out.println("n=" + n); } public static int factorial (int x) { return x == 1 ? 1 : x * factorial(x - 1 ); } }
运行截图
第二题 题目 从标准输入端输入一个字符,判断字符是数字、西文字母还是其他的字符。
分析 题目限定使用标准输入 获取字符 ,即需要使用(char) System.in.read()
,判断字符类型只需判断区间即可,java会隐式地将char转为ASCII码(int)进行比较。
值得注意的是java必须对可能被抛出的异常进行捕获或声明,否则编译不予通过,这体现了语言的健壮性。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.io.IOException;public class Second { public static void main (String[] args) { try { char ch = (char ) System.in.read(); if ('0' <= ch && ch <= '9' ) System.out.println(ch + " is digit" ); else if ('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' ) System.out.println(ch + " is letter" ); else System.out.println(ch + " is other" ); } catch (IOException e) { e.printStackTrace(); } } }
运行截图
第三题 题目 利用辗转相除法(欧几里得算法)求两个正整数的最大公约数
分析 欧几里得算法依赖于原理: $gcd(a,b) = gcd(b, a\ mod\ b)$
$proof:$
不妨设$a>b$且$r=a\ mod\ b$ ,$r$不为0
那么有$a=kb+r$
假设d是a,b的一个公约数,记作$d|a$, $d|b$
由$r = a - kb$得$\frac{r}{d}=\frac{a}{d}-k\frac{b}{d}$
因为$d|a$, $d|b$,所以$\frac{a}{d}-k\frac{b}{d}\in{Z}$
即$\frac{r}{d}\in{Z}$,$d|r$,$d|(a\ mod\ b)$
因为$d|a$, $d|b$, $d|(a\ mod\ b)$
所以$d_{max}|a$, $d_{max}|b$, $d_{max}|(a\ mod\ b)$
即 $gcd(a,b) = gcd(b, a\ mod\ b)$
$Q.E.D.$
值得注意的是,我们看似只证明了$a>b$时的辗转相除法,但算法实现时不需要考虑$a<b$的情况,因为如果$a<b$,那么$a\ mod\ b=a$,即 $gcd(a,b) = gcd(b, a\ mod\ b)=gcd(b,a)$ ,仍然是$gcd(较大数, 较小数)$的形式。
另外,Scanner对象使用后需要关闭。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.util.Scanner;public class Third { public static void main (String[] args) { Scanner reader = new Scanner (System.in); try { int x = reader.nextInt(); int y = reader.nextInt(); System.out.println("gcd(" + x + "," + y + ")=" + gcd(x, y)); } finally { reader.close(); } } public static int gcd (int a, int b) { if (a % b == 0 ) { return b; } else { return gcd(b, a % b); } } }
运行截图
第四题 题目 假设一个数在1000到1100之间,那除以3结果余2,除以5结果余3,除以7结果余2(中国剩余定理),求此数。
分析 题目限定了未知数的范围,应该是在暗示使用穷举法。
这道题用中国剩余定理(CRT)做的话最小答案应该是23,另外java没有引用传递,我目前还没办法编写CRT算法所需的拓展欧几里得除法。
代码 1 2 3 4 5 6 7 8 public class Fourth { public static void main (String[] args) { for (int x=1000 ;x<=1100 ;x++){ if (x % 3 == 2 && x % 5 == 3 && x % 7 == 2 ) System.out.println(x); } } }
运行截图
第五题 题目 小球从100米高度自由落下,每次触地后反弹到原来高度的一半,求第10次触地时经历的总路程以及第10次反弹高度。
分析 基本上是学习每种语言必写的一道题了,按照题意模拟即可。
要注意的是“第10次触地时 经历的总路程”和与“第10次反弹高度”同义的“第10次触地后 的反弹高度”
我们进行模拟的时候只能模拟一个来回,即触地前的下落过程和触地后的反弹过程,那么对于“触地时” 这种发生在一个来回中间的事件,我们要么在十个来回结束后减去最后一次的半个来回(反弹过程) ,要么单独只模拟九个来回,最后一个来回单独算,要么在模拟来回的过程中加一个中断if (i == 10) sumTen=sum
……总之方法很多,我选择了第一个。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 public class Fifth { public static void main (String[] args) { double h = 100 ; double sum = 0 ; for (int i = 0 ; i < 10 ; i++) { sum += h; h /= 2 ; sum += h; } System.out.println("Total distance at 10th touchdown=" + (sum - h)); System.out.println("Height of rise after 10th touchdown=" + h); } }
运行截图
作业二 第一题 题目 读程序,写结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package com.wanpengxu.homework2;class A { public String Show (D obj) { return ("A and D" ); } public String Show (A obj) { return ("A and A" ); } } class B extends A { public String Show (B obj) { return ("B and B" ); } public String Show (A obj) { return ("B and A" ); } } class C extends B { public String Show (C obj) { return ("C and C" ); } public String Show (B obj) { return ("C and B" ); } } class D extends B { public String Show (D obj) { return ("D and D" ); } public String Show (B obj) { return ("D and B" ); } } public class mainTest { public static void main (String[] args) { A a1 = new A (); A a2 = new B (); B b = new B (); C c = new C (); D d = new D (); System.out.println(a1.Show(b)); System.out.println(a1.Show(c)); System.out.println(a1.Show(d)); System.out.println(a2.Show(b)); System.out.println(a2.Show(c)); System.out.println(a2.Show(d)); System.out.println(b.Show(b)); System.out.println(b.Show(c)); System.out.println(b.Show(d)); } }
答案 1 2 3 4 5 6 7 8 9 A and A A and A A and D B and A B and A A and D B and B B and B A and D
分析 先来看一下类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 classDiagram A <|-- B B <|-- C B <|-- D class A{ +Show(D obj) String +Show(A obj) String } class B{ +Show(B obj) String +Show(A obj) String } class C{ +Show(C obj) String +Show(B obj) String } class D{ +Show(D obj) String +Show(B obj) String }
继承链:从某个特定的类到其祖先的路径。
在类图中继承链便是从某个类沿向上箭头直到最初的祖先的路径。
动态绑定方法调用的优先问题 ,优先级由高到低依次为:一级调度this.show(O)、二级调度super.show(O)、三级调度this.show((super)O)、 四级调度super.show((super)O)
首先是a1对象的调用,由A a1 = new A();
知a1的编译时类型是A,运行时类型是A ,那么a1中的方法有:
自己定义的方法
1 2 3 public String Show (D obj) { return ("A and D" ); }
自己定义的方法
1 2 3 public String Show (A obj) { return ("A and A" ); }
a1.Show(b):a1中不含有参数为B的方法,由继承链知B可向上转型至A,那么调用2
,输出A and A
a1.Show(c):a1中不含有参数为C的方法,由继承链知C可向上转型至A,那么调用2
,输出A and A
a1.Show(d):a1中含有参数为D的方法,那么调用1
,输出A and D
接着是a2对象的调用,由A a2 = new B();
知a2的编译时类型是A,运行时类型是B ,那么a2中的方法有:
继承自A的方法
1 2 3 public String Show (D obj) { return ("A and D" ); }
继承自A但被自己覆盖的方法
1 2 3 public String Show (A obj) { return ("B and A" ); }
自己定义的方法
1 2 3 public String Show (B obj) { return ("B and B" ); }
这里可以使用反射机制进行验证,代码如下:
1 2 3 4 5 6 7 8 import java.lang.reflect.Method;... System.out.println(a2.getClass()); Class<? extends A > testa2 = a2.getClass(); Method[] methods = testa2.getMethods(); for (Method m : methods) { System.out.println(m); }
其中:
getClass()
用于获取变量运行时类型的类名;
getMethods()
用于获取Class变量中的所有方法(从父类继承的和自己声明的)
getDeclaredMethods()
用于获取Class变量自己声明的方法。
但是,由于a2的编译时类型是A,所以在代码中只能调用1
和2
,否则会报错,即使你的实例中真的有3
这个方法。
a2.Show(b):a2中含有参数为B的方法,但不能调用,由继承链知B可向上转型至A,那么调用2
,输出B and A
a2.Show(c):a2中不含有参数为C的方法,由继承链知C可向上转型至A,那么调用2
,输出B and A
a2.Show(d):a2中含有参数为D的方法,那么调用1
,输出A and D
最后是b对象的调用,由B b = new B();
知b的编译时类型是B,运行时类型是B ,那么b中的方法有:
继承自A的方法
1 2 3 public String Show (D obj) { return ("A and D" ); }
继承自A但被自己覆盖的方法
1 2 3 public String Show (A obj) { return ("B and A" ); }
自己定义的方法
1 2 3 public String Show (B obj) { return ("B and B" ); }
b.Show(b):b中含有参数为B的方法,那么调用3
,输出B and B
b.Show(c):b中不含有参数为C的方法,由继承链知C可向上转型至B,那么调用3
,输出B and B
b.Show(d):b本类不中含有参数为D的方法,但其超类中含有参数为D的方法,那么调用1
,输出A and D
第二题 题目 读程序,写结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.wanpengxu.homework2;class Base { private String name = "base" ; public Base () { tellName(); } public void tellName () { System.out.println("Base tell name: " + name); } } public class Derived extends Base { private String name = "derived" ; public Derived () { tellName(); } public void tellName () { System.out.println("Derived tell name: " + name); } public static void main (String[] args) { new Derived (); } }
答案 1 2 Derived tell name: null Derived tell name: derived
分析 流程如下:
从main
方法进入,执行new Derived();
语句。
调用Derived的构造方法Derived()
。
因class Derived extends Base
,所以Derived()
的第一句是由编译器添加的Base()
,那么调用其父类的构造方法Base()
。
Base类的构造方法初始化自己的属性,首先执行private String name = "base";
,使Base.name=”base”,随后调用tellName()
方法,因为运行时类型是Derived,所以根据继承链,优先匹配Derived中的tellName()
方法,但因为此时还没有到Derived初始化,所以this.name=null,打印出的结果为
随后Base()
方法结束,继续执行Derived()
。
此时Derived()
初始化自己的属性,执行private String name = "derived";
,使this.name=”derived”,随后调用tellName()
方法,同样的原因,匹配Derived中的tellName()
方法,但此时this.name=”derived”,打印出的结果为
1 Derived tell name: derived
随后Derived()
方法结束,new Derived();
语句完成,main
方法结束,程序结束。
第三题 题目 生成动物,要求如下:
循环通过标准输入端输入需要生成的动物,当遇到结束标志,则结束程序运行。
每次生成动物,通过标准输出端显示动物的信息。
动物的信息包括:目前所有动物的总数,当前这一类动物的总数。
整个程序结构用工厂模式设计,保证将来动物园有新的动物加入时,程序可扩展。
答案 项目结构树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 D:. │ IdeaProjects.iml │ ├─.idea │ ... ├─out │ ... └─src └─com └─wanpengxu └─homework2 │ Derived.java │ mainTest.java │ └─third │ Main.java │ ├─factory │ AnimalFactory.java │ └─product Animal.java Cat.java Dog.java Panda.java
类图
代码
Animal.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.wanpengxu.homework2.third.product;public class Animal { public static int num = 0 ; public String name; public Animal (String name) { this .name = name; num += 1 ; System.out.println("现在共有" + num + "只动物!" ); } public String getInformation () { return "\"" + name + "\"" ; } }
Cat.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.wanpengxu.homework2.third.product;public class Cat extends Animal { private static int _num = 0 ; public Cat (String name) { super (name); Cat._num += 1 ; System.out.println("你获得了1只猫!现在已有" + getCatNumber() + "只猫!" ); } @Override public String getInformation () { return "\"" + name + "\":一只猫" ; } public int getCatNumber () { return Cat._num; } }
Dog.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.wanpengxu.homework2.third.product;public class Dog extends Animal { private static int _num = 0 ; public Dog (String name) { super (name); Dog._num += 1 ; System.out.println("你获得了1只狗!现在已有" + getDogNumber() + "只狗!" ); } @Override public String getInformation () { return "\"" + name + "\":一只狗" ; } public int getDogNumber () { return Dog._num; } }
Panda.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.wanpengxu.homework2.third.product;public class Panda extends Animal { private static int _num = 0 ; public Panda (String name) { super (name); Panda._num += 1 ; System.out.println("你获得了1只熊猫!现在已有" + getPandaNumber() + "只熊猫!" ); } @Override public String getInformation () { return "\"" + name + "\":一只熊猫" ; } public int getPandaNumber () { return Panda._num; } }
AnimalFactory.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.wanpengxu.homework2.third.factory;import com.wanpengxu.homework2.third.product.*;public class AnimalFactory { public static Animal addAnimal (String type, String name) { return switch (type.toLowerCase()) { case "cat" -> new Cat (name); case "dog" -> new Dog (name); case "panda" -> new Panda (name); default -> null ; }; } }
Main.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.wanpengxu.homework2.third;import com.wanpengxu.homework2.third.factory.AnimalFactory;import com.wanpengxu.homework2.third.product.Animal;import java.util.ArrayList;import java.util.Scanner;public class Main { public static void main (String[] args) { ArrayList<Animal> animals = new ArrayList <>(); System.out.println("欢迎来到动物工厂!工厂现在可以生产以下动物:Cat, Dog, Panda!输入Quit退出!" ); try (Scanner sc = new Scanner (System.in)) { while (true ) { System.out.print("请输入想生产的动物类型:" ); String type = sc.next(); if (type.equalsIgnoreCase("Quit" )) break ; System.out.print("请输入它的名字:" ); String name = sc.next(); Animal animal = AnimalFactory.addAnimal(type, name); if (animal == null ) System.out.println("请输入可生产的动物!" ); else animals.add(animal); } } System.out.println("生成完毕!现在你有:" ); for (Animal animal : animals) System.out.println(animal.getInformation()); } }
运行截图
分析 题目要求使用工厂模式,那么按照工厂模式的实现,有:
要有一个父类Animal,这里因为动物本身有一些公用的属性和方法,所以最好不要定义为abstract类;
要有一些继承自Animal的具体的类作为某种动物,如Cat, Dog, Panda;
需要一个工厂类AnimalFactory生成基于给定信息的实体类的对象,这里我是用增强型switch实现的,当然也可以用普通switch或if-else if-else实现;
需要一个使用该工厂的类(用户类)来操作工厂完成生产任务。我加入了一点功能:生成动物时为其命名,并在退出后显示已生成动物名和其类型。另外,使用了可以自动关闭资源的try-with-resources语法,它的功能类似于try-finally,但语法十分精简。
因为采用了工厂模式,所以拓展动物种类的操作也很简单:
复制一份动物类,将其类名重构为新增的动物,方法名同理;
更改一些字符串以与新增动物相对应;
在AnimalFactory类中新增一行case。
作业三 第一题 题目
答案 项目结构树 1 2 3 4 5 6 7 8 9 10 D:. │ ... └─src └─edu └─wanpengxu └─homework3 └─first Student.java StudentAdministrator.java Test.java
代码 Student.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package edu.wanpengxu.homework3.first;public class Student { private String name; private String studentID; private int age; private String major; public Student (String name, String studentID, int age, String major) { this .name = name; this .studentID = studentID; this .age = age; this .major = major; } public String getName () { return name; } public String getStudentID () { return studentID; } public int getAge () { return age; } public String getMajor () { return major; } public void setName (String name) { this .name = name; } public void setStudentID (String studentID) { this .studentID = studentID; } public void setAge (int age) { this .age = age; } public void setMajor (String major) { this .major = major; } public void display () { System.out.println("name:" + name); System.out.println("studentID:" + studentID); System.out.println("age:" + age); System.out.println("major:" + major); System.out.println(); } }
StudentAdministrator.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package edu.wanpengxu.homework3.first;import java.util.ArrayList;import java.util.List;public class StudentAdministrator { private static final List<Student> database = new ArrayList <>(); public boolean add (Student student) { if (student.getStudentID().length()!=8 ){ return false ; } for (Student studentI:database){ if (studentI.getStudentID().equals(student.getStudentID())){ return false ; } } return database.add(student); } public List<Student> find (String name) { List<Student> foundStudent = new ArrayList <>(); for (Student student : database) { if (student.getName().equals(name)) { foundStudent.add(student); } } return foundStudent; } public boolean delete (String studentID) { return database.removeIf(student -> student.getStudentID().equals(studentID)); } public void showAll () { for (Student student : database) { student.display(); } } }
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 package edu.wanpengxu.homework3.first;import java.io.Console;import java.util.*;public class Test { public static void main (String[] args) { HashMap<String, String> administrators = new HashMap <>(); StudentAdministrator studentAdministrator = null ; System.out.println("Welcome to undergraduate management system!" ); try (Scanner scanner = new Scanner (System.in)) { boolean logged = false ; while (true ) { System.out.println("Please select: [C]Creat Administrator\t[L]Login\t[A]Add\t[D]Delete\t[F]Find\t[S]Show All\t[E]Exit" ); String type = scanner.next(); if (type.equalsIgnoreCase("C" )) { System.out.println("Please input your username:" ); String username = scanner.next(); System.out.println("Please input your password:" ); String password = scanner.next(); if (administrators.get(username) == null ) { administrators.put(username, password); System.out.println("Creat succeeded!" ); } else { System.out.println("Creat failed!" ); } } else if (type.equalsIgnoreCase("L" )) { System.out.println("Please input your username:" ); String username = scanner.next(); System.out.println("Please input your password:" ); String password = scanner.next(); if (administrators.get(username) != null && administrators.get(username).equals(password)) { System.out.println("Login succeeded!" ); logged = true ; studentAdministrator = new StudentAdministrator (); } else { System.out.println("Login failed!" ); } } else if (type.equalsIgnoreCase("E" )) { System.out.println("Exit succeeded!" ); break ; } else { if (logged) { if (type.equalsIgnoreCase("A" )) { System.out.println("Please input student's name:" ); String name = scanner.next(); System.out.println("Please input 8-digit student's studentID:" ); String studentID = scanner.next(); System.out.println("Please input student's age:" ); int age; try { age = scanner.nextInt(); } catch (InputMismatchException ex) { System.out.println("Invalid input!" ); scanner.next(); continue ; } System.out.println("Please input student's major:" ); String major = scanner.next(); if (studentAdministrator.add(new Student (name, studentID, age, major))) { System.out.println("Added successfully!" ); } else { System.out.println("Added failed!" ); } } else if (type.equalsIgnoreCase("D" )) { System.out.println("Please input 8-digit student's studentID:" ); String studentID = scanner.next(); if (studentAdministrator.delete(studentID)) { System.out.println("Delete successfully!" ); } else { System.out.println("Delete failed!" ); } } else if (type.equalsIgnoreCase("F" )) { System.out.println("Please input student's name:" ); String name = scanner.next(); List<Student> found = studentAdministrator.find(name); System.out.println("Find " + found.size() + " students!" ); for (Student student : found) { student.display(); } } else if (type.equalsIgnoreCase("S" )) { studentAdministrator.showAll(); } } else { System.out.println("Please login first!" ); } } } } } }
运行截图
分析 一道比较简单的模拟题,大二时我们在程序设计综合实践课上做过更加复杂的例子。
首先我认为需要有学生类Student
还需要一个学生管理员类StudentAdministrator用于管理学生
最后在主类里调用学生管理员对象去管理学生对象
要注意的是这里涉及到了数据库的知识,学生表(在这里是List<Student>
)的主键是学号 ,所以添加、删除这些操作都需要使用学号完成,同时考虑到非主键会有重复,所以查找操作使用了姓名,当然这种写法只适用于小型系统,如学院、学校的数据库系统,如果同名人数多的系统,如某省的户籍系统,还需要加入其他限定条件。
在基本要求外做了一个比较简单的访问控制,并没有保存它使其持久化。
注意到控制台程序的每个选项间距都不同,因为制表符\t
用空格补全其左边字符串,使其长度到8的整数倍(正好是8的倍数也补,不然体现不出按制表键)。
第二题 题目 一元二次方程类:
输入一元二次方程的三个系数,确定这个方程式。
标明一个一元二次方程的属性。
如果是一个一元二次方程需要的操作(注意方法的权限):
获取三个系数,确立一元二次方程.
求$\delta=b^2-4ac$
求根(可能是实根,也可能是虚根)
如果二次系数$a$为0 ,主动抛出异常,因为这不满足一元二次方程的条件。
1 public class AWrongException extends Exception
把生成的一元二次方程对象(包含根的值)序列化并保存在文件file1.data
中。
根求出之后,再把对象序列化到文件file1.data中。
要求:
实现一元二次方程类
在主类中调用这个一元二次方程类。(编程用命令行或者可视化方法获取系数 $a$,$b$,$c$)
答案 项目结构树 1 2 3 4 5 6 7 8 9 D:. └─src └─edu └─wanpengxu └─homework3 └─second AWrongException.java Equation.java Test.java
代码 核心代码 Equation.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import java.io.Serializable;import java.lang.Math;public class Equation implements Serializable { private final double a, b, c; private double x1, x2; private double r, v; int type; public Equation (double a, double b, double c) throws AWrongException { if (a == 0 ) throw new AWrongException ("二次项系数不能为0!" ); this .a = a; this .b = b; this .c = c; System.out.println("成功创建一元二次方程组:" ); System.out.print((a == 1 ? "" : a) + "x^2" ); if (b != 0 ) System.out.print("+" + (b == 1 ? "" : b) + "x" ); if (c != 0 ) System.out.print("+" + c); System.out.println("=0" ); } private double calculateDelta () { return Math.pow(b, 2 ) - 4 * a * c; } public void solve () { double delta = calculateDelta(); if (Math.abs(delta) < 1e-5 ) { type = 0 ; x1 = x2 = -b / (2 * a); } else if (delta > 0 ) { type = 1 ; x1 = (-b - Math.sqrt(delta)) / (2 * a); x2 = (-b + Math.sqrt(delta)) / (2 * a); } else { type = 2 ; r = -b / (2 * a); v = Math.sqrt(-delta) / (2 * a); } } public void display () { switch (type) { case 0 -> System.out.println("该一元二次方程有两个相等的实数根:\nx1=x2=" + x1); case 1 -> System.out.println("该一元二次方程有两个不同的实数根:\nx1=" + x1 + '\n' + "x2=" + x2); case 2 -> System.out.printf("该一元二次方程有两个不同的复数根:\nx1=%1$s+%2$si\nx2=%1$s-%2$si\n" , r, v); } } public String getSolution () { return switch (type) { case 0 -> String.format("该一元二次方程有两个相等的实数根:\nx1=x2=%s" , x1); case 1 -> String.format("该一元二次方程有两个不同的实数根:\nx1=%s\nx2=%s" , x1, x2); case 2 -> String.format("该一元二次方程有两个不同的复数根:\nx1=%1$s+%2$si\nx2=%1$s-%2$si\n" , r, v); default -> throw new IllegalStateException ("Unexpected value: " + type); }; } }
AWrongException.java
1 2 3 4 5 6 7 package edu.wanpengxu.homework3.second;public class AWrongException extends Exception { AWrongException(String s){ super (s); } }
控制台程序 Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package edu.wanpengxu.homework3.second;import java.io.*;import java.util.Scanner;public class Test { public static void main (String[] args) { try (Scanner sc = new Scanner (System.in)) { System.out.println("请输入一元二次方程的系数:" ); double a = sc.nextDouble(); double b = sc.nextDouble(); double c = sc.nextDouble(); try { Equation equation = new Equation (a, b, c); equation.solve(); equation.display(); try (FileOutputStream fileOutputStream = new FileOutputStream ("file1.data" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream)) { objectOutputStream.writeObject(equation); System.out.println("已将一元二次方程对象序列化为byte数组保存至文件\"file1.data\"中" ); } catch (IOException e) { System.out.println(e.getMessage()); } } catch (AWrongException e) { System.out.println(e.getMessage()); e.printStackTrace(); } } try (FileInputStream fileInputStream = new FileInputStream ("file1.data" ); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream)) { Equation equation = (Equation) objectInputStream.readObject(); System.out.println("已将文件\"file1.data\"中的byte数组反序列化为一元二次方程对象" ); equation.display(); } catch (IOException | ClassNotFoundException e) { System.out.println(e.getMessage()); } } }
GUI程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 import com.formdev.flatlaf.FlatLightLaf;import javax.swing.*;import java.awt.*;import java.awt.event.*;import java.io.*;public class UnivariateQuadraticEquation { private JPanel mainPanel; private JTextField textFieldA; private JTextField textFieldB; private JTextField textFieldC; private JButton 求解Button; private JButton 序列化至文件Button; private JButton 清空Button; private JButton 退出Button; public static void main (String[] args) { try { UIManager.setLookAndFeel(new FlatLightLaf ()); } catch (Exception ex) { System.err.println("Failed to initialize LaF" ); } JFrame frame = new JFrame ("一元二次方程综合工具" ); final Toolkit toolkit = Toolkit.getDefaultToolkit(); toolkit.addAWTEventListener(e -> { if (e.getID() == KeyEvent.KEY_PRESSED) { KeyEvent evt = (KeyEvent) e; if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) { frame.dispose(); System.exit(0 ); } } }, AWTEvent.KEY_EVENT_MASK); Image icon = toolkit.getImage("img/CUMTlogo5.png" ); frame.setIconImage(icon); frame.setContentPane(new UnivariateQuadraticEquation ().mainPanel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null ); frame.setResizable(false ); frame.setVisible(true ); } public UnivariateQuadraticEquation () { 求解Button.addActionListener(e -> { try { double a = Double.parseDouble(textFieldA.getText()); double b = Double.parseDouble(textFieldB.getText()); double c = Double.parseDouble(textFieldC.getText()); Equation equation = new Equation (a, b, c); equation.solve(); equation.display(); JOptionPane.showMessageDialog(null , equation.getSolution(), "成功" , JOptionPane.INFORMATION_MESSAGE); } catch (NumberFormatException e1) { JOptionPane.showMessageDialog(null , "请输入合法的系数!" , "异常" , JOptionPane.WARNING_MESSAGE); } catch (AWrongException e2) { JOptionPane.showMessageDialog(null , e2.getMessage(), "异常" , JOptionPane.WARNING_MESSAGE); } }); 序列化至文件Button.addActionListener(new ActionListener () { @Override public void actionPerformed (ActionEvent e) { try (FileOutputStream fileOutputStream = new FileOutputStream ("file1.data" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream)) { double a = Double.parseDouble(textFieldA.getText()); double b = Double.parseDouble(textFieldB.getText()); double c = Double.parseDouble(textFieldC.getText()); Equation equation = new Equation (a, b, c); objectOutputStream.writeObject(equation); JOptionPane.showMessageDialog(null , "已将一元二次方程对象序列化为byte数组保存至文件\"file1.data\"中" , "消息提示" , JOptionPane.INFORMATION_MESSAGE); } catch (NumberFormatException e1) { JOptionPane.showMessageDialog(null , "请输入合法的系数!" , "异常" , JOptionPane.WARNING_MESSAGE); } catch (IOException | AWrongException e2) { JOptionPane.showMessageDialog(null , e2.getMessage(), "异常" , JOptionPane.WARNING_MESSAGE); e2.printStackTrace(); } } }); 清空Button.addActionListener(e -> { textFieldA.setText("" ); textFieldB.setText("" ); textFieldC.setText("" ); }); 退出Button.addActionListener(e -> System.exit(0 )); } }
运行截图 控制台程序
GUI程序
分析 模拟题,用一些基本的数学知识可以写出核心代码,这里我认为解的形式都应该是double
,如果将复数解作为一个整体,那么要么用String
表示,要么自己写一个复数类,前者不那么专业,后者使程序复杂,所以我认为将实部(real part)和虚部(imaginary part)拆开都用基本数据类型double
表示更优。
再用一些课上的知识可以写出控制台程序
GUI程序需要自己学习,不过在周三的课上已经讲了很大一部分,包括监听器的三种回调方式,所以我在课内基本目标外实现的主要有:
按ESC键退出,知识是任意componet监听,详见GUI程序中的toolkit.addAWTEventListener(...)
。
程序自定义图标,调JFrame.setIconImage(Image)
即可,但将其打包到JAR中有难度。
界面美化,swing支持第三方主题包,我使用的是Intelli IDEA
同款主题FlatLaf
,使用方法是在项目中导入JAR,之后使用初始化语句UIManager.setLookAndFeel(new FlatLightLaf());
即可。另外,可将LookAndFeel译为“观感”。
作业四 第一题 题目 输入三个字符串,分别:
必须满足密码复杂性要求(认证需求)
满足身份证号码规范(15位/18位)
满足电子邮件规范
答案 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package edu.wanpengxu.homework4.first;import java.io.Console;import java.util.Scanner;import java.util.regex.Pattern;public class Test { public static boolean isIdCard (String idCard) { String idCardCheck = "(^[1-8]\\d((0[1-9])|([1-6][0-9])|70|90)(0[1-9]|1[0-8]|2[1-9]|[3-9][0-9])(19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|[1-3]0|31)\\d{3}[0-9Xx]$)|" + "(^[1-8]\\d((0[1-9])|([1-6][0-9])|70|90)(0[1-9]|1[0-8]|2[1-9]|[3-9][0-9])\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|[1-3]0|31)\\d{3}$)" ; if (idCard == null || "" .equals(idCard)) return false ; if (!Pattern.compile(idCardCheck).matcher(idCard).matches()) return false ; if (idCard.length() == 18 ) { int sum = 0 ; for (int index = 0 ; index < 17 ; index++) sum += (Math.pow(2 , 17 - index) % 11 ) * (idCard.charAt(index) - '0' ); char checkBit = (char ) ('0' + ((12 - sum % 11 ) % 11 )); checkBit = checkBit == ('0' + 10 ) ? 'X' : checkBit; return Character.toString(checkBit).equalsIgnoreCase(String.valueOf(idCard.charAt(17 ))); } else return true ; } public static void main (String[] args) { System.out.println("欢迎来到邮箱注册程序!" ); try (Scanner scanner = new Scanner (System.in)) { String emailCheck = "^(?!.*\\.{2})[A-Za-z][0-9A-Za-z.]{4,13}[0-9A-Za-z](?!(@\\.|@-|.*\\.-|.*\\.{2})|.*-\\.|.*\\-{2})@[0-9A-Za-z-]{1,63}(\\.[0-9A-Za-z-]{1,63})+$" ; String email; while (true ) { System.out.println("请输入邮箱账号(6~18个字符,可使用字母、数字、下划线,需要以字母开头)" ); email = scanner.nextLine(); if (Pattern.compile(emailCheck).matcher(email).matches()) break ; else System.out.println("请检查输入格式!" ); } String passwordCheck = "^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)[a-zA-Z0-9\\W]{8,16}$" ; String password; while (true ) { System.out.println("请输入密码(8~16个字符,需包含“大小写字母、数字、标点符号”中3种或以上的组合)" ); Console con = System.console(); password = new String (con.readPassword()); if (Pattern.compile(passwordCheck).matcher(password).matches()) break ; else System.out.println("请检查输入格式!" ); } String idCard; while (true ) { System.out.println("请输入您的真实身份证号码" ); idCard = scanner.nextLine(); if (isIdCard(idCard)) break ; else System.out.println("请检查输入格式!" ); } System.out.println("注册成功!" ); } } }
运行截图
分析 构建正则表达式就和写程序一样,要由编译器运行起来才能直观地看到结果,这里可以使用正则表达式在线测试| 菜鸟工具
电子邮件匹配 首先,要对电子邮件进行匹配,就需要了解电子邮件规范RFC 5322
RFC 5322 电子邮件地址的域内部分可以使用以下任何ASCII 字符:
大小写拉丁字母A
到Z
和a
到z
;
数字0
到9
;
除了字母与数字之外的可打印字符,!#$%&'*+-/=?^_`{|}~
;
点.
,但不能作为首尾字符,也不能连续出现,若放在引号中则不受这些限制(例如John..Doe@example.com
是不允许的,而"John..Doe"@example.com
是允许的)。
空格和特殊字符"(),:;<>@[\]
被允许有限制地使用(域内部分字符串必须放在引号中,后面的段落将会描述,并且,反斜杠或双引号之前,必须加一个反斜杠来转义);
允许将注释放在小括号内,并放在域内部分的开头或结尾,例如john.smith(comment)@example.com
和(comment)john.smith@example.com
都等同于john.smith@example.com
。
电子邮件地址的域名部分必须符合严格的规则:它必须满足对主机名的要求,一个以点分隔的DNS标签序列,每个标签被限定为长度不超过63个字符,且只能由下列字符组成:
大小写拉丁字母A
到Z
和a
到z
;
数字0
到9
,但顶级域名不能是纯数字;
连字符-
,但不能作为首尾字符。
可以看出,RFC 5322电子邮件规范包含范围广泛的特殊字符,虽然在技术上可行,但在实践中往往并不能接受所有这些字符。
所以我们需要调研知名邮箱的电子邮件规范,我在这里选择了国内最流行的163邮箱和国际最流行的gmail邮箱,他们在格式错误时都会回显原因。
域内部分 网易 163邮箱
163给出的邮箱规范很具体,我们可以自行测试它的细则。
邮箱地址需以字母或数字结尾
Google gmail邮箱
gmail给出的邮箱规范很简洁,我们也可以自行测试它的细则。
用户名的字符数须介于6到30之间。
用户名的8个或以上字符中应至少包括一个字母(a-z)
只能使用字母(a-z)、数字(0-9)和数点(.)
用户名的最后一个字符必须为ASCII字母(a-z)或数字(0-9)
用户名中不能包含连续的数点(.)
我还对Microsoft outlook邮箱的电子邮件规范进行了调研,个人认为outlook邮箱的格式太过宽泛(比如单个字母可以作为用户名,且用户名最长可达65位),实现出来并不利于使用。
对以上规范进行总结,得到实践中的电子邮件规范:
用户名的字符数介于一定范围
可用字符为大小写字母(a-z
和A-Z
)、数字(0-9
)、一些特殊符号(-_.
)
用户名至少包含一个字母(有些邮箱要求其在开头,有些邮箱在小于一定长度时不要求)
用户名的结束字符为字母或数字(有些邮箱允许_-
)
那么我们可以制定一个属于自己的、简易的邮箱规范:
用户名的字符数须介于6到15之间;
用户名可使用字母(a-z
和A-Z
)、数字(0-9
)、数点(.
);
用户名需要以字母(a-z
和A-Z
)开头;
用户名中不能包含连续的数点(.
);
用户名需要以字母或数字(a-z
和A-Z
和0-9
)结尾。
开始构建表达式吧!
^
正则表达式开始符
(?!.*\.{2})
不允许两个以上的句点(环视技术,详见1.3.2)
[A-Za-z]
开头部分的拉丁字母
[0-9A-Za-z.]{4,13}
中间部分的允许字符
[0-9A-Za-z]
结尾部分的拉丁字母或数字
$
正则表达式结束符
最后可得
1 "^(?!.*\.{2})[A-Za-z][0-9A-Za-z.]{4,13}[0-9A-Za-z]$"
当然我也搜索学习了RFC 5322电子邮件规范的正则匹配表达式
1 "^[\w!#$%&’*+/=?`{|}~^-]+(?:\.[\\w!#$%&’*+/=?`{|}~^-]+)*$"
域名部分 RFC 5322制定的电子邮件规范在这里需求较少,直接构建正则表达式即可。
^
正则表达式开始符
(?!(@\.|@-|.*\.-|.*\.{2})|.*-\.|.*\-{2})
@
后不能接.
,@
后不能接-
,.
后不能接-
,.
后不能接.
,-
后不能接.
,-
后不能接·-
@[0-9A-Za-z-]{1,63}
第一段DNS标签(@开头),出现一次
(\.[0-9A-Za-z-]{1,63})+
第二段DNS标签及其之后的DNS标签(.开头),出现至少一次(用+
匹配)
$
正则表达式结束符
1 "^(?!(@\.|@-|.*\.-|.*\.{2})|.*-\.|.*\-{2})@[0-9A-Za-z-]{1,63}(\.[0-9A-Za-z-]{1,63})+$"
综上,得到最终电子邮件匹配正则表达式
1 "^(?!.*\.{2})[A-Za-z][0-9A-Za-z.]{4,13}[0-9A-Za-z](?!(@\.|@-|.*\.-|.*\.{2})|.*-\.|.*\-{2})@[0-9A-Za-z-]{1,63}(\.[0-9A-Za-z-]{1,63})+$"
经测试,可以实现上述的所有功能,放一张成功匹配的截图。
密码匹配 同样地,首先我们需要制定一个密码复杂性要求。
对于密码,或者说口令,并没有严格的规范,我们先看一下163邮箱的密码复杂性要求。
我们可以在此基础上制定一个更严格的复杂性要求:
8~16个字符
需包含“大写字母、小写字母、数字、标点符号”中4种或以上的组合
因为只对字符集进行了规定,所以很好构建正则表达式。
我们先来了解一下环视
环视(Lookaround):匹配一个位置而不是字符,因此匹配的结果也可称为零宽字符。具体包括
Lookahead
Lookbehind
Lookahead,中文译作“正向预查” ,其实字面意思就是向前看 ,也就是向字符串的右边看。
Lookbehind,中文译作“负向预查” ,其实字面意思就是向后看 ,也就是向字符串的左边看。
接下来介绍一下环视所使用的符号
符号
意义
(
环视起始符
?
零宽字符,也就是位置,后续的操作就是选出合适的位置
<
Lookbehind,向零宽字符的左边看
(啥也不加,缺省)
Lookahead,向零宽字符的右边看
=
肯定形式,选出满足(有)其后pattern的零宽字符
!
否定形式,选出不满足(没有)其后pattern的零宽字符
$
字符串结尾
)
环视结束符
在这里直接对我的正则表达式进行说明即可理解环视。
1 "^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)[a-zA-Z0-9\\W]{8,16}$"
首先我们知道四种字符,其共有$\sum\limits_{i=1}^{4} C_{4}^{i}=4+6+4+1=15$种情况,接下来要对除了$C_{4}^{4}$这种全选情况外的情况进行排除。
^
正则表达式开始符
(?![0-9A-Za-z]+$)
对整个字符串选出零宽字符后没有数字、大写字母、小写字母三者组合($\sum\limits_{i=1}^{3} C_{3}^{i}$)的零宽字符
(?![0-9a-z\\W]+$)
对整个字符串选出零宽字符后没有数字、小写字母、标点符号三者组合($\sum\limits_{i=1}^{3} C_{3}^{i}$)的零宽字符
(?![A-Za-z\\W]+$)
对整个字符串选出零宽字符后没有大写字母、小写字母、标点符号三者组合($\sum\limits_{i=1}^{3} C_{3}^{i}$)的零宽字符
(?![0-9A-Z\\W]+$)
对整个字符串选出零宽字符后没有数字、大写字母、标点符号三者组合($\sum\limits_{i=1}^{3} C_{3}^{i}$)的零宽字符
[0-9A-Za-z\\W]{8,16}
从选出的零宽字符起,选中含有8~16个数字、大写字母、小写字母、标点符号的字符串。进行到这步时,因为前面几种环视已经排除掉了$\sum\limits_{i=1}^{3} C_{4}^{i}=14$种情况,所以选中的字符串必是四种符号的全选情况。
$
正则表达式结束符
这其中\W
代表匹配任何非单词字符,当然也可以自己指定允许的标点符号集合(注意这个转义字符包含_
)。
下两张图可区分选中零宽字符和选中字符串。
身份证号码匹配 格式匹配 对百位数以下的数字匹配时可以想象出一个每行10个数的表格(个位数0-9),中括号匹配时相当于用矩形选中,每次可能框最多的元素
十八位身份证正则表达式说明:
^
正则表达式开头
[1-8]
大区制代码,全国共分1-8八个大区
\\d
省市编码
( (0[1-9]) | ([1-6][0-9]) | 70 | 90 )
地级行政区:01-70一般 90直辖
( 0[1-9] | 1[0-8] | 2[1-9] |[3-9][0-9] )
县级行政区:01-18、21-99
( 19|20 )
生日期码,年份前两位
\\d{2}
生日期码,年份后两位
( (0[1-9]) | (1[0-2]) )
生日期码,月份
( ([0-2][1-9]) | [1-3]0 |31 )
生日期码,日期
\\d{3}
顺序码
[0-9Xx]
校验码,这里只判断格式而不校验
$
正则表达式结尾1 "^[1-8]\\d((0[1-9])|([1-6][0-9])|70|90)(0[1-9]|1[0-8]|2[1-9]|[3-9][0-9])(19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|[1-3]0|31)\\d{3}[0-9Xx]$"
十五位身份证正则表达式说明: 只是在十八位身份证的基础上去掉了 生日期码,年份前两位 和 校验码 。
1 "^[1-8]\\d((0[1-9])|([1-6][0-9])|70|90)(0[1-9]|1[0-8]|2[1-9]|[3-9][0-9]) \\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|[1-3]0|31)\\d{3} $"
校验码检查 首先了解一下校验码生成算法
将身份证号码从左至右标记为 ;$a_{18}$即为校验码;
计算权重系数${\displaystyle W_{i}=2^{18-i}\ {\bmod {\ }}{11}}$;其中${\displaystyle \ {\bmod {\ }}}$表示求余数。
所以:
i
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Wi
7
9
10
5
8
4
2
1
6
3
7
9
10
5
8
4
2
1
计算${\displaystyle S=\sum _{i=1}^{17}a_{i}\cdot W_{i}}$
${\displaystyle a_{18}=(12-(S\ {\bmod {1}}1)){\bmod {1}}1}$
那么根据算法,很容易写出程序。
1 2 3 4 5 int sum = 0 ;for (int index = 0 ; index < 17 ; index++) sum += (Math.pow(2 , 17 - index) % 11 ) * (idCard.charAt(index) - '0' ); char checkBit = (char ) ('0' + ((12 - sum % 11 ) % 11 ));checkBit = checkBit == ('0' + 10 ) ? 'X' : checkBit;
第二题 题目 利用鼠标事件启动3个线程分别在三个窗口中同时绘制动态图形(图形自选)。
答案 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package edu.wanpengxu.homework4.second;import com.formdev.flatlaf.FlatLightLaf;import javax.swing.*;import java.awt.*;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;import java.net.URL;public class TestFrame extends JFrame implements Runnable { int i = 0 ; public TestFrame () { this .setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this .addMouseListener(new MouseAdapter () { public void mouseClicked (MouseEvent e) { startThread(); } }); } private void startThread () { Thread t1 = new Thread (this ); t1.start(); } public void run () { for (i = 1 ; ; i = (i + 1 ) % 10 ) { try { repaint(); Thread.sleep(500 ); } catch (InterruptedException e) { JOptionPane.showMessageDialog(null , e.getMessage(), "Exceptions" , JOptionPane.WARNING_MESSAGE); } } } public void paint (Graphics g) { super .paint(g); int [] x = {80 + i * 10 , 100 + i * 10 , 50 + i * 10 }; int [] y = {50 + i * 10 , 100 + i * 10 , 80 + i * 10 }; if (i != 0 ) { g.drawPolygon(x, y, 3 ); } } public static void main (String[] args) { try { UIManager.setLookAndFeel(new FlatLightLaf ()); } catch (Exception ex) { System.err.println("Failed to initialize LaF" ); } URL imgURL = TestFrame.class.getResource("CUMTlogo.png" ); ImageIcon imageIcon = new ImageIcon (imgURL); Image image = imageIcon.getImage(); TestFrame testFrame1 = new TestFrame (); testFrame1.setBounds(100 , 200 , 300 , 300 ); testFrame1.setIconImage(image); testFrame1.setTitle("Thread1" ); testFrame1.setVisible(true ); TestFrame testFrame2 = new TestFrame (); testFrame2.setBounds(400 , 200 , 300 , 300 ); testFrame2.setIconImage(image); testFrame2.setTitle("Thread2" ); testFrame2.setVisible(true ); TestFrame testFrame3 = new TestFrame (); testFrame3.setBounds(700 , 200 , 300 , 300 ); testFrame3.setIconImage(image); testFrame3.setTitle("Thread3" ); testFrame3.setVisible(true ); } }
运行截图 三个Frame启动三个Thread,图形绘制时刻由鼠标点击时刻决定。
分析 与课上例题没什么区别,例题创建了一个Frame,每个Frame点击后可启动Thread。那完成基本的要求只需要在主函数里再声明两个Frame即可。
为增强可读性,我将图形绘制改为了循环绘制,另外将例题中原程序的实心圆改为了指向右下方的三角形,更改了主题、自定义了图标、增加了异常弹窗等,都是上次GUI作业中使用过的方法。