1、jdk8新特性default关键字
在jdk1.8以前接口里面是只能有抽象方法,不能有任何方法的实现的
jdk1.8里面打破了这个规定,引入了新的关键字default,使用default修饰方法,可以在接口里面定义具体的方法实现
默认方法: 接口里面定义一个默认方法,这个接口的实现类实现了这个接口之后,不用管这个default修饰的方法就可以直接调用,即接接口方法的默认实现
public interface Animal {
void run();
void eat();
default void breath(){
System.out.println("使用氧气呼吸");
}
public interface Animal {
void run();
void eat();
default void breath(){
System.out.println("使用氧气呼吸");
}
static void test(){
System.out.println("这是静态方法");
}
}
2、JDK8之新增base64加解密API
什么是Base64编码 Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示⼆进制数据的方法 基于64个字符A-Z,a-z,0-9,+,/的编码方式,是一种能将任意二进制数据用64种字元组合成字符串的方法,而这个二进制数据和字符串资料之间是可以互相转换的,在实际应用上,Base64除了能将⼆进制数据可视化之外,也常用来表示字串加密过后的内容
推荐文章:https://blog.youkuaiyun.com/wo541075754/article/details/81734770
早期java要使用Base64怎么做
//使用JDK里sun.misc套件下的BASE64Encoder和BASE64Decoder这两个类
BASE64Encoder encoder = new BASE64Encoder();
BASE64Decoder decoder = new BASE64Decoder();
String text = "hello world!";
byte[] textByte = text.getBytes("UTF-8");
//编码
String encodedText = encoder.encode(textByte);
System.out.println(encodedText);
//解码
System.out.println(new String(decoder.decodeBuffer(encodedText), "UTF-8"));
缺点:编码和解码的效率比较差,公开信息说以后的版本会取消这个方法
Apache Commons Codec有提供Base64的编码与解码 缺点:是需要引用ApacheCommons Codec
jdk1.8之后怎么玩?(首选选推荐)
dk1.8的java.util包中,新增了Base64的类好处:不用引包,编解码销量远大于 sun.misc 和 Apache Commons Codec
Base64.Encoder encoder = Base64.getEncoder();
Base64.Decoder decoder = Base64.getDecoder();
String text = "hello world!";
byte[] textByte = text.getBytes("UTF-8");
//编码
String encodedText = encoder.encodeToString(textByte);
System.out.println(encodedText);
//解码
System.out.println(new String(decoder.decode(encodedText), "UTF8"));
3、JDK8之时间日期处理类(上)
时间处理再熟悉不过,SimpleDateFormat,Calendar等类 旧版缺点: java.util.Date 是非线程安全的 API设计比较较差,日期/时间对象比较,加减麻烦
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理新增了很多常用的api,如日期/时间的比较,加减,格式化等
包所在位置 java.time
核心类:
LocalDate:不包含具体时间的日期。
LocalTime:不含日期的时间。
LocalDateTime:包含了日期及时间。
LocalDate 常用API:
LocalDate today = LocalDate.now();
System.out.println("今天日期:" + today);
//获取年 月 日 周几
System.out.println("现在是哪年:"+today.getYear());
System.out.println("现在是哪月:"+today.getMonth());
System.out.println("现在是哪月(数字):"+today.getMonthValue())
System.out.println("现在是几号:"+today.getDayOfMonth());
System.out.println("现在是周几:"+today.getDayOfWeek());
//加减年份, 加后返回的对象才是修改后的, 旧的依旧是旧的
LocalDate changeDate = today.plusYears(1);
System.out.println("加后是哪年:"+changeDate.getYear());
System.out.println("旧的是哪年:"+today.getYear());
//日期比较
System.out.println("isAfter: "+changeDate.isAfter(today));
//getYear() int 获取当前日期期的年份
//getMonth() Month 获取当前日期的月份对象
//getMonthValue() int 获取当前日期是第几月
//getDayOfWeek() DayOfWeek 表示该对象表示的日期是星期几
//getDayOfMonth() int 表示该对象表示的日期是这个月第几天
//getDayOfYear() int 表示该对象表示的日期是今年第几天
//withYear(int year) LocalDate 修改当前对象的年份
//withMonth(int month) LocalDate 修改当前对象的月份
//withDayOfMonth(int dayOfMonth) LocalDate 修改当前对象在当月的日期
//plusYears(long yearsToAdd) LocalDate 当前对象增加指定的年份数
//plusMonths(long monthsToAdd) LocalDate 当前对象增加指定的月份数
//plusWeeks(long weeksToAdd) LocalDate 当前对象增加指定的周数
//plusDays(long daysToAdd) LocalDate 当前对象增加指定的天数
//minusYears(long yearsToSubtract) LocalDate 当前对象减去指定的年数
//minusMonths(long monthsToSubtract) LocalDate 当前对象减去指定的月数
//minusWeeks(long weeksToSubtract) LocalDate 当前对象减去指定的周数
//minusDays(long daysToSubtract) LocalDate 当前对象减去指定的天数
//compareTo(ChronoLocalDate other) int 比较当前对象和other对象在时间上的大下,返回值如果为正,则当前对象时间较晚,
//isBefore(ChronoLocalDate other) boolean 比较当前对象日期是否在other对象日期之前
//isAfter(ChronoLocalDate other) boolean ⽐较当前对象日期是否在other对象日期之后
//isEqual(ChronoLocalDate other) boolean 比较两个日期对象是否相等
LocalTime 常用API 和 LocalDateTime 常用API 大同小异
4、JDK8之时间日期处理类(下)
日期时间格式化
JDK8之前:SimpleDateFormat来进行格式化,但SimpleDateFormat并不是线程安全的
JDK8之后:引入线程安全的日期与时间DateTimeFormatter
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String ldtStr = dtf.format(ldt);
System.out.println(ldtStr);
获取指定的日期时间对象
LocalDateTime ldt = LocalDateTime.of(2020, 11, 11, 8, 20, 30);
System.out.println(ldt);
计算日期时间差 java.time.Duration
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
LocalDateTime changeDate = LocalDateTime.of(2020,10,1,10,40,30);
System.out.println(changeDate);
//第二个参数减第一个参数
Duration duration = Duration.between( today,changeDate);
System.out.println(duration.toDays());//两个时间差的天数
System.out.println(duration.toHours());//两个时间差的小时数
System.out.println(duration.toMinutes());//两个时间差的分钟数
System.out.println(duration.toMillis());//两个时间差的毫秒数
System.out.println(duration.toNanos());//两个时间差的纳秒数
5、Java8之Optional类
Optional 类有啥用
主要解决的问题是空指针异常(NullPointerException)
怎么解决?
本质是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空
创建Optional类
of()
null 值作为参数传递进去,则会抛异常
Optional opt = Optional.of(user);
ofNullable()
如果对象即可能是 null 也可能是非null,应该使用ofNullable() 方法
Optional opt = Optional.ofNullable(user);
访问 Optional 对象的值
get() 方法
Optional opt = Optional.ofNullable(student);
Student s = opt.get();
如果值存在则isPresent()方法法会返回true,调用get()方法会返回该对象一般使用get之前需要先验证是否有值,不然还会报错
6、Java高级核心玩转JDK8 Lambda表达式
在JDK8之前,Java是不支持函数式编程的,所谓的函数编程,即可理解是将各个函数(也称为“行为”)作为一个参数进行传递, 面向对象编程是对数据的抽象(各种各样的POJO类),而函数式编程则是对行为的抽象(将行为作为一个参数进行传递)
lambda表达式 使用场景(前提):
一个接口中只包含一个方法,在java里面接口类名上有@FunctionalInterface,则可以使用Lambda表达式,这样的接口称之为“函数接口” 语法: (params) -> expression
@FunctionalInterface //添加此注解后,接口中只能有一个抽象方法。
public interface A {
void call();
}
第一部分为括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;
第二部分为一个箭头符号:-> ;
第三部分为方法体,可以是表达式和代码块
参数列表 : 括号中参数列表的数据类型可以省略不写 括号中的参数只有一个,那么参数类型和()都可以省略不写
方法体: 如果{}中的代码只有一行,无论有无返回值,可以省略{}、return、分号,要一起省略,其他则需要加上
Lambda 表达式的实现方式在本质是以匿名内部类的方式进行实现
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
7、Java新特性玩转JDK8之函数式编程
Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现.
Lambda表达式必须先定义接口,创建相关方法之后才可使用,这样做十分不便,其实java8已经内置了许多接口,所以一般般很少会由用户去定义新的函数式接口
Java8的最大特性就是函数式接口,所有标注了@FunctionalInterface注解的接口都是函数式接口,例如下面四个基本功能型接口,
Java8 内置的四大核心函数式接口 T:入参类型,R:出参类型
Consumer<T> : 消费型接口:有入参,无返回值
void accept(T t);
Supplier<T> : 供给型接口:无入参,有返回值
T get();
Function<T, R> : 函数型接口:有入参,有返回值
R apply(T t);
Predicate<T> : 断言型接口:有入参,有返回值,返回值类型确定是boolean
boolean test(T t);
Function的应用
传入一个值经过函数的计算返回另一个值
// 输出入参的10倍
Function<Integer, Integer> func = p -> p * 10;
BiFunction 是继承Function
#Function只能接收一个参数,如果要传递两个参数,则用 BiFunction
@FunctionalInterfacepublic interface BiFunction<T, U, R> {
R apply(T t, U u);
}
Consumer的应用
//典型应用,集合的foreach
List<String> list = Arrays.asList("aaa","bbb");
list.forEach(obj->{
//TODO
});
Supplier的应用
#泛型一定和方法的返回值类型是一种类型,如果需要获得一个数据,并且不需要传入参数,可以使用Supplier接口,例如 无参的工厂方法,即工厂设计模式创建对象,简单来说就是 提供者
public static Student newStudent(){
Supplier<Student> supplier = ()-> {
Student student = new Student();
student.setName("默认名称");
return student;
};
return supplier.get();
}
Predicate的应用
#接收一个参数,用于判断是否满足一定的条件,过滤数据
List<String> list = Arrays.asList("awewrwe","vdssdsd","aoooo","psdddsd");
List<String> results = filter(list,obj->obj.startsWith("a"));
8、JDK8之方法法与构造函数引用
说明:方法引用是一种更简洁易懂的lambda表达式,操作符是双冒号::,用来来直接访问类或者实例已经存在的方法或构造方法
语法:左边是容器(可以是类名,实例名),中间是" :: ",右边是相应的方法名
静态方法,则是ClassName::methodName。如 Object ::equals
实例方法,则是Instance::methodName
构造函数,则是 类名::new;
public static void main(String[] args) {
// 使用双冒号::来构造静态函数引用
Function<String, Integer> fun = Integer::parseInt;
Integer value = fun.apply("1024");
System.out.println(value);
// 使用双冒号::来构造非静态函数引用
String content = "你好世界!";
Function<Integer, String> func = content::substring;
String result = func.apply(1);
System.out.println(result);
// 构造函数引用,多个参数
BiFunction<String, Integer, User> biFunction = User::new;
User user1 = biFunction.apply("李白", 28);
System.out.println(user1.toString());
//构造函数引用,单个参数
Function<String, User> function = User::new;
User user2 = function.apply("小D");
System.out.println(user2.toString());
// 函数引用也是一种函数式接口,可以将函数引用作为方法的参数
sayHello(String::toUpperCase, "www.baidu.com");
/** *
*@param func 函数引用
* @param param 对应的参数
*/
private static void sayHello(Function<String, String> func, String param) {
String result = func.apply(param);
System.out.println(result);
}
}
9、Java新特性玩转JDK8之Stream流操作
map函数
将流中的每一个元素 T 映射为 R(类似类型转换)
类似遍历集合,对集合的每个对象做处理
//转换对象,集合里面的DO对象转换为DTO对象
List<User> list = Arrays.asList(new User(1,"小东","123"),new User(21,"jack","rawer"), new User(155,"tom","sadfsdfsdfsd"), new User(231,"marry","234324"),new User(100,"小D","122223"));
//DTO只需要id和name
List<UserDTO> userDTOList = list.stream().map(obj->{
UserDTO userDTO = new UserDTO(obj.getId(),obj.getName());
return userDTO;
}).collect(Collectors.toList()); System.out.println(userDTOList);
filter函数
用于通过设置的条件过滤出元素
List<String> list = Arrays.asList("springboot", "springcloud", "redis", "git", "netty", "java", "html", "docker");
//需求:过滤出字符串长度大于5的字符串
List<String> resultList = list.stream().filter(obj -> obj.length() > 5).collect(Collectors.toList());
System.out.println(resultList);
sorted函数
orted() 对流进行自然排序, 其中的元素必须实现Comparable 接口
List<String> list = Arrays.asList("springboot", "springcloud", "redis", "git", "netty", "java", "html", "docker");
List<String> resultList = list.stream().sorted().collect(Collectors.toList());
sorted(Comparator<? super T> comparator) 用来自定义升降序
List<String> list = Arrays.asList("springboot", "springcloud", "redis", "git", "netty", "java", "html", "docker");
//根据长度进行自然排序
List<String> resultList = list.stream().sorted(Comparator.comparing(obj -> obj.length())).collect(Collectors.toList());
//倒序
List<String> resultList = list.stream().sorted(Comparator.comparing(obj -> obj.length(),Comparator.reverseOrder())).collect(Collectors.toList());
//倒序
List<String> resultList = list.stream().sorted(Comparator.comparing(String::length).reversed()).collect(Collectors.toList());
limit函数
截断流使其最多只包含指定数量的元素
List<String> list = Arrays.asList("springboot", "springcloud", "redis", "git", "netty", "java", "html", "docker");
//limit截取
List<String> resultList = list.stream().sorted(Comparator.comparing(String::length).reversed()).limit(3).collect(Collectors.toList());System.out.println(resultList);
allMatch和anyMatch函数
List<String> list = Arrays.asList("springboot", "springcloud", "redis", "git", "netty", "java", "html", "docker");
//检查是否匹配所有元素,只有全部符合才返回true
boolean flag = list.stream().allMatch(obj->obj.length()>1);
//检查是否至少匹配一个元素
boolean flag = list.stream().anyMatch(obj->obj.length()>18);
max和min函数
List<Student> list = Arrays.asList(new Student(32),new Student(33),new Student(21),new Student(29),new Student(18));
//最大
list.stream().max(Comparator.comparingInt(Student::getAge));
//最大
Optional<Student> optional = list.stream().max((s1, s2)->Integer.compare(s1.getAge(),s2.getAge()));
//最小
Optional<Student> optional = list.stream().min((s1, s2)->Integer.compare(s1.getAge(),s2.getAge()));
reduce操作
聚合操作,中文意思是 “减少”
根据一定的规则将Stream中的元素进行计算后返回一个唯一的值
// accumulator 计算的累加器
Optional<T> reduce(BinaryOperator<T> accumulator);
//例子: 第一个元素相加和第二个元素相加,结果再和第三个元素相加,直到全部相加完成
int value = Stream.of(1, 2, 3, 4, 5).reduce((item1, item2) -> item1 + item2).get();
//identity 用户提供一个循环计算的初始值
//accumulator 计算的累加器
//例子: 100作为初始值,然后和第一个元素相加,结果在和第二个元素相加,直到全部相加完成
T reduce(T identity, BinaryOperator<T> accumulator);
int value = Stream.of(1, 2, 3, 4,5).reduce(100, (sum, item) -> sum + item);
//求最大值
int value = Stream.of(1645, 234345, 32, 44434,564534,435,34343542,212).reduce( (item1, item2) -> item1 > item2 ? item1 : item2 ).get();
集合的foreach
集合遍历的方式
for循环 迭代器 Iterator
注意点:
不能修改包含外部的变量的值
不能用break或者return或者continue等关键词结束或者跳过循环
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
List<Student> results = Arrays.asList(new Student(32),new Student(33),new Student(21),new Student(29),new Student(18));
results.forEach(obj->{ System.out.println(obj.toString());});
collect()方法
一个终端操作, 用于对流中的数据进行归集操作,collect方法接受的参数是一个Collector(收集器)
Collector的作用:
就是收集器,也是一个接口, 它的工具类Collectors提供了很多工厂方法
Collectors 的作用:
工具类,提供了很多常见的收集器实现
Collectors.toList()
Collectors.toMap()
Collectors.toSet()
Collectors.toCollection()//用户自定义的实现Collection的数据结构收集
Collectors.toCollection(LinkedList::new)
Collectors.toCollection(CopyOnWriteArrayList::new)
Collectors.toCollection(TreeSet::new)
joining函数
拼接函数 Collectors.joining
3种重载方法
Collectors.joining()
Collectors.joining(“param”)
Collectors.joining(“param1”, “param2”, “param3”)
//该方法可以将Stream得到一个字符串, joining函数接受三个参数,分别表示:元素之间的连接符、前缀、后缀。
String result = Stream.of("springboot", "mysql", "html5", "css3").collect(Collectors.joining(",", "[", "]"));
partitioningBy分组和group by分组
Collectors.partitioningBy 分组,key是boolean类型
//根据list进行分组,字符串长度大于4的为一组,其他为另外一组
List<String> list = Arrays.asList("java", "springboot", "HTML5","nodejs","CSS3");
Map<Boolean, List<String>> result = list.stream().collect(partitioningBy(obj -> obj.length() > 4));
Collectors.groupingBy()分组,key是用户可指定的类型
//根据学生所在的省份,进行分组
Map<String, List<Student>> listMap =
students.stream().collect(Collectors.groupingBy(obj ->
obj.getProvince()));
listMap.forEach((key, value) -> {
System.out.println("========");
System.out.println(key);
value.forEach(obj -> {
System.out.println(obj.getAge());
});
});
summarizing 集合统计
可以一个方法把统计相关的基本上都完成
分类:
summarizingInt
summarizingLong
summarizingDouble
//统计学生的各个年龄信息
List<Student> students = Arrays.asList(new Student("广东", 23), new Student("广东", 24), new Student("广东", 23),new Student("北京", 22), new Student("北京", 20), new Student("北京", 20),new Student("海南", 25));
IntSummaryStatistics summaryStatistics = students.stream().collect(Collectors.summarizingInt(Student::getAge));
System.out.println("平均值:" + summaryStatistics.getAverage());
System.out.println("人数:" + summaryStatistics.getCount());
System.out.println("最大值:" + summaryStatistics.getMax());
System.out.println("最小值:" + summaryStatistics.getMin());
System.out.println("总和:" + summaryStatistics.getSum());
自定义distinctByKey 指定属性集合去重
public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
ArrayList<UserDataRecord> list = new ArrayList<>();
List<UserDataRecord> collect = list.stream().filter(distinctByKey(UserDataRecord::getShopTreeId)).collect(Collectors.toList());
10、Java新特性玩转JDK8之并行流parallelStream
为什么会有这个并行流?
集合做重复的操作,如果使用串行执行会相当耗时,因此一般会采用多线程来加快, Java8的paralleStream用fork/join框架提供了并发执行能力
底层原理:
线程池(ForkJoinPool)维护一个线程队列
可以分割任务,将父任务拆分成子任务,完全贴合分治思想
两个区别:
//顺序输出
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);numbers.stream().forEach(System.out::println);
//并行乱序输出
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);numbers.parallelStream().forEach(System.out::println);
问题
paralleStream并行是否一定比Stream串行快?
错误,数据量少的情况,可能串⾏更快,ForkJoin会耗性能
多数情况下并行比串行快,是否可以都用并行
不行,部分情况会有线程安全问题,parallelStream里面使用的外部变量,比如集合一定要使用线程安全集合,不然就会引发多线程安全问题