Java常见问题的简单解法
提示:总结于LeetCode书籍《Java常见问题的简单解法》,全书其实就是用Stream去通过函数式编程,更加简洁,快速,高效的解决实际问题。
文章目录
第一章 基础知识
一、lambda表达式
1.函数式接口
函数式接口是一种包含单一抽象方法的接口。可以通过顶级类,内部类,匿名内部类来完成。
匿名内部类实现:
以Runnable接口为例,该接口包含的单一抽象方法时run,他不传入任何参数并返回void。Thread类构造函数传入Runnable作为参数。
Runnable接口源码:
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface {@code Runnable} is used
* to create a thread, starting the thread causes the object's
* {@code run} method to be called in that separately executing
* thread.
* <p>
* The general contract of the method {@code run} is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
Thread参数为Runnable的构造函数源码:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
代码示例:
public class RunnableDemo {
public static void main(String[] args) {
new Thread(new Runnable() { ➊
@Override
public void run() {
System.out.println(
"inside runnable using an anonymous inner class");
}
}).start();
}
}
使用lambda表达式优化后代码示例:
new Thread(() -> System.out.println(
"inside Thread constructor using lambda")).start();
lambda表达式必须匹配接口中单一抽象方法签名的参数类型和返回类型,这被称为与方法签名兼容。因此,lambda表达式属于接口方法的实现,可以将其赋给接口类型的引用。如:
Runnable r = () -> System.out.println(
"lambda expression implementing the run method");
new Thread(r).start();
FilenameFilter接口实例:
File directory = new File("./src/main/java");
String[] names = directory.list((dir, name) -> name.endsWith(".java"));
System.out.println(Arrays.asList(names));
}
使用lambda表达式(隐性返回):
File directory = new File("./src/main/java");
String[] names = directory.list((File dir, String name) -> ➊
name.endsWith(".java"));
如果lambda表达式的实现多于一行,则需要使用大括号和显式返回语句:
File directory = new File("./src/main/java");
String[] names = directory.list((File dir, String name) -> { ➊
return name.endsWith(".java");
});
System.out.println(Arrays.asList(names));
二、方法引用
如果说lambda表达式本质上是将方法作为对象进行处理,那么方法引用就是将现有方法作为lambda表达式进行处理。
lambda表达式:
Stream.of(3, 1, 4, 1, 5, 9)
.forEach(x -> System.out.println(x));
方法引用:
Stream.of(3, 1, 4, 1, 5, 9)
.forEach(System.out::println);
将方法引用赋给函数式接口:
Consumer<Integer> printer = System.out::println; ➌
Stream.of(3, 1, 4, 1, 5, 9)
.forEach(printer);
方法引用包括三种形式
object::instanceMethod
引用特定对象的实例方法,如 System.out::println。
Class::staticMethod
引用静态方法,如 Math::max。
Class::instanceMethod
调用特定类型的任意对象的实例方法,如 String::length。
如果通过类名引用一个传入过个参数的方法,则上下文提供的第一个元素将作为方法的目标,其他元素作为方法的参数。
调用多个参数实例方法栗子:其中第一个参数s1作为了目标参数,而s2作为了方法的参数。
List<String> strings =
Arrays.asList("this", "is", "a", "list", "of", "strings");
List<String> sorted = strings.stream()
.sorted((s1, s2) -> s1.compareTo(s2))
.collect(Collectors.toList());
List<String> sorted = strings.stream()
.sorted(String::compareTo)
.collect(Collectors.toList());
再举一个栗子
String::length 通过类名访问实例方法
System.out::println 通过对象引用访问实例方法
Stream.of("this", "is", "a", "stream", "of", "strings")
.map(String::length)
.forEach(System.out::println);
对应的lambda表达式:
Stream.of("this", "is", "a", "stream", "of", "strings")
.map(s -> s.length())
.forEach(x -> System.out.println(x));
三、构造函数引用
给定一个字符串集合,通过lambda表达式或构造函数引用,可以将其中的每个字符串映射到Person类:
List<String> names =
Arrays.asList("Grace Hopper", "Barbara Liskov", "Ada Lovelace",
"Karen Spärck Jones");
//lambda表达式来调整构造函数
List<Person> people = names.stream()
.map(name -> new Person(name)) ➊
.collect(Collectors.toList());
// 或采用以下方案
//使用构造函数引用来实例化Person
List<Person> people = names.stream()
.map(Person::new) ➋
.collect(Collectors.toList());
1.复制构造函数
//befor 和 after 为同一个引用,改变after的值后,before的值也相应改变
Person before = new Person("Grace Hopper");
List<Person> people = Stream.of(before)
.collect(Collectors.toList());
Person after = people.get(0);
复制构造函数栗子:
//Person的复制构造函数,没有该构造函数使用Person::new返回的对象为Lsit<Object>
public Person(Person p) {
this.name = p.name;
}
//此时before和after为俩个对象,不同的引用
Person before = new Person("Grace Hopper");
List<Person> people = Stream.of(before).map(Person::new)
.collect(Collectors.toList());
Person after = people.get(0);
2.可变参数构造函数
可变参数构造器:
public Person(String... names) {
this.name = Arrays.stream(names)
.collect(Collectors.joining(" "));
}
可变参数构造器的应用:
names.stream()
.map(Person::new)
.collect(Collectors.toList());
得到的结果:
3.数组
构造函数引用也可以和数组一起使用。
Person[] people = names.stream()
.map(Person::new)
.toArray(Person[]::new);
四、函数式接口
关键字abstract很重要,接口中的所有方法被默认为抽象方法,不需要为他们添加abstract,同时也默认为public,所以也可省略。
举个栗子:
@FunctionalInterface
public interface PalindromeChecker {
//省略了public abstract
boolean isPalidrome(String s);
}
注:@FunctionalInterface有俩个作用:
1.会触发编译时校验,有助于确保接口符合要求。如果接口不包含或包含多个抽象方法,程序将提示编译错误。
2.会在javadoc中生成以下语句:
Functional Interface:
This is a functional interface and can therefore be used as the assignment
target for a lambda expression or method reference.
函数式接口同样可以使用default和static方法,由于这俩种方法都有相应的实现,他们与“仅包含一个抽象方法”的要求并不矛盾,举个栗子:
该函数式接口包含了静态方法和默认方法
@FunctionalInterface
public interface MyInterface {
int myMethod();
// int myOtherMethod(); 该行若不注释,则不是函数式接口
default String sayHello() {
return "Hello, World!";
}
static void myStaticMethod() {
System.out.println("I'm a static method in an interface");
}
}
接口可以继承一个或多个接口,所以若一个现有接口继承了函数式接口后,又添加了其他抽象方法,则该接口将不再是函数式接口,如:
//该接口不在是函数式接口,无法使用lambda表达式
public interface MyChildInterface extends MyInterface {
int anotherMethod(); ➊
}