Skip to content

Latest commit

 

History

History
574 lines (433 loc) · 30.7 KB

java_from_unknown_to_unable.md

File metadata and controls

574 lines (433 loc) · 30.7 KB
title description published date tags editor dateCreated
Java从不会到学不会
true
2023-03-25 10:32:07 UTC
markdown
2023-03-25 10:32:04 UTC

Java进阶

Unirest代替OkHttp

一段示例代码:

HttpResponse<JsonNode> response =Unirest.post("http://httpbin.org/post")
      .header("accept", "application/json")
      .queryString("apiKey", "123")
      .field("parameter", "value")
      .field("foo", "bar")
      .asJson();

可以看到,我们通过一些链式调用就可以完成添加请求头、添加请求参数、添加请求体等操作,这样的代码非常简洁,而且不需要复杂的回调。

Unirest与okhttp相比,使用起来要简单许多,支持链式调用,不需要复杂的回调。例如比较二者发出一个常见的请求:

// Unirest
HttpResponse<JsonNode> response =Unirest.post("http://httpbin.org/post")
      .header("accept", "application/json")
      .queryString("apiKey", "123")
      .field("parameter", "value")
      .field("foo", "bar")
      .asJson();

// OkHttp
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
        .url("https://jsonplaceholder.typicode.com/todos/1")
        .build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        e.printStackTrace();
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        String body = response.body().string();
        System.out.println(body);
    }
});

在大家的项目中,完全可以使用Unirest来替代OkHttp,这样可以大大简化代码。事实上,许多开源项目也是这么做的,比如Retrofit、Volley等。

反射

按照各种资料,Java反射(Reflection)是指在程序运行时动态地获取类的信息并操作类的成员变量、方法、构造方法等的能力。反射机制使得程序可以通过名称来访问和操作其它程序或者本身运行时的状态,可以实现动态创建对象、动态调用方法、动态修改属性等操作。

Java反射主要涉及以下三个核心类:

  • Class类:表示类的实体,在运行的Java应用程序中表示类和接口。可以通过Class类获取一个类的各种信息,如类名、属性、方法等。
  • Field类:表示类的属性(成员变量)。
  • Method类:表示类的方法。

通过Java反射机制,我们可以在运行时动态地加载、创建、操作、销毁类,并且可以实现很多在编译时无法实现的功能,比如在运行时动态调用方法、动态创建对象等。反射机制在一些框架中得到了广泛应用,如Spring框架、Hibernate框架等。

实际上,这种描述太复杂了,我们可以先举个例子来理解反射:

public class Test {
    public static void main(String[] args) {
        // 1. 通过类名获取Class对象
        Class clazz = Test.class;
        // 2. 通过对象获取Class对象
        Test test = new Test();
        Class clazz2 = test.getClass();
        // 3. 通过Class类的静态方法获取Class对象
        try {
            Class clazz3 = Class.forName("com.example.Test");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

上面的代码中,我们通过三种方式获取了Test类的Class对象,这三种方式都是等价的,都是通过类的全限定名来获取Class对象。这其实就是反射的基本用法,通过Class对象我们可以获取类的各种信息,比如类名、属性、方法等。

public class Test {
    public static void main(String[] args) {
        Class clazz = Test.class;
        // 获取类名
        String className = clazz.getName();
        System.out.println(className);
        // 获取属性
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.getName());
        }
        // 获取方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }
    }
}

上面的代码中,我们通过Class对象获取了类的各种信息,包括类名、属性、方法等。这些信息都是在运行时动态获取的,这就是反射的作用。

啥时候用反射?

反射的作用是在运行时动态获取类的信息,这在一些框架中得到了广泛应用,比如Spring框架、Hibernate框架等。在这些框架中,我们可以通过配置文件来配置类的信息,然后通过反射机制来动态创建对象、调用方法等。

咋用反射?

其实很多时候反射是不得不用的,比如在一些框架中,我们需要通过配置文件来配置类的信息,然后通过反射机制来动态创建对象、调用方法等。但是,反射的使用也是有一定的限制的,比如在Android中,我们不能通过反射来创建Activity、Service等组件,因为这些组件都是在AndroidManifest.xml中注册的,而Android系统在运行时会根据这个配置文件来创建这些组件,所以我们不能通过反射来创建这些组件。

反射在开发中可能遇到的场景包括:

