객체 지향 프로그래밍 ?
현실 세계에서 어떤 제품을 만들 때 부품을 먼저 만들고, 이 부품들을 하나씩 조립해서 완성품을 만든다.
소프트웨어 개발할 때에도 부품에 해당하는 객체들을 먼저 만들고, 이 객체들을 하나씩 조립해서 완성된 프로그램을 만드는 기법을 객체지향프로그래밍 이라고 한다.
객체와 클래스
객체
객체란 물리적으로 존재하거나 개념적인 것 중 다른 것과 식별이 가능한 것을 말한다.
객체는 속성과 동작으로 구성된다. 예를들어 사람이라는 객체는 이름, 나이라는 속성이 있고, 웃다, 걷다 등과 같은 동작이 있다.
자바는 이러한 속성과 동작은 각각 필드, 메소드 라고 부른다.
객체 모델링
현실 세계의 객체를 소프트웨어 객체로 설계하는 것을 객체 모델링이라고 한다.
객체 모델링은 현실 세계 객체의 대표 속성과 동작을 추려내어 소프트웨어 객체의 필드와 메소드로 정의하는 과정이라고 볼 수 있다.
객체의 상호작용
현실세계에서 일어나는 모든 현상은 객체와 객체 간의 상호작용으로 이루어져 있다.
예를들어 사람과 전자 계산기가 있다면 사람은 전자계산이의 기능을 이용하고, 전자 계산기는 계산 결과를 사람에게 리턴하는 상호작용을 한다.
객체 지향 프로그램에도 객체들은 다른 객체와 서로 상호작용하면서 동작한다.
객체들 사이의 상호작용 수단은 메소드이다. 객체가 다른 객체의 기능을 이용할 때 메소드를 호출한다.
메소드 호출을 통해 객체들은 데이터를 서로 주고받는다. 메소드 이름과 함께 전달하고자 하는 데이터를 괄호안에 기술하는데 이러한 데이터를 매개값이라고 한다.
객체간의 관계
객체는 단독으로 존재할 수 있지만 대부분 다른 객체와 관계를 맺고 있다.
관계의 종류에는 집합관계, 사용 관계, 상속 관계가 있다.
객체 지향 프로그래밍의 특징
객체 지향 프로그래밍의 특징은 캡슐화, 상속, 다형성이다.
1. 캡슐화
캡슐화란 객체의 데이터(필드), 동작(메소드)을 하나로 묶고 실제 구현 내용을 외부에 감추는 것을 의미한다.
외부 객체는 객체 내부의 구조를 알지 못하여 객체가 노출해서 제공하는 필드와 메소드만 이용할 수 있다.
필드와 메소드를 캡슐화하여 보호하는 이유는 외부의 잘못된 사용으로 객체가 손상되지 않도록 하는 데 있다.
자바 언어는 캡슐화된 멤버를 노출시킬 숨길지를 결정하기 위해 접근 제한자를 사용한다.
2. 상속
객체지향프로그래밍에서 부모 역할의 상위 객체와 자식 역할의 하위 객체가 있다.
부모 객체는 자기가 가지고 있는 필드와 메소드를 자식객체에게 물려주어 자식 객체가 사용할 수 있도록 하는 것을 상속이라고 한다.
따라서 부모 객체들의 필드와 메소드를 수정할 경우 자식 객체들도 수정된 객체를 사용할 수 있는데, 이러한 상속은 유지 보수 시간을 최소화 시켜 준다.
3. 다형성
다형성이란 사용 방법은 동일하지만 실행 결과가 다양하게 나오는 것을 말한다.
예를들어 자동차에 들어가는 부품을 바꿔야한다. 부품을 바꾸는것으로는 사용방법이 달라지진 않지만 부품에 따라 성능에 차이가 있게된다. 이처럼 프로그램을 구성하는 객체(부품)을 바꾸면 프로그램의 실행 성능이 다르게 나올 수 있다.
다형성을 구현하기 위해서는 자동 타입 변환과 재정의 기술(오버라이드)이 필요하다. 이 기술들은 상속과 인터페이스 구현을 통해 얻어진다.
객체와 클래스
클래스란 객체를 생성하기 위한 설계도라고 생각하면 된다. 현실세계에서 자동차를 생성하면 자동차의 설계도가 필요하듯, 객체 지향 프로그래밍에서도 객체를 생성하려면 설계도에 해당하는 클래스가 필요하다.
클래스로부터 생성된 객체를 해당 클래스의 인스턴스라고 부른다. 그리고 클래스로부터 긱체를 만드는 과정을 인스턴스화라고한다.
만들어진 객체를 인스턴스라고도 한다. 하나의 설계도 , 클래스를 가지고 여러개의 인스턴스를 만들 수 있다.
클래스의 선언
클래스 선언은 객체 생성을 위한 설계도를 작성하는 작업이다. 어떻게 객체를 생성(생성자) 하고, 객체가 가져야 할 데이터(필드)가 무엇이고, 객체의 동작 (메소드는) 무엇인지를 정의하는 내용이 포함된다.
package ch06.sec03; //패키지 선언
//클래스 선언
public class SportsCar { //public : 공개 클래스 선언
}
class Tire{
}
하나의 클래스 파일은 다음과 같이 복수 개의 클래스 선언을 포함할 수 있다. 이때 소스 파일명과 동일한 클래스만 공개 클래스로 선언할 수 있다.
*공개클래스 ? 🧐
공개클래스(public class)란 어느 위치에 있든지 패키지와 상관없이 사용할 수 있는 클래스를 말한다.
복수개의 클래스 선언이 포함된 소스파일을 컴파일 하면 바이트 코드 파일(.class)은 클래스 선언 수 만큼 생긴다. (bin 디렉토리에 위치)
소스파일을 저장하고 컴파일 하면 SportsCar.class와 Tire.class가 생성된 것을 볼 수 있다. 특별한 이유가 없다면 소스파일 하나 당 클래스 하나를 선언하는것이 좋다.
객체 생성과 클래스 변수
클래스로부터 객체를 생성하려면 객체 생성 연산자는 new가 필요하다.
new 연산자 뒤에는 생성자 호출 코드가 오는데, 클래스 () 형태를 가진다. new 연산자는 객체를 생성시킨 후 객체의 주소(번지) 를 리턴한다.
클래스 변수 = new 클래스();
다음 예제는 Student클래스를 선언하고 StudentExample 클래스의 main() 메소드에서 Student 객체를 생성하는 코드이다.
//라이브러리 클래스
package ch06.sce04;
public class Student {
}
//실행 클래스
package ch06.sec04;
public class StudentExample{
public static void main(STring[] args){
Student s2 = new Student();
System.oout.println("s1변수가 Sudent 객체를 참조합니다.");
Student s2 = new Student();
}
}
*클래스의 두 가지 용도
- 라이브러리 클래스 : 실행할 수 없으며 다른 클래스에서 이용하는 클래스
- 실행 클래스 : main() 메소드를 가지고 있는 실행 가능한 클래스
자바 프러그램은 하나의 실행 클래스와 여러개의 라이브러리 클래스들로 구성된다. 실행클래스는 실행하면서 라이브러리 클래스 내부에서 이용한다.
클래스의 구성 멤버
클래스 선언에서는 세가지가 포함된다.
1. 필드 : 객체의 데이터가 저장되는 곳, 변수 선언과 비슷하지만 쓰임새는 다르다.
2. 생성자 : 객체 생성 시 초기화 역할 담당, 선언 형태는 메소드와 동일하지만 리턴 타입이 없고 이름은 클래스 이름과 동일하다.
3. 메소드 : 객체의 동작으로 호출 시 실행되는 목록, 메소드는 객체와 객체간의 상호 작용을 위해 호출된다.
필드의 선언과 사용
필드는 객체의 데이터를 저장하는 역할을 한다. 필드를 선언하는 방법은 변수를 선언하는 방법과 동일하다.
단, 반드시 클래스 블록에 선언되어야만 필드 선언이 된다.
구분 | 필드 | (로컬)변수 |
선언 위치 | 클래스 선언 블록 | 생성자, 메소드 선언 블록 |
존재 위치 | 객체 내부에 존재 | 생성자, 메소드 호출 시에만 존재 |
사용 위치 | 객체 내 외부 어디든 사용 가능 | 생성자, 메소드 블록 내부에서만 사용 |
만약 초기값을 제공하지 않을 경우 필드는 객체 생성 시 자동으로 기본값으로 초기화된다.
(기본타입 int는 0, boolen 필드는 false, String이나 참조타입은 null)
//타입 필드면 [ = 초기값 ];
class Car {
String model = "그랜저"; //고유 데이터 필드
int speed = 300; //상태 데이터 필드
boolean start = true; //상태 데이터 필드
Tire tire = new Tire(); //부품 객체 필드
}
필드 사용
필드를 사용한다는 것은 필드 값을 읽고 변경하는 것을 말한다.
클래스에서 필드를 선언했다고 바로 사용 할 수 있는 것이 아니다. 클래스로부터 객체 생성된 후 필드를 사용할 수 있다.
필드는 객체 내부와 생성자, 메소드 내부에서 사용할 수 있고, 객체 외부에서도 접근해서 사용할 수 있다.
객체 내부에서는 단순히 필드명을 읽고 변경할 수 있지만, 외부 객체에서는 참조 변수와 도트연산자를 이용해서 필드를 사용해야 한다.
생성자 선언과 호출
클래스 변수 = new 클래스();
new연산자는 객체를 생성 후 생성자의 블록을 실행하여(생성자 호출) 객체를 초기화 하는 역할을 한다.
객체 초기화란 필드 초기화를 하거나 메소드를 호출하여 객체를 사용할 준비를 하는 것을 말한다.
생성자가 성공적으로 실행이 끝나면 new연산자는 객체의 주소를 리턴한다. 리턴된 주소는 클래스 변수에 대입되어 객체의 필드나 메소드에 접근할 때 이용된다.
기본 생성자
모든 클래스는 생성자가 존재하며, 하나 이상을 가질 수 있다. 클래스의 생성자 선언이 없으면 컴파일러는 기본 생성자를 바이트 코드 파일에 자동으로 추가시킨다.
클래스가 public class로 선언되면 생성자에도 public 이 붙지만, public 없이 선언되면 기본 생성자에도 public은 붙지 않는다.
[public] 클래스 (){ } //기본 생성자
그렇기 때문에 Car myCar = new Car(); new뒤에는 기본 생성자를 호출 할 수 있다.
생성자 선언
하지만 클래스에 개발자가 명식적으로 선언한 생성자가 있다면 컴파일러는 기본 생성자를 추가하지 않는다.
개발자가 생성자를 선언하는 이유는 객체를 다양하게 초기화 하기 위해서이다.
클래스(매개변수, ... ){
//객체의 초기화 코드
}
생성자를 호출 할 때 3개의 매개값을 블록 내부로 전달하고자 한다. 이 경우 3개의 매개값을 순서대로 매개변수로 대입받기 위해 다음과 생성자를 명시적으로 선언해야한다.
public class Car {
Car(String model, String color, int maxSpeed){...}
}
Car myCar = new Car("그랜저","검정",300);
필드 초기화
필드 초기화란 필드에 값을 최초로 넣는 행위(초기값을 넣는 행위)이다.
클래스 선언 시, 객체를 생성할 때 필드에 초기값을 줄 수 있다
클래스 선언시 초기값을 주는 경우는 모든 객체가 동일한 필드값을 갖는 경우이고,
객체를 생성할 때 필드에 초기값을 주는 경우는 객체마다 고유한 다른 값을 가져야하는 경우이다.
public class Korean {
String nation = "대한민국";
String name;
String ssn;
public Korean(String n, String s){ //매개값으로 받은 이름과 주민등록번호를 필드 초기값으로 사용
name = n;
ssn = s;
}
}
Korean k1 = new Korean("박자바","011225-1234567");
Korean k2 = new Korean("김자바","909090-1122336");
생성자의 매개값은 new 연산자로 생성자를 호출할 때 주어진다.
k1, k2가 참조하는 객체는 주어진 매개값으로 name과 ssn 필드가 각각 초기화된다.
위 예제의 Korean생성자를 보면 매개변수 이름으로 각각 n과 s를 사용했다. 하지만 매개변수의 이름이 너무 짧으면 가독성이 좋지 않다 가능하면 초기화 시킬 필드명과 동일한 이름으로 사용하는 것이 좋다.
public Korean(String name, String ssn){
this.name = name;
this.ssn = ssn;
}
this는 현재 객체를 말하며 this.name 은 현재 객체의 데이터(필드)로서의 name 을 뜻한다.
생성자 오버로딩
매개값으로 객체의 필드를 다양하게 초기화하려면 생성자 오버로딩이 필요하다. 생성자 오버로딩이랑 매개변수를 달리하는 생성자를 여러 개 선언하는 것을 말한다.
//생성자 오버로딩 : 매개변수 타입, 개수, 순서가 다르게 여러 개의 생성자 선언
publice calss Car{
Car(){...}
Car(String model){...}
Car(String model, String color){...}
Car(String model, String color, inst maxSpeed){...}
}
매개 변수의 타입과 개수 그리고 선언된 순서가 똑같을 경우 매개변수 이름만 바꾸는 것을 생성자 오버로딩이 아니다.
Car(String model, String color){...}
Car(String color, Stirng model){...} //오버로딩이 아님, 컴파일 에러 발생
package ch06.sec07.exam04;
public class Car{
//필드 선언
Stirng company = "현대자동차";
String model;
String color;
int maxSpeed;
Car(){}
Car(String model){
this.model = model;
}
Car(String model, Strign color){
this.model = model;
this.color = color;
}
Car(String model, String color, int maxSpeed){
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
}
다른 생성자 호출
생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드가 발생할 수 있다. 매개변수의 수만 달리 하고 필드 초기화 내용이 비슷한 생정자에서 이러한 중복코드를 많이 볼수 있다.
이 경우에는 공통 코드를 한 생성자에서만 집중적으로 작성하고, 나머지 생성자는 this(...)를 사용하여 공통 코드를 가지고 있는 생성자를 호출하는 방법으로 개선할 수 있다.
//중복된 코드가 많아짐
Car(String model){
this.model = model;
this.color = "은색";
this.maxSpeed = 250;
}
Car(String model, String color){
this.model = model;
this.color = color;
this.maxSpeed = 250;
}
Car(String model, String color, inst maxSpeed){
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
공통 코드를 한 생성자에만 집중적으로 작성하고, 나머지 생성자는 this(...)를 사용하여 공통 코드를 가지고 있는 생성자를 호출하는 방법으로 개선할 수 있다. 주의 할 점은 this를 사용할 경우 반드시 첫줄에다가 선언해야한다.
Car(String model){
this(model, "은색", 250); //내가 가지고 있는 생성자중에서 호출
}
Car(String model, String color){
this(model, color, 250);
}
Car(String model, String color, inst maxSpeed){
//공통 초기화 코드
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
메소드 선언과 호출
메소드 선언은 객체의 동작을 실행 블록으로 정의하는 것을 말한다. 메소드 호출은 실행 블록을 실제로 실행하는 것을 말한다.
메소드는 객체 내부에서도 호출되지만 다른 객체에서도 호출될 수 있기 때문에 객체간의 상호작용한느 방법을 정의하는 것이라고 볼 수 있다.
리턴타입 : 리턴 타입은 메소드가 실행할 후 호출한 곳으로 전달한느 결과값의 타입을 말한다. 리턴값이 없는 메소드는 void로 작성해야한다.
void powerOn(){...}
double divide(int xm int y){...}
메소드명 : 메소드명은 첫 문자를 소문자로 시작하고 캐멀 스타일로 작성한다.
매개변수 :매개변수는 메소드를 호출할 때 전달한 매개값을 받기 위해 사용된다.
실행 블록 : 메소드 호출시 실행되는 부분이다.
가변길이의 매개변수
메소드를 호출할 때에는 매개변수의 개수에 맞게 매개값을 제공해야한다.
만약 메소드가 가변길이 매개변수를 가지고 있다면 매개변수의 개수와 상관없이 매개값을 줄 수 있다.
int sum (int ... vlaue){
}
int result = sum(1,2,3);
int reslut = sum(1,2,3,4,5);
매개값들은 자동으로 배열 항목으로 변환되어 메소드에서 사용된다. 그렇기 때문에 메소드 호출 시 직접 배열을 매개값으로 제공해도 된다.
//방법1 : 배열을 만들고 변수를 대입
int[] values = {1,2,3};
int result = sum(vlaues);
//방법2 : 바로 배열을 만들어서 전달
int result = sum(new int[] {1,2,3});
package ch06.sec08.exam02;
public class Computer {
int sum(int ...values){ //가변길이 매개변수를 갖는 메소드 선언, values는 배열 변수
int sum = 0;
for(int i=0; i<values.length ; i++){ //values는 배열 타입의 변수처럼 사용
sum += values[i];
}
return sum;
}
}
Computer myCom = new Computer(); //Computer객체 생성
int result = myCom.sum(1,2,3);
int result2 = myCom.sum(1,2,3,4,5);
int[] values = {1,2,3,4,5};
int result3 = myCom.sum(values);
int result4 = myCom.sum(new int[] {1,2,3,4,5});
메소드 오버로딩
메소드 오버로딩은 메소드 이름은 같되 매개변수의 타입, 개수, 순서가 다른 메소드를 여러개 선언하는 것을 말한다.
void println(){...}
void println(double x){...}
void println(int x){...}
void println(String x){...}
인스턴스 멤버
인스턴스는 객체라고도 한다. 인스턴스 멤버는 즉 필드와 메소드이다. 객체는 이미 만들어져 있기 때문에 생성자가 없다.
구분 | 설명 |
인스턴스 멤버 (객체 멤버) | 객체에 소속된 멤버 (객체를 생성해야만 사용할 수 있는 멤버) |
정적 멤버 (고정된 클래스가 가지고 있는 멤버) | 클래스에 고정된 멤버 (객체 없이도 사용할 수 있는 멤버) |
인스턴스 멤버 선언 및 사용
인스턴스 멤버란 객체에 소속된 멤버를 말한다. 따라서 객체가 있어야만 사용할 수 있는 멤버이다.
필드는 객체에 소속된 멤버가 분명하지만 메소드는 객체에 포함되지 않는다.
메소드는 코드의 덩어리이므로 객체마다 저장한다면 중복 저장으로 인해 메모리 효율이 떨어진다.
따라서 메소드 코드는 메소드 영역에 두되 공유해서 사용하고, 이때 객체 없이는 사용하지 못하도록 제한을 걸어 둔 것이다.
this 키워드
객체 내부에서 인스턴스 멤버에 접근하기 위해 this를 사용할 수 있다.
우리가 자신을 '나'라고 하듯이 객체는 자신을 this라고 한다. (객체 자신이 가지고 있는 = 객체에 소속된)
생성자와 메소드의 매개변수명이 인스턴스 멤버인 필드명과 동일한 경우, 인스턴스 필드임으르 강조하고자 할 때 this를 사용한다.
정적 멤버
자바는 클래스 로더를 이용해서 클래스를 메소드 영역에 저장하고 사용한다. 정적 멤버란 메소드 영역의 클래스에 고정적으로 위치하는 멤버를 말한다. 그렇기 때문에 정적 멤버는 객체를 생성할 필요 없이 클래스를 통해 바로 사용이 가능하다.
정적멤버 선언
필드와 메소드는 모두 정적 멤버가 될 수 있다. 정적 필드와 정적 메소드를 선언하려면 static키워드를 추가하면 된다.
public class 클래스 {
static 타입 필드 [= 초기값]; //정적 필드 선언
static 리턴타입 메소드(매개변수 , ...) {...} //정적 메소드 선언
}
객체마다 가지고 있을 필요성이 없는 공용적인 필드는 정적 필드로 선언하는 것이 좋다. 예를 들어 파이가 있다.
정적 멤버 사용
public class Calculator {
static double pi = 3.141592;
static int plus(int x, int y){ return x+y; }
static int minus(int x, int y){ return x-y; }
}
double result1 = 10 * 10 * Calculator.pi;
int result2 = Calculator.plus(10,5);
int result3 = Calculator.minus(10,5);
정적 블록
정적 필드는 필드 선언과 동시에 초기값을 주는 것이 일반적이다.
하지만 복잡한 초기화 작업이 필요하다면 정적 블록을 이용해야한다.
static {
...
}
정적 블록은 클래스가 메모리로 로딩될 때 자동으로 실행된다. 정적 블록이 클래스 내부에 여러 개가 선언되어 있을 경우 선언된 순서대로 실행된다.
public class Television {
static String company = "MyCompany";
static String model = "LCD";
static String info;
static {
info = company + "-" + model;
}
}
public class TelevisionExample {
public static void main(String[] args){
System.out.println(Television.info);
}
}
인스턴스 멤버 사용 불가
정적 메소드와 정적 블록은 객체가 없이도 실행된다는 특징 때문에 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다.
또한 객체 자신의 참조인 this도 사용할 수 없다.
public class ClassName {
int field1; //인스턴스 필드 선언
voide method1(){...} //인스턴스 메소드 선언
static int field2; //정적 필드 선언
static void method2(){...} //정적 메소드 선언
static {
field1 = 10;//컴파일 에러
method1(); //컴파일 에러
field2 = 10;
method2();
}
//정적 메소드 선언
static void Method3() {
this.field1 = 10; //컴파일 에러
this.method1(); //컴파일 에러
//객체 생성 -> 참조 변수로 접근
ClassName obj = new ClassName();
obj.field1 = 10;
obj.method1();
field2 = 10;
method2();
}
}
main() 메소드도 동일한 규칙이 적용된다. main() 메소드도 정적 메소드이므로 객체 생성없이 인스턴스 필드와 인스턴스 메소드를 바로 사용할 수 없다. 따라서 객체를 생성 후 인스턴스 멤버를 사용해야한다.
public class Car {
int speed;
void run(){...}
public static void main(String[] args){
speed = 60; //컴파일 에러
run(); //컴파일 에러
//객체 생성
Car myCar = new Car();
myCar.speen = 60;
myCar.run();
}
}
final 필드와 상수
인스턴스 필드와 정적 필드은 언제든 값을 변경할 수 있다. 그러나 경우에 따라서는 값을 변경하는 것을 막고 읽기만 허용해야 할 때가 있다.
이때 final 필드와 상수를 선언해야한다.
final은 최종적 이라는 뜻을 가지고 있다. final 필드는 초기값이 저장되면 이것이 최종적이 값이 되어 실행 도중에 수정할 수 없게 된다.
final 타입 필드 [=초기값];
final 필드에 초기값을 줄 수 있는 방법은 두가지 밖에 없다.
- 필드 선언 시에 초기값 대입
- 생성자에서 초기값 대입
고정된 값이라면 필드 선언 시에 주는 것이 제일 간단하다. 하지만 복잡한 초기화 코드가 필요하거나 객체 생성 시 외부에서 전달된 값으로 초기화를 한다면 생성자에서 해야한다.
상수 선언
수학에서 사용되는 원주율 파이나 지구의 무게 및 둘레 등에 해당되는 불변의 값을 저장하는 필드를 자바에서는 상수라고 부른다.
상수는 객체마다 저장할 필요가 없고, 여러 개의 값을 가져도 안되기 때문에 static 이면서 final 인 특성을 가져야한다.
상수 이름은 모두 대문자로 작성하는 것이 관례이며, 단어가 혼합될 경우 _ (언더바)로 단어들을 연결한다.
또한 상수는 정적 필드 이므로 클래스로 접근해서 읽을 수 있다.
static final 타입 상수 [=초기값];
//OR
static final 타입 상수;
static{
상수 = 초기값;
}
비교
static | 객체마다 가질 필요가 없는 공용으로 사용하는 필드 혹은 인스턴스 필드를 포함하지 않는 메소드 |
final | 한 번 값이 정해지고 나면 값을 바꿀 수 없는 필드 |
static final | 모든 영역에서 고정된 값으로 사용하는 상수 |
패키지
자바의 패키지는 단순히 디렉토리만을 의미하지 않는다. 패키지는 클래스의 일부분이며 클래스를 식별하는 용도로 사용된다.
패키지는 주로 개발 회사의 도메인 이름의 역순으로 만든다. 예를들어 namer.com 회사의 패키지는 com.namer 로 사용한다.
패키지는 상위 패키지와 하위 패키지로를 도트(.)로 구분한다. 도트는 물리적으로 하위 디렉토리를 뜻한다.
패키지 선언
패키지 디렉토리는 클래스를 컴파일 하는 과정에서 자동으로 생성된다. 컴파일러는 클래스의 패키지 선언을 보고 디렉토리를 자동 생성 시킨다. 패키지 선언은 package 키워드와 함께 패키지 이름을 기술한 것으로 항상 소스 파일 최상단에 위치해야한다.
패키지 이름은 소문자로 작성하는것이 관례이고, 이름이 서로 중복되지 않도록 하며 회사 도메인 이름의 역순으로 작성하고 마지막에는 프로젝트 이름을 붙여주는 것이 일반적이다.
packages 상위패키지.하위패키지;
public class 클래스명 {...}
import 문
같은 패키지에 있는 클래스는 아무런 조건 없이 사용할 수 있지만 달느 패키지에 있는 클래스를 사용하려면 import문을 이용해서 어떤 패키지의 클래스를 사용해야하는지 명시해야한다.
만약 동일한 패키지에 포함된 다수의 클래스를 사용해야한다면 클래스 이름을 생략하고 *를 사용할 수 있다.
package com.mycompnay;//Car클래스의 패키지
import com.hankook.Tire; //Tire클래스의 전체 이름
public class Car {
Tire tire = new Tire(); //hankook.Tire의 클래스 사용
}
*import문 자동 추가 기능 : ctrl + shift + o
접근 제한자
경우에 따라서 객체의 필드를 외부에서 변경하거나 메소드를 호출할 수 없도록 막아야 할 필요가 있다.
중요한 필드와 메소드가 외부로 노출되지 않도록 해 객체의 무결성을 유지하지 위해서이다.
자바는 이러한 기능을 구현하기 위해 접근 제한자를 사용한다.
접근 제한자 | 제한 대상 | 제한 범위 |
public | 클래스, 필드, 생성자, 메소드 | 없음 |
protected | 필드, 생성자, 메소드 | 같은 패키지이거나 자식 객체만 사용가능 |
(default) | 클래스, 필드, 생성자, 메소드 | 같은 패키지 |
private | 필드, 생성자, 메소드 | 객체 내부 |
클래스의 접근 제한
클래스는 public과 default접근 제한을 가질 수 있다.
public 접근 제한자를 생략했다면 클래스는 default 접근 제한을 가진다.
이 경우 클래스는 같은 패키지에서는 아무런 제한없이 사용할 수 있지만 다른 패키지 에서는 사용할 수 없게된다.
클래스 선언할 때 public 접근 제한을 붙였다면 같은 패키지 뿐만 아니라 다른 패키지에서도 사용할 수 있다.
[public] class 클래스{...}
생성자의 접근 제한
객체를 생성하기 위해 생성자를 어디에서나 호출할 수 있는 것은 아니다.
생성자가 어떤 접근 제한을 갖느냐에 따라 호출 가능 여부가 결정된다.
public class ClassName {
[public | private] ClassName(...) {...}
}
접근 제한자 | 생성자 | 설명 |
public | 클래스 | 모든 패키지에서 생성자를 호출할 수 있다. =모든 패키지에서 객체를 생성할 수 있다. |
클래스 | 같은 패키지에서만 생성자를 호출할 수 있다. =같은 패키지에서만 객체를 생성할 수 있다. |
|
private | 클래스 | 클래스 내부에서만 생성자를 호출할 수 있다. =클래스 내부에서만 객체를 생성할 수 있다. |
필드와 메소드의 접근 제한
//필드 선언
[public | private ] 타입필드;
//메소드 선언
[public | private ] 리턴타입 메소드(...){...};
접근 생성자 | 생성자 | 설명 |
public | 필드, 메소드 | 모든 패키지에서 필드를 읽고 변경할 수 있다. 모든 패키지에서 메소드를 호출할 수 있다. |
필드, 메소드 | 같은 패키지에서만 필드를 읽고 변경할 수 있다. 같은 패키지에서만 메소드를 호출할 수 있다. |
|
private | 필드, 메소드 | 클래스 내부에서만 필드를 읽고 변경할 수 있다. 클래스 내부에서만 메소드를 호출할 수 있다. |
Getter와 Setter
- Getter -> 값을 가져오는 자
- Setter -> 값을 세팅하는 자
객체의 필드(데이터)를 외부에서 마음대로 변경할 경우 객체의 무결성(결점이 없는 성질)이 깨질 수 있다.
예를들어 자동차의 속력은 음수가 될 수 없는데 외부에서 음수로 변경하면 객체의 무결성이 깨진다.
Car myCar = new Car();
myCar.speed = -100;
이러한 문제점때문에 객체 지향 프로그래밍에서는 직접적인 외부에서의 필드 접근을 막고 대신 메소드를 통해 피리드에 접근 하는 것을 선호한다. 그 이유는 메소드는 데이터를 검증해서 유효한 값만 필드에 저장할 수 있기 때문이다. 이러한 역할을 하는 메소드가 Setter이다.
private double speed; //접근 제한자 private 로 외부 접근 막음
public void setSpeed(double speed){
if(speed < 0 ){ //매개값이 음수일 경우 speed를 0으로 저장하고 메소드 실행 종료
this.speed = 0;
return;
}else{
this.speed = speed;
}
}
외부에서 객체의 필드를 읽을 때도 필드 값이 객체 외부에서 사용하기에 부적절한 경우, 메소드로 변환해서 리턴할 수 있다.
이러한 역할을 하는 메소드가 Getter이다.
private double speed;
public double getSpeed(){ //필드값인 마일을 km단위로 환산 후 외부로 리턴
double km = speed * 1.6;
return km;
}
- 필드 접근 제한은 private 로 한다. (숨김 처리 하겠다.)
- getter을 만들 때 get으로 시작 , 리턴 타입을 필드 타입과 동일하게 하기
- setter을 만들 때 set으로 시작, 외부에서 값을 받아 검사 후 필드를 바꾸게, 리턴 타입은 void
- getter , setter 은 public으로 한다
⭐️ 필드 타입이 boolean인 경우에는 Getter는 get으로 시작하지 않고 is로 시작하는 것이 관례이다.
private boolean stop;
//Getter
public boolean isStop(){
return stop;
}
싱글톤 패턴
애플리케이션 전체에서 단 한개의 객체만 생성해서 사용하고 싶다면 싱글톤 패턴을 적용할 수 있다.
싱글톤 패턴의 핵심은 생성자를 private 접근 제한해서 외부에서 new 연산자로 생성자를 호출할 수 없이 막는 것이다.
private 클래스 () {}
public class 클래스 {
private static 클래스 singleton = new 클래스(); //정적 필드 정의
private 클래스() {} //private 접근 권한을 갖는 생성자 선언
public static 클래스 getInstance(){ //public 접근 권한을 갖는 정적 메소드 선언
return singleton;
}
}
생성자를 호출할 수 없으니 외부에서는 객체를 생성하는 것이 불가능 해 진다. 대신 싱글톤 패턴이 제공하는 정적 메소드를 통해서 간접적으로 객체를 얻을 수 있다.
클래스 변수1 = 클래스.getInstance();
클래스 변수2 = 클래스.getInstance();
getInstance( ) 메소드가 리턴하는 객체는 정적 필드가 참조하는 싱글톤 객체이다. 변수1과 변수2가 참조하는 객체는 동일한 객체가 된다.
참고자료 : [자바 기초 강의] 이것이 자바다(개정판) 유튜브
'JAVA' 카테고리의 다른 글
인터페이스 (0) | 2023.01.24 |
---|---|
상속 (0) | 2023.01.24 |
열거(Enum) 타입 (0) | 2023.01.15 |
배열 (0) | 2023.01.15 |
참조 타입, 문자열 (0) | 2023.01.15 |