前言:
因为图片上敲了很多的页码,要求写个程序定位到页码所在位置并去除,翻了很多技术最后决定使用 PaddleOCR。图片如下:

如何在java 中使用参考了这位大佬的博客
软件安装
2.下载PaddleOCR
首先安装PaddlePaddlePaddleOCR依赖于PaddlePaddle
#CPU(一般都是cpu)
pip install paddlepaddle -i https://pypi.tuna.tsinghua.edu.cn/simple
我这里使用的是2.6.2版本 pip install paddlepaddle==2.6.2 -i https://pypi.tuna.tsinghua.edu.cn/simple
#如果是gpu
pip install paddlepaddle-gpu -i https://pypi.tuna.tsinghua.edu.cn/simple
安装 paddleocr
pip install paddleocr -i https://pypi.tuna.tsinghua.edu.cn/simple
安装完成后
可进入 cmd ,输入python 后,再将以下代码直接粘贴进去,查看是否能识别
from paddleocr import PaddleOCR, draw_ocr
# Paddleocr目前支持的多语言语种可以通过修改lang参数进行切换
# 例如`ch`, `en`, `fr`, `german`, `korean`, `japan`
ocr = PaddleOCR(use_angle_cls=True, lang="ch") # need to run only once to download and load model into memory
img_path = '你图片的地址'
result = ocr.ocr(img_path, cls=True)
for line in result:
print(line)
第一种实现方式
代码逻辑
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.benjaminwan.ocrlibrary.OcrResult;
import com.benjaminwan.ocrlibrary.Point;
import com.benjaminwan.ocrlibrary.TextBlock;
import com.xxx.common.utils.file.FileTypeUtils;
import io.github.mymonstercat.Model;
import io.github.mymonstercat.ocr.InferenceEngine;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 使用的是 pp飞浆ocr https://aistudio.baidu.com/modelsdetail/286?modelId=286
* 1.需要在电脑上安装python 这里推荐版本为 python-3.9.13-amd64
* 2.参照博客 https://blog.csdn.net/qq_41793039/article/details/134220607
*/
public class ImageOcrUtil {
//切割整体高度的6分之一
private static final int SUB_HEIGHT = 6;
//切割整体宽度的4分之一
private static final int SUB_WIDTH = 4;
public static void main(String[] args) {
smearingPage("E:\\新建文件夹\\1.jpg");
}
/**
* 涂抹页码
*
* @param path
* @return
*/
public static String smearingPage(String path) {
File file = new File(path);
if (!ImageUtil.isImage(file)) return path;
//获取父级路径
String parentPath = new File(path).getParent();
try {
//原始图片加载
BufferedImage originalImage = ImageIO.read(file);
//1.因为图片是固定A4大小的,且出现页码的位置固定在右上角的区域,为了快速识别,直接按比例切割指定区域图片
String slicingPath = parentPath + File.separator + IdWorker.getIdStr() + ".jpg";
slicing(originalImage, slicingPath);
//2.开始识别切割后的图片结果
InferenceEngine engine = InferenceEngine.getInstance(Model.ONNX_PPOCR_V4);
OcrResult ocrResult = engine.runOcr(slicingPath);
ArrayList<TextBlock> textBlocks = ocrResult.getTextBlocks();
List<PageOcr> ocrCoordinateList = getOcrCoordinate(originalImage, textBlocks);
//new File(slicingPath).delete();
//3.开始进行遮盖文字
if (CollectionUtil.isEmpty(ocrCoordinateList)) return path;
File sendFile = new File(parentPath + File.separator + "smearing-" + file.getName());
coverImage(originalImage, ocrCoordinateList, sendFile);
return sendFile.getPath();
} catch (Exception e) {
e.printStackTrace();
return path;
}
}
/**
* 过滤并计算出需要文字的坐标
*
* @param originalImage
* @param textBlocks
* @return
* @throws Exception
*/
public static List<PageOcr> getOcrCoordinate(BufferedImage originalImage, ArrayList<TextBlock> textBlocks) throws Exception {
//开始循环识别集,并获取识别结果
List<PageOcr> ocrCoordinateList = new ArrayList<>();
for (TextBlock textBlock : textBlocks) {
String text = textBlock.getText();
if (!checkText(text)) continue;
PageOcr pageOcr = new PageOcr();
//获取 x y 最大最小位置的坐标
ArrayList<Point> boxPrint = textBlock.getBoxPoint();
List<Integer> xList = boxPrint.stream().map(Point::getX).sorted().collect(Collectors.toList());
List<Integer> yList = boxPrint.stream().map(Point::getY).sorted().collect(Collectors.toList());
//获取需要绘制区域的宽度与高度
int g2dWidth = xList.get(xList.size() - 1) - xList.get(0);
int g2dHeight = yList.get(yList.size() - 1) - yList.get(0);
//经验,实际测试之后宽度小于32px的也不是需要的,可以根据自己想要的进行更改
if (g2dHeight < 32) continue;
// 读取原始图片,并计算起始位置
int staWidth = originalImage.getWidth() - (originalImage.getWidth() / SUB_WIDTH);
pageOcr.setX(xList.get(0) + staWidth); // 框的起始横坐标
pageOcr.setY(yList.get(0)); // 框的起始纵坐标
pageOcr.setWidth(g2dWidth);
pageOcr.setHeight(g2dHeight);
ocrCoordinateList.add(pageOcr);
}
return ocrCoordinateList;
}
/**
* 获取指定区域后进行覆盖
*
* @param originalImage
* @param ocrCoordinateList
* @param sendFile
* @throws Exception
*/
public static void coverImage(BufferedImage originalImage, List<PageOcr> ocrCoordinateList, File sendFile) throws Exception {
// 获取绘图工具
Graphics2D g2d = originalImage.createGraphics();
// 绘制白色框进行遮盖
g2d.setColor(Color.white);
for (PageOcr pageOcr : ocrCoordinateList) {
g2d.fillRect(pageOcr.getX(), pageOcr.getY(), pageOcr.getWidth(), pageOcr.getHeight());
}
// 释放绘图资源
g2d.dispose();
// 保存新图片
ImageIO.write(originalImage, FileTypeUtils.getFileType(sendFile), sendFile);
}
/**
* 切割指定区域图片图片
*
* @param originalImage
* @param outputPath
* @throws Exception
*/
public static void slicing(BufferedImage originalImage, String outputPath) throws Exception {
int subHeight = originalImage.getHeight() / SUB_HEIGHT;
int subWidth = originalImage.getWidth() / SUB_WIDTH;
int staWidth = originalImage.getWidth() - subWidth;
BufferedImage subImage = originalImage.getSubimage(staWidth, 0, subWidth, subHeight);
ImageIO.write(subImage, "jpg", new File(outputPath));
}
/**
* 过滤识别后的文字 是否是三位数的数字
*
* @param text
* @return
*/
public static boolean checkText(String text) {
if (text.length() > 4) return false;
if (text.equals("000")) return false;
text = text.replace("U", "0");
return isNumeric(text);
}
/**
* 判断是否都是数字
*
* @param str
* @return
*/
public static boolean isNumeric(String str) {
if (str == null || str.length() == 0) {
return false;
}
for (char c : str.toCharArray()) {
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
}
实体类
import lombok.Data;
@Data
public class PageOcr {
private int x;
private int y;
private int width;
private int height;
}
判断方法
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* 图片处理工具类
*
*/
public class ImageUtil {
/**
* 校验图片是否存在是否可读
* @param file
* @return
*/
public static boolean isImage(File file) {
try (FileInputStream fis = new FileInputStream(file);
FileChannel channel = fis.getChannel()) {
// 分配直接内存缓冲区
ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
// 读取文件内容到缓冲区
int bytesRead = channel.read(buffer);
if (bytesRead > 0) {
return true; // 文件可读
} else {
return false; // 文件为空或无法读取
}
} catch (Exception e) {
System.out.println("校验err:" + file.getPath());
return false; // 发生异常,文件不可读
}
}
}
效果展示
1.原图

2.截出来的图片

3.效果(这里使用了黑色快,方便查看)

注意:
当我把项目打包部署到其他地方时一直在报错,报错信息如下
Model dbnet file not found: C:\Users\ADMINI~1\AppData\Local\Temp\1\ocrJava/onnx/ch_PP-OCRv4_det_infer.onnx.param
17:52:14.884 [http-nio-8115-exec-2] INFO c.b.o.OcrEngine - [initEngine,29] - 推理引擎初始化完成,当前使用的推理引擎为:onnx-v1.1.1
17:52:14.884 [http-nio-8115-exec-2] INFO c.b.o.OcrEngine - [initEngine,30] - 初始化时模型配置为:Model.ONNX_PPOCR_V4(modelsDir=/models, modelType=onnx, detName=ch_PP-OCRv4_det_infer.onnx, clsName=ch_ppocr_mobile_v2.0_cls_infer.onnx, recName=ch_PP-OCRv4_rec_infer.onnx, keysName=ppocr_keys_v1.txt), 硬件配置为:HardwareConfig(numThread=4, gpuIndex=-1)
17:52:14.885 [http-nio-8115-exec-2] INFO i.g.m.o.InferenceEngine - [runOcr,57] - 图片路径:D:\图片\1888163925386035201.jpg, 参数配置:ParamConfig(padding=50, maxSideLen=0, boxScoreThresh=0.5, boxThresh=0.3, unClipRatio=1.6, doAngle=false, mostAngle=false)
imgDir=, imgName=D:\图片\1888163925386035201.jpg
find_blob_index_by_name input failed
Try
find_blob_index_by_name output failed
Try
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007fffb6dc694b, pid=5140, tid=0x0000000000002334
#
# JRE version: Java(TM) SE Runtime Environment (8.0_291-b10) (build 1.8.0_291-b10)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.291-b10 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# C [RapidOcr.dll+0x7b694b]
#
# Core dump written. Default location: C:\Home\Project\hs_err_pid5140.mdmp
#
# An error report file with more information is saved as:
# C:\Home\Project\hs_err_pid5140.log
#
# If you would like to submit a bug report, please visit:
# http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
产生原因是我打包部署到其他服务器后系统temp文件夹下不存在需要的文件(需要文件如下)

逐步排查中!!!
注意调用该方法时控制台打印的
OcrResult ocrResult = engine.runOcr(slicingPath);
#打印内容
当前库加载器: OnnxWindowsX8664LibraryLoader
将文件models/ch_PP-OCRv3_det_infer.onnx复制到/onnx,加载此文件:false,JVM退出时删除此文件:false
将文件models/ch_ppocr_mobile_v2.0_cls_infer.onnx复制到/onnx,加载此文件:false,JVM退出时删除此文件:false
将文件models/ch_PP-OCRv3_rec_infer.onnx复制到/onnx,加载此文件:false,JVM退出时删除此文件:false
将文件models/ppocr_keys_v1.txt复制到/onnx,加载此文件:false,JVM退出时删除此文件:false
跟着debug走一下,最后会走到
JarFileUtil这个类
会看到如下代码

会将这几个包下的文件复制到临时文件

所以就直接把自己电脑的C:\Users\xxxx\AppData\Local\Temp\ocrJava\这个包下的丢到服务器的临时文件夹里就行了。
第二种使用java调用python 文件来进行获取返回 ( 巨慢巨慢巨慢!!!!!)
1.新建 imageOcr.py文件,并把代码复制进去
from paddleocr import PaddleOCR
import sys
def ocrImageData(path):
# 初始化 PaddleOCR 对象
ocr = PaddleOCR(use_angle_cls=True, lang='ch')
ocr_data = [];
# 进行文字识别
result = ocr.ocr(path, cls=True)
for idx in range(len(result)):
res = result[idx]
for line in res:
ocr_data.append(line)
return ocr_data
if __name__ == "__main__":
# 从命令行参数中获取数字
path = sys.argv[1]
result = ocrImageData(path)
print('--splitImage--')
print(result) # 输出结果
可以在cmd窗口执行,
python [路径]\imageOcr.py [要识别图片的地址]
如:
python C:\Users\xxx\Desktop\1\imageOcr.py C:\Users\xxx\2.jpg
识别图片

返回结果:

2.在java内进行调用获取 直接上完整代码
package com.xxx.common.utils.image;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.benjaminwan.ocrlibrary.OcrResult;
import com.benjaminwan.ocrlibrary.Point;
import com.benjaminwan.ocrlibrary.TextBlock;
import com.xxx.common.utils.StringUtils;
import com.xxx.common.utils.file.FileTypeUtils;
import io.github.mymonstercat.Model;
import io.github.mymonstercat.ocr.InferenceEngine;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 去除页码
* 使用的是 pp飞浆ocr https://aistudio.baidu.com/modelsdetail/286?modelId=286
* 1.需要在电脑上安装python 这里推荐版本为 python-3.9.13-amd64
* 2.参照博客 https://blog.csdn.net/qq_41793039/article/details/134220607
*/
public class ImageOcrUtil {
/**
* 识别类型
*/
public static final String PA_OCR = "paddleOCR";
public static final String PY_OCR = "pythonOCR";
//切割整体高度的6分之一
private static final int SUB_HEIGHT = 6;
//切割整体宽度的4分之一 17:43:05.379
private static final int SUB_WIDTH = 4;
//.py文件的地址
private static final String PYTHON_PATH = "C:\\xxxxx\\imageOcr.py";
private static final Logger log = LoggerFactory.getLogger(ImageOcrUtil.class);
public static void main(String[] args) {
File file = new File("E:\\图片集");
for (File listFile : file.listFiles()) {
smearingPage(listFile.getPath(), PY_OCR);
}
}
/**
* 涂抹页码
*
* @param path
* @return
*/
public static String smearingPage(String path, String type) {
File file = new File(path);
if (!ImageUtil.isImage(file)) return path;
//获取父级路径
String parentPath = new File(path).getParent();
try {
//原始图片加载
BufferedImage originalImage = ImageIO.read(file);
//1.因为图片是固定A4大小的,且出现页码的位置固定在右上角的区域,为了快速识别,直接按比例切割指定区域图片
String slicingPath = parentPath + File.separator + IdWorker.getIdStr() + ".jpg";
slicing(originalImage, slicingPath);
//2.开始识别切割后的图片结果
List<PageOcr> ocrCoordinateList = new ArrayList<>();
if (StringUtils.equals(type, ImageOcrUtil.PA_OCR)) {
InferenceEngine engine = InferenceEngine.getInstance(Model.ONNX_PPOCR_V3);
OcrResult ocrResult = engine.runOcr(slicingPath);
ArrayList<TextBlock> textBlocks = ocrResult.getTextBlocks();
ocrCoordinateList.addAll(getOcrCoordinate(originalImage, textBlocks));
}
if (StringUtils.equals(type, ImageOcrUtil.PY_OCR)) {
ocrCoordinateList.addAll(getPythonOcrCoordinate(originalImage, slicingPath));
}
new File(slicingPath).delete();
//3.开始进行遮盖文字
if (CollectionUtil.isEmpty(ocrCoordinateList)) return path;
File sendFile = new File(parentPath + File.separator + "smearing-" + file.getName());
coverImage(originalImage, ocrCoordinateList, sendFile);
return sendFile.getPath();
} catch (Exception e) {
System.out.println("错误信息是:" + e.getMessage());
e.printStackTrace();
return path;
}
}
/**
* 过滤并计算出需要文字的坐标
*
* @param originalImage
* @param textBlocks
* @return
* @throws Exception
*/
public static List<PageOcr> getOcrCoordinate(BufferedImage originalImage, ArrayList<TextBlock> textBlocks) throws Exception {
//开始循环识别集,并获取识别结果
List<PageOcr> ocrCoordinateList = new ArrayList<>();
for (TextBlock textBlock : textBlocks) {
String text = textBlock.getText();
if (!checkText(text)) continue;
PageOcr pageOcr = new PageOcr();
//获取 x y 最大最小位置的坐标
ArrayList<Point> boxPrint = textBlock.getBoxPoint();
List<Integer> xList = boxPrint.stream().map(Point::getX).sorted().collect(Collectors.toList());
List<Integer> yList = boxPrint.stream().map(Point::getY).sorted().collect(Collectors.toList());
//获取需要绘制区域的宽度与高度
int g2dWidth = xList.get(xList.size() - 1) - xList.get(0);
int g2dHeight = yList.get(yList.size() - 1) - yList.get(0);
//经验,实际测试之后宽度小于32px的也不是需要的,可以根据自己想要的进行更改
if (g2dHeight < 32) continue;
// 读取原始图片,并计算起始位置
int staWidth = originalImage.getWidth() - (originalImage.getWidth() / SUB_WIDTH);
pageOcr.setX(xList.get(0) + staWidth); // 框的起始横坐标
pageOcr.setY(yList.get(0)); // 框的起始纵坐标
pageOcr.setWidth(g2dWidth);
pageOcr.setHeight(g2dHeight);
ocrCoordinateList.add(pageOcr);
}
return ocrCoordinateList;
}
/**
* 获取指定区域后进行覆盖
*
* @param originalImage
* @param ocrCoordinateList
* @param sendFile
* @throws Exception
*/
public static void coverImage(BufferedImage originalImage, List<PageOcr> ocrCoordinateList, File sendFile) throws Exception {
// 获取绘图工具
Graphics2D g2d = originalImage.createGraphics();
// 绘制白色框进行遮盖
g2d.setColor(Color.white);
for (PageOcr pageOcr : ocrCoordinateList) {
g2d.fillRect(pageOcr.getX(), pageOcr.getY(), pageOcr.getWidth(), pageOcr.getHeight());
}
// 释放绘图资源
g2d.dispose();
// 保存新图片
ImageIO.write(originalImage, FileTypeUtils.getFileType(sendFile), sendFile);
}
/**
* 切割指定区域图片图片
*
* @param originalImage
* @param outputPath
* @throws Exception
*/
public static void slicing(BufferedImage originalImage, String outputPath) throws Exception {
int subHeight = originalImage.getHeight() / SUB_HEIGHT;
int subWidth = originalImage.getWidth() / SUB_WIDTH;
int staWidth = originalImage.getWidth() - subWidth;
BufferedImage subImage = originalImage.getSubimage(staWidth, 0, subWidth, subHeight);
ImageIO.write(subImage, "jpg", new File(outputPath));
}
/**
* 过滤识别后的文字 是否是三位数的数字
*
* @param text
* @return
*/
public static boolean checkText(String text) {
if (text.length() > 4) return false;
if (text.equals("000")) return false;
text = text.replace("U", "0");
return isNumeric(text);
}
/**
* 判断是否都是数字
*
* @param str
* @return
*/
public static boolean isNumeric(String str) {
if (str == null || str.length() == 0) {
return false;
}
for (char c : str.toCharArray()) {
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
//-------------------------------------------------------------
/**
* 通过python 识别
*
* @param originalImage
* @param path
* @return
* @throws Exception
*/
public static List<PageOcr> getPythonOcrCoordinate(BufferedImage originalImage, String path) throws Exception {
log.info("开始识别,{}", path);
try {
// 要调用的Python脚本路径
String pythonScriptPath = PYTHON_PATH;
// 传递给Python脚本的参数
String inputNumber = path;
// 创建ProcessBuilder
ProcessBuilder processBuilder = new ProcessBuilder("python", pythonScriptPath, inputNumber);
processBuilder.redirectErrorStream(true); // 合并错误输出与正常输出
// 启动进程
Process process = processBuilder.start();
// 读取Python脚本的输出
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
String line;
StringBuilder output = new StringBuilder();
while ((line = reader.readLine()) != null) {
output.append(line);
}
// 等待进程结束并获取退出码
int exitCode = process.waitFor();
if (exitCode == 0) {
// 输出结果
String s = String.valueOf(output);
String[] split = s.split("--splitImage--");
return analysisPythonOcr(originalImage, split[1]);
}
} catch (Exception e) {
e.printStackTrace();
}
return Collections.emptyList();
}
/**
* 解析识别后的数据
*
* @param originalImage
* @param input
* @return
*/
public static List<PageOcr> analysisPythonOcr(BufferedImage originalImage, String input) {
List<DataStructure> dataStructures = parseInput(input);
//开始循环识别集,并获取识别结果
List<PageOcr> ocrCoordinateList = new ArrayList<>();
for (DataStructure data : dataStructures) {
String text = data.getTuple().getCode();
if (!checkText(text)) continue;
PageOcr pageOcr = new PageOcr();
//获取 x y 最大最小位置的坐标
List<Integer> xList = data.getPoints().stream().map(x -> x.get(0).intValue()).sorted().collect(Collectors.toList());
List<Integer> yList = data.getPoints().stream().map(x -> x.get(1).intValue()).sorted().collect(Collectors.toList());
//获取需要绘制区域的宽度与高度
int g2dWidth = xList.get(xList.size() - 1) - xList.get(0);
int g2dHeight = yList.get(yList.size() - 1) - yList.get(0);
//经验,实际测试之后宽度小于32px的也不是需要的,可以根据自己想要的进行更改
if (g2dHeight < 32) continue;
// 读取原始图片,并计算起始位置
int staWidth = originalImage.getWidth() - (originalImage.getWidth() / SUB_WIDTH);
pageOcr.setX(xList.get(0) + staWidth); // 框的起始横坐标
pageOcr.setY(yList.get(0)); // 框的起始纵坐标
pageOcr.setWidth(g2dWidth);
pageOcr.setHeight(g2dHeight);
ocrCoordinateList.add(pageOcr);
}
return ocrCoordinateList;
}
// 解析整个输入字符串
private static List<DataStructure> parseInput(String input) {
List<DataStructure> result = new ArrayList<>();
// 使用正则表达式匹配每个数据块
Pattern pattern = Pattern.compile("\\[\\[(.*?)\\]\\], \\((.*?)\\)");
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
// 解析二维坐标
String pointsStr = matcher.group(1);
List<List<Double>> points = parsePoints(pointsStr);
// 解析元组
String tupleStr = matcher.group(2);
Tuple tuple = parseTuple(tupleStr);
// 封装为 DataStructure 对象
result.add(new DataStructure(points, tuple));
}
return result;
}
// 解析二维坐标
private static List<List<Double>> parsePoints(String pointsStr) {
List<List<Double>> result = new ArrayList<>();
// 分割每个坐标点
String[] points = pointsStr.split("\\], \\[");
for (String point : points) {
List<Double> coordinates = new ArrayList<>();
String[] nums = point.replaceAll("[\\[\\]]", "").split(",\\s*");
for (String num : nums) {
coordinates.add(Double.parseDouble(num));
}
result.add(coordinates);
}
return result;
}
// 解析元组
private static Tuple parseTuple(String tupleStr) {
String[] parts = tupleStr.split(",\\s*");
String code = parts[0].replaceAll("['\"]", ""); // 去除引号
double value = Double.parseDouble(parts[1]);
return new Tuple(code, value);
}
}
// 自定义数据结构类
@Data
class DataStructure {
private final List<List<Double>> points;
private final Tuple tuple;
public DataStructure(List<List<Double>> points, Tuple tuple) {
this.points = points;
this.tuple = tuple;
}
public List<List<Double>> getPoints() {
return points;
}
public Tuple getTuple() {
return tuple;
}
}
// 自定义元组类
@Data
class Tuple {
private final String code;
private final double value;
public Tuple(String code, double value) {
this.code = code;
this.value = value;
}
@Override
public String toString() {
return String.format("(%s, %f)", code, value);
}
}

6182

被折叠的 条评论
为什么被折叠?



