[2] Java - DataType, Variable , Array

harold·2021년 2월 1일
0

java

목록 보기
11/13

학습할 것

  • 프리미티브 타입 종류와 값의 범위 그리고 기본 값
  • 프리미티브 타입과 레퍼런스 타입
  • 리터럴
  • 변수 선언 및 초기화하는 방법
  • 변수의 스코프와 라이프타임
  • 타입 변환, 캐스팅 그리고 타입 프로모션
  • 1차 및 2차 배열 선언하기
  • 타입 추론, var

Primitive Type 종류와 값의 범위
그리고 기본 값


Primitive Type 정의

데이터가 메모리에 어떻게 저장되고
프로그램에서 어떻게 처리되어야 하는지를
명시적으로 알려주는 역할을 수행하는 기본 자료형

값 할당 시 변수의 주소값이 저장되는 데이터 타입

값 할당 시
JVM Runtime Data Area -> Stack 영역 순 으로 저장


Primitive Type 의 종류

DivisionTypeDefaultAreaSize (byte)비고
정수형byte0-128 ~ 1271
short0-32,768 ~ 32,7672
int0-2,147,483,648 ~ 2,147,483,6474
unsigned int00 ~ 4,294,967,2954java 8 이상부터 가능
signed long0L-2^63 ~ 2^63 -18
unsigned long0L0 ~ 2^64 - 18java 8이상부터 가능
실수형float0.0f(3.4 x 10^-38 ) ~ (3.4 x 10^38) 의 근사값4
double0.0(1.7 x 10^-308 ) ~ (1.7 x 10^308) 의 근사값8
문자형char'\u0000'0(\u0000) ~ 65,535(\uffff)2
논리형booleanfalseTrue, False1

정수형 타입

소수 부분이 없는 수

정수형 데이터 타입을 결정할때 데이터의 최대 크기를 고려해야 함
만약 해당 타입이 표현할 수 있는 범위를 벗어난 데이터를 저장한다면
overflow가 발생해 전혀 다른 값이 저장될 수 있기 때문

정수형 타입 오버플로우 발생 예제

package me.basic.step2.primitive;

public class OverflowExample {
    public static void main(String[] args) {
        byte num1 = 127;
        byte num2 = -128;

        num1++;
        num2--;

        System.out.println( "num1 of value : " + num1 );
        System.out.println( "num2 of value : " + num2 );
    }
}

// 결과값
// > 정수형 타입 오버플로우 발생
num1 of value : -128
num2 of value : 127

Process finished with exit code 0

실수형 타입

  • 소수부나 지수부가 있는 수
실수형 타입지수의 길이가수의 길이유효 자릿수
float8 비트23 비트소수 부분 6자리까지
double11 비트52 비트소수 부분 15자리까지

실수의 표현 방식

고정 소수점 방식

  • 소수부의 자릿수를 미리 정하여, 고정된 자릿수의 소수를 표현
  • 정수부와 소수부의 자릿수가 크지 않으므로, 표현할 수 있는 범위가 매우 적다는 단점이 있음

Image

부동 소수점 방식

  • 하나의 실수를 가수부와 지수부로 나누어 표현하는 방식
  • 부동 소수점 방식은 다음 수식을 이용하여 매우 큰 실수까지도 표현
  • 대부분의 시스템에서는 부동 소수점 방식으로 실수를 표현

IEEE 부동 소수점 방식

  • 현재 사용되고 있는 부동 소수점 방식은 대부분 IEEE 754 표준을 따르고 있음

Image
Image

부동 소수점 방식의 오차

  • 부동 소수점 방식을 사용하면 고정 소수점 방식보다 훨씬 더 많은 범위까지 표현 가능하지만 부동 소수점 방식에 의한 실수의 표현은 항상 오차가 존재한다는 단점이 있음

문자형 타입

  • 작은 정수나 문자 하나를 표현

논리형 타입

  • 참 ( true ) 이나 거짓 ( false ) 중 한가지 값만 가질수 있는 불리언 타입

리터럴


  • 소스 코드 내에 데이터 값을 그대로 쓴 상수
  • 리터럴도 타입이 있는데 그런 타입은 자바 컴파일러가 소스 코드를 컴파일할 때 자동으로 부여된다.

자바 컴파일러가 부여하는 리터럴 타입

  • 소수점이 없는 수치 리터럴 -> int 타입 부여
  • 소수점이 있는 수치 리터럴 -> double 타입 부여
  • 큰따옴표로 묵여진 텍스트 -> string 타입 부여
  • 작은따옴표로 묵여진 텍스트 -> char 타입 부여

리터럴에도 타입이 있다는 사실을 잘 모르고 작성한 프로그램

package me.basic.step2.primitive;

public class LiteralExample {
    public static void main(String[] args) {
        float num;
        num = 12.34;	// 컴파일 에러 발생
        System.out.println( num );
    }
}

왜 컴파일 에러가 났을까?

