2024/03/18

Guava Collection 1 Immutable Collections

Immutable Collections

ref: ImmutableCollectionsExplained · google/guava Wiki · GitHub

collection 的資料不一定需要隨時可以修改,如果可以任意修改,反而在某些時候,會造成問題。

  1. 不能修改的 collection,將資料傳給 untrusted libraries 使用,也不會被 library 任意修改資料

  2. thread-safe,再多執行緒環境共用資料時,可避免同時修改發生問題

  3. 可節省 time, space

JDK 也有提供 Collections.unmodifiableXXX methods,但還是有可能有以下問題

  1. unwieldy and verbose: 使用時必須要在每一個地方,都先產生一份

  2. unsafe: 只有在沒有其他地方,有儲存原本 collection 的 reference 時,這個 collection 才會是 immutable

  3. inefficient: 資料結構還是跟原本 mutable collection 一樣,所以還是會有 concurrent modification check, 額外耗費的 space ...

    @Test
    public void jdk_unmodifiableMethod() {
        List list = new ArrayList();
        list.add("item1");
        list.add("item2");

        List unmodifiableList = Collections.unmodifiableList(list);

        Exception exception = assertThrows(UnsupportedOperationException.class, () -> {
            unmodifiableList.add("item3");;
        });

        assertEquals(exception.getClass().getName(), "java.lang.UnsupportedOperationException");
        String actualMessage = exception.getMessage();
        assertNull(actualMessage);

        // 如果修改原本的 list,還是會影響到 unmodifiableList
        list.add("item3");
        assertEquals(unmodifiableList.size(), 3);
    }

    @Test
    public void guava_immutable() {
        List<String> stringArrayList = Lists.newArrayList("item1","item2");
        ImmutableList<String> immutableList = ImmutableList.copyOf(stringArrayList);

        Exception exception = assertThrows(UnsupportedOperationException.class, () -> {
            immutableList.add("item3");
        });

        assertEquals(exception.getClass().getName(), "java.lang.UnsupportedOperationException");
        String actualMessage = exception.getMessage();
        assertNull(actualMessage);

        // 如果修改原本的 list,不會影響到 immutableList
        stringArrayList.add("item3");
        assertEquals(stringArrayList.size(), 3);
        assertEquals(immutableList.size(), 2);
    }

Guava immutable collection 不能使用 null values,因為大部分的 code,都是必須要有值

產生 ImmutableXXX collection 的方法:

  1. 使用 copyOf method ex: ImmutableSet.copyOf(set)

  2. 使用 of method ex: ImmutableSet.of("a", "b", "c") or ImmutableMap.of("a", 1, "b", 2)

  3. 使用 Builder

    @Test
    public void immutableCollection() {
        // copyOf
        List<String> stringArrayList = Lists.newArrayList("item1","item2");
        ImmutableList<String> immutableList = ImmutableList.copyOf(stringArrayList);

        // of
        ImmutableSet.of("a", "b", "c", "a", "d", "b");

        // Builder
        Color color1 = new Color(0, 0, 255);
        Color color2 = new Color(0, 255, 0);
        ImmutableSet<Color> colors = ImmutableSet.of(color1, color2);

        ImmutableSet<Color> newcolors =
                ImmutableSet.<Color>builder()
                        .addAll(colors)
                        .add(new Color(0, 191, 255))
                        .build();
    }

所有 immutable collections 都有透過 asList() 產生的 ImmutableList 的 view。

    @Test
    public void asList() {
        ImmutableSet<String> set = ImmutableSet.of("a", "b", "c", "a", "d", "b");

        String item0 = set.asList().get(0);
        assertEquals(item0, "a");
    }
Interface JDK or Guava? Immutable Version
Collection JDK ImmutableCollection
List JDK ImmutableList
Set JDK ImmutableSet
SortedSet/NavigableSet JDK ImmutableSortedSet
Map JDK ImmutableMap
SortedMap JDK ImmutableSortedMap
Multiset Guava ImmutableMultiset
SortedMultiset Guava ImmutableSortedMultiset
Multimap Guava ImmutableMultimap
ListMultimap Guava ImmutableListMultimap
SetMultimap Guava ImmutableSetMultimap
BiMap Guava ImmutableBiMap
ClassToInstanceMap Guava ImmutableClassToInstanceMap
Table Guava ImmutableTable

