泛型
昨天在看代码的时候,发现自己对泛型的相关东西还是不太清楚,于是复习,总结一波泛型的知识,主要基于java编程思想
一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大 ------《Java编程思想》
1.泛型的概念
泛型实现了参数化类型类型的概念,是代码可以应用于多种类型。泛型也即意味着“适用于许多许多类型”。
泛型的设计目的:
希望类或方法能够具备最广泛的表达能力。
创造容器类
2.简单泛型
package fanxing;
public class Holder1 {
private Automobile a;
public Holder1(Automobile a){
this.a = a;
}
Automobile get(){
return a;
}
}
class Automobile{}
对比
package fanxing;
public class Holder2 {
private Object a;
public Holder2(Object a){
this.a = a;
}
public void set(Object a){
this.a = a;
}
public Object get(){ //因为这块的返回值是Object类型,是所有类的父类,具体应用到会向下转型为具体的类型
return a;
}
public static void main(String[] args){
Holder2 h2 = new Holder2(new Automobile()); //只用了一个对象Holder2,却先后三次存储三种不同的类型
Automobile a = (Automobile)h2.get(); //最开始new的Automobile类型
h2.set("not an automobile");
String s = (String)h2.get(); //接着set的String类型
h2.set(1);
Integer x = (Integer)h2.get(); //接着set的Integer类型
System.out.println(a+s+x);
}
}
可以看到下面得代码,可以实现存储多种不同类型的对象,而不用重新创建类。
泛型的主要目的之一就是指定容器要持有什么类型的对象,并且由编译器保证类型的正确性
与其使用Object,选择不指定类型,而是稍后再决定具体使用什么类型。要达到这个目的,需要使用类型参数,用尖括号扩住,放在类名后面。然后在使用这个类的时候,再用实际的类型替换此类型参数。
package fanxing;
public class Holder3<T> { //创建类型时使用参数T代替
private T a;
public Holder3(T a){
this.a = a;
}
public void set(T a){
this.a = a;
}
public T get(){
return a;
}
public static void main(String[] args){
Holder3<Automobile> h3 = new Holder3<Automobile>(new Automobile());//在创建对象时,必须指明想要持有的类型。
Automobile a = h3.get();
System.out.println(a);
}
}
多态与泛型并不冲突,所以在创建对象时可以放入类型或其子类
Java泛型的核心概念:告诉编译器想使用什么类型,然后编译器处理一切
利用泛型创建元组
package fanxing;
public class TwoTuple<A,B> {
public final A first;
public final B second;
public TwoTuple(A a,B b){
first = a;
second = b;
}
public String toString(){
return "(" + first + ',' + second + ")";
}
}
package fanxing;
public class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
public final C third;
public ThreeTuple(A a,B b,C c){
super(a,b);
third = c;
}
public String toString(){
return "(" + first + ',' + second + ',' + third + ")";
}
}
package fanxing;
public class TupleTest {
static TwoTuple<String,Integer> f(){
return new TwoTuple<String,Integer>("hi",47);
}
static ThreeTuple<Amphibian,String,Integer> g(){
return new ThreeTuple<Amphibian, String, Integer>(new Amphibian(),"hi",47);
}
private static class Amphibian {
}
public static void main(String[] args){
TwoTuple<String,Integer> ttsi = f();
System.out.println(ttsi);
System.out.println(g());
}
}
用泛型构建栈
package fanxing;
public class LinkedStack<T> {
private static class Node<U>{ //内部类,节点。有两个成员变量,一个是当前的值,另一个是接下来的值。
U item;
Node<U> next;
Node() {
item = null;
next = null;
}
public Node(U item,Node<U> next) {
this.item = item;
this.next = next;
}
boolean end(){
return item == null && next == null;
}
}
private Node<T> top = new Node<T>(); // new一个新节点
public void push(T item){ //入栈
top = new Node<T>(item,top);
}
public T pop(){ //出栈
T result = top.item;
if (!top.end()){
top = top.next;
}
return result;
}
public static void main(String[] args){
LinkedStack<String> lss = new LinkedStack<String>();
for(String s: "skldjflskjf slkdjfs sdkf sdfs".split(" ")){
lss.push(s);
}
String s;
while ((s = lss.pop()) != null){
System.out.println(s);
}
}
}
随机选取:
package fanxing;
import java.util.ArrayList;
import java.util.Random;
public class RandomList<T> {
private ArrayList<T> storage = new ArrayList<T>();
private Random rand = new Random(47);
public void add(T item){
storage.add(item);
}
public T Select(){
return storage.get(rand.nextInt(storage.size()));
}
public static void main(String[] args){
RandomList<String> rs = new RandomList<String>();
for (String s:("the quick brown fox jumped over"+"the lazy brown dog").split(" ")){
rs.add(s);
}
for(int i = 0;i<11;i++){
System.out.println(rs.Select() + " ");
}
}
}
3.泛型接口
将泛型应用于接口。
生成器:这是一种专门负责创建对象的类,实际上,这是工厂方法设计模式的一种应用。
一般而言,一个生成器只定义一个方法,该方法用以产生新的对象。
package fanxing;
public interface Generator<T> { //创建泛型接口,有next方法
T next();
}
package fanxing;
public class Coffee { //创建coffee类
private static long counter = 0;
private final long id = counter++;
public String toString(){
return getClass().getSimpleName()+" " + id;
}
}
class Latte extends Coffee{} //创建coffee的子类
class generics extends Coffee{}
class Cappuccino extends Coffee{}
class Breve extends Coffee{}
package fanxing;
import java.util.Iterator;
import java.util.Random;
//coffeeGenerator类实现了Generator接口
public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> {
//创建Class类的实例/对象
public Class[] types = {Latte.class,Cappuccino.class,Breve.class,generics.class};
private static Random rand = new java.util.Random(47);
public CoffeeGenerator(){}
private int size = 0;
public CoffeeGenerator(int sz){
size = sz;
}
// @Override
// public boolean hasNext() {
// return true;
// }
public Coffee next(){ //重写Generator接口的next()方法
try{
return (Coffee)types[rand.nextInt(types.length)].newInstance();
}
catch(Exception e){
throw new RuntimeException(e);
}
}
@Override
public Iterator<Coffee> iterator() { //重写Iterable接口的iterator方法
return (Iterator<Coffee>) new CoffeeInterator(); //返回值为向下转型的CoffeeInterator对象
}
class CoffeeInterator implements Iterator<Coffee>{
int count = size;
public boolean hasNext(){ //重写接口的方法
return count>0; //返回值为true,则才有更多地元素
}
public CoffeeInterator(){};
public Coffee next(){ //重写接口的方法
count--; //调用这个方法时,count--
return CoffeeGenerator.this.next(); //返回值为CoffeeGenerator的next方法
}
public void remove(){
throw new UnsupportedOperationException();
}
// @Override
// public Iterator<Coffee> iterator() {
// return (Iterator<Coffee>) new CoffeeInterator();
// }
}
public static void main(String[] args){
CoffeeGenerator gen = new CoffeeGenerator(); //new了一个CoffeeGenerator对象
for (int i = 0;i<5;i++){
System.out.println(gen.next()); //调用对象的next()方法,next方法通过newInstance创建实例。因为type是Class类的对象
}
for (Coffee c:new CoffeeGenerator(5)){ //首先调用构造器,然后调用重写的iterator方法,这个方法返回值为CoffeeInterator,然后执行此类的hasNext()方法,返回值为真时,再调用CoffeeInterator.next(),此next方法又调用了CoffeeGenerator.next()方法,最后还是执行newInstance来创建对象
System.out.println(c);
}
}
}
package fanxing;
import java.util.Iterator;
public class IterableFibonacci extends Fibonacci implements Iterable<Integer> {
private int n;
public IterableFibonacci(int count){
n = count;
}
public Iterator<Integer> iterator(){
return new Iterator<Integer>() {
public boolean hasNext(){ return n>0;}
public Integer next(){
n--;
return IterableFibonacci.this.next(); //实际上调用了父类的next方法,而不是接口的方法
}
public void remove(){
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args){
for (int i:new IterableFibonacci(18)){
System.out.println(i+' ');
}
}
}
4.泛型方法
泛型应用于整个类上。但泛型也可以应用在方法上。
泛型方法使得该方法能够独立于类人产生变化。
原则:无论何时,只要能做到,就应该尽量使用泛型方法。
对于一个static方法而言,无法访问泛型类的类型参数。所以如果static方法需要使用泛型能力,就必须使其成为泛型方法。
package fanxing;
public class GenericMethods {
public <T> void f(T x){
System.out.println(x.getClass().getName());
}
public static void main(String[] args){
GenericMethods gm = new GenericMethods();
gm.f(" ");
gm.f(1);
gm.f(1.0);
gm.f("sdf");
gm.f(gm);
}
}
注:当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这就是:类型参数推断
package fanxing;
import java.util.ArrayList;
import java.util.List;
public class GenericVarargs {
public static <T> List<T> makeList(T...args){
List<T> result = new ArrayList<T>();
for (T item:args)
result.add(item);
return result;
}
public static void main(String[] args){
List<String> ls = makeList("a");
System.out.println(ls);
ls = makeList("A","B","c");
System.out.println(ls);
}
}
5.匿名内部类
泛型还可以应用于内部类以及匿名内部类
package fanxing;
import com.sun.research.ws.wadl.Link;
import java.util.*;
public class BankTeller {
public static void serve(Teller t,Customer c){
System.out.println(t+"serves"+c);
}
public static void main(String[] args){
Random rand = new Random(47);
Queue<Customer> line = new LinkedList<Customer>();
List<Teller> tellers = new ArrayList<Teller>();
}
}
class Customer{
private static long counter = 1;
private final long id = counter++;
private Customer(){}
public String toString(){return "Customer" + id;}
public static Generator<Customer> generator(){
return new Generator<Customer>() {
@Override
public Customer next() {
return new Customer();
}
}; //匿名内部类
}
}
class Teller{
private static long counter = 1;
private final long id = counter++;
private Teller(){}
public String toString(){return "Teller" + id;}
public static Generator<Teller> generator = new Generator<Teller>() {
@Override
public Teller next() {
return new Teller();
}
};
}
6.构建复杂模型
泛型的一个重要好处是能够简单而安全得创建复杂的模型
如list
package fanxing;
import java.util.ArrayList;
public class TupleList<A,B,C> extends ArrayList<ThreeTuple<A,B,C>> {
public static void main(String[] args){
TupleList<TupleTest.Amphibian,String,Integer> t1 = new TupleList<TupleTest.Amphibian,String,Integer>();
t1.add(TupleTest.g());
t1.add(TupleTest.g());
for (ThreeTuple<TupleTest.Amphibian,String,Integer> i:t1){
System.out.println(i);
}
}
}
创造了一个元组的list
7.擦除
在泛型代码内部,无法获得任何有关泛型参数类型的信息
Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被参数了,你唯一知道的就是你在使用一个对象。因此List和List在运行时事实上是相同的类型。这两种形式都擦除成他们“原生”的类型,即List。
package fanxing;
import org.apache.hadoop.util.hash.Hash;
class HasF{
public void f(){
System.out.println("HasF.f()");
}
}
class Manipulator<T extends HasF>{
private T obj;
public Manipulator(T x){ obj = x;}
public void manipulate(){obj.f();}
}
public class Manipulation {
public static void main(String[] args){
HasF hf = new HasF();
Manipulator<HasF> manipulator = new Manipulator<HasF>(hf);
manipulator.manipulate();
}
}
为了调用f(),必须协助泛型类,给定泛型类的边界,以此告知编译器只能接受遵循这个边界的类型。这里重用了extends HasF。
边界声明T必须具有HasF或者从HasF导出的类型。
当你希望代码能够跨多个类工作时,使用泛型才有所帮助。
擦除机制的核心动机是它使得泛化的客户端可以用非泛化的类库来使用。
擦除的主要正当理由是非泛化代码到泛化代码的转变过程,以及在不破坏现有类库的情况下,将泛型融入Java语言。
8.擦除的补偿
有时必须通过引入类型标签来对擦除进行补偿。这意味着你需要显式地传递你的类型的Class对象。
package fanxing;
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
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>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
}
编译器将确保类标签可以匹配泛型参数
9.边界
边界可以用于在泛型的参数类型上设置限制条件。重用extends关键字来实现
10.通配符
在泛型参数表达式中的问号
11.自限定类型
class SelfBounded<T extends SelfBounded<T>>{......}
1.古怪的循环泛型
一个简单版本:没有自限定的边界。不能直接继承一个泛型函数,但是,可以继承在其自己的定义中使用这个泛型参数的类。
class GenericType<T>{}
public class CuriouslyRecurringGeneric extends GenericType<CuriouslyRecurringGeneric>{}
创建一个新类,它继承自一个泛型类型,这个泛型类型接受此类名作为其参数。
package fanxing;
public class BassicHolder<T> {
T element;
void set(T arg){element= arg;}
T get(){return element;}
void f(){
System.out.println(element.getClass().getSimpleName());
}
}
package fanxing;
class Subtype extends BassicHolder<Subtype>{}
public class CRGWithBassicHolder {
public static void main(String[] args){
Subtype st1 = new Subtype(),st2 = new Subtype();
st1.set(st2);
Subtype st3 = st1.get();
st1.f();
}
}
新类Subtype接受的参数和返回的值具有Subtype类型而不仅仅是基类BassicHolder类型。这就是CRG的本质:基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模板,但是这些功能对于其所有参数和返回值,将使用导出类型。也就是说,在所产生的类中将使用确切类型而不是基类形。因此,在Subtype中,传递给set()的参数和从get()返回的类型都是确切的Subtype
2.自限定
BasicHolder可以使用任何类型作为其泛型参数。自限定将采取额外的步骤,强制泛型当做其自己的边界参数来使用。
package fanxing;
class SelfBound<T extends SelfBound<T>>{
T element;
SelfBound<T> set(T arg){
element = arg;
return this;
}
T get() {return element;}
}
class A extends SelfBound<A>{}
class B extends SelfBound<A>{}
class C extends SelfBound<C>{
C setAndGet(C arg){
set(arg);
return get();
}
}
class D{}
class F extends SelfBound{}
public class SelfBounding {
public static void main(String[] args){
A a = new A();
a.set(new A());
a = a.set(new A()).get();
a = a.get();
C c = new C();
c = c.setAndGet(new C());
}
}
自限定参数可以保证类型参数必须与正在被定义的类相同。