실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물간의 상호작용이다. 라는 것이다.객체지향언어는 기존의 프로그래밍언어와 다른 전혀 새로운 것이 아니라, 기존의 프로그래밍 언어에 몇 가지 새로운 규칙을 추가한 보다 발전된 형태의 것이다.
객체지향언어의 주요특징은 다음과 같다.
1. 코드의 재사용성이 높다.
새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.
2. 코드의 관리가 용이하다.
코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다.
3. 신뢰성이 높은 프로그래밍을 가능하게 한다.
제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 하며, 코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다.
-> 앞으로 상속, 다형성과 같은 객체지향개념을 학습할 때 재사용성 과 유지보수 그리고 중복된 코드의 제거 , 이 세 가지 관점에서 보면 보다 쉽게 이해할 수 있을 것이다.
클래스의 정의 : 클래스란 객체를 정의해 놓은 것이다.
클래스의 용도 : 클래스는 객체를 생성하는데 사용된다.객체의 정의 : 실제로 존재하는 것, 사물 또는 개념
객체의 용도 : 객체가 가지고 있는 기능과 속성에 따라 다름유형의 객체 : 책상, 의자, 자동차, TV와 같은 사물
무형의 객체 : 수학공식, 프로그램 에러와 같은 논리나 개념
클래스의 인스턴스화(instantiate) 라고 하며,인스턴스(instance) 라고 한다.속성(property) - 멤버변수(member variable), 특성(attribute), 필드(field), 상태(state)
기능(function) - 메서드(method), 함수(function), 행위(behavior)
class Tv{
String color; // 색깔
boolean power; // 전원상태
int channel; // 채널
void power() { power = !power; }
void channelUp() { channel++; }
void channelDown() { channel--; }
}
예를 들어, 위 코드에서 위 변수 부분이 속성 , 아래 메서드 부분이 기능 이다.
클래스명 변수명; // 클래스의 객체를 참조하기 위한 참조변수를 선언
변수명 = new 클래스명(); // 클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장
Tv t; // Tv클래스 타입의 참조변수 t를 선언
t = new Tv(); // Tv인스턴스를 생선한 후, 생성된 Tv인스턴스의 주소를 t에 저장
class Tv{
// Tv의 속성(멤버변수)
String color; // 색상
boolean power; // 전원상태(on/off)
int channel; // 채널
// Tv의 기능(메서드)
void power() { power = !power; } // TV를 켜거나 끄는 기능을 하는 메서드
void channelUp() { channel++; } // TV의 채널을 높이는 기능을 하는 메서드
void channelDown() { channel--; } // TV의 채널을 낮추는 기능을 하는 메서드
}
public class TvTest {
public static void main(String[] args) {
Tv t; // Tv인스턴스를 참조하기 위한 변수 t를 선언
t = new Tv(); // Tv인스턴스를 생성한다.
t.channel = 7; // Tv인스턴스의 멤버변수 channel의 값을 7로 한다.
t.channelDown(); // Tv인스턴스의 메서드 channelDown()을 호출한다.
System.out.println("현재 채널은 "+t.channel+" 입니다.");
}
}
class TvTest3 {
public static void main(String args[]) {
Tv t1 = new Tv();
Tv t2 = new Tv();
System.out.println("t1의 channel값은 " + t1.channel + "입니다.");
System.out.println("t2의 channel값은 " + t2.channel + "입니다.");
t2 = t1; // t1이 저장하고 있는 값(주소)을 t2에 저장한다.
t1.channel = 7; // channel 값을 7로 한다.
System.out.println("t1의 channel값을 7로 변경하였습니다.");
System.out.println("t1의 channel값은 " + t1.channel + "입니다.");
System.out.println("t2의 channel값은 " + t2.channel + "입니다.");
}
}
t2 = t1; 부분을 보자
t1은 참조변수이므로, 인스턴스의 주소를 저장하고 있다. 이 문장이 수행되면, t2가 가지고 있던 값은 잃어버리게 되고 t1에 저장되어 있던 값이 t2에 저장되게 된다. 그렇게 되면 t2 역시 t1이 참조하고 있던 인스턴스를 같이 참조하게 되고, t2가 원래 참조하고 있던 인스턴스는 더 이상 사용할 수 없게 된다.
참조변수에는 하나의 값(주소)만이 저장될 수 있으므로 둘 이상의 참조변수가 하나의 인스턴스를 가리키는(참조하는) 것은 가능
그러나, 하나의 참조변수로 여러 개의 인스턴스를 가리키는 것은 가능하지 않다..!
많은 객체를 다뤄야할 때, 객체 또한 배열로 다루면 편리할 것.
// Tv 클래스는 위의 코드와 동일
public class TvTest4 {
public static void main(String[] args) {
Tv[] tvArr = new Tv[3]; // 길이가 3인 Tv객체 배열
// Tv객체를 생성해서 Tv객체 배열의 각 요소에 저장
for (int i = 0; i < tvArr.length; i++) {
tvArr[i] = new Tv();
tvArr[i].channel = i + 10; // tvArr[i]의 channel에 i+10을 저장
}
for (int i = 0; i < tvArr.length; i++) {
tvArr[i].channelUp(); // tvArr[i]의 메서드를 호출. 채널이 1증가
System.out.printf("tvArr[%d].channel = %d\n", i, tvArr[i].channel);
}
}
}
클래스는 객체를 생성하기 위한 틀 이며 클래스는 속성과 기능으로 정의되어있다 고 했다. 이것은 객체지향이론의 관점에서 내린 정의이고, 이번엔 프로그래밍적인 관점에서 클래스의 정의와 의미를 보자.
1. 변수 - 하나의 데이터를 저장할 수 있는 공간
2. 배열 - 같은 종류의 여러 데이터를 하나의 집합으로 저장할 수 있는 공간
3. 구조체 - 서로 관련된 여러 데이터를 종류에 관계없이 하나의 집합으로 저장할 수 있는 공간
4. 클래스 - 데이터와 함수의 결합(구조체 + 함수)
프로그래밍언어에서 제공하는 자료형(primitive type)외에 프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것을 사용자정의 타입(user-defined type)이라고 한다.
시, 분, 초를 위한 변수를 hour1, hour2, hour3 이런식으로 사용하거나 int[] hour = new int[3]; 이렇게 사용하다보면 프로그램 수행과정에서 시, 분, 초가 따로 뒤섞여서 올바르지 않은 데이터가 될 가능성이 있다. 이런 경우 이를 하나로 묶는 사용자정의 타입, 즉 클래스를 정의하여 사용해야한다.
class Time {
int hour;
int minute;
float second;
}
변수는 클래스변수, 인스턴스변수, 지역변수 모두 세 종류가 있다.
변수의 중요한 요소는 변수의 선언된 위치 이므로 변수의 종류를 파악하기 위해서는 변수가 어느 영역에 선언되었는지를 확인하는 것이 중요하다.
멤버변수를 제외한 나머지 변수들은 모두 지역변수, 멤버변수 중 static이 붙은 것은 클래스변수, 붙지 않은 것은 인스턴스변수
class Variables
{
int iv; // 인스턴스변수
static int cv; // 클래스변수(static변수, 공유변수)
void method()
{
int lv = 0; // 지역변수
}
}
| 변수의 종류 | 선언위치 | 생성시기 |
|---|---|---|
| 클래스변수(class variable) | 클래스 영역 | 클래스가 메모리에 올라갈 때 |
| 인스턴스 변수(instance variable) | 인스턴스가 생성되었을 때 | |
| 지역변수(local variable) | 클래스 영역 이외의 영역(메세드, 생성자, 초기화 블럭 내부) | 변수 선언문이 수행되었을 때 |
public class CardTest {
public static void main(String[] args) {
System.out.println("Card.width = " + Card.width);
System.out.println("Card.height = " + Card.height);
// 클래스변수(static변수)는 객체생성없이 '클래스이름.클래스변수'로 직접 사용 가능하다.
Card c1 = new Card();
c1.kind = "Heart";
c1.number = 7; // 인스턴스변수의 값을 변경한다.
Card c2 = new Card();
c2.kind = "Spade";
c2.number = 4;
// c1, c2 kind number 크기 출력
c1.width = 50;
c1.height = 80; // 클래스변수의 값을 변경한다.
// 변경된 크기 출력
}
}
class Card{
// 인스턴스변수
String kind;
int number;
// 클래스변수
static int width = 100;
static int height = 250;
}
인스턴스변수는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.
메서드(method) 는 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것이다.
기본적으로 수학의 함수와 유사하며, 어떤 값을 입력하면 이 값으로 작업을 수행해서 결과를 반환한다.
메서드는 크게 두 부분, 선언부(header, 머리) 와 구현부(body, 몸통) 로 이루어져 있다.
반환타입 메서드이름 (타입 변수명, 타입 변수명, ...) // 선언부
{ // 구
// 메서드 호출시 수행될 코드 // 현
} // 부
int add(int a, int b) // 선언부
{ // 구
int result = a + b; // 현
return result; // 호출한 메서드로 결과를 반환한다. //
} // 부
메서드의 이름 과 매개변수 선언 , 그리고 반환타입 으로 구성되어 있으며,이름만으로도 메서드의 기능을 쉽게 알 수 있도록 함축적이면서도 의미있는 이름을 짓도록 노력해야 한다.
반환값(return value) 의 타입을 적는다.void 를 적어야한다.메서드를 호출했을 때 수행될 문장들을 넣는다.
void 가 아닌 경우, 구현부{} 안에 return 반환값; 이 반드시 포함되어 있어야 한다.일치하거나 적어도 자동 형변환이 가능한 것 이어야 한다.메서드 내에 선언된 변수
메서드를 정의해도 호출되지 않으면 아무 일도 일어나지 않는다.
메서드이름(값1, 값2, ...); // 메서드를 호출하는 방법
인자(argument) 또는 인수 라고 하고,응용프로그램이 실행되면, JVM은 시스템으로부터 프로그램을 수행하는데 필요한 메모리를 할당받고 JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.

