前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java从入门到精通九(Java泛型)

Java从入门到精通九(Java泛型)

作者头像
兰舟千帆
发布2022-07-16 11:06:57
6550
发布2022-07-16 11:06:57
举报
文章被收录于专栏:兰舟千帆的java学习笔记

Java从入门到精通九(Java泛型)

泛型说明

泛型是什么?我们在哪里会遇到? 比如在一些集合类里面,我们可以看到对于键值的参数化限制。作用就是指定了键值的类型。

在这里插入图片描述
在这里插入图片描述

当然也有未知类型的时候指定泛型,这种比较灵活,根据传入的具体参数决定具体参数类型。

在这里插入图片描述
在这里插入图片描述

一般具有一些比较规范的泛型类型标记符。

E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number(数值类型) ? - 表示不确定的 java 类型

这种标记符可以用在类,接口,方法中,我们可以称之为泛型类,泛型接口,泛型方法。

使用泛型的好处

1:在代码编译时期对数据类型进行检查

代码语言:javascript
复制
package java_practice;

import java.util.ArrayList;

public class GenericDemo {
    public static void main(String args[])
    {
        ArrayList  list = new ArrayList();
        list.add("jgdaabc");
        list.add(123);
        for(int i =0;i<list.size();i++)
        {
            String item = (String)list.get(i);
            System.out.println(item);

        }

    }
}

这个程序没有使用泛型,也就是没有对参数类型进行限制,集合中可以添加任意类型,但是如果我们后面的需求是String类型的话,我们需要转换。这样转换虽然在编译上没有报错,但是运行的时候便会抛出异常。

在这里插入图片描述
在这里插入图片描述

Integer类型是无法转换为String类型的。

其实我们可以去简单修改

代码语言:javascript
复制
package java_practice;

import java.util.ArrayList;

public class GenericDemo {
    public static void main(String args[])
    {
        ArrayList  list = new ArrayList();
        list.add("jgdaabc");
        list.add(123);
        for(int i =0;i<list.size();i++)
        {
            String item = list.get(i).toString();
            System.out.println(item);

        }

    }
}
在这里插入图片描述
在这里插入图片描述

我们也可以这样

代码语言:javascript
复制
package java_practice;

import java.util.ArrayList;

public class GenericDemo {
    public static void main(String args[])
    {
        ArrayList  list = new ArrayList();
        list.add("jgdaabc");
        list.add(123);
        for(int i =0;i<list.size();i++)
        {
            String item = String.valueOf(list.get(i));
            System.out.println(item);

        }

    }
}

关于这三种方式的区别,本文不做探讨。只是给出了解决办法。

但是我要说明的就是没有泛型的情况下,如果我们错误进行存储的话,倏然类型不可以转换,但是编译通过了。这样就可能在运行的时候抛出异常,但是如果我们很好的使用泛型,这样可以在编译的时候就可以避免这种错误。

代码语言:javascript
复制
ArrayList<String>  list = new ArrayList<String>();
在这里插入图片描述
在这里插入图片描述

给集合添加String类型进行限制,所以泛型为String,这样定义的话说明了集合中存储的元素只能是String类型。所以如果存储其它类型的话,就会在编译的时候进行检查。 2:让程序更加灵活 但是其实并不是说,我们使用泛型的时候类型一定是固定的。 简单举个例子

代码语言:javascript
复制
package java_practice;

import java.util.ArrayList;

public class GenericDemo<T> {
    public GenericDemo(T t) {
        System.out.println(t);
    }

    public static void main(String args[])
    {
        GenericDemo genericDemo = new GenericDemo("hello");
        new GenericDemo(123);

    }
}
在这里插入图片描述
在这里插入图片描述

T具体的类型由参入的参数决定

3:消除强制转换 其实道理还是和第一点的一样

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

提前将泛型写明,可以对后续的类型需求更加清楚。在其它的方面也是这样。

泛型类

代码语言:javascript
复制
class 类名 <T>{
	private T value1;
	/*public 类名(T value1)
	{
		this.value = value;
    }*/
    public T setValue(T value1)
    {
		this.value = value;	
    }
     public T getValue()
     {
		return value;
     }
}

大致的基本可以这样去定义,当然只要符合规范,自己也是可以去设定一些方法等的。类在实例化的时候一定要传入具体的参数。

一个例子

代码语言:javascript
复制
package java_practice;

import java.util.ArrayList;
import java.util.HashMap;

