본문 바로가기

JAVA/JAVA 기초

자바 타입관련

반응형

instanceof 연산자

변수의 타입과 완전히 일치하는 클래스만이 아니라 그 타입을 상속한 서브 클래스도 변수에 대입할 수 있다.

Object로 정의한 경우 그 변수에 String이든 Integer이든 대입할수 있다.

변수에 대입된 객체의 타입이 실제로 무엇인지를 판정하는 방법으로 instancof 연사자를 사용


package com.eeswk.domain;


public interface BaseService {

public String say();

}



package com.eeswk.domain;


public abstract class AbstractBaseService implements BaseService {

protected String name;

public AbstractBaseService(String name) {

this.name = name;

}

}


package com.eeswk.domain;


public class FooService extends AbstractBaseService {


public FooService(String name) {

super(name);

}

@Override

public String say() {

return "Hello";

}

public static void main(String... args) {

Object obj = new FooService("hello");

System.out.println(obj instanceof FooService);

System.out.println(obj instanceof AbstractBaseService); //부모클래스 

System.out.println(obj instanceof BaseService); //인터페이스

System.out.println(obj instanceof Integer); //상속관계 아님

if(obj instanceof FooService) {

FooService service = (FooService) obj; //FooService로 캐스트 가능

System.out.println(service.say());

}

}

}


instanceof 연산자에 의해 객체의 타입을 판정하고 캐스트함으로써 원하는 클래스의 메서드를 호출할 수 있지만 객체지향의 관점에서는 그다지 사용할 만한 것이 아니다.


객체의 등가성

객체가 동등한지 판정

2개의 객체가 동등하다(등가성이 있다)


"객체가 동일 객체"

== 연산자를 이용 이것은 객체의 값이 동일해도 객체가 다르면 별개의 것으로 판정

1234 Integer가 2개 있고 각각이 서로 다른 객체인경우 동일 객체가 아니므로 flase를 반환


"객체의 값이 같다" 

equals 메서드를 이용

비교하려는 객체와 인수로 건네지는 비교 대상의 객체의 값이 동일한지 판정

12345 Integer객체 val1, val2경우 true반환


자신이 직접 클래스를 만든 경우에는 객체가 동일한지의 여부를 적절히 판정하기 위해 equals 메서드를 오버라이드로 구현할 필요가 있다.

equals 메서드의 내부에서는 일반적으로 필드를 하니씩 비교하여 값이 동일한지를 판정한다.


@Override

public boolean equals(Object obj) {

// 이 객체와 인수로 건네짐 obj의 내용이 동일하면 true 다르면 false

}


hashCode 메서드

equals 메서드를 구현한 경우 한가지 더 구현해야 하는 메서드는 hashCode 메서드다.

hashCode 메서드는 객체 자신의 내용을 나타내는 숫자값(해시값)을 반환하는 메서드다.

해시값은 객체의 값을 일정 규칙에 따라 숫자값으로 한 것으로 동일 값을 갖는 객체는 동일 해시값이다.

반대로 해시값이 다른 경우 다른 객체가 된다.

그러나 동일 해시값이라고 해서 동일한 값을 갖는 객체라고 단정할 수 없다.

hashCode 메서드에 의한 해시값의 성질

- 동일 객체의 해시값은 반드시 동일하다.

- 해시값이 다른 경우 서로 다른 객체다.

- 서로 다른 객체라도 해시값이 동일한 경우가 있다.


@Override

public int hashCode() {

// 객체의 내용을 나타내는 숫자값을 반환

}


equals 메서드는 값을 하나씩 검증하기 때문에 계산에 시간이 걸린다.

그에 비해 해시값이라면 일정의 계산과 계산결과(int)의 비교만으로 끝나기 때문에 보다 고속으로 객체가 동일한지의 여부를 판정할 수 있다.


-처음에는 해시값으로 객체를 비교한다.

-해시값이 동일한 경우에 한해서 equals 메서드로 엄격히 판정한다.


객체를 비교한다. 2개의 객체가 동일하다고 판정하기 위해서는 두가지 조건이 만족해야 한다.


package com.eeswk.domain;


import java.util.HashSet;

import java.util.Set;


