자바를 설치하면 JDK(Java Development Kit : 자바 개발자 도구)가 설치된다. 개발자들의 편의를 위해 오라클이 미리 만들어놓은 클래스의 집합으로, 이 클래스들은 기능별로 구분되어 패키지 단위로 제공된다. 이렇게 자바에서 기본으로 제공하는 패키지를 자바 API라고 한다.
cd ~/Library/Java/JavaVirtualMachines/adopt-openjdk-11.0.11/Contents/Home/lib
내 맥북의 경우, 이 경로를 통해 가면 jrt-fs.jar
과 src.zip
을 만날 수 있었다. jrt-fs.jar
에는 JDK의 패키지들이 압축되어 있고, src.zip
에는 소스코드들이 담겨있다.
만약 .jar
파일 압축을 풀고 싶다면, jar xvf 파일명.jar
명령어를 사용하면 된다.
java.lang패키지는 JAVA프로그래밍에 필수적인 클래스들을 모아놓은 패키지이다. import문을 사용해야하는 다른 패키지들과 달리, import없이 자동으로 java프로그래밍에 포함된다.
java.lang패키지는 JVM경로/Contents/Home/lib/src.zip
내부에 존재하며, 컴파일시 컴파일러가 코드에 import java.lang.*;
구문을 자동으로 삽입하여 컴파일 한다.
java.lang패키지에는 아래와 같은 클래스들이 포함된다.
String, StringBuffer,Process, Runtime, Thread
Math, StrictMath,Exception Throwable, Error
Package, Class, ClassLoader, Wrapper, System, Stream
참고 : Incodom, 코딩하는 털보 블로그
Object 클래스는 모든 클래스의 최상위 클래스이다. 즉 모든 클래스는 Object클래스로부터 상속을 받는다. 또한 행렬을 포함한 모든 객체는 Object 클래스의 메소드들을 구현한다(from Oracle Doc).
Object 클래스에는 다음과 같은 메소드들이 있다.
현재 객체의 정보를 문자열을 반환하는 메소드. 아래와 같이 정의되어있다.
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
매개변수로 들어온 참조변수가 같은 객체를 참조하는지 판단하는 메소드. 즉 물리적으로 동일한 객체인지 확인한다. 아래와 같이 정의되어있다.
public boolean equals(Object obj) {
return (this == obj);
}
필요에 의해 overriding으로 논리적으로 동일한지 확인하는 메소드로 변경할 수 있다.
public class TestClass {
private String name;
public TestClass(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TestClass) {
return (name == ((TestClass) obj).name);
}
return false;
}
}
...
public class Main {
public static void main(String[] args) {
TestClass testClass = new TestClass("TTest");
TestClass testClass2 = new TestClass("TTest");
System.out.println(testClass.equals(testClass2));
}
}
위의 코드의 경우 물리적으로는 다른 객체이지만, 내용물이 같으므로 논리적으로는 같은 객체이다. 이러한 경우 필요에 따라 equals 메소드를 오버라이딩하여 바꿔서 사용할 수 있다.
해당 객체가 참조하고 있는 주소를 10진수로 반환 하는 함수
@HotSpotIntrinsicCandidate
public native int hashCode();
@HotSpotIntrinsicCandidate
: 아래의 메소드는 HotSpot에서 최적화된 형태로 코드가 작성되었기 때문에, JDK가 구현한 소스 코드를 대체하겠다는 어노테이션이다. 참고 사이트. HotSpot은 JVM이름인데, 1.2버전부터 사용된 유서깊은 JVM이다.
native
: 자바 네이티브 인터페이스. JVM위에서 실행되고 있는 자바코드가 네이티브응용프로그램(JVM을 돌리는 하드웨어/OS에 종속된 프로그램)이나, C/C++/어샘블리등 타 언어로 작성된 라이브러리를 호출하거나 호출될 수 있게 해주는 키워드.
equals()메서드는 해당 메소드가 동일한지 확인하는 메소드이다. 또한, 같은 객체라면 같은 hashcode값을 반환해야한다. 따라서 equals 오버라이딩을 하여 같은 객체의 정의를 변경한다면, hashCode()메소드도 같이 오버라이딩하여 같은 hashcode값을 반환하게 해야한다.
참고 : 망나니 개발가 블로그, nesoy 블로그
객체의 복사본을 만드는 메서드. OOP의 '정보은닉성'에 위배될 수 있다. 그러므로 아래처럼 복사 기능을 넣을 클래스의 선언부에 clonable 인터페이스를 명시하고 재정의하여 사용한다.
public class TestClass **implements Cloneable**{
private String name;
public TestClass(String name) {
this.name = name;
}
**@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}**
}
이 3가지의 메서드는 Thread를 제어할 때 필요한 메서드 들이다.
wait
- 갖고 있던 고유락을 해제하고 스레드를 잠들게 한다.
notify
- 잠들어 있던 스레드 중 임의로 하나를 골라 깨운다.
notifyAll
- 호출로 잠들어 있던 스레드를 모두 깨운다.
이 3가지의 메서드들은 호출하는 스레드가 반드시 고유 락을 갖고 있어야 한다. 다시 말해, synchronized 블록 내에서 호출되어야 한다.
wait() 메서드를 호출하면 락을 해제하고, 스레드는 잠이 든다. 누군가 깨워줄 때 까지 wait()은 리턴되지 않는다.
notify(), notifyAll() 메서드는 둘 다 wait()으로 잠든 메서드를 깨운다. 둘의 차이는 잠든 스레드 하나만 깨우냐, 모두 깨우냐의 차이이다.
notify() 메서드는 어느 스레드를 깨울지 선택할 수 없기 때문에 제어가 어렵다. 그래서 보통은 notifyAll()을 사용한다. notifyAll()이 모든 스레드를 깨우긴 하지만 이 메서드를 호출한다고 해서 잠들어 있던 모든 스레드가 동시에 동작하는 것은 아니다.
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
자바의 메모리 해제는 Garbage Collector(이하 GC)에 의해 수행됩니다. finalize()는 GC에 의해 호출된다.
파이널 라이저의 주요 목적은 오브젝트가 메모리에서 제거되기 전에 오브젝트가 사용하는 자원을 해제하는 것이다.
protected void finalize() throws Throwable {}
String 객체를 선언하는 방식은 new연산자를 이용하여 생성자로 생성하는 방법과, =를 이용해 리터럴 대입으로 생성하는 방식 두가지가 있다.
byte[] bytes = {72, 101, 108, 108, 111, 32,74, 97, 118, 97};
String test = new String(bytes);
String test2 = new String(bytes, 2, 6);
System.out.println(test);
// > Hello Java
System.out.println(test2);
// > llo Ja
String클래스가 입력받은 문자열을 저장하는 프로퍼티. 아래처럼 정의되어 있다.
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
...
@Stable
private final byte[] value;
...
}
String 심화 : 호식이네 블로그, 진성소트프 블로그
char[] 대신 byte[]차이점 : janjanlog 벨로그
String은 어떻게 별도의 메소드 호출 없이, 객체를 호출하면 자동으로 문자열을 반환하는거지????
우선, 객체를 메소드 없이 호출하면 자동으로 toString()메소드가 컴파일러에 의해 자동으로 호출된다.
그리고 String 클래스 및 File클래스에서는 toString()메소드를 재정의하여 의도한 값을 리턴하게 만들었다.
⇒ String객체자체를 호출하는 경우, 컬파일러에서 자동으로 toString()를 호출하여 사용자가 의도한 문자열을 반환하게 한다.
참고 : xemaker 블로그
StringBuffer는 String처럼 문자열을 저장할 수 있고, 문자열 추가(append) 및 변경에 String보다 유리한 클래스이다.
public class Main {
public static void main(String[] args) {
String str1 = new String("str1");
StringBuffer str2 = new StringBuffer("str2");
System.out.println("String\t\t\t: " + str1.hashCode());
System.out.println("StringBuffer\t: " + str2.hashCode());
System.out.println("[add string]");
str1 += "424242";
str2.append("424242");
System.out.println("String\t\t\t: " + str1.hashCode());
System.out.println("StringBuffer\t: " + str2.hashCode());
}
}
String : 3541024
StringBuffer : 434091818
[add string]
String : -1444123366
StringBuffer : 434091818
String형인 str1의 경우 문자열이 변경되자 hashcode값이 변경되었지만(= 이전과 이후가 다른 객체이다.)
StringBuffer형인 str2의 경우 hashcode값이 그대로 유지됨을 알 수 있다.
String 의 초기화는 직접 문자열 리터럴로 초기화 하든지 아니면 new 연산자로 초기화를 한다.
String 리터럴은 공통 pool에 저장이 된다. 이 영역은 저장공간을 아끼기 위해서 같은 내용의 문자열은 공유를 하게끔 만든다. 반면에 String 객체는 힙 영역에 저장이 되고 같은 내용의 문자열이라도 공유를 하지 않는다.
String s1 = "Hello";// String literal
String s2 = "Hello";// String literal
String s3 = s1;// 같은 참조
String s4 =new String("Hello");// String object (객체)
String s5 =new String("Hello");// String object (객체)
String 초기화의 차이
출처: https://pjh3749.tistory.com/255
자바에서는 기본타입(byte, char, short, int, long, float, double, boolean)의 값을 가지고 있는 객체(프로퍼티랑 다름)를 생성할 수 있다. 이러한 객체를 포장클래스라고 한다. 내부의 값을 변경할 수 없으므로, 변경하고자 한다면 새로운 객체가 생성해야한다.
기존 자료형의 앞 자리를 대문자로 바꾼 형태로 선언하며, 생성자 내부에 문자열을 주어도 잘 변경해서 사용한다.
//Character objChar = new ~~Character~~('가');
//Integer objInteger1 = new ~~Integer~~(42);
//Integer objInteger2 = new ~~Integer~~("42");
//Boolean objBoolean1 = new ~~Boolean~~(false);
//Boolean objBoolean2 = new ~~Boolean~~("false");
Character objChar = '가';
Integer objInteger1 = 42;
Integer objInteger2 = Integer.valueOf("42");
Boolean objBoolean1 = Boolean.FALSE;
Boolean objBoolean2 = Boolean.FALSE;
기존에는 주석내용처럼 선언을 했지만, 취소선과 같이 아래와 같은 경고문과 언박싱, valueOf()메소드, 그리고 simple형를 사용할 것을 권장하였다(권장사항은 주석 밑의 선언형태).
Inspection info: Reports explicit boxing, that is wrapping of primitive values in objects.
Explicit manual boxing is unnecessary as for Java 5 and later, and can safely be removed.
Examples:
Integer i = new Integer(1); → Integer i = Integer.valueOf(1);
int i = Integer.valueOf(1); → int i = 1;
...
Number constructor call with primitive argument
Inspection info:
Reports any attempt to instantiate a new Long, Integer, Short, or Byte object from a primitive long, integer, short, or byte argument.
It is recommended that you use the static method valueOf() introduced in Java 5. By default, this method caches objects for values between -128 and 127 inclusive.
Example:
Integer i = new Integer(1);
Long l = new Long(1L);
After the quick-fix is applied, the code changes to:
Integer i = Integer.valueOf(1);
Long l = Long.valueOf(1L);
대충 요약하면 Java5 이후부터는 명시적인 수동 박싱(Integer 변수 = new Integer(숫자)
)은 불필요하다는 이야기 이며, 자주 사용되는 -128~128사이의 값들은 매번 포장객체로 만들 필요가 없다는 내용이다(자주 사용되고, 값이 변할수 있으므로 -128~128사이의 값들은 매번 객체를 만드는 대신 상수풀에서 가져다 쓴다).
public class IntegerClass {
public static void main(String []args)
{
String str = "123";
int num = Integer.parseInt(str);
System.out.println("str = " + str);
System.out.println("num = " + num);
}
}
/*
실행 결과
str = 123
num = 123
*/
public class IntegerClass2 {
public static void main(String []args)
{
int num1 = 1234;
String str = Integer.toString(num1);
System.out.println("str = " + str);
System.out.println("num1 = " + num1);
}
}
/*
실행 결과
str = 1234
num1 = 1234
*/
public class IntegerClass3 {
public static void main(String []args)
{
int num = 1234;
String str = Integer.toBinaryString(num);
System.out.println("2진수 = " + str);
}
}
/*
실행 결과
2진수 = 10011010010
*/
public class IntegerClass4 {
public static void main(String []args) {
int num1 = 5;
int num2 = 3;
System.out.println(Integer.max(num1, num2));
}
}
실행 결과
5
래퍼 클래스(Wrapper class)는 산술 연산을 위해 정의된 클래스가 아니므로, 인스턴스에 저장된 값을 변경할 수 없습니다.
단지, 값을 참조하기 위해 새로운 인스턴스를 생성하고, 생성된 인스턴스의 값만을 참조할 수 있습니다.
위의 그림과 같이 기본 타입의 데이터를 래퍼 클래스의 인스턴스로 변환하는 과정을 박싱(Boxing)이라고 합니다.
반면 래퍼 클래스의 인스턴스에 저장된 값을 다시 기본 타입의 데이터로 꺼내는 과정을 언박싱(UnBoxing)이라고 합니다.
public class Wrapper {
public static void main(String []args)
{
Integer num1 = new Integer(10); //Boxing
Integer num2 = new Integer(13); //Boxing
int int1 = num1.intValue(); //UnBoxing
int int2 = num2.intValue(); //UnBoxing
Integer result1 = num1 + num2;
Integer result2 = num2 - num1;
int result3 = num1 * num2;
System.out.println(result1);
System.out.println(result2);
System.out.println(result3);
}
}
/*
실행 결과
23
3
130
*/
JDK 1.5부터는 박싱과 언박싱이 필요한 상황에서 자바 컴파일러가 이를 자동으로 처리해 줍니다.
이렇게 자동화된 박싱과 언박싱을 오토 박싱(AutoBoxing)과 오토 언박싱(AutoUnBoxing)이라고 부릅니다.
public class Wrapper2 {
public static void main(String []args)
{
Integer num1 = new Integer(10);
int int1 = num1.intValue();
System.out.println("Boxing :" + num1);
System.out.println("UnBoxing :" + int1);
Character char1 = new Character('X');
char ch1 = char1.charValue();
System.out.println("Boxing : " + char1);
System.out.println("UnBoxing :" +ch1);
System.out.println("====================Auto======");
Integer num2 = 12; //auto boxing;
int int2 = num2; //auto unboxing;
System.out.println("Boxing :" + num2);
System.out.println("UnBoxing :" + int2);
}
}
/*
실행 결과
Boxing :10
UnBoxing :10
Boxing : X
UnBoxing :X
====================Auto======
Boxing :12
UnBoxing :12
*/
Java에서 사용되는 Class들에 대한 정의를 하고 있는 틀이 Class.class 입니다.
Instances of the class Class represent classes and interfaces in a running Java application. Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions.
알 수 있는 정보들 : 경로를 포함한 클래스명, 클래스의 생성자(클래스명), 필드, 메소드정보 등등...
얻는 방법
클래스로부터 얻는 방법
Class clazz = 클래스명.class
Class clazz = Class.forName("패키지...클래스명")
컴파일된 인스턴스로부터 얻는 방법
Class clazz = 인스턴스.getClass()
package classClass.example;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class classClassTest {
public static void main(String[] args) throws Exception {
**Class clazz1 = Member.class;
Class clazz2 = Class.forName("classClass.example.Member");**
Member member = new Member();
**Class Clazz3 = member.getClass();**
System.out.println("clazz1.getClass()");
System.out.println(">> " + clazz1.getClass());
System.out.println("clazz1.getName()");
System.out.println(">> " + clazz1.getName());
System.out.println("\nclazz2.getClass()");
System.out.println(">> " + clazz1.getClass());
System.out.println("clazz2.getName()");
System.out.println(">> " + clazz1.getName());
System.out.println("\nclazz3.getClass()");
System.out.println(">> " + clazz1.getClass());
System.out.println("clazz3.getName()");
System.out.println(">> " + clazz1.getName());
System.out.println("--[field]--");
**Field[] fields = clazz1.getDeclaredFields();**
for (Field temp : fields)
System.out.println(temp.getName());
System.out.println("--[method]--");
**Method[] methods = clazz1.getDeclaredMethods();**
for (Method temp : methods)
System.out.println(temp.getName());
}
}
clazz1.getClass()
>> class java.lang.Class
clazz1.getName()
>> classClass.example.Member
clazz2.getClass()
>> class java.lang.Class
clazz2.getName()
>> classClass.example.Member
clazz3.getClass()
>> class java.lang.Class
clazz3.getName()
>> classClass.example.Member
--[field]--
id
age
--[method]--
setId
getAge
setAge
getId
리플렉션 : 객체를 통해 클래스의 정보를 분석해나가는 프로그래밍 기법. 리플렉션을 통해 동적으로 객체를 생성하는 것 또한 가능해진다(임의로 전달받은 객체의 정보를 받아서, 동적으로 객체 생성). 스프링 프레임워크에서 아주 많이 사용한다고 한다.
참고 : https://joont.tistory.com/165
동적으로 객체를 생성(인스턴스화)할때 사용하는 메소드.
Class memberClass = Class.forName("newinstance.example.Member"); //동적 로딩
Member member = (Member) memberClass.~~newInstance~~();
memberClass.newInstance();
→ 'newInstance()' is deprecated. newInstance()와 생성자를 이용해 생성하는 방식으로 변경됨.
package newinstance.example;
import java.lang.reflect.*;
import java.security.cert.Extension;
public class NewInstanceTest {
public static void main(String[] args) throws Exception {
Class[] parameterTypes = new Class[]{int.class, int.class}; //...(1)
Class memberClass = Class.forName("newinstance.example.Member"); //...(2)
Constructor memberConstructor = memberClass.getConstructor(parameterTypes); //...(3)
Member member = (Member) memberConstructor.newInstance(1010, 24); //...(4)
System.out.println(member.getId() + " : " + member.getAge());
}
}
(1) int.class
( = Integer.TYPE
). int.class
에 대한 정보는 찾기 힘들기 때문에, Oracle docs에서 Integer.TYPE
필드에 대한 정보를 찾아보면 The Class instance representing the primitive type int.라고 나온다. 즉, 기본 타입인 int형을 나타내는 Class클래스의 인스턴스라는 뜻이다.
사용하고자하는 Member 클래스의 생성자의 파라매터가 (int id, int age)이기 때문에, parameterTypes
에 배열형태로 int.class
를 저장한다.
(2) Class 클래스인 memberClass에 Class.forName()
메소드를 이용해 Member클래스정보를 저장한다.
(3) getConstructor(Class<?>... parameterTypes)
는 parameterTypes과 동일한 생성자 객체(생성자에 대한 정보 및 엑세스)를 반환. 매개변수가 맞지 않으면 IllegalArgumentException를 throw한다.
(4) Constructor클래스의 newInstance(Object... initargs)
메소드는 저장 및 엑세스 되어있는 생성자에 매개변수(initargs)를 이용하여 새 인스턴스를 만들고 초기화하여 반환한다.
Oracle docs : Class class, Constructor, Intger
참고 : https://seeminglyjs.tistory.com/246, https://joont.tistory.com/167, https://kephilab.tistory.com/97
동적로딩이란 Class.forName(String className)메소드를 통해, 런타임에 결정된 className으로 클래스 정보를 얻고, 객체를 생성하는 것이다. 사실 위에서 한 것에서 조금만 변형시키면 된다.