java核心类库——泛型与集合

一、泛型

1 概述

泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定 成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

2 泛型类

定义一个泛型:

public class ClassName<T>{ 
    private T data; 
    
    public T getData() { 
        return data; 
    }
    
    public void setData(T data) { 
        this.data = data; 
    } 
}

1、泛型接口

public interface IntercaceName<T>{ 
    T getData(); 
}

实现接口时,可以选择指定泛型类型,也可以选择不指定, 如下:

指定类型:

public class Interface1 implements IntercaceName<String> { 
    private String text; 
    
    @Override 
    public String getData() { 
        return text; 
    } 
} 

不指定类型:

public class Interface1<T> implements IntercaceName<T> { 
    private T data; 
    
    @Override 
    public T getData() { 
        return data; 
    } 
}

2、泛型方法

private static <T> T 方法名(T a, T b) {}

泛型限制类型

1. 在使用泛型时,可以指定泛型的限定区域

例如: 必须是某某类的子类或某某接口的实现类,
格式: <T extends 类或接口1 & 接口2>

泛型中的通配符 ?

类型通配符是使用?代替方法具体的类型实参。

1 <? extends Parent> 指定了泛型类型的上届 

2 <? super Child> 指定了泛型类型的下届 

3 <?> 指定了没有限制的泛型类型

作用

1、 提高代码复用率 
2、 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)

注意

在编译之后程序会采取去泛型化的措施。 

也就是说Java中的泛型,只在编译阶段有效。 

在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加 类型检查和类型转换的方法。

也就是说,泛型信息不会进入到运行时阶段。

二、类集概述

问:对象数组有那些问题?

答:普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构,所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现

在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完整的提出类集的完整概念。

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

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

Java 类集结构图:

在这里插入图片描述

三、Collection

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

此接口定义如下:

public interface Collection<E> extends Iterable<E>

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

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

No.方法名称类型描述
1public boolean add(E e)普通向集合中插入一个元素
2public boolean addAll(Collection<? extends E> c)普通向集合中插入一组元素
3public void clear()普通清空集合中的元素
4public boolean contains(Object o)普通查找一个元素是否存在
5public boolean containsAll(Collection<?> c)普通查找一组元素是否存在
6public boolean isEmpty()普通判断集合是否为空
7public Iterator iterator()普通 Iterator 接口实例化
8public boolean remove(Object o)普通从集合中删除一个对象
9boolean removeAll(Collection<?> c)普通从集合中删除一组对象
10boolean retainAll(Collection<?> c)普通判断是否没有指定的集合
11public int size()普通求出集合中元素的个数
12public Object[] toArray()普通以对象数组的形式返回集合中的全部内容
13public Object[] toArray()普通指定操作的泛型类型,并把内容返回
14public boolean equals(Object o)普通从 Object 类中覆写而来
15public int hashCode()普通从 Object 类中覆写而来

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

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

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

2.1 List接口

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

List 子接口的定义:

public interface List<E> extends Collection<E>

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

No.方法名称类型描述
1public void add(int index,E element)普通在指定位置处增加元素
2boolean addAll(int index,Collection<? extends E> c)普通在指定位置处增加一组元素
3public E get(int index)普通根据索引位置取出每一个元素
4public int indexOf(Object o)普通根据对象查找指定的位置,找不到返回-1
5public int lastIndexOf(Object o)普通从后面向前查找位置,找不到返回-1
6public ListIterator listIterator()普通返回 ListIterator 接口的实例
7public ListIterator listIterator(int index)普通返回从指定位置的 ListIterator 接口的实例
8public E remove(int index)普通删除指定位置的内容
9public E set(int index,E element)普通修改指定位置的内容
10List subList(int fromIndex,int toIndex)普通返回子集合

在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。

所以,证明,List 接口拥有比 Collection 接口更多的操作方法。

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

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

2.1.1 ArrayList

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

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable

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

范例:增加及取得元素

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

