【Java并发编程】线程不安全类 (含解决)

线程不安全类

这里主要展示三种常见的类型

一、StringBuilder (StringBuffer)

在说Stringbuilder和StringBuffer之前,先说一下String。

String是超级重要的一个类,提供了构造和管理字符串的各种基本逻辑,它是不可变的Java类,声明为fnal class,所有的属性也都是final的。

由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的String对象。

由于String在Java世界中使用过于频繁,Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池。其运行机制是:创建一个字符串时,首先检查池中是否有值相同的字符串对象,如果有则不需要创建直接从池中刚查找到的对象引用;如果没有则新建字符串对象,返回对象引用,并且将新创建的对象放入池中。

但是,通过new方法创建的String对象是不检查字符串池的,而是直接在堆区或栈区创建一个新的对象,也不会把对象放入池中。上述原则只适用于通过直接量给String对象引用赋值的情况。

以拼接为例:

在 JDK 8 中,字符串拼接操作会自动被 javac 转换为 StringBuilder 操作,而在 JDK 9 里面则是因为 Java 9 为了更加统一字符串操作优化,提供了 StringConcatFactory,作为一个统一的入口。

javac 自动生成的代码,虽然未必是最优化的,但普通场景也足够了。

在 Java 9 中,我们引入了 Compact Strings 的设计,对字符串进行了大幅度的改进。将数据存储方式从 char 数组,改变为一个 byte 数组加上一个标识编码的所谓 coder,并且将相关字符串操作类都进行了修改。

byte数组相比于char数组更加紧凑,即更小的内存占用、更快的操作速度。


下面的讨论以Java8为主

StringBuffer是上面提到的为解决上面提到拼接产生太多中间对象的问题而提供的一个类,我们可以用append或者add方法,把字符串添加到已有序列的末尾或者指定位置。StringBuffer本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销,所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是StringBuilder。

StringBuilder是Java 1.5中新增的,在能力上和StringBufer没有本质区别,但是它去掉了线程安全的部分,有效减小了开销,是绝大部分情况下进行字符串拼接的首选。

StringBufer和StringBuilder都实现了AbstractStringBuilder抽象类,拥有几乎一致对外提供的调用接口;其底层在内存中的存储方式与String相同,都是以一个有序的字符序列(char类型的数组)进行存储,不同点是StringBufer/StringBuilder对象的值是可以改变的,并且值改变以后,对象引用不会发生改变;两者对象在构造过程中,首先按照默认大小申请一个字符数组,由于会不断加入新数据,当超过默认大小后,会创建一个更大的数组,并将原先的数组内容复制过来,再丢弃旧的数组。

因此,对于较大对象的扩容会涉及大量的内存复制操作,如果能够预先评估大小,可提升性能。

唯一需要注意的是:StringBuffer是线程安全的,但是StringBuilder是线程不安全的。可参看Java标准类库的源代码,StringBuffer类中方法定义前面都会有synchronize关键字。为此,StringBufer的性能要远低于StringBuilder。

首先演示String Builder在并发下的表现:

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class StringExample {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static StringBuilder stringBuilder = new StringBuilder();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", stringBuilder.length());
    }

    private static void update() {
        stringBuilder.append("6");
    }
}

在这里插入图片描述


接着演示StringBuffer在并发下的表现


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class StringExample {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static StringBuffer stringBuffer = new StringBuffer();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", stringBuffer.length());
    }

    private static void update() {
        stringBuffer.append("1");
    }
}

在这里插入图片描述


二、SimpleDateFormat(Java8 新时间与日期 API)

Java8之前的版本,格式化日期常使用SimpleDateFormat,但这个类并不是线程安全的,通常要用作局部变量或者使用ThreadLocal包装, 或者使用额外的joda time 依赖来对日期进行操作。

我们看看它在并发环境下的表现:

import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j

public class DateFormatExample {

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
    }

    private static void update() {
        try {
            simpleDateFormat.parse("20191029");
        } catch (Exception e) {
            log.error("parse exception", e);
        }
    }
}

会出现很多的异常,原因在于不是线程安全的类

在这里插入图片描述
在Java8之前的解决这种问题的方案还是值得借鉴的,我们看一下:

改进

import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class DateFormatExample2 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
    }

    private static void update() {
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
            simpleDateFormat.parse("20192029");
        } catch (Exception e) {
            log.error("parse exception", e);
        }
    }
}

每次声明一个新的变量来使用
这个时候就没问题了
在这里插入图片描述


使用额外的joda time 依赖来对日期进行操作

