[笔记] 疯狂JAVA讲义(第3版)第9章 泛型

简介: [笔记] 疯狂JAVA讲义(第3版)第9章 泛型

第9章 泛型

泛型很大程度上是为了让集合能记住其元素的数据类型。

9.1 泛型入门

9.1.1 编译时不检查类型的异常

//ListErr.java
package ch9;

import java.util.ArrayList;
import java.util.List;

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()));//类型转换异常
  }
}

当程序“不小心”把一个Integer对象丢进List集合,导致强制类型转换成String时引发ClassCastException异常。

9.1.2 使用泛型

java5 后引入了“参数化类型”(parameterized type)的概念,允许程序在创建集合时指定集合元素的类型。如List<String>,表明该List只能存放String类型对象。

Java的参数化类型被称为泛型(Generic)。

对于前面的ListErr程序,可以使用泛型改进。

//创建集合,只能保存字符串。放入其他类型时会引起编译错误
List<String> strList = new ArrayList<String>();

9.1.3 Java7泛型的“菱形”语法

java7 之前使用泛型的接口、变量时,构造器也必须带泛型:

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

Java7之后,允许省略构造器后面的泛型信息,只给出<>即可:

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

9.2 深入泛型

所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。Java5改写了集合中的全部类和接口,提供了泛型支持。

9.2.1 定义泛型接口、类

可以为任何类、接口增加泛型声明。下面自定义一个Apple类:

//Apple.java
package ch9;
public class Apple<T> {
  private T info;
  public Apple() {}
  public Apple(T info) {
    this.info = info;
  }
  public T getInfo() {
    return info;
  }
  public void setInfo(T info) {
    this.info = info;
  }
  
  public static void main(String[] args) {
    Apple<String>a1 = new Apple<>("苹果");
    System.out.println(a1.getInfo());
    Apple<Double>a2 = new Apple<>(5.67);
    System.out.println(a2.getInfo());
  }
  
}

9.2.2 从泛型类派生子类

从泛型类、接口派生时,父类不能再包含类型形参:

//错误,父类不能带类型形参
public class A extends Apple<T>{}


如果想从Apple派生出一个子类,可以使用如方式:

public class A extends Apple<String>

也可以是:

public class A extends Apple

定义类、接口、方法时可以声明类型形参,使用时应该向类型形参传入实际类型。

如果从Apple<String>派生子类,Apple类中的T都会被替换成String。

如果使用Apple类时没有传入实际类型参数,java编译器可能会发出警告:使用未经检查或不安全的操作。此时,系统会把Apple<T>的T当成Object类型处理。

9.2.3 并不存在泛型类

不管为泛型的类型形参传入哪一种形参,对于Java来说,它们仍然被当成同一个类处理,在内存中也只占用一块内存空间。 因此,静态方法、静态变量、静态初始化块中不允许使用类型形参。

由于系统并没有真正生成泛型类,所以instanceof也不能用于泛型类。

9.3 类型通配符

9.3.1 使用类型通配符

为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号?,这个问号表示可以匹配任何类型:

public void test(List<?> c){
    for(int i = 0; i < c.size(); i++){
        System.out.println(c.get(i));
    }
}

可以使用任何类型的List来调用它。

但这种List<?>仅表示它是各种List的父类,并不能把元素加入其中,

List<?> c = new ArrayList<String>();
//下面语句引起编译错误
c.add(new Object());

因为无法确定c的类型,所以不能向其中添加对象。

9.3.2 设定类型通配符的上限

如果程序希望List<?> 只是某一类泛型List的父类:

//它表示所有Shape泛型List的父类
List<? extends Shape>


类似地,由于无法确定这个受限制通配符的具体类型,所以不能添加对象进这个集合。

9.3.3 设定类型形参的上限

表示传入的类型形参是上限类型或是上限类型的子类:

//传入的类型形参是Number类或是Number的子类
public class Apple<T extends Number>
{
T col;
...
}

另一个更严格地情况是,类型形参有多个上限:(使用&符号连接多个上限)

T必须是Number或Number子类,并且必须实现ava.io.Serializable接口
public class Apple<T extends Number & java.io.Serializable>

9.4 泛型方法

9.4.1 定义泛型方法

所谓泛型方法,就是声明方法时定义一个或多个类型形参:

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


