读书笔记-《ON JAVA 中文版》-摘要22[第二十章 泛型-1]

news2025/2/23 13:34:35

文章目录

  • 第二十章 泛型
    • 1. 简单泛型
      • 1.1 简单泛型
      • 1.2 一个元组类库
    • 2. 泛型接口
    • 3. 泛型方法
      • 3.1 泛型方法
      • 3.2 变长参数和泛型方法
    • 4. 构建复杂模型

第二十章 泛型

普通的类和方法只能使用特定的类型:基本数据类型或类类型。如果编写的代码需要应用于多种类型,这种严苛的限制对代码的束缚就会很大。

多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。

拘泥于单一的继承体系太过局限,如果方法以接口而不是类作为参数,限制就宽松多了,只要实现了接口就可以。

即便是接口也还是有诸多限制。一旦指定了接口,它就要求你的代码必须使用特定的接口。而我们希望编写更通用的代码,能够适用“非特定的类型”,而不是一个具体的接口或类。

这就是泛型的概念,是 Java 5 的重大变化之一。在很多情况下,它可以使代码更直接更优雅。

1. 简单泛型

1.1 简单泛型

促成泛型出现的最主要的动机之一是为了创建集合类。

先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型:

package generics;

class Automobile {}
public class Holder1 {
    private Automobile a;

    public Holder1(Automobile a) {
        this.a = a;
    }

    Automobile get() {
        return a;
    }
}

这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到的每个类型都编写一个新的类。

在 Java 5 之前,我们可以让这个类直接持有 Object 类型的对象:

package generics;

public class ObjectHolder {
    private Object a;

    public ObjectHolder(Object a) {
        this.a = a;
    }

    public Object get() {
        return a;
    }

    public void set(Object a) {
        this.a = a;
    }

    public static void main(String[] args) {
        ObjectHolder h2 = new ObjectHolder(new Automobile());
        Automobile a = (Automobile) h2.get();
        h2.set("Not an Automobile");
        String s = (String) h2.get();
        h2.set(1);
        Integer x = (Integer) h2.get();
    }
}

与其使用 Object ,我们更希望先指定一个类型占位符,稍后再决定具体使用什么类型。要达到这个目的,需要使用类型参数,用尖括号括住,放在类名后面。然后在使用这个类时,再用实际的类型替换此类型参数。在下面的例子中, T 就是类型参数:

package generics;

public class GenericHolder<T> {
    private T t;

    public GenericHolder() {
    }

    public T get() {
        return t;
    }

    public void set(T t) {
        this.t = t;
    }

    public static void main(String[] args) {
        GenericHolder<Automobile> h3 = new GenericHolder<>();
        h3.set(new Automobile()); // 此处有类型校验
        Automobile a = h3.get(); // 无需类型转换
//        h3.set("Not an Automobile"); // 报错
//        h3.set(1); // 报错
    }
}

这就是 Java 泛型的核心概念:你只需告诉编译器要使用什么类型,剩下的细节交给它来处理。

1.2 一个元组类库

有时一个方法需要能返回多个对象。而 return 语句只能返回单个对象,解决方法就是创建一个对象,用它打包想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。

但是有了泛型,我们就可以一劳永逸。同时,还获得了编译时的类型安全。

这个概念称为元组,它是将一组对象直接打包存储于单一对象中。可以从该对象读取其中的元素,但不允许向其中存储新对象(这个概念也称为 数据传输对象 或 信使 )。

下面是一个可以存储两个对象的元组:

package onjava;

public class Tuple2<A, B> {
    public final A a1;
    public final B a2;

    // 构造函数传入要存储的对象。这个元组隐式地保持了其中元素的次序。
    public Tuple2(A a1, B a2) {
        this.a1 = a1;
        this.a2 = a2;
    }

    public String rep() {
        return a1 + ", " + a2;
    }

