Java 8新增的特性主要包含以下几个新的语法:
下面分别学习Java 8的新特性。
1、什么是lambda表达式
Lambda其实就是一个匿名函数,Lambda允许将函数作为一个方法的参数,传递进方法当中,我们就把Lambda表达式理解为一个可以传递的代码,这样写出来的代码,就可以做到简洁、灵活。
下面看一个例子,对员工按照年龄进行排序,我们在没学Lambda之前,可以这样做。
@Test
public void test4(){
List lists= Arrays.asList(
new Employee("张三",18,5555.55),
new Employee("李四",20,6666.66),
new Employee("王二",5,10000.55),
new Employee("麻子",40,50000.55));
Collections.sort(lists, new Comparator() {
@Override
public int compare(Employee o1, Employee o2) {
return Integer.compare(o1.getAge(),o2.getAge());
}
});
for(Employee employee:lists){
System.out.println(employee);
}
}
运行结果:
List lists= Arrays.asList(
new Employee("张三",18,5555.55),
new Employee("李四",20,6666.66),
new Employee("王二",5,10000.55),
new Employee("麻子",40,50000.55));
Collections.sort(lists,(x,y) -> Integer.compare(x.getAge(),y.getAge()));
for(Employee employee:lists){
System.out.println(employee);
}
同样能够做到按照年龄排序,从Lambda表达式我们可以看出,Lambda表达式替代了Comparator匿名内部类的位子,作为了一个可以传递数据的代码段,是不是很简洁,很高效,下面来了解一下Lambda表达式的语法规则。
2、Lambda表达式的语法规则
Lambda表达式在1.8版本中引入了新的语法规则,包括元素与“->”操作符,“->”操作符将Lambda分为两个部分:
语法格式一:无参,没有返回值,并且Lambda只含有一行代码;
Runnable runnable=() -> System.out.print("Hello Lambda");
语法格式二:只含有一个参数,没有返回值;当只有一个参数时,()也可以省略。
Consumer
consumer=(x) -> System.out.print(x); 当然也可以这样写Consumer
consumer=x -> System.out.print(x);
语法格式三:多个参数时,并且有返回值,当Lambda代码多于两行时,别忘了加{}哦。
BinaryOperator
bo=(x,y)->{ System.out.println("这是实现函数式接口的方法"); return x+y; };
不知道大家有没有发现,Lambda表达式中的代码实现,其实就是实现了函数式接口中的方法。什么是函数式接口?稍后进行学习。
语法格式四:当Lambda只有一条语句时,return与大括号可以省略
BinaryOperator
bo=(x,y)->return x+y;
语法格式五:Lambda表达式中的参数类型,可以省略不写,编译器可以根据上下文进行推断得出。
BinaryOperator
bo=(x,y)->{ System.out.println("这是实现函数式接口的方法"); return x+y; }; 当然也可以这样写
BinaryOperator
bo=(Integer x,Integer y)->{ System.out.println("这是实现函数式接口的方法"); return x+y; };
上述的Lambda表达式中的参数类型都是由编译器推断得出的。Lambda表达式中无需指定类型,程序依然可以编译,这是因为javac根据程序的上下文,在后台推断出了参数的类型。Lambda表达式的类型依赖上下文环境,是由编译器推断出来的。
上面我们接触到了函数式接口这个名词,实际上,上面讲的Lambda表达式需要函数式接口的支持,也就是Lambda表达式的对函数式接口中的方法进行了实现。下面了解一下函数式接口。
什么是函数式接口
从上面,我们可以看出,Lumbda表达式作为参数进行传递,为了将Lambda表达式作为参数传递,接受参数类型必须与Lambda表达式兼容的函数式接口的类型。
四大核心内置函数:
函数式接口 | 参数类型 | 返回类型 | 用途 |
Consumer 消费型接口 |
T | void | 对类型T参数操作,无返回结果,包含方法void accept(T t) |
Supplier 供给型接口 |
无 | T | 返回类型为T的对象,包含方法:T get() |
Function 函数型接口 |
T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象,包含方法 R apply(T t) |
Predicate 断定型接口 |
T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean值,包含方法boolean test(T t) |
内置函数的使用:
/**
* 四大内置函数
* 1、void Consumer 消费型接口
* 2、T Supplier 供给型接口
* 3、Function 函数型接口
* 4、boolean Predicate断言型接口
*
*/
@Test
public void test1(){
double price=10000.0;
consumer(price,(p) -> System.out.print("一共消费了:"+p+"元"));
}
public void consumer(double price, Consumer consumer){
consumer.accept(price);
}
@Test//利用供给型接口随机生成5个大于50小于100的数
public void test2(){
int num=5;
List list=(List)supplier(num,()-> {
return (int)(Math.random()*50+50);
});
list.forEach(System.out::println);
}
public T supplier(int num, Supplier supplier ){
List list=new ArrayList<>();
for(int i=0;i ",(str) -> str.trim());
System.out.print(s);
}
public String strHandle(String str, Function function){
return function.apply(str);
}
@Test//断言式接口 将字符个数大于3的字符串输出
public void test4(){
List list= Arrays.asList("asdbjkfd","shdfk","dfds","a","v");
List newList=filterString(list,(str)-> str.length()>3 );
newList.forEach(System.out::println);
}
public List filterString(List list, Predicate predicate){
List newList=new ArrayList<>();
for(String str:list){
if(predicate.test(str)){
newList.add(str);
}
}
return newList;
}
四大函数式接口,当然还有其他的函数接口类型,他们的设计都是为Lambda表达式服务的,这些内置的函数,涵盖了我们常用的类型,即,消费型接口Consumer
在java 1.8中新增的两个重要改变分别是:Lambda表达式;Stream API。Stream是Java8中处理集合非常厉害的工具,它可以对集合进行查找、过滤、映射等数据操作。Stream的出现使得对数据源的操作更加高效。Stream是操作数据源(集合、数组等)所生成的元素序列,它不会存储数据,而是对数据进行操作,当然这个操作并不会改变源数组,相反,会返回一个操作后的一个结果Stream。Stream操作的特点是延迟执行。这意味着他们会等到需要结果的时候才会执行,也叫做“惰性求值”。
从上图中不难发现,主要分为3步
当然,在操作Stream之前,我们需要做的就是先获取Stream。获取Stream主要有以下几种方式:
获取Stream方式 在java8中的Collection接口被扩展,提供了两个获取流的方法: list.stream();//通过创建串行流 list.parallelStream();//创建并行流 当然Java8的Arrays的静态方法stream()也可以获得流 Arrays.stream(T[] array);//java8的Arrays的静态方法stream可以获取数组流 可以通过静态方法获取流 Stream.of(T...values)//通过Stream.of方式创建流 由函数创建流 Stream.interate()和Stream.generate()创建无限流
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何处理。而只会在终止操作时一次性全部处理,称为“惰性求值”。
方法 | 描述 |
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素 |
filter(Predicate p) | 接收Lambda表达式,从流中过滤出某些元素 |
distinct() | 筛选,通过流生成元素的hashCode()和equals()去除重复元素 |
limit(long maxSize) | 截断流,使元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每一个值都换成另一个流,然后把所有流连接成一个流。 |
sorted() | 产生一个新流,其中按自然顺序排序。 |
sorted(Comparator com) | 产生一个新流,其中按自定义比较强顺序排序 |
@Test
public void test1(){
Stream stream=lists.stream()
.filter(emp -> emp.getSalary()>6000)//筛选出工资大于6000的员工
.distinct()//元素去重
.limit(2)//使元素不超过2个
.skip(2);//跳过2个元素
stream.forEach(System.out::println);
Stream stream1=lists.stream()
.map(e -> e.getName())//将每一个Employee对象的姓名替换原来的Employee
.sorted()//自然排序
.distinct();
stream1.forEach(System.out::println);
}
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void。
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例。
@Test
public void test2(){
//返回流中元素总数
long num=lists.stream()
.count();
System.out.println(num);
//返回流中最大值
Optional maxSalary=lists.stream()
.map(Employee::getSalary)
.max(Double::compareTo);
System.out.println(maxSalary.get());
//是否匹配所有元素
boolean bool=lists.stream()
.allMatch(e -> e.getName().equals("张三"));
System.out.println(bool);
//是否至少匹配一个元素
boolean bool2=lists.stream()
.anyMatch(e -> e.getName().equals("张三"));
System.out.println(bool2);
//检查是否没有匹配到所有元素
boolean bool3=lists.stream()
.noneMatch(e->e.getName().equals("张三"));
System.out.println(bool3);
//将Employee映射为Employee的名字,并且迭代输出
lists.stream()
.map(Employee::getName)
.forEach(System.out::println);
//利用归约,求出所有员工的工资之和
Double salarySum=lists.stream()
.map(Employee::getSalary)
.reduce(0.0,(x,y)-> x+y);
System.out.println(salarySum);
//第二种,利用reduce规约,求出所有员工工资之和,Optional容器的出现,是为了解决空指针异常
Optional salarySum2=lists.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(salarySum2.get());
//收集的功能很强大,该函数需要一个Collector接口,用来实现对流执行收集操作,例如收集到List、Set、Map中去。
//并且Collectors工具类提供了很多静态方法,能够方便的创建常见地方收集器,需要的话,自行查询API文档
HashSet set=lists.stream()
.collect(Collectors.toCollection(HashSet::new));
System.out.println(set);
}
Java8中针对接口又增加了一个新的改进。允许接口中具有具体实现的方法,那么我们就称这个方法为“默认方法”,默认方法的关键字使用default关键字修饰。例如:
public interface MyInterface {
T fun(int a);
default String getName(){
return "Hello Java8!";
}
}
当然接口中默认的方法,有以下两点需要注意的地方,也就是需要遵循的原则,若一个接口中定义了默认方法,而另外一个父类或者接口中又定义了一个同名的方法时,需要注意以下两点:
public interface MyInterface {
default String getName(){
return "Hello Java8!";
}
}
public class MyClass {
public String getName(){
return "Hello,I am a Class";
}
}
public class TestClass extends MyClass implements MyInterface{
}
@Test
public void test3(){
//当一个类继承一个父类,并且实现一个接口是,
// 这个父类和这个接口中的默认方法,方法名和参数列表都相同,这样的话,这个类就会继承父类
//中的这个方法,而接口中的默认方法就会被忽略
TestClass testClass=new TestClass();
String name=testClass.getName();
System.out.println(name);//输出结果 Hello,I am a Class
}
public interface MyInterface1 {
default String getName(){
return "I am a interface1";
}
}
public interface MyInterface2 {
default String getName(){
return "I am a interface2";
}
}
public class TestClass implements MyInterface1,MyInterface2 {
@Override//覆盖了MyInterface1中的方法
public String getName() {
return MyInterface1.super.getName();
}
}
在Java8中,接口允许添加静态方法,这样我们使用接口中方法就可以直接使用接口名.静态方法名调用接口中的方法。
public interface MyInterface1 {
default String getName(){
return "I am a interface1";
}
static void show(){
System.out.println("balabalbal");
}
}
Java1.8对日期改动特别大,之前很多API都被废弃,并且在1.8中引入了许多新的API。这是因为之前的日期类型的API都是线程不安全的,还需要动手封装,才能获得线程安全的时间的工具包。Jdk1.8中主要新增了以下几个包:
- java.time包:这是基础包,这里包含了我们常用的基础类,例如LocalDate、LocalTime、LocalDateTime。这些类都是不可变且线程安全的。
- java.time.chrono包:这个包为非ISO的日历系统定义了一些API,我们可以借助这个包扩展我们自己的日历系统。
- java.time.format包:这个包是用来格式化和解析时间对象的,一般,java.time包下的类就基本满足要求。
- java.time.temporal包:封装了获取特定日期和时间的接口,例如,获取某月的第一天或最后一天。
- java.time.zone包:这个包包含了与时区相关的类。
LocalDate
java.time.LocalDate这个类,是用来表示日期的,也仅包含日期。
@Test
public void test1() {
//获取当前日期(年月日)
LocalDate now = LocalDate.now();
System.out.println(now);
//根据年月日构建日期
LocalDate of = LocalDate.of(2018, 12, 26);
System.out.println(of);
//字符串转化日期,默认按照yyyy-MM-dd格式
LocalDate parse = LocalDate.parse("2018-12-26");
System.out.println(parse);
//获取本月的第一天
LocalDate firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
System.out.println(firstDayOfMonth);
//获取本月的最后一天
LocalDate lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth());
System.out.println(lastDayOfMonth);
//获取本月第5天
LocalDate dayOfMonth = now.withDayOfMonth(5);
System.out.println(dayOfMonth);
//明天
LocalDate tommorrowDay = now.plusDays(1L);
System.out.println(tommorrowDay);
//昨天
LocalDate yesterday = now.minusDays(1L);
System.out.println(yesterday);
//计算两个日期时间的天数
long days = now.until(lastDayOfMonth, ChronoUnit.DAYS);
System.out.println(days);
}
LocalTime
与LocalDate相同,LocalTime与LocalDate都在相同的包下,表示的时间,不包含日期。
public void test2(){
//获取当前时间
LocalTime localTime = LocalTime.now();
//构建时间
LocalTime localTime1 = LocalTime.of(14, 24, 50);
LocalTime localTime2 = LocalTime.parse("11:11:11");
//分别获取时分秒
localTime.getHour();
localTime.getMinute();
localTime.getSecond();
//根据指定单位,计算到另一个时间的时间量
long until = localTime.until(localTime1, ChronoUnit.HOURS);
System.out.println(until);
}
LocalDateTime
既包含日期,又包含时间,经常和DateTimeFormatter一起使用。
@Test
public void test3(){
//获取年月日,时分秒
LocalDateTime localDateTime = LocalDateTime.now();
//构建日期时间
LocalDateTime.of(LocalDate.now(),LocalTime.now());
//格式化当前时间
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
System.out.println(dateTimeFormatter.format(localDateTime));
}
LocalDate
,LocalTime
,LocalDateTime
这三个类基本上处理了大部分的日期,时间。而与这三个类经常结合使用的还有如下几个类:Year
,Month
,YearMonth
,MonthDay
,DayOfWeek
。- 并且,我们在LocalDate相关类的操作也可以通过Year,Month等来实现。
TemporalAdjusters
该类是一个计算类,提供了各种计算方法。例如获取某一天,某个月,第一天等等。该内部类基本上是通过JDK8的Lambda实现的
@Test
public void test4(){
LocalTime localTime = LocalTime.now();
//TemporalAdjuster:时间校正器。
//TemporalAdjusters:该类内部封装了大量的静态方法,TemporalAdjuster的实现。
//获取本月第一天
localTime.with(TemporalAdjusters.firstDayOfMonth());
//获取本月最后一天
localTime.with(TemporalAdjusters.lastDayOfMonth());
//获取本年第一天
localTime.with(TemporalAdjusters.firstDayOfYear());
//本年最后一天
localTime.with(TemporalAdjusters.lastDayOfYear());
//下一个周末
localTime.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
}
Duration和Period
Period是基于ISO-8601标准的日期方式,用于计算两个日期间的年,月,日的差值。而Duration计算的是两个时间之间的差值,是一种更精确的计算方式。
@Test
public void test5(){
//Duration:计算两个“时间”之间的间隔
//Period:计算两个“日期”之间的间隔
LocalTime now1 = LocalTime.now();
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
LocalTime now2 = LocalTime.now();
Duration duration = Duration.between(now1, now2);
//两个时间之间间隔的毫秒数
System.out.println(duration.toMillis());
//计算两个日期之间的差值
LocalDate now3 = LocalDate.now();
LocalDate now4 = LocalDate.of(2017, 12, 11);
Period period = Period.between(now4, now3);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
}
DateTimeFormatter
该类主要用来对日期进行格式化的。
@Test
public void test6(){
//DateTimeFormatter提供了三种格式化的方式
//预定义的标准格式
//自定义模式
//语言环境相关
DateTimeFormatter format = DateTimeFormatter.ISO_DATE_TIME; //预定义的标准格式
LocalDateTime localDate = LocalDateTime.now();
String s=localDate.format(format);
System.out.println(s);
DateTimeFormatter format1= DateTimeFormatter.ofPattern("yyyy年MM月dd日");//自定义模式
String s2 = format1.format(LocalDateTime.now());
System.out.println(s2);
}
当然除了以上讲到的还有一些类,如Instant、ZoneId时区标识、ZoneOffset时区偏移量,与UTC的偏移量、ZoneDateTime与时区有关的日历系统、OffsetDateTime用于与UTC偏移量。
当然以上就是我对java1.8的学习很认识,有不足的话,还请大家多多提出。