  1. 动态加载类和创建对象:使用反射可以在运行时动态地加载类并创建对象,这样就可以实现在编译时无法确定类型的对象的创建。
  2. 动态调用方法和访问属性:通过反射可以动态地调用对象的方法和访问对象的属性,这样就可以实现一些在编译时无法确定的方法调用和属性访问。
  3. 框架中的应用:在一些框架中,反射机制可以用于实现各种功能,如依赖注入、AOP等。
  4. 注解的解析:通过反射可以获取注解信息,实现自定义注解的解析,以及在运行时根据注解信息执行相应的操作。
  5. 动态代理:使用反射可以动态地生成代理对象,并在运行时代理目标对象的方法调用,实现一些动态代理的功能。

举个实际的例子

动态获取请求到的JSON数据并修改原对象是反射机制中可能出现的使用场景之一,通过反射可以根据JSON数据中的属性名称动态地获取对象的属性,并修改对象的属性值,从而实现对原对象的修改。

import com.fasterxml.jackson.databind.ObjectMapper;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public static void main(String[] args) throws Exception {
        // 假设从请求中获取到的JSON数据为 {"name": "Tom", "age": 18}
        String json = "{\"name\": \"Tom\", \"age\": 18}";

        // 将JSON数据转换为Map对象
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> jsonMap = objectMapper.readValue(json, HashMap.class);

        // 创建Person对象
        Person person = new Person("Jerry", 20);

        // 遍历JSON数据中的属性
        for (String key : jsonMap.keySet()) {
            // 使用反射获取对象的属性
            Field field = person.getClass().getDeclaredField(key);
            // 设置属性的访问权限为可修改
            field.setAccessible(true);
            // 修改属性的值
            field.set(person, jsonMap.get(key));
        }

        // 输出Person对象的信息
        System.out.println("Name: " + person.getName());
        System.out.println("Age: " + person.getAge());
    }
}

在上面的示例代码中,首先使用Jackson库将JSON数据转换为Map对象,然后创建Person对象,接着遍历JSON数据中的属性,使用反射机制获取Person对象的属性,并设置属性的访问权限为可修改,最后通过反射机制修改属性的值。这样就实现了根据JSON数据动态地修改原对象的属性值的功能。

自定义注解与反射

在实现一些自定义注解的解析时,我们可以使用反射机制来获取注解信息,从而实现自定义注解的解析,以及在运行时根据注解信息执行相应的操作。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String value();
}

@Table("person")
public class Person {
    @Column("name")
    private String name;
    @Column("age")
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取Person类的Class对象
        Class<?> clazz = Class.forName("com.example.Person");
        // 获取Table注解
        Table table = clazz.getAnnotation(Table.class);
        // 获取注解的值
        String tableName = table.value();
        System.out.println("表名:" + tableName);

        // 获取Person类的所有属性
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 获取属性的Column注解
            Column column = field.getAnnotation(Column.class);
            // 获取注解的值
            String columnName = column.value();
            System.out.println("列名:" + columnName);
        }
    }
}

在上面的示例代码中,首先使用Class.forName()方法获取Person类的Class对象,然后使用Class对象的getAnnotation()方法获取Table注解,最后使用Table注解的value()方法获取注解的值。接着遍历Person类的所有属性,使用Field对象的getAnnotation()方法获取Column注解,最后使用Column注解的value()方法获取注解的值。

可以看到,反射机制很多时候会在编写偏向框架的代码时使用,比如Spring框架中的依赖注入、AOP等功能,都是通过反射机制实现的。你可以尝试使用反射实现一些注解,并在运行时获取注解和对象的信息已实现一些操作;如果只是编写业务功能,那么反射机制的使用场景可能会比较少

Java多线程

什么是多线程

多线程是指在一个进程中同时运行多个线程,每个线程都可以执行不同的任务,多个线程之间可以共享进程中的资源,比如内存、文件等。

几乎所有的语言都会有多线程或者类似的机制,比如C语言中的线程、Python中的线程、JavaScript中的Web Worker等。Java中的多线程是通过Thread类来实现的,每个线程都是Thread类的一个实例。很多情况下我们都需要使用多线程来实现一些功能,比如在Android中,如果在主线程中执行耗时操作,那么应用就会出现卡顿的现象,这时候就需要使用多线程来实现一些功能。

