본문 바로가기
자바

자바 실전개념편 1(클래스와 데이터 ~ 생성자)

by 더하리 2024. 5. 19.

안녕하세요. 오늘은 김영한강사님의

실전 자바 기본편 강의 정리 및 요약을 해보도록 하겠습니다 :)

🙌기본적이면서도 중요한 개념들이 많이 나올 예정


 

목차

01. 클래스와 데이터
02. 기본형과 참조형
03. 객체 지향 프로그래밍
04. 생성자

 

 

 

01. 클래스와 데이터

클래스는 왜 필요한 것일까?

 

예를 들어 학생에 대한 정보를 관리하는 코드를 작성한다고 해보자.

클래스가 없다면 학생의 이름, 나이, 성적을 각각 따로 코드를 작성하여 아래와 같이 코드를 작성할 것이다.

 

하지만 이렇게 하면 특정 학생의 데이터를 변경할 때 실수할 가능성이 매우 높다.

또한 사람이 관리하기에도 이렇게 따로 관리하는 것보다는 학생이라는 개념을 하나로 묶는 것이 더욱 좋다.

 

클래스를 사용하기 위해 먼저 Student클래스를 작성한다.

public class Student {
    String name;
    int age;
    int grade;
}

→ class 키워드를 사용해서 학생 클래스( Student )를 정의한다. 

학생 클래스는 내부에 이름( name ), 나이( age ), 성적( grade ) 변수를 가진다.

    ✅이렇게 클래스에 정의한 변수를 멤버변수, 필드라고 한다.

 

 

클래스을 만들고 나면 아래처럼 사용할 수 있다. 

public class ClassStart5 {

    public static void main(String[] args) {
        Student student1 = new Student(); //x001
        student1.name = "학생1";
        student1.age = 15;
        student1.grade = 90;

        Student student2 = new Student(); //x002
        student2.name = "학생2";
        student2.age = 16;
        student2.grade = 80;

        Student[] students = new Student[]{student1, student2};

        for (int i = 0; i < students.length; i++) {
            System.out.println("이름:" + students[i].name + " 나이:" + students[i].age + " 성적:" + students[i].grade);
        }
    }

1. 변수 선언

  Student student1 

    : int 는 정수를, String 은 문자를 담을 수 있듯이 Student 는 Student타입의 객체를 담을 수 있다.

 

2. 객체 생성

→  student1 = new Student();

    : 객체 사용 전 클래스를 기반으로 객체를 생성해야 한다. 

 

3. 참조값 보관

→ student1= x001; //Student 인스턴스 참조값 보관

    new 키워드를 통해 객체가 생성되고 나면 참조값을 반환하는데 앞서 선언한 변수에 이 참조값을 보관한다.

4. 객체 사용 및 값 대입

→ student1.name="학생1";

    객체에 접근하려면 .(dot)을 사용하여 접근한다.

    x001.name = "학생1" : x001 객체가 있는 곳의 name 멤버 변수"학생1" 데이터가 저장된다.

 

 

클래스, 객체, 인스턴스 정리

클래스-Class

클래스는 객체를 생성하기 위한 '틀' 또는 '설계도'! 쉽게 생각해 붕어빵 틀이라고 생각하면 쉽다. 

객체가 가져야 할 속성(변수)과 기능(메서드)를 정의한다.

 

객체-Object

객체는 클래스에서 정의한 속성과 기능을 가진 실체이다. 말하자면 붕어빵 틀에 찍혀 만들어진 붕어빵인 것이다.

 

인스턴스 - Instance

인스턴스는 특정 클래스로부터 생성된 객체를 의미한다.

객체와의 차이를 말하자면 주로 객체가 어떤 클래스에 속해 있는지(관계) 강조할 때 사용한다.

예를 들어, student1 객체는 Student 클래스의 인스턴스다. 라고 표현한다.

 

 

리펙토링 - 배열 도입 및 선언 최적화

배열을 도입해서 특정 타입을 연속한 데이터 구조로 묶어서 편리하게 관리하자.

Student student1 = new Student(); //x001
student1.name = "학생1";
student1.age = 15;
student1.grade = 90;

Student student2 = new Student(); //x002
student2.name = "학생2";
student2.age = 16;
student2.grade = 80;

//배열 생성
Student[] students = new Student[2];
// 배열에 인스턴스 보관(참조값 대입)
students[0] = student1;
students[1] = student2;

//+ 배열 생성과 선언 동시에
Student[] students = new Student[]{student1, student2};

 

 

리펙토링 - for문 최적화

배열을 사용했기 때문에 for문을 사용해 반복 작업을 깔끔하게 처리할 수 있다.  

for (int i = 0; i < students.length; i++) { 
	Student s = students[i];
	System.out.println("이름:" + s.name + " 나이:" + s.age + ...); 
}

//향상된 for문(Enhanced For Loop)
for (Student s : students) { //단축키: iter+enter키 
	System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade); 
}

 

 


 

