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


177

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에 있습니다. 또한 현재 코 틀린에 대해 정확하지 않은 코 틀린 알파에 대해 작성된 오래된 답변을 명확하게하기 위해.

답변:


296

먼저, 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로 캐스트 할 수있다. 그러나 위에서 언급 한 다른 방법으로 해결되지 않은 유효한 유스 케이스가 없습니다.


1

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

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

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


12
val something : Xyz = createPossiblyNullXyz () !! createPossiblyNullXyz ()가 null을 반환하면 NPE가 발생합니다. 그것은 간단하고 당신이 알고있는 값을 처리하기위한 규칙을 다음은 null가 아닌
스티븐 워터맨
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.