자바 중급 요약

고동현·2024년 7월 7일
0

JAVA

목록 보기
14/22

Object클래스

java.lang의 대표 클래스

  • Object: 모든 자바 객체의 부모 클래스
  • String
  • Integer,Long,Double: 래퍼타입
  • System: 시스템과 관련된 기본기능

lang은 임포트 안해도됨

모든 클래스의 최상단에는 Object가 있다.
child -> parent -> object

child와 parent에 toString이라는 메서드가 없어도 호출가능 -> Object의 toString호출됨

Object클래스를 최상위 부모 클래스로 만들어놓은 이유

  • 공통기능 제공
    toString,equals,getClass같은 공통기능을 모두 개발자보고 개발하라면 일관성,효율성 떨어짐
  • 다형성의 기본 구현
    Object는 모든 객체를 다 담을 수 있다. 타입이 다른 객체를 어딘가에 보관해야한다면 Object에 보관하면 된다.
  private static void action(Object obj) {
        if(obj instanceof Dog dog){
            dog.sound();
        } else if (obj instanceof  Car car) {
            car.move();
        }
    }

action(dog), action(car) 이런식으로 호출 -> 해당 인스턴스 타입이 다르므로 어딘가 보관해야할때 Object사용

올라갈수는 있어도, 내려갈 수는 없다.
obj.sound(), obj.move() 불가.

Ojbect는 모든 객체의 부모이므로 모든 객체 참조가능
but, Object를 통해 전달 받은 객체를 호출하려면, 각 객체에 맞게 다운캐스팅 필요

Object는 다형적 참조는 가능하지만, 메서드 오버라이딩이 불가능하다.(Object에는 다른 객체의 메서드들이 정의 안되어있으니까)

Object 쓰면 좋은곳

		 Dog dog = new Dog();
        Car car = new Car();

        Object object = new Object();

        Object [] objects = {dog,car,object};

println(인스턴스)-> 인스턴스.toString()호출

        Car car = new Car("ModelY");
        Dog dog1 = new Dog("dog1",2);
        Dog dog2 = new Dog("dog2", 4);

        System.out.println(car);
        System.out.println(dog1);
        System.out.println(dog2);

car에는 toString 오버라이드 된게 없으므로, Object의 toString이 호출된다.
dog에는 toString 오버라이된게 있는데, 오버라이드 된 Dog의 toString이 호출됨

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 +
                '}';
    }
}

public class ObjectPrinter {
    public static void print(Object obj){
        String string = "객체 정보 출력:" + obj.toString();
        System.out.println(string);
    }
}
public class ToStringMain2 {
    public static void main(String[] args) {
        Car car = new Car("ModelY");
        Dog dog1 = new Dog("dog1",2);
        Dog dog2 = new Dog("dog2", 4);
        print(car);
        print(dog1);
        print(dog2);
    }
}

현재 main은 print라는 ObjectPrinter에만 의존하고 있다.
Car와 Dog같은 구체 class에 의존하는것이 아닌, Object에 의존하므로, 어떤 Class가 오더라도 print메서드를 고칠 필요가 없다.
dog에는 오버라이드 메서드가 있어서 오버라이드 된 메서드 출력, Car는 오버라이드 된게 없으므로 Object게 출력됨.

equals()

  • 동일성: == 연산자를 통해서 두 객체의 참조가 동일한 객체를 가리키는지 확인
  • 동등성: equals()를 통해 두 객체가 논리적으로 동등한지 확인

User a = new User("id-100")
User b = new User("id-100")

System.out.println(a.equals(b)); id-100 같으니까 true일거 같지만, equals메서드 내부는 ==으로 동일성 확인한다.
왜냐하면, java입장에서는 인스턴스의 필드에 어떤게 올지 모르는 상태이기 때문에,
우리가 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);
    }
}
public class EqualMainV2 {
    public static void main(String[] args) {
        UserV2 user1 = new UserV2("id-100");
        UserV2 user2 = new UserV2("id-100");

        System.out.println("identity = " + (user1==user2));//false
        System.out.println("equality = "+ user1.equals(user2));//오버라이드된 메서드 호출로 true
    }
}

불변객체

  • 기본형: 하나의 값을 여러변수에서 절대 공유하지 않는다.
  • 참조형: 하나의 객체를 참조값을 통하여 여러 변수에서 공유