public class GenericDemo<T> {
    private T name;
    private T age;

    public void setName(T name) {
        this.name = name;
    }

    public void setAge(T age) {
        this.age = age;
    }

    public T getName() {
        return name;
    }

    public T getAge() {
        return age;
    }

    public GenericDemo(T t) {
        System.out.print(t);
    }

    public void print_demo() {
        System.out.println("我的名字叫" + name + ",今年" + age + "岁");
    }

    public static void main(String args[]) {
        GenericDemo demo_1 = new GenericDemo<>("hello,");
        demo_1.setAge(19);
        demo_1.setName("兰舟千帆");
        demo_1.print_demo();

    }
}

使用泛型给我们带来了一定的便捷。属性的确定可以根据传入参数的类型进行确定。

泛型接口

定义一个接口

代码语言:javascript
复制
public interface 类名<T>{
	public default T 方法名()
	{
		.....
    }
    public T demo_1();

}

实现接口类

代码语言:javascript
复制
public class 类名<T> implements 接口类名<T>
{
	
 @Override
    public T demo() {
      .....
    }

    @Override
    public T demo_1() {
     ......
    }

}

default关键字使得接口中的方法有方法体。这是最近的一个新的特性。 被default修饰的方法不再是抽象方法,我们甚至可以不去实现。

用泛型修饰方法的话,其实是代替了方法的返回类型。如果用泛型修饰后,又用其它的类型指定后只是冲突的。

引入类型通配符

这个我是查看了许多文章,然后其中说明的一个比较好的,我摘录一下具体的内容。

不变,协变,逆变

网上查看了许多资料就找到一篇用这几个名词。说实话,这样去描述类型转换或者继承我还是第一次见

逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类); f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;** f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;** f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系

注:该段摘录自java泛型 通配符详解及实践

这样的数学公式表达可能对于一些读者不是很友好。 简单来说,逆变就是比如A是B的父类,但是A可以继承B的类型匹配。。这样其实看起来有点逆向关系,所以叫做逆变,当然一般情况下,是不支持逆变的。 协变就是如果A是B的父类,B可以继承A的属性,也可以认为就是类型关系。 不变就是无论AB是和关系,都不能进行类型匹配。

这里的A或者B可以理解为Number或者Integer.然后f就代表了对应关系,A,B满足转型的条件的话,如果对应的数组是支持这种关系的,也就成立。

在这里插入图片描述
在这里插入图片描述

但是泛型是不变的,那就说明即使你的类型参数的转换满足了这种关系,也是绝对转换不了的。(以不变应万变)比如这样是不可以的。

在这里插入图片描述
在这里插入图片描述

用通配符支持协变和逆变(开挂行为)

解决上面问题的办法就是采用上边界通配符

在这里插入图片描述
在这里插入图片描述

加这个上边界通配符的作用就是说明了list被限制为继承number的任意类型。加了这个之后编译通过了,但是又带来了新的问题,既然是任意类型了,那么就没法再添加数据了,也就是无法添加一个确定的类型。除了null这个特殊的。null是不确定的,再mysql里面也经常说到null都不等于null。 上边界通配符的特点就是上界<? extends T>不能往里存,只能往外取。 既然有上边界通配符,那当然相对的也有下边界通配符。我们可以通过上边界通配符的特点发现,其实也就是帮助了支持协变,那么下边界通配符就是支持逆变。

在这里插入图片描述
在这里插入图片描述

这里的list2的类型就是Integer或者其父类

代码语言:javascript
复制
// An highlighted block
var foo = 'bar';

我们来看一个具体的说明的实例

