[Java 풀스택 개발자] Java의 용어와 개념: 낯선 Java를 친숙하게 만들어보자!

부트캠프 일지/멀티캠퍼스 TIL
2026.02.02

0. 들어가기에 앞서

본래는 특별편인 React 실전으로 먼저 만나려고 했다. 그러나 생각보다 만들려던 것의 규모가 커지면서 다음 기회에 포스팅하게 되었다. 따라서 이번 주는 한국의 백엔드 중심인 java라는 언어에 대해서 정리해보기로 했다.
프런트 엔드만 하거나, 아니면 아예 java를 처음 접한 사람들은 혼란에 빠질 것이다. 그 이유는 생소한 단어가 너무 많기 때문이다. 타입이라든가, 접근 제한자라든가, … 배우면서 그대로 따라하기만 하고, 안 되면 바꾸고 하는 경우가 있을지도 모른다. 그러하여 이번에는 java 용어에 대해서 정돈하고, 개념을 다시 정리하는 포스팅이 된다.
 
 

1. java의 기본 형태

제일 중요한 부분일 것이다. java라는 언어를 알기 위해서는 그 기본 형태에 대해서 파헤칠 필요가 있다. 이 기본 형태에서는 1. 동작 구조 2. 코드의 구조에 대해서 정리해본다.
 

java의 동작 구조

일단 java는 다음과 같은 동작으로 동작한다고 볼 수 있다.
 

 
 
이 부분이 컴파일러 언어의 특징인데, 한 줄씩 읽어 바이트코드로 변환하던 인터프리어 언어와는 다르게, 컴파일러에 의해 바이트 코드로 변환된 것을 기계어로 읽어 실행이 되는 것이다.
이 기계어로 읽어 실행하는 것이 바로 가상머신인 JVM(Java Virtual Machine)이다. JVM의 내부구조는 클래스 로더, 런타임 데이터 영역(메모리), 실행 엔진이라는 세 가지로 이루어져 있다.
 

 
가장 먼저 움직이는 것은 클래스 로더다. 이름 그대로 컴파일된 클래스 파일들을 읽어와서 JVM 내부의 메모리 영역에 배치하는 역할을 한다. 단순히 파일을 옮기는 것뿐만 아니라, 이 과정에서 코드가 자바의 보안 규칙을 잘 지켰는지, 메모리 구조에 적합한지 검증하는 아주 까다로운 절차를 거친다.
이렇게 로딩된 데이터들이 자리를 잡는 곳이 바로 런타임 데이터 영역Runtime Data Area, 즉 우리가 흔히 말하는 메모리 공간이다. 앞서 다루었던 Static 변수들이 머무는 메서드 영역과, 실제 객체들이 생성되는 힙 영역이 바로 이곳에 포함된다. 이곳은 JVM이 프로그램을 실행하는 동안 필요한 모든 재료를 쌓아두는 창고이자 작업대라고 볼 수 있다.
마지막으로 실행 엔진Execution Engine이 이 바이트코드를 실제로 실행한다. 여기서 java만의 독특한 점이 나타나는데, 실행 엔진 안에는 인터프리터와 JIT(Just-In-Time) 컴파일러라는 두 가지 장치가 함께 들어있다.
처음에는 인터프리터가 바이트코드를 한 줄씩 읽으며 실행한다. 한 줄씩 읽다 보니 처음 시작은 빠르지만 전체적인 실행 속도는 다소 느릴 수밖에 없다. 이때 JIT 컴파일러가 등장한다. JIT는 실행 엔진이 돌아가는 모습을 지켜보다가, 자주 반복되는 코드 덩어리(Hotspot)를 발견하면 이를 통째로 기계어로 미리 번역해버린다. 한 번 번역된 코드는 다음부터 인터프리터가 읽을 필요 없이 기계가 즉시 실행할 수 있게 되어 속도가 비약적으로 상승한다.
여기에 자바의 청소부인 가비지 컬렉터(GC)까지 실행 엔진의 일부로서 메모리를 실시간으로 관리한다. 결국 JVM은 클래스 로더를 통해 코드를 들여오고, 런타임 데이터 영역에 데이터를 배치하며, 실행 엔진을 통해 인터프리터와 컴파일러의 장점을 모두 취하는 방식으로 프로그램을 구동하는 것이다. 덕분에 자바는 운영체제에 상관없이 어디서든 동일하게 작동하면서도, 실행하면 할수록 점점 더 빨라지는 영리한 구조를 완성하게 되었다.
 
 

