集合框架和泛型

(一)认识集合

在开发应用程序时,如果想存储多个同类型的数据,可以使用数组来实现,但是使用数组存在以下一些明显的缺陷:

​ 1.数组的长度固定不变,不能很好的适应元素数量动态变化的情况

​ 2.可通过数组名.length获取数组的长度,却无法直接获取数组中的实际存储的元素个数

​ 3.数组采用在内存中分配连续空间的存储方式存储,根据元素信息查找时效率比较低,需要多次比较

​ 从以上分析可以看出数组在处理一些问题时存在明显的缺陷,针对数组的缺陷,Java提供了比数组更灵活、更实用的集合框架,可大大提高软件的开发效率,并且不同的集合可适用于不同应用场合。

​ 从以上分析可以看出数组在处理一些问题时存在明显的缺陷,针对数组的缺陷,Java提供了比数组更灵活、更实用的集合框架,可大大提高软件的开发效率,并且不同的集合可适用不同应用场合。

​ Java集合框架提供了一套性能优良、适用方便的接口和类,他们都位于java.util包中。Java集合类主要由Map接口和Collection接口派生而来的,其中Collection接口有两个常用接口,即List接口和Set接口,所以通常说Java集合框架由3大类接口构成(Map接口、List接口和Set接口)。

数组和集合的区别:
A:长度区别:数组的长度固定,集合长度可变。
B:内容不同:数组存储的是同一种类型的元素,而集合可以存储不同类型的元素。
C:元素的数据类型问题:数组可以存储基本数据类型,也可以存储引用数据类型,集合中存储的类型最后转换Object。

Collection:是集合的一个顶级集合

Collection的功能概述:
1.添加功能:
boolean add(Object object) 添加一个元素
boolean addAll(Collection c):添加一个集合
2.删除功能:
void clear():移除所有的元素
boolean remove(Object o):移除元素
boolean removeAll(Collection c)移除一个集合元素
3.判断功能:
boolean contains(Object o) 判断集合中是否包含着指定元素
boolean containsAll(Collection c):判断集合中是否包含指定的集合元素(一个还是所有) boolean isEmpty():判断集合是否为空
4.获取功能
Iterator 对象:循环遍历
5.长度
int size() :元素的个数 面试题:数组中有length,字符串有,集合中有没有?没有
6.交集功能
boolean retainAll(Collection c):两个集合都有的元素?返回的boolean值又是什么意思
7.把集合转换为数组
Object[] toArray()

(二)List接口

Collection接口是最基本的集合接口,可以存储一组不唯一、无序的对象。List接口继承自Collection接口,是有序集合。用户可使用索引访问List接口中的元素,类似于数组。List接口允许存放重复元素,也就是List可以存储一组不唯一、有序的对象。

​ List接口常用的实现类有ArrayList和LinkedList。

List集合的特有功能
A.添加元素的功能
void add(int index,Object object):在指定的位置添加元素
B.获取功能
Object get(int index):获取指定位置的元素
C.列表迭代器
LisIterator LisIterator():List集合中特有的迭代器
D.删除功能
Object remove(int index):根据索引删除元素,返回被删除的元素
E:修改功能
Object set(int index, Object element):根据索引修改元素,返回被修改的元素

ArrayList类

针对数组的一些缺陷,Java集合框架提供了ArrayList集合类,对数组进行了封装,实现了长度可变的数组,而且和数组采用相同的存储方式,在内存中分配了连续的空间,如下图1.2(画图),所以,经常称ArrayList为动态数组。但是它不等同于数组,ArrayList集合中可以添加任何类型的数据,并且添加的数据都将转换成Object类型,而在数组中只能添加同一个数据类型的数据。
ArrayList类提供了很用于操作数据,如表中列出的是ArrayList类的常用方法。
ArrayList类的常用方法:

