Java基础--------集合框架

2023-11-12

(参考http://blog.csdn.net/zhongkelee/article/details/46801449 点击打开链接,以此为模板 自己做了整理、修改)

目录

一. 概念

二. 集合框架的体系

2.1 Collection接口

2.1.1 List接口

2.1.2 Set接口

2.2 Map接口,>

2.3 Iterator接口

2.4 Enumeration接口

2.5 集合框架的工具类

2.5.1 Collections类

2.5.2 Arrays类

三. 数组与集合之间的转换

四. 泛型

五. 总结


一. 概念

集合:通常情况下,把具有某种相同性质的一类东西,汇聚成的集体,称为集合。比如,用Java编程的所有程序员,全体中国人等。在数学中的定义是,由一个或多个确定的元素所构成的整体叫做集合。

Java中的集合:是一种工具包,就像是一个容器,里面存储着任意数量的具有共同属性的对象。它包含了集合、链表、队列、栈、映射等常用的数据结构。Java集合可以划分为4个部分:List列表、Set集合、Map映射、Iterator迭代器、Enumeration枚举类、工具类(Arrays和Connections)。其实,可以把一个集合类看成一个微型数据库,操作不外乎“增删改查”四种,我们在学习使用一个具体的集合类时,需要把这四个操作的时空复杂度弄清楚了,基本上就可以说掌握这个类了。

集合与数组的比较:数组中的元素可以是基本数据类型,也可以是对象(实际上数组中保存的是对象的引用变量),而集合中只能保存对象(实际上也是保存对象的引用变量,但通常习惯上认为集合类中保存的是对象)。数组的长度是固定的,而集合长度可变。数组只能通过下标访问数组元素,且类型固定,而有的集合可以通过任意类型查找所映射的具体对象。

集合框架:集合框架是为表示和操作Java中的集合而规定的一种统一的、标准的体系结构。集合框架包含三大块内容:对外的接口、接口的实现和对集合运算的算法。

    接口:是代表集合的抽象数据类型。接口允许集合独立操纵其代表的细节。在面向对象的语言,接口通常形成一个层次。

    实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构。

    算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

集合框架的主要理念用一句话概括就是:提供一套“小而美”的API。API需要对程序员友好,增加新功能时能让程序员们快速上手。此外,所有的集合类都必须能提供友好的交互操作,这包括没有继承Collection类的数组对象。因此,框架提供一套方法,让集合类与数组可以相互转化,并且可以把Map看作成集合。Java 集合框架提供了一套性能优良,使用方便的接口和类,java集合框架位于java.util包中, 所以当使用集合框架的时候需要进行导包,import java.util.*。

二. 集合框架的体系

上图完整地给出了集合框架体系中所包含的接口,实现类。学习接口和类的最好方法就是查看它们的源码,并配合使用API文档。下面我们抽取出比较核心的部分,得到简化版框架图,如下:

2.1 Collection<E>接口

    Collection:中可以存储的元素间无序,可以重复组各自独立的元素, 即其内的每个位置仅持有一个元素,同时允许有多个null元素对象。
         |--List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引,允许重复元素。
         |--Set:无序(存入和取出顺序有可能不一致),不允许重复元素,必须保证元素的唯一性。
    java.util.Collection接口中的方法:

可以看出,Collection接口中的主要方法还是相关的增删改查注意:成功之下添加方法和删除方法,集合的长度会改变。

boolean retainAll(Collection c):对当前集合中保留和指定集合中的相同的元素。如果两个集合元素相同,返回false;如果retainAll修改了当前集合,返回true。 

toArray() 和toArray([])将集合转成数组。

Iterator iterator():获取集合中元素上迭代功能的迭代器对象。取出集合元素。Iterator中只有hasNext(),next(),remove()方法

equals(Object):有时候需要重写

hashCode():

2.1.1 List<E>接口

   List本身是Collection接口的子接口,具备了Collection的所有方法。List 体系特有的共性方法都有索引(角标),这是该集合最大的特点。也就是说,List的特有方法都是围绕索引(角标)定义的。List集合的又分了具体的子类,子类之所以要区分开是因为List的不同子类内部的数据结构(存储数据的方式)不同。
   List:有序(元素存入集合顺序和取出一致),元素都有索引,允许重复元素-->自定义元素类型都要复写equals方法。 
        |--ArrayList:底层的也是数组结构,也是长度可变的,可以理解为长度可变的数组线程不同步的,替代了Vector。增删速度不快。查询速度很快。(因为在内存中是连续空间)
        |--LinkedList:底层的数据结构是链表,线程不同步的。与ArrayList相反,增删速度很快,查询速度较慢。(因为在内存中需要一个个查询、判断地址来寻找下一元素)

        |--Vector:底层的数据结构是数组。数组是可变长度的。线程同步的。增删和查询都巨慢!,Vector几乎被ArrayList所替代了,其实可以理解为Vector是一个支持多线程的ArrayList,只是Vector的增删和查询速度过慢,性能差,几乎不被使用。

    可变长度数组的原理:不断地new 新数组并将原数组元素复制到新数组。即当元素超出数组长度,会产生一个新数组,将原数组的数据复制到新数组中,再将新的元素添加到新数组中。其中, 而Vector是允许设置默认的增长长度,Vector的默认扩容方式为原来的2倍。而ArrayList扩容问题相对复杂一些,关注源码中的 int newCapacity = oldCapacity + (oldCapacity >> 1),从这里就可以得到一个结论,每次扩容的容量是之前容量的1.5倍(除了空数组下的扩容)。

空数组的情况下满足newCapacity - minCapacity < 0的条件,所以newCapacity=10
当第二次扩容时,又是如何变化的呢?当add第11个元素时,minCapacity=11,在grow方法中,newCapacity=10 + 10>>1 = 15,故而第二次扩容的容量为15。注意下,那么第三次扩容呢?按照1.5倍的说法计算:15 * 1.5 = 22.5,那应该是22还是23?所以一定要注意底层实现是根据位移运算:15 + 15 >> 1 = 22
总结:
1.第一次进行add操作时,默认扩容容量为10
2.扩容的条件:既存元素数 + 1 > 容量
2.扩容规则为:oldCapacity + oldCapacity >> 1,可理解为旧容量的1.5倍。原容量+原容量除以2并向下取整

 List集合支持对元素的增、删、改、查。
    1.添加(增):
        add(index, element):在指定的索引位插入元素。
        addAll(index, collection):在指定的索引位插入一堆元素。
    2.删除(删):
        remove(index):删除指定索引位的元素。 返回被删的元素。
    3.修改(改):
        element set(index, newElement):对指定索引位进行元素的修改。
    4.获取(查):
        element get(index):通过索引获取指定元素。
        int indexOf(element):获取指定元素第一次出现的索引位,如果该元素不存在返回—1;所以,通过—1,可以判断一个元素是否存在。
        int lastIndexOf(element) :反向索引指定元素的位置。
        List subList(start,end) :获取子列表。 
    5.获取所有元素(查全部):

    ListIterator listIterator():list集合特有的迭代器。 在进行list列表元素迭代的时候,如果想要在迭代过程中,想要对元素进行操作的时候。比如,迭代过程中执行添加,会发生ConcurrentModificationException并发修改异常。那是因为集合引用和迭代器引用在同时操作元素,通过集合获取到对应的迭代器后,在迭代中,进行集合引用的元素添加,迭代器并不知道,所以会出现异常情况。如何解决呢?Iterator中只有hasNext(),next(),remove()方法。而ListIterator这个列表迭代器接口具备了对元素的增、删、改、查的动作。

ArrayList<E>类:

接下来先讨论List接口的第一个重要子类:java.util.ArrayList<E>类,我这里先抛开泛型不说,本篇后面有专门阐述。但要注意,由于还没有使用泛型,利用Iterator的next()方法取出的元素必须向下转型,才可使用子类特有方法。针对ArrayList类,我们最需要注意的是,ArrayList的contains()方法底层使用的equals()方法判别的,所以自定义元素类型中必须复写Object的equals()方法。

案例一,往ArrayList中存储自定义对象Person(name, age),代码如下:

package ustc.lichunchun.list.test;  
  
import java.util.ArrayList;  
import java.util.Iterator;  
import java.util.List;    
import ustc.lichunchun.domian.Person;  
  
public class ArrayListTest {    
    public static void main(String[] args) {  
          
        //1.创建ArrayList集合对象。注意:由超类声明,子类来new。调用的最终是子类中定义的方法,如果子类没有,则调用子类的父类方法。这存在一种向上追溯的过程。
        //在list当中将可以调用object当中所有声明public的方法,而调用的方法实体是来自ArrayList的。
	//而之所以list不可以调用,clone()与finalize()方法,只是因为它们是protected的。    
        List list = new ArrayList();  
          
        //2.添加Person类型的对象。 注意:在这个自定义对象Person类中,必须重写toString()方法,最后直接打印p 
        Person p1 = new Person("lisi1", 21);  
        Person p2 = new Person("lisi2", 22);  
          
	//3.往list这个ArrayList中添加Person类的对象。
        list.add(p1);//add(Object obj)  
        list.add(p2);  
        list.add(new Person("lisi3", 23));  
          
        //4.取出元素。  
        for (Iterator it = list.iterator(); it.hasNext();) {  
            //it.next():取出的元素都是Object类型的。需要用到具体对象内容时,需要向下转型。  
            Person p = (Person)it.next();  
            System.out.println(p.getName()+":"+p.getAge());//如果不向下转型,Object类对象没有getName、getAge方法。  
        }  
    }  
} 

案例二,从ArrayList中取出重复的自定义元素。

第1步,先重写Person类中的equals()、toString()。记住:往集合里面存储自定义元素,该元素所属类一定要重写equals()、toString()方法!,代码如下:

package ustc.lichunchun.domian;  
  
public class Person{  
    private String name;  
    private int age;  
    public Person() {  
        super();  
    }  
    public Person(String name, int age) {  
        super();  
        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() {  //重写了toString()方法,为了满足最后的打印pintln()
        return "Person [name=" + name + ", age=" + age + "]";  
    }  
    /* 
     * 建立Person类自己的判断对象是否相同的依据,必须要覆盖Object类中的equals方法。 
     */  
    public boolean equals(Object obj) {  //重写了equals()方法,为了满足contain()的调用
        //为了提高效率,如果比较的对象是同一个,直接返回true即可。  
        if(this == obj)  
            return true;            
        if(!(obj instanceof Person))  
                throw new ClassCastException("类型错误");  
        Person p = (Person)obj;            
        return this.name.equals(p.name) && this.age==p.age;  
    }  
    /*@Override 
    public boolean equals(Object obj) { 
        if (this == obj) 
            return true; 
        if (obj == null) 
            return false; 
        if (getClass() != obj.getClass()) 
            return false; 
        Person other = (Person) obj; 
        if (age != other.age) 
            return false; 
        if (name == null) { 
            if (other.name != null) 
                return false; 
        } else if (!name.equals(other.name)) 
            return false; 
        return true; 
    }*/  
}  

第2步,在完成上面操作之后,再来操作这个Person类所创建的实体类对象,代码如下:

package ustc.lichunchun.list.test;  
  
import java.util.ArrayList;  
import java.util.Iterator;  
import java.util.List;  
  
import ustc.lichunchun.domian.Person;  
  
public class ArrayListTest3 {  
  
    public static void main(String[] args) {  
 
        List list = new ArrayList();  
	//上文代码中已经对Person类中的equals()和toString()方法进行了重写,equals()的重写是为了满足contain()的调用,toString()的重写是为了满足pintln()的调用
        Person p = new Person("li",19);  
	//往list这个ArrayList中添加Person类的实体类对象
        list.add(p);  
        list.add(p);//存储了一个地址相同的对象。在equals方法中直接先this==obj即可。  
        list.add(new Person("li",20));  
        list.add(new Person("li",23));  
        list.add(new Person("li",26));  
        list.add(new Person("li",23));  
        list.add(new Person("li",26));  
        list.add(new Person("li",20));  
          
        System.out.println(list);  
        singleElement(list);  //调用具体方法,取出重复的元素
        System.out.println(list);  
    }  
    public static void singleElement(List list){  
        List temp = new ArrayList();  
        for (Iterator it = list.iterator(); it.hasNext();) {  
            Object obj = (Object) it.next();  
            if(!temp.contains(obj))// --> contains()方法底层调用的是容器中元素对象的equals()方法!  
                //这里如果Person类自身不定义equals方法,就使用Object的equals()方法,比较的就仅仅是地址了。  
                temp.add(obj);  
        }  
        list.clear();  
        list.addAll(temp);  
    }  
}  

案例三,ArrayList实现遍历的几种方法,代码如下:

import java.util.Iterator;
import java.util.List;
 
/**
 * 测试类
 * @author 小浩
 * @创建日期 2015-3-2
 */

public class Test{
public static void main(String[] args) {
     List<String> list=new ArrayList<String>();
     list.add("Hello");
     list.add("World");
     list.add("HAHAHAHA");
     //第一种遍历方法使用foreach遍历List
     for (String str : list) {            //也可以改写for(int i=0;i<list.size();i++)这种形式
        System.out.println(str);
    } 
     //第二种遍历,把链表变为数组相关的内容进行遍历
    String[] strArray=new String[list.size()]; //先定义一个数据类型一致的数组
    list.toArray(strArray); //将List转换成刚才定义的数组
    for(int i=0;i<strArray.length;i++) //这里也可以改写为foreach(String str:strArray)这种形式
    {
        System.out.println(strArray[i]);
    }     
    //第三种遍历 使用迭代器进行相关遍历    
     Iterator<String> ite=list.iterator();
     while(ite.hasNext())
     {
         System.out.println(ite.next());
     }
 }
}

LinkedList<E>类:

java.util.LinkedList<E>类是List接口的链表实现,底层是一个双向循环链表,可以利用LinkedList实现堆栈、队列这两个数据结构。它的特有方法有如下这些:    
    addFirst();
    addLast();
    在jdk1.6以后:
    offerFirst();
    offerLast();
    removeFirst():获取链表中的第一个元素,并删除链表中的第一个元素。如果链表为空,抛出NoSuchElementException
    removeLast();
    在jdk1.6以后:
    pollFirst();获取链表中的第一个元素,并删除链表中的第一个元素。如果链表为空,返回null。
    pollLast();
    getFirst():获取链表中的第一个元素。如果链表为空,抛出NoSuchElementException;
    getLast();
    在jdk1.6以后:
    peekFirst();获取链表中的第一个元素。如果链表为空,返回null。
    peekLast();

案例,通过LinkedList实现一个堆栈、队列数据结构(堆栈:先进后出。First In Last Out FILO。队列:先进先出。First In First Out   FIFO)。代码如下:

package ustc.lichunchun.list.linkedlist;  
  
import java.util.LinkedList;  
/* 
 * 描述一个队列数据结构。内部使用的是LinkedList。 
 */  
public class MyQueue {  
    private LinkedList link;   //以LinkedList这个类对象作为私有化属性
    MyQueue() {  
        link = new LinkedList();  
    }    
    /** 
     * 添加元素的方法。 
     */  
    public void myAdd(Object obj) {  
        // 内部使用的是LinkedList的方法。  
        link.addFirst(obj);  
    }   
    /** 
     * 获取队列元素的方法。 
     */  
    public Object myGet() {  
        return link.removeFirst();  
    }  
    /** 
     * 集合中是否有元素的方法。 
     */  
    public boolean isNull() {  
        return link.isEmpty();  
    }  
}  

package ustc.lichunchun.list.linkedlist;  
  
import java.util.LinkedList;   
/* 
 *  实现一个堆栈结构。内部使用的是LinkedList。 
 */  
public class MyStack {  
    private LinkedList link;  
  
    MyStack() {  
        link = new LinkedList();  
    }   
    public void myAdd(Object obj) {  
        link.addFirst(obj);  
    }    
    public Object myGet() {  
        return link.removeLast();  
    }  
    public boolean isNull() {  
        return link.isEmpty();  
    }  
}  
package ustc.lichunchun.list.linkedlist;  
  
import java.util.LinkedList;  
//此为测试类 
public class LinkedListTest {  
    public static void main(String[] args) {  
        /* 
         * 练习:请通过LInkedList实现一个堆栈,或者队列数据结构。 
         * 堆栈:先进后出。First In Last Out      FILO. 
         * 队列:先进先出。First In First Out  FIFO. 
         */        
        //1.创建自定义的队列对象。  
        MyQueue queue = new MyQueue();  
          
        //2.添加元素。  
        queue.myAdd("abc1");  
        queue.myAdd("abc2");  
        queue.myAdd("abc3");  
        queue.myAdd("abc4");  
          
        //3.获取所有元素。先进先出。  
        while(!queue.isNull())  
            System.out.println(queue.myGet());  
        System.out.println("--------------------------");  
          
        //1.创建自定义的堆栈对象。  
        MyStack stack = new MyStack();  
          
        //2.添加元素。  
        stack.myAdd("def5");  
        stack.myAdd("def6");  
        stack.myAdd("def7");  
        stack.myAdd("def8");  
          
        //3.获取所有元素。先进后出。  
        while(!stack.isNull())  
            System.out.println(stack.myGet());  
    }  
}  

2.1.2 Set<E>接口

    java.util.Set<E>接口,一个不包含重复元素的 collection。更确切地讲,set 不包含满足e1.equals(e2) 的元素对e1 和e2,并且最多包含一个 null 元素
    Set:不允许重复元素。和Collection的方法相同。Set集合取出方法只有一个:迭代器。
        |--HashSet:底层数据结构是哈希表(散列表)。无序,比数组查询的效率高。线程不同步的。
                 -->根据哈希冲突的特点,为了保证哈希表中元素的唯一性,
                      该容器中存储元素所属类应该复写Object类的hashCode()、equals()方法。
                |--LinkedhashSet:有序,HashSet的子类。
        |--TreeSet:底层数据结构是二叉树。可以对Set集合的元素按照指定规则进行排序。线程不同步的。
                -->add方法新添加元素必须可以同容器已有元素进行比较,
                     所以元素所属类应该实现
Comparable接口的compareTo() 方法,以完成排序。

             或者添加Comparator比较器,实现compare() 方法。

代码示例,如下:

package ustc.lichunchun.set.demo;  
  
import java.util.HashSet;  
import java.util.Iterator;  
import java.util.Set;  
  
public class HashSetDemo {  
    public static void main(String[] args) {         
        //1.创建一个Set容器对象。  
        Set set = new HashSet();  
          
        //Set set = new LinkedHashSet();如果改成LinkedHashSet,可以实现有序。  
          
        //2.添加元素。  
        set.add("haha");  
        set.add("nba");  
        set.add("abc");  
        set.add("nba");  
        set.add("heihei");  
          
        //3.只能用迭代器取出。  
        for (Iterator it = set.iterator(); it.hasNext();) {  
            System.out.println(it.next());  
        }  
    }  
}  

HashSet<E>类:

    java.util.HashSet<E>类实现Set 接口,由哈希表(实际上是一个HashMap 实例)支持,哈希表就是存储哈希值的结构HashSet接口不保证 set 的迭代顺序,特别是它不保证该顺序恒久不变。此类允许使用null 元素。堆内存的底层实现就是一种哈希表结构,需要通过哈希算法来计算对象在该结构中存储的地址。这个方法每个对象都具备,叫做hashCode()方法,隶属于java.lang.Objecct类。hashCode()方法本身调用的是windows系统本地的算法,也可以自己定义。

    哈希表的原理:
    1.对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值称为哈希值
    2.哈希值就是这个元素的位置
    3.如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。
       如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。
    4.存储哈希值的结构,我们称为哈希表
    5.既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字是唯一的。
       这样可以尽量少的判断关键字对应的对象是否相同,提高了哈希表的操作效率。

    哈希表的特点:
    1.不允许存储重复元素,因为会发生查找的不确定性。
    2.不保证存入和取出的顺序一致,即不保证有序。
    3.比数组查询的效率高。

    哈希冲突:
    当哈希算法算出的两个元素的值相同时,称为哈希冲突冲突后,需要对元素进行进一步的判断。判断的是元素的内容,equals。如果不同,还要继续计算新的位置,比如地址链接法,相当于挂一个链表扩展下来。

    如何保证哈希表中元素的唯一性?
    元素必须重写hashCode() 方法 和 equals() 方法。
    
重写hashCode() 方法是为了根据元素自身的特点确定哈希值。
    重写equals() 方法,是为了解决哈希值的冲突。


    如何实现有序?
    LinkedHashSet类,可以实现有序。

案例,往HashSet中存储学生对象(姓名,年龄),出现同姓名、同年龄的学生则视为同一个学生,不存。第1步,在学生类中,要重写里面的hashcode() 方法 和 equals() 方法。代码如下:

package ustc.lichunchun.domian;  
  
public class Student {  
    private String name;  
    private int age;  
    public Student() {  
        super();  
    }  
    public Student(String name, int age) {  
        super();  
        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 + "]";  
    }  
    /* 
    @Override 
    public int hashCode() { 
        final int prime = 31; 
        int result = 1; 
        result = prime * result + age; 
        result = prime * result + ((name == null) ? 0 : name.hashCode()); 
        return result; 
    } 
    @Override 
    public boolean equals(Object obj) { 
        if (this == obj) 
            return true; 
        if (obj == null) 
            return false; 
        if (getClass() != obj.getClass()) 
            return false; 
        Student other = (Student) obj; 
        if (age != other.age) 
            return false; 
        if (name == null) { 
            if (other.name != null) 
                return false; 
        } else if (!name.equals(other.name)) 
            return false; 
        return true; 
    } 
    */  
      
     //重写hashCode() 方法,根据对象自身的特点定义哈希值。  
    public int hashCode(){  
        final int NUMBER = 31;  
        return name.hashCode()+ age*NUMBER;  
    }  
      
     //需要定义对象自身判断内容相同的依据。重写equals() 方法,解决哈希值的冲突。
    public boolean equals(Object obj){  
        if (this == obj)  
            return true;  
        if(!(obj instanceof Student))  
            throw new ClassCastException(obj.getClass().getName()+"类型错误");  
        Student stu = (Student)obj;  
        return this.name.equals(stu.name) && this.age == stu.age;  
    }  
}  

第2步,往HashSet中存储学生对象。代码如下:

package ustc.lichunchun.set.demo;  
  
import java.util.HashSet;  
import java.util.Iterator;  
import java.util.Set;  
  
import ustc.lichunchun.domian.Student;  
  
public class HashSetTest {  
  
    public static void main(String[] args) {  
        /* 
         * 练习:往HashSet中存储学生对象(姓名,年龄)。同姓名、同年龄视为同一个人,不存。 
         * 1.描述学生。 
         * 2.定义容器。 
         * 3.将学生对象存储到容器中。 
         *  
         * 发现存储了同姓名、同年龄的学生是可以的。 
         * 原因是每一次存储学生对象,都先调用hashCode()方法获取哈希值。 
         * 但此时调用的是Object类中的hashCode。所以同姓名同年龄了,但因为是不同的对象,哈希值也不同。 
         * 这就是同姓名同年龄存入的原因。 
         *  
         * 解决: 
         * 需要根据学生对象自身的特点来定义哈希值。 
         * 所以就需要覆盖hashCode方法。 
         *  
         * 发现,当hashCode返回值相同时,会调用equals方法比较两个对象是否相等。 
         * 还是会出现同姓名同年龄的对象,因为子类没有复写equals方法, 
         * 直接用Object类的equals方法仅仅比较了两个对象的地址值。 
         * 这就是同姓名同年龄还会存入的原因。 
         *  
         * 解决: 
         * 需要定义对象自身判断内容相同的依据。 
         * 所以就需要覆盖equals方法。 
         *  
         * 效率问题: 
         * 尽量减少哈希算法求得的哈希值的冲突。减少equals方法的调用。 
         */  
        //1.创建容器对象。  
        Set set = new HashSet();  
          
        //2.存储学生对象。  
        set.add(new Student("xiaoqiang",20));  
        set.add(new Student("wangcai",27));  
        set.add(new Student("xiaoming",22));  
        set.add(new Student("xiaoqiang",20));  
        set.add(new Student("daniu",24));  
        set.add(new Student("xiaoming",22));  
          
        //3.获取所有学生。  
        for (Iterator it = set.iterator(); it.hasNext();) {  
            Student stu = (Student) it.next();  
            System.out.println(stu.getName()+":"+stu.getAge());  
        }  
    }  

ArrayList存储元素依赖的是equals()方法。比如remove()、contains()底层判断用的都是equals()方法。 

HashSet判断元素是否相同依据的是hashCode()和equals()方法。如果哈希冲突(哈希值相同),再判断元素的equals()方法。如果equals()方法返回true,不存;返回false,存储!

TreeSet<E>类:

    java.util.Set<E>TreeSet类基于TreeMap的NavigableSet实现。使用元素的自然顺序(Comparable接口的compareTo()方法)对元素进行排序,或者根据创建 set 时提供的自定义比较器(Comparator的compare()方法)进行排序,具体取决于使用的构造方法。此实现为基本操作(add、remove和contains)提供受保证的 log(n) 时间开销。TreeSet底层是自平衡的二叉树结构,二叉树结构特点是可以排序。并且对二叉树的建立过程内部优化,基于折半的排序思想,以减少比较次数。
    TreeSet:可以对元素排序。注意:排序和有序是两个不同的概念。
    有序:存入和取出的顺序一致。--> List

    排序:升序or降序。--> TreeSet

通过案例,了解TreeSet对元素进行排序的功能,代码如下:

package ustc.lichunchun.set.demo;  
  
import java.util.Iterator;  
import java.util.Set;  
import java.util.TreeSet;  
  
import ustc.lichunchun.domian.Student;  
  
public class TreeSetDemo {  
  
    public static void main(String[] args) {  
        Set set = new TreeSet();  
          
        set.add("abc");  
        set.add("heihei");  
        set.add("nba");  
        set.add("haha");  
        set.add("heihei");  
          
        for (Iterator it = set.iterator(); it.hasNext();) {  
            System.out.println(it.next());  
        }  
    }  
}  

那如果往TreeSet集合中存入的是自定义元素呢?这就需要元素自身具备比较功能。所以元素需要实现Comparable接口,并重写compareTo()方法。如果元素不具备比较性,在运行时会发生ClassCastException异常。TreeSet能够进行排序,但是自定义的Person类需要给出排序的规则。即普通的自定义类不具备排序的功能,所以元素要实现Comparable接口,并重写compareTo()方法,强制让元素具备比较性,重写compareTo()方法。
    如何保证元素唯一性?
    参考的就是比较方法(比如compareTo() )的返回值是否为0。是0,就是重复元素,不存。

    注意:在进行比较时,判断元素是否唯一,需要分主要条件和次要条件,当主要条件相同时,再判断次要条件,按照次要条件排序。比如,同姓名同年龄,才视为同一个人。

案例, 往TreeSet集合存入上文中所描述的学生类对象,并要求按照年龄进行排序。代码如下:

package ustc.lichunchun.domian;  
  
/* 
 * 学生类本身继承自Object类,具备一些方法。 
 * 我们想要学生类具备比较的方法,就应该在学生类的基础上进行功能的扩展。 
 * 比较的功能已经在Comparable接口中定义下来了,学生类只需要实现Comparable接口即可。  
 * 记住:需要对象具备比较性,只要让对象实现comparable接口即可。 
 */  
public class Student implements Comparable{  //自定义元素要实现Comparable接口,并重写其中的compareTo()方法
    private String name;  
    private int age;  
    public Student() {  
        super();  
    }  
    public Student(String name, int age) {  
        super();  
        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 + "]";  
    }  
    /* 
    @Override 
    public int hashCode() { 
        final int prime = 31; 
        int result = 1; 
        result = prime * result + age; 
        result = prime * result + ((name == null) ? 0 : name.hashCode()); 
        return result; 
    } 
    @Override 
    public boolean equals(Object obj) { 
        if (this == obj) 
            return true; 
        if (obj == null) 
            return false; 
        if (getClass() != obj.getClass()) 
            return false; 
        Student other = (Student) obj; 
        if (age != other.age) 
            return false; 
        if (name == null) { 
            if (other.name != null) 
                return false; 
        } else if (!name.equals(other.name)) 
            return false; 
        return true; 
    } 
    */  
      
     //重写hashCode方法。根据对象自身的特点定义哈希值。  
    public int hashCode(){  
        final int NUMBER = 31;  
        return name.hashCode()+ age*NUMBER;  
    }  
      
     //需要定义对象自身判断内容相同的依据。重写equals方法。  
    public boolean equals(Object obj){  
        if (this == obj)  
            return true;  
        if(!(obj instanceof Student))  
            throw new ClassCastException(obj.getClass().getName()+"类型错误");  
        Student stu = (Student)obj;  
        return this.name.equals(stu.name) && this.age == stu.age;  
    }  
      
    //实现了Comparable接口,学生就具备了比较功能。该功能是自然排序使用的方法。  
    //自然排序就以年龄的升序排序为主。 升序 
    //既然是同姓名同年龄是同一个人,视为重复元素,要判断的要素就有两个。  
    //既然是按照年龄进行排序。所以先判断年龄,再判断姓名。  
    @Override  
    public int compareTo(Object o) {  
        Student stu = (Student)o;  
        System.out.println(this.name+":"+this.age+"......"+stu.name+":"+stu.age);  
        if(this.age > stu.age)  
            return 1;  
        if(this.age < stu.age)  
            return -1;  
        //return 0;//0表示重复元素,不存。  
        return this.name.compareTo(stu.name);//完成了主要条件的比较之后还要完成次要条件的比较。在两个age 值一样的情况下,再进一步细化条件,只有姓名、年龄都一样,才是重复元素。  
        /* 
        主要条件: 
        return this.age - stu.age; 
        */  
        /* 
        主要条件+次要条件: 
        int temp = this.age - stu.age; 
        return temp==0?this.name.compareTo(stu.age):temp; 
         */  
    }  
}  
package ustc.lichunchun.set.demo;  
  
import java.util.Iterator;  
import java.util.Set;  
import java.util.TreeSet;  
  
import ustc.lichunchun.domian.Student;  
  
public class TreeSetDemo {  
  
    public static void main(String[] args) {  
        Set set = new TreeSet();  
          
        set.add(new Student("xiaoqiang",20));//java.lang.ClassCastException 类型转换异常  
                                             //问题:因为学生要排序,就需要比较,而没有定义比较方法,无法完成排序。  
                                             //解决:add方法中实现比较功能,使用的是Comparable接口的比较方法。  
                                             //Comparable接口抽取并定义规则,强行对实现它的每个类的对象进行整体排序,实现我的类就得实现我的compareTo() 方法,否则不能创建对象。  
        set.add(new Student("daniu",24));  
        set.add(new Student("xiaoming",22));  
        set.add(new Student("huanhuan",22)); //根据复写的compareTo方法,huanhuan和xiaoming两个对象属于重复元素(进一步细化条件之前,compareTo返回值为0即视为重复),又TreeSet容器不存重复元素,所以huanhuan没有存进去。  
        set.add(new Student("tudou",18));  
        set.add(new Student("dahuang",19));  
          
        /*set.add(new Student("lisi02", 22));   
        set.add(new Student("lisi007", 20));   
        set.add(new Student("lisi09", 19));   
        set.add(new Student("lisi08", 19));   
        set.add(new Student("lisi11", 40));   
        set.add(new Student("lisi16", 30));   
        set.add(new Student("lisi12", 36));   
        set.add(new Student("lisi10", 29));   
        set.add(new Student("lisi22", 90));  
        */  
        for (Iterator it = set.iterator(); it.hasNext();) {  
            Student stu = (Student)it.next();  
            System.out.println(stu.getName()+":"+stu.getAge());  
        }  
    }  
}  

如何实现有序?保证二叉树只return一边即可,比如:

public int compareTo(Object o){  
    if (this.age == o.age)  
        return 0;//保证TreeSet不存入自定义的重复元素。  
    return 1;//保证添加的元素都存入二叉树的右子树。  
}  

TreeSet第一种排序方式:需要元素具备比较功能。所以元素需要实现Comparable接口。重写compareTo()方法。需求中也有这样一种情况,元素具备的比较功能不是所需要的,也就是说不想按照自然排序的方式,而是按照自定义的排序方式,对元素进行排序。而且,存储到TreeSet中的元素万一没有比较功能,该如何排序呢?这时,就只能使用第二种排序方式----让集合具备比较功能,定义一个比较器。联想到集合的构造函数,去查API。

TreeSet第二种排序方式:需要集合具备比较功能,定义一个比较器。所以要实现java.util.Comparator<T>接口,重写compare()方法,将Comparator接口的对象,作为参数传递给TreeSet集合的构造函数。这个compare()方法是用来判断多个比较器是否相同。 

案例,自定义一个比较器,用来对学生对象按照姓名进行排序。

第1步, 实现Comparator自定义比较器,代码如下:

package ustc.lichunchun.comparator;  
  
import java.util.Comparator;  
import ustc.lichunchun.domian.Student;  
  
/** 
 * 自定义一个比较器,用来对学生对象按照姓名进行排序。 
 *  
 * @author lichunchun 
 */  
public class ComparatorByName extends Object implements Comparator {   //实现Comparator接口,并在后面代码中要重写compare() 方法
    @Override  
    public int compare(Object o1, Object o2) {  //重写compare() 方法
        Student s1 = (Student) o1;  
        Student s2 = (Student) o2;  
  
        int temp = s1.getName().compareTo(s2.getName());  //注意区分这个compareTo()方法和上面代码中的compare()方法
        return temp == 0 ? s1.getAge() - s2.getAge() : temp;  
    }   
    //ComparatorByName类通过继承Object类,已经重写了Comparator接口的equals()方法。  
    //这里的equals()方法是用来判断多个比较器是否相同。  
    //如果程序中有多个比较器,这时实现Comparator的类就应该自己重写的equals()方法,来判断几个比较器之间是否相同。  
} 

第2步,往TreeSet集合中存入学生类对象,主要是在TreeSet的构造函数中加入比较器参数,即可完成自定义排序。代码如下:

package ustc.lichunchun.set.demo;  
  
import java.util.Iterator;  
import java.util.Set;  
import java.util.TreeSet;  
import ustc.lichunchun.comparator.ComparatorByName;  
import ustc.lichunchun.domian.Student;  
  
public class TreeSetDemo2 {   
    public static void main(String[] args) {  
  
        //初始化TreeSet集合明确一个比较器。  
        Set set = new TreeSet(new ComparatorByName());  //new ComparatorByName()就是创建了一个上文中自己定义的比较器类的一个实例对象,即一个具体的比较器
                                                        //并把new ComparatorByName()作为实际参数传递给TreeSet 的构造方法
        set.add(new Student("xiaoqiang",20));  
        set.add(new Student("daniu",24));  
        set.add(new Student("xiaoming",22));  
        set.add(new Student("tudou",18));  
        set.add(new Student("daming",19));  
        set.add(new Student("dahuang",19));  
          
        for (Iterator it = set.iterator(); it.hasNext();) {  
            Student stu = (Student)it.next();  //不要忘记类型转换
            System.out.println(stu.getName()+":"+stu.getAge());  //如果不进行上句中的类型转换是无法获取到学生对象的属性的
        }  
    }  
}  

TreeSet集合排序的两种方式中,Comparable接口和Comparator比较器接口的区别:

    1.让元素自身具备比较性,需要元素对象实现Comparable接口,重写compareTo()方法。
    2.如果元素不具备比较性,让集合自身来进行比较性,需要定义一个实现了Comparator接口的比较器,并重写compare()方法,并将该类对象作为实际参数传递给TreeSet集合的构造方法。比如,上面案例中的Set set = new TreeSet(new ComparatorByName());  语句。
    3.容器使用Comparator比较器接口对元素进行排序,只要实现比较器对象就可以。
            -->降低了比较方式和集合之间的耦合性-->自定义比较器的方式更为灵活。
       元素自身可以具备比较功能
            -->自然排序通常都作为元素的默认排序。

    4.Comparable接口的compareTo()方法,一个参数;Comparator接口的compare()方法,两个参数。

Collection集合各个具体实现类的名字特征:前缀名是数据结构名,后缀名是所属体系名。
    ArrayList:数组结构。看到数组,就知道查询快,看到List,就知道可以重复。可以增删改查。
    LinkedList:链表结构,增删快。xxxFirst、xxxLast方法,xxx:add、get、remove
    HashSet:哈希表,查询速度更快,就要想到唯一性、元素必须覆盖hashCode()、equals()。看到Set,就知道不可以重复,不保证有序。
    LinkedHashSet:链表+哈希表。可以实现有序(有点特殊),因为有链表。但保证元素唯一性。
   TreeSet:二叉树,可以排序。就要想到两种比较方式(两个接口):一种是自然排序Comparable,一种是比较器Comparator。

2.2 Map<K, V>接口

    java.util.Map<K,V>接口,将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。要保证键的唯一性-->Set。值可以重复-->Collection。 

Map:双列集合,一次存一对,键值对。
        |--HashMap:底层是哈希表数据结构,是线程不同步的,允许存储null键,null值。HashMap替代了Hashtable。
        |--TreeMap:底层是二叉树结构,线程不同步的。可以对map集合中的键进行指定顺序的排序。
        |--Hashtable:底层是哈希表数据结构,是线程同步的,不允许存储null键,null值。在Java集合框架中,线程同步的只用Hashtable和Vector两个。特别小心Hashtable中的t 是小写的,书写的时候要小心。
               |--Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合。 (留心下实际项目中的Properties文件、文档)

    揭秘:HashSet、TreeSet的底层是用HashMap、TreeMap实现的,只操作键,就是Set集合。

    Map集合存储和Collection有着很大不同:
    Collection一次存一个元素,而Map一次存一对元素。
    Collection是单列集合,而Map是双列集合。
    Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。

    特点:要保证map集合中键的唯一性。

    Map接口中的共性方法:
    1.添加: 会改变集合长度 
        v put(key, value):当存储的键相同时,新的值会替换老的值,并将老值返回。如果键没有重复,返回null。
        putAll(Map<k,v> map);
    2.删除: 会改变集合长度 
        v remove(key):删除指定键 
        void clear():清空
    3.判断:
        boolean containsKey(Object key):是否包含key键
        boolean containsValue(Object value):是否包含value值
        boolean isEmpty();是否为空
    4.取出:
        v get(key):通过指定键获取对应的值。如果返回null,可以判断该键不存在。
                         当然有特殊情况,就是在Hashmap集合中,是可以存储null键null值的。

        int size():返回长度。

package ustc.lichunchun.map;  
  
import java.util.HashMap;  
import java.util.Map;  
  
public class MapDemo {  
  
    public static void main(String[] args) {  
        /* 
         * 需求:Map集合中存储学号、姓名。 
         */  
        Map<Integer, String> map = new HashMap<Integer, String>();  
        methodDemo(map);  
    }  
    public static void methodDemo(Map<Integer, String> map){  
          
        //1.存储键值对。如果键相同,会出现值覆盖。  
        System.out.println(map.put(3, "xiaoqiang"));  
        System.out.println(map.put(3, "erhu"));  
        map.put(7, "wangcai");  
        map.put(2, "daniu");  
          
        //2.移除。-->会改变长度。  
        //System.out.println(map.remove(7));  
          
        //3.获取。  
        System.out.println(map.get(7));        
        System.out.println(map);  
    }  
}  

    5. 想要获取Map中的所有元素:

    原理:map中是没有迭代器的,collection具备迭代器,只要将map集合转成Set集合,可以使用迭代器了。之所以转成set,是因为map集合具备着键的唯一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。

把Map集合转成Set的方法:
    方式1: Set keySet();

    可以将map集合中的键都取出存放到set集合中。对set集合进行迭代。迭代完成,再通过get()方法对获取到的键进行值的获取。

Set keySet = map.keySet();  
Iterator it = keySet.iterator();  
while(it.hasNext()) {  
  Object key = it.next();  
  Object value = map.get(key);  
  System.out.println(key+":"+value);  
}  

方式2: Set entrySet(); 

取的是键和值的映射关系。Map.Entry:其实就是一个Map接口中的内部接口。为什么要定义在map内部呢?entry是访问键值关系的入口,是map的入口,访问的是map中的键值对。

Set entrySet = map.entrySet();  
Iterator it = entrySet.iterator();  
while(it.hasNext()) {  
  Map.Entry  me = (Map.Entry)it.next();  
  System.out.println(me.getKey()+"::::"+me.getValue());  
}  

案例,取出map 中的所有元素,代码如下:

package ustc.lichunchun.map;  
  
import java.util.Collection;  
import java.util.HashMap;  
import java.util.Iterator;  
import java.util.Map;  
import java.util.Set;  
  
public class MapDemo2 {  
  
    public static void main(String[] args) {  
        /* 
         * 取出Map中所有的元素。 map存储姓名---归属地。 
         */  
        Map<String, String> map = new HashMap<String, String>();  
          
        map.put("xiaoqiang", "beijing");  
        map.put("wangcai", "funiushan");  
        map.put("daniu", "heifengzhai");  
        map.put("erhu", "wohudong");  
        map.put("zhizunbao", "funiushan");  
          
        //System.out.println(map.get("wangcai"));  
          
        /* 
        //演示keySet(); 取出所有的键,并存储到Set集合中。 
        Set<String> keySet = map.keySet(); 
         
        //Map集合没有迭代器。但是可以将Map集合转成Set集合,再使用迭代器就ok了。 
        for (Iterator<String> it = keySet.iterator(); it.hasNext();) { 
            String key = it.next(); 
            String value = map.get(key); 
            System.out.println(key+":"+value);           
        } 
         
        //演示entrySet(); Map.Entry:其实就是一个Map接口中的内部接口。 
        Set<Map.Entry<String, String>> entrySet = map.entrySet(); 
         
        for (Iterator<Map.Entry<String, String>> it = entrySet.iterator(); it.hasNext();) { 
            Map.Entry<String, String> me = it.next(); 
            String key = me.getKey(); 
            String value = me.getValue(); 
            System.out.println(key+"::"+value); 
        } 
         */  
          
        //演示values(); 获取所有的值。  
        Collection<String> values = map.values();  
          
        for (Iterator<String> it = values.iterator(); it.hasNext();) {  
            String value = it.next();  
            System.out.println(value);        
        }  
    }  
}  
//原理  
interface MyMap{//-->键值对  
      
    //entry就是map接口中的内部接口。  
    public static interface MyEntry{//-->键值对的映射关系  
          
    }  
}  
class MyDemo implements MyMap.MyEntry{  
      
}  

什么时候使用Map集合呢?当需求中出现映射关系时,应该最先想到map集合。比如,获取星期几,代码如下:

import java.util.Map;  
  import ustc.lichunchun.exception.NoWeekException;  
  public class MapTest {    
    public static void main(String[] args) {  
        /* 
         * 什么时候使用map集合呢? 
         * 当需求中出现映射关系时,应该最先想到map集合。 
         */  
        String cnWeek = getCnWeek(3);  //以数组,查看日期对应的星期几
        System.out.println(cnWeek);  
        String enWeek = getEnWeek(cnWeek);  //以map ,查看各个星期几对应的英文星期几
        System.out.println(enWeek);  
    }        

    /* 
     * 根据用户指定的数据获取对应的星期。 
     */  
    public static String getCnWeek(int num){  
        if (num>7 || num<=0)  
            throw new NotWeekException(num+", 没有对应的星期");  
        String[] cnWeeks = {"","星期一","星期二","星期三","星期四","星期五","星期六","星期日"};  
        return cnWeeks[num];  
    }  

    /* 
     * 根据中文的星期,获取对应的英文星期。 
     * 中文与英文相对应,可以建立表,没有有序的编号,只能通过map集合。 
     */  
    public static String getEnWeek(String cnWeek){  
        //创建一个表。  
        Map<String,String> map = new HashMap<String, String>();  
        map.put("星期一","Monday");  
        map.put("星期二","Tuesday");  
        map.put("星期三","Wednesday");  
        map.put("星期四","Thursday");  
        map.put("星期五","Friday");  
        map.put("星期六","Saturday");  
        map.put("星期日","Sunday");  
        return map.get(cnWeek);  
    }  
}  

上段程序中用到的NotWeekException异常,代码如下:

package ustc.lichunchun.exception;  
  
public class NotWeekException extends RuntimeException {    
    /** 
     *  
     */  
    private static final long serialVersionUID = 1L;    
    public NotWeekException() {  
        super();  
    }  
  
    public NotWeekException(String message, Throwable cause,  
            boolean enableSuppression, boolean writableStackTrace) {  
        super(message, cause, enableSuppression, writableStackTrace);  
    }  
  
    public NotWeekException(String message, Throwable cause) {  
        super(message, cause);  
    }  
  
    public NotWeekException(String message) {  
        super(message);  
    }  
  
    public NotWeekException(Throwable cause) {  
        super(cause);  
    }   
}  

HashMap<K, V>类:

以案例的形式来理解。案例一, 员工对象(姓名,年龄)都有对应的归属地, 将员工和归属存储到HashMap集合中并取出,其中同姓名同年龄视为同一个员工。代码如下:

package ustc.lichunchun.domain;  

public class Employee implements Comparable<Employee>{  //员工类实现Comparable接口,并重写其中的compareTo()方法
    private String name;  //员工类的私有化属性
    private int age;  
    public Employee() {   //员工类的无参构造方法
        super();  
    }  
    public Employee(String name, int age) {  //员工类的有参构造方法
        super();  
        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() {  //重写toString()方法
        return "Employee [name=" + name + ", age=" + age + "]";  
    }  
    @Override  
    public int hashCode() {     //重写hashCode()方法
        final int prime = 31;  
        int result = 1;  
        result = prime * result + age;  
        result = prime * result + ((name == null) ? 0 : name.hashCode());  
        return result;  
    }  
    @Override  
    public boolean equals(Object obj) {  //重写equals()方法
        if (this == obj)  
            return true;  
        if (obj == null)  
            return false;  
        if (getClass() != obj.getClass())  
            return false;  
        Employee other = (Employee) obj;  
        if (age != other.age)  
            return false;  
        if (name == null) {  
            if (other.name != null)  
                return false;  
        } else if (!name.equals(other.name))  
            return false;  
        return true;  
    }  
    @Override  
    public int compareTo(Employee o) {  
        int temp = this.age-o.age;  //年龄
        return temp==0?this.name.compareTo(o.name):temp;  
    }  
}  

package ustc.lichunchun.map;  
  
import java.util.HashMap;  
import java.util.Map;  
import ustc.lichunchun.domain.Employee;  
  
public class HashMapTest {  
  
    public static void main(String[] args) {  
        /* 
         * 练习: 
         * 员工对象(姓名,年龄)都有对应的归属地。 
         * key=Employee  value=String 
         *  
         * 1. 
         * 将员工和归属存储到HashMap集合中并取出。 
         * 同姓名同年龄视为同一个员工。 
         *  
         */  
        Map<Employee,String> map = new HashMap<Employee,String>();//如果改成LinkedHashMap可以实现有序。  
          
        map.put(new Employee("xiaozhang",24),"北京"); //员工、员工的所属地 
        map.put(new Employee("laoli",34),"上海");  
        map.put(new Employee("mingming",26),"南京");  
        map.put(new Employee("xili",30),"广州");  
        map.put(new Employee("laoli",34),"铁岭");//键相同是,值会被覆盖。new Employee("laoli",34)相同,则上海被覆盖掉了  
          
        for (Employee employee : map.keySet()) {  //map自己没有迭代器,转换成set集合再使用迭代器。这里使用了.keySet()方法类转换
            System.out.println(employee.getName()+":"+employee.getAge()+"..."+map.get(employee));  
        }  
    }  
}  

TreeMap<K, V>类:

接着上例,按照员工的年龄进行升序排序并取出。-->Comparable。再按照员工的姓名进行升序排序并取出。-->Comparator。

package ustc.lichunchun.map;  
  
import java.util.Comparator;  
import java.util.Map;  
import java.util.TreeMap;  
  
import ustc.lichunchun.domain.Employee;  
  
public class TreeMapTest {  
      
    public static void main(String[] args) {  
        /* 
         * 练习: 
         * 2. 
         * 按照员工的年龄进行升序排序并取出。-->Comparable 
         * 按照员工的姓名进行升序排序并取出。-->Comparator 
         */  
          
        Comparator<Employee> comparator = new Comparator<Employee>(){  
  
            @Override  
            public int compare(Employee o1, Employee o2) {  
                int temp = o1.getName().compareTo(o2.getName());  
                return temp==0?o1.getAge()-o2.getAge():temp;  
            }  
        };  
          
        //Map<Employee,String> map = new TreeMap<Employee,String>();// 按照年龄  
        Map<Employee,String> map = new TreeMap<Employee,String>(comparator);//按照姓名  
          
        map.put(new Employee("xiaozhang",24),"北京");  
        map.put(new Employee("laoli",34),"上海");  
        map.put(new Employee("mingming",26),"南京");  
        map.put(new Employee("xili",30),"广州");  
        map.put(new Employee("laoli",34),"铁岭");  
          
        for(Map.Entry<Employee, String> me : map.entrySet()){  
            System.out.println(me.getKey().getName()+"::"+me.getKey().getAge()+"..."+me.getValue());  
        }  
    }  
}  

2.3 Iterator接口

java.util.Iterator接口是一个对 collection 进行迭代的迭代器,作用是取出集合中的元素。

Iterator:只有hasNext()、next()、remove()三个方法

        |--ListIterator:List集合所特有的迭代器,可以在遍历的时候对元素进行增删改查操作。ListIterator listIterator():list集合特有的迭代器。 在进行list列表元素迭代的时候,如果想要在迭代过程中,想要对元素进行操作的时候。比如,迭代过程中执行添加,会发生ConcurrentModificationException并发修改异常。那是因为集合引用和迭代器引用在同时操作元素,通过集合获取到对应的迭代器后,在迭代中,进行集合引用的元素添加,迭代器并不知道,所以会出现异常情况。如何解决呢?Iterator中只有hasNext(),next(),remove()方法。而ListIterator这个列表迭代器接口具备了对元素的增、删、改、查的动作。

    Iterator iterator():获取集合中元素上迭代功能的迭代器对象。
    迭代:取出元素的一种方式。有没有啊?有!取一个。还有没有啊?有!取一个。还有没有啊?没有。算了。
    迭代器:具备着迭代功能的对象。迭代器对象不需要new。直接通过 iterator()方法获取即可。

    迭代器是取出Collection集合中元素的公共方法。 

每一个集合都有自己的数据结构,都有特定的取出自己内部元素的方式。为了便于操作所有的容器,取出元素,将容器内部的取出方式按照一个统一的规则向外提供,这个规则就是Iterator接口。也就说只要通过该接口就可以取出Collection集合中的元素,至于每一个具体的容器依据自己的数据结构,如何实现的具体取出细节,这个不用关心,这样就降低了取出元素和具体集合的耦合性。Iterator it = coll.iterator(); //获取容器中的迭代器对象至于这个对象是是什么不重要。这对象肯定符合一个规则Iterator接口。

package ustc.lichunchun.collection.demo;  
  
import java.util.ArrayList;  
import java.util.Collection;  
import java.util.Iterator;  
  
public class IteratorDemo {  
  
    public static void main(String[] args) {  
          
        //1.创建集合。  
        Collection coll = new ArrayList();  
          
        coll.add("abc1");  
        coll.add("abc2");  
        coll.add("abc3");  
          
        //方式一:获取该容器的迭代器。  
        Iterator it = coll.iterator();  
        while(it.hasNext()){  
            System.out.println(it.next());  
        }  
          
        //方式二:直接for+alt+/,选择第三个。  
        for (Iterator it = coll.iterator(); it.hasNext();) {  
            System.out.println(it.next());  
        }  
          
        System.out.println(it.next());//abc1  
        System.out.println(it.next());//abc2  
        System.out.println(it.next());//abc3  
        System.out.println(it.next());//java.util.NoSuchElementException   
    }  
}  

 2.4 Enumeration<E>接口

java.util.Enumeration:枚举。具备枚举取出方式的容器只有Vector,已被淘汰。Vector就是一个支持多线程的ArrayList,只是Vector的查询和增删效率都特别低。举例如下:

package ustc.lichunchun.enumeration;  
  
import java.util.Enumeration;  
import java.util.Iterator;  
import java.util.Vector;  
  
public class EnumerationDemo {  
  
    public static void main(String[] args) {  
        /* 
         * Enumeration:枚举。 
         * 具备枚举取出方式的容器只有Vector。 
         */  
        Vector v = new Vector();  
        v.add("abc1");  
        v.add("abc2");  
        v.add("abc3");  
          
        /*Enumeration en = v.elements(); 
        while(en.hasMoreElements()){ 
            System.out.println(en.nextElement()); 
        }*/  
          
        //获取枚举。-->淘汰了  
        for(Enumeration en = v.elements(); en.hasMoreElements();){  
            System.out.println("enumeration: "+en.nextElement());  
        }  
          
        //获取迭代。-->好用。  
        for (Iterator it = v.iterator(); it.hasNext();) {  
            System.out.println("iterator: "+it.next());  
        }  
          
        //获取高级for。-->无角标,仅为遍历。  
        for (Object obj : v) {  
            System.out.println("foreach: "+obj);  
        }  
    }  
}  

2.5 集合框架的工具类

2.5.1 Collections类

    java.util.Collections类的出现给集合操作提供了更多的功能。这个类不需要创建对象,完全由在 collection 上进行操作或返回 collection 的静态方法组成。静态方法与类相关。
    1.对List排序:
         sort(list);  //具备泛型限定,保证安全。
    2.逆序:
         reverseOrder()
    3.最值:
         max()
         min()
    4.二分查找:
         binarySearch()
    5.将非同步集合转成同步集合: //非同步的集合可以通过工具类Collections类转换成为同步的集合
         synchronizedCollection
         synchronizedList
         SynchronizedSet

         synchronizedMap

Collections工具类代码示例,如下:

package ustc.lichunchun.collections;  
  
import java.util.ArrayList;  
import java.util.Collection;  
import java.util.Collections;  
import java.util.Iterator;  
import java.util.List;  
  
import ustc.lichunchun.comparator.ComparatorByLength;  
  
public class CollectionsDemo {  
  
    public static void main(String[] args) {  
        /* 
         * Collections排序、逆序、最值、同步方法演示 
         */  
        methodDemo1();  
          
        Collection<String> coll = new ArrayList<String>();  
        coll.add("abcd");  
        coll.add("ab");  
        coll.add("haha");  
        coll.add("zzz");  
        String max = getMax(coll);  
        String max1 = Collections.max(coll,new ComparatorByLength());  
        System.out.println("max = "+max);  
        System.out.println("max1 = "+max1);  
          
        /* 
         * Collections中有一个可以将非同步集合转成同步集合的方法。 
         * 同步集合  synchronized集合(非同步集合); 
         */  
        Collection<String> synColl = Collections.synchronizedCollection(coll);  
    }  
  
    public static void methodDemo1() {  
        List<String> list = new ArrayList<String>();  
        list.add("abc");  
        list.add("xy");  
        list.add("haha");  
        list.add("nba");  
        System.out.println(list);  
          
        //对list排序。自然排序。使用的是元素的compareTo方法。  
        Collections.sort(list);  
        System.out.println(list);  
          
        //按照长度排序。  
        Collections.sort(list,new ComparatorByLength());  
        System.out.println(list);  
          
        //按照长度逆序。  
        Collections.sort(list,Collections.reverseOrder(new ComparatorByLength()));//reverseOrder强行逆转比较器顺序。  
        System.out.println(list);  
    }  
}  

上述代码中用到的比较器实现如下:

package ustc.lichunchun.comparator;  
  
import java.util.Comparator;  
  
public class ComparatorByLength implements Comparator<String> {  
    @Override  
    public int compare(String o1, String o2) {  
        int temp = o1.length()-o2.length();  
        return temp==0?o1.compareTo(o2):temp;  
    }  
}  

Collections工具类中将非同步集合转成同步集合的原理解释:定义一个类,将非同步集合所有的方法加同一把锁后返回。

List list = new ArrayList();//非同步的。  
  
list = MyCollections.synList(list);//返回一个同步的list。  
  
class MyCollections{  
    public static List synList(List list){  
        return new MyList(list);  
    }  
    private class MyList{  
        private List list;  
        private static final Object lock = new Object();  
        MyList(List list){  
            this.list = list;  
        }  
        public boolean add(Object obj){  
            synchronized(lock){  //加锁
                return list.add(obj);  
            }  
        }  
        public boolean remove(Object obj){  
            synchronized(lock){  
                return list.remove(obj);  
            }  
        }  
    }  
}  

Collections 和 Collection 的区别:

Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。

Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。

2.5.2 Arrays类

java.util.Arrays类是用来操作数组的工具类,里面的方法都是静态的。代码示例:

package ustc.lichunchun.arrays;  
  
import java.util.Arrays;  
  
public class ArraysDemo {  
  
    public static void main(String[] args) {  
        //int[] arr = new int[3];  
        Integer[] arr = new Integer[3]; //int--->Integer,自动装箱拆箱。 
        String[] arr1 = new String[3];  
        swap(arr,1,2);  
        swap(arr1,1,2);  
          
        int[] arr2 = {45,1,23,56,67};  
        System.out.println(arr2); //[I@1db9742  
        System.out.println(Arrays.toString(arr2)); //[45, 1, 23, 56, 67],底层用的是StringBuilder。StringBuilder效率高,但是不支持线程同步  
    }  
      
    public static <T> void swap(T[] arr, int x, int y){ //T必须是引用类型。int--->Integer,自动装箱拆箱。  
        T temp = arr[x];  
        arr[x] = arr[y];  
        arr[y] = temp;  
    }  
      
    //toString的源码实现。  
    public static String toString(int[] a) {  
        if (a == null)  
            return "null";  
        int iMax = a.length - 1;  
        if (iMax == -1)  
            return "[]";  
  
        StringBuilder b = new StringBuilder();  
        b.append('[');  
        for (int i = 0; ; i++) { //中间省略条件判断,提高了效率。  
            b.append(a[i]);  
            if (i == iMax)  
                return b.append(']').toString();  
            b.append(", ");  
        }  
    }  
}  

Arrays类中有一个很重要的方法就是asList()方法,它返回一个受指定数组支持的固定大小的List列表。所以这里我就重点说一下,如何实现数组和集合之间的转换。

1.数组转成集合

    Arrays.asList()方法:将数组转换成list集合。

<span style="font-size:14px;">String[] arr = {"abc","kk","qq"};  
List<String> list = Arrays.asList(arr);//将arr数组转成list集合。</span>  

将数组转换成集合,有什么好处呢?用asList()方法,将数组变成集合;可以通过list集合中的方法来操作数组中的元素:isEmpty()、contains、indexOf、set等方法。注意(局限性):数组是固定长度,不可以使用集合对象增加或者删除等,会改变数组长度的功能方法。比如add()、remove()、clear()不能使用。(会报不支持操作异常UnsupportedOperationException)。如果数组中存储的引用数据类型,直接作为集合的元素可以直接用集合方法操作。如果数组中存储的是基本数据类型,asList会将数组实体作为集合元素存在。

package ustc.lichunchun.arrays;  
  
import java.util.Arrays;  
import java.util.List;  
  
public class ArraysDemo2 {  
  
    public static void main(String[] args) {  //程序入口主方法
        /* 
         * 数组转成集合。 
         *  
         * Arrays:用来操作数组的工具类,里面的方法都是静态的。 
         */  
        demo_1();  
        demo_2();  
    }  
      
    public static void demo_1() {  
        /* 
         * 重点:Arrays asList(数组) 将数组转成集合。 
         *  
         * 为什么要把数组转成集合? 
         * 因为数组能用的方法是有限的:折半、排序、toString()、equals()、fill(),没了。 
         * 我想知道数组的位置、是否包含某个元素,这些都没有,但是集合中有这些方法。 
         *  
         * 好处:数组转成List集合,就是为了使用集合的方法操作数组中的元素。 
         *  
         * 注意:数组的长度是固定的,所以对于集合的增删方法是不可以使用的, 
         * 否则会发生UnsupportedOperationException 
         */  
        String[] strs = {"abc","haha","nba","zz"};  
          
        //以前的做法:自己定义一个方法实现功能  
        boolean b = myContains(strs, "nba");  
        System.out.println("contains:"+b);  
  
        //现在的做法:数组转成集合,从而利用集合的方法  
        List<String> list = Arrays.asList(strs);  
        System.out.println("list contains:"+list.contains("nba"));  
        System.out.println(list);  
          
        //list.add("qq");//报错--> java.lang.UnsupportedOperationException  
        //因为数组是固定长度的。  
    }  
      
    /* 
     * 自定义 对数组中某元素进行查找。 
     */  
    public static boolean myContains(String[] arr, String key){  
        for (int i = 0; i < arr.length; i++) {  
            String str = arr[i];  
            if(str.equals(key))  
                return true;  
        }  
        return false;  
    }  
      
    public static void demo_2() {  
        /* 
         * 如果数组中都是引用数据类型,转成集合时,数组元素直接作为集合元素。 
         * 如果数组中都是基本数据类型,会将数组对象作为集合中的元素。(因为集合中只能存对象) 
         */  
          
        int[] arr = {45,23,78,11,99};  
        List<int[]> list = Arrays.asList(arr);  
        System.out.println(list);//[[I@1db9742]  
        System.out.println(list.size());//1  
        System.out.println(list.get(0));//[I@1db9742  
        //原因:把数组作为元素,存进了集合中。  
          
        //那上面代码的泛型该咋写?  
        //泛型应该是集合中元素的类型。arr是int[]类型,所以是List<int[]> list  
        //实际中,我们应该声明为Integer数组,而不是int数组,如下:  
          
        Integer[] arr1 = {45,23,78,11,99};  
        List<Integer> list1 = Arrays.asList(arr1);  
        System.out.println(list1);//[45, 23, 78, 11, 99]  
        System.out.println(list1.get(0));//45  
    }  
}  

2.集合转成数组

用的是Collection接口中的方法:toArray()。没有使用到工具类Arrays类

注意:如果给toArray()传递的指定类型的数组长度小于了集合的size,那么toArray()方法,会自定再创建一个该类型的数组,长度为集合的size。如果传递的指定的类型的数组的长度大于了集合的size,那么toArray()方法,就不会创建新数组,直接使用该数组即可,并将集合中的元素存储到数组中,其他为存储元素的位置默认值null。所以,在传递指定类型数组时,最好的方式就是指定的长度和集合size相等的数组将集合变成数组后有什么好处?限定了对集合中的元素进行增删操作,只要获取这些元素即可。

package ustc.lichunchun.arrays;  
  
import java.util.ArrayList;  
import java.util.Arrays;  
import java.util.List;  
  
public class ArraysDemo3 {  
  
    public static void main(String[] args) {  
        /* 
         * 集合转成数组。 
         *  
         * 使用的就是Collection接口中的toArray()方法。没有使用到工具类Arrays类
         *  
         * 为什么要把集合转成数组? 
         * 可以对集合中的元素操作的方法进行限定,不允许对其进行增删。 当然查看操作是允许的
         */  
        List<String> list = new ArrayList<String>();  
        list.add("abc");  
        list.add("haha");  
          
        /* 
         * toArray方法需要传入一个指定类型的数组。 
         * 长度该如何定义呢? 
         * 传入的数组长度,如果小于集合长度,那么该方法会创建一个同类型的数组,并且这个数组的length属性值集合集合的size()值相等。 
         * 传入的数组长度,如果大于集合长度,那么该方法会使用指定的数组,存储集合中的元素,其他位置默认为null。 
         *  
         * 所以建议,长度就指定为集合的size(); 
         */  
        String[] arr =list.toArray(new String[0]);  
        System.out.println(Arrays.toString(arr));//[abc, haha]  
          
        String[] arr1 =list.toArray(new String[3]);  
        System.out.println(Arrays.toString(arr1));//以字符串的形式输出[abc, haha, null]  
          
        String[] arr2 =list.toArray(new String[list.size()]);  
        System.out.println(Arrays.toString(arr2));//以字符串的形式输出[abc, haha]  
    }  
}  

三. 数组与集合之间的转换

数组和集合之间的转换目的,就是为了特定的需要使用对方的优点来弥补自己的不足,转换过程中元素保持不变。 数组和List、Set都可以存放多个元素,数组的特点是长度固定,访问速度非常快,元素类型单一;List的特点是长度可以动态增加,能够维护元素的次序,存入的所有元素都当做Object,允许元素重复;Set的特点是长度可以动态增加,能够保证元素不重复,存入的所有元素都当做Object。

List转换成数组可以使用List的toArray()方法,返回一个Object数组。
Set转换成数组可以使用Set的toArray()方法,返回一个Object数组。
如果List或Set中元素的类型单一都为A,那么可以使用带参数的toArray()方法,得到类型为A的数组,具体语句是“(A[])set.toArray(new A[0])”,要记得强制类型转换。

数组转换成List可以使用Arrays的asList静态方法,得到一个List。

数组转化成Set时,需要先将数组转化成List再用List构造Set。包含两个步骤。

package com.example.test;  
  
import java.util.ArrayList;  
import java.util.Arrays;  
import java.util.HashMap;  
import java.util.HashSet;  
import java.util.List;  
import java.util.Map;  
import java.util.Set;  
  
public class ConvertorTest {  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {           		
		testArray11List();
		testList12Array();
		testArray21Set();
		testArray22Set();
		testSet31List();
		testSet32List();
		testMap411List();
		testMap422List();
    }  
	 
    private static void testArray11List() {  
        //数组-->List   ,为了使用List中除了增删之外的方法,对数组元素进行操作 
        String[] ss = {"JJ","KK"};    
        List<String> list1 = Arrays.asList(ss);  //使用工具类Arrays类中的.asList()方法  
        List<String> list2 = Arrays.asList("AAA","BBB");    
        System.out.println(list1);    
        System.out.println(list2);    
    }  
	
    private static void testList12Array() {  
        //List-->数组    ,限制List中增删方法的使用
        List<String> list = new ArrayList<String>();    
        list.add("AA");    
        list.add("BB");    
        list.add("CC");    
        Object[] objects = list.toArray();//返回Object数组  ,调用List自己的 .toArray()方法  
        System.out.println("objects:"+Arrays.toString(objects));    
          
        String[] arr = new String[list.size()];    
        list.toArray(arr);//将转化后的数组放入已经创建好的对象中    
        System.out.println("strings1:"+Arrays.toString(arr));    
    }  
     	
    private static void testArray21Set() {            
        String[] arr = {"AA","BB","DD","CC","BB"};  
		
        //数组(-->List)-->Set    ,为了使用Set中除了增删之外的方法,对数组元素进行操作
        Set<String> set = new HashSet<String>(Arrays.asList(arr)); //使用工具类Arrays类中的.asList()得到一个List,再将list作为Set构造方法的参数  
        System.out.println(set);    
    }  
  
    private static void testSet22Array() {  
        Set<String> set = new HashSet<String>();  
        set.add("AA");  
        set.add("BB");  
        set.add("CC");  
          
        String[] arr = new String[set.size()];    
        //Set-->数组    ,为了限制Set中增删方法的使用
        set.toArray(arr);   //使用Set自己的 .toArray()方法
        System.out.println(Arrays.toString(arr));    
    }  	
	
    private static void testList31Set() {              
        List<String> list = new ArrayList<String>();  
        list.add("ABC");  
        list.add("EFG");  
        list.add("LMN");  
        list.add("LMN");  
          
        //List-->Set  ,为了排除List中的重复元素
        Set<String> listSet = new HashSet<String>(list);  //将list作为Set的构造方法的参数
        System.out.println(listSet);  
    }  
  
    private static void testSet32List() {             
        Set<String> set = new HashSet<String>();  
        set.add("AA");  
        set.add("BB");  
        set.add("CC");  
          
        //Set --> List  ,为了给Set中的元素添加顺序,即让它们有下标
        List<String> setList = new ArrayList<String>(set);  //将set作为List的构造函数的参数
        System.out.println(setList);    
    }  
  	
    private static void testMap411List() {           
        Map<String, String> map = new HashMap<String, String>();    
        map.put("A", "ABC");    
        map.put("K", "KK");    
        map.put("L", "LV");    
          
        // 将Map Key  转化为List。我自己觉得这种情况不大使用,因为Map中的键是唯一不可重复的,不符合List。    
        List<String> mapKeyList = new ArrayList<String>(map.keySet());    
        System.out.println("mapKeyList:"+mapKeyList);  
          
        // 将Map Value 转化为List。      
        List<String> mapValuesList = new ArrayList<String>(map.values());  //Map自己的 .values()方法获得值的信息,将其存储到Set中  
        System.out.println("mapValuesList:"+mapValuesList);  

    }  
  
    private static void testMap422Set() {            
        Map<String, String> map = new HashMap<String, String>();    
        map.put("A", "ABC");    
        map.put("K", "KK");    
        map.put("L", "LV");    
          
        // 将Map 的键转化为Set      
        Set<String> mapKeySet = map.keySet();  //Map自己的 .keySet()方法获得键的值,将其存储在Set中。还可以使用entry.set()实现
        System.out.println("mapKeySet:"+mapKeySet);  
          
        // 将Map 的值转化为Set。我自己觉得这种情况不大实用,以为Map中的值是可以重复的,不符合Set。      
        Set<String> mapValuesSet = new HashSet<String>(map.values());    
        System.out.println("mapValuesSet:"+mapValuesSet);  
    }  
}  

四. 泛型

泛型是JDK1.5以后出现的新技术。在JDK1.4版本之前,容器什么类型的对象都可以存储。但是在取出时,需要用到对象的特有内容时,需要做向下转型。但是对象的类型不一致,导致了向下转型发生了ClassCastException异常。为了避免这个问题,只能主观上控制,往集合中存储的对象类型保持一致。为了解决该问题,JDK1.5以后,在定义集合时,就直接明确集合中存储元素的具体类型。这样,编译器在编译时,就可以对集合中存储的对象类型进行检查。一旦发现类型不匹配,就编译失败。这个技术就是泛型技术

package ustc.lichunchun.generic.demo;  
  
import java.util.ArrayList;  
import java.util.Iterator;  
import java.util.List;  
  
public class GenericDemo {  
  
    public static void main(String[] args) {  
          
        List list = new ArrayList();  
          
        list.add("abc");  
        list.add(4);//list.add(Integer.valueOf(4));自动装箱.  
          
        for (Iterator it = list.iterator(); it.hasNext();) {  
              
            System.out.println(it.next());  
            //等价于:  
            Object obj = it.next();  
            System.out.println(obj.toString());  
            //因为String和Integer类都重写了Object类的toString方法,所以可以这么做。  
              
            String str = (String)it.next();  
            System.out.println(str.length());  
            //->java.lang.ClassCastException:java.lang.Integer cannot be cast to java.lang.String  
        }  
              
        //为了在运行时期不出现类型异常,可以在定义容器时,就明确容器中的元素的类型。-->泛型  
          
        List<String> list = new ArrayList<String>();  
        list.add("abc");  
        for (Iterator<String> it = list.iterator(); it.hasNext();) {  
            String str = it.next();  
            //class文件中怎么保证it.next()返回的Object类型一定能够变成String类型?  
            //虽然class文件中,没有泛型标识。但是在编译时期就已经保证了元素类型的统一,一定都是某一类元素。  
            //那么在底层,就会有自动的相应类型转换。这叫做泛型的补偿。  
            System.out.println(str.length());  
        }  
    }  
}  

泛型的擦除:编译器通过泛型对元素类型进行检查,只要检查通过,就会生成class文件,但在class文件中,就将泛型标识去掉了。泛型只在源代码中体现。但是通过编译后的程序,保证了容器中元素类型的一致。

泛型的补偿:在运行时,通过获取元素的类型进行转换操作。在底层会有自动的相应类型转换,而不用程序员再进行强制类型转换了。 

泛型的好处:1.)将运行时期的问题,转移到了编译时期,可以更好的让程序员发现问题并解决问题。2.)避免了强制转换、向下转型的麻烦。

package ustc.lichunchun.generic.demo;  
  
import java.util.ArrayList;  
import java.util.Iterator;  
import java.util.List;  
  
public class GenericDemo2 {  
  
    public static void main(String[] args) {  
          
        //创建一个List集合,存储整数。List ArraytList  
        List<Integer> list = new ArrayList<Integer>();  
          
        list.add(5);//自动装箱  list.add(Integer.valueOf(5)); 
        list.add(6);  
          
        for (Iterator<Integer> it = list.iterator(); it.hasNext();) {  
            Integer integer = it.next();//使用了泛型后,it.next()返回的就是指定的元素类型。  
            System.out.println(integer);  
        }  
    }  
}  

总之,泛型就是应用在编译时期的一项安全机制。泛型技术是给编译器使用的技术,用于编译时期,确保了类型的安全。

先给出一个小案例,代码如下:

package ustc.lichunchun.domain;  
  
public class Person implements Comparable<Person> {  
    private String name;  
    private int age;  
 
    public Person() {  
        super();  
    }  
  
    public Person(String name, int age) {  
        super();  
        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 + "]";  
    }  
  
    @Override  
    public int compareTo(Person o) {  
        int temp = this.getAge() - o.getAge();  
        return temp == 0 ? this.getName().compareTo(o.getName()) : temp;  
    }  
  
    @Override  
    public int hashCode() {  
        final int NUMBER = 31;  
        return this.name.hashCode()+this.age*NUMBER;  
    }  
  
    @Override  
    public boolean equals(Object obj) {  
        if (this == obj)  
            return true;  
        if(!(obj instanceof Person))  
            throw new ClassCastException("类型不匹配");  
        Person p = (Person)obj;  
        return this.name.equals(p.name) && this.age == p.age;  
    }  
}  

package ustc.lichunchun.generic.demo;  
  
import java.util.HashSet;  
import java.util.Set;  
import java.util.TreeSet;  
  
import ustc.lichunchun.comparator.ComparatorByName;  
import ustc.lichunchun.domain.Person;  
  
public class GenericDemo3 {  
  
    public static void main(String[] args) {  
          
        Set<String> set = new TreeSet<String>();  
          
        set.add("abcd");  
        set.add("aa");  
        set.add("nba");  
        set.add("cba");  
          
        for (String s : set) {  
            System.out.println(s);  
        }  
          
        //按照年龄排序  
        Set<Person> set = new TreeSet<Person>();  //set的名字可以一样
        set.add(new Person("abcd",20));  
        set.add(new Person("aa",26));  
        set.add(new Person("nba",22));  
        set.add(new Person("cba",24));  
          
        for(Person p: set){  
            System.out.println(p);  
        }  
          
        //按照姓名排序  
        Set<Person> set = new TreeSet<Person>(new ComparatorByName());  //set的名字可以一样
        set.add(new Person("abcd",20));  
        set.add(new Person("aa",26));  
        set.add(new Person("nba",22));  
        set.add(new Person("cba",24));  
          
        for(Person p: set){  
            System.out.println(p);  
        }  
          
        //HashSet不重复的实现  
        Set<Person> set = new HashSet<Person>();  //set的名字可以一样
        set.add(new Person("aa",26));  
        set.add(new Person("abcd",20));  
        set.add(new Person("abcd",20));  
        set.add(new Person("nba",22));  
        set.add(new Person("nba",22));  
        set.add(new Person("cba",24));  
          
        for(Person p: set){  
            System.out.println(p);  
        }  
    }  
}  

泛型技术在集合框架中应用的范围很大。什么时候需要写泛型呢?当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。只要看到类或者接口在描述时右边定义<>,就需要泛型。其实是,容器在不明确操作元素的类型的情况下,对外提供了一个参数,用<>封装。使用容器时,只要将具体的类型实参传递给该参数即可。说白了,泛型就是,传递"类型参数"。下面依次介绍泛型类、泛型方法、泛型接口。

 1.) 泛型类 --> 泛型定义在类上

首先,我们实现两个继承自Person类的子类,分别是Student类、Worker类,代码如下:

package ustc.lichunchun.domain;  
  
public class Student extends Person {  
  
    public Student() {  
        super();  
    }  
  
    public Student(String name, int age) {  
        super(name, age);  
    }  
  
    @Override  
    public String toString() {  
        return "Student [name="+getName()+", age="+getAge()+"]";  
    }  
}  
package ustc.lichunchun.domain;  
  
public class Worker extends Person {  
  
    public Worker() {  
        super();  
    }  
  
    public Worker(String name, int age) {  
        super(name, age);  
    }  
  
    @Override  
    public String toString() {  
        return "Worker [name=" + getName() + ", age=" + getAge() + "]";  
    }  
}  

需求:创建一个用于操作Student对象的工具类。对 对象进行设置和获取。JDK1.5以后,就可以使用泛型。类型不确定时,可以对外提供参数。由使用者通过传递参数的形式完成类型的确定。

//JDK 1.5 在类定义时就明确参数。由使用该类的调用者,来传递具体的类型。  
class Util<W>{//-->泛型类。  
    private W obj;  
  
    public W getObj() {  
        return obj;  
    }  
  
    public void setObj(W obj) {  
        this.obj = obj;  
    }  
}  

利用泛型类,我们就可以直接在编译时期及时发现程序错误,同时避免了向下转型的麻烦。利用上述泛型类工具,代码如下:

package ustc.lichunchun.generic.demo;  
  
import ustc.lichunchun.domain.Student;  
import ustc.lichunchun.domain.Worker;  
  
public class GenericDemo4 {  
  
    public static void main(String[] args) {  
        /* 
         * 泛型1:泛型类-->泛型定义在类上。 
         */  
          
   /*   //JDK 1.4  
        Tool2 tool = new Tool2();  
        tool.setObj(new Worker());  
        Student stu = (Student)tool.getObj();//异常-->java.lang.ClassCastException: Worker cannot be cast to Student  
        System.out.println(stu);  
   */        
        //JDK 1.5  
        Util<Student> util = new Util<Student>();  
        //util.setObj(new Worker());//编译报错-->如果类型不匹配,直接编译失败。  
        //Student stu = util.getObj();//避免了向下转型。不用强制类型转换。  
        System.out.println(stu);  
          
        //总结:什么时候定义泛型?  
        //当类型不明确时,就应该使用泛型来表示,在类上定义参数,由调用者来传递实际类型参数。  
    }  
}  

2. )泛型方法 --> 泛型定义在方法上。这里只需要注意一点,如果静态方法需要定义泛型,泛型只能定义在方法上。代码如下:

package ustc.lichunchun.generic.demo;  
  
public class GenericDemo5 {  
  
    public static void main(String[] args) {  
        /* 
         * 泛型2:泛型方法-->泛型定义在方法上。 
         */  
        Demo1<String> d = new Demo1<String>();  
        d.show("abc");  
        //d.print(6);在类上明确类型后,错误参数类型在编译时期就报错。  
        Demo1<Integer> d1 = new Demo1<Integer>();  
        d1.print(6);  
        //d1.show("abc");  
        System.out.println("----------------");  
          
        Demo2<String> d2 = new Demo2<String>();  
        d2.show("abc");  
        d2.print("bcd");  
        d2.print(6);  
    }  
}  
class Demo1<W>{  //泛型定义在类上
    public void show(W w){  
        System.out.println("show: "+w);  
    }  
    public void print(W w){  
        System.out.println("print: "+w);  
    }  
}  
class Demo2<W>{  
    public void show(W w){  
        System.out.println("show: "+w);  
    }  
    public <Q> void print(Q w){//-->泛型方法。某种意义上可以将Q理解为Object。  
        System.out.println("print: "+w);  
    }  
    /* 
    public static void show(W w){//报错-->静态方法是无法访问类上定义的泛型的。 
                                //因为静态方法隶属于类,优先于对象存在,而泛型的类型参数确定,需要对象明确。 
        System.out.println("show: "+w); 
    } 
    */  
    public static <A> void staticShow(A a){//如果静态方法需要定义泛型,泛型只能定义在方法上。含静态方法的类中不能定义泛型类 
        System.out.println("static show: "+a);  
    }  
}  

