Java 8教程

/ 语言 / 没有评论 / 270浏览

Java 8 - 简介


Java 8于2014年初发布。在java 8中,大多数关于功能的是lambda表达式。它还有许多其他重要功能,如默认方法,Streams API和新的日期/时间API。让我们在java 8中了解这些新功能的例子。

-----------------来自小马哥的故事

xxx

Lambda表达

我们许多已经使用高级语言(如Scala)的人们并不知道Lambda表达式。在编程中,Lambda表达式(或函数)只是一个匿名函数,即一个没有名称而不被绑定到一个标识符的函数。它们被完全写在需要的地方,通常作为其他功能的参数。

lambda表达式的基本语法是:

either
(parameters) -> expression
or
(parameters) -> { statements; }
or
() -> expression

典型的lambda表达式示例将如下所示:

(x, y) -> x + y  //This function takes two parameters and return their sum.

请注意,根据x和y的类型,方法可能会在多个地方使用。参数可以匹配int,或整数或简单的字符串。基于上下文,它将添加两个整数或两个字符串。

编写lambda表达式的规则

1. lambda表达式可以具有零个,一个或多个参数。

2. 可以显式声明参数的类型,也可以从上下文推断参数的类型。

3. 多个参数用强制括号括起来,用逗号分隔。空括号用于表示一组空的参数。

4. 当有一个参数时,如果推断出它的类型,则不必使用括号。例如a - > return a * a。

5. lambda表达式的主体可以包含零个,一个或多个语句。

6. 如果lambda表达式的主体具有单个语句,则大括号不是强制性的,匿名函数的返回类型与body表达式的返回类型相同。当身体中有多于一个的声明必须用大括号括起来。

阅读更多:Java 8 Lambda表达式教程

函数式接口

函数式接口也称为单抽象方法接口(SAM接口)。正如名字所暗示的,他们只允许一个抽象方法。Java 8引入了一个注释,即@FunctionalInterface当您注释的界面违反了函数式接口的合同时,可以用于编译器级错误。

典型的函数式接口示例:

@FunctionalInterface
public interface MyFirstFunctionalInterface {
	public void firstWork();
}

请注意,即使@FunctionalInterface省略注释,函数式接口也是有效的。它仅用于通知编译器在界面内强制执行单个抽象方法。

此外,由于默认方法不是抽象的,您可以随意添加默认方法到您的函数式接口尽可能多的你喜欢。

要记住的另一个重要的一点是,如果一个接口声明一个覆盖其中一个公共方法的抽象方法java.lang.Object,也不会计入接口的抽象方法计数,因为接口的任何实现都将具有来自java.lang.Object或其他地方的实现。例如,下面是完全有效的函数式接口。

@FunctionalInterface
public interface MyFirstFunctionalInterface 
{
	public void firstWork();

	@Override
	public String toString();//Overridden from Object class

	@Override
	public boolean equals(Object obj);//Overridden from Object class
}

阅读更多:Java 8函数式接口教程

默认方法

Java 8允许您在接口中添加非抽象方法。这些方法必须被声明为默认方法。java 8中引入了默认方法来启用lambda表达式的功能。

默认方法使您能够向库的接口添加新功能,并确保与旧版本的这些接口编写的代码的二进制兼容性。

我们来看一个例子:

public interface Moveable {
    default void move(){
        System.out.println("I am moving");
    }
}

Moveable接口定义了一个方法,move()并提供了一个默认的实现。如果任何类实现了这个接口,那么它不需要实现它自己的move()方法版本。它可以直接打电话instance.move()。例如

public class Animal implements Moveable{
    public static void main(String[] args){
        Animal tiger = new Animal();
        tiger.move();
    }
}

 Output: I am moving

如果类愿意定制move()方法的行为,那么它可以提供它自己的自定义实现并覆盖该方法。

Reda更多:Java 8默认方法教程

Streams

另一个重大改变引入了Java 8 Streams API,它提供了一种以各种方式处理一组数据的机制,可以包括过滤,转换或可能对应用程序有用的任何其他方式。

