Minecraft(我的世界)中文论坛

标题: 【万年坑】【本章完结】Java高手训练营第十一章:泛型

作者: ufof    时间: 2015-12-4 23:22
标题: 【万年坑】【本章完结】Java高手训练营第十一章:泛型
本帖最后由 ufof 于 2015-12-14 05:52 编辑



11.1 泛型类



11.1.1 一个令程序员纠结的问题

在给大家讲泛型之前,我先讲一个例子将这个概念引入。

假如说你是JDK的开发者,你正在编写集合类。考虑到用户在集合中存入的元素的类是完全不确定的。不过还好,我们还有多态性。我们可以把集合类中的字段的类型都声明为Object,让所有类的元素都能存入。这样这个问题就轻松解决了。

但是麻烦到了用户身上:

  1. import java.util.*;

  2. public class GenericDemo {

  3.     public static void main(String[] args) throws Exception {
  4.         ArrayList al = new ArrayList();
  5.         
  6.         al.add(new MyClass());        //加入多个MyClass对象
  7.         al.add(new MyClass());
  8.         al.add(new MyClass());
  9.         al.add(new MyClass());
  10.         al.add(5);                    //加入一个Integer
  11.         
  12.         Iterator i = al.iterator();
  13.         
  14.         while(i.hasNext()){
  15.            //为了调用MyClass的method()方法,我们需要将迭代器中的元素向下转换为MyClass类
  16.             MyClass mc = (MyClass)i.next();           
  17.             
  18.             mc.method();                          //调用method()方法
  19.         }
  20.     }
  21. }

  22. class MyClass{            //自定义类
  23.     public void method(){    //自定义方法
  24.         System.out.println("method runs");
  25.     }
  26. }
复制代码

在这个程序中,我在ArrayList中添加了多个MyClass类对象,以及一个int。在对这个ArrayList迭代的时候,我想要调用MyClass的method()方法。所以说我需要将迭代器中的下一个元素向下转换成MyClass类型(见面向对象上)。

结果:



很容易发现,由于我在ArrayList中添加了四个MyClass对象,一个Integer对象,在第五次循环迭代时出现了异常。原因显而易见:Integer怎么能够被向下转换为MyClass!可以看出,这是用户没有检查传入的类型而导致的问题。

从中可以发现多态性的两个缺点:


那么大家回忆一下,为什么要使用多态这门技术。就是因为写这个泛型类的时候不知道用户会存哪类的对象,所以说必须要用Object类来吸收。如果我们在类上面定义一个占位符,然后把这些不确定类型的值的类型定义为这个占位符,然后让用户去告诉我这个占位符究竟是什么,那这个问题不就是解决了吗?

Java的泛型技术和上述的猜想差不多。但是有一个小区别:用户指定这些占位符的类型时,类中的占位符不会是“变成”用户指定的类型,而是会被java编译器视为用户指定的类型。到了运行时期,这些类型就都没了。详情见泛型擦除。

通过这种方式,我们可以在编译时期对不正确的类型报出错误。这样就保障了类型安全。因此,Java泛型技术的定义是:对编译器类型的指定是否安全的一个检查。(感谢@DeathWolf96 的纠正)

11.1.2 泛型类

当一个类中的引用数据类型不明确,需要由用户指定,并由编译器检查,可以使这个在这个类上定义泛型。这里说的泛型,就是刚才说的“占位符”。

泛型类定义方法如下:

  1. 若干个修饰符 class 类名<泛型类型1,泛型类型2,泛型类型3....泛型类型n>{
  2.    //一些代码
  3. }
复制代码

一般来讲,泛型类型的命名是一个大写字母。
在一个有泛型的类中,泛型可以像普通的类型一样的使用。你可以用它作为字段的类型、用它作为方法的返回值、用它作为构造方法的参数....

我们写一个类。这个类可以存储一个对象,并拥有获取这个对象以及设置这个对象的方法。考虑到,这个对象的类型不确定,需要由实例化者指定,应当使用泛型。

  1. class ObjectTool<T> {         // 在ObjectTool类上面定义一个叫做T的泛型

  2.     private T t;             // 一个类型是T的字段

  3.     public void set(T t) {    //接收一个T类型的参数
  4.         this.t = t;
  5.     }
  6.    
  7.     public T get() {         //一个返回T的方法
  8.         return t;
  9.     }
  10. }
