使用 Picocli 开发 Java 命令行,5 分钟上手

大家好,我是鱼皮,对不会前端的同学来说,开发 命令行工具 是一种不错的展示系统功能的方式。在 Java 中开发命令行工具也很简单,使用框架,几分钟就能学会啦~

Picocli 入门

Picocli 是 Java 中个人认为功能最完善、最简单易用的命令行开发框架,可以帮助大家快速开发命令行工具。

网上有关 Picocli 框架的教程非常少,最推荐的入门方式除了看鱼皮的教程外,就是阅读官方文档了。

官方文档:https://picocli.info/

推荐从官方提供的快速入门教程开始:https://picocli.info/quick-guide.html

一般我们学习新技术的步骤是:先跑通入门 Demo,再学习该技术的用法和特性。

入门 Demo

1)在 Maven 项目的 pom.xml 文件中引入 picocli 的依赖:

<!-- https://picocli.info -->
<dependency>
    <groupId>info.picocli</groupId>
    <artifactId>picocli</artifactId>
    <version>4.7.5</version>
</dependency>

然后我们在 com.yupi 包下新建 cli.example 包,用于存放所有和 Picocli 入门有关的示例代码。

2)复制官方快速入门教程中的示例代码到 com.yupi.cli.example 包下,并略微修改 run 方法中的代码,打印参数的值。

完整代码如下:

package com.yupi.cli.example;

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

@Command(name = "ASCIIArt", version = "ASCIIArt 1.0", mixinStandardHelpOptions = true
public class ASCIIArt implements Runnable 

    @Option(names = { "-s""--font-size" }, description = "Font size"
    int fontSize = 19;

    @Parameters(paramLabel = "<word>", defaultValue = "Hello, picocli"
               description = "Words to be translated into ASCII art.")
    private String[] words = { "Hello,""picocli" }; 

    @Override
    public void run() {
        // 自己实现业务逻辑
        System.out.println("fontSize = " + fontSize);
        System.out.println("words = " + String.join(",", words));
    }

    public static void main(String[] args) {
        int exitCode = new CommandLine(new ASCIIArt()).execute(args); 
        System.exit(exitCode); 
    }
}

看不懂这段代码没关系,官方文档已经给了非常详细的解释:

帮大家翻译一下:

  1. 创建一个实现 RunnableCallable 接口的类,这就是一个命令。
  2. 使用 @Command 注解标记该类并为其命名, mixinStandardHelpOptions 属性设置为 true 可以给应用程序自动添加 --help--version 选项。
  3. 通过 @Option 注解将字段设置为命令行选项,可以给选项设置名称和描述。
  4. 通过 @Parameters 注解将字段设置为命令行参数,可以指定默认值、描述等信息。
  5. Picocli 会将命令行参数转换为强类型值,并自动注入到注解字段中。
  6. 在类的 runcall 方法中定义业务逻辑,当命令解析成功(用户敲了回车)后被调用。
  7. main 方法中,通过 CommandLine 对象的 execute 方法来处理用户输入的命令,剩下的就交给 Picocli 框架来解析命令并执行业务逻辑啦~
  8. CommandLine.execute 方法返回一个退出代码。可以调用 System.exit 并将该退出代码作为参数,从而向调用进程表示成功或失败。

3)让我们更改主程序的执行参数(args)来测试程序,能够成功看到输出结果,如下图:

通过这个入门 Demo,我们可以简单总结一个命令的开发流程:

  1. 创建命令
  2. 设置选项和参数
  3. 编写命令执行的业务逻辑
  4. 通过 CommandLine 对象接受输入并执行命令

在跑通了入门 Demo 后,我们来学习一些 Picocli 开发命令行的实用功能。

实用功能

1、帮助手册

通过给类添加的 @Command 注解参数 mixinStandardHelpOptions 设置为 true 来开启:

@Command(name = "ASCIIArt", mixinStandardHelpOptions = true

然后将主程序的输入参数设置为 --help 就能打印出命令的帮助手册信息了,如下图:

可以看到,Picocli 生成的帮助手册不仅规范、而且清晰完整。

2、命令解析

Picocli 最核心的能力就是命令解析,能够从一句完整的命令中解析选项和参数,并填充到对象的属性中。

Picocli 使用注解的方式实现命令解析,不需要自己编写代码,整个类看起来非常清晰。

最核心的 2 个注解其实在入门 Demo 中我们已经使用到了:

  • @Option 注解用于解析选项
  • @Parameters 注解用于解析参数

示例代码如下:

@Option(names = { "-s""--font-size" }, description = "Font size"
int fontSize = 19;

@Parameters(paramLabel = "<word>", defaultValue = "Hello, picocli"
           description = "Words to be translated into ASCII art.")
private String[] words = { "Hello,""picocli" }; 

可以给这些注解指定参数,比较常用的参数有:

1)@Option 注解的 names 参数:指定选项英文名称。

2)description 参数:指定描述信息,从而让生成的帮助手册和提示信息更清晰。

3)@Parameters 注解的 paramLabel 参数:参数标签,作用类似于描述信息。

4)@Parameters 注解的 defaultValue 参数:默认值,参考文档:https://picocli.info/#_default_values

5)required 参数:要求必填,参考文档:https://picocli.info/#_required_arguments

示例代码如下:

class RequiredOption {
    
    @Option(names = "-a", required = true)
    String author;
}

此外,命令解析天然支持 多值选项,只需要把对象属性的类型设置为数组类型即可,比如:

@Option(names = "-option")
int[] values;

具体可以参考官方文档:https://picocli.info/#_multiple_values

更多关于选项和参数注解的用法,也可以阅读官方文档学习:https://picocli.info/quick-guide.html#_options_and_parameters

3、交互式输入

所谓的交互式输入就是允许用户像跟程序聊天一样,在程序的指引下一个参数一个参数地输入。

如下图:

Picocli 为交互式输入提供了很好的支持,我梳理了大概 4 种交互式输入的模式。

1)基本能力

交互式输入的一个典型应用场景就是:用户要登录时,引导 ta 输入密码。

官方已经为我们提供了一段交互式输入的示例代码,鱼皮对它进行了简化,示例代码如下:

参考官方文档:https://picocli.info/#_interactive_password_options

package com.yupi.cli.example;

import picocli.CommandLine;
import picocli.CommandLine.Option;

import java.util.concurrent.Callable;

public class Login implements Callable<Integer{
    @Option(names = {"-u""--user"}, description = "User name")
    String user;

    @Option(names = {"-p""--password"}, description = "Passphrase", interactive = true)
    String password;

    public Integer call() throws Exception {
        System.out.println("password = " + password);
        return 0;
    }

    public static void main(String[] args) {
        new CommandLine(new Login()).execute("-u""user123""-p");
    }
}

让我们分析下上面的代码,主要包含 4 个部分:

1)首先命令类需要实现 Callable 接口

public class Login implements Callable<Integer{
 ...
}

2)将 @Option 注解的 interactive 参数设置为 true,表示该选项支持交互式输入

@Option(names = {"-p""--password"}, interactive = true)
String password;

3)在所有参数都输入完成后,会执行 call 方法,可以在该方法中编写具体的业务逻辑:

public Integer call() throws Exception {
    System.out.println("password = " + password);
    return 0;
}

4)在 Main 方法中执行命令并传入参数:

new CommandLine(new Login()).execute("-u""user123""-p");

执行上述代码,看到程序提示我们输入密码:

注意,如果以 jar 包方式运行上述程序,用户的输入默认是不会显示在控制台的(类似输入密码时的体验)。从 Picocli 4.6 版本开始,可以通过指定 @Option 注解的 echo 参数为 true 来显示用户的输入,并通过 prompt 参数指定引导用户输入的提示语。

2)多个选项交互式

Picocli 支持在一个命令中指定多个交互式输入的选项,会按照顺序提示用户并接收输入。

在上述代码中再增加一个 checkPassword 选项,同样开启交互式输入,代码如下:

