본 게시글은 대학 전공수업을 들으며 노션에 정리한 내용을 블로그로 옮긴 것으로, 에디터 차이로 인해 일부 수식, 서식, 내용 등이 빠져 있을 수 있습니다.

노션 웹을 통해 최적화된 형태로 읽으시길 권장드립니다.(➡️ 노션 링크)

4강 - static, final, 오버로딩/오버라이딩, 상속, this, super, 메소드체이닝

클래스 정의와 사용(2)

static 필드

  • 정적 필드 or 클래스 변수라고 칭함

    • 클래스의 모든 객체가 공유하는 데이터

    • 객체 생성이 없어도 항상 사용 가능

    • 어떤 객체도 값을 변경할 수 있음

    • 예시

      
        public class Circle {
      
          // 인스턴스 변수: 각 객체가 개별적으로 소유
          private int radius;
          private float pie = 3.14f;
      
          // 클래스 변수: 모든 객체가 공유
          private static int instanceCount = 0;
      
          // 생성자: 인스턴스 변수를 초기화하고, 클래스 변수 증가
          public Circle(int radius) {
            this.radius = radius;
            instanceCount++;
          }

사용법

  1. 클래스이름.정적필드 ex) Cricle.instanceCount
  2. 객체변수.정적필드 ex) c2.instanceCount

static 메소드

정적 메소드 or 클래스 메소드라고 칭함

  • non-static메소드와 달리, 객체와 무관하게 호출되고 실행된다.
  • 메소드 몸체에서 this를 사용할 수 없음.
  • static 메소드는 static 필드를 다룸!

호출 방법

클래스이름.정적메소드() ex) Math.sqrt(2.0); Integer.parseInt("12")


final 필드

상수 데이터!

선언할 때 초기값을 지정해줘야 한다.

자주 static과 함께 사용 됨.

final static doulbe PI = 3.141592;

final 메소드

  • 자식클래스로 상속은 가능
  • but 자식클래스에서 재정의(오버라이딩)할 수 없음.

필드 초기화

  • 객체를 생성할 때 데이터 필드에 초기값을 지정

  • 객체 초기화를 위해 초기화 블록을 사용할 수 있음.

    • 초기값 지정을 위한 코드. 임의 위치
    • static 필드는 static 초기화 블록을 사용.
  • 코드

      package lecture04;
    
      public class FieldInitialization {
    
        // 클래스 변수(static 변수) 선언 및 초기화
        private static int classVariable = 10;
    
        // 인스턴스 변수 선언
        private int instanceVariable;
    
        // 인스턴스 초기화 블록: 객체가 생성될 때마다 실행
        {
          instanceVariable = 20; // 인스턴스 변수 초기화
          System.out.println("인스턴스 초기화 블록 실행: instanceVariable = " + instanceVariable);
        }
    
        // static 초기화 블록: 클래스가 로드될 때 실행
        static {
          classVariable = 30; // 클래스 변수 초기화
          System.out.println("정적 초기화 블록 실행: classVariable = " + classVariable);
        }
    
        // 생성자: 객체를 생성할 때 호출되는 메서드
        public FieldInitialization() {
          System.out.println("생성자 호출: instanceVariable = " + instanceVariable);
        }
    
        public static void main(String[] args) {
          // 클래스 변수 출력
          System.out.println("클래스 변수 값: " + classVariable);
    
          // 클래스의 인스턴스 생성
          FieldInitialization obj = new FieldInitialization();
        }
      }
    

    실행 결과(순서에 유념!)

      정적 초기화 블록 실행: classVariable = 30
      클래스 변수 값: 30
    
      인스턴스 초기화 블록 실행: instanceVariable = 20
      생성자 호출: instanceVariable = 20

순서

  1. static 필드 선언문에서 초기화 (프로그램 시작)
    • 클래스 변수는 프로그램이 시작될 때 초기화 됨
  2. static 초기화 블록 실행
  3. non-static 필드 초기화 (객체 생성)
  4. non-static 초기화 블록 실행
  5. 생성자 호출

