为了账号安全,请及时绑定邮箱和手机立即绑定

Java泛型详解及面试问题

Java中的泛型是什么,简单说来?

泛型是指参数化类型。泛型让你可以创建类、接口和方法,这些类型和方法可以在参数中指定具体类型,并且使用泛型,你可以编写处理不同类型并在编译时保证类型安全的代码。

不含泛型的示例:

     List list = new ArrayList();  
    list.add("Hello");,  
    String s = (String) list.get(0); // 需要转换为字符串

使用泛型:

    List<String> list = new ArrayList<>();  
    list.add("Hello");  
    String s = list.get(0); // 无需类型转换,类型安全有保障

为什么使用泛型(Generic)编程?

  1. 类型安全特性: 泛型允许你在编译时捕获类型错误,从而减少 ClassCastException 的出现。
    // 没有泛型,可以存储任何类型的对象。
    List list = new ArrayList();      
    list.add(10);    
    list.add("10");    

    // 有了泛型,需要指定要存储的对象类型。
    List<Integer> 列表 = new ArrayList<Integer>();      
    list.add(10);    
    list.add("10"), // 编译时错误

2. 省去类型声明: 在获取元素时无需进行类型声明,因为编译器已经知道元素的类型。

    // 在泛型出现之前,我们需要进行强制类型转换。
    List list = new ArrayList();      
    list.add("hello");      
    String s = (String) list.get(0); // 强制类型转换

    // 使用泛型之后,我们不需要进行对象的类型转换。
    List<String> list = new ArrayList<String>();      
    list.add("hello");      
    String s = list.get(0);   

3. 代码复用: 你可以编写一个类或方法,使其能处理各种数据类型,从而让代码更灵活且可复用。

    import java.util.Arrays;  

    public class GenericSorting {  

      public static void main(String[] args) {  
        Integer[] a = {76, 55, 58, 23, 6, 50};  
        Character[] c = {'v', 'g', 'a', 'c', 'x', 'd', 't'};  
        String[] s = {"Vali", "Ali", "Ahmed", "Aysu", "Leman", "Orkhan", "Lale"};  

        System.out.println("排序后的整数数组 :  ");  
        bubbleSort(a);  

        System.out.println("排序后的字符数组 :  ");  
        bubbleSort(c);  

        System.out.println("排序后的字符串数组 :  ");  
        bubbleSort(s);  

      }  

      // 使用冒泡排序算法对数组进行排序
      public static <T extends Comparable<T>> void bubbleSort(T[] array) {  
        for (int i = 0; i < array.length - 1; i++) {  
          for (int j = 0; j < array.length - i - 1; j++) {  
            if (array[j].compareTo(array[j + 1]) > 0) {  
              swap(j, j + 1, array);  
            }  
          }  
        }  

        System.out.println(Arrays.toString(array));  
      }  

      // 交换数组中的两个元素
      public static <T> void swap(int i, int j, T[] a) {  
        T t = a[i];  
        a[i] = a[j];  
        a[j] = t;  
      }  

    }  

    // 排序结果:
    排序后的整数数组 :  [6, 23, 50, 55, 58, 76]  
    排序后的字符数组 :  [a, c, d, g, t, v, x]  
    排序后的字符串数组 :  [Ahmed, Ali, Aysu, Lale, Leman, Orkhan, Vali]

泛型类
泛型类允许定义可以与任何数据类型配合的类。

语法:

// 这是一个泛型类,用于存储任意类型的物品.
class Box<T> {  
    private T item;  

    public void 设置项目(T item) {  
        this.item = item;  
    }  

    public T 获取项目() {  
        return item;  
    }  
}

在此示例中,T 是一个类型参数,可以在实例化 Box 时将其替换为任何类型。

用法:

// 创建一个字符串盒子,并设置其内容为 "Hello",然后输出内容
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
System.out.println(stringBox.getItem());

泛型方法
泛型方法允许你定义带有类型参数的方法,这样它们就可以在运行时处理任何类型的输入了。

