文章目录
参考文章:放大招了,肝了一篇8万字的Java8新特性总结,赶快收藏
一、函数式接口
- 只包含一个抽象方法的接口,称为函数式接口
- 并且可以使用lambda表达式来创建该接口的对象,
- 可以在任意函数式接口上使用@FunctionalInterface注解,来检测它是否是符合函数式接口。
- 同时javac也会包含一条声明,说明这个接口是否符合函数式接口。
1. 自定义函数式接口
@FunctionalInterface
public interface MyNuber{
public double getValue();
}
2. 泛型函数式接口
@FunctionalInterface
public interface MyFunc<T>{
public T getValue(T t);
}
二、Java内置函数式接口
- 消费型接口 Consumer void accept(T t)
Consumer c = new Consumer<Student>() {
@Override
public void accept(Student student) {
System.out.println(student);
}
};
c.accept(new Student("张三",18));
//使用Lambda表达式实现
Consumer c1 = student->System.out.println(student);
c1.accept(new Student("李四",19));
- 供给型接口 Supplier T get()
Supplier<Student> s = new Supplier<Student>() {
@Override
public Student get() {
Student s = new Student("王五", 20);
return s;
}
};
//lambda表达式
Supplier<Student> s1=()->new Student("a",19);
Student student1 = s1.get();
System.out.println(student1);
- 函数型接口 Function<T,R> R apply(T t)
Function<Integer, Student> f = new Function<Integer, Student>() {
@Override
public Student apply(Integer id) {
Student s = Student.getByIdStudent(id);
return s;
};
Student a = f.apply(3);
System.out.println(a);
//使用lambda表达式实现
Function<Integer, Student> ss=id->Student.getByIdStudent(id);
Student apply = ss.apply(2);
System.out.println(apply);
- 断定型接口 Predicate boolean test(T t)
Predicate<Student> z = new Predicate<Student>() {
@Override
Student s = new Student(4, "zl", 18);
return student.age == s.age;
};
Student s1 = Student.getByIdStudent(2);
System.out.println(s1);
System.out.println(test);
//使用lambda表达式实现
Student s = new Student(4, "zl", 18);
Predicate<Student> z1=a->a.age==s.age;
Student s11 = new Student(5, "xx", 18);
boolean test1 = z1.test(s11);
System.out.println(test1);
三、接口内允许添加默认实现的方法
在下面这个接口中,我们除了定义了一个抽象方法 study,还定义了一个带有默认实现的方法 play。 我们在实现这个接口时,可以只需要实现 study方法,默认方法 play可以直接调用即可。
// 定义一个公式接口
interface school{
// 学习
void study();
// 做游戏
default string play(String name) {
return "我和"+ name + "一起做游戏";
}
}
School school = new School() {
@Override
public void study() {
system.out.println("学习中");
}
};
四、Lambda表达式
可以用lambda表达式创建函数式接口对象
(参数列表) -> { 方法体 }
- 参数列表的数据类型可以省略
- 参数列表只有一个参数,可以把()去掉
- 如果方法体只有一条语句,可以把 { } 和 return 去掉
- 无参、无返回值
public void test1(){
Runnable r1 = new Runnable(){
@Override
public void run(){
System.out.println("我爱北京天安门");
}
};
Runnable r2 = ()->{
System.out.println("我爱北京天安门");
};
}
- 需要一个参数,但是没有返回值;
@Test
public void test2(){
Consumer<String> con1 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s + 1);
}
};
Consumer<String> con2 = (String s)->{
System.out.println(s + 2);
}
//数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
Consumer<String> con3 = (s)->{
System.out.println(s + 3);
}
}
- Lambda 若只需要一个参数时,参数的小括号可以省略
@Test
public void test3(){
Consumer<String> con1 = (s) -> {
System.out.println(s);
};
Consumer<String> con2 = s -> {
System.out.println(s);
};
}
- Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
@Test
public void test4(){
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1 + o2);
System.out.println(o1);
}
};
Comparator<Integer> com2 = (o1,o2) -> {
System.out.println(o1 + o2);
return o1.compareTo(o2);
};
}
- 当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
@Test
public void test5(){
Comparator<Integer> com1 = (o1,o2) -> {
return o1.compareTo(o2);
};
Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
}
五、方法引用
应用场景:如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用,有下列四种情况:
- 类 :: 静态方法
- 类 :: 实例方法
- 类 :: 构造器
- 对象 :: 实例方法
- 数组:构造器
- 类 :: 静态方法
public static void main(String[] args) {
// 使用Lambda
Function<Integer, Integer> func = (i) -> Math.abs(i);
Integer i = func.apply(-10);
System.out.println(i);
// 使用方法引用
Function<Integer, Integer> func1 = Math::abs;
Integer i1 = func1.apply(-10);
System.out.println(i1);
}
- 构造器引用
public class MethodReferenece {
public static void main(String[] args) {
// Lambda方式
PersonBuilder pb = (name) -> new Person(name);
System.out.println(pb.buildPerson("zhangsan").getName());
// 方法引用
PersonBuilder pb1 = Person::new;
System.out.println(pb1.buildPerson("lisi").getName());
}
}
- 数组引用
// Lambda方式
ArrayBuilder ab = (len) -> new int[len];
int[] array = ab.buildArray(5);
// 方法引用
ArrayBuilder ab1 = int[]::new;
int[] array1 = ab1.buildArray(5);
六、Stream API
Stream操作的三个步骤
- 创建流
//第一种:通过集合:对于Collection接口(List 、Set、Queue等)直接调用Stream()方法可以获取Stream
List<String> list = new ArrayList<>();
Stream<String> stringStream = list.stream(); //返回一个顺序流
Stream<String> parallelStream = list.parallelStream(); //返回一个并行流(可多线程)
//第二种:通过数组
Stream<String> stream1 = Arrays.stream(new String[]{"CBB", "YJJ", "CB", "CJJ"});
//第三种:Stream.of()静态方法直接手动生成一个Stream
Stream<String> stream = Stream.of("A", "B", "C", "D");
- 中间操作:一个流可以跟随着若干中间操作,做出某种程度的数据过滤、去重、排序、映射、跳过等,然后返回一个新的流。
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
- 终止操作:执行中间操作连,产生结果
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
Stream API示例
创建List集合
//首先我们创建一个List集合
List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");
Filter过滤
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa2", "aaa1"
Sorted排序
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa1", "aaa2"
Map转换
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match匹配
// 验证 list 中 string 是否有以 a 开头的, 匹配到第一个,即返回 true
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
// 验证 list 中 string 是否都是以 a 开头的
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
// 验证 list 中 string 是否都不是以 z 开头的,
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
Count计数
// 先对 list 中字符串开头为 b 进行过滤,让后统计数量
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); //
Reduce
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
Parallel-Streams并行流
我们创建一个包含 1000000 UUID list 集合,分别通过顺序流和并行流,对这个 list 进行排序,测算耗时:
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
顺序流:
// 纳秒:获取当前时间1
long t0 = System.nanoTime();
//获取串行流输出
long count = values.stream().sorted().count();
System.out.println(count);
//纳秒:获取当前时间2
long t1 = System.nanoTime();
// 纳秒转微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("顺序流排序耗时: %d ms", millis));
// 顺序流排序耗时: 899 ms
并行流
// 纳秒:获取当前时间1
long t0 = System.nanoTime();
//并行流输出
long count = values.parallelStream().sorted().count();
System.out.println(count);
//纳秒:获取当前时间2
long t1 = System.nanoTime();
// 纳秒转微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("并行流排序耗时: %d ms", millis));
// 并行流排序耗时: 472 ms
同样的逻辑处理,通过并行流,我们的性能提升了近 50%。完成这一切,我们需要做的仅仅是将 stream 改成了 parallelStream。
七、新时间日期接口API
九、Annotations注解
回顾注解
- 四个常用的元注解:负责注解其他注解
- @Target:用于描述注解的使用范围:接口、类、枚举等等
- @Retention:表示在”source、class、runtime“哪个阶段,注解依然存在
- @Documented:说明该注解将被包含在javadoc中
- @Inherited:说明子类可以继承父类中的该注解
//使用自定义注解
//通过四个元注解自定义注解
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface MyAnnotation{
}
@MyAnnotation
public class Test01{
public void test(){
System.out.println("hello");
}
}
Java8注解
对于注解(也被称做元数据),Java 8 主要有两点改进:类型注解和重复注解。
类型注解
类型注解被用来支持在Java的程序中做强类型检查。配合第三方插件工具Checker Framework,可以在编译的时候检测出runtime error,以提高代码质量。
import checkers.nullness.quals.*;
public class TestDemo{
void sample() {
@NonNull Object my = new Object(); //通过,不报错
}
}
重复注解
允许在同一声明类型(类,属性,或方法)上多次使用同一个注解。
- Java8以前,相同的注解在同一位置只能使用一次
- Java 8 引入了重复注解机制,这样相同的注解可以在同一地方使用多次。
- 重复注解机制本身必须用
@Repeatable
注解,通过@Repeatable
元注解标记该注解使用哪个注解组进行包含。
- 自定义一个包装类Hints注解用来放置一组具体的Hint注解(老版本)
@interface MyHints {
Hint[] value();
}
@interface Hint {
String value();
}
@MyHints({@Hint("hint1"), @Hint("hint2")})
class Person {}
- 自定义一个包装类Hints注解用来放置一组具体的Hint注解(新版本)
@interface MyHints {
Hint[] value();
}
@Repeatable(MyHints.class) //通过@Repeatable 元注解标记该注解使用哪个注解组进行包含。
@interface Hint {
String value();
}
@Hint("hint1")
@Hint("hint2")
class Person {}
- 完整类测试
public class RepeatingAnnotations {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Filters {
Filter[] value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Filters.class)
public @interface Filter {
String value();
}
@Filter("filter1")
@Filter("filter2")
public interface Filterable {
}
public static void main(String[] args) {
for (Filter filter : Filterable.class.getAnnotationsByType(Filter.class)) {
System.out.println(filter.value());
}
}
}
输出结果:
filter1
filter2