[1주차] Java 기본개념과 OOP

Dev.Dana·2024년 10월 31일

Java

목록 보기
1/7
post-thumbnail

Java란?

Java는 1995년에 썬 마이크로시스템즈(Sun Microsystems)에서 제임스 고슬링(James Gosling)을 중심으로 한 개발 팀에 의해 만들어졌다.

플랫폼 독립적인 객체 지향 프로그래밍 언어로써 한 번 작성되면 다양한 운영체제에서 실행 할 수 있는게 특징이자 장점이다.

Java의 특징 및 단점

Java의 특징

  1. 플랫폼 독립성
    • Write Once, Run Anywhere라는 모토처럼 한 번 작성한 Java 코드는 다양한 운영 체제에서 실행 할 수 있다. 소스 코드를 컴파일할 때 기계어가 아닌 바이트코드로 변환되기 때문에 가능해졌다.
    • JVM이 각 운영체제에 맞춰 설치되기 때문에 동일한 바이트코드를 다양한 운영 체제에서 해석하고 실행할 수 있다.
  2. 객체 지향 프로그래밍
    • Java는 객체 지향 언어로 클래스와 객체를 기반으로 설계된다.
    • 상속, 다형성, 캡슐화, 추상화와 같은 OOP 개념을 통해 코드의 재사용성을 높이고 유지보수성을 강화하는 장점이 있다.
      • 대규모 프로젝트에서 구조화된 코드 작성이 용이하다.
  3. 메모리 관리와 보완성
    • GC(Garbage Collection)을 통해 개발자가 직접 메모리 해제를 하지 않아도 자동으로 메모리를 최적화 해 준다.
    • JVM 자체의 보안 관리 기능 덕분에 애플리케이션이 불필요한 자원에 접근하는 것을 방지하여 보안성이 강화된다.

Java의 단점

  1. 속도
    • JVM이 바이트코드를 해석하는 과정에서 C++같은 순수 컴파일 언어보다는 성능이 느릴 수 있다. → 하지만 JIT(Just In Time)컴파일러가 자주 실행되는 코드는 기계어로 바로 컴파일하여 성능을 개선한다.
  2. 메모리 사용량
    • JVM이 실행되는 동안 필요한 메모리 자원이 비교적 크기 때문에 가벼운 환경에서는 성능이 저하될 수 있다.
  3. 긴 코드
    • 객체 지향적인 특성으로 인해 간단한 작업도 클래스를 정의하고 메서드를 구현해야하기 때문에 다른 언어에 비해 코드가 길어질 수 있다.

Java의 실행 과정, JIT 컴파일 방식

  1. 소스코드 작성 및 컴파일
    • Java 코드를 작성한 후 컴파일러가 바이트코드로 변환한다. 이 바이트코드는 .class파일로 저장된다.
  2. JVM에서 해석 및 실행
    • 각 운영체제에 맞게 설치된 JVM은 이 바이트코드를 해석하고 기계어로 변환하여 실행한다. → 다양한 운영체제에서 동일한 바이트코드를 실행
  3. JIT 컴파일
    • 자주 실행되는 코드 블록을 기계어로 변환하여 캐시에 저장한다.
    • 반복 실행 시에 코드를 해석하는 시간을 줄이고 실행속도를 향상시킨다.
📌
소스코드(.java) → 컴파일러 → 바이트코드(.class) → JVM 해석 및 JIT 컴파일 → 프로그램 실행

Java 주요 버전 (Java 8, 11, 17)

Java는 정기적으로 업데이트 되고, 각 버전마다 주요 기능이 추가된다.

특히 Java 8, 11, 17은 현업에서 많이 사용되는 버전 LTS (Long-Term Support) 들이다.

LTS 버전이 일반적인 버전보다 더 오랜 기간 보안 패치, 버그 수정, 성능 개선 등의 업데이트가 제공되기 때문에 기업들이 안정적인 애플리케이션 환경을 유지할 수 있어 많이 선택한다.

  • Java 8 :
    • 람다 표현식스트림 API가 추가되어 함수형 프로그래밍 요소가 강화되었다.
    • NullPointerException을 방지하기 위해 Optional 클래스가 도입되었습니다.
  • Java 11 :
    • 모듈 시스템HTTP 클라이언트 API가 추가되었다.
  • Java 17 :
    • 패턴 매칭 기능과 스위치 표현식 개선이 포함되었다.

JDK와 JRE의 차이점

  • JDK : Java 애플리케이션을 개발할 때 필요한 컴파일러, 디버거, 그리고 JRE가 포함된 개발 툴킷 JDK : 개발 도구 + JRE
  • JRE : JVM과 필요한 라이브러리만 포함되어 있어 Java 애플리케이션을 실행하는 데 필요한 환경만을 제공한다. ⇒ 개발 기능은 없고 실행 기능만 있음 JRE : JVM + 라이브러리

