第四章、第3节 集合

一、类集概述(重点)

​ Java中类集是对各种数据结构成熟的实现。

​ 类集中最大的几个常用操作父接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。
所有类集操作的接口或类都在java.util包中。

在这里插入图片描述

二、链表与二叉树

1、链表

​ 链表(Linked List):链表是由一组连续或非连续的内存结构(节点)组成,按特定的顺序链接在一起的抽象数据类型。

eg:

在这里插入图片描述

​ 补充:

​ 抽象数据类型(Abstract Data Type [ADT]):表示数学中抽象出来的一些操作的集合。
​ 内存结构:内存中的结构,如:struct、特殊内存块…等等之类。

​ 数组和链表的区别和优缺点:

​ 数组是一种连续存储线性结构,元素类型相同,大小相等。

​ 数组的优点:存取速度快。
​ 数组的缺点:
​ ①事先必须知道数组的长度;
​ ②插入删除数据很慢(效率低);
​ ③空间通常是有限制的;
​ ④需要大块连续的内存块;

​ 链表是离散存储线性结构。

​ 链表的优点:没有空间限制,插入删除元素很快。
​ 链表的缺点:存取速度慢。

//链表节点
class Node {
    Object data;	//存放数据
    Node next;		//指向下一个节点
}

链表常用的有3类:单链表、双向链表、循环链表。

2、二叉树

​ 二叉树是树的一种,每个节点最多可具有两个子树,即节点的度最大为2(节点度:节点所拥有的子树数)。

eg:

在这里插入图片描述

​ 二叉树的优点:查找方便(类二分查找级)

通常二叉树都是有序的,根节点向下分叉,存储数据与根节点作比较,比根节点小的存至左子节点,反之存至右子节点。在之后每存储一个数据就按这个存储顺序依次向下分叉。

  • 二叉树的遍历方式
    • 先序遍历:先访问根节点,然后访问左节点,最后访问右节点。(根->左->右)
    • 中序遍历:先访问左节点,然后访问根节点,最后访问右节点。(左->根->右)
    • 后序遍历:先访问左节点,然后访问右节点,最后访问根节点。(左->右->根)

三、常见数据结构

​ 数据存储常用结构有:栈、队列、数组、链表和红黑树。

1、栈

(stactk,又称堆栈):栈是限定仅在表尾进行插入和删除操作的线性表。通常把允许插入和删除的一端称为栈顶,另一端为栈底。

​ 采用该结构的集合,对元素的存取有如下的特点:
​ ① 先进后出,后进先出。
​ ② 栈中数据的出入口都在栈的顶端。

在这里插入图片描述

需要注意的是:

  • 压栈:存元素。
  • 弹栈:取元素。

2、队列

队列(Queue):队列是一种特殊的线性表,是运算受限的一种线性表,只允许在表的一端进行插入,而在另一端删除元素的线性表。队尾(rear)是允许插入的一端,队头(front)是允许删除的一端。

​ 采用该结构的集合,对元素的存取有如下的特点:
​ ① 先进先出,后进后出。
​ ② 队列的入口、出口各占一侧。

在这里插入图片描述

3、数组

数组(Array):是有序的元素序列,数组是内存中开辟一段连续的空间,并在此空间存放元素。

​ 采用此集合有如下特点:

​ ①查找元素快:通过索引,可以快速访问指定位置的元素。
​ ②增删元素慢。

4、链表

​ 略。

5、红黑树

红黑树(Red Black Tree):是一种自平衡二叉树。

​ 特点:

​ ① 尽量保持平衡,不会将数据只存在一端。
​ ② 速度特快,趋近于平衡,查找叶子元素最少和最多次数不多于二倍。

四、Collection接口(重点)

​ 在整个Java类集中Collection是最大的单值存储(每次存储只能存储一个对象)父接口,在开发中通常只用List接口、Set接口(因它俩继承了Collection接口(意味着继承了其所有方法))。

Collection接口定义如下:

public interface Collection extends Iterable

​ Collection接口常用方法如下:

在这里插入图片描述

1、List接口(重点)

​ 在整个集合中List是Collection的子接口,里面的所有内容都是允许重复的。

List子接口的定义如下:

public interface List extends Collection

​ List接口对Collection接口方法的扩充如下:

在这里插入图片描述

​ 常用的实现类:ArrayList(95%)、Vector(4%)、LinkedList(1%)。

1-1、ArrayList(重点)

​ ArrayList是List接口的子类。

ArrayList类的定义如下:

public class ArrayList extends AbstractList implements List,RandomAccess,Cloneable,Serializable

此类继承了AbstractList类。AbstractList是List接口的子类。AbstractList是个抽象类,适配器设计模式。

示例代码

import java.util.ArrayList;
import java.util.List;

public class _1_CollectionListArrayList {
    public static void main(String[] args) {
        List<String> all = new ArrayList<>();
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");	//向集合中插入一个元素
        all.remove("C");//从集合中删除一个对象
        all.remove(1);	//删除指定位置的内容
        //第一种打印方式
        for(int i = 0;i < all.size();i++){
            System.out.println(all.get(i));	//根据指定位置取出每一个元素,打印输出。
        }
        //第二种打印方式(增强for循环打印输出)
        for (String s : all) {
            System.out.println(s);
        }
    }
}
1-2、Vector(重点)

​ 与ArrayList一样,Vector本身也属于List接口的子类。

Vector类的定义如下:

public class Vector extends AbstractList implements List,RandomAccess,Cloneable,Serializable

此类与ArrayList类一样,都是AbstractList的子类。所以,此时的操作只要是List接口的子类就都按照List进行操作。

示例代码

import java.util.List;
import java.util.Vector;

public class _2_CollectionListVector {
    public static void main(String[] args) {
        List<String> all = new Vector<>();
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        all.remove("E");
        all.remove(3);
        for (String s : all) {
            System.out.println(s);
        }
    }
}
1-3、Vector类和ArrayList类的区别(重点)
No.区别点ArrayListVector
1时间是新的类,在JDK1.2之后推出的是旧的类,在JDK1.0的时候就定义了
2性能性能较高,是采用了异步处理性能较低,是采用了同步处理
3输出支持Iterator、ListIterator输出除了支持Iterator、ListIterator输出,还支持Enumeration输出
1-4、链表操作类:LinkedList(理解)

​ 此类的使用率非常低。

LinkedList类的定义如下:

public class LinkedList AbstractSequentialList implements List, Deque,Cloneable,Serializable

​ 此类继承了AbstractList,所以是List的子类。此类也是Queue接口的子类,Queue接口定义了如下方法:

在这里插入图片描述

2、Set接口(重点)

​ Set接口也是Collection的子接口,与List接口不同的是Set接口里面的内容是不允许重复的。

​ Set接口并没有对Collection接口进行扩充,基本上与Collection接口保持一致,此接口没有List接口中定义的get(int index)方法,因此无法使用循环输出。

​ 在此接口中有两个常用的子类:HashSet、TreeSet。

2-1、散列存放:HashSet(重点)

​ HashSet属于散列的存放类集,里面的内容是无序存放的。

示例代码1

import java.util.HashSet;
import java.util.Set;

public class _4_CollectionSetHashSet {
    public static void main(String[] args) {
        Set<Integer> all = new HashSet<>();
        all.add(100);
        all.add(200);
        all.add(300);
        all.add(400);
        all.add(500);
        all.add(600);

        System.out.println(all);
    }
}
//输出结果:[400, 100, 500, 200, 600, 300](无序)

示例代码2:借助对象数组化,循环方式实现Set接口中的内容输出。

import java.util.HashSet;
import java.util.Set;

public class _4_CollectionSetHashSet {
    public static void main(String[] args) {
        Set<Integer> all = new HashSet<>();
        all.add(100);
        all.add(200);
        all.add(300);
        all.add(400);
        all.add(500);
        all.add(600);

        Object[] o = all.toArray();	//将集合变为对象数组
        for(int i = 0;i < o.length;i++){
            System.out.print(o[i] + "、");
        }
    }
}
//输出结果:400、100、500、200、600、300、(无序)

示例代码3:凭借指定的泛型类型数组( T[] toArray(T[] a)),循环方式实现Set接口中的内容输出。

import java.util.HashSet;
import java.util.Set;

