이전 포스팅에서 객체지향이 추구하는 4가지 특징에 관하여 설명하였다.

이번에는 4가지 특징에 동반되는 5가지 원칙에 대하여 설명하겠다.

OOP의 5대원칙은 각각의 앞글자를 따서(SOLID)라고 불리며 여러 디자인패턴은 5가지 원칙을 준수하며 만들어졌단걸 기억하면 이해하기 더욱 쉬울것이다.

 

SRP(Single Responsibility Principle): 단일 책임 원칙 - 객체는 1개의 책임만 가져야한다

OCP(Open Closed Priciple): 개방 폐쇄 원칙 - 확장에 열려있어야하며 수정엔 닫혀있어야 한다.

LSP(Listov Substitution Priciple): 리스코프 치환 원칙 - 서브타입은 언제나 부모타입으로 교체할수 있어야한다.

ISP(Interface Segregation Principle): 인터페이스 분리 원칙 - 적재적소에 맞도록 인터페이스를 설계해야한다.

DIP(Dependency Inversion Principle): 의존 역전 원칙 - (구현된)클래스를 참조하는게 아닌 그 상위요소로 참조하라


SRP(Single Responsibility Principle) - 단일 책임 원칙

책임은 기능이라고 이해하면 쉽다.

1개의 클래스는 1개의 책임(기능)만 갖고 있어야 한다는 원칙이다.

그렇다면 어째서 이런 원칙을 지켜야하는가?

 

유지보수 VS 성능

개발자 입장에서만 생각한다면 코드 전체를 여러 클래스가 아닌 한 클래스에 전부 몰아넣어서 확인한다면 외부참조 등 여러가지 문제에서 해방될수 있을것이다 (성능을 우선시한 결과이며 코드의 가독성, 유지보수등의 문제는 논외로 치자..)

하지만 단순한 계산프로그램이 아닌 100개의 기능을 구현하였고 이것이 얽히고 섥혀 난잡하게된 상황에서 1개의 기능을 수정한 경우는 큰 에러가 발생할것이다.

 

한가지 예시를 들어보겠다. 

상단의 사진은 한 게임의 대미지 계산공식으로 해당 공식에선 여러 변수들이 계산되는걸 볼수있는데(캐릭터 스텟, 몬스터 방어력으로 2개이상 참조) 만일 일정시간동안 입는 대미지별로 몬스터의 방어력이 달라지는 기능을 넣는다고 가정해보자.(기능변경으로 단일책임 원칙 위배) -> 레이드시 보스의 순삭을 막기위한 기능이다

 

이 기능이 정상적으로 작동이 된다면 문제가 없겠지만 에러가 발생하는경우 어디서 에러가 발생하였는지 찾을때 굉장히 어려움이 발생할것이고 이러한 기능을 방지하고자 만들어진게 SRP이다. (유지보수 우선)

 

또한 이전의 카카오 화재사태처럼 코드가 소실된경우 이러한 에러발생시 이와 관련된 에러를찾을때 굉장한 시간이 걸리는데 SRP를 지켜가며 설계한경우 문제되는 부분만 캐치하여 코드만 기입하면 되기에 DR(Disaster Recovery) 에 대응하는 시간또한 줄어든다고 볼수있다.

 

(SRP는 모듈화를 최대한 지향한다고 생각하면 된다.)

(모듈화 - SW설계에서 기능단위로 분해, 추상화하여 재사용 및 공유가능한 수준으로 만들어진 단위로 모듈화가 강해질수록 다른 객체와의 의존,연관성이 줄어든다.)

 

장점

클래스의 책임영역이 확실하다 -> 책임 변경(기능수정,추가 등)에 따른 연쇄변경에서 자유롭다 (한개만 바꿔도 다른곳 신경 안써도 된다)

응집도(cohesion)강화, 결합도(coupling)약화, 가독성 향상, 유지보수 용이

