java

[ JAVA ] class 클래스

변쌤(이젠강남) 2024. 5. 31. 18:07
반응형

class 클래스 

 

  1. 클래스 원리
  2. this , 메소드
  3. 접근제어자
  4. 상속
  5. super
  6. 오버로딩, 오버라이딩
  7. gtter , stter 
  8. static

 

클래스, 객체

 

 

클래스와 객체 관계

 

 

 

자바프로그램은 클래스로부터 객체를 생성하여 프로그램을 작성합니다.

객체를 생성하기 위해서는 클래스를 작성하여야 합니다.

 

 

 

 

# 클래스 구성

 

클래스는 멤버변수, 생성자, 메서드 3가지 요소로 구성

 

[접근 제어자] class 클래스명 {
    // 필드
    [접근 제어자] 데이터 타입 필드명;

    // 생성자
    [접근 제어자] 클래스명(매개변수들) {
        // 생성자 코드
    }

    // 메소드
    [접근 제어자] 반환 타입 메소드명(매개변수들) {
        // 메소드 코드
    }
}

 

 

# 접근 제어자

 

접근 제어자는 클래스, 메소드, 필드 등에 대한 접근을 제어하는 키워드입니다.

 

  • public: 모든 클래스에서 접근 가능
  • protected: 같은 패키지 내부와 상속받은 클래스에서 접근 가능
  • default: 같은 패키지 내부에서만 접근 가능
  • private: 같은 클래스 내부에서만 접근 가능

 

public (공개) : 클래스, 메소드, 필드에 public 접근 제어자를 붙이면, 이는 모든 다른 클래스에서 접근 가능합니다.

public class MyClass {
    public int myField;
    public void myMethod() {
        // 메소드 내용
    }
}

 

 

private (비공개)  : private 접근 제어자를 가진 멤버는 선언된 클래스 내에서만 접근할 수 있습니다. 다른 클래스에서는 접근할 수 없습니다.

public class MyClass {
    private int myField;
    private void myMethod() {
        // 메소드 내용
    }
}

 

 

default (패키지 전용) : 접근 제어자를 명시하지 않으면 기본적으로 default 접근 제어자를 가지며, 이는 같은 패키지 내에서만 접근할 수 있습니다.

class MyClass {
    int myField; // default 접근 제어자
    void myMethod() {
        // 메소드 내용
    }
}

 

 

protected (보호) : protected 접근 제어자를 가진 멤버는 같은 패키지 내의 클래스와 이를 상속받는 하위 클래스에서 접근할 수 있습니다.

public class MyClass {
    protected int myField;
    protected void myMethod() {
        // 메소드 내용
    }
}

 

 

접근 범위 요약

접근 제어자같은 클래스 같은 패키지하위 클래스모든 클래스

접근 제어자 같은  클래스 같은  패키지 하위 클래스 모든 클래스
public O O O O
protected O O O X
default O O X X
private O X X X

 

각 접근 제어자는 클래스 멤버의 캡슐화 수준을 결정하며, 코드의 재사용성과 보안성을 높이는데 중요한 역할을 합니다.

 

 

 

객체 선언과 생성

// 1. 클래스 참조 변수 선언
MyClass myObject;

// 2. new 키워드를 사용하여 객체 생성
myObject = new MyClass();

// 3. 참조 변수에 객체 할당
// 이 단계는 위의 두 단계를 합쳐서 한 번에 작성할 수도 있습니다.
MyClass myObject = new MyClass();

 

 

 

클래스 선언 

public class Person {
    String name;
    int age;

    // 생성자
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 메소드
    public void introduce() {
        System.out.println("안녕하세요, 저는 " + name + "이고, 나이는 " + age + "살입니다.");
    }
}

 

 

객체 생성

public class Main {
    public static void main(String[] args) {
        // 객체 선언 및 생성
        Person person1 = new Person("홍길동", 25);
        Person person2 = new Person("김영희", 30);

        // 메소드 호출
        person1.introduce();
        person2.introduce();
    }
}

 

 

결과

안녕하세요, 저는 홍길동이고, 나이는 25살입니다.
안녕하세요, 저는 김영희이고, 나이는 30살입니다.

 

 

 

 

 

