- 第⼀节:Lambda表达式
- 1.1 使⽤lambda表达式
- 1.2使⽤lambda表达式注意事项
- 1.3函数式接⼝
- 第⼆节:⽅法引⽤
- 第三节:Stream API
- 3.1 什么是Stream?
- 3.2 如何使⽤Stream API
- 3.2.1 Stream的创建⽅法
- 3.2.2 Stream中间操作
- 3.2.3 Stream 的终⽌操作
- 3.2.4 并⾏操作
- 第四节:新时间⽇期API
- 4.1 本地化⽇期时间 API
- 4.2 Instant、ZoneId
- 4.3 时间矫正器 TemporalAdjuster
- 4.4 DateTimeFormatter
- 第五节: 接⼝中的默认⽅法和静态⽅法
- 第六节: 对HashMap的优化
- 第七节: Optional 类
- 第⼋节: base64
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 新增加的函数接⼝:
- java.util.function
基本语法:
<函数式接⼝> <变量名> = (参数1,参数2...) -> {
//⽅法体
}
案例1:
public class Demo1 {
public static void main(String[] args) {
//匿名内部类
//表达式
//集合中进行字符串长度的排序
Comparator 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 list){
List userList=new ArrayList();
for (Users users : list) {
if(users.getAge()>=25){
userList.add(users);
}
}
return userList;
}
//限定工资大于1000
public static List filterSalary(List 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));
//限定出大于25岁的用户
// List list2 = filterAge(list);
List list2 = filterSalary(list);
for (Object o : list2) {
System.out.println(o);
}
}
}
问题:如果再添加类似需求,需要再添加⼀个⽅法。如何解决?
- 使⽤策略设计模式
//定义⼀个接⼝ public interface MyFilter{ public boolean test(T t); }
//按照年龄过滤 public class AgeFilter implements MyFilter{ @Override public boolean test(Users users) { return users.getAge()>=25; } } //按照⼯资过滤 public class SalaryFilter implements MyFilter { @Override public boolean test(Users o) { return o.getSalary()>=1000; } }
测试类:
public class TestUsers2 {
//限定年龄或工资
public static List filter(List 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));
//限定出大于25岁的用户
List list2 = filter(list,new AgeFilter());
// List list2 = filter(list,new SalaryFilter());
for (Object o : list2) {
System.out.println(o);
}
}
}
- 使⽤Lambada表达式优化策略模式(不创建实现类)
public interface MyFilter2{ public boolean test(Users t); }
public class TestUsers3 {
//限定年龄或工资
public static List filter(List list, MyFilter2 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));
//限定出大于25岁的用户
// List list2 = filter(list,(u)->u.getAge()>=30);
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 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));
//限定出大于25岁的用户
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{ T convert(F from); } Converter c = (from) -> Integer.valueOf(from); Integer converted = c.convert("123"); System.out.println(converted); // 123
Java为了程序员⽅便使⽤Lambda表达式,Java内置了四个核⼼函数式接⼝
提供四个核⼼函数式接⼝
- Consumer 消费型 有参⽆返回值
- Supplier 供给型 ⽆参有返回值
- Function 函数型 有参有返回值
- Predicate 断⾔型 有参返回boolean值
public class Demo1 {
//测试consumer
public static void a(int money, Consumer consumer){
consumer.accept(money);
}
//supplier
public static List b(Supplier supplier){ //get()
List list=new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(supplier.get());
}
return list;
}
public static List c(List list, Predicate predicate){
List newList=new ArrayList();
for (String s : list) {
if(predicate.test(s)){
newList.add(s);
}
}
return newList;
}
public static void main(String[] args) {
//测试consumer
//将字符串转换成大写
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 comparator=(o1,o2)->Integer.compare(o1,o2);
Comparator comparator=Integer::compare;
int rs = comparator.compare(10, 1);
System.out.println(rs);
//类::实例方法
//BiPredicate biPredicate=(s1,s2)->s1.equals(s2);
BiPredicate biPredicate=String::equals;
boolean test = biPredicate.test("abc1", "abc");
System.out.println(test);
//类::new
// Supplier supplier=()->new Users();
Supplier supplier=Users::new;
Users users = supplier.get();
System.out.println(users);
//数组引用
MyArray myArray=(n)->new String[n];
//等价于
MyArray myArray2=String[]::new;
String[] strings = myArray2.get(10);
System.out.println(strings.length);
}
}
public interface MyArray{ T[] get(int count); }
public interface MySupplier第三节:Stream API{ T get(String name,int age); //参数列表的类型要和构造⽅法的类型相同 }
Stream是Java8中处理数组、集合的抽象概念,它可以指定你希望对集合进⾏的操作,可以执⾏⾮常复杂的查找、过滤和映射数据等操作。使⽤Stream API对集合数据进⾏操作,就类似于使⽤SQL执⾏的数据库查询。也可以使⽤Stream API来并⾏执⾏操作。
简单应⽤:统计⼀个字符串类型集合中,所有⻓度⼤于3的元素个数。
public static void main(String[] args) {
//传统实现
List 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);
//Stream API
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遵循“做什么,⽽不是怎么去做”的原则。只需要描述需要做什么,⽽不⽤考虑程序是怎样实现的。
使⽤Stream,会有三个阶段(步骤):
- 创建⼀个Stream。 (创建)
- 在⼀个或多个步骤中,将初始Stream转化到另⼀个Stream的中间操作。 (中间操作)
- 使⽤⼀个终⽌操作来产⽣⼀个结果。该操作会强制他之前的延迟操作⽴即执⾏。在这之后,该Stream就不会在被使⽤了。(终⽌操作)
public class Test2 {
public static void main(String[] args) {
//创建Stream
//方法1:Stream.of()
//方法2:Arrays工具类
//方法3:使用集合的Stream()方法
//方法4:创建无限流
//4.1 迭代
Stream iterate = Stream.iterate(0, n -> n + 2);
iterate.limit(5).forEach(System.out::println);
System.out.println("===========================");
//4.2 生成
Stream 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 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));
//1.过滤
// usersList.stream().filter((u)->u.getAge()>25).forEach(System.out::println);
//2 分页
// usersList.stream().limit(5).forEach(System.out::println);
//3 跳过
// usersList.stream().skip(5).limit(5).forEach(System.out::println);
//4 去重
//5 映射(获取部分数据)-map(Function(T,R))
// usersList.stream().map((e)->e.getName()).forEach(System.out::println);
//6.排序
List list2=new ArrayList();
list2.add(111);
list2.add(4223);
list2.add(2);
list2.add(23);
list2.add(3);
// list2.stream().sorted().forEach(System.out::println);
//6.1 排序内容为对象时?
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。
遍历
- forEach
查找和匹配
- allMatch——检查是否匹配所有元素
- anyMatch——检查是否⾄少匹配⼀个元素
- noneMatch——检查是否没有匹配的元素
- findFirst——返回第⼀个元素
- findAny——返回当前流中的任意元素
- max——返回流中最⼤值
- min——返回流中最⼩值
public class Test4 {
public static void main(String[] args) {
List 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 stream = usersList.stream();
//1.全部匹配
//2.至少匹配一个
//3.检查都不匹配的
//4.返回第一个元素
//5.返回总条数
//6.最大值 最高的工资
//7.最小值
//8.终止操作
//9.归约(统计操作)-计算总的工资
//10.collect() 收集
List 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 values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
// System.out.println("-------------并行----------------");
long t0 = System.currentTimeMillis();
// parallelStream()是Collection接口的方法
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.currentTimeMillis();
long millis = t1-t0;
System.out.println(millis);
}
}
串行需要880ms,并行只需要450ms。
第四节:新时间⽇期APIJava 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 task = new Callable() {
@Override
public LocalDate call() throws Exception {
// return simpleDateFormat.parse("20200301");
return LocalDate.parse("20200301",formatter);
}
};
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
List list=new ArrayList();
for(int i=0;i<10;i++){
Future 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 task = new Callable() {
@Override
public LocalDate call() throws Exception {
// return simpleDateFormat.parse("20200301");
return LocalDate.parse("20200301",formatter);
}
};
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
List list=new ArrayList();
for(int i=0;i<10;i++){
Future 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 时间矫正器 TemporalAdjusterTemporalAdjuster : 时间校正器。有时我们可能需要获 取例如:将⽇期调整到“下个周⽇”等操作。
TemporalAdjusters : 该类通过静态⽅法提供了⼤量的常 ⽤ TemporalAdjuster 的实现。
4.4 DateTimeFormatterjava.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);
//date->String
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
String format = formatter.format(now2);
System.out.println(format);
//String -date
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 类是⼀个可以为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 a1 = Optional.of(b);//of()如果是null值,则报错NullPointerException
Optional a2 = Optional.ofNullable(b);//ofNullable
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));
}
}



