Java8 lambda表达式10个示例

Java 8 刚于几周前发布,日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动。特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里。在Java 8之前,如果想将行为传入函数,仅有的选择就是匿名类,需要6行代码。而定义行为最重要的那行代码,却混在中间不够突出。Lambda表达式取代了匿名类,取消了模板,允许用函数式风格编写代码。这样有时可读性更好,表达更清晰。在Java生态系统中,函数式表达与对面向对象的全面支持是个激动人心的进步。将进一步促进并行第三方库的发展,充分利用多核CPU。尽管业界需要时间来消化Java 8,但我认为任何严谨的Java开发者都不应忽视此次Java发布的核心特性,即lambda表达式、函数式接口、流API、默认方法和新的Date以及Time API。作为开发人员,我发现学习和掌握lambda表达式的最佳方法就是勇于尝试,尽可能多练习lambda表达式例子。鉴于受Java 8发布的影响最大的是Java集合框架(Java Collections framework),所以最好练习流API和lambda表达式,用于对列表(Lists)和集合(Collections)数据进行提取、过滤和排序。我一直在进行关于Java 8的写作,过去也曾分享过一些资源来帮助大家掌握Java 8。本文分享在代码中最有用的10个lambda表达式的使用方法,这些例子都短小精悍,将帮助你快速学会lambda表达式。

Java 8 lambda表达式示例

我个人对Java 8发布非常激动,尤其是lambda表达式和流API。越来越多的了解它们,我能写出更干净的代码。虽然一开始并不是这样。第一次看到用lambda表达式写出来的Java代码时,我对这种神秘的语法感到非常失望,认为它们把Java搞得不可读,但我错了。花了一天时间做了一些lambda表达式和流API示例的练习后,我开心的看到了更清晰的Java代码。这有点像学习泛型,第一次见的时候我很讨厌它。我甚至继续使用老版Java 1.4来处理集合,直到有一天,朋友跟我介绍了使用泛型的好处(才意识到它的好处)。所以基本立场就是,不要畏惧lambda表达式以及方法引用的神秘语法,做几次练习,从集合类中提取、过滤数据之后,你就会喜欢上它。下面让我们开启学习Java 8 lambda表达式的学习之旅吧,首先从简单例子开始。

例1、用lambda表达式实现Runnable

我开始使用Java 8时,首先做的就是使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例。看一下Java 8之前的runnable实现方法,需要4行代码,而使用lambda表达式只需要一行代码。我们在这里做了什么呢?那就是用() -> {}代码块替代了整个匿名类

// Java 8之前: new Thread(new Runnable() { @Override public void run() { System.out.println("Before Java8, too much code for too little to do"); } }).start(); //Java 8方式: new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();

输出:

too much code, for too little to do Lambda expression rocks !!

这个例子向我们展示了Java 8 lambda表达式的语法。你可以使用lambda写出如下代码:

(params) -> expression (params) -> statement (params) -> { statements }

例如,如果你的方法不对参数进行修改、重写,只是在控制台打印点东西的话,那么可以这样写:

() -> System.out.println("Hello Lambda Expressions");

如果你的方法接收两个参数,那么可以写成如下这样:

(int even, int odd) -> even + odd

顺便提一句,通常都会把lambda表达式内部变量的名字起得短一些。这样能使代码更简短,放在同一行。所以,在上述代码中,变量名选用a、b或者x、y会比even、odd要好。

例2、使用Java 8 lambda表达式进行事件处理

如果你用过Swing API编程,你就会记得怎样写事件监听代码。这又是一个旧版本简单匿名类的经典用例,但现在可以不这样了。你可以用lambda表达式写出更好的事件监听代码,如下所示:

// Java 8之前: JButton show = new JButton("Show"); show.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("Event handling without lambda expression is boring"); } }); // Java 8方式: show.addActionListener((e) -> { System.out.println("Light, Camera, Action !! Lambda expressions Rocks"); });

Java开发者经常使用匿名类的另一个地方是为 Collections.sort() 定制 Comparator。在Java 8中,你可以用更可读的lambda表达式换掉丑陋的匿名类。我把这个留做练习,应该不难,可以按照我在使用lambda表达式实现 Runnable 和 ActionListener 的过程中的套路来做。

例3、使用lambda表达式对列表进行迭代

如果你使过几年Java,你就知道针对集合类,最常见的操作就是进行迭代,并将业务逻辑应用于各个元素,例如处理订单、交易和事件的列表。由于Java是命令式语言,Java 8之前的所有循环代码都是顺序的,即可以对其元素进行并行化处理。如果你想做并行过滤,就需要自己写代码,这并不是那么容易。通过引入lambda表达式和默认方法,将做什么和怎么做的问题分开了,这意味着Java集合现在知道怎样做迭代,并可以在API层面对集合元素进行并行处理。下面的例子里,我将介绍如何在使用lambda或不使用lambda表达式的情况下迭代列表。你可以看到列表现在有了一个 forEach()  方法,它可以迭代所有对象,并将你的lambda代码应用在其中。

// Java 8之前: List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API"); for (String feature : features) { System.out.println(feature); } // Java 8之后: List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API"); features.forEach(n -> System.out.println(n)); // 使用Java 8的方法引用更方便,方法引用由::双冒号操作符标示, // 看起来像C++的作用域解析运算符 features.forEach(System.out::println);

输出:

Lambdas Default Method Stream API Date and Time API

列表循环的最后一个例子展示了如何在Java 8中使用方法引用(method reference)。你可以看到C++里面的双冒号、范围解析操作符现在在Java 8中用来表示方法引用。

例4、使用lambda表达式和函数式接口Predicate

除了在语言层面支持函数式编程风格,Java 8也添加了一个包,叫做 java.util.function。它包含了很多类,用来支持Java的函数式编程。其中一个便是Predicate,使用 java.util.function.Predicate 函数式接口以及lambda表达式,可以向API方法添加逻辑,用更少的代码支持更多的动态行为。下面是Java 8 Predicate 的例子,展示了过滤集合数据的多种常用方法。Predicate接口非常适用于做过滤。