객체 배열 선언과 생성

public class Main {
    public static void main(String[] args) {
        // Person 객체 배열 선언 및 생성
        Person[] people = new Person[2];

        // 각 배열 요소에 Person 객체 생성
        people[0] = new Person("홍길동", 25);
        people[1] = new Person("김영희", 30);

        // 각 객체의 메소드 호출
        for (Person person : people) {
            person.introduce();
        }
    }
}

 

 

 

 

# this

this 키워드는 객체 자신을 참조하는 데 사용됩니다.

 

 

  • 인스턴스 변수와 메소드 매개변수 구분
  • 생성자에서 다른 생성자 호출
  • 현재 객체의 참조 반환

 

인스턴스 변수와 메소드 매개변수 구분

클래스의 인스턴스 변수와 메서드 매개변수의 이름이 동일할 때, this 키워드를 사용하여 인스턴스 변수를 참조할 수 있습니다.

 

 

public class Person {
    private String name;
    private int age;

    // 생성자
    public Person(String name, int age) {
        this.name = name; // this.name은 인스턴스 변수, name은 매개변수
        this.age = age;   // this.age는 인스턴스 변수, age는 매개변수
    }

    // 메소드
    public void setName(String name) {
        this.name = name; // this.name은 인스턴스 변수, name은 매개변수
    }

    public void setAge(int age) {
        this.age = age; // this.age는 인스턴스 변수, age는 매개변수
    }

    public void introduce() {
        System.out.println("안녕하세요, 저는 " + this.name + "이고, 나이는 " + this.age + "살입니다.");
    }
}

 

 

생성자에서 다른 생성자 호출

this 키워드를 사용하여 같은 클래스의 다른 생성자를 호출할 수 있습니다. 이를 통해 코드의 중복을 줄일 수 있습니다.

 

public class Person {
    private String name;
    private int age;

    // 기본 생성자
    public Person() {
        this("이름없음", 0); // 다른 생성자 호출
    }

    // 매개변수가 있는 생성자
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void introduce() {
        System.out.println("안녕하세요, 저는 " + this.name + "이고, 나이는 " + this.age + "살입니다.");
    }
}

 

 

현재 객체의 참조 반환

메소드 내에서 현재 객체의 참조를 반환할 때 this를 사용할 수 있습니다. 이는 메소드 체이닝을 구현할 때 유용합니다.

 

public class Person {
    private String name;
    private int age;

    public Person setName(String name) {
        this.name = name;
        return this; // 현재 객체의 참조 반환
    }

    public Person setAge(int age) {
        this.age = age;
        return this; // 현재 객체의 참조 반환
    }

    public void introduce() {
        System.out.println("안녕하세요, 저는 " + this.name + "이고, 나이는 " + this.age + "살입니다.");
    }

    public static void main(String[] args) {
        // 메소드 체이닝
        Person person = new Person();
        person.setName("홍길동").setAge(25).introduce();
    }
}

 

결과

안녕하세요, 저는 홍길동이고, 나이는 25살입니다.

 

 

 

# 오버로딩과 오버라이딩 

 

오버로딩 (Overloading)

오버로딩은 같은 이름의 메소드를 여러 개 정의하되, 매개변수의 타입이나 개수, 순서를 다르게 하는 것입니다. 이는 컴파일 시점에 결정되며, 주로 메소드의 이름을 재사용하면서 다양한 입력을 처리하기 위해 사용됩니다.

 

class MathUtils {
    // 두 개의 정수를 더하는 메소드
    int add(int a, int b) {
        return a + b;
    }

    // 세 개의 정수를 더하는 메소드
    int add(int a, int b, int c) {
        return a + b + c;
    }

    // 두 개의 실수를 더하는 메소드
    double add(double a, double b) {
        return a + b;
    }
}

 

 

오버라이딩 (Overriding)

오버라이딩은 부모 클래스에 정의된 메소드를 자식 클래스에서 재정의하는 것입니다. 이는 런타임 시점에 결정되며, 주로 상속 관계에서 부모 클래스의 메소드를 자식 클래스에서 재정의하여 동작을 변경하기 위해 사용됩니다.

 

