Java中的泛型

Java中的泛型

一:泛型深入:

泛型:时JDK5引入的特性,可以在编译阶段约束操作的数据类型,并进行检查

泛型的格式:<数据类型>

注意:泛型只支持引用数据类型。

二:没有泛型的时候集合的类型是怎么限定的?
public class Test6 {
    public static void main(String[] args) {
        // 创建集合
        ArrayList list = new ArrayList();
        // 添加数据
        list.add("嘿嘿");
        list.add(1);
        list.add(new Student("张三", 23, null));
        // 迭代器遍历
        Iterator iterator = list.iterator();
        while(iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println(obj); // 嘿嘿 1 Student{name='张三', age=23, data=null}
        }
    }
}
public class Test6 {
    public static void main(String[] args) {
        /*
        * 结论:
        *   如果没有给集合指定类型,默认所有的数据类型都是Object
        * 此时可以往集合里面添加任意数据类型。
        * 带来了一个坏处:我们在获取数据的时候,没办法使用它的特有行为
        * 此时推出了泛型,可以在修改数据的时候就把类型进行统一,而且我们获取数据的时候也不用强转了,非常方便
        * */
        // 创建集合
        ArrayList list = new ArrayList();
        // 添加数据
        list.add("嘿嘿");
        list.add(1);
        list.add(new Student("张三", 23, null));
        // 迭代器遍历
        Iterator iterator = list.iterator();
        while(iterator.hasNext()) {
            String obj = (String) iterator.next();
            // 多态的弊端:不能访问子类的特有功能
            // 所以这里Object类型的obj是没办法用子类的特有功能的,比如String的length方法
            // 所以如果要用子类的特有方法必须强转
            // 但是强转的时候会报错Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String
            // 原因是因为Integer类型不能强转为String类型
            System.out.println(obj); // 嘿嘿 1 Student{name='张三', age=23, data=null}
        }
    }
}
三:泛型的好处:

(1)统一数据类型

(2)把运行时期的问题提前到了编译时期,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来。

四:泛型的扩展:

Java中的泛型其实是伪泛型,伪就是假的意思。所以可以理解为:Java中的泛型是一个假的类型限定,这个泛型只在编译时期是有效的,什么意思?假设现在有一个集合,集合的门口也有一个泛型String,那么所有的字符串都可以添加到集合当中,当数据添加到集合中的时候,他其实仅仅是在门口去检查了一下这些数据是否符合类型。如果说你是字符串,就可以添加成功,但是当数据真正的添加到集合里面的时候,集合还是会把这些数据当做是Object类型来处理,只不过在往外获取的时候,集合的底层额外多做了一件事情,他会把这些Object类型的数据按照泛型进行强转,强转成String字符串类型。在代码中的体现就是:当在编写Java文件的时候,在Java文件里面是真正的存在泛型的,但是当这个Java文件编译成class字节码文件的时候,泛型就会消失,这个过程在Java当中有一个专业的名称叫做“泛型的擦除”,也就是说,在编译的时候他会检查你添加的数据是不是字符串,如果不是,直接编译报错,在idea中的体现就是下面会有红色的波浪线。

五:Java为什么会这么设计呢?

在Java的老版本当中是没有泛型的,泛型是在JDK5的时候才出来的,在JDK5以前集合里面所有的数据都是当做Object类型,在使用的时候就非常不方便。不方便了就要改,具体怎么改?改动源码吗?不是的。在JDK5以前,已经写了很多很多代码了,而且这些代码在市面上也有很多地方都在用这些老版本里面的代码,如果当时直接改动老版本的源代码,那么全世界所有用到集合的项目里面都要跟着去改动,非常不现实。所以说在当时的做法就是:原本的代码不动,在这些代码的门口加一个限定,这就搞定了。这样就不需要修改原有的代码,又能够统一类型,两全其美,这就是泛型的由来。所以对于泛型只需要记住一句话就可以了:它的出现就是为了统一集合里面数据的类型的。

六:泛型的细节:

(1)泛型中不能写基本数据类型

为什么不能写基本数据类型呢?

在集合当中,就算你写了泛型,那些数据在存入集合当中的时候也会变成Object类型,所以如果写基本数据类型的话,它是没办法转成Object类型的,只有写它的包装类Integer类型,他才能够转成Object类型。所以这就是泛型当中不能写基本数据类型的原因。

(2)指定泛型的具体类型后,传递数据时,可以传入该类型或者其子类类型

ArrayList<Animal> list = new ArrayList<>();
list.add(new Animal());
list.add(new Cat());
list.add(new Dog());

比如这里有一个集合,泛型是Animal,Animal是Cat和Dog的父类。此时我们在添加数据的时候,可以往里面添加Animal类型的对象,

还可以添加Animal子类类型的对象,比如Cat、Dog,只要是继承Animal都可以往里面添加,但是很少会这么干,一般我们写泛型的时候要保证泛型跟数据的类型保持一致。

(3)如果不写泛型,类型默认是Object

如果不写泛型,代码是不会报错的,集合默认能存储的类型就是Object类型。

七:泛型可以在很多地方定义

在以前我们仅仅是使用泛型,我们可以自己写一个类似于ArrayList带有泛型的类让别人去用呢?必须可以!!

