# 华东师范大学数据科学与工程学院学生期末项目报告

课程名称:计算机网络与编程年级:22 级上机实践成绩
指导教师:张召姓名:邓博昊学号:10225501432
上机实践名称上机实践日期:2024/3/15
上机实践编号组号上机实践时间:10:02

# 一、 题目要求

  • Task1: 设计一个名为 StopWatch(秒表)的类,该类继承 Watch(表)类

  • **Bonus Task1 (optional)😗* 回顾 C++ 的继承方式,其有 public 继承、protected 继承和 private 继承,后两种常用作空基类优化等技巧,然而 Java 只有一种继承方式 extends,这是为什么?

  • **Task 2:** 对于提供的 Fish 类,实现 Comparable 接口。初始化 10 个 Fish 对象放入数组或容器,并使用按照 size 属性从小到大排序,排序后从前往后对每个对象调用 print () 进行打印

  • **Task 3:** 根据要求创建 SalesEmployee、HourlyEmployee、SalariedEmployee 三个类的对象各一个,并计算某个月这三个对象员工的工资

  • **Task4: ** 简要理解并说明:接口和抽象类在使用场景上的区别

  • Task5: 请在实验报告中列举出 Error 和 Exception 的区别

  • Task6: 请设计可能会发生的 5 个 RuntimeException 案例并将其捕获,将捕获成功的运行时截图和代码附在实验报告中

  • Task7: 对于 list 中的每个 Color 枚举类,输出其 type(不用换行),请使用 swtich 语句实现,请将 代码和运行截图附在实验报告中

  • Task8: 使用 switch 表达式实现判断某月有多少天的功能(需要考虑闰年),请将代码和运行截图附在实验报告中。

# 二、 功能实现情况

# Task 1

# 理论

# 嵌套类
  • 定义:Java 允许一个类被定义在另一个类中,这样的类就称为嵌套类
  • 嵌套类默认是封闭的(enclosed)。
    • 这意味着除了外部类之外,其他类无法直接访问嵌套类。
    • 如果要从外部类之外访问嵌套类,可以将嵌套类声明为 static ,这样就可以直接通过外部类的名称访问了。
# 静态方法
  • 定义:用 static 修饰的方法称为静态方法
  • 特点:静态方法中不能直接调用非静态方法
    • 非静态方法不是独立存在的:它是依附于对象存在 —— 即只有申明了对象,才能通过对象调用
    • 静态方法则可以直接通过类名调用,而不需要申明对象
  • 实例:比如常见的 main 方法都是静态的,必须由 static 修饰,因此在 main 方法里调用类的其他非静态方法,都是需要先申明对象,才能用。否则就会出现引用非静态方法的错误
