Java集合定义与用法实例总结【Set、List与Map】

本文实例讲述了Java集合定义与用法。分享给大家供大家参考,具体如下:

java集合大体可分为三类,分别是Set、List和Map,它们都继承了基类接口Collection,Collection接口定义了众多操作集合的基本方法,如下:

为了访问Collection集合,不得不去了解Iterator接口。该接口很简单,主要用于定义访问集合的方法,如下:

所以上述的三大类子集合必定都继承了上面2个接口。其中Set集合要求元素不重复,且内部无序,所以访问时只能根据元素值来访问;List内部为动态数组,支持有序,元素也可重复,所以往往有index;Map所代表的集合是具有Key-Value的映射关系的集合,如哈希表。

1. Set

1.1 Set不可添加相同元素

import java.util.Collection;
import java.util.HashSet;
public class TestSet {
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public static void main(String[] args) {
    Collection c1 = new HashSet();
    Person p = new Person();
    c1.add(p);
    c1.add(p);
    System.out.println(c1);
    Collection c2 = new HashSet();
    String str1 = new String("123");
    String str2 = new String("123");
    c2.add(str1);
    c2.add(str2);
    System.out.println(c2);
  }
}
class Person {
  public Person() {
  }
  public Person(String name) {
    this.name = name;
  }
  public String name;
}

运行输出:

[demo.Person@1db9742]
[123]

第一次添加了俩次p对象,集合不会重复添加,所以输出了[Person@1db9742],这很合理。但是第二次明明new了两个字符串,str1和str2的引用肯定是不同的,那为什么程序还是会认为是相同的元素呢。查找add(E e)的源码,找到了其中的关键部分,如下

public boolean add(E e) {
  return map.put(e, PRESENT)==null;
 }
 public V put(K key, V value) {
  if (key == null)
   return putForNullKey(value);
  int hash = hash(key.hashCode());
  int i = indexFor(hash, table.length);
  for (Entry<K,V> e = table[i]; e != null; e = e.next) {
   Object k;
   if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    V oldValue = e.value;
    e.value = value;
    e.recordAccess(this);
    return oldValue;
   }
  }
  modCount++;
  addEntry(hash, key, value, i);
  return null;
 }

这一句

if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

表明,当两个对象的哈希值相等并且对象的equals方法返回真时,则认为两个对象是相同的,并不会进行后面的addEntry操作,即不会添加至集合。

这也就难怪String str1=new String("123")String str2=new String("123");被认为是同一个对象了,因为String在做equals的时候恰好很特殊,只要值相等,则euqals就返回真。

为了测试源码是否真的是这么执行的,改写程序如下:

import java.util.Collection;
import java.util.HashSet;
public class TestSet {
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public static void main(String[] args) {
    Collection c1 = new HashSet();
    c1.add(new A());
    c1.add(new A());
    c1.add(new B());
    c1.add(new B());
    c1.add(new C());
    c1.add(new C());
    System.out.println(c1);
  }
}
class A {
  @Override
  public boolean equals(Object obj) {
    return true;
  }
  @Override
  public int hashCode() {
    return 1;
  }
}
class B {
  @Override
  public int hashCode() {
    return 1;
  }
}
class C {
  @Override
  public boolean equals(Object obj) {
    return true;
  }
}

输出:

[demo.A@1, demo.B@1, demo.B@1, demo.C@1db9742, demo.C@106d69c]

可以看到,B和C的对象都没有被集合认为是同一个对象,而A类中重写的哈希值和equals永远相等,导致A类new出的匿名对象也是相等的,故只添加了一个。

1.2 Set不可修改元素的值

