이것이 자바다 4일차 - Chapter 6 클래스 (1/2)

Seo-Faper·2023년 1월 12일
0

이것이 자바다

목록 보기
4/20

객체 지향 프로그래밍

자바의 가장 중요한 개념인 객체 지향(Object Oriented)이다.
현실 세계에선 어떤 물건을 만들 때 부품을 먼저 만들고 하나씩 조립해서 완성한다.
프로그래밍도 마찬가지로 객체를 먼저 만들어 놓고 객체들을 하나씩 조립해 프로그램을 만드는 기법을 객체 지향 프로그래밍이라고 한다.


객체란?

객체란 물리적으로 존재하거나 개념적인 것 중 다른 것과 식별 가능한 것을 말한다.
물리적으로 존재하는 자동차, 자전거는 물론 개념적인 학과, 강의, 등도 모두 객체로 다룰 수 있다.

객체는 속성과 동작으로 구성된다. 사람은 이름과 나이, 성별 등을 속성으로 가지고 동작으로는 걷다, 웃다, 이런 것이 있는 것과 같다.

자바는 이러한 속성과 동작을 각각 필드(field)와 메소드(method)라고 부른다.


객체의 상호작용

현실 세계에서 일어나는 모든 현상은 객체와 객체 간의 상호작용으로 이루어져 있다.
예를 들어서 사람은 계산기의 기능을 이용하고 계산기는 계산 결과를 사람에게 리턴하는 상호작용을 한다.

자바에서도 마찬가지로 객체와 객체끼리 서로 상호작용하면서 동작한다. 서로 메소드를 호출하면서 다른 객체의 기능을 사용할 수 있다.

메소드 호출은 다음과 같은 형태를 가진다.

메소드(매개값1, 매개값2);

다른 프로그래밍언어에서 함수 호출이랑 비슷하다고 보면 된다.

int result = add(10,20); //30을 반환

객체 간의 관계

객체들은 서로 상호관계를 맺고 있는데, 관계의 종류에는 3가지로 집합 관계, 사용 관계, 상속 관계가 있다.

  1. 집합 관계
    완성품과 부품의 관계를 말한다. 자동차는 엔진, 타이어, 핸들 등으로 구성되므로 자동차와 그의 부품들은 서로 집합 관계라 할 수있다.
  2. 사용 관계
    다른 객체의 필드를 읽고 변경하거나 메소드를 호출하는 관계를 말한다.
    자동차에 사람이 타고, 달린다, 멈춘다 등의 메소드를 호출하면 사람과 자동차는 사용 관계다.
  3. 상속 관계
    부모와 자식관계라 볼 수 있다. 자동차가 기계의 특징을 물려받았다면 기계와 자동차는 서로 상속 관계인 것이다.

객체 지향 프로그래밍의 특징

객체 지향 프로그래밍의 3대 특징이라고도 불리는 중요한 개념이다.

캡슐화, 상속, 다형성

캡슐화

캡슐화란 객체의 데이터, 메소드를 하나로 묶고 실제 구현 내용을 외부에 감추는 것을 말한다.
그래서 해당 객체를 호출한 외부 객체는 내부 구조를 알지 못하며 오직 개발자의 의도대로 제공된 필드와 메소드만 사용할 수 있다. 이 기능을 구현하기 위해 자바에서는 접근 제한자(Access Modifier)를 사용한다.

상속

객체 지향에서는 부모 역할의 상위 객체와 자식 역할의 하위 객체가 있다.
부모 객체는 자기가 가지고 있는 필드와 메소드를 자식 객체에게 물려주어 자식 객체가 사용할 수 있도록 한다.
한번 잘 만들어 놓은 부모 객체를 자식 객체에게 물려주어 코드의 재사용성을 높여주고 부모의 객체를 수정하면 상속된 자식 객체도 수정된 기능을 사용할 수 있어 유지 보수 시간을 최소화 시켜준다.

다형성

다형성이란 사용 방법은 동일하지만 실행 결과가 다양하게 나오는 성질을 말한다.
자동차의 타이어를 교환하면 성능이 다르게 나오듯, 프로그램을 구성하는 객체를 바꾸면 프로그램의 실행 성능이 다르게 나올 수 있다. 이 다형성을 구현하기 위해서는 자동 타입 변환과 재정의 기술이 필요하다. 이는 상속과 인터페이스 구현을 통해 얻을 수 있다.


