Home [Java 프로그래밍] 4강 - 클래스와 상속
Post
Cancel

[Java 프로그래밍] 4강 - 클래스와 상속

💡해당 게시글은 방송통신대학교 김희천 교수님의 'Java 프로그래밍' 강의를 개인 공부 목적으로 메모하였습니다.



학습 목차


  1. 클래스 정의와 사용
  2. 상속

학습 개요


  • 클래스를 정의하고, 정의된 클래스를 사용하는 것에 관해 살펴봄
  • 생성자를 정의하는 방법과 static 필드와 메소드 및 final 필드와 메소드의 의미 학습
  • 객체의 생성과 초기화 및 메소드의 오버로딩 학습
  • 기존 클래스를 이용해 새로운 클래스 정의하기 위한 클래스 상속 개념 이해

학습 목표


  1. 클래스 정의 문법 숙지
  2. staticfinal 키워드의 의미와 사용법 이해
  3. 메소드 오버로딩과 오버라이딩 구분하고 적용 가능
  4. 상속 개념 활용해 클래스 정의



1. 클래스 정의와 사용


1-1. 메소드 정의

  • 클래스 정의 내부에 존재
  • 해더와 몸체로 구성
    ex) void f( ) { }
  • 메소드 정의 문법
    1
    2
    3
    4
    5
    
      [접근 제어자] 반환형 메소드이름([자료형 인자[, 자료형 인자...]]) // 헤더
      		                            [throws 예외이름] // 예외 처리
      	{ // 메소드의 몸체
      		문장 ...
      	}
    
    1
    2
    3
    4
    5
    6
    7
    
      public double getArea() {
      	return radius * radius * PI;
      }
        
      public void setRadius(int r) { // int 값 입력받는 메소드, return 값 없음
      	radius = r;
      }
    

1-2. 생성자

  • 객체가 생성될 때 자동으로 실행되는 메소드
    • 객체의 필드 값을 초기화 하거나 메모리 할당 등의 작업
  • 객체 생성 방법은 new 클래스 이름(인자..)
    ex) Circle c = newCircle(5);
    • new 연산자를 이용해 객체 생성 (메모리 할당)
    • 생성자가 호출 (데이터 필드의 초기화)되면서
    • 객체의 참조 값을 변수에 대입(=)

1-3. 생성자 정의

  • 보통의 메소드와 정의 방법 다름
    • 생성자는 new로 객체 생성 시 자동 호출
  • 정의 방법
    • 생성자 이름은 클래스 이름과 같음
    • 반환형을 선언하지 않음
    • 여러 생성자 정의 가능(생성자 오버로딩)
      • 하나의 클래스에 여러개의 생성자 정의 가능
        • 이름이 같은 메소드가 하나의 클래스 안에 중복해서 정의 가능
      • 인자의 개수인자의 자료형으로 구분
    • 접근 제어자는 보통 public
  • 생성자 정의 문법
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      class Circle { // 클래스 이름과 생성자 이름이 같음
      	double r;
      	public Circle(double a) { // 생성자 정의
      		r = a; // 반환형 존재하지않음
      	}
        	
      	public double getArea() {
      		return r * r * 3.14;
      	}
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      public class CircleArea2 {
      	public static void main(String args[])
      	{
      		Circle c = new Circle(5.0); // 생성자 호출
        
      		System.out.println(c.r); // r은 접근 제어자가 없어 같은 패키지의 다른 클래스에 존재하는 r 사용 가능
      		System.out.println(c.getArea());
      	}
      }
    

1-4. 기본 생성자

  • 인자가 없는 생성자, 디폴트 생성자(default construcor)
  • 클래스 정의에 한 개의 생성자 정의도 없으면 컴파일러가 다음과 같은 것을 자동으로 생성
    • public Circle() {}
    • 생성자 몸체의 첫 줄에 부모 생성자의 명시적 호출이 없다면 다음 코드가 자동으로 들어감
      • super(); // 부모 클래스의 기본 생성자를 호출
      • 따라서 부모 클래스에서 기본 생성자의 존재를 확인해야함
      • 자식 클래스의 생성자 정의 첫 줄에 부모 생성자의 호출이 존재하지 않을 경우 부모 클래스의 기본 생성자가 자동 호출

1-5. 클래스의 사용

  • 상속을 위해 사용
    • class CSub extends CSuper { }
      • Csub
        • 새로운 클래스
        • 자식 클래스
      • CSuper
        • 기존 클래스
        • 부모 클래스
  • 변수를 선언하고 객체 생성
    • 클래스 참조형 변수의 선언
      • Circle c; // 어딘가에 Circle 클래스에 대한 정의 존재
      • c = new Circle(-5.;

1-6. 객체의 사용

  • 객체 변수와 점(.) 연산자를 사용하여 멤버에 접근
    • 객체가 소유하는 데이터(인스턴스 변수)를 읽거나 쓰기
    • 객체를 이용하여 메소드(인스턴스 메소드)를 호출
      ex) c.r = 5; // c의 r을 변경, 객체 사용
      ex) c.getArea() // c에게 getArea() 실행을 요청
      1
      2
      3
      
        public double getArea() {
        	return this.r * this.r * 3.14;
        }
      