Java 8中的Streams API支持一种不同类型的迭代,其中您只需定义要处理的项目集合,对每个项目执行的操作以及要存储这些操作的输出的位置。

StreamsAPI的一个例子。在此示例中,items是String值的集合,并且要删除以某些前缀文本开头的条目。

List<String> items;
String prefix;
List<String> filteredList = items.stream().filter(e -> (!e.startsWith(prefix))).collect(Collectors.toList());

这里items.stream()表示我们希望items使用Streams API处理集合中的数据。

阅读更多:Java 8内部与外部迭代

日期/时间API更改

新的日期和时间API /类(JSR-310)也称为ThreeTen,它们简单地改变了在java应用程序中处理日期的方式。

日期

日期类甚至已经过时了。旨在取代Date类新的类LocalDate,LocalTime和LocalDateTime。

1. 本LocalDate类代表一个日期。没有时间或时区的表示。

2. 这个LocalTime班代表一个时间。没有表示日期或时区。

3. 本LocalDateTime类代表一个日期-时间。没有时区的表示。

4. 如果您希望使用区域信息的日期功能,那么拉姆达为您提供额外的3类类似于一个,即上面OffsetDate,OffsetTime和OffsetDateTime。时区偏移可以用“+05:30”或“欧洲/巴黎”格式表示。这是通过使用另一个类来完成的ZoneId。

LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.of(12, 20);
LocalDateTime localDateTime = LocalDateTime.now(); 
OffsetDateTime offsetDateTime = OffsetDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Europe/Paris"));

时间戳和持续时间

为了表示任何时刻的具体时间戳,需要使用的类是Instant。本Instant类表示时间纳秒的精度瞬间。Instant上的操作包括与另一个进行比较,Instant并添加或减少持续时间。

Instant instant = Instant.now();
Instant instant1 = instant.plus(Duration.ofMillis(5000));
Instant instant2 = instant.minus(Duration.ofMillis(5000));
Instant instant3 = instant.minusSeconds(10);

Durationclass是Java语言第一次带来的全新概念。它代表两个时间戳之间的时差。

Duration duration = Duration.ofMillis(5000);
duration = Duration.ofSeconds(60);
duration = Duration.ofMinutes(10);

Duration处理小时间单位,例如毫秒,秒,分钟和小时。它们更适合与应用程序代码交互。要与人交互,你需要获得更多的持续时间,这是与Period课堂呈现的。

Period period = Period.ofDays(6);
period = Period.ofMonths(6);
period = Period.between(LocalDate.now(), LocalDate.now().plusDays(60));

阅读更多:Java 8日期和时间API更改

Java 8 Lambda表达式教程


一个非常全新而令人兴奋的功能,java 8搭配它,是Lambda表达式。我们许多已经从事高级语言(如scala)工作的人们并不为人所知。事实上,如果你看历史,并尝试在过去二十年里发现java中的任何语言改进,你将无法回想起许多令人兴奋的事情。在过去十年中,只有少数并发类,泛型,如果您同意注释,在java中是显着的增加。Lambda表达式打破了这场干旱,感觉像一个愉快的礼物。


Java中的lambda表达式是什么?

在编程中,Lambda表达式(或函数)只是一个匿名函数,即一个没有名称而不被绑定到一个标识符的函数。

换句话说,lambda表达式是作为常量值给出的无名函数,并且准确地写在需要的地方,通常作为一些其他函数的参数。

Lambda表达式最重要的特征是它们在其外观的上下文中执行。所以,类似的lambda表达式可以在某些其他上下文中执行不同的方式(即逻辑将是相同的,但结果会根据传递给函数的不同参数而不同)。 以上定义充满了关键字,只有当您已经深入了解什么是lambda表达式才能被理解。所以,一旦你在下一节更好地了解了lambda表达式,我建议你重新阅读上面的段落。

所以,很明显,lambda是没有名称和标识符的某种功能。那么什么大事呢?为什么大家都很兴奋?