java 코드의 구조

그럼 이제 java 코드의 구조에 대해서 알아볼 차례다. java 코드의 구조는 다음처럼 이루어져 있다.
 

 
 
package명으로 패키지를 지정한다. 그리고 import로 외부 패키지를 불러온다. 이후 class가 전개된다. 이 package와 class에 대해서는 8. package & class에서 상세 설명하겠다. 그리고 필드(변수)와 생성자(constructor), 메서드를 거쳐 메인을 통하여 구현된다.
 

  • 변수field
    필드는 클래스에 포함된 변수(Variable)의 의미이다. 여기에 형(Type)이 들어간다.
  • 생성자constructor
    인스턴스가 생성될 때 자동으로 호출되는 클래스에 속한 특수한 멤버함수로, 메서드와는 구분된다. 인스턴스의 초기화를 위해 사용된다. 이름은 클래스 이름과 동일해야한다. return을 갖지 않는다.
  • 메서드method
    객체 지향 프로그래밍 언어에서 사용하는, 클래스 내부에서 정의된 멤버 함수를 말한다.

 

2. 멤버Member

멤버란 클래스 안에 선언되며, 그 클래스의 구성 요소가 되는 것들을 말한다. 클래스의 '상태'와 '행위'를 정의하는 핵심 요소라고 볼 수 있다. 이 멤버에는 변수, 메서드, 그리고 중첩 클래스와 인터페이스가 있다. 위에서 설명한 것과 비슷해보이지만, 생성자constructor는 빠져있다.
어째서일까? 자바 언어 명세(JLS)에서 생성자는 멤버가 아니라고 정의한다. 이유는 다음과 같다.
 

1. 상속되지 않는다.
2. 생성자의 이름은 클래스와 동일해야 한다.
3. 반환 타입이 존재하지 않는다.

 
 
이 때문에 클래스를 구성하는 요소이기는 하지만, '멤버'라는 범주에는 포함시키지 않는다고 한다. 그렇다면 반대로 멤버에 들어가기 위해서는, 1. 상속을 보증하는가 2. 식별자가 독립성을 가지는가 3. 반환 정의가 존재하는가라고 할 수 있다.
 

3. 형 Type

이전에 JavaScript 언어 체계에 다루면서 JavaScript는 동적 타이핑 언어라는 이야기를 한 적이 있다. 동적 타이핑 언어의 특징에 대해서는 다음을 참조해보자.
 

 

[Java 풀스택 개발자] #특별편(보충학습) - Javascript의 언어체계

0. 들어가기에 앞서본 포스트는 JS의 언어체계와 관련된 보충학습이다. 강의에서는 다루지 않았던, JavaScript의 이론적인 부분을 추가적으로 다루고자 한다. 3주차는 아마 jQuery와 관련된 내용이 될

bbbbabbbababababa.tistory.com

 
java를 포함한 컴파일 언어는 미리 각 변수의 데이터 타입을 선언해야하는 정적 타이핑Static typing이다. 여기서 Type, 즉 형에 대해서 다시 정의를 해볼까 한다.
타입Type(다른 이름으로는 형)이란, MDN의 정의에 의하면 저장할 수 있는 데이터 종류와 데이터가 지켜야 할 구조에 영향을 미치는 값의 특성이라고 한다. 어째서 이런 Type의 정의가 필요할까? 그건 바로 메모리 때문이다. 메모리의 저장 공간을 확보하기 위해서는, 각 타입이 얼마나의 메모리를 차지하는가에 대한 계산이 먼저 필요하다.
 

 
int는 4byte, float은 4byte, double은 8byte 등 각자의 타입에 따라 할당되는 메모리의 크기가 다르다. 이런 할당량을 계산하여 메모리의 공간 크기를 결정해야한다. 또한 불러올 때도 읽어야하는 메모리의 크기를 결정해야하기 때문에, 값에 대한 정의가 필요하다.
 
 
 

