Java核心技术第八章-泛型

简介: Java核心技术第八章-泛型

摘要

本文根据《Java核心技术 卷一》一书的第八章总结而成,部分文章摘抄书内,作为个人笔记。

文章不会过于深入,望读者参考便好。

为什么要使用泛型程序设计

泛型程序设计(Generic programming) 意味着编写的代码可以被很多不同类型的对象所重用。

类型参数的好处

在没有泛型类之前,ArrayList类只维护一个Object引用的数组:

public class ArrayList {
   private Object[] elementData; // 用于存放数据
   public Object get(int i) { . . . }
   public void add(Object o) { . . . }
     ...
}

问题

1.获取一个值时必须进行强制类型转换

2.这里没有错误检査。可以向数组列表中添加任何类的对象,如果数组的类型不一致,将 get 的结果进行强制强制类型,就会错误。

泛型提供了一个更好的解决方案: 类型参数:

ArrayList<String> array = new ArrayList<String>():

利用类型参数的信息,我们就可以在添加数据的时候保持类型统一,调用get方法时候也不需要进行强制类型转换,因为我们在初始化的时候就定义了类型,编译器识别返回值的类型就会帮我们转换该类型。

定义一个简单泛型类

public class Pair<T> {
    private T num;
 
    public Pair(T num) { this.num = num; }
    public T getNum() { return num; }
    public void setNum(T num) { this.num = num; }
}

我们可以看到,在Pair类名后面添加了一个 ,这个是泛型类的类型变量,而且还可以有多个类型变量,如

public class Pair<T,U> {
    ...
}

如果我们实例化Pair类,例如:

new Pair<String>;

那么我们就可以把上述的Pair类想象成如下:

public class Pair<String> {
    private String num;
 
    public Pair(String num) { this.num = num; }
    public String getNum() { return num; }
    public void setNum(String num) { this.num = num; }
}

是不是很简单呢?在Java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型,T、U、S表示任意类型。

泛型方法

定义一个带有类型参数的方法

    public static <T> T getMiddle(T... a) {
        return a[a.length / 2];
    }

可以看到类型变量(< T >)放在修饰符( public static )的后面,返回类型(T)的前面。泛型方法可以定义在普通类或泛型类中。

类型变量的限定

如果我们需要对类型变量加以约束,例如:传入的变量必须实现Comparable接口,因为需要该变量调用compareTo的方法。这样我们就可以使用extends关键字对变量进行限定。

    public <T extends Comparable> T max(T a) {
        a.compareTo(...);
        ...
    }

无论变量需要限定为继承某个类或者实现某个接口,都是使用extends关键字进行限定。

泛型代码和虚拟机

类型擦除

无论我们在代码中怎么定义一个泛型类、泛型方法,都提供了一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类姓名。如 <T> 的原始类型为 Object<T extends MyClass>的原始类型为MyClass

代码就像下面这样:

public class Pair<T> {
    private T property;
 
    public Pair(T property) {
        this.property = property;
    }
}

类型擦除后:

public class Pair<Object> {
    private Object property;
 
    public Pair(Object property) {
        this.property = property;
    }
}

翻译泛型表达式

如果擦除返回类型,编译器会插入强制类型转换,就像下面这样:

Pair<Employee> buddies = . .
Employee buddy = buddies.getFirst();

擦除getFirst的返回类型后将返回Object类型,但是编译器将自动帮我们强制类型转换为Employee。

所以:编译器把这个方法执行操作分为两条指令:

对原始方法Pair.getFirst的调用

将返回的Object类型强制转换为Employee类型

小节总结:

虚拟机中没有泛型,只有普通的类和方法

所有的类型参数都用他们的限定类型替换

为保持类型安全性,必要时插入强制类型转换

桥方法被合成来保持多态(本文没有讲到,不过桥方法可以忽略,Java编写不规范才会有桥方法生成)

约束与局限性

不能用基本类型实例化类型参数

不可以用八大基本数据类型去实例化类型参数,你也没见过ArrayList<int>这样的代码吧。只有ArrayList<Integer>。原因是因为基本数据类型是不属于Object的。所以只能用他们的包装类型替换。

