你应该更新的Java知识之常用程序库

你应该更新的Java知识之常用程序库(一)
Tag:Java 程序库 Guava JodaTime 你应该更新的Java知识
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/dreamhead-logs/226738702.html


在很多人眼中,Java已经是一门垂垂老矣的语言,但并不妨碍Java世界依然在前进。如果你曾离开Java,云游于其它世界,或是每日只在遗留代码中挣扎,或许是时候抬起头,看看老Java中的新东西。


Guava


一句话,只要你做Java项目,就应该用Guava。


guava是Google出品的一套Java核心库,在我看来,它甚至应该是JDK的一部分。作为一个Java程序员,如果你没抱怨过JDK的设计,只能说明一点,你写得程序还是太少。正是JDK设计不彰,才有了一些项目来补充JDK的不足。如果说老Java程序员应该听说过Apache Commons Lang,那新Java程序员该知道的就是Guava了。


老Java程序员更多的是知道Google Collections,不妨到它的主页上走一遭,你会看到这个库已经改名为Guava。事实上,Guava并不直接等于Google Collections,Guava是一个超集。Guava实在太强大了,要想展现它的强大,需要专门的介绍,这里就不展开了。


下面以一个统计单词出现个数的小程序作为这个段落的结尾,虽然无法与许多其它语言的实现相提并论,但作为一个Java程序员,你不妨想一下按照传统方式,这段代码应该是什么样子。


  String content = Files.toString(new File(args[0]), Charset.defaultCharset());
  Iterable texts = Splitter.on(CharMatcher.WHITESPACE)
                                                 .omitEmptyStrings()
                                                 .trimResults()
                                                 .split(content);
  Multiset collection = HashMultiset.create(texts);


Joda Time


你觉得一个API设计得差到什么份上,才会把自己差不多的API全部Deprecated掉。java.util.Date便是这样的奇葩。因为它的API几乎都是反直觉的,几乎所有敢于用它的Java程序员都吃过它的亏。想初始化个2013年的第一天,还真不那么容易:


  Date firstDayOf2013 = new Date(113, 0, 1);


如果你是个Java新手,你能猜出113是从哪来的吗?(好吧,它是2013-1900,至于为什么是1900,这真得问API的设计者了)。


Joda Time就是人们实在无法忍受这样东西的产物。同样的代码用Joda Time实现:


  DateTime firstDayOf2013 = new DateTime().withDate(2013, 1, 1);


无论如何,你知道这能看出这些参数的含义了。不只如此,你还可以计算两天后是什么日子:


  firstDate.plusDays(2);


日期格式化,也是JDK Date系列API里一大特色,你必须把代码写成下面这样:


  new SimpleDateFormat("yyyy.MM.dd").format(firstDayOf2013)


作为一个初始化很慢的构造函数,你还必须每次调用,因为它不是线程安全的。同样的代码,在Joda Time里,我们可以用DateTimeFormatter:


  DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy.MM.dd");


  ...


  formatter.print(dateTime);


请尽管放心大胆把formatter声明成一个字段,因为它是线程安全的。


当然,Joda Time的强大远不止于此。当然,JDK也并不是那么完全的自暴自弃,于是,有了一个JSR 310专门设计新的Date API。JSR 310的spec lead是Steven Colebourne,此人正是Joda Time的作者。不过,虽然JSR 310为我们描绘了Date的全新景象,但Java 8出来之前就先别打它的主意了,乖乖地用Joda Time吧。


------------------------------------------------------------------------------------------------------------


你应该更新的Java知识之常用程序库(二)
Tag:Java 程序库 hamcrest mockito slf4j logback 你应该更新的Java知识
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/dreamhead-logs/226738756.html


Hamcrest


一句话,如果你写单元测试,就应该用Hamcrest。


如今不写单元测试,你都不好意思说自己在做工程项目了。但你一般这么写断言呢?如果你还写成下面这样,我只能说你落伍了:


  assertEquals(a, b);


请告诉我,哪个是执行结果,哪个是预期结果,不管你是怎样,反正大多数情况下,我是记不住的。所以,这个在只重功能不重可读性年代产生的API该更新了。于是,Hamcrest就是为了解决这样的问题而生的。


  assertThat(a, is(b));


很明显,前面一个是执行结果,后面一个是预期结果,当然这只是一个简单的例子。由于Hamcrest引入了matcher的概念(就是你看到的is部分),我们可以进行更多的组合:


  assertThat(number, greaterThan(5));
  assertThat(text, startsWith("Hello"));
  assertThat(array, hasItem("World"));


Hamcrest如此好用,以至于JUnit已经将其吸纳进去。如果你现在用的JUnit是4.4之后的版本,那你已经有了Hamcrest。无需额外的配置,就可以拿过来用。


