集合提供了一种存储空间可变的模型,对于数据的容量可以随时发生改变,前面提到的ArrayLIst就是集合的一种典型例子。集合的整体框架如下:(其中左侧为单列集合,右侧为多列集合)
2Collection系列 其实Collection是一个接口,List和Set都是继承了顶层的Collection接口,直接使用Collection需要重写其中的抽象方法。简单回顾一下接口与抽象类的区别,主要可以分为语法上的区别和设计上的区别:
(1)语法上:
1.抽象类可以实现抽象方法的一些细节,但是接口只能利用public abstract functionName()声明函数(也就是不能有方法体的意思);
2.抽象类中的成员变量可以有多种类型,但是接口中的成员变量必须是public static final类型;
3.接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4.一个类只能继承一个抽象类,而一个类却可以实现多个接口;
(2)设计上
抽象类主要是对一个抽象出来的一些列对象做一个整体上的描述,包括这一系列不同对象的属性和方法(或者说是行为)。接口则更偏向于规范对象的行为。二者都是为了提高代码的复用性。
下面主要总结一下Collection下面的具体类的常用使用方法。
2.1List系列其实主要的使用方法和之前提到的ArrayList一样,下面说一下中间报异常的代码:
public static void main(String[] args) {
List arrList=new ArrayList();
arrList.add(new Student("张三",16));
arrList.add(new Student("李四",18));
arrList.add(1,new Student("王五",18));
for(Student item:arrList){//这里希望删除名字为张三的学生信息
if(item.name.equals("张三")){
arrList.remove(item);
}
System.out.println("输出学生信息:"+item.name+","+item.age);
}
}
通过控制台提示,这里报了一个运行时异常需要我们去处理
其实这里使用的增强遍历语法本质是基于迭代器实现的,在迭代器的遍历中每次会使用next向下遍历,遍历过程中执行了一个checkForComodification方法比较了modCount(实际修改集合的次数)和expectedModCount(期望修改集合的次数)。其实这里异常检测是为了监听并发修改。
好,接下来再来复盘一下。正常情况下我们直接利用迭代器遍历会有一个next检测,如果只是遍历,没有进行相关的修改操作,那么对应的modCount和expectedModCount都不会改变,但是我们调用了remove方法删除元素,删除元素优先检测是否含有某个元素,找到了,于是调用函数fastRemove(),最终修改了modCount,导致在下一次遍历的时候出现的modCount!=expectedModCount的情况,于是就有了异常的抛出。
于是,又来思考一下,既然需要保持expectedModCount与modCount相同,那么对于一个remove或者add操作不是只对modCount++,那么expectedModCount没有同步变化。那么我们一开始就是用了add,接下来的遍历应该会直接报错才是。这是因为初始化的时候,有这么一个操作,修改了expectedModCount。
得到结论:add方法能够修改modCount,并且不修改expectedModCount,但是在使用迭代器的之后会重新初始化expectedModCount,因此不会报错。如果在使用迭代器中途修改modCount,会引起引起并发异常。
解决方法,跳过并发检测或者同步修改expectedModCount,显然后者不太现实,因为这样修改的话很多地方法容易被我们忽视。采用如下方法:
for(int i=0;i2.2迭代器 迭代器是集合里面的特有的遍历方式,使用方式如下:
Iteratorit=arrList.iterator(); while(it.hasNext()){ Student s=it.next(); System.out.println("输出学生成绩信息:"+s.name+","+s.age); } //hasNext()用来检测迭代之后时候还有元素 //next()用来获取下一个元素 补充说明一下使用iterator方法可以不用考虑并发修改,iterator里面的add方法存在修改expectedModCount的代码:
另外还有一种列表迭代器,列表迭代器继承了基础的iterator,并在其基础上增加了反向遍历的功能:
if(it.hasPrevious()) s2=it.previous(); //hasPrevious()用来检测当前是否有上一个元素 //previous()用来获取上一个元素2.3linkedList 基于链表设计的List,相比于ArrayList,linkedList能够更快的修改元素,除了一般的方法外,还有如下方法:
addFirst()//从头部添加元素 addLast()//从尾部添加元素 removeFisrt()//从尾部删除元素 removeFirst()//从头部删除元素 getFirst()//获取首部元素 getLast()//获取最后尾部元素2.4Set集合 相比于List集合,Set集合最大的特点就是利用存储的数据不会重复,在常用的函数上其实和List差不多,给出如下例子:
public static void main(String[] args) { Setstrlist=new HashSet<>(); strlist.add("hello"); strlist.add("world"); strlist.add("java"); strlist.add("world"); //注意HashSet里面没有add(index,value)方法 //同样的数据存储到集合中,后进入的数据将会被忽略 //HashSet里面的数据是随机存放的,没有顺序。 strlist.remove("hello"); System.out.println("检测是否含有某元素:"+strlist.contains("world")); for(String item:strlist){ System.out.println("输出数据:"+item); } } HashSet保证元素唯一的原理:当一个元素需要进行存储的时候,首先利用Object.hashCode()得到该对象的hash值。在存储数据的时候,首先会检测当前hash表是否被初始化。确保初始化之后,通过hash空间存储容量和hash值进行逻辑与运算,计算需要存储的对象的位置。如果得到的目标位置没有数据,那么直接存放。如果该位置有元素,首先需要判断hash值是否一致,如果hash值不一样则更改目标存储位置。如果还是一直则利用equals比较,如果还是一样的则放弃增加,否则添加元素。
除了HashSet外,还有一个TreeSet。TreeSet最大的特点就是能够最进入set里面的数据进行自然排序。但是自然排序对象只能处理基础的数据,对于自定义对象的排序,需要去手动实现Comparable接口,具体写法如下:
//Student类 public class Student implements Comparable参考文献{//注意这里的Comparable是一个泛型接口 String name=""; int score=0; public Student(String name, int score) { this.name = name; this.score = score; } public int compareTo(Student s) { return this.score-s.score!=0 ? (this.score-s.score) : this.name.compareTo(s.name); } } //demo类中使用 public static void main(String[] args) { TreeSet studentList=new TreeSet (); studentList.add(new Student("张三",100)); studentList.add(new Student("李四",90)); studentList.add(new Student("李六",90)); studentList.add(new Student("王五",95)); for (Student item : studentList) { System.out.println("输出数据:" + item.name+","+item.score); } } //结果: (1)java接口与对象区别



