매개변수에 따라 Enum 하위 클래스 인스턴스를 반환한다.
핵심 컴포넌트
예시
- registerDriver : JDBC Driver 등록
```java
/**
* Registers the given driver with the {@code DriverManager}.
* A newly-loaded driver class should call
* the method {@code registerDriver} to make itself
* known to the {@code DriverManager}. If the driver is currently
* registered, no action is taken.
*
* @param driver the new JDBC Driver that is to be registered with the
* {@code DriverManager}
* @exception SQLException if a database access error occurs
* @exception NullPointerException if {@code driver} is null
*/
public static void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
/**
* Registers the given driver with the {@code DriverManager}.
* A newly-loaded driver class should call
* the method {@code registerDriver} to make itself
* known to the {@code DriverManager}. If the driver is currently
* registered, no action is taken.
*
* @param driver the new JDBC Driver that is to be registered with the
* {@code DriverManager}
* @param da the {@code DriverAction} implementation to be used when
* {@code DriverManager#deregisterDriver} is called
* @exception SQLException if a database access error occurs
* @exception NullPointerException if {@code driver} is null
* @since 1.8
*/
public static void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/*
* Register the driver if it has not already been added to our list
*/
if (driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
```
- getConnection : DB 연결
- registerDriver 를 통해 연결된 드라이버를 순회하며 Connection 을 맺는다.
- getConnection() 메서드 작성 시점에는 Driver 가 존재하지 않았지만, 실행 시점에 인스턴스를 할당 받는 구조로 동작한다.
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) {
callerCL = Thread.currentThread().getContextClassLoader();
}
if (url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
ensureDriversInitialized();
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for (DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if (isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " +
aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
public class User {
//회원 ID(필수)
private int id;
//회원 이름(필수)
private String userName;
//회원 별명(선택)
private String nickName;
//회원 사물함번호(선택)
private int lockerNumber;
//필수 매개변수를 받는 생성자
public User(int id, String userName){
this(id,userName,null);
}
public User(int id, String userName, String nickName){
this(id,userName,nickName,0);
}
public User(int id, String userName, String nickName, int lockerNumber){
this.id = id;
this.userName = userName;
this.nickName = nickName;
this.lockerNumber = lockerNumber;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", userName='" + userName + '\''
+ ", nickName='" + nickName + '\'' + ", lockerNumber=" + lockerNumber + '}';
}
}
public class NutritionFacts {
private int servingSize = -1; // 필수
private int servings = -1; // 필수
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() { }
public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}
public void setServings(int servings) {
this.servings = servings;
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setFat(int fat) {
this.fat = fat;
}
public void setSodium(int sodium) {
this.sodium = sodium;
}
public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
}
public class main {
public static void main(String[] args) {
NutritionFacts cocalCola = new NutritionFacts();
cocalCola.setServingSize(240);
cocalCola.setServings(8);
cocalCola.setCalories(100);
cocalCola.setSodium(35);
cocalCola.setCarbohydrate(27);
}
}
public class NutritionFacts {
private int servingSize; // 필수
private int servings; // 필수
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public static class Builder {
// 필수 매개변수
private int servingSize;
private int servings;
// 선택 매개변수
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
private Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder servingSize(int servingSize) {
this.servingSize = servingSize;
return this;
}
public Builder servings(int servings) {
this.servings = servings;
return this;
}
public Builder calories(int calories) {
this.calories = calories;
return this;
}
public Builder fat(int fat) {
this.fat = fat;
return this;
}
public Builder sodium(int sodium) {
this.sodium = sodium;
return this;
}
public Builder carbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
return this;
}
public NutritionFacts build() {
NutritionFacts nutritionFacts = new NutritionFacts();
nutritionFacts.fat = this.fat;
nutritionFacts.sodium = this.sodium;
nutritionFacts.servings = this.servings;
nutritionFacts.carbohydrate = this.carbohydrate;
nutritionFacts.servingSize = this.servingSize;
nutritionFacts.calories = this.calories;
return nutritionFacts;
}
}
}
public class Main {
public static void main(String[] args) {
NutritionFacts cocalCola = new NutritionFacts.Builder(240, 8)
.calories(100)
.sodium(35)
.carbohydrate(27)
.build();
}
}
private 생성자 + public static final 필드
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
/**
* public static final 필드인 Elvis.INSTANCE 초기화할 때 한번만 호출됨
**/
private Elvis() { ... }
public void leaveTheBuilding()
}
장점
예외
리플렉션 api인 AccessibleObject.setAccessible 을 사용해 생성자 호출 가능
Constructor<Elvis> constructor = (Constructor<Elvis>) elvis2.getClass().getDeclaredConstructor();
constructor.setAccessible(true);
Elvis elvis3 = constructor.newInstance();
assertNotSame(elvis2, elvis3); // FAIL
해결방법 : private 생성자에서 객체 null 체크하여 null이 아닐 경우 예외를 던지면 된다.
private 생성자 + public static 멤버인 정적 팩토리 메서드
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
// 항상 같은 객체를 제공함 --> 두번째 인스턴스는 만들어지지 않음.
// 리플렉션을 사용한 예외는 적용되므로 예외처리가 필요.
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding()
}
장점
제네릭 싱글톤 팩토리란?
Supplier
- JAVA8에 추가된 함수형 인터페이스
- 매개변수를 받지 않고 단순히 반환하는 추상메서드 존재
- Lazy Evalution 가능 —> 불필요한 연산을 피한다
![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/62bb9e03-0126-4bd3-a8c7-3b791df2dacc/Untitled.png)
- 참고 블로그 : https://m.blog.naver.com/zzang9ha/222087025042
Supplier<Elvis> elvisSupplier = Elvis::getInstance;
Elvis elvis = elvisSupplier.get();
readResolve() 메서드에서 싱글턴 인스턴스 반환
모든 필드에 transient(직렬화 제외) 키워드 추가
// 역질렬화시 반드시 호출되는 readResolve 메서드를 싱글턴을 리턴하도록 수정
// 가짜 elvis 는 가비지 컬렉터로...
private Obejct readResolve() {
return INSTANCE;
}
원소가 하나인 열거타입 선언하는 방식
싱글턴을 만드는 가장 좋은 방법!!
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {}
}
public final class Math {
/**
* Don't let anyone instantiate this class.
*/
**private Math() {}**
/**
* The {@code double} value that is closer than any other to
* <i>e</i>, the base of the natural logarithms.
*/
public static final double E = 2.7182818284590452354;
/**
* The {@code double} value that is closer than any other to
* <i>pi</i>, the ratio of the circumference of a circle to its
* diameter.
*/
public static final double PI = 3.14159265358979323846;
/**
* Constant by which to multiply an angular value in degrees to obtain an
* angular value in radians.
*/
private static final double DEGREES_TO_RADIANS = 0.017453292519943295;
/**
* Constant by which to multiply an angular value in radians to obtain an
* angular value in degrees.
*/
private static final double RADIANS_TO_DEGREES = 57.29577951308232;
/**
* Returns the trigonometric sine of an angle. Special cases:
* <ul><li>If the argument is NaN or an infinity, then the
* result is NaN.
* <li>If the argument is zero, then the result is a zero with the
* same sign as the argument.</ul>
*
* <p>The computed result must be within 1 ulp of the exact result.
* Results must be semi-monotonic.
*
* @param a an angle, in radians.
* @return the sine of the argument.
*/
@HotSpotIntrinsicCandidate
public static double sin(double a) {
return StrictMath.sin(a); // default impl. delegates to StrictMath
}
/**
* Returns the trigonometric cosine of an angle. Special cases:
* <ul><li>If the argument is NaN or an infinity, then the
* result is NaN.</ul>
*
* <p>The computed result must be within 1 ulp of the exact result.
* Results must be semi-monotonic.
*
* @param a an angle, in radians.
* @return the cosine of the argument.
*/
@HotSpotIntrinsicCandidate
public static double cos(double a) {
return StrictMath.cos(a); // default impl. delegates to StrictMath
}
/**
* Returns the trigonometric tangent of an angle. Special cases:
* <ul><li>If the argument is NaN or an infinity, then the result
* is NaN.
* <li>If the argument is zero, then the result is a zero with the
* same sign as the argument.</ul>
*
* <p>The computed result must be within 1 ulp of the exact result.
* Results must be semi-monotonic.
*
* @param a an angle, in radians.
* @return the tangent of the argument.
*/
@HotSpotIntrinsicCandidate
public static double tan(double a) {
return StrictMath.tan(a); // default impl. delegates to StrictMath
}
/**
* Returns the arc sine of a value; the returned angle is in the
* range -<i>pi</i>/2 through <i>pi</i>/2. Special cases:
* <ul><li>If the argument is NaN or its absolute value is greater
* than 1, then the result is NaN.
* <li>If the argument is zero, then the result is a zero with the
* same sign as the argument.</ul>
*
* <p>The computed result must be within 1 ulp of the exact result.
* Results must be semi-monotonic.
*
* @param a the value whose arc sine is to be returned.
* @return the arc sine of the argument.
*/
public static double asin(double a) {
return StrictMath.asin(a); // default impl. delegates to StrictMath
}
/**
* Returns the arc cosine of a value; the returned angle is in the
* range 0.0 through <i>pi</i>. Special case:
* <ul><li>If the argument is NaN or its absolute value is greater
* than 1, then the result is NaN.</ul>
*
* <p>The computed result must be within 1 ulp of the exact result.
* Results must be semi-monotonic.
*
* @param a the value whose arc cosine is to be returned.
* @return the arc cosine of the argument.
*/
public static double acos(double a) {
return StrictMath.acos(a); // default impl. delegates to StrictMath
}
}
public class Arrays {
/**
* The minimum array length below which a parallel sorting
* algorithm will not further partition the sorting task. Using
* smaller sizes typically results in memory contention across
* tasks that makes parallel speedups unlikely.
*/
private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;
// Suppresses default constructor, ensuring non-instantiability.
// 기본 생성자를 억제하여 비인스턴스성을 보장합니다.
private Arrays() {}
/**
* A comparator that implements the natural ordering of a group of
* mutually comparable elements. May be used when a supplied
* comparator is null. To simplify code-sharing within underlying
* implementations, the compare method only declares type Object
* for its second argument.
*
* Arrays class implementor's note: It is an empirical matter
* whether ComparableTimSort offers any performance benefit over
* TimSort used with this comparator. If not, you are better off
* deleting or bypassing ComparableTimSort. There is currently no
* empirical case for separating them for parallel sorting, so all
* public Object parallelSort methods use the same comparator
* based implementation.
*/
static final class NaturalOrder implements Comparator<Object> {
@SuppressWarnings("unchecked")
public int compare(Object first, Object second) {
return ((Comparable<Object>)first).compareTo(second);
}
static final NaturalOrder INSTANCE = new NaturalOrder();
}
/**
* Checks that {@code fromIndex} and {@code toIndex} are in
* the range and throws an exception if they aren't.
*/
static void rangeCheck(int arrayLength, int fromIndex, int toIndex) {
if (fromIndex > toIndex) {
throw new IllegalArgumentException(
"fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
}
if (fromIndex < 0) {
throw new ArrayIndexOutOfBoundsException(fromIndex);
}
if (toIndex > arrayLength) {
throw new ArrayIndexOutOfBoundsException(toIndex);
}
}
}
생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만들어준다.
- 매개변수를 받지 않는 public 생성자가 생성
—> 추상 클래스로 만들면 되지 않는가?
하위 클래스를 만들어 인스턴스화가 가능하다.
결론 ⇒ private 생성자를 만든다.
컴파일러가 기본생성자를 만드는 경우는 명시된 생성자가 없을 때 뿐이므로 인스턴스화를 막을 수 있다.
private이니 클래스 바깥에서 접근할 수 없다.
+) 상속이 불가능하다.
예시코드
public class Utility {
// 기본 생성자가 만들어지는 것을 막는다. (인스턴스화 방지용)
private Utility() {
throw new AssertionError();
}
// 코드 생략 .....
}
// 정적 유틸리티 클래스 잘못된 예
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // 인스턴스화 방지 (아이템 4 참고)
public static boolean isVaild(String word) {...}
public static List<String> suggestions(String typo) {...}
}
//사용은 이렇게!
SpellChecker.isValid(word);
// 싱글턴 클래스 잘못된 예
public class SpellChecker {
private final Lexicon dictionary = ...;
private SpellChecker() {} // 인스턴스화 방지 (아이템 4 참고)
public static SpellChecker INSTANCE = new SpellChecker(...);
public static boolean isVaild(String word) {...}
public static List<String> suggestions(String typo) {...}
}
//사용은 이렇게!
SpellChecker.INSTANCE.isValid(word);
public class Main {
public static void main(String[] args) {
Runnable d = new CheckThread("으니");
// d2 생성 시 name static 변수에는 으니2로 세팅된다.
Runnable d2 = new CheckThread("으니2");
Thread t1 = new Thread(d);
Thread t2 = new Thread(d2);
// 따라서 결과는 으니2만 출력된다. (의도한바가 아님.)
t1.start();
t2.start();
}
}
// 멀티 쓰레드 테스트
class CheckThread implements Runnable {
private static String name;
CheckThread(String name) {
setName(name);
}
public static void setName(String name) {
CheckThread.name = name;
}
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println(name);
Thread.sleep(1000);
}
} catch (Exception e) {
System.out.println(e);
}
System.out.println("쓰레드 종료 : " + name);
}
}
public class SpellChecker {
private final Lexicon dictionary; //final로 불변을 보장하자
//생성자에 필요한 자원을 넘겨준다
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) {...}
public List<String> suggestions(String typo) {...}
}
package pattern.factory;
// 로봇 추상 클래스
public abstract class Robot {
public abstract String getName();
}
// 슈퍼로봇 구현체
public class SuperRobot extends Robot {
@Override
public String getName() {
return "SuperRobot";
}
}
// 파워로봇 구현체
public class PowerRobot extends Robot {
@Override
public String getName() {
return "PowerRobot";
}
}
// 로봇 공장
public class RobotFactory {
@Override
Robot createRobot(String name) {
switch( name ){
case "super": return new SuperRobot();
case "power": return new PowerRobot();
}
return null;
}
}
public class FactoryMain {
public static void main(String[] args) {
RobotFactory rf = new RobotFactory();
Robot r = rf.createRobot("super");
Robot r2 = rf.createRobot("power");
System.out.println(r.getName());
System.out.println(r2.getName());
}
}
String s = new String("으니");
왜 잘못되었는가?실행될 때마다 String 인스턴스를 새로 만든다.
⇒ 쓸데없이 String 인스턴스가 수백 만 개 만들어질 수 있다!
개선 버전
/**
* 하나의 String 인스턴스를 사용한다.
* 생성자 대신 정적 팩터리 메서드를 사용하는 예시이다.
* 생성자는 호출할 때마다 새로운 객체를 만들지만, 팩토리 메서드는 전혀 그렇지않다.
**/
String s = "으니";
new String 과 String 의 차이점 (참고링크)
https://ict-nroo.tistory.com/18
https://starkying.tistory.com/entry/what-is-java-string-pool
static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(c[md]|D?C{0,3})"+"(X[CL]|L?X{0,3})(I[XV]|V?I{0.3})$");
}
왜 잘못되었는가?
String.matches 는 생성 비용이 아주 비싼 객체이다.
⇒ 캐싱하여 재사용 하는 게 좋다!
- 개선 버전
```java
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(c[md]|D?C{0,3})"+"(X[CL]|L?X{0,3})(I[XV]|V?I{0.3})$"
);
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
```
- **개선점**
1. 코드 의미가 명확하다.
2. 성능이 좋아진다.
⇒ 클래스 초기화 과정에서 직접 생성해 캐싱하고, 호출될 때 인스턴스를 재사용하기 때문이다.
private static long sum() {
Long sum = 0L;
for(long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i; // sum += Long.valueOf(i); 와 같다.
}
return sum;
}
왜 잘못되었는가?
오토박싱
개선버전
private static long sum() {
long sum = 0L;
for(long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}