引一个依赖

     <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.2</version>
        </dependency>
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class DateFormatExample {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd");

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
    }

    private static void update(int i) {
        log.info("{}, {}", i, DateTime.parse("20180208", dateTimeFormatter).toDate());
    }
}

在这里插入图片描述


到了Java8,这类烦恼都没有了,全新的日期api可以快速实现日期格式化。通过这个楔子,我们好好说一下。

接触过Date(JDK1.0),应该都清楚,好多的方法都过时,废弃了,而且不易于使用。

在JDK1.1新增了Calendar,但是方法的使用上也有很多迷惑人的地方,比如说减去10天,也要使用add方法.

需要关注的是Date和Calendar是可变的。因而导致TimeZone 和 SimpleDateFormat都不是线程安全的

TimeZone 表示时区偏移量,也可以计算夏令时。在操作 Date, Calendar等表示日期/时间的对象时,经常会用到TimeZone;因为不同的时区,时间不同。

Java8出了一套全新的时间与日期 API,在time包下
在这里插入图片描述
它们都是不可变的,能够很容易解决线程的安全问题

我们看java.time包下的几个方法
在这里插入图片描述
LocalDate表示日期。
在这里插入图片描述
LocalDateTime表示时期和时间
在这里插入图片描述
LocalTime表示时间

LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

每个类的第一道红线为显示的格式,这是我们非常熟悉的时间表示。

ISO-8601 是一套世界的时间统一标准

计算机可读的时间为时间戳,用Instant表示。

Java8还提供了两个时间间隔相关的类Duration与Period
在这里插入图片描述
在这里插入图片描述

还有做偏移量运算的,比如加减几个小时的OffsetTime
在这里插入图片描述
还有时期运算的等等等

java.time.chrono包里面放着与特殊的时间相关的类,这是考虑到不同的国家和地区,比如暂时未回归的台湾省,目前的时间格式为中华民国XX年啥的。

java.time.format包内存放着格式时间相关的类

java.time.temporal包内存放着时间矫正器相关的类

java.time.zone包内存放着时区时间处理相关的类

我们看一下,利用Java8新时间API是如何解决这个问题的:

  /**
   * 不带时分秒的日期字符串转化
   *
   * @param input 输入的日期
   * @param inputFormat 输入日期的格式
   * @param outputFormat 输出日期的格式
   * @return 输出的日期,不带时分秒
   */
  public static String formattedDate(String input, String inputFormat, String outputFormat) {
    DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern(inputFormat);
    LocalDate localDate = LocalDate.parse(input, inputFormatter);
 
    DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(outputFormat);
    return localDate.format(outputFormatter);
  }

测试

    String output1 = formattedDate("2019年10月29日", "yyyy年MM月dd日", "yyyy-MM-dd");
    String output2 = formattedDate("2019/10/29", "yyyy/MM/dd", "yyyy-MM-dd");
    String output3 = formattedDate("2019-10-29", "yyyy-MM-dd", "yyyy/MM/dd");

输出结果

2019-10-29
2019-10-29
2019/10/29

使用LocalDate + DateTimeFormatter仅仅能够转换不带时分秒的日期格式

日期格式化-带时分秒的日期类型的字符串转换成其他形式

  /**
   * 带时分秒的日期字符串转换
   *
   * @param input 输入的日期
   * @param inputFormat 输入日期的格式
   * @param outputFormat 输出日期的格式
   * @return 输出指定格式的日期,可以带时分秒,也可以不带
   */
  public static String formattedDateTime(String input, String inputFormat, String outputFormat) {
    DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern(inputFormat);
    LocalDateTime localDateTime = LocalDateTime.parse(input, inputFormatter);
 
    DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(outputFormat);
    return localDateTime.format(outputFormatter);
  }

测试

String output1 = formattedDateTime("20191029000000", "yyyyMMddHHmmss", "yyyy-MM-dd");
    String output2 = formattedDateTime("201910290000000", "yyyyMMddHHmmss", "yyyy-MM-dd HH:mm:ss");
 
    String output3 = formattedDateTime("2019年10月29日00时00分00秒", "yyyy年MM月dd日HH时mm分ss秒",
        "yyyy/MM/dd");
    String output4 = formattedDateTime("2019年10月29日00时00分00秒", "yyyy年MM月dd日HH时mm分ss秒",
        "yyyyMMddHHmmss");
    String output5 = formattedDateTime("2019-10-29 00:00:00", "yyyy-MM-dd HH:mm:ss", "yyyyMMdd");
    String output6 = formattedDateTime("2019-10-29 00:00:00", "yyyy-MM-dd HH:mm:ss",
        "yyyy年MM月dd日HH时mm分ss");