public class _4_CollectionSetHashSet {
    public static void main(String[] args) {
        Set<Integer> all = new HashSet<>();
        all.add(100);
        all.add(200);
        all.add(300);
        all.add(400);
        all.add(500);
        all.add(600);

        Integer[] arr = all.toArray(new Integer[]{});
        for(int i = 0;i < arr.length;i++){
            System.out.println(arr[i]);
        }
    }
}
//输出结果:(无序)
400
100
500
200
600
300
2-2、排序的子类:TreeSet(重点)

​ 与HashSet不同的是,TreeSet本身属于排序的子类。

TreeSet类的定义如下:

public class TreeSet extends AbstractSet implements NavigableSet,Cloneable,Serializable

示例代码

import java.util.Set;
import java.util.TreeSet;

public class _5_CollectionSetTreeSet {
    public static void main(String[] args) {
        Set<Integer> all = new TreeSet<>();
        all.add(500);
        all.add(200);
        all.add(300);
        all.add(100);
        all.add(400);
        
        System.out.println(all);
    }
}
//输出结果:[100, 200, 300, 400, 500](无序插入变成了有序存入)
2-3、排序说明(重点)

​ 借助Set接口的TreeSet类排序的功能,实现自定义类Person中成员变量的排序。要实现此类成员排序在类中需实现Comparable接口,并重写CompareTo()方法添加其排序规则即可(实现原理分析源码)。

import java.util.Set;
import java.util.TreeSet;

public class _3_CollectionSetTreeSetSort {
    public static void main(String[] args) {
        Set<Person> all = new TreeSet<>();
        all.add(new Person("小明",23));
        all.add(new Person("小张",24));
        all.add(new Person("小米",22));
        all.add(new Person("小刚",25));
        all.add(new Person("小丽",24));

        for (Person person : all) {
            System.out.println(person);
        }
    }
}

class Person implements Comparable<Person>{
    private String name;
    private int age;

    public Person(){}

    public Person(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;
    }

    @Override
    public int compareTo(Person o) {
        //按照年龄升序排列,年龄相同则按字典顺序比较名字中首字母大小升序排列(避免了重复年龄不同姓名成员的丢失)
        return (age > o.age) ? 1 : ((age == o.age) ? -name.compareTo(o.name) : -1);
        //<==>
//        if(age > o.age){
//            return 1;
//        }else if(age < o.age){
//            return -1;
//        }
//
//        return -name.compareTo(o.name);
    }

    @Override
    public String toString() {
        return "姓名:" + name + ",年龄:" + age;
    }
}

小结:

​ 关于TreeSet的排序实现,若集合中对象是自定义的或其他系统定义的类没有实现Comparable接口,则不能实现TreeSet的排序,执行时会报类型转换错误。此时该类必须实现Comparable接口,并重写CompareTo()方法定排序规则。

​ 因TreeSet的集合借用了Comparable接口,实现了去重复值的效果,而HashSet虽是Set接口子类,但对于没有重写Object的equals和hashCode方法的对象,加入了HashSet集合中也是不能去重复值的。

3、集合输出(重点)

​ 对于集合的输出有多种形式,有如下几种:

  • Iterator迭代输出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%)
3-1、Iterator(绝对重点)

​ Iterator属于迭代输出,基本操作原理:是不断的判断是否有下一个元素,有则直接输出。

Iterator接口定义格式如下:

public interface Iterator

​ 此接口规定了以下三个方法:

No.方法名称类型描述
1boolean hasNext()普通是否有下一个元素
2E next()普通取出内容
3void remove()普通删除当前内容

​ 通过Collection接口为其进行实例化之后,一定要记住,Iterator中的操作指针是在第一条元素之上,当调用next()方法时,获取当前指针指向的值并向下移动,使用hasNext()可以检查序列中是否还有元素。

在这里插入图片描述

示例代码

import java.util.Collection;
import java.util.Iterator;
import java.util.TreeSet;

