Java泛型,数组和方法返回类型 - 协变,逆变和不变

首先,让我们通常理解一下子类型规则是什么。

协变vs逆变vs双变vs不变

编程语言可能有支持以下子类型规则的特性:

    协变

        允许用超类型替换子类型。

    逆变

        允许用子类型替换超类型。

    双变

        同时是协变和逆变。

    不变

        不允许上述任何替换。

让我们看看Java支持哪些子类型规则:

Java数组是协变的

Java数组支持协变替换:

Integer[] integers = new Integer[10];
Number[] numbers = integers;

但是,上面的赋值是危险的,因为它可能最终导致ArrayStoreException:

public class CovariantArraysExample {

  public static void main(String[] args) {
      Integer[] integers = new Integer[10];
      Number[] numbers = integers;
      numbers[0] = new Double(25);
  }
}

输出

Caused by: java.lang.ArrayStoreException: java.lang.Double
	at com.logicbig.example.CovariantArraysExample.main(CovariantArraysExample.java:8)
	... 6 more

Java泛型是不变的

Java泛型具有特定类型T,不支持协变或逆变替换。这是为了避免像我们在上面的数组案例中看到的那样出现ArrayStoreException的情况。

public class InvariantGenericsExample {

  public static void main(String[] args) {
      List<Integer> integers = new ArrayList<>();
      List<Number> numbers = new ArrayList<>();
      numbers = integers;
  }
}

输出

Compilation errors:
InvariantGenericsExample.java:[11,19] incompatible types: java.util.List<java.lang.Integer> cannot be converted to java.util.List<java.lang.Number>
1 error

带有通配符'?extends'的Java泛型是协变的:

public class CovariantGenericsExample {

  public static void main(String[] args) {
      List<Integer> integers = new ArrayList<>();
      integers.add(1);

      List<? extends Number> numbers = integers;
      System.out.println(numbers);
  }
}

输出

[1] 

在赋值给List<? extends Number>后,我们不能向List中添加Number的任何子类型。例如,这样的尝试将导致编译错误:

public class CovariantGenericsExample2 {

  public static void main(String[] args) {
      List<Integer> integers = new ArrayList<>();
      integers.add(1);

      List<? extends Number> numbers = integers;
      Double d = new Double(23);
      numbers.add(d);
  }
}

输出

