Spring
Basic
Java 문법
자바 문법

Java 문법

변수와 심볼 테이블(Symbol Table)

심볼 테이블이란?

변수가 만들어지면 변수를 관리하는 테이블에 변수 정보가 저장된다.

main

Java는 main이라는 이름의 메소드를 가지고 있는 클래스가 시작점이 된다.

public class Calculator {
    public static void main(String[] args) {
        int a, b, sum;
        a = 1;
        b = 1;
        sum = a + b;
 
        System.out.println(sum);
    }
}

자료형

Prime Type(기본형)

  • 자바에서 기본 자료형은 반드시 사용하기 전에 선언되어야 한다.
  • OS에 따라 자료형의 길이가 변하지 않는다.
  • 비객체 타입이다. 따라서 null을 가질 수 없다.
TypeBitsRange of Value
byte8-128 ~ 127
short16-32,768 ~ 32,767
int32-2,147,483,648 ~ 2,147,483,647
long64-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
float321.4E-45 ~ 3.4028235E38
double644.9E-324 ~ 1.7976931348623157E308
char160 ~ 65,535
boolean1true, false

Reference Type(참조형)

  • class type : 클래스 형태로 만들어 놓은 모델
  • string class
    • String은 의외로 class이며 기본 자료형이 아니다.
    • 불변객체이다.

Enum

기본형은 아래와 같다.

public enum Company {
    SK,
    LG,
    KT,
    SAMSUNG,
    APPLE
}

열거체의 자식들에게 ()로 감싸고 상수값을 추가할 수 있다. 이 값은 열거형 안에

private final String value;
Company(String value){
    this.value = value;
}
public String getValue(){
    return value;
}

와 같이 선언해놓고 사용할 수 있는데, 얘를 들어서 Company.APPLE이락 하면 this.value에 APPLE의 상수값이 들어가게 되고 getValue함수를 통해서 해당하는 상수값을 가져오는 식이다.

Company Enum
public enum Company {
    SK("에스케이"),
    LG("엘쥐"),
    KT("케이티"),
    SAMSUNG("삼성"),
    APPLE("애플");
    private final String value;
    Company(String value){
        this.value = value;
    }
    public String getValue(){
        return value;
    }
}

Wrapper Class

기본형은 null을 넣을 수 없지만 기본형에 null을 넣는 방법이 있다.
이를 Wrapper Class라고 한다.

기본형Wrapper
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charChar
booleanBoolean

형변환

강제 타입 변환

캐스팅 또는 명시적 타입 변환이라고도 한다.

변환수식결과
int > charchar c = (char) 65;A
char > intint i = (int) 'A';65
char > StringString s = Character.toString('A');A
String > charchar c = s.charAt(0);A
int > floatfloat f = (float) 3.14;3.14
float > intint i = (int) 3.14;3
double> intint i = (int) 3.14;3
int > doubledouble d = (double) 3;3.0

자동 타입 변환

산술 변환 또는 묵시적 변환이라고도 한다.
Java는 JavaScript처럼 문자열 뒤에 숫자를 붙이면 숫자를 모두 문자열로 바꿔준다. 숫자끼리 더해주고 싶으면 ()로 묶어줘야 한다.

// ()로 a + b를 안감싸주면 a + b = 122가 출력이 된다.
System.out.println("a + b = " + (a + b));

숫자 -> 문자

숫자를 문자로 변환하는 방법

int num = 23;
String str1 = String.valueOf(num);
String str2 = "" + num;
String str3 = String.valueOf(num);

문자 -> 숫자

문자를 숫자로 변환하는 방법

String str = "23";
int num = Integer.parseInt(str);

Null > NonNullable

Java에서는 Optional<T>로 Null을 표현한다. 강의 진행 중 Memory DB Part에서 List에 remove를 하는데 remove가 되지 않는 버그가 발생했다. 문제가 됐던 부분은 다음과 같은데,

Optional<T> prevData = dataList.stream()
        .filter(d -> d.getId()
                .equals(data.getId()))
        .findFirst();
 
 
if (prevData.isPresent()) {
    // 기존 데이터가 있는 경우
    dataList.remove(prevData);
    dataList.add(data);
} 

위의 부분에서 remove에 들어가는 Object가 Optional이라서 생긴 것이다. Nullable을 NonNullable로 바꿔주는 기본적인 방식은 다음과 같다.

Optional<T> nullableObject = Optional.ofNullable(object); 
if(nullableObject.isPresent()) {
    NonNullable nonNullableObject = nullableObject.get();
}

