了解的Java泛型

作者:~小明学编程  

文章专栏:JavaSE基础

格言:目之所及皆为回忆,心之所想皆为过往
在这里插入图片描述

目录

前言

什么是泛型

为什么要引入泛型

使用泛型

裸类型

泛型类的定义

类型擦除

通配符

什么是通配符

通配符的上下界

通配符的使用

泛型中的父子类


前言

今天给大家带来的文章是Java中的泛型,对于泛型的学习主要是为了让我们能够去阅读Java中的底层源码。

什么是泛型

为什么要引入泛型

 我们随意的点开了我们ArrayList的源码我们就可以看到形如ArrayList<E>的这种写法,我们可以把我们的数据类型当作参数传递给我们的类中,这样我们new对象的时候就比较的灵活了,就不会拘泥于一种类型了。

例如:

        ArrayList<Integer> arrayList1 = new ArrayList<>();
        ArrayList<String> arrayList2 = new ArrayList<>();

因为我们有泛型所以我们的ArrayList才能操作我们不同的数据类型,否则的话只能我们的ArrayList的类就只能操作一种类型,这样的话就很鸡肋,不够灵活。

使用泛型

class MyArray<T> {
    public T[] object = (T[])new Object[10];
    public void set(int pos,T val) {
        this.object[pos] = val;
    }

    public T get(int pos) {
        return object[pos];
    }
}

这里我们自己定义了一个泛型的类里面有一个数组和两个方法。

    public static void main(String[] args) {
        MyArray<String> myArray = new MyArray<>();
        myArray.set(1,"haha");
        String str = myArray.get(1);//这里自动的帮我们将Object的类型转换为String
        System.out.println(str);
    }

然后我们new了一个对象,我们传入一个String类型,然后将我们数组中的1的下标给设置为"haha"。

public T[] object = (T[])new Object[10];

对于这行代码,写的其实不是很好但是基本实现了我们想要表达的东西,因为我们是Object类型的,所以这里要强转为我们的泛型的类型。

裸类型

前面说到泛型,那么与之对应的就是我们的裸类型

MyArray myArray = new MyArray();

裸类型就是在我们new一个对象的时候不给它传递我们的数据类型,这就会导致我们下面的问题

    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.set(0,13);
        myArray.set(1,"haha");
        String str = (String) myArray.get(1);//这里我们必须进行一个强制类型的转换才能接受
        System.out.println(str);
    }

因为我们没有传递我们的数据类型,所以在我呢吧返回我们对象里面的数据的时候其实都是Object类型的,所以我们这里要进行一个强制类型的转换。

注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制。

泛型类的定义

上面我们说到泛型可以传入我们的数据类型,但是有时候不是什么类型都能传入的,这个时候就需要我们传入的类型进行一个约束了。

语法

class 泛型类名称<类型形参 extends 类型边界> {

}

其中类型边界表示我们的泛型类可以接受的类型参数的最高的父类。

示例

class C {

}
class D extends C{

}
class E extends D {

}
class Demo<T extends D> {

}

这里我们创建了四个类,其中我们的E继承了D,然后D继承C,然后我们的Demo类接受一个泛型的参数,参数的最高级别为D类,所以

 上面这幅图可以清楚的看到我们最高可以接受的参数类型为D类,然而我们的C是我们D的父类,所以这里就给报错了。

提醒

当我们没有指定我们的上界的时候例如

class MyArray<T>

这时候就默认我们的上界为Object,此时我们可以传入任何的类型,同时也正是因为这个原因,所以我们才需要对其上界进行一个限制。

类型擦除

这里我们有必要说的一点就是泛型是作用在编译期间的一种机制,在我们代码的运行期间实际上是没有这么多的类型的,那么到底我们传入的T在运行的时候是什么类型呢?
   实际上我们运行期间的类型主要取决于我们的边界。

class MyArray<T> {
    public T[] object = (T[])new Object[10];
    public void set(int pos,T val) {
        this.object[pos] = val;
    }

    public T get(int pos) {
        return object[pos];
    }
}

例如我们前面的这个代码我们前面说过没指定边界的话,就默认为Object,所以在代码的运行过程中我们的T就变成了Object。

