Java 17新特性实战:虚拟线程、模式匹配与记录类在企业级应用中的应用

DeepScream
DeepScream 2026-03-14T22:05:10+08:00
0 0 0

引言

Java 17作为LTS(长期支持)版本,带来了许多重要的新特性,这些特性不仅提升了Java语言的表达能力,更重要的是为企业的应用程序开发提供了更高效、更安全的解决方案。本文将深入探讨Java 17中的三个核心新特性:虚拟线程(Virtual Threads)、模式匹配(Pattern Matching)以及记录类(Records),并结合企业级应用场景展示如何利用这些特性来提升代码质量和开发效率。

Java 17核心新特性概述

虚拟线程(Virtual Threads)

虚拟线程是Java 17中最重要的新特性之一,它解决了传统线程在高并发场景下的性能瓶颈问题。虚拟线程本质上是轻量级的线程实现,能够显著减少系统资源消耗并提高并发处理能力。

模式匹配(Pattern Matching)

模式匹配为Java语言引入了更强大的类型检查和解构能力,使得代码更加简洁和安全,特别是在处理复杂的对象结构时表现出色。

记录类(Records)

记录类是Java中一种新的类声明形式,它能够自动为类生成构造函数、getter方法、equals、hashCode和toString等常用方法,大大减少了样板代码的编写。

虚拟线程在企业级应用中的实践

什么是虚拟线程

虚拟线程是Java 17中引入的一种新的线程实现方式,它与传统的平台线程不同。虚拟线程由JVM管理,而不是直接映射到操作系统的线程。这意味着一个虚拟线程可以被映射到多个平台线程上,从而大大减少了系统资源的消耗。

// 传统平台线程创建方式
Thread platformThread = new Thread(() -> {
    System.out.println("Platform thread: " + Thread.currentThread().getName());
});

// 虚拟线程创建方式
Thread virtualThread = Thread.ofVirtual()
    .name("Virtual-Thread-")
    .start(() -> {
        System.out.println("Virtual thread: " + Thread.currentThread().getName());
    });

虚拟线程的性能优势

在企业级应用中,虚拟线程能够显著提升高并发场景下的性能表现。让我们通过一个实际的例子来展示这种优势:

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

public class VirtualThreadExample {
    
    public static void main(String[] args) throws InterruptedException {
        // 测试传统线程的性能
        testPlatformThreads();
        
        // 测试虚拟线程的性能
        testVirtualThreads();
    }
    
    private static void testPlatformThreads() throws InterruptedException {
        System.out.println("=== 传统平台线程测试 ===");
        long startTime = System.currentTimeMillis();
        
        ExecutorService executor = Executors.newFixedThreadPool(1000);
        for (int i = 0; i < 1000; i++) {
            final int taskId = i;
            executor.submit(() -> {
                try {
                    Thread.sleep(100); // 模拟工作
                    System.out.println("Platform thread task " + taskId + " completed");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        long endTime = System.currentTimeMillis();
        System.out.println("传统线程耗时: " + (endTime - startTime) + "ms");
    }
    
    private static void testVirtualThreads() throws InterruptedException {
        System.out.println("=== 虚拟线程测试 ===");
        long startTime = System.currentTimeMillis();
        
        // 使用虚拟线程池
        ExecutorService virtualExecutor = Executors.newVirtualThreadPerTask();
        for (int i = 0; i < 1000; i++) {
            final int taskId = i;
            virtualExecutor.submit(() -> {
                try {
                    Thread.sleep(100); // 模拟工作
                    System.out.println("Virtual thread task " + taskId + " completed");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }
        
        virtualExecutor.shutdown();
        virtualExecutor.awaitTermination(1, TimeUnit.MINUTES);
        long endTime = System.currentTimeMillis();
        System.out.println("虚拟线程耗时: " + (endTime - startTime) + "ms");
    }
}

企业级应用场景:异步API处理

在企业级应用中,虚拟线程特别适合处理大量的异步API调用。以下是一个典型的微服务场景:

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 AsyncApiService {
    
    private final HttpClient httpClient = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10))
        .build();
    
    // 使用传统线程处理API调用
    public CompletableFuture<String> callApiWithPlatformThreads(String url) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(url))
                    .timeout(Duration.ofSeconds(30))
                    .build();
                
                HttpResponse<String> response = httpClient.send(request, 
                    HttpResponse.BodyHandlers.ofString());
                return response.body();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }
    
    // 使用虚拟线程处理API调用
    public CompletableFuture<String> callApiWithVirtualThreads(String url) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(url))
                    .timeout(Duration.ofSeconds(30))
                    .build();
                
                HttpResponse<String> response = httpClient.send(request, 
                    HttpResponse.BodyHandlers.ofString());
                return response.body();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, Executors.newVirtualThreadPerTask());
    }
    