class Animal {
    void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("Dog barks");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Cat meows");
    }
}

 

 

 

오버로딩과 오버라이딩의 차이점

비교 항목오버로딩 (Overloading)오버라이딩 (Overriding)

     
비교항목 오버로딩 오버라이딩
정의 같은 이름의 메소드를 매개변수의 타입, 개수, 순서를 다르게 정의하는 것 부모 클래스의 메소드를 자식 클래스에서 재정의하는 것
시점 컴파일 시점에 결정 런타임 시점에 결정
관계 같은 클래스 내에서 발생 상속 관계에 있는 클래스 간에 발생
목적 다양한 입력을 처리하기 위해 상속받은 메소드의 동작을 변경하기 위해
애노테이션 사용하지 않음 @Override 애노테이션을 사용하여 명시적으로 재정의 표시
접근 제한자 상관없음 부모 클래스의 메소드와 동일하거나 더 좁은 접근 제한자를 가질 수 없음
반환 타입 다를 수 있음 부모 클래스의 메소드와 동일하거나 더 좁은 반환 타입 (공변 반환 타입) 가능

 

 

 

 

 

# getter 와 setter 

 

gettersetter는 객체의 필드에 접근하고 수정하는 메소드입니다. 이 메소드는 캡슐화를 유지하면서 객체의 데이터를 안전하게 관리할 수 있게 해줍니다. 캡슐화는 객체 지향 프로그래밍의 중요한 원칙 중 하나로, 데이터의 직접적인 접근을 막고, 대신 메소드를 통해 접근하게 함으로써 데이터의 무결성을 유지할 수 있습니다.

 

Getter 메소드

  • 역할: 객체의 필드 값을 반환합니다.
  • 이름 규칙: 보통 get 접두사를 붙이고 필드 이름의 첫 글자를 대문자로 합니다.
public class Person {
    private String name;
    private int age;

    // Getter for name
    public String getName() {
        return name;
    }

    // Getter for age
    public int getAge() {
        return age;
    }
}

 

 

Setter 메소드

  • 역할: 객체의 필드 값을 설정(수정)합니다.
  • 이름 규칙: 보통 set 접두사를 붙이고 필드 이름의 첫 글자를 대문자로 합니다.
public class Person {
    private String name;
    private int age;

    // Setter for name
    public void setName(String name) {
        this.name = name;
    }

    // Setter for age
    public void setAge(int age) {
        this.age = age;
    }
}

 

 

 

Getter와 Setter의 사용 예제

 

public class Main {
    public static void main(String[] args) {
        // Person 객체 생성
        Person person = new Person();

        // Setter 메소드를 사용하여 필드 값 설정
        person.setName("홍길동");
        person.setAge(25);

        // Getter 메소드를 사용하여 필드 값 가져오기
        System.out.println("이름: " + person.getName());
        System.out.println("나이: " + person.getAge());
    }
}

 

 

결과

이름: 홍길동
나이: 25

 

 

장점

  1. 캡슐화: 직접 필드에 접근하지 않고 메소드를 통해 접근하므로, 데이터의 무결성을 유지할 수 있습니다.
  2. 유효성 검사: setter 메소드 안에서 값의 유효성을 검사할 수 있습니다.
  3. 읽기 전용/쓰기 전용: getter만 제공하여 읽기 전용 필드로 만들거나, setter만 제공하여 쓰기 전용 필드로 만들 수 있습니다.
  4. 디버깅 용이: 필드 접근 로직을 메소드에 넣음으로써 디버깅과 로깅을 쉽게 할 수 있습니다.

유효성 검사

 

public class Person {
    private String name;
    private int age;

    // Getter for name
    public String getName() {
        return name;
    }

    // Setter for name
    public void setName(String name) {
        if (name != null && !name.isEmpty()) {
            this.name = name;
        } else {
            System.out.println("유효한 이름을 입력하세요.");
        }
    }

    // Getter for age
    public int getAge() {
        return age;
    }

    // Setter for age
    public void setAge(int age) {
        if (age > 0) {
            this.age = age;
        } else {
            System.out.println("유효한 나이를 입력하세요.");
        }
    }
}

 

 

 

 

