20,JDK1.8特性

Java8新特性简介

Java8(又称为jdk1.8)是Java语言开发的一个重要版本。Java8是Oracle公司于2014年3月发布,可以看成是自Java5以来最具革命性的版本。Java8为Java语言,编译器,类库,开发工具与JVM带来了大量的新特性。

  • 代码更少(增加了新的语法:Lambda表达式
  • 强大的Stream API
  • 速度更快
  • 最大化减少空指针异常:Optional
  • Nashorm引擎,允许在JVM上运行JS应用
  • 便于并行

Tips:

  1. Nashorn,发音"nass-horn",是二战德国时一个坦克的命名,同时也是java8新一代的javascript引擎。
  2. javascript运行jvm已经不是新鲜事,Rhino早在jdk6的时候就已经存在,但是为什么现在要代替Rhino,官方解释是Rhino相比其他JavaScript引擎(比如:Google的V8)是在是太慢,要改造Rhino还不如重写,所以Nashorn的性能也是其中一个亮点。

并行流与串型流
Java8中将并行进行了优化,可以很容易的对数据进行并行操作。Stream API 可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换。
并行流就是把一个内容(数组或者集合)分成多个数据块,并且不同的线程分别处理每个数据块的流。这样一来,就可以自动把给定的操作的工作符合分配给多核处理器的所有内核,让他们都忙起来。整个过程无需程序员显示实现优化。

	public static long parallelSum(Long n){
		return Stream.iterate(1L, i->i+1)
		.limit(n)
		.parallel()
		.reduce(0L, Long :: sum);
		}

在这里插入图片描述

1-接口的新特性

Java 8中关于接口的改进

Java 8中,你可以为接口添加静态方法默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。

静态方法: 使用static关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到Collection/Collections或者Path/Paths这样成对的接口和类。

默认方法:默认方法使用default关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。

public interface AA {
	double PI = 3.14;
	public default void method() {
	System.out.println("北京");
	}
	default String method1() {//默认方法
	return "上海";
	}
	public static void method2() { /静态方法
	System.out.println(“hello lambda!");
	}
}

接口中的默认方法

接口默认方法的“类优先”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时

  • 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
  • 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么实现类必须覆盖该方法来解决冲突。

接口冲突的解决方法
在这里插入图片描述

2 –注解的新特性

Java 8 中关于注解的修改
Java 8对注解处理提供了两点改进:可重复的注解可用于类型的注解。此外,反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法参数上的注解。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

@Target({TYPE, FIELD, METHOD, PARAMETER,PACKAGE, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
String[] value();
}
-----
应用场景
public Person(@MyAnnotation(value="notnull")String name){
this.name = name;
}

3 –集合的底层源码实现

ArrayList、LinkedList、Vector区别?

|------List子接口:存储序的、可重复的数据---->"动态"数组

  • |-----ArrayList:作为List的主要实现类;线程不安全的,效率高;底层使用数组实现
    (Collections中定义了synchronizedList(List list)将此ArrayList转化为线程安全的)
  • |-----LinkedList:对于频繁的插入、删除操作,我们建议使用此类,因为效率高;内存消耗较ArrayList大;底层使用双向链表实现
  • |-----Vector:List的古老实现类;线程安全的,效率低;底层使用数组实现

数据结构

在这里插入图片描述在这里插入图片描述
分析一下ArrayList和LinkedList的底层源码实现:

ArrayList 源码分析:

jdk7

ArrayList list = new ArrayList();//初始化一个长度为10的Object[] elementData
sysout(list.size());//返回存储的元素的个数:0
list.add(123);
list.add(345);
...

当添加第11个元素时,需要扩容,默认扩容为原来的1.5倍。还需要将原有数组中的数据复制到新的数组中。
删除操作:如果删除某一个数组位置的元素,需要其后面的元素依次前移。
remove(Object obj) / remove(int index)
jdk8:

ArrayList list = new ArrayList();//初始化一个长度为0的Object[] elementData
sysout(list.size());//返回存储的元素的个数:0
list.add(123);//此时才创建一个长度为10的Object[] elementData
list.add(345);
...

当添加第11个元素时,需要扩容,默认扩容为原来的1.5倍。还需要将原有数组中的数据复制到新的数组中。
开发时的启示

  1. 建议使用:ArrayList list = new ArrayList(int length);
  2. jdk8延迟了底层数组的创建:内存的使用率;对象的创建更快

LinkedList 源码分析:

LinkedList:底层使用双向链表存储添加的元素

void linkLast(E e) {
	final Node<E> l = last;
	final Node<E> newNode = new Node<>(l, e, null);
	last = newNode;
	if (l == null)
	first = newNode;
	else
	l.next = newNode;
	size++;
	modCount++;
}
内部类体现:
private static class Node<E> {
	E item;
	Node<E> next;
	Node<E> prev;
}

HashMap和Hashtable的对比

HashMap:Map的主要实现类;线程不安全的,效率高;可以存储null的key和value
存储结构:jdk7.0 数组+链表;jdk8.0 数组+链表+红黑树
Hashtable:Map的古老实现类;线程安全的,效率低;不可以存储null的key和value

HashMap的底层实现原理

HashMap map = new HashMap();//底层创建了长度为16的Entry数组向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数组中要存储的位置i.如果位置i上没有元素,则entry1直接添加成功。如果位置i上已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次比较entry1中key和其他的entry是否equals.如果返回值为true.则使用entry1的value去替换equals为true的entry的value.如果遍历一遍以后,发现所有的equals返回都为false,则entry1仍可添加成功。entry1指向原有的entry元素。

默认情况下,如果添加元素的长度>= DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR(临界值threshold默认值为12)且新要添加的数组位置不为null的情况下,就进行扩容。默认扩容为原有长度的2倍。将原有的数据复制到新的数组中。

HashMap的底层实现原理

jdk 8.0 :
1.HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组。

2.当首次调用map.put()时,再创建长度为16的数组

3.当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置上的所有entry使用红黑树进行存储。而jdk 7 中没有红黑树结构

4.新添加的元素如果与现有的元素以链表方式存储的时候:“七上八下”:jdk7:新添加的当链表头,jdk8:新添加的当链表尾

HashMap的存储结构

JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)
JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。
在这里插入图片描述

HashMap的存储结构

在这里插入图片描述
**负载因子值的大小,对HashMap有什么影响**
负载因子的大小决定了HashMap的数据密度,负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,
造成查询或插入时的比较次数增多,性能会下降。负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。
在这里插入图片描述

public static void main(String[] args) {
	List list = new ArrayList();
	list.add(1):
	list.add(2);
	list.add(3);
	updateList(list);
	System.out.println(list);
}
private static void updateList(List list) {
	list.remove(2);
}
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
p1.name = "CC";
set.remove(p1);
System.out.println(set);
set.add(new Person(1001,"CC"));
System.out.println(set);
set.add(new Person(1001,"AA"));
System.out.println(set);
其中,其中Person类中重写了hashCode()equal()方法

4 –新日期时间的API

新时间日期API

如果我们可以跟别人说:“我们在1502643933071见面,别晚了!”那么就再简单不过了。但是我们希望时间与昼夜和四季有关,于是事情就变复杂了。JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是:
可变性:像日期和时间这样的类应该是不可变的。
偏移性:Date中的年份是从1900开始的,而月份都从0开始。
格式化:格式化只对Date有用,Calendar则不行。
此外,它们也不是线程安全的;不能处理闰秒等。

总结:对日期和时间的操作一直是Java程序员最痛苦的地方之一。

  • 第三次引入的API是成功的,并且java 8中引入的java.time API 已经纠正了过去的缺陷,将来很长一段时间内它都会为我们服务。
  • Java 8 吸收了Joda-Time 的精华,以一个新的开始为Java 创建优秀的API。新的java.time 中包含了所有关于本地日期(LocalDate)本地时间(LocalTime)、本地日期时间(LocalDateTime)时区(ZonedDateTime)持续时间(Duration)的类。历史悠久的Date 类新增了toInstant()方法,用于把Date 转换成新的表示形式。这些新增的本地化时间日期API 大大简化了日期时间和本地化的管理。
    • java.time–包含值对象的基础包
    • java.time.chrono–提供对不同的日历系统的访问
    • java.time.format–格式化和解析时间和日期
    • java.time.temporal–包括底层框架和扩展特性
    • java.time.zone–包含时区支持的类

    说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。

LocalDate、LocalTime, LocalDateTime

LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。

注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历。

方法描述
now() /* now(ZoneId zone)静态方法,根据当前时间创建对象/指定时区的对象
of()静态方法,根据指定日期/时间创建对象
getDayOfMonth()/getDayOfYear()获得月份天数(1-31)/获得年份天数(1-366)
getDayOfWeek()获得星期几(返回一个DayOfWeek枚举值)
getMonth()获得月份,返回一个Month枚举值
getMonthValue() / getYear()获得月份(1-12)/获得年份
getHour()/getMinute()/getSecond()获得当前对象对应的小时、分钟、秒
withDayOfMonth()/withDayOfYear()/withMonth()/withYear()将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
plusDays(), plusWeeks(),plusMonths(), plusYears(),plusHours()向当前对象添加几天、几周、几个月、几年、几小时
minusMonths() / minusWeeks()/minusDays()/minusYears()/minusHours()从当前对象减去几月、几周、几天、几年、几小时

Instant 时间点

  • 在处理时间和日期的时候,我们通常会想到年,月,日,时,分,秒。然而,这只是时间的一个模型,是面向人类的。第二种通用模型是面向机器的,或者说是连续的。在此模型中,时间线中的一个点表示为一个很大的数,这有利于计算机处理。在UNIX中,这个数从1970年开始,以秒为的单位;同样的,在Java中,也是从1970年开始,但以毫秒为单位
  • java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。 Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。(1 ns = 10-9s) 1秒= 1000毫秒=106微秒=109纳秒
    在这里插入图片描述
方法描述
now()静态方法,返回默认UTC时区的Instant类的对象
ofEpochMilli(long epochMilli)静态方法,返回在1970-01-0100:00:00基础上加上指定毫秒数之后的Instant类的对象
atOffset(ZoneOffset offset)结合即时的偏移来创建一个OffsetDateTime
toEpochMilli()返回1970-01-0100:00:00到当前时间的毫秒数,即为时间戳

时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。

格式化与解析日期或时间

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:

  • 预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE
  • 本地化相关的格式。如:ofLocalizedDate(FormatStyle.FULL)
  • 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss E”)
方法描述
ofPattern(Stringpattern)静态方法,返回一个指定字符串格式的DateTimeFormatter
format(TemporalAccessort)格式化一个日期、时间,返回字符串
parse(CharSequencetext)将指定格式的字符序列解析为一个日期、时间

5 –Optional类的使用

Optional 类

到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
Optional实际上是个容器它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional类的Javadoc描述如下这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象

Optional<T> 类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用null 表示一个值不存在,现在Optional 可以更好的表达这个概念。并且可以避免空指针异常。
常用方法:

  • Optional.empty(): 创建一个空的Optional 实例
  • Optional.of(T t) : 创建一个Optional 实例
  • Optional.ofNullable(T t):若t 不为null,创建Optional 实例,否则创建空实例
  • isPresent(): 判断是否包含值
  • T get(): 如果调用对象包含值,返回该值,否则抛异常
  • orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
  • orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回s 获取的值
  • map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
  • flatMap(Function mapper):与map 类似,要求返回值必须是Optional

6-1 Lambda表达式

为什么使用Lambda 表达式

  • Lambda 是一个匿名函数,我们可以把Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

Lambda 表达式

  • 从匿名类到Lambda的转换示例1
//匿名内部类
Runnable r1 = new Runnable(){
	@Override
	public void run(){
		System.out.println("Hello World")
		}
	};
	---------
	//Lambda 表达式
	Runnable r1 = () -> System.out.println("Hello World");
  • 从匿名类到Lambda的转换示例2
//原来使用匿名内部类作为参数传递
TreeSet<String> ts = new TreeSet<>(new Comparator<String>(){
	@Override
	public int compare(String 01,String 02){
		return Integer.compare(01.length(), 02.length()
		}
	});
	----------
	//Lambda 表达式作为参数传递
	TreeSet<String> ts2 =new TreeSet<>(
	(01,02)->Integer.compare(01.length(), 02.length())
);

Lambda 表达式语法

Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为“->”,该操作符被称为Lambda 操作符或箭头操作符。它将Lambda 分为两个部分:
左侧:指定了Lambda 表达式需要的参数列表
右侧:指定了Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。

  • 语法格式一:无参,无返回值
    Runnable r1 =() ->{System.out.println("Hello Lambda");};
    
  • 语法格式二:Lambda 需要一个参数,但是没有返回值。
    Consumer<String> con = (String str) ->{System.out.println(str);};
    
  • 语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
    Consumer<String> con = ( str) ->{System.out.println(str);};
    
  • 语法格式四:Lambda 若只需要一个参数时参数的小括号可以省略
    	Consumer<String> con = str ->{System.out.println(str);};
    
  • 语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
    Comparator <Integer> com = (x,y) ->{System.out.println("实现函数式接口的方法");
    return Integer.compara(x,y);
    };
    
  • 语法格式六:当Lambda 体只有一条语句时,return 与大括号若有,都可以省略
    Comparator <Integer> com = (x,y) ->Integer.compara(x,y);
    

类型推断

上述Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为javac根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。

6-2 函数式接口

什么是函数式(Functional)接口

  • 只包含一个抽象方法的接口,称为函数式接口。
  • 你可以通过Lambda 表达式来创建该接口的对象。(若Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在一个接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口。同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
  • java.util.function包下定义了java 8 的丰富的函数式接口

如何理解函数式接口

Java从诞生日起就是一直倡导“一切皆对象”,在java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)

在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。

简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
所以以前用匿名内部类表示的现在都可以用Lambda表达式来写。

函数式接口举例

在这里插入图片描述

自定义函数式接口

在这里插入图片描述
函数式接口中使用泛型:
在这里插入图片描述

作为参数传递Lambda 表达式

在这里插入图片描述
作为参数传递Lambda 表达式:
在这里插入图片描述

作为参数传递Lambda 表达式:为了将Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该Lambda 表达式兼容的函数式接口的类型。

Java 内置四大核心函数式接口

函数式接口参数类型返回类型用途
Consumer消费型接口Tvoid对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier 供给型接口T返回类型为T的对象,包含方法:T get()
Function<T, R> 函数型接口TR对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
Predicate 断定型接口Tboolean确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法 boolean test(T t)

其他接口

函数式接口参数类型返回类型用途
BiFunction<T,U,R>T, UR对类型为T,U参数应用操作,返回R类型的结果。包含方法为 Rapply(Tt,Uu);
UnaryOperator (Function子接口)TT对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为 Tapply(Tt);
BinaryOperator (BiFunction子接口)T, TT对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为 Tapply(Tt1,Tt2);
BiConsumer<T,U>T, Uvoid对类型为T,U参数应用操作。包含方法为 voidaccept(Tt,Uu)
BiPredicate<T,U>T,Uboolean包含方法为booleantest(Tt,Uu)
ToIntFunction,ToLongFunction,ToDoubleFunctionTint,long,double分别计算int、long、double、值的函数
IntFunction,LongFunction,DoubleFunctionint,long,doubleR参数分别为int、long、double类型的函数

6-3 方法引用与构造器引用

方法引用(Method References)

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

  • 方法引用就是Lambda表达式,就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。

  • 要求:实现抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!

  • 方法引用:使用操作符“::” 将类(或对象) 与方法名分隔开来。

  • 如下三种主要使用情况

    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名

方法引用

例如

Consumer <String> con = (x) -> System .out.println(x);
//等同于
Consumer <String> con2 = System.out::println;

例如

Comparator <Integer> com = (x,y) -> Integer.compare(x,y);
//等同于
Comparator <Integer> com1 =Integer::compare;
int value = com.compare(12,32)

例如

BiPredicate <String ,String > bp = (x,y)->x.equals(y);
//等同于
BiPredicate <String,String> bp1 = String::equals;
boolean flag = bp1.test("hello" , "hi");

注意:当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName

构造器引用

格式:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。
可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象
举例

Function <Integer ,MyClass> fun =(n) -> new MyClass(n);
//等同于
Function <Integer ,MyClass> fun =MyClass::new;

7 -强大的Stream API

Stream API说明

Java8中有两大最为重要的改变。第一个是Lambda 表达式;另外一个则是Stream API

Stream API ( java.util.stream)把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

Stream 是Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL 执行的数据库查询。也可以使用Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

为什么要使用Stream API

实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要java层面去处理。

什么是Stream

Stream到底是什么呢?
数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,Stream讲的是计算!”
注意:

  1. Stream 自己不会存储元素。
  2. Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  3. Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream 的操作三个步骤

  1. 创建Stream
    一个数据源(如:集合、数组),获取一个流
  2. 中间操作
    一个中间操作链,对数据源的数据进行处理
  3. 终止操作(终端操作)
    一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
    在这里插入图片描述

创建Stream方式一:通过集合

Java8 中的Collection 接口被扩展,提供了两个获取流的方法:

  • default Stream<E> stream(): 返回一个顺序流

  • default Stream<E> parallelStream() : 返回一个并行流

创建Stream方式二:通过数组

Java8 中的Arrays 的静态方法stream() 可以获取数组流:
static <T> Stream<T> stream(T[] array): 返回一个流
重载形式,能够处理对应基本类型的数组:
- public static IntStream stream(int[] array)
- public static LongStream stream(long[] array)
- public static DoubleStream stream(double[] array)

创建Stream方式三:通过Stream的of()

可以调用Stream类静态方法of(), 通过显示值创建一个流。它可以接收任意数量的参数。
public static<T> Stream<T> of(T... values): 返回一个流

创建Stream方式四:创建无限流

可以使用静态方法Stream.iterate() 和Stream.generate(), 创建无限流。

  • 迭代
    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
  • 生成
    public static<T> Stream<T> generate(Supplier<T> s)

Stream 的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
1-筛选与切片

方法描述
filter(Predicatep)接收Lambda ,从流中排除某些元素
distinct()筛选,通过流所生成元素的hashCode() 和equals() 去除重复元素
limit(long maxSize)截断流,使其元素不超过给定数量
skip(long n)跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足n 个,则返回一个空流。与limit(n) 互补

2-映射

方法描述
map(Functionf)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

3-排序

方法描述
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparatorcom)产生一个新流,其中按比较器顺序排序

Stream 的终止操作

  • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void 。
  • 流进行了终止操作后,不能再次使用。
    1-匹配与查找
方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicatep)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparatorc)返回流中最大值
min(Comparatorc)返回流中最小值
forEach(Consumerc)内部迭代(使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)

2-归约

方法描述
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回Optional

备注:map 和reduce 的连接通常称为map-reduce 模式,因Google 用它来进行网络搜索而出名。

3-收集

方法描述
collect(Collector c)将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。

另外,Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方法返回类型作用示例
toListList把流中元素收集到ListListemps=list.stream().collect(Collectors.toList());
toSetSet把流中元素收集到SetSetemps=list.stream().collect(Collectors.toSet());
toCollectionCollection把流中元素收集到创建的集合Collectionemps=list.stream().collect(Collectors.toCollection(ArrayList::new));
countingLong计算流中元素的个数longcount=list.stream().collect(Collectors.counting());
summingIntInteger对流中元素的整数属性求和inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingIntDouble计算流中元素Integer属性的平均值doubleavg=list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingIntIntSummaryStatistics收集流中Integer属性的统计值。如:平均值intSummaryStatisticsiss=list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joiningString连接流中每个字符串String str=list.stream().map(Employee::getName).collect(Collectors.joining());
maxByOptional根据比较器选择最大值Optionalmax= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minByOptional根据比较器选择最小值Optional min=list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing归约产生的类型从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThen转换函数返回的类型包裹另一个收集器,对其结果转换函数int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingByMap<K, List>根据某属性值对流分组,属性为K,结果为VMap<Emp.Status, List> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningByMap<Boolean, List>根据true或false进行分区Map<Boolean,List> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值