Android Hilt:强大的依赖注入框架,高级传参解个耦?

news2024/10/5 19:12:19

作者:J船长

一、Hilt 干依赖注入的

Hilt是干嘛的

Hilt,一个依赖注入框架,谷歌搞出来的,基于Dagger,但是更加简洁易用。

什么是依赖注入 (Dependency Injection)

依赖注入是一种设计模式。主要就是解耦!解耦!解耦!。

依赖注入是一种设计模式,它通过在对象之间传递依赖关系,使得对象能够更灵活、可测试和可维护。在Android开发中,依赖注入可以帮助我们管理和解耦应用程序中的各种组件,如Activity、Fragment、ViewModel、Repository等。

既生Dagger 何生 Hilt ?

安卓中的Hilt是一种用于依赖注入(Dependency Injection)的库。Hilt是由Google开发的,它是基于Dagger的一个扩展库,旨在简化Android应用程序中的依赖注入过程。

Hilt提供了一套注解和生成代码,用于在Android应用程序中自动完成依赖注入的配置。它简化了Dagger的使用,减少了样板代码的编写,提高了开发效率。
Hilt还提供了一些特定于Android的功能,如对Android组件的注入支持,以及在不同生命周期中管理依赖关系的能力。

Hilt 大概怎么用

  • Application 注解下
  • 在需要的标记为依赖注入的Activity、Fragment注解下 @AndroidEntryPoint

使用Hilt进行依赖注入时,首先需要在应用程序的Application类上添加@HiltAndroidApp注解,以告知Hilt该应用程序将使用Hilt进行依赖注入。然后,可以在其他组件(如Activity、Fragment)中使用@AndroidEntryPoint注解来标记需要进行依赖注入的地方。Hilt会根据注解生成必要的代码,在运行时完成依赖注入。

Hilt还提供了一些其他的注解,用于标记和配置依赖关系。例如,

  • 可以使用@Inject注解来标记需要注入的依赖项,
  • 使用@Module注解来标记用于提供依赖项的模块类,
  • 使用@Provides注解来标记提供依赖项的方法等。

二、什么是TM的依赖注入,给个例子

比如我们创建一辆汽车,汽车需要引擎。

  • 如果不用依赖注入,创建汽车,汽车内部还要创建引擎,这样就 耦合 耦合 偶尔
  • 如果使用依赖注入,创建汽车不需要创建引擎,而是从外部将Engine对象传递给Car类,这样就 解耦 解耦 解耦

不使用依赖注入,汽车内部创建引擎,耦合耦合耦合,

class Engine {
    void start() {
        System.out.println("Engine started");
    }
}

class Car {
    private Engine engine;

    Car() {
        engine = new Engine();  // Car类自己创建Engine对象
    }

    void drive() {
        engine.start();
        System.out.println("Car is driving");
    }
}

使用依赖注入,传递引擎对象给汽车,汽车内部不创建引擎对象,解耦解耦解耦

class Car {
    private Engine engine;

    Car(Engine engine) {  // Engine对象通过构造函数注入
        this.engine = engine;
    }

    void drive() {
        engine.start();
        System.out.println("Car is driving");
    }
}

现在,我们可以在创建Car对象时,把一个Engine对象传递给它:

Engine engine = new Engine();
Car car = new Car(engine);
car.drive();

上面的对比。虽然简单,但是是个挺好的例子
"Engine对象通过构造函数注入"是指我们通过Car类的构造函数将Engine对象传递给Car类。这是一种依赖注入的方式,被称为构造函数注入。

在这个例子中,Car类需要一个Engine对象来工作。在没有使用依赖注入的情况下,Car类会自己创建一个Engine对象。但这样做的问题是,Car类与Engine类紧密耦合在一起,如果我们想要更换一个不同类型的Engine,或者我们想要在测试时使用一个模拟的Engine,我们就需要修改Car类的代码。

当我们使用构造函数注入时,我们不再在Car类内部创建Engine对象,而是在创建Car对象时,从外部将Engine对象传递给Car类。这样,Car类就不再依赖于Engine类的具体实现,我们可以很容易地更换不同类型的Engine,或者在测试时使用一个模拟的Engine,而不需要修改Car类的代码。

也许你会说,不就是传个参吗,说得这么麻烦????
某种程度上,你可以这么说,当时,这个玩意,能放大了玩。

依赖注入,可不仅仅是高级的传参 (李姐会理解)

