JDK8: オプションの詳細な説明とソースコード分析、null ポインターを適切に処理する方法

1. オプションの概要

1. 迷惑な NullPointerException

日常の開発では、NullPointerException は、初心者であろうと熱心なプレイヤーであろうと、よく知っている誰もが目にするものであると考えられています。それはどこにでも現れると言え、常にさまざまな場面で現れる可能性があります。では、それをどのように防ぐかというと、通常はさまざまな非 null チェックを受動的に採用していますが、それでも頻繁に目に見えてきます。

public String getCompanyName(Student student){
    
    
	
	if (student != null){
    
    
		Job job = student.getJob();
		
		if (job != null){
    
    
			Company company = job.getCompany();
			if (company != null){
    
    
				String name = company.getName();
				return name;
			}else {
    
    
				return "no company";
			}
		}else {
    
    
			return "no job";
		}
	}else {
    
    
		 return "no student";
	}
}

上記のコードですが、普段の仕事でも似たようなコードがよくあると思います。オブジェクトを取得するたびにnull判定が行われ、以降の実装が続行されます。しかし、この方法は非常にまずく、まず、if-else 判定が大量に入れ子になってしまい、コードの可読性やスケーラビリティが非常に悪くなります。現時点では、一部の学生はガード ステートメントを使用して次のように変更する場合があります。

public String getCompanyName(Student student){
    
    
	if (student == null){
    
    
		return "no student";
	}
	Job job = student.getJob();
	if (job == null){
    
    
		return "no job";
	}
	Company company = job.getCompany();
	if (company == null){
    
    
		return "no company";
	}
	return company.getName();
}

このような判定は、多数の判定の入れ子を意識的に避けていますが、判定ポイントも多岐に渡っており、コードのメンテナンスも困難です。

2. オプションの概要

null ポインタ例外の発生を防ぐために、Java8 では新しいクラスが導入されておりOptional、すでに簡単な実装が実装されています。その本質は、Optional クラスを通じて値をカプセル化することであり、値がある場合、その値は Optional クラスにカプセル化されます。値がない場合、Empty がこのクラスにカプセル化されます。

2、任意使用

Optional クラスに基づいて、函数式接口値を操作するためのいくつかのメソッドが提供されます。
ここに画像の説明を挿入

1. オプションのオブジェクトを作成する

Create Optional。3 つのメソッド操作、つまり empty()、of()、ofNullable() を提供します。使用方法は次のとおりです。

Optional<Student> studentOptional = Optional.empty();
Optional<Student> studentOptional = Optional.of(student);
Optional<Student> studentOptional = Optional.ofNullable(student);

これら 3 つの違いは何ですか? ソースコード分析によると次のようになります。

// 源码分析
public static<T> Optional<T> empty() {
    
    
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY; // private static final Optional<?> EMPTY = new Optional<>();
    return t;
}
public static <T> Optional<T> of(T value) {
    
    
    return new Optional<>(value);
}

private Optional(T value) {
    
    
    this.value = Objects.requireNonNull(value);
}

public static <T> T requireNonNull(T obj) {
    
    
    if (obj == null)
        throw new NullPointerException();
    return obj;
}
public static <T> Optional<T> ofNullable(T value) {
    
    
    return value == null ? empty() : of(value);
}

ソース コードによれば、empty()空の Optional インスタンスが直接返され、内部には値がありません。

of() は、null を許可しない値を持つ Optional オブジェクトを返します。メソッドが呼び出された場合传入参数是null,则立刻抛出NullPointerException、オブジェクトが使用されるまで待ってからスローするのではなく、即時チェックと同等になります。

ofNullable() も値を持つ Optional オブジェクトを返しますが、of() との最大の違いは、渡された値を判断することです。渡された値が null の場合は、empty() を呼び出して値を返します。内容のないオプション。null でない場合、of() を呼び出すと内容のあるオプションが返されます。

2. isPresent()、ifPresent()の応用とソースコード解析

Optional クラスは、Optional に値があるかどうかを判断する 2 つのメソッドをそれぞれ提供しますisPresent()和ifPresent(Consumer<? super T> consumer)of() は作成時にすでに判定を完了しており、empty() は単に Optional オブジェクトをインスタンス化するだけであるため、通常は ofNullable() と組み合わせて使用​​されます。

// 使用实例
public class PresentDemo {
    
    

    public static void getStudentName(Student student){
    
    

        Optional<Student> optional = Optional.ofNullable(student);

        if (optional.isPresent()){
    
    
            //student不为null
            Student student1 = optional.get();
            System.out.println(student1);
        }else {
    
    
            System.out.println("student为null");
        }

        optional.ifPresent(s-> System.out.println(s));
    }

    public static void main(String[] args) {
    
    

        Student student = new Student(1,"zhangsan","M");
        getStudentName(student);
    }
}
// 源码分析
// isPresent()内部非常简单,就是判断这个值是否为null。
public boolean isPresent() {
    
    
    return value != null;
}

public void ifPresent(Consumer<? super T> consumer) {
    
    
    if (value != null)
        consumer.accept(value);
}

ifPresent() メソッドが実行されると、値が 1 つ受信されますconsumer函数式接口。値が null でない場合、値はコンシューマの accept メソッドを通じて取得されます。

3. get() アプリケーションとソースコードの分析

get() の使用は非常に簡単ですが、値を取得するときに、値が存在する場合は Optional にカプセル化された値を直接返し、存在しない場合は NoSuchElementException がスローされるため、安全ではありません。したがって、それを使用する前提は、Optional に値があると判断されていることです。そうでない場合は、役に立ちません。

