Java 17新特性深度解读:虚拟线程与Records的实战应用

Violet340
Violet340 2026-02-27T20:01:09+08:00
0 0 0

引言

Java 17作为Java 17 LTS(长期支持)版本,带来了许多重要的新特性和改进。作为Java开发者,我们有必要深入了解这些新特性,以便在实际项目中更好地应用它们来提升开发效率和系统性能。本文将重点介绍Java 17中的两个核心特性:虚拟线程(Virtual Threads)和Records类,并探讨Pattern Matching等其他重要改进。通过实际代码示例和最佳实践,帮助读者在生产环境中有效利用这些新特性。

Java 17核心特性概览

Java 17作为LTS版本,不仅延续了Java 11的特性,还引入了许多实用的新功能。这些特性涵盖了并发编程、数据处理、语言改进等多个方面,为Java开发者提供了更强大的工具集。

主要特性更新

  1. 虚拟线程(Virtual Threads):这是Java 17中最重要的新特性之一,极大地简化了并发编程
  2. Records类:提供了一种简洁的方式来创建不可变数据类
  3. Pattern Matching for switch:增强了switch语句的功能
  4. Sealed Classes:提供更严格的类继承控制
  5. Foreign Function & Memory API:改善了与本地代码的交互

虚拟线程(Virtual Threads)深度解析

什么是虚拟线程

虚拟线程是Java 17中引入的一个全新概念,它是一种轻量级的线程实现。与传统的平台线程(Platform Threads)不同,虚拟线程由JVM管理,而不是直接映射到操作系统线程。这种设计使得我们可以创建成千上万个虚拟线程而不会遇到平台线程的性能瓶颈。

虚拟线程的优势

虚拟线程的主要优势体现在以下几个方面:

  1. 资源消耗极低:虚拟线程的创建和销毁成本远低于平台线程
  2. 高并发处理能力:可以轻松创建大量线程而不会导致系统资源耗尽
  3. 简化并发编程:开发者可以像使用普通线程一样使用虚拟线程,无需关心底层实现细节

虚拟线程的创建与使用

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class VirtualThreadExample {
    
    public static void main(String[] args) {
        // 创建虚拟线程
        Thread virtualThread = Thread.ofVirtual()
                .name("VirtualThread-")
                .unstarted(() -> {
                    System.out.println("虚拟线程执行任务");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    System.out.println("虚拟线程任务完成");
                });
        
        virtualThread.start();
        
        // 使用虚拟线程池
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
                System.out.println("任务1执行中");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println("任务1完成");
            }, executor);
            
            CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
                System.out.println("任务2执行中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println("任务2完成");
            }, executor);
            
            CompletableFuture.allOf(future1, future2).join();
        }
    }
}

虚拟线程与平台线程对比

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadComparison {
    
    public static void main(String[] args) {
        // 平台线程示例
        long startTime = System.currentTimeMillis();
        try (ExecutorService platformExecutor = Executors.newFixedThreadPool(1000)) {
            for (int i = 0; i < 1000; i++) {
                final int taskId = i;
                platformExecutor.submit(() -> {
                    System.out.println("平台线程任务 " + taskId);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
            }
        }
        long platformTime = System.currentTimeMillis() - startTime;
        
        // 虚拟线程示例
        startTime = System.currentTimeMillis();
        try (ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 1000; i++) {
                final int taskId = i;
                virtualExecutor.submit(() -> {
                    System.out.println("虚拟线程任务 " + taskId);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
            }
        }
        long virtualTime = System.currentTimeMillis() - startTime;
        
        System.out.println("平台线程耗时: " + platformTime + "ms");
        System.out.println("虚拟线程耗时: " + virtualTime + "ms");
    }
}

实际应用场景

高并发Web服务处理

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HighConcurrencyService {
    
    private final HttpClient httpClient;
    private final ExecutorService executor;
    
    public HighConcurrencyService() {
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(5))
                .build();
        
        // 使用虚拟线程池处理高并发请求
        this.executor = Executors.newVirtualThreadPerTaskExecutor();
    }
    
    public CompletableFuture<String> fetchUserData(String userId) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                HttpRequest request = HttpRequest.newBuilder()
                        .uri(URI.create("https://api.example.com/users/" + userId))
                        .timeout(Duration.ofSeconds(10))
                        .header("Accept", "application/json")
                        .GET()
                        .build();
                
                HttpResponse<String> response = httpClient.send(request, 
                        HttpResponse.BodyHandlers.ofString());
                
                return response.body();
            } catch (Exception e) {
                throw new RuntimeException("获取用户数据失败", e);
            }
        }, executor);
    }
    
    public CompletableFuture<Void> processBatchUsers(String[] userIds) {
        CompletableFuture<?>[] futures = new CompletableFuture[userIds.length];
        
        for (int i = 0; i < userIds.length; i++) {
            futures[i] = fetchUserData(userIds[i])
                    .thenAccept(data -> System.out.println("处理用户数据: " + data))
                    .exceptionally(throwable -> {
                        System.err.println("处理用户数据失败: " + throwable.getMessage());
                        return null;
                    });
        }
        
        return CompletableFuture.allOf(futures);
    }
    
    public static void main(String[] args) {
        HighConcurrencyService service = new HighConcurrencyService();
        
        String[] userIds = {"user1", "user2", "user3", "user4", "user5"};
        
        service.processBatchUsers(userIds)
                .thenRun(() -> System.out.println("所有用户数据处理完成"))
                .join();
    }
}

