java代码之美(3)

Map中的computeIfAbsent方法

 

Map接口的实现类如HashMap,ConcurrentHashMap,HashTable等继承了此方法,通过此方法可以在特定需求下,让你的代码更加简洁。

一、案例说明

1、概述

    在JAVA8的Map接口中,增加了一个方法computeIfAbsent,此方法签名如下:

public V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) 

    此方法首先判断缓存MAP中是否存在指定key的值,如果不存在,会自动调用mappingFunction(key)计算key的value,然后将key = value放入到缓存Map。

如果mappingFunction(key)返回的值为null或抛出异常,则不会有记录存入map

 

2、代码说明

复制代码
public class Java8Map {

    public Map<String,Object>  map1= Maps.newHashMap();

    public Map<String,AtomicInteger>  map2=Maps.newHashMap();

    public Map<String,List<String>>  map3=Maps.newHashMap();

    List<String> list = Lists.newArrayList("1", "2", "3","2","3","2");

    /**
     * 1、业务逻辑:如果key的value值为null,则在map中放入该key和设置相应的value值
     */
    @Test
    public  void  map1(){

        // java8之前,从map中根据key获取value操作可能会有下面的操作
        Object key = map1.get("key");
        if (key == null) {
            key = new Object();
            map1.put("key", key);
        }else{
            //如果key对应的value值存在,进行相应的操作
        }

        // java8之后,上面的操作可以简化为一行,若key对应的value为空,会将第二个参数的返回值存入并返回
        Object key2 = map1.computeIfAbsent("key1", k -> new Object());
        System.out.println(map1);
        //输出:{key1=java.lang.Object@708f5957, key=java.lang.Object@68999068}
    }


    /**
     * 2、统计List出现相同字符串的个数
     */
    @Test
    public void  map2() {

        //这里虽然只有一行代码但信息量很大,首先它采用量java的新特性lambda表达式来遍历list集合
        //这里表示如果map中的key对应的value值为null,则该key对应value值为new AtomicInteger()并执行自增加1,如果key已经存在,则直接value值自增1
        list.forEach( str-> map2.computeIfAbsent(str, k -> new AtomicInteger()).incrementAndGet());
        System.out.println(map2);
        //输出:{1=1, 2=3, 3=2}

    }

    /**
     * 3、如果key对应的value不存在,则创建新List并放入数据,存在则往直接往list放入数据
     */
    @Test
    public void  map3() {

        map3.computeIfAbsent("zhangsan", k -> genValue(k)).add("apple");
        map3.computeIfAbsent("zhangsan", k -> genValue(k)).add("orange");
        map3.computeIfAbsent("zhangsan", k -> genValue(k)).add("pear");
        map3.computeIfAbsent("zhangsan", k -> genValue(k)).add("banana");
        map3.computeIfAbsent("lisi", k -> genValue(k)).add("water");

        System.out.println(map3);
        //输出结果:{lisi=[water], zhangsan=[apple, orange, pear, banana]}

    }

    static List<String> genValue(String str) {
        return new ArrayList<String>();
    }
    
}
复制代码

总结:computeIfAbsent在一些实际开发场景中,能让我们代码看去更加简洁,代码质量看去也更高。

有关下面这步确实很优秀。它知道我map里的value是什么类型从而可以调相应的方法,比如这里是AtomicInteger对象,那就可以调incrementAndGet()方法

如果你放的是集合就可以调集合相关方法。

list.forEach( str-> map2.computeIfAbsent(str, k -> new AtomicInteger()).incrementAndGet());
java代码的优化

随着自己做开发时间的增长,越来越理解雷布斯说的: 敲代码要像写诗一样美。也能理解有一次面试官问我你对代码有洁癖吗?

一段好的代码会让人看就像诗一样,也像一个干净房间会让人看去很舒服。

一段好的项目代码我觉得可以用这三个维度去分析。1)性能 2)可扩展性3)可读性

有关代码的规范早在很久就有阿里巴巴的java开发手册,里面有非常多的规范。太多了,自己也没完全记住,抽空也会时不时再去翻翻。

