Programming/Java

자바 기초부터 모던 자바까지 - 기초편

KOOCCI 2022. 7. 1. 23:34

목표 : 자바의 가장 기초적인 내용부터 알아본다.


java만큼 아직 대중적인 프로그래밍 언어는 없다.

점차 포스팅으로 모던 자바에 대해 정리하겠지만, 알았으면서도 오랜만에 보면 새로울 수 있는 포인트들이 있다.

변수가 무엇인지, 자료형이 무엇인지 이런 내용은 아니지만, 조금은 익숙해져야 할 내용으로 작성하려 한다.

상수와 리터럴이 뭐지?

상수(constant)는 알다 시피, 변하지 않는 수를 말하고 final이라는 키워드를 쓴다. 

final을 쓰면, 변수는 상수가 되고, 상수는 변할 수 없다.

 

리터럴(literal)프로그램에서 사용하는 모든 숫자, 값, 논리 값 (ex> 10, 3.14, 'A', true)을 의미 한다. 

그 중에서 리터럴은 상수풀(constant pool) 영역에 저장되어 있다.

변수는 해당 리터럴이 상수풀에 있는지 확인하고 사용하게 된다.

 

switch문이 바뀌고 있다?

기존에 사용하던 switch 문은 다음과 같다.

switch (today) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        System.out.println("today is Weekday");
        break;
    case 6:
    case 7:
        System.out.println("today is Weekend");
        break;
}

여기서 java 버전이 업그레이드 되면서 다음과 같은 기능이 생겼다.

  1. 쉼표로 구분이 가능하다.
  2. 식으로 표현하여 반환 값을 받을 수 있고, 리턴이 없으면 오류
    1. switch라는 함수명을 가지고, 파라미터로 비교대상을 받는 형태로 가능하다.
  3. break를 안쓰고 괄호로 묶을 수 있다. 또한, -> 수식을 쓴다.
  4. switch case에서 yield 문으로 반환이 가능하다.
int day = switch(month) {
	case 1,3,5,7,8,10,12 -> 31;
    case 2 -> 28;
    case 4,6,9,11 -> 30;
    default -> {
    	System.out.println("x");
        yield -1; // 수행과 반환이 같이 쓰여서, yield를 써서 반환함을 알려주어야 한다.
    }
}; // day의 끝이므로 세미콜론 필요

Public 클래스는 하나다.

자바 파일 하나에 여러 개의 클래스가 존재할 수는 있다.

단, public 클래스는 하나이고, public 클래스와 자바 파일 이름은 동일해야 한다.

 

접근 제어 지시자(Access Modifier)

클래스 외부에서 클래스의 멤버 변수, 메서드, 생성자를 사용할 수 있는지 여부를 지정하는 키워드다.

  • private : 같은 클래스 내부만 접근 가능 (외부 클래스, 상속 관계 클래스라도 접근 불가)
  • default : 같은 패키지 내부에서만 접근 가능 (상속 관계라도 패키지가 다르면 접근 불가)
  • protected : 같은 패키지나 상속관계의 클래스에서 접근 가능하고 그 외 외부에서는 접근할 수 없음.
  • public : 클래스 외부 어디서나 접근 가능

Call by Value VS Call by Reference

java는 Call By Value밖에 없다. Object의 경우 주소값을 넘기는 Call By Value이기 때문에 헤깔리지만 염두해두자.

 

static 변수, static 메서드

여러 인스턴스가 공통으로 사용하는 변수를 static 변수라고 한다.

인스턴스가 생성될 때가 아닌, 처음 프로그램이 메모리에 로딩될 때 메모리를 할당하는 변수다.

인스턴스 생성과 상관 없이 사용가능하여, 클래스 이름으로 직접 참조한다.

 

static 메서드는 클래스 메서드라고도 하며, 멤버변수를 사용할 수 없는 메서드다.

이 역시, 클래스 이름으로 사용할 수 있다.

