JAVA의 모든 프로그램 소스는 클래스 단위로 시작한다.
일반적으로 클래스 이름과 소스파일명은 동일하며, 실행을 위해서는 main() 메서드가 필요하다.
객체(인스턴스) 설계도를 담당한다. 클래스를 선언하고 인스턴스를 만들면 클래스 형태를 가진 인스턴스가 만들어지는 것이다
클래스의 형태로 생성된 객체이다.
클래스가 설계도라면 인스턴스는 실제 목자재 등의 재료로써 이런 인스턴스들을 쌓아서 전체 프로그램을 구성하게 된다.
인스턴스의 목적은 멤버 변수를 통한 정보 보관 및 멤버 메서드를 통한 행위이다.
인스턴스의 주솟값을 레퍼런스라고 하며, 레퍼런스 값을 저장한 변수를 레퍼런스 변수라고 한다.
class Class_Example{
int num1;
String name;
}
// Class_Example : 클래스. 인스턴스를 만들 때 모든 인스턴스는 num1, name이라는 공간을 가지는 형태로
// 생성되게 된다.
public class Main2 {
public static void main(String[] args) {
Class_Example classes = new Class_Example();
// classes : Class_Example이라는 클래스로 만들어진 Instance.
}
}
대부분의 프로그램 언어는 주석을 지원한다.
코드 자체에 주석을 달기 위해서 JAVA 에서는 //
를 통해 1줄 주석을 달 수 있으며, /* ~~~~ */
를 통해 여러 줄 주석도 가능하다
또한 JavaDoc과 같은 특수한 목적도 존재하는데 JavaDoc은 /** ~~~ */
으로 추가할 수 있다.
class Class{
// 주석을 1줄만 달기 위해선 //를 사용!
/* (원래 여기서부터 주석을 달아도 되지만, 개인적으로 여기는 너무 더러워 보여서 비워놓는 편)
* 주석을 여러줄 달기 위해선 /* 사용!
* 중간 *표시는 엔터를 입력하면 자동으로 추가되며 주석칸이 늘어남
* 중간 *표시가 없어도 주석으로 처리됨
*/
/**
* JavaDoc 달기. JavaDoc은 나중에 따로 정리할 예정
/
}
변수, 상수, 메서드, 클래스 등을 선언할 때 이름을 붙여야 하는데, 이를 식별자라고 하며 규칙이 몇 개 존재한다.
Convention이란 문법적으로 무조건 지켜야 하는 약속은 아니지만, 관례 같은 것을 의미한다.
즉, 굳이 이렇게 코드를 짜지 않아도 되지만 코드를 읽을 때 조금 더 편하게 읽기 위해 이렇게 짜는 것을 추천하는 약속 같은 개념이다
변수는 데이터를 저장하기 위한 메모리 공간에 대한 이름이다.
내가 메모리 100에 1을 저장했다고 가정했을 때, 우리는 메모리 100을 직접 찾지 않고 mem이라는 변수에 메모리 주소를 저장하여 mem을 통해 메모리 100에 저장된 1값을 활용하는 것이다.
(mem : 레퍼런스 변수, 주소값 100 : 레퍼런스)
자바의 경우 원시 자료형, 클래스 타입을 모두 지원하고 있으며 원시 자료형들도 클래스 타입으로 표기할 수 있는데 이를 Wrapper Class라고 한다.
(원시 자료형 : 자바 측에서 미리 제공하는 자료형. 클래스 타입 : "우리가 만들었거나 자바 측에서 만들었던" 클래스 자료형)
자바 원시 자료형 종류는 위 사진과 같다.
여기서 중요한 점은 JAVA에서 많이 활용되는 String은 기본 자료형이 아니라는 것이다.
C언어 등에서 문자열을 활용해봤으면 결국 문자형도 문자의 배열형태라는 것을 알 수 있다.
따라서 String은 "클래스 타입"으로 간주해야 하는 것이다
JAVA에서는 원시 자료형이 아닌 "Class Type" 형태만 입력으로 받는 경우가 존재한다.(대표적인 예시로 Collection이 존재한다)
Primitive 원시 자료형은 클래스가 아닌데, 그러면 이 때문에 새로운 클래스를 하나 선언해야 할까?
정답은 NO이다.
이런 문제를 해결하기 위해 활용하는 것이 Wrapper 클래스이다. Primitive 자료형마다 개별의 Wrappper 클래스가 존재하며 클래스 이름은 아래와 같다.
추가 Auto Boxing과 Auto Unboxing이라는 것이 존재한다.
Auto Boxing이란 Primitive 데이터를 자동으로 Wrapper 클래스로 바꿔주는 과정이고, Auto UnBoxing이란 Wrapper 클래스로 저장되었더라도 연산을 수행할 때는 다른 클래스처럼 주솟값을 활용하는 것이 아닌 Primitive 자료형처럼 실제로 저장되어 있는 Data로 계산을 수행하는 과정을 말한다. 예시를 통해 자세히 알아보자.
// Auto Boxing
Integer num = 10;
// num은 클래스 이므로 원래라면 new Integer(10)으로 선언해줘야 한다.
// 하지만 Integer는 Wrapper 클래스로써 10이라는 Primitive 데이터를 자동으로 클래스로 변환해준다
// Auto Unboxing
num + 1;
// num은 현재 "클래스" 형태를 띄기 때문에 사실은 +1 연산을 수행할 수 없다.
// 하지만 Auto Unboxing을 통해 num을 int형처럼 생각하여 10 + 1 = 11 연산을 자동으로 수행해준다
멤버 변수(Field)
static
으로 선언된 변수. 중요하기 때문에 따로 설명매개 변수
지역 변수
this
예약어를 통해 멤버 변수에 접근 가능하지만, 개인적으로는 생성자 이외의 경우에는 지역 변수와 멤버 변수 이름을 구분짓는 것이 좋은 것 같다클래스 변수
클래스 변수를 생성하는 방법은 간단하다. 인스턴스 변수에
static
예약어만 붙여주면 된다.
그렇다면 이 클래스 변수가 도대체 뭐가 중요하길래 인스턴스 변수와는 다르게 따로 설명까지 추가하였을까?클래스 변수는 모든 인스턴스(객체)가 공통된 값을 공유한다.
따라서 모든 인스턴스들이 공통된 메모리 값을 바라보며, 이 때문에 클래스가 로딩될 때 생성되어 메모리에 딱 한번 올라가고 이후 메모리 값이 바뀌지 않는다.자. 머리가 헷갈릴 수 있다. 예시를 들어보자.
"소나기"라는 책이 팔리고 있다고 가정하자.
"소나기"라는 책은 고유의 특징을 가지고 있다. 책 고유 번호인 ISBN 값도 다를 것이며 생성 시간도 다를 것이다. 또한 바코드도 다를 것이다.
이런 Instance 1개가 생성될 때 마다 온전히 Instance 고유의 값을 가질 수 있는 것이 "인스턴스 변수"이다.
하지만 소나기라는 책의 가격은 동일해야 할 것이다. 또한 책이 팔릴 때마다 팔린 책의 숫자도 1 증가 해야 할 것이다.
이 때 클래스 변수를 사용하는 것이다.
static int selling = 0
및static long price = 2000
이라고 설정되었다고 가정하자.
그리고 소나기 책 1, 2, 3, ..., 100이 존재한다고 가정하자.
1, 2, 3, ..., 100은 모두 똑같은 주솟값의 selling과 price를 가진다. 조금 더 쉽게 이해해보자.
만약 1, 2, 3이 팔렸다. 그렇다면 책 1, 2, 3의 selling 값에 1씩을 더해준다.
이후 책 100에서 selling이라는 주솟값에 접근한다고 가정해보자. 책 1, 2, 3은 결국 "똑같은 주솟값"에 접근하여 1씩을 증가시켰으므로 3이 될 것이고, 해당 주솟값에 책 100이 접근할 것이기 때문에 책 100은 아무 것도 하지 않았지만 selling 값이 3으로 변하게 된 것이다.
또한 책 값을 변경할 때에도 책 100의 price에 접근하여 3000으로 수정하더라도 "똑같은 주솟값"에 접근하여 수정한 것이므로 결국 책 1, 2,... , 99의 price도 3000으로 변경된 값을 가지게 되는 것이다.모든 Instance가 같은 주솟값을 보기 때문에 굳이 여러 번 초기화 할 필요가 없으므로 클래스가 처음으로 로딩될 때 메모리 할당을 해주고 더 이상 메모리를 할당하지 않는 것이다.
이 static이라는 예약어가 매우 특수한 이유는 static 함수 내에는 외부에서 선언된 변수 중 static 변수밖에 사용할 수 없다. 또한 결국 모든 Instance가 공유하는 값이므로 취급에 주의를 기울여야 한다. 따라서, 꼭 static이 필요한지 잘 생각해보고 활용하는 것을 추천한다.
아래 용어는 사실 JAVA에서만 활용되는 것은 아니다. Python이나 C언어에서도 매우 중요시 하는 개념들이다.
단지 JAVA에서 처음 다루기 때문에 JAVA Section에서 다룰 뿐이므로 JAVA를 사용하지 않을 것이더라도 꼭 이해야하는 개념들이다
얕은 복사는 복사본에 변경이 일어날 경우 원본에도 변경이 일어나는 복사이다(반대로 원본이 변경되더라도 복사본이 변경된다)
깊은 복사는 반대로 원본으로부터 Item 값만 복사해 오는 복사로써 원본의 변형이나 복사본에 영향을 끼치지 못하고 반대의 경우도 그러하다.
왜 이런 상황이 벌어질까? 이는 복사의 방법 차이이다.
얕은 복사는 한 마디로 말하자면 "주솟값의 공유"이다. 사실은 실제로 복사가 되는게 아니라는 의미이다.
JAVA가 Instance에 접근하는 방법은 간단하다. 레퍼런스 변수에 저장되어 있는 메모리 주소를 찾아가 해당 메모리에 저장되어 있는 값을 꺼내오는 것이 Instance에 접근하는 방식이다.
그렇다면, 2개의 레퍼런스 변수가 똑같은 메모리 주소를 가지면 어떻게 될까? 마치 외부에서 볼 때는 2개의 변수가 복사되었다(일치한다) 라고도 착각할 수 있지 않을까? 이것이 얕은 복사의 개념인 것이다.
원본 파일의 주솟값이 100이라고 했을 때 얕은 복사는 복사본이 주솟값 100을 가리키도록 만들어준다. 그렇다면 우리는 복사본이 원본과 동일한 것처럼 보이기 때문에 복사가 되었다고 착각하는 것이다.
이제는 왜 원본과 복사본이 서로 영향을 끼치는지도 알 수 있을 것이다. 결국 원본이든 복사본이든 똑같은 주솟값을 가리키고 있기 때문에 원본을 복사본을 변경하든 똑같은 객체를 변경하는 것이라고 볼 수 있는 것이다.
깊은 복사는 정말로 우리가 생각하는 복사이다.
원본과 다른 공간(메모리 주소)에 원본 크기의 공간을 마련한 뒤 원본의 모든 값을 복사 + 붙여넣기 하여 원본을 복사하는 방식이다.
깊은 복사는 다른 메모리 주소에 새로운 Instance를 만들어 값을 복사 후 붙여넣은 것이기 때문에 원본과 복사본은 다른 주솟값을 가지며 당연히 이후 서로 영향을 끼치지 않은 독립적인 2개의 Instance가 되는 것이다
프로그램에서 값을 저장하기 위해 필요한 만큼 메모리에 공간을 확보하는 것을 메모리 할당이라고 한다. 할당을 해 주는 방법에는 동적 할당과 정적 할당이 있는데, 과연 어떤 차이가 존재할까?
자바 프로그램 구조는 클래스, 인스턴스, 주석과 변수로 크게 구성된다.
클래스는 설계도의 역할을 하며 인스턴스는 클래스를 가지고 와서 실제로 내가 사용할 물건을 만든 것이라고 생각하면 된다.
변수란 데이터를 저장하기 위한 메모리 공간을 가리키는 이름을 의미하는데 JAVA에서는 원시 자료형, 클래스 타입을 모두 지원하고 있다.
원시 자료형은 byte, char, short, int, long, float, double, boolean형이 존재하며 각각의 원시 자료형마다 Wrapper Class라는 것이 존재하여 클래스 타입으로도 활용할 수 있다
변수 유형은 클래스 내부에 선언된 멤버 변수(Field), 메서드 인자로 전달되는 매개 변수, 메서드 내에서 선언된 지역 변수와 static 예약어를 활용하여 모든 Instance에서 공유하는 클래스 변수가 존재한다.
복사 방식에는 주솟값을 공유하는 얕은 복사와 다른 주솟값에 모든 값을 복사하여 새로운 객체를 만드는 깊은 복사가 존재한다.
메모리에 공간을 확보하는 것을 메모리 할당이라고 하는데, 방식에는 Compile 시에 할당 여부가 결정되는 정적 할당과 Runtime 시 할당 여부가 결정되는 동적 할당 방식이 존재한다.