导语:
最近学习了集合,认为有必要做一下总结。希望能够帮助自己的同时,帮助到更多人。本文内容较长,如有错误,还请指出,万分感激。
1.Java 集合类的基本概念
在编程中,常常需要集中存放多个数据。从传统意义上讲,数组是我们的一个很好的选择,前提是我们事先已经明确知道我们将要保存的对象的数量。一旦在数组初始化时指定了这个数组长度,这个数组长度就是不可变的,如果我们需要保存一个可以动态增长的数据(在编译时无法确定具体的数量),java的集合类就是一个很好的设计方案了。
集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。
数组可以存放基本数据类型,存放引用数据类型。但是集合只能存放引用数据类型。但是我们学习过包装类,所以可以自动装箱,把基本数据类型直接转化为包装类存入集合中。
Java容器类类库的用途是"保存对象",并将其划分为两个不同的概念:Collection 和 Map。Collection和Map的区别在于容器中每个位置保存的元素个数:
- Collection 每个位置只能保存一个元素(对象)
- Map 保存的是"键值对",就像一个小型数据库。我们可以通过"键"找到该键对应的"值"
先把这两个方面的结构图放下面,再详细来说一下这两个方面。
2.Collection 接口
Collection 是一个接口,不能创建对象,只能创建他的实现类。
Collection 中有很多方法,在这儿只看其中典型的方法:
- 增加:add(E e) addAll(Collection extends E> c) ;
- 删除:clear() remove(Object o) removeAll(Collection> c);
- 查看:contains(Object o) containsAll(Collection> c) isEmpty()
- iterator() retainAll(Collection> c) size()
2.1 利用 Collection 接口创建 ArrayList 类:
Collection col=new ArrayList();
System.out.println(col.isEmpty());//true
System.out.println(col.size());//0
col.add(12);//我这里放入的不是int类型的12,而是自动装箱 等效于 col.add(new Integer(12));
col.add(7);
col.add(9);
col.add(12);
col.add(19);
System.out.println(col);//[12, 7, 9, 12, 19]
System.out.println(col.isEmpty()); //false
System.out.println(col.size()); //5
注意:例如 col.add(12); 放入的不是 int 类型的12,而是自动装箱,等效于 col.add(new Integer(12));
Collection col2=new ArrayList();
col2.add(11);
col2.add(22);
col2.add(33);
col2.add(44);
System.out.println(col2);//[11, 22, 33, 44]
@col.addAll(col2);
System.out.println(col);//[12, 7, 9, 12, 19, 11, 22, 33, 44]
System.out.println(col2);//[11, 22, 33, 44]
这段代码中的addAll() 方法(标*)相当将 col2 里面的全部内容放到了 col 里面。
@System.out.println(col.retainAll(col2));//这个方法做了两件事:(1)返回true/false;(2)同时对集合进行处理。
System.out.println(col); //[]
System.out.println(col2); //[11, 22, 33, 44]
retainAll() 方法(标@):对 col 取 col 与 col2 的交集。(注意:是只对 col 操作,不涉及 col2 !!!)
2.2 利用 List 接口创建 ArrayList 类:
下面只讲几个比较常用的方法,其他的很好理解,不再赘述,还请谅解。
List li=new ArrayList();
li.add(12);
li.add(3);
li.add(36);
li.add(24);
li.add(12);
li.add(9);
System.out.println(li);//[12, 36, 24, 12, 9]
@li.add(2,53);
System.out.println(li);//[12, 3, 53, 36, 24, 12, 9]
add(int index, E element) 方法(标@):相当于在索引为 index 的位置插入元素 element,注意是插入,不是取代!
li.remove(3);//首要还是当做索引来看
System.out.println(li);//[12, 3, 53, 24, 12, 9]
li 中虽然有元素3,但是 remove 方法还是把 3 当作索引来看。
li.set(2, 66);
System.out.println(li);//[12, 3, 66, 24, 12, 9]
set(int index, E element)方法:将索引为 index 的位置上的内容替换为 element。
System.out.println(li.indexOf(12));//获取第一个元素12对应的索引、下标
indexOf(Object o)方法:得到索引为 o 的位置上的内容。
2.3 List 集合遍历
说明:与索引有关的遍历都是在 List 接口下的。
List 集合遍历有三种方式
第一种:foreach(代码接上面)
//1.foreach
for(Object o:li){
System.out.print(o+"\t");
}
第二种:迭代器
介绍一下迭代器接口:Interface Iterable
迭代器接口,这是Collection类的父接口。实现这个Iterable接口的对象允许使用foreach进行遍历,也就是说,所有的Collection集合对象都具有"foreach可遍历性"。这个Iterable接口只有一个方法: iterator()。它返回一个代表当前集合对象的泛型
//2.迭代器
Iterator it = li.iterator();
while(it.hasNext()){
System.out.print(it.next()+"\t");
}
第三种:普通 for 循环
//3.普通for循环
for(int i=0;i<=li.size()-1;i++){
System.out.print(li.get(i)+"\t");
}
补充一个知识点:在 ArrayList 类中放入引用数据类型。代码如下(引用数据类型自己可以随便建,不要在意我的代码中的引用数据类型哈):
public static void main(String[] args) {
ArrayList al=new ArrayList();
Person p=new Person("kelsey", 18, 165, Gender.女);
al.add(p);
System.out.println(al);
System.out.println("================================");
al.add(new Person("Oliver",19,182,Gender.男));
al.add(new Person("lay",18,160,Gender.女));
System.out.println(al);
System.out.println("=================================");
//遍历;
//1.普通 for 循环;
for (int i = 0; i < al.size(); i++) {
//Person p1=(Person)al.get(i);
System.out.println(al.get(i));
}
//2.foreach
for (Object o : al) {
System.out.println(o);
}
//3.迭代器;
Iterator it = al.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
3.泛型
简单来说,泛型就是规定你这个集合中只能存入这个类型的数据。
泛型的格式:<> ,那么为啥用<>呢? --- 因为没办法。。。(凡是成对的符号都被占用了,我能怎么办,我也很绝望)
代码如下:
ArrayList
加上
3.1 泛型类
public class FanXing{
//我现在定义了一个普通的类。这个类的名字叫:FanXing
//如何变成泛型类呢?在后面加泛型,泛型格式:<> 里面的字母 就是代表一个未知的类型。这个字母你自己定义。
//这个类型AA在你定义的时候,是不确定的,那啥时候确定??在创建对象的时候。
}
创建泛型对象:
public static void main(String[] args) {
//创建一个普通的类的对象:FanXing ---出现黄色警告 ,因为是泛型类 但是你把它当做普通的类使用的。
FanXing fx=new FanXing();
List li=new ArrayList();
//定义泛型类:
FanXing fx02=new FanXing();
FanXing fx03=new FanXing();
FanXing fx04=new FanXing();
ArrayList al=new ArrayList();
ArrayList al2=new ArrayList();
ArrayList al3=new ArrayList();
}
3.2 泛型方法
public class FanXing02{//我现在定义了一个泛型类:名字是FanXing02;后面的AA类型在创建对象的时候确定。
//普通方法
public void a(){}
//方法的参数是AA---AA的类型是在创建对象的时候就已经确定了。
public void b(AA a){}
//方法的参数是BB ---BB在创建对象的时候并没有确定,而是在方法调用的时候确定的。
public void c(BB b){}
//静态方法1----不可以。static修饰的方法,先于对象存在的,此时没有AA这个类型。
//public static void d(AA a){}
//静态方法2-----可以 :因为BB只要你不调用,就是不确定的,所以随你加不加static
public static void e(BB b){}
//可变参数,内部当做数组来处理
public Q[] f(Q...q){
//foreach处理数组。
for(Q a:q){
System.out.println(a);
}
return q;
}
}
class Demo{
public static void main(String[] args) {
FanXing02 fx=new FanXing02();
fx.a();
fx.b("java");
//这个方法 解决了参数个数相同下的方法重载的问题。
fx.c("java");
fx.c(12);
fx.c(new Person(18, 180.8));
FanXing02.e("java");
//这个方法 解决了参数个数不同下的方法重载的问题。
fx.f(12,"java");
fx.f("html");
fx.f(23,"888",new Person(18, 180.8));
}
}
3.3 泛型接口
public interface FanXing03 {
//FanXing03是一个普通的接口,名字是:FanXing03.如何变成泛型接口:在后面加泛型即可
}
class A implements FanXing03{//实现接口的时候,没有泛型
}
class B implements FanXing03{//在实现接口的时候,泛型种类确定--String
}
class c implements FanXing03{//在实现接口的时候,泛型种类不确定。
}
3.4 泛型的高级应用
3.4.1 泛型的上限
public class Test {
public static void main(String[] args) {
ArrayList al1=new ArrayList();
al1.add(new Person("lili", 18));
al1.add(new Person("nana", 19));
al1.add(new Person("feifei", 15));
al1.add(new Person("sisi", 17));
bianLi(al1);
ArrayList al2=new ArrayList();
al2.add(new Student(180.0));
al2.add(new Student(170.4));
al2.add(new Student(169.6));
al2.add(new Student(159.7));
bianLi(al2);
}
public static void bianLi(ArrayList extends Person> al){//只要是Person的子类或者Person 可以传入
for (Object p : al) {
System.out.println(p);
}
}
}
3.4.1 泛型的上限
public static void bianLi(ArrayList super Person> al){//只要是Person的父类或者Person 可以传入
}
4. LinkedList
implements List
4.1 iterator()方法,Iterator接口,Iterable接口区别
上文说过, 实现Iterable接口的对象允许使用foreach进行遍历。
4.2 ConcurrentModificationException
ConcurrentModificationException 称为并发修改异常。
- 异常产生的原因
迭代器是依赖于集合而存在的,在判断成功后,集合的中新添加了元素,而迭代器却不知道,所以就报错了,这个错叫并发修改异常。
简单描述就是:迭代器遍历元素的时候,通过集合是不能修改元素的。 - 解决方法
(1)迭代器迭代元素,迭代器修改元素;
(2)集合遍历元素,集合修改元素(普通for)。
在这里主要讲一下第一种解决方法:
ArrayList al=new ArrayList();
al.add("javase");
al.add("html");
al.add("css");
al.add("js");
ListIterator li = al.listIterator();
while(li.hasNext()){
if(li.next().equals("javase")){
li.add("oracle");
}
}
System.out.println(li.hasNext());
5. 子接口 set
5.1 HashSet
5.1.1 在集合中存放 Integer 类型的数据:
public class Test {
public static void main(String[] args) {
Set set=new HashSet();
set.add(12);
set.add(8);
set.add(32);
set.add(12);
set.add(23);
set.add(19);
System.out.println(set);//[19, 32, 23, 8, 12] ---唯一,无序(没有按照输入顺序进行输出)
System.out.println(set.size());//5
//两种遍历方式 :foreach,迭代器。(参照上面的自己试着完成)
}
}
5.1.2 在集合中存放 String 类型的数据:
public class Test02 {
public static void main(String[] args) {
Set set=new HashSet();
set.add("java");
set.add("html");
set.add("js");
set.add("java");
set.add("php");
set.add("ios");
System.out.println(set); //[ios, php, js, html, java]---唯一,无序。(没有按照输入顺序进行输出)
System.out.println(set.size()); //5
}
}
5.1.3 在集合中存放引用数据类型的数据:
代码中的引用数据类型自己随便定义,只要正确就行。
public class Test03 {
public static void main(String[] args) {
Set set=new HashSet();
set.add(new Student("lili", 18));
set.add(new Student("nana", 17));
set.add(new Student("lulu", 19));
set.add(new Student("lili", 18));
set.add(new Student("feifei", 18));
set.add(new Student("sisi", 16));
System.out.println(set);//[Student [name=feifei, age=18], Student [name=lili, age=18], Student [name=lulu, age=19], Student [name=lili, age=18], Student [name=sisi, age=16], Student [name=nana, age=17]]
System.out.println(set.size());// 6
}
}
在这段代码中大家可以发现重复的数据仍然放进去了。这是为啥呢,不是说 set 是唯一的吗?
那么就要了解 HashSet 底层原理了:
- 如果看Integer 和 String 的源码(看源码:按住Ctrl,同时鼠标点击Integer 或者 String),就会发现它们都含有 hashCode() 和 equals() 方法,HashSet 会调用这两个,所以不论是放入 Integer 类型,还是放入 String 类型,结果都会是唯一的。但是放入的引用数据类型里却没有这两种方法。
- 解决方法:引用数据类型里重写hashCode和equals方法。
5.2 LinkedHashSet 类
LinkedHashSet 集合是唯一,有序的(按照输入输出的顺序)。
public static void main(String[] args) {
Set set=new LinkedHashSet<>();
set.add(12);
set.add(8);
set.add(32);
set.add(12);
set.add(23);
set.add(19);
System.out.println(set);//[12, 8, 32, 23, 19]
System.out.println(set.size());//5
}
5.3 TreeSet
5.3.1 放入Integer类型数据:
public class Test {
public static void main(String[] args) {
TreeSet ts=new TreeSet();
System.out.println(ts.add(33));;
ts.add(16);
ts.add(23);
ts.add(19);
ts.add(5);
System.out.println(ts.add(33));;
ts.add(42);
System.out.println(ts);//[5, 16, 19, 23, 33, 42]
System.out.println(ts.size());//6
//唯一,有序(按照从小到大的顺序) 无序(没有按照输入顺序进行输出)
}
}
- TreeSet 原理
5.3.2 放入String类型数据:
public class Test2 {
public static void main(String[] args) {
TreeSet ts=new TreeSet();
ts.add("banana");
ts.add("apple");
ts.add("demo");
ts.add("excuse me?");
ts.add("apple");
ts.add("coco");
ts.add("final");
System.out.println(ts);// [apple, banana, coco, demo, excuse me?, final]
System.out.println(ts.size());// 6
}
}
5.3.3 放入自定义 引用数据类型:
- 原因:
实际上,Integer,String的底层全部都实现了Comparable接口。实现了compareTo方法。这个方法返回int类型的数据。
- 解决:
实现Comparable接口,重写compareTo方法。
方式1:内部比较器:
public class Person implements Comparable{
String name;
int age;
double height;
public Person(String name, int age, double height) {
super();
this.name = name;
this.age = age;
this.height = height;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", height=" + height
+ "]";
}
@Override
public int compareTo(Object o) {
//按照年龄排序
//Person p=(Person)o;
//return this.age-p.age;
//按照身高排序
//Person p=(Person)o;
//return (int)(this.height-p.height);这种不行
//return -(((Double)this.height).compareTo((Double)p.height));
//按照姓名排序
Person p=(Person)o;
return this.name.compareTo(p.name);
}
}
方式2:外部比较器:
public class Person {
String name;
int age;
double height;
public Person(String name, int age, double height) {
super();
this.name = name;
this.age = age;
this.height = height;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", height=" + height
+ "]";
}
}
//按照年龄
class BiJiao01 implements Comparator{
@Override
public int compare(Object o1, Object o2) {
Person p1=(Person)o1;
Person p2=(Person)o2;
return p1.age-p2.age;
}
}
//按照姓名比较
class BiJiao02 implements Comparator{
@Override
public int compare(Object o1, Object o2) {
Person p1=(Person)o1;
Person p2=(Person)o2;
return p1.name.compareTo(p2.name);
}
}
那么这哪种比较器好?? ---- 外部
因为这样耦合性低,代码扩展性好,你要加比较器,或者删比较器,对其余的代码影响最小。
6. Map 接口
废话不说,直接上图。
- Map 的特点:Map用于保存具有"映射关系"的数据,因此Map集合里保存着两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的value。key和value都可以是任何引用类型的数据。Map的key不允许重复。
- Map的这些实现类和子接口中key集的存储形式和Set集合完全相同(即key不能重复)。
- Map的这些实现类和子接口中value集的存储形式和List非常类似(即value可以重复、根据索引来查找)。
6.1 HashMap 类
public static void main(String[] args) {
Map m=new HashMap();
m.put(11, "children");
m.put(33, "fine");
m.put(23, "good");
m.put(88, "best");
1 System.out.println(m.put(77, "better"));//null
System.out.println(m);//{33=fine, 23=good, 77=better, 11=children, 88=best}
System.out.println(m.size());//5
//m.remove(11);
System.out.println("==========================");
System.out.println(m);//{33=fine, 23=good, 77=better, 11=children, 88=best}
System.out.println(m.containsKey(3));//false
System.out.println(m.containsValue("good"));//true
System.out.println("============遍历1==============");
//对所有 key进行遍历:
Set ks = m.keySet();
for(Integer i:ks){
System.out.print(i+"\t");//33 23 77 11 88
}
System.out.println();
//对所有的value进行遍历:
for(Integer i:ks){
System.out.print(m.get(i)+"\t");//fine good better children best
}
System.out.println();
System.out.println("============遍历2==============");
//对所有的value进行遍历
Collection v = m.values();
for(String str:v){
System.out.print(str+"\t");//fine good better children best
}
System.out.println();
System.out.println("============遍历3==============");
for(Iterator it=v.iterator();it.hasNext();){
System.out.print(it.next()+"\t");//fine good better children best
}
System.out.println();
System.out.println("============遍历4==============");
Set> entrySet = m.entrySet();
2 for (Entry entry : entrySet) {
System.out.print(entry+" ");//33=fine 23=good 77=better 11=children 88=best
}
System.out.println();
System.out.println("==============================");
System.out.print(m.get(33));//fine
}
标 1 处,put(K key,V value) 方法:(1)将指定的值与此映射中的指定键关联(可选操作)。如果此映射以前包含一个该键的映射关系,则用指定值替换旧值。(2)以前与 key 关联的值,如果没有针对 key 的映射关系,则返回 null。
标 2 处,entrySet()方法:此映射中包含的映射关系的 set 视图。
6.2 TreeMap
之前的集合都是没有按照从小到大的这种有序进行输出的。
现在我想按照有序输出,用TreeMap。
这里还是主要讲述当 Key 值为引用数据类型时:
public class Test {
public static void main(String[] args) {
TreeMap tm=new TreeMap();
tm.put(new Person("banana", 18, 170.8), "学生1");
tm.put(new Person("coco", 13, 160.8), "学生2");
tm.put(new Person("apple", 17, 175.8), "学生3");
tm.put(new Person("banana", 18, 170.8), "学生4");
tm.put(new Person("demo", 16, 150.8), "学生5");
tm.put(new Person("excuse me ", 21, 180.8), "学生6");
System.out.println(tm);//{Person [name=coco, age=13, height=160.8]=学生2, Person [name=demo, age=16, height=150.8]=学生5, Person [name=apple, age=17, height=175.8]=学生3, Person [name=banana, age=18, height=170.8]=学生4, Person [name=excuse me , age=21, height=180.8]=学生6}
System.out.println(tm.size());//5
}
}
public class Person implements Comparable {
String name;
int age;
double height;
public Person(String name, int age, double height) {
super();
this.name = name;
this.age = age;
this.height = height;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", height=" + height
+ "]";
}
@Override
public int compareTo(Object o) {
Person p=(Person)o;
return this.age-p.age;
}
}
此处比较的是 age 属性,可以看出来,TreeMap 是唯一,无序的(没有按照输入输出的顺序,按照大小顺序来输出的)
总结:
- Collection 接口下的所有实现类,都可以用迭代器进行遍历。
- HashSet 类是唯一,无序,没有按照输入顺序进行输出;TreeSet 类是唯一,有序(按照从小到大的顺序) 无序(没有按照输入顺序进行输出);LinkedHashSet 类是唯一,有序。
- ArrayList 类是不唯一,有序,按照输入顺序进行输出;LinkedList 类是不唯一,有序,按照输入顺序进行输出。
- HashMap 类是按照key值唯一、无序的;LinkedHashMap 类 是唯一,有序,按照输入输出顺序进行输出;TreeMap 类是唯一,有序(按照从小到大)。