Java基础 您所在的位置:网站首页 java集合框架是什么意思啊英文 Java基础

Java基础

2024-07-18 04:14| 来源: 网络整理| 查看: 265

文章目录 1.Collection2. List**2.1List集合概述和使用****2.2List集合特有方法****2.3并发修改异常****2.4 ListIterator:列表迭代器****2.5 数据结构**2.6 List集合子类 **3. Set****3.1 Set****3.2 哈希值****3.3 HashSet****3.4 哈希表****3.5 LinkedHashSet****3.6 TreeSet** 4. 泛型**4.1泛型概述****4.2泛型类****4.3类型通配符****4.4 可变参数** 5. Map6. Collections

1.Collection

1.1 集合知识

集合类的特点:提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变

1.2 集合的体系结构

在这里插入图片描述

1.3 Collection集合概述和使用

Collection集合概述

是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素

JDK不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现

创建Collection集合的对象

多态的方式具体的实现类ArrayList

1.4 Collection集合的遍历

lterator:迭代器,集合的专用遍历方式

lterator iterator():返回此集合中元素的迭代器,通过集合的iterator(方法得到迭代器是通过集合的iterator)方法得到的,所以我们说它是依赖于集合而存在的

lterator中的常用方法

E next():返回迭代中的下一个元素。用一个对应的E类来接收,可以处理数据

boolean hasNext():如果迭代具有更多元素,则返回true。用来循环获取集合里的元素

实例