public class Login implements Callable<Integer{
    @Option(names = {"-u""--user"}, description = "User name")
    String user;

    @Option(names = {"-p""--password"}, description = "Passphrase", interactive = true)
    String password;

    @Option(names = {"-cp""--checkPassword"}, description = "Check Password", interactive = true)
    String checkPassword;

    public Integer call() throws Exception {
        System.out.println("password = " + password);
        System.out.println("checkPassword = " + checkPassword);
        return 0;
    }

    public static void main(String[] args) {
        new CommandLine(new Login()).execute("-u""user123""-p");
    }
}

但运行上述代码我们会发现,怎么只提示我输入了密码,没提示我输入确认密码呢?

这是由于 Picocli 框架的规则,用户必须在命令中指定需要交互式输入的选项(比如 -p),才会引导用户输入。

所以我们需要修改上述代码中的 main 方法,给命令输入补充 -cp 参数:

public static void main(String[] args) {
    new CommandLine(new Login()).execute("-u""user123""-p""-cp");
}

再次执行,这下程序会依次提醒我们输入两个选项啦:

根据实际使用情况,又可以将交互式输入分为 2 种模式:

  • 可选交互式:用户可以直接在整行命令中输入选项,而不用给用户提示信息。
  • 强制交互式:用户必须获得提示并输入某个选项,不允许不填写。

下面分别讲解这两种模式。

3)可选交互式

默认情况下,是无法直接在命令中给交互式选项指定任何参数的,只能通过交互式输入,比如命令中包含 -p xxx 会报错。

可选交互式官方文档:https://picocli.info/#_optionally_interactive

让我们测试一下,给上面的示例代码输入以下参数:

new CommandLine(new Login()).execute("-u""user123""-p""xxx""-cp");

执行效果如下图,出现了参数不匹配的报错:

官方提供了可选交互式的解决方案,通过调整 @Option 注解中的 arity 属性来指定每个选项可接受的参数个数,就能解决这个问题。

arity 官方介绍:https://picocli.info/#_arity

示例代码如下:

@Option(names = {"-p""--password"}, arity = "0..1", description = "Passphrase", interactive = true)
String password;

然后可以直接在完整命令中给交互式选项设置值:

new CommandLine(new Login()).execute("-u""user123""-p""123""-cp");

执行结果如图,不再提示让用户输入 password 选项,而是直接读取了命令中的值:

这里鱼皮推荐一个最佳实践:建议给所有需要交互式输入的选项都增加 arity 参数(一般是 arity = "0..1"),这样用户既可以在完整命令中直接给选项填充参数,也可以选择交互式输入。

示例代码如下:

public class Login implements Callable<Integer{
    @Option(names = {"-u""--user"}, description = "User name")
    String user;

    // 设置了 arity 参数,可选交互式
    @Option(names = {"-p""--password"}, arity = "0..1", description = "Passphrase", interactive = true)
    String password;

    // 设置了 arity 参数,可选交互式
    @Option(names = {"-cp""--checkPassword"}, arity = "0..1", description = "Check Password", interactive = true)
    String checkPassword;

    public Integer call() throws Exception {
        System.out.println("password = " + password);
        System.out.println("checkPassword = " + checkPassword);
        return 0;
    }

    public static void main(String[] args) {
        new CommandLine(new Login()).execute("-u""user123""-p""123""-cp""456");
    }
}
4)强制交互式

在之前已经提到,如果用户不在命令中输入交互式选项(比如 -p),那么系统不会提示用户输入这个选项,属性的值将为默认值(比如 null)。

举个例子,下列命令中不带 -p 选项:

new CommandLine(new Login()).execute("-u""user123");

执行就会发现,程序不会提示用户输入 -p 选项的参数,而是直接输出结果,值为 null:

但有些时候,我们要求用户必须输入某个选项,而不能使用默认的空值,怎么办呢?

官方给出了强制交互式的解决方案,参考文档:https://picocli.info/#_forcing_interactive_input

