매직 넘버 치환

  • 소스 코드에 특정한 숫자(매직 넘버(magic number))를 직접 작성하는 것은 나쁜 코딩 스타일

나쁜 예

public class MagicNumber {
    public static void main(String[] args) {
        MagicNumber magicNumber = new MagicNumber();
        MagicNumber.Player player = magicNumber.new Player("Hoonmaro");
        // 0은 공격
        player.action(0);
        // 1은 방어
        player.action(1);
    }

    public class Player {

        private String name;

        public Player() {
        }

        public Player(String name) {
            this.name = name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void action(int command) {
            if (command == 0) {
                attack();
            } else if (command == 1) {
                defence();
            }
        }

        private void attack() {
            System.out.println(this.name + " is going to Attack!!");
        }

        private void defence() {
            System.out.println(this.name + " is going to Defence!!");
        }

    }
}

나쁜이유

  1. 매직 넘버의 의미를 알기 어렵다
    • 0과 1이 무슨 의미인가?
    • COMMAND_ATTACK과 같은 기호 상수를 쓰면 의미를 알기 쉽다
  2. 매직 넘버는 수정하기 어렵다
    • 0 또는 1 커맨드 숫자가 변경 될 경우 치환해야 하는데, 만약 많은 곳에서 동일한 커맨드를 쓴다면?
    • 손이 많이 가고, 틀리기 쉽고, 빼먹는 경우가 생길 수도 있다.

카탈로그

이름 매직 넘버를 기호 상수로 치환 (Replace Magic Number with Symbolic Constant)
상황 상수를 사용하고 있음
문제 • 매직 넘버는 알기 어려움
• 매직 넘버가 여러 곳에 있으면 변경하기 어려움
해법 매직 넘버를 기호 상수로 치환함
결과 장점
• 상수의 의미를 알기 쉬워짐
• 기호 상수의 값을 변경하면 상수를 사용하는 모든 곳이 변경됨
단점/주의 • 이해하기 어려운 이름을 사용하면 오해가 생길 수 있음
방법
  1. 기호 상수 선언하기
    • 1. 기호 상수 선언
    • 2. 매직 넘버를 기호 상수로 치환
    • 3. 기호 상수에 의존하는 다른 매직 넘버를 찾아서 기호 상수를 사용한 표현식으로 변환
    • 4. 컴파일
  2. 2. 테스트
    • 1. 모든 기호 상수 치환이 끝나면 컴파일해서 테스트
    • 2. 가능하다면 기호 상숫값을 변경한 후 컴파일해서 테스트 |
관련 항목 • 분류 코드를 클래스로 치환
• 분류 코드를 상태/전략 패턴으로 치환

리팩토링

기호 상수 선언하기