# 상속

상속은 객체 지향 프로그래밍(OOP)의 중요한 개념 중 하나로, 기존 클래스의 특성과 동작을 새로운 클래스에 물려주는 것을 의미합니다. 상속을 통해 코드의 재사용성을 높이고, 계층 구조를 만들어 프로그램의 구조를 더 체계적으로 만들 수 있습니다.

 

상속의 기본 개념

  • 부모 클래스 (Superclass, Base Class)
    • 다른 클래스에 상속되는 클래스입니다.
    • 일반적으로 상위 클래스라고도 합니다.
  • 자식 클래스 (Subclass, Derived Class)
    • 부모 클래스를 상속받는 클래스입니다.
    • 부모 클래스의 모든 멤버 변수와 메소드를 물려받으며, 추가적인 멤버 변수와 메소드를 가질 수 있습니다.

 

구조

 

 

상속의 원리

 

 

상속의 문법

자바에서 상속을 구현하려면 extends 키워드를 사용합니다. 자식 클래스는 부모 클래스의 멤버(필드와 메소드)를 상속받으며, 필요에 따라 이러한 멤버를 오버라이딩(재정의)할 수 있습니다.

public class 부모클래스 {
    // 필드
    private String name;

    // 생성자
    public 부모클래스(String name) {
        this.name = name;
    }

    // 메소드
    public void display() {
        System.out.println("부모 클래스: " + name);
    }
}

public class 자식클래스 extends 부모클래스 {
    // 추가 필드
    private int age;

    // 생성자
    public 자식클래스(String name, int age) {
        super(name); // 부모 클래스의 생성자 호출
        this.age = age;
    }

    // 메소드 오버라이딩
    @Override
    public void display() {
        super.display(); // 부모 클래스의 메소드 호출
        System.out.println("자식 클래스: 나이 = " + age);
    }
}

 

 

public class Animal {
    protected String name;

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

    public void makeSound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

public class Dog extends Animal {
    private String breed;

    public Dog(String name, String breed) {
        super(name); // 부모 클래스의 생성자 호출
        this.breed = breed;
    }

    @Override
    public void makeSound() {
        System.out.println(name + "가 멍멍 짖습니다.");
    }

    public void displayBreed() {
        System.out.println(name + "는 " + breed + "입니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog myDog = new Dog("바둑이", "진돗개");
        myDog.makeSound(); // 오버라이딩된 메소드 호출
        myDog.displayBreed();
    }
}

 

바둑이가 멍멍 짖습니다.
바둑이는 진돗개입니다.

 

 

상속의 장점

  1. 코드 재사용성 증가: 기존 클래스의 코드를 재사용할 수 있어 중복 코드를 줄이고 유지보수를 쉽게 합니다.
  2. 계층 구조 형성: 상속을 통해 클래스 간의 계층 구조를 형성하여 체계적인 코드 구조를 만들 수 있습니다.
  3. 다형성 (Polymorphism): 상속을 통해 다형성을 구현할 수 있으며, 이는 코드의 유연성과 확장성을 높여줍니다.

상속의 단점

  1. 강한 결합: 상속은 부모 클래스와 자식 클래스 간의 강한 결합을 초래할 수 있어, 부모 클래스의 변경이 자식 클래스에 영향을 미칠 수 있습니다.
  2. 설계의 어려움: 적절한 계층 구조를 설계하는 것이 어려울 수 있으며, 잘못된 상속 구조는 코드의 복잡성을 증가시킬 수 있습니다.

상속 관련 주요 용어

  • 오버라이딩 (Overriding): 자식 클래스가 부모 클래스의 메소드를 재정의하는 것.
  • super: 부모 클래스의 멤버에 접근하거나 부모 클래스의 생성자를 호출할 때 사용.
  • 다형성 (Polymorphism): 같은 타입의 객체가 다양한 형태로 동작할 수 있게 하는 특성. 보통 상속과 함께 사용됨.

 

# 메소드 오버라이딩

 

메소드 오버라이딩(Method Overriding)은 객체 지향 프로그래밍(OOP)에서 상속받은 부모 클래스의 메소드를 자식 클래스에서 재정의하는 것을 의미합니다. 이를 통해 자식 클래스는 부모 클래스의 메소드 기능을 자신에 맞게 변경할 수 있습니다. 메소드 오버라이딩은 다형성(Polymorphism)을 구현하는 중요한 방법 중 하나입니다.

 

메소드 오버라이딩의 규칙

