(커피만큼 썼던 Java의 첫 맛)
Java
자바는 1996년 1월 제임스 고슬링에 의해 탄생한 객체지향 프로그래밍 언어다. 현재까지 많은 개발자들에게 사랑 받는 언어이며, 앞으로 내가 마스터 할 언어다.
JVM(Java Virtual Machine)
자바는 컴파일럴러를 통해 기계어로 변환되는 언어다. 컴파일 = 특정 프로그래밍 언어를 기계가 이해할 수 있게 옮기는 번역 과정.
컴파일러 = 자바 언어를 JVM이 이해할 수 있는 바이트코드(Bytecode)로 변환
JVM = 자바 코드로 작성한 걸 해석해 실행하는 별도의 프로그램. (프로그램을 위한 프로그램)
JDK (Java Development Kit)
JRE = JVM + 표준 클래스라이브러리 (자바 실행만)
JDK = JRE + 개발 툴 (자바 실행 및 프로그램 개발)
JDK는 OracleJDK, OpenJDK 2가지가 있는데, 전자는 오라클 회사가 관리하고 후자는 오픈소스다.
클래스 (Class)
= 객체를 찍어낼 수 있는 틀.
메서드 (Method)
= 클래스 내부에 정의된 함수.
= 특정 작업을 하기 위한 명령문의 집합.
자바가 실행되면 main() 메서드를 제일 먼저 찾아 그 안의 것들을 순차적으로 실행하기 때문에 다른 메서드를 만들고 싶다면 main() 메서드 안에서 호출해야 한다.
접근 제어자 (Access Modifier)
= 클래스, 변수, 메서드의 접근 권한 정의 키워드.
public: 모든 접근 허용
protected: 같은 패키지(폴더) 내 객체와 상속 관계의 객체들만 허용
default: 같은 패키지(폴더) 내 객체들만 허용
private: 같은 클래스 내에서만 허용
일반적으로 많이 쓰이는 건 public, private이다.
Static
변수나 메서드가 객체의 것이 아니라, 클래스 내용물이라는 것을 지정하는 키워드.
return, void
자바 메서드는 리턴할 값을 타입을 명시해줘야 한다.
return = 메서드가 작업 마치고 데이터를 반환하는 것.
void = 데이터를 리턴하지 않을 경우, 특정 타입을 리턴할 경우 따로 명시.
타입
타입은 데이터의 종류와 크기를 결정 짓는 요소다. 자바에는 2가지 데이터 타입이 있다.
이제부터 각 타입을 하나하나 살펴보자.
정수 타입에는 byte, short, int, long 총 4가지 타입이 있다. 예전에는 메모리가 한정적이어서 필요에 따라 자료 범위를 바꿔야 했기 때문이다. 현재는 제한이 거의 없어져 int형을 주로 쓰고, 이것을 정수형의 기본형이라고 한다.
ex. 자바에 byte byteNumber = 60;
을 넣으면 숫자가 byte 범위 내의 값이기 때문에 괜찮으나, long longNumber = 12345678910;
을 넣으면 정수 범위를 벗어나서 오류가 난다. (12345678910이라는 숫자 자체는 Long형 범위이내지만, 뒤에 L
을 붙여주지 않으면 컴퓨터가 int형이라고 인식해 에러를 낸다.)
자료형의 범위를 넘을 경우 오버플로우/언더플로우가 발생해서 필요에 따라 long형을 쓴다.
이미지출처
오버플로우
= 자료형이 표현 가능한 범위 이상의 값을 표현한 경우 발생하는 현상.
(byte형의 최대값인 127에 +1을 줄 경우, 128이 아닌 -128이 된다.)
언더플로우
= 자료형이 표현 가능한 범위 이하의 값을 표현한 경우 발생하는 현상.
(byte형의 최소값인 -128에 -1을 줄 경우, -129이 아닌 127이 된다.)
이 개념이 낯설어서 이해가 잘 안 되었지만, 일반적으로 생각하는 숫자 개념이 아니라 최소~최대값을 그림의 모양처럼 동그랗게 이어서 생각하면 어느정도 이해가 된다.
실수는 소수점을 가지는 값이다. 2가지 형이 있는데,
float num1 = 3.95f;
double num2 = 3.0182d;
double num2 = 3.0182;
double형이 실수 타입의 디폴트값이기 때문에, 뒤에 d 붙이기를 생략할 수 있다.
2가지 형의 가장 큰 차이점은 정밀도에 있다. 컴퓨터가 실수를 저장할 때는 부동소수점 표현 방식으로 저장한다. 이 방식은 효율적이지만 약간의 오차를 가진다. 정밀도란, 실수를 정밀하게 표현하는 정도다. double형이 float형보다 정밀도가 높다. 따라서 더 큰 실수를, 더 정확하게 저장할 수 있다.
논리 타입은 boolean형 한 종류만 있다.
boolean은 참 또는 거짓을 저장할 수 있는 데이터 타입이다. 때문에 값은 true와 false가 있다. 이것을 0과 1로 저장하는데, JVM이 다룰 수 있는 데이터의 최소 단위가 1byte기 때문에 1byte의 크기를 차지한다.
문자 타입 변수는 하나의 문자만 저장할 수 있다. (여러 문자를 저장하려면 문자열 String을 써야 한다.) 문자란 apple의 'a'와 같이, 글자 한 자를 의미한다.
자바는 유니코드에 기반해 문자를 표현하기 때문에, 2byte 크기인 자바의 문자에 하나의 유니코드를 저장한다. 때문에 문자를 변수에 대입하면 문자 그대로 저장되는 게 아니라, 그에 해당하는 유니코드값(정수값)이 저장된다.
이 부분 역시 코딩이 처음인 나로서는 이해하기 힘든 부분이었는데, 위의 코드를 직접 입력하고 실행시켜보며 이해할 수 있었다.
자바는 문자열을 클래스를 통해 다룬다. 클래스는 그 자체로 타입이 될 수 있고, 다른 기능과 묶어서도 쓸 수 있다. 때문에 String 클래스는 문자열 타입으로 쓰이고, 문자열 관련 메소드를 다양하게 쓸 수 있다.
💡 String 선언
(문자열은 변수에 값을 저장하는 방식이 아니라, 문자열이 담긴 주소를 참조하는 방식인 참조 자료형으로 작동한다는 점을 알고 들어갈 필요가 있다.)
String 타입 선언은 2가지 방식이 있다.
1️⃣ 리터럴 대입 방식
String 변수; 변수 = "문자열";
String 변수 = "문자열";
2️⃣ new 연산자로 객체 생성 후 문자열 대입 방식
String 변수 = new String("문자열");
👉 두 방식의 차이?
리터럴로 문자열을 대입한 변수끼리는 둘 다 리터럴로 생성된 객체를 참조하기 때문에 비교 연산자로 비교했을 때 true가 나온다. 그러나 리터럴로 문자열을 대입한 변수와 new 연산자로 변수3이라는 인스턴스를 생성한 경우는 서로 다른 주소값을 가지고 있어 비교 연산자 비교시 false가 나온다.
이 부분 역시 잘 이해가 가지 않아 조사를 해봤더니 이런 이미지를 찾았다. 한 마디로 리터럴로 선언한 값들은 자바의 문자열 리터럴 저장 공간인 String Pool에 저장되어, String s1, s2, s3....등 여러 번 리터럴 대입을 해도 같은 리터럴 값이면, String Pool에 한 번 저장된 값을 동일하게 가르켜 주소값이 같다는 의미다.
반면 new 연산자로 String에 대해 s3라는 새로운 인스턴스를 만들면, 같은 문자열 같아도 Java Heap에 새로운 값으로 저장되어 호출시 위와 다른 주소값을 가진다.
비교 연산자로 비교시 true가 나오게 하려면, new 연산자 사용후 .intern()
이라는 메소드를 사용해야 한다고 한다. 이 메소드는 해당 문자열을 String Pool에서 찾아보고, 없으면 참조값을 가져오거나 새로 입력하여 해당 주소값을 가져온다. 안 되는 것도 되게 만드는 만능키 같은 느낌이랄까? 이 부분에 대해서는 이 블로그 내용으로 공부했다.
charAt() : 해당 문자열의 특정 인덱스에 해당하는 문자 반환
compareTo() : 해당 문자열을 인수로 전달된 문자열과 사전 편찬 순으로 비교 (대소문자 구분 O)
concat() : 해당 문자열 뒤에 인수로 전달된 문자열을 추가한 새로운 문자열로 반환
indexOf() : 해당 문자열에서 특정 문자나 문자열이 처음으로 등장하는 위치의 인덱스 반환
trim() : 해당 문자열의 맨 앞과 맨 뒤에 포함된 모든 공백 문자 제거 (띄어쓰기와 탭 문자)
toLowerCase(), toUpperCase() : 해당 문자열의 모든 문자를 소문자/대문자로 변환