3.)泛型接口--> 泛型定义在接口上。

package ustc.lichunchun.generic.demo;  
  
public class GenericDemo6 {  
  
    public static void main(String[] args) {  
        /* 
         * 泛型3:泛型接口-->泛型定义在接口上。 
         */  
        SubDemo d = new SubDemo();  
        d.show("abc");  
    }  
      
}  
interface Inter<T>{//泛型接口。   
    public void show(T t);  
}  
class InterImpl<W> implements Inter<W>{//依然不明确要操作什么类型。  
  
    @Override  
    public void show(W t) {  
        System.out.println("show: "+t);  
    }  
}  
class SubDemo extends InterImpl<String>{  
      
}  
/* 
interface Inter<T>{//泛型接口。  
    public void show(T t); 
} 
class InterImpl implements Inter<String>{ 
    @Override 
    public void show(String t) { 
    } 
} 
*/  

泛型通配符<?>当具体类型不确定的时候,可以使用<?>通配符就是。当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

package ustc.lichunchun.generic.demo;  
  
import java.util.ArrayList;  
import java.util.Collection;  
import java.util.HashSet;  
import java.util.Iterator;  
import java.util.List;  
import java.util.Set;  
  
import ustc.lichunchun.domain.Student;  
  
public class GenericDemo7 {  
  
