[java] 기초(3)

세상을 바꾸는 개발자·2023년 2월 25일
0

LikeLion

목록 보기
3/5

일반변수에 대해서는, 값을 잃을 가능성이 이없는지 여부가 안전의 기준

  • 자바 컴파일러는 코드를 실행해 볼 수 없다.
  • 컴파일러에는 문법검사샘이 있는데 굉장히 엄격하다.
    • 차라리 오류가 개발자 앞에서 터지는게 더 낫기 때문에
  • 문법검사샘은 안전하지 않으면 무조건 개발자의 관심을 끌기 위해서 오류를 발생시킨다.


리모콘이 하위(구체적)타입으로 변하는 경우는 버튼이 추가될 가능성만 존재. 수동형변환 필수

자바는 버튼을 제거하는 것은 불편해지지만, 안전하다고 판단한다.
자바는 버튼이 추가하는 것은 편리해지지만, 위험하다고 판단한다.


구성

class Main {
	public static void main(String[] args) {
		전사 a전사 = new 전사();
		a전사.나이 = 22;
		a전사.a무기 = new();	
		a전사.a무기 = new();
		a전사.공격();
		// 전사가 활로 공격합니다.
	}
}
class 전사 {
	String 이름;
	int 나이;
	무기 a무기;
	void 공격() {
		a무기.작동(); // 위임
	}
}
abstract class 무기 {
	abstract void 작동();
}
classextends 무기 {
	void 작동() {
		System.out.println("전사가 칼로 공격합니다.");
	}
}
classextends 무기 { 
	void 작동() {
		System.out.println("전사가 활로 공격합니다.");
	}
}

=> a무기에 대해 칼, 활을 둘 다 사용해야하는데 class 전사{ 칼 a무기;} 하면 활을 못쓰고, class 전사{활 a무기;} 하면 칼을 못 씀. 그래서 둘 다 쓸 수 있는 무기 클래스를 만들어 줌

상속은 칼 is 무기
구성은 전사 has 무기


생성자

생성자를 만들지 않아도 기본 생성자가 있음

class Main {
    public static void main(String[] args) {
        전사 a전사 = new 전사("하하");
        // a전사.이름 = "카니"; // 자동으로 NoName
        // a전사.나이 = 22; // 자동으로 20살
        System.out.println(a전사.이름); // 출력 : 카니
        System.out.println(a전사.나이); // 출력 : 22
    }
}
class 전사 {
    String 이름;
    int 나이;

	// 생성자 메서드는 특수한 메서드 이다.
    // 명시적으로 개발자가 호출 할 수 없고, 객체가 생성될 때 자동으로 호출된다.
    // 생성자 메서드는 리턴타입이 없다.
    전사() { //클래스명과 같아야함
    	// this(); 와 같은 형태의 호출은 생성자에서만 가능하다.
        이름 = "NoName"; // 아래 있는 생성자를 호출
    }
}

=> 전사 객체가 만들어지자마자 디폴트값으로 이름="NoName"이 들어감


오버로딩

두 개이상의 똑같은 메소드가 한 클래스안에서 호출되는 것. 단, 매개변수가 달라야함.

class 전사 {
    String 이름;
    int 나이;

	// 생성자 메서드는 특수한 메서드 이다.
    // 명시적으로 개발자가 호출 할 수 없고, 객체가 생성될 때 자동으로 호출된다.
    // 생성자 메서드는 리턴타입이 없다.
    전사() { //클래스명과 같아야함
        이름 = "NoName"; // 아래 있는 생성자를 호출
    }
    
    전사(String 이름) {
        this.이름 = 이름; 
        나이 = 20;
    }
}

new 전사(); 하면 전사()가 실행
new 전사(이름); 하면 전사(String 이름)이 실행

전사(String 이름) 메소드 사용시
class의 이름매개변수의 이름이 같아서 헷갈릴 수 있기 때문에 this.이름을 사용(this.이름 == class의 이름)

생성자에서 생성자를 호출할 때, this("이름");
// this(); 와 같은 형태의 호출은 생성자에서만 가능하다.(this.이름의 this와 별개의 내용)


