본문으로 바로가기

기본형 변수와 참조형 변수

 

JS에서도 primitive type이 있고 reference type이 있듯, java에도 값 자체를 저장하는 primitive type과 메모리 주소를 참고하는 reference type이 있다.

 

  • primitive type에는 boolean, char, byte, short, int, long, float, double (8개)
  • reference type에는 array, enum, class, interface, String이 있다. 예를 들어 클래스를 통해 생성한 인스턴스는 자체의 값을 저장하지 않고 그 값이 존재하는 메모리의 주소값을 저장한다. 따라서 GC의 관리 대상이다.
  • 돈계산에는 BigInteger, 정확한 소수점계산에는 BigDecimal을 사용해야 한다.

구체적으로 어떻게 작동하는 지를 보자면,

 

  • Primitive type은 실제 값을 저장하는 공간으로 스택(Stack)메모리에 저장된다.
  • reference type은 스택 영역에는 변수의 이름만 저장하고 객체의 주소를 힙영역에 저장하여 포인터로 가리키는 것을 볼 수 있다.
  • 스택 메모리에는 GC가 도는데 힙에서는 안 돈다.

 

구체적인 예시를 보자

String name = "Echo" 
char ch = 'A';
int num2 = 32.5
int num1 = 10

 

여기서 포인터인데 & 어디갔냐는 질문이 있을 수 있겠다. C나 Go에서는 포인터를 * &를 사용해서 명시적으로 사용하였는데, JAVA에서는 포인터를 명시적으로 사용하지 않는다.  JVM이 참조값을 이용해서 알아서 처리 하기 때문에 레퍼런스 & 를 사용할 필요가 없다.

 

 

 

데이터 타입

 

primitive type에 할당되는 크기도 기억해둬야 하는데, 쉽다.

boolean : 참/거짓만 존재하므로 1byte
char : 유니코드(2byte) 체계를 이용하므로 2byte
byte는 당연히 1byte
int는 4byte인데 이보다 작은 short는 2byte, 큰 long은 8byte.
float은 4byte고 double은 이의 2배인 8byte

 

  • int는 10자리 수, 대략 20억까지의 값을 다룰 수 있다. 따라서 7~9 자리 정도 되는 수를 다루면 long을 사용하자.
  • int는 하드웨어가 가장 효율적으로 처리 하는 정수형태의 크기로 설정되고 CPU 연산 처리에 더 효율적이며 정수형 연산을 진행 할때 모든 피연산자를 int형으로 변환하는 과정을 거친다고 한다. 결론은 영상처리, 컴퓨터 그래픽스와 같이 메모리에 그렇게 신경써도 되지 않는 프로그램에서는 int를 쓰고, 메모리를 신경써야 하는 프로그램에서만 short를 사용하면 된다.
  • float는 소수점 이하 6자리의 정밀도를 갖고 있고, double은 소수점 이하 15자리의 정밀도를 갖고 있다. 하지만 double형도 15자리 이하로는 오차가 있을 수 밖에 없기 때문에, 소수점을 연산할 때에는 오차가 발생할 수 있음을 항상 인지해야 한다.
  • 제대로 소수점을 다루고 연산하기 위해서는 BigDecimal을 사용하자. 
  • C언어에선 char가 1byte 인데, 자바에서는 2byte단위로 사용된다

 

정 기억이 안나면 다음과 같이 출력해볼 수도 있다.

public class Demo {
   public static void main(String[] args) {
      System.out.println("Integer Datatype values...");
      System.out.println("Min = " + Integer.MIN_VALUE);
      System.out.println("Max = " + Integer.MAX_VALUE);

      System.out.println("Float Datatype values...");
      System.out.println("Min = " + Float.MIN_VALUE);
      System.out.println("Max = " + Float.MAX_VALUE);

      System.out.println("Double Datatype values...");
      System.out.println("Min = " + Double.MIN_VALUE);
      System.out.println("Max = " + Double.MAX_VALUE);

      System.out.println("Byte Datatype values...");
      System.out.println("Min = " + Byte.MIN_VALUE);
      System.out.println("Max = " + Byte.MAX_VALUE);

      System.out.println("Short Datatype values...");
      System.out.println("Min = " + Short.MIN_VALUE);
      System.out.println("Max = " + Short.MAX_VALUE);
   }
}

 

 

변수 선언 및 초기화

int i = 3;
System.out.println(i);

 

큰 자료형은 작은 자료형을 포함할 수 있습니다. 그러나 작은 자료형은 큰 자료형을 담을 수 없습니다.

