Java8新特性
一、概述
于2014年发布,支持
- lambda表达式
- 函数式接口
- 方法引用
- Stream API
- 新时间API
二、lambda表达式
是特殊的匿名内部类,只是语法更简洁。只能简化接口中只有一个抽象方法的匿名内部类
定义一个接口:
@FunctionLInterface //可以不加,但是如果加上此注解必须是函数式接口,里面
public interface MyInterface {
void say(String name);
}
通过匿名内部类的方式,创建对象,并调用该方法:
public class TestMain1 {
public static void main(String[] args) {
MyInterface m = new MyInterface() {
@Override
public void say(String name) {
System.out.println(name);
}
};
m.say("zhangsan");
}
}
//如果是无返回值的话,最终形式
//如果只有一个参数,参数外的小括号可以不加,无参数不可省略小括号
//如果方法体只有一行,大括号可以不加
public class TestMain1 {
public static void main(String[] args) {
MyInterface m = name -> System.out.println(name);
m.say("zhangsan");
}
}
//有返回值最终形式
//如果方法有返回值,且代码只有一行,可以简化去掉大括号,但是需要去掉return关键字。
public class TestMain2 {
public static void main(String[] args) {
MyInterface2 m = () -> "Hello";
System.out.println(m.m1());
}
}
//示例,对一个数组进行升序排列,用lambda表达式简化
public static void main(String[] args) {
Integer[] age = {23,53,24,66,22,78,56,87,43};
/*
Arrays.sort(age);
for (Integer integer : age) {
System.out.print(integer+" ");
}
sort默认的是升序的
*/
/*
Arrays.sort(age, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2 ;
}
}).;
指定排序按照降序,简化之后如下
*/
Arrays.sort(age, ( o1, o2) -> o2-o1 );
System.out.println(Arrays.toString(age));
// [87, 78, 66, 56, 53, 43, 24, 23, 22]
}
将上面的匿名内部类简化为:
public class TestMain1 {
public static void main(String[] args) {
MyInterface m = (String name) -> {
System.out.println(name);
};
m.say("zhangsan");
}
}
- 参数列表的类型会自动推断。
上面的代码可以简化为:
public class TestMain1 {
public static void main(String[] args) {
MyInterface m = (name) -> {
System.out.println(name);
};
m.say("zhangsan");
}
}
- 如果参数列表为空,需要保留小括号。
- 如果参数列表有参,且只有一个参数,那么小括号可以去掉。
public class TestMain1 {
public static void main(String[] args) {
MyInterface m = name -> {
System.out.println(name);
};
m.say("zhangsan");
}
}
- 如果方法没有返回值,且代码只有一行,大括号可以省略。
public class TestMain1 {
public static void main(String[] args) {
MyInterface m = name -> System.out.println(name);
m.say("zhangsan");
}
}
如果有返回值, 代码变成lambda表达式简化过程,如下:
public interface MyInterface2 {
String m1();
}
public class TestMain2 {
public static void main(String[] args) {
MyInterface2 m = new MyInterface2() {
@Override
public String m1() {
return "Hello";
}
};
System.out.println(m.m1());
}
}
简化后:
public class TestMain2 {
public static void main(String[] args) {
MyInterface2 m = () -> {return "Hello";};
System.out.println(m.m1());
}
}
简化成一行:
- 如果方法有返回值,且代码只有一行,可以简化去掉大括号,但是需要去掉return关键字。
public class TestMain2 {
public static void main(String[] args) {
MyInterface2 m = () -> "Hello";
System.out.println(m.m1());
}
}
- lambda表达式不会单独生成一个内部类的文件,
三、函数式接口
3.1 函数式接口的定义
上面的lambda表达式的使用需要接口中只有一个方法,如果接口中有多个方法,则无法使用,如果在定义接口时,只定义了一个方法,并且使用了lambda表达式,但是如果后面扩展时添加了其他抽象方法,会导致之前使用的lambda表达式报错,为了解决此问题,使用了函数式接口的定义。
语法:
@FunctionalInterface
public interface MyInterface2 {
String m1();
}
此时,如果在该接口中定义多个抽象方法,会报错。
3.2 常用的函数式接口
- 消费型接口Cosumer,方法的特点是有参T,无返回值,主要作用是对该参数进行处理。
- 供给型接口Supplier,方法的特点是无参,返回一个T对象,主要作用是用来创建对象。
- 函数型接口Fuction
,方法的特点是有参T,返回一个R对象。 - 断言型接口Predicate,方法的特点是有参T,返回一个判断结果boolean值。
四、方法引用
4.1 概念和分类
是lambda表达式的简写,如果lambda表达式中只是调用一个特定的方法,则可以使用方法引用。
方法引用有4种形式:
对象::实例方法
类::静态方法
类::实例方法
类::new
注意:方法引用实际上类似于c语言中的方法指针。
4.2 对象::实例方法
当函数式接口中的方法定义与类中的实例方法定义基本一致(返回值和参数列表),此时,可以直接作为方法的引用,表示使用该类中的实例方法来作为接口方法的实现。
public interface MyInterface {
void say(String name);
}
public class MyClass1 {
public void a(String b) {
String str = "hello, " + b;
System.out.println(str);
}
}
上面类中定义的a方法,与接口中定义的say方法,返回值类型与参数列表几乎一致,可以作为方法引用。
public class TestMain3 {
public static void main(String[] args) {
MyClass1 c = new MyClass1();
// 接口中的方法引用对象中的方法
MyInterface m = c::a;
// 调用接口中的方法
m.say("张三");
// 接口中的方法与系统提供的println方法也一致,也可以引用
MyInterface m1 = System.out::println;
m1.say("hello, world");
}
}
注意:返回值类型是否可以不一致?接口中返回值类型必须大于引用的方法类型。
public interface MyInterface {
double say(String name);
}
public class MyClass1 {
public int a(String b) {
String str = "hello, " + b;
System.out.println(str);
return 1;
}
}
注意:返回值类型是否可以不一致?接口中返回值类型为void时,引用的方法类型可以有返回值。
public interface MyInterface {
void say(String name);
}
public class MyClass1 {
public int a(String b) {
String str = "hello, " + b;
System.out.println(str);
return 1;
}
}
4.3 类::静态方法
当函数式接口中的方法定义与类中的静态方法定义基本一致(返回值和参数列表),此时,可以直接作为方法的引用,表示使用该类中的静态方法来作为接口方法的实现。
public interface MyInterface {
void say(String name);
}
public class MyClass1 {
public static void m1(String b) {
System.out.println(b);
}
}
public class TestMain3 {
public static void main(String[] args) {
MyInterface m2 = MyClass1::m1;
m2.say("aaa");
}
}
4.4 类::new
用来创建对象。可以使用系统定义供给型接口。
@FunctionalInterface
public interface MyInterface3<T> {
T get();
}
public class TestMain4 {
public static void main(String[] args) {
MyInterface3<MyClass1> m = MyClass1::new;
MyClass1 c = m.get();
System.out.println(c);
// 使用系统提供的供给型接口
Supplier<MyClass1> s = MyClass1::new;
MyClass1 c1 = s.get();
System.out.println(c1);
// 消费型接口的使用
Consumer<String> con = System.out::println;
con.accept("hello, world");
// 断言型接口的使用,返回boolean值
String str = "hello";
Predicate<String> p = str::equals;
System.out.println(p.test("Hello"));
}
}
4.5 类::实例方法
类::实例方法,是表示可以使用方法的引用去引用类中的实例方法,需要注意虽然是用类来::实例方法,但是还是需要使用对象才能调用该方法,所以在使用接口去引用时,接口中的方法必须要传入类的对象。意味着接口会比引用的方法要多一个参数(该对象类型的参数)。
public interface MyInterface {
void say(String name);
}
public class TestMain5 {
public static void main(String[] args) {
MyInterface m = String::length;
m.say("hello");
}
}
自定义的案例:
public interface MyInter<T> {
void m1(T t);
}
public class MyClass {
public void test() {
System.out.println("hello, world");
}
}
public class TestMain {
public static void main(String[] args) {
MyInter<MyClass> m1 = MyClass::test;
m1.m1(new MyClass());
}
}
自定义的案例2:
public interface MyInter1<T> {
void m1(T t, String str);
}
public class MyClass {
public void test1(String str) {
System.out.println(str);
}
}
public class TestMain {
public static void main(String[] args) {
MyInter1<MyClass> m2 = MyClass::test1;
m2.m1(new MyClass(), "aaaaa");
}
}
比较字符串是否相等:
public class TestMain1 {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
s1.equals(s2);
// 在比较字符串前,需要先创建一个字符串,意味着只能比较hello与另一个字符串
Predicate<String> p1 = s1::equals;
p1.test(s2);
// 在比较字符串前,不需要先创建任何字符串,直到调用时才传入两个字符串,灵活性更强
BiPredicate<String, String> p2 = String::equals;
p2.test(s1, s2);
}
}
五、StreamAPI
5.1 作用和目的
用于简化集合和数组操作的API;
使用步骤:
1、创建流,分为数组和集合
2、中间操作,可以使用链式操作
3、终止操作,一旦终止操作结束,流就关闭了
创建流.中间操作.中间操作....终止操作
5.2 创建流
// [集合创建流] 先创建一个集合,然后调用Steam()方法,返回就是一个流对象 *
/**Map集合获取流*/
HashMap<String, Integer> maps = new HashMap<>();
Stream<String> keyStream = maps.keySet().stream();
Stream<Integer> stream = maps.values().stream();
//键值对流
Stream<Map.Entry<String, Integer>> entryStream = maps.entrySet().stream();
// [数组创建流] 两种方法
String[] names={"张三","李四","王五","赵六","田七"};
Stream<String> stream1 = Arrays.stream(names);
Stream<String> stream2 = Stream.of(names);
public class TestMain1 {
public static void main(String[] args) {
Stream<Integer> s1 = Stream.iterate(1, x -> x + 1); // 后面的数字比前面的数字的变化
Stream.iterate(1, x -> {
// 可以加一些判断条件
return x + 1;
});
Stream<Integer> s2 = Stream.of(12,35,78,43,22,15); // 列举,得到一组数据的流
Stream<Integer> s3 = Stream.generate(new Random()::nextInt); // 得到一组随机数操作
String [] arr = {"hello", "aaa", "bbb", "ccc"};
Stream<String> s4 = Arrays.stream(arr); // 将数组变成流操作
ArrayList<String> list = new ArrayList<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
Stream<String> s5 = list.stream(); // 将集合变成流,单线程操作
Stream<String> s6 = list.parallelStream();// 将集合变成流,多线程操作
LongStream s7 = LongStream.range(1, 100); // 得到1到100(不包含100)的流
LongStream s8 = LongStream.rangeClosed(1, 100); // 得到1到100(包含100)的流
Stream.iterate(1, x ->x + 1) // 创建一个流操作,每个数字比前面大1,第一个数字是1
.limit(100) // 得到100个数字,中间操作
.forEach(System.out::println); // 循环打印,终止操作
}
}
5.3 中间操作
常用的API
limit:表示长度限制
filter:过滤、筛选
skip:跳过多少个元素
distinct:去掉重复的元素
sorted:排序
map:映射,迭代对每一个元素进行操作
parallel:使用多线程操作
public class TestMain2 {
public static void main(String[] args) {
// 得到10个随机数
Stream.generate(Math::random)
.limit(10)
.forEach(System.out::println);
Stream.iterate(1, x -> x + 1)
.limit(100)
.skip(20) // 跳过20个数字
.filter(t -> t % 2 == 0) // 筛选所有的偶数
.forEach(System.out::println);
Stream.of(12,34,34,23,15,88,12,15)
.distinct() // 去掉重复的元素
.map(x -> x * 2) // 映射,循环每一个元素进行处理
// .parallel() // 使用多线程操作,对排序结果有影响
.sorted() // 排序
.forEach(System.out::println);
}
}
5.4 终止操作
forEach:循环
min:最小值
max:最大值
count:计数
reduce:归约,可以用来求和等操作
collect:收集
public class TestMain3 {
public static void main(String[] args) {
System.out.println(
Stream.of(12,34,34,23,15,88,12,15)
.min((o1, o2) -> o1 - o2) // 求最小值
.get() // 得到值
);
long count = Stream.iterate(1, x -> x + 1)
.limit(100)
.filter(x -> x % 2 == 0)
.count();
System.out.println(count);
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("zhangsan1", 20, "男"));
list.add(new Student("zhangsan2", 21, "女"));
list.add(new Student("zhangsan3", 22, "男"));
list.add(new Student("zhangsan4", 23, "女"));
list.add(new Student("zhangsan5", 24, "男"));
list.add(new Student("zhangsan6", 25, "女"));
list.add(new Student("zhangsan7", 26, "男"));
long count2 = list.stream()
.filter(x->x.getAge() > 22).count();
System.out.println(count2);
Integer integer = Stream.iterate(1, x -> x + 1)
.limit(100)
.filter(x -> x % 2 == 0)
.reduce(0, (a, b) -> a + b); // 归约,此处用来求和
System.out.println(integer);
Integer ages = list.stream()
.filter(x->x.getAge() > 22 && x.getSex().equals("男")) // 筛选出年龄大于22并且男性
.map(Student::getAge) // 只要年龄属性构成一个新的流
.reduce(0, Integer::sum); // 求和
System.out.println(ages);
List<Integer> list2 = Stream.of(12,34,34,23,15,88,12,15)
.distinct() // 去掉重复的元素
.sorted()
.collect(Collectors.toList()); // 将结果收集为一个集合
System.out.println(list2);
List<Student> list3 = list.stream()
.filter(x->x.getAge()>22 && x.getSex().equals("男"))
.collect(Collectors.toList());
System.out.println(list3);
// 分组统计数量
System.out.println(list.stream()
.filter(x->x.getAge()>22)
// 将男性和女性分别计数
.collect(Collectors.groupingBy(Student::getSex, Collectors.counting())));
}
}
六、新时间API
public class TestMain4 {
public static void main(String[] args) {
LocalDate now = LocalDate.now(); // 得到当前时间
LocalDate date = LocalDate.of(2014, 3, 20); // 根据年月日创建当前时间
System.out.println(date);
int year = date.getYear();// 得到年
// LocalDate得到日期
// LocalTime得到时间
// LocalDateTime得到日期时间
// 时间戳
Instant now2 = Instant.now();
// 打印的格式是:2021-07-29T09:12:07.666Z
System.out.println(now2);
// 得到当前默认时区
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);
// 格式化工具
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd");
System.out.println(pattern);
// 格式化字符串
LocalDate date2 = LocalDate.parse("2020-02-12", pattern);
System.out.println(date2.getYear());
}
}