答案在于面向对象编程(OOP)的功能编程所带来的好处。大多数OOP语言围绕对象和实例进行演化,仅对待他们的一Streams公民。另一个重要的实体即功能占据了位置。这在java中尤其如此,其中函数不能存在于对象之外。一个函数本身并不意味着java中的任何东西,直到它与某个对象或实例相关。

但是在函数式编程中,您可以定义函数,给它们引用变量,并将它们作为方法参数传递。JavaScript是一个很好的例子,您可以将回调方法传递给Ajax调用。这是非常有用的功能,一开始就缺少java。现在用java 8,我们也可以使用这些lambda表达式。

典型的lambda表达式语法将如下所示:

(x, y) -> x + y  //This function takes two parameters
//and return their sum.

现在基于x和y的类型,方法可以在多个地方使用。参数可以匹配int,或整数或简单的字符串。基于上下文,它将添加两个整数或两个字符串。

句法:

lambda表达式的基本语法是

either
(parameters) -> expression
or
(parameters) -> { statements; }
or
() -> expression

让我们看一些例子:

(int a, int b) ->a * b   // takes two integers and returns their multiplication
(a, b)  ->   a - b   // takes two numbers and returns their difference
() -> 99 // takes no values and returns 99
(String a) -> System.out.println(a)  // takes a string, prints its value to the console, and returns nothing
a -> 2 * a   // takes a number and returns the result of doubling it
c -> { //some complex statements }   // takes a collection and do some procesing

我们来确定一些可以帮助我们编写lambda表达式的规则:

● lambda表达式可以具有零个,一个或多个参数。

● 可以显式声明参数的类型,也可以从上下文推断参数的类型。

● 多个参数用强制括号括起来,用逗号分隔。空括号用于表示一组空的参数。

● 当有一个参数时,如果推断出它的类型,则不必使用括号。例如a - > return a * a。

● lambda表达式的主体可以包含零个,一个或多个语句。

● 如果lambda表达式的主体具有单个语句,则大括号不是强制性的,匿名函数的返回类型与body表达式的返回类型相同。当身体中有多于一个的声明必须用大括号括起来。

所以,我们简要概述了什么是lambda表达式。如果您感到遗失并且无法关联,请耐心等待,如何在Java编程语言中使用。我们将在接下来的30分钟内做出一切。所以我们来吧

在深入讨论lambda表达式和java编程之前,您还必须了解功能接口。这太重要了

什么是函数式接口?

单抽象方法接口(SAM接口)不是一个新概念。这意味着只有一种方法的接口。在java中,我们已经有了很多这样的SAM接口的例子。从java 8,他们也将被称为功能接口。Java 8通过使用新的注释(即@FunctionalInterface)标记这些接口来强制执行单一责任规则。

例如,Runnable界面的新定义如下:

@FunctionalInterface
public interface Runnable {
public abstract void run();
}

如果您尝试在任何函数式接口中添加新方法,编译器将不允许您执行此操作,并将抛出编译时错误。

到现在为止还挺好。但是,它们如何与Lambda表达式相关?让我们找出答案。

我们知道Lambda表达式是没有名称的匿名函数,它们(主要)被传递给其他函数作为参数。那么在java方法中,参数总是有一个类型,并且这种类型的信息被查找以确定在方法重载或甚至简单的方法调用的情况下需要调用哪个方法。因此,基本上每个lambda表达式也必须可转换为某些类型才能被接受为方法参数。那么lambda表达式转换的类型总是功能接口类型。

让我们以一个例子来理解它。如果我们要编写一个在控制台中打印“howtodoinjava”的线程,那么最简单的代码将是:

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("howtodoinjava");
}
}).start();

如果我们使用lambda表达式来执行此任务,那么代码将是:

new Thread(
() ->   {
System.out.println("My Runnable");
}
 ).start();

我们还看到,Runnable是一个函数式接口,具有单一方法run()。因此,当您将lambda表达式传递给Thread类的构造函数时,编译器将尝试将表达式转换为等效的Runnable代码,如第一个代码示例所示。如果编译器成功,那么一切运行正常,如果编译器无法将表达式转换为等效的实现代码,则会抱怨。这里,在上面的例子中,lambda表达式被转换为Runnable类型。