public class javaVar {
    public static void main(String[] args) {
        int i = 'A'; // 문자 A의 유니코드인 65가 들어감
        long l = 123; // int는 long보다 작기 때문에 가능
        double d = 1.34f; // float은 double보다 작기 때문에 가능
        
        float f = 3.14; // 아무 리터럴도 없으면 double이므로 float을 초과함. 컴파일 에러
    }
}

 

char와 String에서 주의하셔야 할 점이 char는 작은 따옴표 '', String은 큰 따옴표를 써야 한다는 것입니다. 이를 구분합니다. 

public class javaVar {
    public static void main(String[] args) {
        char a = 'a';
        String b = "this is B";
        System.out.println(a);
        System.out.println(b);
    }
}

 

 

상수(final)

 

final은 변수, 메서드, 클래스에 붙일 수 있습니다.

 

final 변수

변수의 타입 앞에 'final'을 붙여주면 상수가 됩니다. 선언함과 동시에 초기화해주는 것이 좋습니다.

jdk1.6 부터 상수 사용 전에만 초기화하면 된다고 바뀌기는 했지만 가독성과 코딩 컨벤션을 위해 선언과 동시에 초기화해줍시다.

public class javaVar {
    public static void main(String[] args) {
        final int MAX_SPEED = 10;
        System.out.println(MAX_SPEED);
    }
}

 

final 메서드

단순 변수 뿐만 아니라 메서드에도 final을 붙일 수 있습니다.

이 경우에는 overriding을 할 수 없습니다.

// final 클래스
public final class FinalClass {

	// final 변수
    public final int a = 1;
    public final String b = "hello";

	// final 메서드
    public final void finalMethod() {
        System.out.println("this is final");
    }
}

 

 

final 클래스

final이 붙은 클래스는 상속할 수 없습니다. 즉, 부모 클래스가 될 수 없습니다.

final로 선언한 class를 이용하여 상속하려고 하니, 불가능하다고 나옵니다.

 

 

 

리터럴(l, f, d, e, 0, 0x, 0b)

 

리터럴은 무엇이냐? 그냥 값이다. 실제로 저장되는 값, 컴파일 타임에 값이 정해지는 것, 그 자체로 메모리에 저장되어 변하지 않는 값 등 정의 하려고 하면 오히려 더 복잡해진다. 

public class javaVar {
    public static void main(String[] args) {
        final int MAX_SPEED = 10; // 10이 리터럴
        int year = 2020; // 2020이 리터럴
    }
}

 

그런데 단순히 값임에도 타입에 따라 리터럴을 다른 방식으로 정의해야할 때가 있다. 리터럴 중 접미사가 붙는 건 long, float, double 뿐이다. int, short, byte는 생략 가능하며 double도 생략 가능하다. (실수는 기본형이 double이기 때문) 그러니 long, float, double에 리터럴을 넣을 때 주의하자.

public class javaVar {
    public static void main(String[] args) {
        long big = 100_000_000_000_000L;  // long은 l혹은 =L, _ 구분은 jdk 1.7부터 추가되었다
        float pi = 3.14f; // float은 f 혹은 F
        double rate = 1.6143D; // double은 d 혹은 D
        
        
        // 만약 float pi = 3.14 처럼 리터럴 접미사를 붙이지 않는다면 컴파일 에러가 발생함
    }
}

 

또한, 리터럴에 e는 10의 n승수를 의미한다.

public class javaVar {
    public static void main(String[] args) {
        double a = 1e2; // 1 * 10^2
        System.out.println(a);
    }
}

 

진수 표현은 0, 0x, 0b로 가능하다.

public class javaVar {
    public static void main(String[] args) {
       int octNum = 010; // 앞에 0이 붙으면 8진수. 10(8) 이므로 값은 8
       int hexNum = 0x10; // 0x가 붙으면 16진수 10(16) 이므로 값은 16
       int binNum = 0b10; // ob가 붙으면 2진수 10(2) 이므로 값은 2

        System.out.println(octNum);
        System.out.println(hexNum);
        System.out.println(binNum);
    }
}

 

 

문자 리터럴

 

java에서는 문자 ''와 문자열 ""을 구분한다.

public class javaVar {
    public static void main(String[] args) {
        char ch = 'A';
        String name = "java";
        
        String empty = ""; // 빈문자열 가능
        char emp = ' '; // 빈문자 불가능. 단, 공백도 문자이므로 공백은 가능
    }
}

 

 

String 선언 방식에 대하여

 