(응집도 -  모듈 내부의 기능적인 집중정도 // 결합도 - 모듈 상호간 의존하는 정도)

 

요점정리

1. 클래스는 1개의 책임(1개의 기능)만 갖는다

2. 클래스는 캡슐화를 하여야한다.

3. 클래스를 변경하려는 이유는 단 하나(필요할때마다 구현) 해야한다.

4. 유지보수 VS 성능에서 유지보수가 이긴결과이다.


 

OCP(Open Closed Priciple) - 개방 폐쇄 원칙

SW개발작업중 1개의 모듈에 수정을 가할경우 그 모듈을 이용하는 다른 모듈을 전부 고쳐야한다면 수정하기에 굉장히 어려운 작업일것이다.

이러한 불편함을 해소하고자 세운 원칙이 OCP이다.

확장에 열려있어야하며 수정엔 닫혀있어야 한다. (추상화라고 생각하고 이해하면 쉽다)

 

먼저 확장과 변경에 대한 설명부터 시작하겠다.

확장 -> 모듈의 확장성(파생)을 보장하는것 (새로운 변경사항 발생시 코드를 기능을 추가할수 있는것 -> 추상메서드 구현)

변경 -> 객체를 직접적으로 수정하는건 제한해야한다(새로운 변경사항이 발생했을때 객체를 직접적으로 수정하지 않게 해야한다 -> 추상메서드를 수정하지말고 구현메서드를 수정해라)

 

위의 요약을 해석하자면 원래코드(추상메서드)를 변경없이 기존 코드에 새로운 코드를 추가함 또는 상속받음으로써 기능의 추가 및 변경이 가능하다. (추상메서드의 구현)

즉, 다형성을 활용하여 생성하는 클래스를 최소한으로 하되 상속 및 추상클래스를 구현하는 방식으로 설계하라 

 

이것을 가장 잘 확인해볼수 있는것이 자바의 기본API이다. (개인적인 의견입니다!...)

우리는 필요할때마다 자바에 내장된 각종 API를 가져와서 사용하고 필요하면 수정Override하여 API 자체를 수정하는 작업은 기피하고있는데 이것이 가장 OCP를 잘지키고있는 모습이라 생각한다.

 

여담으로 하단에 서술할 DIP는 OCP를 기반으로 작성 된다고 한다.

 

요점정리

1. 다형성과 확장을 최대한 지향하는것이다.

2. 기능이 필요할경우 추상화 및 상속을 활용하여 필요할때마다 구현하되 필요한 기능은 상위클래스가 아닌 하위클래스를 변경할것 

 


LSP(Listov Substitution Priciple): 리스코프 치환 원칙

상위객체를 하위객체로 치환하여도 상위타입을 사용하는 프로그램은 정상동작해야한다.

즉, 언제든 자식객체가 부모타입으로 교체하여도 문제없이 돌아가야한다.(다형성)

 

이를 조금더 자세히 설명하자면 다음과 같다.

 

정사각형 클래스가 직사각형 클래스를 상속하면 정사각형의 특징인 "네 변의 길이는 동일하다" 라는 특징과 그렇지 않은 직사각형의 차이로 인하여 직사각형을 정사각형 클래스로 치환해서 사용할 때, 두 클래스의 특징 차이 때문에 오류가 날 수 있다는 의미이다.

 

부모객체와 이를 상속한 자식객체가 잇을떄 부모객체를 호출하는 동작에서 자식객체가 부모객체를 완전히 대체할수 있다는 원칙이다. -> 자식객체의 확장이 부모객체의 방향을 온전히 따르도록 권고하는 원칙

 

요점정리

부모클래스의 인스턴스를 사용하는 위치에 자식클래스의 인스턴스를 대신 사용해도 정상작동해야 한다. (부모클래스의 구현조건사항을 자식클래스도 따라야한다. -> 오버라이딩할때 다형성을 지켜지도록 설계하자)


ISP(Interface Segregation Principle) - 인터페이스 분리 원칙

큰덩어리의 인터페이스를 작은단위로 분리시켜 필요한 메서드만 사용하게 해야한다. (적재적소에 맞도록 인터페이스를 설계해야한다.)

 

예를들어 OS는 버전이 올라가면서 여러가지 기능을 지원하기 시작했는데 현재 window 98에 없던 블루투스 기능이 현재(window 22)는 구현되어 있다 가정해보자. (이하 A 인터페이스 -> 1998버전, B 인터페이스 -> 2022버전)

업데이트 방식을 인터페이스로 구현한다고 가정해보면 A 인터페이스를 사용할경우 22버전,98버전 모두 사용에 문제가 없으나 (2022의 블루투스 기능은 추가로 구현하면 되기에 문제가 되지않음) B 인터페이스를 사용할경우 98버전에선 사용할수도 없는 기능인 블루투스기능을 구현해야하는 불편함이 생긴다. (불필요한 코드가 늘어난다)

 

 

즉, 인터페이스를 너무 크게 만들면 나중에 이를 사용할때 사용안하는 코드를 구현하는데 불필요한 코드가 늘어나니 가능하면 최소단위로 만들어라 이것이 ISP의 주요목적이다. (인터페이스는 다중상속을 지원한다)

 

요점정리

1. 인터페이스를 너무 크게(기능이 많게) 만들면 나중에 이를 구현할때 사용안하는 코드를 구현하는데 불필요한 코드가 늘어나니 가능하면 최소단위로 만들어라

2. 한번 분리한 인터페이스를 추가 수정사항이 생겨서 또 이것을 분리하는행위는 하지말아라(한번 구성하였는데 또 분리한다면 기존에 사용되던 인터페이스를 구현한 객체들도 전부 수정이 필요하기 때문이다)


DIP(Dependency Inversion Principle) - 의존 역전 원칙

(구현된)클래스를 참조하는게 아닌 그 상위요소로 참조하라

추상클래스, 추상메서드를 우선적으로 사용할것이며 구현클래스는 가능하면 건드리지 말아라.

의존 - 객체끼리 서로 참조등으로 A를 실행하기 위해선 B가 필요한경우 A는 B에 의존한다고 한다 (내가 메세지를 보내기 위해서는 카카오톡이 필요하다 -> 내가 메세지를 보내는 행동을 위해선 카카오톡이 필요(의존)한다)

의존성주입 - 클래스 외부에서 의존되는것을 대상 객체의 IV에 주입하는것

 

이것을 조금더 풀어쓰면 상위모듈(부모개체), 하위모듈(자식개체) 모두 추상화에 의존해야하며 상위모듈이 하위모듈에 의존하면 안된다.

즉, 의존관계를 형성할때 구체적인것(변하기 쉬운 구현객체)보단 추상적인것(변화하기 어려운 추상객체)에 의존해야하며(상위모듈(부모개체), 하위모듈(자식개체) 모두 추상화에 의존해야하며) 저수준의 모듈이 변경되어도 고수준 모듈은 타격을 입지 않는 형태가 좋다. (상위모듈이 하위모듈에 의존하면 안된다)

 

요점정리

참조가 필요할경우 가능하다면 구현된 클래스가 아닌 그것의 상위요소 (추상클래스 및 인터페이스)를 우선적으로 참조하라 (구현된 클래스가 수정이 되는경우 이것을 참조하는 객체도 수정이 필요하기 때문이다)


'CS' 카테고리의 다른 글

프로세서, 프로세스  (0) 2023.03.21
주소 바인딩  (0) 2023.03.18
제네릭(Generic)  (0) 2023.03.14
스택트레이스(Stack Trace)  (0) 2023.02.26
객체 지향 프로그래밍 (OOP) - 1  (0) 2022.12.25

스택트레이스란 프로그램이 시작도니 시점부터 현재 위치까지의 메서드 호출목록을 뜻한다.

쉽게말해서...요거다..

java.lang.reflect.InvocationTargetException
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
     at com.mylibrary.ap.xul.builder.handler.MethodCallback.invokeCallback(MethodCallback.java:32)
     at com.mylibrary.ap.xul.builder.handler.ViewHandler.invokeActionResult(ViewHandler.java:581)
     at com.mylibrary.ap.xul.context.impl.ViewContextImpl$2.onActionResult(ViewContextImpl.java:283)
     at com.mylibrary.ap.xul.context.impl.ActionContextImpl.setResult(ActionContextImpl.java:90)
     at com.mylibrary.ap.xul.action.stock.DialogAction.handleDialogClose(DialogAction.java:156)
     at com.mylibrary.ap.xul.action.stock.DialogAction$1.windowClosed(DialogAction.java:142)
     at com.mylibrary.api.Window.fireWindowClosedEvent(Unknown Source)
     at com.mylibrary.api.Window.onClose(Unknown Source)
     at com.mylibrary.api.platform.WindowBase.BaseClose(Native Method)
     at com.mylibrary.api.platform.WindowBase.BaseClose(Unknown Source)
     at com.mylibrary.api.Window.close(Unknown Source)
     at com.mylibrary.ap.xul.ui.MessageDialog.handleButtonClick(MessageDialog.java:145)
     at com.mylibrary.ap.xul.ui.MessageDialog$1.handleClick(MessageDialog.java:134)
     at com.mylibrary.api.ClickableSupport.fireClickEvent(Unknown Source)
     at com.mylibrary.api.ClickableSupport.handleAction(Unknown Source)
     at com.mylibrary.api.Button.actionHook(Unknown Source)
     at com.mylibrary.api.Component.action(Unknown Source)
Caused by: java.lang.NullPointerException
     at com.mycompany.service.impl.PortalManagerImpl.deleteMenuItem(PortalManagerImpl.java:603)
     at com.mycompany.service.impl.PortalManagerImpl.deletePortal(PortalManagerImpl.java:358)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
     at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
     at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
     at org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:66)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
     at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
     at $Proxy54.deletePortal(Unknown Source)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
     at com.mycompany.util.SpringSecurityContextInvocationHandler.invoke(SpringSecurityContextInvocationHandler.java:62)
     at $Proxy84.deletePortal(Unknown Source)
     at com.mycompany.ui.binding.PortalDataProvider.doDelete(PortalDataProvider.java:81)
     at com.mycompany.ui.binding.PortalDataProvider.doDelete(PortalDataProvider.java:12)
     at com.mycompany.ui.binding.AbstractEISDataProvider.delete(AbstractEISDataProvider.java:105)
     at com.mylibrary.ap.xul.binding.dataset.impl.DatasetImpl.doCommit(DatasetImpl.java:90)
     at com.mylibrary.ap.xul.binding.dataset.impl.AbstractDataset.commit(AbstractDataset.java:251)
     at com.mylibrary.ap.xul.binding.dataset.impl.AbstractDataset.deleteRow(AbstractDataset.java:201)
     at com.mylibrary.ap.xul.action.dataset.DeleteDataRowAction.execute(DeleteDataRowAction.java:22)
     at com.mylibrary.ap.xul.context.impl.ViewContextImpl.execute(ViewContextImpl.java:294)
     at com.mycompany.ui.portal.PortalInfoHandler.onPostConfirmDeleteAction(PortalInfoHandler.java:192)
     ... 21 more

