Java集合定义与用法实例总结【Set、List与Map】
作者:喜欢特别冷的冬天下着雪 发布时间:2023-11-21 08:26:40
本文实例讲述了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程序设计有所帮助。
来源:https://blog.csdn.net/kkkkkxiaofei/article/details/18041205


猜你喜欢
- 这篇文章主要介绍了springboot 定时任务@Scheduled实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的
- springboot 启动排除某些bean的注入问题:最近做项目的时候,需要引入其他的jar。然后还需要扫描这些jar里的某些bean。于是
- ExpandoObject:表示一个对象,该对象包含可在运行时动态添加和移除的成员。 dynamic dynEO = new Expando
- 前言:synchronized 在 JDK 1.5 之前性能是比较低的,在那时我们通常会选择使用 Lock 来替代 synchronized
- 过去在Android上网络通信都是使用的Xutils 因为用它可以顺道处理了图片和网络这两个方面,后来发觉Xutils里面使用的是HttpC
- Java与Scala创建List与Map//JavaList<String> languages = new ArrayList
- 本项目为大家分享了Java实现简单计算器功能的具体代码,供大家参考,具体内容如下一 项目说明实训目的:掌握 Java GUI 开发中的布局管
- ThreadLocal简介变量值的共享可以使用public static的形式,所有线程都使用同一个变量,如果想实现每一个线程都有自己的共享
- 在上篇文章给大家介绍了使用Java8 实现观察者模式的方法(上),本文继续给大家介绍java8观察者模式相关知识,具体内容如下所述:线程安全
- 实践仿照@EnableEurekaServer实现自动装配如果你使用过Eureka作为微服务的注册中心,那么对@EnableWebConfi
- Memcached 介绍Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据
- 一、项目简述功能包括: 登录注册,办理借阅。借阅记录,预约借阅,借出未还, 借阅逾期,学生管理,图书管理,书库分类查询搜索。二、项目运行环境
- 1.先在build.gradle(Module)下添加引用viewPager2的库implementation 'androidx.
- 一、Java 运行时数据区域友情提示:这部分内容可能大部分同学都有一定的了解了,可以跳过直接进入下一小节哈。Java 虚拟机在执行 Java
- JSON数据格式简洁,用于数据的持久化和对象传输很实用。最近在做一个Razor代码生成器,需要把数据库的表和列的信息修改后保存下来,想到用J
- 本文介绍 Spring Boot 项目中整合 ElasticSearch 并实现 CRUD 操作,包括分页、滚动等功能。之前在公司使用 ES
- 作用mybatis-plus接口mapper方法中的注解(如@Select)或者xml(如)传入的参数是通过#{param}或者${para
- 需求:字符串(字符串只有一位小数)转float进行运算, 将结果转成字符串(保留一位小数)直接上代码:float f1 = 0.1f;Str
- 1、unity的脚本模板新版本unity中的C#脚本有三类,第一类是我们平时开发用的C# Script;第二类是Testing,用来做单元测
- String和StringBuilder和StringBuffer,这三个都是值得深究一翻的,可能很多人会说,实在不行的话,都全部用Stri