기초

자바 기초 복기4

Bordercolli 2024. 12. 18. 12:51
728x90

객체 지향 프로그래밍 vs 절차 지향 프로그래밍

객체지향 프로그래밍과 절차지향 프로그래밍은 서로 대치되는 개념이 아니다. 객체 지향이라도 프로그램의 작동 순서는 중요하다. 다만 어디에 더 초점을 맞추는가에 둘의 차이가 있다. 객체지향의 경우 객체의 설계와 관계를 중시한다. 반면, 절차 지향의 경우 데이터와 기능이 분리되어 있고, 프로그램이 어떻게 작동하는지 그 순서에 초점을 맞춘다.

 

"절차지향 프로그래밍"

1. 실행 순서를 중요시 한다.

2. 프로그램의 흐름을 순차적으로 따르며 처리하는 방식이다. 즉, "어떻게" 중심으로 프로그래밍 한다.

 

"객체지향 프로그래밍"

1. 실제 세계의 사물이나 사건을 객체로 보고, 이러한 객체들 간의 상호작용을 중심으로 프로그래밍하는 방식. "무엇을"중심으로

 

객체란? 세상의 모든 사물들을 단순하게 추상화해보면 속성(데이터)과 기능 딱 두 가지로 설명할 수 있다.

 

자동차 

속성: 색상, 키, 온도

기능: 엑셀, 브레이크, 문 열기, 문 닫기

 

동물

속성: 색상, 키, 온도

기능: 먹는다, 걷는다...

 

객체지향 프로그래밍은 모든 사물을 속성과 기능을 가진 객체로 생각하는 것이다. 객체에는 속성과 기능만 존재한다. 이렇게 단순화하면 세상에 있는 객체들을 컴퓨터 프로그램으로 설계할 수 있다. 이러한 장점들 덕분에 지금은 객체 지향 프로그래밍이 가장 많이 사용된다.

 

[생성자]

객체를 생성하는 시점에 어떤 작업을 하고 싶다면 생성자(construct)를 이용하면 된다.

생성자를 알아보기 전에 먼저 생성자가 왜 필요한지 코드로 간단히 알아보자.

public class MethodInitMain2 {
    public static void main(String[] args) {
        MemberInit member1=new MemberInit();
        initMember(member1, "user", 15, 90);

        MemberInit member2=new MemberInit();
        initMember(member2, "user2", 16, 88);

        MemberInit[] members={member1, member2};
        for (MemberInit s : members) {
            System.out.println("이름: " + s.name + ", 나이: " + s.age + ", 성적: " + s.grade);
        }
    }

    static void initMember(MemberInit member, String name, int age, int grade){
        member.name=name;
        member.age=age;
        member.grade=grade;
    }
}

이 코드를 보면 initMember메서드를 사용해서 반복을 제거 했다. 그런데, 이 메서드는 대부분 member객체의 멤버 변수를 사용한다. 이런 경우 속성과 기능을 한 곳에 두는 것이 더 나은 방법이다. 쉽게 이야기해서 member가 자기 자신의 데이터를 변경하는 기능(메서드)를 제공하는 것이 좋다.

 

[this]

this => 이름이 똑같을 때, 가까운 스콮의 변수를 가져온다.(자신의 인스턴스를 가져온다.)

 

memberinit-initMemeber()추가

public class MemberInit {
    String name;
    int age, grade;

    void initMember(String name, int age, int grade){
        this.name=name; // null
        this.age=age;  //0
        this.grade=grade; //0
        // this를 넣지 않으면 값이 세팅되지 않는다.
    }
}
public class MethodInitMain3 {
    public static void main(String[] args) {
        MemberInit member1=new MemberInit();
        member1.initMember("user", 15, 90);

        MemberInit member2=new MemberInit();
        member2.initMember("user2", 16, 80);

        MemberInit[] members={member1, member2};
        
        for (MemberInit s : members) {
            System.out.println("이름: " + s.name + ", 나이: " + s.age + ", 성적: " + s.grade);
        }
    }
}

 

this

멤버 변수와 메서드의 매개 변수의 이름이 같으면 둘을 어떻게 구분해야 할까?

