본문 바로가기

내배캠/TIL

Java 문법 종합반 1~3주

1. JVM, 변수

  • 바이트 코드 : 내가 작성한 코드가 운영체제가 읽을 수 있는 코드(바이트 코드)로 Java 컴파일러가 변환한 코드
  • compiler : .java → .class로 변환해줌
  • 인터프리터 : .class 코드 해석기. 운영체제가 읽은 바이트 코드를 기기가 실행할 수 있는 기계어로 번역
  • JIT 컴파일러(Just In Time) : 빠른 .class 코드 해석기. 인터프리터의 효율을 높여주는 서포터 해석기
  • 메모리 영역 : 운영체제로부터 JVM이 할당받은 메모리 영역
  • 클래스 로더 : .class 바이트 코드를 메모리 영역에 담는 운반기. JVM으로 class(바이트 코드)를 불러와 메모리에 저장함
  • 가비지 컬렉터

래퍼 클래스 변수

// 박싱 VS 언박싱

// 박싱
// Integer 래퍼 클래스 num 에 21 의 값을 저장
int number = 21;
Integer num = number;

// 언박싱
int n = num.intValue(); // 래퍼 클래스들은 inValue() 같은 언박싱 메서드들을 제공해줍니다.

클래스로 변수 관리하며 객체지향의 많은 기능 사용할 수 있기 때문에 사용.

 

참조형 변수

  • 다른 기본형 변수가 실제 값을 저장하는 저장 공간이라면 참조형 변수는 실제 값이 아닌 원본 값의 주솟값을 저장
  • 기본형 변수 : 원본 값이 Stack 영역에 있다.
  • 참조형 변수 : 원본 값이 Heap 영역에 있다.
    • Stack 영역에는 따로 저장 해둔 원본 값의 Heap 영역 주소를 저장한다.

📌 Stack 영역 vs Heap 영역

  • Stack의 경우에는 정적으로 할당된 메모리 영역
    • 그래서, 크기가 몇 byte 인지 정해져 있는 기본형 변수를 저장
    • 추가로, 크기가 정해져 있는 참조형 변수의 주솟값도 저장
  • Heap의 경우에는 동적으로 할당된 메모리 영역
    • 그래서, 크기가 계속 늘어날 수 있는 참조형 변수의 원본을 저장

 

비트&바이트

  • Bit
    • Bit(비트)는 0,1 형태의 2진수 데이터로써 컴퓨터가 저장(표현) 할 수 있는 최소 단위
    • 정수형 값은 10진수 숫자(0~10범위의 숫자)이며 2진수(0~1범위) Bit로 저장(표현)
    • 4개의 Bit로 16진수 숫자(0~F(16) 범위의 숫자)를 2진수(0~1범위) Bit로 저장(표현)
  • 1 Byte = 8 Bit
    • Byte(바이트)는 8개의 Bit(비트)로 구성
    • 1 Byte 내에서 숫자 변수는 Bit 2진수를 10진수로 저장(표현)
      • 10진수로는 0~255(2의8승)까지 저장(표현)
    • 1 Byte 내에서 문자 변수의 경우만 Bit 2진수를 16진수로 저장(표현)

 

자동 형변환

📌 변수 타입별 크기 순서

byte(1) → short(2) → int(4) → long(8) → float(4) → double(8)

byte byteNumber = 10;
int intNumber = byteNumber;    // byte -> int 형변환
System.out.println(intNumber); // 10

char charAlphabet = 'A';
intNumber = charAlphabet;   // char -> int 형변환
System.out.println(intNumber); // A의 유니코드 : 65

intNumber = 100;
long longNumber = intNumber; // int -> long 형변환
System.out.println(longNumber); // 100

intNumber = 200;
double doubleNumber = intNumber; // int -> double 형변환
System.out.println(doubleNumber); // 200.0  (소수점이 추가된 실수출력)

 


 

2. 연산자, 조건문, 반복문, 배열, 컬렉션

