본문 바로가기
Back-end

java.lang패키지와 유용한 클래스(5)

by 신재권 2021. 6. 27.

예외를 발생시키는 메서드

메서드 이름에 Exact가 포함된 메서드들이 JDK1.8부터 새로 추가되었다. 이들은 정수형간의 연산에서 발생시할 수 있는 오버플로우(overflow)를 감지하기 위한 것이다.

int addExact(int x, int y) // x+ y 
int subtractExact(int x, int y) // x -y 
int multiplyExact(int x, int y) // x * y
int incrementExact(int a)   // a++
int decrementExact(int a) // a--
int negateExact(int a)  //-a
int toIntExact(long value) // (int) value -int로의 형변환

연산자는 단지 결과를 반환할 뿐, 오버플로우의 발생여부에 대해 알려주지 않는다. 그러나 위의 메서드들은 오버플로우가 발생하면 예외(ArithmeticException)를 발생시킨다.

negateExact(int a)는 매개변수의 부호를 반대로 바꿔주는데 무슨 예외가 발생할까? 라고 생각할 수 있다. 부호를 반대로바꾸는 식은 '~a +1'이다. '~a'의 결과가 int의 최대값이면, 여기에 1을 더하니까 오버플로우가 발생할 수 있는 것이다.

import static java.lang.Math.*;
public class MathEx2 {

	public static void main(String[] args) {
		int i = Integer.MIN_VALUE;
		
		System.out.println("i ="+i);
		System.out.println("-i ="+(-i));
		
		try{
			System.out.printf("negateExact(%d) = %d%n", 10, negateExact(10));
			System.out.printf("negateExact(%d) = %d%n", -10, negateExact(-10));
			System.out.printf("negateExact(%d) = %d%n", i, negateExact(i)); //예외발생
		}catch(ArithmeticException e){
			//i를 long타입으로 형변환다음에 negateExact(long a)를 호출
			System.out.printf("negateExact(%d) = %d%n", (long)i, negateExact((long)i));
		}
	}

}
===========
i =-2147483648
-i =-2147483648
negateExact(10) = -10
negateExact(-10) = 10
negateExact(-2147483648) = 2147483648

변수 i에 int타입의 최소값인 Integer.MIN_VALUE를 저장한 다음에, 부호 연산자로 i의 부호를 반대로 바꾸었다. 그런데 실행결과에서 -i의 값을 보면 부호가 바뀌지 않고 i의 값 그대로이다. 왜 그런 것일까? 정수형의 최소값에 비트전환연산자(~)를 적용하면, 최대값이 되는데 여기에 1을 더하니까 오버플로우가 발생하는 것이다.

그래서 int의 최소값이 다시 원래의 값이 되어버렸다. 예제에서는 try-catch문을 사용해 오버플로우가 발생하면 , i를 long타입으로 형변환하여 negateExcat(long a)를 호출하도록 작성하였다.

실행결과를 보면 오버플로우로 인한 예외가 발생했지만, catch블럭에 의해 예외처리가 되어 올바른 결과가 출력된 것을 확인할 수 있다.

삼각함수와 지수, 로그

Math클래스에는 이름에서 알 수 있듯이 수학 관련 메서드들이 많이 있다. 보다 자세한 내용은 Java API를 참고하고 예제를 통해 몇 가지 자주 쓰이는 메서드들의 사용방법만 확인하자

import static java.lang.Math.*;

public class MathEx3 {

	public static void main(String[] args) {
		int x1 = 1, y1 = 1;  //(1, 1)
		int x2 = 2, y2 = 2;  //(2, 2)

		double c = sqrt(pow(x2-x1,2)+ pow(y2-y1,2));
		double a = c * sin(PI/4);  // PI/4 rad =  45degree
		double b = c * cos(PI/4);  
//		double b = c* cos(toRadians(45));
		
		System.out.printf("a=%f%n", a);
		System.out.printf("b=%f%n", b);
		System.out.printf("c=%f%n", c);
		System.out.printf("angle=%f rad%n", atan2(a,b));
		System.out.printf("angle=%f degree%n%n", atan2(a,b)*180/PI);
//		System.out.printf("angle=%f degree%n%n", toDegrees(atan2(a,b)));
		System.out.printf("24 * log10(2)= %f%n", 24 * log10(2)); // 7.224720
		System.out.printf("53 * log10(2)= %f%n", 53 * log10(2)); // 15.954590
		
		
	}

}
===================
a=1.000000
b=1.000000
c=1.414214
angle=0.785398 rad
angle=45.000000 degree

