A024_Java8新特性

1.内容介绍

Java8新特性介绍【了解】
接口中方法(抽象、default、static)【掌握】
函数式接口【掌握】
Lambda表达式【重点】
Lambda表达式的作用域【了解】
方法引用(lambda的使用)【掌握】
Stream流【重点】
Optional类【了解】
时间日期API【了解】

2.Java8新特性介绍

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。

2.1.主要新特性

1.Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。
2.方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
3.默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
4.Nashorn, JavaScr新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
5.Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
6.Date Time API − 加强对日期与时间的处理。
7.Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
8.ipt 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

3.接口的默认方法

接口中的默认方法主要就是接口这种语法结构本身的语法特点,所以大家根据特点和提示熟悉就好,在以后的应用中慢慢去感受它的好处。

3.1.传统的方法(之前的)

在Java8之前Java中接口里面的方法默认都是public abstract 修饰的抽象方法并且没有方法体;

3.2.static方法

1.使用static修饰接口中的方法并且必须有主体;
2.接口的static方法只能够被接口本身调用;接口名.方法名(…);
3.接口的static方法不能够被子接口继承;
4.接口的static方法不能够被实现类覆写及直接调用;

3.3.default方法

在接口中可定义一个使用default修饰有方法体的方法,接口中可以对这个方法提供默认的一种实现。
1.使用default修饰接口中的方法并且必须有主体;
2.接口的default方法不能够被接口本身调用,需要接口的实例(实现类对象)来调用;
3.接口的default方法可以背子接口继承、覆写或者直接调用;
4.接口的default方法可以被实现类覆写及直接调用;

在接口中,经过static和default修饰的方法必须有方法体;
static修饰的方法调用方式为类.方法名
default修饰的方法必须是实现类的对象调用
static修饰的方法不能被子接口继承,default修饰的方法可以被子接口继承
并复写方法,就可以创建子接口实现类对象进行调用

4.函数式接口

4.1.什么是函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为lambda表达式。
可以使用注解@FunctionalInterface标记该接口为函数式接口

4.2.函数式接口API

JDK 1.8之前已有的函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
java.util.function 此包中包含了很多类,用来支持 Java的 函数式编程
Predicate
Consumer
Supplier

4.3. 函数式接口注解

@FunctionalInterface
我们在函数式接口上面加上此注解后,里面就只能够有一个抽象方法了,当然不加此注解且只有一个抽象方法的接口也是函数式接口,只是没有限定提示而已。

5.Lambda表达式

5.1.什么是Lambda表达式

简单来说:可以看成是对匿名内部类的简写,使用Lambda表达式时,接口必须是函数式接口。

5.2. Lambda表达式的语法

基本语法:
<函数式接口> <变量名> = (参数1,参数2…) -> {
//方法体
}
特点说明:
(参数1,参数2…)表示参数列表;->表示连接符;{}内部是方法体
1、=右边的类型会根据左边的函数式接口类型自动推断;
2、如果形参列表为空,只需保留();
3、如果形参只有1个,()可以省略,只需要参数的名称即可;
4、如果执行语句只有1句,且无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句也保证只有1句;
5、形参列表的数据类型会自动推断;
6、lambda不会生成一个单独的内部类文件;
7、lambda表达式若访问了局部变量,则局部变量必须是final的,若是局部变量没有加final关键字,系统会自动添加,此后在修改该局部变量,会报错;

5.3.学习过程(一个案例学会Lambda)

1、一个类实现一个接口基本写法
接口:

public interface MyInterface{
	int sum(int num1,int num2);
}

实现类:

public class MyInterfaceImpl implements MyInterface{
	@Override
	public int sum(int num1, int num2) {
		// TODO Auto-generated method stub
		return num1+num2;
	}
}

测试类:

public static void main(String[] args) {
	MyInterfaceImpl mf = new MyInterfaceImpl();
	System.out.println(mf.sum(10, 20));
}

2、匿名内部类的写法、

public static void main(String[] args) {
	MyInterface mf = new MyInterface() {
	    @Override
	    public int sum(int num1, int num2) {
				return num1+num2;
		}
	};
	System.out.println(mf.sum(10, 20));
}

3、使用Lambda写法一:常规写法
我们可以看成是把函数式接口中唯一的实现方法的核心: 参数列表和方法体抽取出来

//砖石符号->左边是参数  右边是实现
MyInterface mf = (int num1,int num2)->{
	 return num1+num2;
};

4、使用Lambda写法二:两个参数有返回值的简写