4. 접근 제한자 Public / Private / Protected

접근 제한자Access Modifier라는 존재는 java에서 중요하다. 정확히는 java의 추구는 보안과 캡슐화라고 할 수 있으므로, 접근 제한자의 개념 자체를 알면 java의 중심을 아는 것과 비슷하다.
먼저 접근이란 클래스와 인터페이스, 그리고 이들이 가지고 있는 멤버에서의 접근을 이야기한다. 그럼 어째서 이러한 접근을 제한할 필요가 있느냐면, 위에서 말했듯 보안과 캡슐화Encapsulation 때문이다. 이전에 JS를 이야기하면서 프토로타입 기반 상속은 private의 구현에 문제가 있다고 말한 적이 있는데, private는 외부의 선언 등에 영향받지 않기 때문에 중요하다 말한 적이 있다.
이처럼 접근 제한자는 다른 클래스, 인터페이스, 멤버가 접근할 수 있는 권한을 통제한다. JS에서 var가 사용되지 않기 시작한 결정적인 계기가 함수 스코프로 인한 변수 오염 문제라고 이야기했듯, 각 클래스 및 변수들을 오염되지 않게 보호하려면 이렇게 접근 제한자를 사용할 필요가 있다.
 

  • public: 프로젝트 내의 어떤 클래스에서도 접근할 수 있다. 주로 외부에서 호출해야 하는 기능(메서드)에 붙인다.
  • private: 오직 해당 클래스 내부에서만 볼 수 있다. 데이터가 외부의 실수로 오염되는 것을 막기 위해 대부분의 변수(필드)는 private으로 설정한다.
  • protected: 같은 패키지에 있거나, 자신을 상속받은 자식 클래스에게만 접근이 가능하도록 한다.
  • default: 같은 패키지 안에 있는 클래스끼리만 자유롭게 소통할 수 있다.

 

 
 
 

5. Static vs Instance

이 개념은 자바 메모리 관리의 핵심이다. JS에서 전역 변수와 지역변수와 비슷한 차이가 있지만, 더욱 구체적인 차이가 있다. 이 둘은 클래스, 메서드 등등에서 계속 나오기 때문에 제대로 숙지해둘 필요가 있다.
 

Static은 클래스의 것이고, Instance는 객체의 것이다.

 

둘의 차이를 가르는 가장 핵심적인 문장이다. 이 문장을 바탕으로 대체 왜 자바는 이들을 갈라놓았는지, 그 소유권과 메모리의 위치, 그리고 코드 내에서 벌어지는 엄격한 접근 규칙에 대해 파헤쳐보자.

 

소유권

가장 먼저 따져봐야 할 지점은 소유권의 문제다. Static은 클래스 레벨에 귀속된다. 어떤 객체를 만들지 않아도 클래스의 존재 자체가 static을 보증해준다. 변수일 수도 있고 메서드일 수도 있는 이것은 JS에서는 전역 변수와 비슷한 위치를 가진다. 해당 클래스로부터 생성된 모든 객체가 동일한 메모리 주소를 공유하고 있다. 이 때문에 한 곳에서 static을 수정하면, 그 수정된 static의 값이 해당 클래스 내의 객체 전체에 적용된다. static을 만들려면 static이라는 선언문이 필요하다.

반면 Instance는 철저히 개별 객체에 귀속된다. 각 객체는 자신만의 고유한 필드 값을 가지며, 각 객체의 인스턴스 변수는 서로 다른 메모리 공간을 점유한다. 따라서 한 객체의 변수를 수정해도 다른 객체에 영향을 주지 않는다. 그러므로 반드시 사용할 때마다 새로이 객체와 함께 생성하지 않으면 안 된다.

 

생명주기

