
1. 问题分析:数据解析的陷阱
在处理文件输入时,开发者经常会遇到两种主要问题:
- NumberFormatException: 当尝试将包含非数字字符的字符串转换为数字类型(如 int 或 double)时,会抛出此异常。这通常是由于输入字符串中包含意外的空格、换行符或其他不可见字符。
- 数据分类失败: 即使数据被成功读取,如果分类逻辑(例如,基于字符串比较)不够健壮,也可能导致数据无法正确匹配到预期的类别,从而使相应的 ArrayList 保持为空。
在给定的电影信息处理案例中,原始代码在尝试解析电影年份和流派时遇到了上述问题。例如,当文件中的一行数据为 Schindler's List, 1994, War, R 时:
- movieInfo[1] 得到的是 " 1994",而非 "1994"。直接使用 Integer.parseInt(" 1994") 会导致 NumberFormatException。
- movieInfo[2] 得到的是 " War",而非 "War"。因此,genre.equals("War") 的判断始终为 false,导致所有电影都无法被正确分类到任何流派的 ArrayList 中。
这些问题的根源在于文件数据中的额外空格,而默认的 String.split(",") 方法无法自动处理这些空格。
2. 解决方案:数据清洗与健壮解析
为了解决上述问题,我们需要在数据读取和解析阶段进行必要的清洗。有两种主要方法可以实现这一点:
2.1 使用 String.trim() 方法
String.trim() 方法可以移除字符串两端的空白字符(包括空格、制表符、换行符等)。这是最直接且易于理解的方法。在解析每个字段后,立即调用 trim() 方法。
立即学习“Java免费学习笔记(深入)”;
// 原始代码:
// String[] movieInfo = movie.split(",");
// String title = movieInfo[0];
// int year = Integer.parseInt(movieInfo[1]);
// String genre = movieInfo[2];
// 改进后:
String[] movieInfo = movie.split(",");
String title = movieInfo[0].trim(); // 移除标题两端空格
int year = Integer.parseInt(movieInfo[1].trim()); // 移除年份两端空格后再转换
String genre = movieInfo[2].trim(); // 移除流派两端空格
String rating = movieInfo[3].trim(); // 移除评分两端空格通过在每个字段上调用 trim(),可以确保 Integer.parseInt() 接收到纯数字字符串,并且流派字符串与预定义的常量完全匹配。
2.2 使用正则表达式优化 String.split()
更优雅且推荐的方法是利用正则表达式来增强 split() 方法。我们可以让 split() 不仅在逗号处分割,还能自动忽略逗号后的任意数量的空白字符。
Delphi 7应用编程150例 CHM全书内容下载,全书主要通过150个实例,全面、深入地介绍了用Delphi 7开发应用程序的常用方法和技巧,主要讲解了用Delphi 7进行界面效果处理、图像处理、图形与多媒体开发、系统功能控制、文件处理、网络与数据库开发,以及组件应用等内容。这些实例简单实用、典型性强、功能突出,很多实例使用的技术稍加扩展可以解决同类问题。使用本书最好的方法是通过学习掌握实例中的技术或技巧,然后使用这些技术尝试实现更复杂的功能并应用到更多方面。本书主要针对具有一定Delphi基础知识
// 改进后:
// 使用正则表达式 ",\\s*"。
// "," 表示匹配一个逗号。
// "\\s*" 表示匹配零个或多个空白字符(空格、制表符、换行符等)。
String[] movieInfo = movie.split(",\\s*");
String title = movieInfo[0]; // 无需再trim,因为split已经处理了
int year = Integer.parseInt(movieInfo[1]); // 无需再trim
String genre = movieInfo[2]; // 无需再trim
String rating = movieInfo[3]; // 无需再trim这种方法简化了后续代码,因为每个 movieInfo 数组元素在被访问时已经去除了前导空白。
3. 数据排序:实现 Comparator
为了实现按年份对电影进行排序,我们需要定义一个 Comparator。Comparator 是一个函数式接口,用于定义两个对象之间的比较规则。
import java.util.Comparator; public class MovieComparator implements Comparator{ @Override public int compare(Movie m1, Movie m2) { // 按照年份升序排序 return Integer.compare(m1.getYearReleased(), m2.getYearReleased()); } }
有了 MovieComparator,就可以使用 ArrayList 的 sort() 方法对电影列表进行排序:
adventure.sort(new MovieComparator()); drama.sort(new MovieComparator()); // ... 对其他所有流派列表进行排序
4. 完整示例代码与改进
以下是整合了数据清洗和排序功能的完整 Java 代码示例。
4.1 MovieListing.txt 文件示例
假设 MovieListing.txt 文件内容如下(请注意逗号后的空格):
Steven Spielberg John Williams Schindler's List, 1994, War, R Amistad, 1997, Drama, R The Post, 2017, Drama, PG-13 E.T. the Extra-Terrestrial, 1982, Sci Fi, PG Jurassic Park, 1993, Sci Fi, PG-13
4.2 Director.java
public class Director {
private String directorName;
private String composerName;
public Director(String d, String c) {
this.directorName = d;
this.composerName = c;
}
public String getDirectorName() {
return directorName;
}
public void setDirectorName(String directorName) {
this.directorName = directorName;
}
public String getComposerName() {
return composerName;
}
public void setComposerName(String composerName) {
this.composerName = composerName;
}
}4.3 Movie.java
public class Movie extends Director { // 注意:Movie继承Director在面向对象设计上可能不合理,但此处保留原结构
private String title;
private int yearReleased;
private String genre;
private String rating;
public Movie(String title, int yearReleased, String genre, String rating, String directorName, String composerName) {
super(directorName, composerName);
this.title = title;
this.yearReleased = yearReleased;
this.genre = genre;
this.rating = rating;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getYearReleased() {
return yearReleased;
}
public void setYearReleased(int yearReleased) {
this.yearReleased = yearReleased;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
public String getRating() {
return rating;
}
public void setRating(String rating) {
this.rating = rating;
}
@Override
public String toString() { // 重写toString方便调试输出
return "Movie{" +
"title='" + title + '\'' +
", yearReleased=" + yearReleased +
", genre='" + genre + '\'' +
", rating='" + rating + '\'' +
", director='" + getDirectorName() + '\'' +
", composer='" + getComposerName() + '\'' +
'}';
}
}4.4 MovieComparator.java
import java.util.Comparator; public class MovieComparator implements Comparator{ @Override public int compare(Movie m1, Movie m2) { // 按照年份升序排序 return Integer.compare(m1.getYearReleased(), m2.getYearReleased()); } }
4.5 Driver.java (核心逻辑改进)
import java.util.ArrayList;
import java.util.Scanner;
import javax.swing.JOptionPane;
import java.io.*;
public class Driver {
public void start() throws FileNotFoundException {
// Initialize arraylists
ArrayList adventure = new ArrayList<>();
ArrayList drama = new ArrayList<>();
ArrayList fantasy = new ArrayList<>();
ArrayList romance = new ArrayList<>();
ArrayList sciFi = new ArrayList<>();
ArrayList thriller = new ArrayList<>();
ArrayList war = new ArrayList<>();
String directorName = "";
String composerName = "";
File myObj = new File("MovieListing.txt");
try (Scanner myReader = new Scanner(myObj)) { // 使用try-with-resources确保Scanner关闭
if (myReader.hasNextLine()) {
directorName = myReader.nextLine().trim(); // 读取并trim导演名
}
if (myReader.hasNextLine()) {
composerName = myReader.nextLine().trim(); // 读取并trim作曲家名
}
while (myReader.hasNextLine()) {
String movieLine = myReader.nextLine();
// 改进点:使用正则表达式 ",\\s*" 分割字符串,自动处理逗号后的空格
String[] movieInfo = movieLine.split(",\\s*");
if (movieInfo.length < 4) { // 简单的数据完整性检查
System.err.println("Skipping malformed line: " + movieLine);
continue;
}
String title = movieInfo[0];
int year = Integer.parseInt(movieInfo[1]);
String genre = movieInfo[2];
String rating = movieInfo[3];
Movie movie1 = new Movie(title, year, genre, rating, directorName, composerName);
// sort movies into arraylists
if (genre.equals("Adventure")) {
adventure.add(movie1);
} else if (genre.equals("Drama")) {
drama.add(movie1);
} else if (genre.equals("Fantasy")) {
fantasy.add(movie1);
} else if (genre.equals("Romance")) {
romance.add(movie1);
} else if (genre.equals("Sci Fi")) {
sciFi.add(movie1);
} else if (genre.equals("Thriller")) {
thriller.add(movie1);
} else if (genre.equals("War")) {
war.add(movie1);
}
}
} catch (FileNotFoundException e) {
System.err.println("Error: MovieListing.txt not found. " + e.getMessage());
throw e; // 重新抛出异常,让调用者处理
} catch (NumberFormatException e) {
System.err.println("Error parsing number: " + e.getMessage());
// 可以选择跳过该行或进行其他错误处理
}
// 调试输出,检查列表是否已填充
System.out.println("Adventure Movies: " + adventure);
System.out.println("War Movies: " + war); // 示例
// Ask the user which genre they would like to view
String genreChoice = JOptionPane.showInputDialog("Director: " + directorName + "\n" +
"Composer: " + composerName + "\n" +
"Which genre would you like? \n" +
"1. Adventure \n" +
"2. Drama \n" +
"3. Fantasy \n" +
"4. Romance \n" +
"5. SciFi \n" +
"6. Thriller \n" +
"7. War \n" +
"Your choice: ");
// Sort all movie lists by year released
MovieComparator movieComparator = new MovieComparator();
adventure.sort(movieComparator);
drama.sort(movieComparator);
fantasy.sort(movieComparator);
romance.sort(movieComparator);
sciFi.sort(movieComparator);
thriller.sort(movieComparator);
war.sort(movieComparator);
// Display the output dialog box
StringBuilder output = new StringBuilder();
output.append("Director: ").append(directorName).append("\n");
output.append("Composer: ").append(composerName).append("\n\n");
output.append("Genre: ");
ArrayList selectedGenreList = null;
String genreName = "";
switch (genreChoice) {
case "1":
selectedGenreList = adventure;
genreName = "Adventure";
break;
case "2":
selectedGenreList = drama;
genreName = "Drama";
break;
case "3":
selectedGenreList = fantasy;
genreName = "Fantasy";
break;
case "4":
selectedGenreList = romance;
genreName = "Romance";
break;
case "5":
selectedGenreList = sciFi;
genreName = "Sci Fi";
break;
case "6":
selectedGenreList = thriller;
genreName = "Thriller";
break;
case "7":
selectedGenreList = war;
genreName = "War";
break;
default:
JOptionPane.showMessageDialog(null, "Invalid genre choice.");
return;
}
output.append(genreName).append("\n\n");
output.append("Movie Title\tYear Released\tRating\n");
if (selectedGenreList != null) {
for (Movie movie : selectedGenreList) {
output.append(String.format("%-20s\t%-15d\t%s\n",
movie.getTitle(),
movie.getYearReleased(),
movie.getRating()));
}
}
JOptionPane.showMessageDialog(null, output.toString());
}
} 4.6 Main.java
import java.io.FileNotFoundException;
public class Main {
public static void main(String[] args) throws FileNotFoundException {
Driver driver = new Driver();
driver.start();
}
}5. 注意事项与总结
- 数据清洗的重要性: 在从外部源(如文件、网络)读取数据时,始终假定数据可能不完全符合预期格式。对输入数据进行严格的验证和清洗是避免运行时错误的关键。String.trim() 和正则表达式是处理这类问题的强大工具。
- 异常处理: 在文件 I/O 和数据类型转换时,FileNotFoundException 和 NumberFormatException 是常见的受检异常。使用 try-catch 块进行适当的异常处理至关重要,这可以使程序更加健壮,并提供有用的错误信息。使用 try-with-resources 语句(如 try (Scanner myReader = new Scanner(myObj)))可以确保资源(如 Scanner)在使用完毕后自动关闭,避免资源泄漏。
- 面向对象设计: 原始代码中 Movie extends Director 的继承关系在语义上可能不完全符合现实世界模型(电影通常“有”一个导演,而不是“是”一个导演)。在实际项目中,更好的设计可能是让 Movie 类包含一个 Director 类型的成员变量(组合关系),而不是继承关系。然而,本文主要关注数据解析和排序,因此保留了原有结构。
- 格式化输出: 使用 String.format() 可以更灵活地控制输出字符串的格式,例如对齐文本、设置宽度等,以生成更美观的报告。
- 代码可读性: 适当的注释、有意义的变量名和清晰的代码结构对于维护和理解代码至关重要。
通过上述改进,我们不仅解决了 ArrayList 为空和 NumberFormatException 的问题,还使文件数据处理流程更加健壮和专业。这对于任何需要处理外部数据的 Java 应用来说都是基本且重要的实践。









