Java进阶 1-1 枚举

目录

枚举的基本特性

枚举类型中的自定义方法

switch语句中的枚举

编译器创建的values()方法

使用实现代替继承

构建工具:生成随机的枚举

组织枚举

EnumSet

EnumMap


本笔记参考自: 《On Java 中文版》


        枚举类型通过enum关键字定义,其中包含了数量有限的命名变量。

枚举的基本特性

       当我们创建枚举类型时,系统会自动为我们生成一个辅助类,这个类继承自java.lang.Enum。下面的例子展示了其中的一些方法:

【例子:枚举自带的方法】

enum Fruit {APPLE,BANANA,ORANGE
}public class EnumClass {public static void main(String[] args) {for (Fruit f : Fruit.values()) {System.out.println(f + "对应序数:" + f.ordinal());System.out.println("compareTo(BANANA):" + f.compareTo(Fruit.BANANA));System.out.println("equals(BANANA):" + f.equals(Fruit.BANANA));System.out.println("f == Fruit.ORANGE? " +(f == Fruit.ORANGE));System.out.println(f.getDeclaringClass());System.out.println(f.name());System.out.println("====================");}}
}

        程序执行的结果是:

        简单介绍一些方法:

  • values():生成一个由枚举常量组成的数组,其中常量的顺序和常量声明的顺序保持一致。
  • ordinal():返回一个从0开始的int值,代表每个枚举实例的声明顺序。
  • getDeclaringClass():获得该枚举实例所属的外部包装类。
  • name():返回枚举实例被声明的名称。

    equals()方法由编译器自动生成,而compareTo()方法则来自Comparable接口(Enum实现了它),这里不再赘述。

静态导入枚举类型

        使用枚举类型的理由之一,就是枚举可以增强我们代码的可读性。有时,我们会使用静态导入枚举的方式使用枚举:

【例子:静态导入的枚举类型】

        首先创建一个枚举类型:

// 关于香料的枚举:
public enum SpicinessEnum {NOT, MILD, MEDIUM, HOT, FLAMING
}

        现在,让我们在程序中静态导入它:

// 静态导入一个枚举类型:
import static enums.SpicinessEnum.*;// 制作一个玉米煎饼:
public class Burrito2 {SpicinessEnum degree;public Burrito2(SpicinessEnum degree) {this.degree = degree;}@Overridepublic String toString() {return "来个玉米饼,添加香料:" + degree;}public static void main(String[] args) {System.out.println(new Burrito2(NOT));System.out.println(new Burrito2(MEDIUM));System.out.println(new Burrito2(HOT));}
}

        程序执行的结果是:

        通过static import,我们将所有的枚举实例标识符都引入了本地命名空间。值得一提的是,是否静态导入枚举类型大多不会影响代码的运行,但我们仍需要考虑代码的可读性:若代码本身很复杂,静态导入或许就不会是一个更好的选择。

    若枚举定义在通过文件,或定义在默认包中,则无法使用上述的这种方式。

枚举类型中的自定义方法

        除去无法继承,基本上可以将枚举类型看做一个普通的类。可以向其中添加自定义方法,甚至于main()方法。

        通过创建一个含参构造器,枚举可以获取额外的信息,并通过额外的方法来扩展应用。例如:

【例子:在枚举中创建新的方法】

public enum MakeAHuman {HEAD("我来组成头部"),BODY("我来组成躯体"),TONSIL("我来组成扁桃体");private String description;private MakeAHuman(String description) {this.description = description;}public String getDescription() {return description;}// 也可以进行方法重载:@Overridepublic String toString() {String id = name();String lower = id.substring(1).toLowerCase();return id.charAt(0) + lower;}public static void main(String[] args) {for (MakeAHuman human : MakeAHuman.values())System.out.println(human +": " + human.getDescription());}
}

        程序执行的结果是:

        若想要添加自定义方法,首先必须用分号结束枚举实例的序列。注意:Java会强制我们在枚举中先定义实例

        之前也提到过,枚举类型不允许继承。这意味着枚举在被定义完毕后,无法被用于创建任何新的类型。

