Java 8 (?称为 jdk 1.8) 是 Java 语?开发的?个主要版本。 Oracle 公司于 2014 年 3 ? 18 ?发布Java 8,它?持函数式编程,新的 JavaScript 引擎,新的?期 API,新的Stream API 等。
- Lambda 表达式 ? Lambda允许把函数作为?个?法的参数(函数作为参数传递进?法中)。
- ?法引? ? ?法引?提供了?常有?的语法,可以直接引?已有Java类或对象(实例)的?法或构造器。与lambda联合使?,?法引?可以使语?的构造更紧凑简洁,减少冗余代码。
- 默认?法 ? 默认?法就是?个在接???有了?个实现的?法。
- Stream API ?新添加的Stream API(java.util.stream) 把真正的函数式编程?格引?到Java中。
- Date Time API ? 加强对?期与时间的处理。
- Optional 类 ? Optional 类已经成为 Java 8 类库的?部分,?来解决空指针异常。
- Nashorn, JavaScript 引擎 ? Java 8提供了?个新的Nashorn javascript引擎,它允许我们在JVM上运?特定的javascript应?。 在 Java 11 已经不可?了
其中核?:Lambda表达式和Stream API
第?节:Lambda表达式
1.1 使?lambda表达式
Lambda表达式可以看成是匿名内部类,Lambda 允许把函数作为?个?法的参数(函数作为?法参数传递),将代码像数据?样传递,使? Lambda 表达式可以使代码变的更加简洁紧凑。
Lambda表达需要函数式接?的?持。
函数式接?(Functional Interface)就是?个有且仅有?个抽象?法,但是可以有多个?抽象?法的接?(jdk1.8以后可以在接?中定义?抽象?法,但是该?法需要使?default或者static修饰,使?default修饰的?法也称为默认?法)。
函数式接?可以被隐式转换为 lambda 表达式。
JDK 1.8 之前已有的函数式接?:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接?:
基本语法:
<函数式接?> <变量名> = (参数1,参数2...) -> {
}
案例1:
public class Demo1 {
public static void main(String[] args) {
Comparator<String> comparator=(o1,o2)->{
return o1.length()-o2.length();
};
List list=new ArrayList<>();
list.add("aaa");
list.add("a");
list.add("aa");
Collections.sort(list,comparator);
for (Object o : list) {
System.out.println(o);
}
}
}
需求1:有?个员?集合,获取年龄?于25的员?信息(数据筛选)
public class Users {
private String name;
private int age;
private int salary;
@Override
public String toString() {
return "Users{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
public Users() {
}
public Users(String name, int age, int salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
}
测试类:
public class TestUsers {
public static List filterAge(List<Users> list){
List userList=new ArrayList();
for (Users users : list) {
if(users.getAge()>=25){
userList.add(users);
}
}
return userList;
}
public static List filterSalary(List<Users> list){
List userList=new ArrayList();
for (Users users : list) {
if(users.getSalary()>=1000){
userList.add(users);
}
}
return userList;
}
public static void main(String[] args) {
List list=new ArrayList();
list.add(new Users("aa",18,900));
list.add(new Users("aa2",25,2000));
list.add(new Users("aa3",30,3000));
list.add(new Users("aa4",18,4000));
list.add(new Users("aa5",29,5000));
List list2 = filterSalary(list);
for (Object o : list2) {
System.out.println(o);
}
}
}
问题:如果再添加类似需求,需要再添加?个?法。如何解决?
- 使?策略设计模式
public interface MyFilter<T> {
public boolean test(T t);
}
public class AgeFilter implements MyFilter<Users> {
@Override
public boolean test(Users users) {
return users.getAge()>=25;
}
}
public class SalaryFilter implements MyFilter<Users> {
@Override
public boolean test(Users o) {
return o.getSalary()>=1000;
}
}
测试类:
public class TestUsers2 {
public static List filter(List<Users> list, MyFilter myFilter){
List userList=new ArrayList();
for (Users users : list) {
if(myFilter.test(users)){
userList.add(users);
}
}
return userList;
}
public static void main(String[] args) {
List list=new ArrayList();
list.add(new Users("aa",18,900));
list.add(new Users("aa2",25,2000));
list.add(new Users("aa3",30,3000));
list.add(new Users("aa4",18,4000));
list.add(new Users("aa5",29,5000));
List list2 = filter(list,new AgeFilter());
for (Object o : list2) {
System.out.println(o);
}
}
}
- 使?Lambada表达式优化策略模式(不创建实现类)
public interface MyFilter2<Users> {
public boolean test(Users t);
}
public class TestUsers3 {
public static List filter(List<Users> list, MyFilter2<Users> myFilter2){
List userList=new ArrayList();
for (Users users : list) {
if(myFilter2.test(users)){
userList.add(users);
}
}
return userList;
}
public static void main(String[] args) {
List list=new ArrayList();
list.add(new Users("aa",18,900));
list.add(new Users("aa2",25,2000));
list.add(new Users("aa3",30,3000));
list.add(new Users("aa4",18,4000));
list.add(new Users("aa5",29,5000));
List list2 = filter(list,(u)->u.getSalary()>=2000);
for (Object o : list2) {
System.out.println(o);
}
}
}
- 使?Stream API再优化lambda表达式
public class TestUsers4 {
public static void main(String[] args) {
List<Users> list=new ArrayList();
list.add(new Users("aa",18,900));
list.add(new Users("aa2",25,2000));
list.add(new Users("aa3",30,3000));
list.add(new Users("aa4",18,4000));
list.add(new Users("aa5",29,5000));
list.stream().filter(u->u.getAge()>=25).forEach(System.out::println);
int a=10;
list.stream().filter((u)->u.getSalary()>=2000).forEach(u1-> System.out.println(u1+","+a));
}
}
1.2使?lambda表达式注意事项
Lambda引?了新的操作符: ->(箭头操作符),->将表达式分成两部分
左侧:(参数1,参数2…)表示参数列表;
右侧:{}内部是?法体
- 形参列表的数据类型会?动推断;
- 如果形参列表为空,只需保留();
- 如果形参只有1个,()可以省略,只需要参数的名称即可;
- 如果执?语句只有1句,且?返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执?语句也保证只有1句;
- lambda不会?成?个单独的内部类?件;
- lambda表达式若访问了局部变量,则局部变量必须是final的,若是局部变量没有加final关键字,系统会?动添加,此后在修改该局部变量,会报错。
Lambda表达式的语法总结: () -> ();
前置 | 语法 |
---|
?参数?返回值 | () -> 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表达式,并不能智能匹配对应的抽象?法,因此引?了函数式接?的概念
1.3函数式接?
如果?个接?只有?个抽象?法,则该接?称之为函数式接?,函数式接?可以使?Lambda表达式,lambda表达式会被匹配到这个抽象?法上 。
为了确保你的接??定达到这个要求,你只需要给你的接?添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接?有多于?个抽象?法的时候会报错的。
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> c = (from) -> Integer.valueOf(from);
Integer converted = c.convert("123");
System.out.println(converted);
Java为了程序员?便使?Lambda表达式,Java内置了四个核?函数式接?
提供四个核?函数式接?
- Consumer 消费型 有参?返回值
- Supplier 供给型 ?参有返回值
- Function 函数型 有参有返回值
- Predicate 断?型 有参返回boolean值
public class Demo1 {
public static void a(int money, Consumer consumer){
consumer.accept(money);
}
public static List b(Supplier supplier){
List list=new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(supplier.get());
}
return list;
}
public static List c(List<String> list, Predicate<String> predicate){
List newList=new ArrayList();
for (String s : list) {
if(predicate.test(s)){
newList.add(s);
}
}
return newList;
}
public static void main(String[] args) {
List list=new ArrayList();
list.add("abc");
list.add("bcd");
list.add("ccd");
list.add("acd");
list.add("aqqqcd");
List list1 = c(list, (s) -> s.startsWith("a"));
for (Object o : list1) {
System.out.println(o);
}
}
}
第?节:?法引?
使?“::”操作符将?法名和对象或类的名字分隔开来。以下是四种使?情况:
- 对象 :: 实例?法
- 类 :: 静态?法
- 类 :: 实例?法
- 类 :: new
public class Test {
public static void main(String[] args) {
Consumer c=System.out::println;
c.accept("abcd");
Comparator<Integer> comparator=Integer::compare;
int rs = comparator.compare(10, 1);
System.out.println(rs);
BiPredicate<String,String> biPredicate=String::equals;
boolean test = biPredicate.test("abc1", "abc");
System.out.println(test);
Supplier<Users> supplier=Users::new;
Users users = supplier.get();
System.out.println(users);
MyArray<String> myArray=(n)->new String[n];
MyArray<String> myArray2=String[]::new;
String[] strings = myArray2.get(10);
System.out.println(strings.length);
}
}
public interface MyArray<T> {
T[] get(int count);
}
public interface MySupplier<T> {
T get(String name,int age);
}
第三节:Stream API
Stream是Java8中处理数组、集合的抽象概念,它可以指定你希望对集合进?的操作,可以执??常复杂的查找、过滤和映射数据等操作。使?Stream API对集合数据进?操作,就类似于使?SQL执?的数据库查询。也可以使?Stream API来并?执?操作。
简单应?:统计?个字符串类型集合中,所有?度?于3的元素个数。
public static void main(String[] args) {
List<String> data=new ArrayList<>();
data.add("hello");
data.add("world");
data.add("ni");
data.add("apple");
data.add("china");
int count = 0;
for (String s : data) {
if (s.length() > 3)
count++;
}
System.out.println(count);
long count2= data.stream().filter(s->s.length()>3).count();
System.out.println(count2);
}
上?代码中stream?法会为字符串列表?成?个Stream。filter?法会返回只包含字符串?度?于3的?个Stream,然后通过count?法计数。
3.1 什么是Stream?
?个Stream表?上与?个集合很类似,集合中保存的是数据,?流中是对数据的操作。
特点:
- Stream ??不会存储元素。
- Stream 不会改变源对象。相反,他们会返回?个持有结果的新Stream。
- Stream 操作是延迟执?的。这意味着他们会等到需要结果的时候才执?。
- Stream遵循“做什么,?不是怎么去做”的原则。只需要描述需要做什么,?不?考虑程序是怎样实现的。
3.2 如何使?Stream API
使?Stream,会有三个阶段(步骤):
- 创建?个Stream。 (创建)
- 在?个或多个步骤中,将初始Stream转化到另?个Stream的中间操作。 (中间操作)
- 使??个终?操作来产??个结果。该操作会强制他之前的延迟操作?即执?。在这之后,该Stream就不会在被使?了。(终?操作)
3.2.1 Stream的创建?法
public class Test2 {
public static void main(String[] args) {
Stream<Integer> iterate = Stream.iterate(0, n -> n + 2);
iterate.limit(5).forEach(System.out::println);
System.out.println("===========================");
Stream<Integer> generate = Stream.generate(() -> new Random().nextInt());
generate.limit(5).forEach(System.out::println);
}
}
3.2.2 Stream中间操作
中间操作包括:map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。
多个中间操作可以连接起来形成?个流?线,除?流? 线上触发终?操作,否则中间操作不会执?任何的处理! ?在终?操作时?次性全部处理,称为“惰性求值”。
public class Test3 {
public static void main(String[] args) {
List<Users> usersList=new ArrayList<>();
usersList.add(new Users("xxx", 30, 10000));
usersList.add(new Users("yyy", 29, 8000));
usersList.add(new Users("zzz", 22, 12000));
usersList.add(new Users("张三", 21, 20000));
usersList.add(new Users("李四", 32, 22000));
usersList.add(new Users("李四", 32, 22000));
List list2=new ArrayList();
list2.add(111);
list2.add(4223);
list2.add(2);
list2.add(23);
list2.add(3);
usersList.stream().sorted((o1,o2)->o2.getAge()-o1.getAge()).forEach(System.out::println);
}
}
3.2.3 Stream 的终?操作
终?操作包括:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。
遍历
查找和匹配
- allMatch——检查是否匹配所有元素
- anyMatch——检查是否?少匹配?个元素
- noneMatch——检查是否没有匹配的元素
- findFirst——返回第?个元素
- findAny——返回当前流中的任意元素
- max——返回流中最?值
- min——返回流中最?值
public class Test4 {
public static void main(String[] args) {
List<Users> usersList=new ArrayList<>();
usersList.add(new Users("xxx", 30, 10000));
usersList.add(new Users("yyy", 29, 8000));
usersList.add(new Users("zzz", 22, 12000));
usersList.add(new Users("张三", 21, 20000));
usersList.add(new Users("李四", 32, 22000));
usersList.add(new Users("李四", 32, 22000));
Stream<Users> stream = usersList.stream();
List<String> collect = stream.filter((u) -> u.getAge() >= 25).map(Users::getName).distinct()
.collect(Collectors.toList());
collect.forEach(System.out::println);
}
}
3.2.4 并?操作
Stream有串?和并?两种,串?Stream上的操作是在?个线程中依次完成,?并?Stream则是在多个线程上同时执?。
计算?下排序这个Stream要耗时多久:
public class Test5 {
public static void main(String[] args) {
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
long t0 = System.currentTimeMillis();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.currentTimeMillis();
long millis = t1-t0;
System.out.println(millis);
}
}
串行需要880ms,并行只需要450ms。
第四节:新时间?期API
Java 8通过发布新的Date-Time API (JSR 310)来进?步加强对?期与时间的处理。
在旧版的 Java 中,?期时间 API 存在诸多问题,其中有:
- ?线程安全 ? java.util.Date 是?线程安全的,所有的?期类都是可变的,这是Java?期类最?的问题之?。
- 设计很差 ? Java的?期/时间类的定义并不?致,在java.util和java.sql的包中都有?期类,此外?于格式化和解析的类在java.text包中定义。java.util.Date同时包含?期和时间,?java.sql.Date仅包含?期,将其纳?java.sql包并不合理。另外这两个类都有相同的名字,这本身就是?个?常糟糕的设计。
- 时区处理麻烦 ? ?期类并不提供国际化,没有时区?持,因此Java引?了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个?较重要的 API:
- Local(本地) ? 简化了?期时间的处理,没有时区的问题。
- ZoneId (时区) ? 通过定制的时区处理?期时间。
线程安全问题演示:
public class Test6 {
public static void main(String[] args) throws Exception {
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMdd");
Callable<LocalDate> task = new Callable<LocalDate>() {
@Override
public LocalDate call() throws Exception {
return LocalDate.parse("20200301",formatter);
}
};
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future> list=new ArrayList<Future>();
for(int i=0;i<10;i++){
Future<LocalDate> submit = executorService.submit(task);
list.add(submit);
}
for (Future future : list) {
System.out.println(future.get());
}
executorService.shutdown();
}
}
使?新时间?期API解决
public class Test6 {
public static void main(String[] args) throws Exception {
DateTimeFormatter formatter=DateTimeFormatter.ofPattern("yyyyMMdd");
Callable<LocalDate> task = new Callable<LocalDate>() {
@Override
public LocalDate call() throws Exception {
return LocalDate.parse("20200301",formatter);
}
};
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future> list=new ArrayList<Future>();
for(int i=0;i<10;i++){
Future<LocalDate> submit = executorService.submit(task);
list.add(submit);
}
for (Future future : list) {
System.out.println(future.get());
}
executorService.shutdown();
}
}
4.1 本地化?期时间 API
LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。
LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使? ISO-8601? 历系统的?期、时间、?期和时间。它们提供了简单的?期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
public class Test7 {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
LocalDateTime time = LocalDateTime.of(2020, 11, 20, 10, 20);
System.out.println(time);
LocalDateTime localDateTime = time.plusDays(3);
System.out.println(localDateTime);
LocalDateTime localDateTime1 = time.minusHours(3);
System.out.println(localDateTime1);
System.out.println(now.getYear());
System.out.println(now.getHour());
}
}
4.2 Instant、ZoneId
Instant 时间戳
它是以Unix元年(传统 的设定为UTC时区1970年1?1?午夜时分)开始 所经历的描述进?运算
4.3 时间矫正器 TemporalAdjuster
TemporalAdjuster : 时间校正器。有时我们可能需要获 取例如:将?期调整到“下个周?”等操作。
TemporalAdjusters : 该类通过静态?法提供了?量的常 ? TemporalAdjuster 的实现。
4.4 DateTimeFormatter
java.time.format.DateTimeFormatter 类:该类提供了三种 格式化?法:
预定义的标准格式
语?环境相关的格式
?定义的格式
public class Test8 {
public static void main(String[] args) {
LocalDate now = LocalDate.now();
System.out.println(now);
LocalTime now1 = LocalTime.now();
System.out.println(now1);
LocalDateTime now2 = LocalDateTime.now();
System.out.println(now2);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
String format = formatter.format(now2);
System.out.println(format);
LocalDateTime parse = LocalDateTime.parse("20201212 12:02:35", formatter);
System.out.println(parse);
}
}
第五节: 接?中的默认?法和静态?法
在接?中可以使?default和static关键字来修饰接?中定义的普通?法
interface A{
public void abc();
default void bcd(){
System.out.println("bcd");
}
static void cc(){
System.out.println("cccc");
}
}
interface B{
public void abc();
}
public class Demo1 implements B,A{
@Override
public void abc() {
}
public static void main(String[] args) {
new Demo1().abc();
new Demo1().bcd();
A.cc();
}
}
在JDK1.8中很多接?会新增?法,为了保证1.8向下兼容,1.7版本中的接?实现类不?每个都重新实现新添加的接??法,引?了default默认实现,static的?法是直接?接?名去调?法即可。当?个类继承?类?实现接?时,若后两者?法名相同,则优先继承?类中的同名?法,即“类优先”,如果实现两个同名?法的接?,则要求实现类必须?动声明默认实现哪个接?中的?法。
第六节: 对HashMap的优化
在jdk1.8中对hashMap等map集合的数据结构优化。hashMap数据结构的优化 。原来的hashMap采?的数据结构是哈希表(数组+链表),hashMap默认??是16,?个0-15索引的数组,如何往??存储元素,?先调?元素的hashcode ?法,计算出哈希码值,经过哈希算法算成数组的索引值,如果对应的索引处没有元素,直接存放,如果有对象在,那么?较它们的equals?法?较内容 如果内容?样,后?个value会将前?个value的值覆盖,如果不?样,在1.7的时候,后加的放在前?,形成?个链表,形成了碰撞,在某些情况下如果链表?限下去,那么效率极低,碰撞是避免不了的。
加载因?:0.75,数组扩容,达到总容量的75%,就进?扩容,但是?法避免碰撞的情况发?在1.8之后,在数组+链表+红?树来实现hashmap,当碰撞的元素个数?于8时 & 总容量?于64,会有红?树的引? 。除了添加之后,效率都?链表?。JDK1.7及之前的 ConcurrentHashMap 使? 锁分段机制 实现,JDK1.8则使? 数组+链表+红?树数据结构 和 CAS原?操作实现。
jdk 7 与 jdk 8 中关于HashMap的对?
- 8时红?树+链表+数组的形式,当桶内元素?于8时,便会树化
- hash值的计算?式不同
- 1.7 table在创建hashmap时分配空间,?1.8在put的时候分配,如果table为空,则为table分配空间。
- 在发?冲突,插?链中时,7是头插法,8是尾插法。
- 在resize操作中,7需要重新进?index的计算,?8不需要,通过判断相应的位是0还是1,要么依旧是原index,要么是oldCap + 原index。
第七节: Optional 类
Optional 类是?个可以为null的容器对象。如果值存在则isPresent()?法会返回true,调?get()?法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有?的?法,这样我们就不?显式进?空值检测。
Optional 类的引?很好的解决空指针异常。
类中的?法
注意: 这些?法是从 java.lang.Object 类继承来的。
public class Test2 {
public static void main(String[] args) {
Integer a=null;
Integer b=new Integer(20);
Optional<Integer> a1 = Optional.of(b);
Optional<Integer> a2 = Optional.ofNullable(b);
System.out.println(a2);
System.out.println(a2.isPresent());
Integer integer = a2.get();
System.out.println(integer);
System.out.println(a2.orElse(new Integer(30)));
}
}
第?节: Base64
在Java 8中,Base64编码已经成为Java类库的标准。
Java 8 内置了 Base64 编码的编码器和解码器。
Base64?具类提供了?套静态?法获取下?三种BASE64编解码器:
- 基本:输出被映射到?组字符A-Za-z0-9+/,编码不添加任何?标,输出的解码仅?持A-Za-z0-9+/。
- URL:输出映射到?组字符A-Za-z0-9+_,输出是URL和?件。
- MIME:输出隐射到MIME友好格式。输出每?不超过76字符,并且使?’\r’并跟随’\n’作为分割。编码输出最后没有?分割。
内嵌类
?法
注意:Base64 类的很多?法从 java.lang.Object 类继承。
public class Test3 {
public static void main(String[] args) {
String str="yhp";
String s = Base64.getEncoder().encodeToString(str.getBytes());
System.out.println(s);
byte[] decode = Base64.getDecoder().decode(s);
System.out.println("解码:"+new String(decode));
}
}
|