    @Override
    public String toString() {
        return "(" + rep() + ")";
    }
}

以利用继承机制实现长度更长的元组。添加更多的类型参数就行了:

package onjava;

public class Tuple3<A, B, C> extends Tuple2<A, B> {
    public final C a3;

    public Tuple3(A a, B b, C c) {
        super(a, b);
        a3 = c;
    }

    @Override
    public String rep() {
        return super.rep() + ", " + a3;
    }
}
package onjava;

public class Tuple4<A, B, C, D> extends Tuple3<A, B, C> {
    public final D a4;

    public Tuple4(A a, B b, C c, D d) {
        super(a, b, c);
        a4 = d;
    }

    @Override
    public String rep() {
        return super.rep() + ", " + a4;
    }
}
package onjava;

public class Tuple5<A, B, C, D, E> extends Tuple4<A, B, C, D> {
    public final E a5;

    public Tuple5(A a, B b, C c, D d, E e) {
        super(a, b, c, d);
        a5 = e;
    }

    @Override
    public String rep() {
        return super.rep() + ", " + a5;
    }
}

演示需要,再定义两个类:

package generics;

public class Amphibian {
}

package generics;

public class Vehicle {
}

使用元组时,你只需要定义一个长度适合的元组,将其作为返回值即可。注意下面例子中方法的返回类型:

package generics;

import onjava.Tuple2;
import onjava.Tuple3;
import onjava.Tuple4;
import onjava.Tuple5;

public class TupleTest {
    static Tuple2<String, Integer> f() {
        return new Tuple2<>("hi", 47);
    }

    static Tuple3<Amphibian, String, Integer> g() {
        return new Tuple3<>(new Amphibian(), "hi", 47);
    }

    static Tuple4<Vehicle, Amphibian, String, Integer> h() {
        return new Tuple4<>(new Vehicle(), new Amphibian(), "hi", 47);
    }

    static Tuple5<Vehicle, Amphibian, String, Integer, Double> k() {
        return new Tuple5<>(new Vehicle(), new Amphibian(), "hi", 47, 11.1);
    }

    public static void main(String[] args) {
        Tuple2<String, Integer> ttsi = f();
        System.out.println(ttsi);
//        ttsi.a1="there"; // 编译错误,因为 final 不能重新赋值
        System.out.println(g());
        System.out.println(h());
        System.out.println(k());
    }
}

输出:

(hi, 47)
(generics.Amphibian@7699a589, hi, 47)
(generics.Vehicle@58372a00, generics.Amphibian@4dd8dc3, hi, 47)
(generics.Vehicle@6d03e736, generics.Amphibian@568db2f2, hi, 47, 11.1)

2. 泛型接口

泛型也可以应用于接口。例如 生成器,这是一种专门负责创建对象的类

一般而言,一个生成器只定义一个方法,用于创建对象。例如 java.util.function 类库中的 Supplier 就是一个生成器,调用其 get() 获取对象。 get() 是泛型方法,返回值为类型参数 T 。

package generics.coffee;

public class Coffee {
    private static long counter = 0;
    private final long id = counter++;

    @Override
    public String toString() {
        return getClass().getSimpleName() + " " + id;
    }
}

package generics.coffee;

public class Americano extends Coffee{
}

package generics.coffee;

public class Breve extends Coffee{
}

package generics.coffee;

public class Cappuccino extends Coffee{
}

package generics.coffee;

public class Latte extends Coffee{
}

package generics.coffee;

public class Mocha extends Coffee{
}

编写一个类,实现 Supplier 接口,它能够随机生成不同类型的 Coffee 对象:

package generics.coffee;

