java 泛型

泛型的本质是: 变量 数据类型 参数化

泛型标识 

常用的标识是:T、E、K、V 其实这个标识只要是字母就行,其只是一个声明数据类型的形参

要明晰,这个形参可以代表的数据的类型可以是String、Integer等数据类型,也可以是自己定义的类,其不支持基本数据类型。

泛型类

public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }
}

使用<>表示使用了泛型,而其中的K,V则标示使用了两种数据类型。泛型类中也可以有具体的数据类型,在我的理解看来,泛型类就是部分数据类型不确定的类模板。

在使用泛型时(创建对象时),需要指定数据类型

public class TestPair {
    public static void main(String[] args) {
        // 正确:为 K 和 V 同时指定类型
        Pair<String, Integer> pair = new Pair<>("Age", 30);

        System.out.println("Key: " + pair.getKey());    // 输出: Age
        System.out.println("Value: " + pair.getValue());  // 输出: 30
    }
}

就指定了Pair的K是String  V 是Integer

如果不指定,则会按照Object类型来处理

        Pairs b = new Pairs("tom",12);
        Object key = b.getKey();
        Object value = b.getValue();
        

同时 同一泛型类根据不同数据类型创建的对象,其本质上仍然是同一类型

        Pairs <String,Integer> b = new Pairs("tom",12);
        Pairs<Integer,String> c = new Pairs(12,"hei");
        System.out.println(b.getClass() == c.getClass());

该结果执行的是true.虽然对象b和对象c 其数据类型是不同的,但是其都是class Pairs

泛型类派生子类
  • 保持父类的泛型参数:子类仍然是一个泛型类,并且继承父类的泛型参数。
  • 指定具体的类型:子类不再是泛型类,父类的泛型参数在子类中被具体化为某个具体类型。
  • 子类自定义新的泛型参数:子类可以引入新的泛型参数,或者与父类的泛型参数一起使用。

子类继承父类泛型

// 泛型父类
public class GenericParent<T> {
    private T data;

    public GenericParent(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}

// 泛型子类,保留泛型参数 T
public class GenericChild<T> extends GenericParent<T> {
    public GenericChild(T data) {
        super(data);//调用父类构造函数
    }

    // 子类方法可以继续使用泛型参数 T
    public void printData() {
        System.out.println("Data: " + getData());
    }
}

子类指定具体类型

 当子类数据类型确认时,需要只当父类的泛型数据类型

// 泛型父类
public class GenericParent<T> {
    private T data;

    public GenericParent(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}

// 子类指定具体的类型为 String,不再是泛型类
public class StringChild extends GenericParent<String> {
    public StringChild(String data) {
        super(data)//调用父类构造函数,因为传入的是String类型data,所以父类的泛型也需要是String
    }

    public void printData() {
        System.out.println("Data: " + getData());
    }
}

子类加入新的泛型参数

// 泛型父类
public class GenericParent<T> {
    private T data;

    public GenericParent(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}

// 子类引入新的泛型参数 U
public class GenericChild<T, U> extends GenericParent<T> {
    private U extraData;

    public GenericChild(T data, U extraData) {
        super(data);
        this.extraData = extraData;
    }

    public U getExtraData() {
        return extraData;
    }
}
 泛型类上界

可以为泛型类的类型参数指定一个上界,即类型参数必须是某个类或接口的子类型。

public class GenericClassExample {
    // 这个类的泛型参数 T 必须是 Number 或其子类
    static class GenericClass<T extends Number> {
        private T value;

        public GenericClass(T value) {
            this.value = value;
        }

        public T getValue() {
            return value;
        }
    }

