Java学习笔记十八——集合类之泛型

学习笔记参考书籍《Java编程基础》主编 张焕生。本书内容比较适合没有什么基础的入门小白,学完一章还有习题,比较适合初学者。
自律、积极、勤奋以及先从一个怎么样都不可能不会实现的小目标开始。

本文已收录于[ 专栏 ]


🌳《Java入门》系列🌳

前面[ 章节 ]


🎉🎉 Java学习笔记十七——集合类详细总结各自对比
✌ Java必备基础十六——三万字总结输入与输出流
😀Java必备基础十五——万字长文总结异常处理基本知识点
👋Java必备基础十四——内部类详细知识点归纳
🤞Java必备基础十三——接口详细知识点归纳
✨Java必备基础十二——抽象类
✌Java必备基础十一——多态详解以及多态原则
🙌Java必备基础十——类的继承详解
😊Java必备基础九——包装类
😎Java必备基础八——Math类、Random类、日期时间类
👌Java必备基础七——字符串的基本常用方法
😜Java必备基础六——一维数组
👏Java必备基础五——类的封装
🤳Java必备基础四——成员变量和局部变量
👍Java必备基础三——方法的声明与调用,递归
😘Java必备基础二——类和对象
👍Java必备基础一——Java语言基础

一、泛型是什么

1.1泛型的由来

在没有泛型之前,集合中加入特定的对象时,就会被当成Object类型。当从集合中取出对象后,需要进行强制类型转换如何打印相应的对象。这种操作的弊端很容易引起异常,并且代码臃肿。

举个简单的被说了无数遍的例子:

public class Example1_1 {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add("a");
        arrayList.add(1);

        for (int i=0;i<arrayList.size();i++){
            String s = (String)arrayList.get(i) ;
            System.out.println(s);
        }
    }
}

输出的结果是:ClassCastException异常

a
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at com.chapter8.Example1_1.main(Example1_1.java:17)

集合类ArrayList添加了String类型和Integer类型,但是输出时把这两个类型强转了,因此程序崩溃了。为了解决这样的问题 ,引入泛型。

引入泛型的集合可以记住元素类型,在编译时检查元素类型,如果集合中添加了不满足类型的对象时编译器就会提示错误。可以看到下图中定义了泛型元素为String类型,添加int类型的对象时编译器提示要求String类型对象。

请添加图片描述
需要额外注意的一点是:泛型的集合可以记住元素类型,在编译时检查元素类型,举个例子理解这句话:

public class Example1_1 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        ArrayList<Integer> arrayList1 = new ArrayList<>();
        arrayList1.add(1);
        arrayList.add("2");

        Class classStringArrayList = arrayList.getClass();
        Class classStringArrayList1 = arrayList1.getClass();

        if(classStringArrayList.equals(classStringArrayList1)){
            System.out.println("类型相同");
        }
    }
}

可以猜测一下程序的输出结果,程序会打印出类型相同。虽然两个类添加了不同类型的泛型,但是这两个类依旧是相同的类别。于是我们可以大胆的猜测一下:泛型只在编译阶段有效,在检验泛型结果后,程序会将泛型的相关信息擦除,泛型信息不会进入到运行时阶段。

1.2 泛型的优点

在由来中已经简单提到了没有泛型时的缺点,那么有了泛型时,缺点就是优点:

  • 减少代码臃肿,使代码变得简洁
  • 消除源代码中许多强制类型转换,提高Java中的类型安全。

二、泛型的简单使用

2.1 泛型菱形语法

List<String> list = new LinkedList<>();
Set<String> set = new HashSet<>();
Map<String,String> map = new HashMap<>();

2.2 泛型简单举例

泛型对于集合类非常重要,在集合中引入泛型能够提供编译时的类型安全,并且从集合中取出元素后不必再强制转换,简化了程序代码。