运行时类型查询只适用于原始类型

所有的类型查询只产生原始类型,因为在虚拟机没有所谓的泛型类型。

例如:

    Pair<String> pair = new Pair<String>("johnson");
    if (pair instanceof Pair<String>) {} // Error
    if (pair instanceof Pair<T>) {} // Error
    if (pair instanceof Pair) {} // Pass

不能创建参数化类型的数组

Pair<String>[] pairs = new Pair<String>[10]; //error

为什么不能这样定义呢?因pairs的类型是Pair[],可以转换为Object[],如果试图存储其他类型的元素,就会抛出ArrayStoreException异常,

pairs[0] = 10L; //抛异常

总之一句话,不严谨。所以不能创建参数化类型的数组。

不能实例化类型变量

不能使用 new T(...)、new T[...] 或 T.class。因为类型擦除后,T将变成Object,而且我们肯定不是希望实例化Object。

不过在Java8之后,我们可以使用Supplier<T>接口实现,这是一个函数式接口,表示一个无参数而且有返回类型为T的函数:

public class Pair<T> {
    private T first;
    private T second;
 
    private Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }
 
    public static <T> Pair<T> makePair(Supplier<T> constr) {
        return new Pair<>(constr.get(), constr.get());
    }
    
    public static void main(String[] args) {
        Pair<String> pair = Pair.makePair(String::new);
    }
}

泛型类中的静态上下文中类型变量无效

不能在静态域或方法中引用类型变量。例如:

public class Pair<T> {
    private static T instance; //Error
 
    public static T getInstance() { //Error
        if (instance == null) 
            instance = new Pair<T>();
        return instance;
    }
}

因为类中的类型变量(<T>)是在对象中的作用域有效,而不是在类中的作用域有效。如果要使用泛型方法,可以参照文章上面的泛型方法哦~

不能抛出或捕获泛型类的实例

即不能抛出也不能捕获泛型类的对象,甚至扩展Throwable都是不合法的:

public class Pair<String> extend Exceotion {} //Error
public static <T extends Throwable> void doWork(Class<T> t) {
    try {
        ...
    } catch (T e) { //Error
        ...
    }
}

但是在抛出异常后对异常使用类型变量是允许的(个人感觉没见过这样的代码)。

public static <T extends Throwable> void doWork(Class<T> t) throw T { //Pass
    try {
        ...
    } catch (Exception e) {
        throw e;
    }
}

泛型类型的继承规则

Manager类继承Employee类。但是Pair<Employee>Pair<Manager>是没有关联的。就像下面的代码,就会提示报错,传递失败:

        Pair<Manager> managerPair = new Pair<Manager>();
        Pair<Employee> employeePair = managerPair; //Error

通配符类型

通配符概念

通配符类型中,允许类型参数变化,使用 ? 标识通配符类型:

Pair<? extends Employee>

若Pair类如下

public class Pair<T> {
    private T object;
 
    public void setObject(T object) {
        this.object = object;
    }
}

那么使用通配符可以解决泛型类型的继承规则问题,如:

        Pair<Manager> managerPair = new Pair<Manager>();
        Pair<? extends Employee> employeePair = managerPair; //Pass
        Manager manager = new Manager();
        employeePair.setObject(manager); //Error

使用<? extends Employee>,编译器只知道employeePair 是Employee的子类,但是不清楚具体是什么子类,所以最后一步employeePair.setObject(manager)不能执行。

通配符的超类型限定

通配符还有一个附加的能力,就是可以指定一个超类型限定,如:

public class Pair<T> [
    ...
    public static void salary(Pair<? super Manager> result) {
        //...
    }
}

<? super Manager>这个通配符为Manager的所有超类型(包含Manger),例如:

        Pair<Manager> managerPair = new Pair<Manager>();
        Pair<Employee> employeePair = new Pair<Employee>();
 
        Pair.salary(managerPair); //Pass
        Pair.salary(employeePair); //Pass
 
        // 假如 ManagerChild为Manager子类
        Pair<ManagerChild> managerChildPair = new Pair<ManagerChild>();
        Pair.salary(managerChildPair); //Error