상단 코드에서 유심히 볼건 해당코드로

Caused by: java.lang.NullPointerException
     at com.mycompany.service.impl.PortalManagerImpl.deleteMenuItem(PortalManagerImpl.java:603)
     at com.mycompany.service.impl.PortalManagerImpl.deletePortal(PortalManagerImpl.java:358)

603번째줄 또는 358번째 줄에서 에러가 나타나는것을 의미한다

여기서 나타난 에러는 NullPointerException으로 Null값을 가진 객체나 변수를 참조할떄 에러가 나타난다는 의미이다.

if (item == null) {
    throw new NullArgumentException("item");
}

//중간 생략
List<PortalMenu> children = getMenuItems(item.getPortal().getId(), item.getId()); // 603번째 줄

for (PortalMenu child : children) {
    deleteMenuItem(child);
 }

 

출처 : https://jaehoney.tistory.com/51

'CS' 카테고리의 다른 글

프로세서, 프로세스  (0) 2023.03.21
주소 바인딩  (0) 2023.03.18
제네릭(Generic)  (0) 2023.03.14
객체 지향 프로그래밍 (OOP) - 2 (5대원칙)  (0) 2023.03.02
객체 지향 프로그래밍 (OOP) - 1  (0) 2022.12.25

이전에 포스팅을 진행하며 동일성과 동등성에 관하여 언급한 기억이 있는데 다시 상기시킬겸 좀더 상세히 작성하려고 포스팅을 진행한다.

 


동일성과 동등성의 사전적의미는 다음과 같다.

동일성 (identity) - 객체가 완전히 같은경우를 의미한다

동등성 (equality) - 값이 같은경우를 의미한다.

 

한가지 예시를 들어보겠다.

위의 사진처럼 똑같은 모델, 똑같은 스펙의 핸드폰이 있다. (좌측을 A, 우측을 B라 가정)

그렇다면 이것을 동일하다고 설명할 수 있을까?

A와 B는 스펙은 똑같을지언정 제품마다 별도의 제조번호가 부여되었고 이는 2개의 핸드폰이 엄연히 다르다는것을 보여주는것으로 즉, A와 B는 동등(equality)하지만 동일(identity)하지는 않다고 말하는것이다.

이러한 동등성과 동일성을 자바에서는 hashcode 메서드와 equal 메서드로 확인할수 있다. 

 


동등성 - equal() 

public boolean equals (Object anObject){
    if (this == anObject){
    	return true;
    }
    if (anObject intstanceof String)
        String aString = (String)anObject;
        if(coder() == aString.coder()) {
            return isLatin1() ? StringLatin1.equals(value,aString.value)
            			:StringUTF16.equals(value, aString.value);
        }
    }
    return false;
}

상단의 코드는 Object의 내장함수중 equals의 로직을 보여주는 코드로 객체의 값이 일치하는 여부(boolean)를 반환하는 메서드이다 ("=="을 의미한다)

메모리 주소가 아닌 객체 값만을 비교하는 메서드이다.

3항 연산자를 사용해 StingLatin1과 StringUTF16를 비교하여 반환하기로 하는 메서드이다.

 

StringUTF16 = JVM이 문자, 문자열을 메모리에 저장할때 UTF16을 사용하여 인코딩 하는데 이를 같은지를 확인하기 위해 StringUTF16.equal()메서드를 사용하였다.

 

isLatin1

private boolean isLatin1() {
    return COMPACT_STRINGS && coder == LATIN1;
}

 

coder() =

 

byte 배열(value)을 인코딩하는데 사용하는 변수이며 그리고 지원하는 인코딩 타입은 LATIN1, UTF16 이다.

/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*/
private final byte coder;

equals 연산자는 두객체의 동등성을 판별하기 위하여 사용하지만 equals연산자를재정의하지 않으면 내부적으로 == 연산자와 같은 로직을 수행하므로 차이가 없다 그렇기때문에 각 객체의 특성에 맞게 재정의를 하여야 동등성의 기능을 수행한다.

 

 

//int형 equals 연산자
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

//Object의 equals 연산자
public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

//ArrayList의 equals 연산자
private boolean equalsArrayList(ArrayList<?> other) {
    final int otherModCount = other.modCount;
    final int s = size;
    boolean equal;
    if (equal = (s == other.size)) {
        final Object[] otherEs = other.elementData;
        final Object[] es = elementData;
        if (s > es.length || s > otherEs.length) {
            throw new ConcurrentModificationException();
        }
        for (int i = 0; i < s; i++) {
            if (!Objects.equals(es[i], otherEs[i])) {
                equal = false;
                break;
            }
        }
    }
    other.checkForComodification(otherModCount);
    return equal;
}

 


동일성 - hashcode() 

hashcode란 객체를 식별할 하나의 정수값을 의미한다. 일반적으로 객체의 주소값(메모리번지)을 변환하여 생성한 객체의 고유한 정수값이다.

 

(논리적 동등 비교시 hashcode를 오버라이딩 할 필요성이 있다고 하는데 이것은 hashset,hashmap,hashtable방식이며 3항목은 강의 순서에 맞게 나중에 돌아와서 추가로 작성예정..)

public class Key{
	public int number;
    
    public Key(int number){
    	this.number = number;
    }
	
    @Override
    public boolean equals(Object obj) [
		if(obj instanceof Key)	{	//클래스확인용
        	Key compareKey = (key) obj; //캐스팅
        	if(this.number == compareKey.number) {
            return ture;
            }
        }
        return false;
	}
}

 