메소드 오버로딩

  • 매개변수 개수 또는 매개변수 자료형이 다르면 같은 이름의 메소드를 여러 개 정의 가능

    • 반환형과 접근제어자는 기준x
  • 예시 코드/설명

      public class PrintStream extends FilterOutputStream
          implements Appendable, Closeable {
    
      ...생략
          public void println() {
                  newLine();
              }
      ...생략
          public void println(int x) {
              if (getClass() == PrintStream.class) {
                  writeln(String.valueOf(x));
              } else {
                  synchronized (this) {
                      print(x);
                      newLine();
                  }
              }
          }
      ...생략
      }

    자바는 위처럼 오버로딩 되어있기 때문에 println() 라는 하나의 메서드가 여러 자료형을 커버해준다!


클래스의 재사용 방법 - 합성과 상속

합성

  • 기존 클래스를 새로운 클래스 정의에서 데이터 필드의 자료형으로 사용.
  • “has-a” 관계 : 자동차는 엔진을 가지고(포함하고) 있다.
  • 예시 : 자동차와 엔진 / 선과 점

핵심은!!!!

private Engine engine; // 엔진 클래스의 인스턴스를 데이터 필드로 포함
  • 예시 코드

      package lecture04;
    
      public class HasARelation {
    
        // 엔진 클래스
        public class Engine {
    
          private String type;
    
          public Engine(String type) {
            this.type = type;
          }
    
          public void start() {
            System.out.println("Engine starts");
          }
    
          // 기타 엔진 관련 메서드들...
        }
    
        // 자동차 클래스
        public class Car {
    
          private String brand;
          private String model;
          private Engine engine; // 엔진 클래스의 인스턴스를 데이터 필드로 포함
    
          public Car(String brand, String model, Engine engine) {
            this.brand = brand;
            this.model = model;
            this.engine = engine;
          }
    
          public void start() {
            System.out.println("Car starts");
            engine.start(); // 엔진을 시작하는 메서드 호출
          }
    
          // 기타 자동차 관련 메서드들...
        }
    
        public static void main(String[] args) {
          // 엔진 인스턴스 생성
          Engine engine = new HasARelation().new Engine("V4");
    
          // 자동차 인스턴스 생성 및 엔진 인스턴스 주입
          Car car = new HasARelation().new Car("Kia", "K3", engine);
    
          // 자동차 시작
          car.start();
        }
    
      }
    

    실행 결과

      Car starts
      Engine starts

    당연하게도 Car 먼저 실행 후 Engine의 메서드가 실행됨.

상속

  • 클래스의 재사용방법(2), 기존 클래스를 확장 or 특화 하는 것!
  • 부모 클래스를 사용하여 자식 클래스를 정의한다. 즉, 상속이란 부모-자식의 관계!
  • 재사용성 + 유지보수성 + 확장성.
  • “is a” 관계 : 자식 is a 부모.
  • 상속받을 때 extends 를 사용!
class Manager extends Employee {...}
//매니저도 직원이다.

자식클래스가 부모클래스의 필드와 메소드를 상속 받는다!

✅부모클래스로부터 상속받은 메소드를 재정의(오버라이딩)할 수 있음.

✅클래스 상속은 단일 상속만 가능

✅인터페이스 : 다중 상속 가능

  • 예시 코드

      public class InheritanceSuper {
    
        private int superVar1;
        public int superVar2;
    
      }
      public class InheritanceSub extends InheritanceSuper {
    
        private int subVar;
        public int subVar2;
    
      }
      public class InheritMain {
    
        public static void main(String[] args) {
          InheritanceSub sub = new InheritanceSub();
    
          //private 변수에는 접근 불가
      //    sub.superVar = 10;
          sub.superVar2 = 20;
      //    sub.subVar = 30;
          sub.subVar2 = 40;
        }
      }

    sub 객체에 부모클래스의 필드를 상속받음!