复制代码

其实,大家可以理解为类中的“T”就是一个占位符。当用户实例化时指定这个占位符的类型,使用“T”的变量就被约束成了用户指定的类型。

实例化一个泛型类的语法如下:

  1. 类名<类型1,类型2,类型3,....,类型n> 实例名 = new 类名<类型1,类型2,类型3,....,类型n>(构造方法参数);
复制代码
其中,在“<”和“>”中间的类型的数量就是要被实例化类的泛型的数量。

不过,可以发现在等号的右边,泛型的类型又要指定一次,看上去重复很严重。因此,在Java7当中,增加了一个语法糖。上面的实例化语法可以被简化为:

  1. 类名<类型1,类型2,类型3,....,类型n> 实例名 = new 类名<>(构造方法参数);
复制代码

Java会自动用过左边的泛型,推断出右边的泛型。“<>”很像一个菱形,所以这个语法糖被称为“菱形语法”

好的!现在我们想要实例化我们刚才写的ObjectTool类。我想要存入的对象的类型是String,我实例化的时候就可以指定。

  1. ObjectTool<String> tool = new ObjectTool<>();
复制代码

在我们执行这条语句时,tool实例中的所有“T”约束成了String。

如果实例化一个泛型类时没有指定泛型类型,就像是我们以前学的实例化方法,会出现警告。这个类中的泛型会统统变成Object。现在大家可以理解为什么上一章我们学集合类时出现那么多警告。

  1. public class GenericDemo {

  2.     public static void main(String[] args){
  3.         ObjectTool<String> tool = new ObjectTool<>();
  4.         
  5.         tool.set("abc");
  6.         
  7.         System.out.println(tool.get());
  8.     }
  9. }

  10. class ObjectTool<T> {         // 在ObjectTool类上面定义一个叫做T的泛型

  11.     private T t;             // 一个类型是T的字段

  12.     public void set(T t) {    //接收一个T类型的参数
  13.         this.t = t;
  14.     }
  15.    
  16.     public T get() {         //一个返回T的方法
  17.         return t;
  18.     }
  19. }
复制代码
结果:



好的!现在我们了解了泛型类,以及实例化泛型类的语法。现在我们来解决我们刚开始提到的集合类的问题吧。

11.1.13 解决问题

我们又变回了JDK程序员。我们在开发集合类的时候,发现集合类中的某些变量的类型不确定,如果定义为Object会有安全隐患。我们可以在集合类上面加上一个泛型。

如果有同学有兴趣查阅API,可以发现ArrayList是这样定义的:

  1. class ArrayList<E> {
  2.     //某些代码
  3. }
复制代码

其中,E代表要存储的对象的类型。

同时,Iterator也有一个泛型。这个泛型代表Iterator中的元素的类型。

好的,现在我们通过实例化泛型的方法,使用ArrayList。

  1. import java.util.ArrayList;
  2. import java.util.Iterator;

  3. public class GenericDemo {

  4.     public static void main(String[] args) {
  5.         //将ArrayList中的泛型指定为MyClass。这样ArrayList中存储的对象必须都是MyClass
  6.         ArrayList<MyClass> al = new ArrayList<>();
  7.         
  8.         al.add(new MyClass());
  9.         al.add(new MyClass());
  10.         al.add(new MyClass());
  11.         al.add(new MyClass());
  12.         
  13.         //下面这句话引发编译时错误
  14.         //al.add(5);
  15.         
  16.         Iterator<MyClass> i = al.iterator();
  17.         
  18.         while(i.hasNext()){
  19.             //不需要向下转换了!
  20.             MyClass mc = i.next();
  21.             
  22.             mc.method();
  23.         }
  24.     }
  25. }

  26. class MyClass {
  27.     public void method() {
  28.         System.out.println("method runs");
  29.     }
  30. }
复制代码
结果:



可以发现,有了泛型,我们把上面说的两个问题解决了。


而且,上一章一直困扰我们的警告消失了。


本章小结


11.2 泛型方法



11.2.1 泛型方法概述

