public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
Optional<T> findById(ID id);
boolean existsById(ID id);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> ids);
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAllById(Iterable<? extends ID> ids);
void deleteAll(Iterable<? extends T> entities);
void deleteAll();
}
看完后的一些疑惑:
- 为什么要让S extends T,操作S,而不是直接操作T?
答:这种设计的目的是为了提供更大的灵活性和可扩展性。使用<S extends T>
表示保存的实体可以是 T 类型的任何子类型,这样在调用 save() 方法时可以传入更具体的子类型对象,而不仅限于 T 类型本身。这样做的好处是可以支持多态性,允许在运行时保存具体子类型的对象,而不仅仅是 T 类型。
举例:
假设有一个 Person 类和一个 Employee 类,其中 Employee 是 Person 的子类,如果 save() 方法只接受 T 类型的对象,那么在保存 Employee 类型的对象时将不允许传入。但是通过使用<S extends T> S save(S entity)
的泛型约束,可以传入 Employee 类型的对象进行保存。
public class Person {
... }
public class Employee extends Person {
... }
Employee employee = new Employee();
repository.save(employee);
- 为什么返回类型是Iterable,而不是List或者Set之类的?
答:使用 Iterable 和 Iterator 接口作为返回类型可以适应不同类型的集合,包括 List、Set、Collection 等。List 和 Set 都实现了 Iterable 接口。 这样设计的好处是,调用方可以根据自己的需求选择将结果集转换为不同的集合类型或直接使用迭代器进行遍历,而不会对调用方强加特定的集合类型。 - 为什么返回类型是Optional,而不是 Iterable?
答:Optional<T> findById(ID id)
方法会返回一个 Optional 实例,该实例可能包含找到的对象或者为空。使用 Optional 的好处是,调用方可以在获取结果之前检查返回的 Optional 实例是否包含非空值,从而避免出现空指针异常。
如果数据存储库中没有任何对象,返回类型为Iterable的将返回一个空的迭代器。即使集合为空,也不会引发空指针异常。 <? extends T>
和<S extends T>
的区别?
答:<? extends T>
通常用于方法参数或集合的声明,表示集合中的元素类型未知,但可以是T 类型或其子类型。这样做是为了使方法更具灵活性,可以接受不同类型的集合。通配符<? extends T>
不能用于写入数据,只能用于读取数据。
因为当使用<? extends T>
作为类型限定时,编译器无法确定具体的类型,只知道它是 T 或 T 的子类型。因此,编译器不允许向这种类型的集合中添加元素,因为无法确保添加的元素是否与实际类型兼容。
<S extends T>
用于在定义泛型类或泛型方法时指定类型参数的范围。它限制了类型参数的上界,确保类型参数必须是 T 类型或其子类型。这样做是为了在泛型类或泛型方法内部可以使用 T或其子类型的特定方法和属性。
综上所述,<? extends T>
是用于限制集合的类型范围,<S extends T>
而是用于定义泛型类或泛型方法的类型参数范围。
List<? extends Fruit> fruits = new ArrayList<Apple>();
fruits.add(new Apple()); // 编译错误
List<T> list = new ArrayList<>();
list.add(new Apple()); // 正确