一、泛型
3.1 认识泛型
所谓泛型指的是,在定义类、接口、方法时,同时声明了一个或者多个类型变量(如:),称为泛型类、泛型接口、泛型方法、它们统称为泛型。
比如我们前面学过的ArrayList类就是一个泛型类,我们可以打开API文档看一下ArrayList类的声明。
ArrayList集合的设计者在定义ArrayList集合时,就已经明确ArrayList集合时给别人装数据用的,但是别人用ArrayList集合时候,装什么类型的数据他不知道,所以就用一个表示元素的数据类型。
当别人使用ArrayList集合创建对象时,new ArrayList
就表示元素为String类型,new ArrayList
表示元素为Integer类型。
我们总结一下泛型的作用、本质:
- 泛型的好处:在编译阶段可以避免出现一些非法的数据。
- 泛型的本质:把具体的数据类型传递给类型变量。
3.2 自定义泛型类
接下来我们学习一下自定义泛型类,但是有一些话需要给大家提前交代一下:泛型类,在实际工作中一般都是源代码中写好,我们直接用的,就是ArrayList这样的,自己定义泛型类是非常少的。
自定义泛型类的格式如下
//这里的<T,W>其实指的就是类型变量,可以是一个,也可以是多个。 public class 类名<T,W>{ }
接下来,我们自己定义一个MyArrayList泛型类,模拟一下自定义泛型类的使用。注意这里重点仅仅只是模拟泛型类的使用,所以方法中的一些逻辑是次要的,也不会写得太严谨。
//定义一个泛型类,用来表示一个容器 //容器中存储的数据,它的类型用<E>先代替用着,等调用者来确认<E>的具体类型。 public class MyArrayList<E>{ private Object[] array = new Object[10]; //定一个索引,方便对数组进行操作 private int index; //添加元素 public void add(E e){ array[index]=e; index++; } //获取元素 public E get(int index){ return (E)array[index]; } }
接下来,我们写一个测试类,来测试自定义的泛型类MyArrayList是否能够正常使用
public class Test{ public static void main(String[] args){ //1.确定MyArrayList集合中,元素类型为String类型 MyArrayList<String> list = new MyArrayList<>(); //此时添加元素时,只能添加String类型 list.add("张三"); list.add("李四"); //2.确定MyArrayList集合中,元素类型为Integer类型 MyArrayList<Integer> list1 = new MyArrayList<>(); //此时添加元素时,只能添加String类型 list.add(100); list.add(200); } }
关于自定义泛型类,你们把这个案例理解,对于初学者来说,就已经非常好了。
3.3 自定义泛型接口
在上一节中,我们已经学习了自定义泛型类,接下来我们学习一下泛型接口。泛型接口其实指的是在接口中把不确定的数据类型用<类型变量>
表示。定义格式如下:
//这里的类型变量,一般是一个字母,比如<E> public interface 接口名<类型变量>{ }
比如,我们现在要做一个系统要处理学生和老师的数据,需要提供2个功能,保存对象数据、根据名称查询数据,要求:这两个功能处理的数据既能是老师对象,也能是学生对象。
首先我们得有一个学生类和老师类
public class Teacher{ }
public class Student{ }
我们定义一个Data
泛型接口,T表示接口中要处理数据的类型。
public interface Data<T>{ public void add(T t); public ArrayList<T> getByName(String name); }
接下来,我们写一个处理Teacher对象的接口实现类
//此时确定Data<E>中的E为Teacher类型, //接口中add和getByName方法上的T也都会变成Teacher类型 public class TeacherData implements Data<Teacher>{ public void add(Teacher t){ } public ArrayList<Teacher> getByName(String name){ } }
接下来,我们写一个处理Student对象的接口实现类
//此时确定Data<E>中的E为Student类型, //接口中add和getByName方法上的T也都会变成Student类型 public class StudentData implements Data<Student>{ public void add(Student t){ } public ArrayList<Student> getByName(String name){ } }
再啰嗦几句,在实际工作中,一般也都是框架底层源代码把泛型接口写好,我们实现泛型接口就可以了。
3.4 泛型方法
同学们,接下来我们学习一下泛型方法。下面就是泛型方法的格式
public <泛型变量,泛型变量> 返回值类型 方法名(形参列表){ }
下图中在返回值类型和修饰符之间有定义的才是泛型方法。
接下我们看一个泛型方法的案例
public class Test{ public static void main(String[] args){ //调用test方法,传递字符串数据,那么test方法的泛型就是String类型 String rs = test("test"); //调用test方法,传递Dog对象,那么test方法的泛型就是Dog类型 Dog d = test(new Dog()); } //这是一个泛型方法<T>表示一个不确定的数据类型,由调用者确定 public static <T> test(T t){ return t; } }
3.5 泛型限定
接着,我们来学习一个泛型的特殊用法,叫做泛型限定。泛型限定的意思是对泛型的数据类型进行范围的限制。有如下的三种格式
- 表示任意类型
- 表示指定类型或者指定类型的子类
- 表示指定类型或者指定类型的父类
下面我们演示一下,假设有Car作为父类,BENZ,BWM两个类作为Car的子类,代码如下
class Car{} class BENZ extends Car{} class BWN extends Car{} public class Test{ public static void main(String[] args){ //1.集合中的元素不管是什么类型,test1方法都能接收 ArrayList<BWM> list1 = new ArrayList<>(); ArrayList<Benz> list2 = new ArrayList<>(); ArrayList<String> list3 = new ArrayList<>(); test1(list1); test1(list2); test1(list3); //2.集合中的元素只能是Car或者Car的子类类型,才能被test2方法接收 ArrayList<Car> list4 = new ArrayList<>(); ArrayList<BWM> list5 = new ArrayList<>(); test2(list4); test2(list5); //2.集合中的元素只能是Car或者Car的父类类型,才能被test3方法接收 ArrayList<Car> list6 = new ArrayList<>(); ArrayList<Object> list7 = new ArrayList<>(); test3(list6); test3(list7); } public static void test1(ArrayList<?> list){ } public static void test2(ArrayList<? extends Car> list){ } public static void test3(ArrayList<? super Car> list){ } }
3.6 泛型擦除
最后,关于泛型还有一个特点需要给同学们介绍一下,就是泛型擦除。什么意思呢?也就是说泛型只能编译阶段有效,一旦编译成字节码,字节码中是不包含泛型的。而且泛型只支持引用数据类型,不支持基本数据类型。
把下面的代码的字节码进行反编译
下面是反编译之后的代码,我们发现ArrayList后面没有泛型
二、集合概述和分类
2.1 集合的分类
同学们,前面我们已经学习过了ArrayList集合,但是除了ArrayList集合,Java还提供了很多种其他的集合,如下图所示:
我想你的第一感觉是这些集合好多呀!但是,我们学习时会对这些集合进行分类学习,如下图所示:一类是单列集合元素是一个一个的,另一类是双列集合元素是一对一对的。
在今天的课程中,主要学习Collection单列集合。Collection是单列集合的根接口,Collection接口下面又有两个子接口List接口、Set接口,List和Set下面分别有不同的实现类,如下图所示:
上图中各种集合的特点如下图所示:
可以自己写代码验证一下,各种集合的特点
//简单确认一下Collection集合的特点 ArrayList<String> list = new ArrayList<>(); //存取顺序一致,可以重复,有索引 list.add("java1"); list.add("java2"); list.add("java1"); list.add("java2"); System.out.println(list); //[java1, java2, java1, java2] HashSet<String> list = new HashSet<>(); //存取顺序不一致,不重复,无索引 list.add("java1"); list.add("java2"); list.add("java1"); list.add("java2"); list.add("java3"); System.out.println(list); //[java3, java2, java1]
2.2 Collection集合的常用方法
接下来,我们学习一下Collection集合的一些常用方法,这些方法所有Collection实现类都可以使用。 这里我们以创建ArrayList为例,来演示
Collection<String> c = new ArrayList<>(); //1.public boolean add(E e): 添加元素到集合 c.add("java1"); c.add("java1"); c.add("java2"); c.add("java2"); c.add("java3"); System.out.println(c); //打印: [java1, java1, java2, java2, java3] //2.public int size(): 获取集合的大小 System.out.println(c.size()); //5 //3.public boolean contains(Object obj): 判断集合中是否包含某个元素 System.out.println(c.contains("java1")); //true System.out.println(c.contains("Java1")); //false //4.pubilc boolean remove(E e): 删除某个元素,如果有多个重复元素只能删除第一个 System.out.println(c.remove("java1")); //true System.out.println(c); //打印: [java1,java2, java2, java3] //5.public void clear(): 清空集合的元素 c.clear(); System.out.println(c); //打印:[] //6.public boolean isEmpty(): 判断集合是否为空 是空返回true 反之返回false System.out.println(c.isEmpty()); //true //7.public Object[] toArray(): 把集合转换为数组 Object[] array = c.toArray(); System.out.println(Arrays.toString(array)); //[java1,java2, java2, java3] //8.如果想把集合转换为指定类型的数组,可以使用下面的代码 String[] array1 = c.toArray(new String[c.size()]); System.out.println(Arrays.toString(array1)); //[java1,java2, java2, java3] //9.还可以把一个集合中的元素,添加到另一个集合中 Collection<String> c1 = new ArrayList<>(); c1.add("java1"); c1.add("java2"); Collection<String> c2 = new ArrayList<>(); c2.add("java3"); c2.add("java4"); c1.addAll(c2); //把c2集合中的全部元素,添加到c1集合中去 System.out.println(c1); //[java1, java2, java3, java4]
最后,我们总结一下Collection集合的常用功能有哪些,ArrayList、LinkedList、HashSet、LinkedHashSet、TreeSet集合都可以调用下面的方法。
最新Java基础系列课程--Day11-范型与集合(二)https://developer.aliyun.com/article/1423517