public class _1_Iterator {
    public static void main(String[] args) {
        Collection<String> all = new TreeSet<>();
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");

        Iterator<String> iterator = all.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

​ 使用Iterator输出的时候有一点必须注意,在进行迭代输出的时候,若要删除当前元素,则只能使用Iterator接口中的remove()方法,而不能使用集合中的remove()方法。否则出现未知的错误!

示例代码

import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

public class _2_Iterator {
    public static void main(String[] args) {
        Set<String> s = new TreeSet<>();
        s.add("A");
        s.add("B");
        s.add("C");
        s.add("D");
        Iterator<String> i = s.iterator();
        //s.remove("B");	//error,ConcurrentModificationException
        while (i.hasNext()){
            String str = i.next();
            if(str.equals("B")){
                //s.remove("B");	//error,ConcurrentModificationException
                i.remove();
            }else {
                System.out.println(str);
            }
        }
    }
}
3-2、ListIterator(理解)

​ ListIterator是可以进行双向输出的迭代接口。

ListIterator接口的定义如下:

public interface ListIterator extends Iterator

​ 此接口是Iterator的子接口,此接口定义了如下方法:

No.方法名称类型描述
1void add(E e)普通增加元素
2boolean hasNext()普通判断是否有下一个元素
3E next()普通取出下一个元素
4boolean hasPrevious()普通判断是否有前一个元素
5E previous()普通取出前一个元素
6void set(E e)普通修改元素的内容
7int previousIndex()普通前一个索引位置
8int nextIndex()普通下一个索引位置

示例代码

import java.util.*;

public class _2_ListIterator {
    public static void main(String[] args) {
        List<String> all = new ArrayList<>();
        all.add("A");
        all.add("B");
        all.add("C");

        ListIterator<String> li = all.listIterator();
        while (li.hasNext()){       //判断是否有下一个元素
            System.out.println(li.next());      //取出下一个元素,并打印,指针再向下偏移
        }
        while (li.hasPrevious()){   //判断是否有前一个元素
            System.out.println(li.previous());  //取出前一个元素,并打印,指针再向上偏移
        }
    }
}
3-3、废弃的接口:Enumeration(了解)

​ Enumeration是一个非常古老的输出接口,其实是一个元老级的输出接口,最早的动态数组使用Vector完成,那么只要是使用了Vector则就必须使用Enumeration进行输出。

Enumeration接口的定义如下:

public interface Enumeration

​ 此接口常用方法如下:

No.方法名称类型描述
1boolean hasMoreElements()普通判断是否有下一个元素
2E nextElement()普通取出当前元素

​ 但是,与Iterator不同的是,若要想使用Enumeration输出的话,则还必须使用Vector类完成,在类中定义了如下方法:

public Enumeration elements()

示例代码

import java.util.Enumeration;
import java.util.Vector;

public class _3_Enumeration {
    public static void main(String[] args) {
        Vector<String> v = new Vector<>();
        v.add("A");
        v.add("B");
        v.add("C");
        v.add("D");

        Enumeration<String> e = v.elements();
        while (e.hasMoreElements()){
            System.out.println(e.nextElement());
        }
    }
}
3-4、新的支持:foreach(理解)

​ foreach可以用来输出数组的内容,也可以输出集合中的内容。

示例代码

import java.util.Set;
import java.util.TreeSet;

public class _4_foreach {
    public static void main(String[] args) {
        Set<String> s = new TreeSet<>();
        s.add("D");
        s.add("C");
        s.add("A");
        s.add("B");

        for (String s1 : s) {
            System.out.println(s1);	//因TreeSet的有序存储的特性,有序输出
        }
    }
}

注意:在使用foreach输出时,里面的操作泛型要指定具体的类型,这样在输出的时候才更加有针对性。

五、Map接口(重点)

​ 以上的Collection中,每次操作都是一个对象。若要操作两个对象,则必须使用Map接口,类似于以下一种情况:

  • 小明 123455
  • 小李 234162

​ 若要保存以上的某一组俩值的信息,使用Collection就不便了,所以要使用Map接口,因Map接口都按照Key->value的形式保存,也称为二元偶对象。

Map接口的定义如下:

public interface Map<K,V>

​ 此接口与Collection接口没有任何的关系,是第二大集合操作接口。此接口常用方法如下:

在这里插入图片描述

1、新的子类:HashMap(重点)

​ HashMap是Map的子类。

HashMap类的定义如下:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,Cloneable,Serializable

​ 此类继承了AbstractMap类,同时可以被克隆,可以被序列化下来。

示例代码

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class _1_MapHashMap {
    public static void main(String[] args) {
        Map<Integer,String> map1 = new HashMap<>();
        map1.put(2,"小明");               //向map1集合赋予Key->Value(键值对)为“2,"小明"”的参数。(增加内容)
        map1.put(1,"小黑");
        map1.clear();                    //清空map1集合。
        if(map1.isEmpty()){              //判断map1集合为空
            System.out.println("map集合为空~");
        }
		map1.put(null,null);             //允许向map集合中赋予null值。
        map1.put(5,"小刚");
        map1.put(3,"小李");
        map1.put(4,"小米");
        if(map1.containsKey(3)){         //判断map集合中是否包含指定的Key为3。
            System.out.println("Yes");
        }
        if(map1.containsValue("小米")){   //判断map集合中是否包含指定的Value为"小明"。
            System.out.println("Yes");
        }
        //将Map转为Set集合,即可实现迭代(意味着可实现迭代打印输出)
        Set<Map.Entry<Integer,String>> e = map1.entrySet();
        for (Map.Entry<Integer, String> integerStringEntry : e) {
            System.out.println(integerStringEntry);
            						//3=小刚
            						//4=小李
            						//5=小米
        }
		
        System.out.println(map1.get(2));     //获取key为2的Value,并打印输出:null。

        Set<Integer> s = map1.keySet();      //将map集合中的所有Key变为Set集合,赋给s。
        Collection<String> c = map1.values();//将map集合中的所有Value变为Collection集合,赋给c
        for (int i = 0;i < s.size();i++){
            System.out.println(s.toArray()[i] + " -> " + c.toArray()[i]);
                                             // 3 -> 小李
                                             // 4 -> 小米
                                             // 5 -> 小刚
        }

        Map<Integer,String> map2 = new HashMap<>();
        map2.put(2,"B");
        map2.put(1,"A");
        map1.(map2);          //向map1集合中增加一组集合map2(拼接)。
        System.out.println(map1 + " " + map2);  //{1=A, 2=B, 3=小李, 4=小米, 5=小刚} {1=A, 2=B}

        map1.remove(4);             //删除Key为4的Value。
        System.out.println(map1);   //{1=A, 2=B, 3=小李, 5=小刚}
    }
}

2、旧的子类:Hashtable(重点)

​ Hashtable是一个最早的Key->Value操作类,本身是在JDK1.0时推出的。其基本操作与HashMap是类似的。

Hashtable类的定义如下:

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>,Cloneable,java.io.Serializable

示例代码

import java.util.Hashtable;
import java.util.Map;

public class _2_MapHashtable {
    public static void main(String[] args) {
        Map<Integer,String> map1 = new Hashtable<>();
        map1.put(3,"C");
        map1.put(1,"A");
        map1.put(4,"D");
        map1.put(2,"B");
        //map1.put(5,null);  //不允许向Hashtable中赋予null,会报空指针异常!

        System.out.println(map1);   //{4=D, 3=C, 2=B, 1=A}
    }
}

3、HashMap 与 Hashtable 的区别(重点)

​ 在整个集合中除了ArrayList和Vector的区别之外,另外一个最重要的区别是HashMap与Hashtable的区别。

No.区别点HashMapHashtable
1推出时间JDK1.2之后推出的,新的操作类JDK1.0时推出的,旧的操作类
2性能异步处理,性能较高同步处理,性能较低
3null运行设置为null不允许设置,否则将出现空指针异常
4存储顺序以Key的大小排列,顺序排列以Key的大小排列,倒序排列

4、排序的子类:TreeMap(理解)

​ TreeMap子类是允许Key进行排序的操作子类,其本身在操作的时候将按Key进行排序,另外,Key中的内容可以是任意的对象,但是要求对象所在的类必须实现Comparable接口。

TreeMap类的定义如下:

public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>,Cloneable,java.io.Serializable

示例代码:

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class _3_MapTreeMap {
    public static void main(String[] args) {
        Map<Integer,String> map1 = new TreeMap<>();
        map1.put(3,"C");
        map1.put(1,"A");
        map1.put(2,"B");
        System.out.println(map1);   //{1=A, 2=B, 3=C}

        Map<Student,Integer> map2 = new TreeMap<>();
        map2.put(new Student("小明",12),65);
        map2.put(new Student("小张",15),90);
        map2.put(new Student("小雷",14),99);
        map2.put(new Student("小李",13),97);
        map2.put(new Student("小刚",14),82);

        Set<Student> stu = map2.keySet();       //得到全部的Key,赋给stu
        Iterator<Student> itr = stu.iterator(); //
        while (itr.hasNext()){
            Student i = itr.next();
            System.out.println(i + " -> " + map2.get(i) + "分");
        }
    }
}

class Student implements Comparable<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;
    }

