Java 泛型 三

一、泛型初衷

Java集合不会知道我们需要用它来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要就具有很好的通用性。但这样做也带来两个问题:

  –集合对元素类型没有任何限制,这样可能引发一些问题:例如想创建一个只能保存Sting对象的集合,但程序也可以轻易地将int对象“丢”进去,所以可能引发异常。

  –由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既会增加编程的复杂度、也可能引发ClassCastException。

二、在集合中使用泛型

在集合中使用泛型后带来如下优势

  –程序再也不能“不小心”把其他对象“丢进”strList集合中;

  –程序更加简洁,集合自动记住所有集合元素的数据类型,从而无需对集合元素进行强制类型转换。

下面的代码中,"不小心"把一个Integer对象"丢进"了集合。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import java.util.*;

public class ListErr

{

    public static void main(String[] args)

    {

        // 创建一个只想保存字符串的List集合

        List strList = new ArrayList();

        strList.add("疯狂Java讲义");

        strList.add("疯狂Android讲义");

        // "不小心"把一个Integer对象"丢进"了集合

        strList.add(5);     // ①

        strList.forEach(str -> System.out.println(((String)str).length())); // ②

    }

}


1

"不小心"把一个Integer对象"丢进"了集合


1

②引发ClassCastException异常。

三、什么是泛型

  所谓泛型:就是允许在定义类、接口指定类型形参,这个类型形参在将在声明变量、创建对象时确定(即传入实际的类型参数,也可称为类型实参)。

  JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

四、泛型的“菱形”语法 <>

  如下代码:


1

2

List<String> books = new ArrayList<String>();

Map<String,Integer> books = new ArrayList<String,Integer>();

  在java 7 以前,<>中的粗体字代码都是必须的,但是现在可以不带粗体字代码。Java自动推断出ArrayList的<>里应该是String还是String,Integer。

  现在改为:


1

2

List<String> books = new ArrayList<>();

Map<String,Integer> books = new ArrayList<>();

第一段和第二段代码是完全等价的。

泛型的简单应用:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

import java.util.*;

public class DiamondTest

{

    public static void main(String[] args)

    {

        // Java自动推断出ArrayList的<>里应该是String

        List<String> books = new ArrayList<>();

        books.add("疯狂Java讲义");

        books.add("疯狂Android讲义");

        // 遍历books集合,集合元素就是String类型

        books.forEach(ele -> System.out.println(ele.length()));

        // Java自动推断出HashMap的<>里应该是String , List<String>

        Map<String , List<String>> schoolsInfo = new HashMap<>();

        // Java自动推断出ArrayList的<>里应该是String

        List<String> schools = new ArrayList<>();

        schools.add("斜月三星洞");

        schools.add("西天取经路");

        schoolsInfo.put("孙悟空" , schools);

        // 遍历Map时,Map的key是String类型,value是List<String>类型

        schoolsInfo.forEach((key , value) -> System.out.println(key + "-->" + value));

    }

}

  

五、深入泛型

1.定义泛型接口、类

  定义Apple类时使用了泛型声明


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

// 定义Apple类时使用了泛型声明

public class Apple<T>

{

    // 使用T类型形参定义实例变量

    private T info;

    public Apple(){}

    // 下面方法中使用T类型形参来定义构造器

    public Apple(T info)

    {

        this.info = info;

    }

    public void setInfo(T info)

    {

        this.info = info;

    }

    public T getInfo()

    {

        return this.info;

    }

    public static void main(String[] args)

    {

        // 由于传给T形参的是String,所以构造器参数只能是String

        Apple<String> a1 = new Apple<>("苹果");

        System.out.println(a1.getInfo());

        // 由于传给T形参的是Double,所以构造器参数只能是Double或double

        Apple<Double> a2 = new Apple<>(5.67);

        System.out.println(a2.getInfo());

    }

}

2.从泛型派生子类

A1继承泛型类:


1

2

