- java约定,当把一些共用骨架功能抽取出为一个父类时,可定义为Abstract抽象类
如果需要要求子类去覆盖抽象类的某个方法,却不想提供默认实现,可采取如下策略:
抽象类用Abstaract声明,与类无本质区别,好处是可以声明抽象方法,要求子类必须实现抽象方法覆盖
- 抽象类不能被实例化,可以包含成员变量等普通类的任何内容
- 可以实例化的类一定要补全所有的方法体,这样实例的方法在被调用时才能提供具体的功能
可以包含抽象方法,抽象方法不能是private或static的
接口
接口不是类,只代表一种功能,接口可以视为 所有方法均为抽象的“类”
- 接口只代表一种功能,例如 public class 鸟 extends 动物 implements 会飞,会呼吸,会新陈代谢
- 类继承就是is,接口则表示can 它拥有什么功能
- 经典的接口定义是不能提供任何方法实现的,java8后可提供默认方法
- 接口可以通过继承实现所有父接口的抽象方法
- 实现某个接口的类需要实现接口的所有方法,除非是抽象类
- 接口中的成员均为 public static final的,这样的常量为全大写
接口一旦发布则无法增添方法,否则打破了向后兼容性backward compatibility
默认方法
java8之前接口只有抽象方法,所有方法没有具体实现,之后包含了具有方法体的默认方法
- 默认方法是为了保证向后兼容性,既为接口增加了新功能,也没有破坏实现该接口的用户代码
- 但是默认方法的引入出现了二义性,因为一个类是可以实现多个接口的,如果两个接口提供了相同方法名的默认方法,就出现了选择二义性问题
接口与抽象类对比
| | 抽象类 | 接口 | | —- | —- | —- | | 相同 | 抽象的,不可实例化 | 抽象的,不可实例化 | | 相同 | 包含抽象方法
(没有方法体,非static、private、final的) | 包含抽象方法
(没有方法体,非static、private、final的) | | 不同 | 是类,可以包含类的一切东西 | 只能包含受限的成员(public static final)
和方法(抽象方法和默认方法) | | 不同 | 只能单一继承 | 可以多继承,甚至继承多次 |
小结
- java设计类与接口体系的原因是实现最大程度的灵活性与最大程度的代码复用
- 实现某个接口代表对面向对象系统做出一种承诺,当你对该类成员调用某方法时,可以利用多态做出正确的响应
- instanceof不仅仅可以检测是否是一个对象的实例,也可以检测是否是一个接口的实例
API应用程序接口(application program interface)和java中的接口有相似的含义,代表和一个系统通信时遵循的一系列约定
实战案例
Comparable接口,(set中要避免比较完毕返回0)
- Comparable接口,(使用collection.sort对某类型)
- 实现一个通用的过滤器
- Files.walkFileTree 自定义实现一个文件过滤器 ```java // comparable约定自然顺序为从小到大,ab返回正数,a=b返回0 // 修复compareTo方法不正确实现导致产生相同对象,进而导致set错误丢失对象的问题 import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.TreeSet;
public class User implements Comparable
/** 用户名 */
private final String name;
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User person = (User) o;
return Objects.equals(id, person.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
/** 老板说让我按照用户名排序 */
@Override
public int compareTo(User o) {
if(name.compareTo(o.name)==0){
return id.compareTo(o.id);
}
return name.compareTo(o.name);
}
public static void main(String[] args) {
List<User> users =
Arrays.asList(
new User(100, "b"),
new User(10, "z"),
new User(1, "a"),
new User(2000, "a"));
TreeSet<User> treeSet = new TreeSet<>(users);
// 为什么这里的输出是3?试着修复其中的bug
System.out.println(treeSet.size());
}
}
```java
使用Collection.sort对某类型排序,需要在该类实现comparable接口
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Point implements Comparable<Point> {
private final int x;
private final int y;
// 代表笛卡尔坐标系中的一个点
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Point point = (Point) o;
if (x != point.x) {
return false;
}
return y == point.y;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
@Override
public String toString() {
return String.format("(%d,%d)", x, y);
}
// 按照先x再y,从小到大的顺序排序
// 例如排序后的结果应该是 (-1, 1) (1, -1) (2, -1) (2, 0) (2, 1)
public static List<Point> sort(List<Point> points) {
Collections.sort(points);
return points;
}
public static void main(String[] args) throws IOException {
List<Point> points =
Arrays.asList(
new Point(2, 0),
new Point(-1, 1),
new Point(1, -1),
new Point(2, 1),
new Point(2, -1));
System.out.println(Point.sort(points));
}
@Override
public int compareTo(Point o) {
if(x < o.getX()){
return -1;
}else if(x > o.getX()){
return 1;
}
if(y < o.getY()){
return -1;
}else if(y > o.getY()){
return 1;
}
return 0;
}
}
// 通过predicate接口抽取公用过滤器函数,以内置匿名类的策略模式简化代码
// 通过jdk内置Predicate(判定)函数接口将上述代码抽取成一个公用的过滤器函数
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class User {
/** 用户ID,数据库主键,全局唯一 */
private final Integer id;
/** 用户名 */
private final String name;
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
// 过滤ID为偶数的用户
public static List<User> filterUsersWithEvenId(List<User> users) {
return User.filter(users, new Predicate<User>() {
@Override
public boolean test(User o) {
return o.id % 2 == 0;
}
});
}
// 过滤姓张的用户
public static List<User> filterZhangUsers(List<User> users) {
return User.filter(users, new Predicate<User>() {
@Override
public boolean test(User o) {
return o.getName().startsWith("张");
}
});
}
// 过滤姓王的用户
public static List<User> filterWangUsers(List<User> users) {
return User.filter(users, new Predicate<User>() {
@Override
public boolean test(User o) {
return o.getName().startsWith("王");
}
});
}
public static List<User> filter(List<User> users, Predicate<User> predicate) {
List<User> results = new ArrayList<>();
for (User user : users) {
if (predicate.test(user)) {
results.add(user);
}
}
return results;
}
}
// 通过多态、匿名内部类实现一个文件过滤器
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
public class FileFilter {
public static void main(String[] args) throws IOException {
Path projectDir = Paths.get(System.getProperty("user.dir"));
Path testRootDir = projectDir.resolve("test-root");
if (!testRootDir.toFile().isDirectory()) {
throw new IllegalStateException(testRootDir.toAbsolutePath().toString() + "不存在!");
}
List<String> filteredFileNames = filter(testRootDir, ".csv");
System.out.println(filteredFileNames);
}
/**
* 实现一个按照扩展名过滤文件的功能
*
* @param rootDirectory 要过滤的文件夹
* @param extension 要过滤的文件扩展名,例如 .txt
* @return 所有该文件夹(及其后代子文件夹中)匹配指定扩展名的文件的名字
*/
public static List<String> filter(Path rootDirectory, String extension) throws IOException {
List<String> fileNames = new ArrayList<>();
Files.walkFileTree(rootDirectory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.getFileName().toString().endsWith(extension)){
fileNames.add(file.getFileName().toString());
}
return FileVisitResult.CONTINUE;
}
});
return fileNames;
}
}
内部类
- 如果两个类的逻辑非常紧密,可以考虑将其中一个放入另一个类中,作为内部类
- 内部类可以提供更加精细的封装,该类可以不受控制的访问外部类的所有成员(private也可以),本质上是一种封装
- 内部类之所以可以调用外围类的成员方法,是因为jvm的编译器在底层自动注入了一个外围类的实例(private 外围类 this$0)共内部类使用
- 内部类用于补充一次性的逻辑,类似于一段函数逻辑,java8可采用lambda表达式来处理
-
静态内部类
内部类和静态内部类的区别是,内部类是和外围类的实例相绑定的(隐式被this调用),而静态内部类不与任何外围类的实例绑定,所以不调用任何外围类实例方法(如果非要用,要么改为非静态,要么通过构造器传入外围类)
- 通过注入外围类的实例来在静态内部类中调用外围类的成员方法的方式其实是补充了jvm提供的方式
- 要永远优先使用静态内部类,除非编译器报错,这样可以节省创造一个对象的内存开支 ```java import java.util.ArrayList; import java.util.List; import java.util.function.Consumer;
public class Home {
List
public List<String> getCatNames() {
CatNameCollector collector = new CatNameCollector();
cats.forEach(collector);
return collector.getCatNames();
}
// 记录日志
private void log(Cat cat) {
System.out.println("Collecting cat " + cat.getName());
}
// 在这个类里会产生一个编译错误
// 请思考一下为什么
// 并将此类改写成非静态的内部类,以修复此问题
class CatNameCollector implements Consumer<Cat> {
private List<String> catNames = new ArrayList<>();
@Override
public void accept(Cat cat) {
log(cat);
catNames.add(cat.getName());
}
private List<String> getCatNames() {
return catNames;
}
}
}
```java
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class Home2 {
List<Cat> cats = new ArrayList<>();
public List<String> getCatNames() {
CatNameCollector collector = new CatNameCollector(this);
cats.forEach(collector);
return collector.getCatNames();
}
// 记录日志
private void log(Cat cat) {
System.out.println("Collecting cat " + cat.getName());
}
static class CatNameCollector implements Consumer<Cat> {
// 在这个类里会产生一个编译错误
// 请思考一下为什么
// 不要将此类改写成非静态的内部类
// 而是引入一个外围类的实例以调用外围类的实例方法
// private Home2 home;
private List<String> catNames = new ArrayList<>();
private Home2 $this0;
public CatNameCollector(Home2 home) {
this.$this0 = home;
}
@Override
public void accept(Cat cat) {
$this0.log(cat);
catNames.add(cat.getName());
}
private List<String> getCatNames() {
return catNames;
}
}
}
匿名内部类
- 直接实例化一个接口或一个被继承的父类,并提供覆盖方法,其实就是在使用匿名内部类的实现
- 匿名内部类可以转换为lambda表达式
- 匿名内部类的字节码中是有名字的,jvm用外围类$1、外围类$2这样的形式来命名 ```java class xxx implements Predicate