public static void main(args[]){ List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp"); System.out.println("Languages which starts with J :"); filter(languages, (str)->str.startsWith("J")); System.out.println("Languages which ends with a "); filter(languages, (str)->str.endsWith("a")); System.out.println("Print all languages :"); filter(languages, (str)->true); System.out.println("Print no language : "); filter(languages, (str)->false); System.out.println("Print language whose length greater than 4:"); filter(languages, (str)->str.length() > 4); } public static void filter(List names, Predicate condition) { for(String name: names) { if(condition.test(name)) { System.out.println(name + " "); } } }

输出:

Languages which starts with J : Java Languages which ends with a Java Scala Print all languages : Java Scala C++ Haskell Lisp Print no language : Print language whose length greater than 4: Scala Haskell

更好的办法

public static void filter(List names, Predicate condition) { names.stream().filter((name) -> (condition.test(name))).forEach((name) -> { System.out.println(name + " "); }); }

可以看到,Stream API的过滤方法也接受一个Predicate,这意味着可以将我们定制的 filter() 方法替换成写在里面的内联代码,这就是lambda表达式的魔力。另外,Predicate接口也允许进行多重条件的测试,下个例子将要讲到。

例5、如何在lambda表达式中加入Predicate

上个例子说到,java.util.function.Predicate 允许将两个或更多的 Predicate 合成一个。它提供类似于逻辑操作符AND和OR的方法,名字叫做and()、or()和xor(),用于将传入 filter() 方法的条件合并起来。例如,要得到所有以J开始,长度为四个字母的语言,可以定义两个独立的 Predicate 示例分别表示每一个条件,然后用 Predicate.and() 方法将它们合并起来,如下所示:

// 甚至可以用and()、or()和xor()逻辑函数来合并Predicate, // 例如要找到所有以J开始,长度为四个字母的名字,你可以合并两个Predicate并传入 Predicate<String> startsWithJ = (n) -> n.startsWith("J"); Predicate<String> fourLetterLong = (n) -> n.length() == 4; names.stream() .filter(startsWithJ.and(fourLetterLong)) .forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is : " + n));

类似地,也可以使用 or() 和 xor() 方法。本例着重介绍了如下要点:可按需要将 Predicate 作为单独条件然后将其合并起来使用。简而言之,你可以以传统Java命令方式使用 Predicate 接口,也可以充分利用lambda表达式达到事半功倍的效果。

例6、Java 8中使用lambda表达式的Map和Reduce示例

本例介绍最广为人知的函数式编程概念map。它允许你将对象进行转换。例如在本例中,我们将 costBeforeTax 列表的每个元素转换成为税后的值。我们将 x -> x*x lambda表达式传到 map() 方法,后者将其应用到流中的每一个元素。然后用 forEach() 将列表元素打印出来。使用流API的收集器类,可以得到所有含税的开销。有 toList() 这样的方法将 map 或任何其他操作的结果合并起来。由于收集器在流上做终端操作,因此之后便不能重用流了。你甚至可以用流API的 reduce() 方法将所有数字合成一个,下一个例子将会讲到。

// 不使用lambda表达式为每个订单加上12%的税 List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500); for (Integer cost : costBeforeTax) { double price = cost + .12*cost; System.out.println(price); } // 使用lambda表达式 List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500); costBeforeTax.stream().map((cost) -> cost + .12*cost).forEach(System.out::println);

输出:

112.0 224.0 336.0 448.0 560.0 112.0 224.0 336.0 448.0 560.0 例6.2、Java 8中使用lambda表达式的Map和Reduce示例

在上个例子中,可以看到map将集合类(例如列表)元素进行转换的。还有一个 reduce() 函数可以将所有值合并成一个。Map和Reduce操作是函数式编程的核心操作,因为其功能,reduce 又被称为折叠操作。另外,reduce 并不是一个新的操作,你有可能已经在使用它。SQL中类似 sum()、avg() 或者 count() 的聚集函数,实际上就是 reduce 操作,因为它们接收多个值并返回一个值。流API定义的 reduceh() 函数可以接受lambda表达式,并对所有值进行合并。IntStream这样的类有类似 average()、count()、sum() 的内建方法来做 reduce 操作,也有mapToLong()、mapToDouble() 方法来做转换。这并不会限制你,你可以用内建方法,也可以自己定义。在这个Java 8的Map Reduce示例里,我们首先对所有价格应用 12% 的VAT,然后用 reduce() 方法计算总和。

// 为每个订单加上12%的税 // 老方法: List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500); double total = 0; for (Integer cost : costBeforeTax) { double price = cost + .12*cost; total = total + price; } System.out.println("Total : " + total); // 新方法: List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500); double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get(); System.out.println("Total : " + bill);

输出:

Total : 1680.0 Total : 1680.0 例7、通过过滤创建一个String列表

过滤是Java开发者在大规模集合上的一个常用操作,而现在使用lambda表达式和流API过滤大规模数据集合是惊人的简单。流提供了一个 filter() 方法,接受一个 Predicate 对象,即可以传入一个lambda表达式作为过滤逻辑。下面的例子是用lambda表达式过滤Java集合,将帮助理解。

// 创建一个字符串列表,每个字符串长度大于2 List<String> filtered = strList.stream().filter(x -> x.length()> 2).collect(Collectors.toList()); System.out.printf("Original List : %s, filtered list : %s %n", strList, filtered);

输出:

Original List : [abc, , bcd, , defg, jk], filtered list : [abc, bcd, defg]

另外,关于 filter() 方法有个常见误解。在现实生活中,做过滤的时候,通常会丢弃部分,但使用filter()方法则是获得一个新的列表,且其每个元素符合过滤原则。

例8、对列表的每个元素应用函数

我们通常需要对列表的每个元素使用某个函数,例如逐一乘以某个数、除以某个数或者做其它操作。这些操作都很适合用 map() 方法,可以将转换逻辑以lambda表达式的形式放在 map() 方法里,就可以对集合的各个元素进行转换了,如下所示。

// 将字符串换成大写并用逗号链接起来 List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada"); String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", ")); System.out.println(G7Countries);

输出:

USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA 例9、复制不同的值,创建一个子列表

本例展示了如何利用流的 distinct() 方法来对集合进行去重。

// 用所有不同的数字创建一个正方形列表 List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4); List<Integer> distinct = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList()); System.out.printf("Original List : %s, Square Without duplicates : %s %n", numbers, distinct);

输出:

Original List : [9, 10, 3, 4, 7, 3, 4], Square Without duplicates : [81, 100, 9, 16, 49] 例10、计算集合元素的最大值、最小值、总和以及平均值

IntStream、LongStream 和 DoubleStream 等流的类中,有个非常有用的方法叫做 summaryStatistics() 。可以返回 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistic s,描述流中元素的各种摘要数据。在本例中,我们用这个方法来计算列表的最大值和最小值。它也有 getSum() 和 getAverage() 方法来获得列表的所有元素的总和及平均值。

//获取数字的个数、最小值、最大值、总和以及平均值 List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29); IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("Highest prime number in List : " + stats.getMax()); System.out.println("Lowest prime number in List : " + stats.getMin()); System.out.println("Sum of all prime numbers : " + stats.getSum()); System.out.println("Average of all prime numbers : " + stats.getAverage());

输出:

Highest prime number in List : 29 Lowest prime number in List : 2 Sum of all prime numbers : 129 Average of all prime numbers : 12.9 Lambda表达式 vs 匿名类

既然lambda表达式即将正式取代Java代码中的匿名内部类,那么有必要对二者做一个比较分析。一个关键的不同点就是关键字 this。匿名类的 this 关键字指向匿名类,而lambda表达式的 this 关键字指向包围lambda表达式的类。另一个不同点是二者的编译方式。Java编译器将lambda表达式编译成类的私有方法。使用了Java 7的 invokedynamic 字节码指令来动态绑定这个方法。

Java 8 Lambda表达式要点 10个Java lambda表达式、流API示例

到目前为止我们看到了Java 8的10个lambda表达式,这对于新手来说是个合适的任务量,你可能需要亲自运行示例程序以便掌握。试着修改要求创建自己的例子,达到快速学习的目的。我还想建议大家使用Netbeans IDE来练习lambda表达式,它对Java 8支持良好。当把代码转换成函数式的时候,Netbeans会及时给你提示。只需跟着Netbeans的提示,就能很容易地把匿名类转换成lambda表达式。此外,如果你喜欢阅读,那么记得看一下Java 8的lambdas,实用函数式编程这本书(Java 8 Lambdas, pragmatic functional programming),作者是Richard Warburton,或者也可以看看Manning的Java 8实战(Java 8 in Action),这本书虽然还没出版,但我猜线上有第一章的免费pdf。不过,在你开始忙其它事情之前,先回顾一下Java 8的lambda表达式、默认方法和函数式接口的重点知识。

1)lambda表达式仅能放入如下代码:预定义使用了 @Functional 注释的函数式接口,自带一个抽象函数的方法,或者SAM(Single Abstract Method 单个抽象方法)类型。这些称为lambda表达式的目标类型,可以用作返回类型,或lambda目标代码的参数。例如,若一个方法接收Runnable、Comparable或者 Callable 接口,都有单个抽象方法,可以传入lambda表达式。类似的,如果一个方法接受声明于 java.util.function 包内的接口,例如 Predicate、Function、Consumer 或 Supplier,那么可以向其传lambda表达式。