//GenericMethodTest.java
package ch9;
import java.util.ArrayList;
import java.util.Collection;
public class GenericMethodTest {
  static <T> void fromArrayToCollection(T[] a,Collection<T> c) {
    for(T o: a) {
      c.add(o);
    }
  }
  public static void main(String[] args) {
    Object[] oa = new Object[10];
    Collection<Object> co = new ArrayList<>();
    fromArrayToCollection(oa, co);
    String[] sa = new String[10];
    Collection<String> cs = new ArrayList<>();
    fromArrayToCollection(sa, cs);  
        //T是Object
    fromArrayToCollection(sa, co);
  }
}


方法中的泛型形参无需显式传入实际类型参数,调用时根据实参推断类型形参的值。

9.4.2 泛型方法和类型通配符的区别

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

public interface Collection<E>
{
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
}

public interface Collection<E>
{
<T>boolean containsAll(Collection<T> c);
<T extends E> boolean addAll(Collection<T> c);
}

类型通配符:类型形参T的唯一效果是可以在不同调用点传入不同的实际类型。

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

如果需要,也可以同时使用泛型方法和通配符:

public static<T>void copy(List<T>dest,
                          List<? exteds T>src)

9.4.3 Java 7 的菱形语法与泛型构造器

构造器签名中也可以声明类型形参。

如果构指定了造器中声明的类型形参的实际类型,则不可以使用“菱形”语法。

9.4.4 设定通配符下限

<? super Type>

表示是Type或Type父类

9.4.5 泛型方法与方法重载

public static<T>void copy(Collection<T>dest,
                          Collection<? exteds T>src)
 public static<T> T copy(Collection<? superT>dest,
                         Collection T>src)  

调用copy时会引起编译错误。

9.4.6 Java 8 改进的类型推断

1、根据调用方法的上下文推断参数的目标类型

2、在方法调用链中,将推断得到的类型参数传递到最后一个方法。

需要注意的是,这种推断并不万能,有些地方要手动指定类型参数。

9.5 擦除和转换

在严格地泛型代码中,带泛型声明的类总应该带着类型参数。但为了与老版本兼容,也允许不指定类型参数。如果没指定类型参数,则该类型参数为raw type(原始类型),默认为声明该参数指定的第一个上限类型。

9.6 泛型和数组

数组元素的类型不能包含类型变量或类型形参,除非是无上限的类型通配符。

简单来说,不要创建泛型数组。

9.7 小结

奇怪的知识增加了。

很多奇怪的用法,奇怪的错误,比如9.5和9.6节。

相关文章
|
2月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
214 10
|
4月前
|
安全 Java API
在Java中识别泛型信息
以上步骤和示例代码展示了怎样在Java中获取泛型类、泛型方法和泛型字段的类型参数信息。这些方法利用Java的反射API来绕过类型擦除的限制并访问运行时的类型信息。这对于在运行时进行类型安全的操作是很有帮助的,比如在创建类型安全的集合或者其他复杂数据结构时处理泛型。注意,过度使用反射可能会导致代码难以理解和维护,因此应该在确有必要时才使用反射来获取泛型信息。
192 11
|
Java 开发工具 Android开发
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
131 2
|
Java 开发工具 Android开发
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
本系列教程笔记详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。若需快速学习Kotlin,建议查看“简洁”系列教程。本期重点介绍了Kotlin与Java的共存方式,包括属性、单例对象、默认参数方法、包方法、扩展方法以及内部类和成员的互操作性。通过这些内容,帮助你在项目中更好地结合使用这两种语言。
150 1
|
10月前
|
存储 Java 开发者
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
本文详细介绍了 Java 中 `toString()` 方法的重写技巧及其重要
516 10
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
|
10月前
|
前端开发 JavaScript Java
Java构建工具-maven的复习笔记【适用于复习】
这篇文档由「潜意识Java」创作,主要介绍Maven的相关知识。内容涵盖Maven的基本概念、作用、项目导入步骤、依赖管理(包括依赖配置、代码示例、总结)、依赖传递、依赖范围以及依赖的生命周期等七个方面。作者擅长前端开发,秉持“得之坦然,失之淡然”的座右铭。期待您的点赞、关注和收藏,这将是作者持续创作的动力! [个人主页](https://blog.csdn.net/weixin_73355603?spm=1000.2115.3001.5343)
233 3
|
11月前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
11月前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
12月前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
179 2