동일성(Identity)과 동등성(Equality)

Java에서는 객체를 비교할 때 동일성동등성의 개념이 중요하다.

  • 동일성 (== 연산자) : 두 객체의 메모리 주소를 비교하여 같은 객체인지 확인 동일성 : == 사용, 메모리 주소 비교
  • 동등성 (equals() 메서드) : 객체가 같은 값을 가지고 있는지 확인하고, 객체의 내용 자체를 비교 동등성 : equals() 사용, 객체의 내용 비교

equals()와 hashCode()의 관계

Java에서 객체의 동등성을 비교할 때 equals()와 hashCode()를 함께 재정의해야 한다.

→ HashMap이나 HashSet 같은 자료구조를 사용할 때 주로 사용된다.

  • equals()는 객체의 값을 비교하고, hashCode()는 객체의 해시 코드를 반환한다.
  • 두 객체가 같은 값이라면 동일한 해시 코드를 반환해야 자료구조 내에서 올바르게 검색될 수 있다.
  • 찾고싶은 정보를 더 빨리 찾기 위해 hashcode()를 먼저 사용해서 고유 번호를 찾은 후 equals()로 진짜 값이 맞는지 한 번 더 확인한다.

toString() 메서드

  • 객체를 사람이 읽기 쉬운 문자열 형태로 변환해 주는 역할

메인 메서드의 static 선언 이유

  • Java 프로그램의 시작점인 메인 메서드 main은 프로그램이 실행될 때 JVM에 의해 먼저 호출된다.
  • 프로그램을 실행할 때 클래스가 인스턴스화되지 않아도 main 메서드를 호출할 수 있어야 한다. static메서드는 클래스의 인스턴스 없이 호출이 가능하기 때문에 main에는 항상 static이 붙어있다 !!

상수(Constant)와 리터럴(Literal)

  • 상수(Constant) :
    • 한 번 설정한 뒤 값이 변하지 않는 변수
    • final 키워드를 사용해 선언하고 주로 고정된 값을 정의할 때 사용
      • ex. final int MAX_VALUE = 100;
  • 리터럴(Literal) :
    • 코드 내에서 고정된 값으로 직접 사용되는 값입니다.
    • 숫자 10, 문자열 "Hello" 등이 리터럴이라고 할 수 있다.
    • 현업에서는 하드코딩 한다고 불리운다.

Primitive Type과 Reference Type

  • Primitive Type(기본형)
    • 값을 직접 저장하는 자료형으로 int, float, boolean 등이 있다.
    • 메모리에는 값 자체가 저장됩니다.
  • Reference Type(참조형)
    • 객체의 메모리 주소를 저장하는 자료형. 클래스 타입의 변수나 배열 등이 참조형이다.
    • 변수는 실제 값을 가진 객체의 주소를 저장한다.

Java는 Call by Value일까, Call by Reference일까?

Java는 Call by Value 방식을 사용한다. 메서드 호출 시 인수의 값이 복사되어 전달된다.

하지만 참조형 변수가 전달될 때는 객체의 주소 값이 복사되어 전달되기 때문에 메서드 내부에서 상태를 변경할 수 있다. 이 때 변수 자체가 가리키는 객체의 주소는 변경되지 않는다.

Object-Oriented Programming (OOP, 객체 지향 프로그래밍)

오버로딩(Overloading)과 오버라이딩(Overriding)의 차이

  • 오버로딩(Overloading)
    • 같은 이름의 메서드를 매개변수의 개수나 타입을 다르게 정의하여 여러 번 선언하는 것.
    • 컴파일 시점에서 어떤 메서드가 호출될지 결정됩니다.
  • 오버라이딩(Overriding)
    • 부모 클래스의 메서드를 자식 클래스에서 재정의
    • 런타임 시점에서 어떤 메서드가 호출될지 결정되고, 다형성을 구현할 때 중요한 역할을 한다.
//Overloading
void print(String text) { ... }
void print(int number) { ... }

//Overridding
@Override
void print(String text) { ... }

다형성(Polymorphism)이란 무엇이고, 왜 필요할까요?

다형성이란 같은 인터페이스를 구현한 다양한 객체가 서로 다른 동작을 수행할 수 있도록 만드는 특성

다형성을 통해 코드의 유연성과 재사용성이 높아지고, 코드의 수정 없이도 다양한 객체를 쉽게 교체할 수 있다 !

상속(Inheritance)이란?

기존 클래스의 속성과 메서드를 물려받아 새로운 클래스를 만드는 것.

자식 클래스는 부모 클래스의 특성을 이어받아 확장하거나 변경할 수 있다.

코드의 재사용성과 확장성을 높여주는 장점이 있지만 상속 구조가 길어질수록 코드 간 결합도가 높아져서 유지보수가 어려워 질 수 있다.

상속과 조합(Composition)의 차이