객체와 클래스

객체를 생성하려면 클래스를 만들어야 한다.
클래스는 객체가 어떤 필드와 메소드를 가지는지 사전에 정의해 놓은 파일이다.
자동차를 만들려면 자동차의 설계도가 필요하듯 이 클래스는 설계도와도 같다.

그 설계도로 만들어진 객체를 인스턴스라고 하며, 하나의 클래스로 여러개의 인스턴스를 생성 할 수 있다.


클래스 선언

public class 클래스명{

}

이렇게 클래스를 선언 할 수 있다.

public class SportsCar
{

    class Tire{
        
    }
}

이런식으로 하나의 소스 파일에 여러 개의 클래스를 선언 할 수 있다.


객체 생성과 클래스 변수

이 클래스를 통해 인스턴스를 생성하는 방법은 다음과 같다.

<클래스명> <변수명> = new <클래스명>();

배열을 생성하는 방법과 동일하다. 사실 배열도 자바가 기본적으로 제공하는 클래스에서 인스턴스를 생성하는 것이기 때문이다.

    public static void main(String[] args) {
    
        Student s1 = new Student();
        System.out.println("s1 변수가 Student 객체를 참조합니다.");

        Student s2 = new Student();
        System.out.println("s2 변수가 Student 객체를 참조합니다.");

    }

클래스의 구성 멤버

그렇다면 이제 본격적으로 클래스를 구성하는 것들을 알아보자.

public class ClassName {
	//필드 선언
    int fieldName;
    
    //생성자 선언
    ClassName() { ... }
    
    //메소드 선언
    int methodName() { ... }
}

필드는 객체의 데이터를 담는 공간이다.

생성자는 객체 생성 시 초기화 하는 역할을 담당한다.

메소드는 객체의 동작으로 호출 시 실행하는 블록이다.


필드 선언과 사용

public class Car
{
    String model;
    boolean start;
    int speed;
}

이렇게 필드를 만들어 주고

    public static void main(String[] args) {
        Car myCar = new Car();

        System.out.println("모델명 : "+myCar.model); //null;
        System.out.println("시동여부 : "+myCar.start); //false;
        System.out.println("현재속도 : "+myCar.speed); //0;
    }

인스턴스를 만들면 어떻게 될까? 각 자료형의 초기값들이 출력된다.

myCar.speed = 10;

이런 식으로 생성한 인스턴스에 대해 값을 대입할 수도 있고

public class Car
{
    String company = "현대자동차";
    String model = "그랜저";
    String color = "검정";
    int maxSpeed  = 350;
    int speed;


}

이렇게 필드에서 바로 선언 할 수도 있다.


생성자 선언과 호출

생성자는 객체를 만들 때 기본값을 new 키워드로 생성할 때 부터 넣고 싶어서 만들어 진 것이다. 기존에 객체를 Car myCar = new Car(); 이렇게 선언한 후 필드의 속성을 하나씩 myCar.speed = 10; 이렇게 넣는다면 코드가 길어지기 때문이다.

public class Car
{
    Car(String model, String color, int maxSpeed){
        
    }
}

이렇게 클래스의 이름과 동일한 키워드를 통해 기본값을 세팅할 수 있다.

    public static void main(String[] args) {
        Car myCar = new Car("그랜저","검정",250);
    }

그리고 이렇게 생성 부터 필드의 값을 세팅 할 수 있게 된다.

필드 초기화

public class Korean
{
    String nation = "대한민국";
    String name;
    String ssn;
    public Korean(String n, String s){
        name = n;
        ssn = s;
    }
}

생성자를 통해 필드를 초기화 하는 방법이다.

    public static void main(String[] args) {
        Korean k1 = new Korean("박자바","011225-1234567");
      
        System.out.println("k1.nation : "+k1.nation);
        System.out.println("k1.name : "+k1.name);
        System.out.println("k1.ssn : "+k1.ssn);
        System.out.println("");

        Korean k2 = new Korean("김자바","011225-1234567");
      
        System.out.println("k2.nation : "+k2.nation);
        System.out.println("k2.name : "+k2.name);
        System.out.println("k2.ssn : "+k2.ssn);
        System.out.println("");
    }

