딸기말차
[Java] 6. 상속, Interface, 추상 클래스 본문
엔코아 플레이데이터(Encore Playdata) Backend 2기 백엔드 개발 부트캠프 (playdata.io)
백엔드 개발 부트캠프
백엔드 기초부터 배포까지! 매력있는 백엔드 개발자 포트폴리오를 완성하여 취업하세요.
playdata.io
1. 다중 클래스 실습_7) 두 가지 txt 파일을 통한 파일 입출력
어제 해결한 검색단어리스트 txt파일과, 하둡분산처리시스템 txt파일을 사용했던 실습을 오전에 강사님께서 풀이해 주셨다.
강사님의 풀이는 어제 스스로 풀었던 방식과 방식이 달랐다.
필자는 Data 클래스에서 파일을 읽는 메서드, 데이터의 중복을 검사하는 로직을 담은 메서드를 다 구현해 바로 저장하고, 정작 Method 클래스에선 로직이아니라 생성한 데이터를 사용하여 HTML파일을 생성하기 위한 tag들만 만들었다.
반면 강사님께서는 Data 클래스를 마치 DB 처럼 순수하게 데이터를 저장하기 위한 list들만 구현하셨고, 이 후 필요한 로직들은 Method 클래스에서 전부 구현하셨다.
우선 강사님의 Data 클래스이다. 클래스 이름에 맞게 순수하게 DB의 역할을 제공하도록 구성하셨다. 또한 DB의 역할만 수행하기 위해서는 굳이 객체를 생성할 필요없이 데이터만 뽑아다 사용할 수 있으면 된다. 때문에 static과 final을 사용해 list를 생성하셨고, 생성자를 private으로 막아 타 클래스에서 객체를 생성할 수 없게 막아두셨다.
public class WordDataClass {
/*
* 클래스 명으로 접근 가능하도록 static, 한번 선언하면 더 이상 수정할 수 없도록 final
*/
static final ArrayList<String> sentence = new ArrayList<String>(); // 줄단위 문장
static final ArrayList<WordCountClass> words = new ArrayList<WordCountClass>(); // 검색 단어
static final ArrayList<SentenceSplitClass> splitSentence = new ArrayList<SentenceSplitClass>(); // 줄단위 문장분리
private WordDataClass() {
}
}
위 코드를 보면 WordCountClass와 SentenceSplitClass라는 클래스가 보인다.
혼자 실습을 진행했을 땐 검색할 문자와 해당 문자의 count 값을 올려줄 class를 만들어 저장하면 된다는 것을 생각하지 못하고 HashMap을 이용하였다. 또한, 읽어온 문자열들을 분리 후 저장하여 사용한다는 생각을 못하고 읽어 오자마자 중복제거 로직을 구현하였다.
이 때문에 각 클래스 간 역할이 명확히 분리되지 못했는데, 강사님께서는 역할을 정확히 분담해 실습을 진행하셨다.
public class WordCountClass {
String word = null; // 검색 단어 저장
int count = 0; // 검색 된 횟수
public WordCountClass() {
}
public WordCountClass(String word) {
this.word = word;
}
}
public class SentenceSplitClass {
String[] splitSentence = null;
public SentenceSplitClass() {
}
public SentenceSplitClass(String[] splitsentence) {
this.splitSentence = splitsentence;
}
}
필요한 데이터를 저장하기 위한 클래스를 전부 구현했으니, 다음은 MethodClass에서 사용만 하면 되었다.
두 txt파일을 읽어오는 코드는 거의 동일하게 구현하였으나 필자는 읽어온 데이터를 그 자리에서 바로 사용한 반면, 강사님의 코드는 각각 WordCountClass와 SentenceSplitClass를 이용해 저장하셨다.
중복검사를 하는 로직 또한 방식은 동일했지만, 강사님께서는 데이터를 검색하는 메서드, 검사하는 메서드를 분리하여 좀더 세분화 시켜 구현을 완료하셨다.
// 문장별로 분리 된 전체 데이터를 검색
public static void repeatWordCount() {
for (SentenceSplitClass s : WordDataClass.splitSentence)
searchWordCount(s.splitSentence);
}
// repeatWordCount() 부터 분리 된 하나의 문장에 각 검색단어가 있는지 확인하여 count
public static void searchWordCount(String[] splitSentence) {
for (String s : splitSentence) {
for (WordCountClass w : WordDataClass.words) {
if (s.contains(w.word))
w.count++;
}
}
}
2. 상속
Java의 모든 클래스는 기본적으로 최상위 부모인 Object를 상속 받는다. 이렇게 처음 Java를 접할 때부터 눈에 보이진 않지만 우린 상속받은 클래스를 사용하고 있다.
상속의 가장 큰 장점은 이미 생성 된 클래스를 재사용하여 새로 만들어야 되는 양을 줄여, 메모리의 효율성을 높여줄 수 있다는 점이다. 또한 새로 만들어야 되는 양이 줄어드니 당연히 개발 시간도 줄어든다는 장점이 있다.
* 상속(extends) : 부모(super) 로부터 물려받은 자원을 자식이 그대로 사용하거나 수정해서 사용 가능.
*
* Java는 한번에 하나의 클래스만 상속 가능
* 예) public class ChildClass extends ParentClass, ParentClass2 {} : 불가능
*
* 부모 클래스 -> 상속 -> 자식클래스 -> 상속 -> 자식2클래스 도 가능하다.
* 부모의 데이터에 private가 붙어있을 경우 상속을해도 사용할 수 없다.
* 생성자는 상속받지 못한다. 생성자를 제외한 나머지 자원을 상속받는다.
* 부모클래스에서 상속받은 메서드를 그대로 사용해도 되고, 상속받은 메서드의 내부를 수정해 사용할 수도 있다.
* override : 부모로부터 상속받은 메서드의 내부를 수정. 선언부를 수정하는 것이 아니다.
*
* 상속을 사용하는 이유 : 메모리 효율 때문. 상속을 통해 생성 할 변수를 줄일 수 있다.
* 상속은 클래스 파일이 상속되는 것이 아니라 객체가 상속되는 것이다. (부모객체 생성 -> 자식객체 생성)
다음으로 상속을 사용한 실습을 진행하였다. 실습코드를 보기 전 정리해야할 몇 가지 키워드가 있다.
1. protected : 부모클래스-자식클래스 사이에서만 접근, 사용할 수 있는 접근 제어자
2. super : 부모클래스는 자식클래스를 모르기 때문에 상속해주는 것 이외의 행동은 불가능하지만, 자식클래스에서는 super라는 키워드를 통해 부모클래스의 생성자에 값을 전달할 수 있다. 주의해야할 점이 있다면 super 키워드는 그 위에 어떤 코드도 작성할 수 없다. 항상 영역의 최상단에 존재해야한다.
public class ParentClass {
int a = 10; // default
protected String name = "lhs"; // default
private boolean b = false;
public char c = 'A';
public ParentClass() {
}
public ParentClass(int a) {
this.a = a;
System.out.println("ParentClass() 생성자 종료");
}
// Object.Class에 존재하는 toString()을 재정의 : override
public String toString() {
String result = a + "--" + name;
return result;
}
}
부모클래스 생성 이후 상속받을 자식클래스를 생성하였는데, 그 중 하나는 다른 패키지를 하나 만들어 생성하였다.
package com.lhs;
public class ChildClass extends ParentClass {
// 자식 클래스에서는 부모클래스의 생성자에 값을 전달할 수 있다.
public ChildClass() {
super(1000); // 항상 맨위에 있어야한다. super 위에 다른 코드가 들어갈 수없다.
System.out.println("ChildClass() 생성자 종료");
}
}
package com.lhs.test;
import com.lhs.ParentClass;
public class Child2 extends ParentClass {
public Child2() {
}
public Child2(int a) {
super(a);
}
}
생성한 부모, 자식 클래스들을 Main에서 테스트하였는데, 여기서 주의할 점은 child2 클래스는 다른 패키지에 존재하기 때문에 사용하기 위해서는 해당 패키지를 import 해줘야 한다는 점이다.
ParentClass p = new ParentClass();
System.out.println(p.toString());
ChildClass c = new ChildClass();
System.out.println(c.toString());
/*
* 다른 패키지에 있는 클래스를 사용하기 위해서는 import 가 필요하다.
* c와 c2는 같은 부모를 상속받지만, c2는 다른 패키지에 있기 때문에 default로 선언된 값을 사용할 수 없다.
*/
Child2 c2 = new Child2();
3. Interface
Java의 interface는 코드의 유연성, 유지보수, 메서드에 대한 다형성을 구현하기 위해 반드시 알아야 한다.
interface를 비유하자면 눌러도 아무런 동작을 하지 않는 리모컨이라 할 수 있다. 리모컨의 형태를 가지고 있지만, 내부기능은 구현되지 않아 이거 하나만 가지고는 아무것도 할 수 없는 것이다. 하지만, 내부기능을 여러 버전으로 구현해 교체하며 끼워넣을 수 있다면 하나의 리모컨을 가지고 여러 기능을 수행할 수 있을 것이다.
이를 다형성이라 정의하고, 이것을 활용한다면 여러 리모컨을 만들게 아니라 하나의 리모컨만 만들고 내부의 기능만 여러가지를 만들어 바꿔 끼워가며 더 수월한 개발, 유지보수를 진행하는 것이 가능해진다.
그렇기 때문에 interface 내부엔 로직을 구현하는 것이 아니라 추상메서드만 구현이 가능하고, 변수를 선언한다 해도 컴파일 시 자동으로 final이 붙는 변수로 바뀌기 때문에 값을 변경할 수 없다.
* interface : 코드의 유연성, 유지보수, 메서드에 대한 다형성
*
* public interface 클래스명 { }
*
* interface 클래스 구성
* 1. 상수 선언 가능 : 컴파일 단계에서 int a = 10; -> final int a = 10; 이 된다.
* 2. 메서드명 '만' 선언 가능
* 3. 변수 선언 불가
* 4. 생성자 선언 불가
* 때문에 interface는 단독으로 사용하는 것이 아니라, 다른 클래스에 구현되어 사용된다. -> 객체 생성이 불가능하다.
*
* 한 클래스에서 interface를 implements 할 때, 여러 interface를 implements 하는 것도 가능하다.
* interface 끼리는 다중 상속이 가능하다.
이 후 3종류의 interface를 구현하고, 이를 implements해 메서드를 override하여 사용하는 실습을 진행하였다.
public interface A {
int a = 10; // 변수가 아니라 final이 붙은 상수로 선언되어, 더이상 값을 변경할 수 없다.
// public A() { // interface는 생성자를 가질 수 없다. }
// public void sum() {} // interface 내에서는 메서드 선언이 불가능하다.
public void sum(); // 때문에 이렇게 메서드명 까지만 선언 가능하고, 이 interface를 구현받는 클래스는 반드시 해당 메서드를 재정의 해야한다.
}
public interface B extends A{
}
public interface C extends A, B{
}
public class InterfaceTest implements A {
public InterfaceTest() {
// a = 100; // interface에 선언 된 변수는 final이 붙은 상수기 때문에 값을 변경할 수 없다.
// int c = a; // 변경은 불가능하지만 추출은 가능하다.
int ccc = 0;
}
@Override // interface에 선언 된 메서드를 재정의했다 라는 어노테이션
public void sum() {
System.out.println('A');
}
}
public class InterfaceTest2 implements A {
int ddd = 0;
public InterfaceTest2() {
}
@Override
public void sum() {
System.out.println("B");
}
}
B, C interface에선 interface 끼리도 상속, 다중상속이 가능하다는 것을 볼 수있고, InterfaceClass를 통해 implements 한 interface의 메서드를 override해 구현하는 것을 볼 수 있다.
이 후 Main에서 interface의 메서드와 변수의 사용여부를 실습하였다.
A a = new InterfaceTest();
A a2 = new InterfaceTest2();
// 인터페이스로 선언한 클래스는 내부에 만든 메서드만 사용할 수 있고, 변수는 사용할 수 없다.
a.sum();
// a.ccc;
4. 추상 클래스
Java에서 하나 이상의 추상 메서드를 포함하는 클래스를 가리켜 추상 클래스라고 한다. 추상 클래스는 객체 지향 프로그래밍에서 중요한 특징인 다형성을 가지는 메서드의 집합을 정의할 수 있도록 해준다.
또한, 추상 클래스는 생성자를 가지고 있긴 하지만 단독으로 사용할 순 없고, 자식 클래스를 추가로 만들어 추상 클래스를 상속 받은 후 추상 클래스의 메서드를 자식 클래스에서 override 후 사용해야한다.
* 추상클래스(일반클래스 + interface) 구성요소
* 1. 변수
* 2. 상수
* 3. 생성자 : new는 사용 불가능하다.(객체 생성 불가)
* 4. 메서드 (추상메서드 선언 가능)
* 추상클래스 선언을 위해선 abstract 키워드가 필요하다.
* 추상메서드는 컴파일 단계에서 public void sum(); -> public abstract void sum(); 으로 변경된다.
이를 실습하기 위해 추상 클래스 및 자식 클래스를 생성하고, Main에서 호출해 보았다.
public abstract class ABClass {
int a = 0;
final boolean B = true; // 일반적으로 상수를 만들 때의 변수명은 대문자로만든다.
public ABClass() {
System.out.println("ABClass() 호출");
}
public void avg() {
}
public abstract void prn(); // 추상메서드는 반드시 재정의를 해야한다.
}
public class ABChild extends ABClass{
public ABChild() {
System.out.println("ABChild() 생성자 호출");
}
@Override
public void prn() {
}
}
// 추상클래스는 생성자는 가지고있지만, 단독으로 스스로의 객체를 만들지는 못한다. 그 생성자는 상속에서만 사용 가능하다.
// ABClass abClass = new ABClass();
ABChild abChild = new ABChild();
5. 6일차 후기
두가지 txt 파일을 통한 파일입출력 실습을 혼자 풀어본 코드와, 오늘 강사님이 풀이해주신 코드를 보고 로직 자체는 비슷하지만 설계부분에서 굉장히 많은 차이가 난다는 것을 느꼈다. 기존에 코드를 짤 때 로직을 구현하는 것에만 집중하여 어떤 자료구조를 사용해볼까? 하는 고민 위주로 문제를 해결해 왔기 때문인 것 같다.
때문에 앞으로Collections에 있는 자료구조들을 갖다 써서 구현하는 것에 집중할게 아니라, 프로그램을 설계할 때 어떻게하면 각 클래스 간 역할을 정확히 분리하여 객체지향 프로그래밍을 구현할 수 있을지 고민하는 것에 집중하려고 한다.
'Bootcamp > Java' 카테고리의 다른 글
[Java] 8. Generic, SOLID, Lambda, Singleton, Database (0) | 2023.07.03 |
---|---|
[Java] 7. Thread, Collections, 외부 라이브러리 (0) | 2023.07.02 |
[Java] 5. 파일 입출력, 다중 클래스 (0) | 2023.06.29 |
[Java] 4. ArrayList, 접근 제어자, 다중 클래스 (0) | 2023.06.28 |
[Java] 3. static, final, 다중 클래스 (0) | 2023.06.26 |