🌑 객체지향언어
✔️ 코드의 높은 재사용성
✔️ 코드의 관리가 용이
✔️ 제어자와 메서드를 이용한 데이터 보호
✔️ 코드의 중복을 제거하여 불일치로 인한 오동작 방지
🌒 클래스(Class) · 객체(Object)
✔️ 클래스와 객체의 정의 · 용도
🔹 클래스 : 객체를 정의해 놓은 것
~ 객체를 생성하는데 사용
🔹 객체 : 실제로 존재하는 것, 사물 또는 개념
~ 객체가 가지고 있는 기능과 속성에 용도가 달라진다.
✔️ 객체(Object) · 인스턴스(Instance)
🔹 클래스의 인스턴스화 (Instantiate) ~ 클래스로부터 객체를 만드는 과정
🔹 클래스로부터 만들어진 객체 ⮕ 그 클래스의 인스턴스(Instance)
🔸 인스턴스 ⊆ 객체
✔️ 객체의 구성요소 - 속성(Property) & 기능(Function)
🔹 속성
- 멤버변수 (Member variable)
- 특성 (Attribute)
- 필드 (Field)
- 상태 (State)
🔹 기능
- 메서드(Method)
- 함수 (Function)
- 행위 (Behavior)
✔️ 인스턴스(Instance)의 생성 · 사용
🔹 className variableName;
⇒ 클래스의 객체를 참조하기 위한 참조변수 선언
🔹 variableName = new className();
⇒ 클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장
Tv t;
t = new Tv();
t.channel = 7; // 참조변수 t에 저장된 주소에 있는 인스턴스의 멤버변수 'channel'에 7을 저장
t.channelDown(); // 참조변수 t가 참조하고 있는 Tv인스턴스의 channelDown메서드를 호출
🔻 instance는 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입 = instance의 타입
Tv t1 = new Tv();
Tv t2 = new Tv();
...
t2 = t1; // t2는 t1과 같은 instance를 참조하게 되고
// t2가 원래 참조하고 있던 instance는 더이상 사용 불가능
🔻 같은 클래스로부터 생성되더라도 각 instance의 속성(멤버변수)은 서로 다른 값을 유지
✔️ 객체 배열
Class_Name obj1, obj2, obj3;
🔻많은 수의 객체를 다뤄야할 때, 배열로 다루면 편리 ⮕ 객체 배열
Class_Name[] objArr = new ClassName[3];
objArr[0] = new Class_Name();
objArr[1] = new Class_Name();
objArr[2] = new Class_Name();
🔻 위처럼 객체를 생성해서 꼭 객체 배열의 각 요소에 저장
- 객체 배열을 생성 = 객체를 다루기 위한 참조 변수를 생성
⮕ 객체 배열을 생성한다고 객체가 저장된 것이 아니다 !
🔹 객체 배열에 저장된 것은 객체가 아닌 객체의 주소 !
🌓 변수 · 메서드 ( Method )
✔️ 선언위치에 따른 변수의 종류
class Variables {
int instanceVariable;
static int classVariable;
void Method() {
int localVariable;
}
}
🔹 인스턴스 변수 (Instance variable) ~ 객체에 속해있는 개념 ⇒ 전역 변수
- Class 영역
- Class의 Instance를 생성해야 사용 가능
🔺 독립적인 공간 ⇒ 서로 다른 값을 갖는 것이 가능
⇒ instance마다 고유한 상태를 유지해야 하는 속성의 경우
⇒ instance 변수로 선언
🔹 클래스 변수 (Class variable) ~ 객체가 아닌 클래스에 속해있는 개념
- 선언 방법 - static + instance 변수
- 'class_Name . class_vaiable' ~ instance 변수와 달리 instance를 생성하지 않고도 사용 가능
🔺 모든 instance가 공통된 저장공간(변수)를 공유
⇒ 한 클래스의 모든 instance(객체)들이 공통적인 값을 유지해야하는 속성의 경우
⇒ 클래스 변수로 선언
🔹 지역 변수 (Local variable)
⇔ instance 변수, class 변수 ⇒ 전역 변수
- 매서드 영역
- 지역변수가 선언된 블럭{ } 내에서만 사용 가능
✔️ 메서드(Method) ~ 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것
🔹 높은 재사용성 · 중복된 코드 제거 · 프로그램의 구조화
✔️ 메서드(Method)의 선언 · 구현
Return_Type Method_Name ( /*parameter declaration*/ ) {
statements ...
}
🔻 반환값(return value) ⇒ 반환타입과 같은 타입이거나 자동 형변환이 가능한 타입
🔻 반환값이 없는 경우, 반환타입을 'void' 선언
✔️ 메서드(Method) 호출
Method_Name(value1, value2, ...)
🔹 인자(Argument) : 메서드를 호출할 때 괄호( )안에 지정해준 값 (value1, value2, ...)
🔸 인자의 개수 · 순서는 호출된 메서드에 선언된 매개변수와 일치!
- 인자의 타입은 매개변수의 타입 혹은 자동 형변환이 가능한 타입이어야 한다.
✔️ return 문
return x >= 0 ? true : false;
🔻return 값으로 변수뿐만 아니라 삼항연산자를 활용해서도 반환할 수 있다.
float divide (int x, int y) {
// 작업하기 전에 나누는 수(y)가 0인지 확인
if (y==0) {
System.out.println("0으로 나눌 수 없습니다.");
return 0; // 매개변수가 유효하지 않으므로 메서드를 종료
}
return x / (float) y; // 메소드 타입과 반환 타입을 맞춰줘야 한다.
}
🔻 "return 0;" 처럼 메서드에 적절하지 않은 매개변수의 값이 넘어온 경우 메서드를 빠져나올 수 있도록
매개변수의 유효성을 검사하는 용도로 유용하게 사용
✔️ JVM (Java Virtual Machine) 의 메모리 구조 - JAVA가 운영체제에 독립적인 이유
🔹 메서드 영역 (Method Area)
▪️ 프로그램 실행 중 어떠한 클래스가 사용
⇒ JVM은 해당 클래스의 클래스 파일(*.class)을 읽어서 분석
⇒ 클래스 데이터를 이곳에 저장, 클래스 변수도 이 영역에 함께 생성
🔹 힙 (heap)
▪️ 프로그램 실행 중 생성되는 모든 인스턴스가 생성되는 공간
⇒ 인스턴스 변수들이 생성되는 공간
🔹 호출스택 (call stack 또는 execution stack)
▪️ 메서드의 수행에 필요한 메모리 공간 제공
▪️ 호출된 메서드 수행에 필요한 메모리를 호출스택에 할당
⇒ 메서드를 수행하는 동안 지역변수(매개변수 포함)와 연산의 중간결과 등 관련 데이터를 저장
▪️ 메서드가 수행을 마치면 할당되었던 메모리 공간을 반환하여 스택에서 제거
🔻 Runtime Data Area ( Method Area + Heap + Call Stack )
✔️ 기본형 매개변수 (read only) · 참조형 매개변수 (read & write) ~ Pass By Value
🔹 기본형 매개변수 ~ 변수의 값을 읽는 것만 가능
🔹 참조형 매개변수
➞ 참조형 변수의 값(참조형 변수가 가리키는 변수의 주소 값)을 읽고 변경 가능
➞ 참조형 매개변수가 주소값을 저장하고 있기 때문에 Pass By Reference로 착각할 수 있다.
✔️ 참조형 반환타입
🔹 매개변수뿐만 아니라 반환타입도 참조형으로 선언 가능
// 클래스 Data의 객체를 생성하고 객체의 주소에 어떠한 값을 저장한 코드는 생략
static Data copy (Data d) {
Data tmp = new Data();
tmp.x = d.x;
return tmp;
}
🔻 메서드의 반환타입이 '참조형' ⇒ 해당 메서드가 '객체의 주소'를 반환한다는 것을 의미
✔️ 재귀호출 (recursive call)
static int factorial (int n) {
// factorial을 계산하는 메서드
int result = 0;
if (n == 1) result = 1;
/*
else result = n * factorial(n-1);
*/
result = n * factorial(n-1); // 재귀호출
return result;
}
🔻 이처럼 재귀호출은 조건문이 필수적!
static int factorial (int n) {
if (n <= 0 || n > 12) return -1; // 매개변수 n의 유효성 검사
if (n == 1) result = 1;
return n * factorial(n-1); // 재귀호출
}
🔻 매개변수 n <= 0 또는 메서드의 반환 타입인 int의 최대값보다 커질 경우(n > 12) 발생하는 에러 방지
🔻 매개변수의 유효성을 검사하는 코드 필요 ~ 어떤 값이 들어와도 코드를 에러없이 처리
✔️ 클래스 메서드(static 메서드) · instance 메서드
🔹 instance 메서드 ⇒ instance 변수 · 메서드, static 변수 · 메서드 사용(호출) 가능
🔹 static 메서드 ⇒ static 변수 · 메서드 직접 호출 가능
- 객체를 통해 참조해야 하는 인스턴스 변수 · 메서드는 직접 사용(호출) 불가능
🔻 instance 멤버가 존재하는 시점엔 class 멤버는 항상 존재하지만,
class 멤버가 존재하는 시점엔 instance 멤버가 존재하지 않을 수 있기 때문이다.
- 반드시 instance를 생성하여 instance 멤버를 사용(호출) !
class MyMath {
long a, b;
// instance 변수 a, b만을 이용해서 작업하므로 매개변수 필요 X
long add() { return a + b; }
double divide() { return a / b; }
// instance 변수와 관계없이 매개변수만으로 작업 가능
static long add(long a, long b) { return a + b; }
static double divide(double a, double b) { return a / b; }
}
class MyMathTest {
public static void main(String[] args) {
// 클래스 메서드 호출. instance 생성없이 호출 가능
System.out.println(MyMath.add(200L, 100L));
System.out.println(MyMath.divide(200.0, 100.0));
MyMath mm = new MyMath(); // 인스턴스 생성
mm.a = 200L;
mm.b = 100L;
// instance 메서드는 객체 생성 후에만 호출 가능
System.out.println(add());
System.out.println(mm.divide());
}
}
🔻 해당 메서드에서 instance 변수나 instance 메서드를 사용하지 않는다면 static을 붙이는 것을 고려
✔️ class 멤버와 instance 멤버간의 참조 · 호출
🔹 실제로 같은 class 내에서 class 멤버가 instance 멤버를 참조 · 호출해야 하는 경우는 드물다.
⇒ 만약 그런 경우가 발생한다면, instance 메서드로 작성해야할 메서드를
class 메서드로 작성한 것은 아닌지 확인 !
✔️ T i p
MemberCall c = new MemberCall();
int result = c.instanceMethod();
int result = new MemberCall().instanceMethod(); // 간략하게 표현 가능
🔻 하지만, 참조변수를 선언하지 않았기 때문에 생성된 MemberCall의 instance는 더 이상 사용 불가능 !
🌔 오버로딩 ( Overloading )
✔️ 오버로딩
🔹 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것
⇒ 하나의 메서드 이름으로 여러 기능을 구현
✔️ 오버로딩의 조건
🔹 메서드 이름이 같아야 한다.
🔹 매개변수의 개수 또는 타입이 달라야 한다.
🔸 오버로딩된 메서드들은 매개변수에 의해서만 구별 가능
⇒ 반환 타입은 오버로딩을 구현하는데 영향 X
✔️ 오버로딩의 장점
void println()
void printlnBoolean(boolean x)
void printlnChar(char x)
void printlnDouble(double x)
void printlnString(String x)
void println(/*parameter*/) // 같은 기능을 하는 메서드를 이름으로 구별할 필요 X
🔹 같은 기능을 하는 메서드들을 하나의 이름으로 간편하게 정의 가능
⇒ 메서드의 이름 절약 가능
✔️ 가변인자 ( varargs ) · 오버로딩
🔹 가변인자 ~ 매개변수의 개수를 동적으로 지정해줄 수 있는 기능
public PrintStream printf(String format, Object.. args) { ... }
🔻 가변인자 외에 매개변수도 존재할 경우, 가변인자를 매개변수 중에서 가장 마지막에 선언
String concatenate(String... str) { ... }
System.out.println(concatenate());
System.out.println(concatenate("a"));
System.out.println(concatenate("a", "b"));
System.out.println(concatenate("new String[] {"A", "B")); // 배열도 가능
🔻 인자가 없는 경우도 가능하며, 배열도 인자로 선언 가능
🔻 즉, 가변인자는 내부적으로 배열을 사용하는 개념 !
⇒ 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성되어 비효율적이므로, 꼭 필요할 때만 사용
🔹 가변인자와 매개변수의 타입을 배열로 하는 것의 차이점
String concatenate(String[] str) { ... }
String result = concatenate(new String[0]); // 인자로 배열을 지정
String result = concatenate(null); // 인자로 null을 지정
String result = concatenate(); // Error : 항상 인자가 필요하다.
public static void main(String[] args) {
String[] strArr = { "100", "200", "300" };
System.out.println(concatenate("-", strArr); // OK
System.out.println(concatenate("-", new String[] {"100", "200", "300"}); // OK
System.out.println(concatenate("-", {"100", "200", "300"}); // Compile error
}
static String concatenate(String delim, String... args) { ... }
🔻 String으로 선언된 배열 strArr 처럼 배열을 생성할 땐 new (객체 생성) 연산자는 생략 가능하지만,
배열을 가변인자로 선언된 매개변수로 지정할 경우엔 new 연산자 생략 불가능 !
static String concatenate(String delim, String... args) { ... }
static String concatenate(String... args) { ... }
System.out.println(concatenate("-", "100", "200", "300"); // Compile error
🔻 가변메서드를 호출했을 때 이와 같이 구별되지 못하는 경우가 발생하기 쉽다.
⇒ 가변인자를 사용한 메서드는 오버로딩을 지양
✔️ 생성자 ( Consructor )
🔹 생성자 ~ instance가 생성될 때 호출되는 'instance 초기화 메서드'
- instance 변수의 초기화 작업에 주로 사용
- instance 생성 시에 실행되어야 할 작업을 위해서도 사용
- 생성자는 instance를 생성 X, 연산자 new가 instance를 생성
⇒ 생성자도 하나의 특별한 메서드 !
- 클래스 내에 선언되며, 생성자의 이름은 클래스이 이름과 동일 O
- 모든 생성자는 리턴 값 X ⇒ 리턴값이 없다는 의미의 void 생략
class Constructor {
Constructor() { // 매개변수가 없는 생성자
...
}
Constructor(String k, int num) { // 매개변수가 있는 생성자
...
}
...
}
🔻 생성자도 Overloading 가능
✔️ 기본 생성자 ( Default constructor )
🔹 클래스에 생성자를 정의하지 않고도 instance 생성이 가능하게 했던 이유
⇒ 컴파일러가 자동적으로 제공하는 '기본 생성자'
class Data2 {
int value;
Data2(int x) { // 매개변수가 있는 생성자
value = x;
}
}
class ConstructorTest {
public static void main(String[] args) {
Data d1 = new Data1();
Data d2 = new Data2(); // Compile error
}
}
🔻 컴파일러가 자동적으로 기본 생성자를 추가해주는 경우
⇒ 클래스 내에 생성자가 하나도 없는 경우 ( ex. Data d1 )
✔️ 매개변수가 있는 생성자
~ 이때는 기본 생성자를 자동으로 생성 X
🔻사용자가 생성자를 추가로 정의한 경우엔, 기본 생성자를 따로 선언해줘야
' Car c1 = new Car(); ' 처럼 기본 생성자를 호출하더라도 컴파일 에러가 발생하지 않는다 !
class Car {
String color;
String gearType;
int door;
Car() {}
Car(String c, String g, int d) {
color = c;
gearType = g;
door = d;
}
}
class CarTest {
public static void main(String[] args) {
Car c1 = new Car();
c1.color = "white";
c1.gearType = "auto";
c1.door = 4;
Car c2 = new Car("white", "auto", 4);
...
}
}
🔻 매개변수를 갖는 매개변수를 갖는 생성자를 사용하면, instance 생성 후에 별도로 초기화 X
✔️ 생성자에서 다른 생성자 호출하기 - this(), this
🔹생성자에서 다른 생성자 호출하기
- 생성자 간에서 서로의 호출은 클래스 이름이 아닌 this를 이용하여 호출
- 한 생성자에서 다른 생성자를 호출할 때에는 반드시 첫 줄에서만 호출 가능
class Car {
String color;
String gearType;
int door;
Car() {
this("white", "auto", 4);
}
Car(String color) {
this(color, "auto", 4):
}
Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
🔻 'this.color'는 instance 변수, color는 매개 변수로 정의된 지역 변수로 구별 가능
🔻 this를 사용할 수 있는 것은 instance 멤버뿐이며, static 메서드(클래스 메서드)에선 this도 사용 불가능
🔹 this
: instance 자신을 가리키는 '참조변수', instance의 주소가 저장되어 있다.
🔹 this(), this(매개변수)
: '생성자', 같은 클래스의 다른 생성자를 호출할 때 사용
✔️ 생성자를 이용한 instance의 복사
🔹 기존 instance의 모든 instance 변수와 동일한 값을 갖고 있는 instance를 복사하고자 하는 경우
- 생성자 이용 가능
class Car {
String color;
String gearType;
int door;
Car() {
this("white", "auto", 4);
}
Car(Car c) { // instance 복사를 위한 생성자
color = c.color;
gearType = c.gearType;
door = c.door;
}
Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
🔹 instance를 생성할 때 고려해야할 점
- 어떤 클래스의 instance를 생성할 것인가?
- 선택한 클래스의 어떤 생성자로 instance를 생성할 것인가?
🌕 변수의 초기화
✔️ 변수의 초기화
🔹 멤버 변수(instance 변수)는 초기화를 없이 자동적으로 변수의 자료형에 맞는 기본값으로 초기화
🔹 지역 변수는 사용하기 전에 반드시 초기화
class InitTest {
int x;
int y = x;
void method1() {
int i; // 지역변수
int j = i; // Error : 지역변수를 초기화하지 않고 사용
}
}
✔️ 명시적 초기화 ( Explicit initialization )
🔹 변수를 선언과 동시에 초기화
class Car {
int door = 4; // 기본형 변수의 명시적 초기화
Engine e = new Engine(); // 참조형 변수의 명시적 초기화
...
}
🔻 이보다 복잡한 초기화 작업이 필요할 경우
⇒ '초기화 블럭' 또는 생성자 사용
✔️ 초기화 블럭 ( Initialization block )
class InitBlock {
static { /* 클래스 초기화 블럭 */ }
{ /* 인스턴스 초기화 블럭 */ }
...
}
🔹 클래스 초기화 블럭 ~ instance 초기화 블럭 앞에 static 을 추가
- 클래스가 메모리에 처음 로딩될 때만 한 번 수행
🔹 instance 초기화 블럭 ~ 클래스 내에 블럭{ }을 만들고 그 안에 코드 작성
- instance가 생성될 때마다 수행
Car() {
count++;
serialNo = count;
color = "White";
gearYpe = "Auto";
}
Car(String color, String gearType) {
count++;
serialNo = count;
this.color = color;
this.gearType = gearType;
}
---------------------------------------------------------------------------
* instance 초기화 블럭 활용
{
count++;
serialNo = count;
}
Car() {
color = "White";
gearType = "Auto";
}
Car(String color, String gearType) {
this.color = color;
this.gearType = gearType;
}
🔻 모든 생성자에서 공통적으로 사용되는 코드를 따로 빼는 개념
🔻 instance 초기화 블럭은 코드의 중복을 제거
⇒ 코드의 신뢰성 ⇈, 오류 발생가능성 ⇊
⇒ 즉, 코드의 재사용성을 높이고 중복을 제거하고자 하는 객체지향프로그래밍의 본질
✔️ 멤버변수의 초기화 시기와 순서
🔹 클래스 변수의 초기화 시점 ~ 클래스가 처음 로딩될 때 단 한 번 초기화
🔹 instance 변수의 초기화 시점 ~ instance가 생성될 때마다 각 instance별로 초기화
🔹클래스 변수의 초기화 순서 ~ 기본값 ⮕ 명시적 초기화 ⮕ 클래스 초기화 블럭
🔹 instance 변수의 초기화 순서 ~ 기본값 ⮕ 명시적 초기화 ⮕ instance 초기화 블럭 ⮕ 생성자