2)lambda表达式内可以使用方法引用,仅当该方法不修改lambda表达式提供的参数。本例中的lambda表达式可以换为方法引用,因为这仅是一个参数相同的简单方法调用。

list.forEach(n -> System.out.println(n)); list.forEach(System.out::println); // 使用方法引用

然而,若对参数有任何修改,则不能使用方法引用,而需键入完整地lambda表达式,如下所示:

list.forEach((String s) -> System.out.println("*" + s + "*"));

事实上,可以省略这里的lambda参数的类型声明,编译器可以从列表的类属性推测出来。

3)lambda内部可以使用静态、非静态和局部变量,这称为lambda内的变量捕获。

4)Lambda表达式在Java中又称为闭包或匿名函数,所以如果有同事把它叫闭包的时候,不用惊讶。

5)Lambda方法在编译器内部被翻译成私有方法,并派发 invokedynamic 字节码指令来进行调用。可以使用JDK中的 javap 工具来反编译class文件。使用 javap -p 或 javap -c -v 命令来看一看lambda表达式生成的字节码。大致应该长这样:

private static java.lang.Object lambda$0(java.lang.String);

6)lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量。

List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7}); int factor = 2; primes.forEach(element -> { factor++; }); Compile time error : "local variables referenced from a lambda expression must be final or effectively final"

另外,只是访问它而不作修改是可以的,如下所示:

List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7}); int factor = 2; primes.forEach(element -> { System.out.println(factor*element); });

输出:

4 6 10 14

因此,它看起来更像不可变闭包,类似于Python。

以上就是Java 8的lambda表达式的全部10个例子。此次修改将成为Java史上最大的一次,将深远影响未来Java开发者使用集合框架的方式。我想规模最相似的一次修改就是Java 5的发布了,它带来了很多优点,提升了代码质量,例如:泛型、枚举、自动装箱(Autoboxing)、静态导入、并发API和变量参数。上述特性使得Java代码更加清晰,我想lambda表达式也将进一步改进它。我在期待着开发并行第三方库,这可以使高性能应用变得更容易写。

更多阅读:http://javarevisited.blogspot.com/2014/02/10-example-of-lambda-expressions-in-java8.html#ixzz3gCMp6Vhc

原文链接: javarevisited 翻译: ImportNew.com - lemeilleur
译文链接: http://www.importnew.com/16436.html

[ 转载请保留原文出处、译者和译文链接。]

阅读更多
Java NIO(3) 选择器 Selector

Selector 是Java NIO的重要组件之一,它可以检查一个或多个通道(Channel),并确定哪个通道进入准备好的状态,比如:准备读或者准备写数据。通过Selector 这种方式,单个线程可以管理多个通道,从而实现处理多个网络连接。

为什么用选择器(Selector)?

仅使用单个线程来处理多个通道的优点是,可以需要较少的线程来处理通道。实际上,你可以只使用一个线程来处理所有的通道。对于操作系统来说,在线程之间切换成本很高,而且每个线程都占用了操作系统中的一些资源(内存)。因此,使用的线程越少越好。

现代操作系统和CPU在多任务处理方面变得越来越好,因此随着时间的推移,多线程的开销变得越来越小。如果一个CPU有多个内核,那么如果不同时处理多个任务,反而会浪费CPU资源。但是请了解,设计问题不属于本文讨论范畴。在这里,你只需要了解,使用一个选择器,你可以通过一个线程来处理多个通道。