public class Employee {


private int employeeNo;

private String employeeName;

public Employee(int employeeNo, String employeeName) {

this.employeeNo = employeeNo;

this.employeeName = employeeName;

}

public int getEmployeeNo() {

return employeeNo;

}


public void setEmployeeNo(int employeeNo) {

this.employeeNo = employeeNo;

}


public String getEmployeeName() {

return employeeName;

}


public void setEmployeeName(String employeeName) {

this.employeeName = employeeName;

}


@Override

public boolean equals(Object obj) {

if(this == obj) {

return true;

}

if(obj == null) {

return false;

}

if(getClass() != obj.getClass()) {

return false;

}

Employee other = (Employee)obj;

if(this.employeeNo != other.employeeNo) {

return false;

}

if(employeeName == null) {

if(other.employeeName != null) {

return false;

}

}else if(!employeeName.equals(other.employeeName)) {

return false;

}

return true;

}

public static void main(String...arg){

Employee employee1 = new Employee(1, "이은유");

Employee employee2 = new Employee(1, "이은유");

Set<Employee> employees = new HashSet<>();

employees.add(employee1);

employees.add(employee2);

System.out.println(employees.size());

}

}


equals 메서드에서는 employeeNo와 employeeName의 값이 동일하면 true를 반환하는 처릴르 구현했으나

hashCode 메서드를 구현하지 않았다


HashSet에 같은 2개의 Employee 객체를 넣었다.


HashSet에서는 우선 객체가 동일한지를 판정하기 위해 hashCode 메서드의 반환값을 사용한다.

hashCode 메서드가 구현되지 않기 때문에 Object클래스의 디폴트로 구현되어 있는 hashCode 메서드의 반환값이 사용된다.

Object 클래스의 hashCode메서드의 반환값이 다르면 다른 값을 반환한다.

HashSet에서 추가하려는 2개의 employee 객체가 서로 다른 객체라고 인색해서 두개다 추가된다.


이문제를 해결하기 위해서는 hashCode 메서드를 구현하여 객체가 같은 경우 hashCode 메서드도 동일한 값을 반환하도록 해야한다.


@Override

public int hashCode() {

final int prime =31;

int result =1;

result = prime * result + employeeNo;

result = prime * result + ((employeeName == null) ? 0 : employeeName.hashCode());

return result;

}


package com.eeswk.domain;


public class Point {


private final int x;

private final int y;

public Point(int x, int y) {

this.x  = x;

this.y = y;

}

public int getX(){

return x;

}

public int getY(){

return y;

}

public static void main(String... args) {

Point point1 = new Point(3, 2);

Point point2 = new Point(3, 2);

System.out.println(point1);

System.out.println(point2);

System.out.println(point1.hashCode());

System.out.println(point2.hashCode());

System.out.println(point1.equals(point2));

}

}



객체 point1, point2는 둘다 좌표(3,2)를 나타내는 객체이지만 Point 클래스에서 hashCode, equals 메서드가 오버라이드 되지 않아 Object 클래스의 디폴트 구현이 사용돼 같은 객체라고 판단되지 않았다.

hashCode, eqauls 메서드를 오버라이드하여 구현한다.


package com.eeswk.domain;


public class Point {


private final int x;

private final int y;

public Point(int x, int y) {

this.x  = x;

this.y = y;

}

public int getX(){

return x;

}

public int getY(){

return y;

}

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + x;

result = prime * result + y;

return result;

}


@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass())

return false;

Point other = (Point) obj;

if (x != other.x)

return false;

if (y != other.y)

return false;

return true;

}


public static void main(String... args) {

Point point1 = new Point(3, 2);

Point point2 = new Point(3, 2);

System.out.println(point1);

System.out.println(point2);

System.out.println(point1.hashCode());

System.out.println(point2.hashCode());

System.out.println(point1.equals(point2));

}

}



com.eeswk.domain.Point@420

com.eeswk.domain.Point@420

1056

1056

true



Objects.hash 메서드

자바 7부터는 Objects.hash 메서드를 사용하여 hashCode를 생성할 수 있다.


@Override

public int hashCode() {

return Objects.hash(this.x, this.y);

}



toString 메서드

자신이 직접 클래스를 정의한 경우 Object 클래스의 toString 메서드도 오버라이드하면 좋다.

toString 메서드를 오버라이드 하지 않을 경우 Object 클래스의 toString 메서드가 호출되어 hashCode의 값이 문자열로 반환된다.


