Java 中的集合

导语:

最近学习了集合,认为有必要做一下总结。希望能够帮助自己的同时,帮助到更多人。本文内容较长,如有错误,还请指出,万分感激。

1.Java 集合类的基本概念

在编程中,常常需要集中存放多个数据。从传统意义上讲,数组是我们的一个很好的选择,前提是我们事先已经明确知道我们将要保存的对象的数量。一旦在数组初始化时指定了这个数组长度,这个数组长度就是不可变的,如果我们需要保存一个可以动态增长的数据(在编译时无法确定具体的数量),java的集合类就是一个很好的设计方案了。

集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。

数组可以存放基本数据类型,存放引用数据类型。但是集合只能存放引用数据类型。但是我们学习过包装类,所以可以自动装箱,把基本数据类型直接转化为包装类存入集合中。

Java容器类类库的用途是"保存对象",并将其划分为两个不同的概念:Collection 和 Map。Collection和Map的区别在于容器中每个位置保存的元素个数:

  1. Collection 每个位置只能保存一个元素(对象)
  2. Map 保存的是"键值对",就像一个小型数据库。我们可以通过"键"找到该键对应的"值"

先把这两个方面的结构图放下面,再详细来说一下这两个方面。

Java 中的集合_第1张图片
Collection 详细结构图
Java 中的集合_第2张图片
Map 详细结构图

2.Collection 接口

Collection 是一个接口,不能创建对象,只能创建他的实现类。
Collection 中有很多方法,在这儿只看其中典型的方法:

  • 增加:add(E e) addAll(Collection 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 al=new ArrayList();
加上以后,只能放进 String 类型的变量。

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 al){//只要是Person的子类或者Person 可以传入
        for (Object p : al) {
            System.out.println(p);
        }
    }
}
3.4.1 泛型的上限
public static void bianLi(ArrayList al){//只要是Person的父类或者Person 可以传入

}

4. LinkedList

implements List,实现List接口,能对它进行队列操作,即可以根据索引来随机访问集合中的元素。


Java 中的集合_第3张图片
LinkedList 原理.png

4.1 iterator()方法,Iterator接口,Iterable接口区别

Java 中的集合_第4张图片
Iterable 接口1.png

Java 中的集合_第5张图片
Iterable 接口2.png

上文说过, 实现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 底层原理了:

Java 中的集合_第6张图片
HashSet 底层原理.png
  • 如果看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 原理
Java 中的集合_第7张图片
TreeSet 原理.png
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 放入自定义 引用数据类型:
Java 中的集合_第8张图片
LinkedList 原理.png
  • 原因:
    实际上,Integer,String的底层全部都实现了Comparable接口。实现了compareTo方法。这个方法返回int类型的数据。
Java 中的集合_第9张图片
Integer 底层原理.png

Java 中的集合_第10张图片
String 底层原理.png
  • 解决:
    实现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 接口

废话不说,直接上图。

Java 中的集合_第11张图片
Map 详细结构图.png
  • 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 类是唯一,有序(按照从小到大)。

你可能感兴趣的:(Java 中的集合)