人家,可传参,牛逼多了。

  • Activity和Fragment:在Android开发中,Activity和Fragment经常需要访问一些共享的资源或服务,例如网络请求Retrofit、数据库访问、ViewModel等。如果没有依赖注入,我们可能需要在每个Activity或Fragment中手动创建这些对象,这会导致代码重复,而且使得Activity和Fragment与这些服务紧密耦合,难以进行单元测试。通过使用依赖注入,我们可以在一个统一的地方配置这些服务,然后在需要的地方自动注入,这使得代码更加清晰,更容易测试。

  • 主App和Module:在一个模块化的应用中,不同的模块可能需要访问一些共享的服务。如果没有依赖注入,我们可能需要通过一些复杂的方式来让这些模块获取到这些服务,这会使得代码变得复杂,而且难以管理。通过使用依赖注入,我们可以在主App中配置这些服务,然后在各个模块中自动注入,这使得代码更加清晰,更容易管理。

  • 管理单例依赖项:在 Android 应用程序中,有一些依赖项是单例的,如数据库、网络客户端等。使用 Hilt 可以更轻松地管理这些单例依赖项,同时避免了手动管理单例依赖项的复杂性。

这个说是不是不会觉得是简答传参了!

依赖注入使得我们的代码更加模块化,每个类都只关注自己的职责,不需要关心其依赖对象的创建和管理。这使得我们的代码更容易重用。

三、Hilt的一些常见注解

在开始app例子之前,还需要需要将一些注解前面说明一下。
Hilt使用了一系列的注解来简化依赖注入的过程。以下是一些最常用的Hilt注解:

@HiltAndroidApp

  • @HiltAndroidApp: 这个注解用于Application类,它会触发Hilt的代码生成,包括一个应用级别的组件,这个组件可以为其他Hilt组件(如Activity组件)提供依赖。
@HiltAndroidApp
class MyApplication : Application()

@AndroidEntryPoint

  • @AndroidEntryPoint: 这个注解用于Android组件,如Activity、Fragment、Service等,它告诉Hilt这些组件可以接收依赖注入。
@AndroidEntryPoint class MainActivity : AppCompatActivity() { @Inject lateinit var someClass: SomeClass }

@Inject

  • @Inject: 这个注解用于字段、构造函数或方法,告诉Hilt需要注入依赖。对于字段,Hilt会自动注入相应的依赖;对于构造函数,Hilt会使用它来创建类的实例;对于方法,Hilt会在注入后调用它。
class SomeClass @Inject constructor(private val someDependency: SomeDependency)

@Module

  • @Module: 这个注解用于对象,这些对象提供了一系列的依赖提供方法。这些方法用@Provides注解标记,Hilt会在需要时调用它们。

@Provides

  • @Provides: 这个注解用于在@Module注解的类中的方法,这些方法提供了依赖的实例。Hilt会在需要时调用这些方法。
@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Singleton
    @Provides
    fun provideSomeDependency(): SomeDependency {
        return SomeDependency()
    }
}

@InstallIn

  • @InstallIn: 这个注解用于@Module注解的类,指定这个模块安装在哪个Hilt组件中。

@Singleton

  • @Singleton: 这个注解用于@Provides注解的方法或@Inject注解的构造函数,告诉Hilt提供的依赖是单例的。
@Singleton
class SomeSingletonClass @Inject constructor()

@ViewModelInject

  • @ViewModelInject: 这个注解用于ViewModel的构造函数,告诉Hilt如何创建ViewModel的实例。
class MyViewModel @ViewModelInject constructor(private val repository: Repository) : ViewModel()

@Assisted

  • @Assisted:用于标记 ViewModel 的构造函数参数,以便在使用 assisted injection 时注入这些参数。

@AssistedInject

  • @AssistedInject:用于标记使用 assisted injection 创建的 ViewModel 的构造函数。

这些注解使得Hilt能够自动处理依赖的创建和注入,大大简化了依赖注入的过程。

  • @Module@Provides:这两个注解通常一起使用,定义在一个类中,这个类提供了一系列的依赖提供方法。这些方法用@Provides注解标记,Hilt会在需要时调用它们。

四、上个App

1、hilt准备

app目录下 build.gradle

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    // 添加kapt注解处理器
    id 'kotlin-kapt'
    // hilt所需
    id 'com.google.dagger.hilt.android'
}

dependencies {
    implementation 'com.google.dagger:hilt-android:2.44'
    kapt 'com.google.dagger:hilt-android-compiler:2.44'
}

项目级别build

plugins {
    id 'com.android.application' version '8.0.0' apply false
    id 'com.android.library' version '8.0.0' apply false
    id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
    // hilt
    id "com.google.dagger.hilt.android"  version "2.44" apply false
}

