Java Basics (3)

Wonho Kim·2025년 1월 8일

Java Basics

목록 보기
3/6

해당 시리즈의 게시물은 codelatte.io 사이트를 참고하여 정리한 내용입니다.
https://www.codelatte.io/courses/java_programming_basic

10. 객체, 클래스, 인스턴스

10-1. 클래스

객체 생성의 가장 첫 번재 단계는 클래스를 생성하는 것이다. class 키워드를 이용하여 클래스를 만들 수 있다.

class Person {

}

위에 만든 Person 클래스를 통해 객체 인스턴스를 생성할 수 있다.

Person person = new Person();

지금까지 배운 byte, short, int, long, float, double, char, boolean은 기본 자료형이지만 class를 통해 만든 자료형은 참조 자료형이라고 따로 부른다.

지금까지 우리가 사용한 참조 자료형은 String, Scanner가 있으며, 이는 이미 자바 라이브러리에 내장되어 있기 때문에 별도의 클래스 생성 없이 사용이 가능했던 것이다.

10-2. 인스턴스

위에서 잠깐 언급했지만 new 키워드를 통해 생성된 객체를 인스턴스라고 부르며, 클래스를 인스턴스화 했다고 하기도 하고, 객체를 메모리에 적재했다고 부르기도 한다.

new라는 키워드는 새로 만든다는 의미이다. 따라서 인스턴스는 생성될 때 마다 별도의 메모리 공간에 적재된다.

// person1 참조 자료형 변수에 저장된 인스턴스 참조값과
// person2 참조 자료형 변수에 저장된 인스턴스 참조값은 서로 다르다.
Person person1 = new Person();
Person person2 = new Person();

System.out.println(person1 == person2) // false
// person1 참조 자료형 변수와 person2 참조 자료형 변수는
// 같은 인스턴스를 참조하고 있으므로 같은 메모리 공간
Person person1 = new Person();
Person person2 = person1;

System.out.println(person1 == person2); // true
  • 객체는 속성과 행위를 가지고 있는 물리적인 것을 포함하여 논리적인 어떤 것, 현실 세계에 존재하는 것을 프로그램으로 구현할 대상이다.

  • 클래스는 현실 세계의 객체를 프로그래밍의 세계로 가져와서 실제로 구현한 것이다. 또한 클래스는 프로그램에서 사용할 인스턴스를 생성하기 위한 틀이라고 볼 수 있다.

  • 인스턴스는 클래스를 통해 객체를 메모리에 적재한 실체이다. 그리고 참조 자료형 변수는 인스턴스에 접근할 수 있는 참조값을 저장할 수 있다.

10-3. 객체와 배열 인스턴스

배열 변수에는 하나의 값을 저장하고 배열 인스턴스에는 배열이 저장하는 값들이 저장된다.

배열 변수에 저장되는 것은 인스턴스에 접근할 수 있는 참조값을 저장하며, 배열의 가장 첫 번째 인스턴스의 주소를 저장한다.

해당 원리를 기본 자료형(int, long, float, double 등) 뿐만 아니라 참조 자료형(String, Scanner, Person 등)에도 똑같이 적용할 수 있다.

Person[] array = new Person[6];
array[0] = new Person();
array[1] = new Person();
array[2] = new Person();

// 0x10000
// 0x100xx
// 0x10xxx
// null
// null
// null

배열 인스턴스의 각 공간에는 각 Person 인스턴스의 참조값을 저장하며, 아무것도 저장하지 않으면 기본적으로 null 값이 저장된다.

10-4. 멤버 변수(인스턴스 변수, 정적 변수)

클래스 내에 선언된 변수를 멤버 변수라고 하며, 멤버 변수에는 인스턴스 변수와 정적 변수가 있다.

// 모두 멤버 변수
class Person {
	String name; // 인스턴스 변수
    int age; // 인스턴스 변수
    String sex; // 인스턴스 변수
    static string nationality = "korea"; // 정적 변수
}

인스턴스 변수는 인스턴스 생성 후에 접근 가능한 변수를 의미하며 인스턴스 변수는 인스턴스의 생성과 소멸 생명주기와 동일하다. 다시 말해 인스턴스가 소멸하면 인스턴스 변수도 소멸한다.

Person person1 = new Person();
person1 name = "ABC"; // 인스턴스 변수
person1 sex = "female"; // 인스턴스 변수

정적(static) 변수는 인스턴스를 생성하지 않아도 접근 가능한 변수를 의미한다. 그래서 다른 말로 클래스 변수라고도 부른다.

static 키워드를 변수 자료형 앞에 붙여서 사용한다.

