作业一

第一题

题目

计算多项式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();
// System.out.println("Error reading from user");
}
}
}

运行截图

第三题

题目

利用辗转相除法(欧几里得算法)求两个正整数的最大公约数

分析

欧几里得算法依赖于原理: $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. 自己定义的方法

    1
    2
    3
    public String Show(D obj) {
    return ("A and D");
    }
  2. 自己定义的方法

    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中的方法有:

  1. 继承自A的方法

    1
    2
    3
    public String Show(D obj) {
    return ("A and D");
    }
  2. 继承自A但被自己覆盖的方法

    1
    2
    3
    public String Show(A obj) {
    return ("B and A");
    }
  3. 自己定义的方法

    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(); // getDeclaredMethods()
for (Method m : methods) {
System.out.println(m);
}

其中:

  1. getClass()用于获取变量运行时类型的类名;
  2. getMethods()用于获取Class变量中的所有方法(从父类继承的和自己声明的)
  3. getDeclaredMethods()用于获取Class变量自己声明的方法。

但是,由于a2的编译时类型是A,所以在代码中只能调用12,否则会报错,即使你的实例中真的有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中的方法有:

  1. 继承自A的方法

    1
    2
    3
    public String Show(D obj) {
    return ("A and D");
    }
  2. 继承自A但被自己覆盖的方法

    1
    2
    3
    public String Show(A obj) {
    return ("B and A");
    }
  3. 自己定义的方法

    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

分析

流程如下:

  1. main方法进入,执行new Derived();语句。

  2. 调用Derived的构造方法Derived()

  3. class Derived extends Base,所以Derived()的第一句是由编译器添加的Base(),那么调用其父类的构造方法Base()

  4. Base类的构造方法初始化自己的属性,首先执行private String name = "base";,使Base.name=”base”,随后调用tellName()方法,因为运行时类型是Derived,所以根据继承链,优先匹配Derived中的tellName()方法,但因为此时还没有到Derived初始化,所以this.name=null,打印出的结果为

    1
    Derived tell name: null

    随后Base()方法结束,继续执行Derived()

  5. 此时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. 整个程序结构用工厂模式设计,保证将来动物园有新的动物加入时,程序可扩展。

答案

项目结构树

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

类图

代码

  1. 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 + "\"";
    }
    }
  2. 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;
    }
    }
  3. 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;
    }
    }
  4. 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;
    }
    }
  5. 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;
    };
    }
    }
  6. 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)) { // try-with-resources 类似try-finally
    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());
    }
    }

运行截图

分析

题目要求使用工厂模式,那么按照工厂模式的实现,有:

  1. 要有一个父类Animal,这里因为动物本身有一些公用的属性和方法,所以最好不要定义为abstract类;

  2. 要有一些继承自Animal的具体的类作为某种动物,如Cat, Dog, Panda;

  3. 需要一个工厂类AnimalFactory生成基于给定信息的实体类的对象,这里我是用增强型switch实现的,当然也可以用普通switch或if-else if-else实现;

  4. 需要一个使用该工厂的类(用户类)来操作工厂完成生产任务。我加入了一点功能:生成动物时为其命名,并在退出后显示已生成动物名和其类型。另外,使用了可以自动关闭资源的try-with-resources语法,它的功能类似于try-finally,但语法十分精简。

因为采用了工厂模式,所以拓展动物种类的操作也很简单:

  1. 复制一份动物类,将其类名重构为新增的动物,方法名同理;
  2. 更改一些字符串以与新增动物相对应;
  3. 在AnimalFactory类中新增一行case。

作业三

第一题

题目

  • 用泛型List管理学生信息,学生对象信息为:姓名、学号、年龄、专业

  • 对学生列表完成:添加、删除、查找、全部显示操作。

答案

项目结构树

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<>(); // database变量始终引用数组

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(); // 不隐藏输入
// Console con = System.console();
// String password = new String(con.readPassword()); // 隐藏输入,在IDE中不可行,因为控制台被重定向了
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();
// Console con = System.console();
// String password = new String(con.readPassword());
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的倍数也补,不然体现不出按制表键)。

第二题

题目

一元二次方程类:

  1. 输入一元二次方程的三个系数,确定这个方程式。

    标明一个一元二次方程的属性。

    • 三个系数

      $a$, $b$ ,$c$

    • 两个根(最多)(而且可能是虚根所以用统一的形式表示)
      $x_1$,$y_1$, $x_2$,$y_2$

    • 决定根的特征的
      $\delta=b^2-4ac$

    如果是一个一元二次方程需要的操作(注意方法的权限):

    • 获取三个系数,确立一元二次方程.

    • 求$\delta=b^2-4ac$

    • 求根(可能是实根,也可能是虚根)

  2. 如果二次系数$a$为0 ,主动抛出异常,因为这不满足一元二次方程的条件。

    1
    public class AWrongException extends Exception
  3. 把生成的一元二次方程对象(包含根的值)序列化并保存在文件file1.data中。

    • 根求出之后,再把对象序列化到文件file1.data中。

要求:

  1. 实现一元二次方程类
  2. 在主类中调用这个一元二次方程类。(编程用命令行或者可视化方法获取系数 $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; // 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"); // 系数为1时省略
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{ // 系数a错误异常
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(); // 销毁当前JFrame
System.exit(0); // 终止当前程序的JVM
}
}
}, 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.setSize(400, 130);
frame.pack();
frame.setLocationRelativeTo(null); // 这句话要放在形状设置后,否则是将size(0,0)的窗口居中,造成窗口左上角在中间的情况
frame.setResizable(false);
// frame.getContentPane().setBackground(Color.white);
frame.setVisible(true);
}

