Java 1일차

진창호·2023년 1월 16일

Java

목록 보기
1/9

Java는 기본적으로 8개의 기본 자료형을 지원한다.

논리형 : boolean(1) / 정수형 : byte(1), short(2), int(4), long(8) / 실수형 : float(4), double(8) /
문자형 : char(2)


Java는 Integer Overflow 발생 시 오류를 띄우지 않는다.

아래 코드를 살펴보자.

public static void main(String[] args) {
        int i1 = Integer.MAX_VALUE;

        int i2 = i1 + 1;

        System.out.println(i2);
    }

위 코드에서 i2는 int가 표현할 수 있는 최저값이 된다.


Java의 실수 표현은 정확하지 않을 수 있다.

public static void main(String[] args) {
        float f1 = 2.0f;
        float f2 = 1.1f;
        float f3 = f1 - f2;
        System.out.println(f3);

        double d1 = 2.0;
        double d2 = 1.1;
        double d3 = d1 - d2;
        System.out.println(d3);

        System.out.println(((int) (d1 * 100) - (int) (d2 * 100)) / 100.0);

        BigDecimal b1 = new BigDecimal("2.0");
        BigDecimal b2 = new BigDecimal("1.1");
        System.out.println("BigDecimal을 이용한 빼기 : " + b1.subtract(b2));
    }

f3는 0.9, d3는 0.8999999999999999가 된다.
이는 실수는 정확히 표현되지 않을 수 있다는 점을 환기시킨다. (실무에서 문제가 되는 경우 있음)
따라서 실수 연산시 정수로 변환하거나, 적절한 객체를 사용하는 게 좋다.


Java는 묵시적 형변환, 명시적 형변환이 존재한다.

byte -> short, char -> int -> long -> float -> double

화살표 순으로 변환이 일어나면 묵시적 형변환, 역방향으로 변환이 일어나면 명시적 형변환이라고 한다.
묵시적 형변환은 컴파일러가 자동으로 수행해주지만, 명시적 형변환은 컴파일 에러가 발생한다.
따라서 명시적 형변환은 개발자가 형변환을 명시해야 한다.

※ short, char는 표현 범위가 다르기 때문에 서로 호환되지 않는다.
또한, long이 bit 수가 더 많지만, float가 표현 범위가 더 크기 때문에 long -> float로 호환이 이뤄진다.


Java 연산은 주의가 필요하다.

public static void main(String[] args) {
        byte b1 = 10;
        byte b2 = 20;
        // TODO: 다음에서 발생하는 오류를 읽고 원인을 말한 후 수정하시오.
         byte b3 = b1 + b2;

        int i1 = 10;
        long l1 = 20;
        // TODO: 다음에서 발생하는 오류를 읽고 원인을 말한 후 수정하시오.
         int i2 = i1 + l1; 

        // TODO: 다음에서 발생하는 오류를 읽고 원인을 말한 후 수정하시오.
         float f1 = 10.0; 
         float f2 = f1 + 20.0;
    }

1번 코드의 문제점 : 정수 간 연산은 int 형이다. 따라서 명시적 형변환이 필요하다.
2번 코드의 문제점 : int와 long의 계산은 long 형이다. 따라서 명시적 형변환이 필요하다.
3번 코드의 문제점 : 10.0은 double 형이다. 따라서 명시적 형변환이 필요하다.


Java 연산자의 우선순위는 아래와 같다.

단항 > 산술 > 시프트 > 관계 > 비트 > 논리 > 삼항 > 대입


Java 배열 특징은 아래와 같다.

  1. 값이 아닌 주소가 저장되는 레퍼런스 타입이다.
  2. int[] a = new int[5]; 방식으로 생성한다.
  3. 생성됨과 동시에 자료형의 기본값으로 초기화가 진행된다.
  4. index 번호는 0부터 시작한다.
  5. arr.length 로 배열의 크기를 알 수 있다.
  6. 초기화는 int[] b = new int[] {1, 3, 5, 6, 8}; 또는 int[] b = {1, 3, 5, 6, 8}; 방식으로 생성과 초기화를 동시에 진행한다.
  7. 생성과 초기화가 따로 진행되면 int[] b; b = new int[] {1, 3, 5, 6, 8}; 방식을 준수한다.
  8. 배열은 메모리가 할당되면 길이를 변경할 수 없다.

Java의 2차원 배열 생성방법

  1. int arr[][] = new int [4][3];
  2. int[] arr[] = new int [4][3];
  3. int[][] arr = new int [4][3];

Java의 2차원 배열 초기화방법

  1. int[][] arr = new int [][] {{1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}};
  2. int[][] arr = {{1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}};

Java는 사각형이 아닌 2차원 배열 또한 지원한다. 아래는 4 * ? 배열의 생성을 보여준다.

int[][] arr = new int[4][];
arr[0] = new int[3];
arr[1] = new int[2];