또 생명주기가 나왔다. 이전에 리액트를 다룰 때도 나왔던 생명주기는, 메모리에서의 생성-사용-소멸 과정을 이야기한다. static의 경우, 프로그램이 실행되어 클래스가 로드될 때 메모리의 Method Area에 그 주소를 할당받는다. 그리고 프로그램이 종료될 때까지 계속해서 존재한다.

반면 instance는 우리가 생성하는 순간(new를 외치는 순간) Heap Area에 그 주소를 할당받는다. 이후 자신이 더 이상 사용되지 않을 때(참조 등) 가비지 컬렉터에 의해서 수거되어 사라진다.

이 때문에 메모리적인 관점에서는 static을 계속 사용하는 것보다는 instance를 사용하는 편이 관리에 편하다. static을 사용하는 경우는 대체로 프로그램의 끝까지 사용할 만큼 변하지 않고 지대한 영향을 끼치는 변수나 메서드 정도에 사용하게 된다.

 

접근 규칙

java를 배우다 보면 가장 많이 마주치는 에러는 "Static 환경에서 Non-static을 참조할 수 없다"는 문구다. static에서는 instance를 참조할 수 없다는 것이다. 이 부분은 스코프를 배웠을 때 생각하면 당연한데, 블록 바깥의 전역변수는 지역변수를 참조할 수가 없다. 즉, 프로그램이 시작되며 먼저 생성된 static은 후에 생성되는 instance를 참조할 수가 없다. 반대로 instance는 쉬이 static을 참조할 수가 있다.

 

 

보다시피 java가 이 둘을 나눈 이유는 메모리 효율성과 데이터 무결성 때문이다. static만을 사용하면 메모리는 효율적으로 관리되지 못한다. 그리고 개별 객체가 관리해야하는 instance 덕분에 독립성과 캡슐화가 보장된다. 하지만 그렇다고 instance만 사용하는 것은, java로 하여금(정확히는 JVM으로 하여금) 프로그램의 시작점을 알려줄 수가 없다. 그러하여 main이 public static void main을 가지는 이유는, 이러한 시작점을 알려주기 위해서다.

 
 

6. Final

북미쪽에는 유명한 퀴즈 프로그램이 하나 있었다. "Is that your final answer?"라는 유명한 문구는 그 프로그램 이름보다 유명할 정도이다. Who Wants to Be a Millionaire?는, 퀴즈의 참가자에 대한 답변을 확정 짓기 위해서 MC가 그러한 질문을 하고는 했다. java에서도 마찬가지다. Is that your final answer? final을 붙인다는 건, 그에 대한 대답이다.
final이라는 이름이 붙은 변수, 메서드, 클래스는 후에 어떠한 형태로든 고칠 수 없다. 그야말로 확정적인 답이기 때문에 어떤 편법으로든 수정할 수 없게 한 것이다. 심지어 클래스에 붙으면 상속조차 할 수 없기 때문에, 절대불변인 값에만 사용할 수 있다.
이러한 불변성은 데이터를 보호하기 위해서다. 중요한 설정값이나 고정된 데이터가 중간에 바뀌어버린다면? 그렇게 되었을 때 오류가 일어난다면? 퀴즈쇼에서도 마찬가지다. 2번이었다가 4번이었다가 하는 참가자가 있다면? 틀렸다고 했을 때 난 사실 3번을 선택했어요 한다면? 그렇기 때문에 최종적으로 이것이 "마지막 선언"이다라고 하는 마지막 선언이 필요한 것이다.
 

 

7. Void와 Return