24 * log10(2)= 7.224720
53 * log10(2)= 15.954590

두 점 (x1, y1),(x2,y2)간의 거리 c는 √(x2-x1)^2 + (y2-y1)^2 로 구할 수 있다.

제곱근을 계산해주는 sqrt()와 n제곱을 계산해주는 pow()를 사용해서 식을 구성하면 다음과 같다.

double c = sqrt(pow(x2-x1,2) + pow(y2-y1,2);

예제에서 x1과 y1의 값은 1이고, x2와 y2의 값은 2니까, 이 값들을 대입하면 다음과 같이 계산된다.

double c = sqrt(pow(2-1,2) + pow(2-1,2));
->double c = sqrt(pow(1,2) + pow(1,2));
->double c = sqrt(1.0 + 1.0);
->double c = sqrt(2.0);
->double c = 1.414214;
√2 ≒ 1.414214

두 점 (x1,y1)과 (x2,y2)을 이은 직선을 빗변으로 하는 삼각형을 그려보자.

점 (1,1) , 점 (2,2)의 길이는 √2이고, 90도 부분은 점(2,1)이다.

계산하지 않아도 나머지 두변의 길이가 1이고 끼인각 θ가 45도라는 것을 알 수 있지만, 자바에서 제공하는 삼각함수들을 이용해서 각 값을 구하였다.

a = c * sinθ
b = c * cosθ

a와 b를 구하는 수학공식은 위와 같으며, 이 공식을 자바로 작성하면 다음과 같다.

  • Math클래스에는 원자율 PI와 자연로그의 밑인 E가 상수로 정의되어 있다.
double a = c * sin(PI/4);  // PI/4 radian = 45 degree
double b = c * cos(PI/4):
// double b = c * cose(toRadians(45));  //각도를 라디안으로 변환

삼각함수는 매개변수의 단위가 라디안(radian)이므로, 45도를 라디안(radian)단위의 값으로 변환해야 한다. 180 = 파이 rad이므로, 45 = PI/4 rad이다. 아니면 toRadians(double anadeg)을 이용할 수도 있다. 이 메서드의 반환값은 double이다.

System.out.printf("angle=%f rad%n", atan2(a,b));
	System.out.printf("angle=%f degree%n%n", atan2(a,b)*180/PI);
//System.out.printf("angle=%f degree%n%n", toDegrees(atan2(a,b)));

atan2메서드는 직각 삼각형에서 두 변의 길이 a, b를 알면 끼인각θ를구해준다. 이 메서드의 결과값 역시 단위가 라디안이므로 도(degree)단위로 변환하려면 180/PI를 곱하거나 toDegrees(double angrad)를 이용하면 된다.

24자리의 2진수는 10진수로 몇 자리의 값인지 알아내려면 다음의 식을 풀어야 한다.

2^24 = 10^x

이 식의 양변에 상용로그(log 10)을 취하면 , 다음과 같은 식이 된다.

24 x log10(2) = x

이 식은 아래와 같이 계산할 수 있으며, 결과는 약 7.2이다. 즉 24자리의 2진수는 10진수로 7자리의 값을 표현할 수 있다는 얘기이다.

		System.out.printf("24 * log10(2)= %f%n", 24 * log10(2)); // 7.224720
		System.out.printf("53 * log10(2)= %f%n", 53 * log10(2)); // 15.954590
	

그래서 float타입의 정밀도가 7자리 인것이다. 마찬가지로 double 타입의 정밀도는 15자리임을 알 수 있다.

  • loat타입의 가수는 23자리지만, 정규화를 통해 1자리를 더 확보할 수 있으므로 실제로 저장할 수 있는 가수는 24자리이다. double타입 역시 52+1= 53자리이다.

StrictMath클래스

Math클래스는 최대한의 성능을 얻기 위해 JVM이 설치된 OS의 메서드를 호출해서 사용한다. 즉 OS에 의존적인 계산을 하고 있는 것이다. 예를 들어 부동소소점 계산의 경우 반올림의 처리방법 설정이 OS마다 다를 수 있기 때문에 자바로 작성된 프로그램임에도 불구하고 컴퓨터마다 결과가 다를 수 있다.

이러한 차이를 없애기 위해 성능은 다소 포기하는 대신, 어떤 OS에서 실행되어도 항상 같은 결과를 얻도록 Math클래스를 새로 작성한 것이 StrictMath클래스이다.

Math클래스의 메서드

메서드 설명 예제/결과
static double abs(double a)
static float abs(float f)
static int abs(int f)
static long abs(long f)
주어진 값의 절대값을 반환한다. int i = Math.abs(-10);
double d = Math.abs(-10.0); ——————————————
i = 10 d= 10.0
static double ceil(double a) 주어진 값을 올림하여 반환한다. double d = Math.ceil(10.1);
double d2 = Mathg.ceil(-10.1);
double d3 = Math.ceil(10.000015): —————————————
d = 11.0
d2= -10.0
d3 = 11.0
static double floor(double a) 주어진 값을 버림하여 반환한다. double d = Math.floor(10.8):
double d2 = Math.floor(-10.8); ————————————————
d = 10.0
d2 = -11.0
static double max(double a, double b) static float max(float a, float b)
static int max(int a, int b)
static long max(long a, long b)
주어진 두 값을 비교하여 큰 쪽을 반환한다. double d = Math.max(9.5, 9.50001):
int i = Max.max(0, -1); ————————————————
d = 9.50001
i = 0
static double min(double a, double b) static float min(float a, float b)
static int min(int a, int b)
static long min(long a, long b)
주어진 두 값을 비교하여 작은쪽을 반환한다. double d = Math.min(9.5, 9.50001):
int i = Max.min(0, -1); ————————————————
d = 9.5
i = -1
static double random() 0.0~1.0 범위의 임의의 double 값을 반환한다. (1.0은 범위에 포함되지 않는다.) double d = Math.random();
int i = (int) (Math.random()*10)+1 ————————————————- 0.0 ≤ d <1.0
i≤i <11
static double rint(double a) 주어진 double값과 가장 가까운 정수값을 double형으로 반환한다. 단 두 정수의 정가운데 있는 값(1.5, 2.5,3.5 등)은 짝수를 반환 double d = Math.rint(1.2);
double d2 = Math.rint(2.6);
double d3 = Math.rint(3.5);
double d4 = Math.rint(4.5); ————————————————
d = 1.0
d2 = 3.0
d3 = 4.0
d4= 4.0
static long round(double a)
static long round(float a)
소수점 첫째자리에서 반올림 한 정수값(long)을 반환한다. 매개변수의 값이 음수인 경우 round()와 int()의 결과가 다르다는 것에 주의하자 long l =Math.round(1.2);
long l2 =Math.round(2.6);
long l3 =Math.round(3.5);
long l4 =Math.round(4.5);
double d = 90.7552;
double d2 = Math.round(d*100)/100.0; ————————————————-
l= 1
l2 =3
l3 = -4
l4 = -5
d = 90.7552
d2 = 90.76

래퍼(wrapper) 클래스

객체지향개념에서 모든것은 객체로 다루어져야 한다. 그러나 자바에서는 8개의 기본형을 객체로 다루지 않는데, 이것이 바로 자바가 완전한 객체지향 언어가 아니라는 얘기를 듣는 이유이다. 그 대신 보다 높은 성능을 얻을 수 있었다.

때로는 기본형(primitive type)변수도 어쩔 수 없이 객체로 다뤄야 하는 경우가 있다. 예를 들면, 매개변수로 객체를 요구할 때, 기본형 값이 아닌 객체로 저장해야할 때, 객체 간의 비교가 필요할 때 등등의 경우에는 기본형 값들을 객체로 변환하여 작업을 수행해야 한다.

이때 사용되는 것이 래퍼(wrapper)클래스이다. 8개의 기본형을 대표하는 8개의 래퍼클래스가 있는데, 이 클래스를 이용하면 기본형 값을 객체로 다룰 수 있다.

다음 표에서 알 수 있듯이 char형과 int형을 제외한 나머지는 자료형 이름의 첫 글자로 대문자로 한 것이 각 래퍼 클래스의 이름이다.

래퍼클래스의 생성자는 매개변수로 문자열이나 각 자료형의 값들을 인자로 받는다. 이 때 주의해야 할 것은 생성자의 매개변수로 문자열을 제공할 때, 각 자료형에 알맞는 문자열을 사용해야 한다는 것이다. 예를 들어 'new Integer("1.0");' 과 같이하면 NumberFormatException이 발생한다.

아래의 코드는 int형 래퍼 클래스인 Integer클래스의 실제 코드이다.

public final class Integer extends Number implements Comparable{
		....
		private int value;
		...
}

이처럼 래퍼 클래스들은 객체 생성시에 생성자의 인자로 주어진 각 자료형에 알맞은 값을 내부적으로 저장하고 있으며, 이에 관련된 여러 메서드가 정의되어 있다.

래퍼클래스의 생성자

기본형 래퍼클래스 생성자 활용 예
boolean Boolean Boolean(boolean value)
Boolean(String s)
Boolean b= new Boolean(true);
Boolean b2 = new Boolean("true");
char Character Character(char value) Character c= new Character('a);
byte Byte Byte(byte value)
Byte(String s)
Byte b =new Byte(10);
Byte b2 = new Byte("10");
short Short Short(short value)
Short(String s)
Short s = new Short(10);
Short s2 =new Short("10");
int Integer Integer(int value)
Integer(String s)
Integer i = new Integer(100); Integer i2 = new Integer("100");
long Long Long(long value)
Long(String s)
Long l =new Long(100);
Long l2 = new Long("100");
float Float Float(double value)
Float (float value)
Float(String s)
Float f = new Float(1.0);
Float f2 = new Float(1.0f);
Float f3 = new Float("1.0f");
double Double Double(double value)
Double(String s)
Double d = new Double(1.0);
Double d2 =new Dobule("1.0")
public class WrapperEx1 {

	public static void main(String[] args) {
		Integer i =new Integer(100);
		Integer i2 =new Integer(100);
		
		System.out.println("i == i2 ? " + (i==i2));
		System.out.println("i.equals(i2) ? "+i.equals(i2));
		System.out.println("i.compareTo(i2) ="+ i.compareTo(i2));
		System.out.println("i.toString() ="+i.toString());
		
		System.out.println("MAX_VALUE ="+Integer.MAX_VALUE);
		System.out.println("MIN_VALUE ="+Integer.MIN_VALUE);
		System.out.println("SIZE="+Integer.SIZE+" bits");
		System.out.println("BYTES="+Integer.BYTES+ " bytes");
		System.out.println("TYPE="+Integer.TYPE);
	}

}
===================
i == i2 ? false
i.equals(i2) ? true
i.compareTo(i2) =0
i.toString() =100
MAX_VALUE =2147483647
MIN_VALUE =-2147483648
SIZE=32 bits
BYTES=4 bytes
TYPE=int

래퍼 클래스들은 모두 equals()가 오버라이딩 되어 있어서 주소값이 아닌 객체가 가지고 있는 값을 비교한다. 그래서 실행결과를 보면 equals()를 이용한 두 Integer객체의 비교결과가 true라는 것을 알 수 있다. 오토박싱이 된다고 해서 Integer객체에 비교연산자를 사용할 수 없다. 대신 compareTo()를 제공한다.

그리고 toString()도 오버라이딩되어 있어서 객체가 가지고 있는 값을 문자열로 변환하여 반환한다. 이 외에도 래퍼 클래스들은 MAX_VALUE, MIN_VALUE, SIZE, BYTES, TYPE 등의 static상수를 공통적으로 가지고 있다.

  • BYTES는 JDK1.8부터 추가되었다.

Number클래스

이 클래스는 추상클래스로 내부적으로 숫자를 멤버변수로 갖는 래퍼클래스의 조상이다. 아래의 그림은 래퍼클래스의 상속계층도인데, 기본형 중에서 숫자와 관련된 래퍼 클래스들은 모두 Number클래스의 자손이라는 것을 알 수 있다.

그 외에도 Number클래스 자손으로 BigInteger와 BigDecimal 등이 있는데, BigInteger는 long으로도 다룰 수 없는 큰 범위의 정수를 ,BigDecimal은 double로도 다룰 수 없는 큰 범위의 부동 소수점수를 처리하기 위한 것으로 연산자의 역할을 대신하는 다양한 메서드를 제공한다.

참고로 Number클래스의 실제 소스는 다음과 같다. 객체가 가지고 있는 값을 숫자와 관련된 기본형으로 변환하여 반환하는 메서드를 정의하고 있다.

public abstract class Number implements java.io.Serializable{
	public abstract int intValue();
	public abstract long longValue();
	public abstract float floatValue();
	public abstract double doubleValue();
	
	public byte byteValue(){
		return (byte)intValue();
	}
	public short shortValue(){
		return (short)intValue();
	}
}

문자열을 숫자로 변환하기

다음은 문자열을 숫자로 변환하는 다양한 방법을 보여준다. 문자열을 숫자로 변환할 때는 아래의 방법 중 하나를 선택해서 사용하면 된다.

int i =new Integer("100").intValue(); 
int i2 =Integer.parseInt("100");  //주로 이 방법을 많이 사용
Integer i3 = Integer.valueOf("100");

아래 표는 래퍼 클래스의 '타입.parse타입(String a)'형식의 메서드와 '타입.valueOf()'메서드를 정리한 것이다. 둘 다 문자를 숫자로 바꿔주는 일을 하지만, 전자는 반환값이 기본형(primitve type)이고 후자는 반환값이 래퍼 클래스 타입이라는 차이가 있다.

문자열 -> 기본형 문자열 -> 래퍼클래스
byte b= Byte.parseByte("100"); Byte b= Byte.valueOf("100")
short a= = Short.parseShort("100"); Short s= Short .valueOf("100")
int i= Integer.parseInt("100"); Integer i= Integer.valueOf("100")
long l= Long.parseLong("100"); Long l= Long.valueOf("100")
float f= Float.parseFloat("3.14"); Float f= Float.valueOf("100")
double d= Double.parseDouble("3.14"); Double d= Double .valueOf("100")

JDK 1.5부터 도입된 오토박싱(autoboxing)기능 떄문에 반환값이 기본형일 때와 래퍼클래스일 때의 차이가 없어졌다. 그래서 그냥 구별없이 valueOf()를 쓰는 것도 괜찮은 방법이다. 단 성능은 valueOf()가 조금 더 느리다.

문자열이 10진수가 아닌 다른 진법(radix)의 숫자일때도 변환이 가능하다록 다음과 같은 메서드가 제공된다.

static int parseInt(String s, int radix)  //문자열 s를 radix진법으로 인식
static Integer valueOf(String s, int radix)

문자열 "100"이 2진법의 숫자라면 10진수로 4이고, 8진법의 숫자라면 10진수로 64이고, 16진법의 숫자라면 10진수로 256이된다. 참고로 2진수 100은 100_(2)와 같이 표기한다.

int i4 =Integer.parseInt("100", 2); -> 4 
int i5 = Integer.parseInt("100", 8); -> 65
int i6 =Integer.parseInt("100", 16); ->256
int i7 = Integer.parseInt("FF", 16);  ->255
int i8 = Integer.parseInt("FF");  //NumberFormatException 발생

16진법에는 A~F의 문자도 허용하므로 Integer.parseInt("FF", 16)와 같은 코드가 가능하지만, 진법을 생략하면 10진수로 간주하기 때문에 Integer.parseInt("FF")에서는 NumberFormatException이 발생한다.

public class WrapperEx2 {

	public static void main(String[] args) {
		int i = new Integer("100").intValue();
		int i2 = Integer.parseInt("100");
		Integer i3 = Integer.valueOf("100");
		
		int i4 = Integer.parseInt("100",2);
		int i5 = Integer.parseInt("100",8);
		int i6 = Integer.parseInt("100",16);
		int i7 = Integer.parseInt("FF",16);
//		int i8 = Integer.parseInt("FF"); //NumberFormatException 발생
		
		Integer i9 = Integer.valueOf("100",2);
		Integer i10 = Integer.parseInt("100",8);
		Integer i11 = Integer.parseInt("100",16);
		Integer i12 = Integer.parseInt("FF",16);
//		Integer i13 = Integer.parseInt("FF"); //NumberFormatException 발생
		
		System.out.println(i);
		System.out.println(i2);
		System.out.println(i3);
		System.out.println("100(2) -> "+i4);
		System.out.println("100(8) -> "+i5);
		System.out.println("100(16) -> "+i6);
		System.out.println("FF(16) -> "+i7);
		
		System.out.println("100(2) -> "+i9);
		System.out.println("100(8) -> "+i10);
		System.out.println("100(16) -> "+i11);
		System.out.println("FF(16) -> "+i12);
		
	}

}
----------------------------
100
100
100
100(2) -> 4
100(8) -> 64
100(16) -> 256
FF(16) -> 255
100(2) -> 4
100(8) -> 64
100(16) -> 256
FF(16) -> 255