java泛型的用法和详细的解释

简介: java泛型的用法和详细的解释

一直在使用泛型,但是对泛型的了解非常浅,所以今天就做一个详细的笔记。


Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。


1,定义泛型类、泛型接口



//泛型类
public class Apple<T>{
    private T info;
    Apple(T info){
        this.info = info;
    }
    public T getInfo() {
        return info;
    }
    public void setInfo(T info) {
        this.info = info;
    }
    public static void main(String[] args){
        Apple<String> apple = new Apple<>("苹果");
        System.out.println(apple.getInfo());
        Apple<Integer> intapp = new Apple<>(52);
        System.out.println(intapp.getInfo());
    }
}
//泛型接口
public interface List<E> {
    void add(E e);
}


上面定义了一个带泛型的Apple类,在使用的时候,为T传入实际的值,这样就可以生成如Apple,Apple形式的逻辑子类(物理上并不存在)


- 怎么派生带泛型的子类?


当创建了带泛型声明的接口、类之后,可以为该接口或者类创建实现类,或者是派生子类,当使用这些接口、父类时不能再包含形参:例如下面的做法就是错误的:

//定义类A继承Apple类,Apple类不能跟泛型形参
public class A extends Apple<T>{}


如果想从Apple类派生一个子类则可以修改为如下代码:


//使用Apple类时为T穿融入String类型
public class A extends Apple<String>{}


调用方法时必须为所有的数据形参传入参数值,与调用方法不同的是,使用接口、类时也可以不为泛型形参传入实际的参数参数,即下面的代码也是正确的


//使用Apple类时,没有为T形参传入实际的参数
public class A extends Apple{}


像这种使用Apple类时省略泛型的形式被称为原始类型(raw type)


如果从Apple 类派生子类1,则在Apple类中所有使用T类型的地方都将被替换成String类型,如果子类需要重写它的方法,就必须注意这一点。如下所示:


public class A extends Apple<String> {
    public A(String info) {
        super(info);
    }
    //正确的重写了父类的方法,返回值
    //与父类Apple<String>的返回值完全相同
    @Override
    public String getInfo() {
        return "重写父类的方法";
    }
    @Override
    public void setInfo(String info) {
        super.setInfo(info);
    }
}


如果使用Apple时没有传入实际的类型(即原始的类型),Java编译器可能发出警告:使用了未检查或不安全的操作----这就是泛型的警告。此时会把形参T当做为Object类型处理。如下所示:


//没有使用泛型,默认为Object类型
public class A extends Apple {
    public A(String info) {
        super(info);
    }
    @Override
    public Object getInfo() {
        return super.getInfo();
    }
    @Override
    public void setInfo(Object info) {
        super.setInfo(info);
    }
}


类型通配符


当我们是用一个泛型类时,都应该为这个泛型类传入一个类型实参。如果没有传入类型实际参数编译器就会提出泛型警告。如下所示:


public void test(List list){
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }


上面程序当然没有问题,这是一段最普通的遍历集合的代码,问题是List是一个有泛型声明的接口,此处使用没有传入实际的参数,这将引起泛型的警告。所以要为List接口传入时间的类型参数,因为List集合中的元素类型是不确定的,所以讲上面方法改为如下形式。


public void test(List<Object> list){
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }


表明看起来没有问题,这个方法的声明确实没有任何问题。问题是调用该方法传入的实际参数值可能不是我们期望的。如下:


public static void main(String[] args){
        List<String> list = new ArrayList<>();
        test(list);
    }


编译上面的程序,将发生如下错误:


不兼容的类型: List无法转换为List


表明List对象不能被当成List来使用。


使用类型通配符


为了表示各种泛型List的父类可以使用类型通配符,将一个问号作为他的参数类型传给list集合,写作List,意思是元素类型为未知的Object,这个问号被称为 通配符 ,他的元素可以匹配任何类型。如上面的可以写成如下形式:


public static void test(List<?> list){
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }


现在使用任何类型的List来调用它,都可以访问集合list中得元素,其类型时候Object,这个永远是安全的,不管List的真实数据是什么,它包含的都是Object。这种写法可以适应于任何支持泛型声明的接口和类。


但这种带通配符的List仅表示他是各种泛型的List的父类,并不能将元素加入到其中,如下就会引起编译错误:


List<?> list = new ArrayList<>();
        //下面将会引起编译错误
        list.add(new Object());


因为程序无法确定list集合中的元素类型,所以不能向其中添加对象。


类型通配符的上限


当我们定义某个方法的时候,如果想要让这个方法被特定的类调用,就可以使用这种形式。如下所示:


public static void test(List<? extends Apple<String>> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }


当我们调用这个方法的时候,传入的参数必须是继承自Apple的,


public class A extends Apple<String> {
    public A(String info) {
        super(info);
    }
    public static void test(List<? extends Apple<String>> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
    public static void main(String[] args) {
        List<A> list = new ArrayList<>();
        test(list);
    }
}


如上,list是一个受限制的通配符的例子,此处的 ?代表是一个未知的类型,就想前面看到的通配符一样,但此处的未知类型一定是Apple的子类型(也可以是 Apple本身),因此把这种称为通配符的上限。


由于这个程序无法确定这个受限通配符的类型是什么,所以不能把Apple对象或者他的子类加入到这个泛型的集合中去,例如,下面的代码就是错误的:


public static void test(List<? extends Apple<String>> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        //下面引起编译错误
        list.add(new A(""));
    }


简而言之,这种指定类型通配符上限的集合,只能从集合中取元素(取出的元素是总上限的类型),不能像集合添加元素。因为编译器无法确定这集合中的元素是那种类型。


类型通配符的下限


通配符下限用 的方式来指定,和通配符上限的作用恰好相反.


public class A extends Apple<String> {
    public A(String info) {
        super(info);
    }
    public static void test(List<? super A> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
    public static void main(String[] args) {
        List<Apple<String>> list = new ArrayList<>();
        test(list);
    }
}


修改一下text方法,改为 List ,这个表示这个list集合里面存的只能是A,或者是A的父类。向下限定可以向其中添加元素,但是添加的类型必须是 super 后面跟的类型,如下所示:


public class A extends Apple {
    public A(String info) {
        super(info);
    }
    public static void test(List<? super A> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        //可以添加
        list.add(new A(""));
        //编译报错
//        list.add(new Apple<String>(""));
    }
    public static void main(String[] args) {
        List<Apple> list = new ArrayList<>();
        test(list);
    }
}


泛型形参的上限


java泛型不仅允许在通配符上使用上限,而且可以在定义泛型形参的时候设定上限,表示传给该泛型形参必须是该类上限类型,要不就是该上限类型的子类,如下所示:


public class Apple<T extends Number>{
    private T info;
    Apple(T info){
        this.info = info;
    }
    public T getInfo() {
        return info;
    }
    public void setInfo(T info) {
        this.info = info;
    }
    public static void main(String[] args){
        Apple<Double> dou = new Apple<>(2.2);
        Apple<Integer> integer = new Apple<>(2);
        //下面将编译失败,视图将String类型传给形参T
        //是String不是Number的子类,所以编译错误
        Apple<String> apple = new Apple<>("苹果");
    }
}


String既不是Number类型,也不是他的子类,所以报错.


在一种更极端的情况下,程序需要为泛型设置多个上限(至少有一个父类上限,可一个有多个接口上限),表明该泛型形参必须是父类的子类(父类也行),并且实现多个上限的接口,如下所示:


//表明T类型必须是Number 或 是其子类,并且必须实现 Runnable接口
public class Apple<T extends Number & Runnable>{}


与类同时继承父类,实现接口类似的是,为泛型形参设置多个上限的时候,所有的接口上限必须位于类上限之后。也就是说,如果需要为泛型形参指定类上限,类上限必须放在第一位。


泛型方法


如下所示:


//泛型方法,该方法中带有一个T泛型形参 
    public static <T> void copy(List<T> from, List<T> to) {
        for (T t : from) {
            to.add(t);
        }
    }
    public static void main(String[] args){
        List<Integer> list1 = new ArrayList<>();
        List<Number> list2 = new ArrayList<>();
        copy(list1,list2);//报错:编译器无法确定泛型T的类型。
        List<String> list3 = new ArrayList<>();
        List<String> list4 = new ArrayList<>();
        copy(list3,list4);//泛型T 为String 类型
    }


copy方法中带有一个带T的泛型形参,但是在调用的时候 传的泛型参数为String,Integer类型,编译器无法准确的推断出泛型方法中泛型形参的类型,所以会报错.


为了避免上面的错误,可将代码改为如下格式.


//泛型方法,该方法中带有一个T泛型形参
    public static <T> void copy(List<? extends T> from, List<T> to) {
        for (T t : from) {
            to.add(t);
        }
    }
    public static void main(String[] args){
        List<Integer> list1 = new ArrayList<>();
        List<Number> list2 = new ArrayList<>();
        copy(list1,list2);//报错:编译器无法确定泛型T的类型。
        List<String> list3 = new ArrayList<>();
        List<String> list4 = new ArrayList<>();
        copy(list3,list4);//泛型T 为String 类型
    }


这种采用了类型通配符的方式,只要copy方法中的前一个集合的元素类型是最后一个集合元素的子类即可。


带泛型的返回值


//该返回值表明这个方法的返回值是一个list集合,并且元素的类型为T
   public static <T>  List<T> copy( List<T> to) {
        List<T> list = new ArrayList<>();
        for (int i = 0; i < to.size(); i++) {
            list.add(to.get(i));
        }
        return list;
    }
    //返回值为T
    public static <T extends Number> T  test(T t){
        return t;
    }
    public static void main(String[] args){
        List<Integer> list1 = new ArrayList<>();
        //该方法复制了一个新的集合,返回值为list集合 返回值为泛型
        List<Integer> copy = copy(list1);
       Double d = new Double(2.0);
       Double test1 = test(d);//返回值为传入的类型
    }



相关文章
|
2天前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
2月前
|
存储 安全 Java
深入理解Java中的FutureTask:用法和原理
【10月更文挑战第28天】`FutureTask` 是 Java 中 `java.util.concurrent` 包下的一个类,实现了 `RunnableFuture` 接口,支持异步计算和结果获取。它可以作为 `Runnable` 被线程执行,同时通过 `Future` 接口获取计算结果。`FutureTask` 可以基于 `Callable` 或 `Runnable` 创建,常用于多线程环境中执行耗时任务,避免阻塞主线程。任务结果可通过 `get` 方法获取,支持阻塞和非阻塞方式。内部使用 AQS 实现同步机制,确保线程安全。
112 3
|
2月前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
51 0
[Java]泛型
|
2月前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
22 1
|
2月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
29 5
|
2月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
21 1
|
2月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
23 2
|
3月前
|
Java
Java 正则表达式高级用法
Java 中的正则表达式是强大的文本处理工具,用于搜索、匹配、替换和分割字符串。`java.util.regex` 包提供了 `Pattern` 和 `Matcher` 类来高效处理正则表达式。本文介绍了高级用法,包括使用 `Pattern` 和 `Matcher` 进行匹配、断言(如正向和负向前瞻/后顾)、捕获组与命名组、替换操作、分割字符串、修饰符(如忽略大小写和多行模式)及 Unicode 支持。通过这些功能,可以高效地处理复杂文本数据。
66 10
|
3月前
|
存储 Java 数据处理
Java 数组的高级用法
在 Java 中,数组不仅可以存储同类型的数据,还支持多种高级用法,如多维数组(常用于矩阵)、动态创建数组、克隆数组、使用 `java.util.Arrays` 进行排序和搜索、与集合相互转换、增强 for 循环遍历、匿名数组传递以及利用 `Arrays.equals()` 比较数组内容。这些技巧能提升代码的灵活性和可读性,适用于更复杂的数据处理场景。
44 10
|
3月前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。