  1. 기호 상수 선언
  • public static final 클래스 필드 사용
  • 또는 enum 사용
  • 어떤 클래스 안에서만 사용할 기호 상수를 선언할 경우 private 접근지시자를 사용할 수 있다
public static final int COMMAND_ATTACK = 0;
public static final int COMMAND_DEFENCE = 1;
  1. 매직 넘버를 기호 상수로 치환
  • 0, 1과 같은 매직 넘버를 기호 상수로 치환
// if (command == 0) {
if (command == COMMAND_ATTACK) {

// else if (command == 1) {
} else if (command == COMMAND_DEFENCE) {


// player.action(0)
// player.aciton(1)
player.action(Player.COMMAND_ATTACK);
player.action(Player.COMMAND_DEFENCE);
  1. 기호 상수에 의존하는 다른 매직 넘버를 기호 상수를 사용한 표현식으로 변환
  • 상수 의존 관계는 상수 사이에 의존 관계가 있어 한 상수의 변경이 다른 상수에게도 영향을 미치는 경우를 말한다.
  • 이 때, 표현식으로 의존 관계를 표현해야 한다.
// public static final int MAX_INPUT_LENGTH = 100;
// public static final int WOR_ARE_LENGTH = 100 * 2;

public static final int MAX_INPUT_LENGTH = 100;
public static final int WOR_ARE_LENGTH = MAX_INPUT_LENGTH * 2;

테스트

  1. 모든 기호 상수 치환이 끝나면 컴파일해서 테스트

    • 테스트 결과는 리팩토링하기 전과 같아야 한다.
  2. 가능하다면 기호 상숫값을 변경한 후 컴팡리해서 테스트

    • 기호 상수의 값을 다른 값으로 변경한 후 테스트하면 빠드린 곳이 없는지 확인할 수 있다.
    • COMMAND_ATTACK 값을 0에서 1000으로 변경해도, 매직 넘버가 없다면 테스트가 성공하지만 매직 넘버가 있다면 테스트가 실패한다.

리팩토링 후

public class MagicNumber {
    public static void main(String[] args) {
        MagicNumber magicNumber = new MagicNumber();
        MagicNumber.Player player = magicNumber.new Player("Hoonmaro");
        player.action(Player.COMMAND_ATTACK);
        player.action(Player.COMMAND_DEFENCE);
    }

    public class Player {

        public static final int COMMAND_ATTACK = 0;
        public static final int COMMAND_DEFENCE = 1;


        private String name;

        public Player() {
        }

        public Player(String name) {
            this.name = name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void action(int command) {
            if (command == COMMAND_ATTACK) {
                attack();
            } else if (command == COMMAND_DEFENCE) {
                defence();
            }
        }

        private void attack() {
            System.out.println(this.name + " is going to Attack!!");
        }

        private void defence() {
            System.out.println(this.name + " is going to Defence!!");
        }

    }
}
  • 기호 상수가 충분한 정보를 제공하므로 주석이 필요 없다.

더 나은 리팩토링

  • 기호 상수로 만든다고 해도 실제로는 상수 값이므로 매직 넘버를 직접 적어도 문제없이 컴파일 된다.
  • 실수가 생길 수 있다.
  • 커맨드 클래스 객체를 활용하거나 enum을 활용한다.

enum 활용

  • 자바 5부터 enum을 사용할 수 있다.
public class EnumMagicNumber {
    public static void main(String[] args) {
        EnumMagicNumber magicNumber = new EnumMagicNumber();
        Player player = new Player("Hoonmaro");
        player.action(Player.Command.ATTACK);
        player.action(Player.Command.DEFENCE);
    }
}

// Player 클래스 분리
public class Player {

    public enum Command {
        ATTACK,
        DEFENCE
    }

    private String name;

    public Player() {
    }

    public Player(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void action(Command command) {
        if (command == Command.ATTACK) {
            attack();
        } else if (command == Command.DEFENCE) {
            defence();
        }
    }

    private void attack() {
        System.out.println(this.name + " is going to Attack!!");
    }

    private void defence() {
        System.out.println(this.name + " is going to Defence!!");
    }

}

  • 기존 Player Inner 클래스를 분리하였다.
  • 증첩 enum 은 암묵적으로 static 클래스 이기 때문에 Inner 클래스 안에 선언할 수 없기 때문이다.
  • Java Language Spec 8.9 Enums 참고
  • enum을 활용하여 직관적으로 의미를 전달 할 수 있다.
  • 상수가 아니므로 잘못 사용된 곳에서 컴파일에서 경고가 발생하여 에러를 사전에 방지할 수 있다.

기호 상수가 적합하지 않은 경우

  • 배열 길이
    • 배열 길이는 배열 객체에 length라는 필드가 있다.
    • 기호 상수가 언제나 올바른 배열길이가 아닐 수도 있다.
  • 잘 알려진 값을 대체하는 기호 상수 (오히려 가독성이 떨어짐)

바이트 코드에 내장된 상수에 주의

  • 필드값이 컴파일할 때 정해지는 상수일 경우 리컴파일시 변경된 값으로 바뀌겠지만,
  • 이를 사용하는 다른 클래스에서는 리컴파일 하기 전에 이전 상수 값을 가지고 있으므로 문제가 발생한다.
  • 매직 넘버를 치환한 모든 소스코드를 리컴파일 해야 정상 작동 된다.

객체지향 프로그래밍

  • Object Oriented Programming, OOP *객체
    • 생활에서 일종의 물건
    • 속성(Attribute)와 행동(Action)을 가짐
  • OOP는 속성은 변수, 행동은 함수로 객체의 개념을 프로그램으로 표현

클래스, 객체, 인스턴스

  • 클래스는 객체의 구체적인 형태를 설계한 명령어의 집합, 템플릿 또는 설계도
  • 객체는 속성과 행위로 구성된 사물 또는 추상적 개념들의 정보를 표현한 것
  • 인스턴스는 클래스로부터 인스턴스화를 통해 구현된 객체

추상화 기법

  • 추상화 기법의 하나로 분류/인스턴스화(classification/instantiation) 개념 존재
  • 분류는 객체들을 공통적인 속성을 공유하는 개념으로 범주화
  • 인스턴스화는 추상화된 범주로부터 실재하는 객체를 만드는 과정을 의미
  • 인스턴스라는 말은 추상적인 개념과 구체적인 객체 사이의 관계에 초점에 맞춘 용어

프로그램에서의 클래스, 인스턴스

  • 추상화의 과정을 거쳐 속성과 행위를 가진 객체를 클래스로 표현하며, 클래스는 변수와 메서드를 가진다
  • 프로그램 언어별로 클래스를 만드는 문법을 사용하여 인스턴스를 생성하게 된다.
    • Java: ExampleClass exampleClass = new ExampleClass();
    • Python: example_class = ExampleClass()

문법

[접근지정자] [기타제어자] class 클래스명 [extends Super클래스] [implements 인터페이스…] {
	/* 필드(멤버변수) */
	/* 메서드(멤버함수) */
}
  • 멤버: 클래스 안에 선언된 것들

클래스명 작성 규칙

작성규칙
하나 이상의 문자로 이루어져야 한다 Car, SportsCar
첫 번째 글자는 숫자가 올 수 없다 Car, 3Car(x)
'$','_' 외의 특수 문자는 사용할 수 없다 $car, _Car, @Car(x), #Car(x)
자바 키워드(예약어)는 사용할 수 없다 int(x), for(x)
단일 단어 => 첫문자는 대문자 혼합 단어 => 각 단어 첫글자는 대문자

예제

  • Cat 클래스
public class Cat {
	
	/* 클래스 필드 (객체의 속성) */
	// 이름
	private String name;
	// 품종
	private String breed;
	// 나이
	private int age;
	
	/* 클래스 메서드 (객체의 행동) */
	public String cry() {
		return "냐옹";
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public void setBreed(String breed) {
		this.breed = breed;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public String getName() {
		return this.name;
	}
	
	public String getBreed() {
		return this.breed;
	}
	
	public int getAge() {
		return this.age;
	}
	
	// 모든 클래스의 부모인 Object 클래스의 toString 메서드 오버라이드
	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder("Cat{");
			sb.append("name='").append(name).append('\'');
			sb.append(", breed='").append(breed).append('\'');
			sb.append(", age='").append(age).append('\'');
			sb.append('}');
		return sb.toString();
	}
}
  • Cat 클래스 인스턴스화
public class CatClassTest {
	public static void main(String[] args) {
		// new 연산자를 통해 Cat 클래스를 인스턴스화
		Cat nabi = new Cat();
		nabi.setName("나비");
		nabi.setBreed("페르시안");
		nabi.setAge(2);
		System.out.println(nabi);
	}
}
  • 결과
Cat{name='나비', breed='페르시안', age='2'}

예제 설명

  • Cat 클래스를 만들고 클래스의 필드와 메서드를 선언
  • CatClassTest 클래스에서 Cat 클래스를 new 연산자를 통해 인스턴스화하여 인스턴스를 생성 후 nabi라는 변수에 레퍼런스 저장
  • 이 때 new 연산자를 통해 생성된 인스턴스는 힙(heap) 메모리 영역에 저장되며 nabi 변수는 메모리 레퍼런스(주소)를 가지고 있다.
  • setter(세터) 메서드를 통해 name, breed, age를 세팅 후 오버라이드한 toString 메서드를 통해 필드값들을 출력한다.

구성 멤버

필드(field)

  • 객체의 고유 데이터, 상태 정보를 저장하는 곳
  • 생성자와 메소드 전체에서 사용되며 객체가 소멸되지 않는 한 객체와 함께 존재
  • 초기화 하지 않으면 각 자료형의 기본값으로 초기화 됨
  • 필드선언
    • 생성자 선언과 메소드 선언의 앞 뒤 어떤 곳에서도 필드 선언 가능
    • 단, 생성자와 메소드 내부에서는 선언될 수 없음 (생성자,메소드 내부의 변수는 지역변수)
  • 필드사용
    • 필드 값을 읽고 변경하는 작업
    • 클래스 내부에서는 단순히 필드 이름으로 읽고 변경
    • 클래스 외부에서는 클래스로부터 객체 생성 후 사용 (일반적으로 직접 접근 못하게 캡슐화)

생성자(Constructor)

  • new 연산자로 호출되는 특별한 중괄호 블록
  • 객체 생성 시 초기화 담당
  • 필드를 초기화하거나 메소드를 호출해서 객체를 사용할 준비
  • 클래스 이름으로 되어 있고 리턴 타입이 없다
  • 생성자 선언
    • 생성자를 선언 안해도 기본적으로 컴파일시 디폴트 생성자가 생성됨
    • 생성자는 메소드와 비슷한 모양을 가지나, 리턴 타잆이 없고 클래스 이름과 동일
    • 클래스에 생성자가 명시적으로 선언되어 있을 경우 반드시 선언된 생성자를 호출해서 객체를 생성
    • 하나라도 인자값을 가진 다른 생성자를 호출할 경우 디폴트 생성자는 자동으로 생성되지 않으므로 디폴트 생성자를 호출하기 위해선 디폴트 생성자도 명시적으로 선언해줘야 함
  • 필드 초기화
    • 필드를 선언할 때 초기값을 주면 동일한 클래스로부터 생성되는 객체들은 모두 같은 데이터를 가짐
    • 객체 생성 시점에 외부에서 제공되는 다양한 값들로 초기화 되어야 한다면 생성자에서 초기화 해야 함
  • 관례적으로 필드와 동일한 이름을 갖는 매개변수 사용
    • 이 경우 필드와 매개변수 이름이 동일하므로 생성자 내부에서 해당 필드에 접근할 수 없다
    • why? 동일한 이름의 매개 변수가 사용 우선순위가 높다. 따라서 this 를 사용한다.
    • this는 객체 자신의 참조
public class Cat {
	private String name;
	public setName(String name) {
		// 필드 name과 매개변수 name의 이름이 같다
		// name = name
		this.name = name
	}
	
}
  • 생성자 오버로딩
    • 매개변수를 달리하는 생성자를 여러 개 선언하는 것
    • 오버로딩 시 주의점은 매개 변수의 타입과 개수 그리고 선언된 순서가 똑같을 경우 매개 변수 이름만 바꾸는 것은 오버로딩이라고 볼 수 없음
  • 다른 생성자 호출(this())
    • 생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드 발생
    • 이 경우 필드 초기화한 내용은 한 생성자에만 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법으로 개선
    • 생성자에서 다른 생성자 호출할 때 this() 코드 사용
    • this()는 자신의 다른 생성자를 호출하는 코드, 반드시 생성자 첫줄에서만 허용
public Cat(String name, String breed) {
        this.name = name;
        this.breed = breed;
}

public Cat(String name, String breed, int age) {
        this(name, breed);
        this.age = age;
}


메서드(Method)

  • 객체의 동작에 해당하는 중괄호 블록
  • 메서드 호출 => 중괄호 블록 내 모든 코드 실행
  • 용도
    • 필드 읽고 수정
    • 다른 객체 생성해서 다양한 기능 수행
    • 객체 간의 데이터 전달 수단
    • 이외 다양한 행동 구현
  • 메서드 선언
[접근제한자] [기타제어자] 반환자료형 메서드명(매개변수) {
    [return 리턴값;]
}
  • 메서드 선언부 = 메서드 시그너처

  • 리턴 타입

    • 메서드가 실행 후 리턴하는 값의 타입
    • 메서드 실행 후 결과를 호출한 곳에 넘겨줄 경우에는 리턴 값이 있어야 함
    • 리턴 값의 타입은 선언부의 반환자료형과 동일해야 함
    • 리턴 타입이 있다고 해서 반드시 리턴값을 변수에 저장할 필요 없음
    • void 타입의 경우 리턴문 없이 사용 가능
  • 매개 변수의 수를 모를 경우

    • 매개 변수를 배열 타입으로 선언
    • … 으로 선언후 리스트 나열
public int sum(int ... args) {
        return IntStream.of(args).sum();
}
  • 메서드 호출
  • 메서드는 클래스 내/외부의 호출에 의해 실행
    • 클래스 외부에서 호출할 경우 우선 클래스로부터 객체 생성
    • 클래스 참조변수 = new 클래스(매개값); * 참조변수.메서드(매개값); (리턴값 없거나, 받지 않을 경우) * 타입 변수 = 참조변수.메서드(매개값); (리턴값 받고 싶을 때)
  • 메서드 오버로딩
    • 클래스 내에 같은 이름의 메서드를 여러 개 선언하는 것
    • 매개 변수의 타입, 개수, 순서 중 하나가 달라야 한다 (시그니처가 달라야 한다)

열거 타입(Enumeration Type)

  • 한정된 값만을 갖는 데이터 타입이다.

열거 타입 선언

  • 열거 타입의 이름을 정하고 열거 타입 이름으로 소스 파일(.java)을 생성해야 한다.
  • 열거 타입 이름은 관례적으로 첫 문자를 대문자로하고 나머지는 소문자로 구성한다.
  • 만약 여러 단어로 구성된 이름일 경우 각 단어의 첫 문자는 대문자로 작성하는 것이 관례이다.
  • 예) Week.java, MemberLevel.java
  • 형식
  • 
    public enum 열거타입이름 {
    	(열거 상수 선언)
    }
    
    
    
  • 열거 상수는 열거 타임의 값으로 사용되며 관례적으로 모두 대문자로 작성한다.
  • 만약 열거 상수가 여러 단어로 구성될 경우 단어 사이를 밑줄(_)로 연결한다.
  • 
    public enum Month {
    	JANUARY,
    	FEBRUARY,
    	MARCH,
    	APRIL,
    	MAY,
    	JUNE,
    	JULY,
    	.
    	.
    }
    
    
    
  • 열거 상수는 열거 객체로 생성된다. 해당 열거 타입의 열거 상수 개수만큼 객체가 열거 타입 객체가 생성된다.
  • 열거 타입 Month의 경우 JANUARY부터 DECEMBER까지 12개의 열거 상수는 힙 영역에 Month 객체로 생성된다.
  • 그리고 메소드 영역에 생성된 열거 상수가 해당 해당 Month 객체를 각각 참조한다.


열거 타입 변수

  • 열거 타입을 선언했다면 열거 타입은 하나의 데이터 타입이므로 변수를 선언하고 사용한다.
  • 형식
  • 열거타입 변수;
  • 
    Month thisMonth;
    Month birthMonth;
    
    
  • 열거 타입 변수를 선언 후 열거 상수를 저장할 수 있다. 이 때, 열거 상수는 반드시 열거타입.열거상수로 사용된다.
  • 열거 타입 변수는 null 값도 저장할 수 있다.
  • 형식
  • 열거타입 변수 = 열거타입.열거상수;
  • 
    Month thisMonth = Month.JUNE;
    
    
  • 열거 타입 변수 thisMonth는 스택 영역에 생성된다.
  • thisMonth에 저장되는 값은 Month.JUNE 열거 상수가 참조하는 객체의 번지이다.
  • 즉, 열거 상수 Month.JUNE와 thisMonth 변수는 서로 같은 Month 객체를 참조하게 된다.
  • 그러므로 thisMonth == Month.JUNE 상수의 비교 결과는 true가 된다.

열거 객체의 메소드

  • 열거 객체는 열거 상수의 문자열을 내부 데이터로 가지고 있다.
  • 열거 객체의 메소드들은 java.lang.Enum 클래스에 선언된 메소드인데, 모든 열거 타입은 컴파일 시에 Enum 클래스를 상속하게 되어 있어 열거 객체에서 해당 메소드들을 사용할 수 있다.


리턴 타입

메소드(매개 변수)

설명

String

name()

열거 객체의 문자열을 리턴

int

ordinal()

열거 객체의 순번(0부터 시작) 리턴

int

compareTo()

열거 객체를 비교해서 순번 차이를 리턴

열거 타입

valueOf(String name)

주어진 문자열의 열거 객체를 리턴

열거 배열

values()

모든 열거 객체들을 배열로 리턴


  • name()
    • 열거 객체가 가지고 있는 문자열을 리턴한다.
    • 리턴되는 문자열은 열거 타입을 정의할 때 사용한 상수 이름과 동일하다.
    • 예)
    • 
      Month thisMonth = Month.JUNE;
      String month = thisMonth.name(); // month = "JUNE"
      
      
  • ordinal()
    • ordinal() 메소드는 전체 열거 객체 중 몇 번째 열거 객체인지 알려준다.
    • 열거 객체의 순번은 열거 객체 타입을 정의할 때 주어진 순번이며 0번부터 시작한다.
    • 예)
    • 
      Month thisMonth = Month.JUNE;
      int ordinal = thisMonth.ordinal(); // ordinal = 5
      
      
  • compareTo()
    • 매개값으로 주어진 열거 객체를 기준으로 전후로 몇 번째 위치하는지를 비교한다.
    • 열거 객체가 매개값의 열거 객체보다 순번이 빠르다면 음수, 순번이 늦다면 양수가 리턴된다.
    • 예)
    • 
      Month month1 = Month.JUNE;
      Month month2 = Month.MARCH;
      int result1 = month2.compareTo(month1); // -3
      int result2 = month1.compareTo(month2); // 3
      
      
  • valueOf()
    • 매개값으로 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴한다.
    • 이 메소드는 외부로부터 문자열을 입력 받아 열거 객체로 변환할 때 유용하게 사용할 수 있다.
    • 예)
    • 
      Month thisMonth = Month.valueOf("JUNE");
      
      
  • values()
    • 열거 타입의 모든 열거 객체들을 배열로 만들어 리턴한다.
    • 예)
    • 
      Month[] months = Month.values();
      for(Month month : months) {
      	System.out.println(month); // JANUARY, FEBRUARY, … DECEMBER
      }
      
      