앞에서 메서드에 대해서 말한 적이 있다. 메서드는 일종의 함수다. 프로그램 내에서 함수라는 것은 알다시피 어떠한 작업을 의미한다. 그러므로 메서드를 호출한다는 건, 어떠한 작업을 시킨다는 것인데, 그 결과물의 처리에 따라서 void와 return이 달라진다.
void의 경우 미리 클래스나 메서드에 선언하게 된다. 말 그대로 "공허", "빈 공간"으로, 아무것도 반환하지 않기 때문이다. void는 일반 쓰레기를 버리는 것과 비슷하다. 쓰레기를 가져다가 버리면 된다. 반면 return은 결과를 반드시 가져온다. 쓰레기-공병-을 10원으로 바꿔오든, 100원으로 바꿔오든, 여하튼 가져가 다시 돈을 가져오게 된다. 이 결과물은 메서드 이름 앞에 적힌 데이터 타입과 일치해야 하며, 이 값을 받은 우리는 이를 변수에 담아 다른 곳에 사용할 수 있다.
 

 
그렇다면 void를 사용하는 이유는 뭘까? 글자를 출력하거나 객체의 상태를 바꾸는 등의 행위를 시키기 위해서다. 즉, 이 메서드를 실행하여 딱히 return할 값이 없다면 void를 사용하면 된다. 반면 return은 돌아온 값을 토대로 무언가를 더 하기 위해서 사용한다. 이 선택은 어느게 더 낫다기보다는 필요에 의해 정해진다.
main이 void를 갖는 이유도 이러하다. 결국에는 "출력"을 중심으로 하고, 결과 값을 반환할 수가 없는 시작점이기 때문이다.
 
 

8. Package & Class

자바의 세계는 거대한 도서관과 같다. 클래스가 한 권 한 권의 책이라면, 패키지는 그 책들이 꽂혀 있는 분류된 서가다. 클래스는 객체를 만들기 위한 설계도이자 그 자체로 하나의 독립된 존재지만, 서비스가 커지면 수백 수천 개의 설계도가 뒤섞이게 된다. 이때 패키지라는 이름의 주머니가 없다면 우리는 이름이 겹치는 클래스들을 관리할 방법이 없다.
마치 전 세계에 '철수'라는 이름을 가진 사람이 많아도 주소와 소속을 통해 구별하듯, 자바도 패키지 경로를 통해 클래스의 고유함을 유지한다. 클래스가 "나는 이런 기능을 가진 물건이야"라고 정의한다면, 패키지는 "나는 이 구역에 속해 있어"라고 말하며 질서를 잡는다. 우리가 코드를 짤 때 가장 윗줄에 패키지를 선언하고 클래스를 정의하는 행위는, 이 설계도가 도서관의 어느 칸에 놓일지 주소를 적어주는 아주 기초적이면서도 중요한 정돈 작업인 셈이다.
 

 
 

9. 다형성Polymorphism

다형성이란, 하나의 타입이 여러 가지 형태를 가질 수 있는 성질을 의미한다. 뭔 소리인가 싶냐면, 앞으로 이야기할 메서드 오버로딩, 오버라이딩 등에서도 두드러지게 나타나는데, "하나에 한 번"이 원칙이지만 "여러 가지도 괜찮다"를 나타내는 것이다. 같은 자료형이어도 여러 가지 타입의 데이터를 대입해 다양한 결과를 얻어낼 수 있다는 것인데… 이 성질을 가장 명확하게 보여주는 것이 바로 참조 변수의 형변환이다. 자바에서 모든 객체는 자기 자신의 타입으로 불릴 수 있지만, 동시에 자신을 있게 한 부모의 타입으로도 불릴 수 있다.
 
먼저 업캐스팅Upcasting을 보자. 업캐스팅이란, 자식 클래스의 인스턴스를 부모 타입의 참조 변수에 담는 것을 말한다. 예를 들어 Parent p = new Child(); 같은 선언이 있다. 자바에서 자식은 언제나 부모의 속성을 모두 포함하고 있기 때문에, 별도의 형변환 연산자 없이도 이 과정은 자동으로 이루어진다. 하지만 여기서 중요한 제약이 생긴다. 변수의 타입이 Parent로 바뀌는 순간, 메모리에 실제로 Child 객체가 올라와 있더라도 우리는 Parent 클래스에 정의된 멤버들만 사용할 수 있게 된다. 자식 클래스에서 새로 추가한 고유한 기능들은 잠시 가려지는 셈이다.
 