비트 연산

  • Bit의 자릿수를 옮기는 것
  • <<(왼쪽으로 자릿수옮기기), >>(오른쪽으로 자릿수옮기기)
  • 0, 1 은 2진수 값이기 때문에,
    • 자릿수를 왼쪽으로 옮기는 횟수만큼 2의 배수로 곱셈이 연산되는 것과 동일하다.
    • 자릿수를 오른쪽으로 옮기는 횟수만큼 2의 배수로 나눗셈이 연산되는 것과 동일하다.
// 3의 이진수값은 11(2). 12의 이진수값은 1100(2).
// (2) 표기는 이 숫자가 이진수값이라는 표식

System.out.println(3 << 2); 
// 3의 이진수값인 11(2) 에서 왼쪽으로 2번 옮겨져서 1100(2) 인 12값이 된다.

System.out.println(3 >> 1); 
// 3의 이진수값인 11(2) 에서 오른쪽으로 1번 옮겨져서 1(2) 인 1 값이 된다.

복사

얕은 복사

  • 주소값만 복사되고 실제 값은 1개로 유지되는 것.
  • 대입 연산자 = 사용

깊은 복사

  • 진짜 새로운 배열을 똑같이 만들고 싶을 때
  • 실제 값을 가지고 있는 배열의 기본형 값을 꺼내서 복사
// 1. clone() 메서드
int[] a = { 1, 2, 3, 4 };
int[] b = a.clone();
// 하지만, clone() 메서드는 2차원이상 배열에서는 얕은 복사로 동작
import java.util.Arrays;

public class Main {
	public static void main(String[] args) {
		// 2. Arrays.copyOf() 메서드
		int[] a = { 1, 2, 3, 4 };
		int[] b = Arrays.copyOf(a, a.length); // 배열과 함께 length값도 같이 넣어준다.
	}
}

 

컬렉션

List

  • LinkedList는 메모리에 남는 공간 요청해서 여기저기 나누어 실제 값을 담아 놓고, 실제 값이 있는 주소값으로 목록 구성하고 저장함.
  • 기본적인 기능은 ArrayList와 동일하지만 값을 나누어 담기 때문에 모든 값을 조회하는 속도가 느림. 대신 값을 중간에 추가하거나 삭제할 때는 속도가 빠름
  • 중간에 값을 추가하는 기능이 있음. linkedList.add({추가할 순번}, {추가할 값})

Set

  • 순서 없고 중복 없는 배열
  • HashSet : 가장 빠르며 순서를 전혀 예측할 수 없음
  • TreeSet : 정렬된 순서대로 보관하며 정렬 방법을 지정할 수 있음
  • LinkedHashSet : 추가된 순서, 또는 가장 최근에 접근한 순서대로 접근 가능

보통 HashSet 을 쓰는데 순서 보장이 필요하면 LinkedHashSet 을 주로 사용한다.

// Set 
// (사용하기 위해선 import java.util.Set; 와 java.util.HashSet; 를 추가.)
import java.util.HashSet;
import java.util.Set;

public class Main {

	public static void main(String[] args) {
		Set<Integer> intSet = new HashSet<Integer>(); // 선언 및 생성

		intSet.add(1);
		intSet.add(2);
		intSet.add(3);
		intSet.add(3); // 중복된 값은 덮어쓴다.
		intSet.add(3); // 중복된 값은 덮어쓴다.

		for (Integer value : intSet) {
			System.out.println(value); // 1,2,3 출력
		}

		// contains()
		System.out.println(intSet.contains(2)); // true 출력
		System.out.println(intSet.contains(4)); // false 출력

		// remove()
		intSet.remove(3); // 3 삭제

		for (Integer value : intSet) {
			System.out.println(value); // 1,2 출력
		}
	}
}

 

Map

  • key-value 형태로 저장, key 값 기준으로 value 조회 가능.
  • key 값은 중복 허용 X
  • HashMap : 중복을 허용하지 않고 순서를 보장하지 않음, 키와 값으로 null이 허용
  • TreeMap : key 값을 기준으로 정렬 가능. 다만, 저장 시 정렬(오름차순)을 하기 때문에 저장시간이 다소 오래 걸림
// Map 
// (사용하기 위해선 import java.util.Map; 를 추가)
import java.util.Map;

public class Main {

