专注IT技术、资源分享!
Java8面试不完全指南
Java8面试不完全指南

Java8面试不完全指南

目录 隐藏

这期文章给大家整理了Java 8的一些重要面试问题。多年来,Java发生了很大的变化,Java 8引入了许多新特性,在准备Java面试时,您需要了解这些新特性。

在Java 8中引入了什么新特性?

在Java 8中添加了许多新特性。以下是一些重要的功能:

  • Lambda表达式
  • Stream API
  • 接口默认方法
  • 函数式接口
  • Optional
  • 方法引用
  • 新的Date API
  • Nashorn,一个JavaScript引擎

使用Java 8的主要优点是什么?

  • 更紧凑的代码
  • 更具可读性和可重用性的代码
  • 更可测试的代码
  • 并行操作

Lambda表达式是什么?

Lambda表达式是一个匿名函数,它有一组参数、一个Lambda(->)和一个函数体。

Lambda表达式的结构

(Argument List) ->{expression;}
(Argument List) ->{statements;}

使用lambda表达式实现线程代码:

public class ThreadSample {
public static void main(String[] args) {
 // 未使用lambda
 new Thread(new Runnable() {
  @Override
  public void run() {
   System.out.println("Thread is started");
  }
}).start();
 // 使用lambda
 new Thread(()->System.out.println("Thread is started")).start();
}

}

你能解释Lambda表达式的语法吗?

可以将Lambda表达式的结构分为三部分:

参数列表

Lambda表达式可以有0个或多个参数。

()->{System.out.println("Hello")}; //没有参数 
(int a)->{System.out.println(a)} //一个参数
(int a,int b)-> {a+b};//两个参数

您可以选择不声明参数类型,因为它可以从上下文推断出来。

(a,b)->{a+b};

当然,不支持只声明一个参数类型,会编译报错。

当只有一个参数时,如果它的类型是推断的,则不强制使用括号。

a->{System.out.println(a)}; 

函数体

  • lambda表达式的函数体可以是一个表达式或语句;
  • 如果函数体中只有一条语句,则不需要花括号,匿名函数的返回类型与函数体表达式的返回类型相同;
  • 如果有多个语句,那么它应该在花括号中,并且匿名函数的返回类型与代码块中的value return相同,如果没有返回,则为void。

什么是函数式接口?

函数式接口是那些只能有一个抽象方法的接口。它可以有静态方法、默认方法或者可以覆盖Object的类方法。

java中有许多函数式接口,如Comparable、Runnable;

由于在Runnable中只有一个方法,因此它被认为是功能接口。

lambda表达式和函数式接口是如何关联的?

Lambda表达式只能应用于函数接口的抽象方法。

例如:

Runnable接口因为只有一个run方法,所以可以按照下面方式使用:

Thread t1=new Thread(()->System.out.println("In Run method"));

在这里,我使用了以Runnable作为参数的Thread构造方法。在这里没有指定任何方法名,因为Runnable只有一个抽象方法,java将隐式创建匿名Runnable并执行run方法。从功能上和下面代码相同。

Thread t1=new Thread(new Runnable() { 
   @Override
   public void run() {
       System.out.println("In Run method");
  }
});

可以创建自己的函数式接口吗?

可以。Java可以隐式地标识函数接口,也可以使用@FunctionalInterface注解声明它。

public interface Printable {
   void print();
   default void printColor()
  {
       System.out.println("Printing Color copy");
  }
}

创建名为functionalinterfacemain的主类,则可以用lambda表达式与Printable关联。

public class FunctionalIntefaceMain {
public static void main(String[] args)
{
FunctionalIntefaceMain pMain=new FunctionalIntefaceMain();
pMain.printForm(() -> System.out.println("Printing form"));
}
public void printForm(Printable p)
{
p.print();
}
}

java 8中的方法引用是什么?

方法引用是函数式接口的引用方法。它只不过是lambda表达式的紧凑方式。您可以简单地用方法引用替换lambda表达式。

// 语法:class::methodname
// 例如:
p->System.out:println

什么是Optional?为什么以及如何使用它?

Java 8引入了新的类Optional。这个类的引入基本上是为了避免java中的NullPointerException。

Optional类封装了存在或不存在的可选值。它是一个包装对象,可以用来避免nullpointerexception。

比如现在有一个方法来获得字符串中的第一个非重复字符(这里的代码逻辑不是重点)。