下图是一个线程处理3个通道的示意图:

创建选择器

通过调用selector. open()方法创建一个选择器,如下所示:

Selector selector = Selector.open(); 使用选择器的注册通道

为了让选择器使用通道,你必须在选择器上注册通道。你可以使用SelectableChannel.register() 方法来注册,如下:

channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

注意, 只有非阻塞模式的通道才可以使用选择器。这意味着,因为 FileChannel 不能切换成非阻塞模式,所以不能使用选择器。Socket的通道 则可以正常工作。

注意 register() 方法的第二个参数。这是一个“兴趣集”,意思是你想通过选择器在通道中监听的事件。你可以监听下面四个不同的事件:

Connect
Accept
Read
Write

一个通道“触发一个事件”称之为“就绪”。所以,一个通道成功连接到另一台服务器,这里称之为“连接(Connect)就绪”;服务器套接字通道( socket channel )接收了来自客户端的连接,称之为“接收(Accept)就绪”;一个可以读取数据的通道是“读(Read)”就绪;一个可以写数据的通道是“写(Write)”就绪。

这四个事件由四个 SelectionKey 常量表示:

SelectionKey.OP_CONNECT SelectionKey.OP_ACCEPT SelectionKey.OP_READ SelectionKey.OP_WRITE

如果你对不止一个事件感兴趣,可以通过 “OR” 把常量放在一起,像这样:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; SelectionKey

正如你在上一节中看到的,当您使用选择器注册一个通道时,register()方法将返回一个SelectionKey对象。这个SelectionKey对象包含一些有趣的属性:

兴趣集 interest set
就绪集 ready set
通道Channel
选择器Selector
一个连接对象(可选)

Interest Set

兴趣集是你感兴趣的“选择”的事件集合,如“使用选择器的注册通道”中所描述的,你可以通过下面这样的写法来判断趣集:

int interestSet = selectionKey.interestOps(); boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE; Ready Set

已就绪的集合是通道已准备就绪的操作集合。在“选择(selection)”之后,你将主要访问已就绪的集合。“选择(selection)” 将在后面的部分中解释。你可以像这样访问就绪集:

int readySet = selectionKey.readyOps();

你可以用类似判断兴趣集的方式来判断哪个渠道的事件或操作已经就绪,也可以通过下面的四个方法来判断:

selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable(); 获取 Channel 和 Selector Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector(); 附加对象

你可以将一个对象附加到SelectionKey,这是用于识别特定的通道的简便方法,或者将更多的信息附加到通道上。例如,你可以附加使用通道的缓冲区,或者一个包含更多聚合数据的对象。下面是如何附加对象:

selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment();

你还可以在使用选择器注册通道的时候,同时附加一个对象。像下面这样:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject); 通过选择器(Selector)选择通道

一旦你使用选择器(Selector)注册了一个或多个通道,你就可以调用Selector中的 select() 方法。这个方法返回对你所感兴趣的事件(connect, accept, read or write) 已经就绪的通道。比如,如果你对已经准备好read的通道感兴趣,那么你将可以接收准备好read的通道。

select()方法有三个:

int select() //阻塞,直到至少一个通道的事件且是你注册的事件就绪 int select(long timeout) // 与select() 一样阻塞,直到达到了超时的时间(毫秒数)为止 int selectNow()//不阻塞,它会立即返回任何准备好的通道

select()方法返回的 int 表示多少通道准备好了。也就是说,自上次调用select()以来,有多少通道已经准备好了。如果你调用select(),它返回1,表示一个通道已经准备好了。如果你再次调用select()一次,此时另外一个通道已经就绪,它将再次返回1。如果你没有处理第一次调用select()时准备好的通道,那么此时实际上有两个就绪通道,但是每次select()调用,只有一个通道已经准备好了。

selectedKeys()

一旦你调用 select() 方法返回大于0,表明一个或多个通道已经就绪,你就可以通过调用选择器的selectedKeys()方法,得到“SelectionKey”集合,通过集合访问就绪的通道。代码:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

当你使用选择器注册一个通道时,Channel.register()方法将返回一个SelectionKey对象。这个SelectionKey对象就代表了该选择器注册的一个通道。你可以通过selectedKeySet()方法得到这些SelectionKey。例子:

Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. SocketChannel client = (SocketChannel) key.channel(); } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); }

注意在每次迭代结束时调用 keyIterator.remove() 。选择器不会从SelectionKey集合删除SelectionKey实例。当你处理通道时,你需要手动这样做。当通道再次变为其他“就绪”状态时,选择器会再次将它(Channel)添加到所选的键集中。

调用SelectionKey.channel()方法返回通道。你需要自己转换成实际的类型使用,比如:ServerSocketChannel 或 SocketChannel 。

wakeUp()

如果一个线程调用了select() 方法,这个线程阻塞了。那么你可以通过另外一个线程调用Selector.wakeup() 方法,来使这个阻塞的线程立刻返回,即使还没有就绪的Channel事件。

如果一个线程调用了Selector.wakeup() 方法,且当前没有其他因为调用select() 方法而阻塞的线程。那么当下一次某个线程调用select()方法的时候,它将立即返回。

close()

当选择器调用 close(),选择器所以的SelectionKey 将失效,对应的渠道则不会关闭。

完整的示例代码:

Selector selector = Selector.open(); channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ); while(true) { int readyChannels = selector.select(); if(readyChannels == 0) continue; Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); } }

阅读更多
Learn Gradle - 3 Java 快速入门

上一节主要对Gradle的脚本进行了简要的介绍,本节将继续学习Gradle的另外一个特性——插件(plugins)。

1、插件介绍

插件是对Gradle功能的扩展,Gradle有着丰富的插件,你可以在这里搜索相关插件(传送门)。本章将简要介绍Gradle的Java插件(Java plugin),这个插件会给你的构建项目添加一些任务,比如编译java类、执行单元测试和将编译的class文件打包成jar文件等。

Java插件是基于约定的(约定优于配置),它在项目的很多方面定义了默认值,例如,Java源文件应该位于什么位置。我们只要遵循插件的约定,就不需要在Gradle配置脚本进行额外的相关配置。当然,在某些情况下,你的项目不想或不能遵循这个约定也是可以的,这样你就需要额外的配置你的构建脚本。