기본형
int a = 10;
int b = a; //a의 값을 복사해서 대입

b = 20; // b를 변경하더라도 a가 변경되지 않음 why? 값을 복사해서 넣었기 떄문

참조형
Address a = new Address("서울");
Address b = a;

여기서 b.setValuse("부산");
a와 b의 address가 전부 부산으로 바뀐다.

왜냐하면 a와 b는 동일한 참조값을 가지고 하나의 인스턴스를 바라보고 있기 때문이다.

but, 여러변수가 하나의 객체를 공유하는것을 막을 방법이 없음, Address b = a는 옳은 문법이니까.

public class ImmutableAddress {
    private final String value;
...

애초에 만들때 내부값이 변경 안되게 value필드를 final로 선언

b.setValue("부산") 불가 final이므로 컴파일오류가 생긴다.

그렇다면 불변객체인데 이렇게 setValue처럼 값을 변경해야하면 어떻게 해야할까?
변경하고자하는 값으로 새로운 객체를 만들어서 반환한다.

public class ImmutableObj {
    private final int value;
    public ImmutableObj(int value){
        this.value =value;
    }

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

    public int getValue(){
        return value;
    }
}

value는 final로 불변이다. 그러나 value에다가 addValue값을 더하고 싶으면 result를 가지고 새로운 Immutable Obj를 만들어 반환한다.

public class ImmutableMain1 {
    public static void main(String[] args) {

        ImmutableObj obj = new ImmutableObj(20);
        obj = obj.add(10);
        System.out.println(obj.getValue());
    }
}

그러면 ImmutalbeObj가 두개가 만들어지는데 20으로 만든 Immutable Obj는 GC가 어디에서도 사용되지 않는 인스턴스를 지워준다.

String

int,boolean과 같은 기본형이 아닌 참조형이다.

실제 문자를 저장할 수 있는 value배열이 존재한다.
자바 9 이전에는 byte가 아니라 char였는데, 문자는 2byte가 필요하지만, 영어나 숫자로 이루어진 문자는 1byte로 표현이 가능하므로 byte로 변경

String a = "hello"

new String("hello")해야하는데 자바가 봐준다.

String b = "cat"
String c = a+b;

a+b도 원래는 참조값이니까 x001+x002라서 안되는데 자바가 편의를 봐준다.

  • ==: 동일성 두 객체의 참조가 동일한 객체인지 확인
  • equals(): 두 객체가 논리적으로 같은지 확인
public class StringEqualsMain1 {
    public static void main(String[] args) {
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println("==비교: "+(str1==str2));
        System.out.println("equals비교: "+(str1.equals(str2)));

        String str3 = "hello";
        String str4 = "hello";
        System.out.println("==비교: "+(str3==str4));
        System.out.println("equals비교: "+(str3.equals(str4)));

    }
}
==비교: false
equals비교: true
==비교: true
equals비교: true

str1,str2 서로 다른 인스턴스가 만들어지니까 ==비교 false, equals는 true

str3도 원래는 new String("hello")로 자바가 만들어줘서 서로 x003,x004가 다를텐데 동일성 어떻게 통과?

문자열 풀에 답이 있다.

String str3 = "hello"와 같은 문자열 리터럴을 사용하는경우 자바가 실행되는 시점에 문자열 풀에 String같은 인스턴스를 미리 만들어둔다.
고로 같은 인스턴스를 참조하게 된다.

String은 불변이다.

public class StringImmutable {
    public static void main(String[] args) {
        String str1 = "hello";
        str1.concat(" java");
        System.out.println(str1);
    }
}

String 내부의 value type이 final이다. 고로 hello가 할당되면 다음에 절대로 바꿀수 없다.
고로, 기존값을 변경x => 새로운 결과를 만들어야한다.
String str1 = "hello";
String str2 = str1.concat(" java");

왜 String이 불변으로 설계되어있을까?
리터럴로 str3,str4가 만들어졌다면, x003으로 동일한 인스턴스를 참조하고있다.
String str3 = "hello";
String str4 = "hello";

str3이 hello를 hhh로 바꿔버리면 동일하게 x003을 참조하고 있던 str4도 hhh로 바뀌어버린다.
String은 이러한 경우를 방지하기 위해서 불변으로 만들었다.

가변 String
String str = new String("AB")+new String("ABC")+new String("ddd");
String은 불변이므로 new String("ABABCddd") 객체를 반환, 나머지 AB,ABC,ddd 객체는 쓰이지 않게된다.

나머지 3개 인스턴스는 GC가 버린다.

private이 아니고 가변으로 설정하기 위해서는 String Builder를 사용하면된다.

StringBuilder sb = new StringBuilder();
        sb.append("A");
        sb.append("B");
        sb.append("C");
        sb.append("D");
		sb.insert(4,"java");
        sb.delete(4,8);
        sb.reverse();
        String string = sb.toString();

문자열을 변경할때마다, 새로운 객체를 생성하지 않고,
해당 문자열을 toString메서드를 통해 String class로 변환도 가능

그래서, StringBuilder는 보통 문자열을 변경하는 동안만 사용되다가, 문자열 변경이 끝나면 안전한 String으로 변환

String 최적화
String helloWorld = "Hello, " + "World!";
Hello,World! 두개다 문자열 풀에 만드는게 아니고, Hello World! 인스턴스를 문자열 풀에 만들어준다.

String result = str1 + str2;
자바가 내부에서 new StringBuilder().append(str1).append(str2).toString()
이런식으로 최적화를 알아서 처리해준다.

JAVA가 자동으로최적화가 어려운경우