在上一节当中,泛型的声明是在类上面的。这使这个泛型再这个类当中有效。如果仅仅是一个方法中的某个引用数据类型未知的话,可以把泛型加在方法上。

泛型方法的声明语法如下:

  1. 若干个修饰符 <泛型类型1,泛型类型2,....泛型类型3> 返回值类型 方法名(参数列表){
  2.     //一些代码
  3. }
复制代码

可以发现,泛型的声明方法和泛型类是差不多的。不过值得注意的一点是泛型的声明必须要在修饰符后,返回值前

11.2.2 使用泛型方法

泛型方法和泛型类有一个很大的区别:那就是泛型类实例化时必须显式的指定每一个泛型的类型;然而,泛型方法中的泛型不需要显式的指定类型。那么用户是如何指定这个类型的呢?

泛型方法的泛型的引用数据类型由使用这个泛型的参数指定。之后编译器会将这个泛型视为这个参数的类型,以保障类型安全。

假如说我们有这样的一个方法,可以打印指定的对象的类名。考虑到指定的对象的类型不确定,应当使用泛型方法暂时代替。

  1. public static <T> void printClassName(T t) {
  2.         System.out.println(t.getClass());
  3. }
复制代码

注:getClass()方法是Object类中的方法。用于获取对象的类。

调用这个方法时,不需要指定“T”是什么类型。由于这个方法的参数是以“T”作为类型的,这个参数你传入什么对象,“T”就会被编译器视为这个参数的对象的类型

  1. public class GenericMethodDemo {

  2.     public static void main(String[] args) {
  3.         GenericMethodDemo demo = new GenericMethodDemo();

  4.         //  调用方法时无需指定泛型的类型。你传入什么参数,泛型就成为这个参数的类
  5.         demo.printClassName(5);
  6.         demo.printClassName("abc");
  7.         demo.printClassName(new Object());
  8.         demo.printClassName(3.14);
  9.     }

  10.     public <T> void printClassName(T t) {
  11.         System.out.println(t.getClass());
  12.     }
  13. }
复制代码
结果:



大家看主方法:我第一次传入的是一个int(Integer),所以第一行打印结果就是Integer类;第二次传入String,第二行打印的就是String类....  以此类推。

可以发现:方法中的泛型是随着参数而改变的。只要你的某个参数用了泛型作为类型声明,编译器会将泛型的类型视为参数传入的类型

11.2.3 静态方法泛型

静态方法使用泛型稍微有些特殊。

上一节中我们讲过了,泛型类的泛型是在实例化时显示的指定类型的。这说明每一个对象都有自己的泛型类型。由于静态方法比对象更加早进入内存,静态方法无法使用类的泛型

不过还好,静态方法可以使用方法上的泛型。例如拿上面的程序来讲,我们可以把printClass()方法改成静态的:

  1. public class GenericMethodDemo {

  2.     public static void main(String[] args) {
  3.         
  4.         printClassName(5);
  5.         printClassName("abc");
  6.         printClassName(new Object());
  7.         printClassName(3.14);
  8.     }

  9.     public static <T> void printClassName(T t) {
  10.         System.out.println(t.getClass());
  11.     }
  12. }
复制代码
结果同上。

本章小结


11.3 泛型接口



11.3.1 泛型接口概述

我们已经学会了在类上、在方法上定义泛型。有的时候我们在写接口的时候也会遇到引用数据类型不明确的时候。因此,泛型也可以定义在接口上。

泛型接口的定义方式如下:

  1. interface 接口<泛型1,泛型2,泛型3.....,泛型n> extends 父接口1,父接口2,父接口3....,父接口n {
  2.     //一些代码
  3. }
复制代码

例如,我写一个这样的接口:

  1. interface MyInterface<T> {
  2.     void method(T t);  //一个无返回值,要求一个类型为T的参数的方法
  3.     T method2();        //一个返回T类型,不要求参数的方法
  4. }
复制代码

值得注意的是,泛型接口的泛型不能用作这个接口的字段的类型:

  1. interface MyInterface<T> {
  2.     T t;
  3. }
复制代码

这是因为,接口中的字段都是固定的public static final。然而,类中的静态成员不能使用类上的泛型。

好的,现在一个简单的泛型接口已经定义完了。接口不能够被实例化,那么接口的泛型是什么时候被确定呢?

