JDK基础类库java.util包下那些你可能不知道的工具们大盘点(一)EnumMap、EnumSet【享学Java】

前言

聊了很长时间逻辑性非常强的Spring框架,这篇文章来些轻松点的内容:聊聊JDK基础类库中那些你可能不知道的工具

正所谓玩好JDK,面试不用愁。那么JDK好掌握吗,答案是非常难。 大家平时理解的JDK可能就只是指的它的基础类库部分,但其实此处可扫盲一下。JDK主要包含有如下三个部分:

  1. Java运行时环境(JRE),说白了就是JVM
  2. Java的基础类库(它数量可观,功能强大,覆盖面广。对开发者来说是最为中重要的部分)
  3. Java的开发工具(比如javacjmapjconsolejstackjvisualvm等等)

本文将着眼于第二部分:基础类库,并且讲解的也只还是它的冰山一角,因为本文主要还是着眼于我认为的(本人水平有限)还能稍微比较常用的一些工具

工具们介绍

下面介绍的工具,大都来自于java.util包。其实之前也有好几篇相关的工具类介绍文章。 为何专门篇幅介绍工具?因为我认为:工欲善其事必先利其器。你掌握的工具越多,你才能做好更多的事。 【小家java】聊聊Java中的java.util.Arrays类和java.util.Collections工具类 【小家Java】Java第二API之apache的commons-lang3工具包史上最完整的讲解(书写优雅代码必备工具) 【小家java】Java中Apache Commons-lang3提供的DateUtils等时间、日期工具类

EnumMap和EnumSet

这两个哥们都是JDK1.5提供的。

EnumMap

EnumMap它也属于Map体系的东西,该类是专门针对枚举类设计的一个Map集合类。集合中的所有键必须是同一个枚举类的实例,它的key为枚举元素,value自定义。

其实有小伙伴包括我也这样疑问过,我们都可以自己使用Map来实现,为何要使用EnumMap呢? 答案是:它的性能高。因为它的内部是**用数组的数据结构**来维护的!

使用Demo:

public enum Color {
    RED, BLACK, WHITE, GREEN
}

public class Main {
    public static void main(String[] args) {
        // 它没有空构造,必须制定枚举class类型。当然构造函数也可以接收一个Mapnew EnumMap<>(EnumMap/Map);
        Map<Color, String> map = new EnumMap<>(Color.class);
        map.put(Color.RED, "红色");
        map.put(Color.BLACK, "黑色");
        map.put(Color.WHITE, "白色");
        map.put(Color.WHITE, "白色"); // 故意重复放一个

        // 遍历:发现EnumMap它的存储是有序的
        map.forEach((k, v) -> System.out.print(k + ":" + v + "   "));

        System.out.println();

        // 可以看到,它并不会发生并发修改异常ConcurrentModificationException
        map.forEach((k, v) -> {
            if (k == Color.WHITE) {
                map.put(Color.GREEN, "绿色");
            }
            System.out.print(k + ":" + v + "   ");
        });

        // key不允许为null  java.lang.NullPointerException
        map.put(null,"未知");
    }
}

运行结果如下:

RED:红色   BLACK:黑色   WHITE:白色   
RED:红色   BLACK:黑色   WHITE:白色   GREEN:绿色   
红色
Exception in thread "main" java.lang.NullPointerException
	at java.util.EnumMap.typeCheck(EnumMap.java:743)
	at java.util.EnumMap.put(EnumMap.java:267)
	at java.util.EnumMap.put(EnumMap.java:79)
	at com.fsx.maintest.Main.main(Main.java:40)