- 이 경우 멤버 변수보다 매개 변수가 코드 블럭의 더 안쪽에 있기 때문에 매개변수가 우선순위를 가진다. 따라서 initMember(String name...)메서드 안에서 name이라고 적으면 매개변수에 접근하게 된다.

- 멤버 변수에 접근하려면 앞에 this라고 붙혀주면 된다. 여기서 this는 인스턴스 자신의 참조값을 가리킨다.

 

* this를 생략하면, 가까운 지역변수를 먼저 찾고 없으면 그 다음으로 멤버 변수를 찾는다. 멤버 변수도 없으면 오류를 반환한다.

 

[생성자 - 도입]

프로그래밍을 하다 보면 객체를 생성하고 이후에 바로 초기값을 할당해야 하는 경우가 많다. 그래서 대부분의 객체 지향 언어는 객체를 생성하자마자 즉시 필요한 기능을 좀 더 편리하게 수행할 수 있도록 생성자라는 기능을 제공한다. 생성자를 사용하면 객체를 생성하는 시점에 즉시 필요한 기능을 수행할 수 있다.

생성자는 앞서 살펴본 initMember(...)와 같이 메서드와 유사하지만, 몇 가지 다른 특징이 있다.

public class MemberConstruct {
    String name;
    int age, grade;

    MemberConstruct(String name, int age, int grade){ // 이 부분이 생성자
        System.out.println("생성자 호출 name=" + name + ", age=" + age +", grade="+ grade);
        this.name=name;
        this.age=age;
        this.grade=grade;
    }
}

 

생성자와 메서드 차이

1. 생성자의 이름은 클래스 이름과 같아야 한다. 따라서 첫 글자도 대문자로 시작한다.

2. 생성자는 반환타입이 없다. 비워두어야 한다.

3. 나머지는 메서드와 같다.

public class MemberConstruct {
    String name;
    int age, grade;

    MemberConstruct(String name, int age, int grade){ // 이 부분이 생성자
        System.out.println("생성자 호출 name=" + name + ", age=" + age +", grade="+ grade);
        this.name=name;
        this.age=age;
        this.grade=grade;
    }
}

생성자 이름은 클래스 이름과 같은 것을 사용해야 한다. -> 아니면 컴파일 오류 발생함.

public class ConstructMain1 {
    MemberConstruct member1= new MemberConstruct("user1", 15, 90);
    // 객체를 생성해라, 그러면서 동시에 이 생성자를 불러라
    // 자바는 객체를 생성하자마자 매개변수를 설정할 수 있다(원큐에 가능)
}

 

public class ConstructMain1 {
    MemberConstruct member1= new MemberConstruct("user1", 15, 90);
    MemberConstruct member2= new MemberConstruct("user2", 16, 80);
    // 객체를 생성해라, 그러면서 동시에 이 생성자를 불러라
    // 자바는 객체를 생성하자마자 매개변수를 설정할 수 있다(원큐에 가능)

    MemberConstruct[] members={member1, member2};

    for (MemberConstruct s : members) {
        System.out.println("이름: " + s.name + ", 나이: " + s.age + ", 성적: " + s.grade);
    }
}

 

생성자 호출 방법: new명령어 다음에 생성자 이름과 매개 변숭 맞추어 인수를 전달한다.

* new키워드를 사용해서 객체를 생성할 때 마지막에 괄호()도 포함해야 하는 이유가 바로 생성자 때문이다. 객체를 생성하면서 동시에 생성자를 호출한다는 의미를 포함한다.

 

생성자 장점

1. 중복 호출 제거: 객체를 생성하면서 동시에 생성 직후에 필요한 작업을 한번에 처리할 수 있게 되었다.

2. 제약 - 생성자 호출 필수: 회원 객체는 생성하는 순간 초기 값을 세팅해야 한다. 아니면 큰 문제가 발생함.

(인스턴스는 안만들어도 오류가 발생하지 않지만, 생성자는 안만들면 컴파일 오류가 발생한다.)

 

 

이처럼 생성자 덕분에 회원의 이름, 나이, 성적은 항상 필수로 입력하게 된다. 따라서 아무 정보가 없는 유령 회원이 시스템 내부에 등장할 가능성이 원천 차단된다.

 

생성자를 사용하면 필수 값 입력을 보장할 수 있다. 

