[Java] 기초3

김민우·2023년 2월 27일
0
post-thumbnail

형변환

//문제가 없다.
int a = 10;
int b = 20;

//컴파일러가 묵시적 형변환을 해준다. c = (double) a
double c = a; 

//이 부분이 에러가 난다.
float d = 5.1f;
int e = d;

-> java: incompatible types: possible lossy conversion from double to int 라는 에러가 Intellij에서 실행시키면 발생한다.

에러가 발생하는 이유

  • 컴파일러가 봤을때 d가 5.1인줄 모른다. 컴파일러는 float이라는 것에만 의존한다. 따라서 e = d;에서 d에 어떤 값이 들어있는지 모르기 때문에 컴파일러가 에러를 터트린다.
  • 컴파일러는 안전하다고 판단되면 자동 형변환을 해주는데 그렇지 않으면 에러를 터트린다.
int e = (int) d

따라서 위와 같이 수동 형변환을 해야한다. float 형태로 int 타입으로 형변환을 했기 때문에 데이터 손실이 발생한다. but 실행은 된다.

상속과 구성

-> 결론부터 말하면 상속만으로는 중복제거가 쉽지 않다. 따라서 구성을 사용한다.

  • 구성은 클래스내의 멤버변수로 클래스 변수를 가지는 것이다.

구성

초기코드

// 1단계
class Main {
  public static void main(String[] args) {
      전사타입A a전사타입A = new 전사타입A();
      a전사타입A.공격();
      
      
      전사타입D a전사타입D = new 전사타입D();
      a전사타입D.공격();
  }
}

class 전사타입A extends 전사 {
  void 공격() {
      System.out.println("칼로 공격");
  }
}

class 전사타입D extends 전사타입C {
  void 공격() {
      System.out.println("활로 공격");
  }
}

2단계 - 칼, 활 클래스 도입

class Main {
    public static void main(String[] args) {
        전사타입A a전사타입A = new 전사타입A();
        a전사타입A.공격();
        
        전사타입B a전사타입B = new 전사타입B();
        a전사타입B.공격();
        
        전사타입C a전사타입C = new 전사타입C();
        a전사타입C.공격();
        
        전사타입D a전사타입D = new 전사타입D();
        a전사타입D.공격();
    }
}

class 전사{
}

class 전사타입A extends 전사 {
    void 공격() {
        new().작동();
    }
}

}
class 전사타입D extends 전사타입C {
    void 공격() {
        new().작동();
    }
}

class{
  void 작동() { System.out.println("칼로 공격"); }
}
class{
  void 작동() { System.out.println("활로 공격"); }
}

3단계 - 전사 클래스의 구성으로 무기 도입

class Main {
    public static void main(String[] args) {
        전사타입A a전사타입A = new 전사타입A();
        a전사타입A.공격();
        
		//신무기 나오면 이렇게 교체하면 된다.
        a전사타입D.a무기 = new 로켓런처();
        a전사타입D.공격(); // 로켓이 발사됩니다.
    }
}

abstract class 전사 {
  무기 a무기;
    void 공격() {
        a무기.작동();
    }
}

class 전사타입A extends 전사 {
  전사타입A() { a무기 = new(); }
}
class 전사타입D extends 전사 {
  전사타입D() { a무기 = new(); }
}

abstract class 무기 { abstract  void 작동(); }
class{
  void 작동() { System.out.println("칼로 공격"); }
}
class{
  void 작동() { System.out.println("활로 공격"); }
}
class 로켓런처 extends 무기 {
  void 작동() { System.out.println("로켓이 발사됩니다."); }
}

-> 위와 같이 상속과 구성을 함께 사용하면 중복코드를 좀 더 수월하게 제거할 수 있다.


생성자 연쇄 호출

  • 모든 클래스는 따로 생성하지 않아도 클래스명() 형태의 기본 생성자가 생성된다. 이것을 "기본 생성자"라고 부른다.
  • 생성자도 상속이 된다.
  • 생성자는 오버로딩이 가능해서 여러 개 작성 가능하다.