11.3.2 实现一个泛型接口

一个接口的泛型类型的确认时机其被实现时。当一个类实现一个泛型接口,必须指定这个接口中的泛型的类型。若不,所有泛型视为Object。

  1. interface MyInterface<T> {
  2.     void method(T t);

  3.     T method2();
  4. }

  5. class ImplementingClass implements MyInterface<String> {    //传入String

  6.     public void method(String t) {    //复写method()方法
  7.         System.out.println(t);
  8.     }

  9.     public String method2() {        //复写method2()方法
  10.         return "hello";
  11.     }
  12. }
复制代码

ImplementingClass类在实现MyInterface的时候,为该接口的泛型类型指定为String。因此,其复写接口中的抽象方法时,方法中的“T”全部被编译器视为String。

还有另一种情况:实现类也不确定给接口的泛型传入哪个类型。需要由实例化者指定。可以在类上建立一个泛型,然后把这个泛型传入接口的泛型。

  1. interface MyInterface<T> {
  2.     void method(T t);

  3.     T method2();
  4. }

  5. //实现类也不知道给泛型传入什么类型。在类上面定义一个泛型,将这个泛型传入
  6. class ImplementingClass<T> implements MyInterface<T> {

  7.     public void method(T t) {
  8.         System.out.println(t);
  9.     }

  10.     public T method2() {
  11.         return null;
  12.     }
  13. }
复制代码

实现类定义泛型T,给接口传入T。因此类中复写的方法中的“T”不变。

本章小结



11.4 泛型通配符以及限定



11.4.1  无界通配符

请观察下列代码:

  1. public static void printList(List<Object> list) {
  2.     Iterator<Object> i = list.iterator();
  3.         
  4.     while(i.hasNext()){
  5.         System.out.println(i.next());
  6.     }
  7. }
复制代码

假设我定义一个方法,这个方法可以打印一个List中的所有元素。这个时候许多人会陷入一个误区:只要将List中的泛型定义为Object类,那么List<String>、List<Integer>....都可以接收了。

其实不是这样的。在Java的泛型机制中,如果A类是B类的父类,XXX<A>不是XXX<B>的父类。因此,这个方法的功能很有限:只能打印List<Object>的元素。

那么,我这个方法究竟该如何写呢?我们可以不给List和Iterator加上泛型,不过会引起警告。这里就涉及到泛型类型不确定的问题。遇到这种问题,应当使用泛型通配符“?”当做类型传入。

  1. public static void printList(List<?> list) {    //接收List<?>
  2.     Iterator<?> i = list.iterator();
  3.             
  4.     while(i.hasNext()){
  5.         System.out.println(i.next());
  6.     }
  7. }
复制代码
当一个泛型类的实例传入泛型通配符作为类型,其可以接收任何泛型类型。换句话说,在这个方法当中,我传入一个List<Integer>、List<String>、List<XXX>都行。

  1. import java.util.ArrayList;
  2. import java.util.Iterator;
  3. import java.util.List;

  4. public class WildCardDemo {

  5.     public static void main(String[] args) {
  6.         List<Integer> list = new ArrayList<>();
  7.         list.add(5);
  8.         list.add(6);
  9.         
  10.         List<String> list2 = new ArrayList<>();
  11.         list2.add("abc");
  12.         list2.add("def");
  13.         
  14.         List<Object> list3 = new ArrayList<>();
  15.         list3.add(new Object());
  16.         list3.add(new Object());
  17.         
  18.         printList(list);        //传入List<Integer>
  19.         printList(list2);        //传入List<String>
  20.         printList(list3);        //传入List<Object>
  21.     }

  22.     public static void printList(List<?> list) {    //接收List<?>
  23.         Iterator<?> i = list.iterator();
  24.                
  25.         while(i.hasNext()){
  26.             System.out.println(i.next());
  27.         }
  28.     }
  29. }
复制代码
结果:



可以看出,使用泛型通配符作为参数中的List的泛型类型可以增加方法的扩展性。

学生提问:泛型方法也可以做到呀,那么泛型方法和泛型通配符有什么区别?

  1. public static void printList(List<?> list)         //使用泛型通配符
  2. public static <T> void printList(List<T> list) //使用泛型方法
复制代码