1-7. static 필드

  • static 필드
    image
    1
    2
    3
    
      class Circle {
      	static int count;
      }
    
    • 정적 필드 or 클래스 변수
    • 클래스의 모든 객체가 공유하는 데이터
      • 객체의 생성이 없어도 항상 사용 가능
      • 어떤 객체도 값을 변경할 수 있음
    • 사용 방법
      • 클래스이름.정적필드
        • 객체변수.정적필드도 가능

1-8. static 메소드

  • static 메소드
    • 정적 메소드 or 클래스 메소드
    • 객체에 무관하게 호출되고 실행
      • 메소드의 몸체에서 this사용 불가
    • static 필드와 인자 가지고 작업
    • 사용 방법
      • 클래스이름.정적메소드()
        ex) Math.sqrt(2.0);
        ex) Integer.parselnt("102");

1-9. final 필드와 final 메소드

  • final 필드
    • 상수 데이터 선언
    • 선언할 때 초기 값 지정 필요
    • static과 자주 함께 사용
      • final static double PI = 3.141592;
  • final 메소드
    • 자식 클래스로 상속은 가능하나 재정의 할 수 없는 메소드

1-10. 객체 초기화

  • 객체 생성 시 데이터 필드에 초기값 지정하는 것
    • 클래스 변수는 프로그램 시작 시에 자동 초기화
  • 데이터 필드는 자동으로 초기값이 주어질 수 있음
  • 방법 (순서)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      class IniTest {
      	int nValue  = 1; // 초기값 선언하지 않을경우 0으로 자동 초기화
      	{
      		nValue  = 2; // 초기값 변경
      	}
      	public IniTest() {
      		nValue = 3; // 초기값 변경
      	}
      }
    
    • static(클래스 변수) 필드의 선언문에서 초기화
    • static(클래스 변수) 초기화 블록 실행
      1
      2
      3
      4
      
        	static int nValue  = 1;
        	static {
        		nValue  = 2; // 클래스 변수 초기화 블록
        	}
      
    • non-static(인스턴스 변수) 필드의 선언문에서 초기화
    • non-static(인스턴스 변수) 초기화 블록 실행
      1
      2
      3
      4
      
        	int nValue  = 1;
        	{
        		nValue  = 2; // 인스턴스 변수 초기화 블록
        	}
      
      • 클래스 몸체 내 임의 위치에 포함
      • 초기값 지정을 위한 코드
      • static 필드는 static 블록을 사용
    • 생성자 실행
  • 객체 초기화 코드
    image
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
      import java.awt.Point;
        
      class Rectangle {
      	public int width = 0; // non-static 변수 초기화
      	public int height = 0;
      	public Point origin; // Point 클래스 : 좌표상의 어떤 위치를 나타내는데 사용하는 클래스
        
      	public Rectangle() {
      		origin = new Point(0,0);
      	}
        
      	public Rectangle(Point p, int w, int h) { // 생성자를 사용해 데이터 필드 초기화
      		origin = p;
      		width = w;
      		height = h;
      	}
      }
    
    1
    2
    3
    4
    5
    6
    
      public class Test {
      	public static void main(String args[]) {
      		Point originOne = new Point(23, 94); // Point 클래스에 x와 y좌표값을 저장하기 위한 멤버 변수 존재
      		Rectangle rectOne = new Rectangle(originOne, 100, 200);
      	}
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
      package kr.ac.knou.mxxikr;
        
      import java.awt.Point;
        
      public class RectangleTest {
      	public static void main(String args[]) {
      		Point originOne = new Point(23, 94); // Point 클래스에 x와 y좌표값을 저장하기 위한 멤버 변수 존재
        		
      		Rectangle rectOne = new Rectangle(originOne, 100, 200); // 매개변수가 3개인 Rectangle 객체 생성
      		Rectangle rectTwo = new Rectangle(); // 매개변수가 없는 Rectangle 객체 생성
      		Rectangle rectThree = new Rectangle(rectTwo.origin, 200, 300);
        		
      		System.out.println("rectOne origin: "+ rectOne.origin); // Rectangle(Point p, int w, int h) 생성자 호출
      		System.out.println("rectOne width: "+ rectOne.width);
      		System.out.println("rectOne height: "+ rectOne.height + "\n");
        
      		System.out.println("rectTwo origin: "+ rectTwo.origin + "\n"); // Rectangle() 생성자 호출
        
      		System.out.println("rectThree origin: "+ rectThree.origin); // Rectangle(Point p, int w, int h) 생성자 호출
      		System.out.println("rectThree width: "+ rectThree.width); 
      		System.out.println("rectThree height: "+ rectThree.height);
      	}
      }
    

1-11. 메소드 오버로딩

  • 인자의 개수나 인자의 자료형이 다르면 같은 이름의 메소드를 한 클래스에서 여러개 정의 가능
    • 인자의 개수와 자료형이 정확히 일치하면 중복 정의 불가
    • 메소드 시그니처를 가지고 메소드 구분
      • 메소드의 이름, 매개 변수 리스트
  • 메소드를 호출할 때, 가장 가까운 매개변수 목록을 가진 메소드 호출
    ex) System.out.println(); // 인자 없음
    ex) System.out.println("문자열"); // 인자는 String
    ex) System.out.println(241); // 인자는 int
    ex) System.out.println(34.5); // 인자는 double
    ex) mc.add(10, 10.5); // add(int, int)와 add(double, double) → 자동 형변환 후 add(double, double) 호출
  • 클래스와 객체의 사용
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      class Cylinder {
      	private Circle c; // 원 (Circle 클래스 변수 정의)
      	private int h; // 높이
        	
      	public Cylinder(Circle a, int b) {
      		c = a;
      		h = b;
      	}
        
      	public double getVolume() {
      		return c.getArea() * h;
      	}
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    
      public class CylinderVolume {
      	public static void main(String args[]) {
      		Circle c = new Circle(7); // Circle 객체 생성
      		int h = 8;
      		Cylinder cy = new Cylinder(c, h);
      		System.out.println(cy.getVolume());
      	}
      }
    



2. 상속


2-1. 클래스의 재사용

  • 합성
    • 기존 클래스를 새로운 클래스에서 데이터 필드의 자료형으로 사용
    • has-a 관계
    • class Line { Point begin, end; }
  • 상속
    • 기존 클래스(부모)를 사용해 새로운 클래스(자식)를 정의
    • 코드의 중복 작성을 줄이고 프로그램의 확장성 증가
    • 기존 클래스를 확장 or 특화하는 것
    • 자식 is -a 부모의 관계

2-2. 클래스의 상속

  • 상속은 부모 클래스와 자식 클래스 간의 관계
    • 자식 클래스가 부모 클래스의 필드와 메소드를 상속 받음
    • 기존 클래스를 상속받을 때 키워드 extends 사용
      ex) class Manager extends Employee {} // 자식 클래스(새로운 클래스)-Manager 부모클래스(기존 존재 클래스)-Employee
    • 자식 클래스에서 상속받은 메소드 오버라이딩(재정의) 가능
  • 클래스의 상속단일 상속만 가능
    • 인터페이스 상속의 경우는 다중 상속 가능
  • 클래스의 상속
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      class CSuper {
      	private int f1;
      	public int f2;
      	public void setPrivate() { f1 = 10; }
      	public void setPublic() { f2 = 20; }
      	private void mPrivate() { f1 = 30; }
      }
        
      class CSub extends CSuper {
      	private int f3;
      	public int f4;
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      public class InheritTest {
      	public static void main(String args[]) {
      		CSub sub  = new CSub();
      		sub.f1 = 40; // 오류 발생
      		sub.f2 = 50;
      		sub.f3 = 60; // 오류 발생
      		sub.f4 = 70;
      		sub.setPrivate();
      		sub.setPublic();
      		sub.mPrivate(); // 오류 발생
      	}
      }
    

2-3. 메소드 오버라이딩

  • 부모로부터 상속받은 메소드의 몸체를 자식 클래스에서 재정의하는 것
  • 부모와 자식에서 같은 이름의 메소드가 다른 기능 수행하게 됨
  • 방법
    • 메소드의 이름, 인자의 개수와 자료형, 반환형이 같은 메소드를 정의
    • 반환형은 서브 타입(상속 관계에서 자식 클래스)도 가능함
    • 접근 제어자의 가시성(접근 범위)은 같거나 커져야함
      • protected인 경우 protected 또는 public
      • public인 경우 public만 가능
  • 메소드 오버라이딩의 사용
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      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; }
      } // getArea 메소드 재정의 -> 메소드 오버라이딩
        
      public class OverridingTest {
      	public static void main(String args[]) {
      		Triangle t = new Triangle(); // Shape 자식 클래스 Triangle 객체 생성
      		System.out.println(t.getArea(3.0, 4.0)); // 3.0 * 4.0 * 0.5 = 6.0 출력
      	}
      }
    