3

4

5

6

7

8

//使用泛型类时,为T形参传入String类类型

public class A1 extends Apple<String>{}//正确

//使用泛型类时,没有为T形参传入实际类型参数,这会产生警告:泛型检查警告,使用了未经检查或不安全的操作

public class A1 extends Apple{}//正确

//apple类不能跟类型形参

public class A1 extends Apple<T>{}//错误

继承Apple类,T被String代替。子类会继承到String getInfo()和void setInfo()两个方法。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public class A1 extends Apple<String>

{

    // 正确重写了父类的方法,返回值

    // 与父类Apple<String>的返回值完全相同

    public String getInfo()

    {

        return "子类" super.getInfo();

    }

    /*

    // 下面方法是错误的,重写父类方法时返回值类型不一致。从父类继承的应该是public String getinfo()

    public Object getInfo()

    {

        return "子类";

    }

    */

}

正确写法如下:


1

2

3

4

5

6

7

8

9

10

public class A2 extends Apple

{

    // 重写父类的方法

    public String getInfo()

    {

        // super.getInfo()方法返回值是Object类型,

        // 所以加toString()才返回String类型

        return super.getInfo().toString();

    }

}

3.并不存在泛型类

  虽然可以把ArrayList<String>类当成ArrayList的子类,事实上ArrayList<String>类也确实是一种特殊的ArrayList类,这个ArrayList<String>对象只能添加String对象作为集合元素。但实际上,系统并没有为ArrayList<String>生成新的class文件,而且也不会把ArrayList<String>当成新类来处理。

  实际上,泛型对其所有可能的类型参数,都具有同样的行为,从而可以把相同的类被当成许多不同的类来处理。与此完全一致的是,类的静态变量和方法也在所有的实例间共享,所以在静态方法、静态初始化、或者静态变量的声明和初始化中不允许使用类型形参。

  系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。

 六、类型通配符


1

2

3

4

public void test(List<Object> c)

{

  for(int i=0;i<c.size();i++)<br data-filtered="filtered">  {<br data-filtered="filtered">    Syso(c.get(i));<br data-filtered="filtered">  }<br data-filtered="filtered">

}

这段代码看上去没有任何问题,方法的声明也没有任何问题。但是问题在于:调用该方法传入的实际参数的值。例如:


1

2

//创建一个List<String>对象

List<String> strList = new ArrayList<>();<br data-filtered="filtered">//将strList作为参数调用test<br data-filtered="filtered">test(strList);

编译上面的程序,发生错误。


1

无法将Test中的test(java.util.list<java.lang.Object>)应用于java.util.list<java.lang.String>

这说明List<String>对象不能被当成List<Object>对象使用,也就是说:List<String>类并不是List<Object>类的子类。

此外,数组和泛型有所不同:假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的自类型;但G<Foo>不是G<Bar>的子类型。

七、?的用法

  为了表示各种泛型List的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是未知类型元素的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。

  在“六”中的程序,将


1

2

3

4

5

6

7

8

public void test(List<Object> c)

{

  for(int i=0;i<c.size();i++)

  {

    Syso(c.get(i));

  }

}

改为:


1

2

3

4

5

6

7

8

public void test(List<?> c)

{

  for(int i=0;i<c.size();i++)

  {

    Syso(c.get(i));

  }

}

再次编译就没有了错误。

这里的?可谓什么都可以表示,是不是给它的权力太大了!!   当然我们有自己的解决办法:设定类型通配符的上限

  使用List<?>这种形式是,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,我们不想这个List<?>是任何泛型List的父类,只想表示它是某一类泛型List的父类。 
  我们需要一种泛型表示方法,它可以表示所有Shape泛型List的父类,为了满足这种需求,Java泛型提供了被限制的泛型通配符。被限制的泛型通配符的如下表示:List<? extends Shape>


1

2

3

4

5

// 定义一个抽象类Shape

public abstract class Shape

{