代码语言:javascript
复制
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    
    //一个基本的前提:父类赋值给子类,而子类可以赋值给父类
    public class GenericTest {
        public static void main(String[] args) throws Exception {
            class Animal {}
            class Bird extends Animal {}
            class Dog extends Animal {}
            class Cat extends Animal {}
            
            List<Animal> tmpList = new ArrayList<>();
            tmpList.add(1);
            tmpList.add(2);
            tmpList.add(3);
    
            // extendsList 元素的类型是 Animal 或其子类。
            List<? extends Animal> extendsList = tmpList;
            // add,set 的参数包括泛型,要将 Bird 转换为 Animal 的子类,由于具体子类不清楚,这是错误的
            extendsList.add(new Bird('a'));
            extendsList.set(1, new Bird('b'));
    
            // remove 的参数是 Object,不是泛型,因此没问题
            extendsList.remove(new Bird('a'));
            extendsList.contains(new Bird('b'));
    
            // get 的返回值为泛型 Animal 的子类,可以转换为父类 Animal
            Animal extendsGet = extendsList.get(1);
    
    
            // superList 元素的类型是 Animal 或其父类。
            List<? super Animal> superList = tmpList;
            
            // add,set 的参数包括泛型,要将 Integer 转换为 Number 的父类,这是没问题的
            superList.add(new Bird('a'));
            superList.set(1, new Bird('b'));
    
            // remove, contains 的参数是 Object,不是泛型,因此没问题
            superList.remove(new Bird(1));
            superList.contains(1);
    
            // get 返回值为泛型 Animal 的父类,Animal 的父类不可以转换为 Animal
            Animal superGet = superList.get(1);
    }

但是我觉得两行代码就足够理解了。

在这里插入图片描述
在这里插入图片描述

可以看出采用上边界通配符修饰是不能够添加数据的。但是下边界可以。 什么时候使用向上转,和向下转?

in"类型: “in”类型变量向代码提供数据。 如copy(src,dest) src参数提供要复制的数据,因此它是“in”类型变量的参数。 "out"类型: “out”类型变量保存接收数据以供其他地方使用.如复制示例中,copy(src,dest),dest参数接收数据,因此它是“out”参数。 “in”,“out” 准则 “in” 类型使用 上边界通配符? extends. “out” 类型使用 下边界通配符? super. 如果即需要 提供数据(in), 又需要接收数据(out), 就不要使用通配符.

泛型方法

代码语言:javascript
复制
 public void setName(T name) {
        this.name = name;
    }

这个叫泛型方法吗?并不叫

代码语言:javascript
复制
 public T getName() {
        return name;
    }

这个也不叫泛型方法 这样可以

代码语言:javascript
复制
 public <T> void getAge(T t)
    {
		...
    }
  public<T>void show(T t)
  {
		...
}

如果再泛型类中声明了泛型方法,泛型方法使用的泛型类型T可以与类中的T不是同一种类型,也就是T不等于T。

泛型方法与可变参数

代码语言:javascript
复制
public <T> void printfMsg(T... args)
    {
        for(T t:args)
        {
            System.out.println(t);
        }
    }

调用赋值

代码语言:javascript
复制
 demo_1.printfMsg(1,2,3,4,5,6,7,8,9);

输出

在这里插入图片描述
在这里插入图片描述

泛型上界下界

其实还是和通配符的道理一样。上界就是<? extends 指定类型> 来举一个例子。字迹琢磨出来一个无聊的栗子,来吃个栗子

代码语言:javascript
复制
    public <T>void  show_insert(GenericDemo<? extends Number> t) {
        System.out.println(t);

    }

我是如何调用这个方法给这个t赋值呢?

代码语言:javascript
复制
  GenericDemo<Integer> demo2 = new GenericDemo<>(2);
  demo_1.show_insert(demo2);
在这里插入图片描述
在这里插入图片描述

相应的下界也是一样的道理

代码语言:javascript
复制
public <T>void  show_insert(GenericDemo<? super Integer> t) {
        System.out.println(t);

    }
代码语言:javascript
复制
  GenericDemo<Integer> demo2 = new GenericDemo<>(2);
  demo_1.show_insert(demo2);
在这里插入图片描述
在这里插入图片描述

super以后你这个传入必须是指定类型或者是其父类型。

泛型数组

一般类型的数组直接指定泛型去操作是行不通的。可以用集合类型的数组,但是这样用的话,会有套娃的风险

在这里插入图片描述
在这里插入图片描述

其实这样做不一定有实用的价值,只是sun其实给出了这部分的有关说明。目前,对集合采用这样的操作自己不是怎么去用。

但是总说来。泛型合理使用还是对代码的优化很有帮助的。

自己以后要是遇到这方面的事情会再说明。就先菜到这里吧!该文是自己的一些认识,如果有不足或者说的不对的地方,还请指正。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-02-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java从入门到精通九(Java泛型)
  • 泛型说明
  • 使用泛型的好处
  • 泛型类
  • 泛型接口
  • 引入类型通配符
    • 不变,协变,逆变
      • 用通配符支持协变和逆变(开挂行为)
      • 泛型方法
        • 泛型方法与可变参数
        • 泛型上界下界
        • 泛型数组
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档