Gradle Java插件对于项目文件存放的默认位置与maven类似。

<!--more-->

Java源码存放在目录:src/main/java

Java测试代码存放目录:src/test/java

资源文件存放目录:src/main/resources

测试相关资源文件存放目录:src/test/resources

所有输出文件位于目录:build

输出的jar文件位于目录:build/libs

2、一个简单的Java项目

新建一个文件build.gradle,添加代码:

apply plugin: 'java'

以上代码即配置java插件到构建脚本中。当执行构建脚本时,它将给项目添加一系列任务。我们执行:gradle build,来看看输出的结果:

:compileJava UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :jar UP-TO-DATE :assemble UP-TO-DATE :compileTestJava UP-TO-DATE :processTestResources UP-TO-DATE :testClasses UP-TO-DATE :test UP-TO-DATE :check UP-TO-DATE :build UP-TO-DATE BUILD SUCCESSFUL

根据输出结果可以看出,我们执行的build这个任务依赖其他任务,比如compileJava等,这就是java插件预先定义好的一系列任务。

你还可以执行一些其他的任务,比如执行:gradle clean,gradle assemble,gradle check等。

gradle clean:删除构建目录以及已经构建完成的文件;

gradle assemble(装配):编译和打包java代码,但是不会执行单元测试(从上面的任务依赖结果也可以看出来)。如果你应用了其他插件,那么还会完成一下其他动作。例如,如果你应用了War这个插件,那么这个任务将会为你的项目生成war文件。

gradle check:编译且执行测试。与assemble类似,如果你应用了其他包含check任务的插件,例如,Checkstyle插件,那么这个任务将会检查你的项目代码的质量,并且生成检测报告。

如果想知道Gradle当前配置下哪些任务可执行,可以执行:gradle tasks,例如应用了java插件的配置,执行该命令,输出:

:tasks ------------------------------------------------------------ All tasks runnable from root project ------------------------------------------------------------ Build tasks ----------- assemble - Assembles the outputs of this project. build - Assembles and tests this project. buildDependents - Assembles and tests this project and all projects that depend on it. buildNeeded - Assembles and tests this project and all projects it depends on. classes - Assembles classes 'main'. clean - Deletes the build directory. jar - Assembles a jar archive containing the main classes. testClasses - Assembles classes 'test'. Build Setup tasks ----------------- init - Initializes a new Gradle build. [incubating] wrapper - Generates Gradle wrapper files. [incubating] Documentation tasks ------------------- javadoc - Generates Javadoc API documentation for the main source code. Help tasks ---------- components - Displays the components produced by root project 'learn-gradle'. [incubating] dependencies - Displays all dependencies declared in root project 'learn-gradle'. dependencyInsight - Displays the insight into a specific dependency in root project 'learn-gradle'. help - Displays a help message. model - Displays the configuration model of root project 'learn-gradle'. [incubating] projects - Displays the sub-projects of root project 'learn-gradle'. properties - Displays the properties of root project 'learn-gradle'. tasks - Displays the tasks runnable from root project 'learn-gradle'. Verification tasks ------------------ check - Runs all checks. test - Runs the unit tests. Rules ----- Pattern: clean<TaskName>: Cleans the output files of a task. Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration. Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration. To see all tasks and more detail, run gradle tasks --all To see more detail about a task, run gradle help --task <task> BUILD SUCCESSFUL

小伙伴们看到这里会不会有疑问,如果在构建脚本中定义了名为tasks的任务,执行会是如何?好奇的小伙伴可以自己试一试噢。事实上,是会覆盖原有的任务的。

3、外部依赖

通常一个Java项目会依赖多个其他项目或jar文件,我们可以通过配置gradle仓库(repository)告诉gradle从哪里获取需要的依赖,并且gradle还可以配置使用maven仓库。例如,我们配置gradle使用maven中央仓库,在build.gradle中添加代码:

repositories { mavenCentral() }

接下来,我们来添加一些依赖。代码示例:

dependencies { compile group: 'commons-collections', name: 'commons-collections', version: '3.2' testCompile group: 'junit', name: 'junit', version: '4.+' }

关于依赖,暂时就点这么多。详细可以参考gradle依赖管理基础,也可以关注后续文章。

4、定义项目属性

Java插件会为项目添加一系列的属性,通常情况下,初始的Java项目使用这些默认配置就足够了,我们不需要进行额外的配置。但是如果默认属性不满足于你的项目,你也可以进行自定义项目的一些信息。例如我们为项目指定版本号和一些jar manifest信息。

sourceCompatibility = 1.5 version = '1.0' jar { manifest { attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version } }

事实上,Java插件添加的一系列任务与我们之前在脚本中自定义的任务没什么区别,都是很常规的任务。我们可以随意定制和修改这些任务。例如,设置任务的属性、为任务添加行为、改变任务的依赖,甚至替换已有的任务。例如我们可以配置Test类型的test任务,当test任务执行的时候,添加一个系统属性。配置脚本如下:

test { systemProperties 'property': 'value' }

另外,与之前提到的“gradle tasks”命令类型,我们可以通过“gradle properties”来查看当前配置所支持的可配置属性有哪些。

5、将Jar文件发布到仓库 uploadArchives { repositories { flatDir { dirs 'repos' } } }

执行gradle uploadArchives,将会把相关jar文件发布到reops仓库中。更多参考:Publishing artifacts

6、构建多个Java项目

假设我们的项目结构如下所示:

multiproject/ --api/ --services/webservice/ --shared/ --services/shared/

项目api生成jar文件,Java客户端通过jar提供的接口访问web服务;项目services/webservice是一个webapp,提供web服务;项目shared 包含api和webservice公共代码;项目services/shared依赖shared项目,包含webservice公共代码。

接下来,我们开始定义多项目构建。

1)首先,我们需要添加一个配置文件:settings.gradle文件。settings.gradle位于项目的根目录,也就是multiproject目录。编辑settings.gradle,输入配置信息:

include "shared", "api", "services:webservice", "services:shared"

include是Gradle DSL定义的核心类型Settings的方法,用于构建指定项目。配置中指定的参数“shared”、“api”等值默认是当前配置目录的目录名称,而“services:webservice”将根据默认约定映射系统物理路径"services/webservice"(相对于根目录)。关于include更详细的信息可以参考:构建树

