java 클래스!
클래스
여기선 java 클래스 문법에서 사용되는 특이한 문법과 개념만을 소개한다.
다중상속
C++ 에선 class
의 다중상속이 가능하지만 java 에선 class
의 상속은 하나만 가능하다.
대신 interace
는 다중상속이 가능하며 형태가 동일할 경우 이후에 상속된 메서드의 재정의를 강제한다.
public class Main {
public static void main(String[] args) {
A a = new A();
System.out.println(a.hello()); // hihi
}
public static class A extends AA implements AI, BI {
}
public static class AA {
public String hello() {
return "hihi!";
}
}
public interface AI {
default String hello() {
return "world";
}
}
public interface BI {
default String hello() {
return "foo! bar!";
}
}
}
Java의 경우 상속이 순환처리되어 있을 경우 에러를 발생한다, 별도의 상속을 선택하는 로직을 만들어 두지 않았기때문에 허용하지 않는다.
이뿐만 아니라 다중상속의 문제점이 몇가지 있는데 어떤 내부 구조를 직관적으로 파악할 수 없다.
위 코드만 봐도 어떤 hello 메서드가 호출될지 헷갈린다.
다중상속이 있는 C++도 다중상속을 잘 쓰지 않는다(여러 에러를 동반하기 때문)
물론 탬플릿패턴
, 전략패턴
같은 디자인패턴을 사용할 때 다중상속을 사용할 수 있지만,
잘 분리되어 있는 인터페이스만 다중상속하길 권장하며 그 외의 경우에는 추천하지 않는다.
최근 만들어지는 언어들도 다중상속의 여러 문제점 때문에 다중상속 문법을 구현하지 않는다.
abstract, final
abstract
키워드를 사용하면 interface
와 class
의 중간단계로 설계 가능하다.
Test.print
몸체를 생략한 불완전한 메소드를 abstract
메소드라 한다.
그리고 메서드에 abstract
가 들어가 있으면 class
앞에도 abstract
키워드를 붙여야한다.
상속만 가능하고 인스턴스화 시킬 순 없다.
class abstract Test {
void add() {
System.out.println("추가합니다.");
}
//print를 아직 어떻게 구현할지 안정했다.
// 앞에 abstract 키워드를 붙이자.
abstract void print();
}
반대로 final
키워드를 사용하면 추가적으로 상속을 금지시키는 완전한 class
를 정의할 수 있다.
public static final class Test2{ // 더이상 상속 불가 객체
void add() {
System.out.println("더이상 add overriding 불가");
}
}
다형성(polymorphism) - UpCasting DownCasting
자식객체를 인스턴스화 시켜서 부모객체에 참조시키는 것을 UpCasting
.
반대로 자식참조변수에 형변환을 통해 인스턴스를 참조시키는 것을 DownCasting
이라 한다.
다형성은 UpCasting
과 오버라이딩을 통해 알수있는 개념이다.
public class Demo {
public static void main(String[] args) {
Car a = new Truck(); // UpCasting
Truck b = (Truck) a; // DownCasting
}
}
class Car { }
class Truck extends Car { }
아래와 같이 클래스 4개 선언
class명 | dscription |
---|---|
Employee |
(사원), 이름, 주소, 연락처, 입사일자 를 필드로 갖고있다. |
Regular extends Employee |
(정규직), Employee + 기본급 - 기본생성자, 매개변수 5개 생성자. |
SalesMan extends Regular |
(영업직), Regular + 판매개수 + 물품금액 |
TempEmployee extends Employee |
(임시직), Employee + 출근일수 + 일급 |
Employee
객체로 다양한(다형) 인스턴스를 모두 참조할 수 있다.
다형성과 abstract
는 항상 자주 붙어다니는데 getPay
라는 함수로 예를 들어보면
Employee
는 그저 사원의 이름, 주소정도만 저장하는 객체이다 (절대 생성을 목표로 만들어진게 아님)
이렇게 코드 절약을 위해 생겨난 상속만을 위한 클래스를 추상클래스라 한다.
Employee
에 getPay
라는 추상 메소드를 만들고
Employee
를 상속하는 하위 클래스들은 모두 getPay
를 오버라이딩 해야한다.
보직마다 모두 다른 급여형태로 오버라이딩 될거고
이게 다형성을 의미한다.
Employee
같은 추상 클래스 객체에 할당된 인스턴스들은 인스턴스 종류에 따라 overrding
된 각기 다른 getPay
함수를 수행할 것이다.
Employee
클래스를 배열로 만들고 각종 다른 종류의 사원들은 이 배열안에 모두 인스턴스화 될수 있다.
관리도 편하고 다양하고!
함수에서 Employee
객체로 parameter를 사용하면 Employee
를 상속하는 하위클래스가 매개변수로 들어와도 하나의 함수로 모두 커버 가능하다.
instanceof 연산자
다형성에 UpCasting
이였다면 instanceof
에는 DownCasting
이다.
아무생각없이 DownCasting
쓰면 런타임 에러나는데
instanceof
를 쓰면 자기보다 큰 인스턴스를 할당 받을수있는걸 확신할 수 있다.
instanceof
란 어떤 인스턴스인지 물어보는 연산자이다
private static void printEmpPay(Employee emp) {
String empName;
//emp가 SalesMan이니?
if(emp instanceof SalesMan) {
SalesMan e = (SalesMan)emp;
//확인하고 downcasting하자 안그러면 런타임 에러나니까
empName = "영업직";
}
else if(emp instanceof TempEmployee) {
empName = "임시직";
}
else if(emp instanceof Regular) {
empName = "정규직";
}
else if(emp instanceof Employee) {
empName = "사원";
}
System.out.printf("%s의 급여: %d", empName, emp.getPay());
}
주의할점은 instanceof
은 상위 하위 인스턴스 구분을 못한다.
만약 if(emp instanceof Employee)
을 if
문들중 가장 위에 올려다 놓으면 모두 사원으로 출력이 된다.
(모두 Employee를 상위객체로 두고있으니까!). 쓸거면 자식부터 물어보자.
그럼 DownCasting
하는이유는 뭘까
class Parent {}
class Child extends Parent {}
여기서 Child
만 갖고있는 함수를 하나 만들자
public void printChild() {}
그리고 별도의 함수를 선언한다. 그리고 child
만 갖고있는 함수를 호출해보자.
public static void DownCastindTest(Parent p)
{
//p.printChild(); //에러난다. 만약 이 함수를 쓰고 싶다면 Parent객체인 p를 Child로 DownCasting해줘야한다.
if(p instanceof Child)
{
Child c = (Child)p;
c.printChild();
}
}
DI(Dependency Injection)관계
Car
와 Engine
클래스가 있을때 has-a
관계이다.
Engine
이 초기화된 NewCar
를 생성하는 여러가지 방법이 있다.
class NewCar { // 1
public Engine eng = new Engine(); // 명시적 초기화
}
class NewCar { // 2
public Engine eng;
public NewCar() {
this.eng = new Engine();
}
}
class NewCar { // 3
public Engine eng;
public NewCar(Engine eng) {
this.eng = eng;
}
}
아래로 갈수록 결합력이 약한 생성방식이다.
Engine 클래스가 abstract, interface 키워드가 붙은 클래스라 할 경우 3번째 방법 외에는 모두 오류가 발생한다.
업캐스팅
코드를 추가했을 때를 생각해 보아도 3번째 방법이 유일하다.
has-a
관계를 의존성(Dependency
)관계라고 하는데 이런 관계에서 밖에서 인스턴스를 만들고 객체에 생성자를 통해 주입하는 방법을
Dependency + Injection = DI
관계라고 한다.
래퍼클래스
원시타입인 int, long 등의 변수를 래퍼클래스로 감싸 지원하는 함수들을 쉽게 사용할 수 있다.
// 명시적 변환
Integer integer = Integer.valueOf(n);
int i = Integer.valueOf(n).intValue();
// 자동 변환
int i = 10;
Integer rapperInt = i;
long l = 1L;
Long rapperLong = l;
자동형변환이 되는 이유는 Boxing
과 Unboxing
때문이다.
기본형 → 래퍼: Auto Boxing
래퍼 → 기본형: Auto Unboxing
둘다 캐스팅 연산이 필요없이 자동으로 변환되기 때문에 앞에 auto
가 붙는다.
public static void disp(Object o) {
System.out.println(o);
}
public static void main(String[] args){
disp(100); // 100
}
int
자료형 100
을 Object
매개변수로 넘겨서 바로 출력한다.
100
이 바로 Object
로 변환되는 것이 아니라 몇 단계를 거쳐서 Object
로 변환된다.
100 → Auto Boxing → Integer → UpCasting → Object
문자열의 원시타입 변환도 래퍼클래스에서 제공하는 static 메서드를 사용하면 쉽게 처리 가능.
String n = "100";
int i = Integer.parseInt(n);
byte b = Byte.parseByte(n);
short s = Short.parseShort(n);
long l = Long.parseLong(n);
float f = Float.parseFloat(n);
double d = Double.parseDouble(n);
Integer를 사용한 진법 변환
i = Integer.parseInt("100", 2);
System.out.println(i); // 4
2진수 100(2)
으로 인식한다.
System.out.println(Integer.parseInt("110", 2)); // 6
System.out.println(Integer.parseInt("FF", 16)); // 255
System.out.println(Integer.parseInt("1F", 16)); // 31
System.out.println(Integer.parseInt("100", 2)); // 4
System.out.println(Integer.parseInt("100", 8)); // 64
System.out.println(Integer.parseInt("100", 16)); // 256
문자열 format
함수로도 진법 변환을 할 수 있다.
System.out.println(String.format("%#X", 10)); // 0XA, 16진수로 출력
System.out.println(String.format("%#o", 10)); // 012, 8진수로 출력
아쉽게도 2진수는 없음….
2진수 -> 16진수로 변경하여 출력하기
System.out.println(Integer.toHexString(Integer.valueOf("1010", 2))); // a
enum
java
에서 enum
은 일종의 클래스이다.
public class EnumDemo {
enum DayOfWeek {SUN,MON,TUE,WED,THU,FRI,SAT}
public static void main(String[] args) {
System.out.println(DayOfWeek.MON); //MON
System.out.println(DayOfWeek.MON.name()); //MON
System.out.println(DayOfWeek.MON.ordinal()); //1
DayOfWeek week = DayOfWeek.MON;
switch(week) {
case MON:
//스위치문에선 특이하게 열거형이름 생략,
//이미 week이 열거형이란걸 알고있기때문에
break;
case TUE:
break;
case WED:
break;
....
}
}
}
생각해보면 고유명칭이 int
형 변수로 저장되는것도 이상하다.
int
형 변수와 비교하다보면 에러도 생긴다
name()
열거형 상수의 이름을 문자열로 반환한다.
ordinal()
은 열거형 상수값위치를 반환한다.맨 처음값이 0
, 맨 뒤의 값 N
까지의 값을 반환할 수 있다.
열거형 변수간의 비교연산으로 ==
을 사용할 수 있지만 <, >
비교연산자는 사용할 수 없다.
비교를 위해선 compareTo()
를 사용해야 한다.
비교대상이 같으면 0
, 왼쪽이 크면+
, 오른쪾이 크면-
를 반환한다.
valueOf(열거명.class, 문자열)
아래와 같이 뜻이다.
valueOf(DayOfWeek.class, "MON")
는 DayOfWeek.MON
와 같은 값을 반환한다.
제어문을 사용할 때 편리할 수 있다.
문자열에 해당하는 열거형 인스턴스를 반환한다. 문자열이 MON
이라면 MON
반환
.class
는 Class
라는 상위 클래스의 참조변수로 해당 클래스 자료형 정보를 담고있는 Class클래스
의 멤버(참조)변수이다.
enum
안의 값들이 0~N
까지의 값으로 채워지지 않고 불규칙하게 채우려면 어떻게 해야할까?
public class EnumDemo {
enum DayOfWeek {
SUN(10),MON(20),TUE(30),WED(40),THU(50),FRI(60),SAT(70);
private final int value;
//생성자
DayOfWeek(int value) { this.value = value; }
public int getValue() { return this.value; }
}
public static void main(String[] args) {
System.out.println(DayOfWeek.MON); //MON
System.out.println(DayOfWeek.MON.name()); //MON
System.out.println(DayOfWeek.MON.ordinal()); // 1
System.out.println(DayOfWeek.MON.getValue()); // 20
}
}
SUN
, MON
, TUE
같은 열거형안의 참조변수(?) 모두가 DayOfWeek
클래스 자료형의 인스턴스인샘
사실 한번 값이 초기화되면 고정되니 참조변수는 아니다, 참조상수라 해야하나?
DayOfWeek
클래스 맴버로는 int value
하나있다.
그래서 MON
, TUE
, WED
등은 인스턴스를 참조하는 일종의 참조변수,
그렇기 때문에 int
형변수가 아니라 비교가 안된다.
그래서 특이한 값을 넣으려면 생성자도 있어야 하고 값을가져올 함수도 필요하다.
물론 DayOfWeek.MON.value
이렇게 갖고와도 된다.
디버그 결과 열거형 참조변수를 사용하는 순간 DayOfWeek
안의
모든 인스턴스(MON~SAT
)가 생성자를 통해 value
를 초기화한다.(한꺼번에)
사용하는 순간 모든 enum
인스턴스들이 초기화 되서 메모리에 올라가나 보다.
참고로 모든 열거형 클래스는 Enum
이라는 상위 클래스가 있다, 이안에 valueOf
라던가 compareTo
메소드가 있는 것. 물론 Enum
클래스는 Object
클래스를 상속한다.
조금 특이한 클래스….
상수를 전의하는 다양한 방법: http://www.nextree.co.kr/p11686/