多线程示意图

为什么要使用多线程

通常,一个操作可以区分为IO密集型CPU密集型两种类型,IO密集型操作指的是需要大量的IO操作,比如读写文件、网络请求等,而CPU密集型操作指的是需要大量的CPU计算,比如数学运算、字符串处理等。

在IO密集型操作中,由于IO操作的速度远远低于CPU的运算速度,所以如果在主线程中执行IO密集型操作,那么主线程就会一直等待IO操作完成,这样就会导致主线程无法执行其他任务,从而导致应用出现卡顿的现象。而在CPU密集型操作中,由于CPU的运算速度远远高于IO操作的速度,所以如果在主线程中执行CPU密集型操作,那么主线程就会一直占用CPU,这样就会导致主线程无法执行其他任务,从而导致应用出现卡顿的现象。在更糟糕的情况下,如果主线程中执行的是死循环,那么主线程就会一直占用CPU,这样就会导致主线程无法执行其他任务,从而导致应用出现ANR的现象。

以网络请求为例,一个请求动辄需要几十毫秒的时间,而CPU的运算速度是几十G的速度,所以如果在主线程中执行网络请求,那么主线程就会一直等待网络请求完成,这样就会导致主线程无法执行其他任务,表现为这几十毫秒内应用无法响应用户的操作,从而导致应用出现卡顿的现象。而如果在子线程中执行网络请求,那么主线程就可以继续执行其他任务,这样就不会导致应用出现卡顿的现象。

当然,大家一定会发现一个问题,当我们在子线程中执行网络请求时,其他线程是如何知道网络请求已经完成的呢?这就需要使用回调函数来实现了,我们可以在子线程中执行网络请求,然后在网络请求完成后调用回调函数,这样就可以在主线程中更新UI了。

以上便是多线程的基本概念和引出的问题,接下来我们将介绍如何使用多线程。

JavaFX中的多线程

Java课程设计中使用的JavaFX框架是基于JavaFX 8的,JavaFX 8中的多线程是通过Task类来实现的。对于JavaFX来讲,有一个线程是专门用来更新UI的,这个线程就是JavaFX Application Thread,也就是主线程。在JavaFX中,如果在主线程中执行耗时操作,那么应用就会出现卡顿的现象,这时候就需要使用多线程来实现一些功能。而且,如果你要更新UI,那么就必须在主线程中执行,否则就会出现异常。所以,通常使用多线程的策略大致就是通过回调函数来实现,即在子线程中执行耗时操作,然后在耗时操作完成后调用回调函数,这样就可以在主线程中更新UI了。

让我们有请ChatGPT来为我们介绍一下JavaFX中的多线程。

