[24.09.28] TIL

yy·2024년 9월 27일

개발일지

목록 보기
112/122

JAVA 공부중

Scanner vs BufferedReader / BufferedWriter

사용자의 입력을 받을 때 사용


Scanner

scanner로 사용자의 입력을 받아 사용하고 있었는데 이건 입출력 방식이 느리면 여러 줄을 입력받을 때 시간초과가 날 수 있다고 한다.

Scanner scanner = new Scanner(System.in);
String i = scanner.nextLine(); //문자
int i = scanner.nextInt(); //숫자

또한 System.out.println(); 도 출력 시 시간초과를 낼 수 있다고 함.

System.out.println();

시간초과를 막기위해서 좀 더 빠른 입력시 bufferedReader와 출력시 bufferedWriter를 쓴다고 함.



BufferedReader / BufferedWriter

  • 입력 시 : BufferedReader (scanner와 비슷)
  • 출력 시 : BufferedWriter (System.out.println()과 비슷)

//BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int t = Integer.parseInt(br.readLine()); //숫자로 쓸거면 형변환 필수

//BufferedWriter
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

bw.write(Integer.toString(num1 + num2)); // 내보낼때도 toString으로 바꿔서 해줘야함. 안그럼 이상한 문자가 출력된다.
bw.newLine(); //줄바꿈

bw.flush(); //남아있는 데이터 모두 출력
bw.close();

  • BufferedReader로 입력받은 데이터는 무조건 문자열이라서 숫자로 쓰기위해서는 형변환을 해줘야한다.
  • BufferedReader는 줄바꿈을 통해서 데이터가 입력되기 때문에 공백을 이용해서 데이터를 입력받기 위해서는 다른게 필요하다. StringTokenizer

//StringTokenizer
StringTokenizer st = new StringTokenizer(br.readLine()); //BufferedReader로 받은걸 넣서 쓰면 됨.
int num1 = Integer.parseInt(st.nextToken()); //이것도 형변환 필수
int num2 = Integer.parseInt(st.nextToken())

참고 블로그
https://m.blog.naver.com/ka28/221850826909
https://rlakuku-program.tistory.com/33



Object 클래스

  • 모든 클래스의 최상위 부모 클래스는 항상 Object 클래스.
  • extends로 표시를 안해도 묵시적으로 상속이 되어있는 상태.
  • 고로 다형성(다형적 참조, 메서드 오버라이딩)을 이용할 수 있음.
  • 자식들의 메서드를 부모가 다운캐스팅해서 호출이 가능하지만 그것보다 부모의 메서드를 메서드 오버라이딩을 활용하면 좋다.

toString() : 객체의 정보 조회

  • 사실 println을 사용했을때와 toString을 사용했을때 객체의 정보를 조회할 수 있다는 점이 같다. 왜냐면 println 내부에 toString이 들어있기 때문. 그래서 toString 을 오버라이딩하게 되면 println을 했을 때도 오버라이딩된 걸 사용할 수 있다.

  • IDE를 이용하면 쉽게 toString 오버라이딩할 수 있다. (인텔리제이 윈도우 Alt+insert 이용)

public class Dog {
    private String dogName;
    private int age;

    public Dog(String dogName, int age) {
        this.dogName = dogName;
        this.age = age;
    }

    @Override 
    public String toString() {
        return "Dog{" +
                "dogName='" + dogName + '\'' + ", age=" + age +
                '}';
    }
}

equals() : 객체의 같음 비교

  • 동일성(Identity) : == 연산자 사용. 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인 (완전히 같은 건지 확인)
  • 동등성(Equality) : equals()를 사용해서 두 객체가 논리적으로 동등한지 확인

동등서을 비교하고 싶으면 equals()메서드를 재정의해야함. 왜냐면 비교할 대상이 다 다르기 때문임.

public class UserV2 {
    private String id;

    public UserV2(String id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object obj) {
        UserV2 user = (UserV2) obj;
        return id.equals(user.id);
    }
}

equals를 재정하기하기 힘들기때문에 IDE로 재정의하는게 정확하다.

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Rectangle rectangle = (Rectangle) o;
        return width == rectangle.width && height == rectangle.height;
    }


불변객체

기본형은 값이 복사가 되어 사이드 이펙트를 발생시키지 않는다.
참조형의 경우 참조값(주소값)이 복사가 되어 사이드 이펙트를 발생시킬 가능성이 크다.

그럴 때 사용되는 것이 불변 객체이다. 그냥 클래스 내 필드를 final 지정해주면된다. 그럼 setValue()와 같은 값을 지정해주는 메서드도 필요없게 되어 불변객체가 된다.

public class ImmutableAddress {
	private final String value;
	public ImmutableAddress(String value) { 
    	this.value = value;
    }
    
    public String getValue() {
		return value;
    }
    
    @Override
	public String toString() {
		return "Address{" +
				"value='" + value + '\'' + '}';
    		} 
 	}

불변객체에서도 값을 변경하는 메서드가 존재할 수 있는데 그건 기존 객체를 변경하는게 아니라 기존 객체를 이용해서 새로운 객체를 생성해 반환하는 방법을 사용한 것.

public ImmutableObj add(int addValue) { 
	int result = value + addValue; 
    return new ImmutableObj(result);
}


String객체