方法说明
boolean add(Object o)在列表的末尾添加元素o,起始索引位置从0开始
void add(int index,Object o)在指定的索引位置添加元素o,在索引位置必须介于0和列表中元素个数之间
int size()返回列表中的元素个数
Object get(int index)返回指定索引位置处的元素,取出的元素是Object类型,使用前需要进行强制类型转换
void set(int index,Object obj)将index索引位置的元素替换为obj元素
boolean contains(Object o)判断列表中是否存在指定元素o
int indexOf(Object obj)返回元素在集合出现的索引位置
方法说明
boolean remove(Object o)从列表中删除元素o
Object remove(int index)从列表中删除指定位置的元素,起始索引位置从0开始

示例1:

​ 使用ArrayList常用方法动态操作数据

​ 实现步骤如下:

​ 1).导入ArrayList对象,并添加数据

​ 2)创建ArrayList对象,并添加数据

​ 3)判断集合中是否包含某个元素

​ 4)移除索引为0的元素

​ 5)把索引为1的元素替换为其他元素

​ 6)输出某个元素所在的索引位置

​ 7)清空ArrayList集合中的数据

​ 8)判断ArrayList集合中是否包含数据

    public static void main(String[] args) {


        ArrayList list =new ArrayList();  //1
        
        list.add("张三丰");
        list.add("郭靖");
        list.add("杨过");
        
        //判断集合中是否包含“李莫愁”
        System.out.println(list.contains("李莫愁"));
        
        //把索引为0的数据移除,对应步骤(4)
        list.remove(0);            //2

        System.out.println("------------");
        
        list.set(1,"黄蓉");             //3
        for (int i=0;i<list.size();i++){          //4
            String name =(String)list.get(i);
            System.out.println(name);
        }

        System.out.println("------");
        System.out.println(list.indexOf("小龙女"));   //5
        
        
        list.clear(); //清空list中的数据

        System.out.println("------------");
        
        for (Object object:list){
            String name = (String) object;
            System.out.println(name);
        }
        System.out.println(list.isEmpty());   //7
    }

​ 分析:

​ 在示例1中,1的代码调用ArrayList的无参构造方法,创建集合对象。常用的ArrayList类的构造方法还有一个带参数的重载版本,即ArrayList(int initialCapacity),它构造一个具有指定初始容量的空列表。

​ 在2的代码将list集合中索引为0的元素删除,list集合的下标是从0开始,也就是删除了"张三丰",集合中现有元素为"郭靖"和“杨过”。

​ 在3的代码将list集合中索引为1的元素替换为"黄蓉",即将"杨过"替换为"黄蓉",集合中现有元素为"郭靖"和"黄蓉"。

​ 在4的代码是使用for循环遍历集合,输出集合中所有的元素。list.get(i)取出集合中索引为i的元素,并强制转换为String类型。

​ 在5的代码为输出元素"小龙女"所在索引位置,因集合中没有该元素,所以输出结果为-1.

​ 在6的代码块是使用增强for循环遍历集合,输出集合中没有该元素。增强for循环的语法在Java基础课程中讲过,这里不再赘述。可以看出,遍历集合时使用增强for循环比普通for循环在写法上更加简单方便,而且不用考虑下标越界的问题。

​ 在7的代码来判断list集合是否空,因为前面执行了list.clear()操作,所以集合已经为空,输出true。

注意:

​ 1.调用ArrayList类的add(Object obj)方法,添加到集合当中的数据将被转换为Object类型

​ 2.使用ArrayList类之间,需要导入相应的接口和类,代码如下

​ import.java.util.ArrayList

​ import.java.util.ArrayList

示例2:

​ 需求:使用ArrayList集合存储新闻标题信息(包含ID、名称、创建者),输出新闻标题的总数量及每条新闻标题的名称

​ 实现步骤:

​ 1).创建ArrayList对象,并添加数据

​ 2).获取新闻标题的总数

​ 3)遍历集合对象,输出新闻标题名称

​ 1.创建一个NewTitle类

public class NewTitle {


    private int id;
    private String name;
    private String createName;
    
    //省略getter等方法

​ 2.实现代码