// 使用实例
Optional<Student> studentOptional = Optional.ofNullable(student);
if (studentOptional.isPresent()){
    
    
	Student result = studentOptional.get();
}
// 源码分析
public Tget() {
    
    
    if (value == null) {
    
    
        throw new NoSuchElementException("No value present");
    }
    return value;
}

4. orElseThrow() アプリケーションとソースコードの分析

このメソッドは、値を取得するために使用される get() に似ていますが、Optional に値がない場合、get() は直接 NoSuchElementException をスローします。この場合、場合によってはスローする必要があるため、特定の制限があります。自己定義の例外。この時点で、orElseThrow() を使用できます。これにより、値を取得するときに Optional に値がない場合にカスタム例外をスローできます。

// 使用实例
Optional<Student> optional = Optional.ofNullable(student);

try {
    
    
	// null 就抛异常
    Student result = optional.orElseThrow(MyException::new);
    System.out.println(result);
} catch (MyException e) {
    
    
    e.printStackTrace();
}
// 源码分析
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    
    
    if (value != null) {
    
    
        return value;
    } else {
    
     // null 的话就抛异常
        throw exceptionSupplier.get();
    }
}

5.map() アプリケーションとソースコードの分析

map() は型変換を実装できます。これは JDK8 の Stream のマップに似ていますが、1 つは Stream のジェネリック型を変換するものであり、もう 1 つは Optional のジェネリック型を変換するものである点が異なります。

// 使用案例
if (studentOptional.isPresent()){
    
    
	// Student类型转为String类型
	Optional<String> nameOptional = studentOptional.map(Student::getName);
}
// 源码分析
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    
    
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
    
    
        return Optional.ofNullable(mapper.apply(value));
    }
}

6. flatMap() アプリケーションとソースコードの分析

生徒の名前は先ほどmap()で取得しましたが、操作は非常に簡単です。しかし、リンクを取得した場合、その地図は利用できるのでしょうか?例: 学生 -> 勤務先 -> 会社 -> 会社名。

これで、頭の中にアイデアが浮かんだかもしれません。つまり、map() を介したコード構造は次のとおりです。

studentOptional.map(Student::getJob).map(Job::getCompany).map(Company::getName);

しかし、このコードはコンパイルできません。なぜなら、map の学習に応じて、呼び出されるたびに Optional のジェネリック型が変更され、最終的には多層の Optional の入れ子構造が生成されるからです。
ここに画像の説明を挿入

この問題を解決するために、Optional クラスは値を取得する別のメソッド flatMap() を提供します。それ自体は多層呼び出しに使用され、結果に対して複数の Optional を形成しませんが、結果を最終的なタイプの Optional に処理します。ただし、 flatMap によって取得される戻り値の型は Optional である必要があります。マップにはこの制限はありません。

// 使用实例
Optional<String> nameOptional = studentOptional.flatMap(Student::getJob).flatMap(Job::getCompany).map(Company::getName);
// 源码分析
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    
    
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
    
    // 多次调用不会生成Optional嵌套
        return Objects.requireNonNull(mapper.apply(value));
    }
}

7. filter() アプリケーションとソースコードの分析

Optional クラスで提供されるメソッド filter() は数据过滤、この要件を満たします。入力された条件に従って判断し、一致する場合は対応する値を含む Optional オブジェクトを返し、一致しない場合は null の Optional を返します。

// 使用实例
Optional<Company> company = companyOptional.filter(c -> "bank".equals(c.getName()));

ソースコードにはPredicateを渡すことになっており、Nullでない場合はPredicateのテストメソッドを呼び出して判定します。

// 源码分析
public Optional<T> filter(Predicate<? super T> predicate) {
    
    
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

8. orElse() アプリケーションとソースコードの分析

値をフェッチするときに、値が存在しない場合は、値を返すことを検討することがあります默认值この要件は orElse() を通じて実現できます。

値が null かどうかを内部で判断し、null でない場合は値を返し、null の場合は渡されたデフォルト値を返します。

// 使用案例
String value = studentOptional.flatMap(Student::getJob).flatMap(Job::getCompany).map(Company::getName).orElse("default value");
// 源码分析
publicT orElse(T other) {
    
    
	// null的话取默认值
    return value != null ? value : other;
}

9. orElseGet() アプリケーションとソースコードの分析

orElseGet() は、Optional に値がない場合にデフォルト値を返すために使用されるメソッドでもあります。ただし、実行するという点で orElse() とは異なります延迟加载Optional に値がない場合にのみ呼び出されます。

// 源码分析
public T orElseGet(Supplier<? extends T> other) {
    
    
    return value != null ? value : other.get();
}
// 代码案例
System.out.println(Optional.ofNullable("student").orElse(getStr("a")));
System.out.println(Optional.ofNullable(null).orElse(getStr("b")));
System.out.println(Optional.ofNullable("student").orElseGet(() ->getStr("a")));
System.out.println(Optional.ofNullable(null).orElseGet(() ->getStr("b")));

データが null でない場合、orElseGet は実行されないことがわかりました。

したがって、これを使用する場合は、遅延呼び出しを使用するため、パフォーマンスが向上する orElseGet() を使用することをお勧めします。

10. orElseThrow() アプリケーションとソースコードの分析

orElseThrow は非常に単純で、null の場合は例外をスローします。

// 源码分析
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    
    
    if (value != null) {
    
    
        return value;
    } else {
    
    
        throw exceptionSupplier.get();
    }
}

おすすめ

転載: blog.csdn.net/A_art_xiang/article/details/132038152