<接口的默认方法>
解决的问题:在java8 之前的版本,在修改已有的接口的时候,需要修改实现该接口的实现类。
作用:解决接口的修改与现有的实现不兼容的问题。在不影响原有实现类的结构下修改新的功能方法。
java 8抽象类和接口的区别
相同点:
Ø 都是抽象类型
Ø 都可以有实现方法(在java8之前是不可以的)
Ø 都可以不需要实现类或者继承者去实现所有方法,(以前不行,现在接口中默认方法不需要实现者实现)
不同点:
Ø 抽象类不可以多重继承,接口可以(无论是多重类型继承还是多重行为继承);
Ø 抽象类和接口所反映出的设计理念不同。其实抽象类表示的是"is-a"关系,接口表示的是"like-a"关系;
Ø 接口中定义的变量默认是publicstatic final 型,且必须给其初值,所以实现类中不能改变其值;抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。
<Lambda表达式>
解决问题:为 Java 添加了缺失的函数式编程特点,
作用:使程序更简洁便于可读可维护。
语法:
(arg1, arg2...) -> { body }
(type1 arg1, type2 arg2...) -> { body }
例子:
(int a, int b) -> { return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> 42
() -> { return 3.1415 };
l lambda表达式的结构:
Ø 一个 Lambda 表达式可以有零个或多个参数
Ø 参数的类型既可以明确声明,也可以根据上下文来推断。
Ø 例如:(int a)与(a)效果相同
所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
Ø 空圆括号代表参数集为空。例如:() -> 42
Ø 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a
Ø Lambda 表达式的主体可包含零条或多条语句
Ø 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
Ø 7.如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空
<Lambda范围>
l 访问本地变量
我们可以访问在lambda表示式之外的本地final变量:
final int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
但是和匿名变量不同的是变量num不必强制的被声明为final。下面的代码依然是合法的:
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
但是实际上,变量num在编译期是被隐式的转换为fianl类型的。下面的代码是不能被成功的编译的:
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num); //这里的num会编译报错
num = 3;
在lambda表达式内部向变量num写入值同样是不允许的。
l 访问对象字段和静态变量
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScopes() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}
l 访问接口默认方法
Ø 接口默认方法不能被Lambda表达式的内部代码所访问
<功能性接口>
Ø JDK1.8包括了许多功能性接口。它们中的一些是老版本中被熟知的接口,例如Comparator和Runnable。这些已存在的接口已经通过@FunctionalInterface注解扩展为支持Lambda表达式。
Ø 在 Java 中,Marker(标记)类型的接口是一种没有方法或属性声明的接口,简单地说,marker 接口是空接口。相似地,函数式接口是只包含一个抽象方法声明的接口
Ø 每个 Lambda 表达式都能隐式地赋值给函数式接口,当不指明函数式接口时,编译器会自动解释这种转化
Ø 函数式接口例子:
Consumer<Integer> c = (int x) -> { System.out.println(x) };
BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);
Predicate<String> p = (String s) -> { s == null };
l 相关接口:
1. 断言接口(Predicates) 是只拥有一个参数的Boolean型功能的接口。这个接口拥有多个默认方法用于构成predicates复杂的逻辑术语
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
2. 功能接口(Functions)Functions接受一个参数并产生一个结果,默认方法能够用于将多个函数链接在一起。
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, Integer> backToString = toInteger.addThen(String::valueOf);
backToString.apply(“5120”);
3. 供应接口(Supplines) 对于给定的泛型类型产生一个实例。不同于Functions,Suppliers不需要任何参数。
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
4. 消费接口(Consumers)代表在只有一个输入参数时操作被如何执行
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
5. 比较接口(Comparators)
Comparator<Person> comparator=
(p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
6.选项接口(Optionals)一种特殊的工具用来解决NullPointerException
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s)->System.out.println(s.charAt(0))); // "b"
7. 流接口(Streams)
java.util.Stream代表着一串你可以在其上进行多种操作的元素。流操作既可以是连续的也可以是中断的。中断操作返回操作结果。而连续操作返回流本身,这样你就可以在该行上继续操作。流是创建在数据源上的,例如:java.util.Collection、list集合和set集合。流操作既可以顺序执行也可以并行执行
首先创建一个字符串的数组
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");
Java8的Collections类已经被扩展了,你可以简单的调用Collection.stream()或者Collection.parallelSteam()来创建流。下面部分将介绍大部分流操作。
Filter
Filter接受一个predicate来过滤流中的所有元素。这个操作是连续的,它可以让我们在结果上继续调用另外一个流操作forEach。ForEach接受一个consumer,它被用来对过滤流中的每个元素执行操作。ForEach是一个中断操作,因此我们不能在ForEach后调用其他流操作。
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa2", "aaa1"
Sorted
Sorted是一个连续操作,它返回流的已排序版本。如果你没有显示的指定Comparator,那么流中元素的排序规则为默认的。
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa1", "aaa2"
需要注意的是sorted只创建了流的排序结果,它并没有改变集合中元素的排序位置。stringCollection中元素排序是没有改变的。
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Map
连续性操作map通过指定的Function将流中的每个元素转变为另外的对象。下面的示例将每个字符串转换为大写的字符串。此外,你也可以使用map将每个元素的类型改变为其它类型。转换后流的泛型类型依赖于你传入的Function的泛型类型。
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match
各种匹配操作可以用来检测是否某种predicate和流中元素相匹配。所有的这些操作是中断的并返回一个boolean结果。
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
Count
Count是中断型操作,它返回流中的元素数量。
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
Reduce
这个中断性操作使用指定的function对流中元素实施消减策略。此操作的返回值是一个包括所有被消减元素的Optional。
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
在前面部分我们提到流可以是顺序的也可以是并行的。顺序流的操作是在单线程上执行的,而并行流的操作是在多线程上并发执行的。
随后的例子我们展示了并行流可以多么容易的提高性能。
首先,我们创建一个包含唯一元素的大容器:
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
现在我们开始测试排序这些元素需要多长时间。
Sequential Sort
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
// sequential sort took: 899 ms
Parallel Sort
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
// parallel sort took: 472 ms
你会观察到这两种模式的代码基本上是一致的,但是并行排序所花费的时间大约是顺序排序的一半。
Map
我们已经提到maps不支持流。然而现在maps包括了许多新的非常有用的方法用于执行通用任务。
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));
上述的代码应该很清晰了:putIfAbsent使得我们不用写是否为null值的检测语句;forEach使用consumer来对map中的每个元素进行操作。下面的例子向我们展示使用功能性函数在map里执行代码:
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
接下来,我们将学习如何删除给定键所对应的元素。删除操作还需要满足给定的值需要和map中的值想等:
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
其他一些帮助性方法:
map.getOrDefault(42, "not found"); // not found
合并map中的实体是十分容易的:
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
如果map不存在指定的键,那么它将把该键值对key/value加入map中。反而,如果存在,它将调用function来进行合并操作。
l Date API 新的date和time 的api,与joda-Time库是兼容的。
Ø Clock
提供了访问当前日期和时间的方法。Clock是时区敏感的并且它可以被用来替代System.currentTimeMillis进行获取当前毫秒数。同时,时间轴上的时间点是可以用类Instant来表示的。Instants可以被用来创建遗留的java.util.Date对象。
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // legacy java.util.Date
Ø TimeZones
被用来表示ZoneId。它们可以通过静态工厂方法访问。TImeZones定义了时差,它在instants和本地日期时间转换上十分重要。
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
Ø LocalTime
本地时间代表了一个和时区无关的时间,e.g. 10pm or 17:30:15.
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween); // -3
System.out.println(minutesBetween); // -239
LocalTime包括很多个工厂方法用来简化创建过程,如下面的例子:
LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime); // 13:37
Ø LocalDate
LocalDate代表了一个可区分日期,e.g.2014-03-11。它是不变的同时工作原理类似于LocalTime。下面的例子描绘了通过加减年,月,日来计算出一个新的日期。需要注意的是这每个操作都返回一个新的实例。
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // FRIDAY
解析字符串的时间格式:
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
Ø LocalDateTime
LocalDateTime代表日期和时间。它将我们前部分看到的时间和日期组合进一个实例。LocalDateTime是不可变的并且它的工作原理和LocalTime和LocalDate十分相似。
使用示例:
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month); // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
在一些额外的时区信息帮助下,它可以被转换为instant。Instants可以被容易的转换为遗留的java.util.Date类型。
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
格式date-time的过程和格式date和time基本上是一样的。在使用系统自带的定义格式时,我们也可以定义我们自己的格式:
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 - 07:13
<方法和构造函数引用>
解决问题:简化静态方法的引用
使用方式:通过关键字::来传递方法和构造函数的引用
例子:
静态方法:
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted); // 123
对象方法:
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"
<Annotations>
支持多重注解,在注解声明时使用@Repeatable。
例子:
我们首先定义一个包装注解
@interface Hints {
Hint[] value();
}
@Repeatable(Hints.class)
@interface Hint {
String value();
}
使用1:容器注解
@Hints({@Hint("hint1"), @Hint("hint2")})
class User {}
使用2:重复注解
@Hint("hint1")
@Hint("hint2")
class User {}
特别注意:
在原有注解使用范围基础之上,如下:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE
Java8注解的使用范围新增两种类型:
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
其他特性:Arrays.parallelSort,StampedLock,CompletableFuture