	public static void main(String[] args) {
		Map<String, Integer> intMap = new HashMap<>(); // 선언 및 생성

		//          키 , 값
		intMap.put("일", 11);
		intMap.put("이", 12);
		intMap.put("삼", 13);
		intMap.put("삼", 14); // 중복 Key값은 덮어쓴다.
		intMap.put("삼", 15); // 중복 Key값은 덮어쓴다.

		// key 값 전체 출력
		for (String key : intMap.keySet()) {
			System.out.println(key); // 일,이,삼 출력
		}

		// value 값 전체 출력
		for (Integer key : intMap.values()) {
			System.out.println(key); // 11,12,15 출력
		}

		// get()
		System.out.println(intMap.get("삼")); // 15 출력
	}
}

 


 

3. 클래스, 상속, 인터페이스

가변 길이의 매개변수

void carSpeeds(double ... speeds) {
    for (double v : speeds) {
        System.out.println("v = " + v);
    }
}
  • 매개값 , 로 구분하여 개수 상관없이 전달 가능
  • carSpeeds(100, 80);
  • carSpeeds(110, 120, 150);

this와 this()

this

  • 객체 즉, 인스턴스 자신을 표현하는 키워드
  • 객체 내부 생성자 및 메서드에서 객체 내부 멤버에 접근하기 위해 사용될 수 있음.
public Car(String model, String color, double price) {
    this.model = model;
    this.color = color;
    this.price = price;
}
  • 객체의 메서드에서 리턴 타입이 인스턴스 자신의 클래스 타입이라면 this를 사용하여 인스턴스 자신의 주소를 반환할 수도 있음.
Car returnInstance() {
    return this;
}

this()

  • 객체 즉, 인스턴스 자신의 생성자를 호출하는 키워드
  • 객체 내부 생성자 및 메서드에서 해당 객체의 생성자를 호출하기 위해 사용될 수 있음.
  • 생성자를 통해 객체의 필드를 초기화할 때 중복되는 코드를 줄여줄 수 있음.
public Car(String model) {
    this(model, "Blue", 50000000);  // 세 번째 생성자 호출
}

public Car(String model, String color) {
    this(model, color, 100000000);  // 세 번째 생성자 호출
}

public Car(String model, String color, double price) {
    this.model = model;
    this.color = color;
    this.price = price;
}

📌 this() 키워드를 사용해서 다른 생성자를 호출할 때는 반드시 해당 생성자의 첫 줄에 작성되어야 함. this() 키워드 이전에 코드 존재 시 오류 발생.

 

패키지와 import

  • 다른 패키지에 존재하는 클래스를 사용하고 싶을 때는
    • 클래스의 전체 경로를 적어 사용해주거나
    • package oop.main;
      
      public class Main {
          public static void main(String[] args) {
              oop.pk1.Car car = new oop.pk1.Car();
              car.horn(); // pk1 빵빵
      
              oop.pk2.Car car2 = new oop.pk2.Car();
              car2.horn(); // pk2 빵빵
          }
      }
      
    • import로 해당 클래스가 존재하는 패키지를 import 한다.
    • package oop.main;
      
      import oop.pk1.Car;
      
      public class Main {
          public static void main(String[] args) {
              Car car = new Car();
              car.horn(); // pk1 빵빵
      
              oop.pk2.Car car2 = new oop.pk2.Car();
              car2.horn(); // pk2 빵빵
          }
      }
      

상속

클래스 간의 관계

📌 클래스 간의 관계를 분석하여 관계 설정을 해줄 수 있다.

  • 상속관계 : is - a (”~은 ~(이)다”)
  • 포함관계 : has - a (”~은 ~을(를) 가지고 있다”)
  • 상속관계 : 고래는 포유류다 👍
  • 포함관계 : 고래는 포유류를 가지고 있다…? 🙅‍♀️
    • 자동차는 타이어를 가지고 있다. 👍
    • 자동차는 차 문을 가지고 있다. 👍
    • 자동차는 핸들을 가지고 있다. 👍

 