리터럴 방식

  • a == b 다.
  • String을 선언할 때 리터럴 방식은 선호되지 않는다. String.valueOf, StringBuilder, StringBuffer가 좋다.
String a = “hello”;
String b = “hello”;

 

new 연산자 할당 방식

  • a != b다. 왜? 메모리 주소가 다르게 할당되었으니까.
  • 단순히 스트링이 같은지 확인하려면 equals 메서드 사용할 것.
String c = new String(“hello”);
String d = new String(“hello”);

boolean bool = c == d; // false
boolean eqlBool = c.equals(d) // true

 

StringBuilder나 StringBuffer를 사용하는 사례가 많다.

public class StringExample {
	public static void main(String[] args) {
		StringBuilder st = new StringBuilder("hello");
		System.out.println(st.toString());
		
		StringBuffer sb = new StringBuffer("hello");
		System.out.println(sb);
	}
}

 

String.valueOf를 사용하게 된다.

String employeeGrade = String.valueOf('S');

 

 

출력 지시자 (%b, %d, %o, %x, %f, %c, %s)

 

여기서 주의할 것은, println이 아니라 printf를 사용해야 한다는 것입니다. intellj 단축어로는 souf 입니다.

public class javaVar {
    public static void main(String[] args) {
        boolean b = true;
        System.out.printf("%b\n", b); // %b 불린값

        int i = 10;
        System.out.printf("%d, %o, %x\n", i, i, i); // decimal, octal, hexa 진법으로 출력
        System.out.printf("%5d\n", i); // 5자리 빈문자 pad (좌측) "   10"
        System.out.printf("%-5d\n", i); // 5자리 빈문자 pad (우측) "10   "
        System.out.printf("%05d\n", i); // 5자리 0 pad "00010"

        float f = 1533.141592f;
        System.out.printf("%f\n", f); %f는 소수점 6자리 까지만 출력 => 7자리에서 반올림한 값을 출력
        System.out.printf("%.2f\n", f); // 소수점 2자리 까지만

        char ch = 'a';
        System.out.printf("%c\n", ch);

        String str = "Hello";
        System.out.printf("%s\n", str);
    }
}

 

여기서, 진법을 바꾸는 %o, %x 에서는 #를 붙여 접두사를 붙일 수 있습니다.

public class javaVar {
    public static void main(String[] args) {
        int i = 1000000;
        System.out.printf("%x \n", i); // f4240 
        System.out.printf("%#x \n", i); // # 하나 붙이면 진수 접두사를 붙여줌 0xf4240 
        System.out.printf("%X \n", i); // 알파벳 대문자 F4240 
        System.out.printf("%#X \n", i); // 접두사도 대문자 0XF4240
    }
}

 

2진수는 지시자가 없습니다. Interger 객체 중 2진 문자열로 변환하여 출력해야 합니다.

public class javaVar {
    public static void main(String[] args) {
        int i = 100;
        System.out.printf("binary : %s", Integer.toBinaryString(i));
    }
}

 

 

캐스팅(타입 변환)

 

boolean을 제외한 primitive type들은 서로 형변환이 가능하다

primitive type과 reference type과의 형변환은 불가능하다.

큰 타입에서 작은 타입으로 변환하는 경우 손실이 일어나 잘못된 값을 반환하는 경우가 있다. (int => byte)

 

 

1) 묵시적 타입 형변환 ( 자동 형변환 ) => 이럴 일을 만들지 말자

// 자동 형 변환이 일어나는 위계
byte(1) < short(2) < int(4) < long(8) < float(4) < double(8)
int i = itsmemario;

작은 메모리 크기(byte)의 데이터타입 에서 큰 메모리 크기(int)의 데이터타입 에 값을 저장하면, 자동으로 형변환이 일어난다.

 

 

2) 명시적 타입 형변환 ( 강제 형변환 ) => "Casting"

 

'(타입)값' 꼴로 타입 캐스팅 가능

 

실수 => 정수

public class javaVar {
    public static void main(String[] args) {
        double d = 34.3;
        float f = 1.34f;
        int score = (int)d;
        int score2 = (int)f;

        System.out.println(score); // 34 (int이므로 소수점 아래 버림됨)
        System.out.println(score2); // 1 (int이므로 소수점 아래 버림됨)
        System.out.println(d); // 34.3
    }
}

 

char/int 간의 유니코드

public class casting {
	public static void main(String[] args) {
		char cha = 'h';
		System.out.println(cha);
		int uni = (int) cha;
		System.out.println(uni); // 유니코드
		
		int k = 123;
		char cha2 = (char) k;
		System.out.println(cha2);
	}
}

 