이렇게 여러 인스턴스를 초기화할 수 있다.
그런데 생성자에 매개변수 이름이 너무 짧으면 가독성이 떨어지기 때문에 초기화하려는 필드와 동일한 변수명으로 지어주는 것이 좋다. 그래서 this 키워드를 통해 현재 객체의 필드를 초기화 해주는 방법이 있다.

public class Korean {
    String nation = "대한민국";
    String name;
    String ssn;
    public Korean(String name, String ssn){
        this.name = name;
        this.ssn = name;
    }
}

생성자 오버로딩

매가값으로 객체의 필드를 다양하게 초기화 하려면 생성자 오버로딩(Overloading)이 필요하다.
간단히 말해 Korean k2 = new Korean("김자바","011225-1234567"); 에서
Korean k2 = new Korean("김자바"); 이렇게만 객체를 생성하고 싶을 수도 있다.

이 때 Korean 클래스에서 생성자를 하나 더 만들어 초기값을 달리 줄 수 있는 것이다.

다른 생성자 호출

그렇게 만든 여러 생성자들은 중복된 코드가 있어 가독성을 떨어트린다.

    Car(String model){
        this.model = model;
    }
    Car(String model, String 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, "은색", 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;
    }

메소드 선언과 호출

이제 본격적으로 객체의 기능을 구현할 차례다.
메소드는 객체 내부에서도 호출되지만 다른 객체에서도 호출될 수 있기 떄문에 객체간의 상호작용하는 방법을 정의하는 것이라고 볼 수 있다.

리턴타입 메소드명 (매개변수, ... ){
	//실행블록
}

기본적으로 이런 형태로 선언 할 수 있다.
리턴타입은 아무것도 리턴하지 않는 void와 그 외 다른 자료형, 객체가 될 수 있다.

메소드 선언

public class Calcuator
{
    void powerOn(){
        System.out.println("전원을 켭니다.");
    }
    void powerOff(){
        System.out.println("전원을 끕니다.");
    }

    int plus(int x, int y){
        int result = x + y;
        return result;
    }

    double divide(int x, int y){
        double result = (double)x / (double)y;
        return result;
    }
}

메소드 호출

    public static void main(String[] args) {
        Calcuator myCalc = new Calcuator();

        myCalc.powerOn();

        int result1 = myCalc.plus(5,6);
        System.out.println("result1: "+result1);

        int x = 10;
        int y = 4;

        double result2 = myCalc.divide(x,y);
        System.out.println("result2: "+result2);

        myCalc.powerOff();
    }

이렇게 선언과 호출을 할수 있다.

가변길이 매개변수

메소드를 호출 할 때는 메소드에 지정된 매개변수의 개수에 맞게 인자를 전달해야 한다. 만약 메소드가 가변길이 매개변수를 가지고 있다면 개수와 상관없이 매개값을 줄 수있다.

int sum(int ... values) { } 

이렇게 선언하면 호출 할 때 매개변수의 개수를 지키지 않아도 된다.

int result = sum(1,2,3);
int result = sum(1,2,3,4,5);

이렇게 전달된 인자는 자동으로 배열로 바꿔서 전달되기 때문에 이렇게 응용도 가능하다.

int[] v = {1,2,3}
int result = sum(v);
int result = sum(new int[] {1,2,3,4});

return 문

return은 메소드의 실행을 강제 종료하고 호출할 곳으로 돌아가는 키워드이다.
리턴 타입이 있는 메소드는 반드시 return 문 뒤에 리턴값을 넣어줘야 한다.

    boolean isLeftGas(){
        if(gas==0){
            System.out.println("gas가 없습니다.");
            return false;
            System.out.println("정지합니다."); // 실행되지 않음 
        }
        System.out.println("gas가 있습니다.");
        return true;
    }

이런 식으로 return이 실행된 시점부터는 블럭 아래가 실행되지 않는다.

메소드 오버로딩

