java8之前HashMap存储结构如下图,将Map中的key进行哈希运算得到hashCode,当出现hashCode相同但equals不同时称此现象为碰撞
,发生碰撞时会形成链表结构,取值时会遍历整个链表结构效率较低。
java8采用的HashMap存储结构如下图,当发生碰撞形成的链表上元素个数大于8时,总容量大于64会将链表转换为红黑树。此种情况下除了添加元素慢一些,其余操作(查询,删除)均高于链表结构。
java8之前concurrentHashMap为HashTable的组合达到线程安全的效果,默认并发级别为16即concurrentHashMap由16个HashTable组成。java8采用CAS算法到达线程安全的效果,数据结构为java8的HashMap结构。
注:hashMap起始默认容器大小为16,当容器元素个数到达75%(扩容因子)开始扩容,扩容一倍大小重新计算位置。
历史:之前很多公司生产的JVM早已没有永久代,只是SUN的JVM还没有淘汰永久代
java8将永久代变为元空间(MetaSpace)。之前永久代在JVM中分配,永久代基本不回收占用JVM内存空间。java8废弃永久代改为元空间,元空间在操作系统的内存上进行分配。
在函数式语言中,我们只需要给函数分配变量,并将这个函数作为参数传递给其它函数就可实现特定的功能。而java如前言中所述,不能直接将方法当作一个参数传递。同时匿名内部类又存在诸多不便:语法过于冗余,匿名类中的this和变量名容易使人产生误解,类型载入和实例创建语义不够灵活,无法捕获非final的局部变量等。 Lambda 表达式的出现为 Java 添加了缺失的函数式编程特点,使我们能将函数当做一等公民看待。
Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为->
,该操作符被称为Lambda 操作符或箭头操作符。它将Lambda 分为两个部分:
左侧:指定了Lambda 表达式需要的所有参数
右侧:指定了Lambda 体,即Lambda 表达式要执行的功能
Lambda表达式实现的必须是函数式接口。
只包含一个抽象方法的接口,称为函数式接口。可以通过Lambda 表达式来创建该接口的对象。(若Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。我们可以在任意函数式接口上使用@FunctionalInterface
注解,这样做可以检查它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
/**
* 情景一:无参数,无返回值,一条语句
*/
@Test
public void test() throws Exception {
Runnable r = () -> System.out.println("Hello Lambda");
r.run();
}
/**
* 情景二:有一个参数,并且无返回值,一条语句
*/
@Test
public void test() throws Exception {
Consumer<String> consumer = (x) -> System.out.println("Hello " + x);
consumer.accept("Lambda");
}
/**
* 情景三:有两个以上参数,多条语句
*/
@Test
public void test() throws Exception {
Comparator<Integer> comparator = (x, y) -> {
System.out.println("多条语句");
return Integer.compare(x, y);
};
}
注:
当参数只有一个时小括号可以不写,但一般情况下不省略。
当只有Lambda体中只有一条语句可以省略{}
和return
,一般情况下省略。
Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器可以通过上下文进行类型推断
。如果要写需要全部写上类型。
①消费型接口
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
// 链式调用,之后继续调用消费型接口
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
②供给型接口
@FunctionalInterface
public interface Supplier<T> {
T get();
}
③函数型接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
// 链式调用,在调用此方法之前调用传入的函数式接口
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
// 链式调用,在调用此方法之后调用传入的函数式接口
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
// 返回输入的参数,Function.identity().apply("AAA")返回AAA
static <T> Function<T, T> identity() {
return t -> t;
}
}
④断言型接口
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
// 链式调用,需要同时满足条件
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
// 取相反值
default Predicate<T> negate() {
return (t) -> !test(t);
}
// 链式调用,只需要满足一个条件即可
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用(实现抽象方法的参数列表和返回类型,必须与方法引用方法的参数列表和返回类型保持一致)
方法引用:使用操作符::
将方法名和对象或类的名字分隔开来。如下三种主要使用情况:
对象::实例方法
@Test
public void test() throws Exception {
PrintStream ps = System.out;
Consumer<String> consumer = ps::println;
consumer.accept("Lambda");
}
类::静态方法
@Test
public void test() throws Exception {
Comparator<Integer> comparator = Integer::compare;
}
类::实例方法,第一个参数是此方法的调用者,第二个参数是此方法的参数时可以使用ClassName::MethodName
@Test
public void test() throws Exception {
BiPredicate<String, String> predicate = String::equals;
}
public void test() throws Exception {
Supplier<Employee> supplier = Employee::new; // 与函数式接口参数列表匹配调用对应的构造方法
supplier.get();
}
@Test
public void test() throws Exception {
// 传入的参数必须是Integer,返回是一个数组
Function<Integer, Employee[]> function = Employee[]::new;//new Employee[num]
}
Stream 是Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL 执行的数据库查询。也可以使用Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算”
一、创建Stream
@Test
public void test() throws Exception {
// 1、通过Collection系列集合提供的stream()或parallelStream()获取
List<String> list = new ArrayList<String>();
Stream<String> stream1 = list.stream();
// 2、通过Arrays中的静态方法stream()获取数组流
Employee[] employees = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(employees);
// 3、通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("AAA","BBB","CCC");
// 4、迭代创建无限流
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 1);
// 5、生成创建无限流
Stream<Double> stream5 = Stream.generate(Math::random);
}
二、中间操作
筛选
/**
* filter——接受Lambda,从流中排除某些元素
* limit——截断流,使其元素不超过给定数量
* skip(n)——跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit互补
* distinct——去重,通过元素生成的hashCode和equals去重
*/
@Test
public void test() throws Exception {
emps.stream().filter((e) -> e.getSalary() > 5000);
emps.stream().limit(5);
emps.stream().skip(5);
emps.stream().distinct();
// 此操作会发生短路,取出工资大于5000的两个其余不再遍历
emps.stream().filter((e) -> e.getSalary() > 5000).limit(2);
}
映射
/**
* map——接收Lambda,将元素转换为其他形式。
* flatMap——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
*/
@Test
public void test() throws Exception {
emps.stream().map(Employee::getName);
// 会将【【A】,【B】,【C】】转换为【A,B,C】
List<List<String>> lls = Arrays.asList(Arrays.asList("A"),Arrays.asList("B"),Arrays.asList("C"));
lls.stream().flatMap((ls) -> ls.stream());
}
排序
/**
* sorted():自然排序
* sorted(Comparator com):定制排序
*/
@Test
public void test() throws Exception {
List<String> arr = Arrays.asList("AAA","BBB","CCC");
arr.stream().sorted(); // 自然排序
emps.stream().sorted((x, y) -> x.getAge().compareTo(y.getAge())); // 定制排序
}
三、终止操作(终端操作)
查找与匹配
/**
* allMatch:检查是否匹配所有元素
* anyMatch:检查是否至少匹配一个元素
* noneMatch:检查是否没有匹配所有元素
* findFirst:返回第一个元素
* findAny:返回任意一个元素
* count:返回元素个数
* max:返回流中最大元素
* min:返回流中最小元素
*/
@Test
public void test() throws Exception {
// employees是否年龄都大于18
boolean allMatch = emps.stream().allMatch((e) -> e.getAge() > 18);
// employees是否存在工资大于6k
boolean anyMatch = emps.stream().anyMatch((e) -> e.getSalary() > 6000);
// employees是否没有人叫小明
boolean noneMatch = emps.stream().noneMatch((e) -> e.getName().equals("小明"));
// 返回第一个元素
Optional<Employee> findFirst = emps.stream().findFirst();
// 返回任意一个元素,stream()会一直返回第一个
Optional<Employee> findAny = emps.parallelStream().findAny();
// 返回元素个数
long count = emps.stream().count();
// 返回年龄最大的
Optional<Employee> max = emps.stream().max((x, y) -> Integer.compare(x.getAge(),y.getAge()));
// 返回年龄最小的
Optional<Employee> min = emps.stream().min((x, y) -> Integer.compare(x.getAge(),y.getAge()));
}
归约与收集
/**
* 归约:reduce(T indetity, BinaryOperator) / reduce(BinaryOperator)
* 可以将流中的元素反复结合起来得到一个新值,indetity-起始值
*/
@Test
public void testReduce() throws Exception {
emps.stream().map(Employee::getSalary).reduce(0.0, Double::sum); //计算工资总和
}
/**
* 收集:collect - 将流转换为其他形式,接收一个Collector接口实现,用于数据汇总【Collectors工具类】
*/
@Test
public void testCollect() throws Exception {
List<String> names = emps.stream().map(Employee::getName).collect(Collectors.toList());
// 转换为自定义数据类型
HashSet<Double> salarys = emps.stream().map(Employee::getSalary).collect(Collectors.toCollection(HashSet::new));
// 用收集器得到总个数
long count = emps.stream().collect(Collectors.counting());
// 取平均值
Double avg = emps.stream().collect(Collectors.averagingDouble(Employee::getSalary));
// 取总和
Double sum = emps.stream().collect(Collectors.summingDouble(Employee::getSalary));
// 取最大值
Optional<Employee> max = emps.stream().collect(Collectors.maxBy(
(x, y) -> Double.compare(x.getSalary(), y.getSalary())));
// 拼接字符串
String joinNames = emps.stream().map(Employee::getName).collect(Collectors.joining(","));
// 通过summaryStatistics获得值
DoubleSummaryStatistics summaryStatistics = emps.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
summaryStatistics.getAverage();
summaryStatistics.getCount();
summaryStatistics.getMax();
summaryStatistics.getMin();
summaryStatistics.getSum();
// 分组,也可以进行多级分组
Map<Double, List<Employee>> groupSalary = emps.stream().collect(Collectors.groupingBy(Employee::getSalary)); //以工资分组
Map<String, List<Employee>> groupAge = emps.stream().collect(Collectors.groupingBy(
(e) -> e.getAge() > 35 ? "中年" : "青年")); //以年龄分组
// 分区是一种特殊的分组,结果 map至少包含两个不同的分组一个true,一个false
Map<Boolean, List<Employee>> partitionSalary = emps.stream().collect(Collectors.partitioningBy((e) -> e.getSalary() > 5000));
}
遍历
@Test
public void test() throws Exception {
emps.stream().forEach(System.out::println);
}
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join 汇总。
采用工作窃取
模式(work-stealing):
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。
Fork/Join的好处
Fork/Join示例
public class ForkJoinCalculate extends RecursiveTask<Long> {
private static final long serialVersionUID = 1L;
private long start;
private long end;
private static final long THRESHOLD = 10000;
public ForkJoinCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if(length <= THRESHOLD) {
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else {
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start,middle);
left.fork();
ForkJoinCalculate right = new ForkJoinCalculate(middle + 1,end);
right.fork();
return left.join() + right.join();
}
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
ForkJoinCalculate calculate = new ForkJoinCalculate(1, 1000000000L);
Long result = pool.invoke(calculate);
System.out.println(result);
}
}
底层依旧使用的Fork/Join框架,使用的公共的ForkJoinPool,大大简化了Fork/Join框架的使用难度
@Test
public void test() throws Exception {
OptionalLong result = LongStream.rangeClosed(1, 10000000L).parallel().reduce(Long::sum);
System.out.println(result.getAsLong());
}
Optional
常用方法:
Java 8中允许接口中包含具有具体实现的方法,该方法称为
默认方法
,使用default关键字修饰。Java 8中允许接口中定义和实现静态方法。
接口默认方法的”类优先”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
接口冲突
如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突。
interface Foo {
default String getFoo() { // 默认方法
return "foo";
}
static String showFoo() { // 静态方法
System.out.println("foo");
}
}
在可重复注解上使用@Repeatable
标注且提供容器类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
String value();
}
容器类必须提供可重复注解[] value()
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotations {
MyAnnotation[] value();
}
ElementType.TYPE_PARAMETER(Type parameter declaration) 用来标注类型参数
@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeParameterAnnotation {
}
// 如下是该注解的使用例子
public class TypeParameterClass<@TypeParameterAnnotation T> {
public <@TypeParameterAnnotation U> T foo(T t) {
return null;
}
}
ElementType.TYPE_USE(Use of a type) 能标注任何类型名称
public class TestTypeUse {
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeUseAnnotation {
}
public static @TypeUseAnnotation class TypeUseClass<@TypeUseAnnotation T> extends @TypeUseAnnotation Object {
public void foo(@TypeUseAnnotation T t) throws @TypeUseAnnotation Exception {
}
}
// 如下注解的使用都是合法的
@SuppressWarnings({ "rawtypes", "unused", "resource" })
public static void main(String[] args) throws Exception {
TypeUseClass<@TypeUseAnnotation String> typeUseClass = new @TypeUseAnnotation TypeUseClass<>();
typeUseClass.foo("");
List<@TypeUseAnnotation Comparable> list1 = new ArrayList<>();
List<? extends Comparable> list2 = new ArrayList<@TypeUseAnnotation Comparable>();
@TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object();
java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in);
}
}
java8提供的日期时间均是线程安全的,原始的Data、DateFormat、Calendar等均线程不安全