class Person {
	...
	static string nationality = "korea"; // 정적 변수
}

위 예시에서 nationality 정적 변수는 모든 Person 클래스에서 동일한 메모리를 참조하기 때문에 변수를 공유한다고 볼 수 있다.

정적 변수는 인스턴스 생성과 소멸 생명주기와 상관없이 프로그램이 시작되면 메모리에 적재되기 때문에, 인스턴스 생성 없이 접근이 가능하다.

String nation = Person.nationality; // 정적 변수

11. 생성자

11-1. 생성자

인스턴스를 생성하고 초기화를 간편하게 하기 위해 만들어진 개념으로, 클래스 선언 시 생성자를 작성하면 인스턴스를 생성하는 것 만으로도 동시에 초기화가 진행된다.

  1. 생성자가 없으면 인스턴스를 만들 수 없다.
  2. 생성자는 클래스 이름과 동일해야한다.
  3. 생성자는 인스턴스 생성 시 한번만 호출되는 특징을 가진다.
class Person {
    String name;
    int age;
    String sex;

    // 생성자와 매개변수 
    Person(String name, int age, String sex) { 
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

위의 생성자 매개변수 순서에 맞춰서 아래와 같이 인스턴스 생성과 동시에 멤버 변수 필드 초기화가 가능하다.

Person person = new Person("홍길동", 18, "male");

만약 지금까지 배운 것 처럼 생성자를 명시적으로 선언하지 않는 경우 생성자가 없는게 아니라 사실 default 생성자라고 해서 기본 생성자를 자동으로 생성한다.

class Person {
    String name;
    int age;
    String sex;

    // 컴파일러가 default 생성자를 주입한다.
}

11-2. this 키워드

this는 자기 자신 클래스를 지칭하는 키워드이다. 해당 키워드를 통해 클래스 스코프 내의 멤버 변수에 접근이 가능하다.

class Person {
    String name;
    int age;
    String sex;

    // 생성자와 매개변수 
    Person(String name, int age, String sex) { 
        this.name = name;
        // this.name은 클래스의 멤버 변수를 지칭하고
        //name은 매개 변수를 지칭한다.
        this.age = age;
        // this.age는 클래스의 멤버 변수를 지칭하고
        // age는 매개 변수를 지칭한다.
        this.sex = sex;
        // this.sex는 클래스의 멤버 변수를 지칭하고
        // sex는 매개 변수를 지칭한다.
    }
}

11-3. 생성자 오버로딩

여러개의 생성자를 선언하는 것을 생성자 오버로딩이라고 한다.

class Person {
    String name;
    int age;
    String gender;

    Person(String name) { 
        this.name = name;
    }

    Person(int age) {
        this.age = age;
    }

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
Person person1 = new Person("이름", 1);
Person person2 = new Person("이름");
Person person3 = new Person(1);

오버로딩 시 주의해야할 점은 같은 자료형의 매개변수로 생성자를 선언하여 오버로딩 할 수 없다.

class Person {
    String name;
    int age;
    String gender;
 
    // 같은 자료형의 매개변수로 오버로딩 할 수 없다.
    Person(String name, int age) { 
        this.name = name;
        this.age = age; 
    }

    // 같은 자료형의 매개변수로 오버로딩 할 수 없다.
    Person(String gender, int age) { 
        this.gender = gender;
        this.age = age;
    }

}

당연한 말이지만 자료형이 같으면 어떤 생성자를 호출해야할 지 알 수 없기 때문에 불가능하다.

※ this(...)

자주 쓰이진 않지만 자기 자신의 생성자를 호출 하는 방식으로 사용할 수 있다.
(this.name = name과는 다른거다)

this(...) 사용하지 않으면 아래와 같이 작성하게 되는데

class Person {
	String name;
    int age;
    String gender;
    
    Person(String name) {
    	this.name = name;
    }
    Person(String name, int age) {
    	this.name = name;
        this.age = age;
    }
    Person(String name, int age, String gender) {
    	this.name = name;
        this.age = age;
        this.gender = gender;
    }
}

코드 중복을 줄이기 위해 this(...) 키워드를 사용하면 아래와 같다.

class Person {
	String name;
    int age;
    String gender;
    
    Person(String name) {
    	this.name = name;
    }
    Person(String name, int age) {
    	this(name);
        this.age = age;
    }
    Person(String name, int age, String gender) {
    	this(name, age);
        this.gender = gender;
    }
}

this(name)은 String 자료형 매개변수 생성자 호출, this(name, age)는 String, int 자료형 매개변수 생성자를 호출하는 것을 의미한다.

12. 메서드

12-1. 메서드

메서드는 크게 4가지 메서드 형태로 존재할 수 있다.
1. 반환값이 있고 매개변수도 있는 메서드
2. 반환값이 있고 매개변수도 없는 메서드
3. 반환값이 없고 매개변수도 있는 메서드
4. 반환값이 없고 매개변수도 없는 메서드

class Person {
    String name;
    int age;
    String gender;

    // 반환값이 없고 매개변수가 있는 메서드
    void setName(String name) {
        this.name = name;
    }

    // 반환값이 있고 매개변수가 없는 메서드
    String getName() {
        System.out.println(name);
        return name;
    }
}

반환값이 없는 경우 void 키워드를 선언해야 하며, 반환값이 있는 경우 반환값 자료형을 적고 메서드 내부에 return 키워드 + 반환값이 존재해야한다.

반환값이 없는 메서드도 return 키워드를 붙여서 메서드 실행을 종료시킬 수 있다.

class Study {boolean isStuding;

    void start() {
        if (isStuding) {
            System.out.println("이미 공부중이다");
            return;
        }
        isStuding = true;
        System.out.println("공부를 시작한다");
    }
}

메서드에는 인스턴스 메서드와 정적 메서드가 존재하며, 지금까지 설명한 내용은 인스턴스 메서드이다.

Person person = new Person();
// 인스턴스 메서드의 실행
person.setName("록카");

String name = person.getName();

인스턴스 메서드는 인스턴스 변수와 동일하게 인스턴스를 생성한 후에 호출할 수 있다.

반대로 정적 메서드는 정적 변수와 같이 객체를 인스턴스화 하지 않아도 사용할 수 있는 메서드이다.

class Utils {
    static int plus(int a, int b) {
        return a + b;
    }
}
int sum = Utils.plus(1, 3);

System.out.println(sum);

정적 메서드 사용 시 주의해야할 점은 정적 메서드도 생성과 소멸의 생명주기가 인스턴스 변수와 다르므로 정적 메서드 내부에서 인스턴스 변수에 접근할 수 없다.

class Utils {
    // 인스턴스 변수
    int c = 3;
 
    static int plus(int a, int b) {
        // 인스턴스 변수 c에 접근할 수 없다 
        return a + b + c;
    }
}

하지만 아래와 같이 정적 변수의 경우에는 정적 메서드와 동일하게 프로그램 시작이 메모리에 적재되므로 접근이 가능하다.

class Utils {
    // 정적 변수
    static int c = 3;
 
    static int plus(int a, int b) {
        return a + b + c;
    }
}

12-2. 메서드 오버로딩

생성자 오버로딩과 같이 메서드도 오버로딩이 가능하다. 메서드의 이름이 같더라도 메서드에 선언된 매개변수의 개수나 자료형이 다르면 동일한 이름의 메서드를 여러 개 선언할 수 있다.

class Person {

    void setData(String name) {}

    void setData(int age) {}

    void setData(String name, int age) {}
}

그리고 이런 방식으로 메서드 사용이 가능하다.

Person person = new Person();

person.setData("이철수");
person.setData(30);
person.setData("이철수", 30);

주의해야할 점도 생성자 오버로딩과 동일하게 같은 자료형의 매개변수로 메서드를 선언하여 오버로딩이 불가능하다.

class Person {

    // 같은 자료형의 매개변수로 오버로딩 할 수 없다
    void setData(String name) {}

    // 같은 자료형의 매개변수로 오버로딩 할 수 없다
    void setData(String sex) {}

}

13. 객체의 합성(has-a)

각 객체들은 독립적인 속성과 행위를 가지고 있는데 이러한 객체들이 모여 또 하나의 객체를 만들 수 있다. 이것을 객체를 합성한다라고 부른다.

class Car {

    String name;
    Accelerator accelerator;
    Engine engine;
    Handle handle;
    Wheel wheel;

    Car(String name) {
        this.name = name;
        this.accelerator = new Accelerator(name);
        this.engine = new Engine(name);
        this.handle = new Handle();
        this.wheel = new Wheel("빠른 바퀴");
    }
}
Car car = new Car("이쁜 자동차");

자동차는 엔진, 엑셀, 핸들, 바퀴를 가지고 있으므로 has - a 관계라고 부른다. (Car has a Engine)

핵심은 Car라는 임의의 객체에 Wheel, Engine 등 임의의 객체를 멤버 변수로 선언하여 사용이 가능하다는 것만 알아두면 충분하다.

14. 패키지

자바의 패키지는 우리가 생각하는 폴더의 개념과 동일하지만 이후 설명할 접근 제어 지시자 개념과 연관되므로 해당 개념을 확실하게 숙지하도록 하자.

Car.java 파일은 codelatte.car 패키지 내에 있으므로 package 키워드와 이름이 작성되어 있다.

package codelatte.car;

보통은 에디터에서 패키지 내부에 클래스를 생성할 경우 자동으로 작성해주긴 하는데, 그래도 의미를 짚고 넘어가자면 package 키워드를 통해 클래스가 특정 패키지에 포함되어 있다고 알려주는 구문이라고 생각하면 된다.

해당 package 선언을 해야함 이후 **다른 패키지의 클래스에서 이를 사용하고 싶을 경우 import가 가능하다.

아래는 Main.java 클래스에서 Car 클래스를 사용하는 코드이다.

package codelatte;

import codelatte.car.Car;

Main 클래스는 codelatte 패키지 내에 있다는 선언문과, codelatte.car 패키지에 있는 Car 클래스 사용을 위해 import 키워드가 추가되었다.

15. 접근 제어 지시자

접근 제어 지시자를 이용하여 클래스, 변수, 메서드의 접근을 가능하게 하거나 접근하지 못하도록 막을수도 있다.

이는 자바 언어의 특징 중 하나인 캡슐화와 관련이 있는데,

캡슐 안에(클래스 안에) 들어간 내용물(내부 클래스, 멤버 변수, 메서드 등)을 보호하고 숨기고(정보 은닉)
변수에 저장된 값이 외부에서 변하지 않도록 보호하기 위함이다.

15-1. default 지시자

default는 접근 제어 지시자를 선언하지 않은 순수한 클래스, 생성자, 변수, 메서드를 말한다. default 클래스는 같은 패키지 내에서만 import가 가능하고 생성자도 같은 패키지 내에서만 생성자를 통해 인스턴스를 생성할 수 있다.

변수, 메서드 역시 같은 패키지 내에서만 접근이 가능하다.

15-2. private 지시자

private 지시자는 생성자, 변수, 메서드에 선언할 수 있고, 클래스 내부에서만 접근이 가능하도록 한다.

class Person {
    private String name;

    Person(String name) {
        this.name = name;
    }

    private void think() {
        System.out.printf("%s는 생각한다\n", name);
    }

    void eat() {
        think();
        System.out.println("그리고 밥을 먹는다");
    }
}
Person person = new Person(“록카”);
person.name = “roka”; // name 변수에 접근할 수 없다.

person.think(); // 호출 할 수 없다.

15-3. public 지시자

public 지시자는 클래스, 생성자, 변수, 메서드에 선언할 수 있고 모든 곳에서 접근 가능한 가장 열려있는 접근제어 지시자이다.

public class Person {
    public String name;

    public Person(String name) {
        this.name = name;
    }

    public void think() {
        System.out.printf("%s는 생각한다\n", name);
    }

    public void eat() {
        think();
        System.out.println("그리고 밥을 먹는다");
    }
}

15-4. protected 지시자

일반적으로 같은 패키지가 아닌 다른 패키지 내에서 접근이 불가하다. 하지만 다른 패키지라고 하더라도 상속받은 자식 클래스로는 접근이 가능하다.

protected 지시자는 상속 개념과 관련이 있으므로 추후 상속을 배우면 더 쉽게 이해할 수 있을것이다.

package com.codelatte.inheritance.dog;

public class GrandParentDog {
    String leg = "long";
    void bite() {
        System.out.println("으르릉! 깨물다");
    }
}
package com.codelatte.inheritance.dog;

public class ParentDog extends GrandParentDog {
    protected String color = "black";
    protected void bark() {
        System.out.println("왈왈!");
    }
}
package com.codelatte.inheritance.outdog;
import com.codelatte.inheritance.dog.ParentDog;

public class ChildDog extends ParentDog {
    public void info() {
        System.out.println(leg); // 접근 불가능
        System.out.println(color);
    }

    public void howling() {
        bite(); // 호출 불가능
        bark();
        System.out.println("아오오오~~");
    }
}

※ 접근 제어 지시자 정리표

접근 지시자설명선언 가능한 곳
public모든 곳에서 접근 가능, inner 클래스도 접근 가능클래스, 생성자, 변수, 메서드
protected일반적으론 같은 패키지가 아닌 다른 패키지 내에서 접근 불가, but 다른 패키지라고 하더라도 상속으로는 접근 가능생성자, 변수, 메서드
default같은 패키지가 아닌 다른 패키지 내에서 접근 불가클래스, 생성자, 변수, 메서드
private클래스 내부에서만 접근 가능inner 클래스, 생성자, 변수, 메서드
profile
새싹 백엔드 개발자

0개의 댓글