2024/03/11

Guava in Java - ObjectUtilities

基本工具

CommonObjectUtilities

equals

當使用 Object.equals 時,如果遇到某個物件為 null,就會發生問題

com.google.common.base.Objects.equal 可做 null Object 的比較

JDK 提供類似的 java.util.Objects.equals()

    @Test
    public void equals() {
        com.google.common.base.Objects.equal("a", "a"); // returns true
        com.google.common.base.Objects.equal(null, "a"); // returns false
        com.google.common.base.Objects.equal("a", null); // returns false
        com.google.common.base.Objects.equal(null, null); // returns true
        // JDK 提供類似的 java.util.Objects.equals()
        java.util.Objects.equals("a", "a"); // returns true
        java.util.Objects.equals(null, "a"); // returns false
        java.util.Objects.equals("a", null); // returns false
        java.util.Objects.equals(null, null); // returns true

        String a = null;
        String b = "b";
        Exception exception = assertThrows(NullPointerException.class, () -> {
            a.equals(b);
        });
        Exception exception2 = assertThrows(NullPointerException.class, () -> {
            b.equals(a);
        });
    }

hashCode

簡化 hashCode 的做法,可直接根據多個 fields 產生 hash

    @Test
    public void hashCodeTest() {
        InnerClass innerClass = new InnerClass();
        int hash1 = innerClass.hashCode();
        int hash2 = innerClass.hashCode2();
        assertEquals(hash1, hash2);;
    }

    public static class InnerClass {
        private String a="a";
        private String b="b";

        @Override
        public int hashCode() {
            return com.google.common.base.Objects.hashCode(a, b);
        }
        // JDK 有對應類似的 java.util.Objects.hash
        public int hashCode2() {
            return java.util.Objects.hash(a, b);
        }
    }

toString

利用 MoreObjects.toStringHelper() 簡化 toString

    @Test
    public void toStringTest() {
        InnerClass2 cls = new InnerClass2();
        String clsString = cls.toString();
//        System.out.printf("clsString=%s%n", clsString);
        assertEquals(clsString, "InnerClass2{a=a, b=2, x=1}");;
    }

    public static class InnerClass2 {
        private String a="a";
        private int b=2;

        @Override
        public String toString() {
            return com.google.common.base.MoreObjects.toStringHelper(this)
                    .add("a", a)
                    .add("b", b)
                    .add("x", 1)
                    .toString();
        }
    }

compare/compareTo

guava 提供 ComparisonChain,他是 fluent Comparator 可改善 compareTo 的寫法

    class Person implements Comparable<Person> {
        private String lastName;
        private String firstName;
        private int zipCode;

        public int compareTo(Person that) {
            return ComparisonChain.start()
                    .compare(this.firstName, that.firstName)
                    .compare(this.lastName, that.lastName)
                    .compare(this.zipCode, that.zipCode, Ordering.natural().nullsLast())
                    .result();
        }

        public int compareTo2(Person other) {
            int cmp = lastName.compareTo(other.lastName);
            if (cmp != 0) {
                return cmp;
            }
            cmp = firstName.compareTo(other.firstName);
            if (cmp != 0) {
                return cmp;
            }
            return Integer.compare(zipCode, other.zipCode);
        }
    }

Throwable

package guava.basic;

import java.util.List;

import com.google.common.base.Throwables;

public class ThrowableTest {
    public static void main(String[] args) {

        ThrowableTest tester = new ThrowableTest();

        try {
            System.out.println("invalidInputExceptionTest");
            tester.invalidInputExceptionTest();
        } catch (InvalidInputException e) {
            //get the root cause
            System.out.println("invalidInputExceptionTest getRootCause");
            System.out.println(Throwables.getRootCause(e));

        } catch (Exception e) {
            //get the stack trace in string format
            System.out.println("invalidInputExceptionTest getStackTraceAsString");
            System.out.println(Throwables.getStackTraceAsString(e));
        }

        System.out.println("");
        try {
            System.out.println("indexOutOfBoundsExceptionTest");
            tester.indexOutOfBoundsExceptionTest();
        } catch (Exception e) {
            System.out.println("indexOutOfBoundsExceptionTest getStackTraceAsString");
            List<Throwable> elist = Throwables.getCausalChain(e);
            for( Throwable t1: elist ) {
                System.out.println(Throwables.getStackTraceAsString(t1));
            }
//            System.out.println(Throwables.getStackTraceAsString(e));
        }
    }