public class Demo1 {
    public static void main(String[] args) {
        List<String> ar=new ArrayList<>();
        Map<Integer,String> map = new HashMap<>();
        ar.add("a");
        ar.add("b");
        map.put(1,"c");
        map.put(2,"d");
        System.out.println("ar的集合元素为:"+ar);
        //遍历Map集合中的元素的一种方法
        Iterator<Map.Entry<Integer,String>> map1=map.entrySet().iterator();

        while (map1.hasNext()){
            Map.Entry<Integer,String> next = map1.next();
            System.out.println(next.getKey()+""+next.getValue());
        }
    }
}

输出的结果为:

ar的集合元素为:[a, b]
1c
2d

2.3 定义一个泛型类

2.3.1 基本写法

class 类名<泛型标识>{
  private 泛型标识 标识名
}

2.3.2 举个例子

/**
 * @param <T> T代表泛型标识,也可以写成E、K,编程人自己定义
 *           在实例化泛型类型时,必须指定T的具体类型
 */
public class Example1_1<T>{

    /**
     * key这个成员变量的类型为T,T的类型由外部决定
     */
    private T key;

    /**
     * @param key 泛型构造方法形参key的类型为T,由外部指定
     */
    public Example1_1(T key){//泛型构造方法形参key的类型也为T,T的类型也由外部决定
        this.key = key;
    }

    /**
     * @return 泛型普通方法getKey的返回类型为T,T的类型由外部指定
     */
    public T getKey() {
        return key;
    }

    public static void main(String[] args) {
        //泛型的类型参数只能是引用类型,并且传入的实参类型必须要与定义的泛型对象的类型相同
        Example1_1<Integer> exampleInt= new Example1_1<>(0);
        //错误,定义泛型对象类型要和传入的实参类型相同
        Example1_1<Integer> exampleInt1= new Example1_1<String>("1"); 
        
        Example1_1<String> exampleString = new Example1_1<>("2");
        System.out.println("exampleInt的key值:"+exampleInt.getKey());
        System.out.println("exampleString的key值:"+exampleString.getKey());


 //下面这些写法都是正确的,定义的泛型类不用都传入泛型的实参,实际上的泛型类可以被定义成任何类型,如果有定义,就会像上面一下做出限制
        Example1_1 a = new Example1_1(2);
        Example1_1 b = new Example1_1("hello");
        Example1_1 c = new Example1_1(false);

    }
}

输出结果为:

exampleInt的key值:0
exampleString的key值:2

2.4 定义一个泛型接口

2.4.1 基本写法

//定义一个泛型接口
public interface Example1_2<T> {
    T next();
}

2.4.2 举个例子


//定义一个泛型接口Example1_2
public interface Example1_2<T> {
    T fly();
}

//接下来实现这个接口

/**
 * 错误写法:不声明泛型编译器会报错无法找到T
 */
class Bird implements Example1_2<T>{
    @Override
    public T fly() {
        return null;
    }
}

/**
 * @param <T> 正确写法1,未传入泛型实参,
 *           与泛型类的定义相同,在声明类的时候,需将泛型的声明一起加入到类中
 */
class Bird1<T> implements Example1_2<T>{
    @Override
    public T fly() {
        return null;
    }
}


/**
 * 正确写法2,传入泛型实参:
 * 可以看到,当实现接口时确定了泛型的类型,则使用使用到泛型的地方都要保持一致的类型
 * 
 */
class Bird2 implements Example1_2<String>{
    @Override
    public String fly() {
        return null;
    }
}

2.4 定义一个泛型方法

2.5.1 基本写法

    /**
     * @param tClass 传入的泛型实参
     * @param <T> 返回值为T类型
     * @return
     * 说明:
     *  
     */
public <T>T genericMethod(Class<T> tClass) throws Exception{
    T instance = tClass.newInstance();//newInstance方法抛出了异常
    return instance;
}