接下来就写一些有关性能和可读性一些习惯,不全以后想到什么会再补充进来。

一、性能考虑

1、必须注意: 不对数据库层做任何操作 如果业务的确需要,那也最好注解说明原因

2、尽量减少对变量的重复计算

在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。

for (int i = 0; i < list.size(); i++)
{...}
 //建议修改成:
for (int i = 0, length = list.size(); i < length; i++)
{...}

这样list.size()只会调用一次,减少性能消耗。

3、尽量采用懒加载的策略,即在需要的时候才创建

这个习惯需要培养,在写逻辑的时候,尤其是创建对象的时候是否需要考虑懒加载。

例如:

A a = new A();
if (i == 1) 
{
list.add(a);
} 
//建议替换为:
if (i == 1) 
{
A a = new A();
list.add(a);
} 

4、字符串累加

1)循环外: 字符串拼接可以直接使用String的+操作,没有必要通过StringBuilder进行append.
2)循环内: 好的做法是在循环外声明StringBuilder对象,在循环内进行手动append。不论循环多少层都只有一个 StringBuilder对象。

反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行 append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。

StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");
//不在循环体内其实可以直接用加号,优化后一行代码:
String sb="a"+"b"+"c"+"d";

有关JDK不同版本对String拼接的优化可以参考:jdk不同版本对String拼接的优化分析

5、尽量避免使用split

split由于支持正则表达式,所以效率比较低。

替代

  String str1="a,b,c,d,,f,g"; 
  //可以考虑使用apache的StringUtils.split(string,char)
  List<String> list = Arrays.asList(StringUtils.split(str1, ","));
  //可以考虑guava工具
  List<String> list1=Splitter.on(",").splitToList(str1);

6、确定Stringbuffer的容量

Stringbuffer的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建Stringbuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。

例子:

 Stringbuffer buffer = new Stringbuffer(); // violation
 buffer.append ("hello");
//更正好:为stringbuffer提供寝大小。一般循环体内使用都可以知道大小
 Stringbuffer buffer = new Stringbuffer(max);
 buffer.append ("hello");

7、使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方 法

它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。

说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台的数据仍是数组。

String[] str = new String[] { "a", "b" };
List list = Arrays.asList(str);
//第一种情况:list.add("c"); 运行时异常。
//第二种情况:str[0]= "gujin"; 那么list.get(0)也会随之修改。

8、查找数组元素,可以用Arrays.asList(T[] array).contains(T obj)

二、可读性考虑

1、推荐尽量少用 else, if-else 的方式

可以考虑:

 if(condition){
   ...
 return obj; }
// 接着写 else 的业务逻辑代码;

说明:如果非得使用if()...else if()...else...方式表达逻辑,【强制】请勿超过3层,超过请使用策略设计模式。
正例:逻辑上超过 3 层的 if-else 代码可以使用卫语句,或者状态模式来实现。

接下来抽空会写一篇超过三层if-else更好的解决方案博客。

2、在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码

避免使用: if (condition) statements;

3、使用条件操作符替代"if (cond) return; else return;" 结构

//条件操作符更加的简捷
     if (isdone) {
         return 0;
     } else {
         return 10;
        }
//更正
   return (isdone ? 0 : 10);

4、Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals

正例: "test".equals(object);
反例: object.equals("test");
说明:推荐使用java.util.Objects (JDK7引入的工具类)

5、不允许出现任何魔法值(即未经定义的常量)直接出现在代码中

反例

String key="Id#taobao_"+tradeId;cache.put(key, value);

6、取反操作符(!)降低程序的可读性,所以不要总是使用

 boolean method (boolean a, boolean b) {
        if (!a)
            return !a;
        else
            return !b;
    }

7、注释掉的代码尽量要配合说明,而不是简单的注释掉

代码被注释掉有两种可能性:1)后续会恢复此段代码逻辑。2)永久不用。前者如果没 有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码)。

