11、集合

集 合

1、概述

    集合类,API的常用对象,其实是一个容器。

    集合用于存对象,是能存储对象的对象。

    数组也能存,但数组固定长度,而集合不固定长度。

 

2、集合的由来:

    对象用于封装特有数据,对象多了需要存储,如果对象的个数不确定,就使用集合容器来进行存储。

 

3、集合的特点:

    *用于存储对象的容器

    *集合的长度是可变的

    *集合中不可以存储基本数据类型值。

      共享数据都存储在方法区里的

      集合容器因为内部的数据结构不同,有多种具体容器。不断的向上抽取,就形成了集合框架。

 

4、Java中集合类的关系图

 

5、集合框架的顶层Collection接口:

  (1)Collection接口常用方法:

     添加

      boolean add(Object obj)

      boolean addAll(Collection coll);

 

    删除

      boolean remove(Object obj)

      boolean removeAll(Collection coll);

      void clear();

 

    判断

      boolean contains(Object obj)

      boolean containsAll(Collection coll);

      boolean isEmpty()判断集合中是否有元素

 

    获取:

      int size():

      Iterator接口:对Collection进行迭代的迭代器,取出元素的方式:迭代器

 

    其他

      boolean retainAll(Collection coll);取交集

      Object[] toArray():将集合转成数组;

 

    集合的remove方法会改变集合的长度。

 

5、迭代器

  迭代器其实就是集合的取出方式。把取出元素的方式定义在集合内部,这样取出方式就可以直接访问集合内部的元素。

  该对象必须依赖于具体容器,因为每一个容器的数据结构都不同。所以该迭代器对象是在容器中进行内部实现的,对于使用容器者而言,具体的实现不重要,只要通过容器获取到该实现的迭代器的对象即可,也就是iterator方法。

  Iterator接口就是对所有的Collection容器进行元素取出的接口

  使用了Collection中的iterator()方法获取集合中的迭代器对象。

  Iterator it = coll.iterator();   

 

6、Collection下体系:

  *List:有序(存入和取出的属性一致),元素都有索引,元素可以重复

  *Set:元素不能重复,无序。

 

  (1)List集合

    a、List:特有常见方法:有一个共享特点就是都可以操作脚标

      *添加

        void add(index,element)

        void add(index,collection)

 

      *删除

        Object remove(index);

 

      *修改

        Object set(index,element)

 

      *获取:

        Object get(index);

        int indexOf(Object);

        int lastIndexOf(Object)

        List subList(from,to)

 

    b、List下属子类:

      *Vector:内部是数组数据结构,同步的。增删,查询都很慢。

        **Vector如果存储元素超过默认长度时,会以100%长度扩展。

        **Vector特有的方法:枚举

          ***通过elements()方法获取组件的枚举。

          ***枚举是Vector特有的取出方式。枚举和迭代器实质是一样的,但是其名称及方法名过长,被迭代器取代了。  

      *ArrayList:内部是数组数据结构,是不同步的,替代了Vector。查询的速度快。

        **集合长度不是固定的,ArrayList()创建时数字默认长度是10,当存储元素超过10时,会以50%的长度进行扩展。将原来元素复制到新数组中,将新元素往后添加。                          

      *LinkedList:内部是链表数据结构,是不同步的。增删元素的速度很快。

         **LinkedList是一个双向队列集合,特有方法就是可以直接对集合首尾进行添加、获取和删除操作。

         **LinkedList例子:

   

import java.util.LinkedList;


/**

 * 使用LinkedList模拟一个堆栈或者队列数据结构

 * 

 * 堆栈:先进后出

 * 队列:先进先出

 * @author xiuyuan

 *

 */

class DuiLie{

  private LinkedList link;

 

    DuiLie(){

        link = new LinkedList();

    }

 

    public void myAdd(Object obj){

        link.addFirst(obj);

    }

 

    public Object myGet(){

        return link.removeLast();

    }

 

    public boolean isNull(){

        return link.isEmpty();

    }

}

 

public class LinkedListTest {