    public static void main(String[] args) {


        //创建一个新闻标题对象,
        NewTitle car = new NewTitle(1, "汽车", "管理员");
        NewTitle test = new NewTitle(2, "高考", "管理员");

        List list =new ArrayList();

        //按照顺序依次添加新闻标题
        list.add(car);
        list.add(test);

        //获取新闻标题的总数
        System.out.println("新闻标题数目为:"+list.size()+"条");
        System.out.println("新闻的标题名称为");
        //遍历集合
        for (Object object:list){
            NewTitle tile = (NewTitle) object;
            System.out.println(tile.getName());
        }
    }

​ 分析:

​ 在示例2中,ArrayList集合中存储的是新闻标题对象。在ArrayList集合中可以存储任何类型的对象。其中代码 List list =new ArrayList();是将接口List的引用指向实现类ArrayList的对象。在编程中将接口的引用指向实现类的对象是Java实现多态的一种形式,也是软件开发中实现低耦合的方式以,这样的用法可以大大提高程序的灵活性。随着编程经验的积累,开发者对这个用法的理解会逐步加深。

​ ArrayList集合因为可以使用索引来直接获取元素,所以优点是遍历元素和随机访问元素的效率比较高。但是由于ArrayList集合采用了和数组相同的存储方式,在内存中分配连续的空间,因此在添加和删除非尾部元素时会导致所有元素的移动,这就造成在插入、删除等操作频繁的应用场景下使用ArrayList会导致性能低下。所以数据操作频繁,最好使用LinkedList存储数据

Linkedlist类

LinkedList类是List接口的链接列表实现类。它支持实现所有List接口可选的列表的操作,并且允许元素值是任何数据,包括null。

​ LinkedList类采用链表存储方式存储数据,如图下(画图),优点在于插入、删除元素时效率比较高,但是LinkedList类的查找效率很低

​ 它除了包含ArrayList类所包含的方法外,还提供了如下表的一些方法,可以在LinkedList类的首部或尾部进行插入、删除操作。

方法说明
void addFirst(Object obj)将指定元素出入到当前集合的首部
void addLast(Object obj)将指定元素插入到当前集合的尾部
Object getFirst()获得当前集合的第一个元素
Object getLast()获得当前集合的最后一个元素
Object removeFirst()移除并返回当前集合的第一个元素
Object removeLast()移除并返回当前集合的最后一个元素

示例3:

​ 需求:

​ 使用LinkedList集合存储新闻标题(包含ID、名称、创建者),实现获取、添加及删除头条和末条新闻标题信息功能,并遍历集合

​ 实现步骤如下:

​ 1.创建LinkedList对象,并添加数据

​ 2.添加头条和末条新闻标题

​ 3.获取头条和末条新闻标题信息

​ 4.删除头条和末条新闻标题

​ (1)借用上面案例的NewTiltle的业务模型

​ (2)实现需求

