1. 객체지향언어
- 객체지향이론은 실제 사물의 속성과 기능을 분석하고, 이를 데이터와 함수로 정의하여 가상세계를 구현하는 것이다.
- 코드 간에 서로 관계를 맺어줌으로써 보다 유기적인 프로그램 구성이 가능해졌다.
- 주요 특징은 코드의 재사용성이 높고, 관리가 용이하며, 신뢰성이 높다는 것이다.
2. 클래스와 객체
- 클래스
- 객체를 정의한 것
- 제품의 설계도
- 객체
- 클래스에 정의된 내용대로 메모리에 생성된 것
- 설계도로 제작한 제품
- 속성과 기능으로 이루어져 있으며, 이들을 멤버라고 한다.
class Tv {
String color;
boolean power;
int channel;
void power() { power = !power; }
void channelUp() { channel++; }
void channelDown() { channel--; }
}
class Test {
public static void main(String[] args) {
Tv t;
t = new Tv();
t.channel = 7;
t.channelDown();
}
}
- Tv클래스 타입의 참조변수 t 선언시 메모리에 t를 위한 공간이 마련된다.
- 연산자 `new`로 의해 Tv클래스의 인스턴스가 메모리의 빈 공간에 생성된다. 이때 멤버변수는 각 자료형에 해당하는 기본값으로 초기화된다.
- 대입연산자에 의해 객체의 주소값이 t에 저장된다.
class Tv {
String color;
boolean power;
int channel;
void power() { power = !power; }
void channelUp() { channel++; }
void channelDown() { channel--; }
}
class Test2 {
public static void main(String[] args) {
Tv t1 = new Tv();
Tv t2 = new Tv();
t1.channel = 7;
System.out.println(t1.channel); // 7
System.out.println(t2.channel); // 0
}
}
- 같은 클래스에서 생성되었더라도 각 인스턴스의 속성은 서로 다른 값을 유지할 수 있다.
class Tv {
String color;
boolean power;
int channel;
void power() { power = !power; }
void channelUp() { channel++; }
void channelDown() { channel--; }
}
class Test2 {
public static void main(String[] args) {
Tv t1 = new Tv();
Tv t2 = new Tv();
t2 = t1;
t1.channel = 7;
System.out.println(t1.channel); // 7
System.out.println(t2.channel); // 7
}
}
- t1이 저장하고 있는 객체의 주소를 t2에 저장했으므로, t2도 t1과 같은 객체를 참조하게 된다.
- t2가 원래 참조하던 객체는 가비지 컬렉터에 의해 메모리에서 제거된다.
- 여러개의 참조변수가 하나의 객체를 가리키는 것은 가능하지만, 하나의 참조변수가 여러개의 객체를 가리키는 것은 불가능하다.
- 하나의 주소만 저장 가능
2-1. 객체 배열
- 객체를 배열로 다루는 것
- 객체의 주소 저장
- 결국 참조변수들을 하나로 묶은 참조변수 배열인 것
3. 변수와 메서드
3-1. 변수의 종류
- 변수의 선언된 위치에 따라 변수의 종류를 구분한다.
- 멤버 변수
- 클래스 변수
- 클래스 영역에 선언된다.
- 클래스가 메모리에 올라갈 때 생성된다.
- static이 붙는다.
- 모든 인스턴스가 공통된 저장공간을 공유한다. 즉, 한 클래스의 모든 인스턴스들이 공유해야 하는 속성이라면 클래스 변수로 선언한다.
- public을 붙이면 같은 프로그램 어디서나 접근할 수 있는 전역변수가 된다.
- 인스턴스 변수
- 클래스 영역에 선언된다.
- 인스턴스가 생성되었을 때 생성된다.
- 사용하기 전에 인스턴스를 생성해야 한다.
- 독립적인 저장 공간을 가진다. 즉, 인스턴스마다 고유한 상태를 유지해야하는 속성이라면 인스턴스변수로 선언한다.
- 클래스 변수
- 지역 변수
- 클래스 영역 외(메서드, 생성자, 초기화 블럭 내부)에서 선언된다.
- 변수 선언문이 수행되었을 때 생성된다.
- 메서드 내에 선언되었다면, 메서드가 종료된 후에는 소멸되어 사용할 수 없다.
- for문 또는 while문 블럭 내에 선언되었다면 블럭 내에서만 사용할 수 있고, 블럭을 벗어나면 소멸되어 사용할 수 없다.
3-2. 메서드
- 특정 작업을 수행하는 문장들을 하나로 묶은 것
- 입력값 또는 출력값이 없을 수도 있으며, 모두 없을 수도 있다.
- 메서드를 사용함으로써 높은 재사용성, 중복 코드 제거, 프로그램 구조화와 같은 이점을 얻을 수 있다.
- 크게 선언부와 구현부로 이루어져 있다.
반환타입 메서드이름(타입 변수명, 타입 변수명, ...) { // 선언부
// 구현부
// 메서드 호출시 수행될 문장들
}
- 매개변수(parameter)
- 메서드가 작업 수행시 필요한 값들을 입력받기 위한 것이다.
- 매개변수도 지역변수이다.
- return문
- 메서드의 반환타입이 `void`가 아닌 경우, 구현부 안에 `return 반환값;`이 반드시 포함되어야 한다.
- 메서드의 반환타입이 `void`라면 컴파일러가 메서드의 마지막에 `return;`을 자동으로 추가해준다.
- 반환값의 타입은 반환타입과 일치하거나 자동 형변환이 가능해야 한다.
- 단 하나의 값만 반환할 수 있다.
- 메서드의 반환타입이 `void`가 아닌 경우, 구현부 안에 `return 반환값;`이 반드시 포함되어야 한다.
3-3. 메서드의 호출
- `메서드이름(값1, 값2, ...);`
- 인자(argument)
- 메서드 호출시 괄호 안에 지정한 값들을 인자(argument) 또는 인수라고 한다.
- 인자의 개수화 순서는 호출된 메서드에 선언된 매개변수와 일치해야 한다.
- 인자는 메서드가 호출되면서 매개변수에 대입되므로 인자의 타입은 매개변수의 타입과 일치하거나 자동 형변환이 가능해야 한다.
- static 메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없다.
class MyMath {
long add(long a, long b) { return a+b; }
}
class Test {
MyMath mm = new MyMath();
long value = mm.add(1L, 2L);
}
- MyMath 클래스의 인스턴스 메서드를 호출하려면 먼저 인스턴스를 생성하고, 참조변수를 이용해야 한다.
- 메서드가 호출되면 실행 중이던 메서드가 잠시 중단되고 호출된 메서드의 문장들이 수행된다.
3-4. JVM의 메모리 구조
- 프로그램이 실행되면 JVM은 시스템에게 프로그램 수행에 필요한 메모리를 할당받고, 용도에 따라 여러 영역으로 나누어 관리한다.
- 메서드 영역
- 프로그램 실행 중 어떤 클래스가 사용되면, JVM이 해당 클래스의 클래스 파일을 읽고 분석하여 클래스 데이터를 해당 영역에 저장한다.
- 클래스 변수도 함께 생성된다.
- 호출스택
- 메서드의 작업에 필요한 메모리 공간
- 메서드가 호출되면 호출스택에 메서드를 위한 메모리가 할당된다.
- 메서드가 작업을 수행하는 동안 지역변수들과 연산의 중간결과 등을 저장하는데 사용된다.
- 메서드가 작업을 마치면 할당되었던 메모리 공간이 반환되어 비어진다.
- 메서드 간의 호출 관계와 현재 수행중인 메서드를 파악할 수 있다.
- 메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당받는다.
- 메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다.
- 호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드이다.
- 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드이다.
- 힙
- 인스턴스가 생성되는 공간
- 인스턴스 변수도 함께 생성된다.
3-5. 기본형 매개변수와 참조형 매개변수
- 매개변수의 타입이 기본형이면 값이 복사되고, 참조형이면 주소가 복사된다.
- 주소가 복사된다는 것은 변수의 값을 변경할 수도 있다는 의미이다.
class Data { int x; }
class Ex {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
System.out.println("main() x : " + d.x); // 10
change(d.x);
System.out.println("After change x : " + d.x); // 10
}
static void change(int x) {
x = 1000;
System.out.println("change() x : " + x); // 1000
}
}
- 매개변수 x의 값이 변경된 것이기 때문에 원본에는 영향이 없다.
class Data { int x; }
class Ex {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
System.out.println("main() x : " + d.x); // 10
change(d);
System.out.println("After change x : " + d.x); // 1000
}
static void change(Data d) {
d.x = 1000;
System.out.println("change() x : " + d.x); // 1000
}
}
- 매개변수에 값이 저장된 주소가 전달된 것이므로 해당 주소에 저장된 객체의 필드 값을 변경할 수 있다.
class Data { int x; }
class Ex {
public static void main(String[] args) {
int[] x = {10};
System.out.println("main() x : " + x[0]); // 10
change(x);
System.out.println("After change x : " + x[0]); // 1000
}
static void change(int[] x) {
x[0] = 1000;
System.out.println("change() x : " + x[0]); // 1000
}
}
- x에 배열의 주소가 저장되어 있기 때문에 위 예제와 동일하게 원본 값을 변경할 수 있다.
int add(int a, int b) {
return a+b;
}
void add(int a, int b, int[] result) {
result[0] = a+b;
}
- 배열을 활용하면 이처럼 반환값이 있는 메서드를 반환값이 없는 메서드로 바꿀 수 있다.
class Data { int x; }
class Ex {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
Data d2 = copy(d);
System.out.println(d.x); // 10
System.out.println(d2.x); // 10
}
static Data copy(Data d) {
Data tmp = new Data();
tmp.x = d.x;
return tmp;
}
}
- 참조형도 메서드의 매개변수가 될 수 있으며, 모든 참조형 타입의 값은 결국 객체의 주소이므로 정수값이 반환된다.
- 한 가지 주의할 점은 copy() 메서드 내에서 생성한 객체를 main() 메서드에서 사용하려면 생성한 객체의 주소를 반환해야 한다는 것이다. copy() 메서드가 종료되면 새로운 객체의 참조가 사라져서 더이상 그 객체를 사용할 방법이 없기 때문이다.
3-6. 재귀호출
- 메서드 내부에서 메서드 자신을 다시 호출하는 것
- 재귀호출하는 메서드를 재귀 메서드라고 한다.
- 무한루프에 빠지지 않도록 조건문을 작성해야 한다.
- 재귀호출은 비효율적이지만 간결하고 알아보기 쉽다.
class Ex {
public static void main(String[] args) {
int result = factorial(4);
System.out.println(result);
}
static int factorial(int n) {
int result = 0;
if(n == 1) {
result = 1;
} else {
result = n * factorial(n - 1);
}
return result;
}
}
3-7. 클래스 메서드와 인스턴스 메서드
- 클래스 메서드
- 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드
- 메서드 앞에 static이 붙어있다.
- 인스턴스 메서드
- 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드
- 인스턴스를 생성해야만 호출 가능
- 클래스를 설계할 때 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
- 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있다.
- 클래스 메서드는 인스턴스 변수를 사용할 수 없다.
- 메서드 내에서 인스턴스 변수를 사용하지 않는다면 static을 붙이는 것을 고려한다.
3-8. 클래스 멤버와 인스턴스 멤버간의 참조와 호출
- 클래스 멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.
- 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수 있기 때문이다.
- 인스턴스 멤버는 언제나 인스턴스 멤버를 호출할 수 있다.
- 하나의 인스턴스 멤버가 존재한다는 것은 인스턴스가 이미 생성되어 있다는 것을 의미하기 때문이다.
4. 오버로딩
- 한 클래스 내에 같은 이름의 메서드를 여러개 정의할 수 있다.
- 메서드의 이름이 같고, 매개변수의 개수 또는 타입이 달라야 한다.
- 반환 타입은 오버로딩에 아무 상관없다.
- 가장 대표적인 예시로 println() 메서드를 들 수 있다.
class Ex {
int add(int a, int b) { return a+b; }
long add(long a, int b) { return a+b; }
}
4-1. 가변인자와 오버로딩
- 가변인자를 사용하여 매개변수의 개수를 동적으로 지정할 수 있다.
- 가변인자 외에 매개변수가 더 있다면, 가변인자를 매개변수 중 가장 마지막에 선언해야 한다.
- `타입... 변수명`과 같은 형식으로 선언한다.
class Ex {
public static void main(String[] args) {
String result = concatenate();
String result1 = concatenate(new String[0]);
String result2 = concatenate(null);
String result3 = concatenate("a", "b", "c");
}
static String concatenate(String... str) {
return "";
}
}
- 인자의 개수를 가변적으로 할 수 있다.
- 배열도 인자가 될 수 있다.
- 인자가 아예 없어도 된다.
class Ex {
public static void main(String[] args) {
// String result = concatenate(); 에러
String result1 = concatenate(new String[0]);
String result2 = concatenate(null);
// String result3 = concatenate("a", "b", "c"); 에러
}
static String concatenate(String[] str) {
return "";
}
}
- 매개변수의 타입을 배열로 지정할 경우 반드시 인자를 지정해야 한다.
class Ex {
static String concatenate(String... str) {
return "";
}
static String concatenate(String str, String... args) {
return "";
}
public static void main(String[] args) {
concatenate("");
}
}
- 위와 같이 가변인자를 사용한 메서드를 오버라이딩할 경우 메서드를 구별하지 못해 에러가 발생한다.
5. 생성자
- 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드
- 인스턴스 변수의 초기화에 주로 사용된다.
- 클래스의 이름과 같아야 하고, 리턴 값이 없다.
- 오버라이딩이 가능하다.
Card c = new Card();
- 연산자 new에 의해 메모리(heap)에 Card 클래스의 인스턴스가 생성된다.
- 생성자 Card()가 호출되어 수행된다.
- 연산자 new의 결과로 생성된 Card 인스턴스의 주소가 반환되어 참조변수 c에 저장된다.
5-1. 기본 생성자
- 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.
- 컴파일시 클래스에 생성자가 하나도 정의되지 않은 경우, 컴파일러가 자동으로 기본 생성자를 추가한다.
- 컴파일러가 자동으로 추가하는 기본 생성자는 매개변수도 없고, 아무 내용도 없다.
Card() {}
class Ex {
public static void main(String[] args) {
Data1 d1 = new Data1();
Data2 d2 = new Data2(); // 에러 발생
}
}
class Data1 {
int value;
}
class Data2 {
int value;
Data2(int x) {
value = x;
}
}
- Data2 클래스에는 이미 생성자가 있기 때문에 컴파일러가 기본 생성자를 자동으로 추가하지 않는다.
- 따라서 `Data2 d2 = new Data2(10);`과 같이 객체를 생성하거나, Data2 클래스에 기본 생성자를 추가해야 한다.
5-2. 매개변수가 있는 생성자
- 인스턴스 생성시, 기본 생성자를 사용한다면 인스턴스를 생성한 다음 인스턴스 변수들을 따로 초기화해야 하지만 매개변수가 있는 생성자를 사용한다면 인스턴스를 생성하는 동시에 원하는 값으로 초기화할 수 있다.
class Ex {
public static void main(String[] args) {
Car c = new Car("black", "auto", 4);
}
}
class Car {
String color;
String gearType;
int door;
Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
5-3. 생성자에서 다른 생성자 호출하기
- 생성자 간에도 서로 호출이 가능하다.
- 생성자의 이름으로 클래스 이름 대신 `this`를 사용해야 하고, 반드시 첫 줄에서만 호출해야 한다.
class Ex {
public static void main(String[] args) {
Car c = new Car();
}
}
class Car {
String color;
String gearType;
int door;
Car() {
this("white", "auto", 4);
}
Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
- `this`는 참조변수로 인스턴스 자신을 가리킨다.
- this를 사용할 수 있는 것은 인스턴스 멤버뿐이다.
- 생성자를 포함한 모든 인스턴스 메서드에는 자신이 관련된 인스턴스를 가리키는 참조변수 this가 지역변수로 숨겨진 채 존재한다.
5-4. 생성자를 이용한 인스턴스의 복사
- 어떤 인스턴스의 상태를 알지 못해도 똑같은 상태의 인스턴스를 추가로 생성할 수 있다.
class Ex {
public static void main(String[] args) {
Car c1 = new Car();
Car c2 = new Car(c1); // c1의 복사본
}
}
class Car {
String color;
String gearType;
int door;
Car() {
this("white", "auto", 4);
}
Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
// 인스턴스의 복사를 위한 생성자
Car(Car c) {
this.color = c.color;
this.gearType = c.gearType;
this.door = c.door;
}
}
- 인스턴스 c2는 c1을 복사하여 생성된 것이므로 서로 같은 상태를 갖지만, 서로 독립적으로 메모리 공간에 존재하는 별도의 인스턴스이므로 c1의 값들이 변경되어도 c2는 영향을 받지 않는다.
6. 변수의 초기화
- 변수를 선언하고 처음으로 값을 저장하는 것을 변수의 초기화라고 한다.
- 멤버변수는 초기화를 하지 않아도 자동으로 변수의 자료형에 맞는 기본값으로 초기화된다.
- 지역변수는 사용하기 전에 반드시 초기화해야 한다.
6-1. 멤버변수 초기화
- 명시적 초기화
- 변수를 선언과 동시에 초기화하는 것
- `int door = 4;`
- 생성자
- 초기화 블럭
- 인스턴스 초기화 블럭
- 클래스 내에 블럭`{}`을 만들고 그 안에 코드를 작성한다.
- 인스턴스를 생성할 때마다 수행된다.
- 생성자보다 인스턴스 초기화 블럭이 먼저 수행된다.
- 클래스의 모든 생성자에 공통으로 수행되어야 하는 문장이 있을 때, 이 문장들을 인스턴스 블럭에 넣으면 코드가 보다 간결해진다.
- 클래스 초기화 블럭
- 인스턴스 초기화 블럭 앞에 static을 붙인다.
- 클래스가 메모리에 처음 로딩될 때 한번만 수행된다.
- 인스턴스 초기화 블럭
class Ex {
public static void main(String[] args) {
Car c1 = new Car();
Car c2 = new Car(c1); // c1의 복사본
}
}
class Car {
String color;
String gearType;
int door;
// 클래스 초기화 블럭
static {
System.out.println("static {}");
}
// 인스턴스 초기화 블럭
{
System.out.println("{}");
}
Car() {
this("white", "auto", 4);
}
Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
// 인스턴스의 복사를 위한 생성자
Car(Car c) {
this.color = c.color;
this.gearType = c.gearType;
this.door = c.door;
}
}
6-2. 멤버변수의 초기화 순서
- 클래스 변수의 초기화 순서
- 기본값 -> 명시적 초기화 -> 클래스 초기화 블럭
- 인스턴스 변수의 초기화 순서
- 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자
'Java' 카테고리의 다른 글
Java :: 객체지향 II (0) | 2024.07.07 |
---|---|
Java :: 배열 (0) | 2024.06.24 |
Java :: 제어문 (0) | 2024.06.24 |
Java :: 연산자 (0) | 2024.06.23 |
Java :: 변수와 형변환에 대하여 (0) | 2024.06.22 |