Java集合(知识点整理)

集合

集合按照其存储结构可以分为两大类,分别是单列集合 java.util.Collection 和双列集合java.util.Map

对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构,所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。 在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完 整的提出类集的完整概念。

类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。

所有的类集操作的接口或类都在 java.util 包中。

java类集结构图:

在这里插入图片描述

Collection接口

Collection是单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是 java.util.List 和 java.util.Set 。其中, List 的特点是元素有序、元素可重复。 Set 的特点是元素无序,而且不可重复。 List 接口的主要实现类有java.util.ArrayListjava.util.LinkedList , Set 接口的主要实现类有 java.util.HashSetjava.util.TreeSet

Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。

此接口定义在 java.util 包中。

此接口定义如下:

public interface Collection<E> extends Iterable<E>

此接口使用了泛型技术,在 JDK 1.5 之后为了使类集操作的更加安全,所以引入了泛型。

此接口的常用方法如下所示:

在这里插入图片描述

本接口中一共定义了 15 个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。

但是,在开发中不会直接使用 Collection 接口。而使用其操作的子接口:List、Set。

之所以有这样的明文规定,也是在 JDK 1.2 之后才有的。一开始在 EJB 中的最早模型中全部都是使用 Collection 操作的,所以很早之前开发代码都是以 Collection 为准,但是后来为了更加清楚的区分,集合中是否允许有重复元素所以 SUN在其开源项目 —— PetShop(宠物商店)中就开始推广 List 和 Set 的使用。

List接口

它是一个元素存取有序的集合。在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。

List 子接口的定义:

public interface List<E> extends Collection<E>

此接口上依然使用了泛型技术。此接口对于 Collection 接口来讲有如下的扩充方法:

在这里插入图片描述

在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。 所以,证明,List 接口拥有比 Collection 接口更多的操作方法。

了解了 List 接口之后,那么该如何使用该接口呢?需要找到此接口的实现类,常用的实现类有如下几个:

  • ArrayList(95%)、Vector(4%)、LinkedList(1%)
ArrayList类

ArrayList 是 List 接口的子类,此类的定义如下:

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

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

范例: 增加及取得元素

public static void main(String[] args) { 
    List<String> all = new ArrayList<String>(); // 实例化List对象,并指定泛型类型 
    all.add("hello "); // 增加内容,此方法从Collection接口继承而来 
    all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义的 
    all.add("world"); // 增加内容,此方法从Collection接口继承而来 
    System.out.println(all); // 打印all对象调用toString()方法 
}

以上的操作向集合中增加了三个元素,其中在指定位置增加的操作是 List 接口单独定义的。随后进行输出的时候,实际上调用的是 toString()方法完成输出的。

可以发现,此时的对象数组并没有长度的限制,长度可以任意长,只要是内存够大就行。

范例: 进一步操作

  • 使用 remove()方法删除若干个元素,并且使用循环的方式输出。

  • 根据指定位置取的内容的方法,只有 List 接口才有定义,其他的任何接口都没有任何的定义。

public static void main(String[] args) { 
    List<String> all = new ArrayList<String>(); // 实例化List对象,并指定泛型类型 
    all.add("hello "); // 增加内容,此方法从Collection接口继承而来 
    all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义的 
    all.add("world"); // 增加内容,此方法从Collection接口继承而来 
    all.remove(1); // 根据索引删除内容,此方法是List接口单独定义的 
    all.remove("world");// 删除指定的对象 
    System.out.print("集合中的内容是:"); 
    for (int x = 0; x < all.size(); x++) { // size()方法从Collection接口继承而来 
        System.out.print(all.get(x) + "、"); // 此方法是List接口单独定义的 
    } 
}

但是,这里需要注意的是,对于删除元素的操作,后面还会有更加清楚的讲解,此处只是简单的理解一下元素删除的基本操作即可。具体的原理可以暂时不进行深入掌握。

注意:ArrayList定义的时候是个空集合,在第一次添加数据的时候才扩容成大小为10的集合。

Vector类

与 ArrayList 一样,Vector 本身也属于 List 接口的子类,此类的定义如下:

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

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

public static void main(String[] args) {
    List<String> all = new Vector<String>(); // 实例化List对象,并指定泛型类型 
    all.add("hello "); // 增加内容,此方法从Collection接口继承而来 
    all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义的 
    all.add("world"); // 增加内容,此方法从Collection接口继承而来 
    all.remove(1); // 根据索引删除内容,此方法是List接口单独定义的 
    all.remove("world");// 删除指定的对象 
    System.out.print("集合中的内容是:"); 
    for (int x = 0; x < all.size(); x++) { // size()方法从Collection接口继承而来 
        System.out.print(all.get(x) + "、"); // 此方法是List接口单独定义的 
    } 
}