import java.util.Collection;
import java.util.Iterator;
import java.util.HashSet;
public class TestSet {
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public static void main(String[] args) {
    Collection coll = new HashSet();
    coll.add(new Person("f"));
    coll.add(new Person("l"));
    coll.add(new Person("y"));
    System.out.println(coll);// a
    Iterator it = coll.iterator();// b
    while (it.hasNext()) {
      Person p = (Person) it.next();
      if (p.name.equals("f")) {
        p = new Person();// c
      }
    }
    Iterator it1 = coll.iterator();// d
    while (it1.hasNext()) {
      Person p = (Person) it1.next();
      System.out.println(p.name);
    }
    System.out.println(coll);
  }
}
class Person {
  public Person() {
  }
  public Person(String name) {
    this.name = name;
  }
  public String name;
}

运行输出:

[demo.Person@52e922, demo.Person@1db9742, demo.Person@106d69c]
y
f
l
[demo.Person@52e922, demo.Person@1db9742, demo.Person@106d69c]

代码输出表明,HashSet集合的元素并不是有序的,另外在代码c处取出了元素后,为该元素重新赋值,而后输出发现集合并没有改变,这说明iterator迭代器在提供next的方法里应该是类似于copy的技术,目的就是防止在遍历set集合的时候元素被改变。

2. List

List作为Collection的子接口,自然可以调用父接口的基本方法,但由于List集合元素是有序的,所以List接口在父接口的基础上又增加了些方法。这些方法的作用与类父接口类似,只是都会增加一个index参数做为索引。

List中最常用的就是ArrayList,它在Vector的基础上做了许多改进,下面代码将展示List的基本操作用法:

import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
public class TestList {
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public static void main(String[] args) {
    // 向list中添加不同类型的元素,会自动装箱
    List list = new ArrayList();
    list.add(1);
    list.add("123");
    list.add(3.14f);
    // 列表元素:[1, 123, 3.14]
    System.out.println("列表元素:" + list);
    // 清除列表
    list.clear();
    list.add("我");
    list.add("们");
    list.add("交");
    list.add("个");
    list.add("朋");
    list.add("友");
    list.add("吧");
    // 列表元素:[我, 们, 交, 个, 朋, 友, 吧]
    System.out.println("列表元素:" + list);
    List sub1 = list.subList(0, list.size() / 2);
    // 子列表元素:[我, 们, 交]
    System.out.println("子列表元素:" + sub1);
    // 从list中删除sub
    sub1.removeAll(list);
    // 列表元素:[个, 朋, 友, 吧]
    System.out.println("列表元素:" + list);
    // 添加至头
    List sub2 = new ArrayList();
    sub2.add("我");
    sub2.add("们");
    sub2.add("交");
    System.out.println("子列表元素:" + sub2);
    // 在list中添加sub2
    list.addAll(0, sub2);
    System.out.println("列表元素:" + list);
    // 遍历操作
    ListIterator iter = list.listIterator();
    System.out.println("--正向遍历--");
    while (iter.hasNext()) {
      System.out.println(iter.next());
    }
    System.out.println("--反向遍历--");
    while (iter.hasPrevious()) {
      System.out.println(iter.previous());
    }
  }
}

运行输出:

列表元素:[1, 123, 3.14]
列表元素:[我, 们, 交, 个, 朋, 友, 吧]
子列表元素:[我, 们, 交]
列表元素:[个, 朋, 友, 吧]
子列表元素:[我, 们, 交]
列表元素:[我, 们, 交, 个, 朋, 友, 吧]
--正向遍历--







--反向遍历--






List就像是一个动态且元素类型可不一的数组,它不仅具有iterator迭代器,而且还有listIterator,后者就像数组一样,支持正向和反向遍历。

3. Map

Map是具有映射关系的集合,key做为主键,可以索引到唯一的value,key和value都可以是对象。如果单独取出Map里的所有值的话,Map看起来就像是Set,而又由于它较之Set又具有索引功能,所以又似乎有些List的影子。实际上,Map的key必须实现equals和hashCode方法,这也就解释了为什么可以将一个对象的引用做为key了(实际上是计算这个对象的hashCode做为主键),因此不能将同一个对象的引用存入某一个Map中。HashSet实现了Set接口,ArrayList实现了List接口,那么单从命名上就能得知,HashMap肯定实现了Map接口,Map接口的功能如下,