public class ArraylistDemo01 {

    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 接口才有定义,其他的任何接口都没有任何的定义。

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

public class ArraylistDemo01 {

    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接口单独定义的
        }
    }
}

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

2.1.2 Vector

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

public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable

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

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

public class VectorDemo01 {

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

        all.remove(1);// 根据索引删除内容,此方法是List接口单独定义的
        System.out.println("集合中的内容是:");
        for (int i = 0;i < all.size();i++){//size()方法从Collection接口继承而来
            System.out.println(all.get(i) + "、");//此方法是List接口单独定义的
        }

        all.remove("world");// 删除指定的对象
    }
}

以上的操作结果与使用 ArrayList 本身并没有任何的区别。因为操作的时候是以接口为操作的标准。

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

2.1.3 Vector 类和 ArrayList 类的区别

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

No.区别点ArrayListVector
1时间是新的类,是在JDK1.2之后推出的是旧的类是在JDK1.0的时候就定义的
2性能性能较高,采用异步处理性能较低,采用同步处理(线程安全)
3输出支持Iterator、ListIterator输出支持Iterator、ListIterator、Enumeration输出
4扩容需要扩容时,扩容0.5*初始大小需要扩容时,扩容2*初始大小

2.1.4 链表操作类:LinkedList

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

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

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

No.方法名称类型描述
1public boolean add(E e)普通增加元素,如果有容量限制,并且已满,则抛出异常
2public E element()普通取得,但是不删除当前元素,如果对列为空则抛出异常
3boolean offer(E e)普通添加,如果有容量限制,并且已满,只是无法添加,但不抛出异常,返回 false
4E peek()普通取得头元素,但是不删除,如果队列为空,则返回null
5E poll()普通取得头元素,但是删除, 如果队列为空,则返回 null
6E remove()普通删除当前元素, 如果对列为空则抛出异常

范例:验证 LinkedList 子类

import java.util.LinkedList;
import java.util.Queue;
public class LinkedListDemo01 {
    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 i=0;i < len;i++){
            System.out.println(queue.poll());
        }
        System.out.println(queue);
    }
}

2.2 Set接口

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

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

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

2.2.1 散列存放:HashSet

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

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

范例:观察 HashSet

import java.util.HashSet;
import java.util.Set;
public class HashSetDemom01 {
    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 接口中定义了将集合变为对象数组进行输出。

import java.util.HashSet;
import java.util.Set;
public class HashSetDemom01 {
    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 i = 0;i < obj.length;i++){
            System.out.println(obj[i] + "、");
        }
    }
}

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

型变为指定的数组。

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

import java.util.HashSet;
import java.util.Set;
public class HashSetDemom01 {
    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 i = 0;i < str.length;i++){
            System.out.println(str[i] + "、");
        }
    }
}

2.2.2 排序的子类:TreeSet

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

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable

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

import java.util.Set; 
mport java.util.TreeSet; 
public class TreeSetDemo01 { 
    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); 
    } 
}

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

2.2.3 排序的说明

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

范例:定义 Person 类

public 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 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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

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

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

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

上述代码运行结果如下:
在这里插入图片描述
此时的提示是:Person 类不能向 Comparable 接口转型的问题?

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

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

    @Override
    public int compareTo(Person per) {
        if (this.age > per.age) { 
            return 1; 
        } else if (this.age < per.age) { 
            return -1; 
        } else { 
            return 0; 
        }
    }

    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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

再次运行TreeSetPersonDemo01.java的结果为:
在这里插入图片描述
从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则此时必须修改 Person 类,如果假设年龄相等的话,按字符串进行排序。

public int compareTo(Person per) {
    if (this.age > per.age) {
        return 1;
    } else if (this.age < per.age) {
        return -1;
    } else {
        return this.name.compareTo(per.name);
    }
 }

2.2.4 关于重复元素的说明

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