简单来说,lambda表达式是函数式接口的一个实例。但是,lambda表达式本身并不包含其实现的功能接口的信息; 该信息是从其使用的上下文推导出来的。

几个Lambda表达式的例子

我列出了一些代码示例,您可以阅读和分析如何在日常编程中使用lambda表达式。

1)迭代列表并执行一些操作

List pointList = new ArrayList(); pointList.add("1"); pointList.add("2");

pointList.forEach(p ->  {
System.out.println(p);
//Do more work
}
 );

2)创建一个新的runnable并传递给线程

new Thread(
() -> System.out.println("My Runnable");
).start();

3)按照他们的名字对雇员对象进行排序

public class LambdaIntroduction {
 
  public static void main (String[] ar){
  Employee[] employees  = {
  new Employee("David"),
  new Employee("Naveen"),
  new Employee("Alex"),
  new Employee("Richard")};

  System.out.println("Before Sorting Names: "+Arrays.toString(employees));
  Arrays.sort(employees, Employee::nameCompare);
  System.out.println("After Sorting Names "+Arrays.toString(employees));
  }
}
  
class Employee {
  String name;
  
  Employee(String name) {
this.name = name;
  }
  
  public static int nameCompare(Employee a1, Employee a2) {
return a1.name.compareTo(a2.name);
  }

  public String toString() {
return name;
  }
}
 
Output:
 
Before Sorting Names: [David, Naveen, Alex, Richard]
After Sorting Names [Alex, David, Naveen, Richard]

4)将事件侦听器添加到GUI组件

JButton button =  new JButton("Submit");
button.addActionListener((e) -> {
System.out.println("Click event triggered !!");
});          

以上是java 8中的lambda表达式的非常基本的例子。我将不时提出更有用的示例和代码示例。

Java 8方法引用与示例


在Java 8中,您可以使用class::methodName类型语法引用类或对象的方法。让我们在java 8中了解不同类型的可用方法引用。


方法参考的类型 - 快速概述

Java 8有四种类型的方法引用。

方法参考描述
参考静态方法用于引用类中的静态方法Math::max 相当于 Math.max(x,y)
从实例引用instance方法请参考实例方法使用提供的对象的引用System.out::println 相当于System.out.println(x)
从类类型引用instance方法在对上下文提供的对象的引用上调用实例方法String::length 相当于str.length()
参考构造函数引用构造函数ArrayList::new 相当于 new ArrayList()

引用静态方法 - Class :: staticMethodName

一个使用Math.max()静态方法的例子。

List<Integer> integers = Arrays.asList(1,12,433,5);
 
Optional<Integer> max = integers.stream().reduce( Math::max );
 
max.ifPresent(value -> System.out.println(value));

输出:

433

引用实例方法从实例 - ClassInstance :: instanceMethodName

在上面的例子中,我们System.out.println(value)用来打印找到的最大值。我们可以System.out::println用来打印值。

List<Integer> integers = Arrays.asList(1,12,433,5);
 
Optional<Integer> max = integers.stream().reduce( Math::max );
 
max.ifPresent( System.out::println );

输出:

433

引用类类型的实例方法 - Class :: instanceMethodName

在这个例子中,s1.compareTo(s2)被称为String::compareTo。

List<String> strings = Arrays
.asList("how", "to", "do", "in", "java", "dot", "com");
 
List<String> sorted = strings
.stream()
.sorted((s1, s2) -> s1.compareTo(s2))
.collect(Collectors.toList());
 
System.out.println(sorted);
 
List<String> sortedAlt = strings
.stream()
.sorted(String::compareTo)
.collect(Collectors.toList());
 
System.out.println(sortedAlt);

输出:

[com,do,dot,how,in,java,to]
[com,do,dot,how,in,java,to]

参考构造函数 - Class :: new

