Java 基础面试300题 (201-230)
201.下面代码片段的输出是什么?
Predicate<Integer> numberChecker = (num)–> num > 20;
int input = 10;
System.out.println(input+” greater than 20–”+numberChecker.test(input)); //Line 1
input = 40;
System.out.println(input+” greater than 20–”+numberChecker.test(input)); //Line 2
上述代码片段使用了Predicate
内置函数接口,该接口接受一个任何数据类型的参数,并返回一个布尔值, 用于测试一个条件。在上述情况下,代码创建一个名为numberChecker
的谓词,该谓词接受整数值。它通过lambda表达式实现,该表达式检查输入数是否大于20
。第1行使用输入数据10
应用谓词值,而第2行使用输入数据40
应用谓词值,因此代码会打印以下输出:
10 greater than 20:false
40 greater than 20:true
202. 使用哪个内置函数接口将字符串转换为大写?
Java 有一个名为UnaryOperator
的内置函数接口,它接受任何数据类型的参数,并返回相同数据类型的结果。它有一个名为apply
的方法,可以对输入值进行操作。因此,可以使用它将字符串转换为大写。如下代码所示:
UnaryOperator<String> converter = str–> str.toUpperCase();
System.out.println(converter.apply(“hello,world”));
在这里,代码创建了一个名为converter
的UnaryOperator
类型的函数接口,并使用lambda表达式实现该接口, lambda表达式的主体逻辑调用字符串的toUpperCase()
方法将字符串转换为大写。因此,上述代码会打印以下输出:
HELLO WORLD
203.下面的代码有效吗?
@FunctionalInterface
public interface Interface1 {
public void doSomething();
}
public interface Interface2 extends Interface1{
public void doSomethingElse();
}
上面代码是有效的。 代码声明了一个名为Interface1
的函数接口, 该接口有一个名为doSomething
的抽象方法,然后定义了另一个名为Interface2
的接口(不一定是函数接口),它扩展了Interface1
,并有一个doSomethingElse
的抽象方法。 接口Interface2
现在有两个抽象方法,doSomething()
和doSomethingElse()
,因为它没有用@FunctionalInterface
来注解,所以有多个抽象方法是可以的。但如果在Interface2
上使用@FunctionalInterface
进行注解,代码将导致编译错误。
204. 有哪些Java 8之前的接口由 Java 8转换成了函数接口。?
在Java 8之前,有一些接口已经有一个抽象方法。Java 8通过在这些接口上指定@FunctionalInterface
注解将这些接口指定为函数接口。因此,这些接口中的抽象方法都可以通过lambda表达式来实现。下表列出了这些接口:
接口 | 描述 |
---|---|
java.lang.Runnable | 用于创建线程。 |
java.util.cocurrent.Callable | 用于创建具有返回值的线程。 |
java.util.Comparator | 用于比较值。 |
java.io.FileFilter | 用于过滤文件。 |
205. Java 8为什么为基础数据类型内置了一些专门的函数接口 ,如IntSupplier
、BooleanSupplier
等?
主要原因是提高性能,减少内存使用。 使用这些专门的内置接口可以消除了自动装箱的需求,从而提高了性能。考虑如下需求,随机生成一个小于100的整数 。 下面代码使用通用的 Supplier<Integer>
实现上述需求,但是因为是通用的 Supplier
,它只能够处理对象类型,而不能够处理基础数据类型,所以必须将Random
类返回的值包装为整数,带来了一定的性能损失。
Supplier<Integer> integerSupplier = ()–> new Integer(new Random().nextInt());
现在考虑以下代码,它使用了专门的IntSupplier
函数接口,同样返回一个小于100的随机整数:
IntSupplier integerSupplier2 = ()–> new Random().nextInt();
此时,没有必要将Random
类的返回值包装为Integer
了, 避免了自动装箱,性能更好,内存使用也更少 。
206.内置函数接口通常在哪里使用?
Java 8增加了流 API,有助于在集合上执行批量操作。内置的函数接口通常用作流API中方法的参数。流方法将函数接口指定的操作应用于流中的每个元素。例如,Stream
接口有一个过滤方法,可以根据某些条件过滤集合。该条件通过Predicate
接口指定,该接口是一个内置的函数接口。它检查输入流中的每个值是否与谓词指定的条件匹配。同样,Stream
接口有一个名为map
的方法,可以转换Stream
中的元素。它接受内置函数接口的实例为参数, 然后将函数接口指定的操作应用于流中的每个元素。
207.什么是lambda表达式?有什么优点?
Lambda表达式用于指定没有名称的函数。它主要用于实现函数接口中的抽象方法。Lambda表达式有如下几个优点:
-
使代码简洁。在Java 8之前,开发人员必须编写大量代码来实现一个接口,代码冗长而繁琐。Lambda表达式消除了与实现接口相关的大多数样板代码。
-
lambda表达式允许将代码作为方法的参数传递。
-
lambda 可以轻而易举地为同一方法提供不同的实现。
-
Java 8增加的其他一些新功能,如
for-each
遍历和Stream
框架也大量使用了lambda表达式。
208.Lambda表达式和函数接口是什么关系?
函数接口是具有单一抽象方法的接口。而Lambda表达式用于实现函数接口中指定的抽象方法。
由于Java是一种面向对象的语言,lambda表达式不能单独存在,需要与一个对象相关联。因此,Java 8引入了函数式接口,并强制lambda表达式仅与函数式接口一起使用,用来实现函数接口的抽象方法。 因此lambda表达式成为函数接口一个具体的实现,成为OO的一部分。
函数接口和lambda表达式一起有助于编写干净简洁的Java代码。
Java 8增加的函数接口使Java 语言也支持某种程度的函数式编程。
209.解释lambda表达式的语法。
下面是lambda表达式的语法:
(parameters)–> {lambda body}
lambda表达式由输入参数,lambda运算符 ->
和表达式主体三部分构成。 lambda表达式可以接收零个或更多参数。如果它不接受任何参数,则需要使用空括号。如果它接受单个参数,则无需在括号中指定该参数。如果它接受多个参数,则需要在括号中指定并用逗号分隔。lambda主体是普通的Java代码,可以包含任意数量的语句。如果lambda主体只是一个单语句,则可以省略主体的大括号。
210.给出一些lambda表达式的例子。
以下是lambda表达式的一些示例:
示例1-单个参数,无返回值
str–> System.out.println(str);
这个lambda表达式接受一个参数,并使用Sysout 语句打印。它不返回任何值。
示例2-多个参数,有返回值
(num1, num2)–> num1+num2;
这个lambda表达式接受两个参数,并返回它们的相加的结果。代码中并未明确指定return
关键字。
示例3-无参数,有返回值
()–>{
//doing something here
return 0;
}
这个lambda表达式不接受任何参数,它返回值0,代码显示使用了return
关键字。
211. 如何使用Lambda表达式实现函数接口? 。
函数接口是只有一个抽象方法的接口。下面代码定义了一个名为StringConverter
的函数接口:
@FunctionalInterface
public interface StringConverter {
public String convert(String s);
}
它的唯一抽象方法名是 convert
,这个方法接受一个String
参数并返回String
。下面的Lambda表达式提供了该函数接口的一个具体实现,它将输入字符串转换为大写并返回:
StringConverter converter = (str)–> str.toUpperCase();
212.如何使用不同的lambda表达式以不同的方式实现相同的函数接口?
考虑以下StringConverter
函数接口:
@FunctionalInterface
public interface StringConverter {
public String convert(String s);
}
我们可以通过不同的lambda表达式为convert
方法提供不同的实现,如下是一种实现, 它通过lambda表达式将输入字符串转换为大写并返回它:
StringConverter upperCaseconverter = str–> str.toUpperCase();
String result = upperCaseconverter.convert(“Hello”);
下面是另外一种实现, 它通过lambda表达式将输入字符串转换为小写并返回 :
StringConverter lowerCaseconverter = str–> str.toLowerCase();
String result = lowerCaseconverter.convert(“Hello”);
因此,不同的lambda表达式为StringConverter
函数接口提供了不同的实现。
213.下面lambda表达式有错误吗, 如何修复?
(str)–> return str.toUpperCase();
上面的lambda表达式使用了显示的 return
关键字, 但没有将 lambda主体包含在大括号中,因此错误。如下是正确代码:
(str)–> {return str.toUpperCase();};
也可以直接删除return
关键字,而使用隐式返回,如下代码:
(str)–> str.toUpperCase();
214.以下代码正确吗?
public void generateSquareRoot(int num) {
Function<Integer, Double> squareRoot = (num)–> Math.sqrt(num);
}
上面的代码定义了一个名为generateSquareRoot
的方法,它接受整数值为参数,使用内置的函数接口Function
,并通过lambda表达式实现它。
上述代码无效,会导致编译错误。这是因为, 在lambda表达式中声明的参数与其包裹方法中声明的参数或局部变量同名。
generateSquareRoot
方法接受一个名为num
的参数,在lambda表达式中又使用了相同的名称声明参数,这会导致编译错误。要修复此问题,只需要将方法参数或lambda表达式参数重新命名为另外一个不同的名称即可。
215.编写一个函数式接口,使其成为以下lambda表达式的目标类型。
(input)–> input.toUpperCase();
上述代码定义了一个lambda表达式,它接受一个String
类型为参数, 将其转换为大写并返回,返回类型也是String
。 因此,该lambda表达式可用于实现以下函数接口:
@FunctionalInterface
public interface Interface1 {
public String doSomething(String input);
}
此代码定义了一个名为Interface1
的函数接口,它有一个doSomething
方法,接受一个字符串为参数,并返回一个字符串 。
216. 如何使用lambda表达式创建新线程?
java.lang.Runnable
接口用于创建新线程。Java 8将此接口指定为函数接口,因此可以使用lambda表达式实现它。如下代码所示 :
Runnable r = ()–> {
System.out.println(“Starting Thread..”);
//some more code
};
Thread myThread = new Thread(r);
myThread.start();
在上面代码中, 我们通过一个简单的lambda表达式实现了Runnable
接口,然后使用此Runnable
对象创建一个myThread
线程对象,调用myThread.start()
方法, 创建、并运行一个新的线程。
217. 创建流的有哪几种不同方式?
如下几种方法都可以创建流:
- 直接从一组值创建,如下所示:
Stream<String> values = String.of(“Red, “Blue”);
- 从实现了
Collections
接口的对象中创建流,如下所示:
List<Integer> numbers = Arrays.asList(1,2,3,4);
Stream<Integer> stream = numbers.stream();
- 从数组创建流,如下所示:
int[] numbers = new int[] {1,2,3,4};
IntStream arrayStream= Arrays.stream(numbers);
218.用代码示例解释流的filter
操作。
Stream.filter()
操作可用于根据某些条件对流进行过滤。 它创建了一个新的流,由与指定过滤条件匹配的元素组成。它的输入参数是Predicate
函数接口,根据其指定的谓词条件过滤流中的元素。如下代码示例:
Stream<Integer> numbers = Stream.of(7,3,9,6,1);
Stream<Integer> output = numbers.filter(num–> num > 5);
上述代码创建一个整数值的流,然后用lambda表达式调用filter()
方法进行过滤,该表达式检查输入的数是否大于5。因此,输出流将由仅大于5的数字组成。
219. Java流有哪些主要不同类型的操作?
Java流操作可以分类如下:
类型 | 描述 |
---|---|
中间操作 | 中间操作在流上操作并产生流输出。由于中间操作生成流,因此它们可以链接在一起执行一系列操作。一些中间操作的例子包括filter 、map 、sorted 。 |
终止操作 | 终止操作在流上操作,但产生非流结果, 可以产生任何数据类型的结果。终止操作不能链接。当一系列流操作被链接时,终止操作通常是最后一个操作。一些终止操作的例子包括count 、anyMatch 、allMatch 、collect 。 |
220.什么是并行流? 如何创建并行流?
除了顺序流外,Java 流API还支持并行流。并行流在流中的元素上同时运行,从而具有更好的性能。Java 8为Collection
接口增加了一个名为parallelStream()
的新方法, 该方法可用于获取与底层集合相对应的并行流。如下代码示例:
List<String> colours = Arrays.asList(“Red”,”Green”,”Blue”);
Stream<String> parallelStream = colours.parallelStream();
此代码首先创建一个名为colors
的字符串列表,然后调用parallelStream()
方法, 返回与列表对应的并行流。随后在并行流上执行的任何进一步操作都将在流中的元素上同时执行。
221.如何将流转换回为集合?
Java流接口有一个名为collect()
的方法,可用于将流转换回集合。如下代码所示 :
Stream<Integer> numbers = Stream.of(7,3,9,6,1);
List<Integer> filteredList = numbers.filter(num–> num > 5).collect(Collectors.toList());
上述代码首先创建一个整数流 , 然后,调用filter()
方法来获取大于5的数字。之后,它调用collect()
方法, 该方法接受一个收集器 Collector
类型参数, Collectors.toList()
方法返回一个将输入流转换为列表的收集器。因此最终,filteredList
是一个由大于5的数字构成的列表。Collectors
类中还有其他方法,如Collectors.toSet
、Collectors.toMap
、Collectors.toCollection
等等,可用于将流转换为(回)其他类型的集合。
222.以下代码片段的输出是什么?
Stream<Integer> numbers = Stream.of(10,15,20,25);
boolean anyMatch = numbers.anyMatch(num–> num%2==0);
System.out.println(anyMatch);
上面的代码片段使用流接口的anyMatch()
方法。如果输入流中的任何元素与指定条件匹配,则此方法返回true
。在上面情形,它检查输入流中是否有偶数。由于流中有偶数,所以anyMatch()
方法返回true
。因此,此代码打印以下输出:
true
223. 流和集合有什么区别?
流和集合之间有下面几个区别:
-
集合实际上存储数据。 另一方面,流不存储任何数据,它只是对底层数据执行一些操作。
-
对集合执行操作时,集合会被修改。对在集合上获得的流执行操作时,底层集合不会被修改。
-
集合可以在创建后进行修改。另一方面,流在构建后无法修改。
-
可以多次遍历集合,而流只能遍历一次。 如果希望再次遍历流,这是不可能的,需要从底层数据源创建一个新的流并再次遍历。
224. 如何使用Java流操作从一个字符串列表中删除重复项, 并转换为大写?
下面代码片段演示了如何实现上述要求:
List<String> fruits =
Arrays.asList(“Apple”,”Mango”,“Banana”,”Apple”, ”Orange”, “Mango”,”Strawberry”); //Line 1
List<String> uniqueFruits = fruits.stream().distinct().map(str–> str.toUpperCase()).collect(Collectors.toList()); //Line 2
uniqueFruits.forEach(str–> System.out.println(str));//Line 3
上述代码在第 1行首先创建一个名为fruits
的字符串值列表。第2行调用fruits.stream()
,生成一个字符串流。然后,调用流的 distinct()
操作删除重复项。distinct()
方法返回一个新的流,该流仅由输入流中的不同元素组成。 然后调用流的map()
操作。Map()
操作用于转换流。它接受函数实例(可以是lambda表达式)作为参数,并将函数应用于流中的每个元素。在上述情形,使用lambda表达式将流中的每个字符串转换为大写。最后,调用collect()
方法,对流进行收集并将结果转换为列表 uniqueFruits
, 并在控制台上打印该列表中的所有元素。因此,代码最终输出如下:
APPLE
MANGO
BANANA
ORANGE
STRAWBERRY
225.可以将数组转换为流吗?
是的,可以将数组转换为流。Java 8在java.util.Arrays
类中添加了stream()
方法,用于从数组生成流。 这个方法有多个重载版本,接受不同数据类型的数组,并将数组转换为流。以下代码
将一个整数数组转换为IntStream
流:
int[] numbers = {10,20,30};
IntStream numberStream = Arrays.stream(numbers);
226. 考虑下面 Employee
类 :
public class Employee {
private String name;
private int salary;
}
现在假设有以下员工对象的列表:
List<Employee> employees = new ArrayList<Employee>();
employees.add(new Employee(“John”,10000));
employees.add(new Employee(“Ana”,15000));
employees.add(new Employee(“Tia”,8000));
如何使用流API创建一个新列表,该列表仅包含员工工资,且按升序排列?
下面代码可用于实现上述需求:
List<Employee> sortedList = employees.stream().sorted((emp1, emp2)–> emp1.getSalary()–emp2.getSalary()).collect(Collectors.toList());
sortedList.forEach(emp–> System.out.println(emp.getSalary()));
上述代码首先在员工列表上获取流。然后,它调用排序方法sorted()
对流进行排序。sorted()
方法接受一个Comparator
为输入,并使用一个lambda表达式实现的Comparator
,lambda表达式的逻辑是比较Employee
对象的工资字段 。最后,调用collect()
方法将流转换回列表。 代码最终打印以下输出:
8000
10000
15000
20000
227.什么是方法引用?有什么优点?
方法引用是Java 8引入的新运算符。它是lambda表达式的快捷方式。 它由两个连续的冒号表示::
表示。
方法引用有如下几个优点:
-
可以避免代码重复。 有时,程序可能已经有一个方法,它的代码可能和 lambda表达式中的代码相同。在这种情况下可通过方法引用直接调用现有方法,而不是在lambda表达式中再次编写新代码 。
-
它们使代码简洁:方法引用比lambda表达式更进一步。它们通过将代码从lambda表达式中移到单独的方法中而使代码更加简洁。
228. Java有哪些不同类型的方法引用?
Java有如下4种不同类型的方法引用:
- 静态方法引用:当通过方法引用引用类的静态方法时,就会发生这种情况。语法如下:
class::staticmethod
- 实例方法引用:当通过方法引用引用类的实例方法时,就会发生这种情况。语法如下:
object::instancemethod
- 构造函数引用:当通过方法引用引用类的构造函数时,就会发生这种情况。语法如下:
class::new
- 任意方法引用:当访问类的实例方法但不指定任何特定对象时,就会发生这种情况。语法如下:
class::instancemethod
229.举例说明静态方法引用。
当通过方法引用运算符访问类的静态方法时,就会发生静态方法引用。以下代码演示了这一点:
public class MethodReferenceDemo {
public static boolean checkIfNumberGreaterThan8(int num) {
return num > 8;
}
public static void main(String[] args) {
Predicate<Integer> numberChecker =
MethodReferenceDemo::checkIfNumberGreaterThan8;
boolean flag = numberChecker.test(10);
}
}
在上述代码中 ,MethodReferenceDemo
类有一个名为checkIfNumberGreaterThan8()
的静态方法,它检查输入数字是否大于8,并相应地返回一个布尔值。在main
方法中,定义了一个谓词, 谓词的逻辑使用lambda表达式实现, 这个lambda表达式直接使用静态方法引用来引用已经存在的静态方法 checkIfNumberGreaterThan8()
,而不是重新编写新的代码(来实现谓词逻辑) 。由于checkIfNumberGreaterThan8()
是一个静态方法,因此使用类限定符后跟方法名方式引用。
230.考虑以下 Shape
类:
public class Shape {
private String name;
}
为了使以下代码有效,需要进行哪些更改?
List<String> shapeNames = Arrays.asList(“Circle”,”Triangle”);
List<Shape> shapes = shapeNames.stream().map(Shape::new).collect(Collectors.toList());
上面的代码创建一个名为shapeNames
的字符串列表,然后使用Stream 的map()
操作创建一个名为shapes
的Shape
对象列表, 代码使用构造函数引用创建一个新的Shape
对象,因此,Shape
类需要一个接受String
为输入参数构造函数,但是目前Shape
类没有这样的构造函数,因此上述代码会编译出错, 按如下方式增加构造函数即可:
public Shape(String name) {
this.name = name;
}