스칼라에서 var와 val 정의의 차이점은 무엇입니까?


301

스칼라에서 a varval정의 의 차이점은 무엇 이며 왜 언어에 둘 다 필요한가요? 왜 val이상 을 선택 var하고 그 반대를 선택 하시겠습니까?

답변:


331

다른 많은 사람들이 말했듯이 val캔에 할당 된 객체 는 교체 할 수 없으며 캔에 할당 된 객체 는 교체 할 수 없습니다 var. 그러나, 상기 객체는 내부 상태가 수정 될 수있다. 예를 들면 다음과 같습니다.

class A(n: Int) {
  var value = n
}

class B(n: Int) {
  val value = new A(n)
}

object Test {
  def main(args: Array[String]) {
    val x = new B(5)
    x = new B(6) // Doesn't work, because I can't replace the object created on the line above with this new one.
    x.value = new A(6) // Doesn't work, because I can't replace the object assigned to B.value for a new one.
    x.value.value = 6 // Works, because A.value can receive a new object.
  }
}

따라서에 할당 된 객체를 변경할 수는 없지만 해당 객체 x의 상태를 변경할 수 있습니다. 그러나 그 근본에는 var.

불변성은 여러 가지 이유로 좋은 것입니다. 첫째, 객체가 내부 상태를 변경하지 않아도 코드의 다른 부분이 변경되는지 걱정할 필요가 없습니다. 예를 들면 다음과 같습니다.

x = new B(0)
f(x)
if (x.value.value == 0)
  println("f didn't do anything to x")
else
  println("f did something to x")

이것은 다중 스레드 시스템에서 특히 중요합니다. 멀티 스레드 시스템에서 다음이 발생할 수 있습니다.

x = new B(1)
f(x)
if (x.value.value == 1) {
  print(x.value.value) // Can be different than 1!
}

당신이 사용하는 경우 val에만, 오직 불변의 데이터 구조 (즉, 피할 어레이의 모든 사용 scala.collection.mutable등), 당신은 이런 일이되지 않습니다 안심 할 수 있습니다. 즉, 리플렉션 트릭을 수행하는 코드가 있거나 프레임 워크가없는 경우 리플렉션은 "불변"값을 변경할 수 있습니다.

한 가지 이유가 있지만 또 다른 이유가 있습니다. 를 사용하면 여러 용도로 var동일한 것을 재사용하려는 유혹을받을 수 있습니다 var. 여기에는 몇 가지 문제가 있습니다.

  • 코드를 읽는 사람들은 코드의 특정 부분에서 변수의 값이 무엇인지 알기가 더 어려울 것입니다.
  • 일부 코드 경로에서 변수를 다시 초기화하고 코드에서 잘못된 값을 다운 스트림으로 전달하는 것을 잊을 수 있습니다.

간단히 말해, 사용하는 val것이 더 안전하고 더 읽기 쉬운 코드로 이어집니다.

그러면 우리는 다른 방향으로 갈 수 있습니다. val그것이 더 나은 경우 , 왜 var전혀 있습니까? 글쎄, 일부 언어는 그 길을 택했지만 가변성이 성능을 향상시키는 상황이 많이 있습니다.

예를 들어, 불변을 가져옵니다 Queue. 당신이 enqueue또는 그 dequeue안에있을 때, 당신은 새로운 Queue물건 을 얻습니다 . 그렇다면 모든 항목을 처리하는 방법은 무엇입니까?

예제를 통해 살펴 보겠습니다. 숫자 줄이 있고 숫자를 작성한다고 가정 해 봅시다. 예를 들어, 순서대로 2, 1, 3의 대기열이 있으면 숫자 213을 다시 얻고 싶습니다. 먼저 다음과 같이 해결하십시오 mutable.Queue.

def toNum(q: scala.collection.mutable.Queue[Int]) = {
  var num = 0
  while (!q.isEmpty) {
    num *= 10
    num += q.dequeue
  }
  num
}

이 코드는 빠르고 이해하기 쉽습니다. 주요 결점은 전달 된 대기열이에 의해 수정 toNum되므로 미리 사본을 만들어야한다는 것입니다. 그것은 불변성이 당신을 자유롭게하는 종류의 객체 관리입니다.

자, 그것을 다음과 같이 보자 immutable.Queue.

def toNum(q: scala.collection.immutable.Queue[Int]) = {
  def recurse(qr: scala.collection.immutable.Queue[Int], num: Int): Int = {
    if (qr.isEmpty)
      num
    else {
      val (digit, newQ) = qr.dequeue
      recurse(newQ, num * 10 + digit)
    }
  }
  recurse(q, 0)
}

num이전 예제 에서처럼 내 변수를 추적하기 위해 일부 변수를 재사용 할 수 없기 때문에 재귀를 사용해야합니다. 이 경우에는 꼬리 재귀이며 성능이 매우 좋습니다. 그러나 항상 그런 것은 아닙니다. 때로는 좋은 (읽기 쉽고 간단한) 꼬리 재귀 솔루션이 없습니다.

단, 내가 사용하는 코드를 다시 작성할 수있는 immutable.Queue과를 var동시에를! 예를 들면 다음과 같습니다.