    public static void main(String[] args) {  
        /* 
         * 通配符<?> --> 相当于<? extends Object> 
         */  
        List<String> list = new ArrayList<String>();  
        list.add("abc1");  
        list.add("abc2");  
        list.add("abc3");  
        printCollection(list);  
          
        Set<String> set = new HashSet<String>();  
        set.add("haha");  
        set.add("xixi");  
        set.add("hoho");  
        printCollection(set);  
          
        List<Student> list2 = new ArrayList<Student>();  
        list2.add(new Student("abc1",21));  
        list2.add(new Student("abc2",22));  
        list2.add(new Student("abc3",23));  
        list2.add(new Student("abc4",24));  
        //Collection<Object> coll = new ArrayList<Student>();-->wrong-->左右不一样,可能会出现类型不匹配  
        //Collection<Student> coll = new ArrayList<Object>();-->wrong-->左右不一样,可能会出现类型不匹配  
        //Collection<?> coll = new ArrayList<Student>();-->right-->通配符  
        printCollection(list2);  
    }  
  
    /*private static void printCollection(List<String> list) { 
        for (Iterator<String> it = list.iterator(); it.hasNext();) { 
            String str = it.next(); 
            System.out.println(str); 
        } 
    }*/  
    /*private static void printCollection(Collection<String> coll) { 
        for (Iterator<String> it = coll.iterator(); it.hasNext();) { 
            String str = it.next(); 
            System.out.println(str); 
        } 
    }*/  
    private static void printCollection(Collection<?> coll) {//在不明确具体类型的情况下,可以使用通配符来表示。  
        for (Iterator<?> it = coll.iterator(); it.hasNext();) {//技巧:迭代器泛型始终保持和具体集合对象一致的泛型。  
            Object obj = it.next();  
            System.out.println(obj);  
        }  
    }  
}  

