泛型,你真的懂了么?

目录

Java 中的泛型是什么?

不使用泛型的示例:

使用泛型:

为什么要使用泛型?

1.类型安全

2.消除强制类型转换

3.代码可重用性

4.通用类

泛型方法

1.泛型示例

2.泛型中的类型参数

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");// compile-time error  

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("Sorted Integer array :  ");
    bubbleSort(a);

    System.out.print("Sorted Character array :  ");
    bubbleSort(c);

    System.out.print("Sorted String array :  ");
    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;
  }

}

// Output:
Sorted Integer array :  [6, 23, 50, 55, 58, 76]
Sorted Character array :  [a, c, d, g, t, v, x]
Sorted String array :  [Ahmed, Ali, Aysu, Lale, Leman, Orkhan, Vali]

4.通用类
 

        通用类允许您定义可与用户指定的任何类型的类一起使用的类。

句法:

class Box<T> {
    private T item;

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

    public T getItem() {
        return item;
    }
}

在这个例子中,T 是一个类型参数,在创建 Box 实例时可以将其替换为任何类型。

用法:

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.泛型示例

  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);
      }
    }

  }

}

2.泛型中的类型参数


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

  • 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);       // Works with Integer
printDouble(5.5);     // Works with Double
// printDouble("Hello"); // 编译错误, String 不是 Number子类

通配符(?)

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

  1. 无界通配符( <?> ):接受任何类型。
List<?> list = new ArrayList<String>();
list = new ArrayList<Integer>(); // Works with any type

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); // You can add Integer or its subclass
}

差异List、List<T>、List<Object>、List<?> 和 List<Student>。

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

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

Java 泛型的常见面试问题

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

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

2.什么是类型擦除?

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

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
发出的红包

打赏作者

肉三

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值