语法结构:

    public class GenericsExample {  
        public static <T> void printArray(T[] array) {  
            for (T element : array) {  
                System.out.println(element);  
            }  
        }  
    }

用法如下:

    Integer[] intArray = {1, 2, 3};  
    String[] strArray = {"Hello", "World"};  

    GenericsExample.printArray(intArray);  
    GenericsExample.printArray(strArray);

泛型示例(示例仅供参考)

  1. 互为递归的类型参数约束
    interface ConvertibleTo<T> {  
        T convert();  
    }  

    // ReprChange 类用于表示类型的转换关系
    class ReprChange<T extends ConvertibleTo<S>,  
                     S extends ConvertibleTo<T>> {   
        T t;   

        // 将 s 转换后的结果赋值给 t
        void set(S s) { t = s.convert();    }   

        // 返回 t 转换后的结果
        S get() { return t.convert(); }   

        // T 和 S 都需要是 ConvertibleTo 类型
    }

2. 内部嵌套的泛型类

class Seq<T> {  
  T head;  
  Seq<T> tail;  

  Seq() {  
    this(null, null);  
  }  

  Seq(T head, Seq<T> tail) {  
    this.head = head;  
    this.tail = tail;  
  }  

  boolean isEmpty() {  
    return tail == null;  
  }  

  class Zipper<S> {  
    Seq<Pair<T, S>> zip(Seq<S> that) {  
      if (isEmpty() || that.isEmpty()) {  
        return new Seq<>();  
      }  
      Seq<T>.Zipper<S> tailZipper = tail.new Zipper<>();  
      return new Seq<>(new Pair<>(head, that.head), tailZipper.zip(that.tail));  
    }  
  }  
}  

class Pair<T, S> {  
  T fst;  
  S snd;  

  Pair(T f, S s) {  
    fst = f;  
    snd = s;  
  }  
}  

class Test {  
  public static void main(String[] args) {  
    Seq<String> strs = new Seq<>("a", new Seq<>("b", new Seq<>()));  
    Seq<Number> nums = new Seq<>(1, new Seq<>(1.5, new Seq<>()));  

    Seq<String>.Zipper<Number> zipper = strs.new Zipper<Number>();  

    Seq<Pair<String, Number>> combined = zipper.zip(nums);  
  }  
}

3. 多重边界

    interface IoeThrowingSupplier<S> {  
      S get();  
    }  

    public class Generics {  

      public static <S extends Readable & Closeable,  
                     T extends Appendable & Closeable>  
      void copy(IoeThrowingSupplier<S> src,   
                IoeThrowingSupplier<T> tgt,   
                int size) throws IOException { // 可能抛出IO异常  

        try (S s = src.get(); T t = tgt.get()) {  
          CharBuffer buf = CharBuffer.allocate(size);  
          int i = s.read(buf); // 读取字符到缓冲区,结果保存在i中  
          while (i >= 0) { // 在还有字符可读时循环  
            buf.flip(); // 准备写  
            t.append(buf);  
            buf.clear(); // 准备读  
            i = s.read(buf); // 读取字符到缓冲区,结果保存在i中  
          }  
        }  

      }  

    }

泛型中的类型参数命名约定
学习泛型时,了解类型参数命名约定非常重要。常见的类型参数如下:

  • T — 类型 (T)
  • E — 项目
  • K — 键
  • N — 数值
  • V — 值

限定类型参数

泛型也可以限制可用的类型,也被称为“有界类型”。

语法:

定义一个泛型方法 printDouble,它接受一个 Number 类型的参数,并打印该参数的 double 值。

解释: 这里的 <T extends Number> 意味着 T 必须是 Number 的子类,因此只允许像 Integer、Double、Float 等这样的类型。

如何使用:

     printDouble(5);       // 可以处理整数(Integer)  
    printDouble(5.5);     // 可以处理浮点数  
    // printDouble("Hello"); // 编译时错误,字符串不是数字类型

这里的通配符是问号 (?).