이것도 생성자 처럼 이름은 같지만 매개변수의 타입, 개수, 순서등이 다를 때 여러개를 선언 하기 위해 메소드 오버로딩을 할 수 있다.

public class Calculator
{
    double areaRectangle(double width){
        return width * width;
    }

    double areaRectangle(double width, double height){
        return width * height;
    }
}
    public static void main(String[] args) {
        Calculator cal = new Calculator();

        double result1 = cal.areaRectangle(10);
        double result2 = cal.areaRectangle(10,20);
        System.out.println("정사각형 넓이 : "+result1);
        System.out.println("직사각형 넓이 : "+result2);
    }

인스턴스 멤버 선언 및 사용

인스턴스 멤버란 객체에 소속된 멤버를 말한다. 우리가 지금까지 선언했던 필드와 메소드가 인스턴스 멤버였다.

public class Car{
	
    int gas; //인스턴스 필드 선언
    
    void setSpeed(int speed) { ... } //인스턴스 메소드 선언
}

인스턴스 멤버는 외부 클래스에서 사용하기 위해서는 반드시 객체를 먼저 만들고 참조 변수로 접근해 사용해야 한다.

Car myCar = new Car(); //객체 생성
myCar.gas = 10;  //참조 변수
myCar.setSpeed(60); //참조 변수

Car yourCar = new Car(); //객체 생성
yourCar.gas = 10;  //참조 변수
yorCar.setSpeed(60); //참조 변수

이 코드를 실행하게 되면 gas 필드는 각 객체마다 따로 존재하게 되고
setSpeed() 메소드는 메소드 영역에 저장되고 공유된다.

인스턴스 멤버는 객체에 소속된 멤버기 때문에 gas 필드는 객체에 소속된 멤버가 분명하지만, setSpeed() 메소드는 객체에 포함되지 않는다.

저렇게 setSpeed()는 메소드 영역에 하나만 생성되며 필요할 때 마다 공유해 준다.

this 키워드

객체 내부에서 인스턴스 멤버에 접근하기 위해 this를 쓸 수 있다.
어떤 클래스 안에서 this가 쓰이면 this의 대상은 해당 객체가 된다.
그래서 생성자를 만들 때도 인자로 받은 변수명과 인스턴스 멤버인 필드의 변수명이 같을 때 this 를 통해 지정해 준 것이다.


정적 멤버

자바는 실행할 때 클래스 로더를 통해 메소드 영역에 클래스를 저장하고 읽어온다. 이 때 메소드 영역에 메소드와 각종 필드를 정적으로 고정시키면 객체를 생성하지 않고도 바로 사용이 가능하다.

정적 멤버 선언

필드와 메소드 모두 정적 멤버가 될 수 있다.
정적 필드와 정적 메소드로 선언하려면 앞에 static 키워드를 추가하면 된다.

public class{
	//정적 필드
    static 타입 필드 = 초기값;
    
    //정적 메소드
    static 리턴타입 메소드(매개변수, ...) { ...}
    
}

주로 객체마다 가지고 있을 필요성이 없는 공용적인 필드는 정적 필드로 선언하는 것이 좋다.
원의 넓이나 둘레를 구할 때 필요한 파이(PI)는 객체를 생성 할 때 마다 가지고 있을 필요 없이 static으로 선언하는 것이 좋다.

정적 메소드는 주로 인스턴스 필드를 사용하지 않는 메소드를 대상으로 만들어 주는 것이 좋다.
예를 들어

public class Calculator{
	
    String color; //인스턴스 필드
	void setColor(String color) { this.color = color; } //인스턴스 메소드
    
    static int plus(int x, int y) { return x + y; } // 정적 메소드
}

plus() 메소드는 외부적으로 들어오는 인자에 대해서만 처리하기 때문에 정적 메소드가 적합한 반면, setColor()의 경우 인스턴스 필드인 color를 참조하기 때문에 적합하지 않다.

정적 멤버 사용

클래스가 메모리에 로딩되면 바로 정적 멤버를 쓸 수 있다. 클래스 이름뒤에 (.)을 찍고 접근하면 된다.

