🏹기본형(Primitive type) : 값을 그대로 할당
기본형 데이터는 값을 그대로 할당하는 것.
메모리 내에 고정된 크기로 저장되면서, 원시 데이터 값 자체를 보관, 불변적
기본적으로 데이터는 하나의 메모리로 사용한다. (재사용)
예를 들어보자.
변수명(a,b,c)를 만들면 변수를 저장할 비어있는 데이터 영역을 확보한다.
--변수 a는 313번 저장공간에, b는 314번 공간에 c는 315 공간에 해당 주소를 확보하고 각
해당 주소를 변수명과 맵핑시킨다.
--기존 변수명을 새롭게 할당하려고 하는 경우, 새로운 변수는 별도의 공간을 확보하고,
불리언 값을 통해 기존 변수에 대입된다.(기본 값은 직접적 비교 가능)
여기다가 b와 c가 같다고 표현을 해보았다.
false의 명제는 세상에 하나 뿐이므로 b===c가 된다.
.
.
다시 C에 20을 대입하였다.👇👇
그렇게되면 c는 20이란 데이터값으로 대체 되므로 b !== c 이다.
.
.
🏹Object
참조형은 기본 데이터의 조합
참조형 데이터는 값이 지정된 주소의 값을 할당한다.
저장되는 과정
결론
.
.
.
Immutability(변경불가성)는 객체가 생성된 이후 그 상태를 변경할 수 없는 디자인 패턴을 의미한다. Immutability은 함수형 프로그래밍의 핵심 원리이다.
불변 객체를 사용하면 복제나 비교를 위한 조작을 단순화 할 수 있고 성능 개선에도 도움이 된다.
하지만 객체가 변경 가능한 데이터를 많이 가지고 있는 경우 오히려 부적절한 경우가 있다.ES6에서는 불변 데이터 패턴(immutable data pattern)을 쉽게 구현할 수 있는 새로운 기능이 추가되었다.
.
.
아래 Javascript의 원시 타입(primitive data type)은 변경 불가능한 값(immutable value)이다.
원시 타입 이외의 모든 값은 객체(Object) 타입이며 객체 타입 은 변경 가능한 값(mutable value)이다.
즉, 객체는 새로운 값을 다시 만들 필요없이 직접 변경이 가능하다는 것이다.
💡 객체는 변할 수 있는 값이고 원시 타입은 불변한 값이다.
예를 들어 살펴보자. C 언어와는 다르게 Javascript의 문자열은 변경 불가능한 값(immutable value) 이다. 이런 값을 “primitive values” 라 한다. (변경이 불가능하다는 뜻은 메모리 영역에서의 변경이 불가능하다는 뜻이다. 재할당은 가능하다)
let str = 'Hello'
str = 'world'
위의 예시를 보면 쉽게 이해할 수 있다.
첫번째 줄이 실행되면 메모리에 문자열 'Hello'가 생성되고, 변수 str은 'Hello'의 메모리 주소를 가리킨다. 그리고 두번째 줄이 실행되면 이전에 생성되었던 문자열 'Hello'를 수정하는 것이 아니라 새로운 문자열 ‘world’를 메모리에 생성하고 식별자 str은 이것을 가리킨다. 이때 문자열 ‘Hello’와 ‘world’는 모두 메모리에 존재하고 있다. 변수 str은 문자열 ‘Hello’를 가리키고 있다가 문자열 ‘world’를 가리키도록 변경되었을 뿐이다. *(원시 타입은 불변한 값이기 때문)
.
.
.
먼저 불변(immutability)이란 뭘까? 단어에서 유추해볼 수 있다시피 '변하지 않는' 뜻이라고 생각하면 되겠다. 그럼 '불변 객체'란? '변하지 않는 객체' 즉 이미 할당된 객체가 변하지 않는다는 뜻을 가지고 있다.
자바스크립트에서 불변 객체를 만들 수 있는 방법은 기본적으로 2가지 인데 const와 Object.freeze()를 사용하는 것이다.
❓ Const
자바스크립트는 ES6(ECMA 2015)부터 let과 const 키워드를 제공한다. 이 중 const 키워드를 사용하면 상수 변수 선언을 할 수 있다. 그리고 const는 상수로 취급되기 때문에 다음과 같이 값을 변경하려고 시도하면 에러가 발생한다.
const hello = 'JS World';
hello = 'JS Hell'; // Uncaught TypeError: Assignment to constant variable.
하지만 자바스크립트의 const는 다른 여러 언어의 상수 취급과는 완전히 동일하지 않다.
그 이유는 정확히 얘기하면 ES6의 const는 할당된 값이 상수가 되는 것이 아니고, 바인딩된 값이 상수가 되기 때문이다.
const b = {};
b.key = 'value';
console.log(b); // {'key': 'value'}
위 코드와 같이 const로 선언된 객체의 속성 변경이 가능한 이유는 실제 객체가 변경되는 것은 맞지만 const로 선언한 변수와 객체 사이의 바인딩은 변경되지 않기 때문이다. 따라서 가장 먼저 봤던 코드와 같이 변수 자체를 재할당하려는 경우에는 변수와 값 사이의 바인딩이 변경되어 오류가 발생하는 것이다.
프로그래밍에서 상수는 코드 내에서 개발자의 실수로 인해 값이 변경되지 않도록 변수를 보호하거나 다른 코드에서 실수로 이미 할당된 변수를 재할당하지 않도록 하는데 유용하기 때문에 많이 사용된다.
하지만 자바스크립트의 const로 객체를 선언할 경우에 객체의 속성은 언제든지 변경이 가능하기 때문에 immutable한 상수로 사용된다고 보기 어렵다.
그래서 const와 같이 유용하게 사용되는 것이 Object.freeze()이다.
.
.
❓ Object.freeze()
바스크립트에서 제공하는 Object.freeze()는 MDN 문서에서 "객체를 동결하기 위한 메서드"라고 설명하고 있다.
즉, Object.freeze를 사용하면 동결된 객체를 만들 수 있고, 동결된 객체에는 속성을 추가하거나 제거하는 동작이 불가능한 Immutable한 객체를 만들 수 있는 것이다. 또한 Object.freeze로 동결된 객체는 프로토타입의 변경도 막아준다.
let itGo = {
elsa = 'Princess';
};
Object.freeze(itGo);
먼저 let으로 선언된 객체를 Object.freeze를 통해 동결된 객체로 만들었다.
따라서 아래와 같이 해당 객체의 속성을 변경하는 시도는 무시된다.
itGo.elsa = 'Prince';
console.log(itGo) // {elsa: "Princess"} -> Not Modified
하지만 Object.freeze는 동결된 객체를 반환할 뿐 재할당을 허용한다.
따라서 let으로 선언된 객체는 Object.freeze를 사용하더라도 다음과 같이 재할당 할 수 있다.
itGo = {
'Olaf': 'Snowman'
};
console.log(itGo); // {'Olaf': 'Snowman'}
❗ const + Object.freeze
그렇다!
우리가 원하는 Immutable한 객체를 생성하기 위해서는 const와 Object.freeze를 함께 사용하면 된다.
const truth = {
'dogIs': 'Cute'
};
Object.freeze(truth);
위 코드는 "강아지는 귀엽다" 라는 절대 불변의 진리(?)를 truth라는 객체의 속성으로 등록했다.
그리고 해당 truth 객체를 Object.freeze를 통해 동결된 객체로 만들었다.
===============================================================================
해당 객체는 아래와 같이 재할당과 속성 변경이 불가능한 Immutable한 객체가 되었다.
truth.dogIs = 'Not Cute';
truth = {'catIs': 'Pretty'}; // Uncaught TypeError: Assignment to constant variable.
concole.log(truth); // {'dogIs': 'Cute'} // Not Modified
.
.
.
public class CopyObject {
private String name;
private int age;
public CopyObject() {
}
public CopyObject(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CopyObjectTest {
@Test
void shallowCopy() {
CopyObject original = new CopyObject("JuHyun", 20);
CopyObject copy = original; // 얕은 복사
copy.setName("JuBal");
System.out.println(original.getName());
System.out.println(copy.getName());
}
}
위 코드에서는 copy 객체에 set메소드를 통해 이름을 변경했는데,
실제 결과는 original 객체와 copy 객체 모두 값이 변경이 되었습니다.
CopyObject copy = original 의 코드에서 객체의 얕은 복사를 통해 '주소 값'을 변경했기 때문에 참조하고 있는 실제 값은 동일하고, 복사한 객체가 변경된다면 기존의 객체도 변경이 되는 것입니다.
.
.
이해를 돕기 위한 메모리 구조 상태🎈
따라서 코드로는 copy 객체의 name만 변경했지만,
동일한 주소를 참조하고 있기 때문에 original의 객체에도 영향을 끼치게 됩니다.
.
.
.
깊은 복사를 구현하는 방법은 여러가지가 있습니다.
public class CopyObject implements Cloneable {
private String name;
private int age;
public CopyObject() {
}
public CopyObject(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected CopyObject clone() throws CloneNotSupportedException {
return (CopyObject) super.clone();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
@Test
void shallowCopy() throws CloneNotSupportedException {
CopyObject original = new CopyObject("JuHyun", 20);
CopyObject copy = original.clone();
copy.setName("JuBal");
System.out.println(original.getName());
System.out.println(copy.getName());
}
// * 깊은 복사를 통해 테스트를 진행해보면 얕은 복사와는 달리 original 인스턴스의 값은 변경이 되지 않습니다.
.
.
이해를 돕기 위한 메모리 구조 상태🎈