Java中的泛型总结

本文详细介绍了Java中的泛型,包括引入泛型的原因、泛型的基本使用、泛型类和泛型方法的定义、类型通配符的运用,以及泛型与反射的结合。通过实例展示了泛型如何提高代码复用性和安全性,并提供了泛型类`Pair<T>`的示例。此外,还解释了泛型擦除的概念,以及如何通过反射获取泛型信息。

前言

在Java SE 5 中Java中迎来了新的技术泛型,而添加泛型的主要原因是为了满足在1999年制定的最早Java开发规范需求之一(JSR14),专家组们花了大概5年左右的时间来定义规范和测试实现,才有了现在的泛型。在Java中增加泛型之前是用继承来实现的。

为什么要引入泛型?

思考这样一个问题,如果现在有一个挑选最大值方法,可以将传入的数值类型的两个参数中选择出最大的数值返回。我们知道Java中的数值类型包括如基本数据类型int、float以及包装引用类型Integer、Float等,如果我们定义这个方法的参数类型为int,那么想要实现float类型的参数的方法,我们可能就需要对该方法进行重载又或者定义该方法的两个参数都为Object,然后通过强制类型转换等进行数值比较。不管是哪种方法都显得不够“优雅”,而泛型就可以解决此问题。
泛型设计可以让编写的代码被很多不同的类型的对象所有重用,这是泛型解决的主要问题之一。

Java中的泛型记号

泛型需要使用 <> 进行标记,内部标记为字母或者为符号 ?,一般使用大写字母.常见的泛型及其含义如下:
E -Element(在集合中使用,因为集合中存放的是元素)
T -Type (Java类型)
K -key(键)
V -Value(值)
N -Number(数值类型)
? - 通配符,表示不确定的java类型

泛型擦除

在正式使用泛型前,还需要先了解一下泛型擦除。泛型的类型在编译阶段会被编译器进行擦除,java编译器生成的字节码是不包含泛型信息的,如果泛型参数无界,则在编译阶段用Object代替,如果泛型参数有界则替换为其边界或对象。也就是说Java编译器在处理泛型时生成的只包含普通的类、接口以及方法。

有界的参数类型:
上界:类型参数的名称,后面紧跟extends关键字,最后紧跟它的上界。
下界:类型参数的名称,后面紧跟super关键字,最后紧跟它的下界。

泛型方法

泛型方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

下面是定义泛型方法的规则:

所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 )。
每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)。

public class GenericMethodTest
{
   // 泛型方法 printArray                         
   public static < E > void printArray( E[] inputArray )
   {
      // 输出数组元素            
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }

    public static void main( String args[] )
    {
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };

        System.out.println( "整型数组元素为:" );
        printArray( intArray  ); // 传递一个整型数组

        System.out.println( "\n双精度型数组元素为:" );
        printArray( doubleArray ); // 传递一个双精度型数组

        System.out.println( "\n字符型数组元素为:" );
        printArray( charArray ); // 传递一个字符型数组
    }
}

泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。

和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

public class Box<T> {

  private T t;

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

  public T get() {
    return t;
  }

  public static void main(String[] args) {
    Box<Integer> integerBox = new Box<Integer>();
    Box<String> stringBox = new Box<String>();

    integerBox.add(new Integer(10));
    stringBox.add(new String("菜鸟教程"));

    System.out.printf("整型值为 :%d\n\n", integerBox.get());
    System.out.printf("字符串为 :%s\n", stringBox.get());
  }
}

类型通配符

1、类型通配符一般是使用 ? 代替具体的类型参数。例如 List<?> 在逻辑上是 List,List 等所有 List<具体类型实参> 的父类。

import java.util.*;

public class GenericTest {

    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();

        name.add("icon");
        age.add(18);
        number.add(314);

        getData(name);
        getData(age);
        getData(number);

   }

   public static void getData(List<?> data) {
      System.out.println("data :" + data.get(0));
   }
}

2、类型通配符上限通过形如List来定义,如此定义就是通配符泛型值接受Number及其下层子类类型。