    @Override
    public String toString() {
        return "姓名:" + name + ",年龄:" + age;
    }

    @Override
    public int compareTo(Student o) {
        //设置排序规则:返回比较的参数。
        return (age > o.age) ? 1 : (age == o.age) ? name.compareTo(o.name) : -1;
    }
}

5、关于Map集合的输出

​ 在Collection接口中,可以使用iterator()方法为Iterator接口实例化,并进行输出操作,但是在Map接口中并没有此方法的定义,所以Map接口本身不能使用Iterator进行输出。

​ 因Map接口中存放的每一个内容都是一对值,而Iterator接口输出的时候,每次取出的都是一个完整的对象。若要使用,按照以下步骤完成:

  1. 使用Map接口中的entrySet()方法将Map接口的全部变成Set集合。
  2. 使用Set接口中定义的iterfator()方法为Iterator接口进行实例化。
  3. 使用Iterator接口进行迭代输出,每一个的迭代都取出一个Map.Entry的实例。
  4. 通过Map.Entry进行Key和Value的分离。

浮点数

​ Map.Entry是一个接口,此接口定义在Map接口内部,是Map的内部接口。此内部接口被static定义,所以此接口将成为外部接口。

​ 对于每一个存放到Map集合中的Key和Value都是变成了Map.Entry并将Map.Entry保存在了Map集合之中。

