java가 OOP를 배우는데 참 좋은 언어라고 한다.
OOP의 기본은 클래스니까 주욱 살펴보자.
1. 클래스 변수, 메서드
클래스 정의. 속성과 메서드를 지정.
public class House {
public static String name = "my house";
public String airconStat = "off";
public String heatStat = "off";
public String doorStat = "close";
public void airconOn() {
airconStat = "on";
}
public void airconOff() {
airconStat = "off";
}
}
클래스를 통해 인스턴스를 생성하여 활용.
public class HouseTest {
public static void main(String[] args) {
House house = new House();
System.out.println(House.name); // static 변수. 클래스에서 직접 접근
System.out.println(house.airconStat);
house.airconOn();
System.out.println(house.airconStat);
}
}
2. 클래스 인스턴스를 배열에 넣기
public class EntityArrayTest {
public static void main(String[] args) {
House[] houseArray = new House[3];
// House[] houseArr = {new House(), new House(), new House()};
// 이런 방식도 가능하지만 선호되지는 않는다고 함..
houseArray[0].airconOn();
System.out.println(houseArray[0].airconStat);
}
}
3. 초기화 블럭과 생성자 함수
static 초기화 블럭 => 초기화 블럭 => 생성자 함수 순서로 실행됨.
package lec2.OOP02;
public class PropertyOfClass {
// 정적 변수
public static int entityCount;
// 일반 변수
public String goodsNo;
public String goodsName;
public String goodsPrice;
public enum EnumDataType {
INSERT, UPDATE, DELETE, NORMAL
}
// 정적 초기화. 최초로 클래스가 사용될 때 딱 한 번 실행됨.
static {
System.out.println("static start");
System.out.println("static var" + String.valueOf(entityCount));
System.out.println("static end");
}
// 초기화 블럭. 객체 생성시마다 최초로 호출되는 부분. 심지어 생성자 부분보다 먼저 실행됨.
{
System.out.println("초기화 블럭 실행");
entityCount++;
goodsNo = "goods" + entityCount;
System.out.println("상품 번호 : " + goodsNo);
}
// Constructor 함수임. 클래스 이름과 동일해야 함.
public PropertyOfClass(String goodsName, String goodsPrice) {
System.out.println("constructor init");
this.goodsName = goodsName;
this.goodsPrice = goodsPrice;
}
// eclipse 기준 우클릭 > source > toStric 클릭. 모든 프로퍼티를 확인하는 메서드임.
@Override
public String toString() {
return "PropertyOfClass [goodsNo=" + goodsNo + ", goodsName=" + goodsName + ", goodsPrice=" + goodsPrice + "]";
}
}
위의 코드를 기반으로 아래 main 함수를 실행했을 경우 출력의 순서를 예상해보자.
public class PropertyMain {
public static void main(String[] args) {
PropertyOfClass PropertyOfClass = new PropertyOfClass(null, null);
PropertyOfClass PropertyOfClassHasName = new PropertyOfClass("커피", "35000");
}
}
PropertyOfClass 클래스의 인스턴스 생성이 이루어졌으므로 static 초기화 블럭 실행
=> PropertyOfClass의 초기화 블럭
=> PropertyOfClass 의 생성자 함수 실행
=> PropertyOfClassHasName의 초기화 블럭
=> PropertyOfClassHasName의 생성자 함수 실행
4. 동적 파라미터(String... Int...)
python에서 def func(param, *args): 로 작성해서 동적인 파라미터를 받은 적이 있었습니다.
*args면 리스트, **kwargs면 딕셔너리로 활용했죠.
JAVA에서도 마찬가지로 가능합니다.
아래와 같이 Type... 꼴로 적어주면 됩니다. ...이 핵심입니다.
public class MethodDesc {
public void dynamicParam(String... params) {
System.out.println(Arrays.toString(params));
}
}
아래와 같이 사용하면 됩니다.
public class MethodDescMain {
public static void main(String[] args) {
MethodDesc a = new MethodDesc();
a.dynamicParam("hello", "guys", "aws", "gcp");
a.dynamicParam(new String[] {"go", "js", "java", "ruby"});
}
}
5. static의 규칙과 사용 자제요구.
사용법이 문제가 아니다.
데이터의 오염을 줄일 수 있도록 클래스를 설계하고, OOP 프로그래밍의 원칙을 생각해보면 그렇다.
public class ClassInstanceMethodMain {
public static void main(String[] args) {
// static. 하지마! 👎
int a = ClassInstanceMethod.staticSum(3, 5);
// method. 굳. 좋다. 👍
ClassInstanceMethod instanceOne = new ClassInstanceMethod();
int c = instanceOne.entitySum(3, 5);
}
}
static 값에 일반 변수를 담을 수 없고
static 메서드에는 일반 변수를 사용할 수 없음.
근데 상식적으로 static을 사용할 이유도 별로 없고, static에 일반 변수를 할당할 일은 더더욱 없다.
static이 먼저 초기화되기도 하고 뭐 여러 이유가 있다지만 솔직히 코딩하면서 저런 짓을 해본 적이 없다.
public class ClassEntityDiff {
int value = 20;
static int staticValue = 10;
int valueTwo = staticValue;
static staticValueTwo = value; // Error. static에 일반 변수 담을 수 없음.
static void method() {
System.out.println(value); // Error. static 메서드에서는 일반 변수 사용 불가. static 변수만 사용 가
}
}
6. 오버로딩(overLoading)과 오버라이딩(Overriding)
보면, 철자부터가 다르다. 한국어로 배워서 헷갈리는 것이다.
overloading은 초과하여 물건을 load한다는 의미이며 override는 문서 작성하면 흔히 보는 '덮어쓰기'라는 의미이다.
- 오버로딩(Overloading) : 같은 이름의 메서드 여러개를 가지면서 매개변수의 유형과 개수가 다르도록 하는 기술
- 오버라이딩(Overriding) : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의해서 사용
오버로딩의 예시
똑같은 메서드 명을 사용했지만, 인자의 타입이 다른 경우 해당하는 메서드가 실행된다.calculateLong 메서드를 별도로 만드는 것보다 사용이 간결한 편이다.그런데 이는 개인적인 선호에 가깝고, 명시적으로 다른 메서드라는 것을 표현하고 싶다면 오버로딩은 좋은 선택이 아니다.프로젝트의 코딩 컨벤션에 따르도록하자.
package lec2.OOP10;
public class OverloadingExam {
public int calculate(int one, int two) {
return one + two;
}
public long calculate(long one, long two) {
return one + two;
}
public long calculate(long one, int two) {
return one + two;
}
public long calculate(int one, long two) {
return one + two;
}
}
사용하려고보면, 아래와 같이 오버로딩된 4개의 메서드가 보이는 것을 확인할 수 있다.
오버라이딩의 예시
오버로드는 취향이나 컨벤션에 따라 사용여부가 갈리지만 오버라이딩은 매우 일반적으로 사용되는 편이다.
부모 클래스가 가지고 있는 메서드를 자식 클래스가 재정의해서 사용할 수 있는데, 이것도 조건이 있다.
- 이름이 같아야 한다
- 매개변수(param)가 같아야 한다
- 반환타입이 같아야 한다
- 부모 클래스의 메서드의 접근 제어자보다 더 좁은 범위로 지정할 수 없다.
- 예외처리를 부모 클래스의 메서드보다 많이할 수 없다.
- 일반 메서드를 static으로 혹은 static을 일반 메서드로 변경할 수 없다. (이런 짓을 왜 함?)
이런 조건들을 외우기보다, 내부 로직만 바꿀 수 있다고 생각하면 대체로 이해할 수 있다. 일단 써보면 알게 됩니다.
구체적인 코드로 살펴보자.
아래는 일반적인 상속을 이용하여 클래스를 정의한 코드이다.
public class OverridingParent {
public String showCompany() {
String companyName = "tesla";
return companyName;
}
}
// 상속
public class OverridingChild extends OverridingParent {
@Override
public String showCompany() {
// super는 부모 클래스를 가리킴. 부모 클래스의 showCompany의 메서드를 사용하는 것임.
String parentString = super.showCompany();
// 자식 클래스에서 일부 수정 가함.
String childString = parentString + " spaceX";
return childString;
}
}
오버라이딩 코드는 직접 작성해줄 수도 있지만 ide에서 제공하는 기능을 이용하여 자동 생성하는 것이 좋다.
상속(extends)까지 작성한 후에 child 클래스의 내부에 우클릭-[source]-[override/implement method]
intellij에서는 alt + enter 단축키를 누른 후 오버라이딩을 선택해서 진행하면 된다.
메서드 오버라이딩을 잘 사용하기 위해서 부모 클래스는 로직을 추상적으로 구현하여서, 이를 상속 받는 클래스가 이를 활용할 수 있도록 하는 것이 좋다. 너무 구체적으로 코드를 짜면 오버라이딩할 구석이 없어진다.
public class OverridingChild extends OverridingParent {
@Override
public String showCompany() {
// TODO Auto-generated method stub
return super.showCompany();
}
}
7. 생성자(constructor)에 대하여
앞에서 이미 살펴봤지만, 클래스 이름과 동일해야 함.
1. 생성자를 명시하지 않아도 컴파일할 때 생성자가 없으면 클래스이름() {} 꼴의 default constructor를 자동으로 추가한다.
그러니까 아래 처럼 작성해도, 해당 클래스를 기반으로 한 인스턴스도 정상적으로 속성을 갖는다.
public class ConsSampleOne {
public String cpuName;
public String gpuName;
}
2. 일반적인 생성자
에이, js/ts하면서 다 해봤잖아요. 패스
public class ConsSampleTwo {
public String cpuName;
public String gpuName;
ConsSampleTwo(String cpuName, String gpuName) {
this.cpuName = cpuName;
this.gpuName = gpuName;
}
}
8. 상속에 대해서
생성자와 초기화 블록은 상속되지 않는다.
단 하나의 부모만 상속받을 수 있다.
예시... 생략하겠다. 다들 알잖아요 ㅋㅋ
9. abstract
추상클래스에 추상메서드 만들어 보겠습니다.
// abstract class
public abstract class Car {
// abstract method.
// Abstract methods cannot have a body
public abstract void sayBrand();
}
자동으로 abstract method를 implement하라고 경고를 띄워주네요.
public class Hyundai extends Car{
@Override
public void sayBrand() {
System.out.println("hyendai");
}
}
10. 접근 제한자
public, private, protected. got it?
작성은 뭐 편하게 아래처럼 해주시면 되겠구요
public class GoodsVO {
public String goodsName = "연필";
protected int goodsCount = 1;
String goodsUnitName = "다스";
private int goodsPrice = 8000;
public int getGoodsPrice() {
return goodsPrice;
}
public void setGoodsPrice(int goodsPrice) {
this.goodsPrice = goodsPrice;
}
}
getter/setter 코드 생성을 시도하면 아래와 같이 public, protected, (아무 것도 안한 것), private이 다음과 같은 기호로 표현됩니다. (intellij 기준)
'Programming Language > ☕ Java' 카테고리의 다른 글
연산자 (0) | 2020.11.26 |
---|---|
변수 타입, 상수(final), 리터럴, 출력 지시자, 타입 캐스팅 (0) | 2020.11.15 |
제어문 : 조건문(if/switch)과 반복문(for/while) (0) | 2020.09.12 |
IDE없이 직접 컴파일하고 실행해보기 (0) | 2020.09.12 |
openJDK 설치, JVM, JIT 컴파일 방식, hello world (0) | 2020.09.11 |