面试中常见的Java 中的泛型<>

Java 中的泛型是什么?

泛型是指参数化类型。泛型允许您创建对指定为参数的类型进行操作的类、接口和方法。使用泛型,您可以编写适用于不同类型的代码,同时强制执行编译时类型安全。

不使用泛型的示例:

List  list  =  new  ArrayList (); 
list.add( "Hello" ); 
String  s  = (String) list.get( 0 ); // 必须转换为 String

使用泛型:

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

为什么要使用泛型?

  1. 类型安全:泛型允许您在编译时捕获类型错误,从而减少 ClassCastException 的可能性。
    // 不使用泛型,我们可以存储任何类型的对象
    List list = new ArrayList();    
    list.add(10);  
    list.add("10");  
    
    // 使用泛型时,需要指定需要存储的对象类型。  
    List<Integer> list = new ArrayList<Integer>();    
    list.add(10);  
    list.add("10");// 编译时  错误  

   2.消除强制类型转换:检索元素时无需强制类型转换,因为编译器已经知道类型。

 

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

// 类型转换, 使用泛型之后,我们不需要对对象进行类型转换  
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.print("已排序的整数数组:");
    bubbleSort(a);

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

    System.out.print("已排序的字符串数组:  ");
    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 setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

泛型方法
泛型方法允许您定义具有类型参数的方法,使它们能够与运行时指定的任何类型一起使用。

句法:

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

用法:

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

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

泛型示例

  1. 相互递归类型变量界限
    interface ConvertibleTo<T> {
        T convert();
    }
    
    class ReprChange<T extends ConvertibleTo<S>,
                     S extends ConvertibleTo<T>> { 
        T t; 
    
        void set(S s) { t = s.convert();    } 
        S    get()    { return t.convert(); } 
    }

    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 {
    
        try (S s = src.get(); T t = tgt.get()) {
          CharBuffer buf = CharBuffer.allocate(size);
          int i = s.read(buf);
          while (i >= 0) {
            buf.flip(); // prepare buffer for writing
            t.append(buf);
            buf.clear(); // prepare buffer for reading
            i = s.read(buf);
          }
        }
    
      }
    
    }

    泛型中的类型参数
    类型参数命名规范对于彻底学习泛型非常重要。常见的类型参数如下:

    • T — 类型
    • E — 元素
    • K — 钥匙
    • N — 数字
    • V — 价值

    有界类型参数

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

    句法:

    public <T extends Number> void printDouble(T number) {
        System.out.println(number.doubleValue());
    }

    解释:这里,<T extends Number> 表示 T 必须是 Number 的子类,因此只允许使用 Integer、Double、Float 等类型。

    用法:

    printDouble ( 5 );        // 适用于 Integer 
    printDouble ( 5.5 );      // 适用于 Double 
    // printDouble("Hello"); // 编译时错误,String 不是 Number 的子类

    通配符(?)

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

    无界通配符( <?> ):接受任何类型。

    List <?> list = new  ArrayList < String >(); 
    list = new  ArrayList < Integer >(); // 适用于任何类型

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

    public  void  printNumbers ( List<? extends  Number > list ) { 
        for ( Number n : list ) { 
            System . out . println ( n ); 
        } 
    }

    3.下限通配符( <? super Type> ):接受一个类型或任何超类型。

    public  void  addNumbers ( List<? super Integer> list ) { 
        list.add ( 10 ); // 您可以添加Integer 或其子类
    }

    每个版本的 List 参数都有独特的用例:

    • 原始 列表:由于缺乏类型安全性,因此很少使用。
    • List<T>:最灵活且类型安全,常用于泛型方法。
    • List<Object>:灵活性较差,仅限于 Object 类型。
    • List<?>:适用于接受任何列表类型的只读方法。
    • List<Student>:针对特定类型的强类型安全性。

    Java 泛型的常见面试问题

    1.什么是泛型以及它们为什么有用?

    泛型允许类和方法对任何指定类型进行操作,从而使代码类型安全且可重用。它们可以防止运行时类型错误并减少强制类型转换的需要。

    2.什么是类型擦除?

    类型擦除是在运行时删除泛型类型并由其边界或对象替换的过程。这允许向后兼容旧 Java 版本,但意味着您不能在运行时使用类型参数。https
    ://www.baeldung.com/java-type-erasure

    3. <?extends T><?super T>有什么区别

    <? extends T>允许任何 T 的子类型,并用于只读操作(不能安全地添加元素)。

    <? super T>允许任何类型,只要它是 T 的超类型,并用于只写操作(可以安全地添加元素,但读取可能会给出 Object)。

    4.可以将原始类型与泛型一起使用吗?

    不可以,Java 泛型仅适用于引用类型。您需要使用包装类(例如,Integer 用于 int,Double 用于 double)。

    5.有界类型参数如何提高类型安全性?

    有界类型允许您指定类型上的约束,确保泛型仅适用于满足特定条件的类型(如扩展数字),从而减少运行时错误。

    6. List<Object>List<?>有什么区别

    List<Object>只能接受对象类型,并且不能分配特定类型的列表(如 List<String>)。

    List<?>可以接受任何类型,因此更灵活,但在添加元素方面受到限制。

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

    由于类型擦除,Java 禁止创建泛型数组,这使其不安全。相反,您可以使用 Object[] 并根据需要进行强制转换,或者使用集合。

    8.泛型中的 PECS 是什么?

    PECS代表Producer Extends, Consumer Super。当您想要从集合中生产项目时,请使用 <? extends T>。当您想要消费项目时,请使用 <? super T>。

    9.什么是类型推断?

    类型推断是指编译器可以通过查看方法参数的类型来推断泛型类型。例如,如果我们将T传递给返回 T 的方法那么编译器就可以找出返回类型。让我们通过调用上一个问题中的泛型方法来尝试一下:

    Integer inferredInteger = returnType(1);
    String inferredString = returnType("String");"); 

    我们可以看到,不需要强制类型转换,也不需要传入任何泛型类型参数。参数类型只会推断返回类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值