在这里插入图片描述

​ 在Map.Eantry接口中以下的方法最为常用:

No.方法名称类型描述
1K getKey( )普通得到 key
2V getValue( )普通得到 value

示例代码:使用Iterator输出Map接口(常用模板)

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set

public class MapOutDemo{
    public static void main(String[] args){
        Map<String,String> map = new HashMap<>();
        map.put("a","A");
        map.put("c","C");
        map.put("b","B");
        Set<Map.Entry<String,String>> set = map.entrySet();	//变为Set实例
        Iterator<Map.Entry<String,String>> iter = set.iterator();
        while(iter.HasNext()){
            Map.Entry<String,String> m = iter.next;
            System.out.println(m.getKey() + " -> " + m.getValue());
        }
    }
}

六、两种关系(理解)

​ 使用类集,除了可以清楚的表示出动态数组的概念及各个数据结构的操作之外,也可以表示出以下的两种关系。

1、第一种关系:一对多关系

​ eg:一个学校有多个学生,是一个典型的一对多的关系。

示例代码

① 定义学生类,一个学生属于一个学校

package com.listdemo.casedemo01;
public class Student{
    private String name;
    private int age;
    private School school;
    
    public Student(String name,int age){
        this.name = name;
        this.age = age;
    }
    public School getSchool(){
        return school;
    }
    public void setSchool(School school){
        this.school = school;
    }
    public String toString(){
        return "学生信息" + "\n" + "\t|- 姓名:" + this.name + "\n" + "\t|- 年龄:" + this.age;
    }
}

② 定义学校类,一个学校有多个学生

package com.listdemo.casedemo01;
import java.util.ArrayList;
import java.util.List;
public class School{
    private String name;
    private List<Student> allStudents = null;
    
    public School(){
        this.allStudents = new ArrayList<Student>();
    }
    public School(String name){
        this();
        this.name = name;
    }
    public List<Student> getAllStudents(){
        return allStudents;
    }
    public String toString(){
        return "学校信息:" + "\n" + "\t- 学校名称:" + this.name;
    }
}

③ 主方法建立以上两者的关系

