===================java
与历史上任何其余的语言相比,这里要排除c语言和cobol语言,如今愈来愈多的工做中,有用的代码用Java语言写出。在20年前Java首次发布时,它引了软件界的风暴。在那时,相对c++语言,Java语言要更简单,更安全,并且在一段时间后,Java语言的性能也获得了提高(这依赖于具体的使用状况,一个大型的Java程序于相同的c++程序相比,可能会慢一点,或者同样快,或者更快一些)。比起c++,Java牺牲很是少性能,却提供了巨大的生产力提高。python
Java是一门blue-collar language,程序员值得信赖的工具,它只会采用已经被别的语言尝试过的正确的理念,同时增长新的特性只会去解决主要的痛点问题。Java是否一直忠于它的使命是一个开放性的问题,但它确实是努力让自已的道路不被当前的时尚所左右太远。在智能芯片,嵌入式设备和大型主机上,java都在用于编写代码。甚至被用来编写对任务和安全要求苛刻的硬件实时软件。c++
然而,最近一些年,Java获得了很多负面的评价,特别是在互联网初创公司中。相对于别的语言如Ruby和python,Java显得死板,并且与配置自由的框架如Rails相比,java的网页开发框架须要使用大量的xml文件作为配置文件。进一步说,java在大型企业中普遍使用致使了java所采用的编程模式和作法在一个很是大的具备鲜明等级关系的技术团队中会颇有用,可是这些编程模式和作法对于快速开发打破常规的初创公司来讲,不是很合适。git
可是,Java已经改变。Java最近增长了lambda表达式和traits。以库的形式提供了像erlang和go所支持的轻量级线程。而且最重要的是,提供了一个现代的、轻量级的方式用于取代陈旧笨重以大量xml为基础的方法,指导API、库和框架的设计。程序员
最近一些年,Java生态圈发生了一些有趣的事:大量的以jvm为基础的程序语言变得流行;其中一些语言设计的十分好(我我的喜欢Clojure和Kotlin)。可是与这些可行或者推荐的语言相比,Java与其它基于JVM的语言来讲,确实有几个优势:熟悉,技持,成熟,和社区。经过新代工具和新代的库,Java实际上在这几个方面作了不少的工做。所以,许多的硅谷初创公司,一但他们成长壮大后,就会回到Java,或者至少是回到JVM上,这点就不会另人惊奇了。github
这份介绍性指南的目标是想学习如何写现代精简Java代码的程序员(900万),或者是那些听到了或体验过Java坏的方面的Python/Ruby/Javascript程序员。而且指南展现了Java中已经改变的方面和这些改变的方面如何让Java得到另人赞叹的性能,灵活性和可监控性而不会牺牲太多的Java沉稳方面。web
对Java术语简单价绍一下,Java在概念上被分为三个部分:Java,Java运行时库和Java虚拟机,或者叫JVM。若是你熟悉Node.js,Java语言类同于JavaScript,运行时库类同于Node.js,JVM类同于V8引擎。JVM和运行时库被打包成你们所熟知的Java运行时环境,或者叫JRE(虽然经常人们说JVM实际上指的是JRE)。Java开发工具,JDK,是指某一个JRE的发行版,一般包括不少开发工具像java编绎器javac
,还有不少程序监控和性能分析工具。JRE一般有几个分支,如支持嵌入式设备开发版本,可是本博客中,咱们只会涉及到JRE支持服务器(桌面)开发的版本,这就是众所周知的 JavaSE(Java标准版)。正则表达式
有一些项目实现了JVM和JRE的标准,其中一些是开源的项目,还有一些是商业项目。有些JVM很是特殊,若有些JVM运行硬件实时嵌入式设备软件,还有JVM能够在巨大的内存上运行软件。可是咱们将会使用HotSpot,一个由Oracle支持的的自由,通用的JVM实现,同时HotSpot也是开源OpenJDK项目的一部分。算法
Java构建JVM,JVM同时运行Java(虽然JVM最近为了其它语言作了一些专门的修改)。可是什么是JVM,Cliff Click的这个演讲解释了什么是JVM,简单来讲,JVM是一台抽象现实的魔法机器。JVM使用漂亮,简单和有用的抽象,好像无限的内存和多态,这些听起来实现代价很高,而且实现这些特征用如此高效的形式以至于他们能很容易能与没有提供这些有用抽象的运行时竞争。更须要说明的是,JVM拥有最好内存回收算法并能在大范围的产品中使用,JVM的JIT容许内联和优化虚方法的调用(这是许多语言中最有用的抽像的核心),在保存虚方法的用处的同时,使调用虚方法很是方便和快捷。JVM的JIT(即时编绎器)是基础的高级性能优化编绎器,和你的应用一块儿运行。
固然JVM也隐藏了不少的操做系统级别的细节,如内存模型(代码在不一样的CPU上运行怎样看待其它的CPU操做引发的变量的状态的变化)和使用定时器。JVM还提供运行时动态连接,热代码交换,监控几乎全部在JVM上运行的代码,还有库中的代码。
这并非说JVM是完美的。当前Java的数组缺失存放复杂结构体的能力(计划将在Java9中解决),还有适当的尾调用优化。尽管JVM有这样的问题,可是JVM的成熟,测试良好,快速,灵活,还有丰富的运行时分析和监控,让我不会考虑运行一个关键重要的服务器进程在别的任何基础之上(除了JVM别无选择)。
理论已经足够了。在咱们深刻讲解以前,你应该下载在这里下载最新的JDK,或者使用你系统自带的包管理器安装最新的OpenJDK。
让咱们开启现代Java构建工具旅程。在很长的一段历史时间内,Java出现过几个构建工具,如Ant和Maven,他们大多数都基于XML。可是现代的Java开发者使用Gradle(最近成为Android的官方构建工具)。Gradle是一个成熟,深刻开发,现代Java构建工具,它使用了在Groovy基础上的DSL语言来讲明构建过程。他集成了Maven的简单性和Ant的强大性和灵活性,同时抛弃全部的XML。可是Gradle并非没有错误:当他使最通用的部分简单和可声明式的同时,就会有不少事情变得很是不通用,这就要求返回来使用命令式的Groovy。
如今让咱们使用Gradle建立一个新的Java项目。首先,咱们从这里下载Gradle,安装。如今咱们开始建立项目,项目名叫JModern
。建立一个叫Jmodern的目录,切换到击刚才建立的目录,执行:
gradle init --type java-library
Gradle 建立了项目的初始文件夹结构,包括子类(Library.java
和LibraryTest.java
),咱们将在后面删除这两个文件:
代码在src/main/java/
目录下,测试代码在src/test/java
目录下。咱们将主类命名为jmodern.Main
(因此主类的源文件就在src/main/java/jmodern/Main.java
),这个程序将会把Hello World
程序作一点小小的变化。同时为了使用Gradle更方便,将会使用Google's Guava
。使用你喜欢的编辑器建立src/main/java/jmodern/Main.java
,初始的代码以下:
package jmodern; import com.google.common.base.Strings; public class Main { public static void main(String[] args) { System.out.println(triple("Hello World!")); System.out.println("My name is " + System.getProperty("jmodern.name")); } static String triple(String str) { return Strings.repeat(str, 3); } }
相应建立一个小的测试用例:在src/test/java/jmodern/MainTest.java
:
package jmodern; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import org.junit.Test; public class MainTest { @Test public void testTriple() { assertThat(Main.triple("AB"), equalTo("ABABAB")); } }
在项目根目录,找到build.gradle
文件,修改该文件:
apply plugin: 'java' apply plugin: 'application' sourceCompatibility = '1.8' mainClassName = 'jmodern.Main' repositories { mavenCentral() } dependencies { compile 'com.google.guava:guava:17.0' testCompile 'junit:junit:4.11' // A dependency for a test framework. } run { systemProperty 'jmodern.name', 'Jack' }
构建程序设置jmoder.Main
为主类,声明Guava
为该程序的依赖库,而且jmodern.name
为系统属性,方便运行时读取。当输入如下命令:
gradle run
Gradle会从Maven中心仓库下载Guava,编绎程序,而后运行程序,把jmodern.name
设置成"Jack"
。总的过程就是这样。
接下来,运行一下测试:
gradle build
生成的测试报告在build/reports/tests/index.html
:
有些人说IDE会稳藏编程语言的问题。好吧,对于这个问题,我没有意见,可是无论你使用任何语言,一个好的IDE老是有帮助的,而Java在这方面作的最好。固然在文章中选择IDE不是重要的部分,老是要提一下,在Java世界中,有三大IDE: Eclipse,IntelliJ IDEA,和NetBeans,你应该之后使用一下后二者。IntelliJ多是三者之中最强大的IDE,而NetBeans应该是最符合程序员直觉和最易于使用(我认为也最好看)的IDE。NetBeans经过Gradle的插件对Gradle有最好的支持。Eclipse是最受欢迎的IDE。我在不少年前感受Eclipse变得混乱,就不使用Eclipse了。固然若是你是一个长期使用Eclipse的用户,也没有什么问题。
安装完Gradle插件,咱们的小项目在NetBeans中的样子以下:
我最喜欢NetBeans的Gradle插件功能不只是由于IDE列出了全部有关项目的依赖,还有其它的配置插件也能列出,因此咱们只须要在构建文件中声明他们一次。若是你在项目中增长新的依赖库,在NetBeans中右键单击项目,选择Reload Project
,而后IDE将下载你新增长的依赖库。若是你右键单击Dependencies
结点,选择Download Sources
,IDE会下载依赖库的源代码和相关javadoc,这样你就能够调试第三方库的代码,还能查看第三方库的文档。
长期以来,Java经过Javadoc生成很好的API文档,并且Java开发者也习惯写Javadoc形式的注释。可是现代的Java开发者喜欢使用Markdown,喜欢使用Markdown为Javadoc增长点乐趣。为了达在Javadoc使用Markdown,咱们在构建文件中dependencies
部分的前面,增长Pegdown Doclet
Javadoc插件:
configurations { markdownDoclet }
而后,在dependencies
中添加一行:
markdownDoclet 'ch.raffael.pegdown-doclet:pegdown-doclet:1.1.1'
最后,构建文件的最后增长这个部分:
javadoc.options { docletpath = configurations.markdownDoclet.files.asType(List) // gradle should relly make this simpler doclet = "ch.raffael.doclets.pegdown.PegdownDoclet" addStringOption("parse-timeout", "10") }
终于,能够在Javadoc注释使用Markdown,还有语法高亮。
你可能会想关掉你的IDE的注释格式化功能(在Netbeans: Preferences -> Editor -> Formatting, choose Java and Comments, and uncheck Enable Comments Formatting)。IntelliJ 有一个插件能高亮在Javadoc中的Markdown语法。
为了测试新增的设置,咱们给方法randomString
增长Markdown格式的javadoc,函数以下:
/** * ## The Random String Generator * * This method doesn't do much, except for generating a random string. It: * * * Generates a random string at a given length, `length` * * Uses only characters in the range given by `from` and `to`. * * Example: * * ```java * randomString(new Random(), 'a', 'z', 10); * ``` * * @param r the random number generator * @param from the first character in the character range, inclusive * @param to the last character in the character range, inclusive * @param length the length of the generated string * @return the generated string of length `length` */ public static String randomString(Random r, char from, char to, int length) ...
而后使用命令gradle javadoc
在build/docs/javadoc/
生成html格式文档:
通常我不经常使用这个功能,由于IDE对这个功能的语法高亮支持的不太好。可是当你须要在文档中写例子时,这个功能能让你的工做变得更轻松。
最近发布的Java8给Java语言带来了很大的改变,由于java原生支持lambda表达式。lambda表达式解决了一个重大的问题,在过去人们解决作一些简单事却写不合理的冗长的代码。为了展现lambda有多大的帮助,我拿出我能想到的使人很恼火的,简单的数据操做代码,并把这段代码改用Java8写出。这个例子产生了一个list,里面包含了随机生成的学生名字,而后进行按他们的头字母进行分组,并以美观的形式打印出来。如今,修改Main
类:
package jmodern; import java.util.List; import java.util.Map; import java.util.Random; import static java.util.stream.Collectors.*; import static java.util.stream.IntStream.range; public class Main { public static void main(String[] args) { // generate a list of 100 random names List<String> students = range(0, 100).mapToObj(i -> randomString(new Random(), 'A', 'Z', 10)).collect(toList()); // sort names and group by the first letter Map<Character, List<String>> directory = students.stream().sorted().collect(groupingBy(name -> name.charAt(0))); // print a nicely-formatted student directory directory.forEach((letter, names) -> System.out.println(letter + "\n\t" + names.stream().collect(joining("\n\t")))); } public static String randomString(Random r, char from, char to, int length) { return r.ints(from, to + 1).limit(length).mapToObj(x -> Character.toString((char)x)).collect(Collectors.joining()); } }
Java自动推导了全部lambda的参数类型,Java确保了参数是类型安全的,而且若是你使用IDE,IDE中的自动完成和重构功能对这些参数均可以用的。Java不会像c++使用auto
和c#中的var
还有Go同样,自动推导局部变量,由于这样会让代码的可读性下降。可是这并不意味着要须要手动输入这些类型。例如,光标在students.stream().sorted().collect(Collectors.groupingBy(name -> name.charAt(0)))
这一行代码上,在NetBeans中按下Alt+Enter
,IDE会推导出结果适当的类型(这里是Map<Character, String>
)。
若是想感受一下函数式编程的风格,将main
函数改为下面的形式:
public static void main(String[] args) { range(0, 100) .mapToObj(i -> randomString(new Random(), 'A', 'Z', 10)) .sorted() .collect(groupingBy(name -> name.charAt(0))) .forEach((letter, names) -> System.out.println(letter + "\n\t" + names.stream().collect(joining("\n\t")))); }
跟之前的代码确实不同(看哪,没有类型),可是这应该不太容易理解这段代码的意思。
就算Java有lambda,可是Java仍然没有函数类型。其实,lambda在java中被转换成近似为functional接口,即有一个抽象方法的接口。这种自动转换使遗留代码可以和lambda在一块儿很好的工做。例如:Arrays.sort
方法是须要一个Comparateor
接口的实例,这个接口简单描述成单一的揭抽象 int compare(T o1, T o2)
方法。在java8中,可使用lambda表达式对字符串数组进行排序,根据数组元素的第三个字符:
Arrays.sort(array, (a, b) -> a.charAt(2) - b.charAt(2));
Java8也增长了能实现方法的接口(将这种接口换变成“traits”)。例如,FooBar
接口有两个方法,一个是抽象方法foo
,另外一个是有默认实现的bar
。另外一个useFooBar
调用FooBar
:
interface FooBar { int foo(int x); default boolean bar(int x) { return true; } } int useFooBar(int x, FooBar fb) { return fb.bar(x) ? fb.foo(x) : -1; }
虽然FooBar
有两个方法,可是只有一个foo
是抽象的,因此FooBar
也是一个函数接口,而且可使用lambda表达式建立FooBar,例如:
useFooBar(3, x -> x * x)
将会返回9。
有许多人和我同样,都对并发数据结构感兴趣,而这一块是JVM的后花园。一方面,JVM对于CPU的并发原语提供了低级方法如CAS结构和内存栅栏,另外一方面结合内存回收机制提供了平台中立的内存模型。可是,对那些使用并发控制的程序员来讲,并非为了扩展他们的软件,而使用并发控制,而是他们不得不使用并发控制使本身的软件可扩展。从这方面说,Java并发控制并非很好,是有问题。
真的,Java从开始就被设计成为并发控制,而且在每个版本中都强调他的并发控制数据结构。Java已经高质量的实现了不少很是有用的并发数据结构(如并发HashMap,并发SkipListMap,并发LinkedQueue),有些都没有在Erlang和Go中实现。Java的并发控制一般领先c++5年或者更长的时间。可是你会发现正确高效地使用这些并发控制数据结构很是困难。当咱们使用线程和锁时,刚开始你会发现它们工做的很好,到了后面当你须要更多并发控制时,发现这些方法不能很好的扩展。而后咱们使用线程池和事件,这两个东西有很好的扩展性,可是你会发现很难去解释共享变量,特别是在语言级别没有对共享变量的可变性进行限制。进一步说,若是你的问题是内核级线程不能很好的扩展,那么对事件的异步处理是一个坏想法。为何不简单修复线程的问题呢?这偏偏是Erlang和Go所采用的方式:轻量级的用户线程。轻量级用户线程经过简单,阻塞式的编程方法高效使用同步结构,将内核级的并发控制映射到程序级的并发控制,而不用牺牲可扩展性,同时比锁和信号更简单。
Quasar是一个咱们建立的开源库,它给JVM增长了真正的轻量级线程(在Quasar叫纤程),同得可以很好的同系统级线程很好在一块儿的工做。Quasar同Go的CSP同样,同时有一个基结Erlang的Actor系统。对付并发控制,纤程是一个很好的选择。纤程简单、优美和高效。如今让咱们来看看它:
首先,咱们设置构建脚本,添加如下的代码在build.gradle
中:
configurations { quasar } dependencies { compile "co.paralleluniverse:quasar-core:0.5.0:jdk8" quasar "co.paralleluniverse:quasar-core:0.5.0:jdk8" } run { jvmArgs "-javaagent:${configurations.quasar.iterator().next()}" // gradle should make this simpler, too }
更新依赖,编辑Main.java:
package jmodern; import co.paralleluniverse.fibers.Fiber; import co.paralleluniverse.strands.Strand; import co.paralleluniverse.strands.channels.Channel; import co.paralleluniverse.strands.channels.Channels; public class Main { public static void main(String[] args) throws Exception { final Channel<Integer> ch = Channels.newChannel(0); new Fiber<Void>(() -> { for (int i = 0; i < 10; i++) { Strand.sleep(100); ch.send(i); } ch.close(); }).start(); new Fiber<Void>(() -> { Integer x; while((x = ch.receive()) != null) System.out.println("--> " + x); }).start().join(); // join waits for this fiber to finish } }
如今有经过channel,有两个纤程能够进行通讯。
Strand.sleep
,和Strand
类的全部方法,在原生Java线程和fiber中都能很好的运行。如今咱们将第一个fiber替换成原生的线程:
new Thread(Strand.toRunnable(() -> { for (int i = 0; i < 10; i++) { Strand.sleep(100); ch.send(i); } ch.close(); })).start();
这也运行的很好(固然咱们已在咱们的应用中运行百万级的fiber,也用了几千线程)。
咱们处一下channel selection (模拟Go的select)。
package jmodern; import co.paralleluniverse.fibers.Fiber; import co.paralleluniverse.strands.Strand; import co.paralleluniverse.strands.channels.Channel; import co.paralleluniverse.strands.channels.Channels; import co.paralleluniverse.strands.channels.SelectAction; import static co.paralleluniverse.strands.channels.Selector.*; public class Main { public static void main(String[] args) throws Exception { final Channel<Integer> ch1 = Channels.newChannel(0); final Channel<String> ch2 = Channels.newChannel(0); new Fiber<Void>(() -> { for (int i = 0; i < 10; i++) { Strand.sleep(100); ch1.send(i); } ch1.close(); }).start(); new Fiber<Void>(() -> { for (int i = 0; i < 10; i++) { Strand.sleep(130); ch2.send(Character.toString((char)('a' + i))); } ch2.close(); }).start(); new Fiber<Void>(() -> { for (int i = 0; i < 10; i++) { SelectAction<Object> sa = select(receive(ch1), receive(ch2)); switch (sa.index()) { case 0: System.out.println(sa.message() != null ? "Got a number: " + (int) sa.message() : "ch1 closed"); break; case 1: System.out.println(sa.message() != null ? "Got a string: " + (String) sa.message() : "ch2 closed"); break; } } }).start().join(); // join waits for this fiber to finish } }
从Quasar 0.6.0开始,能够在选择状态中使用使用lambda表达式,最新的代码能够写成这样:
for (int i = 0; i < 10; i++) { select( receive(ch1, x -> System.out.println(x != null ? "Got a number: " + x : "ch1 closed")), receive(ch2, x -> System.out.println(x != null ? "Got a string: " + x : "ch2 closed"))); }
看看fiber的高性能io:
package jmodern; import co.paralleluniverse.fibers.*; import co.paralleluniverse.fibers.io.*; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.*; import java.nio.charset.*; public class Main { static final int PORT = 1234; static final Charset charset = Charset.forName("UTF-8"); public static void main(String[] args) throws Exception { new Fiber(() -> { try { System.out.println("Starting server"); FiberServerSocketChannel socket = FiberServerSocketChannel.open().bind(new InetSocketAddress(PORT)); for (;;) { FiberSocketChannel ch = socket.accept(); new Fiber(() -> { try { ByteBuffer buf = ByteBuffer.allocateDirect(1024); int n = ch.read(buf); String response = "HTTP/1.0 200 OK\r\nDate: Fri, 31 Dec 1999 23:59:59 GMT\r\n" + "Content-Type: text/html\r\nContent-Length: 0\r\n\r\n"; n = ch.write(charset.newEncoder().encode(CharBuffer.wrap(response))); ch.close(); } catch (IOException e) { e.printStackTrace(); } }).start(); } } catch (IOException e) { e.printStackTrace(); } }).start(); System.out.println("started"); Thread.sleep(Long.MAX_VALUE); } }
咱们作了什么?首先咱们启动了一个一直循环的fiber,用于接收TCP链接。对于每个链接上的链接,这个fiber会启动另一个fiber去读请求,发送回应,而后关闭。这段代码是阻塞IO的,在后台使用异步EPoll IO,因此它和异步IO服务器,有同样的扩展性。(咱们将在Quasar中极大的提升IO性能)。
Actor模型,受欢迎是有一半缘由是Erlang,意图是编写可容错,高可维护的应用。它将应用分割成独立可容错的容器单元-Actors,标准化处理错误中恢复方式。
当咱们开始Actor,将compile "co.paralleluniverse:quasar-actors:0.5.0"
加到你的构建脚本中的依赖中去。
咱们重写Main
函数,要让咱们的应用可容错,代码会变的更加复杂。
package jmodern; import co.paralleluniverse.actors.*; import co.paralleluniverse.fibers.*; import co.paralleluniverse.strands.Strand; import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) throws Exception { new NaiveActor("naive").spawn(); Strand.sleep(Long.MAX_VALUE); } static class BadActor extends BasicActor<String, Void> { private int count; @Override protected Void doRun() throws InterruptedException, SuspendExecution { System.out.println("(re)starting actor"); for (;;) { String m = receive(300, TimeUnit.MILLISECONDS); if (m != null) System.out.println("Got a message: " + m); System.out.println("I am but a lowly actor that sometimes fails: - " + (count++)); if (ThreadLocalRandom.current().nextInt(30) == 0) throw new RuntimeException("darn"); checkCodeSwap(); // this is a convenient time for a code swap } } } static class NaiveActor extends BasicActor<Void, Void> { private ActorRef<String> myBadActor; public NaiveActor(String name) { super(name); } @Override protected Void doRun() throws InterruptedException, SuspendExecution { spawnBadActor(); int count = 0; for (;;) { receive(500, TimeUnit.MILLISECONDS); myBadActor.send("hi from " + self() + " number " + (count++)); } } private void spawnBadActor() { myBadActor = new BadActor().spawn(); watch(myBadActor); } @Override protected Void handleLifecycleMessage(LifecycleMessage m) { if (m instanceof ExitMessage && Objects.equals(((ExitMessage) m).getActor(), myBadActor)) { System.out.println("My bad actor has just died of '" + ((ExitMessage) m).getCause() + "'. Restarting."); spawnBadActor(); } return super.handleLifecycleMessage(m); } } }
代码中有一个NaiveActor
产生一个BadActor
,这个产生出来的的Actor会偶然失败。因为咱们的父actor监控子Actor,当子Actor过早的死去,父actor会获得通知,而后从新启动一个新的Actor。
这个例子,Java至关的恼人,特别是当它用instanceof
测试消息的类型和转换消息的类型的时候。这一方面经过模式匹配Clojure和Kotlin作的比较好(之后我会发一篇关于Kotlin的文章)。因此,是的,全部的类型检查和类型转换至关另人讨厌。这种类型代码鼓励你去试一下Kotlin,你真的该去使用一下(我就试过,我很是喜欢Kotlin,可是要用于生产环境使用它还有待成熟)。就我的来讲,这种恼人很是小。
回到主要问题来。一个基于Actor的可容错系统关键的组件是减小宕机时间无论是因为应用的错误,仍是因为系统维护。咱们将在第二部分探索JVM的管理,接下来展现一下Actor的热代码交换。
在热代码交换的问题上,有几种方法(例如:JMX,将在第二部分讲)。可是如今咱们经过监控文件系统来实现。首先在项目目录下建立一个叫modules
子文件夹,在build.gradle
的run
添加如下代码:
systemProperty "co.paralleluniverse.actors.moduleDir", "${rootProject.projectDir}/modules"
打开终端,启动程序。程序启动后,回到IDE,修改BadActor
:
@Upgrade static class BadActor extends BasicActor<String, Void> { private int count; @Override protected Void doRun() throws InterruptedException, SuspendExecution { System.out.println("(re)starting actor"); for (;;) { String m = receive(300, TimeUnit.MILLISECONDS); if (m != null) System.out.println("Got a message: " + m); System.out.println("I am a lowly, but improved, actor that still sometimes fails: - " + (count++)); if (ThreadLocalRandom.current().nextInt(100) == 0) throw new RuntimeException("darn"); checkCodeSwap(); // this is a convenient time for a code swap } } }
咱们增长了@Upgrade
注解,由于咱们想让这个类进行升级,这个类修改后失败变少了。如今程序还在运行,新开一个终端,经过gradle jar
,从新构建程序。不熟悉java程序员,JAR(Java Archive)用来打包Java模块(在第二部分会讨论Java打包和部署)。最后,在第二个终端中,复制build/libs/jmodern.jar
到modeules
文件夹中,使用命令:
cp build/libs/jmodern.jar modules
你会看到程序更新运行了(这个时候取决于你的操做系统,大概要十秒)。注意不像咱们在失败后从新启动BadActor
,当咱们交换代码时,程序中的中间变量保存下来了。
设计一个基于Actor设计可容错的系统是一个很大的主题,可是我但愿你已经对它有点感受。
结束以前,咱们将探索一个危险的领域。咱们接下来介绍的工具尚未加入到现代Java开发工具箱中,由于使用它仍然很繁琐,不过它将会从IDE融合中获得好处,如今这个工具仍然很陌生。虽然如此,若是这个工具持继开发而且不断充实,它带来的可能性很是的酷,若是他不会在疯子手中被乱用,它将会很是有价值,这就是为何咱们把它列在这里。
在Java8中,一个潜在最有用的新特性,是类型注解和可拔类型系统。Java编绎器如今容许在任何地方增长对类型的注解(一会咱们举个例子)。这里结合注解预处理器,打发可插拔类型系统。这些是可选的类型系统,能够关闭或打开,能给Java代码够增长强大的基于类型检查的静态验证功能。Checker框架就这样一个库,它容许高级开发者写本身的可插拔类型系统,包括继承,类型接口等。它本身包括了几种类型系统,如检查可空类型,污染类型,正则表达式,物理单位类型,不可变数据等等。
Checker目前还不能很好的与IDE一块儿工做,全部这节,我将不使用IDE。首先修改build.gradle
,增长:
configurations { checker } dependencies { checker 'org.checkerframework:jdk8:1.8.1' compile 'org.checkerframework:checker:1.8.1' }
到相应的configurations
,dependencies
部分。
而后,增长下面部分到构建文件中:
compileJava { options.fork = true options.forkOptions.jvmArgs = ["-Xbootclasspath/p:${configurations.checker.asPath}:${System.getenv('JAVA_HOME')}/lib/tools.jar"] options.compilerArgs = ['-processor', 'org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.units.UnitsChecker,org.checkerframework.checker.tainting.TaintingChecker'] }
正如我说的,笨重的。
最后一行说明咱们使用Checker的空值类型系统,物理单位类型系统,污染数据类型系统。
如今咱们作一些实验。首先,试一下空值类型系统,他能防止空指针的错误。
package jmodern; import org.checkerframework.checker.nullness.qual.*; public class Main { public static void main(String[] args) { String str1 = "hi"; foo(str1); // we know str1 to be non-null String str2 = System.getProperty("foo"); // foo(str2); // <-- doesn't compile as str2 may be null if (str2 != null) foo(str2); // after the null test it compiles } static void foo(@NonNull String s) { System.out.println("==> " + s.length()); } }
Checker的开发者很友好,注解了整个JD可空的返回类型,因此当有@NonNull
注解时,从库中返回值不要返回null
值,。
接下来,咱们试一下单位类型系统,防止单位类型转换错误。
package jmodern; import org.checkerframework.checker.units.qual.*; public class Main { @SuppressWarnings("unsafe") private static final @m int m = (@m int)1; // define 1 meter @SuppressWarnings("unsafe") private static final @s int s = (@s int)1; // define 1 second public static void main(String[] args) { @m double meters = 5.0 * m; @s double seconds = 2.0 * s; // @kmPERh double speed = meters / seconds; // <-- doesn't compile @mPERs double speed = meters / seconds; System.out.println("Speed: " + speed); } }
很是酷吧,根据Checker的文档,你也能够定义本身的物理单位。
最后,试试污染类型系统,它能帮你跟踪被污染(潜在的危险)的数据,例如用户数录入的数据:
package jmodern; import org.checkerframework.checker.tainting.qual.*; public class Main { public static void main(String[] args) { // process(parse(read())); // <-- doesn't compile, as process cannot accept tainted data process(parse(sanitize(read()))); } static @Tainted String read() { return "12345"; // pretend we've got this from the user } @SuppressWarnings("tainting") static @Untainted String sanitize(@Tainted String s) { if(s.length() > 10) throw new IllegalArgumentException("I don't wanna do that!"); return (@Untainted String)s; } // doesn't change the tainted qualifier of the data @SuppressWarnings("tainting") static @PolyTainted int parse(@PolyTainted String s) { return (@PolyTainted int)Integer.parseInt(s); // apparently the JDK libraries aren't annotated with @PolyTainted } static void process(@Untainted int data) { System.out.println("--> " + data); } }
Checker经过类型接口给于Java可插拔交互类型。而且能够经过工具和预编绎库增长类型注解。Haskell都作不到这一点。
Checker尚未到他的黄金时段,若是使用明智的话,它会成为现代Java开发者手中强有力的工具之一。
咱们已经看到了Java8中的变化,还有相应现代的工具和库,Java相对于与旧的版原本说,类似性不高。可是Java仍然是大型应用中的亮点,并且Jva和它的生态圈比新的简单的语言,更为成熟和高效。咱们了解现代Java程序员是怎样写代码的,可是咱们很难一开始就解开Java和Jvm的所有力量。特别当咱们知道了Java的监控和性能分析工具,和新的微应用网络应用开发框架。在接下来的文章中咱们会谈到这几个话题。
假如你想了解一个开头,第二部分,咱们会讨论现代Java打包方法(使用Capsule,有点像npm,可是更酷),监控和管理(使用VisualVM, JMX, Jolokia 和Metrics)
,性能分析(使用 Java Flight Recorder, Mission Control, 和 Byteman),基准测试(JMH)。第三部分,咱们会讨论用Dropwizard,Comsat和Web Actors,JSR-330写一个轻量级可扩展的HTTP服务。
原文地址:Not Your Father's Java: An Opinionated Guide to Modern Java Development, Part 1