欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

带你入门Java的泛型

程序员文章站 2022-06-19 09:03:18
目录(2)通用的generator(3)set实用工具实现数学方法(1)由于擦除原因,无法通过instanceof比较类型。如果引入类型标签,就可以转而使用动态的isinstance()。(1)lis...

泛型

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);
        }
    }
}

总结

本篇文章就到这里了,希望能给您带来帮助,也希望您能够多多关注的更多内容!