    // 批量API调用示例
    public CompletableFuture<Void> batchApiCalls(String[] urls) {
        CompletableFuture<?>[] futures = new CompletableFuture[urls.length];
        
        for (int i = 0; i < urls.length; i++) {
            final int index = i;
            futures[i] = CompletableFuture.runAsync(() -> {
                try {
                    System.out.println("Processing URL: " + urls[index] + 
                        " on thread: " + Thread.currentThread().getName());
                    callApiWithVirtualThreads(urls[index]).join();
                } catch (Exception e) {
                    System.err.println("Error processing URL: " + urls[index]);
                    throw new RuntimeException(e);
                }
            }, Executors.newVirtualThreadPerTask());
        }
        
        return CompletableFuture.allOf(futures);
    }
}

虚拟线程与线程池管理

在企业应用中,合理使用虚拟线程可以大大简化线程池的配置和管理:

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

public class ThreadManagementExample {
    
    // 使用虚拟线程池处理并发任务
    public void processTasksWithVirtualThreads() {
        ExecutorService virtualExecutor = Executors.newVirtualThreadPerTask();
        
        try {
            // 创建大量并发任务
            CompletableFuture<?>[] tasks = new CompletableFuture[1000];
            for (int i = 0; i < 1000; i++) {
                final int taskId = i;
                tasks[i] = CompletableFuture.runAsync(() -> {
                    try {
                        // 模拟工作负载
                        Thread.sleep(100);
                        System.out.println("Task " + taskId + " completed on thread: " 
                            + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }, virtualExecutor);
            }
            
            // 等待所有任务完成
            CompletableFuture.allOf(tasks).join();
        } finally {
            virtualExecutor.shutdown();
        }
    }
    
    // 限制并发数量的虚拟线程实现
    public void processLimitedConcurrency() {
        Semaphore semaphore = new Semaphore(10); // 最多10个并发
        
        ExecutorService virtualExecutor = Executors.newVirtualThreadPerTask();
        
        try {
            CompletableFuture<?>[] tasks = new CompletableFuture[100];
            for (int i = 0; i < 100; i++) {
                final int taskId = i;
                tasks[i] = CompletableFuture.runAsync(() -> {
                    try {
                        semaphore.acquire();
                        // 执行任务
                        Thread.sleep(1000);
                        System.out.println("Task " + taskId + " completed");
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    } finally {
                        semaphore.release();
                    }
                }, virtualExecutor);
            }
            
            CompletableFuture.allOf(tasks).join();
        } finally {
            virtualExecutor.shutdown();
        }
    }
}

模式匹配在企业级应用中的应用

模式匹配基础语法

模式匹配是Java 17中引入的语法特性,它允许我们在switch语句中使用更复杂的类型匹配和解构操作。这大大简化了复杂对象的处理逻辑。

// Java 17之前的switch语法
public String processShape(Object shape) {
    if (shape instanceof Circle) {
        Circle circle = (Circle) shape;
        return "Circle with radius: " + circle.radius();
    } else if (shape instanceof Rectangle) {
        Rectangle rectangle = (Rectangle) shape;
        return "Rectangle with width: " + rectangle.width() + 
               ", height: " + rectangle.height();
    } else if (shape instanceof Triangle) {
        Triangle triangle = (Triangle) shape;
        return "Triangle with base: " + triangle.base() + 
               ", height: " + triangle.height();
    }
    return "Unknown shape";
}

// 使用模式匹配的switch表达式
public String processShapeWithPatternMatching(Object shape) {
    return switch (shape) {
        case Circle c -> "Circle with radius: " + c.radius();
        case Rectangle r -> "Rectangle with width: " + r.width() + 
                           ", height: " + r.height();
        case Triangle t -> "Triangle with base: " + t.base() + 
                          ", height: " + t.height();
        default -> "Unknown shape";
    };
}

企业级数据处理场景

在企业应用中,模式匹配特别适合处理复杂的业务对象和数据转换:

import java.util.List;
import java.util.ArrayList;

// 定义数据模型
sealed interface PaymentMethod permits CreditCard, DebitCard, BankTransfer {
    String id();
    double amount();
}

final class CreditCard implements PaymentMethod {
    private final String id;
    private final double amount;
    private final String cardNumber;
    
    public CreditCard(String id, double amount, String cardNumber) {
        this.id = id;
        this.amount = amount;
        this.cardNumber = cardNumber;
    }
    
    @Override
    public String id() { return id; }
    
    @Override
    public double amount() { return amount; }
    
    public String cardNumber() { return cardNumber; }
}

final class DebitCard implements PaymentMethod {
    private final String id;
    private final double amount;
    private final String accountNumber;
    
    public DebitCard(String id, double amount, String accountNumber) {
        this.id = id;
        this.amount = amount;
        this.accountNumber = accountNumber;
    }
    
    @Override
    public String id() { return id; }
    
    @Override
    public double amount() { return amount; }
    
    public String accountNumber() { return accountNumber; }
}

final class BankTransfer implements PaymentMethod {
    private final String id;
    private final double amount;
    private final String bankAccount;
    
    public BankTransfer(String id, double amount, String bankAccount) {
        this.id = id;
        this.amount = amount;
        this.bankAccount = bankAccount;
    }
    
    @Override
    public String id() { return id; }
    
    @Override
    public double amount() { return amount; }
    
    public String bankAccount() { return bankAccount; }
}

public class PaymentProcessor {
    
    // 使用模式匹配处理不同类型的支付方式
    public String processPayment(PaymentMethod payment) {
        return switch (payment) {
            case CreditCard c -> {
                String maskedNumber = maskCardNumber(c.cardNumber());
                yield "Processing credit card payment: " + maskedNumber + 
                      " for amount $" + c.amount();
            }
            case DebitCard d -> {
                String maskedAccount = maskAccountNumber(d.accountNumber());
                yield "Processing debit card payment: " + maskedAccount + 
                      " for amount $" + d.amount();
            }
            case BankTransfer b -> {
                String maskedAccount = maskBankAccount(b.bankAccount());
                yield "Processing bank transfer: " + maskedAccount + 
                      " for amount $" + b.amount();
            }
            case null, default -> "Unknown payment method";
        };
    }
    
    private String maskCardNumber(String cardNumber) {
        if (cardNumber == null || cardNumber.length() < 4) {
            return "****";
        }
        return "****-****-****-" + cardNumber.substring(cardNumber.length() - 4);
    }
    
    private String maskAccountNumber(String accountNumber) {
        if (accountNumber == null || accountNumber.length() < 4) {
            return "****";
        }
        return "****" + accountNumber.substring(accountNumber.length() - 4);
    }
    
    private String maskBankAccount(String bankAccount) {
        if (bankAccount == null || bankAccount.length() < 4) {
            return "****";
        }
        return bankAccount.substring(0, Math.min(4, bankAccount.length())) + 
               "***" + bankAccount.substring(bankAccount.length() - 4);
    }
    
    // 处理支付列表的模式匹配
    public List<String> processPayments(List<PaymentMethod> payments) {
        List<String> results = new ArrayList<>();
        
        for (PaymentMethod payment : payments) {
            String result = switch (payment) {
                case CreditCard c when c.amount() > 1000 -> 
                    "High-value credit card payment: $" + c.amount();
                case CreditCard c -> 
                    "Standard credit card payment: $" + c.amount();
                case DebitCard d when d.amount() > 500 -> 
                    "High-value debit card payment: $" + d.amount();
                case DebitCard d -> 
                    "Standard debit card payment: $" + d.amount();
                case BankTransfer b when b.amount() > 10000 -> 
                    "Large bank transfer: $" + b.amount();
                case BankTransfer b -> 
                    "Standard bank transfer: $" + b.amount();
                default -> "Unknown payment type";
            };
            results.add(result);
        }
        
        return results;
    }
}

复杂对象解构与数据验证

模式匹配还可以用于复杂对象的解构和数据验证:

import java.util.Map;
import java.util.HashMap;

public class DataValidationExample {
    
    // 使用模式匹配进行数据解构
    public String validateUserData(Object data) {
        return switch (data) {
            case Map<String, Object> userData when 
                userData.containsKey("name") && 
                userData.containsKey("email") -> {
                String name = (String) userData.get("name");
                String email = (String) userData.get("email");
                yield validateUser(name, email);
            }
            case Map<String, Object> userData -> 
                "Incomplete user data: " + userData;
            default -> "Invalid data type";
        };
    }
    
    private String validateUser(String name, String email) {
        if (name == null || name.trim().isEmpty()) {
            return "Name is required";
        }
        if (email == null || !email.contains("@")) {
            return "Valid email is required";
        }
        return "User data is valid";
    }
    
    // 处理嵌套对象的模式匹配
    public String processNestedData(Object obj) {
        return switch (obj) {
            case Person p when p.address() instanceof Address a -> 
                "Processing person: " + p.name() + 
                " at " + a.street() + ", " + a.city();
            case Person p -> 
                "Processing person: " + p.name() + 
                " with no address";
            case null -> "Null object provided";
            default -> "Unknown object type";
        };
    }
    
    // 定义嵌套类
    record Person(String name, Address address) {}
    
    record Address(String street, String city, String zipCode) {}
}

记录类在企业级应用中的实践

记录类基础概念

记录类是Java 17中引入的一种新的类声明形式,它能够自动为类生成构造函数、getter方法、equals、hashCode和toString等常用方法。这大大减少了样板代码的编写。

// 传统类实现
public class Point {
    private final int x;
    private final int y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int x() {
        return x;
    }
    
    public int y() {
        return y;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Point point = (Point) obj;
        return x == point.x && y == point.y;
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
    
    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

// 使用记录类实现
public record PointRecord(int x, int y) {
    // 记录类会自动提供构造函数、getter方法和相关方法
}

企业级数据模型应用

在企业应用中,记录类特别适合用于创建不可变的数据传输对象(DTO)和值对象:

import java.time.LocalDateTime;
import java.util.List;

// 用户信息记录类
public record User(String id, String name, String email, 
                  LocalDateTime createdAt, List<String> roles) {
    // 验证构造参数的构造函数
    public User {
        if (id == null || id.trim().isEmpty()) {
            throw new IllegalArgumentException("User ID cannot be null or empty");
        }
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("User name cannot be null or empty");
        }
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("Invalid email format");
        }
        if (createdAt == null) {
            throw new IllegalArgumentException("Created at cannot be null");
        }
    }
    
    // 额外的业务方法
    public boolean isAdmin() {
        return roles != null && roles.contains("ADMIN");
    }
    
    public String getDisplayName() {
        return name + " (" + id + ")";
    }
}

// 产品信息记录类
public record Product(String id, String name, double price, 
                     String category, LocalDateTime createdDate) {
    // 验证构造参数
    public Product {
        if (id == null || id.trim().isEmpty()) {
            throw new IllegalArgumentException("Product ID cannot be null or empty");
        }
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("Product name cannot be null or empty");
        }
        if (price < 0) {
            throw new IllegalArgumentException("Price cannot be negative");
        }
        if (category == null || category.trim().isEmpty()) {
            throw new IllegalArgumentException("Category cannot be null or empty");
        }
    }
    
    // 创建新的产品实例(用于修改)
    public Product withPrice(double newPrice) {
        return new Product(id, name, newPrice, category, createdDate);
    }
    
    public Product withName(String newName) {
        return new Product(id, newName, price, category, createdDate);
    }
}

// API响应记录类
public record ApiResponse<T>(boolean success, String message, 
                           T data, int code) {
    public ApiResponse {
        if (message == null) {
            throw new IllegalArgumentException("Message cannot be null");
        }
        if (code < 0) {
            throw new IllegalArgumentException("Code cannot be negative");
        }
    }
    
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(true, "Success", data, 200);
    }
    