이 에러의 원인은 자바 컴파일러가 12.34라는 리터럴에 double 타입을 부여했기 때문이다.

그래서 num = 12.34l라는 명령문은 double 타입의 값을 float 타입의 변수에 대입하는 명령문이 된 것이고, double 타입의 값을 float 타입의 변수에 넣으면 데이터의 정확도에 손상이 갈 수 있기 때문에 이런 컴파일 에러가 발생한 것이다.

이런 문제에 부딪히지 않기 위해서 우리는 리터럴 타입에 대해 알아야 하는데, 리터럴 타입은 표기 방법에 의해 결정된다.

정수형 리터럴

정수형 리터럴은 일반적인 숫자( 정수 데이터 )를 의미 함.

표현할 수 있는 방법

  • 10진수 - 234
  • 8진수 - 030
  • 16진수 - 0xA4
  • 2진수 - 0b1010

정수형 리터럴의 표기방법

소수점이 없는 10진수 리터럴은 int타입으로 간주 됨.

< 정수형 리터럴의 표기방법 예제 >

  • 아라비아 숫자로만 구성된 정수 리터럴은 int타입
    120

이때 주의할 점은 0으로 시작되는 정수는 10진수처럼 보여도 8진수로 해석됨.

  • 0으로 시작되는 정수 리터럴은 8진수로 취급
    024

컴퓨터는 바이트 단위로 데이터를 처리하기 때문에 10진수보다 16진수로 정수를 표기하는것이 많아 그럴때는 10부터 15까지 숫자를 A,B,C,D,E,F or a,b,c,d,e,f로 표시, 제일앞에 0x나 0X를 붙이면 된다.

  • 0x또는 0X로 시작하는 정수 리터럴은 16진수로 취급

  • 10부터 15까지 숫자는 A,B,C,D,E 또는 a,b,c,d,e로 표시
    0x30A1


< int 타입 리터럴 사용 예 >

package me.basic.step2.primitive;

public class LiteralExample2 {
    public static void main(String[] args) {
        System.out.println( 120 );
        System.out.println( 024 );
        System.out.println( 0x30A1 );
        System.out.println( 0x0030a1 );
    }
}

< 결과값 >

120
20
12449
12449

< 정수형 리터럴 예제 >

public class LiteralExample {
    public static void main(String[] args) {
        // 정수형
        // byte , short , int , long

        // 10진수
        int i1 = 10;
        System.out.println(  "i1 : " + i1 );

        // 2진수, 8진수, 16진수
        int i2 = 0b1010;
        System.out.println( "i2 : " + i2 );

        int i3 = 030;
        int i4 = 0xA4;

        System.out.println( "i3 : " + i3 );
        System.out.println( "i4 : " + i4 );

        byte s1 = 10;
        System.out.println( "s1 : " + s1 );

        byte s2 = 127;
        System.out.println( "s2 : " + s2 );

        long l1 = 10;
        // long 은 int 보다 커서 15 = l1 이라는 공식이 성립 안됨
        System.out.println( "l1 : " + l1 );

        // 형변환
        // 묵시적 형변환 : 작은 자료형 -> 큰 자료형
        // 명시적 형변환 : 작은 자료형 -> 작은 자료형 - 변환형에 이름 기입
        int i5 = (int)l1;   // 이때 (int)로 long을 변환 시켜 준다면 이 공식은 성립 함
        System.out.println( "i5 : " + i5 );

        long l2  = i5;
        System.out.println( "l2 : " + l2 );

        // char <-> int
        char c1 = 'A';
        System.out.println( "c1 : " + c1 );

        // ascii 코드 - 영문자, 숫자를 저장할 때 사용하는 내부 코드
        // 유니코드 - 영문자 이외의 문자를 저장할 때 사용하는 내부코드
        System.out.println( "(int)c1 : " + (int)c1 );

        // char 상태로 전환하면 그 값에 따른 B로 바뀜
        int c2 = 'A' + 1;
        System.out.println( "(char)c2 : " + (char)c2 );
    }
}

< 결과값 >

i1 : 10
i2 : 10
i3 : 24
i4 : 164
s1 : 10
s2 : 127
l1 : 10
i5 : 10
l2 : 10
c1 : A
(int)c1 : 65
(char)c2 : B

long 타입 리터럴의 표기 방법

정수 리터럴의 마지막에 대문자L이나 소문자l을 쓰면 long타입으로 간주

< long 타입 리터럴의 사용 예를 보여주는 예제 >

package me.basic.step2.primitive;

public class LiteralExample2 {
    public static void main(String[] args) {
        long num = 1234567890123L;
        System.out.println( num );
    }
}

< 결과값 >

1234567890123

만약 위 예제에서 L자를 빼면?
java: integer number too large: 1234567890123
위 결과와 같이 컴파일 에러가 발생한다.

다른 타입의 정수 리터럴은 어떻게 표기할까?

long 타입의 리터럴 표기 방법이 따로 있다면, byte타입이나 short 타입 리터럴 표기 방법도 따로 있을까?