数据处理管道

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

public class DataProcessingPipeline {
    
    private final ExecutorService executor;
    private final AtomicInteger processedCount = new AtomicInteger(0);
    
    public DataProcessingPipeline() {
        // 使用虚拟线程池创建处理管道
        this.executor = Executors.newVirtualThreadPerTaskExecutor();
    }
    
    public CompletableFuture<Void> processBatchData(int[] data) {
        CompletableFuture<Void> pipeline = CompletableFuture.completedFuture(null);
        
        // 数据预处理阶段
        pipeline = pipeline.thenComposeAsync(v -> {
            System.out.println("开始预处理数据");
            return CompletableFuture.runAsync(() -> {
                IntStream.of(data).forEach(value -> {
                    // 模拟预处理工作
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
                System.out.println("预处理完成");
            }, executor);
        });
        
        // 数据转换阶段
        pipeline = pipeline.thenComposeAsync(v -> {
            System.out.println("开始转换数据");
            return CompletableFuture.runAsync(() -> {
                IntStream.of(data).forEach(value -> {
                    // 模拟数据转换
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
                System.out.println("数据转换完成");
            }, executor);
        });
        
        // 数据验证阶段
        pipeline = pipeline.thenComposeAsync(v -> {
            System.out.println("开始验证数据");
            return CompletableFuture.runAsync(() -> {
                IntStream.of(data).forEach(value -> {
                    // 模拟数据验证
                    try {
                        Thread.sleep(15);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
                System.out.println("数据验证完成");
            }, executor);
        });
        
        // 数据存储阶段
        pipeline = pipeline.thenComposeAsync(v -> {
            System.out.println("开始存储数据");
            return CompletableFuture.runAsync(() -> {
                IntStream.of(data).forEach(value -> {
                    // 模拟数据存储
                    try {
                        Thread.sleep(25);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
                System.out.println("数据存储完成");
            }, executor);
        });
        
        return pipeline;
    }
    
    public static void main(String[] args) {
        DataProcessingPipeline pipeline = new DataProcessingPipeline();
        
        int[] testData = IntStream.range(0, 1000).toArray();
        
        long startTime = System.currentTimeMillis();
        pipeline.processBatchData(testData)
                .thenRun(() -> {
                    long endTime = System.currentTimeMillis();
                    System.out.println("批量数据处理完成,耗时: " + (endTime - startTime) + "ms");
                })
                .join();
    }
}

Records类详解

Records的引入背景

Records类是Java 17中引入的一个重要语言特性,它简化了不可变数据类的创建。在引入Records之前,开发者需要编写大量的样板代码来创建简单的数据类,这不仅繁琐,而且容易出错。

Records的基本语法

// 传统方式创建不可变数据类
public class Person {
    private final String name;
    private final int age;
    private final String email;
    
    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    
    public String name() {
        return name;
    }
    
    public int age() {
        return age;
    }
    
    public String email() {
        return email;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age &&
                Objects.equals(name, person.name) &&
                Objects.equals(email, person.email);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age, email);
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", email='" + email + "'}";
    }
}

// 使用Records简化
public record PersonRecord(String name, int age, String email) {
    // Records会自动生成构造函数、getter方法、equals、hashCode和toString方法
}

Records的高级特性

构造函数和验证

public record UserRecord(String username, String email, int age) {
    
    // 验证构造函数
    public UserRecord {
        if (username == null || username.trim().isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("邮箱格式不正确");
        }
        if (age < 0) {
            throw new IllegalArgumentException("年龄不能为负数");
        }
    }
    
    // 静态工厂方法
    public static UserRecord of(String username, String email, int age) {
        return new UserRecord(username, email, age);
    }
    
    // 只读方法
    public String displayName() {
        return username + " (" + email + ")";
    }
}

继承和接口实现

// 实现接口的Records
public record Point(int x, int y) implements Comparable<Point> {
    
    @Override
    public int compareTo(Point other) {
        int result = Integer.compare(this.x, other.x);
        if (result == 0) {
            result = Integer.compare(this.y, other.y);
        }
        return result;
    }
    
    // 计算距离
    public double distance(Point other) {
        return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
    }
    
    // 静态方法
    public static Point origin() {
        return new Point(0, 0);
    }
}

// 嵌套Records
public record Rectangle(Point topLeft, Point bottomRight) {
    
    public record Dimensions(int width, int height) {
        public static Dimensions fromPoints(Point p1, Point p2) {
            return new Dimensions(Math.abs(p2.x() - p1.x()), Math.abs(p2.y() - p1.y()));
        }
    }
    
    public Dimensions getDimensions() {
        return Dimensions.fromPoints(topLeft, bottomRight);
    }
}

Records在实际项目中的应用

数据传输对象(DTO)

// 用户信息DTO
public record UserDTO(String id, String name, String email, String role, long createdAt) {
    
    public UserDTO {
        if (id == null || id.trim().isEmpty()) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("邮箱格式不正确");
        }
        if (createdAt <= 0) {
            throw new IllegalArgumentException("创建时间必须大于0");
        }
    }
    
    // 从实体对象创建DTO
    public static UserDTO fromEntity(UserEntity entity) {
        return new UserDTO(
            entity.getId(),
            entity.getName(),
            entity.getEmail(),
            entity.getRole(),
            entity.getCreatedAt().toEpochMilli()
        );
    }
    
    // 转换为实体对象
    public UserEntity toEntity() {
        return new UserEntity(
            this.id,
            this.name,
            this.email,
            this.role,
            new Date(this.createdAt)
        );
    }
}

// API响应封装
public record ApiResponse<T>(boolean success, String message, T data, int code) {
    
    public ApiResponse {
        if (message == null) {
            throw new IllegalArgumentException("消息不能为空");
        }
        if (code < 0) {
            throw new IllegalArgumentException("响应码不能为负数");
        }
    }
    
    public static <T> ApiResponse<T> success(T data, String message) {
        return new ApiResponse<>(true, message, data, 200);
    }
    
    public static <T> ApiResponse<T> error(String message, int code) {
        return new ApiResponse<>(false, message, null, code);
    }
}

配置对象

// 数据库配置
public record DatabaseConfig(String host, int port, String database, String username, String password) {
    
    public DatabaseConfig {
        if (host == null || host.trim().isEmpty()) {
            throw new IllegalArgumentException("主机地址不能为空");
        }
        if (port <= 0 || port > 65535) {
            throw new IllegalArgumentException("端口号必须在1-65535之间");
        }
        if (database == null || database.trim().isEmpty()) {
            throw new IllegalArgumentException("数据库名称不能为空");
        }
        if (username == null || username.trim().isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
    }
    
    public String getConnectionString() {
        return "jdbc:mysql://" + host + ":" + port + "/" + database;
    }
    
    public static DatabaseConfig fromProperties(String host, int port, String database, 
                                               String username, String password) {
        return new DatabaseConfig(host, port, database, username, password);
    }
}

// 应用配置
public record AppConfig(String appName, String version, int maxThreads, boolean debugMode) {
    
    public AppConfig {
        if (appName == null || appName.trim().isEmpty()) {
            throw new IllegalArgumentException("应用名称不能为空");
        }
        if (version == null || version.trim().isEmpty()) {
            throw new IllegalArgumentException("版本号不能为空");
        }
        if (maxThreads <= 0) {
            throw new IllegalArgumentException("最大线程数必须大于0");
        }
    }
    
    public static AppConfig defaultConfig() {
        return new AppConfig("MyApp", "1.0.0", 100, false);
    }
    
    public AppConfig withDebugMode(boolean debugMode) {
        return new AppConfig(this.appName, this.version, this.maxThreads, debugMode);
    }
}

Pattern Matching for switch

语法改进

Pattern Matching for switch是Java 17中对switch语句的重要改进,它允许在switch表达式中使用模式匹配,使代码更加简洁和强大。

// 传统switch语句
public class TraditionalSwitch {
    
    public static String processObject(Object obj) {
        if (obj == null) {
            return "null";
        }
        if (obj instanceof String s) {
            return "String: " + s;
        }
        if (obj instanceof Integer i) {
            return "Integer: " + i;
        }
        if (obj instanceof Double d) {
            return "Double: " + d;
        }
        return "Unknown: " + obj.getClass().getSimpleName();
    }
    
    // 使用switch表达式
    public static String processObjectSwitch(Object obj) {
        return switch (obj) {
            case null -> "null";
            case String s -> "String: " + s;
            case Integer i -> "Integer: " + i;
            case Double d -> "Double: " + d;
            default -> "Unknown: " + obj.getClass().getSimpleName();
        };
    }
}

// 复杂模式匹配
public class PatternMatchingExample {
    
    public static String evaluateExpression(Object obj) {
        return switch (obj) {
            case Integer i when i > 0 -> "正整数: " + i;
            case Integer i when i < 0 -> "负整数: " + i;
            case Integer i -> "零: " + i;
            case Double d when d > 0 -> "正浮点数: " + d;
            case Double d when d < 0 -> "负浮点数: " + d;
            case Double d -> "零浮点数: " + d;
            case String s when s.length() > 10 -> "长字符串: " + s;
            case String s -> "短字符串: " + s;
            case null -> "null值";
            default -> "其他类型: " + obj.getClass().getSimpleName();
        };
    }
    
    // 嵌套模式匹配
    public static String processNested(Object obj) {
        return switch (obj) {
            case List<?> list when !list.isEmpty() -> {
                Object first = list.get(0);
                yield switch (first) {
                    case String s -> "列表第一个元素是字符串: " + s;
                    case Integer i -> "列表第一个元素是整数: " + i;
                    default -> "列表第一个元素是其他类型";
                };
            }
            case List<?> list when list.isEmpty() -> "空列表";
            case null -> "null值";
            default -> "其他类型";
        };
    }
}

实际应用场景

数据处理和转换

import java.util.*;
import java.util.stream.Collectors;

public class DataProcessor {
    
    public static List<String> processUserData(List<Object> data) {
        return data.stream()
                .map(item -> switch (item) {
                    case String s -> "字符串: " + s;
                    case Integer i -> "整数: " + i;
                    case Double d -> "浮点数: " + d;
                    case Boolean b -> "布尔值: " + b;
                    case List<?> list -> "列表,大小: " + list.size();
                    case Map<?, ?> map -> "映射,大小: " + map.size();
                    case null -> "null值";
                    default -> "其他类型: " + item.getClass().getSimpleName();
                })
                .collect(Collectors.toList());
    }
    
    // 复杂的业务逻辑处理
    public static String handleRequest(Object request) {
        return switch (request) {
            case String s when s.startsWith("GET") -> "处理GET请求";
            case String s when s.startsWith("POST") -> "处理POST请求";
            case String s when s.startsWith("PUT") -> "处理PUT请求";
            case String s when s.startsWith("DELETE") -> "处理DELETE请求";
            case Map<String, Object> map when map.containsKey("method") -> {
                String method = (String) map.get("method");
                yield switch (method) {
                    case "GET" -> "处理GET请求";
                    case "POST" -> "处理POST请求";
                    case "PUT" -> "处理PUT请求";
                    case "DELETE" -> "处理DELETE请求";
                    default -> "未知请求方法";
                };
            }
            case null -> "请求为空";
            default -> "未知请求类型";
        };
    }
    
    // 异常处理
    public static String handleException(Throwable throwable) {
        return switch (throwable) {
            case NullPointerException npe -> "空指针异常: " + npe.getMessage();
            case IllegalArgumentException iae -> "参数异常: " + iae.getMessage();
            case RuntimeException re -> "运行时异常: " + re.getMessage();
            case Exception e -> "其他异常: " + e.getMessage();
            case null -> "异常为空";
            default -> "未知异常类型: " + throwable.getClass().getSimpleName();
        };
    }
}

最佳实践和性能优化

虚拟线程的最佳实践

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

public class VirtualThreadBestPractices {
    
    // 1. 合理使用虚拟线程池
    private static final ExecutorService VIRTUAL_EXECUTOR = 
        Executors.newVirtualThreadPerTaskExecutor();
    
    // 2. 控制并发度
    private static final Semaphore CONCURRENT_SEMAPHORE = new Semaphore(100);
    
    // 3. 使用线程池管理资源
    public static CompletableFuture<String> processWithResourceControl(String data) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                // 获取资源许可
                CONCURRENT_SEMAPHORE.acquire();
                
                // 模拟处理工作
                Thread.sleep(1000);
                
                return "处理完成: " + data;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("处理中断", e);
            } finally {
                // 释放资源许可
                CONCURRENT_SEMAPHORE.release();
            }
        }, VIRTUAL_EXECUTOR);
    }
    
    // 4. 异常处理和资源清理
    public static CompletableFuture<Void> robustAsyncOperation(String taskId) {
        return CompletableFuture.runAsync(() -> {
            try {
                System.out.println("开始处理任务: " + taskId);
                Thread.sleep(2000);
                System.out.println("任务完成: " + taskId);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.err.println("任务被中断: " + taskId);
                throw new RuntimeException("任务中断", e);
            } catch (Exception e) {
                System.err.println("任务执行失败: " + taskId + ", 错误: " + e.getMessage());
                throw new RuntimeException("任务执行失败", e);
            }
        }, VIRTUAL_EXECUTOR);
    }
    
    // 5. 批量处理优化
    public static CompletableFuture<Void> batchProcess(List<String> tasks) {
        CompletableFuture<?>[] futures = tasks.stream()
                .map(task -> robustAsyncOperation(task))
                .toArray(CompletableFuture[]::new);
        
        return CompletableFuture.allOf(futures);
    }
    
    public static void main(String[] args) {
        // 测试批量处理
        List<String> tasks = Arrays.asList("task1", "task2", "task3", "task4", "task5");
        
        batchProcess(tasks)
                .thenRun(() -> System.out.println("所有任务处理完成"))
                .exceptionally(throwable -> {
                    System.err.println("批量处理失败: " + throwable.getMessage());
                    return null;
                })
                .join();
    }
}

Records的最佳实践

// 1. 使用不可变性
public record ImmutablePoint(int x, int y) {
    // 不提供setter方法,确保不可变性
    
    public ImmutablePoint move(int dx, int dy) {
        return new ImmutablePoint(this.x + dx, this.y + dy);
    }
    
    public double distance(ImmutablePoint other) {
        return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
    }
}

// 2. 验证和约束
public record ValidatedUser(String username, String email, int age) {
    
    public ValidatedUser {
        if (username == null || username.trim().isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("邮箱格式不正确");
        }
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄必须在0-150之间");
        }
    }
    
    public boolean isAdult() {
        return age >= 18;
    }
    
    public ValidatedUser withAge(int newAge) {
        return new ValidatedUser(this.username, this.email, newAge);
    }
}

// 3. 组合使用
public record UserWithProfile(String id, String name, String email, 
                             UserProfile profile, List<String> roles) {
    
    public UserWithProfile {
        if (id == null || id.trim().isEmpty()) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        if (profile == null) {
            throw new IllegalArgumentException("用户资料不能为空");
        }
        if (roles == null) {
            throw new IllegalArgumentException("角色列表不能为空");
        }
    }
    
    public boolean hasRole(String role) {
        return roles.contains(role);
    }
    
    public UserWithProfile addRole(String role) {
        List<String> newRoles = new ArrayList<>(roles);
        newRoles.add(role);
        return new UserWithProfile(this.id, this.name, this.email, this.profile, newRoles);
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000