结果不多说了吧

使用LocalDateTime + DateTimeFormatter 可以将指定日期类型转换成任意形式的日期类型。

.Date和LocalDateTime互转:

  /**
   * LocalDateTime -> Date
   */
  public static Date toDate(LocalDateTime localDateTime) {
    ZoneId zoneId = ZoneId.systemDefault();
    ZonedDateTime zdt = localDateTime.atZone(zoneId);
    return Date.from(zdt.toInstant());
  }
 
  /**
   * Date -> LocalDateTime
   */
  public static LocalDateTime toLocalDateTime(Date date) {
    Instant instant = date.toInstant();
    ZoneId zoneId = ZoneId.systemDefault();
    return instant.atZone(zoneId).toLocalDateTime();
  }

这些日期的转换应用很简单,从网上直接找来的,自己写有点费劲呀,来源:传送门


三、ArrayList, HashSet, HashMap等 (Collections)

在集合框架中,有些类是线程安全的,这些都是jdk1.1中的出现的。在jdk1.2之后,就出现许许多多非线程安全的类。

线程安全的同步的类有:

  1. vector:相比ArrayList多了个同步化机制。因为效率较低,现在已经不太建议使用。

  2. statck:堆栈类,先进后出

  3. hashtable:就比hashmap多了个线程安全

  4. enumeration:枚举,相当于迭代器

线程安全的类其方法是同步的,每次只能一个访问。是重量级对象,效率较低。

除了这些之外,其他的都是非线程安全的类和接口。比如ArrayList, HashSet, HashMap等

以不安全的集合为例,我们看看它们在并发条件下的表现:

ArrayList

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class ArrayListExample {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    private static List<Integer> list = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", list.size());
    }

    private static void update(int i) {
        list.add(i);
    }
}

在这里插入图片描述
HashSet

import lombok.extern.slf4j.Slf4j;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class HashSetExample {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    private static Set<Integer> set = new HashSet<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", set.size());
    }

    private static void update(int i) {
        set.add(i);
    }
}

在这里插入图片描述

HashMapExample

import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class HashMapExample {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    private static Map<Integer, Integer> map = new HashMap<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", map.size());
    }

    private static void update(int i) {
        map.put(i, i);
    }
}

在这里插入图片描述

具体解决的话,可以使用Collections工具类。

Collections工具类中提供了多个synchronizedXxx方法,该方法返回指定集合对象对应的同步对象,从而解决多线程并发访问集合时线程的安全问题。HashSet、ArrayList、HashMap等都是线程不安全的,如果需要考虑同步,则使用这些方法。这些方法主要有:synchronizedSet、synchronizedSortedSet、synchronizedList、synchronizedMap、synchronizedSortedMap。

使用的话,大概是这种:

Map m = Collections.synchronizedMap(new HashMap());
      ...
  Set s = m.keySet();  // Needn't be in synchronized block
      ...
  synchronized (m) {  // Synchronizing on m, not s!
      Iterator i = s.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }

特别需要指出的是,在用Iterator遍历器进行遍历的过程中删除或者添加集合中元素会抛异常。

原因是因为迭代器在调用next方法时,会检查集合是否被修改过,如果被修改过,就会抛出ConcurrentModificationException异常。

以ArrayList为例

public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

在执行next()方法时,会先执行checkForComodification()方法,做一个判断(检查所迭代的列表对象是否被修改过):

final void checkForComodification() {
     if (modCount != expectedModCount) throw new ConcurrentModificationException();
}

既然说到了Collections工具类,就扯点别的吧。

Collections工具类提供了大量针对Collection/Map的操作,都为静态(static)方法,上面那一类已经说过了,下面还有积几类

  1. 排序操作(主要针对List接口相关)
  • reverse(List list):反转指定List集合中元素的顺序
  • shuffle(List list):对List中的元素进行随机排序(洗牌)
  • sort(List list):对List里的元素根据自然升序排序
  • sort(List list, Comparator c):自定义比较器进行排序
  • swap(List list, int i, int j):将指定List集合中i处元素和j出元素进行交换
  • rotate(List list, int distance):将所有元素向右移位指定长度,如果distance等于size那么结果不变

演示

public void testSort() {
        System.out.println("原始顺序:" + list);
        
        Collections.reverse(list);
        System.out.println("reverse后顺序:" + list);

        Collections.shuffle(list);
        System.out.println("shuffle后顺序:" + list);
        
        Collections.swap(list, 1, 3);
        System.out.println("swap后顺序:" + list);

        Collections.sort(list);
        System.out.println("sort后顺序:" + list);

        Collections.rotate(list, 1);
        System.out.println("rotate后顺序:" + list);
    }