그렇다면 가려진 자식만의 기능을 다시 쓰고 싶을 때는 어떻게 할까? 이때 필요한 것이 다운캐스팅Downcasting이다. 부모 타입으로 변해버린 객체를 다시 원래의 자식 타입으로 되돌리는 작업이다. 업캐스팅과 달리 이 과정은 매우 위험하다. 실제 메모리에 있는 객체가 내가 변환하려는 자식 타입이라는 보장이 없기 때문이다. 그래서 자바는 Child c = (Child) p;처럼 명시적으로 타입을 적어줄 것을 강제하며, 만약 실제 객체와 타입이 맞지 않으면 프로그램이 즉시 멈춰버린다. 이를 안전하게 처리하기 위해 우리는 instanceof 연산자를 사용하여 실제 객체의 정체를 먼저 확인하는 과정을 거치게 된다.
 

class Parent {
    void parentMethod() {
        System.out.println("Parent 메서드");
    }
}

class Child extends Parent {
    void childOnly() {
        System.out.println("Child만의 메서드");
    }
}

public class CastingImpossible {
    public static void main(String[] args) {
        // 실제 객체는 Parent. Child가 아님.
        Parent p = new Parent();

        // 다운캐스팅 시도 → 실제 객체가 Child가 아니므로 실행 시 ClassCastException
        // Child c = (Child) p;   // 런타임 에러: ClassCastException

        // 안전하게: instanceof로 먼저 확인
        if (p instanceof Child) {
            Child c = (Child) p;
            c.childOnly();
        } else {
            System.out.println("p는 Child가 아님. 다운캐스팅 불가.");
        }
    }
}

 
 

10. 메서드 오버로딩 Method Overloading

다형성을 알았다면, 이제 메서드 오버로딩에 대해서 설명할 차례다. 오버로딩은 다형성의 예시가 되어주기도 한다. 본래 하나의 이름에는 하나의 기능만 연결되어야 한다. 이건 당연하다. 세미콜론 하나도 예민하게 대응하는 프로그래밍 세계에서, 이름이 같은 메서드라는 건 같은 기능을 사용하겠다는 말이나 다름 없다. 그러나 오버로딩과 함께라면 달라진다. 자바는 특정 조건을 만족하면 같은 이름을 공유할 수 있게 허용해 주고 있다.
 
이 오버로딩의 조건 중 재미있는 부분은 바로 메서드 시그니처Method Signature다. 이를 통해서 같은 이름의 메서드여도 메서드들을 컴파일러가 구분해줄 수 있다. 이것은 메서드 이름과 매개변수의 리스트(타입, 개수, 순서)로 구분된다. 즉, 메서드 이름이 같아도 매개 변수의 리스트가 다르면 다른 메서드마냥 취급해준다. 그러나 이런 생각이 들 수도 있다.
 

다 다르게 이름을 지으면 되지… 왜?

 
이 부분은 가독성과 편의성 때문이다. 이름이 같다는 이유로 같이 취급해버리면 비슷한 작용의 메서드여도, 데이터의 타입에 따라 메서드 이름을 다르게 처리해야한다. 즉, 데이터의 타입에 대한 엄격한 규칙에 대한 보상인 셈이다.
 
 
 

11. 상속Inherit & 오버라이딩

상속은 기존 클래스(부모)의 필드와 메서드를 새로운 클래스(자식)가 물려받는 것이다. 클래스 선언에서 extends 키워드를 사용하면 상속이 이루어진다. 이 상속은 단일 상속만 가능하며, 최상위에는 Object 클래스가 존재한다. 자식 객체를 생성하면 메모리(Heap) 상에는 부모 객체의 멤버들도 함께 올라가게 되는데, 이로서 자식이 부모 객체의 멤버들도 사용할 수 있게 된다. 다만, 부모라도 사생활은 지켜줘야하는 터라 private로 선언되었다면 그건 자식이라고 할 지언정 접근할 수 없다.
 