这里需要说明一下,比较新的版本是如上这么写。

如果是比较旧的android studio,则是classpath的写法。比如:

buildscript {
    dependencies {

        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.44'
    }
}

以上,配置完成。

2、汽车和引擎的例子

// 使用 @HiltAndroidApp 注解标记一个应用,这是使用 Hilt 进行依赖注入的入口点。
// Hilt 需要一个入口点来提供依赖,这通常是 AndroidApplication 类。
// 这个注解会触发 Hilt 的代码生成,生成的代码包括一个应用级别的组件,这个组件可以为其他 Hilt 组件(如 Activity 组件)提供依赖。
@HiltAndroidApp
class MyApplication : Application() {
    // 这个类通常不需要写任何代码,除非你需要在应用启动时执行一些操作。
}

android:name=".MyApplication"

Engine

// 使用 @Inject 注解标记构造函数,告诉 Hilt 如何创建这个类的实例。
// 当 Hilt 需要提供一个 Engine 实例时,它会调用这个构造函数。
// 在这个例子中,Engine 类没有任何依赖,所以构造函数没有任何参数。
class Engine @Inject constructor() {
    fun start() {
        println("Engine started")
    }
}

Car

// 使用 @Inject 注解标记构造函数,告诉 Hilt 如何创建这个类的实例。
// 当 Hilt 需要提供一个 Car 实例时,它会调用这个构造函数。
// 在这个例子中,Car 类依赖于 Engine 类,所以构造函数有一个 Engine 类型的参数。
// Hilt 会查找提供 Engine 实例的方法(在这个例子中,就是 Engine 类的构造函数),然后用提供的 Engine 实例来创建 Car 实例。
class Car @Inject constructor(private val engine: Engine) {
    fun drive() {
        engine.start()
        println("Car is driving")
    }
}

Activity

/**
 *
 *  使用 @AndroidEntryPoint 注解标记一个 Android 组件,告诉 Hilt 这个组件可以接收依赖注入。
 *  Hilt 支持多种 Android 组件,包括 Activity、Fragment、View、Service、BroadcastReceiver 等。
 *  当一个组件被标记为 @AndroidEntryPoint,Hilt 会为这个组件生成一个组件类(在这个例子中,是 MainActivityComponent)。
 *  这个组件类是用来提供依赖的,它是由 Hilt 自动生成的,你无需手动创建。
 */



@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var car: Car

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Use the car
        car.drive()
    }
}

运行结果:

System.out: Engine started
System.out: Car is driving

嗯。如果只是这么用,那么确实平平无奇,仿佛是一个高级传参。
但是,如果只是这样,那我们还H个什么H。

但是,这是一个挺好的小例子。

五、实际开发中一些常用的场景

SharedPreferences的注入:

// 创建一个Hilt模块,用于提供SharedPreferences实例
@Module
@InstallIn(SingletonComponent::class)
object SharedPreferencesModule {

    @Provides
    fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
        return context.getSharedPreferences("pref_name", Context.MODE_PRIVATE)
    }
}

// 在Activity中,你可以使用@Inject注解来请求一个SharedPreferences实例。
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var sharedPreferences: SharedPreferences

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 使用SharedPreferences
        val editor = sharedPreferences.edit()
        editor.putString("key", "value")
        editor.apply()
    }
}

上述代码中,@AndroidEntryPoint是一个Hilt注解,用于指示Hilt应该为这个Activity提供依赖。然后,通过在sharedPreferences字段上添加@Inject注解,Hilt就会知道它需要为这个字段提供一个SharedPreferences实例。这个实例是由SharedPreferencesModule模块中的provideSharedPreferences方法提供的。

注意,你可以随时使用sharedPreferences字段,Hilt会确保在onCreate方法调用时,它已经被正确初始化。

你也可以将这种模式应用于其他的例子,例如网络服务、视图模型、数据库和数据仓库等。

多模块项目
在多模块项目中,Hilt可以帮助你更好地管理跨模块的依赖。例如,假设你有一个data模块和一个app模块,data模块提供了一个Repository类,app模块需要使用这个Repository。

首先,在data模块中,你定义了一个Repository类,并用@Inject注解标记其构造函数:

// 在 data 模块中
class Repository @Inject constructor() {
    // ...
}

然后,在app模块中,你可以直接在需要的地方注入Repository:

// 在 app 模块中
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var repository: Repository

    // ...
}

在这个例子中,你不需要在app模块中手动创建Repository的实例,Hilt会自动为你处理。

