IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java从入门到精通九(Java泛型) -> 正文阅读

[Java知识库]Java从入门到精通九(Java泛型)

泛型说明

泛型是什么?我们在哪里会遇到?
比如在一些集合类里面,我们可以看到对于键值的参数化限制。作用就是指定了键值的类型。
在这里插入图片描述
当然也有未知类型的时候指定泛型,这种比较灵活,根据传入的具体参数决定具体参数类型。
在这里插入图片描述

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

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

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

使用泛型的好处

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

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类型的。

其实我们可以去简单修改

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);

        }

    }
}

在这里插入图片描述
我们也可以这样

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);

        }

    }
}

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

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

ArrayList<String>  list = new ArrayList<String>();

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

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:消除强制转换
其实道理还是和第一点的一样
在这里插入图片描述
在这里插入图片描述
提前将泛型写明,可以对后续的类型需求更加清楚。在其它的方面也是这样。

泛型类

class 类名 <T>{
	private T value1;
	/*public 类名(T value1)
	{
		this.value = value;
    }*/
    public T setValue(T value1)
    {
		this.value = value;	
    }
     public T getValue()
     {
		return value;
     }
}

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

一个例子

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();

    }
}

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

泛型接口

定义一个接口


public interface 类名<T>{
	public default T 方法名()
	{
		.....
    }
    public T demo_1();

}

实现接口类

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或者其父类

// An highlighted block
var foo = 'bar';

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

    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), 就不要使用通配符.

泛型方法

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

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

 public T getName() {
        return name;
    }

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

 public <T> void getAge(T t)
    {
		...
    }
  public<T>void show(T t)
  {
		...
}

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

泛型方法与可变参数

public <T> void printfMsg(T... args)
    {
        for(T t:args)
        {
            System.out.println(t);
        }
    }

调用赋值

 demo_1.printfMsg(1,2,3,4,5,6,7,8,9);

输出
在这里插入图片描述

泛型上界下界

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


    public <T>void  show_insert(GenericDemo<? extends Number> t) {
        System.out.println(t);

    }

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

  GenericDemo<Integer> demo2 = new GenericDemo<>(2);
  demo_1.show_insert(demo2);

在这里插入图片描述

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

public <T>void  show_insert(GenericDemo<? super Integer> t) {
        System.out.println(t);

    }

  GenericDemo<Integer> demo2 = new GenericDemo<>(2);
  demo_1.show_insert(demo2);

在这里插入图片描述
super以后你这个传入必须是指定类型或者是其父类型。

泛型数组

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

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

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

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-22 20:26:07  更:2022-02-22 20:26:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:58:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码