상속과 구성 사용

1단계 : 초기상태

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 공격() {
        System.out.println("칼로 공격"); //전사타입B와 칼 중복
    }
}

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

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

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



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 전사타입B extends 전사 {
    void 공격() {
        new().작동(); //칼 생성
    }
}

class 전사타입C extends 전사 {
    void 공격() {
        new().작동(); //활 생성
    }
}

class 전사타입D extends 전사 {
    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.공격();

        전사타입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 전사 {
    칼 a무기;

    전사타입A() {
        a무기 = new(); //생성자로 칼 자동 생성
    }

    void 공격() {
        a무기.작동();
    }
}

class 전사타입B extends 전사 {
    칼 a무기;

    전사타입B() {
        a무기 = new(); //생성자로 칼 자동 생성
    }

    void 공격() {
        a무기.작동();
    }
}

class 전사타입C extends 전사 {
    활 a무기;

    전사타입C() {
        a무기 = new(); //생성자로 활 자동 생성
    }

    void 공격() {
        a무기.작동();
    }
}

class 전사타입D extends 전사 {
    활 a무기;

    전사타입D() {
        a무기 = new(); //생성자로 활 자동 생성
    }

    void 공격() {
        a무기.작동();
    }
}

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

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



4단계 : 무기 클래스 도입

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 전사 {
    무기 a무기;

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

    void 공격() {
        a무기.작동();
    }
}

class 전사타입B extends 전사 {
    무기 a무기;

    전사타입B() {
        a무기 = new();
    }

    void 공격() {
        a무기.작동();
    }
}

class 전사타입C extends 전사 {
    무기 a무기;

    전사타입C() {
        a무기 = new();
    }

    void 공격() {
        a무기.작동();
    }
}

class 전사타입D extends 전사 {
    무기 a무기;

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

    void 공격() {
        a무기.작동();
    }
}

abstract class 무기 {
    abstract void 작동();
}

classextends 무기 {
    void 작동() {
        System.out.println("칼로 공격");
    }
}

classextends 무기 {
    void 작동() {
        System.out.println("활로 공격");
    }
}



5단계 : 전사클래스들에 있던 중복코드를 전사클래스로 모아서 중복제거

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 전사 {
    무기 a무기;

    void 공격() {
        a무기.작동();
    }
}

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

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

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

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

abstract class 무기 {
    abstract void 작동();
}

classextends 무기 {
    void 작동() {
        System.out.println("칼로 공격");
    }
}

classextends 무기 {
    void 작동() {
        System.out.println("활로 공격");
    }
}



6단계 : 무기 교체도 가능

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.공격();
        a전사타입D.공격();

        a전사타입D.a무기 = new 로켓런처();
        a전사타입D.공격();
    }
}

class 전사 {
    무기 a무기;

    void 공격() {
        a무기.작동();
    }
}

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

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

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

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

abstract class 무기 {
    abstract void 작동();
}

classextends 무기 {
    void 작동() {
        System.out.println("칼로 공격");
    }
}

classextends 무기 {
    void 작동() {
        System.out.println("활로 공격");
    }
}

class 로켓런처 extends 무기 {
    void 작동() { System.out.println("로켓으로 공격"); }
}



생성자 연쇄호출

실행순서는 부모 생성자가 먼저 호출됨
청둥오리() -> 오리() -> 동물() -> 생물() -> Object() 순으로 호출 되지만, 실질적으로 출력이 실행되는 순서는
Object() -> 생물 -> 동물() -> 오리() -> 청둥오리() 이다.


super 메서드를 명시적으로 호출해야할 때

class 동물{
    동물(String 이름){}
    동물(String 이름, int 나이){}
}

class 사람 extends 동물{
    사람(){
        super("홍길동");
        //super("홍길동", 20);
    }
}

생성자 실행시 super()가 기본적으로 실행되는데, 부모 클래스인 동물 클래스에 매개변수가 있는 생성자만 가지고 있음. super("홍길동")을 하여 매개변수와 맞춰줘야 함

class 동물{
    동물(String 이름){}
    동물(String 이름, int 나이){}
}

