개발야옹

[CS] IT(전산) 필기 시험 대비 Java 뿌시기👊🏻 본문

CS

[CS] IT(전산) 필기 시험 대비 Java 뿌시기👊🏻

kitez 2024. 10. 17. 23:46

IT(전산) 필기 시험 대비 Java 뿌시기👊🏻

1. Integer vs int

int

  • Primitive 자료형
  • 산술 연산이 가능하며, null값을 가질 수 없다.

Integer

  • Wrapper클래스(객체)
  • Unboxing을 하지 않으며 산술 연산이 불가능하지만, null값을 가질 수 있다.
  • Collection, null값이 필요한 경우 사용한다.

Integer와 int의 size 비교

  • Integer 및 int비여을 1,000,000개 생성한다.
  • 결과
    • Integer: 19986824 byte
    • int: 3998536 byte
    • 4.99배(약 5배)

 

2. == 와 equals()의 차이

== > 주소 비교

  • 비교를 위한 연산자
  • 비교하고자 하는 대상의 주소값을 비교한다.

equals() > 내용 비교

  • 메소드이며, 객체끼리 내용을 비교할 수 있다.
  • 비교하고자 하는 대상의 내용 자체를 비교한다.
String a = "a";
Stirng b = a;
String c = new String("a"); // 새로운 객체 생성. 주소가 다름.

// 주소값을 비교
a == b; // true
a == c; // false

// 내용(값)을 비교.
a.equals(b); // true
a.eqauls(c); // true

 

3. Call by value vs Call by Reference

자바는 Call by Value이다.

Primitive Type(원시 자료형)의 경우 Call by Value

  • int, short, long, float, double, char, boolean

Reference Type(참조 타입)의 경우 Call by Value

  • Array, 참조 타입

 

자바에서는 함수의 인자로 전달되는 타입이 기본형(원시 자료형)인 경우 값을 넘기게 되어있다. 이 경우 메모리에는 함수를 위한 별도의 공간이 생성된다. 이는 함수 종료시 사라진다. 따라서 함수 안에서 해당 인자의 값을 변경하더라도 원본 값은 바뀌지 않는 특징이 있다.

public class Test {
	public static void main(String[] args) {
    	int n = 10;
        System.out.println(n);
        test(10);
        System.out.println(n);
        // 값이 바뀌지 않는다는 것을 볼 수 있다.
    }
    
    public static void test(int n) {
    	n -= 5;
        System.out.println(n);
    }
}

// 결과
10
5
10

 

4. Java의 String에 관하여

Java에서 String은 굉장히 자주 사용되며, 두 가지 생성 방식이 있다.

  1. new연산자를 이용한 방식
  2. 리터럴을 이용한 방식

이 두 가지 방식에는 큰 차이점이 존재한다.

new를 통해 String 객체를 생성하면 Heap영역에 존재하게 된다.

리터럴을 이용할 경우, String. Constance Pool이라는 영역에 존재하게 된다.

public class StringMemory {
	public static void main(String[] args) {
    	String literal = "loper";
        String object = new String("loper");
        
        literal == object; // false
        literal.equals(object); // true
    }
}
  • == 연산의 결과는 false이다. == 연산자는 객체의 주소값을 비교하기 때문에 일반 객체처럼 Heap영역에 생성된 String 객체와 리터럴을 이용해 String Constant Pool에 저장된 String 객체의 주소값은 다를 수 밖에 없다.
  • equals() 메소드의 수행 결과는 true이다. 이는 문자열 내용을 비교하기 때문에 같은 문자열에 대해서 true를 반환하는 것이 맞다.

왜 이런 결과가 나올까?

이를 위해서는 동작 방식에 대한 이해가 필요하다. String을 리터럴로 선언한 경우, 내부적으로 String의 intern()이라는 메소드가 호출되게 된다.

  • intern(): 주어진 문자열이 String Constant Pool에 존재하는지 검색하고 있다면 그 주소값을 반환하고 없다면 String Constant Pool에 넣고 새로운 주소값을 반환하게 된다.
public class StringMemoryInternTest{
	public static void main(String[] args) {
    	String literal = "loper";
        String object = new String("loper");
        String intern = object.intern(); // 명시적으로 호출
        
        literal = object; // false
        literal.equals(object); // true
        
        literal == intern; // true
        literal.equals(intern); // true
    }
}

 