public class TestIterator { public static void main(String[] args) { Collection collection = new ArrayList(); collection.add("java"); collection.add("hello"); collection.add("world"); //获得迭代器 Iterator it = collection.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } } 2. List 2.1List集合概述和使用

List集合概述

有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素, 并搜索列表中的元素与Set集合不同,列表通常允许重复的元素

List集合特点

有序:存储和取出的元素顺序一蚁可重复:存储的元素可以重复

迭代器和Collection一样

2.2List集合特有方法

(因为它有序,所以可以使用索引)

方法名说明void add(int index,E element)在此集合中的指定位置插入指定的元素E remove(int index)删除指定索引处的元素,返回被删除的元素E set(int index,Eelement)修改指定索引处的元素,返回被修改的元素E get(int index)返回指定索引处的元素

注意:操作的时候索引不要越界

List可以通过get方法(获得索引对应的元素)和size方法(循环结束条件)对其进行遍历

2.3并发修改异常

看这个例子,我们使用正确的思路写下了正确的代码,却抛出了异常:ConcurrentModificationException

public class ListDemo { public static void main(String[] args) { List list = new ArrayList(); list.add("java"); list.add("hello"); list.add("world"); //遍历方法一 Iterator it = list.iterator(); while (it.hasNext()) { String s = it.next(); if ("world".equals(s)) {//如果两个字符串相等 list.add("abc"); //抛出异常:ConcurrentModificationException } } /* 遍历方法二 for (int i = 0; i < len; i++) { String s = list.get(i); if ("world".equals(s)) {//如果两个字符串相等 list.add("abc"); //这里会抛出异常吗 } } */ //输出集合对象 System.out.println(list); } }

ConcurrentModificationException异常是如何产生的?

查看源码:

//什么情况下会抛出这个异常 final void checkForComodification() { if (modCount != expectedModCount) //当这两个不相等的时候 throw new ConcurrentModificationException(); } //这两个又是什么,继续翻源码 private class Itr implements Iterator { //这是实现迭代器接口的类里。我们使用的是迭代器方式遍历 int expectedModCount = modCount; //一个值在这里被定义 /* modCount:实际修改集合的次数 expectedModCount:预期修改集合的次数 */ 一开始他们是相等的,但是我们上面在上面使用迭代器遍历过程中让它进行了一次add操作,导致这两个值不等,所以抛出了异常。 ...

思考:

如果if条件不成立,程序仍然会抛出异常吗。

答案是不会,因为if条件不成立,那么if (modCount != expectedModCount) 也成立,就不会抛出异常

如果遍历方式改为使用第二种会抛出异常吗

答案是不会,因为get方法只有检测索引的范围是否越界,并没有做checkForComodification()方法。只有索引越界了才会抛出异常:IndexOutOfBoundsException。

总结:

产生原因

迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致增强for循环内部也使用了迭代器实现,他也可能抛出并发修改异常

解决方案

用for循环遍历,然后用集合对象做对应的操作即可 2.4 ListIterator:列表迭代器

通过List集合的listlterator)方法得到,所以说它是List集合特有的迭代器

用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置

Listlterator中的常用方法

E next():返回迭代中的下一个元素boolean hasNext():如果迭代具有更多元素,则返回trueE previous):返回列表中的上一个元素boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回truevoid add(Ee):将指定的元素插入列表

实例

import java.util.ArrayList; import java.util.List; import java.util.ListIterator; public class TestListItr { //列表迭代器,含有add方法,不会抛出并发修改异常 public static void main(String[] args) { List list = new ArrayList(); list.add("java"); list.add("hello"); list.add("world"); ListIterator listIt = list.listIterator(); //正序输出 while (listIt.hasNext()) { String s = listIt.next(); //主要是想说明,可以使用add if ("world".equals(s)) { listIt.add("llllll"); } System.out.println(s); } System.out.println("-----------"); //反序,基本不用 while (listIt.hasPrevious()) { String previous = listIt.previous(); System.out.println(previous); } /*运行结果 java hello world ----------- llllll world hello java */ } }

思考:为什么ListIterator使用add方法,不会抛出并发修改异常?

查看源码

//在调用 list.listIterator()方法的时候,返回的是一个ListItr。 public ListIterator listIterator() { return new ListItr(0); } //找到ListItr,查看ListItr的add方法, private class ListItr extends Itr implements ListIterator { ... ... public void add(E e) { checkForComodification(); try { int i = cursor; ArrayList.this.add(i, e); cursor = i + 1; lastRet = -1; expectedModCount = modCount; //发现在执行add方法,他也会将这两个值保持一致。所以在检测的时候不会出现异常。 } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } }

总结:

ListIterator使用add方法,不会抛出并发修改异常,因为它会在add方法中执行 expectedModCount = modCount;所以不会抛出并发修改异常

2.5 数据结构

栈(先进后出) 在这里插入图片描述

队列先进先出

在这里插入图片描述

数组

在这里插入图片描述

链表

在这里插入图片描述

2.6 List集合子类

ArrayList

底层数据结构是用大小可变的数组实现的,特点是查询快,增删慢

在这里插入图片描述

LinkedList

底层数据结构是链表,特点是查询慢,增删快。

在这里插入图片描述

3种遍历方式

迭代器普通for(用索引)增强for

LinkedList 相比ArrayList 多了几种常用的特有的方法

addFirstaddLastgetFirstgetLastpeekpoll… 3. Set 3.1 Set

概述:

继承了Collection接口一个不包含重复元素的 collection底层结构是哈希表

特点:

不包含重复元素的集合,可以当成数学里的集合来理解。没有带索引的方法,所以不能使用普通for循环遍历

实现类:

HashSet

对元素的迭代顺序不做保证

此实现不是同步的。

应该使用 Collections.synchronizedSet 方法来“包装” set。最好在创建时完成这一操作,以防止对该 set 进行意外的不同步访问:

Set s = Collections.synchronizedSet(new HashSet(...)); 3.2 哈希值

哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值

Object类中有一个方法可以获取对象的哈希值

public int hashCode):返回对象的哈希码值 class HashDemo { public static void main(String[] args) { //创建学生对象 Student s1 = new Student("林青霞", 30); //同一个对象多次调用hashCode()方法返回的哈希值是相同的 System.out.println(s1.hashCode()); //460141958 System.out.println(s1.hashCode()); //460141958 System.out.println("--------"); Student s2 = new Student("林青霞", 30); //默认情况下,不同对象的哈希值是不相同的 //但是我们可以重写hashCode方法,达到自己想要的结果。比如这里可以在Student类种重写 System.out.println(s2.hashCode()); //1163157884 } } class Student { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } //重写哈希值方法 @Override public int hashCode() { return 0; } } 在运行,发现三次都是返回0

对象的哈希值特点

同一个对象多次调用hashCode(方法返回的哈希值是相同的

默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同

3.3 HashSet

HashSet集合特点

底层数据结构是哈希表对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致没有带索引的方法,所以不能使用普通for循环遍历由于是Set集合,所以是不包含重复元素的集合

HashSet集合保证元素唯一性的源码分析

HashSet hashSet = new HashSet(); //添加元素 hashSet.add("java"); hashSet.add("hello"); hashSet.add("world"); hashSet.add("world"); //--------------------------- //源码部分: //跟进add方法,参数就是我们传进来的参数,比如E是String类型,e=hello public boolean add(E e) { return map.put(e, PRESENT)==null; } //hash(),该方法返回的是一个hash值 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } //跟进put,hash值与元素的hashCode相关 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } //跟进putVal,这里我们只需要观察前面这两个参数, final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { //元素为结点的数组,hash的一种实现 Node[] tab; Node p; int n, i; //为空,长度为0。意思是如果哈希表未初始化,就对其进行初始化 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //根据对象的哈希值计算对象的存储位置,如果该位置没有元素,就存储元素, if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else {//如果该位置由元素 Node e; K k; /* 存入的元素和之前的元素比较哈希值 如果哈希值不同,会继续向下执行,把元素添加到集合 如果哈希值相同,会调用对象的equals方法比较 如果返回false,会继续向下执行,把元素添加到集合 如果返回true,说明元素重复 */ if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))//equals方法比较 e = p;//说明元素重复,并没有将它添加到集合。 else if (p instanceof TreeNode) e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); else {//哈希值不同 for (int binCount = 0; ; ++binCount) { //把元素添加到集合 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }

流程图:

在这里插入图片描述

总结:

它是根据hashCode方法和equals方法来确认元素是否重复HashSet集合存储元素,要保证元素的唯一性,需要重写hashCode方法和equals方法 3.4 哈希表

JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组

JDK8以后,在长度比较长的时候,底层实现了优化

哈希表是如何保证元素的唯一性呢?

原理和上面差不多,举个例子说明:

将元素存储到哈希表中。

1.首先计算出每个元素的哈希值,

默认构造一个新的空 set,其底层 HashMap 实例的默认初始容量是 16。

把元素对应的哈希值存储到里面,将其对16取余,计算出自己存储的位置。

在这里插入图片描述

存储过程:

hello:其对应的哈希值计算后的需要存储的位置为2,2没有元素,直接存储

在这里插入图片描述

world: 2里有一个元素,有元素就需要和里面的元素进行比较,比较哈希值,哈希值不相同,world加入该位置。

在这里插入图片描述

java:2的位置有多个元素,首先与第一个比较,哈希值不同,与第二个比较哈希值也不同。要与这多个元素进行比较,哈希值都不相同,才加入该位置

world:与第一个比,哈希值相同,比较内容,内容也相同。所以这个不加入

通话:直接加入

重地:3的位置有元素,比较哈希值,哈希值相同,比较内容,内容不同,加入。

最后结构如图,使用的是存储结构是数组+链表的形式。

了解更多关于哈希表,一定要了解这些

在这里插入图片描述

3.5 LinkedHashSet

LinkedHashSet集合特点

哈希表和链表实现的Set接口,具有可预测的迭代次序由链表保证元素有序,也就是说元素的存储和取出顺序是一致的由哈希表保证元素唯一,也就是说没有重复的元素 3.6 TreeSet

TreeSet集合特点

元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法

TreeSet():根据其元素的自然排序进行排序

TreeSet(Comparator comparator):根据指定的比较器进行排序没有带索引的方法,所以不能使用普通for循环遍历

由于是Set集合,所以不包含重复元素的集合

自然排序 Comparable的使用

题目要求:

存储学生对象并遍历,创建TreeSet集合使用无参构造方法

按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

//学生类 public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } //测试类:主方法 public class DemoTreeSet { public static void main(String[] args) { //无参构造 TreeSet students = new TreeSet(); //添加元素 students.add(new Student("貂蝉", 23)); students.add(new Student("王昭君", 29)); students.add(new Student("西施", 18)); students.add(new Student("杨玉环", 20)); //直接运行,程序异常 Comparable比较器异常 //java.lang.ClassCastException: com.collection.Student cannot be cast to java.lang.Comparable //遍历集合 for (Student student : students) { System.out.println(student.getName()+" "+student.getAge()); } } }

出现问题,那就寻找解决问题的方法:

去jdk文档里看看Comparable接口

在这里插入图片描述

原因是我们没有让学生类自然排序,所以学生类需要实现这个接口,并重写他的自然比较方法

public class Student implements Comparable{ //实现接口 private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } //比较方法 @Override public int compareTo(Student o) { return 0;//注意这里返回值是0 } ... ... }

**运行程序:**我们添加的是四个学生,发现只输出了一个貂蝉。

在这里插入图片描述

原因在于我们在学生类的比较器中的返回值是0,集合中无元素时,第一个元素能加进来。而后的元素因为需要比较,而返回值是0,它会认为比较的这两个元素是同一个,所以添加失败。

那么我们将返回值改为1.

发现所有的元素都添加进来了。它是按照我们存储的顺序输出来的。

因为返回值是1,他会认为后面的元素会比前面的元素要大!所以应该添加在后面。

在这里插入图片描述

同理,如果改为-1,输出的是添加顺序的倒序。

那我们的要求是按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

先看按年龄排序

@Override public int compareTo(Student o) { return this.age-o.age;//this是当前的,o是前面的,如果是个正数,当前的会加在后面。 }

输出的是

在这里插入图片描述

没问题,都存储进来了,而且年龄是按照升序排序。

但是如果又多了一个学生

students.add(new Student("西门庆", 29));

他的年龄和王昭君的一样,那他是否能添加成功呢。

显然是不行的。因为我们只对年龄进行了比较。他们两个年龄一样,会认为这两个是相同的元素,添加失败。

完整的比较

@Override public int compareTo(Student o) { int num1 = this.age - o.age; int num2 = this.name.compareTo(o.name);//compareTo方法大于返回1,相等返回0,小于返回-1 return num1 == 0 ? num2:num1; }

运行结果

在这里插入图片描述

总结:

用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写 4. 泛型 4.1泛型概述

泛型:是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型它的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?

顾名思义,就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型

这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口

泛型定义格式:

:指定一种类型的格式。这里的类型可以看成是形参:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型

用例子来说明泛型的好处

public class GenericDemo { public static void main(String[] args) { Collection c = new ArrayList(); c.add("java"); c.add("hello"); c.add("world");//添加的是字符串类型 Iterator it = c.iterator(); while (it.hasNext()) { String s = (String) it.next(); //输出的时候希望转为对应的类型输出 System.out.println(s); } /* 成功输出了这几个字符串 java hello world */ } }

如果再添加一个元素

c.add(10);//自动装箱为Integer类型,并且编译器不会报错。

点击运行,发现抛出ClassCastException:类转换异常。

这是非常不友好的,需要程序员在运行程序的时候才抛出异常,这是运行时异常,我们希望它能在编译期的时候就抛出异常。所以这时候就用到泛型。

Collection c = new ArrayList(); c.add(10);//编译不通过 Iterator it = c.iterator(); while (it.hasNext()) { String s = it.next(); //同时也避免了强制转换 System.out.println(s); }

这样子我们就能将运行期间的异常提前到编译期来处理也避免了强制类型转换,这就是泛型带来的好处

4.2泛型类

泛型类的使用

//定义泛型类: class 类名 public class Generic { private T t;//类里面的 属性,类型为T public T getT() { return t; } public void setT(T t) { this.t = t; } } public class TestGeneric { public static void main(String[] args) { Generic generic = new Generic();//String类型 generic.setT("林志颖"); System.out.println(generic.getT());//输出林志颖 Generic generic1 = new Generic();//Integer类型 generic1.setT(22); System.out.println(generic1.getT());//输出22 } }

泛型类中这个它的类型是未知的,只有等被调用的时候传入来的类型才确定,可以看成一个参数,需要等被调用它才有意义,可以被多种类型调用。

泛型方法

public class GenericMethod { public static void main(String[] args) { Generic1 generic = new Generic1(); generic.show("周瑞发"); generic.show(20); generic.show('c'); /*输出: 周瑞发 20 c */ } } class Generic1{ public void show(T t){ System.out.println(t); } }

泛型接口

//接口 public interface Generic { void show(T t); } //实现类 public class GenericImpl implements Generic{ @Override public void show(T t) { System.out.println(t); } } //测试 public class GenericDemo { public static void main(String[] args) { GenericImpl generic = new GenericImpl(); generic.show("刘德华"); GenericImpl generic1 = new GenericImpl(); generic1.show(20); } } 4.3类型通配符

为了表示各种泛型List的父类,可以使用类型通配符

类型通配符:List:表示元素类型未知的List,它的元素可以匹配任何的类型这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中

如果说不希望List是任何泛型List的父类,只希望它代表某一类泛型List的父类,可以使用类型通配符的上限

类型通配符上限: 可以匹配任意类型 List list1 = new ArrayList();//编译通过 List list2 = new ArrayList();//编译通过 List list3 = new ArrayList();//编译通过 //



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有