배열

  • 정의: 같은 타입의데이터를 메모리상에 연속적으로 나열시키고, 각 데이터에 인덱스(index)를 부여해 놓은 자료 구조
  • 인덱스: 첨자값, [](대괄호) 사용
  • 배열은 참조 타입에 속한다.

배열의 선언


/* 대괄호의 위치 차이 */
int[] intArray; // 타입[] 변수(배열명);
int intArray[]; // 타입 변수(배열명)[];

/* null로 초기화 */
String[] stringArray = null;

/* 값 목록을 가진 배열 생성 */
String[] names = { "홍길동", "전지현", "설현", "수지" };
int[] scores = { 80, 90, 95, 99 };

/* new 연산자로 바로 배열 객체 생성 */
double[] doubleArray = new double[5]; // 배열의 길이를 지정한다. (이 때 인덱스는 0~4)

/* 컴파일 에러 */
String[] names = null;
// names = { "홍길동", "전지현", "설현", "수지 }; -> 배열 변수 선언 후 중괄호를 사용한 배열 생성은 에러 발생
names = new String[] { "홍길동", "전지현", "설현", "수지 };


  • 중괄호{}는 주어진 값들을 가진 배열 객체를 힙(heap) 메모리 영역에 생성하고, 배열 객체의 번지(주소)를 리턴한다.
  • 배열 변수는 리턴된 번지를 저장하여 참조한다.
  • 위의 예에서 "홍길동"은 names[0] "전지현"은 names[1], 80은 scores[0] 이다.

배열의 값 바꾸기


String[] names = { "홍길동", "전지현", "설현", "수지" };
System.out.println("names[0] > " + names[0]); // 홍길동
names[0] = "에일리";
System.out.println("names[0] > " + names[0]); //에일리

  • 배열의 값 바꾸기는 = (대입 연산자)를 사용한다.

배열 길이

  • 배열의 길이: 배열에 저장할 수 있는 전체 항목 수를 말한다.
  • 배열 객체의 length 필드(field)를 읽어야 한다.
  • 필드(field): 객체 내부의 데이터
  • 배열의 마지막 요소의 인덱스는 배열 길이-1이다.

int[]  intArray = {100, 200, 300};
int len = intArray.length;
System.out.println("배열 intArray의 길이는 > " + len);

참조 타입(reference type)

  • 참조 타입의 종류: 배열, 열거, 클래스, 인터페이스

  • 참조 타입 변수는 스택(stack) 영역에 생성되어 힙(heap) 메모리 영역의 번지(주소)를 값으로 갖는다.

  • 주소를 통해 객체를 참조한다는 뜻에서 참조 타입이라고 부른다.

참조 변수의 ==, != 연산

  • 참조 타입 변수들 간의 ==, != 연산은 동일한 객체를 참조하는지, 다른 객체를 참조하는지 알아볼 때 사용된다.

  • 서로 다른 객체를 참조하는 참조 타입 변수들간의 비교는 기본 타입과 다른 결과를 나타낸다.

  • 예)