기존에 new를 통해 생성된 String 객체와 리터럴로 생성된 String 객체를 == 연산하였을 경우, false를 반환했지만 new를 통해 생성된 String객체의 intern() 메소드를 호출하여 새로운 String 객체인 intern에 대입할 경우, 리터럴로 생성된 String 객체와 == 연산시 true를 반환하게 된다.

 

5. String, StringBuilder, StringBuffer 파이

[String]

  • Immutable하기 때문에 + 등 concat연산 시 원본을 변경하지 않고 새로운 String 객체를 생성한다. 이로 인해 메모리 공간의 낭비가 발생하고 성능이 떨어진다. 
  • JDK1.5 이후부터는 컴파일 타임에 StringBuilder로 변경한다고 한다.
  • 불변 객체이기 때문에 멀티 쓰레드 환경에서 동기화를 신경쓰지 않아도 된다.
  • 문자열 연산이 적고, 조회가 많은 상황에서 쓰기 좋다.

[StringBuilder, StringBuffer]

  • 공통점
    • String과 다르게 Mutable한 객체이다.
    • 따라서 문자열 연산 시 새롭게 객체를 생성하지 않고, 처음에 만든 객체를 이용해 연산하고 크기를 변경시켜 문자열을 변경한다.
    • 따라서 문자열 연산이 자주 발생하는 상황에서 성능적으로 유리하며 쓰기 좋다.
  • 차이점
    • StringBuilder: Thread-Safe 하지 않다. 멀티 쓰레드 지원하지 않음.
    • StringBuffer: Thread-Safe하다. 멀티 쓰레드 지원함.
    • 죽, 동기화 지원의 유무

StringBuilder는 동기화를 고려하지 않는 상황에서 사용.(Thread를 사용하지 않는 상황). 문자열 연산이 많은 싱글 쓰레드 환경

StringBuffer는 동기화가 필요한 멀티 쓰레드 환경에서 사용. 문자열 연산이 많은 멀티 쓰레드 환경

 

6. final 키워드

  • final class
    • 다른 클래스가 상속받지 못한다.
  • final method
    • 자식 클래스에서 상위 클래스의 final mehtod를 오버라이드 하지 못한다.
  • final variable
    • 변하지 않는 상수 값이 되어 새롭게 값을 할당할 수 없는 변수가 된다.

7. non-static 멤버와 static 멤버의 차이

  • non-static 멤버
    • 공간적 특성: 해당 멤버는 객체마다 별도로 존재한다.
      • 인스턴스 멤버라고 부른다.
    • 시간적 특성: 객체 생성 시에 멤버가 생성된다.
      • 객체가 생성될 때, 멤버가 생성되므로 객체 생성 후에 멤버 사용이 가능
      • 객체가 사라지면 해당 멤버도 사라진다.
    • 공유의 특성: 공유되지 않는다.
      • 멤버는 객체 내에 각각 독립된 공간을 유지하므로 공유되지 않는다.
  • static 멤버
    • 공간적 특성: 해당 멤버는 클래스 당 하나만 생성된다.
      • 해당 멤버는 객체 내부가 아닌 별도의 공간에 생겅된다.
      • 클래스 멤버라고 부른다.
    • 시간적 특성: 클래스 로딩 시에 멤버가 생성된다.
      • 객체가 생성되기 전에 이미 생성되므로 객체를 생성하지 않고도 사용 가능.
      • 객체가 사라져도 해당 멤버가 사라지지 않는다.
      • 해당 멤버는 프로그램이 종료될 때, 사라진다.
    • 공유의 특성: 동일한 클래스의 모든 객체들에 의해 공유된다. (하나의 클래스로부터 생성된 여러 객체가 공유한다.)

8. 객체지향 프로그래밍의 개념

객체 지향 프로그래밍은 OOP(Object Oriented Programming)이라고도 한다.

프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체를 만들고 그 객체들 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법이.

[장점]

  • 코드의 재사용성이 높다.
    • 누군가가 만든 클래스를 가져와 사용할 수 있고 상속을 통해 확장할 수도 있다.
  • 유지보수가 쉽다.
    • 수정해야 할 부분이 클래스 내부에 멤버 변수 혹은 메소드로 존재하기 때문에 해당 부분만 수정하면 된다.
  • 대형 프로젝트에 적합하다.
    • 클래스 단위로 모듈화시켜서 개발할 수 있으므로 업무 분담하기가 쉽다.

[단점]

  • 처리 속도가 상대적으로 느리다.
  • 객체가 많으면 용량이 커질 수 있다.
  • 설계 시 많은 노력과 시간이 필요하다.