참고: 좋은 프로그램은 무한한 자유도가 주어지는 프로그램이 아니라 적절한 제약이 있는 프로그램이다.

[기본 생성자]

1. 매개변수가 없는 생성자를 기본 생성자라고 한다.

2. 클래스에 생성자가 하나도 없으면 자바 컴파일러는 매개변수가 없고, 작동하는 코드가 없는 기본 생성자를 자동으로 만들어준다.

3. 생성자가 하나라도 있으면, 자바는 기본 생성자를 만들지 않는다.

public class MemberDefault {
    String name;

    public MemberDefault(){
        System.out.println("생성자 호출");
    }
}
public class MemberDefaultMain {
    public static void main(String[] args) {
        MemberDefault memberDefault=new MemberDefault();
    }
}

 

기본 생성자를 왜 자동으로 만들까? 만약 자바에서 기본 생성자를 만들어주지 않으면, 생성자 기능이 필요하지 않은 경우에도 모든 클래스에 개발자가 직접 기본 생성자를 정의해야 한다. 생성자 기능을 사용하지 않는 경우도 많기 때문에 이런 편의 기능을 제공한다.

 

<정리>

1. 생성자는 반드시 호출

2. 생성자가 없으면 기본 생성자가 제공

3. "생성자가 하나라도 있으면 기본 생성자가 제공되지 않는다."  

 

[오버로딩과 this]

생성자도 메서드 오버로딩처럼 매개 변수만을 다르게 해서 여러 생성자를 제공할 수 있다.

public class MemberConstruct {
    String name;
    int age, grade;

    MemberConstruct(String name, int age){
        this.name=name;
        this.age=age;
        this.grade=50;
    }

    MemberConstruct(String name, int age, int grade){ // 이 부분이 생성자
        System.out.println("생성자 호출 name=" + name + ", age=" + age +", grade="+ grade);
        this.name=name;
        this.age=age;
        this.grade=grade;
    }
}

 

-> 이렇게 하면 기존 MemberConstruct에 생성자를 하나 추가해서 생성자가 2개가 되었다.

public class ConstructMain2 {
    public static void main(String[] args) {
        MemberConstruct member1= new MemberConstruct("user1", 15, 90);
        MemberConstruct member2= new MemberConstruct("user1", 15);  // 생성자 두번째

        MemberConstruct members[] = {member1, member2};

        for (MemberConstruct s : members) {
            System.out.println("이름: " + s.name + ", 나이: " + s.age + ", 성적: " + s.grade);
        }
    }    
}

-> 이렇게 해도 오류가 발생하지 않는다.

 

생성자를 오버로딩 한 덕분에 성적 입력이 꼭 필요한 경우에는 grade가 있는 생성자를 호출하면 되고, 그렇지 않은 경우에는 grade가 없는 생성자를 호출하면 된다. 

MemberConstruct(String name, int age){
    this.name=name;
    this.age=age;
    this.grade=50;
}

MemberConstruct(String name, int age, int grade){ // 이 부분이 생성자
    System.out.println("생성자 호출 name=" + name + ", age=" + age +", grade="+ grade);
    this.name=name;
    this.age=age;
    this.grade=grade;
}

이거 중복 제거하는 방법 -> this사용

MemberConstruct(String name, int age){
    this(name, age, 50); //자기자신의 생성자를 호출(생성자 안에서만 사용)
    // -> 이렇게 호출도 가능
}

MemberConstruct(String name, int age, int grade){ // 이 부분이 생성자
    System.out.println("생성자 호출 name=" + name + ", age=" + age +", grade="+ grade);
    this.name=name;
    this.age=age;
    this.grade=grade;
}

이때 this()라는 기능을 사용하면 생성자 내부에서 자신의 생성자를 호출할 수 있다. 참고로 this는 인스턴스 자신의 참조값을 가리킨다. 그래서 자신의 생성자를 호출한다고 생각하면 된다.

 

this()규칙

* this()는 생성자 코드의 첫 줄에만 작성할 수 있다.

MemberConstruct(String name, int age){
    System.out.println("go"); // 여기다가 this()적어야 한다.
    this(name, age, 50); // 첫 줄에 작성한 것이 아니라서 오류가 발생함
}

 