int x = 3;
int y = 3;
String hello = new String("안녕하세요");
String hi = new String("안녕하세요");

if ( hello == hi ) {
    System.out.println("안녕하세요.");
} else if ( hello != hi) {
    System.out.println("안녕 못해요.");
}

if (x == y) {
   System.out.println("X와 y는 같습니다.");
} else if ( x != y) {
   System.out.println("x와 y는 다릅니다.");
}
  • 출력
  • 안녕 못해요.
    x와 y는 같습니다.
    

참조 타입 변수의 null

  • 참조 타입 변수가 null 값을 가질 경우 참조할 객체가 없으므로 객체는 힙 메모리 영역에 생성되지 않고, 변수만 스택 영역에서 생성되어 null 값을 가진다.
  • 따라서, null 값을 가진 참조 타입 변수의 ==, != 연산은 기본 타입과 동일하다.
  • 참조 타입 변수가 null 값을 가진 상황에서 프로그램을 실행할 경우 (혹은 프로그램 실행 중 참조 타입 변수가 null 값을 가질 경우) NullPointerException 예외가 발생한다.

String 타입

  • 문자열을 저장하는 참조 타입

  • 자바에서 String은 문자열 리터럴이 동일할 경우 객체를 공유하도록 되어 있다. (new 연산자로 새로운 객체를 생성하지 않고 = 대입 연산자로 같은 문자열을 저장할 경우)

  • String 객체의 equals() 메소드를 사용해서 객체에 상관없이 문자열을 비교할 수 있다.

  • 형식

    String x; // 기본값은 null
    x = "hello"; // 선언한 x에 문자열 값을 대입, " " (쌍따옴표) 사용
    String  y = "hello"; // 선언과 동시에 문자열 저장 x == y 는 true
    String  z = new String("hello"); // 새로운 객체 생성, y == z 는 false
    if ( y.equals(z) ) {
       System.out.println("true"); // true 출력
    }