    public static <T> ApiResponse<T> error(String message, int code) {
        return new ApiResponse<>(false, message, null, code);
    }
}

数据处理与转换

记录类在数据处理和转换场景中表现出色:

import java.util.List;
import java.util.stream.Collectors;

public class DataProcessingExample {
    
    // 处理订单数据
    public record Order(String id, String customerId, List<OrderItem> items) {}
    
    public record OrderItem(String productId, int quantity, double price) {}
    
    public record OrderSummary(String orderId, String customerName, 
                              double totalAmount, int itemCount) {
        public OrderSummary {
            if (orderId == null || orderId.trim().isEmpty()) {
                throw new IllegalArgumentException("Order ID cannot be null or empty");
            }
            if (totalAmount < 0) {
                throw new IllegalArgumentException("Total amount cannot be negative");
            }
            if (itemCount < 0) {
                throw new IllegalArgumentException("Item count cannot be negative");
            }
        }
    }
    
    // 转换订单数据
    public List<OrderSummary> processOrders(List<Order> orders, 
                                           Map<String, String> customerNames) {
        return orders.stream()
            .map(order -> {
                double totalAmount = order.items().stream()
                    .mapToDouble(item -> item.quantity() * item.price())
                    .sum();
                
                int itemCount = order.items().stream()
                    .mapToInt(OrderItem::quantity)
                    .sum();
                
                String customerName = customerNames.getOrDefault(order.customerId(), "Unknown");
                
                return new OrderSummary(
                    order.id(),
                    customerName,
                    totalAmount,
                    itemCount
                );
            })
            .collect(Collectors.toList());
    }
    