Mockito


写单元测试不用Mock框架几乎是一件不可能的事,我是说Mock框架,不是Mock模式哦!对于老Java程序员来说,提起Mock框架,率先在脑海中撞线的多半是JMock或EasyMock。


使用Mockito,只要有一个理由就够了,简单。相比于JMock,它不用写checking,相比于EasyMock,它省去了replay。下面是个例子:


  List mockedList = mock(List.class);
  when(mockedList.get(0)).thenReturn("first");
  System.out.println(mockedList.get(0));


当然,Mockito还是非常强大的。


最后再强调一遍,无论使用哪个框架,请尽量不要使用verify,也就是传说中的Mock模式,那是把代码拉入泥潭的开始。


SLF4J和Logback


日志几乎是稍微有点规模的项目躲不开的一个东西,如果你是个老Java程序员,你必然知道Log4J,多半也知道Commons Logging。是时候把它们扔掉了,因为有SLF4J和Logback了。SLF4J要替代Commons Logging,而Logback的目标是Log4J。


程序员里愤青多,SLF4J和Logback的作者就是一个,他叫Ceki Gülcü,事实上,他也是Log4J的作者。Log4J的开发状态实在让他太不爽了,于是,他另起炉灶,打造出新的替代品。


只凭一点就足以让我们对SLF4J义无反顾了,你还记得用Commons Logging写出这样的代码吗?


  if (logger.debugEnable()) {
    logger.debug("Hello, ", name);
  }


而SLF4J的写法只有一句话:


  logger.debug("Hello, {}", name);


从根源来说,这是时代造成的,Commons Logging是Java 5之前产生的,那时候还没有变参,所以,我们不得不说,它老了。


至于Logback,性能是最重要的噱头,当然,还有一些其它的理由。理由里有一点并未提及,但对于开发人员很贴心的改进,就是日志模式的改进,还记得Log4J那密码一样的日志模式吗?


  %d{dd MMM yyyy HH:mm:ss} [%t] %-5p %m%n


下面是Logback的版本,不用查文档,我也看出每段表示的都是什么:


  %d{dd MMM yyyy HH:mm:ss} [%thread] %-5level %msg%n


这里介绍的几个程序库都是很通用的,无论是你做怎样的开发,应该都或多或少给你一些帮助。时间未曾停步,Java开发也未曾停留,如果你是个老Java程序员,是时候更新一下自己的知识了。


------------------------------------------------------------------------------------------------------------


你应该更新的Java知识之构建工具
Tag:构建工具 gradle buildr maven ant 你应该更新的Java知识
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/dreamhead-logs/227427912.html


时至今日,如果你的Java项目依然需要启动一个特定的工具,比如IDE,才能编译部署,那你的项目不在本文的讨论之中,因为实在落后得太让人无语了。


好,回归正题。旧时代的Java世界,构建工具等同于两个词Ant和Maven。


Ant源自Make,同JUnit一样,它也是一个航班产物(参见《Ant权威指南》序)。不过,Make的基础注定了它的起点不高,所有一切都要手工打造。我还依稀记得曾几何时,每每开始一个项目都要重新复制一份Ant脚本,修修改改。Maven给了人们新的希望,目录结构的约定、依赖管理、IDE集成,那时看来,几近完美。是的,几近,还差了那么一点点。就是那么一点点,却是致命伤。


只要一个项目进行一段时间,一个必然遇到的问题就是,编写新的自动化脚本。因为每个项目都有自己的特定需求,标准做法必然是无法满足的。扩展Maven对任何新手都是一件头疼的事,我们要学会编写插件,要搞清楚生命周期,这时,突然会唤起一丝丝对于ANT的怀念,虽然它做简单事不容易,但做复杂事却也没这么困难。


如果那些日子,我们不得不忍受Ant和Maven的不完美,那现在,是时候抛弃它们了。新时代Java构建有两个很好的候选:Gradle和Buildr。在我看来,它们真正比Maven更强大的地方,编写自己的任务更加容易。更值得兴奋的一点是,我们终于可以抛弃冗长的XML,选择一种更优雅的程序设计语言来写代码了,这几乎意味着你可以做到想做的一切。


Buildr是Apache出品的构建工具,它以Ruby作为构建脚本。我曾在InfoQ发表过一篇很长的文章《软件开发地基》,讨论软件项目应该具备一些基础构建项,就是以Buildr为基础的。有兴趣的话,可以参考一下。这里就不再赘述。顺便说一下,那篇文章里的内容,除了某些写法现在需要做一些微调,大部分内容依然是适用于大多数Java项目。


