带你入门Java的泛型
泛型
1、简单泛型
泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。
泛型暂时不指定类型,在使用时决定具体使用什么类型。通过<t>来实现,t就是类型参数。
(1)元组
class twotuple<a,b>{ public final a first; public final b second; public twotuple(a a,b b){ first = a; second = b; } @override public string tostring() { return "{ " + first + ", " + second + '}'; } }
(2)堆栈
class linkedstack<t>{ private class node { t item; node next; node() { item = null; next = null; } node(t item, node next) { this.item = item; this.next = next; } boolean end() { return item == null && next == null; } } private node top = new node(); public void push(t item) { top = new node(item, top); } public t pop() { t result = top.item; if(!top.end()) top = top.next; return result; } } (3)randomlist class randomlist<t>{ private arraylist<t> storage = new arraylist<>(); private random rand = new random(47); public void add(t item){ storage.add(item); } public t select(){ return storage.get(rand.nextint(storage.size())); } }
2、泛型接口
泛型也可以应用于接口,例如生成器,这是一种专门负责创建对象的类。
import net.mindview.util.generator; import java.util.iterator; class fibonacci implements generator<integer> { private int count = 0; public integer next(){ return fib(count++); } private int fib(int n){ if(n<2) return 1; return fib(n-2) + fib(n-1); } } class iterablefibonacci implements iterable<integer> { private fibonacci fib = new fibonacci(); 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 fib.next(); } public void remove() { // not implemented throw new unsupportedoperationexception(); } }; } }
3、泛型方法
泛型方法使得该方法能够独立于类而产生变化。使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型,这称为类型参数推断。
class genericmethods{ public <t> void f(t x){ system.out.println(x.getclass().getsimplename()); } }
(1)类型推断
使用泛型有时候需要向程序中加入更多的代码。如下所示:
map<person,list<? extends pet>> petperson = new hashmap<person,list<? extends pet>>();
在泛型方法中可以通过类型推断来简化一部分工作。如下所示:
class new{ public static <k,v> map<k,v> map(){ return new hashmap<k,v>(); } public static void main(string[] args) { map<person,list<? extends pet>> petperson = new.map(); } }
类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法的结果作为参数,传递给另一个方法时,另一个方法需要显式的类型说明。如下所示:
public class explicittypespecification{ static void f(map<person,list<? extends pet>> petperson){} public static void main(string[] args) { f(new.<person,list<? extends pet>>map()); } }
(2)通用的generator
import net.mindview.util.generator; public class basicgenerator<t> implements generator<t>{ private class<t> type; public basicgenerator(class<t> type){ this.type = type; } public t next(){ try { return type.newinstance(); }catch (exception e){ throw new runtimeexception(e); } } public static <t> generator<t> create(class<t> type){ return new basicgenerator<t>(type); } }
(3)set实用工具实现数学方法
public class sets{ @suppresswarnings("unchecked") protected static <t> set<t> copy(set<t> s) { if(s instanceof enumset) return ((enumset)s).clone(); return new hashset<t>(s); } //并集 public static <t> set<t> union(set<t> a, set<t> b) { set<t> result = copy(a); result.addall(b); return result; } //交集 public static <t> set<t> intersection(set<t> a, set<t> b) { set<t> result = copy(a); result.retainall(b); return result; } //差集 public static <t> set<t> difference(set<t> superset, set<t> subset) { set<t> result = copy(superset); result.removeall(subset); return result; } //包含除了交集以外的所有元素 public static <t> set<t> complement(set<t> a, set<t> b) { return difference(union(a, b), intersection(a, b)); } }
4、擦除
java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此list<string>和list<integer>在运行时事实上是相同的类型,都被擦除成它们的“原生”类型list。
(1)迁移兼容性
泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为他们的非泛型上界。擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为“迁移兼容性”。
(2)擦除的问题
泛型的所有关于参数的类型信息都丢失了,所以不能用于显式地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。
5、擦除的补偿
(1)由于擦除原因,无法通过instanceof比较类型。如果引入类型标签,就可以转而使用动态的isinstance()。
public class classtypecapture<t>{ class<t> kind; public classtypecapture(class<t> kind){ this.kind = kind; } public boolean f(object arg){ return kind.isinstance(arg); } }
(2)创建类型实例
通过工厂对象来创建实例。如果使用类型标签,就可以使用newinstance()来创建这个类型的新对象。
class classasfactory<t>{ t x; public classasfactory(class<t> kind){ try{ x = kind.newinstance(); }catch(exception e){ throw new runtimeexception(e); } } }
如果类没有默认的构造器,上面的案例会创建失败。为了解决这个问题,可以通过显示的工厂来实现。
interface factoryi<t>{ t create(); } class foo2<t>{ private t x; public <f extends factoryi<t>> foo2(f factory){ x = factory.create(); } } class integerfactory implements factoryi<integer>{ public integer create(){ return new integer(6); } }
另一种方式是模板方法设计模式。
abstract class genericwithcreate<t>{ final t element; genericwithcreate(){ element = create(); } abstract t create(); } class x{} class creator extends genericwithcreate<x>{ x create(){ return new x(); } }
(3)泛型数组
无法通过 t[] array = new t[sz] 来创建泛型数组,一般的解决方法是在需要泛型数组的地方都使用arraylist。
在创建泛型数组时,有以下三种情况:
①创建时强制转型
public class genericarray<t>{ private t[] array; @suppresswarnings("unchecked") public genericarray(int sz){ array = (t[])new object[sz]; } public t[] rep(){ return array; } public static void main(string[] args) { genericarray<integer> gai = new genericarray<integer>(10); integer[] ia = gai.rep();//引起classcastexception object[] oa = gai.rep(); } }
②方法返回时强制转型
class genericarray2<t>{ private object[] array; @suppresswarnings("unchecked") public genericarray(int sz){ array = new object[sz]; } public t[] rep(){ return (t[])array; } public static void main(string[] args) { genericarray<integer> gai = new genericarray<integer>(10); integer[] ia = gai.rep();//引起classcastexception object[] oa = gai.rep(); } }
③使用array.newinstance()
以上两种方法都无法创建具体类型的数组,无法推翻底层的数组类型,只能是object[]。通过传入类型标记class<t>,可以从擦除中恢复。
class genericarray3<t>{ private t[] array; @suppresswarnings("unchecked") public genericarray(class<t> type,int sz){ array = (t[]) array.newinstance(type,sz); } public t[] rep(){ return array; } public static void main(string[] args) { genericarray<integer> gai = new genericarray<integer>(integer.class,10); integer[] ia = gai.rep();//可以正常运行 object[] oa = gai.rep(); } }
6、边界
边界使得你可以在用于泛型的参数类型上设置限制条件,可以按照自己的边界类型来调用方法。
public class test { public static void main(string[] args) { man m = new man(); m.hear(); m.smell(); } } interface superpower{} interface superhearing extends superpower{ void hearsubtlenoises(); } interface supersmell extends superpower{ void trackbysmell(); } class superhero<power extends superpower>{ power power; superhero(power power){ this.power = power; } power getpower(){ return power; } } class cainehero<power extends superhearing & supersmell> extends superhero<power>{ cainehero(power power){ super(power); } void hear(){ power.hearsubtlenoises(); } void smell(){ power.trackbysmell(); } } class superhearsmell implements superhearing,supersmell{ @override public void hearsubtlenoises() { system.out.println("hearsubtlenoises"); } @override public void trackbysmell() { system.out.println("trackbysmell"); } } class man extends cainehero<superhearsmell>{ man(){ super(new superhearsmell()); } }
7、通配符
(1)list<? extends fruit>协变
表示具有任何从fruit继承的类型的列表。list<? extends fruit>可以合法地指向一个list<apple>。一旦执行这种类型的向上转型,就将丢失掉向其中传递任何对象的能力,甚至是传递object也不行。
list<? extends fruit> flist = arrays.aslist(new apple()); //compile error:can't add any type of object //add()的参数是<? extends fruit>,编译器不知道需要fruit的哪个 //具体的子类型,因此不接受任何类型的fruit //flist.add(new apple()); //flist.add(new fruit()); //flist.add(new object()); flist.add(null);//legal but uninteresting apple a = (apple)flist.get(0);//no warning fruit f = flist.get(0);//no warning flist.contains(new apple());//参数是object flist.indexof(new apple());//参数是object
(2)list<? super fruit>逆变
超类型通配符可以安全地传递一个类型对象到泛型类型中。list<? super fruit>意味着向其中添加fruit或fruit的子类型是安全的。
list<? super fruit> flist = new arraylist<fruit>(); flist.add(new apple()); flist.add(new fruit()); //error:incompatible type //fruit f = flist.get(0); object f = flist.get(0);//ok,but type information has been lost
(3)*通配符list<?>
list实际上表示“持有任何object类型的原生list”,list<?>表示“具有某种特定类型的非原生list,只是我们不知道那种类型是什么”,list<? extends object>表示“类型是object的导出类”。
*通配符的一个重要应用:处理多个泛型参数时,允许一个参数可以是任何类型,同时为其他参数确定某种特定类型。
map<string,?> map = new hashmap<string,integer>; map = new hashmap<string,string>;
原生holder与holder<?>是大致相同的事物,但存在不同。它们会揭示相同的问题,但是后者将这些问题作为错误而不是警告报告。
static void rawargs(holder holder,object arg){ //holder.set(arg); //warning:unchecked call to set(t) as member //of the raw type holder //holder.set(new wildcards());//same warning //can't do this:don't have any 't' //t t = holder.get(); //ok,but type infomation has been lost object obj = holder.get(); } //similar to rawargs(),but errors instead of warnings static void unboundedarg(holder<?> holder,object arg){ //holder.set(arg); //error:set(capture of ?) in holder<capture of ?> //cannot be applied to (object) //holder.set(new wildcards());//same error //can't do this:don't have any 't' //t t = holder.get(); //ok,but type infomation has been lost object obj = holder.get(); }
(4)捕获转换
未指定的通配符类型被捕获,并被转换为确切类型。在f2()中调用f1(),参数类型在调用f2()的过程中被捕获,因此它可以在对f1()的调用中被使用。不能从f2()中返回t,因为t对于f2()来说是未知的。
static <t> void f1(holder<t> holder){ t t = holder.get(); system.out.println(t.getclass().getsimplename()); } static <t> void f2(holder<t> holder){ f1(holder); }
8、问题
(1)任何基本类型都不能作为类型参数
(2)实现参数化接口
一个类不能实现同一个泛型接口的两种变体。将泛型参数移除掉后,这段代码就可以正常编译了。
interface payable<t>{} class employee implements payable<employee>{} //compile error:cannot be inherited with different type arguments class hourly extends employee implements payable<hourly>{}
(3)转型和警告
使用带有泛型类型参数的转型或instanceof不会有任何效果。
由于擦除原因,编译器无法知道这个转型是否安全,并且pop()方法实际上并没有执行任何转型。如果没有@suppresswarnings注解,编译器将对pop()产生“unchecked cast”警告。
private int index = 0; private object[] storage; @suppresswarnings("unchecked") public t pop(){ return (t)storage[--index]; }
(4)重载
由于擦除的原因,重载方法将产生相同的类型签名,导致程序不能编译。
public class uselist<w,t>{ void f(list<t> v){} void f(list<w> v){} }
(5)基类劫持了接口
一旦为comparable确定了comparablepet参数,那么其他任何实现类都不能与comparablepet之外的任何对象比较。在前面的“实现参数化接口”章节里面的第一个例子,就体现了基类劫持接口。
public class comparablepet implements comparable<comparablepet> { public int compareto(comparablepet arg) { return 0; } } class cat extends comparablepet implements comparable<cat>{ // error: comparable cannot be inherited with // different arguments: <cat> and <pet> public int compareto(cat arg) { return 0; } } ///:~ class hamster extends comparablepet implements comparable<comparablepet>{ public int compareto(comparablepet arg) { return 0; } }
9、自限定
class subtype extends basicholder<subtype> {}这样用,就构成自限定了。从定义上来说,它继承的父类的类型参数是它自己。
从使用上来说,subtype对象本身的类型是subtype,且subtype对象继承而来的成员(element)、方法的形参(set方法)、方法的返回值(get方法)也是subtype了(这就是自限定的重要作用)。这样subtype对象就只允许和subtype对象(而不是别的类型的对象)交互了。
class basicholder<t> { t element; void set(t arg) { element = arg; } t get() { return element; } void f() { system.out.println(element.getclass().getsimplename()); } } class subtype extends basicholder<subtype> {} public class crgwithbasicholder { public static void main(string[] args) { subtype st1 = new subtype(), st2 = new subtype(), st3 = new subtype(); st1.set(st2); st2.set(st3); subtype st4 = st1.get().get(); st1.f(); } } /* output: subtype */
10、异常
由于擦除原因,将泛型应用于异常是非常受限的。但是,类型参数可能会在一个方法的throws子句中用到,这使得你可以编写随检查型异常的类型而发生变化的泛型代码。
interface processor<t,e extends exception> { void process(list<t> resultcollector) throws e; } class processrunner<t,e extends exception> extends arraylist<processor<t,e>> { list<t> processall() throws e { list<t> resultcollector = new arraylist<t>(); for(processor<t,e> processor : this) processor.process(resultcollector); return resultcollector; } } class failure extends exception {} class processor1 implements processor<string,failure> { static int count = 3; public void process(list<string> resultcollector) throws failure1_1, failure1_2 { if(count-- > 1) resultcollector.add("hep!"); else resultcollector.add("ho!"); if(count < 0) throw new failure1(); } } public class test { public static void main(string[] args) { processrunner<string,failure> runner = new processrunner<string,failure>(); for(int i = 0; i < 3; i++) runner.add(new processor1()); try { system.out.println(runner.processall()); } catch(failure e) { system.out.println(e); } } }
总结
本篇文章就到这里了,希望能给您带来帮助,也希望您能够多多关注的更多内容!