概要
Java8 には多くの新機能が含まれています。ここでは、よく使用される機能をいくつか簡単に紹介します。
- ラムダ式
- メソッドリファレンス
- ストリームAPI
- 日時クラス
- オプションクラス
- インターフェースのデフォルトメソッド
- JavaScriptエンジン
1.ラムダ式
公式説明: ラムダ式は匿名関数、つまり関数名のない関数です。
1.1 Lambdaの有用性
実際、私はこれを関数型インターフェイスの実装として理解することを好みます。
関数型インターフェイス (@FuctionalInterface): メソッドが 1 つだけあるインターフェイス
1.1.1 簡単な例
たとえば、次のインターフェイスがあるとします。
@FunctionalInterface
public interface Arithmetic {
int add(int a, int b);
}
このインターフェースを使用したい場合は、最初に実装クラスを定義し、次に実装クラスのインスタンスを作成する必要があります。コードは以下のように表示されます:
実装クラス:
public class ArithmeticImpl implements Arithmetic{
@Override
public int add(int a, int b) {
return a + b;
}
}
特定の呼び出し:
public class MainDemo {
@Test
public void test(){
Arithmetic arithmetic = new ArithmeticImpl();
int result = arithmetic.add(1, 2);
System.out.println(result);
}
}
ただし、ラムダ式を使用する場合は、実装クラスを定義するステップを直接回避し、ラムダ式をインターフェイス実装として直接使用できます。コードは以下のように表示されます:
public class MainDemo {
@Test
public void test2(){
Arithmetic arithmetic = (a, b) -> a + b;
int result = arithmetic.add(1, 2);
System.out.println(result);
}
}
1.1.2 別の Lambda の例
上記の違いがわからない場合は、より一般的な例として、スレッドの作成を考えてみましょう。
方法 1: Runnable 実装クラスを定義する
1. Runnable実装クラスを定義する
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable运行啦");
}
}
2. スレッドを作成して実行する
public class ThreadTest {
@Test
public void test1(){
Runnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
方法 2: 匿名実装クラス
匿名実装クラスを直接作成します。上記と比較すると、実装クラスを定義する手順が省略されています。
public class ThreadTest {
@Test
public void test2(){
Runnable runnableImpl = new Runnable() {
@Override
public void run() {
System.out.println("匿名实现类 运行啦");
}
};
Thread thread = new Thread(runnableImpl);
thread.start();
}
}
方法 3: ラムダ式
2 番目の方法については、もっと簡単にできるでしょうか?
答えはもちろん「はい」です。Runnable はメソッドを 1 つだけ持つ関数型インターフェイスであるため、メソッド本体が存在する限りメソッド名さえ省略できます。
したがって、ラムダ式を実装クラスとして直接使用できます。
public class ThreadTest {
@Test
public void test3(){
Runnable lambda = () -> System.out.println("lambda实现类 运行啦");
Thread thread = new Thread(lambda);
thread.start();
}
}
上記の 2 つの例を通して、ラムダ式がどこで使用できるかを理解できます。
つまり、関数型インターフェイス実装クラスが必要な場合はどこでも、lamba 式を実装クラスとして使用できます。
(さらに、ランバ式を使用できる場所は他にもあるかもしれませんが、私見では知識が限られているため、まだ明確ではありません)
1.2 ラムダ式の構造
その有用性を理解したら、その書き方も知る必要があります。lambda は匿名クラス関数であるため、メソッド宣言とメソッド本体も必要であり、その構造は次のとおりです。
(...) -> {...}
主に次の 3 つの部分に分かれています。
1. ()パラメータ部分
パラメータリストを宣言します。パラメータの型の数はインターフェイスで宣言されています。ここでパラメータの型を宣言する必要はなく、パラメータ名のみを指定する必要があります。
2. -> 矢印記号
パラメーター セクションとメソッド本体セクションを区別します。
3. {} メソッド本体部分
通常のメソッド本体ですので、通常のメソッド本体と同じように書くだけです。
特別な指示が必要な箇所を簡単に紹介し、いくつかの例を示します。
パラメータセクションの説明:
1. パラメータリストが空の場合、パラメータ部分は空にすることができます
() -> {System.out.println("hello")}
2. パラメータリストが 1 つの場合、括弧 () は省略できます。
(a) -> {System.out.println("hello " + a)}
a -> {System.out.println("hello " + a)}
3. 複数のパラメータがある場合は、変数名を 1 つずつ宣言するだけです
(a,b,c) -> {
System.out.println("hello " + a);
System.out.println("hello " + b);
System.out.println("hello " + c);
}
矢印部分:
矢印部分は固定形式であり必須であり、変更することはできません。
メソッド本体部分:
1. 通常の構造:
(a) -> {
System.out.println("hello " + c);
}
2. メソッド本体にコードが 1 行しかない場合は、{} 括弧を省略できます。
a -> System.out.println("hello " + a);
3. メソッドに戻りパラメータがある場合、通常は return キーワードを使用します。
(a,b,c) -> {
System.out.println("hello " + a);
System.out.println("hello " + b);
System.out.println("hello " + c);
return a + b + c;
}
4. メソッドが 1 行のみで逆パラメータがある場合、{} は省略でき、return キーワードも省略できます。
(a,b,c) -> a + b + c;
1.3 java.util.function包
JDK 1.8 API には多くの組み込み関数インターフェイスが含まれており、単純な列挙は次のとおりです。
名前 | タイプ | 説明 |
---|---|---|
消費者 | 消費者< T > | T オブジェクトを受け取りますが、値は返しません |
述語 | 述語< T > | T オブジェクトを受信してブール値を返す |
関数 | 関数< T、R > | T オブジェクトを受信して R オブジェクトを返す |
サプライヤー | サプライヤー< T > | T オブジェクト (ファクトリなど) を提供しますが、値は受け取りません |
単項演算子 | 単項演算子 | T オブジェクトを受信して T オブジェクトを返す |
二項演算子 | 二項演算子 | 2 つの T オブジェクトを受信し、T オブジェクトを返す |
多くの場合、上記のインターフェイスのいくつかの実装クラスを使用する必要がある場合、これらの場所でランバ式を直接使用できます。
たとえば、後で説明する Stream クラスはこれらのインターフェイスに大きく依存しているため、ラムダ式では Stream を使用する必要があるとよく言われます。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
Stream<T> filter(Predicate<? super T> predicate);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
...
}
2. メソッドのリファレンス
メソッド参照は、名前によってメソッドを指します。これにより、言語構造がよりコンパクトかつ簡潔になり、冗長なコードが削減されます。
メソッド参照では、コロン :: のペアを使用してメソッドを呼び出します。通常は次の 2 つの形式です。
ClassName::MethodName
ObjectName::MethodName
定義を直接理解するのは簡単ではないので、デモを直接見てみましょう。これは依然として上記の算術インターフェイスです。
@FunctionalInterface
public interface Arithmetic {
int add(int a, int b);
}
このインターフェイスを実装するには、lamba 式を使用します
public class ReferDemo {
@Test
public void test1(){
Arithmetic arithmetic = (a, b) -> a + b;
System.out.println(arithmetic.add(1,2));
}
}
上記のコードはすでに非常に単純ですが、さらに単純にすることはできますか? もちろん、a+b のメソッド本体は Integer.sum() インターフェイスで直接置き換えることができます。置換後のコードは次のようになります。
Arithmetic arithmetic = (a, b) -> Integer.sum(a,b);
この形式を見ると、今度はメソッド参照を使用し、上記のコードをメソッド参照に直接置き換えます。
public class ReferDemo {
@Test
public void test2(){
Arithmetic arithmetic = Integer::sum;
System.out.println(arithmetic.add(1,2));
}
}
これを見ると、メソッド参照とは何かを要約できます。
ラムダ式が特定のクラスまたはオブジェクトのメソッドのみを呼び出し、式と参加メソッドの入力パラメータが同じである場合、このラムダ式はメソッド参照として直接省略できます。
3.ストリームAPI
最後に、Java8 のハイライトですが、Stream クラスは、日常業務でデータ収集クラスを処理するために最もよく使用されるツール クラスです。
ストリーム API の機能は次のとおりです。
- Stream API は、ストリーム処理用の新しい抽象シーケンスのセットを提供します。
- ストリーム API は関数型プログラミングとチェーン操作をサポートします。
- ストリームは無限シーケンスを表すことができ、ほとんどの場合遅延評価されます。
(正直に言うと、私は上記の概念を理解していません。使用するだけです...)
3.1 使用方法の簡単な説明
Stream を使用してコレクションを処理する場合は、次の 3 つの手順があります。
ストリーミング プロセスには、複数の処理操作と 1 つの最終操作を含めることができます。
1. フローの生成
toStream() はストリームを生成します
toParallel() は並列ストリームを生成します (データ量が大きくない場合、ストリームの変換コストは並列で節約される時間コストよりも高くなります)
2. 加工
フィルタリングされたストリーム内のデータを処理しますが、最終結果は生成されません。
filter() 一部のデータをフィルタリングし、修飾された要素を新しいストリームに抽出します
map() は各要素を他のオブジェクトに変換し、新しいストリームを生成します。
sort() はルールに従って要素を並べ替え、新しいストリームを生成します
...
3. 最終決定/まとめ
前のプロセスで処理されたデータを要約して、最終結果を生成します。
forEach() は操作のために各要素を走査します
collect() データを要約し、コレクションに変換します。
max() は最大値を取得します
count() 数値を取得する
anyMatch() 条件を満たす要素があるかどうか
...
3.3 一般的な用途
次のケースでは、事前に定義された Person クラスを使用します。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
private int age;
}
1.トラバース
void forEach(Consumer<? super T> action);
まずストリームを生成してから、各要素を出力します。
上記のメソッド参照もありますのでご注意ください。
public class StreamDemo {
List<Person> personList;
@Before
public void init(){
personList = new ArrayList<>();
personList.add(new Person("xiang",22));
personList.add(new Person("ming",15));
}
@Test
public void test1(){
personList.stream().forEach(System.out::println);
}
}
2. フィルター
Stream filter(Predicate<? super T> predicate);
条件を満たすデータのみが渡されて、新しいストリームが生成されます。つまり、18 歳以上のユーザーのみが印刷されます。
public class StreamDemo {
List<Person> personList;
@Before
public void init() {
personList = new ArrayList<>();
personList.add(new Person("xiang", 22));
personList.add(new Person("ming", 15));
}
@Test
public void test2() {
//过滤,获取年龄大于18的人员
personList.stream()
.filter(s -> s.getAge() > 18)
.forEach(System.out::println);
}
}
3. 変換
Stream map(Function<? super T, ? extends R> mapper);
ストリーム内の要素を別の型の要素に変換します。
ここでは、Person 型のオブジェクトが String 型の名前に変換されます。
public class StreamDemo {
List<Person> personList;
@Before
public void init() {
personList = new ArrayList<>();
personList.add(new Person("xiang", 22));
personList.add(new Person("ming", 15));
}
@Test
public void test3() {
//转换,Person 转为 String
personList.stream()
.map(s->s.getName() + "," + s.getAge())
.forEach(System.out::println);
}
}
4. 並べ替え + 変換
ストリーム処理なので、実際に書くとストリーミングできます。
以下はソートしてから変換する場合です。
public class StreamDemo {
List<Person> personList;
@Before
public void init() {
personList = new ArrayList<>();
personList.add(new Person("xiang", 22));
personList.add(new Person("ming", 15));
}
@Test
public void test4() {
//过滤,获取年龄大于18的人员
personList.stream()
.sorted(Comparator.comparing(Person::getAge))
.map(s->s.getName() + "," + s.getAge())
.forEach(System.out::println);
}
}
5. リストに変換
以下は、人物を文字列に変換し、最終的にそれをリストに収集する操作です。
public class StreamDemo {
List<Person> personList;
@Before
public void init() {
personList = new ArrayList<>();
personList.add(new Person("xiang", 22));
personList.add(new Person("ming", 15));
}
@Test
public void test5() {
List<String> descList = personList.stream()
.map(s -> s.getName() + "," + s.getAge())
.collect(Collectors.toList());
System.out.println(descList);
}
}
6. 地図に変換
ここではリストがマップに変換されます。
名前はキーとして使用され、Person オブジェクトは値として使用されます。
public class StreamDemo {
List<Person> personList;
@Before
public void init() {
personList = new ArrayList<>();
personList.add(new Person("xiang", 22));
personList.add(new Person("ming", 15));
personList.add(new Person("xiao", 15));
}
@Test
public void test6() {
Map<String, Object> objectMap = personList.stream().collect(Collectors.toMap(Person::getName, s -> s));
System.out.println(objectMap);
}
}
7. 年齢ごとにグループ化する
これにより、リストの要素が年齢別にグループ化されます。
age をキー、リストを値として
public class StreamDemo {
List<Person> personList;
@Before
public void init() {
personList = new ArrayList<>();
personList.add(new Person("xiang", 22));
personList.add(new Person("ming", 15));
personList.add(new Person("xiao", 15));
}
@Test
public void test7() {
Map<Integer, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getAge));
System.out.println(group);
}
}
8. 年齢の合計を取得する
public class StreamDemo {
List<Person> personList;
@Before
public void init() {
personList = new ArrayList<>();
personList.add(new Person("xiang", 22));
personList.add(new Person("ming", 15));
personList.add(new Person("xiao", 15));
}
@Test
public void test8() {
int ageSum = personList.stream().mapToInt(Person::getAge).sum();
System.out.println(ageSum);
}
}
このような操作は、一つずつ試してみると、Stream が非常に使いやすいことがわかります。
4.日時クラス
以前の Date time クラスは、計算でも書式設定でもあまり役に立たないためです。したがって、java.time パッケージは java8 で起動されました。その中でも、LocalDate や LocalDateTime などの時間クラスは、効率と使用の点で大幅に向上しました。
4.1 日付と時刻の加減算
public class TimeTest {
@Test
public void test1(){
LocalDate today = LocalDate.now();
LocalDate yesterday = today.minusDays(1);
LocalDate lastMonthDay = today.minusMonths(1);
System.out.println(today);
System.out.println(yesterday);
System.out.println(lastMonthDay);
}
@Test
public void test2(){
LocalDateTime now = LocalDateTime.now();
LocalDateTime tomorrow = now.plusDays(1);
LocalDateTime nextMonthDay = now.plusMonths(1);
System.out.println(now);
System.out.println(tomorrow);
System.out.println(nextMonthDay);
}
}
出力:
4.2 日付と時刻のフォーマット
public class TimeTest {
@Test
public void test3(){
//定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
//字符串转日期
LocalDate localDate = LocalDate.parse("2021-01-01", formatter);
System.out.println(localDate);
//日期转字符串
LocalDate today = LocalDate.now();
String todayStr = today.format(formatter);
System.out.println(todayStr);
}
}
5. オプションクラス
java8 より前では、オブジェクトに対して空の判定処理を行う必要がある場合があります。Optional クラスは、この種の空の判定処理のコード ロジックを減らすのに役立ちます。
isPresent() や orElse() などのいくつかの便利なメソッドを提供します。
Optional を使用すると、特定のシナリオに応じて使用できます。
public class OptionalDemo {
List<Person> personList;
@Before
public void init() {
personList = new ArrayList<>();
personList.add(new Person("ming", 15));
personList.add(new Person("xiao", 15));
}
@Test
public void test1(){
Optional<Person> first = personList.stream().filter(s->"xiang".equals(s.getName())).findFirst();
//如果first.get()为空的话,提供一个默认值对象
Person xiang = first.orElse(new Person("xiang",25));
System.out.println(xiang);
}
}
6. インターフェースのデフォルトメソッド
6.1 インターフェースのデフォルト実装
インターフェイスにはデフォルトの実装を含めることができ、実装クラスはそのデフォルトの実装を直接使用できます。
インターフェース:
public interface Arithmetic {
default int add(int a, int b){
return a +b;
}
}
実装クラス:
public class ArithmeticImpl implements Arithmetic{
}
テスト:
public class InterfaceTest {
@Test
public void test1(){
Arithmetic arithmetic = new ArithmeticImpl();
System.out.println(arithmetic.add(1,2));
}
}
出力:
6.2 インターフェースの静的メソッド
インターフェイスには静的メソッドを含めることもでき、外部から呼び出されたときにクラスの静的メソッドのように使用できます。
インターフェース:
public interface Arithmetic {
static String hello(String name){
return "hello " + name;
}
}
テスト:
public class InterfaceTest {
@Test
public void test2(){
System.out.println(Arithmetic.hello("xiang"));
}
}
7.JavaScriptエンジン
java8 は、js スクリプトを解析して js コードを実行できるエンジンを提供します
public class JSDemo {
/**
* var fun1 = function(name) {
* print('Hello ' + name);
* return "Hi!";
* }
*
* var fun2 = function (object) {
* print(Object.prototype.toString.call(object));
* };
*/
@Test
public void test1() throws ScriptException, NoSuchMethodException {
//js脚本
String script ="var fun1 = function(name) {\n" +
" print('Hello ' + name);\n" +
" return \"Hi!\";\n" +
"}\n" +
"\n" +
"var fun2 = function (object) {\n" +
" print(Object.prototype.toString.call(object));\n" +
"};";
//声明js引擎
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
//处理js脚本
engine.eval(script);
//调用js方法
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("fun1", "Nashorn");
System.out.println(result);
System.out.println(result.getClass());
}
}
出力結果:
8.最後に書く
どんなに単純であっても、それは単なる糖衣構文であり、コンパイルされたバイトコードは変わりません。
しかし、コードの構文を簡素化して美しくできる限り、コードを書くように努めるべきです。
結局のところ、コードは人々に見せるものであり、コードが洗練されていればいるほど、チームの調和が促進されます。