class 사람 extends 동물{
    사람(){
        this(23);
    }
    
    사람(int 나이){
    	super("홍길동", 나이);
    }
}

super()사용하지 않고 this()를 사용하여 호출에 대한 책임을 위임
this(23); 실행시 사람(int 나이){} 생성자에게 책임이 넘어감.


인터페이스

인터페이스 == 클래스
구현 == 상속
java에서는 다중 상속이 불가하므로 구현을 사용(extends 대신에 implements 사용)
implements는 interface에 대해서만 사용
인터페이스는 추상메소드로만 구성되어야 함

class Main {
	public static void main(String[] args) {
		사람 a사람 = new 홍길동();
		변호사 a변호사 = (변호사)a사람;
	}
}

abstract class 사람 {
	void 말하다(){} //구상메소드
    abstract void 일하다(){{ //추상메소드
}

class 홍길동 extends 사람 implements 변호사 {
}

interface 변호사 {
	abstract void 일하다(); //추상메소드
}



접근제한자와 게터, 세터

public : 접근 제한이 없음
(default) : 같은 패키지 내에서 접근 가능
proteted : 같은 패키지 내에서, 다른 패키지 자손 클래스에서 접근 가능
private : 같은 클래스 내에서만 접근 가능

class Main {
	public static void main(String[] args) {
		사람 a사람 = new 사람();
		
		a사람.setId(20);
		System.out.println("제 번호는 " + a사람.getId() +" 입니다.");
		// 출력 : 제 번호는 20 입니다.
	}
}

class 사람 {
	private int id;
	
	// 세터 메서드
	void setId(int id) {		
		this.id = id;
	}
	
	// 게터 메서드
	int getId() {
		return id;
	}
}

private int id이기 때문에 외부에서는 접근할 수 없으므로 getId(), setId()를 만들어서 접근


예외

오류 : 컴파일 오류(문법 오류 등)
예외 : 실행중 오류

try-catch

  • 예외가 발생할 가능성이 있는 코드를 처리해주고 프로그램이 정상적으로 실행되도록 도와줌
  • { } 생략 불가
  • 해당되는 catch블럭이 있으면 catch문 실행 후 전체 try-catch문 빠져나감
class Main {
	public static void main(String[] args) {
		int rs = 계산기.나누다(10, 0);
		System.out.println(rs);
	}
}
class 계산기 {
	static int 나누다(int a, int b) {
		int rs = 0;
		
		try {
			rs = a / b;
		}
		catch ( ArithmeticException e ) { //0으로 나눌 경우 예외 처리
			rs = 0;
		}
		
		return rs;
	}
}



v1 : 경우에 따라서 main에서 try-catch가 이뤄질 수 도 있음.
그럴 경우 예외가 날 수 있는 메소드에 throws Exception을 붙임

class Main {
	public static void main(String[] args) {
		
        try {
        	int rs = 계산기.나누다(10, 0);
			System.out.println(rs);
			
		}
		catch ( ArithmeticException e ) { //0으로 나눌 경우 예외 처리
			rs = 0;
		}
	}
}

class 계산기 throws ArithmeticException { //예외날 수 있음을 미리 알림
	static int 나누다(int a, int b) {
		int rs = a / b;
		return rs;
	}
}



v2 : 예외가 날 것을 예측해서 미리 보고
throw 키워드로 예외 발생 시킴. (ex. throw new IllegalArgumentException();)

class Main {
    public static void main(String[] args) {
        int[] datas = new int[2];
        
        try {
            work(datas);
        }
        catch ( IllegalArgumentException e ) { // v1 의 코드보다 원인이 좀 더 명확해진다. 즉 v1 보다 더 가독성 높은 코드이다.
            System.out.println("이런.. 오류가 발생했군요.");
        }
    }
    
    static void work(int[] datas) {
        if ( datas.length < 3 ) {
            throw new IllegalArgumentException(); // 함수가 여기서 멈춤
        }
        
        datas[0] = 10;
        datas[1] = 20;
        datas[2] = 30;
    }
}



v3 : 직접 예외클래스 생성

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 { }
profile
초심 잃지 않기

0개의 댓글