答:从上面我举得例子来讲,使用泛型方法也是可以的。但是泛型通配符和泛型方法有一个逻辑层面上的区别:那么就是泛型通配符是永远不确定的;泛型方法是等到方法被调用时,类型就会被确定。由于我们这个方法的参数是不确定的,应当使用通配符。


可以看出来,上面使用通配符接收任意类型进来。由于没有类型的限制,这种通配符叫做无界通配符。

11.4.2  泛型限定

为了方便讲解,我们先定义一个体系:

  1. class Human {                                //人类
  2.     String name;
  3.     int age;

  4.     public Human(String name, int age) {    //构造方法
  5.         this.name = name;
  6.         this.age = age;
  7.     }

  8.     public void introduce() {                //自我介绍方法
  9.         System.out.println("我的名字是" + name + ",我" + age + "岁了");
  10.     }
  11. }

  12. class Student extends Human {                //学生类
  13.     public Student(String name, int age) {
  14.         super(name, age);
  15.     }

  16.     public void study() {                    //学习方法
  17.         System.out.println("学习中");
  18.     }
  19. }

  20. class Teacher extends Human {                //老师类
  21.     public Teacher(String name, int age) {
  22.         super(name, age);
  23.     }

  24.     public void teach() {                    //教课方法
  25.         System.out.println("教课中");
  26.     }
  27. }
复制代码

现在,我的需求改了。我现在有一个集合,这个List可以装Human类。我希望我上面写的printList()方法不仅仅可以遍历List中的元素,还可以调用元素中的introduce()方法。

但是,泛型通配符是所有类型都支持的。所以说如果我要调用introduce()方法,我必须要让传入进来的参数的泛型类型都是Human或Human的子类。因此,Java为我们提供泛型限定机制。

我们把刚才写的方法更改成:

  1. public static void printList(List<? extends Human> list) {
  2.     Iterator<? extends Human> i = list.iterator();

  3.     while (i.hasNext()) {
  4.         i.next().introduce();
  5.     }
  6. }
复制代码
请注意“List<? extends Human>”以及“Iterator<? extends Human>”,这表示这个泛型通配符只支持Human类或Human的子类。因此,在迭代时,我们可以调用Human的introduce()方法。

  1. import java.util.ArrayList;
  2. import java.util.Iterator;
  3. import java.util.List;

  4. public class WildCardDemo {

  5.     public static void main(String[] args) {
  6.         List<Human> list1 = new ArrayList<>();        //List<Human>
  7.         list1.add(new Human("张三",15));
  8.         list1.add(new Human("李四",17));
  9.         list1.add(new Human("王五",19));
  10.         
  11.         List<Student> list2 = new ArrayList<>();    //List<Student>
  12.         list2.add(new Student("小王",13));
  13.         list2.add(new Student("小李",14));
  14.         
  15.         List<String> list3 = new ArrayList<>();        //List<String>
  16.         list3.add("abc");
  17.         list3.add("def");
  18.         
  19.         printList(list1);
  20.         printList(list2);
  21.         // 由于String不是Human子类,下面代码报错
  22.         // printList(list3);
  23.     }

  24.     public static void printList(List<? extends Human> list) {
  25.         Iterator<? extends Human> i = list.iterator();
  26.    
  27.         while (i.hasNext()) {
  28.             i.next().introduce();
  29.         }
  30.     }
  31. }
复制代码
结果:



请大家注意看主方法:我有三个List,一个是List<Human>,一个是List<Student>,最后一个是List<String>。由于printList()方法已经限定其通配符必须是Human或Human子类,String不是Human子类,所以说最后一个List不能最为printList()的参数

这样就完成了泛型的限定上限。当然了,既然可以限定上限,是否可以限定下限?
  1. <泛型类型或通配符 super 类>
复制代码
这表示,这个泛型类型或通配符只支持指定的类,或这个类的父类。这就是限定下限。不过
在开发当中不常用。这里就不举出例子了。

本章小结


11.5 泛型擦除



11.5.1  泛型擦除概述

不仅仅是Java,其他的主流语言也有泛型技术的提供。不过Java的泛型和其他的泛型稍微有一些区别:Java的泛型是伪泛型。