    public static void main(String[] args) {
        // 可以使用 Integer 类型的对象,因为 Integer 是 Number 的子类
        GenericClass<Integer> intObject = new GenericClass<>(123);
        System.out.println("Value: " + intObject.getValue());

        // 可以使用 Double 类型的对象,因为 Double 也是 Number 的子类
        GenericClass<Double> doubleObject = new GenericClass<>(456.78);
        System.out.println("Value: " + doubleObject.getValue());

        // 不能使用 String 类型的对象,因为 String 不是 Number 的子类
        // GenericClass<String> stringObject = new GenericClass<>("ABC");  // 编译错误
    }
}

但是泛型类没有下界, 在定义泛型类时不能直接使用 super 来限制泛型类型参数,但可以在泛型类的方法参数或方法签名中使用 super 来实现下界通配符。

class GenericClass<T> {
    public void addValue(List<? super T> list, T value) {
        list.add(value);
    }
}

泛型接口

泛型接口的定义方式与泛型类类似,通常在接口名后使用尖括号 <T> 来表示泛型参数。

// 定义一个泛型接口,泛型参数 T
public interface GenericInterface<T> {
    T getData();
    void setData(T data);
}
泛型接口的实现
  • 指定具体类型:实现类直接指定接口中的泛型参数为具体的类型。
  • 保持泛型:实现类也可以保留泛型,并在实现时继续使用泛型参数。
指定具体类型
// 实现时将泛型类型 T 具体化为 String
public class StringImplementation implements GenericInterface<String> {
    private String data;

    @Override
    public String getData() {
        return data;
    }

    @Override
    public void setData(String data) {
        this.data = data;
    }
}
保持泛型
// 保持泛型接口中的泛型
public class GenericImplementation<T> implements GenericInterface<T> {
    private T data;

    @Override
    public T getData() {
        return data;
    }

    @Override
    public void setData(T data) {
        this.data = data;
    }
}

实现类的数据类型是可以比接口数据类型多的

// 定义一个只有一个泛型参数的接口
public interface GenericInterface<T> {
    T getData();
    void setData(T data);
}

// 实现类,增加了第二个泛型参数 U
public class GenericImplementation<T, U> implements GenericInterface<T> {
    private T data;
    private U additionalData;  // 这是额外的泛型参数,仅供实现类使用

    // 实现接口的 getData 方法
    @Override
    public T getData() {
        return data;
    }

    // 实现接口的 setData 方法
    @Override
    public void setData(T data) {
        this.data = data;
    }

    // 实现类可以自由使用额外的泛型参数 U
    public U getAdditionalData() {
        return additionalData;
    }

    public void setAdditionalData(U additionalData) {
        this.additionalData = additionalData;
    }
}

只不过额外的数据类型及相关的方法是和接口没有关系的,只和当前类有关。

不指定泛型类型

在实现泛型接口时,如果不指定接口的泛型类型,接口中的泛型参数会默认被视为 Object 类型。这意味着实现类将会处理 Object 类型的参数,所有的泛型类型参数在实现类中都会被擦除为 Object

// 实现类不指定接口的泛型类型
public class GenericImplementation implements GenericInterface {
    private Object data;

    @Override
    public Object getData() {
        return data;
    }

    @Override
    public void setData(Object data) {
        this.data = data;
    }
}
泛型接口的通配符

使用泛型接口时,还可以结合通配符(如 ? extends T? super T)来增强其灵活性。通配符的应用可以让你更方便地处理类型继承的关系。

// 泛型接口
public interface Container<T> {
    void add(T item);
    T get();
}

// 使用 ? extends Number
public class NumberContainer implements Container<? extends Number> {
    private Number number;

    @Override
    public void add(Number item) {
        this.number = item;
    }

    @Override
    public Number get() {
        return number;
    }
}

在这个例子中,NumberContainer 使用了通配符 ? extends Number,表示它可以接收 Number 类型或 Number 的子类。这种虽然提高了灵活性,但也牺牲了部分东西,后面会提到。

无论是泛型类派生子类还是泛型接口的实现,都需要保证类型的一致性,符合继承和实现规则。

泛型方法

public <T> T methodName(T param) {
    // 方法体
    return param;
}

泛型方法在调用方法时指明泛型的具体类型

泛型方法与泛型类的成员方法区别

泛型方法的标识还式<> ,其和泛型类中的成员方法是有区别的,成员方法是没有<>的,而且成员方法的泛型必须和类的泛型相同吗,同时,泛型类的成员方法是不可以定义成static(静态)的,但泛型方法是可以的。如例子,泛型方法可以使用泛型类的泛型,也可以不使用泛型类的泛型,其和泛型类是互相不影响的。

public class GenericClass<T> {