但是,官方的解决方案是需要自己定义业务逻辑的。原理是在命令执行后对属性进行判断,如果用户没有输入指定的参数,那么再通过 System.console().readLine 等方式提示用户输入,示例代码如下:

@Command
public class Main implements Runnable {
    @Option(names = "--interactive", interactive = true)
    String value;

    public void run() {
        if (value == null && System.console() != null) {
            // 主动提示用户输入
            value = System.console().readLine("Enter value for --interactive: ");
        }
        System.out.println("You provided value '" + value + "'");
    }

    public static void main(String[] args) {
        new CommandLine(new Main()).execute(args);
    }
}

个人不是很喜欢这种方案,因为要额外编写提示代码,感觉又回到自主实现了。

鱼皮想出的一种方案是,编写一段通用的校验程序,如果用户的输入命令中没有包含交互式选项,那么就自动为输入命令补充该选项即可,这样就能强制触发交互式输入。

说通俗一点,检测 args 数组中是否存在对应选项,不存在则为数组增加选项元素。

该思路作为一个小扩展点,实现起来并不复杂,大家可以自行实现。(小提示:可以利用反射自动读取必填的选项名称)

4、子命令

子命令是指命令中又包含一组命令,相当于命令的分组嵌套,适用于功能较多、较为复杂的命令行程序,比如 git、docker 命令等。

官方文档:https://picocli.info/#_subcommands

在 Picocli 中,提供了两种设置子命令的方式。

1)声明式

通过 @Command 注解的 subcommands 属性来给命令添加子命令,优点是更直观清晰。

示例代码如下:

@Command(subcommands = {
    GitStatus.class,
    GitCommit.class,
    GitAdd.class,
    GitBranch.class,
    GitCheckout.class,
    GitClone.class,
    GitDiff.class,
    GitMerge.class,
    GitPush.class,
    GitRebase.class,
    GitTag.class
})
public class Git 
/* ... */ }
2)编程式

在创建 CommandLine 对象时,调用 addSubcommand 方法来绑定子命令,优点是更灵活。

示例代码如下:

CommandLine commandLine = new CommandLine(new Git())
        .addSubcommand("status",   new GitStatus())
        .addSubcommand("commit",   new GitCommit())
        .addSubcommand("add",      new GitAdd())
        .addSubcommand("branch",   new GitBranch())
        .addSubcommand("checkout"new GitCheckout())
        .addSubcommand("clone",    new GitClone())
        .addSubcommand("diff",     new GitDiff())
        .addSubcommand("merge",    new GitMerge())
        .addSubcommand("push",     new GitPush())
        .addSubcommand("rebase",   new GitRebase())
        .addSubcommand("tag",      new GitTag());
实践

让我们编写一个示例程序,支持增加、删除、查询 3 个子命令,并传入不同的 args 来测试效果。

完整代码如下:

package com.yupi.cli.example;

import picocli.CommandLine;
import picocli.CommandLine.Command;

@Command(name = "main", mixinStandardHelpOptions = true)
public class SubCommandExample implements Runnable {

    @Override
    public void run() {
        System.out.println("执行主命令");
    }

    @Command(name = "add", description = "增加", mixinStandardHelpOptions = true)
    static class AddCommand implements Runnable {
        public void run() {
            System.out.println("执行增加命令");
        }
    }

    @Command(name = "delete", description = "删除", mixinStandardHelpOptions = true)
    static class DeleteCommand implements Runnable {
        public void run() {
            System.out.println("执行删除命令");
        }
    }

    @Command(name = "query", description = "查询", mixinStandardHelpOptions = true)
    static class QueryCommand implements Runnable {
        public void run() {
            System.out.println("执行查询命令");
        }
    }

