【优雅代码】14-guava精选方法及eventBus观察者模式源码解析

【优雅代码】14-guava精选方法及eventBus观察者模式源码解析

欢迎关注b站账号/公众号【六边形战士夏宁】,一个要把各项指标拉满的男人。该文章已在github目录收录。
屏幕前的大帅比大漂亮如果有帮助到你的话请顺手点个赞、加个收藏这对我真的很重要。别下次一定了,都不关注上哪下次一定。

1.背景

google的guava是非常经典的工具类,但是java经过多年的发展,不少方法已经有了优秀的替代方案,以下分享是个人在日常开发的时候依然觉得非常经典的方法。

2.基本工具Basic utilities

因为Optional和stream的流行,这个工具类里面的方法基本都用不上了。

3.集合Collections(重要)

这块主要介绍guava的集合,工具类虽然是对apache的补充,但是过于花里胡哨。

3.1不可变集合

  1. 常规用法
public static void immutableOrdinary() {
    // 常规创建
    Set<Integer> set = ImmutableSet.of(1, 2, 3, 1);
    List<Integer> list = ImmutableList.of(1, 2, 3, 1);
    // map的k-v连续的写法还是非常舒服的
    Map<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 1);

    // 循环创建
    ImmutableSet.Builder<Integer> builder = ImmutableSet.<Integer>builder();
    for (int i = 0; i < 10; i++) {
        builder.add(i);
    }
    Set<Integer> build = builder.build();

    // 常规list转不可变,值得注意的是Collections.unmodifiableList()
    List<Integer> collect = Stream.of(1, 2, 3, 4).collect(Collectors.toList());
    List<Integer> integers = ImmutableList.copyOf(collect);
}
  1. 以近乎同样的方式插入性能对比
public static void effectiveList() {
    StopWatch sw = new StopWatch();
    sw.start("listAdd");
    Stream.Builder<Integer> builder1 = Stream.builder();
    for (int i = 0; i < 10000; i++) {
        builder1.add(i);
    }
    List<Integer> list = builder1.build().collect(Collectors.toList());
    sw.stop();

    sw.start("ImmutableListAdd");
    ImmutableList.Builder<Integer> builder = ImmutableList.builder();
    for (int i = 0; i < 10000; i++) {
        builder.add(i);
    }
    List<Integer> guava = builder.build();
    sw.stop();
    System.out.println(sw.prettyPrint());
}
  • 可以看到插入速度非常明显的不可变集合要更快
---------------------------------------------
ns         %     Task name
---------------------------------------------
072909616  081%  listAdd
017632120  019%  ImmutableListAdd
  1. 获取单个元素的速度对比
List<Object> listUn = Collections.unmodifiableList(list);
sw.start("listGet");
list.get(0);
sw.stop();
sw.start("listUnGet");
listUn.get(0);
sw.stop();
sw.start("guavaGet");
guava.get(0);
sw.stop();
  • list可太慢了,其它两个效率差不多,guava还是要更快一点
---------------------------------------------
000018405  089%  listGet
000001323  006%  listUnGet
000000936  005%  guavaGet
  1. 循环性能对比
List<Object> listUn = Collections.unmodifiableList(list);
sw.start("listFor");
IntStream.range(0, 10000).boxed().forEach(list::get);
sw.stop();
sw.start("listUnFor");
IntStream.range(0, 10000).boxed().forEach(listUn::get);
sw.stop();
sw.start("guavaFor");
IntStream.range(0, 10000).boxed().forEach(guava::get);
sw.stop();
  • 和上面的结果一致
---------------------------------------------
007031946  053%  listFor
003760153  028%  listUnFor
002458426  019%  guavaFor

如上所述,这就是我非常喜欢guava的原因

  1. 区别对比
// 常规list转不可变,值得注意的是Collections.unmodifiableList()如果原list被改变不可变是会被改变的
list.remove(0);
// 即list和unmodifiableList都会少一个

3.2新集合类型