//instanceof는 서로 알맞는 타입을 찾을때까지 컴파일시 모든 타입을 돌며 검사하는데 이러한 단점때문에 가급적 다형성을 활용한 타입점검이 필요하다

'JAVA' 카테고리의 다른 글

스레드풀  (0) 2023.03.07
프로세스와 스레드  (0) 2023.03.07
예외에 관하여  (0) 2023.02.24
중첩클래스  (0) 2023.02.21
생성자(Constructor)와 this  (0) 2023.02.06

프로그램을 실행할때 사진과 같은 에러가 나오는 경우가 종종 있을것이다.

예전엔 모르고 넘어가도 상관없지만 개발을 하는 입장이 되었으니 이것이 왜 에러가 나타나는지 알아야한다!

오늘은 이러한 에러 및 예외에 관하여 설명하겠다.


에러와 예외

먼저 에러와 예외의 정의를 확인하고 가자.

예외 (Exception) - 코드로 수습할수 있는 미약한 오류 (실행은 됩니다!)

오류(Error) - 코드로 수습 불가능한 심각한 오류로 수습이 안되니 일단 종료시키는것이다. (실행도 안됩니다!, 가장 자주 나타는 오류는 OM으로 사용되는 메모리를 초과할때 나타나는 오류인데 게임 2개를 동시에 켜보면 알것이다..)

여기서 OM이란 out of memory의 약자로 만일 GC(Garbage Collection)가 제대로 실행되지 않는다면 메모리 누수가 점점 쌓여서 OM이 발생될것이다.

 

하단의 사진은 에러와 예외의 관한 계층도이다.

여기서 에러의 종류를 나타내면 다음과 같다.(더있을수도 있다..)

분류 설명
컴파일 에러 (Compile Error) 컴파일 작업시 발생하는 에러(문법적인 오류)
런타임 에러 (Run-time Error) 실행할때 발생하는 에러(무한루프, Nullpoint 등 설계미숙)
논리적 에러 (Logic Error) 사용자가 의도한 작업을 실행하지 못한에러
링킹 에러 (Linking Error) 컴파일 후 이를 연결할때 발생 (헤더파일등이 없는경우 발생)
파스 에러 (Parse Error) 인터프리터 언어에서 나타나는 문법오류

컴파일 : 내가 작성한 "hello world"(고급언어)를 add  esp,12(어셈블리어) -> 010100010101(기계어)로 바꾸는것

인터프리터 : 고급언어로 작성된 코드를 한줄씩 읽어 내려가며 실행하는 프로그램(파이썬, MatLAB)

 

상단에서 오류는 수습이 불가능하지만 예외는 수습이 가능하다고 설명을 해놨는데 이것을 예외처리 한다는 표현을 사용한다. (프로그램 실행시 발생할수 있는 예외의 발생에 대비한 코드를 작성하는것 -> 프로그램의 비정상적인 종료를 막기 위함)

 

에러의 종류

분류 설명
컴파일 에러 (Compile Error) 컴파일 작업시 발생하는 에러(문법적인 오류)
런타임 에러 (Run-time Error) 실행할때 발생하는 에러(무한루프, Nullpoint 등 설계미숙)
논리적 에러 (Logic Error) 사용자가 의도한 작업을 실행하지 못한에러
링킹 에러 (Linking Error) 컴파일 후 이를 연결할때 발생 (헤더파일등이 없는경우 발생)
파스 에러 (Parse Error) 인터프리터 언어에서 나타나는 문법오류

 


 

예외의 종류

 

Exception 클래스 - 사용자 실수에 따른 문제발생 (메모리가 안맞음, 사용자의 파일이 없음 등)

IOException - I/O (Input,Output)오류가 발생하는경우에 throw되는 예외

Runtime Exception 클래스 - 프로그래머 실수에 따른 문제발생 (코드 안맞음)

Runtime Exception 종류

예외코드 설명
NullPointerException  객체의 참조가 없을경우 (Null을 갖는경우)
ArrayIndexOutOfBoundException  배열을 참조하는 인덱스가 잘못된경우(초과하는경우)
NumberFormatException  문자열 <-> 숫자간 변경할수 없는경우 출력
ClassCastException  클래스 형변환(Casting)이 어려울경우 -> 다형성 참고
ArithmeticException  예욎거인 산술조건이 발생하는경우 (5/0.0/6 등)
NoClassDefFoundException 원하는 클래스가 없음
NegativeArraySizeException 배열의 크기가 음수인경우
OutOfMemoryException 사용 가능한 메모리가 없는경우(OM)
interruptedException  
ILLEGAlThreadStateException(대소문자 확인 잘할것..) 스레드가 실행중인데 또 Start를 호출한경우(이중실행시)

상단 외에도 기타 문제가 발생하면 콘솔창에 예외코드가 나타나게 되는데 이러한문제를 방지하는걸 예외처리 한다고 표현한다.

 

예외를 처리하는 방법은 크게 2가지 방법이 있다.

1.try-catch-finally 문법(JDK 7 미만), try-with-resources(JDK 7 이상)문법을 활용해 에러를 처리하는 방법

2.throws 2가지이다.


try-catch-finally 

//try-catch예시
//try { 에러 발생우려 코드 }
//catch ( 예외클래스 예외객체생성 ) { 해당 에러시 실행할 내용 }

try {
	//예외 가능성이 있는 문장을 넣는다
	    
} catch (Exception1 e1) {		//case1
        //Exception1이 발생할 우려가 있는경우
        System.out.println("잔액이 부족합니다");  
} catch (Exception e2) {		//case2
	System.out.println("최소주문금액이 부족합니다");
} catch (Exception e3) {		//case3
	System.out.println("배달불가지역 입니다");
} finally {
	System.out.println("이전화면으로 돌아갑니다");	//예외처리가 발생해도 실행
}

이것을하단의 예시를 참고하면서 설명을진행하겠다.

사용자 정의 예외클래스(A)

//사용자 정의 예외 클래스 (A)

public class BalanceInsufficientException extends Exception {
    public BalanceInsufficientException {}
    public BalanceInsufficientException (String messge) {	//오버로딩
    	super(messge);	//부모클래스 호출
	}
}

에러 발생메서드 (B)

//사용자 정의 예외 발생시키기(B)
public class Account{
	private long balance;
    
    public Account() {
    }

	public lon getBalance()	{	//값얻기.
    	return balance;
    }
    
	public voud deposit (int money) {
    	balance += money;
    }
	
    public void withdraw(int money) throws BalanceInsufficientException {
    	if (balance < money) {
        	throw new BalanceInsufficientException("잔고부족:"+(money-balance)+"원 모자람");
        }
        balance -= money;
    }
}

실제코드(C)