以上的操作结果与使用 ArrayList 本身并没有任何的区别。因为操作的时候是以接口为操作的标准。但是 Vector 属于 Java 元老级的操作类,是最早的提供了动态对象数组的操作类,在 JDK 1.0 的时候就已经推出了此类的使用,只是后来在 JDK 1.2 之后引入了 Java 类集合框架。但是为了照顾很多已经习惯于使用 Vector 的用户,所以在JDK 1.2 之后将 Vector 类进行了升级了,让其多实现了一个 List 接口,这样才将这个类继续保留了下来。

Vector类和ArrayList类的区别:

这两个类虽然都是 List 接口的子类,但是使用起来有如下的区别:

在这里插入图片描述

链表的操作类:LinkedList

此类的使用几率是非常低的,但是此类的定义如下:

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable

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

在这里插入图片描述

范例: 验证 LinkedList 子类

public static void main(String[] args) { 
    Queue<String> queue = new LinkedList<String>(); 
    queue.add("A"); queue.add("B"); 
    queue.add("C"); 
    int len=queue.size();//把queue的大小先取出来,否则每循环一次,移除一个元素,就少 一个元素,那么queue.size()在变小,就不能循环queue.size()次了。 
    for (int x = 0; x <len; x++) { 
        System.out.println(queue.poll()); 
    }
    System.out.println(queue);
}

Set 接口

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

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

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

set接口大部分集合内存使用了Map集合存储,但是只使用Map集合的键key存取,因为Map的键key不可重复。

散列存放:HashSet

既然 Set 接口并没有扩充任何的 Collection 接口中的内容,所以使用的方法全部都是 Collection 接口定义而来的。

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

***范例:***观察 HashSet

public static void main(String[] args) { 
    Set<String> all = new HashSet<String>(); // 实例化Set接口对象 
    all.add("A"); 
    all.add("B"); 
    all.add("C");
    all.add("D"); 
    all.add("E"); 
    System.out.println(all);
}

使用 HashSet 实例化的 Set 接口实例,本身属于无序的存放。

那么,现在思考一下?能不能通过循环的方式将 Set 接口中的内容输出呢?

是可以实现的,因为在 Collection 接口中定义了将集合变为对象数组进行输出。

public static void main(String[] args) { 
    Set<String> all = new HashSet<String>(); // 实例化Set接口对象 
    all.add("A"); 
    all.add("B"); 
    all.add("C"); 
    all.add("D"); 
    all.add("E"); 
    Object obj[] = all.toArray(); // 将集合变为对象数组 
    for (int x = 0; x < obj.length; x++) { 
        System.out.print(obj[x] + "、");
    } 
}

但是,以上的操作不好,因为在操作的时候已经指定了操作的泛型类型,那么现在最好的做法是由泛型所指定的类型变为指定的数组。

所以只能使用以下的方法:<T> T[] toArray(T[] a)

public static void main(String[] args) { 
    Set<String> all = new HashSet<String>(); // 实例化Set接口对象 
    all.add("A");
    all.add("B"); 
    all.add("C"); 
    all.add("D"); 
    all.add("E"); 
    String[] str = all.toArray(new String[] {});// 变为指定的泛型类型数组 
    for (int x = 0; x < str.length; x++) { 
        System.out.print(str[x] + "、"); 
    } 
}

下面再进一步验证 Set 接口中是不能有重复的内容的。

public static void main(String[] args) { 
    Set<String> all = new HashSet<String>(); // 实例化Set接口对象 
    all.add("A"); 
    all.add("A"); // 重复元素
    all.add("A"); // 重复元素
    all.add("A"); // 重复元素 
    all.add("A"); // 重复元素 
    all.add("B"); 
    all.add("C");
    all.add("D"); 
    all.add("E");
    System.out.println(all); 
}

以上字符串“A”设置了很多次,因为 Set 接口中是不能有任何的重复元素的,所以其最终结果只能有一个“A”。

注意:HastSet调用add方法时,如果要添加的值不存在,则返回true,如果已经存在,则返回false。

HashSet集合存储数据的结构(哈希表)

HashSet存储数据时调用的是HashMap。

重写对象的hasCode方法可以优化哈希表(散列表的性能)。

什么是哈希表(散列表)呢?

JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。当哈希桶中的数据量减少到6时,又从红黑二叉树转换为链表。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。

在这里插入图片描述

哈希表中默认容量16,下标计算默认除以16取余,但是当数据量到达75%,容量扩增到32,下标计算除以32取余。

看到这张图就有人要问了,这个是怎么存储的呢?

为了方便大家的理解我们结合一个存储流程图来说明一下:

在这里插入图片描述

总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

HashSet存储自定义类型元素

给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。

LinkedHashSet

我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?

