java8入门
一、接口里有默认方法
Java 8使我们能够通过使用 default
关键字向接口添加非抽象方法实现。 此功能也称为虚拟扩展方法。
/**
* @author DMJ
* @date 2020/7/6
*/
public interface Person {
void say();
//静态的东西是属于class
//接口里可以定义静态的
public static final String state = "xixi";
//接口里定义静态的方法
public static void play(){
System.out.println("play");
}
default void run(){
System.out.println("run");
}
default void eat(){
System.out.println("eat");
}
}
/**
* @author DMJ
* @date 2020/7/6
*/
public class Tom implements Person{
@Override
public void say() {
System.out.println("say");
}
public static void main(String[] args) {
Person p = new Tom();
p.say();
p.eat();
p.run();
}
}
say
eat
run
在java8以后 接口里可以写默认的方法实现
可以重写
@Override
public void run() {
System.out.println("run1");
}
say
eat
run1
不管是抽象类还是接口,都可以通过匿名内部类的方式访问。不能通过抽象类或者接口直接创建对象。对于上面通过匿名内部类方式访问接口,我们可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,之后我们让接口的引用来指向这个对象。
public interface Person {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
public class Tom {
public static void main(String[] args) {
Person person = new Person() {
@Override
public double calculate(int a) {
return sqrt(a*100);
}
};
System.out.println(person.calculate(100));
System.out.println(person.sqrt(16));
}
}
二、Lambda表达式
在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:
可以看出,代码变得更段且更具有可读性,但是实际上还可以写得更短:
对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点
public static void main(String[] args) {
/*Person tom = new Person() {
@Override
public void say() {
System.out.println("say");
}
};
tom.say();*/
//函数式表达方式
Person tom = () -> System.out.println("say");
tom.say();
}
public interface Person {
String say(String msg);
}
public class Tom {
public static void main(String[] args) {
//函数式表达方式
Person tom = (msg) -> "jerry say:" + msg;
System.out.println(tom.say("hello world"));
}
}
//jerry say:hello world
public class Tom {
public static void tomSay(Person p){
String helloWorld = p.say("hello world");
System.out.println(helloWorld);
}
public static void main(String[] args) {
Tom.tomSay((msg) -> "jerry say:" + msg);
}
}
//jerry say:hello world
1、函数式接口
Java 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持Lambda。最终采取的方法是:增加函数式接口的概念。
“函数式接口”指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是默认方法)的接口。
像这样的接口,可以被隐式转换为lambda表达式。
java.lang.Runnable
与 java.util.concurrent.Callable
是函数式接口最典型的两个例子。(马上学)
Java 8增加了一种特殊的注解@FunctionalInterface
,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用@FunctionalInterface
注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的
@FunctionalInterface
public interface Person {
String say(String msg);
}
2、Lamda 表达式作用域
我们可以直接在 lambda 表达式中访问外部的局部变量:
但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:
public class Tom {
public static void tomSay(Person p){
String helloWorld = p.say("hello world");
System.out.println(helloWorld);
}
public static void main(String[] args) {
final int tail = 10;
Person p = (msg) -> "jerry say:" + msg + "," + tail;
Tom.tomSay(p);
}
}
不过这里的 num 必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:
public static void main(String[] args) {
final int tail = 10;
Person p = (msg) -> "jerry say:" + msg + "," + tail;
tail = 11;
Tom.tomSay(p);
}
3、内置函数式接口
JDK 1.8 API包含许多内置函数式接口。 其中一些借口在老版本的 Java 中是比较常见的比如: Comparator
或Runnable
,这些接口都增加了@FunctionalInterface
注解以便能用在 lambda 表达式上。
但是 Java 8 API 同样还提供了很多全新的函数式接口来让你的编程工作更加方便,有一些接口是来自 Google Guava 库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。
3.1 Predicates
Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):
3.2 Consumers
public class Tom {
public static void tomSay(Consumer<String> consumer,String msg){
consumer.accept(msg);
}
public static void main(String[] args) {
/*Tom.tomSay(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s + "消费了");
}
} ,"jerry");
};*/
Tom.tomSay((msg) -> System.out.println(msg + "消费了"),"jerry");
}
}
3.3 Functions
Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen):
/**
* @author DMJ
* @date 2020/7/6
*/
public class Tom {
public static void tomSay(Function<String,String> function, String msg){
String result = function.apply(msg);
System.out.println(result);
}
public static void main(String[] args) {
/*Tom.tomSay((s) -> s + "转化了" ,"jerry");*/
Tom.tomSay(new jerry(),"jerry");
}
static class jerry implements Function<String,String>{
@Override
public String apply(String s) {
return s + "转化了";
}
}
//或者
public static void main(String[] args) {
/*Tom.tomSay((s) -> s + "转化了" ,"jerry");*/
Tom.tomSay(new Function<String, String>() {
@Override
public String apply(String s) {
return s + "转化了";
}
}, "jerry");
}
//最终可以简化为
/*Tom.tomSay((s) -> s + "转化了" ,"jerry");*/
}
三、Streams(流)
java.util.Stream
表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如java.util.Collection
的子类,List 或者 Set, Map 不支持。Stream 的操作可以串行执行或者并行执行。
首先看看Stream是怎么用,首先创建实例代码的用到的数据List:
Filter(过滤)
过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。
Sorted(排序)
排序是一个 中间操作,返回的是排序好后的 Stream。如果你不指定一个自定义的 Comparator 则会使用默认排序。
Map(映射)
中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
Reduce(规约)
这是一个 最终操作 ,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规约后的结果是通过Optional 接口表示的:
Count(计数)
计数是一个 最终操作,返回Stream中元素的个数,返回值类型是 long。
Match(匹配)
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是 最终操作 ,并返回一个 boolean 类型的值。
public static void main(String[] args) {
List<Integer> list = Arrays.asList(12, 25, 30, 10, 14, 12, 21,14);
//将list变为流
//中间是中间操作 最后是最终操作
Optional<Integer> reduce = list.stream()
//map映射
.map(item -> item * 10)
//过滤
.filter(item -> item > 120)
//排序 正序
.sorted((num1, num2) -> num1 - num2)
//去重
.distinct()
//规约
.reduce((num1, num2) -> num1 + num2);
System.out.println(reduce.get());
}
public static void main(String[] args) {
List<Integer> list = Arrays.asList(12, 25, 30, 10, 14, 12, 21,14);
//将list变为流
//中间是中间操作 最后是最终操作
list.stream()
//map映射
.map( item -> item * 10)
//过滤
.filter(item -> item > 120)
//排序 正序
.sorted((num1,num2) -> num1 - num2)
//去重
.distinct()
//将数遍历出来
.forEach( System.out::println);
}
/**
* @author DMJ
* @date 2020/7/6
*/
public class Person {
private String name;
private int age;
private int height;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public Person(String name, int age, int height) {
this.name = name;
this.age = age;
this.height = height;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
/**
* @author DMJ
* @date 2020/7/6
*/
public class Tom {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("tom",10,175),
new Person("jerry",15,160),
new Person("xiongxiong",18,155),
new Person("lucy",20,170),
new Person("mark",16,165)
);
List<Person> collect = people.stream().filter(p -> p.getAge() > 12).collect(Collectors.toList());
System.out.println(collect);
}
}
//[Person{name='jerry', age=15, height=160}, Person{name='xiongxiong', age=18, height=155}, Person{name='lucy', age=20, height=170}, Person{name='mark', age=16, height=165}]
List<Person> collect = people.stream()
//排序 正序 倒序p2-p1
.sorted((p1,p2) -> p1.getAge()-p2.getAge())
//获取两个
//.limit(2);
//判断所有的年龄是否大于50 返回false
//.allMatch(p -> p.getAge()>50)
//找到第一个
//.findFirst()
System.out.println(collect);
最优的写法
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("tom",10,175),
new Person("jerry",15,160),
new Person("xiongxiong",18,155),
new Person("lucy",20,170),
new Person("mark",16,165)
);
//可以直接调用 正序
people.sort((p1,p2) -> p1.getAge() - p2.getAge());
//最好的写法
people.sort(Comparator.comparingInt(Person::getAge));
//倒序
people.sort(Comparator.comparingInt(p -> -p.getAge()));
people.forEach(System.out::println);
//先按照身高的倒序排 在按照年龄的正序排
people.sort(Comparator.comparingInt(Person::getHeight).reversed().thenComparingInt(Person::getAge));
people.forEach(System.out::println);
//Person{name='tom', age=10, height=175}
Person{name='lucy', age=20, height=170}
Person{name='mark', age=16, height=165}
Person{name='jerry', age=15, height=160}
Person{name='xiongxiong', age=18, height=155}
}
- List item
四、并行流 Parallel Streams
前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
下面的例子展示了是如何通过并行Stream来提升性能:
/**
* @author DMJ
* @date 2020/7/6## 标题
*/
public class Tom {
public static void main(String[] args) {
ArrayList<Double> nums = new ArrayList<>();
for (int i = 0; i <10000000 ; i++) {
double random = Math.random();
nums.add(random);
}
//普通流
long start = System.currentTimeMillis();
List<Double> collect = nums.stream().sorted().collect(Collectors.toList());
long end = System.currentTimeMillis();
System.out.println(end - start);
//并行流 以多线程的形式
long start2 = System.currentTimeMillis();
List<Double> collect1 = nums.parallelStream().sorted().collect(Collectors.toList());
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);
}
}
4240
2540
//冒泡形式很慢
public static void main(String[] args) {
ArrayList<Double> nums = new ArrayList<>();
for (int i = 0; i <100000 ; i++) {
double random = Math.random();
nums.add(random);
}
long start = System.currentTimeMillis();
long start2 = System.currentTimeMillis();
for (int i = 0; i <nums.size()-1 ; i++) {
for (int j = 0; j < nums.size()-1-i; j++) {
if (nums.get(j) > nums.get(j + 1)) {
double temp = nums.get(j);
nums.set(j, nums.get(j + 1));
nums.set(j + 1, temp);
}
}
}
//普通流
List<Double> collect = nums.stream().sorted().collect(Collectors.toList());
long end = System.currentTimeMillis();
System.out.println(end - start);
//并行流 以多线程的形式
List<Double> collect1 = nums.parallelStream().sorted().collect(Collectors.toList());
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);
}
五、Data API
Java 8在 java.time
包下包含一个全新的日期和时间API。新的Date API与Joda-Time库相似,但它们不一样。以下示例涵盖了此新 API 的最重要部分。译者对这部分内容参考相关书籍做了大部分修改。
译者注(总结):
Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 System.currentTimeMillis()
来获取当前的微秒数。某一个特定的时间点也可以使用 Instant
类来表示,Instant
类也可以用来创建旧版本的java.util.Date
对象。
在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类ZoneId
(在java.time
包中)表示一个区域标识符。 它有一个名为getAvailableZoneIds
的静态方法,它返回所有区域标识符。
jdk1.8中新增了 LocalDate 与 LocalDateTime等类来解决日期处理方法,同时引入了一个新的类DateTimeFormatter 来解决日期格式化问题。可以使用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。
和 SimpleDateFormat 相比,DateTimeFormatter 是线程安全的。
Instant 的精确度更高,可以精确到纳秒级。
Duration 可以便捷得到时间段内的天数、小时数等。
LocalDateTime 能够快速地获取年、月、日、下一月等。
TemporalAdjusters 类中包含许多常用的静态方法,避免自己编写工具类
Clock
Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 System.currentTimeMillis()
来获取当前的微秒数。某一个特定的时间点也可以使用 Instant
类来表示,Instant
类也可以用来创建旧版本的java.util.Date
对象。
Timezones(时区)
在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类ZoneId
(在java.time
包中)表示一个区域标识符。 它有一个名为getAvailableZoneIds
的静态方法,它返回所有区域标识符。
LocalTime(本地时间)
LocalTime 定义了一个没有时区信息的时间,例如 晚上10点或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
LocalDate(本地日期)
LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。
LocalDateTime(本地日期时间)
LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime 和 LocalTime还有 LocalDate 一样,都是不可变的。LocalDateTime 提供了一些能访问具体字段的方法。
public static void main(String[] args) {
//获取时区
Clock clock = Clock.systemDefaultZone();
System.out.println(clock.getZone());
//获取当前的时间
LocalDate localDate = LocalDate.now();
System.out.println(localDate);
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
//获取其他时区的时间 以美国纽约为例
LocalDate localDate1 = LocalDate.now(ZoneId.of("America/New_York"));
System.out.println(localDate1);
//格式化时间对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String format = localDate.format(dateTimeFormatter);
System.out.println(format);
LocalDateTime localDateTime1 = LocalDateTime.now();
String time = localDateTime1.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(time);
}
//Asia/Shanghai
2020-07-07
2020-07-07T01:14:05.449
2020-07-06
2020-07-07
2020-07-07 01:14:05