  public static void main(String[] args) {

    DuiLie dl =  new DuiLie();
 

    dl.myAdd("java01");

    dl.myAdd("java02");

    dl.myAdd("java03");

    dl.myAdd("java04");

 

    while(!dl.isNull()){
  
        System.out.println(dl.myGet());

    }

  }

}

     

 

    c、迭代器:

      List集合有自身的迭代器,通过listIterator()方法获取该迭代器对象。

      ListIterator是Iterator的子接口,通过该接口,可以对list集合迭代时进行增删改查。

 

  (2)Set集合

    Set:元素不可以重复,是无序的。

    Set接口中的方法和Collection一致

 

    a、HashSet:内部数据结构是哈希表,是不同步的。

      如何保证该集合的元素唯一性呢?

        保证元素唯一性的原理:判断元素的hashCode值是否相同,如果相同,还会继续判断元素的equals方法是否为true。需要注意的是,hashCode和equals不是我们调用的,是底层自己调用的。对于判断元素是否存在以及删除等操作,依赖的都是元素的hashCode和equals。

        记住:如果元素要存储到HashSet集合中,必须覆盖hashCode方法和equals方法。

        一般情况下,如果定义的类会产生很多对象,比如人,学生,书,通常都需要覆盖equals,hashCode方法,建立对象判断是否相同的依据。

      HashSet:因为底层为哈希表结构,存储元素时,会先计算对象的哈希值,按哈希值来确定存储的位置,如果哈希值相同,则会进一步判断对象的内容是否相同,相同则不会存储,如果不同,则会在相同的哈希值位置上顺延下去。 

 

     b、TreeSet:底层是二叉树结构,可以对Set集合中的元素进行排序。是不同步的。

      判断元素唯一性的方式:就是根据比较方法的返回结果是否是0,0表示相同。

      TreeSet对元素进行排序的第一种方式:

        让元素自身具备比较功能,元素需要实现Comparable接口。覆盖compareTo方法,这种方法也称为元素的自然顺序或默认顺序。

class Student implements Comparable{//该接口强制让学生具备比较性

  private String name;

  private int age;

 

  Student(String name,int age){

    this.name = name;

    this.age = age;

  }

 

  public int compareTo(Object obj){

    if(!(obj instanceof Student)){

        throw new RuntimeException("不是学生对象");

    }

 

    Student stu = (Student)obj;

 

    System.out.println(this.name+"...compareto..."+stu.name);

 

    if(this.age == stu.age){

        return this.name.compareTo(stu.name);

    }

    return this.age - stu.age;

  }
  
public String getName(){     return name;   }   public int getAge(){   return age;   } }

 

      TreeSet对元素进行排序的第二种方式:

        当元素自身不具备比较性时,或具备的比较性不是所需要的,这时就需要让集合自身具备比较功能,定义一个类实现Comparator接口,覆盖compare方法。

        将该类对象作为参数传递给TreeSet集合的构造函数。

        当两种比较方式都存在时,以集合的比较方式为主。

代码示例:

public class TreeSetDemo2 {

 

  public static void main(String[] args){

    TreeSet ts = new TreeSet(new MyCompare());

 

    ts.add(new Student("lisi",22));

    ts.add(new Student("lisi007",20));

    ts.add(new Student("lisi09",19));

    ts.add(new Student("lisi08",19));

    ts.add(new Student("lisi01",40));

 

    for(Iterator it = ts.iterator();it.hasNext();){

      Student stu = (Student)it.next();

      System.out.println(stu.getName()+"..."+stu.getAge());
  
    }

  }

}

 

class MyCompare implements Comparator{

  public int compare(Object o1, Object o2){

    Student s1 = (Student)o1;

    Student s2 = (Student)o2;

 

    int num = s1.getName().compareTo(s2.getName());
  
 

    return num==0?s1.getAge()-s2.getAge():num;

  }

}

 泛型

 

  JDK1.5版本以后出现的新特性,用于解决安全问题,是一个类型安全机制。

 

  好处:

 

    1、经运行时期出现的问题ClassCastException,转移到了编译时期。方便程序员解决问题,让运行时期问题减少,安全。

 

    2、避免了强制转换的麻烦。

 

 

 

  泛型格式:通过<>来定义要操作的引用数据类型。

 

  在使用java提供的对象时,什么时候写泛型呢?

 

    通常在集合框架中很常见,只要见到<>就要定义泛型。

 

  其实<>就是用来接收类型的。当使用集合时,将集合中要存储的数据类型作为参数传递到<>即可。

 

 

 