오버라이딩 또한 위에서 말한 다형성의 대표적인 예제이다. 물려받은 메서드를 자식이 상황에 맞게 덮어쓰는 작업, 즉 재정의다. 이 오버라이딩은 부모가 정의한 메서드와 이름, 매개변수, 반환 타입이 모두 동일해야 성립한다. 부모 클래스에 "차가 달린다"라는 메서드가 있다면, 그걸 상속받은 자식 클래스는 "벤츠가 시속 180km로 달린다"처럼 덮어쓸 수가 있다. 부모의 틀 안에서 자식의 특정한 메서드를 실행하는 것이 바로 오버라이딩이라 할 수 있다. 이 오버라이딩이 바로 자바가 객체지향 언어로서 변화와 확장에 유연하게 대처하는 핵심적인 방식이다.
 
 

12. 초기화 Initial

객체가 태어나는 순간, 즉 'new'를 통해 세상에 나오는 그 찰나에 가장 먼저 일어나는 일이 초기화다. 아무리 좋은 설계도가 있어도, 막 태어난 객체의 변수들이 쓰레기 값으로 가득 차 있다면 그건 제대로 된 물건이라 할 수 없다. 초기화는 객체가 안전하게 활동할 수 있도록 최소한의 기본값을 세팅하거나, 우리가 원하는 시작 상태를 만들어주는 과정이다.
우리는 생성자를 통해 이 과정을 제어한다. 생성자는 객체가 생성될 때 단 한 번만 호출되며, 이때 전달받은 값들로 객체의 속성을 채워 넣는다. 초기화되지 않은 변수를 사용하려다 프로그램이 멈추는 비극을 막기 위해, 자바는 객체의 탄생과 동시에 모든 변수를 정해진 규칙에 따라 깨끗이 정리한다. 탄생과 동시에 이름을 지어주고 옷을 입혀주는 과정, 그것이 바로 초기화가 가진 의미다.
 
 

13. == 연산자와 equals

java에서 가장 많이 헷갈리는 것 중 하나가 바로 '동일한가'이다. 이 문제는 바로 데이터의 타입에 따라서 기본형이냐 참조형이냐가 갈리기 때문이다. 기본형(자료형)으로는 int와 같은 숫자들이나 char과 같은 문자가 있다. 반면 참조형의 대표적인 예는 바로 문자열String이다. 이녀석은 다른 녀석들과 달리 메모리를 미리 계산하지도 않으며, 주소를 참조 한다.
주소를 참조…? 바로 heap에서 실제 데이터가 살고 있는 '번지수'를 따온다는 뜻이다. 기본형인 int나 double은 변수 안에 진짜 '값'이 들어있다. 그러나 참조형은 변수 안에 해당 '참조 주소'를 가지고 있으며, 값을 불러들일 때 메모리에서 '참조 주소'에 있는 값을 가져오게 된다. 참조 주소에 관해서는 일지에서 내용을 쓴 적이 있다.
이 때문에 == 연산자의 사용이 기본형과 참조형이 달라진다. 기본형의 경우 ==를 쓴다면 진짜 값을 사용하여 비교하게 된다. 그러나 참조형은 그 '참조 주소'를 비교하게 된다. 각 객체나 필드의 생성이 별도일 경우, 값이 동일하더라도 참조 주소는 다르게 된다. 즉, == 연산자를 쓰면 아무리 이름이 동일한 "홍길동"이어도 주소가 다르니 다른 것으로 취급한다.
 
그렇기 때문에 String 등의 참조형에서는 equals를 사용하게 된다. 참조 주소가 다르더라도 그 값은 동일한가를 따지는 것이다. 네가 강남에 살든, 제주도에 살든 "홍길동"이라는 이름이 맞냐고 물어보는 것이 바로 equals라고 할 수 있다.
 

 

14. try-catch

개발자는 언제나 예외 상황을 맞닥뜨린다. SNS에 돌던 유명한 프로그래머 농담이 있다.
 

QA 엔지니어가 술집에 들어갔습니다.
맥주 한 잔을 주문했습니다.
맥주를 한 잔도 주문하지 않았습니다.
맥주를 99999999999잔 주문했습니다.
도마뱀을 주문했습니다.
맥주를 -1잔 주문했습니다.
ㅁㄴ래ㅓㅐㅈ를 주문했습니다.
첫 번째 손님이 들어와 화장실이 어디냐고 묻자, 술집은 불타 모두 죽었습니다.

 
 