# 继承
  • 作用:允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。通过继承,子类可以重用父类的代码,并可以在此基础上添加新的属性和方法,从而实现代码的复用和扩展

  • 声明继承关系:

    子类可以访问父类的非私有成员(属性和方法),包括公有(public)、受保护(protected)和默认(没有修饰符)访问级别的成员。子类可以使用 super 关键字来引用父类的成员。

    class ParentClass {
        // 父类的属性和方法
    }
     
    class ChildClass extends ParentClass {
        // 子类的属性和方法
  • 访问父类:

    class ParentClass {
        public void parentMethod() {
            System.out.println("这是父类的方法");
        }
    }
     
    class ChildClass extends ParentClass {
        public void childMethod() {
            // 调用父类的方法
            super.parentMethod();
            System.out.println("这是子类的方法");
        }
  • 方法重写:

    子类可以重写(覆盖)父类的方法,以实现自己的特定行为。方法重写要求子类方法的名称、参数列表和返回类型与父类方法相同。使用 @Override 注解可以提醒编译器验证该方法是否正确地重写了父类的方法。

    class ParentClass {
        public void parentMethod() {
            System.out.println("这是父类的方法");
        }
    }
     
    class ChildClass extends ParentClass {
        @Override
        public void parentMethod() {
            System.out.println("这是子类重写的父类方法");
        }
    }

# 代码

# Task1.java
package cc.aquaoh;
public class Task1 {
    public static class Watch {
        public long startTime;
        public long endTime;
        public void start(){
            // 获取时间戳,时间戳单位为 ms
            startTime = System.currentTimeMillis();
        }
        public void stop(){
            endTime = System.currentTimeMillis();
        }
    }
    public static class StopWatch extends Watch {
        public long getElapsedTime(){
            return super.endTime - super.startTime;
        }
    }
}
# Main.java
import cc.aquaoh.*;
public class Main {
    public static void task1() throws InterruptedException {
        Task1.StopWatch clock = new Task1.StopWatch();
        clock.start();
        //static void sleep (long mills),单位为 ms
        Thread.sleep(1000);
        clock.stop();
        long gap = clock.getElapsedTime();
        System.out.printf("%s ms\n",gap);
    }
    public static void main(String[] args) throws InterruptedException {
        task1();
    }
}

# Bonus Task1

  • Java 只有一种 extends 继承方式是因为将继承的语义和访问权限的控制进行了整合,减少了概念上的混淆,提高了代码的易用性和可读性,以及可维护性。并且简化的继承概念,使得 Java 语言更易于学习和使用。

  • C 有 public、protected 和 private 三种继承方式,每种方式都提供了不同的访问权限控制。这样的设计虽然让 C 更加灵活,但是也增加了语言的复杂性和学习曲线,并且对于不熟练的开发者而言,会增加代码的复杂性和错误的可能性。

# Task 2

# 理论

# Random 类
Random r = new Random();
// 生成 [0,100) 之间的随机数
this.size = r.nextInt(100);
# Comparable 接口
  • 表示:可以比较的,定义了类的比较 (排序) 标准,使自定义的类支持比较

  • 原因:如果一个类无法实现 Comparable 接口,则代表不能进行比较

  • public interface Comparable<T> {
    	public int compareTo(T o);
    }
    // T 表示要比较的对象的类型,即 Type
    //o 表示要与当前对象进行比较的另一个对象,这里的 o 是一个约定的命名,通常代表 "other",即另一个对象
    // 方法的语义上,o 代表着与当前对象进行比较的对象,它是一个占位符,用于指示要比较的对象参数。
    //compareTo 函数需要实现
    // 如果 i=0, 也表明对象 x 与 y 排位上是相等的 (并非意味 x.equals (y) = true, 但是 jdk api 上强烈建议这样处理)
    // 如果返回数值 i>0,则意味,x > y, 
    // 反之若 i<0,则意味 x < y
    // 实例,otherFish 为约定俗成写法
        @Override
    public int compareTo(Fish otherFish) {
            return Integer.compare(this.size, otherFish.size);
        }
    //Integer.compare
    //This method returns the value zero if (x==y), 
    //if (x < y) then it returns a value less than zero 
    //and if (x > y) then it returns a value greater than zero
# 对象数组
  • 对象数组:包含了一组相关的对象

  • 数组一定要先开辟空间,但是因为其是引用数据类型,所以数组里面的每一个对象都是 null 值,则在使用的时候数组中的每一个对象必须分别进行实例化操作。

  • // 定义并开辟数组
    // 类名称 对象数组名 [] = null; 
    // 对象数组名 = new 类名称 [长度];
    Fish[] fishArray = null;
    fishArray = new Fish[10];
    // 定义并开辟数组
    // 类名称 对象数组名 [] = new 类名称 [长度];
    Fish[] fishArray = new Fish[10];
  • 如果类定义了 Comparable 接口,则能使用 Array 类的 sort 方法

    Fish[] fishArray = new Fish[10];
    // 初始化 10 个 Fish 对象
    for (int i = 0; i < 10; i++) {
      fishArray[i] = new Fish();
    }
    // 排序,排序的标准定义在 Comparable 接口中
    Arrays.sort(fishArray);
# 增强 for 循环
  • 格式: for(声明语句 : 表达式)
    • ** 声明语句:** 声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等
    • ** 表达式:** 表达式是要访问的数组名,或者是返回值为数组的方法
    • 注意这种方法只能遍历读取数组而不能修改数组元素的值
  • 实例: for(int x : array){System.out.print(x);}

# 代码

# Task2.java
package cc.aquaoh;
import java.util.Random;
public class Task2 {
    // 补全或改写 Fish 类定义,满足 Comparable 接口
    public static class Fish implements Comparable<Fish>{
        int size;
        public int compareTo(Fish otherFish){//otherFish 为约定俗成写法
            return Integer.compare(this.size, otherFish.size);
        }
        public Fish(){
            Random r = new Random();
            // 生成 [0,100) 之间的随机数
            this.size = r.nextInt(100);
        }
        public void print(){
            System.out.print(this.size + " < ");
        }
        public void endprint(){
            System.out.print(this.size + "\n");
        }
    }
}
# Main.java
import cc.aquaoh.*;
import java.util.Arrays;
public class Main {
    public static void task2(){
        Task2.Fish[] fishArray = new Task2.Fish[10];
        for(int i=0;i<10;i++){
            fishArray[i] = new Task2.Fish();
        }
        Arrays.sort(fishArray);
        int i;
        for(i=0;i<9;i++){
            fishArray[i].print();
        }
        fishArray[i].endprint();
    }
    public static void main(String[] args) throws InterruptedException {
        
        task2();
    }
}

# Task 3

# 理论

# 抽象类
  • 特点
    • 不能实例化对象 \rightarrow 如果子类继承抽象类后想不实例化使用,则父类子类都添加 static 关键字
    • 类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
    • 抽象类必须被继承,才能被使用

# 代码

# Task3.java
package cc.aquaoh;
public class Task3 {
    abstract static class Employee {
        private String name;
        private int birthmonth;
        public Employee(String name, int birthmonth) {
            //System.out.println("Constructing an Employee");
            this.name = name;
            this.birthmonth = birthmonth;
        }
        public String getName() {
            return name;
        }
        public int getBirthmonth() {
            return birthmonth;
        }
        abstract public double getSalary(int month);
    }
    public static class SalesEmployee extends Employee{
        private double amount;
        private double rate;
        private double basicSalary;
        public SalesEmployee(String name, int birthmonth, double amount, double rate, double basicSalary){
            super(name, birthmonth);
            this.amount = amount;
            this.rate = rate;
            this.basicSalary = basicSalary;
        }
        @Override
        public double getSalary(int month) {
            return amount * rate + basicSalary;
        }
    }
    public static class SalariedEmployee extends Employee{
        private double basicSalary;
        public SalariedEmployee(String name, int birthmonth, double basicSalary) {
            super(name, birthmonth);
            this.basicSalary = basicSalary;
        }
        @Override
        public double getSalary(int month) {
            if(month==super.getBirthmonth()){
                return basicSalary + 100;
            }
            else {
                return basicSalary;
            }
        }
    }
    public static class HourlyEmployee extends Employee{
        private double hours;
        private double payment;
        public HourlyEmployee(String name, int birthmonth, double hours,double payment) {
            super(name, birthmonth);
            this.hours = hours;
            this.payment = payment;
        }
        @Override
        public double getSalary(int month) {
            return hours*payment;
        }
    }
}
# Main.java
import cc.aquaoh.*;
import java.util.Arrays;
public class Main {
    public static void task3(){
        Task3.SalesEmployee salesEmployee = new Task3.SalesEmployee("张伟",11,114514,0.19,1981);
        Task3.SalariedEmployee salariedEmployee = new Task3.SalariedEmployee("李强",1,14514);
        Task3.HourlyEmployee  hourlyEmployee = new Task3.HourlyEmployee("丁真",5,240,50);
        int month = 1;
        System.out.printf("第%d月的工资为%.2f\n",month,salesEmployee.getSalary(month));
        System.out.printf("第%d月的工资为%.2f\n",month,salariedEmployee.getSalary(month));
        System.out.printf("第%d月的工资为%.2f\n",month, hourlyEmployee.getSalary(month));
    }
    public static void main(String[] args) throws InterruptedException {
        task3();
    }
}

# Task 4

区别:

  • 抽象类
    • 主要用于抽象类别,强调所属关系。
  • 接口
    • 主要用于抽象功能,强调特定功能的实现。
  • 接口支持多继承,抽象类不支持。

使用场景:

  • 当需要使用多继承特性时使用接口
  • 不使用多继承特性时,抽象类或者接口均可

# Task 5

# 额外理论

# try catch
  • try 语句允许定义一个代码块,以便在执行时对其进行错误测试。

  • 如果 try 块中发生错误, catch 语句允许定义要执行的代码块。

  • trycatch 关键字成对出现:

    try {
      	int a[] = new int[2];
      	System.out.println("Access third element :" + a[3]);
    } catch (Exception e) {
      	System.out.println("Exception thrown :" + e);
    }

image-20240320105420809

# finally
  • finally 语句允许在 try...catch 之后执行代码,而不管结果如何:

  • 如果 finally {} 语句块中有 return 语句,那么会永远返回 finally 中的结果。

    try {
      	int a[] = new int[2];
      	System.out.println("Access third element :" + a[3]);
    } catch (Exception e) {
      	System.out.println("Exception thrown :" + e);
    } finally{
        System.out.println("The 'try catch' is finished.");
    }
# throw 关键字
  • throw 语句允许创建自定义错误

  • throw 语句与异常类型一起使用。 Java 中有许多异常类型可用: ArithmeticException , FileNotFoundException , ArrayIndexOutOfBoundsException , SecurityException , etc:

  • 如果父类方法抛出多个异常,那么子类重写父类方法时,可以选择抛出和父类相同的异常或者是父类异常的子类或者不抛出异常

    如果父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出。

    // 如果年龄低于 18 岁,则抛出异常(打印 "访问被拒绝")。如果年龄为 18 岁或以上,请打印 "访问权限":
    public class MyClass {
      static void checkAge(int age) {
        if (age < 18) {
          throw new ArithmeticException("Access denied - You must be at least 18 years old.");
        }
        else {
          System.out.println("Access granted - You are old enough!");
        }
      }
      public static void main(String[] args) {
        checkAge(15); // 将年龄设置为 15 岁(低于 18 岁......)
      }
    }
# throws 关键字
  • throws 关键字用于在方法声明中指定该方法可能抛出的异常。

  • 当方法内部抛出指定类型的异常时,该异常会被传递给调用该方法的代码,并在该代码中处理异常。

    当 readFile 方法内部发生 IOException 异常时,会将该异常传递给调用该方法的代码。在调用该方法的代码中,必须捕获或声明处理 IOException 异常。

    public void readFile(String filePath) throws IOException {
      BufferedReader reader = new BufferedReader(new FileReader(filePath));
      String line = reader.readLine();
      while (line != null) {
        System.out.println(line);
        line = reader.readLine();
      }
      reader.close();
    }

# Error Exception 的区别

  • Error 类和 Exception 类都是继承 Throwable 类

在这里插入图片描述

# Error
  • 程序无法处理,通常指程序中出现的严重问题。
  • 是系统中的错误,程序员是不能改变的和处理的,是在程序编译时出现的错误,只能通过修改程序才能修正
    • 例如 java.lang.VirtualMachineError (Java 虚拟机运行错误):当 Java 虚拟机崩溃或用尽了它继续操作所需的资源时,抛出该错误
    • 例如 java.lang.StackOverflowError (栈溢出错误):当应用程序递归太深而发生堆栈溢出时,抛出该错误。
    • 例如 java.lang.OutOfMemoryError (内存溢出):内存溢出或没有可用的内存提供给垃圾回收器时,产生这个错误
  • Error(错误)是不可查的,而且也常常在应用程序的控制和处理能力之外,因此当 Error(错误)出现时,程序会立即奔溃,Java 虚拟机立即停止运行
# Exception
  • 程序本身可以处理的异常(可以向上抛出或者捕获处理)
  • 遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
  • Exception 又分为两类:
    • CheckedException:(编译时异常)
      • 程序编译时就会出现的异常
      • 编译异常是 java.lang.RuntimeException 以外的异常。
      • 编译异常必须进行处理,如果不处理,程序就不能编译通过。
      • 这种异常的特点是 Java 编译器会检查它,也就是说,当程序中可能出现这类异常,要么用 try-catch 语句捕获它,要么用 throws 子句声明抛出它,否则编译不会通过。
    • UnCheckedException(RuntimeException):(运行时异常)不需要捕获,对于程序错误(不可恢复)的异常使用 RuntimeException。
      • 运行异常是 java.lang.RuntimeException 类及其子类的统称
      • 如 NullPointerException(空指针异常)、IndexOutOfBoundsException(数组下标越界异常)等。
      • 运行异常一般是由程序逻辑错误导致的,可以通过捕获处理或向上抛出。
      • 运行异常的特点是 Java 编译器不会检查它,也就是说,当程序中可能出现运行异常,也会被编译通过。
      • 运行时异常被抛出,调用者可以不处理,那么就会由 jvm 处理

在这里插入图片描述

# Task 6

# 理论

# ArithmeticException (算术异常)
  • 除数为零:当我们尝试将一个数除以零时,会抛出 ArithmeticException 异常。如 int result = 10 / 0;
  • 模数为零:当我们尝试对一个数取模时,如果模数为零,同样会抛出 ArithmeticException 异常。如, int result = 10 % 0;
# IndexOutOfBoundsException (数组下标越界异常)
  • 当程序一旦抛出 ArrayIndexOutOfBoundsException 异常的时候,那就说明某个地方使用或者调用了超过数组最大长度的元素,而这样的元素是不存在的
# NullPointerException (空指针异常)
  • 如果一个对象为 null ,调用其方法或访问其字段就会产生 NullPointerException
    • 尝试使用指向内存中空位置的引用(null)时发生的异常,就好像它引用了一个对象一样。
  • 这个异常通常是由 JVM 抛出的
# ClassCastException (类型转换异常)
  • 对象的强制转换(也称为类型转换)是将一个对象的引用转换为另一个类的引用,前提是这两个类之间存在继承或实现关系

  • 子类转父类:

    Son s = new Son();
    // 类型提升
    Father f = (Father)s;// 可以

    子类继承父类,就拥有了父类的一切。某种程度上说,儿子可以代替爸爸,爸爸能做的事儿子也可以做。子类转父类后,父类对象 f 引用指向子类对象,所以其本质任然是子类,f 只能调用父类的方法,如果子类重写了父类的方法,则调用的是子类的方法(多态性)

  • 父类转子类:

    1. 真实父类对象转子类对象,报 ClassCastException 异常

    Father f = new Father();
    Son s = (Son)f;// 出错 ClassCastException

    分析: 创建一个父类的实例,想要强制把父类对象转换成子类的对象,是不行的。父亲有的,通过继承儿子也有,反过来儿子有的父亲却不一定有。

  • 2. “假” 父类对象转子类对象,可以

    Father f = new Son();
    Son s = (Son)f;// 可以

    ** 分析:** 只有父类对象本身就是用子类 new 出来的时候,才可以在将来被强制转换为子类对象。这个时候父类的本质依然是子类对象(儿子只是装成了爸爸),子类有的属性 f 都拥有,只是 f 暂时不能操作子类特有的属性,所以可以转换回为子类对象(变回儿子本身)

# IllegalArgumentException (非法参数)

IllegalArgumentException 异常通常在以下情况下会被抛出:

  • 方法接收到一个非法的参数值。

    public void doSomething(Object obj) {
        if (obj == null) {
            throw new IllegalArgumentException("Parameter obj cannot be null");
        }
        // do something with obj
    }
  • 方法接收到一个不在允许范围内的参数值。

    public void setValue(int value) {
        if (value < 0 || value > 100) {
            throw new IllegalArgumentException("Value must be between 0 and 100");
        }
        // set the value
    }
  • 方法接收到一个空的参数,但不允许为空。

    public void processString(String str) {
        if (!str.startsWith("prefix")) {
            throw new IllegalArgumentException("String must start with 'prefix'");
        }
        // process the string
    }

# 代码

# Task6.java
package cc.aquaoh;
public class Task6 {
    public static void getArithmeticException(){
        try{
            System.out.println( 1 / 0 );
            //System.out.println( 1 % 0 );
        } catch (ArithmeticException e) {
            System.out.println("Exception: "+ e );
        }
    }
    public static void getIndexOutOfBoundsException(){
        try{
            int[] array = new int[3];
            System.out.println(array[3]);
        } catch (IndexOutOfBoundsException e){
            System.out.println("Exception: "+ e );
        }
    }
    public static void getNullPointerException(){
        try {
            String s = null;
            System.out.println(s.toLowerCase());
        }catch (NullPointerException e){
            System.out.println("Exception: "+ e );
        }
    }
    public static void getClassCastException(){
        try{
            Task1.Watch watch = new Task1.Watch();
            Task1.StopWatch stopWatch = (Task1.StopWatch) watch;
        }catch (ClassCastException e){
            System.out.println("Exception: "+ e );
        }
    }
    public static void produceIllegalArgumentException(int value){
        if (value < 0 || value > 100) {
            throw new IllegalArgumentException("参数value的取值范围必须在[0, 100]之间");
        }
    }
    public static void getIllegalArgumentException(int value){
        try{
            produceIllegalArgumentException(value);
        } catch (IllegalArgumentException e){
            System.out.println("Exception: "+ e );
        }
    }
}
# Main.java
import cc.aquaoh.*;
import java.util.Arrays;
public class Main {
    public static  void task6(){
        Task6.getArithmeticException();
        Task6.getIndexOutOfBoundsException();
        Task6.getNullPointerException();
        Task6.getClassCastException();
        Task6.getIllegalArgumentException(101);
    }
    public static void main(String[] args) throws InterruptedException {
        task6();
    }
}

# Task 7

# 理论

# enum (枚举类)
# 定义
  • 定义:

    • Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。
    • 每个常量实际上是枚举类的实例。
    • 枚举类是一个特殊的类,,它一样有自己的成员变量、方法,可以实现一个或多个接口,也可以定义自己的构造器。
    • 特性:
      • 一个 java 源文件最多只能定义一个 public 访问权限的 枚举类。且该 java 源文件也必须和该枚举的类名相同(嵌套类不包含其中)
      • 使用 enum 定义、非抽象的枚举类会默认使用 final 修饰
      • 在枚举类中列出枚举值时,实际上就是调用构造器创建枚举类对象。只是这里无需使用 new 关键字。也无需显示调用构造器。前面列出枚举值时无需传入参数(调用无参的构造方法),也可以传入参数(有参的构造方法)
      • 枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远不能产生实例。列出这些实例时系统会自动
        添加 public static final 修饰,无需自动添加
  • 继承关系:

    • 枚举类默认继承了 java.lang.Enum 类,而不是 Object 类,所以枚举类不能显示继承其他父类。
      • Enum 类中有一个唯一的构造器: protected Enum(String name, int ordinal)
      • 这个构造器不是程序员手动调用的,是编译器自动调用,在所有枚举类型的构造器的首行,并且自动传入 name 和 ordinal 的值。
      • name:就是枚举对象名称
      • ordinal:就是枚举对象的序号(在枚举声明中的位置),其中初始常量序号为 0
    • 其中 java.lang.Enum 类实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。
  • 自动编号:

    • Java 自动给按照枚举值出现的顺序,从 0 开始分配了编号。
    • 通过 name () 方法可以获得枚举值的名称。等价于 toString () 方法。
    • 通过 ordinal () 方法可以获得枚举值的编号。
    public class Task7 {
        enum Color{
            RED, GREEN, BLUE;
        }
        public static void test(){
            Color c1 = Color.RED;
            System.out.println(c1.name());
            System.out.println(c1.toString());
            System.out.println(c1.ordinal());
        }
    }
    // 输出值
    /*
    RED
    RED
    0
    */
  • 枚举构造器:

    • 枚举类的构造器只能用 private 访问控制符,因为子类构造器总要调用父类构造器一次,所以枚举类不能派生子类
    • 枚举类是一个类,有自己的方法和属性,通过构造器可以给类的实例赋值
    public enum Sex {
    ("李华",18),("李花",17);
            private String name;
            private int age;
            // 构造方法,默认为 private
            private Sex (String name,int age){
                this.name = name;
                this.age = age;
            }
            public String getName(){
                return name;
            }
            public int getAge(){
                return age;
            }
        }
  • 枚举可以在内部类中声明

    public class Test 
    { 
        enum Color 
        { 
            RED, GREEN, BLUE; 
        } 
      
        // 执行输出结果
        public static void main(String[] args) 
        { 
            Color c1 = Color.RED; 
            System.out.println(c1); 
        } 
    }
# 使用
  • 迭代枚举元素:

    enum Color 
    { 
        RED, GREEN, BLUE; 
    } 
    public class MyClass { 
      public static void main(String[] args) { 
        for (Color myVar : Color.values()) {
          System.out.println(myVar);
        }
      } 
    }
  • values (), ordinal () 方法:

    • values () 返回枚举类中所有的值。(将枚举类转变为一个枚举类型的数组)

      • enumType.values
    • ordinal () 方法可以找到每个枚举常量的索引,就像数组索引一样。

      enum Color 
      { 
          RED, GREEN, BLUE; 
      } 
        
      public class Test 
      { 
          public static void main(String[] args) 
          { 
              // 调用 values () 
              Color[] arr = Color.values(); 
        
              // 迭代枚举
              for (Color col : arr) 
              { 
                  // 查看索引
                  System.out.println(col + " at index " + col.ordinal()); 
              } 
        
              // 使用 valueOf () 返回枚举常量,不存在的会报错 IllegalArgumentException 
              System.out.println(Color.valueOf("RED")); 
              // System.out.println(Color.valueOf("WHITE")); 
          } 
      }
      // 输出结果
      /*
      RED at index 0
      GREEN at index 1
      BLUE at index 2
      RED
      */
  • compareTo () 方法:

    • 函数原型: public final int compareTo(Enum object)
    • 参数:
      • object − 这是要比较的对象。
    • 返回值:
      • 假定 A.compareTo(B)
      • 返回 A.ordinal()-B.ordinal()
  • valueOf () 方法:

    • 函数原型:

      • public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
      • 实际用法: A.valueOf(String name) 或者 public static A valueOf(A.class, String name)
      • 备注:两个参数的可以返回,任何类型的枚举,而具体枚举类的 valueOf () 只可以返回本身对象的枚举对象
    • 参数:

      • enumType − 这是枚举类型的 Class 对象,从中返回一个常量 (如上面枚举的 Color)
      • name − 这是要返回的常量的名称。即 toString () 方法返回的值(比如枚举类为 SPRING,对应的名称就是 "SPRING")
    • 返回值:

      • 该方法返回具有指定名称的指定枚举类型的枚举常量。
    • 异常:

      • IllegalArgumentException − 如果指定的枚举类型没有指定名称的常量,或者指定的类对象不代表枚举类型。
      • NullPointerException − 如果 enumType 或 name 为 null。
    • 实例:

      SeasonEnum a;// 定义枚举类型变量 a,用于接收 valueOf () 方法的返回值
      a=SeasonEnum.valueOf("SPRING");//valueOf () 方法调用者为枚举类,此处为 SeasonEnum;形参只需一个 —— 枚举值名称,注意是 String 类型,要加双引号,此处为 "SPRING"
      System.out.println(a.compareTo(SeasonEnum.SPRING));// 通过 a 调用实例方法 compareTo (),输出 0,表明 a 为枚举值 SPRING
  • switch

    enum Signal {
        GREEN, YELLOW, RED
    }
    public class TrafficLight {
        Signal color = Signal.RED;
        public void change() {
            switch (color) {
                case RED:
                    color = Signal.GREEN;
                    break;
                case YELLOW:
                    color = Signal.RED;
                    break;
                case GREEN:
                    color = Signal.YELLOW;
                    break;
            }
        }
    }
# ArrayList (动态数组)
# 定义
  • ArrayList<E> objectName =new ArrayList<>();
    • E: 泛型数据类型,用于设置 objectName 的数据类型,只能为引用数据类型
    • objectName: 对象名
# Collections 类
  • addAll () 方法

    • 函数原型: public static boolean addAll(Collection<? super T> c, T... elements) { ... }

    • 实例:

      // 用法 1
      String[] arr = {"河南", "郑州", "开封", "周口", "商丘"};
              ArrayList<String> list = new ArrayList<>();
              Collections.addAll(list, arr);
      // 用法 2
        ArrayList<String> list = new ArrayList<>();
              Collections.addAll(list, "河南", "郑州", "开封", "周口", "商丘");
  • shuffle () 方法

    • 函数原型:

      • public static void shuffle (List<?> list)
      • public static void shuffle (List<?> list, Random rand)
    • 作用:

      • 随机打乱原来的顺序

      • 使用随机源对列表进行置换,所有置换发生的可能性都是大致相等的。(假定随机源是公平的)

      • 未指定随机源则使用默认随机源。

    • 实例:

      • List<Integer> list=ArrayList(Arrays.asList(ia)) , 用 shuffle () 打乱不会改变底层数组的顺序。

      • List<Integer> list=Arrays.aslist(ia) , 然后用 shuffle () 打乱会改变底层数组的顺序。代码例子如下:(即内存相同了)

      package ahu;
      import java.util.*;
       
      public class Modify {
      	public static void main(String[] args){
      		Random rand=new Random(47);
      		Integer[] ia={0,1,2,3,4,5,6,7,8,9};
      		List<Integer> list=new ArrayList<Integer>(Arrays.asList(ia));
      		System.out.println("Before shufflig: "+list);
      		Collections.shuffle(list,rand);
      		System.out.println("After shuffling: "+list);
      		System.out.println("array: "+Arrays.toString(ia));
      		List<Integer> list1=Arrays.asList(ia);
      		System.out.println("Before shuffling: "+list1);
      		Collections.shuffle(list1,rand);
      		System.out.println("After shuffling: "+list1);
      		System.out.println("array: "+Arrays.toString(ia));
      		
      	}
      }

# 代码

# Task7.java
package cc.aquaoh;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
public class Task7 {
    enum Color {
        RED(1),
        GREEN(2),
        BLUE(3);
        int type;
        Color(int _type) {
            this.type = _type;
        }
    }
    public static void test() {
        ArrayList<Color> list = new ArrayList<>();
        for (int i = 1; i <= 3; i++) {
            Collections.addAll(list, Color.values());
        }
        Random r = new Random(1234567);
        Collections.shuffle(list, r);
        for (int i = 0; i < list.size(); i++) {
            Color c = list.get(i);
            switch (c){
                case RED:
                    System.out.print(Color.RED.type);
                    break;
                case GREEN:
                    System.out.print(Color.GREEN.type);
                    break;
                case BLUE:
                    System.out.print(Color.BLUE.type);
                    break;
            }
        }
    }
}
# Main.java
import cc.aquaoh.*;
import java.util.Arrays;
public class Main {
    public static void task7(){
        Task7.test();
    }
    public static void main(String[] args) throws InterruptedException {
        task7();
    }
}

# Task 8

# 理论

# Lambda 表达式
  • 语法:

    (parameters) -> expression
    (parameters) ->{ statements; }
    • parameters 是参数列表
    • expression{ statements; } 是 Lambda 表达式的主体。
    • 如果只有一个参数,可以省略括号;如果没有参数,也需要空括号。
  • 实例:

    // 使用 Lambda 表达式计算两个数的和
    MathOperation addition = (a, b) -> a + b;
    // 调用 Lambda 表达式
    int result = addition.operation(5, 3);
    System.out.println("5 + 3 = " + result);public class Main {
        public static void main(String[] args) {
            String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
            Arrays.sort(array, (s1, s2) -> {
                return s1.compareTo(s2);
            });
            System.out.println(String.join(", ", array));
        }
    }

    提炼 Lambda 表达式

    (s1, s2) -> {
        return s1.compareTo(s2);
    }
    • 参数是 (s1, s2) ,参数类型可以省略,因为编译器可以自动推断出 String 类型

    • -> { ... } 表示方法体,所有代码写在内部即可。

    • Lambda 表达式没有 class 定义,因此写法非常简洁。

    • 如果只有一行 return xxx 的代码,完全可以用更简单的写法:

      Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));
  • 其他实例

    // 1. 不需要参数,返回值为 5  
    () -> 5  
      
    // 2. 接收一个参数 (数字类型), 返回其 2 倍的值  
    x -> 2 * x  
      
    // 3. 接受 2 个参数 (数字), 并返回他们的差值  
    (x, y) -> x – y  
      
    // 4. 接收 2 个 int 型整数,返回他们的和  
    (int x, int y) -> x + y  
      
    // 5. 接受一个 string 对象,并在控制台打印,不返回任何值 (看起来像是返回 void)  
    (String s) -> System.out.print(s)
  • 函数式编程支持

    • 允许将函数当作参数传递给方法,或者将函数作为返回值

    • // 使用 Lambda 表达式作为参数传递给方法
      List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
      names.forEach(name -> System.out.println(name));
    • 补充:forEach ()

      default void forEach(Consumer<? super T> action) {
              Objects.requireNonNull(action);
              for (T t : this) {
                  action.accept(t);
              }
       }
  • 变量捕获

    • Lambda 表达式可以访问外部作用域的变量,这种特性称为变量捕获

    • Lambda 表达式可以隐式地捕获 final 或事实上是 final 的局部变量。

    • // 变量捕获
      int x = 10;
      MyFunction myFunction = y -> System.out.println(x + y);
      myFunction.doSomething(5); // 输出 15
  • 方法引用

    // 使用方法引用
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
    names.forEach(System.out::println);