泛型类:

 

  什么时候定义泛型类?

 

    当类中要操作的引用数据类不确定的时候,

 

    早期定义Object来完成扩展

 

    现在定义泛型来完成扩展。

 

 

 

 

 

泛型方法:

  泛型类定义的泛型,在整个类中有效,如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。

  为了让不同方法可以操作不同类型,而且类型还不确定,那么可以将方向定义在方法上。

 

   上图,show方法类型跟着类上定义的泛型,而print方法则有随自己定义的泛型

 

静态方法泛型:

  特殊之处:静态方法不可以方法类上定义的泛型。

  如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上

 

 

注意:方法泛型位置:必须方法修饰符后面,返回值类型前面!

 

泛型定义在接口上:

 

 

泛型限定:

  ? 通配符,也可以理解为占位符

泛型的限定:

  ? extends E:可以接收E类型或E的子类型。上限

  ? super E:可以接收E类型或者E的父类型。下限

 

  泛型通配符<?>叫做占位符,表示不明确类型

 

上图结果是什么类型都能够传入printColl方法里

即定义方向为<?>后,什么类型都可以传入

 

 上图即是泛型限定,限定了传入的类型范围,

<? extends Person> 表示只可以传入Person类或其子类。

 

 

Map集合

  该集合存储键值对,一对一对往里存,而且要保证键的唯一性

  方法:

    1、添加

      put(K key,V value);添加元素,存入相同键的值时,新值会替代旧值,并且会返回旧值。

      putAll(Map<? extends K,? extends V> m);添加传入的所有元素

 

    2、删除

      clear();从此映射中移除所有映射关系

      remove(Object key);根据键删除元素

 

    3、判断

      containsValue(Object value);判断值

      containsKey(Object key);判断键

      isEmpty();

 

    4、获取

      get(Object key);获取该键对应的值

      size()

      values();获取所有值。返回此映射中包含的值的Collection视图。

      entrySet()

      keySet()

 

Map:

  |--Hashtable:底层是哈希表数据结构,不可以存入null键null值。该集合是线程同步的。效率低

  |--HashMap:底层是哈希表数据结构,允许使用null键和null值,该集合是线程非同步的。效率高

  |--TreeMap:底层是二叉树结构,线程不同步,可以用于给Map集合中的键进行排序。

 

  和Set很像,其实Set底层就是使用了Map集合。

  可以通过get方法的返回值来判断一个键是否存在,通过返回null来判断

 

Map集合的两种取出方式:

  Map集合的取出原理:将map集合转成set集合,再通过迭代器取出。

    1、Set<k> keySet:将map中所以的键存入到Set集合中,因为Set集合具备迭代器,所以可以通过迭代方式取出所有的键,然后通过get方法获取每一个键对应的值

//先获取map集合中的所有的键的set集合keySet();方法

Set<String> keySet = map.keySet();

 

//通过Set集合,就可以获取其迭代器

for(Iterator<String> it = keySet.iterator();it.hasNext();){

  String key = it.next();

  //有了键就可以通过map集合的get方法获取其对应的值

  String value = map.get(key);

  System.out.println(key+":"+value);

}

 

    2、Set<Map.Entry<K,V>> entrySet:将map集合中的映射关系存入到了set集合中而这个关系的数据类型就是Map.Entry。

//将map集合中的映射关系取出

Set<Map.Entry<String, String>> set = map.entrySet();

for(Iterator<Map.Entry<String,String>> it = set.iterator();it.hasNext();){

  Map.Entry<String, String> me = it.next();

  String key = me.getKey();

  String value = me.getValue();

  System.out.println(key+":"+value);

}

 

  Map.Entry<K,V>接口:

    Map.Entry 其实Entry也是一个接口,它是Map接口中的一个内部接口。

      该接口是静态修饰的,故一定是内嵌的接口,在成员位置上,才也可以被static修饰符修饰,

 

  什么时候使用Map集合呢?

    当数据之间存在着映射关系时,就要先考虑使用map集合。

 

Map扩展知识:

  Map集合被使用是因为具备映射关系。

  一对多映射关系

  

