32 KiB
封装、继承、多态(Java示例)
本文档基于当前项目中的 Java 源代码,整理了封装、继承、多态三个面向对象核心概念的说明和示例代码。
1. 说明
1.1 封装(Encapsulation)
封装是把类的数据(属性)和对数据的操作(方法)放在一起。通过把字段设为 private,再提供 public getter/setter 方法,让外部不能直接访问内部数据,从而保护数据、隐藏实现。
项目中典型示例:
src/main/java/com/movieratings/model/Movie.javasrc/main/java/com/movieratings/model/DirectorStats.java
1.2 继承(Inheritance)
继承让一个类获得另一个类或接口的特性。Java 中使用 extends 表示类继承类,使用 implements 表示类实现接口。接口也可以继承其他接口。
项目中典型示例:
src/main/java/com/movieratings/repository/MovieRepository.javasrc/main/java/com/movieratings/DataInitializer.javasrc/main/java/com/movieratings/model/Movie.javasrc/main/java/com/movieratings/model/DirectorStats.java
1.3 多态(Polymorphism)
多态是指同一个引用类型,运行时可以指向不同的对象,并执行不同的实现。通常表现为接口类型引用指向实现类对象,或者子类重写父类方法。
项目中典型示例:
DataInitializer实现了CommandLineRunner,Spring 会以接口类型调用它MovieRepository继承自JpaRepository,可以把它当作JpaRepository使用Movie重写了toString()方法,展示了动态绑定
2. 相关代码
2.1 Movie.java - 封装与多态示例
文件路径:src/main/java/com/movieratings/model/Movie.java
package com.movieratings.model;
import javax.persistence.*;
import java.io.Serializable;
/**
* 电影数据实体类
*/
@Entity
@Table(name = "movies")
public class Movie implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title; // 标题
private double rating; // 评分
private int releaseYear; // 年份
private int rank; // 排名
private String quote; // 简评/台词
private String director; // 导演
private int reviewCount; // 评价人数
private String country; // 国家/地区
private double boxOffice; // 票房 (模拟/演示)
private String type; // 作品类型 (电影、电视剧、纪录片等)
private String posterUrl; // 海报图片链接
public Movie() {}
public Movie(String title, double rating, int releaseYear, int rank, String quote, String director, int reviewCount, String country, double boxOffice, String type, String posterUrl) {
this.title = title;
this.rating = rating;
this.releaseYear = releaseYear;
this.rank = rank;
this.quote = quote;
this.director = director;
this.reviewCount = reviewCount;
this.country = country;
this.boxOffice = boxOffice;
this.type = type;
this.posterUrl = posterUrl;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
public double getBoxOffice() { return boxOffice; }
public void setBoxOffice(double boxOffice) { this.boxOffice = boxOffice; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public double getRating() { return rating; }
public void setRating(double rating) { this.rating = rating; }
public int getReleaseYear() { return releaseYear; }
public void setReleaseYear(int releaseYear) { this.releaseYear = releaseYear; }
public int getRank() { return rank; }
public void setRank(int rank) { this.rank = rank; }
public String getQuote() { return quote; }
public void setQuote(String quote) { this.quote = quote; }
public String getDirector() { return director; }
public void setDirector(String director) { this.director = director; }
public int getReviewCount() { return reviewCount; }
public void setReviewCount(int reviewCount) { this.reviewCount = reviewCount; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getPosterUrl() { return posterUrl; }
public void setPosterUrl(String posterUrl) { this.posterUrl = posterUrl; }
@Override
public String toString() {
return "Movie{" +
"id=" + id +
", title='" + title + '\'' +
", rating=" + rating +
", releaseYear=" + releaseYear +
", rank=" + rank +
", quote='" + quote + '\'' +
", director='" + director + '\'' +
", reviewCount=" + reviewCount +
", type='" + type + '\'' +
'}';
}
/**
* 重写 equals() — 两部电影 ID 相同则认为是同一部
* equals 和 hashCode 必须配对重写
*/
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Movie other = (Movie) obj;
return id != null && id.equals(other.id);
}
/**
* 重写 hashCode() — 与 equals() 保持一致,基于 id 计算
*/
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}
2.1.1 说明
- 所有字段都使用
private,这是封装的核心。 - 通过
public getXxx()和public setXxx(...)提供访问和修改接口。 toString()方法被重写,这是多态的一种体现:运行时调用的是Movie的实现。equals()和hashCode()配对重写 — 这是 Java OOP 的重要规则:- 如果两个对象
equals()为 true,它们的hashCode()必须相等。 - 重写
equals()时必须同时重写hashCode(),否则在HashMap、HashSet等集合中会出错。 getClass() != obj.getClass()确保只比较同类型的对象。
- 如果两个对象
2.2 DirectorStats.java - 封装示例
文件路径:src/main/java/com/movieratings/model/DirectorStats.java
package com.movieratings.model;
import java.io.Serializable;
/**
* 导演作品统计 DTO
*/
public class DirectorStats implements Serializable {
private String director; // 导演姓名
private long totalWorks; // 作品总数
private String representativePoster; // 代表作品海报
private double averageRating; // 平均评分
private double totalBoxOffice; // 总票房
public DirectorStats(String director, long totalWorks, String representativePoster, double averageRating, double totalBoxOffice) {
this.director = director;
this.totalWorks = totalWorks;
this.representativePoster = representativePoster;
this.averageRating = averageRating;
this.totalBoxOffice = totalBoxOffice;
}
public String getDirector() { return director; }
public void setDirector(String director) { this.director = director; }
public long getTotalWorks() { return totalWorks; }
public void setTotalWorks(long totalWorks) { this.totalWorks = totalWorks; }
public String getRepresentativePoster() { return representativePoster; }
public void setRepresentativePoster(String representativePoster) { this.representativePoster = representativePoster; }
public double getAverageRating() { return averageRating; }
public void setAverageRating(double averageRating) { this.averageRating = averageRating; }
public double getTotalBoxOffice() { return totalBoxOffice; }
public void setTotalBoxOffice(double totalBoxOffice) { this.totalBoxOffice = totalBoxOffice; }
}
2.2.1 说明
- 类字段同样设为
private。 - 通过 getter/setter 访问属性,这是封装的标准写法。
2.3 MovieRepository.java - 继承示例
文件路径:src/main/java/com/movieratings/repository/MovieRepository.java
package com.movieratings.repository;
import com.movieratings.model.Movie;
import com.movieratings.model.DirectorStats;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MovieRepository extends JpaRepository<Movie, Long> {
/**
* 按导演统计作品数量排行榜,支持搜索、作品类型过滤和分页
*/
@Query("SELECT new com.movieratings.model.DirectorStats(m.director, COUNT(m), MAX(m.posterUrl), AVG(m.rating), SUM(m.boxOffice)) " +
"FROM Movie m " +
"WHERE (:name IS NULL OR m.director LIKE %:name%) " +
"AND (:type IS NULL OR m.type = :type) " +
"GROUP BY m.director " +
"ORDER BY COUNT(m) DESC")
Page<DirectorStats> findDirectorRankings(@Param("name") String name, @Param("type") String type, Pageable pageable);
/**
* 获取指定导演的作品列表
*/
List<Movie> findByDirector(String director);
/**
* 获取所有不同的作品类型
*/
@Query("SELECT DISTINCT m.type FROM Movie m WHERE m.type IS NOT NULL")
List<String> findAllTypes();
}
2.3.1 说明
MovieRepository通过extends JpaRepository<Movie, Long>继承了 Spring Data JPA 提供的通用数据访问方法。- 这就是继承的写法,子接口自动拥有父接口的行为。
2.5 DataAnalyzer.java - 内部类封装与方法封装
文件路径:src/main/java/com/movieratings/analysis/DataAnalyzer.java
package com.movieratings.analysis;
import com.movieratings.model.Movie;
import org.apache.commons.math3.stat.correlation.PearsonsCorrelation;
import org.apache.commons.math3.stat.inference.TTest;
import java.util.*;
import java.util.stream.Collectors;
/**
* 数据分析类
*/
public class DataAnalyzer {
/**
* 年份与评分相关性分析 (Pearson 相关系数)
*/
public CorrelationResult analyzeYearRatingCorrelation(List<Movie> movies) {
double[] years = movies.stream().mapToDouble(Movie::getReleaseYear).toArray();
double[] ratings = movies.stream().mapToDouble(Movie::getRating).toArray();
PearsonsCorrelation correlation = new PearsonsCorrelation();
double coefficient = correlation.correlation(years, ratings);
TTest tTest = new TTest();
double pValue = tTest.tTest(years, ratings);
return new CorrelationResult(coefficient, pValue);
}
/**
* 导演作品统计结果类(内部静态类,体现封装)
*/
public static class DirectorStats {
private String name; // 导演姓名
private long count; // 作品数量
private double avgRating; // 平均评分
private double totalBoxOffice; // 总票房
public DirectorStats(String name, long count, double avgRating, double totalBoxOffice) {
this.name = name;
this.count = count;
this.avgRating = avgRating;
this.totalBoxOffice = totalBoxOffice;
}
public String getName() { return name; }
public long getCount() { return count; }
public double getAvgRating() { return avgRating; }
public double getTotalBoxOffice() { return totalBoxOffice; }
}
/**
* 导演作品数量排行榜 (前 20 位)
*/
public List<DirectorStats> getTopDirectors(List<Movie> movies, int topN) {
Map<String, List<Movie>> directorMap = movies.stream()
.filter(m -> m.getDirector() != null)
.collect(Collectors.groupingBy(Movie::getDirector));
return directorMap.entrySet().stream()
.map(entry -> {
String name = entry.getKey();
List<Movie> directorMovies = entry.getValue();
long count = directorMovies.size();
double avgRating = directorMovies.stream()
.mapToDouble(Movie::getRating).average().orElse(0.0);
double totalBoxOffice = directorMovies.stream()
.mapToDouble(m -> m.getBoxOffice()).sum();
return new DirectorStats(name, count, avgRating, totalBoxOffice);
})
.sorted((a, b) -> Long.compare(b.getCount(), a.getCount()))
.limit(topN)
.collect(Collectors.toList());
}
/**
* 相关性结果封装类(内部静态类)
*/
public static class CorrelationResult {
private double coefficient;
private double pValue;
public CorrelationResult(double coefficient, double pValue) {
this.coefficient = coefficient;
this.pValue = pValue;
}
public double getCoefficient() { return coefficient; }
public double getPValue() { return pValue; }
public String getSignificance() {
if (pValue < 0.01) return "极显著 (p < 0.01)";
if (pValue < 0.05) return "显著 (p < 0.05)";
return "不显著 (p >= 0.05)";
}
}
/**
* 统计评分基本信息
*/
public DoubleSummaryStatistics analyzeRatings(List<Movie> movies) {
return movies.stream()
.mapToDouble(Movie::getRating)
.summaryStatistics();
}
/**
* 按评分段统计
*/
public Map<String, Long> countMoviesByRatingRange(List<Movie> movies) {
return movies.stream()
.collect(Collectors.groupingBy(m -> {
double r = m.getRating();
if (r >= 9.5) return "9.5-10.0";
if (r >= 9.0) return "9.0-9.4";
if (r >= 8.5) return "8.5-8.9";
return "8.5以下";
}, Collectors.counting()));
}
/**
* 找出评价人数最多的前 N 部电影
*/
public List<Movie> findMostReviewed(List<Movie> movies, int n) {
return movies.stream()
.sorted((m1, m2) -> Integer.compare(m2.getReviewCount(), m1.getReviewCount()))
.limit(n)
.collect(Collectors.toList());
}
}
2.5.1 说明
- 封装:
DirectorStats和CorrelationResult是DataAnalyzer的内部静态类,字段为private,通过publicgetter 暴露数据,是封装的典型应用。 - 封装:
getSignificance()方法把 p 值的判断逻辑封装在类内部,外部只需调用方法,不需要知道判断规则。 - 封装:各个分析方法(
analyzeRatings、countMoviesByRatingRange等)将复杂的数据处理逻辑封装为单一方法调用。 - 多态:
sorted((m1, m2) -> ...)使用了函数式接口Comparator,这是多态在 Java 8+ 中的体现——接口引用指向 Lambda 表达式实现。
2.6 MovieCrawler.java - 常量封装与私有方法封装
文件路径:src/main/java/com/movieratings/crawler/MovieCrawler.java
package com.movieratings.crawler;
import com.movieratings.model.Movie;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 电影数据爬虫类 - 抓取豆瓣 Top 250
*/
@Component
public class MovieCrawler {
// 封装:常量使用 private static final,外部无法修改
private static final String BASE_URL = "https://movie.douban.com/top250";
private static final String USER_AGENT = "Mozilla/5.0 ...";
// 公开方法:提供单一清晰的调用入口
public List<Movie> crawl(int limit) {
List<Movie> movies = new ArrayList<>();
int start = 0;
while (movies.size() < limit && start < 250) {
String url = BASE_URL + "?start=" + start + "&filter=";
try {
Document doc = Jsoup.connect(url).userAgent(USER_AGENT).get();
Elements items = doc.select(".item");
if (items.isEmpty()) break;
for (Element item : items) {
if (movies.size() >= limit) break;
try {
Movie movie = parseMovie(item); // 调用私有方法
movies.add(movie);
} catch (Exception e) {
System.err.println("解析失败: " + e.getMessage());
}
}
start += 25;
Thread.sleep(1000); // 控制请求频率
} catch (IOException | InterruptedException e) {
System.err.println("网络请求失败: " + e.getMessage());
break;
}
}
return movies;
}
// 封装:私有方法隐藏解析细节,外部只需调用 crawl()
private Movie parseMovie(Element item) {
Movie movie = new Movie();
movie.setRank(Integer.parseInt(item.select(".pic em").text()));
movie.setTitle(item.select(".title").first().text());
movie.setRating(Double.parseDouble(item.select(".rating_num").text()));
movie.setPosterUrl(item.select(".pic img").attr("src"));
movie.setType("电影");
String bdText = item.select(".bd p").first().text();
String[] parts = bdText.split("\n");
String infoLine = parts[0];
Pattern yearPattern = Pattern.compile("\\d{4}");
Matcher matcher = yearPattern.matcher(infoLine);
if (matcher.find()) {
movie.setReleaseYear(Integer.parseInt(matcher.group()));
}
if (infoLine.contains("导演: ")) {
int start = infoLine.indexOf("导演: ") + 4;
int end = infoLine.indexOf(" ", start);
if (end == -1) end = infoLine.length();
movie.setDirector(infoLine.substring(start, end).trim());
}
String[] infoParts = infoLine.split(" / ");
if (infoParts.length >= 3) {
movie.setCountry(infoParts[infoParts.length - 2].trim());
}
Element starDiv = item.selectFirst(".star");
if (starDiv != null) {
String starText = starDiv.text();
Pattern reviewPattern = Pattern.compile("([\\d,]+)人评价");
Matcher reviewMatcher = reviewPattern.matcher(starText);
if (reviewMatcher.find()) {
String countStr = reviewMatcher.group(1).replace(",", "");
int count = Integer.parseInt(countStr);
movie.setReviewCount(count);
movie.setBoxOffice(count * 0.5 + (Math.random() * 100));
}
}
movie.setQuote(item.select(".inq").text());
return movie;
}
}
2.6.1 说明
- 封装:
BASE_URL和USER_AGENT使用private static final,外部无法访问和修改,是常量封装的标准做法。 - 封装:
parseMovie()是private方法,把 HTML 解析细节隐藏起来。外部调用者只需知道crawl(limit)返回列表,不关心内部如何解析。 - 这是方法级别的封装——将复杂的实现细节隐藏在私有方法中,对外暴露简洁的公共接口。
2.7 MovieService.java / DirectorController.java - 组合与依赖注入
文件路径:src/main/java/com/movieratings/service/MovieService.java
@Service
public class MovieService {
@Autowired
private MovieRepository movieRepository; // 组合:持有另一个对象的引用
@Cacheable(value = "directorRankings", key = "{#name, #type, #page, #size}")
public Page<DirectorStats> getDirectorRankings(String name, String type, int page, int size) {
Pageable pageable = PageRequest.of(page, size);
return movieRepository.findDirectorRankings(
(name == null || name.isEmpty()) ? null : name,
(type == null || type.isEmpty()) ? null : type,
pageable
);
}
@Transactional
@CacheEvict(value = {"directorRankings", "movieTypes"}, allEntries = true)
public void refreshData(List<Movie> movies) {
movieRepository.deleteAll();
movieRepository.saveAll(movies);
}
}
2.7.1 说明
- 组合优于继承:
MovieService没有继承MovieRepository,而是通过@Autowired持有它的引用。这是"组合"关系,是比继承更推荐的设计。 - 初学者应理解:继承表示"是一个"(is-a)关系,组合表示"有一个"(has-a)关系。 优先使用组合。
3. 概念对照表
| 概念 | 代码位置 | 说明 |
|---|---|---|
| 封装 | Movie.java, DirectorStats.java |
字段 private + getter/setter |
| 封装 | DataAnalyzer.java 内部类 |
内部静态类的 private 字段 + getter |
| 封装 | MovieCrawler.java |
private static final 常量 + private 方法 |
| 封装 | MovieService.java |
业务逻辑封装,隐藏 Repository 细节 |
| 继承 | MovieRepository.java |
extends JpaRepository<Movie, Long> |
| 接口实现 | DataInitializer.java |
implements CommandLineRunner |
| 组合 | MovieService.java |
@Autowired private MovieRepository |
| 多态 | Movie.toString() / DataInitializer / MovieRepository |
运行时接口引用与子类方法重写 |
| 多态 | DataAnalyzer.java |
Lambda 实现 Comparator 函数式接口 |
4. 项目代码中缺漏的 OOP 代码(重要)
当前项目代码能说明基本的封装、继承、多态概念,但作为教学项目,以下重要代码是缺失的。 建议初学者在理解本项目后,自行编写补充练习。
4.1 缺少 equals() 和 hashCode() 重写(已补充到源码)
Movie.java 的 toString() 已使用 @Override。本项目已在源码中补充 equals() 和 hashCode() 的配对重写示例。这是 Java OOP 的核心规则:
- 如果两个对象
equals()为 true,它们的hashCode()必须相等。 - 重写
equals()时必须同时重写hashCode(),否则在HashMap/HashSet等集合中会出错。
项目中 Movie.toString() 已有 @Override,但以下为完整的方法重写对照示例:
/**
* 完整的方法重写示例,注意每个方法都有 @Override 注解
*/
public class Movie {
// ... 字段省略 ...
/**
* 重写 Object 类的 toString() 方法
* @Override 告诉编译器:我在重写父类方法,如果拼写错误会报错
*/
@Override
public String toString() {
return title + " (" + releaseYear + ") - 评分: " + rating;
}
/**
* 重写 Object 类的 equals() 方法
* 两部电影 ID 相同就认为是同一部
*/
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Movie other = (Movie) obj;
return id != null && id.equals(other.id);
}
/**
* 重写 hashCode() — equals 和 hashCode 必须配对重写
*/
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}
初学者提示:重写方法时始终加上
@Override,这样如果方法签名写错,编译器会报错。
4.2 缺少 extends 父类的显式类继承示例
项目中唯一的 extends 出现在 MovieRepository extends JpaRepository,这是一个接口继承接口。
项目中没有 class A extends class B 的类继承示例。
建议补充示例 — 类继承:
/**
* 父类:媒体作品(电影、电视剧、纪录片的共同父类)
*/
public class MediaWork {
private String title;
private double rating;
private int releaseYear;
private String director;
public MediaWork(String title, double rating, int releaseYear, String director) {
this.title = title;
this.rating = rating;
this.releaseYear = releaseYear;
this.director = director;
}
public String getTitle() { return title; }
public double getRating() { return rating; }
public int getReleaseYear() { return releaseYear; }
public String getDirector() { return director; }
@Override
public String toString() {
return title + " (" + releaseYear + ") 导演: " + director;
}
}
/**
* 子类:电影 — 继承 MediaWork,添加电影特有属性
*/
public class Movie extends MediaWork {
private String quote; // 电影特有:经典台词
private String country; // 电影特有:国家
private double boxOffice; // 电影特有:票房
public Movie(String title, double rating, int releaseYear, String director,
String quote, String country, double boxOffice) {
super(title, rating, releaseYear, director); // 调用父类构造方法
this.quote = quote;
this.country = country;
this.boxOffice = boxOffice;
}
public String getQuote() { return quote; }
public double getBoxOffice() { return boxOffice; }
@Override
public String toString() {
return super.toString() + " | 票房: " + boxOffice;
}
}
/**
* 子类:电视剧 — 继承 MediaWork,添加电视剧特有属性
*/
public class TVSeries extends MediaWork {
private int seasons; // 电视剧特有:季数
private int episodesPerSeason; // 电视剧特有:每季集数
public TVSeries(String title, double rating, int releaseYear, String director,
int seasons, int episodesPerSeason) {
super(title, rating, releaseYear, director);
this.seasons = seasons;
this.episodesPerSeason = episodesPerSeason;
}
public int getSeasons() { return seasons; }
@Override
public String toString() {
return super.toString() + " | " + seasons + "季";
}
}
使用示例(体现多态):
// 多态:父类引用指向子类对象
MediaWork work1 = new Movie("肖申克的救赎", 9.7, 1994, "弗兰克·德拉邦特",
"希望让人自由", "美国", 5000);
MediaWork work2 = new TVSeries("绝命毒师", 9.5, 2008, "文斯·吉里根", 5, 13);
// 多态数组
MediaWork[] works = {work1, work2};
for (MediaWork work : works) {
// 运行时根据实际类型调用对应的 toString()
System.out.println(work.toString());
// 还可以访问父类定义的方法
System.out.println("评分: " + work.getRating());
}
初学者提示:
super(...)调用父类构造方法,必须放在子类构造方法的第一行。@Override重写的方法,运行时会根据实际对象类型调用对应版本,这就是多态。- Java 中类只能单继承(一个类只能有一个直接父类),但可以实现多个接口。
4.3 缺少抽象类和抽象方法示例
项目中没有 abstract 关键字的使用。抽象类是理解继承和多态的重要桥梁。
建议补充示例:
/**
* 抽象类:定义媒体作品的共同行为和强制子类实现的规则
* 抽象类不能被直接实例化,只能被继承
*/
public abstract class MediaWork {
private String title;
private double rating;
public MediaWork(String title, double rating) {
this.title = title;
this.rating = rating;
}
public String getTitle() { return title; }
public double getRating() { return rating; }
/**
* 抽象方法:没有方法体,强制子类提供自己的实现
* 不同媒体类型的展示格式不同,由各自子类决定
*/
public abstract String getDisplayFormat();
/**
* 普通方法:所有子类共享,不需要重写
*/
public String getRatingStars() {
int stars = (int) Math.round(rating / 2.0); // 5星制
return "★".repeat(stars) + "☆".repeat(5 - stars);
}
}
/**
* 具体子类:必须实现所有抽象方法
*/
public class Movie extends MediaWork {
private String country;
public Movie(String title, double rating, String country) {
super(title, rating);
this.country = country;
}
@Override
public String getDisplayFormat() {
return "电影: " + getTitle() + " [" + country + "] " + getRatingStars();
}
}
public class TVSeries extends MediaWork {
private int seasons;
public TVSeries(String title, double rating, int seasons) {
super(title, rating);
this.seasons = seasons;
}
@Override
public String getDisplayFormat() {
return "电视剧: " + getTitle() + " (" + seasons + "季) " + getRatingStars();
}
}
初学者提示:
- 包含抽象方法的类必须声明为
abstract。- 子类如果不实现所有抽象方法,也必须声明为
abstract。- 抽象类和接口的区别:抽象类可以有构造方法和字段,接口(Java 8+)可以有默认方法但不能有实例字段。
4.4 缺少接口定义示例
项目中有 implements(实现接口)和 extends(接口继承接口),但没有自己定义接口。
建议补充示例:
/**
* 接口:定义媒体作品应该具备的行为
* 接口中所有方法默认是 public abstract 的
*/
public interface MediaPlayable {
/** 播放 */
void play();
/** 暂停 */
void pause();
/** 停止 */
void stop();
/**
* 默认方法(Java 8+):提供默认实现,实现类可以选择性地覆盖
*/
default void showInfo() {
System.out.println("正在播放媒体内容...");
}
}
/**
* 实现接口
*/
public class MoviePlayer implements MediaPlayable {
private String title;
public MoviePlayer(String title) {
this.title = title;
}
@Override
public void play() {
System.out.println("开始播放电影: " + title);
}
@Override
public void pause() {
System.out.println("暂停播放: " + title);
}
@Override
public void stop() {
System.out.println("停止播放: " + title);
}
}
/**
* 多态示例:接口类型引用不同实现
*/
public class Demo {
public static void main(String[] args) {
MediaPlayable player = new MoviePlayer("肖申克的救赎");
player.play(); // 调用 MoviePlayer 的实现
player.pause();
player.showInfo(); // 调用接口的默认方法
}
}
初学者提示:
- 接口定义"能做什么"(能力),类定义"是什么"(身份)。
- 一个类可以实现多个接口(
implements A, B, C),弥补了 Java 单继承的限制。- Java 8 起,接口可以有
default方法和static方法。
4.5 缺漏总结
| 缺失的 OOP 代码 | 状态 | 说明 |
|---|---|---|
@Override 注解 |
已有 | Movie.toString/equals/hashCode 均使用 @Override |
equals() / hashCode() 重写 |
已补充到源码 | Movie.java 已添加配对重写 |
类继承 (class A extends class B) |
缺失 | 只有接口继承,没有父类派生子类(见补充示例代码) |
super() 调用父类构造 |
缺失 | 类继承中调用父类构造方法(见补充示例代码) |
抽象类 (abstract class) |
缺失 | 抽象方法和具体方法的混合(见补充示例代码) |
自定义接口 (interface) |
缺失 | 只有自己定义接口才能完整展示接口概念(见补充示例代码) |
| 多态数组 / 多态集合 | 缺失 | 父类引用数组指向不同子类对象(见补充示例代码) |
5. 初学者提示
- 封装让你控制类的数据访问权限,不要直接访问对象的字段。
- 继承让你复用已有行为,接口继承有助于解耦。优先使用接口继承而非类继承。
- 多态让代码更灵活,写接口类型变量并在运行时指向不同实现。
- 组合优于继承:能用
has-a的地方就不要用is-a。 - 始终使用
@Override注解标注重写方法,避免拼写错误。 - 在这个项目中,
Movie和DirectorStats是数据模型类,MovieRepository是持久化接口,DataInitializer是程序启动时执行的初始化逻辑。
以上内容已经包含项目中所有与封装、继承、多态相关的主要代码段,并补充了项目中缺失的重要 OOP 代码示例。