[nullable] Kotlin에서 nullable 값을 처리하거나 참조하거나 변환하는 관용적 방법은 무엇입니까

nullable 유형 Xyz?이 있으면이를 참조하거나 nullable이 아닌 유형으로 변환하려고합니다 Xyz. 코 틀린에서 그렇게하는 관용적 방법은 무엇입니까?

예를 들어이 코드에 오류가 있습니다.

val something: Xyz? = createPossiblyNullXyz()
something.foo() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Xyz?"

그러나 null을 먼저 확인하면 허용되는 이유는 무엇입니까?

val something: Xyz? = createPossiblyNullXyz()
if (something != null) {
    something.foo() 
}

확인 null하지 않아도 값을 변경하지 않거나 처리하지 않으려면 어떻게해야 합니까? 예를 들어, 여기서 나는지도에서 값을 검색하고 있음을 보증하고 그 결과 는 아닙니다 . 하지만 오류가 있습니다.ifnullget()null

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")
something.toLong() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?"

이 메소드 get()는 항목이 누락되어 type을 리턴 할 수 있다고 생각합니다 Int?. 따라서 값 유형을 Null 허용하지 않도록 설정하는 가장 좋은 방법은 무엇입니까?

참고 : 이 질문은 저자 ( 자기 답변 질문 ) 가 의도적으로 작성하고 답변 하므로 일반적인 Kotlin 주제에 대한 관용적 답변이 SO에 있습니다. 또한 현재 코 틀린에 대해 정확하지 않은 코 틀린 알파에 대해 작성된 오래된 답변을 명확하게하기 위해.



답변

먼저, Kotlin의 Null Safety 에 관한 모든 내용을 읽어야합니다 .

코 틀린에, 당신은 아닌지 확인하지 않고 null 허용 값에 액세스 할 수 없습니다 null( 조건에 널 (null)에 대한 검사 ), 또는이 확실히되지 않는다는 주장 null은 Using !!확인 연산자를 하는으로 접근 ?.안전 통화 마지막으로 가능성이 뭔가주고, 또는 null?:Elvis Operator를 사용하는 기본값 입니다.

귀하의 질문에 대한 첫 번째 사례의 경우 코드의 의도에 따라 옵션 중 하나를 사용하며 모두 관용적이지만 결과가 다릅니다.

val something: Xyz? = createPossiblyNullXyz()

// access it as non-null asserting that with a sure call
val result1 = something!!.foo()

// access it only if it is not null using safe operator, 
// returning null otherwise
val result2 = something?.foo()

// access it only if it is not null using safe operator, 
// otherwise a default value using the elvis operator
val result3 = something?.foo() ?: differentValue

// null check it with `if` expression and then use the value, 
// similar to result3 but for more complex cases harder to do in one expression
val result4 = if (something != null) {
                   something.foo()
              } else {
                   ...
                   differentValue
              }

// null check it with `if` statement doing a different action
if (something != null) {
    something.foo()
} else {
    someOtherAction()
}

“널 (null) 검사시 왜 작동합니까?”에 대해서는 스마트 캐스트에서 아래의 배경 정보를 읽으십시오 .

의 질문 에서 2 번째 사례의 경우Map , 개발자로서 결과가 절대로 확실 하지 않다면 확실한 연산자를 어설 션으로 null사용 !!하십시오.

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid

또는 다른 경우 맵 COULD가 널을 리턴하지만 기본값을 제공 할 수 Map있는 경우 getOrElse메소드 자체가 있습니다 .

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.getOrElse("z") { 0 } // provide default value in lambda
something.toLong() // now valid

배경 정보:

참고 : 아래 예제에서 명시 적 유형을 사용하여 동작을 명확하게합니다. 형식 유추를 사용하면 일반적으로 지역 변수 및 개인 멤버에 대해 형식을 생략 할 수 있습니다.

!!확실한 연산자 에 대한 추가 정보

!!연산자 값이 아님을 주장 null하거나 NPE를 던진다. 개발자가 가치를 절대로 보장하지 않는 경우에 사용해야 null합니다. 그것을 똑똑한 캐스트가 뒤 따르는 것으로 생각하십시오 .

