Welcome! Everything is fine.

[Java/Study] 김영한의 실전 자바 기본 - 스터디 2회차 본문

Java

[Java/Study] 김영한의 실전 자바 기본 - 스터디 2회차

개발곰발 2024. 10. 8.
728x90

 

인프런 강의 <김영한의 실전 자바 - 기본편>을 보고 정리한 내용입니다.

매주 모여 각자 정리한 내용을 기반으로 발표하고 질문 공유하는 스터디입니다.


클래스와 데이터

  • 클래스 : 객체를 생성하기 위한 설계도로서 객체가 가져야 할 속성(변수)과 기능(메서드)을 정의한다.
  • 객체 : 클래스에서 정의한 속성과 기능을 가진 실체

학생의 이름, 나이, 성적을 저장하고 출력하려고 할 때 다음과 같이 간단하게 만들 수 있다. 만약 학생이 영원히 2명뿐이라면 이렇게 만들어도 그렇게 불편하진 않을 것이다. 하지만 학생이 늘어난다면 변수도 추가로 선언하고 출력하는 코드도 더 추가해야 한다. 학생이 1000명이라면 여기서 998명을 더 추가하고 그만큼의 출력 코드도 짜야한다는 것이다. 이런 코드의 변경을 최소화하하기 위해서는 배열을 사용할 수 있지만 수정 시 실수할 확률이 높다. 

String student1Name = "학생1";
int student1Age = 15;
int student1Grade = 90;
 
String student2Name = "학생2";
int student2Age = 16;
int student2Grade = 80;
 
System.out.println("이름:" + student1Name + " 나이:" + student1Age + " 성적:" + student1Grade);
System.out.println("이름:" + student2Name + " 나이:" + student2Age + " 성적:" + student2Grade);

 

코드의 수정을 최소화하고 학생들을 잘 관리하기 위해서는 다음과 같이 클래스를 사용하여 해결할 수 있다. 여기서 클래스에 정의한 변수들을 멤버 변수 or 필드라고 한다.

public class Student { // 클래스는 관례상 대문자로 시작 & 낙타표기법 사용
    String name;
    int age;
    int grade;
}

 

Student 클래스를 사용하는 코드를 다음과 같이 만들 수 있다.

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

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

// (4)
Student[] students = {student1, student2};
for (Student s : students) {
    System.out.println("이름: " + s.name + " 나이: " + s.age + " 성적: " + s.grade);
}

 

각 코드를 간단하게 설명하자면 다음과 같다.

(1) Student 타입의 변수 선언

(2) Student 객체(인스턴스) 생성, 참조값 반환(student1에 저장)

(3) .(점, dot)을 이용해 객체에 접근해 값 대입

(4) 배열로 만들어 출력 코드 최소화, 향상된 for문

 

배열에 객체를 보관하는 과정을 자세히 보자. 여기서 꼭 알아둬야할 점은 자바에서는 항상 변수에 들어 있는 값을 복사해서 대입한다는 점이다. 즉 student1, student2에 저장된 참조값을 복사해서 배열에 대입하는 것이다. 따라서 student1, student2에 들어있던 참조값에는 아무 변화가 없다.

Student[] students = new Student[2];
students[0] = student1;
students[1] = student2;

기본형과 참조형

변수의 데이터 타입은 크게 기본형(Primitive Type)과 참조형(Reference Type)으로 나뉜다.

  • 기본형 : 변수에 사용할 값을 직접 넣을 수 있는 데이터 타입(ex.int, long, double, boolean)
    • 해당 값을 바로 사용하거나 계산하는 것이 가능
    • null 할당 불가능
  • 참조형 : 객체가 저장된 참조값을 저장하는 데이터 타입(ex.Student student1, 배열과 같은 타입)
    • 실제 객체의 위치를 저장해서 해당 값을 그대로 사용하거나 계산하는 것이 불가능
    • null 할당 가

위에서도 언급했지만 기본형이든 참조형이든 자바에서는 항상 변수의 값을 복사해서 대입한다.

// 기본형 대입 - 값을 복사하여 대입
int a = 10;
int b = a;

// 참조형 대입 - 객체의 위치를 가르키는 참조값을 복사하여 대입(실제 사용하는 객체X)
Student s1 = new Student();
Student s2 = s1;

 

아래 Data 클래스와 그것을 사용하는 코드가 있을 때, 어떤 결과가 나올까?

public class Data {
    int value;
}
public static void main(String[] args) {
    Data dataA = new Data();
    dataA.value = 10;
    Data dataB = dataA;
    
    System.out.println("dataA 참조값=" + dataA);
    System.out.println("dataB 참조값=" + dataB);
    System.out.println("dataA.value = " + dataA.value);
    System.out.println("dataB.value = " + dataB.value);
    
    //dataA 변경
    dataA.value = 20;
    System.out.println("변경 dataA.value = 20");
    System.out.println("dataA.value = " + dataA.value);
    System.out.println("dataB.value = " + dataB.value);
    
    //dataB 변경
    dataB.value = 30;
    System.out.println("변경 dataB.value = 30");
    System.out.println("dataA.value = " + dataA.value);
    System.out.println("dataB.value = " + dataB.value);
 }

 