在HashSet和ArrayList都有一个访问迭代器的方法iterator(),在Set接口中却没有,毕竟Set是key-value组合,取而代之的是一个keySet()方法,用以返回一个实现了Set接口的对象,从而又可以进行iterator的操作。

基本操作如下:

import java.util.HashMap;
import java.util.Iterator;
public class TestMap {
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public static void main(String[] args) {
    HashMap hash = new HashMap();
    hash.put("1", "我");
    hash.put("2", "们");
    // 主键可为null,但只能有一个null值的主键
    hash.put(null, null);
    // 值可以为null,可以有很多个值为null
    hash.put("3", null);
    hash.put("4", null);
    System.out.println("直接遍历:" + hash);
    System.out.println("----keySey遍历----:");
    for (Object key : hash.keySet()) {
      System.out.println("key:" + key + " value:" + hash.get(key));
    }
    System.out.println("----iterator遍历----:");
    Iterator iter = hash.keySet().iterator();
    while (iter.hasNext()) {
      String key = (String) iter.next();
      System.out.println("key:" + key + " value:" + hash.get(key));
    }
  }
}

输出:

直接遍历:{null=null, 1=我, 2=们, 3=null, 4=null}
----keySey遍历----:
key:null value:null
key:1 value:我
key:2 value:们
key:3 value:null
key:4 value:null
----iterator遍历----:
key:null value:null
key:1 value:我
key:2 value:们
key:3 value:null
key:4 value:null

HashMap可以有空key,但是只能有一个,,这符合唯一主键的原则,并且若主键重复了,则会覆盖之前的相同主键。而值却没有限制,有多少个null都可以。此外,在使用HashMap的时候还需要注意下面两点:

1.HashMap是非线程安全的,而Hashtable是线程安全的。

对于各种集合的各种操作,其实可以依赖于Collections类,该类提供了许多静态操作集合的方法,其中就可以将一个普通集合封装为线程安全的集合,如下

Collection c=Collections.synchronized(new ArrayList());

2.了解HashMap的性能

HashMap利用每一个key的哈希值,去为value找寻存储位置。这个存储位置往往被称为“桶”,当哈希值唯一时,那么一个桶中就只有一个对象,这时情况最理想,然而若非正常情况下(比如重写hashCode强制返回相等),那么一个桶能就有放多个对象,这时性能最差。

上面说道,HashMap与Set、List在某方面都很相似,做为一个强大的集合,它的内部自然也有会动态开辟内存的操作。所有就有了下面几个参数,

  • capacity(容量):在初始化HashMap时将会有一个默认值(好象是10吧),随着集合的大小也会自身调整。
  • size(元素个数):有多少个元素size就是多少。
  • load factor(负载因子):load factor=size/capacity,取值0~1。

当负载因子很大时,如有90个元素,而集合的容量为100,因子就是0.9,这样情况非常不利于查询操作,因为put和get操作会遍历大量的元素,时间复杂度无形就会增加,但在内存开销上确实是比较节省的,因为集合不会反复的创建,因为每一次扩充集合的操作,就意味着要将原始元素重新插入到新的集合中去,性能开销是很大的。

而当负载因子很小时,查询效率将会非常高(因为遍历少),但是却在内部进行了许多次开辟内存的操作。

因此,在系统中,要根据实际需求正确把握HashMap的用法,如一开始建立集合的时候就知道这个集合非常大,那么就有必要在初始化的时候就指明capacity,不应该使用默认值,这样效率能高点;相反只有少量集合元素时,不应该在创建的时候指定很大的capacity,这明显是在浪费内存。

更多关于java算法相关内容感兴趣的读者可查看本站专题:《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》

希望本文所述对大家java程序设计有所帮助。

(0)