  1. 메소드 시그니처 동일: 오버라이딩하는 메소드는 부모 클래스의 메소드와 동일한 이름, 매개변수 리스트, 반환 타입을 가져야 합니다.
  2. 접근 제어자: 오버라이딩하는 메소드의 접근 제어자는 부모 클래스의 메소드보다 더 좁은 범위로 설정할 수 없습니다. 예를 들어, 부모 클래스의 메소드가 public이면 자식 클래스의 메소드도 public이어야 합니다.
  3. 예외: 자식 클래스의 메소드는 부모 클래스의 메소드보다 더 많은 예외를 던질 수 없습니다.

@Override 어노테이션

@Override 어노테이션은 자바 컴파일러에게 해당 메소드가 부모 클래스의 메소드를 오버라이딩하고 있음을 알리는 역할을 합니다. 이는 오타나 잘못된 메소드 시그니처로 인한 오류를 방지하는 데 도움이 됩니다.

 

 

부모 클래스 메소드 호출

자식 클래스에서 부모 클래스의 메소드를 호출하려면 super 키워드를 사용합니다.

 

 

오버라이딩과 오버로딩의 차이점

  • 오버라이딩 (Overriding): 상속 관계에서 자식 클래스가 부모 클래스의 메소드를 재정의하는 것.
  • 오버로딩 (Overloading): 같은 클래스 내에서 같은 이름의 메소드를 여러 개 정의하되, 매개변수의 타입, 개수, 순서가 다르게 하는 것.

 

메소드 오버라이딩은 자바에서 다형성을 구현하는 중요한 방법입니다. 이를 통해 자식 클래스는 부모 클래스의 메소드를 재정의하여 더 구체적이고 적합한 동작을 구현할 수 있습니다. @Override 어노테이션을 사용하면 코드의 가독성과 안정성을 높일 수 있습니다.

 

 

# super

 

super 키워드는 자식 클래스에서 부모 클래스의 멤버(변수, 메소드)나 생성자에 접근할 때 사용됩니다.

 

 

  • 부모 클래스의 변수에 접근: 자식 클래스에서 부모 클래스의 변수를 사용할 때.
  • 부모 클래스의 메소드 호출: 자식 클래스에서 부모 클래스의 메소드를 호출할 때.
  • 부모 클래스의 생성자 호출: 자식 클래스의 생성자에서 부모 클래스의 생성자를 호출할 때.

 

부모 클래스의 변수에 접근

부모 클래스의 인스턴스 변수를 자식 클래스에서 사용할 때 super를 사용할 수 있습니다.

 

부모 클래스의 메소드 호출

자식 클래스에서 부모 클래스의 메소드를 호출할 때 super를 사용할 수 있습니다.

 

부모 클래스의 생성자 호출

자식 클래스의 생성자에서 부모 클래스의 생성자를 호출할 때 super를 사용합니다. 이는 부모 클래스의 필드를 초기화하거나 부모 클래스의 생성자에서 수행하는 작업을 수행하기 위해 필요합니다.

 

 

문제

 

class

 

 

1. 필드명 : private 

2. getter , setter : 값 가져오기, 변경하기 

3. printBookInfo : 책정보 출력 메서드 

 

 

 

 

# static

 

1. 메모리 절약

  • 공유 데이터: 클래스의 모든 인스턴스가 동일한 데이터를 공유해야 할 때 static 변수를 사용합니다. 이를 통해 동일한 데이터를 여러 번 저장할 필요가 없어 메모리를 절약할 수 있습니다.
  • 예시: 모든 인스턴스가 같은 상수를 공유할 때 사용합니다. 예를 들어, 모든 동물의 왕국 이름이 "Animalia"인 경우.

2. 공통 기능 제공