[패키지]

쇼핑몰 시스템을 개발한다고 가정하자. 다음과 같이 프로그램이 매우 작고 단순해서 클래스가 몇 개 없다면 크게 고민할 거리가 없겠지만, 기능이 추가되어 프로그램이 아주 커지면?

 

매우 많은 클래스가 등장하면서 관련 기능들을 분류해서 관리하고 싶을 것이다.

컴퓨터는 보통 파일을 분류하기 위해 폴더, 디렉토리라는 개념을 제공한다. 자바도 이런 개념을 제공하는데, 이것이 바로 패키지

 

* user - user, usermanager, userhistory

* product - product, productCatalog, productImage....

 

<패키지 사용방법>

1. 패키지 사용법을 코드로 확인

2. 패키지를 먼저 만들고 그 다음에 클래스를 만들어야 한다.

3. 패키지 위치에 주의

public class PackageMain1 {
    public static void main(String[] args) {
        Data data=new Data();
        Pack.a.User user= new Pack.a.User();
    }
}

사용자와 같은 위치: PackageMain1과 Data는 같은 pack이라는 패키지에 소속되어 있다. 이렇게 같은 패키지에 있는 경우에는 패키지 경로를 생략해도 된다.

사용자와 다른 위치: PackageMain1과 User는 서로 다른 패키지이다. 이렇게 패키지가 다르면 Pack.a.User와 같이 패키지 전체 경로를 포함해서 클래스를 적어야 한다.

 

<패키지 import>

package Pack;

import Pack.a.User; // 이거를 가져다 쓸거야

public class PackageMain1 {
    public static void main(String[] args) {
        Data data=new Data();
        User user= new User();
    }
}

패키지를 쓰고 그 다음 import를 사용할 수 있다.

import를 사용하면 다른 패키지에 있는 클래스를 가져와서 사용할 수 있다.

import를 사용한 덕분에 코드에서는 패키지 명을 생략하고 클래스 이름만 적을 수 있다.

 

한 패키지에 여러 클래스를 가져올 수도 있다.

package Pack;

import Pack.a.User;
import Pack.a.User2;  // 변경될 부분

public class PackageMain1 {
    public static void main(String[] args) {
        Data data=new Data();
        User user= new User();
        User2 user2=new User2();
    }
}

이거를

package Pack;

import Pack.a.*; // 지정된 패키지에 모든 클래스를 가져온다.

public class PackageMain1 {
    public static void main(String[] args) {
        Data data=new Data();
        User user= new User();
        User2 user2=new User2();
    }
}

 

이런 식으로 변경할 수 있다.

 

<클래스 이름 중복>

패키지 덕분에 클래스 이름이 같아도 패키지 이름으로 구분해서 같은 이름의 클래스를 사용할 수 있다.

 

import Pack.a.User;

public class PackageMain3 {
    public static void main(String[] args) {
        User user = new User();
        Pack.b.User userB=new Pack.b.User();  // b패키지의 경로를 다 적어줌
}

이렇게 이름이 겹치는 경우에는 어쩔 수 없이 둘 중 하나는 이름을 다 적어줘야 한다.

 

<패키지의 규칙>

1. 패키지의 이름과 위치는 폴더(디렉토리) 위치와 같아야 한다.(필수)

2. 패키지 이름은 모두 소문자를 사용한다.

3. 패키지 이름은 앞 부분에는 일반적인 회사의 도메인 이름을 거꾸로 사용한다. (ex) com.company.myapp)과 같이 사용

- 이 부분은 필수는 아니지만, 수 많은 라이브러리가 함꼐 사용되면, 같은 패키지에 같은 클래스 이름이 존재할 수도 있다. 이렇게 도메인 이름을 거꾸로 사용하면 이런 문제를 방지할 수 있다.

- 내가 오픈소스나 라이브러리를 만들어서 외부에 제공한다면 꼭 지키는 것이 좋다.

- 내가 만든 app을 다른 곳에 공유하지 않고, 직접 배포한다면 보통 문제가 발생x

 

패키지 == 디렉터리

 

패키지를 구성할 때는 서로 관련된 클래스는 하나의 패키지에 모으고, 관련이 적은 클래스는 다른 패키지로 분리하는 것이 좋다.

 