Gradle现在是整个Java社区的构建新宠,它采用Groovy作为自己的构建语言。如果你知道,Groovy是一门诞生自JVM平台的语言,这就决定了它要比其它移植到JVM上的语言能更好的适应JVM平台,它可以采用更符合Java世界的方式无缝地整合Java既有的程序库,而不必像移植语言那样削足适履。


初涉Gradle,最让人吃惊的一点莫过于它详尽的文档,涵盖了Gradle使用的方方面面,这是许多开源软件项目无法媲美,即便早在它的1.0版本尚未发布之时。当然,能做到这一点是因为它背后有一个公司在支撑:GradleWare,这意味着如果你需要商业支持,也是可以的。


Gradle 1.0尚未发布之,它就捧回2010年的Spring大奖和入围了2011年的JAX大奖。如果你还需要更多的信心,作为Java开发人员,你不可能不知道Spring,那Spring转投Gradle怀抱,应该是对Gradle最有利的支持了。


说了这么多,程序员最喜欢看到的东西还是代码。首先,请自行下载安装Gradle,然后,按照常见的Java项目布局把代码准备好(感谢Maven为我们给予我们约定),比如:


src/main/java,源代码文件目录
src/main/resources,资源文件目录
src/test/java,测试代码目录
下面是一个简单的Gradle构建脚本,将其存放于build.gradle


apply plugin: 'java'


repositories {
    mavenCentral()
}


dependencies {
  compile(
    'com.google.guava:guava:13.0.1',
    'joda-time:joda-time:2.1'
  )


  testCompile(
    'junit:junit:4.10',
    'org.mockito:mockito-all:1.9.0'
  )
}
(build.gradle)


接下来的事情就很简单了,在命令行里键入


  gradle build


如果一切正常,我们应该会看到构建成功的字样,然后,到build/lib,你就看到自己构建出来的JAR文件了。当然,这只是一个简单得不能再简单的例子,如果需要了解更多,Gradle那详尽的文档便是最好的去处。


关于构建工具的选择考量,我曾经写过一篇blog专门讨论,《选择,构建工具》。如果你在做构建工具的技术选型,可以参考。


-----------------------------------------------------------------------------------------------------------------------------------


你应该更新的Java知识之Observer
Tag:Java Guava EventBus Observer 你应该更新的Java知识
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/dreamhead-logs/231594181.html


作为一个Java程序员,不熟悉设计模式简直不好意思和人打招呼,而Observer模式可以说是位列最常用的设计模式之列,虽然有时候在具体代码里,它不一定叫这个名字,比如改头换面叫个Listener,但模式就是这个模式。


手工实现一个Observer也不是多复杂的一件事,只是因为这个设计模式实在太常用了,Java就把它放到了JDK里面:Observable和Observer,从JDK 1.0里,它们就一直在那里。从某种程度上说,它简化了Observer模式的开发,至少我们不用再手工维护自己的Observer列表了。


不过,如前所述,JDK里的Observer从1.0就在那里了,直到Java 7,它都没有什么改变,就连通知的参数还是Object类型。要知道,Java 5就已经泛型了。Java 5是一次大规模的语法调整,许多程序库从那开始重新设计了API,使其更简洁易用。当然,那些不做应对的程序库,多半也就过时了。这也就是这里要讨论知识更新的原因所在。


今天,对于普通的应用,如果要使用Observer模式该如何做呢?答案是Guava的EventBus。如你所见,它的名字并没有直接告诉你它是一个Observer,但这有什么关系呢,Listener不也是这样。


首先,我们声明一个Observer:


public class EventObserver {
 @Subscribe public void onMessage(Message message) {
   ...
 }
}


你会发现,这个类并没有继承任何接口,只是在用来响应通知的方法上声明了一个@Subscribe。


使用EventBus很简单,先声明一个


  EventBus eventBus = new EventBus();


然后,把我们写好的Observer注册进去:


  eventBus.register(new EventObserver());


当要通知Observer时,我们只要这样即可:


  eventBus.post(message);


这里,我们并没有告诉EventBus,我们要处理的是一个Message类型,只是在EventObserver的onMessage方法的接口声明上使用了这个类型而已。但是,当我们把消息发送出去的时候,它会根据类型进行匹配,保证我们的消息正确地发送到对应的地方。


相比于JDK原有的实现,这个实现会更简单。EventObserver不再需要存在一个继承体系中,而继承总是一种枷锁,把我们套牢在一个体系之中:


我们不必遵循一个特定的名字,比如Observer的update,而这里的名字onMessage是我们自己起的。
我们不必遵循特定的类型,比如update方法中作为被观察对象Observable和作为参数的Object,而是根据我们自己的需求选择的类型。
这种变换让静态类型的Java语言,有了一些动态类型的特质,也让程序更加灵活。这种灵活性多半要归功于Annotation,它在很大程度上影响了Java的程序设计风格。