 public static void main(String[] args) {


        //创建一个新闻标题对象,
        NewTitle car = new NewTitle(1, "汽车", "管理员");
        NewTitle test = new NewTitle(2, "高考", "管理员");

        List list =new ArrayList();

        //按照顺序依次添加新闻标题
        list.add(car);
        list.add(test);

        //获取新闻标题的总数
        System.out.println("新闻标题数目为:"+list.size()+"条");
        System.out.println("新闻的标题名称为");
        //遍历集合
        for (Object object:list){
            NewTitle tile = (NewTitle) object;
            System.out.println(tile.getName());
        }
    }

​ 除了表1-2中列出的LinkedList类提供的方法外,LinkedList类和ArrayList类所包含的大部分方法都是一样的,主要的原因是因为他们都是List接口的实现类。由于ArrayList采用和数组一样的连续的顺序存储方法,当对数据频繁检索时效率较高,而LinkedList类采用链表存储方式,当数据添加,删除或修改比较多时,建议选择LinkedList类存储数据

LinkedList解答

1.什么是链表结构

​ 链表是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每个节点里存到下一个节点指针(标志、路标)

​ 链表分类:

​ 单向链表

​ 双向链表

​ 队列:Queue接口:

​ 存储特点:先进先出

​ Queue的子接口Deque接口:

​ 也是线型表的一种,称之为双端队列,两端都支持存储操作

ArrayList和LinkedList区别

​ 1.底层数据结构:ArrayList底层使用的是数组;LinkedList底层使用的是双向链表;

2.插入和删除元素操作:ArrayList采用的是数组存储,所以插入和删除元素是跟元素的位置有关系。LinkedList采用的是链表存储,删除元素是不受元素位置影响的;如果是要在指定位置i插入和删除的话((add(int index,E 因为需要先移动再插入。

3.随机访问:ArrayList对于随机元素访问的效率明显比LinkedList高。随机访问就是通过元素的索引来获取元素(也就是set和get(int index)方法)。

4.线程不安全:ArrayList和LinkedList都是不同步的,也就是说都是线程不安全的。

5.接口实现:,而LinkedList实现了Deque可以当做队列使用

(三)Set接口

Set接口是Collection接口另外一个常用子接口,Set接口描述的是一种比较简单的集。集合中的对象并不按特定的方式排序,并且不能保存重复的对象,也就是说Set接口可以存储一组唯一、无序的对象。

​ Set接口常用的实现类有HashSet。

HashSet类

加入现在需要很多数据总查找某个数据,LinkedList类就无需考虑了,它的数据结构决定了它的查找效率低下。如果使用ArrayList类,在部知道数据的索引且需要全部遍历的情况下,效率一样很低下。为此Java集合框架提供了一个查找效率高的集合类HashSet。HashSet类实现了Set接口,是使用Set集合时最常用的一个实现类。HashSet。HashSet类实现了Set接口,是使用Set集合时最常用的一个实现类。

HashSet集合的特点:

​ 1).集合内的元素是无序排列

​ 2).HashSet类是非线程安全

​ 3)允许集合元素为null。

​ HashSet类的常用方法

方法说明
boolean add(Object o)如果Set中尚未包含指定元素o,则添加指定元素o
void clear()从set中移除所有的元素
int size()返回Set中的元素的数量(Set的容量)
boolean isEmpty()如果Set不包含任何元素,则返回true
boolean contains(Object o)如果Set包含指定元素o,则返回true
boolean remove(Object o)如果指定元素o存在于set中,则将移除

示例4

​ 需求:使用HashSet类的常用方法存储操作新闻标题信息,并返回遍历集合

​ 实现步骤:

​ (1).创建HashSet对象,并添加数据

​ (2).获取新闻标题的总数

​ (3).判断集合中是否包含汽车新闻标题

​ (4).移除对象

​ (5).判断集合是否为空

​ (6).遍历集合

public static void main(String[] args) {

        //创建一个新闻标题对象,
        NewTitle car = new NewTitle(1, "汽车", "管理员");
        NewTitle test = new NewTitle(2, "高考", "管理员");


        //创建存储新闻标题的集合对象
        Set newsTitle = new HashSet();

        //按照顺序依次添加新闻标题
        newsTitle.add(car);
        newsTitle.add(test);

        //获取新闻标题的总数
        System.out.println("新闻标题数目为:"+newsTitle.size()+"条");

        //判断集合中是否含汽车新闻标题
        System.out.println("汽车新闻是否存在:"+newsTitle.contains(car));

        //移除对象
        newsTitle.remove(test);
        System.out.println("教育对象删除");
        System.out.println("集合是否为空:"+newsTitle.isEmpty());

        //遍历所有的新闻标题
        System.out.println("遍历所有的新闻标题");
        for (Object object:newsTitle){

            NewTitle title = (NewTitle) object;
            System.out.println(title.getName());
        }

    }

存储自定义对象,并且保证元素的唯一性

HashSet底层依赖的是hashCode()和equals()方法。
而这两个方法我们在自定义类中没有重写,所以,默认使用的是Object类。
这个时候,他们的哈希值是不会一样的,根本就不会继续判断,执行了添加操作。
要在自定义存储的类中覆写,hashCode()和equals(),可以解决。

下面学生类有name,age属性,重写hashcode,equals。重写后,元素就是唯一的。