从上示例对EnumMap总结出以下几点:

  1. EnumMap是有序的,这个顺序是按照你枚举类的定义顺序走的
  2. EnumMap可以一边遍历一边修改,不会抛ConcurrentModificationException异常
  3. EnumMap的key不允许为null
  4. EnumMap是线程不安全的(若需要安全可以使用Collections#synchronizedMap来一下子)
  5. EnumMap效率高,所有操作都是常量时间。(因为底层是数组)

从源码处稍微了解一下为何它的效率高?

// @since 1.5  继承自AbstractMap,所以它也能够当作Map来使用
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable {

	// 所有key都必须这个类型
    private final Class<K> keyType; 
    // 简单的说它把所有的key(枚举)都缓存起来  此处用的数组
    private transient K[] keyUniverse;

	// 所有的值  也是数组  它的length和keyUniverse是相同的
    private transient Object[] vals;

	// 它的get方法,直接根据ordinal()去数组找了  这就是它快最为核心的原因
    public V get(Object key) {
        return (isValidKey(key) ? unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
    }

}

它的get方法,直接使用的ordinal()去数组里拿值,那可不效率高吗。

最后介绍:EnumMap还有一种用法是被继承:

public class ColorMap extends EnumMap<Color, String> {
    public ColorMap() {
        super(Color.class);
    }
}

public class Main {

    public static void main(String[] args) {
        Map<Color, String> map = new ColorMap();
        map.put(Color.BLACK, "黑色");

        System.out.println(map.get(Color.BLACK)); //黑色
    }

}
EnumSet

EnumSet是一个专为枚举设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。 同样的我们使用它的原因是:效率高(EnumSetHashSet更快)。

使用Demo:

public class Main {

    public static void main(String[] args) {
        Set<Color> set = EnumSet.allOf(Color.class);
        System.out.println(set); //[RED, BLACK, WHITE, GREEN]

        set = EnumSet.noneOf(Color.class);
        System.out.println(set); //[]
        // 添加元素
        set.add(Color.RED);
        set.add(Color.BLACK);
        System.out.println(set); //[RED, BLACK]


        // 自己人工指定
        set = EnumSet.of(Color.WHITE, Color.BLACK, Color.WHITE);
        System.out.println(set); //[BLACK, WHITE]  请注意这个顺序并不是上面add的顺序  毕竟自己去重了


        // 根据区间来创建
        set = EnumSet.range(Color.BLACK, Color.GREEN);
        //set = EnumSet.range(Color.GREEN, Color.BLACK); // 这样抛出异常了, java.lang.IllegalArgumentException: GREEN > BLACK
        System.out.println(set); //[BLACK, WHITE, GREEN] 可以看到含头含尾的


        // 相当于取差集的意思~
        System.out.println(EnumSet.complementOf((EnumSet) set)); //[RED]


        // copyOf方法克隆一个(请注意是克隆,并不是返回的一个视图)
        System.out.println(EnumSet.copyOf((EnumSet) set)); //[BLACK, WHITE, GREEN]

    }

}

输出结果:

[RED, BLACK, WHITE, GREEN]
[]
[RED, BLACK]
[BLACK, WHITE]
[BLACK, WHITE, GREEN]
[RED]
[BLACK, WHITE, GREEN]

EnumSet是个抽象类,所以构建它只能通过static方法。它通过内建的实现类RegularEnumSet来保证高效率,下面可以简单看看它为何这么优秀的原因。 RegularEnumSet #add()方法源码如下:

// @since 1.5
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> implements Cloneable, java.io.Serializable { ... }

//@since 1.5  访问权限非public 属于内建类
class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
    public boolean add(E e) {
        typeCheck(e);
        long oldElements = elements;
        elements |= (1L << ((Enum<?>)e).ordinal());
        return elements != oldElements;
    }
}

这个实现非常非常非常的高大上:add方法只是对长整型数据element做了一个操作而已,也就是说EnumSet实际上将枚举值ordinal()保存在一个长整型long数据上,每个枚举值占用一bit

我们知道Long型是64bit,那枚举值数量超过64咋办呢? 其实这个时候JDK就会用EnumSet的另一个实现JumboEnumSet 实际情况是:试问一下,谁的枚举值能定义64个这么多???拉出去斩了???

另还注意这里指的是64bit,也就是说是8byte。(计算机中一个字节(byte)占8位(bit)

总而言之,EnumSet的实现是非常高级,效率也是非常的高的。

StringTokenizer和StringJoiner

StringTokenizer

Java中的StringTokenizer类用于将字符串分解为标记,效果类似split,但split方法@since 1.4,而StringTokenizer 1.0版本就有了。

使用Demo:

public class Main {

    public static void main(String[] args) {
		
		// 它只能靠构造函数把分隔符传进去
		// 若不指定分隔符,默认使用" \t\n\r\f"去分割
        StringTokenizer st1 = new StringTokenizer("Hello Geeks How are you", " ");
        while (st1.hasMoreTokens())
            System.out.println(st1.nextToken());

        StringTokenizer st2 = new StringTokenizer("JAVA : Code : String", " :");
        while (st2.hasMoreTokens())
            System.out.println(st2.nextToken());

        StringTokenizer st3 = new StringTokenizer("JAVA : Code : String", " :", true);
        while (st3.hasMoreTokens())
            System.out.println(st3.nextToken());
    }

}
StringJoiner:拯救字符串拼接

它是JDK8新增的一个工具类,位于java.util包下。那么为何JDK8要新增这样一个类呢?原因是之前的StringBuilder太死板了,不支持分隔

比如我们经常会有这样的需求:把一个字符串数组用,分隔开,但是最后一个不要逗号?

public class Main {

    public static void main(String[] args) {
        String[] strs = {"wo", "ai", "ni"};

        // 使用原始的StringBuider方式~
        StringBuilder sb = new StringBuilder();
        Arrays.stream(strs).forEach(s -> {
            sb.append(s);
            sb.append(",");
        });
        System.out.println(sb.substring(0, sb.lastIndexOf(","))); //wo,ai,ni

		// String内置的工具类方式  它底层还是使用的StringBuilder
        System.out.println(StringUtils.arrayToDelimitedString(strs, ",")); //wo,ai,ni

        //使用StringJoiner
        StringJoiner sj = new StringJoiner(",");
        Arrays.stream(strs).forEach(x -> sj.add(x));
        System.out.println(sj); //wo,ai,ni

    }

}

StringJoiner它主要就是掌握构造函数+add这个方法,其余方法不常用就不介绍了~

虽然它最终实现还是依赖StringBuilder,但是它的实现还是很高效的,特别是有一些小技巧比如对前缀、后缀的处理~

UUID和Base64

UUID

UUID (Universally Unique Identifier)缩写,即通用唯一识别码,也是被开源软件基金会 (Open Software Foundation, OSF) 的组织应用在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部分。

JDK5提供了UUID这个工具类,我们经常这么使用它:

public class Main {
    public static void main(String[] args){
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        System.out.println(uuid);
    }
}
Base64

在过去的很多年中,JDK都只有一个未公开的类(BASE64Encoder)。在Java 8中,Base64编码已经成为Java类库的标准内置了 Base64 编码的编码器和解码器

public class Main {

    public static void main(String[] args) throws UnsupportedEncodingException {

        // 编码
        String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
        System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);

        // 解码
        byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
        System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8")); //  runoob?java8  还原出来了
        System.out.println("-------------------------------");


        base64encodedString = Base64.getUrlEncoder().encodeToString("TutorialsPoint?java8".getBytes("utf-8"));
        System.out.println("Base64 编码字符串 (URL) :" + base64encodedString);

        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < 10; ++i) {
            stringBuilder.append(UUID.randomUUID().toString());
        }
        byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
        String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
        System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString);

    }

}