따라서, 위의 코드에서 remove가 진행이 안되던 방식을 아래와 같이 바꿔주니 제대로 동작했다.

Optional<T> prevData = dataList.stream()
        .filter(d -> d.getId()
                .equals(data.getId()))
        .findFirst();
 
 
if (prevData.isPresent()) {
    // 기존 데이터가 있는 경우
    dataList.remove(prevData.get());
    dataList.add(data);
} 

배열

값 초기화

Type[] 변수명 = new Type{ 변수1, 변수2, 변수3 };

public class Main {
  public static void main(String[] args) {
    Book a = new Book();
    Book b = new Book();
    Book[] bookArray = {a, b};
  }
}

크기 정의

Type[] 변수명 = new Type[크기];

public class Main {
  public static void main(String[] args) {
 
    int[] a = new int[3];
    // int[] a = new [3]; // 에러 출력(Type 없음)
 
    a[0] = 1;
    a[1] = 2;
    a[2] = 3;
    // a[3] = 4; // 에러 출력(Index 3 out of bounds for length 3)
    // a = {1,2,3}; // 에러 출력
  }
}
  • 초기화가 안된 배열도 길이를 가질 수 있음
초기화
public class Main {
  public static void main(String[] args) {
    int[] array1 = {};
    int[] array2 = new int[3];
 
    System.out.println(array1.length); // 0
    System.out.println(array2.length); // 3
  }
}

2차원 배열

선언
public class Main {
  public static void main(String[] args) {
    // 기본
    int[][] array1;
 
    // 배열 크기 지정
    int[][] array2 = new int[1][5];
    int array3[][] = new int[1][5];
 
    // 선언과 동시에 지정
    int[][] array4 = {{1,2,3},{4,5,6}};
    int array5[][] = {{1,2},{3,4}};
 
    for(int i[]: array5) {
      System.out.println("Array : " + Arrays.toString(i));
    }
  }
}
구구단 만들기
2차원 배열 초기화를 통한 구구단 만들기
public class Main {
  public static void main(String[] args) {
    int array[][] = new int[8][9];
    int cnt = 0;
 
    for (int i = 2; i < 10; i++) {
      for(int j = 0; j < 9; j++) {
        array[cnt][j] = i * (j+1);
      }
      System.out.println(i + "단 : " + Arrays.toString(array[cnt]));
      cnt += 1;
    }
  }
}

배열 함수

  1. Arrays.copyOf(배열, 복사할 길이)

길이만큼 복사

int[] arr = {0, 1, 2, 3, 4};
 
int[] newArray1 = Arrays.copyOf(arr, arr.length);         // [0, 1, 2, 3, 4]
int[] newArray2 = Arrays.copyOf(arr, 3);                  // [0, 1, 2]
int[] newArray3 = Arrays.copyOf(arr, arr.length + 3);     // [0, 1, 2, 3, 4, 0, 0, 0] (0으로 채워짐)
  1. Arrays.copyOfRange(배열, 시작인덱스, 끝인덱스)

시작 인덱스 ~ 끝 인덱스까지 복사

int[] arr = {0, 1, 2, 3, 4};
int[] copyArrIdx = Arrays.copyOfRange(arr, 2, arr.length + 2);  // [2, 3, 4, 0, 0]
  1. Arrays.sort

정렬

public static void main(String[] args) {
    int[] arr = {5,3,1,2,6};
    Integer arr2 = {5,3,1,2,6};
    Arrays.sort(arr);
    arr2.sort();
    System.out.println(Arrays.toString(arr));
}
숫자 오름차순 / 내림차순 정렬
public class Main {
  public static void main(String[] args) {
    Integer[] array = {5, 3, 1, 4, 2};
    Arrays.sort(array, Comparator.reverseOrder());
    System.out.println(Arrays.toString(array)); // 5,4,3,2,1
    Arrays.sort(array, Comparator.naturalOrder()); // 1,2,3,4,5
    System.out.println(Arrays.toString(array)); // 5,4,3,2,1
  }
}

3.1 Comparator Custom

private Comparator<T> sort = new Comparator<T>() {
    @Override
    public int compare(T o1, T o2) {
        return Long.compare(o1.getId(), o2.getId());
    }
};
 
@Override
public List<T> findAll() {
    return dataList.stream().sorted(sort).toList();
}

ArrayList

ArrayList는 배열과 달리 크기가 고정되어 있지 않고, 데이터를 추가하거나 삭제할 수 있다.