8、特殊注释标记,请注明标记人与标记时间

  1. 待办事宜(TODO)😦 标记人,标记时间,[预计处理时间]) 表示需要实现,但目前还未实现的功能。

  2. 错误不能工作(FIXME):(标记人,标记时间,[预计处理时间])在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。

CollectionUtils工具类

这篇讲的CollectionUtils工具类是在apache下的, 而不是springframework下的CollectionUtils。

个人觉得CollectionUtils在真实项目中,可以使你的代码更加简洁和安全。

所以需要倒入相关jar包,目前从maven找到最新jar包如下:

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.3</version>
    </dependency>

一、API常用方法

        /**
         * 1、除非元素为null,否则向集合添加元素
         */
        CollectionUtils.addIgnoreNull(personList,null);
        /**
         * 2、将两个已排序的集合a和b合并为一个已排序的列表,以便保留元素的自然顺序
         */
        CollectionUtils.collate(Iterable<? extends O> a, Iterable<? extends O> b)
        /**
         * 3、将两个已排序的集合a和b合并到一个已排序的列表中,以便保留根据Comparator c的元素顺序。
         */
        CollectionUtils.collate(Iterable<? extends O> a, Iterable<? extends O> b, Comparator<? super O> c)
        /**
         * 4、返回该个集合中是否含有至少有一个元素
         */
        CollectionUtils.containsAny(Collection<?> coll1, T... coll2)
        /**
         * 5、如果参数是null,则返回不可变的空集合,否则返回参数本身。(很实用 ,最终返回List EMPTY_LIST = new EmptyList<>())
         */
        CollectionUtils.emptyIfNull(Collection<T> collection)
        /**
         * 6、空安全检查指定的集合是否为空
         */
        CollectionUtils.isEmpty(Collection<?> coll)
        /**
         * 7、 空安全检查指定的集合是否为空。
         */
        CollectionUtils.isNotEmpty(Collection<?> coll)
        /**
         * 8、反转给定数组的顺序。
         */
        CollectionUtils.reverseArray(Object[] array);
        /**
         * 9、差集
         */
        CollectionUtils.subtract(Iterable<? extends O> a, Iterable<? extends O> b)
        /**
         * 10、并集
         */
        CollectionUtils.union(Iterable<? extends O> a, Iterable<? extends O> b)
        /**
         * 11、交集
         */
        CollectionUtils.intersection(Collection a, Collection b)
        /**
         *12、 交集的补集(析取)
         */
        CollectionUtils.disjunction(Collection a, Collection b)

 

二、非对象集合交、并、差处理

对于集合取交集、并集的处理其实有很多种方式,这里就介绍3种

  • 第一种 是CollectionUtils工具类
  • 第二种 是List自带方法
  • 第三种 是JDK1.8 stream 新特性

1、CollectionUtils工具类

下面对于基本数据(包扩String)类型中的集合进行demo示例。

 public static void main(String[] args) {
        String[] arrayA = new String[] { "1", "2", "3", "4"};
        String[] arrayB = new String[] { "3", "4", "5", "6" };
        List<String> listA = Arrays.asList(arrayA);
        List<String> listB = Arrays.asList(arrayB);

        //1、并集 union
        System.out.println(CollectionUtils.union(listA, listB));
        //输出: [1, 2, 3, 4, 5, 6]

        //2、交集 intersection
        System.out.println(CollectionUtils.intersection(listA, listB));
        //输出:[3, 4]

        //3、交集的补集(析取)disjunction
        System.out.println(CollectionUtils.disjunction(listA, listB));
        //输出:[1, 2, 5, 6]

        //4、差集(扣除)
        System.out.println(CollectionUtils.subtract(listA, listB));
        //输出:[1, 2]
    }

