理解Java泛型的用例


理解泛型的一般用例解决了问题的一半。首先,我们应该知道它们是什么,为什么要考虑它们,以及它们适用于哪里。

什么事?

考虑一个简单的添加方法,如下所示。您不能将长整型、浮点型或双精度型作为此方法的输入,对吗?

public static int add(int a, int b) {
    return a + b;
}


如果我们能从方法中提取出数据类型,我们就得到一个新的方法,如下所示。这里,< T >是类型参数,类似于我们为方法声明的参数。我们为类型参数传递的值<整数>或<双精度>是类型参数,类似于我们传递的方法参数。

public class Main {
    //In the below 2 methods, only Data Type which extends Number are allowed.
    public static < T extends Number > double addStaticMethod(T a, T b) {
        return a.doubleValue() + b.doubleValue();
    }
    public < T extends Number > double addInstanceMethod(T a, T b) {
        return a.doubleValue() + b.doubleValue();
    }

    public static void main(String[] args) {
        // static method invocation with Type Argument. It is type safe, only Integer is allowed.
        System.out.println(Main. < Integer > addStaticMethod(3, 4));
        //static method invocation without Type Argument, not type safe. Both Integer and Float is allowed.
        System.out.println(addStaticMethod(3, 4.3));

        Main m = new Main();
        //Instance method invocation with Type Argument.
        System.out.println(m. < Double > addInstanceMethod(3.2, 4.3));
    }
}


现在考虑一个数据结构。为简单起见,让我们考虑一下阵列。我们能创建任何类型的数组吗?不,我们不能。我们可以创建一个整数、浮点或任何特定类型的数组。忘掉任何语言的数组实现,然后问:“我们能从这个数据结构中抽象出数据类型吗?”

回答:是的,我们可以。在Java中,数组列表就是这样一个类。当你说List<String> = new ArrayList<>();,它创建一个字符串数组。当您将整数作为类型参数而不是字符串传递时,它会创建一个整数数组,依此类推。

尽管已经谈到了数组列表,但由于其复杂性,我们不会讨论它们的实现。因此,我们将拿一个盒子来研究如何制作盒子——一个普通盒子和一个特殊类型的盒子。

考虑以下代码。您可以将一个字符串放入一个特定的字符串框对象中,然后从中获取一个字符串。

public class SpecilizedStringBox  {

    private String item;

    public String getItem() {
        return item;
    }

    public void setItem(String item) {
        this.item = item;
    }
}


现在,如果我们从SpecilizedStringBox中抽象出数据类型“字符串”,我们会得到一个由以下代码表示的通用框,当然,它可以采用字符串、整数、布尔或任何数据类型。

public class GenericBox<T>  {

    private T item;

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}


因此,使用泛型就是从一个方法或类中抽象出类型,以创建适用于比特定类型更多类型的泛型方法或类。

为什么?

一个简单的答案是抽象出数据类型,允许您重用代码并更容易地维护它。

它们适用于哪里?

看起来我们可以通过重构一个现有的特定类型化方法或者一个盒子来应用泛型。在我们处理数据结构和原始数据类型之前,它看起来很简单,但是我们确实用不同的类创建了许多数据类型。将泛型编程范式与面向对象编程相结合,使得选择是否应用泛型变得非常困难。了解在哪里可以应用它们解决了一半的问题。

本文让您了解泛型的一些用例,包括它们通常应用的地方,并且确保如果您遇到这样的用例,您也可以应用泛型。

Java在Java 5.0中加入了泛型,以实现:

  1. 类型安全性确保一旦应用了类型参数,就不允许其他数据类型进入方法或框中,并避免了强制转换的要求。
  2. 泛型编程/参数多态性

C++模板编程帮助我们实现泛型编程/参数多态性。相同的算法模板可以根据其数据类型(预定义/用户定义)进行变形,从而重用相同的代码/程序。没什么不同。这同样适用于Java。

让我们进入泛型的常见用例。

用例类型1:算法和数据结构是泛型的一流用例

算法与数据结构密不可分。算法中数据结构的简单改变就能改变它的复杂性。

数据结构中的数据有一个类型。用类型参数抽象出这个类型是通过泛型实现的。

任何算法的输入参数都有一个数据类型。从输入参数中抽象出类型是通过泛型实现的。

因此,泛型非常适合任何使用特定数据结构的特定算法。事实上,泛型主要是为Java的集合应用编程接口设计的。

如果您编写自己的数据结构,请尝试应用泛型。除了Java集合应用编程接口,您还可以在Guava,Apache Common Collections,FastUtils,JCtools,和Eclipse Collections

用例类型2:值类型框或单元素容器

泛型类型的数据结构是泛型框。数组列表、链表等类。,表示数据结构并充当同类的泛型盒。

有时,通用框确实显示为单个元素,而不是集合。它们只是作为特定数据类型的数据的持有者或包装者。例如:映射中的条目< K,V >、节点< K,V >、对< K,V >,以及代数数据类型,如可选< T >、选择< U,V >,等等。

ThreadLocalAtomicReference是应用并发访问所需算法的单元素容器的很好的例子。

