머리말
널 포인터는 우리가 싫어하는 가장 일반적이고 성가신 예외입니다. Null 포인터가 예외를 방지하지 않으려면 코드에 많은 비 널 판단을 작성해서는 안됩니다.
Java 8은 새로운 옵션 클래스를 소개합니다. 널 포인터의 발생을 피하기 위해, 데이터를 옵션으로로드 해야하는 한 많은 if(obj!=null) 판단을 작성할 필요가 없습니다. 객체를 감싸는 컨테이너입니다.
NULL 포인터 예외를 겪은 프로그래머는 Java 프로그래머가 아니며 NULL은 실제로 많은 문제를 일으켰다고합니다. Java 8은 null로 인한 많은 문제를 피하기 위해 java.util.Optional 이라는 새로운 클래스를 소개합니다.
널 참조가 어떤 해를 끼칠 수 있는지 보자. 먼저 클래스 컴퓨터를 생성하면 구조가 아래 그림에 나와 있습니다.
다음 코드를 호출하면 어떻게됩니까?
문자열 version = computer.getSoundCard (). getUSB (). getVersion ();
위의 코드는 괜찮은 것처럼 보이지만 많은 컴퓨터 (예 : Raspberry Pi)는 실제로 사운드 카드가 없으므로 getSoundcard() 메소드를 호출하면 널 포인터 예외가 발생합니다.
규칙적이지만 나쁜 방법은 컴퓨터에 사운드 카드가 없음을 나타 내기 위해 Null Reference를 반환하는 것입니다. 그러나 이는 getUSB () 메소드가 빈 참조에서 호출되므로 프로그램 실행 중에 제어 예외가 발생하여 프로그램이 실행 중지를 중지합니다. 클라이언트 컴퓨터에서 프로그램이 실행될 때 갑자기 나타나는 것이 얼마나 당황 스럽습니까?
위대한 컴퓨터 과학 Tony Hoare는 다음과 같이 썼습니다. "Null 인용이 1965 년에 만들어 졌다고 생각하여 10 억 달러의 손실이 발생했다고 생각합니다. Null 인용을 사용할 때 가장 큰 유혹은 구현하기 쉽다는 것이 었습니다."
그렇다면 프로그램이 실행될 때 Null 포인터 예외를 어떻게 피할 수 있습니까? 다음과 같이 경고하고 가능한 널 포인터를 지속적으로 확인해야합니다.
문자열 버전 = "알 수없는"; if (computer! = null) {SoundCard SoundCard = computer.getSoundCard (); if (soundcard! = null) {usb usb = soundcard.getusb (); if (usb! = null) {version = usb.getversion (); }}} 그러나 위의 코드에 너무 많은 검사가 있고 전체 코드 구조가 매우 추악하다는 것을 알 수 있습니다. 그러나 시스템이 실행될 때 널 포인터가 없도록이 판단을 사용해야합니다. 비즈니스 코드에 빈 참조가 많이 있는지 판단하는 것은 단순히 성가신 일이며 코드의 가독성이 좋지 않습니다.
값이 비어 있는지 확인하는 것을 잊어 버리면 Null 참조에도 큰 문제가 있습니다. 이 기사에서는 NULL 참조를 값이 존재하지 않는 표현으로 사용하는 것이 나쁜 방법임을 증명할 것입니다. NULL 참조를 다시 사용하지 않고 값이 존재하지 않음을 나타내는 더 나은 모델이 필요합니다.
Java 8은 Haskell 언어와 Scala Language에서 영감을 얻은 java.util.Optional<T> 라는 새로운 클래스를 소개했습니다. 이 클래스는 아래 그림과 코드에 표시된 것처럼 임의의 값을 포함 할 수 있습니다. 선택 사항을 값을 포함 할 수있는 값으로 생각할 수 있습니다. 옵션에 값이 포함되지 않으면 아래 그림과 같이 비어 있습니다.
공개 클래스 컴퓨터 {개인 옵션 <사운드 카드> 사운드 카드; 공개 옵션 <사운드 카드> getSoundCard () {...} ...} 공개 클래스 사운드 카드 {개인 옵션 <USB> USB; 공개 옵션 <USB> getUSB () {... ...}} public class USB {public String getVersion () {...}} 위의 코드는 컴퓨터가 사운드 카드를 교체 할 수 있음을 보여줍니다 (사운드 카드가 존재하거나 존재하지 않을 수도 있음). 사운드 카드에는 USB 포트도 포함될 수도 있습니다. 이것은 개선 방법이며, 모델은 주어진 값이 존재하지 않을 수 있음을 더 명확하게 반영 할 수 있습니다.
그러나 Optional<Soundcard> 객체를 다루는 방법은 무엇입니까? 결국, 당신이 얻고 싶은 것은 USB 포트 번호입니다. 매우 간단합니다. 선택적 클래스에는 값이 존재하는지 여부를 다루는 몇 가지 방법이 포함되어 있습니다. NULL 참조와 비교하여 선택적 클래스는 값이 관련되어 있는지 여부를 처리하여 NULL 포인터 예외를 피할 수 있도록합니다.
선택적 클래스는 NULL 참조를 대체하지 않습니다. 반대로, 설계된 API를 이해하기 쉽게 만들기 위해 함수의 서명을 볼 때 함수로 전달 될 값이 존재하지 않을 수 있는지 여부를 결정할 수 있습니다. 이를 통해 실제 값을 처리하기 위해 선택적인 클래스를 열게됩니다.
선택 모드를 채택하십시오
너무 많이 말한 후 몇 가지 코드를 살펴 보겠습니다! 먼저 옵션을 사용하여 기존 NULL 참조 탐지를 다시 작성하는 방법을 살펴 보겠습니다. 이 기사의 끝에서 선택 사항을 사용하는 방법을 이해합니다.
문자열 이름 = computer.flatmap (computer :: getsoundcard) .flatmap (soundcard :: getUsb) .map (usb :: getVersion) .Orelse ( "Unknown");
선택적 객체를 만듭니다
빈 선택적 객체를 만들 수 있습니다.
옵션 <사운드 카드> sc = 옵션 .empty ();
다음은 널 값이 아닌 값을 포함하는 옵션을 작성하는 것입니다.
SoundCard SoundCard = New SoundCard (); 옵션 <사운드 카드> sc = 옵션 (사운드 카드);
사운드 카드가 NULL 인 경우 NULL 포인터 예외가 즉시 발생합니다 (사운드 카드 속성을 얻을 때 버리는 것보다 더 낫습니다).
OfNullable을 사용하면 NULL 참조가 포함될 수있는 선택적 객체를 만들 수 있습니다.
옵션 <사운드 카드> sc = 옵션 .ofNullable (사운드 카드);
사운드 카드가 Null 참조 인 경우 옵션 객체가 비어 있습니다.
선택 사항에서 값 처리
선택적 객체가 있으므로 해당 메소드를 호출하여 옵션 객체의 값이 존재하는지 여부를 처리 할 수 있습니다. Null Detection과 비교하여 다음과 같은 ifpresent () 메소드를 사용할 수 있습니다.
선택적 <사운드 카드> 사운드 카드 = ...; SoundCard.IfPresent (System.out :: println);
이런 식으로 널 감지 할 필요가 없습니다. 옵션 객체가 비어 있으면 모든 정보가 인쇄되지 않습니다.
isPresent() 메소드를 사용하여 옵션 객체가 실제로 존재하는지 확인할 수도 있습니다. 또한, 현재의 경우 옵션 객체에 포함 된 값을 반환하는 get () 메소드도 있습니다. 그렇지 않으면 NosuchelementException이 발생합니다. 이 두 가지 방법은 예외를 피하기 위해 다음과 같이 함께 사용할 수 있습니다.
if (soundcard.ispresent ()) {system.out.println (soundcard.get ());} 그러나이 방법은 권장되지 않습니다 (NULL 감지에 비해 개선이 없습니다). 아래에서 우리는 일반적인 작업 방법에 대해 논의 할 것입니다.
기본값 및 관련 작업을 반환합니다
NULL을 만나면 정기적 인 작업은 기본값을 반환하는 것입니다.이 값은 3 배 표현식을 사용하여 구현할 수 있습니다.
사운드 카드 사운드 카드 = MaybesoundCard! = null? Maybesoundcard : 새 사운드 카드 ( "Basic_sound_card");
선택적 객체를 사용하는 경우 orElse() 사용하여 재정의 할 수 있습니다. 옵션이 빈 orElse() 인 경우 기본값을 반환 할 수 있습니다.
SoundCard SoundCard = MaybesoundCard.orelse (새 사운드 카드 ( "defaut"));
마찬가지로 선택 사항이 비어 있으면 OrelsetHrow ()를 사용하여 예외를 던질 수 있습니다.
SoundCard SoundCard = MaybesoundCard.OrelSethrow (불법 스테이트 렉싱 :: new);
필터를 사용하여 특정 값을 필터링하십시오
우리는 종종 객체 방법을 호출하여 속성을 판단합니다. 예를 들어 USB 포트 번호가 특정 값인지 확인해야 할 수도 있습니다. 안전상의 이유로 USB를 가리키는 의료용 사용이 NULL인지 확인한 다음 다음과 같은 getVersion() 메소드를 호출해야합니다.
USB USB = ...; if (usb! = null && "3.0".equals (usb.getversion ())) {System.out.println ( "OK");} 선택 사항을 사용하는 경우 필터 기능을 사용하여 다시 작성할 수 있습니다.
옵션 <USB> MaygEusB = ...; Mayalusb.filter (usb-> "3.0".equals (usb.getversion ()) .ifpresent ((() -> system.out.println ( "Ok")));
필터 방법은 매개 변수와 반대의 술어가 필요합니다. 선택 사항의 값이 존재하고 예측을 만족 시키면 필터 함수는 조건을 충족시키는 값을 반환합니다. 그렇지 않으면 빈 선택적 객체가 반환됩니다.
맵 방법을 사용하여 데이터를 추출하고 변환하십시오
일반적인 패턴은 물체의 일부 특성을 추출하는 것입니다. 예를 들어, 사운드 카드 객체의 경우 USB 객체를 가져 와서 버전 번호를 결정해야 할 수도 있습니다. 일반적으로 우리의 구현은 다음과 같습니다.
if (soundcard! = null) {usb usb = soundcard.getusb (); if (usb! = null && "3.0".equals (usb.getversion ()) {System.out.println ( "OK");}} 맵 메소드를 사용 하여이 감지 NULL을 무시한 다음 객체 유형의 객체를 추출 할 수 있습니다.
선택 사항 <usb> USB = MaybesoundCard.map (SoundCard :: getUSB);
스트림을 사용하여 맵 함수를 사용하는 것과 동일합니다. 스트림을 사용하려면 함수를 매개 변수로 맵 함수로 전달해야하며 전달 된 함수는 스트림의 각 요소에 적용됩니다. 공간과 시간을 스트리밍 할 때 아무 일도 일어나지 않습니다.
옵션에 포함 된 값은 전달 된 함수에 의해 변환됩니다 (여기서는 사운드 카드에서 USB를 얻는 함수). 옵션 객체가 시공간 인 경우 아무 일도 일어나지 않습니다.
그런 다음 맵 메소드와 필터 방법을 결합하여 사운드 카드를 3.0이 아닌 USB 버전 번호로 필터링합니다.
MayBesoundCard.map (SoundCard :: getUSB) .filter (usb -> "3.0".equals (usb.getversion ()) .ifpresent (() -> system.out.println ( "Ok"));
이런 식으로, 우리의 코드는 널 감지없이 처음에 우리가 제공 한 것과 비슷해 보이기 시작합니다.
FlatMap 함수를 사용하여 선택적 객체를 전달합니다
이제 옵션을 사용하여 코드를 리팩터링하는 방법의 예가 소개되었습니다. 그렇다면 다음 코드를 안전한 방식으로 어떻게 구현해야합니까?
문자열 version = computer.getSoundCard (). getUSB (). getVersion ();
위의 코드는 맵 함수를 사용하여 구현할 수있는 한 개체에서 다른 객체를 추출하는 것입니다. 이전 기사에서는 컴퓨터에서 Optional<Soundcard> 객체를 설정하고 사운드 카드는 Optional<USB> 객체가 포함되어 있으므로 이러한 방식으로 코드를 리팩토링 할 수 있습니다.
문자열 version = computer.map (computer :: getSoundcard) .map (SoundCard :: getUsb) .map (usb :: getVersion) .Orelse ( "Unknown");
불행히도 위의 코드는 오류를 컴파일하므로 왜? 컴퓨터 변수는 Optional<Computer> 유형이므로 맵 함수를 호출하는 데 아무런 문제가 없습니다. 그러나 getSoundcard() 메소드는 Optional<Soundcard> 개체를 반환하여 Optional<Optional<Soundcard>> 유형의 객체를 반환합니다. 두 번째 맵 함수가 호출되면 getUSB() 함수로의 호출이 불법이됩니다.
다음 그림은이 시나리오를 설명합니다.
맵 함수의 소스 코드 구현은 다음과 같습니다.
public <u> 옵션 <u> map (함수 <? super t,? extends u> mapper) {objects.requirenonnull (mapper); if (! ispresent ()) return empty (); else {return optional.ofnullable (mapper.apply (value)); }} 맵 함수가 Optional.ofNullable() 다시 호출하여 Optional<Optional<Soundcard>> 를 반환합니다.
옵션은 옵션 객체의 값 (맵 작동과 같은)의 값을 변환하도록 설계된 FlatMap 함수를 제공 한 다음 2 레벨 옵션을 하나로 압축하도록 설계되었습니다. 다음 그림은 MAP 및 FlatMap을 호출하여 유형 변환의 선택적 객체 간의 차이를 보여줍니다.
그래서 우리는 이것을 쓸 수 있습니다.
문자열 version = computer.flatmap (computer :: getSoundcard) .flatmap (soundcard :: getusb) .map (usb :: getversion) .orelse ( "Unknown");
첫 번째 FlatMap은 Optional<Soundcard> Optional<Optional<Soundcard>> 를 보장하고, 두 번째 플랫 맵은 반환이 Optional<USB> 되도록 동일한 함수를 구현합니다. getVersion() 선택적 객체 대신 문자열 객체를 반환하기 때문에 map() 는 세 번째라고합니다.
우리는 마침내 우리가 방금 사용하기 시작한 중첩 null 검사의 못생긴 코드를 다시 작성합니다. 이는 읽을 수 있으며 Null 포인터 예외의 발생을 피합니다.
요약
이 기사에서는 Java 8에서 제공 한 새로운 클래스 java.util.Optional<T> 채택합니다.이 클래스의 원래 의도는 NULL 참조를 대체하는 것이 아니라 디자이너가 더 나은 API를 설계하는 데 도움이됩니다. 함수의 서명을 읽고 함수가 존재하거나 존재하지 않을 수도있는 값을 수용하는지 여부를 알고 있습니다. 또한 옵션 옵션을 선택한 다음 값이 존재하는지 여부를 처리하여 코드가 잠재적 인 널 포인터 예외를 피할 수 있도록합니다.
좋아, 위는이 기사의 전체 내용입니다. 이 기사의 내용에 모든 사람의 연구 나 작업에 대한 특정 참조 가치가 있기를 바랍니다. 궁금한 점이 있으면 의사 소통을 위해 메시지를 남길 수 있습니다. Wulin.com을 지원 해주셔서 감사합니다.