byte나 short 타입을 위한 리터럴 표기 방법은 따로 없다.

실수형 리터럴

  • 소수점을 가진 실수현 데이터를 의미

< 실수형 리터럴 예제 >

  • 일반적인 실수 표현 방식

    • 3.14
  • 지수를 이용한 표현 방식

    • 6.02E23
  • 간단한 float 형을 표현

    • 2.718F
  • double 형을 명시한 큰 실수 데이터를 표현

    • 123.4E + 306D

부동소수점 리터럴의 표기 방법

  • 소수점이 있는 10진수 리터럴은 -> double형으로 간주 됨
  • 소수점과 아라비아 숫자로 구서된 리터럴은 double형으로 취급됨
    • 12.025

double타입 리터럴에서는 정수부나 소수부 중 한쪽이 0일 경우에 생략가능,
하지만 이때 소수점까지 생략하면 안된다, 그렇게 하면 정수 리터럴과 구분이
되지 않는다.

< double형 타입 리터럴 예 >

  • 12.0과 동일

  • 0.025와 동일

    • .025
  • 12 x 10**10 을 표현하는 부동소수점 리터럴

    • 12e100

부동소수점 타입은 아주 큰 수나 작은 수의 표현에도 많이 사용, 지수와 기수로 표기하는 것이 편리할 때도 있음, 그럴때는 기수를 먼저 쓰고, 그 다음에 대문자 E나 소문자 e를 쓰고, 그다음에 지수를 사용하면 됨

  • 0.25 x 10**-20 을 표현하는 부동소수점 리터럴
    • 0.25E - 20

부동소수점 리터럴도 정수 리터럴처럼 16진법의 지수, 기수로 표현
이때는 E나 e대신 P나 p를 써야 함

  • 0xA1.27 x 16**5 값을 표현하는 부동소수점 리터럴
    • 0xA1.27p5

< double 타입 리터럴 사용 예제 >

package me.basic.step2.primitive;

public class LiteralExample2 {
    public static void main(String[] args) {
        System.out.println( "12.025 : " + 12.025 );
        System.out.println( "12e3 : " + 12e3 );
        System.out.println( "12e-3 : " + 12e-3 );
        System.out.println( "0xA1.27p5 : " + 0xA1.27p5 );
    }
}

< 결과값 >

12.025 : 12.025
12e3 : 12000.0
12e-3 : 0.012
0xA1.27p5 : 5156.875

float 타입 리터럴 표기

  • double 형 타입 리터럴 뒤에 대문자 F나 소문자 f를 붙여쓰면 됨
  • 제일 뒤에 F나 f를 붙인 부동소수점 리터럴은 모두 float 타입으로 취급 됨
  • float 타입 리터럴은 뒤에 붙은 F나 f로 표기하기 때문에 불필요한 소수점을 생략할 수 있음.
  • 뒤에 F나 f가 붙은 10진수는 소수점이 없어도 float 타입
    • 12F

< float 타입 리터럴의 사용 예제 >

public class LiteralExample2 {
    public static void main(String[] args) {
        float num = 12.34f;
        System.out.println( num );
    }
}

< 결과값 >

12.34

문자 리터럴

  • 작은 따옴표로 묶은 하나의 문자는 char 타입 리터럴

  • char 타입 리터럴 예

    • '자' , 'J'

자바에서 escape sequence로 쓸 수 있는 문자들 목록

Escape Sequence의미Unicode
\b백스페이스0x0008
\t수평 탭0x0009
\n줄 바꿈 문자0x000a
\f새 페이지 문자0x000c
\r리턴 문자0x000d
\"큰따옴표0x0022
\'작은따옴표0x0027
\백슬래쉬0x005c
\8진수8진수에 해당하는 Unicode 문자. 예) \8, \42, \3770x0000 ~ 0x00ff

논리형 리터럴

  • 논리형 리터럴은 참(true)과 거짓(False)를 표현할때 사용하는 논리데이터
  • 논리형 리터럴을 쓸때는 boolean을 사용한다.
  • 정수나 대문자( TRUE | FALSE )는 compile error

< 논리형 리터럴 예제 >

public class LiteralEx04
{
    boolean b1 = true;
    System.out.println( b1 );
}

변수 선언 및 초기화하는 방법


변수 정의

  • 데이터를 저장하기 위해 프로그램에 의해 이름을 할당받는 메모리 공간 ( 변경 가능 )
  • '=' 기호를 사용하여 변수에 값을 대입

변수의 선언

  • 변수는 사용하기 전에 반드시 미리 선언하여야 함
  • 변수 선언이란, 컴파일러에게 어떤 변수를 사용하겠다고 미리 알리는 것
  • 선언을 하게 되면 컴파일러는 변수의 자료형에 맞는 기억 공간을 미리 확보
  • 만일 변수를 선언하지 않고 사용하게 되면 컴파일 오류 발생
  • 변수 선언도 하나의 문장이므로 반드시 세미콜론으로 종료하여야 함