public class Main {
  public static void main(String[] args) {
    ArrayList<Integer> arr = new ArrayList<Integer>();
    arr.add(1);
    arr.add(2);
    arr.add(3);
    System.out.println(arr.toString()); // 1, 2, 3 출격
    arr.remove(1);
    System.out.println(arr.toString()); // 1, 3 출력
    arr.set(1, 5);
    System.out.println(arr.toString()); // 1, 5 출력
    arr.clear();
    System.out.println(arr.toString()); // [] 출력
 
    // addAll 사용법 1
    arr.addAll(Arrays.asList(1,2,3,4,5));
    System.out.println(arr.toString()); // [1,2,3,4,5] 출력
    arr.removeAll(Arrays.asList(2,3,4));
    System.out.println(arr.toString()); // [1,5] 출력
    ArrayList<Integer> addArr = new ArrayList<Integer>();
 
    // addAll 사용법 2
    addArr.addAll(Arrays.asList(6,7,8,9));
    arr.addAll(addArr);
    System.out.println(arr.toString()); // [1, 5, 6, 7, 8, 9] 출력
  }
}

반복문

for-each

파이썬이나 dart의 for i in list와 같은 기능을 한다.

public class Main {
  public static void main(String[] args) {
    int[] a = {1, 2, 3, 4};
    int result = 0;
    for(int i: a) {
      result += i;
    }
    System.out.println("Result = " + result); // 10 출력
  }
}

함수

java에서는 기본적으로 named parameter가 없다. 이를 해소하기 위해서 나온 기법이 builder 기법과 method chaining이라는 기법이다.

메서드 오버로딩

java에서는 같은 클래스에 동작이 비슷한 동일한 이름의 메서드가 존재할 수 있다. 이를 위해서는 메서드끼리 시그니처가 달라야 한다.

  • 시그니처 : parameter type, return type 등 함수의 형태
예시
public class Main {
  public static void main(String[] args) {
    int result = new Calc().add(5, 6);
    float result2 = new Calc().add(5.6F, 4.6F);
 
    System.out.println(result);
    System.out.println(result2);
  }
}
 
class Calc {
  public int add(int a, int b) {
    return a+b;
  }
  public float add(float a, float b) {
    return a + b;
  };
}

정적 바인딩

컴파일 시점에서 사용될(호출될) 메서드가 결정(연결)되는 바인딩이다.
위의 코드는 컴파일 되는 시점에

public add_int(int a, int b) {
  return a + b;
}
public add_float(int a, int b) {
  return a + b;
}

와 같이 바꿔버린다.

Lambda

  • 특징
  1. 익명함수이다.
  2. 메서드에 대한 구현을 간결하게 할 수 있다.
  3. return과 대괄호 까지 생략이 가능하다.
  4. 함수형 인터페이스의 구현체로 사용할 수 있다.
함수형 인터페이스의 구현체
public class Main {
    public  static  void main(String[] args) {
        MathOperation add = (x, y) -> x + y;
        int result = add.operation(6, 5);
        System.out.println(result);
    }
}
 
@FunctionalInterface
interface  MathOperation {
    int operation(int x, int y);
}

named parameter

java는 기본적으로 named parameter를 지원하지 않는다. 이를 해결하기 위해 사용하는 2가지 패턴을 알아보자.

메소드 체이닝

public class User {
    private String name;
    private String email;
 
    public User setName(String name) {
        this.name = name;
        return this;
    }
 
    public User setEmail(String email) {
        this.email = email;
        return this;
    }
}

빌더

public class User {
    private String name;
    private String email;
 
    private User() {
        // private constructor to enforce object creation through builder
    }
 
    public static class Builder {
        private User user;
 
        public Builder() {
            user = new User();
        }
 
        public Builder withName(String name) {
            user.name = name;
            return this;
        }
 
        public Builder withEmail(String email) {
            user.email = email;
            return this;
        }
 
        public User build() {
            return user;
        }
    }
}

Class

public class Book {
    public  int page;
    public  String title;
    public float price;
    public String company;
}

Constuctor

public class Main {
  public static void main(String[] args) {
    int result = new Calc(5,6).add();
 
    System.out.println(result);
  }
}
// Constructor
class Calc {
  int a;
  int b;
  Calc(int a, int b) {
    this.a = a;
    this.b = b;
  }
 
  public int add() {
    return a+b;
  }
}

Class Model의 종류

  • 시작 클래스 모델
  • 데이터를 담는 모델 (DTO)
  • 데이터를 처리 하는 모델 (CRUD, DAO)
  • 도움(Utility)을 주는 모델 (Helper Class)

DTO or VO