final 클래스와 final 메서드

  • 클래스에 final 키워드를 지정하여 선언하면 최종적인 클래스가 됨으로 더 이상 상속할 수 없는 클래스가 된다.
  • public final class Car {}
    
    ...
    
    public class SportsCar extends Car{} // 오류 발생.
    
  • 메서드에 final 키워드를 지정하여 선언하면 최종적인 메서드가 됨으로 더 이상 오버라이딩할 수 없는 메서드가 된다.
  • public class Car {
        public final void horn() {
            System.out.println("빵빵");
        }
    }
    
    ...
    
    public class SportsCar extends Car{
        public void horn() { // 오류 발생.
            super.horn();
        }
    }
    

super와 super()

  • super
    • 부모 클래스의 멤버를 참조할 수 있는 키워드
    • public void setCarInfo(String model, String color, double price) {
          super.model = model; // model은 부모 필드에 set
          super.color = color; // color는 부모 필드에 set
          this.price = price; // price는 자식 필드에 set
      }
      
  • super()
    • 부모 클래스의 생성자를 호출할 수 있는 키워드
    • 항상 부모 클래스의 생성자가 먼저 호출됨. 따라서 눈에 보이지 않지만 컴파일러가 super();를 자식 클래스 생성자 첫 줄에 자동으로 추가해 줌.
    • // 자식 클래스 SportsCar 생성자
      public SportsCar(String model, String color, double price, String engine) {
           // this.engine = engine; // 오류 발생
          super(model, color, price);
          this.engine = engine;
      }
      

다형성

  • 자동 타입 변환
    • 부모 타입 변수 = 자식 타입 객체;
    • 자식 객체는 부모 객체의 멤버를 상속받기 때문에 부모와 동일하게 취급될 수 있음.
    • 다만 부모 클래스에 선언된, 즉 상속받은 멤버만 접근 가능.
    • public class Main {
          public static void main(String[] args) {
              // 고래는 포유류이기 때문에 포유류 타입으로 변환될 수 있습니다.
              Mammal mammal = new Whale();
      
              // 하지만 포유류 전부가 바다에 살고 수영을 할 수 있는 것은 아니기 때문에
              // 수영 하다 메서드는 실행 불가
              // 즉, 부모 클래스에 swimming이 선언되어있지 않아서 사용 불가능합니다.
              // mammal.swimming(); // 오류 발생
      
              // 반대로 모든 포유류가 전부 고래 처럼 수영이 가능한 것이 아니기 때문에 타입변환이 불가능합니다.
              // 즉, 부모타입의 객체는 자식타입의 변수로 변환될 수 없습니다.
              // Whale whale = new Mammal(); // 오류 발생
      
              mammal.feeding();
          }
      }
      
  • 강제 타입 변환
    • 자식 타입 변수 = (자식 타입) 부모 타입 객체;
    • 부모 타입 객체는 자식 타입 변수로 자동으로 타입 변환되지 않음.
    • 무조건 강제 타입 변환을 할 수 있는 것은 아니다!
      • 자식 타입 객체가 부모 타입으로 자동 타입 변환된 후 다시 자식 타입으로 변환될 때만 강제 타입 변환이 가능
      • 부모 타입 변수로는 자식 타입 객체의 고유한 멤버를 사용할 수 없기 때문에 사용이 필요한 경우가 생겼을 때 강제 타입 변환을 사용
      public class Main {
          public static void main(String[] args) {
              // 자동 타입변환된 부모타입의 변수의 자식 객체
              Mammal mammal = new Whale();
              mammal.feeding();
      
              // 자식객체 고래의 수영 기능을 사용하고 싶다면
              // 다시 자식타입으로 강제 타입변환을 하면된다.
              Whale whale = (Whale) mammal;
              whale.swimming();
      
              Mammal newMammal = new Mammal();
              // Whale newWhale = (Whale) newMammal;
          }
      }
      
  • instanceof
    • 해당 클래스 객체의 원래 클래스명 체크
    • {대상 객체} instance of {클래스 이름}
    // 다형성
    
    class Parent { }
    class Child extends Parent { }
    class Brother extends Parent { }
    
    public class Main {
        public static void main(String[] args) {
    
            Parent p = new Parent();
    
            System.out.println(p instanceof Object); // true 출력
            System.out.println(p instanceof Parent); // true 출력
            System.out.println(p instanceof Child);  // false 출력
    
            Parent c = new Child();
    
            System.out.println(c instanceof Object); // true 출력
            System.out.println(c instanceof Parent); // true 출력
            System.out.println(c instanceof Child);  // true 출력
    
        }
    }
    

