JAVA基础总结(十)java 泛型详解

本文深入探讨了Java中的泛型,包括在集合、方法、类和接口上的应用,强调了泛型在提升类型安全性及避免类型转换异常方面的作用。同时,介绍了泛型的通配符使用,包括无界通配符、下界限定和上界限定,展示了不同通配符在集合操作中的限制和灵活性。
摘要由CSDN通过智能技术生成

一、泛型


泛型概括:

       泛型就是类型的参数化,就是可以把类型像方法的参数那样传递。泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。
       也就是说在泛型的使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
  • 泛型的应用场景:
    • 集合
    • 方法 (泛型方法)
    • 类 (泛型类)
    • 接口 (泛型接口)

       当我们使用泛型来约束集合、方法、类、接口时,如果直接指定类型的话,那么在使用泛型约束的集合、方法、类、接口时便只能操作与泛型类型相同的数据。(强制绑定数据类型,比如给List集合定义上String类型的泛型,那么只能存储String类型的数据)
       如果不定义泛型的类型而是使用类型变量的话,那么此时的泛型便可以认为是 Object 类型,可以对所有类型的数据进行操作。
注:泛型所指定的类型都是引用类型


1、泛型应用在集合上

       众所周知,如果不定义集合的存储类型,那么集合的默认类型便是 Object 类型。这样的好处便是在创建集合对象,并向集合中存储元素时,不会有类型的限制,可以存储所有的数据类型。
       我们平时在使用集合时,往往涉及到元素类型的转换。当集合中存在多种数据类型的元素时,我们便无法对集合整体做出类型转换的操作。虽然代码在编译期不会显示出异常,但是当对集合进行强制转换时,会将集合中的所有元素都进行类型转换,这时候如果其中某些元素与强转类型不符合,比如:不含数字的字符串类型元素转换成数字类型。对于这种不合理的类型转换,系统便会报出元素类型转换异常。
       为了避免这种类型转换的异常,Java提供了一种数据类型定义规范 — 泛型。使用泛型可以在定义集合对象时候指定集合的类型,这样在向集合中存储元素时便只能存储泛型指定类型的元素数据。有效的避免了集合元素的类型转换出现异常的问题。
  • 泛型应用在集合上的好处:

    • 好处:避免了类型强制转化的麻烦,存的什么类型,取出来的也是什么类型;代码运行之后才会抛出异常,写代码时不会报错
  • 泛型应用在集合上的弊端:

    • 弊端:泛型是什么类型只能存储什么类型的数据。

集合转换异常, Demo 代码示例:

public class GenericDemo {
    public static void main(String[] args) {
        // 创建
     List list = new ArrayList();

        // 添加元素
        //添加int类型的元素
        list.add(10); // JDK5以后的自动装箱
        // 等价于:array.add(Integer.valueOf(10));
        list.add("String");
        list.add(2.14);
        list.add('i');
        list.add(true);
        list.add("hello");
     
         // 遍历
        Iterator it = list.iterator();
        while (it.hasNext()) {
            Integer integer =(Integer)  it.next();
            System.out.println(integer);
        }
    }
}

运行结果:
在这里插入图片描述

从运行结果可以看出,使用强制类型转换时,转换类型为 Integer ,只有第一个元素遍历出来了,因为第一个元素是整数类型,可以与 Integer 之间相互转换,其他的都不可以转换,所以报出类型转换异常。




使用泛型定义集合类型, Demo 代码示例:

public class GenericDemo {
    public static void main(String[] args) {
        // 创建
    List<String> list = new ArrayList<String>();

      //非字符串类型不可存储
      //list.add(12.12);
      list.add("String");
      list.add("java");
      /*list.add(2.14);
      list.add('i');
      list.add(true);
      */

        // 遍历
        Iterator it = list.iterator();
        while (it.hasNext()) {
           // Integer integer =(Integer)  it.next();//字符串类型不可转换成数字类型
            System.out.println(it.next());
        }
    
    }
}

运行结果:
在这里插入图片描述


2、泛型应用在方法上(泛型方法)

  • 把泛型定义在方法上
    • 定义格式:
      	修饰符 <泛型变量> 返回值的类型 方法名称(形参列表){
                    //方法体
       	}
      

