这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战
这期文章给大家整理了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,你已经知道它也可以返回空值。
你可以调用Optional的isPresent方法来检查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的问题:
线程安全: 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吗?
LocalDate、LocalTime和LocalDateTime是Java 8的核心API类。
顾名思义,这些类是使用系统上下文的本地类。它表示本地系统上下文中的当前日期和时间。
如何使用Java 8日期和时间API获取当前日期和时间?
可以简单地使用LocalDate的now()方法来获取今天的日期。
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岁的员工,并打印姓名
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);
给定List<Employee>将所有员工的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));
stream的中间操作和终端操作的区别?
- 中间操作本质上是惰性的,不会立即执行。
- 终端操作不是惰性操作,一旦遇到它们就立即执行。
- 中间操作被记录并在终端操作被调用时被调用。
- 所有的中间操作都返回流,因为它只是将流转换成另一个流,而终端操作不会返回流。
中间操作的例子有:
- 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则是非确定性行为。
给定一个Integer[],先平方,过滤大于10000的数字,求其平均值
可以使用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方法返回值。
以上是本期的所有内容,如果有帮助点个赞是对我最大的鼓励。