객체 지향 프로그래밍의 특징(추캡상다)

  • 추상화
    • 불필요한 정보는 숨기고 필요한 정보만을 표현함으로써 공통의 속성이나 기능을 묶어 이름을 붙이는 것이다.
  • 캡슐화
    • 속성과 기능을 정의하는 멤버 변수와 메소드를 클래스라는 캡슐에 넣는 것이다. 즉, 관련된 기능(메소드)과 속성(변수)을 한 곳에 모으고 분류하기 때문에 재활용이 원활하다.
    • 목적: 코드를 수정 없이 재활용 하는 것
    • 또한, 캡슐화를 통해 정보 은닉이 가능하다.
  • 상속
    • 부모 클래스의 속성과 기능을 그대로 이어 받아 사용할 수 있게 하고 기능의 일부분을 변경해야 할 경우, 상속 받은 자식 클래스에서 해당 기능만 다시 수정(정의)하여 사용할 수 있게 하는 것이다.
    • 상속을 통해서 클래스를 작성하면 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있다.
    • 또한, 코드를 공통적으로 관리하여 코드 추가 및 변경이 용이하다.
  • 다형성
    • 하나의 변수며으 함수명 등이 상황에 따라서 다른 의미로 해석될 수 있는 것이다.
    • 즉, 오버라이딩, 오버로딩이 가능하다.

객체 지향 설계 원칙

  1. SRP(Single Responsibility Principle): 단일 책임 원칙
    • 클래스는 단 하나의 책임을 가져야 하며, 클래스를 변경하는 이유는 단 하나의 이유여야 한다.
  2. OCP(Open Clsoed Principle): 개방 폐쇄 원칙
    • 확장에는 열려있고, 변경에는 닫혀있어야 한다.
  3. LSP(Liskov Substitution Principle): 리스코프 치환 원칙
    • 상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.
  4. ISP(Interface Segregation Principle): 인터페이스 분리 원칙
    • 인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.
  5. DIP(Dependency Inversion Principle): 의존 역전 원칙
    • 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다.

 

9. 오버라이딩 vs 오버로딩

오버라이딩과 오버로딩은 자주 나오면서도 중요한 개념이다. 하지만, 그만큼 잘 까먹기 때문에 정리하고 넘어가려 한다.

  • 오버라이딩: 상위 클래스가 가지고 있는 메소드를 하위 클래스에서 재정의해서 사용하는 것을 의미한다.
    • 상속 시, 상위 클래스의 private멤버를 제외한 모든 멤버를 상속받는다.
  • 오버로딩: 같은 이름의 메소드를 여러 개 가지면서 매개변수의 타입과 개수를 다르게 하여 정의하는 것을 의미한다. 즉, 메소드의 시그니처를 다르게 하여 정의하는 것이다.
구분 오버로딩 오버라이딩
메소드 이름 동일 동일
매개변수, 타입 다름 동일
반환 타입 상관 없음 동일

 

 

10. 접근 제어 지시자

  • public: public으로 선언된 멤버는 어떤 클래스에서라도 접근이 가능하다. public 메소드는 private멤버와 프로그램 사이의 인터페이스 역할을 수행하기도 한다.
  • protected: protected멤버를 포함하는 클래스가 정의되어 있는 해당 패키지 내 그리고 해당 클래스를 상속 받은 외부 패키지의 자식 클래스에서 접근이 가능하다.
  • private: private으로 선언된 멤버는 해당 멤버를 선언한 클래스에서만 접근이 가능하다. public메소드를 이용한다면 해당 객체의 private한 멤버에 접근이 가능하다.
  • Defulat(package private): 같은 클래스의 멤버와 해당 클래스가 정의되어 있는 패키지 내에서만 접근이 가능하다.

참고

  1. private의 경우
    • Private멤버나 메소드를 가지고 있는 클래스를 A 라고 하자.
    • 그리고 B라는 클래스가 A를 상속받는다. 이 경우, B 클래스는 Protected로 선언된 멤버 혹은 메소드에 접근이 가능하다.
    • 따라서 상속을 받더라도 private한 멤버에는 접근이 불가능 하다.
    • 대신, public 메소드를 통해 getter를 만들면 private 멤버를 사용할 수 있다.
  2. protected의 경우
    • Protected 멤버나 메소드를 가지고 있는 클래스를 A라고 하자.
    • 마찬가지로 B라는 클래스가 A를 상속받는다. 이 경우, B클래스는 protected로 선언된 멤버 혹은 메소드에 접근이 가능하다.
    • B라는 클래스가 다른 패키지에 선언되었을지라도 A클래스이 멤버에 접근이 가능하다.
    • 하지만, 다른 패키지의 A클래스를 상속받지 않은 클래스는 A 클래스의 멤버에 접근할 수 없다. 마치 private처럼.

 