那么什么是伪泛型呢?为了示范,我们写一个程序。需求是提供一个用于存储String的ArrayList,在迭代时打印通过字符串的length()方法来获取每一个元素的长度。

  1. import java.util.ArrayList;
  2. import java.util.Iterator;

  3. public class GenericErasureDemo {

  4.     public static void main(String[] args) {
  5.         ArrayList<String> al = new ArrayList<>();

  6.         al.add("java");
  7.         al.add("c++");
  8.         al.add("c#");

  9.         Iterator<String> i = al.iterator();

  10.         while (i.hasNext()) {
  11.             System.out.println("长度为:"+i.next().length());
  12.         }
  13.     }
  14. }
复制代码
结果:



这个程序并不是很难理解。但是我想要让大家回忆一下,没有学泛型的时候该如何写。
如果没有对ArrayList进行泛型指定,那么其中的字段都会变成默认的Object类,并给出警告。在迭代时,由于length()方法是String的特有方法,需要对i.next()进行向下转换,也就是(String)i.next()。

如果使用反编译软件把这个程序的字节码文件(.class文件)反编译成源文件(.java),会看到这番场景:

  1. import java.io.PrintStream;
  2. import java.util.ArrayList;
  3. import java.util.Iterator;

  4. class GenericErasure
  5. {
  6.   public static void main(String[] paramArrayOfString)
  7.   {
  8.     ArrayList localArrayList = new ArrayList();
  9.    
  10.     localArrayList.add("java");
  11.     localArrayList.add("c++");
  12.     localArrayList.add("c#");
  13.    
  14.     Iterator localIterator = localArrayList.iterator();
  15.     while (localIterator.hasNext()) {
  16.       System.out.println(((String)localIterator.next()).length());
  17.     }
  18.   }
  19. }
复制代码
与之前写的程序相比较,这两个代码有若干个区别:


通过这个实例,我们可以观察到一点:Java中的泛型只存在于编译时期,运行时期带泛型的类型会统统变回没有泛型指定的类型。这种类型叫做原始类型(raw type)。例如:List<Integer>的原始类型是List。

因此:
  1. ArrayList<Integer> al = new ArrayList<>();
  2. ArrayList<String> al2 = new ArrayList<>();
  3. System.out.println(al.getClass() == al2.getClass()); //通过getClass()方法比较两个实例类是否相等
复制代码
本程序的结果是true。

按理说,两个ArrayList的实例有着不同的泛型类型,但是对两个实例的getClass()方法比较的值是true。这更加证明了:泛型只存在在编译时期。

本章小结


[groupid=546]Command Block Logic[/groupid]
作者: Skey    时间: 2015-12-5 09:11
QwQ前排~
感谢大大~
作者: asjkdaskljda    时间: 2015-12-5 10:06
感谢教程,正在学习,有朝一日,...等着吧!
作者: ufof    时间: 2015-12-9 00:21
@yuxuanchiadm 泛型大触求帮看


作者: DeathWolf96    时间: 2015-12-10 11:47
本帖最后由 DeathWolf96 于 2015-12-10 11:54 编辑

楼主对泛型的理解有很大的问题呀 :I

就说这一句话:所谓泛型,就是在对象被实例化的时候才决定类型的一种类型。

对java来说,这句话是不成立的。
泛型对java来说只在编译期存在,是对编译期的类型约束是否合法的一种检查。

比如说:

  1. List<String> list = new ArrayList<>();
  2. list.add("aaa"); // OK
  3. list.add(1); //Compile error: Incompatible type
复制代码


但是如果你的List句柄不包含类型信息:

  1. List list = new ArrayList<String>();
  2. list.add("aaa");
  3. list.add(1);
复制代码

这段代码是可以通过编译的,而且实际上存储的就是"aaa"和1两个不同类型的对象。

楼主所说的泛型是强泛型,例如C++中的模板就是一种强泛型。

  1. template<typename T>
  2. void print(const T& a) {
  3. std::cout << a << std::endl;
  4. }

  5. print(std::string("aaa"));
  6. print(123);
复制代码

在这里实际上进行了模板的实例化,也就是说不同的类型T对应了一个不同的函数,但是它们都以模板函数为蓝图生成。(这叫做模板的实例化)

但是java不一样,不管泛型类型如何,调用的都是同样的代码段

