📜  Java中的泛型

📅  最后修改于: 2022-05-13 01:55:30.569000             🧑  作者: Mango

Java中的泛型

泛型意味着参数化类型。这个想法是允许类型(整数、字符串等,以及用户定义的类型)作为方法、类和接口的参数。使用泛型,可以创建使用不同数据类型的类。对参数化类型进行操作的实体(例如类、接口或方法)是泛型实体。

为什么是泛型?

Object是所有其他类的超类,Object 引用可以引用任何对象。这些功能缺乏类型安全性。泛型添加了这种类型的安全功能。我们将在后面的示例中讨论这种类型的安全功能。

Java中的泛型类似于 C++ 中的模板。比如HashSet、ArrayList、HashMap等类,就很好的使用了泛型。泛型类型的两种方法之间存在一些根本差异。

Java泛型的类型

通用方法:通用Java方法接受一个参数并在执行任务后返回一些值。它与普通函数完全一样,但是,泛型方法具有由实际类型引用的类型参数。这允许以更通用的方式使用通用方法。编译器负责使程序员能够轻松编码的安全类型,因为他们不必执行冗长的、单独的类型转换。

泛型类:泛型类的实现与非泛型类完全相同。唯一的区别是它包含一个类型参数部分。可以有不止一种类型的参数,以逗号分隔。接受一个或多个参数的类称为参数化类或参数化类型。

泛型类

与 C++ 一样,我们使用 <> 来指定泛型类创建中的参数类型。要创建泛型类的对象,我们使用以下语法。

// To create an instance of generic class 
BaseType  obj = new BaseType ()
Java
// Java program to show working of user defined
// Generic classes
  
// We use < > to specify Parameter type
class Test {
    // An object of type T is declared
    T obj;
    Test(T obj) { this.obj = obj; } // constructor
    public T getObject() { return this.obj; }
}
  
// Driver class to test above
class Main {
    public static void main(String[] args)
    {
        // instance of Integer type
        Test iObj = new Test(15);
        System.out.println(iObj.getObject());
  
        // instance of String type
        Test sObj
            = new Test("GeeksForGeeks");
        System.out.println(sObj.getObject());
    }
}


Java
// Java program to show multiple
// type parameters in Java Generics
  
// We use < > to specify Parameter type
class Test
{
    T obj1;  // An object of type T
    U obj2;  // An object of type U
  
    // constructor
    Test(T obj1, U obj2)
    {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }
  
    // To print objects of T and U
    public void print()
    {
        System.out.println(obj1);
        System.out.println(obj2);
    }
}
  
// Driver class to test above
class Main
{
    public static void main (String[] args)
    {
        Test  obj =
            new Test("GfG", 15);
  
        obj.print();
    }
}


Java
// Java program to show working of user defined
// Generic functions
  
class Test {
    // A Generic method example
    static  void genericDisplay(T element)
    {
        System.out.println(element.getClass().getName()
                           + " = " + element);
    }
  
    // Driver method
    public static void main(String[] args)
    {
        // Calling generic method with Integer argument
        genericDisplay(11);
  
        // Calling generic method with String argument
        genericDisplay("GeeksForGeeks");
  
        // Calling generic method with double argument
        genericDisplay(1.0);
    }
}


Java
// Java program to show working
// of user-defined Generic classes
  
// We use < > to specify Parameter type
class Test {
    // An object of type T is declared
    T obj;
    Test(T obj) { this.obj = obj; } // constructor
    public T getObject() { return this.obj; }
}
  
// Driver class to test above
class Main {
    public static void main(String[] args)
    {
        // instance of Integer type
        Test iObj = new Test(15);
        System.out.println(iObj.getObject());
  
        // instance of String type
        Test sObj
            = new Test("GeeksForGeeks");
        System.out.println(sObj.getObject());
        iObj = sObj; // This results an error
    }
}


Java
// Java program to demonstrate that NOT using
// generics can cause run time exceptions
  
import java.util.*;
  
class Test
{
    public static void main(String[] args)
    {
        // Creatinga an ArrayList without any type specified
        ArrayList al = new ArrayList();
  
        al.add("Sachin");
        al.add("Rahul");
        al.add(10); // Compiler allows this
  
        String s1 = (String)al.get(0);
        String s2 = (String)al.get(1);
  
        // Causes Runtime Exception
        String s3 = (String)al.get(2);
    }
}


Java
// Using Java Generics converts run time exceptions into 
// compile time exception.
import java.util.*;
  
class Test
{
    public static void main(String[] args)
    {
        // Creating a an ArrayList with String specified
        ArrayList  al = new ArrayList ();
  
        al.add("Sachin");
        al.add("Rahul");
  
        // Now Compiler doesn't allow this
        al.add(10); 
  
        String s1 = (String)al.get(0);
        String s2 = (String)al.get(1);
        String s3 = (String)al.get(2);
    }
}


Java
// We don't need to typecast individual members of ArrayList
  
import java.util.*;
  
class Test {
    public static void main(String[] args)
    {
        // Creating a an ArrayList with String specified
        ArrayList al = new ArrayList();
  
        al.add("Sachin");
        al.add("Rahul");
  
        // Typecasting is not needed
        String s1 = al.get(0);
        String s2 = al.get(1);
    }
}


输出
15
GeeksForGeeks

我们还可以在 Generic 类中传递多个 Type 参数。

Java

// Java program to show multiple
// type parameters in Java Generics
  
// We use < > to specify Parameter type
class Test
{
    T obj1;  // An object of type T
    U obj2;  // An object of type U
  
    // constructor
    Test(T obj1, U obj2)
    {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }
  
