Java工具类之Guava库


目录:

  1. Guava简介
  2. Maven依赖
  3. Guava的几个类使用
  4. Guava Cache的使用

参考:

Guava简介

 Guava 是一个 Google 开发的 基于 Java 的类库集合的扩展项目,包括集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 、I/O 和验证的实用方法等等。这些高质量的 API 可以使你的JAVA代码更加优雅,更加简洁,让你工作更加轻松愉悦。下面我们就简单的介绍一下Guava的大致的功能!

优点

  • 标准化 - Guava 库是由谷歌托管。
  • 高效 - 可靠,快速和有效的扩展 JAVA 标准库。
  • 优化 -Guava 库经过高度的优化。

Maven依赖

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

Guava的几个类使用

有很多类,我看了资料有很多的Java封装类,这里主要记录2个和参数校验有关的类和集合工具类 Collections。

Optional

Optional是一个不可变的对象,用于包含一个非空对象。可选对象被用来表示没有数值的空值。这个类有各种实用方法,以方便代码处理可用或不可用的值,而不是检查空值。

序号 函数和描述
1 static <T> Optional<T> absent() 返回一个没有包含引用的Optional实例.
2 abstract Set<T> asSet() 返回一个不可变的单子集合,如果该集合存在,其唯一的元素就是所包含的实例;否则就是一个空的不可变的集合.
3 abstract boolean equals(Object object) 如果对象是一个Optional实例,并且所包含的引用彼此相等或两者都不存在,则返回true.
4 static <T> Optional<T> fromNullable(T nullableReference) 如果nullableReference不是空的,返回一个包含该引用的Optional实例;否则返回absent().
5 abstract T get() 返回所包含的实例,该实例必须存在.
6 abstract int hashCode() 返回该实例的哈希代码.
7 abstract boolean isPresent() 如果这个持有人包含一个(非空的)实例,则返回真.
8 static <T> Optional<T> of(T reference) 返回一个包含给定非空引用的Optional实例.
9 abstract Optional<T> or(Optional<? extends T> secondChoice) 如果有一个值存在,返回这个Optional;否则返回secondChoice.
10 abstract T or(Supplier<? extends T> supplier) 如果包含的实例存在,则返回该实例;否则返回supplier.get().
11 abstract T or(T defaultValue) 如果所包含的实例存在,则返回该实例;否则返回默认值.
12 abstract T orNull() 如果包含的实例存在,则返回该实例;否则为空.
13 static <T> Iterable<T> presentInstances(Iterable<? extends Optional<? extends T>> optionals) 从提供的选项中依次返回每个present实例的值,跳过absence()的出现次数.
14 abstract String toString() 返回这个实例的字符串表示.
15 abstract <V> Optional<V> transform(Function<? super T,V> function) 如果实例存在,则用给定的Function对其进行转换;否则,返回absent().

demo:

public class GuavaTester {
   public static void main(String args[]) {
      GuavaTester guavaTester = new GuavaTester();

      Integer value1 =  null;
      Integer value2 =  new Integer(10);

      //Optional.fromNullable - allows passed parameter to be null.
      Optional<Integer> a = Optional.fromNullable(value1);

      //Optional.of - throws NullPointerException if passed parameter is null
      Optional<Integer> b = Optional.of(value2);        

      System.out.println(guavaTester.sum(a,b));
   }

   public Integer sum(Optional<Integer> a, Optional<Integer> b) {
      //Optional.isPresent - checks the value is present or not
      System.out.println("First parameter is present: " + a.isPresent());

      System.out.println("Second parameter is present: " + b.isPresent());

      //Optional.or - returns the value if present otherwise returns
      //the default value passed.
      Integer value1 = a.or(new Integer(0));    

      //Optional.get - gets the value, value should be present
      Integer value2 = b.get();

      return value1 + value2;
   }    
}

Preconditions