JVM 구조

  • 실행될 클래스 파일을 메모리에 로드 후 초기화 작업 수행

  • 메소드와 클래스변수들을 해당 메모리 영역애 배치

  • 클래스로드가 끝난 후 JVM은 main 메소드를 찾아 지역변수, 객체변수, 참조변수를 스택에 쌓음
  • 다음 라인을 진행하면서 상황에 맞는 작업 수행(함수 호출, 객체 할당 등)

자바 메모리 구조



  • Class Loader: JVM내로 클래스를 로드하고 링크를 통해 배치하는 작업을 수행하는 모듈로써 런타임시 동적으로 클래스를 로드한다.
  • Execution Engine: Class Loader를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트 코드를 실행한다. 이 때, Execution Engine은 자바 바이트 코드를 명령어 단위로 읽어서 실행한다.
  • Garbage Collector: JVM은 Garbage Collector를 통해 메모리 관리 기능을 자동으로 수행한다. 애플리케이션이 생성한 객체의 생존 여부를 판단하여 더 이상 사용되지 않는 객체를 해제하는 방식으로 메모리를 자동 관리한다.
  • Runtime Data Areas: JVM이 운영체제 위에서 실행되면서 할당받는 메모리 영역이다. Class Loader에서 준비한 데이터들을 보관하는 저장소이다.