泛型的限定
    <? extends E>:接收E类型或者E的子类型对象。上限。一般存储对象的时候用。比如添加元素 addAll。

    <? super E>:接收E类型或者E的父类型对象。下限。一般取出对象的时候用。比如比较器。

package ustc.lichunchun.generic.demo;  
  
import java.util.ArrayList;  
import java.util.Collection;  
import java.util.HashSet;  
import java.util.Iterator;  
import java.util.List;  
import java.util.Set;  
  
import ustc.lichunchun.domain.Person;  
import ustc.lichunchun.domain.Student;  
import ustc.lichunchun.domain.Worker;  
  
public class GenericDemo8 {  
  
    public static void main(String[] args) {  
        /* 
         * 泛型的限定 
         */  
        List<Student> list = new ArrayList<Student>();  
        list.add(new Student("abc1",21));  
        list.add(new Student("abc2",22));  
        list.add(new Student("abc3",23));  
        list.add(new Student("abc4",24));  
        printCollection(list);  
          
        Set<Worker> set = new HashSet<Worker>();  
        set.add(new Worker("haha",23));  
        set.add(new Worker("xixi",24));  
        set.add(new Worker("hoho",21));  
        set.add(new Worker("haha",29));  
        printCollection(set);  
    }  
  
    /* 
     * 泛型的限定: 
     * ? extends E :接收E类型或者E的子类型。-->泛型上限。 
     * ? super E :接收E类型或者E的父类型。-->泛型下限。 
     */  
    private static void printCollection(Collection<? extends Person> coll) {//泛型的限定,支持一部分类型。  
        for (Iterator<? extends Person> it = coll.iterator(); it.hasNext();) {  
            Person obj = it.next();//就可以使用Person的特有方法了。  
            System.out.println(obj.getName()+":"+obj.getAge());  
        }  
    }  
}  