  • 반복문에서 반복해서 문자를 연결하는경우
  • 조건문을 통해 동적으로 문자열 조합
  • 복잡한 문자열에서 특정 부분을 변경해야할때
  • 매우 긴 대용량 문자열을 다룰때

StringBuilder vs String Buffer
String Buffer는 StringBuilder와 동일한 역할 수행
String Buffer는 내부에 동기화가 되어있어서, 멀티 스레드 환경에서 안전, 동기화 오버헤드로 성능이 느리다.
String Builder는 멀티 스레드 환경에서 안전하지 않지만, 동기화 오버헤드가 없으므로 속도가 빠르다.

String Buffer는 메서드에서 synchronized 키워드 사용
sychronized는 현재 데이터 사용하고 있는 스레드를 제외하고 나머지 스레드들이 데이터에 접근할 수 없도록 막는다.

메서드 체이닝
메서드에서 반환형이 자신의 참조형이다.

 StringBuilder sb = new StringBuilder();
        String str = sb.append("A").append("B").append("C")
                .insert(3,"java")
                .reverse()
                .toString();
        System.out.println(str);

sb에는 StringBuilder()생성자를 통한 인스턴스가 들어가고,
sb.append("A")를하면 해당 인스턴스의 참조값을 반환하므로 0x001.append("B")가 된다.

래퍼 클래스

기본형의 한계
int,double,boolean과 같은 타입은 기본형

  • 객체가 아님, 유용한 메서드 제공 불가
  • null값을 가질 수 없음

래퍼클래스: int는 데이터 쪼가리니까, int 클래스로 감싸서 만드는 것처럼, 특정 기본형을 감싸서(Wrap) 만드는 클래스를 래퍼 클래스라 한다.

public class MyInteger {
    private final int value;

    public MyInteger(int value) {
        this.value = value;
    }
    public int getValue(){
        return value;
    }

    public int compareTo(int target){
        if(value < target){
            return -1;
        }else if(value > target){
            return 1;
        }else{
            return 0;
        }
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }
}

이런식으로 int를 감싸서 다양한 메서드를 제공할 수 있다.

MyInteger myint = null
이런식으로 참조형에 null을 사용할 수 있다.

자바 래퍼 클래스

  • byte => Byte
  • int => Integer
  • long => Long
  • double => Double

자바가 기본으로 제공하는 레퍼 클래스는, 불변이다. equals로 비교해야한다는 특징이있다.

 Integer newInteger = new Integer(10); //곧 사라지므로 사용 x
 Integer integerObj = Integer.valueOf(10); 
 Integer integerObj2  = Integer.valueOf(10);
 