public static Character getNonRepeatedCharacter(String str) {
Map<Character, Integer> countCharacters = new LinkedHashMap<Character, Integer>();
for (int i = 0; i < str.length() - 1; i++) {
Character c = str.charAt(i);
if (!countCharacters.containsKey(c)) {
countCharacters.put(c, 1);
} else {
countCharacters.put(c, countCharacters.get(c) + 1);
}
}
for (Entry<Character, Integer> e : countCharacters.entrySet()) {
if (e.getValue() == 1)
return e.getKey();
}
return null;
}

通过下面的方式对这个方法调用。

Character c=getNonRepeatedCharacter("SASAS");
System.out.println("Non repeated character is :"+c.toString());

你看到问题了吗? getNonRepeatedCharacter("SASAS")没有非重复字符,因此它将返回null,我们调用c.toString(),所以它显然会抛出NullPointerException

你可以使用Optional来避免这个NullPointerException。将方法改为返回可选对象而不是String

public static Optional<Character> getNonRepeatedCharacterOpt(String str) {
Map<Character, Integer> countCharacters = new LinkedHashMap<Character, Integer>();
for (int i = 0; i < str.length(); i++) {
Character c = str.charAt(i);
if (!countCharacters.containsKey(c)) {
countCharacters.put(c, 1);
} else {
countCharacters.put(c, countCharacters.get(c) + 1);
}
}
for (Entry<Character, Integer> e : countCharacters.entrySet()) {
if (e.getValue() == 1)
return Optional.of(e.getKey());

}
return Optional.ofNullable(null);
}

当上面的方法返回Optional,你已经知道它也可以返回空值。

你可以调用OptionalisPresent方法来检查Optional中是否包含了任何值。

Optional<Character> opCh=getNonRepeatedCharacterOpt("SASAS");
if(opCh.isPresent())
System.out.println("Non repeated character is :"+opCh.toString());
else
{
System.out.println("No non repeated character found in String");
}

如果在Optional中没有值,它将简单地打印“No non repeated character found in String”

默认方法是什么?

默认方法是接口中具有方法体并使用Default关键字的方法。默认方法在Java 8中引入,主要目的是为了“向后兼容”。

Predicate和Function的区别是什么?

两者都是函数式接口。

Predicate<T>是一个单参数函数式接口,它返回true或false。这可以用作lambda表达式或方法引用的赋值目标。

Function<T,R>也是单参数函数接口,但它返回一个Object。这里T表示函数的输入类型,R表示返回值类型。也可以用做lambda表达式或方法引用的赋值目标。

你知道Java 8中引入的日期和时间API吗? 旧的日期和时间API有什么问题?

旧的日期和时间API的问题:

线程安全: java.util.Date是可变的,并且不是线程安全的。java.text.SimpleDateFormat也不是线程安全的。新的Java 8日期和时间api是线程安全的。

性能: Java 8的新api在性能上优于旧的api。

可读性更强: 像Calendar和Date这样的旧api设计得很差,很难理解。Java 8日期和时间api易于理解并符合ISO标准。

你能说一些Java 8的Date和Time的api吗?

LocalDateLocalTimeLocalDateTime是Java 8的核心API类。

顾名思义,这些类是使用系统上下文的本地类。它表示本地系统上下文中的当前日期和时间。

如何使用Java 8日期和时间API获取当前日期和时间?

可以简单地使用LocalDatenow()方法来获取今天的日期。

LocalDate currentDate = LocalDate.now();
System.out.println(currentDate);

在Java 8中有永久代吗? 你知道MetaSpace吗?

在Java 7之前,JVM使用一个称为PermGen的区域来存储类。它在Java 8中被删除,并被MetaSpace取代。

MetaSpace相对于PermGen的主要优势:

  • PermGen的最大大小是固定的,不能动态增长,但MetaSpace可以动态增长,没有任何大小限制。

接下来的7个问题将基于下面的class。

public class Employee {
private String name;
private int age;

public Employee(String name, int age) {
super();
this.name = name;
this.age = age;
}
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 String toString()
{
return "Employee Name: "+name+" age: "+age;
}
}

给定一个List<Employee>,过滤所有年龄大于20岁的员工,并打印员工姓名。 (使用Java 8 APIs)

List<String> employeeFilteredList = employeeList.stream()
.filter(e->e.getAge()>20)
.map(Employee::getName)
.collect(Collectors.toList());

根据员工名单,数一下有多少员工年龄在25岁?

List<Employee> employeeList = createEmployeeList();
long count = employeeList.stream()
.filter(e->e.getAge()>25)
.count();
System.out.println("Number of employees with age 25 are : "+count);

根据员工名单,找出名字叫“Mary”的员工?