2、List自带方法

    public static void main(String[] args) {
        String[] arrayA = new String[] { "1", "2", "3", "4"};
        String[] arrayB = new String[] { "3", "4", "5", "6" };
        List<String> listA = Arrays.asList(arrayA);
        List<String> listB = Arrays.asList(arrayB);

        //1、交集
        List<String>  jiaoList = new ArrayList<>(listA);
        jiaoList.retainAll(listB);
        System.out.println(jiaoList);
        //输出:[3, 4]

       //2、差集
        List<String>  chaList = new ArrayList<>(listA);
        chaList.removeAll(listB);
        System.out.println(chaList);
        //输出:[1, 2]

        //3、并集 (先做差集再做添加所有)
        List<String>  bingList = new ArrayList<>(listA);
        bingList.removeAll(listB); // bingList为 [1, 2]
        bingList.addAll(listB);  //添加[3,4,5,6]
        System.out.println(bingList);
        //输出:[1, 2, 3, 4, 5, 6]
    }

注意 : intersection和retainAll的差别

要注意的是它们的返回类型是不一样的,intersection返回的是一个新的List集合,而retainAll返回是Bollean类型那就说明retainAll方法是对原有集合进行处理再返回原有集合,会改变原有集合中的内容。

个人观点:1、从性能角度来考虑的话,List自带会高点,因为它不用再创建新的集合。2、需要注意的是:因为retainAll因为会改变原有集合,所以该集合需要多次使用就不适合用retainAll。

注意 : Arrays.asList将数组转集合不能进行add和remove操作。

原因:调用Arrays.asList()生产的List的add、remove方法时报异常,这是由Arrays.asList() 返回的市Arrays的内部类ArrayList, 而不是java.util.ArrayList。Arrays的内部类ArrayList和java.util.ArrayList都是继承AbstractList,remove、add等方法AbstractList中是默认throw UnsupportedOperationException而且不作任何操作。java.util.ArrayList重新了这些方法而Arrays的内部类ArrayList没有重新,所以会抛出异常。

所以正确做法如下

        String[] array = {"1","2","3","4","5"};
        List<String> list = Arrays.asList(array);
        List arrList = new ArrayList(list);
        arrList.add("6");

3、JDK1.8 stream 新特性

    public static void main(String[] args) {
        String[] arrayA = new String[] { "1", "2", "3", "4"};
        String[] arrayB = new String[] { "3", "4", "5", "6" };
        List<String> listA = Arrays.asList(arrayA);
        List<String> listB = Arrays.asList(arrayB);

        // 交集
        List<String> intersection = listA.stream().filter(item -> listB.contains(item)).collect(toList());
        System.out.println(intersection);
        //输出:[3, 4]

        // 差集 (list1 - list2)
        List<String> reduceList = listA.stream().filter(item -> !listB.contains(item)).collect(toList());
        System.out.println(reduceList);
        //输出:[1, 2]

        // 并集 (新建集合:1、是因为不影响原始集合。2、Arrays.asList不能add和remove操作。
        List<String> listAll = listA.parallelStream().collect(toList());
        List<String> listAll2 = listB.parallelStream().collect(toList());
        listAll.addAll(listAll2);
        System.out.println(listAll);
        //输出:[1, 2, 3, 4, 3, 4, 5, 6]

        // 去重并集 
        List<String> list =new ArrayList<>(listA);
        list.addAll(listB);
        List<String> listAllDistinct = list.stream().distinct().collect(toList());
        System.out.println(listAllDistinct);
        //输出:[1, 2, 3, 4, 5, 6]
    }

总结 : 这三种我还是最喜欢第一种方式,因为第二种还需要确定该集合是否被多次调用。第三种可读性不高。

 

三、对象集合交、并、差处理

因为对象的equels比较是比较两个对象的内存地址,所以除非是同一对象,否则equel返回永远是false。

但我们实际开发中 在我们的业务系统中判断对象时有时候需要的不是一种严格意义上的相等,而是一种业务上的对象相等。在这种情况下,原生的equals方法就不能满足我们的需求了,所以这个时候我们需要重写equals方法。

说明 :String为什么可以使用equels方法为什么只要字符串相等就为true,那是因为String类重写了equal和hashCode方法,比较的是值。

1、Person对象

