简介
Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本,有自己的独特编程风格,使代码更简洁化。
编程风格
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Comparator;
public class Java8Tester {
public static void main(String args[]){
List<String> names1 = new ArrayList<String>();
names1.add("Google ");
names1.add("Runoob ");
names1.add("Taobao ");
names1.add("Baidu ");
names1.add("Sina ");
List<String> names2 = new ArrayList<String>();
names2.add("Google ");
names2.add("Runoob ");
names2.add("Taobao ");
names2.add("Baidu ");
names2.add("Sina ");
Java8Tester tester = new Java8Tester();
System.out.println("使用 Java 7 语法: ");
tester.sortUsingJava7(names1);
System.out.println("使用 Java 8 语法: ");
tester.sortUsingJava8(names2);
}
// 使用 java 7 排序
private void sortUsingJava7(List<String> names){
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
}
// 使用 java 8 排序
private void sortUsingJava8(List<String> names){
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
}
}
这是Java中的集合排序,使用Collections类提供的方法进行排序。从代码上就可以看出,Java 8的代码更简洁化。
Java 8 的新特性
1.Lambda 表达式
- Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
- Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
-
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
语法
语法格式:
(parameters) -> expression
或 (parameters) ->{ statements; }
语法特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
demo
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
代码
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
说明:
Lambda表达式就是可以把函数当成方法参数传输,如果按照Java7的写法实现上述代码的效果,可以用多个类实现一个MathOperation 接口,或者用一个base类,然后多个子类重写父类的方法。一比较很显然,Java 8 的Lambda表达式使代码更简洁,耦合度也降低了很多。其实上边描述Java 8 编码风格的例子就是Lambda表达式。
注意:
lambda 表达式的变量作用域问题,必须是外部局部final变量,如果是内部局部变量,可以不声明final,但是必须保证变量是不可变的,否则会报错。
2.方法引用
- 方法引用通过方法的名字来指向一个方法。
- 方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 方法引用使用一对冒号 ::
语法格式:
Class::method
俗语(个人理解):
原来调用方法需要通过 类名.方法名 java8可以 类名::方法名
例如:jdk自带转字符串的方法 String.ValueOf(ParmClass parmName);
java8写法:String::ValueOf;
代码
public class StringToDo {
public static String toDo(StringFun fun,String t){
return fun.stringFun(t);
}
}
配置函数式接口的通用调用
public class StringToDo {
public static String toDo(StringFun fun,String t){
return fun.stringFun(t);
}
}
String字符串的一些处理方法
public class StringUtil {
//去除 -
public static String replaceGang(String str){
return str.replaceAll("-", "");
}
//反转字符串
public static String strReverse(String str) {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i);
}
return result;
}
//md5加密
public static String md5(String str){
return MD5.MD5Encode(str, "utf-8");
}
}
demo
public class Java8Test {
public static void main(String[] args) {
String a = StringToDo.toDo(StringUtil::md5, "1-1-1");
System.out.println("a:"+a);
}
}
说明:
函数式接口后面会讲解,先无需关注。
方法引用就是改变了之前调用方法的一些方式,使代码更简洁。
这里可能有人问那我通过StringUtil.md5(参数)可以直接调用,干嘛要这么费劲,目前看来是这样,但是我在StringUtil里写的很多处理字符串的多种方法,后面还可再扩展,以后我处理的字符串情况只要在StringUtil里存在,那么我可以直接StringToDo.toDo(Class::方法名);扩展性高,封装的深, 同时也减少了很多不必要的内存对象的开销(因为有函数式接口的存在),如果单纯的使用Java8的方法引用,那么确实只是换了一种编码风格而已。
注意:
java8的方法引用并不是独立存在使用的,与Lambda 表达式、接口式函数使用时效果显著
3.接口式函数
- 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
- 函数式接口可以被隐式转换为 lambda 表达式。
语法格式:
@FunctionalInterface
public interface StringFun {
String stringFun(String str);
}
使用@FunctionalInterface注解表明接口式函数
代码:
@FunctionalInterface
public interface StringFun {
String stringFun(String str);
}
public class Java8Test {
public static void main(String[] args) {
StringFun fun = str ->{
return MD5.MD5Encode(str, "utf-8");
};
String md5str = fun.stringFun("passsword");
}
}
说明:
函数式接口可以把接口当成参数进行传参
例如我写的StringToDo.toDo(StringUtil::md5, "1-1-1"); toDo方法的第一个参数就是StringFun(函数式接口)。
这样达到直接实现StringFun中的方法 ,无需通过implements去实现接口
函数式接口的实现可以通过与Lambda表达式结合来实现
例如 StringFun fun = str -> MD5.MD5Encode(str, "utf-8");
-> 后面的就是具体的实现 然后调用 fun.stringFun()方法 ,执行MD5加密,但是这种实现只是写在了方法内,若是其他方法或者类想调用,仍需重新编写,所以这种写法还需根据各种应用场景进行定制开发。
4.默认方法
- 默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法
语法:
public interface DefaultInterface {
default void inPrint(){
System.out.println("接口输入");
}
}
在方法名前面加个 default 关键字即可实现默认方法
代码
接口默认方法
public interface DefaultInterface {
default void inPrint(){
System.out.println("接口输入");
}
default void outPrint(){
System.out.println("接口输出");
}
static void staticPrint(){
System.out.println("接口静态输入");
}
}
接口实现类
public class DefaultInterfaceImpl implements DefaultInterface{
public void inPrint(){
System.out.println("实现类输入");
DefaultInterface.super.inPrint();
DefaultInterface.super.outPrint();
DefaultInterface.staticPrint();
}
}
demo
public class Java8Test {
public static void main(String[] args) {
DefaultInterface defaultInterfaceImpl = new DefaultInterfaceImpl();
defaultInterfaceImpl.inPrint();
DefaultInterface.staticPrint();
}
}
说明:
默认方法就是可以将接口的实现方法直接在接口中体现,无需通过class类implements去实现接口
接口中可以写静态方法,直接通过Class.method进行调用。
如果实现类里重写了方法 ,可以通过Interface.super.method调用接口中的方法
5.Stream
- Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
- Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
- Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
语法格式:
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();
什么是 Stream?
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
Stream 中的方法
- forEach:与平时写的for(对象:名称 集合)类似
- filter:过滤数据,返回为true的List/数组
- limit:List分页
- sorted:List排序
- collectors:归集,将流进行转换/聚合
代码:
package test;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Java8Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12);
list.stream().skip(2*2).limit(2).sorted(Comparator.reverseOrder()).forEach(System.out::println);
Map<Integer, Integer> collect = list.stream().map(i -> i*i).collect(Collectors.toMap(a -> a, b -> b*b));
System.out.println(collect.toString());
}
}
说明:
Stream就是将之前一下繁琐的写法全部通过流的方式实现,并且增加了很多新功能,例如list转map,分页等,
Stream中的方法很多,在这里只介绍了常用的一些部分,并且很多方法可以一起结合使用,比如先排序后分页,或者先过滤数据然后将list转为map等等,省去了之前很多的if else写法,使代码更简洁高效。
6. Optional 类
- Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
- Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
- Optional 类的引入很好的解决空指针异常。
类方法
- isPresent:如果值存在则方法会返回true,否则返回 false。
- ofNullable:如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
- orElse:如果存在该值,返回值, 否则返回 other。
- get:如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
- map:如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
Optional类的方法有很多,这里主要展示一些常用的操作方法
代码
public class Java8Test {
public static void main(String[] args) {
Integer a = 1;
Optional<Integer> Opt = Optional.ofNullable(a);
Opt.isPresent();
Optional<Integer> map = Opt.map(as -> as+as);
Integer orElse = map.orElse(111);
System.out.println("--orElse--"+orElse);
}
}
说明:
Optional 类主要是对null数据进行处理,防止经常出现空指针异常
例如:之前判断是否为空 data.equals 如果data为空,会引发空指针,如果用Optional 类,isPresent方法可以有效解决
7.日期时间 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(本地) − 简化了日期时间的处理,没有时区的问题。
-
Zoned(时区) − 通过制定的时区处理日期时间。
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
代码
// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间: " + currentTime);
LocalDate date1 = currentTime.toLocalDate();
System.out.println("date1: " + date1);
Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();
System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println("date2: " + date2);
// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
// 解析字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
// 获取当前时间日期
ZonedDateTime date12 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date12: " + date1);
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println("当期时区: " + currentZone);
说明:
时间处理更简洁方便,parse也无需抛异常,如果有时间处理相关业务可以学习使用