    public static void main(String[] args) {
        // 执行主命令
        String[] myArgs = new String[] { };
        // 查看主命令的帮助手册
//        String[] myArgs = new String[] { "--help" };
        // 执行增加命令
//        String[] myArgs = new String[] { "add" };
        // 执行增加命令的帮助手册
//        String[] myArgs = new String[] { "add", "--help" };
        // 执行不存在的命令,会报错
//        String[] myArgs = new String[] { "update" };
        int exitCode = new CommandLine(new SubCommandExample())
                .addSubcommand(new AddCommand())
                .addSubcommand(new DeleteCommand())
                .addSubcommand(new QueryCommand())
                .execute(myArgs);
        System.exit(exitCode);
    }
}

测试运行,发现当输入 --help 参数时,打印出了主命令和所有的子命令信息,证明子命令绑定成功:

实践

编程导航星球的定制化代码生成项目就是使用了 Picocli 来开发命令行应用。

👉🏻 编程导航原创项目教程系列:https://yuyuanweb.feishu.cn/wiki/SePYwTc9tipQiCktw7Uc7kujnCd


OK 就到这里,创作不易,学会的同学点个赞吧~

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

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

相关文章

[ PyQt入门教程 ] Qt Designer工具的使用

Qt Designer是PyQt程序UI界面的实现工具&#xff0c;使用Qt Designer可以拖拽、点击完成GUI界面设计&#xff0c;并且设计完成的.ui程序可以转换成.py文件供python程序调用。本文主要通过用户登录需求描述Qt Designer工具开发界面的使用方法。 主要内容 1、Qt Designer程序主界…

从濒临破产到玩具行业天花板,乐高怎么做到的?

成立于1932年的乐高一开始只是儿童玩具&#xff0c;到现在也俘获了许多大人的心&#xff0c;然而在2003年乐高遇到了巨大的经营危机&#xff0c;离破产只有一一步之遥&#xff0c;然而在当下&#xff0c;乐高已经成功跻身世界500强。今天媒介盒子就来和大家聊聊&#xff1a;从濒…

Server/PC 稳定性测试(Windows)

Server/PC 稳定性测试 1. CPU - 中央处理器1.1 CPU稳定性1.2 CPU温度 2. Memory - 内存2.1 内存性能 3. DISK - 硬盘3.1 HDD - 机械硬盘机械硬盘转速详解监测工具 3.2 SSD - 固态硬盘监测工具 4. 使用时长cpu能用多久&#xff1f;主板能用多久&#xff1f;内存能用多久&#xf…

第二篇:新建node项目并运行

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 安装 Node.js&#xff1a;首先&#xff0c;确保你的…

【Java】后端开发语言Java和C#,两者对比注解和属性的区别以及作用

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《Java》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握…

抖音SEO搜索排名优化培训教程课件

【干货资料持续更新&#xff0c;以防走丢】 抖音SEO搜索排名优化培训教程课件 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 抖音运营资料合集 &#xff08;完整资料包含以下内容&#xff09; 目录 抖音易爆单的商品特征 抖音作为一款短视频平台&#xff0c;…

【应用方案】基于MT7628 JN5169 和SUN724的4G-Zigbee智能网关方案

产品方案特性 - 4G-Zigbee智能网关扩展性强&#xff1b; - 百兆以太网接入&#xff1b; - 支持Zigbee 3.0智能家居产品和智能安防产品接入&#xff1b; - 实时检测烟感、气感&#xff0c;联动阀门控制器可自动关闭燃气阀门&#xff0c;并拨打报警电话&#xff0c;防止火灾&…

静态住宅代理与动态住宅代理的区别?如何选?

住宅代理ip分类两种类型&#xff1a;静态住宅代理和动态住宅代理&#xff0c;他们有什么区别又能用在什么场景呢&#xff1f;我们先从他们是如何运作开始。 一、什么是住宅代理ip isp住宅代理ip我们称为真人住宅代理&#xff0c;地址是从真人/家庭中出发&#xff0c;安全性更高…

游戏素材永不缺,免费在线AI工具Scenario功能齐全,简单易用

Scenario是一个在线的AI驱动的工具&#xff0c;主要用于游戏艺术创作。它提供了一套全面的功能&#xff0c;旨在帮助游戏开发者创建与其独特风格和艺术方向相符的独特、高质量的游戏艺术。Scenario的突出特点之一是它的微调能力&#xff0c;允许用户根据独特的风格和艺术方向训…

