현실 세계를 객체로 만들어 객체 간의 상호작용을 활용하고 프로그래밍 하는 것
객체를 정의한 것 = 객체의 설계도
현실 세계에서 TV를 만들기 위한 TV 설계도가 존재한다. 이처럼, 프로그래밍에서 객체를 만들기 위해서 객체의 설계도인 클래스가 필요하다.
실제로 존재하는 사물 또는 개념 = 속성 + 기능
현실 세계에서 TV 설계도를 따라 TV를 만드는 것처럼, 프로그래밍에서는 클래스로부터 객체를 생성하여 사용할 수 있다. 객체는 속성과 기능을 가진다.
- 속성 (property) : 멤버변수, 필드, 상태, ...
- 기능 (function) : 메서드, 함수, ...
TV가 하나의 객체라고 생각했을 때, TV의 속성은 제품명, 전원, 채널
등이 있고, 기능으로는 전원 켜기/끄기, 채널 올리기/내리기, 볼륨 올리기/내리기
등이 있다. 이를 프로그래밍으로 표현하면 다음과 같다.
class TV { // 설계도 = 클래스
// 속성 = 멤버변수
String name; // 제품명
boolean power; // 전원
int channel; // 채널
// 기능 = 메서드
void power() { // 전원 켜기/끄기
power = !power;
}
void channelUp(int channel) { // 채널 올리기
channel++;
}
void channelDown(int channel) { // 채널 내리기
channel--;
}
}
클래스로부터 만들어진 객체
인스턴스와 객체는 같은 의미이지만, 객체가 인스턴스를 포함하고 있다고 생각하면 된다.
클래스를 선언했다면 인스턴스를 생성하여 TV 객체를 사용해야 한다. 인스턴스 생성 방법은 다음과 같다.
클래스명 변수명; // 참조변수 선언
변수명 = new 클래스명(); // 참조변수에 객체 주소값 대입
클래스명 변수명 = new 클래스명(); // 한 줄로 작성 가능
먼저, 클래스 타입의 참조변수를 선언하면 메모리에 참조변수를 위한 공간이 마련된다. 이 공간에 new 연산자를 통해 생성된 인스턴스의 주소값을 대입하면 참조변수가 인스턴스를 참조하게 된다. 이 때, 참조변수의 타입은 인스턴스의 타입과 일치해야 한다.
참조변수를 통해 객체 안의 멤버변수와 메서드에 접근 가능하다.
public static void main(String[] args) {
TV tv1 = new TV(); // 인스턴스 생성
// 멤버변수에 접근
tv1.name = "삼성TV";
tv1.channel = 3;
// 메서드에 접근
tv1.channelDown(tv1.channel);
System.out.println("제품명 : " + tv1.name + ", 채널 : " + tv1.channel);
}
TV 객체를 생성하고, 멤버변수에 접근하여 제품명을 삼성TV
, 채널을 3
으로 설정한 후 메서드를 호출하여 채널을 한 칸 내렸기 때문에 최종 채널은 2
이다.
public static void main(String[] args) {
TV tv1 = new TV();
tv1.name = "삼성TV";
tv1.channel = 3;
tv1.channelDown();
System.out.println("제품명 : " + tv1.name + ", 채널 : " + tv1.channel);
TV tv2 = new TV();
tv2.name = "LGTV";
tv2.channel = 10;
tv2.channelUp();
System.out.println("제품명 : " + tv2.name + ", 채널 : " + tv2.channel);
}
new 연산자를 통해 새로운 인스턴스가 생성되면 기존의 인스턴스와 별개의 주소값을 가지므로 tv1
과 tv2
는 각자 다른 인스턴스를 참조하고 있는 별개의 객체다.
- 인스턴스 변수 : 클래스 영역에 선언 ➡️ 인스턴스 생성 시 만들어짐
- 클래스 변수 (
static 인스턴스 변수
) : 한 클래스의 모든 인스턴스가 공통적인 값을 가짐 ➡️ 클래스가 메모리에 로딩될 때 생성
- 지역 변수 : 메서드 내에서 선언 ➡️ 블럭
{}
밖에서 사용 불가능
class Variables {
int iv; // 인스턴스 변수
static int cv; // 클래스 변수
void method() {
int lv = 0; // 지역변수
}
}
인스턴스를 여러 개 생성했을 때, 인스턴스 변수는 각 인스턴스 별로 값을 설정할 수 있고, 클래스 변수는 모든 인스턴스가 공통된 값을 가진다.
public class Test {
public static void main(String[] args) {
Card card1 = new Card();
card1.pattern = "♥";
card1.num = 3;
Card card2 = new Card();
card2.pattern = "★";
card2.num = 7;
card2.width = 150;
System.out.println("카드 1 : " + card1.pattern + card1.num + " : " + card1.width + " x " + card1.height);
System.out.println("카드 2 : " + card2.pattern + card2.num + " : " + card2.width + " x " + card2.height);
}
}
class Card {
// 인스턴스 변수
String pattern; // 무늬
int num; // 숫자
// 클래스 변수
static int width = 100; // 폭
static int height = 250; // 높이
}
클래스 변수인 width
가 100으로 초기화 된 상태에서 card2
의 width
만 150으로 수정했음에도 불구하고 card1
의 width
또한 100이 아닌 150으로 수정되었다.
특정 작업을 수행하는 문장들을 하나로 묶은 것 = 함수
높은 재사용성 : 한 번 만들어놓은 메서드는 다른 프로그램에서 호출하여 사용 가능
중복된 코드 제거 : 중복된 코드를 하나의 메서드로 만들어서 필요할 때마다 한 줄로 호출하여 사용 가능
반환타입(없으면 void) 메서드명(타입 변수명, ...) {
// 실행할 문장 1;
// 실행할 문장 2;
...
return 반환값; // 반환 타입과 반환값의 타입이 일치해야함
}
int sum(int a, int b) {
return a + b;
}
return
: 현재 실행중인 메서드를 종료하고 호출한 메서드로 되돌아감메서드이름(값1, ...)
int result = add(3, 5); // 반환값 저장
- 메서드 영역 (method area) : 사용되고 있는 클래스의 정보를 저장하는 영역
- 힙 (heap) : 인스턴스가 생성되는 공간
- 호출 스택 (call stack) : 메서드 작업에 필요한 메모리 공간 제공 ➡️ 메서드가 종료되면 비워짐
public class Test {
public static void main(String[] args) {
System.out.println("mian() 시작!");
firstMethod();
System.out.println("main() 종료!");
}
static void firstMethod() {
System.out.println("firstMethod() 시작!");
secondMethod();
System.out.println("firstMethod() 종료!");
}
static void secondMethod() {
System.out.println("secondMethod() 시작!");
System.out.println("secondMethod() 종료!");
}
}
main()
이 가장 먼저 호출되어 스택에 쌓이고, 실행 도중 firstMethod()
를 호출하여 실행하는 도중에 또 secondMethod()
를 호출하여 스택에 쌓이게 된다. 스택의 가장 위에 위치한 secondMethod()
를 먼저 실행하고, 종료되면 그 다음인 firstMethod()
를 실행하고 종료한다. 마지막으로 스택의 가장 밑에 있던 main()
을 실행하고 종료하면 호출 스택이 비게 된면서 프로그램이 종료된다.
- 기본형 : 변수의 값을 읽기만 할 수 있음
기본형 매개변수를 사용하게 되면 스택에서 변수를 복사해 간 후 로직에 따라 값을 처리하고, 메서드가 종료되면 메서드와 매개변수가 함께 스택에서 제거된다. 즉, 복사된 매개변수 값은 실제 변수에 아무런 영향을 미치지 않는다.
public class Test {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
System.out.println("Before change(x) x = " + d.x);
change(d.x);
System.out.println("After change(x) x = " + d.x);
}
static void change(int x) {
x = 100;
System.out.println("change(x) x = " + x);
}
}
class Data {
int x;
}
- 참조형 : 변수의 값을 읽고 변경할 수 있다.
참조형 매개변수를 사용하는 경우, 스택에서 참조변수에 저장된 주소값을 복사해 간 후, 해당 주소값을 가진 인스턴스의 멤버변수에 직접 접근하여 로직을 처리한다. 이 때, 실행이 종료되어 메서드가 스택에서 제거되어도 실제 주소값을 사용하여 값을 처리하였으므로 변경된 값이 유지된다.
public class Test {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
System.out.println("Before change(x) x = " + d.x);
change(d);
System.out.println("After change(x) x = " + d.x);
}
static void change(Data d) {
d.x = 100;
System.out.println("change(x) x = " + d.x);
}
}
class Data {
int x;
}
메서드 내부에서 메서드 자신을 다시 호출하는 것
void method() { // 재귀 메서드
method(); // 자기 자신을 호출
}
위 메서드의 경우 자기 자신을 무한으로 호출한다. 이러한 경우를 방지하기 위해 재귀 호출을 사용할 때는 조건문과 return
문을 적절히 사용하여 조건을 만족하면 메서드를 종료시켜줘야 한다.
static int factorial(int n){
if(n == 1) return 1; // n이 1이면 1을 반환하며 메서드 종료
return n * factorial(n-1); // 재귀 호출
}
변수와 같이 클래스 안에 생성된 메서드를 인스턴스 메서드, 인스턴스 메서드 앞에
static
을 붙인 메서드를 클래스 메서드라고 한다.
클래스 내의 메서드가 인스턴스 변수를 사용한다면 인스턴스 변수와 함께 객체 생성 후에 호출이 가능하다. 하지만, 메서드가 인스턴스 변수를 사용하지 않는다면 static
을 붙일 수 있다. 클래스 메서드의 경우, 메모리 로딩 시점에 생성되기 때문에 객체 생성 후에 생성되는 인스턴스 변수는 사용할 수 없다.
public class Test {
public static void main(String[] args) {
MyMath m = new MyMath();
m.a = 5;
m.b = 10;
System.out.println(m.add1());
System.out.println(MyMath.add2(5, 10));;
}
}
class MyMath {
int a, b; // 인스턴스 변수
// 인스턴스 메서드
int add1() {
return a + b;
}
// 클래스 메서드
static int add2(int a, int b) {
return a - b;
}
}
한 클래스 내에 같은 이름의 메서드를 여러개 정의하는 것
int add(int a, int b) { return a + b; }
// 매개변수 개수
int add(int a, int b, int c) { return a + b + c; }
// 매개변수 타입
long add(long a, long b) { return a + b; }
타입... 변수명
: 매개변수의 개수를 가변적으로 설정 가능
매개변수가 여러 개인 경우, 가변인자를 가장 마지막에 써야 한다! 또한, 가변인자를 사용하면 매개변수의 개수를 정확히 알 수 없기 때문에 오버로딩 시 에러가 발생한다.
public class Test {
public static void main(String[] args) {
System.out.println(concat());
System.out.println(concat("a", " ", "b"));
}
static String concat(String... str) {
String result = "";
for(String s : str) {
result += s;
}
return result;
}
}
인스턴스 초기화 메서드 : 인스턴스가 생성될 때 호출됨
클래스이름(타입 변수명, ...) {
// 인스턴스 변수 초기화
}
생성자가 하나도 정의되지 않은 경우 컴파일러가 자동으로 추가하여 실행되는 생성자
클래스이름() {}
인스턴스 생성 시, 매개변수 값을 넘겨받아서 인스턴스의 멤버변수 초기화 가능
public class Test {
public static void main(String[] args) {
Car car1 = new Car("벤츠", "black");
// Car car2 = new Car(); // 에러 발생
}
}
class Car {
String name;
String color;
public Car(String name, String color) {
this.name = name;
this.color = color;
}
}
클래스 내에 매개변수 생성자가 존재하는 경우에는 기본 생성자를 자동으로 추가해주지 않는다. 필요하면 명시적으로 생성해야 한다!
➕
this
: 인스턴스 자신을 가리키는 참조변수
변수를 선언하고 처음으로 값을 저장하는 것
변수 선언과 동시에 초기화 하는 것
class Car {
String name = "벤츠";
String color = "black";
...
}
출처 : Java의 정석