
4주차 학습 내용을 정리합니다.
Java 상속, 형변환, 추상클래스, 인터페이스, 내부클래스, 익명클래스, 예외처리 등의 내용을 학습했습니다.
상속
- 정의
- 기존 클래스를 재사용하여 새로운 클래스를 정의하는 것
- 상속의 장점
- 썼던 코드를 반복해서 사용하지 않아도 됨
- 코드를 공통적으로 관리할 수 있음
- 코드의 추가 및 변경이 용이해짐
- 특징
- extends 키워드 사용
- 상속 관계 (부모 클래스 - 자식 클래스)
- Java에서 다중 상속은 허용하지 않음 ➡ 이러한 경우, 인터페이스 사용!
- is-A 관계일 때만 사용! (상속받은 클래스의 모든 기능을 다 갖게 됨)
- has-A 관계는 (클래스 내부에서) 다른 클래스의 객체를 생성하여 필요한 기능 사용
- is-A 관계가 아니면 모두 has-A 관계로 생각할 것!
참조형 변수의 형변환
기본형 변수와 마찬가지로 '참조형 변수'도 형변환이 가능함.
Java에서 "형변환(Type Casting)"은 다형성(Polymorphism)을 보장하는 매우 중요한 개념임!
- 자동 형변환 (자식클래스 ➡ 부모클래스)
- 1) 자식 클래스로 객체 생성
- 2) 생성된 객체를 부모 클래스로 형변환
- 명시적(강제) 형변환
- 1) 자식 클래스로 객체 생성
- 2) 생성된 객체를 부모 클래스로 형변환 ➡ 자동 형변환
- 3) 자동 형변환된 부모 타입의 변수에서 자식 클래스로 다시 변환 ➡ 명시적(강제) 형변환
- 명시적(강제) 형변환 제약
- 초기에 부모 클래스로 생성되어 부모 타입을 갖는 변수가 명시적 형변환으로 자식 타입으로 변경될 수는 없음!
- 초기에 '자식 클래스'로 생성될 경우 ➡ 가능⭕
- 자식 클래스의 생성자(Constructor)가 호출될 때 내부적으로 가장 먼저 부모의 생성자를 호출함
- HEAP 메모리 상에 부모 객체와 자식 객체가 존재하고 있으므로 자식 변수는 부모 타입으로 형변환(=접근)이 가능함
- 이렇게 부모 타입으로 형변환된 변수는 여전히 HEAP 메모리에 자식 객체가 존재하고 있으므로 다시 자식 타입으로 형변환(=접근) 할 수 있음 ➡ 명시적(강제) 형변환
- 초기에 '부모 클래스'로 생성될 경우 ➡ 불가능❌
- 부모 클래스의 생성자(Constructor)가 호출될 때 자식 클래스의 생성자가 호출되지는 않음
- 따라서 HEAP 메모리 상에는 부모 객체만 존재함
- 이 때 자식 타입으로 형변환을 시도할 경우, 메모리 상에 존재하는 자식 객체가 없으므로 에러 발생
class Parent {
int su = 10;
void print() {
System.out.println(su); // 10
}
// non-overriding 메서드 (부모만의 메서드)
void mp() {
System.out.println("Parent클래스");
}
}
class Child1 extends Parent {
// int super.su = 10;
int su = 20;
// overriding 메서드
void print() {
System.out.println(su); // 20
// super.su를 쓰려면 오버라이딩을 하지 말아야 함!
}
// 자식만의 메서드
void mc() {
System.out.println("Child1클래스");
}
}
public class CastingTest {
public static void main(String[] args) {
Parent p1 = new Parent();
System.out.println(p1.su); // 10
p1.print(); // 10
p1.mp();
p1.mc(); // 컴파일 에러 -> 메모리에 존재하지도 않음!
Child1 c1 = new Child1();
System.out.println(c1.su); // 20
c1.print(); // 20
c1.mp();
c1.mc();
// 자동 형변환 (자식 -> 부모) - 자식클래스로 생성
Parent p2 = new Child1();
System.out.println(p2.su);//10 (부모 멤버 변수 출력)
p2.print(); // 20 (자식의 오버라이딩된 메서드 실행됨 - this.su)
p2.mp();6
p2.mc(); // 컴파일 에러 -> 오버라이딩되지 않은 자식 메서드에는 접근 불가 (기본적으로 자식 객체 접근 불가!)
// p2.mc()는 메모리에 존재하기는 함 -> "접근할 수 있는 방법이 없을까?" = <명시적 형변환> 필요!
}
}
인터페이스
- 문제 상황
- '학생' 클래스가 존재하고 '교직원' 클래스가 존재하는 상황에서 '조교' 클래스를 정의하려고 함
- 두 클래스의 특성을 모두 갖는 '조교' 클래스는 두 클래스를 상속받으려고 하지만 Java에서 다중 상속은 불가능함
- 다중상속을 막는 이유 : 상속받는 서로 다른 클래스 내부에 동일한 이름의 멤버 변수 또는 메서드가 존재할 경우, 상황이 매우 복잡해지기 때문!
- 이러한 문제 상황을 해결하기 위해 '인터페이스' 사용
- 인터페이스는 일종의 추상 클래스임
- 오직 추상메서드와 상수만을 변수로 가짐
- public abstract [메서드명]([매개변수]); ➡ 추상 메서드 (선언부만)
- public static final [타입] [변수명] = [값]; ➡ 상수
- 생성자 없음
- 인터페이스로 객체 생성 불가
- 추상 클래스보다 추상화 정도가 더 높음
- 인터페이스는 '완벽하게 추상적'임!
- 오직 추상메서드와 상수만을 변수로 가짐
- 서로 다른 클래스에 공통적으로 들어가야 하는 메서드 스펙을 정의한 것 (=표준화)
- 장점
- 1) 개발 시간 단축 가능
- 2) 표준화 가능
- 3) 서로 관계없는 클래스들에게 관계를 맺어줄 수 있음
- 4) 독립적인 프로그래밍 가능
- 인터페이스에 꼭 필요한 내용이 정해져 있으니, 서로 다른 코드에서 각자의 부분만 구현하면 됨!
// [1]
interface I { // 이미 메서드 종류(=구조)가 정해져 있으니, 구현에만 신경쓰면 됨!
void m1();
void m2();
}
// [2]
interface I { // 이미 메서드 종류(=구조)가 정해져 있으니, 구현에만 신경쓰면 됨!
void m1();
void m2();
}
// [3]
// 자식클래스로 객체를 생성하고 부모 인터페이스 타입으로 형변환 (*다형성*)
I i1 = new B();
I i2 = new C();
// B와 C 객체를 하나의 배열 안에 묶을 수 있음!
I[] array = new I[2];
array[0] = i1;
array[1] = i2;
- 사용 예시
- 서로 다른 클래스 중 일부 클래스의 부모 클래스가 이미 정해진 상태(= 이미 다른 클래스를 상속받고 있어서 변경이 어려운 상태)라면 Interface를 사용하여 유연하계 대처할 수 있음!
- 서로 다른 클래스 중 일부 클래스의 부모 클래스가 이미 정해진 상태(= 이미 다른 클래스를 상속받고 있어서 변경이 어려운 상태)라면 Interface를 사용하여 유연하계 대처할 수 있음!
추상클래스
- 추상 메서드를 하나라도 가지고 있으면 추상 클래스임!
- 추상 메서드
- 구현부가 없이 선언부만 존재하는 메서드 (abstract 키워드 사용)
- 메서드의 내용(=구현부) 구현은 해당 클래스를 상속받는 하위클래스(=구체클래스)에게 맡김
- 추상 메서드
- 일반적인 메서드(=구체 정의 메서드)도 가질 수 있음
- 추상 클래스는 "부분적으로 추상적"임!
- 일반적인 클래스와 동일하게 단 한 개의 클래스만 상속(=extends) 가능 ➡ 단일 상속
내부클래스
- 소속(= 포함관계)을 분명히하여 사용 방법을 제한할 때 사용
- 클래스를 마치 멤버 변수처럼 취급함
// A라는 클래스를 외부 클래스에서 사용하지 못하게 하고 나만 사용하고 싶은 경우
class B { // outer class
class A { // inner class
....
}
A a1 = new A();
}
class C {
A a1 = new A(); // 사용 불가!
...
}
익명클래스
- Javascript의 익명 함수와 유사
- 클래스를 정의하는 동시에 객체를 생성하여 일회성으로 사용
- 사용 예시
- 한 클래스가 여러 개의 인터페이스를 복잡하게 구현하고 있을 경우, 클래스와 인터페이스의 관계가 불분명해질 수 있음
- 여러 인터페이스 중 일부를 분리시켜 클래스와 인터페이스의 관계를 조금 더 명확히 표현하는 것!
interface I1 {
void m1();
}
interface I2 {
void m2();
}
/*
class Anon implements I1, I2 {
public void m1() { }
public void m2() { }
}
*/
public class AnonymousTest {
public static void main(String[] args) {
//Anon a = new Anon();
// a.new I1(); // 인터페이스 타입이라 직접 객체를 생성할 수 없음
// 지역변수처럼 사용 -> 이름이 없으므로 바로 생성한 뒤 사용
// => 여러 인터페이스 중 일부를 분리시키는 것! *
// => 인터페이스와의 관계를 좀 더 명확하게 표현하기 위해! **
//class Anon implements I1 {
I1 i1 = new I1() { // "이름없는 class 블록 {}" -> i1을 상속받자마자 m1메서드 오버라이딩
public void m1() {
System.out.println("m1 호출");
} // m1 method end
}; // new I1();
// => I1 인터페이스를 상속을 받은 클래스(무명 클래스)를 정의해주고, 동시에 객체를 생성하는 문장 작성
// => 해당 무명클래스의 객체를 생성하고 타입을 인터페이스 타입으로 형변환 하는 것!
i1.m1();
// ===============================================================================
// 더 축약된 표현 -> 객체도 일회용으로 사용
new I1() {
public void m1() {
System.out.println("m1 호출");
}
}.m1();
// ===============================================================================
I2 i2 = new I2() {
public void m2() {
System.out.println("m2 호출");
}
};
i2.m2();
}
}
예외처리
- 예외처리가 필요한 이유
- 견고한 프로그램을 만들기 위해서는, 발생 가능한 오동작을 미리 처리하여 프로그램이 항상 일정한 동작을 할 수 있도록 보장해야 함!
- 오류의 종류
- 컴파일 오류
- 문법이나 구문이 잘못된 경우, 컴파일러에 의해 사전에 발견되는 오류
- 소스코드를 수정하기 전까지 사라지지 않음
- 실행 오류(=런타임 오류)
- 실행 조건에 따라 발생하기도 하고 발생하지 않기도 하는 오류
- 에러와 예외의 차이
- 에러 : 프로그래밍적으로 해결이 불가능한 오류
- 컴퓨터 전원 OFF
- 메모리 부족
- 예외(Exception) : 프로그래밍적으로 미리 처리하여 해결이 가능한 오류
- 정수를 0으로 나누는 상황
- 배열의 범위를 넘어서는 인덱스 조회
- 에러 : 프로그래밍적으로 해결이 불가능한 오류
- Exception 클래스
- 예외 상황들을 미리 정의해 놓은 자바 클래스 ➡ Exception 클래스 중 최상위 클래스임
- 대표적 예시
- java.lang.ArithmeticException : 산술연산과 관련된 예외 정의
- java.lang.ArrayIndexOutOfBoundException : 비정상적인 인덱스에 접근할 때 발생하는 예외
- 예외처리 키워드
- try-catch-finally
- 예외 직접 처리
- 예외가 발생하고 있는 메서드 내부에서 예외를 직접 처리할 때 사용
- 메서드 구현부 내부에 문장으로 작성
- try문 ( = try { ... })
- 내부에 예외상황이 발생할 것 같은( 또는 발생할 수 있는) 문장 - 일반적인 소스코드 - 가 포함됨
- 예외상황이 발생하면 try문의 코드가 중단되고 곧바로 catch문으로 넘어감
- catch문 (= catch ([예외클래스명] [변수명]) { ... })
- try문 내부에서 예외상황이 발생했을 때 곧바로 catch문 블록으로 이동함
- 해당 예외가 발생했을 때 처리할 로직을 작성
- finally문 (= finally { ... })
- try문이 종료되거나 catch문이 종료되었을 때, 반드시 실행되어야 하는 문장들이 포함된 블록
- try문에서 예외처리가 발생하여 코드 실행이 중단되더라도 반드시 실행되어야 하는 부분은 finally문 내부로 이동시켜야 함!
- try문 ( = try { ... })
- throws
- 예외 간접 처리
- 예외가 발생하고 있는 메서드 이외의 다른 메서드(=일반적으로 상위 클래스의 메서드)에서 처리하도록 예외 상황을 떠넘길 때 사용
- 해당 클래스의 메서드에서 처리를 안 한다는 것 뿐이지, 상위 클래스에서 try-catch문으로 예외처리가 필요함!
- 메서드 선언부에 키워드로 작성
- throws [예외클래스명] { ... }
- 발생할 수 있는 예외상황을 미리 파악(가정)하여 작성
- throw
- 의도적으로 예외를 발생시킴
- 일반적으로 발생하면 안되는 상황을 조건식(if문)으로 가정하고 그 안에서 throw 키워드를 사용하여 예외 발생시킴
- 예외가 발생하면 예외 상황이 상위클래스로 전달(throw)되므로, 상위 클래스에서 try-catch문으로 예외처리가 필요함!
- 메서드 구현부 내부에 문장으로 작성
- try-catch-finally
- 컴파일 오류
try {
FileReader fr = new FileReader("a.txt");
fr.read(); // 예외 발생 - 중단 (-> close 메서드 수행되지 못함)
...
//fr.close(); // 중간에 예외가 발생하여 중단되더라도 이 부분은 반드시 실행되어야 함
} catch (AExcepction e) {
...
} finally {
fr.close(); // finally 문 안으로 fr.close() 옮김!
}

