集合
-
集合的概念
1、 集合是Java API所提供的一系列类,可以用于动态存放多个对象。–集合只能存对象
2、 集合与数组的不同在于,集合是大小可变的序列,而且元素类型可以不受限定,只要是引用类型
3、集合类全部支持泛型,是一种数据安全的用法。 -
集合框架图
Java的集合框架从整体上可以分为两大家族。
1、 Collection(接口)家族。该接口下的所有子孙均存储的是单一对象。
2、 Map(接口)家族。该接口下的所有子孙均存储的是key-value(键值对)形式的数据。
另外还有三个分支,均是为上述两大家族服务的。
1、 Iterator(迭代器)家族。主要用于遍历Colleciton接口的及其子类而设计。
2、 Comparator(比较器), 在集合中存储对象时候,用于对象之间的比较
Collecitons是工具类。注意该类名带个s,一般就表示工具类。里面提供了N多静态方法,来对Colleciton集合进行操作。
3.Connection接口
Collection接口-定义了存取对象的方法。有两个非常常用的子接口:
List接口:存放的元素有序且允许有重复的集合接口。
Set接口:存放的元素无序不包含重复的集合接口。
常用方法:
1、 int size(); 返回此collection中的元素数。
2、 boolean isEmpty(); 判断此collection中是否包含元素。
3、 boolean contains(Object obj); 判断此collection是否包含指定的元素。
4、 boolean contains(Collection c); 判断此collection是否包含指定collection中的所有元素。
5、 boolean add(Object element); 向此collection中添加元素。
6、 boolean addAll(Collection c); 将指定collection中的所有元素添加到此collection中
7、 boolean remove(Object element); 从此collection中移除指定的元素。
8、 boolean removeAll(Collection c); 移除此collection中那些也包含在指定collection中的所有元素。
9、 void clear(); 移除些collection中所有的元素。
10、 boolean retainAll(Collection c); 仅保留此collection中那些也包含在指定collection的元素。
11、 Iterator iterator(); 返回在此collection的元素上进行迭代的迭代器。
12、Object[] toArray(); 把此collection转成数组。
4.List接口
List相比Connection新增的几个使用方法;
List接口比Collection接口中新增的几个实用方法:
1、 public Object get(int index) 根据下标,返回列表中的元素
2、 public Object add(int index, Object element); 在列表的指定位置插入指定元素.将当前处于该位置的元素(如果有的话)和所有后续元素向右移动
3、 public Object set(int index, Object element) ; 用指定元素替换列表中指定位置的元素
4、 public Object remove(int index) 移除列表中指定位置的元素
注意:list集合中的元素的索引与数组中元素的索引一样,均是从0开始。
ArrayList集合
- List接口的实现类:ArrayList、LinkedList
- 分析ArrayList存储特点:
-
有序:按什么顺序存进去就按什么顺序取出来
-
存储方式:数组扩容
-
原理:存储第一个元素时,内部给定一个数组,初始为10个空间;当空间不够存时,进行扩容,将原先数组元素拷贝进来;再将新增元素添加到新扩容的空间中
- LinkedList存储方式:双向链表
-
原理:当存第一个元素时,创建一个节点,分三个区域,分别为prev,data,next。prev指向上一个节点, 初始为null,data存元素,next存下一个节点初始为null;当创建新节点时,新节点的prev指向上一个节点,上一个节点的 next指向下一个节点;以此类推,构成双向链表。
- ArrayList VS LinkedList
- 相同点:存储特点一致,都是List的实现类;都具有有序且允许重复的特点
- 不同点:存储原理不同
- 分场景进行效率PK(增删改查):
-
1.添加:向后追加、指定位置插入
-
向后追加:ArrayList稍高
-
指定位置连续插入:LinkedList高
-
2.删除:指定位置删除:LinkedList高
-
3.查找:就是定位:明显ArrayList块
-
4.修改:定位好,在修改:ArrayLis快
-
结论:ArrayList更常用,常用操作:向后追加和查找
5.迭代器iteratior
迭代器应用:
有两个方法:hasNext():判断是否有下一个,next():获取下一个元素
案例:迭代器遍历存自定义对象的集合
泛型:用于约束集合中存储的类型
引入泛型:遍历元素时,要获取本身的类型-强转,如果存储过其他类型的元素,遍历强转时会崩溃;
通过泛型处理:从源头上约束集合存储的类型,存储时不能存其他的类型。
优点:
1.简化集合的使用
2.增强代码的可读性和稳定性
问题:
1.为什么有了基本for和增强for,还需要有迭代器?
----性能上考虑 看本质:增强for就是迭代器
2.迭代器内部用什么方法进行迭代的?
----如果是ArrayList方式存,则用数组方式迭代
----如果是LinkedList方式存,则用链表方式迭代
List list = new ArrayList();
list.add(new Student(“zs”));
list.add(new Student(“ls”));
list.add(new Student(“ww”));
//通过限制存入集合元素类型为Student对象
// list.add(1); 存入其他类型对象强转对象时会报错
Iterator it = list.iterator();
while (it.hasNext()) {
//java.lang.Integer cannot be cast to com.qf.b_iterator.Student
//Student st = (Student)it.next();
System.out.println(it.next().getName());
}
JDK1.4以前不能使用泛型的弊端:
1、 装入集合的数据都会被当作Object对象来存放,从而失去了自己的实际类型。
2、 从集合中取出元素时,需要进行强制类型转换。效率低,容易产生错误。
从JDK1.5开始,sun公司推出了泛型来解决上述问题:
1、在定义一个集合时就指定集合存储的对象的数据类型
如:List list = new ArrayList<>();
2、存入数据时只能存入泛型指定的数据类型,如果存入其他数据类型,则会编译错误。
list.add(1); //编译错误!
3、从集合中取出元素时,无需转型了。
如:Student st = it.next();
6.Set接口
HashSet集合
Set:存储的元素无需且唯一
两个实现类:HashSet,TreeSet
注意:两个类都没有下标操作,所以不能用for循环
HashSet的基本操作、遍历
细节:HashSet的存储–》在内部由HashMap实现的
存储方式:hash算法
原理:当存储第一个元素时,内部给定一张hash表(数组),初始长度16;每个元素都有一个对应的hash值,根据hash值在hash表中可计算出位置,如果该位置为null则直接存储,否则将该元素与该位置的链表元素一一比较;如果找到相同的,则确定唯一性。
探究唯一性
package com.qf.d_hashset;
import java.util.HashSet;
import java.util.Set;
/**
-
验证原理:HashSet存自定义对象
-
目标:确定唯一性—(2个)
-
实际:打印三个对象–暂时不能确定唯一性–没有重写hashCode和equals
-
结论:如果想按自己的方式确定唯一性(只要属性值一致认为是唯一的),必须重写hashCode和equals
-
@author Zhouzilong
-
@date 2019年7月30日
*/
public class Test2 {
public static void main(String[] args) {
Set set = new HashSet();
set.add(new Teacher(“苍老师”));
set.add(new Teacher(“陈老师”));
set.add(new Teacher(“苍老师”));
System.out.println(set);Set<String> set2 = new HashSet<String>(); set2.add("苍老师"); set2.add("陈老师"); set2.add("苍老师"); System.out.println(set2);
}
}
重写hashCode方法和equals方法之前输出结果:
重复输出了,没有体现唯一性。因为查看底层实现发现按它的比较算法来说,虽然值是一样,但总体来看是不一样的,所以重复,但我们想要的是值一样就算重复,所以需要重写hashCode方法和equals方法
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Teacher other = (Teacher) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
重写之后:
TreeSet集合
TreeSet类存储特点:可排序,唯一
*
- 存储方式:二叉树算法
- 原理:存储第一个元素时,作为根;存第二个元素时,与根比较 ;如果比根小,
-
看左子树是否有元素,如果为null,直接存,否则,以左子树为根继续比较;
-
如果比根大,看右子树是否有元素,如果为null,直接存,否则以右子树为
-
根继续比较。
探究唯一性与有序性
package com.qf.e_treeset;
import java.util.Set;
import java.util.TreeSet;
/**
- 验证原理:
- TreeSet集合存储自定义对象,看是否能排序和确定唯一。
- 实验结果:会报错
- 方案1:
- 解决方案:存储的对象所在类实现Comparable接口
- 按自己的方式进行比较和确定唯一
- 将比较对象转为比较属性
- 如果是字符串则比较ASCII码
- @author Zhouzilong
- @date 2019年7月30日
*/
public class Test2 {
public static void main(String[] args) {
Set set = new TreeSet();
set.add(new Student(“zs”));
set.add(new Student(“ls”));
set.add(new Student(“zs”));
System.out.println(set);
}
}
直接运行,报错com.qf.e_treeset.Student cannot be cast to java.lang.Comparable
Student类无法被转型为Comparable类,说明底层实现中有转型的语句,查询后发现Comparable类为借口,为了能够实现转型,将Student类变为Comparable类的实现类,此时发现需要实现Comparable类的CompareTo方法,先不写方法体。继续看底层实现,发现如下代码
do {
parent = t;
//t为根节点
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
可以看出,调用compareTo方法用当前对象比较根节点对象,结果小于0放左子树,大于0放右子树,等于0则说明是相等的,直接return。之前重写compareTo方法的方法体正好没写,我们想比对的是值,那么怎么写合适呢?由于要比的值是字符串,当时马上就想到了比较值的字典大小,返回值小于0放左子树( t = t.left;),返回值大于0放右子树(t = t.right;),正好适合这里:
@Override
public int compareTo(Student o) {
return this.name.compareTo(o.name);
}
其中this.name代表调用这个方法的对象的name值,o.name代表对象参数的name值。
另外,用System.out.println(“set”);输出是默认从左子树开始输出,也就是从小到大输出,若调换o.name和this.name的位置,同样是从左子树开始输出,此时自然就为从大到小输出