DTO / VO (Data Transfer Object) : 데이터를 하나로 묶어야 될 경우 바구니가 필요, 데이터를 하나로 수집하는 역할을 한다.

DAO

DAO(Database Access Object) : 데이터베이스에 데이터(VO, DTO)를 저장 수정하거나 검색, 삭제를 하기 위해서 만들어지는 모델(클래스)이다.
CRUD 동작을 가지고 있는 클래스이며 비즈니스 로직을 처리하는 클래스이다.

상속 체이닝

맨 위 부모클래스부터 객체가 생성되어 자식까지 연결되는 구조
결국 java에선 맨 위로 올라가면 Object가 최상위이다.

public class Main {
    public  static  void main(String[] args) {
        Child child = new Child();
        // Hello from Child
        // Hello from Parent
        child.sayHello();
    }
}
 
class Parent {
    void sayHello() {
        System.out.println("Hello from Parent");
    }
}
 
class Child extends Parent {
    @Override
    void sayHello() {
        System.out.println("Hello from Child");
        super.sayHello();
    }
}

상속 Constructor

class Parent {
    private String name;
 
    Parent(String name) {
        this.name = name;
    }
}
 
class Child extends Parent {
    Child(String name) {
        super(name);  // 부모 클래스의 생성자를 호출
    }
}
 
public class Main {
    public static void main(String[] args) {
        Child child = new Child("John");
    }
}

다운 캐스팅과 업 캐스팅

  • 동적 바인딩 :업 캐스팅시에 실행시점에서 사용될 메서드가 결정되는 바인딩
import java.lang.reflect.Type;
 
public class Main {
    public  static  void main(String[] args) {
        Cat cat = new Cat();
        Animal animal = (Animal)cat;
        // Cat 업 캐스팅은 잘됨
        System.out.println(animal.getClass().getName());
        Animal animal1 = new Animal();
        Cat cat1 = (Cat)animal1;
        // Error 다운 캐스팅 실패 
        System.out.println(cat1.getClass().getName());
    }
}
 
class Animal {
    void eat() {
        System.out.println("Eat Feed");
    }
}
 
class Cat extends Animal{
    @Override
    void eat() {
        System.out.println("Eat Cat Food");
    }
}

함수형 인터페이스

함수형 인터페이스(Funtional Interface)는 정확히 하나의 추상 메서드를 가진 인터페이스를 의미한다.

함수형 인터페이스 예시
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

@FunctionalInterface 어노테이션은 해당 인터페이스가 함수형 인터페이스임을 명시적으로 나타낸다.

메서드 참조

형이 동일하고 인수가 동일하고 내가 원하는 동작을 하는 메서드를 참조할 때 사용할 수 있는 방법이다.

  1. 정적 메서드 참조 : 생성 안된 개체를 참조하는 방법
  • 클래스명::메서드명
  1. 인스턴스 메서드 참조 : 이미 생성된 개체를 참조하는 방법
  • 객체명::메서드명
  1. 특정 객체의 인스턴스 메서드 참조 : 클래스명::메서드명
  2. 생성자 참조 : 클래스명::new
public class Main {
    public  static  void main(String[] args) {
        Converter<String, Integer>  converter = IntegerUtils::stringToInt;
        int result = converter.convert("123");
        System.out.println(result);
    }
}
 
@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}
 
class IntegerUtils {
    public static int stringToInt(String s) {
        return Integer.parseInt(s);
    }
}

Wrapper Class

public  static  void main(String[] args) {
    // Integer String 같은 변수들을 참조 변수, Wrapper Class라고 함
    // Integer a = new(10); 이런식으로 선언을 안함
    Integer a = 10; // auto Boxing
    System.out.println(a.intValue()); // Unboxing(Integer -> int)
}

Annotation

Annotation이란 비즈니스 로직에는 영향을 주지는 않지만 해당 타겟의 연결 방법이나 소스코드의 구조를 변경할 수 있다. (opens in a new tab)

Annotation의 용도로는 크게 3가지로 나뉠 수 있다. (opens in a new tab)

  1. 컴파일러에게 코드 작성 문법 에러를 체크하도록 정보를 제공한다.
  2. 소프트웨어 개발툴이 빌드나 배치시 코드를 자동으로 생성할 수 있도록 정보를 제공한다. (Code Generation)
  3. 실행시(런타임시)특정 기능을 실행하도록 정보를 제공한다.

Custom Annotation