通配符用问号 ? 来表示,它们允许在泛型中使用未知类型。主要有三种通配符类型:

无限制通配符(<?>):可以匹配任何类型。

    List<?> list = new ArrayList<String>();  
    list = new ArrayList<Integer>(); // 可以使用任何类型

2. 上限通配符(<? extends Type>): 接受特定类型或其任何子类型。

    public void 打印数字(List<? extends 数字> list) {  
        for (数字 n : list) {  
            // 输出数字n
            System.out.println(n);  
        }  
    }

3. 下界通配符(<? super Type>): 可以接受指定类型及其任何超类型。

    public void addNumbers(List<? super Integer> list) {  
        list.add(10); // 可以添加整数或其子类对象,例如 Long
    }

以下几种情况的区别:泛型列表,List<T>,List<Object>,List<?>,和 List<Student>。

每个版本的列表参数项都有其独特的应用场景:

• 未泛型化的 List:因为没有类型安全性的保障,很少使用。
• List<T>:既灵活又类型安全,通常用于泛型函数中。
• List<Object>:灵活性较差,只能容纳 Object 类型。
• List<?>:适用于接受任意类型列表的只读操作。
• List<Student>:针对特定类型提供强类型安全性。

Java泛型面试常见问题

  1. *什么是泛型,为什么它们很有用?\

泛型让类和方法能够处理任何指定的类型,这样代码既安全又可重用,同时避免了运行时类型错误,减少了类型转换的需求。

  1. 什么是类型擦除?

类型擦除机制是指在运行时移除泛型类型,用它们的边界或 Object 替换。这使得它与旧版本的 Java 兼容,但这也意味着你无法在运行时使用类型参数。
参考链接_

  1. <? extends T> 和 <? super T> 有什么不同?

<? extends T> 允许任何 T 的子类型,并用于这类操作主要用于只读操作(你无法安全地向其中添加元素)。

<? super T> 允许任何 T 的超类型,并用于只写操作(你可以安全地添加元素,但读取时可能得到 Object)。

4. 你能用基本类型和泛型一起使用吗?

不,Java 泛型仅适用于引用类型,而不是基本类型。你需要使用对应的包装类(比如用 Integer 替换 int,用 Double 替换 double)。

5. 限定类型的参数怎样提高代码的类型安全?

有界类型允许你指定类型约束,确保泛型仅与符合特定条件(例如,Number的子类)的类型一起使用,从而减少运行时的错误。

6. List<Object>和List<?>有什么不同?

List <对象类型>只能包含对象类型,不能赋值为特定类型的列表,例如 List <String>。

_列表 <?>_可以接受任何类型,所以更灵活,但在添加元素方面却受到限制。

7. 为什么我们在Java中不能创建泛型数组,例如,T[] array = new T[10]; ?

Java 不允许创建泛型数组,因为类型擦除这使得不安全。因此,你可以使用 Object[] 并在需要时进行类型转换,或者使用集合类。

在泛型中,PECS 是指什么?

PECS 代表 生产者使用上限通配符,消费者使用下限通配符。当你想要 生产 集合中的项目时,使用 <? extends T>。当你想要 消费 项目时,使用 <? super T>

9.: 什么是类型推断?

类型推断是指编译器可以根据方法参数的类型推断出泛型类型。例如,如果我们向一个返回 T 的方法传递 T 参数,那么编译器就能推断出返回类型。让我们来试试通过调用上一个问题中的泛型方法。

    int inferredInteger = returnType(1);  
    String inferredString = returnType("String");"); background-repeat: no-repeat; background-position: center center; background-color: rgb(99, 177, 117);">复制代码

正如我们所看到的,无需进行类型转换,也不需要传递任何泛型类型参数。参数类型只会推导返回类型。

资源如下。

  1. https://www.amazon.com/Java-Generics-Collections-Development-Process/dp/0596527756 (《Java泛型与集合》这本书的Amazon页面)
  2. https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.6 (Java SE 8语言规范中关于类型的描述)
  3. https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) (维基百科上关于协变和逆变的页面)
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消