public UnivariateQuadraticEquation() {
// lambda表达式
求解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);
// e2.printStackTrace();
}
});
// 匿名内部类
序列化至文件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) {
// 这里用word打开可以测试占用
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程序需要自己学习,不过在周三的课上已经讲了很大一部分,包括监听器的三种回调方式,所以我在课内基本目标外实现的主要有:

  1. 按ESC键退出,知识是任意componet监听,详见GUI程序中的toolkit.addAWTEventListener(...)
  2. 程序自定义图标,调JFrame.setIconImage(Image)即可,但将其打包到JAR中有难度。
  3. 界面美化,swing支持第三方主题包,我使用的是Intelli IDEA同款主题FlatLaf,使用方法是在项目中导入JAR,之后使用初始化语句UIManager.setLookAndFeel(new FlatLightLaf());即可。另外,可将LookAndFeel译为“观感”。

作业四

第一题

题目

输入三个字符串,分别:

  1. 必须满足密码复杂性要求(认证需求)

  2. 满足身份证号码规范(15位/18位)

  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
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;
// 若为18位则对校验位进行校验
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; // 最后一位若为阿拉伯数字10则用罗马数字X表示
return Character.toString(checkBit).equalsIgnoreCase(String.valueOf(idCard.charAt(17)));
}
// 若为15位,不需校验
else return true;
}

public static void main(String[] args) {
System.out.println("欢迎来到邮箱注册程序!");
try (Scanner scanner = new Scanner(System.in)) {
// ^[\\w!#$%&’*+/=?`{|}~^-]+(?:\\.[\\w!#$%&’*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$
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种或以上的组合)");
// password = scanner.nextLine();
Console con = System.console();
password = new String(con.readPassword()); // 隐藏输入,在IDE中不可行,因为控制台被重定向了
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字符:

  • 大小写拉丁字母AZaz
  • 数字09
  • 除了字母与数字之外的可打印字符,!#$%&'*+-/=?^_`{|}~
  • .,但不能作为首尾字符,也不能连续出现,若放在引号中则不受这些限制(例如John..Doe@example.com是不允许的,而"John..Doe"@example.com是允许的)。
  • 空格和特殊字符"(),:;<>@[\]被允许有限制地使用(域内部分字符串必须放在引号中,后面的段落将会描述,并且,反斜杠或双引号之前,必须加一个反斜杠来转义);
  • 允许将注释放在小括号内,并放在域内部分的开头或结尾,例如john.smith(comment)@example.com(comment)john.smith@example.com都等同于john.smith@example.com

电子邮件地址的域名部分必须符合严格的规则:它必须满足对主机名的要求,一个以点分隔的DNS标签序列,每个标签被限定为长度不超过63个字符,且只能由下列字符组成:

  • 大小写拉丁字母AZaz
  • 数字09,但顶级域名不能是纯数字;
  • 连字符-,但不能作为首尾字符。

可以看出,RFC 5322电子邮件规范包含范围广泛的特殊字符,虽然在技术上可行,但在实践中往往并不能接受所有这些字符。

所以我们需要调研知名邮箱的电子邮件规范,我在这里选择了国内最流行的163邮箱和国际最流行的gmail邮箱,他们在格式错误时都会回显原因。

域内部分

网易 163邮箱

163给出的邮箱规范很具体,我们可以自行测试它的细则。

  1. 邮箱地址需以字母或数字结尾

Google gmail邮箱

gmail给出的邮箱规范很简洁,我们也可以自行测试它的细则。

  1. 用户名的字符数须介于6到30之间。

  1. 用户名的8个或以上字符中应至少包括一个字母(a-z)

  1. 只能使用字母(a-z)、数字(0-9)和数点(.)

  1. 用户名的最后一个字符必须为ASCII字母(a-z)或数字(0-9)

  1. 用户名中不能包含连续的数点(.)

我还对Microsoft outlook邮箱的电子邮件规范进行了调研,个人认为outlook邮箱的格式太过宽泛(比如单个字母可以作为用户名,且用户名最长可达65位),实现出来并不利于使用。

对以上规范进行总结,得到实践中的电子邮件规范:

  1. 用户名的字符数介于一定范围
  2. 可用字符为大小写字母(a-zA-Z)、数字(0-9)、一些特殊符号(-_.
  3. 用户名至少包含一个字母(有些邮箱要求其在开头,有些邮箱在小于一定长度时不要求)
  4. 用户名的结束字符为字母或数字(有些邮箱允许_-

那么我们可以制定一个属于自己的、简易的邮箱规范:

  1. 用户名的字符数须介于6到15之间;
  2. 用户名可使用字母(a-zA-Z)、数字(0-9)、数点(.);
  3. 用户名需要以字母(a-zA-Z)开头;
  4. 用户名中不能包含连续的数点(.);
  5. 用户名需要以字母或数字(a-zA-Z0-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邮箱的密码复杂性要求。

我们可以在此基础上制定一个更严格的复杂性要求:

  1. 8~16个字符
  2. 需包含“大写字母、小写字母、数字、标点符号”中4种或以上的组合

因为只对字符集进行了规定,所以很好构建正则表达式。

我们先来了解一下环视

环视(Lookaround):匹配一个位置而不是字符,因此匹配的结果也可称为零宽字符。具体包括

  1. Lookahead

  2. 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}       $"
校验码检查

首先了解一下校验码生成算法

  1. 将身份证号码从左至右标记为 ;$a_{18}$即为校验码;

  2. 计算权重系数${\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
  1. 计算${\displaystyle S=\sum _{i=1}^{17}a_{i}\cdot W_{i}}$

  2. ${\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; // 最后一位若为阿拉伯数字10则用罗马数字X表示

第二题

题目

利用鼠标事件启动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);
// g.fillOval(50 + i * 10, 50 + i * 10, 100, 100);
}
}

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作业中使用过的方法。