추상 클래스

  • 미완성된 설계도
  • abstract 키워드 사용
  • 추상 메서드를 하나라도 가지고 있는 클래스는 추상 클래스
  • 추상 메서드가 없어도 추상 클래스로 선언 가능
  • 추상 클래스는 여러 개의 자식 클래스들에서 공통적인 필드나 메서드를 추출해서 만들 수 있음.
  • 추상 메서드는 아직 구현되지 않은 미완성된 메서드. 정의만 할 뿐, 실행 내용 갖고있지 않음.
  • 상속받은 클래스에서 추상 메서드는 반드시 오버라이딩!

 

인터페이스

📌 인터페이스의 멤버

  • 모든 멤버 변수는 public static final
  • 모든 메서드는 public abstract
  • 생략되는 제어자는 컴파일러가 자동으로 추가해준다.

📌 인터페이스의 구현

  • 인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩 되어야 한다.
  • 만약 인터페이스의 추상 메서드를 일부만 구현해야 한다면 해당 클래스를 추상 클래스로 변경.

📌 인터페이스의 상속

  • 인터페이스 간의 상속이 가능.
  • 인터페이스 간의 상속은 implements 가 아니라 extends 키워드를 사용.
  • 인터페이스는 클래스와는 다르게 다중 상속이 가능.
public class Main implements C {

    @Override
    public void a() {
        System.out.println("A");
    }

    @Override
    public void b() {
				System.out.println("B");
    }
}

interface A {
    void a();
}
interface B {
    void b();
}
interface C extends A, B { }
  • 인터페이스 C가 A, B를 상속받고 인터페이스 C를 구현하므로 상속받은 a(), b() 메소드가 모두 오버라이딩 되어야 한다. 

 

디폴트 메서드와 static 메서드

  • 디폴트 메서드 : 추상 메서드의 기본적인 구현 제공.
  • 메서드 앞에 default 키워드 붙이며 블럭{} 존재.
  • static 메서드 : 사용 방법 동일
  • public class Main implements A {
    
        @Override
        public void a() {
            System.out.println("A");
        }
    
        public static void main(String[] args) {
            Main main = new Main();
            main.a();
            
            // 디폴트 메서드, 재정의 없이 바로 사용 가능.
            main.aa();
    
            // static 메서드 aaa() 호출
            A.aaa();
        }
    }
    
    interface A {
        void a();
        default void aa() {
            System.out.println("AA");
        }
        static void aaa() {
            System.out.println("static method");
        }
    }
    

다형성

  • 자동 타입 변환
    • 인터페이스 변수 = 구현객체;
  • 강제 타입 변환
    • 구현 객체 타입 변수 = (구현 객체 타입) 인터페이스 변수;
public class Main {
    public static void main(String[] args) {

        // A 인터페이스에 구현체 B 대입
        A a1 = new B();
        a1.a();
        // a1.b(); // 불가능

        System.out.println("\\nB 강제 타입변환");
        B b = (B) a1;
        b.a();
        b.b(); // 강제 타입변환으로 사용 가능
        System.out.println();

        // A 인터페이스에 구편체 B를 상속받은 C 대입
        A a2 = new C();
        a2.a();
        //a2.b(); // 불가능
        //a2.c(); // 불가능

        System.out.println("\\nC 강제 타입변환");
        C c = (C) a2;
        c.a();
        c.b(); // 강제 타입변환으로 사용 가능
        c.c(); // 강제 타입변환으로 사용 가능

    }
}

interface A {
    void a();
}
class B implements A {
    @Override
    public void a() {
        System.out.println("B.a()");
    }

    public void b() {
        System.out.println("B.b()");
    }
}
class C extends B {
    public void c() {
        System.out.println("C.c()");
    }
}

 

'내배캠 > TIL' 카테고리의 다른 글

N의 약수 찾기 알고리즘  (0) 2024.07.25
Java 문법 종합반 4~5주  (0) 2024.07.24
SQL 정리 2  (7) 2024.07.22
SQL 정리 1  (1) 2024.07.22
24. 07. 19 - 웹 기초 특강  (0) 2024.07.19