import java.util.Iterator;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class CoffeeSupplier
        implements Supplier<Coffee>, Iterable<Coffee> {
    private Class<?>[] types = {Latte.class, Mocha.class,
            Cappuccino.class, Americano.class, Breve.class};
    private static Random rand = new Random(47);

    public CoffeeSupplier() {
        System.out.println("1");
    }

    // For iteration:
    private int size = 0;

    public CoffeeSupplier(int sz) {
        System.out.println("2");
        size = sz;
    }

    @Override
    public Coffee get() {
        System.out.println("3");
        try {
            return (Coffee) types[rand.nextInt(types.length)].newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    class CoffeeIterator implements Iterator<Coffee> {
        int count = size;

        @Override
        public boolean hasNext() {
            System.out.println("4");
            return count > 0;
        }

        @Override
        public Coffee next() {
            System.out.println("5");
            count--;
            return CoffeeSupplier.this.get();
        }

        @Override
        public void remove() {
            System.out.println("6");
            throw new UnsupportedOperationException();
        }
    }

    @Override
    public Iterator<Coffee> iterator() {
        System.out.println("7");
        return new CoffeeIterator();
    }

    public static void main(String[] args) {
        Stream.generate(new CoffeeSupplier())
                .limit(5)
                .forEach(System.out::println);
        System.out.println("------------------");
        for (Coffee c : new CoffeeSupplier(5)) {
            System.out.println(c);
        }
    }
}

输出:

1
3
Americano 0
3
Latte 1
3
Americano 2
3
Mocha 3
3
Mocha 4
------------------
2
7
4
5
3
Breve 5
4
5
3
Americano 6
4
5
3
Latte 7
4
5
3
Cappuccino 8
4
5
3
Cappuccino 9
4

—PS:为了了解代码执行顺序,加了几个输出语句。迷糊的话,可以看下这个大佬的文章:字节面试官问:Iterator和erable有什么区别?

3. 泛型方法

3.1 泛型方法

泛型方法独立于类而改变方法。

如果方法是 static 的,则无法访问该类的泛型类型参数,因此,如果使用了泛型类型参数,则它必须是泛型方法。

要定义泛型方法,请将泛型参数列表放置在返回值之前,如下所示:

package generics;

public class GenericMethods {
    // PS:<T> 泛型参数列表
    public <T> void f(T x) {
        System.out.println(x.getClass().getName());
    }

    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1);
        gm.f(1.0);
        gm.f(1.0F);
        gm.f('c');
        gm.f(gm);
    }
}

输出:

java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
java.lang.Character
generics.GenericMethods

对于泛型类,必须在实例化该类时指定类型参数。使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型。 这称为 类型参数推断

3.2 变长参数和泛型方法

泛型方法和变长参数列表可以很好地共存:

package generics;

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

public class GenericVarargs {
    @SafeVarargs
    public static <T> List<T> makeList(T... args) {
        List<T> result = new ArrayList<>();
        for (T item : args) {
            result.add(item);
        }
        return result;
    }

    public static void main(String[] args) {
        List<String> ls = makeList("A");
        System.out.println(ls);
        ls = makeList("A", "B", "C");
        System.out.println(ls);
        ls = makeList(
                "ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
        System.out.println(ls);
    }
}

输出:

[A]
[A, B, C]
[A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]

4. 构建复杂模型

泛型的一个重要好处是能够简单安全地创建复杂模型。例如,我们可以轻松地创建一个元组列表:

package generics;

import onjava.Tuple4;

import java.util.ArrayList;

public class TupleList<A, B, C, D> extends ArrayList<Tuple4<A, B, C, D>> {

    public static void main(String[] args) {
        TupleList<Vehicle, Amphibian, String, Integer> tl = new TupleList<>();
        tl.add(TupleTest2.h());
        tl.add(TupleTest2.h());
        tl.forEach(System.out::println);
    }
}

class TupleTest2 {
    public static <A, B, C, D> Tuple4<A, B, C, D> tuple(A a, B b, C c, D d) {
        return new Tuple4<>(a, b, c, d);
    }