  • 유틸리티 메서드: 특정 객체와 무관하게 공통적인 기능을 제공하는 메서드를 정의할 때 static 메서드를 사용합니다. 이러한 메서드는 인스턴스를 생성하지 않고도 호출할 수 있습니다.
  • 예시: 수학적 계산을 위한 Math 클래스의 Math.sqrt()와 같은 메서드들.

3. 클래스 수준의 접근

  • 클래스 변수 및 메서드: 특정 객체가 아닌 클래스 수준에서 접근할 수 있는 변수나 메서드를 정의할 때 static을 사용합니다. 이를 통해 인스턴스 없이도 클래스 자체로 접근할 수 있습니다.
  • 예시: 생성된 객체의 수를 추적하는 카운터 변수.

4. 초기화 블록

  • 정적 초기화 블록: 클래스가 처음 로드될 때 단 한 번 실행되는 초기화 블록을 정의할 때 사용합니다. 이를 통해 클래스 변수의 복잡한 초기화를 할 수 있습니다.
  • 예시: 데이터베이스 연결 설정과 같은 리소스를 클래스 로드 시 초기화하는 경우

 

 

Animal 클래스 

public class Animal {
    // 모든 동물이 공유하는 static 변수
    private static String kingdom = "Animalia"; // 동물의 왕국 (모든 동물에게 동일한 값)
    
    // 인스턴스 변수
    private String name;
    private String species;

    // 생성자
    public Animal(String name, String species) {
        this.name = name;
        this.species = species;
    }

    // static 메서드: 왕국 이름 반환
    public static String getKingdom() {
        return kingdom;
    }

    // static 메서드: 왕국 이름 설정
    public static void setKingdom(String newKingdom) {
        kingdom = newKingdom;
    }

    // 인스턴스 메서드: 동물의 이름을 반환
    public String getName() {
        return name;
    }

    // 인스턴스 메서드: 동물의 종을 반환
    public String getSpecies() {
        return species;
    }
}

 

 

 

AnimalTest 

    // main 메서드: static 멤버 사용 예시
    public static void main(String[] args) {
        // Animal 클래스의 static 변수와 메서드 사용
        System.out.println("동물의 왕국: " + Animal.getKingdom()); // 출력: 동물의 왕국: Animalia

        Animal animal1 = new Animal("사자", "Felidae");
        Animal animal2 = new Animal("코끼리", "Elephantidae");

        System.out.println("동물 1: " + animal1.getName() + " - " + animal1.getSpecies());
        System.out.println("동물 2: " + animal2.getName() + " - " + animal2.getSpecies());

        // static 메서드를 사용하여 왕국 이름 변경
        Animal.setKingdom("Animal Kingdom");

        System.out.println("변경된 동물의 왕국: " + Animal.getKingdom()); 
        // 출력: 변경된 동물의 왕국: Animal Kingdom
    }

 

 

  • static 변수 kingdom:
    • kingdom 변수는 모든 Animal 인스턴스가 공유하는 변수입니다.
    • Animalia라는 값으로 초기화되어 있으며, 모든 동물에게 동일한 왕국을 나타냅니다.
    • static 변수이기 때문에 클래스의 모든 인스턴스가 이 변수를 공유합니다.
  • static 메서드 getKingdom():
    • 이 메서드는 현재 kingdom 값을 반환합니다.
    • static 메서드이기 때문에 Animal 클래스의 인스턴스를 생성하지 않고도 호출할 수 있습니다.
  • static 메서드 setKingdom(String newKingdom):
    • 이 메서드는 kingdom 값을 새 값으로 설정합니다.
    • static 메서드이기 때문에 인스턴스 생성 없이 호출할 수 있습니다.
  • 생성자:
    • 생성자는 동물의 이름과 종을 매개변수로 받아 해당 인스턴스 변수를 초기화합니다.
  • 인스턴스 메서드 getName()와 getSpecies():
    • 이 메서드들은 각각 동물의 이름과 종을 반환합니다.
  • main 메서드:
    • main 메서드에서는 Animal 클래스의 static 변수와 메서드를 사용하여 왕국 이름을 출력하고 변경합니다.
    • 또한, 두 Animal 인스턴스를 생성하고 각각의 정보를 출력합니다.

 

 

 

 

반응형