这样的用例有时证明它们的使用是合理的,而其他的则不然。一个盒子可以装任何类型的物品——以前,我们可以把任何东西放进一个盒子里。但是现在我们开始分类:这个盒子是给玩具的,下一个盒子是给钢笔的,等等。

作为支架的杯子可以装茶、咖啡或任何饮料。杯子是实时对象类型(茶、咖啡等)的一个很好的例子。)持有者。一辆公共汽车可以载男载女。如果我们让它类型安全,只允许女性,我们可以称它为女士/女士巴士。不用说,它可能合适,也可能不合适。问题是业务用例,尤其是包装器或容器,也提供了应用泛型的机会。询问业务包装或持有者的使用是否倾向于数据结构类型的使用。如果是这样,泛型的使用会做得更好。

用例类型3:抽象类的通用实用方法

通用算法不需要总是绑定到特定的数据结构或算法。有时,它可以应用于大多数基于具体实现所满足的契约的抽象数据结构组。

在Java中,我们有“集合”实用类。

检查类中的以下方法,了解可以实现哪种方法。

收集工厂方法(空、单例):

  • emptyList、emptyMap、emptySet等。,

  • 单例、单例列表、单例映射等。,

  • 包装方法(同步、不可修改、已检查的集合):

  • 同步收集、同步设置、同步映射等。,

  • 未修改的集合、未修改的集合、未修改的列表等。,

  • checkedCollection、checkedList、checkedSet等。,

一些更通用的方法分为四大类

1.更改列表中元素的顺序:反转、旋转、无序播放、排序、交换

2.更改列表内容:复制、填充、替换全部

3.在集合中查找极值:最大值、最小值

4.在列表中查找特定值:二进制搜索、发布的索引、发布的最后索引

它们代表可重用的功能,因为它们适用于任何类型的列表(或者在某些情况下适用于集合)。我们可以找到许多泛型方法,一般适用于大多数集合类型。

用例类型4:类的并行层次中的通用方法

JpaRepository,CrudRepository在Spring中,框架是用泛型构建的。创建、更新、查找、查找、删除等。通用方法是否适用于所有实体。

对于每个实体,需要创建一个并行的DAO类,因此parallel hierarchy of classes出现在这些案例中。DAO模式不是它们出现的唯一情况。

如果我们应用策略模式来解决我们的业务问题,通过从一个对象中分离方法来提供该方法的许多可能的实例,通常会出现这种情况。

每当我们添加一个新的类,我们就添加一个并行的测试用例。如果我们需要工厂,我们添加一个并行工厂类。业务用例中出现了类的并行层次结构。考虑一种新的车辆,比如“公共汽车”,被添加到下面的车辆层次结构中。在这种情况下,我们可能需要添加“总线驱动程序”类。

下面是一个类和泛型的并行层次的例子。

import java.util.ArrayList;
import java.util.Collection;

public abstract class Habitat <A extends Animal> {

 //A generic collection that can hold Animal or any subclass of animal 
 Collection<A> collection = new ArrayList<A>();

 /*
  * add an Inhabitant to the collection. Should be overridden by subclass
  */
  public abstract  void addInhabitant( A animal);
}


public class Aquarium extends Habitat < Fish > {

    @Override
    public void addInhabitant(Fish fish) {
        collection.add(fish);
        System.out.println(Aquarium.class);
    }
}


/*
 * Super class for Animal hierarchy
 */
public abstract class Animal {

}


public class Fish extends Animal {

}


public class Test {
    public static void main(String[] args) {
        Animal animal = new Fish();
        Fish fish = new Fish();
        new Aquarium().addInhabitant(fish);
    }

}

用例类型5:创建类型安全的异构容器

集合<字符串>是同类容器的一个示例。除了字符串之外,您不能将任何东西放入该框中。同时,集合<对象>是一个异构容器的例子。你可以把任何东西放进这个盒子里。集合<对象>不是类型安全的。您需要检查类型并强制转换它,类似于原始类型“集合”(原始类型是没有应用泛型类型参数的泛型类型。它将对象作为默认类型参数)。Java没有为类型安全的异构容器提供一流的支持。

在集合<字符串>中,类型参数“字符串”应用于类型参数“T”,使其类型安全。考虑映射<字符串,字符串>。这里需要两个类型参数。泛型的正常使用,以集合应用编程接口为例,限制了每个容器的类型参数的固定数量。通过将类型参数放在映射的键上,而不是放在容器上,可以避开这个限制。您可以使用类对象作为构建类型安全异构容器或映射的键。

容器,如bean创建容器、异常处理程序容器或服务查找容器,都是异构容器的例子,在这些容器中,泛型可以用来使它们在动态转换时以类对象作为键实现类型安全。

我不会给出任何异构容器的代码示例,因为将会有一篇专门的文章发表在上面。同时,你也可以在谷歌上寻找不同种类的容器。

我希望下次你想到泛型、数据结构、盒子、集合、类方法、类的并行层次结构和异构容器的时候也能想到。如果你认为你知道一般可以应用泛型的其他用例,我会很高兴听到你的消息。