Random和ThreadLocalRandom

参考:【小家java】Java中Random ThreadLocalRandom 设置随机种子获取随机数精讲

OptionalInt和OptionalLong和OptionalDouble

一看就能明白,这三个Optional都特别的相似,只是把Optional的泛型换成了具体类型而已。也一样都位于java.util包内~

OptionalInt为例写个Demo:

public class Main {

    public static void main(String[] args) {

        //静态工厂方法获取一个示例
        OptionalInt op = OptionalInt.of(1);
        if (op.isPresent()) {
            //获得OptionalInt对象里面的值,输出1
            System.out.println(op.getAsInt());
        }
        op.ifPresent((value) -> System.out.println("value:" + value));


        //创建一个空值对象 ==========
        OptionalInt opint = OptionalInt.empty();
        if (opint.isPresent()) {
            //和Optional一样,输出No value present
            System.out.println(opint.getAsInt());
        } else {
            //如果没有值,赋初始值  这里,现在这里只能写int值
            System.out.println(opint.orElse(222));
            //如果没有值,赋初始函数
            System.out.println(opint.orElseGet(() -> 333));
        }
        //如果【没有值】则抛出异常
        opint.orElseThrow(NullPointerException::new);
    }

}

输出:

1
value:1
222
333
Exception in thread "main" java.lang.NullPointerException
	at java.util.OptionalInt.orElseThrow(OptionalInt.java:189)
	at com.fsx.maintest.Main.main(Main.java:35)