public class Calculator {
	static double pi = 3.141592;
    static int plus(int x, int y) {...}
    static int minus(int x, int y) {...}
}
double result1 = 10*10*Calculator.pi;
int result2 = Calculator.plus(10,20);
int result3 = Calculator.minus(20,10);
Calculator c = new Calculator();
int result = c.plus(10,10); //가능은 하지만 선호하지 않음 

객체를 만들고 거기서 정적 멤버를 호출할 수도 있지만, 정적 멤버는 클래스 이름에 바로 쓰는 것이 관례이다.

정적 블록

static 필드는 보통 필드 선언과 동시에 초기값을 주는게 일반적이지만
초기화 작업이 길어진다면 정적 블록을 통해 초기화 할 수 있다. 정적 블록이 클래스 내부에 여러 개가 선언되었을 경우는 선언된 순서내로 실행한다.

static{
 ...
}

이렇게 생성 할 수 있다.

public class Televison
{
    static String company = "MyCompany";
    static String model = "LCD";
    static String info;

    static {
        info = company + " - "+model;
    }
}
 System.out.println( Televison.info); // static 블럭에서 초기화 한 값이 출력됨.

인스턴스 멤버 사용 불가

정적 메소드와 정적 블록은 객체가 생성되어있지 않아도 실행된다는 특징 때문에 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다. 또한 this 키워드도 쓸 수 없다. 지정할 객체가 없기 때문이다.

만약 정적 메소드 안에서 인스턴스 멤버를 사용하고 싶다면 객체를 먼저 생성하고 참조 변수로 접근해야 한다.

public class Car
{
    int speed;

    void run(){
        System.out.println(speed+" 으로 달립니다.");
    }

    static void simulate(){
        Car myCar = new Car();
        myCar.speed = 200;
        myCar.run();
    }

    public static void main(String[] args) {
        simulate(); // 정적 메소드 호출

        Car myCar = new Car(); //객체 생성
        // 인스턴스 멤버 사용
        myCar.speed = 60;
        myCar.run();
    }
}

final 필드와 상수

인스턴스 필드와 정적 필드는 언제든 값을 바꿀 수 있지만, 때에 따라 오직 읽는 것만 허용해야 할 때가 있다. 그 때 final 필드와 상수를 선언해서 사용한다.

'최종적인' 이란 뜻을 가진 final 키워드는 초기값이 정해지면 이것이 프로그램 실행 도중에 바뀌는 것을 막는다.

final 필드에 초기값을 줄 수 있는 방법은 두 가지 밖에 없다.

  1. 필드 선언 시에 초기값 대입
  2. 생성자에 초기값 대입
public class Korean
{
    final String nation = "대한민국";
    final String ssn;

    String name;

    public Korean(String ssn, String name){
        this.ssn = ssn;
        this.name = name;
    }
}

국가와 주민번호는 바꿀 수 없고 이름은 바꿀 수 있으니 이런식으로 사용할 수 있다.

상수 선언

불변의 값을 담아야 할 때 변하는 수라는 뜻인 변수 대신 상수를 써야 한다.
원주율의 값, 중력 가속도, 또는 수학에서 쓰이는 각종 상수들을 표현할 때 쓴다.

static final 타입 상수 = 초기값;

또는 static 블럭으로도 할 수 있다.

static final 타입 변수;
static{
	상수 = 초기값;
}

그리고 상수의 이름은 모두 대문자로 작성하는 것이 관례이다. 띄어쓰기는 언더바(_)로 쓴다.

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;
    }
}

여기서 Math.PI 또한 자바에서 제공하는 상수이다.

System.out.println("지구의 반지름: "+Earth.EARTH_RADIUS); // 6400
System.out.println("지구의 표면적: "+Earth.EARTH_SURFACE_AREA); //5.147185403641517E8

패키지

지금까지 공부한 내용들은 모두 장별, 절별, 예제별로 패키지를 만들어 생성했다.

자바의 패키지는 단순히 디렉토리만을 의미하지 않는다. 패키지는 클래스의 일부분이며 클래스를 식별하는 용도로 사용된다.
주로 개발 회사의 도메인 이름을 역순으로 만든다.
그래서 이름이 같은 클래스가 있더라도 패키지별로 구분해 관리할 수 있다.