相关推荐

  • java如何对map进行排序详解(map集合的使用)

    今天做统计时需要对X轴的地区按照地区代码(areaCode)进行排序,由于在构建XMLData使用的map来进行数据统计的,所以在统计过程中就需要对map进行排序. 一.简单介绍Map 在讲解Map排序之前,我们先来稍微了解下map.map是键值对的集合接口,它的实现类主要包括:HashMap,TreeMap,Hashtable以及LinkedHashMap等.其中这四者的区别如下(简单介绍): HashMap:我们最常用的Map,它根据key的HashCode 值来存储数据,根据key可以直接

  • Java集合Set、List、Map的遍历方法

    本文实例讲述了Java集合Set.List.Map的遍历方法,分享给大家供大家参考. 具体方法如下: package com.shellway.javase; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.uti

  • 一段代码搞懂关于Java中List、Set集合及Map的使用

    Java中List.Set集合及Map的使用代码如下所示: package tingjizifu; import java.util.*; public class TongJi { /* * 使用Scanner从控制台读取一个字符串,统计字符串中每个字符出现的次数,要求使用学习过的知识完成以上要求 * 实现思路根据Set.List.Map集合的特性完成. */ public static void main(String[] args) { // 输入字符串 Scanner input = n

  • Java中的Map集合简单汇总解析

    Map接口简介 Map接口是一种双列集合,它的每个元素都包含一个键对象Key和值对象Value,键和值对象之间存在一种对应关系,称为映射.从Map集合中访问元素时,只要指定了Key,就能找到对应的Value, Map中的键必须是唯一的,不能重复,如果存储了相同的键,后存储的值会覆盖原有的值,简而言之就是键相同,值覆盖. Map常用方法 put(K key, V value) 添加数据,如果先前包含该键的映射,则替换旧值 get(Object key) 返回指定键所映射的值 Set<Map.Ent

  • Java8中利用stream对map集合进行过滤的方法

    前言 Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找.遍历.过滤以及常见计算等. 最近公司在大张旗鼓的进行代码审核,从中也发现自己写代码的不好习惯.一次无意的点到了公司封装的对map集合过滤的方法,发现了stream.于是研究了一下.并对原有的代码再次结合Optional进行重构下 原有方法说明 主要处理过滤条件Map对象,过滤掉了null和空字符串 等操作 这

  • Java集合之HashMap用法详解

    本文实例讲述了Java集合之HashMap用法.分享给大家供大家参考,具体如下: HashMap是最常用的Map集合,它的键值对在存储时要根据键的哈希码来确定值放在哪里. HashMap 中作为键的对象必须重写Object的hashCode()方法和equals()方法 import java.util.Map; import java.util.HashMap; public class lzwCode { public static void main(String [] args) { M

  • java集合map取key使用示例 java遍历map

    复制代码 代码如下: for (Iterator i = keys.iterator(); i.hasNext()        {           String key = (String) i.next();           String value = (String) map.get(key);           text+=key + " = " + value;       } 复制代码 代码如下: <span style="border-coll

  • Java实现过滤掉map集合中key或value为空的值示例

    本文实例讲述了Java实现过滤掉map集合中key或value为空的值.分享给大家供大家参考,具体如下: import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * 过滤掉map集合中key或value为空的值 * @author lmb * @date 2017-3-14 */ public

  • Java集合定义与用法实例总结【Set、List与Map】

    本文实例讲述了Java集合定义与用法.分享给大家供大家参考,具体如下: java集合大体可分为三类,分别是Set.List和Map,它们都继承了基类接口Collection,Collection接口定义了众多操作集合的基本方法,如下: 为了访问Collection集合,不得不去了解Iterator接口.该接口很简单,主要用于定义访问集合的方法,如下: 所以上述的三大类子集合必定都继承了上面2个接口.其中Set集合要求元素不重复,且内部无序,所以访问时只能根据元素值来访问:List内部为动态数组,

  • Java泛型定义与用法实例详解

    本文实例讲述了Java泛型定义与用法.分享给大家供大家参考,具体如下: 1. 泛型的由来 先看如下代码: import java.util.List; import java.util.ArrayList; public class TestGeneric { @SuppressWarnings({ "rawtypes", "unchecked" }) public static void main(String[] args) { List list = new

  • Java集合功能与用法实例详解

    本文实例讲述了Java集合功能与用法.分享给大家供大家参考,具体如下: 本文内容: 什么是集合 Collection Iterator List set Map Collections工具类 首发日期:2018-05-17 什么是集合: 集合是一种新容器,集合可以存储数量不固定的元素(数组的空间是固定的,你申请多少空间以后都不能改变),而集合可以动态的增加空间(有些是空间不够时新建一个足够大的数组再把原来的元素移到新的数组中). 集合的出现解决的几个问题: 存储数量不等的元素. 定义了数据结构,

  • Java多态性定义与用法实例详解

    本文实例讲述了Java多态性定义与用法.分享给大家供大家参考,具体如下: 多态性是通过: 1 接口和实现接口并覆盖接口中同一方法的几不同的类体现的 2 父类和继承父类并覆盖父类中同一方法的几个不同子类实现的. 一.基本概念 多态性:发送消息给某个对象,让该对象自行决定响应何种行为 .通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用 . java 的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须

  • Java泛型定义与用法入门示例

    本文实例讲述了Java泛型定义与用法.分享给大家供大家参考,具体如下: 一 引入泛型背景 Java集合不会知道它们需要用它来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要求具有很好的通用性.但这样做也带来两个问题: 集合对元素类型没有任何限制,这样可能引发一些问题:例如想创建一个只能保存Dog对象的集合,但程序也可以轻易地将Cat对象"丢"进去,所以可能引发异常. 由于把对象"丢进"集合时,集合丢失了对象的状态信息,集合只知道它盛装的是Objec

  • java队列之queue用法实例分析

    Queue: 基本上,一个队列就是一个先入先出(FIFO)的数据结构 Queue接口与List.Set同一级别,都是继承了Collection接口.LinkedList实现了Deque接 口. Queue的实现 1.没有实现的阻塞接口的LinkedList: 实现了java.util.Queue接口和java.util.AbstractQueue接口 内置的不阻塞队列: PriorityQueue 和 ConcurrentLinkedQueue PriorityQueue 和 Concurren

  • Java适配器模式定义与用法示例

    本文实例讲述了Java适配器模式定义与用法.分享给大家供大家参考,具体如下: 将一个类的接口转换成客户想要的另一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作 适配器一共包括3种角色 1.目标:目标是一个接口,该接口是客户想使用的接口 2.被适配者:被适配者是一个已存在的接口或抽象类,这个接口或抽象类需要适配 3.适配器:适配器是一个类,该类实现了目标接口并包含有被适配者的引用,即适配者的指着是对被适配者接口与目标进行适配 package org.zhy.adapte

  • Java抽象类概念与用法实例分析

    本文实例讲述了Java抽象类概念与用法.分享给大家供大家参考,具体如下: 抽象:就是对一个事物的大概描述 抽象方法:以abstract修饰的方法,这种方法只声明返回数据类型,方法名和所需参数,并没有函数体.如 abstract void study(); 抽象类特点: 1.抽象类中不一定含有抽象方法:但抽象方法一定在抽象类中. 2.抽象类不具备实际功能,只能用于派生子类 3.抽象类中可以包含构造函数,但是构造函数不能被声明成抽象.抽象类中的成员方法包括一般方法和抽象方法 4.抽象方法和抽象类都必

  • Java集合删除元素ArrayList实例详解

    Java集合删除元素ArrayList实例详解 AbstractCollection集合类中有一个remove方法,该方法为了适配多种不同的集合,允许删除空的元素,看这部分代码的时候产生了疑问,为什么这里直接用it.remove()就直接删除了? public boolean remove(Object o) { Iterator<E> it = iterator(); if (o==null) { while (it.hasNext()) { if (it.next()==null) { i

随机推荐