1. 객체 지향 프로그래밍 (OOP : Object Oriented Programming) : 객체들을 생성하고 조립하여 프로그램을 만드는 기법
객체 (Object) : 필드와 메소드로 구성되어 있으며, 자신만의 속성이 있어 다른 것과 식별 가능한 것
메소드 (Method) : 객체들 사이의 상호작용 수단
- 객체에 도트 (.) 연산자를 붙이고 메소드 이름을 기술 / 도트 연산자는 객체의 필도와 메소드에 접근할 때 사용
리턴값 = 객체.메소드(매개값 1, 매개값 2, ...);
객체 간 관계
- 집합 관계 : 집합 관계에 있는 객체들 중 하나는 부품, 하나는 완성품
- 사용 관계 : 객체 간의 상호작용
- 상속 관계 : 부모 객체를 기반으로 자식 객체를 생성하는 관계 / 보통 부모 객체는 종류, 자식 객체는 구체적인 사물에 해당
특징
- 캡슐화 : 접근 제한자 (Access Modifier)로 객체의 사용 범위를 제한해 객체의 필드, 메소드를 하나로 묶고, 실제 구현 내용을 감추는 것 / 외부 객체는 객체 내부의 구조를 알 수 없어 객체가 노출해서 제공하는 필드와 메소드만 사용 가능 / 외부의 잘못된 사용으로 인한 객체 손상을 방지하는데 사용됨
- 상속 : 부모 객체가 가지고 있는 필드와 메소드를 자식 객체에게 물려줘 자식 객체가 사용할 수 있도록 하는 기능 / 부모 객체를 재사용해서 자식 객체를 쉽고 빠르게 설계할 수 있도록 돕고 반복되는 코드의 중복을 줄여주며 유지 보수 시간을 최소화 시킴
- 다형성 : 하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 이용할 수 있게 함 / 부모 클래스 또는 인터페이스의 타입 변환을 허용 / 부모 타입에 모든 자식 객체가 대입될 수 있음 / 인터페이스 타입에는 모든 구현 객체가 대입될 수 있음
2. 객체와 클래스
- 객체를 생성하기 위한 필드와 메소드는 클래스로 작성
- 인스턴스 : 클래스로부터 생성되는 객체 / 객체를 생성하는 과정 = 인스턴스화
3. 클래스 선언
- 클래스 이름은 객체의 대표 이름
- 영어 대소문자를 구분
- 각 단어의 첫 머리 글자는 대문자로 작성하는 것이 관례
- 소스 파일당 하나의 클래스를 선언 / 두 개 이상의 클래스가 선언된 소스 파일을 컴파일 하면 바이트 코드 파일이 클래스 개수만큼 생성됨 (파일 이름과 동일한 이름의 클래스 선언에만 public 접근 제한자를 붙일 수 있음)
| 클래스 이름 작성 규칙 |
| 하나 이상의 문자로 이루어져야 함 |
| 첫 번째 글자는 숫자 사용 불가능 |
| '$', '_' 외의 특수 문자 사용 불가능 |
| 자바 키워드 사용 불가능 |
public class Car {
}
class Tire {
}
4. 객체 생성과 클래스 변수
- new 연산자 : 클래스로부터 객체를 생성시킴 / new 연산자 + 클래스() 와 같이 생성자가 붙음 / new 연산자로 생성된 객체는 메모리 힙 영역에 생성되고 객체의 주소를 리턴
- 객체의 주소가 저장된 변수를 통해 객체를 사용
- 객체 클래스는 하나지만 new 연산자를 사용한 만큼 객체가 메모리에 생성됨
public class Example {
public static void main (String[] args) {
Car car = new Car();
System.out.println("car 변수가 Car 객체를 참조");
}
}
- 클래스 : 라이브러리 (API : Application Program Interface)와 실행용 클래스로 나뉘는데 라이브러리 클래스는 다른 클래스에서 이용할 목적으로 설계되어 실제 프로그램은 한개의 main() 메소드를 포함한 실행 클래스와 나머지 라이브러리 클래스로 구성됨 / 라이브러리인 동시에 실행 클래스로 프로그램을 구성할 수 있음
클래스는 필드, 생성자, 메소드로 구성됨
5. 필드 (멤버 변수) : 객체의 고유 데이터, 부품 객체, 상태 정보를 저장
- 생성자와 메소드 전체에서 사용되며 객체가 소멸되지 않는 한 객체와 함께 존재
필드 선언 : 생성자와 메소드 중괄호 블록 내부를 제외한 클래스 중괄호 블록 어디서든 선언 가능
타입 필드 [= 초기값];
- 타입에는 기본 타입, 참조 타입 둘 다 가능
- 필드 선언 시 초기값 생략 가능하고 초기값이 없는 필드들은 자동으로 기본 초기값으로 설정됨
필드 사용 : 필드값을 읽고, 변경하는 작업
- 클래스 외부에서 필드를 사용할 경우 먼저 클래스로부터 객체를 생성한 뒤 사용 가능
- 도트 (.) 연산자를 사용하여 객체가 가지고 있는 필드나 메소드를 사용 가능
6. 생성자 : 객체 생성 시 초기화를 담당
- new 연산자로 호출되는 특별한 중괄호 {} 블록
- 필드를 초기화하거나, 메소드를 호출해서 객체를 사용할 준비를 함
- 메소드와 비슷하게 생겼지만, 클래스 이름으로 되어 있고 리턴 타입이 존재하지 않음
- 생성자를 실행시키지 않으면 클래스로부터 객체 생성 불가능
기본 생성자
- 모든 클래스는 하나 이상의 생성자가 반드시 존재 / 생략하면 컴파일러가 중괄호 블록 내용이 빈 기본 생성자를 바이트 코드에 추가함
- 접근 제한자가 없는 클래스는 기본 생성자에도 접근 제한자가 붙지 않음
- 클래스에서 명시적으로 선언한 생성자가 하나라도 존재하면 컴파일러는 기본 생성자를 추가하지 않음
생성자 선언
- 생성자를 명시적으로 선언하여 객체를 다양하게 초기화 가능
- 리턴 타입이 없고 클래스 이름과 동일하며 블록 내부에 객체 초기화 코드가 작성됨
- 일반적으로 필드에 초기값을 저장하거나 메소드를 호출하여 객체 사용 전에 필요한 준비를 함
- 클래스에 생성자가 명시적으로 선언되어 있으면 선언된 생성자로 객체를 생성해야 함
- 매개 변수 선언은 생략할 수도 있고 여러 개를 선언해도 됨
필드 초기화
- 클래스로부터 객체가 생성될 때 필드는 기본 초기값으로 자동 설정되는데, 다른 값으로 초기화하려면 필드 선언 시 초기값을 주거나 생성자에서 초기값을 줘야 함
- 필드 선언 시 초기값을 주면 동일 클래스로부터 생성되는 객체들은 해당 필드에 대하여 동일한 값을 가짐
- 관례적으로 필드와 동일한 이름을 갖는 매개 변수를 사용하는데 이렇게 사용하면 필드와 매개 변수의 이름이 동일하기 때문에 해당 필드에 접근하기 위해서는 "this.필드"를 사용해야 함
- 필드가 많을 땐, 중요한 몇 개의 필드만 매개 변수를 통해 초기화하고 나머지 필드들은 필드 선언 시 초기화 하거나 생성자 내부에서 임의의 값 또는 계산된 값으로 초기화하고 또는 객체 생성 후 필드값을 별도로 저장함
public class Person {
// 필드
String nation = "대한민국";
String name;
String ssn;
// 생성자
public Person(String name, String ssn) {
this.name = name;
this.ssn = ssn;
}
}
생성자 오버로딩 (Overloading) : 매개 변수를 달리하는 생성자를 여러 개 선언하는 것
- 매개 변수의 타입과 개수, 선언된 순서가 똑같을 경우 매개 변수 이름만 바꾸는 것은 생성자 오버로딩이 아님
- 생성자가 오버로딩되어 있을 경우, new 연산자로 생상자를 호출할 때 제공되는 매개값의 타입과 수에 의해 호출될 생성자가 결정됨
다른 생성자 호출 (this())
- 생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드가 발생할 수 있는데, 필드 초기화 내용은 한 생성자에만 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법으로 개선할 수 있음
- this() : 자신의 다른 생성자를 호출하는 코드로 반드시 생성자의 첫줄에서만 사용 가능 / 매개값은 호출되는 생성자의 매개 변수 타입에 맞게 제공해야 함
public class Person {
// 필드
String name;
String sex;
String nation;
// 생성자
Person() {
}
Person(String name) {
this(name, "man", "대한민국");
}
Person(String name, String sex) {
this(name, sex, "대한민국");
}
Person(String name, String sex, String nation) {
this(name, sex, nation);
}
}
7. 메소드 : 객체의 동장을 담당
- 필드를 읽고 수정하는 역할
- 다른 객체를 생성해서 다양한 기능을 수행
- 객체 간의 데이터 전달의 수단으로 사용 / 외부로부터 매개값을 받을 수도 있고 실행 후 어떤 값을 리턴할 수도 있음
- 메소드를 호출하면 중괄호 블록에 있는 모든 코드들이 일괄적으로 실행됨
메소드 선언
리턴타입 메소드이름 ([메개변수선언, ...]) { // 메소드 선언부 (메소드 시그니처)
실행 코드 // 실행 블록
}
- 리턴 타입 : 메소드가 실행 후 리턴하는 값 / 리턴값이 없으면 리턴 타입은 void / 리턴값의 유무에 따라 메소드 호출 방법이 다른데, 리턴 타입이 있다고 해서 반드시 리턴값을 변수에 저장할 필요는 없음
- 메소드 이름
| 메소드 이름 작성 규칙 |
| 숫자로 시작하면 안되고, '$'와 '_'를 제외한 특수 문자를 사용하면 안됨 |
| 소문자로 작성 |
| 단어들이 혼합된 이름이라면 뒤이어 오는 단어의 첫머리 글자는 대문자 |
- 매개 변수 선언 : 메소드를 실행할 때 필요한 데이터를 외부로부터 받기 위해 사용됨 / 매개 변수는 생략 가능하며, 매개값은 반드시 매개 변수의 타입에 부합되는 값이어야 함
- 매개 변수의 수를 모를 경우에는 매개 변수를 배열 타입으로 선언해야 함
- 메소드 호출 전 배열 생성이 불편하다면 매개 변수 "..."를 사용해서 배열을 생성하지 않고 값의 리스트만 넘겨줄 수 있음 / 넘겨준 값의 수에 따라 자동으로 배열이 생성되고 매개값으로 사용됨 / 배열을 매개값으로 사용해도 됨
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 Example {
public static void main(String[] args) {
Computer myCom = new Computer;
int[] values1 = {1, 2, 3};
int result1 = myCom.sum1(values1);
System.out.println("result1 : " + result1); // result1 : 6
int result2 = myCom.sum1(new int[] {1, 2, 3, 4, 5});
System.out.println("result2 : " + result2); // result2 : 15
int result3 = myCom.sum2(1, 2, 3);
System.out.println("result3 : " + result3); // result3 : 6
int result4 = myCom.sum2(new int[] {1, 2, 3, 4, 5});
System.out.println("result4 : " + result4); // result4 : 15
}
}
리턴문
- 리턴값이 있는 메소드 : 메소드 선언에 리턴 타입이 있는 메소드는 반드시 리턴문을 사용해서 리턴값을 지정해야 함 / 리턴문의 리턴값은 리턴 타입이거나 리턴 타입으로 변환될 수 있어야 함 / 리턴문 이후 실행문이 오면 "Unreachable code" 오류 발생
- 리턴값이 없는 메소드 : void로 선언된 리턴값이 없는 메소드에도 리턴문 사용 가능 > 메소드 실행 강제 종료
return;
메소드 호출
- 메소드는 클래스 내, 외부의 호출에 의해 실행
- 객체 내부에서 호출 : 리턴값을 받고 싶다면, 리턴값을 받을 변수의 타입은 메소드 리턴 타입과 동일하거나, 타입 변환 될 수 있어야 함
메소드(매개값, ...);
- 객체 외부에서 호출 : 클래스 외부에서 호출할 경우에는 객체가 존재해야 메소드가 존재할 수 있기 때문에 클래스로부터 객체를 생성한 뒤, 참조 변수와 도트 연산자를 이용해 메소드를 호출해야 함
메소드 오버로딩
- 클래스 내에 같은 이름의 메소드를 여러 개 선언하면 매개값을 다양하게 받아 처리할 수 있음
- 조건 : 매개 변수이 타입, 개수, 순서 중 하나가 달라야 함
- 오버로딩된 메소드를 호출하면 JVM은 매개값의 타입을 보고 메소드를 선택함
- 매개 변수의 타입이 일치하지 않을 경우, 자동 타입 변환이 가능한지 검사
public class Calculator {
// 정사각형 넓이
double areaRectangle(double width) {
return width * width;
}
// 직사각형 넓이
double areaRectangle(double width, double height) {
return width * height;
}
}
public class Example {
public static void main(String[] args) {
Calculator myCalcu = new Calculator();
// 정사각형 넓이 구하기
double result1 = myCalcu.areaRectangle(10);
System.out.println("정사각형 넓이 = " + result1); // 정사각형 넓이 = 100.0
// 직사각형 넓이 구하기
double result2 = myCalcu.areaRectangle(10, 20);
System.out.println("직사각형 넓이 = " + result2); // 직사각형 넓이 = 200.0
}
}
8. 인스턴스 멤버와 this
- 객체를 생성한 후 사용할 수 있는 필드와 메소드 / 각각 인스턴스 필드, 인스턴스 메소드
- 객체 외부에서 인스턴스 멤버에 접근하기 위해 참조 변수를 사용하는 것과 마찬가지로 객체 내부에서도 인스턴스 멤버에 접근하기 위해 this를 사용
- 주로 생성자와 메소드의 매개 변수 이름이 필드와 동일한 경우, 인스턴스 멤버인 필드임을 명시하고자 할 때 사용
9. 정적 멤버와 static
- 정적 멤버 : 클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드와 메소드 / 각각 정적 필드 정적 메소드 / 클래스 멤버
정적 멤버 선언
public class 클래스 {
// 정적 필드
static 타입 필드 [= 초기값];
// 정적 메소드
static 리턴타입 메소드(매개변수, ...) {
실행문;
}
}
- 클래스 로더가 클래스 바이트 코드를 로딩해서 메소드 메모리 영역에 적재할 때 클래스별로 관리됨 > 클래스 로딩 직후 바로 사용 가능
- 인스턴스 필드와 정적 필드 선언 판단 기준 : 객체마다 가지고 있어야 할 데이터 > 인스턴스 필드 / 공용적인 데이터 > 정적 필드
- 인스턴스 메소드와 정적 메소드 선언 판단 기준 : 인스턴스 필드를 이용해서 실행해야 하는 메소드 > 인스턴스 메소드 / 인스턴스 필드 사용하지 않으면 정적 메소드
정적 멤버 사용
클래스.필드;
클래스.메소드(매개값, ...);
- 클래스 이름과 함께 도트 (.)연산자로 접근
- 객체 참조 변수로도 접근 가능 > 추천하진 않음
정적 초기화 블록
- 정적 필드는 보통 필드 선언과 동시에 초기값을 줌
- 정적 필드는 생성자로 초기화 할 수 없는데, 그럼 복잡한 초기화는 어떻게 해야하는가? > 정적 블록에서 초기화함
public class Tv {
static String company = "Samsung";
static String model = "LCD";
static String info;
// 정적 블록
static {
info = company + "-" + model;
}
}
- 정적 블록은 클래스 내부에 여러개가 선언되어도 상관 없음
정적 메소드와 블록 선언 시 주의할 점
- 객체가 없어도 실행되기 때문에 이들 내부에 인스턴스 필드나 인스턴스 메소드 사용 불가능
- this 키워드 사용 불가능
- 정적 메소드와 블록에서 인스턴스 멤버를 사용하고 싶다면 내부에서 객체를 먼저 생성하고 참조 변수로 접근해야 함
- main() 메소드 또한 정적 메소드이므로 객체 생성 없이 인스턴스 멤버를 사용할 수 없음
public Class Car {
int speed;
void run() {
System.out.println("속도 : " + speed);
}
public static void main(String[] args) {
Car myCar = new Car();
myCar.speed = 60;
myCar.run(); // 속도 : 60
}
}
싱글톤 (Singleton) : 전체 프로그램에서 단 하나의 객체만 만들도록 보장해야하는 객체
- 클래스 외부에서 new 연산자로 생성자를 호출할 수 없도록 막아야 함 > private 접근 제한자 사용
public class 클래스 {
// 정적 필드
private static 클래스 singleton = new 클래스();
// 생성자
private 클래스() {}
// 정적 메소드
static 클래스 getInstance() {
return singleton;
}
}
- 자신의 타입인 정적 필드를 하나 선언하고 자신의 객체를 생성해 초기화 함 / 클래스 내부에서는 new 연산자로 생성자 호출 가능
- 정적 필드도 private 접근 제한자로 외부에서 필드값을 변경하지 못하도록 막음
- 외부에서 호출할 수 있도록 정적 메소드 getInstance()를 선언하고 정적 필드에서 참조하는 자신의 객체를 리턴함
- 외부에서 객체를 얻는 유일한 방법은 getInstance() 메소드를 호출하는 것 뿐
10. final 필드와 상수
final 필드 : 초기값이 저장되면 이것이 최종값이 되어서 프로그램 실행 도중에 수정할 수 없음
final 타입 필드 [=초기값];
- final 필드에 초기값을 주는 두 가지 방법 : 필드 선언 시에 주기 / 생성자에서 주기 (복잡한 초기화 코드 또는 객체 생성 시 외부 데이터로 초기화 해야할 때)
- 초기화되지 않은 final 필드를 그대로 남겨두면 컴파일 에러 발생
상수 (static final)
static final 타입 상수 [=초기값];
- 불변의 값 / 객체마다 저장되지 않고 클래스에만 포함되며 한 번 초기값이 저장되면 변경 불가능
- 복잡한 초기화의 경우 정적 블록에서 초기화 가능
- 상수 이름은 모두 대문자로 작성하는 것이 관례 / 혼합된 이름은 언더바 (_)로 연결
11. 패키지
- 클래스를 체계적으로 관리하기 위해 사용하는 폴더 형식의 관리 방법
- 실제 클래스의 전체 이름은 "패키지명 + 클래스명" / 도트 (.)연산자를 사용하여 상, 하위 패키지를 구분
- 클래스를 다른 패키지로 이동시키면 해당 클래스는 사용 불가능 > 패키지 전체를 이동시켜야 함
패키지 선언
package 상위패키지.하위패키지;
public class 클래스 {...}
- 패키지는 클래스를 컴파일하는 과정에서 폴더로 자동 생성됨
- 흔히 회사의 도메인 이름의 역순으로 패키지를 만듦
| 패키지 이름 작성 규칙 |
| 숫자로 시작 불가능, '_', '$'를 제외한 특수 문자 사용 불가능 |
| java로 시작하는 패키지는 자바 표준 aPI에서만 사용하므로 사용 불가능 |
| 모두 소문자로 작성 |
- 패키지 선언이 포함된 클래스를 명령 프롬프트로 컴파일할 경우, "javac -d 패키지생성경로 클래스이름.java" 형식으로 컴파일 해야 함
- 패키지에 소속된 클래스를 명령 프롬프트에서 실행하려면 패키지가 시작하는 폴더에서 java 명령어를 사용해야 함
import문
- 같은 패키지에 속하는 클래스들은 아무런 조건 없이 다른 클래스 사용 가능
- 다른 패키지의 클래스를 사용하는 방법
① 패키지와 클래스를 모두 기술
package com.mycompany;
public class Car {
com.hankook.Tire tire = new com.hankook.Tire();
}
- 서로 다른 패키지에 동일한 클래스 이름이 존재하고 두 패키지 모두 import 되어 있을 경우 패키지와 클래스를 모두 기술하는 방법을 사용해야 함
② import문 사용
package com.mycompany;
import com.hankook.Tire;
public class Car {
Tire tire = new Tire();
}
- import문의 작성 위치는 패키지 선언과 클래스 선언 사이
- 패키지에 포함된 다수의 클래스를 사용해야 한다면, 클래스 이름을 생략하고 *를 사용해 한 번에 작성
- import문으로 지정된 패키지의 하위 패키지는 import 대상이 아님 / 하위 패키지를 사용하고 싶으면 import문을 더 작성해야 함
12. 추상 클래스 : 객체를 직접 생성할 수 있는 실체 클래스들의 공통적인 특성을 추출해서 선언한 클래스
- 추상 클래스와 실체 클래스는 상속 관계
- 객체를 직접 생성해서 사용할 수 없음 / 부모 클래스로만 사용되기 때문에 extends 뒤에만 올 수 있음
용도
- 실체 클래스들의 공통된 필드와 메소드의 이름을 통일
- 실체 클래스를 작성할 때 시간 절약
선언
public abstract class 클래스 {
// 필드
// 생성자
// 메소드
}
- 클래스 선언에 abstract 키워드를 붙이면 new 연산자를 이용해 객체를 생성할 수 없고 상속을 통해 자식 클래스만 생성 가능
- 자식 객체가 생성될 때 super(...)를 호출해서 추상 클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 존재해야 함
추상 메소드 : 메소드의 선언부만 있고 실행 내용인 중괄호 {}가 없는 메소드
[public | protected] abstract 리턴타입 메소드명(매개변수, ...);
-하위 클래스가 반드시 실행 내용을 채우도록 강요하고 싶은 메소드가 있을 경우 사용
- 자식 클래스는 반드시 추상 메소드를 오버라이딩해서 실행 내용을 작성해야 함 / 안하면 컴파일 에러