switch语句中的枚举

        枚举类型可以被应用于switch语句。一般,switch语句只支持整型或字符串类型,但ordinal()方法可以获取枚举内部的整型序列。在这里,编译器完成了后台的各种工作。

        在通常情况下,若要使用枚举实例,就需要使用枚举的类型名对其进行限定。但在switch语句中不需要这么做:

【例子:switch中的枚举】

enum Signal {GREEN, YELLOW, RED
}public class TrafficLight {Signal color = Signal.RED;public void change() {// 在case语句中,无需使用Signal进行限定:switch (color) {case RED:color = Signal.GREEN;break;case GREEN:color = Signal.YELLOW;break;case YELLOW:color = Signal.RED;break;}}@Overridepublic String toString() {return "现在,信号灯的颜色是:" + color;}public static void main(String[] args) {TrafficLight t = new TrafficLight();for (int i = 0; i < 7; i++) {System.out.println(t);t.change();}}
}

        程序执行的结果是:

        尽管没有添加default语句,但编译器也没有报错。这不见得是一件好事,因为即使我们注释掉了其中的一条分支,编译器也不会报错:

        因此,在编写分支语句时我们必须小心,确保代码已经覆盖了所有的分支。

编译器创建的values()方法

        根据官方文档的描述,Enum类中并不存在values()方法:

因此我们可以猜测,编译器在后台为我们完成了某件事。接下来的例子会通过反射分析Enum类中的方法:

【例子:通过反射探究Enum类】

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.TreeSet;enum Explore {HERE,THERE
}public class Reflection {public static Set<String> analyze(Class<?> enumClass) {System.out.println("_____分析" + enumClass + "_____");System.out.println("$接口:");for (Type t : enumClass.getGenericInterfaces())System.out.println(t);System.out.println("$基类:" +enumClass.getSuperclass());System.out.println("$方法:");Set<String> methods = new TreeSet<>();for (Method m : enumClass.getMethods())methods.add(m.getName());System.out.println(methods);return methods;}public static void main(String[] args) {Set<String> exploreMethods =analyze(Explore.class);System.out.println();Set<String> enumMethods =analyze(Enum.class);System.out.println();System.out.println("Explore.containsAll(Enum)? " +exploreMethods.containsAll(enumMethods));System.out.print("Explore.removeAll(Enum): ");exploreMethods.removeAll(enumMethods);System.out.println("[" + exploreMethods + "]");}
}

        程序执行的结果是:

        从反射的结果可以发现,枚举Explore中多出了Enum没有的values()方法。现在让我们进一步,通过反编译Explore来查看它的内部信息:

反编译告诉我们,values()方法是由编译器添加的一个静态方法。除此之外,还可以发现:Explore内部的valueOf()方法只有一个参数,而Enum自带的valueOf()却有两个参数:

然而,Set只会关注方法名,因此在执行Explore.remove(Enum)后,valueOf()方法也被去除了。

    从反射的结果中,我们还可以知道两件事:① Explore枚举是final类,因此我们无法继承;②反射认为Explore的基类就是Enum,这并不准确,Explore的基类应该是Enum<Explore>,类型擦除使得编译器无法获取完整类型信息,因此才会出现这种现象。

        需要注意的一点是,values()只在子类Explore中存在(由编译器插入),因此当我们将该枚举类型向上转型为Enum时。我们将无法使用这个方法。作为替代,可以使用Class.getEnumConstants()

【例子:getEnumConstants()的使用例】

enum Search {HITHER,YON
}public class UpcastEnum {public static void main(String[] args) {Search[] vals = Search.values();Enum e = Search.HITHER; // 发生向上转型// e.values(); // 此时会发现Enum中并没有values()方法// Class.getEnumConstants()方法返回一个包含枚举中的每个元素的数组for (Enum en : e.getClass().getEnumConstants())System.out.println(en);}
}

        程序执行的结果是:

    因为getEnumConstants()方法属于Class类,因此非枚举类型也可以调用它。不过此时方法会返回null,调用这个结果会抛出异常。

使用实现代替继承

        已知,所有枚举类都会默认继承java.lang.Enum。而Java不支持多重继承,这就意味着一个枚举类无法再继承任何其他的类:

// enum NotPossible extends SomethingElse { ... // 不允许这么做

        作为替代,我们可以令枚举类型实现一些接口:

【例子:让枚举类实现接口】

import java.util.Random;
import java.util.function.Supplier;enum LetterCharacterimplements Supplier<LetterCharacter> {A, B, C, D, E, F, G;private Random rand =new Random(47);@Overridepublic LetterCharacter get() {return values()[rand.nextInt(values().length)];}
}public class EnumImplementation {public static <T> void printNext(Supplier<T> rg) {System.out.print(rg.get() + " ");}public static void main(String[] args) {LetterCharacter ll = LetterCharacter.G;for (int i = 0; i < 10; i++)printNext(ll);}
}

        程序执行的结果是:

        这种做法有一点很奇怪:我们必须传入一个枚举实例,然后才能使用printNext()方法。

构建工具:生成随机的枚举

       为了方便我们使用枚举,可以创建一个用于随机生成枚举的Enums类:

package onjava;public class Enums {private static Random rand = new Random(47);public static <T extends Enum<T>> T random(Class<T> ec) {return random(ec.getEnumConstants());}public static <T> T random(T[] values) {return values[rand.nextInt(values.length)];}
}

        这个类中的random()方法会接收Class对象,并返回随机的枚举对象。

(因为之后也会使用该类,因此在这里提前进行展示)

组织枚举

        尽管枚举类型无法继承,但我们任然会有可能用到继承关系的情况。一般地,继承枚举有两个动机:

  1. 希望扩充原始枚举中的元素。
  2. 想要使用子类型创建不同的子分组。

        一个方法是通过接口对枚举进行分类。下面的例子在接口中将元素分类完毕,然后会基于这个接口生成一个枚举,这样就能实现分类的目的:

【例子:在接口中分类】

public interface Food {enum Appetizer implements Food {SALAD, SOUP, SPRING_ROLLS;}enum MainCourse implements Food {RICE, NOODLES, BREAD, PASTA;}enum Dessert implements Food {CUPCAKE, JELLY, CANDY, CHOCOLATE, COOKIES;}enum Drink implements Food {COFFEE, TEA, JUICE, MILK}
}

        这种方式就像是将枚举作为了接口的子类型一样。通过静态导入,就可以使用它:

import enums.menu.Food;import static enums.menu.Food.*;public class TypeOfFood {public static void main(String[] args) {Food food = Appetizer.SALAD;food = MainCourse.RICE;food = Dessert.CANDY;}
}

        通过这种方法,我们就得到了“由接口组织的枚举”,但它还不足以应对所有情况。当我们需要处理一组类型时,接口并没有内置的方法能够为我们提供便利。此时,使用“由枚举组织的枚举”更为管用:

【例子:由枚举来组织枚举】

import onjava.Enums;public enum Course {APPETIZER(Food.Appetizer.class),MAINCOURSE(Food.MainCourse.class),DESSERT(Food.Dessert.class),COFFEE(Food.Drink.class);private Food[] values;// 接收枚举类型对应的Class对象private Course(Class<? extends Food> kind) {values = kind.getEnumConstants();}public Food randomSelection() {return Enums.random(values);}
}

         因为Course是一个枚举类型,因此我们可以直接使用枚举特有的方法:

public class Meal {public static void main(String[] args) {for (int i = 0; i < 5; i++) {for (Course course : Course.values()) {Food food = course.randomSelection();System.out.println(food);}System.out.println("======");}}
}

        程序执行的结果如下:

        Course可以直接调用枚举所有的方法,因此可以很方便地进行遍历操作。

        上述的做法需要使用接口和枚举。显然,可以将它们整理到一起,形成一种更加清晰的写法:

【例子:在枚举中嵌套枚举】

import onjava.Enums;enum SecurityCategory {STOCK(Security.Stock.class),BOND(Security.Bond.class);Security[] values;SecurityCategory(Class<? extends Security> kind) {values = kind.getEnumConstants();}interface Security {enum Stock implements Security {SHORT, LONG, MARGIN}enum Bond implements Security {MUNICIPAL, JUNK}}public Security randomSelection() {return Enums.random(values);}public static void main(String[] args) {for (int i = 0; i < 10; i++) {SecurityCategory category =Enums.random(SecurityCategory.class);System.out.println(category + ": " +category.randomSelection());}}
}

        程序执行的结果是:

        在这种方法中,枚举内部存在一个接口。通过它,我们可以将所需的枚举类型进行聚合。

EnumSet

        Set和枚举都对元素的唯一性有所要求,但枚举无法进行增删操作,因此不如Set来得便利。因此,用于配合枚举的Set类型,EnumSet诞生了。

    EnumSet的一个目的,是替代原本基于int的“位标记”用法。

    这一类型的一大优势就是速度,其内部的实现基于一个long类型的变量(位向量)

        EnumSet中的元素必须来源于某个枚举类型:

【例子:EnumSet的使用】

        为了使用EnumSet,首先需要创建一个枚举(报警器的位置信息):

// 报警感应器的位置
public enum AlarmPoints {STAIR1, STAIR2,LOBBY,OFFICE1, OFFICE2, OFFICE3, OFFICE4,BATHROOM,UTILITY,KITCHEN
}

        利用这个枚举,下面的程序会展示一些EnumSet的基本用法:

import java.util.EnumSet;import static enums.AlarmPoints.*;public class EnumSets {public static void main(String[] args) {// 使用noneOf()方法创建一个空的EnumSetEnumSet<AlarmPoints> points =EnumSet.noneOf(AlarmPoints.class);points.add(BATHROOM);System.out.println(points);points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));System.out.println(points);System.out.println();points = EnumSet.allOf(AlarmPoints.class);points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));System.out.println(points);points.removeAll(EnumSet.range(OFFICE1, OFFICE4));System.out.println(points);System.out.println();// complementOf()方法返回points中未包含的枚举集points = EnumSet.complementOf(points);System.out.println(points);}
}

        程序执行的结果是:

        EnumSet.of()方法具有多个重载

这是处于性能的考量。尽管这些of()方法可以被一个使用了可变参数的方法替代,但那样做的效率会略低于现在的这种做法。

    尽管表格上没有出现,但EnumSet中是存在使用可变参数列表的of()方法的。而如果我们只传入一个参数,编译器不会调用这个of()方法,因此也不会产生额外的开销。

        通常情况下,EnumSet是基于64位的long构建的。其中,每个枚举实例需要通过1位来表达其的状态。因此,在使用一个long时,单个EnumSet只能支持包含64个元素的枚举类型。但有时,我们的枚举会超过64个元素:

【例子:超过64个元素的EnumSet

public class BigEnumSet {enum Big {A1, A2, A3, A4, A5, A6, A7, A8, A9, A10,A11, A12, A13, A14, A15, A16, A17, A18, A19, A20,A21, A22, A23, A24, A25, A26, A27, A28, A29, A30,A31, A32, A33, A34, A35, A36, A37, A38, A39, A40,A41, A42, A43, A44, A45, A46, A47, A48, A49, A50,A51, A52, A53, A54, A55, A56, A57, A58, A59, A60,A61, A62, A63, A64, A65}public static void main(String[] args) {EnumSet<Big> bigEnumSet = EnumSet.allOf(Big.class);System.out.println(bigEnumSet);}
}

        程序执行的结果是:

        从输出结果可以看出,若元素超过64个,EnumSet会进行额外的处理。这一点也可以从源代码处了解:

EnumMap

        除了EnumSet,也存在EnumMap。这一特殊的Map要求所有的来自于某个枚举类型。EnumMap内部的实现基于数组,因此有着很高的效率。

    与普通的Map相比,EnumMap在操作上的特殊之处只在于:当我们调用put()方法时,只能使用枚举中的值。

【例子:EnumMap的使用例】

import java.util.EnumMap;
import java.util.Map;import static enums.AlarmPoints.*;// 使用了命令模式:
// 创建一个接口(只包含一个方法),衍生出不同的实现
interface Command {void action();
}public class EnumMaps {public static void main(String[] args) {EnumMap<AlarmPoints, Command> em =new EnumMap<>(AlarmPoints.class);em.put(KITCHEN,() -> System.out.println("厨房失火"));em.put(BATHROOM,() -> System.out.println("水龙头坏了"));for (Map.Entry<AlarmPoints, Command> e :em.entrySet()) {System.out.println(e.getKey() + ": ");e.getValue().action();}try { // 若不存在指定key值em.get(UTILITY).action();} catch (Exception e) {System.out.println("异常:" + e);}}
}

        程序执行的结果是:

        在上述的em中,所有的枚举元素都有其对应的键。并且根据输出结果的异常显示,所有的键对应的值都会被初始化为null

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/216739.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【从零开始学习JVM | 第六篇】快速了解 直接内存

前言&#xff1a; 当谈及Java虚拟机&#xff08;JVM&#xff09;的内存管理时&#xff0c;我们通常会想到堆内存和栈内存。然而&#xff0c;还有一种被称为"直接内存"的特殊内存区域&#xff0c;它在Java应用程序中起着重要的作用。直接内存提供了一种与Java堆内存和…

三天搞定jmeter入门到入职全套教程之使用Jmeter录制脚本

相对于LoadRunner跟SilkPerformer来说&#xff0c;Jmeter确实有差距&#xff0c;但毕竟前两者太贵&#xff0c;Jmeter胜在免费开源。 先看下LoadRunner录制的脚本如下&#xff0c;美如画&#xff0c;结构清晰&#xff0c;易于修改编辑&#xff0c;比如做关联等。当然目前LoadR…

Java项目-瑞吉外卖Day2

完善登录功能&#xff1a; 完善未登录不能访问/backend/index.html。使用拦截器或过滤器。 创建过滤器。 重写doFilter方法。 查看是否过滤成功。 处理流程如下&#xff1a; 添加员工功能&#xff1a; 点击保存&#xff0c;可以看到请求信息。 再看前端代码&a…

C++学习笔记—— C++内存管理方式:new和delete操作符进行动态内存管理

系列文章目录 http://t.csdnimg.cn/d0MZH 目录 系列文章目录http://t.csdnimg.cn/d0MZH 比喻和理解a.比喻C语言开空间C开空间 b.理解a、C语言的内存管理的缺点1、开发效率低&#xff08;信息传递繁琐&#xff09;2、可读性低&#xff08;信息展示混乱&#xff09;3、稳定性差&…

中间件系列 - Redis入门到实战(基础篇)

前言 1.学习视频&#xff1a; 黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目 2. 本内容仅用于个人学习笔记&#xff0c;如有侵扰&#xff0c;联系删除 3. 本章学习目标&#xff1a; 初始Redis 认识NoSQL认识Redi…

C++STL库的 deque、stack、queue、list、set/multiset、map/multimap

deque 容器 Vector 容器是单向开口的连续内存空间&#xff0c; deque 则是一种双向开口的连续线性空 间。所谓的双向开口&#xff0c;意思是可以在头尾两端分别做元素的插入和删除操作&#xff0c;当然&#xff0c; vector 容器也可以在头尾两端插入元素&#xff0c;但是在其…

【LeetCode刷题-树】-- 116.填充每个节点的下一个右侧节点指针

116.填充每个节点的下一个右侧节点指针 方法&#xff1a;层次遍历 /* // Definition for a Node. class Node {public int val;public Node left;public Node right;public Node next;public Node() {}public Node(int _val) {val _val;}public Node(int _val, Node _left, N…

利用anaconda中的Conda创建虚拟环境

目录 1. Anaconda 环境变量手动设置(详细)2. Conda 创建虚拟环境参考文献 1. Anaconda 环境变量手动设置(详细) 问题 Win键r打开运行对话框&#xff0c;输入cmd回车 输入conda&#xff0c;显示&#xff1a;‘conda’ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处…

gamit一(虚拟机启动不了)

Intel VT-x处于禁用状态怎么办-百度经验 1重新启动电脑 2找到电脑对应的品牌&#xff0c;联想G510是F2, 3进去BIOS&#xff0c;configure里面修改virtual为enable&#xff0c;回车 4F10保存&#xff0c;退出

【教3妹学编程-算法题】需要添加的硬币的最小数量

3妹&#xff1a;2哥2哥&#xff0c;你有没有看到新闻&#xff0c; 有人中了2.2亿彩票大奖&#xff01; 2哥 : 看到了&#xff0c;2.2亿啊&#xff0c; 一生一世也花不完。 3妹&#xff1a;为啥我就中不了呢&#xff0c;不开心呀不开心。 2哥 : 得了吧&#xff0c;你又不买彩票&…

HTML实现页面

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>工商银行电子汇款单</title> </head> &…

Ubuntu22.04 LTS + CUDA12.3 + CUDNN8.9.7 + PyTorch2.1.1

简介 本文记录Ubuntu22.04长期支持版系统下的CUDA驱动和cuDNN神经网络加速库的安装&#xff0c;并安装PyTorch2.1.1来测试是否安装成功。 安装Ubuntu系统 如果是旧的不支持UEFI启动的主板&#xff0c;请参考本人博客U盘系统盘制作与系统安装&#xff08;详细图解&#xff09…

1839_emacs中org-mode的代码结构

Grey 全部学习内容汇总&#xff1a; GitHub - GreyZhang/g_org: my learning trip for org-mode 1839_emacs中org-mode的代码结构 org-mode的代码结构主要是简单介绍一下如何让插入的代码片段具备源代码的处理属性&#xff0c;比如说以一定的语法进行显示、执行、被某些程序…

【探讨】bp神经网络是前馈还是后馈

目录 一、BP神经网络简介 1.1 什么是BP神经网络 1.2 BP神经网络的结构 二、BP神经网络的前馈与后馈 2.1 什么是BP神经网络的前馈 2.2 什么是BP神经网络的后馈 三、BP神经网络前馈与后馈的关系 3.1 BP神经网络前馈与后馈的区别 3.2 BP神经网络前馈与后馈的意义 四、BP…

php实现个性化域名(短网址)和个性化登录模版的解决方案

在PHP中&#xff0c;个性化域名通常指的是根据用户或业务需求动态生成具有特定规律的子域名。实现个性化域名的方法主要依赖于服务器配置和路由规则。下面是一些基本的步骤和考虑因素&#xff0c;以帮助你了解如何个性化域名&#xff0c;并了解这样做的好处。 如何实现个性化域…

注意力机制和自注意力机制

有很多自己的理解&#xff0c;仅供参考 Attention注意力机制 对于一张图片&#xff0c;我们第一眼看上去&#xff0c;眼睛会首先注意到一些重点的区域&#xff0c;因为这些区域可能包含更多或更重要的信息&#xff0c;这就是注意力机制&#xff0c;我们会把我们的焦点聚焦在比…

开源治理典型案例分享(汇编转)

当前&#xff0c;越来越多的企业申请通过信通院的开源治理成熟度评估和认证&#xff0c;获得增强级或先进级评估。这些企业包括中国工商银行股份有限公司、中国农业银行、上海浦东发展银行股份有限公司、中信银行股份有限公司、中国太平洋保险&#xff08;集团&#xff09;股份…

练练手之“四环”“磁铁”(svg)

文本是闲暇之余练习svg的运用的产物&#xff0c;记录以备有需。 <svg xmlns"http://www.w3.org/2000/svg" viewBox"0 0 500 500" width"500px" height"500px"><path d"M150,100 A50,50 0 1,1 150,99.999" stroke&q…

MySQL笔记-第07章_单行函数

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第07章_单行函数1. 函数的理解1.1 什么是函数1.2 不同DBMS函数的差异1.3 MySQL的内置函数及分类 2. 数值函数2.1 基本函数2.2 角度与弧度互换…

孩子还是有一颗网安梦——Bandit通关教程:Level 8 → Level 9

&#x1f575;️‍♂️ 专栏《解密游戏-Bandit》 &#x1f310; 游戏官网&#xff1a; Bandit游戏 &#x1f3ae; 游戏简介&#xff1a; Bandit游戏专为网络安全初学者设计&#xff0c;通过一系列级别挑战玩家&#xff0c;从Level0开始&#xff0c;逐步学习基础命令行和安全概念…