Browse Source

Add w5 to w11 projects: shape inheritance, animal interface, exception handling, generics, maven project, repository with analyze command, exception hierarchy with retry

main
Luojiale 3 weeks ago
parent
commit
3557013fe8
  1. 135
      w10/ARCHITECTURE_AUDIT.md
  2. BIN
      w10/AnalyzeCommand.class
  3. 85
      w10/AnalyzeCommand.java
  4. BIN
      w10/Article.class
  5. 65
      w10/Article.java
  6. BIN
      w10/ArticleAnalyzer.class
  7. 21
      w10/ArticleAnalyzer.java
  8. BIN
      w10/ArticleCountAnalyzer.class
  9. 18
      w10/ArticleCountAnalyzer.java
  10. BIN
      w10/ArticleRepository.class
  11. 73
      w10/ArticleRepository.java
  12. BIN
      w10/TestMain.class
  13. 95
      w10/TestMain.java
  14. BIN
      w10/TitleLengthAnalyzer.class
  15. 44
      w10/TitleLengthAnalyzer.java
  16. BIN
      w11/ExceptionDemo.class
  17. 54
      w11/ExceptionDemo.java
  18. BIN
      w11/NetworkException.class
  19. 9
      w11/NetworkException.java
  20. 48
      w11/README.md
  21. BIN
      w11/RetryUtils.class
  22. 36
      w11/RetryUtils.java
  23. BIN
      w11/UrlFormatException.class
  24. 17
      w11/UrlFormatException.java
  25. BIN
      w11/UrlValidator.class
  26. 16
      w11/UrlValidator.java
  27. BIN
      w5/Circle.class
  28. 6
      w5/Circle.java
  29. BIN
      w5/Rectangle.class
  30. 6
      w5/Rectangle.java
  31. BIN
      w5/Shape.class
  32. 5
      w5/Shape.java
  33. BIN
      w5/ShapeTest.class
  34. 24
      w5/ShapeTest.java
  35. BIN
      w6/Animal.class
  36. 3
      w6/Animal.java
  37. BIN
      w6/AnimalTest.class
  38. 27
      w6/AnimalTest.java
  39. BIN
      w6/Cat.class
  40. 6
      w6/Cat.java
  41. BIN
      w6/Dog.class
  42. 11
      w6/Dog.java
  43. BIN
      w6/Swimmable.class
  44. 3
      w6/Swimmable.java
  45. BIN
      w7/Account.class
  46. 19
      w7/Account.java
  47. BIN
      w7/AccountTest.class
  48. 24
      w7/AccountTest.java
  49. BIN
      w7/InsufficientFundsException.class
  50. 12
      w7/InsufficientFundsException.java
  51. BIN
      w8/Box.class
  52. 26
      w8/Box.java
  53. BIN
      w8/GenericDemo.class
  54. 51
      w8/GenericDemo.java
  55. BIN
      w8/GenericUtils.class
  56. 24
      w8/GenericUtils.java
  57. BIN
      w8/Pair.class
  58. 30
      w8/Pair.java
  59. 54
      w8/README.md
  60. 127
      w9/README.md
  61. 167
      w9/RUNNING.md
  62. 40
      w9/pom.xml
  63. 43
      w9/src/main/java/com/crawler/Main.java
  64. 6
      w9/src/main/java/com/crawler/command/Command.java
  65. 36
      w9/src/main/java/com/crawler/command/CrawlCommand.java
  66. 24
      w9/src/main/java/com/crawler/command/ExitCommand.java
  67. 21
      w9/src/main/java/com/crawler/command/HelpCommand.java
  68. 25
      w9/src/main/java/com/crawler/command/ListCommand.java
  69. 39
      w9/src/main/java/com/crawler/controller/CommandController.java
  70. 55
      w9/src/main/java/com/crawler/model/Article.java
  71. 60
      w9/src/main/java/com/crawler/view/ConsoleView.java
  72. 10
      w9/test.bat

135
w10/ARCHITECTURE_AUDIT.md