# 类型推导
  • 通过使用 var 关键字,可以让编译器根据上下文自动推断局部变量的类

    型。这个特性可以使代码更加简洁和易读

  • public class Main {
    public static void main(String[] args) {
    String str1 = "Hello Java";
    var str2 = "Hello JDK10";
    System.out.println(str1);
    System.out.println(str2);
    }
    }
# switch 表达式
  • Switch 表达式允许在 Switch 语句中使用 Lambda 风格的语法进行模式匹配,并直接返回值。

  • 在传统的 Switch 语句中,每个 case 分支都需要使用 break 语句来防止掉落到下一个分支。而在 Switch 表达式中,

我们可以使用箭头 (-> ) 来直接返回值,而不需要使用 break 语句。

  • public class Main {
    public static void main(String[] args) {
    var day = 3;
    var dayName = switch (day) {
    case 1, 2, 3, 4, 5 -> "工作日";
    case 6, 7 -> "周末";
    default -> throw new RuntimeException("Invalid day");
    };
    System.out.println(dayName);
    }
    }

# 代码

# Task8.java
package cc.aquaoh;
public class Task8 {
    public static boolean isLeapYear(int year){
        return (year % 100 != 0 && year % 4 == 0) || year % 400 == 0;
    }
    public static void getDateNumOfMonth(int year,int month){
        int dateNum = switch (month){
            case 1, 3, 5, 7, 8, 10, 12 -> 31;
            case 4, 6, 9, 11 -> 30;
            case 2 -> {
                if(isLeapYear(year)){
                    yield 29;
                }else {
                    yield 28;
                }
            }
            default -> throw new RuntimeException("Invalid month");
        };
        System.out.println("\n"+dateNum);
    }
}