Preconditions提供了静态方法来检查一个方法或构造函数是否被适当的参数所调用。它检查预设条件。它的方法在失败时抛出IllegalArgumentException.

序号 函数和描述
1 static void checkArgument(boolean expression) 确保涉及一个或多个参数的表达式对调用方法的真实性.
2 static void checkArgument(boolean expression, Object errorMessage) 确保涉及一个或多个参数的表达式对调用方法的真实性.
3 static void checkArgument(boolean expression, String errorMessageTemplate, Object. errorMessageArgs) 确保涉及一个或多个参数的表达式对调用方法的真实性.
4 static int checkElementIndex(int index, int size) 确保索引在一个数组、列表或大小的字符串中指定一个有效元素.
5 static int checkElementIndex(int index, int size, String desc) 确保索引在数组、列表或大小的字符串中指定一个有效元素.
6 static <T> T checkNotNull(T reference) 确保作为参数传递给调用方法的对象引用不是空的.
7 static <T> T checkNotNull(T reference, Object errorMessage) 确保作为参数传递给调用方法的对象引用不是空的.
8 static <T> T checkNotNull(T reference, String errorMessageTemplate, Object... errorMessageArgs) 确保作为参数传递给调用方法的对象引用不是空的.
9 static int checkPositionIndex(int index, int size) 确保索引在一个数组、列表或大小的字符串中指定一个有效的位置.
10 static int checkPositionIndex(int index, int size, String desc) 确保索引在一个数组、列表或大小的字符串中指定一个有效的位置.
11 static void checkPositionIndexes(int start, int end, int size) 确保start和end在数组、列表或大小字符串中指定有效的位置,并且是按顺序排列的.
12 static void checkState(boolean expression) 确保涉及调用实例状态的表达式的真实性,但不涉及调用方法的任何参数.
13 static void checkState(boolean expression, Object errorMessage) 确保涉及调用实例状态的表达式的真实性,但不涉及调用方法的任何参数.
14 static void checkState(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) 确保涉及调用实例状态的表达式的真实性,但不涉及调用方法的任何参数.
public class GuavaTester {

   public static void main(String args[]) {
      GuavaTester guavaTester = new GuavaTester();

      try {
         System.out.println(guavaTester.sqrt(-3.0));
      } catch(IllegalArgumentException e) {
         System.out.println(e.getMessage());
      }

      try {
         System.out.println(guavaTester.sum(null,3));
      } catch(NullPointerException e) {
         System.out.println(e.getMessage());
      }

      try {
         System.out.println(guavaTester.getValue(6));
      } catch(IndexOutOfBoundsException e) {
         System.out.println(e.getMessage());
      }
   }

   public double sqrt(double input) throws IllegalArgumentException {
      Preconditions.checkArgument(input > 0.0,
         "Illegal Argument passed: Negative value %s.", input);
      return Math.sqrt(input);
   }

   public int sum(Integer a, Integer b) {
      a = Preconditions.checkNotNull(a, "Illegal Argument passed: First parameter is Null.");
      b = Preconditions.checkNotNull(b, "Illegal Argument passed: Second parameter is Null.");

      return a+b;
   }

   public int getValue(int input) {
      int[] data = {1,2,3,4,5};
      Preconditions.checkElementIndex(input,data.length, "Illegal Argument passed: Invalid index.");
      return 0;
   }
}

Collections 工具类

序号 Collection 名字和描述
1 Multiset 对Set接口的扩展,允许重复的元素.
2 Multimap 对Map接口的扩展,使其键值可以同时映射为多个值.
3 BiMap 对Map接口的扩展,以支持逆向操作.
4 Table Table代表了一种特殊的映射,其中两个键可以以组合的方式被指定,以指代一个单一的值.

Guava提供了许多Multiset的实现,这些实现与JDK map的实现大体一致。

Guava - Multiset接口

其中ImmutableMap用来构造Map还是非常方便的

/**
 * Author: momo
 * Date: 2018/6/7
 * Description:
 */