변수의 초기화

  • 변수가 선언되면 변수의 값은 아직 정의되지 않은 상태가 됨
  • 변수를 선언과 동시에 값을 넣는 방법은 변수 이름 뒤에 대입 연산자 = 를 넣고 초기값을 적으면 됨
char c = 'a';
int i = 7;
double interestRate = 0.05;

변수의 이름

변수의 이름은 '식별자( identifier )' 의 일종으로 규칙은 다음 과 같다.

  • 식별자는 유니코드 문자와 숫자의 조합으로 만들어짐
  • 한글도 가능
  • 식별자의 첫 문자는 유니코드 문자이여야 함
    ( 첫 문자가 _, $로 시작될 수도 있으나 이는 특별한 경우만 )
  • 두 번째 문자부터는 문자, 숫자, _, $ 등이 가능
  • 대문자와 소문자 구별
  • 식별자의 이름으로 키워드( keyword )를 사용해선 안됨.
  • true, false, null 등은 키워드는 아니지만 역시 변수의 이름으로 사용할 수 없음
  • assert 는 1.4버전부터, enum은 1.5버전부터 사용되는 키워드

변수의 선언만 하는 방법

  • 이 방법은 먼저 변수를 선언하여 메모리 공간을 할당받고, 나중에 변수를 초기화 하는 방법 하지만 이렇게 선언만 된 변수는 초기화되지 않았으므로, 해당 메모리 공간에는 알 수 없는 쓰레기값만 들어가 있음 자바에서는 프로그램의 안정성을 위해 초기화하지 않는 변수는 사용할 수 없도록 함, 만약 초기화 되지 않은 변수를 사용하려고 하면, 컴파일 에러 발생

< 예시 >

int num;				// 변수의 선언
System.out.println( num );		// 오류 발생

num = 20;				// 변수의 초기화
System.out.println( num );		// 20

잘못된 변수 선언 예시

int 1stPrizeMoney;	// 첫 글자가 숫자

double super;		// 키워드

int #ofComputer;	// 허용되지 않는 기호

식별자 이름 작성 관계

종류사용 방법
클래스명각 단어의 첫글자는 '대'문자로StaffMember, ItemProducer
변수명, 메소드명소문자로 시작되어 2번째 단어의 첫 글자는 대문자로 한다width, payRate, acctNumber, getMonthDays(), fillRect()
상수상수는 모든 글자는 '대' 문자로MAX_NUMBER

변수의 스코프와 라이프타임


프로그램상에서 사용되는 변수들은 사용 가능한 범위를 가짐, 그 범위를 scope라고 한다.

변수가 선언된 블록이 그 변수의 사용범위이다.

package me.basic.step2.primitive;

public class ValableScopeExample
{
    int globalScope = 10;       // 인스턴스 변수

	public void scopeTest(int value)
    {
		int localScope = 10;
		System.out.println(globalScope);
		System.out.println(localScpe);
		System.out.println(value);
	}
}

클래스의 속성으로 선언된 변수 globalScope의 사용 범위는 클래스 전체

매개변수로 선언된 int value는 블록 바깥에 존재 하지만, 메서드 선언부에 존재하므로 사용범위는 해당 메소드 블록내 이다

메소드 블록내에서 선언된 localScope 변수의 사용범위는 메소드 블록내


main메소드에서 사용하기

public class ValableScopeExample
{
    int globalScope = 10;       // 인스턴스 변수

    public void scopeTest( int value )
    {
        int localScope = 10;

        System.out.println( globalScope );
        System.out.println( localScope );
        System.out.println( value );
    }

    public static void main(String[] args)
    {
        System.out.println( globalScope );      // error
        System.out.println( localScope );       // error
        System.out.println( value );            // error
    }
}

같은 클래스 안에 있는데 globalScope 변수를 사용할 수 없다.

main은 static한 메소드이다. static한 메서드에서는 static 하지 않은 필드를 사용할 수 없다.


static

같은 클래스 내에 있음에도 해당 변수들을 사용할 수 없다.

main 메소드는 static 이라는 키워드로 메소드가 정의되어 있다.

static 한 필드( 필드 앞에 static 키워드를 붙힘 )나, static한 메소드는 class 가 인스턴스화 되지 않아도 사용할 수 있다.

public class ValableScopeExample
{
    int globalScope = 10;
    static int staticVal = 7;

    public void scopeTest( int value )
    {
        int localScope = 20;
    }

    public static void main(String[] args)
    {
        System.out.println( staticVal );
    }
}

static한 변수는 공유됨

  • static하게 선언된 변수는 값을 저장할 수 있는 공간이 하나만 생성된다. 그러므로, 인스턴스가 여러개 생성되도 static한 변수는 하나다.
package me.basic.step2.primitive;

