jdk1.8特性
函数式接口
函数接口是只有一个抽象方法的接口,用作lambda表达式返回类型,该类型的接口都有@FunctionalInterface
这个注解。虽然该类型接口只有一个抽象方法,但可以有多个默认实现方法和静态方法
默认方法
为了以兼容方式改进API,Java 8中加入了默认方法。主要是为了支持库设计师,让他们能够写出更容易改进的接口。具体写法是在接口中加default关键字修饰,一般正常开发中不会使用到。
由于类可以实现多个接口,也可以继承类,当接口和类中有相同的方法名时会使用那个类或者接口的实现。
(1) 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
(2) 如果无法依据第一条进行判断,那么子接口的优先级更高:方法名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
(3) 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法, 显式地选择使用哪一个默认方法的实现。不然编译报错。
自定义函数接口
@FunctionalInterface
public interface TestFunction {
//@FunctionalInterface 表示该接口为函数接口
/** 函数接口特点
* 只能有一个接口
* 可以有多个默认实现方法和静态方法
*/
String getName(String name);
/** 该方法来自顶层object的equals
* @param obj
* @return
*/
boolean equals(Object obj);
default String then() {
return "中国";
}
default String then2() {
return "my world";
}
static String getHost() {
return "localhost";
}
}
lambda表达式
lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码
语法
完整的Lambda表达式由三部分组成:参数列表、箭头、声明语句;
(Type1 param1, Type2 param2, ..., TypeN paramN) ‐> { statment1; statment2; //............. return statmentM;}
简化:
1.绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类 型,所以参数可以省略;
2.当lambda表达式的参数个数只有一个,可以省略小括号:
param1 ‐> { statment1; statment2; //............. return statmentM;}
- 当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号:
param1 ‐> statment
案例
//内部类创建多线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类当前线程"+Thread.currentThread().getName());
}
});
thread.start();
//lambda创建多线层
Thread tlam = new Thread(() -> {
System.out.println("lambda当前线程"+Thread.currentThread().getName());
});
tlam.start();
//lambda简化
/*
* 如果 当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的 分号
*/
Thread tt = new Thread(() -> System.out.println("lambda简化当前线程"+Thread.currentThread().getName()));
tt.start();
demo2:在java.util.function
包下有着许多函数式接口例如Consumer
package lambda;
import java.util.function.Consumer;
public class Test5 {
private static void consumer(String something,Consumer<String> con) {con.accept(something);}
private static void doubleConsumer(String something,Consumer<String> con1,Consumer<String> con2) {con1.andThen(con2).accept(something);}
public static void main(String[] args) {
//完整的lambda表达式由三部分组成:参数列表、箭头、声明语句
consumer("中国", (String s)->{
System.out.println("我在"+s);
});
//简化1 参数类型可以省略,如果参数只有一个那么小括号也能省略
consumer("中国", s->{
System.out.println("我在"+s);
});
//简化2 当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的 分号
consumer("中国", s->System.out.println("我在"+s));
//简化3 方法引用 如果一个Lambda代表的只是“直接调用这个方法”,那 最好还是用名称来调用它,而不是去描述如何调用它 所以方法引用只是在内容中只有一个表达式的简写
consumer("中国", System.out::println);
doubleConsumer("中国",
s->System.out.println("我再"+s),
s->System.out.println(s+"NB")
);
}
}
局部变量限制
Lambda表达式也允许使用自由变量(不是参数,而是在外层作用域中定义的变量),就像匿名类一样。 它们被称作捕获Lambda。 Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final。
为什么局部变量有这些限制?
实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用 的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变 量。因此, Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变 量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。
demo3:
package lambda;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/** lambda局部变量的限制
* @author Admin
*
*/
public class Test3 {
// Lambda可以没有限制地捕获(也就是在其主体 中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final
public Map<String, Object> map = new HashMap<>();
public static Map<String, Object> mapst = new HashMap<>();
public static void main(String[] args) {
Map<String, Object> mm = new HashMap<>();
mm.put("111", 111);
mapst.put("111", 111);
Integer a = 22;
new Thread(() -> {
//没有限制的使用实例变量和静态变量
mapst.put("aa", 11);
mapst = new HashMap<>();
new Test3().map.put("bb", 22);
//局部变量的限制
// a =33; //报错 Integer a 是final 不能修改a的值
//map可以修改参数,不能修改引用地址
mm.put("mm", 222);
// mm = new HashMap<>(); //报错
}).start();
}
}
方法引用
方法引用的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称, 你的代码的可读性会更好。所以方法引用只是在内容中只有一个表达式的简写。
demo
package method;
import java.util.Arrays;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/** 方法引用案例
* @author Admin
*
*/
public class Test1 {
/** 方法引用注意事项
* 1: lambda实现体中调用方法的参数列表与返回值类型,与函数式接口中接口的参数列表和返回值类型相同
* 2: 若lambda参数列表中的第一个参数是实例方法的调用者,第二个参数是实例方法的参数时
* 满足两个条件中任意一个 可以使用
*/
public static void main(String[] args) {
//lambda
Consumer<Integer> con = s->{System.out.println(s);};
con.accept(100);
//方法引用简化 --实例方法
Consumer<Integer> con2 = System.out::println;
con2.accept(10000);
//静态方法 满足第一个条件
BiFunction<Integer, Integer, Integer> biFun = (x,y)->{ return Integer.compare(x, y);};
BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare;
Integer apply = biFun2.apply(100, 20);
System.out.println(apply);
//实例方法 满足第二个条件
BiFunction<String, String, Boolean> biFun3 = (x,y)->x.equals(y);
BiFunction<String, String, Boolean> biFun4 = String::equals;
Boolean bo = biFun4.apply("aa", "bb");
System.out.println(bo);
//构造方法引用
Supplier<String> supplier = ()-> new String();
Supplier<String> supplier2 =String::new;
//构造方法引用--带一个参数
Function<String, Integer> fun = x-> new Integer(x);
Function<String, Integer> fun2 =Integer::new;
Integer aa = fun2.apply("10");
System.out.println(aa);
//数组引用
Function<Integer, String[]> fun3 = a->new String[a];
Function<Integer, String[]> fun4 = b->new String[b];
String[] ss = fun4.apply(10);
System.out.println(Arrays.toString(ss));
}
}
StreamAPI
介绍
流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来 表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集 的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!
注意
每个流只能使用一次,如果重复使用编译不会报错,但运行会报错
stream has already been operated upon or closed
常用StreamAPI案例
package stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test1 {
private static boolean compares30(Integer x) {
if (x>=30) {
return true;
}
return false;
}
private static boolean compares70(Integer x) {
if (x<=70) {
return true;
}
return false;
}
//测试过滤
private static void testFilter() {
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(30);
list.add(55);
list.add(96);
list.add(24);
list.add(27);
list.add(63);
list.add(52);
list.add(77);
list.add(22);
list.add(2);
list.add(18);
Stream<Integer> stream = list.stream();
// stream.filter(a->compares30(a)).filter(a->compares70(a)).forEach(System.out::println);
stream.filter(Test1::compares30).filter(Test1::compares70).forEach(System.out::println);
}
//获取流
private static void testGetStream() {
List<Object> list = new ArrayList<>();
Stream<Object> listStream = list.stream();
Set<Object> set = new HashSet<>();
Stream<Object> setStream = set.stream();
Map<String, Object> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<Object> valueStream = map.values().stream();
String[] aff = {"你好","世界","中国"};
Stream<String> stream = Stream.of(aff);
}
//类型转换
private static void streamMap() {
Stream<String> stream = Stream.of("11","22","33","44","55");
stream.map(Integer::valueOf).forEach(System.out::println);
}
//数据截取limit
private static void limit() {
Stream<String> stream = Stream.of("11","22","33","44","55");
stream.limit(2).forEach(System.out::println);
}
//跳过操作
private static void skip() {
Stream<String> stream = Stream.of("11","22","33","44","55");
stream.skip(2).forEach(System.out::println);
}
//连接两个流concat
private static void concat() {
Stream<String> stream1 = Stream.of("11","22");
Stream<String> stream2 = Stream.of("33","44","55");
Stream.concat(stream1, stream2).forEach(System.out::println);
}
//规约操作
private static void reduce() {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Stream<Integer> stream = list.stream();
Integer integer = stream.reduce(0,(x,y)->x+y);
System.out.println(integer);
}
//去重
private static void distinct() {
List<Integer> list = Arrays.asList(1,2,2,4,4,6,8,8,9);
list.stream().distinct().forEach(System.out::println);
}
//list 转 map
private static void listToMap() {
List<User> userList = new ArrayList<>();
userList.add(new User(1L,"张三"));
userList.add(new User(2L,"李四"));
userList.add(new User(3L,"张飞"));
userList.add(new User(4L,"关羽"));
Map<Long, User> map = userList.stream().collect(Collectors.toMap(User::getId,Function.identity()));
map.forEach((x,y)->System.out.println(x+":"+y));
}
//获取 list中实体类的id作为集合
private static void mapToList() {
List<User> userList = new ArrayList<>();
userList.add(new User(1L,"张三"));
userList.add(new User(2L,"李四"));
userList.add(new User(3L,"张飞"));
userList.add(new User(4L,"关羽"));
List<Long> list = userList.stream().map(User::getId).collect(Collectors.toList());
list.forEach(System.out::println);
}
private static Predicate<User> predicate(User user) {
return t -> {
if (t.getId().equals(user.getId()) && t.getName().equals(user.getName())) {
return true;
}
return false;
};
}
//匹配
private static void anyMatch() {
List<User> userList = new ArrayList<>();
userList.add(new User(1L,"张三"));
userList.add(new User(2L,"李四"));
userList.add(new User(3L,"张飞"));
userList.add(new User(4L,"关羽"));
User user = new User(1L,"张三");
boolean anyMatch = userList.stream().anyMatch(predicate(user));
System.out.println(anyMatch);
}
public static void main(String[] args) {
testFilter();
}
}
时间日期API
1 * 之前使用的java.util.Date月份从0开始,我们一般会+1使用,很不方便, java.time.LocalDate月份和星期都改成了enum
2 * java.util.Date和SimpleDateFormat都不是线程安全的,而LocalDate和LocalTime 和最基本的String一样,是不变类型,不但线程安全,而且不能修改。
3 * java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,更加明确需求取 舍
4 * 新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情 况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。
1.8之前JDK自带的日期处理类非常不方便,不过1.8出现之后这个改观了很多,比如日期时间的创建、比 较、调整、格式化、时间间隔等。 这些类都在java.time包下。比原来实用了很多。
介绍几个常用类:
LocalDate/LocalTime/LocalDateTime
LocalDate为日期处理类、LocalTime为时间处理类、LocalDateTime为日期时间处理类, 方法都类似, now相关的方法可以获取当前日期或时间,of方法可以创建对应的日期或时间,parse方法 可以解析日期或时间,get方法可以获取日期或时间信息,with方法可以设置日期或时间信 息,plus或minus方法可以增减日期或时间信息;
TemporalAdjusters
这个类在日期调整时非常有用,比如得到当月的第一天、最后一天,当年的第一天、最后一 天,下一周或前一周的某天等。
**DateTimeFormatter **以前日期格式化一般用SimpleDateFormat类,但是不怎么好用,现在1.8引入了 DateTimeFormatter类,默认定义了很多常量格式(ISO打头的),在使用的时候一般配合 LocalDate/LocalTime/LocalDateTime使用。
API案例
package localdate;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Date;
public class Test1 {
public static void main(String[] args) {
//获取当前日期
LocalDate toDate = LocalDate.now();
//根据年月日生成时间
LocalDate of = LocalDate.of(2021, 8, 27);
//根据字符串获取日期
LocalDate date = LocalDate.parse("2021-08-27");
//===============日期计算==========
//获取本月第一天
LocalDate day1 = toDate.with(TemporalAdjusters.firstDayOfMonth());
//获取今年第一天
LocalDate day2 = toDate.with(TemporalAdjusters.firstDayOfYear());
//获取明年第一天
LocalDate day3 = toDate.with(TemporalAdjusters.firstDayOfNextYear());
//下个月第一天
LocalDate day4 = toDate.with(TemporalAdjusters.firstDayOfNextMonth());
//获取本月
int monthValue = toDate.getMonthValue();
//一年中的第几天
int dayOfYear = toDate.getDayOfYear();
LocalDate localDate1 = LocalDate.parse("2020-08-28");
LocalDate LocalDate2 = LocalDate.parse("2021-08-28");
//两个日期年份相差
int years = localDate1.until(LocalDate2).getYears();
//两个日期月份相差(不计算年份)
int months = localDate1.until(LocalDate2).getMonths();
//两个日期天数相差(不计算年份和月份)
int days = localDate1.until(LocalDate2).getDays();
//计算两个日期天数相差
long dayLong = localDate1.until(LocalDate2, ChronoUnit.DAYS);
//两个日期月份相差
long monthLong = localDate1.until(LocalDate2, ChronoUnit.MONTHS);
//============时间====================
//带有纳秒
LocalTime now = LocalTime.now();
//不带纳秒
LocalTime now2 = LocalTime.now().withNano(0);
//根据传入的时分秒获取时间
LocalTime now3 = LocalTime.now().withHour(21).withMinute(30).withSecond(30).withNano(0);
//获取毫秒时间戳
long epochMilli = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
long currentTimeMillis = System.currentTimeMillis();
long time = new Date().getTime();
//获取秒时间戳
long epochSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
//时间戳转指定格式日期
DateTimeFormatter df = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
String format = df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(1630057193027L), ZoneId.of("Asia/Shanghai")));
//解析字符串to时间
DateTimeFormatter df2 = DateTimeFormatter.ofPattern("YYYY年MM月dd日 HH:mm:ss");
String format2 = df2.format(LocalDateTime.now());
LocalDateTime parse = LocalDateTime.parse("2021年08月27日 18:04:48", df2);
System.out.println(format2);
System.out.println(parse);
//加上或减去指定天数
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//五天后
LocalDateTime plusDays = LocalDateTime.now().plusDays(5);
//五天前
LocalDateTime minusDays= LocalDateTime.now().minusDays(5);
}
}