public static void main(String[] args) {
    Set<Person> all = new HashSet<>();
    all.add(new Person("张三", 10));
    all.add(new Person("李四", 10));
    all.add(new Person("王五", 25));
    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 集合中也是不能去掉重复值的。

2.3 集合输出

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

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

一般原则:只要是碰到了集合,则输出的时候想都不想就使用 Iterator 进行输出。

2.3.1 Iterator(重点!!!)

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

此接口定义如下:

public interface Iterator<E>

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

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

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

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

import java.util.ArrayList; 
import java.util.Collection; 
import java.util.Iterator; 
public class IteratorDemo01 { 
    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()方法。否则将出现未知的错误。

import java.util.ArrayList; 
import java.util.Collection; 
import java.util.Iterator; 
public class IteratorDemo02 { 
    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。

2.3.2 ListIterator(理解)

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

public interface ListIterator<E> extends Iterator<E>

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

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

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

List 接口中定义了以下的方法: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(); 
    while (iter.hasNext()) {// 判断是否有下一个元素 
        System.out.print(iter.next() + "、");
    } 
    System.out.print("\n从后向前输出:");
    while (iter.hasPrevious()) {
        System.out.print(iter.previous() + "、");
    }	
} 

但是,此处有一点需要注意的是,如果要想进行由后向前的输出,则首先必须先进行由前向后的输出。此接口一般使用较少.

2.3.3 Enumeration(废弃的接口,了解即可)

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

此接口定义如下:

public interface Enumeration<E>

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

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

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

public Enumeration elements()

范例:验证 Enumeration 接口

package org.listdemo.enumerationdemo; 
import java.util.Enumeration; 
import java.util.Vector;
public class EnumerationDemo01 { 
    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()); 
        } 
    } 
}

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

2.3.4 foreach

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

public static void main(String[] args) {
    List<String> arr = new ArrayList<String>();
    arr.add("你好");
    arr.add("我好");
    arr.add("大家好");

    //foreach循环
    for(String str : arr){                      //这里的str就是为了获取每次循环的arr中的值
        System.out.println(str);               //就相当于 String str=arr[i]
    }
}

四、Map

Collection 中,每次操作的都是一个对象,

Map中,每次操作的都是一对对象(key—value)

接口定义:

public interface Map<K,V>

此接口常用方法如下:

No.方法名称类型描述
1void clear()普通清空 Map 集合中的内容
2boolean containsKey(Object key)普通判断集合中是否存在指定的 key
3boolean containsValue(Object value)普通判断集合中是否存在指定的 value
4Set<Map.Entry<K,V>> entrySet()普通 Map 接口变为 Set 集合
5V get(Object key)普通根据 key 找到其对应的 value
6boolean isEmpty()普通判断是否为空
7Set keySet()普通将全部的 key 变为 Set 集合
8Collection values()普通将全部的 value 变为 Collection 集合
9V put(K key,V value)普通向集合中增加内容
10void putAll(Map<? extends K,? extends V> m)普通增加一组集合
11V remove(Object key)普通根据 key 删除内容

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

4.1 HashMap(重点!!!)

此类的定义:

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

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

范例

public static void main(String[] args) { 
    HashMap<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); //根据指定的 key 查找内容
    System.out.println(val); 
    
    Set<Integer> set = map.keySet(); // 得到全部的key
    Collection<String> value = map.values(); // 得到全部的value
    Iterator<Integer> iter = set.iterator();

    //循环输出 Map 中的全部内容
    while (iter.hasNext()) {
        Integer i = iter.next(); // 得到key
        System.out.println(i + " --:> " + map.get(i));
    }
}

4.2 Hashtable(重点!!!)

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

public static void main(String[] args) { 
    Hashtable<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); 
    } 
}

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

4.3 HashMap Hashtable 的区别

No.区别点HashMapHashtable
1推出时间JDK 1.2 之后推出的,新的操作类JDK 1.0 时推出的,旧的操作类
2性能异步处理,性能较高同步处理,性能较低
3null允许设置为 null不允许设置,否则将出现空指向异常

4.4 TreeMap(理解)

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

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