@interface Annotation을 이용해서 User Custom Annotation을 만들어줄 수 있다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Authenticator {
    int sortOrder() default 0;
}
  • @Retension : @interface의 적용범위로 어떤 시점까지 사용될지를 결정한다.

    • Class : Default값으로 클래스파일에는 포함되지만 JVM이 로드하진 않는다.
    • Runtime : 클래스 파일에 포함되고, JVM이 로드해서 사용한다.
    • Source : 컴파일할 때만 사용하고, 클래스파일에는 포함되지 않는다.
  • @Target : @interface 어노테이션의 적용 위치를 설정하는 옵션이다. 즉, Annotation을 클래스에 붙일건지 함수에 붙일건지 등을 정의한다.

    • TYPE : 클래스 및 인터페이스
    • FIELD : 클래스의 멤버 변수
    • METHOD : 함수
    • PARAMETER : 파라미터
    • CONSTRUCTURE : 생성자
    • LOCAL_VARIABLE : 지역 변수
    • ANNOTATION_TYPE : 어노테이션 타입
    • PACKAGE : 패키지
    • TYPE_PARAMETER : 타입 파라미터
    • TYPE_USE : 타입 사용
  • @Documented : 해당 어노테이션을 javadoc에 포함시킬지를 결정한다.

  • @Inherited : 해당 어노테이션을 자식 클래스에 상속할지를 결정한다.

  • @Repeatable : 해당 어노테이션을 반복해서 사용할 수 있도록 한다.

Custom Annotation의 활용

String vs StringBuilder vs StringBuffer

Stringimmutable하다. 즉, String에 새로운 값을 할당할 경우 새로운 주소가 할당되는거지 주소에 있는 값이 변하는 것이 아니다. 반면에 StringBuilder 또는 StringBuffer는 mutable하다.

String Buuilder vs String Buffer

공통점

String Builder와 StringBuffer는 가변 문자열을 다루는 클래스이다.

차이점

StrinbBuilder는 스레드 안정성을 고려하지 않고 작성된 클래스이고 StringBuffder는 java 5 버전 이후로 스레드 안정성을 고려하여 작성된 클래스이다.

StringBuilder

스레드 안정성을 고려하지 않은 클래스이다.
아래 코드에서 보면 sb라는 StringBuilder에 두 개의 스레드에서 각각 "0"과 "1"을 10000번씩 추가한 후, 스레드가 끝났을 때 sb의 길이를 출력하는 코드이다.
이 코드가 실행이 되고 나서 length는 40000이 출력되야할 것 같지만, Thread 안정성이 고려되지 않고 작성된 코드라서 40000이 아닌 다른 값이 출력된다.

public class StringBuilderExample implements Runnable {
  StringBuilder sb;
 
  public StringBuilderExample() {
    sb = new StringBuilder();
  }
 
  public void run() {
    addChar();
  }
 
  public void addChar() {
    for (int i = 0; i < 10000; i++) {
      sb.append("1");
      sb.append("0");
    }
  }
 
  public static void main(String[] args) {
    StringBuilderExample sbTest = new StringBuilderExample();
    Thread threadOne = new Thread(sbTest, "ThreadOne");
    Thread threadTwo = new Thread(sbTest, "ThreadTwo");
 
    // Thread를 시작할 때 run 메서드를 실행함
    threadOne.start();
    threadTwo.start();
 
    try {
      threadOne.join();
      threadTwo.join();
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
    // 40000을 기대했으나 Thread Safe 하지 않아서 다른 값이 나옴
    System.out.println("length:" + sbTest.sb.length());
  }
}

StringBuffer

StringBuffer는 스레드 안정성을 고려한 클래스이다. 아래 코드를 실행시켜보면 40000이라는 값이 출력되는 것을 알 수 있다.

public class StringBufferExample implements Runnable {
  StringBuffer sb;
 
  public StringBufferExample() {
    sb = new StringBuffer();
  }
 
  public void run() {
    addChar();
  }
 
  public void addChar() {
    for (int i = 0; i < 10000; i++) {
      sb.append("1");
      sb.append("0");
    }
  }
 
  public static void main(String[] args) {
    StringBufferExample sbTest = new StringBufferExample();
    Thread threadOne = new Thread(sbTest, "ThreadOne");
    Thread threadTwo = new Thread(sbTest, "ThreadTwo");
 
    threadOne.start();
    threadTwo.start();
 
    try {
      threadOne.join();
      threadTwo.join();
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
    System.out.println("length:" + sbTest.sb.length());
  }
}

Reference

참조한 블로그 (opens in a new tab) Enum 참조 블로그 (opens in a new tab) @interface 참조 블로그 (opens in a new tab)