除了标准的EventBus,Guava还提供了另外一个AsyncEventBus,从名字就可以看出,这是一个异步的EventBus,也就是说,消息扔给它之后,会立即返回,至于Observer什么时候处理,那就是它的事情了。


如果你想更多地了解EventBus,那Guava的官方文档是个不错的去处。




-----------------------------------------------------------------------------------------------------------------------------------


你应该更新的Java知识之集合初始化
Tag:你应该更新的Java知识 Java Guava 集合
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/dreamhead-logs/232899025.html


你应该更新的Java知识之常用程序库(一)
你应该更新的Java知识之常用程序库(二)
你应该更新的Java知识之构建工具
你应该更新的Java知识之Observer


在Java里,如何初始化一个List呢?


我知道,你可以毫不费力地写出这样的代码:


List<String> names = new ArrayList<String>();
names.add("dreamhead");


这几乎是在Java 5/6风格的代码中随处可见的,但不知道你怎么想,反正这样的代码让我觉得很不爽。


首先,变量声明里存在重复,明明我已经知道它是一个String的List,还要再后面再说一次。如果你尝试过一些具有类型推演功能的语言,你就会知道,这种重复完全是可以由编译器处理掉的。Java 7也认为这样的代码不好,所以,它给我们提供了一个新的语法:


List<String> names = new ArrayList<>();


其实,即便是Java 5/6,我们也是有办法写出不那么冗余的代码。Guava已经为我们提供了一个解决方案,所以,同样的代码如果用Guava来写,它会是这样:


List<String> names = newArrayList();


这里,它利用了编译器的类型推演功能保证了类型的正确性。从代码上看,它甚至要比Java 7提供的解决方案键入的字符还少。


这只是第一步,再有我还要在List里面放入内容,让我们继续来看在Guava里面怎么做。


List<String> names = newArrayList("dreamhead");


一行代码搞定,因为支持变参,所以,我们甚至可以一次放入很多元素。但是,这不是终点。


在很多数情况下,使用List时,我们并不关心其具体的类型,只是把它当做一组数据来用,而并不是为了动态添加一些东西。这个时候,我们只要有一个不变的List就可以了。Guava里的ImmutableList就可以很好地把我们的意图表现出来。一方面,它实现了List接口,你可以把它当做一个List来用,另一方面,如果试图改变其中的内容,它会抛出异常。


就我们这里讨论的内容而言,它的初始化方法更为简单:


List<String> names = of("dreamhead");


这里讨论的只是List,但大多数内容同样适用于Set和Map,另外两种我们最常用的数据结构。


需要额外加以解释的是,of其实是为少量元素初始化打开的方便之门。所以,如果查看of的实现,你会发现List最多支持12个元素的处理,而Set只有6个,只是因为可变参数的存在,它可以支持到很多。但是,Map的则不同,它只有5对,因为它的键值对必须成对出现。如果你需要元素很大的ImmutableMap,一个更好的解决方法是采用Builder,下面是一个例子:


ImmutableMap<String, String> contentTypeMap = ImmutableMap.<String, String>builder()
           .put("png", "image/png")
           .put("gif", "image/gif")
           .put("jpg", "image/jpeg")
           .put("jpeg", "image/jpeg")
           .put("tiff", "image/tiff")
           .put("css", "text/css")
           .put("html", "text/html")
           .put("txt", "text/plain")
           .put("js", "application/x-javascript")
           .put("json", "application/json")
           .put("pdf", "application/pdf")
           .put("zip", "application/zip")
           .put("xml", "text/xml")
           .build();


在程序设计领域,有一个经典的说法,语言设计就是程序库设计,程序库设计就是语言设计。像List、Map这样的集合类型,有些语言把它放进了语法,使得它用起来更简单。像Java这样做起事来一板一眼的程序设计语言,如果有了好的程序库支撑,也可以让它相对方便,Guava在这方面做了一个很好的尝试。


对普通的Java程序员而言,是时候抛弃繁重的Java旧语法,拥抱新时代的Java了。


-----------------------------------------------------------------------------------------------------------------------------------


你应该更新的Java知识之集合操作
Tag:你应该更新的Java知识 Java Guava 集合
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/dreamhead-logs/234113759.html


你应该更新的Java知识之常用程序库(一)
你应该更新的Java知识之常用程序库(二)
你应该更新的Java知识之构建工具
你应该更新的Java知识之Observer
你应该更新的Java知识之集合初始化


我们打算做要做这样一件事,给出所有有资格过五四青年节的人的名字。如果你不知道的话,有资格过五四青年节的人要年龄在14周岁到28周岁之间。


按照传统的思路,这段代码应该是这样写:


List names = new ArrayList();
for (Person person : people) {
  int age = person.getAge();
  if (age >= 14 && age <= 28) {
    names.add(person.getName());
  }
}