package com.listdemo.casedemo01;
import java.util.Iterator;
public class TestDemo{
    public static void main(String[] args){
        Student stu1 = new Student("小A",10);
        Student stu2 = new Student("小B",11);

        School sch = new School("LAMP JAVA");
        sch.getAllStudents().add(stu1);	//一个学校有多个学生
        stu1.setSchool(sch);			//一个学生属于一个学校
        sch.getAllStudents().add(stu2);	//一个学校有多个学生
        stu2.setSchool(sch);			//一个学生属于一个学校
        
        System.out.println(sch);
        Iterator<Student> iter = sch.getAllStudents().iterator();
        while(iter.hasNext()){
            System.out.println(iter.next());
        }
        System.out.println(stu1.getSchool());
    }
}

2、第二种关系:多对多关系

​ eg:一个学生可以选择多门课程,一门课程允许有多个学生参加。

示例代码

① 定义学生类,一个学生可以选择多门课程

package com.listdemo.casedemo02;
import java.util.ArrayList;
import java.util.List;
public class Student{
    private String name;
    private int age;
    private List<Course> allCourses;
    public Student(){
        this.allCourses = new ArrayList<Course>();
    }
    public Student(String name,int age){
        this();
        this.name = name;
        this.age = age;
    }
    public List<Course> getAllCourses(){
        return allCourses;
    }
    public String toString(){
        return "学生信息:" + "\n" + "\t- 姓名:" + this.name + "\n" + "\t- 年龄:" + this.age;
    }
}

② 定义课程类,一门课程可以有多个学生参加

package com.listdemo.casedemo02;
import java.util.ArrayList;
import java.util.List;
public class Course{
    private String name;
    private int credit;
    private List<Student> allStudents = null;
    public Course(){
        this.allStudents = new ArrayList<Student>();
    }
    public Course(String name,int credit){
        this();
        this.name = name;
        this.credit = credit;
    }
    public List<Student> getAllStudents(){
        return allStudents;
    }
    public String toString(){
        return "课程信息:" + "\n" + "\t- 课程名称:" + this.name + "\n" + "\t- 课程学分:" + this.credit;
    }
}

③ 主方法建立以上两者的关系

package com.listdemo.casedemo02;
import java.util.Iterator;
public class TestDemo{
    public static void main(String[] args){
        Student stu1 = new Student("A",10);
        Student stu2 = new Student("B",11);
        Student stu3 = new Student("C",12);
        
        Course c1 = new Course("Oracle",5);
        Course c2 = new Course("Java SE基础",10);
        c1.getAllStudents().add(stu1);	//参加第一门课程
        c1.getAllStudents().add(stu2);	//参加第一门课程
        stu1.getAllCourse().add(c1);	//学生选择课程
        stu2.getAllCourse().add(c1);	//学生选择课程
        c2.getAllStudents().add(stu1);	//参加第二门课程
        c2.getAllStudents().add(stu2);	//参加第二门课程
        c2.getAllStudents().add(stu3);	//参加第二门课程
        stu1.getAllCourse().add(c1);	//学生选择课程
        stu2.getAllCourse().add(c2);	//学生选择课程
        stu3.getAllCourse().add(c2);	//学生选择课程
        
        System.out.println(c2);
        Iterator<Student> iter = c2.getAllStudents().iterator();
        while(iter.hasNext()){
            System.out.println(iter.next());
        }
        System.out.println("-----------------------------");
        System.out.println(stu1);
        Iterator<Course> iters = stu1.getAllCourses().iterator();
        while(iters.hasNext()){
            System.out.println(iters.next());
        }
    }
}

七、Collections类(理解)

​ Collections实际上是一个集合的操作类,此类的定义如下:

public class Collections extends Object

​ 此类与Collection接口没有任何关系。是一个单独存在的类。

示例代码:返回空的List集合

import java.util.Collections;
import java.util.List;
public class CollectionsDemo{
    public static void main(String[] args){
        List<String> all = Collections.emptyList();	//空的集合
        all.add("A");
    }
}

​ 使用Collections类返回的空的集合对象,本身是不支持任何的修改操作的,因为所有的方法都没有实现。

示例代码:使用Collections进行增加元素的操作

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsDemo{
    List<String> all = new ArrayList<>();
    Collections.addAll(all,"A","B","C");	//向集合增加元素
    System.out.println(all);
}

​ 但是,从实际考虑,使用此类操作并不是很方便,最好的做法就是使用各个接口的直接操作的方法完成。此类只是一个集合的操作类。

八、分析equals、hashCode与内存泄漏(理解)