泛型除了可以写在类的后面,还可以写在方法上面,接口的后面。

如果把泛型写在类的后面,这个类叫做泛型类,写在方法上面叫做泛型方法,写在接口后面叫做泛型接口。

泛型类:当我们在定义一个类的时候,如果说类里面某个变量的数据类型不确定,此时就可以定义带泛型的类。

// 格式
修饰符 class 类名<类型> {
    
}
// 举例
public class ArrayList<E> {
    
}
// 这里的E就表示不确定的类型,当别人在使用ArrayList创建ArrayList对象的时候,才会去确定它的类型
// 所以这里的E可以“理解”为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成:T、E、K、V等等,也可以写ABC,随便你
// 只不过写T是英文Type单词的缩写,E是Element单词的缩写,K表示Key单词的缩写,V表示Value单词的缩写,所以一般用这四个字母多一些
// 用其他的也没问题

下面自定义一个泛型类:

// 当我在编写一个类的时候,如果不确定类型,那么这个类就可以定义为泛型类
public class MyArrayList<E> {
    Object[] obj = new Object[10];
    int size;

    /*
    * E:表示不确定的类型,该类型在类名后面已经定义过了,如果类名后面没有指定,这里写E会报错
    * e:形参的名字,也就是变量名
    * */
    public boolean add(E e) {
        obj[size] = e;
        size++;
        return true;
    }

    public E get(int index) {
        return (E)obj[index];
    }

    @Override
    public String toString() {
        return Arrays.toString(obj);
    }
}
public class Test4 {
    public static void main(String[] args) {
        //如果类名的后面带有泛型的话,那么在创建对象的时候就必须要给他传递一个类型过去
        MyArrayList<String> list = new MyArrayList<>();
        // list.add(123); // 编译报错
        list.add("aaa");
        list.add("bbb");
        System.out.println(list.get(0));// aaa
        System.out.println(list);// [aaa, bbb, null, null, null, null, null, null, null, null]
    }
}

泛型方法:当方法中形参类型不确定的时候,可以使用类名后面定义的泛型

比如刚刚写的ArrayList里面的add方法,添加之前类型是不确定的,此时就可以用类名后面定义的泛型E表示不确定的类型。

一旦我们在类名的后面写了一个类型之后,那么这个类中所有的方法就都可以使用这个不确定的类型E了。 如下所示

public class MyArrayList<E> {   
    public boolean add(E e) {
            obj[size] = e;
            size++;
            return true;
        }
}

但是会有一个小问题:如果说这个类当中现在只有一个方法的形参不确定,其实没必要把泛型定义在类后面,此时可以把泛型定义在方法上面,这个方法就叫做泛型方法。如下所示。

所以当形参类型不确定的时候,有两种解决方案:

1.使用类名后面定义的泛型(类里面所有方法都能用)

2.在方法上声明上定义自己的泛型(只有本方法能用)

public class MyArrayList {   
    public <E> boolean add(E e) {
            obj[size] = e;
            size++;
            return true;
        }
}

格式:

// 格式
修饰符 <类型> 返回值类型 方法名(类型 变量名) {
    
}
// 举例
public <T> void show(T t) {
    
}
// 这里的T可以“理解”为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成:T、E、K、V等等。
// 当现在调用show方法的时候,此时这个T才会确定类型

泛型方法的练习:

// 定义一个工具类,ListUtil
// 类中定义一个静态方法addAll,用来添加多个集合的元素
public class ListUtils {
    private ListUtils(){} // 私有化构造方法,不让外界造对象
    
    // 参数一:集合
    // 参数二:要添加的元素
    // 泛型要写在修饰符的后面,public static都是修饰符,所以要写在static后面
    // E...e:可变参数,就是可以任意数量个参数
    public static <E> void addAll(ArrayList<E> list, E...e) {
        for (E element : e) {
            list.add(element);
        }
    }
}
public class Test5 {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        ListUtils.addAll(list1, "a", "b");
        System.out.println(list1); // [a, b]

        ArrayList<Integer> list2 = new ArrayList<>();
        ListUtils.addAll(list2, 1, 2, 3, 4, 5);
        System.out.println(list2); // [1, 2, 3, 4, 5]
    }
}

泛型接口:当一个接口当中类型不确定的时候,就可以用泛型接口

// 格式
修饰符 interface 接口名<类型> {
    
}

// 举例
public interface List<E> {
    
}

重点:如何使用一个带泛型的接口?

方式一:实现类给出具体类型

public class MyArrayList2 implements List<String> { // 直接指定类型为String
	// 重写方法省略...
}

public class MyArrayList2Test {
    public static void main(String[] args) {
        MyArrayList2 list2 = new MyArrayList2(); // 里面只能存String
        list2.add("add");
        list2.add("张三");
        list2.add(123); // 编译报错
    }
} 

方式二:实现类延续泛型,创建对象时再确定

public class MyArrayList3<E> implements List<E> {
 	// 重写方法省略...   
}

public class MyArrayList3Test {
    public static void main(String[] args) {
        MyArrayList3<String> list3 = new MyArrayList3<>();
        list3.add("嘿嘿");
        list3.add(111); // 编译报错
    }
}

嗯,没了。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值