자바 기본

|

JAVA 프로그래밍 면접 이렇게 준비한다 - 한빛미디어를 학습하며 공부한 것을 정리한 내용입니다.

소개

  1. 원시 타입

  2. 객체란 무엇인가?

  3. final 키워드의 역활


  1. 원시 타입
  • Boolean, int, double 같은 각각의 기본 타입은 원시 타입이라고 알려져 있다.
    JVM은 이들을 객체라고 알려진 참조 타입과는 다른 방식으로 다룬다.
    원시타입들은 항상 값이 있는 상태, 즉 Null이 될 수 없기 때문이다.
  1. 객체란 무엇인가?
  • 상태와 행위의 모음. 원시 타입을 제외하면 자바에서 모든 변수들은 참조 타입이다. 객체는 여러 가지 의미에서 원시 타입과 다르며 가장 중요한 차이점은 빈 객체를 의미하는 표현인 null이 존재한다는 것이다. 즉, 변수들은 null로 설정될 수 있으며 메서드 또한 null을 반환할 수 있다. 그러나 Null 참조에 대한 메서드를 호출할 수는 없다. 호출하려고 하면 NullPointerException이 발생한다.

객체가 ‘참조 타입이다’라는 말이 정확히 무슨 의미인지 살펴보자.

/* 원시 타입 */
int i = 42; // 변수 i에 42라는 값이 할당된다.
int j = i;  // 변수 j에 i와 같은 42라는 값을 할당한다.
j = 50; // j의 값을 50으로 변경

System.out.println(i); // 42
System.out.println(j); // 50

/* 참조 타입 */
List<Integer> list = new ArrayList(); // ArrayList를 생성하고 주소값을 list에 할당한다.
list.add(1);

List<Integer> list2 = list; // list2에 ArrayList의 주소값을 할당한다.
list2.add(2);

System.out.println(list.size()); // list와 list2는 같은 주소값을 가지므로 list.size()의 반환 값은 2이다.

  1. final 키워드의 역활
  • 변수를 선언하고 값을 할당하고 나면 메모리 위치가 변경되지 않는다.
    객체에 선언하는 final 키워드는 원시 타입에 선언하는 final 키워드와 동일한 역활을 한다.
    단, 객체 내부의 값들은 개별 값들이 final이 아니라면 변경할 수 있다.

    final int num = 1;
    num = 2; // 컴파일 에러. 할당이 되고 난 뒤에는 변경이 불가능하다.
        
    final List<Integer> list = new ArrayList<>();
    list = new ArrayList<>(); // // 컴파일 에러. 할당이 되고 난 뒤에는 변경이 불가능하다.

자바 기본(2)

|

JAVA 프로그래밍 면접 이렇게 준비한다 - 한빛미디어를 학습하며 공부한 것을 정리한 내용입니다.

소개

  1. 메서드와 변수에 사용되는 static 키워드의 역활은 무엇인가?
  2. String과 interning

  1. 메서드와 변수에 사용되는 static 키워드의 역활은 무엇인가?

Class Person{

  // 정적 변수
  public static List<String> nationality = new ArrayList();
  
  // 정적 메셔드  
  public static void staticMethod(){
   ...
  }

}

static 키워드가 붙은 변수는 정적 변수, static 키워드가 붙은 메서드는 정적 메서드라 한다.

  • 정적 변수와 정적 메서드는 클래스가 로딩될때 static 영역에 할당되어 프로그램 종료시까지 객체 생성없이 접근이 가능하다.
  • 클래스.정적변수, 클래스.정적메서드() 로 접근하고 사용한다.
    • ex) Person.nationality, Person.staticMethod()
  • 프로그램 종료 시까지 static 영역에 존재하므로 정적 변수와 정적 메서드를 남발한다면 메모리를 낭비시킬 수 있다.

정적 변수와 정적 메서드는 클래스 내부에 정의하지만 인스턴스에는 속하지 않는다.

  • 정적 변수를 사용하는 이유 모든 인스턴스에서 공통으로 사용하는 변수를 정적 변수로 선언하고 사용한다면 객체 생성시마다 중복된 값이 생성되어 발생하는 메모리 낭비를 막을 수 있다.

  • 정적 메서드를 사용하는 이유 정적 변수를 사용하려면 메서드를 정적 메서드로 선언해야 하며, 자주 사용하는 Util 성격의 클래스들을 정적 메서드로 선언하면 인스턴스화 없이 편하게 사용가능 하다.