메소드 오버라이딩

  • 부모로부터 상속받은 메소드 몸체를 자식클래스에서 재정의
    • ex. 도형 클래스(부모)와 사각형 클래스(자식)일 때, get넓이()라는 도형클래스의 메소드를 사각형클래스에서 재정의함.
  • 오버라이딩 할 경우, 같은 이름이지만 다른 기능을 수행함.

✅ 메소드 이름, 인자 갯수와 자료형, 반환형이 같은 메소드이여야 한다.

  • 단, 반환형은 자식클래스도 가능

  • 접근제어자의 접근 범위는 같거나 넓어져야함.

  • 예시코드 : 도형과 사각형, getArea()메서드 오버라이딩

      public class OverRiding {
    
        public static void main(String args[]) {
          //둘 다 가능(다형성)
          Shape tri = new Triangle();
      //  Triangle tri = new Triangle();
    
          System.out.println(tri.getArea(3.0, 4.0));
        }
    
      }
    
      class Shape {
    
        public double getArea(double h, double w) {
          return h * w;
        }
      }
    
      class Triangle extends Shape {
    
        public double getArea(double h, double w) {
          return h * w * 0.5;
        }
      }

this

메소드 호출시, 숨은 인자로 this가 메소드에 전달됨.

this는 현재 객체(자기 자신)에 대한 참조값을 가지고 있음!

✅static 메소드에서는 사용 불가!

  • 예시

      // 도형 클래스
      class Shape {
          private String name;
    
          // 생성자
          public Shape(String name) {
              // 인스턴스 변수에 전달받은 이름 할당
              this.name = name;
          }
    
          // 메서드
          public void printName() {
              // 현재 도형 객체의 이름 출력
              System.out.println("Shape: " + this.name);
          }
      }
    
      // 메인 클래스
      public class Main {
          public static void main(String[] args) {
              // 객체 생성
              Shape triangle = new Shape("Triangle");
              Shape square = new Shape("Square");
    
              // 메서드 호출
              triangle.printName(); // "Shape: Triangle" 출력
              square.printName();   // "Shape: Square" 출력
          }
      }
    

    핵심부분

          public void printName() {
              // 현재 도형 객체의 이름 출력
              System.out.println("Shape: " + this.name);
          }

super

  • this와 같지만 자료형이 부모클래스의 유형.
  • 자식클래스의 인스턴스 메소드나 생성자에서 사용됨.

✅ this와 마찬가지로, static 메소드에서는 사용 불가!

  • 부모 클래스에서 오버로딩 당한 메소드를 호출하거나, 상속 되었으나 감춰진 필드에 접근할 때 필요함.

  • 예시

      class Child extends Parent {
          // 생성자
          public Child(int number) {
              super(number); // 부모 클래스의 생성자 호출
          }
    
          // 메서드
          public void printNumber() {
              super.printNumber(); // 부모 클래스의 메서드 호출
              System.out.println("Child's number: " + number);
          }
      }
    

this()

  • 같은 클래스의 다른 생성자를 호출

super()

  • 자식 클래스의 생성자에서 부모 클래스의 생성자를 호출.
  • 상속받은 데이터 필드를 초기화하기 위해 사용