//사용자 정의 예외 테스트 코드(C)
public class AccountExample {
	public static void main(String[] args) {
		Account account = new Account();	//생성자
        
        //예금하기
        account.deposit(10000);	//입금하는금액
        System.out.println("예금액 : "+account.getBalnce());
        
        //출금하기
        try {
        	account.withdraw(30000);	//가격비교용 메서드
        }   catch(BalanceInsufficientException e) {	//에러throws
        	String messge = e.getMessage();
           	System.out.println(messge);
            	System.out.println();
            	e.printStackTrace();
        }
        finally {
        	if(e != null) is.close();
        
        }
    }
}
//deposit = 입금액
//getBalance = 현재 잔고에 있는금액 (정상입금 확인용)
//withdraw = 입금액과 상품가격 비교 메서드

(A)는 사용자가 예외클래스를 정의한 내용

(B)는 예외를 발생시켰을때 실행할 내용을 작성

(C)는 실제 사용되는 코드 이다.

 

(C)를 기준으로 설명하자면

deposit 메서드를 사용하여 10000을 넣었고((B)의 Balance에 삽입), 정상적으로 입금된것인지 확인하기 위하여 출력문을 작성하였다.

이후 withdraw메서드를 활용하여 Balance에서 값을 출력하는 방식인데 Balance는 10000밖에 없으니 당연히 e에러가 출력될것이다.

이것을 (C)의 catch에서 잡아서 (B)로 예외(e)를 (B)로던지고 (B)는 에러에 해당하는 메세지를 출력하게 되는것이다. 

 

하지만 try-catch 방식은 사용이 종료한후 close메서드를 호출해야 자원을 반납할수 있었는데 이러한 방식을 해결하기 위해 나타난것이 try-with-resources 방식이다.


try-with-resources

try-with-resources는 close를 자동적으로 진행되는 기능으로 하단의 코드를 보며 설명하겠다.

 