Class Person{

  // 정적 변수가 아니라면 Person 객체가 인스턴스화 될때마다 nationality가 생성되어 메모리 낭비가 발생할 겻이다.
  public List<String> nationality = new ArrayList();
  
  // 정적 메셔드  
  public static void staticMethod(){
   ...
  }

}

  1. String과 interning

String 객체를 생성하는 방법은 2가지가 있다.

  1. 리터럴을 이용하는 방식.

  2. new 연산자를 이용하는 방식.

두 가지 방법의 차이는 무엇일까?

리터럴을 이용하여 String 객체를 생성하면 string constant pool이라는
Heap 영역 중에 특별한 곳에 저장되고, new 연산자를 이용하여 객체를 생성하면 Heap 영역에 생성된다.

’==’ 연산자와 ‘equals()’ 메서드를 이용하여 구체적으로 살펴보자.

’==’ 연산자는 물리적인 메모리 주소가 같은 지 판별하는 연산자이고, String 클래스의 ‘equals()’ 메서드는 문자열이 같은 지 판별하는 메서드이다.


Class Main{

    public static void main(String[] args) {
        String literal = "Cat";          // 리터럴을 이용한 방식
        String obj = new String("Cat");  // new 연산자를 이용한 방식

        System.out.println(literal == obj);         //false
        System.out.println(literal.equals(obj));    //true

    }
}

  • 결과 string constant pool에 생성된 literal과 일반 객체처럼 Heap 영역에 생성된 obj의 주소 값은 다르므로 == 연산 결과는 false일 수 밖에 없으며, equals()의 결과는 true이다.

  • intern() 메서드 String을 literal로 선언할 경우 내부적으로 intern() 메서드를 호출하게 된다. intern() 메서드는 주어진 문자열이 string constant pool에 존재하는 지 확인하고 존재한다면 그 주소값을 반환하고 없다면 string constant pool에 넣고 새로운 주소값을 반환하게 된다.


Class Main{

    public static void main(String[] args) {
        String literal = "String";          // 리터럴을 이용한 방식
        String obj = new String("String");  // new 연산자를 이용한 방식
        String intern = obj.intern();

        System.out.println(literal == obj);         //false
        System.out.println(literal == intern);      //true
        System.out.println(literal.equals(obj));    //true
        System.out.println(literal.equals(intern)); //true

        System.out.println(obj == intern);          //false
        System.out.println(obj.equals(intern));     //true
    }
}

ref. https://www.journaldev.com/797/what-is-java-string-pool
ref.https://medium.com/@joongwon/string-%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0-57af94cbb6bc

캡슐화와 은닉화

|

객체지향 프로그래밍의 특성 중 하나인 캡슐화에 대해 찾아보다가 캡슐화에 대한 설명과 예제가 잘 정리되어 있는 블로그를 발견하여 그 내용을 토대로 정리 해보고자 한다.

캡슐화는 무엇인가 ?

  • 캡슐화(영어: encapsulation)는 객체 지향 프로그래밍에서 다음 2가지 측면이 있다.
    • 객체의 속성(data fields)과 행위(메서드, methods)를 하나로 묶고,
    • 실제 구현 내용 일부를 외부에 감추어 은닉한다.

객체의 속성과 행위를 하나로 묶으면 응집도가 높아져서 좋다는 건 직관적으로 알겠는데 실제 구현 내용 일부를 외부에 감추는 은닉화는 굳이 왜 하는걸까?

예를 보며 이해해보자.

요구사항

    1. ‘Account’ 클래스는 은행 계좌 클래스다.
    1. getBalance() 메서드로 한화/달러 잔액을 확인 할 수 있다.
    1. CountryCode는 추가될 수 있다.

 public class Account {
    
    private String accountName;
    private String accountNumber;
    private int balance;

    public Account(String accountName, String accountNumber, int balance){
        this.accountName = accountName;
        this.accountNumber = accountNumber;
        this.balance = balance;
    }

    public String getAccountName(){
        return accountName;
    }

    public String accountNumber(){
        return accountNumber;
    }

    public int getBalance(CountryCode countryCode) {
        switch (countryCode){
            case KR:
                return balance;
            case EN:
                return balance / 1000; // 1$ = 1000원인 경우
            default:
                return 0;
        }
    }

    public void add(int money) {
        this.balance += money;
    }

    public int withdraw(int money){
        if(balance - money >= 0)
            this.balance -= money;
        else
            throw new IllegalArgumentException("not enough balance");

        return money;
    }
}

    public enum CountryCode{
        KR,EN
    }

