2024/05/27

Guava Reflection

TypeToken

因為 java 有 type erasure 特性,無法在 runtime 得知 generic Class 物件

TypeToken 使用了 workaround 方法,可處理 generict types,就是能夠在 runtime 檢測 Generic Type

    @Test
    public void typeToken() {
        ArrayList<String> stringList = Lists.newArrayList();
        ArrayList<Integer> intList = Lists.newArrayList();
        System.out.println(stringList.getClass().isAssignableFrom(intList.getClass()));
        // returns true, even though ArrayList<String> is not assignable from ArrayList<Integer>

        TypeToken<List<String>> stringListToken = new TypeToken<List<String>>() {};
        TypeToken<List<Integer>> integerListToken = new TypeToken<List<Integer>>() {};
        TypeToken<List<? extends Number>> numberTypeToken = new TypeToken<List<? extends Number>>() {};
        assertFalse(stringListToken.isSubtypeOf(integerListToken));
        assertFalse(numberTypeToken.isSubtypeOf(integerListToken));
        assertTrue(integerListToken.isSubtypeOf(numberTypeToken));
    }

利用 TypeToken 在 runtime 取得 complex type 資訊

    abstract class ParametrizedClass<T> {
        TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
    @Test public void typeToken2() throws NoSuchMethodException {
        //// 產生一個 class 裡面儲存了 TypeToken 欄位
        ParametrizedClass<String> parametrizedClass = new ParametrizedClass<String>() {};
        assertEquals(parametrizedClass.type, TypeToken.of(String.class));

        //// 也能產生 TypeToken of a complex type,裡面有多個 generic type
        // runtime 還是能取得 type 資訊
        TypeToken<Function<Integer, String>> funToken
                = new TypeToken<Function<Integer, String>>() {};
        TypeToken<?> funResultToken = funToken
                .resolveType(Function.class.getTypeParameters()[1]);
        assertEquals(funResultToken, TypeToken.of(String.class));

        /// 也能在 Map 裡面得知 entry 的 data type
        TypeToken<Map<String, Integer>> mapToken
                = new TypeToken<Map<String, Integer>>() {};
        TypeToken<?> entrySetToken = mapToken
                .resolveType(Map.class.getMethod("entrySet")
                        .getGenericReturnType());
        assertEquals(
                entrySetToken,
                new TypeToken<Set<Map.Entry<String, Integer>>>() {});
    }

Invokable

Invokable 是 java.lang.reflec.Method 及 java.lang.reflect.Constructor 的 fluent wrapper,提供簡化後的 API

    @Test
    public void invokable() throws NoSuchMethodException {
        Method method = CustomClass.class.getMethod("somePublicMethod");
        Invokable<CustomClass, ?> invokable
                = new TypeToken<CustomClass>() {}
                .method(method);
        // 分別透過 Guava 及 標準的 JAVA reflection API 檢測是否有 somePublicMethod 這個 method
        boolean isPublicStandradJava = Modifier.isPublic(method.getModifiers());
        boolean isPublicGuava = invokable.isPublic();

        assertTrue(isPublicStandradJava);
        assertTrue(isPublicGuava);

        ////////
        // checking if a method is overridable
        Method method2 = CustomClass.class.getMethod("notOverridablePublicMethod");
        // 用 Guava TypeToken 比較簡單
        Invokable<CustomClass, ?> invokable2
                = new TypeToken<CustomClass>() {}.method(method2);

        // 用標準 java 的方法
        boolean isOverridableStandardJava = (!(Modifier.isFinal(method2.getModifiers())
                || Modifier.isPrivate(method2.getModifiers())
                || Modifier.isStatic(method2.getModifiers())
                || Modifier.isFinal(method2.getDeclaringClass().getModifiers())));
        boolean isOverridableFinalGauava = invokable2.isOverridable();

        assertFalse(isOverridableStandardJava);
        assertFalse(isOverridableFinalGauava);
    }

References

Guide to Guava's Reflection Utilities | Baeldung

ReflectionExplained · google/guava Wiki · GitHub

2024/05/20

Guava Math

Guava 針對 int, long, BigIntegers, double 提供一些 Math Utility Class methods

IntMath

    @Test
    public void intMath() {
        // binomial(int n, int k)
        // binomial coefficient of n and k
        // 計算 n/k(n-k)
        int result = IntMath.binomial(6, 3);
        assertEquals(20, result);
        // 超過 Integer range 時,會回傳 MAX_VALUE
        result = IntMath.binomial(Integer.MAX_VALUE, 3);
        assertEquals(Integer.MAX_VALUE, result);

        //// ceilingPowerOfTwo(int x)
        // the smallest power of two which is greater than or equal to x
        // x 落在 2 的哪一個指數結果的區間
        // 2^(n-1) < x < 2 ^n
        result = IntMath.ceilingPowerOfTwo(20);
        assertEquals(32, result);

        //// checkedAdd(int a, int b)
        // a+b ,同時會檢查是否結果有 overflow,發生時會 throw ArithmeticException
        result = IntMath.checkedAdd(1, 2);
        assertEquals(3, result);

        Exception exception = assertThrows(ArithmeticException.class, () -> {
            IntMath.checkedAdd(Integer.MAX_VALUE, 100);
        });

        /// divide(int p, int q, RoundingMode mode)
        // 定義 rounding mode 的除法
        result = IntMath.divide(10, 3, RoundingMode.CEILING);
        assertEquals(4, result);
        Exception exception2 = assertThrows(ArithmeticException.class, () -> {
            // 未定義 Rounding Mode  但實際上結果需要 rounding
            IntMath.divide(10, 3, RoundingMode.UNNECESSARY);
        });

        /// factorial(int n)
        // n!   n 階層
        result = IntMath.factorial(5);
        assertEquals(120, result);
        result = IntMath.factorial(Integer.MAX_VALUE);
        assertEquals(Integer.MAX_VALUE, result);

        //// floorPowerOfTwo(int x)
        // 2^n < x < 2 ^(n+1)
        result = IntMath.floorPowerOfTwo(30);
        assertEquals(16, result);

        //// gcd(int a, int b)
        // 最大公因數
        result = IntMath.gcd(30, 40);
        assertEquals(10, result);

        //// isPowerOfTwo(int x)
        // 是否為 2 的倍數
        assertTrue( IntMath.isPowerOfTwo(16) );
        assertFalse( IntMath.isPowerOfTwo(20) );

        //// isPrime(int n)
        // 是否為質數
        assertFalse( IntMath.isPrime(20) );

        //// log10(int x, RoundingMode mode)
        // 以 10 為底 的對數
        result = IntMath.log10(30, RoundingMode.CEILING);
        assertEquals(2, result);
        Exception exception3 = assertThrows(ArithmeticException.class, () -> {
            // 未定義 Rounding Mode  但實際上結果需要 rounding
            IntMath.log10(30, RoundingMode.UNNECESSARY);
        });

        //// log2(int x, RoundingMode mode)
        // 以 2 為底 的對數
        result = IntMath.log2(30, RoundingMode.CEILING);
        assertEquals(5, result);
        Exception exception4 = assertThrows(ArithmeticException.class, () -> {
            // 未定義 Rounding Mode  但實際上結果需要 rounding
            IntMath.log2(30, RoundingMode.UNNECESSARY);
        });

        //// mean(int x, int y)
        //平均
        result = IntMath.mean(30, 20);
        assertEquals(25, result);

        // mod(int x, int m)
        // 餘數
        result = IntMath.mod(30, 4);
        assertEquals(2, result);

        // pow(int b, int k)
        // 指數
        result = IntMath.pow(6, 4);
        assertEquals(1296, result);

        //// saturatedAdd(int a, int b)
        // 控制 overflow/ underflow 時,會改回傳 MAX_VALUE/MIN_VALUE
        result = IntMath.saturatedAdd(6, 4);
        assertEquals(10, result);
        result = IntMath.saturatedAdd(Integer.MAX_VALUE, 1000);
        assertEquals(Integer.MAX_VALUE, result);

        //// sqrt(int x, RoundingMode mode)
        // 平方根
        result = IntMath.sqrt(30, RoundingMode.CEILING);
        assertEquals(6, result);
        Exception exception5 = assertThrows(ArithmeticException.class, () -> {
            // 未定義 Rounding Mode  但實際上結果需要 rounding
            IntMath.sqrt(30, RoundingMode.UNNECESSARY);
        });

    }

LongMath

大部分跟 IntMath 一樣,除了 mod

    @Test
    public void longMath() {
        // Most operations are similar to the IntMath utility
        // 以下是不同的地方

        /// mod(long x, int m) and mod(long x, long m)
        // x mod m
        int result = LongMath.mod(30L, 4);
        assertEquals(2, result);
        long result2 = LongMath.mod(30L, 4L);
        assertEquals(2L, result);
    }

BigIntegerMath

跟 IntMath 類似

DoubleMath

    @Test
    public void doubleMath() {
        //// isMathematicalInteger(double x)
        // 是否為整數
        boolean result = DoubleMath.isMathematicalInteger(5);
        assertTrue(result);
        result = DoubleMath.isMathematicalInteger(5.2);
        assertFalse(result);


        //// log2(double x)
        double result2 = DoubleMath.log2(4);
        assertEquals(2, result2, 0);
    }

References

MathExplained · google/guava Wiki · GitHub

Guide to Mathematical Utilities in Guava | Baeldung

2024/05/13

Guava Hashing

Guava Hash 裡面有幾個重要的類別:Hashing, HashFunction, Hasher, HashCode, Funnel, PrimitiveSink

HashFunction

HashFunction 是 stateless function,可將任意的 data 轉換為 fixed number of bits,相同的 input 會得到相同的 output 結果,不同的 input 得到不同的唯一的結果

可產生 Hasher 物件,也可以根據條件回傳 HashCode 結果

    @Test
    public void hashFunction() {
        HashFunction hf = Hashing.md5();
        HashCode hc = hf.newHasher()
                .putLong(10)
                .putString("abc", Charsets.UTF_8)
                .hash();
        System.out.println("hashFunction="+hc.toString());
    }

Hasher

HashFunction 可以使用 stateful Hasher,Hasher 提供 fluent syntax,可將資料填充到 Hash 裡面,並得到 hash value。Hasher 可接受 primitive input, byte array, slices of byte arrays, character sequences, 任意物件產生的 Funnel。

資料透過 putXXX() method 放到 Hasher 的資料源中,有 putByte, putBytes, putShort, putInt, putLong, putFloat, putdouble, putBoolean, putChar, putEnencodedChars, putString, putObject

Funnel

如果有一個類別

class Person {
  final int id;
  final String firstName;
  final String lastName;
  final int birthYear;
}

其 Funnel 會是

Funnel<Person> personFunnel = new Funnel<Person>() {
  @Override
  public void funnel(Person person, PrimitiveSink into) {
    into
        .putInt(person.id)
        .putString(person.firstName, Charsets.UTF_8)
        .putString(person.lastName, Charsets.UTF_8)
        .putInt(birthYear);
  }
};

組合起來的code

    @Test
    public void objectToHashCode() {

        // 需要hash的对象
        Person person = new Person(27, "John", "Wink", 1990);

        Funnel<Person> personFunnel = new Funnel<Person>() {
            @Override
            public void funnel(Person person, PrimitiveSink into) {
                into
                        .putInt(person.id)
                        .putString(person.firstName, Charsets.UTF_8)
                        .putString(person.lastName, Charsets.UTF_8)
                        .putInt(person.birthYear);
            }
        };

        // md5算法
        HashFunction hf = Hashing.md5();
        HashCode hc = hf.newHasher()
                .putString("abc", Charsets.UTF_8)
                .putObject(person, personFunnel)
                .hash();
        System.out.println("objectToHashCode="+hc.toString());
    }

    class Person {
        int id;
        String firstName;
        String lastName;
        int birthYear;

        public Person(int id, String firstName, String lastName, int birthYear) {
            this.id = id;
            this.firstName = firstName;
            this.lastName = lastName;
            this.birthYear = birthYear;
        }
    }

HashCode

當 Hasher 給了所有 input 資料,他的 hash() method 會產生 HashCode 結果

HashCode 支援 euqality testing 例如 asInt(), asLong, asBytes()

另外有 writeBytesTo(array, offset, maxLength) 可將 hash 的 maxLength bytes 寫入 array

BloomFilter

可偵測物件是否 "definitely" 不在 filter 裡面,或是有可能已經被加入 Bloom filter

對於 hash 來說,就是偵測是否 hash value 有碰撞,不同的 input 得到相同的結果

BloomFilter<Person> friends = BloomFilter.create(personFunnel, 500, 0.01);
for (Person friend : friendsList) {
  friends.put(friend);
}
// much later
if (friends.mightContain(someone)) {
  // the probability that someone reached this place if they aren't a friend is
  // 1% we might, for example, start asynchronously loading things for someone
  // while we do a more expensive exact check
}

Hashing

Hashing (Guava: Google Core Libraries for Java HEAD-jre-SNAPSHOT API)

裡面有 static method,可取得某些演算法的 HashFunction

  • adler32()

  • crc32(), crc32c()

  • farmHashFingerprint64()

  • fingerprint2011()

  • goodFastHash(int minimumBits)

  • hmacMd5

  • hmacSha1

  • hmacSha256

  • hmacSha512

  • md5()

  • murmur3_128(), murmur3_32()

  • sha1(), sha256(), sha384(), sha512()

  • sipHash24

    @Test
    public void hashing() {
        // 測試 Hasing 演算法
        String input = "hello, world";
        // MD5
        System.out.println("md5="+Hashing.md5().hashBytes(input.getBytes()).toString());
        // sha256
        System.out.println("sha256="+Hashing.sha256().hashBytes(input.getBytes()).toString());
        // sha512
        System.out.println("sha512="+Hashing.sha512().hashBytes(input.getBytes()).toString());
        // crc32
        System.out.println("crc32="+Hashing.crc32().hashBytes(input.getBytes()).toString());
        // MD5
        System.out.println("MD5="+Hashing.md5().hashUnencodedChars(input).toString());
    }

References

HashingExplained · google/guava Wiki · GitHub

2024/05/06

Guava String Utilities 2

CharMatcher

CharMatcher 用在字元的 trimming, collapsing, removing, retaining

    @Test
    public void char_matchers() {
        String string = "12 ab 34 CD\r\n 56 ef789GH";
        String noControl = CharMatcher.javaIsoControl().removeFrom(string); // remove control characters
        String theDigits = CharMatcher.digit().retainFrom(string); // only the digits
        String spaced = CharMatcher.whitespace().trimAndCollapseFrom(string, ' ');
        // trim whitespace at ends, and replace/collapse whitespace into single spaces
        String noDigits = CharMatcher.javaDigit().replaceFrom(string, "*"); // star out all digits
        String lowerAndDigit = CharMatcher.javaDigit().or(CharMatcher.javaLowerCase()).retainFrom(string);
        // eliminate all characters that aren't digits or lowercase

        assertEquals("12 ab 34 CD 56 ef789GH", noControl);
        assertEquals("123456789", theDigits);
        assertEquals("12 ab 34 CD 56 ef789GH", spaced);
        assertEquals("** ab ** CD\r\n ** ef***GH", noDigits);
        assertEquals("12ab3456ef789", lowerAndDigit);
    }

除了一些已經封裝的methods 以外,通用的 method 有三個

Method Description example
anyOf(CharSequence) 要符合的字元 CharMatcher.anyOf("aeiou")
is(char) 特定的 char
inRange(char, char) 一連串的字元 CharMatcher.inRange('a', 'z')
    @Test
    public void char_matchers2() {
        String string = "12 ab 34 CD\r\n 56";
        String anyOfResult = CharMatcher.anyOf("cdef\r\n").removeFrom(string);
        String isResult = CharMatcher.is('C').removeFrom(string);
        String inRangeResult = CharMatcher.inRange('A', 'Z').removeFrom(string);

        assertEquals("12 ab 34 CD 56", anyOfResult);
        assertEquals("12 ab 34 D\r\n 56", isResult);
        assertEquals("12 ab 34 \r\n 56", inRangeResult);
    }

使用 CharMatcher 的 methods

Method Description
collapseFrom(CharSequence, char) 將一連串的字元,縮小變成一個  ex: WHITESPACE.collapseFrom(string, ' ') 會減少為只有一個空白字元
matchesAllOf(CharSequence) 符合所有字元
removeFrom(CharSequence) 移除符合字元
retainFrom(CharSequence) 保留符合的字元
trimFrom(CharSequence) 去掉 leading, trailing 符合的字元
replaceFrom(CharSequence, CharSequence) 取代字元

Charsets

注意 "不要" 這樣寫

try {
  bytes = string.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
  // how can this possibly happen?
  throw new AssertionError(e);
}

要改用 Charsets

bytes = string.getBytes(Charsets.UTF_8);

CaseFormat

Format Example
LOWER_CAMEL lowerCamel
LOWER_HYPHEN lower-hyphen
LOWER_UNDERSCORE lower_underscore
UPPER_CAMEL UpperCamel
UPPER_UNDERSCORE UPPER_UNDERSCORE
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "CONSTANT_NAME"));
// returns "constantName"

References

StringsExplained · google/guava Wiki · GitHub