 System.out.println(newInteger==integerObj);
 System.out.println(integerObj2==integerObj);

valueOf에는 성능 최적화 기능이 있다.
-128~127까지 범위의 Integer클래스를 미리 생성하고 반환하다.
범위가 넘어간 숫자는 그때 new Integer()호출로 생성한다.

newInteger와 integerObj는 인스턴스 생성 방식이 다르므로, false가 나오지만,
intgerObj와 interObj2는 둘다 자바가 문자열 풀과 비슷하게 해당 숫자에 대응하는 인스턴스를 미리 생성해둔것을 가져다 쓴것이므로, 동일성을 비교해도 true가 나온다.

Auto Boxing, Auto UnBoxing

Integer integer = 4;
int i = integer;

원래는 기본형을 래퍼형, 래퍼형을 기본형으로 변환할때는 .valueOf(),xxxValue()를 사용해야만 한다.
그런데 이러한 과정은 너무 빈번히 일어나서 JAVA가 기본적으로 Auto로 바꾸어서 넣어준다.

래퍼클래스와 기본형의 성능차이
만약 for문에서 long +=10,Long +=10(자동으로 auto boxing해줌) 이 루프를 10억번 돌린다치면 성능차이가 한 5배난다.

왜냐하면 기본형은 메모리에서 단순히 그 크기만큼 공간을 차지하지만, 래퍼 클래스의 인스턴스는 내부에 필드를 가지고 자바가 객체를 다루는데 필요한 메타 데이터가 있으므로, 메모리를 더 사용하기 때문에 성능차이가 난다.

그러나 이 경우는 10억번 가량 돌렸을때 이야기이고, 1회로 환산하면 애플리케이션 관점에서 성능차이는 사막의 모래알 하나 정도 차이이다.

Class 클래스
Class클래스를 통해서 클래스의 메타정보를 확인 할수 있고, 이를 통해 객체 인스턴스를 생성하거나 메서드를 호출하는 작업을 할 수 있다.

조회방법 3가지

  • Class clazz = String.class;

  • Class clazz = new String().getClass()

  • Class clazz = Class.forName("java.lang.String");

   Class clazz = String.class;
   String s = (String) clazz.getDeclaredConstructor().newInstance("hello");

Class에 String.class를 받고
getDeclaredConstructor().newInstance()호출을 통해 인스턴스를 생성할 수 있다.
이렇게 class를 통해 해당 클래스의 객체 인스턴스를 생성하거나 메서드를 호출하는 작업을 할 수 있는데, 이런작업을 리플랙션이라한다.

System 클래스
시스템과 관련된 기본 기능을 제공한다.

public class SystemMain {
    public static void main(String[] args) {
        //현재시간을 밀리초로 가져온다.
        long currentTimeMillis = System.currentTimeMillis()
 
        //현재시간을 나노초로 가져온다.
        long currentTimeNano = System.nanoTime();
      
        //환경변수를 읽는다.
        System.out.println(System.getenv());
        
        //시스템 속성을 읽는다.
        System.out.println("propertis = " + System.getProperties());
      
        //배열 고속 복사
        char[] originalArray = new char[]{'h','e','l','l','o'};
        char[] copiedArray = new char[5];
        System.arraycopy(originalArray,0,copiedArray,0,originalArray.length);
        
        //배열 출력
        System.out.println(copiedArray);
    }
}

Math와 Random class

public class MathMain {
    public static void main(String[] args) {
        int max = Math.max(10, 20);
        int min = Math.min(10, 20);
        int abs = Math.abs(-10);

        double ceil = Math.ceil(2.1);//올림
        double floor = Math.floor(2.8);//내림
        long round = Math.round(2.5);//반올림

        double sqrt = Math.sqrt(4);//제곱근
        double random = Math.random();//0.0~1.0사이의 double 값
    }
}
public class RandomMain {
    public static void main(String[] args) {
        Random random = new Random();
        int i = random.nextInt();
        double v = random.nextDouble();

        boolean b = random.nextBoolean();

        int i1 = random.nextInt(10);//0부터9까지 출력
        int i2 = random.nextInt(10) + 1;//1부터 10까지 출력
    }
}

nextInt메서드는 랜덤 int값을 반환한다.
메서드에 파라미터를 넣으면 0~bound미만의 숫자를 랜덤으로 반환한다.

Random random = new Random(1);
이렇게 생성자에 seed를 넣어서 사용할 수 있는데, seed가 같으면 항상 Random의 결과가 같다.
즉, random메서드를 반복 수행해도 항상 같은 결과가 나온다.

profile
항상 Why?[왜썻는지] What?[이를 통해 무엇을 얻었는지 생각하겠습니다.]

0개의 댓글