val possibleXyz: Xyz? = ...
// assert it is not null, but if it is throw an exception:
val surelyXyz: Xyz = possibleXyz!!
// same thing but access members after the assertion is made:
possibleXyz!!.foo()

더 읽기 : !! 확실한 연산자


null확인 및 스마트 캐스트 에 대한 추가 정보

null검사 로 널 입력 가능 유형에 대한 액세스를 보호하는 경우 , 컴파일러는 명령문 본문 내의 값을 널 입력 가능하지 않게 스마트 캐스트 합니다. 이것이 일어날 수없는 복잡한 흐름이 있지만 일반적인 경우에는 잘 작동합니다.

val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
   // allowed to reference members:
   possiblyXyz.foo()
   // or also assign as non-nullable type:
   val surelyXyz: Xyz = possibleXyz
}

또는 is널 입력 불가능 유형을 점검 하는 경우 :

if (possibleXyz is Xyz) {
   // allowed to reference members:
   possiblyXyz.foo()
}

그리고 안전한 캐스팅도 할 때 ‘언제’표현에 대해서도 마찬가지입니다.

when (possibleXyz) {
    null -> doSomething()
    else -> possibleXyz.foo()
}

// or

when (possibleXyz) {
    is Xyz -> possibleXyz.foo()
    is Alpha -> possibleXyz.dominate()
    is Fish -> possibleXyz.swim()
}

나중에 변수를 사용하기 위해 null검사에서 스마트 캐스트 를 허용하지 않는 것도 있습니다. 사용 위의 예를 어떠한 방식으로 응용 프로그램의 흐름에 돌연변이 수 있었다는 것을 지역 변수 여부 val또는 var이 변수는로 변이 할 기회가 없었다 null. 그러나 컴파일러가 흐름 분석을 보장 할 수없는 다른 경우에는 오류가됩니다.

var nullableInt: Int? = ...

public fun foo() {
    if (nullableInt != null) {
        // Error: "Smart cast to 'kotlin.Int' is impossible, because 'nullableInt' is a mutable property that could have been changed by this time"
        val nonNullableInt: Int = nullableInt
    }
}

변수의 수명주기가 nullableInt완전히 표시되지 않고 다른 스레드에서 할당 될 수 있으며 null검사는 널 입력 불가능한 값으로 스마트 캐스트 할 수 없습니다 . 해결 방법은 아래 “안전한 통화”주제를 참조하십시오.

스마트 캐스트 에서 변경하지 않도록 신뢰할 수없는 또 다른 경우 val는 사용자 지정 게터가있는 객체 의 속성입니다. 이 경우 컴파일러는 값을 변경하는 내용을 볼 수 없으므로 오류 메시지가 표시됩니다.

class MyThing {
    val possibleXyz: Xyz?
        get() { ... }
}

// now when referencing this class...

val thing = MyThing()
if (thing.possibleXyz != null) {
   // error: "Kotlin: Smart cast to 'kotlin.Int' is impossible, because 'p.x' is a property that has open or custom getter"
   thing.possiblyXyz.foo()
}

더 읽기 : 조건에서 null 확인


?.안전 통화 교환 원 에 대한 추가 정보

왼쪽의 값이 null 인 경우 안전 호출 연산자는 null을 반환하고, 그렇지 않으면 오른쪽의 식을 계속 평가합니다.

val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()

목록을 반복하지만 null비어 있지 않은 경우에만 안전한 호출 연산자 를 사용하는 또 다른 예는 다음 과 같습니다.

