赞
踩
个人博客: https://www.crystalblog.xyz/
备用地址: https://wang-qz.gitee.io/crystal-blog/
JDK8是官方发布的一个大版本, 提供了很多新特性功能给开发者使用, 包含语言、编译器、库、工具和JVM等方面的十多个新特性。 本文将介绍编码过程中常用的一些新特性. JDK8帮助文档
lambda表达式 (也称为闭包), 本质上是一段匿名内部类或一段可以传递的代码 . 可以理解为使用更加方便的“语法糖”,但原理不变的编写语法。 它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理 .
语法糖: 简洁而紧凑的语言结构 .
Lambda表达式由逗号分隔的参数列表、->符号和语句块三部分组成 .
// jdk8之前需要循环集合进行遍历输出
Arrays.asList( "a", "b", "d" ).forEach( (String s) -> {
System.out.print( s );
} );
如果语句块只有一句, 可以省略大括号; 参数列表的类型也可以省略, 如果只有一个参数, 参数的括号也可以省略.
Arrays.asList( "a", "b", "d" ).forEach( s -> System.out.print( s ) );
如果有语句块中有返回值
// jdk8之前,sort(Comparator c)需要写一个匿名内部类实现
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
可以继续简化, 只有一个返回值语句, 大括号和return都可以省略.
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.
传统方式编写, 每次筛选都要对集合进行循环遍历判断, 这些都是重复的操作.
/** * 需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品. */ public class LambdaTest { @Test public void testFilterProducts() { List<Product> products = getProducts(); products = this.filterProductByColor(products); products = this.filterProductByPrice(products); products.forEach(System.out::println); } // 筛选颜色 public List<Product> filterProductByColor(List<Product> list) { List<Product> prods = new ArrayList<>(); for (Product product : list) { if ("红色".equals(product.getColor())) { prods.add(product); } } return prods; } // 筛选价格 public List<Product> filterProductByPrice(List<Product> list) { List<Product> prods = new ArrayList<>(); for (Product product : list) { if (product.getPrice() < 8000) { prods.add(product); } } return prods; } // 添加商品数据 private List<Product> getProducts() { List<Product> products = new ArrayList<>(); for (int i = 1; i <= 10; i++) { Product pro = Product.builder() .color(i % 2 == 0 ? "红色" : "绿色") .price(i * 1000) .build(); products.add(pro); } return products; } }
使用lambda表达式+函数接口进行过滤
package cn.itcast.test.newf; import cn.itcast.pojo.Product; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Predicate; /** * 需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品. */ public class LambdaTest { // lambda表达式过滤 @Test public void testFilterProducts2() { List<Product> products = getProducts(); products = filterProductByPredicate(products, (product -> Objects.equals("红色", product.getColor()))); products = filterProductByPredicate(products, (product -> Double.compare(8000, product.getPrice()) > 0)); products.forEach(System.out::println); } // 使用lambda表达式, Predicate为断言的函数接口, 接口方法返回boolean类型 public List<Product> filterProductByPredicate(List<Product> list, Predicate<Product> p) { List<Product> prods = new ArrayList<>(); for (Product product : list) { if (p.test(product)) { prods.add(product); } } return prods; } }
不使用函数接口, 单纯使用lambda表达式实现
@Test
public void testFilterProducts3() {
List<Product> products = getProducts();
products.stream()
.filter((product -> Objects.equals("红色", product.getColor())))
.filter(product -> Double.compare(8000, product.getPrice()) > 0)
.forEach(System.out::println);
}
测试结果
Product(color=红色, price=2000.0)
Product(color=红色, price=4000.0)
Product(color=红色, price=6000.0)
Lmabda表达式的语法总结: () -> ();
| 场景 | 语法 |
|---|---|
| 无参数无返回值 | () -> System.out.println(“Hello WOrld”) |
| 有一个参数无返回值 | (x) -> System.out.println(x) |
| 有且只有一个参数无返回值 | x -> System.out.println(x) |
| 有多个参数,有返回值,有多条lambda体语句 | (x,y) -> {System.out.println(“xxx”);return xxxx;}; |
| 有多个参数,有返回值,只有一条lambda体语句 | (x,y) -> xxxx |
口诀:左右遇一省括号,左侧推断类型省
函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。
函数接口指的是只有一个抽象方法的接口,这样的接口可以隐式转换为Lambda表达式。 当然接口中可以包含其他的方法(默认,静态,私有) .
java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解**@FunctionalInterface** , 所以可以通过@FunctionalInterface注解检测接口是否为一个函数式接口 .
默认方法和静态方法不会破坏函数式接口的定义 , 如下所示:
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
// 默认方法
default void defaultMethod() {}
// 静态方法
static void staticMethod(){}
}
下面学习JDK8提供的常用函数式接口API, 主要有Consumer, Supplier , Predicate , Function.
消费型接口,有参无返回值 , 参数类型由泛型决定 .
@FunctionalInterface
public interface Consumer<T> {
//....
}
表示消费一个指定泛型类型的数据 .
void accept(T t);
accept使用
/** * Consumer<T>:消费型接口,有参无返回值 */ public class ConsumerTest { //定义一个方法 //方法的参数传递一个字符串的姓名 //方法的参数传递Consumer接口,泛型使用String //可以使用Consumer接口消费字符串的姓名 private void accept(String name, Consumer<String> consumer) { consumer.accept(name); } @Test public void test() { //对传递的字符串进行消费 //消费方式,把字符串进行反转输出 accept("admin", (name) -> { String reName = new StringBuilder(name).reverse().toString(); System.out.println(reName); }); } }
Consumer接口的默认方法andThen , 可以把两个Consumer接口组合到一起,再对数据进行消费 ( 谁写前边,谁先消费 ).
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
andThen方法使用
/** * Consumer<T>:消费型接口,有参无返回值 */ public class ConsumerTest { //定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串 private void andThen(String s, Consumer<String> c1, Consumer<String> c2) { //使用andThen方法,把两个Consumer接口连接到一起,先消费c1再消费c2 c1.andThen(c2).accept(s); } @Test public void test2() { andThen("admin", (name) -> { System.out.println(name); }, (name) -> { //消费方式,把字符串转换为大写输出 System.out.println(name.toUpperCase(Locale.ROOT)); }); } }
需求: 格式化打印信息 , 字符串数组中存有多条信息,请按照格式,“姓名:xx,性别:xx“的格式信息打印出来 .
分析:
将打印姓名的动作作为第一个Consumer接口的Lambda实例
将打印性别的动作作为第二个Consumer接口的Lambda实例
将两个Consumer接口按照顺序”拼接“到一起
package cn.itcast.test.jdk8; import org.junit.Test; import java.util.Locale; import java.util.function.Consumer; /** * Consumer<T>:消费型接口,有参无返回值 */ public class ConsumerTest { //定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String // 用两个Consumer接口消费字符串 private void printInfo(String[] arr, Consumer<String> consumer, Consumer<String> consumer2) { //遍历字符串数组 for (String s : arr) { consumer.andThen(consumer2).accept(s); } } @Test public void test3() { //定义一个字符串类型的数据 String[] arr = {"刘辣子,女", "杨哈哈,男", "赵屌屌,男"}; //调用printInfo方法,传递一个字符串数组,和两个Lambda表达式 printInfo(arr, (s) -> { String name = s.split(",")[0]; System.out.print("姓名:" + name); }, (s) -> { String gender = s.split(",")[1]; System.out.println("、性别:" + gender + "."); }); } }
输出结果
姓名:刘辣子、性别:女.
姓名:杨哈哈、性别:男.
姓名:赵屌屌、性别:男.
供给型接口,无参有返回值 .
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Supplier接口仅包含一个无参的方法:T get() .
用来获取一个泛型参数指定类型的对象数据。意味着对应的Lambda表达式需要"对外提供"一个符合泛型类型的对象数据。
/** * Supplier<T>:供给型接口,无参有返回值 */ public class SupplierTest { public String getString(Supplier<String> supplier) { return supplier.get(); } @Test public void test() { //定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String String s = getString(() -> "人才"); System.out.println(s); } private Integer getMax(Supplier<Integer> supplier) { //定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用包装类Integer return supplier.get(); } @Test public void test2() { //定义一个int类型的数组,并赋值 int[] arr = {100, 78, -887, 66, 90}; //调用getMax方法,方法参数Supplier是一个函数式接口,可以传递Lambda表达式 int maxValue = getMax(() -> { int max = arr[0]; for (int a : arr) { if (a > max) { max = a; } } return max; }); System.out.println(maxValue); } }
断言型接口,有参有返回值, 对某种类型的数据进行判断 , 返回boolean类型 .
@FunctionalInterface
public interface Predicate<T> {
//...
}
boolean test(T t):用于对指定类型数据进行判断, 符合条件返回true,不符合条件返回false
boolean test(T t);
test方法的使用
/** * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型 */ public class PredicateTest { //定义一个方法 //参数传递一个String类型的字符串 //传递一个Predicate接口,泛型使用String //使用predicate中的方法test对字符串进行判断,并把判断的结果返回 public boolean checkString(String s, Predicate<String> predicate) { // 对某种数据类型的数据进行判断,结果返回一个boolean值 return predicate.test(s); } @Test public void test() { //定义一个字符串 String s = "abcdef"; //调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式 //对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回 boolean b = checkString(s, str -> str.length() > 5); System.out.println(b); } }
逻辑表达式:可以连接多个判断的条件 .
&&:与运算符. 连接的多个判断条件都为true才返回true, 否则false.
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
需求:判断一个字符串,有两个判断的条件
1、判断字符串的长度是否大于5
2、判断字符串中是否包含a
两个条件必须同时满足,使用&&运算符连接两个条件 .
/** * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型 */ public class PredicateTest { //定义一个方法,方法的参数,传递一个字符串 //传递俩个Predicate接口 //1、判断字符串的长度是否大于5 2、判断字符串中是否包含a 两个条件必须同时满足,使用&&运算符连接两个条件 private boolean checkString(String s, Predicate<String> predicate, Predicate<String> predicate2) { //等价于return pre1.test(s)&&pre2.test(s); return predicate.and(predicate2).test(s); } @Test public void test2() { //定义一个字符串 String s = "asdfgi"; //调用checkString方法,参数传递字符串和两个Lambda表达式 boolean b = checkString(s, str -> !StringUtils.isEmpty(str) && str.length() > 5, str -> !StringUtils.isEmpty(str) && str.contains("a")); System.out.println(b); } }
||:或运算符,连接的多个判断条件有一个为true就返回true, 都为false才返回false.
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
需求:判断一个字符串,有两个判断的条件
1、判断字符串的长度是否大于5
2、判断字符串中是否包含a
两个条件满足一个即可,使用||运算符连接两个条件 .
/** * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型 */ public class PredicateTest { @Test public void test2() { //定义一个字符串 String s = "asdfgi"; //调用checkStringOr方法,参数传递字符串和两个Lambda表达式 boolean b = checkStringOr(s, str -> !StringUtils.isEmpty(str) && str.length() > 5, str -> !StringUtils.isEmpty(str) && str.contains("a") ); System.out.println(b); } //定义一个方法,方法的参数,传递一个字符串 //传递俩个Predicate接口 //1、判断字符串的长度是否大于5 2、判断字符串中是否包含a 满足一个条件即可,使用||运算符连接两个条件 private boolean checkStringOr(String s, Predicate<String> predicate, Predicate<String> predicate2) { return predicate.or(predicate2).test(s); } }
!:非(取反):非真则假,非假则真 .
default Predicate<T> negate() {
return (t) -> !test(t);
}
需求:判断一个字符串长度是否大于5
如果字符串的长度大于5,返回false
如果字符串的长度小于5,返回true
使用取反符号!对判断的结果进行取反 .
/** * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型 */ public class PredicateTest { //定义一个方法,方法的参数,传递一个字符串 public boolean checkStringNegate(String s, Predicate<String> predicate) { //使用Predicate接口判断字符串的长度是否大于5 //return !pre.test(s); return predicate.negate().test(s); } @Test public void testNegate() { //定义一个字符串 String s = "asdfg"; //调用checkString方法,参数传递字符串和Lambda表达式 boolean b = checkStringNegate(s, str -> str.length() > 5); System.out.println(b); } }
需求: 集合信息筛选
数组当中有多条“姓名+性别”的信息如下:
String[] array = {"刘小甜甜,女","杨哈哈,男","赵屌屌,男"}
通过Predicate接口的拼装将符合要求的字符串筛选到ArrayList集合中
需求同时满足两个条件:必须为女生,姓名为4个字
1、有两个判断条件,需要使用两个Predicate接口,对条件进行判断
2、必须同时满足两个条件,可以使用and方法连接两个判断条件
/** * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型 */ public class PredicateTest { // 定义一个方法, 两个Predicate接口参数 public ArrayList<String> filter(String[] arr, Predicate<String> predicate, Predicate<String> predicate2) { ArrayList<String> list = new ArrayList<>(); for (String s : arr) { if (predicate.and(predicate2).test(s)) { list.add(s); } } return list; } @Test public void test4() { //定义一个存储字符串的数组 String[] array = {"刘小甜甜,女", "杨哈哈,男", "赵屌屌,男"}; //调用filter方法,传递字符串数组和两个Lambda表达式 ArrayList<String> list = filter(array, str -> !StringUtils.isEmpty(str) && str.split(",")[0].length() == 4, str -> !StringUtils.isEmpty(str) && str.split(",")[1].equals("女") ); list.forEach(System.out::println); } }
函数式接口,有参有返回值 . java.util.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据.
前者称为前置条件,后者称为后置条件 .
@FunctionalInterface
public interface Function<T, R> {
// ...
}
Function接口中最主要的抽象方法为:R apply(T,t),根据类型T的参数获取类型的R结果 .
R apply(T t);
例如将String转换为Integer类型 .
/** * Function<T, R> : 函数式接口,有参有返回值 * java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据 */ public class FunctionTest { //定义一个方法 //方法的参数传递一个字符串类型的整数 //方法的参数传递一个Function接口,泛型使用<String,Integer> //使用Function接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数 private Integer change(String s, Function<String, Integer> function) { Integer apply = function.apply(s); return apply; } @Test public void test() { //定义一个字符串类型的整数 String s = "1234"; //调用change方法传递字符串类型的整数,和Lambda表达式 Integer change = change(s, str -> Integer.parseInt(str)); System.out.println(change instanceof Integer); } }
Function接口中的默认方法andThen,用来进行组合操作 . 先转换第一个, 再转换第二个.
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
需求:把String类型的“123”,转换为Integer类型,把转换后的结果+10, 再把增加之后的Integer类型的数据转换为String类型 .
分析: String->Integer-> 加10->String.
/** * Function<T, R> : 函数式接口,有参有返回值 * java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据 */ public class FunctionTest { //定义一个方法 //参数传一个字符串类型的整数 //参数在传两个Function接口 //一个泛型使用Function<String,Integer> //一个泛型使用Function<Integer,String> private String change(String s, Function<String, Integer> fun, Function<Integer, String> fun2) { System.out.println("apply..."); return fun.andThen(fun2).apply(s); } @Test public void test2() { //定义一个字符串类型的整数 String s = "123"; //调用change方法,传递字符串和两个Lambda表达式 String change = change(s, str -> Integer.parseInt(s) + 10, integer -> String.valueOf(integer) ); System.out.println(change + ", " + change instanceof String); } }
使用Function进行函数模型的拼接,按照顺序需要执行的多个函数操作为:String str = “刘辣子,18”;
分析:
(1)、将字符串截取数字年龄部分,得到数字字符串
Function<String,String> “刘辣子,18”->“18”
(2)、将上一步的数字字符串转换为int类型的数字
Function<String,Integer>“18”->18
(3)、将上一步的int数字累加100,得到结果int数字
Function<Integer,Inerger>18->118
/** * Function<T, R> : 函数式接口,有参有返回值 * java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据 */ public class FunctionTest { //定义一个方法 //参数传递包含姓名和年龄的字符串 //参数传递3个Function接口用于类型转换 private Integer change(String s, Function<String, String> fun, Function<String, Integer> fun2, Function<Integer, Integer> fun3) { return fun.andThen(fun2).andThen(fun3).apply(s); } /** * 1、将字符串截取数字年龄部分,得到数字字符串 * Function<String,String> “刘辣子,18”->“18” * 2、将上一步的数字字符串转换为int类型的数字 * Function<String,Integer>“18”->18 * 3、将上一步的int数字累加100,得到结果int数字 * Function<Integer,Inerger>18->118 */ @Test public void test3() { //定义一个字符串 String s = "刘辣子,18"; //调用change方法,参数传递字符串和3个Lambda表达式 Integer change = change(s, str -> str.split(",")[1], str -> Integer.parseInt(str), num -> num + 100 ); System.out.println(change); } }
定义函数式接口
@FunctionalInterface
public interface MyFunctionalInterface {
//定义一个抽象方法, public abstract可以省略
public abstract void method();
}
接口实现类方式实现接口方法
public class MyFunctionalInterfaceImpl implements MyFunctionalInterface {
@Override
public void method() {
System.out.println(">>>MyFunctionalInterfaceImpl");
}
}
函数式接口的使用
/** * 函数式接口的使用测试 */ public class MyFunctionalInterfaceTest { @Test public void test() { // 调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象 show(new MyFunctionalInterfaceImpl()); // 调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类 show(new MyFunctionalInterface() { @Override public void method() { System.out.println(">>>MyFunctionalInterface的匿名内部类实现"); } }); // 调用show方法,方法的参数是一个函数式接口,可以传递Lambda表达式 show(() -> System.out.println(">>>MyFunctionalInterface的lambda表达式实现")); } // 定义一个方法,参数使用函数式接口MyFunctionalInterInterface public void show(MyFunctionalInterface myFunctionalInterface) { myFunctionalInterface.method(); } }
测试结果
>>>MyFunctionalInterfaceImpl
>>>MyFunctionalInterface的匿名内部类实现
>>>MyFunctionalInterface的lambda表达式实现
使用函数式接口, 应用Lambda表达式方式实现可以达到延迟加载的目的, 优化提升系统性能.
场景: 程序运行根据日志级别输出日志信息.
实现方式1
/** * 根据不同日志级别输出日志信息 */ public class LogPrintTest { @Test public void test() { //定义三个日志信息 String msg1 = "Hello "; String msg2 = "World "; String msg3 = "Java"; //调用showLog方法,传递日志级别和日志信息 printLog(2, msg1 + msg2 + msg3); } //定义一个根据日志的级别,显示日志信息的方法 public void printLog(int level, String message) { //对日志的等级进行判断,如果式1级别,那么输出日志信息 if (level == 1) { System.out.println(message); } } }
分析:
以上代码存在性能浪费, 调用printLog方法,第二个参数是一个拼接后的字符串, 先把字符串拼接好,然后在调用printLog方法打印日志信息. printLog方法中如果传递的日志等级不是1, 也会进行字符串的拼接, 导致不必要的资源浪费.
实现方式2: Lamdba表达式优化
/** * 根据不同日志级别输出日志信息 */ public class LogPrintTest { @Test public void test2() { //定义三个日志信息 String msg1 = "Hello "; String msg2 = "World "; String msg3 = "Java"; //调用showLog方法,参数MessageBulider是一个函数式接口,所以可以传递Lambda表达式 showLog(1, () -> msg1 + msg2 + msg3); } //定义一个显示日志的方法,方法的参数传递日志的等级和Message接口 public void showLog(int level, MessageBulider mb) { //对日志的等级进行判断,如果是1级,则调用MessageBulider接口中的BuilderMessage方法 if (level == 1) { System.out.println(mb.builderMessage()); } } // 定义一个函数式接口 @FunctionalInterface public interface MessageBulider { //定义一个拼接消息的抽象方法,返回拼接的消息 String builderMessage(); } }
分析:
使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中 , 只有满足日志等级是1的条件, 才会传递第二个参数(
接口MessageBuilder中的方法builderMessage拼接日志信息); 如果不满足日志等级是1的条件, 那么MessageBuilder接口中的方法builderMessage也不会执行, 拼接字符串的代码也不会执行, 所以不会存在性能的浪费 .
Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。
例如, java.lang.Runnable 接口就是一个函数式接口,假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
lambda表达式作为方法的参数使用
/** * lambda表达式作为方法的参数 */ public class LambdaTest2 { @Test public void test() { startThread(() -> System.out.println(">>>lambda表达式实现线程任务执行!")); startThread(new Runnable() { @Override public void run() { System.out.println(">>>匿名内部类方式实现线程任务执行!"); } }); } //定义一个方法startThread,方法的参数使用函数式接口Runnable private void startThread(Runnable task) { //开启多线程 new Thread(task).start(); } }
类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。
例如, 当需要通过一个方法来获取一个java.util.Comparator 接口类型的对象作为排序器时, 就可以调该方法获取。
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
// .....
}
lambda表达式作为方法的返回值使用
/** * lambda表达式作为方法的返回值 */ public class LambdaTest3 { @Test public void test() { String[] array = {"aaa", "bbb", "ccc"}; System.out.println(Arrays.toString(array)); // lambda表达式实现 Comparator#compare方法返回值 Arrays.sort(array, (o1, o2) -> o2.length() - o1.length()); System.out.println(Arrays.toString(array)); // 匿名内部类方式实现 Comparator#compare方法返回值 Arrays.sort(array, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o2.length() - o1.length(); } }); } }
若lambda体中的内容有方法已经实现了,那么可以使用方法引用 , 也可以理解为方法引用是lambda表达式的另外一种表现形式, 并且其语法比lambda表达式更加简单 . 下面先通过一个简单的案例来说明.
/** * 方法的基本引用 */ public class PrintFunctionalTest { @Test public void test() { // 方式1: lambda表达式实现了函数式接口, 进行字符串的输出打印 this.printstring("test print", str -> System.out.println(str)); // 方式2: 方法引用方式实现字符串的输出打印 // 引用方法的参数就是lambda表达式的参数 this.printstring("test print by qout", System.out::println); } //定义一个方法,方法的参数传递PrintFunctionInterface接口 private void printstring(String str, PrintFunctionInterface printFunctionInterface) { printFunctionInterface.printStr(str); } // 定义一个函数式接口 @FunctionalInterface public interface PrintFunctionInterface { //定义打印字符串的抽象方法 void printStr(String str); } }
双冒号:: 为引用运算符 , 它所在的表达式被称为方法引用 . 如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号::来引用该方法作为Lambda表达式的替代者。
语义分析 :
上例中,System.out对象中有一个重载的println(String str)方法恰好就是我们所需要的。 那么对于printstring方法的函数式接口参数,对比下面两种写法,完全等效:
第一种语义是拿到参数之后经Lambda表达式,继而传递给 System.out.println方法去处理。
第二种语义是直接让System.out中的println方法来取代Lambda表达式。
两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。
注意 : Lambda表达式中传递的参数一定是方法引用中的那个方法可以接收的类型 , 否则会抛出异常
对象名已经存在,成员方法也已经存在的, 就可以使用对象名来引用成员方法 .
public class PrintFunctionalTest { @Test public void test2() { // 方式1: 通过对象直接调用类的成员方法 this.printString2("test print by qout", str -> { MethodRerObject methodRerObject = new MethodRerObject(); methodRerObject.printUpperCaseString(str); }); // 方式2: 使用方法引用来优化lambda, 使用对象名引用成员方法 MethodRerObject methodRerObject = new MethodRerObject(); this.printString2("test print by qout", methodRerObject::printUpperCaseString); } //定义一个方法,方法的参数传递PrintFunctionInterface接口 private void printString2(String str, PrintFunctionInterface printFunctionInterface) { printFunctionInterface.printStr(str); } // 定义一个函数式接口 @FunctionalInterface public interface PrintFunctionInterface { //定义打印字符串的抽象方法 void printStr(String str); } // 定义一个类,提供一个字符串输出方法 class MethodRerObject { //定义一个成员方法,传递字符串,把字符串按照大写输出 public void printUpperCaseString(String str) { System.out.println(str.toUpperCase(Locale.ROOT)); } } }
上面自定义的函数式接口PrintFunctionInterface是消费型接口 , 可以直接使用JDK8自带的Consumer接口, 调用里面accept抽象方法消费数据即可.
@Test
public void test3() {
MethodRerObject methodRerObject = new MethodRerObject();
this.printString3("test print by qout", methodRerObject::printUpperCaseString);
}
//定义一个方法,方法的参数传递Consumer接口, 也就是消费数据
private void printString3(String str, Consumer<String> consumer) {
consumer.accept(str);
}
当lambda表达式的实现内容有两个相同类型的参数 , 且一个参数对象调用成员方法的参数列表是另外一个参数, 那么可以使用类名引用成员方法来简化代码.
@Test
public void test4() {
// 方式1: 参数对象进行调用
boolean b = this.equalsStr("hello", "world",
(str1, str2) -> !StringUtils.isEmpty(str1) && str1.equals(str2));
// 方式2: 方法引用-类名::实例方法名
b = this.equalsStr("hello", "world", String::equals);
System.out.println(b);
}
// 定义一个成员方法, 比较两个字符串是否相等, 第三个参数传递一个BiFunction函数
private boolean equalsStr(String str1, String str2, BiFunction<String, String, Boolean> fun) {
return fun.apply(str1, str2);
}
类已经存在,静态成员方法也已经存在,就可以通过类名直接引用静态成员方法 .
/** * 类名引用静态方法 */ public class StaticMethodReferenceTest { @Test public void test() { //方式1: 调用method方法,传递计算绝对值的整数和Lambda表达式 int number = method(-10, num -> Math.abs(num)); System.out.println(number); //方式2: 使用方法引用优化Lambda表达式, 通过类名引用静态方法 number = method(-10, Math::abs); System.out.println(number); } //定义一个方法,方法的参数传递要计算绝对值的整数和函数式接口Calcable public int method(int num, Calcable c) { return c.calsAbs(num); } @FunctionalInterface public interface Calcable { //定义一个抽象方法,传递一个整数,对整数进行绝对值计算并返回 int calsAbs(int num); } }
案例2
@Test
public void test2() {
Integer number = method2(100, 200, (num1, num2) -> Integer.compare(num1, num2));
System.out.println(number);
number = method2(200, 100, Integer::compare);
System.out.println(number);
}
// 定义一个方法, 消费参数num1和num2, 并返回消费结果
private Integer method2(Integer num1, Integer num2, BiFunction<Integer, Integer, Integer> biFunction) {
return biFunction.apply(num1, num2);
}
因为有子父类关系,所以存在一个关键字super,代表父类,所以直接使用Super调用父类的成员方法.
定义父类
public class Parent {
void sayHello() {
System.out.println("hello, i'm parent");
}
}
定义子类
/** * super引用父类成员方法 */ public class SuperReferenceTest extends Parent { @Test public void test() { //调用method方法,方法的参数Greetable是一个函数式接口,所以可传递Lambda greet(() -> { Parent parent = new Parent(); parent.sayHello(); }); // 直接使用super调用 greet(() -> { super.sayHello(); }); // supper优化lambda表达式, 方法引用 greet(super::sayHello); } //定义一个方法参数传递Greetable接口 public void greet(Greetable greetable) { greetable.greet(); } @Override void sayHello() { System.out.println("hello, i'm children"); } // 定义打招呼的函数式接口 @FunctionalInterface interface Greetable { void greet(); } }
使用this引用本类的成员方法
/** * super引用父类成员方法 */ public class SuperReferenceTest extends Parent { @Test public void test2() { // 方式1 greet(() -> { SuperReferenceTest children = new SuperReferenceTest(); children.sayHello(); }); // 方式2 greet(() -> this.sayHello()); // 方式三 this引用 greet(this::sayHello); } //定义一个方法参数传递Greetable接口 public void greet(Greetable greetable) { greetable.greet(); } @Override void sayHello() { System.out.println("hello, i'm children"); } // 定义打招呼的函数式接口 @FunctionalInterface interface Greetable { void greet(); } }
格式:ClassName::new
定义一个创建Person对象的函数式接口 , 创建Person类.
有参构造器方法的引用
/** * 构造器的方法引用 */ public class ConstructRefrenceTest { /** * 有参构造方法的引用 */ @Test public void test() { // 方式1: lambda表达式, 自己new对象实例化, 传入name参数 this.printName("crysw", name -> new Person(name)); // 方式2: 传入的name参数就是构造方法的参数, 可以使用类的构造器引用来替换lambda表达式实现 this.printName("crysw agin", Person::new); } private void printName(String name, PersonBuilder personBuilder) { personBuilder.builderPerson(name); System.out.println(name); } //定义一个创建Person对象的函数式接口 @FunctionalInterface interface PersonBuilder { //定义一个方法,根据传递的姓名,创建Person对象返回 Person builderPerson(String name); } // 定义一个Person类 public class Person { private String name; public Person() { } public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } }
无参构造方法的引用
/**
* 无参构造方法的引用
*/
@Test
public void test2() {
// 方式1:
Person person = this.getPerson(() -> new Person());
System.out.println(">>" + person);
person = this.getPerson(Person::new);
System.out.println(">>>" + person);
}
private Person getPerson(Supplier<Person> supplier) {
return supplier.get();
}
格式:Type[]::new
demo1
/** * 数组的构造器引用 */ public class ArrayRefrenceTest { @Test public void test() { // 方式1: lambda表达式,传递length参数构造数组对象 int[] array = this.createArray(2, (length) -> new int[length]); System.out.println(">>" + array.length); // 方式2: 传递的length参数就是构造数组对象的参数, 可以使用数组的构造器引用 array = this.createArray(2, int[]::new); System.out.println(">>>" + array.length); } //定义一个方法,方法的参数传递创建数组的长度和ArrayBuilder接口 //方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回 private int[] createArray(int length, ArrayBuilder arrayBuilder) { return arrayBuilder.builderArray(length); } @FunctionalInterface interface ArrayBuilder { //定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组 int[] builderArray(int length); } }
demo2
@Test
public void test2() {
// 方式1: lambda表达式, 通过传递的参数构建String数组
String[] strArr = this.getString(10, integer -> new String[integer]);
System.out.println(">>" + strArr.length);
// 方式2: String数组构造方法的引用实现
strArr = this.getString(20, String[]::new);
System.out.println(">>>" + strArr.length);
}
// 定义一个方式, 参数传入函数接口, 将Integer类型参数转换为一个String[interger]类型返回
private String[] getString(Integer integer, Function<Integer, String[]> function) {
return function.apply(integer);
}
Java8 中添加了一个新的流接口操作的概念, 在 java.util.stream包下面,相当于高级版的 Iterator,它可以通过 Lambda 表达式对集合进行大批量数据操作,或者各种便利、高效的聚合数据操作, 用于解决集合类库既有的弊端。
为什么选择Stream流
在 Java8 之前,主要通过 for 循环或者 Iterator 迭代来重新排序合并数据,又或者通过重新定义 Collections.sorts 的 Comparator 方法来实现,这两种方式对于大数据量系统来说,效率并不是很理想。
Stream 的聚合操作与数据库 SQL 的聚合操作 sorted、filter、map 等类似。我们在应用层就可以高效地实现类似数据库 SQL 的 聚合操作了,而在数据操作方面,Stream 不仅可以通过串行的方式实现数据操作,还可以通过并行的方式处理大批量数据,提高数据 的处理效率。
超类接口 BaseStream 派生出了 IntStream,LongStream,Stream 以及 DoubleStream 4个流接口.
public interface IntStream extends BaseStream<、Intege、r, IntStream> {}
public interface LongStream extends BaseStream<Long, LongStream> {}
public interface DoubleStream extends BaseStream<Double, DoubleStream> {}
public interface Stream<T> extends BaseStream<T, Stream<T>> {}
Stream操作的三个步骤 : 创建Stream, 中间操作(过滤, map), 终止操作.

获取Stream流的方式
所有的Collection集合都可以通过默认的stream方法获取Stream流 .
default Stream stream()
所有的Collection集合也可以通过默认的parallelStream方法获取Stream流, 将数据拆分并行处理.
default Stream<E> parallelStream()
Stream接口的of静态方法可以获取数组对应的流.
static Stream of (T… values)
@Test public void test2() { //把集合转换为Stream流 List<String> list = new ArrayList<>(); Stream<String> stream = list.stream(); // parallelStream 将数据拆分成小块并行处理 Stream<String> parallelStream = list.parallelStream(); Set<String> set = new HashSet<>(); Stream<String> stream1 = set.stream(); Map<String, String> map = new HashMap<>(); //获取键,存储到一个Set集合中 Set<String> keySet = map.keySet(); Stream<String> stream2 = keySet.stream(); //获取值,存储到一个Collection集合中 Collection<String> values = map.values(); Stream<String> stream3 = values.stream(); //获取键值对(键与值的映射关系 entrySet) Set<Map.Entry<String, String>> entries = map.entrySet(); Stream<Map.Entry<String, String>> stream4 = entries.stream(); //把数组转换为Stream流 Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5); //可变参数可以传递数组 Integer[] arr = {1, 2, 3, 4, 5}; Stream<Integer> stream5 = Stream.of(arr); String[] arr2 = {"a", "bb", "ccc"}; Stream<String> stream6 = Stream.of(arr2); // 4.创建无限流 // 迭代 Stream<Integer> stream7 = Stream.iterate(0, x -> x + 2); //生成 Stream<Double> stream8 = Stream.generate(() -> Math.random()); }
中间操作会返回一个新的流,一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤. 然后会返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),仅仅调用到这类方法,并没有真正开始流的遍历, 而是在终结操作开始的时候才真正开始执行。
中间操作又可以分为无状态(Stateless)与有状态(Stateful)操作.
无状态是指元素的处理不受之前元素的影响;
有状态是指该操作只有拿到所有元素之后才能继续下去。
因为 Stream 操作类型非常多,列出常用的:
/**
* Stream流中的常用方法 遍历forEach
* void forEach(Consumer<? super T> action);
* 该方法接收一个Consumer接口函数,会将一个流元素交给函数进行处理
* forEach方法,用来遍历流中的数据
* 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
*/
@Test
public void test3() {
//获取一个Stream流
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");
//使用Stream流中的方法forEach对Stream流中的数据进行遍历
stream.forEach(System.out::println);
}
/** * Stream流中的常用方法filter,用于对Stream流中的数据进行过滤 * Stream filter(Predicate<? super T>predicate); * filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤 * Predicate中的抽象方法:boolean test(T t) */ @Test public void test4() { //创建一个Stream流 Stream<String> stream = Stream.of("张三丰", "张翠山", "赵煜", "杨哲", "刘哈哈", "武屌屌"); //对Stream流中的元素进行filter过滤,只要姓张的人 stream.filter(name -> name.startsWith("张")).forEach(System.out::println); // 第一个Stream流已经使用完毕,就会关闭了,所以第一个Stream就不能再调用方法了 // java.lang.IllegalStateException: stream has already been operated upon or closed // System.out.println(stream.count()); }
Stream流属于一个管道流,只能被消费(使用)一次.
第一个Stream流调用完毕,数据就会流转到下一个Stream上, 而这时第一个Stream流已经使用完毕,就会关闭了, 所以第一个Stream就不能再调用方法了. Stream流调用filter方法后返回一个新的Stream流.
例如,我们之前使用的元素为圆形,调用map方法就可以映射为菱形 .

/** * 如果需要将流中的元素映射到另一个流中,可以使用map方法 * Stream map(Function<? super T,? extends R> mapper); * IntStream mapToInt(ToIntFunction mapper):产生包含这些元素的一个新的流 * 该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流 * Function中的抽象方法 apply(T t) */ @Test public void test5() { //获取一个String类型的Stream流 Stream<String> stream = Stream.of("1", "2", "3", "4", "5"); //使用map方法,把字符串类型的整数转换(映射)为Integer类型的整数 Stream<Integer> integerStream = stream.map((str) -> Integer.parseInt(str)); //遍历Stream1流 integerStream.forEach(System.out::println); }
/**
* Stream流中常用方法:limit:用于截取流中的元素
* limit方法可以对流进行截取,只取用前n个,
* Stream limit(long maxSize);
* 参数是一个long型,如果集合当前长度大于参数则进行截取,否则不进行操作
* limit是一个延迟方法,只对流的元素进行截取,返回一个新的流,所以可以继续调用Stream流中的其他方法
*/
@Test
public void test7() {
//获取一个Stream流
String[] arr = {"慢羊羊", "喜羊羊", "懒羊羊", "美羊羊", "沸羊羊", "暖羊羊", "快羊羊", "瘦羊羊"};
Stream<String> stream = Stream.of(arr);
Stream<String> limitStream = stream.limit(4);
limitStream.forEachOrdered(System.out::println);
}
/**
* Stream流中的常用方法:skip:用于跳过元素
* 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流
* Stream ship(long n);
* 如果流的当前长度大于n,则跳过前n个,否则将会得到一个长度为0的空流
*/
@Test
public void test8() {
//获取一个Stream流
String[] arr = {"慢羊羊", "喜羊羊", "懒羊羊", "美羊羊", "沸羊羊", "暖羊羊", "快羊羊", "瘦羊羊"};
Stream<String> stream = Stream.of(arr);
Stream<String> stringStream = stream.skip(4);
stringStream.forEach(System.out::println);
}
/**
* Stream流中的常用方法 distinct, 返回流中的不同元素, 达到去重的效果
* Stream<T> distinct();
* 需要流中的元素重写hashCode和equals方法
*/
@Test
public void test90() {
String[] arr = {"喜羊羊", "喜羊羊", "懒羊羊", "美羊羊", "喜羊羊", "暖羊羊", "暖羊羊", "暖羊羊"};
Stream<String> stream = Stream.of(arr);
stream.distinct().forEach(System.out::println);
}
/** * Stream流中的常用方法 sorted, 排序 * Stream<T> sorted(); 默认排序 * Stream<T> sorted(Comparator<? super T> comparator); 自定义排序 * {@link Comparator#compare} */ @Test public void test91() { //获取一个String类型的Stream流 Stream<String> stream = Stream.of("3", "1", "2", "5", "4"); // 升序 stream.sorted().forEach(System.out::println); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>"); Stream<String> stream2 = Stream.of("3", "1", "2", "5", "4"); // 降序, 自定义排序 stream2.sorted((a, b) -> b.compareTo(a)).forEach(System.out::println); }
/**
* Stream流中的常用方法concat 用于把流组合到一起
* 如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat, 返回一个新的流
* static Stream concat(Stream<? extends T>a,Stream(Stream<? extends T>b)
*/
@Test
public void test9() {
String[] arr = {"慢羊羊", "喜羊羊", "懒羊羊", "美羊羊", "沸羊羊", "暖羊羊", "快羊羊", "瘦羊羊"};
Stream<String> stream = Stream.of("张三丰", "张翠山", "赵煜", "杨哲", "刘哈哈", "武屌屌");
Stream<String> stream1 = Stream.of(arr);
//把以上两个流组合为一个流
Stream<String> concatStream = Stream.concat(stream, stream1);
concatStream.forEach(System.out::println);
}
@Test
public void test6() {
Stream.iterate(0, n -> n + 3). // 从0开始, 迭代加3
limit(10). // 返回10个元素
forEach(x -> System.out.print(x + " "));
// 执行结果 0 3 6 9 12 15 18 21 24 27
}
终止操作是指返回最终的结果。一个流只能有一个终止操作,当这个操作执行后,这个流就被使用完毕了,无法再被操作。终止操作的执行才会真正开始流的遍历,并且会生成一个结果。
终止操作又可以分为短路(Short-circuiting)与非短路(Unshort-circuiting)操作.
短路是指遇到某些符合条件的元素就可以得到最终结果.
非短路是指必须处理完所有元素才能得到最终结果.
常用终止操作的方法:
allMatch-检查是否匹配所有元素
anyMatch-检查是否至少匹配一个元素
noneMatch-检查是否没有匹配所有元素
findFirst-返回第一个元素
findAny-返回当前流中的任意元素
count-返回流中元素的总个数
max-返回流中最大值
min-返回流中最小值
/**
* Stream流中的常用方法,count,用于统计Stream流中元素的个数
* long count();
* count方法是一个终结方法,返回值是一个long类型的整数
* 所以不能再继续调用Stream流中的其他方法
*/
@Test
public void test6() {
//获取一个Stream流
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
Stream<Integer> stream = list.stream();
long count = stream.count();
System.out.println(count);
}
reduce 的主要作用是把 Stream 元素组合起来。它提供一个起始值, 然后依照运算规则(BinaryOperator) , 和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就可以用reduce实现,如下:
@Test
public void test7() {
// reduce(T identity, BinaryOperator<T> accumulator);
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum);
// Optional<T> reduce(BinaryOperator<T> accumulator);
Optional<Integer> sum2 = list.stream()
.reduce(Integer::sum);
System.out.println(sum2.get());
}
字符串拼接, 求和, 计算最大值, 最小值操作.
@Test public void test4() { // 字符串连接,有起始值(空字符串), concat = "ABCD" String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); System.out.println(concat); // 求最小值,minValue = -3.0 double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MIN_VALUE, Double::min); System.out.println(minValue); minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double::min).get(); System.out.println(minValue); // 求和,sumValue = 10, 有起始值 int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); System.out.println(sumValue); // 求和,sumValue = 10, 无起始值 sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get(); System.out.println(sumValue); // 过滤,字符串连接,有起始值(空字符串), concat = "ace" concat = Stream.of("a", "B", "c", "D", "e", "F") .filter(x -> x.compareTo("Z") > 0) .reduce("", String::concat); System.out.println(concat); }
对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中 .
collect(Collectors.toList()) 返回List集合
@Test
public void test8(){
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> squaresList = numbers.stream()
.map(i -> i * i) // 计算元素平方
.distinct() // 去重
.collect(Collectors.toList()); // 聚合元素
System.out.println("Squares List: " + squaresList);
}
**collect(Collectors.joining(“,”) )**聚合数据后加入分隔符, 并转换为字符串类型返回
@Test
public void testJdk8P() {
//数字平方排序(倒叙)输出
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
String squaresList = numbers.stream()
.map(i -> i * i) // 获取对应的平方数
.sorted((x, y) -> y - x) // 降序排序
.map(x -> String.valueOf(x)) // 集合元素由Integer转换为String
.collect(Collectors.joining(","));// 聚合数据且加入分隔符, 返回String类型结果集
System.out.println(squaresList); // 49,25,9,9,9,4,4
}
collect(Collectors.toMap(Function keyMapper, Function valueMapper)) 将List转换成Map, keyMapper的lambda实现对应Map的key, valueMapper的lambda实现对应Map的value
@Test public void testJdk8P2() { //字符串转 map 输出 List<String> strList = Arrays.asList("a", "ba", "bb", "abc", "cbb", "bba", "cab"); Map<Integer, String> strMap = strList.stream().collect(Collectors.toMap( // key-value key=索引 value=str str -> strList.indexOf(str), // key=index str -> str) // value=str ); strMap.forEach((key, value) -> { System.out.println(key + ":" + value); /** 执行结果 0:a 1:ba 2:bb 3:abc 4:cbb */ }); }
JDK7实现
@Test public void testJdk7() { System.out.println("使用 Java 7: "); // 计算空字符串 List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); System.out.println("列表: " + strings); long count = getCountEmptyStringUsingJava7(strings); System.out.println("空字符数量为: " + count); // 计算字符串长度为3的个数 count = getCountLength3UsingJava7(strings); System.out.println("字符串长度为 3 的数量为: " + count); // 删除空字符串 List<String> filtered = deleteEmptyStringsUsingJava7(strings); System.out.println("筛选后的列表: " + filtered); // 删除空字符串,并使用逗号把它们合并起来 String mergedString = getMergedStringUsingJava7(strings, ", "); System.out.println("合并字符串: " + mergedString); // 获取列表元素平方数 List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); List<Integer> squaresList = getSquares(numbers); System.out.println("平方数列表: " + squaresList); List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19); System.out.println("列表: " + integers); System.out.println("列表中最大的数 : " + getMax(integers)); System.out.println("列表中最大的数 : " + getMax2(integers)); System.out.println("列表中最小的数 : " + getMin(integers)); System.out.println("所有数之和 : " + getSum(integers)); System.out.println("平均数 : " + getAverage(integers)); System.out.println("随机数: " + new Random().nextInt()); } // 计算空字符串的个数 private static int getCountEmptyStringUsingJava7(List<String> strings) { int count = 0; for (String string : strings) { if (string.isEmpty()) { count++; } } return count; } // 计算字符串长度为3的个数 private static int getCountLength3UsingJava7(List<String> strings) { int count = 0; for (String string : strings) { if (!StringUtils.isEmpty(string) && Objects.equals(3, string.length())) { count++; } } return count; } // 删除空字符串 private List<String> deleteEmptyStringsUsingJava7(List<String> strings) { List<String> resultList = new ArrayList<>(); for (String string : strings) { if (!StringUtils.isEmpty(string)) { resultList.add(string); } } return resultList; } // 删除空字符串,并使用分隔符(逗号)把它们合并起来 private String getMergedStringUsingJava7(List<String> strings, String delimiter) { List<String> resultList = deleteEmptyStringsUsingJava7(strings); StringBuilder sb = new StringBuilder(); for (int i = 0; i < resultList.size(); i++) { if (i == resultList.size() - 1) { sb.append(resultList.get(i)); } else { sb.append(resultList.get(i)).append(delimiter); } } return sb.toString(); } // 获取列表元素平方数 private List<Integer> getSquares(List<Integer> numbers) { List<Integer> resultList = new ArrayList<>(); for (int i = 0; i < numbers.size(); i++) { Integer num = numbers.get(i); resultList.add(num * num); } return resultList; } private Integer getMax(List<Integer> integers) { Integer max = integers.get(0); for (int i = 0; i < integers.size(); i++) { Integer num = integers.get(i); if (num > max) { max = num; } } return max; } private Integer getMax2(List<Integer> integers) { // 先集合元素升序排序, 然后取最大值 Collections.sort(integers, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }); return integers.get(integers.size() - 1); } private Integer getMin(List<Integer> integers) { Integer min = integers.get(0); for (int i = 0; i < integers.size(); i++) { Integer num = integers.get(i); if (num < min) { min = num; } } return min; } // 集合元素求和 private Integer getSum(List<Integer> integers) { Integer sum = 0; for (int i = 0; i < integers.size(); i++) { Integer num = integers.get(i); sum += num; } return sum; } // 集合元素计算平均数 private double getAverage(List<Integer> integers) { double average = getSum(integers) * 1.00 / integers.size(); return average; }
JDK8实现
@Test public void testJdk8() { System.out.println("使用 Java 8: "); List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); System.out.println("列表: " + strings); long count = strings.stream().filter(str -> StringUtils.isEmpty(str)).count(); System.out.println("空字符串数量:" + count); count = strings.stream().filter(str -> !StringUtils.isEmpty(str) && Objects.equals(3, str.length())).count(); System.out.println("字符串长度为 3 的数量为: " + count); List<String> filtered = strings.stream().filter(str -> !StringUtils.isEmpty(str)).collect(Collectors.toList()); System.out.println("筛选后的列表: " + filtered); String mergedString = strings.stream().filter(str -> !StringUtils.isEmpty(str)).collect(Collectors.joining(", ")); System.out.println("合并字符串: " + mergedString); List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); List<Integer> squaresList = numbers.stream() .map(i -> i * i) .distinct() .collect(Collectors.toList()); System.out.println("Squares List: " + squaresList); List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19); System.out.println("列表: " + integers); IntSummaryStatistics stats = integers.stream().mapToInt(x -> x).summaryStatistics(); System.out.println("列表中最大的数 : " + stats.getMax()); System.out.println("列表中最小的数 : " + stats.getMin()); System.out.println("所有数之和 : " + stats.getSum()); System.out.println("平均数 : " + stats.getAverage()); System.out.println("随机数: "); Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println); // 并行处理 count = strings.parallelStream().filter(str -> StringUtils.isEmpty(str)).count(); System.out.println("空字符串的数量为: " + count); }
文件流读取文件的文本内容, 然后转换成Stream流进行一些列操作.
@Test public void test5() throws IOException { // 找出最长一行的长度 BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log")); int longest = br.lines() .mapToInt(String::length) // 读取每行的字符长度 .max() // 最大长度 .getAsInt(); // 转换为int返回 br.close(); System.out.println(longest); // 找出全文的单词,转小写,并排序 List<String> words = br.lines(). flatMap(line -> Stream.of(line.split(" "))) // 每行分割单词 .filter(word -> word.length() > 0) // 去除空格 .map(String::toLowerCase) // 单词转大写 .distinct() // 去重 .sorted() // 默认升序排序 .collect(Collectors.toList()); // 收集数据 br.close(); System.out.println(words); }
Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。 默认方法让开发者可以在不破坏二进制兼容性的前提下,往现存接口中添加新的方法,不用强制那些已经实现了该接口的类也同时实现这个新加的方法。
默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:
private interface Defaulable { // 默认方法 default String notRequired() { return "Default implementation"; } } // 继承接口的默认方法 private static class DefaultableImpl implements Defaulable { } // 重写接口的默认方法 private static class OverridableImpl implements Defaulable { @Override public String notRequired() { return "Overridden implementation"; } }
Defaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。
Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:
private interface DefaulableFactory {
// 静态方法
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
下面的代码片段整合了默认方法和静态方法的使用场景:
/** * 接口中的默认方法和静态方法 */ public class DefaultNStaticInterfaceTest { @Test public void test() { Defaulable defaulable = DefaulableFactory.create(DefaultableImpl::new); System.out.println(defaulable.notRequired()); defaulable = DefaulableFactory.create(OverridableImpl::new); System.out.println(defaulable.notRequired()); } private interface DefaulableFactory { // 静态方法 static Defaulable create(Supplier<Defaulable> supplier) { return supplier.get(); } } private interface Defaulable { // 默认方法 default String notRequired() { return "Default implementation"; } } // 继承接口的默认方法 private static class DefaultableImpl implements Defaulable { } // 重写接口的默认方法 private static class OverridableImpl implements Defaulable { @Override public String notRequired() { return "Overridden implementation"; } } }
尽管默认方法有这么多好处,但在实际开发中应该谨慎使用. 在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档。
LocalDateTime是一个不可变的日期时间对象,代表日期时间,通常被视为年 - 月 - 日 - 时 - 分 - 秒。 也可以访问其他日期和时间字段,例如日期,星期几和星期。 时间表示为纳秒精度。 例如,值“2007年10月2日在13:45.30.123456789”可以存储在LocalDateTime 。
获取当前的日期时间
LocalDateTime date = LocalDateTime.now();
System.out.println("当前时间: " + date);
System.out.println(date.getYear());
System.out.println(date.getMonthValue());
System.out.println(date.getDayOfMonth());
System.out.println(date.getHour());
System.out.println(date.getMinute());
System.out.println(date.getSecond());
System.out.println(date.getNano());
手动创建一个LocalDateTime实例
LocalDateTime date2 = LocalDateTime.of(2017, 12, 17, 9, 31, 31, 31);
//LocalDateTime date3 = date2.withMonth(10).withDayOfMonth(10).withYear(2012);
System.out.println(date2);
// 进行加操作,得到新的日期实例
LocalDateTime date3 = date2.plusDays(12);
System.out.println(date3);
// 进行减操作,得到新的日期实例
LocalDateTime date4 = date3.minusYears(2);
System.out.println(date4);
LocalDate是一个不可变的日期对象,表示日期,通常被视为年月日。 也可以访问其他日期字段,例如日期,星期几和星期。 例如,值“2007年10月2日”可存储在LocalDate 。
创建LocalDate实例
// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
// 解析日期字符串
LocalDate parseDate = LocalDate.parse("2014-12-12");
System.out.println("parseDate: " + parseDate);
LocalTime是一个不可变的时间对象,代表一个时间,通常被看作是小时 - 秒。 时间表示为纳秒精度。
创建LocalTime实例
// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
// 解析时间字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
ZonedDateTime是具有时区的日期时间的不可变表示。 此类存储所有日期和时间字段,精度为纳秒,时区为区域偏移量,用于处理模糊的本地日期时间。
创建ZonedDateTime对象
// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date1: " + date1); // date1: 2015-12-03T10:15:30+08:00[Asia/Shanghai]
// 获取时区
System.out.println(date1.getZone().getId()); // Asia/Shanghai
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id); // Europe/Paris
Instant表示在时间线上建立单个瞬时点。 这可能用于在应用程序中记录事件时间戳。
// 时间戳 1970年1月1日00:00:00 到某一个时间点的毫秒值
// 默认获取UTC时区
Instant ins = Instant.now();
System.out.println(ins);
// 下面两个等价
long x = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
System.out.println(x);
long x1 = System.currentTimeMillis();
System.out.println(x1);
System.out.println(Instant.now().toEpochMilli());
System.out.println(
Instant.now().atOffset(ZoneOffset.ofHours(8)).toInstant().toEpochMilli()
);
Duration用于计算两个时间之间的间隔 .
计算Instance
@Test
public void test6() throws InterruptedException {
// Duration:计算两个时间之间的间隔
Instant ins1 = Instant.now();
TimeUnit.MILLISECONDS.sleep(1000);
Instant ins2 = Instant.now();
Duration dura = Duration.between(ins1, ins2);
System.out.println(dura);
System.out.println(dura.toMillis());
}
计算LocalTime
@Test
public void test7() throws InterruptedException {
// Period:计算两个日期之间的间隔
LocalTime localTime = LocalTime.now();
TimeUnit.MILLISECONDS.sleep(1000);
LocalTime localTime2 = LocalTime.now();
Duration du2 = Duration.between(localTime, localTime2);
System.out.println(du2);
System.out.println(du2.toMillis());
}
计算LocalDateTime
@Test
public void test8() throws InterruptedException {
LocalDateTime localDateTime = LocalDateTime.now();
TimeUnit.MILLISECONDS.sleep(1000);
LocalDateTime localDateTime2 = LocalDateTime.now();
Duration du2 = Duration.between(localDateTime, localDateTime2);
System.out.println(du2);
System.out.println(du2.toMillis());
}
Period:计算两个日期之间的间隔.
@Test
public void test9() throws InterruptedException {
LocalDate localDate = LocalDate.now();
TimeUnit.MILLISECONDS.sleep(1000);
LocalDate localDate1 = LocalDate.now();
Period p = Period.between(localDate, localDate1);
System.out.println(p);
System.out.println(p.getYears() + "-" + p.getMonths() + "-" + p.getDays());
}
LocalDate
@Test
public void localDateTest() {
//获取当前日期,只含年月日 固定格式 yyyy-MM-dd 2018-05-04
LocalDate today = LocalDate.now();
// 根据年月日取日期,5月就是5,
LocalDate oldDate = LocalDate.of(2018, 5, 1);
// 根据字符串取:默认格式yyyy-MM-dd,02不能写成2
LocalDate yesteday = LocalDate.parse("2022-05-03");
// 如果不是闰年 传入29号也会报错
LocalDate.parse("2022-02-28");
}
LocalDate常用转化
/** * 日期转换常用,第一天或者最后一天... */ @Test public void localDateTransferTest() { //2018-05-04 LocalDate today = LocalDate.now(); // 取本月第1天: 2018-05-01 LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 取本月第2天:2018-05-02 LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 取本月最后一天,再也不用计算是28,29,30还是31: 2018-05-31 LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 取下一天:2018-06-01 LocalDate nextDay = lastDayOfThisMonth.plusDays(1); // 取2018年10月第一个周三 so easy?: 2018-10-03 LocalDate thirdMondayOf2018 = LocalDate.parse("2018-10-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.WEDNESDAY)); }
LocalTime
@Test
public void localTimeTest() {
//16:25:46.448(纳秒值)
LocalTime todayTimeWithMillisTime = LocalTime.now();
//16:28:48 不带纳秒值
LocalTime todayTimeWithNoMillisTime = LocalTime.now().withNano(0);
LocalTime time1 = LocalTime.parse("23:59:59");
}
LocalDateTime
@Test
public void localDateTimeTest() {
//LocalDateTime转化为时间戳 毫秒值
long time1 = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
long time2 = System.currentTimeMillis();
//时间戳转化为localdatetime
DateTimeFormatter df = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS");
String dateTime = df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time1), ZoneId.of("Asia/Shanghai")));
System.out.println(dateTime);
}
Clock
@Test
public void clockTest() {
// Get the system clock as UTC offset
final Clock clock = Clock.systemUTC();
clock.withZone(ZoneId.of("Asia/Shanghai"));
System.out.println(clock.instant());
System.out.println(clock.millis());
System.out.println(clock.getZone().getId());
}
Java应用中最常见的bug就是空指针异常NPE。在Java 8之前,Google Guava引入了Optionals类来解决这个空指针异常NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。官方资料
Optional仅仅是一个容器:存放T类型的值或者null。它提供了方法来避免显式的null检查.
常用的方法介绍:
Optional.of(T t) 创建一个非空Optional实例, 如果传递的参数是null,抛出NullPointerException.
Optional.empty() 创建一个空的Optional实例
Optional.ofNullable(T t) 若参数T t不为null,创建一个非空Optional实例,否则创建一个空实例.
isPresent() 如果Optional实例持有一个非空值 , 返回true ; 否则 返回false.
ifPresent(Consumer<? super T> consumer) 如果存在值,则使用该值调用指定的消费者,否则不执行任何操作。
orElse(T t) 如果Optional实例包含值,返回该值,否则返回默认值T t.
orElseGet(Supplier s) 如果Optional实例包含值,返回该值,否则返回函数接口的lambda表达式实现生成的默认值;
map(Function f) 如果Optional实例有值, 将现有值转换成新的值,否则返回Optional.empty();
flatMap(Function f, , Optional> mapper) 与 map(Function f)类似, 返回值是Optional.
提示:Optional.of(null) 会直接报NullPointerException.
@Test public void test() { Integer value1 = null; Integer value2 = new Integer(10); // Optional.ofNullable - 允许传递为 null 参数 Optional a = Optional.ofNullable(value1); // Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException (NPE) Optional b = Optional.of(value2); System.out.println("求和:" + sum(a, b)); System.out.println(a.toString()); System.out.println(b.toString()); } private Integer sum(Optional<Integer> a, Optional<Integer> b) { // Optional.isPresent - 判断值是否存在 System.out.println("第一个参数值存在: " + a.isPresent()); System.out.println("第二个参数值存在: " + b.isPresent()); // Optional.orElse - 如果值存在,返回它,否则返回默认值 Integer value = a.orElse(new Integer(20)); //Optional.get - 获取值,值需要存在 Integer value2 = b.get(); return value + value2; }
@Test
public void test2() {
Optional<String> fullName = Optional.ofNullable("admin");
System.out.println("Full Name is set? " + fullName.isPresent());
System.out.println("Full Name: " + fullName.orElseGet(() -> "[none]"));
System.out.println(fullName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!"));
}
// 执行结果
Full Name is set? true
Full Name: admin
Hey admin!
private void print(String s) { // 如果参数s构建的Optional实例不为空, 则执行指定的lambda进行消费 Optional.ofNullable(s).ifPresent(System.out::println); } private Integer getLength(String s) { // 如果参数s构建的Optional实例不为空, 则继续转换获取s的长度, 如果为空返回-1 return Optional.ofNullable(s).map(String::length).orElse(-1); } @Test public void test3() { String strA = "abcd "; String strB = null; print(strA); // abcd print(strB); // 没有操作被执行 System.out.println(getLength(strA)); // 4 System.out.println(getLength(strB)); // -1 // java.util.NoSuchElementException: No value present System.out.println(Optional.empty().get()); // java.lang.NullPointerException System.out.println(Optional.of(null)); // java.util.NoSuchElementException: No value present System.out.println(Optional.ofNullable(null).get()); }
在jdk1.8新的stream包中针对集合的操作也提供了并行操作流和串行操作流。并行流就是把内容切割成多个数据块,并且使用多个线程分别处理每个数据块的内容。Stream api中声明可以通过parallel()与sequential()方法在并行流和串行流之间进行切换; 集合中的 parallelStream ()方法开启一个并行流处理。jdk1.8并行流使用的是fork/join框架进行并行操作.
常用流转换方法:
Stream#parallel()
/**
* java8 并行流 parallel()
*/
@Test
public void test2() {
//开始时间
Instant start = Instant.now();
// 并行流计算 累加求和
long reduce = LongStream.rangeClosed(0, 10L).parallel()
.reduce(0, Long::sum);
System.out.println("result=" + reduce);
//结束时间
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getSeconds());
}
List#parallelStream()
@Test
public void test3() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// 顺序流执行
list.stream().forEach(System.out::print); // 12345
System.out.println("\n");
// 并行流, 多核多线程处理, 打印结果不是顺序打印
list.parallelStream()
.forEach(System.out::print); // 34521
System.out.println("\n");
}
Fork/Join 框架:Java7就出现的框架, 在必要的情况下,将一个大任务拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行汇总( join )。
在一般的线程池中, 如果一个线程正在执行的任务由于某些原因无法继续运行, 那么该线程会处于等待(阻塞)状态.
在fork/join框架中,如果某个子任务由于等待另外一个子任务的完成而无法继续运行. 那么处理该子任务的线程会主动寻找其他尚未运行的子任务来执行. 这种方式减少了线程的等待时间, 提高了性能.。
/** * 要想使用Fark—Join,类必须继承 * RecursiveAction(无返回值) * Or * RecursiveTask(有返回值) * */ public class ForkJoin extends RecursiveTask<Long> { /** * 要想使用Fark—Join * 类必须继承RecursiveAction(无返回值) 或者 RecursiveTask(有返回值) */ private static final long serialVersionUID = 23423422L; private long start; private long end; public ForkJoin() { } public ForkJoin(long start, long end) { this.start = start; this.end = end; } // 定义阙值 private static final long THRESHOLD = 10000L; @Override protected Long compute() { if (end - start <= THRESHOLD) { long sum = 0; for (long i = start; i < end; i++) { sum += i; } return sum; } else { long middle = (end - start) / 2; ForkJoin left = new ForkJoin(start, middle); //拆分子任务,压入线程队列 left.fork(); ForkJoin right = new ForkJoin(middle + 1, end); right.fork(); //合并并返回 return left.join() + right.join(); } } }
测试
/** * 实现数的累加 */ @Test public void test1() { //开始时间 Instant start = Instant.now(); //这里需要一个线程池的支持 ForkJoinPool pool = new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoin(0L, 10000000000L); // 没有返回值 pool.execute(task); // 有返回值 // long sum = pool.invoke(task); // System.out.println("sum=" + sum); //结束时间 Instant end = Instant.now(); System.out.println(Duration.between(start, end).get(ChronoUnit.SECONDS)); }
over.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。