├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── me │ │ └── whiteship │ │ ├── chapter01 │ │ ├── item01 │ │ │ ├── ActionEnum.java │ │ │ ├── AdvancedSettings.java │ │ │ ├── App.java │ │ │ ├── AppConfig.java │ │ │ ├── Difficulty.java │ │ │ ├── HelloService.java │ │ │ ├── HelloServiceFactory.java │ │ │ ├── ListQuiz.java │ │ │ ├── Order.java │ │ │ ├── OrderStatus.java │ │ │ ├── Product.java │ │ │ └── Settings.java │ │ ├── item02 │ │ │ ├── builder │ │ │ │ ├── BuilderTest.java │ │ │ │ └── NutritionFacts.java │ │ │ ├── freeze │ │ │ │ ├── FreezeTest.js │ │ │ │ └── Person.java │ │ │ ├── hierarchicalbuilder │ │ │ │ ├── Calzone.java │ │ │ │ ├── NyPizza.java │ │ │ │ ├── Pizza.java │ │ │ │ └── PizzaTest.java │ │ │ ├── illegalargumentexception │ │ │ │ └── Order.java │ │ │ ├── javabeans │ │ │ │ └── NutritionFacts.java │ │ │ ├── telescopingconstructor │ │ │ │ └── NutritionFacts.java │ │ │ └── varargs │ │ │ │ └── VarargsSamples.java │ │ ├── item03 │ │ │ ├── enumtype │ │ │ │ ├── Elvis.java │ │ │ │ ├── EnumElvisReflection.java │ │ │ │ └── EnumElvisSerialization.java │ │ │ ├── field │ │ │ │ ├── Concert.java │ │ │ │ ├── Elvis.java │ │ │ │ ├── ElvisReflection.java │ │ │ │ ├── ElvisSerialization.java │ │ │ │ └── IElvis.java │ │ │ ├── functionalinterface │ │ │ │ ├── DefaultFunctions.java │ │ │ │ ├── MyFunction.java │ │ │ │ └── UsageOfFunctions.java │ │ │ ├── methodreference │ │ │ │ └── Person.java │ │ │ ├── serialization │ │ │ │ ├── Book.java │ │ │ │ └── SerializationExample.java │ │ │ └── staticfactory │ │ │ │ ├── Concert.java │ │ │ │ ├── Elvis.java │ │ │ │ ├── MetaElvis.java │ │ │ │ └── Singer.java │ │ ├── item04 │ │ │ ├── DefaultUtilityClass.java │ │ │ └── UtilityClass.java │ │ ├── item05 │ │ │ ├── DefaultDictionary.java │ │ │ ├── Dictionary.java │ │ │ ├── MockDictionary.java │ │ │ ├── dependencyinjection │ │ │ │ ├── DictionaryFactory.java │ │ │ │ └── SpellChecker.java │ │ │ ├── factorymethod │ │ │ │ ├── DefaultDictionaryFactory.java │ │ │ │ ├── DictionaryFactory.java │ │ │ │ ├── MockDictionaryFactory.java │ │ │ │ └── SpellChecker.java │ │ │ ├── package-info.java │ │ │ ├── singleton │ │ │ │ └── SpellChecker.java │ │ │ ├── springioc │ │ │ │ ├── App.java │ │ │ │ ├── AppConfig.java │ │ │ │ ├── SpellChecker.java │ │ │ │ └── SpringDictionary.java │ │ │ └── staticutils │ │ │ │ └── SpellChecker.java │ │ ├── item06 │ │ │ ├── Client.java │ │ │ ├── Deprecation.java │ │ │ ├── RegularExpression.java │ │ │ ├── RomanNumerals.java │ │ │ ├── Strings.java │ │ │ └── Sum.java │ │ ├── item07 │ │ │ ├── cache │ │ │ │ ├── CacheKey.java │ │ │ │ ├── Post.java │ │ │ │ └── PostRepository.java │ │ │ ├── executor │ │ │ │ └── ExecutorsExample.java │ │ │ ├── listener │ │ │ │ ├── ChatRoom.java │ │ │ │ └── User.java │ │ │ ├── optional │ │ │ │ ├── Channel.java │ │ │ │ └── MemberShip.java │ │ │ ├── reference │ │ │ │ ├── BigObject.java │ │ │ │ ├── BigObjectReference.java │ │ │ │ ├── PhantomReferenceExample.java │ │ │ │ ├── SoftReferenceExample.java │ │ │ │ └── WeakReferenceExample.java │ │ │ └── stack │ │ │ │ ├── EmptyStackException.java │ │ │ │ └── Stack.java │ │ ├── item08 │ │ │ ├── autoclosable │ │ │ │ ├── App.java │ │ │ │ └── AutoClosableIsGood.java │ │ │ ├── cleaner │ │ │ │ ├── BigObject.java │ │ │ │ └── CleanerIsNotGood.java │ │ │ ├── cleaner_as_a_safetynet │ │ │ │ ├── Adult.java │ │ │ │ ├── Room.java │ │ │ │ └── Teenager.java │ │ │ ├── finalizer │ │ │ │ ├── App.java │ │ │ │ └── FinalizerIsBad.java │ │ │ ├── finalizer_attack │ │ │ │ ├── Account.java │ │ │ │ └── BrokenAccount.java │ │ │ └── outerclass │ │ │ │ ├── LambdaExample.java │ │ │ │ └── OuterClass.java │ │ └── item09 │ │ │ ├── puzzler │ │ │ └── Copy.java │ │ │ ├── suppress │ │ │ ├── BadBufferedReader.java │ │ │ └── TopLine.java │ │ │ ├── tryfinally │ │ │ ├── Copy.java │ │ │ └── TopLine.java │ │ │ └── trywithresources │ │ │ ├── Copy.java │ │ │ ├── TopLine.java │ │ │ └── TopLineWithDefault.java │ │ └── chapter02 │ │ ├── item10 │ │ ├── CaseInsensitiveString.java │ │ ├── Color.java │ │ ├── EqualsInJava.java │ │ ├── PhoneNumber.java │ │ ├── Point.java │ │ ├── autovalue │ │ │ ├── AutoValueTest.java │ │ │ └── Point.java │ │ ├── composition │ │ │ └── ColorPoint.java │ │ ├── inheritance │ │ │ ├── ColorPoint.java │ │ │ ├── CounterPoint.java │ │ │ ├── CounterPointTest.java │ │ │ ├── SmellPoint.java │ │ │ └── SmellPointTest.java │ │ ├── lombok │ │ │ ├── LombokTest.java │ │ │ └── Point.java │ │ └── record │ │ │ ├── Point.java │ │ │ └── PointTest.java │ │ ├── item11 │ │ ├── guava │ │ │ └── PhoneNumber.java │ │ ├── hashcode │ │ │ └── PhoneNumber.java │ │ ├── hashtable │ │ │ └── HashMapTest.java │ │ └── package-info.java │ │ ├── item12 │ │ └── PhoneNumber.java │ │ ├── item13 │ │ ├── EmptyStackException.java │ │ ├── HashTable.java │ │ ├── PhoneNumber.java │ │ ├── Stack.java │ │ ├── clone_use_constructor │ │ │ ├── Item.java │ │ │ └── SubItem.java │ │ ├── copy_constructor │ │ │ └── HashSetExample.java │ │ ├── exception │ │ │ ├── MyApp.java │ │ │ └── MyException.java │ │ ├── inheritance │ │ │ ├── Shape.java │ │ │ └── Square.java │ │ └── treeset │ │ │ └── TreeSetExample.java │ │ └── item14 │ │ ├── CaseInsensitiveString.java │ │ ├── CompareToConvention.java │ │ ├── PhoneNumber.java │ │ ├── WordList.java │ │ ├── composition │ │ ├── NamedPoint.java │ │ └── Point.java │ │ ├── decimal │ │ ├── DecimalIsNotCorrect.java │ │ └── IntOverflow.java │ │ └── interitance │ │ ├── NamedPoint.java │ │ └── Point.java └── resources │ └── application.properties └── test └── java └── me └── whiteship └── chapter01 ├── item03 └── field │ ├── ConcertTest.java │ └── MockElvis.java ├── item05 ├── dependencyinjection │ └── SpellCheckerTest.java └── staticutils │ └── SpellCheckerTest.java ├── item07 ├── cache │ └── PostRepositoryTest.java ├── listener │ └── ChatRoomTest.java └── optional │ └── ChannelTest.java └── item08 └── finalizer_attack └── AccountTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteship/effective-java/b24c323cdbca06a710133bd577e208a8ef5d9b45/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.3 9 | 10 | 11 | me.whiteship 12 | effective-java-part1 13 | 0.0.1-SNAPSHOT 14 | effective-java-part1 15 | effective-java-part1 16 | 17 | 11 18 | 19 | 20 | 21 | 22 | com.google.guava 23 | guava 24 | 31.1-jre 25 | 26 | 27 | com.google.auto.value 28 | auto-value-annotations 29 | 1.9 30 | 31 | 32 | com.google.auto.value 33 | auto-value 34 | 1.9 35 | 36 | 37 | 38 | me.whiteship.hello 39 | chinese-hello-service 40 | 0.0.1-SNAPSHOT 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter 46 | 47 | 48 | 49 | org.projectlombok 50 | lombok 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-test 56 | test 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/ActionEnum.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | public enum ActionEnum { 4 | ACTION_1(new App()) 5 | , ACTION_2(new App()); 6 | 7 | private final App action; 8 | 9 | ActionEnum(App action) {this.action = action;} 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/AdvancedSettings.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | public class AdvancedSettings { 4 | 5 | Settings settings; 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/App.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 5 | 6 | public class App { 7 | 8 | public static void main(String[] args) { 9 | ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); 10 | HelloService helloService = applicationContext.getBean(HelloService.class); 11 | System.out.println(helloService.hello()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/AppConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | import me.whiteship.hello.ChineseHelloService; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class AppConfig { 9 | 10 | @Bean 11 | public HelloService helloService() { 12 | return new ChineseHelloService(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/Difficulty.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | public enum Difficulty { 4 | 5 | EASY, NORMAL, HARD, HELL 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/HelloService.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | public interface HelloService { 4 | 5 | String hello(); 6 | 7 | static String hi() { 8 | prepareMessage(); 9 | return "hi"; 10 | } 11 | 12 | static private void prepareMessage() { 13 | } 14 | 15 | static String hi1() { 16 | prepareMessage(); 17 | return "hi"; 18 | } 19 | 20 | static String hi2() { 21 | prepareMessage(); 22 | return "hi"; 23 | } 24 | 25 | default String bye() { 26 | return "bye"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/HelloServiceFactory.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | import me.whiteship.hello.ChineseHelloService; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.util.Optional; 8 | import java.util.ServiceLoader; 9 | 10 | public class HelloServiceFactory { 11 | 12 | public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { 13 | ServiceLoader loader = ServiceLoader.load(HelloService.class); 14 | Optional helloServiceOptional = loader.findFirst(); 15 | helloServiceOptional.ifPresent(h -> { 16 | System.out.println(h.hello()); 17 | }); 18 | 19 | HelloService helloService = new ChineseHelloService(); 20 | System.out.println(helloService.hello()); 21 | 22 | // Class aClass = Class.forName("me.whiteship.hello.ChineseHelloService"); 23 | // Constructor constructor = aClass.getConstructor(); 24 | // HelloService helloService = (HelloService) constructor.newInstance(); 25 | // System.out.println(helloService.hello()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/ListQuiz.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Comparator; 5 | import java.util.List; 6 | 7 | public class ListQuiz { 8 | 9 | public static void main(String[] args) { 10 | List numbers = new ArrayList(); 11 | numbers.add(100); 12 | numbers.add(20); 13 | numbers.add(44); 14 | numbers.add(3); 15 | 16 | System.out.println(numbers); 17 | 18 | Comparator desc = (o1, o2) -> o2 - o1; 19 | 20 | numbers.sort(desc.reversed()); 21 | 22 | System.out.println(numbers); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/Order.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | import java.util.*; 4 | 5 | public class Order { 6 | 7 | private boolean prime; 8 | 9 | private boolean urgent; 10 | 11 | private Product product; 12 | 13 | private OrderStatus orderStatus; 14 | 15 | public static Order primeOrder(Product product) { 16 | Order order = new Order(); 17 | order.prime = true; 18 | order.product = product; 19 | 20 | return order; 21 | } 22 | 23 | public static Order urgentOrder(Product product) { 24 | Order order = new Order(); 25 | order.urgent = true; 26 | order.product = product; 27 | return order; 28 | } 29 | 30 | public static void main(String[] args) { 31 | 32 | Order order = new Order(); 33 | if (order.orderStatus == OrderStatus.DELIVERED) { 34 | System.out.println("delivered"); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | public enum OrderStatus { 4 | 5 | PREPARING(0), SHIPPED(1), DELIVERING(2), DELIVERED(3); 6 | 7 | private int number; 8 | 9 | OrderStatus(int number) { 10 | this.number = number; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/Product.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | import java.util.EnumSet; 4 | import java.util.Set; 5 | 6 | public class Product { 7 | 8 | public static void main(String[] args) { 9 | Settings settings1 = Settings.getInstance(); 10 | Settings settings2 = Settings.getInstance(); 11 | 12 | System.out.println(settings1); 13 | System.out.println(settings2); 14 | 15 | Boolean.valueOf(false); 16 | EnumSet.allOf(Difficulty.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item01/Settings.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item01; 2 | 3 | /** 4 | * 이 클래스의 인스턴스는 #getInstance()를 통해 사용한다. 5 | * @see #getInstance() 6 | */ 7 | public class Settings { 8 | 9 | private boolean useAutoSteering; 10 | 11 | private boolean useABS; 12 | 13 | private Difficulty difficulty; 14 | 15 | private Settings() {} 16 | 17 | private static final Settings SETTINGS = new Settings(); 18 | 19 | public static Settings getInstance() { 20 | return SETTINGS; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/builder/BuilderTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.builder; 2 | 3 | public class BuilderTest { 4 | 5 | public static void main(String[] args) { 6 | new NutritionFacts.Builder(10, 10) 7 | .calories(10) 8 | .build(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/builder/NutritionFacts.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.builder; 2 | 3 | // 코드 2-3 빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다. (17~18쪽) 4 | public class NutritionFacts { 5 | private final int servingSize; 6 | private final int servings; 7 | private final int calories; 8 | private final int fat; 9 | private final int sodium; 10 | private final int carbohydrate; 11 | 12 | public static void main(String[] args) { 13 | NutritionFacts cocaCola = new Builder(240, 8) 14 | .calories(100) 15 | .sodium(35) 16 | .carbohydrate(27).build(); 17 | } 18 | 19 | public static class Builder { 20 | // 필수 매개변수 21 | private final int servingSize; 22 | private final int servings; 23 | 24 | // 선택 매개변수 - 기본값으로 초기화한다. 25 | private int calories = 0; 26 | private int fat = 0; 27 | private int sodium = 0; 28 | private int carbohydrate = 0; 29 | 30 | public Builder(int servingSize, int servings) { 31 | this.servingSize = servingSize; 32 | this.servings = servings; 33 | } 34 | 35 | public Builder calories(int val) 36 | { calories = val; return this; } 37 | public Builder fat(int val) 38 | { fat = val; return this; } 39 | public Builder sodium(int val) 40 | { sodium = val; return this; } 41 | public Builder carbohydrate(int val) 42 | { carbohydrate = val; return this; } 43 | 44 | public NutritionFacts build() { 45 | return new NutritionFacts(this); 46 | } 47 | } 48 | 49 | private NutritionFacts(Builder builder) { 50 | servingSize = builder.servingSize; 51 | servings = builder.servings; 52 | calories = builder.calories; 53 | fat = builder.fat; 54 | sodium = builder.sodium; 55 | carbohydrate = builder.carbohydrate; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/freeze/FreezeTest.js: -------------------------------------------------------------------------------- 1 | const keesun = { 2 | 'name': 'Keesun', 3 | 'age': 40 4 | }; 5 | 6 | Object.freeze(keesun); 7 | 8 | keesun.kids = ["서연"]; 9 | 10 | console.info(keesun.name); -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/freeze/Person.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.freeze; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Person { 7 | 8 | private final String name; 9 | 10 | private final int birthYear; 11 | 12 | private final List kids; 13 | 14 | public Person(String name, int birthYear) { 15 | this.name = name; 16 | this.birthYear = birthYear; 17 | this.kids = new ArrayList<>(); 18 | } 19 | 20 | public static void main(String[] args) { 21 | Person person = new Person("keesun", 1982); 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/Calzone.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.hierarchicalbuilder; 2 | 3 | // 코드 2-6 칼초네 피자 - 계층적 빌더를 활용한 하위 클래스 (20~21쪽) 4 | public class Calzone extends Pizza { 5 | private final boolean sauceInside; 6 | 7 | public static class Builder extends Pizza.Builder { 8 | private boolean sauceInside = false; // 기본값 9 | 10 | public Builder sauceInside() { 11 | sauceInside = true; 12 | return this; 13 | } 14 | 15 | @Override public Calzone build() { 16 | return new Calzone(this); 17 | } 18 | 19 | @Override protected Builder self() { return this; } 20 | } 21 | 22 | private Calzone(Builder builder) { 23 | super(builder); 24 | sauceInside = builder.sauceInside; 25 | } 26 | 27 | @Override public String toString() { 28 | return String.format("%s로 토핑한 칼초네 피자 (소스는 %s에)", 29 | toppings, sauceInside ? "안" : "바깥"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/NyPizza.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.hierarchicalbuilder; 2 | 3 | import java.util.Objects; 4 | 5 | // 코드 2-5 뉴욕 피자 - 계층적 빌더를 활용한 하위 클래스 (20쪽) 6 | public class NyPizza extends Pizza { 7 | public enum Size { SMALL, MEDIUM, LARGE } 8 | private final Size size; 9 | 10 | public static class Builder extends Pizza.Builder { 11 | private final Size size; 12 | 13 | public Builder(Size size) { 14 | this.size = Objects.requireNonNull(size); 15 | } 16 | 17 | @Override public NyPizza build() { 18 | return new NyPizza(this); 19 | } 20 | 21 | @Override protected Builder self() { return this; } 22 | } 23 | 24 | private NyPizza(Builder builder) { 25 | super(builder); 26 | size = builder.size; 27 | } 28 | 29 | @Override public String toString() { 30 | return toppings + "로 토핑한 뉴욕 피자"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/Pizza.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.hierarchicalbuilder; 2 | 3 | import java.util.EnumSet; 4 | import java.util.Objects; 5 | import java.util.Set; 6 | 7 | // 코드 2-4 계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴 (19쪽) 8 | 9 | // 참고: 여기서 사용한 '시뮬레이트한 셀프 타입(simulated self-type)' 관용구는 10 | // 빌더뿐 아니라 임의의 유동적인 계층구조를 허용한다. 11 | 12 | public abstract class Pizza { 13 | public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE } 14 | final Set toppings; 15 | 16 | abstract static class Builder> { 17 | EnumSet toppings = EnumSet.noneOf(Topping.class); 18 | public T addTopping(Topping topping) { 19 | toppings.add(Objects.requireNonNull(topping)); 20 | return self(); 21 | } 22 | 23 | abstract Pizza build(); 24 | 25 | // 하위 클래스는 이 메서드를 재정의(overriding)하여 26 | // "this"를 반환하도록 해야 한다. 27 | protected abstract T self(); 28 | } 29 | 30 | Pizza(Builder builder) { 31 | toppings = builder.toppings.clone(); // 아이템 50 참조 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/PizzaTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.hierarchicalbuilder; 2 | 3 | 4 | import static me.whiteship.chapter01.item02.hierarchicalbuilder.NyPizza.Size.SMALL; 5 | import static me.whiteship.chapter01.item02.hierarchicalbuilder.Pizza.Topping.*; 6 | 7 | // 계층적 빌더 사용 (21쪽) 8 | public class PizzaTest { 9 | public static void main(String[] args) { 10 | NyPizza pizza = new NyPizza.Builder(SMALL) 11 | .addTopping(SAUSAGE) 12 | .addTopping(ONION).build(); 13 | 14 | Calzone calzone = new Calzone.Builder() 15 | .addTopping(HAM).sauceInside().build(); 16 | 17 | System.out.println(pizza); 18 | System.out.println(calzone); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/illegalargumentexception/Order.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.illegalargumentexception; 2 | 3 | import java.time.LocalDate; 4 | 5 | public class Order { 6 | 7 | public void updateDeliveryDate(LocalDate deliveryDate) { 8 | if (deliveryDate == null) { 9 | throw new NullPointerException("deliveryDate can't be null"); 10 | } 11 | 12 | if (deliveryDate.isBefore(LocalDate.now())) { 13 | //TODO 과거로 배송 해달라고?? 14 | throw new IllegalArgumentException("deliveryDate can't be earlier than " + LocalDate.now()); 15 | } 16 | 17 | // 배송 날짜 업데이트 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/javabeans/NutritionFacts.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.javabeans; 2 | 3 | // 코드 2-2 자바빈즈 패턴 - 일관성이 깨지고, 불변으로 만들 수 없다. (16쪽) 4 | public class NutritionFacts { 5 | // 필드 (기본값이 있다면) 기본값으로 초기화된다. 6 | private int servingSize = -1; // 필수; 기본값 없음 7 | private int servings = -1; // 필수; 기본값 없음 8 | private int calories = 0; 9 | private int fat = 0; 10 | private int sodium = 0; 11 | private int carbohydrate = 0; 12 | private boolean healthy; 13 | 14 | public NutritionFacts() { } 15 | 16 | public void setServingSize(int servingSize) { 17 | this.servingSize = servingSize; 18 | } 19 | 20 | public void setServings(int servings) { 21 | this.servings = servings; 22 | } 23 | 24 | public void setCalories(int calories) { 25 | this.calories = calories; 26 | } 27 | 28 | public void setFat(int fat) { 29 | this.fat = fat; 30 | } 31 | 32 | public void setSodium(int sodium) { 33 | this.sodium = sodium; 34 | } 35 | 36 | public void setCarbohydrate(int carbohydrate) { 37 | this.carbohydrate = carbohydrate; 38 | } 39 | 40 | public void setHealthy(boolean healthy) { 41 | this.healthy = healthy; 42 | } 43 | 44 | public static void main(String[] args) { 45 | NutritionFacts cocaCola = new NutritionFacts(); 46 | cocaCola.setServingSize(240); 47 | cocaCola.setServings(8); 48 | 49 | cocaCola.setCalories(100); 50 | cocaCola.setSodium(35); 51 | cocaCola.setCarbohydrate(27); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/telescopingconstructor/NutritionFacts.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.telescopingconstructor; 2 | 3 | // 코드 2-1 점층적 생성자 패턴 - 확장하기 어렵다! (14~15쪽) 4 | public class NutritionFacts { 5 | private final int servingSize; // (mL, 1회 제공량) 필수 6 | private final int servings; // (회, 총 n회 제공량) 필수 7 | private final int calories; // (1회 제공량당) 선택 8 | private final int fat; // (g/1회 제공량) 선택 9 | private final int sodium; // (mg/1회 제공량) 선택 10 | private final int carbohydrate; // (g/1회 제공량) 선택 11 | 12 | public NutritionFacts(int servingSize, int servings) { 13 | this(servingSize, servings, 0); 14 | } 15 | 16 | public NutritionFacts(int servingSize, int servings, 17 | int calories) { 18 | this(servingSize, servings, calories, 0); 19 | } 20 | 21 | public NutritionFacts(int servingSize, int servings, 22 | int calories, int fat) { 23 | this(servingSize, servings, calories, fat, 0); 24 | } 25 | 26 | public NutritionFacts(int servingSize, int servings, 27 | int calories, int fat, int sodium) { 28 | this(servingSize, servings, calories, fat, sodium, 0); 29 | } 30 | 31 | public NutritionFacts(int servingSize, int servings, 32 | int calories, int fat, int sodium, int carbohydrate) { 33 | this.servingSize = servingSize; 34 | this.servings = servings; 35 | this.calories = calories; 36 | this.fat = fat; 37 | this.sodium = sodium; 38 | this.carbohydrate = carbohydrate; 39 | } 40 | 41 | public static void main(String[] args) { 42 | NutritionFacts cocaCola = 43 | new NutritionFacts(10, 10); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item02/varargs/VarargsSamples.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item02.varargs; 2 | 3 | import java.util.Arrays; 4 | 5 | public class VarargsSamples { 6 | 7 | public void printNumbers(int... numbers) { 8 | System.out.println(numbers.getClass().getCanonicalName()); 9 | System.out.println(numbers.getClass().getComponentType()); 10 | Arrays.stream(numbers).forEach(System.out::println); 11 | } 12 | 13 | public static void main(String[] args) { 14 | VarargsSamples samples = new VarargsSamples(); 15 | samples.printNumbers(1, 20, 20, 39, 59); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/enumtype/Elvis.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.enumtype; 2 | 3 | // 열거 타입 방식의 싱글턴 - 바람직한 방법 (25쪽) 4 | public enum Elvis { 5 | INSTANCE; 6 | 7 | public void leaveTheBuilding() { 8 | System.out.println("기다려 자기야, 지금 나갈께!"); 9 | } 10 | 11 | // 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다! 12 | public static void main(String[] args) { 13 | Elvis elvis = Elvis.INSTANCE; 14 | elvis.leaveTheBuilding(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/enumtype/EnumElvisReflection.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.enumtype; 2 | 3 | import java.lang.reflect.Constructor; 4 | 5 | public class EnumElvisReflection { 6 | 7 | public static void main(String[] args) { 8 | try { 9 | Constructor declaredConstructor = Elvis.class.getDeclaredConstructor(); 10 | System.out.println(declaredConstructor); 11 | } catch (NoSuchMethodException e) { 12 | e.printStackTrace(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/enumtype/EnumElvisSerialization.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.enumtype; 2 | 3 | import me.whiteship.chapter01.item03.field.Elvis; 4 | 5 | import java.io.*; 6 | 7 | public class EnumElvisSerialization { 8 | 9 | public static void main(String[] args) { 10 | try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("elvis.obj"))) { 11 | out.writeObject(Elvis.INSTANCE); 12 | } catch (IOException e) { 13 | e.printStackTrace(); 14 | } 15 | 16 | try (ObjectInput in = new ObjectInputStream(new FileInputStream("elvis.obj"))) { 17 | Elvis elvis = (Elvis) in.readObject(); 18 | System.out.println(elvis == Elvis.INSTANCE); 19 | } catch (IOException | ClassNotFoundException e) { 20 | e.printStackTrace(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/field/Concert.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.field; 2 | 3 | public class Concert { 4 | 5 | private boolean lightsOn; 6 | 7 | private boolean mainStateOpen; 8 | 9 | private IElvis elvis; 10 | 11 | public Concert(IElvis elvis) { 12 | this.elvis = elvis; 13 | } 14 | 15 | public void perform() { 16 | mainStateOpen = true; 17 | lightsOn = true; 18 | elvis.sing(); 19 | } 20 | 21 | public boolean isLightsOn() { 22 | return lightsOn; 23 | } 24 | 25 | public boolean isMainStateOpen() { 26 | return mainStateOpen; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/field/Elvis.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.field; 2 | 3 | import java.io.Serializable; 4 | 5 | // 코드 3-1 public static final 필드 방식의 싱글턴 (23쪽) 6 | public class Elvis implements IElvis, Serializable { 7 | 8 | /** 9 | * 싱글톤 오브젝트 10 | */ 11 | public static final Elvis INSTANCE = new Elvis(); 12 | private static boolean created; 13 | 14 | private Elvis() { 15 | if (created) { 16 | throw new UnsupportedOperationException("can't be created by constructor."); 17 | } 18 | 19 | created = true; 20 | } 21 | 22 | public void leaveTheBuilding() { 23 | System.out.println("Whoa baby, I'm outta here!"); 24 | } 25 | 26 | public void sing() { 27 | System.out.println("I'll have a blue~ Christmas without you~"); 28 | } 29 | 30 | // 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다! 31 | public static void main(String[] args) { 32 | Elvis elvis = Elvis.INSTANCE; 33 | elvis.leaveTheBuilding(); 34 | } 35 | 36 | private Object readResolve() { 37 | return INSTANCE; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/field/ElvisReflection.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.field; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.InvocationTargetException; 5 | 6 | // 생성자로 여러 인스턴스 만들기 7 | public class ElvisReflection { 8 | 9 | public static void main(String[] args) { 10 | try { 11 | Constructor defaultConstructor = Elvis.class.getDeclaredConstructor(); 12 | defaultConstructor.setAccessible(true); 13 | Elvis elvis1 = defaultConstructor.newInstance(); 14 | Elvis elvis2 = defaultConstructor.newInstance(); 15 | Elvis.INSTANCE.sing(); 16 | } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) { 17 | e.printStackTrace(); 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/field/ElvisSerialization.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.field; 2 | 3 | import java.io.*; 4 | 5 | // 역직렬화로 여러 인스턴스 만들기 6 | public class ElvisSerialization { 7 | 8 | public static void main(String[] args) { 9 | try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("elvis.obj"))) { 10 | out.writeObject(Elvis.INSTANCE); 11 | } catch (IOException e) { 12 | e.printStackTrace(); 13 | } 14 | 15 | try (ObjectInput in = new ObjectInputStream(new FileInputStream("elvis.obj"))) { 16 | Elvis elvis3 = (Elvis) in.readObject(); 17 | System.out.println(elvis3 == Elvis.INSTANCE); 18 | } catch (IOException | ClassNotFoundException e) { 19 | e.printStackTrace(); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/field/IElvis.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.field; 2 | 3 | public interface IElvis { 4 | 5 | void leaveTheBuilding(); 6 | 7 | void sing(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/functionalinterface/DefaultFunctions.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.functionalinterface; 2 | 3 | 4 | import me.whiteship.chapter01.item03.methodreference.Person; 5 | 6 | import java.time.LocalDate; 7 | import java.util.function.Consumer; 8 | import java.util.function.Function; 9 | import java.util.function.Predicate; 10 | import java.util.function.Supplier; 11 | 12 | public class DefaultFunctions { 13 | 14 | public static void main(String[] args) { 15 | Function intToString = Object::toString; 16 | 17 | Supplier personSupplier = Person::new; 18 | Function personFunction = Person::new; 19 | 20 | Consumer integerConsumer = System.out::println; 21 | 22 | Predicate predicate; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/functionalinterface/MyFunction.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.functionalinterface; 2 | 3 | @FunctionalInterface 4 | public interface MyFunction { 5 | 6 | String valueOf(Integer integer); 7 | 8 | static String hello() { 9 | return "hello"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/functionalinterface/UsageOfFunctions.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.functionalinterface; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | 8 | public class UsageOfFunctions { 9 | 10 | public static void main(String[] args) { 11 | List dates = new ArrayList<>(); 12 | dates.add(LocalDate.of(1982, 7, 15)); 13 | dates.add(LocalDate.of(2011, 3, 2)); 14 | dates.add(LocalDate.of(2013, 1, 28)); 15 | 16 | List before2000 = dates.stream() 17 | .filter(d -> d.isBefore(LocalDate.of(2000, 1, 1))) 18 | .map(LocalDate::getYear) 19 | .collect(Collectors.toList()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/methodreference/Person.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.methodreference; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | 8 | public class Person { 9 | 10 | LocalDate birthday; 11 | 12 | public Person() { 13 | 14 | } 15 | 16 | public Person(LocalDate birthday) { 17 | this.birthday = birthday; 18 | } 19 | 20 | public static int compareByAge(Person a, Person b) { 21 | return a.birthday.compareTo(b.birthday); 22 | } 23 | 24 | public static void main(String[] args) { 25 | List people = new ArrayList<>(); 26 | people.add(new Person(LocalDate.of(1982, 7, 15))); 27 | people.add(new Person(LocalDate.of(2011, 3, 2))); 28 | people.add(new Person(LocalDate.of(2013, 1, 28))); 29 | 30 | people.sort(new Comparator() { 31 | @Override 32 | public int compare(Person a, Person b) { 33 | return a.birthday.compareTo(b.birthday); 34 | } 35 | }); 36 | } 37 | 38 | public int getAge() { 39 | return LocalDate.now().getYear() - birthday.getYear(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/serialization/Book.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.serialization; 2 | 3 | import java.io.Serializable; 4 | import java.time.LocalDate; 5 | 6 | public class Book implements Serializable { 7 | 8 | private static final long serialVersionUID = 1L; 9 | 10 | private String isbn; 11 | 12 | private String title; 13 | 14 | private LocalDate published; 15 | 16 | private String name; 17 | 18 | private transient int numberOfSold; 19 | 20 | public Book(String isbn, String title, String author, LocalDate published) { 21 | this.isbn = isbn; 22 | this.title = title; 23 | this.published = published; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "Book{" + 29 | "isbn='" + isbn + '\'' + 30 | ", title='" + title + '\'' + 31 | ", published=" + published + 32 | ", numberOfSold=" + numberOfSold + 33 | '}'; 34 | } 35 | 36 | public String getIsbn() { 37 | return isbn; 38 | } 39 | 40 | public void setIsbn(String isbn) { 41 | this.isbn = isbn; 42 | } 43 | 44 | public String getTitle() { 45 | return title; 46 | } 47 | 48 | public void setTitle(String title) { 49 | this.title = title; 50 | } 51 | 52 | public LocalDate getPublished() { 53 | return published; 54 | } 55 | 56 | public void setPublished(LocalDate published) { 57 | this.published = published; 58 | } 59 | 60 | public int getNumberOfSold() { 61 | return numberOfSold; 62 | } 63 | 64 | public void setNumberOfSold(int numberOfSold) { 65 | this.numberOfSold = numberOfSold; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/serialization/SerializationExample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.serialization; 2 | 3 | import java.io.*; 4 | import java.time.LocalDate; 5 | 6 | public class SerializationExample { 7 | 8 | private void serialize(Book book) { 9 | try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("book.obj"))) { 10 | out.writeObject(book); 11 | } catch (IOException e) { 12 | throw new RuntimeException(e); 13 | } 14 | } 15 | 16 | private Book deserialize() { 17 | try (ObjectInput in = new ObjectInputStream(new FileInputStream("book.obj"))) { 18 | return (Book) in.readObject(); 19 | } catch (IOException | ClassNotFoundException e) { 20 | throw new RuntimeException(e); 21 | } 22 | } 23 | 24 | public static void main(String[] args) { 25 | // Book book = new Book("12345", "이팩티브 자바 완벽 공략", "백기선", 26 | // LocalDate.of(2022, 3, 21)); 27 | // book.setNumberOfSold(200); 28 | 29 | SerializationExample example = new SerializationExample(); 30 | // example.serialize(book); 31 | Book deserializedBook = example.deserialize(); 32 | 33 | // System.out.println(book); 34 | System.out.println(deserializedBook); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/staticfactory/Concert.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.staticfactory; 2 | 3 | import java.util.function.Supplier; 4 | 5 | public class Concert { 6 | 7 | public void start(Supplier singerSupplier) { 8 | Singer singer = singerSupplier.get(); 9 | singer.sing(); 10 | } 11 | 12 | public static void main(String[] args) { 13 | Concert concert = new Concert(); 14 | concert.start(Elvis::getInstance); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/staticfactory/Elvis.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.staticfactory; 2 | 3 | // 코드 3-2 정적 팩터리 방식의 싱글턴 (24쪽) 4 | public class Elvis implements Singer { 5 | private static final Elvis INSTANCE = new Elvis(); 6 | private Elvis() { } 7 | public static Elvis getInstance() { return INSTANCE; } 8 | 9 | public void leaveTheBuilding() { 10 | System.out.println("Whoa baby, I'm outta here!"); 11 | } 12 | 13 | // 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다! 14 | public static void main(String[] args) { 15 | Elvis elvis = Elvis.getInstance(); 16 | elvis.leaveTheBuilding(); 17 | 18 | System.out.println(Elvis.getInstance()); 19 | System.out.println(Elvis.getInstance()); 20 | } 21 | 22 | @Override 23 | public void sing() { 24 | System.out.println("my way~~~"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/staticfactory/MetaElvis.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.staticfactory; 2 | 3 | import java.util.Objects; 4 | 5 | // 코드 3-2 제네릭 싱글톤 팩토리 (24쪽) 6 | public class MetaElvis { 7 | 8 | private static final MetaElvis INSTANCE = new MetaElvis<>(); 9 | 10 | private MetaElvis() { } 11 | 12 | @SuppressWarnings("unchecked") 13 | public static MetaElvis getInstance() { return (MetaElvis) INSTANCE; } 14 | 15 | public void say(T t) { 16 | System.out.println(t); 17 | } 18 | 19 | public void leaveTheBuilding() { 20 | System.out.println("Whoa baby, I'm outta here!"); 21 | } 22 | 23 | public static void main(String[] args) { 24 | MetaElvis elvis1 = MetaElvis.getInstance(); 25 | MetaElvis elvis2 = MetaElvis.getInstance(); 26 | System.out.println(elvis1); 27 | System.out.println(elvis2); 28 | elvis1.say("hello"); 29 | elvis2.say(100); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item03/staticfactory/Singer.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.staticfactory; 2 | 3 | public interface Singer { 4 | 5 | void sing(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item04/DefaultUtilityClass.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item04; 2 | 3 | public class DefaultUtilityClass extends UtilityClass { 4 | 5 | public static void main(String[] args) { 6 | DefaultUtilityClass utilityClass = new DefaultUtilityClass(); 7 | utilityClass.hello(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item04/UtilityClass.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item04; 2 | 3 | import org.springframework.context.annotation.AnnotationConfigUtils; 4 | 5 | public class UtilityClass { 6 | 7 | /** 8 | * 이 클래스는 인스턴스를 만들 수 없습니다. 9 | */ 10 | // private UtilityClass() { 11 | // throw new AssertionError(); 12 | // } 13 | 14 | public static String hello() { 15 | return "hello"; 16 | } 17 | 18 | public static void main(String[] args) { 19 | String hello = UtilityClass.hello(); 20 | 21 | UtilityClass utilityClass = new UtilityClass(); 22 | utilityClass.hello(); 23 | 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/DefaultDictionary.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.List; 6 | 7 | public class DefaultDictionary implements Dictionary{ 8 | 9 | @Override 10 | public boolean contains(String word) { 11 | return false; 12 | } 13 | 14 | @Override 15 | public List closeWordsTo(String typo) { 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/Dictionary.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05; 2 | 3 | import java.util.List; 4 | 5 | public interface Dictionary { 6 | 7 | boolean contains(String word); 8 | 9 | List closeWordsTo(String typo); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/MockDictionary.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05; 2 | 3 | import java.util.List; 4 | 5 | public class MockDictionary implements Dictionary{ 6 | @Override 7 | public boolean contains(String word) { 8 | return false; 9 | } 10 | 11 | @Override 12 | public List closeWordsTo(String typo) { 13 | return null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/dependencyinjection/DictionaryFactory.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.dependencyinjection; 2 | 3 | import me.whiteship.chapter01.item05.DefaultDictionary; 4 | 5 | public class DictionaryFactory { 6 | public static DefaultDictionary get() { 7 | return new DefaultDictionary(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/dependencyinjection/SpellChecker.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.dependencyinjection; 2 | 3 | import me.whiteship.chapter01.item05.Dictionary; 4 | 5 | import java.util.List; 6 | import java.util.function.Supplier; 7 | 8 | public class SpellChecker { 9 | 10 | private final Dictionary dictionary; 11 | 12 | public SpellChecker(Dictionary dictionary) { 13 | this.dictionary = dictionary; 14 | } 15 | 16 | public SpellChecker(Supplier dictionarySupplier) { 17 | this.dictionary = dictionarySupplier.get(); 18 | } 19 | 20 | public boolean isValid(String word) { 21 | // TODO 여기 SpellChecker 코드 22 | return dictionary.contains(word); 23 | } 24 | 25 | public List suggestions(String typo) { 26 | // TODO 여기 SpellChecker 코드 27 | return dictionary.closeWordsTo(typo); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/factorymethod/DefaultDictionaryFactory.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.factorymethod; 2 | 3 | import me.whiteship.chapter01.item05.DefaultDictionary; 4 | import me.whiteship.chapter01.item05.Dictionary; 5 | 6 | public class DefaultDictionaryFactory implements DictionaryFactory { 7 | @Override 8 | public Dictionary getDictionary() { 9 | return new DefaultDictionary(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/factorymethod/DictionaryFactory.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.factorymethod; 2 | 3 | import me.whiteship.chapter01.item05.Dictionary; 4 | 5 | public interface DictionaryFactory { 6 | 7 | Dictionary getDictionary(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/factorymethod/MockDictionaryFactory.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.factorymethod; 2 | 3 | import me.whiteship.chapter01.item05.DefaultDictionary; 4 | import me.whiteship.chapter01.item05.Dictionary; 5 | import me.whiteship.chapter01.item05.MockDictionary; 6 | 7 | public class MockDictionaryFactory implements DictionaryFactory { 8 | @Override 9 | public Dictionary getDictionary() { 10 | return new MockDictionary(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/factorymethod/SpellChecker.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.factorymethod; 2 | 3 | import me.whiteship.chapter01.item05.Dictionary; 4 | 5 | import java.util.List; 6 | 7 | public class SpellChecker { 8 | 9 | private Dictionary dictionary; 10 | 11 | public SpellChecker(DictionaryFactory dictionaryFactory) { 12 | this.dictionary = dictionaryFactory.getDictionary(); 13 | } 14 | 15 | public boolean isValid(String word) { 16 | // TODO 여기 SpellChecker 코드 17 | return dictionary.contains(word); 18 | } 19 | 20 | public List suggestions(String typo) { 21 | // TODO 여기 SpellChecker 코드 22 | return dictionary.closeWordsTo(typo); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/package-info.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05; -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/singleton/SpellChecker.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.singleton; 2 | 3 | import me.whiteship.chapter01.item05.DefaultDictionary; 4 | import me.whiteship.chapter01.item05.Dictionary; 5 | 6 | import java.util.List; 7 | 8 | public class SpellChecker { 9 | 10 | private final Dictionary dictionary = new DefaultDictionary(); 11 | 12 | private SpellChecker() {} 13 | 14 | public static final SpellChecker INSTANCE = new SpellChecker(); 15 | 16 | public boolean isValid(String word) { 17 | // TODO 여기 SpellChecker 코드 18 | return dictionary.contains(word); 19 | } 20 | 21 | public List suggestions(String typo) { 22 | // TODO 여기 SpellChecker 코드 23 | return dictionary.closeWordsTo(typo); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/springioc/App.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.springioc; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 5 | 6 | public class App { 7 | 8 | public static void main(String[] args) { 9 | ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); 10 | SpellChecker spellChecker = applicationContext.getBean(SpellChecker.class); 11 | spellChecker.isValid("test"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/springioc/AppConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.springioc; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan(basePackageClasses = AppConfig.class) 8 | public class AppConfig { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/springioc/SpellChecker.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.springioc; 2 | 3 | import me.whiteship.chapter01.item05.Dictionary; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.List; 7 | 8 | @Component 9 | public class SpellChecker { 10 | 11 | private Dictionary dictionary; 12 | 13 | public SpellChecker(Dictionary dictionary) { 14 | this.dictionary = dictionary; 15 | } 16 | 17 | public boolean isValid(String word) { 18 | // TODO 여기 SpellChecker 코드 19 | return dictionary.contains(word); 20 | } 21 | 22 | public List suggestions(String typo) { 23 | // TODO 여기 SpellChecker 코드 24 | return dictionary.closeWordsTo(typo); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/springioc/SpringDictionary.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.springioc; 2 | 3 | import me.whiteship.chapter01.item05.Dictionary; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.List; 7 | 8 | @Component 9 | public class SpringDictionary implements Dictionary { 10 | 11 | @Override 12 | public boolean contains(String word) { 13 | System.out.println("contains " + word); 14 | return false; 15 | } 16 | 17 | @Override 18 | public List closeWordsTo(String typo) { 19 | return null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item05/staticutils/SpellChecker.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.staticutils; 2 | 3 | import me.whiteship.chapter01.item05.DefaultDictionary; 4 | import me.whiteship.chapter01.item05.Dictionary; 5 | 6 | import java.util.List; 7 | 8 | public class SpellChecker { 9 | 10 | private static final Dictionary dictionary = new DefaultDictionary(); 11 | 12 | private SpellChecker() {} 13 | 14 | public static boolean isValid(String word) { 15 | // TODO 여기 SpellChecker 코드 16 | return dictionary.contains(word); 17 | } 18 | 19 | public static List suggestions(String typo) { 20 | // TODO 여기 SpellChecker 코드 21 | return dictionary.closeWordsTo(typo); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item06/Client.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item06; 2 | 3 | public class Client { 4 | 5 | public static void main(String[] args) { 6 | Deprecation deprecation = new Deprecation("string"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item06/Deprecation.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item06; 2 | 3 | public class Deprecation { 4 | 5 | /** 6 | * @deprecated in favor of 7 | * {@link #Deprecation(String)} 8 | */ 9 | @Deprecated(forRemoval = true, since = "1.2") 10 | public Deprecation() { 11 | } 12 | 13 | private String name; 14 | 15 | public Deprecation(String name) { 16 | this.name = name; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item06/RegularExpression.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item06; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class RegularExpression { 6 | 7 | private static final Pattern SPLIT_PATTERN = Pattern.compile(","); 8 | 9 | public static void main(String[] args) { 10 | long start = System.nanoTime(); 11 | for (int j = 0; j < 10000; j++) { 12 | String name = "keesun,whiteship"; 13 | name.split(","); 14 | // SPLIT_PATTERN.split(name); 15 | } 16 | System.out.println(System.nanoTime() - start); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item06/RomanNumerals.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item06; 2 | import java.util.regex.Pattern; 3 | 4 | // 값비싼 객체를 재사용해 성능을 개선한다. (32쪽) 5 | public class RomanNumerals { 6 | // 코드 6-1 성능을 훨씬 더 끌어올릴 수 있다! 7 | static boolean isRomanNumeralSlow(String s) { 8 | return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 9 | } 10 | 11 | // 코드 6-2 값비싼 객체를 재사용해 성능을 개선한다. 12 | private static final Pattern ROMAN = Pattern.compile( 13 | "^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 14 | 15 | static boolean isRomanNumeralFast(String s) { 16 | return ROMAN.matcher(s).matches(); 17 | } 18 | 19 | public static void main(String[] args) { 20 | boolean result = false; 21 | long start = System.nanoTime(); 22 | for (int j = 0; j < 100; j++) { 23 | //TODO 성능 차이를 확인하려면 xxxSlow 메서드를 xxxFast 메서드로 바꿔 실행해보자. 24 | result = isRomanNumeralSlow("MCMLXXVI"); 25 | } 26 | long end = System.nanoTime(); 27 | System.out.println(end - start); 28 | System.out.println(result); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item06/Strings.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item06; 2 | 3 | public class Strings { 4 | 5 | public static void main(String[] args) { 6 | String hello = "hello"; 7 | 8 | //TODO 이 방법은 권장하지 않습니다. 9 | String hello2 = new String("hello"); 10 | 11 | String hello3 = "hello"; 12 | 13 | System.out.println(hello == hello2); 14 | System.out.println(hello.equals(hello2)); 15 | System.out.println(hello == hello3); 16 | System.out.println(hello.equals(hello3)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item06/Sum.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item06; 2 | 3 | public class Sum { 4 | private static long sum() { 5 | // TODO Long을 long으로 변경하여 실행해 보세요. 6 | Long sum = 0L; 7 | for (long i = 0; i <= Integer.MAX_VALUE; i++) 8 | sum += i; 9 | return sum; 10 | } 11 | 12 | public static void main(String[] args) { 13 | long start = System.nanoTime(); 14 | long x = sum(); 15 | long end = System.nanoTime(); 16 | System.out.println((end - start) / 1_000_000. + " ms."); 17 | System.out.println(x); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/cache/CacheKey.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.cache; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public class CacheKey { 6 | 7 | private Integer value; 8 | 9 | private LocalDateTime created; 10 | 11 | public CacheKey(Integer value) { 12 | this.value = value; 13 | this.created = LocalDateTime.now(); 14 | } 15 | 16 | @Override 17 | public boolean equals(Object o) { 18 | return this.value.equals(o); 19 | } 20 | 21 | @Override 22 | public int hashCode() { 23 | return this.value.hashCode(); 24 | } 25 | 26 | public LocalDateTime getCreated() { 27 | return created; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "CacheKey{" + 33 | "value=" + value + 34 | ", created=" + created + 35 | '}'; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/cache/Post.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.cache; 2 | 3 | public class Post { 4 | 5 | private Integer id; 6 | 7 | private String title; 8 | 9 | private String content; 10 | 11 | public Integer getId() { 12 | return id; 13 | } 14 | 15 | public void setId(Integer id) { 16 | this.id = id; 17 | } 18 | 19 | public String getTitle() { 20 | return title; 21 | } 22 | 23 | public void setTitle(String title) { 24 | this.title = title; 25 | } 26 | 27 | public String getContent() { 28 | return content; 29 | } 30 | 31 | public void setContent(String content) { 32 | this.content = content; 33 | } 34 | 35 | @Override 36 | public void finalize() { 37 | System.out.println("gc called"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/cache/PostRepository.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.cache; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.WeakHashMap; 6 | 7 | public class PostRepository { 8 | 9 | private Map cache; 10 | 11 | public PostRepository() { 12 | this.cache = new WeakHashMap<>(); 13 | } 14 | 15 | public Post getPostById(CacheKey key) { 16 | if (cache.containsKey(key)) { 17 | return cache.get(key); 18 | } else { 19 | // TODO DB에서 읽어오거나 REST API를 통해 읽어올 수 있습니다. 20 | Post post = new Post(); 21 | cache.put(key, post); 22 | return post; 23 | } 24 | } 25 | 26 | public Map getCache() { 27 | return cache; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/executor/ExecutorsExample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.executor; 2 | 3 | import me.whiteship.chapter01.item01.Product; 4 | 5 | import java.util.concurrent.*; 6 | 7 | public class ExecutorsExample { 8 | 9 | public static void main(String[] args) throws ExecutionException, InterruptedException { 10 | ExecutorService service = Executors.newFixedThreadPool(10); 11 | 12 | Future submit = service.submit(new Task()); 13 | 14 | System.out.println(Thread.currentThread() + " hello"); 15 | 16 | System.out.println(submit.get()); 17 | 18 | service.shutdown(); 19 | } 20 | 21 | static class Task implements Callable { 22 | 23 | @Override 24 | public String call() throws Exception { 25 | Thread.sleep(2000L); 26 | return Thread.currentThread() + " world"; 27 | } 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/listener/ChatRoom.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.listener; 2 | 3 | import java.lang.ref.WeakReference; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Objects; 7 | 8 | public class ChatRoom { 9 | 10 | private List> users; 11 | 12 | public ChatRoom() { 13 | this.users = new ArrayList<>(); 14 | } 15 | 16 | public void addUser(User user) { 17 | this.users.add(new WeakReference<>(user)); 18 | } 19 | 20 | public void sendMessage(String message) { 21 | users.forEach(wr -> Objects.requireNonNull(wr.get()).receive(message)); 22 | } 23 | 24 | public List> getUsers() { 25 | return users; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/listener/User.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.listener; 2 | 3 | public class User { 4 | 5 | public void receive(String message) { 6 | System.out.println(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/optional/Channel.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.optional; 2 | 3 | import java.util.Optional; 4 | import java.util.OptionalLong; 5 | 6 | public class Channel { 7 | 8 | private int numOfSubscribers; 9 | 10 | public Optional defaultMemberShip() { 11 | if (this.numOfSubscribers < 2000) { 12 | return Optional.empty(); 13 | } else { 14 | return Optional.of(new MemberShip()); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/optional/MemberShip.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.optional; 2 | 3 | public class MemberShip { 4 | 5 | public String hello() { 6 | return "hello"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/reference/BigObject.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.reference; 2 | 3 | public class BigObject { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/reference/BigObjectReference.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.reference; 2 | 3 | import java.lang.ref.PhantomReference; 4 | import java.lang.ref.ReferenceQueue; 5 | 6 | public class BigObjectReference extends PhantomReference { 7 | 8 | public BigObjectReference(BigObject referent, ReferenceQueue q) { 9 | super(referent, q); 10 | } 11 | 12 | public void cleanUp() { 13 | System.out.println("clean up"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/reference/PhantomReferenceExample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.reference; 2 | 3 | import java.lang.ref.PhantomReference; 4 | import java.lang.ref.Reference; 5 | import java.lang.ref.ReferenceQueue; 6 | 7 | public class PhantomReferenceExample { 8 | 9 | public static void main(String[] args) throws InterruptedException { 10 | BigObject strong = new BigObject(); 11 | ReferenceQueue rq = new ReferenceQueue<>(); 12 | 13 | BigObjectReference phantom = new BigObjectReference<>(strong, rq); 14 | strong = null; 15 | 16 | System.gc(); 17 | Thread.sleep(3000L); 18 | 19 | // TODO 팬텀은 유령이니까.. 20 | // 죽었지만.. 사라지진 않고 큐에 들어갑니다. 21 | System.out.println(phantom.isEnqueued()); 22 | 23 | Reference reference = rq.poll(); 24 | BigObjectReference bigObjectCleaner = (BigObjectReference) reference; 25 | bigObjectCleaner.cleanUp(); 26 | reference.clear(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/reference/SoftReferenceExample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.reference; 2 | 3 | import java.lang.ref.SoftReference; 4 | 5 | public class SoftReferenceExample { 6 | 7 | public static void main(String[] args) throws InterruptedException { 8 | Object strong = new Object(); 9 | SoftReference soft = new SoftReference<>(strong); 10 | strong = null; 11 | 12 | System.gc(); 13 | Thread.sleep(3000L); 14 | 15 | // TODO 거의 안 없어집니다. 16 | // 왜냐면 메모리가 충분해서.. 굳이 제거할 필요가 없으니까요. 17 | System.out.println(soft.get()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/reference/WeakReferenceExample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.reference; 2 | 3 | import java.lang.ref.WeakReference; 4 | 5 | public class WeakReferenceExample { 6 | 7 | public static void main(String[] args) throws InterruptedException { 8 | Object strong = new Object(); 9 | WeakReference weak = new WeakReference<>(strong); 10 | strong = null; 11 | 12 | System.gc(); 13 | Thread.sleep(3000L); 14 | 15 | // TODO 거의 없어집니다. 16 | // 왜냐면 약하니까(?)... 17 | System.out.println(weak.get()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/stack/EmptyStackException.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.stack; 2 | 3 | // (36쪽의 Stack 코드에서 던지는 예외) 4 | public class EmptyStackException extends IllegalStateException { 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item07/stack/Stack.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.stack; 2 | 3 | import java.util.Arrays; 4 | 5 | // 코드 7-1 메모리 누수가 일어나는 위치는 어디인가? (36쪽) 6 | public class Stack { 7 | private Object[] elements; 8 | private int size = 0; 9 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 10 | 11 | public Stack() { 12 | elements = new Object[DEFAULT_INITIAL_CAPACITY]; 13 | } 14 | 15 | public void push(Object e) { 16 | ensureCapacity(); 17 | elements[size++] = e; 18 | } 19 | 20 | // public Object pop() { 21 | // if (size == 0) 22 | // throw new EmptyStackException(); 23 | // return elements[--size]; 24 | // } 25 | 26 | /** 27 | * 원소를 위한 공간을 적어도 하나 이상 확보한다. 28 | * 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다. 29 | */ 30 | private void ensureCapacity() { 31 | if (elements.length == size) 32 | elements = Arrays.copyOf(elements, 2 * size + 1); 33 | } 34 | 35 | // 코드 7-2 제대로 구현한 pop 메서드 (37쪽) 36 | public Object pop() { 37 | if (size == 0) 38 | throw new EmptyStackException(); 39 | Object result = elements[--size]; 40 | elements[size] = null; // 다 쓴 참조 해제 41 | return result; 42 | } 43 | 44 | public static void main(String[] args) { 45 | Stack stack = new Stack(); 46 | for (String arg : args) 47 | stack.push(arg); 48 | 49 | while (true) 50 | System.err.println(stack.pop()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/autoclosable/App.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.autoclosable; 2 | 3 | public class App { 4 | 5 | public static void main(String[] args) { 6 | try(AutoClosableIsGood good = new AutoClosableIsGood("")) { 7 | // TODO 자원 반납 처리가 됨. 8 | 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/autoclosable/AutoClosableIsGood.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.autoclosable; 2 | 3 | import java.io.*; 4 | 5 | public class AutoClosableIsGood implements Closeable { 6 | 7 | private BufferedReader reader; 8 | 9 | public AutoClosableIsGood(String path) { 10 | try { 11 | this.reader = new BufferedReader(new FileReader(path)); 12 | } catch (FileNotFoundException e) { 13 | throw new IllegalArgumentException(path); 14 | } 15 | } 16 | 17 | @Override 18 | public void close() { 19 | try { 20 | reader.close(); 21 | } catch (IOException e) { 22 | throw new RuntimeException(e); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/cleaner/BigObject.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.cleaner; 2 | 3 | import java.util.List; 4 | 5 | public class BigObject { 6 | 7 | private List resource; 8 | 9 | public BigObject(List resource) { 10 | this.resource = resource; 11 | } 12 | 13 | public static class ResourceCleaner implements Runnable { 14 | 15 | private List resourceToClean; 16 | 17 | public ResourceCleaner(List resourceToClean) { 18 | this.resourceToClean = resourceToClean; 19 | } 20 | 21 | @Override 22 | public void run() { 23 | resourceToClean = null; 24 | System.out.println("cleaned up."); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/cleaner/CleanerIsNotGood.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.cleaner; 2 | 3 | import java.lang.ref.Cleaner; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class CleanerIsNotGood { 8 | 9 | public static void main(String[] args) throws InterruptedException { 10 | Cleaner cleaner = Cleaner.create(); 11 | 12 | List resourceToCleanUp = new ArrayList<>(); 13 | BigObject bigObject = new BigObject(resourceToCleanUp); 14 | cleaner.register(bigObject, new BigObject.ResourceCleaner(resourceToCleanUp)); 15 | 16 | bigObject = null; 17 | System.gc(); 18 | Thread.sleep(3000L); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Adult.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.cleaner_as_a_safetynet; 2 | 3 | // cleaner 안전망을 갖춘 자원을 제대로 활용하는 클라이언트 (45쪽) 4 | public class Adult { 5 | public static void main(String[] args) { 6 | try (Room myRoom = new Room(7)) { 7 | System.out.println("안녕~"); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Room.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.cleaner_as_a_safetynet; 2 | 3 | import java.lang.ref.Cleaner; 4 | 5 | // 코드 8-1 cleaner를 안전망으로 활용하는 AutoCloseable 클래스 (44쪽) 6 | public class Room implements AutoCloseable { 7 | private static final Cleaner cleaner = Cleaner.create(); 8 | 9 | // 청소가 필요한 자원. 절대 Room을 참조해서는 안 된다! 10 | private static class State implements Runnable { 11 | int numJunkPiles; // Number of junk piles in this room 12 | 13 | State(int numJunkPiles) { 14 | this.numJunkPiles = numJunkPiles; 15 | } 16 | 17 | // close 메서드나 cleaner가 호출한다. 18 | @Override public void run() { 19 | System.out.println("Cleaning room"); 20 | numJunkPiles = 0; 21 | } 22 | } 23 | 24 | // 방의 상태. cleanable과 공유한다. 25 | private final State state; 26 | 27 | // cleanable 객체. 수거 대상이 되면 방을 청소한다. 28 | private final Cleaner.Cleanable cleanable; 29 | 30 | public Room(int numJunkPiles) { 31 | state = new State(numJunkPiles); 32 | cleanable = cleaner.register(this, state); 33 | } 34 | 35 | @Override public void close() { 36 | cleanable.clean(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Teenager.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.cleaner_as_a_safetynet; 2 | 3 | // cleaner 안전망을 갖춘 자원을 제대로 활용하지 못하는 클라이언트 (45쪽) 4 | public class Teenager { 5 | 6 | public static void main(String[] args) { 7 | new Room(99); 8 | System.out.println("Peace out"); 9 | 10 | // 다음 줄의 주석을 해제한 후 동작을 다시 확인해보자. 11 | // 단, 가비지 컬렉러를 강제로 호출하는 이런 방식에 의존해서는 절대 안 된다! 12 | System.gc(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/finalizer/App.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.finalizer; 2 | 3 | import com.sun.management.UnixOperatingSystemMXBean; 4 | import org.springframework.jmx.support.MBeanServerFactoryBean; 5 | 6 | import javax.management.MBeanServer; 7 | import javax.management.MBeanServerFactory; 8 | import java.lang.management.OperatingSystemMXBean; 9 | import java.lang.ref.ReferenceQueue; 10 | import java.lang.reflect.Field; 11 | 12 | public class App { 13 | 14 | /** 15 | * 코드 참고 https://www.baeldung.com/java-finalize 16 | */ 17 | public static void main(String[] args) throws InterruptedException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { 18 | int i = 0; 19 | while(true) { 20 | i++; 21 | new FinalizerIsBad(); 22 | 23 | if ((i % 1_000_000) == 0) { 24 | Class finalizerClass = Class.forName("java.lang.ref.Finalizer"); 25 | Field queueStaticField = finalizerClass.getDeclaredField("queue"); 26 | queueStaticField.setAccessible(true); 27 | ReferenceQueue referenceQueue = (ReferenceQueue) queueStaticField.get(null); 28 | 29 | Field queueLengthField = ReferenceQueue.class.getDeclaredField("queueLength"); 30 | queueLengthField.setAccessible(true); 31 | long queueLength = (long) queueLengthField.get(referenceQueue); 32 | System.out.format("There are %d references in the queue%n", queueLength); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/finalizer/FinalizerIsBad.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.finalizer; 2 | 3 | public class FinalizerIsBad { 4 | 5 | @Override 6 | protected void finalize() throws Throwable { 7 | System.out.print(""); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/finalizer_attack/Account.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.finalizer_attack; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class Account { 6 | 7 | private String accountId; 8 | 9 | public Account(String accountId) { 10 | this.accountId = accountId; 11 | 12 | if (accountId.equals("푸틴")) { 13 | throw new IllegalArgumentException("푸틴은 계정을 막습니다."); 14 | } 15 | } 16 | 17 | public void transfer(BigDecimal amount, String to) { 18 | System.out.printf("transfer %f from %s to %s\n", amount, accountId, to); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/finalizer_attack/BrokenAccount.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.finalizer_attack; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class BrokenAccount extends Account { 6 | 7 | public BrokenAccount(String accountId) { 8 | super(accountId); 9 | } 10 | 11 | @Override 12 | protected void finalize() throws Throwable { 13 | this.transfer(BigDecimal.valueOf(100), "keesun"); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/outerclass/LambdaExample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.outerclass; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | public class LambdaExample { 6 | 7 | private int value = 10; 8 | 9 | private Runnable instanceLambda = () -> { 10 | System.out.println(value); 11 | }; 12 | 13 | public static void main(String[] args) { 14 | LambdaExample example = new LambdaExample(); 15 | Field[] declaredFields = example.instanceLambda.getClass().getDeclaredFields(); 16 | for (Field field : declaredFields) { 17 | System.out.println("field type: " + field.getType()); 18 | System.out.println("field name: " + field.getName()); 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item08/outerclass/OuterClass.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.outerclass; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | public class OuterClass { 6 | 7 | private void hi() { 8 | 9 | } 10 | 11 | class InnerClass { 12 | 13 | public void hello() { 14 | OuterClass.this.hi(); 15 | } 16 | 17 | } 18 | 19 | public static void main(String[] args) { 20 | OuterClass outerClass = new OuterClass(); 21 | InnerClass innerClass = outerClass.new InnerClass(); 22 | 23 | System.out.println(innerClass); 24 | 25 | outerClass.printFiled(); 26 | } 27 | 28 | private void printFiled() { 29 | Field[] declaredFields = InnerClass.class.getDeclaredFields(); 30 | for(Field field : declaredFields) { 31 | System.out.println("field type:" + field.getType()); 32 | System.out.println("field name:" + field.getName()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item09/puzzler/Copy.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item09.puzzler; 2 | 3 | import java.io.*; 4 | 5 | public class Copy { 6 | private static final int BUFFER_SIZE = 8 * 1024; 7 | 8 | // 코드 9-2 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다! (47쪽) 9 | static void copy(String src, String dst) throws IOException { 10 | InputStream in = new FileInputStream(src); 11 | OutputStream out = new FileOutputStream(dst); 12 | try { 13 | byte[] buf = new byte[BUFFER_SIZE]; 14 | int n; 15 | while ((n = in.read(buf)) >= 0) 16 | out.write(buf, 0, n); 17 | } finally { 18 | try { 19 | out.close(); 20 | } catch (IOException e) { 21 | // TODO 이렇게 하면 되는거 아닌가? 22 | } 23 | 24 | try { 25 | in.close(); 26 | } catch (IOException e) { 27 | // TODO 안전한가? 28 | } 29 | } 30 | } 31 | 32 | public static void main(String[] args) throws IOException { 33 | String src = args[0]; 34 | String dst = args[1]; 35 | copy(src, dst); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item09/suppress/BadBufferedReader.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item09.suppress; 2 | 3 | import java.io.*; 4 | 5 | public class BadBufferedReader extends BufferedReader { 6 | public BadBufferedReader(Reader in, int sz) { 7 | super(in, sz); 8 | } 9 | 10 | public BadBufferedReader(Reader in) { 11 | super(in); 12 | } 13 | 14 | @Override 15 | public String readLine() throws IOException { 16 | throw new CharConversionException(); 17 | } 18 | 19 | @Override 20 | public void close() throws IOException { 21 | throw new StreamCorruptedException(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item09/suppress/TopLine.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item09.suppress; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileReader; 5 | import java.io.IOException; 6 | 7 | public class TopLine { 8 | // 코드 9-1 try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다! (47쪽) 9 | static String firstLineOfFile(String path) throws IOException { 10 | try(BufferedReader br = new BadBufferedReader(new FileReader(path))) { 11 | return br.readLine(); 12 | } 13 | } 14 | 15 | public static void main(String[] args) throws IOException { 16 | System.out.println(firstLineOfFile("pom.xml")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item09/tryfinally/Copy.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item09.tryfinally; 2 | 3 | import java.io.*; 4 | 5 | public class Copy { 6 | private static final int BUFFER_SIZE = 8 * 1024; 7 | 8 | // 코드 9-2 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다! (47쪽) 9 | static void copy(String src, String dst) throws IOException { 10 | InputStream in = new FileInputStream(src); 11 | try { 12 | OutputStream out = new FileOutputStream(dst); 13 | try { 14 | byte[] buf = new byte[BUFFER_SIZE]; 15 | int n; 16 | while ((n = in.read(buf)) >= 0) 17 | out.write(buf, 0, n); 18 | } finally { 19 | out.close(); 20 | } 21 | } finally { 22 | in.close(); 23 | } 24 | } 25 | 26 | public static void main(String[] args) throws IOException { 27 | String src = args[0]; 28 | String dst = args[1]; 29 | copy(src, dst); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item09/tryfinally/TopLine.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item09.tryfinally; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileReader; 5 | import java.io.IOException; 6 | 7 | public class TopLine { 8 | // 코드 9-1 try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다! (47쪽) 9 | static String firstLineOfFile(String path) throws IOException { 10 | BufferedReader br = new BufferedReader(new FileReader(path)); 11 | try { 12 | return br.readLine(); 13 | } finally { 14 | br.close(); 15 | } 16 | } 17 | 18 | public static void main(String[] args) throws IOException { 19 | String path = args[0]; 20 | System.out.println(firstLineOfFile(path)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item09/trywithresources/Copy.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item09.trywithresources; 2 | 3 | import java.io.*; 4 | 5 | public class Copy { 6 | private static final int BUFFER_SIZE = 8 * 1024; 7 | 8 | // 코드 9-4 복수의 자원을 처리하는 try-with-resources - 짧고 매혹적이다! (49쪽) 9 | static void copy(String src, String dst) throws IOException { 10 | try (InputStream in = new FileInputStream(src); 11 | OutputStream out = new FileOutputStream(dst)) { 12 | byte[] buf = new byte[BUFFER_SIZE]; 13 | int n; 14 | while ((n = in.read(buf)) >= 0) 15 | out.write(buf, 0, n); 16 | } 17 | } 18 | 19 | public static void main(String[] args) throws IOException { 20 | String src = args[0]; 21 | String dst = args[1]; 22 | copy(src, dst); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item09/trywithresources/TopLine.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item09.trywithresources; 2 | 3 | 4 | import java.io.BufferedReader; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | 8 | public class TopLine { 9 | // 코드 9-3 try-with-resources - 자원을 회수하는 최선책! (48쪽) 10 | static String firstLineOfFile(String path) throws IOException { 11 | try (BufferedReader br = new BufferedReader( 12 | new FileReader(path))) { 13 | return br.readLine(); 14 | } 15 | } 16 | 17 | public static void main(String[] args) throws IOException { 18 | String path = args[0]; 19 | System.out.println(firstLineOfFile(path)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter01/item09/trywithresources/TopLineWithDefault.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item09.trywithresources; 2 | 3 | 4 | import java.io.BufferedReader; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | 8 | public class TopLineWithDefault { 9 | // 코드 9-5 try-with-resources를 catch 절과 함께 쓰는 모습 (49쪽) 10 | static String firstLineOfFile(String path, String defaultVal) { 11 | try (BufferedReader br = new BufferedReader( 12 | new FileReader(path))) { 13 | return br.readLine(); 14 | } catch (IOException e) { 15 | return defaultVal; 16 | } 17 | } 18 | 19 | public static void main(String[] args) throws IOException { 20 | String path = args[0]; 21 | System.out.println(firstLineOfFile(path, "Toppy McTopFace")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/CaseInsensitiveString.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Objects; 6 | 7 | // 코드 10-1 잘못된 코드 - 대칭성 위배! (54-55쪽) 8 | public final class CaseInsensitiveString { 9 | private final String s; 10 | 11 | public CaseInsensitiveString(String s) { 12 | this.s = Objects.requireNonNull(s); 13 | } 14 | 15 | // 대칭성 위배! 16 | @Override public boolean equals(Object o) { 17 | if (o instanceof CaseInsensitiveString) 18 | return s.equalsIgnoreCase( 19 | ((CaseInsensitiveString) o).s); 20 | if (o instanceof String) // 한 방향으로만 작동한다! 21 | return s.equalsIgnoreCase((String) o); 22 | return false; 23 | } 24 | 25 | // 문제 시연 (55쪽) 26 | public static void main(String[] args) { 27 | CaseInsensitiveString cis = new CaseInsensitiveString("Polish"); 28 | // CaseInsensitiveString cis2 = new CaseInsensitiveString("polish"); 29 | String polish = "polish"; 30 | System.out.println(cis.equals(polish)); 31 | // System.out.println(cis2.equals(cis)); 32 | 33 | List list = new ArrayList<>(); 34 | list.add(cis); 35 | 36 | System.out.println(list.contains(polish)); 37 | } 38 | 39 | // 수정한 equals 메서드 (56쪽) 40 | // @Override public boolean equals(Object o) { 41 | // return o instanceof CaseInsensitiveString && 42 | // ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); 43 | // } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/Color.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10; 2 | 3 | public enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET } 4 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/EqualsInJava.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10; 2 | 3 | import java.net.MalformedURLException; 4 | import java.net.URL; 5 | import java.sql.Timestamp; 6 | import java.util.Date; 7 | 8 | public class EqualsInJava extends Object { 9 | 10 | public static void main(String[] args) throws MalformedURLException { 11 | long time = System.currentTimeMillis(); 12 | Timestamp timestamp = new Timestamp(time); 13 | Date date = new Date(time); 14 | 15 | // 대칭성 위배! P60 16 | System.out.println(date.equals(timestamp)); 17 | System.out.println(timestamp.equals(date)); 18 | 19 | // 일관성 위배 가능성 있음. P61 20 | URL google1 = new URL("https", "about.google", "/products/"); 21 | URL google2 = new URL("https", "about.google", "/products/"); 22 | System.out.println(google1.equals(google2)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/PhoneNumber.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10; 2 | 3 | // 코드 10-6 전형적인 equals 메서드의 예 (64쪽) 4 | public final class PhoneNumber { 5 | private final short areaCode, prefix, lineNum; 6 | 7 | public PhoneNumber(int areaCode, int prefix, int lineNum) { 8 | this.areaCode = rangeCheck(areaCode, 999, "지역코드"); 9 | this.prefix = rangeCheck(prefix, 999, "프리픽스"); 10 | this.lineNum = rangeCheck(lineNum, 9999, "가입자 번호"); 11 | } 12 | 13 | private static short rangeCheck(int val, int max, String arg) { 14 | if (val < 0 || val > max) 15 | throw new IllegalArgumentException(arg + ": " + val); 16 | return (short) val; 17 | } 18 | 19 | @Override public boolean equals(Object o) { 20 | if (o == this) 21 | return true; 22 | if (!(o instanceof PhoneNumber)) 23 | return false; 24 | PhoneNumber pn = (PhoneNumber)o; 25 | return pn.lineNum == lineNum && pn.prefix == prefix 26 | && pn.areaCode == areaCode; 27 | } 28 | 29 | // 나머지 코드는 생략 - hashCode 메서드는 꼭 필요하다(아이템 11)! 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/Point.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | // 단순한 불변 2차원 정수 점(point) 클래스 (56쪽) 7 | public class Point { 8 | 9 | private final int x; 10 | private final int y; 11 | 12 | public Point(int x, int y) { 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | @Override public boolean equals(Object o) { 18 | if (this == o) { 19 | return true; 20 | } 21 | 22 | if (!(o instanceof Point)) { 23 | return false; 24 | } 25 | 26 | Point p = (Point) o; 27 | return p.x == x && p.y == y; 28 | } 29 | 30 | public static void main(String[] args) { 31 | Point point = new Point(1, 2); 32 | List points = new ArrayList<>(); 33 | points.add(point); 34 | System.out.println(points.contains(new Point(1, 2))); 35 | } 36 | 37 | // 잘못된 코드 - 리스코프 치환 원칙 위배! (59쪽) 38 | // @Override public boolean equals(Object o) { 39 | // if (o == null || o.getClass() != getClass()) 40 | // return false; 41 | // Point p = (Point) o; 42 | // return p.x == x && p.y == y; 43 | // } 44 | 45 | // 아이템 11 참조 46 | @Override public int hashCode() { 47 | return 31 * x + y; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/autovalue/AutoValueTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10.autovalue; 2 | 3 | public class AutoValueTest { 4 | 5 | public static void main(String[] args) { 6 | Point point = Point.create(1, 2); 7 | System.out.println(point.equals(Point.create(1, 2))); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/autovalue/Point.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10.autovalue; 2 | 3 | import com.google.auto.value.AutoValue; 4 | 5 | /** 6 | * AutoValue 참고 7 | * 8 | * https://github.com/google/auto/blob/master/value/userguide/index.md 9 | */ 10 | @AutoValue 11 | abstract class Point { 12 | static Point create(int x, int y) { 13 | return new AutoValue_Point(x, y); 14 | } 15 | 16 | abstract int x(); 17 | abstract int y(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/composition/ColorPoint.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10.composition; 2 | 3 | 4 | import me.whiteship.chapter02.item10.Color; 5 | import me.whiteship.chapter02.item10.Point; 6 | 7 | import java.util.Objects; 8 | 9 | // 코드 10-5 equals 규약을 지키면서 값 추가하기 (60쪽) 10 | public class ColorPoint { 11 | private final Point point; 12 | private final Color color; 13 | 14 | public ColorPoint(int x, int y, Color color) { 15 | point = new Point(x, y); 16 | this.color = Objects.requireNonNull(color); 17 | } 18 | 19 | /** 20 | * 이 ColorPoint의 Point 뷰를 반환한다. 21 | */ 22 | public Point asPoint() { 23 | return point; 24 | } 25 | 26 | @Override public boolean equals(Object o) { 27 | if (!(o instanceof ColorPoint)) 28 | return false; 29 | ColorPoint cp = (ColorPoint) o; 30 | return cp.point.equals(point) && cp.color.equals(color); 31 | } 32 | 33 | @Override public int hashCode() { 34 | return 31 * point.hashCode() + color.hashCode(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/inheritance/ColorPoint.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10.inheritance; 2 | 3 | import me.whiteship.chapter02.item10.Color; 4 | import me.whiteship.chapter02.item10.Point; 5 | 6 | // Point에 값 컴포넌트(color)를 추가 (56쪽) 7 | public class ColorPoint extends Point { 8 | private final Color color; 9 | 10 | public ColorPoint(int x, int y, Color color) { 11 | super(x, y); 12 | this.color = color; 13 | } 14 | 15 | // 코드 10-2 잘못된 코드 - 대칭성 위배! (57쪽) 16 | // @Override public boolean equals(Object o) { 17 | // if (!(o instanceof ColorPoint)) 18 | // return false; 19 | // return super.equals(o) && ((ColorPoint) o).color == color; 20 | // } 21 | 22 | // // 코드 10-3 잘못된 코드 - 추이성 위배! (57쪽) 23 | @Override public boolean equals(Object o) { 24 | if (!(o instanceof Point)) 25 | return false; 26 | 27 | // o가 일반 Point면 색상을 무시하고 비교한다. 28 | if (!(o instanceof ColorPoint)) 29 | return o.equals(this); 30 | 31 | // o가 ColorPoint면 색상까지 비교한다. 32 | return super.equals(o) && ((ColorPoint) o).color == color; 33 | } 34 | 35 | public static void main(String[] args) { 36 | // 첫 번째 equals 메서드(코드 10-2)는 대칭성을 위배한다. (57쪽) 37 | // Point p = new Point(1, 2); 38 | // ColorPoint cp = new ColorPoint(1, 2, Color.RED); 39 | // System.out.println(p.equals(cp) + " " + cp.equals(p)); 40 | 41 | // 두 번째 equals 메서드(코드 10-3)는 추이성을 위배한다. (57쪽) 42 | ColorPoint p1 = new ColorPoint(1, 2, Color.RED); 43 | Point p2 = new Point(1, 2); 44 | ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE); 45 | System.out.printf("%s %s %s%n", 46 | p1.equals(p2), p2.equals(p3), p1.equals(p3)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/inheritance/CounterPoint.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10.inheritance; 2 | 3 | 4 | import me.whiteship.chapter02.item10.Point; 5 | 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | // Point의 평범한 하위 클래스 - 값 컴포넌트를 추가하지 않았다. (59쪽) 9 | public class CounterPoint extends Point { 10 | private static final AtomicInteger counter = 11 | new AtomicInteger(); 12 | 13 | public CounterPoint(int x, int y) { 14 | super(x, y); 15 | counter.incrementAndGet(); 16 | } 17 | public static int numberCreated() { return counter.get(); } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/inheritance/CounterPointTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10.inheritance; 2 | 3 | 4 | import me.whiteship.chapter02.item10.Color; 5 | import me.whiteship.chapter02.item10.Point; 6 | 7 | import java.util.Set; 8 | 9 | // CounterPoint를 Point로 사용하는 테스트 프로그램 10 | public class CounterPointTest { 11 | // 단위 원 안의 모든 점을 포함하도록 unitCircle을 초기화한다. (58쪽) 12 | private static final Set unitCircle = Set.of( 13 | new Point( 1, 0), new Point( 0, 1), 14 | new Point(-1, 0), new Point( 0, -1)); 15 | 16 | public static boolean onUnitCircle(Point p) { 17 | return unitCircle.contains(p); 18 | } 19 | 20 | public static void main(String[] args) { 21 | Point p1 = new Point(1, 0); 22 | Point p2 = new CounterPoint(1, 0); 23 | 24 | // true를 출력한다. 25 | System.out.println(onUnitCircle(p1)); 26 | 27 | // true를 출력해야 하지만, Point의 equals가 getClass를 사용해 작성되었다면 그렇지 않다. 28 | System.out.println(onUnitCircle(p2)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/inheritance/SmellPoint.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10.inheritance; 2 | 3 | import me.whiteship.chapter02.item10.Point; 4 | 5 | public class SmellPoint extends Point { 6 | 7 | private String smell; 8 | 9 | public SmellPoint(int x, int y, String smell) { 10 | super(x, y); 11 | this.smell = smell; 12 | } 13 | 14 | @Override public boolean equals(Object o) { 15 | if (!(o instanceof Point)) 16 | return false; 17 | 18 | // o가 일반 Point면 색상을 무시하고 비교한다. 19 | if (!(o instanceof SmellPoint)) 20 | return o.equals(this); 21 | 22 | // o가 ColorPoint면 색상까지 비교한다. 23 | return super.equals(o) && ((SmellPoint) o).smell.equals(smell); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/inheritance/SmellPointTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10.inheritance; 2 | 3 | import me.whiteship.chapter02.item10.Color; 4 | 5 | public class SmellPointTest { 6 | 7 | /** 8 | * TODO -Xss10M 9 | * @param args 10 | */ 11 | public static void main(String[] args) { 12 | SmellPoint p1 = new SmellPoint(1, 0, "sweat"); 13 | ColorPoint p2 = new ColorPoint(1, 0, Color.RED); 14 | p1.equals(p2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/lombok/LombokTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10.lombok; 2 | 3 | public class LombokTest { 4 | 5 | public static void main(String[] args) { 6 | Point point = new Point(1, 2); 7 | System.out.println(point.equals(new Point(1, 2))); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/lombok/Point.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item10.lombok; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.ToString; 5 | 6 | /** 7 | * https://projectlombok.org/features/EqualsAndHashCode 8 | * https://projectlombok.org/features/ToString 9 | */ 10 | @EqualsAndHashCode 11 | @ToString 12 | public class Point { 13 | private final int x; 14 | private final int y; 15 | 16 | public Point(int x, int y) { 17 | this.x = x; 18 | this.y = y; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/record/Point.java: -------------------------------------------------------------------------------- 1 | //package me.whiteship.chapter02.item10.record; 2 | // 3 | //public record Point(int x, int y) { 4 | // 5 | //} 6 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item10/record/PointTest.java: -------------------------------------------------------------------------------- 1 | //package me.whiteship.chapter02.item10.record; 2 | // 3 | //public class PointTest { 4 | // 5 | // public static void main(String[] args) { 6 | // Point p1 = new Point(1, 0); 7 | // Point p2 = new Point(1, 0); 8 | // System.out.println(p1.equals(p2)); 9 | // System.out.println(p1); 10 | // 11 | // System.out.println(p1.x()); 12 | // System.out.println(p1.y()); 13 | // } 14 | //} 15 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item11/guava/PhoneNumber.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item11.guava; 2 | 3 | import com.google.common.hash.Funnel; 4 | import com.google.common.hash.Hashing; 5 | import com.google.common.hash.PrimitiveSink; 6 | 7 | import java.nio.charset.Charset; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | // equals를 재정의하면 hashCode로 재정의해야 함을 보여준다. (70-71쪽) 13 | public final class PhoneNumber { 14 | private final short areaCode, prefix, lineNum; 15 | 16 | public PhoneNumber(int areaCode, int prefix, int lineNum) { 17 | this.areaCode = rangeCheck(areaCode, 999, "area code"); 18 | this.prefix = rangeCheck(prefix, 999, "prefix"); 19 | this.lineNum = rangeCheck(lineNum, 9999, "line num"); 20 | } 21 | 22 | private static short rangeCheck(int val, int max, String arg) { 23 | if (val < 0 || val > max) 24 | throw new IllegalArgumentException(arg + ": " + val); 25 | return (short) val; 26 | } 27 | 28 | @Override public boolean equals(Object o) { 29 | if (o == this) 30 | return true; 31 | if (!(o instanceof PhoneNumber)) 32 | return false; 33 | PhoneNumber pn = (PhoneNumber)o; 34 | return pn.lineNum == lineNum && pn.prefix == prefix 35 | && pn.areaCode == areaCode; 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Hashing.goodFastHash(32) 41 | .hashObject(this, PhoneNumberFunnel.INSTANCE) 42 | .hashCode(); 43 | } 44 | 45 | private static class PhoneNumberFunnel implements Funnel { 46 | 47 | private static final PhoneNumberFunnel INSTANCE = new PhoneNumberFunnel(); 48 | 49 | @Override 50 | public void funnel(PhoneNumber from, PrimitiveSink into) { 51 | into.putShort(from.areaCode).putShort(from.prefix).putShort(from.lineNum); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item11/hashcode/PhoneNumber.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item11.hashcode; 2 | 3 | import me.whiteship.chapter02.item10.Point; 4 | 5 | import java.util.*; 6 | 7 | // equals를 재정의하면 hashCode로 재정의해야 함을 보여준다. (70-71쪽) 8 | public final class PhoneNumber { 9 | private final short areaCode, prefix, lineNum; 10 | 11 | public PhoneNumber(int areaCode, int prefix, int lineNum) { 12 | this.areaCode = rangeCheck(areaCode, 999, "area code"); 13 | this.prefix = rangeCheck(prefix, 999, "prefix"); 14 | this.lineNum = rangeCheck(lineNum, 9999, "line num"); 15 | } 16 | 17 | private static short rangeCheck(int val, int max, String arg) { 18 | if (val < 0 || val > max) 19 | throw new IllegalArgumentException(arg + ": " + val); 20 | return (short) val; 21 | } 22 | 23 | @Override public boolean equals(Object o) { 24 | if (o == this) 25 | return true; 26 | if (!(o instanceof PhoneNumber)) 27 | return false; 28 | PhoneNumber pn = (PhoneNumber)o; 29 | return pn.lineNum == lineNum && pn.prefix == prefix 30 | && pn.areaCode == areaCode; 31 | } 32 | 33 | // @Override 34 | // public int hashCode() { 35 | // return 42; 36 | // } 37 | 38 | // hashCode 없이는 제대로 동작하지 않는다. 다음 셋 중 하나를 활성화하자. 39 | 40 | // 코드 11-2 전형적인 hashCode 메서드 (70쪽) 41 | // @Override public int hashCode() { 42 | // int result = Short.hashCode(areaCode); // 1 43 | // result = 800000 * result + Short.hashCode(prefix); // 2 44 | // result = 800000 * result + Short.hashCode(lineNum); // 3 45 | // return result; 46 | // } 47 | 48 | // 코드 11-3 한 줄짜리 hashCode 메서드 - 성능이 살짝 아쉽다. (71쪽) 49 | // @Override public int hashCode() { 50 | // return Objects.hash(lineNum, prefix, areaCode); 51 | // } 52 | 53 | // 해시코드를 지연 초기화하는 hashCode 메서드 - 스레드 안정성까지 고려해야 한다. (71쪽) 54 | private volatile int hashCode; // 자동으로 0으로 초기화된다. 55 | 56 | @Override public int hashCode() { 57 | if (this.hashCode != 0) { 58 | return hashCode; 59 | } 60 | 61 | synchronized (this) { 62 | int result = hashCode; 63 | if (result == 0) { 64 | result = Short.hashCode(areaCode); 65 | result = 31 * result + Short.hashCode(prefix); 66 | result = 31 * result + Short.hashCode(lineNum); 67 | this.hashCode = result; 68 | } 69 | return result; 70 | } 71 | } 72 | 73 | public static void main(String[] args) { 74 | Map m = new HashMap<>(); 75 | m.put(new PhoneNumber(707, 867, 5309), "제니"); 76 | System.out.println(m.get(new PhoneNumber(707, 867, 5309))); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item11/hashtable/HashMapTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item11.hashtable; 2 | 3 | import me.whiteship.chapter02.item11.guava.PhoneNumber; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class HashMapTest { 9 | 10 | public static void main(String[] args) { 11 | Map map = new HashMap<>(); 12 | 13 | PhoneNumber number1 = new PhoneNumber(123, 456, 7890); 14 | PhoneNumber number2 = new PhoneNumber(456, 789, 1111); 15 | 16 | // TODO 같은 인스턴스인데 다른 hashCode 17 | // 다른 인스턴스인데 같은 hashCode를 쓴다면? 18 | System.out.println(number1.equals(number2)); 19 | System.out.println(number1.hashCode()); 20 | System.out.println(number2.hashCode()); 21 | 22 | map.put(number1, "keesun"); 23 | map.put(number2, "whiteship"); 24 | 25 | String s = map.get(number2); 26 | System.out.println(s); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item11/package-info.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item11; -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item12/PhoneNumber.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item12; 2 | 3 | import lombok.ToString; 4 | 5 | // PhoneNumber에 toString 메서드 추가 (75쪽) 6 | public final class PhoneNumber { 7 | private final short areaCode, prefix, lineNum; 8 | 9 | public PhoneNumber(int areaCode, int prefix, int lineNum) { 10 | this.areaCode = rangeCheck(areaCode, 999, "지역코드"); 11 | this.prefix = rangeCheck(prefix, 999, "프리픽스"); 12 | this.lineNum = rangeCheck(lineNum, 9999, "가입자 번호"); 13 | } 14 | 15 | private static short rangeCheck(int val, int max, String arg) { 16 | if (val < 0 || val > max) 17 | throw new IllegalArgumentException(arg + ": " + val); 18 | return (short) val; 19 | } 20 | 21 | /** 22 | * 이 전화번호의 문자열 표현을 반환한다. 23 | * 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다. 24 | * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다. 25 | * 각각의 대문자는 10진수 숫자 하나를 나타낸다. 26 | * 27 | * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면, 28 | * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면 29 | * 전화번호의 마지막 네 문자는 "0123"이 된다. 30 | */ 31 | @Override public String toString() { 32 | return String.format("%03d-%03d-%04d", 33 | areaCode, prefix, lineNum); 34 | } 35 | 36 | public static PhoneNumber of(String phoneNumberString) { 37 | String[] split = phoneNumberString.split("-"); 38 | PhoneNumber phoneNumber = new PhoneNumber( 39 | Short.parseShort(split[0]), 40 | Short.parseShort(split[1]), 41 | Short.parseShort(split[2])); 42 | return phoneNumber; 43 | } 44 | 45 | @Override public boolean equals(Object o) { 46 | if (o == this) 47 | return true; 48 | if (!(o instanceof PhoneNumber)) 49 | return false; 50 | PhoneNumber pn = (PhoneNumber)o; 51 | return pn.lineNum == lineNum && pn.prefix == prefix 52 | && pn.areaCode == areaCode; 53 | } 54 | 55 | @Override public int hashCode() { 56 | int result = Short.hashCode(areaCode); 57 | result = 31 * result + Short.hashCode(prefix); 58 | result = 31 * result + Short.hashCode(lineNum); 59 | return result; 60 | } 61 | 62 | public short getAreaCode() { 63 | return areaCode; 64 | } 65 | 66 | public short getPrefix() { 67 | return prefix; 68 | } 69 | 70 | public short getLineNum() { 71 | return lineNum; 72 | } 73 | 74 | public static void main(String[] args) { 75 | PhoneNumber jenny = new PhoneNumber(707, 867, 5309); 76 | System.out.println("제니의 번호: " + jenny); 77 | 78 | PhoneNumber phoneNumber = PhoneNumber.of("707-867-5309"); 79 | System.out.println(phoneNumber); 80 | System.out.println(jenny.equals(phoneNumber)); 81 | System.out.println(jenny.hashCode() == phoneNumber.hashCode()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/EmptyStackException.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13; 2 | 3 | public class EmptyStackException extends IllegalStateException { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/HashTable.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13; 2 | 3 | public class HashTable implements Cloneable { 4 | 5 | private Entry[] buckets = new Entry[10]; 6 | 7 | private static class Entry { 8 | final Object key; 9 | Object value; 10 | Entry next; 11 | 12 | Entry(Object key, Object value, Entry next) { 13 | this.key = key; 14 | this.value = value; 15 | this.next = next; 16 | } 17 | 18 | public void add(Object key, Object value) { 19 | this.next = new Entry(key, value, null); 20 | } 21 | 22 | // public Entry deepCopy() { 23 | // return new Entry(key, value, next == null ? null : next.deepCopy()); 24 | // } 25 | 26 | public Entry deepCopy() { 27 | Entry result = new Entry(key, value, next); 28 | for (Entry p = result ; p.next != null ; p = p.next) { 29 | p.next = new Entry(p.next.key, p.next.value, p.next.next); 30 | } 31 | return result; 32 | } 33 | } 34 | 35 | /** 36 | * TODO hasTable -> entryH[], 37 | * TODO copy -> entryC[] 38 | * TODO entryH[0] == entryC[0] 39 | * 40 | * @return 41 | */ 42 | // @Override 43 | // public HashTable clone() { 44 | // HashTable result = null; 45 | // try { 46 | // result = (HashTable)super.clone(); 47 | // result.buckets = this.buckets.clone(); // p82, shallow copy 라서 위험하다. 48 | // return result; 49 | // } catch (CloneNotSupportedException e) { 50 | // throw new AssertionError(); 51 | // } 52 | // } 53 | 54 | /** 55 | * TODO hasTable -> entryH[], 56 | * TODO copy -> entryC[] 57 | * TODO entryH[0] != entryC[0] 58 | * 59 | * @return 60 | */ 61 | @Override 62 | public HashTable clone() { 63 | HashTable result = null; 64 | try { 65 | result = (HashTable)super.clone(); 66 | result.buckets = new Entry[this.buckets.length]; 67 | 68 | for (int i = 0 ; i < this.buckets.length; i++) { 69 | if (buckets[i] != null) { 70 | result.buckets[i] = this.buckets[i].deepCopy(); // p83, deep copy 71 | } 72 | } 73 | return result; 74 | } catch (CloneNotSupportedException e) { 75 | throw new AssertionError(); 76 | } 77 | } 78 | 79 | public static void main(String[] args) { 80 | HashTable hashTable = new HashTable(); 81 | Entry entry = new Entry(new Object(), new Object(), null); 82 | hashTable.buckets[0] = entry; 83 | HashTable clone = hashTable.clone(); 84 | System.out.println(hashTable.buckets[0] == entry); 85 | System.out.println(hashTable.buckets[0] == clone.buckets[0]); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/PhoneNumber.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | // PhoneNumber에 clone 메서드 추가 (79쪽) 7 | public final class PhoneNumber implements Cloneable { 8 | private final short areaCode, prefix, lineNum; 9 | 10 | public PhoneNumber(int areaCode, int prefix, int lineNum) { 11 | this.areaCode = rangeCheck(areaCode, 999, "지역코드"); 12 | this.prefix = rangeCheck(prefix, 999, "프리픽스"); 13 | this.lineNum = rangeCheck(lineNum, 9999, "가입자 번호"); 14 | System.out.println("constructor is called"); 15 | } 16 | 17 | public PhoneNumber(PhoneNumber phoneNumber) { 18 | this(phoneNumber.areaCode, phoneNumber.prefix, phoneNumber.lineNum); 19 | } 20 | 21 | private static short rangeCheck(int val, int max, String arg) { 22 | if (val < 0 || val > max) 23 | throw new IllegalArgumentException(arg + ": " + val); 24 | return (short) val; 25 | } 26 | 27 | // 코드 13-1 가변 상태를 참조하지 않는 클래스용 clone 메서드 (79쪽) 28 | @Override 29 | public PhoneNumber clone() { 30 | try { 31 | return (PhoneNumber) super.clone(); 32 | } catch (CloneNotSupportedException e) { 33 | throw new AssertionError(); // 일어날 수 없는 일이다. 34 | } 35 | } 36 | 37 | public static void main(String[] args) { 38 | PhoneNumber pn = new PhoneNumber(707, 867, 5309); 39 | Map m = new HashMap<>(); 40 | m.put(pn, "제니"); 41 | PhoneNumber clone = pn.clone(); 42 | System.out.println(m.get(clone)); 43 | 44 | System.out.println(clone != pn); // 반드시 true 45 | System.out.println(clone.getClass() == pn.getClass()); // 반드시 true 46 | System.out.println(clone.equals(pn)); // true가 아닐 수도 있다. 47 | } 48 | 49 | @Override public boolean equals(Object o) { 50 | if (o == this) 51 | return true; 52 | if (!(o instanceof PhoneNumber)) 53 | return false; 54 | PhoneNumber pn = (PhoneNumber)o; 55 | return pn.lineNum == lineNum && pn.prefix == prefix 56 | && pn.areaCode == areaCode; 57 | } 58 | 59 | @Override public int hashCode() { 60 | int result = Short.hashCode(areaCode); 61 | result = 31 * result + Short.hashCode(prefix); 62 | result = 31 * result + Short.hashCode(lineNum); 63 | return result; 64 | } 65 | 66 | /** 67 | * 이 전화번호의 문자열 표현을 반환한다. 68 | * 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다. 69 | * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다. 70 | * 각각의 대문자는 10진수 숫자 하나를 나타낸다. 71 | * 72 | * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면, 73 | * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면 74 | * 전화번호의 마지막 네 문자는 "0123"이 된다. 75 | */ 76 | @Override public String toString() { 77 | return String.format("%03d-%03d-%04d", 78 | areaCode, prefix, lineNum); 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/Stack.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13; 2 | import java.util.Arrays; 3 | 4 | // Stack의 복제 가능 버전 (80-81쪽) 5 | public class Stack implements Cloneable { 6 | private Object[] elements; 7 | private int size = 0; 8 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 9 | 10 | public Stack() { 11 | this.elements = new Object[DEFAULT_INITIAL_CAPACITY]; 12 | } 13 | 14 | public void push(Object e) { 15 | ensureCapacity(); 16 | elements[size++] = e; 17 | } 18 | 19 | public Object pop() { 20 | if (size == 0) 21 | throw new EmptyStackException(); 22 | Object result = elements[--size]; 23 | elements[size] = null; // 다 쓴 참조 해제 24 | return result; 25 | } 26 | 27 | public boolean isEmpty() { 28 | return size ==0; 29 | } 30 | 31 | // 코드 13-2 가변 상태를 참조하는 클래스용 clone 메서드 32 | // TODO stack -> elementsS[0, 1] 33 | // TODO copy -> elementsC[0, 1] 34 | // TODO elementsS[0] == elementsC[0] 35 | 36 | @Override public Stack clone() { 37 | try { 38 | Stack result = (Stack) super.clone(); 39 | result.elements = elements.clone(); 40 | return result; 41 | } catch (CloneNotSupportedException e) { 42 | throw new AssertionError(); 43 | } 44 | } 45 | 46 | // 원소를 위한 공간을 적어도 하나 이상 확보한다. 47 | private void ensureCapacity() { 48 | if (elements.length == size) 49 | elements = Arrays.copyOf(elements, 2 * size + 1); 50 | } 51 | 52 | // clone이 동작하는 모습을 보려면 명령줄 인수를 몇 개 덧붙여서 호출해야 한다. 53 | public static void main(String[] args) { 54 | Object[] values = new Object[2]; 55 | values[0] = new PhoneNumber(123, 456, 7890); 56 | values[1] = new PhoneNumber(321, 764, 2341); 57 | 58 | Stack stack = new Stack(); 59 | for (Object arg : values) 60 | stack.push(arg); 61 | 62 | Stack copy = stack.clone(); 63 | 64 | System.out.println("pop from stack"); 65 | while (!stack.isEmpty()) 66 | System.out.println(stack.pop() + " "); 67 | 68 | System.out.println("pop from copy"); 69 | while (!copy.isEmpty()) 70 | System.out.println(copy.pop() + " "); 71 | 72 | System.out.println(stack.elements[0] == copy.elements[0]); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/clone_use_constructor/Item.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13.clone_use_constructor; 2 | 3 | public class Item implements Cloneable { 4 | 5 | private String name; 6 | 7 | /** 8 | * 이렇게 구현하면 하위 클래스의 clone()이 깨질 수 있다. p78 9 | * @return 10 | */ 11 | @Override 12 | public Item clone() { 13 | Item item = new Item(); 14 | item.name = this.name; 15 | return item; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/clone_use_constructor/SubItem.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13.clone_use_constructor; 2 | 3 | public class SubItem extends Item implements Cloneable { 4 | 5 | private String name; 6 | 7 | @Override 8 | public SubItem clone() { 9 | return (SubItem)super.clone(); 10 | } 11 | 12 | public static void main(String[] args) { 13 | SubItem item = new SubItem(); 14 | SubItem clone = item.clone(); 15 | 16 | System.out.println(clone != item); 17 | System.out.println(clone.getClass() == item.getClass()); 18 | System.out.println(clone.equals(item)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/copy_constructor/HashSetExample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13.copy_constructor; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.TreeSet; 6 | 7 | public class HashSetExample { 8 | 9 | public static void main(String[] args) { 10 | Set hashSet = new HashSet<>(); 11 | hashSet.add("keesun"); 12 | hashSet.add("whiteship"); 13 | System.out.println("HashSet: " + hashSet); 14 | 15 | Set treeSet = new TreeSet<>(hashSet); 16 | 17 | System.out.println("TreeSet: " + treeSet); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/exception/MyApp.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13.exception; 2 | 3 | public class MyApp { 4 | 5 | /** 6 | * 7 | * @param name 8 | * @throws MyException 9 | */ 10 | public void hello(String name) throws MyException { 11 | if (name.equals("푸틴")) { 12 | throw new MyException(); 13 | } 14 | 15 | System.out.println("hello"); 16 | } 17 | 18 | public static void main(String[] args) { 19 | MyApp myApp = new MyApp(); 20 | try { 21 | myApp.hello("푸틴"); 22 | } catch (MyException e) { 23 | e.printStackTrace(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/exception/MyException.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13.exception; 2 | 3 | public class MyException extends Exception { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/inheritance/Shape.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13.inheritance; 2 | 3 | /** 4 | * p84, p126 일반적으로 상속용 클래스에 Cloneable 인터페이스 사용을 권장하지 않는다. 5 | * 해당 클래스를 확장하려는 프로그래머에게 많은 부담을 주기 때문이다. 6 | */ 7 | public abstract class Shape implements Cloneable { 8 | 9 | private int area; 10 | 11 | public abstract int getArea(); 12 | 13 | 14 | /** 15 | * p84, 부담을 덜기 위해서는 기본 clone() 구현체를 제공하여, 16 | * Cloenable 구현 여부를 서브 클래스가 선택할 수 있다. 17 | * @return 18 | * @throws CloneNotSupportedException 19 | */ 20 | @Override 21 | public Object clone() throws CloneNotSupportedException { 22 | return super.clone(); 23 | } 24 | 25 | /** 26 | * p85, Cloneable 구현을 막을 수도 있다. 27 | */ 28 | // @Override 29 | // protected final Object clone() throws CloneNotSupportedException { 30 | // throw new CloneNotSupportedException(); 31 | // } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/inheritance/Square.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13.inheritance; 2 | 3 | public class Square extends Shape { 4 | 5 | private int length, height; 6 | 7 | public Square(int length, int height) { 8 | this.length = length; 9 | this.height = height; 10 | } 11 | 12 | public static void main(String[] args) throws CloneNotSupportedException { 13 | Square square = new Square(10, 2); 14 | Square copy = (Square) square.clone(); 15 | System.out.println(copy.getArea()); 16 | } 17 | 18 | @Override 19 | public int getArea() { 20 | return this.length * this.height; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item13/treeset/TreeSetExample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item13.treeset; 2 | 3 | import me.whiteship.chapter02.item13.PhoneNumber; 4 | 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.Set; 8 | import java.util.TreeSet; 9 | 10 | public class TreeSetExample { 11 | 12 | public static void main(String[] args) { 13 | // TreeSet numbers = new TreeSet<>(); 14 | // numbers.add(10); 15 | // numbers.add(4); 16 | // numbers.add(6); 17 | 18 | TreeSet numbers = new TreeSet<>(Comparator.comparingInt(PhoneNumber::hashCode)); 19 | Set phoneNumbers = Collections.synchronizedSet(numbers); 20 | phoneNumbers.add(new PhoneNumber(123, 456, 780)); 21 | phoneNumbers.add(new PhoneNumber(123, 456, 7890)); 22 | phoneNumbers.add(new PhoneNumber(123, 456, 789)); 23 | 24 | for (PhoneNumber number : numbers) { 25 | System.out.println(number); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item14/CaseInsensitiveString.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item14; 2 | 3 | import java.util.Objects; 4 | import java.util.Set; 5 | import java.util.TreeSet; 6 | 7 | // 코드 14-1 객체 참조 필드가 하나뿐인 비교자 (90쪽) 8 | public final class CaseInsensitiveString 9 | implements Comparable { 10 | private final String s; 11 | 12 | public CaseInsensitiveString(String s) { 13 | this.s = Objects.requireNonNull(s); 14 | } 15 | 16 | // 수정된 equals 메서드 (56쪽) 17 | @Override public boolean equals(Object o) { 18 | return o instanceof CaseInsensitiveString && 19 | ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); 20 | } 21 | 22 | @Override public int hashCode() { 23 | return s.hashCode(); 24 | } 25 | 26 | @Override public String toString() { 27 | return s; 28 | } 29 | 30 | // 자바가 제공하는 비교자를 사용해 클래스를 비교한다. 31 | public int compareTo(CaseInsensitiveString cis) { 32 | return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s); 33 | } 34 | 35 | public static void main(String[] args) { 36 | Set s = new TreeSet<>(); 37 | for (String arg : args) 38 | s.add(new CaseInsensitiveString(arg)); 39 | System.out.println(s); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item14/CompareToConvention.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item14; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class CompareToConvention { 6 | 7 | public static void main(String[] args) { 8 | BigDecimal n1 = BigDecimal.valueOf(23134134); 9 | BigDecimal n2 = BigDecimal.valueOf(11231230); 10 | BigDecimal n3 = BigDecimal.valueOf(53534552); 11 | BigDecimal n4 = BigDecimal.valueOf(11231230); 12 | 13 | // p88, 반사성 14 | System.out.println(n1.compareTo(n1)); 15 | 16 | // p88, 대칭성 17 | System.out.println(n1.compareTo(n2)); 18 | System.out.println(n2.compareTo(n1)); 19 | 20 | // p89, 추이성 21 | System.out.println(n3.compareTo(n1) > 0); 22 | System.out.println(n1.compareTo(n2) > 0); 23 | System.out.println(n3.compareTo(n2) > 0); 24 | 25 | // p89, 일관성 26 | System.out.println(n4.compareTo(n2)); 27 | System.out.println(n2.compareTo(n1)); 28 | System.out.println(n4.compareTo(n1)); 29 | 30 | // p89, compareTo가 0이라면 equals는 true여야 한다. (아닐 수도 있고..) 31 | BigDecimal oneZero = new BigDecimal("1.0"); 32 | BigDecimal oneZeroZero = new BigDecimal("1.00"); 33 | System.out.println(oneZero.compareTo(oneZeroZero)); // Tree, TreeMap 34 | System.out.println(oneZero.equals(oneZeroZero)); // 순서가 없는 콜렉션 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item14/PhoneNumber.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item14; 2 | 3 | import me.whiteship.chapter02.item10.Point; 4 | 5 | import java.util.*; 6 | import java.util.concurrent.ThreadLocalRandom; 7 | 8 | import static java.util.Comparator.comparingInt; 9 | 10 | // PhoneNumber를 비교할 수 있게 만든다. (91-92쪽) 11 | public final class PhoneNumber implements Cloneable, Comparable { 12 | private final short areaCode, prefix, lineNum; 13 | 14 | public short getAreaCode() { 15 | return areaCode; 16 | } 17 | 18 | public short getPrefix() { 19 | return prefix; 20 | } 21 | 22 | public short getLineNum() { 23 | return lineNum; 24 | } 25 | 26 | public PhoneNumber(int areaCode, int prefix, int lineNum) { 27 | this.areaCode = rangeCheck(areaCode, 999, "지역코드"); 28 | this.prefix = rangeCheck(prefix, 999, "프리픽스"); 29 | this.lineNum = rangeCheck(lineNum, 9999, "가입자 번호"); 30 | } 31 | 32 | private static short rangeCheck(int val, int max, String arg) { 33 | if (val < 0 || val > max) 34 | throw new IllegalArgumentException(arg + ": " + val); 35 | return (short) val; 36 | } 37 | 38 | @Override public boolean equals(Object o) { 39 | if (o == this) 40 | return true; 41 | if (!(o instanceof PhoneNumber)) 42 | return false; 43 | PhoneNumber pn = (PhoneNumber)o; 44 | return pn.lineNum == lineNum && pn.prefix == prefix 45 | && pn.areaCode == areaCode; 46 | } 47 | 48 | @Override public int hashCode() { 49 | int result = Short.hashCode(areaCode); 50 | result = 31 * result + Short.hashCode(prefix); 51 | result = 31 * result + Short.hashCode(lineNum); 52 | return result; 53 | } 54 | 55 | /** 56 | * 이 전화번호의 문자열 표현을 반환한다. 57 | * 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다. 58 | * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다. 59 | * 각각의 대문자는 10진수 숫자 하나를 나타낸다. 60 | * 61 | * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면, 62 | * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면 63 | * 전화번호의 마지막 네 문자는 "0123"이 된다. 64 | */ 65 | @Override public String toString() { 66 | return String.format("%03d-%03d-%04d", 67 | areaCode, prefix, lineNum); 68 | } 69 | 70 | // 코드 14-2 기본 타입 필드가 여럿일 때의 비교자 (91쪽) 71 | @Override 72 | public int compareTo(PhoneNumber pn) { 73 | int result = Short.compare(areaCode, pn.areaCode); 74 | if (result == 0) { 75 | result = Short.compare(prefix, pn.prefix); 76 | if (result == 0) 77 | result = Short.compare(lineNum, pn.lineNum); 78 | } 79 | return result; 80 | } 81 | 82 | // 코드 14-3 비교자 생성 메서드를 활용한 비교자 (92쪽) 83 | private static final Comparator COMPARATOR = 84 | comparingInt((PhoneNumber pn) -> pn.areaCode) 85 | .thenComparingInt(pn -> pn.getPrefix()) 86 | .thenComparingInt(pn -> pn.lineNum); 87 | // 88 | // @Override 89 | // public int compareTo(PhoneNumber pn) { 90 | // return COMPARATOR.compare(this, pn); 91 | // } 92 | 93 | private static PhoneNumber randomPhoneNumber() { 94 | Random rnd = ThreadLocalRandom.current(); 95 | return new PhoneNumber((short) rnd.nextInt(1000), 96 | (short) rnd.nextInt(1000), 97 | (short) rnd.nextInt(10000)); 98 | } 99 | 100 | public static void main(String[] args) { 101 | Set s = new TreeSet<>(); 102 | for (int i = 0; i < 10; i++) 103 | s.add(randomPhoneNumber()); 104 | System.out.println(s); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item14/WordList.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item14; 2 | 3 | import java.util.Collections; 4 | import java.util.Set; 5 | import java.util.TreeSet; 6 | 7 | // Comparable 구현 시의 이점 (87쪽) 8 | public class WordList { 9 | public static void main(String[] args) { 10 | String[] values = new String[]{"keesun", "whiteship", "java"}; 11 | 12 | Set s = new TreeSet<>(); 13 | Collections.addAll(s, values); 14 | System.out.println(s); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item14/composition/NamedPoint.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item14.composition; 2 | 3 | public class NamedPoint implements Comparable { 4 | 5 | private final Point point; 6 | private final String name; 7 | 8 | public NamedPoint(Point point, String name) { 9 | this.point = point; 10 | this.name = name; 11 | } 12 | 13 | public Point getPoint() { 14 | return this.point; 15 | } 16 | 17 | @Override 18 | public int compareTo(NamedPoint namedPoint) { 19 | int result = this.point.compareTo(namedPoint.point); 20 | if (result == 0) { 21 | result = this.name.compareTo(namedPoint.name); 22 | } 23 | return result; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item14/composition/Point.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item14.composition; 2 | 3 | public class Point implements Comparable{ 4 | 5 | final int x, y; 6 | 7 | public Point(int x, int y) { 8 | this.x = x; 9 | this.y = y; 10 | } 11 | 12 | @Override 13 | public int compareTo(Point point) { 14 | int result = Integer.compare(this.x, point.x); 15 | if (result == 0) { 16 | result = Integer.compare(this.y, point.y); 17 | } 18 | return result; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item14/decimal/DecimalIsNotCorrect.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item14.decimal; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class DecimalIsNotCorrect { 6 | 7 | public static void main(String[] args) { 8 | int i = 1; 9 | double d = 0.1; 10 | System.out.println(i - d * 9); 11 | 12 | BigDecimal bd = BigDecimal.valueOf(0.1); 13 | System.out.println(BigDecimal.valueOf(1).min(bd.multiply(BigDecimal.valueOf(9)))); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item14/decimal/IntOverflow.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item14.decimal; 2 | 3 | public class IntOverflow { 4 | 5 | public static void main(String[] args) { 6 | System.out.println(-2147483648 - 10); 7 | System.out.println(Integer.compare(-2147483648, 10)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item14/interitance/NamedPoint.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item14.interitance; 2 | 3 | import java.util.Comparator; 4 | import java.util.Set; 5 | import java.util.TreeSet; 6 | 7 | public class NamedPoint extends Point { 8 | 9 | final private String name; 10 | 11 | public NamedPoint(int x, int y, String name) { 12 | super(x, y); 13 | this.name = name; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return "NamedPoint{" + 19 | "name='" + name + '\'' + 20 | ", x=" + x + 21 | ", y=" + y + 22 | '}'; 23 | } 24 | 25 | public static void main(String[] args) { 26 | NamedPoint p1 = new NamedPoint(1, 0, "keesun"); 27 | NamedPoint p2 = new NamedPoint(1, 0, "whiteship"); 28 | 29 | Set points = new TreeSet<>(new Comparator() { 30 | @Override 31 | public int compare(NamedPoint p1, NamedPoint p2) { 32 | int result = Integer.compare(p1.getX(), p2.getX()); 33 | if (result == 0) { 34 | result = Integer.compare(p1.getY(), p2.getY()); 35 | } 36 | if (result == 0) { 37 | result = p1.name.compareTo(p2.name); 38 | } 39 | return result; 40 | } 41 | }); 42 | 43 | points.add(p1); 44 | points.add(p2); 45 | 46 | System.out.println(points); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/me/whiteship/chapter02/item14/interitance/Point.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter02.item14.interitance; 2 | 3 | public class Point implements Comparable{ 4 | 5 | final int x, y; 6 | 7 | public Point(int x, int y) { 8 | this.x = x; 9 | this.y = y; 10 | } 11 | 12 | public int getX() { 13 | return x; 14 | } 15 | 16 | public int getY() { 17 | return y; 18 | } 19 | 20 | @Override 21 | public int compareTo(Point point) { 22 | int result = Integer.compare(this.x, point.x); 23 | if (result == 0) { 24 | result = Integer.compare(this.y, point.y); 25 | } 26 | return result; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/test/java/me/whiteship/chapter01/item03/field/ConcertTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.field; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | class ConcertTest { 8 | 9 | @Test 10 | void perform() { 11 | Concert concert = new Concert(new MockElvis()); 12 | concert.perform(); 13 | 14 | assertTrue(concert.isLightsOn()); 15 | assertTrue(concert.isMainStateOpen()); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/test/java/me/whiteship/chapter01/item03/field/MockElvis.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item03.field; 2 | 3 | public class MockElvis implements IElvis { 4 | @Override 5 | public void leaveTheBuilding() { 6 | 7 | } 8 | 9 | @Override 10 | public void sing() { 11 | System.out.println("You ain't nothin' but a hound dog."); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/me/whiteship/chapter01/item05/dependencyinjection/SpellCheckerTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.dependencyinjection; 2 | 3 | import me.whiteship.chapter01.item05.MockDictionary; 4 | import org.junit.jupiter.api.Test; 5 | 6 | class SpellCheckerTest { 7 | 8 | @Test 9 | void isValid() { 10 | SpellChecker spellChecker = new SpellChecker(MockDictionary::new); 11 | spellChecker.isValid("test"); 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/test/java/me/whiteship/chapter01/item05/staticutils/SpellCheckerTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item05.staticutils; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class SpellCheckerTest { 8 | 9 | @Test 10 | void isValid() { 11 | assertTrue(SpellChecker.isValid("test")); 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/test/java/me/whiteship/chapter01/item07/cache/PostRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.cache; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.*; 6 | import java.util.concurrent.Executors; 7 | import java.util.concurrent.ScheduledExecutorService; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertFalse; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | class PostRepositoryTest { 14 | 15 | @Test 16 | void cache() throws InterruptedException { 17 | PostRepository postRepository = new PostRepository(); 18 | CacheKey key1 = new CacheKey(1); 19 | postRepository.getPostById(key1); 20 | 21 | assertFalse(postRepository.getCache().isEmpty()); 22 | 23 | key1 = null; 24 | // TODO run gc 25 | System.out.println("run gc"); 26 | System.gc(); 27 | System.out.println("wait"); 28 | Thread.sleep(3000L); 29 | 30 | assertTrue(postRepository.getCache().isEmpty()); 31 | } 32 | 33 | // @Test 34 | // void backgroundThread() throws InterruptedException { 35 | // ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 36 | // PostRepository postRepository = new PostRepository(); 37 | // CacheKey key1 = new CacheKey(1); 38 | // postRepository.getPostById(key1); 39 | // 40 | // Runnable removeOldCache = () -> { 41 | // System.out.println("running removeOldCache task"); 42 | // Map cache = postRepository.getCache(); 43 | // Set cacheKeys = cache.keySet(); 44 | // Optional key = cacheKeys.stream().min(Comparator.comparing(CacheKey::getCreated)); 45 | // key.ifPresent((k) -> { 46 | // System.out.println("removing " + k); 47 | // cache.remove(k); 48 | // }); 49 | // }; 50 | // 51 | // System.out.println("The time is : " + new Date()); 52 | // 53 | // executor.scheduleAtFixedRate(removeOldCache, 54 | // 1, 3, TimeUnit.SECONDS); 55 | // 56 | // Thread.sleep(20000L); 57 | // 58 | // executor.shutdown(); 59 | // } 60 | 61 | } -------------------------------------------------------------------------------- /src/test/java/me/whiteship/chapter01/item07/listener/ChatRoomTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.listener; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.lang.ref.WeakReference; 6 | import java.util.List; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | class ChatRoomTest { 11 | 12 | @Test 13 | void charRoom() throws InterruptedException { 14 | ChatRoom chatRoom = new ChatRoom(); 15 | User user1 = new User(); 16 | User user2 = new User(); 17 | 18 | chatRoom.addUser(user1); 19 | chatRoom.addUser(user2); 20 | 21 | chatRoom.sendMessage("hello"); 22 | 23 | user1 = null; 24 | 25 | System.gc(); 26 | 27 | Thread.sleep(5000L); 28 | 29 | List> users = chatRoom.getUsers(); 30 | assertTrue(users.size() == 1); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/test/java/me/whiteship/chapter01/item07/optional/ChannelTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item07.optional; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Optional; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class ChannelTest { 10 | 11 | @Test 12 | void npe() { 13 | Channel channel = new Channel(); 14 | Optional optional = channel.defaultMemberShip(); 15 | optional.ifPresent(MemberShip::hello); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/test/java/me/whiteship/chapter01/item08/finalizer_attack/AccountTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.chapter01.item08.finalizer_attack; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | 7 | class AccountTest { 8 | 9 | @Test 10 | void 일반_계정() { 11 | Account account = new Account("keesun"); 12 | account.transfer(BigDecimal.valueOf(10.4),"hello"); 13 | } 14 | 15 | @Test 16 | void 푸틴_계정() throws InterruptedException { 17 | Account account = null; 18 | try { 19 | account = new BrokenAccount("푸틴"); 20 | } catch (Exception exception) { 21 | System.out.println("이러면???"); 22 | } 23 | 24 | System.gc(); 25 | Thread.sleep(3000L); 26 | } 27 | 28 | } --------------------------------------------------------------------------------