인스턴스 생성 전에 호출 될 수 있으므로, static 메서드 안에는 인스턴스 변수를 사용할 수 없다.

 

싱글톤 패턴에서 주로 사용하는 방법이다.

static class Singleton {
	private static Singleton instance;
    
    private Singleton() {}
    public static Singleton getInstance() {
    	if(instance == null) {
        	instance = new Singleton();
        } 
        return instance;
    }

}
public class ExampleClass {

    //private construct
    private ExampleClass() {}

    private static class InnerInstanceClazz() {
        private static final ExampleClass instance = new ExampleClass();
    }

    public static ExampleClass getInstance() {
        return InnerInstanceClazz.instance;
    }
}

형변환은 왜 이렇게 쓰고 있을까 ?

Customer c = new VIPCustomer();

위 형태를 자주 볼 것이며, 업캐스팅하여 형 변환한 케이스다.

아래 예시처럼, 상속된 구조에서 사용할 수 있는 부분으로 VIPCustomer 클래스의 모든 멤버 변수에 대한 메모리는 생성되지만, 변수 타입이 Customer이므로 실제로 접근 가능한 변수나 메서드는 Customer의 변수와 메서드가 된다.

class Customer {
    private String customerId;
    private String name;
    public String getCustomerId() {
        return customerId;
    }
}

class VIPCustomer extends Customer {
    private String vipId;
    public String getVipId() {
        return vipId;
    }
}

이 때, 오버라이딩이 되면 인스턴스 타입을 따라가게 된다.

java는 항상 인스턴스의 메서드가 호출된다 (가상 메서드 원리)

자바의 모든 메서드는 가상 메서드 (virtual method)다.

 

인터페이스를 구현한 클래스는 인터페이스 형으로 선언한 변수로 형 변환할 수 있다.

이 역시 인터페이스에 선언된 메서드만 사용가능하다.

가상 메서드(virtual method) 간단하게 알아보면?

메서드의 이름은 주소값을 나타낸다. (메서드는 명령어의 set이고 프로그램이 로드되면 메서드 영역(코드영역)에 명령어 set이 위치한다.)

메서드가 호출되면 명령어 set이 있는 주소를 찾아 명령어가 실행된다.

메서드 변수들은 stack에 위치하게 될 것이고,  인스턴스 변수는 힙 메모리에 있을 것이다.

메서드는 메서드 영역에 있으니 처음 한번만 호출된다.

final 예약어를 정리해보자.

  • final 변수 : 값이 변경될 수 없는 상수
  • final 메서드 : 하위 클래스에서 재정의 할 수 없는 메서드
  • final 클래스 : 상속할 수 없는 클래스

String의 선언은 달라질 수 있다.

String str1 = new String("AAA");
String str2 = "AAA";

1. 힙메모리에 인스턴스를 생성한 경우

2. 상수 풀(constant pool)에 있는 주소를 참조

힙 메모리는 매번 다른 주소지만, 상수 풀의 문자열은 모두 같은 주소를 가진다.

String str1 = new String("abc");
String str2 = new String("abc");

System.out.println(str1 == str2); // false

String str3 = "abc";
String str4 = "abc";

System.out.println(str3 == str4); // true

String 도 하나의 Wrapper Class이고, Reference 타입이므로 주소값(참조값)을 가진다.

상수 풀에 있는 주소를 참조하여 생성했을 때 == 연산자가 정상인 것처럼 보일 떄도 있겠지만, equal 연산자를 쓰는게 옳다.

String은 Immutable(불변)한다?

한번 생성된 String은 변하지 않는다. 즉, String을 연결하면 기존 String에 연결되는게 아니라, 완전 새로운 문자열이 생성된다. (메모리 낭비가 생길 수 있다)

그래서 StringBuilder, StringBuffer 가 나온다.

특징은 다음과 같다.

1. 내부적으로 가변적인 char[]를 가지고 있다. (새로운 인스턴스를 생성하지 않고, char[]를 변경한다)