在HashSet下面有一个子类 java.util.LinkedHashSet ,它是链表和哈希表组合的一个数据存储结构。

排序的子类:TreeSet

与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:

public class TreeSet extends AbstractSet implements NavigableSet, Cloneable, Serializable

下面通过代码来观察其是如何进行排序的。

public static void main(String[] args) { 
    Set<String> all = new TreeSet<String>(); // 实例化Set接口对象
    all.add("D");
    all.add("X");
    all.add("A");
    System.out.println(all);
}

虽然在增加元素的时候属于无序的操作,但是增加之后却可以为用户进行排序功能的实现。

注意:TreeSet内部是用二叉树的原理进行存取的,存储结构调用的是TreeMap集合。

排序的说明

Comparable

既然 Set 接口的 TreeSet 类本身是允许排序,那么现在自定义一个类是否可以进行对象的排序呢?

范例: 对象排序

//定义 Person 类
class 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 namjavae; 
    }
    public void setName(String name) {
        this.name = name; 
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age; 
    }
    public String toString() { 
        return "姓名:" + this.name + ",年龄:" + this.age;
    } 
}

//下面定义一个 TreeSet 集合,向里面增加若干个 Person 对象。

public class TreeSetPersonDemo01 { 
    public static void main(String[] args) { 
        Set<Person> all = new TreeSet<Person>(); 
        all.add(new Person("张三", 10)); 
        all.add(new Person("李四", 10));
        all.add(new Person("王五", 11)); 
        all.add(new Person("赵六", 12)); 
        all.add(new Person("孙七", 13)); 
        System.out.println(all);
    } 
}

执行以上的操作代码之后,发现出现了如下的错误提示:

在这里插入图片描述

此时的提示是:Person 类不能向 Comparable 接口转型的问题

所以,证明,如果现在要是想进行排序的话,则必须在 Person 类中实现 Comparable 接口。

class Person implements Comparable<Person> {
    private String name; 
    private int age;
    public int compareTo(Person per) {//返回的数据:负数this小/零一样大/正数this大
        if (this.age > per.age) {
            return 1;
        } else if (this.age < per.age) { 
            return -1; 
        } else {
            return 0; 
        }
    }
    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; 
    }
    public String toString() { 
        return "姓名:" + this.name + ",年龄:" + this.age;
    } 
}

那么此时再次使用之前的代码运行程序。程序的执行结果如下:

在这里插入图片描述

从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则此时必须修改 Person 类,如果假设年龄相等的话,按字符串进行排序。

public int compareTo(Person per) { //这个方法是Comparable接口的重写,效果和String同名方法类似
    if (this.age > per.age) { 
        return 1; 
    } else if (this.age < per.age) { 
        return -1; 
    } else { 
        return this.name.compareTo(per.name); //这里的compareTo调用的是String类的方法。按字典顺序比较两个字符串。 比较基于字符串中每个字符的Unicode值。 此String对象表示的字符序列按字典顺序与参数字符串表示的字符序列进行比较。 如果此String对象按字典顺序排在参数字符串之前,则结果为负整数。 如果此String对象按字典顺序跟随参数字符串,则结果为正整数。 如果字符串相等,结果为零; compareTo在equals(Object)方法返回0完全返回true 。 
    } 
}

此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。

Comparator比较器

我们还是先研究这个方法

public static <T> void sort(List<T> list) :将集合中元素按照默认规则排序。不过这次存储的是字符串类型。

public static void main(String[] args) { 
    ArrayList<String> list = new ArrayList<String>(); 
    list.add("cba"); 
    list.add("aba"); 
    list.add("sba"); 
    list.add("nba"); //排序方法 
    Collections.sort(list); 
    System.out.println(list); 
}//结果:[aba, cba, nba, sba]

我们使用的是默认的规则完成字符串的排序,那么默认规则是怎么定义出来的呢?

说到排序了,简单的说就是两个对象之间比较大小,那么在JAVA中提供了两种比较实现的方式,一种是比较死板的采用 java.lang.Comparable 接口去实现,一种是灵活的当我需要做排序的时候在去选择的 java.util.Comparator 接口完成。那么我们采用的 public static <T> void sort(List<T> list) 这个方法完成的排序,实际上要求了被排序的类型需要实现Comparable接口完成比较的功能,在String类型上如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

String类实现了这个接口,并完成了比较规则的定义,但是这样就把这种规则写死了,那比如我想要字符串按照第一个字符降序排列,那么这样就要修改String的源代码,这是不可能的了,那么这个时候我们可以使用public static <T> void sort(List<T> list,Comparator<? super T> )方法灵活的完成,这个里面就涉及到了Comparator这个接口,位于位于java.util包下,排序是comparator能实现的功能之一,该接口代表一个比较器,比较器具有可比性!顾名思义就是做排序的,通俗地讲需要比较两个对象谁排在前谁排在后,那么比较的方法就是:

public int compare(String o1, String o2) :比较其两个参数的顺序。

两个对象比较的结果有三种:大于,等于,小于。

如果要按照升序排序,则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数)

如果要按照降序排序,则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)

操作如下:

public static void main(String[] args) { 
    ArrayList<String> list = new ArrayList<String>(); 
    list.add("cba"); 
    list.add("aba"); 
    list.add("sba"); 
    list.add("nba"); //排序方法 按照第一个单词的降序 
    Collections.sort(list, new Comparator<String>() {
        @Override 
        public int compare(String o1, String o2) { 
            return o2.charAt(0) - o1.charAt(0); 
        } 
    }); 
    System.out.println(list); 
}//结果如下:[sba, nba, cba, aba]

练习

//创建一个学生类,存储到ArrayList集合中完成指定排序操作。
//Student 初始类
class 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 "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}
public class Demo { 
    public static void main(String[] args) { // 创建四个学生对象 存储到集合中 
        ArrayList<Student> list = new ArrayList<Student>();
        list.add(new Student("rose",18)); 
        list.add(new Student("jack",16));
        list.add(new Student("abc",16));
        list.add(new Student("ace",17));
        list.add(new Student("mark",16)); /*让学生 按照年龄排序 升序 */ 
        // Collections.sort(list);//要求 该list中元素类型 必须实现比较器Comparable接口 
        for (Student student : list) { 
            System.out.println(student); 
        } 
    } 
}

发现,当我们调用Collections.sort()方法的时候 程序报错了。

原因:如果想要集合中的元素完成排序,那么必须要实现比较器Comparable接口。于是我们就完成了Student类的一个实现,如下:

public class Student implements Comparable<Student>{ 
    .... 
    @Override 
    public int compareTo(Student o) { 
        return this.age-o.age;//升序
    }
}

再次测试,代码就OK 了效果如下:

Student{name='jack', age=16} 
Student{name='abc', age=16} 
Student{name='mark', age=16} 
Student{name='ace', age=17}
Student{name='rose', age=18}

扩展

如果在使用的时候,想要独立的定义规则去使用 可以采用Collections.sort(List list,Comparetor c)方式,自己定义规则:

Collections.sort(list, new Comparator<Student>() { 
    @Override 
    public int compare(Student o1, Student o2) { 
        return o2.getAge()-o1.getAge();//以学生的年龄降序
    } 
});

效果:

Student{name='rose', age=18} 
Student{name='ace', age=17} 
Student{name='jack', age=16} 
Student{name='abc', age=16} 
Student{name='mark', age=16}

如果想要规则更多一些,可以参考下面代码:

Collections.sort(list, new Comparator<Student>() { 
    @Override 
    public int compare(Student o1, Student o2) { // 年龄降序 
        int result = o2.getAge()-o1.getAge();//年龄降序 
        if(result==0){//第一个规则判断完了 下一个规则 姓名的首字母 升序 
            result = o1.getName().charAt(0)-o2.getName().charAt(0); 
        }
        return result; 
    } 
});

效果如下:

Student{name='rose', age=18}
Student{name='ace', age=17} 
Student{name='abc', age=16} 
Student{name='jack', age=16}
Student{name='mark', age=16}
简述Comparable和Comparator两个接口的区别

Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

关于重复元素的说明

之前使用 Comparable 完成的对于重复元素的判断,那么 Set 接口定义的时候本身就是不允许重复元素的,那么证明如果现在真的是有重复元素的话,使用 HashSet 也同样可以进行区分。

public static void main(String[] args) {
    Set<Person> all = new HashSet<Person>(); 
    all.add(new Person("张三", 10));
    all.add(new Person("李四", 10));
    all.add(new Person("李四", 10));
    all.add(new Person("王五", 11)); 
    all.add(new Person("赵六", 12));
    all.add(new Person("孙七", 13));
    System.out.println(all); 
}

在这里插入图片描述

此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过 Comparable接口间接完成的。

如果要想判断两个对象是否相等,则必须使用 Object 类中的 equals()方法。

从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:

  • 第一种判断两个对象的编码是否一致,这个方法需要通过 hashCode()完成,即:每个对象有唯一的编码

  • 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。

所以此时需要覆写 Object 类中的 hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。

public boolean equals(Object obj) { 
    if (this == obj) { 
        return true; 
    }if (!(obj instanceof Person)) {
        return false;
    }
    Person per = (Person) obj;
    if (per.name.equals(this.name) && per.age == this.age) { 
        return true;
    } else { 
        return false; 
    } 
}
public int hashCode() { 
    return this.name.hashCode() * this.age; 
}

发现,此时已经不存在重复元素了,所以如果要想去掉重复元素需要依靠 hashCode()和 equals()方法共同完成。