    // 使用类的泛型 T 和方法的泛型 E
    public <E> void compare(T element1, E element2) {
        System.out.println("Element 1: " + element1);
        System.out.println("Element 2: " + element2);
    }

    public static void main(String[] args) {
        GenericClass<String> stringInstance = new GenericClass<>();
        stringInstance.compare("Hello", 123);   // T 是 String,E 是 Integer
        stringInstance.compare("Hello", 45.67); // T 是 String,E 是 Double
    }
}

 下面的例子更能体现其互不影响的特点,即使泛型方法和泛型类都是T,但其依旧是不同的,泛型类的T是String类型,而泛型方法的T是Double类型的 ,这两个泛型参数是有不同的作用域的。

public class GenericClass<T> {

    // 定义方法级别的泛型 T,和类级别的泛型 T 无关
    public <T> void compare(T element1) {
        System.out.println("Element 1: " + element1);
    }

    public static void main(String[] args) {
        GenericClass<String> stringInstance = new GenericClass<>();

        // 调用方法级别的泛型,传入 Double 类型的参数
        stringInstance.compare(45.67);
    }
}
泛型方法与可变参数

结合可变参数,又能进一步提高泛型方法的灵活性,这样可以让方法接收不确定数量的泛型参数。可变参数允许你传递多个同类型的参数,而不需要显式地创建数组。

public class GenericVarargs {

    // 泛型方法,使用可变参数
    public static <T> void printElements(T... elements) {
        for (T element : elements) {
            System.out.println(element);
        }
    }

    public static void main(String[] args) {
        // 传递多个 Integer 类型参数
        printElements(1, 2, 3, 4, 5);

        // 传递多个 String 类型参数
        printElements("A", "B", "C");
        
        // 混合类型的调用
        printElements(1.1, "Hello", 3, true);
    }
}

结果

1
2
3
4
5
A
B
C
1.1
Hello
3
true

 使用 @SafeVarargs 注解可以避免编译时的警告,但要确保泛型数组不会被非法操作。

import java.util.List;

public class GenericVarargs {