public static void newCollections(){
    // 这里只介绍BiMap和Table。Multiset、Multimap这两个就是嵌了list进去
    BiMap<Integer,String> biMap=HashBiMap.create();
    biMap.put(1,"张三");
    biMap.put(2,"李四");
    biMap.put(3,"王五");
    biMap.put(4,"赵六");
    biMap.put(5,"李七");
    biMap.put(6,"小小");
    Integer result = biMap.inverse().get("赵六");
    // 输出结果4
    System.out.println(result);
    // ===========================================================
    // table是个很有意思的数据结构,很有启发性思维,虽然我也不知道这个有啥用
    /*
     *  Company: IBM, Microsoft, TCS
     *  IBM         -> {101:Mahesh, 102:Ramesh, 103:Suresh}
     *  Microsoft     -> {101:Sohan, 102:Mohan, 103:Rohan }
     *  TCS         -> {101:Ram, 102: Shyam, 103: Sunil }
     *
     * */
    //create a table
    Table<String, String, String> employeeTable = HashBasedTable.create();

    //initialize the table with employee details
    employeeTable.put("IBM", "101","Mahesh");
    employeeTable.put("IBM", "102","Ramesh");
    employeeTable.put("IBM", "103","Suresh");

    employeeTable.put("Microsoft", "111","Sohan");
    employeeTable.put("Microsoft", "112","Mohan");
    employeeTable.put("Microsoft", "113","Rohan");

    employeeTable.put("TCS", "121","Ram");
    employeeTable.put("TCS", "102","Shyam");
    employeeTable.put("TCS", "123","Sunil");

    //所有行数据
    System.out.println(employeeTable.cellSet());
    //所有公司
    System.out.println(employeeTable.rowKeySet());
    //所有员工编号
    System.out.println(employeeTable.columnKeySet());
    //所有员工名称
    System.out.println(employeeTable.values());
    //公司中的所有员工和员工编号
    System.out.println(employeeTable.rowMap());
    //员工编号对应的公司和员工名称
    System.out.println(employeeTable.columnMap());
}

输出如下

4
[(IBM,101)=Mahesh, (IBM,102)=Ramesh, (IBM,103)=Suresh, (Microsoft,111)=Sohan, (Microsoft,112)=Mohan, (Microsoft,113)=Rohan, (TCS,121)=Ram, (TCS,102)=Shyam, (TCS,123)=Sunil]
[IBM, Microsoft, TCS]
[101, 102, 103, 111, 112, 113, 121, 123]
[Mahesh, Ramesh, Suresh, Sohan, Mohan, Rohan, Ram, Shyam, Sunil]
{IBM={101=Mahesh, 102=Ramesh, 103=Suresh}, Microsoft={111=Sohan, 112=Mohan, 113=Rohan}, TCS={121=Ram, 102=Shyam, 123=Sunil}}
{101={IBM=Mahesh}, 102={IBM=Ramesh, TCS=Shyam}, 103={IBM=Suresh}, 111={Microsoft=Sohan}, 112={Microsoft=Mohan}, 113={Microsoft=Rohan}, 121={TCS=Ram}, 123={TCS=Sunil}}

3.3集合工具类

public static void Collections() {
    // 个人觉得比较又用的有如下几个方法,前几个可以看做是redis交集、并集的内存实现。后面是数据库笛卡尔积的内存实现
    // 交集、并集、
    Set<Integer> set1 = Stream.of(1, 2, 3, 4).collect(Collectors.toSet());
    Set<Integer> set2 = Stream.of(3, 4, 5, 6).collect(Collectors.toSet());
    // 求set1的差集
    System.out.println(Sets.difference(set1, set2));
    // 求set1和set2差集的并集
    System.out.println(Sets.symmetricDifference(set1, set2));
    // 求交集
    System.out.println(Sets.intersection(set1, set2));
    // 笛卡尔积
    System.out.println(Sets.cartesianProduct(set1, set2));

    // 笛卡尔积
    List<Integer> list1 = Stream.of(1, 2, 3, 4).collect(Collectors.toList());
    List<Integer> list2 = Stream.of(2, 3, 4, 5).collect(Collectors.toList());
    System.out.println(Lists.cartesianProduct(list1, list2));
}

输出如下

[1, 2]
[1, 2, 5, 6]
[3, 4]
[[1, 3], [1, 4], [1, 5], [1, 6], [2, 3], [2, 4], [2, 5], [2, 6], [3, 3], [3, 4], [3, 5], [3, 6], [4, 3], [4, 4], [4, 5], [4, 6]]

4.缓存(重要)

该部分单独在下一篇分享。

5.函数式风格Functional idioms

同2一样,在stream的强力作用下不怎么用了

6.并发Concurrency

Futrue部分已经有了CompletableFuture(在第4节thread有介绍),非常好用。Service部分功能是很强,但这东西一般情况是真的用不上。
限流部分在下下篇分享

7.字符串处理Strings

apache的也不差,这块都差不多

8.原生类型Primitives

功能看起来很棒,但是目前还没找到实际使用场景

9.区间Ranges

功能看起来很棒,但是目前还没找到实际使用场景

10.I/O

