使用Apache POI处理.xlsx文件时遇“无法从字符串单元格获取数值”错误
解决Apache POI处理Excel时的"Cannot get a NUMERIC value from a STRING cell"错误
错误原因
你遇到的异常是因为Excel单元格的实际类型与调用的取值方法不匹配:比如sharePriceCell或shareBoughtCell中存在字符串类型的单元格(可能是单元格格式设为文本,或输入了非数字内容),但直接调用getNumericCellValue(),导致类型转换失败。
解决方案
需要根据单元格的实际类型,安全提取对应的值。可以编写通用工具方法处理不同类型的单元格,避免硬编码调用特定取值方法,同时优化线程逻辑提升健壮性。
修改后的完整代码
import java.io.FileInputStream; import org.apache.poi.ss.usermodel.*; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class DataSetTest { private static final String EXCEL_FILE_PATH = "C:/Users/assignment/src/dataset.xlsx"; private static final int NUM_THREADS = 4; public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(NUM_THREADS); Map<String, Double> userSpendingMap = new HashMap<>(); double totalMoneySpent = 0.0; try (InputStream fis = new FileInputStream(EXCEL_FILE_PATH); Workbook workbook = new XSSFWorkbook(fis)) { Sheet sheet = workbook.getSheetAt(0); // 跳过表头(根据你的Excel结构调整,无表头可删除这段) boolean isFirstRow = true; for (Row row : sheet) { if (isFirstRow) { isFirstRow = false; continue; } executorService.execute(() -> processTransaction(row, userSpendingMap)); } executorService.shutdown(); // 用awaitTermination替代循环sleep,更优雅高效 executorService.awaitTermination(1, java.util.concurrent.TimeUnit.MINUTES); for (Double amount : userSpendingMap.values()) { totalMoneySpent += amount; } System.out.println("User ID\tTotal Money Spent"); for (Map.Entry<String, Double> entry : userSpendingMap.entrySet()) { System.out.println(entry.getKey() + "\t" + entry.getValue()); } System.out.println("Total money spent by all users: " + totalMoneySpent); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } private static void processTransaction(Row row, Map<String, Double> userSpendingMap) { Cell userIdCell = row.getCell(1); Cell sharePriceCell = row.getCell(2); Cell shareBoughtCell = row.getCell(3); if (userIdCell == null || sharePriceCell == null || shareBoughtCell == null) { return; } // 安全获取各单元格的值 String userId = getCellStringValue(userIdCell); Double sharePrice = getCellNumericValue(sharePriceCell); Integer shareBought = getCellIntegerValue(shareBoughtCell); if (userId == null || sharePrice == null || shareBought == null) { return; } double transactionAmount = sharePrice * shareBought; synchronized (userSpendingMap) { userSpendingMap.put(userId, userSpendingMap.getOrDefault(userId, 0.0) + transactionAmount); } } // 工具方法:获取单元格的字符串值 private static String getCellStringValue(Cell cell) { if (cell == null) return null; switch (cell.getCellType()) { case STRING: return cell.getStringCellValue().trim(); case NUMERIC: // 数字类型转为字符串(适配数字格式的用户ID) return String.valueOf((long) cell.getNumericCellValue()); case BOOLEAN: return String.valueOf(cell.getBooleanCellValue()); case BLANK: return null; default: return null; } } // 工具方法:获取单元格的数值(Double类型) private static Double getCellNumericValue(Cell cell) { if (cell == null) return null; switch (cell.getCellType()) { case NUMERIC: return cell.getNumericCellValue(); case STRING: // 字符串类型的数字尝试转换为Double try { return Double.parseDouble(cell.getStringCellValue().trim()); } catch (NumberFormatException e) { System.err.println("无法将字符串转换为数字:" + cell.getStringCellValue()); return null; } case BLANK: return null; default: return null; } } // 工具方法:获取单元格的整数(Integer类型) private static Integer getCellIntegerValue(Cell cell) { Double numericValue = getCellNumericValue(cell); return numericValue != null ? numericValue.intValue() : null; } }
关键优化点
- 单元格类型安全处理:新增三个工具方法,根据
CellType分支处理,支持数字转字符串、字符串转数字的兼容场景,彻底避免类型不匹配异常。 - 表头跳过:默认跳过第一行(适配带表头的Excel结构,无表头可删除),避免处理表头数据导致错误。
- 优雅的线程等待:用
executorService.awaitTermination()替代循环Thread.sleep(),符合并发编程规范且更高效。 - 空值校验增强:在获取值后再次校验,避免后续计算出现空指针。
额外建议
- 频繁处理Excel时,可使用Apache POI提供的
DataFormatter类,它能自动格式化不同类型的单元格并返回字符串,再按需转换为对应类型。 - 考虑用
ConcurrentHashMap替代HashMap,可去掉synchronized块,提升并发性能。
内容的提问来源于stack exchange,提问作者QasyeefSunnymizer
相关产品推荐
相关产品推荐