2)定义所有子项目公用配置。在根目录创建文件:build.gradle,输入配置信息:

subprojects { apply plugin: 'java' apply plugin: 'eclipse-wtp' repositories { mavenCentral() } dependencies { testCompile 'junit:junit:4.12' } version = '1.0' jar { manifest.attributes provider: 'gradle' } }

subprojects 是Gradle DSL定义的构建脚本模块之一,用于定义所有子项目的配置信息。在以上配置中,我们给所有子项目定义了使用“java”和“eclipse-wtp”插件,还定义了仓库、依赖、版本号以及jar(jar是Gradle的任务类型之一,任务是装配jar包,jar任务包含属性manifest,用于描述jar的信息,具体参考:Jar)。

我们在根目录执行gradle build命令时,这些配置会应用到所有子项目中。

3)给项目添加依赖

新建文件:api/build.gradle,添加配置:

dependencies { compile project(':shared') }

以上,我们定义了api项目依赖shared项目,当我们在根目录执行gradle build命令时,gradle会确保在编译api之前,先完成shared项目编译,然后才会编译api项目。

同样,添加services/webservice/build.gradle,添加配置:

dependencies { compile project(':services:shared') }

在根目录执行:gradle compileJava,输出:

:shared:compileJava UP-TO-DATE :shared:processResources UP-TO-DATE :shared:classes UP-TO-DATE :shared:jar UP-TO-DATE :api:compileJava UP-TO-DATE :services:compileJava UP-TO-DATE :services:shared:compileJava UP-TO-DATE :services:shared:processResources UP-TO-DATE :services:shared:classes UP-TO-DATE :services:shared:jar UP-TO-DATE :services:webservice:compileJava UP-TO-DATE BUILD SUCCESSFUL

通过输出信息我们就可以清楚看出依赖配置是否正确啦。

阅读更多
Java 经典设计模式(3):十一种行为型模式

行为型模式细分为如下11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
接下来对11种行为型模式逐个进行介绍。

一、策略模式

策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类,视实际需求是否添加),提供辅助函数。

首先统一接口:

package com.model.behaviour; public interface ICalculator { public int calculate(String exp); }

辅助类:

package com.model.behaviour; public abstract class AbstractCalculator { public int[] split(String exp, String opt) { String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; } }

<!--more-->
三个实现类:

package com.model.behaviour; public class Plus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp, "\\+"); return arrayInt[0] + arrayInt[1]; } } package com.model.behaviour; public class Minus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp, "\\-"); return arrayInt[0] - arrayInt[1]; } } package com.model.behaviour; public class Multiply extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"\\*"); return arrayInt[0]*arrayInt[1]; } }

测试类:

package com.model.behaviour; public class StrategyTest { public static void main(String[] args) { String exp = "8-2"; ICalculator cal = new Minus(); int result = cal.calculate(exp); System.out.println(exp + "=" + result); } }

策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。

二、模板方法模式

解释一下模板方法模式,就是指:一个抽象类中,有一个主方法,再定义1…n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用。

就是在AbstractCalculator类中定义一个主方法calculate,calculate()调用spilt()等,Plus和Minus分别继承AbstractCalculator类,通过对AbstractCalculator的调用实现对子类的调用,看下面的例子:

package com.model.behaviour; public abstract class AbstractCalculator { /*主方法,实现对本类其它方法的调用*/ public final int calculate(String exp,String opt){ int array[] = split(exp,opt); return calculate(array[0],array[1]); } /*被子类重写的方法*/ abstract public int calculate(int num1,int num2); public int[] split(String exp,String opt){ String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; } } package com.model.behaviour; public class Plus extends AbstractCalculator { @Override public int calculate(int num1,int num2) { return num1 + num2; } } package com.model.behaviour; public class StrategyTest { public static void main(String[] args) { String exp = "8+8"; AbstractCalculator cal = new Plus(); int result = cal.calculate(exp, "\\+"); System.out.println(result); } } 三、观察者模式

包括这个模式在内的接下来的四个模式,都是类和类之间的关系,不涉及到继承。

观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。