Runtime Data Areas


Runtime Data Areas & Heap Area



  • Method (Static) Area: JVM이 읽어들인 클래스와 인터페이스 대한 런타임 상수 풀, 멤버 변수(필드), 클래스 변수(Static 변수), 생성자와 메소드를 저장하는 공간이다.
  • Runtime Constant Pool
    • 메소드 영역에 포함되지만 독자적 중요성이 있다.
    • 클래스 파일 constant_pool 테이블에 해당하는 영역이다.
    • 클래스와 인터페이스 상수, 메소드와 필드에 대한 모든 레퍼런스를 저장한다.
    • JVM은 런타임 상수 풀을 통해 해당 메소드나 필드의 실제 메모리 상 주소를 찾아 참조한다
  • 메소드 영역/런타임 상수 풀의 사용기간 및 스레드 공유 범위
    • JVM 시작시 생성
    • 프로그램 종료 시까지
    • 명시적으로 null 선언 시
    • 구성 방식이나 GC 방법은 JVM 벤더마다 다를 수 있다.
    • 모든 스레드에서 공유한다.
  • Heap Area
    • JVM이 관리하는 프로그램 상에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역이다.
    • New 연산자로 생성된 객체 또는 객체(인스턴스)와 배열을 저장한다.
    • 힙 영역에 생성된 객체와 배열은 스택 영역의 변수나 다른 객체의 필드에서 참조한다.
    • 참조하는 변수나 필드가 없다면 의미 없는 객체가 되어 GC의 대상이 된다.
    • 힙 영역의 사용기간 및 스레드 공유 범위
      • 객체가 더 이상 사용되지 않거나 명시적으로 null 선언 시
      • GC(Garbage Collection) 대상
      • 구성 방식이나 GC 방법은 JVM 벤더마다 다를 수 있다.
      • 모든 스레드에서 공유한다.
  • Stack Area
    • 각 스레드마다 하나씩 존재하며, 스레드가 시작될 때 할당된다.
    • 메소드를 호출할 때마다 프레임(Frame)을 추가(push)하고 메소드가 종료되면 해당 프레임을 제거(pop)하는 동작을 수행한다.
    • 선입후출(FILO, First In Last Out) 구조로 push와 pop 기능 사용
    • 메소드 호출 시 생성되는 스레드 수행정보를 기록하는 Frame을 저장
    • 메소드 정보, 지역변수, 매개변수, 연산 중 발생하는 임시 데이터 저장
    • 기본(원시)타입 변수는 스택 영역에 직접 값을 가진다.
    • 참조타임 변수는 힙 영역이나 메소드 영역의 객체 주소를 가진다.
  • PC Register
    • 현재 수행 중인 JVM 명령 주소를 갖는다.
    • 프로그램 실행은 CPU에서 인스트럭션(Instruction)을 수행.
    • CPU는 인스트럭션을 수행하는 동안 필요한 정보를 CPU 내 기억장치인 레지스터에 저장한다.
    • 연산 결곽값을 메모리에 전달하기 전 저장하는 CPU 내의 기억장치
  • Native Method Stack Area
    • 자바 외 언어로 작성된 네이티브 코드를 위한 Stack이다.
    • 즉, JNI(Java Native Interface)를 통해 호출되는 C/C++ 등의 코드를 수행하기 위한 스택이다.
    • 네이티브 메소드의 매개변수, 지역변수 등을 바이트 코드로 저장한다.

Heap Area

  • Young Generation: 이 영역은 자바 객체가 생성되자마자 저장되고, 생긴지 얼마 안되는 객체가 저장되는 공간이다. 시간이 지나 우선순위가 낮아지면 Old 영역으로 옮겨진다. 이 영역에서 객체가 사라질 때 Minor GC가 발생한다.
  • Old(Tenured) Generation: Young Generation 영역에서 저장되었던 객체 중에 오래된 객체가 이동되어 저장되는 영역이다. 이 영역에서 객체가 사라질 때 Major GC(Full GC)가 발생한다.

None Heap Area

  • Permanent Generation(Java 7 이전): 클래스 로더에 의해 로드되는 클래스, 메소드 등에 대한 메타 정보가 저장되는 영역으로 JVM에 의해 사용된다. 리플렉션을 사용하여 동적으로 클래스가 로딩되는 경우에 사용된다. 내부적으로 리플렉션 기능을 자주 사용하는 Spring Framework를 이용할 경우 이 영역에 대한 고려가 필요하다. 런타임시 사이즈를 조절할 수 없어 OutOfMemoryError: PermGen Space error 가 발생하는 메모리 영역이다. JVM 실행시 PermSize와 MaxPermSize 옵션을 사용한다.
  • Metaspace(Java 8 이후): Permanent Generation이 Metaspace로 변경 되었다. 기능은 비슷하지만, 주요 차이점은 동적으로 사이즈를 바꿀 수 있다. JVM 옵션도 PermGem관련하여 사라지고, Metaspace 관련하여 MetaspaceSize 및 MaxMEtaspaceSize 옵션이 새로 생겼다.