실행결과는 다음과 같다. dataB에 dataA를 대입할 때 해당 인스턴스가 복사된다고 생각하면 안되고, 참조값이 복사되어 들어간다고 생각해야한다.(중요☆☆☆☆☆) 따라서 dataA나 dataB의 값이을 변경해도 그 둘이 같은 것을 참조하고 있기 때문에 같은 값이 나오는 것이다.

dataA 참조값=ref.Data@x001
dataB 참조값=ref.Data@x001
dataA.value = 10
dataB.value = 10
변경 dataA.value = 20
dataA.value = 20
dataB.value = 20
변경 dataB.value = 30
dataA.value = 30
dataB.value = 30

 

이 개념을 가지고 메서드 호출도 똑같이 보면 된다. 기본형을 가지고 메서드를 호출할 때와 비교해보자.

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

 

메서드를  호출할 때 dataX에 dataA의 값을 전달한다. 즉 dataX = dataA 와  같이 대입한다고 보면 된다. 자바에서 변수에 값을 대입하는 것은 항상 값을 복사해서 대입하므로 dataX는 dataA와 같은 참조값을 가진다.

메서드 호출 전: dataA.value = 10
메서드 호출 후: dataA.value = 20

 

다음으로 변수의 종류에 대해 알아보자.

  • 멤버변수(필드)
    • 클래스에 선언
    • 인스턴스의 멤버변수는 인스턴스 생성 시 자동 초기화 됨
    • 개발자가 초기값 직접 정할 수 있음 
  • 지역변수
    • 메서드에 선언
    • 매개변수도 지역변수의 한 종류
    • 항상 직접 초기화해야 함

참조형 변수에는 객체의 위치를 가르키는 참조값이 들어 있는데, 위치가 없거나 위치를 나중에 입력하고 싶다면 null값을 넣어 표현할 수 있다. 간단한 예시를 보자.

public static void main(String[] args) {
    Data data = null;
    System.out.println("1. data = " + data);
    data = new Data();
    System.out.println("2. data = " + data);
    data = null;
    System.out.println("3. data = " + data);
    data.value = 10; // NullPointerException 발생
}

 

null 값을 할당하면 data 변수는 가르키는 객체가 없다는 것을 의미한다. 2번에서는 new 키워드를 통해 새로운 Data 객체를 할당해서 참조값을 저장했다. 마지막은 다시 null을 할당했는데 그러면 2번에서 할당한 Data 객체는 사용되지 않는다. 이렇게 참조되지 않는 객체는 메모리 용량만 차지하므로 JVM의 GC가 자동으로 제거해준다.

1. data = null
2. data = ref.Data@x001
3. data = null

 

null 값이 들어가 있는 참조형 변수에 값을 할당하려고 하면 참조할 인스턴스가 존재하지 않아 NullPointerException 에러가 발생하게 된다. 따라서 참조형 변수를 사용할 때는 참조값이 잘 할당되어 있는지 확인해야한다!

객체 지향 프로그래밍

개발을 배우면서 많이 듣는 것 중 하나가 '객체 지향 프로그래밍'이다. 객체 지향 프로그래밍이란 무엇일까? 어떤 현실에서의 사물이나 사건을 하나의 객체로 보고 객체를 중심으로 프로그래밍하는 것이라고 배웠다. 하지만 객체를 사용하기만 한다고 모두 객체 지향 프로그래밍이라고 할 수 는 없다.

  • 절차 지향 프로그래밍 : 프로그램의 흐름을 순차적으로 따르며 처리하는 방식(실행순서⭐)
  • 객체 지향 프로그래밍 : 객체들 간의 상호작용을 중심으로 처리하는 방식(객체⭐)

데이터와 기능을 하나로 묶어 객체를 생성하고, 필요한 기능(메서드)를 호출해서 사용하면 된다. 아래의 직사각형의 넓이와 둘레, 정사각형 여부를 구하는 간단한 예제를 보자. Rectangle이라는 클래스 안에 width, height를 넣고 넓이, 둘레, 정사각형 여부를 구하는 메서드와 결과를 출력하는 메서드를 묶어놓았다.

public class Rectangle {
    int width;
    int height;

    int calculateArea() {
        return width * height;
    }

    int calculatePerimeter() {
        return 2 * (width + height);
    }

    boolean isSquare() {
        return width == height;
    }

    void showArea() {
        System.out.println("넓이: " + calculateArea());
    }

    void showPerimeter() {
        System.out.println("둘레 길이: " + calculatePerimeter());
    }

    void showSquare() {
        System.out.println("정사각형 여부: " + isSquare());
    }
}

 

실제로 사용할 때는 필요한 변수에 값을 할당하고, 메서드를 이용해 결과를 얻을 수 있다. 객체 지향은 내가 직접 넓이를 구해서 출력을 하고, 다시 둘레를 구하고...하지 않고 객체에게 물어보면 된다는 것이다.

public class RectangleOopMain {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.width = 5;
        rectangle.height = 8;

        rectangle.showArea();
        rectangle.showPerimeter();
        rectangle.showSquare();
    }
}