회고
이번주 후반부터 남은 Udemy Java 기초 강의는 살짝 뛰어넘고, JSP 강의를 듣기 시작했습니다. JSP와 servlet의 기초 강의부터 상속, 인터페이스, 제어자, 추상클래스 등 그동안 열심히 배웠던 자바 개념들이 복합적으로 등장했습니다. 반갑기도 했지만 솔직히 좀 놀랐습니다. 왜 그렇게 사람들이 기초 문법을 중요시하는지 단번에 이해가 되었습니다. 남은 기초 문법 강의도 열심히 수강하고 정리해두면 이후 과정에 큰 도움이 될 것 같습니다.
참고
- 자바의 정석 기초편(2019), 도우출판, 남궁성 저
* 유데미 바로가기 : https://bit.ly/3V220ri
* STARTERS 취업 부트캠프 공식 블로그 보러가기 : https://blog.naver.com/udemy-wjtb
본 후기는 유데미-웅진씽크빅 취업 부트캠프 3기 백엔드 과정 학습 일지 리뷰로 작성되었습니다.
'유데미 스타터스 3기 > 학습일지' 카테고리의 다른 글
| 유데미 스타터스 취업 부트캠프 3기 - 백엔드 6주차 학습 일지 (0) | 2023.01.01 |
|---|---|
| 유데미 스타터스 취업 부트캠프 3기 - 백엔드 5주차 학습 일지 (2) | 2022.12.25 |
| 유데미 스타터스 취업 부트캠프 3기 - 백엔드 3주차 학습 일지 (0) | 2022.12.11 |
| 유데미 스타터스 취업 부트캠프 3기 - 백엔드 2주차 학습 일지 (0) | 2022.12.04 |
| 유데미 스타터스 취업 부트캠프 3기 - 백엔드 1주차 학습 일지 (1) | 2022.11.27 |