 @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

LinkedHashSet

LinkedHashSet 是HashSet子类

1.底层的数据结构:
数组+双向链表/红黑树(当链表节点数>8同时数组元素>=64时,会把链表变成红黑树)
2.查询速度快,增删速度也不慢
3.无索引:指的是不能通过索引来获得元素
4.元素不可重复:依赖所属类的hashCode()和equals()方法
5.线程不安全,不同同步,但是效率高
6有序的:怎么存就是怎么取(因为加了一个链表)

(四)使用迭代器遍历集合

Iterator iterator():迭代器,集合的专用遍历方式
Object next():获取元素,并移动到下个位置
boolean hasNext():如果仍有元素,则返回true


        Collection list = new ArrayList();
        list.add("apple");
        list.add("banana");
        list.add("lemon");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            String a=(String)iterator.next();
            System.out.println(a);
        }

        //创建集合Collection,多态
        Collection c=new ArrayList();


        //创建学生
        Student s1=new Student();
        s1.setName("周杰伦");
        s1.setAge(38);


        Student s2 =new Student("刘德华",58);
        Student s3=new Student("周润发",58);
        Student s4=new Student("林明",58);
        Student s5=new Student("郭富城",50);

        //学生添加到集合中
        c.add(s1);
        c.add(s2);
        c.add(s3);
        c.add(s4);
        c.add(s5);


        //遍历
        //首先获得迭代器对象
        Iterator it = c.iterator();

        while (it.hasNext()){

            //进行强制类型转换,把Object对象转换为Student;
            Student student =(Student)it.next();
            System.out.println("学生的姓名:"+student.getName()+"学生的姓名:"+student.getAge());
        }

(五)Map接口

​ Map接口存储一组成对的键(key)——值(value)对象,提供key到value的映射,通过key来检索。Map接口中的key不要求有序,不允许重复。value同样不要求有序,但允许重复

方法说明
Object put(Object key,Object value)将相互关联的一个key与value放入该集合中,如果

此Map接口中已经包含了Key对应的Value,则旧值将被替换
Object remove(Object key)从当前结合中移除与指定key相关的映射

并返回该key关联的旧Value,如果Key没有关联,则返回null
Object get(Object key)获得与key相关的value

,如果该key不关联任何非null值,则返回null
boolean contains(Object key)判断集合中是否存在这个key
boolean isEmpty()判断集合中是否存在元素
void clear()清除集合中所有的元素
int size()返回集合中元素的数量
Set keySet()获得所有key的集合
Collection values()获取所有value的集合

Map接口中存储的数据都是键——值对,例如,一个身份证号码对应一个人,其中身份证号码就是key,与此号码对应的人都是value。

使用HashMap动态存储数据

常用的map的实现类HashMap,其优点是查询指定元素效率高

(六)Collections类

​ Collections类是Java提供的一个集合操作工具类,它包含了大量的静态的方法,实现了对集合元素的排序、查找和替换操作

注意:Collections和Collection是不同的,前者是集合的操作类,后者是集合的接口

对集合元素排序与查找

排序是针对集合的一个常见需求。要排序就要知道两个元素哪个大哪个小。在Java中,如果想实现一个类的对象之间比较大小,那么这个类就要实现Comparable。此接口强行对实现它的每个类的对象进行整体排序。这种整体排序被称为类的自然排序,类的comparaTo()方法被称为它的自然比较方法。此方法用于比较此对象与指定对象的顺序,如果该对象小于、大于或等于指定对象,则分别返回负整数、零或正整数。

​ CompareTo()方法的定义语法格式如下:

​ int compareTo(Object obj);

​ 其中:

​ 参数:obj即要比较的对象

​ 返回值: 负整数、零或整数,根据此对象的大小,等于还是大于指定对象返回不同的值

​ 实现此接口的对象列表(和数组)可以通过Collections.sort()方法(和Arrays.sort()方法)进行自动排序

示例8:

​ 学生类Student实现了Comparable接口,重写了compareTo()方法,通过比较学号实现对象之间的大小比较

​ 实现步骤:

​ (1)创建Student类