02. 기본형과 참조형

변수의 데이터 타입을 가장 크게 보면 기본형과 참조형으로 분류할 수 있다.

 

기본형(Primitive Type): int , long 처럼 변수에 사용할 값을 직접 넣을 수 있는 데이터 타입

  - 숫자 10 , 20 과 같이 실제 사용하는 값을 변수에 담을 수 있어 해당 값을 바로 사용할 수 있다.

참조형(Reference Type): Student student1 , int[] students 와 같이 데이터에 접근하기 위한 참조(주소)를 저장하는 데이터 타입

  - 실제 사용하는 값을 변수에 담는 것이 X

  - 객체의 위치(참조, 주소)를 저장한다.(객체와 배열이 있음)

 

변수 대입

대원칙: 자바는 항상 변수의 값을 복사해서 대입한다. 

기본형이면 변수에 들어 있는 실제 사용하는 값을 복사→대입,

참조형이면 변수에 들어 있는 참조값을 복사해서 대입!

 

변수와 초기화

변수의 종류

- 멤버 변수(필드): 클래스에 선언

public class Student { 
    String name; //멤버변수 name, age, grade
    int age; 
    int grade;
}

- 지역 변수: 메서드에 선언, 매개변수도 지역 변수의 한 종류!

public static void main(String[] args) { 
    Student student1; //지역변수: student1, student2
    student1 = new Student();
    Student student2 = new Student();
}

 

변수의 값 초기화

● 멤버 변수: 자동 초기화

   ○ 인스턴스의 멤버 변수는 인스턴스를 생성할 때 자동으로 초기화된다.(개발자가 초기값을 지정할 수 있음)

    숫자(int)=0, boolean  = false , 참조형 = null  

      ( null 값은 참조할 대상이 없다는 뜻으로 사용된다.)

 

 지역 변수: 수동 초기화

   ○ 항상 직접 초기화해야함

기본형과 메서드 호출

public class MethodChange1 {

    public static void main(String[] args) {
        int a = 10;
        System.out.println("메서드 호출 전: a = " + a);//10
        changePrimitive(a);
        System.out.println("메서드 호출 후: a = " + a);//10
    }

    static void changePrimitive(int x) {
        x = 20;
    }
}

: 값만 복사해서 다른 매개변수에 전달하는 것이므로 관련이 없음

참조형과 메서드 호출

package ref;

public class MethodChange2 {

    public static void main(String[] args) {
        Data dataA = new Data();
        dataA.value = 10;
        System.out.println("메서드 호출 전: dataA.value = " + dataA.value); //10
        System.out.println("dataA=" + dataA);
        changeReference(dataA);
        System.out.println("메서드 호출 후: dataA.value = " + dataA.value);//20
    }

    static void changeReference(Data dataX) {
        System.out.println("dataX=" + dataX);
        dataX.value = 20;
    }
}

 

참조값을 통해 x001인스턴스에 접근하고 그 안에 있는 value값을 20으로 변경했다.

dataA와 X는 모두 같은 x001인스턴스를 참조하기 때문에 dataA.value 와 dataX.value 는 둘다 20 이라는 값을 가지는 것!

 

정리 

기본형과 참조형의 메서드 호출

- 기본형: 메서드로 기본형 데이터 전달 → 해당 값 복사 - 전달  메서드 내부에서 매개변수 값 변경해도 호출자 변수값에는 영향X (단지 값만 복사해서 붙이니까!)

- 참조형: 메서드로 기본형 데이터 전달 → 해당 값 복사 - 전달  메서드 내부에서 매개변수 값 변경해도 호출자 변수값에는 영향O (같은 참조값으로 인스턴스를 참조하니까!)

 

 

null

: 참조형 변수에서 아직 가르키는 대상이 없다면 null 값을 넣을 수 있음

null은 값이 존재하지 않는, 없다의 의미를 가지고 있음

public class Data {
	int value; 
}
public class NullMain1 {
    public static void main(String[] args) { 
    	Data data = null;
    	System.out.println("1. data = " + data);
        // null
        data = new Data();
    	System.out.println("2. data = " + data);
        //(참조값) ref.Data@x001
        data = null;
    	System.out.println("3. data = " + data);
        //null
    } 
}

null에서 새로운 Data 객체를 생성해 참조값을 data변수에 할당한 후,

