diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e77568 --- /dev/null +++ b/.gitignore @@ -0,0 +1,264 @@ +# Created by https://www.toptal.com/developers/gitignore/api/windows,macos,intellij,eclipse,java +# Edit at https://www.toptal.com/developers/gitignore?templates=windows,macos,intellij,eclipse,java + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project + +### Eclipse Patch ### +# Spring Boot Tooling +.sts4-cache/ + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/windows,macos,intellij,eclipse,java diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..c1dd12f Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..b7cb93e --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a59067 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# 이펙티브 자바 스터디 + +> 2022\. 05. 28 ~ + +
+ +## 스터디원 + +[김재호](https://github.com/chamominedev) [이민지](https://github.com/MinJee-lee) [임대진](https://github.com/fineapplepizza) [전선규](https://github.com/sungyujeon) [전희진](https://github.com/h2jinee) + +
+ +## 학습내용 + +1장 들어가기 + +2장 객체 생성과 파괴 + +|아이템|내용|발표자|참고링크| +|:-:|:---:|:---:|:---:| +|1|생성자 대신 정적 팩터리 메서드를 고려하라|전희진|[LINK](https://h2jinee.notion.site/1-6657e62fab5948febb2fba00c3c53046)| +|2|생성자에 매개변수가 많다면 빌더를 고려하라|김재호|[LINK](https://velog.io/@chamominedev/생성자에-매개변수가-많다면-빌더를-고려하라)| +|3|private 생성자나 열거 타입으로 싱글턴임을 보증하라|전선규|[LINK](https://github.com/sungyujeon/TIL/blob/master/java/effective-java/01_creating-destroying-objects.md)| +|4|인스턴스화를 막으려거든 private 생성자를 사용하라|이민지|| +|5|자원을 직접 명시하지 말고 의존 객체 주입을 사용하라|임대진|| + diff --git a/effective-java.git/HEAD b/effective-java.git/HEAD new file mode 100644 index 0000000..b870d82 --- /dev/null +++ b/effective-java.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/effective-java.git/config b/effective-java.git/config new file mode 100644 index 0000000..afffd3a --- /dev/null +++ b/effective-java.git/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = true + symlinks = false + ignorecase = true +[remote "origin"] + url = https://github.com/h2jinee/effective-java/ + fetch = +refs/*:refs/* + mirror = true diff --git a/effective-java.git/description b/effective-java.git/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/effective-java.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/effective-java.git/hooks/applypatch-msg.sample b/effective-java.git/hooks/applypatch-msg.sample new file mode 100644 index 0000000..a5d7b84 --- /dev/null +++ b/effective-java.git/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/effective-java.git/hooks/commit-msg.sample b/effective-java.git/hooks/commit-msg.sample new file mode 100644 index 0000000..b58d118 --- /dev/null +++ b/effective-java.git/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/effective-java.git/hooks/fsmonitor-watchman.sample b/effective-java.git/hooks/fsmonitor-watchman.sample new file mode 100644 index 0000000..14ed0aa --- /dev/null +++ b/effective-java.git/hooks/fsmonitor-watchman.sample @@ -0,0 +1,173 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + } + my $query = <<" END"; + ["query", "$git_work_tree", { + "since": $last_update_token, + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/effective-java.git/hooks/post-update.sample b/effective-java.git/hooks/post-update.sample new file mode 100644 index 0000000..ec17ec1 --- /dev/null +++ b/effective-java.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/effective-java.git/hooks/pre-applypatch.sample b/effective-java.git/hooks/pre-applypatch.sample new file mode 100644 index 0000000..4142082 --- /dev/null +++ b/effective-java.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/effective-java.git/hooks/pre-commit.sample b/effective-java.git/hooks/pre-commit.sample new file mode 100644 index 0000000..e144712 --- /dev/null +++ b/effective-java.git/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/effective-java.git/hooks/pre-merge-commit.sample b/effective-java.git/hooks/pre-merge-commit.sample new file mode 100644 index 0000000..399eab1 --- /dev/null +++ b/effective-java.git/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/effective-java.git/hooks/pre-push.sample b/effective-java.git/hooks/pre-push.sample new file mode 100644 index 0000000..4ce688d --- /dev/null +++ b/effective-java.git/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/effective-java.git/hooks/pre-rebase.sample b/effective-java.git/hooks/pre-rebase.sample new file mode 100644 index 0000000..6cbef5c --- /dev/null +++ b/effective-java.git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/effective-java.git/hooks/pre-receive.sample b/effective-java.git/hooks/pre-receive.sample new file mode 100644 index 0000000..a1fd29e --- /dev/null +++ b/effective-java.git/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/effective-java.git/hooks/prepare-commit-msg.sample b/effective-java.git/hooks/prepare-commit-msg.sample new file mode 100644 index 0000000..10fa14c --- /dev/null +++ b/effective-java.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/effective-java.git/hooks/push-to-checkout.sample b/effective-java.git/hooks/push-to-checkout.sample new file mode 100644 index 0000000..af5a0c0 --- /dev/null +++ b/effective-java.git/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/effective-java.git/info/exclude b/effective-java.git/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/effective-java.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/effective-java.git/objects/pack/pack-df45e6fb3b48ee1346b65d61b1d9bafc795fd097.idx b/effective-java.git/objects/pack/pack-df45e6fb3b48ee1346b65d61b1d9bafc795fd097.idx new file mode 100644 index 0000000..73c3a1c Binary files /dev/null and b/effective-java.git/objects/pack/pack-df45e6fb3b48ee1346b65d61b1d9bafc795fd097.idx differ diff --git a/effective-java.git/objects/pack/pack-df45e6fb3b48ee1346b65d61b1d9bafc795fd097.pack b/effective-java.git/objects/pack/pack-df45e6fb3b48ee1346b65d61b1d9bafc795fd097.pack new file mode 100644 index 0000000..3034049 Binary files /dev/null and b/effective-java.git/objects/pack/pack-df45e6fb3b48ee1346b65d61b1d9bafc795fd097.pack differ diff --git a/effective-java.git/packed-refs b/effective-java.git/packed-refs new file mode 100644 index 0000000..9bd8a51 --- /dev/null +++ b/effective-java.git/packed-refs @@ -0,0 +1,7 @@ +# pack-refs with: peeled fully-peeled sorted +af592dd06cd89193d952ab8d852fb07082ea77c2 refs/heads/daejin +af592dd06cd89193d952ab8d852fb07082ea77c2 refs/heads/heejin +af592dd06cd89193d952ab8d852fb07082ea77c2 refs/heads/jaeho +a5ee82bb39eb806b0129e0fc0f4103f2aeed3949 refs/heads/main +af592dd06cd89193d952ab8d852fb07082ea77c2 refs/heads/minjee +85e1ec02f7daa6772ce41d08f55d9f253e5e9ab7 refs/heads/sungyu diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..8a8fb22 --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..1d8ab01 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a5b1c70 --- /dev/null +++ b/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + me.whiteship + effective-java-part1 + 0.0.1-SNAPSHOT + effective-java-part1 + effective-java-part1 + + 11 + + + + + com.google.guava + guava + 31.1-jre + + + com.google.auto.value + auto-value-annotations + 1.9 + + + com.google.auto.value + auto-value + 1.9 + + + + + + + + + + org.springframework.boot + spring-boot-starter + + + + org.projectlombok + lombok + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + + + diff --git a/src/main/java/me/whiteship/chapter01/item01/ActionEnum.java b/src/main/java/me/whiteship/chapter01/item01/ActionEnum.java new file mode 100644 index 0000000..4a13404 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/ActionEnum.java @@ -0,0 +1,11 @@ +package me.whiteship.chapter01.item01; + +public enum ActionEnum { + ACTION_1(new App()) + , ACTION_2(new App()); + + private final App action; + + ActionEnum(App action) {this.action = action;} + +} \ No newline at end of file diff --git a/src/main/java/me/whiteship/chapter01/item01/AdvancedSettings.java b/src/main/java/me/whiteship/chapter01/item01/AdvancedSettings.java new file mode 100644 index 0000000..fbe7028 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/AdvancedSettings.java @@ -0,0 +1,8 @@ +package me.whiteship.chapter01.item01; + +public class AdvancedSettings { + + Settings settings; + + +} diff --git a/src/main/java/me/whiteship/chapter01/item01/App.java b/src/main/java/me/whiteship/chapter01/item01/App.java new file mode 100644 index 0000000..af910d4 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/App.java @@ -0,0 +1,13 @@ +package me.whiteship.chapter01.item01; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class App { + + public static void main(String[] args) { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); + HelloService helloService = applicationContext.getBean(HelloService.class); + System.out.println(helloService.hello()); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item01/AppConfig.java b/src/main/java/me/whiteship/chapter01/item01/AppConfig.java new file mode 100644 index 0000000..b7cff42 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/AppConfig.java @@ -0,0 +1,15 @@ +package me.whiteship.chapter01.item01; + +//import me.whiteship.hello.ChineseHelloService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig { +// +// @Bean +// public HelloService helloService() { +// return new ChineseHelloService(); +// } + +} diff --git a/src/main/java/me/whiteship/chapter01/item01/Difficulty.java b/src/main/java/me/whiteship/chapter01/item01/Difficulty.java new file mode 100644 index 0000000..bb7c753 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/Difficulty.java @@ -0,0 +1,6 @@ +package me.whiteship.chapter01.item01; + +public enum Difficulty { + + EASY, NORMAL, HARD, HELL +} diff --git a/src/main/java/me/whiteship/chapter01/item01/HelloService.java b/src/main/java/me/whiteship/chapter01/item01/HelloService.java new file mode 100644 index 0000000..9d8b45b --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/HelloService.java @@ -0,0 +1,28 @@ +package me.whiteship.chapter01.item01; + +public interface HelloService { + + String hello(); + + static String hi() { + prepareMessage(); + return "hi"; + } + + static private void prepareMessage() { + } + + static String hi1() { + prepareMessage(); + return "hi"; + } + + static String hi2() { + prepareMessage(); + return "hi"; + } + + default String bye() { + return "bye"; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item01/HelloServiceFactory.java b/src/main/java/me/whiteship/chapter01/item01/HelloServiceFactory.java new file mode 100644 index 0000000..a552e61 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/HelloServiceFactory.java @@ -0,0 +1,28 @@ +package me.whiteship.chapter01.item01; + +//import me.whiteship.hello.ChineseHelloService; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; +import java.util.ServiceLoader; + +public class HelloServiceFactory { + + public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + ServiceLoader loader = ServiceLoader.load(HelloService.class); + Optional helloServiceOptional = loader.findFirst(); + helloServiceOptional.ifPresent(h -> { + System.out.println(h.hello()); + }); + +// HelloService helloService = new ChineseHelloService(); +// System.out.println(helloService.hello()); + +// Class aClass = Class.forName("me.whiteship.hello.ChineseHelloService"); +// Constructor constructor = aClass.getConstructor(); +// HelloService helloService = (HelloService) constructor.newInstance(); +// System.out.println(helloService.hello()); + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item01/ListQuiz.java b/src/main/java/me/whiteship/chapter01/item01/ListQuiz.java new file mode 100644 index 0000000..5c3a549 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/ListQuiz.java @@ -0,0 +1,24 @@ +package me.whiteship.chapter01.item01; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class ListQuiz { + + public static void main(String[] args) { + List numbers = new ArrayList(); + numbers.add(100); + numbers.add(20); + numbers.add(44); + numbers.add(3); + + System.out.println(numbers); + + Comparator desc = (o1, o2) -> o2 - o1; + + numbers.sort(desc.reversed()); + + System.out.println(numbers); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item01/Order.java b/src/main/java/me/whiteship/chapter01/item01/Order.java new file mode 100644 index 0000000..6c959ea --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/Order.java @@ -0,0 +1,38 @@ +package me.whiteship.chapter01.item01; + +import java.util.*; + +public class Order { + + private boolean prime; + + private boolean urgent; + + private Product product; + + private OrderStatus orderStatus; + + public static Order primeOrder(Product product) { + Order order = new Order(); + order.prime = true; + order.product = product; + + return order; + } + + public static Order urgentOrder(Product product) { + Order order = new Order(); + order.urgent = true; + order.product = product; + return order; + } + + public static void main(String[] args) { + + Order order = new Order(); + if (order.orderStatus == OrderStatus.DELIVERED) { + System.out.println("delivered"); + } + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item01/OrderStatus.java b/src/main/java/me/whiteship/chapter01/item01/OrderStatus.java new file mode 100644 index 0000000..785b204 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/OrderStatus.java @@ -0,0 +1,12 @@ +package me.whiteship.chapter01.item01; + +public enum OrderStatus { + + PREPARING(0), SHIPPED(1), DELIVERING(2), DELIVERED(3); + + private int number; + + OrderStatus(int number) { + this.number = number; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item01/Product.java b/src/main/java/me/whiteship/chapter01/item01/Product.java new file mode 100644 index 0000000..7891629 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/Product.java @@ -0,0 +1,18 @@ +package me.whiteship.chapter01.item01; + +import java.util.EnumSet; +import java.util.Set; + +public class Product { + + public static void main(String[] args) { + Settings settings1 = Settings.getInstance(); + Settings settings2 = Settings.getInstance(); + + System.out.println(settings1); + System.out.println(settings2); + + Boolean.valueOf(false); + EnumSet.allOf(Difficulty.class); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item01/Settings.java b/src/main/java/me/whiteship/chapter01/item01/Settings.java new file mode 100644 index 0000000..a5566ba --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item01/Settings.java @@ -0,0 +1,23 @@ +package me.whiteship.chapter01.item01; + +/** + * 이 클래스의 인스턴스는 #getInstance()를 통해 사용한다. + * @see #getInstance() + */ +public class Settings { + + private boolean useAutoSteering; + + private boolean useABS; + + private Difficulty difficulty; + + private Settings() {} + + private static final Settings SETTINGS = new Settings(); + + public static Settings getInstance() { + return SETTINGS; + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item02/builder/BuilderTest.java b/src/main/java/me/whiteship/chapter01/item02/builder/BuilderTest.java new file mode 100644 index 0000000..f505b26 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/builder/BuilderTest.java @@ -0,0 +1,10 @@ +package me.whiteship.chapter01.item02.builder; + +public class BuilderTest { + + public static void main(String[] args) { + new NutritionFacts.Builder(10, 10) + .calories(10) + .build(); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item02/builder/NutritionFacts.java b/src/main/java/me/whiteship/chapter01/item02/builder/NutritionFacts.java new file mode 100644 index 0000000..78d00c4 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/builder/NutritionFacts.java @@ -0,0 +1,57 @@ +package me.whiteship.chapter01.item02.builder; + +// 코드 2-3 빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다. (17~18쪽) +public class NutritionFacts { + private final int servingSize; + private final int servings; + private final int calories; + private final int fat; + private final int sodium; + private final int carbohydrate; + + public static void main(String[] args) { + NutritionFacts cocaCola = new Builder(240, 8) + .calories(100) + .sodium(35) + .carbohydrate(27).build(); + } + + public static class Builder { + // 필수 매개변수 + private final int servingSize; + private final int servings; + + // 선택 매개변수 - 기본값으로 초기화한다. + private int calories = 0; + private int fat = 0; + private int sodium = 0; + private int carbohydrate = 0; + + public Builder(int servingSize, int servings) { + this.servingSize = servingSize; + this.servings = servings; + } + + public Builder calories(int val) + { calories = val; return this; } + public Builder fat(int val) + { fat = val; return this; } + public Builder sodium(int val) + { sodium = val; return this; } + public Builder carbohydrate(int val) + { carbohydrate = val; return this; } + + public NutritionFacts build() { + return new NutritionFacts(this); + } + } + + private NutritionFacts(Builder builder) { + servingSize = builder.servingSize; + servings = builder.servings; + calories = builder.calories; + fat = builder.fat; + sodium = builder.sodium; + carbohydrate = builder.carbohydrate; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item02/freeze/FreezeTest.js b/src/main/java/me/whiteship/chapter01/item02/freeze/FreezeTest.js new file mode 100644 index 0000000..8991197 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/freeze/FreezeTest.js @@ -0,0 +1,10 @@ +const keesun = { + 'name': 'Keesun', + 'age': 40 +}; + +Object.freeze(keesun); + +keesun.kids = ["서연"]; + +console.info(keesun.name); \ No newline at end of file diff --git a/src/main/java/me/whiteship/chapter01/item02/freeze/Person.java b/src/main/java/me/whiteship/chapter01/item02/freeze/Person.java new file mode 100644 index 0000000..3d1781e --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/freeze/Person.java @@ -0,0 +1,24 @@ +package me.whiteship.chapter01.item02.freeze; + +import java.util.ArrayList; +import java.util.List; + +public class Person { + + private final String name; + + private final int birthYear; + + private final List kids; + + public Person(String name, int birthYear) { + this.name = name; + this.birthYear = birthYear; + this.kids = new ArrayList<>(); + } + + public static void main(String[] args) { + Person person = new Person("keesun", 1982); + + } +} diff --git a/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/Calzone.java b/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/Calzone.java new file mode 100644 index 0000000..5ef1df5 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/Calzone.java @@ -0,0 +1,31 @@ +package me.whiteship.chapter01.item02.hierarchicalbuilder; + +// 코드 2-6 칼초네 피자 - 계층적 빌더를 활용한 하위 클래스 (20~21쪽) +public class Calzone extends Pizza { + private final boolean sauceInside; + + public static class Builder extends Pizza.Builder { + private boolean sauceInside = false; // 기본값 + + public Builder sauceInside() { + sauceInside = true; + return this; + } + + @Override public Calzone build() { + return new Calzone(this); + } + + @Override protected Builder self() { return this; } + } + + private Calzone(Builder builder) { + super(builder); + sauceInside = builder.sauceInside; + } + + @Override public String toString() { + return String.format("%s로 토핑한 칼초네 피자 (소스는 %s에)", + toppings, sauceInside ? "안" : "바깥"); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/NyPizza.java b/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/NyPizza.java new file mode 100644 index 0000000..b0f04c1 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/NyPizza.java @@ -0,0 +1,32 @@ +package me.whiteship.chapter01.item02.hierarchicalbuilder; + +import java.util.Objects; + +// 코드 2-5 뉴욕 피자 - 계층적 빌더를 활용한 하위 클래스 (20쪽) +public class NyPizza extends Pizza { + public enum Size { SMALL, MEDIUM, LARGE } + private final Size size; + + public static class Builder extends Pizza.Builder { + private final Size size; + + public Builder(Size size) { + this.size = Objects.requireNonNull(size); + } + + @Override public NyPizza build() { + return new NyPizza(this); + } + + @Override protected Builder self() { return this; } + } + + private NyPizza(Builder builder) { + super(builder); + size = builder.size; + } + + @Override public String toString() { + return toppings + "로 토핑한 뉴욕 피자"; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/Pizza.java b/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/Pizza.java new file mode 100644 index 0000000..edb3dd0 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/Pizza.java @@ -0,0 +1,33 @@ +package me.whiteship.chapter01.item02.hierarchicalbuilder; + +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; + +// 코드 2-4 계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴 (19쪽) + +// 참고: 여기서 사용한 '시뮬레이트한 셀프 타입(simulated self-type)' 관용구는 +// 빌더뿐 아니라 임의의 유동적인 계층구조를 허용한다. + +public abstract class Pizza { + public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE } + final Set toppings; + + abstract static class Builder> { + EnumSet toppings = EnumSet.noneOf(Topping.class); + public T addTopping(Topping topping) { + toppings.add(Objects.requireNonNull(topping)); + return self(); + } + + abstract Pizza build(); + + // 하위 클래스는 이 메서드를 재정의(overriding)하여 + // "this"를 반환하도록 해야 한다. + protected abstract T self(); + } + + Pizza(Builder builder) { + toppings = builder.toppings.clone(); // 아이템 50 참조 + } +} diff --git a/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/PizzaTest.java b/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/PizzaTest.java new file mode 100644 index 0000000..797365e --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/hierarchicalbuilder/PizzaTest.java @@ -0,0 +1,20 @@ +package me.whiteship.chapter01.item02.hierarchicalbuilder; + + +import static me.whiteship.chapter01.item02.hierarchicalbuilder.NyPizza.Size.SMALL; +import static me.whiteship.chapter01.item02.hierarchicalbuilder.Pizza.Topping.*; + +// 계층적 빌더 사용 (21쪽) +public class PizzaTest { + public static void main(String[] args) { + NyPizza pizza = new NyPizza.Builder(SMALL) + .addTopping(SAUSAGE) + .addTopping(ONION).build(); + + Calzone calzone = new Calzone.Builder() + .addTopping(HAM).sauceInside().build(); + + System.out.println(pizza); + System.out.println(calzone); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item02/illegalargumentexception/Order.java b/src/main/java/me/whiteship/chapter01/item02/illegalargumentexception/Order.java new file mode 100644 index 0000000..35ac437 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/illegalargumentexception/Order.java @@ -0,0 +1,20 @@ +package me.whiteship.chapter01.item02.illegalargumentexception; + +import java.time.LocalDate; + +public class Order { + + public void updateDeliveryDate(LocalDate deliveryDate) { + if (deliveryDate == null) { + throw new NullPointerException("deliveryDate can't be null"); + } + + if (deliveryDate.isBefore(LocalDate.now())) { + //TODO 과거로 배송 해달라고?? + throw new IllegalArgumentException("deliveryDate can't be earlier than " + LocalDate.now()); + } + + // 배송 날짜 업데이트 + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item02/javabeans/NutritionFacts.java b/src/main/java/me/whiteship/chapter01/item02/javabeans/NutritionFacts.java new file mode 100644 index 0000000..9e829e8 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/javabeans/NutritionFacts.java @@ -0,0 +1,53 @@ +package me.whiteship.chapter01.item02.javabeans; + +// 코드 2-2 자바빈즈 패턴 - 일관성이 깨지고, 불변으로 만들 수 없다. (16쪽) +public class NutritionFacts { + // 필드 (기본값이 있다면) 기본값으로 초기화된다. + private int servingSize = -1; // 필수; 기본값 없음 + private int servings = -1; // 필수; 기본값 없음 + private int calories = 0; + private int fat = 0; + private int sodium = 0; + private int carbohydrate = 0; + private boolean healthy; + + public NutritionFacts() { } + + public void setServingSize(int servingSize) { + this.servingSize = servingSize; + } + + public void setServings(int servings) { + this.servings = servings; + } + + public void setCalories(int calories) { + this.calories = calories; + } + + public void setFat(int fat) { + this.fat = fat; + } + + public void setSodium(int sodium) { + this.sodium = sodium; + } + + public void setCarbohydrate(int carbohydrate) { + this.carbohydrate = carbohydrate; + } + + public void setHealthy(boolean healthy) { + this.healthy = healthy; + } + + public static void main(String[] args) { + NutritionFacts cocaCola = new NutritionFacts(); + cocaCola.setServingSize(240); + cocaCola.setServings(8); + + cocaCola.setCalories(100); + cocaCola.setSodium(35); + cocaCola.setCarbohydrate(27); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item02/telescopingconstructor/NutritionFacts.java b/src/main/java/me/whiteship/chapter01/item02/telescopingconstructor/NutritionFacts.java new file mode 100644 index 0000000..e9bc38f --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/telescopingconstructor/NutritionFacts.java @@ -0,0 +1,46 @@ +package me.whiteship.chapter01.item02.telescopingconstructor; + +// 코드 2-1 점층적 생성자 패턴 - 확장하기 어렵다! (14~15쪽) +public class NutritionFacts { + private final int servingSize; // (mL, 1회 제공량) 필수 + private final int servings; // (회, 총 n회 제공량) 필수 + private final int calories; // (1회 제공량당) 선택 + private final int fat; // (g/1회 제공량) 선택 + private final int sodium; // (mg/1회 제공량) 선택 + private final int carbohydrate; // (g/1회 제공량) 선택 + + public NutritionFacts(int servingSize, int servings) { + this(servingSize, servings, 0); + } + + public NutritionFacts(int servingSize, int servings, + int calories) { + this(servingSize, servings, calories, 0); + } + + public NutritionFacts(int servingSize, int servings, + int calories, int fat) { + this(servingSize, servings, calories, fat, 0); + } + + public NutritionFacts(int servingSize, int servings, + int calories, int fat, int sodium) { + this(servingSize, servings, calories, fat, sodium, 0); + } + + public NutritionFacts(int servingSize, int servings, + int calories, int fat, int sodium, int carbohydrate) { + this.servingSize = servingSize; + this.servings = servings; + this.calories = calories; + this.fat = fat; + this.sodium = sodium; + this.carbohydrate = carbohydrate; + } + + public static void main(String[] args) { + NutritionFacts cocaCola = + new NutritionFacts(10, 10); + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item02/varargs/VarargsSamples.java b/src/main/java/me/whiteship/chapter01/item02/varargs/VarargsSamples.java new file mode 100644 index 0000000..692780f --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item02/varargs/VarargsSamples.java @@ -0,0 +1,17 @@ +package me.whiteship.chapter01.item02.varargs; + +import java.util.Arrays; + +public class VarargsSamples { + + public void printNumbers(int... numbers) { + System.out.println(numbers.getClass().getCanonicalName()); + System.out.println(numbers.getClass().getComponentType()); + Arrays.stream(numbers).forEach(System.out::println); + } + + public static void main(String[] args) { + VarargsSamples samples = new VarargsSamples(); + samples.printNumbers(1, 20, 20, 39, 59); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/enumtype/Elvis.java b/src/main/java/me/whiteship/chapter01/item03/enumtype/Elvis.java new file mode 100644 index 0000000..2162896 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/enumtype/Elvis.java @@ -0,0 +1,16 @@ +package me.whiteship.chapter01.item03.enumtype; + +// 열거 타입 방식의 싱글턴 - 바람직한 방법 (25쪽) +public enum Elvis { + INSTANCE; + + public void leaveTheBuilding() { + System.out.println("기다려 자기야, 지금 나갈께!"); + } + + // 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다! + public static void main(String[] args) { + Elvis elvis = Elvis.INSTANCE; + elvis.leaveTheBuilding(); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/enumtype/EnumElvisReflection.java b/src/main/java/me/whiteship/chapter01/item03/enumtype/EnumElvisReflection.java new file mode 100644 index 0000000..67cffdf --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/enumtype/EnumElvisReflection.java @@ -0,0 +1,15 @@ +package me.whiteship.chapter01.item03.enumtype; + +import java.lang.reflect.Constructor; + +public class EnumElvisReflection { + + public static void main(String[] args) { + try { + Constructor declaredConstructor = Elvis.class.getDeclaredConstructor(); + System.out.println(declaredConstructor); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/enumtype/EnumElvisSerialization.java b/src/main/java/me/whiteship/chapter01/item03/enumtype/EnumElvisSerialization.java new file mode 100644 index 0000000..b67a348 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/enumtype/EnumElvisSerialization.java @@ -0,0 +1,23 @@ +package me.whiteship.chapter01.item03.enumtype; + +import me.whiteship.chapter01.item03.field.Elvis; + +import java.io.*; + +public class EnumElvisSerialization { + + public static void main(String[] args) { + try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("elvis.obj"))) { + out.writeObject(Elvis.INSTANCE); + } catch (IOException e) { + e.printStackTrace(); + } + + try (ObjectInput in = new ObjectInputStream(new FileInputStream("elvis.obj"))) { + Elvis elvis = (Elvis) in.readObject(); + System.out.println(elvis == Elvis.INSTANCE); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/field/Concert.java b/src/main/java/me/whiteship/chapter01/item03/field/Concert.java new file mode 100644 index 0000000..a357c35 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/field/Concert.java @@ -0,0 +1,28 @@ +package me.whiteship.chapter01.item03.field; + +public class Concert { + + private boolean lightsOn; + + private boolean mainStateOpen; + + private IElvis elvis; + + public Concert(IElvis elvis) { + this.elvis = elvis; + } + + public void perform() { + mainStateOpen = true; + lightsOn = true; + elvis.sing(); + } + + public boolean isLightsOn() { + return lightsOn; + } + + public boolean isMainStateOpen() { + return mainStateOpen; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/field/Elvis.java b/src/main/java/me/whiteship/chapter01/item03/field/Elvis.java new file mode 100644 index 0000000..f72600a --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/field/Elvis.java @@ -0,0 +1,40 @@ +package me.whiteship.chapter01.item03.field; + +import java.io.Serializable; + +// 코드 3-1 public static final 필드 방식의 싱글턴 (23쪽) +public class Elvis implements IElvis, Serializable { + + /** + * 싱글톤 오브젝트 + */ + public static final Elvis INSTANCE = new Elvis(); + private static boolean created; + + private Elvis() { + if (created) { + throw new UnsupportedOperationException("can't be created by constructor."); + } + + created = true; + } + + public void leaveTheBuilding() { + System.out.println("Whoa baby, I'm outta here!"); + } + + public void sing() { + System.out.println("I'll have a blue~ Christmas without you~"); + } + + // 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다! + public static void main(String[] args) { + Elvis elvis = Elvis.INSTANCE; + elvis.leaveTheBuilding(); + } + + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item03/field/ElvisReflection.java b/src/main/java/me/whiteship/chapter01/item03/field/ElvisReflection.java new file mode 100644 index 0000000..e9c0211 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/field/ElvisReflection.java @@ -0,0 +1,21 @@ +package me.whiteship.chapter01.item03.field; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +// 생성자로 여러 인스턴스 만들기 +public class ElvisReflection { + + public static void main(String[] args) { + try { + Constructor defaultConstructor = Elvis.class.getDeclaredConstructor(); + defaultConstructor.setAccessible(true); + Elvis elvis1 = defaultConstructor.newInstance(); + Elvis elvis2 = defaultConstructor.newInstance(); + Elvis.INSTANCE.sing(); + } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item03/field/ElvisSerialization.java b/src/main/java/me/whiteship/chapter01/item03/field/ElvisSerialization.java new file mode 100644 index 0000000..6507ed7 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/field/ElvisSerialization.java @@ -0,0 +1,23 @@ +package me.whiteship.chapter01.item03.field; + +import java.io.*; + +// 역직렬화로 여러 인스턴스 만들기 +public class ElvisSerialization { + + public static void main(String[] args) { + try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("elvis.obj"))) { + out.writeObject(Elvis.INSTANCE); + } catch (IOException e) { + e.printStackTrace(); + } + + try (ObjectInput in = new ObjectInputStream(new FileInputStream("elvis.obj"))) { + Elvis elvis3 = (Elvis) in.readObject(); + System.out.println(elvis3 == Elvis.INSTANCE); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item03/field/IElvis.java b/src/main/java/me/whiteship/chapter01/item03/field/IElvis.java new file mode 100644 index 0000000..fd60bfa --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/field/IElvis.java @@ -0,0 +1,8 @@ +package me.whiteship.chapter01.item03.field; + +public interface IElvis { + + void leaveTheBuilding(); + + void sing(); +} diff --git a/src/main/java/me/whiteship/chapter01/item03/functionalinterface/DefaultFunctions.java b/src/main/java/me/whiteship/chapter01/item03/functionalinterface/DefaultFunctions.java new file mode 100644 index 0000000..672c493 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/functionalinterface/DefaultFunctions.java @@ -0,0 +1,24 @@ +package me.whiteship.chapter01.item03.functionalinterface; + + +import me.whiteship.chapter01.item03.methodreference.Person; + +import java.time.LocalDate; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public class DefaultFunctions { + + public static void main(String[] args) { + Function intToString = Object::toString; + + Supplier personSupplier = Person::new; + Function personFunction = Person::new; + + Consumer integerConsumer = System.out::println; + + Predicate predicate; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/functionalinterface/MyFunction.java b/src/main/java/me/whiteship/chapter01/item03/functionalinterface/MyFunction.java new file mode 100644 index 0000000..521a650 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/functionalinterface/MyFunction.java @@ -0,0 +1,11 @@ +package me.whiteship.chapter01.item03.functionalinterface; + +@FunctionalInterface +public interface MyFunction { + + String valueOf(Integer integer); + + static String hello() { + return "hello"; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/functionalinterface/UsageOfFunctions.java b/src/main/java/me/whiteship/chapter01/item03/functionalinterface/UsageOfFunctions.java new file mode 100644 index 0000000..ec8071c --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/functionalinterface/UsageOfFunctions.java @@ -0,0 +1,21 @@ +package me.whiteship.chapter01.item03.functionalinterface; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class UsageOfFunctions { + + public static void main(String[] args) { + List dates = new ArrayList<>(); + dates.add(LocalDate.of(1982, 7, 15)); + dates.add(LocalDate.of(2011, 3, 2)); + dates.add(LocalDate.of(2013, 1, 28)); + + List before2000 = dates.stream() + .filter(d -> d.isBefore(LocalDate.of(2000, 1, 1))) + .map(LocalDate::getYear) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/methodreference/Person.java b/src/main/java/me/whiteship/chapter01/item03/methodreference/Person.java new file mode 100644 index 0000000..835ca38 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/methodreference/Person.java @@ -0,0 +1,42 @@ +package me.whiteship.chapter01.item03.methodreference; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class Person { + + LocalDate birthday; + + public Person() { + + } + + public Person(LocalDate birthday) { + this.birthday = birthday; + } + + public static int compareByAge(Person a, Person b) { + return a.birthday.compareTo(b.birthday); + } + + public static void main(String[] args) { + List people = new ArrayList<>(); + people.add(new Person(LocalDate.of(1982, 7, 15))); + people.add(new Person(LocalDate.of(2011, 3, 2))); + people.add(new Person(LocalDate.of(2013, 1, 28))); + + people.sort(new Comparator() { + @Override + public int compare(Person a, Person b) { + return a.birthday.compareTo(b.birthday); + } + }); + } + + public int getAge() { + return LocalDate.now().getYear() - birthday.getYear(); + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item03/serialization/Book.java b/src/main/java/me/whiteship/chapter01/item03/serialization/Book.java new file mode 100644 index 0000000..940329d --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/serialization/Book.java @@ -0,0 +1,67 @@ +package me.whiteship.chapter01.item03.serialization; + +import java.io.Serializable; +import java.time.LocalDate; + +public class Book implements Serializable { + + private static final long serialVersionUID = 1L; + + private String isbn; + + private String title; + + private LocalDate published; + + private String name; + + private transient int numberOfSold; + + public Book(String isbn, String title, String author, LocalDate published) { + this.isbn = isbn; + this.title = title; + this.published = published; + } + + @Override + public String toString() { + return "Book{" + + "isbn='" + isbn + '\'' + + ", title='" + title + '\'' + + ", published=" + published + + ", numberOfSold=" + numberOfSold + + '}'; + } + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public LocalDate getPublished() { + return published; + } + + public void setPublished(LocalDate published) { + this.published = published; + } + + public int getNumberOfSold() { + return numberOfSold; + } + + public void setNumberOfSold(int numberOfSold) { + this.numberOfSold = numberOfSold; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/serialization/SerializationExample.java b/src/main/java/me/whiteship/chapter01/item03/serialization/SerializationExample.java new file mode 100644 index 0000000..0fa0074 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/serialization/SerializationExample.java @@ -0,0 +1,36 @@ +package me.whiteship.chapter01.item03.serialization; + +import java.io.*; +import java.time.LocalDate; + +public class SerializationExample { + + private void serialize(Book book) { + try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("book.obj"))) { + out.writeObject(book); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Book deserialize() { + try (ObjectInput in = new ObjectInputStream(new FileInputStream("book.obj"))) { + return (Book) in.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) { +// Book book = new Book("12345", "이팩티브 자바 완벽 공략", "백기선", +// LocalDate.of(2022, 3, 21)); +// book.setNumberOfSold(200); + + SerializationExample example = new SerializationExample(); +// example.serialize(book); + Book deserializedBook = example.deserialize(); + +// System.out.println(book); + System.out.println(deserializedBook); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/staticfactory/Concert.java b/src/main/java/me/whiteship/chapter01/item03/staticfactory/Concert.java new file mode 100644 index 0000000..8abaf76 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/staticfactory/Concert.java @@ -0,0 +1,16 @@ +package me.whiteship.chapter01.item03.staticfactory; + +import java.util.function.Supplier; + +public class Concert { + + public void start(Supplier singerSupplier) { + Singer singer = singerSupplier.get(); + singer.sing(); + } + + public static void main(String[] args) { + Concert concert = new Concert(); + concert.start(Elvis::getInstance); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/staticfactory/Elvis.java b/src/main/java/me/whiteship/chapter01/item03/staticfactory/Elvis.java new file mode 100644 index 0000000..382c222 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/staticfactory/Elvis.java @@ -0,0 +1,26 @@ +package me.whiteship.chapter01.item03.staticfactory; + +// 코드 3-2 정적 팩터리 방식의 싱글턴 (24쪽) +public class Elvis implements Singer { + private static final Elvis INSTANCE = new Elvis(); + private Elvis() { } + public static Elvis getInstance() { return INSTANCE; } + + public void leaveTheBuilding() { + System.out.println("Whoa baby, I'm outta here!"); + } + + // 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다! + public static void main(String[] args) { + Elvis elvis = Elvis.getInstance(); + elvis.leaveTheBuilding(); + + System.out.println(Elvis.getInstance()); + System.out.println(Elvis.getInstance()); + } + + @Override + public void sing() { + System.out.println("my way~~~"); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item03/staticfactory/MetaElvis.java b/src/main/java/me/whiteship/chapter01/item03/staticfactory/MetaElvis.java new file mode 100644 index 0000000..ce30e64 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/staticfactory/MetaElvis.java @@ -0,0 +1,32 @@ +package me.whiteship.chapter01.item03.staticfactory; + +import java.util.Objects; + +// 코드 3-2 제네릭 싱글톤 팩토리 (24쪽) +public class MetaElvis { + + private static final MetaElvis INSTANCE = new MetaElvis<>(); + + private MetaElvis() { } + + @SuppressWarnings("unchecked") + public static MetaElvis getInstance() { return (MetaElvis) INSTANCE; } + + public void say(T t) { + System.out.println(t); + } + + public void leaveTheBuilding() { + System.out.println("Whoa baby, I'm outta here!"); + } + + public static void main(String[] args) { + MetaElvis elvis1 = MetaElvis.getInstance(); + MetaElvis elvis2 = MetaElvis.getInstance(); + System.out.println(elvis1); + System.out.println(elvis2); + elvis1.say("hello"); + elvis2.say(100); + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item03/staticfactory/Singer.java b/src/main/java/me/whiteship/chapter01/item03/staticfactory/Singer.java new file mode 100644 index 0000000..1d607f0 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item03/staticfactory/Singer.java @@ -0,0 +1,6 @@ +package me.whiteship.chapter01.item03.staticfactory; + +public interface Singer { + + void sing(); +} diff --git a/src/main/java/me/whiteship/chapter01/item04/DefaultUtilityClass.java b/src/main/java/me/whiteship/chapter01/item04/DefaultUtilityClass.java new file mode 100644 index 0000000..1d3b932 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item04/DefaultUtilityClass.java @@ -0,0 +1,9 @@ +package me.whiteship.chapter01.item04; + +public class DefaultUtilityClass extends UtilityClass { + + public static void main(String[] args) { + DefaultUtilityClass utilityClass = new DefaultUtilityClass(); + utilityClass.hello(); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item04/UtilityClass.java b/src/main/java/me/whiteship/chapter01/item04/UtilityClass.java new file mode 100644 index 0000000..5afb751 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item04/UtilityClass.java @@ -0,0 +1,26 @@ +package me.whiteship.chapter01.item04; + +import org.springframework.context.annotation.AnnotationConfigUtils; + +public class UtilityClass { + + /** + * 이 클래스는 인스턴스를 만들 수 없습니다. + */ +// private UtilityClass() { +// throw new AssertionError(); +// } + + public static String hello() { + return "hello"; + } + + public static void main(String[] args) { + String hello = UtilityClass.hello(); + + UtilityClass utilityClass = new UtilityClass(); + utilityClass.hello(); + + + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/DefaultDictionary.java b/src/main/java/me/whiteship/chapter01/item05/DefaultDictionary.java new file mode 100644 index 0000000..e02a16d --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/DefaultDictionary.java @@ -0,0 +1,18 @@ +package me.whiteship.chapter01.item05; + +import org.springframework.stereotype.Component; + +import java.util.List; + +public class DefaultDictionary implements Dictionary{ + + @Override + public boolean contains(String word) { + return false; + } + + @Override + public List closeWordsTo(String typo) { + return null; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/Dictionary.java b/src/main/java/me/whiteship/chapter01/item05/Dictionary.java new file mode 100644 index 0000000..1f6ac9b --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/Dictionary.java @@ -0,0 +1,10 @@ +package me.whiteship.chapter01.item05; + +import java.util.List; + +public interface Dictionary { + + boolean contains(String word); + + List closeWordsTo(String typo); +} diff --git a/src/main/java/me/whiteship/chapter01/item05/MockDictionary.java b/src/main/java/me/whiteship/chapter01/item05/MockDictionary.java new file mode 100644 index 0000000..adc401a --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/MockDictionary.java @@ -0,0 +1,15 @@ +package me.whiteship.chapter01.item05; + +import java.util.List; + +public class MockDictionary implements Dictionary{ + @Override + public boolean contains(String word) { + return false; + } + + @Override + public List closeWordsTo(String typo) { + return null; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/dependencyinjection/DictionaryFactory.java b/src/main/java/me/whiteship/chapter01/item05/dependencyinjection/DictionaryFactory.java new file mode 100644 index 0000000..ee41ce9 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/dependencyinjection/DictionaryFactory.java @@ -0,0 +1,9 @@ +package me.whiteship.chapter01.item05.dependencyinjection; + +import me.whiteship.chapter01.item05.DefaultDictionary; + +public class DictionaryFactory { + public static DefaultDictionary get() { + return new DefaultDictionary(); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/dependencyinjection/SpellChecker.java b/src/main/java/me/whiteship/chapter01/item05/dependencyinjection/SpellChecker.java new file mode 100644 index 0000000..787b437 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/dependencyinjection/SpellChecker.java @@ -0,0 +1,29 @@ +package me.whiteship.chapter01.item05.dependencyinjection; + +import me.whiteship.chapter01.item05.Dictionary; + +import java.util.List; +import java.util.function.Supplier; + +public class SpellChecker { + + private final Dictionary dictionary; + + public SpellChecker(Dictionary dictionary) { + this.dictionary = dictionary; + } + + public SpellChecker(Supplier dictionarySupplier) { + this.dictionary = dictionarySupplier.get(); + } + + public boolean isValid(String word) { + // TODO 여기 SpellChecker 코드 + return dictionary.contains(word); + } + + public List suggestions(String typo) { + // TODO 여기 SpellChecker 코드 + return dictionary.closeWordsTo(typo); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/factorymethod/DefaultDictionaryFactory.java b/src/main/java/me/whiteship/chapter01/item05/factorymethod/DefaultDictionaryFactory.java new file mode 100644 index 0000000..ee40273 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/factorymethod/DefaultDictionaryFactory.java @@ -0,0 +1,11 @@ +package me.whiteship.chapter01.item05.factorymethod; + +import me.whiteship.chapter01.item05.DefaultDictionary; +import me.whiteship.chapter01.item05.Dictionary; + +public class DefaultDictionaryFactory implements DictionaryFactory { + @Override + public Dictionary getDictionary() { + return new DefaultDictionary(); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/factorymethod/DictionaryFactory.java b/src/main/java/me/whiteship/chapter01/item05/factorymethod/DictionaryFactory.java new file mode 100644 index 0000000..7964e30 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/factorymethod/DictionaryFactory.java @@ -0,0 +1,9 @@ +package me.whiteship.chapter01.item05.factorymethod; + +import me.whiteship.chapter01.item05.Dictionary; + +public interface DictionaryFactory { + + Dictionary getDictionary(); + +} diff --git a/src/main/java/me/whiteship/chapter01/item05/factorymethod/MockDictionaryFactory.java b/src/main/java/me/whiteship/chapter01/item05/factorymethod/MockDictionaryFactory.java new file mode 100644 index 0000000..d644636 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/factorymethod/MockDictionaryFactory.java @@ -0,0 +1,12 @@ +package me.whiteship.chapter01.item05.factorymethod; + +import me.whiteship.chapter01.item05.DefaultDictionary; +import me.whiteship.chapter01.item05.Dictionary; +import me.whiteship.chapter01.item05.MockDictionary; + +public class MockDictionaryFactory implements DictionaryFactory { + @Override + public Dictionary getDictionary() { + return new MockDictionary(); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/factorymethod/SpellChecker.java b/src/main/java/me/whiteship/chapter01/item05/factorymethod/SpellChecker.java new file mode 100644 index 0000000..eae6e99 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/factorymethod/SpellChecker.java @@ -0,0 +1,25 @@ +package me.whiteship.chapter01.item05.factorymethod; + +import me.whiteship.chapter01.item05.Dictionary; + +import java.util.List; + +public class SpellChecker { + + private Dictionary dictionary; + + public SpellChecker(DictionaryFactory dictionaryFactory) { + this.dictionary = dictionaryFactory.getDictionary(); + } + + public boolean isValid(String word) { + // TODO 여기 SpellChecker 코드 + return dictionary.contains(word); + } + + public List suggestions(String typo) { + // TODO 여기 SpellChecker 코드 + return dictionary.closeWordsTo(typo); + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item05/package-info.java b/src/main/java/me/whiteship/chapter01/item05/package-info.java new file mode 100644 index 0000000..e55642a --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/package-info.java @@ -0,0 +1 @@ +package me.whiteship.chapter01.item05; \ No newline at end of file diff --git a/src/main/java/me/whiteship/chapter01/item05/singleton/SpellChecker.java b/src/main/java/me/whiteship/chapter01/item05/singleton/SpellChecker.java new file mode 100644 index 0000000..d0dc41e --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/singleton/SpellChecker.java @@ -0,0 +1,25 @@ +package me.whiteship.chapter01.item05.singleton; + +import me.whiteship.chapter01.item05.DefaultDictionary; +import me.whiteship.chapter01.item05.Dictionary; + +import java.util.List; + +public class SpellChecker { + + private final Dictionary dictionary = new DefaultDictionary(); + + private SpellChecker() {} + + public static final SpellChecker INSTANCE = new SpellChecker(); + + public boolean isValid(String word) { + // TODO 여기 SpellChecker 코드 + return dictionary.contains(word); + } + + public List suggestions(String typo) { + // TODO 여기 SpellChecker 코드 + return dictionary.closeWordsTo(typo); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/springioc/App.java b/src/main/java/me/whiteship/chapter01/item05/springioc/App.java new file mode 100644 index 0000000..16290f7 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/springioc/App.java @@ -0,0 +1,13 @@ +package me.whiteship.chapter01.item05.springioc; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class App { + + public static void main(String[] args) { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); + SpellChecker spellChecker = applicationContext.getBean(SpellChecker.class); + spellChecker.isValid("test"); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/springioc/AppConfig.java b/src/main/java/me/whiteship/chapter01/item05/springioc/AppConfig.java new file mode 100644 index 0000000..efc1147 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/springioc/AppConfig.java @@ -0,0 +1,10 @@ +package me.whiteship.chapter01.item05.springioc; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackageClasses = AppConfig.class) +public class AppConfig { + +} diff --git a/src/main/java/me/whiteship/chapter01/item05/springioc/SpellChecker.java b/src/main/java/me/whiteship/chapter01/item05/springioc/SpellChecker.java new file mode 100644 index 0000000..fcec03a --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/springioc/SpellChecker.java @@ -0,0 +1,26 @@ +package me.whiteship.chapter01.item05.springioc; + +import me.whiteship.chapter01.item05.Dictionary; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class SpellChecker { + + private Dictionary dictionary; + + public SpellChecker(Dictionary dictionary) { + this.dictionary = dictionary; + } + + public boolean isValid(String word) { + // TODO 여기 SpellChecker 코드 + return dictionary.contains(word); + } + + public List suggestions(String typo) { + // TODO 여기 SpellChecker 코드 + return dictionary.closeWordsTo(typo); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/springioc/SpringDictionary.java b/src/main/java/me/whiteship/chapter01/item05/springioc/SpringDictionary.java new file mode 100644 index 0000000..948dd43 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/springioc/SpringDictionary.java @@ -0,0 +1,21 @@ +package me.whiteship.chapter01.item05.springioc; + +import me.whiteship.chapter01.item05.Dictionary; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class SpringDictionary implements Dictionary { + + @Override + public boolean contains(String word) { + System.out.println("contains " + word); + return false; + } + + @Override + public List closeWordsTo(String typo) { + return null; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item05/staticutils/SpellChecker.java b/src/main/java/me/whiteship/chapter01/item05/staticutils/SpellChecker.java new file mode 100644 index 0000000..73e17f2 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item05/staticutils/SpellChecker.java @@ -0,0 +1,23 @@ +package me.whiteship.chapter01.item05.staticutils; + +import me.whiteship.chapter01.item05.DefaultDictionary; +import me.whiteship.chapter01.item05.Dictionary; + +import java.util.List; + +public class SpellChecker { + + private static final Dictionary dictionary = new DefaultDictionary(); + + private SpellChecker() {} + + public static boolean isValid(String word) { + // TODO 여기 SpellChecker 코드 + return dictionary.contains(word); + } + + public static List suggestions(String typo) { + // TODO 여기 SpellChecker 코드 + return dictionary.closeWordsTo(typo); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item06/Client.java b/src/main/java/me/whiteship/chapter01/item06/Client.java new file mode 100644 index 0000000..248b6a2 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item06/Client.java @@ -0,0 +1,8 @@ +package me.whiteship.chapter01.item06; + +public class Client { + + public static void main(String[] args) { + Deprecation deprecation = new Deprecation("string"); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item06/Deprecation.java b/src/main/java/me/whiteship/chapter01/item06/Deprecation.java new file mode 100644 index 0000000..0f67d86 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item06/Deprecation.java @@ -0,0 +1,18 @@ +package me.whiteship.chapter01.item06; + +public class Deprecation { + + /** + * @deprecated in favor of + * {@link #Deprecation(String)} + */ + @Deprecated(forRemoval = true, since = "1.2") + public Deprecation() { + } + + private String name; + + public Deprecation(String name) { + this.name = name; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item06/RegularExpression.java b/src/main/java/me/whiteship/chapter01/item06/RegularExpression.java new file mode 100644 index 0000000..3c99ecd --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item06/RegularExpression.java @@ -0,0 +1,18 @@ +package me.whiteship.chapter01.item06; + +import java.util.regex.Pattern; + +public class RegularExpression { + + private static final Pattern SPLIT_PATTERN = Pattern.compile(","); + + public static void main(String[] args) { + long start = System.nanoTime(); + for (int j = 0; j < 10000; j++) { + String name = "keesun,whiteship"; + name.split(","); +// SPLIT_PATTERN.split(name); + } + System.out.println(System.nanoTime() - start); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item06/RomanNumerals.java b/src/main/java/me/whiteship/chapter01/item06/RomanNumerals.java new file mode 100644 index 0000000..f5a4dca --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item06/RomanNumerals.java @@ -0,0 +1,31 @@ +package me.whiteship.chapter01.item06; +import java.util.regex.Pattern; + +// 값비싼 객체를 재사용해 성능을 개선한다. (32쪽) +public class RomanNumerals { + // 코드 6-1 성능을 훨씬 더 끌어올릴 수 있다! + static boolean isRomanNumeralSlow(String s) { + return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); + } + + // 코드 6-2 값비싼 객체를 재사용해 성능을 개선한다. + private static final Pattern ROMAN = Pattern.compile( + "^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); + + static boolean isRomanNumeralFast(String s) { + return ROMAN.matcher(s).matches(); + } + + public static void main(String[] args) { + boolean result = false; + long start = System.nanoTime(); + for (int j = 0; j < 100; j++) { + //TODO 성능 차이를 확인하려면 xxxSlow 메서드를 xxxFast 메서드로 바꿔 실행해보자. + result = isRomanNumeralSlow("MCMLXXVI"); + } + long end = System.nanoTime(); + System.out.println(end - start); + System.out.println(result); + } +} + diff --git a/src/main/java/me/whiteship/chapter01/item06/Strings.java b/src/main/java/me/whiteship/chapter01/item06/Strings.java new file mode 100644 index 0000000..af7f15c --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item06/Strings.java @@ -0,0 +1,18 @@ +package me.whiteship.chapter01.item06; + +public class Strings { + + public static void main(String[] args) { + String hello = "hello"; + + //TODO 이 방법은 권장하지 않습니다. + String hello2 = new String("hello"); + + String hello3 = "hello"; + + System.out.println(hello == hello2); + System.out.println(hello.equals(hello2)); + System.out.println(hello == hello3); + System.out.println(hello.equals(hello3)); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item06/Sum.java b/src/main/java/me/whiteship/chapter01/item06/Sum.java new file mode 100644 index 0000000..cd71be4 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item06/Sum.java @@ -0,0 +1,19 @@ +package me.whiteship.chapter01.item06; + +public class Sum { + private static long sum() { + // TODO Long을 long으로 변경하여 실행해 보세요. + Long sum = 0L; + for (long i = 0; i <= Integer.MAX_VALUE; i++) + sum += i; + return sum; + } + + public static void main(String[] args) { + long start = System.nanoTime(); + long x = sum(); + long end = System.nanoTime(); + System.out.println((end - start) / 1_000_000. + " ms."); + System.out.println(x); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/cache/CacheKey.java b/src/main/java/me/whiteship/chapter01/item07/cache/CacheKey.java new file mode 100644 index 0000000..6ac4ee0 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/cache/CacheKey.java @@ -0,0 +1,37 @@ +package me.whiteship.chapter01.item07.cache; + +import java.time.LocalDateTime; + +public class CacheKey { + + private Integer value; + + private LocalDateTime created; + + public CacheKey(Integer value) { + this.value = value; + this.created = LocalDateTime.now(); + } + + @Override + public boolean equals(Object o) { + return this.value.equals(o); + } + + @Override + public int hashCode() { + return this.value.hashCode(); + } + + public LocalDateTime getCreated() { + return created; + } + + @Override + public String toString() { + return "CacheKey{" + + "value=" + value + + ", created=" + created + + '}'; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/cache/Post.java b/src/main/java/me/whiteship/chapter01/item07/cache/Post.java new file mode 100644 index 0000000..344a0f0 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/cache/Post.java @@ -0,0 +1,39 @@ +package me.whiteship.chapter01.item07.cache; + +public class Post { + + private Integer id; + + private String title; + + private String content; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + @Override + public void finalize() { + System.out.println("gc called"); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/cache/PostRepository.java b/src/main/java/me/whiteship/chapter01/item07/cache/PostRepository.java new file mode 100644 index 0000000..792923d --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/cache/PostRepository.java @@ -0,0 +1,29 @@ +package me.whiteship.chapter01.item07.cache; + +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + +public class PostRepository { + + private Map cache; + + public PostRepository() { + this.cache = new WeakHashMap<>(); + } + + public Post getPostById(CacheKey key) { + if (cache.containsKey(key)) { + return cache.get(key); + } else { + // TODO DB에서 읽어오거나 REST API를 통해 읽어올 수 있습니다. + Post post = new Post(); + cache.put(key, post); + return post; + } + } + + public Map getCache() { + return cache; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/executor/ExecutorsExample.java b/src/main/java/me/whiteship/chapter01/item07/executor/ExecutorsExample.java new file mode 100644 index 0000000..d5ed3ad --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/executor/ExecutorsExample.java @@ -0,0 +1,31 @@ +package me.whiteship.chapter01.item07.executor; + +import me.whiteship.chapter01.item01.Product; + +import java.util.concurrent.*; + +public class ExecutorsExample { + + public static void main(String[] args) throws ExecutionException, InterruptedException { + ExecutorService service = Executors.newFixedThreadPool(10); + + Future submit = service.submit(new Task()); + + System.out.println(Thread.currentThread() + " hello"); + + System.out.println(submit.get()); + + service.shutdown(); + } + + static class Task implements Callable { + + @Override + public String call() throws Exception { + Thread.sleep(2000L); + return Thread.currentThread() + " world"; + } + } + + +} diff --git a/src/main/java/me/whiteship/chapter01/item07/listener/ChatRoom.java b/src/main/java/me/whiteship/chapter01/item07/listener/ChatRoom.java new file mode 100644 index 0000000..9561f7b --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/listener/ChatRoom.java @@ -0,0 +1,27 @@ +package me.whiteship.chapter01.item07.listener; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class ChatRoom { + + private List> users; + + public ChatRoom() { + this.users = new ArrayList<>(); + } + + public void addUser(User user) { + this.users.add(new WeakReference<>(user)); + } + + public void sendMessage(String message) { + users.forEach(wr -> Objects.requireNonNull(wr.get()).receive(message)); + } + + public List> getUsers() { + return users; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/listener/User.java b/src/main/java/me/whiteship/chapter01/item07/listener/User.java new file mode 100644 index 0000000..c721af8 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/listener/User.java @@ -0,0 +1,8 @@ +package me.whiteship.chapter01.item07.listener; + +public class User { + + public void receive(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/optional/Channel.java b/src/main/java/me/whiteship/chapter01/item07/optional/Channel.java new file mode 100644 index 0000000..095618d --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/optional/Channel.java @@ -0,0 +1,17 @@ +package me.whiteship.chapter01.item07.optional; + +import java.util.Optional; +import java.util.OptionalLong; + +public class Channel { + + private int numOfSubscribers; + + public Optional defaultMemberShip() { + if (this.numOfSubscribers < 2000) { + return Optional.empty(); + } else { + return Optional.of(new MemberShip()); + } + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/optional/MemberShip.java b/src/main/java/me/whiteship/chapter01/item07/optional/MemberShip.java new file mode 100644 index 0000000..a384c43 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/optional/MemberShip.java @@ -0,0 +1,8 @@ +package me.whiteship.chapter01.item07.optional; + +public class MemberShip { + + public String hello() { + return "hello"; + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/reference/BigObject.java b/src/main/java/me/whiteship/chapter01/item07/reference/BigObject.java new file mode 100644 index 0000000..41cab6e --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/reference/BigObject.java @@ -0,0 +1,4 @@ +package me.whiteship.chapter01.item07.reference; + +public class BigObject { +} diff --git a/src/main/java/me/whiteship/chapter01/item07/reference/BigObjectReference.java b/src/main/java/me/whiteship/chapter01/item07/reference/BigObjectReference.java new file mode 100644 index 0000000..faa311a --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/reference/BigObjectReference.java @@ -0,0 +1,15 @@ +package me.whiteship.chapter01.item07.reference; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; + +public class BigObjectReference extends PhantomReference { + + public BigObjectReference(BigObject referent, ReferenceQueue q) { + super(referent, q); + } + + public void cleanUp() { + System.out.println("clean up"); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/reference/PhantomReferenceExample.java b/src/main/java/me/whiteship/chapter01/item07/reference/PhantomReferenceExample.java new file mode 100644 index 0000000..a025ccc --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/reference/PhantomReferenceExample.java @@ -0,0 +1,28 @@ +package me.whiteship.chapter01.item07.reference; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; + +public class PhantomReferenceExample { + + public static void main(String[] args) throws InterruptedException { + BigObject strong = new BigObject(); + ReferenceQueue rq = new ReferenceQueue<>(); + + BigObjectReference phantom = new BigObjectReference<>(strong, rq); + strong = null; + + System.gc(); + Thread.sleep(3000L); + + // TODO 팬텀은 유령이니까.. + // 죽었지만.. 사라지진 않고 큐에 들어갑니다. + System.out.println(phantom.isEnqueued()); + + Reference reference = rq.poll(); + BigObjectReference bigObjectCleaner = (BigObjectReference) reference; + bigObjectCleaner.cleanUp(); + reference.clear(); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/reference/SoftReferenceExample.java b/src/main/java/me/whiteship/chapter01/item07/reference/SoftReferenceExample.java new file mode 100644 index 0000000..a89393e --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/reference/SoftReferenceExample.java @@ -0,0 +1,19 @@ +package me.whiteship.chapter01.item07.reference; + +import java.lang.ref.SoftReference; + +public class SoftReferenceExample { + + public static void main(String[] args) throws InterruptedException { + Object strong = new Object(); + SoftReference soft = new SoftReference<>(strong); + strong = null; + + System.gc(); + Thread.sleep(3000L); + + // TODO 거의 안 없어집니다. + // 왜냐면 메모리가 충분해서.. 굳이 제거할 필요가 없으니까요. + System.out.println(soft.get()); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/reference/WeakReferenceExample.java b/src/main/java/me/whiteship/chapter01/item07/reference/WeakReferenceExample.java new file mode 100644 index 0000000..32a5d70 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/reference/WeakReferenceExample.java @@ -0,0 +1,19 @@ +package me.whiteship.chapter01.item07.reference; + +import java.lang.ref.WeakReference; + +public class WeakReferenceExample { + + public static void main(String[] args) throws InterruptedException { + Object strong = new Object(); + WeakReference weak = new WeakReference<>(strong); + strong = null; + + System.gc(); + Thread.sleep(3000L); + + // TODO 거의 없어집니다. + // 왜냐면 약하니까(?)... + System.out.println(weak.get()); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item07/stack/EmptyStackException.java b/src/main/java/me/whiteship/chapter01/item07/stack/EmptyStackException.java new file mode 100644 index 0000000..b6147c4 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/stack/EmptyStackException.java @@ -0,0 +1,5 @@ +package me.whiteship.chapter01.item07.stack; + +// (36쪽의 Stack 코드에서 던지는 예외) +public class EmptyStackException extends IllegalStateException { +} diff --git a/src/main/java/me/whiteship/chapter01/item07/stack/Stack.java b/src/main/java/me/whiteship/chapter01/item07/stack/Stack.java new file mode 100644 index 0000000..98618aa --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item07/stack/Stack.java @@ -0,0 +1,52 @@ +package me.whiteship.chapter01.item07.stack; + +import java.util.Arrays; + +// 코드 7-1 메모리 누수가 일어나는 위치는 어디인가? (36쪽) +public class Stack { + private Object[] elements; + private int size = 0; + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + public Stack() { + elements = new Object[DEFAULT_INITIAL_CAPACITY]; + } + + public void push(Object e) { + ensureCapacity(); + elements[size++] = e; + } + +// public Object pop() { +// if (size == 0) +// throw new EmptyStackException(); +// return elements[--size]; +// } + + /** + * 원소를 위한 공간을 적어도 하나 이상 확보한다. + * 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다. + */ + private void ensureCapacity() { + if (elements.length == size) + elements = Arrays.copyOf(elements, 2 * size + 1); + } + + // 코드 7-2 제대로 구현한 pop 메서드 (37쪽) + public Object pop() { + if (size == 0) + throw new EmptyStackException(); + Object result = elements[--size]; + elements[size] = null; // 다 쓴 참조 해제 + return result; + } + + public static void main(String[] args) { + Stack stack = new Stack(); + for (String arg : args) + stack.push(arg); + + while (true) + System.err.println(stack.pop()); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item08/autoclosable/App.java b/src/main/java/me/whiteship/chapter01/item08/autoclosable/App.java new file mode 100644 index 0000000..c234747 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/autoclosable/App.java @@ -0,0 +1,11 @@ +package me.whiteship.chapter01.item08.autoclosable; + +public class App { + + public static void main(String[] args) { + try(AutoClosableIsGood good = new AutoClosableIsGood("")) { + // TODO 자원 반납 처리가 됨. + + } + } +} diff --git a/src/main/java/me/whiteship/chapter01/item08/autoclosable/AutoClosableIsGood.java b/src/main/java/me/whiteship/chapter01/item08/autoclosable/AutoClosableIsGood.java new file mode 100644 index 0000000..bf9f931 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/autoclosable/AutoClosableIsGood.java @@ -0,0 +1,25 @@ +package me.whiteship.chapter01.item08.autoclosable; + +import java.io.*; + +public class AutoClosableIsGood implements Closeable { + + private BufferedReader reader; + + public AutoClosableIsGood(String path) { + try { + this.reader = new BufferedReader(new FileReader(path)); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException(path); + } + } + + @Override + public void close() { + try { + reader.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/me/whiteship/chapter01/item08/cleaner/BigObject.java b/src/main/java/me/whiteship/chapter01/item08/cleaner/BigObject.java new file mode 100644 index 0000000..731d302 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/cleaner/BigObject.java @@ -0,0 +1,27 @@ +package me.whiteship.chapter01.item08.cleaner; + +import java.util.List; + +public class BigObject { + + private List resource; + + public BigObject(List resource) { + this.resource = resource; + } + + public static class ResourceCleaner implements Runnable { + + private List resourceToClean; + + public ResourceCleaner(List resourceToClean) { + this.resourceToClean = resourceToClean; + } + + @Override + public void run() { + resourceToClean = null; + System.out.println("cleaned up."); + } + } +} diff --git a/src/main/java/me/whiteship/chapter01/item08/cleaner/CleanerIsNotGood.java b/src/main/java/me/whiteship/chapter01/item08/cleaner/CleanerIsNotGood.java new file mode 100644 index 0000000..fb9736b --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/cleaner/CleanerIsNotGood.java @@ -0,0 +1,21 @@ +package me.whiteship.chapter01.item08.cleaner; + +import java.lang.ref.Cleaner; +import java.util.ArrayList; +import java.util.List; + +public class CleanerIsNotGood { + + public static void main(String[] args) throws InterruptedException { + Cleaner cleaner = Cleaner.create(); + + List resourceToCleanUp = new ArrayList<>(); + BigObject bigObject = new BigObject(resourceToCleanUp); + cleaner.register(bigObject, new BigObject.ResourceCleaner(resourceToCleanUp)); + + bigObject = null; + System.gc(); + Thread.sleep(3000L); + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Adult.java b/src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Adult.java new file mode 100644 index 0000000..475d68d --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Adult.java @@ -0,0 +1,10 @@ +package me.whiteship.chapter01.item08.cleaner_as_a_safetynet; + +// cleaner 안전망을 갖춘 자원을 제대로 활용하는 클라이언트 (45쪽) +public class Adult { + public static void main(String[] args) { + try (Room myRoom = new Room(7)) { + System.out.println("안녕~"); + } + } +} diff --git a/src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Room.java b/src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Room.java new file mode 100644 index 0000000..c8046ec --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Room.java @@ -0,0 +1,38 @@ +package me.whiteship.chapter01.item08.cleaner_as_a_safetynet; + +import java.lang.ref.Cleaner; + +// 코드 8-1 cleaner를 안전망으로 활용하는 AutoCloseable 클래스 (44쪽) +public class Room implements AutoCloseable { + private static final Cleaner cleaner = Cleaner.create(); + + // 청소가 필요한 자원. 절대 Room을 참조해서는 안 된다! + private static class State implements Runnable { + int numJunkPiles; // Number of junk piles in this room + + State(int numJunkPiles) { + this.numJunkPiles = numJunkPiles; + } + + // close 메서드나 cleaner가 호출한다. + @Override public void run() { + System.out.println("Cleaning room"); + numJunkPiles = 0; + } + } + + // 방의 상태. cleanable과 공유한다. + private final State state; + + // cleanable 객체. 수거 대상이 되면 방을 청소한다. + private final Cleaner.Cleanable cleanable; + + public Room(int numJunkPiles) { + state = new State(numJunkPiles); + cleanable = cleaner.register(this, state); + } + + @Override public void close() { + cleanable.clean(); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Teenager.java b/src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Teenager.java new file mode 100644 index 0000000..0cd7021 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/cleaner_as_a_safetynet/Teenager.java @@ -0,0 +1,14 @@ +package me.whiteship.chapter01.item08.cleaner_as_a_safetynet; + +// cleaner 안전망을 갖춘 자원을 제대로 활용하지 못하는 클라이언트 (45쪽) +public class Teenager { + + public static void main(String[] args) { + new Room(99); + System.out.println("Peace out"); + + // 다음 줄의 주석을 해제한 후 동작을 다시 확인해보자. + // 단, 가비지 컬렉러를 강제로 호출하는 이런 방식에 의존해서는 절대 안 된다! + System.gc(); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item08/finalizer/App.java b/src/main/java/me/whiteship/chapter01/item08/finalizer/App.java new file mode 100644 index 0000000..a8ac0cc --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/finalizer/App.java @@ -0,0 +1,36 @@ +package me.whiteship.chapter01.item08.finalizer; + +import com.sun.management.UnixOperatingSystemMXBean; +import org.springframework.jmx.support.MBeanServerFactoryBean; + +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import java.lang.management.OperatingSystemMXBean; +import java.lang.ref.ReferenceQueue; +import java.lang.reflect.Field; + +public class App { + + /** + * 코드 참고 https://www.baeldung.com/java-finalize + */ + public static void main(String[] args) throws InterruptedException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { + int i = 0; + while(true) { + i++; + new FinalizerIsBad(); + + if ((i % 1_000_000) == 0) { + Class finalizerClass = Class.forName("java.lang.ref.Finalizer"); + Field queueStaticField = finalizerClass.getDeclaredField("queue"); + queueStaticField.setAccessible(true); + ReferenceQueue referenceQueue = (ReferenceQueue) queueStaticField.get(null); + + Field queueLengthField = ReferenceQueue.class.getDeclaredField("queueLength"); + queueLengthField.setAccessible(true); + long queueLength = (long) queueLengthField.get(referenceQueue); + System.out.format("There are %d references in the queue%n", queueLength); + } + } + } +} diff --git a/src/main/java/me/whiteship/chapter01/item08/finalizer/FinalizerIsBad.java b/src/main/java/me/whiteship/chapter01/item08/finalizer/FinalizerIsBad.java new file mode 100644 index 0000000..04c5c20 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/finalizer/FinalizerIsBad.java @@ -0,0 +1,9 @@ +package me.whiteship.chapter01.item08.finalizer; + +public class FinalizerIsBad { + + @Override + protected void finalize() throws Throwable { + System.out.print(""); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item08/finalizer_attack/Account.java b/src/main/java/me/whiteship/chapter01/item08/finalizer_attack/Account.java new file mode 100644 index 0000000..fc04278 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/finalizer_attack/Account.java @@ -0,0 +1,21 @@ +package me.whiteship.chapter01.item08.finalizer_attack; + +import java.math.BigDecimal; + +public class Account { + + private String accountId; + + public Account(String accountId) { + this.accountId = accountId; + + if (accountId.equals("푸틴")) { + throw new IllegalArgumentException("푸틴은 계정을 막습니다."); + } + } + + public void transfer(BigDecimal amount, String to) { + System.out.printf("transfer %f from %s to %s\n", amount, accountId, to); + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item08/finalizer_attack/BrokenAccount.java b/src/main/java/me/whiteship/chapter01/item08/finalizer_attack/BrokenAccount.java new file mode 100644 index 0000000..886e192 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/finalizer_attack/BrokenAccount.java @@ -0,0 +1,16 @@ +package me.whiteship.chapter01.item08.finalizer_attack; + +import java.math.BigDecimal; + +public class BrokenAccount extends Account { + + public BrokenAccount(String accountId) { + super(accountId); + } + + @Override + protected void finalize() throws Throwable { + this.transfer(BigDecimal.valueOf(100), "keesun"); + } +} + diff --git a/src/main/java/me/whiteship/chapter01/item08/outerclass/LambdaExample.java b/src/main/java/me/whiteship/chapter01/item08/outerclass/LambdaExample.java new file mode 100644 index 0000000..de58cb9 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/outerclass/LambdaExample.java @@ -0,0 +1,22 @@ +package me.whiteship.chapter01.item08.outerclass; + +import java.lang.reflect.Field; + +public class LambdaExample { + + private int value = 10; + + private Runnable instanceLambda = () -> { + System.out.println(value); + }; + + public static void main(String[] args) { + LambdaExample example = new LambdaExample(); + Field[] declaredFields = example.instanceLambda.getClass().getDeclaredFields(); + for (Field field : declaredFields) { + System.out.println("field type: " + field.getType()); + System.out.println("field name: " + field.getName()); + } + } + +} diff --git a/src/main/java/me/whiteship/chapter01/item08/outerclass/OuterClass.java b/src/main/java/me/whiteship/chapter01/item08/outerclass/OuterClass.java new file mode 100644 index 0000000..d62bcf4 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item08/outerclass/OuterClass.java @@ -0,0 +1,35 @@ +package me.whiteship.chapter01.item08.outerclass; + +import java.lang.reflect.Field; + +public class OuterClass { + + private void hi() { + + } + + class InnerClass { + + public void hello() { + OuterClass.this.hi(); + } + + } + + public static void main(String[] args) { + OuterClass outerClass = new OuterClass(); + InnerClass innerClass = outerClass.new InnerClass(); + + System.out.println(innerClass); + + outerClass.printFiled(); + } + + private void printFiled() { + Field[] declaredFields = InnerClass.class.getDeclaredFields(); + for(Field field : declaredFields) { + System.out.println("field type:" + field.getType()); + System.out.println("field name:" + field.getName()); + } + } +} diff --git a/src/main/java/me/whiteship/chapter01/item09/puzzler/Copy.java b/src/main/java/me/whiteship/chapter01/item09/puzzler/Copy.java new file mode 100644 index 0000000..c6b1dfe --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item09/puzzler/Copy.java @@ -0,0 +1,37 @@ +package me.whiteship.chapter01.item09.puzzler; + +import java.io.*; + +public class Copy { + private static final int BUFFER_SIZE = 8 * 1024; + + // 코드 9-2 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다! (47쪽) + static void copy(String src, String dst) throws IOException { + InputStream in = new FileInputStream(src); + OutputStream out = new FileOutputStream(dst); + try { + byte[] buf = new byte[BUFFER_SIZE]; + int n; + while ((n = in.read(buf)) >= 0) + out.write(buf, 0, n); + } finally { + try { + out.close(); + } catch (IOException e) { + // TODO 이렇게 하면 되는거 아닌가? + } + + try { + in.close(); + } catch (IOException e) { + // TODO 안전한가? + } + } + } + + public static void main(String[] args) throws IOException { + String src = args[0]; + String dst = args[1]; + copy(src, dst); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item09/suppress/BadBufferedReader.java b/src/main/java/me/whiteship/chapter01/item09/suppress/BadBufferedReader.java new file mode 100644 index 0000000..e532ce8 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item09/suppress/BadBufferedReader.java @@ -0,0 +1,23 @@ +package me.whiteship.chapter01.item09.suppress; + +import java.io.*; + +public class BadBufferedReader extends BufferedReader { + public BadBufferedReader(Reader in, int sz) { + super(in, sz); + } + + public BadBufferedReader(Reader in) { + super(in); + } + + @Override + public String readLine() throws IOException { + throw new CharConversionException(); + } + + @Override + public void close() throws IOException { + throw new StreamCorruptedException(); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item09/suppress/TopLine.java b/src/main/java/me/whiteship/chapter01/item09/suppress/TopLine.java new file mode 100644 index 0000000..160fa27 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item09/suppress/TopLine.java @@ -0,0 +1,18 @@ +package me.whiteship.chapter01.item09.suppress; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; + +public class TopLine { + // 코드 9-1 try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다! (47쪽) + static String firstLineOfFile(String path) throws IOException { + try(BufferedReader br = new BadBufferedReader(new FileReader(path))) { + return br.readLine(); + } + } + + public static void main(String[] args) throws IOException { + System.out.println(firstLineOfFile("pom.xml")); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item09/tryfinally/Copy.java b/src/main/java/me/whiteship/chapter01/item09/tryfinally/Copy.java new file mode 100644 index 0000000..63ae3dd --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item09/tryfinally/Copy.java @@ -0,0 +1,31 @@ +package me.whiteship.chapter01.item09.tryfinally; + +import java.io.*; + +public class Copy { + private static final int BUFFER_SIZE = 8 * 1024; + + // 코드 9-2 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다! (47쪽) + static void copy(String src, String dst) throws IOException { + InputStream in = new FileInputStream(src); + try { + OutputStream out = new FileOutputStream(dst); + try { + byte[] buf = new byte[BUFFER_SIZE]; + int n; + while ((n = in.read(buf)) >= 0) + out.write(buf, 0, n); + } finally { + out.close(); + } + } finally { + in.close(); + } + } + + public static void main(String[] args) throws IOException { + String src = args[0]; + String dst = args[1]; + copy(src, dst); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item09/tryfinally/TopLine.java b/src/main/java/me/whiteship/chapter01/item09/tryfinally/TopLine.java new file mode 100644 index 0000000..52c55e8 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item09/tryfinally/TopLine.java @@ -0,0 +1,22 @@ +package me.whiteship.chapter01.item09.tryfinally; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; + +public class TopLine { + // 코드 9-1 try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다! (47쪽) + static String firstLineOfFile(String path) throws IOException { + BufferedReader br = new BufferedReader(new FileReader(path)); + try { + return br.readLine(); + } finally { + br.close(); + } + } + + public static void main(String[] args) throws IOException { + String path = args[0]; + System.out.println(firstLineOfFile(path)); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item09/trywithresources/Copy.java b/src/main/java/me/whiteship/chapter01/item09/trywithresources/Copy.java new file mode 100644 index 0000000..87083b6 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item09/trywithresources/Copy.java @@ -0,0 +1,24 @@ +package me.whiteship.chapter01.item09.trywithresources; + +import java.io.*; + +public class Copy { + private static final int BUFFER_SIZE = 8 * 1024; + + // 코드 9-4 복수의 자원을 처리하는 try-with-resources - 짧고 매혹적이다! (49쪽) + static void copy(String src, String dst) throws IOException { + try (InputStream in = new FileInputStream(src); + OutputStream out = new FileOutputStream(dst)) { + byte[] buf = new byte[BUFFER_SIZE]; + int n; + while ((n = in.read(buf)) >= 0) + out.write(buf, 0, n); + } + } + + public static void main(String[] args) throws IOException { + String src = args[0]; + String dst = args[1]; + copy(src, dst); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item09/trywithresources/TopLine.java b/src/main/java/me/whiteship/chapter01/item09/trywithresources/TopLine.java new file mode 100644 index 0000000..d088f44 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item09/trywithresources/TopLine.java @@ -0,0 +1,21 @@ +package me.whiteship.chapter01.item09.trywithresources; + + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; + +public class TopLine { + // 코드 9-3 try-with-resources - 자원을 회수하는 최선책! (48쪽) + static String firstLineOfFile(String path) throws IOException { + try (BufferedReader br = new BufferedReader( + new FileReader(path))) { + return br.readLine(); + } + } + + public static void main(String[] args) throws IOException { + String path = args[0]; + System.out.println(firstLineOfFile(path)); + } +} diff --git a/src/main/java/me/whiteship/chapter01/item09/trywithresources/TopLineWithDefault.java b/src/main/java/me/whiteship/chapter01/item09/trywithresources/TopLineWithDefault.java new file mode 100644 index 0000000..bbd8d39 --- /dev/null +++ b/src/main/java/me/whiteship/chapter01/item09/trywithresources/TopLineWithDefault.java @@ -0,0 +1,23 @@ +package me.whiteship.chapter01.item09.trywithresources; + + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; + +public class TopLineWithDefault { + // 코드 9-5 try-with-resources를 catch 절과 함께 쓰는 모습 (49쪽) + static String firstLineOfFile(String path, String defaultVal) { + try (BufferedReader br = new BufferedReader( + new FileReader(path))) { + return br.readLine(); + } catch (IOException e) { + return defaultVal; + } + } + + public static void main(String[] args) throws IOException { + String path = args[0]; + System.out.println(firstLineOfFile(path, "Toppy McTopFace")); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/CaseInsensitiveString.java b/src/main/java/me/whiteship/chapter02/item10/CaseInsensitiveString.java new file mode 100644 index 0000000..7d682d7 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/CaseInsensitiveString.java @@ -0,0 +1,44 @@ +package me.whiteship.chapter02.item10; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +// 코드 10-1 잘못된 코드 - 대칭성 위배! (54-55쪽) +public final class CaseInsensitiveString { + private final String s; + + public CaseInsensitiveString(String s) { + this.s = Objects.requireNonNull(s); + } + +// 대칭성 위배! + @Override public boolean equals(Object o) { + if (o instanceof CaseInsensitiveString) + return s.equalsIgnoreCase( + ((CaseInsensitiveString) o).s); + if (o instanceof String) // 한 방향으로만 작동한다! + return s.equalsIgnoreCase((String) o); + return false; + } + + // 문제 시연 (55쪽) + public static void main(String[] args) { + CaseInsensitiveString cis = new CaseInsensitiveString("Polish"); +// CaseInsensitiveString cis2 = new CaseInsensitiveString("polish"); + String polish = "polish"; + System.out.println(cis.equals(polish)); +// System.out.println(cis2.equals(cis)); + + List list = new ArrayList<>(); + list.add(cis); + + System.out.println(list.contains(polish)); + } + + // 수정한 equals 메서드 (56쪽) +// @Override public boolean equals(Object o) { +// return o instanceof CaseInsensitiveString && +// ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); +// } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/Color.java b/src/main/java/me/whiteship/chapter02/item10/Color.java new file mode 100644 index 0000000..1d3e793 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/Color.java @@ -0,0 +1,3 @@ +package me.whiteship.chapter02.item10; + +public enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET } diff --git a/src/main/java/me/whiteship/chapter02/item10/EqualsInJava.java b/src/main/java/me/whiteship/chapter02/item10/EqualsInJava.java new file mode 100644 index 0000000..ecf3848 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/EqualsInJava.java @@ -0,0 +1,24 @@ +package me.whiteship.chapter02.item10; + +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.Timestamp; +import java.util.Date; + +public class EqualsInJava extends Object { + + public static void main(String[] args) throws MalformedURLException { + long time = System.currentTimeMillis(); + Timestamp timestamp = new Timestamp(time); + Date date = new Date(time); + + // 대칭성 위배! P60 + System.out.println(date.equals(timestamp)); + System.out.println(timestamp.equals(date)); + + // 일관성 위배 가능성 있음. P61 + URL google1 = new URL("https", "about.google", "/products/"); + URL google2 = new URL("https", "about.google", "/products/"); + System.out.println(google1.equals(google2)); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/PhoneNumber.java b/src/main/java/me/whiteship/chapter02/item10/PhoneNumber.java new file mode 100644 index 0000000..cbd3b58 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/PhoneNumber.java @@ -0,0 +1,30 @@ +package me.whiteship.chapter02.item10; + +// 코드 10-6 전형적인 equals 메서드의 예 (64쪽) +public final class PhoneNumber { + private final short areaCode, prefix, lineNum; + + public PhoneNumber(int areaCode, int prefix, int lineNum) { + this.areaCode = rangeCheck(areaCode, 999, "지역코드"); + this.prefix = rangeCheck(prefix, 999, "프리픽스"); + this.lineNum = rangeCheck(lineNum, 9999, "가입자 번호"); + } + + private static short rangeCheck(int val, int max, String arg) { + if (val < 0 || val > max) + throw new IllegalArgumentException(arg + ": " + val); + return (short) val; + } + + @Override public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof PhoneNumber)) + return false; + PhoneNumber pn = (PhoneNumber)o; + return pn.lineNum == lineNum && pn.prefix == prefix + && pn.areaCode == areaCode; + } + + // 나머지 코드는 생략 - hashCode 메서드는 꼭 필요하다(아이템 11)! +} diff --git a/src/main/java/me/whiteship/chapter02/item10/Point.java b/src/main/java/me/whiteship/chapter02/item10/Point.java new file mode 100644 index 0000000..eb25a78 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/Point.java @@ -0,0 +1,49 @@ +package me.whiteship.chapter02.item10; + +import java.util.ArrayList; +import java.util.List; + +// 단순한 불변 2차원 정수 점(point) 클래스 (56쪽) +public class Point { + + private final int x; + private final int y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + @Override public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof Point)) { + return false; + } + + Point p = (Point) o; + return p.x == x && p.y == y; + } + + public static void main(String[] args) { + Point point = new Point(1, 2); + List points = new ArrayList<>(); + points.add(point); + System.out.println(points.contains(new Point(1, 2))); + } + + // 잘못된 코드 - 리스코프 치환 원칙 위배! (59쪽) +// @Override public boolean equals(Object o) { +// if (o == null || o.getClass() != getClass()) +// return false; +// Point p = (Point) o; +// return p.x == x && p.y == y; +// } + + // 아이템 11 참조 + @Override public int hashCode() { + return 31 * x + y; + } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/autovalue/AutoValueTest.java b/src/main/java/me/whiteship/chapter02/item10/autovalue/AutoValueTest.java new file mode 100644 index 0000000..dcb73bd --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/autovalue/AutoValueTest.java @@ -0,0 +1,9 @@ +package me.whiteship.chapter02.item10.autovalue; + +public class AutoValueTest { + + public static void main(String[] args) { + Point point = Point.create(1, 2); + System.out.println(point.equals(Point.create(1, 2))); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/autovalue/Point.java b/src/main/java/me/whiteship/chapter02/item10/autovalue/Point.java new file mode 100644 index 0000000..f8c0727 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/autovalue/Point.java @@ -0,0 +1,18 @@ +package me.whiteship.chapter02.item10.autovalue; + +import com.google.auto.value.AutoValue; + +/** + * AutoValue 참고 + * + * https://github.com/google/auto/blob/master/value/userguide/index.md + */ +@AutoValue +abstract class Point { + static Point create(int x, int y) { + return new AutoValue_Point(x, y); + } + + abstract int x(); + abstract int y(); +} diff --git a/src/main/java/me/whiteship/chapter02/item10/composition/ColorPoint.java b/src/main/java/me/whiteship/chapter02/item10/composition/ColorPoint.java new file mode 100644 index 0000000..be5e4f2 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/composition/ColorPoint.java @@ -0,0 +1,36 @@ +package me.whiteship.chapter02.item10.composition; + + +import me.whiteship.chapter02.item10.Color; +import me.whiteship.chapter02.item10.Point; + +import java.util.Objects; + +// 코드 10-5 equals 규약을 지키면서 값 추가하기 (60쪽) +public class ColorPoint { + private final Point point; + private final Color color; + + public ColorPoint(int x, int y, Color color) { + point = new Point(x, y); + this.color = Objects.requireNonNull(color); + } + + /** + * 이 ColorPoint의 Point 뷰를 반환한다. + */ + public Point asPoint() { + return point; + } + + @Override public boolean equals(Object o) { + if (!(o instanceof ColorPoint)) + return false; + ColorPoint cp = (ColorPoint) o; + return cp.point.equals(point) && cp.color.equals(color); + } + + @Override public int hashCode() { + return 31 * point.hashCode() + color.hashCode(); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/inheritance/ColorPoint.java b/src/main/java/me/whiteship/chapter02/item10/inheritance/ColorPoint.java new file mode 100644 index 0000000..598124e --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/inheritance/ColorPoint.java @@ -0,0 +1,48 @@ +package me.whiteship.chapter02.item10.inheritance; + +import me.whiteship.chapter02.item10.Color; +import me.whiteship.chapter02.item10.Point; + +// Point에 값 컴포넌트(color)를 추가 (56쪽) +public class ColorPoint extends Point { + private final Color color; + + public ColorPoint(int x, int y, Color color) { + super(x, y); + this.color = color; + } + + // 코드 10-2 잘못된 코드 - 대칭성 위배! (57쪽) +// @Override public boolean equals(Object o) { +// if (!(o instanceof ColorPoint)) +// return false; +// return super.equals(o) && ((ColorPoint) o).color == color; +// } + +// // 코드 10-3 잘못된 코드 - 추이성 위배! (57쪽) + @Override public boolean equals(Object o) { + if (!(o instanceof Point)) + return false; + + // o가 일반 Point면 색상을 무시하고 비교한다. + if (!(o instanceof ColorPoint)) + return o.equals(this); + + // o가 ColorPoint면 색상까지 비교한다. + return super.equals(o) && ((ColorPoint) o).color == color; + } + + public static void main(String[] args) { + // 첫 번째 equals 메서드(코드 10-2)는 대칭성을 위배한다. (57쪽) +// Point p = new Point(1, 2); +// ColorPoint cp = new ColorPoint(1, 2, Color.RED); +// System.out.println(p.equals(cp) + " " + cp.equals(p)); + + // 두 번째 equals 메서드(코드 10-3)는 추이성을 위배한다. (57쪽) + ColorPoint p1 = new ColorPoint(1, 2, Color.RED); + Point p2 = new Point(1, 2); + ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE); + System.out.printf("%s %s %s%n", + p1.equals(p2), p2.equals(p3), p1.equals(p3)); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/inheritance/CounterPoint.java b/src/main/java/me/whiteship/chapter02/item10/inheritance/CounterPoint.java new file mode 100644 index 0000000..b26cdad --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/inheritance/CounterPoint.java @@ -0,0 +1,18 @@ +package me.whiteship.chapter02.item10.inheritance; + + +import me.whiteship.chapter02.item10.Point; + +import java.util.concurrent.atomic.AtomicInteger; + +// Point의 평범한 하위 클래스 - 값 컴포넌트를 추가하지 않았다. (59쪽) +public class CounterPoint extends Point { + private static final AtomicInteger counter = + new AtomicInteger(); + + public CounterPoint(int x, int y) { + super(x, y); + counter.incrementAndGet(); + } + public static int numberCreated() { return counter.get(); } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/inheritance/CounterPointTest.java b/src/main/java/me/whiteship/chapter02/item10/inheritance/CounterPointTest.java new file mode 100644 index 0000000..7c2c3fe --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/inheritance/CounterPointTest.java @@ -0,0 +1,30 @@ +package me.whiteship.chapter02.item10.inheritance; + + +import me.whiteship.chapter02.item10.Color; +import me.whiteship.chapter02.item10.Point; + +import java.util.Set; + +// CounterPoint를 Point로 사용하는 테스트 프로그램 +public class CounterPointTest { + // 단위 원 안의 모든 점을 포함하도록 unitCircle을 초기화한다. (58쪽) + private static final Set unitCircle = Set.of( + new Point( 1, 0), new Point( 0, 1), + new Point(-1, 0), new Point( 0, -1)); + + public static boolean onUnitCircle(Point p) { + return unitCircle.contains(p); + } + + public static void main(String[] args) { + Point p1 = new Point(1, 0); + Point p2 = new CounterPoint(1, 0); + + // true를 출력한다. + System.out.println(onUnitCircle(p1)); + + // true를 출력해야 하지만, Point의 equals가 getClass를 사용해 작성되었다면 그렇지 않다. + System.out.println(onUnitCircle(p2)); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/inheritance/SmellPoint.java b/src/main/java/me/whiteship/chapter02/item10/inheritance/SmellPoint.java new file mode 100644 index 0000000..e2beff3 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/inheritance/SmellPoint.java @@ -0,0 +1,25 @@ +package me.whiteship.chapter02.item10.inheritance; + +import me.whiteship.chapter02.item10.Point; + +public class SmellPoint extends Point { + + private String smell; + + public SmellPoint(int x, int y, String smell) { + super(x, y); + this.smell = smell; + } + + @Override public boolean equals(Object o) { + if (!(o instanceof Point)) + return false; + + // o가 일반 Point면 색상을 무시하고 비교한다. + if (!(o instanceof SmellPoint)) + return o.equals(this); + + // o가 ColorPoint면 색상까지 비교한다. + return super.equals(o) && ((SmellPoint) o).smell.equals(smell); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/inheritance/SmellPointTest.java b/src/main/java/me/whiteship/chapter02/item10/inheritance/SmellPointTest.java new file mode 100644 index 0000000..a7c1f8a --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/inheritance/SmellPointTest.java @@ -0,0 +1,16 @@ +package me.whiteship.chapter02.item10.inheritance; + +import me.whiteship.chapter02.item10.Color; + +public class SmellPointTest { + + /** + * TODO -Xss10M + * @param args + */ + public static void main(String[] args) { + SmellPoint p1 = new SmellPoint(1, 0, "sweat"); + ColorPoint p2 = new ColorPoint(1, 0, Color.RED); + p1.equals(p2); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/lombok/LombokTest.java b/src/main/java/me/whiteship/chapter02/item10/lombok/LombokTest.java new file mode 100644 index 0000000..c17e031 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/lombok/LombokTest.java @@ -0,0 +1,9 @@ +package me.whiteship.chapter02.item10.lombok; + +public class LombokTest { + + public static void main(String[] args) { + Point point = new Point(1, 2); + System.out.println(point.equals(new Point(1, 2))); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item10/lombok/Point.java b/src/main/java/me/whiteship/chapter02/item10/lombok/Point.java new file mode 100644 index 0000000..30b8a8d --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/lombok/Point.java @@ -0,0 +1,21 @@ +package me.whiteship.chapter02.item10.lombok; + +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * https://projectlombok.org/features/EqualsAndHashCode + * https://projectlombok.org/features/ToString + */ +@EqualsAndHashCode +@ToString +public class Point { + private final int x; + private final int y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + +} diff --git a/src/main/java/me/whiteship/chapter02/item10/record/Point.java b/src/main/java/me/whiteship/chapter02/item10/record/Point.java new file mode 100644 index 0000000..a0f0fe1 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/record/Point.java @@ -0,0 +1,5 @@ +//package me.whiteship.chapter02.item10.record; +// +//public record Point(int x, int y) { +// +//} diff --git a/src/main/java/me/whiteship/chapter02/item10/record/PointTest.java b/src/main/java/me/whiteship/chapter02/item10/record/PointTest.java new file mode 100644 index 0000000..be58a3a --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item10/record/PointTest.java @@ -0,0 +1,14 @@ +//package me.whiteship.chapter02.item10.record; +// +//public class PointTest { +// +// public static void main(String[] args) { +// Point p1 = new Point(1, 0); +// Point p2 = new Point(1, 0); +// System.out.println(p1.equals(p2)); +// System.out.println(p1); +// +// System.out.println(p1.x()); +// System.out.println(p1.y()); +// } +//} diff --git a/src/main/java/me/whiteship/chapter02/item11/guava/PhoneNumber.java b/src/main/java/me/whiteship/chapter02/item11/guava/PhoneNumber.java new file mode 100644 index 0000000..1452447 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item11/guava/PhoneNumber.java @@ -0,0 +1,55 @@ +package me.whiteship.chapter02.item11.guava; + +import com.google.common.hash.Funnel; +import com.google.common.hash.Hashing; +import com.google.common.hash.PrimitiveSink; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +// equals를 재정의하면 hashCode로 재정의해야 함을 보여준다. (70-71쪽) +public final class PhoneNumber { + private final short areaCode, prefix, lineNum; + + public PhoneNumber(int areaCode, int prefix, int lineNum) { + this.areaCode = rangeCheck(areaCode, 999, "area code"); + this.prefix = rangeCheck(prefix, 999, "prefix"); + this.lineNum = rangeCheck(lineNum, 9999, "line num"); + } + + private static short rangeCheck(int val, int max, String arg) { + if (val < 0 || val > max) + throw new IllegalArgumentException(arg + ": " + val); + return (short) val; + } + + @Override public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof PhoneNumber)) + return false; + PhoneNumber pn = (PhoneNumber)o; + return pn.lineNum == lineNum && pn.prefix == prefix + && pn.areaCode == areaCode; + } + + @Override + public int hashCode() { + return Hashing.goodFastHash(32) + .hashObject(this, PhoneNumberFunnel.INSTANCE) + .hashCode(); + } + + private static class PhoneNumberFunnel implements Funnel { + + private static final PhoneNumberFunnel INSTANCE = new PhoneNumberFunnel(); + + @Override + public void funnel(PhoneNumber from, PrimitiveSink into) { + into.putShort(from.areaCode).putShort(from.prefix).putShort(from.lineNum); + } + } + +} diff --git a/src/main/java/me/whiteship/chapter02/item11/hashcode/PhoneNumber.java b/src/main/java/me/whiteship/chapter02/item11/hashcode/PhoneNumber.java new file mode 100644 index 0000000..918f8d3 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item11/hashcode/PhoneNumber.java @@ -0,0 +1,78 @@ +package me.whiteship.chapter02.item11.hashcode; + +import me.whiteship.chapter02.item10.Point; + +import java.util.*; + +// equals를 재정의하면 hashCode로 재정의해야 함을 보여준다. (70-71쪽) +public final class PhoneNumber { + private final short areaCode, prefix, lineNum; + + public PhoneNumber(int areaCode, int prefix, int lineNum) { + this.areaCode = rangeCheck(areaCode, 999, "area code"); + this.prefix = rangeCheck(prefix, 999, "prefix"); + this.lineNum = rangeCheck(lineNum, 9999, "line num"); + } + + private static short rangeCheck(int val, int max, String arg) { + if (val < 0 || val > max) + throw new IllegalArgumentException(arg + ": " + val); + return (short) val; + } + + @Override public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof PhoneNumber)) + return false; + PhoneNumber pn = (PhoneNumber)o; + return pn.lineNum == lineNum && pn.prefix == prefix + && pn.areaCode == areaCode; + } + +// @Override +// public int hashCode() { +// return 42; +// } + + // hashCode 없이는 제대로 동작하지 않는다. 다음 셋 중 하나를 활성화하자. + + // 코드 11-2 전형적인 hashCode 메서드 (70쪽) +// @Override public int hashCode() { +// int result = Short.hashCode(areaCode); // 1 +// result = 800000 * result + Short.hashCode(prefix); // 2 +// result = 800000 * result + Short.hashCode(lineNum); // 3 +// return result; +// } + + // 코드 11-3 한 줄짜리 hashCode 메서드 - 성능이 살짝 아쉽다. (71쪽) +// @Override public int hashCode() { +// return Objects.hash(lineNum, prefix, areaCode); +// } + + // 해시코드를 지연 초기화하는 hashCode 메서드 - 스레드 안정성까지 고려해야 한다. (71쪽) + private volatile int hashCode; // 자동으로 0으로 초기화된다. + + @Override public int hashCode() { + if (this.hashCode != 0) { + return hashCode; + } + + synchronized (this) { + int result = hashCode; + if (result == 0) { + result = Short.hashCode(areaCode); + result = 31 * result + Short.hashCode(prefix); + result = 31 * result + Short.hashCode(lineNum); + this.hashCode = result; + } + return result; + } + } + + public static void main(String[] args) { + Map m = new HashMap<>(); + m.put(new PhoneNumber(707, 867, 5309), "제니"); + System.out.println(m.get(new PhoneNumber(707, 867, 5309))); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item11/hashtable/HashMapTest.java b/src/main/java/me/whiteship/chapter02/item11/hashtable/HashMapTest.java new file mode 100644 index 0000000..3a6338c --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item11/hashtable/HashMapTest.java @@ -0,0 +1,28 @@ +package me.whiteship.chapter02.item11.hashtable; + +import me.whiteship.chapter02.item11.guava.PhoneNumber; + +import java.util.HashMap; +import java.util.Map; + +public class HashMapTest { + + public static void main(String[] args) { + Map map = new HashMap<>(); + + PhoneNumber number1 = new PhoneNumber(123, 456, 7890); + PhoneNumber number2 = new PhoneNumber(456, 789, 1111); + +// TODO 같은 인스턴스인데 다른 hashCode +// 다른 인스턴스인데 같은 hashCode를 쓴다면? + System.out.println(number1.equals(number2)); + System.out.println(number1.hashCode()); + System.out.println(number2.hashCode()); + + map.put(number1, "keesun"); + map.put(number2, "whiteship"); + + String s = map.get(number2); + System.out.println(s); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item11/package-info.java b/src/main/java/me/whiteship/chapter02/item11/package-info.java new file mode 100644 index 0000000..b899588 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item11/package-info.java @@ -0,0 +1 @@ +package me.whiteship.chapter02.item11; \ No newline at end of file diff --git a/src/main/java/me/whiteship/chapter02/item12/PhoneNumber.java b/src/main/java/me/whiteship/chapter02/item12/PhoneNumber.java new file mode 100644 index 0000000..c7c011f --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item12/PhoneNumber.java @@ -0,0 +1,83 @@ +package me.whiteship.chapter02.item12; + +import lombok.ToString; + +// PhoneNumber에 toString 메서드 추가 (75쪽) +public final class PhoneNumber { + private final short areaCode, prefix, lineNum; + + public PhoneNumber(int areaCode, int prefix, int lineNum) { + this.areaCode = rangeCheck(areaCode, 999, "지역코드"); + this.prefix = rangeCheck(prefix, 999, "프리픽스"); + this.lineNum = rangeCheck(lineNum, 9999, "가입자 번호"); + } + + private static short rangeCheck(int val, int max, String arg) { + if (val < 0 || val > max) + throw new IllegalArgumentException(arg + ": " + val); + return (short) val; + } + + /** + * 이 전화번호의 문자열 표현을 반환한다. + * 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다. + * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다. + * 각각의 대문자는 10진수 숫자 하나를 나타낸다. + * + * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면, + * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면 + * 전화번호의 마지막 네 문자는 "0123"이 된다. + */ + @Override public String toString() { + return String.format("%03d-%03d-%04d", + areaCode, prefix, lineNum); + } + + public static PhoneNumber of(String phoneNumberString) { + String[] split = phoneNumberString.split("-"); + PhoneNumber phoneNumber = new PhoneNumber( + Short.parseShort(split[0]), + Short.parseShort(split[1]), + Short.parseShort(split[2])); + return phoneNumber; + } + + @Override public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof PhoneNumber)) + return false; + PhoneNumber pn = (PhoneNumber)o; + return pn.lineNum == lineNum && pn.prefix == prefix + && pn.areaCode == areaCode; + } + + @Override public int hashCode() { + int result = Short.hashCode(areaCode); + result = 31 * result + Short.hashCode(prefix); + result = 31 * result + Short.hashCode(lineNum); + return result; + } + + public short getAreaCode() { + return areaCode; + } + + public short getPrefix() { + return prefix; + } + + public short getLineNum() { + return lineNum; + } + + public static void main(String[] args) { + PhoneNumber jenny = new PhoneNumber(707, 867, 5309); + System.out.println("제니의 번호: " + jenny); + + PhoneNumber phoneNumber = PhoneNumber.of("707-867-5309"); + System.out.println(phoneNumber); + System.out.println(jenny.equals(phoneNumber)); + System.out.println(jenny.hashCode() == phoneNumber.hashCode()); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item13/EmptyStackException.java b/src/main/java/me/whiteship/chapter02/item13/EmptyStackException.java new file mode 100644 index 0000000..f3cea49 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/EmptyStackException.java @@ -0,0 +1,4 @@ +package me.whiteship.chapter02.item13; + +public class EmptyStackException extends IllegalStateException { +} diff --git a/src/main/java/me/whiteship/chapter02/item13/HashTable.java b/src/main/java/me/whiteship/chapter02/item13/HashTable.java new file mode 100644 index 0000000..7d07b6a --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/HashTable.java @@ -0,0 +1,87 @@ +package me.whiteship.chapter02.item13; + +public class HashTable implements Cloneable { + + private Entry[] buckets = new Entry[10]; + + private static class Entry { + final Object key; + Object value; + Entry next; + + Entry(Object key, Object value, Entry next) { + this.key = key; + this.value = value; + this.next = next; + } + + public void add(Object key, Object value) { + this.next = new Entry(key, value, null); + } + +// public Entry deepCopy() { +// return new Entry(key, value, next == null ? null : next.deepCopy()); +// } + + public Entry deepCopy() { + Entry result = new Entry(key, value, next); + for (Entry p = result ; p.next != null ; p = p.next) { + p.next = new Entry(p.next.key, p.next.value, p.next.next); + } + return result; + } + } + + /** + * TODO hasTable -> entryH[], + * TODO copy -> entryC[] + * TODO entryH[0] == entryC[0] + * + * @return + */ +// @Override +// public HashTable clone() { +// HashTable result = null; +// try { +// result = (HashTable)super.clone(); +// result.buckets = this.buckets.clone(); // p82, shallow copy 라서 위험하다. +// return result; +// } catch (CloneNotSupportedException e) { +// throw new AssertionError(); +// } +// } + + /** + * TODO hasTable -> entryH[], + * TODO copy -> entryC[] + * TODO entryH[0] != entryC[0] + * + * @return + */ + @Override + public HashTable clone() { + HashTable result = null; + try { + result = (HashTable)super.clone(); + result.buckets = new Entry[this.buckets.length]; + + for (int i = 0 ; i < this.buckets.length; i++) { + if (buckets[i] != null) { + result.buckets[i] = this.buckets[i].deepCopy(); // p83, deep copy + } + } + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + public static void main(String[] args) { + HashTable hashTable = new HashTable(); + Entry entry = new Entry(new Object(), new Object(), null); + hashTable.buckets[0] = entry; + HashTable clone = hashTable.clone(); + System.out.println(hashTable.buckets[0] == entry); + System.out.println(hashTable.buckets[0] == clone.buckets[0]); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item13/PhoneNumber.java b/src/main/java/me/whiteship/chapter02/item13/PhoneNumber.java new file mode 100644 index 0000000..4dbae02 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/PhoneNumber.java @@ -0,0 +1,82 @@ +package me.whiteship.chapter02.item13; + +import java.util.HashMap; +import java.util.Map; + +// PhoneNumber에 clone 메서드 추가 (79쪽) +public final class PhoneNumber implements Cloneable { + private final short areaCode, prefix, lineNum; + + public PhoneNumber(int areaCode, int prefix, int lineNum) { + this.areaCode = rangeCheck(areaCode, 999, "지역코드"); + this.prefix = rangeCheck(prefix, 999, "프리픽스"); + this.lineNum = rangeCheck(lineNum, 9999, "가입자 번호"); + System.out.println("constructor is called"); + } + + public PhoneNumber(PhoneNumber phoneNumber) { + this(phoneNumber.areaCode, phoneNumber.prefix, phoneNumber.lineNum); + } + + private static short rangeCheck(int val, int max, String arg) { + if (val < 0 || val > max) + throw new IllegalArgumentException(arg + ": " + val); + return (short) val; + } + + // 코드 13-1 가변 상태를 참조하지 않는 클래스용 clone 메서드 (79쪽) + @Override + public PhoneNumber clone() { + try { + return (PhoneNumber) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // 일어날 수 없는 일이다. + } + } + + public static void main(String[] args) { + PhoneNumber pn = new PhoneNumber(707, 867, 5309); + Map m = new HashMap<>(); + m.put(pn, "제니"); + PhoneNumber clone = pn.clone(); + System.out.println(m.get(clone)); + + System.out.println(clone != pn); // 반드시 true + System.out.println(clone.getClass() == pn.getClass()); // 반드시 true + System.out.println(clone.equals(pn)); // true가 아닐 수도 있다. + } + + @Override public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof PhoneNumber)) + return false; + PhoneNumber pn = (PhoneNumber)o; + return pn.lineNum == lineNum && pn.prefix == prefix + && pn.areaCode == areaCode; + } + + @Override public int hashCode() { + int result = Short.hashCode(areaCode); + result = 31 * result + Short.hashCode(prefix); + result = 31 * result + Short.hashCode(lineNum); + return result; + } + + /** + * 이 전화번호의 문자열 표현을 반환한다. + * 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다. + * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다. + * 각각의 대문자는 10진수 숫자 하나를 나타낸다. + * + * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면, + * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면 + * 전화번호의 마지막 네 문자는 "0123"이 된다. + */ + @Override public String toString() { + return String.format("%03d-%03d-%04d", + areaCode, prefix, lineNum); + } + + +} diff --git a/src/main/java/me/whiteship/chapter02/item13/Stack.java b/src/main/java/me/whiteship/chapter02/item13/Stack.java new file mode 100644 index 0000000..8192cb2 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/Stack.java @@ -0,0 +1,74 @@ +package me.whiteship.chapter02.item13; +import java.util.Arrays; + +// Stack의 복제 가능 버전 (80-81쪽) +public class Stack implements Cloneable { + private Object[] elements; + private int size = 0; + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + public Stack() { + this.elements = new Object[DEFAULT_INITIAL_CAPACITY]; + } + + public void push(Object e) { + ensureCapacity(); + elements[size++] = e; + } + + public Object pop() { + if (size == 0) + throw new EmptyStackException(); + Object result = elements[--size]; + elements[size] = null; // 다 쓴 참조 해제 + return result; + } + + public boolean isEmpty() { + return size ==0; + } + + // 코드 13-2 가변 상태를 참조하는 클래스용 clone 메서드 + // TODO stack -> elementsS[0, 1] + // TODO copy -> elementsC[0, 1] + // TODO elementsS[0] == elementsC[0] + + @Override public Stack clone() { + try { + Stack result = (Stack) super.clone(); + result.elements = elements.clone(); + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + // 원소를 위한 공간을 적어도 하나 이상 확보한다. + private void ensureCapacity() { + if (elements.length == size) + elements = Arrays.copyOf(elements, 2 * size + 1); + } + + // clone이 동작하는 모습을 보려면 명령줄 인수를 몇 개 덧붙여서 호출해야 한다. + public static void main(String[] args) { + Object[] values = new Object[2]; + values[0] = new PhoneNumber(123, 456, 7890); + values[1] = new PhoneNumber(321, 764, 2341); + + Stack stack = new Stack(); + for (Object arg : values) + stack.push(arg); + + Stack copy = stack.clone(); + + System.out.println("pop from stack"); + while (!stack.isEmpty()) + System.out.println(stack.pop() + " "); + + System.out.println("pop from copy"); + while (!copy.isEmpty()) + System.out.println(copy.pop() + " "); + + System.out.println(stack.elements[0] == copy.elements[0]); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item13/clone_use_constructor/Item.java b/src/main/java/me/whiteship/chapter02/item13/clone_use_constructor/Item.java new file mode 100644 index 0000000..41597a1 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/clone_use_constructor/Item.java @@ -0,0 +1,17 @@ +package me.whiteship.chapter02.item13.clone_use_constructor; + +public class Item implements Cloneable { + + private String name; + + /** + * 이렇게 구현하면 하위 클래스의 clone()이 깨질 수 있다. p78 + * @return + */ + @Override + public Item clone() { + Item item = new Item(); + item.name = this.name; + return item; + } +} diff --git a/src/main/java/me/whiteship/chapter02/item13/clone_use_constructor/SubItem.java b/src/main/java/me/whiteship/chapter02/item13/clone_use_constructor/SubItem.java new file mode 100644 index 0000000..b96d157 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/clone_use_constructor/SubItem.java @@ -0,0 +1,20 @@ +package me.whiteship.chapter02.item13.clone_use_constructor; + +public class SubItem extends Item implements Cloneable { + + private String name; + + @Override + public SubItem clone() { + return (SubItem)super.clone(); + } + + public static void main(String[] args) { + SubItem item = new SubItem(); + SubItem clone = item.clone(); + + System.out.println(clone != item); + System.out.println(clone.getClass() == item.getClass()); + System.out.println(clone.equals(item)); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item13/copy_constructor/HashSetExample.java b/src/main/java/me/whiteship/chapter02/item13/copy_constructor/HashSetExample.java new file mode 100644 index 0000000..f57b6c8 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/copy_constructor/HashSetExample.java @@ -0,0 +1,19 @@ +package me.whiteship.chapter02.item13.copy_constructor; + +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +public class HashSetExample { + + public static void main(String[] args) { + Set hashSet = new HashSet<>(); + hashSet.add("keesun"); + hashSet.add("whiteship"); + System.out.println("HashSet: " + hashSet); + + Set treeSet = new TreeSet<>(hashSet); + + System.out.println("TreeSet: " + treeSet); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item13/exception/MyApp.java b/src/main/java/me/whiteship/chapter02/item13/exception/MyApp.java new file mode 100644 index 0000000..1612ec8 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/exception/MyApp.java @@ -0,0 +1,26 @@ +package me.whiteship.chapter02.item13.exception; + +public class MyApp { + + /** + * + * @param name + * @throws MyException + */ + public void hello(String name) throws MyException { + if (name.equals("푸틴")) { + throw new MyException(); + } + + System.out.println("hello"); + } + + public static void main(String[] args) { + MyApp myApp = new MyApp(); + try { + myApp.hello("푸틴"); + } catch (MyException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/me/whiteship/chapter02/item13/exception/MyException.java b/src/main/java/me/whiteship/chapter02/item13/exception/MyException.java new file mode 100644 index 0000000..7c0ca22 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/exception/MyException.java @@ -0,0 +1,4 @@ +package me.whiteship.chapter02.item13.exception; + +public class MyException extends Exception { +} diff --git a/src/main/java/me/whiteship/chapter02/item13/inheritance/Shape.java b/src/main/java/me/whiteship/chapter02/item13/inheritance/Shape.java new file mode 100644 index 0000000..34244cc --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/inheritance/Shape.java @@ -0,0 +1,32 @@ +package me.whiteship.chapter02.item13.inheritance; + +/** + * p84, p126 일반적으로 상속용 클래스에 Cloneable 인터페이스 사용을 권장하지 않는다. + * 해당 클래스를 확장하려는 프로그래머에게 많은 부담을 주기 때문이다. + */ +public abstract class Shape implements Cloneable { + + private int area; + + public abstract int getArea(); + + + /** + * p84, 부담을 덜기 위해서는 기본 clone() 구현체를 제공하여, + * Cloenable 구현 여부를 서브 클래스가 선택할 수 있다. + * @return + * @throws CloneNotSupportedException + */ + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * p85, Cloneable 구현을 막을 수도 있다. + */ +// @Override +// protected final Object clone() throws CloneNotSupportedException { +// throw new CloneNotSupportedException(); +// } +} diff --git a/src/main/java/me/whiteship/chapter02/item13/inheritance/Square.java b/src/main/java/me/whiteship/chapter02/item13/inheritance/Square.java new file mode 100644 index 0000000..2d99e56 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/inheritance/Square.java @@ -0,0 +1,22 @@ +package me.whiteship.chapter02.item13.inheritance; + +public class Square extends Shape { + + private int length, height; + + public Square(int length, int height) { + this.length = length; + this.height = height; + } + + public static void main(String[] args) throws CloneNotSupportedException { + Square square = new Square(10, 2); + Square copy = (Square) square.clone(); + System.out.println(copy.getArea()); + } + + @Override + public int getArea() { + return this.length * this.height; + } +} diff --git a/src/main/java/me/whiteship/chapter02/item13/treeset/TreeSetExample.java b/src/main/java/me/whiteship/chapter02/item13/treeset/TreeSetExample.java new file mode 100644 index 0000000..e335637 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item13/treeset/TreeSetExample.java @@ -0,0 +1,28 @@ +package me.whiteship.chapter02.item13.treeset; + +import me.whiteship.chapter02.item13.PhoneNumber; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Set; +import java.util.TreeSet; + +public class TreeSetExample { + + public static void main(String[] args) { +// TreeSet numbers = new TreeSet<>(); +// numbers.add(10); +// numbers.add(4); +// numbers.add(6); + + TreeSet numbers = new TreeSet<>(Comparator.comparingInt(PhoneNumber::hashCode)); + Set phoneNumbers = Collections.synchronizedSet(numbers); + phoneNumbers.add(new PhoneNumber(123, 456, 780)); + phoneNumbers.add(new PhoneNumber(123, 456, 7890)); + phoneNumbers.add(new PhoneNumber(123, 456, 789)); + + for (PhoneNumber number : numbers) { + System.out.println(number); + } + } +} diff --git a/src/main/java/me/whiteship/chapter02/item14/CaseInsensitiveString.java b/src/main/java/me/whiteship/chapter02/item14/CaseInsensitiveString.java new file mode 100644 index 0000000..57f9335 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item14/CaseInsensitiveString.java @@ -0,0 +1,41 @@ +package me.whiteship.chapter02.item14; + +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; + +// 코드 14-1 객체 참조 필드가 하나뿐인 비교자 (90쪽) +public final class CaseInsensitiveString + implements Comparable { + private final String s; + + public CaseInsensitiveString(String s) { + this.s = Objects.requireNonNull(s); + } + + // 수정된 equals 메서드 (56쪽) + @Override public boolean equals(Object o) { + return o instanceof CaseInsensitiveString && + ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); + } + + @Override public int hashCode() { + return s.hashCode(); + } + + @Override public String toString() { + return s; + } + + // 자바가 제공하는 비교자를 사용해 클래스를 비교한다. + public int compareTo(CaseInsensitiveString cis) { + return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s); + } + + public static void main(String[] args) { + Set s = new TreeSet<>(); + for (String arg : args) + s.add(new CaseInsensitiveString(arg)); + System.out.println(s); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item14/CompareToConvention.java b/src/main/java/me/whiteship/chapter02/item14/CompareToConvention.java new file mode 100644 index 0000000..59e8765 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item14/CompareToConvention.java @@ -0,0 +1,36 @@ +package me.whiteship.chapter02.item14; + +import java.math.BigDecimal; + +public class CompareToConvention { + + public static void main(String[] args) { + BigDecimal n1 = BigDecimal.valueOf(23134134); + BigDecimal n2 = BigDecimal.valueOf(11231230); + BigDecimal n3 = BigDecimal.valueOf(53534552); + BigDecimal n4 = BigDecimal.valueOf(11231230); + + // p88, 반사성 + System.out.println(n1.compareTo(n1)); + + // p88, 대칭성 + System.out.println(n1.compareTo(n2)); + System.out.println(n2.compareTo(n1)); + + // p89, 추이성 + System.out.println(n3.compareTo(n1) > 0); + System.out.println(n1.compareTo(n2) > 0); + System.out.println(n3.compareTo(n2) > 0); + + // p89, 일관성 + System.out.println(n4.compareTo(n2)); + System.out.println(n2.compareTo(n1)); + System.out.println(n4.compareTo(n1)); + + // p89, compareTo가 0이라면 equals는 true여야 한다. (아닐 수도 있고..) + BigDecimal oneZero = new BigDecimal("1.0"); + BigDecimal oneZeroZero = new BigDecimal("1.00"); + System.out.println(oneZero.compareTo(oneZeroZero)); // Tree, TreeMap + System.out.println(oneZero.equals(oneZeroZero)); // 순서가 없는 콜렉션 + } +} diff --git a/src/main/java/me/whiteship/chapter02/item14/PhoneNumber.java b/src/main/java/me/whiteship/chapter02/item14/PhoneNumber.java new file mode 100644 index 0000000..b83e73b --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item14/PhoneNumber.java @@ -0,0 +1,107 @@ +package me.whiteship.chapter02.item14; + +import me.whiteship.chapter02.item10.Point; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +import static java.util.Comparator.comparingInt; + +// PhoneNumber를 비교할 수 있게 만든다. (91-92쪽) +public final class PhoneNumber implements Cloneable, Comparable { + private final short areaCode, prefix, lineNum; + + public short getAreaCode() { + return areaCode; + } + + public short getPrefix() { + return prefix; + } + + public short getLineNum() { + return lineNum; + } + + public PhoneNumber(int areaCode, int prefix, int lineNum) { + this.areaCode = rangeCheck(areaCode, 999, "지역코드"); + this.prefix = rangeCheck(prefix, 999, "프리픽스"); + this.lineNum = rangeCheck(lineNum, 9999, "가입자 번호"); + } + + private static short rangeCheck(int val, int max, String arg) { + if (val < 0 || val > max) + throw new IllegalArgumentException(arg + ": " + val); + return (short) val; + } + + @Override public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof PhoneNumber)) + return false; + PhoneNumber pn = (PhoneNumber)o; + return pn.lineNum == lineNum && pn.prefix == prefix + && pn.areaCode == areaCode; + } + + @Override public int hashCode() { + int result = Short.hashCode(areaCode); + result = 31 * result + Short.hashCode(prefix); + result = 31 * result + Short.hashCode(lineNum); + return result; + } + + /** + * 이 전화번호의 문자열 표현을 반환한다. + * 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다. + * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다. + * 각각의 대문자는 10진수 숫자 하나를 나타낸다. + * + * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면, + * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면 + * 전화번호의 마지막 네 문자는 "0123"이 된다. + */ + @Override public String toString() { + return String.format("%03d-%03d-%04d", + areaCode, prefix, lineNum); + } + + // 코드 14-2 기본 타입 필드가 여럿일 때의 비교자 (91쪽) + @Override + public int compareTo(PhoneNumber pn) { + int result = Short.compare(areaCode, pn.areaCode); + if (result == 0) { + result = Short.compare(prefix, pn.prefix); + if (result == 0) + result = Short.compare(lineNum, pn.lineNum); + } + return result; + } + + // 코드 14-3 비교자 생성 메서드를 활용한 비교자 (92쪽) + private static final Comparator COMPARATOR = + comparingInt((PhoneNumber pn) -> pn.areaCode) + .thenComparingInt(pn -> pn.getPrefix()) + .thenComparingInt(pn -> pn.lineNum); +// +// @Override +// public int compareTo(PhoneNumber pn) { +// return COMPARATOR.compare(this, pn); +// } + + private static PhoneNumber randomPhoneNumber() { + Random rnd = ThreadLocalRandom.current(); + return new PhoneNumber((short) rnd.nextInt(1000), + (short) rnd.nextInt(1000), + (short) rnd.nextInt(10000)); + } + + public static void main(String[] args) { + Set s = new TreeSet<>(); + for (int i = 0; i < 10; i++) + s.add(randomPhoneNumber()); + System.out.println(s); + } + +} diff --git a/src/main/java/me/whiteship/chapter02/item14/WordList.java b/src/main/java/me/whiteship/chapter02/item14/WordList.java new file mode 100644 index 0000000..222a971 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item14/WordList.java @@ -0,0 +1,16 @@ +package me.whiteship.chapter02.item14; + +import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; + +// Comparable 구현 시의 이점 (87쪽) +public class WordList { + public static void main(String[] args) { + String[] values = new String[]{"keesun", "whiteship", "java"}; + + Set s = new TreeSet<>(); + Collections.addAll(s, values); + System.out.println(s); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item14/composition/NamedPoint.java b/src/main/java/me/whiteship/chapter02/item14/composition/NamedPoint.java new file mode 100644 index 0000000..a5b3ef7 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item14/composition/NamedPoint.java @@ -0,0 +1,25 @@ +package me.whiteship.chapter02.item14.composition; + +public class NamedPoint implements Comparable { + + private final Point point; + private final String name; + + public NamedPoint(Point point, String name) { + this.point = point; + this.name = name; + } + + public Point getPoint() { + return this.point; + } + + @Override + public int compareTo(NamedPoint namedPoint) { + int result = this.point.compareTo(namedPoint.point); + if (result == 0) { + result = this.name.compareTo(namedPoint.name); + } + return result; + } +} diff --git a/src/main/java/me/whiteship/chapter02/item14/composition/Point.java b/src/main/java/me/whiteship/chapter02/item14/composition/Point.java new file mode 100644 index 0000000..6a76365 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item14/composition/Point.java @@ -0,0 +1,20 @@ +package me.whiteship.chapter02.item14.composition; + +public class Point implements Comparable{ + + final int x, y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public int compareTo(Point point) { + int result = Integer.compare(this.x, point.x); + if (result == 0) { + result = Integer.compare(this.y, point.y); + } + return result; + } +} diff --git a/src/main/java/me/whiteship/chapter02/item14/decimal/DecimalIsNotCorrect.java b/src/main/java/me/whiteship/chapter02/item14/decimal/DecimalIsNotCorrect.java new file mode 100644 index 0000000..4597276 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item14/decimal/DecimalIsNotCorrect.java @@ -0,0 +1,15 @@ +package me.whiteship.chapter02.item14.decimal; + +import java.math.BigDecimal; + +public class DecimalIsNotCorrect { + + public static void main(String[] args) { + int i = 1; + double d = 0.1; + System.out.println(i - d * 9); + + BigDecimal bd = BigDecimal.valueOf(0.1); + System.out.println(BigDecimal.valueOf(1).min(bd.multiply(BigDecimal.valueOf(9)))); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item14/decimal/IntOverflow.java b/src/main/java/me/whiteship/chapter02/item14/decimal/IntOverflow.java new file mode 100644 index 0000000..2a1c0ed --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item14/decimal/IntOverflow.java @@ -0,0 +1,9 @@ +package me.whiteship.chapter02.item14.decimal; + +public class IntOverflow { + + public static void main(String[] args) { + System.out.println(-2147483648 - 10); + System.out.println(Integer.compare(-2147483648, 10)); + } +} diff --git a/src/main/java/me/whiteship/chapter02/item14/interitance/NamedPoint.java b/src/main/java/me/whiteship/chapter02/item14/interitance/NamedPoint.java new file mode 100644 index 0000000..b062903 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item14/interitance/NamedPoint.java @@ -0,0 +1,49 @@ +package me.whiteship.chapter02.item14.interitance; + +import java.util.Comparator; +import java.util.Set; +import java.util.TreeSet; + +public class NamedPoint extends Point { + + final private String name; + + public NamedPoint(int x, int y, String name) { + super(x, y); + this.name = name; + } + + @Override + public String toString() { + return "NamedPoint{" + + "name='" + name + '\'' + + ", x=" + x + + ", y=" + y + + '}'; + } + + public static void main(String[] args) { + NamedPoint p1 = new NamedPoint(1, 0, "keesun"); + NamedPoint p2 = new NamedPoint(1, 0, "whiteship"); + + Set points = new TreeSet<>(new Comparator() { + @Override + public int compare(NamedPoint p1, NamedPoint p2) { + int result = Integer.compare(p1.getX(), p2.getX()); + if (result == 0) { + result = Integer.compare(p1.getY(), p2.getY()); + } + if (result == 0) { + result = p1.name.compareTo(p2.name); + } + return result; + } + }); + + points.add(p1); + points.add(p2); + + System.out.println(points); + } + +} diff --git a/src/main/java/me/whiteship/chapter02/item14/interitance/Point.java b/src/main/java/me/whiteship/chapter02/item14/interitance/Point.java new file mode 100644 index 0000000..1839ea8 --- /dev/null +++ b/src/main/java/me/whiteship/chapter02/item14/interitance/Point.java @@ -0,0 +1,28 @@ +package me.whiteship.chapter02.item14.interitance; + +public class Point implements Comparable{ + + final int x, y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + @Override + public int compareTo(Point point) { + int result = Integer.compare(this.x, point.x); + if (result == 0) { + result = Integer.compare(this.y, point.y); + } + return result; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/src/test/java/me/whiteship/chapter01/item03/field/ConcertTest.java b/src/test/java/me/whiteship/chapter01/item03/field/ConcertTest.java new file mode 100644 index 0000000..f54cdad --- /dev/null +++ b/src/test/java/me/whiteship/chapter01/item03/field/ConcertTest.java @@ -0,0 +1,18 @@ +package me.whiteship.chapter01.item03.field; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ConcertTest { + + @Test + void perform() { + Concert concert = new Concert(new MockElvis()); + concert.perform(); + + assertTrue(concert.isLightsOn()); + assertTrue(concert.isMainStateOpen()); + } + +} \ No newline at end of file diff --git a/src/test/java/me/whiteship/chapter01/item03/field/MockElvis.java b/src/test/java/me/whiteship/chapter01/item03/field/MockElvis.java new file mode 100644 index 0000000..ab34246 --- /dev/null +++ b/src/test/java/me/whiteship/chapter01/item03/field/MockElvis.java @@ -0,0 +1,13 @@ +package me.whiteship.chapter01.item03.field; + +public class MockElvis implements IElvis { + @Override + public void leaveTheBuilding() { + + } + + @Override + public void sing() { + System.out.println("You ain't nothin' but a hound dog."); + } +} diff --git a/src/test/java/me/whiteship/chapter01/item05/dependencyinjection/SpellCheckerTest.java b/src/test/java/me/whiteship/chapter01/item05/dependencyinjection/SpellCheckerTest.java new file mode 100644 index 0000000..dc7459c --- /dev/null +++ b/src/test/java/me/whiteship/chapter01/item05/dependencyinjection/SpellCheckerTest.java @@ -0,0 +1,14 @@ +package me.whiteship.chapter01.item05.dependencyinjection; + +import me.whiteship.chapter01.item05.MockDictionary; +import org.junit.jupiter.api.Test; + +class SpellCheckerTest { + + @Test + void isValid() { + SpellChecker spellChecker = new SpellChecker(MockDictionary::new); + spellChecker.isValid("test"); + } + +} \ No newline at end of file diff --git a/src/test/java/me/whiteship/chapter01/item05/staticutils/SpellCheckerTest.java b/src/test/java/me/whiteship/chapter01/item05/staticutils/SpellCheckerTest.java new file mode 100644 index 0000000..d32f6a2 --- /dev/null +++ b/src/test/java/me/whiteship/chapter01/item05/staticutils/SpellCheckerTest.java @@ -0,0 +1,14 @@ +package me.whiteship.chapter01.item05.staticutils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SpellCheckerTest { + + @Test + void isValid() { + assertTrue(SpellChecker.isValid("test")); + } + +} \ No newline at end of file diff --git a/src/test/java/me/whiteship/chapter01/item07/cache/PostRepositoryTest.java b/src/test/java/me/whiteship/chapter01/item07/cache/PostRepositoryTest.java new file mode 100644 index 0000000..5b531d5 --- /dev/null +++ b/src/test/java/me/whiteship/chapter01/item07/cache/PostRepositoryTest.java @@ -0,0 +1,61 @@ +package me.whiteship.chapter01.item07.cache; + +import org.junit.jupiter.api.Test; + +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PostRepositoryTest { + + @Test + void cache() throws InterruptedException { + PostRepository postRepository = new PostRepository(); + CacheKey key1 = new CacheKey(1); + postRepository.getPostById(key1); + + assertFalse(postRepository.getCache().isEmpty()); + + key1 = null; + // TODO run gc + System.out.println("run gc"); + System.gc(); + System.out.println("wait"); + Thread.sleep(3000L); + + assertTrue(postRepository.getCache().isEmpty()); + } + +// @Test +// void backgroundThread() throws InterruptedException { +// ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); +// PostRepository postRepository = new PostRepository(); +// CacheKey key1 = new CacheKey(1); +// postRepository.getPostById(key1); +// +// Runnable removeOldCache = () -> { +// System.out.println("running removeOldCache task"); +// Map cache = postRepository.getCache(); +// Set cacheKeys = cache.keySet(); +// Optional key = cacheKeys.stream().min(Comparator.comparing(CacheKey::getCreated)); +// key.ifPresent((k) -> { +// System.out.println("removing " + k); +// cache.remove(k); +// }); +// }; +// +// System.out.println("The time is : " + new Date()); +// +// executor.scheduleAtFixedRate(removeOldCache, +// 1, 3, TimeUnit.SECONDS); +// +// Thread.sleep(20000L); +// +// executor.shutdown(); +// } + +} \ No newline at end of file diff --git a/src/test/java/me/whiteship/chapter01/item07/listener/ChatRoomTest.java b/src/test/java/me/whiteship/chapter01/item07/listener/ChatRoomTest.java new file mode 100644 index 0000000..4ca3b82 --- /dev/null +++ b/src/test/java/me/whiteship/chapter01/item07/listener/ChatRoomTest.java @@ -0,0 +1,33 @@ +package me.whiteship.chapter01.item07.listener; + +import org.junit.jupiter.api.Test; + +import java.lang.ref.WeakReference; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class ChatRoomTest { + + @Test + void charRoom() throws InterruptedException { + ChatRoom chatRoom = new ChatRoom(); + User user1 = new User(); + User user2 = new User(); + + chatRoom.addUser(user1); + chatRoom.addUser(user2); + + chatRoom.sendMessage("hello"); + + user1 = null; + + System.gc(); + + Thread.sleep(5000L); + + List> users = chatRoom.getUsers(); + assertTrue(users.size() == 1); + } + +} \ No newline at end of file diff --git a/src/test/java/me/whiteship/chapter01/item07/optional/ChannelTest.java b/src/test/java/me/whiteship/chapter01/item07/optional/ChannelTest.java new file mode 100644 index 0000000..5787aa2 --- /dev/null +++ b/src/test/java/me/whiteship/chapter01/item07/optional/ChannelTest.java @@ -0,0 +1,18 @@ +package me.whiteship.chapter01.item07.optional; + +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +class ChannelTest { + + @Test + void npe() { + Channel channel = new Channel(); + Optional optional = channel.defaultMemberShip(); + optional.ifPresent(MemberShip::hello); + } + +} \ No newline at end of file diff --git a/src/test/java/me/whiteship/chapter01/item08/finalizer_attack/AccountTest.java b/src/test/java/me/whiteship/chapter01/item08/finalizer_attack/AccountTest.java new file mode 100644 index 0000000..3d1ea8b --- /dev/null +++ b/src/test/java/me/whiteship/chapter01/item08/finalizer_attack/AccountTest.java @@ -0,0 +1,28 @@ +package me.whiteship.chapter01.item08.finalizer_attack; + +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +class AccountTest { + + @Test + void 일반_계정() { + Account account = new Account("keesun"); + account.transfer(BigDecimal.valueOf(10.4),"hello"); + } + + @Test + void 푸틴_계정() throws InterruptedException { + Account account = null; + try { + account = new BrokenAccount("푸틴"); + } catch (Exception exception) { + System.out.println("이러면???"); + } + + System.gc(); + Thread.sleep(3000L); + } + +} \ No newline at end of file