    public abstract void draw(Canvas c);

}

  


1

2

3

4

5

6

7

8

9

/ 定义Shape的子类Circle

public class Circle extends Shape

{

    // 实现画图方法,以打印字符串来模拟画图方法实现

    public void draw(Canvas c)

    {

        System.out.println("在画布" + c + "上画一个圆");

    }

}

  


1

2

3

4

5

6

7

8

9

// 定义Shape的子类Rectangle

public class Rectangle extends Shape

{

    // 实现画图方法,以打印字符串来模拟画图方法实现

    public void draw(Canvas c)

    {

        System.out.println("把一个矩形画在画布" + c + "上");

    }

}

上面定义了三个形状类,Sharp抽象父类,Circle类和Rectangle类继承了抽象类Sharp。

下面定义一个Canvas类,该画布类不同的形状。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

import java.util.*;

public class Canvas

{

    // 同时在画布上绘制多个形状

    public void drawAll(List< Shape> shapes)

    {

        for (Shape s : shapes)

        {

            s.draw(this);

        }

    }

    public static void main(String[] args)

    {

        List<Circle> circleList = new ArrayList<Circle>();

        Canvas c = new Canvas();

        // 由于List<Circle>并不是List<Shape>的子类型,

        // 所以下面代码引发编译错误

        c.drawAll(circleList);

    }

}

修改如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

import java.util.*;

public class Canvas

{

    // 同时在画布上绘制多个形状,使用被限制的泛型通配符

    public void drawAll(List<? extends Shape> shapes)

    {

        for (Shape s : shapes)

        {

            s.draw(this);

        }

    }

    public static void main(String[] args)

    {

        List<Circle> circleList = new ArrayList<Circle>();

        Canvas c = new Canvas();

        // 由于List<Circle>并不是List<Shape>的子类型,但是使用了通配符

        // 所以下面代码正确

        c.drawAll(circleList);

    }

}

这段代码就没有了错误。

  Java泛型不仅允许在使用通配符形参时设定类型上限,也可以在定义类型形参时设定上限,用于表示创给该类型形参的实际类型必须是该上限类型,或是该上限类型的子类。 例如:


1

2

3

4

5

6

7

8

9

10

11

12

public class Apple<T extends Number>

{

    T col;

    public static void main(String[] args)

    {

        Apple<Integer> ai = new Apple<>();

        Apple<Double> ad = new Apple<>();

        // 下面代码将引起编译异常,下面代码试图把String类型传给T形参

        // 但String不是Number的子类型,所以引发编译错误

        Apple<String> as = new Apple<>();       // ①

    }

}

八、泛型方法

  如果定义类、接口是没有使用类型形参,但定义方法时想自己定义类型形参,这也是可以的,JDK1.5还提供了泛型方法的支持。

  泛型方法的语法格式为:

    修饰符 <T , S> 返回值类型 方法名(形参列表)

    {

      //方法体...

    }

  泛型方法的方法签名比普通方法的方法签名多了类型形参声明,类型形参声明以尖括号括起来,多个类型形参之间以逗号(,)隔开,所有类型形参声明放在方法修饰符和方法返回值类型之间。

  

  与类、接口中使用泛型参数不同的是,方法中的泛型参数无需显式传入实际类型参数,因为编译器根据实参推断类型形参的值。它通常推断出最直接的类型参数。

九、泛型方法与类型通配符的区别

  大时候都可以使用泛型方法来代替类型通配符。

  泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,不应该使用泛型方法。

十、设定通配符的下限

  Java集合框架中的TreeSet<E>有一个构造器也用到了这种设定通配符下限的语法,如下所示:

    TreeSet(Comparator<? super E> c)

十一、擦除与转换

  在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定类型参数。如果没有为这个泛型类指定类型参数,则该类型参数被称作一个raw type(原始类型),默认是该声明该参数时指定的第一个上限类型。

  当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,则所有在尖括号之间的类型信息都被扔掉了。比如说一个List<String>类型被转换为List,则该List对集合元素的类型检查变成了成类型变量的上限(即Object),这种情况被为擦除。

