객체 지향 프로그래밍
- 객체들을 먼저 만들고, 이것들을 하나씩 조립해서 완성된 프로그램을 만드는 기법
- 객체(Object) : 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 가지고 있고, 다른 것과 식별 가능한 것.
- 속성 = 필드(field)
- 동작 = 메소드(method) : 객체들 사이의 상호작용 수단은 메소드
- 객체 모델링(Object Modeling) : 현실 세계의 객체를 소프트웨어 객체로 설계하는 것
- 메소드 호출 : 객체가 다른 객체의 기능을 이용하는 것
- 객체 간의 관계 : 집합 관계, 사용 관계, 상속 관계
객체 지향 프로그래밍의 특징
- 캡슐화(Encapsulation)
- 객체의 필드, 메소드를 하나로 묶고, 실제 구현 내용을 감추는 것
- 외부 객체는 객체의 내부 구조를 알 수 없다.
- 자바 언어는 캡슐화된 멤버를 노출시킬 것인지, 숨길 것인지 접근 제한자(Access Modifier) 사용
- 상속(Inheritance)
- 부모가 가지고 있는 재산을 자식에게 물려주는 것
- 코드의 중복 최소화 가능
- 유지 보수 시간 최소화
- 다형성(Polymorphism)
- 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질
- 자바는 다형성을 위해 부모 클래스 또는 인터페이스의 타입 변환을 허용
- 부모 타입에는 모든 자식 객체 대입 가능
- 인터페이스 타입에는 모든 구현 객체가 대입 가능
- 다형성으로 인해 객체는 부품화가 가능
객체와 클래스
- 현실에서 객체는 설계도를 바탕으로 만들어진다.
- 자바에서의 설계도는 클래스(class)이다.
- 클래스(class)
- 객체를 생성하기 위한 필드와 메소드가 정의되어 있다.
- 클래스로부터 만들어진 객체를 해당 클래스의 인스턴스(instance)라고 한다.
- 인스턴스화 : 클래스로부터 객체를 만드는 과정
- 객체지향 프로그래밍 개발 3단계
- 클래스 설계
- 설계된 클래스를 가지고 사용할 객체 생성
- 생성된 객체 사용
클래스 선언
- 클래스 이름은 자바의 식별자 작성 규칙을 따른다.
- 일반적으로 소스 파일당 동일한 이름의 하나의 클래스를 선언한다.
- 두 개 이상도 가능하지만, 컴파일을 하면 바이트 코드 파일은 클래스를 선언한 개수만큼 생기기 때문에, 파일 이름과 일치하지 않는 클래스 선언에 public 접근 제한자를 붙이면 컴파일 에러가 발생한다.
객체 생성과 클래스 변수
- new 연산자
- 클래스로부터 객체를 생성하는 방법.
- 연산자 뒤에 생성자가 온다.
- new 연산자로 생성된 객체는 메모리 힙(heap) 영역에 생성된다.
- 힙 영역에 객체를 생성시킨 후, 객체의 주소를 리턴한다.
클래스 변수 = new 클래스();
- 클래스의 2가지 용도
- 라이브러리(API)용 : 다른 클래스에서 이용할 목적으로 설계
- 실행용 : main() 메소드를 제공하는 역할
- 프로그램을 하나의 클래스로 구성할 수 있지만, 객체 지향 프로그램은 대부분 라이브러리와 실행 클래스가 분리되어 있다.
public class studnet { // 라이브러리 클래스
}
public class StudentExample { // 실행 클래스
public static void main(String[] args){
...
}
}
클래스의 구성 멤버
- 필드(Field)
- 객체의 데이터가 저장되는 곳
- 변수와 비슷하지만. 변수는 생성자와 메소드 내에서만 사용되고 생성자는 메소드가 실행 종료되면 자동 소멸한다. 하지만 필드는 메소드 전체에서 사용되고, 객체가 소멸되지 않으면 객체와 함께 존재한다.
- 생성자(Constructor)
- 객체 생성 시 초기화 역할 담당
- 생성자는 메소드와 비슷하지만, 클래스 이름으로 되어 있고, 리턴 타입이 없다.
- 생성자가 성공적으로 실행되면 힙(heap) 영역에 객체가 생성되고 객체의 주소가 리턴된다.
- 모든 클래스는 생성자가 반드시 존재한다.
- 기본 생성자
- 중괄호 블록 내용이 비어있는 기본 생성자. 클래스 내부에 생성자 선언을 생략하면 컴파일러가 자동으로 추가한다.
- 클래스에 생성자를 선언하지 않아도 new 연산자 뒤에 기본 생성자를 호출해서 객체 생성이 가능하다.
- 기본 생성자
- 메소드(Method)
- 객체의 동작에 해당하는 실행 블록
- 객체 간의 데이터 전달 수단
public class ClassName{
int fieldName; // 필드
ClassName() {...} // 생성자
void methodName() {...} // 메소드
}
외부 클래스에서 Car 필드 값 읽기와 변경
public class Car {
// 필드
String company = "현대자동차";
String model = "그랜저";
String color = "검정";
int maxSpeed = 350;
int speed;
}
public class CarExample {
public static void main(String[] args){
// 객체 생성
// 다른 클래스의 필드 값을 사용하기 위해서는 다른 클래스의 객체를 생성해야한다.
Car myCar = new Car();
// 필드 값 읽기
System.out.println("제작회사 " + myCar.company);
System.out.println("모델명 " + myCar.model);
System.out.println("색깔 " + myCar.color);
System.out.println("최고속도 " + myCar.maxSpeed);
System.out.println("현재속도 " + myCar.speed);
// 필드 값 변경
myCar.speed = 60;
System.out.println("수정된 속도 " + myCar.speed);
}
}
기본 생성자 대신 명시적 생성자 선언
- 명시적 생성자의 매개 변수는 new 연산자로 생성자를 호출할 때 외부의 값을 생성자 블록 내부로 전달한다.
public class Car {
// 생성자
Car(String color, int cc) {
}
}
public class CarExample {
public static void main(String[] args){
Car myCar = new Car("검정", 3000);
// Car myCar new Car();
// 생성자가 명시적으로 선언되어 있을 경우 기본 생성자 호출이 불가능
}
}
필드 초기화
- 클래스로부터 객체가 생성될 때, 필드는 기본 초기 값으로 자동 설정
- 다른 값으로 초기화하는 2가지 방법
- 필드를 선언할 때 초기값을 준다.
- 생성자에서 초기값을 준다. (객체 생성 시점에 외부에서 제공되는 다양한 값들로 초기화될 경우)
public class Korean {
// 필드
String nation = "대한민국"; // 필드에서 초기화
String name;
String ssn;
// 생성자
// 생성자에서 초기화
public Korean(String n, String s) {
name = n;
ssn = s;
}
}
- 생성자의 매개 변수 이름은 필드와 동일한 이름을 갖는 매개 변수를 사용한다. 그렇게 되면 필드와 매개 변수 이름이 동일하기 때문에 생성자 내부에서 해당 필드에 접근이 불가능하다. (매개 변수가 사용 우선순위가 높기 때문)
- 해결 방법 : 필드 앞에 this를 붙인다.
public Korean(String name, String ssn) {
this.name = name; // this.필드 = 매개변수
this.ssn = ssn;
}
}
생성자 오버로딩(Overloading)
- 매개 변수를 달리하는 생성자를 여러 개 선언하는 것
- 다양한 방법으로 객체를 생성할 수 있도록 사용한다.
- 매개 변수의 타입, 개수, 순서를 다르게 선언한다.
public class Car {
Car(){}
Car(String model) {}
Car(String model, String color) {}
}
- 매개 변수의 이름만 바꾸는 것은 생성자 오버로딩이 아니다.
Car(String color, String model) {}
Car(String model, String color) {}
public class Car {
// 필드
String company = "현대자동차";
String model;
String color;
int maxSpeed;
// 생성자
Car(){
}
Car(String model) {
this(model, "은색", 250); // 맨 아래 생성자 호출
}
Car(String model, String color) {
this(model, color, 250); // 맨 아래 생성자 호출
}
Car(String model, String color, int maxSpeed){
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
}
메소드
- 메소드 : 객체의 동작에 해당하는 중괄호 블록
- 리턴타입, 메소드이름, 매개변수선언
- 시그너처(signature) : 메소드 선언부
- 메소드 이름은 자바 식별자 규칙에 맞게 사용한다.
- 소문자로 작성, 단어의 첫머리는 대문자
public class Caculator {
// 메소드
void powerOn(){
System.out.println("전원을 켭니다.");
}
int plus(int x, int y){
int result = x + y;
return result;
}
}
public class CaculratorExample {
public static void main(String[] args){
Caculator myCalc = new Caculator();
myCalc.powerOn(); // 메소드 호출
int result1 = myCalc.plus(5, 6);
System.out.println(result1);
}
}
- 매개 변수의 수를 모를 경우 : 매개 변수를 배열 타입으로 선언한다.
public class Computer {
// 첫번째 방법 생성자
int sum1(int[] values){
int sum = 0;
for(int i=0; i<values.length; i++){
sum += values[i];
}
return sum;
}
// 두번째 방법 생성자
int sum2(int ... values) {
int sum = 0;
for (int i = 0; i < values.length; i++) {
sum += values[i];
}
return sum;
}
}
public class ComputerExample {
public static void main(String[] args){
Computer myCom = new Computer(); // 객체 생성
int[] values1 = {1, 2, 3};
int result1 = myCom.sum1(values1);
System.out.println(result1);
int result2 = myCom.sum1(new int[] {1,2,3,4,5});
System.out.println(result2);
int result3 = myCom.sum2(1, 2, 3);
System.out.println(result3);
}
}
메소드 오버로딩(overloading)
- 클래스 내부에 같은 이름의 메소드를 여러 개 선언하는 것
- 매개 값을 다양하게 받아 처리할 수 있도록 사용한다.
- 매개 변수의 타입, 개수, 순서 중 하나가 달라야 한다.
인스턴스 멤버와 this
- 인스턴스 멤버
- 객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드
- 객체 없이는 사용 불가능
- 인스턴스 필드, 인스턴스 메소드
- this
- 객체 내부에서도 인스턴스 멤버에 접근하기 위해 this를 사용한다.
Car(String model) {
this(model, "은색", 250);
}
void setModel(String model){
this.model = model;
}
정적 멤버와 static
- 정적 멤버(클래스 멤버)
- 클래스에 고정된 멤버
- 객체를 생성하지 않고 사용할 수 있는 필드와 메소드
- 정적 필드, 정적 메소드
- 클래스 로더가 클래스(바이트 코드)를 로딩해서 메소드 메모리 영역에 적재할 때 클래스별로 관리된다.
- 클래스 이름으로 접근할 수 있다. 객체 참조 변수로도 가능하지만, 사용 시 경고 표시가 나타난다.
- 정적 필드는 필드 선언과 동시에 초기 값을 주는 것이 보통이지만, 계산이 필요한 경우 정적 블록을 사용한다.
public class Television {
static String company = "Samsung"; // 보통 필드에서 초기값 결정
static String model = "LCD";
static String info;
static { // 정적 블록 사용
info = company + "_" + model;
}
}
- 정적 블록과 메소드에서 인스턴스 필드와 메소드를 사용할 수 없다.
- 해결 방법은 객체를 먼저 생성(new 연산자 사용)하고 참조 변수로 접근해야 한다.
public class ClassName {
// 인스턴스 필드와 메소드
int field1;
void method1() {}
// 정적 필드와 메소드
static int field2;
static void method2() {}
// 정적 블록
static {
field1 = 10; // 컴파일 에러
method1(); // 컴파일 에러
field2 = 10;
method2();
}
// 정적 메소드
static void Method3 {
this.field1 = 10; // 컴파일 에러
this.method1(); // 컴파일 에러
field2 = 10;
method2();
}
}
싱글톤(Singleton)
- 단 하나의 객체만 만드는 객체를 싱글톤이라고 한다.
- 클래스 외부에서 new 연산자로 생성자를 호출할 수 없도록 해야 한다.
- 호출할 수 없도록 생성자 앞에 private 접근 제한자를 붙여준다.
- 클래스 내부에서 new 연산자로 생성자 호출이 가능하기 때문에, 정적 필드도 private 접근 제한자를 붙인다.
- 외부에서 호출할 수 있도록, 정적 메소드인 getInstance()를 선언하고, 정적 필드에서 참조하고 있는 자신의 객체를 리턴해준다.
public class Singleton {
// private + 정적 필드
private static Singleton singleton = new Singleton();
// private + 생성자
private Singleton(){}
// private + 정적 메소드
static Singleton getInstance(){
return singleton;
}
}
// 외부에서 객체를 얻는 유일한 방법
public class SingleExample(){
public static void main(String[] args){
Singleton obj = Singleton.getInstance();
}
}
final 필드와 상수
- final 필드
- 초기 값이 저장되면 이것이 최종적인 값이 되어서 프로그램 실행 도중에 수정할 수 없다.
- final 필드 초기값 주는 2가지 방법
- 필드 선언 시에 준다.
- 생성자에서 준다.
public class Person {
final String nation = "Korea"; // 수정 불가
final String ssn;
String name;
public Person(String ssn, String name){
this.ssn = ssn;
this.name = name;
}
}
- 상수(static final)
- 변하지 않는 값
- final 필드는 객체마다 저장되고, 생성자의 매개값을 통해서 여러 가지 값을 가질 수 있기 때문에 상수가 될 수 없다.
- 상수는 static + final
- 상수 이름은 모두 대문자
public class Earth {
static final double EARTH_RADIUS = 6400;
static final double EARTH_SURFACE_AREA;
static {
EARTH_SURFACE_AREA = 4 * Math.PI * EARTH_RADIUS * EARTH_RADIUS;
}
}
패키지(Package)
- 클래스를 체계적으로 관리하기 위해서 사용
- 패키지의 물리적 형태는 파일 시스템의 폴더
- 클래스를 식별해준다.
- 상위패키지.하위패키지.클래스
- package 상위패키지.하위패키지;
- 모두 소문자로 작성
- 중복되지 않도록 보통 회사의 도메인 이름으로 패키지 작성
- 패키지 폴더를 자동으로 생성하려면 javac 명령어 다음 -d 옵션을 추가해 패키지가 생성될 경로를 지정해줘야한다.
- javac -d 경로
import문
- 다른 패키지에 속하는 클래스를 사용할 수 있는 방법
- * 은 패키지에 속하는 모든 클래스
- import 문으로 지정된 패키지의 하위 패키지는 import 대상이 아니다.
접근 제한자(Access Modifier)
- 객체 생성을 막기 위해 생성자를 호출하지 못하게 하거나 객체의 특정 데이터를 보호하기 위해서 해당 필드에 접근하지 못하도록 막아준다.
- public, protected, default, private
- 클래스에 적용할 수 있는 접근 제한 : public, default
Getter와 Setter 메소드
- 객체 지향 프로그래밍에서는 객체의 무결성을 유지하기 위해, 외부의 직접적 접근을 막는다.
- 메소드를 통해 데이터를 변경하는 방법을 선호한다.
- Setter : 검증을 통해 유효한 값만 데이터로 저장 가능 (ex. 음수 검증)
- Getter : 객체 외부에서 객체 필드값을 사용하기 부적절한 경우, 메소드에서 필드 값 가공 후 외부로 전달
private 타입 fieldName;
// Getter
public 리턴타입 getFieldName(){
return fieldName;
}
// Setter
public void setFieldName(타입 fieldName){
this.fieldName = fieldName;
}
어노테이션(Annotation)
- 메타데이터(metadata)라고 볼 수 있다.
- 애플리케이션에서 처리해야 하는 데이터가 아니라, 컴파일 과정과 실행 과정에서 코드를 어떻게 컴파일하고 처리할 것인지 알려주는 정보
- 어노테이션의 3가지 용도
- 컴파일러에게 코드 문법 에러를 체크하도록 정보 제공
- 대표적인 예시 : @Override (메소드가 오버라이드된 것인지 컴파일러에게 알려주고, 제대로 되지 않았으면 에러 발생)
- 소프트웨어 개발 툴이 빌드나 배치 시 코드를 자동으로 생성할 수 있도록 정보 제공
- 실행 시 특정 기능을 실행하도록 정보 제공
- 컴파일러에게 코드 문법 에러를 체크하도록 정보 제공
- 어노테이션 타입 정의 방법
- public @interface AnnotationName {}
- 엘리먼트(element)를 멤버로 가질 수 있다.
- 타입 element() [default 값];
- 엘리먼트 타입 : int, double, String, 열거, Class 타입 등등
- 어노테이션 타입 적용 방법
- @AnnotationName
- 어노테이션 적용 대상 : java.lang.annotation.ElementType 열거 상수로 정의
- TYPE, ANNOTATION_TYPE, FIELD, CONSTRUCTOR, METHOD, LOCAL_VARIABLE, PACKAGE
- @Target 사용
- 어노테이션 유지 정책 : java.lang.annotation.RetentionPolicy 열거 상수로 정의
- SOURCE, CLASS, RUNTIME
- @Retention 사용
- 리플렉션(Reflection) : 런타임 시에 클래스의 메타 정보를 얻는 기능
- java.lang.refelct
'프로그래밍 > JAVA' 카테고리의 다른 글
[자바의 정석] 연습문제 1.변수와 타입 (0) | 2022.06.23 |
---|---|
[JAVA] 자바 메모리 구조 - Heap, Stack, JVM, GC (0) | 2022.06.23 |
Ch05. 참조 타입 (0) | 2022.01.01 |
Ch04. 조건문과 반복문 (0) | 2022.01.01 |
Ch03. 연산자 (0) | 2022.01.01 |