public class Person {
    private String name;
    private Integer age;
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    /**
     * 为什么重写equals方法一定要重写hashCode方法下面也会讲
     */
    @Override
    public int hashCode() {
        String result = name + age;
        return result.hashCode();
    }
    /**
     * 重写 equals 方法 根据name和age都相同那么对象就默认相同
     */
    @Override
    public boolean equals(Object obj) {
        Person u = (Person) obj;
        return this.getName().equals(u.getName()) && (this.age.equals(u.getAge()));
    }
    /**
     * 重写 toString 方法
     */
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
  }

2、测试

这里根据name和age都相同那么就默认相同对象。

    public static void main(String[] args) {

        List<Person> personList = Lists.newArrayList();
        Person person1 = new Person("小小",3);
        Person person2 = new Person("中中",4);
        personList.add(person1);
        personList.add(person2);

        List<Person> person1List = Lists.newArrayList();
        Person person3 = new Person("中中",4);
        Person person4 = new Person("大大",5);
        person1List.add(person3);
        person1List.add(person4);
        /**
         * 1、差集
         */
        System.out.println(CollectionUtils.subtract(personList, person1List));
        //输出:[Person{name='小小', age=3}]

        /**
         * 2、并集
         */
        System.out.println(CollectionUtils.union(personList, person1List));
        //输出:[Person{name='小小', age=3}, Person{name='中中', age=4}, Person{name='大大', age=5}]

        /**
         * 3、交集
         */
        System.out.println(CollectionUtils.intersection(personList, person1List));
        //输出:[Person{name='中中', age=4}]

        /**
         * 4、交集的补集(析取)
         */
        System.out.println(CollectionUtils.disjunction(personList, person1List));
        //输出:[Person{name='小小', age=3}, Person{name='大大', age=5}]
    }

其它两种方式就不在测了,因为都一样。

 

四、为什么重写equels方法一定要重写hashCode方法

1、源码

其实上面的Person类我可以只重写equels方法而不写hashCode方法,一样能达到上面的效果。但为什么还是建议写上呢?官方的说法是:对象的equals方法被重写,那么对象的hashCode()也尽量重写

重写equals()方法就必须重写hashCode()方法的原因,从源头Object类讲起就更好理解了。

先来看Object关于hashCode()和equals()的源码:

  public native int hashCode();
        
  public boolean equals(Object obj) {
           return (this == obj);
      }

光从代码中我们可以知道,hashCode()方法是一个本地native方法,返回的是对象引用中存储的对象的内存地址。而equals方法是利用==来比较的也是对象的内存地址。从上边我们可以看出,hashCode方法和equals方法是一致的。还有最关键的一点,我们来看Object类中关于hashCode()方法的注释:

  1.在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。    
  2.如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。    
  3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。
     但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。      

整理 : hashCode()和equals()保持一致,如果equals方法返回true,那么两个对象的hasCode()返回值必须一样。如果equals方法返回false,hashcode可以不一样,但是这样不利于哈希表的性能,一般我们也不要这样做。

假设两个对象,重写了其equals方法,其相等条件是某属性相等,就返回true。如果不重写hashcode方法,其返回的依然是两个对象的内存地址值,必然不相等。这就出现了equals方法相等,但是hashcode不相等的情况。这不符合hashcode的规则。

2、HashSet和Map集合类型

重写equals()方法就必须重写hashCode()方法主要是针对HashSet和Map集合类型,而对于List集合倒没什么影响。

原因: 在向HashSet集合中存入一个元素时,HashSet会调用该对象(存入对象)的hashCode()方法来得到该对象的hashCode()值,然后根据该hashCode值决定该对象在HashSet中存储的位置。简单的说:HashSet集合判断两个元素相等的标准是:两个对象通过equals()方法比较相等,并且两个对象的HashCode()方法返回值也相等。如果两个元素通过equals()方法比较返回true,但是它们的hashCode()方法返回值不同,HashSet会把它们存储在不同的位置,依然可以添加成功。

这就是问题所在:就是如果你只重写equals()方法,而不重写hashCode(),如果equals()为true,而它们的hashCode()方法返回值肯定不一样,因为它们都不是同一对象所以内存地址肯定不一样,所以它还是添加成功了,那么其实你写的equals()方法根本没啥软用。