마지막엔 다시 null값을 할당하는 코드이다.

→ 앞서 만든 Data인스턴스 더는 참조하지 x, 다시 접근할 방법 없음

 

그렇다면 아무도 참조하지 않는 인스턴스는❓

→ JVM에서 GC(가비지 컬렉션)이 자동으로 메모리에서 제거해줌

 

NullPointerException

//예외 발생 과정
bigData.data.value
x001.data.value //bigData는 x001 참조값을 가진다. 
null.value //x001.data는 null 값을 가진다.
NullPointerException //null 값에 .(dot)을 찍으면 예외가 발생한다.

: 참조값 없이 객체를 찾아가면 발생하는 문제

해결을 위해서는 인스턴스를 만들고 참조값 할당하면 됨!

 


 

03. 객체 지향 프로그래밍

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

절차 지향 프로그램밍
: 이름처럼 절차를 지향함! 실행순서를 중요하게 생각하는 방식
  프로그램의 흐름을 순차적으로 따르며 처리하는 방식으로 "어떻게"가 중심!

객체 지향 프로그래밍
: 객체를 중요하게 생각하는 방식
  실제 세계의 사물이나 사건을 객체로 보고, 객체 간 상호작용을 중심으로 프로그래밍. "무엇을"이 중심!

 

✅가장 큰 차이는 절차지향은 데이터와 처리 방식이 분리되어 있지만 객체 지향에서는 데이터와 메서드가 하나의 '객체' 안에 포함되어 있다는 것!

 

코드로 확인해보자❗

 

절차 지향- 음악 플레이어

package oop1;

public class MusicPlayerMain1 {

    public static void main(String[] args) {
        int volume = 0;
        boolean isOn = false;

        //음악 플레이어 켜기
        isOn = true;
        System.out.println("음악 플레이어를 시작합니다");

        //볼륨 증가
        volume++;
        System.out.println("음악 플레이어 볼륨:" + volume);
        //볼륨 증가
        volume++;
        System.out.println("음악 플레이어 볼륨:" + volume);
        //볼륨 감소
        volume--;
        System.out.println("음악 플레이어 볼륨:" + volume);

        //음악 플레이어 상태
        System.out.println("음악 플레이어 상태 확인");
        if (isOn) {
            System.out.println("음악 플레이어 ON, 볼륨:" + volume);
        } else {
            System.out.println("음악 플레이어 OFF");
        }

        //음악 플레이어 끄기
        isOn = false;
        System.out.println("음악 플레이어를 종료합니다.");
    }
}

 

이를 변경해보면

 

public class MusicPlayerData { 
	//음악 플레이어에 사용되는 속성을 MusicPlayerData 멤버 변수에 포함!
	int volume = 0;
	boolean isOn = false; 
}
public class MusicPlayerMain3 {

    public static void main(String[] args) {
        MusicPlayerData data = new MusicPlayerData();
        //음악 플레이어 켜기
        on(data);
        //볼륨 증가
        volumeUp(data);
        //볼륨 증가
        volumeUp(data);
        //볼륨 감소
        volumeDown(data);
        //음악 플레이어 상태
        showStatus(data);
        //음악 플레이어 끄기
        off(data);
    }
	//!메서드 추출!
    static void on(MusicPlayerData data) {
        data.isOn = true;
        System.out.println("음악 플레이어를 시작합니다");
    }

    static void off(MusicPlayerData data) {
        data.isOn = false;
        System.out.println("음악 플레이어를 종료합니다.");
    }

    static void volumeUp(MusicPlayerData data) {
        data.volume++;
        System.out.println("음악 플레이어 볼륨:" + data.volume);
    }

    static void volumeDown(MusicPlayerData data) {
        data.volume--;
        System.out.println("음악 플레이어 볼륨:" + data.volume);
    }

    static void showStatus(MusicPlayerData data) {
        System.out.println("음악 플레이어 상태 확인");
        if (data.isOn) {
            System.out.println("음악 플레이어 ON, 볼륨:" + data.volume);
        } else {
            System.out.println("음악 플레이어 OFF");
        }
    }
}

 

위와 같이 변경하면 각각의 기능이 모듈화되었다. 

● 중복 제거: 로직 중복 제거

변경 영향 범위: 기능 수정 시 해당 메서드 내부만 변경가능

메서드 이름 추가: 코드 이해가 용이

 

하지만...

한계가 존재한다

 

데이터와 기능이 분리되어 있다는 점!

음악 플레이어의 데이터는 MusicPlayerData 에 있는데, 그 데이터를 사용하는 기능은 MusicPlayerMain3 에 있는 각각의 메서드에 분리되어 있다. 이는 유지보수 관점에서도 관리할 곳이 2곳으로 늘어나 비효율적!!

 