再来一个依赖管理的例子

以为简化的Retrofit为例子
假设我们有一个NetworkModule,它提供了一个Retrofit实例:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Singleton
    @Provides
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .build()
    }
}

在这个例子中,NetworkModule是一个Hilt模块,它在应用级别的组件(SingletonComponent)中提供了一个Retrofit实例。这个Retrofit实例是一个单例,因为我们使用了@Singleton注解。

然后,我们有一个Repository类,它需要这个Retrofit实例来发起网络请求:

class Repository @Inject constructor(private val retrofit: Retrofit) {
    // ...
}

在这个例子中,Repository类通过构造函数注入获取了Retrofit实例。我们不需要手动创建Retrofit实例,Hilt会自动为我们处理。
最后,我们有一个ViewModel,它需要这个Repository来获取数据:

class MyViewModel @ViewModelInject constructor(private val repository: Repository) : ViewModel() {
    // ...
}

在这个例子中,MyViewModel通过@ViewModelInject注解获取了Repository实例。我们不需要手动创建Repository实例,Hilt会自动为我们处理。

这就是一个典型的依赖链:MyViewModel依赖于Repository,Repository依赖于Retrofit。通过Hilt,我们可以轻松地管理这个依赖链,而无需手动创建和管理每个依赖。这使得代码更加清晰和直观,也使得新成员更容易理解项目的结构。

ViewModel注入

// Hilt提供了一个HiltViewModel注解,它允许你在ViewModel的构造函数中使用@Inject。
@HiltViewModel
class MyViewModel @Inject constructor(private val userRepository: UserRepository) : ViewModel() {
    // ...
}

// 在Activity或Fragment中,你可以使用由Hilt提供的ViewModel实例。
@AndroidEntryPoint
class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Use viewModel here
    }
}

数据库的注入

// 创建一个Hilt模块,用于提供Room数据库实例
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    @Provides
    fun provideDatabase(@ApplicationContext context: Context): MyDatabase {
        return Room.databaseBuilder(
            context,
            MyDatabase::class.java, "database-name"
        ).build()
    }

    @Provides
    fun provideUserDao(database: MyDatabase): UserDao {
        return database.userDao()
    }
}

// 在需要UserDao的地方,使用@Inject注解来请求一个UserDao实例。
class UserRepository @Inject constructor(private val userDao: UserDao) {
    // ...
}

不是每一个Activity都需要依赖注入,如果这个activity只在当前页面使用,那么没必要依赖注入。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

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

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

相关文章

高校劳动积分小程序/基于微信小程序的高校劳动积分系统

摘 要 随着信息技术互联网和小程序的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的微信小程序应运而生,各行各业相继进入信息管理…

系统架构设计师-系统工程与信息系统基础(1)

一、系统工程概念 【系统工程】是一种组织管理技术。 【系统工程】是为了更好的实现系统的目的,对系统的组成要素、组成结构、信息流、控制机构进行分析研究的科学方法。 【系统工程】从整体出发、从系统观念出发、以求【整体最优】 【系统工程】利用计算机作为…

开放式耳机会不会吵到别人?2023年开放式耳机科普!

在了解开放式耳机会不会吵到别人之前,我们先了解下开放式耳机的基本知识! 开放式耳机是一种不入耳,没有封闭耳朵的蓝牙耳机,可以听歌的同时接收来自外界声音,安全性高,也减少长期佩戴耳机带来的负担&#…

2023火爆的11门编程语言

2023火爆的11门编程语言 对于我个人来说没有语言偏好,根据不同的应用领域和需求,不同的编程语言都有其独特的优势和适用性。无论使用何种语言只要能更好的实现需求,解决痛点问题,就是好语言。 那么各种语言应用的场景解决了哪些…

项目管理专业人员能力评价CSPM与项目管理PMP对应关系

2021年10月,中共中央、国务院发布的《国家标准化发展纲要》明确提出构建多层次从业人员培养培训体系,开展专业人才培养培训和国家质量基础设施综合教育。建立健全人才的职业能力评价和激励机制。由中国标准化协会(CAS)组织开展的项…

Solon 成为信通院可信开源社区、可信开源项目

自2021年9月17日成立以来,可信开源社区共同体共有五批新成员加入。在4月21日“OSCAR开源生态建设论坛”上,可信开源社区共同体又迎来2位正式成员和6位预备成员,Solon 为其一(图之右下角): 图片引用自CAICT可…

开放式耳机和封闭式耳机的区别?开放式耳机到底有哪些优缺点?