public class ListTest {

    public static void main(String[] args) {
        /**List的常见用法*/

        //构造list
        List<Integer> list1 = Lists.newArrayList(1,2,3,4,5,6,7,8,9);
        System.out.println(list1);

        //反转list
        List<Integer> reverseList1 = Lists.reverse(list1);
        System.out.println(reverseList1);

        //切割集合
        List<List<Integer>> partition = Lists.partition(list1, 4);
        partition.stream().forEach(list->System.out.println(list));

        //拷贝为不可变集合
        List list2 = new ArrayList();
        list2.add(11);
        list2.add(41);
        list2.add(51);
        list2.add(12);
        ImmutableList immutableList = ImmutableList.copyOf(list2);

        //创建不可变集合
        ImmutableList<Integer> imList = ImmutableList.of(1, 2, 4, 12);

        //获取不可变字符集合
        ImmutableList<Character> asff = Lists.charactersOf("asff");
        asff.stream().forEach(character -> System.out.println(character));
        
        /**Map的常见用法*/

        Map<String,Object> leftMap = ImmutableMap.of("name", "汪", "age", 18, "address", "陕西", "city", "西安","love","张");
        Map<String,Object> rightMap = ImmutableMap.of("name", "张", "age", 16, "address", "陕西", "city", "西安","home","美国");
        MapDifference<String, Object> deffMap = Maps.difference(leftMap, rightMap);
        //相同的
        Map<String, Object> map = deffMap.entriesInCommon();
        System.out.println("相同的:"+map);
        //同key不同value
        Map<String, MapDifference.ValueDifference<Object>> stringValueDifferenceMap = deffMap.entriesDiffering();
        System.out.println("同key不同value:"+stringValueDifferenceMap);
        //仅仅左边有的
        Map<String, Object> onlyLeft = deffMap.entriesOnlyOnLeft();
        System.out.println("仅仅左边有的:"+onlyLeft);
        //仅仅右边有的
        Map<String, Object> onlyRight = deffMap.entriesOnlyOnRight();
        System.out.println("仅仅右边有的:"+onlyRight);


        /**BiMap*/
        BiMap<Object, Object> biMap = HashBiMap.create();
        biMap.put("张三",54);
        biMap.put("李四",23);
        biMap.put("程思",33);
        biMap.put("吴楠",16);

        //key相同value不同,后面的会覆盖前面的
        biMap.put("吴楠",46);
        //启动程序会报错 java.lang.IllegalArgumentException: value already present: 23
        //biMap.put("张刚",23);
        //强行添加,会覆盖
        //biMap.forcePut("张刚",23);
        System.out.println(biMap);

        //反转 key和value反转
        BiMap<Object, Object> inverseMap = biMap.inverse();
        System.out.println(inverseMap);

    }
}

Springboot整合Guava Cache

概述

Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。类似Caffeine框架。

Spring 5 使用 Caffeine 来代替 Guava Cache,应该是从性能的角度考虑的。从很多性能测试来看 Caffeine 各方面的性能都要比 Guava 要好。Caffeine 的 API 的操作功能和 Guava 是基本保持一致的,并且 Caffeine 为了兼容之前 Guava 的用户,做了一个 Guava 的 Adapter, 也是十分的贴心。

Guava CacheConcurrentMap 很相似,但也不完全一样。最基本的区别是 ConcurrentMap 会一直保存所有添加的元素,直到显式地移除。

相对地,Guava Cache 为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管 LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。

Guava Cache 是在内存中缓存数据,相比较于数据库或 redis 存储,访问内存中的数据会更加高效。Guava 官网介绍,下面的这几种情况可以考虑使用 Guava Cache

  1. 愿意消耗一些内存空间来提升速度。
  2. 预料到某些键会被多次查询。
  3. 缓存中存放的数据总量不会超出内存容量。