泛型也无法产生不同的类型。如果你尝试获取ArrayList<String>.class你会获取一个警告:无法获取泛型类型,因为实际上就没有,所有泛型类型的类都是它的基本类型(ArrayList.class)。

java的这种行为叫做泛型擦除,它实际上是语言设计的一个极大缺陷,带来了很多的易用性和安全性上的问题。

比如说,即便有泛型,我们也可以写出这样的代码,它仍然是类型不安全的

  1. List<String> list1 = new ArrayList<>();
  2. List list2 = list1; // No warning
  3. list2.add(123); // No warning or error
  4. // 现在list1里存了一个int
复制代码


希望楼主在发教程之前先确定对所研究的问题了解足够透彻 不要误人子弟:-)
作者: ufof    时间: 2015-12-10 17:29
本帖最后由 ufof 于 2015-12-10 01:45 编辑
DeathWolf96 发表于 2015-12-9 19:47
楼主对泛型的理解有很大的问题呀 :I

就说这一句话:所谓泛型,就是在对象被实例化的时候才决定类型的一种类 ...

首先感谢你如此认真的答复。总的来说,我知道Java中的泛型是伪泛型。我如此说是为了方便教学。

你可以观察一下本章的目录,我是先讲泛型类(也就是在这里面讲的“不准确”的泛型的定义),最后才讲的泛型擦除。也就是在泛型擦除中,我才说了Java泛型是伪泛型。

11.5.1  泛型擦除概述

不仅仅是Java,其他的主流语言也有泛型技术的提供。不过Java的泛型和其他的泛型稍微有一些区别:Java的泛型是伪泛型。
(因此我不确定层主是否读完了这一整章?目录是可以戳的)。

我这样先把不准确的定义说明出来,在最后才进行解释,简单的来说是为了帮助大家理解。我想要想让大家了解,什么时候使用泛型,泛型有什么好处。到最后,才让大家了解泛型在编译器和虚拟机中的形态,难道不是更好?

作者: DeathWolf96    时间: 2015-12-10 19:16
本帖最后由 DeathWolf96 于 2015-12-10 19:22 编辑
ufof 发表于 2015-12-10 17:29
首先感谢你如此认真的答复。总的来说,我知道Java中的泛型是伪泛型。我如此说是为了方便教学。

你可以观 ...

可以忍受之后再提,但是不能忍受提出明确错误的结论。

楼主既然知道java的泛型是伪泛型,那在写出“类型在实例化时确定”这样的话的时候就应该有所警觉。 我同意循序渐进,但是希望在措辞的正确性方面多加注意:) 之前的回复言辞可能激烈了一些 而且略戳牛角尖 抱歉了

(目录为啥是黑色的。。。根本看不出来能戳嘛qwq)

作者: 1527802264    时间: 2016-1-22 02:53
個人感覺是比較蛋疼的
作者: 947132885    时间: 2016-1-27 12:28
恩,感觉泛型很像宏。
作者: ufof    时间: 2016-1-27 13:04
947132885 发表于 2016-1-26 20:28
恩,感觉泛型很像宏。

Java中的泛型其实更像C++的模板
作者: 947132885    时间: 2016-1-27 13:05
恩,教程学完了,然后应该去学点什么呢?
作者: 947132885    时间: 2016-1-27 13:06
ufof 发表于 2016-1-27 13:04
Java中的泛型其实更像C++的模板

不知道,学了c之后,没有学c++,原因不详...
作者: 947132885    时间: 2016-1-27 13:12
947132885 发表于 2016-1-27 13:05
恩,教程学完了,然后应该去学点什么呢?

ok,好的,反正天天看着玩意也烦了,去实践下
作者: yuxuanchiadm    时间: 2016-3-4 13:39
本帖最后由 yuxuanchiadm 于 2016-3-4 13:42 编辑
ufof 发表于 2015-12-9 00:21
@yuxuanchiadm 泛型大触求帮看

http://build.cthuwork.com:8081/wordpress/
有2篇關於汎型的博文,可以參考下
作者: tyxiaomin    时间: 2016-10-15 15:28
看大触的意义是收下膝盖?
作者: 柴源    时间: 2016-10-16 12:26
播放音乐的执行代码是什么