val things: List? = makeMeAListOrDont()
things?.forEach {
    // this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}

위의 예제 중 하나에서 if검사를했지만 다른 스레드가 값을 변경하여 스마트 캐스트 가 발생하지 않은 경우가있었습니다 . 안전 호출 연산자를 사용 let하여이 문제를 해결하기 위해이 샘플을 변경할 수 있습니다 .

var possibleXyz: Xyz? = 1

public fun foo() {
    possibleXyz?.let { value ->
        // only called if not null, and the value is captured by the lambda
        val surelyXyz: Xyz = value
    }
}

더 읽어보기 : 안전한 통화


?:엘비스 운영자 에 대한 추가 정보

Elvis 연산자를 사용하면 연산자 왼쪽의 표현식이 다음과 같은 경우 대체 값을 제공 할 수 있습니다 null.

val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()

창의적인 용도로도 사용됩니다. 예를 들어 다음과 같은 경우 예외가 발생합니다 null.

val currentUser = session.user ?: throw Http401Error("Unauthorized")

또는 함수에서 일찍 반환하려면

fun foo(key: String): Int {
   val startingCode: String = codes.findKey(key) ?: return 0
   // ...
   return endingValue
}

더 읽어보기 : Elvis Operator


관련 기능을 가진 널 연산자

Kotlin stdlib에는 위에서 언급 한 연산자와 잘 작동하는 일련의 기능이 있습니다. 예를 들면 다음과 같습니다.

// use ?.let() to change a not null value, and ?: to provide a default
val something = possibleNull?.let { it.transform() } ?: defaultSomething

// use ?.apply() to operate further on a value that is not null
possibleNull?.apply {
    func1()
    func2()
}

// use .takeIf or .takeUnless to turn a value null if it meets a predicate
val something = name.takeIf { it.isNotBlank() } ?: defaultName

val something = name.takeUnless { it.isBlank() } ?: defaultName

관련 주제

Kotlin에서 대부분의 응용 프로그램 null은 값 을 피하려고 하지만 항상 가능한 것은 아닙니다. 때로는 null완벽한 의미가 있습니다. 고려해야 할 몇 가지 지침 :

  • 경우에 따라 메소드 호출 상태 및 성공한 경우 결과를 포함하는 다른 리턴 유형을 보증합니다. 결과 와 같은 라이브러리는 코드를 분기 할 수있는 성공 또는 실패 결과 유형을 제공합니다. 그리고 Kovenant 라는 Kotlin의 Promises 라이브러리는 약속 형태로 동일하게 수행합니다.

  • 콜렉션이 리턴 유형으로서의 경우 null, “not present”의 세 번째 상태가 필요하지 않으면 항상 a 대신 빈 콜렉션을 리턴합니다 . Kotlin에는 이러한 빈 값을 생성 emptyList()하거나emptySet() 생성하는 등의 도우미 기능 이 있습니다.

  • 기본값 또는 대안이있는 널 입력 가능 값을 리턴하는 메소드를 사용하는 경우 Elvis 연산자를 사용하여 기본값을 제공하십시오. 를 Map사용하면 nullable 값을 반환하는 메서드 getOrElse()대신 기본값을 생성 할 수 있습니다 . 동일Mapget()getOrPut()

  • Kotlin이 Java 코드의 Null 허용 여부에 대해 확신하지 못하는 Java에서 메소드를 대체 할 때 ?서명 및 기능이 무엇인지 확실하면 항상 무시 에서 Null 허용 여부를 삭제할 수 있습니다. 따라서 재정의 된 방법이 더 null안전합니다. Kotlin에서 Java 인터페이스를 구현할 때와 동일하게 null 허용 여부를 알고있는 것으로 변경하십시오.

  • 등을 위해 이미 도움이 될 수 있습니다 기능,보고 String?.isNullOrEmpty()하고 String?.isNullOrBlank()있는 안전하게 널 (NULL) 값을 운영하고 당신이 무엇을 기대 할 수 있습니다. 실제로 표준 라이브러리의 간격을 채우기 위해 고유 한 확장을 추가 할 수 있습니다.

  • 표준 라이브러리 checkNotNull()와 같은 어설 션 기능requireNotNull()

  • 도우미 함수 filterNotNull()는 컬렉션에서 null을 제거하거나 listOfNotNull()가능한 null값 에서 0 또는 단일 항목 목록을 반환 합니다.

  • 안전 (널 (NULL)) 캐스트 연산자는 가능하지 않은 경우도 그 비 nullable 형식 반환 null로 캐스트 할 수있다. 그러나 위에서 언급 한 다른 방법으로 해결되지 않은 유효한 유스 케이스가 없습니다.


답변

이전 답변은 따라하기 어려운 행동이지만 빠르고 쉬운 방법이 있습니다.

val something: Xyz = createPossiblyNullXyz() ?: throw RuntimeError("no it shouldn't be null")
something.foo() 

실제로 null이 아닌 경우 예외는 발생하지 않지만 문제가 발생한 경우 무엇이 잘못되었는지 확인할 수 있습니다.


답변