class Demo<T extends D> {

}

再如上面的这段代码,在我们代码运行的过程中,我们的T就是我们的边界D类型,即使我们传入的参数是C也一样。

这就是我们的类型擦除,也就是在我们运行的时候我们所传的参数类型会被替换为我们的类型上界,同时这个过程是发生在我们的编译期间的。

我们想要实现这样一个功能,有一个类然后类里面有一个方法可以求出我们不同类型数组的的最大值。

写法一:

class Ale<T extends Comparable<T>> {
    public T getMax(T[] array) {
        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (max.compareTo(array[i])<0) {
                max = array[i];
            }
        }
        return max;
    }
}

写法一是这样的,我们的T的上界是Comparable,为什么上界要写成这样呢?因为我们下面要对数组里面的元素进行一个比较,如果我们没有Comparable的话我们进行完类型擦除,T将会替换为Object,此时里面没有compareTo方法,无法进行比较。

    public static void main(String[] args) {
        Ale<Integer> ale = new Ale<>();
        System.out.println(ale);
        Integer[] array = {9,3,2,65,34,77,457};
        Integer a = ale.getMax(array);
        System.out.println(a);
        System.out.println(Ale1.getMax(array));
    }

写法二:

class Ale1 {
    public static<T extends Comparable<T>> T getMax(T[] array) {
        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (max.compareTo(array[i])<0) {
                max = array[i];
            }
        }
        return max;
    }
}

第二种就是在我们的静态方法中使用泛型。

通配符

什么是通配符

 我们的源码中有很多形如上图的情况问号(?)是什么东西?

问号就是我们Java泛型中的通配符,

class Ale4 {
    public static void print2(ArrayList<?> list) {
        for (Object x:list) {
            System.out.println(x);
        }
    }
}

形如上面这段代码,当我们传参的时候不知道传入什么的时候我们可以使用通配符?,这样我们就可以接受任何泛型类型的参数了。

class Ale3 {
    public static<T> void print1(ArrayList<T> list) {
        for (T x:list) {
            System.out.println(x);
        }
    }
}

当然像上面这样不用通配符写也是能够实现我们上述的功能的。

通配符的上下界

上界的写法

<? extends 上界>

上界表示我们传入的类型最高只能是上界,上界的父类就不能传了。

下界的写法

<? super 下界>

下界表示我们传入的数据类型最低要求是下界,或者下界的父类

首先先来看看我们的上界的用法,

class Ale4 {
    public static void print2(ArrayList<? extends Number> list) {
        for (Object x:list) {
            System.out.println(x);
        }
    }
}

我们通配符和泛型的参数类型一样可以接受上界以内的任何的参数类型,但是我们的泛型会被替换为具体的上界类型而我们的通配符则是对所有对象都适用。

通配符的使用

public static void main10(String[] args) {
        ArrayList<? super Number> arrayList = new ArrayList<>();
        arrayList.add(new Integer(12));
    }

我么的下界适合添加数据,因为因为最低都是Number那么作为我们Number的子类,肯定是能够添加进去的。

    public static void main10(String[] args) {
        ArrayList<? extends Number> arrayList1 = new ArrayList<>();
        arrayList1.get(0);
    }

上界比较适合读入数据,因为我们返回的起码是Number,只要接受类型大于Number就行了。

泛型中的父子类

泛型中的父子类是根据我们通配符来决定的,例如


 MyArrayList<?> 是 MyArrayList<? extends Number> 的父类型
 MyArrayList<? extends Number> 是 MyArrayList<Integer> 的父类型

那么我们就可以写出如下的代码

    public static void main(String[] args) {
        MyArray<Integer> myArray1 = new MyArray<>();
        MyArray<? extends Number> myArray = myArray1;
    }

因为我们的MyArrayList<? extends Number> 是 MyArrayList<Integer> 的父类型所以我们的myArray可以直接引用我们的myArray1。

最后:

这是个人对Java泛型的一些理解,对于泛型的学习可以不用学的很深,只要能看的懂我们的源码就行了,自己写代码的时候就更不要设计到泛型了,除非自己去设计jdk。

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
### 回答1: Java泛型中的extend和super是用于限制泛型型参数的关键字。 extend用于限制泛型型参数的上界,表示泛型型参数必须是指定型的子或实现。例如,List<? extends Number>表示泛型型参数必须是Number或其子。 super用于限制泛型型参数的下界,表示泛型型参数必须是指定型的父或超。例如,List<? super Integer>表示泛型型参数必须是Integer或其父。 使用extend和super可以使泛型型参数更加灵活,可以适应不同的场景需求。但是需要注意的是,使用过多的extend和super可能会导致代码可读性降低,因此需要根据实际情况进行选择和使用。 ### 回答2: Java中可以通过泛型实现更强大的型安全和代码的复用性。在使用泛型时,我们常常需要了解泛型的extend和super关键字,以便更好地理解和使用。 泛型的extend关键字 泛型的extend关键字用于限定泛型型参数的上限,表示泛型型参数必须是继承了某个或实现了某个接口才能被使用。例如: ``` public class GenericClass<T extends Number> { private T data; public GenericClass(T data) { this.data = data; } public T getData() { return data; } } ``` 在上面的泛型中,使用了T extends Number语法,表示泛型型参数T必须是继承了Number的某个子,否则编译会报错。这样可以保证data属性的型一定是Number或其子型。 泛型的super关键字 泛型的super关键字用于限定泛型型参数的下限,表示泛型型参数必须是某个的父或某个接口的实现。例如: ``` public class GenericClass<T super Number> { private T data; public GenericClass(T data) { this.data = data; } public T getData() { return data; } } ``` 在上面的泛型中,使用了T super Number语法,表示泛型型参数T必须是Number的父,即可以是Number本身或其父型。这样可以保证data属性的型一定是Number或其父型。 需要注意的是,泛型的extend和super关键字只能用于泛型型参数,而不能用于普通型。在实际使用中,我们需要根据具体的情况来灵活地使用泛型的extend和super关键字,以便在代码中实现更好的型安全和更高的代码复用性。 ### 回答3: Java中的泛型是一种非常强大的语言特性,允许我们使用一种型来表示多种型,在编写型安全且可重用的代码时经常用到。在泛型的使用中,我们有时需要约束所能使用的泛型型,使得代码更加型安全。在这种情况下,我们可以使用泛型的 extend 和 super 关键字。 在 Java 中,使用 extends 关键字可以为泛型型参数添加上限限制。通过 extends 关键字,我们可以指定泛型型参数必须是某个或接口的子。这样做的好处是能够防止代码中使用无效的型参数,从而保证代码的型安全性。值得注意的是,我们只能使用 extends 关键字来添加上限限制,而不能添加下限限制。 下面是一个例子,演示如何使用 extends 关键字: ``` public class Box<T extends Number> { private T t; public Box(T t) { this.t = t; } public T get() { return t; } public void set(T t) { this.t = t; } } ``` 可以看到,Box 使用了 extends 关键字,将泛型型参数 T 限制为 Number 型或其子型。在此之后,我们就可以在 Box 中安全地使用 Number 型或其子型,如 Integer、Double 等。 除了上限限制之外,Java 还提供了另一种泛型型参数的约束方式:super 关键字。使用 super 关键字,我们可以限制泛型型参数必须是某个或接口的父。这个约束通常用于需要消费泛型对象的情况中,例如集合的 add 方法。通过为泛型型参数添加 super 关键字,我们可以保证只能添加某个的子到集合中,从而防止集合中出现无效的元素。似地,我们也只能使用 super 关键字添加下限限制,而不能添加上限限制。 这里是一个例子,演示如何使用 super 关键字: ``` public class Box<T> { private List<? super T> list; public Box() { list = new ArrayList<>(); } public void add(T t) { list.add(t); } public void addAll(List<? extends T> otherList) { list.addAll(otherList); } } ``` 可以看到,Box 使用了 super 关键字来限定集合中存储的型,只能是 T 的父或其本身。这样做的好处是,保证添加到集合中的元素都是型安全的,避免了程序运行时出现错误。 总之,泛型型参数的 extend 和 super 关键字是 Java泛型使用的重要工具,能够帮助我们实现型安全的代码。在实际使用中,根据具体场景灵活运用这两个关键字,能够提高代码的可读性和可维护性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值