//try-catch-finally예시
public class TCF{
	public stati void main(String[] args) {
    	FileWriter f = null;
    	try{
            f = new FileWriter ("data.txt");
            f.write("Hello");
        } catch (IOException e){
            e.printStackTrace();
        } finally{
            if(f != null){
            	try {
                    f.close();
                } catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

상단의 코드를 줄인것이 하단의 코드이다.

//try-with-resource예시
public class TWR{
	public stati void main(String[] args) {
    //try with resource statemanets
    	try (FileWriter f = new FileWriter ("data.txt")) {
        	//close를 시켜야하는 코드를 파라미터로 넣는다.
            
            f.write("Hello");
        } catch(IOException e){
            e.printStackTrace();
            System.out.println("에러내용 :"e.getMessage());
            //e.getMessage 메서드는 내장메서드로 어떠한 오류인지 간략하게 출력해주는 메서드
        }
        
    }
}

코드의 줄이 눈에띄게 줄어든것을 확인할수 있다.

try안에 close()가 필요한 클래스를 인스턴스화 시켜 자동으로 f.close를 내부적으로 실행하는것이다.

(Filewriter에 내장된 API중 AutoCloseable란 인터페이스가 try문 안에 선언된 객체를 자동으로 close해주기 때문에 가능)

 

추가로 printStackTrace()는 예외 발생 당시의 호출스택(Call Stack)에 잇던 메서드의 정보와 예외결과를 화면에 출력하는것으로 디버깅할때 유용하게 사용할수 있다.

하지만 성능은 떨어져(리플렉션을 활용해 에러를 추적하기에 오버헤드 발생, 스택정보 취합으로 서버에 부하 우려) 실제 런칭할때는 없애고 테스트 할때만 사용하는게 좋다.


Throws,Throw

예외를 임의적으로 발생시켜 해당 문제가 발생했을때 해당 에러가 나타나도 정상적으로 작동되도록 하는 기능이다.

 

throws - 메서드,새생성자가 수행할때 발생하는 예외를 선언할때 사용하며 상위의 메서드로 예외를 보낸다(던진다)

throw값을 받는곳에서 예외를 처리하도록 예외처리의 주체를 바꾸는것

public class ThrowExample {
						//요기!!
    public void divideNum(int i, int a) throws OneShouldNotInHereException {
        if (a == 1) {

            throw new OneShouldNotInHereException();

        } else {
            System.out.println(i / a);
        }
    }

    public static void main(String[] args) {

        ThrowExample throwExample = new ThrowExample();
        System.out.println("*첫 번째 계산");
        // 예외 발생 가능성이 있는 코드
        int i = 100;
        int a = 1;
        try {

            throwExample.divideNum(i, a);

        }
        catch (OneShouldNotInHereException e) {
            System.out.println("에러: 1로 나눌 수 없습니다.");
        }
    }
}

throw - 강제로 예외를 발생시키는경우 사용, 발생시킨 예외를try-catch로 잡는다. 

나눗셈을 만들었는데 1로 나누면 안되도록 조건을걸어 코딩

public class ThrowExample {

    public void divideNum(int i, int a) {
        if (a == 1) {
            try {
                throw new OneShouldNotInHereException();
            } catch (OneShouldNotInHereException e) {
                System.out.println("에러: 1로 나눌 수 없습니다.");
            }

        } else {
            System.out.println(i / a);
        }
    }

    public static void main(String[] args) {

        ThrowExample throwExample = new ThrowExample();
        System.out.println("*첫 번째 계산");
        // 예외 발생 가능성이 있는 코드
        int i = 100;
        int a = 1;

        throwExample.divideNum(i, a);
    }
}

예외코드

public class OneShouldNotInHererException extends Exception{

}

'JAVA' 카테고리의 다른 글

프로세스와 스레드  (0) 2023.03.07
동일성(identity)과 동등성(equality)  (0) 2023.02.26
중첩클래스  (0) 2023.02.21
생성자(Constructor)와 this  (0) 2023.02.06
오버로딩  (0) 2023.02.02

앞에서의 포스팅은 전부 클래스를 단독으로 사용하는것을 예제로 들었는데 코드를 작성했을때 class안에서 다른 class를 선언한다던가 잘못 복사했을때 내부에 class파일가 여러개 생기는 경우가 종종 있었을것이다.

이것을 자바에선 중첩클래스라고 의미하며 이것에 대해 설명하려고 한다.

 

중첩클래스

중첩 클래스는 클래스 안에서 또다른 클래스가 정의되는것을 말한다.(클래스 안의 클래스)

그리고 이것을 2가지로 나눠서 외부클래스(A), 내부클래스(B)로 나눌수 있다.

//기존클래스
class A{
    int i = 100;
    B b = new B();
}	

class B{
    void method(){
    	A a = new A();
        System.out.println(a.i);
    }
}

class C{
    B b = new B();	//실행가능
    b.method();	//100출력
}

상단의 코드는 클래스를 호출하여 값을 사용하는 코드이다.

이것을 중첩클래스로 바꾼다면 하단과 같은 코드로 변경할수 있다.

//중첩클래스
class A{	//B의 외부클래스
    int i = 100;
    B b = new B();
	
    class B{	//A의 내부클래스
        void method(){
            int a = 200;
            System.out.println(i);	//객체 생성 없이 즉시 외부 클래스 맴버 접근가능
        }
    }
}

class C{
//B b = new B();	//실행불가

//외부클래스의 멤버에 접근하는 방식
//바깥클래스.this.필드OR메서드
    System.out.println(test.this.a);

}

 

class B가 A의 내부로 들어간것을 볼수 있는데 이러한 형태가 중첩클래스의 기본형태이다

그렇다면 굳이 중첩클래스를 사용하는 이유는 무엇일까?

(외부클래스에 종속되어 사용되기 때문에 외부에서 접근이 불가능한 private 접근제어자를 사용한다.)


중첩클래스를 사용하는 이유

1. 외부 클래스의 메서드가 너무 많아 기능별로 분류하기 위하여(개발자가 보고 나중에 유지보수나 활용하기 좋으려고)

2. 선언하려는 클래스가 외부 클래스에 종속적일경우 사용 (얘만 가지고 있는 특수 기믹 또는 특수한 기능 등 -> 마린의 스팀팩, 탱크의 시즈모드 등)

3.특정 클래스를 자신의 클래스 내부적인 용도로만 사용하고자 할때 효율적 -> 만일 다른 클래스에서 내부클래스에 접근하려면 외부클래스를 통하여 접근할수 있다.

중첩클래스의 장점

1. 내부 클래스(B)에서 외부 클래스(A)의 맴버들을 쉽게 접근할수 있다 (객체 생성 없이 사용가능)

2. 코드복잡성(캡슐화) 가능 ->사용하는 이유 1번과 연관

3. 클래스가 타 클래스에 종속적이라면 캡슐화된 클래스들 간의 맴버를 접근할땐 getter와 setter를 사용하여야 하는데 중첩클래스를 사용하는경우 getter와setter를 사용없이 서로의 맴버에 접근이 가능하다.

 


중첩클래스의 종류

중첩클래스는 static으로 선언되지 않은 내부클래스, static으로 선언된 정적클래스로 나뉜다.

내부클래스 - 인스턴스, 지역클래스,익명클래스

인스턴스 클래스(instance class -> IC) - 외부클래스의 맴버변수, 메서드처럼 클래스 정의

스태틱 클래스(static class -> SC) - static키워드를 이용해 클래스가 정의

지역 클래스 (local class -> LC) - 외부클래스의 특정 메서드 안에서 클래스가 정의

익명 클래스 (anonymous calss -> AC) - 익명클래스를 이용해 클래스 정의

//내부클래스 종류
class test{

//인스턴스클래스
    class A{	        
        int inv = 100;
//      static int cv = 100;	                //인스턴스는 static변수를 선언할수 없다.
        final static int CONST = 100;		//final static은 상수이므로 허용된다.
    }

//정적클래스
    static class B{   
        int iv = 200;
        static int cv = 200;	                //static클래스만 static맴버를 정의할수 있다.
    }

//로컬클래스
    void method(){	        
        class C{
            int iv = 300;    
//    	    static int cv = 300;	        //static 클래스만 static맴버 정의가능
            final static int CONST = 300;	//fianl static은 상수이므로 허용!
        }
    }
    

//메인함수    
    public static void main(String args[]){
    	System.out.println(A.CONST);
    	System.out.println(B.cv);
    	System.out.println(C.CONST);	//지역클래스는 메서드 내에서만 허용
    }
    
}

static은 객체생성 없이 사용이 가능해야 하는데 정적클래스를 제외한 나머지는 모두 객체생성을 필요로 하기 때문이다 ,

단, fianl은 상수이므로 사용이 가능하다.

종류 사용불가 선언위치 비고
인스턴스클래스 static 외부클래스와 맴버변수 사이에 선언 외부클래스의 인스턴스 맴버와 관련된 작업
스태틱
클래스
(정적
클래스)
- 외부클래스와 맴버변수 사이에 선언 staic은 메서드,변수,클래스 모두 상위에 존재하는 클래스의 인스턴스를 필요로 하지 않는다.
로컬
클래스
(지역
클래스)
static,
접근
제한자
외부 클래스의 메서드 내부, 초기화 블럭 내부 메모리 실행이 끝나도 힙메모리에 계속 존재,
파라미터는 로컬클래스에 복사해서 사용
익명
클래스
- 외부 클래스의 메서드 내부에 선언 상속 또는 생성과 동시에 재정의하여 사용
-> 주로 1회용 목적으로 사용

 

익명클래스 

// 인터페이스
interface IAnimal {
    public String bark(); // 추상 메소드
    public String run();
}

public class Main {
    public static void main(String[] args) {
        // 인터페이스 익명 구현 객체 생성
        IAnimal dog = new IAnimal() {
            @Override
            public String bark() {
                return "개가 짖습니다";
            }
            
            @Override
            public String run() {
                return "개가 달립니다";
            }
        };	//뒤에 세미콜론을 붙여줘야 한다
        
        // 인터페이스 구현 객체 사용
        dog.bark();
        dog.run();
    }
}

'JAVA' 카테고리의 다른 글

동일성(identity)과 동등성(equality)  (0) 2023.02.26
예외에 관하여  (0) 2023.02.24
생성자(Constructor)와 this  (0) 2023.02.06
오버로딩  (0) 2023.02.02
매개변수  (0) 2023.01.31

생성자란 - 인스턴스가 생성될때마다 호출되는 인스턴스 초기화 메서드

//생성자 코드
class Calculator {			//클래스명
	int left, right;
    
    public Calculator(){			//기본생성자
    	int vlaue;
    }
    
    public Calculator(int left){		//생성자 오버로딩, 매개변수가 있는 생성자
    	this.left = left;
	}
    
    public Calculator(int left, int right){	//생성자 오버로딩,매개변수가 있는 생성자
    	this.left = left;
        this.right = right;
	}

	public void sum(){
    	System.out.println(this.left + this.right);}
    
	public void avg(){
    	System.out.println((this.left + this.right)/2);}
}

public class CalculatorDemo1 {
	public static void main(String[] args){
    
    Calculator a = new calculator();	//기본생성자
    Calculator c1 = new Calculator(10,20);	//매개변수가 있는 생성자
    c1.sum();
    c1.avg();
    
    Calculator c2 = new Calculator(20,40);	//new 뒤에있는 Calculator는 클래스에 있는 생성자를 의미한다.
    c2.sum();					  
    c2.avg();
    }

}

상단의 코드를 확인하면 생성자와 클래스이름이 같은것을 확인할수 있는데

생성자는 클래스가 생성될때 클래스와 동일한 이름을 갖고있는 메서드가 실행하고, 어떤 메소드보다 가장 먼저 실행되도록 되어있다.

 

생성자의 조건

1. 생성자와 클래스의 이름이 같아야한다! (오버로딩 가능)

2. 리턴값이 없다(리턴값이 없음에도 불구하고 void를 안붙인다)

3. 모든 클래스는 반드시 생성자를 가져야한다. (내부적으로 동일한 이름의 메서드가 없는경우 컴파일러가 자동으로 생성을 해준다) 

 

추가로 상단의 코드에서 사용되는 this의 대한 설명도 진행하겠다.


this는 크게 this() - 생성자, this - 참조변수 2가지 용도로 사용되는데 코드를 확인하면서 설명하겠다.

 

생성자 this() - 같은 클래스안에 있는 생성자가 다른 생성자를 호출할때 사용한다.

코드중복을 줄이기는게 주요 목적인데 하단의 코드를 참고하길 바란다.

//생성자 this
class Car{
    String color;
    String gearType;
    int door;
    
/*    Car(String color){			//A
    	this.color ="white"
        this.gearType = "auto",
        thos.door = 4;
    }*/

/*    Car(String color,String gearType){	//B
    	this.color ="white"
        this.gearType = "auto",
        this.door = 4;
    }*/
    
    Car(){					//C
    	this("white","auto",4);			//매개변수를 3개 받는 E호출
    }
    
    Car(){					//D
    	this("black","auto",4);			//매개변수를 3개 받는 E호출
    }

    Car(String c, String g,int d){		//E
    	color = c;
        gearType = g;
    	door = d;
        //door = d						
    }
}

 

생성자 this의 특징은

1. 다른 생성자 호출시 첫번째줄에서만 사용가능

2. 코드중복을 줄이기 위한 목적으로 사용된다. (A,B의 코드중복을 호출하는 형식으로 바꾼것이 C,D이다.)

3. this는 객체를 구별해주며(필드에 this를 붙여 LV와 필드를 구분 -> 메모리 절약효과 ->효율적인 인스턴스 관리)

4. 효율적인 인스턴스 관리가 가능하다.

 

 

//인스턴스 - 객체가 메모리에 할당되어 실제로 사용되는것


참조변수 this (이하 참조this)

(this.에서 뒤에붙는 .(점) 은 ~안에 라고 생각하면 된다.)

 

지역변수(LV)와 인스턴스변수(IV)를 구별할때 사용

Car(String c,String g,int d){		//(A)
    //color,gearType,door는 iv, c,g,d는 lv
    
    color = c;					//IV = LV
    gearType = g;				//IV = LV
    door= d;					//IV = LV
}

Car(String color, String gearType,int door){	//(B)
    //하단의 this.color,this.gearType,this.door는iv
    //상단의 color,gearType,door는 lv

    this.color = color;				//IV = LV
    this.gearType = gearType;			//IV = LV
    this.door= door;				//IV = LV
    //door = door 	 			//LV = LV
}

상단의 코드를 특징과 연관하자면

(B)코드의 마지막 door(이하 K)= door(이하 Z)를 확인해보면 K와 Z가 어떤것이 LV이고 IV인지 구분할수 없는데 이러한 불편함을 없애고자 나타난게 참조 this이다.

 

참조this의 특징은

1. static 메서드에선 참조this사용불가

2. 인스턴스 메서드에서만 사용이 가능하다.

3. IV에 this가 없는경우 this를 자동으로 붙여준다.

'JAVA' 카테고리의 다른 글

예외에 관하여  (0) 2023.02.24
중첩클래스  (0) 2023.02.21
오버로딩  (0) 2023.02.02
매개변수  (0) 2023.01.31
메서드  (0) 2023.01.30

한 클래스 내부에서 동일 이름의 메서드 여러개를 정의하는것(동명이인 같은것)

 

성립조건

1.메서드의 이름이 같아야한다 (동명이인이면서)

2.매개변수 개수, 타입이 달라야한다 (무언가 특정할만한게 다를것 -> 사람이 주민번호까지 다 똑같으면 그건 동일인물이다...)

3.반환타입은 영향이 없다

//"옆반 키작은 A말고 키큰 A한테 쌤이 불렀다고 말해줘!"

 

//오버로딩X (단순 중복정의)
int add (int a, int b) {return a+b;}
int add (int a, int b) {return a+b;}

//오버로딩O (ambiguous 에러 나올수도 있음)
int add (int a, long b) {return a+b;}
int add (long a, int b) {return a+b;}

//에러용 코드
//add(3,3) -> ambiguous 에러발생
add(3l,3)	//정상작동
add(3,3l)	//정상작동

상단 코드에서 차이점은 값을 받는 파라미터(매개변수)가 다르기 때문에 오버로딩이 성립한다.

여기서 ambiguous(모호한) 에러란 컴퓨터가 어떤 코드를 사용하려고 할때 어떤것을 활용해야할지 모르는경우 이렇게 바뀌는것인데 에러용 코드를 보며 설명하겠다.

 

나와 동명이인이 있더라도 그사람만 갖고있는 특징(옷,주민번호,성별 등)을 말하면 누구를 뜻하는지 알수 있는데 여기서 컴퓨터는 파라미터를 다르게 주어지더라도 Casting(강제타입변환) 기능으로 인하여 발생하는 문제이다.

 

Casting(강제타입변환) -> 기본형이 같은 타입끼리 자동적으로 변환하여 다형성을 유지하는 목적(Long->int(O) // String -> char(O) // Long->char(X))

(캐스팅에 관한건 다른 게시글에서 다룰예정입니다)

 

//메서드 오버로딩 예시
//이름같음, 매개변수 다름, 전부 더하는 기능을 수행

calss MyMath3 {

	//예시1
	int add(int a, int b){
    System.out.print("int add(int a, int b - ");
    return a+b;}

	//예시2
    long add(long a, long b){
    System.out.print("long add(long a, long b - ");
    return a+b;}
    
    //예시3
	int add(int [] a){
    System.out.print("int add(int[] - ");
    int result =0;
    for (int k =0; k<a.length; k++){
    	result += a[i];
    return result;}

}

 

여담으로 말하자면 오버로딩의 종류는 3가지가 있다

 

메서드 오버로딩 - 지금까지 상단에서 다룬것이 메서드 오버로딩이다

 

생성자 오버로딩

// 생성자 오버로딩
public class Test{
    String x;
    int y;
    public Test(){			//A
      System.out.println("변화없음");}
      
    public Test(String x) {		//B
         this.x = x;
         System.out.println("x의 값이 "+ x+"로 초기화 되었다.");}
         
    public Test(Sting x, int y) {	//C
         this.x = x;
         this.y = y;
         System.out.println("x의 값이 "+x+", y의 값은 "+y+"로 초기화 되었다.");}
         
 }
 
// 오버로딩된 생성자 사용
public class ConstructorOverloading {
    public static void main(Stirng args[][) {
        Test test1 = new Test();		//A
        Test test2 = new Test("Hi");		//B
        Test test3 = new Test("Hi", 1234);}	//C
}

 

연산자 오버로딩

"+"를 예시로 들면 3가지 기능을 담당하는데 이것을 연산자 오버로딩이라 칭한다.

1. 텍스트를 연결시켜주는 기능

2. 사칙연산을 도와주는 덧셈기능

3. 부호연산자 기능

//1번 예시 - 텍스트 연결
int test1 = 1000;
system.out.println("오늘 배추 가격은 "+test1+"원 입니다!");

//2번 예시 - 사칙연산
int test2 = test1 + 500;

//3번 예시 - 부호연산자
int test3a = +100;	//양수100
int test3b = -100;	//음수100
}

 

'JAVA' 카테고리의 다른 글

중첩클래스  (0) 2023.02.21
생성자(Constructor)와 this  (0) 2023.02.06
매개변수  (0) 2023.01.31
메서드  (0) 2023.01.30
클래스 변수,인스턴스 변수  (0) 2023.01.30

기본형 매개변수 - 변수의 값을 읽기만 가능 (read only)

참조형 매개변수 - 변수값을 읽고 변경이 가능(read & write)

 

기본형 매개변수 에제

//기본형
class Data {int x;}

class test{
pulic static void main(String[] args){		//호출스택에 main생성
    Data d = new Data ();			//main스택에 d생성후 Data값 대입(d엔 Data의 주소값 저장 "*" )
    d.x = 10;					//d가 가리키는 x의 값을 10으로 변경
    System.out.println("main() : x = "+d.x);	//(A)
    
    change(d.x);				//d.x는 data의 주소값이 아닌 1000을 대입
    System.out.println("After change(dx.)");
    System.out.println("main() : x = "+d.x); 	//(B)
    }
#####################
    static void change(int x){			//change 스택에 생성 -> 기본형 매개변수
    x = 1000;					//x는 1000으로 대입
    System.out.println("change() : x = "+x); 	//(C)
	}
#####################
}

해당 코드에서 얘기하고 싶은점은 변수가(d.x)가 어떤 스택에 생성되고 값이 달라지는걸 보여주며 동시에 매개변수를 비교하기 위한 예시코드이다.

이전에 설명한 호출스택과 연결되는데 (A),(B),(C) 모두 d.x를 출력하는 println 라인에 붙어있는걸 확인할수 있다. 

세세하게 설명하자면 

(A) - d.x는 호출스택에서 main에 생성된 d.x의 주소를 의미한다. (포인터)

(B) - d.x는 호출스택에서 main에 생성된 d.x의 주소를 의미한다. (포인터) 
(C) - d.x는 호출스택에서 change에 생성된 d.x를 의미한다. (지역변수)

(A),(B)에 값을 넣는경우 d.x는 x가 들어가있는 주소를 찾아가 값을 바꾼뒤 그 주소를 갖고있는것이고, 실제 값을 출력하는건 Data의 x변수가 값을 갖는것이다.(main이 종료될경우 main에서 조작한 데이터는 전부 원래의 값으로 돌아간다)

(C)는 x의 값을 바꾸는 것인데 여기선 매개변수가 d.x(지역변수)로 인식되어 change에서 생성한 스택(x)에 값을 넣고 출력하는 것이다. -> 여기서 d.x(지역변수)는 스택 종료시 값이 사라지기 때문에 (B)를 실행해도 기존값인 10이 출력되는걸 알수있다.

 


참조형 매개변수 

하단 예제를 읽기에 앞서 "d.x"와 "d.x(지역변수)"는 다른것임을 염두에 두고 읽으면 된다.

//참조형 매개변수
class Data {int x;}

class test{
pulic static void main(String[] args){		//호출스택에 main생성
    Data2 d = new Data2 ();			//main스택에 d생성후 Data2값 대입(d엔 Data2의 주소값 저장 "*" )
    d.x = 10;					//d가 가리키는 x의 값을 10으로 변경
    System.out.println("main() : x = "+d.x);	//(A)
    
    change(d.x);				//d.x는 data2의 주소값이 아닌 1000을 대입
    System.out.println("After change(dx.)");
    System.out.println("main() : x = "+d.x); 	//(B)
    }
#######################
    static void change(Data2 d){		//change 스택에 생성 -> 참조형 매개변수
    d.x = 1000;					//x는 1000으로 대입
    System.out.println("change() : x = "+d.x); 	//(C)
	}
#######################
}

기본형 코드와 참조형 코드에서 달라진점은 change 메소드의 매개변수 타입이 바뀌었단점이다.

 

(C) - d.x는 호출스택에서 "change에 생성된 d.x이며 d.x는 (A)의 주소값을갖고있음"을 의미한다. (참조변수)

(C)는 x의 값을 바꾸는 것인데 여기선 매개변수가 d.x(참조변수)로 인식되어 change에서 생성한 스택(x)에 d.x(지역변수)의 주소값 넣고 출력하는 것이다. -> d.x(지역변수)는 d.x의 주소를 타고 들어가(참조하여) 값을 변경한 것이기에 상단의 기본형 매개변수와 다르게 (B)가 다르게 출력됨을 알수있다. 


참조형 반환타입

 

 

//참조형 반환타입
class Data {int x;}

class test{
pulic static void main(String[] args){		//호출스택에 main생성
    Data3 d = new Data3 ();			//main스택에 d생성후 Data3주소값 대입(d엔 Data3의 주소값 저장 "*" )
    d.x = 10;					//d가 가리키는 x의 값을 10으로 변경
    
    Data3 d2 = copy(d);				//main스택에 d2생성후 data3의 주소값을 대입
    System.out.println("d.x = "+d.x);
    System.out.println("d2.x = "+d2.x); 	//(B)
    }
#######################
    static Data3 copy (Data3 d){	//스택에 copy 생성
    Data3 tmp = new Data3();		//copy 스택에 tmp 객체 생성(엘리먼트)
    tmp.x = d.x;					//tmp.x에 d.x(10) 대입
    return tmp;						//복사한 객채의 주소 (tmp) 반환 -> 참조형 반환타입
    }
#######################
}

반환타입이 참조형이란건 객체의 주소를 반환한다는 의미인데 여기서 return tmp는 주소값 즉, d.x의 주소값을 들고있고 d.x는 10을 갖고 있으므로 d.x와 d2.x는 같은 값(같은 주소)이 출력됨을 확인할수 있다.

 

'JAVA' 카테고리의 다른 글

생성자(Constructor)와 this  (0) 2023.02.06
오버로딩  (0) 2023.02.02
메서드  (0) 2023.01.30
클래스 변수,인스턴스 변수  (0) 2023.01.30
인터페이스란?  (0) 2023.01.13

+ Recent posts