一、Lambda表达式
二、函数式接口
三、Stream API
四、默认方法和静态方法
五、新时间日期API
一、Lambda表达式
Java8中引入了一种新的语法元素和操作符”->”该符号被称作为Lambda操作符,格式如下:
(param)->System.out.println("param:"+param);
“->”将Lambda表达式分成了两个部分:
左侧括号内指定了Lambda表达式需要的所有参数
右侧为Lambda体
优点:让代码变的简洁、灵活
1.使用Lambda表达式代替匿名
public interface A {
public void Demo();
}
//一般的写法,首先实现接口然后再实现方法
public class TestA implements A {
@Override
public void Demo() {
System.out.println("这是A的实现类");
}
}
public class Lambda {
public static void main(String[] args) {
A a=new TestA();
a.Demo();
}
}
//(2).匿名内部类的写法
public class Lambda {
public static void main(String[] args) {
A a=new A(){
@Override
public void Demo() {
System.out.println("这是匿名内部类");
}
};
a.Demo();
}
}
//(3).使用Lambda表达式的写法
public class Lambda {
public static void main(String[] args) {
A a=()->System.out.println("这是Lambda表达式");
a.Demo();
}
}
2、Lambda表达式语法
(1)无返回值
第1种:无参的情况
() ->System.out.println(“Hello”);
第2种:有一个参数的情况,当只有一个参数时可以将参数两边的括号省略和参数类型
写法一、(str) ->System.out.println(“str:”+str);
写法二、str ->System.out.println(“str:”+str);
第3种:多个参数的情况,可以省去参数类型,参数的类编译器能够自行推断
写法一、(String str1,String str2) ->System.out.println(“str1:”+str1+”str2:”+str2);
写法二、(str1,str2) ->System.out.println(“str1:”+str1+”str2:”+str2);
(2)有返回值
第1种:当Lambda体中只有一条语句时,大括号{}和return都可以省略。
写法一、(x,y) ->{return x+y;}
写法二、(x,y) ->x+y;
注意:当Lambda体中有多条语句时要将语句写在大括号{}中,用分号”;”隔开。只用一条语句时就不需要带大括号了。
例1:() ->System.out.println(“Hello”);
例2:
() ->{ System.out.println(“Hello”); return “World”; }
二、函数式接口
函数式接口@FunctionalInterface,简单的来说函数式接口就是指仅含有一个抽象方法的接口,以@Functionalnterface标注,注意这里的抽象方法指的是该接口自己特有的抽象方法,而不包含它从其上级继承过来的抽象方法。
1、将Lambda作为参数传递
@FunctionalInterface
public interface A {
public String Demo(String str);
}
public class Lambda {
public static void main(String[] args) {
String str0=DemoPlay(
(str1->{ System.out.println("******");return str1; }),
"hello");
System.out.println(“输出结果:”str0); //输出结果:hello
}
/**
* @param a 函数式接口A
* @param str
* @return
*/
public static String DemoPlay(A a,String str){
return a.Demo(str);
}
}
2、Java8内置的四大核心函数式接口
Consumer: 消费型接口
void accept(T t);
Supplier:供给型接口
T get();
Function<T, R>: 函数型接口
R apply(T t);
Predicate: 断言型接口:
boolean test(T t);
T:是参数,R:是返回值
public class Lambda1 {
@Test
public void test1(){
System.out.println("这是测试");
testConsumer(1000,(el) -> System.out.println("消费了"+el+"元"));
}
public void testConsumer(Integer money,Consumer<Integer> con){
con.accept(money);
}
@Test
public void test2(){
System.out.println(testSupplier(() -> {return "供给型接口";}));
}
public String testSupplier(Supplier<String> sup){
return sup.get();
}
@Test
public void test3(){
System.out.println(testFunction(123,(n) -> {return "函数型接口"+n;}));
}
public String testFunction(int num,Function<Integer,String> fun){
return fun.apply(num);
}
@Test
public void test4(){
System.out.println(testPredicate("123",(str)->{
if(str.equals("1234")){
return true;
}
return false;
}));
}
public boolean testPredicate(String str,Predicate<String> pre){
return pre.test(str);
}
}
三、Stream API
Stream是Java8中处理集合的关键抽象概念,可以对指定的集合进行复杂的查找、过滤、映射数据等各种操作,和数据库中使用SQL查询数据相类似。
注意:Stream不会存储数据元素,使用Stream也不会改变源对象,只是会返回一个被包装过的新Stream(符合查找、过滤条件的)。此外Stream操作是延迟执行的,也就是会等到需要的结果的时候才执行。
对Stream操作的三个步骤
1、创建Stream
创建Stream需要有一个数据源,像集合、数组等,不同类型的数据源有不同的获取流的方式,
(1)集合:Java8中的Collection被扩展后提供了两个获取流的方法
default Stream<E> stream() //获取顺序流
default Stream<E> parallelStream() //并行流
例1:
List<String> list = Arrays.asList("aaa","bbb","bcc","add","aee","aff");
list.stream().forEach(System.out::println);
例2:
list.parallelStream().forEach(System.out::println);
(2)数组:Java8中的数组采用Arrays的静态方法stream()获取流
Static <T> Stream<T> stream(T[] array)
例3:
String[] list = { "a", "b", "c" };
Arrays.stream(list).forEach(System.out::print);
(3)值:可以使用静态方法Stream.of(),通过显示值创建一个流,并且它可以接收任意数量的不同类型的参数
Public static<T> Stream<T> of(T.. values)
Stream.of(11,12,13,14).forEach(System.out::println);
2、中间操作
在一组中间操作中如果没有触发终止操作的操作存在,那么这一组中间操作将不会执行任何处理,而是在最后的终止操作中一次性处理,这种操作被称为”惰性求值”
筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收lambda,从流中排除某些元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
distinct() | 筛选,通过流所生成元素的hashcode()和equals()去重复元素 |
skip(long n) | 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流,与limit(n)互补 |
映射
方法 | 描述 |
---|---|
map(Function f) | 接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素 |
排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序 |
例1
@Test
public void test3(){
List<String> list =Arrays.asList("aa","bb","cc","dd");
list.stream().sorted().forEach(System.out::print);
//输出结果:aa bb cc dd
list.stream().sorted((x,y) -> {
if(x.equals(y)){
return 1;
}else{
return -1;
}
} ).forEach(System.out::println);
//输出结果:dd cc bb aa
}
3、终止操作
终止操作会从中间操作生成结果,结果可以是任何不是流的值,例如:List、Integer,甚至是void。
查找与匹配
方法 | 描述 |
---|---|
findFirst | 返回第一个元素 |
findAny | 返回当前流中的任意元素 |
count | 返回流中元素的总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
froEach(Consumer c) | 内部迭代 |
归约
方法 | 描述 |
---|---|
reduce(T iden,BinaryOperator b) | 可以将流中的值反复结合起来,得到一个值。返回T |
reduce(BinaryOperator b) | 可以将流中的值反复结合起来,得到一个值。返回Optional |
例1:
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);
输出结果:55
例2:
Optional<Double> op = employeeList.stream() //可能为空 所有返回的是Optional
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(op.get());
输出结果是所有员工的工资和
收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
例:提取User对象的name,并将结合内的所有对象name收集到list1中
List<Employee> employeeList = Arrays.asList(
new Employee("zhangsan", 18, 19999, Employee.Status.FREE),
new Employee("lisi", 28, 29999, Employee.Status.BUSY),
new Employee("wangwu", 38, 39999, Employee.Status.VOCATION),
new Employee("zhaoliu", 18, 17999, Employee.Status.BUSY),
new Employee("tianqi", 28, 12999, Employee.Status.FREE)
);
List<String> list1 =employeeList.stream().map(Employee::getName).collect(Collectors.toList()) ;
list1.forEach(System.out::println);
例:将结合内的所有对象name收集到set中
HashSet<String> set = employeeList.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));
set.forEach(System.out::println);
例:集合中元素总数
Long count = employeeList.stream().collect(Collectors.counting());
System.out.println(count);
例:平均年龄
double avAge=employeeList.stream().collect(Collectors.averagingInt(Employee::getAge));
System.out.println(avAge);
例:总年龄
int toAge = employeeList.stream().collect(Collectors.summingInt(Employee::getAge));
System.out.println(toAge);
例:年龄最大值
Optional<Employee> u = employeeList.stream().collect(Collectors.maxBy((e1,e2)->Integer.compare(e1.getAge(),e2.getAge() )));
System.out.println(u);
例:平均年龄
IntSummaryStatistics collect = employeeList.stream().collect(Collectors.summarizingInt(Employee::getAge));
System.out.println(collect.getAverage());
例:按年龄分组
Map<Integer, List<Employee>> l= employeeList.stream().collect(Collectors.groupingBy(Employee::getAge));System.out.println(l);
输出 结果:
{18=[Employee{name='zhangsan', age=18, salary=19999.0, id=0, status=FREE}, Employee{name='zhaoliu', age=18, salary=17999.0, id=0, status=BUSY}], 38=[Employee{name='wangwu', age=38, salary=39999.0, id=0, status=VOCATION}], 28=[Employee{name='lisi', age=28, salary=29999.0, id=0, status=BUSY}, Employee{name='tianqi', age=28, salary=12999.0, id=0, status=FREE}]}
例:根据true和false进行分区,年龄大于18岁和小于18岁两个区
Map<Boolean,List<Employee>> map= employeeList.stream().collect(Collectors.partitioningBy((x) -> x.getAge()>18));
System.out.println(map);
输出结果:
{false=[Employee{name='zhangsan', age=18, salary=19999.0, id=0, status=FREE}, Employee{name='zhaoliu', age=18, salary=17999.0, id=0, status=BUSY}], true=[Employee{name='lisi', age=28, salary=29999.0, id=0, status=BUSY}, Employee{name='wangwu', age=38, salary=39999.0, id=0, status=VOCATION}, Employee{name='tianqi', age=28, salary=12999.0, id=0, status=FREE}]}
例:连接字符串
String str = employeeList.stream().map(Employee::getName).collect(Collectors.joining(",","*","*"));
System.out.println(str);
输出结果:zhangsan,lisi,wangwu,zhaoliu,tianqi
并行流 : 就是把一个内容分成多个数据块,并用不同的线程分 别处理每个数据块的流。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并 行操作。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。
四、默认方法和静态方法
1、Java8允许接口中存在具体的实现方法,该方法称为默认方法,默认方法采用default关键字修饰
public interface A {
default void Demo1(){
System.out.println("这是默认方法");
}
}
java8在接口中引入了默认方法,通过在方法前加上default关键字就可以在接口中写方法的默认实现,但是当多个接口或父类中有相同签名的方法时,会引发一些问题,经过实验得出如下结论:(参考:https://blog.csdn.net/travellersy/article/details/74537170)
(1)当继承的父类和实现的接口中有相同签名的方法时,优先使用父类的方法。
(2)当接口的父接口中也有同样的默认方法时,就近原则调用子接口的方法。
(3)当实现的多个接口中有相同签名的方法时,必须在实现类中通过重写方法解决冲突问题,否者无法通过编译,在重写的方法中可以通过 接口名.super.方法名(); 的方式显示调用需要的方法。
例:
有一个类和两个接口
public class A {
public void methods(){
System.out.println("Object A methods");
}
}
public interface B {
default void methods(){
System.out.println("interface B methods");
}
}
public interface C {
default void methods(){
System.out.println("interface C methods");
}
}
下面通过一个类继承自A并且实现B接口:
public class Test extends A implements B{
public static void main(String[] args){
System.out.println("HelloWord!");
Test test = new Test();
test.methods();
}
}
根据就近原则test将调用父类A的方法并输出"Object A methods"
如果一个类同时实现了接口B和C:
public class Test1 implements B, C {
public static void main(String[] args){
System.out.println("HelloWord!");
Test1 test1 = new Test();
test1.methods();
}
@Override
public void methods() {
B.super.methods();
}
}
通过使用Override重写的方式解决冲突问题,并且在Override方法中通过B.super.methods();的方式显示指定调用B接口中methods方法,最终输出结果为:“interface B methods”
2、Java中允许添加静态方法
public interface A {
//抽象方法
public String Demo(String str);
default void Demo1(){
System.out.println("这是默认方法");
}
static void show(){
System.out.println("这是静态方法");
}
}
五、时间和日期操作
相比Date,LoalDate更安全、更精确也更明确
System.out.println("------------------日期操作-------------------");
//获取当前日期
LocalDate today = LocalDate.now();
System.out.println(today);
//本月的第一天
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
System.out.println(firstDayOfThisMonth);
//本月的第N天
LocalDate NDayOfThisMonth = today.withDayOfMonth(30); // 2017-03-02
System.out.println(NDayOfThisMonth);
//后一天
LocalDate firstDayOf2015 = today.plusDays(1);
System.out.println(firstDayOf2015);
//指定日期2015-02-10
LocalDate ld=LocalDate.of(2015, 2, 10);
System.out.println(ld);
//指定日期2014-03-28
LocalDate endOfFeb = LocalDate.parse("2014-03-28");
System.out.println(endOfFeb);
//两个日期之间相隔几天
Period period = Period.between(today, today.plus(2, ChronoUnit.DAYS));
System.out.println(period.getDays());
System.out.println("------------------时间操作-------------------");
//获取当前时间
LocalTime now = LocalTime.now();
System.out.println(now);
System.out.println("*********日期和时间************");
//获取当前日期和时间
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
//获取当前月的天数
System.out.println(localDateTime.getMonth().length(true));
//当月名称(英文)
System.out.println(localDateTime.getMonth().name());
//往后推迟两天
System.out.println(localDateTime.plus(2, ChronoUnit.DAYS));
//往前两天
System.out.println(localDateTime.minus(2, ChronoUnit.DAYS));
输出结果:
Mon Jul 16 11:01:04 CST 2018
------------------日期操作-------------------
2018-07-16
2018-07-01
2018-07-30
2018-07-17
2015-02-10
2014-03-28
2
------------------时间操作-------------------
11:01:05.084
日期和时间***
2018-07-16T11:01:05.087
31
JULY
2018-07-18T11:01:05.087
2018-07-14T11:01:05.087