apache的IOUtils用起来比这个要简单

11.散列Hash(因业务而异)

这个里面提供的布隆过滤,在有有需要的场景的时候还是可以用的。哈希算法在处理文件一致性校验等也有一席之地。
该部分单独在下下一篇分享。

12.事件总线EventBus(重要)

超级容易的观察者模式,有用到的可以用这东西,写起来舒心不少。这一块的设计思想个人还是非常喜欢的,觉得有必要展开一下。需要注意的是@Subscribe才会被通知,依赖的是方法参数和投递的对象参数一致。

12.1代码使用

  1. 创建被观察者
public static class eventBusObject {
    @Subscribe
    public void listenStr1(String str) {
        System.out.println(str + "listenStr1");
    }

    @Subscribe
    public void listenStr2(String str) {
        System.out.println(str + "listenStr2");
    }

    @Subscribe
    public void listenObj(Object str) {
        System.out.println(str + "listenStr1");
    }

    @Subscribe
    public void listenInt1(Integer str) {
        System.out.println(str + "listenInt1");
    }

    public void listenInt2(Integer str) {
        System.out.println(str + "listenInt2");
    }
}
  1. 方法通知
public static void eventBus() {
    EventBus eventBus = new EventBus("eventBusTest");
    eventBus.register(new eventBusObject());
    eventBus.post(100);
    eventBus.post("我是字符串");
}
  1. 输出结果
100listenInt1
100listenStr1
我是字符串listenStr1
我是字符串listenStr2
我是字符串listenStr1

12.2核心源码

  1. 注册
// 点击进入register方法如下
public void register(Object object) {
    subscribers.register(object);
}
// 再点击进入register方法如下,这里主要是反射,然后就存起来
void register(Object listener) {
    // 核心方法反射获取@Subscribe该注解的方法,并将传入的class进行分类
    Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);

    for (Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
      Class<?> eventType = entry.getKey();
      Collection<Subscriber> eventMethodsInListener = entry.getValue();

      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);

      if (eventSubscribers == null) {
        CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<>();
        eventSubscribers =
            MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
      }

      eventSubscribers.addAll(eventMethodsInListener);
    }
  }
  1. 调用
// 主方法调用post
public void post(Object event) {
    // 该方法反符合event的迭代集合
    Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
    if (eventSubscribers.hasNext()) {
      dispatcher.dispatch(event, eventSubscribers);
    } else if (!(event instanceof DeadEvent)) {
      // the event had no subscribers and was not itself a DeadEvent
      post(new DeadEvent(this, event));
    }
  }

// 主方法调用getSubscribers方法
Iterator<Subscriber> getSubscribers(Object event) {
    // 该方法返回event所有的父级对象,最上级即为Object,下面就是取两者交集进行组装
    ImmutableSet<Class<?>> eventTypes = flattenHierarchy(event.getClass());

    List<Iterator<Subscriber>> subscriberIterators =
        Lists.newArrayListWithCapacity(eventTypes.size());

    for (Class<?> eventType : eventTypes) {
      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
      if (eventSubscribers != null) {
        // eager no-copy snapshot
        subscriberIterators.add(eventSubscribers.iterator());
      }
    }

    return Iterators.concat(subscriberIterators.iterator());
  } 
// 回到主方法dispatcher.dispatch进行调用,该方法标记为2.1
@Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
  checkNotNull(event);
  checkNotNull(subscribers);
  Queue<Event> queueForThread = queue.get();
  queueForThread.offer(new Event(event, subscribers));

  if (!dispatching.get()) {
    dispatching.set(true);
    try {
      Event nextEvent;
      while ((nextEvent = queueForThread.poll()) != null) {
        while (nextEvent.subscribers.hasNext()) {
            //  核心方法调用,标记为2.1.1
          nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
        }
      }
    } finally {
      dispatching.remove();
      queue.remove();
    }
  }
}
// 从2.1.1这里就可以看到把任务直接丢到线程池
final void dispatchEvent(final Object event) {
    executor.execute(
        new Runnable() {
          @Override
          public void run() {
            try {
              invokeSubscriberMethod(event);
            } catch (InvocationTargetException e) {
              bus.handleSubscriberException(e.getCause(), context(event));
            }
          }
        });
  }

13.数学运算Math

一般情况用不到,apache下也有Math的包,功能基本一致

14.反射Reflection(次重要)

spring里面也带了这块的工具类,个人感觉spring的在处理常规情况下已经非常优秀了,这边补充了泛型方向的,用起来也比较简单,不过鉴于平常使用的机会不多就不展开了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容