11. 직렬화

메모리 내에 존재하는 정보를 보다 쉽게 전송 및 전달하기 위해서 byte 코드 형태로 나열하는 것을 말한다. 

직렬화가 무엇인가?

  • 자바 직렬화란 자바 시스템 내부에서 사용된느 객체 또는 데이터를 외부으 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터를 변환하는 기술과 바이트로 변환된 데이터를 다시 객체로 변환하는 기술(역직렬화)을 아울러서 이야기 한다.
  • 시스템적으로 이야기하면, JVM(Java Virtual Machine이하 JVM)의 메모리에 상주(힙 또는 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술과 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태를 같이 이야기 한다.

 

12. 추상 클래스란?

  • 추상 클래스는 미완성된 클래스이다.
  • 미완성된 클래스는 미완성된 메소드인 추상 메소드를 포함하고 있다.
  • 추상 클래스는 혼자로는 클래스의 역할을 다 못하지만, 새로운 클래스를 작성하는 데 있어 그 바탕이 되는 부모 클래스로서의 중요한 의미를 갖는다. 왜냐하면 클래스를 작성함에 있어서 어느정도 작성된 상태에서 시작할 수 있기 때문이다.
  • 클래스 앞에 abstract 키워드를 붙인다.
abstract class Car {
	abstract void accelrate();
}

 

[추상 클래스의 목적]

  • 기존의 클래스에서 공통된 부분을 추상화하여 상속하는 클래스에게 구현을 강제화한다. 메소드의 동작은 구현하는 자식 클래스에게 위임한다.
  • 공유의 목적을 갖고 있다.

[추상 클래스의 특징]

  • 추상 클래스는 추상 메소드가 아닌 일반 메소드, 멤버도 포항할 수 있다. 하지만 추상 메소드를 하나라도 포함하고 있다면 추상 클래스로 선언해야 한다.
  • 추상 클래스는 동작이 정의되어 있지 않은 추상 메소드를 포함하고 있으므로 인스턴스를 생성할 수 없다.
abstract class Animal {
	abstract void cry();
}

class Cat extends Animal {
	@Override
    void cry() {
    	System.out.println("냐오옹");
    }
}

class Dog extends Animal {
	@Override
    void cry() {
    	System.out.println("멍머어엉");
    }
}

public class Test {
	public static void main(String[] args) {
    	// Animal animal = new Animal();
        // 추상 클래스는 자체적으로 인스턴스를 생성할 수 없다. 불완전하기 때문에
        
        Cat cat = new Cat();
        Dog dog = new Dog();
        
        cat.cry();
        dog.cry();
    }
}

 

추상 클래스인 Animal은 추상 메소드인 cry()를 가지고 있으며, Animal 클래스를 상속받는 자식 클래스인 Dog, Cat클래스는 cry()메소드를 오버라이딩해야만 인스턴스를 생성할 수 있다.

  • 추상 메소드
    • 선언부만 작성하고 구현부는 작성하지 않는 메소드이며, 앞에 abstract 키워드를 붙인다.
    • 구현부를 작성하지 않는 이유는 메소드의 내용이 상속받은 클래스에 따라 달라질 수 있기 때문이다.
    • 사용하는 목적은 추상 메소드를 포함한 클래스를 상속받는 자식 클래스가 반드시 추상 메소드를 구현하도록 강제하기 위함이다.
    • 추상 클래스를 상속받은 자식 클래스는 오버라이딩을 통해 조상인 추상 클래스의 추상 메소드를 모두 구현해야 한다.
    • 만약, 자식 클래스에서 추상 메소드를 하나라도 구현하지 않는다면 자식 클래스 역시 추상 클래스로 지정해야 한다.

13. 추상 클래스와 인터페이스의 차이점

  • 인터페이스
    • 클래스가 아니며, 클래스와 관련이 없다.
    • 추상 메소드와 상수만을 멤버로 가진다.
    • 한 개의 클래스가 여러 인터페이스를 구현할 수 있다. (다중 구현 가능.)
    • Java 8부터 default 메소드가 추가되었다.
      • default 키워드가 붙은 메소드는 구현할 수 있으며(일반 메소드처럼), 자식 클래스에서는 이를 오버라이딩할 수 있다.
      • 인터페이스가 변경되면 이를 구현하는 모든 클래스들이 해당 메소드를 다시 구현해야하는 번거로운 문제가 있었다. 이런 문제를 해결하기 위하여 인터페이스에 메소드를 구현할 수 있도록 변경되었다.
    • Java 8부터 static 메소드가 추가되었다.
      • 인터페이스에 static 메소드를 선언 가능하게 함으로써, 간단한 기능을 가지는 유틸리티성 인터페이스를 만들 수 있게 되었다.
    • 목적 : 구현 객체의 같은 동작을 보장하기 위해 사용한다.
  • 추상 클래스
    • 클래스이며, 클래스와 관련이 있다. (주로 베이스 클래스로 사용)
    • 추상 메소드 및 일반 메소드와 멤버도 포함할 수 있다.
    • 한 개의 클래스가 여러 개의 클래스를 상속받을 수 없다. (다중 상속 불가능.)
    • 상속을 받아 기능을 확장시키는 데 목적이 있다.
    • 목적 : 기존의 클래스에서 공통된 부분을 추상화하여 상속하는 클래스에게 구현을 강제화한다. 메소드의 동작은 구현하는 자식 클래스로 위임한다.
    • 공유의 목적.

14. 클래스와 인터페이스

  • 클래스
    • 어떤 문제를 해결하기 위한 데이터를 만들기 위해 추상화를 거쳐 집단에 속하는 속성과 행위를 변수와 메소드로 정의한 것이다.
  • 인스턴스
    • 클래스에서 정의한 것을 토대로 실제 메모리 상에 할당된 것으로 실제 프로그램에서 사용되는 데이터를 말한다.
    • 인스턴스는 독립된 메모리 공간에 저장된 자신만의 필드를 가질 수 있다. 하지만 해당 클래스의 모든 메소드는 해당 클래스로부터 생성된 모든 인스턴스가 하나의 메소드를 공유하게 되는 특징을 갖고 있다.

15. 값 타입 vs 참조 타입

가장 큰 차이점은 데이터가 저장되는 메모리 상의 위치이다.

편의상 값 타입은 원시 타입(Primitive Type)이라고 부르겠다. 참조 타입(Reference Type)

  • 원시 타입 : 크기가 작고 고정적이기 때문에 스택 영역에 저장된다.
  • 참조 타입 : 크기가 크고 가변적이기 때문에 동적으로 관리되는 힙에 저장된다.

이처럼 저장되는 위치의 차이는 여러 차이점을 만든다.

 

1. 메모리 및 접근 속도

  • 원시 타입
    • 선언시 스택에 즉시 생성되므로 선언 직후부터 데이터를 저장하는 용도로 사용할 수 있다.
  • 참조 타입
    • 선언에 의해 참조만 생성될 뿐, 데이터를 저장할 수 있는 실제 메모리가 할당된 것은 아니다. 따라서 선언 즉시 사용할 수 없다. 반드시 new 연산자로 메모리를 할당받아 초기화해야 한다.
    • '스택' 메모리에는 참조값만 존재하고 실제 값은 '힙' 메모리에 존재한다. 값을 필요로 할 때마다 언박싱 과정을 거쳐여 하므로 원시타입과 비교해서 접근 속도가 느려진다.

2. 소멸 시점

  • 원시 타입 : 변수를 선언한 메소드가 종료될 때 혹은 소속된 객체가 사라질 때, 소멸된다.
  • 참조 타입 : 더 이상 참조하는 변수가 없을 때 GC에 의해 제거된다.

3. 복사

  • 원시 타입 : 복사에 의해 별개의 복사본이 생성되며 복사 후 원본과 복사본은 별개의 변수이다. 완전히 다른 두 개의 변수가 생성되는 것이다.
  • 참조 타입 : 참조 타입끼리의 대입은 힙 영역에 존재하는 데이터를 참조하는 참조자가 하나 더 늘어날 뿐이다. 따라서 별도의 메모리가 추가로 할당되지 않는다. 그래서 둘 중 하나를 변경하면 다른 참조자는 같은 값을 참조하기 때문에 같이 변경된다. (해결하기 위해서는 Deep Copy 해야 한다.)

4. Null

  • 원시 타입 : Null 값을 저장할 수 없다.
  • 참조 타입 : Null 값을 저장할 수 있다.

결론
성능과 메모리에 장점이 있는 원시 타입을 먼저 고려해본다. 만약, Null을 다뤄야 하거나 제네릭 타입에서 사용되어야 한다면 참조 타입을 사용한다.

728x90