目录
1.Lambda表达式
为什么使用lambda:
使用Lambda表达式注意事项
函数式接口
方法引用(了解)
Stream API
新时间日期API
Lambda表达式可以看成是匿名内部类,Lambda允许把函数作为一个方法的参数(函数作为方法参数传递),将代码像数据一样传递,使用Lambda表达式可以使代码变的更加的间接紧凑。
匿名内部类简化成Lambda表达式:
基本语法:
<函数式接口> <变量名> = (参数1,参数2...) -> {
//方法体
};
案例1:
public class Demo1 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程执行了");
}
};
Runnable runnable1 = ()-> System.out.println("线程1执行了");
}
}
如:集合中获取年龄大于20的员工信息,获取工资大于20000的员工信息,获取每个需求的员工信息都需要创建新方法,每个方法返回不同需求的员工集合,造成代码重复性大。如果在添加类似需求,需要再添加方法,这时我们可以使用策略模式进行代码简化,更好的是代码可以重用。
1.使用策略模式优化代码
策略模式代码及解释如下:
package day0818.demo1;
import java.util.ArrayList;
import java.util.List;
public class Demo2 {
public static void main(String[] args) {
ArrayList employees=new ArrayList<>();
employees.add(new Employee("少泊", 26, 32000));
employees.add(new Employee("发海", 28, 30000));
employees.add(new Employee("真真", 18, 28000));
employees.add(new Employee("本伟", 27, 25000));
employees.add(new Employee("坤坤", 21, 18000));
//未使用策略模式每个需求都要调用不同的方法,如下
List employees1 = filterByAge(employees);
List employees2 = filterBySalary(employees);
//使用策略模式相同需求只需调用同一种方法,如下:
filter(employees, new MyInter() {
@Override
public boolean test(Employee employee) {
//若此条件为employee.getSalary()>20000,筛选出工资大于20000的员工
if(employee.getAge()>20){//筛选出符合条件的员工
return true;
}
return false;
}
});
}
//以下两个方法代码重复较多
public static List filterByAge(ArrayList empList){
List list = new ArrayList<>();
for (Employee employee : empList) {
if (employee.getAge()>=25){
list.add(employee);
}
}
return list;
}
public static List filterBySalary(ArrayList empList){
List list = new ArrayList<>();
for (Employee employee : empList) {
if (employee.getSalary()>=20000){
list.add(employee);
}
}
return list;
}
//改为策略模式,需要写功能接口,并将此接口作为方法的参数
public static List filter(ArrayList empList,MyInter myInter){
List list = new ArrayList<>();
for (Employee employee : empList) {
if (myInter.test(employee)){//调用此接口的test方法,判断是否满足条件
list.add(employee);
}
}
return list;
}
}
MyInter接口:
package day0818.demo1;
public interface MyInter {
boolean test(T t);
}
2.使用Lambda表达式优化策略模式
将此处代码优化成Lambda表达式:
Lambda引入了新的操作符:->(箭头操作符),->将表达式分成两部分
左侧:(参数1,参数2…)表示参数列表;
右侧:{}内部是方法体
1、形参列表的数据类型会自动推断;
2、如果形参列表为空,只需保留();
3、如果形参只有1个,()可以省略,只需要参数的名称即可;
4、如果执行语句只有1句,且无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句也保证只有1句;
5、lambda不会生成一个单独的内部类文件;
6、lambda表达式若访问了局部变量,则局部变量必须是final的,若是局部变量没有加final关键字,系统会自动添加,此后在修改该局部变量,会报错。
如果一个接口只有一个抽象方法,则该接口称之为函数式接口,函数式接口可以使用Lambda表达式,lambda表达式会被匹配到这个抽象方法上。
Java四个核心函数式接口:
函数式接口 | 参数类型 | 返回类型 | 说明 |
---|---|---|---|
Consumer |
T | void | void accept(T t);对类型为T的对象应用操作 |
Supplier |
无 | T | T get(); 返回类型为T的对象 |
Function |
T | R | R apply(T t);对类型为T的对象应用操作,并返回类型为R类型的对象。 |
Predicate |
T | boolean | boolean test(T t);确定类型为T的对象是否满足条件,并返回boolean类型。 |
案例:
public class Demo3 {
public static void main(String[] args) {
//Consumer接口,有参无返回值
consumer(1000,money-> System.out.println("本次蹦迪消费:"+money));
consumer(2000,money-> System.out.println("本次聚餐消费:"+money));
//Supplier接口,有参数有返回值
int[] ints = supplier(10, () -> new Random().nextInt(100));
System.out.println(Arrays.toString(ints));
//Function韩属型接口
String s1 = function("hello", s -> s.substring(1, 4));
System.out.println(s1);
//Predicate 断言型接口
List strList = new ArrayList<>();
strList.add("hello");
strList.add("goodbye");
strList.add("verygood");
strList.add("hi");
List list = predicate(strList, s -> s.length() >= 3);
System.out.println(list.toString());
}
public static void consumer(double money, Consumer consumer){
consumer.accept(money);
}
public static int[] supplier(int len, Supplier supplier){
int[] arr = new int[len];
for (int i=0;i function){
String str1 = function.apply(str);
return str1;
}
public static List predicate(List stringList, Predicate predicate){
List list = new ArrayList<>();
for (String s : stringList) {
if(predicate.test(s)){
list.add(s);
}
}
return list;
}
}
法引用是lambda表达式的一种简写形式。 如果lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。
有四种形式:
对象::实例方法
类::静态方法
类::实例方法
类::new
对象::实例方法
public class Demo1 {
public static void main(String[] args) {
//对象::实例方法
//条件1 如果lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。
//条件2 方法的参数个数类型和返回值类型和接口中的方法一致
Consumer consumer = s-> System.out.println(s);
Consumer consumer1 = System.out::println;
consumer1.accept("hello");
// 类::静态方法
//条件1 如果lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。
//条件2 方法的参数个数类型和返回值类型和接口中的方法一致
Comparator comparator = (o1,o2)->Integer.compare(o1,o2);
Comparator comparator1 = Integer::compareTo;
comparator(10,10,comparator1);
//类::实例方法(了解)
//条件1 如果lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。
//条件2 接口中两个参数,有一个作为方法的调用者,另一个作为参数,返回类型一致
BiPredicate p = (x,y)->x.equals(y);
BiPredicate p1 = String::equals;
boolean b = biPredicate("hello", "world", p1);
System.out.println(b);
//条件1 如果lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。
//条件2 接口中一个参数,参数作为方法的调用者,返回类型一致
Function function = e->e.getName();
Function function1 = Employee::getName;
function(new Employee("张三",25,20000,"男"),function1);
//类::new
//条件1 如果lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。
Supplier supplier = ()->new Employee();
Supplier supplier1 = Employee::new;
supplier(supplier1);
//思考:方法引用只能调用无参的构造方法吗?如何调用有参构造?
//可以调用有参构造,调用时必须重写Supplier接口中的get方法,把属性当成get方法参数
MySupplier mySupplier = (name,age,salary,gender)->new Employee(name,age,salary,gender);
MySupplier mySupplier1 = Employee::new;
String name = "LBJ";
int age = 35;
double salary = 25000;
String gender = "男";
mySupplier(name,age,salary,gender,mySupplier1);
}
public static boolean biPredicate(String str1,String str2,BiPredicate biPredicate){
return biPredicate.test(str1,str2);
}
public static void comparator(int n1,int n2,Comparator comparator){
int compare = comparator.compare(n1, n2);
if(compare>0){
System.out.println(n1+"大");
}else if(compare==0){
System.out.println("两个数相等");
}else{
System.out.println(n2+"大");
}
}
public static void function(Employee employee,Function function){
if(employee!=null){
System.out.println(employee.getName().toString());
}
}
//打印无参对象
public static void supplier(Supplier supplier){
Employee employee = supplier.get();
if(employee!=null){
System.out.println(employee);
}
}
public static void mySupplier(String name,int age,double salary,String gender,MySupplier mySupplier){
Employee employee = mySupplier.get(name, age, salary, gender);
if(employee!=null){
System.out.println(employee.toString());
}
}
}
什么是Stream:
一个Stream表面上与一个集合很类似,集合中保存的是数据,而流中对数据的操作。
特点:
Stream 自己不会存储元素。
Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream遵循“做什么,而不是怎么去做”的原则。只需要描述需要做什么,而不用考虑程序是怎样实现的。
简单应用
如果想计算集合中字符串长度大于3的元素个数,传统方法:遍历,逐一判断是否满足条件
而用Stream API:将集合转成流进行操作,如:list.stream().filter(s->s.length()>3).count();就能很方便的计算出长度大于3的字符串个数。
public class Demo1 {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("hello");list.add("world");list.add("hi");list.add("we");list.add("seeyou");
//传统做法,遍历
int count=0;
for (String s : list) {
if(s.length()>3){
count++;
}
}
System.out.println(count);
//Stream API
long count1 = list.stream()
.filter(s -> s.length() > 3)
.count();
System.out.println(count1);
}
}
使用Stream会有三个阶段:
创建一个Stream。 (创建)
在一个或多个步骤中,将初始Stream转化到另一个Stream的中间操作。 (中间操作)
使用一个终止操作来产生一个结果。该操作会强制他之前的延迟操作立即执行。在这之后,该Stream就不会在被使用了。(终止操作)
Stream的创建方法:以下4种
// 1. Stream.of方法
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays.of方法
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. 集合方法
List list = Arrays.asList(strArray);
stream = list.stream();
stream = list.parallelStream();//并行流
//4.创建无限流
//迭代
Stream stream = Stream.iterate(0, (x)->x+2);
stream.limit(10)
.forEach(System.out::println);
//生成
Stream stream2 = Stream.generate(()->Math.random());
stream2.limit(5)
.forEach(System.out::println);
以上四种方法中重点掌握集合方法创建流
Stream中间操作:
中间操作包括:map(映射) (mapToInt, flatMap 等)、 filter(过滤)、distinct(去重)、sorted、peek()、limit、skip、parallel、sequential、unordered。
//filter---从流中排除元素
// limit——截断流,使其元素不超过给定数量。
// skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
// distinct——筛选,通过流所生成元素的 equals() 去除重复元素
// map——接收 Lambda ,
// 将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
//sorted()——自然排序
//sorted(Comparator com)--------定制排序
// unsorted() 把比较的约束去掉,并不能真正打乱集合中元素的顺序,只是保证下次插入元素时不按之前规定的条件
参考代码:
public class Demo3 {
public static void main(String[] args) {
List employees=new ArrayList<>();
employees.add(new Employee("xxx", 30, 10000));
employees.add(new Employee("yyy", 29, 8000));
employees.add(new Employee("zzz", 22, 12000));
employees.add(new Employee("张三", 21, 20000));
employees.add(new Employee("李四", 32, 22000));
employees.add(new Employee("李四", 32, 24000));
//employees.add(new Employee("李四", 32, 22000));
//1 筛选和切片
// filter---从流中排除元素
// limit——截断流,使其元素不超过给定数量。
// skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
// distinct——筛选,通过流所生成元素的 equals() 去除重复元素
System.out.println("------filter-----");
employees.stream()
.filter(e->e.getAge()>25)
.forEach(System.out::println);
System.out.println("------limit-----");
employees.stream()
.limit(2)
.forEach(System.out::println);
System.out.println("-------skip------");
employees.stream()
.skip(2)
.limit(2)
.forEach(System.out::println);
System.out.println("-----distinct------");
employees.stream()
.distinct()
.forEach(System.out::println);
System.out.println("-------------map---------------");
employees.stream()
.map(e->e.getName())
.forEach(System.out::println);
List strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
strList.stream()
.map(s->s.toUpperCase())
.forEach(System.out::println);
System.out.println("-------排序 ---------");
employees.stream().sorted((o1,o2)->{
int n1=o1.getAge()-o2.getAge();
int n2=Double.compare(o1.getSalary(), o2.getSalary());
return n1==0?n2:n1;
}).unordered()
.forEach(System.out::println);
System.out.println("----------");
//unordered:把比较的约束给去掉
employees.stream().unordered().forEach(System.out::println);
}
}
Stream 的终止操作
终止操作包括:forEach(遍历)、forEachOrdered、toArray、reduce(规约)、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。
参考代码:
public class Demo4 {
public static void main(String[] args) {
List employees=new ArrayList<>();
employees.add(new Employee("xxx", 30, 10000,"男"));
employees.add(new Employee("yyy", 29, 8000,"男"));
employees.add(new Employee("zzz", 22, 12000,"男"));
employees.add(new Employee("张三", 21, 20000,"男"));
employees.add(new Employee("李四", 32, 22000,"妖"));
// allMatch——检查是否匹配所有元素
// anyMatch——检查是否至少匹配一个元素
// noneMatch——检查是否没有匹配的元素
// findFirst——返回第一个元素
// findAny——返回当前流中的任意元素
// count——返回流中元素的总个数
// max——返回流中最大值
// min——返回流中最小值
boolean b=employees.stream()
.allMatch(e->e.getGender().equals("男"));
System.out.println("结果:"+b);
boolean b2= employees.stream().anyMatch(e -> e.getGender().equals("妖"));
System.out.println(b2);
boolean b3= employees.stream().noneMatch(e -> e.getGender().equals("女"));
System.out.println(b3);
Optional first = employees.stream().findFirst();
Employee employee = first.get();
System.out.println(employee.toString());
System.out.println("-------findAny() 当数据比较少的使用单线程一般都是第一个,并行流返回执行最快的线程的数据-------");
Optional any = employees.parallelStream().findAny();
System.out.println(any.get().toString());
System.out.println("------max--------");
Optional max = employees.stream().max((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary()));
System.out.println(max.get().toString());
System.out.println("--------reduce-----------------");
List list= Arrays.asList(10,20,30,40);
Integer sum = list.stream().reduce(10, (x, y) -> x + y);
System.out.println(sum);
System.out.println("计算所有员工的工资");
Double salay = employees.stream()
.map(Employee::getSalary)
.reduce(0.0, Double::sum);
System.out.println(salay);
System.out.println("--------获取所有的员工姓名的集合--------");
List collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println(collect);
}
}
并行操作:
Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
list.stream()
list.parallelStream()
当集合中元素非常庞大的时候parallelStream()的效率比stream()的效率高很多
Local(本地) − 简化了日期时间的处理,没有时区的问题。
ZoneId (时区) − 通过定制的时区处理日期时间。
SimpleDateFormat是线程非安全的!!
java.util.Date 是非线程安全的,所有的日期类都是可变的(每次修改后data就会发生改变),这是Java日期类最大的问题之一。
本地化日期时间API
LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。
LocalDate只表示日期,LocalTime只表示时间,而LocalDateTime表示日期和时间(比较常用)
基本用法:
LocalDateTime date1 = LocalDateTime.now();//创建当前时间
LocalDateTime date2 = LocalDateTime.of(year,month,day,hour,minute,second);//创建指定的时间
LocalDateTime newDate = date1.plusDay(int n)/plusMonth(int n)....//表示在date1的基础上加上指定数值,返回一个新日期,而不改变date1的值
LocalDateTime newDate = date1.minusDay(int n)/minusMonth(int n)//同上
日期和字符串之间的转换:DateTimeFormatter
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("YYYY-MM-DD HH:mm-SS");
LocalDateTime date = LocalDateTime.now();
String str = date.format(dtf);
Instant、ZoneId、Date----Instance----LocalDateTime(重点,看代码中时间转换)
Instant 时间戳 类似以前的Date、Timestamp,它是以Unix元年(传统 的设定为UTC时区1970年1月1日午夜时分)开始 所经历的描述进行运算
ZoneId 时区
public static void main(String[] args) throws Exception {
//时间戳
Instant instant=Instant.now();
System.out.println(instant);
Thread.sleep(1000);
Instant instant2=Instant.now();
long millis = Duration.between(instant, instant2).toMillis();
System.out.println(millis);
//时区
System.out.println(ZoneId.getAvailableZoneIds());
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
//时间转换
//Date-----Instant-------LocalDateTime
System.out.println("--------Date-----Instant-------LocalDateTime---------");
Date date=new Date();
//把date转成instant
Instant instant1 = date.toInstant();
//intant转成LocalDateTime
LocalDateTime localDateTime = instant1.atZone(ZoneId.systemDefault()).toLocalDateTime();
System.out.println(localDateTime);
System.out.println("--------LocalDateTime-----Instant-------Date---------");
Instant instant3 = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Date date2 = Date.from(instant3);
System.out.println(date2);
}