案例一,演示泛型上限(存入操作)在API中的体现。我们这里使用的是TreeSet的构造函数:TreeSet<E>(Collection<? extends E> coll)

package ustc.lichunchun.generic.demo;  
  
import java.util.ArrayList;  
import java.util.Collection;  
import java.util.Iterator;  
import java.util.TreeSet;  
  
import ustc.lichunchun.domain.Person;  
import ustc.lichunchun.domain.Student;  
import ustc.lichunchun.domain.Worker;  
  
public class GenericDemo9 {  
  
    public static void main(String[] args) {  
        /* 
         *  演示泛型限定在API中的体现。 
         *  TreeSet的构造函数。 
         *  TreeSet<E>(Collection<? extends E> coll); 
         *   
         *  什么时候会用到上限呢? 
         *  一般往集合存储元素时。如果集合定义了E类型,通常情况下应该存储E类型的对象。 
         *  对于E的子类型的对象,E类型也可以接受(多态)。所以这时可以将泛型从E改为 ? extends E. 
         */  
        Collection<Student> coll = new ArrayList<Student>();  
        coll.add(new Student("abc1",21));  
        coll.add(new Student("abc2",22));  
        coll.add(new Student("abc3",23));  
        coll.add(new Student("abc4",24));  
          
        Collection<Worker> coll2 = new ArrayList<Worker>();  
        coll2.add(new Worker("abc11",21));  
        coll2.add(new Worker("abc22",27));  
        coll2.add(new Worker("abc33",35));  
        coll2.add(new Worker("abc44",29));  
          
        TreeSet<Person> ts = new TreeSet<Person>(coll);//coll2 也可以传进来。泛型中的Person类型是Student和Worker的父类  
        ts.add(new Person("abc8",21));  
        ts.addAll(coll2);//addAll(Collection<? extends E> c);  
        for (Iterator<Person> it = ts.iterator(); it.hasNext();) {  
            Person person = it.next();  
            System.out.println(person.getName());  
        }  
    }  
}  
//原理  
class MyTreeSet<E>{  
    MyTreeSet(){  
          
    }  
    MyTreeSet(Collection<? extends E> c){  
          
    }  
}  

 案例二,演示泛型下限(取出操作)在API中的体现。同样,我们这里使用的是另一个TreeSet的构造函数:TreeSet<E>(Comparator<? super E> comparator)