import 문

같은 패키지 안에 있는 클래스들은 자유롭게 쓸 수 있지만, 그렇지 않다면 import 키워드를 통해 어떤 패키지의 클래스를 쓰는지 명시해 줘야 한다.

package ch06.sec12.hyundai;
import ch06.sec12.hankook.SnowTire;
import ch06.sec12.kumho.AllSeasonTire;
public class Car
{
    ch06.sec12.hankook.Tire tire1 = new ch06.sec12.hankook.Tire();
    ch06.sec12.kumho.Tire tire2 = new ch06.sec12.kumho.Tire();
    
    SnowTire tire3 = new SnowTire();
    AllSeasonTire tire4 = new AllSeasonTire();
}

디렉토리 구조는 다음과 같다.


접근 제한자

경우에 따라서 객체의 필드를 외부에서 변경하거나 메소드를 호출할 수 없도록 막아야할 때가 있다.
의도하지 않는 필드와 메소드가 외부 객체에서 호출되어 무결성을 해칠 수 있기 때문이다. 그래서 자바는 클래스나 필드, 생성자, 메소드 등의 앞에 어디까지 허용할지를 지정하는 접근 제한자(Access Modifier)를 제공한다.

접근 제한자제한 대상제한 범위
public클래스, 필드, 생성자, 메소드없음
protected필드, 생성자, 메소드같은 패키지이거나, 자식 객체만 사용 가능
(default)클래스, 필드,생성자,메소드같은 패키지
private필드, 생성자, 메소드객체 내부

default는 아무것도 쓰지 않는 상태이다.

클래스의 접근 제한

클래스는 public 또는 default를 쓸 수 있다.

A.java

package ch06.sec13.exam01.package1;

class A { //<- default로 선언 
}

B.java

package ch06.sec13.exam01.package1;

public class B {
    A a; //같은 패키지라 가능
}

C.java (다른 패키지)

package ch06.sec13.exam01.package2;

import ch06.sec13.exam01.package1.*;

public class C {
    A a; //다른 패키지라 접근 불가, 
	B b; //가능
}

생성자의 접근 제한

객체를 생성하기 위해 생성자를 호출하지만, 어디에서나 할 수 있는 건 아니다.
public, default, private를 쓸 수있으며, 어떤 접근 제한자인지에 따라 호출 여부가 갈린다.

  • public일 때는 모든 패키지에서 생성자를 호출 할 수 있다.
    -> 모든 패키지에서 객체 생성 가능

  • default일 때는 같은 패키지에서만 생성자를 호출 할 수 있다.
    -> 같은 패키지에서만 객체 생성 가능

  • private는 클래스 내부에서만 생성자를 호출 할 수 있다.
    -> 클래스 내부에서만 객체 생성 가능

A.java

package ch06.sec13.exam02.package1;

public class A
{
    A a1 = new A(true);
    A a2 = new A(1);
    A a3 = new A("문자열");
    
    public A(boolean b){ }
    A(int b){ }

    private A(String s){ }
}

B.java

package ch06.sec13.exam02.package1;

public class B
{
    A a1 = new A(true);
    A a2 = new A(1);
    //A a3 = new A("문자열"); <-- private라 호출 불가
}

C.java

package ch06.sec13.exam02.package2;

import ch06.sec13.exam02.package1.A;

public class C
{
    A a1 = new A(true);
    //A a2 = new A(1); <- 다른 패키지라 불가
    //A a3 = new A("문자열"); <-private라 불가
}

필드와 메소드의 접근 제한

필드와 메소드도 어디서나 읽고 호출할 수 있는 건 아니다.

접근 제한자생성자설명
public필드, 메소드(...)모든 패키지에서 호출 할 수 있다.
필드,메소드(...)같은 패키지에서만 호출 할 수 있다.
private필드, 메소드(...)클래스 내부에서만 필드를 읽고 변경할 수 있다.

A.java

package ch06.sec13.exam03.package1;

public class A
{
    public int field1;
    int field2;
    private int field3;

    public A(){
        field1 = 1;
        field2 = 1;
        field3 = 1;
        // 모두 가능
    }
    public void method1(){

    }
    void method2(){

    }
    private void method3(){

    }
}