    public void invalidInputExceptionTest() throws InvalidInputException {
        try {
            sqrt(-1.0);
        } catch (Throwable e) {
            //check the type of exception and throw it
            Throwables.propagateIfInstanceOf(e, InvalidInputException.class);
            // Throws throwable as-is only if it is a RuntimeException or an Error.
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }

    public void indexOutOfBoundsExceptionTest() {
        try {
            int[] data = {1, 2, 3};
            getValue(data, 4);
        } catch (Throwable e) {
            Throwables.propagateIfInstanceOf(e, IndexOutOfBoundsException.class);
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }

    public double sqrt(double input) throws InvalidInputException {
        if (input < 0) throw new InvalidInputException();
        return Math.sqrt(input);
    }

    public double getValue(int[] list, int index) throws IndexOutOfBoundsException {
        return list[index];
    }
}

class InvalidInputException extends Exception {
}

執行結果

invalidInputExceptionTest
invalidInputExceptionTest getRootCause
com.maxkit.guava.InvalidInputException

indexOutOfBoundsExceptionTest
indexOutOfBoundsExceptionTest getStackTraceAsString
java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 3
    at com.maxkit.guava.ThrowableTest.getValue(ThrowableTest.java:71)
    at com.maxkit.guava.ThrowableTest.indexOutOfBoundsExceptionTest(ThrowableTest.java:57)
    at com.maxkit.guava.ThrowableTest.main(ThrowableTest.java:31)

References

Home · google/guava Wiki · GitHub

Guava Guide | Baeldung

Guava:Google开源的Java工具库,太强大了 | Java程序员进阶之路

Google Guava 工具類 的介紹和使用 - HackMD

# Google Guava官方教程

專欄文章:Guava 教學

2024/03/04

Guava Basic Preconditions

Guava 是 Google 開發的 Java 開源工具 Library。內容主要有兩個部分:擴充 Java Collection Framework,提供更好用的 function,例如cache, range,以及 hash function

使用 Guava 只需要在 maven pom.xml 直接加入 dependency 即可

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>32.1.2-jre</version>
  <!-- or, for Android: -->
  <!-- <version>32.1.2-android</version> -->
</dependency>

基本工具

Null Check

JDK 跟 Guava 都有針對 null check 問題提供了 Optional 類別來解決

Optional,Gauva工具及和Java8中实现的区别_Tonels的博客-CSDN博客

java.util.Optional 是 final 的類別,無法被繼承

com.google.common.base.Optional 是 abstract class,有實作 Serializable

兩種 Optional 基本的使用方式差不多

        com.google.common.base.Optional<Integer> possible = com.google.common.base.Optional.of(5);
        boolean isPresent = possible.isPresent(); // returns true
//        int val = possible.get(); // returns 5
        int val = possible.or(-1); // returns 5
        System.out.printf("isPresent=%b, val=%d%n", isPresent, val);

        com.google.common.base.Optional<Integer> possible2 = com.google.common.base.Optional.fromNullable(null);
        isPresent = possible2.isPresent();
        val = possible2.or(-1);
        System.out.printf("isPresent=%b, val=%d%n", isPresent, val);

        java.util.Optional<Integer> opt = java.util.Optional.of(5);
        boolean isPresent2 = opt.isPresent();
//        int val2 = opt.get();
        int val2 = opt.orElse(-1);
        System.out.printf("isPresent2=%b, val2=%d%n", isPresent2, val2);

        java.util.Optional<Integer> opt2 = java.util.Optional.ofNullable(null);
        isPresent2 = opt2.isPresent();
        val2 = opt2.orElse(-1);
        System.out.printf("isPresent2=%b, val2=%d%n", isPresent2, val2);

Preconditions

有很多 static method 可檢查 method 或 constructor 是否有正確的參數數值,如果檢查結果失敗,就會 throw exception

每一種 Preconditions 的 static method 都有三種變化

  1. No arguments,exception 裡面沒有 error message

  2. 有一個 Object argument,作為 error message,丟出的 exception 裡面會有 error message

  3. 有一個 String argument,搭配任意數量的 Object arguments,作為 error message 的 placeholder,類似 printf

Preconditions 的 checkArgument 可檢查參數的正確性,失敗時會丟出 IllegalArgumentException

沒有 error messge

    @Test
    public void checkArgument_without_error_message() {
        int age = -18;

        Exception exception = assertThrows(IllegalArgumentException.class, () -> {
            Preconditions.checkArgument(age > 0);
        });

//        String expectedMessage = null;
        String actualMessage = exception.getMessage();
        assertNull(actualMessage);
    }

有 error message

    @Test
    public void checkArgument_with_error_message() {
        int age = -18;
        String message = "Age can't be zero or less than zero.";

        Exception exception = assertThrows(IllegalArgumentException.class, () -> {
            Preconditions.checkArgument(age > 0, message);
        });

        String expectedMessage = message;
        String actualMessage = exception.getMessage();
        assertEquals(expectedMessage, actualMessage);
    }

有 error message template

    @Test
    public void checkArgument_with_template_error_message() {
        int age = -18;
        String message = "Age should be positive number, you supplied %s.";

        Exception exception = assertThrows(IllegalArgumentException.class, () -> {
            Preconditions.checkArgument(age > 0, message, age);
        });

        String expectedMessage = String.format(message, age);
        String actualMessage = exception.getMessage();
        assertEquals(expectedMessage, actualMessage);;
    }

checkElementIndex

    @Test
    public void checkElementIndex() {
        int[] numbers = { 1, 2, 3, 4, 5 };
        String message = "Please check the bound of an array and retry";

        Exception exception = assertThrows(IndexOutOfBoundsException.class, () -> {
            Preconditions.checkElementIndex(6, numbers.length - 1, message);
        });

//        expectedMessage: Please check the bound of an array and retry (6) must be less than size (4)
        String expectedMessage = String.format(message+" (%d) must be less than size (%d)", 6, numbers.length - 1);
//        System.out.printf("expectedMessage=%s%n", expectedMessage);
        String actualMessage = exception.getMessage();
        assertEquals(expectedMessage, actualMessage);;
    }

checkNotNull

    @Test
    public void checkNotNull () {
        String nullObject = null;
        String message = "Please check the Object supplied, its null!";

        Exception exception = assertThrows(NullPointerException.class, () -> {
            Preconditions.checkNotNull(nullObject, message);
        });

        String expectedMessage = message;
//        System.out.printf("expectedMessage=%s%n", expectedMessage);
        String actualMessage = exception.getMessage();
        assertEquals(expectedMessage, actualMessage);;
    }

checkPositionIndex

    @Test
    public void checkPositionIndex() {
        int[] numbers = { 1, 2, 3, 4, 5 };
        String message = "Please check the bound of an array and retry";

        Exception exception = assertThrows(IndexOutOfBoundsException.class, () -> {
            Preconditions.checkPositionIndex(6, numbers.length - 1, message);
        });

//        expectedMessage: Please check the bound of an array and retry (6) must not be greater than size (4)
        String expectedMessage = String.format(message+" (%d) must not be greater than size (%d)", 6, numbers.length - 1);
//        System.out.printf("expectedMessage=%s%n", expectedMessage);
        String actualMessage = exception.getMessage();
        assertEquals(expectedMessage, actualMessage);;
    }

checkState

    @Test
    public void checkState() {
        int[] validStates = { -1, 0, 1 };
        int givenState = 10;
        String message = "You have entered an invalid state";

        Exception exception = assertThrows(IllegalStateException.class, () -> {
            Preconditions.checkState(
                    Arrays.binarySearch(validStates, givenState) > 0, message);
        });

        String expectedMessage = message;
        String actualMessage = exception.getMessage();
        assertEquals(expectedMessage, actualMessage);;
    }

References

Home · google/guava Wiki · GitHub

Guava Guide | Baeldung

Guava:Google开源的Java工具库,太强大了 | Java程序员进阶之路

Google Guava 工具類 的介紹和使用 - HackMD

# Google Guava官方教程

專欄文章:Guava 教學