需要注意的一点是:上面这3个类并不支持ofNullable()filter()map()flatMap()等这几个Optional里有的方法。

总结

其实也没什么好总结,主要说一点吧:主动性的培养技术敏感度,处处留心皆学问。

The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏京程一灯

怎样成为全栈开发工程师[每日前端夜话0xAA]

在 LinkedIn 和 Facebook 上,有很多人将当前的工作标记为全栈工开发程师。在 Medium 上关于这个问题的文章也收到了很多读者的好评。一些人认...

7330
来自专栏牛客网

顺丰&百度 前端 一面 面经

2.数据库的选择,redis和其他数据库怎么同步(简历写了敲过java。。然后自己给自己挖了个坑。。。)

17120
来自专栏Java研发军团

Java性能优化的50个细节,我必须分享给你!

来源:blog.csdn.net/dongnan591172113/article/details/51790428

7520
来自专栏美团技术团队

美团大规模微服务通信框架及治理体系OCTO核心组件开源

微服务通信框架及治理平台OCTO作为美团基础架构设施的重要组成部分,目前已广泛应用于公司技术线,稳定承载上万应用、日均支撑千亿级的调用。业务基于OCTO提供的标...

15110
来自专栏技术从心

JAVAAPI中SortedMap解释

A Map进一步提供其键上的总排序 。地图根据其键的natural ordering或通过在分类地图创建时提供的Comparator进行排序。当迭代排序的地图的...

15720
来自专栏AI科技大本营的专栏

如何用Python编写一个Lisp解释器

这篇文章有两个目的:一是展示如何实现一个计算机语言的解释器,二是演示如何使用 Python 3 构造 Lisp 的一种方言 Schema,作者把自己的这个语言解...

13940
来自专栏女程序员的日常_Lin

Iterator 、Generator(一)

调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和don...

7930
来自专栏Java技术栈

Java 12 骚操作, String居然还能这样玩!

栈长之前在Java技术栈微信公众号分享过《Java 11 已发布,String 还能这样玩!》这篇文章,介绍了 Java 11 的 String 新玩法,让大家...

10330
来自专栏前端迷

高阶函数详解与实战训练

当大家看到这个面试题的时候,能否在第一时间想到使用高阶函数实现?想到在实际项目开发过程中,用到哪些高级函数?有没有想过自己创造一个高阶函数呢?开始本篇文章的学习

6310
来自专栏大数据实战演练

Elasticsearch BulkProcessor 的具体实现

本文示例使用的是 Spring Boot 框架,由于该框架有默认的 Elasticsearch 版本,为了避免版本混乱或冲突,我在 pom.xml 文件内添加了...

78630

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励