java-泛型

泛型是Java中一种推迟类型明确到运行时的技术,主要用于集合、类和接口,提供编译期的类型安全,避免类型转换。类型擦除是泛型的关键特性,编译后泛型参数会被转换为普通类型。桥方法保证了泛型类的继承正确性,而反射则允许在运行时获取泛型信息。

 什么是泛型:

 泛型:是一种把明确类型的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,而这种参数类型可以用在类、方法和接口中,分别被称为`泛型类`、`泛型方法`、`泛型接口`。
  注意:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。

使用泛型的好处:

- 避免了类型强转的麻烦。
- 它提供了编译期的**类型安全**,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现ClassCastException。

 泛型的使用:

泛型虽然通常会被大量的使用在集合当中,但是我们也可以完整的学习泛型只是。泛型有三种使用方式,分别为:泛型类、泛型方法、泛型接口。将数据类型作为参数进行传递。

 泛型类:

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种集合框架容器类,如:List、Set、Map。

public class Box<T> {
    private T contents;

    public void setContents(T contents) {
        this.contents = contents;
    }

    public T getContents() {
        return contents;
    }
}

在上面的代码中,Box<T> 中的 T 就是一个类型形参,表示一个占位符类型。在实例化 Box 对象时,需要指定一个具体的类型参数来替换 T;

泛型接口:

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中

public interface List<T> {
    void add(T element);
    T get(int index);
}

在这里,List<T> 中的 T 是一个类型形参,表示列表中的元素类型。实现该接口时需要指定一个具体的类型参数来替换 T;

泛型方法:

泛型机制可以用于方法的参数类型和返回值类型

public static <T> T getFirstElement(List<T> list) {
    if (list == null || list.isEmpty()) {
        return null;
    }
    return list.get(0);
}

泛型的实现原理主要涉及三个概念:类型擦除、桥方法和反射。

 1.类型擦除

Java 在编译期间会将泛型转换为普通类型,这个过程被称为类型擦除。在类型擦除时,对于泛型类型参数所对应的具体类型,只有在运行时才能确定,而在编译期间是无法确定的。因此,Java 在编译期间会将所有泛型类型转换为其原始类型,也就是没有泛型参数的类型。

例如,List<String> 在编译期间会被转换为 List。

2.桥方法

泛型类中的方法在类型擦除后,如果该方法定义的返回类型是泛型类型参数,那么编译器会生成一个桥方法来确保类的继承关系正确。桥方法的作用是将泛型方法转换为普通方法,以便在运行时调用。例如,假设有以下泛型类:

class MyClass<T> {
    public T getValue() {
        return null;
    }
}

在类型擦除后,该类会变为:

class MyClass {
    public Object getValue() {
        return null;
    }
}

但是,由于 getValue() 方法在 MyClass 中被声明为返回类型为 T 的方法,因此,编译器会自动生成一个桥方法,用于确保类的继承关系正确。生成的桥方法如下:

class MyClass {
    public Object getValue() {
        return this.getBridgeValue();
    }
    public T getBridgeValue() {
        return null;
    }
}

3.反射

Java 的反射机制可以让程序在运行时获取类的信息,包括泛型类型参数的信息。例如,可以通过 Class 类中的 getGenericSuperclass() 和 getGenericInterfaces() 方法获取类和接口中声明的泛型类型参数的信息。反射机制还提供了 ParameterizedType 接口、TypeVariable 接口等用于表示泛型类型参数的类型对象。

注:

方法getGenericSuperclass():
从一个Class对象中,获取该对象父类接收到的参数化类型(泛型);

方法getGenericInterfaces():

获取类的接口实现信息,返回实现接口信息的Type数组,包含泛型信息;