디버깅 시에 toString메서드를 호출하여 내용을 출력해도 어떤 객체인지 모른다.


@Override

public String toString() {

return "Point [x=" + x + ", y=" + y + "]";

}


StringBuilder 사용하는 방법

StringBuilder를 메서드 체인형식으로 사용하는 방법


@Override

public String toString() {

StringBuilder builder = new StringBuilder();

builder.append("Point [x=");

builder.append(x);

builder.append(", y=");

builder.append(y);

builder.append("]");

return builder.toString();

}


@Override

public String toString() {

StringBuilder builder = new StringBuilder();

builder.append("Point [x=").append(x).append(", y=").append(y).append("]");

return builder.toString();

}



@Override

public String toString() {

return ToStringBuilder.reflectionToString(this);

}


import org.apache.commons.lang.builder.ToStringBuilder;


IDE에서 제공해준다.


열거형(enum)

public static final에 의한 상수에는 '타입 안전이 아닌' 문제가 있다.

타입안전이란 변수에 타입을 할당함으로써 부정한 동작을 방지하는 것을 말한다.


public static final String COLOR_BLUE = "blue";

public static final String COLOR_RED = "red";

public static final String COLOR_GREEN = "green";


public void processColor(String color) {

}


processColor 메서드는 COLOR_BLUE, COLOR_RED, COLOR_GREEN중 하나가 전달될 것으로 생각했는데, 개발자가 그런 사양을 인지 못하거나 코딩상의 실수를 일으켜 color값으로 black을 보낼 가능성도 있다.

예상과 다른 동작이 될것이다. 이것이 타입 안전이 아닌 상태이다.


자바에서는 컴파일 시에 상수를 이용하고 있는 클래스(이용 클래스)의 상수에 상수를 정의하고 있는 클래스(상수 클래스)의 상수값 그 자체가 전개된다.

그 때문에 상수 클래스쪽에서 상수의 값을 변경해서 컴파일해도 이용클래스 쪽의 상수의 값은 변경되지 않는다.

상수클래스 쪽을 컴파일한 경우에는 이용클래스도 함께 컴파이할 필요가 있다.


상수클래스

public static final String SELECTED_COLOR = "red";


이용클래스

public String color = SELECTED_COLOR;


컴파일

public String color = "red";


이런 상태에서 상수 클래스를 "white"로 변경해서 컴파일해도 이용 클래스는 "red"로 되어 있다.

이용 클래스도 동시에 컴파일하면 값이 "white"로 변한다.

이러한 문제를 해결하기 위해 자바 5.0부터 enum 타입(열거형)을 사용한다.

enum 타입은 몇가지 상수의 집합을 정의하는 타입으로 클래스의 특수한 형식이다.


public enum TaskType {

PRIVATE, WORK

};


enum의 상수는 지금까지의 상수와 동일하게 대문자로 정의한다.


package com.eeswk.domain;


public enum TaskType {

PRIVATE, WORK

}





package com.eeswk.domain;


import java.util.UUID;


public class Task {


private String id;


private TaskType taskType;


private String body;


public Task(TaskType taskType, String body) {

this.id = UUID.randomUUID().toString();

this.taskType = taskType;

this.body = body;

}

public Task() {

this(TaskType.PRIVATE, "");

}


public String getId() {

return id;

}


public void setId(String id) {

this.id = id;

}


public TaskType getTaskType() {

return taskType;

}


public void setTaskType(TaskType taskType) {

this.taskType = taskType;

}


public String getBody() {

return body;

}


public void setBody(String body) {

this.body = body;

}

public static void main(String[] args) {

Task task = new Task(TaskType.PRIVATE, "buy milk");

TaskType type = task.getTaskType();

System.out.println(TaskType.PRIVATE.equals(type));

switch(type) {

case PRIVATE: //TaskType. 붙지않음 붙으면 컴파일 에러

System.out.println("Task[type = " +  type + "]");

break;

case WORK:

System.out.println("Task[type = " +  type + "]");

break;

}

}

}


true

Task[type = PRIVATE]



enum 타입은 클래스의 한 종류이기 때문에 다른 enum값은 대입할수 없어 타입 안전이 보장된다.

TaskType 쪽의 필드에는 TaskType enum으로 선언된 값 이외의 것은 설정할 수 없다.