# Main.java

import cc.aquaoh.*;
import java.util.Arrays;
public class Main {
    public static void task8(){
        Task8.getDateNumOfMonth(2024,3);
    }
    public static void main(String[] args) throws InterruptedException {
        task8();
    }
}

# 三、 性能测试情况

# Task 1

image-20240315102547393

# Task 2

image-20240322001037812

# Task 3

image-20240322001127379

# Task 6

image-20240322001220964

# Task 7

image-20240322001304843

# Task 8

image-20240322001340674

# 四、总结

# Main.java

import cc.aquaoh.*;
import java.util.Arrays;
public class Main {
    public static void task1() throws InterruptedException {
        Task1.StopWatch clock = new Task1.StopWatch();
        clock.start();
        //static void sleep (long mills),单位为 ms
        Thread.sleep(1000);
        clock.stop();
        long gap = clock.getElapsedTime();
        System.out.printf("%s ms\n",gap);
    }
    public static void task2(){
        Task2.Fish[] fishArray = new Task2.Fish[10];
        for(int i=0;i<10;i++){
            fishArray[i] = new Task2.Fish();
        }
        Arrays.sort(fishArray);
        int i;
        for(i=0;i<9;i++){
            fishArray[i].print();
        }
        fishArray[i].endprint();
    }
    public static void task3(){
        Task3.SalesEmployee salesEmployee = new Task3.SalesEmployee("张伟",11,114514,0.19,1981);
        Task3.SalariedEmployee salariedEmployee = new Task3.SalariedEmployee("李强",1,14514);
        Task3.HourlyEmployee  hourlyEmployee = new Task3.HourlyEmployee("丁真",5,240,50);
        int month = 1;
        System.out.printf("第%d月的工资为%.2f\n",month,salesEmployee.getSalary(month));
        System.out.printf("第%d月的工资为%.2f\n",month,salariedEmployee.getSalary(month));
        System.out.printf("第%d月的工资为%.2f\n",month, hourlyEmployee.getSalary(month));
    }
    public static void task6(){
        Task6.getArithmeticException();
        Task6.getIndexOutOfBoundsException();
        Task6.getNullPointerException();
        Task6.getClassCastException();
        Task6.getIllegalArgumentException(101);
    }
    public static void task7(){
        Task7.test();
    }
    public static void task8(){
        Task8.getDateNumOfMonth(2024,3);
    }
    public static void main(String[] args) throws InterruptedException {
        task1();
        task2();
        task3();
        task6();
        task7();
        task8();
    }
}