[접근제어자]

자바는 public, private같은 접근 제어자(access modifier)를 제공한다. 접근 제어자를 사용하면 해당 클래스 외부에서 특정 필드나 메서드에 접근하는 것을 허용하거나 제한할 수 있다.

 

ex) 여러분은 스피커에 들어가는 sw을 개발하는 개발자다.

스피커 용량은 절대로 100을 넘어가면 안된다는 요구사항이 있다.(100을 넘어가면 스피커 부품들이 고장난다.)

 

 

스피커 객체를 만들어보자. 스피커 음량을 높이고, 현재 음량을 확인할 수 있는 단순한 기능을 제공한다.

요구사항대로 스피커 음량은 100까지만 증가할 수 있다. 절대로 100을 넘어서는 안된다.

public class Speaker {
    int volume;

    Speaker(int volume){
        this.volume=volume;
    }

    void volumeUp(){
        if(volume>=100){
            System.out.println("음량을 증가할 수 없습니다. 최대 음량입니다.");
        }else{
            volume+=10;
            System.out.println("음량을 10 증가합니다.");
        }
    }
    void volumeDown(){
        volume-=10;
        System.out.println("volumeDown 호출");
    }

    void showVolume(){
        System.out.println("현재 음량: " + volume);
    }
}

 

public class SpeakerMain {
    public static void main(String[] args) {
        Speaker speaker = new Speaker(90);
        speaker.showVolume();
        speaker.volumeUp();

        speaker.showVolume();
        speaker.volumeDown();

        speaker.volumeUp();
        speaker.showVolume();
    }
}

 

새로운 개발자가 급하게 기존 코드를 이어받아서 개발을 하게 되었다. 참고로 새로운 개발자는 기존 요구사항을 잘 몰랐다. 코드를 실행해보니 이상하게 음량이 100이상 올라가지 않았다. 소리를 더 올리면 좋겠다고 생각한 개발자는 다양한 방면을 고민했다. Speaker 클래스를 보니 volume필드를 직접 사용할 수 있었다. volume필드의 값을 200으로 설정하고 이 코드를 실행한 순간 스피커의 부품들에 과부하가 걸리면서 폭팔했다.

public class SpeakerMain {
    public static void main(String[] args) {
        Speaker speaker = new Speaker(90);
        speaker.showVolume();
        speaker.volumeUp();

        speaker.showVolume();
        speaker.volumeDown();

        speaker.volumeUp();
        speaker.showVolume();

        System.out.println("volume필드 직접 접근 수정"); // 밖에서 주니까 스피커 볼륨 자의적으로 수정되었다.
        speaker.volume=200;
        speaker.showVolume();
    }
}

 

이처럼 speaker객체를 사용하는 사용자는 speaker의 volume필드와 메서드에 모두 접근할 수 있다.

앞서 volumeUp()과 같은 메서드를 만들어서 음량 100을 넘지 못하도록 기능을 개발했지만, 소용이 없다. 왜냐하면 Speaker를 사용하는 입장에서 volume필드에 직접 접근해서 원하는 값을 설정할 수 있기 떄문이다.

 

이런 문제를 근본적으로 해결하기 위해서는 volume필드의 외부 접근을 막을 수 있는 방법이 필요로 하다.

-> 이때 필요한 것이 "접근 제어자"

private int volume;  // 이렇게 수정하면 이 블럭 안에서만 필드를 접근할 수 있다.

 

이렇게 외부에서 접근하려니 빨간 글씨가 나옴.

이렇게 하면 Speaker클래스 내에서만 volume을 접근할 수 있다.

 

<접근 제어자 종류>

private: 모든 외부 호출을 막는다.

default(package-private): 같은 패키지 안에서 호출은 허용한다.(아무것도 안적으면 default)

protected:같은 패키지 안에서 호출은 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다.

public : 모든 외부 호출을 허용한다.

private -> default -> protected-> public 순으로 공개적임

"핵심은 속성과 기능을 외부로부터 숨기는 것이다."

 

package-private

접근 제어자를 명시하지 않으면, 같은 패키지 안에서 호출을 허용하는 default접근 제어자가 적용된다.