equals的作用:比较两个对象的地址值是否相等

equals()方法在Object类中的定义如下:
public boolean equals(Object obj){
return (this == obj);
}

​ 需注意的是,当String、Math、及Integer、Double等这些封装类在使用equals()方法时,已覆盖了Object类的equals()方法,不再是地址的比较而是内容的比较。

​ 还应注意,Java语言对equals()的要求如下,这些要求必须遵循:

  1. 对称性:如果x.equals(y)返回“true”,那么y.equals(x)也应返回“true”。
  2. 自反性:x.equals(x)必须返回“true”。
  3. 传递性:如果x.equals(y)返回“true”,且y.equals(z)返回“true”,那么z.equals(x)也应返回“true”。
  4. 一致性:如果x.equals(y)返回“true”,只要x和y内容一直不变,无论你重复x.equals(y)多少次,返回都是“true”。
  5. 非空性:任何情况下,x.equals(null),永远返回“false”;x.equals(和x不同类型的对象),永远返回“false”。

以上五点是重写equals()方法时,必须遵守的准则。


hashCode()方法,在Object类中的定义如下:
public native int hashCode();

​ 这是一个本地方法,它的实现是根据本地机器相关的。当我们可以在自己写的类中覆盖hashCode()方法,比如:String、Integer、Double等这些类都是覆盖了hashCode()方法的。


java.lang.Object中对hashCode的约定(特重要)

  1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
  2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个中任一对象的hashCode方法必须产生相同的整数结果。
  3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

在java的集合中,判断两个对象是否相等的规则是

(1) 判断两个对象的hashCode是否相等

​ 如果不相等,认为两个对象也不相等,完毕。

​ 如果相等,转入2

(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其作为必需的。)

(2) 判断两个对象用equals运算是否相等

​ 如果不相等,认为两个对象也不相等

​ 如果相等,认为连个对象相等(equals()是判断两个对象是否相等的关键)

提示

​ 当一个对象被存进HashSet集合后,就不能修改这个对象中的那些参与计算的哈希值的字段了,否则,对象被修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除当前对象,从而造成内存泄漏。


总结

  1. 类集就是一个动态的对象数组,可以向集合中加入任意多的内容。
  2. List接口中是允许有重复元素的,Set接口中是不允许有重复元素。
  3. 所有的重复元素依靠hashCode()和equals进行区分。
  4. List接口的常用子类:ArrayList、Vector
  5. Set接口的常用子类:HashSet、TreeSet
  6. TreeSet是可以排序,一个类的对象依靠Comparable接口排序。
  7. Map接口中允许存放一对内容,key->value。
  8. Map接口的子类:HashMap、Hashtable、TreeMap。
    关的。当我们可以在自己写的类中覆盖hashCode()方法,比如:String、Integer、Double等这些类都是覆盖了hashCode()方法的。

java.lang.Object中对hashCode的约定(特重要)

  1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
  2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个中任一对象的hashCode方法必须产生相同的整数结果。
  3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

在java的集合中,判断两个对象是否相等的规则是

(1) 判断两个对象的hashCode是否相等

​ 如果不相等,认为两个对象也不相等,完毕。

​ 如果相等,转入2

(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其作为必需的。)

(2) 判断两个对象用equals运算是否相等

​ 如果不相等,认为两个对象也不相等

​ 如果相等,认为连个对象相等(equals()是判断两个对象是否相等的关键)

提示

​ 当一个对象被存进HashSet集合后,就不能修改这个对象中的那些参与计算的哈希值的字段了,否则,对象被修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除当前对象,从而造成内存泄漏。


总结

  1. 类集就是一个动态的对象数组,可以向集合中加入任意多的内容。
  2. List接口中是允许有重复元素的,Set接口中是不允许有重复元素。
  3. 所有的重复元素依靠hashCode()和equals进行区分。
  4. List接口的常用子类:ArrayList、Vector
  5. Set接口的常用子类:HashSet、TreeSet
  6. TreeSet是可以排序,一个类的对象依靠Comparable接口排序。
  7. Map接口中允许存放一对内容,key->value。
  8. Map接口的子类:HashMap、Hashtable、TreeMap。
  9. Map使用Iterator输出的详细步骤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值