    // 泛型方法,接受多个 List 对象并打印
    @SafeVarargs
    public static <T> void printLists(List<T>... lists) {
        for (List<T> list : lists) {
            for (T element : list) {
                System.out.print(element + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        List<Integer> intList = List.of(1, 2, 3);
        List<String> strList = List.of("A", "B", "C");

        printLists(intList, strList);
    }
}
限定泛型类型

有时候,我们希望限制泛型方法的类型,使它只能接收特定类型或其子类型。这可以通过使用 extends 关键字实现。

public class BoundedGenericMethod {

    // 泛型方法,T 只能是 Number 或其子类
    public static <T extends Number> void printNumber(T number) {
        System.out.println("Number: " + number);
    }

    public static void main(String[] args) {
        printNumber(123);      // Integer
        printNumber(45.67);    // Double

        // printNumber("Hello"); // 编译错误,String 不是 Number 的子类
    }
}
使用通配符(通配符方法)
import java.util.List;

public class WildcardExample {

    // 使用通配符 ? 来接受任何类型的 List
    public static void printList(List<?> list) {
        for (Object element : list) {
            System.out.println("Element: " + element);
        }
    }

    public static void main(String[] args) {
        List<Integer> intList = List.of(1, 2, 3);
        List<String> strList = List.of("A", "B", "C");

        printList(intList);  // 接受 Integer 类型的 List
        printList(strList);  // 接受 String 类型的 List
    }
}

通配符 ?,表示未知类型。通配符在处理泛型集合或不确定类型的对象时非常有用。但严格来说,这并不是泛型方法,因为通配符 ? 仅表示未知类型,并没有引入方法级别的类型参数。泛型方法 的定义在于引入一个类型参数(如 <T>)来代替某个具体的类型,而通配符 ? 并没有提供这样的灵活性。

public static <T> void printList(List<T> list) {
    for (T element : list) {
        System.out.println("Element: " + element);
    }
}

这个就是泛型方法,其和上面的通配符方法能有相同的作用。

通配符

是在使用泛型时引入的一种特殊符号,用于表示一个不确定的类型。通配符使用 ? 表示,可以与 extendssuper 结合使用,形成有界通配符。它们主要用于解决泛型中涉及继承和协变/逆变的问题。通配符是一个实参。

  • 无界通配符 (?):表示可以接受任何类型。
  • 有界通配符 (? extends T):表示可以接受 T 类型及其子类。
  • 下界通配符 (? super T):表示可以接受 T 类型及其父类。
无界通配符

当你只关心操作中的对象,但不关心具体的类型时,可以使用无界通配符。? 表示未知的类型,可以用来接受任何类型的泛型参数。

如通配符方法中的通配符使用就是无界的

有界通配符

又叫上界通配符,? extends T 表示可以接受类型 T 及其子类。它常用于只读场景,即你希望从泛型容器中读取某些对象,但不向其中添加新对象

import java.util.List;

public class BoundedWildcardExample {

    // 使用有界通配符,接受 Number 及其子类的 List
    public static void printNumbers(List<? extends Number> list) {
        for (Number number : list) {
            System.out.println("Number: " + number);
        }
    }

    public static void main(String[] args) {
        List<Integer> intList = List.of(1, 2, 3);
        List<Double> doubleList = List.of(1.1, 2.2, 3.3);

        printNumbers(intList);   // 可以接受 Integer 类型的 List
        printNumbers(doubleList);  // 可以接受 Double 类型的 List
    }
}
下界通配符

? super T 表示可以接受类型 T 及其父类。下界通配符通常用于写入场景,即你希望向泛型容器中写入某种类型的对象。

import java.util.List;
import java.util.ArrayList;

public class LowerBoundWildcardExample {

    // 使用下界通配符,接受 Integer 及其父类的 List
    public static void addNumbers(List<? super Integer> list) {
        list.add(1);
        list.add(2);
        list.add(3);
    }

    public static void main(String[] args) {
        List<Number> numList = new ArrayList<>();
        addNumbers(numList);   // 可以接受 Number 类型的 List

        for (Object number : numList) {
            System.out.println("Number: " + number);
        }
    }
}
通配符类型说明适用场景
?无界通配符,表示任何类型。只对数据做读取操作,不关心类型。
? extends T上界通配符,表示类型 T 及其子类。从泛型容器中读取数据,但不进行写入操作。
? super T下界通配符,表示类型 T 及其父类。向泛型容器中写入数据,保证插入的数据类型是 T 或其子类。
PECS原则
  • Producer Extends: 如果你想要从集合中获取数据,且集合是一个生产者,使用 ? extends
  • Consumer Super: 如果你想要向集合中写入数据,且集合是一个消费者,使用 ? super

继承与泛型

普通的类与接口可以通过继承(extendsimplements)来形成父子类关系

class Animal {}
class Dog extends Animal {}



Animal animal = new Dog(); // 合法

泛型类之间没有继承的直接关系。即使 DogAnimal 的子类,List<Dog> 并不是 List<Animal> 的子类。这就是泛型中的一大关键点。如下例子是不合法的

List<Animal> animals = new ArrayList<Dog>(); // 不合法,编译错误

泛型类的继承关系不会随着其类型参数的继承关系而自动形成。

协变与逆变

协变逆变是泛型类型的行为,用来描述在子类与父类关系中的类型转换行为。主要通过通配符?extendssuper)来实现。

协变

协变允许你使用一个子类的集合当作父类的集合。这在泛型中使用 ? extends T 表示。

  • 协变使得你可以读取集合中的数据,但不能安全地向其中写入数据。
List<? extends Animal> animals = new ArrayList<Dog>();
Animal a = animals.get(0); // 合法,因为 Dog 是 Animal 的子类
animals.add(new Dog());    // 不合法,不能确定集合的具体类型
逆变

逆变允许你使用一个父类的集合当作子类的集合。这在泛型中使用 ? super T 表示。

  • 逆变使得你可以写入数据到集合,但不能安全地读取集合中的数据。
List<? super Dog> animals = new ArrayList<Animal>();
animals.add(new Dog());    // 合法,可以向 List 中添加 Dog 或其子类
Animal a = animals.get(0); // 不合法,编译错误,无法确定类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值