    // 处理复杂的业务数据
    public record BusinessData(String businessId, 
                             String businessName, 
                             String category, 
                             double rating, 
                             int reviewCount) {
        public BusinessData {
            if (businessId == null || businessId.trim().isEmpty()) {
                throw new IllegalArgumentException("Business ID cannot be null or empty");
            }
            if (businessName == null || businessName.trim().isEmpty()) {
                throw new IllegalArgumentException("Business name cannot be null or empty");
            }
            if (category == null || category.trim().isEmpty()) {
                throw new IllegalArgumentException("Category cannot be null or empty");
            }
            if (rating < 0 || rating > 5) {
                throw new IllegalArgumentException("Rating must be between 0 and 5");
            }
            if (reviewCount < 0) {
                throw new IllegalArgumentException("Review count cannot be negative");
            }
        }
        
        public boolean isHighRated() {
            return rating >= 4.5;
        }
        
        public boolean hasManyReviews() {
            return reviewCount > 100;
        }
    }
    
    // 处理业务数据集合
    public List<BusinessData> filterBusinessData(List<BusinessData> businesses) {
        return businesses.stream()
            .filter(business -> business.isHighRated() && business.hasManyReviews())
            .collect(Collectors.toList());
    }
}

企业级最佳实践与性能优化

虚拟线程的最佳实践

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