또한 값을 문자열로 출력하는 경우 int 타입의 상수라면 int값이 그대로 문자열로 출력되지만, enum 타입이라면 명칭(상수명)이 출력되어 그 의미를 금방 알수 있다.

그리고 enum의 값은 상수값과 달리 이용하는 쪽의 클래스에는 값이 전개되지 않기 때문에 TaskType enum을 변경할 경우에도 이용하는 쪽의 클래스를 다시 컴파일할 필요가 없다.

enum 타입은 실레조 java.lang.Enum 클래스를 상속한 클래스이므로 필드나 메서드를 정의할 수 있다.

그 때문에 상수의 이름뿐만 아니라 임의의 값을 관련지어 보관할 수 잇다.

HTTP의 상태 코드를 표현하는 HttpStatus경우

OK => 200

NOT FOUND => 404

INTERNAL SERVER ERROR => 500



package com.eeswk.domain;


public enum HttpStatus {

OK(200), NOT_FOUND(404), INTERNAL_SERVER_ERROR(500);

private final int value;

private HttpStatus(int value) {

this.value = value;

}

public int getValue(){

return value;

}

public static void main(String[] args) {

HttpStatus hs = HttpStatus.OK;

System.out.println("HttpStatus = " + hs + "[" + hs.getValue() + "]");

}

}


HttpStatus = OK[200]



제네릭스(Generics)

복수의 객체를 보관할 수 있는 클래스로 ArrayList 클래스가 있다.

ArrayList에 객체를 추가한 후 꺼내는 처리를 생각해보자

ArrayList 클래스는 List 인터페이스를 구현하고 있으며, add메서드로 객체를 추가 get메서드로 객체를 꺼낸다.

get 메서드의 인수에는 취득할 객체의 인덱스를 지정한다.


package com.eeswk.domain;


import java.util.ArrayList;

import java.util.List;


public class GenericsSample {


public static void main(String[] args) {

List list = new ArrayList();

list.add("Java");

String element = (String)list.get(0); //캐스트가 필요

System.out.println(element);

}

}


add메서드에 의해 Object 클래스의 객체를 등록할 수 있지만 get 메서드로 취득할 때는 이용할 타입으로 캐스트할 필요가 있다.

ArrayList에 String의 개게만 추가하려는 의도였으나 실제로는 다른 타입의 객체를 추가할 수도 있다.

그 때문에 객체를 취득할때 String이라고 생각해서 캐스트하면 실제로 String이 아닌 예상 외의 동작이 발생할수 있다.


자바 5.0부터 제네릭스 타입이 도입

제네릭스란 특정 타입으로 연관될 추상적 타입을 말하며 범용적인 처리를 기술할 때 자주 사용한다.


List<String> list2 = new ArrayList<String>();

list2.add("JAVA");

String element2 = list2.get(0);

System.out.println(element2);


제네릭스에 의해 요소에 대한 타입을 String클래스로 고정하기 때문에 ArrayList에는 String 클래스 객체만 추가할 수 있다.

요소를 꺼내올때 캐스트가 불필요하다.

자바 7이라면 다이아몬드 연산자가 이용 가능하므로 보다 간략화한 기술이 가능하다.


제네릭스를 사용한 클래스의 작성

package com.eeswk.domain;


import java.util.ArrayList;

import java.util.List;


public class StringStack {


private List<String> taskList;


public StringStack() {

taskList = new ArrayList<>();

}

public boolean push(String task) {

return taskList.add(task);

}

public String pop(){

if (taskList.isEmpty()) {

return null;

}

return taskList.remove(taskList.size() - 1);

}

public static void main(String[] args) {

StringStack strStack = new StringStack();

String strElement = strStack.pop();

strStack.push("Scala");

strStack.push("Groovy");

strStack.push("Java");

strElement = strStack.pop();

if(strElement != null) {

System.out.println(strElement);

}

}

}


요소가 하나도 등록되어 있지 않은 경우 null를 반환하므로 이용시에는 null체크가 필요하다

그럼 제네릭스를 이용하여 임의의 타입이 추가 가능한 스택인 GenericStack 클래스를 작성해보자.