public class ValableScopeDisplay {
    public static void main(String[] args) 
    {
        ValableScopeExample v1 = new ValableScopeExample();
        ValableScopeExample v2 = new ValableScopeExample();

        v1.globalScope = 20;
        v2.globalScope = 30;

        System.out.println( v1.globalScope );
        System.out.println( v2.globalScope );

        System.out.println( ValableScopeExample.staticVal );
    }
}

golvalScope 같은 변수(필드)는 인스턴스가 생성될때 생성되기 때문에 인스턴스 변수라고 한다.

staticVal 같은 static한 필드를 클래스 변수라고 한다.

클래스 변수는 레퍼런스.변수명 하고 사용하기 보다는 클래스명.변수명으로 사용하는것이 더 바람직하다

VariableScopeExam.staticVal

타입 변환, 캐스팅 그리고 타입 프로모션


타입변환 = 형변환 (cast)

어떤 자료형의 값을 -> 다른 자료형의 값으로 바꾸어 주는 연산

반환되는 값의 왼쪽에서 원하는 타입을 소괄호로 둘러싸서 적어주면 됨

( 새로운 자료형 ) 수식;

예를들어 int형 변수 x가 가지고 있는 값을 -> double로 형변환하여 y에 대입하려면

y = (double) x;

형변환은 크게 3가지

  • 자동적인 형변환
  • 축소 변환 ( narrowing conversion )
  • 확대 변환 ( widening conversion )

자동적인 형변환

java는 필요할 떄마다 자동적으로 형변환을 수행 함.

수식에서 서로 다른 자료형이 등장하면 java compiler는 그 중에서 가장 큰 타입으로 자동적으로 변환

// 정수 2가 2.0으로 변환된 후에 3.5와 더해져서 5.5로 계산 됨
double d = 2 + 3.5

축소 변환 ( narrowing conversion )

더 작은 크기의 자료형에 값을 저장하는 형변환

실수형 변수를 정수형 변수에 저장하는 것

이 변환은 정밀한 숫자나 큰 숫자를 나타내는 정보를 잃을 가능성이 있다.

// i에는 12만 저장
int i = (int) 12.5;

위 예에서 소수점 이하는 사라진다.

그러므로, 축소 변환을 할때는 자료를 잃을 가능성 때문에 항상 주의!!


확대 변환 ( widening conversion )

더 큰 크기의 변수로 값을 이동하는 반환

확대 변환은 '안전한 변환'

// 정수 100이 변수 d에 100.0으로 형변환되어 저장
double d = (double) 100;

여러가지 형변환 예제

package me.basic.step2.primitive;

public class TypeConversion {
    public static void main(String[] args) {
        int i;
        double f;
		
        // 피연산자가 정수이므로 정수 연산으로 계산되어 1
        // 이것이 double형 변수로 대입되므로 올림 변환이 발생하여 1.0이 f에 저장됨 
        f = 5 / 4;
        System.out.println( "f : " + f );

	/*
	(double) 5 / 4에서는 먼저 형변환 연산자가 우선 순위가 높기 때문에 먼저 실행, 
    	정수 5가 부동소수점수 5.0으로 변환됨 5.0 / 4는 피연산자 중 하나가 double형이므로 4도 double형
        으로 자동 형변환되고 5.0 / 4.0 으로 계산되어서 1.25가 수식의 결과값이 됨, 
        따라서 1.25가 변수 f에 저장
        */
        f = (double) 5 / 4;
        System.out.println( "f : " + f );

	/*
      	5 / (double)4 에서도 두번째 피연산자가 double형으로 변환되면 나머지 피연산
        자도 double형으로 되어서 전체 수식의 결과값이 double형이 됨
        */
        f = 5 / (double)4;
        System.out.println( "f : " + f );

	/*
    	두 개의 피연산자가 모두 double형으로 변환됨
	*/
        f = (double) 5 / (double) 4;
        System.out.println( "f : " + f );

	/* 
    	모두 int형으로 변환되어 소수점 제거 후 1 + 1 = 2가 저장됨
	*/
        i = (int)1.3 + (int)1.8;
        System.out.println( "i : " + i );
    }
}

결과값

f : 1.0
f : 1.25
f : 1.25
f : 1.2,

Process finished with exit code 0

1차 및 2차 배열 선언하기


메모리 구조

Image


메소드 영역

클래스 변수( static variable )이 저장되는 영역

JVM은 특정 클래스가 사용되면 클래스 파일(.class)를 읽어들여, 해당 클래스에 대한 정보를 메소드 영역에 저장


Heap 영역

모든 인스턴스 변수가 저장되는 영역

new 키워드를 사용하는 인스턴스의 정보

메모리의 낮은주소 -> 높은 주소의 방향으로 할당


stack 영역

메소드가 호출될떄 스택 프레임이 저장되는 영역

메소드가 호출되면, 관계되는 지역변수와 매개변수를 스택 영역에 저장

메소드 호출이 완료되면 소멸됨

스택 영역에 저장되는 메소드의 호출 정보를 스택 프레임( stack frame )이라고 한다.

스택 영역은( push )로 데이터를 저장, 팝( pop ) 동작으로 데이터를 인출