原始顺序:[b张三, d孙六, a李四, e钱七, c赵五]
reverse后顺序:[c赵五, e钱七, a李四, d孙六, b张三]
shuffle后顺序:[b张三, c赵五, d孙六, e钱七, a李四]
swap后顺序:[b张三, e钱七, d孙六, c赵五, a李四]
sort后顺序:[a李四, b张三, c赵五, d孙六, e钱七]
rotate后顺序:[e钱七, a李四, b张三, c赵五, d孙六]

  1. 查找和替换(主要针对Collection接口相关)
  • binarySearch(List list, Object key):使用二分搜索法,以获得指定对象在List中的索引,前提是集合已经排序
  • max(Collection coll):返回最大元素
  • max(Collection coll, Comparator comp):根据自定义比较器,返回最大元素
  • min(Collection coll):返回最小元素
  • min(Collection coll, Comparator comp):根据自定义比较器,返回最小元素
  • fill(List list, Object obj):使用指定对象填充
  • frequency(Collection Object o):返回指定集合中指定对象出现的次数
  • replaceAll(List list, Object old, Object new):替换
public void testSearch() {
        System.out.println("给定的list:" + list);
        System.out.println("max:" + Collections.max(list));
        System.out.println("min:" + Collections.min(list));
        System.out.println("frequency:" + Collections.frequency(list, "a李四"));
        Collections.replaceAll(list, "a李四", "aa李四");
        System.out.println("replaceAll之后:" + list);
        
        // 如果binarySearch的对象没有排序的话,搜索结果是不确定的
        System.out.println("binarySearch在sort之前:" + Collections.binarySearch(list, "c赵五"));
        Collections.sort(list);
        // sort之后,结果出来了
        System.out.println("binarySearch在sort之后:" + Collections.binarySearch(list, "c赵五"));

        Collections.fill(list, "A");
        System.out.println("fill:" + list);
    }

输出
给定的list:[b张三, d孙六, a李四, e钱七, c赵五]
max:e钱七
min:a李四
frequency:1
replaceAll之后:[b张三, d孙六, aa李四, e钱七, c赵五]
binarySearch在sort之前:-4
binarySearch在sort之后:2
fill:[A, A, A, A, A]

  1. 设置不可变集合
    Collections有三类方法可返回一个不可变集合:
  • emptyXxx():返回一个空的不可变的集合对象
  • singletonXxx():返回一个只包含指定对象的,不可变的集合对象。
  • unmodifiableXxx():返回指定集合对象的不可变视图
public void testUnmodifiable() {
        System.out.println("给定的list:" + list);
        List<String> unmodList = Collections.unmodifiableList(list);
        
        unmodList.add("再加个试试!"); // 抛出:java.lang.UnsupportedOperationException
        
        // 这一行不会执行了
        System.out.println("新的unmodList:" + unmodList);
    }
  1. 其它
  • disjoint(Collection<?> c1, Collection<?> c2) - 如果两个指定 collection 中没有相同的元素,则返回 true。
  • addAll(Collection<? super T> c, T… a) - 一种方便的方式,将所有指定元素添加到指定 collection 中。示范:
    Collections.addAll(flavors, “Peaches 'n Plutonium”, “Rocky Racoon”);
  • Comparator reverseOrder(Comparator cmp) - 返回一个比较器,它强行反转指定比较器的顺序。如果指定比较器为 null,则此方法等同于 reverseOrder()(换句话说,它返回一个比较器,该比较器将强行反转实现 Comparable 接口那些对象 collection 上的自然顺序)。
public void testOther() {
        List<String> list1 = new ArrayList<String>();
        List<String> list2 = new ArrayList<String>();
        
        // addAll增加变长参数
        Collections.addAll(list1, "大家好", "你好","我也好");
        Collections.addAll(list2, "大家好", "a李四","我也好");
        
        // disjoint检查两个Collection是否的交集
        boolean b1 = Collections.disjoint(list, list1);
        boolean b2 = Collections.disjoint(list, list2);
        System.out.println(b1 + "\t" + b2);
        
        // 利用reverseOrder倒序
        Collections.sort(list1, Collections.reverseOrder());
        System.out.println(list1);
    }

输出

true false
[我也好, 大家好, 你好]

最后一部分内容摘自他人博客,这里表示感谢,源地址传送门

发布了127 篇原创文章 · 获赞 3078 · 访问量 36万+

猜你喜欢

转载自blog.csdn.net/qq_42322103/article/details/102804575