    public static Tuple4<Vehicle, Amphibian, String, Integer> h() {
        return tuple(new Vehicle(), new Amphibian(), "hi", 47);
    }
}

输出:

(generics.Vehicle@306a30c7, generics.Amphibian@b81eda8, hi, 47)
(generics.Vehicle@68de145, generics.Amphibian@27fa135a, hi, 47)

这将产生一个功能强大的数据结构,而无需太多代码。

在这里插入图片描述
(图网,侵删)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/897568.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

神卓互联内网穿透

神卓互联内网穿透是一种技术&#xff0c;用于实现在不同网络环境下的设备互相访问。通常情况下&#xff0c;内网设备无法直接从外部访问&#xff0c;但通过内网穿透技术可以实现外部设备与内网设备之间的通信。 使用神卓互联内网穿透&#xff0c;您可以将内网设备暴露在公网上…

苹果也顶不住了,警告睡觉时不要将iPhone放在旁边,有自燃的风险

苹果或许是由于近几年来&#xff0c;iPhone自燃导致损失的案例增加&#xff0c;外媒报道指苹果警告用户不要将充电的iPhone放在身边&#xff0c;特别是睡觉的时候更不要将充电中的iPhone放在身边&#xff0c;这可能导致危险。 这几年iPhone自燃的事件屡屡有报道&#xff0c;甚至…

(黑客)自学

一、前言&#xff1a; 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发. 3.有时多 google,baidu,我们往往都遇不到好心的大神,谁会无聊天天给你做解答. 4.遇到实在搞不懂的,可以先放放,以…

「UG/NX」Block UI 超级截面SuperSection

✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#

【Freertos基础入门】队列(queue)的使用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、队列是什么&#xff1f;二、队列的操作二、示例代码总结 前言 本系列基于stm32系列单片机来使用freerots FreeRTOS是一个广泛使用的开源实时操作系统&…

如何在Java实现TCP方式发送和接收Socket消息(多线程模式)

目录 导言&#xff1a;正文&#xff1a;1. 创建Server端&#xff1a;2. 创建Client端&#xff1a;3. 多线程模式&#xff1a; 代码示例Server端代码示例&#xff1a;Client端代码示例&#xff1a;同步模式发送TCP消息异步模式 结论&#xff1a; 导言&#xff1a; 在Java编程中…

POJ 3641 Pseudoprime numbers 米勒拉宾素数判定+埃氏筛法

一、思路 对于输入的一个数字n和a&#xff0c;我们用快速幂判断 n ^ a % n 是否等于n&#xff0c;如果不等于直接输出no&#xff0c;等于的话&#xff0c;再判断n是否为素数&#xff0c;如果n为素数&#xff0c;输出no&#xff0c;否则输出yes。 判断素数的话&#xff0c;对于…

德赛西威半年报:竞争加剧,座舱乏力?智驾继续保持高增速

作为国内头部的智能网联核心软硬件供应商&#xff0c;德赛西威的表现&#xff0c;也在一定程度上反映了市场的行情走向&#xff0c;也凸显不同细分赛道的竞争白热化。 半年报数据显示&#xff0c;2023年1-6月&#xff0c;德赛西威实现营业收入87.24亿元&#xff0c;同比增长36.…

【LeetCode75】第三十三题 二叉树的最大深度

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 从这一题开始&#xff0c;LeetCode75进入到了二叉树章节。 这边建议不熟悉二叉树的小伙伴可以先去做做力扣的前序遍历&#xff0c;中序遍…

Azure存储访问层

blob数据的热访问层&#xff0c;冷访问层和存档访问层 Azure Blob 存储是一种托管对象存储服务&#xff0c;可用于存储和访问大量非结构化数据&#xff0c;如文本和二进制数据。Azure Blob 存储提供了三个不同层级的访问方式&#xff0c;以适应不同数据的使用模式和成本效益需…

基于GUI的卷积神经网络和长短期神经网络的语音识别系统,卷积神经网的原理,长短期神经网络的原理

目录 背影 卷积神经网络CNN的原理 卷积神经网络CNN的定义 卷积神经网络CNN的神经元 卷积神经网络CNN的激活函数 卷积神经网络CNN的传递函数 长短期神经网络的原理 基于GUI的卷积神经网络和长短期神经网络的语音识别系统 代码下载链接:基于MATLABGUI编程的卷积神经网络和长短期…

XQuery创建BaseX数据库实例

XQuery创建BaseX数据库实例 文章目录 XQuery创建BaseX数据库实例1、准备工作2、demo目录结构3、IDEA配置BaseX4、工具类BaseXClient5、Example 1、准备工作 开发工具&#xff1a; IDEAOxygen 技术&#xff1a; JavaBaseXXpathXquery BaseX需要阅读的文档&#xff1a; htt…

【学习日记】【FreeRTOS】延时列表的实现

前言 本文在前面文章的基础上实现了延时列表&#xff0c;取消了 TCB 中的延时参数。 本文是对野火 RTOS 教程的笔记&#xff0c;融入了笔者的理解&#xff0c;代码大部分来自野火。 一、如何更高效地查找延时到期的任务 1. 朴素方式 在本文之前&#xff0c;我们使用了一种朴…

二进制数的左移和右移位运算numpy.left_shift()numpy.right_shift()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 二进制数的左移和右移位运算 numpy.left_shift() numpy.right_shift() [太阳]选择题 下列代码最后一次输出的结果是&#xff1f; import numpy as np a 8 print("【显示】a ", a)…

AgentBench::AI智能体发展的潜在问题(二)

从历史上看&#xff0c;几乎每一种新技术的广泛应用都会在带来新机遇的同时引发很多新问题&#xff0c;AI智能体也不例外。从目前的发展看&#xff0c;AI智能体的发展可能带来的新问题可能包括如下方面&#xff1a; 第二是AI智能体的普及将有可能进一步加剧AI造成的技术性失业。…

无脑入门pytorch系列(四)—— scatter_

本系列教程适用于没有任何pytorch的同学&#xff08;简单的python语法还是要的&#xff09;&#xff0c;从代码的表层出发挖掘代码的深层含义&#xff0c;理解具体的意思和内涵。pytorch的很多函数看着非常简单&#xff0c;但是其中包含了很多内容&#xff0c;不了解其中的意思…

【云原生】k8s存储管理中ConfigMap Secret的使用

目录 1 ConfigMap 1.1 简介 1.2 优点 1.3 定义 ConfigMap 1.4 使用 2 Secret 2.1 简介 2.1 定义 Secret 2.2 使用 1 ConfigMap 1.1 简介 在 Kubernetes 中&#xff0c;ConfigMap 是一种用于存储非敏感信息的 Kubernetes 对象。它用于存储配置数据&#xff0c;如键值…

【Redis从头学-4】Redis中的String数据类型实战应用场景之验证码、浏览量、点赞量、Json格式存储

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Re…

融媒行业落地客户旅程编排,详解数字化用户运营实战

移动互联网时代是流量红利的时代&#xff0c;企业常用低成本的方式进行获客&#xff0c;“增长黑客”的概念大范围传播。与此同时&#xff0c;机构媒体受到传播环境的影响&#xff0c;也开始启动全行业的媒体融合转型。在此背景下&#xff0c;2015 年神策数据成立&#xff0c;核…

数据结构,线性表与线性结构关系,顺序表与顺序结构关系,线性表与顺序表关系

学习数据结构会出现很多的概念如顺序结构&#xff0c;非线性结构&#xff0c;顺序表&#xff0c;顺序结构&#xff0c;顺序表&#xff0c;链表&#xff0c;栈&#xff0c;队列&#xff0c;堆等。今天来小讲以下其中的线性表与线性结构&#xff0c;顺序表与顺序结构的关系。 在数…