스택은 후입선출( LIFO, Last-In First-Out ) 방식으로, 가장 늦게 저장된 데이터가 가장 먼저 인출됨.

스택 영역은 메모리의 낮은주소 <- 높은주소 방향으로 할당 됨.


배열

같은 타입의 변수들 모임.

자바에서 배열은 객체이다.

먼저 참조 변수를 선언하고 이어서 객체를 생성한다.

int[] numbers;		// 배열 참조 변수 선언

배열 참조 변수를 선언했다고 해서 배열이 생성된 것은 아님, 자바에서는 배열도 객체이므로 반드시 new 연산자를 사용

// 위의 배열 참조변수 객체 생성
numbers = new int[6];		

// 실수 배열
float[] distances = new float[20];

// 문자 배열
char[] letters = new char[50];

사용자로부터 5명의 성적을 입력받아서 평균을 구하는 프로그램

package me.basic.step2.primitive;

import java.util.Scanner;

public class ArrayExam {
    public static void main(String[] args) {
        final int STUDENTS = 5;
        int total = 0;

        Scanner sc = new Scanner( System.in );

        int[] scores = new int[STUDENTS];

        for( int i = 0; i < STUDENTS; i++ )
        {
            System.out.print( "성적을 입력하시오 : " );
            scores[i] = sc.nextInt();
        }

        for( int i = 0; i < STUDENTS; i++ )
        {
            total += scores[i];
        }
        System.out.println( "\n 평균 성적은" + total / STUDENTS );
    }
}

결과값

성적을 입력하시오 : 100
성적을 입력하시오 : 70
성적을 입력하시오 : 50
성적을 입력하시오 : 60
성적을 입력하시오 : 80

 평균 성적은72

Process finished with exit code 0

오류주의

// 잘못됨
int matrix[5] = {1, 2, 3, 4, 5};

// 잘못됨
int matrix[5];

배열의 초기화

중괄호를 사용하여 배열 원소의 초기값을 적어 넣음

package me.basic.step2.primitive;

public class ArrayExam2{
    public static void main(String[] args) {
        int[] numbers = { 10, 20, 30 };
		
        // 일반 for문 방식
        for( int i = 0; i < numbers.length; i++)
        {
            System.out.println( numbers[i] );
        }
        
        // for-each Loop 방식 - jdk 1.5 버전 부터 사용
        for( int value : numbers )
        {
            System.out.println( value);
        }
    }
}

결과값

10
20
30

10
20
30

Process finished with exit code 0

향상된 for-each루프와 전통적인 for 루프의 비교

배열의 첫 번째 원소부터 마지막 원소의 값을 꺼내서 처리하는 경우라면 for-each 루프가 훨씬 사용하기 쉬움.

배열의 크기에 신경쓰지 않아도 되고 인덱스 변수를 생성할 필요도 없음.

for-each를 사용할 수 없는 경우

  • 배열 원소의 값을 변경하는 경우
  • 역순으로 배열 원소를 처리하는 경우
  • 전체가 아니고 일부 원소만 처리하는 경우
  • 하나의 반복 루프에서 두 개 이상의 배열을 처리하는 경우

사용자가 배열의 크기를 지정

import java.util.Scanner;

public class ArrayExam3 {
    public static void main(String[] args) {
        int total = 0;
        Scanner sc = new Scanner( System.in );

        System.out.print( "배열의 크기를 입력 : ");
        int size = sc.nextInt();
        int[] scores = new int[size];

        for( int i = 0; i < scores.length; i++ )
        {
            System.out.print( "성적을 입력하시오 : " );
            scores[i] = sc.nextInt();
        }

        for( int i = 0; i < scores.length; i++ )
        {
            total += scores[i];
        }

        System.out.print( "평균 성적은 : " + total / scores.length + "입니다." );
    }
}

결과값

배열의 크기를 입력 : 3
성적을 입력하시오 : 1
성적을 입력하시오 : 2
성적을 입력하시오 : 3

평균 성적은 : 2입니다.

메소드 매개변수로 배열 전달

배열 전체가 전달되는 배열 참조 변수 복사 예제


package me.basic.step2.primitive;

import java.util.Scanner;

public class ArrayExam4 {
    final static int STUDENT = 5;

    public static void main(String[] args) {
        int[] scores = new int[STUDENT];
        getValues( scores );
        getAverage( scores );
    }

    private static void getValues( int[] array ) {
        Scanner sc = new Scanner( System.in );

        for( int i = 0; i < array.length; i++ )
        {
            System.out.println( "성적을 입력하시오." );
            array[i] = sc.nextInt();
        }
    }

    private static void getAverage( int[] array )
    {
        int total = 0;
        for ( int i = 0; i < array.length; i++ ){
            total += array[i];
        }
        System.out.println( "평균 성적은 " + total / array.length  + "이다" );
    }
}

결과값