小结:

关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现Comparable 接口,则不能实现 TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。换句话说要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。

不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet集合中也是不能去掉重复值的。

集合输出

对于集合的输出本身也是有多种形式的,可以使用如下的几种方式:

  • Iterator 迭代输出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%)

但是在讲解输出的时候一定要记住以下的原则:“只要是碰到了集合,则输出的时候想都不想就使用 Iterator 进行输出。

Iterator

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

此接口定义如下:

public interface Iterator<E>

要想使用此接口,则必须使用 Collection 接口,在 Collection 接口中规定了一个 iterator()方法,可以用于为 Iterator 接口进行实例化操作。

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

在这里插入图片描述

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

在这里插入图片描述

范例: 观察 Iterator 输出

public static void main(String[] args) { 
    Collection<String> all = new ArrayList<String>();
    all.add("A"); all.add("B"); 
    all.add("C"); 
    all.add("D"); 
    all.add("E");
    Iterator<String> iter = all.iterator(); 
    while (iter.hasNext()) {// 判断是否有下一个元素 
        String str = iter.next(); // 取出当前元素 
        System.out.print(str + "、"); 
    } 
}

以上的操作是 Iterator 接口使用最多的形式,也是一个标准的输出形式。

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

public static void main(String[] args) {
    Collection<String> all = new ArrayList<String>();
    all.add("A"); all.add("B"); 
    all.add("C"); 
    all.add("D"); 
    all.add("E"); 
    Iterator<String> iter = all.iterator();
    while (iter.hasNext()) {// 判断是否有下一个元素 
        String str = iter.next(); // 取出当前元素 
        if (str.equals("C")) { 
            all.remove(str); // 错误的,调用了集合中的删除
        } else { 
            System.out.print(str + "、");
        }
    }
}

此时出现了错误,因为原本的要输出的集合的内容被破坏掉了。

Iterator<String> iter = all.iterator(); 
while (iter.hasNext()) {// 判断是否有下一个元素 
    String str = iter.next(); // 取出当前元素 
    if (str.equals("C")) { 
        iter.remove();
    } else { 
        System.out.print(str + "、");
    } 
}

但是,从实际的开发角度看,元素的删除操作出现的几率是很小的,基本上可以忽略,即:集合中很少有删除元素的操作。

Iterator 接口本身可以完成输出的功能,但是此接口只能进行由前向后的单向输出。如果要想进行双向输出,则必须使用其子接口 —— ListIterator。

ListIterator

ListIterator 是可以进行双向输出的迭代接口,此接口定义如下:

public interface ListIterator extends Iterator

此接口是 Iterator 的子接口,此接口中定义了以下的操作方法:

在这里插入图片描述

但是如果要想使用 ListIterator 接口,则必须依靠 List 接口进行实例化。

List 接口中定义了以下的方法:ListIterator<E> listIterator()

范例: 使用 ListIterator 输出

public static void main(String[] args) { 
    List<String> all = new ArrayList<String>();
    all.add("A"); 
    all.add("B");
    all.add("C"); 
    all.add("D"); 
    all.add("E");
    ListIterator<String> iter = all.listIterator();
    System.out.print("从前向后输出:"); 
    while (iter.hasNext()) { 
        System.out.print(iter.next() + "、"); 
    }
    System.out.print("\n从后向前输出:"); 
    while (iter.hasPrevious()) { 
        System.out.print(iter.previous() + "、");
    }
}

但是,此处有一点需要注意的是,如果要想进行由后向前的输出,则首先必须先进行由前向后的输出

但是,此接口一般使用较少。

Iterator和 ListIterator的区别:

Iterator主要是用来迭代Collection接口下的所有集合,ListIterator主要是迭代List接口下的集合。

废弃的接口:Enumeration

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

此接口定义如下:

public interface Enumeration<E>

在 JDK 1.5 之后,此接口实际上也已经加入了泛型操作。此接口常用方法如下:

在这里插入图片描述

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

public Enumeration<E> elements()

范例: 验证 Enumeration 接口

public static void main(String[] args) { 
    Vector<String> v = new Vector<String>(); 
    v.add("A"); 
    v.add("B"); 
    v.add("C");
    Enumeration<String> enu = v.elements(); 
    while (enu.hasMoreElements()) {
        System.out.println(enu.nextElement()); 
    } 
}

需要注意的是,在大部分的情况下,此接口都不再使用了,但是对于一些古老的类库,本身依然要使用此接口进行操作,所以此接口一定要掌握。

新的支持:foreach

增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。

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

public static void main(String[] args) { 
    Collection<String> all = new ArrayList<String>(); 
    all.add("A"); 
    all.add("B"); 
    all.add("C"); 
    all.add("D"); 
    all.add("E"); 
    for (String str : all) { 
        System.out.println(str) ;
    }
}

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