​ (2)添加属性学号number(int)、姓名name(String)和性别gender(String)

​ (3)实现Comparable接口、compareTo()方法

​ Student类

public class Student implements Comparable{


    private int number=0;   //学号
    private String name=""; //姓名
    private String gender="";   //性别

	//省略set和get方法,有参构造和无参构造
    //实现comparable接口重写comparableTo方法
    @Override
    public int compareTo(Object obj) {

        Student student = (Student) obj;

        //如果学号相同,那么两者就是相等的
        if (this.number==student.getNumber()) {
            return 0;
        }else if (this.number>student.getNumber()) {  //如果这个学生的学号大于传入学生的学号
            return 1;
        }else {
            return -1;
        }

    }
}

分析:

​ 元素之间可以比较大小之后,就可以使用Collections类的sort()方法对元素进行排序操作了,前面介绍过List接口和Map接口,Map接口本身是无序的,所以不能对Map接口做排序操作;但是List接口是有序的,所以可以对List接口进行排序。注意List接口存放的元素,必须是实现Comparable接口的元素才可以

示例9

​ 使用Collections类的静态方法sort()和binarySearch()对List集合进型排序与查找

​ 实现步骤:

​ (1)导入相关类

​ (2)初始化数据

​ (3)遍历排序前的集合并输出

​ (4)使用Collection类的sort()方法排序

​ (5)遍历排序后集合并输出

​ (6)查找排序后某元素的索引

    public static void main(String[] args) {

        //先创建4个学生,对其学号设值
        Student student = new Student();
        student.setNumber(1);
        Student student1 =new Student();
        student1.setNumber(2);
        Student student2 =new Student();
        student2.setNumber(3);
        Student student3 =new Student();
        student3.setNumber(4);

        //创建集合,把学生添加到集合中
        ArrayList list =new ArrayList();
        list.add(student2);
        list.add(student3);
        list.add(student);
        list.add(student1);

        System.out.println("---排序前----");

        Iterator iterator = list.iterator();

        while (iterator.hasNext()){

            Student stu= (Student) iterator.next();
            System.out.println(stu.getNumber());
        }

        //使用Collections类的sort()方法对list集合进行排序
        System.out.println("----排序后----");

        Collections.sort(list);

        Iterator iterator1 =list.iterator();
        while (iterator1.hasNext()){

            Student stu1= (Student) iterator1.next();
            System.out.println(stu1.getNumber());
        }

        //使用Collection类的binarySearch()方法对list集合进行查找

        int index =Collections.binarySearch(list,student);  //1
        System.out.println("student3的索引值:"+index);

    }

​ 分析:1处的代码使用Collections类的binarySearch()方法对list集合进行查找,因为studnet的学号是1,所以索引值是0.

替换集合元素

​ 若有个需求,需要把一个list集合中所有的元素换为相同的元素,则可以使用Collections类的静态方法fill()来实现。

示例10:

​ 使用Collections类的静态方法fill()替换List集合中所有元素为相同的元素。

​ 实现步骤如下:

​ (1)导入相关类,初始化数据

​ (2)使用Collections类的fill()方法替换集合中的元素

​ (3)遍历输出替换后的集合

public static void main(String[] args) {

        List list =new ArrayList();
        list.add("张三丰");
        list.add("杨过");
        list.add("郭靖");

        Collections.fill(list,"东方不败");  //此次替换是把所有的元素替换为同一个元素
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){

            String name= (String) iterator.next();

            System.out.println(name);
        }

        //list集合删除指定元素位置,再在指定位置添加元素。
        list.remove(0);
        list.add(0,"杨过");
        System.out.println(list);

    }

(七)泛型

泛型是JDK1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,使代码可以应用于多种类型。简单来说,Java语言引入泛型的好处是安全简单的,且所强制转换都是自动和隐士进行的,提高了代码的重用率。

1.泛型的定义

​ 将对象的类型作为参数,指定到其他类或者方法上,从而保证类型转换的安全性和稳定性,这就是泛型。泛型的本质就是参数化类型。

​ 泛型的定义语法格式如下:

​ 类1 或者接口 <类型实参> 对象 =new 类2<类型实参>();

注意:

​ 首先,“类2”可以是"类1"本身,可以是"类1"的子类,还可以是接口的实现类;其次“类2”的类型实参必须与“类1”中的类型实参相同。

​ 例如:

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

​ 上述代码表示创建一个ArrayLsit集合,但规定该集合中的存储的元素类必须为String类型

2.泛型在集合中的应用

​ 前面学习List接口时已经提到,其add()方法的参数是Object类型,不管把什么对象放入List接口及其子接口或实现类中,都会被转换为Object类型。在通过get()方法取出集合中元素时必须进行强制类型转换,不仅繁琐而且容易出现ClassCastException异常。Map接口中使用put方法和get()方法存取对象时,以及使用Iterator的next()方法获取元素时存在同样问题。JDK1.5中拖过引入泛型有效解决了这个问题。JDK1.5中已经改写了集合框架中所有的接口和类,增加了对泛型的支持,也就是泛型集合。

背景示例

public static void main(String[] args) {

        ArrayList arrayList =new ArrayList();

        arrayList.add("jack");
        arrayList.add(true);

        for (int i = 0; i < arrayList.size(); i++) {

            Object o = arrayList.get(i);
            String s= (String) o;
            System.out.println(s);

        }

    }
    
    运行结果:报出ClassCastException
    

​ 使用泛型集合在创建集合对象时指定集合中元素的类型,从集合中取出元素时无需进行强制类型转换,并且如果把非指定集合中元素的类型,从集合中取出元素时无需进行强制类型转换,并且如果非指定类型对象放入集合,会出现编译错误。

​ List和ArrayList的泛型形式是List和ArrayList,ArrayList与ArrayList类常用方法基本一样,示例11演示了List和ArrayList的用法。
示例11
​ 使用ArrayList的泛型形式改进示例2
​ 实现步骤如下:

public static void main(String[] args) {


        //创建一个新闻标题对象,
        NewTitle car = new NewTitle(1, "汽车", "管理员");
        NewTitle test = new NewTitle(2, "高考", "管理员");


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

        list.add(car);
        list.add(test);


        Iterator<NewTitle> iterator = list.iterator();
        while (iterator.hasNext()){

            NewTitle newTitle = iterator.next();
            System.out.println("新闻的标题的名称:"+newTitle.getName());
        }


    }

​ 分析:

​ 示例11中通过指定了ArrayList中元素的类型,代码中指定了ArrayList中只能添加NewTitle类型的数据,如果添加其他类型数据,将会出现编译错误,ArrayList中只能添加NewTitle类型的数据,如果添加到集合中后不再转换为Object类型,这在一定程度上保证了代码安全性,并且数据添加到集合后不再转为Object类型,保存的是指定的数据类型,所以在集合中获取数据时也再不需要进行强制类型转换。

​ 同样的,Map与HashMap也它们的泛型形式,如:Map<K,V>和HashMap<K,V>。因为他们的每个元素都包含两个部分,即key,和value,所以,在应用泛型时,要同时指定key的类型和value的类型,K表示key的类型,V表示value类型。

​ HashMap<K,V>操作数据的方法与HashMap基本一样。
示例12:

​ 使用HashMap的泛型形式改进示例7

 public static void main(String[] args) {
        //创建学员对象
        Student student1 =new Student("李明","男");
        Student student2 =new Student("刘丽","女");


        HashMap<String, Student> map = new HashMap<>();

        map.put("Jack",student1);
        map.put("Rose",student2);

        for (String key:map.keySet()){

            System.out.println(key);
        }

        for (Student student:map.values()){

            System.out.println(student.getName()+",性别是"+student.getSex());
        }
    }

​ 分析:

​ 通过<String,Student>指定了Map集合的数据类型,在使用put()方法存储数据时,Map集合的key必须为String类型,value必须为Student类型的数据,而在遍历键集的for循环中,变量key的类型不再是Object,而是String;在遍历值集的for循环中,变量value类型不再是Object,而是Student,同样,Map.get(key)得到的值也是Student类型数据,不再需要进行强制类型转换。

