값(입력)을 받아 처리하고 결과를 반환(출력) - 코드의 중복을 줄이고, 코드의 관리가 쉽다, 코드 재사용 가능, 가독성 향상
메서드와 함수는 비슷한 개념이라고 보면 되는데 주요 차이점은
메서드 - 클래스 내부에 존재
함수 - 클래스 내부, 외부에 존재
메서드 - 선언부와 구현부로 구분되며 이를 합하여 메서드영역 이라고 칭한다.
int add(int a, int b) //선언부
{ //구현부 시작
int result = a+b;
return result;
} //구현부 끝
호출스택 (call stack) - 메서드 수행에 필요한 메모리가 제공되는 공간
메서드가 호출되면 호출스택에 메모리가 할당되며 사용이 종료된경우 해제된다 (GC와 같은 원리)
스택- LIFO(후입선출)의 원리로
public static void main(String args[]){
System.out.println("test");
}
상단의 코드를 실행시 하단의 순서에따라 스택이 push되고 pop 되는구조다.
스택구조의 원리를 참고해서 설명하자면
① - 초기상태
② - main이 실행된 상태
③ - main은 대기, println은 실행상태
④ - println이 사용이 끝나 Pop(메모리 반환)되어 바로 밑에 있는 main이 다시 실행
⑤ - 프로그램 종료
메서드는 맨위의 1개만 실행중이며 나머지는 대기중이다.
static메서드, 인스턴스 메서드
IV -> Instance Variable(인스턴스변수)
IM -> Instance Method (인스턴스 메서드)
LV -> Local Variable(지역변수)
CV -> Class Variable(클래스변수)
인스턴스 메서드
인스턴스맴버 (IV,IM) 관련 작업을 하는 메서드
인스턴스 생성후 "참조변수.메서드 이름()"으로 호출
메서드 내에서 인스턴수 변수 (IV)사용가능 -> 객체가 있기 때문에 사용가능?
static 메서드 (클래스 메서드)
객체생성없이 "클래스이름.메서드이름()"으로 호출
인스턴스멤버와 관련없는 작업을 하는 메서드
메서드 내에서 인스턴스 변수(IV) 사용불가 -> 객체가 없기 때문에 사용불가?
두 메서드의 차이점은 하단의 코드를 확인하길 바란다.
//메서드 정의
class MyMath2 {
long a,b; //인스턴스변수 (구분을 위하여 하단의 설명에선 a,b를 aa,bb로 지칭한다.)
long add() { //인스턴스 메서드 -> (I)
return a+b;
}
static long add(long a, long b){ // 클래스메서드 (static메서드) -> (J)
return a+b;
}
}
메서드 정의 코드에서 (I)와 (J)의 차이점은 매개변수가 들어간것, 앞쪽에 static이 붙은것 2가지 차이인데 상세하게 설명하자면
(I)는 파라미터를 없이 a+b를 계산하는데 여기서 a,b는 인스턴스 변수인 aa,bb를 의미한다. (IM는 IV,CV 둘다 사용가능)
(J)는 파라미터(지역변수)를 받고 (a와 aa 다른변수이다.) 호출할시 파라미터가 주어지지 않으면 오류를 출력하게 된다.
인터페이스는 단일객체가 아닌 여러 객체들과 사용이 가능하기에 (한번 선언해두면 필요할때마다 가져와서 사용할수 있기 때문에) 코드의 수정이 필요할경우 사용하는 객체 (파라미터)만 변경하면 다른 값이 반환시키는것(코드의 종속성을 낮추는것)이 주요 목적이다.
또한 인터페이스가 추상메서드만 갖게 된 이유는 기존 메서드와의 충돌로 인하여 이를 없애고자 추상메서드만 갖도록 정한것이다.
인터페이스의 다형성
//다형성예시 코드
//추상메서드
abstract class Unit{
int x, y;
abstract void move (int x, int y);
void stop() {System.out.println("멈춤")};
}
//인터페이스 선언
interface Fightable{
void move(int x, int y); //public abstract 생략(추상메서드)
void attack(Fightable f); //public abstract 생략(추상메서드)
}
//인터페이스 구현(오버라이딩)
class Fighter extnes Unit implments fightable{
//오버라이딩 규칙 : 조상보다 접근제어자가 낮으면 안된다.
//인터페이스 선언 코드의 접근제어자는 public이다
pbulic void move(int x, int y) {
System.out.println(x+","+y+"위치로 이동");
}
pbulic void attack(Fightable f) {
System.out.println(f+"를 공격");
}
}
//메인함수
public class FighterTest{
public static void main(String[] args){
Fighter f= new Fighter();
f.move(100,200);
f.attack(new Fighter());
}
}
다형성 코드에서 주목해서 볼것은 Fightable(인터페이스)가 Fighter(자손객체) 를 참조해서 받는모습을 볼수있는데 이것은 상속과 다형성을 설명하면서 이야기하겠다.
이전에 배운 상속은 부모클래스가 자식클래스에게 맴버를 물려주는것을 상속이라고 하고있는데 (A)같은 경우는 매개변수 타입이 인터페이스인경우 이것을 구현한 클래스(자손클래스)의 인스턴스만 사용이 가능하다.(사용 가능한 메서드는 move와attack 2개이고 stop은 사용 불가능하다. -> 부모님이 주신 유산을 받은 자식도, 물려준 부모도 쓸수 있되 자식이 산건 못쓴다고 생각하면 된다.)
//부모의 자손참조
Unit u = new Fighter(); //조상클래스의 자손객체참조
Fightable f = new Fighter(); //인터페이스의 자손객체참조 (A)
//단, f는 상단의 Fightable의 메서드만 사용이 가능하다.
//즉,f.move(100,200); OR f.attack(new Fighter()); 2개는가능 f.stop();는 불가능
다형성은 자식객체가 부모객체로 자동변환(Casting)이 가능하다는 특징이 있는데 인터페이스에서 이러한 예시를 보여주는것이 (A)이다.
하단의 주석처리를 해놨듯이 부모클래스는 자식클래스에서 구현된것만 사용가능하다.
조금더 알기쉽게 설명하자면 포유류(부모) / 박쥐,고래(자식)으로 생각하고 하단의 사진을 보길 바란다.
포유류인 고래는 헤엄을 칠수 있고 박쥐는 날수 있다.(부모클래스를 자식클래스에게 상속)
박쥐는 포유류이며 날수있다 하지만 헤엄은 못친다.(자식클래스를 부모클래스로 변환)
고래는 포유류이며 헤엄칠수 있다. 하지만 날수는 없다. (자식클래스를 부모클래스로 변환)
이런식으로 이해할수 있다.
//(B)
Fightable method(){
....//구현내용
Fighter f = new Fighter(); //여기서부터
return f; //여기까지 한줄로 줄여 쓰면 return new Fighter(); 이다.
}
(B)는메서드 반환타입이 인터페이스면 반환받는 변수의 타입도 인터페이스이어야 한다.
이것은 인터페이스의 구현한 객체를 반환해야한다 -> 인터페이스가 아닌 추상메서드, 객체등을 반환할수 없기 떄문이다.
(A)에서 나타나 있듯이 메서드의 반환타입이 인터페이스면 반환받는 변수도 인터페이스이어야 한다.
여기서도 자식객체(retutn f)가 부모객체(Fightable)로 자동변환(Casting)이 되기 때문에 해당코드를 실행해도 문제없는 모습을 확인할수 있다.
인터페이스 선언맴버
//인터페이스 선언 방법
//interface 인터페이스 이름
interface PlayingCard{
//상수필드 : 타입 상수명 = 값;
public static final int SPADE = 5;
final int DIAMOND = 3; //public static final int로 자동 치환
static int HEART = 2; //public static final int로 자동 치환
int CLOVER = 1; //public static final int로 자동 치환
//추상메서드 : 타입 메소드명(매개변수,..);
public abstract String getCardNumber();
String getCardKind(); //public abstract String getCardkind로 자동 치환
//디폴트 메서드 : default 리턴타입 메소드명 (매개변수,..) {...내용...}
default void setCard(boolean gameplay){
if(gameplay){
System.out.println("게임 시작");
} else {
System.out.println("게임 종료");
}
}
//정적메서드 : static 타입 메소드명(매개변수) {..내용..};
static void changeSatge(){
System.out.println("스테이지 변경");
}
}
상단은 인터페이스의 예시인데 인터페이스의 선언에서 허용되는건 다음과 같다.
1.상수필드 - 상단에 기술한대로 인터페이스는 데이터를 저장할수 없고 추상 메서드의 집합이므로추상메서드의 규칙에 반하지 않는 값이 고정된 상수는 사용이 가능하다
2.추상메서드 - 메서드인데 내용( {..내용..} ) 이 없는 메서드 (내용은 구현단계에서 오버라이드로 구현한다)
3.디폴트메서드 - 기존 인터페이스를 확장하여 새로운 기능을 추가하기 위한 목적이다. (실행내용을 작성한것)
4.정적메서드 - 객체가 없어도 인터페이스 호출이 가능하다
(인터페이스 내부의 필드는 자동적으로 public static final이 접두로 붙는다 -> 그래야 추상메서드의 목적인 다른메서드에서 참조가 가능하니까)
추상클래스와 인터페이스 차이점
추상클래스 - 일반 클래스 인데 추상메서드를 갖고있는것 (일부 미완성)
인터페이스 - 추상메서드만 갖고있는것 (완전 미완성 - 구현된게 없음)
만일 하단의 코드처럼 추상클래스의 attack 메서드가 미구현된 경우 (인터페이스에서 일부만 구현하는 경우) 추상클래스로 인식이 되며 앞쪽에 abstract를 붙여준다.
//인터페이스 예시
interface Play_Unit{
void move(int x, int y);
void attack(string m); //public abstract 생략됨
}
//추상클래스 예시) (B)
abstract class Play_Unit2 implements Play_Unit{
public void move(int x, int y){
System.out.println("캐릭터가"+x+"위치에서"+y+"위치로 이동합니다");} //구현됨
public void attack(string m); //미구현됨
}
//인터페이스 구현
//class 클래스명 implements 인터페이스 이름{..}
class Unit implements Play_Unit{
public void move(int x, int y){
System.out.println("캐릭터가"+x+"위치에서"+y+"위치로 이동합니다"); //구현완료
}
public void attack(string m){
System.out.println("캐릭터가+m+"를 공격합니다."); //구현완료
}
}
인터페이스의 특징
1. 인터페이스는 다중 상속이 가능하다. (extends 는 동일한 메서드가 있을경우 이전에 설명한 충돌의 우려 때문에 다중상속 불가 -> ambiguous 에러)
2. 여러개의 인터페이스를 상속받은 인터페이스를 클래스에적용할 땐 모든메서드를 구현해 줘야한다.
3. 추상메서드와 상수로만 이뤄진다(코드의 종속성을 낮추기 위함, 접근제어자는 public이 붙으며 상수처럼 값이 명확하거나 메서드가 추상적이어야 타객체에서 접근할때 문제가 없다)
4. 생성자 사용 불가(객체가 아니기 때문이다)
5. 인터페이스의 조상은 인터페이스만 가능하다
인터페이스 구현
인터페이스에 구현된 추상메서드를 완성하는것으로 클래스 뒤쪽에 implements 인터페이스명을 붙인다
또한 기존 인터페이스선언에 있던걸 구현하는건 오버라이드기능을 활용하는 것이다.
//인터페이스 구현
//class 클래스명 implements 인터페이스 이름{..}
class Unit implements Play_Unit{
public void move(int x, int y){
System.out.println("캐릭터가"+x+"위치에서"+y+"위치로 이동합니다"); //구현완료
}
public void attack(string m){
System.out.println("캐릭터가+m+"를 공격합니다."); //구현완료
}
}
여담..
기존의 인터페이스는 추상메서드만 가질수 있지만 디폴트 메서드가 생김으로써 메서드간 충돌할 우려가 생긴것인데 이것은 크게 인터페이스간 충돌, 디폴트메서드와 조상클래스 메서드간 충돌로 나뉘는데
그렇지만 int형 도 4byte인데 float과 int형이 같은 메모리 크기를 갖는건 무언가 이상하다.
그이유인 지수와 가수에 대하여 알아보자
상단의 사진은 IEEE가 제공하는 부동소수점 방식의 형태인데 가수부와 지수부의 크기가 다른것을확인할수 있다
이러한 이유는
float형은 4byte -> 32bit이며 (7자리)
double형은 8byte -> 64bit를 (15자리)갖는데 이것은 표현하는 자릿수의 차이이며
이를 설명하기 위한 코드는 아래와같다
//코드1
int a = 1;
float b = 1.1f; (float은 뒤에 f붙이기)
double c = 1.1;
//자리수 테스트
float testFloat = 0.1234567890123456789f;
double testDouble = 0.1234567890123456789;
System.out.println(testFloat);
System.out.println(testDouble);
코드1과 사진1을 참조하면 뒤의 자릿수가 2배차이나는것 확인할수 있고 뒤의 숫자는 자릿수를 넘어간 관계로 자동적으로 반올림 된것을 확인할수 있다.
조금더 자세하게 설며하면 하단의 사진을 참조하면 된다.
부동 소수점 방식은 부호는+(0) 또는 1(-)으로 제일 앞쪽에 , 지수, 가수로 나뉘는데
가수 - 곱하는수 (2)
지수 - 제곱하는 부분을 뜻하며
float은 128~126 (256 = 2^8)으로 총 구현 가능한 숫자는 128+126+0 총 255 이며 여기에 bias(127)을 추가하여 총 256이 나오는것이다.
1024,-1022(총 구현가능한 숫자는 1024+1022+0으로 총 2047개이며 1024 = 2^10) 여기서도 bias(1023)을 추가하여 2048이 되는것이다.
여기서 의문을 가질것은 지수부분과 가수부분의 비트가 23,52로 측정된 이유가 궁금할것이다.
이것은 소수점 표현방식의 차이로 나타나는 숫자이다.
실수의 표현방식은 고정소수점, 부동소수점 방식이 있는데 float형을 기준으로 설명하자면 아래와 같다.
고정소수점
float의 경우 32bit를 부호 (1bit), 정수(15bit),소수(16bit)로 구분하는 방식인데 이는 정밀한 값을 표현할수 없다는 단점이 있다 (정수가 20bit로 넘어가거나 소수가 20bit로 넘어가는 경우 정확한 값이 출력할수 없다.)
부동소수점
고정소수점의 정확한 값을 표현하지 못한다는 문제점을 보완한 방식으로 모든 2진수 숫자를 1.xxx의 형태로 출력하는 것으로 현재 가장 보편적으로 사용된다.
32bit를 부호(1bit), 소수점이 움직일 위치 - 지수부(8bit), 소수점이 움직인 결과에서 소수점 뒤로 오는 부분을 채워넣는것(23bit)
double형의 경우 지수부에 11bit를 할당해 더 많은 양(소수점이 움직일수 있는 범위가 커짐)을 저장할수 있게된것이다.
bias
IEEE에선 지수에 해당하는 숫자 + bias(127)을 더한 숫자가 2진수로 바뀌어 들어가는데
bias를 쓰는 이유는 지수가 음수일수도 있기 때문에 들어가는것이다.
float의 경우 -126~128인데 두 숫자의 중간값인 127을 할당했고
double의 경우 - -1022~1024인데 두 숫자의 중간값이 1023을 할당한것이다.
프로그래밍에서 필요한 데이터를 추상화시켜상태와 행위를 가진 객체를 만들고 그 객체들 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법
-장점
▶코드 재사용이 용이
남이 만든 클래스를 가져와서 이용할 수 있고 상속을 통해 확장해서 사용할 수 있다.
▶유지보수가 쉬움
절차 지향 프로그래밍에서는 코드를 수정해야할 때 일일이 찾아 수정해야하는 반면 객체 지향 프로그래밍에서는 수정해야 할 부분이 클래스 내부에 멤버 변수혹은 메서드로 존재하기 때문에 해당 부분만 수정하면 된다.
▶대형 프로젝트에 적합
클래스 단위로 모듈화시켜서 개발할 수 있으므로 대형 프로젝트처럼 여러 명, 여러 회사에서 프로젝트를 개발할 때 업무 분담하기 쉽다.
-단점
▶처리 속도가 상대적으로 느림
▶객체가 많으면 용량이 커질 수 있음
▶설계시 많은 시간과 노력이 필요
OOP의 4가지 특징
1. 캡슐화
2. 추상화
3. 다형성
4. 상속
오버라이딩: 부모클래스의 메서드와 같은 이름, 매개변수를 재정의 하는것.
오버로딩: 같은 이름의 함수를 여러개 정의하고, 매개변수의 타입과 개수를 다르게 하여 매개변수에 따라 다르게 호출할 수 있게 하는 것.
1. 캡슐화
원래의 목적은 객체의 속성(data fields)과 행위(methods)를 하나로 묶고, 실제 구현 내용 일부를 외부에 감추어 은닉한다.
즉, 사용하려는 메서드에 접근제어자를 활용하여 기존의 데이터에 손상이 가지 않도록(접근 못하도록) 하는것으로 주요목적은
1. 메서드를 통한 간접접근이 가능하도록 하는것
2. 내부적으로만 사용되는 부분을 감추기 위하여 사용
이것을 조금더 상세하게 설명하자면
//접근 가능 코드
public class Time{
public int hour = 12; //0~23
public int minute = 35; //0~60
public int second = 27; //0~60
//출력시 12:35:27
}
public class TimeTest{
Time t = new Time(); //생성
t.hour = 50; //Time 맴버변수에 직접 접근
System.out.println(t.hour); //출력시 50:35:27이 출력된다...
}
}
한가지 예시로 실시간 반영 서비스를 위한 코드를 작성하고 있다고 가정했을때 public으로 구현된 hour에 직접 접근하여 50을 넣는다면 코드상으론 문제가 없지만 실제 서비스를 구현한다고 했을때 사용자는 이로인해 여러 애로사항이 발생할것이다
여기서 발생하는 문제점은
문제점1. 접근제어자가 public이라 데이터의 원형이 바뀔 우려가 있다.
문제점2. 시간,분,초가 기준범위를 넘어선 값이 입력될 우려가 있다(현재시간 50시입니다..)
//수정한 코드
public class Time2{ //접근제어자 변경으로 문제점1 해결
private int hour = 12; //0~23
private int minute = 35; //0~60
private int second = 27; //0~60
//출력시 12:35:27
public int getHour() {return hour;} //다른 메서드에서 호출은 가능해야 하기 때문에 public으로 선언
public void setHour() (int hour) { //조건문을 활용해 문제점 2 해결
if (hour < 0 || hour >23) return;
this.hour = hour;
}
}
public class TimeTest {
public static void main(String[] args){
Time t - new Time();
t.setHour (21);
System.out.println(t.getHour));
}
}
이것이 캡슐화가 진행되도록 수정된 코드이다.
문제점1은 public에서 private로 접근제어자를 변경하여 데이터의 원형을 참조할수 없도록 하였다
문제점2는 조건문을 활용하여 정해진 범위만 들어가도록 하였다
추가로 hour은 private로 선언하여 외부에선 접근이 불가능 한데 이를 활용하기 위하여 public을 사용하여 외부의 다른 클래스에서 사용이 가능하도록 하였다.
객체의 속성과 행위를 하나로 묶고-> 시간과 관련된 객체, 행위(유효한지 확인)를 한곳에("Time2"메서드) 묶고
실체 구현 내용 일부를 외부에 감추어 은닉한다 -> private를 활용하여 외부에서 접근할수 없고 참조하는 형식으로만 진행된다.
두 코드의 차이점은 타입이 불일치 한다는것을 확인할 수 있는데 이것을 조금더 자세히 설명해보겠다.
상단에서 다형성이란 조상타입참조변수(Controller)가 자손타입 객체를 다루는것(SmartTv, TvController)이라고 설명을 해놨는데 일단 타입이 일치할때(A)와 일치하지 않을경우(B)의 차이부터 알아보자
(A)는 동일한 타입을 생성하기에(SmartTv-SmartTv) (A)가 사용가능한 맴버의 숫자는 7개이다. (Controller + SmartTv)
(B)의 타입이 다르기 때문에 맴버는 7개이나 사용가능한 맴버는 5개이다. (Controller)
그러면 제약이 더 많은것 같은데 다형성의 장점이 무엇이냐?
1. 유지보수가 쉽다. (상단 코드의 예시로 Controller만 건드리면 자식클래스도 모두 변경되기 때문이다.)
2. 느슨한 결합 (클래스간 의존성이 줄어들며 확장성이 늘어나고 결합도가 낮아 안정성이 높아진다. -> 참조타입이 달라도 사용이 가능하기 때문)
3. 재사용성 증가 (한번 선언해두면 필요할때마다 호출해서 사용이 가능하기때문 참조타입이 달라도 된다!)
조건
자손타입 참조변수로 조상타입 객체를 가리킬순 없다. (업캐스팅만 가능하다)
한가지 예시를 들면 갤럭시10(이하 "10v")에서 20(이하 "20v")으로 넘어갈때 터치기능 외에 스와이프 기능이 추가되었다 가정을해보자
그리고 10v에서 작성된 프로그램 (A)는 터치기능만 있으면 작동이 되지만
20v에서 작성된 프로그램이(B)는 터치기능과 스와이프가 필수로 필요하다고 가정을 했을때
10v에서 사용하던(A)는 20v에선 사용이 가능하다. -> 20v도 터치기능은 내장되있기 때문이다!
그러나 20v에서 사용하던(B)는 10v에선 사용이 불가능하다. -> 스와이프 기능이 없기 때문이다!
class phone10V{
boolean touch; //터치기능
}
class phone20V extends phone10V{
boolean swape; //스와이프기능
}
phone10V A = new phone20v(); //가능 - A는 터치만 있으면 사용가능
phone20V B = new phone10v(); //불가능 - B는 스와이프까지 있어야 사용가능
3항연산자와 if-else의 가장큰 차이점은 표현식(3항 연산자)과 명령문(if-else)의 차이이다.
표현식(expression)은 항상 어떠한 값을 계산하며단일 값으로 평가되는 코드이다
표현식이 모여서 명령문(statement)을 만드며 명령문은 하나의 실행 단위로 존재한다.
표현식은 값을 계산하지만, 명령문은 결과값을 어떤 변수에 할당 및 함수의 인수로 넘겨주는것은 할수없습니다.
그렇지만 대부분의 경우 if 문은 명령문 그 이상 동작을 수행합니다. 이들은 부수효과를 발생시키고 변수를 변경합니다. 부수효과의 영향과 가변적인 공유변수를 다루는 제어 흐름을 완전히 파악하지 못한다면 if 문이 작동하는 모든 그림을 볼 수 없습니다.
장점
삼항 연산자는 임시 변수를 필요로하지 않으므로 단기기억력의 부하가 줄어 듭니다.
삼항식은 신호 대 잡음비가 더 좋습니다. - 버그가 날시 원인을 찾기 쉽다
if문은 부수작용과 변이를 권장하지만, 삼항연산자는 순수한 코드를 권장합니다.
if보다 연산이 빠르다 - 과거보다 처리속도가 빨라져 if를 사용해도 되지만 프로그램 성능이 중요시 된다면 삼항 연산자를 쓸것
단점
브레이크 포인트를 만들수 없다
조건에 따른 결과를 쉽게 이해하기엔 어렵다 - IF는 문제발생시 시험용객체를넣어 어디가 문제인지 확인이 가능하지만 3항연산자는불가능하다 (0 또는 1만 나오기떄문)
// 함수적이지 않은함수 예시
// b가 0인경우 에러가 발생한다.
public static int div(int a, int b) {
return a / b;
}
// 변경한 함수
public static int div(int a, int b) {
return (int) (a / (float) b);
}
부수효과
부수 효과는 함수 내의 실행으로 인해 함수 외부가 영향을 받는 것을 의미한다. 함수의 매개 변수의 값이 변경되어, 이로 인해 함수를 사용하는 코드에 영향을 주거나, 함수의 외부 세계인 데이터베이스, 파일 시스템, 네트워크로 데이터 이동이 함수 실행 중에는 발생하지 않아야 함수형 코드가 된다.