开放式耳机从字面意思可以理解为:开放耳朵,不需要入耳就可以听见声音的耳机,所以它和封闭式耳机的最大区别就是不入耳。这种耳机最大的优点就是不压迫不封闭耳道,而且在听耳机音的同时能够及时注意到周围环境的声音,从…

轻量级日志系统Loki——安装配置

关注“云计算就该这么学”微信公众号,搜索“001”,即可获取氪肝整理的154页Linux学习笔记。 Loki对标EFK/ELK,由于其轻量的设计,备受欢迎,Loki相比EFK/ELK,它不对原始日志进行索引,只对日志的标…

《主责数据保护与流动安全监管框架》重磅发布,美创以“产业研究力量”深入参与

历时四年,聚合行业安全专家智慧,凝炼行业安全最佳实践,数字时代:基于行业最佳实践的《主责数据保护与流动安全监管框架》(以下简称“框架”)于2023年6月17日第三届数字安全大会上正式发布。 该框架是在中国…

基于SpringBoot+Html的前后端分离的学习平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背景介绍: 在知识大爆炸的现代,怎…

一.Elasticsearch快速入门及使用

Elasticsearch快速入门及使用 一.Elasticsearch是什么二.基本概念1.index (索引)2. type (类型)3.Document (文档) 三.为什么Elasticsearch可以从海量数据里快速检索出数据四.Elasticsearch安装1.解压2.运行3.显示以下内容就是启动成功14.Kibana可视化软件安装 五.入门(基本的操…

代码浅析Point-LIO

0. 简介 对于最近出来的Point-LIO(鲁棒高带宽激光惯性里程计),本人还是非常该兴趣的,为此花了一些时间重点分析了Point-LIO的代码,并研究了它相较于Fast-LIO2的区别 1. laserMapping.cpp 第一部分就是实现对激光雷达视场角的图像分割。首先…

Day01 项目简介分布式基础概念

最近在改进公司开发的商城项目,看到了尚硅谷的谷粒商城,就快速学习了下,因为之前的Kafka,Redis都是在这学习的,还有大数据的Flink。所以感觉一定不错,就开始了。 这里做一下学习笔记 一、项目简介 1 、项目背景 1 &…

UIAutomatorViewer工具的使用

目录 前言: 一、uiautomatorviewer介绍 二、启动uiautomatorviewer 三、uiautomatorviewer界面 四、连接手机 前言: UIAutomatorViewer是Android SDK提供的一个可视化界面分析工具,可以用于查看Android应用的UI界面结构、属性信息以及布…

元宇宙在技术大爆炸时代迎来链游新世界

元宇宙是从虚拟游戏、虚拟艺术收藏品开始兴起,然后逐步扩展到社交和金融领域的。元宇宙的终极形态就是一种“无限游戏”,也即打破边界、颠覆规则、不断迭代和进化发展的新世界。 政策落地,元宇宙未来才能充满潜力 2021 年以来,元…

SSM在线学习平台-计算机毕设 附源码85204

SSM在线学习平台 摘 要 随着科学技术的飞速发展,社会的方方面面、各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,在线学习平台当然也不能排除在外。在线学习平台是以实际运用为开发背景,运用软件工程原理和…

数字非洲,沐光而行

“华为是什么公司?我们不相信中国企业能有先进的通信技术!你们也不要总是来找我!”1998年,华为人刚踏上非洲所遇到的,不是来自阳光大陆的热情,而是来自刚果(金)客户冷冰冰的拒绝。 2…

国产崛起,继操作系统装机500w,又有软件平台用户突破600w

国产操作系统行业部署超500W 这次,国产操作系统终于扬眉吐气了! 国产的操作系统装机已达到500万,国产崛起的时刻已来! 近日,国产操作系统的佼佼者——统信UOS,召开见面会,宣布:目…

7DGroup性能实施项目日记2

壬寅年 己酉月 己卯日 秋高气爽 天气转凉 昨天大风昨天看到项目计划中已经有了很多内容。 也就是说计划感觉已经写得差不多了。我仔细看了一下之后觉得这个计划不可行,就是样子挺像。 然后我问管理组,这个是你们商量之后写的吗?他们说不…

Java互联网工程师1100题(总结最全面的面试题)

本来已经在为去大厂工作摩拳擦掌的 Java 朋友,社招又是需要 5 年以上的,今年显得格外艰难: 就业人数高达 874 万!人才竞争加剧! 大多数公司的招聘需求缩减!对社招来说,人才招聘要求愈来愈高&am…