앞에 만든 StringStack 클래스에서는 taskList 필드의 요소의 타입이나 push 메서드의 인수, pop 멘서드의 반환값의 타입이 String이였는데 임으의 타입으로 하기위해서 이것을 임시 타입은 E라는 문자로 표현하기 하자

이 임시 타입은 E를 타입 매개변수라고 부른다.

제네릭스를 정의하려면 타입 매개변수를 사용하여 GenericStack<E>와 같이 매개변수화한 타입으로 정의한다.



/**

 * 

 */

package com.eeswk.domain;


import java.util.ArrayList;

import java.util.List;


/**

 * @author swlee

 *

 */

public class GenericStack<E> {

private List<E> taskList;


public GenericStack() {

taskList = new ArrayList<>();

}

public boolean push(E task) {

return taskList.add(task);

}

public E pop(){

if(taskList.isEmpty()) {

return null;

}

return taskList.remove(taskList.size() - 1);

}

public static void main(String[] args) {

GenericStack<String> genStack = new GenericStack<>();

genStack.push("Scala");

genStack.push("Groovy");

genStack.push("Java");

String genElement = genStack.pop(); //캐스트가 불필요

if(genElement != null) {

System.out.println(genElement); //Java

}

GenericStack<Integer> genStack2 = new GenericStack<>();

genStack2.push(100);

genStack2.push(200);

Integer genElement2 = genStack2.pop(); //캐스트가 불필요

if(genElement2 != null) {

System.out.println(genElement2); //200

}

}

}


제네릭스는 클래스에 대해서가 아닌 메서드만 정의할수도 있다.

그런 경우 메서드의 반환값 앞에 타입 매개변수를 지정한다.


package com.eeswk.domain;


import java.util.ArrayList;

import java.util.List;


public class GenericsStackUtil {


public static <T> GenericStack<T> as(List<T> list) {

GenericStack<T> stack = new GenericStack<>();

list.forEach(stack::push);

return stack;

}

public static void main(String[] args) {

List<String> strList = new ArrayList<>();

strList.add("Java");

strList.add("Groovy");

GenericStack<String> gstack = GenericsStackUtil.as(strList);

}

}


타입 매개변수에 제한을 덧붙임으로써 지정 가능한 타입을 한정시킬 수 있다.

예) extends는 타입 매개변수 E가 지정하는 클래스(Number)의 자식 클래스라는 제한을 만들어보자


이것에 의해 타입 매개변수로 선언된 변수에 대해 extends로 제한한 클래스의 메서드를 호출할 수 있다.

push메서드의 실행시 추가된 ㄱ밧의 정수값을 화면에 표시하는 소스코드다.

타임 매개변수 E는 Number 클래스 또는 그 서브 클래스인 것으로 제한되어 있으므로 타입 매개변수 E로 선언된 task 변수에 대해 Number 클래스의 메서드인 intValue 메서드를 호추할 수 있다.

package com.eeswk.domain;


import java.util.ArrayList;

import java.util.List;


public class NumberStack<E extends Number> {

private List<E> taskList;


public NumberStack() {

taskList = new ArrayList<>();

}

public boolean push(E task){

System.out.println("Added " + task.intValue() + " (Integer)");

return taskList.add(task);

}

public E pop() {

if(taskList.isEmpty())

return null;

return taskList.remove(taskList.size() - 1);

}

public static void main(String[] args) {

NumberStack<Integer> intStack = new NumberStack<>(); //Integer클래스는 Number클래스의 자식 클래스

NumberStack<Long> longStack = new NumberStack<>(); //Long 클래스는 Number클래스의 자식 클래스

intStack.push(100);

intStack.push(200);

Integer numElement = intStack.pop();

if(numElement != null) {

System.out.println(numElement);

}

}

}


extends 이외에도 자기 자신의 클래스 또는 부모 클래스로 제한하는 super, 정해지지 않음을 나타내는 ? 등을 사용할 수 있다.

제네릭스는 자바 언어에 있어서 꽤 심도 깊은 기능이다.

출처: 자바 마스터북

반응형

'JAVA > JAVA 기초' 카테고리의 다른 글

예외 관련  (0) 2018.10.21
자바 컬렉션관련  (0) 2018.10.13
자바 배열관련  (0) 2018.10.13
자바 기본형 참조형 클래스 관련  (0) 2018.10.12
자바 기초 작성법  (0) 2018.10.12