Compilation errors:
CovariantGenericsExample2.java:[14,16] no suitable method found for add(java.lang.Double)
    method java.util.Collection.add(capture#1 of ? extends java.lang.Number) is not applicable
      (argument mismatch; java.lang.Double cannot be converted to capture#1 of ? extends java.lang.Number)
    method java.util.List.add(capture#1 of ? extends java.lang.Number) is not applicable
      (argument mismatch; java.lang.Double cannot be converted to capture#1 of ? extends java.lang.Number)
1 error 

原因是我们不能保证列表最初是为哪种类型的元素构造的。'?extends Number'表示任何扩展Number的类型,但编译器永远不知道是什么类型。我们只能向此类集合引用添加null,或者从中删除项目,但我们不能向其添加新元素。

带有通配符'?super'的Java泛型是逆变的:

Java泛型允许用'? super'通配符将超类型替换为子类型:

public class ContravariantGenericsExample {

    public static void main(String[] args) {
        List<A> aList = new ArrayList<>();
        aList.add(new A());

        List<? super B> bList = aList;
        bList.add(new B());

        System.out.println(bList);
    }

    private static class A {
    }

    private static class B extends A {
    }
}

输出

[com.logicbig.example.ContravariantGenericsExample$A@436e27a6, com.logicbig.example.ContravariantGenericsExample$B@69c3928e]

请注意,在上述情况下,我们在赋值后能够添加类型B的元素。这是因为,编译器知道bList是B的任何超类型的列表,而B(或B的子类型)肯定可以始终分配给B的超类型。

另一方面,从'? super B',编译器将永远不知道B的哪个超类型,这意味着我们不能向bList添加类型A或A的其他子类型的任何元素。

Java中的协变方法返回类型

Java允许子类覆盖可以返回更特定类型的方法。换句话说,子类方法的返回类型必须能够替换超类方法的返回类型。

方法的协变返回类型是当方法在子类或子接口中被覆盖时,可以被“更窄”的类型(子类型)替换的类型。

换句话说,被覆盖的方法可以有更具体的返回类型。只要新的返回类型可以赋值给我们正在覆盖的方法的返回类型,它就可以被使用。

Java自5.0版本以来就支持这个特性。

public class CovariantReturnExample {

    interface SuperType {
    }

    interface SubType extends SuperType {
    }

    interface A {
        SuperType getType ();
    }

    interface B extends A {
        SubType getType ();
    }
}

注意:协变返回可以在扩展类或扩展接口时使用。在上述示例中,SuperType-SubType 和/或 A-B 可以是类。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用泛型数组作为方法的参数,例如: public static <T> void printArray(T[] array) { for (T element : array) { System.out.println(element); } } 这个方法可以打印任何类型数组,例如: Integer[] intArray = {1, 2, 3, 4, 5}; String[] stringArray = {"Hello", "World"}; printArray(intArray); printArray(stringArray); 输出结果分别为: 1 2 3 4 5 Hello World ### 回答2: 泛型数组参数是指在Java中可以使用泛型类型作为方法的参数类型,并且该参数是一个数组。使用泛型数组参数可以实现更加灵活和通用的方法设计。 在定义方法时,可以使用泛型参数来代替数组元素的具体类型。例如,可以定义一个方法来打印任意类型数组元素: ```java public static <T> void printArray(T[] arr) { for (T element : arr) { System.out.println(element); } } ``` 在调用该方法时,可以传入不同类型数组,例如整型数组、字符串数组或自定义类型数组: ```java Integer[] intArr = {1, 2, 3}; String[] strArr = {"Hello", "World"}; CustomType[] customArr = {new CustomType(), new CustomType()}; printArray(intArr); printArray(strArr); printArray(customArr); ``` 通过使用泛型数组参数,可以在方法中处理不同类型数组,提高代码的复用性和可读性。 需要注意的是,Java中不能直接创建泛型数组,只能创建泛型数组的引用。例如,以下代码是错误的: ```java T[] arr = new T[size]; // 错误,无法创建泛型数组 ``` 如果需要创建泛型数组,可以使用类型擦除和强制类型转换的方式: ```java T[] arr = (T[]) new Object[size]; // 正确,通过类型擦除和强制类型转换创建泛型数组 ``` 总结:Java泛型数组参数可以提供更加灵活和通用的方法设计,可以处理不同类型数组。虽然不能直接创建泛型数组,但可以通过类型擦除和强制类型转换的方式创建泛型数组的引用。 ### 回答3: Java中的泛型数组参数指的是在方法中以泛型数组作为参数进行传递。使用泛型数组参数可以提高代码的可重用性和安全性。 首先,泛型数组参数可以使方法更加通用。通过将数组的元素类型设置为泛型,可以在方法中处理不同类型数组。这样一来,同一个方法就可以处理多种类型数组,提高了代码的可重用性。 其次,泛型数组参数可以增加代码的类型安全性。通过使用泛型数组参数,可以在编译时进行类型检查,避免在运行时出现类型转换错误。这种类型安全性可以大大减少因为类型错误而导致的bug。 使用泛型数组参数的方法的声明方式如下: public static <T> void methodName(T[] array) { // 方法体 } 在方法体内部,就可以对泛型数组进行处理。在调用方法时,需要传入一个具体类型数组作为参数。 总之,Java泛型数组参数可以提高代码的可重用性和安全性。通过使用泛型数组参数,可以处理不同类型数组,并在编译时进行类型检查,避免类型转换错误。这使得代码更加灵活和健壮。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值