import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class HttpRequestDemo extends Application {
    private static final String URL = "https://jsonplaceholder.typicode.com/todos/1";
    private Label statusLabel;

    @Override
    public void start(Stage primaryStage) {
        statusLabel = new Label("Click the button to make a network request.");
        Button btn = new Button();
        btn.setText("Make Request");
        btn.setOnAction(event -> {
            // Create a new instance of the HTTPRequestService and start it
            HTTPRequestService service = new HTTPRequestService();
            service.start();
        });

        VBox root = new VBox();
        root.getChildren().addAll(statusLabel, btn);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("HTTP Request Demo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private class HTTPRequestService extends Service<String> {

        @Override
        protected Task<String> createTask() {
            return new Task<>() {
                @Override
                protected String call() throws Exception {
                    // Perform the network request using Unirest
                    HttpResponse<String> response = Unirest.get(URL).asString();
                    return response.getBody();
                }

                @Override
                protected void succeeded() {
                    // Update the UI with the response body
                    String responseBody = getValue();
                    statusLabel.setText(responseBody);
                }

                @Override
                protected void failed() {
                    // Handle any errors
                    Throwable exception = getException();
                    statusLabel.setText("Error: " + exception.getMessage());
                }
            };
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在这个示例中,我们创建了一个JavaFX应用程序,它包含一个按钮和一个标签。当用户点击按钮时,应用程序将使用Unirest库执行一个HTTP GET请求,并在任务完成后将响应正文更新到标签中。我们使用了JavaFX的ServiceTask类来管理后台任务,并使用Unirest库执行网络请求。注意,网络请求代码是在Task对象的call()方法中执行的,这确保了请求是在后台线程中执行的,而不是在UI线程中执行的,从而避免了UI线程的阻塞。当任务完成时,我们使用Task对象的succeeded()方法更新UI,如果任务失败,则使用failed()方法处理任何错误。

使用场景

在大家学习完多线程的基本概念和使用方法后,我们来看一下多线程在Java课程设计中的应用场景。

  1. 执行耗时操作:当应用需要执行一些耗时的操作时,例如读取大量数据、处理图像或进行网络请求,这些操作都可能阻塞UI线程,使得应用程序变得不稳定,甚至崩溃。在这种情况下,我们可以使用多线程来将这些耗时操作转移到后台线程中执行,以确保UI线程的流畅性。
  2. 更新UI组件:当需要在后台线程中更新UI组件时,例如根据用户输入动态搜索并更新下拉菜单或根据网络请求动态更新标签或图像,这些操作都必须在JavaFX的应用程序线程中执行,因为UI组件只能由应用程序线程更新。在这种情况下,我们可以使用JavaFX的Task和Service类来管理后台任务,并在任务完成后更新UI组件。
  3. 并发处理:当应用程序需要处理多个任务或事件时,例如同时处理多个用户输入或同时处理多个网络请求,这些操作可能会导致竞争条件或死锁。在这种情况下,我们可以使用多线程来并行处理这些任务,以提高应用程序的性能和响应能力。

可能的问题

所有多线程的应用都会有一些问题,这里我们列举一些可能会遇到的问题。

  1. 竞争条件:当多个线程同时访问共享资源时,可能会导致竞争条件。竞争条件可能会导致数据不一致或不正确的结果。解决这个问题的基本思路是使用同步机制来协调对共享资源的访问。例如,使用synchronized关键字或ReentrantLock类来实现同步访问共享资源。
  2. 死锁:当多个线程相互等待对方释放资源时,可能会发生死锁。解决这个问题的基本思路是避免线程间的循环等待,例如按照固定的顺序获取锁来避免死锁。
  3. 线程安全问题:当多个线程同时访问共享资源时,可能会导致线程安全问题,例如读取不正确的值或写入不正确的值。解决这个问题的基本思路是使用同步机制来保护共享资源的读写操作,例如使用volatile关键字来保证可见性,或使用synchronized关键字或Atomic类来保证原子性。
  4. 性能问题:当使用多线程时,可能会发生性能问题,例如线程间的上下文切换和资源争用。解决这个问题的基本思路是优化代码,例如使用线程池来重用线程、减少锁的使用、使用非阻塞算法等。
  5. 调试问题:当多个线程同时运行时,可能会很难调试问题,例如线程间的竞争条件或死锁。解决这个问题的基本思路是使用调试工具和技术,例如使用断点、打印日志、使用线程安全的数据结构等。

幸运的是,Java提供了很多工具和技术来帮助我们解决这些问题,例如使用synchronized关键字、ReentrantLock类、volatile关键字、Atomic类、线程池、断点、打印日志、线程安全的数据结构等。而且大家很可能不会遇到这些问题。

编写更刺激的程序

避免硬编码

在编写程序时,我们经常会遇到一些常量,例如数字、字符串、文件路径等。这些常量通常是硬编码的,即直接写在代码中,例如:

int x = 10;
String s = "Hello";

显然,当你想要更改这些常量时,你需要修改代码,这样做会增加代码的复杂性和维护成本。因此,我们应该避免硬编码常量。对于Spring Boot来讲,有一些可以参考的方法:

  1. 使用@ConfigurationProperties注解:可以将应用程序中的配置属性注入到Spring Boot的bean中,避免硬编码。例如:
@ConfigurationProperties(prefix = "myconfig")
public class MyConfigProperties {
    private String username;
    private String password;
    // getter和setter
}

在配置文件中,可以设置myconfig.usernamemyconfig.password属性,然后在其他bean中注入MyConfigProperties即可使用。 2. 用@Value注解:可以将配置属性注入到Spring Boot的bean中,避免硬编码。例如:

@Component
public class MyService {
    @Value("${myconfig.username}")
    private String username;
    @Value("${myconfig.password}")
    private String password;
    // ...
}

当然了,对于比较新潮的微服务架构,我们可以使用Spring Cloud Config或者Nacos提供动态下发配置的功能,这样就可以避免硬编码了。

花里胡哨的对象类型

通常,我们可以针对一个类及其实例的功能来做出一些区分,例如有些类只是和数据库对应的实体类,有些类只是用来传递数据的,有些类只是用来做一些工具类的功能,有些类只是用来做一些业务逻辑的功能,有些类只是用来做一些数据结构的功能,有些类只是用来做一些数据校验的功能,有些类只是用来做一些数据转换的功能,有些类只是用来做一些数据缓存的功能,有些类只是用来做一些数据加密的功能,有些类只是用来做一些数据压缩的功能,有些类只是用来做一些数据解压的功能,有些类只是用来做一些数据解析的功能,有些类只是用来做一些数据序列化的功能,有些类只是用来做一些数据反序列化的功能,有些类只是用来做一些数据格式化的功能。

我们通常可以区分出来这些:

  • POJO:Plain Old Java Object,普通的Java对象,通常是和数据库对应的实体类,只是用来做一些数据结构的功能。
  • DTO:Data Transfer Object,数据传输对象,通常是用来传递数据的,只是用来做一些数据结构的功能。
  • VO:Value Object,值对象,通常是用来传递数据的,只是用来做一些数据结构的功能。

其中POJO不需要多做解释,我们可以讨论一下DTO的使用场景。

假设我们需要从一个服务中获取一些用户数据,服务返回的数据格式如下:

{
  "id": 1,
  "name": "John Doe",
  "email": "[email protected]",
  "age": 30
}

现在我们需要在代码中获取这些用户数据,并且需要将这些数据传递给其他部分的代码。我们可以使用Map来传递数据,如下所示:

Map<String, Object> userMap = getUserDataFromService();
int userId = (int) userMap.get("id");
String userName = (String) userMap.get("name");
String userEmail = (String) userMap.get("email");
int userAge = (int) userMap.get("age");

然而,使用Map传递数据存在以下问题:

  • 类型不安全:Map是一个键值对集合,键和值的类型可以是任意类型。如果我们在代码中错误地使用了错误类型的值,就可能会在运行时发生类型错误。
  • 可读性较差:使用Map传递数据时,我们必须使用字符串作为键来访问数据,这可能会导致代码可读性较差。
  • 可维护性较差:使用Map传递数据时,我们必须记住使用哪些键以及这些键的数据类型。这可能会导致代码可维护性较差。
  • 相反,我们可以使用DTO来传递数据。我们可以为用户数据创建一个UserDTO类,如下所示:
public class UserDTO {
    private int id;
    private String name;
    private String email;
    private int age;

    // 省略getter和setter
}

然后我们可以修改代码以使用UserDTO类,如下所示:

UserDTO user = getUserDataFromService();
int userId = user.getId();
String userName = user.getName();
String userEmail = user.getEmail();
int userAge = user.getAge();

使用DTO传递数据的优点如下:

  1. 类型安全:使用DTO传递数据时,我们可以在编译时检查类型错误,从而提高代码的类型安全性。
  2. 可读性更高:使用DTO传递数据时,我们可以使用具有明确含义的属性名称来访问数据,从而提高代码的可读性。
  3. 可维护性更高:使用DTO传递数据时,我们可以清晰地描述数据的结构,从而提高代码的可维护性。

如果数据不太多,那么使用Map也是OK的,不过个人建议使用MapBuilder的方式来构建Map来优化代码风格。

如果你有一个POJO,需要将他包装成不同的返回样式,或者需要组合一些数据,那么定义一个VO对象作为返回值的容器是一个不错的选择。所谓VO也就是Value Object,值对象,通常是用来向前端传递数据的,只是用来做一些数据结构的功能,可以使用VO对各种数据进行最后的封装,然后返回给前端。

多多封装工具类

工具类,顾名思义,就是用来做一些工具类的功能。比如说,我们需要对一个字符串进行加密,那么我们可以定义一个工具类,然后在工具类中定义一个加密的方法,这样我们就可以在任何地方使用这个加密的方法了。

流式API

Java 流式 API(Stream API)是 Java 8 中新增的一种处理集合(Collections)和数组(Arrays)的方式。相较于传统的集合处理方式,它更加简洁、易读、易写,并且具有更高的性能。

Java 流式 API 可以用于对集合中的元素进行过滤、转换、排序、归约等一系列操作,而这些操作都是以流(Stream)的形式进行的。流(Stream)可以被看作是一系列元素的源,这些元素可以是一个集合、一个数组或者其他一些数据源。流的操作可以分为两类:中间操作和终止操作。中间操作是指可以对流进行转换,返回一个新的流,但并不会立即执行的操作;而终止操作则是指执行流操作并返回结果的操作。

下面是 Java 流式 API 的一些常见用法:

过滤:通过 filter() 方法过滤掉不符合条件的元素,返回一个新的流。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
                                    .filter(n -> n % 2 == 0)
                                    .collect(Collectors.toList());

映射:通过 map() 方法将流中的每个元素映射成一个新的元素,返回一个新的流。

List<String> words = Arrays.asList("hello", "world");
List<Integer> wordLengths = words.stream()
                                 .map(String::length)
                                 .collect(Collectors.toList());

排序:通过 sorted() 方法对流中的元素进行排序,返回一个新的流。

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
List<Integer> sortedNumbers = numbers.stream()
                                      .sorted()
                                      .collect(Collectors.toList());

归约:通过 reduce() 方法对流中的元素进行归约操作,返回一个归约后的结果。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b);

Java 流式 API 是 Java 8 中新增的一种非常强大的集合处理方式,可以大大提高开发效率和代码质量。熟练掌握 Java 流式 API 可以让 Java 开发更加轻松和高效。

适用的场景

  • 集合处理:Java 流式 API 可以用于对集合中的元素进行过滤、转换、排序、归约等一系列操作,可以简化集合处理的代码。
  • 数据处理:Java 流式 API 可以用于处理大量数据,如日志分析、数据挖掘等场景。
  • 并行处理:Java 流式 API 可以轻松地实现并行处理,提高程序的执行效率。
  • 数据转换:Java 流式 API 可以将数据从一种形式转换为另一种形式,如将字符串转换为数字、将日期转换为字符串等。
  • 数据筛选:Java 流式 API 可以对数据进行筛选,如筛选出某个范围内的数据、筛选出满足某个条件的数据等。
  • 数据统计:Java 流式 API 可以用于统计数据,如计算平均值、求和、最大值、最小值等

尽管流式 API 可以大大提高开发效率,写起来很爽,许多语言也提供了类似的功能,但是流式 API 也有一些缺点。其最大的缺点就是可读性差,如果不熟悉流式 API 的用法,那么很容易就会写出难以理解的代码。因此,如果你的代码不是很复杂,那么还是建议使用传统的集合处理方式,这样可以让代码更加易读。

但是好处其实也很明显,流式API的复杂度是很低的,而且可以很好的解决并发问题,所以在一些复杂的场景下,还是推荐使用流式API。

如果你需要做用户画像这样的业务,那么流式API是一个不错的选择。比如,如果你需要统计用户的年龄分布,那么你可以使用如下的代码:

Map<Integer, Long> ageDistribution = users.stream()
                                          .collect(Collectors.groupingBy(User::getAge, Collectors.counting()));

使用SQL而不是Java代码

有了ORM框架,我们可以使用Java代码来操作数据库,但是这样做的效率并不高,因为Java代码需要经过编译、运行等一系列步骤,而SQL语句只需要经过编译即可。因此,如果你的业务逻辑比较复杂,那么使用SQL语句来操作数据库会更加高效。

除此以外,如果你需要比较复杂的排序筛选,那么使用SQL语句来操作数据库也是一个不错的选择。比如,如果你需要根据用户的年龄进行排序,那么你可以使用如下的SQL语句:

SELECT * FROM user ORDER BY age DESC

而如果使用Java代码来实现这个功能,那么你需要先将所有的用户查询出来,然后再对用户进行排序,这样的效率就比较低了。