티스토리 뷰
1. 객체 지향 언어
객체지향언어의 역사
- 과거: 모의 실험시 실제 세계와 유사한 가상 세계를 컴퓨터 속에 구현하려고 노력 → 객체 지향론 탄생
- 객체지향론의 기본 개념
- 실제 세계는 사물(객체)로 이루어져 있고 발생하는 모든 사건은 이들간의 상호 작용이다.
- 실제 사물의 속성 → 데이터(변수), 사물의 기능 → 함수로 정의
- 절차적 언어가 많이 사용되다가 프로그램의 규모 증가와 사용자들의 요구의 빠른 변화에 대응하기 위해서 객체 지향 언어의 입지가 넓어짐
- 또한 인터넷의 발전은 객체 지향 언어를 프로그래밍의 주류로 만듬
객체지향언어
- 객체 지향이란 완전히 새로운 것이 아닌 기존의 규칙에 몇가지 새로운 규칙이 추가된 것
- 이러한 규칙으로 코드간에 서로 관계를 맺어주고 유기적으로 프로그램을 구성
- 객체지향언어의 주요 특징
- 코드의 재사용성
- 유지보수 용이
- 코드의 관계를 이용, 적은 노력으로 코드를 쉽게 변경 가능
- 중복된 코드의 제거(신뢰성 높은 프로그래밍)
- 제어자, 메소드 → 데이터 보호, 올바른 값 유지
- 코드의 중복 제거 → 코드의 불일치로 인한 오동작 방지
- 이러한 특징은 프로그램의 유지보수에 드는 비용을 획기적으로 개선
- 처음부터 객체지향적으로 프로그래밍하려고 하지 말고, 기능적인 구현을 한 다음 코드를 객체 지향적으로 개선하려고 고민해보기
2. 클래스와 객체
클래스와 객체의 정의와 용도
- 클래스
- 객체를 정의해 놓은 것, 객체의 설계도
- 용도: 객체를 생성하는데 사용
- 객체
- 실제 존재하는 사물 또는 개념(유, 무형 모두 가능)
- 무형 예시 - 수학 공식, 프로그램 에러과 같은 논리나 개념
- 클래스에 정의된 대로 메모리에 생성된 것 (프로그래밍 관점)
- 용도: 객체의 속성과 기능에 따라 다름
- 실제 존재하는 사물 또는 개념(유, 무형 모두 가능)
- 클래스는 TV 설계도, 객체는 TV에 비유
- TV 설계도는 TV를 만드는데 사용된다.
- TV 설계도가 있어야 TV를 만들 수 있다.
- 하나의 설계도만 만들어놓으면 제품을 설계도대로만 만들면 됨 → 클래스를 사용하는 이유
- JDK(Java Development Kit)에서 제공하는 유용한 클래스를 사용하여 원하는 프로그램을 보다 쉽게 작성 가능
객체와 인스턴스
- 클래스의 인스턴스화: 클래스로부터 객체를 만드는 과정
- 클래스의 인스턴스: 어떤 클래스로부터 만들어진 객체
- 객체: 모든 인스턴스를 대표하는 포괄적인 의미 → 책상은 객체다.
- 인스턴스: 어떤 클래스로부터 만들어진 것인지를 강조 → 책상은 책상 클래스의 인스턴스다.
객체의 구성요소 - 속성과 기능
- 객체: 속성과 기능의 집합
- 객체의 멤버: 객체가 가지고있는 속성과 기능
- 클래스에는 이러한 속성과 기능이 미리 정의되어 있음
- 속성과 기능의 동의어
- 속성(property): 멤버 변수(member variable), 특성(attribute), 필드(field), 상태(state)
- 기능(function): 메서드(method), 함수(function), 행위(behavior)
- 객체 지향 프로그래밍: 속성 → 변수, 기능 → 메서드
Class Tv {
String color;
boolean power;
int channel;
void power () { power = !power }
void chnnelUp () { channel++;}
void channelDown () { channel--; }
}
- 멤버변수는 멤버변수끼리 메서드는 메서드끼리 모아놓는 것이 일반적
인스턴스의 생성과 사용
- TV 설계도만으로는 TV를 사용할 수 없음 → TV 인스턴스를 생성해야 함
Tv t; // 클래스명 변수명; ----- (1)
t = new Tv(); // 변수명 = new 클래스명(); ----- (2)
- Tv t;
- 참조변수 t 선언
- 메모리에 참조변수 t를 위한 공간이 마련 (내용은 없음)
- t = new Tv();
- 연산자 new에 의해 Tv 클래스의 인스턴스가 메모리의 빈공간에 생성
- 멤버변수는 각 자료형에 해당하는 기본값으로 초기화(ex. 참조형: null, boolean: false, int: 0)
- 주소가 0x100인곳에 생성되었다면 대입 연산자에 의해 생성된 객체의 주소값이 참조변수 t에 저장
- → 참조변수 t가 Tv 인스턴스 주소를 가리킴
⇒ 인스턴스는 참조변수를 통해서만 다룰 수 있으며 참조변수의 타입은 인스턴스의 타입과 일치해야 함!
class Tv {
String cololr;
boolean power;
int channel;
void power() {power = !power;}
void channelUp() {++channel;}
void channelDown() {--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("t1의 channel값은 " + t2.channel + "입니다");
t2 = t1;
t1.channel = 7;
System.out.println("t1의 channel값은 " + t1.channel + "입니다");
System.out.println("t1의 channel값은 " + t2.channel + "입니다");
}
}
- t2 = t1; 에서 참조변수 t2는 t1의 인스턴스의 주소까지 가르키게 되기 때문에 t2 인스턴스는 참조되는 곳이 하나도 없어짐 → 더이상 사용될 수 없는 인스턴스 ⇒ 가비지 콜렉터에 의해 자동적으로 메모리에서 제거
- 참조변수에는 하나의 값만 저장 가능 → 여러개의 인스턴스를 가르킬 수는 없음
객체배열
- 많은 수의 객체를 다뤄야할 때 객체를 배열로 다룰 수 있음
- 객체 배열은 참조 변수들을 하나로 묶은 참주 변수 배열
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() };
// 배열이 많을때는 for문을 사용할수도 있음
Tv[] tvArr = new Tv[100];
for(int i=0, i<tvArr.length;i++) {
tvArr[i] = new Tv();
}
- 객체 배열은 같은 타입의 객체만 저장 가능
프로그래밍 관점에서의 클래스의 정의와 의미
- 객체지향적 관점의 클래스: 객체를 생성하기 위한 틀, 속성과 기능으로 정의됨
1. 데이터와 함수의 결합
- 변수: 하나의 데이터를 저장
- 배열: 같은 종류의 여러 데이터를 하나의 집합으로 저장
- 구조체: 여러 종류의 여러 데이터를 하나의 집합으로 저장
- 클래스: 데이터와 함수의 결합(구조체에 함수가 더해짐)
- 예시로 자바에서는 문자열을 String이라는 클래스로 다룸
- → 문자열 + 문자열을 다루는데 필요한 함수(문자열 뽑아내기, 문자열 길이 등)를 함께 묶기 위해서
- 변수와 함수가 유기적으로 연결되어 작업이 간단하고 명료해짐
2. 사용자 정의 타입(user-defined type)
언어에서 기본으로 제공하는 자료형 외에 프로그래머가 변수들을 묶어서 하나의 타입으로 새로 추가하는 타입
자바에서는 클래스가 곧 사용자 정의 타입
ex. 여러개의 시간을 다뤄야할 경우
- 위와 같은 코드의 한계
- 다루는 시간이 추가될때마다 변수를 하나씩 추가해야함 ⇒ 배열로 해결 가능
int[] hour = new int[3]; int[] minute = new int[3]; float[] second = new float[3];
- 하지만 배열로 다루어도 시, 분, 초가 따로 존재하기 때문에 올바르지 않은 데이터가 될 가능성이 있음
- ⇒ 클래스로 정의가 필요
class Time { int hour; int minute; int second; }
시, 분, 초가 하나의 단위로 묶임
but 제약조건이 필요(시: 0
24, 분&초: 060) → 제어자와 메소드로 쉽게 반영이 가능- 제어자로 변수의 값을 직접 변경하지 못하도록 하고, 메소드로만 변경할 수 있도록 함
- 메소드로 변경시 유효한 값인지 유효성을 검사
public class Time { private int hour; private int minute; private int second; public int getHour() { return hour; } public int getMinute() {return minute; } public int getSecond() { return second; } public void setHour(int h) { if (h<0 || h>23) return; hour = h; } public void setMinute(int m) { if (m<0 || m>59) return; minute = m; } public void setSecond(float s) { if (s<0.0f || s>59.99f) return; second = s; }
- 위와 같은 코드의 한계
3. 변수와 메서드
선언 위치에 따른 변수의 종류
- 변수의 종류를 결정 짓는 요소: 변수의 선언된 위치
- 멤버변수
- 선언 위치: 클래스 영역
- static이 붙음: 클래스변수
- static이 붙지 않음: 인스턴스 변수
- 지역변수: 멤버변수를 제외한 나머지 변수
- 선언 위치: 클래스 영역 이외의 영역(메서드, 생성자, 초기화 블럭 내부)
- 멤버변수
class Variables {
int iv; // 멤버변수, 인스턴스 변수
static int cv; // 멤버변수, 클래스 변수
void method()
{
int lv = 0; // 지역변수
}
}
인스턴스 변수(instance variable)
- 선언 위치: 클래스 영역
- 생성 시기: 클래스의 인스턴스 생성시
- 독립적인 저장 공간 소유 → 서로 다른 값을 가질 수 있음
- 인스턴스마다 다른 값을 사용해야 하는 경우
클래스 변수(class variable)
- 선언 위치: 클래스 영역, 인스턴스 변수 앞에 static을 붙임
- 생성 시기: 클래스가 메모리에 올라갈때
- 모든 인스턴스가 공통된 저장공간을 공유
- 모든 인스턴스가 동일한 값을 공유해야하는 경우
- 인스턴스를 생성하지 않고 바로 사용할 수 있음 (
클래스명.변수명
) - 프로그램이 종료될때까지 유지됨
- public을 붙이면 프로그램 어디서나 접근 가능한 전역 변수로 사용됨
지역 변수(local variable)
- 메서드 내에서 선언, 메서드 내에서만 사용 가능
- 생성 시기: 변수 선언문이 수행되었을때
- 메서드 종료시 소멸됨
- 블럭(
{}
) 내에서 선언된 경우에는 블록 내에서만 사용 가능, 블록을 벗어날 시 소멸됨
클래스 변수와 인스턴스 변수
카드 클래스를 생성한다고 할때 공통적인 속성인 폭, 높이는 클래스변수, 카드의 개별적인 속성인 무늬, 숫자는 인스턴스 변수가 되어야 함
class CardTest{ public static void main(String args[]) { System.out.println("Card.width = " + Card.width); System.out.println("Card.height = " + Card.height); Card c1 = new Card(); c1.kind = "Heart"; c1.number = 7; Card c2 = new Card(); c2.kind = "Spade"; c2.number = 4; System.out.println("c1은 " + c1.kind + ", " + c1.number + "이며, 크기는 (" + c1.width + ", "+ c1.height + ")"); System.out.println("c2은 " + c1.kind + ", " + c1.number + "이며, 크기는 (" + c1.width + ", "+ c1.height + ")"); System.out.println("c1의 width, height를 각각 50, 80으로 변경합니다."); c1.width = 50; c1.height = 80; System.out.println("c1은 " + c1.kind + ", " + c1.number + "이며, 크기는 (" + c1.width + ", "+ c1.height + ")"); System.out.println("c1은 " + c1.kind + ", " + c1.number + "이며, 크기는 (" + c1.width + ", "+ c1.height + ")"); } } class Card { String kind; int number; static int width = 100; static int height = 250; }
- 클래스 변수를 사용할때
참조변수.클래스변수
로도 사용할 수 있지만 클래스 변수를 인스턴스 변수로 착각하기 쉬우므로클래스이름.클래스변수로 사용
을 추천
- 클래스 변수를 사용할때
메서드
- 특정 작업을 하는 일련의 문장들을 하나로 묶은 것
- 특정 입력값을 넣으면 작업을 수행 후 결과를 반환
- 입력과 출력만 알고 구체적인 작업 수행 과정은 알지 않아도 됨
메서드를 사용하는 이유
높은 재사용성(Reusability)
- 한번 메서드를 만들어두면 여러번 호출이 가능하며 다른 프로그램에서도 호출하여 재사용이 가능
중복된 코드의 제거
같은 내용의 문장을 메서드를 호출하는 한 문장으로 대체
전체 소스코드의 길이가 짧아짐
변경사항이 발생했을때 수정할 코드가 줄어듦 → 관리 용이, 오류 발생 가능성도 낮아짐
class MethodExam { static void printArr(int[] numArr){ for (int i=0;i<10;i++) { System.out.printf("%d", numArr[i]); System.out.println(); } } public static void main(String args[]) { int[] numArr = new int[10]; for (int i=0;i<10;i++) numArr[i] = (int) (Math.random()*10); // for (int i=0;i<10;i++) { // System.out.printf("%d", numArr[i]); // System.out.println(); // } printArr(numArr); } }
프로그램의 구조화
main안에 모든 문장을 넣는 방식 대신, 작업 단위로 나누어 메서드를 생성하여 프로그램의 구조를 단순화
public static void main(String args[]) { int[] numArr = new int[10]; initArr(numArr); printArr(numArr); sortArr(numArr); printArr(numArr); } }
이처럼 main에서는 전체 흐름을 한눈에 파악할 수 있도록 단순하게 구조화하는 것이 좋다.
- 프로그램에 문제가 발생하면 원인을 빠르게 파악하여 수정이 가능
처음 프로그램 설계시 메소드 별로 작업 단위를 만들어두고 작업을 하는 것도 구조화에 좋은 방법임
static int showMenu() {} static void inputRecord() {} static void changeRecord() {} static void deleteRecord() {} static void searchRecord() {} static void showRecordList() {} public static void main(String args[]){ switch (showMenu()) { case 1: inputRecord(); break; case 2:changeRecord(); break; case 3: deleteRecord(); break; case 4: searchRecord(); break; default: showRecordList(); } }
메서드의 선언부(method declaration, method header)
- 메서드의 선언부:
반환타입 메서드이름 (타입 변수명, 타입 변수명, ...)
int add (int a, int b)
- 선언부에 변경이 생기게되면 호출하는 부분을 모두 수정해야하기 때문에 신중히 작성해야한다.
매개변수 선언
- 메서드가 작업을 수행하는데 필요한 값들
- 두 변수 타입이 같아도 타입은 생략할 수 없다. → [X]
int add (int x, y)
- 입력해야할 값이 많다면 배열이나 참조변수를 이용
매서드의 이름
- 동사 사용
- 이름만 봐도 메서드의 기능을 유추할 수 있도록 함축적이면서 의미있는 이름
반환 타입
- 작업 수행 결과인 반환 값의 타입
- 반환값이 없는 경우 void로 설정
메서드의 구현부(method body)
- 메서드 선언 이후 {} 안에 들어가는 내용
- 메서드를 호출했을때 수행될 문장
return 문
- 메서드의 반환 타입이 void가 아닌 경우에는 무조건 return 문이 들어가있어야 한다.
- return으로 반환되는 값의 타입은 메서드 선언시 지정해주었던 반환 타입과 동일하거나 자동 형변환(작은 타입 → 큰 타입)이 가능해야한다.
- return문은 단 하나의 값만 반환 가능하다.
지역변수(local variables)
- 메서드 내에서 선언된 변수들은 지역변수라고 하며 함수 종료시 소멸되는 변수이기 때문에 서로 다른 메서드에서 동일한 변수명을 사용할 수 있다.
int add(int x, int y){
int result = x + y;
return result;
}
int multifly(int x, int y){
int result = x * y;
return result;
}
// 두 메소드의 x, y는 각각 이름만 같을 뿐 서로 다른 변수이다.
메서드의 호출
- 메서드를 호출해야만 구현부의 코드들이 실행됨
인자와 매개변수(argument, parameter)
- 인자: 메서드 호출시 괄호안에 지정한 값
- 인자의 개수와 순서는 매개변수와 동일해야 함
- 인자의 타입은 매개변수의 타입과 동일하거나 자동형변환 가능한 타입이어야 함
class MethodExam {
static int add(int x, int y){ // 매개변수(parameter)
int result = x + y;
return result;
}
public static void main(String args[]) {
int result = add(1, 2); // 인자(argument)
}
}
- 매개변수의 타입을 맞춰주지 않으면 다음과 같은 에러가 발생
add(1.0, 2)
호출시java: incompatible types: possible lossy conversion from double to int
- 반환 타입이 void가 아닌 경우에 반환된 값을 변수에 저장하는 것이 보통이지만 저장하지 않아도 된다.
메서드의 실행흐름
- 같은 클래스내의 메서드끼리는 참조변수를 사용하지 않고 바로 호출 가능
- 단, static 메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없음
class MyMathTest{
public static void main(String args[]){
MyMath mm = new MyMath();
long result1 = mm.add(5L, 3L);
// 1. 호출시 지정한 5L, 3L이 메서드의 매개변수에 각각 복사됨
// 2. 메서드 호출부가 실행됨
// 3. 호츨부가 모두 실행되거나 return문을 만나면 호출한 메서드(main)으로 돌아온 후 이후 문장들을 실행
// 호출 결과가 바로 변수에 저장되는 것이 아니라 호출한 자리를 반환 값이 대신한 후 대입 연산자에 의해 변수에 저장됨
long result2 = mm.subtract(5L, 3L);
long result3 = mm.multiply(5L, 3L);
double result4= mm.divide(5L, 3L);
// long 타입을 인자로 넣어도 double형으로 자동형변환(작은타입 -> 큰타입)이 가능하므로 실행 가능
// 연산 결과는 double형으로 나옴
System.out.println("add(5L, 3L) = "+result1);
System.out.println("subtract(5L, 3L) = "+result2);
System.out.println("multiply(5L, 3L) = "+result3);
System.out.println("divide(5L, 3L) = "+result4);
}
}
class MyMath{
long add(long a, long b){
long result = a+b;
return result;
// return a+b 한줄로 가능
}
long subtract(long a, long b) {return a-b;}
long multiply(long a, long b) {return a*b;}
double divide(double a, double b) {return a/b;}
}
- result
add(5L, 3L) = 8 subtract(5L, 3L) = 2 multiply(5L, 3L) = 15 divide(5L, 3L) = 1.6666666666666667
return문
- return문: 현재 실행중인 메소드를 종료하고 호출한 메서드로 되돌아감
- 원래는 반환값의 유무에 관계 없이 메서드에는 return문이 존재해야함
- void인 경우에는 컴파일러에서 자동으로
return;
을 추가하는 것 - void가 아닌 경우, return문이 없으면 컴파일 에러가 발생
- 조건문에 따라 return문이 호출되지 않는 경우가 발생할 수 있으므로 주의
- void인 경우에는 컴파일러에서 자동으로
반환값(return value)
반환값으로 주로 변수가 오지만 수식이 올수도 있음
수식이 아닌, 수식의 결과가 반환됨(
return x+y;
)단, 호출한 메서드의 반환 타입이 호출되는 메서드의 반환타입과 일치해야 함
int diff(int x, int y) { return abs(x-y); }
매개변수의 유효성 검사
구현부 작성시 매개변수의 값이 적절한지를 가장 먼저 판단해야함
호출하는 쪽에서는 어떠한 값이나 넣을 수 있기 때문에 가능한 경우의 수를 고민하고 이에 대비한 코드를 작성해야함
float divide(int x, int y){ if(y==0) { System.out.println("0으로 나눌 수 없습니다.); return 0; } return x/(float)y; }
적절하지 않은 값이 매개변수를 통해 넘어온 경우
- 매개변수를 보정
- 보정이 불가능한 경우라면 return문을 통해 작업을 중단 후 호출한 메서드로 되돌아감
'Java' 카테고리의 다른 글
[Java] Kafka Producer Java Client 실습해보기 (0) | 2023.11.04 |
---|