package ustc.lichunchun.generic.demo;  
  
import java.util.Comparator;  
import java.util.Iterator;  
import java.util.TreeSet;  
import ustc.lichunchun.domain.Person;  
import ustc.lichunchun.domain.Student;  
import ustc.lichunchun.domain.Worker;  
  
public class GenericDemo10 {  
  
    public static void main(String[] args) {  
        /* 
         * 演示泛型限定在API中的体现。 
         * TreeSet的构造函数。 
         * TreeSet<E>(Comparator<? super E> comparator) 
         *  
         * 什么时候用到下限呢? 
         * 当从容器中取出元素操作时,可以用E类型接收,也可以用E的父类型接收。 
         *  
         */  
        //创建一个Student、Worker都能接收的比较器。  
        Comparator<Person> comp = new Comparator<Person>() {//匿名内部类  
            @Override  
            public int compare(Person o1, Person o2) {//每次都是容器中的两个元素过来进行比较   
                int temp = o1.getAge()-o2.getAge();  
                return temp==0?o1.getName().compareTo(o2.getName()):temp;  
            }  
        };  
          
        TreeSet<Student> ts = new TreeSet<Student>(comp);  
        ts.add(new Student("abc1",21));  
        ts.add(new Student("abc2",28));  
        ts.add(new Student("abc3",23));  
        ts.add(new Student("abc4",25));  
          
        TreeSet<Worker> ts1 = new TreeSet<Worker>(comp);  
        ts1.add(new Worker("abc11",21));  
        ts1.add(new Worker("abc22",27));  
        ts1.add(new Worker("abc33",22));  
        ts1.add(new Worker("abc44",29));  
          
        for (Iterator<? extends Person> it = ts1.iterator(); it.hasNext();) {  
            Person p = it.next();//多态  
            System.out.println(p);  
        }  
    }  
  
}  
//原理  
class YouTreeSet<E>{  
    YouTreeSet(Comparator<? super E> comparator){  
          
    }  
}  