List<Employee> employeeList = createEmployeeList();
Optional<Employee> e1 = employeeList.stream()
.filter(e->e.getName().equalsIgnoreCase("Mary")).findAny();
if(e1.isPresent())
System.out.println(e1.get());

给出员工名单,找出员工的最高年龄?

List<Employee> employeeList = createEmployeeList();
OptionalInt max = employeeList.stream().
mapToInt(Employee::getAge).max();
if(max.isPresent())
System.out.println("Maximum age of Employee: "+max.getAsInt());

给一个List<Employee> ,根据年龄对所有员工进行排序? 使用java 8 api

List<Employee> employeeList = createEmployeeList();
employeeList.sort((e1,e2)->e1.getAge()-e2.getAge());
employeeList.forEach(System.out::println);

给定员工列表使用java 8将所有员工的name用“,”连接

 List<Employee> employeeList = createEmployeeList();
List<String> employeeNames = employeeList
.stream()
.map(Employee::getName)
.collect(Collectors.toList());
String employeeNamesStr = String.join(",", employeeNames);
System.out.println("Employees are: "+employeeNamesStr);

给定员工列表,根据员工姓名对他们进行分组?

可以使用Collections.groupBy()按员工名对员工列表进行分组。

List<Employee> employeeList = createEmployeeList();
Map<String, List<Employee>> map = employeeList.stream() .collect(Collectors.groupingBy(Employee::getName));
map.forEach((name,employeeListTemp)->System.out.println("Name: "+name+" ==>"+employeeListTemp));

流中的中间操作和终端操作的区别?

  • 中间操作本质上是惰性的,不会立即执行。
  • 终端操作不是惰性操作,一旦遇到它们就立即执行。
  • 中间操作被记录并在终端操作被调用时被调用。
  • 所有的中间操作都返回流,因为它只是将流转换成另一个流,而终端操作不会返回流。

中间操作的例子有:

  • filter(Predicate)
  • map(Function)
  • flatmap(Function)
  • sorted(Comparator)
  • distinct()
  • limit(long n)
  • skip(long n)

终端操作有:

  • forEach
  • toArray
  • reduce
  • collect
  • min
  • max
  • count
  • anyMatch
  • allMatch
  • noneMatch
  • findFirst
  • findAny

给定一个数字列表,从列表中删除重复的元素?

可以使用stream,然后使用Collections.toSet()方法来收集到set中。

Integer[] arr=new Integer[]{1,2,3,4,3,2,4,2};
List<Integer> list = Arrays.asList(arr);
Set<Integer> setWithoutDups = list.stream().collect(Collectors.toSet());
setWithoutDups.forEach((i)->System.out.print(" "+i));

也可以使用distinct来避免重复,如下所示。

Integer[] arr=new Integer[]{1,2,3,4,3,2,4,2};
List<Integer> list = Arrays.asList(arr);
List<Integer> listWithoutDups = list.stream().distinct().collect(Collectors.toList());
listWithoutDups.forEach((i)->System.out.print(" "+i));

Stream的findFirst()和findAny()的区别?

findFirst将始终返回流中的第一个元素,而findAny则允许从流中选择任何元素。

findFirst具有确定性行为,而findAny则是非确定性行为。

给定一个数字列表,对其进行平方,并过滤大于10000的数字,然后求其平均值。 (只适用于Java 8 APIs)

可以使用map函数对数字进行平方,然后使用filter筛选小于10000的数字。在这种情况下,我们将使用average ()作为终止函数。

Integer[] arr=new Integer[]{100,24,13,44,114,200,40,112};
List<Integer> list = Arrays.asList(arr);
OptionalDouble average = list.stream()
.mapToInt(n->n*n)
.filter(n->n>10000)
.average();
if(average.isPresent()){
System.out.println(average.getAsDouble());
}

Optional在Java 8中有什么用?

Java 8可选可以用来避免NullPointerException。

Predicate是什么函数式接口?

Predicate是一个返回true或false的单参数函数。它有返回布尔值的测试方法。

当我们在上面的例子中使用filter时,我们实际上是将Predicate函数接口传递给它。

Consumer是什么函数式接口?

Consumer是一个不返回任何值的单参数函数接口。

当我们在上面的例子中使用foreach时,我们实际上是将消费者函数接口传递给它。

Supplier是什么函数式接口?

Supplier是一个函数接口,它不接受任何参数,但使用get方法返回值。


以上是本期的所有内容,如果有帮助点个赞是对我最大的鼓励。

发表回复

您的电子邮箱地址不会被公开。