2. StringBuffer는 멀티 쓰레드 프로그래밍에서 동기화(Synchronization)을 보장한다.

3. 단일 쓰레드 프로그램에서는 StringBuilder 사용을 권장한다.

4. toString()메서드로 String을 반환한다.

 

제네릭(Generic) 프로그래밍

1. 클래스에서 사용하는 변수의 자료형이 여러개일 수 있고, 그 기능(메서드)은 동일한 경우 클래스의 자료형을 특정하지 않고 추후 해당 클래스를 사용할 수 있게 지정할 수 있도록 선언한다.

2. 실제 사용되는 자료형 변환은 컴파일러에 의해 검증된다.

3. Collection 프레임워크에서 많이 사용한다.

 

 

public class GenericPrinter<T> {
	private T material;
	
	public void setMaterial(T material) {
		this.material = material;
	}
	
	public T getMaterial() {
		return material;
	}
	
	public String toString(){
		return material.toString();
	}
}

자료형 매개변수 T(type parameter) : 이 클래스를 사용하는 시점에 실제 사용할 자료형을 지정, static 변수는 사용할 수 없음

 

T라는 자리에 Powder, Plastic 과 같은 다른 클래스가 들어갈 수 있다. 즉, Object로도 취환해서 class를 만들 수 있다.

다만, 그렇게 안하는 이유는 형 변환을 결국 해야하기 때문.

public class ThreeDPrinter{

	private Object material;
	
	public void setMaterial(Object material) {
		this.material = material;
	}
	
	public Object getMaterial() {
		return material;
	}
}

//

ThreeDPrinter printer = new ThreeDPrinter();

Powder powder = new Powder();
printer.setMaterial(powder);

Powder p = (Powder)printer.getMaterial();

제네릭 메서드는 또 뭐지?

자료형 매개 변수메서드의 매개변수반환 값으로 가지는 메서드는 자료형 매개 변수가 하나 이상인 경우도 있다.

꼭 제네릭 클래스가 아니어도 내부에 제네릭 메서드는 구현 가능하다.

public class Point<T, V> {
	T x;
	V y;
	
	Point(T x, V y){
		this.x = x;
		this.y = y;
	}
	
	public  T getX() {
			return x;
	}

	public V getY() {
		return y;
    }
}

포인트를 만드는 간단한 제네릭 클래스이다.

이를 활용하는 제네릭 메서드도 만들어 보자.

public class GenericMethod {

	public static <T, V> double makeRectangle(Point<T, V> p1, Point<T, V> p2) {
		double left = ((Number)p1.getX()).doubleValue();
		double right =((Number)p2.getX()).doubleValue();
		double top = ((Number)p1.getY()).doubleValue();
		double bottom = ((Number)p2.getY()).doubleValue();
		
		double width = right - left;
		double height = bottom - top;
		
		return width * height;
	}
	
	public static void main(String[] args) {
		
		Point<Integer, Double> p1 = new Point<Integer, Double>(0, 0.0);
		Point<Integer, Double> p2 = new Point<>(10, 10.0);
		
		double rect = GenericMethod.<Integer, Double>makeRectangle(p1, p2);
		System.out.println("두 점으로 만들어진 사각형의 넓이는 " + rect + "입니다.");
	}
}

makeRectangle이라는 함수로 그 형태를 확인해보자.

 

public static <T, V> double makeRectangle(Point<T, V> p1, Point<T, V> p2)

public <자료형 매개 변수> 반환형 메서드 이름(자료형 매개변수.....) { }

 

메서드의 매개변수로 T,V라는 Type Parameter(자료형 매개변수)를 사용하였기 때문에, Generic Method로 볼수 있다.

 

다음 으로는 컬렉션, 람다, 스트림 등 jdk 1.8 이후에 격변한 내용들을 알아보려고 한다.

다만, 그 내용을 조금 간단히 먼저 알아보고, 하나씩 조금 심화해서 볼 예정이다.