### Java练习题及解析 #### 1. 类的基本使用 考虑以下类的定义: ```java class Holder<T> { T value; public Holder(T value) { this.value = value; } public T getValue() { return value; } } ``` 该类定义了一个类 `Holder`,其中 `T` 是一个类参数。可以使用该类来存储任何类的对象,并通过 `getValue()` 方法获取该对象。例如,以下代码演示了如何创建一个 `Holder` 实例来存储一个字符串: ```java Holder<String> stringHolder = new Holder<>("Hello"); System.out.println(stringHolder.getValue()); // 输出 "Hello" ``` 如果尝试将错误类的对象传递给 `Holder`,编译器会报错。例如,以下代码会导致编译错误: ```java Holder<Integer> intHolder = new Holder<>("Hello"); // 编译错误 ``` 这是因为 `Holder<Integer>` 要求传入的值必须是 `Integer` 类,而 `"Hello"` 是 `String` 类。 #### 2. 原始类的兼容性 Java 允许将类的实例赋值给原始类的变量,但这种做法不推荐,因为它会失去类安全性。例如: ```java Holder rawHolder = new Holder<String>("Hello"); String value = rawHolder.getValue(); // 不推荐,但可以编译通过 ``` 尽管这段代码可以编译通过,但它失去了带来的类检查。如果尝试从 `rawHolder` 获取一个非 `String` 类的对象,可能会在运行时抛出 `ClassCastException`。 #### 3. 类推断的增强 在 Java 23 中,类推断得到了增强,允许在某些情况下省略显式的类参数。例如: ```java List<String> list = Collections.emptyList(); // Java 23 支持空目标类推断 ``` 在旧版本的 Java 中,必须显式指定类参数: ```java List<String> list = Collections.<String>emptyList(); ``` 这种改进使得代码更加简洁,并减少了冗余的类声明。 #### 4. 可变参数与的结合 Java 允许将可变参数与结合使用。例如,可以定义一个方法来接受可变数量的参数: ```java public static <T> void printValues(T... values) { for (T value : values) { System.out.println(value); } } ``` 调用该方法时,可以传入任意数量的参数: ```java printValues("Apple", "Banana", "Cherry"); // 输出三个字符串 printValues(1, 2, 3); // 输出三个整数 ``` 这种方法在处理不确定数量的输入时非常有用,并且保持了类安全性。 #### 5. 方法的使用 不仅可以用于类,还可以用于方法。例如,定义一个方法来交换两个元素的位置: ```java public static <T> void swap(T[] array, int i, int j) { T temp = array[i]; array[i] = array[j]; array[j] = temp; } ``` 调用该方法时,可以传入任意类的数组和索引: ```java String[] names = {"Alice", "Bob"}; swap(names, 0, 1); System.out.println(Arrays.toString(names)); // 输出 "[Bob, Alice]" ``` 这种方法可以应用于任何类的数组,并且保证了类安全性。 #### 6. 接口的实现 Java 允许定义接口,并由具体的类实现这些接口。例如,定义一个接口 `Container<T>`: ```java interface Container<T> { void add(T item); T get(int index); } ``` 然后,可以实现该接口的具体类: ```java class StringContainer implements Container<String> { private List<String> items = new ArrayList<>(); @Override public void add(String item) { items.add(item); } @Override public String get(int index) { return items.get(index); } } ``` 这样,`StringContainer` 类只能用于处理 `String` 类的对象,确保了类安全性。 #### 7. 的边界限制 Java 支持通过边界限制来约束类参数的范围。例如,定义一个方法,要求类参数必须是 `Number` 的子类: ```java public static <T extends Number> double sum(T[] numbers) { double total = 0; for (T number : numbers) { total += number.doubleValue(); } return total; } ``` 调用该方法时,只能传入 `Number` 的子类数组: ```java Integer[] integers = {1, 2, 3}; Double[] doubles = {1.5, 2.5, 3.5}; System.out.println(sum(integers)); // 输出 6.0 System.out.println(sum(doubles)); // 输出 7.5 ``` 这种方法确保了类的安全性和操作的正确性。 #### 8. 多类参数的使用 Java 支持多个类参数的定义。例如,定义一个类 `Pair<K, V>`,用于存储键值对: ```java class Pair<K, V> { K key; V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } } ``` 调用该类时,可以传入任意类的键和值: ```java Pair<String, Integer> pair = new Pair<>("Age", 25); System.out.println(pair.getKey()); // 输出 "Age" System.out.println(pair.getValue()); // 输出 25 ``` 这种方法可以灵活地处理不同类的数据组合。 #### 9. 的通配符使用 Java 支持使用通配符 `?` 来表示未知类。例如,定义一个方法来打印任意类的列表: ```java public static void printList(List<?> list) { for (Object obj : list) { System.out.println(obj); } } ``` 调用该方法时,可以传入任意类的 `List`: ```java List<String> stringList = Arrays.asList("A", "B", "C"); List<Integer> integerList = Arrays.asList(1, 2, 3); printList(stringList); // 输出三个字符串 printList(integerList); // 输出三个整数 ``` 这种方法在处理未知类的数据时非常有用,并且保持了类安全性。 #### 10. 的递归类限制 Java 支持递归类限制,允许类参数继承自身。例如,定义一个接口 `SelfBound<T>`: ```java interface SelfBound<T extends SelfBound<T>> { T self(); } ``` 这种设计模式常用于链式调用和构建器模式中,确保返回的类与当前类一致。 --- ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值