所以,可以将程序频繁用到的少量数据存储到 Guava Cache 中,以改善程序性能。内部缓存的限制就是存放的数据总量不能超出内存容量,毕竟还是在 JVM 里的。

依赖

<!--guava-start-->
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.1-jre</version>
</dependency>
<!--因为springboot新版本已经弃用guava作为本地缓存,所以需要切换到以前的spring版本-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.3.13.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--guava-end-->

配置类

需要@EnableCaching开启缓存

@Configuration
@EnableCaching
public class GuavaCacheConfig {

    @Bean
    public CacheManager cacheManager() {
        GuavaCacheManager cacheManager = new GuavaCacheManager();
        cacheManager.setCacheBuilder(
                CacheBuilder.newBuilder().
                        //缓存过期时间
                        expireAfterWrite(10, TimeUnit.SECONDS).
                        maximumSize(1000));
        return cacheManager;
    }
}

核心是使用 CacheBuilder 类构建一个缓存对象,CacheBuilder 类采用 builder 设计模式,它的每个方法都返回 CacheBuilder 本身,直到 build 方法被调用。

Cache<String,String> cache = CacheBuilder.newBuilder().build();
4         cache.put("word","Hello Guava Cache");
5         System.out.println(cache.getIfPresent("word"));

实体类

/**
 * <p>
 * 用户实体
 * </p>
 *
 * @package: com.example.li.springboot_cache_redis_demo.entity
 * @description: 用户实体
 * @author: LJH
 */

public class User implements Serializable {
    /**
     * 主键id
     */
    private Long id;
    /**
     * 姓名
     */
    private String name;
}

service层

spring提供了下面四种缓存注解:

@CachePut 用于某个方法,每次被调用,此方法都执行,并把结果更新到PutCache配置的地方,一般用于缓存更新。

@CacheEvict 调用有此注解的方法可以删除一个缓存

@CachesEvict 调用有此注解的方法可以删除多个缓存,很多时候一个操作会涉及到多个缓存的更新或删除。

@Cacheable 用于某个方法,希望这个方法添加缓存,此方法被调用的时候,如果有缓存,此方法不执行。

@Service
public class UserServiceImpl implements UserService {
    /**
     * 模拟数据库
     */
    private static final Map<Long, User> DATABASES = new ConcurrentHashMap();

    /**
     * 初始化数据
     */
    static {
        DATABASES.put(1L, new User(1L, "user1"));
        DATABASES.put(2L, new User(2L, "user2"));
        DATABASES.put(3L, new User(3L, "user3"));
    }

    /**
     * 保存或修改用户
     *
     * @param user 用户对象
     * @return 操作结果
     */
    @CachePut(value = "user", key = "#user.id")
    @Override
    public User saveOrUpdate(User user) {
        DATABASES.put(user.getId(), user);
        return user;
    }

    /**
     * 获取用户
     *
     * @param id key值
     * @return 返回结果
     */
    @Cacheable(value = "user", key = "#id")
    @Override
    public User get(Long id) {
        // 我们假设从数据库读取
        System.out.println("这是从数据库读取的,不是从缓存读取的");
        return DATABASES.get(id);
    }

    /**
     * 删除
     *
     * @param id key值
     */
    @CacheEvict(value = "user", key = "#id")
    @Override
    public void delete(Long id) {
        DATABASES.remove(id);
    }
}

Controller层

@RestController
public class UserController {

    @Resource
    private UserService userService;

    @GetMapping("saveOrUpdate")
    public String saveOrUpdate(){
        userService.saveOrUpdate(new User(4L, "user4"));
        return "ok";
    }

    @GetMapping("get")
    public User get(Long id){
        return userService.get(id);
    }

    @GetMapping("delete")
    public String delete(Long id){
        userService.delete(id);
        return "d-ok";
    }
}

结果:先调用saveOrUpdate方法插入缓存,第一次访问get方法会访问数据库,后续的10s内都是访问本地缓存。


文章作者: 小小千千
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小小千千 !
评论
  目录