泛型的细节:

    1.泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型;
    2.使用带泛型的类创建对象时,等式两边指定的泛型必须一致;原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;       
    3.等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);

ArrayList<String> al = new ArrayList<Object>();  //错  
//要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。  
  
ArrayList<? extends Object> al = new ArrayList<String>();  
al.add("aa");  //错  
//因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。  
// ?extends Object 代表Object的子类型不确定,怎么能添加具体类型的对象呢?  
  
public static void method(ArrayList<? extends Object> al) {  
    al.add("abc");  //错  
    //只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。  
}  

五. 总结

注意:Map没有迭代器,只有将Map中的转换成Set之后才可以进行遍历。

在Java集合框架中,线程同步的只用Hashtable和Vector两个。

Java中==  和 equals() 的区别

       //基本数据类型的比较
        int num1 = 10;
        int num2 = 10;
        System.out.println(num1 == num2);   //true
        //引用数据类型的比较
        String s1 = "chance";
        String s2 = "chance";
        System.out.println(s1 == s2);        //true
        System.out.println(s1.equals(s2));    //true
        //String类中==与equals的比较
        String s3 = new String("chance");     
        String s4 = new String("chance");
        System.out.println(s3 == s4);        //false
        System.out.println(s3.equals(s4));    //true
        //非String类中==与equals类型的比较
        Scanner scanner = new Scanner(System.in);      
        Scanner scanner2 = new Scanner(System.in);
        System.out.println(scanner.equals(scanner2));       //false
        Scanner sc = scanner;
        System.out.println(scanner.equals(sc));            //true

输出结果如下:

true
true
true
false
true
false
true

                    

------------------------------------------------------------------ 我是低调的分隔线  ----------------------------------------------------------------------

                                                                                                                                     吾欲之南海,一瓶一钵足矣...

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java基础--------集合框架 的相关文章

  • 无法在 Android 10 中创建目录

    我无法在 android 10 中创建目录 它可以在 android Oreo 之前的设备上运行 我尝试了两种创建文件夹的方法 Using File mkdir File f new File Environment getExternal
  • HashMap不写入数据库

    我尝试在我的数据库中写入 但只写入发件人和消息 我不明白为什么会发生这种情况 我认为问题出在我使用 sendMessage 的地方 我认为问题是我没有什么可以做的读 写其他用户的主键 我在数据库中写入消息的活动 public class M
  • Java Try Catch Final 没有 Catch 的情况下会阻塞

    我正在审查一些新代码 该程序只有一个 try 和一个 finally 块 既然排除了 catch 块 那么如果 try 块遇到异常或任何可抛出的内容 它如何工作 它直接进入finally块吗 如果 try 块中的任何代码可以引发已检查异常
  • 使用 GWT CellTableBuilder 构建树表

    Is it possible to build a tree table like this http www sencha com examples ExamplePlace basictreegrid with the new Cell
  • 添加动态数量的监听器(Spring JMS)

    我需要添加多个侦听器 如中所述application properties文件 就像下面这样 InTopics Sample QUT4 Sample T05 Sample T01 Sample JT7 注意 这个数字可以多一些 也可以少一些
  • 使用cameltestsupport进行Camel单元测试,模板始终为空

    我正在用 Camel 做一个简单的单元测试 我想做的就是从文件 在资源下 读取 JSON 内容 将其发送到 Java 类进行验证 这是我试图测试的路线 无论我做什么 模板 我用来发送正文 json 始终为空 这是我的代码 public cl
  • 如何在spring mvc中从控制器名称+操作名称获取映射的URL?

    是否有现有的解决方案可以从 Spring MVC3 中的 控制器名称 操作名称 获取映射的 URL 例如 asp net mvc 或 Rails 中的 UrlHelper 我觉得非常有用 thx 也许 你想要这样的东西 in your Co
  • 如果在睡眠线程上调用interrupt()会发生什么?

    我有一个线程 然后run I call sleep 如果我中断这个线程会发生什么 MyThread extends Thread public void run try sleep 1000000 catch InterruptedExce
  • 无法在 Spring Boot 测试中模拟 persistenceContext

    我正在使用带有 Mockito 框架的 spring boot 测试来测试我的应用程序 存储库类 EntityManager 之一作为参考 我的班级如下所示 Repository Transactional Slf4j public cla
  • 如何让spring为JdbcMetadataStore创建相应的schema?

    我想使用此处描述的 jdbc 元数据存储 https docs spring io spring integration docs 5 2 0 BUILD SNAPSHOT reference html jdbc html jdbc met
  • 如何记录来自 Akka (Java) 的所有传入消息

    在 Scala 中 您可以使用 LoggingReceive 包装接收函数 如何通过 Java API 实现相同的目标 def receive LoggingReceive case x do something Scala API 有Lo
  • 尝试使用等于“是”或“否”的字符串变量重新启动 do-while 循环

    计算行程距离的非常简单的程序 一周前刚刚开始 我有这个循环用于解决真或假问题 但我希望它适用于简单的 是 或 否 我为此分配的字符串是答案 public class Main public static void main String a
  • Linux 上有关 getBounds() 和 setBounds() 的 bug_id=4806603 的解决方法?

    在 Linux 平台上 Frame getBounds 和 Frame setBounds 的工作方式不一致 这在 2003 年就已经有报道了 请参见此处 http bugs java com bugdatabase view bug do
  • 对象锁定私有类成员 - 最佳实践? (爪哇)

    I asked 类似的问题 https stackoverflow com questions 10548066 multiple object locks in java前几天 但对回复不满意 主要是因为我提供的代码存在一些人们关注的问题
  • 将图像添加到自定义 AlertDialog

    我制作了一个 AlertDialog 让用户可以从我显示的 4 个选项中选择一个 前 3 个让他们在单击号码时直接拨打号码 第 4 个显示不同的视图 现在看起来是这样的 由于第四个选项的目的是不同的任务 我想让它看起来不同 因为用户可能会感
  • JVM:是否可以操作帧堆栈?

    假设我需要执行N同一线程中的任务 这些任务有时可能需要来自外部存储的一些值 我事先不知道哪个任务可能需要这样的值以及何时 获取速度要快得多M价值观是一次性的而不是相同的M值在M查询外部存储 注意我不能指望任务本身进行合作 它们只不过是 ja
  • 解决错误javax.mail.AuthenticationFailedException

    我不熟悉java中发送邮件的这个功能 我在发送电子邮件重置密码时遇到错误 希望你能给我一个解决方案 下面是我的代码 public synchronized static boolean sendMailAdvance String emai
  • Java的-XX:+UseMembar参数是什么

    我在各种地方 论坛等 看到这个参数 并且常见的答案是它有助于高并发服务器 尽管如此 我还是找不到 sun 的官方文档来解释它的作用 另外 它是Java 6中添加的还是Java 5中存在的 顺便说一句 许多热点虚拟机参数的好地方是这一页 ht
  • 嵌入式 Jetty - 以编程方式添加基于表单的身份验证

    有没有一种方法可以按如下方式以编程方式添加基于表单的身份验证 我用的是我自己的LdapLoginModule 最初我使用基本身份验证并且工作正常 但现在我想在登录页面上进行更多控制 例如显示徽标等 有没有好的样品 我正在使用嵌入式 jett
  • 在哪里存储 Java 的 .properties 文件?

    The Java教程 http download oracle com javase tutorial essential environment properties htmlon using Properties 讨论如何使用 Prop

随机推荐