是不是很熟悉?这样的代码在我们的代码库中几乎随处可见。单就这段代码而言,本身没什么问题。这里只是举了一个简单的例子,但现实情况往往比这复杂得多,于是,我们经常看到一个for循环里嵌套了大量的代码。


好,新风格代码时间。这次我们还是会用到Guava,先上代码后解释:


Iterable names = transform(filter(people, isAgeQualified()), toName());


这里先介绍两个重要的函数transform和filter,这两个函数都是对一个集合中的各个元素进行操作。


transform如名所示,它把集合中的一个值转换为另一个值的函数,至于具体怎么转换,则由后面的函数定义。我们来看看toName的定义:


Function<Person, String> toNames() {
  return new Function<String, Integer>() {
    @Override
    public boolean apply(Person person) {
      return person.getName();
    }
  };
}


这个函数的返回值是一个Function,是的,它是一个“函数”。在Java里,函数不是一等公民,所谓一等公民指的是:


它可以按需创建
它可以在数据结构中存储
它可以作为实参传给一个函数
它可以当做一个函数的返回值
Java世界的一等公民是对象,但我们可以用对象包装函数,就像前面这样,这就是函数对象。如果你了解函数式编程,你会发现,transform接收一个函数作为它的参数,那它实际上就是一个高阶函数:


接收一个或多个函数做实参
以一个函数做返回值
在函数式编程里,最为强大的一点就是通过函数的组合,我们可以做到很多未曾想过的做法,程序的可扩展性也会出乎意料地好。


让我们继续把未介绍完的说完,接下来是filter,它表示用一个条件对集合的元素进行过滤,条件在哪?我们来看isAgeQualified的定义:


Predicate isAgeQualified() {
  return new Predicate() {
    @Override
    public boolean apply(Person person) {
      int age = person.getAge();
      return age >= 14 && age <= 28;
    }
  };
}


Predicate实际上也是一种形式的函数,只不过,它的返回值是boolean,有了上面的基础,它也就不那么难以理解了。


好,为了体现这段程序的扩展性,我们继续扩展一下我们的需求:我们要找出所有符合条件的男同胞。如果采用的是原来的循环,那么我们通常的做法是在if里面在添加一个条件,事实上,大量复杂的for循环就是这么一点一点扩充出来的。好,我们看看按照新的做法,我们会怎么做:


Iterable names = transform(filter(people, and(isAgeQualified(), isMale()), toName());


这里新增了isMale()函数,它的实现你也可以想到:


Predicate isMale() {
  return new Predicate() {
    @Override
    public boolean apply(Person person) {
      return person.isMale();
    }
  };
}


这里还增加了一个and函数,它可以把多个Predicate组合起来。Guava本身给我们还提供许多这样的逻辑运算符,比如or、not等等。


一旦形成了一个函数,它最大的价值在于重用,比如,我们现在要超出一些男同胞,干些体力活,那这个函数就又可以发挥价值了。


如果你接触 Lisp 族的程序设计语言,这样的括号套括号的做法你会觉得眼熟。当我们有很多的操作要组合在一起的时候,这种写法就颇具 Lisp 风味了。之所以我们需要写成这样,很大程度是拜 Java 僵化的语法所赐,我们无法给既有类型添加方法。在一 些可以扩展类的语言里,这段代码会更流畅一些。


Guava还给我们提供了另外一种做法,既然我们不能扩展已有的类,就不妨提供一个新的类,它的名字叫做FluentIterable:


FluentIterable names = FluentIterable.from(people).filter(isAgeQualified()).transform(toName());


如果你说过反if和反for运动,看了这篇,你就应该知道怎么样消除集合操作中的一些if和for了。




-------------------------------------------------------------------------------------------------------------------------------------------------


你应该更新的Java知识之惰性求值
Tag:你应该更新的Java知识 Java Guava Supplier 惰性求值
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/dreamhead-logs/234741366.html


你应该更新的Java知识之常用程序库(一)
你应该更新的Java知识之常用程序库(二)
你应该更新的Java知识之构建工具
你应该更新的Java知识之Observer
你应该更新的Java知识之集合初始化
你应该更新的Java知识之集合操作


在开发中,我们经常会遇到一些需要延迟计算的情形,比如某些运算非常消耗资源,如果提前算出来却没有用到,会得不偿失。在计算机科学中,有个专门的术语形容它:惰性求值。惰性求值是一种求值策略,也就是把求值延迟到真正需要的时候。


在Java里,我们有一个专门的设计模式几乎就是为了处理这种情形而生的:Proxy。不过,现在我们有了新的选择:Supplier。


我们先来看看Supplier的定义:


public interface Supplier {
 T get();
}


非常简单的一个定义,简而言之,得到一个对象。


但它有什么用呢?我们可以把耗资源运算放到get方法里,在程序里,我们传递的是Supplier对象,直到调用get方法时,运算才会执行。这就是所谓的惰性求值。


对于Java这种缺乏惰性求值的语言,惰性一般就是通过一个间接层来实现的。David Wheeler曾经说过:“计算机科学中的所有问题都可以通过引入一个间接层解决。”


理解了基本的用法,实现一个Supplier并不困难:


Supplier ultimateAnswerSupplier = new Supplier() {
 @Override
 public Integer get() {
   // Long time computation
   return 42;          
 }
};


当我们需要这个终极答案时,只要


int ultimateAnswer = ultimateAnswerSupplier.get();


确实很简单,但是,我知道你已经心生另一个疑问。通常实现Proxy模式,我们只会计算一次,像终极答案这样的东西,反复运算我们可承受不起,也没有必要。


我甚至知道你已经迫不及待地打算动手实现自己的解决方案,把结果保留下来,再下次调用时,直接返回结果。但,且慢。


不,我并不是说多线程并发让保存结果这件小事变得复杂,因为我相信你的能力。但你是否想过,如果你打算为它这么做,也就意味着,你几乎要为所有的Supplier对象这么做。反复做一件事,显然不应该是一个程序员的作为。


幸好Guava已经给我们准备好了:


memorizedUltimateAnswerSupplier = Suppliers.memoize(ultimateAnswerSupplier);


memoize函数帮我打点了前面所说的那些事情:第一次get()的时候,它会调用真正Supplier,得到结果并保存下来,下次再访问就返回这个保存下来的值。


有时候,这个值只在一段时间内是有效的,你知道我要说什么了,Guava还给我们提供了一个另一个函数,让我们可以设定过期时间:


expirableUltimateAnswerSupplier = memoizeWithExpiration(target, 100, NANOSECONDS);


好了,还在自己编写Proxy处理惰性求值吗?Supplier便是你需要更新的Java知识。顺便说一下,Java 8里也有Supplier哦!


-------------------------------------------------------------------------------------------------------------------------------------------------


你应该更新的Java知识之Optional
Tag:你应该更新的Java知识 Java Guava Null Optional
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/dreamhead-logs/235329092.html


你应该更新的Java知识之常用程序库(一)
你应该更新的Java知识之常用程序库(二)
你应该更新的Java知识之构建工具
你应该更新的Java知识之Observer
你应该更新的Java知识之集合初始化
你应该更新的Java知识之集合操作
你应该更新的Java知识之惰性求值


java.lang.NullPointerException,只要敢自称Java程序员,那对这个异常就再熟悉不过了。为了防止抛出这个异常,我们经常会写出这样的代码:


Person person = people.find("John Smith");
if (person != null) {
 person.doSomething();
}


遗憾的是,在绝大多数Java代码里,我们常常忘记了判断空引用,所以,NullPointerException便也随之而来了。


“Null Sucks.”,这就是Doug Lea对空的评价。作为一个Java程序员,如果你还不知道Doug Lea是谁,那赶紧补课,没有他的贡献,我们还只能用着Java最原始的装备处理多线程。


"I call it my billion-dollar mistake.",有资格说这话是空引用的发明者,Sir C. A. R. Hoare。你可以不知道Doug Lea,但你一定要知道这位老人家,否则,你便没资格使用快速排序。


在Java世界里,解决空引用问题常见的一种办法是,使用Null Object模式。这样的话,在“没有什么”的情况下,就返回Null Object,客户端代码就不用判断是否为空了。但是,这种做法也有一些问题。首先,我们肯定要为Null Object编写代码,而且,如果我们想大规模应用这个模式,我们要为几乎每个类编写Null Object。


幸好,我们还有另外一种选择:Optional。Optional是对可以为空的对象进行的封装,它实现起来并不复杂。在某些语言里,比如Scala,Optional实现成了语言的一部分。而对于Java程序员而言,Guava为我们提供了Optional的支持。闲言少叙,先来如何使用Optional,完成前面的那段代码。


Optional person = people.find("John Smith");
if (person.isPresent()) {
 person.get().doSomething();
}


这里如果isPresent()返回false,说明这是个空对象,否则,我们就可以把其中的内容取出来做自己想做的操作了。


如果你期待的是代码量的减少,恐怕这里要让你失望了。单从代码量上来说,Optional甚至比原来的代码还多。但好处在于,你绝对不会忘记判空,因为这里我们得到的不是Person类的对象,而是Optional。


看完了客户端代码,我们再来看看怎样创建一个Optional对象,基本的规则很简单:


如果我们知道自己要封装的对象是一个空对象,可以用
 Optional.absent();


如果封装的对象是一个非空对象,则可以用
 Optional.of(obj);


如果不知道对象是否为空,就这样创建创建
 Optional.fromNullable(obj);


有时候,当一个对象为null的时候,我们并不是简单的忽略,而是给出一个缺省值,比如找不到这个人,任务就交给经理来做。使用Optional可以很容易地做到这一点,以上面的代码为例:


  Optional person = people.find("John Smith");
  person.or(manager).doSomething()


说白了,Optinal是给了我们一个更有意义的“空”。


------------------------------------------------------------------------------------------------------------------------------------------------------


你应该更新的Java知识之Optional高级用法
Tag:你应该更新的Java知识 Java Guava Null Optional Maybe_Monad
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/dreamhead-logs/235334714.html


你应该更新的Java知识之常用程序库(一)
你应该更新的Java知识之常用程序库(二)
你应该更新的Java知识之构建工具
你应该更新的Java知识之Observer
你应该更新的Java知识之集合初始化
你应该更新的Java知识之集合操作
你应该更新的Java知识之惰性求值
你应该更新的Java知识之Optional


介绍了Optinal的基本用法,我们来看一个有趣的例子,找到一个人的出生国家。按照传统的思路,代码大约是这个样子:


Place place = person.getPlaceOfBirth();
if (place != null) {
 City city = place.getCity();
 if (city != null) {
   Province province = city.getProvince();
   if (province != null) {
     return province.getCountry();
   }
 }
}


return null;


如果你对整洁代码稍有追求,这样的if套if都会让你觉得不爽。让我们尝试用Optional改造它,不过,事先声明一下,以下代码并不在Guava代码库里,而是自行的扩展,也是弥补Guava Optional的缺失,你可以把下面的代码添加到你自己的程序库中,作为基础代码:


首先,我们要给Optionals添加一个方法:


public class Optionals {
   public static <T, U> Optional bind(Optional value,
                                       Function<T, Optional> function) {
     if (value.isPresent()) {
      return function.apply(value.get());
    }


    return absent();
  }
}
(参见具体代码)


这个方法的意图是,对一个Optional值(value)执行一个操作(function),如果value不是空,则对value执行操作,否则,返回空。


如果单纯从这个函数,你还不是很清楚它到底能做些什么,那我们就直接来看代码:


bind(
 bind(
   bind(
     bind(personOptional, getPlaceOfBirth()),                          
     getCityFromPlace()),
   getProvinceFromCity()),
 getCountryFromProvince());


我们连着用了几个bind连Person对象里一层一层地往外找我们所需的值,如你所料,这里的每个函数实际上都是一个函数对象,我们就以其中的一个演示一下基本的做法:


Function<Province, Optional> getCountryFromProvince() {
 return new Function<Province, Optional>() {
   @Override
   public Optional apply(Province input) {
     return fromNullable(input.getCountry());
   }
 };
}


把所有这些放在一起你就应该理解了,在这中间执行的任何一个环节如果出现空值,那么整个的返回值就是一个空值,否则,它就会一层一层的执行下去。


这样一来,如果我们把bind函数视为程序库里的函数,那我们的客户端代码里面,一个if都没有出现,我们成功地消除那一大堆的if嵌套。


不过,这种括号套括号的用法颇有Lisp风味,作为一个Java程序员,我们对于这样的写法还着实需要适应一下。让我们再进一步探索一下,看看怎么能把它做得更Java一些。


public class FluentOptional {
   private Optional optional;


   private FluentOptional(Optional optional) {
     this.optional = optional;
  }


  public static FluentOptional from(Optional optional) {
     return new FluentOptional(optional);
 }


  public FluentOptional bind(Function<T, Optional> function) {
     if (isPresent()) {
         return from(function.apply(get()));
     }


      return from(Optional.absent());
 }


  ...


(参见具体代码)


通过这段代码,我们可以用FluentOptional提供一个对Optional类的封装,这里面我们新增了两个方法from和bind,其它方法都是可以由Optional提供,实现很容易,这里省略了。我们看看通过这个新实现,我们的方法变成了什么模样:


from(personOptional)
  .bind(getPlace())
  .bind(getCityFromPlace())
  .bind(getProvinceFromCity())
  .bind(getCountryFromProvince());


怎么样,如此一来,代码就就像Java代码了吧!


实际上,这种做法也是来自一种函数式编程的理念:Maybe Monad,这是Haskell程序设计语言为探索纯函数式编程所做的努力之一,这里就不做过多的介绍了。


----------------------------------------------------------------------------------------------------------------
你应该更新的Java知识之不变集合
Tag:你应该更新的Java知识 Java Guava 不变集合
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/dreamhead-logs/236028457.html


你应该更新的Java知识之常用程序库(一)
你应该更新的Java知识之常用程序库(二)
你应该更新的Java知识之构建工具
你应该更新的Java知识之Observer
你应该更新的Java知识之集合初始化
你应该更新的Java知识之集合操作
你应该更新的Java知识之惰性求值
你应该更新的Java知识之Optional
你应该更新的Java知识之Optional高级用法


在传统的Java里,为了表示一个集合,我们常常会写出这样的代码:


public class People {
    private List people;


    public void setPeople(List people) {
        this.people = people;
    }


    public List getPeople() {
       return this.people;
   }


     ...
}


 严格说来,这样的代码存在缺陷。虽然貌似List被封装到People里,但实际上,这个List的引用却暴露在外面,这个类的用户可以轻松地拿到List的引用,随意修改。所以,下面是一种更严谨的写法:


public class People {
    private List people;


    public void setPeople(List people) {
         this.people.addAll(people);
    }


    @SuppressWarnings("unchecked")
    public List getPeople() {
        return (List)this.people.clone();
    } 


    ...
}


 这里的做法基本思路是,做一个副本,保证内部引用(这里的people)不会传播到外面。但在实际编写代码的时候,大多数人都不会这么做,能否意识到这样的问题只是一个方面,这样的代码写起来,也要比原来的那种写法麻烦得多。按照许多公司的做法,这种要求只能放到代码规范里,但无论如何,在程序世界里,人为规定永远是最容易忽略的约定。


 不过,在真实世界中,即便是我们写的只是上面的最简单那种形式,却很少出现问题。原因何在呢?因为大多数情况下,我们编写这样程序的时候,会有一种隐形的约定,这个“List”是不变的。我们设置(set)完这个List之后,基本上不会留着它的引用在做任何操作;而得到(get)它之后,也基本上不会去修改它。


 在这种情况下,我们使用的实际上是一个不变的List。既然是一个不变的List,那不如就更直接地把它表现出来。


 Guava为我们提供了不变集合的概念,对应着各种具体类型,有ImmutableList、ImmutableSet,还有ImmutableMap。从名字上,我们不难看出它们的用法。


 Guava不变集合的做法并不是另起炉灶,而是遵循了Java已有集合框架的约定。比如,通过查阅文档,我们不难发现,ImmutableList就是一个List,只不过使用这个“List”,只不过,当我们尝试调用诸如add、set之类试图修改集合的方法时,它会抛出异常。正是有了这样的基础,不变集合可以和很多已有的类库、框架配合在一起。


 有了这个基础,我们把不变集合的概念应用于之前的代码,它就是下面这个样子:


public class People {
    private ImmutableList people;


    public void setPeople(ImmutableList people) {
        this.people = people;
    }


    public ImmutableList getPeople() {
        return this.people;
    } 


    ...
}


这样一来,代码依然很简单,但是,意义却与从前完全不一样了。我们不必再为可能存在的隐忧顾虑了:一旦元素放到集合里,就不可能修改它了,因为它是不可变的。


对于使用这段代码的人来说,getter自不必说,如往常一样使用,setter也不费力。只是当做字面量使用的集合,我们已经在《你应该更新的Java知识之集合初始化》中讨论过了。如果要适配于现有程序库,把一个已有的List转成ImmutableList也不复杂,一个简单的copyOf方法就可以实现:


    List existingPeople = ... 
    ImmutableList immutablePeople = copyOf(existingPeople);


为了让讨论更完整,这里不得不提到另外一个不变接口的选择:Iterable。它是从Java 5开始进入JDK的一个接口:


public interface Iterable  {
    Iterator iterator();
}


作为一个Java程序员,我们对Iterator简直再熟悉不过了,有了Iterator,我们就可以遍历一个集合。所以,在一些情况下,我们也可以使用Iterable表示一个不变集合。大多数Java已有的集合类都实现了这个接口。


既然它是JDK里的东西,为什么不把它优先推荐呢?原因有几个。最重要的一个原因在于我们熟知的Iterator,它有一个方法叫做remove,可能大多数Java程序员已经习惯性地忽略了这个方法,但它就在哪里,它是一个可以“改变”集合的方法,所以,从语义上说,它不是一个很好的选择。另外,从已有的代码习惯来说,很多程序员还是很喜欢用List、Set作为接口,所以,ImmutableList从心理上来说,更接近已有的习惯。剩下的一个点似乎不那么重要,有些代码真的需要使用到特定类型的接口。不过,就大多数代码而言,我们只是要得到的一个集合,做一些操作,而这些操作我们在《你应该更新的Java知识之集合操作》中做了一些讨论。


在函数式编程中,不变是提升程序稳定性一个很重要的概念。既然我们大多数情况下实际用到的是不变集合,那就不妨直接把它表现出来。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值