它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作

Map 接口

以上的 Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用 Map 了,类似于以下一种情况:

  • 张三 123456

  • 李四 234567

那么保存以上信息的时候使用 Collection 就不那么方便,所以要使用 Map 接口。里面的所有内容都按照 key->value的形式保存,也称为二元偶对象。 key不能重复

Map输出内容的时候可以调用remove方法,既能返回数值,也能清理集合空间。

此接口定义如下:

public interface Map<K,V>

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

在这里插入图片描述

Map 本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable

新的子类:HashMap

HashMap 是 Map 的子类,此类的定义如下:

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

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

范例: 向集合中增加内容

public static void main(String[] args) { 
    Map<Integer, String> map = new HashMap<Integer, String>();
    map.put(1, "张三A"); 
    map.put(1, "张三B"); // 新的内容替换掉旧的内容 
    map.put(2, "李四"); 
    map.put(3, "王五"); 
    String val = map.get(6); 
    System.out.println(val);
}

以上的操作是 Map 接口在开发中最基本的操作过程,根据指定的 key 找到内容,如果没有找到,则返回 null,找到了则返回具体的内容。

范例: 得到全部的 key 或 value

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<Integer, String>();
    map.put(1, "张三A");
    map.put(2, "李四"); 
    map.put(3, "王五"); 
    Set<Integer> set = map.keySet(); // 得到全部的key 
    Collection<String> value = map.values(); // 得到全部的value 
    Iterator<Integer> iter1 = set.iterator(); 
    Iterator<String> iter2 = value.iterator(); 
    System.out.print("全部的key:"); 
    while (iter1.hasNext()) { 
        System.out.print(iter1.next() + "、"); 
    }
    System.out.print("\n全部的value:"); 
    while (iter2.hasNext()) { 
        System.out.print(iter2.next() + "、");
    }
}

既然可以取得全部的 key,那么下面就可以对以上的操作进行扩充,循环输出 Map 中的全部内容。

public static void main(String[] args) { 
    Map<String, String> map = new HashMap<String, String>(); 
    map.put("ZS", "张三"); 
    map.put("LS", "李四");
    map.put("WW", "王五");
    map.put("ZL", "赵六");
    map.put("SQ", "孙七"); 
    Set<String> set = map.keySet(); // 得到全部的key 
    Iterator<String> iter = set.iterator(); 
    while (iter.hasNext()) { 
        String i = iter.next(); // 得到key 
        System.out.println(i + " --:> " + map.get(i)); 
    } 
}

HashMap 本身是属于无序存放的。

自定义对象

public class Demo {
    public static void main(String[] args) {
        HashMap<Book,String> data = new HashMap<>();
        Book book1 = new Book("金苹果","讲述了苹果种植的心酸历程");
        data.put(book1,"我们人生的第一本书");
        Book book2 = new Book("银苹果","讲述了苹果种植的心酸历程");
        data.put(book2,"我们人生的第二本书");
        book1.setName("铜苹果");
        System.out.println(data.get(book1));			//输出:null
        //哈希表查找数据时是依据键key的哈希值,因为data中存储了book1的经过计算哈希值的键,但是被改变了对象对容,该对象内容并没有在哈希表中存储,所有查询为空
        Book book3 = new Book("金苹果","讲述了苹果种植的心酸历程");
        System.out.println(data.get(book3));			//输出:null
        //虽然查询的该对象在哈希表中有对应的哈希值存储,但是查询时还是调用equals方法进行比较,所以两者不相等,查询为空。
    }
}
class Book{
    private String name;
    private String info;
    public Book() {
    }
    public Book(String name, String info) {
        this.name = name;
        this.info = info;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getInfo() {
        return info;
    }
    public void setInfo(String info) {
        this.info = info;
    }
    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", info='" + info + '\'' +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return Objects.equals(name, book.name) &&
                Objects.equals(info, book.info);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, info);
    }
}
旧的子类:Hashtable

Hashtable 是一个最早的 key->value 的操作类,本身是在 JDK 1.0 的时候推出的。其基本操作与 HashMap 是类似的。

public static void main(String[] args) { 
    Map<String, Integer> numbers = new Hashtable<String, Integer>(); 
    numbers.put("one", 1); 
    numbers.put("two", 2); 
    numbers.put("three", 3);
    Integer n = numbers.get("two"); 
    if (n != null) { 
        System.out.println("two = " + n);
    } 
}

操作的时候,可以发现与 HashMap 基本上没有什么区别,而且本身都是以 Map 为操作标准的,所以操作的结果形式 都一样。但是 Hashtable 中是不能向集合中插入 null 值的。

HashMap 、Hashtable与 ConcurrentHashMap 的区别

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