无限定通配符

无限定通配符,如:Pair<?>,当我们不需要理会他的实际类型时候,就可以使用无限定通配符,上代码:

    public static boolean hasNull(Pair<?> pair) {
        return pair.getObject() == null;
    }

说实话,通配符搞得我头昏脑胀的,反复不断地看文章,才开始慢慢看懂,我太难了。。。



文章到这里就结束啦,不知道各位小伙伴看懂了没,没看懂的话可能是我的功底和文章写作能力还有待提高,小伙伴们也可以去看一下《Java核心技术 卷一》

这本书呢,感觉还是挺不错的。最近把这本书捡起来看也是发现基础是非常重要的,先把基础沉淀好了,再学习其他的技术点也会更容易入手,也会知其然知其所然。最近非常喜欢的一句话,送给大家:“万丈高楼平地起,勿在浮沙筑高台”。

相关文章
|
3天前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
20 9
Java——包装类和泛型
|
6天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
17天前
|
Java API
Java技术体系
Java技术体系包括运行于Java虚拟机上的各种语言及其相关程序,传统上由Java程序设计语言、Java虚拟机、Class文件格式、Java API类库以及第三方类库组成,可进一步细分为Java Card、Java ME、Java SE和Java EE四个平台。
32 3
Java技术体系
|
3天前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。
|
7天前
|
存储 负载均衡 Java
Jetty技术深度解析及其在Java中的实战应用
【9月更文挑战第3天】Jetty,作为一款开源的、轻量级、高性能的Java Web服务器和Servlet容器,自1995年问世以来,凭借其卓越的性能、灵活的配置和丰富的扩展功能,在Java Web应用开发中占据了举足轻重的地位。本文将详细介绍Jetty的背景、核心功能点以及在Java中的实战应用,帮助开发者更好地理解和利用Jetty构建高效、可靠的Web服务。
22 2
|
12天前
|
Java 程序员 编译器
Java的反射技术reflect
Java的反射技术允许程序在运行时动态加载和操作类,基于字节码文件构建中间语言代码,进而生成机器码在JVM上执行,实现了“一次编译,到处运行”。此技术虽需更多运行时间,但广泛应用于Spring框架的持续集成、动态配置及三大特性(IOC、DI、AOP)中,支持企业级应用的迭代升级和灵活配置管理,适用于集群部署与数据同步场景。
|
11天前
|
算法 Oracle Java
Java字符串拼接技术演进及阿里巴巴的贡献
本文主要讲述了Java字符串拼接技术的演进历程,以及阿里巴巴贡献的最新实现 PR 20273。
|
16天前
|
算法 Oracle Java
Java字符串拼接技术演进及阿里巴巴的贡献
本文主要讲述了Java字符串拼接技术的演进历程,以及阿里巴巴贡献的最新实现 PR 20273。
|
1天前
|
Kubernetes Cloud Native Java
探索未来编程新纪元:Quarkus带你秒建高性能Kubernetes原生Java应用,云原生时代的技术狂欢!
Quarkus 是专为 Kubernetes 设计的全栈云原生 Java 框架,凭借其轻量级、快速启动及高效执行特性,在 Java 社区脱颖而出。通过编译时优化与原生镜像支持,Quarkus 提升了应用性能,同时保持了 Java 的熟悉度与灵活性。本文将指导你从创建项目、编写 REST 控制器到构建与部署 Kubernetes 原生镜像的全过程,让你快速上手 Quarkus,体验高效开发与部署的乐趣。
8 0
|
14天前
|
关系型数据库 Java MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【9月更文挑战第6天】在Linux环境下安装JDK 1.8、Tomcat和MariaDB是搭建Java Web应用的关键步骤。本文详细介绍了使用apt-get安装OpenJDK 1.8、下载并配置Tomcat,以及安装和安全设置MariaDB(MySQL的开源分支)的方法。通过这些步骤,您可以快速构建一个稳定、高效的开发和部署环境,并验证各组件是否正确安装和运行。这为您的Java Web应用提供了一个坚实的基础。
30 0