default라는 용어는 해당 접근 제어자가 기본값으로 사용되기 때문에, 붙여진 이름이지만, package-private이 더 정확한 표현이다. 왜냐하면 해당 젖ㅂ근 제어자를 사용하는 멤버는 동일한 패키지 내의 다른 클래스에서만 접근이 가능하기 때문이다. 참고로 두 용어를 함께사용한다.

 

접근 제어자 사용 위치

접근 제어자는 필드메서드, 생성자에 사용된다.

추가로 클래스 레벨에도 일부 접근 제어자를 사용할 수 있다. 이 부분은 뒤에 따로 설명

 

접근 제어자 사용 예시

private은 나의 클래스 안으로 속성과 기능을 숨길 떄 사용, 외부 클래스에서 기능을 호출할 수 없다.

default는 나의 패키지 안으로 속성과 기능을 숨길 때 사용, 외부 패키지에서 해당 기능을 호출할 수 없다.

protected는 상속 관계로 속성과 기능을 숨길 때 사용, 상속 관계가 아닌 곳에서 해당 기능을 호출할 수 없다.

public은 기능을 숨기지 않고 어디서든 호출할 수 있게 공개한다.

 

접근제어자 사용 - 필드, 메서드

public class AccessData {
    public int publicField;
    int defaultField;
    private int privateField;

    public void PublicMethod(){
        System.out.println("publicMethod 호출 "+ publicField);
    }
    void DefaultMethod(){
        System.out.println("defaultMethod 호출 "+defaultField);
    }
    private void PrivateMethod(){
        System.out.println("privateMethod 호출"+ privateField);
    }
    public void innerAccess(){
        System.out.println("내부 호출");
        publicField=100;
        defaultField=100;
        privateField=100;
        PublicMethod();
        DefaultMethod();
        PrivateMethod();
    }
}

1. 패키지 위치는 package access.a이다. 패키지 위치를 꼭 맞추어야 한다. 

2. 순서대로 public, default, private을 필드와 메서드에 사용했다.

3. 마지막에 innerAccess()가 있는데, 이 메서드는 내부 호출을 보여준다. 내부 호출은 자기 자신에게 접근하는 것이다. 따라서 private을 포함한 모든 곳에 접근할 수 있다.

package access.a;

public class AccessInnerMain {
    public static void main(String[] args) {
        AccessData data = new AccessData();
        // public호출 가능
        data.publicField = 1;
        data.PublicMethod();

        // 같은 패키지 default호출 가능
        data.defaultField = 2;
        data.DefaultMethod();

        // private호출 불가 -> 에러 발생
        // data.privateField=3;
        // data.privateMethod();

        data.innerAccess();
    }
}

1. 패키지 위치는 package access.a이다. 패키지 위치를 꼭 맞추어야 한다.

2. public은 모든 접근을 허용하기 때문에 필드, 메서드 모두 접근이 가능하다.

3. default는 같은 패키지에서 접근할 수 있다. AccessInnerMain은 AccessData와 같은 패키지이다. 따라서 default 접근 제어자에 접근할 수 있다.

4. private은 accessData내부에서만 접근할 수 있다. 따라서 호출 불가.

5. AccessData.innerAccess()메서드는 public이다. 따라서 외부에서 호출할 수 있다. innerAcess()메서드는 외부에서 호출되었지만, innerAccess()메서드는 AccessData에 포함되어 있다. 이 메서드는 자신의 private필드와 메서드에 모두 접근할 수 있다.

package access.b;

import access.a.AccessData;

public class AccessOuterMain{
    public static void main(String[] args) {
        AccessData data = new AccessData();
        // public호출 가능
        data.publicField = 1;
        data.PublicMethod();

        // 같은 패키지아 아니라서 호출 불가능!
        //data.defaultField = 2;
        //data.DefaultMethod();

         //private호출 불가 -> 에러 발생
        //data.privateField=3;
        // data.privateMethod();

        data.innerAccess();
    }
}

 

* 생성자도 접근 제어자 관점에서 메서드와 같다

 

<접근 제어자 사용 - 클래스 레벨>

규칙

1. 클래스 레벨의 접근 제어자는 public, default만 사용할 수 있다.(앞에서는 함수)