술집을 불태우고 싶지 않다면 해야하는 것이 바로 이 try-catch다.
 
일단 문제가 생길 법한 코드를 'try'라는 박스 안에 넣어둔다. 그리고 혹시라도 사고가 터지면 'catch'라는 구급대원이 출동해서 상황을 수습한다. 프로그램 전체를 멈추는 대신, 에러 메시지를 보여주거나 우회하게 한다. 사고가 나지 않는 건 중요하지만, 술집은 어쨌든 불탈 위기에 처한다. AI가 아니라 사람이 만드는 코드라면 더더욱 그렇다. 위험할 수 있는 일을 예방하는 것, 그것이 java에서 제공하는 try-catch문이다.
 

 

15. Interface

인터페이스Interface는 java의 다형성의 예제 중 하나다. 상속과 비슷해보이지만, 인터페이스는 클래스가 갖춰야 할 기능을 정의해둔 설계도나 표준 규격에 가깝다. 클래스는 하나의 규칙Regulation을 제시한다고 할 수 있다. 정작 인터페이스 안에는 메서드의 이름과 재료만 적혀 있을 뿐, 실제 어떻게 동작하는지에 대한 내용은 텅 비어 있다. 이 비어있는 것들은 바로 추상 메서드로, 선언만 하고 나중에 채워넣는다는 약속을 한 메서드이다.

그리고 이것을 인터페이스와 짝을 이루기로 한 클래스들이 메서드를 채운다. 인터페이스에서 만든 규칙에 따른 메서드를 반드시 짝을 이루기로 한 클래스들은 오버라이딩하여 채워넣어야 한다. 그렇지 않으면 그 클래스는 컴파일 되지 않는다. 오버라이딩? 그렇다. 인터페이스interface는 상속과 비슷하지만, 상속의 단점을 보완한다. 클래스 상속은 하나만 가능한 것에 반해, 인터페이스는 그 개수에 대한 제한이 없다.

 

그리고 인터페이스는 클래스 간 의존성을 줄인다. 이를 전문 용어로 결합도를 낮춘다고 표현한다. 어떤 메서드의 매개변수 타입을 구체적인 클래스가 아니라 인터페이스 타입으로 선언해두면, 나중에 그 인터페이스를 구현한 어떤 클래스가 들어오더라도 코드를 수정할 필요가 없다. 이 부분이 바로 인터페이스가 갖는 다형성이라고 할 수 있다.

 

인터페이스는 메서드뿐만 아니라 변수도 가질 수 있는데, 이때 모든 변수는 자동으로 public static final이 된다. 즉, 인터페이스의 변수는 무조건 상수가 된다. 이 값은 바꿀 수 없고, 인터페이스 이름만으로 어디서든 접근할 수 있는 공용 데이터가 된다.

 

그 외 특기할만한 것으론 java 8 이후부터는 default 메서드라는 개념이 추가되었다. 인터페이스 안에서도 기본적인 구현 코드를 가질 수 있게 된 것인데, 이는 이미 널리 쓰이고 있는 인터페이스에 새로운 기능을 추가해야 할 때, 그 인터페이스를 사용하던 수많은 클래스를 일일이 수정하지 않아도 되게 하고자 추가된 기능이다.

 

결국 인터페이스는 복잡한 시스템 속에서 부품과 부품이 서로 소통하는 규칙을 정하는 일이다. 이 규칙을 얼마나 잘 세우느냐에 따라 프로그램이 얼마나 유연하게 변화에 대처할 수 있는지가 결정된다.

 
 

16. 정리하며

이번 주는 java의 용어와 개념에 대해서 간단히 정리해보았다. 처음 접하면 복잡하지만, 이 개념들만 알아두면 웬만한 자바의 구조는 이해할 수 있다고 할 수 있다. 다음 주는 무엇이 될지 알 수가 없지만 React 실전은 가급적이면 넣을 예정이다.