3、代码示例

1、People类

重写equals方法,但并没有hashCode方法。

public class People {
    private String name;
    private Integer age;

    public People(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 重写 equals 方法
     */
    @Override
    public boolean equals(Object obj) {
        People u = (People) obj;
        return this.getName().equals(u.getName()) && (this.age.equals(u.getAge()));
    }
    /**
     * 重写 toString 方法
     */
    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2、实现类

    public static void main(String[] args) {
        HashSet<People> hashSet = Sets.newHashSet();
        People people1 = new People("小小",3);
        People people2 = new People("中中",4);
        People people3 = new People("中中",4);
        People people4 = new People("大大",5);
        hashSet.add(people1);
        hashSet.add(people2);
        hashSet.add(people3);
        hashSet.add(people4);

        System.out.println(hashSet);
        //输出:[People{name='小小', age=3}, People{name='中中', age=4}, People{name='大大', age=5}, People{name='中中', age=4}]
    }

很明显,我重写了equals方法,那么people2和people3的equals应该相同,所以不能放入HashSet,但它们的hashCode()方法返回不同,所以导致同样能放入HashSet。

Predicate详解

遇到Predicate是自己在自定义Mybatis拦截器的时候,在拦截器中我们是通过反射机制获取对象的所有属性,再查看这些属性上是否有我们自定义的UUID注解

如果有该注解,那么就给该属性赋值UUID随机字符串,作为主键保存到数据库。所以前提条件就是获取带有UUID注解的属性,就需要用到Predicate。

//获取所有带UUID注解的属性
 Set<Field> allFields = ReflectionUtils.getFields(object.getClass(),x.getAnnotation(UUId.class) != null);

也想到之前自己在用steam处理集合的时候,添加的过滤条件也是用Predicate,只不过它们不在同一包下。虽然它们不在同一包下但它们的作用是一致的,就是

Predicate接口主要用来判断一个参数是否符合要求

下面对这两个接口分别进行说明并举例。

一、java.util.function.Predicate

这里类是java自带主要广泛用在支持lambda表达式的API中。

1、接口源码

@FunctionalInterface
public interface Predicate<T> {
    /**
     * 具体过滤操作 需要被子类实现.
     * 用来处理参数T是否满足要求,可以理解为 条件A
     */
    boolean test(T t);
    /**
     * 调用当前Predicate的test方法之后再去调用other的test方法,相当于进行两次判断
     * 可理解为 条件A && 条件B
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    /**
     * 对当前判断进行"!"操作,即取非操作,可理解为 ! 条件A
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    /**
     * 对当前判断进行"||"操作,即取或操作,可以理解为 条件A ||条件B
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     * 对当前操作进行"="操作,即取等操作,可以理解为 A == B
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

2、常规示例

    public static void main(String[] args) {
        /**
         * 1、判断数字是否大于7
         */
        //设置一个大于7的过滤条件
        Predicate<Integer> predicate = x -> x > 7;
        System.out.println(predicate.test(10)); //输出 true
        System.out.println(predicate.test(6));  //输出 fasle
         /**
          * 2、大于7并且
          */
        //在上面大于7的条件下,添加是偶数的条件
        predicate = predicate.and(x -> x % 2 == 0);
        System.out.println(predicate.test(6));  //输出 fasle
        System.out.println(predicate.test(12)); //输出 true
        System.out.println(predicate.test(13)); //输出 fasle
        /**
         * 3、add or 简化写法
         */
        predicate = x -> x > 5 && x < 9;
        System.out.println(predicate.test(10)); //输出 false
        System.out.println(predicate.test(6));  //输出 true
    }

3、集合Stream示例

User对象

@Data
@AllArgsConstructor
@ToString
public class User {
    /**
     * 姓名
     */
    private String name;

    /**
     * 性别
     */
    private String sex;

    /**
     * 年龄
     */
    private Integer age;
    
   /**
     * 重写equals和hashCode
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof User) {
            User user = (User) obj;
            if (name.equals(user.name)){
                return true;
            }
        }
            return false;
        }
        @Override
        public int hashCode () {
            return name.hashCode();
        }

    }

测试代码

    public static void main(String[] args) {
        User user1 = new User("张三", "女", 1);
        User user2 = new User("李四", "男", 2);
        User user3 = new User("张三", "女", 3);
        List<User> list = Lists.newArrayList(user1, user2, user3);

       /**
        * 1、获取年龄大于2的对象
        */
        List<User> collect = list.stream().filter(x -> x.getAge() > 2).collect(Collectors.toList());
        System.out.println("获取年龄大于2的数量 = " + collect.size());
        //输出:获取年龄大于2的数量 = 1

        /**
         * 2、去重 设置name相同即为相同对象
         */
        //方式1直接使用 distinct
        List<User> collect1 = list.stream().distinct().collect(Collectors.toList());
        System.out.println("输出剩余对象" + collect1);
        //输出:输出剩余对象[User(name=张三, sex=女, age=1), User(name=李四, sex=男, age=2)]

        /**
         * 3、从集合找出与该对象相同的元素 同样name相同即为相同对象
         */
        User user4 = new User("张三", "男", 8);
        Predicate<User> predicate =  Predicate.isEqual(user4);
        List<User> collect2 = list.stream().filter(predicate).collect(Collectors.toList());
        System.out.println("与该对象相同的对象有" + collect2);
        //输出:与该对象相同的对象有[User(name=张三, sex=女, age=1), User(name=张三, sex=女, age=3)]
    }

运行结果

 

二、com.google.common.base.Predicate

这里的Predicate是配合guava使用的。

作用

  1. 处理集合的过滤条件
  2. 反射工具类的过滤条件

如果作为集合的过滤条件,现在已经没有必要用它了,因为JDK1.8的stream在处理集合的时候比它好用多了。

1、接口源码

@GwtCompatible
public interface Predicate<T> {
  //重写过滤条件
  @CanIgnoreReturnValue
  boolean apply(@Nullable T input);
  //重写equals
  boolean equals(@Nullable Object object);

在使用它的时候需要重写两个方法。

2、示例

自定义UUID注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface UUID {
}

Person

@Data
@AllArgsConstructor
@ToString
public class Person {
    /**
     * 姓名 在name上使用UUID注解
     */
    @UUID
    private String name;
    /**
     * 性别
     */
    private String sex;
    /**
     * 年龄
     */
    private Integer age;
}

测试代码

 public static void main(String[] args) {
        Person person1 = new Person("张三", "女", 1);
        Person person2 = new Person("李四", "男", 2);
        Person person3 = new Person("张三", "女", 3);

        List<Person> list = Lists.newArrayList(person1, person2, person3);
        /**
         * 1、guava使用过滤 年龄大于2的
         */
        Predicate<Person> predicate1 = new Predicate<Person>() {
            //重写两个方法
            @Override
            public boolean apply(Person input) {
                if (input.getAge() > 2) {
                    return true;
                }
                return false;
            }
            @Override
            public boolean equals(Object object) {
                return true;
            }
        };
        list = Lists.newArrayList(Iterables.filter(list,predicate1));
        System.out.println("过滤后的集合数据: "+list);
        //输出: 过滤后的集合数据: [Person(name=张三, sex=女, age=3)]

        /**
         * 2、配合反射工具类ReflectionUtils过滤获取属性
         */
        Person person4 = new Person("张三", "女", 1);
        Set<Field> allFields = org.reflections.ReflectionUtils.getFields(person4.getClass(),x -> x != null && x.getAnnotation(UUID.class) != null);
        System.out.println("带UUID注解的属性有 "+ allFields);
        //输出 :带UUID注解的属性有 [private java.lang.String com.jincou.vo.Person.name]
    }

运行结果

很明显,这里已经获取到了带有UUID注解的属性为name。

 

posted @ 2022-02-25 14:25  hanease  阅读(163)  评论(0编辑  收藏  举报