# Task1:

  • 学习了类的继承、子类对父类方法的重写、类的方法重载
    • 只能单继承, childClass extends fatherClass
    • 重写是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变
    • 重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或构造函数)都必须有一个独一无二的参数类型列表。
  • 学习了如何调用嵌套类的内部方法
    • 对封闭类添加 static 关键字
  • 学习了静态方法
    • 静态方法可以不创建实例直接调用

# Bonus Task1 (optional):

  • 比较了 java 的统一 extends 继承方式和 C++ 的 publicprotectedprivate 三种继承方式的优略
    • Java 更简洁易读且更容易学习

# Task 2:

  • 学习了接口

    • 接口不能实例化

    • 一个类可以多继承接口,被继承的接口的所有方法必须在子类实现

    • 接口可以继承接口

    • 接口内方法均为抽象方法

  • 学习了 Random 类

  • 学习了 Comparable 接口

    public interface Comparable<T> {
    	public int compareTo(T o);
    }
    
  • 学习了对象数组

  • 学习了增强 for 循环

# Task 3:

  • 学习了抽象类
    • 不能实例化对象 \rightarrow 如果子类继承抽象类后想不实例化使用,则父类子类都添加 static 关键字
    • 类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
    • 抽象类必须被继承,才能被使用

# **Task4: **

  • 比较了抽象类和接口的使用场景区别
    • 当需要使用多继承特性时使用接口
    • 不使用多继承特性时,抽象类或者接口均可

