diff --git a/axon-cqrs-example/.gitignore b/axon-cqrs-example/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..2af7cefb0a3f1e7df2fc27b8421f0e16b460e680
--- /dev/null
+++ b/axon-cqrs-example/.gitignore
@@ -0,0 +1,24 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
\ No newline at end of file
diff --git a/axon-cqrs-example/.mvn/wrapper/maven-wrapper.jar b/axon-cqrs-example/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..9cc84ea9b4d95453115d0c26488d6a78694e0bc6
Binary files /dev/null and b/axon-cqrs-example/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/axon-cqrs-example/.mvn/wrapper/maven-wrapper.properties b/axon-cqrs-example/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000000000000000000000000000000000000..c315043703752ef4d11cf7d93f2c324852b2ebff
--- /dev/null
+++ b/axon-cqrs-example/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
diff --git a/axon-cqrs-example/mvnw b/axon-cqrs-example/mvnw
new file mode 100755
index 0000000000000000000000000000000000000000..5bf251c0774593ca4f5335acf0f7483eaa162e8f
--- /dev/null
+++ b/axon-cqrs-example/mvnw
@@ -0,0 +1,225 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Migwn, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+echo $MAVEN_PROJECTBASEDIR
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/axon-cqrs-example/mvnw.cmd b/axon-cqrs-example/mvnw.cmd
new file mode 100644
index 0000000000000000000000000000000000000000..019bd74d766ebd4c033528112148d866555b5c9e
--- /dev/null
+++ b/axon-cqrs-example/mvnw.cmd
@@ -0,0 +1,143 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/axon-cqrs-example/pom.xml b/axon-cqrs-example/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ef90ef4e9f131d8c08865e88becf6d473c551677
--- /dev/null
+++ b/axon-cqrs-example/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+ com.brook.example
+ axon-cqrs-example
+ 1.0-SNAPSHOT
+ jar
+ axon-cqrs-example
+ Demo project for Spring Boot
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.4.RELEASE
+
+
+
+
+ UTF-8
+ UTF-8
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ mysql
+ mysql-connector-java
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
diff --git a/axon-cqrs-example/src/main/java/com/brook/example/axon/AxonApplication.java b/axon-cqrs-example/src/main/java/com/brook/example/axon/AxonApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..970b2bd2d0e6dc4b16d7561f5c6e3582f7a95907
--- /dev/null
+++ b/axon-cqrs-example/src/main/java/com/brook/example/axon/AxonApplication.java
@@ -0,0 +1,12 @@
+package com.brook.example.axon;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AxonApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AxonApplication.class, args);
+ }
+}
diff --git a/axon-cqrs-example/src/main/resources/application.yml b/axon-cqrs-example/src/main/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e8d4692132ed2cad5299f9cb9b49318312d622b5
--- /dev/null
+++ b/axon-cqrs-example/src/main/resources/application.yml
@@ -0,0 +1,11 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:test;MODE=mysql;DB_CLOSE_DELAY=-1
+ username: sa
+ password:
+ type: org.apache.tomcat.jdbc.pool.XADataSource
+ driver-class-name: org.h2.Driver
+ jpa:
+ database: h2
+ hibernate:
+ ddl-auto: update
\ No newline at end of file
diff --git a/axon-cqrs-example/src/test/java/com/brook/example/axon/AxonApplicationTests.java b/axon-cqrs-example/src/test/java/com/brook/example/axon/AxonApplicationTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..24602a0f625f37c19684e23137c1f7d15e49bd64
--- /dev/null
+++ b/axon-cqrs-example/src/test/java/com/brook/example/axon/AxonApplicationTests.java
@@ -0,0 +1,16 @@
+package com.brook.example.axon;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class AxonApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/learn-java8-example/README.md b/learn-java8-example/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..20cc319298f8b1a5d8ff6a323426e32a3cb5d5c6
--- /dev/null
+++ b/learn-java8-example/README.md
@@ -0,0 +1,147 @@
+## 带你实战Java8
+
+> Java8 于2014年发布,是自Java1.0发布18年来最大变化的版本,完全向前兼容的版本。
+新功能中提供了更多的语法和设计,帮助开发者编写更清楚、简洁的代码。
+ Java8把函数式编程里一些最好的思想融入到大家熟知的Java语法中,让你用更少的时间写出高效代码。
+
+![](http://biezhi.me/static/img/article/java8-banner.png)
+
+
开启JAVA8之路
+
+### 新特性
+- 语言新特性
+ - Lambda 表达式 与函数式接口
+ - 流式API(可读性更好)
+ - 接口默认与静态方法
+ - 方法引用
+ - 重复注解
+ - 更好的类型推测机制
+- Java 编译器新特性
+ - 参数名称
+- 官方库的新特性
+ - Optional 防止null
+ - Date/Time 更友好的日期API
+ - 增强并行和并发处理
+ - Nashorn JavaScript引擎,可以在jvm上运行js
+ - Base64 (不需第三方库)
+- 新的java工具
+ - jjs 命令行工具
+ - jdeps 类分析其
+- JVM新特性
+ 使用Metaspace(JEP 122)代替持久代(PermGen space)。
+ 在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-X
+
+### lambda表达式
+`java8` 出现后,`lambda`赋予了java的语言的魅力。很长一段时间java被吐槽是冗余和缺乏函数式编程能力的语言,
+随着函数式编程的流行java8种也引入了 这种编程风格。我们再也不用写那么多的内部类,
+下面我们介绍如何使用lambda, 带你体验函数式编程的魔力。
+
+![](http://biezhi.me/static/img/article/lambda-expression.png)
+
+##### 什么是lambda
+lambda表达式是一段可以传递的代码,它的核心思想是将面向对象中的传递数据变成传递行为。 java8 以前编写一个线程时这样的:
+
+```java
+// 匿名内部类的写法,有的人会去实现`Runnable`接口或者继承`Thread`类
+Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ System.out.println("do something.");
+ }
+}
+```
+lambda 只用一行就实现了这个需求
+```java
+Runnable r = () -> System.out.println("do something.");
+
+```
+#### 语法
+```java
+expression = (var) -> action
+```
+- `var`: 这是一个变量,一个占位符。像x,y,z,可以是多个变量。
+- `action`: 这里我称它为action, 它可以是一行代码或者代码片段
+
+lambda 的多个参数
+```java
+int f2 = (x, y) -> x + y;
+```
+
+#### 函数式接口
+> 函数式接口是只有一个方法的接口,用作lambda表达式的类型。上个例子中`Runnable`其实就是一个函数式接口。
+``` java
+@FunctionalInterface
+public interface Runnable {
+ /**
+ * When an object implementing interface Runnable
is used
+ * to create a thread, starting the thread causes the object's
+ * run
method to be called in that separately executing
+ * thread.
+ *
+ * The general contract of the method run
is that it may
+ * take any action whatsoever.
+ *
+ * @see java.lang.Thread#run()
+ */
+ public abstract void run();
+}
+```
+
+例子:
+```java
+public class FunctionInterfaceDemo {
+ @FunctionalInterface
+ interface Predicate {
+ boolean test(T t);
+ }
+ /**
+ * 执行Predicate判断
+ *
+ * @param age 年龄
+ * @param predicate Predicate函数式接口
+ * @return 返回布尔类型结果
+ */
+ public static boolean doPredicate(int age, Predicate predicate) {
+ return predicate.test(age);
+ }
+
+ public static void main(String[] args) {
+ boolean isAdult = doPredicate(20, x -> x >= 18);
+ System.out.println(isAdult);
+ }
+}
+```
+#### 函数式接口分类
+
+函数接口大致有四大类:
+- Consumer 消费类型接口
+```java
+Consumer greeter = (p) -> System.out.println("Hello, " + p.getName());
+greeter.accept(new Person(1L, "tom"));
+```
+
+- Supplier 生产类型接口
+``` java
+Supplier create = Person::new;
+Person p = create.get(); // new Person
+```
+
+- Function 函数类型接口
+```java
+Function toInteger = Integer::valueOf;
+Function backToString = toInteger.andThen(String::valueOf);
+backToString.apply("123"); // "123"
+```
+
+- Predicate 断言类型接口
+```java
+Predicate predicate = (s) -> s.length() > 0;
+predicate.test("foo"); // true
+predicate.negate().test("foo"); // false
+
+Predicate nonNull = Objects::nonNull;
+Predicate isNull = Objects::isNull;
+
+Predicate isEmpty = String::isEmpty;
+Predicate isNotEmpty = isEmpty.negate();
+```
\ No newline at end of file
diff --git a/learn-java8-example/pom.xml b/learn-java8-example/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..414f742e7deb3a621708555e1f9d7dc078d341e4
--- /dev/null
+++ b/learn-java8-example/pom.xml
@@ -0,0 +1,62 @@
+
+
+ example
+ com.brook.example
+ 1.0-SNAPSHOT
+
+ 4.0.0
+ learn-java8-example
+ 1.0-SNAPSHOT
+ jar
+
+ learn-java8-example
+ http://maven.apache.org
+
+
+ UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ 5.0.0-M6
+ test
+
+
+
+ org.junit.platform
+ junit-platform-console-standalone
+ 1.0.0-M6
+ test
+
+
+ org.junit.platform
+ junit-platform-launcher
+ 1.0.0-M6
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.0.0-M6
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ 4.12.0-M6
+ test
+
+
+
diff --git a/learn-java8-example/src/main/java/com/brook/example/java8/App.java b/learn-java8-example/src/main/java/com/brook/example/java8/App.java
new file mode 100644
index 0000000000000000000000000000000000000000..e39f6222526eb6ff6158928dfd60af0954cc37cd
--- /dev/null
+++ b/learn-java8-example/src/main/java/com/brook/example/java8/App.java
@@ -0,0 +1,13 @@
+package com.brook.example.java8;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/learn-java8-example/src/main/resources/log4j2.xml b/learn-java8-example/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b047371f05fbc2cc5f943e54164d467401062c4e
--- /dev/null
+++ b/learn-java8-example/src/main/resources/log4j2.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/AppTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/AppTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5552241d51042fcd970d530a6091941c45c820a
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/AppTest.java
@@ -0,0 +1,63 @@
+package com.brook.example.java8;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.DynamicTest.stream;
+
+/**
+ * Unit test for simple App.
+ */
+@DisplayName("junit5 testcase.")
+class AppTest {
+
+ static IntStream range() {
+ return IntStream.range(0, 20).skip(10);
+ }
+
+ @Test
+ @DisplayName("junit5 helloJUnit5.")
+ void helloJUnit5() {
+ assertEquals("hello junit5", "hello junit5");
+ }
+
+ /**
+ * use {@code {@link ParameterizedTest}} annotation ,
+ * required dependency on the junit-jupiter-params
artifact.
+ * @param argument
+ */
+ @ParameterizedTest
+ @ValueSource(strings = { "Hello", "World" })
+ void testWithStringParameter(String argument) {
+ assertNotNull(argument);
+ }
+
+ @TestFactory
+ Stream streamDynamicTest() {
+ return stream(
+ Stream.of("Hello", "World").iterator(),
+ (word) -> String.format("Test - %s", word),
+ (word) -> {
+ assertTrue(word.length() > 4);
+ assertNotNull(word);
+
+ });
+ }
+
+ @ParameterizedTest
+ @MethodSource("range")
+ void testWithRangeMethodSource(int argument) {
+ assertNotEquals(9, argument);
+ }
+
+
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/base/PersistentTree.java b/learn-java8-example/src/test/java/com/brook/example/java8/base/PersistentTree.java
new file mode 100644
index 0000000000000000000000000000000000000000..c69704522a4ffc0773101845985e48e237a0cff2
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/base/PersistentTree.java
@@ -0,0 +1,107 @@
+package com.brook.example.java8.base;
+
+import org.junit.jupiter.api.Test;
+
+class PersistentTree {
+
+ @Test
+ void test() {
+ Tree t = new Tree("Mary", 22,
+ new Tree("Emily", 20,
+ new Tree("Alan", 50, null, null),
+ new Tree("Georgie", 23, null, null)
+ ),
+ new Tree("Tian", 29,
+ new Tree("Raoul", 23, null, null),
+ null
+ )
+ );
+
+ // found = 23
+ System.out.println(lookup("Raoul", -1, t));
+ // not found = -1
+ System.out.println(lookup("Jeff", -1, t));
+
+ Tree f = fupdate("Jeff", 80, t);
+ // found = 80
+ System.out.println(lookup("Jeff", -1, f));
+
+ Tree u = update("Jim", 40, t);
+ // t was not altered by fupdate, so Jeff is not found = -1
+ System.out.println(lookup("Jeff", -1, u));
+ // found = 40
+ System.out.println(lookup("Jim", -1, u));
+
+ Tree f2 = fupdate("Jeff", 80, t);
+ // found = 80
+ System.out.println(lookup("Jeff", -1, f2));
+ // f2 built from t altered by update() above, so Jim is still present = 40
+ System.out.println(lookup("Jim", -1, f2));
+ }
+
+
+ static class Tree {
+ private String key;
+ private int val;
+ private Tree left, right;
+
+ @Override
+ public String toString() {
+ return "Tree{" +
+ "key='" + key + '\'' +
+ ", val=" + val +
+ ", left=" + left +
+ ", right=" + right +
+ '}';
+ }
+
+ public Tree(String k, int v, Tree l, Tree r) {
+ key = k;
+ val = v;
+ left = l;
+ right = r;
+ }
+ }
+
+ public static int lookup(String k, int defaultval, Tree t) {
+ if (t == null)
+ return defaultval;
+ if (k.equals(t.key))
+ return t.val;
+ return lookup(k, defaultval, k.compareTo(t.key) < 0 ? t.left : t.right);
+ }
+
+ public static Tree update(String k, int newval, Tree t) {
+ if (t == null)
+ t = new Tree(k, newval, null, null);
+ else if (k.equals(t.key))
+ t.val = newval;
+ else if (k.compareTo(t.key) < 0)
+ t.left = update(k, newval, t.left);
+ else
+ t.right = update(k, newval, t.right);
+ return t;
+ }
+
+ /**
+ * 采用函数的方法更新
+ * 和{@link #update(String, int, Tree)} 区别
+ *
+ * 因为update试图对树进行原地更新,它返回的是跟传入的参数同样的树,但是 如果最初的树为空,
+ * 那么新的节点会作为结果返回。
+ * @param k
+ * @param newval
+ * @param t
+ * @return
+ */
+ public static Tree fupdate(String k, int newval, Tree t) {
+ return (t == null) ?
+ new Tree(k, newval, null, null) :
+ k.equals(t.key) ?
+ new Tree(k, newval, t.left, t.right) :
+ k.compareTo(t.key) < 0 ?
+ new Tree(t.key, t.val, fupdate(k,newval, t.left), t.right) :
+ new Tree(t.key, t.val, t.left, fupdate(k,newval, t.right));
+ }
+
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/base/RepeateAnnoTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/base/RepeateAnnoTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..24d8627e7906aa2ebf1da57574a5c3aedd125dcc
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/base/RepeateAnnoTest.java
@@ -0,0 +1,33 @@
+package com.brook.example.java8.base;
+
+import com.brook.example.java8.domain.Author;
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+import static org.junit.jupiter.api.Assertions.assertIterableEquals;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/31
+ */
+public class RepeateAnnoTest {
+ @Test
+ void getAuthors(){
+ Author [] authors = Book.class.getAnnotationsByType(Author.class);
+ List names = Arrays.asList(authors)
+ .stream()
+ .map(Author::name)
+ .collect(toList());
+ assertIterableEquals(Lists.newArrayList("tom", "lucy", "小明"),names);
+ }
+}
+@Author(name = "tom")
+@Author(name = "lucy")
+@Author(name = "小明")
+class Book{
+
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/collections/MapTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/collections/MapTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..10572fc4d60a65ce68c620284b3aad2e915b0f94
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/collections/MapTest.java
@@ -0,0 +1,137 @@
+package com.brook.example.java8.collections;
+
+import com.brook.example.java8.domain.Person;
+import com.google.common.collect.Maps;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestReporter;
+
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.LongAccumulator;
+import java.util.concurrent.atomic.LongAdder;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * java8 Map test
+ * @author Shaojun Liu
+ * @create 2017/7/29
+ */
+@Log4j2
+public class MapTest {
+ @Test
+ void test(TestReporter reporter){
+ Map map = Maps.newHashMapWithExpectedSize(16);
+ map.putIfAbsent("a",1);
+ int val = map.putIfAbsent("a",2);
+ assertEquals(1,val);
+ Integer val3 = map.putIfAbsent("b",3);
+ assertNull(val3);
+ int val4 = map.put("b",4);
+ assertEquals(3,val4);
+ Integer val5 = map.put("c",5);
+ assertNull(val5);
+ map.computeIfAbsent("c", key -> 6 );
+ assertNotNull(map.get("c"));
+ map.computeIfPresent("b",(k,v)-> v * 2);
+ assertEquals(8, map.get("b").intValue());
+ int val6 = map.getOrDefault("d",-1);
+ assertEquals(-1,val6);
+ map.put("d",null);
+ int val7 = map.merge("d",6,(v,newVal)->newVal);
+ assertEquals(6,val7);
+ int val8 = map.merge("d", 8,(v,newVal)->newVal+v);
+ assertEquals(14,val8);
+ log.info("map is >>>{}",map);
+ }
+ @Test
+ void testCurrentHashMap(){
+ ConcurrentHashMap map = new ConcurrentHashMap<>();
+ String word = "test";
+ // 以下这段代码不是原子性,因为另一个线程可能同时在更新相同的计数
+ Long oldVal = map.get(word);
+ Long newVal = oldVal == null ?1 : oldVal +1;
+ map.put(word,newVal);
+ // 1. 可以使用replace
+ do {
+ oldVal = map.get(word);
+ newVal = oldVal == null ?1 : oldVal + 1;
+ }while (!map.replace(word,oldVal,newVal));
+ // 2. 可以使用 java8 LongAdder
+ ConcurrentHashMap laMap = new ConcurrentHashMap<>();
+ laMap.putIfAbsent(word,new LongAdder());
+ laMap.get(word).increment();
+ // 如果是复杂计算可以用, val 不能为null
+ map.compute(word, (k, v) -> v == null ? 1 : v + 1);
+ // putIfAbsent 区别,只有在需要计算时才会被调用
+ laMap.computeIfAbsent(word, key -> new LongAdder());
+ laMap.get(word).increment();
+ // 或者
+ map.merge(word,1L,Long::sum);
+ //注意 传递给 compute 和 merge 方法的函数不能为null,否则已有的数据项会从映射中删除
+ // 小心使用compute 和 merge 方法,牢记不要进行大量操作
+ }
+
+ /**
+ * U searchKeys(long threshold, BiFunction super K, ? extends U> f)
+ U searchValues(long threshold, BiFunction super V, ? extends U> f)
+ U search(long threshold, BiFunction super K, ? super V,? extends U> f)
+ U searchEntries(long threshold, BiFunction, ? extends U> f)
+ */
+ @Test
+ void expand(){
+ ConcurrentHashMap map = (ConcurrentHashMap) Stream.generate(new
+ RandomPerson())
+ .limit(100)
+ .collect(Collectors.toConcurrentMap(Person::getName,
+ Person::getAge));
+ // threshold 越小越快
+ String result = map.search(1,(k, v) ->v > 50? k: null );
+ System.out.println("result >>> " + result);
+ int sum = map.reduceValues(1,Integer::sum);
+ System.out.println("sum >>>" + sum);
+ long lsum = map.reduceValuesToLong(1,Long::valueOf,0,Long::sum);
+ System.out.println("lsum >>>" + lsum);
+ int keysLenSum = map.reduceKeys(2, String::length, Integer::sum);
+ System.out.println("keysLenSum >>>" + keysLenSum);
+ }
+
+ /**
+ * LongAdder(加法器) 适合做统计
+ * 类似的有 {@link java.util.concurrent.atomic.LongAccumulator}
+ */
+ @Test
+ void adder(){
+ LongAdder adder = new LongAdder();
+ adder.increment();
+ adder.increment();
+ assertEquals(adder.sum(),2);
+ adder.increment();
+ assertEquals("3",adder.toString());
+ assertEquals(3,adder.sumThenReset());
+ adder.add(12);
+ adder.increment();
+ assertEquals(13,adder.sum());
+
+ LongAccumulator accumulator = new LongAccumulator(Long::sum,1);
+ accumulator.accumulate(2);
+ assertEquals(3,accumulator.get());
+ }
+
+
+}
+class RandomPerson implements Supplier{
+
+ // LongAdder adder = new LongAdder();
+ long i = 1;
+ Random r = new Random();
+ @Override
+ public Person get() {
+ return new Person(i++,"user"+ i,10 +r.nextInt(90));
+ }
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/currency/CompletableFutureTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/currency/CompletableFutureTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5cd0da75bbbd6d4b0f0d9131dbc28f90e984a70c
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/currency/CompletableFutureTest.java
@@ -0,0 +1,166 @@
+package com.brook.example.java8.currency;
+
+import com.google.common.collect.ImmutableMap;
+import org.assertj.core.util.Lists;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/31
+ */
+public class CompletableFutureTest {
+
+ // 合并
+ private static String merge(List datas){
+ return datas.parallelStream()
+ .reduce((s, s2) -> {
+ try {
+ TimeUnit.SECONDS.sleep(2);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return s + ";" + s2;
+
+ }).orElse("");
+ }
+
+ /**
+ * 流水式执行
+ *
+ * @throws ExecutionException
+ * @throws InterruptedException
+ */
+ @Test
+ void sequentially() throws ExecutionException, InterruptedException {
+ // 如果是I/O密集型,使用Executor 增加线程池大小
+ // 如果是CPU密集型计算,就不能增加太多的计算使用 parallel stream 比较好
+ ExecutorService executor = Executors.newFixedThreadPool(5);
+ CompletableFuture db = CompletableFuture.supplyAsync(()->this.getDataFromDB("db"),executor);
+ db.thenApplyAsync(this::getDataFromRedis,executor)
+ .thenAcceptAsync(this::handler,executor)
+ // 异常捕获
+ .exceptionally(e -> {
+ System.out.println("error :" + e.getMessage());
+ return null;
+ })
+ .whenComplete((aVoid, e) -> System.out.println("----> finish."))
+ .get();
+ executor.shutdown();
+
+ }
+
+ /**
+ * 模拟从数据库获取数据
+ * @return
+ */
+ public String getDataFromDB(String data){
+ System.out.println("-------> get data from db.");
+ try {
+ TimeUnit.SECONDS.sleep(5);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return data;
+ }
+
+ /**
+ * 并行执行
+ * (7s)
+ * db -> redis-> \
+ * => merge(2s) => finish (9s)
+ * net -> /
+ * (6s)
+ *
+ * @throws ExecutionException
+ * @throws InterruptedException
+ */
+ @Test
+ void parallel() throws ExecutionException, InterruptedException {
+ ExecutorService executor = Executors.newFixedThreadPool(4);
+
+ List> futures = getFutures(executor);
+ String result = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures
+ .size()]))
+ .thenApplyAsync((v)-> futures.stream()
+ .map(CompletableFuture::join)
+ .collect(Collectors.toList()))
+ // 合并
+ .thenApplyAsync(CompletableFutureTest::merge,executor)
+ .exceptionally((e) -> {
+ System.out.println("error :" + e.getMessage());
+ return null;
+ })
+ .whenComplete(this::done)
+ .get();
+ executor.shutdown();
+ assertEquals("redis:data;net:data",result);
+ }
+
+ private List> getFutures(Executor executor){
+ CompletableFuture dbFuture = CompletableFuture
+ .supplyAsync(() -> this.getDataFromDB("db:data"),executor)
+ .thenApplyAsync(this::getDataFromRedis,executor)
+ .thenApplyAsync((map) -> map.values().toArray(new String[]{})[0]);
+
+ CompletableFuture netFuture = CompletableFuture
+ .supplyAsync(this::getDataFromNet);
+ return Lists.newArrayList(dbFuture, netFuture);
+
+ }
+
+ void handler(Map data){
+ System.out.println("-------> 处理数据");
+ try {
+ TimeUnit.SECONDS.sleep(2);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ data.forEach((k, v) -> System.out.printf("k = %s,v = %s \n\r",k,v));
+ }
+
+ private void done(String data,Throwable e){
+ System.out.println("-----> " + data);
+ System.out.println("----> " + "finish.");
+ }
+
+ /**
+ * 模拟从redis 获取数据
+ * @return
+ */
+ public Map getDataFromRedis(String key){
+ System.out.println("-------> get data from redis.");
+
+ System.out.println("key is "+key );
+ if(key == null){
+ throw new RuntimeException(String.format("key [%s]不存在!",key));
+ }
+ try {
+ TimeUnit.SECONDS.sleep(2);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return ImmutableMap.of(key,"redis:data");
+ }
+
+ /**
+ * 模拟从网络获取数据
+ * @return
+ */
+ public String getDataFromNet(){
+ System.out.println("-------> get data from net.");
+
+ try {
+ TimeUnit.SECONDS.sleep(6);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return "net:data";
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/currency/FutureParallelTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/currency/FutureParallelTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..444b5faade82778d7154d49c72f68733c5b86561
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/currency/FutureParallelTest.java
@@ -0,0 +1,128 @@
+package com.brook.example.java8.currency;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static java.util.stream.Collectors.toList;
+import static org.junit.jupiter.api.Assertions.assertTimeout;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/31
+ */
+public class FutureParallelTest {
+ List tasks;
+
+ @BeforeEach
+ void initTasks() {
+ tasks = IntStream.range(0, 10)
+ .mapToObj(i -> new MyTask(1))
+ .collect(toList());
+
+ }
+
+ @DisplayName("串行流")
+ @Test
+ void seq() {
+ assertTimeout(Duration.ofSeconds(11), () -> MyTask.runSequentially(tasks));
+ }
+
+ @DisplayName("并行流")
+ @Test
+ void ps() {
+ MyTask.useParallelStream(tasks);
+ }
+
+ @DisplayName("CompletableFuture 并行")
+ @Test
+ void cf() {
+ MyTask.useCompletableFutureWithExecutor(tasks);
+ }
+
+ @DisplayName("CompletableFuture With Executor 并行")
+ @Test
+ void cfe() {
+ MyTask.useCompletableFutureWithExecutor(tasks);
+ }
+
+}
+
+class MyTask {
+ private final int duration;
+
+ public MyTask(int duration) {
+ this.duration = duration;
+ }
+
+ public static void runSequentially(List tasks) {
+ long start = System.nanoTime();
+ List result = tasks.stream()
+ .map(MyTask::calculate)
+ .collect(toList());
+ long duration = (System.nanoTime() - start) / 1_000_000;
+ System.out.printf("Processed %d tasks in %d millis\n", tasks.size(), duration);
+ System.out.println(result);
+ }
+
+ public static void useParallelStream(List tasks) {
+ long start = System.nanoTime();
+ List result = tasks.parallelStream()
+ .map(MyTask::calculate)
+ .collect(toList());
+ long duration = (System.nanoTime() - start) / 1_000_000;
+ System.out.printf("Processed %d tasks in %d millis\n", tasks.size(), duration);
+ System.out.println(result);
+ }
+
+ public static void useCompletableFuture(List tasks) {
+ long start = System.nanoTime();
+ List> futures =
+ tasks.stream()
+ .map(t -> CompletableFuture.supplyAsync(() -> t.calculate()))
+ .collect(Collectors.toList());
+
+ List result =
+ futures.stream()
+ .map(CompletableFuture::join)
+ .collect(Collectors.toList());
+ long duration = (System.nanoTime() - start) / 1_000_000;
+ System.out.printf("Processed %d tasks in %d millis\n", tasks.size(), duration);
+ System.out.println(result);
+ }
+
+ public static void useCompletableFutureWithExecutor(List tasks) {
+ long start = System.nanoTime();
+ ExecutorService executor = Executors.newFixedThreadPool(Math.min(tasks.size(), 10));
+ List> futures =
+ tasks.stream()
+ .map(t -> CompletableFuture.supplyAsync(t::calculate, executor))
+ .collect(Collectors.toList());
+ List result =
+ futures.stream()
+ .map(CompletableFuture::join)
+ .collect(Collectors.toList());
+ long duration = (System.nanoTime() - start) / 1_000_000;
+ System.out.printf("Processed %d tasks in %d millis\n", tasks.size(), duration);
+ System.out.println(result);
+ executor.shutdown();
+ }
+
+ public int calculate() {
+ System.out.println(Thread.currentThread().getName());
+ try {
+ Thread.sleep(duration * 1000);
+ } catch (final InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ return duration;
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/currency/StampedLockTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/currency/StampedLockTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a075efeea604092d53d8b96fc84c66f1c1845686
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/currency/StampedLockTest.java
@@ -0,0 +1,117 @@
+package com.brook.example.java8.currency;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.locks.StampedLock;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * StampedLock
+ *
+ * java8 新出的读写锁,比{@link java.util.concurrent.locks.ReentrantReadWriteLock}性能更好。
+ * {@code ReentrantReadWriteLock} 在沒有任何读写锁时,才可以取得写入锁,
+ * 这可用于实现了悲观读取(Pessimistic Reading),即如果执行中进行读取时,
+ * 经常可能有另一执行要写入的需求,为了保持同步, {@code ReentrantReadWriteLock} 的读取锁定就可派上用场。
+ * 然而,如果读取执行情况很多,写入很少的情况下,使用 {@code ReentrantReadWriteLock} 可能
+ * 会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程迟迟无法竞争到锁定而一直处于等待状态。
+ * {@code StampedLock}控制锁有三种模式: 写
,读
,乐观读
。
+ * 一个{@code StampedLock} 状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据
+ * stamp
,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。
+ * 在读锁上分为悲观锁和乐观锁。
+ *
+ *
+ * @author Shaojun Liu
+ * @create 2017/7/30
+ */
+public class StampedLockTest {
+
+ @Test
+ void deposit(){
+ AccountWithSampedLock account = new AccountWithSampedLock();
+ account.deposit(1000d);
+ double amount = account.getBalance();
+ assertEquals(1000d,amount);
+ }
+}
+
+class AccountWithSampedLock {
+ private final StampedLock lock = new StampedLock();
+ private double balance;
+
+ void deposit(double amount) {
+ long stamp = lock.writeLock();
+ try {
+ balance = balance + amount;
+ } finally {
+ lock.unlockWrite(stamp);
+ }
+ }
+
+ double getBalance() {
+ long stamp = lock.readLock();
+ try {
+ return balance;
+ } finally {
+ lock.unlockRead(stamp);
+ }
+ }
+}
+
+/**
+ * java doc提供的StampedLock一个例子
+ */
+class Point {
+ private double x, y;
+ private final StampedLock sl = new StampedLock();
+
+ void move(double deltaX, double deltaY) {
+ long stamp = sl.writeLock();
+ try {
+ x += deltaX;
+ y += deltaY;
+ } finally {
+ sl.unlockWrite(stamp);
+ }
+ }
+
+ //乐观读锁案例
+ double distanceFromOrigin() {
+ long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
+ double currentX = x, currentY = y; //将两个字段读入本地局部变量
+ if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生?
+ stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁
+ try {
+ currentX = x; // 将两个字段读入本地局部变量
+ currentY = y; // 将两个字段读入本地局部变量
+ } finally {
+ sl.unlockRead(stamp);
+ }
+ }
+ return Math.sqrt(currentX * currentX + currentY * currentY);
+ }
+
+ //悲观读锁案例
+ void moveIfAtOrigin(double newX, double newY) {
+ // upgrade
+ // Could instead start with optimistic, not read mode
+ long stamp = sl.readLock();
+ try {
+ while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合
+ long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
+ if (ws != 0L) {
+ // 成功转为写锁
+ stamp = ws; //如果成功 替换票据
+ x = newX; //进行状态改变
+ y = newY; //进行状态改变
+ break;
+ } else {
+ sl.unlockRead(stamp); //我们显式释放读锁
+ stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试
+ }
+ }
+ } finally {
+ sl.unlock(stamp); // 释放读写锁
+ }
+ }
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/currency/WordCountTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/currency/WordCountTest.java
new file mode 100755
index 0000000000000000000000000000000000000000..fed918ab31c44498db01e1dd1a4ec8474b00d7d0
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/currency/WordCountTest.java
@@ -0,0 +1,127 @@
+package com.brook.example.java8.currency;
+
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WordCountTest {
+
+ public static final String SENTENCE =
+ " Nel mezzo del cammin di nostra vita " +
+ "mi ritrovai in una selva oscura" +
+ " che la dritta via era smarrita ";
+
+ public static void main(String[] args) {
+ int words = countWordsIteratively(SENTENCE);
+ assertThat(words).isEqualTo(countWords(SENTENCE));
+
+ }
+
+ /**
+ * 普通循环
+ * @param s
+ * @return
+ */
+ public static int countWordsIteratively(String s) {
+ int counter = 0;
+ boolean lastSpace = true;
+ for (char c : s.toCharArray()) {
+ if (Character.isWhitespace(c)) {
+ lastSpace = true;
+ } else {
+ if (lastSpace) counter++;
+ lastSpace = Character.isWhitespace(c);
+ }
+ }
+ return counter;
+ }
+
+ // 使用流
+ public static int countWords(String s) {
+// 直接用并行流会导致统计不正确,默认的Spliterator在并行时并不知道整个字符串从哪里开始切割
+// Stream stream = IntStream.range(0, s.length())
+// .mapToObj(SENTENCE::charAt).parallel();
+ Spliterator spliterator = new WordCounterSpliterator(s);
+ Stream stream = StreamSupport.stream(spliterator, true);
+
+ return countWords(stream);
+ }
+
+ private static int countWords(Stream stream) {
+ WordCounter wordCounter = stream.reduce(new WordCounter(0, true),
+ WordCounter::accumulate,
+ WordCounter::combine);
+ return wordCounter.getCounter();
+ }
+
+ private static class WordCounter {
+ private final int counter;
+ private final boolean lastSpace;
+
+ public WordCounter(int counter, boolean lastSpace) {
+ this.counter = counter;
+ this.lastSpace = lastSpace;
+ }
+
+ public WordCounter accumulate(Character c) {
+ if (Character.isWhitespace(c)) {
+ return lastSpace ? this : new WordCounter(counter, true);
+ } else {
+ return lastSpace ? new WordCounter(counter+1, false) : this;
+ }
+ }
+
+ public WordCounter combine(WordCounter wordCounter) {
+ return new WordCounter(counter + wordCounter.counter, wordCounter.lastSpace);
+ }
+
+ public int getCounter() {
+ return counter;
+ }
+ }
+
+ private static class WordCounterSpliterator implements Spliterator {
+
+ private final String string;
+ private int currentChar = 0;
+
+ private WordCounterSpliterator(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer super Character> action) {
+ action.accept(string.charAt(currentChar++));
+ return currentChar < string.length();
+ }
+
+ @Override
+ public Spliterator trySplit() {
+ int currentSize = string.length() - currentChar;
+ if (currentSize < 10) {
+ return null;
+ }
+ for (int splitPos = currentSize / 2 + currentChar; splitPos < string.length(); splitPos++) {
+ if (Character.isWhitespace(string.charAt(splitPos))) {
+ Spliterator spliterator = new WordCounterSpliterator(string.substring(currentChar, splitPos));
+ currentChar = splitPos;
+ return spliterator;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public long estimateSize() {
+ return string.length() - currentChar;
+ }
+
+ @Override
+ public int characteristics() {
+ return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE;
+ }
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/domain/Author.java b/learn-java8-example/src/test/java/com/brook/example/java8/domain/Author.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a5622ed0fd19319f9d6133004b5309754afab1d
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/domain/Author.java
@@ -0,0 +1,13 @@
+package com.brook.example.java8.domain;
+
+import java.lang.annotation.*;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/31
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(Authors.class)
+public @interface Author {
+ String name() default "";
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/domain/Authors.java b/learn-java8-example/src/test/java/com/brook/example/java8/domain/Authors.java
new file mode 100644
index 0000000000000000000000000000000000000000..7529d09181b11edfb77ad4e173b1e25b95e1fb9c
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/domain/Authors.java
@@ -0,0 +1,13 @@
+package com.brook.example.java8.domain;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/31
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Authors {
+ Author[] value() default {};
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/domain/Car.java b/learn-java8-example/src/test/java/com/brook/example/java8/domain/Car.java
new file mode 100644
index 0000000000000000000000000000000000000000..3bb3b3d3b35d9b68a201d97ba0b46937377f9129
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/domain/Car.java
@@ -0,0 +1,28 @@
+package com.brook.example.java8.domain;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Optional;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/30
+ */
+@Data
+@NoArgsConstructor
+public class Car {
+ private String name;
+ public Car(String name){
+ this.name = name;
+ }
+ private Optional insurance;
+ @Data
+ @NoArgsConstructor
+ public static class Insurance {
+ private String name;
+ public Insurance(String name){
+ this.name = name;
+ }
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/domain/Person.java b/learn-java8-example/src/test/java/com/brook/example/java8/domain/Person.java
new file mode 100644
index 0000000000000000000000000000000000000000..b92c5ff774e9b416c29027631db0682ccae94462
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/domain/Person.java
@@ -0,0 +1,31 @@
+package com.brook.example.java8.domain;
+
+import lombok.*;
+
+import java.util.Optional;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/28
+ */
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Person {
+ @Setter
+ Optional car;
+ public Person(Long id,String name){
+ this.id = id;
+ this.name = name;
+ }
+
+ public Person(Long id, String name,int age) {
+ this(id,name);
+ this.age = age;
+ }
+ private Long id;
+ private String name;
+ private Integer age;
+
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/functions/ConsumerTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/functions/ConsumerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e63735fa90cc7fe057c7ce9b8715c572e9dcb083
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/functions/ConsumerTest.java
@@ -0,0 +1,8 @@
+package com.brook.example.java8.functions;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/30
+ */
+public class ConsumerTest {
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/functions/DefaultMethodTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/functions/DefaultMethodTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..409ffc2da761cacb2e4c950d4dd575a053e519d1
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/functions/DefaultMethodTest.java
@@ -0,0 +1,81 @@
+package com.brook.example.java8.functions;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * 默认方法
+ *
+ * 1. 默认方法
+ * 2. 多继承
+ * @author Shaojun Liu
+ * @create 2017/7/30
+ */
+public class DefaultMethodTest {
+
+ @Test
+ void getName(){
+ TomPlayer player = new TomPlayer();
+ assertEquals("tom",player.getName());
+ assertTrue(player.isMale());
+ Player.foo();
+ }
+
+ /**
+ * 多重继承
+ */
+ @Test
+ void foobar(){
+ Foobar foobar = new Foobar();
+ assertEquals("bar",foobar.name());
+ }
+}
+interface Player {
+ String getName();
+ default boolean isMale(){
+ return true;
+ }
+
+ static void foo(){
+ System.out.println("我是静态方法");
+ }
+}
+class TomPlayer implements Player{
+ @Override
+ public String getName() {
+ return "tom";
+ }
+}
+class LucyPlayer implements Player{
+ @Override
+ public boolean isMale() {
+ return false;
+ }
+
+ @Override
+ public String getName() {
+ return "lucy";
+ }
+}
+
+interface Foo{
+ default String name() {
+ return "foo";
+ }
+}
+
+interface Bar{
+ default String name() {
+ return "bar";
+ }
+}
+class Foobar implements Foo, Bar {
+ // 菱形问题
+ // 如果覆盖 name() 编译器就会报错,
+ @Override
+ public String name() {
+ return Bar.super.name();
+ }
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/functions/FunctionsTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/functions/FunctionsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f484937e4e4012c2b6337523f1766d915500d1ae
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/functions/FunctionsTest.java
@@ -0,0 +1,77 @@
+package com.brook.example.java8.functions;
+
+import com.brook.example.java8.domain.Person;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.Test;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/30
+ */
+@Log4j2
+public class FunctionsTest {
+ /**
+ * @see com.brook.example.java8.pattern.StrategyPattern
+ */
+ @Test
+ void predicate(){
+ Predicate predicate = (s) -> s.length() > 0;
+ assertTrue(predicate.test("foo"));
+ assertFalse(predicate.negate().test("foo"));
+ Predicate nonNull = Objects::nonNull;
+ assertTrue(nonNull.test(Boolean.FALSE));
+ Predicate isNull = Objects::isNull;
+ assertTrue(isNull.test(null));
+
+ Predicate isEmpty = String::isEmpty;
+ assertTrue(isEmpty.test(""));
+ Predicate isNotEmpty = isEmpty.negate();
+ assertTrue(isNotEmpty.test("Not Empty."));
+ }
+
+ /**
+ * 案例请看
+ * @see com.brook.example.java8.pattern.ChainPattern
+ */
+ @Test
+ void function() {
+ Function toInteger = Integer::valueOf;
+ Function backToString = toInteger.andThen(String::valueOf);
+ assertEquals("123", backToString.apply("123"));
+ // andThen 和 compose 区别
+ Function times2 = e -> e * 2;
+ Function sqr = e -> e * e;
+ int r1 = times2.compose(sqr).apply(3); // 18
+ int r2 = times2.andThen(sqr).apply(3); // 36
+ // 结果说明 compose 先执行参数,而andThen 先执行函数调用者
+ assertEquals(r1,18);
+ assertEquals(r2,36);
+ }
+
+ /**
+ * @see com.brook.example.java8.pattern.ObserverPattern
+ */
+ @Test
+ void consumer() {
+ Consumer greeter = (p) -> log.info("Hello, " + p.getName());
+ greeter.accept(new Person(1L, "tom"));
+ }
+
+ /**
+ * @see com.brook.example.java8.pattern.FactoryPattern
+ */
+ @Test
+ void supplier(){
+ Supplier create = Person::new;
+ Person p = create.get();
+ assertNotNull(p);
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/functions/MethodRefTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/functions/MethodRefTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4099afb72c7e483e39f92e2a00a789773176a96f
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/functions/MethodRefTest.java
@@ -0,0 +1,45 @@
+package com.brook.example.java8.functions;
+
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/30
+ */
+@Log4j2
+public class MethodRefTest {
+ @Test
+ void test(){
+ new ConcurrentGreeter().greet();
+ }
+}
+@Log4j2
+class Greeter {
+ public void greet(){
+ try {
+ TimeUnit.SECONDS.sleep(2);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ log.info("Greet !");
+ }
+}
+@Log4j2
+class ConcurrentGreeter extends Greeter{
+ @Override
+ public void greet() {
+
+ Thread t = new Thread(super::greet); // 原型就是 ()->super.greet();
+ t.start();
+ log.info("------ Output Greet !-------");
+ try {
+ t.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ log.info("-------- End !--------");
+ }
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/io/FilesTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/io/FilesTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c646790a25f4693215af489dcd299b4d91bf465b
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/io/FilesTest.java
@@ -0,0 +1,45 @@
+package com.brook.example.java8.io;
+
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Files test case.
+ * @author Shaojun Liu
+ * @create 2017/7/28
+ */
+@Log4j2
+class FilesTest {
+ static String home = System.getProperty("user.home");
+
+ @Test
+ void list() throws IOException {
+ Files.list(Paths.get(home, "/Desktop"))
+ .map(path -> path.getFileName() + " -->" + (path.toFile().isDirectory()
+ ? "目录" : "文件"))
+ .forEach(System.out::println);
+
+ }
+
+ @Test
+ void walk() throws IOException {
+ String currentDir = getClass().getResource(".").getPath();
+ String filesTest = Files.walk(Paths.get(currentDir), 2)
+ .map(Path::getFileName)
+ .map(Object::toString)
+ .filter(fileName ->fileName.startsWith("FilesTest"))
+ .findFirst()
+ .orElse("");
+ assertEquals("FilesTest.class",filesTest);
+
+ }
+
+
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/optional/OptionalTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/optional/OptionalTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..cad6fb2c42d66125a4bea845271ce1cfba36d12b
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/optional/OptionalTest.java
@@ -0,0 +1,58 @@
+package com.brook.example.java8.optional;
+
+import com.brook.example.java8.domain.Car;
+import com.brook.example.java8.domain.Person;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @see java.util.Optional
+ * @author Shaojun Liu
+ * @create 2017/7/29
+ */
+class OptionalTest {
+
+ @DisplayName("Optional testcase.")
+ @Test
+ void create() {
+ String isNull = null;
+ isNull = Optional.ofNullable(isNull)
+ .orElse("is null");
+ List strs = null;
+ strs = Optional.ofNullable(strs)
+ .orElseGet(Collections::emptyList);
+ assertNotNull(strs);
+ assertEquals(isNull, "is null");
+ assertThrows(NullPointerException.class, () -> Optional.of(null));
+ Assertions.assertThat(Optional.ofNullable("")).isPresent();
+ assertThrows(IllegalArgumentException.class,
+ () -> Optional.ofNullable(null).orElseThrow(IllegalArgumentException::new));
+ }
+
+ @Test
+ void testGetName() {
+ Person tom = new Person(1L, "tom");
+ tom.setCar(Optional.empty());
+ assertEquals("unknown",getInsuranceName(tom));
+ Car.Insurance insurance = new Car.Insurance("阳光车险");
+ Car car = new Car("宝马");
+ car.setInsurance(Optional.of(insurance));
+ tom.setCar(Optional.of(car));
+ assertEquals("阳光车险",getInsuranceName(tom));
+ }
+
+ private String getInsuranceName(Person person) {
+ return Optional.of(person)
+ .flatMap(Person::getCar)
+ .flatMap(Car::getInsurance)
+ .map(Car.Insurance::getName)
+ .orElse("unknown");
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/pattern/ChainPattern.java b/learn-java8-example/src/test/java/com/brook/example/java8/pattern/ChainPattern.java
new file mode 100755
index 0000000000000000000000000000000000000000..76d461dcd2827164ba1e4b18129ee307153f00a5
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/pattern/ChainPattern.java
@@ -0,0 +1,64 @@
+package com.brook.example.java8.pattern;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+public class ChainPattern {
+
+ @Test
+ void test(){
+ // 传统的设计模式
+ ProcessingObject p1 = new HeaderTextProcessing();
+ ProcessingObject p2 = new SpellCheckerProcessing();
+ p1.setSuccessor(p2);
+ String result1 = p1.handle("Aren't labdas really sexy?!!");
+ System.out.println(result1);
+ // 使用java8 函数编程重构
+ UnaryOperator headerProcessing =
+ (String text) -> "From Raoul, Mario and Alan: " + text;
+ UnaryOperator spellCheckerProcessing =
+ (String text) -> text.replaceAll("labda", "lambda");
+ Function pipeline = headerProcessing.andThen(spellCheckerProcessing);
+ String result2 = pipeline.apply("Aren't labdas really sexy?!!");
+ assertEquals(result1,result2);
+ }
+
+ static private abstract class ProcessingObject {
+ protected ProcessingObject successor;
+
+ public void setSuccessor(ProcessingObject successor) {
+ this.successor = successor;
+ }
+
+ public T handle(T input) {
+ T r = handleWork(input);
+ if (successor != null) {
+ return successor.handle(r);
+ }
+ return r;
+ }
+
+ abstract protected T handleWork(T input);
+ }
+
+ static private class HeaderTextProcessing
+ extends ProcessingObject {
+ public String handleWork(String text) {
+ return "From Raoul, Mario and Alan: " + text;
+ }
+ }
+
+ static private class SpellCheckerProcessing
+ extends ProcessingObject {
+ public String handleWork(String text) {
+ return text.replaceAll("labda", "lambda");
+ }
+ }
+}
+
+
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/pattern/FactoryPattern.java b/learn-java8-example/src/test/java/com/brook/example/java8/pattern/FactoryPattern.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f775e4c8fbfae4c5877e15c0c485102bffeb7ff
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/pattern/FactoryPattern.java
@@ -0,0 +1,66 @@
+package com.brook.example.java8.pattern;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+public class FactoryPattern {
+
+ final static private Map> map = new HashMap<>();
+
+ static {
+ map.put("cup", Cup::new);
+ map.put("vacuumCup", VacuumCup::new);
+ map.put("wine", Wine::new);
+ }
+
+ @Test
+ void test() {
+ Product p1 = ProductFactory.createProduct("cup");
+ assertThat(p1).isInstanceOf(Cup.class);
+ Supplier wineSupplier = Wine::new;
+ Product p2 = wineSupplier.get();
+ assertThat(p2).isInstanceOf(Wine.class);
+ Product p3 = ProductFactory.createProductLambda("vacuumCup");
+ assertThat(p3).isInstanceOf(VacuumCup.class);
+
+ }
+
+ private interface Product {
+ }
+
+ static private class ProductFactory {
+ public static Product createProduct(String name) {
+ switch (name) {
+ case "cup":
+ return new Cup();
+ case "vacuumCup":
+ return new VacuumCup();
+ case "wine":
+ return new Wine();
+ default:
+ throw new RuntimeException("No such product " + name);
+ }
+ }
+
+ public static Product createProductLambda(String name) {
+ Supplier p = map.get(name);
+ if (p != null) return p.get();
+ throw new RuntimeException("No such product " + name);
+ }
+ }
+
+ static private class Cup implements Product {
+ }
+
+ static private class VacuumCup implements Product {
+ }
+
+ static private class Wine implements Product {
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/pattern/ObserverPattern.java b/learn-java8-example/src/test/java/com/brook/example/java8/pattern/ObserverPattern.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe75bcf3ab85f85ccbe3d0c53d41277c23eeb04d
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/pattern/ObserverPattern.java
@@ -0,0 +1,85 @@
+package com.brook.example.java8.pattern;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ObserverPattern {
+
+ @Test
+ void test() {
+ Feed f = new Feed();
+ f.registerObserver(new NYTimes());
+ f.registerObserver(new Guardian());
+ f.registerObserver(new LeMonde());
+ f.notifyObservers("The queen said her favourite book is Java 8 in Action!");
+
+ Feed feedLambda = new Feed();
+
+ feedLambda.registerObserver((String tweet) -> {
+ if (tweet != null && tweet.contains("money")) {
+ System.out.println("Breaking news in NY! " + tweet);
+ }
+ });
+ feedLambda.registerObserver((String tweet) -> {
+ if (tweet != null && tweet.contains("queen")) {
+ System.out.println("Yet another news in London... " + tweet);
+ }
+ });
+
+ feedLambda.notifyObservers("Money money money, give me money!");
+
+ }
+
+
+ interface Observer {
+ void inform(String tweet);
+ }
+
+ interface Subject {
+ void registerObserver(Observer o);
+
+ void notifyObservers(String tweet);
+ }
+
+ static private class NYTimes implements Observer {
+ @Override
+ public void inform(String tweet) {
+ if (tweet != null && tweet.contains("money")) {
+ System.out.println("Breaking news in NY!" + tweet);
+ }
+ }
+ }
+
+ static private class Guardian implements Observer {
+ @Override
+ public void inform(String tweet) {
+ if (tweet != null && tweet.contains("queen")) {
+ System.out.println("Yet another news in London... " + tweet);
+ }
+ }
+ }
+
+ static private class LeMonde implements Observer {
+ @Override
+ public void inform(String tweet) {
+ if (tweet != null && tweet.contains("wine")) {
+ System.out.println("Today cheese, wine and news! " + tweet);
+ }
+ }
+ }
+
+ static private class Feed implements Subject {
+ private final List observers = new ArrayList<>();
+
+ public void registerObserver(Observer o) {
+ this.observers.add(o);
+ }
+
+ public void notifyObservers(String tweet) {
+ observers.forEach(o -> o.inform(tweet));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/pattern/StrategyPattern.java b/learn-java8-example/src/test/java/com/brook/example/java8/pattern/StrategyPattern.java
new file mode 100755
index 0000000000000000000000000000000000000000..24eb5997f59f27528babe4801a3ad080aaf0e727
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/pattern/StrategyPattern.java
@@ -0,0 +1,55 @@
+package com.brook.example.java8.pattern;
+
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class StrategyPattern {
+
+ public final static String NUMBERIC ="\\d+";
+ public final static String LOWER_CASE ="[a-z]+";
+ @Test
+ void test() {
+ // old school
+ Validator v1 = new Validator(new IsNumeric());
+ assertFalse(v1.validate("abc"));
+ Validator v2 = new Validator(new IsAllLowerCase());
+ assertTrue(v2.validate("abc"));
+
+ // with lambdas
+ Validator v3 = new Validator((String s) -> s.matches(NUMBERIC));
+ assertFalse(v3.validate("abc"));
+ Validator v4 = new Validator((String s) -> s.matches(LOWER_CASE));
+ assertTrue(v4.validate("abc"));
+ }
+
+ interface ValidationStrategy {
+ boolean execute(String s);
+ }
+
+ static private class IsAllLowerCase implements ValidationStrategy {
+ public boolean execute(String s) {
+ return s.matches(LOWER_CASE);
+ }
+ }
+
+ static private class IsNumeric implements ValidationStrategy {
+ public boolean execute(String s) {
+ return s.matches(NUMBERIC);
+ }
+ }
+
+ static private class Validator {
+ private final ValidationStrategy strategy;
+
+ public Validator(ValidationStrategy v) {
+ this.strategy = v;
+ }
+
+ public boolean validate(String s) {
+ return strategy.execute(s);
+ }
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/stream/StreamTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/stream/StreamTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e3a4edd9d69e712b8c835672e93ffd181e6265a
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/stream/StreamTest.java
@@ -0,0 +1,255 @@
+package com.brook.example.java8.stream;
+
+import com.brook.example.java8.domain.Person;
+import com.brook.example.java8.stream.forker.Dish;
+import com.google.common.collect.Lists;
+import com.google.common.math.IntMath;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.TestReporter;
+
+import java.util.*;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.toList;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.DynamicTest.stream;
+
+/**
+ * java8 stream example
+ *
+ * @author Shaojun Liu
+ * @create 2017/7/28
+ */
+@Log4j2
+public class StreamTest {
+
+ @TestFactory
+ Stream range(){
+ // numeric ranges
+ long evenCount = IntStream.rangeClosed(1, 100)
+ .filter(n -> n % 2 == 0)
+ .count();
+
+ assertEquals(50,evenCount);
+ //毕氏三元数;
+
+ Stream pythagoreanTriples =
+ IntStream.rangeClosed(1, 100)
+ .boxed()
+ .flatMap(a -> IntStream.rangeClosed(a, 100)
+ .filter(b -> Math.sqrt(a * a + b * b) % 1 == 0)
+ .boxed()
+ .map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)}));
+ return stream(
+ pythagoreanTriples.iterator()
+ ,ints -> ints[0]+"² +"+ints[1]+"² = "+ints[2]+"²"
+ ,(ints)-> assertEquals(IntMath.pow(ints[2],2), IntMath.pow(ints[0],2)+ IntMath.pow(ints[1],2))
+ );
+ }
+ @Test
+ void min() {
+ int min = IntStream
+ .of(5, 3, 1, 6)
+ .min()
+ .orElse(-1);
+ assertEquals(1, min, "This' min element is 1");
+ }
+
+ /**
+ * 自然排序
+ *
+ * {@link Stream#sorted()}
+ *
+ * @param reporter
+ */
+ @Test
+ void sort(TestReporter reporter) {
+ List sorts = IntStream.of(5, 3, 1, 6)
+ .boxed()
+ .sorted()
+ .collect(Collectors.toList());
+ assertIterableEquals(sorts, Lists.newArrayList(1, 3, 5, 6));
+ reporter.publishEntry("排序(asc)后结果:", sorts.toString());
+ }
+
+ @Test
+ void sortWithComparator(TestReporter reporter) {
+ List sorts = IntStream.of(5, 3, 1, 6)
+ // .map(Integer::valueOf)
+ .boxed() // 源码就是 .map(Integer::valueOf)
+ .sorted(Comparator.reverseOrder())
+ .collect(Collectors.toList());
+ assertIterableEquals(sorts, Lists.newArrayList(6, 5, 3, 1));
+ reporter.publishEntry("排序(desc)后结果:", sorts.toString());
+ }
+
+ @Test
+ void sortWithComparing(TestReporter reporter) {
+ Person tom = Person
+ .builder()
+ .id(1L)
+ .age(20)
+ .name("tom")
+ .build();
+ Person jack = Person.builder()
+ .id(2L)
+ .age(20)
+ .name("jack")
+ .build();
+ List persons = Lists.newArrayList(tom, jack);
+ persons.stream()
+ .sorted(Comparator.comparing(Person::getName)
+ .thenComparing(Person::getAge))
+ .forEach((p) -> reporter.publishEntry(p.getName(), p.getAge() + ""));
+ }
+
+ @Test
+ void listToMap(TestReporter reporter) {
+ List languages = getLanguages();
+ Map result = languages.parallelStream()
+ .collect(Collectors.toMap(Object::toString, String::toUpperCase));
+ reporter.publishEntry(result);
+ }
+
+ private List getLanguages() {
+ return Lists.newArrayList("java", "php", "python");
+ }
+
+ @Test
+ void flatMap(TestReporter reporter) {
+ List> lists = Lists.newArrayList();
+ lists.add(Arrays.asList("apple", "banana", "pear"));
+ lists.add(Arrays.asList("email", "weibo", "qq", "wechat"));
+ lists.add(Arrays.asList("c#", "java", "php", "python"));
+ long total = lists.stream()
+ // flatMap 可以减少一次循环
+ .flatMap(Collection::stream)
+ .filter(str -> str.length() > 3)
+ .count();
+ assertEquals(8, total);
+ }
+
+ @Test
+ void match() {
+ List words = Lists.newArrayList("abc", "acb", "ace");
+ boolean allMathch = words.stream()
+ .allMatch(w -> w.startsWith("a"));
+ assertTrue(allMathch);
+ boolean noneMatch = words.stream()
+ .noneMatch(w -> w.contains("z"));
+ assertTrue(noneMatch, "words does'nt contains z!");
+ }
+
+ @Test
+ void counter() {
+ long count = getLanguages()
+ .stream()
+ .filter((s) -> s.contains("p"))
+ .count();
+ assertEquals(2, count, "The number of languages containing p is 2");
+ }
+
+ @Test
+ void filter() {
+ // 过滤出素食的菜单
+ List vegetarianMenu =
+ Dish.MENU.stream()
+ .filter(Dish::isVegetarian)
+ .collect(toList());
+ assertEquals("french fries", vegetarianMenu.get(0).getName());
+ // 过滤 能量大于300的3个菜单
+ List dishesLimit3 =
+ Dish.MENU.stream()
+ .filter(d -> d.getCalories() > 300)
+ .limit(3)
+ .collect(toList());
+ assertEquals(3, dishesLimit3.size());
+ }
+
+ /**
+ * 分组分片
+ */
+ @Test
+ void groupBy() {
+ Map> localsOfCountry = Stream.of(Locale.getAvailableLocales())
+ .collect(Collectors.groupingBy(Locale::getCountry));
+ localsOfCountry.get("CH")
+ .stream()
+ .map(Locale::getLanguage)
+ .forEach(System.out::println);
+ // 按国家代号统计语言总数
+ Map countLocalsOfCountry = Stream.of(Locale.getAvailableLocales())
+ .collect(Collectors.groupingBy(Locale::getCountry, Collectors.counting()));
+ long count = countLocalsOfCountry.get("CH");
+ assertEquals(3, count);
+ }
+
+ @Test
+ void distinct(TestReporter reporter) {
+ String result = getLanguages()
+ .stream()
+ .map(s -> s.split(""))
+ .flatMap(Stream::of)
+ .distinct()
+ .map(String::valueOf) // 注意: 这里尽可能较少的处理
+ .reduce(String::concat)
+ .orElse("");
+ assertEquals("javphyton", result);
+ reporter.publishEntry("result >>>", result);
+
+ }
+
+ @Test
+ void reduce(TestReporter reporter) {
+ String result = getLanguages().stream()
+ .reduce((a, b) -> a.concat(";").concat(b))
+ .orElse("");
+ assertEquals(result, String.join(";", getLanguages()));
+ reporter.publishEntry("result is >>>", result);
+ int totalWords = getLanguages().stream()
+ .map(String::length)
+ .reduce(Integer::sum)
+ .orElse(0);
+ assertEquals(13, totalWords);
+ }
+
+ @Test
+ void generate() {
+ List result = Stream
+ .generate(new FibonacciSupplier())
+ .peek(log::info)
+ .skip(5)
+ .limit(5)
+ .collect(Collectors.toList());
+ log.info("result is >>> {}", result);
+ }
+
+ @Test
+ void iterate() {
+ Stream.iterate(0, n -> n + 2)
+ .limit(10)
+ .map(n -> n + " ")
+ .forEach(System.out::print);
+ }
+
+
+ class FibonacciSupplier implements Supplier {
+
+ long a = 0;
+ long b = 1;
+
+ @Override
+ public Long get() {
+ long x = a + b;
+ a = b;
+ b = x;
+ return a;
+ }
+ }
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/stream/forker/Dish.java b/learn-java8-example/src/test/java/com/brook/example/java8/stream/forker/Dish.java
new file mode 100755
index 0000000000000000000000000000000000000000..74fc6a7b363227569811b03ca5d9fd68e307f378
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/stream/forker/Dish.java
@@ -0,0 +1,40 @@
+package com.brook.example.java8.stream.forker;
+
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Getter
+public class Dish {
+
+ private final String name;
+ private final boolean vegetarian;
+ private final int calories;
+ private final Type type;
+
+ public Dish(String name, boolean vegetarian, int calories, Type type) {
+ this.name = name;
+ this.vegetarian = vegetarian;
+ this.calories = calories;
+ this.type = type;
+ }
+
+ public enum Type { MEAT, FISH, OTHER }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ public static final List MENU =
+ Arrays.asList( new Dish("pork", false, 800, Dish.Type.MEAT),
+ new Dish("beef", false, 700, Dish.Type.MEAT),
+ new Dish("chicken", false, 400, Dish.Type.MEAT),
+ new Dish("french fries", true, 530, Dish.Type.OTHER),
+ new Dish("rice", true, 350, Dish.Type.OTHER),
+ new Dish("season fruit", true, 120, Dish.Type.OTHER),
+ new Dish("pizza", true, 550, Dish.Type.OTHER),
+ new Dish("prawns", false, 400, Dish.Type.FISH),
+ new Dish("salmon", false, 450, Dish.Type.FISH));
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/stream/forker/StreamForker.java b/learn-java8-example/src/test/java/com/brook/example/java8/stream/forker/StreamForker.java
new file mode 100755
index 0000000000000000000000000000000000000000..d29b025aa5f11ba20ad69abdb60a674a497e9423
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/stream/forker/StreamForker.java
@@ -0,0 +1,175 @@
+package com.brook.example.java8.stream.forker;
+
+import java.util.*;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * 复制流
+ *
+ * {@code StreamForker} 在一个流上执行多个操作。
+ * 因为Java 8中,流有一个非常大的(也可能是最大的)局限性,使用时,对它操作一次仅能得到
+ * 一个处理结果。
+ */
+public class StreamForker {
+
+ private final Stream stream;
+ private final Map, ?>> forks = new HashMap<>();
+
+ public StreamForker(Stream stream) {
+ this.stream = stream;
+ }
+
+
+ public StreamForker fork(Object key, Function, ?> f) {
+ forks.put(key, f);
+ return this;
+ }
+
+ public Results getResults() {
+ ForkingStreamConsumer consumer = build();
+ try {
+ stream.sequential().forEach(consumer);
+ } finally {
+ consumer.finish();
+ }
+ return consumer;
+ }
+
+ /**
+ * 构建一个消费流,把结果放到阻塞队列中
+ * @return
+ */
+ private ForkingStreamConsumer build() {
+ List> queues = new ArrayList<>();
+
+ Map> actions =
+ forks.entrySet().stream().reduce(new HashMap>(),
+ (map, e) -> {
+ map.put(e.getKey(),
+ getOperationResult(queues, e.getValue()));
+ return map;
+ },
+ (m1, m2) -> {
+ m1.putAll(m2); // 经个人测试这的代码没有效果
+ return m1;
+ });
+
+ return new ForkingStreamConsumer<>(queues, actions);
+ }
+
+ /**
+ * 创建一个新的{@code BlockingQueue},并将其添加到队列的列表。 这个队列会被传递给一个
+ * 新的{@code BlockingQueueSpliterator }对象,后者是一个延迟绑定的 {@code Spliterator},
+ * 它会遍历读取队列中的每个元素;
+ * @param queues
+ * @param f
+ * @return
+ */
+ private Future> getOperationResult(List> queues, Function, ?> f) {
+ BlockingQueue queue = new LinkedBlockingQueue<>();
+ queues.add(queue);
+ Spliterator spliterator = new BlockingQueueSpliterator<>(queue);
+ Stream source = StreamSupport.stream(spliterator, false);
+ return CompletableFuture.supplyAsync(() -> f.apply(source));
+ }
+
+ public interface Results {
+ R get(Object key);
+ }
+
+ private static class ForkingStreamConsumer implements Consumer, Results {
+ static final Object END_OF_STREAM = new Object();
+
+ private final List> queues;
+ private final Map> actions;
+
+ ForkingStreamConsumer(List> queues, Map> actions) {
+ this.queues = queues;
+ this.actions = actions;
+ }
+
+ @Override
+ public void accept(T t) {
+ queues.forEach(q -> q.add(t));
+ }
+
+ @Override
+ public R get(Object key) {
+ try {
+ return ((Future) actions.get(key)).get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void finish() {
+ accept((T) END_OF_STREAM);
+ }
+ }
+
+ private static class BlockingQueueSpliterator implements Spliterator {
+ private final BlockingQueue q;
+
+ BlockingQueueSpliterator(BlockingQueue q) {
+ this.q = q;
+ }
+
+ /**
+ *
+ * @implNote 依据 {@link #getOperationResult}方法创建 {@link Spliterator }
+ * 同样的方式,这些元素会被作为进一步处理流的源头传递给Consumer对象 (在流上要执行的
+ * 函数会作为参数传递给某个fork方法调用)。
+ * {@link #tryAdvance(Consumer)} 方法返回true
通知调用方还有其他
+ * 的元素需要处理,直到它发现由 {@code ForkingSteamConsumer} 添加的特殊对象
+ * ,表明队列中已经没有更多需要处理的元素了。
+ * @param action
+ * @return boolean
+ */
+ @Override
+ public boolean tryAdvance(Consumer super T> action) {
+ T t;
+ while (true) {
+ try {
+ t = q.take();
+ break;
+ } catch (InterruptedException e) {
+ }
+ }
+
+ if (t != ForkingStreamConsumer.END_OF_STREAM) {
+ action.accept(t);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 由于无法预测能从队列中取得多少个元素,所以{@code estimatedSize}方法也无法返回
+ * 任何有意 义的值。更进一步,由于你没有试图进行任何切分,所以这时的估算也没什么用处
+ *
+ * @return
+ */
+ @Override
+ public Spliterator trySplit() {
+ return null;
+ }
+
+ @Override
+ public long estimateSize() {
+ return 0;
+ }
+
+ @Override
+ public int characteristics() {
+ return 0;
+ }
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/stream/forker/StreamForkerExample.java b/learn-java8-example/src/test/java/com/brook/example/java8/stream/forker/StreamForkerExample.java
new file mode 100755
index 0000000000000000000000000000000000000000..336ae631c9c335cd194790bf9e49eea48527bb06
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/stream/forker/StreamForkerExample.java
@@ -0,0 +1,43 @@
+package com.brook.example.java8.stream.forker;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class StreamForkerExample {
+
+ @Test
+ void testProcessMenu(){
+ processMenu();
+ }
+ private static void processMenu() {
+ Stream menuStream = Dish.MENU.stream();
+
+ StreamForker.Results results = new StreamForker(menuStream)
+ // 获取菜单
+ .fork("shortMenu", s -> s.map(Dish::getName).collect(joining(", ")))
+ // 统计卡路里
+ .fork("totalCalories", s -> s.mapToInt(Dish::getCalories).sum())
+ // 哪个菜单的热量最多
+ .fork("mostCaloricDish", s -> s.reduce((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)
+ .get())
+ // 按类型分组
+ .fork("dishesByType", s -> s.collect(groupingBy(Dish::getType)))
+ .getResults();
+
+ String shortMenu = results.get("shortMenu");
+ int totalCalories = results.get("totalCalories");
+ Dish mostCaloricDish = results.get("mostCaloricDish");
+ Map> dishesByType = results.get("dishesByType");
+
+ assertEquals(Dish.MENU.size(),shortMenu.split(",").length);
+ assertEquals(4300, totalCalories);
+ assertEquals("pork",mostCaloricDish.getName());
+ assertEquals(Dish.Type.values().length,dishesByType.size());
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/stream/spliterator/NumCounter.java b/learn-java8-example/src/test/java/com/brook/example/java8/stream/spliterator/NumCounter.java
new file mode 100644
index 0000000000000000000000000000000000000000..c88838eb6c5421a8b5fbe113585993bad020c40b
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/stream/spliterator/NumCounter.java
@@ -0,0 +1,36 @@
+package com.brook.example.java8.stream.spliterator;
+
+/**
+ * 字符串中的数字计算器实现
+ */
+public class NumCounter {
+
+ private int num;
+ private int sum;
+ // 是否是一个完整的数字
+ private boolean isWholeNum;
+
+ public NumCounter(int num, int sum, boolean isWholeNum) {
+ this.num = num;
+ this.sum = sum;
+ this.isWholeNum = isWholeNum;
+ }
+
+ public NumCounter accumulate(Character c) {
+ if (Character.isDigit(c)) {
+ return isWholeNum ? new NumCounter(Integer.parseInt("" + c),
+ sum + num, false)
+ : new NumCounter(Integer.parseInt("" + num + c), sum, false);
+ } else {
+ return new NumCounter(0, sum + num, true);
+ }
+ }
+
+ public NumCounter combine(NumCounter numCounter) {
+ return new NumCounter(numCounter.num, this.getSum() + numCounter.getSum(), numCounter.isWholeNum);
+ }
+
+ public int getSum() {
+ return sum + num;
+ }
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/stream/spliterator/NumCounterSpliterator.java b/learn-java8-example/src/test/java/com/brook/example/java8/stream/spliterator/NumCounterSpliterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..a79641dc4d3a0765bc753c418bf84c8839dd6871
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/stream/spliterator/NumCounterSpliterator.java
@@ -0,0 +1,62 @@
+package com.brook.example.java8.stream.spliterator;
+
+import java.util.Spliterator;
+import java.util.function.Consumer;
+
+/**
+ * Spliterator 详细参考 http://blog.csdn.net/anonymousprogrammer/article/details/76034365
+ */
+class NumCounterSpliterator implements Spliterator {
+
+ private String str;
+ private int currentChar = 0;
+
+ public NumCounterSpliterator(String str) {
+ this.str = str;
+ }
+
+ /**
+ * @param action
+ * @return
+ */
+ @Override
+ public boolean tryAdvance(Consumer super Character> action) {
+ action.accept(str.charAt(currentChar++));
+ return currentChar < str.length();
+ }
+
+ @Override
+ public Spliterator trySplit() {
+
+ int currentSize = str.length() - currentChar;
+ if (currentSize < 10) return null;
+
+ for (int pos = currentSize/2 + currentSize; pos < str.length(); pos++){
+ if (pos+1 < str.length()){
+ // 当前Character是数字,且下一个Character不是数字,才需要划分一个新的Spliterator
+ if (Character.isDigit(str.charAt(pos)) && !Character.isDigit(str.charAt(pos+1))){
+ Spliterator spliterator = new NumCounterSpliterator(str.substring(currentChar, pos));
+ currentChar = pos;
+ return spliterator;
+ }
+ }else {
+ if (Character.isDigit(str.charAt(pos))){
+ Spliterator spliterator = new NumCounterSpliterator(str.substring(currentChar, pos));
+ currentChar = pos;
+ return spliterator;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public long estimateSize() {
+ return str.length() - currentChar;
+ }
+
+ @Override
+ public int characteristics() {
+ return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE;
+ }
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/stream/spliterator/NumCounterTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/stream/spliterator/NumCounterTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..fef022c4a0d9410142e5de074810b5ad52a9a158
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/stream/spliterator/NumCounterTest.java
@@ -0,0 +1,36 @@
+package com.brook.example.java8.stream.spliterator;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Spliterator;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/31
+ */
+public class NumCounterTest {
+ final static String arr = "12%3 21sdas s34d dfsdz45 R3 jo34 sjkf8 3$1P 213ikflsd fdg55 kfd";
+
+ private static int countNum(Stream stream) {
+ NumCounter numCounter = stream
+ .reduce(new NumCounter(0, 0, false)
+ , NumCounter::accumulate
+ ,NumCounter::combine);
+ return numCounter.getSum();
+ }
+
+ @Test
+ void counter() {
+ Stream stream = IntStream.range(0, arr.length()).mapToObj(arr::charAt);
+ System.out.println("ordered total: " + countNum(stream));
+
+ Spliterator spliterator = new NumCounterSpliterator(arr);
+ // 传入true表示是并行流
+ Stream parallelStream = StreamSupport.stream(spliterator, true);
+ System.out.println("parallel total: " + countNum(parallelStream));
+ }
+
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/time/ClockTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/time/ClockTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c68bcf631ac7afad7fe3fae02d80a8ace55286c
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/time/ClockTest.java
@@ -0,0 +1,25 @@
+package com.brook.example.java8.time;
+
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.Test;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Date;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/29
+ */
+@Log4j2
+public class ClockTest {
+ @Test
+ void clock() {
+ Clock clock = Clock.systemDefaultZone();
+ log.info("current millis is {}",clock.millis());
+ Instant instant = clock.instant();
+ Date now = Date.from(instant);
+ log.info("current times is {}",now.getTime());
+
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/time/DateConvertUtils.java b/learn-java8-example/src/test/java/com/brook/example/java8/time/DateConvertUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..204f57ae221d11511115e29ea3ecc3fb69102cc4
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/time/DateConvertUtils.java
@@ -0,0 +1,88 @@
+package com.brook.example.java8.time;
+
+import java.sql.Date;
+import java.time.*;
+
+public class DateConvertUtils {
+ /**
+ * Calls {@link #asLocalDate(java.util.Date, ZoneId)} with the system default time zone.
+ */
+ public static LocalDate asLocalDate(java.util.Date date) {
+ return asLocalDate(date, ZoneId.systemDefault());
+ }
+
+ /**
+ * Creates {@link LocalDate} from {@code java.util.Date} or it's subclasses. Null-safe.
+ */
+ public static LocalDate asLocalDate(java.util.Date date, ZoneId zone) {
+ if (date == null) return null;
+ if (date instanceof java.sql.Date) return ((java.sql.Date) date).toLocalDate();
+ else return Instant.ofEpochMilli(date.getTime()).atZone(zone).toLocalDate();
+ }
+
+ /**
+ * Calls {@link #asLocalDateTime(java.util.Date)} (Date, ZoneId)} with the system default time zone.
+ */
+ public static LocalDateTime asLocalDateTime(java.util.Date date) {
+ return asLocalDateTime(date, ZoneId.systemDefault());
+ }
+
+ /**
+ * Creates {@link java.time.LocalDateTime}
+ * from {@code java.util.Date} or it's subclasses. Null-safe.
+ */
+ public static LocalDateTime asLocalDateTime(java.util.Date date, ZoneId zone) {
+ if (date == null) return null;
+ if (date instanceof java.sql.Timestamp)
+ return ((java.sql.Timestamp) date).toLocalDateTime();
+ else return Instant.ofEpochMilli(date.getTime()).atZone(zone).toLocalDateTime();
+ }
+
+ /**
+ * Calls {@link #asUtilDate(Object, ZoneId)} with the system default time zone.
+ */
+ public static java.util.Date asUtilDate(Object date) {
+ return asUtilDate(date, ZoneId.systemDefault());
+ }
+
+ /**
+ * Creates a {@link java.util.Date} from various date objects. Is null-safe. Currently supports: * {@link java.util.Date} * {@link java.sql.Date} * {@link java.sql.Timestamp} * {@link java.time.LocalDate} * {@link java.time.LocalDateTime} * {@link java.time.ZonedDateTime} * {@link java.time.Instant} * * * @param zone Time zone, used only if the input object is LocalDate or LocalDateTime. * * @return {@link java.util.Date} (exactly this class, not a subclass, such as java.sql.Date)
+ */
+ public static java.util.Date asUtilDate(Object date, ZoneId zone) {
+ if (date == null) return null;
+ if (date instanceof java.sql.Date || date instanceof java.sql.Timestamp)
+ return new java.util.Date(((java.util.Date) date).getTime());
+ if (date instanceof java.util.Date) return (java.util.Date) date;
+ if (date instanceof LocalDate)
+ return java.util.Date.from(((LocalDate) date).atStartOfDay(zone).toInstant());
+ if (date instanceof LocalDateTime)
+ return java.util.Date.from(((LocalDateTime) date).atZone(zone).toInstant());
+ if (date instanceof ZonedDateTime)
+ return java.util.Date.from(((ZonedDateTime) date).toInstant());
+ if (date instanceof Instant) return java.util.Date.from((Instant) date);
+ throw new UnsupportedOperationException("Don't know hot to convert " + date.getClass().getName() + " to java.util.Date");
+ }
+
+ /**
+ * Creates an {@link Instant} from {@code java.util.Date} or it's subclasses. Null-safe.
+ */
+ public static Instant asInstant(Date date) {
+ if (date == null) return null;
+ else return Instant.ofEpochMilli(date.getTime());
+ }
+
+ /**
+ * Calls {@link #asZonedDateTime(Date, ZoneId)} with the system default time zone.
+ */
+ public static ZonedDateTime asZonedDateTime(Date date) {
+ return asZonedDateTime(date, ZoneId.systemDefault());
+ }
+
+ /**
+ * Creates {@link ZonedDateTime} from {@code java.util.Date} or it's subclasses. Null-safe.
+ */
+ public static ZonedDateTime asZonedDateTime(Date date, ZoneId zone) {
+ if (date == null) return null;
+ else return asInstant(date).atZone(zone);
+ }
+}
\ No newline at end of file
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/time/DateTimeTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/time/DateTimeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a449001ccef30956b40d45e91851ef9343648b0
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/time/DateTimeTest.java
@@ -0,0 +1,163 @@
+package com.brook.example.java8.time;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.chrono.JapaneseDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.*;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+import static java.time.temporal.TemporalAdjusters.lastDayOfMonth;
+import static java.time.temporal.TemporalAdjusters.nextOrSame;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/31
+ */
+public class DateTimeTest {
+
+ public static final String DATE_PATTERN = "yyyy-MM-dd";
+ public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+ private static final ThreadLocal formatters =
+ ThreadLocal.withInitial(() -> new SimpleDateFormat(DATETIME_PATTERN));
+
+ @Test
+ void useTemporalAdjuster() {
+ LocalDate date = LocalDate.of(2014, 3, 18);
+ date = date.with(nextOrSame(DayOfWeek.SUNDAY));
+ assertThat(date.toString()).isEqualTo("2014-03-23");
+ date = date.with(lastDayOfMonth());
+ assertThat(date.toString()).isEqualTo("2014-03-31");
+
+ date = date.with(new NextWorkingDay());
+ assertThat(date.toString()).isEqualTo("2014-04-01");
+ date = date.with(nextOrSame(DayOfWeek.FRIDAY));
+ assertThat(date).isEqualTo("2014-04-04");
+ date = date.with(new NextWorkingDay());
+ assertThat(date).isEqualTo("2014-04-07");
+
+ date = date.with(nextOrSame(DayOfWeek.FRIDAY));
+ assertThat(date).isEqualTo("2014-04-11");
+ date = date.with(temporal -> {
+ DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
+ int dayToAdd = 1;
+ if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;
+ if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
+ return temporal.plus(dayToAdd, ChronoUnit.DAYS);
+ });
+ assertThat(date).isEqualTo("2014-04-14");
+ }
+
+ @Test
+ void useDateFormatter() {
+ LocalDate date = LocalDate.of(2017, 7, 7);
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
+ DateTimeFormatter chinaFormatter = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.CHINA);
+
+ assertThat(date.format(DateTimeFormatter.ISO_LOCAL_DATE)).isEqualTo("2017-07-07");
+ assertThat(date.format(formatter)).isEqualTo("07/07/2017");
+ assertThat(date.format(chinaFormatter)).isEqualTo("七月 2017");
+
+ DateTimeFormatter complexFormatter = new DateTimeFormatterBuilder()
+ .appendText(ChronoField.DAY_OF_MONTH)
+ .appendLiteral(" ")
+ .appendText(ChronoField.MONTH_OF_YEAR)
+ .appendLiteral(" ")
+ .appendText(ChronoField.YEAR)
+ .parseCaseInsensitive()
+ .toFormatter(Locale.CHINESE);
+
+ assertThat(date.format(complexFormatter)).isEqualTo("7 七月 2017");
+ }
+
+ @Test
+ void useOldDate() {
+ Date date = new Date(114, 2, 18);
+ System.out.println(formatters.get().format(date));
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(2014, Calendar.FEBRUARY, 18);
+ assertThat(calendar.get(Calendar.MONTH)).isEqualTo(1);
+ }
+
+ @Test
+ void useLocalDate() {
+ LocalDate date = LocalDate.of(2014, 3, 18);
+ int year = date.getYear();
+ assertThat(year).isEqualTo(2014);
+ Month month = date.getMonth(); // MARCH
+ assertThat(month).isEqualTo(Month.MARCH);
+ int day = date.getDayOfMonth();
+ assertThat(day).isEqualTo(18);
+ DayOfWeek week = date.getDayOfWeek();
+ assertThat(week).isEqualTo(DayOfWeek.TUESDAY);
+ int len = date.lengthOfMonth();
+ assertThat(len).isEqualTo(31);
+ boolean leap = date.isLeapYear();
+ assertThat(leap).isFalse();
+
+ int y = date.get(ChronoField.YEAR);
+ int m = date.get(ChronoField.MONTH_OF_YEAR);
+ int d = date.get(ChronoField.DAY_OF_MONTH);
+ Assertions.assertAll("LocalDate ",()->{
+ Assertions.assertEquals(2014, y);
+ Assertions.assertEquals(3, m);
+ Assertions.assertEquals(18, d);
+ });
+ LocalTime time = LocalTime.of(13, 45, 20);
+ assertThat(time.toString()).isEqualTo("13:45:20");
+
+ LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);
+ LocalDateTime dt2 = LocalDateTime.of(date, time);
+ LocalDateTime dt3 = date.atTime(13, 45, 20);
+ LocalDateTime dt4 = date.atTime(time);
+ LocalDateTime dt5 = time.atDate(date);
+ assertThat(dt1).isEqualTo( "2014-03-18T13:45:20");
+ assertThat(dt1).isEqualTo(dt2)
+ .isEqualTo(dt3)
+ .isEqualTo(dt4)
+ .isEqualTo(dt5);
+ Instant instant = Instant.ofEpochSecond(44 * 365 * 86400);
+ Instant now = Instant.now();
+
+ Duration d1 = Duration.between(LocalTime.of(13, 45, 10), time);
+ Duration d2 = Duration.between(instant, now);
+ // Duration 和 java.time.Period 区别
+ // Duration 表示 LocalDateTime 而 Period 只是LocalDate
+ assertThat(d1.getSeconds()).isEqualTo(10);
+
+ Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
+ assertThat(threeMinutes.getSeconds()).isEqualTo(180);
+ JapaneseDate japaneseDate = JapaneseDate.from(date);
+ System.out.println(japaneseDate);
+ }
+ @Test
+ void convert(){
+ LocalDateTime localDateTime = LocalDateTime.now();
+ Date date = DateConvertUtils.asUtilDate(localDateTime);
+ Date date1 = new Date();
+ assertThat(date).isEqualToIgnoringSeconds(date1);
+ String localDateTimeStr = DateConvertUtils.asLocalDateTime(date1)
+ .format(DateTimeFormatter.ofPattern(DATETIME_PATTERN));
+ assertThat(localDateTimeStr).isEqualTo(localDateTime
+ .format(DateTimeFormatter.ofPattern(DATETIME_PATTERN)));
+ }
+
+ private static class NextWorkingDay implements TemporalAdjuster {
+ @Override
+ public Temporal adjustInto(Temporal temporal) {
+ DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
+ int dayToAdd = 1;
+ if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;
+ if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
+ return temporal.plus(dayToAdd, ChronoUnit.DAYS);
+ }
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/time/TimezoneTest.java b/learn-java8-example/src/test/java/com/brook/example/java8/time/TimezoneTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..11aa38a2bb93e674675ec5bf687f4f589634a9a9
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/time/TimezoneTest.java
@@ -0,0 +1,22 @@
+package com.brook.example.java8.time;
+
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.Test;
+
+import java.time.ZoneId;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/29
+ */
+@Log4j2
+class TimezoneTest {
+ @Test
+ void test() {
+ log.info("zoneIds is {}", ZoneId.getAvailableZoneIds());
+ ZoneId zone1 = ZoneId.of("Europe/Berlin");
+ ZoneId zone2 = ZoneId.of("Brazil/East");
+ log.info("zone1 rules is {} ", zone1.getRules());
+ log.info("zone1 rules is {} ", zone2.getRules());
+ }
+}
diff --git a/learn-java8-example/src/test/java/com/brook/example/java8/util/Base64Test.java b/learn-java8-example/src/test/java/com/brook/example/java8/util/Base64Test.java
new file mode 100644
index 0000000000000000000000000000000000000000..33fb4f6609879c25561bcabb1828f8b9065f1f88
--- /dev/null
+++ b/learn-java8-example/src/test/java/com/brook/example/java8/util/Base64Test.java
@@ -0,0 +1,30 @@
+package com.brook.example.java8.util;
+
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestReporter;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author Shaojun Liu
+ * @create 2017/7/30
+ */
+@Log4j2
+class Base64Test {
+ @Test
+ void base64(TestReporter reporter) {
+ final String text = "Base64 for Java 8!";
+ final String encoded = Base64
+ .getEncoder()
+ .encodeToString(text.getBytes(StandardCharsets.UTF_8));
+ log.info("base64 encoded {}", encoded);
+ final String decoded = new String(
+ Base64.getDecoder().decode(encoded),
+ StandardCharsets.UTF_8);
+ assertEquals(text, decoded);
+ }
+}
diff --git a/pom.xml b/pom.xml
index 6c99cabe9206ce13a53de2f97b4a365820f0fba8..4f58ea2a39a5ecb24561fe65de2cbafde72dcbd0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,6 +17,7 @@
disruptor-example
axon-cqrs-example
utils
+ learn-java8-example
diff --git a/utils/pom.xml b/utils/pom.xml
index 53e2eeae8be122e2b167db19f083b441fe0736c3..65b883cd2172cd8f0b9a84aaa5214b2852090fc2 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -1,9 +1,10 @@
- example
com.brook.example
+ example
1.0-SNAPSHOT
+ pom.xml
4.0.0
@@ -17,5 +18,16 @@
UTF-8
-
+
+
+ org.apache.commons
+ commons-io
+ 1.3.2
+
+
+ com.google.guava
+ guava
+ 20.0
+
+
diff --git a/utils/src/main/java/com/brook/example/utils/io/FileCharset.java b/utils/src/main/java/com/brook/example/utils/io/FileCharset.java
new file mode 100644
index 0000000000000000000000000000000000000000..17dc81cb544db18f7a209f651ad7633a684d74d4
--- /dev/null
+++ b/utils/src/main/java/com/brook/example/utils/io/FileCharset.java
@@ -0,0 +1,104 @@
+package com.brook.example.utils.io;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.*;
+
+/**
+ *
+ * 判断文件编码 不保证100%正确性 但测试结果是一般的文件没问题
+ *
+ * 只能判断常见的GBK,UTF-16LE,UTF-16BE,UTF-8,其分别对应window下的记事本可另存为的编码类型ANSI,Unicode,Unicode big endian,UTF-8
+ *
+ * @author Shaojun Liu
+ * @create 2017/7/28
+ */
+public interface FileCharset {
+ String DEFAULT_CHARSET = "GBK";
+
+ static String getCharset(String fileName) {
+ return getCharset(new File(fileName));
+ }
+
+ /**
+ * 只能判断常见的GBK,UTF-16LE,UTF-16BE,UTF-8,
+ * 其分别对应window下的记事本可另存为的
+ * 编码类型ANSI,Unicode,Unicode big endian,UTF-8
+ *
+ * @param file
+ * @return
+ */
+ static String getCharset(File file) {
+ InputStream is = null;
+ try {
+ is = new FileInputStream(file);
+ return getCharset(new BufferedInputStream(is));
+ } catch (FileNotFoundException e) {
+ return DEFAULT_CHARSET;
+ } finally {
+ IOUtils.closeQuietly(is);
+ }
+ }
+
+ static String getCharset(final BufferedInputStream is) {
+ String charset = DEFAULT_CHARSET;
+ byte[] first3Bytes = new byte[3];
+ try {
+ boolean checked = false;
+ is.mark(0);
+ int read = is.read(first3Bytes, 0, 3);
+ if (read == -1)
+ return charset;
+ if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) {
+ charset = "UTF-16LE";
+ checked = true;
+ } else if (first3Bytes[0] == (byte) 0xFE
+ && first3Bytes[1] == (byte) 0xFF) {
+ charset = "UTF-16BE";
+ checked = true;
+ } else if (first3Bytes[0] == (byte) 0xEF
+ && first3Bytes[1] == (byte) 0xBB
+ && first3Bytes[2] == (byte) 0xBF) {
+ charset = "UTF-8";
+ checked = true;
+ }
+ is.reset();
+ if (!checked) {
+ int loc = 0;
+
+ while ((read = is.read()) != -1 && loc < 100) {
+ loc++;
+ if (read >= 0xF0)
+ break;
+ if (0x80 <= read && read <= 0xBF) // 单独出现BF以下的,也算是GBK
+ break;
+ if (0xC0 <= read && read <= 0xDF) {
+ read = is.read();
+ if (0x80 <= read && read <= 0xBF) // 双字节 (0xC0 - 0xDF)
+ // (0x80
+ // - 0xBF),也可能在GB编码内
+ continue;
+ else
+ break;
+ } else if (0xE0 <= read && read <= 0xEF) {// 也有可能出错,但是几率较小
+ read = is.read();
+ if (0x80 <= read && read <= 0xBF) {
+ read = is.read();
+ if (0x80 <= read && read <= 0xBF) {
+ charset = "UTF-8";
+ break;
+ } else
+ break;
+ } else
+ break;
+ }
+ }
+ }
+ is.reset();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return charset;
+ }
+}
diff --git a/utils/src/main/java/com/brook/example/utils/io/RemoteFileFetcher.java b/utils/src/main/java/com/brook/example/utils/io/RemoteFileFetcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..70c04c42335d2a77171b863477745c98b98efe03
--- /dev/null
+++ b/utils/src/main/java/com/brook/example/utils/io/RemoteFileFetcher.java
@@ -0,0 +1,79 @@
+package com.brook.example.utils.io;
+
+import com.brook.example.utils.lang.Console;
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 定时抓取远程文件
+ * @author brook
+ * @create 2017/7/7
+ */
+public class RemoteFileFetcher {
+
+ private static final ScheduledExecutorService scheduledExecutorService = Executors
+ .newSingleThreadScheduledExecutor(r -> new Thread(r, "RemoteFileFetcher_Schedule_Thread"));
+ private byte[] fileConent;
+ private String url;
+ private long lastModified;
+ private int connectTimeout;
+ private int readTimeout;
+ private FileChangeListener listener;
+
+ private RemoteFileFetcher(String url, int reloadInterval, FileChangeListener listener) {
+ this.connectTimeout = 1000;
+ this.readTimeout = 1000;
+
+ this.url = url;
+ this.listener = listener;
+ if (reloadInterval > 0) {
+ scheduledExecutorService.scheduleWithFixedDelay(
+ RemoteFileFetcher.this::doFetch,
+ reloadInterval, reloadInterval, TimeUnit.MILLISECONDS);
+ }
+ doFetch();
+ }
+
+ private void doFetch() {
+ if (url == null) {
+ return;
+ }
+ Console.log("Begin fetch remote file... url = {}", this.url);
+ try {
+ URL target = new URL(this.url);
+ this.fileConent = IOUtils.toByteArray((InputStream) target.getContent());
+ this.lastModified = System.currentTimeMillis();
+ if (this.listener != null && this.fileConent != null) {
+ this.listener.fileReloaded(this.fileConent);
+ }
+ } catch (Exception e) {
+ Console.error("read from url failed", e);
+ }
+ }
+
+ public static RemoteFileFetcher createPeriodFetcher(String url,
+ int reloadInterval,
+ FileChangeListener listener) {
+
+ return new RemoteFileFetcher(url, reloadInterval, listener);
+
+ }
+
+ public long getLastModified() {
+ return this.lastModified;
+ }
+
+ public byte[] getFileByteArray() {
+ return this.fileConent;
+ }
+
+ public interface FileChangeListener {
+ void fileReloaded(byte[] contentBytes) throws IOException;
+ }
+}
\ No newline at end of file
diff --git a/utils/src/main/java/com/brook/example/utils/lang/ArrayUtil.java b/utils/src/main/java/com/brook/example/utils/lang/ArrayUtil.java
index 5e6da6f87a412176b02b7dbfddcbaede51b66610..b90622834d0d02f9859e147eb5166675a3449d51 100644
--- a/utils/src/main/java/com/brook/example/utils/lang/ArrayUtil.java
+++ b/utils/src/main/java/com/brook/example/utils/lang/ArrayUtil.java
@@ -1,7 +1,6 @@
package com.brook.example.utils.lang;
import com.brook.example.utils.text.Strs;
-import com.sun.xml.internal.ws.util.UtilException;
import java.util.Arrays;
import java.util.Iterator;
@@ -52,7 +51,7 @@ public interface ArrayUtil {
case "double":
return Arrays.toString((double[]) obj);
default:
- throw new UtilException(e);
+ throw new RuntimeException(e);
}
}
}
@@ -105,7 +104,7 @@ public interface ArrayUtil {
*
* @param obj 对象,可以是对象数组或者基本类型数组
* @return 包装类型数组或对象数组
- * @throws UtilException 对象为非数组
+ * @throws RuntimeException 对象为非数组
*/
static Object[] wrap(Object obj) {
if (isArray(obj)) {
@@ -131,11 +130,11 @@ public interface ArrayUtil {
case "double":
return wrap(obj);
default:
- throw new UtilException(e);
+ throw new RuntimeException(e);
}
}
}
- throw new UtilException(Strs.format("[{}] is not Array!", obj.getClass()));
+ throw new RuntimeException(Strs.format("[{}] is not Array!", obj.getClass()));
}
}
diff --git a/utils/src/main/java/com/brook/example/utils/text/ForbiddenWordUtils.java b/utils/src/main/java/com/brook/example/utils/text/ForbiddenWordUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..44dad3cd92984e5de0f282467dc4b9dfd27883ec
--- /dev/null
+++ b/utils/src/main/java/com/brook/example/utils/text/ForbiddenWordUtils.java
@@ -0,0 +1,140 @@
+package com.brook.example.utils.text;
+
+import com.brook.example.utils.io.RemoteFileFetcher;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import lombok.experimental.UtilityClass;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.io.IOUtils;
+
+import java.io.*;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * 屏蔽关键词 工具类
+ * @author brook
+ * @create 2017/7/7
+ */
+@Log4j2
+@UtilityClass
+public class ForbiddenWordUtils {
+
+ /**
+ * 默认的遮罩文字
+ */
+ private static final String DEFAULT_MASK = "***";
+ /**
+ * 屏蔽关键词抓取的url
+ */
+ private static String forbiddenWordFetchURL;
+
+ /**
+ * 屏蔽关键词抓取时间间隔 毫秒
+ */
+ private static int reloadInterval = 60000; //10分钟
+
+ /**
+ * 屏蔽关键词
+ */
+ private static List forbiddenWords;
+
+ public static void setForbiddenWordFetchURL(String forbiddenWordFetchURL) {
+ ForbiddenWordUtils.forbiddenWordFetchURL = forbiddenWordFetchURL;
+ }
+
+ public static void setReloadInterval(int reloadInterval) {
+ ForbiddenWordUtils.reloadInterval = reloadInterval;
+ }
+
+ /**
+ * 替换input中的屏蔽关键词为默认的掩码
+ *
+ * @param input
+ * @return
+ */
+ public static String replace(String input) {
+ return replace(input, DEFAULT_MASK);
+ }
+
+ /**
+ * 将屏蔽关键词 替换为 mask
+ *
+ * @param input
+ * @param mask
+ * @return
+ */
+ public static String replace(String input, String mask) {
+ for (int i = 0, l = forbiddenWords.size(); i < l; i++) {
+ Pattern forbiddenWordPattern = forbiddenWords.get(i);
+ input = forbiddenWordPattern.matcher(input).replaceAll(mask);
+ }
+ return input;
+ }
+
+
+ /**
+ * 是否包含屏蔽关键词
+ *
+ * @param input
+ * @return
+ */
+ public static boolean containsForbiddenWord(String input) {
+ for (int i = 0, l = forbiddenWords.size(); i < l; i++) {
+ Pattern forbiddenWordPattern = forbiddenWords.get(i);
+ if (forbiddenWordPattern.matcher(input).find()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ static {
+ InputStream is = null;
+ try {
+ String fileName = "forbidden.txt";
+ is = ForbiddenWordUtils.class.getClassLoader().getResourceAsStream(fileName);
+ byte[] fileCBytes;
+ fileCBytes = IOUtils.toByteArray(is);
+ ForbiddenWordUtils.loadForbiddenWords(fileCBytes);
+ } catch (IOException e) {
+ log.error("read forbidden file failed", e);
+ } finally {
+ IOUtils.closeQuietly(is);
+ }
+
+ }
+
+ /**
+ * 初始化远程抓取配置
+ */
+ public static void initRemoteFetch() {
+ RemoteFileFetcher.createPeriodFetcher(
+ forbiddenWordFetchURL,
+ reloadInterval,
+ fileConent -> ForbiddenWordUtils.loadForbiddenWords(fileConent));
+ }
+
+ private static void loadForbiddenWords(byte[] fileCBytes) throws IOException {
+ Reader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(fileCBytes), Charsets.UTF_8));
+ List forbiddenWordsStrList = IOUtils.readLines(reader);
+ forbiddenWords = Lists.newArrayList();
+ for (int i = forbiddenWordsStrList.size() - 1; i >= 0; i--) {
+ String forbiddenWord = forbiddenWordsStrList.get(i).trim();
+ if (forbiddenWord.length() == 0 || forbiddenWord.startsWith("#")) {
+ continue;
+ } else {
+ forbiddenWords.add(Pattern.compile(forbiddenWord));
+ }
+ }
+ } catch (Exception e) {
+ log.error("load forbidden words failed", e);
+ } finally {
+ IOUtils.closeQuietly(reader);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/utils/src/main/resources/forbidden.txt b/utils/src/main/resources/forbidden.txt
new file mode 100644
index 0000000000000000000000000000000000000000..88da85160ca6bca0df6d0ed2bb6b7e3289c2bba0
--- /dev/null
+++ b/utils/src/main/resources/forbidden.txt
@@ -0,0 +1,1030 @@
+#google的
+马驰.*新加坡
+新加坡.*马驰
+自由光诚
+陈光诚事件
+光诚.*沂南
+沂南.*光诚
+陈光诚.*使馆
+使馆.*陈光诚
+职称英语.*答案
+答案.*职称英语
+公务员.*答案
+答案.*公务员
+薄瓜瓜
+海伍德
+尼尔伍德
+heywood
+neil.*wood
+wood.*neil
+天线宝宝.*康师傅
+康师傅.*天线宝宝
+天线宝宝.*方便面
+方便面.*天线宝宝
+天线宝宝.*轮胎
+轮胎.*天线宝宝
+轮胎.*方便面
+方便面.*轮胎
+政变
+枪声
+戒严
+3\.19
+北京事件
+北京.*出事了
+出事了.*北京
+北京怎么了
+不厚
+薄督
+谷开来
+重庆
+叶城.*砍杀
+砍杀.*叶城
+连承敏
+弟弟.*睡
+睡.*弟弟
+要有光.*要有诚
+要有诚.*要有光
+杨杰
+陈刚
+山水文园
+跑官
+移动.*十年兴衰
+十年兴衰.*移动
+陈坚
+戴坚
+冯珏
+罗川
+马力
+盛勇
+谢岷
+谢文
+杨希
+叶兵
+张斌
+陈瑞卿
+高念书
+华如秀
+鲁向东
+曲乃杰
+孙静晔
+涂志森
+于剑鸣
+张晓明
+赵志强
+郑建源
+先皇
+太上皇
+蛤蟆
+驾崩
+丘小雄
+公诉
+右派
+增城
+暴动
+宣言
+莫日根
+内蒙古.*抗议
+抗议.*内蒙古
+西乌旗
+方滨兴
+moli
+麦当劳
+天府
+人民公园
+广场
+埃及
+突尼斯
+茉莉
+jasmine.*revolution
+revolution.*jasmine
+集会
+革命
+齐鲁银行
+公开信
+高考时间
+诺贝尔和平奖
+被就业
+小屋
+日记
+鲁昕
+天安.*事件
+事件.*天安
+1989.*天安門
+天安門.*1989
+天安门
+八九
+六四
+六 四
+六\.四
+平反64
+5月35日
+5月35号
+89动乱
+89.*学生动乱
+学生动乱.*89
+89.*学生运动
+学生运动.*89
+64.*学生运动
+学生运动.*64
+64.*镇压
+镇压.*64
+64.*真相
+真相.*64
+64memo
+tiananmen
+8964
+学潮
+罢课
+民运
+学运
+学联
+学自联
+高自联
+工自联
+民联
+民阵
+中国民主党
+中国民主正义党
+中国民主运动
+世纪中国基金会
+坦克人
+挡坦克
+tankman
+木犀地
+维园晚会
+blood is on the square
+姜维平
+艾未未
+艾末末
+路青
+发课
+余杰
+辛子陵
+茅于轼
+铁流
+liu.*xiaobo
+xiaobo.*liu
+蟹农场
+陈西
+谭作人
+高智晟
+冯正虎
+丁子霖
+唯色
+焦国标
+何清涟
+耀邦
+紫阳
+方励之
+严家其
+鲍彤
+鮑彤
+鲍朴
+柴玲
+乌尔凯西
+封从德
+炳章
+苏绍智
+陈一谘
+韩东方
+辛灏年
+曹长青
+陈破空
+盘古乐队
+盛雪
+伍凡
+魏京生
+司徒华
+黎安友
+防火长城
+great.*firewall
+firewall.*great
+gfw.*什么
+什么.*gfw
+国家防火墙
+翻墙
+代理
+vpn.*免费
+免费.*vpn
+vpn.*下载
+下载.*vpn
+vpn.*世纪
+世纪.*vpn
+hotspot.*shield
+shield.*hotspot
+无界
+ultrasurf
+^freenet
+safeweb
+动态网
+花园网
+^cache
+阅后即焚
+法轮
+falun
+明慧
+minghui
+退党
+三退
+九评
+nine commentaries
+洪吟
+神韵艺术
+神韵晚会
+人民报
+renminbao
+纪元
+^dajiyuan
+epochtimes
+新唐人
+ntdtv
+ndtv
+新生网
+^xinsheng
+正见网
+zhengjian
+追查国际
+真善忍
+法会
+正念
+经文
+天灭
+天怒
+讲真相
+马三家
+善恶有报
+活摘器官
+群体灭绝
+中功
+张宏堡
+地下教会
+冤民大同盟
+达赖
+藏独
+freetibet
+雪山狮子
+西藏流亡政府
+青天白日旗
+民进党
+洪哲胜
+独立台湾会
+台湾政论区
+台湾自由联盟
+台湾建国运动组织
+台湾.*独立联盟
+独立联盟.*台湾
+新疆.*独立
+独立.*新疆
+东土耳其斯坦
+east.*turkistan
+turkistan.*east
+世维会
+迪里夏提
+美国之音
+自由亚洲电台
+记者无疆界
+维基解密.*中国
+中国.*维基解密
+facebook
+twitter
+推特
+新京报
+世界经济导报
+中国数字时代
+^ytht
+新语丝
+^creaders
+^tianwang
+中国.*禁闻
+禁闻.*中国
+阿波罗网
+阿波罗新闻
+大参考
+^bignews
+多维
+看中国
+博讯
+^boxun
+peacehall
+^hrichina
+独立中文笔会
+华夏文摘
+开放杂志
+大家论坛
+华夏论坛
+中国论坛
+木子论坛
+争鸣论坛
+大中华论坛
+反腐败论坛
+新观察论坛
+新华通论坛
+正义党论坛
+热站政论网
+华通时事论坛
+华语世界论坛
+华岳时事论坛
+两岸三地论坛
+南大自由论坛
+人民之声论坛
+万维读者论坛
+你说我说论坛
+东西南北论坛
+东南西北论谈
+知情者
+红太阳的陨落
+和谐拯救危机
+血房
+一个孤僻的人
+零八.*宪章
+宪章.*零八
+08.*宪章
+宪章.*08
+八宪章
+8宪章
+零八.*县长
+县长.*零八
+08县长
+淋巴县长
+我的最后陈述
+我没有敌人
+河殇
+天葬
+黄祸
+我的奋斗
+历史的伤口
+改革.*历程
+历程.*改革
+国家的囚徒
+prisoner of the state
+改革年代的政治斗争
+改革年代政治斗争
+关键时刻
+超越红墙
+梦萦未名湖
+一寸山河一寸血
+政治局常委内幕
+北国之春
+北京之春
+中国之春
+东方红时空
+纳米比亚
+婴儿汤
+泄题
+罢餐
+月月
+代开.*发票
+发票.*代开
+钓鱼岛
+^triangle
+女保镖
+玩ps
+玩photoshop
+chinese people eating babies
+开枪
+迫害
+酷刑
+邪恶
+洗脑
+网特
+内斗
+党魁
+文字狱
+一党专政
+一党独裁
+新闻封锁
+老人政治
+^freedom
+^freechina
+反社会
+维权人士
+维权律师
+异见人士
+异议人士
+地下刊物
+高瞻
+共产
+共铲党
+共残党
+共惨党
+共匪
+赤匪
+中共
+中宣
+真理部
+十八大
+18大
+太子
+上海帮
+团派
+北京当局
+裆中央
+九常委
+九长老
+锦涛
+家宝
+影帝
+近平
+回良玉
+汪洋
+张高丽
+俞正声
+徐才厚
+郭伯雄
+熙来
+梁光烈
+孟建柱
+戴秉国
+马凯
+令计划
+韩正
+章沁生
+陈世炬
+泽民
+贼民
+邓小平
+庆红
+罗干
+假庆淋
+hujin
+wenjiabao
+xijinping
+likeqiang
+zhouyongkang
+lichangchun
+wubangguo
+heguoqiang
+jiaqinglin
+jiangzemin
+xjp
+jzm
+色情
+花花公子
+tits
+boobs
+^\s*海峰\s*$
+^\s*威视公司\s*$
+^\s*nuctech\s*$
+^\s*逍遥游\s*$
+^\s*自由门\s*$
+^\s*自由門\s*$
+^\s*自由之门\s*$
+^\s*freegate\s*$
+^\s*freegate download\s*$
+^\s*download freegate\s*$
+^\s*自由门下载\s*$
+^\s*自由門下載\s*$
+^\s*無界瀏覽\s*$
+^\s*無界浏览\s*$
+^\s*动网通\s*$
+^\s*dynaweb\s*$
+^\s*dongtaiwang\s*$
+高潮
+诱惑
+快感
+淫
+婊子
+勃起
+博讯
+操逼
+操她
+操你
+操他
+操我
+插你
+车仑
+赤匪
+抽插
+臭逼
+吹箫
+打炮
+大b
+大逼
+大波
+吊毛
+二逼
+发抡
+发仑
+发伦
+发囵
+发轮
+发论
+法功
+法愣
+法抡
+法仑
+法伦
+法囵
+法纶
+法轮
+肥逼
+肥穴
+分裂
+阝月
+干逼
+肛交
+工力
+功法
+功友
+共党
+共匪
+共狗
+共军
+狗操
+狗日
+龟公
+龟头
+哈批
+豪乳
+宏志
+洪志
+黄菊
+回民
+鸡八
+鸡巴
+鸡吧
+妓女
+奸轮
+贱逼
+贱比
+贱货
+贱人
+江猪
+叫床
+精液
+巨波
+巨乳
+口交
+狂操
+捆绑
+烂逼
+烂比
+烂货
+浪穴
+炼功
+六四
+轮功
+轮奸
+妈批
+卖逼
+卖比
+蒙独
+迷奸
+蜜穴
+民运
+民猪
+明慧
+奶头
+奶子
+皮条
+屁眼
+嫖娼
+嫖妓
+强暴
+强鸡
+强奸
+求欢
+群奸
+群交
+日B
+日逼
+日比
+日她
+日你
+日死
+日我
+肉棒
+肉洞
+乳交
+瑞环
+色诱
+傻B
+傻逼
+射精
+绳虐
+兽交
+熟女
+氵去
+水扁
+私处
+外阴
+我操
+我日
+小逼
+小洞
+小穴
+泄欲
+性爱
+性交
+性梦
+性奴
+性虐
+性事
+性欲
+艳情
+阳具
+阳痿
+阴部
+阴唇
+阴道
+阴蒂
+阴茎
+阴毛
+阴水
+淫手
+杂种
+招妓
+正法
+猪操
+猪毛
+自焚
+自虐
+自慰
+作爱
+做爱
+操你妈
+操你娘
+处女膜
+打飞机
+大参考
+大花逼
+大纪元
+你妈
+你娘
+动态网
+发抡功
+发伦功
+发论公
+发论功
+发正念
+反封锁
+干你妈
+干你娘
+干死你
+共产党
+狗卵子
+古怪歌
+回民暴
+简鸿章
+江八点
+江独裁
+江流氓
+江戏子
+江.?民
+江贼民
+江折民
+江猪媳
+僵贼民
+酱猪媳
+靠你妈
+李大师
+李登辉
+李红痔
+李宏志
+李洪志
+李总统
+毛厕洞
+毛贼东
+蒙古独
+你妈逼
+人民报
+日你妈
+日你娘
+日死你
+十八摸
+十六大
+四川独
+台湾独
+台湾狗
+我操你
+小鸡鸡
+性游戏
+一夜情
+一夜性
+真善忍
+中国猪
+朱容基
+猪聋畸
+操你老妈
+操你老母
+操你老娘
+操你奶奶
+大法弟子
+法轮大法
+干你老母
+干你奶奶
+共青团派
+回民暴动
+美国之音
+日你老妈
+日你老母
+日你老娘
+日你奶奶
+声色场所
+新华内情
+一边一国
+一党专制
+一中一台
+欲死欲仙
+处女终结者
+独立台湾会
+反封锁技术
+佛展千手法
+鸡毛信文汇
+神通加持法
+天安门事件
+天安门屠杀
+天安门一代
+联总之声传单
+人民内情真相
+人民真实报导
+人民真实报道
+氵去车仑工力
+天安门录影带
+中俄边界新约
+人民大众时事参考
+印尼伊斯兰祈祷团
+印尼依斯兰祈祷团
+东突厥斯坦伊斯兰运动
+乳交绳虐滴蜡
+操你妈逼
+日你妈逼
+奸夫淫妇
+人民报讯
+你妈的
+他妈的
+发轮功
+法抡功
+法轮功
+两性狂情
+操你吗逼
+乳房畜生
+骚逼
+淫逼
+淫荡
+淫妇
+骚货
+淫贱
+你老母
+你奶奶
+骚女
+淫片
+淫水
+骚穴
+淫穴
+东突厥斯坦依斯兰运动
+发骚
+风骚
+奸淫
+九评共产党
+卖骚
+卖淫
+去你妈的
+手淫
+维吾尔自由运动
+西藏独
+西藏独立
+新疆独
+成人
+淫片
+外阴
+小洞
+抽插
+泄欲
+激情
+性交
+性事
+性游戏
+色欲
+少女高潮
+X夜激情
+轮奸
+AV女优
+少儿勿入
+色诱
+声色场所
+骚逼
+卖骚
+两性狂情
+18岁以下勿入
+18岁以下勿看
+禁片
+AV女优
+处女终结者
+法轮
+法伦
+法抡
+大法
+共产
+艳情
+操逼
+日逼
+骚逼
+干逼
+操我
+操你
+操她
+性交
+性事
+淫贱
+淫荡
+淫逼
+淫水
+狂操
+操你妈
+操你奶奶
+淫穴
+蜜穴
+骚穴
+集会
+罢工
+游行
+狗日
+日她
+日你
+日我
+日批
+日逼
+日B
+日比
+日死
+自焚
+主席
+大法
+真善忍
+愚民
+泽民
+台独
+锦涛
+温家宝
+李鹏
+瑞环
+邦国
+家宝
+云帆
+杨宁
+邓小平
+庆林
+庆红
+反共
+一塌糊涂
+共产党
+公安
+动态网
+六四
+fuck
+六合彩
+法轮功
+李宏志
+真善忍
+宏志
+法伦
+法抡
+法囵
+法仑
+法纶
+发仑
+发囵
+自由运动
+法轮大法
+E周刊
+龙卷风
+大纪元
+大参考
+正法
+镇压
+达赖
+迫害
+嫖娼
+静坐
+政变
+造反
+分裂
+自焚
+弟子
+发轮功
+功友
+人民大众时事参考
+示威
+游行
+共匪
+明慧
+大法弟子
+疆独
+民运
+印尼伊斯兰祈祷团
+中俄边界新约
+大法
+政治运动
+压迫
+博讯
+反革命
+胡锦涛
+温家宝
+吴邦国
+江独裁
+江泽民
+杨宁
+云帆
+东突厥斯坦伊斯兰运动
+一边一国
+人民内情真相
+回民
+新闻封锁
+鸡毛信文汇
+新华举报
+新华内情
+政治风波
+江八点
+古怪歌
+民猪
+突厥斯坦
+广闻
+简鸿章
+人民报
+联总之声传单
+人民报讯
+东突
+美国之音
+人民真实报道
+教徒
+打倒
+推翻
+人权
+操你奶奶
+操你妈
+fa lun
+falun
+洪志
+f a lun
+法轮
+大法
+李洪志
+自焚
+分裂
+西藏
+中华民国
+造反
+示威
+政变
+江泽民
+李鹏
+朱容基
+教徒
+人权
+迫害
+新华内情
+达赖
+镇压
+新闻封锁
+他妈的
+东突
+吕秀莲
+正法
+新华举报
+人民大众时事参考
+人民内情真相
+人民真实报导
+鸡毛信文汇
+联总之声传单
+美国之音
+民运
+人民报讯
+E周刊
+博讯
+人民报
+简鸿章
+迫害
+人权
+政变
+压迫
+反革命
+无能
+突厥斯坦
+印尼依斯兰祈祷团
+东突厥斯坦依斯兰运动
+疆独
+维吾尔自由运动
+自由运动
+1989天安门
+九评共产党
+一党专制
+太子党
+共青团派
+共党
+共匪
+共狗
+共军
+黄菊
+江流氓
+天安门事件
+共产党
+达赖
+藏独
+热比娅
+热比亚
+疆独
+台独
+
+#测试的
+test
\ No newline at end of file
diff --git a/utils/src/test/java/com/brook/example/AppTest.java b/utils/src/test/java/com/brook/example/AppTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b1eb9725f732747c3b19aab7305c9e378517de9
--- /dev/null
+++ b/utils/src/test/java/com/brook/example/AppTest.java
@@ -0,0 +1,38 @@
+package com.brook.example;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+ extends TestCase
+{
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public AppTest( String testName )
+ {
+ super( testName );
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite()
+ {
+ return new TestSuite( AppTest.class );
+ }
+
+ /**
+ * Rigourous Test :-)
+ */
+ public void testApp()
+ {
+ assertTrue( true );
+ }
+}
diff --git a/utils/src/test/java/com/brook/example/utils/text/ForbiddenWordUtilsTest.java b/utils/src/test/java/com/brook/example/utils/text/ForbiddenWordUtilsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..92df550b46bf2579eacaa7daf3c140cd022446f3
--- /dev/null
+++ b/utils/src/test/java/com/brook/example/utils/text/ForbiddenWordUtilsTest.java
@@ -0,0 +1,52 @@
+package com.brook.example.utils.text;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * ForbiddenWordUtils test case.
+ *
+ * @author Shaojun Liu
+ * @create 2017/7/28
+ */
+public class ForbiddenWordUtilsTest {
+ @Test
+ public void testReplaceWithDefaultMask() {
+
+ String input = "你是傻逼";
+ String expected = "你是***";
+ String actual = ForbiddenWordUtils.replace(input);
+ Assert.assertEquals(expected, actual);
+
+ }
+
+ @Test
+ public void testReplaceWithDefaultMask2() {
+
+ String input = "你是主席吗";
+ String expected = "你是***吗";
+ String actual = ForbiddenWordUtils.replace(input);
+ Assert.assertEquals(expected, actual);
+
+ }
+
+
+ @Test
+ public void testReplaceWithDefaultMask3() {
+
+ String input = "傻B123";
+ String expected = "***123";
+ String actual = ForbiddenWordUtils.replace(input);
+ Assert.assertEquals(expected, actual);
+
+ }
+
+ @Test
+ public void testReplaceWithCustomMask() {
+ String input = "傻B123";
+ String expected = "###123";
+ String actual = ForbiddenWordUtils.replace(input,"###");
+ Assert.assertEquals(expected, actual);
+ }
+
+}
\ No newline at end of file
diff --git a/utils/src/test/resources/forbidden-test.txt b/utils/src/test/resources/forbidden-test.txt
new file mode 100644
index 0000000000000000000000000000000000000000..88da85160ca6bca0df6d0ed2bb6b7e3289c2bba0
--- /dev/null
+++ b/utils/src/test/resources/forbidden-test.txt
@@ -0,0 +1,1030 @@
+#google的
+马驰.*新加坡
+新加坡.*马驰
+自由光诚
+陈光诚事件
+光诚.*沂南
+沂南.*光诚
+陈光诚.*使馆
+使馆.*陈光诚
+职称英语.*答案
+答案.*职称英语
+公务员.*答案
+答案.*公务员
+薄瓜瓜
+海伍德
+尼尔伍德
+heywood
+neil.*wood
+wood.*neil
+天线宝宝.*康师傅
+康师傅.*天线宝宝
+天线宝宝.*方便面
+方便面.*天线宝宝
+天线宝宝.*轮胎
+轮胎.*天线宝宝
+轮胎.*方便面
+方便面.*轮胎
+政变
+枪声
+戒严
+3\.19
+北京事件
+北京.*出事了
+出事了.*北京
+北京怎么了
+不厚
+薄督
+谷开来
+重庆
+叶城.*砍杀
+砍杀.*叶城
+连承敏
+弟弟.*睡
+睡.*弟弟
+要有光.*要有诚
+要有诚.*要有光
+杨杰
+陈刚
+山水文园
+跑官
+移动.*十年兴衰
+十年兴衰.*移动
+陈坚
+戴坚
+冯珏
+罗川
+马力
+盛勇
+谢岷
+谢文
+杨希
+叶兵
+张斌
+陈瑞卿
+高念书
+华如秀
+鲁向东
+曲乃杰
+孙静晔
+涂志森
+于剑鸣
+张晓明
+赵志强
+郑建源
+先皇
+太上皇
+蛤蟆
+驾崩
+丘小雄
+公诉
+右派
+增城
+暴动
+宣言
+莫日根
+内蒙古.*抗议
+抗议.*内蒙古
+西乌旗
+方滨兴
+moli
+麦当劳
+天府
+人民公园
+广场
+埃及
+突尼斯
+茉莉
+jasmine.*revolution
+revolution.*jasmine
+集会
+革命
+齐鲁银行
+公开信
+高考时间
+诺贝尔和平奖
+被就业
+小屋
+日记
+鲁昕
+天安.*事件
+事件.*天安
+1989.*天安門
+天安門.*1989
+天安门
+八九
+六四
+六 四
+六\.四
+平反64
+5月35日
+5月35号
+89动乱
+89.*学生动乱
+学生动乱.*89
+89.*学生运动
+学生运动.*89
+64.*学生运动
+学生运动.*64
+64.*镇压
+镇压.*64
+64.*真相
+真相.*64
+64memo
+tiananmen
+8964
+学潮
+罢课
+民运
+学运
+学联
+学自联
+高自联
+工自联
+民联
+民阵
+中国民主党
+中国民主正义党
+中国民主运动
+世纪中国基金会
+坦克人
+挡坦克
+tankman
+木犀地
+维园晚会
+blood is on the square
+姜维平
+艾未未
+艾末末
+路青
+发课
+余杰
+辛子陵
+茅于轼
+铁流
+liu.*xiaobo
+xiaobo.*liu
+蟹农场
+陈西
+谭作人
+高智晟
+冯正虎
+丁子霖
+唯色
+焦国标
+何清涟
+耀邦
+紫阳
+方励之
+严家其
+鲍彤
+鮑彤
+鲍朴
+柴玲
+乌尔凯西
+封从德
+炳章
+苏绍智
+陈一谘
+韩东方
+辛灏年
+曹长青
+陈破空
+盘古乐队
+盛雪
+伍凡
+魏京生
+司徒华
+黎安友
+防火长城
+great.*firewall
+firewall.*great
+gfw.*什么
+什么.*gfw
+国家防火墙
+翻墙
+代理
+vpn.*免费
+免费.*vpn
+vpn.*下载
+下载.*vpn
+vpn.*世纪
+世纪.*vpn
+hotspot.*shield
+shield.*hotspot
+无界
+ultrasurf
+^freenet
+safeweb
+动态网
+花园网
+^cache
+阅后即焚
+法轮
+falun
+明慧
+minghui
+退党
+三退
+九评
+nine commentaries
+洪吟
+神韵艺术
+神韵晚会
+人民报
+renminbao
+纪元
+^dajiyuan
+epochtimes
+新唐人
+ntdtv
+ndtv
+新生网
+^xinsheng
+正见网
+zhengjian
+追查国际
+真善忍
+法会
+正念
+经文
+天灭
+天怒
+讲真相
+马三家
+善恶有报
+活摘器官
+群体灭绝
+中功
+张宏堡
+地下教会
+冤民大同盟
+达赖
+藏独
+freetibet
+雪山狮子
+西藏流亡政府
+青天白日旗
+民进党
+洪哲胜
+独立台湾会
+台湾政论区
+台湾自由联盟
+台湾建国运动组织
+台湾.*独立联盟
+独立联盟.*台湾
+新疆.*独立
+独立.*新疆
+东土耳其斯坦
+east.*turkistan
+turkistan.*east
+世维会
+迪里夏提
+美国之音
+自由亚洲电台
+记者无疆界
+维基解密.*中国
+中国.*维基解密
+facebook
+twitter
+推特
+新京报
+世界经济导报
+中国数字时代
+^ytht
+新语丝
+^creaders
+^tianwang
+中国.*禁闻
+禁闻.*中国
+阿波罗网
+阿波罗新闻
+大参考
+^bignews
+多维
+看中国
+博讯
+^boxun
+peacehall
+^hrichina
+独立中文笔会
+华夏文摘
+开放杂志
+大家论坛
+华夏论坛
+中国论坛
+木子论坛
+争鸣论坛
+大中华论坛
+反腐败论坛
+新观察论坛
+新华通论坛
+正义党论坛
+热站政论网
+华通时事论坛
+华语世界论坛
+华岳时事论坛
+两岸三地论坛
+南大自由论坛
+人民之声论坛
+万维读者论坛
+你说我说论坛
+东西南北论坛
+东南西北论谈
+知情者
+红太阳的陨落
+和谐拯救危机
+血房
+一个孤僻的人
+零八.*宪章
+宪章.*零八
+08.*宪章
+宪章.*08
+八宪章
+8宪章
+零八.*县长
+县长.*零八
+08县长
+淋巴县长
+我的最后陈述
+我没有敌人
+河殇
+天葬
+黄祸
+我的奋斗
+历史的伤口
+改革.*历程
+历程.*改革
+国家的囚徒
+prisoner of the state
+改革年代的政治斗争
+改革年代政治斗争
+关键时刻
+超越红墙
+梦萦未名湖
+一寸山河一寸血
+政治局常委内幕
+北国之春
+北京之春
+中国之春
+东方红时空
+纳米比亚
+婴儿汤
+泄题
+罢餐
+月月
+代开.*发票
+发票.*代开
+钓鱼岛
+^triangle
+女保镖
+玩ps
+玩photoshop
+chinese people eating babies
+开枪
+迫害
+酷刑
+邪恶
+洗脑
+网特
+内斗
+党魁
+文字狱
+一党专政
+一党独裁
+新闻封锁
+老人政治
+^freedom
+^freechina
+反社会
+维权人士
+维权律师
+异见人士
+异议人士
+地下刊物
+高瞻
+共产
+共铲党
+共残党
+共惨党
+共匪
+赤匪
+中共
+中宣
+真理部
+十八大
+18大
+太子
+上海帮
+团派
+北京当局
+裆中央
+九常委
+九长老
+锦涛
+家宝
+影帝
+近平
+回良玉
+汪洋
+张高丽
+俞正声
+徐才厚
+郭伯雄
+熙来
+梁光烈
+孟建柱
+戴秉国
+马凯
+令计划
+韩正
+章沁生
+陈世炬
+泽民
+贼民
+邓小平
+庆红
+罗干
+假庆淋
+hujin
+wenjiabao
+xijinping
+likeqiang
+zhouyongkang
+lichangchun
+wubangguo
+heguoqiang
+jiaqinglin
+jiangzemin
+xjp
+jzm
+色情
+花花公子
+tits
+boobs
+^\s*海峰\s*$
+^\s*威视公司\s*$
+^\s*nuctech\s*$
+^\s*逍遥游\s*$
+^\s*自由门\s*$
+^\s*自由門\s*$
+^\s*自由之门\s*$
+^\s*freegate\s*$
+^\s*freegate download\s*$
+^\s*download freegate\s*$
+^\s*自由门下载\s*$
+^\s*自由門下載\s*$
+^\s*無界瀏覽\s*$
+^\s*無界浏览\s*$
+^\s*动网通\s*$
+^\s*dynaweb\s*$
+^\s*dongtaiwang\s*$
+高潮
+诱惑
+快感
+淫
+婊子
+勃起
+博讯
+操逼
+操她
+操你
+操他
+操我
+插你
+车仑
+赤匪
+抽插
+臭逼
+吹箫
+打炮
+大b
+大逼
+大波
+吊毛
+二逼
+发抡
+发仑
+发伦
+发囵
+发轮
+发论
+法功
+法愣
+法抡
+法仑
+法伦
+法囵
+法纶
+法轮
+肥逼
+肥穴
+分裂
+阝月
+干逼
+肛交
+工力
+功法
+功友
+共党
+共匪
+共狗
+共军
+狗操
+狗日
+龟公
+龟头
+哈批
+豪乳
+宏志
+洪志
+黄菊
+回民
+鸡八
+鸡巴
+鸡吧
+妓女
+奸轮
+贱逼
+贱比
+贱货
+贱人
+江猪
+叫床
+精液
+巨波
+巨乳
+口交
+狂操
+捆绑
+烂逼
+烂比
+烂货
+浪穴
+炼功
+六四
+轮功
+轮奸
+妈批
+卖逼
+卖比
+蒙独
+迷奸
+蜜穴
+民运
+民猪
+明慧
+奶头
+奶子
+皮条
+屁眼
+嫖娼
+嫖妓
+强暴
+强鸡
+强奸
+求欢
+群奸
+群交
+日B
+日逼
+日比
+日她
+日你
+日死
+日我
+肉棒
+肉洞
+乳交
+瑞环
+色诱
+傻B
+傻逼
+射精
+绳虐
+兽交
+熟女
+氵去
+水扁
+私处
+外阴
+我操
+我日
+小逼
+小洞
+小穴
+泄欲
+性爱
+性交
+性梦
+性奴
+性虐
+性事
+性欲
+艳情
+阳具
+阳痿
+阴部
+阴唇
+阴道
+阴蒂
+阴茎
+阴毛
+阴水
+淫手
+杂种
+招妓
+正法
+猪操
+猪毛
+自焚
+自虐
+自慰
+作爱
+做爱
+操你妈
+操你娘
+处女膜
+打飞机
+大参考
+大花逼
+大纪元
+你妈
+你娘
+动态网
+发抡功
+发伦功
+发论公
+发论功
+发正念
+反封锁
+干你妈
+干你娘
+干死你
+共产党
+狗卵子
+古怪歌
+回民暴
+简鸿章
+江八点
+江独裁
+江流氓
+江戏子
+江.?民
+江贼民
+江折民
+江猪媳
+僵贼民
+酱猪媳
+靠你妈
+李大师
+李登辉
+李红痔
+李宏志
+李洪志
+李总统
+毛厕洞
+毛贼东
+蒙古独
+你妈逼
+人民报
+日你妈
+日你娘
+日死你
+十八摸
+十六大
+四川独
+台湾独
+台湾狗
+我操你
+小鸡鸡
+性游戏
+一夜情
+一夜性
+真善忍
+中国猪
+朱容基
+猪聋畸
+操你老妈
+操你老母
+操你老娘
+操你奶奶
+大法弟子
+法轮大法
+干你老母
+干你奶奶
+共青团派
+回民暴动
+美国之音
+日你老妈
+日你老母
+日你老娘
+日你奶奶
+声色场所
+新华内情
+一边一国
+一党专制
+一中一台
+欲死欲仙
+处女终结者
+独立台湾会
+反封锁技术
+佛展千手法
+鸡毛信文汇
+神通加持法
+天安门事件
+天安门屠杀
+天安门一代
+联总之声传单
+人民内情真相
+人民真实报导
+人民真实报道
+氵去车仑工力
+天安门录影带
+中俄边界新约
+人民大众时事参考
+印尼伊斯兰祈祷团
+印尼依斯兰祈祷团
+东突厥斯坦伊斯兰运动
+乳交绳虐滴蜡
+操你妈逼
+日你妈逼
+奸夫淫妇
+人民报讯
+你妈的
+他妈的
+发轮功
+法抡功
+法轮功
+两性狂情
+操你吗逼
+乳房畜生
+骚逼
+淫逼
+淫荡
+淫妇
+骚货
+淫贱
+你老母
+你奶奶
+骚女
+淫片
+淫水
+骚穴
+淫穴
+东突厥斯坦依斯兰运动
+发骚
+风骚
+奸淫
+九评共产党
+卖骚
+卖淫
+去你妈的
+手淫
+维吾尔自由运动
+西藏独
+西藏独立
+新疆独
+成人
+淫片
+外阴
+小洞
+抽插
+泄欲
+激情
+性交
+性事
+性游戏
+色欲
+少女高潮
+X夜激情
+轮奸
+AV女优
+少儿勿入
+色诱
+声色场所
+骚逼
+卖骚
+两性狂情
+18岁以下勿入
+18岁以下勿看
+禁片
+AV女优
+处女终结者
+法轮
+法伦
+法抡
+大法
+共产
+艳情
+操逼
+日逼
+骚逼
+干逼
+操我
+操你
+操她
+性交
+性事
+淫贱
+淫荡
+淫逼
+淫水
+狂操
+操你妈
+操你奶奶
+淫穴
+蜜穴
+骚穴
+集会
+罢工
+游行
+狗日
+日她
+日你
+日我
+日批
+日逼
+日B
+日比
+日死
+自焚
+主席
+大法
+真善忍
+愚民
+泽民
+台独
+锦涛
+温家宝
+李鹏
+瑞环
+邦国
+家宝
+云帆
+杨宁
+邓小平
+庆林
+庆红
+反共
+一塌糊涂
+共产党
+公安
+动态网
+六四
+fuck
+六合彩
+法轮功
+李宏志
+真善忍
+宏志
+法伦
+法抡
+法囵
+法仑
+法纶
+发仑
+发囵
+自由运动
+法轮大法
+E周刊
+龙卷风
+大纪元
+大参考
+正法
+镇压
+达赖
+迫害
+嫖娼
+静坐
+政变
+造反
+分裂
+自焚
+弟子
+发轮功
+功友
+人民大众时事参考
+示威
+游行
+共匪
+明慧
+大法弟子
+疆独
+民运
+印尼伊斯兰祈祷团
+中俄边界新约
+大法
+政治运动
+压迫
+博讯
+反革命
+胡锦涛
+温家宝
+吴邦国
+江独裁
+江泽民
+杨宁
+云帆
+东突厥斯坦伊斯兰运动
+一边一国
+人民内情真相
+回民
+新闻封锁
+鸡毛信文汇
+新华举报
+新华内情
+政治风波
+江八点
+古怪歌
+民猪
+突厥斯坦
+广闻
+简鸿章
+人民报
+联总之声传单
+人民报讯
+东突
+美国之音
+人民真实报道
+教徒
+打倒
+推翻
+人权
+操你奶奶
+操你妈
+fa lun
+falun
+洪志
+f a lun
+法轮
+大法
+李洪志
+自焚
+分裂
+西藏
+中华民国
+造反
+示威
+政变
+江泽民
+李鹏
+朱容基
+教徒
+人权
+迫害
+新华内情
+达赖
+镇压
+新闻封锁
+他妈的
+东突
+吕秀莲
+正法
+新华举报
+人民大众时事参考
+人民内情真相
+人民真实报导
+鸡毛信文汇
+联总之声传单
+美国之音
+民运
+人民报讯
+E周刊
+博讯
+人民报
+简鸿章
+迫害
+人权
+政变
+压迫
+反革命
+无能
+突厥斯坦
+印尼依斯兰祈祷团
+东突厥斯坦依斯兰运动
+疆独
+维吾尔自由运动
+自由运动
+1989天安门
+九评共产党
+一党专制
+太子党
+共青团派
+共党
+共匪
+共狗
+共军
+黄菊
+江流氓
+天安门事件
+共产党
+达赖
+藏独
+热比娅
+热比亚
+疆独
+台独
+
+#测试的
+test
\ No newline at end of file