@ -0,0 +1,135 @@
# AI 架构审计:策略模式解耦与封装审查
## 一、类签名汇总
### 1. Article(数据模型)
```java
public class Article {
private String title;
private String url;
private String content;
private long timestamp;
public Article(String title, String url) // 防御性参数校验
public void setTitle(String title) // 防御性参数校验
public void setUrl(String url) // 防御性参数校验
// Getter方法
}
```
### 2. ArticleRepository(仓储层)
```java
public class ArticleRepository {
private final List<Article> articles;
public void add(Article article) // 防御 null
public void addAll(Collection<Article>) // 防御 null 和空集合
public List<Article> getAll() // 返回不可变视图
public Article findByTitle(String) // 防御 null
public int count()
public void clear()
public boolean contains(Article) // 防御 null
}
```
### 3. ArticleAnalyzer(策略接口)
```java
public interface ArticleAnalyzer {
Map<String, Object> analyze(List<Article> articles);
String getName();
}
```
### 4. ArticleCountAnalyzer(具体策略1)
```java
public class ArticleCountAnalyzer implements ArticleAnalyzer {
public Map<String, Object> analyze(List<Article>);
public String getName();
}
```
### 5. TitleLengthAnalyzer(具体策略2)
```java
public class TitleLengthAnalyzer implements ArticleAnalyzer {
public Map<String, Object> analyze(List<Article>);
public String getName();
}
```
### 6. AnalyzeCommand(命令类)
```java
public class AnalyzeCommand {
private final List<Article> articles;
private final List<ArticleAnalyzer> analyzers;
public AnalyzeCommand(List<Article>, List<ArticleAnalyzer>);
public void execute(); // 复用策略解析,不存储结果
public void addAnalyzer(ArticleAnalyzer);
}
```
## 二、AI 架构审计请求
> 作为 Java 架构师,请审查我的代码设计:
>
> 1. **策略解耦**
> - ArticleAnalyzer 接口是否定义清晰?
> - 具体策略(ArticleCountAnalyzer、TitleLengthAnalyzer)是否与上下文解耦?
> - 添加新策略是否无需修改现有代码?
>
> 2. **封装性**
> - ArticleRepository 的内部状态是否得到保护?
> - getAll() 返回不可变视图是否正确?
> - 防御性编程是否过度或不足?
>
> 3. **单一职责**
> - AnalyzeCommand 是否只负责协调执行,不承担存储职责?
> - 各分析器是否只关注单一分析维度?
>
> 4. **依赖注入**
> - 依赖是否通过构造函数注入?
> - 是否符合依赖倒置原则?
>
> 请给出改进建议和优化方案。
## 三、设计原则应用
### 已应用的设计原则
| 原则 | 应用位置 | 说明 |
|------|---------|------|
| 单一职责 | ArticleAnalyzer 接口 | 每个分析器只负责一种分析 |
| 开闭原则 | 策略模式 | 添加新分析器无需修改现有代码 |
| 依赖倒置 | AnalyzeCommand | 依赖抽象接口而非具体实现 |
| 里氏替换 | 策略实现 | 所有分析器可互换使用 |
### 防御性编程
| 防御点 | 位置 | 方式 |
|--------|------|------|
| null检查 | Article构造器 | IllegalArgumentException |
| null检查 | Repository.add() | Objects.requireNonNull |
| null检查 | Repository.addAll() | 遍历检查每个元素 |
| 不可变返回 | Repository.getAll() | Collections.unmodifiableList |
## 四、文件列表
- [Article.java](file:///D:\嘻嘻哈哈\Git\java\w10\Article.java)
- [ArticleRepository.java](file:///D:\嘻嘻哈哈\Git\java\w10\ArticleRepository.java)
- [ArticleAnalyzer.java](file:///D:\嘻嘻哈哈\Git\java\w10\ArticleAnalyzer.java)
- [ArticleCountAnalyzer.java](file:///D:\嘻嘻哈哈\Git\java\w10\ArticleCountAnalyzer.java)
- [TitleLengthAnalyzer.java](file:///D:\嘻嘻哈哈\Git\java\w10\TitleLengthAnalyzer.java)
- [AnalyzeCommand.java](file:///D:\嘻嘻哈哈\Git\java\w10\AnalyzeCommand.java)
- [TestMain.java](file:///D:\嘻嘻哈哈\Git\java\w10\TestMain.java)
## 五、运行方式
```bash
# 编译
javac Article.java ArticleRepository.java ArticleAnalyzer.java \
ArticleCountAnalyzer.java TitleLengthAnalyzer.java \
AnalyzeCommand.java TestMain.java
# 运行
java TestMain
```

BIN
w10/AnalyzeCommand.class

Binary file not shown.

85
w10/AnalyzeCommand.java

@ -0,0 +1,85 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 分析命令类
* 复用策略解析但不存储只输出统计信息
*/
public class AnalyzeCommand {
private final List<Article> articles;
private final List<ArticleAnalyzer> analyzers;
/**
* 构造函数接收文章列表和分析器列表
* @param articles 要分析的文章列表
* @param analyzers 分析器列表
*/
public AnalyzeCommand(List<Article> articles, List<ArticleAnalyzer> analyzers) {
Objects.requireNonNull(articles, "文章列表不能为null");
Objects.requireNonNull(analyzers, "分析器列表不能为null");
this.articles = new ArrayList<>(articles);
this.analyzers = new ArrayList<>(analyzers);
}
/**
* 执行分析并输出结果不存储
*/
public void execute() {
System.out.println("================================================");
System.out.println(" 文章分析报告");
System.out.println("================================================");
System.out.println("分析文章数量: " + articles.size());
System.out.println("分析器数量: " + analyzers.size());
System.out.println("------------------------------------------------");
for (ArticleAnalyzer analyzer : analyzers) {
System.out.println("\n【" + analyzer.getName() + "】");
Map<String, Object> result = analyzer.analyze(articles);
// 输出分析结果(不存储)
for (Map.Entry<String, Object> entry : result.entrySet()) {
if (!"analyzer".equals(entry.getKey())) {
System.out.println(" " + formatKey(entry.getKey()) + ": " + formatValue(entry.getValue()));
}
}
}
System.out.println("\n================================================");
System.out.println(" 分析报告结束");
System.out.println("================================================");
}
/**
* 格式化键名
*/
private String formatKey(String key) {
switch (key) {
case "totalCount": return "文章总数";
case "averageLength": return "平均标题长度";
case "maxLength": return "最长标题长度";
case "minLength": return "最短标题长度";
default: return key;
}
}
/**
* 格式化值
*/
private String formatValue(Object value) {
if (value instanceof Double) {
return String.format("%.2f", value);
}
return value.toString();
}
/**
* 添加分析器
*/
public void addAnalyzer(ArticleAnalyzer analyzer) {
Objects.requireNonNull(analyzer, "分析器不能为null");
analyzers.add(analyzer);
}
}

BIN
w10/Article.class

Binary file not shown.

65
w10/Article.java

@ -0,0 +1,65 @@
public class Article {
private String title;
private String url;
private String content;
private long timestamp;
public Article(String title, String url) {
if (title == null || title.trim().isEmpty()) {
throw new IllegalArgumentException("标题不能为空");
}
if (url == null || url.trim().isEmpty()) {
throw new IllegalArgumentException("URL不能为空");
}
this.title = title;
this.url = url;
this.timestamp = System.currentTimeMillis();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
if (title == null || title.trim().isEmpty()) {
throw new IllegalArgumentException("标题不能为空");
}
this.title = title;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
if (url == null || url.trim().isEmpty()) {
throw new IllegalArgumentException("URL不能为空");
}
this.url = url;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
@Override
public String toString() {
return "Article{" +
"title='" + title + '\'' +
", url='" + url + '\'' +
", timestamp=" + timestamp +
'}';
}
}

BIN
w10/ArticleAnalyzer.class

Binary file not shown.

21
w10/ArticleAnalyzer.java

@ -0,0 +1,21 @@
import java.util.List;
import java.util.Map;
/**
* 文章分析策略接口
* 策略模式定义分析算法的接口不同的分析算法可以独立变化
*/
public interface ArticleAnalyzer {
/**
* 分析文章并返回统计结果
* @param articles 文章列表
* @return 统计结果映射
*/
Map<String, Object> analyze(List<Article> articles);
/**
* 获取分析器名称
* @return 分析器名称
*/
String getName();
}

BIN
w10/ArticleCountAnalyzer.class

Binary file not shown.

18
w10/ArticleCountAnalyzer.java

@ -0,0 +1,18 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ArticleCountAnalyzer implements ArticleAnalyzer {
@Override
public Map<String, Object> analyze(List<Article> articles) {
Map<String, Object> result = new HashMap<>();
result.put("analyzer", getName());
result.put("totalCount", articles.size());
return result;
}
@Override
public String getName() {
return "文章数量分析器";
}
}

BIN
w10/ArticleRepository.class

Binary file not shown.

73
w10/ArticleRepository.java

@ -0,0 +1,73 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class ArticleRepository {
private final List<Article> articles;
public ArticleRepository() {
this.articles = new ArrayList<>();
}
/**
* 添加单个文章防御 null
*/
public void add(Article article) {
Objects.requireNonNull(article, "文章不能为null");
articles.add(article);
}
/**
* 添加多个文章防御 null 和空集合
*/
public void addAll(Collection<Article> articlesToAdd) {
Objects.requireNonNull(articlesToAdd, "文章集合不能为null");
for (Article article : articlesToAdd) {
Objects.requireNonNull(article, "集合中包含null文章");
articles.add(article);
}
}
/**
* 获取所有文章的不可变视图
*/
public List<Article> getAll() {
return Collections.unmodifiableList(articles);
}
/**
* 根据标题查找文章
*/
public Article findByTitle(String title) {
Objects.requireNonNull(title, "标题不能为null");
return articles.stream()
.filter(a -> title.equals(a.getTitle()))
.findFirst()
.orElse(null);
}
/**
* 获取文章数量
*/
public int count() {
return articles.size();
}
/**
* 清空所有文章
*/
public void clear() {
articles.clear();
}
/**
* 检查是否包含指定文章
*/
public boolean contains(Article article) {
Objects.requireNonNull(article, "文章不能为null");
return articles.contains(article);
}
}

BIN
w10/TestMain.class

Binary file not shown.

95
w10/TestMain.java

@ -0,0 +1,95 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TestMain {
public static void main(String[] args) {
System.out.println("===== 测试 ArticleRepository =====");
testRepository();
System.out.println("\n===== 测试 AnalyzeCommand =====");
testAnalyzeCommand();
System.out.println("\n===== 测试防御性编程 =====");
testDefensiveProgramming();
}
private static void testRepository() {
ArticleRepository repo = new ArticleRepository();
Article article1 = new Article("Java编程入门", "https://example.com/java");
Article article2 = new Article("Python数据分析", "https://example.com/python");
Article article3 = new Article("JavaScript高级", "https://example.com/js");
// 测试添加单个文章
repo.add(article1);
repo.add(article2);
System.out.println("添加2篇文章后数量: " + repo.count());
// 测试添加多个文章
repo.addAll(Arrays.asList(article3));
System.out.println("批量添加后数量: " + repo.count());
// 测试查找
Article found = repo.findByTitle("Java编程入门");
System.out.println("查找结果: " + (found != null ? found.getTitle() : "未找到"));
// 测试获取所有文章(不可变视图)
List<Article> all = repo.getAll();
System.out.println("获取文章列表大小: " + all.size());
}
private static void testAnalyzeCommand() {
// 创建示例文章
List<Article> articles = new ArrayList<>();
articles.add(new Article("Java编程入门教程", "https://example.com/java"));
articles.add(new Article("Python数据分析实战", "https://example.com/python"));
articles.add(new Article("JavaScript高级编程", "https://example.com/js"));
articles.add(new Article("Spring Boot指南", "https://example.com/spring"));
articles.add(new Article("Docker容器化部署", "https://example.com/docker"));
// 创建分析器列表
List<ArticleAnalyzer> analyzers = new ArrayList<>();
analyzers.add(new ArticleCountAnalyzer());
analyzers.add(new TitleLengthAnalyzer());
// 创建并执行分析命令
AnalyzeCommand command = new AnalyzeCommand(articles, analyzers);
command.execute();
}
private static void testDefensiveProgramming() {
ArticleRepository repo = new ArticleRepository();
try {
repo.add(null);
System.out.println("ERROR: 应该抛出异常!");
} catch (NullPointerException e) {
System.out.println("正确: 成功捕获 null 文章异常");
}
try {
repo.addAll(null);
System.out.println("ERROR: 应该抛出异常!");
} catch (NullPointerException e) {
System.out.println("正确: 成功捕获 null 集合异常");
}
try {
List<Article> listWithNull = new ArrayList<>();
listWithNull.add(new Article("Test", "https://test.com"));
listWithNull.add(null);
repo.addAll(listWithNull);
System.out.println("ERROR: 应该抛出异常!");
} catch (NullPointerException e) {
System.out.println("正确: 成功捕获集合中包含 null 异常");
}
try {
new Article(null, "https://test.com");
System.out.println("ERROR: 应该抛出异常!");
} catch (IllegalArgumentException e) {
System.out.println("正确: 成功捕获空标题异常");
}
}
}

BIN
w10/TitleLengthAnalyzer.class

Binary file not shown.

44
w10/TitleLengthAnalyzer.java

@ -0,0 +1,44 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TitleLengthAnalyzer implements ArticleAnalyzer {
@Override
public Map<String, Object> analyze(List<Article> articles) {
Map<String, Object> result = new HashMap<>();
if (articles.isEmpty()) {
result.put("analyzer", getName());
result.put("averageLength", 0);
result.put("maxLength", 0);
result.put("minLength", 0);
return result;
}
int totalLength = articles.stream()
.mapToInt(a -> a.getTitle().length())
.sum();
int maxLength = articles.stream()
.mapToInt(a -> a.getTitle().length())
.max()
.orElse(0);
int minLength = articles.stream()
.mapToInt(a -> a.getTitle().length())
.min()
.orElse(0);
result.put("analyzer", getName());
result.put("averageLength", (double) totalLength / articles.size());
result.put("maxLength", maxLength);
result.put("minLength", minLength);
return result;
}
@Override
public String getName() {
return "标题长度分析器";
}
}

BIN
w11/ExceptionDemo.class

Binary file not shown.

54
w11/ExceptionDemo.java

@ -0,0 +1,54 @@
import java.util.concurrent.atomic.AtomicInteger;
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("===== 测试 URL 校验测试 =====");
testUrlValidation();
System.out.println("\n===== 测试重试机制测试 =====");
testRetryMechanism();
}
private static void testUrlValidation() {
String[] testUrls = {
"https://www.example.com",
"invalid-url",
null
};
for (String url : testUrls) {
try {
System.out.println("正在校验 URL: " + url);
UrlValidator.validateUrl(url);
System.out.println("URL 格式有效!");
} catch (UrlFormatException e) {
System.err.println("错误: " + e.getMessage());
System.err.println("无效的 URL: " + e.getInvalidUrl());
}
}
}
private static void testRetryMechanism() {
// 模拟一个会失败几次的操作
AtomicInteger attemptCounter = new AtomicInteger(0);
try {
String result = RetryUtils.executeWithRetry(() -> {
int currentAttempt = attemptCounter.incrementAndGet();
System.out.println("执行操作... (尝试次数: " + currentAttempt + ")");
// 模拟前3次失败,第4次成功
if (currentAttempt < 4) {
throw new RuntimeException("模拟失败");
}
return "操作成功完成!";
});
System.out.println("结果: " + result);
} catch (NetworkException e) {
System.err.println("错误: " + e.getMessage());
e.printStackTrace();
}
}
}

BIN
w11/NetworkException.class

Binary file not shown.

9
w11/NetworkException.java

@ -0,0 +1,9 @@
public class NetworkException extends Exception {
public NetworkException(String message) {
super(message);
}
public NetworkException(String message, Throwable cause) {
super(message, cause);
}
}

48
w11/README.md

@ -0,0 +1,48 @@
# Java 异常体系设计文档
## 1. 异常层次设计
### 1.1 未检查异常 (RuntimeException)
- **UrlFormatException**:继承自 RuntimeException,用于处理 URL 格式校验失败
- 包含无效 URL 的信息
- 提供有意义的错误消息
### 1.2 已检查异常 (Exception)
- **NetworkException**:继承自 Exception,用于处理网络相关问题
- 支持链式异常 (cause)
- 提供网络操作的上下文信息
## 2. 异常设计原则
### 2.1 Checked/Unchecked 选择
- **UrlFormatException (Unchecked)**:因为 URL 格式校验是编程错误,可以通过预检查避免,使用 Unchecked 异常更合理
- **NetworkException (Checked)**:因为网络问题是不可预测的外部因素,使用 Checked 异常强制调用者处理
### 2.2 异常包装
- 所有异常都保留原始异常信息 (cause)
- 使用链式异常避免根因丢失
- 提供构造函数支持 cause 链
### 2.3 上下文信息
- UrlFormatException 保存无效的 URL
- NetworkException 支持传递详细的错误信息
- 异常消息包含足够的上下文帮助调试
## 3. 重试机制
### 3.1 指数退避策略
- 基础等待时间:500ms
- 计算公式:`wait = base * 2^attempt`
- 最大重试次数:5次
- 等待时间示例:500ms, 1000ms, 2000ms, 4000ms, 8000ms
## 4. AI 架构审计请求
> 作为Java架构师,请审查我的异常层次设计:Checked/Unchecked选择是否合理?异常包装是否丢失根因?日志是否包含足够上下文?
## 5. 文件列表
- [UrlFormatException.java](file:///D:\嘻嘻哈哈\Git\java\w11\UrlFormatException.java)
- [NetworkException.java](file:///D:\嘻嘻哈哈\Git\java\w11\NetworkException.java)
- [RetryUtils.java](file:///D:\嘻嘻哈哈\Git\java\w11\RetryUtils.java)
- [UrlValidator.java](file:///D:\嘻嘻哈哈\Git\java\w11\UrlValidator.java)
- [ExceptionDemo.java](file:///D:\嘻嘻哈哈\Git\java\w11\ExceptionDemo.java)

BIN
w11/RetryUtils.class

Binary file not shown.

36
w11/RetryUtils.java

@ -0,0 +1,36 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class RetryUtils {
private static final long BASE_WAIT_MS = 500;
private static final int MAX_ATTEMPTS = 5;
public static <T> T executeWithRetry(Supplier<T> supplier) throws NetworkException {
int attempt = 0;
while (true) {
try {
return supplier.get();
} catch (Exception e) {
attempt++;
if (attempt >= MAX_ATTEMPTS) {
throw new NetworkException("重试失败,已达最大重试次数", e);
}
long waitTime = calculateWaitTime(attempt);
System.out.println("重试失败,等待 " + waitTime + "ms 后重试... (第 " + attempt + " 次尝试)");
try {
TimeUnit.MILLISECONDS.sleep(waitTime);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new NetworkException("重试被中断", ie);
}
}
}
}
private static long calculateWaitTime(int attempt) {
// 指数退避公式:wait = base * 2^attempt
return BASE_WAIT_MS * (long) Math.pow(2, attempt);
}
}

BIN
w11/UrlFormatException.class

Binary file not shown.

17
w11/UrlFormatException.java

@ -0,0 +1,17 @@
public class UrlFormatException extends RuntimeException {
private String invalidUrl;
public UrlFormatException(String message, String invalidUrl) {
super(message);
this.invalidUrl = invalidUrl;
}
public UrlFormatException(String message, Throwable cause, String invalidUrl) {
super(message, cause);
this.invalidUrl = invalidUrl;
}
public String getInvalidUrl() {
return invalidUrl;
}
}

BIN
w11/UrlValidator.class

Binary file not shown.

16
w11/UrlValidator.java

@ -0,0 +1,16 @@
import java.net.MalformedURLException;
import java.net.URL;
public class UrlValidator {
public static void validateUrl(String urlStr) {
if (urlStr == null || urlStr.trim().isEmpty()) {
throw new UrlFormatException("URL不能为空", urlStr);
}
try {
new URL(urlStr);
} catch (MalformedURLException e) {
throw new UrlFormatException("URL格式无效", e, urlStr);
}
}
}

BIN
w5/Circle.class

Binary file not shown.

6
w5/Circle.java

@ -0,0 +1,6 @@
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}

BIN
w5/Rectangle.class

Binary file not shown.

6
w5/Rectangle.java

@ -0,0 +1,6 @@
public class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}

BIN
w5/Shape.class

Binary file not shown.

5
w5/Shape.java

@ -0,0 +1,5 @@
public class Shape {
public void draw() {
System.out.println("绘制形状");
}
}

BIN
w5/ShapeTest.class

Binary file not shown.

24
w5/ShapeTest.java

@ -1,4 +1,22 @@
package PACKAGE_NAME;
public class ShapeTest {
}
public static void drawShape(Shape s) {
s.draw();
}
public static void main(String[] args) {
// 创建形状对象
Shape myShape = new Shape();
Shape myCircle = new Circle();
Shape myRectangle = new Rectangle();
// 测试 drawShape 方法
System.out.println("测试 Shape 基类:");
drawShape(myShape);
System.out.println("\n测试 Circle 子类:");
drawShape(myCircle);
System.out.println("\n测试 Rectangle 子类:");
drawShape(myRectangle);
}
}

BIN
w6/Animal.class

Binary file not shown.

3
w6/Animal.java

@ -0,0 +1,3 @@
public abstract class Animal {
public abstract void makeSound();
}

BIN
w6/AnimalTest.class

Binary file not shown.

27
w6/AnimalTest.java

@ -1,4 +1,25 @@
package PACKAGE_NAME;
public class AnimalTest {
}
public static void main(String[] args) {
// 多态:使用 Animal 类型引用指向子类对象
Animal dog = new Dog();
Animal cat = new Cat();
System.out.println("===== 测试动物叫声 =====");
dog.makeSound();
cat.makeSound();
System.out.println("\n===== 测试接口能力 =====");
// 使用 instanceof 检查对象是否实现了 Swimmable 接口
if (dog instanceof Swimmable) {
((Swimmable) dog).swim();
} else {
System.out.println("这个动物不会游泳");
}
if (cat instanceof Swimmable) {
((Swimmable) cat).swim();
} else {
System.out.println("这个动物不会游泳");
}
}
}

BIN
w6/Cat.class

Binary file not shown.

6
w6/Cat.java

@ -0,0 +1,6 @@
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("猫叫:喵喵喵!");
}
}

BIN
w6/Dog.class

Binary file not shown.

11
w6/Dog.java

@ -0,0 +1,11 @@
public class Dog extends Animal implements Swimmable {
@Override
public void makeSound() {
System.out.println("狗叫:汪汪汪!");
}
@Override
public void swim() {
System.out.println("狗在游泳");
}
}

BIN
w6/Swimmable.class

Binary file not shown.

3
w6/Swimmable.java

@ -0,0 +1,3 @@
public interface Swimmable {
void swim();
}

BIN
w7/Account.class

Binary file not shown.

19
w7/Account.java

@ -0,0 +1,19 @@
public class Account {
private double balance;
public Account(double initialBalance) {
this.balance = initialBalance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(balance);
}
balance -= amount;
System.out.println("取款成功!当前余额:" + balance);
}
public double getBalance() {
return balance;
}
}

BIN
w7/AccountTest.class

Binary file not shown.

24
w7/AccountTest.java

@ -0,0 +1,24 @@
public class AccountTest {
public static void main(String[] args) {
// 创建账户,初始余额 1000 元
Account account = new Account(1000.0);
System.out.println("当前账户余额:" + account.getBalance());
// 测试取款功能
System.out.println("\n===== 尝试取款 500 元 =====");
try {
account.withdraw(500.0);
} catch (InsufficientFundsException e) {
System.out.println("错误:" + e.getMessage());
System.out.println("当前余额:" + e.getBalance());
}
System.out.println("\n===== 尝试取款 700 元 =====");
try {
account.withdraw(700.0);
} catch (InsufficientFundsException e) {
System.out.println("错误:" + e.getMessage());
System.out.println("当前余额:" + e.getBalance());
}
}
}

BIN
w7/InsufficientFundsException.class

Binary file not shown.

12
w7/InsufficientFundsException.java

@ -0,0 +1,12 @@
public class InsufficientFundsException extends Exception {
private double balance;
public InsufficientFundsException(double balance) {
super("余额不足,当前余额为:" + balance);
this.balance = balance;
}
public double getBalance() {
return balance;
}
}

BIN
w8/Box.class

Binary file not shown.

26
w8/Box.java

@ -0,0 +1,26 @@
public class Box<T> {
private T content;
public Box() {}
public Box(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
public boolean isEmpty() {
return content == null;
}
@Override
public String toString() {
return "Box{" + "content=" + content + '}';
}
}

BIN
w8/GenericDemo.class

Binary file not shown.

51
w8/GenericDemo.java

@ -0,0 +1,51 @@
import java.util.ArrayList;
import java.util.List;
public class GenericDemo {
public static void main(String[] args) {
// 测试 Box 泛型类
System.out.println("===== 测试 Box 泛型类 =====");
Box<String> stringBox = new Box<>("Hello, Generics!");
System.out.println(stringBox);
Box<Integer> integerBox = new Box<>(100);
System.out.println(integerBox);
integerBox.setContent(200);
System.out.println("修改后:" + integerBox);
Box<Double> doubleBox = new Box<>();
System.out.println("空盒子:" + doubleBox);
System.out.println("是否为空:" + doubleBox.isEmpty());
// 测试 Pair 泛型类
System.out.println("\n===== 测试 Pair 泛型类 =====");
Pair<String, Integer> nameAgePair = new Pair<>("张三", 25);
System.out.println(nameAgePair);
System.out.println("姓名:" + nameAgePair.getKey());
System.out.println("年龄:" + nameAgePair.getValue());
Pair<String, String> countryCapitalPair = new Pair<>("中国", "北京");
System.out.println(countryCapitalPair);
// 测试泛型方法
System.out.println("\n===== 测试泛型方法 =====");
String[] strArray = {"Java", "Python", "C++", "JavaScript"};
System.out.print("字符串数组:");
GenericUtils.printArray(strArray);
Integer[] intArray = {1, 2, 3, 4, 5};
System.out.print("原始数组:");
GenericUtils.printArray(intArray);
GenericUtils.swap(intArray, 0, 4);
System.out.print("交换后数组:");
GenericUtils.printArray(intArray);
List<Double> doubleList = new ArrayList<>();
doubleList.add(1.5);
doubleList.add(2.5);
doubleList.add(3.5);
System.out.println("列表元素数量:" + GenericUtils.countListElements(doubleList));
}
}

BIN
w8/GenericUtils.class

Binary file not shown.

24
w8/GenericUtils.java

@ -0,0 +1,24 @@
import java.util.ArrayList;
import java.util.List;
public class GenericUtils {
// 泛型方法:打印数组元素
public static <E> void printArray(E[] array) {
for (E element : array) {
System.out.print(element + " ");
}
System.out.println();
}
// 泛型方法:交换数组中的两个元素
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// 泛型方法:计算列表中的元素数量
public static <E> int countListElements(List<E> list) {
return list.size();
}
}

BIN
w8/Pair.class

Binary file not shown.

30
w8/Pair.java

@ -0,0 +1,30 @@
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
@Override
public String toString() {
return "Pair{" + "key=" + key + ", value=" + value + '}';
}
}

54
w8/README.md

@ -0,0 +1,54 @@
# Java 泛型类练习
本项目包含几个典型的 Java 泛型类练习,展示了泛型的使用方法。
## 文件结构
1. [Box.java](file:///D:\嘻嘻哈哈\Git\java\w8\Box.java) - 简单的泛型容器类
2. [Pair.java](file:///D:\嘻嘻哈哈\Git\java\w8\Pair.java) - 存储键值对的泛型类
3. [GenericUtils.java](file:///D:\嘻嘻哈哈\Git\java\w8\GenericUtils.java) - 包含泛型方法的工具类
4. [GenericDemo.java](file:///D:\嘻嘻哈哈\Git\java\w8\GenericDemo.java) - 演示程序
5. README.md - 本文档
## 泛型类概述
### Box<T>
一个简单的泛型容器类,可以存储任意类型的对象。
- 使用单个类型参数 T
- 提供构造函数、getter、setter 方法
- 判断是否为空的方法
### Pair<K, V>
一个存储键值对的泛型类,可以存储两个不同类型的对象。
- 使用两个类型参数 K 和 V
- 提供构造函数和 getter/setter 方法
### GenericUtils
一个包含泛型方法的工具类,展示了如何创建和使用泛型方法。
- printArray: 打印任意类型数组的元素
- swap: 交换数组中的任意两个元素
- countListElements: 计算列表中的元素数量
## 编译运行
```bash
# 编译所有 Java 文件
javac Box.java Pair.java GenericUtils.java GenericDemo.java
# 运行演示程序
java GenericDemo
```
## 运行结果
程序会展示泛型类的各种用法,包括:
- Box 类存储不同类型的数据
- Pair 类存储键值对
- GenericUtils 的泛型方法的使用
## 泛型的主要优点
1. **类型安全**:编译时检查,避免运行时类型错误
2. **代码复用**:一套代码支持多种数据类型
3. **可读性**:更清晰的代码,更易理解
4. **避免强制类型转换**:减少代码中的类型转换

127
w9/README.md

@ -0,0 +1,127 @@
# 文章爬虫系统
这是一个使用 Maven 创建的 Java 项目,实现了命令行界面的文章爬虫系统。
## 项目结构
```
w9/
├── pom.xml # Maven 配置文件
├── README.md # 项目文档
└── src/
└── main/
└── java/
└── com/
└── crawler/
├── Main.java # 主程序入口
├── model/
│ └── Article.java # 文章数据模型
├── view/
│ └── ConsoleView.java # 控制台视图
├── command/
│ ├── Command.java # 命令接口
│ ├── HelpCommand.java # 帮助命令
│ ├── ListCommand.java # 列表命令
│ ├── CrawlCommand.java # 爬取命令
│ └── ExitCommand.java # 退出命令
└── controller/
└── CommandController.java # 命令控制器
```
## 功能特性
### 已实现的命令
1. **help** - 显示帮助信息
2. **list** - 显示已抓取的文章列表
3. **crawl** - 开始抓取文章(目前为存根实现)
4. **exit** - 退出程序
### 项目架构
- **model**: 数据模型层 - 定义文章数据结构
- **view**: 视图层 - 处理控制台输出
- **command**: 命令层 - 实现各种命令
- **controller**: 控制层 - 管理命令执行
## 编译运行
### 编译项目
```bash
cd w9
mvn clean compile
```
### 运行项目
```bash
mvn exec:java
```
或者先打包再运行:
```bash
mvn package
java -jar target/crawler-app-1.0-SNAPSHOT.jar
```
## 使用示例
```
================================================
欢迎使用文章爬虫系统
================================================
可用命令:
help - 显示帮助信息
list - 显示已抓取的文章列表
crawl - 开始抓取文章
exit - 退出程序
请输入命令 > help
可用命令:
help - 显示帮助信息
list - 显示已抓取的文章列表
crawl - 开始抓取文章
exit - 退出程序
请输入命令 > crawl
正在开始抓取文章...
抓取完成,共抓取了 3 篇文章
请输入命令 > list
已抓取的文章列表:
----------------------------------------
1. 示例文章1
URL: https://example.com/article1
----------------------------------------
2. 示例文章2
URL: https://example.com/article2
----------------------------------------
3. 示例文章3
URL: https://example.com/article3
----------------------------------------
共 3 篇文章
请输入命令 > exit
感谢使用,再见!
```
## 技术栈
- Java 8
- Maven 3.x
## 扩展功能
当前 crawl 命令是存根实现,可以扩展为:
1. 连接实际的文章网站
2. 解析 HTML 内容
3. 提取文章标题、URL、内容等
4. 保存到数据库或文件

167
w9/RUNNING.md

@ -0,0 +1,167 @@
# Maven 项目运行说明
## 项目已成功创建并测试完成
### 项目信息
- **项目类型**: Maven Java 项目
- **包结构**: com.crawler
- **功能**: 命令行文章爬虫系统
### 已实现的包结构
```
w9/
├── pom.xml # Maven 配置文件
├── README.md # 详细项目文档
├── test.bat # Windows 测试脚本
└── src/
└── main/
└── java/
└── com/
└── crawler/
├── Main.java # 主程序入口,包含命令循环
├── model/
│ └── Article.java # 文章数据模型
├── view/
│ └── ConsoleView.java # 控制台视图(用户界面)
├── command/
│ ├── Command.java # 命令接口
│ ├── HelpCommand.java # help 命令实现
│ ├── ListCommand.java # list 命令实现
│ ├── CrawlCommand.java # crawl 命令实现
│ └── ExitCommand.java # exit 命令实现
└── controller/
└── CommandController.java # 命令控制器
```
### 已实现的 4 个命令
1. **help** - 显示所有可用命令的帮助信息
2. **list** - 显示已抓取的文章列表(目前为存根)
3. **crawl** - 开始抓取文章(目前为存根实现,添加3篇示例文章)
4. **exit** - 退出程序
### 运行方法
#### 方法一:使用 Java 编译器直接运行(已测试)
```bash
# 1. 编译项目
cd D:\嘻嘻哈哈\Git\java\w9\src\main\java
javac -d ../../../../target/classes -cp . com/crawler/model/Article.java com/crawler/view/ConsoleView.java com/crawler/command/*.java com/crawler/controller/*.java com/crawler/Main.java
# 2. 运行程序
java -cp ../../../../target/classes com.crawler.Main
```
#### 方法二:使用 Maven 运行(需要配置 JAVA_HOME)
```bash
# 设置 JAVA_HOME
set JAVA_HOME=你的Java安装路径
# 编译项目
cd w9
mvn clean compile
# 运行程序
mvn exec:java
```
### 测试结果
程序已成功运行,所有命令都正常工作:
```
================================================
欢迎使用文章爬虫系统
================================================
可用命令:
help - 显示帮助信息
list - 显示已抓取的文章列表
crawl - 开始抓取文章
exit - 退出程序
请输入命令 > help
可用命令:
help - 显示帮助信息
list - 显示已抓取的文章列表
crawl - 开始抓取文章
exit - 退出程序
请输入命令 > list
已抓取的文章列表:
----------------------------------------
暂无已抓取的文章
共 0 篇文章
请输入命令 > crawl
正在开始抓取文章...
抓取完成,共抓取了 3 篇文章
请输入命令 > list
已抓取的文章列表:
----------------------------------------
1. 示例文章1
URL: https://example.com/article1
----------------------------------------
2. 示例文章2
URL: https://example.com/article2
----------------------------------------
3. 示例文章3
URL: https://example.com/article3
----------------------------------------
共 3 篇文章
请输入命令 > exit
感谢使用,再见!
```
### 项目架构说明
#### MVC 架构模式
- **Model (模型层)**: `model/Article.java` - 数据模型
- **View (视图层)**: `view/ConsoleView.java` - 用户界面输出
- **Controller (控制层)**: `controller/CommandController.java` - 命令调度
#### 命令模式
- **Command 接口**: 定义命令的基本结构
- **具体命令类**: HelpCommand, ListCommand, CrawlCommand, ExitCommand
- **命令控制器**: 管理命令的注册和执行
### 扩展建议
当前 crawl 命令是存根实现,可以扩展为:
1. **网络爬虫**: 添加 Jsoup 依赖,实现真实的网页爬取
2. **数据存储**: 将抓取的文章保存到文件或数据库
3. **多线程**: 使用 ExecutorService 实现并行爬取
4. **配置管理**: 添加配置文件管理爬取规则
5. **日志系统**: 使用 SLF4J 添加日志记录
### Maven 依赖配置
如需添加 Jsoup 依赖,可以在 pom.xml 中添加:
```xml
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
</dependencies>
```
### 总结
✅ Maven 项目结构已创建
✅ 完整的包结构 (model/view/command/controller) 已实现
✅ 4 个命令已全部实现并测试通过
✅ list 命令可展示已抓取的文章
✅ 命令循环运行正常
✅ 所有文件已保存在 w9 目录中

40
w9/pom.xml

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>crawler-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>com.crawler.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

43
w9/src/main/java/com/crawler/Main.java

@ -0,0 +1,43 @@
package com.crawler;
import com.crawler.command.*;
import com.crawler.controller.CommandController;
import com.crawler.model.Article;
import com.crawler.view.ConsoleView;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// 初始化组件
ConsoleView view = new ConsoleView();
List<Article> articles = new ArrayList<>();
boolean[] running = {true};
// 创建控制器
CommandController controller = new CommandController(view);
// 注册命令
controller.registerCommand(new HelpCommand(view));
controller.registerCommand(new ListCommand(view, articles));
controller.registerCommand(new CrawlCommand(view, articles));
controller.registerCommand(new ExitCommand(view, running));
// 显示欢迎信息
view.showWelcome();
view.showHelp();
// 创建扫描器读取用户输入
Scanner scanner = new Scanner(System.in);
// 命令循环
while (running[0]) {
view.showPrompt();
String input = scanner.nextLine();
controller.executeCommand(input);
}
scanner.close();
}
}

6
w9/src/main/java/com/crawler/command/Command.java

@ -0,0 +1,6 @@
package com.crawler.command;
public interface Command {
String getName();
void execute();
}

36
w9/src/main/java/com/crawler/command/CrawlCommand.java

@ -0,0 +1,36 @@
package com.crawler.command;
import com.crawler.model.Article;
import com.crawler.view.ConsoleView;
import java.util.List;
public class CrawlCommand implements Command {
private ConsoleView view;
private List<Article> articles;
public CrawlCommand(ConsoleView view, List<Article> articles) {
this.view = view;
this.articles = articles;
}
@Override
public String getName() {
return "crawl";
}
@Override
public void execute() {
view.showCrawlStart();
// 这里是存根实现,实际可以在这里调用爬虫逻辑
Article article1 = new Article("示例文章1", "https://example.com/article1");
Article article2 = new Article("示例文章2", "https://example.com/article2");
Article article3 = new Article("示例文章3", "https://example.com/article3");
articles.add(article1);
articles.add(article2);
articles.add(article3);
view.showCrawlComplete(articles.size());
}
}

24
w9/src/main/java/com/crawler/command/ExitCommand.java

@ -0,0 +1,24 @@
package com.crawler.command;
import com.crawler.view.ConsoleView;
public class ExitCommand implements Command {
private ConsoleView view;
private boolean[] running;
public ExitCommand(ConsoleView view, boolean[] running) {
this.view = view;
this.running = running;
}
@Override
public String getName() {
return "exit";
}
@Override
public void execute() {
view.showGoodbye();
running[0] = false;
}
}

21
w9/src/main/java/com/crawler/command/HelpCommand.java

@ -0,0 +1,21 @@
package com.crawler.command;
import com.crawler.view.ConsoleView;
public class HelpCommand implements Command {
private ConsoleView view;
public HelpCommand(ConsoleView view) {
this.view = view;
}
@Override
public String getName() {
return "help";
}
@Override
public void execute() {
view.showHelp();
}
}

25
w9/src/main/java/com/crawler/command/ListCommand.java

@ -0,0 +1,25 @@
package com.crawler.command;
import com.crawler.model.Article;
import com.crawler.view.ConsoleView;
import java.util.List;
public class ListCommand implements Command {
private ConsoleView view;
private List<Article> articles;
public ListCommand(ConsoleView view, List<Article> articles) {
this.view = view;
this.articles = articles;
}
@Override
public String getName() {
return "list";
}
@Override
public void execute() {
view.showArticles(articles);
}
}

39
w9/src/main/java/com/crawler/controller/CommandController.java

@ -0,0 +1,39 @@
package com.crawler.controller;
import com.crawler.command.Command;
import com.crawler.view.ConsoleView;
import java.util.HashMap;
import java.util.Map;
public class CommandController {
private Map<String, Command> commands;
private ConsoleView view;
public CommandController(ConsoleView view) {
this.view = view;
this.commands = new HashMap<>();
}
public void registerCommand(Command command) {
commands.put(command.getName(), command);
}
public void executeCommand(String input) {
String commandName = input.trim().toLowerCase();
if (commandName.isEmpty()) {
return;
}
Command command = commands.get(commandName);
if (command != null) {
command.execute();
} else {
view.showUnknownCommand(commandName);
}
}
public boolean hasCommand(String commandName) {
return commands.containsKey(commandName);
}
}

55
w9/src/main/java/com/crawler/model/Article.java

@ -0,0 +1,55 @@
package com.crawler.model;
public class Article {
private String title;
private String url;
private String content;
private long timestamp;
public Article(String title, String url) {
this.title = title;
this.url = url;
this.timestamp = System.currentTimeMillis();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
@Override
public String toString() {
return "Article{" +
"title='" + title + '\'' +
", url='" + url + '\'' +
", timestamp=" + timestamp +
'}';
}
}

60
w9/src/main/java/com/crawler/view/ConsoleView.java

@ -0,0 +1,60 @@
package com.crawler.view;
import com.crawler.model.Article;
import java.util.List;
public class ConsoleView {
public void showWelcome() {
System.out.println("================================================");
System.out.println(" 欢迎使用文章爬虫系统");
System.out.println("================================================");
System.out.println();
}
public void showPrompt() {
System.out.print("请输入命令 > ");
}
public void showHelp() {
System.out.println("\n可用命令:");
System.out.println(" help - 显示帮助信息");
System.out.println(" list - 显示已抓取的文章列表");
System.out.println(" crawl - 开始抓取文章");
System.out.println(" exit - 退出程序");
System.out.println();
}
public void showArticles(List<Article> articles) {
System.out.println("\n已抓取的文章列表:");
System.out.println("----------------------------------------");
if (articles.isEmpty()) {
System.out.println("暂无已抓取的文章");
} else {
for (int i = 0; i < articles.size(); i++) {
Article article = articles.get(i);
System.out.println((i + 1) + ". " + article.getTitle());
System.out.println(" URL: " + article.getUrl());
System.out.println("----------------------------------------");
}
}
System.out.println("共 " + articles.size() + " 篇文章\n");
}
public void showCrawlStart() {
System.out.println("\n正在开始抓取文章...");
}
public void showCrawlComplete(int count) {
System.out.println("抓取完成,共抓取了 " + count + " 篇文章\n");
}
public void showGoodbye() {
System.out.println("\n感谢使用,再见!");
}
public void showUnknownCommand(String command) {
System.out.println("未知命令: " + command);
System.out.println("输入 'help' 查看可用命令\n");
}
}

10
w9/test.bat

@ -0,0 +1,10 @@
@echo off
cd /d "%~dp0src\main\java"
echo help > input.txt
echo list >> input.txt
echo crawl >> input.txt
echo list >> input.txt
echo exit >> input.txt
java -cp ..\..\..\..\target\classes com.crawler.Main < input.txt
del input.txt
Loading…
Cancel
Save