상속(Inheritance)은 클래스 간에 “is-a” 관계를 나타내며, 부모 클래스의 특성을 자식 클래스가 상속받아 사용한다.

조합(Composition)은 클래스 간에 “has-a” 관계를 나타내며, 다른 클래스의 객체를 자신의 필드로 사용하여 기능을 조합한다.

  • 상속은 클래스의 타입을 확장할 때 사용되고, 조합은 클래스의 기능을 결합할 때 사용된다.

instanceof 키워드란 무엇이며, 사용할 때의 문제점

  • nstanceof : 객체가 특정 클래스의 인스턴스인지 확인할 때 사용
if (obj instanceof String) {
    System.out.println("obj는 String 타입입니다.");
}
  • instanceof는 특정 클래스의 타입을 확인하여 동작을 결정하므로 코드가 특정 타입에 의존적이 되는 문제가 발생할 수 있다. → 객체 지향 프로그래밍에서 권장되는 다형성(Polymorphism) 개념과 충돌

인터페이스(Interface)와 추상 클래스(Abstract Class)

인터페이스란?

  • 인터페이스는 추상 메서드(구현되지 않은 메서드)의 집합으로 클래스가 구현해야하는 기능을 명세한다.
  • 인터페이스에 정의된 메서드는 모두 추상적이며 실제 구현은 없다.
  • 다형성을 높이고, 코드의 일관성을 유지할 수 있다.

인터페이스와 추상클래스 비교

비교 항목인터페이스 (Interface)추상 클래스 (Abstract Class)
목적기능을 명세하고 구현을 강제하여 다형성을 높임공통 속성 및 기능을 공유하고, 확장을 위한 기반 제공
메서드기본적으로 추상 메서드만 포함 (defaultstatic 제외)추상 메서드와 일반 메서드 모두 포함 가능
필드상수만 가능 (public static final이 자동으로 적용)일반 변수와 상수 모두 가능
다중 상속여러 개의 인터페이스를 구현 가능하나의 추상 클래스만 상속 가능
접근 제어자모든 메서드와 필드는 기본적으로 public필드와 메서드에 접근 제어자 설정 가능
상속 관계"can do" 관계를 표현하며, 구현하는 클래스가 기능을 제공해야 함"is-a" 관계를 표현하며, 상속받는 클래스가 부모의 특성을 가짐

인터페이스와 추상 클래스의 사용 시점

  • 인터페이스 사용 시점
    • 여러 클래스가 공통 기능을 구현해야 할 때 (ex. Runnable, Comparable 인터페이스)
    • 클래스가 다양한 기능을 혼합하여 구현해야 할 때 (다중 구현이 필요할 때)
    • 특정 기능을 제공하는 클래스를 모아서 공통된 타입으로 처리할 때
  • 추상 클래스 사용 시점
    • 공통 속성이나 메서드를 공유하면서, 일부 기능만 자식 클래스에서 구현하도록 할 때
    • “is-a” 관계를 명확히 나타낼 때 (ex. Vehicle 추상 클래스 → Car, Bike 클래스)
    • 기본 동작을 정의하면서 일부만 자식 클래스가 오버라이드하도록 할 때

final 키워드란?

final 키워드는 Java에서 변경되지 않도록 고정하고 싶을때 사용되는 키워드

변수, 메서드, 클래스에 붙일 수 있고 각각 다른의미를 지닌다.

1. 변수에 final을 붙이는 경우

변수에 붙이면 한 번 값이 정해졌을때 절대로 바꿀 수 없게 된다. ⇒ 상수처럼 작동

final int MAX_SCORE = 100;

만약 바꾸려고 하면 컴파일 오류가 발생한다.

2. 메서드에 final을 붙이는 경우

메서드에 붙이면 이 메서드를 자식 클래스가 오버라이드하지 못한다. (재정의 X)

class Animal {
    final void makeSound() {
        System.out.println("소리나요.");
    }
}

class Dog extends Animal {
    // 여기서 makeSound() 메서드를 재정의하려고 하면 오류가 발생함 !!
}

final이 붙은 메서드는 그대로 사용해야 하며, 내용을 바꾸는 것이 불가능하다.

→ 메서드가 원래 의도대로 안전하게 사용되도록 보장할 수 있음

3. 클래스에 final을 붙이는 경우

클래스에 붙이면 그 클래스는 다른 클래스가 상속할 수 없게 된다.

→ 이 클래스가 마지막 클래스가 되는 것 !

final class Vehicle {
    // 이 클래스는 상속할 수 없다.
}

class Car extends Vehicle {
    // 이렇게 상속하려고 하면 오류가 발생 !! ! !!
}

final 클래스는 더 이상 확장되지 않도록 보호되기 때문에, 클래스를 그대로 사용해야 하고, 자식 클래스를 만들 수 없다.

profile
어제의 나보단 나은 오늘의 내가 되기를

0개의 댓글