import java.util.*;

public class GenericTest {

    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();

        name.add("icon");
        age.add(18);
        number.add(314);

        //getUperNumber(name);//1
        getUperNumber(age);//2
        getUperNumber(number);//3

   }

   public static void getData(List<?> data) {
      System.out.println("data :" + data.get(0));
   }

   public static void getUperNumber(List<? extends Number> data) {
          System.out.println("data :" + data.get(0));
       }
}

泛型与反射

Java中的反射不仅可以查看普通类的信息也可以查看有关泛型的信息,如下为反射包中一些可以解析泛型信息的类和接口。

  • CLass 类,描述具体类型
  • Typevariable 接口,描述类型变量(如 T extends Comparable<? super T>)
  • WildcardType 接口,描述通配符(如? superT)
  • ParameterizedType 接口,描述泛型类或接口类型(如 Comparable<? super T>)
  • GenericArrayType 接口: 描述泛型数组(如 T[ ])

可以写一个工具类解析某个类的泛型信息,程序如下:

package fanxing;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.Objects;
import java.util.Scanner;

public class GenericReflectionTest {

    public static void main(String[] args) {
        String name;
        if (args.length > 0){
            name = args[0];
        }else{
            try(Scanner in = new Scanner(System.in)){
                System.out.println("Enter Class name:");
                name = in.next();
            }
        }
        try{
            Class<?> cl = Class.forName(name);
            printClass(cl);
            for (Method e: cl.getDeclaredMethods()){
                printMethod(e);
            }
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }
    }

    public static void printClass(Class<?> cl){
        System.out.print(cl);
        printTypes(cl.getTypeParameters(), "<", ", ", ">", true);
        Type sc = cl.getGenericSuperclass();
        if (Objects.nonNull(sc)){
            System.out.print(" extends ");
            printType(sc, false);
        }
        printTypes(cl.getGenericInterfaces(), "implements ", ", ", "", false);
        System.out.println();
    }

    public static void printMethod(Method m){
        String name = m.getName();
        System.out.print(Modifier.toString(m.getModifiers()));
        System.out.print(" ");
        printTypes(m.getTypeParameters(), "<", ", ", "", true);

        printType(m.getGenericReturnType(), false);
        System.out.print(" ");
        System.out.print(name);
        System.out.print("(");
        printTypes(m.getGenericParameterTypes(), "", ", ", "", false);
        System.out.println(")");
    }

    public static void printTypes(Type [] types, String pre, String sep, String suf,
        boolean isDefinition){
        if ("extends".equals(pre) && Arrays.equals(types, new Type [] {Object.class} )){
            return;
        }
        if (types.length > 0){
            System.out.print(pre);
        }
        for (int i = 0; i < types.length; i++){
            if (i > 0){
                System.out.print(sep);
            }
            printType(types[i], isDefinition);
        }
        if(types.length > 0){
            System.out.print(suf);
        }
    }

    public static void printType(Type type, boolean isDefinition){
        if (type instanceof Class){
            Class<?> t = (Class<?>) type;
            System.out.print(t.getName());
        }else if(type instanceof TypeVariable){
            TypeVariable<?> t =(TypeVariable<?>) type;
            System.out.print(t.getName());
            if (isDefinition){
                printTypes(t.getBounds(), " extends ", "&", "", false);
            }
        }else if (type instanceof WildcardType){
            WildcardType t = (WildcardType) type;
            System.out.print("?");
            printTypes(t.getUpperBounds(), " extends ", "&", "", false);
            printTypes(t.getUpperBounds(), " super ", "&", "", false);
        }else if (type instanceof ParameterizedType){
            ParameterizedType t = (ParameterizedType) type;
            Type owner = t.getOwnerType();
            if (Objects.nonNull(owner)){
                printType(owner, false);
            }
            printType(t.getRawType(), false);
            printTypes(t.getActualTypeArguments(), "<", ", ", ">", false);
        }else if (type instanceof GenericArrayType){
            GenericArrayType t = (GenericArrayType) type;
            System.out.print("");
            printType(t.getGenericComponentType(), isDefinition);
            System.out.print("[]");
        }
    }

}