Collections类

  工具类:里面的方法都是静态的,不具备构造函数,不需要被实例化的,因为内部并没有封装特有数据,都是共享方法。

    该类是专门用于对集合进行操作的工具类。

 

  常用方法:

    排序:

      static <T extends Comparable<? super T>> void sort(List<T> list) 根据元素的自然顺序排序,泛型限定要保证元素具备比较性

      static <T> void sort(List<T> list,Comarator<? super T> c)根据指定比较器产生的顺序排序,不需限定元素是否具备比较性。

        public static <Comparable<? super T>> T Max(Collection<? extends T> coll)获取最大元素

        binarySearch(List<? extends Comparable<? Super T>> list,T key) 使用二分查找获取指定对象。

        public static <T> void fill(List<? Super T> list,T obj)是指定元素替换列表中的所有元素。

        replaceAll(List,old,new)替换元素

      reverse(list)将集合元素反转

  

      Comparator<T> reverseOrder();返回一个比较器,强行逆转实现了Comparable接口的对象collection的自然顺序。

       重载的函数。传入比较器,强行将其逆转。

 

  将线程不同步转为同步

其实底层原理就是加锁,加锁后里面调用的还是原集合中的方法。

    底层源代码:

static <T> List<T> synchronizedList(List<T> list, Object mutex) {

  return (list instanceof RandomAccess ?

                new SynchronizedRandomAccessList<T>(list, mutex) :

                new SynchronizedList<T>(list, mutex));

}

 
static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {

  static final long serialVersionUID = -7754090372962971524L;
  
final List<E> list;
  SynchronizedList(List
<E> list) {   super(list);    this.list = list;   }   SynchronizedList(List<E> list, Object mutex) { super(list, mutex);   this.list = list; }   public boolean equals(Object o) {        synchronized(mutex) {return list.equals(o);}   }   public int hashCode() {     synchronized(mutex) {return list.hashCode();}   }   public E get(int index) {     synchronized(mutex) {return list.get(index);}   }   public E set(int index, E element) {     synchronized(mutex) {return list.set(index, element);}   }   public void add(int index, E element) {     synchronized(mutex) {list.add(index, element);}   }   public E remove(int index) {     synchronized(mutex) {return list.remove(index);}   }   public int indexOf(Object o) {     synchronized(mutex) {return list.indexOf(o);}   }   public int lastIndexOf(Object o) {     synchronized(mutex) {return list.lastIndexOf(o);}   }   public boolean addAll(int index, Collection<? extends E> c) {     synchronized(mutex) {return list.addAll(index, c);}   }   public ListIterator<E> listIterator() {     return list.listIterator(); // Must be manually synched by user   }   public ListIterator<E> listIterator(int index) {     return list.listIterator(index); // Must be manually synched by user   }   public List<E> subList(int fromIndex, int toIndex) {      synchronized(mutex) {       return new SynchronizedList<E>(list.subList(fromIndex, toIndex), mutex);     }   }

 

Arrays:用于操作数组的工具类,里面都是静态方法。

  asList:将数组变成list集合

    把数组变成list集合有什么好处?

      可以使用集合的思想和方法来操作数组中的元素。

      注意:将数组变成集合,不可以使用集合的增删方法,因为数组的长度是固定的

 

如果数组中的元素都是对象,那么变成集合时,数组中的元素就直接转成集合中的元素,如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在

 

  集合变数组:

    Collection接口中的toArray方法

       1、指定类型的数组到底要定义多长呢?

        当指定类型的数组的长度小于了集合的size,那么该方法内部会创建一个新的数组,长度为集合的size;

        当指定类型的数组的长度大于了集合的size,就不会新创建数组,而是使用传递进来的数组

        所以创建一个刚刚好的数组最优。

      2、为什么要将集合变数组?

        为了限定对元素的操作。不需要进行增删了。

 

高级for循环:

  格式:

    For(数据类型 变量名:被遍历的集合(Collection)或数组){

    }

  对集合进行遍历时,只能获取元素,不能对集合进行操作。

  迭代器除了遍历,还可以进行remove集合中元素的动作。

  如果使用ListIterator,还可以在遍历过程中对集合进行增删改查的动作。

  传统for和高级for有什么区别呢?

    高级for有一个局限性,必须有被遍历的目标,

    建议:在遍历数组时,还是希望使用传统for,因为传统for可以定义角标。

 

JDK1.5版本出现的新特性:可变参数,其实就是一种数组参数的简写形式,不用每一次都建立数组对象,只要将要操作的元素作为参数传递即可。隐式将这些参数封装成数组。

  格式:类型...形参名

    方法的可变参数

  在使用时注意:可变参数一定要定义在参数列表的最后面。

 

JDK1.5版本出现的新特性:StaticImport:静态导入。

  当类名重名时,需要指定具体的包名

  当方法重名时,需要指定具体所属的对象或者类。

  

 

System类:类中的方法和属性都是静态的

  Out:标准输出,默认是控制台

  Int:标准输入,默认是键盘

  System类包含一些有用的类字段和方法。它不能被实例化。

  描述系统的一些信息

  获取系统属性信息:Properties getProperties();

 

Runtime类:

  该类中并没有提供构造函数,说明不可以new对象,那么会直接想到该类中的方法都是静态的,发现该类中还有非静态方法,说明该类肯定会提供一个方法提供本类对象,而且该方法是静态的,并返回本类类型对象

  该方法是static Runtime getRuntime();

  由这个特点,可以看出该类使用了单例设计模式。

  每个java应用程序都有一个Runtime类实例,是应用程序能够与其运行的环境相连接。可以通过getRuntime方法获取当前运行时。应用程序不能创建自己的Runtime实例

 

  方法:

    public Process exec(String command);该方法在单独的进程中执行指定的字符串命令。

      会返回一个新的Process对象,用于管理子进程。

      Process类是一个抽象类,里面全都是抽象方法,不能够new对象,并且没有子类。

      Process类里方法:destroy()方法可以杀掉子进程。

 

Date类:日期类

  Date类表示特定的瞬间,精确到毫秒

  该类可以创建对象,但类中许多方法已过时

  Date类的参考类:DateFormat类,

  DateFormat类是日期/时间格式化子类的抽象类,它以与语言无关的方式格式化并解析日期或时间。

 

 

SimpleDateFormat类是DateFormat的子类

  该类是一个以与语言环境有关的方式来格式化和解析日期的具体类。

  

 

通过构造函数就可以将自定义的规则传入

 代码体现:

 

 

Calendar类

  Calendar类是一个抽象类,(注:Calendar表示日历,Date表示日期)

  可先通过Date类中的方法来寻找Calendar中的字段,Date类中的许多方法被Calendar的字段替代了。

  其直接子类:GregorianCalendar:提供了世界上大多数国家/地区使用的标准日历系统。

  Calendar类不能创建对象,但对外提供了方法获取其对象

  static Calendar getInstance():使用默认时区和语言环境获得一个日历。

  Set(int field,int value)方法可以设定时间

  Add(int field,int amount)方法按照日历的规则,为给定的日历字段添加或减去指定的时间量

 

public static void main(String[] args) {

  Calendar c = Calendar.getInstance();

  c.set(2011, 2, 1);

  c.add(Calendar.DAY_OF_MONTH, -1);

  printCalendar(c);

}


/**

 * 该方法用于打印Calendar

 */

public static void printCalendar(Calendar c){

    String[] mons = {"一月","二月","三月","四月"

            ,"五月","六月","七月","八月"

            ,"九月","十月","十一月","十二月"};

    String[] weeks = {"","星期日","星期一","星期二","星期三"

              ,"星期四","星期五","星期六"};

 

  System.out.println(c.get(Calendar.YEAR)+"年");
  
  System.out.println(mons[c.get(Calendar.MONTH)]);
  
  System.out.println(c.get(Calendar.DAY_OF_MONTH));

  System.out.println(weeks[c.get(Calendar.DAY_OF_WEEK)]);

  System.out.print(c.get(Calendar.HOUR_OF_DAY)+":"+c.get(Calendar.MINUTE)+":"+c.get(Calendar.SECOND));

}

 

 

Math类:数学类,工具类,都是静态方法

  方法:

    static double abs(E e)返回绝对值

    Double ceil(double e)方法返回大于指定数据的最小整数。

    Double floor(double e)方法,返回小于指定数据的最大整数。

    Long round(double a)返回最接近参数的long;四舍五入

    Double pow(double a,double b)返回a的b次幂。

    Double random()返回带正号的double,该值大于等于0.0且小于1.0,即随机数

 

原文地址:https://www.cnblogs.com/zyh-blog/p/3256494.html