……待续

时间: 2024-10-18 08:53:34

Java 泛型 三的相关文章

Effective Java 第三版——29. 优先考虑泛型

Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化. 在这里第一时间翻译成中文版.供大家学习分享之用. 29. 优先考虑泛型 参数化声明并使用JDK提供的泛型类型和方法通常不会太困难. 但编写自己的泛型类型有点困难,但值得努力学习. 考虑条目 7中的简单堆栈实现: // Ob

2017.4.5 Java 泛型

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数. 假定我们有这样一个需求:写一个排序方法,能够对整形数组.字符串数组甚至其他任何类型的数组进行排序,该如何实现? 答案是可以使用 Java 泛型. 使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序.然后,调用该泛型方法来对整型数组.浮点数数组.字符串数组等进行排

java 泛型 窜讲

一.为什么使用泛型      复用性:泛型的本质就是参数化类型,因而使用编写的泛型代码可以被许多不同类型的对象所复用.      安全性:在对类型Object引用的参数操作时,往往需要进行显式的强制类型转换.这种强制类型转换需要在运行时才能被发现是否转换异常,通过引入泛型能将在运行时才能检查类型转换,提前到编译时期就能检查. 二.自定义泛型 java中自定义泛型分为三种:泛型类.泛型接口.泛型方法. 下面使用一个案例演示泛型类.泛型方法,泛型接口类似,所以不再演示. // 自定义泛型类publi

1月21日 - (转)Java 泛型

java泛型 什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样. 可以在集合框架(Collection framework)中看到泛型的动机.例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象. 因为 M

java泛型的讲解

java泛型 什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样. 可以在集合框架(Collection framework)中看到泛型的动机.例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象. 因为 M

Java 泛型 Java使用泛型的意义

Java 泛型 Java使用泛型的意义 @author ixenos 直接意义 在编译时保证类型安全 根本意义 a) 类型安全问题源自可复用性代码的设计,泛型保证了类型安全的复用模板 b) 使用复用性模板时不用手动强制类型转换 三种泛型实现方式的优缺点 C++:模板方式实现,在编译时完全展开并且支持偏特化,类型精度高,代码共享差: Java 5:擦除方式实现,仅用于编译时类型检查,在运行时擦除,向后兼容性好,代码共享好,类型精度非常差: C# 2.0:混合方式实现,在运行时展开特化,类型精度高,

Java泛型再学习

泛型是对于数据的一种规范,他限定了类.容器.方法可以接受的参数类型,避免参数类型混乱. 一.基本泛型 泛型最常见的地方就是集合,如: -- ArrayList<String>  表示这个集合中只能存放String类型的元素 -- HashMap<String, Object>  表示这个图中只能存放键为String类型,值为Object类型的元素 特别要注意的时,泛型只存在于编译阶段,在程序运行阶段,我们定义的泛型是并不存在的,这种方案叫“擦除”,示例: 1 public clas

java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题

java泛型(二).泛型的内部原理:类型擦除以及类型擦除带来的问题 参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首要前提是理解类型擦出(type erasure). Java中的泛型基本上都是在编译器这个层次来实现的.在生成的Java字节码中是不包含泛型中的类型信息的.使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉.这个过程就称为类型

Java进阶(三十五)java int与integer的区别

Java进阶(三十五)java int与integer的区别 前言 int与integer的区别从大的方面来说就是基本数据类型与其包装类的区别: int 是基本类型,直接存数值,而integer是对象,用一个引用指向这个对象. 1.Java 中的数据类型分为基本数据类型和复杂数据类型 int 是前者而integer 是后者(也就是一个类):因此在类进行初始化时int类的变量初始为0.而Integer的变量则初始化为null. 2.初始化时: int i =1; Integer i= new In