public class TreeMapDemo {
    public static void main(String[] args) {
        TreeMap<String, String> map = new TreeMap<>();
        map.put("张三","物理");
        map.put("李四","化学");
        map.put("王五","英语");
        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 接口的时候并不关心其是否排序,所以不常用。

4.5 Map集合的输出

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

因为 Map 接口中存放的是一对对象(key,value),而使用 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 接口中以下的方法最为常用:

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

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

public static void main(String[] args) { 
    HashMap<String, String> map = new HashMap<String, String>(); 
    map.put("张三","物理");
    map.put("李四","化学");
    map.put("王五","英语"); 
    
    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()); 
    } 
    
    //使用 foreach 完成同样的输出(基本不使用)
    for (Map.Entry<String, String> me : map.entrySet()) {
        System.out.println(me.getKey() + " --> " + me.getValue()); 
    }
}

五、Collections 类(理解)

Collections 实际上是一个集合的操作类,与 Collection 接口没有任何的关系,是一个单独存在的类。

此类的定义如下:

public class Collections extends Object

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

范例:返回空的 List 集合

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

在这里插入图片描述
使用 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 与内存泄露(理解)

6.1 equals

equals的作用是判断两个对象是否相等,如果对象重写了equals()方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,价于“==”。同样的,equals()定义在JDK的Object.java中,这就意味着Java中的任何类都包含有equals()函数。

equals()方法在 object 类中定义如下:

public boolean equals(Object obj) { 
    return (this == obj); 
}

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

Java 语言对 equals()的要求如下,重写 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”。

6.2 hashcode

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。

hashcode() 方法,在 object 类中定义如下:

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()是判断两个对象是否相等的关键)

equals和hashcode间的关系:

1.如果两个对象相同(即equals比较返回true),那么他们的hashcode一定要相等

2.如果他们的hashcode相等,他们的equals不一定相等

为什么重写equals还要重写hashcode

假设两个对象,重写了其equals方法,其相等条件是属性相等,equals比较之后返回true说明假设的两个对象相等,如果没有重写hashcode的话,其返回的依然是两个对象的内存地址,那么他们的hashcode不相同,这和上面的equals和hashcode间的关系相矛盾!所以如果根据equals(Object)方法,两个对象是相等的,那么在两个对象中的每个对象上调用hashCode()方法必须生成相同的整数结果。

注意:相等的对象必须有相同的散列码,反之散列码相同则不一定对象相等,而且不相等的对象并不一定需要有不同的散列码。

提示:

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

如一地返回同一个整数。
2. 如果两个对象根据 equals(Object o)方法是相等的,则调用这两个对象中任一对象的 hashCode 方法必须产生相同的整数结果。
3. 如果两个对象根据 equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的 hashCode 方法,不要求产生 不同的整数结果。但如果能不同,则可能提高散列表的性能。

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

(1)判断两个对象的 hashCode 是否相等 
	如果不相等,认为两个对象也不相等,完毕 
	如果相等,转入 (2)
(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们 这里将其做为必需的。后面会重点讲到这个问题。) 

(2)判断两个对象用 equals 运算是否相等 
	如果不相等,认为两个对象也不相等 
	如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)

equals和hashcode间的关系:

1.如果两个对象相同(即equals比较返回true),那么他们的hashcode一定要相等

2.如果他们的hashcode相等,他们的equals不一定相等

为什么重写equals还要重写hashcode

假设两个对象,重写了其equals方法,其相等条件是属性相等,equals比较之后返回true说明假设的两个对象相等,如果没有重写hashcode的话,其返回的依然是两个对象的内存地址,那么他们的hashcode不相同,这和上面的equals和hashcode间的关系相矛盾!所以如果根据equals(Object)方法,两个对象是相等的,那么在两个对象中的每个对象上调用hashCode()方法必须生成相同的整数结果。

注意:相等的对象必须有相同的散列码,反之散列码相同则不一定对象相等,而且不相等的对象并不一定需要有不同的散列码。

提示:

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值