泛型
-
基本类型不能作为泛型参数
-
泛型类
public class Position <T>{ private T sth; }
-
泛型方法
public class GenericMethods { public <T> void f(T x) { System.out.println(x.getClass().getName()); } }
-
通常来说使用泛型方法时不需要手动指出参数类型,可以自动推断
public <A,B> Tuple2<A,B>tuple(A a,B b)
- 第一个<>是方法的参数,在调用时可以自动推断
-
第二个<>是返回值的参数,应该指出
Tuple2
表示为参数化的对象,类似一种向上转型 -
允许多参数,但不能重写一个函数
public class UseList <W,T>{ void f(List <T> v){} void f(List <W> v){}//错误 }
-
泛型接口
-
生成器模式,
Spplier<T>
,要求重写实现public T get(){}
public class Fibonacci implements Supplier <Integer> { private int count = 0; @Override public Integer get() { return fib(count++); } private int fib(int n) { if(n < 2) return 1; return fib(n-2) + fib(n-1); } public static void main(String [] args) { Stream.generate(new Fibonacci()) .limit(18) .map(n -> n + " ") .forEach(System.out:: print); } } //通过适配器再实现可迭代 public class IterableFibonacci extends Fibonacci implements Iterable <Integer> { private int n; public IterableFibonacci(int count) { n = count; } @Override public Iterator <Integer> iterator() { return new Iterator <Integer>() { @Override public boolean hasNext() { return n > 0; } @Override public Integer next() { n--; //内部类访问外部类对象 return IterableFibonacci.this.get(); } @Override public void remove() { // Not implemented throw new UnsupportedOperationException(); } }; } public static void main(String [] args) { for(int i : new IterableFibonacci(18)) System.out.print(i + " "); } }
-
扁平化多级集合
class Shelf extends ArrayList <Product> { Shelf(int nProducts) { Suppliers.fill(this, Product.generator, nProducts); } } class Aisle extends ArrayList <Shelf> { Aisle(int nShelves, int nProducts) { for(int i = 0; i < nShelves; i++) add(new Shelf(nProducts)); } }
-
一个类不能同时实现一个泛型接口的两种变体(不同泛型参数的泛型接口),因为他们在类型擦除后实际上是相同的
-
针对特定类型的非泛型实现
- 实现为特定的类型
public class CharSet1 implements Set<Character> { private String s = ""; @Override public boolean contains(Character e) { return s.indexOf(e) != -1; } @Override public void add(Character e) { if (!contains(e)) s += e; } // ... }
- 实现为特定的类型
-
泛型实现
- 实现之后仍为泛型 (如ArrayList)
public class HashSet<E> implements Set<E> { // ... }
- 实现之后仍为泛型 (如ArrayList)
类型擦除¶
- 类型擦除
-
Java泛型内部不存在有关泛型参数类型的信息
class Manipulator <T> { private T obj; Manipulator(T x) { obj = x; } // Error: cannot find symbol: method f(): public void manipulate() { obj.f(); } } public class Manipulation { public static void main(String [] args) { HasF hf = new HasF(); Manipulator <HasF> manipulator = new Manipulator <>(hf); manipulator.manipulate(); } } //java 中只知道是一个未知的 T(会被认为是 Object),并不知道具体是什么,因此不认为有 f class Manipulator2 <T extends HasF> { private T obj; Manipulator2(T x) { obj = x; } public void manipulate() { obj.f(); } } //这样就知道是 HasF 了!
-
java的字节码中实际上没有泛型,是将类型参数用边界类型替换(类型替换)
- 如无限制的泛型\
会替换为Object,而有限制的\ 会被替换为X - 也就是说T类型并不是真正存在的,不能直接使用T进行实例,使用T作为参数
class<T>
也是存在问题的 - 但是T作为返回值时可以正确处理,会返回传入的类型
public class Holder <T> { private T obj; public void set(T obj){ this.obj = obj; } public T get(){ return obj; } public void testT(Object arg){ if (arg instanceof T){ ... } //编译错误 T var = new T(); //编译错误 T [] array = new T [100]; //编译错误 } } }
public class Holder <T> {
private T obj; //在编译时,该类中的所有的 T 都会被替换为边界类型 Object。
public void set(T obj){ this.obj = obj; }
public T get(){ return obj; }
public static void main(String [] args){
Holder <Integer> holder = new Holder <>();
//编译器会检查实参是不是一个 Integer,
//虽然这里的 1 是 int 类型,但是因为自动包装机制的存在,
//他会被转化为一个 Integer,因此能够通过类型检查。
holder.set(1);
//编译器也会进行类型检查,
//并且自动插入一个 Object 类型到 Integer 类型的转型操作。
Integer obj = holder.get();
}
}
- 对泛型的处理全部集中在编译期,在编译时,编译器会执行如下操作。
- 会将泛型类的类型参数都用边界类型替换。
- 对于传入对象给方法形参的指令,编译器会执行一个类型检查,看传入的对象是不是类型参数所指定的类型。
- 对于返回类型参数表示对象的指令,也会执行一个类型检查,还会插入一个自动的向下转型,将对象从边界类型向下转型到类型参数所表示的类型。
- 任何需要在运行时知道确切类型的操作都无法运行(因为
T
的具体类型信息只存在于编译时期的检查,在运行时期信息已经丢失了)[[java泛型]] - 如
new T()
、x instanceof T
- 虽然不能使用T进行类型检查,但是可以通过传入
class<T>
实现
class Building {}
class House extends Building {}
public class ClassTypeCapture <T> {
Class <T> kind;
public ClassTypeCapture(Class <T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public static void main(String [] args) {
ClassTypeCapture <Building> ctt1 =
new ClassTypeCapture <>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture <House> ctt2 =
new ClassTypeCapture <>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
}
/* Output:
true
true
false
true
*/
生成泛型对象¶
- 法一,工厂模式
class Holder <T>{ private T t; public void init(IFactory <T> factory){ this.t = factory.create(); // 此处即为 new T()的工厂方法的实现 } } interface IFactory <T>{ //接口也可以参数化 T create(); } class IntegerFactory implements IFactory <Integer>{ public Integer create(){ return new Integer(10); } } public class newTwithFactory{ public static void main(String [] args){ Holder <Integer> holder = new Holder <>(); //明确支出创建对象使用的工厂方法 holder.init(new IntegerFactory()); } }
-
使用一个工厂方法,如
IntegerFactory
,它具体指明了要创建的对象的类型(在这个例子中是Integer
)。 -
法二Class
- 将
Class
对象作为类型标签来存储和使用类型信息。 - 可以实现在运行中查询、使用对象类型。
class Holder <T>{ private T t; private Class <T> kind; public Holder(Class <T> kind){ this.kind = kind; } public void init(){ try{ this.t = kind.newInstance(); }catch (Exception e){ e.printStackTrace(); } } public static void main(String [] args) { Holder <Integer> holder = new Holder <>(Integer.class); holder.init(); } }
- 只能使用无参构造
协变与逆变¶
- 协变(covariant),如果它保持了子类型序关系≦。该序关系是:子类型≦基类型。
- 逆变(contravariant),如果它逆转了子类型序关系。
-
不变(invariant),如果上述两种均不适用。
-
基类会劫持接口
- 对于相同的泛型接口,子类不能实现与基类实现的接口的泛型参数不同的(类型擦除后都一样了)
泛型数组¶
class Fruit{}
class Apple extends Fruit{}
public class NonConvariantGeneric {
List <Fruit> flist = new ArrayList <Apple>(); //编译错误
}
List
不是Fruit的List
。Apple的List
将持有Apple和Apple的子类型,Fruit的List
将持有任何类型的Fruit
。这包括Apple,但是它不是一个Apple的List
,它仍然是Fruit的List
。Apple的List
在类型上不等价于Fruit
的List,即使Apple是一种Fruit类型。
- Java泛型中规定,即使泛型类型具有继承关系,但是并不意味着该泛型类型的容器也具有继承关系。
自限定类型¶
- 限制一个泛型类的类型参数,使其只能是该泛型类的子类。
- 与普通继承相比,使用了泛型来确保类型安全,并在子类中特化了基类的行为。在普通的继承中,子类继承父类的属性和方法并可以重写它们。在这种情况下,子类不仅继承了基类的属性和方法,而且通过泛型参数为这些属性和方法提供了一个具体的类型(即其自身)。
-
这提供了额外的类型安全性,并允许在子类中为这些方法提供更具体的实现,同时不牺牲代码的复用性。
-
泛型类强制实例时使用这种模式
//forcing the generic to be used as its own bound argument class SelfBounded <T extends SelfBounded<T> > { T element; SelfBounded <T> set(T arg){ element = arg; return this; } T get(){return element;} } class A extends SelfBounded <A> {} class B extends SelfBounded <A> {} //ok class E extends SelfBounded <D>{} //error public static void main(String [] args){ A a = new A(); a.set(new A()); a.print(); B b = new B(), a2 = new B(); //b.set(b2); //Error //b.print(); }
-
参数协变性
- 方法参数类型会随着子类而变化
- 允许子类方法比其重写的基类方法更具体的类型
- 使用子限定类型就实现了将子类类型作为返回值
通配符¶
- 边界
-
可以对泛型进行一定限制
public class Computer <T extends Disk>{//必须是 Disk 或子类
-
使用类型和接口限制
interface HasColor{ java.awt.Color getColor(); } class Colored <T extends HasColor>{...} class Dimension { public int x, y, z; } class ColoredDimension <T extends HasColor & Dimension>{...} //错误! class ColoredDimension <T extends Dimension & HasColor>{ //why? }
-
类型在前接口在后,并且同样只能继承一个类
-
常用用于方法参数,实现泛型类型的多态,对于数组讨论集合自身的类型而不是所持有的元素类型
- 由于list不存在直接继承关系,可以使用通配符实现通用方法
public void print(List <?> animals) { animals.forEach(a -> System.out.println(((Animal) a).getFood())); } public static void main(String [] args) { Test test = new Test(); List <Animal> animals = Arrays.asList(new Animal()); List <Cat> cats = Arrays.asList(new Cat()); test.print(animals); // 正确执行 test.print(cats); // 正确执行 }
class Fruit{}
class Apple extends Fruit{}
public class GenericsAndCovariance {
public static void main(String [] args){
//一个能放水果以及一切是水果派生类的盘子, 啥水果都能放的盘子
//Plate <? extends Fruit> 和 Plate <Apple> 最大的区别就是:
//Plate <? extends Fruit> 是 Plate <Fruit> 以及 Plate <Apple> 的基类。
Plate <? extends Fruit> p = new Plate <Apple>(new Apple());
// a list of any type that's inherited from Fruit
List <? extends Fruit> flist = new ArrayList <Apple>();
}
}
- 协变
Plate<?extends Fruit>
可以放子类型中任意的 -
List<?>
由于无法在编译期间确定泛型的实际类型,所以没法向List<?>
中添加除了null
外的任意类型元素。(即我不知道最高会是谁,因此我要是放入后得向下转型呢?但是读取可以,因为这总会是向上转型)class Fruit{} class Apple extends Fruit{} Plate <? extends Fruit> p = new Plate <Apple>(new Apple()); //不能存入任何元素 p.set(new Fruit()); //Error p.set(new Apple()); //Error //读取出来的东西只能存放在 Fruit 或它的基类里。 Fruit newFruit1 = p.get(); Object newFruit2 = p.get(); Apple newFruit3 = p.get(); //Error
- 对于
Plate<? exyends fruit>
编译器并不知道具体会被初始化为什么类型,因此如果允许放入元素则可能造成不匹配(类型错误) - 而取出时会获得上界的类型,构成了多态
- 对于非集合类型也会有限制,如
Tile<? extends Thing> tile;
-
注意,这种参数并不是针对对tile变量的赋值和访问,而是针对对tile内部含有泛型参数/返回值的方法的调用
-
逆变
Plate<?super Fruit>
可以放基类中任意 -
我们在编译期只能知道
下界通配符
的下界是什么类型,所以在添加元素时,只能向其中添加下界类型。由于编译期无法知晓具体的实际类型,所以只能使用Object
来**接收获取的元素class Fruit{} class Apple extends Fruit{} public class GenericsAndCovariance { public static void main(String [] args){ Plate <? super Fruit> p = new Plate <Fruit>(new Fruit()); //存入元素正常 p.set(new Fruit()); p.set(new Apple()); //读取出来的东西只能存放在 Object 类里。 Apple newFruit3 = p.get(); //Error Fruit newFruit1 = p.get(); //Error Object newFruit2 = p.get(); } }
-
List<?>
(无界通配符): - 这表示一个未知类型的列表。
?
是一个通配符,代表任何类型。 -
你可以从
List<?>
读取数据,读取的数据将被视为Object
类型,但你不能向其中写入除null
之外的任何数据,因为你不知道列表的确切类型。 -
List
(原生类型): - 这是一个原生类型的列表,没有泛型信息。在泛型被引入Java之前就存在。
- 你可以向其中添加任何类型的对象,也可以从中读取数据,读取的数据被视为
Object
类型。 -
它不安全,因为它不提供类型检查,可能导致运行时错误(例如,
ClassCastException
)。 -
List<Object>
: - 这表示一个可以包含任何类型对象的列表。
- 与
List<?>
不同,你可以安全地向List<Object>
添加任何类型的对象(除了基本类型,它们需要被装箱)。 - 这是一个明确声明你可以存储任何类型对象的列表,提供了类型安全性(即使是
Object
级别的)。
混型¶
- 混合多个类的能力,生成一个可以代表混型中全部类型的类
- 使用接口进行混合
-
因为接口可以多继承,并且在类内保存所有接口的实例
class Mixin extends BasicImp implements TimeStamped, SerialNumbered { private TimeStamped timeStamp = new TimeStampedImp(); private SerialNumbered serialNumber = new SerialNumberedImp(); @Override public long getStamp() { return timeStamp.getStamp(); } @Override public long getSerialNumber() { return serialNumber.getSerialNumber(); } }
潜在类型机制¶
一个东西长得像鸭子,行为像鸭子,那他就是鸭子
-
不一定是同一类型,有正确方法就可以用
class Dog { public: void speak() { cout << " Arf!" << endl; } void sit() { cout << " Sitting " << endl; } void reproduce() {} }; class Robot { public: void speak() { cout << " Click!" << endl; } void sit() { cout << " Clank!" << endl; } void oilChange() {} }; template <class T> void perform(T anything) { anything.speak(); anything.sit(); }
-
在Java中没法实现,因此叫辅助潜在类型机制
- 使用方法引用:不是传入对象,而是传入方法实现调用
class PerformingDogA extends Dog { public void speak() { System.out.println("Woof!"); } public void sit() { System.out.println("Sitting"); } public void reproduce() {} } class RobotA { public void speak() { System.out.println("Click!"); } public void sit() { System.out.println("Clank!"); } public void oilChange() {} } class CommunicateA { public static <P> void perform(P performer, Consumer <P> action1, Consumer <P> action2) { action1.accept(performer); action2.accept(performer); } } public class DogsAndRobotMethodReferences { public static void main(String [] args) { CommunicateA.perform(new PerformingDogA(), PerformingDogA:: speak, PerformingDogA:: sit); CommunicateA.perform(new RobotA(), RobotA:: speak, RobotA:: sit); CommunicateA.perform(new Mime(), Mime:: walkAgainstTheWind, Mime:: pushInvisibleWalls); } }