Java는 두 가지 인수 전달 방법이 존재한다고 여겨진다.

아래 코드는 Call By Value 방식이다.

static void m1(int a) {
		System.out.print(a + " ");
		a = 20;
		System.out.print(a + " ");
	}
	
	public static void main(String[] args) {
		int a = 10;
		System.out.print(a + " ");
		m1(a);
		System.out.print(a);
	}

위 코드의 출력값은 10 10 20 10 이다.
즉, 함수로 전달된 인자값이 함수 내부 연산에 의해 변하지 않았다.

아래 코드는 Call By Reference 방식이다.

static void m2(int[] arr) {
		System.out.print(arr[1] + " ");
		arr[1] = 5;
		System.out.print(arr[1] + " ");
	}
	
	public static void main(String[] args) {
		int[] arr = {1, 2, 3};
		System.out.print(arr[1] + " ");
		m2(arr);
		System.out.print(arr[1] + " ");
	}

위 코드의 출력값은 2 2 5 5 이다.
즉, 함수로 전달된 인자값이 함수 내부 연산에 의해 변했다.

사실 Call By Reference 또한 주소값을 인자로 넘기는 것이므로 Call By Value 이다.
따라서 정확히 따져보면 Java는 Call By Value만 지원한다.


Java에서 배열의 복사는 2가지가 있다.

아래 코드는 얕은 복사이다.

public static void main(String[] args) {
		int[] brr = {1, 2, 3, 4, 5};
		int[] crr = {6, 7, 8, 9, 10};
		
		crr = brr;
	}

이때, brr과 crr은 같은 주소를 가리킨다.

아래 코드는 깊은 복사이다.

public static void main(String[] args) {
		int[] brr = {1, 2, 3, 4, 5};
		int[] crr = {6, 7, 8, 9, 10};
        
        for (int i = 0; i < brr.length; i++) {
			brr[i] = crr[i];
		}

이때, brr과 crr은 다른 주소를 가리킨다.


Java에서 배열의 상, 하, 좌, 우 효율적인 탐색을 알아보자.

단순히 생각해보면 아래 코드와 같다.

public static void main(String[] args) {
		int x = 2;
		int y = 2;
		int sum_value = 0;
		
		sum_value = sum_value + arr[y + -1][x + 0]; // 상
		sum_value = sum_value + arr[y + 1][x + 0]; // 하
		sum_value = sum_value + arr[y + 0][x + -1]; // 좌
		sum_value = sum_value + arr[y + 0][x + 1]; // 우
		
		System.out.println(sum_value);

여기서 sum_value = sum_value + arr[y + ?][x + ?]; 라는 규칙을 발견할 수 있다.
?는 규칙성이 없기 때문에 이 값들을 배열에 넣어 반복문을 돌리는 방법을 채택해보자.

		sum_value = 0;

		int[] dx = {0, 0, -1, 1};
		int[] dy = {-1, 1, 0, 0};
		
		for (int i = 0; i < 4; i++) {
			sum_value = sum_value + arr[y + dy[i]][x + dx[i]];
		}
        
		System.out.println(sum_value);

이제 예외사항을 생각해보자.
x, y가 배열의 범위를 넘어갈 가능성이 있으므로 if문을 활용해보자.

		sum_value = 0;
		
		int ny = 0;
		int nx = 0;
		
		for (int i = 0; i < 4; i++) {
			ny = y + dy[i]; // 메모리 4byte 사용해 if문 연산을 줄일 수 있다.
			nx = x + dx[i];
			
			if (0 <= ny && ny <= 4 && 0 <= nx && nx <= 4) {
				sum_value = sum_value + arr[y + dy[i]][x + dx[i]];
			}
		}
        
		System.out.println(sum_value);

반대 경우로 생각해 볼 수도 있다.

		sum_value = 0;
		
		for (int i = 0; i < 4; i++) {
			ny = y + dy[i];
			nx = x + dx[i];
			
			if (ny < 0 || ny > 4 && nx < 0 && nx > 4) {
				continue;
			}
			sum_value = sum_value + arr[y + dy[i]][x + dx[i]];
		}
		System.out.println(sum_value)

이렇게 효율적인 상, 하, 좌, 우 탐색에 대해 알아봤다.
하지만.. 가장 효율적인 코드는 따로 있다!!

		sum_value = 0;
		
		if (y-1 >= 0) {
			sum_value = sum_value + arr[y-1][x];
		}
		
		if (y+1 < 5) {
			sum_value = sum_value + arr[y+1][x];
		}
		
		if (x-1 >= 0) {
			sum_value = sum_value + arr[y][x-1];
		}
		
		if (x+1 < 5) {
			sum_value = sum_value + arr[y][x+1];
		}
		System.out.println(sum_value);

위 코드가 사실 제일 효율적이다. 그러나 이 방법으로는 풀 수 없는 문제가 많을 것이다.

profile
백엔드 개발자

1개의 댓글