B.java

package ch06.sec13.exam03.package1;

public class B
{
    public void method(){
        A a = new A();

        a.field1 = 1; // 같은 패키지라 가능
        a.field2 = 2; // 같은 패키지라 가능
        //a.field3 = 3; private라 불가

        a.method1();
        a.method2();
        //a.method3(); private라 불가
    }
}

C.java

package ch06.sec13.exam03.package2;

import ch06.sec13.exam03.package1.A;

public class C
{
    public C(){
        A a = new A();

        a.field1 = 1; //다른 패키지라도 public은 가능
        //a.field2 = 2; 다른패키지, default라서 접근 불가
        //a.field3 = 3; 다른 패키지, private라서 접근 불가

        a.method1();
        //a.method2(); 다른패키지, default라서 접근 불가
        //a.method3(); 다른 패키지, private라서 접근 불가
    }
}

Getter와 Setter

객체의 필드를 외부에서 마음대로 읽고 수정하면 객체의 무결성이 깨질 수 있기 때문에 객체 지향 프로그래밍에서는 직접적인 외부에서의 필드 접근을 막고 대신 메소드를 통해 필드에 접근하는 것을 선호한다. 그래서 필드를 private로 선언하고 public으로 선언한 Getter와 Setter로 필드를 관리한다.

값을 설정 해주는 메소드를 Setter, 설정한 값을 읽어오는 메소드를 Getter라 한다.

이는 인텔리제이, 이클립스에서 자동으로 생성해 주는 기능이 있다.

package ch06.sec14;

public class Car
{
    private int speed;
    private boolean stop;

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        if(speed < 0){
            this.speed = 0;
            return;
        }
        this.speed = speed;
    }

    public boolean isStop() {
        return stop;
    }

    public void setStop(boolean stop) {

        this.stop = stop;
        if(this.stop == true) this.speed = 0;
    }
}
package ch06.sec14;

public class CarExample
{
    public static void main(String[] args) {
        Car myCar = new Car();

        myCar.setSpeed(-50); //잘못된 설정
        System.out.println("현재 속도 : "+myCar.getSpeed()); // 0 출력

        myCar.setSpeed(60);
        System.out.println("현재 속도 : "+myCar.getSpeed());

        if(!myCar.isStop()){
            myCar.setStop(true);
        }
        System.out.println("현재 속도 : "+myCar.getSpeed());

    }
}

이렇게 코드의 안정성을 높여주므로 꼭 쓰는게 좋다.


싱글톤 패턴

프로그램 전체에서 딱 하나의 객체만 생성해 사용하고 싶을 땐 싱글톤(Singleton)패턴을 적용 할 수 있다. 싱글톤 패턴의 핵심은 생성자를 private 접근 제한해서 외부에서 new연산자로 생성자를 호출 할 수 없도록 막는 것이다.

private ClassName() {...}

생성자를 호출 할 수 없으니 마음대로 객체를 생성 할 수 없다.
대신 싱글톤 패턴이 제공하는 정적 메소드를 통해 간접적으로 객체를 얻을 수 있다.

public class ClassName{
	private static ClassName singleton = new ClassName(); // 정적 필드를 미리 선언 후 초기화
    
    //private 접근 권한을 갖는 생성자 선언 
    private ClassName() {...} 
    
    //public 접근 권한을 갖는 정적 메소드 선언
    public static ClassName getInstance(){
    	return singleton;
    }
    
 }

저 getInstance()를 통해 public으로 정적 필드값을 리턴 할 수 있다.
그럼 외부에서 이 객체를 얻는 방법은 오직 getInstace() 뿐이다. 그러면 객체를 변수로 만들어도 참조하는 객체는 동일한 싱글톤 객체가 된다.

//ClassName var0 = new ClassName();<- 불가능 
ClassName var1 = ClassName.getInstance(); // 변수가 참조하는 주소값이 동일
ClassName var2 = ClassName.getInstance(); // 변수가 참조하는 주소값이 동일

연습문제

연습문제가 20개나 있는 관계로 나눠 쓰겠습니다.

profile
gotta go fast

0개의 댓글