​ 当然,其他的集合类,如前面讲到的LinkedList、HashSet等都有自己的泛型形式,用法和ArrayLsit、HashMap的泛型形式类似。

​ 泛型使集合的使用更方便,也提升了安全:
​ 1.存储数据时进行严格类型检查,确保只有合适类型的对象才能存储在集合中。
​ 2.从集合中检索对象时,减少了强制类型转换。

3.深入泛型

​ 在集合中是使用泛型只是泛型多种应用的一种,在接口、类、方法等方面也有着泛型的广泛应用。

泛型的本质就是参数化类型,参数化类型的重要性在于允许创建一些类、接口和方法,其所操作的数据类型被定义为参数,可以在真正使用时指定具体类型。

​ 1.参数化类型:参数化类型包含一个类或者接口,以及实际的类型参数列表

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

//ArrayList<String>这个就是参数化类型,ArrayList就是原生类型

​ 参数类型包含接口的:就是指参数化类型中是接口

​ 2.类型变量:是一种非限定性标识符,用来指定类、接口或者方法的类型

​ 比如常用:T(Java的类型)、E(表示元素)、K、V

泛型类

​ 泛型类简单的说就是一个或者多个类型参数的类

​ 定义泛型类的语法格式如下:

访问修饰符 class className

​ TypeList表示类型参数列表,每个类型变量之间以逗号分隔。
​ 例如:

*  T表示类型的形参
 *      T是用于创建对象时来指定具体的数据类型呢
 *      T是由外部使用类时来指定的
 */
public class Generic<T> {
    private T key;

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }

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

    @Override
    public String toString() {
        return "Generic{" +
                "key=" + key +
                '}';
    }
}

​ 创建泛型类实例的语法格式如下:

​ new className(argList)

​ a.TypeList表示定义的类型参数列表,每个类型变量之间以逗号分隔。(JDK1.7后可以省略)

​ b.argList表示实际传递的类型参数列表,每个类型变量之间同样以逗号隔开

从泛型类派生子类

​ 子类也是泛型类,子类和父类的泛型类型要一致

class ChildGeneric<T> extends Generic<T>

泛型接口

​ 语法格式:

 interface 接口名称 <泛型标识,泛型标识,...>{
   
   泛型标识 方法名称();
 }

​ 泛型接口的使用:

​ 1.如果实现类不是泛型类,接口要明确数据类型。
2.如果实现类也是泛型类,实现类和接口的泛型类型要一致。

泛型方法

​ 泛型类,是在实例化类的时候指明泛型

​ 泛型方法,是在调用方法的时候指明泛型的具体类型

​ 语法:

​ 修饰符<T,E,…> 返回值类型 方法名(形参列表){

​ 方法体;

​ }

​ 1).public与返回值中间非常重要,可以理解为声明此方法为泛型方法

​ 2).只有声明的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法

​ 3).标明该方法将使用泛型T,此时才可以在方法中使用泛型类型T

​ 4).与泛型的定义一样,此处的可以随便写为任意标识,常见的如T,E,K,V 等形式的参数常用于表示泛 型
​ 5).泛型类中的普通的成员方法不支持静态关键字static修饰,泛型方法支持。

​ 泛型方法的可变参数:

​ 语法格式:

public <E> void print(E...e){
  
  		for(E e1:e){
          System.out.println(e);
  		}
}

泛型方法的总结:

​ 泛型方法使方法独立于类而产生变化。

​ 如果使用static方法使用泛型能力,就必须使其称为泛型方法。

类型通配符

类型通配符一般使用“?”代替具体的类型是实参,所以类型通配符是类型实参,而不是类型的形参。

通配符的上限
​ 语法格式:

/接口 <? entends 实参类型>

​ 要求该泛型的类型,只能是实参类型,或实参类型的子类型。

类型通配符的下限

​ 语法:

/接口 <? super 实参类型>

​ 要求该泛型的类型,只能是实参类型,或实参类型的父类类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张天靖09

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值