Demo代码示例:

 public class ObjectTool {
public static void main(String[] args) {

        ObjectTool tool = new ObjectTool();
        tool.information("姓名","姚青");
        tool.information("年龄",22);
        tool.information("性别","男");
        tool.information("头发是否浓密?",true);
        tool.information("是否是个憨批?",false);
    }

    public  <M> void information(M m1,M m2){

        System.out.println(m1+":"+m2);
}

运行结果:
在这里插入图片描述
当我们将方法定义成泛型方法,我们就可以向该方法中传递任意类型的参数了。


3、将泛型应用在类上(泛型类)

       类结构是面向对象中最基本的元素,如果我们的类需要有很好的扩展性,那么我们可以将其设置成泛型类。
  • 把泛型定义在类上,类便成为了泛型类
  • 格式:public class 类名<泛型类型1,…>
  • 注意:泛型类型必须是引用类型

泛型类Demo代码示例:

定义泛型实体类:

//定义泛型实体类,泛型中所指定的内容需要是引用类型

//此处O可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定O的具体类型
public class ObjectTool <O>{

    //这个name成员变量的类型为O,O的类型由外部调用指定
    private O name;
    private O age;

    //泛型方法getObj的返回值类型为O,O的类型由外部调用指定
    public O getName() {
        return name;
    }

    //泛型构造方法形参obj的类型也为O,O的类型由外部调用指定
    public void setName(O name) {
        this.name = name;
    }


    //泛型方法getObj的返回值类型为O,O的类型由外部调用指定
    public O getAge() {
        return age;
    }

    //泛型构造方法形参obj的类型也为O,O的类型由外部调用指定
    public void setAge(O age) {
        this.age = age;
    }

}

实现测试类:

public class ObjectToolText {

    public static void main(String[] args) {

        ObjectTool<String> objectTool = new ObjectTool<String>();

        objectTool.setName("李磊");

        ObjectTool<Integer> objectTool1 = new ObjectTool<Integer>();

        objectTool1.setAge(23);
        System.out.println("姓名:"+objectTool.getName()+","+"年龄:"+objectTool1.getAge());

        //可以看出实例化泛型类之后,会根据给对象定义的泛型来定义调用方法时参数的类型
    }
}

运行结果:
在这里插入图片描述


4、将泛型应用在接口上(泛型接口)

  • 定义格式:
    • public interface 接口名<泛型类型> { }

我们使用泛型接口定义接口实现类时,通常会出现两个状况

  • 第一种状况:我们知道接口的泛型是什么类型。
  • 第二种状况:我们不知道接口的泛型是什么类型。

Demo代码示例:

/*
 * 定义泛型接口:把泛型定义在接口上
 */
public interface Inter<T> {
    public abstract void show(T t);
}

--------------------------------------------------
//实现类在实现接口的时候,我们会遇到两种情况
//第一种情况:已经知道是什么类型的了
public class InterImpl implements Inter<String> {
    @Override
    public void show(String t) {
        System.out.println(t);
    }
 }
//第二种情况:还不知道是什么类型的
//这里需要注意,当实现泛型接口时,需要在类名和实现的接口名后面将泛型添上,
//实现类中添加的泛型不需要和接口的泛型一样,可以是自定义的随便搞,但是这两个泛型名需要是相同的
public class InterImpl<T> implements Inter<T> {

    @Override
    public void show(T t) {
        System.out.println(t);
    }
}

------------------------------------------

public class InterDemo {
    public static void main(String[] args) {
        // 第一种情况的测试
         Inter<String> i = new InterImpl();
         i.show("hello");

        // 第二种情况的测试
        Inter<String> i = new InterImpl<String>();
        i.show("hello");

        Inter<Integer> ii = new InterImpl<Integer>();
        ii.show(100);
    }
}
  • 第一种情况,知道什么类型,并且指定了该类型,那么就只能操作指定类型的数据
  • 第二种情况,不知道什么类型,使用类型变量,默认为 Object 类型,可以操作所有类型数据。

二,泛型进阶——通配符

1、泛型冷知识

  • 常用的通配符有: T,E,K,V,?

    • 其实也可以是A、B、C、D、E等的字母代替。使用 T,E,K,V,?只不过是约定俗成而已。
  • T,E,K,V,? 的约定如下:

    • T:(type) 表示具体的一个java类型。

    • E:代表Element。

    • K、V :分别代表java键值中的Key Value。

    • ? :无界通配符,表示不确定的 java 类型

2、通配符

  • 无界通配符:?
    • 任意类型,如果没有明确,那么就是Object以及任意的Java类了

  • 下边界限定通配符: < ? extends E>
    • 下边界:用extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

  • 上边界限定通配符: < ? super E>
    • 上边界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object。

Demo代码示例:

public class GenericDemo {
    public static void main(String[] args) {
        // 泛型如果明确的写的时候,前后必须一致
        Collection<Object> c1 = new ArrayList<Object>();
        // Collection<Object> c2 = new ArrayList<Animal>();//报错
        // Collection<Object> c3 = new ArrayList<Dog>();//报错
        // Collection<Object> c4 = new ArrayList<Cat>();//报错

        // ?表示任意的类型都是可以的,变相的等于 Object
        Collection<?> c5 = new ArrayList<Object>();
        Collection<?> c6 = new ArrayList<Animal>();
        Collection<?> c7 = new ArrayList<Dog>();
        Collection<?> c8 = new ArrayList<Cat>();

        // ? extends E:向下限定,E及其子类 
        // Collection<? extends Animal> c9 = new ArrayList<Object>();//报错
        Collection<? extends Animal> c10 = new ArrayList<Animal>();
        Collection<? extends Animal> c11 = new ArrayList<Dog>();
        Collection<? extends Animal> c12 = new ArrayList<Cat>();

        // ? super E:向上限定,E极其父类
        Collection<? super Animal> c13 = new ArrayList<Object>();
        Collection<? super Animal> c14 = new ArrayList<Animal>();
        // Collection<? super Animal> c15 = new ArrayList<Dog>();//报错
        // Collection<? super Animal> c16 = new ArrayList<Cat>();//报错
    }
}

class Animal {
}

class Dog extends Animal {
}

class Cat extends Animal {
}

通过代码可知:
       集合前面的泛型类型指定为无界通配符时,后面的泛型指定可以为任意类型。
       当在集合前面使用下边界限定通配符时,后面的泛型指定就只能是父类本身和继承该类的向下子类。
       当在集合前面使用上边界限定通配符时,后面的泛型指定就只能是当前指定类本身和其父类,直至向上祖类。


通配符这可能有些不详细,这个好大哥写的挺好,可以看一看。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值