2-4. this

  • 메소드 호출 시, 숨은 인자로 this가 메소드에 전달됨
    • this는 현재 객체에 대한 참조값을 가지고 있음
      • c1.display()c2.display()의 결과가 다른 이유
  • 인스턴스 메소드나 생성자에서만 사용 가능
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      class Circle {
      	static double PI = 3.14;
      	double radius;
        
      	public double getArea() {
      		return this.radius * this.radius * PI; // c1.radius 호출 시 this값은 c1값을 가지게 됨
      	}
      	public void display() {
      		System.out.println("반지름:" + this.radius + " 면적:" + this.getArea());
      	}
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
      class CircleThis {
      	static double PI = 3.14; // 정적 변수, 클래스 변수
      	double radius = 2;
        
      	public double getArea() { 
      		return this.radius * this.radius * PI; // c1.radius 호출 시 this값은 c1값을 가지게 됨
      	}
      	public void display() {
      		System.out.println("반지름:" + this.radius + " 면적:" + this.getArea()+ " this:" + this);
      	}
        	
      	public static void main(String args[]) {
      		CircleThis c1 = new CircleThis();
      		CircleThis c2 = new CircleThis();
      		c1.display(); // 반지름:2.0 면적:12.56 this:kr.ac.knou.mxxikr.CircleThis@76ccd017
      		c2.display(); // 반지름:2.0 면적:12.56 this:kr.ac.knou.mxxikr.CircleThis@182decdb
      	}
      }
    

2-5. super

  • this와 같으나 자료형이 부모 클래스 유형
  • 자식 클래스의 인스턴스 메소드나 생성자에서 사용됨
    • this와 마찬가지로 static 메소드에서 사용할 수 없음
  • 부모 클래스에서 오버로딩 당한 메소드를 호출하거나 상속 되었으나 감춰진 필드에 접근할 때 필요함
    • super.메소드(인자)
    • super.필드
  • thissuper의 사용
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
      class CSuper { // 부모클래스
      	public double x;
      }
        
      class CSub extends CSuper { // 자식 클래스
      	public double x;
        
      	public CSub(double new_x) {
      		this.x = new_x; // 자식 클래스의 x 필드 값 지정
      		super.x = new_x * 10; // 부모클래스의 x 필드 값 지정
      	}
        	
      	public double getSuper() {
      		return super.x; // 상속받은 x 리턴
      	}
        
      	public double getSub() {
      		return this.x; // 재정의한 x 리턴
      	}
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      public class ThisSuperTest {
      	public static void main(String args[]) {
      		CSub sub = new CSub(10.0);
      		System.out.println(sub.x); // 10
        	
      		System.out.println(sub.getSuper()); // 100 출력
         
      		System.out.println(sub.getSub()); // 10 출력
      	}
      }
    

2-7. this()와 super()

  • this()
    • 같은 클래스의 다른 생성자를 호출하는 것
  • super()
    • 부모 클래스의 생성자를 호출하는 것
    • 상속 받은 데이터 필드를 초기화하기 위한 것
    • 생성자 몸체에서 부모 클래스 생성자의 명시적 호출이 없다면, 인자가 없는 생성자인 super()가 자동 호출됨
  • 둘다 생성자 몸체의 첫번째 문장에서만 사용 가능
  • supersuper()의 사용
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
      public class Cyliner extends Circle {
      	private double height;
        
      	public Cylinder() {
      			super(); // 부모클래스의 생성자 초기화
      			height = 1.0;
      	}
        
      	public Cylinder(double radius, double h) {
      			super(radius); // double값을 가지는 인자로 부모클래스의 생성자 초기화
      			this.height = h;
      	}
        
      	public double getHeight() {
      		return height;
      	}
        
      	public void setHeight(double h) {
      		this.height = h;
      	}
        	
      	public double getArea() { // Cylinder의 getArea() 메소드
      		return 2 * PI * getRadius() * height + 2 * super.getArea(); // 상속받은 this.getRadius() 호출
      	}
        	
      	public double getVolume() {
      		return super.getArea() * height; // Circle의 getArea() 메소드 호출
      	}
        	
      	public String toString() {
      		return "Cylinder of radius = " + getRadius() + " height = " + height;
      	}
      }
    



학습 정리


  • 생성자를 정의하려면 이름을 클래스 이름과 같게 하고 반환형을 지정하지 않음
  • 클래스 정의에 있는 데이터 필드의 선언문, 초기화 블록, 생성자 통해 객체 생성 시 필요한 데이터 필드의 초기값 지정 가능
  • 클래스 정의 시 객체마다 각각 데이터가 필요하면 인스턴스 변수(non-static 데이터 필드)로, 모든 객체가 공유하는 데이터는 static 데이터 필드로 정의
  • 메소드 오버로딩이란 한 클래스에서 이름이 같은 여러 메소드가 존재하는 상황
    • 오버로딩된 메소드끼리는 매개변수 목록이 달라 구별 가능
  • 부모로부터 상속받은 메소드의 몸체를 자식 클래스에서 다시 정의하는 것을 메소드 오버라이딩이라함
  • 인스턴스 메소드와 생성자에서 숨은 인자인 thissuper 사용 가능



연습문제


Q1.

1
2
3
4
5
6
7
8
9
10
/**
키워드 final에 관한 설명으로 틀린 것은?

1. final 클래스의 자식 클래스를 정의할 수 없다.
2. 부모 클래스의 final 메소드는 자식 클래스로 상속될 때 재정의될 수 없다.
3. final 변수는 상수로 사용된다.
4. final 클래스의 객체를 생성할 수 없다.
**/

// 4. final 클래스의 객체를 생성할 수 없다.

Q2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Circle {
	// ㄱ
	private double radius; 
	public Circle(double radius) {
		// ㄴ
	}
	public double getArea() {
		return radius * radius * PI;
	}
}
/**
아래 Circle 클래스에서 원주율 PI를 상수로 선언하기 위해 밑줄 친 ㄱ에 들어갈 적당한 내용은 무엇인가?

1. double PI = 3.14;
2. final double PI = 3.14;
3. const double PI = 3.14;
4. static final double PI = 3.14;
**/

// 4. static final double PI = 3.14;

Q3.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Circle {
	// ㄱ
	private double radius; 
	public Circle(double radius) {
		// ㄴ
	}
	public double getArea() {
		return radius * radius * PI;
	}
}
// Circle 클래스의 생성자에서 밑줄 친 ㄴ에 들어갈 적당한 내용을 작성하시오.

// this.radius = radius;