泛型详解

泛型

Java SE5 引入了泛型的概念,泛意为广泛,型是类型。所以泛型就是能广泛适用的类型。

产生泛型的原因

​ 在JDK5.0之前,容器存储的对象都只具有java的通用类型:Object。单根继承结构意味着所有东西都是Object类型,所有该容器可以存储任何东西,但是由于容器只存储Object,所以当将对象引入容器时,它必须被向上转型为Object,因此它丢失了身份,将它取出时取到的是一个Object引用,必须将该Object向下转型为更具体的类型。这种转型方式称为向下转型。向上转型是安全的,但是向下转型是不安全的。

​ 那么是否能创建一个容器,它知道自己所保存的对象类型,从而不需要向下转型以及消除犯错误的可能。这样的解决方案被称为参数化类型机制,参数化类型就是一个编译器可以自动定制作用于特定类型上的类。这正是SE5.0 的重大变化之一,就是增加了参数化类型,在java中称为泛型。泛型的核心概念:告诉编译器想使用什么类型,编译器帮你处理一切细节。

泛型的类型:泛型类,泛型方法,泛型接口

泛型类:使用<>扩住,放在类名后面,在创建这个类时在明确确定的对象

public class TestMethods<T> {
    private T t;

    public TestMethods(T tt) {
        t = tt;
    }

    public T get() {
        return t;
    }

    public static void main(String[] args) {
        TestMethods<String> tt = new TestMethods<>("s");
        String test = tt.get();
        //int test = tt.get(); 编译报错
    }

}

​ 如上例所示,泛型类中T的类型是在运行时(new 时)确定下来的,确定下来后,类中的T就被替换为相应的类,所以会出现上例中编译时报错的情况,来在编译时替程序员发现问题。

泛型方法:定义泛型方法,只需将泛型参数列表用<>扩住,放在返回值之前

public class TestMethods {

    public <T> void next(T t) {
        System.out.print(t.getClass().getName());
    }
    
    public static <T> void next(T t) { //正确写法
        System.out.print(t.getClass().getName());
    }
}



public class TestMethods<T> {

    public static  void next(T t) { //这种写法不存在会报错
        System.out.print(t.getClass().getName());
    }
}

​ 泛型方法使得该方法能独立于类而产生变化,所以使用泛型的原则是,无论何时,只要泛型方法能实现泛型类所需的功能,都要使用泛型方法,此外 static的方法(编译时)因在编译时不能确认泛型类(运行时)的类型参数,所以static方法想使用泛型能力一定是泛型方法。

泛型接口:与泛型类的使用方式无区别

public interface TestImplements<T> {
    T next();
}

public class TestMethods implements TestImplements<String> {
    @Override
    public String next() {
        return null;
    }

}

泛型的擦除

泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除在泛型代码的内部,无法获得任何有关泛型参数类型的信息

为什么会存在擦除

为了迁移兼容性 :java选择泛型的擦除是一种中庸之道。如果重新设计java未必还会这样做。

​ 为了减少潜在的关于 擦除 的混乱,你必须认识到这不是一个语言的特征,它是java泛型实现中的一种折中。因为泛型不是java语言出现时就有的组成成分,所以这种折中是必须的

​ 如果泛型是java1.0就已经是其一部分了,那么这个特征将不会使用擦除来是实现–它将使用具体化,使类型参数保持为第一类实体,这样的话你就可以在类型参数上执行基于类型语言的操作和反射操作。擦除 减少了泛型的泛化性,泛型咋java中仍然是有用的,只是不如它们本来设想的那么有用,其原因就是擦除

​ 在基于擦除的实现中,泛型类型被当作第二类类型来处理,既不能在重要的上下文环境中使用 类型。泛型类型只有在 静态类型 检查期间才出现,在此之后,程序中所有的泛型类型都被擦除了,替换为它们的非泛型上界。例如:诸如List这样的类型注解将被擦除为List,而普通的类型变量在未指定边界的情况下将被擦除为Object

​ 擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然。这经常被称为"迁移兼容性"。因为在理想情况下,当所有的事物都可以使用泛化,那我们就可以专注于此。但是在现实中,5.0之前是没有泛型的,必须要处理这些没有被泛化的类库

​ 因此java泛型不仅必须支持向后兼容性,即现有的代码是合法的,并且继续保持之前的含义。而且还要支持迁移兼容性,使得旧的代码不会影响新的代码。一句话就是允许非泛型代码与泛型代码共存,擦除 使得这种向着泛型的迁移成为可能

擦除的问题

​ 擦除的主要的正当理由是从非泛化代码到泛化代码的转变过程,以及在不破环现有类库的情况下,将泛型融入java语言。擦除使得现有的非泛型客户端代码能够在不改变的情况继续使用直到客户端准备好用泛型重写这些代码。这是一个崇高的动机,因为他不会突然间破环所有现有的代码

​ 擦除的代价是显著的,泛型不能用于显式地引用运行时类型的操作之中。因为所有关于参数的类型信息都丢失了。

​ 无论何时,当你在编写泛型代码的时候,必须时刻提醒自己,你只是看起来 好像拥有了 有关类型的信息。其实它只不过是一个Object而已

​ 泛型不是强制的

​ 当你希望将类型参数不要仅仅当作是Obbject处理时,就需要付出额外努力来管理边界(例如通过编写这些来管理)

​ 其他编程语言的参数化类型相比(例如C++):通常比java得心应手,它们的参数化类型机制比java的更灵活,更强大

通配符

通配符 : 除了用 <T>表示泛型外,还有 <?>这种形式。 被称为通配符。通配符的引用的目的在于明确类型

上界 <? extends Fruit>

下界 <? super Apple>

无界 <?>

边界正是因为上述擦除的原因,在运行阶段,类型信息被擦除,在默认情况下将T识别为Object 。所以才有了通配符的机制,通过extends 关键字,可以使T有一个更小的边界,识别为更具象的对象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章