def toNum(q: scala.collection.immutable.Queue[Int]) = {
  var qr = q
  var num = 0
  while (!qr.isEmpty) {
    val (digit, newQ) = qr.dequeue
    num *= 10
    num += digit
    qr = newQ
  }
  num
}

이 코드는 여전히 효율적이며 재귀를 요구하지 않으며을 호출하기 전에 큐 사본을 만들어야하는지 걱정할 필요가 없습니다 toNum. 당연히 다른 목적으로 변수를 재사용하는 것을 피 했으며이 함수 외부의 코드에서는 변수를 볼 수 없으므로 명시 적으로 수행하지 않는 한 값이 한 줄에서 다음 줄로 변경되는 것에 대해 걱정할 필요가 없습니다.

스칼라는 프로그래머가 최선의 해결책이라고 생각하면 프로그래머가 그렇게 할 수 있도록 선택했다. 다른 언어들은 그러한 코드를 어렵게 만들었습니다. 스칼라 (및 광범위한 가변성을 가진 모든 언어)가 지불하는 가격은 컴파일러가 코드를 최적화하는 데 많은 여유가 없다는 것입니다. 이에 대한 Java의 대답은 런타임 프로파일을 기반으로 코드를 최적화하는 것입니다. 우리는 양측의 장단점에 대해 계속 갈 수있었습니다.

개인적으로, 스칼라는 지금 당장 균형을 맞추고 있다고 생각합니다. 완벽하지는 않다. ClojureHaskell 은 스칼라에서 채택하지 않은 매우 흥미로운 개념을 가지고 있다고 생각 하지만 스칼라는 자체 강점도 있습니다. 우리는 미래에 무슨 일이 일어날 지 보게 될 것입니다.


조금 늦었지만 ... var qr = q사본을 만드 q나요?

5
@davips에 의해 참조되는 객체의 복사본을 만들지 않습니다 q. 스택이 아닌 스택 에 해당 객체 에 대한 참조 사본 을 만듭니다. 성능에 관해서는, 당신이 말하는 "그것"에 대해 더 분명해야합니다.
Daniel C. Sobral

좋아, 귀하의 도움과 여기에있는 일부 정보 ( (x::xs).drop(1)정확하게 xs는 "복사본"이 xs아님) 링크로 이해할 수 있습니다. tnx!

"이 코드는 여전히 효율적입니다"-맞습니까? qr불변 큐 이므로 식이 qr.dequeue호출 될 때마다 new Queue< github.com/scala/scala/blob/2.13.x/src/library/scala/collection/… 참조 ).
Owen

@Owen 네, 그러나 그것은 얕은 물체입니다. 큐를 복사하거나 변경할 수없는 경우 코드 변경 가능 여부는 여전히 O (n)입니다.
Daniel C. Sobral

61

val최종, 즉 설정할 수 없습니다. final자바로 생각하십시오 .


3
그러나 (스칼라 전문가가 아닌) 올바르게 이해하면 val 변수 는 변경할 수 없지만 참조하는 객체는 필요하지 않습니다. Stefan이 게시 한 링크에 따르면 "여기에서 이름 참조는 다른 배열을 가리 키도록 변경할 수 없지만 배열 자체는 수정할 수 있습니다. 즉, 배열의 내용 / 요소를 수정할 수 있습니다." finalJava에서 작동 하는 방식 과 같습니다 .
Daniel Pryden

3
정확히 내가 왜 게시했는지 나는 단지 잘 +=정의 된 가변 해시 맵을 호출 할 수있다. val나는 그것이 final자바에서 정확히 어떻게 작동 하는지 믿는다
Jackson Davis

Ack, 내장 스칼라 유형이 단순히 재 할당을 허용하는 것보다 더 나을 수 있다고 생각했습니다. 사실을 확인해야합니다.
Stefan Kendall

스칼라의 불변 시퀀스 유형을 일반적인 개념과 혼동했습니다. 함수형 프로그래밍은 모두 돌아섰습니다.
Stefan Kendall

나는 당신에게 답장을 줄 수 있도록 당신의 대답에 더미 캐릭터를 추가하고 제거했습니다.
Stefan Kendall


20

차이점은을 var다시 할당 할 수 있지만을 val할 수는 없다는 것입니다. 변경 가능성 또는 실제로 할당 된 것 중 하나는 부수적 인 문제입니다.

import collection.immutable
import collection.mutable
var m = immutable.Set("London", "Paris")
m = immutable.Set("New York") //Reassignment - I have change the "value" at m.

이므로:

val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.

따라서 :

val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.

데이터 구조를 작성 중이고 모든 필드가 vals 인 경우 상태가 변경 될 수 없으므로 해당 데이터 구조는 변경할 수 없습니다.


1
해당 필드의 클래스도 변경할 수없는 경우에만 해당됩니다.
Daniel C. Sobral

그래-나는 그것을 넣을 예정 이었지만 너무 멀었다 고 생각했다! 또한 내가 말하고자하는 논쟁의 여지가있다. 하나의 관점에서 (기능적 관점은 아니지만) 상태의 상태가 변하더라도 상태는 변하지 않습니다.
oxbow_lakes