/*3.参数列表类型可以省略掉
* 4.如果参数只有一个,可以省略()
* 1.方法体只有一句,可以省略{}
* 2.如果有返回值  可以省略return
* /
MyInterface mf = (num1,num2)->num1+num2;
System.out.println(mf.sum(10, 12));

5、使用Lambda写法三:一个参数没有返回值的简写
测试如果只有一个参数的简写
接口:

public interface MyInterface{
	void print(String str);
}

测试类:

/*Lambda常规写法*/
MyInterface mi = (String str)->{
	System.out.println(str);
};
/*Lambda简写*/
MyInterface mi2 = str->System.out.println(str);
mi2.print("Hello Lambda");

6、使用Lambda写法四:没有参数的简写
测试如果函数式接口中的方法没有参数
接口:

public interface MyInterface{
	void test();
}

测试类:

//Lambda常规写法
MyInterface mi = ()->{
	System.out.println("123456");
};
//简写[大括号不写代码没有什么意义]
MyInterface mi2 = ()->{};

6.Lambda 作用域

在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。但如果访问局部变量,要求局部变量必须是final修饰的。

public class Test {
	public static void main(String[] args) {
		final int i = 9;//要求此次需要时final修饰
		MyInterface mf = new MyInterface() {
			@Override
			public int sum(int num1, int num2) {
				System.out.println(i);
				return num1+num2;
			}
		};
	}
}

注意:上面代码中一旦匿名内部类中使用了i,则第5行的i会自动被编辑为final的
下面我们可以看到使用Lambda表达式和上面一样的

public class Test {
	public static void main(String[] args) {
		final int i = 9;
		MyInterface mf = (num1,num2)->{
			System.out.println(i);
			return num1+num2;
		};
	}
}

7.方法引用

7.1.构造方法引用

1、准备一个一个Person类,提供一个两个参数的构造方法

public class Person {
	private int id;
	private String name;
	public Person(int id, String name) {
		this.id = id;
		this.name = name;
	}
	public Person() {
	}
	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + "]";
	}
}

2、准备一个创建Person对象的工厂函数式接口,提供一个获得Person对象的抽象方法

public interface PersonFactory{
	Person creatPerson(int id,String name);
}

3、使用匿名内部类的方式创建一个工厂的实例

public static void main(String[] args) {
		PersonFactory factory = new PersonFactory() {
			@Override
			public Person creatPerson(int id, String name) {
				return new Person(11,"小猪佩奇");
			}
		};
}

4、使用Lambda表达式简写一:

PersonFactory factory = (id,name)->new Person(id,name);

5、使用Lambda表达式简写二:

PersonFactory factory = Person::new;
7.2.静态方法引用

1.准备一个函数式接口,提供一个解析字符串为int的方法

public interface PersonFactory{
	int parse(String str);
}

2.测试

//匿名内部类的写法
PersonFactory pf = new PersonFactory() {
	@Override
	public int parse(String str) {
		// TODO Auto-generated method stub
		return Integer.parseInt(str);
	}
};
//Lambda常规写法
PersonFactory factory = str->Integer.parseInt(str);
//Lambda静态方法引用
PersonFactory fp = Integer::parseInt;
7.3.实例方法引用

1、了解Java1.8提供了一个断言型函数式接口Function,接受两个参数【判断及为断言】
在这里插入图片描述
2、匿名内部类方式

public static void main(String[] args) {
	String str = "hello.itsource";
	//匿名内部类的方式
	Function<String,Boolean> func1 = new Function<String, Boolean>() {
		@Override
		public Boolean apply(String t) {
			return t.endsWith("itsource");
		}
	};
	boolean test = test(func1,str);
	System.out.println(test);
}
public static boolean test(Function<String,Boolean> f,String str){
	return f.apply(str);
}

3、Lambda表达式常规写法

public static void main(String[] args) {
		String str = "hello.itsource";
		//匿名内部类的方式
		Function<String,Boolean> func1 = t->str.endsWith("itsource");
		boolean test = test(func1,str);
		System.out.println(test);
	}
	public static boolean test(Function<String,Boolean> f,String str){
		return f.apply(str);
	}

4、Lambda表达式简写

public static void main(String[] args) {
		String str = "hello.itsource";
		//匿名内部类的方式
		Function<String,Boolean> func1 = str::endsWith;
		boolean test = test(func1,str);
		System.out.println(test);
	}
	public static boolean test(Function<String,Boolean> f,String str){
		return f.apply(str);
	}

8.Stream流

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
1.Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
select * from user where age >30
2.Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
3.这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
4.元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

8.1.什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作
1.<strong元素队列< strong=“”>元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。</strong元素队列<>
2.数据源 流的来源。 可以是集合,数组,I/O channel(nio new IO非阻塞式IO), 产生器generator 等。
3.聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
4.Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
5.内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

8.2.生成流

在 Java 8 中, 集合接口有两个方法来生成流:
stream() − 为集合创建串行流。
parallelStream() − 为集合创建并行流。

8.3.常用方法
8.3.1 forEach