✅ 자식 클래스의 생성자에서, 부모클래스 생성자 호출이 없다 ⇒ super()가 자동 호출

  • 예시 코드

      package lecture04;
    
      // 원(Circle) 클래스
      class Circle {
    
        // 인스턴스 변수: 반지름
        private double radius;
        // 클래스 변수: 파이 값 (상수)
        private static final double PI = 3.14;
    
        // 생성자: 반지름을 초기화
        public Circle(double radius) {
          this.radius = radius;
        }
    
        // 원의 넓이를 계산하는 메서드
        public double getArea() {
          return PI * radius * radius;
        }
    
        // 반지름 값을 반환하는 메서드
        public double getRadius() {
          return radius;
        }
      }
      // ------------------------------------
      // 실린더(Cylinder) 클래스: Circle을 상속받음
      class Cylinder extends Circle {
    
        // 인스턴스 변수: 높이
        private double height;
        // 클래스 변수: 파이 값 (상수)
        static final double PI = 3.14;
    
        // 기본 생성자: 반지름과 높이를 기본 값으로 초기화
        public Cylinder() {
          super(1.0); // 부모 클래스의 기본 생성자를 호출 (Circle 클래스에는 기본 생성자가 없으므로 1.0을 전달)
          height = 1.0;
        }
    
        // 매개변수가 있는 생성자: 반지름과 높이를 초기화
        public Cylinder(double radius, double height) {
          super(radius); // 부모 클래스의 생성자를 호출하여 반지름을 초기화
          this.height = height;
        }
    
        // 실린더의 겉넓이를 계산하는 메서드
        public double getArea() {
          return 2 * PI * getRadius() * height + 2 * super.getArea(); // 원기둥의 측면적과 두 개의 원의 넓이를 더함
        }
    
        // 실린더의 부피를 계산하는 메서드
        public double getVolume() {
          return super.getArea() * height; // 원의 넓이에 높이를 곱함
        }
    
      }
    
      // 메인 클래스
      public class Super {
    
        // 메인 메서드: 프로그램 실행 시작 지점
        public static void main(String args[]) {
          // 새로운 Cylinder 객체를 생성
          Cylinder cylinder = new Cylinder(3, 10);
          // 그 부피를 출력
          System.out.println("실린더 부피 : " + cylinder.getVolume()); // 반지름이 3이고 높이가 10인 실린더의 부피를 출력
        }
      }
    

메소드 체이닝

하나의 명령문에서 동일 객체에 대해 연속적으로 메소드 호출

✅ 메소드 체이닝에 쓰이는 메소드는 현재 객체의 참조값(this)를 반환해야함!

p.setName(”홍길동”).setAge(30).setAddress(”서울”)

  • 코드

    체이닝 vs 그냥

        public static void main(String[] args) {
          Person person = new Person();
          // 그냥했을떄
          person.setName("김길동");
          person.setAge(90);
          person.setAddress("부산");
          person.display();
    
          //메소드 체이닝
          person.setName("홍길동").setAge(30).setAddress("서울").display(); // Method chaining
        }
    

    전제조건 : 메소드가 현재 객체를 반환해야한다! (return this;)

      public Person setName(String name) {
          this.name = name;
          return this; // Returning the current object
        }
    
        public Person setAge(int age) {
          this.age = age;
          return this; // Returning the current object
        }
    
        public Person setAddress(String address) {
          Address = address;
          return this;
        }

    lombok을 사용한다면 @Accessors(chain = true) 달면 된다.

    (Getter, Setter는 당연 필수)


요약

  1. 클래스를 정의할 때, 클래스의 모든 객체가 공유하는 데이터는 static 데이터 필드로 정의한다.

  2. 클래스 정의에 있는 데이터 필드의 선언문, 초기화 블록, 생성자를 통해서 객체 생성 시 필요한 데이터 필드의 초깃값을 지정할 수 있다.

  3. 메소드 오버로딩이란 한 클래스에서 이름이 같은 여러 메소드가 존재하는 상황을 말한다.

  4. 이때 오버로딩된 메소드끼리는 매개변수 목록이 달라 구별될 수 있다.

  5. 부모로부터 상속받은 메소드의 몸체를 자식 클래스에서 다시 정의하는 것을 메소드 오버라이딩이라 한다.

  6. 인스턴스 메소드와 생성자에서 숨은 인자인 this를 사용할 수 있다.

  7. this( )는 같은 클래스에 있는 다른 생성자를, super( )는 자식 클래스 생성자에서 부모 클래스의 생성자를 호출하기 위한 것이다.