문자 표현: char
문자열 표현: String (사실 char[], byte[](자바9부터는 byte로 대체)로 만든 클래스임)

  • String을 표현할 수 있는 방법은 두가지임
//1. 객체 생성
String str1 = new String("hello");

//2. 리터럴 (자바에서 편의를 위해 만든 것)
String str2 = "hello"

  • 문자열간 붙이는 메서드는 concat이지만 +도 허용함. 원래 String은 참조형이라 문자열간 붙이려고 해도 참조값이 들어가 있어서 연산이 불가능하지만 가능하게해줌.
String result1 = a.concat(b);
String result2 = a + b

  • String클래스 비교할 때는 ==비교(동일성)가 아니라 equals()비교(동등성)를 해야함.
public static void main(String[] args) {
    String str1 = new String("hello");
    String str2 = new String("hello");
    System.out.println("new String() == 비교: " + (str1 == str2)); //false
    System.out.println("new String() equals 비교: " + (str1.equals(str2))); // true
    
    String str3 = "hello";
    String str4 = "hello";
    System.out.println("리터럴 == 비교: " + (str3 == str4)); //true
    System.out.println("리터럴 equals 비교: " + (str3.equals(str4))); //true
}

-new String()은 각 인스턴스를 바라보기 때문에 동일성 비교할 때 false로 뜬다.

  • 문자열 리터럴의 경우 문자열을 힙 영역인 문자열 풀에 담아서 중복없이 사용한다. 문자열덕에 같은 문자를 사용하는 경우 메모리 사용을 줄이고, 문자 만드는 시간도 줄여서 성능도 최적화 할 수 있음

  • 상황에 따라 ==equals()를 어떻게 사용해야하나 싶으나 결국엔 그냥 equals()를 사용해서 동등성 비교를 하는것이 낫다.

  • 문자열 리터럴로 사용하는 경우 만약 문자열이 바뀌면 같은 문자열을 참조하고 있는건 어떻게 되는걸까?

  • 다행히도 String 클래스는 불변객체여서 사이드 이펙트같은 문제는 발생하지 않는다. 대신 불변객체처럼 concat이나 + 연산을 사용할 경우는 새로운 객체를 반환해서 사용해야한다.

  • 불변 String 클래스는 내부 값을 변경할 수가 없어서 문자를 더하거나 변경할 때 새로운 객체를 계속해서 만들고, GC해야해서 CPU, 메모리 자원을 더 많이 사용하게됨. 그렇다고 String을 불변객체로만 사용할 수 있느냐? 그건 또 아님 -> 가변String

StringBuilder - 가변 String


public class StringBuilderMain1_1 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        
        sb.append("A");        // 문자열을 추가
        sb.append("B");
        sb.append("C");
        sb.append("D");
        System.out.println("sb = " + sb); //sb = ABCD
       
        sb.insert(4, "Java"); // 특정 위치에 문자열 삽입
        System.out.println("insert = " + sb); // insert = ABCDJava
       
        sb.delete(4, 8);     //특정 범위의 문자열을 삭제
        System.out.println("delete = " + sb); // delete = ABCD
        
        sb.reverse();        // 문자열 뒤집기
        System.out.println("reverse = " + sb); // reverse = DCBA
		
        //StringBuilder -> String
        String string = sb.toString(); // 문자열을 변경하는 동안만 StringBuilder 를 사용하고, 마지막엔 안전한 불변객체로 만들어주는게 좋다.
        System.out.println("string = " + string); // string = DCBA
    }
}

String 최적화

1. 문자 리터럴 최적화

  • 런타임에 별도의 문자열 결합 연산을 수행X -> 성능 향상
//최적화 전
String helloWorld = "Hello, " + "World!";

// 최적화 후
String helloWorld = "Hello, World!";

2. String 변수 최적화

  • 문자열 변수의 경우 그 안에 어떤 값이 들어있는지 컴파일 시점에는 알 수 없어 단순하게 합칠 수 없음.
// 최적화 전
String result = str1 + str2;

//최적화 후
String result = new StringBuilder().append(str1).append(str2).toString();

3. 반복문 내의 문자열 더하는 경우 최적화

// 최적화 전
public static void main(String[] args) {
    long startTime = System.currentTimeMillis();
    String result = "";
    
    for (int i = 0; i < 100000; i++) {
        result += "Hello Java ";
    }
    long endTime = System.currentTimeMillis();
  
    System.out.println("result = " + result);
    System.out.println("time = " + (endTime - startTime) + "ms");
}


// 최적화 후
public static void main(String[] args) {
    long startTime = System.currentTimeMillis();
    StringBuilder sb = new StringBuilder();
    
    for (int i = 0; i < 100000; i++) {
        sb.append("Hello Java ");
    }
    
    String result = sb.toString();
    long endTime = System.currentTimeMillis();
    
    System.out.println("result = " + result);
    System.out.println("time = " + (endTime - startTime) + "ms");
}

StringBuilder를 직접 사용하는 것이 더 좋은 경우

  • 반복문에서 반복해서 문자를 연결할 때
  • 조건문을 통해 동적으로 문자열을 조합할 때
  • 복잡한 문자열의 특정 부분을 변경해야 할 대
  • 매우 긴 대용량 문자열을 다룰 때
profile
시간이 걸릴 뿐 내가 못할 건 없다.

0개의 댓글