    // To print objects of T and U
    public void print()
    {
        System.out.println(obj1);
        System.out.println(obj2);
    }
}
  
// Driver class to test above
class Main
{
    public static void main (String[] args)
    {
        Test  obj =
            new Test("GfG", 15);
  
        obj.print();
    }
}
输出
GfG
15

通用函数:

我们还可以编写泛型函数,这些函数可以根据传递给泛型方法的参数类型使用不同类型的参数调用。编译器处理每个方法。

Java

// Java program to show working of user defined
// Generic functions
  
class Test {
    // A Generic method example
    static  void genericDisplay(T element)
    {
        System.out.println(element.getClass().getName()
                           + " = " + element);
    }
  
    // Driver method
    public static void main(String[] args)
    {
        // Calling generic method with Integer argument
        genericDisplay(11);
  
        // Calling generic method with String argument
        genericDisplay("GeeksForGeeks");
  
        // Calling generic method with double argument
        genericDisplay(1.0);
    }
}
输出
java.lang.Integer = 11
java.lang.String = GeeksForGeeks
java.lang.Double = 1.0

泛型仅适用于引用类型:

当我们声明泛型类型的实例时,传递给类型参数的类型参数必须是引用类型。我们不能使用原始数据类型,如intchar。

Test obj = new Test(20); 

上面的行导致编译时错误,可以使用类型包装器封装原始类型来解决该错误。

但是原始类型数组可以传递给类型参数,因为数组是引用类型。

ArrayList a = new ArrayList<>();

泛型类型根据它们的类型参数而有所不同:

考虑以下Java代码。

Java

// Java program to show working
// of user-defined Generic classes
  
// We use < > to specify Parameter type
class Test {
    // An object of type T is declared
    T obj;
    Test(T obj) { this.obj = obj; } // constructor
    public T getObject() { return this.obj; }
}
  
// Driver class to test above
class Main {
    public static void main(String[] args)
    {
        // instance of Integer type
        Test iObj = new Test(15);
        System.out.println(iObj.getObject());
  
        // instance of String type
        Test sObj
            = new Test("GeeksForGeeks");
        System.out.println(sObj.getObject());
        iObj = sObj; // This results an error
    }
}

输出:

error:
 incompatible types:
 Test cannot be converted to Test 

尽管 iObj 和 sObj 是 Test 类型,但它们是对不同类型的引用,因为它们的类型参数不同。泛型通过这种方式增加了类型安全性并防止了错误。

Java泛型中的类型参数

类型参数命名约定对于彻底学习泛型很重要。常见的类型参数如下:

  • T - 类型
  • E – 元素
  • K - 钥匙
  • N - 号码
  • V – 价值

泛型的优点:

使用泛型的程序比非泛型代码有很多好处。

1.代码重用:我们可以编写一次方法/类/接口,然后将其用于我们想要的任何类型。

2. 类型安全:泛型使错误出现在编译时而不是在运行时(最好在编译时知道代码中的问题,而不是让代码在运行时失败)。假设您要创建一个存储学生姓名的 ArrayList,如果程序员错误地添加了整数对象而不是字符串,编译器会允许这样做。但是,当我们从 ArrayList 检索这些数据时,它会在运行时引起问题。

Java

// Java program to demonstrate that NOT using
// generics can cause run time exceptions
  
import java.util.*;
  
class Test
{
    public static void main(String[] args)
    {
        // Creatinga an ArrayList without any type specified
        ArrayList al = new ArrayList();
  
        al.add("Sachin");
        al.add("Rahul");
        al.add(10); // Compiler allows this
  
        String s1 = (String)al.get(0);
        String s2 = (String)al.get(1);
  
        // Causes Runtime Exception
        String s3 = (String)al.get(2);
    }
}

输出 :

Exception in thread "main" java.lang.ClassCastException: 
   java.lang.Integer cannot be cast to java.lang.String
    at Test.main(Test.java:19)

泛型如何解决这个问题?

在定义 ArrayList 时,我们可以指定这个列表只能取 String 对象。

Java

// Using Java Generics converts run time exceptions into 
// compile time exception.
import java.util.*;
  
class Test
{
    public static void main(String[] args)
    {
        // Creating a an ArrayList with String specified
        ArrayList  al = new ArrayList ();
  
        al.add("Sachin");
        al.add("Rahul");
  
        // Now Compiler doesn't allow this
        al.add(10); 
  
        String s1 = (String)al.get(0);
        String s2 = (String)al.get(1);
        String s3 = (String)al.get(2);
    }
}

输出:

15: error: no suitable method found for add(int)
        al.add(10); 
          ^

3. 不需要单独的类型转换:如果我们不使用泛型,那么,在上面的例子中,每次我们从 ArrayList 中检索数据时,我们都必须对其进行类型转换。每次检索操作的类型转换都是一件令人头疼的事情。如果我们已经知道我们的列表只包含字符串数据,我们就不需要每次都对它进行类型转换。

Java

// We don't need to typecast individual members of ArrayList
  
import java.util.*;
  
class Test {
    public static void main(String[] args)
    {
        // Creating a an ArrayList with String specified
        ArrayList al = new ArrayList();
  
        al.add("Sachin");
        al.add("Rahul");
  
        // Typecasting is not needed
        String s1 = al.get(0);
        String s2 = al.get(1);
    }
}

4.泛型促进代码可重用性:借助Java中的泛型,我们可以编写适用于不同类型数据的代码。例如,

public  void genericsMethod (T data) {...}

在这里,我们创建了一个泛型方法。同样的方法可用于对整数数据、字符串数据等执行操作。

5. 实现泛型算法:通过使用泛型,我们可以实现适用于不同类型对象的算法,同时它们也是类型安全的。