# Task5:

  • 比较了 Error 和 Exception 的区别
    • Error: 程序无法处理,通常指程序中出现的严重问题。
    • Exception: 程序本身可以处理的异常(可以向上抛出或者捕获处理)
      • CheckedException:(编译时异常)
        • 程序编译时就会出现的异常
        • 编译异常是 java.lang.RuntimeException 以外的异常。
      • UnCheckedException(RuntimeException):(运行时异常)不需要捕获,对于程序错误(不可恢复)的异常使用 RuntimeException。
        • 运行异常是 java.lang.RuntimeException 类及其子类的统称

# Task6:

  • 学习了 try catch 语句以及 finally 语句

    • try {
        	statements
      } catch (Exception e) {
      	statements
        	//System.out.println("Exception thrown :" + e);
      } finally{
          // 必定会执行
          statements
      }
  • 学习了 throw 关键字

    • throw 语句允许创建自定义错误

    • throw new Exception (statements)
      
  • 学习了 throws 关键字

    • throws 关键字用于在方法声明中指定该方法可能抛出的异常。

    • 当方法内部抛出指定类型的异常时,该异常会被传递给调用该方法的代码,并在该代码中处理异常。

    • public void method() throws Exception {
      	statements
      }

# Task7:

  • 学习了 enum 枚举
    • Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。
    • 本质还是类,有自己的隐式构造方法,也可以显示定义,可以有自己的属性和方法
  • 学习了 ArrayList 动态数组
  • 学习了 Collections 类

# Task8:

  • 学习了 Lambda 表达式

    • (parameters) -> expression
      (parameters) ->{ statements; }
  • 学习了类型推导

    • var arg1 = "Hello World!"
    • 自动根据上下文推导类型
  • 学习了 switch 语句和 Lambda 表达式的结合

    • Switch 表达式中,可以使用箭头 (-> ) 来直接返回值,而不需要使用 break 语句。