package com.model.behaviour; public interface Observer { public void update(); } ``` java package com.model.behaviour; public class Observer1 implements Observer { @Override public void update() { System.out.println("observer1 has received!"); } } package com.model.behaviour; public class Observer2 implements Observer { @Override public void update() { System.out.println("observer2 has received!"); } } package com.model.behaviour; public interface Subject { /*增加观察者*/ public void add(Observer observer); /*删除观察者*/ public void del(Observer observer); /*通知所有的观察者*/ public void notifyObservers(); /*自身的操作*/ public void operation(); } package com.model.behaviour; import java.util.Enumeration; import java.util.Vector; public abstract class AbstractSubject implements Subject { private Vector<Observer> vector = new Vector<Observer>(); @Override public void add(Observer observer) { vector.add(observer); } @Override public void del(Observer observer) { vector.remove(observer); } @Override public void notifyObservers() { Enumeration<Observer> enumo = vector.elements(); while(enumo.hasMoreElements()){ enumo.nextElement().update(); } } } package com.model.behaviour; public class MySubject extends AbstractSubject { @Override public void operation() { System.out.println("update self!"); notifyObservers(); } } package com.model.behaviour; public class ObserverTest { public static void main(String[] args) { Subject sub = new MySubject(); sub.add(new Observer1()); sub.add(new Observer2()); sub.operation(); } }

运行结果:

update self! observer1 has received! observer2 has received!

也许看完实例之后还是比较抽象,再将文字描述和代码实例看一两遍吧,然后结合工作中看哪些场景可以使用这种模式以加深理解。

四、迭代子模式

顾名思义,迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。

具体来看看代码实例:

package com.model.behaviour; public interface Collection { public Iterator iterator(); /* 取得集合元素 */ public Object get(int i); /* 取得集合大小 */ public int size(); } package com.model.behaviour; public interface Iterator { // 前移 public Object previous(); // 后移 public Object next(); public boolean hasNext(); // 取得第一个元素 public Object first(); } package com.model.behaviour; public class MyCollection implements Collection { public String string[] = { "A", "B", "C", "D", "E" }; @Override public Iterator iterator() { return new MyIterator(this); } @Override public Object get(int i) { return string[i]; } @Override public int size() { return string.length; } } package com.model.behaviour; public class MyIterator implements Iterator { private Collection collection; private int pos = -1; public MyIterator(Collection collection){ this.collection = collection; } @Override public Object previous() { if(pos > 0){ pos--; } return collection.get(pos); } @Override public Object next() { if(pos<collection.size()-1){ pos++; } return collection.get(pos); } @Override public boolean hasNext() { if(pos<collection.size()-1){ return true; }else{ return false; } } @Override public Object first() { pos = 0; return collection.get(pos); } } package com.model.behaviour; public class Test { public static void main(String[] args) { Collection collection = new MyCollection(); Iterator it = (Iterator) collection.iterator(); while(it.hasNext()){ System.out.println(it.next()); } } }

输出结果:

A B C D E

此处我们貌似模拟了一个集合类的过程,感觉是不是很爽?其实JDK中各个类也都是这些基本的东西,加一些设计模式,再加一些优化放到一起的,只要我们把这些东西学会了,掌握好了,我们也可以写出自己的集合类,甚至框架!

五、责任链模式

责任链模式,有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。

package com.model.behaviour; public interface Handler { public void operator(); } package com.model.behaviour; public abstract class AbstractHandler { private Handler handler; public Handler getHandler() { return handler; } public void setHandler(Handler handler) { this.handler = handler; } } package com.model.behaviour; public class MyHandler extends AbstractHandler implements Handler { private String name; public MyHandler(String name) { this.name = name; } @Override public void operator() { System.out.println(name + "deal!"); if (getHandler() != null) { getHandler().operator(); } } } package com.model.behaviour; public class Test { public static void main(String[] args) { MyHandler h1 = new MyHandler("h1"); MyHandler h2 = new MyHandler("h2"); MyHandler h3 = new MyHandler("h3"); h1.setHandler(h2); h2.setHandler(h3); h1.operator(); } }

运行结果:

h1deal! h2deal! h3deal!

此处强调一点就是,链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。

六、命令模式

命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。

package com.model.behaviour; public interface Command { public void exe(); } package com.model.behaviour; public class MyCommand implements Command { private Receiver receiver; public MyCommand(Receiver receiver) { this.receiver = receiver; } @Override public void exe() { receiver.action(); } } package com.model.behaviour; public class Invoker { private Command command; public Invoker(Command command) { this.command = command; } public void action() { command.exe(); } } package com.model.behaviour; public class Test { public static void main(String[] args) { Receiver receiver = new Receiver(); Command cmd = new MyCommand(receiver); Invoker invoker = new Invoker(cmd); invoker.action(); } }

命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开,熟悉Struts的同学应该知道,Struts其实就是一种将请求和呈现分离的技术,其中必然涉及命令模式的思想!

七、备忘录模式

主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象,个人觉得叫备份模式更形象些,通俗的讲下:假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。

package com.model.behaviour; public class Original { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public Original(String value) { this.value = value; } public Memento createMemento(){ return new Memento(value); } public void restoreMemento(Memento memento){ this.value = memento.getValue(); } } package com.model.behaviour; public class Memento { private String value; public Memento(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } package com.model.behaviour; public class Storage { private Memento memento; public Storage(Memento memento) { this.memento = memento; } public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } } package com.model.behaviour; public class Test { public static void main(String[] args) { // 创建原始类 Original origi = new Original("egg"); // 创建备忘录 Storage storage = new Storage(origi.createMemento()); // 修改原始类的状态 System.out.println("初始化状态为:" + origi.getValue()); origi.setValue("niu"); System.out.println("修改后的状态为:" + origi.getValue()); // 回复原始类的状态 origi.restoreMemento(storage.getMemento()); System.out.println("恢复后的状态为:" + origi.getValue()); } }

输出结果:

初始化状态为:egg 修改后的状态为:niu 恢复后的状态为:egg 如果还不能理解,可以给Original类添加一个属性name,然后其他类进行相应的修改试试。 八、状态模式

核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。

package com.model.behaviour; public class State { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public void method1(){ System.out.println("execute the first opt!"); } public void method2(){ System.out.println("execute the second opt!"); } } package com.model.behaviour; public class Context { private State state; public Context(State state) { this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } public void method() { System.out.println("状态为:" + state.getValue()); if (state.getValue().equals("state1")) { state.method1(); } else if (state.getValue().equals("state2")) { state.method2(); } } } package com.model.behaviour; public class Test { public static void main(String[] args) { State state = new State(); Context context = new Context(state); //设置第一种状态 state.setValue("state1"); context.method(); //设置第二种状态 state.setValue("state2"); context.method(); } }

运行结果:

状态为:state1 execute the first opt! 状态为:state2 execute the second opt!

根据这个特性,状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。

九、访问者模式

访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。

访问者模式算是最复杂也是最难以理解的一种模式了。它表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

涉及角色:

1.Visitor 抽象访问者角色,为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色,这样访问者就可以通过该元素角色的特定接口直接访问它。

2.ConcreteVisitor.具体访问者角色,实现Visitor声明的接口。

3.Element 定义一个接受访问操作(accept()),它以一个访问者(Visitor)作为参数。

4.ConcreteElement 具体元素,实现了抽象元素(Element)所定义的接受操作接口。

5.ObjectStructure 结构对象角色,这是使用访问者模式必备的角色。它具备以下特性:能枚举它的元素;可以提供一个高层接口以允许访问者访问它的元素;如有需要,可以设计成一个复合对象或者一个聚集(如一个列表或无序集合)。

abstract class Element { public abstract void accept(IVisitor visitor); public abstract void doSomething(); } class ConcreteElement1 extends Element{ public void doSomething(){ System.out.println("这是元素1"); } public void accept(IVisitor visitor){ visitor.visit(this); } } class ConcreteElement2 extends Element{ public void doSomething(){ System.out.println("这是元素2"); } public void accept(IVisitor visitor){ visitor.visit(this); } } interface IVisitor{ public void visit(ConcreteElement1 el1); public void visit(ConcreteElement2 el2); } class Visitor implements IVisitor{ public void visit(ConcreteElement1 el1){ el1.doSomething(); } public void visit(ConcreteElement2 el2){ el2.doSomething(); } } class ObjectStruture{ public static List<Element> getList(){ List<Element>list = new ArrayList<Element>(); Random ran = newRandom(); for(int i = 0 ; i < 10 ; i ++){ int a=ran.nextInt(100); if(a>50){ list.add(new ConcreteElement1()); }else{ list.add(new ConcreteElement2()); } } return list; } } public class Client{ public static void main (String[]args){ List<Element> list = ObjectStruture.getList(); for(Element e:list){ e.accept(new Visitor()); } } } 十、中介者模式

中介者模式(Mediator):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

举例:在一个公司里面,有很多部门、员工(我们统称他们互相为Colleague“同事”),为了完成一定的任务,“同事”之间肯定有许多需要互相配合、交流的过程。如果由各个“同事”频繁地到处去与自己有关的“同事”沟通,这样肯定会形成一个多对多的杂乱的联系网络而造成工作效率低下。

此时就需要一位专门的“中介者”给各个“同事”分配任务,以及统一跟进大家的进度并在“同事”之间实时地进行交互,保证“同事”之间必须的沟通交流。很明显我们知道此时的“中介者”担任了沟通“同事”彼此之间的重要角色了,“中介者”使得每个“同事”都变成一对一的联系方式,减轻了每个“同事”的负担,增强工作效率。

同事类族:

package com.model.behaviour; public abstract class AbstractColleague { protected AbstractMediator mediator; /**既然有中介者,那么每个具体同事必然要与中介者有联系, * 否则就没必要存在于 这个系统当中,这里的构造函数相当 * 于向该系统中注册一个中介者,以取得联系 */ public AbstractColleague(AbstractMediator mediator) { this.mediator = mediator; } // 在抽象同事类中添加用于与中介者取得联系(即注册)的方法 public void setMediator(AbstractMediator mediator) { this.mediator = mediator; } } //具体同事A package com.model.behaviour; public class ColleagueA extends AbstractColleague { //每个具体同事都通过父类构造函数与中介者取得联系 public ColleagueA(AbstractMediator mediator) { super(mediator); } //每个具体同事必然有自己分内的事,没必要与外界相关联 public void self() { System.out.println("同事A --> 做好自己分内的事情 ..."); } //每个具体同事总有需要与外界交互的操作,通过中介者来处理这些逻辑并安排工作 public void out() { System.out.println("同事A --> 请求同事B做好分内工作 ..."); super.mediator.execute("ColleagueB", "self"); } } //具体同事B package com.model.behaviour; public class ColleagueB extends AbstractColleague { public ColleagueB(AbstractMediator mediator) { super(mediator); } public void self() { System.out.println("同事B --> 做好自己分内的事情 ..."); } public void out() { System.out.println("同事B --> 请求同事A做好分内工作 ..."); super.mediator.execute("ColleagueA", "self"); } }

中介者类族:

package com.model.behaviour; public abstract class AbstractMediator { //中介者肯定需要保持有若干同事的联系方式 protected Hashtable<String, AbstractColleague> colleagues = new Hashtable<String, AbstractColleague>(); //中介者可以动态地与某个同事建立联系 public void addColleague(String name, AbstractColleague c) { this.colleagues.put(name, c); } //中介者也可以动态地撤销与某个同事的联系 public void deleteColleague(String name) { this.colleagues.remove(name); } //中介者必须具备在同事之间处理逻辑、分配任务、促进交流的操作 public abstract void execute(String name, String method); } //具体中介者 package com.model.behaviour; public class Mediator extends AbstractMediator{ //中介者最重要的功能,来回奔波与各个同事之间 public void execute(String name, String method) { if("self".equals(method)){ //各自做好分内事 if("ColleagueA".equals(name)) { ColleagueA colleague = (ColleagueA)super.colleagues.get("ColleagueA"); colleague.self(); }else { ColleagueB colleague = (ColleagueB)super.colleagues.get("ColleagueB"); colleague.self(); } }else { //与其他同事合作 if("ColleagueA".equals(name)) { ColleagueA colleague = (ColleagueA)super.colleagues.get("ColleagueA"); colleague.out(); }else { ColleagueB colleague = (ColleagueB)super.colleagues.get("ColleagueB"); colleague.out(); } } } }

测试类:

//测试类 package com.model.behaviour; public class Client { public static void main(String[] args) { //创建一个中介者 AbstractMediator mediator = new Mediator(); //创建两个同事 ColleagueA colleagueA = new ColleagueA(mediator); ColleagueB colleagueB = new ColleagueB(mediator); //中介者分别与每个同事建立联系 mediator.addColleague("ColleagueA", colleagueA); mediator.addColleague("ColleagueB", colleagueB); //同事们开始工作 colleagueA.self(); colleagueA.out(); System.out.println("======================合作愉快,任务完成!\n"); colleagueB.self(); colleagueB.out(); System.out.println("======================合作愉快,任务完成!"); } }

运行结果:

同事A --> 做好自己分内的事情 ... 同事A --> 请求同事B做好分内工作 ... 同事B --> 做好自己分内的事情 ... ======================合作愉快,任务完成! 同事B --> 做好自己分内的事情 ... 同事B --> 请求同事A做好分内工作 ... 同事A --> 做好自己分内的事情 ... ======================合作愉快,任务完成! 十一、解释器模式

解释器模式:给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子。

解释器模式是一个比较少用的模式。

package com.model.behaviour; public class Context { private int num1; private int num2; public Context(int num1, int num2) { this.num1 = num1; this.num2 = num2; } public int getNum1() { return num1; } public void setNum1(int num1) { this.num1 = num1; } public int getNum2() { return num2; } public void setNum2(int num2) { this.num2 = num2; } } package com.model.behaviour; public interface Expression { public int interpret(Context context); } package com.model.behaviour; public class Minus implements Expression { @Override public int interpret(Context context) { return context.getNum1()-context.getNum2(); } } package com.model.behaviour; public class Plus implements Expression { @Override public int interpret(Context context) { return context.getNum1()+context.getNum2(); } } package com.model.behaviour; public class Test { public static void main(String[] args) { // 计算9+2-8的值 int result = new Minus().interpret((new Context(new Plus() .interpret(new Context(9, 2)), 8))); System.out.println(result); } }

原文:http://blog.csdn.net/u013142781/article/details/50825301

阅读更多