-> private, protected는 사용할 수 없다.

2. public클래스는 반드시 파일명과 이름이 같아야 한다.

- 하나의 자바 파일에 public클래스는 하나만 등장할 수 있다.

- 하나의 자바 파일에 default접근 제어자를 사용하는 클래스는 무한정 만들 수 있다.

public class PublicClass {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        DefaultClass1 class1=new DefaultClass1();
        DefaultClass2 class2=new DefaultClass2();
    }
    class DefaultClass1{  // default접근 제어자라서 같은 패키지 내부에서만 접근할 수 있다.
    }
    class DefaultClass2{
    }
}

이런 식으로 publicClass.java파일 안에 여러 클래스를 만들 수 있다.

 

[캡슐화란?]

캡슐화란? 객체지향 프로그래밍의 중요한 개념 중 하나이다. 캡슐화는 데이터와 해당데이터를 처리하는 메서드를 하나로묶어서 접근을 제한하는 것을 말한다. 캡슐화를 통해 데이터의 직접적인 변경을 방지하거나 제한할 수 있다. 캡슐화는 쉽게 이야기해서 속성과 기능을 하나로 묶고, 외부에 꼭 필요한 기능만 노출하고 나머지는 모두 내부로 숨기는 것이다.

 

이전 객체 지향 프로그래밍을 설명하면서 캡슐화에 대해 알아보았다. 이때 데이터와 데이터를 처리하는 메서드를 하나로 모으는 것에 초점을 맞추었다. 한 발짝 더 나아가 캡슐화를 안전하게 완성할 수 있게 해주는 장치가 바로 접근 제어자이다.

 

그럼 어떤 것을 숨기고 어떤 것을 노출해야 할까?

 

1. 데이터를 숨겨라

객체에는 속성(데이터)와 기능(메서드)가 있다. 캡슐화에서 가장 필수로 숨겨야 하는 것은 속성(데이터)이다. speaker와 volume예시를 떠올리자. 객체 내부의 데이터를 외부에서 함부로 접근하게 두면, 클래스 안에 데이터를 다루는 모든 로직을 무시하고 데이터를 변경할 수 있다. 결국 모든 안전망을 다 빠져나가게 된다. 따라서 캡슐화가 꺠진다.

 

우리는 자동차를 운전할때 자동차 부품을 다 열어서 그 안에 있는 속도계를 조절하지 않는다. 단지 자동차가 제공하는 엑셀 기능을 사용해서 엑셀을 밟으면 자동차가 나머지는 다 알아서 하는 것이다.

 

우리가 일상에서 사용할 수 있는 음악 플레이어를 떠올려보자. 음악 플레이어를 사용할 떄 그 내부에 들어있는 전원부나, 볼륨 상태의 데이터를 직접 수정할 일이 있을까? 우리는 그냥 음악 플레이어를 켜고, 끄고, 볼륨을 조절하는 버튼을 누를 뿐이다. 그 내부에 있는 전원부나 볼륨의 상태 데이터를 직접 수정하지 않는다. 전원 버튼을 눌렀을 때 실제 전원을 받아서 전원ㅇ르 켜는 것은 음악 플레이어의 일부이다. 볼륨을 높였을 때 내부에 있는 볼륨 장치들을 움직이고 볼륨 수치를 조절하는 것도 음악 플레이어가 스스로 해야하는 일이다. 쉽게 이야기해서 우리는 음악 플레이어가 제공하는 기능을 통해 음악 플레이어를 사용하는 것이다. 복잡하게 음악 플레이어의 내부를 까서 그 내부 데이터까지 우리가 직접 사용하는 것은 아니다.

 

"객체의 데이터는 객체가 제공하는 기능인 메서드를 통해서 접근해야 한다."

 

2. 기능을 숨겨라

객체의 기능 중에서 외부에서 사용하지 않고 내부에서만 사용하는 기능들이 있다. 이런 기능도 모두 감추는 것이 좋다. 우리가 자동차를 운전하기 위해 자동차가 제공하는 복잡한 엔진 조절 기능, 배기 기능까지 우리가 알 필요가 없다. 우리는 단지 엑셀과 핸들 정도의 기능만 알면 된다. 만약 사용자에게 이런 기능까지 모두 알려준다면, 사용자가 자동차에 대해 너무 많은 것을 알아야 한다. 사용자 입장에서 꼭 필요한 기능만 외부에 노출하자. 나머지 기능은 모두 내부로 숨기자.

 