Qt单个字符判断

1.相关说明 字符的Unicode编码、单个字符的判断 2.界面绘制 3.相关主要代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui;…

【AIGC入门一】Transformers 模型结构详解及代码解析

Transformers 开启了NLP一个新时代&#xff0c;注意力模块目前各类大模型的重要结构。作为刚入门LLM的新手&#xff0c;怎么能不感受一下这个“变形金刚的魅力”呢&#xff1f; 目录 Transformers ——Attention is all You Need 背景介绍 模型结构 位置编码 代码实现&…

Agent检索增强生成

检索增强生成(RAG)设计模式通常用于在特定数据域中开发大语言模型(LLM)应用。然而&#xff0c;RAG的过往的研究重点主要在于提高检索工具的效率&#xff0c;例如嵌入搜索、混合搜索和微调嵌入&#xff0c;而忽视了智能搜索。本文介绍了一种受人类研究方法启发的新方法&#xff…

for循环判断有几个偶数

num100 count0 for i in range(1,num):if i%20:print("为偶数")count1 print(f"1-100的范围内&#xff0c;有{count}个偶数") 运行结果如下&#xff1a;

代码随想录Day21 | 530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236. 二叉树的最近公共祖先

代码随想录Day21 | 530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236. 二叉树的最近公共祖先 二叉搜索树的最小绝对差二叉搜索树中的众数二叉树的最近公共祖先 二叉搜索树的最小绝对差 文档讲解&#xff1a;代码随想录 视频讲解&#xff1a; 二叉搜索树中&#xff0c;需…

使用免费敏捷工具Leangoo领歌管理Sprint Backlog

什么是Sprint Backlog&#xff1f; Sprint Backlog是Scrum的主要工件之一。在Scrum中&#xff0c;团队按照迭代的方式工作&#xff0c;每个迭代称为一个Sprint。在Sprint开始之前&#xff0c;PO会准备好产品Backlog&#xff0c;准备好的产品Backlog应该是经过梳理、估算和优先…

C语言编译链接

1.翻译环境和运⾏环境 在ANSI C的任何⼀种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执⾏的机器指令。 第2种是执⾏环境&#xff0c;它⽤于实际执⾏代码。 2. 翻译环境 翻译环境是由编译和链接两个⼤的过程组成的&…

Spring环境搭配

概述 Spring 是一个开源框架&#xff0c;Spring 是于2003 年兴起的一个轻量级的Java 开发框架&#xff0c;由 RodJohnson 在其著作 Expert One-On-One J2EE Development and Design 中阐述的部分理念和原型衍生而来。它是 为了解决企业应用开发的复杂性而创建的。框架的主要优势…

mockjs使用1

mockjs使用 1、定义 Mock.js 是一款模拟数据生成器&#xff0c;旨在帮助前端攻城师独立于后端进行开发&#xff0c;帮助编写单元测试。提供了以下模拟功能&#xff1a; 根据数据模板生成模拟数据模拟 Ajax 请求&#xff0c;生成并返回模拟数据基于 HTML 模板生成模拟数据 2…

软件测试|SQLAlchemy query() 方法查询数据

简介 上一篇文章我们介绍了SQLAlchemy 的安装和基础使用&#xff0c;本文我们来详细介绍一下如何使用SQLAlchemy的query()方法来高效的查询我们的数据。 创建模型 我们可以先创建一个可供我们查询的模型&#xff0c;也可以复用上一篇文章中我们创建的模型&#xff0c;代码如…

游戏开发,中小公司跳槽去大厂容易还是考研应届生校招容易?

游戏开发&#xff0c;中小公司跳槽去大厂容易还是考研应届生校招容易&#xff1f; 在之前的文章中&#xff0c;我们提到过&#xff0c;游戏开发行业首选直接进入游戏大厂。《开发者必读&#xff1a;如何选择适合的游戏开发公司&#xff1f;》因为大厂不仅能提供良好的职业发展…