3) 타입 프로모션

 

python에서 정수 + 실수 = 실수 꼴을 보았을텐데 그것이다.

서로 다른 데이터 타입의 연산이 있으면 피연산자 중 크기가 큰 타입으로 자동 형변환(Promotion)된 후 연산이 수행 된다.

int i = 10;
double d = 5.5;
double output = i + d; // 더 큰 타입인 double 타입으로 형변환이 된다.

 

 

1차 및 2차 배열 선언하기

 

자바에서 배열의 선언은 두 가지 방법이 있음.

변수 선언에서 데이터타입 뒤에 []를 입력하는 방법과 변수 명뒤에 []를 입력하는 방법.

int[] array;
int array[]; 

 

아무래도 저는 typescript를 주로 쓰다보니 int[] 꼴이 더 편합니다.

public class javaVar {
    public static void main(String[] args) {
        int[] a = {1,3,5,7,9};
        int[][] b = {{2, 5, 3}, {4, 4, 1}, {1, 7, 3}, {3, 4, 5}};
        /*
        * {2, 5, 3}
        * {4, 4, 1}
        * {1, 7, 3}
        * {3, 4, 5}
        * */
        System.out.println(a[0]); // 1
        System.out.println(b[1][2]); // 1
    }
}

 

 

자바에서 배열은 실제로 객체이기 때문에 Heap 영역에 생성되며 크기가 고정된다. 런타임 스택에서힙영역에 있는 배열의 주소를 가리 킨다.

 

좌) 1차원 / 우) 2차원

 

 

  • 1차원 배열
    • oneDimensionArrayEx1 은 Runtime Stack 영역의 힙 영역 주소값을 가짐
    • Heap 영역에 int 타입 크기의 요소 5개를 할당하여 사용됨
  • 2차원 배열
    • Runtime Stack 영역의 twoDimensionArrayEx1 은 2개의 요소 크기(2개 요소에 주소값을 가지고 있음)를 가진 힙 영역 주소값을 가짐
    • 힙 영역에는 실제 값이 들어있는 요소들과 주소값이 들어있는 요소들로 존재하게됨

출처 : https://www.notion.so/2-38b5d67c7f5a48238529bb8f1617ea0d

 

타입 추론, var

 

var : 자바 10에 추가된 기능.

되는 것과 안되는 것에 대한 짧고 직관적인 예시 : https://johngrib.github.io/wiki/java10-var/

var을 실제 타입으로 치환하는 것은 컴파일 타임 (출처 : http://openjdk.java.net/projects/amber/LVTIFAQ.html)

 

public class javaVar {
    public static void main(String[] args) {
        var num = 1235;
        System.out.println(num);
    }
}

 

 

변수의 스코프와 라이프타임

 

js에서 const/let을 쓰면서 block 범위의 스코프를 가진다고 배웠는데 그 개념이다.

 

scope { } 내 지역 변수, 밖에 존재하는 전역 변수. 당연히 지역 변수가 우선순위

단, scope 를 벗어나면 지역 변수는 존재하지 않음.

 

따라서, 라이프 타임은 다음과 같다.

  • 지역변수 : 블록 영역내에서만 유지
  • 클래스변수 : 클래스 로더에의해 클래스가 초기화되고 프로그램 종료까지 유지
  • 인스턴스변수 : 인스턴스가 생성되고 메모리에서 살아있을 때 까지 유지

 

 

tip) 변수 스와핑

public class javaVar {
    public static void main(String[] args) {
        int x = 10;
        int y = 20;

        // swaping 아.. 이거 python이면 a, b = b, a 하면 끝나는 건데

        int temp;
        temp = x;
        x = y;
        y = temp;

        System.out.println(x);
        System.out.println(y);
    }
}

 

 

vo (variable object)

 

js에서 object를 만들어낸것처럼 사용하시면 됩니다. 간단.

public class Goods01 {
	public String gdsNo;
	public String gdsName;
	public int gdsPrice;
	public int gdsQuantity;
}
public class varibales {
	public static void main(String[] args) {
		
		Goods01 gdsvo1 = new Goods01();
		
		gdsvo1.gdsNo = "gds001";
		gdsvo1.gdsName = "water";
		gdsvo1.gdsPrice = 500;
		gdsvo1.gdsQuantity = 1;
		
		System.out.println(gdsvo1.gdsNo);
	}
}

 

 

참고한 글)

www.notion.so/2-00ffb2aeb41d450aa446675b8a9e91d5

github.com/yeo311/java-study-with-whiteship/tree/main/week2

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체