- 메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당받는다.
- 메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다.
- 호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드이다.
- 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드이다.
기본형 매개변수 - 변수의 값을 읽기만 할 수 있다.(read only)
참조형 매개변수 - 변수의 값을 읽고 변경할 수 있다.(read & write)
반환하는 값의 타입이 참조형이라는 얘기.
static Data copy(Data d){
Data tmp = new Data();
tmp.x = d.x;
return tmp;
}
메서드의 내부에서 메서드 자신을 다시 호출하는 것.
void method(){
method(); // 재귀호출, 메서드 자신을 호출한다.
}
재귀호출이 주는 논리적 간결함 때문이다.
몇 겹의 반복문과 조건문으로 복잡하게 작성된 코드를 단순한 구조로 바꿀 수 있다.
다소 비효율적이더라도 알아보기 쉽게 작성하는 것이 논리적 오류가 발생할 확률도 줄어들고 나중에 수정하기도 좋다.
다만, 재귀호출에 드는 비용 < 재귀호출의 간결함이 주는 이득 인 경우에만 사용하는게 좋다.
예시 코드) 팩토리얼(factorial) 구하기
public class FactorialTest {
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;
}
}
클래스를 정의할 떄, 어느 경우에 static을 사용해서 클래스 메서드로 정의해야 하는가?
- 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
- 클래스 변수 (static변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.
- 클래스 메서드(static메서드)는 인스턴스 변수를 사용할 수 없다.
- 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.
같은 클래스에 속한 멤버들 간에는 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다.
단, 클래스멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.
-> 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문이다.
public class MemberCall {
int iv = 10;
static int cv = 20;
int iv2 = cv;
// static int cv2 = iv; // 에러. 클래스변수는 인스턴스 변수를 사용할 수 없음.
static int cv2 = new MemberCall().iv; // 이처럼 객체를 생성해야 사용가능.
static void staticMethod1(){
System.out.println(cv);
// System.out.println(iv); // 에러. 클래스메서드에서 인스턴스변수를 사용불가.
MemberCall c = new MemberCall();
System.out.println(c.iv); // 객체를 새엇ㅇ한 후에야 인스턴스변수의 참조가능
}
void instanceMethod1(){
System.out.println(cv);
System.out.println(iv); // 인스턴스메서드에서는 인스턴스변수를 바로 사용가능
}
static void staticMethod2(){
staticMethod1();
// instanceMethod1(); // 에러. 클래스메서드에서는 인스턴스메서드를 호출할 수 없음.
MemberCall c = new MemberCall();
c.instanceMethod1(); // 인스턴스를 생성한 후에야 호출할 수 있음.
}
void instanceMethod2(){ // 인스턴스메서드에서는 인스턴스메서드와 클래스메서드
staticMethod1(); // 모두 인스턴스 생성없이 바로 호출이 가능하다.
instanceMethod1();
}
}
한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도 매개변수의 개수 또는 타입이 다르면, 같은 이름을 사용해서 메서드를 정의할 수 있다.
메서드 오버로딩(method overloading) 또는 오버로딩(overloading) 이라 한다.
- 메서드 이름이 같아야 한다.
- 매개변수의 개수 또는 타입이 달라야 한다.
오버로딩된 메서드들은 매개변수에 의해서만 구별될 수 있으므로 반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다 !
void println()
void println(boolean x)
void println(cahr x)
void println(char[] x)
void println(double x)
void println(float x)
void println(int x)
...
JDK1.5부터 메서드의 매개변수 개수를 동적으로 지정해 줄 수 있게 되었으며, 이를 가변인자(variable arguments) 라고 한다.
가변인자는 타입... 변수명 과 같은 형식으로 선언하며, PrintStream클래스의 printf()가 대표적인 예이다.
public PrintStream printf(String format, Object... args) {...}
가변인자를 사용하면 메서드 하나로도 간단히 대체할 수 있다.
가변인자는 내부적으로 배열을 이용하기도 한다. 그래서 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성된다.
-> 편리하지만, 이런 비효율이 숨어있으므로 꼭 필요한 경우에만 가변인자를 사용하자 !
-> 그리고 추가로 가능하면 가변인자를 사용한 메서드는 오버로딩하지 않는 것이 좋다.
생성자는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드 이다. 따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해서도 사용된다.
# 생성자의 조건
1. 생성자의 이름은 클래스의 이름과 같아야 한다.
2. 생성자는 리턴 값이 없다.
클래스이름(타입 변수명, 타입 변수명, ...) {
// 인스턴스 생성 시 수행될 코드,
// 주로 인스턴스 변수의 초기화 코드를 적는다.
}
class Card {
Card() { // 매개변수가 없는 생성자
...
}
Card(String k, int num) { // 매개변수가 있는 생성자
...
}
...
}
연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것은 아니다.
Card 클래스의 인스턴스를 생성하는 코드를 예로 들어, 수행되는 과정을 나누면
Card c = new Card();
1. 연산자 new에 의해서 메모리(heap)에 Card클래스의 인스턴스가 생성된다.
2. 생성자 Card()가 호출되어 수행된다.
3. 연산자 new의 결과로, 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장된다.
본래 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.
지금까지 클래스에 생성자를 정의하지 않고도 인스턴스를 생성했던건 컴파일러가 제공하는 기본 생성자(default constructor) 덕분이었다.
특별히 인스턴스 초기화 작업이 요구되어지지 않는다면 생성자를 정의하지 않고 컴파일러가 제공하는 기본 생성자를 사용하는 것도 좋다.
같은 클래스의 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능하다.
단! 다음의 두 조건을 만족해야함
- 생성자의 이름으로 클래스이름 대신 this를 사용한다.
- 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.
class Car{
String color;
String gearType;
int door;
Car(){
this("white", "auto",4); // Car(String color, String gearType, int door)를 호출
}
Car(String color){
this(color, "auto", 4);
}
public Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
public class CarTest2 {
public static void main(String[] args) {
Car c1 = new Car();
Car c2 = new Car("blue");
// c1의 속성 출력
// c2의 속성 출력
}
}
this 를 사용할 수 있는 것은 인스턴스멤버뿐이다.
static메서드(클래스 메서드)에서는 인스턴스 멤버들을 사용할 수 없는 것처럼, this 역시 사용할 수 없다.
this - 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장되어 있다. 모든 인스턴스메서드에 지역변수로 숨겨진 채로 존재한다.
현재 사용하고 있는 인스턴스와 같은 상태를 갖는 인스턴스를 하나 더 만들고자 할 때 생성자를 이용할 수 있다.
Car(Car c) {
color = c.color;
gearType = c.gearType;
door = c.door;
}
생성자를 잘 활용하면 보다 간결하고 직관적인, 객체지향적인 코드를 작성할 수 있을 것이다.
인스턴스를 생성할 때는 다음의 2가지 사항을 결정해야한다ㅏ.
1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
2. 생성자 - 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가?
멤버변수 는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지므로 초기화하지 않고 사용해도 되지만,지역변수 는 사용하기 전에 반드시 초기화해야 한다.멤버변수의 초기화 방법에는 여러 가지가 있는데 다음과 같다.
> 멤버변수의 초기화 방법
1. 명시적 초기화(explicit initialization)
2. 생성자(constructor)
3. 초기화 블럭(initialization block)
- 인스턴스 초기화 블럭 : 인스턴스변수를 초기화 하는데 사용.
- 클래스 초기화 블럭 : 클래스변수를 초기화 하는데 사용.
clas Car {
int door = 4; // 기본형(primitive type) 변수의 초기화
Engine e = new Engine(); // 참조형(reference type) 변수의 초기화
}
명시적 초기화가 간단하고 명료하긴 하지만, 보다 복잡한 초기화 작업이 필요할 때는 초기화 블럭(initialization block) 또는 생성자 를 사용해야 한다.
클래스 초기화 블럭 : 클래스변수의 복잡한 초기화에 사용된다.
인스턴스 초기화 블럭 : 인스턴스 변수의 복잡한 초기화에 사용된다.
class BlockTest {
// 클래스 초기화 블럭
static {
System.out.println("static { }");
}
// 인스턴스 초기화 블럭
{
System.out.println("{ }");
}
public BlockTest() {
System.out.println("생성자");
}
public static void main(String args[]) {
System.out.println("BlockTest bt = new BlockTest(); ");
BlockTest bt = new BlockTest();
System.out.println("BlockTest bt2 = new BlockTest(); ");
BlockTest bt2 = new BlockTest();
}
}
클래스변수의 초기화시점 : 클래스가 처음 로딩될 때 단 한번 초기화 된다.
인스턴스변수의 초기화시점 : 인스턴스가 생성될 때마다 각 인스턴스별로 초기화가 이루어진다.
클래스변수의 초기화순서 : 기본값 -> 명시적초기화 -> 클래스 초기화 블럭
인스턴스변수의 초기화순서 : 기본값 -> 명시적초기화 -> 인스턴스 초기화 블럭 -> 생성자
프로그램 실행도중 클래스에 대한 정보가 요구될 떄, 클래스는 메모리에 로딩된다. 에를 들면, 클래스 멤버를 사용했을 떄, 인스턴스를 생성할 때 등이 이에 해당된다.
하지만, 해당 클래스가 이미 메모리에 로딩되어 있다면, 또다시 로딩하지 않는다. 물론 초기화도 다시 수행되지 않는다.
class InitTest {
// 명시적 초기화(explicit initialization)
static int cv = 1;
int iv = 1;
static { cv = 2; } // 클래스 초기화 블럭
{ iv = 2; } // 인스턴스 초기화 블럭
InitTest () { // 생성자
iv = 3;
}
}
위의 코드에서 new InitTest(); 와 같이 하여 인스턴스를 생성했을 때, cv와 iv가 초기화되어가는 과정을 단계별로 살펴보면 다음과 같다.
> 클래스 변수 초기화(1~3) : 클래스가 처음 메모리에 로딩될 때 차례대로 수행됨.
> 인스턴스변수 초기화(4~7) : 인스턴스를 생성할 때 차례대로 수행됨.
1. cv가 메모리(method area)에 생성되고, cv에는 int형의 기본값인 0이 cv에 저장된다.
2. 그 다음에는 명시적 초기화(int cv=1)에 의해서 cv에 1이 저장된다.
3. 마지막으로 클래스 초기화 블럭(cv=2)이 수행되어 cv에는 2가 저장된다.
4. InitTest클래스의 인스턴스가 생성되면서 iv가 메모리(heap)에 존재하게 된다.
iv 역시 int형 변수이므로 기본값 0이 저장된다.
5. 명시적 초기화에 의해서 iv에 1이 저장되고
6. 인스턴스 초기화 블럭이 수행되어 iv에 2가 저장된다.
7. 마지막으로 생성자가 수행되어 iv에는 3이 저장된다.