添加一个泛型类Pair进行测试,程序如下:

package fanxing;

public class Pair <T>{

    private T first;

    private T second;

    public Pair(){
        first = null;
        second = null;
    }

    public Pair(T first, T second){
        this.first = first;
        this.second = second;
    }

    public T getFirst(){
        return first;
    }

    public T getSecond(){
        return second;
    }

    public void setFirst(T first) {
        this.first = first;
    }

    public void setSecond(T second) {
        this.second = second;
    }
}

测试一下泛型类Pair,程序运行结果如下:
在这里插入图片描述

本文首发于香菜喵,打开微信随时随地读,扫描文章下方二维码 ↓ ↓ ↓

### Java 使用指南 #### 什么是Java 是一种允许在类、接口和方法中使用类型参数的技术。它提供了更强的类型安全性和更高的代码重用性,减少了运行时错误并简化了调试过程。 --- #### 的基本语法 通过引入类型参数 `<T>` 或其他占位符(如 `E` 表示元素,`K` 表示键,`V` 表示值),可以在定义类或方法时不指定具体的数据类型,在实际使用时再传入具体的类型。 ```java // 定义一个简单的类 public class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } } ``` 在这个例子中,`Box<T>` 是一个通用容器类,可以存储任意类型的对象[^1]。 --- #### 类型推断与菱形操作符 自 Java SE 7 开始,编译器可以通过菱形操作符 (`<>`) 自动推断的实际类型参数,从而减少冗余代码: ```java MyClass<Integer> myObject = new MyClass<>("example"); // 使用菱形操作符 ``` 这使得代码更加简洁明了[^1]。 --- #### 方法 即使在一个非类中,也可以定义方法。这些方法独立于类本身是否具有类型参数工作: ```java public class Util { // 静态方法 public static <T> void printArray(T[] array) { for (T element : array) { System.out.println(element); } } } String[] strings = {"a", "b", "c"}; Util.printArray(strings); Integer[] integers = {1, 2, 3}; Util.printArray(integers); ``` 此方法接受不同类型的数组作为输入,并打印其内容[^2]。 --- #### 通配符及其用途 通配符 `?` 可用于表示未知类型,通常分为三种形式:无界通配符、上限通配符和下限通配符。 ##### 1. **无界通配符** 当只需要访问集合的内容而不需要修改时,可使用无界通配符: ```java public static void printList(List<?> list) { for (Object elem : list) { System.out.println(elem); } } ``` 这里 `list` 的确切类型被隐藏,但仍能遍历其中的元素[^4]。 ##### 2. **上限通配符** 限定某个类型及其子类型可用作参数: ```java public static double sumOfList(List<? extends Number> list) { double s = 0.0; for (Number n : list) { s += n.doubleValue(); } return s; } ``` 这段代码计算了一个包含数值类型(如 `Integer`, `Double` 等)列表的总和。 ##### 3. **下限通配符** 允许向特定超类型及其子类型赋值的操作场景: ```java public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); } } ``` 这种方法支持将整数添加到能够容纳 `Integer` 或更广类型的列表中[^4]。 --- #### 方法签名中的类型捕获限制 需要注意的是,某些情况下无法直接捕获类型参数实例。例如下面的例子会引发编译错误,因为异常类型 `T` 不可能被捕获为局部变量的一部分: ```java public static <T extends Exception, J> void execute(List<J> jobs) throws T { try { for (J job : jobs) {} } catch (T e) { // 错误:不能捕获类型参数 throw e; } } ``` 这种设计是为了防止潜在的安全隐患以及保持 JVM 的一致性[^3]。 --- #### 总结 Java 提供了一种强大的机制来增强程序的功能性和安全性。无论是创建灵活的容器结构还是实现复杂的算法逻辑,合理运用都能带来显著的好处。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喵喵@香菜

感谢观众老爷送的一发火箭!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值