성적을 입력하시오.
50
성적을 입력하시오.
40
성적을 입력하시오.
60
성적을 입력하시오.
80
성적을 입력하시오.
70
평균 성적은 60이다

Process finished with exit code 0

객체들의 배열

자동차를 나타내는 Car 클래스가 있다고 가정하고 배열을 작성하면

Car[] cars = new Car[5];

위 예제 와 같이 배열이 만들어지고 참조값을 저장할 수 있는 5개의 공간이 만들어진다. 즉, 실제 객체가 생성되어 저장되는 것은 아니다.
실제 객체는 다음과 같이 생성하여야 한다.

cars[0] = new Cars();
cars[1] = new Cars();

반복 루프를 사용하여 모든 객체를 생성하는 예제

class Car{
    public int speed;
    public int gear;
    public String color;

    public Car() {
        speed = 0;
        gear = 1;
        color = "red";
    }

    public void speedUp() {
        speed += 10;
    }

    public String toString(){
        return "속도 : " + speed + "기어 : " + gear + " 색상 " + color;
    }
}
public class CarArray {
    public static void main(String[] args) {
        final int NUM_CARS = 5;

        Car[] cars = new Car[NUM_CARS];

        for( int i = 0; i < cars.length; i++ )
            cars[i] = new Car();

        for( int i = 0; i < cars.length; i++ )
            cars[i].speedUp();

        for( int i = 0; i < cars.length; i++ )
            System.out.println( cars[i] );
    }
}

위 예제의 값이 어떻게 나올 것 같은가?

얼핏 봤을때는 Car 클래스의 cars 객체 참조 변수를 활용하여 speedUp을 호출했다면 += 값으로 인해 속도가 계속 증가하는것으로 볼 수 있다. 하지만 아니다.
위 예제는 실 원소가 아니라 참조 변수를 가르키고 있으므로 결과는 다음과 같다.

결과값

속도 : 10기어 : 1 색상 red
속도 : 10기어 : 1 색상 red
속도 : 10기어 : 1 색상 red
속도 : 10기어 : 1 색상 red
속도 : 10기어 : 1 색상 red

Process finished with exit code 0

즉, Car객체가 배열 우너소에 저장되는 것은 절대 아닌다. 참조 변수는 객체가 아니라 객체의 참조값 을 저장하고 있기 때문에 Car배열에서도 각각의 원소들은 참조값만을 저장하게 된다.


2차원 배열

선언

자료형[][] 참조변수이름 = new 자료형[행크기][열크기]

2차원 배열 처리시 중첩된 루프 사용

for( int i = 0; i < 3; i++ )
	for( int j = 0; j < 5; j++ )
    		System.out.println( s[i][j] );

2차원 배열 초기화

자료형[][] 참조변수이름 = { { 10, 20, 30} , { 40, 50, 60 } }

2차원 배열의 length 필드

1차원 배열의 경우 하나의 length필드가 존재했지만 2차원 배열에서는 약간 복잡해진다. 각 행마다 별도의 length필드가 있고 이것은 각 행이 가지고 있는 열의 개수를 나타낸다.

package me.basic.step2.primitive;

public class ArrayExam5 {
    public static void main(String[] args) {
        int[][] array = { { 10, 20, 30, 40}, { 50, 60, 70, 80 } };

        for( int r = 0; r < array.length; r++ )
            for ( int c = 0; c < array[r].length; c++ )
                System.out.println( r + "행" + c + "열" + array[r][c] );
    }
}

결과값

0010
0120
0230
0340
1050
1160
1270
1380

Process finished with exit code 0

다차원 배열

선언

자료형[][][] 참조형변수이름 = new double[3][2][12];


타입 추론, var


타입 추론

정의

타입이 정해지지 않은 변수에 대해서 Compailer가 변수의 타입을 스스로 찾아낼 수 있도록 하는 기능

타입 추론이 가능하다는 얘기는 곧 타입을 명시하지 않아도 된다는 말
이말은 코드량을 좀더 줄이고 코드의 가독성을 높일 수 있다는 뜻

java 10버전부터 사용 가능

자바에서는 일반 변수에 대해 타입 추론을 지원하지 않기 때문에


지원범위

일반변수에 대해서는 타입추론이 지원되지 않고, generics와 lambda식에 대해서만 타입 추론이 지원되고 있다.


예시

var title = "java10 type";

String title = "java10 type";

자바 컴파일러 Type Erasure 사용

제네릭의 연산자(<>)에 타입을 넘겨줄때 타입 정보를 제거한다.


// Integer 타입을 -> Object형태로 변환
List<Integer> age;  -> List<Object> age; 

메소드 호출 시 인자의 타입 추론

자바8 이전에 제네릭 타입의 인자를 넘겨주는 경우 타입 추론이 안되는 경우가 있었습니다.


// Collections.emptyList() 의 메소드 시그니쳐
public static final <T> List<T> emptyList() { ... }

// 이런 메소드가 있다고 하자
static void processNames(List<String> names) {
  for (String name : names) {
    System.out.println("Hello " + name);
  }
}