class Main {
    public static void main(String[] args) {
				//생성자 호출 순서 : Object -> 생물 -> 동물 -> 오리 -> 천둥오리
        천둥오리 천둥오리 = new 천둥오리();
    }
}
//모든 클래스는 extends Object가 생략되었다.
//즉, 모든 클래스는 Object 클래스의 자식이다.
class 생물{
    생물(){
        System.out.println("생물");
    }
    void 호흡(){}
}
class 동물 extends 생물{
    동물(){
        System.out.println("동물");
    }
    void 먹기(){}
}
class 오리 extends 동물{
    오리(){
        System.out.println("오리");
    }
    void 날기(){}
}
class 천둥오리 extends 오리{
    천둥오리(){
        System.out.println("천둥오리");
    }
    void 긴거리이동(){}
}
  • 현재 천둥오리 class의 메서드는 몇개일까?(생성자 포함)
    • 생성자 메서드는 5개(Object, 생물, 동물, 오리, 천둥오리)
    • 일반 메서드는 4개(호흡, 먹기, 날기, 긴거리 이동)
  • 자식 클래스를 생성하면 부모 클래스의 생성자가 자동으로 생성된다. 호출 순서는 부모-> 자식 순서이다.
    • Object -> 생물 -> 동물 -> 오리 -> 천둥오리 순서로 생성자가 호출이 된다.
  • 모든 생성자 코드에는 super()라는 것이 생략되어 있는데 super()는 부모 클래스의 기본 생성자를 호출하는 것이다. 만약, 기본생성자가 아닌, 다른 생성자를 호출하고 싶으면 super(매개변수); 이렇게 호출하면 된다.

인터페이스, 구현

  • 인터페이스는 클래스와 동일하게 보면 된다.
  • 구현은 상속과 동일하게 보면 된다.
  • 인터페이스는 method가 모두 추상이여야 한다.
    • 즉, 인터페이스에는 구현되어 있는 method가 없다.

예외처리(try-catch-finally)


  • 기본적인 예외 처리
class Main {
    public static void main(String[] args) {
        int[] datas = new int[2];
        
        try {
            work(datas);
        }
        catch ( ArrayIndexOutOfBoundsException e ) { // main 함수 입장에서 이 코드는 가독성이 떨어진다.
            System.out.println("이런.. 오류가 발생했군요.");
        }
    }
    
    static void work(int[] datas) {
        datas[0] = 10;
        datas[1] = 20;
        datas[2] = 30; // 여기서 자동으로 throw new ArrayIndexOutOfBoundsException(); 이 발생한다.
    }
}



  • 사건이 터질것을 예측해서 미리 보고(throw new)
class Main {
    public static void main(String[] args) {
        int[] datas = new int[2];
        
        try {
            work(datas);
        }
				//work()에 발생한 에러를 여기서 처리
        catch ( IllegalArgumentException e ) {
            System.out.println("하하");
        }
    }
    
    static void work(int[] datas) {
				//try-catch로 예외를 잡는거보다 이러한 방법이 더 낫다.
        if ( datas.length < 3 ) {
            throw new IllegalArgumentException();
        }
        
        datas[0] = 10;
        datas[1] = 20;
        datas[2] = 30;
    }
}

  • 명확한 사건보고를 위해 직접 예외 클래스 생성하여 보고(예외직접발생, throw)
class Main {
    public static void main(String[] args) {
        int[] datas = new int[2];
        
        try {
            work(datas);
        }
        catch ( 입력된_배열의_사이즈가_3보다_작은_Exception e ) { // 코드 가독성이 v2 보다 좋음, 단 예외클래스를 만들어야 해서 귀찮음, 그래서 실무에서는 예외클래스를 꼭 필요할 때만 직접 만듬
            System.out.println("이런.. 오류가 발생했군요.");
        }
    }
    
    static void work(int[] datas) {
        if ( datas.length < 3 ) {
            throw new 입력된_배열의_사이즈가_3보다_작은_Exception(); // 함수가 여기서 멈춤
        }
        
        datas[0] = 10;
        datas[1] = 20;
        datas[2] = 30;
    }
}

class 입력된_배열의_사이즈가_3보다_작은_Exception extends RuntimeException { }

0개의 댓글