참고자료


수정이력

  • 2019-05-07: Permanent Generation None Heap Area로 재분류 및 설명 추가, Meataspace 추가

반복문

  • 어떤 작업이 반복적으로 수행되도록 할 때 사용되는 구문

반복문의 종류

  • for문: 주로 반복횟수를 알고 있을 때 사용
  • 향상된 for문: 컬렉션과 배열을 처리할 때 더 효과적인 문법으로 JDK5.0 이후에 사용 가능
  • while문: 조건식으로 반복할 때 주로 사용
  • do-while문: 반복하기 전에 먼저 실행해야 하는 구문이 있을 때 사용

for문

  • for문 선언 형식
  • for( 초기식; 조건식; 증감식) {
      // 명령코딩
    }
    
  • 예제: 1부터 10까지의 합
  • public class ExFor {
    	public static void main (String[] args)
    	{
    		int i , sum = 0;   
            for ( i=1 ; i<=10 ;  i++) {
            	System.out.printf(i==10?"%d":"%d+",i);
    			sum+= i;    
    		}
            System.out.println("="+sum);
    	}
    }
    
    1+2+3+4+5+6+7+8+9+10=55
    

향상된 for문

  • 향상된 for문 선언 형식
  • for( 자료형  변수명 : 배열 또는 컬렉션 ) {
     // 명령코딩
    }
    
  • 예제: 배열에 담긴 국어 점수 출력
  • public class ExEnhancedFor
    {
    	public static void main (String[] args)
    	{
    		int [] kors = new int[5];
    		kors[0] = 100;
    		kors[1] = 90;
    		kors[2] = 77;
    		kors[3] = 23;
    		kors[4] = 88;
    		
    		for (int kor : kors) {
    			System.out.println(kor);
    		}
    	}
    }
    
    100
    90
    77
    23
    88
    

while문

  • while문 선언 형식
  • while( 조건식 ) {
     // 조건식이 참일 동안 반복될 명령 코딩
    }
    
  • 예제: 10부터 0까지의 합
  • public class ExWhile
    {
    	public static void main (String[] args)
    	{
    		int i = 10 , sum = 0;
    		while( i >= 0 ){
    			System.out.printf(i==0?"%d":"%d+",i);
    			sum += i; 
    			i--;
    		}
    		System.out.println("="+sum); 
    	}
    }
    
    10+9+8+7+6+5+4+3+2+1+0=55
    

do-while문

  • do-while문 선언 형식
  • do{
    // 먼저 한번 실행하고, 조건식이 참일 동안 반복할 명령 코딩
    }while( 조건식 );
    
  • 예제: 증가치에 따라 증가하는 항 값의 누적 합
  • public class ExDoWhile
    {
    	public static void main (String[] args) throws java.lang.Exception
    	{
    		int h = 1; // n 항의 누적 합
    		int n = 1; // 각 항: [1]+[2]+[4]+[7]+[11]+[16]+[22]
    		int k = 0; // 증가치:   1   2   3   4    5    6
     
    		do{
    			k+=1;
    			n += k;
    			if( n > 100) break;
    			h += n; // (누적)
    			System.out.printf("%d - %d - %d\n",n,k,h);
    		}while(true);	
    	}
    }
    
    2 - 1 - 3
    4 - 2 - 7
    7 - 3 - 14
    11 - 4 - 25
    16 - 5 - 41
    22 - 6 - 63
    29 - 7 - 92
    37 - 8 - 129
    46 - 9 - 175
    56 - 10 - 231
    67 - 11 - 298
    79 - 12 - 377
    92 - 13 - 469
    

제어문(Control Flow Statement)

  • 제어문이란, 프로그램 실행 흐름을 개발자가 원하는 방향으로 바꿀 수 있도록 해주는 것이다.
  • 일반적으로 조건식과 실행 구문인 중괄호(블록, { })으로 구성되어 있다.

제어문의 종류

  • 조건문(decision-making statements): if문, switch문
  • 반복문(looping statements): for문, while문
  • 분기문(branching statements): break, continue, return

조건문-if문

  • 조건식의 결과에 따라 블록 실행여부가 결정
  • 조건식에는 boolean 변수 또는 true/false 값을 산출하는 연산식
  • 조건식이 true이면 블록 실행, false이면 실행하지 않음

if문 종류

  • if문
  • if(조건식) {
    
     // 조건식이 참일 경우 실행 구문
    
    }
    
    • 조건식의 결과에 따라 블록 실행여부가 결정
    • 조건식에는 boolean 변수 또는 true/false 값을 산출하는 연산식
    • 조건식이 true이면 블록 실행, false이면 실행하지 않음
  • if-else문
  • if(조건식) {
    
     // 조건식이 참일 경우 실행 구문
    
    }else {
    
     // if문의 조건식이 거짓일 경우 실행 구문
    
    }
    
    • if문이 false일 경우 else 문으로 넘어감
    • 조건식의 결과에 따라 이 두 개의 블록 중 한 블록만 실행 후 if문을 벗어남
  • if-else if-else문
  • if(조건식A) {
    
     // 조건식A가 참일 경우 실행 구문
    
    } else if(조건식B) {
    
    // 조건식A가 거짓이고, 조건식B가 참일 경우 실행 구문
    
    } else if(조건식C) {
    
    // 조건식A, B가 거짓이고, 조건식C가 참일 경우 실행 구문
    
    } else {
    
    // 모든 조건식이 거짓일 경우 실행 구문
    
    }
    
    • 조건문이 여러 개인 다중 if문
    • if 블록 끝에 else if문을 통해 false 일 경우 계속 다음 else if 문으로 넘어감
    • else if문의 수는 제한이 없다
    • 여러 개의 조건식 중 true가 되는 블록만 실행하고 전체 if문을 벗어남
  • 중첩 if문
  • if(조건식 A) {
         // 조건식 A가 참일 경우 실행 구문
         if(조건식 B) {
                    // 조건식 A와 B가 참일 경우 실행 구문
         }
    }
    
    • if문 블록 내에 또 다른 if문을 사용하는 것
    • 중첩의 단계는 제한이 없어 실행흐름을 잘 판단하여 작성
  • 참고: Oracle JAVA Documentation에서는 if-then (=if)문과 if-then-else(if-else문과 if-else if-else문)문 두개로 나눈다. (여기서는 if-else문 설명을 위해 좀더 자세히 분류했다.)

