객체 지향 언어
객체 지향 언어
클래스와 객체
클래스와 객체의 정의와 용도
- 클래스의 정의 : 객체를 정의해 놓은 것
- 클래스의 용도 : 객체를 생성하는데 사용
- 객체의 정의 : 실제로 존재하는 것. 사물 또는 개념
- 객체의 용도 : 객체가 가지고 있느느 기능과 속성에 따라 다름
- 유형의 객체 : 책상, 의자, 자동차와 같은 사물
- 무형의 객체 : 수학공식, 프로그램 에러와 같은 논리나 개념
- 클래스는 단지 객체를 생성하는데 사용될 뿐, 객체 그 자체는 아니다. 원하는 기능의 객체를 사용하기 위해서는 먼저 클래스로부터 객체를 생성하는 과정이 선행되어야 한다.
- 프로그래밍에서는 먼저 클래스를 작성한 다음, 클래스로부터 객체를 생성하여 사용한다.
- JDK(Java Development Kit)에서는 프로그래밍을 위해 많은 수의 유용한 클래스를 기본적으로 제공하며 이를 활용해 원하는 프로그램을 쉽게 작성 가능하다.
객체와 인스턴스
- 클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instantiate)라고 하며, 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance)라고 한다.
- 인스턴스는 객체와 같은 의미이지만, 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 가지고 있으며, 인스턴스는 어떤 클래스로부터 만들어진 것인지를 강조하는 보다 구체적인 의미를 가지고 있다.
객체의 구성요소 - 속성과 기능
- 객체는 속성과 기능, 두 종류의 구성요소로 이루어져 있으며, 일반적으로 객체는 다수의 속성과 다수의 기능을 가진다.
- 속성(Property) 멤버변수(member variable), 특성(attribute), 필드(field), 상태(state)
- 기능(function) 메서드(method), 함수(function), 행위(behavior)
인스턴스의 생성과 사용
클래스명 변수명;
변수명 = new 클래스명();
TV t;
t = new TV();
인스턴스는 참조 변수를 통해서만 다룰 수 있으며(에어컨은 에어컨 리모콘으로 실행), 참조변수의 타입은 인스턴스의 타입과 일치해야한다.
- 참조 변수에는 하나의 값(주소)만이 저장될 수 있으므로 둘 이상의 참조변수가 하나의 인스턴스를 가리키는(참조하는) 것은 가능하지만 하나의 참조변수로 여러 개의 인스턴스를 가리키는 것은 가능하지 않다.
객체 배열
- 객체 역시 배열로 다루는 것이 가능하며, 이를 객체 배열이라고 한다.
- 객체 배열안에 객체가 저장되는 것이 아니고, 객체의 주소가 저장된다.
- 객체 배열은 참조변수들을 하나로 묶은 참조변수의 배열인 것이다.
Tv[] tvArr = new Tv[3]
tvArr[0] = new Tv();
tvArr[1] = new Tv();
tvArr[2] = new Tv();
Tv[] tvArr = {new Tv(), new Tv(), new Tv()};
클래스의 또 다른 정의
-
클래스 - 데이터와 함수의 결합
- 프로그래밍언어에서 데이터 처리를 위한 데이터 저장형태의 발전 과정
- 변수 : 하나의 데이터를 저장할 수 있는 공간
- 배열 : 같은 종류의 여러 데이터를 하나의 집합으로 저장할 수 있는 공간
- 구조체 : 서로 관련된 여러 데이터를 종류에 관계없이 하나의 집합으로 저장할 수 있는 공간
- 클래스 : 데이터와 함수의 결합(구조체 + 함수)
-
클래스 - 사용자정의 타입(user-defined type)
- 프로그래밍 언어에서 제공하는 자료형외에 프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것을 사용자정의 타입이라고 한다.
변수와 메서드
선언위치에 따른 변수의 종류
- 변수는 클래스변수, 인스턴스변수, 지역변수 모두 세종류가 있다.
- 변수의 종류를 결정짓는 중요한 요소는 '변수의 선언된 위치'이므로 변수의 종류를 파악하기 위해서는 변수가 어느 영역에 선언되었는지를 확인하는 것이 중요하다.
- 멤버변수를 제외한 나머지 변수들은 모두 지역변수이며, 멤버변수 중 static이 붙은 것은 클래스변수, 붙지 않은 것은 인스턴스 변수이다.
class Variables{
int iv;
static int cv;
void method(){
int lv = 0;
}
}
- 인스턴스 변수(instance variable)
- 클래스 영역에서 선언되며, 클래스의 인스턴스를 생성할 때 만들어진다.
- 인스턴스 변수의 값을 읽어 오거나 저장하기 위해서는 먼저 인스턴스를 생성해야한다.
- 인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있다.
- 인스턴스마다 고유한 상태를 유지해야하는 속성의 경우, 인스턴스 변수로 선언한다.
- 클래스 변수(class variable)
- 클래스 변수를 선언하는 방법은 인스턴스변수 앞에 static을 붙이기만 하면 된다.
- 인스턴스마다 독립적인 저장공간을 갖은 인스턴스변수와는 달리, 클래스 변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다.
- 한 클래스 안의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우, 클래스변수로 선언해야 한다.
- 클래스 변수는 인스턴스 변수와 달리 인스턴스를 생성하지 않고도 언제라도 바로 사용할 수 있는 특징이 있다.
- 지역 변수(local variable)
- 메서드 내에 선언되며 메서드 내에서만 사용가능하며, 메서드가 종료되면 소멸되어 사용이 할 수 없게 된다.
클래스변수와 인스턴스변수
인스턴스 변수는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.
메서드
- 메서드(method)는 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것이다.
- 메서드를 사용하는 이유
- 높은 재사용성(reusability)
- 중복된 코드의 제거
- 프로그램의 구조화
메서드 선언과 구현
- 메서드는 크게 두 부분, '선언부(header, 머리)'와 '구현부(body, 몸통)'로 이루어져 있다.
- 메서드 선언부(method declaration, method header)
- 메서드 선언부는 '메서드의 이름'과 '매개변수 선언', 그리고 '반환타입'으로 구성되어 있으며, 메서드가 작업을 수행하기 위해 어떤 값들을 필요로 하고 작업의 결과로 어떤 타입의 값을 반환하는지에 대한 정보를 제공한다.
int add (int x, int y){
return result;
}
- 반환 타입(return type)
- 메서드의 작업 수행 결과(출력)인 '반환값(return value)'의 타입을 적는다. 반환값이 없는 경우 반환 타입으로 'void'를 적어야 한다.
- 지역 변수(lacal variable)
- 매서드 내에 선언된 변수들은 그 메서드 내에서만 사용할 수 있으므로 서로 다른 메서드라면 같은 이름의 변수를 선언해도 된다.
메서드의 호출
- 인자(argument)와 매개변수(paramete)
- 메서드를 호출할 때 괄호()안에 지정해준 값들을 '인자(argument)' 또는 '인수'라고 하는데, 인자의 개수와 순서는 호출된 메서드에 선언된 매개변수와 일치해야한다.
- 매서드의 실행흐름
- 같은 클래스 내에 메서드끼리는 참조변수를 사용하지 않고도 서로 호출이 가능하지만 static메서드는 같은 클래스 내의 인스턴스 메서드를 호출 할 수 없다.
- 매개변수의 유효성 검사
- 메서드를 작성할 때는 매개변수의 유효성 검사하는 코드를 넣는 것이 좋다. 매개변수의 유효성 검사는 메서드의 작성에 있어서 간과하기 쉬운 중요한 부분이다.
float divide(int x, int y){
if (y==0){
System.out.println("0으로 나눌 수 없습니다.");
return 0;
}
return x / (float)y;
}
JVM의 메모리 구조
- 메서드 영역(method area)
- 프로그램 실행 중 어떤 클래스가 사용되면 이곳에 저장한다.
- 힙(heap)
- 호출스택(call stsack 혹은 execution stack)
- 메서드의 작업에 필요한 메모리 공간을 저장한다.
- 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 비어진다.
기본형 매개변수와 참조형 매개변수
- 자바에서는 메서드를 호출할 때 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨준다.
- 매개변수의 타입이 기본형(primitive type)일 때는 기본형 값이 복사되겠지만, 참조형(reference type)이면 인스턴스 주소가 복사된다.
- 기본형 매개변수 : 변수의 값을 읽기만 할 수 있다.
- 참조형 매개변수 : 변수의 값을 읽고 변경할 수 있다.
참조형 반환타입
- 매개변수뿐만 아니라 반환타입도 참조형이 될 수 있다.
- 반환타입이 참조형이라는 것은 반환하는 값의 타입이 참조형이라는 얘긴데, 모든 참조형 타입의 값은 '객체의 주소'이므로 그저 정수값이 반환된느 것일 뿐 특별한 것이 없다.
"반환 타입이 '참조형'이라는 것은 메서드가 '객체의 주소'를 반환한다는 것을 의미한다."
재귀호출(recursive call)
- 메서드 내부에서 메서드 자신을 다시 호출하는 것을 '재귀호출(recursive call)'이라 하고, 재귀호출을 하는 메서드를 '재귀 메서드'라 한다.
void method(){
method();
}
- 대부분의 재귀 호출은 반복문으로 작성하는 것이 가능하다.
- 반복문은 그저 같은 문장을 반복해서 수행하는 것이지만, 메서드를 호출하는 것은 반복문보다 몇 가지 과정, 예를 들면 매개변수 복사와 종료 후 복귀할 주소저장 등이 추가로 필요하기 때문에 반복문보다 재귀호출의 수행시간이 오래 걸린다.
- 반복문 대신 재귀문을 사용하는 이유는 논리적 간결함때문이다. -> 유지보수가 쉬움
class FactorialTest {
public static void main(String args[]) {
System.out.println(factorial(4));
}
static long factorial(int n) {
long result=0;
if (n == 1) return 1;
return n * factorial(n-1);
}
}
클래스 메서드(static메서드)와 인스턴스 메서드
- 변수와 마찬가지로 메서드 앞에 static이 붙어 있으면 클래스메서드이고 붙어있지 않으면 인스턴스 메서드이다.
- 클래스 메서드도 클래스변수처럼, 객체를 생성하지 않고도 '클래스이름.메서드이름(매개변수)'와 같은 식으로 호출이 가능하다. 반면에 인스턴스 메서드는 반드시 객체를 생성해야만 호출할 수 있다.
- 인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다. 그런데 인스턴스 변수는 인스턴스(객체)를 생성해야만 만들어지므로 인스턴스 메서드 역시 인스턴스를 생성해야만 호출할 수 있는 것이다.
- 반면에 메스드 중에서 인스턴스와 관계없는(인스턴스 변수나 인스턴스 메서드를 사용하지 않는) 메서드를 클래스 메서드(static메서드)로 정의한다.
- 물론 인스턴스 변수를 사용하지 않는다고 해서 반드시 클래스 메서드로 정의해야하는 것은 아니지만 특별한 이유가 없는 한 일반적이다.
클래스 영역에 선언된 변수를 멤버변수라 한다. 멤버변수 중에 static이 붙은 것은 클래스변수(static변수), static이 붙지 않은 것은 인스턴스변수라 한다. 멤버변수는 인스턴스와 static변수를 모두 통칭하는 말이다.
- 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
- 클래스 변수(static변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.
- static이 붙은 변수(클래스변수)는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다.
- 클래스 메서드(static)는 인스턴스 변수를 사용할 수 없다.
- 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.
- 클래스의 멤버변수 중 모든 인스턴스에 공통된 값을 유지해야하는 것이 있는지 살펴보고 있으면, static을 붙인다.
- 작성한 메서드 중에서 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드에 static을 붙일 것을 고려한다.
클래스 멤버와 인스턴스 멤버간의 참조와 호출
- 같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다.
- 단, 클래스멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야한다.
- 그 이유는 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있다.
class TestClass{
void instanceMethod() {}
static void staticMethod() {}
void instanceMethod2(){
instanceMethod();
staticMethod();
}
static void staticMethod2(){
instanceMethod();
staticMethod();
}
}
- 같은 클래스 내의 메서드는 서로 객체의 생성이나 참조변수 없이 직접 호출이 가능하지만 sattic메서드는 인스턴스 메서드를 호출할 수 없다.
class TestClass2{
int iv;
static int cv;
void instanceMethod(){
System.out.println(iv)
System.out.println(cv)
}
static void staticMethod(){
System.out.println(iv)
System.out.println(cv)
}
}
- 클래스멤버(클래스 변수와 클래스메서드)는 언제나 참조 또는 호풀이 가능하기 때문에 인스턴스멤버가 클래스멤버를 사용하는 것은 아무런 문제가 안왼다. 클래스멤버간의 참조 또는 호출 역시 아무런 문제가 없다.
- 그러나, 인스턴스멤버(인스턴스변수와 인스턴스메서드)는 반드시 객체를 생성한 후에만 참조 또는 호출이 가능하기 때문에 클래스멤버가 인스턴스멤버를 참조, 호출하기 위해서는 객체를 생성하여야 한다.
- 하지만, 인스턴스멤버간의 호출에는 아무런 문제가 없다. 하나의 인스턴스멤버가 존재한다는 것은 인스턴스가 이미 생성되어있다는 것을 의미하며, 즉 다른 인스턴스멤버들도 모두 존재하기 때문이다.
오버로딩(overloading)
오버로딩이란?
- 메서드도 변수와 마찬가지로 같은 클래스 내에서 서로 구별될 수 있어야 하기 때문에 각기 다른 이름을 가져야한다.
- 한 클래스 내에 같은 이름의 메서드를 여러개 정의하는 것을 '메서드 오버로딩(method overloading)' 또는 간단히 '오버로딩(overloading)'이라 한다.
오버로딩의 조건
- 메서드 이름이 같아야 한다.
- 매개변수의 개수 또는 타입이 달라야한다.
- 오버로딩된 메서드들은 매개변수에 의해서만 구별될 수 있으므로 변환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 않는다.
오버로딩의 장점
- 근본적으로 같은 기능을 하는 메서드들을 하나의 이름으로 지어 기억하기 쉽고 이름도 짧게 할 수 있어서 오류의 가능성을 줄일 수 있다.
-또한 메서드의 이름을 절약할 수 있다.
가변인자(varargs)와 오버로딩
- 가변 인자는 '타입... 변수명'과 같은 형식으로 선언하며, PrintStream클래스의 printf()가 대표적인 예이다.
public PrintStream printf(String format, Object... args){...}
String concatenate(String s1, String s2){...}
String concatenate(String s1, String s2, String s3){...}
String concatenate(String s1, String s2, String s3, String s4){...}
String concatenate(String... str){...}
생성자(Constructor)
생성자란?
class test:
def __init__(self)
class Card{
Card(){}
- 생성자는 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'이다.
- 따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해서도 사용된다.
인스턴스 초기화란, 인스턴스 변수들을 모두 초기화하는 것을 뜻한다.
- 생성자 역시 메서드처럼 클래스 내에 선언되며, 구조도 메서드와 유사하지만 리턴값이 없다는 점이 다르다. 그렇다고 해서 생성자 앞에 리턴값이 없음을 뜻하는 키워드 void를 사용하지는 않고, 단지 아무 것도 적지 않는다.
- 생성자의 조건
- 생성자의 이름은 클래스의 이름과 같아야 한다.
- 생성자는 리턴 값이 없다.
생성자도 메서드이기 때문에 리턴값이 없다는 의미의 void를 붙여야 하지만, 모든 생성자가 리턴값이 없으므로 void를 생략할 수 있게 한 것이다.
- 생성자는 아래와 같이 정의한다. 생성자도 오버로딩이 가능하므로 하나으 ㅣ클래스에 여러개의 생성자가 존재할 수 있다.
클래스 이름(타입 변수명, 타입 변수명, ...){
}
class Card{
Card(){
...
}
Card(String k, int num){
...
}
}
- 연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다.
- 생성자가 갖는 몇 가지 특징만 제외하면 메서드와 다르지 않다.
Card c = new Card();
- 연산자 new에 의해 메모리(heap)에 Card클래스의 인스턴스가 생성된다.
- 생성자 Card()가 호출되어 수행한다.
- 연산자 new의 결과로, 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장된다.
기본 생성자(default constructor)
- 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.
- 컴파일 할 때, 소스파일(*.java)의 클래스에 생성자가 하나도 정의되지 않은 경우 컴파일러는 자동적으로 아래와 같은 내용의 기본 생성자르 추가하여 컴파일 한다. -> 단, 하나라도 존재할 경우 기본 생성자는 생성되지 않는다.
클래스이름() { }
card() {}
매개변수가 있는 생성자
- 생성자도 메서드처럼 매개변수를 선언하여 호출시 값을 넘겨받아서 인스턴스의 초기화 작업에 사용할 수 있다.
class Car{
String color;
String gearType;
int door;
Car() {}
Car(String c, String g, int d) {
color = c;
gearType = g;
door = d;
}
}
생성자에서 다른 생성자 호출하기 - 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;
}
}
class CarTest2 {
public static void main(String[] args) {
Car c1 = new Car();
Car c2 = new Car("blue");
System.out.println("c1의 color=" + c1.color + ", gearType=" + c1.gearType+ ", door="+c1.door);
System.out.println("c2의 color=" + c2.color + ", gearType=" + c2.gearType+ ", door="+c2.door);
}
}
Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
- 위의 예시처럼 color가 구별이 되지 않을 때 인스턴스 변수 앞에 this를 붙여준다.
- 'this'는 참조변수로 인스턴스 자신을 가리킨다.
- 참조변수를 통해 인스턴스의 멤버에 접근할 수 있는 것처럼, 'this'로 인스턴스 변수에 접근할 수 있는 것이다.
this : 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장되어 있다. 모든 인스턴스메서드에 지역변수로 숨겨진 채로 존재한다.
this(), this(매개변수) : 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.
\참고\ this와 this()는 비슷하게 생겼을 뿐 완전히 다른 것이다. this는 '참조 변수'이고, this()는 생성자이다.
생성자를 이용한 인스턴스의 복사
- 현재 사용하고 있는 인스턴스와 같은 상태를 갖는 인스턴스를 하나 더 만들고자 할 때 생성자를 이용할 수 있다.
- 하나의 클래스로부터 생성된 모든 인스턴스의 메서드와 클래스 변수는 서로 동일하기 때문에 인스턴스간의 차이는, 인스턴스마다 각기 다른 값을 가질수 있는 인스턴스 변수 뿐이다.
Car(Car c){
color = c.color;
gearType = c.gearType;
door = c.door;
}
object클래스에 정의된 clone메서드를 이용하면 간단히 복사할 수 있다.
- 인스턴스를 생성할 때는 다음 2가지 사항을 결정해야한다.
- 1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
- 2. 생성자 - 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가?
변수의 초기화
변수의 초기화
- 변수를 선언하고 처음으로 값을 저장하는 것을 '변수의 초기화'라고 한다.
- 변수의 초기화는 경우에 따라서 필수적이기도 하고 선택적이기도 하지만, 가능하면 선언과 동시에 적절한 값으로 초기화하는 것이 바람직하다.
멤버변수(클래스 변수와 인스턴스 변수)와 배열의 초기화는 선택적이지만, 지역변수의 초기화는 필수적이다.
명시적 초기화(explicit initialization)
class Car {
int door = 4;
Engine e = new Engine();
}
초기화 블럭(initialization block)
- 클래스 초기화 블럭 : 클래스변수의 복잡한 초기화에 사용된다.
- 인스턴스 초기화 블럭 : 인스턴스변수의 복잡한 초기화에 사용한다.
-
초기화 블럭을 작성하려면, 인스턴스 초기화 블럭은 단순히 클래스 내에 블럭{}만들고 그 안에 코드를 작성하기만 하면된다. 그리고 클래스 초기화 블럭은 인스턴스 초기화 블럭 앞에 단순히 static을 덧붙이기만 하면 된다.
-
초기화 작업이 복잡하여 명시적 초기화만으로 부족한 경우 초기화 블럭을 사용한다.
class InitBlock{
static {}
{}
}
- 클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한번만 수행되며, 인스턴스 초기화 블럭은 생성자와 같이 인스턴스를 생성할 때마다 수행한다.
멤버 변수의 초기화 시기와 순서
클래스변수의 초기화 시점 : 클래스가 처음 로딩될 때 단 한번 초기화된다.
인스턴스변수의 초기화 시점 : 인스턴스가 생성될 때마다 각 인스턴스별로 초기화가 이루어진다.
클래스변수의 초기화 순서 : 기본값 -> 명시적 초기화 -> 클래스 초기화 블럭
인스턴스변수의 초기화 순서 : 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자