정리하면 데이터는 모두 숨기고, 기능은 꼭 필요한 기능만 노출하는 것이 좋은 캡슐화이다.

 

[문제와 풀이]

MaxCounter클래스를 만들라

이 클래스는 최대값을 지정하고 최대값 까지만 같이 증가하는 기능을 제공

int count: 내부에서 사용하는 숫자. 초기값은 0입니다.

int max: 최대값 입니다. 생성자를 통해 입력합니다.

increment(): 숫자 하나를 증가합니다.

getCount(): 지금까지 증가한 값을 반환

 

<요구사항>

1. 접근 제어자를 사용해서 데이터를 캡슐화

2. 해당 클래스는 다른 패키지에서도 사용할 수 있어야 한다.

package access.ex;

public class MaxCounter {
    private int count=0;
    private int max;

    //생성자는 퍼블릭으로 하여 다른 패키지에서도 사용할 수 있도록 한다.
    public MaxCounter(int max){
        this.max=max;
    }

    public void increment(){
        // 검증 로직
        if(count>=max){
            System.out.println("최대값을 초과할 수 없습니다");
            return;
        }
        // 실행로직
        count++;
    }
    public int getCount(){
        return count;
    }

}

 

package access.ex;

public class CounterMain {
    public static void main(String[] args) {
        MaxCounter counter=new MaxCounter(3);
        counter.increment();
        counter.increment();
        counter.increment();
        counter.increment();
        int count= counter.getCount();
        System.out.println(count);
    }
}

 

쇼핑 카트를 만들라

ShoppingCartMain코드가 작동하도록 item, shoppingCart클래스를 완성해라.

요구사항

1. 접근 제어자를 사용해서 데이터를 캡슐화

2. 해당 클래스는 다른 패키지에서도 사용할 수 있어야 한다.

3. 장바구니에는 상품 최대 10개만 담을 수 있다.

- 10개 초과 등록시: "장바구니가 가득 찼습니다" 출력

 

item클래스

package access.ex;

public class Item {
    private String name;
    private int price;
    private int quantity;

    public Item(String name, int price, int quantity) {
        this.name = name;
        this.price = price;
        this.quantity = quantity;
    }

    public String getName(){
        return name;
    }

    public int getTotalPrice() {
        return price * quantity;
    }
}

 

shoppingCart클래스

package access.ex;

public class ShoppingCart {
    private Item[] items=new Item[10];
    private int itemcount;

    public void addItem(Item item){
        if(itemcount>=items.length){
            System.out.println("장바구니가 가득 찼습니다.");
            return;
        }
        items[itemcount]=item;
        itemcount++;
    }

    public void displayItems() {
        System.out.println("장바구니 상품 출력");
        for(int i=0; i< itemcount; i++){
            Item item=items[i];
            System.out.println("상품명" + item.getName()+ ", 합계: "+item.getTotalPrice());
        }
        System.out.println("전체 가격 합: " + calculateTotalPrice());
    }

    private int calculateTotalPrice(){
        int totalPrice=0;
        for(int i=0; i<itemcount; i++){
            Item item=items[i];
            totalPrice=item.getTotalPrice();
        }
        return totalPrice; // 전체 합계
    }
}

 

main클래스

package access.ex;

public class ShoppingCartMain {
    public static void main(String[] args) {
        ShoppingCart cart=new ShoppingCart();

        Item item1=new Item("마늘", 2000, 2);
        Item item2=new Item("상추", 3000, 4);

        cart.addItem(item1);
        cart.addItem(item2);

        cart.displayItems();
    }

}

* 윈도우 생성자 단축기 : alt + insert

 

 

 

 

'기초' 카테고리의 다른 글

JSP기초1  (1) 2024.12.19
자바 기초 복기5 -> 다시 정리  (2) 2024.12.19
자바 기초 복기3  (0) 2024.12.17
자바 기초복기2  (1) 2024.12.16
자바 기초 복기1  (3) 2024.12.15