可以更新第一种方法来创建从1到100的整数列表。使用lambda表达式相当容易。要创建一个新的实例ArrayList,我们有使用ArrayList::new。

List<Integer> integers = IntStream
.range(1, 100)
.boxed()
.collect(Collectors.toCollection( ArrayList::new ));
 
Optional<Integer> max = integers.stream().reduce(Math::max);
 
max.ifPresent(System.out::println);

输出:

99

这是Java 8 lambda增强功能中的4种类型的方法引用。

Java 8默认方法教程


我们了解了Lambda表达式和函数式接口。现在,让我们继续讨论,并谈谈另一个相关的功能,即默认方法。那么这对java开发者来说真的是革命性的。直到java 7,我们已经了解了很多关于接口的事情,所有这些事情都在我们的头脑中,每当我们编写代码或设计应用程序。在引入默认方法后,其中一些概念将从java 8大幅改变。


java 8中的默认方法是什么?

默认方法使您能够向库的接口添加新功能,并确保与旧版本的这些接口编写的代码的二进制兼容性。 顾名思义,java 8中的默认方法是默认的。如果不覆盖它们,则它们将由调用者类调用的方法。它们在接口中定义。

我们来看一个例子:

public interface Moveable {
    default void move(){
        System.out.println("I am moving");
    }
}

可移动界面定义了一个方法move(); 并提供了默认实现。如果任何类实现了这个接口,那么它不需要实现它自己的move()方法。它可以直接调用instance.move();

public class Animal implements Moveable{
    public static void main(String[] args){
        Animal tiger = new Animal();
        tiger.move();
    }
}

Output: I am moving

而且如果类愿意自定义行为,那么它可以提供它自己的自定义实现并覆盖该方法。现在自己定制的方法会被调用。

public class Animal implements Moveable{
    
    public void move(){
        System.out.println("I am running");
    }
    
    public static void main(String[] args){
        Animal tiger = new Animal();
        tiger.move();
    }
}

Output: I am running

这不是在这里完成的。最好的部分来自以下好处:

1.静态默认方法:可以在接口中定义静态默认方法,该方法对于实现此接口的所有类的实例都可用。这使您更容易在库中组织帮助方法; 您可以将静态方法保留在同一接口中的接口,而不是单独的类中。这使您能够定义类的方法,然后与所有子类共享。

2.它们为您提供了一种非常需要的功能,即使在不接触代码的情况下也可以添加多个类的功能。只需在界面中添加一个默认方法即可实现。

为什么java 8中需要默认的方法?

这是下一个面试问题的好候选人。最简单的答案是在java中启用lambda表达式的功能。Lambda表达式基本上是函数式接口的类型。为了无缝地支持lambda表达式,所有的核心类都必须被修改。但是,像java.util.List这样的核心类不仅在JDK类中实现,而且还在数千个客户端代码中实现。核心课程的任何不相容的变化肯定会回火,根本不会被接受。

默认方法打破了这个死锁,并允许在核心类中添加对函数式接口的支持。我们来看一个例子。下面是一个添加到java.lang.Iterable的方法。

default void forEach(Consumer<? super T> action) {
	Objects.requireNonNull(action);
	for (T t : this) {
		action.accept(t);
	}
}

在java 8之前,如果你必须迭代一个java集合,那么你会得到一个迭代器实例,并调用它的下一个方法,直到hasNext()返回false。这是常见的代码,已被我们日常使用的数千次使用。语法也是一样的。所以我们可以使它紧凑,所以它只需要一行代码,仍然像以前一样为我们做这个工作。以上功能是这样做的。

现在要对列表中的每个项目进行迭代和执行一些简单的操作,所有你需要做的是:

import java.util.ArrayList;
import java.util.List;

    public class Animal implements Moveable{
        public static void main(String[] args){
            List<Animal> list = new ArrayList();
            list.add(new Animal());
            list.add(new Animal());
            list.add(new Animal());
            
            //Iterator code reduced to one line
            list.forEach((Moveable p) -> p.move());
        }
    }