JVM 언어로 불변의 객체를 작성하는 것이 왜 그렇게 어려운가? 게다가 스칼라는 왜 기본적으로 객체를 불변으로 만들지 않았습니까?
Derek Mahar

19

val불변의 var것을 의미하고 변경 가능한 것을 의미합니다.

전체 토론.


1
이것은 사실이 아닙니다. 링크 된 기사는 변경 가능한 배열을 제공하고이를 불변이라고 부릅니다. 심각한 출처가 없습니다.
클라우스 슐츠

사실은 아닙니다. val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ( "")) println ( "")
Guillaume

13

C ++의 관점에서 생각하면

val x: T

일정하지 않은 데이터에 대한 지속적인 포인터와 유사

T* const x;

동안

var x: T 

상수가 아닌 데이터에 대한 상수가 아닌 포인터와 유사

T* x;

우선권 valvar두면 코드베이스의 불변성이 증가하여 정확성, 동시성 및 이해가 용이 해집니다.

상수가 아닌 데이터에 대한 지속적인 포인터의 의미를 이해하려면 다음 스칼라 스 니펫을 고려하십시오.

val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)

여기에서 "포인터" val m 는 일정하므로 다른 것을 가리 키도록 다시 할당 할 수 없습니다

m = n // error: reassignment to val

것을 그러나 우리는 참으로 일정하지 않은 데이터 자체를 변경할 수 있습니다 m포인트가 너무 좋아

m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)

1
Scala는 최종 결론에 대해 불변성을 취하지 않았다고 생각합니다 : 상수 포인터와 상수 데이터. 스칼라는 기본적으로 객체를 불변으로 만들 기회를 놓쳤다. 결과적으로 스칼라는 하스켈과 같은 가치관을 갖지 않습니다.
Derek Mahar

당신이 맞아요을 @DerekMahar,하지만 여전히, 그 구현에 가변성을 사용하는 동안 물체는 같은 완벽 불변 자신을 제시 할 수 있습니다 예를 들어 성능상의 이유로. 컴파일러는 어떻게 진정한 가변성과 내부 전용 가변성을 분리 할 수 ​​있을까요?
francoisr

8

"val은 변경 불가능 함을 의미하고 var는 변경 가능함을 의미합니다."

바꾸어 말하면, "val은 값을 의미하고 var는 변수를 의미합니다".

컴퓨팅에서 매우 중요한 차이 (이 두 개념이 프로그래밍의 핵심 요소를 정의하기 때문에)와 OO가 거의 완전히 흐릿하게 관리되었다는 점은 OO에서 유일한 원칙은 "모든 것이 목적". 그리고 그 결과 오늘날 많은 프로그래머들이 독점적으로 "OO 방식을 생각"하기 위해 세뇌되어 왔기 때문에 이해 / 감사 / 인식하지 않는 경향이 있습니다. 가치 / 불변의 객체가 종종 더 좋았을 수도있는 가변 / 변동 가능한 객체가 어디에서나 사용되는 경우가 종종 있습니다.


이것이 내가 자바보다 Haskell을 선호하는 이유입니다.
Derek Mahar

6

val은 변경 불가능 함을 의미하고 var는 변경 가능함을 의미합니다

valJava 프로그래밍 언어 final키 세계 또는 C ++ 언어 const키 세계 로 생각할 수 있습니다.


1

Val최종을 의미 하며 재 할당 할 수 없음

반면 나중에 다시 할당Var 할 수 있습니다 .


1
이 답변은 이미 제출 된 12 개의 답변과 어떻게 다릅니 까?
jwvh

0

이름만큼 간단합니다.

var는 다를 수 있음을 의미합니다

val은 변하지 않는 것을 의미


0

Val-값은 유형이 지정된 스토리지 상수입니다. 일단 생성 된 값은 다시 할당 할 수 없습니다. 키워드 val을 사용하여 새 값을 정의 할 수 있습니다.

예. val x : Int = 5

스칼라는 할당 된 값에서 유추 할 수 있으므로이 유형은 선택 사항입니다.

Var-변수는 메모리 공간이 예약되어있는 한 값을 다시 할당 할 수있는 유형의 저장 단위입니다.

예. var x : Int = 5

두 스토리지 장치에 저장된 데이터는 더 이상 필요하지 않은 JVM에 의해 자동으로 할당 해제됩니다.

스칼라에서는 특히 동시 및 멀티 스레드 코드에서 코드를 가져 오는 안정성으로 인해 변수보다 선호됩니다.


0

많은 사람들이 이미 Valvar 의 차이점에 대답했습니다 . 그러나 주목해야 할 것은 val은 최종 키워드 와 정확히 다르다는 것입니다 .

재귀를 사용하여 val 값을 변경할 수 있지만 final 값을 변경할 수는 없습니다. 결승전은 Val보다 일정합니다.

def factorial(num: Int): Int = {
 if(num == 0) 1
 else factorial(num - 1) * num
}

메소드 매개 변수는 기본적으로 val이며 모든 호출 값에서 변경됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.