在这里插入图片描述

HashMap线程不安全,但是效率高。相当于多个人同时干一件事。

Hashtable线程安全,但是效率低。相当于多个人排队干一件事。

ConcurrentHashMap采用分段锁机制,保证线程安全,效率又比较高。相当于可以有多个队伍排队干一件事。

排序的子类:TreeMap

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

public static void main(String[] args) { 
    Map<String, String> map = new TreeMap<String, String>(); 
    map.put("ZS", "张三"); 
    map.put("LS", "李四");
    map.put("WW", "王五");
    map.put("ZL", "赵六");
    map.put("SQ", "孙七"); 
    Set<String> set = map.keySet(); // 得到全部的key 
    Iterator<String> iter = set.iterator();
    while (iter.hasNext()) { 
        String i = iter.next(); // 得到key 
        System.out.println(i + " --:> " + map.get(i));
    } 
}

此时的结果已经排序成功了,但是从一般的开发角度来看,在使用 Map 接口的时候并不关心其是否排序,所以此类只需要知道其特点即可。

LinkedHashMap

存储时既存储在HashMap中,又存储在Linked中,这样保证了存储数据时的有序性。

关于 Map 集合的输出

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

因为 Map 接口中存放的每一个内容都是一对值,而使用 Iterator 接口输出的时候,每次取出的都实际上是一个完整的对象。如果此时非要使用 Iterator 进行输出的话,则可以按照如下的步骤进行:

1、 使用 Map 接口中的 entrySet()方法将 Map 接口的全部内容变为 Set 集合

2、 可以使用 Set 接口中定义的 iterator()方法为 Iterator 接口进行实例化

3、 之后使用 Iterator 接口进行迭代输出,每一次的迭代都可以取得一个 Map.Entry 的实例

4、 通过 Map.Entry 进行 key 和 value 的分离

那么,到底什么是 Map.Entry 呢?

Map.Entry 本身是一个接口。此接口是定义在 Map 接口内部的,是 Map 的内部接口。此内部接口使用 static 进行定义,所以此接口将成为外部接口。

实际上来讲,对于每一个存放到 Map 集合中的 key 和 value 都是将其变为了 Map.Entry 并且将 Map.Entry 保存在了Map 集合之中。

在这里插入图片描述

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

在这里插入图片描述

范例: 使用 Iterator 输出 Map 接口

public static void main(String[] args) { 
    Map<String, String> map = new HashMap<String, String>(); 
    map.put("ZS", "张三"); 
    map.put("LS", "李四"); 
    map.put("WW", "王五"); 
    map.put("ZL", "赵六"); 
    map.put("SQ", "孙七"); 
    Set<Map.Entry<String, String>> set = map.entrySet();// 变为Set实例 
    Iterator<Map.Entry<String, String>> iter = set.iterator();
    while (iter.hasNext()) { 
        Map.Entry<String, String> me = iter.next(); 
        System.out.println(me.getKey() + " --> " + me.getValue());
    }
}

以上的代码一定要记住,Map 集合中每一个元素都是 Map.Entry 的实例,只有通过 Map.Entry 才能进行 key 和 value的分离操作。

除了以上的做法之外,在 JDK 1.5 之后也可以使用 foreach 完成同样的输出,只是这样的操作基本上不使用。

public static void main(String[] args) { 
    Map<String, String> map = new HashMap<String, String>(); 
    map.put("ZS", "张三"); 
    map.put("LS", "李四"); 
    map.put("WW", "王五"); 
    map.put("ZL", "赵六"); 
    map.put("SQ", "孙七"); 
    for (Map.Entry<String, String> me : map.entrySet()) { 
        System.out.println(me.getKey() + " --> " + me.getValue());
    } 
}

两种关系

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

第一种关系:一对多关系

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

范例: 学校和学生

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; 
    } 
}
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; 
    } 
}
public class TestDemo01 {
    public static void main(String[] args) { 
        Student stu1 = new Student("张三", 10);
        Student stu2 = new Student("李四", 11);
        Student stu3 = new Student("王五", 12);
        School sch = new School("LAMP JAVA"); 
        sch.getAllStudents().add(stu1); // 一个学校有多个学生 
        stu1.setSchool(sch);// 一个学生属于一个学校 
        sch.getAllStudents().add(stu2); // 一个学校有多个学生 
        stu2.setSchool(sch);// 一个学生属于一个学校 
        sch.getAllStudents().add(stu3); // 一个学校有多个学生 
        stu3.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());
    } 
}

在这里插入图片描述

第二种关系:多对多关系

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