switch문

  • if문과 달리 변수가 어떤 값을 갖느냐에 따라 실행문이 선택
  • if문의 경우의 수가 많아질 수록 switch 문을 사용하는 것이 효과적
  • 괄호 안의 동일한 값을 갖는 case로 가서 실행문을 실행
  • 동일한 값의 case가 없으면 default로 가서 실행문 실행(생략 가능)
  • 변수 값에 따라 case가 실행된 후 제어문을 빠져나가기 위해 break; 사용

형식

switch(변수) {
	case 값1:
		실행문;
		break;
	case 값2:
		실행문;
		break;
	default: // 생략 가능
		실행문;
		break;
}

변수

  • 하나의 값을 저장할 수 있는 메모리 공간

변수의 선언

  • 타입 변수이름;
  • 타입은 변수에 저장되는 값의 종류와 범위를 결정지으므로 어떤 값을 변수에 저장할지 생각한 후 타입을 결정해야 한다.
  • 변수 이름은 메모리 주소에 붙여진 이름이다.
  • 프로그램은 변수 이름을 통해 메모리 주소에 접근하고, 그곳에 값을 저장하거나 그곳에 값을 읽는다.
  • 변수 이름은 자바 언어에서 정한 명명규칙을 사용해야 하며, 예약어는 사용할 수 없다.
  • 개발자는 변수 이름을 보고, 변수가 어떤 값을 저장하고 있는지 쉽게 알 수 있도록 의미 잇는 변수 이름을 지어주는 것이 좋다.
 int age; // 정수값을 저장할 수 있는 age 변수 선언
double value; // 실수값을 저장할 수 있는 value 변수 선언


변수 명명 규칙

작성 규칙

번째 글자는 문자이거나 $, _ 이어야 하고 숫자로 시작할 없다(필수)

가능: price, $price, _companyName

불가: 1v, @speed, $#value

영어 대소문자가 구분된다(필수)

firstname firstName 다른 변수

문자는 영어 소문자로 시작하되, 다른 단어가 붙을 경우 문자를 대문자로 한다

maxSpeed, firstName, carBodyColor

문자 (길이) 제한은 없다

 

자바 예약어는 사용할 없다(필수)

 



자바 예약어

분류

예약어

기본 데이터 타입

boolean, byte, char, short, int, long, float, double

접근 지정자

private, protected, public

클래스와 관련된

class, abstract, interface, extends, implements, enum

객체와 관련된

new, instanceof, this, super, null

메소드와 관련된

void, return

제어문과 관련된

if, else, switch, case, default, for, do, while, break, continue

논리값

true, false

예외 처리와 관련된

try, catch, finally, throw, throws

기타

transient, volatile, package, import, synchronized, native, final, static, strictfp, assert



변수의 범위

  • 변수는 선언된 { } (블록) 안에서만 사용 가능하다.
  • 클래스 블록안에 바로 선언된 변수를 전역 변수라고 한다.
  • 메소드(함수) 내에 선언된 변수를 지역 변수(local variable)
  • 변수는 선언된 블록 안에서만 사용 할 수 있다.
  • 엄밀히 말하면, 전역 변수는 인스턴스 변수와 클래스 변수로 구분할 수 있는데, 이는 추후 클래스를 다룰 때 더 자세히 설명하겠다.

비트 연산자

  • 0과 1로 표현이 가능한 정수타입만 비트 연산이 가능하다.

  • 피연산자를 int 형으로 자동 형변환

비트 반전 연산자(~), 틸드 연산자

  • 정수 타입의 피연산자에만 사용
  • 피연연사즐 2진수로 표현했을 때 비트값인 0을 1로, 1은 0으로 반전
  • 부호가 반대인 새로운 값이 산출됨

비트 논리 연산자(&, |, ^)

  • 비트논리 AND 연산자(&): 두 비트 모두 1일 경우에만 연산 결과가 1
  • 비트논리 OR 연산자(|): 두 비트 중 하나라도 1이면 연산 결과 1
  • 비트논리 XOR 연산자(^): 두 비트 중 하나는 1이고 다른 하나가 0일 경우 연산 결과는 1

비트 이동 연산자(<<, >>, >>>), 쉬프트 연산자

  • a << b: 정수 a의 각 비트를 b만큼 왼쪽으로 이동(빈자리는 0으로 채워짐)
  • a >> b: 정수 a의 각 비트를 b만큼 오른쪽으로 이동(빈자리는 정수 a의 최상위 부호 비트와 같은 값으로 채워짐)
  • a>>>b: 정수 a의 각 비트를 b만큼 오른쪽으로 이동(빈자리는 0으로 채워짐)




+ Recent posts