은닉화는 크게 2가지로 나뉜다.

1) 필드 데이터의 은닉화

    private String accountName;
    private String accountNumber;
    private int balance;

필드 데이터의 접근제어자를 private으로 선언함으로써 필드데이터 조작을 막을 수 있다.

2) 기능(메서드)의 은닉화


   public int getBalance(CountryCode countryCode) {
        switch (countryCode){
            case KR:
                return balance;
            case EN:
                return balance / 1000; // 1$ = 1000원인 경우
            default:
                return 0;
        }
    }

사용자 입장에서 getBalance()만 호출하면 나라별 환율에 맞는 잔액을 알 수 있다.
1) 국가가 추가되는 경우
2) 환율이 변동하는 경우를 신경쓸 필요가 없으므로
추후에 변경사항이 생기더라도 Account 클래스 내부만 수정하면 된다.

따라서 결합도를 낮추고 코드의 재사용성을 높일 수 있다는 장점이 있다.

결론

캡슐화를 하게 되면 내부에 데이터를 어떻게 저장하는 지, 그 데이터를 어떻게 처리하는 지, 또는 특정 기능을 어떻게 제공하는 지에 대한 내용은 드러내지 않는다. 단지, 객체가 어떤 기능을 제공하는 지만 공유하게 된다.

ref. https://cheese10yun.github.io/encapsulation-part-1/
ref. https://javacan.tistory.com/entry/EncapsulationExcerprtFromJavaBook
ref. https://ko.wikipedia.org/wiki/%EC%BA%A1%EC%8A%90%ED%99%94

String 뒤집기

|
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class StringReverse {

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine();
        System.out.println(reverseWithStringBuilder(s));
        System.out.println(reverseWithOtherEnd(s));
    }

    // StringBuilder를 이용한 방법
    // 가장 간단한 알고리즘이나 문자열이 커질 경우
    // String과 StringBuilder를 사용하여 비효율적이다.
    public static String reverseWithStringBuilder(String s){
        StringBuilder sb = new StringBuilder(s.length());
        for(int i=s.length()-1; i>=0; i--){
            sb.append(s.charAt(i));
        }
        return sb.toString();

    }

    // 반대편 문자열과 치환하는 알고리즘
    // StringBuilder만을 사용하므로 효율적이다.
    public static String reverseWithOtherEnd(String s){
        final StringBuilder sb = new StringBuilder(s);
        for(int i=0; i<sb.length()/2; i++){
            final char tmp = sb.charAt(i);
            final int otherEnd = sb.length()-i-1;
            sb.setCharAt(i,sb.charAt(otherEnd));
            sb.setCharAt(otherEnd,tmp);
        }
        return sb.toString();
    }

}

FizzBuzz

|


import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class FizzBuzz {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        System.out.println(fizzBuzz(n).toString());
        System.out.println(newFizzBuzz(n).toString());
    }

    public static List<String> fizzBuzz(int n){
        List<String> toReturn = new ArrayList<String>();

        for(int i=1; i<=n; i++){
            if(i%15 == 0)
                toReturn.add("FizzBuzz");
            else if(i%3 == 0)
                toReturn.add("Fizz");
            else if(i%5 == 0)
                toReturn.add("Buzz");
            else
                toReturn.add(String.valueOf(i));
        }
        return toReturn;
    }

    // 리팩토링을 통해 코드 재사용성을 높여보자.
    public static List<String> newFizzBuzz(int n){
        List<String> toReturn = new ArrayList<String>();
        for(int i=1; i<=n; i++){
            String word = toWord(3,i,"Fizz") + toWord(5,i,"Buzz");

            //apache commons lang3 라이브러리 사용
            if(StringUtils.isEmpty(word))
                toReturn.add(String.valueOf(i));
            else
                toReturn.add(word);
        }
        return toReturn;
    }

    public static String toWord(int divisor, int value, String word){
        return value % divisor == 0 ? word : "";
    }

}