范例: 学生与课程

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; 
    } 
}
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.credit = credit;
        this.name = name;
    }
    public List<Student> getAllStudents() { 
        return allStudents; 
    }
    public String toString() { 
        return "课程信息:" + "\n" + "\t|- 课程名称:" + this.name + "\n" + "\t|- 课程学分: " + this.credit; 	   }
}
//下面同样在主方法处设置关系,但是必须注意的是,这个时候设置的关系也应该是双向操作完成的。
public class TestDemo02 { 
    public static void main(String[] args) { 
        Student stu1 = new Student("张三", 10); 
        Student stu2 = new Student("李四", 11); 
        Student stu3 = new Student("王五", 12); 
        Student stu4 = new Student("赵六", 15);
        Student stu5 = new Student("孙七", 13); 
        Course c1 = new Course("Oracle", 5); 
        Course c2 = new Course("Java SE基础课程", 10); 
        c1.getAllStudents().add(stu1); // 参加第一门课程 
        c1.getAllStudents().add(stu2); // 参加第一门课程 
        stu1.getAllCourses().add(c1); // 学生选择课程 
        stu2.getAllCourses().add(c1); // 学生选择课程 
        c2.getAllStudents().add(stu1); // 参加第二门课程 
        c2.getAllStudents().add(stu2); // 参加第二门课程 
        c2.getAllStudents().add(stu3); // 参加第二门课程 
        c2.getAllStudents().add(stu4); // 参加第二门课程 
        c2.getAllStudents().add(stu5); // 参加第二门课程 
        stu1.getAllCourses().add(c2); // 学生选择课程 
        stu2.getAllCourses().add(c2); // 学生选择课程 
        stu3.getAllCourses().add(c2); // 学生选择课程
        stu4.getAllCourses().add(c2); // 学生选择课程
        stu5.getAllCourses().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 集合

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

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

范例: 使用 Collections 进行增加元素的操作

public static void main(String[] args) { 
    List<String> all = new ArrayList<String>(); 
    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() 方法,在 objec 类中定义如下:

public native int hashCode();

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

java.lnag.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 输出的详细步骤

数组和集合的区别

数组

数组是java语言内置的数据类型,他是一个线性的序列,所有可以快速访问其他的元素,数组和其他语言不同,当你创建了一个数组时,他的容量是不变的,而且在生命周期也是不能改变的,还有JAVA数组会做边界检查,如果发现有越界现象,会报RuntimeException异常错误,当然检查边界会以效率为代价。

数组是相同数据类型的多个数据的容器。

这些元素按线性顺序排列。所谓线性顺序是指除第一个元素外,每一个元素都有唯一的前驱元素;除最后一个

元素外,每一个元素都有唯一的后继元素。(“简单理解就是:一个跟一个顺序排列”)。

  • 格式 1. 数据类型[] 数组名称 = new 数据类型[数组长度];
  • 格式 2. 数据类型[] 数组名称 = {数组内容 1,数组内容 2,数组内容 3…数组内容 n};
  • 格式 3. 数据类型[] 数组名;格式 3 属于只创建了数组引用名, 并未在内存创建数组空间。
  • 格式 4. 数据类型[] 数组名称 = new 数据类型[]{内容 1,内容 2,内容 3…内容 n};
//一维数组
//存储多个数据,只能存储单一类型的数据。
int[] a ={1,2,3}
int[] a = new[5]
//长度为5,根据下标取值。

多维数组

格式:int[][] 二维数组名称=new int [长度][];

注意,第二个长度可以写,也可以不写。不写的话,长度可以不相等,写的话,长度都一致。左边长度必须写。

//二维数组 在一维数组中有添加了新的数组
int[][] nums=new int[10][];
nums[0]=new int[]{1,2,3};
System.out.println(nums[0][2]);
//输出3

int[][] a ={{1,2,3},{4,5,6}}
int[][] a =new int[3][3]  
//3行3列

集合

JAVA还提供其他集合,list,map,set,他们处理对象的时候就好像这些对象没有自己的类型一样,而是直接归根于Object,这样只需要创建一个集合,把对象放进去,取出时转换成自己的类型就行了。

在jdk中按照存储结构分为两大类。单列集合Collection: List:ArrayList、LinkList、vector、set; 双列集合: Map集合无父类:hashMap、treeMap、hashTable

hashMap根据键值对存储 (Put(key,values))获取get key不允许为空 value 允许为空

Hashtable key和value 不允许为空,value可以重复

  • 数组声明了它容纳的元素的类型,而集合不声明。

  • 数组的长度是固定的,集合长度是可以改变的。

  • 数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。

  • 数组的存放的类型只能是一种(基本类型/引用类型),集合存放的类型可以不是一种(不加泛型时添加的类型是Object)。

  • 数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查都是最快的。

  • 集合以接口和类的形式存在,具有封装,继承和多态等类的特性,通过简单的方法和属性调用即可实现各种复杂的操作,大大提高软件的开发效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值