看到上面的例子说明一下泛型的方法:

  • public 与返回值终中间的<T>,可以李佳节为声明此方法的泛型方法
  • 只有声明了<T>的方法才是泛型方法
  • 表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T

2.5.2 举个例子

✨✨✨下面这个例子概括了很多正确的泛型方法和不正确的泛型方法:自己在电脑上敲一遍,泛型方法就大致ok了。

   public class Generic<T> {

    private T key;

    public Generic(T key) {
        this.key = key;
    }

    /**
     * @return 这不是一个泛型方法,其返回值是声明泛型类时声明过的方法
     */
    public T getKey() {
        return key;
    }

    /**
     * 方法有问题,因为在类的的声明中并没有声明泛型E
     * 所以在使用E做形参和返回值的类型时,编译器无法识别
     */
    public E setKey(E key) {
        return key;
    }

    /**
     * @param generic 泛型类引用
     * @param <T>     泛型类类型
     * @return 返回值
     * 这是泛型方法
     * public和返回值之间的 <T> 必须要有,同时声明了返回值T
     * 泛型的数量可以为任意多个
     */
    public <T> T showKeyName(Generic<T> generic) {
        return generic.key;
    }

    /**
     * @param generic 不是泛型方法,是形参,类型是<T>
     */
    public void showKeyName1(Generic<T> generic) {
    }

    /**
     * @param generic 不是泛型方法 ?是实参,赋值的时候再赋予
     */
    public void showKeyName2(Generic<?> generic) {
    }

    /**
     * 这个方法有问题,因为泛型类型E未被声明
     *
     * @param generic Generic类的引用对象,其类型为E
     * @param <T>     声明泛型方法
     * @return 返回值类型未T
     */
    public <T> T showKeyName3(Generic<E> generic) {
    }

    /**
     * 是正确的泛型方法
     * 声明了一个泛型方法,无返回值,使用泛型E,E可为任意类型
     * 由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中没有声明泛型,编译器也坑正确识别泛型方法中的泛型</>
     */
    public <E> void showKeyName3(E t) {
    }

    /**
     * 声明了一个泛型方法,无返回值,使用泛型,T可为任意类型
     * 注意这里的T与泛型类中定义的T不是一个类型
     */
    public <T> void showKeyName4(T t) {
    }
    
  /**
     * 声明了一个泛型方法,无返回值,使用泛型,T可为多个任意类型
     * showKeyName4的输入参数可以为任意个,任意种
     */
    public static  <T> void showKeyName4(T... args) {
        for (T t :args){
            System.out.println(t);
        }
    }

   /**
     * 对于需要使用泛型的静态方法而言,需要添加额外的泛型声明
     * 即使静态方法中使用过泛型类中的声明也不可以。
     * T t和<T>要保持一致
     */
    public static<T> void showKeyName5(T t) {
    }
}

2.5.3 总结

  • 泛型方法能使方法独立于类而产生变化
  • 泛型方法前面一定要声明泛型 <T> ,除了非静态的void修饰的无返回值方法外,其它都要声明
  • 对于静态的无返回值的泛型方法,必须要声明泛型,并且方法的形参的类型一定与声明的泛型保持一致

2.5 泛型通配符

先来看一个例子:

class Bird2<T> {

    public static void showValue(Bird2<Integer> bird2) {
        System.out.println("这是Integer类型");

    }

    public static void main(String[] args) {

        Bird2<Number> bird2 = new Bird2();
        Bird2<Integer> bird1 = new Bird2();

        showValue(bird2);
        showValue(bird1);//这里会报错

    }
}
    

上面的 showValue(bird1)会报错:解决办法就是:将原来的Integer类型修改为,问题就可以解决。

  public static void showValue(Bird2<> bird2) {
        System.out.println(bird2.fly());

类型通配符一般使用代替具体的类型实参,此处的?是类型实参,不是形参。可以解决诸如上面的当具体类型不确定的时候,使用代替,在使用的时候给这个类型实参赋真正的类型。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值