所以在这里,一个额外的方法已添加到列表中,而不会破坏它的任何自定义实现。很久以来,它一直非常需要java的功能。现在跟我们在一起

调用默认方法时如何解决冲突?

到现在为止还挺好。我们所有的基础知识都很好。现在转向复杂的事情。在java中,一个类可以实现N个接口。另外,接口也可以扩展另一个接口。如果任何默认方法在由单个类实现的两个这样的接口中声明。那么明显的类会混淆哪个方法来调用。

此冲突解决的规则如下:

1)最喜欢的是在类中被覆盖的方法。如果在匹配任何东西之前找到,它们将被匹配并调用。

2)选择“最具体的默认提供界面”中具有相同签名的方法。这意味着如果类Animal实现了两个接口,即可移动和可移动,以便Walkable扩展Moveable。那么Walkable是这里最具体的界面,如果方法签名匹配,将从这里选择默认方法。 3)如果“可移动”和“可步行”是独立的接口,那么会发生严重的冲突情况,编译器会抱怨然后无法确定。您必须通过提供额外的信息帮助编译器,从哪个接口应该调用默认方法。例如

Walkable.super.move();
//or 
Moveable.super.move();

Java 8函数式接口教程


我们了解了lambda表达式和功能接口的一些基础知识。在本教程中,我将在函数式接口的上下文中扩展主题。


什么是函数式接口

函数式接口是java 8中的新增功能,它们在其中只允许一个抽象方法。这些接口也称为单抽象方法接口(SAM接口)。这些可以使用Lambda表达式,方法引用和构造函数引用来表示。Java 8引入了一个注释,即@FunctionalInterface,当您注释的界面违反了函数式接口的合同时,可以将其用于编译器级错误。

我们来构建我们的第一个函数式接口:

    package functionalInterfaceExample;
     
    @FunctionalInterface
    public interface MyFirstFunctionalInterface {
    public void firstWork();
    }

我们尝试添加另一个抽象界面:

    package functionalInterfaceExample;
     
    @FunctionalInterface
    public interface MyFirstFunctionalInterface {
    public void firstWork();
    public void doSomeMoreWork();
    }

以上将导致下面给出的编译器错误:

    Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ MyFirstFunctionalInterface is not a functional interface
    multiple non-overriding abstract methods found in interface MyFirstFunctionalInterface

功能接口,错误

Do not do函数式接口

以下是允许的东西和哪些不在函数式接口中的列表。

A)如上所述,在任何函数式接口中只允许一种抽象方法。函数式接口中不允许使用第二抽象方法。如果我们删除@FunctionInterface注释,那么我们被允许添加另一个抽象方法,但它将使接口非功能接口。

B)即使省略@FunctionalInterface注解,函数式接口也是有效的。它仅用于通知编译器在界面内强制执行单个抽象方法。

C)在概念上,函数式接口只有一个抽象方法。由于默认方法有一个实现,它们不是抽象的。由于默认方法不是抽象的,您可以随意添加默认方法到您的函数式接口,尽可能多的你喜欢。 以下是有效的函数式接口:

    package functionalInterfaceExample;
     
    @FunctionalInterface
    public interface MyFirstFunctionalInterface {
    public void firstWork();
    default void doSomeMoreWork1(){
    //Method body
    }
    default void doSomeMoreWork2(){
    //Method body
    }
    }

D)如果接口声明一个覆盖java.lang.Object的公共方法之一的抽象方法,那么这个方法也不会计入接口的抽象方法计数,因为接口的任何实现都将具有java.lang.Object或别处。比如,Comparator是一个函数式接口,尽管它声明了两种抽象方法。为什么?因为这些抽象方法之一“equals()”在Object类中具有与public方法相同的签名。

例如下面的界面是一个有效的函数式接口。

    package functionalInterfaceExample;
     
    @FunctionalInterface
    public interface MyFirstFunctionalInterface {
    public void firstWork();
    @Override
    public String toString();                //Overridden from Object class
    @Override
    public boolean equals(Object obj);        //Overridden from Object class
    }