해서 객체 지향 프로그래밍 탄생

 

객체 지향- 음악 플레이어  

public class MusicPlayer {

    int volume = 0;
    boolean isOn = false;

    void on() {
        isOn = true;
        System.out.println("음악 플레이어를 시작합니다");
    }

    void off() {
        isOn = false;
        System.out.println("음악 플레이어를 종료합니다.");
    }

    void volumeUp() {
        volume++;
        System.out.println("음악 플레이어 볼륨:" + volume);
    }

    void volumeDown() {
        volume--;
        System.out.println("음악 플레이어 볼륨:" + volume);
    }

    void showStatus() {
        System.out.println("음악 플레이어 상태 확인");
        if (isOn) {
            System.out.println("음악 플레이어 ON, 볼륨:" + volume);
        } else {
            System.out.println("음악 플레이어 OFF");
        }
    }

}

- 클래스는 속성(데이터, 멤버 변수)과 기능(메서드)를 정의할 수 있음

- 캡슐화
: MusicPlayer처럼 속성과 기능을 하나로 묶어서 필요한 기능을 메서드를 통해 외부에 전달하는 것

public class MusicPlayerMain4 {

    public static void main(String[] args) {
        MusicPlayer player = new MusicPlayer();
        //음악 플레이어 켜기
        player.on();
        //볼륨 증가
        player.volumeUp();
        //볼륨 증가
        player.volumeUp();
        //볼륨 감소
        player.volumeDown();
        //음악 플레이어 상태
        player.showStatus();
        //음악 플레이어 끄기
        player.off();
    }
}

 

이렇게 객체지향 프로그래밍을 할 경우 속성과 기능이 한 곳에 있어 변경도 쉬워지며 코드가 더 읽기 쉬워진다.

 


 

04. 생성자 

생성자가 필요한 이유

: 객체를 생성하는 시점에 작업을 하고 싶다면 생성자(Constructor)를 이용하면 됨

public class MethodInitMain1 {

    public static void main(String[] args) {
        MemberInit member1 = new MemberInit();
        member1.name = "user1";
        member1.age = 15;
        member1.grade = 90;
        //member1.initMember("user1", 15, 90); 이렇게 가능

        MemberInit member2 = new MemberInit();
        member2.name = "user2";
        member2.age = 16;
        member2.grade = 80;

        MemberInit[] members = {member1, member2};

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

회원 객체를 생성하고 나면 name , age , grade 같은 변수에 초기값을 설정한다.

 

this.name = name; //1. 오른쪽의 name은 매개변수에 접근
this.name = "user"; //2. name 매개변수의 값 사용
x001.name = "user"; //3. this.은 인스턴스 자신의 참조값을 뜻함, 따라서 인스턴스의 멤버 변수에 접 근

● 매개변수의 이름과 맴버 변수의 이름이 같은 경우 this 를 사용해서 둘을 명확하게 구분해야 한다.

   (이름이 다른 경우라면 생략도 가능)

  this는 인스턴스 자신을 가리킨다.

 

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

    //추가
    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;
    }
}

 

생성자는 메서드와 비슷! 하지만 몇가지 차이가 있다.
  생성자의 이름은 클래스 이름과 같아야 한다. 따라서 첫 글자도 대문자로 시작한다. 
  생성자는 반환 타입이 없다. 비워두어야 한다.
  나머지는 메서드와 같다.

 

new MemberConstruct("user1", 15, 90) 

>> 이렇게 하면 인스턴스를 생성하고 즉시 해당 생성자를 호출한다

 

 

생성자를 호출하지 않아도 되는가?

No! 직접 정의한 생성자를 반드시 호출해야 한다 ← 큰 장점

 

생성자 덕분에 아무 정보가 없는 유령 회원이 시스템 내부에 등장할 가능성을 원천 차단이 가능한 것이다!

 

생성자 - 오버로딩과 this()

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

위의 코드에는 2개의 생성자가 있다.

MemberConstruct(String name, int age)
MemberConstruct(String name, int age, int grade)

오버로딩 덕분에 꼭 필요한 것만 적어도 상관없게 됨!!(grade가 없는 생성자를 호출하면 성적은 자동으로 50점)

 

+) this 규칙

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

public MemberConstruct(String name, int age) { 
	System.out.println("go"); //규칙 위반
	this(name, age, 50); 
}

 

 


 

오늘은 클래스와 데이터 ~ 생성자에 대해 알아보았습니다!

다음 시간에도 중요개념 요약 가져오도록 하겠습니다 :)