泛型
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的类型参数是使用尖括号和大驼峰命名的名称:<A, B, ...> 来指定的,泛型类型参数一般用 ((ParameterizedType)obj.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
泛型类/结构体
- Rust泛型结构体
// 具体类型 User。
struct User{
val:i32
};
// `Single` 是个同样也是具体类型,User 取上面的定义。
struct Single(User);
// ^ 这里是 `Single` 对类型 User 的第一次使用。
// 此处 `<T>` 在第一次使用 `T` 前出现,所以 `SingleGen` 是一个泛型类型。
// 因为 `T` 是泛型的,所以它可以是任何类型,包括在上面定义的具体类型 User。
struct SingleGen<T>(T);
fn main() {
// `Single` 是具体类型,并且显式地使用类型 `A`。
let _s = Single(A);
// 创建一个 `SingleGen<char>` 类型的变量 `_char`,并令其值为 `SingleGen('a')`
// 这里的 `SingleGen` 的类型参数是显式指定的。
let _char: SingleGen<char> = SingleGen('a');
// `SingleGen` 的类型参数也可以隐式地指定。
let _t = SingleGen(A); // 使用在上面定义的 `A`。
let _i32 = SingleGen(6); // 使用 `i32` 类型。
let _char = SingleGen('a'); // 使用 `char`。
}
- Java泛型类
class User{}
class Single{
User user;
}
// 此处 `<T>` 在第一次使用 `T` 前出现,所以 `SingleGen` 是一个泛型类型。
// 因为 `T` 是泛型的,所以它可以是任何类型,包括在上面定义的具体类型 User。
class SingleClass<T> {}
public static void main(String[] args) {
User user=new User();
Single single=new Single();
single.user=user;
SingleClass<User> singleClass=new SingleClass<User>();
List list=new Arraylist<Integer>();
}
泛型函数/实现
- Rust泛型函数/实现
// 普通函数
fn reg_fn(_s: User) {}
// SingleGen<>` 显式地接受了类型参数 `User,且在 `gen_spec_t` 中,`User` 没有被用作泛型类型参数,所以函数不是泛型的。
fn gen_spec_t(_s: SingleGen<User>) {}.
// <T> 代表该函数是关于 `T` 的泛型函数。
fn generic<T>(_s: SGen<T>) {}
fn main(){
//隐式调用泛型方法 T=>User
generic(SingleGen(User));
// 泛型方法显示调用
generic::<char>(SingleGen('a'));
}
struct GenUser<T>{
gen_User: T
}
// User 的 `impl` 在这里可以定义User结构体的关联方法
impl User {
fn value(&self) -> &f64 { &self.val }
}
// SingleGen的 `impl`,指定 `T` 是泛型类型
impl <T> SingleGen<T> {
fn value(&self) -> &T { &self.gen_val }
}
fn main() {
let x = User { val: 3 };
let y = GenVal { gen_val: 3i32 };
println!("{}, {}", x.value(), y.value());
}
- Java泛型函数
public class Executer<T>{
// <T> 代表泛型方法
public <T> Map<String,T> execute(T t){
...
return new HashMap<String,T>(){{put("success",t)}};
}
}
泛型trait
struct User;
struct Role;
// 授权 trait
trait Authorization<T> {
// 定义一个调用者的方法,接受一个额外的参数 `T`,但不对它做任何事。
fn authorization(self, _: T);
}
impl<T, U> Authorization<T> for U {
// 此方法获得两个传入参数的所有权,执行授权。
fn authorization(self, _: T) {
println!("{}","执行授权");
}
}
fn main() {
let user = User;
let role = Role;
user.authorization(role);
}
泛型约束
Rust
在Rust使用泛型时,类型参数常常必须使用 trait 作为约束来规定结构体应实现哪些功能。如上面那样约束用户的授权
// 定义一个函数 `printer`,接受一个类型为泛型 `T` 的参数,
// 其中 `T` 必须实现 `Display` trait。
struct S<T: Display>(T);
// 报错!`Vec<T>` 未实现 `Display`。此次泛型具体化失败。
let s = S(vec![1]);
还有就是用trait来限制一个实例的可以调用的实现的trait的方法trait约束 矩形实现了求面积的trait,所以在area方法执行是参数T只能是实现了求面积的trait的类型结构体,不然在编译器就会报错
// 这个 trait 用来实现打印标记:`{:?}`。
use std::fmt::Debug;
trait HasArea {
fn area(&self) -> f64;
}
impl HasArea for Rectangle {
fn area(&self) -> f64 { self.length * self.height }
}
#[derive(Debug)]
struct Rectangle { length: f64, height: f64 }
#[allow(dead_code)]
struct Triangle { length: f64, height: f64 }
// 泛型 `T` 必须实现 `Debug` 。只要满足这点,无论什么类型
// 都可以让下面函数正常工作。
fn print_debug<T: Debug>(t: &T) {
println!("{:?}", t);
}
// `T` 必须实现 `HasArea`。任意符合该约束的泛型的实例
// 都可访问 `HasArea` 的 `area` 函数
fn area<T: HasArea>(t: &T) -> f64 { t.area() }
fn main() {
let rectangle = Rectangle { length: 3.0, height: 4.0 };
let _triangle = Triangle { length: 3.0, height: 4.0 };
print_debug(&rectangle);
println!("Area: {}", area(&rectangle));
//print_debug(&_triangle);
//println!("Area: {}", area(&_triangle));
// | 报错:未实现 `Debug` 或 `HasArea`。
}
此外Rust还有多个trait来约束,如下,compare_prints方法的参数T需要同时实现Debug
和Display
这俩个trait;针对trait较少的情况可以使用+
来实现。如果较多的情况来说会使用where从句
use std::fmt::{Debug, Display};
fn compare_prints<T: Debug + Display>(t: &T) {
println!("Debug: `{:?}`", t);
println!("Display: `{}`", t);
}
fn compare_types<T: Debug, U: Debug>(t: &T, u: &U) {
println!("t: `{:?}", t);
println!("u: `{:?}", u);
}
fn main() {
let string = "words";
let array = [1, 2, 3];
let vec = vec![1, 2, 3];
compare_prints(&string);
//compare_prints(&array);
// 试一试 ^ 将此行注释去掉。
compare_types(&array, &vec);
}
// where从句
impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}
// 使用 `where` 从句来表达约束
impl <A, D> MyTrait<A, D> for YourType where
A: TraitB + TraitC,
D: TraitE + TraitF {}
如果实现了trait 的结构体是泛型的,则须遵守类型规范要求trait
的使用者必须指出trait
的全部泛型类型。Contains
trait
允许使用泛型类型 A 或 B。然后我们为 Container 类型实现了这个 trait,将 A 和 B 指定为 i32,这样就可以对 它们使用 difference() 函数。
struct Container(i32, i32);
// 这个 trait 检查给定的 2 个项是否储存于容器中
// 并且能够获得容器的第一个或最后一个值。
trait Contains<A, B> {
fn contains(&self, _: &A, _: &B) -> bool; // 显式地要求 `A` 和 `B`
fn first(&self) -> i32; // 未显式地要求 `A` 或 `B`
fn last(&self) -> i32; // 未显式地要求 `A` 或 `B`
}
impl Contains<i32, i32> for Container {
// 如果存储的数字和给定的相等则为真。
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
// 得到第一个数字。
fn first(&self) -> i32 { self.0 }
// 得到最后一个数字。
fn last(&self) -> i32 { self.1 }
}
// 容器 `C` 就包含了 `A` 和 `B` 类型。鉴于此,必须指出 `A` 和 `B` 显得很麻烦。
fn difference<A, B, C>(container: &C) -> i32 where
C: Contains<A, B> {
container.last() - container.first()
}
这样的处理导致different方法的泛型trait约束必须把所有的泛型类型都列出来。这里可以使用关联类型来提升代码可读性。通过把容器内部的类型放到 trait 中作为输出类型,改写Contains
trait 如下所示 把原有impl块中的泛型A,B 通过结构体的类型传入
// 关联
trait Contains {
type A;
type B;
// 这种语法能够泛型地表示这些新类型。
fn contains(&self, &Self::A, &Self::B) -> bool;
}
// 不使用关联类型
fn difference<A, B, C>(container: &C) -> i32 where
C: Contains<A, B> { ... }
// 使用关联类型
fn difference<C: Contains>(container: &C) -> i32 { ... }
impl Contains for Container {
// 指出 `A` 和 `B` 是什么类型。如果 `input`(输入)类型
// 为 `Container(i32, i32)`,那么 `output`(输出)类型
// 会被确定为 `i32` 和 `i32`。
type A = i32;
type B = i32;
// `&Self::A` 和 `&Self::B` 在这里也是合法的类型。
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
// 得到第一个数字。
fn first(&self) -> i32 { self.0 }
// 得到最后一个数字。
fn last(&self) -> i32 { self.1 }
}
协变与逆变
官方解释
逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果𝐴、𝐵表示类型,𝑓(⋅)表示类型转换,≤表示继承关系(比如,𝐴≤𝐵表示𝐴是由𝐵派生出来的子类);
𝑓(⋅)是逆变(contravariant)的,当𝐴≤𝐵时有𝑓(𝐵)≤𝑓(𝐴)成立;
𝑓(⋅)是协变(covariant)的,当𝐴≤𝐵时有𝑓(𝐴)≤𝑓(𝐵)成立;
𝑓(⋅)是不变(invariant)的,当𝐴≤𝐵时上述两个式子均不成立,即𝑓(𝐴)与𝑓(𝐵)相互之间没有继承关系。
协变与逆变主要针对继承关系的类型转换, Rust中没有结构体继承,更多的是trait,组合的形式为主。协变与逆变用java类描述下。其实Java泛型是不变的, Java的数组是协变的,编译允许,尽管编译器允许了这样做,运行时的数组机制知道它处理的是子类数组,因此会在向数组中放置异构类型时抛出异常。java可以通过通配符< ? super Xxx>
和< ? extend Xxx>
来实现协变与逆变.多态就顺道说下里氏替换。
里氏替换原则包含一下四层含义:
-
子类完全拥有父类的方法,且具体子类必须实现父类的抽象方法;
-
子类中可以增加自己的方法;
-
当子类覆盖或者实现父类方法时,方法的形参要比父类方法的更为宽松;
-
当子类覆盖或者实现父类方法时,方法的返回值要比父类的更严格。
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
//协变
List<? extends Fruit> flist = new ArrayList<Apple>();
//逆变
void writeTo(List<? super Apple> apples) {
apples.add(new Apple());
apples.add(new Jonathan());
}
producer-extends, consumer-super(PECS)
从数据流来看,extends是限制数据来源的(生产者),而super是限制数据流入的(消费者)
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
Java自限定
class SelfClass<T extends SelfClass<T>> { }
基类用子类代替其参数,泛型基类变成了一种其所有子类的公共方法模版。
Java版本的SingleFlight
public class SingleFlight {
private ReentrantLock reentrantLock;
private HashMap<String, Call> cache;
public SingleFlight() {}
public SingleFlight(ReentrantLock reentrantLock) {
this.reentrantLock = reentrantLock;
}
static class Call<T> {
private CountDownLatch countDownLatch;
private T t;
private int dups;
}
public <T> Result<T> Do(String key, Supplier<T> fn) {
this.reentrantLock.lock();
if (this.cache == null) {
this.cache = new HashMap<>();
}
Call<T> call = (Call<T>) this.cache.get(key);
try {
if (call != null) {
call.dups++;
this.reentrantLock.unlock();
call.countDownLatch.await();
return new Result<>(call.t, false);
}
call = new Call<T>();
call.countDownLatch = new CountDownLatch(1);
this.cache.put(key, call);
call.t = fn.get();
call.countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
this.reentrantLock.unlock();
return new Result<>(null, false);
}
this.reentrantLock.unlock();
return new Result<>(call.t, call.dups > 0);
}
}
class Result<T> {
public T t;
public boolean shared;
public Result(T t, boolean shared) {
this.shared = shared;
this.t = t;
}
}