public class VirtualThreadBestPractices {
    
    // 合理使用虚拟线程池
    public static ExecutorService createOptimalVirtualThreadPool() {
        // 根据CPU核心数创建合理的线程池
        int processors = Runtime.getRuntime().availableProcessors();
        return Executors.newVirtualThreadPerTask();
    }
    
    // 使用虚拟线程处理I/O密集型任务
    public CompletableFuture<String> handleIoTask(String url) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                // 模拟网络请求或其他I/O操作
                Thread.sleep(1000);
                return "Response from " + url;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
        }, createOptimalVirtualThreadPool());
    }
    
    // 避免在虚拟线程中执行CPU密集型任务
    public void cpuIntensiveTask() {
        // 这种做法不推荐,应该使用平台线程
        CompletableFuture.runAsync(() -> {
            // CPU密集型计算
            long result = 0;
            for (int i = 0; i < 1000000; i++) {
                result += i * i;
            }
            System.out.println("CPU intensive result: " + result);
        });
    }
    
    // 使用虚拟线程进行批量处理
    public CompletableFuture<Void> batchProcess(List<String> tasks) {
        CompletableFuture<?>[] futures = tasks.stream()
            .map(task -> CompletableFuture.runAsync(() -> {
                try {
                    processTask(task);
                } catch (Exception e) {
                    System.err.println("Error processing task: " + task);
                    throw new RuntimeException(e);
                }
            }, createOptimalVirtualThreadPool()))
            .toArray(CompletableFuture[]::new);
        
        return CompletableFuture.allOf(futures);
    }
    
    private void processTask(String task) throws InterruptedException {
        // 模拟任务处理
        Thread.sleep(100);
        System.out.println("Processed: " + task);
    }
}

记录类的高级用法

import java.util.List;
import java.util.Map;
import java.util.Set;

public class RecordAdvancedUsage {
    
    // 嵌套记录类
    public record Address(String street, String city, String zipCode) {
        public Address {
            if (street == null || street.trim().isEmpty()) {
                throw new IllegalArgumentException("Street cannot be null or empty");
            }
            if (city == null || city.trim().isEmpty()) {
                throw new IllegalArgumentException("City cannot be null or empty");
            }
        }
    }
    
    public record PersonRecord(String name, int age, Address address) {
        public PersonRecord {
            if (name == null || name.trim().isEmpty()) {
                throw new IllegalArgumentException("Name cannot be null or empty");
            }
            if (age < 0) {
                throw new IllegalArgumentException("Age cannot be negative");
            }
        }
        
        // 记录类方法
        public String getFullAddress() {
            return address.street() + ", " + address.city() + 
                   ", " + address.zipCode();
        }
    }
    
    // 带有静态工厂方法的记录类
    public record ProductRecord(String id, String name, double price) {
        public ProductRecord {
            if (id == null || id.trim().isEmpty()) {
                throw new IllegalArgumentException("ID cannot be null or empty");
            }
            if (name == null || name.trim().isEmpty()) {
                throw new IllegalArgumentException("Name cannot be null or empty");
            }
            if (price < 0) {
                throw new IllegalArgumentException("Price cannot be negative");
            }
        }
        
        //
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000