Stream 提供了新的方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8); 
list.stream().forEach(System.out::println);
8.3.2 map

map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); 
// 获取对应的平方数 
numbers.stream().map( i -> i*i).forEach(System.out::println);
8.3.3 filter

filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); 
//过滤取出每一个不为空的元素
strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);
8.3.4 limit

limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:

Random random = new Random();
//从随机流中获取十个随机数:并输出结果
random.ints().limit(10).forEach(System.out::println);
x.x.x distinct

去重

long[] array = LongStream.of(10, 11, 52, 49, 5, 94, 105, 88).filter(a -> a > 10).map(a -> a * 2).distinct().sorted().toArray();
Arrays.stream(array).forEach(System.out::println);
8.3.5 sorted

sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:

Random random = new Random(); 
//获取十个随机数,并排序
random.ints().limit(10).sorted().forEach(System.out::println);
8.3.6 并行(parallel)流及串行流的区别

parallelStream 是流并行处理程序的代替方法。以下实例我们使用

parallelStream 来输出空字符串的数量:
List<String> strings = Arrays.asList("abc", "f", "bc", "efg", "abcd","", "jkl"); 
// 获取空字符串的数量 
strings.parallelStream().limit(5).forEach(System.out::println);//并行流随机获取【效率高】
strings.stream().limit(5).forEach(System.out::println);//串行流:有序获取

我们可以很容易的在顺序运行和并行直接切换。

8.3.7 Collectors

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); 
//归约操作:操作元素之后返回新的集合
List<String> filtered = strings.stream().filter(string-> !string.isEmpty()).collect(Collectors.toList()); 
System.out.println("筛选列表: " + filtered); 
//归约操作:操作元素之后元素之间用,号分割。合并了一个字符串
String mergedString = strings.stream().filter(string-> !string.isEmpty()).collect(Collectors.joining(", ")); 
System.out.println("合并字符串: " + mergedString);
//归约操作:统计获取空字符串的数量 
long count = strings.stream().filter(string -> string.isEmpty()).count();

9.Optional 类

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常
善用Optional可以使我们代码中很多繁琐、丑陋的设计变得十分优雅。

9.1.声明

以下是一个 java.util.Optional 类的声明:public final class Optional extends Object
Optional容器类常用方法:
Optional.of(T t):创建要给Optional实例
Optional.empty():创建一个空的Optional实例
Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空的实例
isPresent():判断是否包含值
orElse(T t):如果调用对象包含值,返回该值,否则返回t
orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值
map(function f):如果有值对其处理,并发挥处理后的Optional ,否则返回Optional.empty()
flatMap(Function mapper):与map类似,要求返回值必须是Optional

9.2.Optional 实例

我们可以通过以下实例来更好的了解 Optional 类的使用:

public class User{
	private String name;
	private String password;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}

以往的判断方式:

class Mytest {
	public static void main(){
		User user = new User();
		user.setName("admin");
		String password = getPwd(user);
	}
	public static String getPwd(User u){
		if(u==null){
			return "unknow";
		}
		return u.getPassword();
	}
}

引入Optional错误的判断方式

public static String getPwd(User u){
	Optional<User> optional = Optional.ofNullable(u);
	if(!optional.isPresent()){
		return "unknow";
	}
	return optional.get().getPassword();
}

正确的判断方式:

public static String getPwd(User u){
	return Optional.ofNullable(u)
	.map(user->user.getPassword())
	.orElse("unknow");
}

10.日期时间 API

Java 8通过发布新的 API (JSR 310)来进一步加强对日期与时间的处理。在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
1.非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
2.设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
3.时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
4.Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
5.Local(本地) − 简化了日期时间的处理,没有时区的问题。
6.Zoned(时区) − 通过制定的时区处理日期时间。
7.新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作

10.1.本地化日期时间 API

LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。代码如下:

// 获取当前的日期时间
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);
10.2.使用时区的日期时间API

如果我们需要考虑到时区,就可以使用时区的日期时间API:

// 获取当前时间日期
ZonedDateTime date1 =ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]"); 			
System.out.println("date1: " + date1);
ZoneId id = ZoneId.of("Europe/Paris"); 
System.out.println("ZoneId: " + id);
ZoneId currentZone = ZoneId.systemDefault(); 
System.out.println("当期时区: " + currentZone);

11.课程总结

11.1.重点

Jdk1.8的新特性 lambda表达式 方法引用 函数式接口 stream
多线程 线程的创建方式 生命周期 锁机制 synconized与lock区别 threadlocal原理
线程池 线程池原理 常见4种线程池
线程池中最大线程数的设置

11.2.难点

12.课后练习

Jdk1.8新特性中 熟练lambda表达式写法 方法引用写法

13.面试题

Jdk1.8的新特性有哪些

14.扩展知识或课外阅读推荐(可选)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值