// 컴파일러는 제네릭 타입이 String 이라고 유추할 수 있음
List<String> names = Collections.emptyList();

processNames(Collections.emptyList()); // error in Java 7
processNames(Collections.emptyList()); // OK in Java 7

Collections.emptyList() 는 제네릭 타입을 알 수 없기 때문에 List 타입으로 결과를 리턴하게 됩니다. 따라서 processNames() 의 인자는 타입이 맞지 않아 컴파일 에러가 납니다. 하지만 자바8에서 이것이 개선되어 타입 증거 없이도 인자의 타입을 유추할 수 있게 되었습니다.

연쇄 메소드 호출 시 인자의 타입 추론

 static class List<E> {
  static <T> List<T> emptyList() {
    return new List<T>();
  }
    
  List<E> add(E e) {
    // 요소 추가
    return this;
  }
}

List<String> list = List.emptyList(); // OK
List<String> list = List.emptyList().add(":("); // error
List<String> list = List.<String>emptyList().add(":("); // OK
 

emptyList() 메소드를 호출하면서 타입이 제거되기 때문에 연쇄적으로 호출되는 부분에서 인자를 알아챌 수가 없습니다. 자바 8에서 수정될 예정이었으나 취소되어 여전히 컴파일러에게 명시적으로 타입을 알려줘야 합니다.


함수형 인터페이스

자바는 람다를 지원하기 위해서 타입 추론을 강화해야 했습니다. 그래서 '함수형 인터페이스’가 나왔습니다. 함수형 인터페이스는 하나의 추상 메소드(Single abstract method, 단일 추상 메소드)로 이루어진 인터페이스인데, 여기서 함수의 시그니쳐가 정의되어 있기 때문에 컴파일러가 이 정보를 참고해서 람다에서 생략된 정보들을 추론할 수 있게 됩니다.


@FunctionalInterface

함수형 인터페이스는 단 하나의 메소드를 가질 수 있습니다. 컴파일러가 미리 체크할 수 있도록 @FunctionalInterface 어노테이션으로 표시해줄 수 있습니다. 기존 JDK 의 Runnable 이나 Callabe 같은 인터페이스들이 이 어노테이션으로 개선되었습니다. 또한 다른 사용자에게 인터페이스의 의도를 설명해줄 수도 있습니다.

 // 컴파일 OK
public interface FunctionalInterfaceExample {
    
}

// 추상 메소드가 없으므로 컴파일 에러
@FunctionalInterface
public interface FunctionalInterfaceExample {
    
}

// 추상 메소드가 두 개 이상이면 컴파일 에러
@FunctionalInterface
public interface FunctionalInterfaceExample {
  void apply();
  void illigal(); // error
} 
  

상속

만약 함수형 인터페이스를 상속하는 경우에도 이러한 특성을 그대로 이어받습니다.

@FunctionalInterface
interface A {
  abstract void apply();
}

// 함수형 인터페이스로 동작
interface B extends A {

}

// 명시적으로 오버라이드 표시 가능
interface B extends A {
  @Override
  abstract void apply();
}

// 하나의 추상메소드 외에 메소드 추가 불가
interface B extends A {
  void illegal(); // error
}

// 함수형 인터페이스에서 정의한대로 람다는 인자가 없고 리턴값이 없는 함수로 사용할 수 있다.
public static void main(String... args) {
  A a = () -> System.out.println("A");
  B b = () -> System.out.println("B");
}

람다의 타입 추론

람다는 인자의 타입을 추론할 수 있습니다. 위에서 살펴본 것처럼 함수형 인터페이스가 타입에 대한 정보를 컴파일러에게 제공한 덕분입니다.

@FunctionalInterface
interface Calculation {
  Integer apply(Integer x, Integer y);
}

static Integer calculate(Calculation operation, Integer x, Integer y) {
  return operation.apply(x, y);
}

// 람다 생성
Calculation addition = (x, y) -> x + y;
Calculation subtraction = (x, y) -> x - y;

// 사용
calculate(addition, 2, 2);
calculate(substraction, 5, calculate(addition, 3, 2));

예외

@FunctionalInterface 에는 하나의 메소드만 작성할 수 있다고 했는데, 여기에는 예외가 있습니다.

  • Object 클래스의 메소드를 오버라이드하는 경우
  • 디폴트 메소드
  • 스태틱 메소드

예를 들어 Comparator 의 경우 @FunctionalInterface 인데 메소드가 많이 있습니다. 살펴보면 디폴트 메소드, 스태틱 메소드, Object 오버라이드한 메소드가 있고 추상 메소드의 경우는 compare 메소드 하나 뿐입니다


Reference Site

Coding or Gaming ( https://codingisgame.tistory.com/3 )
프로그래머스
TCP School
박철우의 블로그 ( https://parkcheolu.tistory.com/105 )
Power JAVA
https://futurecreator.github.io/2018/07/20/java-lambda-type-inference-functional-interface/

0개의 댓글