TitlePageJava9ProgrammingByExample
Yourguidetosoftwaredevelopment
PeterVerhas
BIRMINGHAM-MUMBAI
Java9ProgrammingByExample
Copyright©2017PacktPublishingAllrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:April2017
Productionreference:1240417
PublishedbyPacktPublishingLtd.LiveryPlace35LiveryStreetBirminghamB32PB,UK.
ISBN978-1-78646-828-4
www.packtpub.com
Credits
Author
PeterVerhas
CopyEditors
MuktikantGarimellaZainabBootwala
Reviewer
JeffFriesen
ProjectCoordinator
UlhasKambali
CommissioningEditor
KunalParikh
Proofreader
SafisEditing
AcquisitionEditor
DenimPinto
Indexer
MariammalChettiyar
ContentDevelopmentEditor
NikhilBorkar
Graphics
AbhinashSahu
TechnicalEditor
HussainKanchwala
ProductionCoordinator
MelwynDsa
AbouttheAuthorPeterVerhasisaseniorsoftwareengineerandsoftwarearchitecthavingelectricalengineeringandeconomicsbackgroundfromTUBudapest(MsC)andPTEHungary(MBA),andalsostudiedatTUDelftandTUVienna.Hecreatedhisfirstprogramsin1979,andsincethenhehasauthoredseveralopensourceprograms.HehasworkedinseveralpositionsinthetelecommunicationsandfinanceindustriesandwastheCIOoftheHungarianstart-upindex.huduringitsearlydays.
PeterworksforEPAMSystemsinSwitzerland,participatinginsoftwaredevelopmentprojectsatvariouscustomersites,andhesupportstalentacquisitionbyinterviewingcandidates,trainingprogramsfordevelopers,andinternalmentoringprograms.
YoucanfollowPeteronTwitterat@verhas,LinkedIn,andGitHub,orreadhistechnicalblog,JavaDeep,athttp://javax0.wordpress.com.
Acknowledgementisthesectionofabookthateverybodyignoresbyturningthepages.Thistime,thissectionisabitdifferent.Iwillmentionafewpeopleandtheirrolesinthemakingofthisbookbut,atthesametime,Iwillexplainwhyandhowitisimportanttorelyonpeople,beingasoftwaredeveloper.Doingprofessionalworkisnotpossiblewithouthavingalife.Itisquiteobviousifyoutakethatliterally,butitisjustastruefiguratively.Ifyoudonotfindthebalancebetweenyourpersonalandprofessionallife,youwillburnoutandwillnotoperateprofessionally.Thisistheplacetomentionmyfamily,myparentswhomIamluckytostillhavearound,mywife,andmyalreadyadultkidswhoneverstoppedbelievinginmebeingabletodothiswork,whoknowmorethanwellwhatahypocriteIam,advocatingpersonal-professionallifebalance,andwhocontinuallypushedmeclosertothisequilibriumpointinlifesothatIcouldkeepperformingprofessionally.Forprofessionalwork,coworkersarealmostasimportantasfamilysupport.Itisimportantthatyousupportyourcolleaguesasmuchasyouaskthemfortheirsupport.Youlearnalotfrombooksandfromexperience,butyoulearnthemostfromotherpeople.Payattentiontoseniordevelopers.Youcan,however,learnjustasmuchfromjuniors.Nomatterhowaceyouare,fromtimetotime,arookiemayshedlightonatopic.Duringtheyears,Ilearnedalotfromjuniorswhobroughtafreshviewtothetable,askingshockingquestionsthatwereabsolutelyvalid.Icannotnameeachandeveryjuniorwhoaidedmyworkwithfreshout-of-the-boxthinking.Ican,andshould,however,namesomepeerprofessionalswhoactivelyparticipatedinthecreationofthisbookwiththeiradvice,discussions,andsuggestions.IshouldcertainlymentionKárolyOláhwhowasveryenthusiasticaboutmyproject,andherepresented,supported,andencouragedtheideainsideEPAMsystems.Heactivelydiscussedwiththeuppermanagementthatthesupportforwritingabookwellfitstheinnovationlineanddevelopmentofthecompany,andthepeoplewhoworktogether.Withouttheofficialsupportfromthecompanyprovidingextratimeforthetask,Iwouldnothavebeenabletocreatethisbook.Goodcompanyattractsgoodpeoplewhoarecleverandalsogoodtoworkwith.Ihadmanydiscussionsaboutthebook,topics,andhowtoexplaincertainaspectswithmyfellowEPAMers:KrisztiánSallai,PeterFodor,SándorSzilágyi,MantasAleknavicius,GáborLénard,andmanyothers.IwillseparatelymentionIstvánAttilaKovácsfromourBudapestofficewithwhomIdiscussedChapter5indetail,andwhogavemeveryvaluablefeedbackaboutthetopic.Ifhedoesnotknowsomething
aboutJavaparallelcomputing,thenthatsomethingdoesnotexist.Asasummaryandtakeawayforthepatientreaderwhoreadthissectiontilltheend,technology,knowledge,skills,andexperienceareextremelyimportantforbeingaprofessionalJava9developer,butitisthepeoplewhoreallymatter.
AbouttheReviewerJeffFriesenisafreelanceauthorandsoftwaredeveloperwhohastaughtJavaintheclassroomandbywritingnumerousarticlesandbookssincethelate1990s.HeholdsaBachelorofSciencedegreeinComputerScienceandMathematics.Priortofreelancing,Jeffworkedfortelecommunications,investment,andsoftwaredevelopmentcompanies.
JefffreelancesasaJavaauthorandsoftwaredeveloper.
JeffhaswrittenJavaI/O,NIOandNIO.2andJavaThreadsandtheConcurrencyUtilitiesforApress.Fulldetailsareavailableonhiswebsite(http://javajeff.ca/cgi-bin/makepage.cgi?/books).
IthankNitinDasanfortheopportunitytotechreviewthisbook.IalsothankUlhasKambaliforassistingmewiththetechreviewprocess.
www.PacktPub.comForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.
DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
https://www.packtpub.com/mapt
Getthemostin-demandsoftwareskillswithMapt.MaptgivesyoufullaccesstoallPacktbooksandvideocourses,aswellasindustry-leadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.
Whysubscribe?
FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser
CustomerFeedback
ThanksforpurchasingthisPacktbook.AtPackt,qualityisattheheartofoureditorialprocess.Tohelpusimprove,pleaseleaveusanhonestreviewonthisbook'sAmazonpageathttps://www.amazon.com/dp/178646828X/.
Ifyou'dliketojoinourteamofregularreviewers,youcane-mailusatcustomerreviews@packtpub.com.WeawardourregularreviewerswithfreeeBooksandvideosinexchangefortheirvaluablefeedback.Helpusberelentlessinimprovingourproducts!
TableofContents
PrefaceWhatthisbookcovers
Whatyouneedforthisbook
Whothisbookisfor
Conventions
Readerfeedback
CustomersupportDownloadingtheexamplecode
Errata
Piracy
Questions
1. GettingStartedwithJava9GettingstartedwithJava
InstallingJavaInstallationonWindows
InstallationonMACOSX
InstallationonLinux
SettingJAVA_HOME
ExecutingjshellLookingatthebytecode
PackagingclassesintoaJARfile
ManagingtherunningJavaapplication
UsinganIDENetBeans
Eclipse
IntelliJ
IDEservicesIDEscreenstructure
Editingfiles
Managingprojects
Buildthecodeandrunit
DebuggingJava
Summary
2. TheFirstRealJavaProgram-SortingNamesGettingstartedwithsorting
Bubblesort
GettingstartedwithprojectstructureandbuildtoolsMake
AntInstallingAnt
UsingAnt
MavenInstallingMaven
UsingMaven
GradleInstallingGradle
SettinguptheprojectwithMaven
Codingthesort
UnderstandingthealgorithmandlanguageconstructsBlocks
Variables
Types
Arrays
Expressions
Loops
Conditionalexecution
Finalvariables
Classes
Inner,nested,local,andanonymousclasses
Packages
Methods
Interfaces
Argumentpassing
Fields
Modifiers
Objectinitializersandconstructors
Compilingandrunningtheprogram
Summary
3. OptimizingtheSort-MakingCodeProfessionalThegeneralsortingprogram
AbriefoverviewofvarioussortingalgorithmsQuicksort
ProjectstructureandbuildtoolsMavendependencymanagement
CodethesortCreatingtheinterfaces
CreatingBubbleSort
Amendingtheinterfaces
Architecturalconsiderations
CreatingunittestsAddingJUnitasdependency
WritingtheBubbleSortTestclass
GoodunittestsAgoodunittestisreadable
Unittestsarefast
Unittestsaredeterministic
Assertionsshouldbeassimpleaspossible
Unittestsareisolated
Unittestscoverthecode
Refactorthetest
Collectionswithwrongelements
Handlingexceptions
Generics
TestDrivenDevelopment
ImplementingQuickSortThepartitioningclass
Recursivesorting
Non-recursivesorting
ImplementingtheAPIclass
CreatingmodulesWhymodulesareneeded
WhatisaJavamodule
Summary
4. Mastermind-CreatingaGameTheGame
Themodelofthegame
JavacollectionsInterfacecollection
SetHashfunctions
Methodequals
MethodhashCode
ImplementingequalsandhashCode
HashSet
EnumSet
LinkedHashSet
SortedSet
NavigableSetTreeSet
ListLinkedList
ArrayList
Queue
Deque
MapHashMap
IdentityHashMap
Dependencyinjection
ImplementingthegameColorManager
Theclasscolor
JavaDocandcodecomments
Row
Table
GuesserUniqueGuesser
GeneralGuesser
TheGameclass
Creatinganintegrationtest
Summary
5. ExtendingtheGame-RunParallel,RunFasterHowtomakeMastermindparallel
Refactoring
Processes
Threads
Fibers
java.lang.Thread
PitfallsDeadlocks
Raceconditions
Overusedlocks
Starving
ExecutorServiceForkJoinPool
Variableaccess
TheCPUheartbeat
Volatilevariables
Synchronizedblock
Waitandnotify
LockCondition
ReentrantLock
ReentrantReadWriteLock
Atomicclasses
BlockingQueueLinkedBlockingQueue
LinkedBlockingDeque
ArrayBlockingQueue
LinkedTransferQueue
IntervalGuesser
ParallelGamePlayer
Microbenchmarking
Summary
6. MakingOurGameProfessional-DoitasaWebappWebandnetwork
IP
TCP/IP
DNS
TheHTTPprotocolHTTPmethods
Statuscodes
HTTP/2.0
Cookies
Clientserverandwebarchitecture
WritingservletsHelloworldservlet
JavaServerPages
HTML,CSS,andJavaScript
MastermindservletStoringstate
HTTPsession
Storingstateontheclient
DependencyinjectionwithGuice
TheMastermindHandlerclass
Storingstateontheserver
TheGameSessionSaverclass
RunningtheJettywebservlet
LoggingConfigurability
Performance
Logframeworks
Java9logging
Loggingpractice
Othertechnologies
Summary
7. BuildingaCommercialWebApplicationUsingRESTTheMyBusinesswebshop
Samplebusinessarchitecture
Microservices
Serviceinterfacedesign
JSON
REST
ModelViewController
SpringframeworkArchitectureofSpring
Springcore
Serviceclasses
Compilingandrunningtheapplication
TestingtheapplicationIntegrationtest
Applicationtest
Servletfilters
AuditloggingandAOP
Dynamicproxy-basedAOP
Summary
8. ExtendingOurE-CommerceApplicationTheMyBusinessordering
Settinguptheproject
OrdercontrollerandDTOs
Consistencychecker
AnnotationsAnnotationretention
Annotationtarget
Annotationparameters
Repeatableannotations
Annotationinheritance
@Documentedannotations
JDKannotations
UsingreflectionGettingannotations
Invokingmethods
Settingfields
FunctionalprogramminginJavaLambda
Streams
Functionalinterfaces
Methodreferences
ScriptinginJava9
Summary
9. BuildinganAccountingApplicationUsingReactiveProgrammingReactive...what?
Reactiveprogramminginanutshell
ReactivesystemsResponsive
Resilient
Elastic
Message-driven
Back-pressure
Reactivestreams
ReactiveprogramminginJavaImplementinginventory
Summary
10. FinalizingJavaKnowledgetoaProfessionalLevelJavadeeptechnologies
Javaagent
PolyglotprogrammingPolyglotconfiguration
Polyglotscripting
BusinessDSL
Problemswithpolyglot
Annotationprocessing
ProgrammingintheenterpriseStaticcodeanalysis
Sourcecodeversioncontrol
Softwareversioning
Codereview
Knowledgebase
Issuetracking
TestingTypesoftests
Testautomation
Blackboxversuswhitebox
SelectinglibrariesFitforthepurpose
License
Documentation
Projectalive
Maturity
Numberofusers
The"Ilikeit"factor
Continuousintegrationanddeployment
Releasemanagement
Coderepository
Walkinguptheladder
Summary
PrefaceJavadrasticallychangedwiththeintroductionofJava8,andthischangehasbeenelevatedtoawholenewlevelwiththenewversion,Java9.Javahasawell-establishedpast,beingmorethan20yearsold,butatthesametime,itisnew,functional,reactive,andsexy.Thisisalanguagethatdeveloperslove,andatthesametime,itisthenumberonechoiceofdeveloperlanguageformanyenterpriseprojects.
ItisprobablymorelucrativetolearnJavanowthaneverbefore,startingwithJava9.WeencourageyoutostartyourprofessionaldevelopercareerbylearningJava9,andwehavedoneourbestinthisbooktohelpyoualongthisroad.Weassembledthetopicsofthebooksothatitiseasytostart,andyoucanfeelthethingsworkingandmovingverysoon.Atthesametime,wehavetriedtoreachveryfar,signalingtheroadaheadforaprofessionaldeveloper.
Thesandsoftimekeptmoving,andIdiscoveredfunctionalprogramming.
Icouldverywellseewhywritingside-effect-freecodeworked!IwashookedandstartedplayingwithScala,Clojure,andErlang.Immutabilitywasthenormhere.
However,Iwonderedhowtraditionalalgorithmswouldlookinafunctionalsettingandstartedlearningaboutit.
Adatastructureisnevermutatedinplace.Instead,anewversionofthedatastructureiscreated.Thestrategyofcopyandwritewithmaximizedsharingwasanintriguingone!Allthatcarefulsynchronizationissimplynotneeded!
Thelanguagescomeequippedwithgarbagecollection.So,ifaversionisnotneededanymore,runtimewouldtakecareofreclaimingthememory.
Allingoodtime,though!Readingthisbookwillhelpyouseethatweneednotsacrificealgorithmicperformancewhileavoidingin-placemutation!
WhatthisbookcoversChapter1,GettingStartedwithJava9,givesyouajumpstartinJava,helpingyouinstallitonyourcomputerandrunyourfirstinteractiveprogramsusingthenewJshell.
Chapter2,TheFirstRealJavaProgram-SortingNames,teachesyouhowtocreateadevelopmentproject.Thistime,wewillcreateprogramfilesandcompilethecode.
Chapter3,OptimizingtheSort-MakingCodeProfessional,developsthecodefurthersothatthecodeisreusableandnotonlyatoy.
Chapter4,Mastermind-CreatingaGame,iswhensomefunstarts.Wedevelopagameapplicationthatisinterestingandnotastrivialasitfirstseems,butwewilldoit.
Chapter5,ExtendingtheGame-RunParallel,RunFaster,showsyouhowtoutilizethemulti-processorcapabilitiesofmodernarchitecture.Thisisaveryimportantchapterthatdetailstechnologies,thatonlyafewdeveloperstrulyunderstand.
Chapter6,MakingOurGameProfessional-DoitasaWebapp,transformstheuserinterfacefromcommand-linetowebbrowser-based,deliveringbetteruserexperience.
Chapter7,BuildingaCommercialWebApplicationUsingREST,takesyouthroughthedevelopmentofanapplicationthathasthecharacteristicsofmanycommercialapplications.WewillusethestandardRESTprotocolwhichhasgainedgroundinenterprisecomputing.
Chapter8,ExtendingOurE-CommerceApplication,helpsyoudeveloptheapplicationfurtherutilizingmodernlanguagefeaturessuchasscriptingandlambdaexpressions.
Chapter9,BuildinganAccountingApplicationUsingReactiveProgramming,teachesyouhowtoapproachsomeproblemsusingreactiveprogramming.
Chapter10,FinalizingJavaKnowledgetoaProfessionalLevel,givesabird's-eyeviewofdevelopertopicsthatplayanimportantroleinthelifeofaJavadeveloper,andwhichwillguideyoufurtherinworkingasaprofessionaldeveloper.
Whatyouneedforthisbook
Toimmerseintothecontentofthisbookandtosoakupmostoftheskillsandknowledge,weassumethatyoualreadyhavesomeexperiencewithprogramming.Wedonotassumetoomuchbuthopethatyoualreadyknowwhatavariableis,thatcomputershavememory,disk,networkinterfaces,andwhattheygenerallyare.Inadditiontothesebasicskills,therearesometechnicalrequirementstotryoutthecodeandtheexamplesofthebook.Youneedacomputer—somethingthatisavailabletodayandcanrunWindows,Linux,orOSX.Youneedanoperatingsystemand,probably,thatisallyouneedtopayfor.Allothertoolsandservicesthatyouwillneedareavailableasopensourceandfreeofcharge.Someofthemarealsoavailableascommercialproductswithanextendedfeatureset,butforthescopeofthisbook,startingtolearnJava9programming,thosefeaturesarenotneeded.Java,adevelopmentenvironment,buildtools,andallothersoftwarecomponentsweuseareopensource.
WhothisbookisforThisbookisforanyonewhowantstolearntheJavaprogramminglanguage.Youareexpectedtohavesomepriorprogrammingexperiencewithanotherlanguage,suchasJavaScriptorPython,butnoknowledgeofearlierversionsofJavaisassumed.
ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:"Thefollowingfunctionfhasasideeffect,though."
Ablockofcodeissetasfollows:
packagepackt.java9.by.example.ch03;
publicinterfaceSort{
voidsort(SortableCollectioncollection);
}
Ifthereisaline(orlines)ofcodethatneedstobehighlighted,itissetasfollows:
id=123
title=BookJava9byExample
description=anewbooktolearnJava9
weight=300
width=20
height=2
depth=18
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:"ClickingtheNextbuttonmovesyoutothenextscreen."
Warningsorimportantnotesappearinaboxlikethis.
Tipsandtricksappearlikethis.
ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.
Tosendusgeneralfeedback,[email protected],andmentionthebook'stitleinthesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.
CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.
Downloadingtheexamplecode
Youcandownloadtheexamplecodefilesforthisbookfromyouraccountathttps://github.com/PacktPublishing/Java-9-Programming-By-Example.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
Youcandownloadthecodefilesbyfollowingthesesteps:
1. Loginorregistertoourwebsiteusingyoure-mailaddressandpassword.2. HoverthemousepointerontheSUPPORTtabatthetop.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchbox.5. Selectthebookforwhichyou'relookingtodownloadthecodefiles.6. Choosefromthedrop-downmenuwhereyoupurchasedthisbookfrom.7. ClickonCodeDownload.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:
WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux
ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/PacktPublishing/Java-9-Programming-By_Example.Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing/.Checkthemout!
Errata
Althoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.
Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.
PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
Pleasecontactusatcopyright@packtpub.comwithalinktothesuspectedpiratedmaterial.
Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.
QuestionsIfyouhaveaproblemwithanyaspectofthisbook,[email protected],andwewilldoourbesttoaddresstheproblem.
GettingStartedwithJava9
YouwanttolearnJavaandyouhaveagoodreasonforit.Javaisamodernandwell-establishedapplicationprogramminglanguage,whichiswidelyusedinmanyindustries,beittelecommunication,finance,orsomethingelse.Javadeveloperpositionsarethemostnumerousand,probably,thebestpaid.This,amongotherthings,makesthelanguagelucrativeforyoungprofessionalstolearn.Ontheotherhand,thisisnotwithoutreason.Javalanguage,thetools,andthewholeinfrastructurearounditiscomplexandcompound.BecomingaJavaprofessionaldoesnothappeninadayorweek;itisaworkofmanyyears.TobeaJavaexpert,youneedtoknownotonlyabouttheprogramminglanguagebutalsoaboutobject-orientedprogrammingprinciples,opensourcelibraries,applicationservers,network,databases,andmanyotherthingsthatyoucanbecomeanexpertin.Nevertheless,learningthelanguageisanabsolutemustthatallotherpracticesshouldbuildon.Throughthisbook,youwillbeabletolearnJavaversion9andabitmore.
Inthischapter,youwillbeintroducedtotheJavaenvironmentandgivenstep-by-stepinstructionsonhowtoinstallit,editsamplecode,compile,andrunJava.Youwillgetacquaintedwiththebasictoolsthathelpdevelopment,betheyareapartofJavaorareprovidedbyothervendors.Wewillcoverthefollowingtopicsinthischapter:
IntroductiontoJavaInstallingWindows,Linux,andMacOSXExecutingjshellUsingotherJavatoolsUsingintegrateddevelopmentenvironment
GettingstartedwithJavaItislikegoingthroughapathinaforest.Youcanfocusonthegraveloftheroadbutitispointless.Instead,youcanenjoytheview,thetrees,thebirds,andtheenvironmentaroundyou,whichismoreenjoyable.ThisbookissimilarasIwon'tbefocusingonlyonthelanguage.Fromtimetotime,Iwillcovertopicsthatareclosetotheroadandwillgiveyousomeoverviewanddirectionsonwhereyoucangofurtherafteryoufinishthisbook.Iwillnotonlyteachyouthelanguagebutalsotalkabitaboutalgorithms,object-orientedprogrammingprinciples,toolsthatsurroundJavadevelopment,andhowprofessionalswork.Thiswillbemixedwiththecodingexamplesthatwewillfollow.Lastly,thefinalchapterwillbefullydevotedtothetopic,whattolearnnextandhowtogofurthertobecomeaprofessionalJavadeveloper.
Bythetimethisbookgetsintoprint,Javawillhavecompleted22years.http://www.oracle.com/technetwork/java/javase/overview/javahistory-index-198355.html.Thelanguagehaschangedalotduringthisperiodandgotbetter.Therealquestiontoaskisnothowlonghasitbeenhere,butratherhowlongwillitstay?Isitstillworthlearningthislanguage?TherearenumerousnewlanguagesthathavebeendevelopedsinceJavawasborn(http://blog.takipi.com/java-vs-net-vs-python-vs-ruby-vs-node-js-who-reigns-the-job-market/).Theselanguagesaremoremodernandhavefunctionalprogrammingfeatures,which,bytheway,Javahasalsohadsinceversion8.ManysaythatJavaisthepast—thefutureisScala,Swift,Go,Kotlin,JavaScript,andsoon.Youcanaddmanyotherlanguagestothislist,andforeach,youcanfindablogarticlethatcelebratestheburialofJava.Therearetwoanswerstothisconcern-oneisapragmaticbusinessapproach,theotherismoreengineering:
ConsideringthatCOBOLisstillactivelyusedinthefinanceindustryandCOBOLdevelopersareperhapsbetterpaidthanJavadevelopers,itisnottooriskytosaythatasaJavadeveloper,youwillfindpositionsinthenext40years.Personally,Iwouldbetmorethana100years,butconsideringmyage,itwillnotbefairpredictingmorethan20to40yearsahead.Javaisnotonlyalanguage;itisalsoatechnologythatyouwilllearnabitaboutfromthisbook.ThetechnologyincludestheJavaVirtualMachine(JVM),whichisusuallyreferredtoasJVM,andgivestheruntimeenvironmentformanylanguages.KotlinandScala,forexample,cannotrunwithoutJVM.EvenifJavawillbeadumbrated,JVMwillstillbeanumberoneplayerintheenterprisescene.
TounderstandandlearnthebasicoperationofJVMisalmostasimportantasthelanguageitself.Javaisacompiledandinterpretedlanguage.Itisaspecialbeastthatforgesthebestofbothworlds.BeforeJava,therewereinterpretedandcompiledlanguages.
Interpretedlanguagesarereadfromthesourcecodebytheinterpreterandthentheinterpreterexecutesthecode.Ineachoftheselanguages,thereissomepreliminarylexicalandsyntaxanalysisstep;however,afterthat,theinterpreter,which,asaprogramitself,isexecutedbytheprocessorandtheinterpretercontinuously,interpretstheprogramcodetoknowwhattodo.Compiledlanguagesaredifferent.Insuchacase,thesourcecodeiscompiledtobinary(.exefileonWindowsplatforms),whichtheoperatingsystemloadsandtheprocessordirectlyexecutes.Compiledprogramsusuallyrunfaster,butthereisusuallyaslowercompilationphasethatmaymakethedevelopmentslower,andtheexecutionenvironmentisnotso
flexible.Javacombinedthetwoapproaches.
ToexecuteaJavaprogram,theJavasourcecodehastobecompiledtotheJVMbytecode(.classfile),whichisloadedbyJVMandisinterpretedorcompiled.Hmm...isitinterpretedorcompiled?ThethingthatcamewithJavaistheJustinTime(JIT)compiler.Thismakesthephaseofthecompilationthatiscalculation-intensiveandthecompilationforcompiledlanguagesrelativelyslow.JVMfirststartstointerprettheJavabytecodeand,whiledoingthat,itkeepstrackofexecutionstatistics.Whenitgathersenoughstatisticsaboutcodeexecutions,itcompilestonativecode(forexample,x86codeonanIntel/AMDplatform)fordirectexecutionofthepartsofcodethatareexecutedfrequentlyandkeepsinterpretingthecodefragmentsthatarerarelyused.Afterall,whywasteexpensiveCPUtimetocompilesomecodethatishardlyeverused?(Forexample,codethatreadsconfigurationduringstartupanddoesnotexecuteagainunlesstheapplicationserverisrestarted.)Compilationtothebytecodeisfastandcodegenerationisdoneonlyforthesegmentsthatpayoff.
ItisalsointerestingthatJITusesthestatisticsofthecodeexecutiontooptimizethecode.If,forexample,itseesthatsomeconditionalbranchisexecutedin99%ofthecasesandtheotherbranchisexecutedonlyin1%,thenitwillgeneratenativecodethatrunsfast,thusfavoringthefrequentbranch.Ifthebehaviorofthatpartoftheprogramchangesbytimeandthestatisticshowsthattheratioschanged,theJITautomaticallyrecompilesthebytecodefromtimetotime.Thisisallautomaticandbehindthescenes.
Inadditiontotheautomaticcompilation,thereisalsoanextremelyimportantfeatureofJVM-itmanagesthememoryfortheJavaprogram.TheexecutionenvironmentofmodernlanguagesdothatandJavawasthefirstmainstreamlanguagethathadanautomaticgarbagecollection(GC).BeforeJava,IwasprogramminginCfor20yearsanditwasagreatpaintokeeptrackofallmemoryallocationandnottoforgettoreleasethememorywhentheprogramnolongerneededit.Forgettingmemoryallocationatasinglepointinthecodeandthelongrunningprogramwaseatingupallmemoryslowly.SuchproblemspracticallyceasedtoexistinJava.Thereisapricethatwehavetopayforit—GCneedsprocessorcapacityandsomeextramemory,butthatissomethingwearenotshortofinmostoftheenterpriseapplications.Somespecialprograms,likereal-timeembeddedsystemsthatcontrolthebrakesofaheavy-dutylorrymaynothavethatluxury.ThosearestillprogrammedinassemblyorC.Fortherestofus,wehaveJava,andthoughitmayseemstrangeformanyprofessionals,evenalmost-real-timeprograms,suchashigh-frequencytradingapplications,arewritteninJava.
Theseapplicationsconnectthroughthenetworktothestockexchangeandtheysellandbuystocksrespondingtomarketchangeinmilliseconds.Javaiscapableofdoingthat.TheruntimeenvironmentofJavathatyouwillneedtoexecuteacompiledJavacode,whichalsoincludestheJVMitself,containscodethatletsJavaprogramsaccessthenetwork,filesondisks,andotherresources.Todothis,theruntimecontainshigh-levelclassesthatthecodecaninstantiate,execute,andwhichdothelow-leveljobs.Youwillalsodothis.ItmeansthattheactualJavacodedoesnotneedtohandleIPpackets,TCPconnections,orevenHTTPhandlingwhenitwantstouseorprovideaRESTserviceinsomemicroservicesarchitecture.Itisalreadyimplementedintheruntimelibraries,andalltheapplicationprogrammerhastodoistoincludetheclassesinthecodeandusetheAPIstheyprovideonanabstractionlevelthatmatchestheprogram.WhenyouprograminJava,youcanfocusontheactualproblemyouwanttosolve,whichisthebusinesscodeandnotthelow-levelsystemcode.Ifitisnotinthestandardlibrary,youwillfinditinsomeproductinsomeexternallibrary,anditisalsoveryprobablethatyouwillfindanopensourcesolutionfortheproblem.
ThisisalsoastrongpointofJava.Thereisavastnumberofopensourcelibrariesavailableforallthedifferentpurposes.Ifyoucannotfindalibraryfittingyourproblemifyoustarttocodesomelow-levelcode,thenprobablyyouaredoingsomethingwrong.Therearetopicsinthisbookthatareimportant,suchasclassloadersorreflection,notbecauseyouhavetousethemeverydaybutratherbecausetheyareusedbyframeworks,andknowingthemhelpsunderstandhowtheseframeworkswork.Ifyoucannotsolveyourproblemwithoutusingreflectionorwritingyourownclassloaderorprogrammultithreaddirectly,thenyouprobablychosethewrongframework.Thereisalmostcertainlyagoodone:Apacheproject,Google,andmanyotherimportantplayersinthesoftwareindustrypublishtheirJavalibrariesasopensource.
Thisisalsotrueformultithreadprogramming.Javaisamultithreadprogrammingenvironmentfromtheverybeginning.TheJVMandtheruntimesupportsprogramsthatexecutethecode.Theexecutionrunsparallelonmultiplethreads.Thereareruntimelanguageconstructsthatsupportparallelexecutingprogramsstartingattheverylowleveltohighabstraction.Multithreadcodeutilizesthemulticoreprocessors,whicharemoreeffective.Theseprocessorsaremoreandmorecommon.20yearsago,onlyhigh-endservershadmultipleprocessorsandonlyDigitalAlphaprocessorshad64-bitarchitectureandCPUclockabove100MHz.10yearsago,multiprocessorstructurewascommonontheserverside,andabout5yearsago,multicoreprocessorswereonsomedesktopsandonnotebooks.Today,evenmobilephoneshavethem.WhenJavawasstartedin1995,thegeniuseswhocreatedithadseenthisfuture.
TheyenvisionedJavatobeawriteonce,runanywherelanguage.Atthattime,thefirsttargetforthelanguagewasappletrunninginthebrowser.Today,manythink(andIalsosharethisopinion)thatappletswereawrongtarget,oratleastthingswerenotdoneintherightway.Asfornow,youwillmeetappletsontheInternetlessfrequentlythanFlashapplicationsordinosaurs.
However,atthesametime,theJavainterpreterwasalsoexecutingserverandclientapplicationswithoutanybrowser;furthermore,asthelanguageandtheexecutingenvironmentdeveloped,theseapplicationareasbecamemoreandmorerelevant.Today,themainuseofJavaisenterprisecomputingandmobileapplicationsmainlyfortheAndroidplatform;forthefuture,theuseoftheenvironmentisgrowinginembeddedsystemsastheInternetofthings(IoT)comesmoreandmoreintopicture.
InstallingJavaTodevelop,compile,andexecuteJavaprograms,youwillneedtheJavaexecutionenvironment.Astheoperatingsystemsthatweusuallyuseforsoftwaredevelopmentdonotcontainthelanguagepreinstalled,youwillhavetodownloadit.Although,thereismultipleimplementationofthelanguage,IrecommendthatyoudownloadtheofficialversionofthesoftwarefromOracle.Theofficialsiteforjavaishttp://java.comandthisisthesitefromwherethelatestreleaseofthelanguagecanbedownloaded.Atthetimeofwritingthisbook,the9thversionofJavaisnotyetreleased.Anearlypre-releaseversionisaccessibleviahttp://jdk9.java.net/download.Laterthereleaseversionswillalsobeavailablefromhere.
Whatyoucandownloadfromhereisasocalledearlyaccessversionofthecodethatisavailableonlytoexperimentwithit,andnoprofessionalsshoulduseitforrealprofessionalpurposes
Onthepage,youhavetoclickontheradiobuttontoacceptthelicense.Afterthat,youcanclickonthe
linkthatdirectlystartsthedownloadoftheinstallationkit.Thelicenseisaspecialearlyaccesslicenseversionthatyou,asaprofessional,shouldcarefullyread,understand,andacceptonlyifyouareagreeablewiththeterms.
ThereisaseparateinstallationkitforWindows32and64bitsystems,MacOSX,Linux32and64bitversions,LinuxforARMprocessor,SolarisforSPARCprocessorsystems,andSolarisx86versions.AsitisnotlikelythatyouwilluseSolaris,IwilldetailtheinstallationprocedureonlyforWindows,Linux,andMacOSX.Inlaterchapters,thesampleswillalwaysbeMacOSX,butsinceJavaisawriteonce,runanywherelanguage,thereisnodifferenceaftertheinstallation.Thedirectoryseparatormaybeslanteddifferently,theclasspathseparatorcharacterisasemicolononWindowsinsteadofacolon,andthelookandfeeloftheTerminalorcommandapplicationisalsodifferent.However,whereitisimportant,Iwilltrynottoforgettomentionit.
Toconfuseyou,theJavadownloadforeachoftheseoperatingsystemversionslistsalinkfortheJREandonefortheJDK.JREstandsforJavaRuntimeEnvironmentanditcontainsallthetoolsandexecutablesthatareneededtorunJavaprograms.JDKistheJavaDevelopmentKitthatcontainsallthetoolsandexecutablesthatareneededtodevelopJavaprogramsincludingtheexecutionoftheJavaprogram.Inotherwords,JDKcontainsitsownJRE.Fornow,allyouneedtodoisdownloadtheJDK.
Thereisoneimportantpointoftheinstallationthatisthesameoneachofthethreeoperatingsystemsthatyouhavetobepreparedforbeforetheinstallation:toinstallJava,youshouldhaveadministrativeprivileges.
InstallationonWindowsTheinstallationprocessonWindowsstartsbydoubleclickingonthedownloadedfile.Itwillstarttheinstallerthatwillpresentyouawelcomescreen.
PressingtheNextbuttonwegetawindowwhereyoucanselectthepartsyouwanttoinstall.Let'sleaveherethedefaultselection,whichmeansthatweinstallallthedownloadedpartsofJavaandpressthebuttonNext.Thefollowingwindowiswherewecanselectthedestinationfolderfortheinstallation.
Asfornowwedonotchangethedirectoryselectedbytheinstaller.PressNext.Later,whenyoubecomeaprofessionalJavadeveloper,youmaydecidetoinstallJavatoadifferentlocationbutthenyouwillalreadyhavetoknowwhatyouaredoing.
YoumayneedtoclicktheNextbuttonafewtimesandthentheinstallerfinishes.Providetheadministrativepasswordwhenaskedandvoila!Javaisinstalled.ThisisreallytheveryusualWindowsinstallationprocess.
ThelaststepistosettheenvironmentvariableJAVA_HOME.TodothatinWindowswehavetoopenthe
controlcenterandselecttheEditenvironmentvariablesforyouraccountmenu.
Thiswillopenanewwindowthatweshouldusetocreateanewenvironmentvariableforthecurrentuser.
ThenameofthenewvariablehastobeJAVA_HOMEandthevalueshouldpointtotheinstallationdirectoryoftheJDK.
ThisvalueonmostofthesystemsisC:ProgramFilesJavajdk-9.ThisisusedbymanyJavaprogramsandtoolstolocatetheJavaruntime.
InstallationonMACOSXInthissection,wewilltakelookathowtoinstallJavastep-by-steponanOSXplatform.Iwilldescribetheinstallationprocessforthereleasedversionavailableatthetimeofwritingthisbook.Asfornow,theJava9earlyaccessversionisabittrickytoinstall.ItisprobablethatversionJava9willhavesimilarorthesameinstallstepsasJava8update92has.
TheOSXversionofJavacomesintheformofa.dmgfile.ThisisapackagingformatofOSX.Toopenit,simplydoubleclickonthefileintheDownloadfolderwherethebrowsersavesitandtheoperatingsystemwillmountthefileasaread-onlydiskimage.
Thereisonlyonefileonthisdisk:theinstallationimage.DoubleclickonthefilenameoriconintheFinderapplicationandtheinstallationprocesswillstart.
Thefirstscreenopeningisawelcomescreen.ClickContinueandyouwillseetheSummarypagethatdisplayswhatwillbeinstalled.
ItisnotasurprisethatyouwillseeastandardJavainstallation.Thistime,thebuttoniscalledInstall.Clickonitandyouwillseethefollowing:
Thisisthetimewhenyouhavetoprovidetheloginparametersfortheadministrativeuser—ausernameandpassword.Whenprovided,installationstartsand,inafewseconds,youwillseeaSummarypage.
ClickCloseandyouareready.YouhaveJavainstalledonyourMac.Optionally,youcandismounttheinstallationdiskand,sometimelater,youcanalsodeletethe.dmgfile.Youwillnotneedthat,andincaseyoudo,youcandownloaditanytimefromOracle.
Thelastthingistocheckwhethertheinstallationwasokay.Proofofthepuddingiseatingit.StartaTerminalwindowandtypejava-versionatthepromptandJavawilltellyoutheversioninstalled.
OnthenextscreenshotyoucanseetheoutputonmyworkstationandalsotheMacOScommandsthatarehandytoswitchbetweenthedifferentversionsofJava:
Onthescreenshot,youcanseethatIhaveinstalledtheJavaJDK1.8u92versionand,atthesametime,IalsohaveaJava9earlyreleaseinstallation,whichIwillusetotestthenewfeaturesofJavaforthisbook.
InstallationonLinuxThereareseveralwaystoinstallJavaonLinux,dependingonitsflavor.Here,Iwilldescribeaninstallationmethodthatworksmoreorlessthesamewayonallflavors.TheoneIusedisDebian.
Firststepisthesameasinanyotheroperatingsystem:downloadtheinstallationkit.InthecaseofLinux,youshouldselectapackagethathasatar.gzending.Thisisacompressedarchiveformat.Youshouldalsocarefullyselectthepackagethatmatchestheprocessorinyourmachineandthe32/64bitversionoftheoperatingsystem.Afterthepackageisdownloaded,youhavetoswitchtorootmode,issuingthesucommand.Thisthefirstcommandyoucanseeonthescreenshotthatshowstheinstallationcommands.
Thetarcommanduncompressedthearchiveintoasubfolder.InDebian,thissubfolderhastobemovedto/opt/jdkandthemvcommandisusedforthispurpose.Thetwoupdate-alternativescommandisDebian-specific.ThesetelltheoperatingsystemtousethisnewlyinstalledJavaincasethereisalreadyanolderJavainstalled.TheDebianIwasusingtotestanddemonstratetheinstallationprocessonavirtualmachinecamewitha7yearoldversionofJava.
Thefinalstepoftheinstallationisthesameasanyotheroperatingsystem:checkingthattheinstallationwassuccessfulinissuingthejava-versioncommand.InthecaseofLinux,thisisevenmoreimportantbecausetheinstallationprocessdoesnotcheckthatthedownloadedversionmatchestheoperatingsystemandtheprocessorarchitecture.
SettingJAVA_HOMETheJAVA_HOMEenvironmentvariableplaysaspecialroleforJava.EventhoughtheJVMexecutable,java.exeorjava,isonthePATH(thusyoucanexecuteitbytypingthenamejavawithoutspecifyingdirectoryontheCommandPrompt)(Terminal),itisrecommendedthatyouusethecorrectJavainstallationtosetthisenvironmentvariable.ThevalueofthevariableshouldpointtotheinstalledJDK.TherearemanyJava-relatedprograms,forexample,TomcatorMaven,thatusethisvariabletolocatetheinstalledandcurrentlyusedJavaversion.InMacOSX,settingthisvariableisunavoidable.
InOSX,theprogramthatstartstoexecutewhenyoutypejavaisawrapperthatfirstlooksatJAVA_HOMEtodecidewhichJavaversiontostart.Ifthisvariableisnotset,thenOSXwilldecideonitsown,selectingfromtheavailableinstalledJDKversions.Toseetheavailableversions,youcanissuethefollowingcommand:~$/usr/libexec/java_home-VMatchingJavaVirtualMachines(10):9,x86_64:"JavaSE9-ea"/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home1.8.0_92,x86_64:"JavaSE8"/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home1.7.0_60,x86_64:"JavaSE7"/Library/Java/JavaVirtualMachines/jdk1.7.0_60.jdk/Contents/Home/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home
YouwillthengetthelistofinstalledJDKs.Notethatthecommandislowercase,buttheoptioniscapital.Ifyoudonotprovideanyoptionsandargumenttotheprogram,itwillsimplyreturntheJDKitthinksisthenewestandmostappropriateforthepurpose.AsIcopiedtheoutputofthecommandfrommyTerminalwindow,youcanseethatIhavequiteafewversionsofJavainstalledonmymachine.
ThelastlineoftheprogramresponseisthehomedirectoryofJDK,whichisthedefault.YoucanusethistosetyourJAVA_HOMEvariableusingsomebashprogramming:exportJAVA_HOME=$(/usr/libexec/java_home)
Youcanplacethisfileinyour.bashrcfile,whichisexecutedeachtimeyoustartTerminalapplicationandthusJAVA_HOMEwillalwaysbeset.Ifyouwanttouseadifferentversion,youcanuse-v,withthelowercaseoptionthistime,tothesameutility,asfollows:exportJAVA_HOME=$(/usr/libexec/java_home-v1.8)
TheargumentistheversionofJavayouwanttouse.Notethatthisversioningbecomes:
exportJAVA_HOME=$(/usr/libexec/java_home-v9)
IfyouwanttouseJavaJDKEarlyAccessversionandnot1.9,thereisnoexplanationforthesame—factoflife.
NotethatthereisanotherenvironmentvariablethatisimportantforJava-CLASSPATH.Wewilltalkaboutitlater.
ExecutingjshellNowthatwehavespentalotoftimeinstallingJava,itistimetogetthefingersburntabit.AsweareusingJava9,thereisanewtoolthathelpsdeveloperstoplayaroundwiththelanguage.ThisisaRead-Eval-Print-Loop(REPL)toolthatmanylanguagetoolsetscontainandtherewerealsoimplementationsfromJava,butversion9isthefirstthatcontainsthisfeatureofftheshelf.
REPLisatoolthathasinteractivepromptandlanguagecommandsthatcanbedirectlyenteredwithouteditingsomestandalonefile.Theenteredcommandsareexecuteddirectlyandthentheloopstartsagain,waitingfortheusertotypeinthenextcommand.Thisisaveryeffectivetooltotryoutsomelanguageconstructswithoutthedelayofediting,compiling,andloading.ThestepsareautomaticallyandtransparentlydonebytheREPLtool.
TheREPLtoolinJava9iscalledjshell.Tostartit,justtypeitsname.IfitisnotonthePATH,thentypethefullpathtojshellthatcomesinstalledwithJava9,asshowninthefollowingexample:
$jshell
|WelcometoJShell--Version9-ea
|Foranintroductiontype:/helpintro
jshell>
Thejshellstartsupinaninteractivewayandthepromptitdisplaysisjshell>tohelpyourecognizethatjshellisrunningandwhatyoutypeisreadbytheprogramandnottheoperatingsystemshell.Asthisisthefirsttimeyouwillstartjshell,ittellsyoutotype/helpintro.Let'sdoit.Itwillprintoutashorttextaboutwhatjshellis,asshowninthefollowingcode:
jshell>/helpintro
|
|intro
|
|ThejshelltoolallowsyoutoexecuteJavacode,gettingimmediateresults.
|YoucanenteraJavadefinition(variable,method,class,etc),like:intx=8
|oraJavaexpression,like:x+x
|oraJavastatementorimport.
|TheselittlechunksofJavacodearecalled'snippets'.
|
|Therearealsojshellcommandsthatallowyoutounderstandand
|controlwhatyouaredoing,like:/list
|
|Foralistofcommands:/help
Okay,sowecantypeJavasnippetsand/list,butthatisonlyoneexampleoftheavailablecommands.Wecanhopeformoreinformationbytyping/help,asshowninthefollowingcode:
jshell>/help
|TypeaJavalanguageexpression,statement,ordeclaration.
|Ortypeoneofthefollowingcommands:
|/list[<nameorid>|-all|-start]--listthesourceyouhavetyped
|/edit<nameorid>--editasourceentryreferencedbynameorid
|/drop<nameorid>--deleteasourceentryreferencedbynameorid
|/save[-all|-history|-start]<file>--Savesnippetsourcetoafile.
...
Whatyougetisalonglistofcommands.Mostofitisnotpresentedheretosavepaperandyourattention.Wewillusemanyofthesecommandsonourjourneythroughthenextfewpages.Let'sstartwithasmallJavasnippetthatistheagelessHelloWorldexample:
jshell>System.out.println("HelloWorld!")
HelloWorld!
ThisistheshortesteverHelloWorldprograminJava.TillJava9,ifyouwantedtodonothingmorethanprintoutHelloWorld!,youhadtocreateaprogramfile.Ithadtocontainthesourcecodeofaclassincludingthepublicstaticmainmethod,whichcontainedtheonelinewehadtotypeinwithJava9jshell.Itwascumbersomejustforasimpleprintoutofsamplecode.Nowitismucheasierandjshellisalsolenient,forgivingusthemissingsemicolonattheendoftheline.
Thenextthingweshouldtryisdeclaringavariable,asfollows:
jshell>inta=13
a==>13
jshell>
Wedeclaredavariable,nameda,andassignedthevaluetoit-13.Thetypeofthevariableisint,whichisanabbreviationforintegertypesinJava.Nowwehavethisvariablealreadyinoursnippet,sowecanprintitoutifwewanttoasshown:
jshell>System.out.println(a)
13
Itistimetowritesomethingmorecomplexintojshellthanaoneliner.
jshell>voidmain(String[]args){
...>System.out.println("HelloWorld")
...>}
|Error:
|';'expected
|System.out.println("HelloWorld")
|
Thejshellrecognizesthatthisisnotaone-linerandthatitcannotprocesswhatwetypedsofar,whenwepressEnterattheendofthefirstline,anditsignalsthatitexpectsmorecharactersfromus,soitdisplays...>asacontinuationprompt.Wetypeinthecommandsthatmakeupthewholehelloworldmainmethod,butthistimejshelldoesnotletusmissthesemicolon.Thatisallowedonlyinthecaseofone-linesnippets.Asjshellisinteractive,itiseasytocorrectthemistake;presstheuparrowkeyafewtimestogetbackthepreviouslinesand,thistime,addthesemicolonattheendofthesecondline:
jshell>voidmain(String[]args){
...>System.out.println("HelloWorld");
...>}
|createdmethodmain(String[])
Thismethodwascreatedforusasasnippetandnowwecancallit:
jshell>main(null)
HelloWorld
Anditworks.Youcanlistallthesnippetsthatwerecreated,asfollows:
jshell>/list
1:System.out.println("HelloWorld!")
2:inta=13;
3:System.out.println(a)
4:voidmain(String[]args){
System.out.println("HelloWorld");
}
And,aswewanttogoonwritingafullJavaversionofhelloworld,wecansaveourworkfromjshelltoafile,asfollows:
jshell>/saveHelloWorld.java
Finally,weexitedfromjshellbytyping/exit.Asyougetbacktothesystemprompt,typecatHelloWorld.java(ortypeHelloWorld.javaonWindows)toseethecontentofthefile.Itisasfollows:
$catHelloWorld.java
System.out.println("HelloWorld!")
inta=13;
System.out.println(a)
voidmain(String[]args){
System.out.println("HelloWorld");
}
Thefilecontainsallthesnippetsthatwetypedinoneaftertheother.Ifyouthinkthatyouhavemesseduptheshellwithlotsofvariablesandcodesnippetsthatyoudonotneedanymore,youcanissuethe/resetcommand:
jshell>/reset
|Resettingstate.
Afterthiscommand,thejshellisascleanaswhenitwasstartedearlier
jshell>/list
jshell>
Listingjustdoesnotproduceanythingaswedeleteditall.Fortunately,wesavedthestateofjshelltoafileandwecanalsoloadthecontentofthefileissuingthe/opencommand:
jshell>/openHelloWorld.java
HelloWorld!
13
ItloadsthelinefromthefileandexecutesitjustasthecharactersweretypedintotheCommandPrompt.
Youmayrecallthatthe/listcommandprintedanumberinfrontofeachsnippet.Wecanuseittoeditthesnippetsindividually.Todoso,issuethe/editcommandfollowedbythenumberofthesnippet:
jshell>/edit1
YoumayrecallthatthefirstcommandweenteredwastheSystem.out.printlnsystemcallthatprintsouttheargumenttotheconsole.WhenyoupressEnterafterthe/edit1command,youdonotgetthepromptback.Instead,jshellopensaseparategraphicaleditorthatcontainsthesnippettoeditasshowninthefollowingimage:
Editthetextintheboxsothatitwilllooklikethis:
printf("HelloWorld!")
ClickonAcceptandthenExit.WhenyouclickonAccept,theTerminalwillexecutethesnippetanddisplaythefollowingresult:
HelloWorld!
Themethodthatweused,printf,standsforformattedprinting.Thismaybewellknownfrommanyotherlanguages.ItwasfirstintroducedbytheClanguageandthoughcryptic,thenamesurvived.ThisisalsopartofthestandardJavaclass,PrintStream,justlikeprintln.Incaseofprintln,wehadtowriteSystem.outinfrontofthemethodname.Incaseofprintf,wedidnot.Why?
Thereasonisthatjshelldefinesafewsnippetsthatareautomaticallyloadedwhenjshellstartsorresets.Youcanseetheseifyouissuethe/listcommandwiththe-startoption,asfollows:
jshell>/list-start
s1:importjava.util.*;
s2:importjava.io.*;
s3:importjava.math.*;
s4:importjava.net.*;
s5:importjava.util.concurrent.*;
s6:importjava.util.prefs.*;
s7:importjava.util.regex.*;
s8:voidprintf(Stringformat,Object...args){System.out.printf(format,args);}
Thesepredefinedsnippetshelptheuseofjshell.Mostoftheuserswillimporttheseclasses,andtoeasetheprinttoscreen,itdefinesamethodsnippetthathappenstohavethename,printf,whichisalsothenameofamethodinthePrintStreamclass.
Ifyouwanttolistallthesnippetsyouenteredaswellasthepredefinedsnippetsandalsothosethatcontainedsomeerrorandthuswerenotexecuted,youcanusethe-alloptiontothe/listcommand,asfollows:
jshell>/list-all
...
s7:importjava.util.regex.*;
...
1:System.out.println("HelloWorld!")
...
e1:System.out.println("HelloWorld!")
inta=14;
5:System.out.println("HelloWorld!");
...
Someofthelinesweredeletedfromtheactualoutputforbrevity.Thelinesthatarepreloadedarenumberedwiththesprefix.Thesnippetsthatcontainanerrorhaveanumberprefixedwithe.
Ifyouwanttoexecutesomeofthesnippetsagain,youonlyhavetotype/nwherenisthenumberofthesnippet,asfollows:
jshell>/1
System.out.println("HelloWorld!")
HelloWorld!
Youcannotre-executethepreloadedsnippetsorsnippetsthatcontainederrors.Thereisnoneedforanyofthoseanyway.Preloadedsnippetsdeclaresomeimportsanddefineasnippetmethod;erroneoussnippetsdonotexecutebecausetheyare,well...erroneous.
Youneednotrelyonthenumberofjshellwhenyouwanttore-executeasnippet.Whenyoualreadyhavealotofsnippetsinyourjshellsession,listingthemallwouldbetoocumbersome;thereisashortcuttore-executethelastn-thsnippet.Youhavetowrite/-n.Here,nisthenumberofthesnippetcountingfromthelastone.So,ifyouwanttoexecutetheverylastsnippet,thenyouhavetowrite/-1.Ifyouwanttoexecutetheonebeforethelastone,youhavetowrite/-2.Notethatifyoualreadytyped/-1,thenthelastoneisthere-executionofthelastsnippetandsnippetnumber-2willbecomenumber-3.
Listingallthesnippetscanalsobeavoidedinotherways.Whenyouareinterestedonlyincertaintypesofsnippets,youcanhavespecialcommands.
Ifwewanttoseeonlythevariablesthatwedefinedinthesnippets,thenwecanissuethe/varscommand,asfollows:
jshell>/vars
|inta=13
Ifwewanttoseeonlytheclasses,thecommand/typeswilldothat:
jshell>classs{}
|createdclasss
jshell>/types
|classs
Here,wejustcreatedanemptyclassandthenwelistedit.
Tolistthemethodsthatweredefinedinthesnippets,the/methodscommandcanbeissued:
jshell>/methods
|printf(String,Object...)void
|main(String[])void
Youcanseeintheoutputthatthereareonlytwomethods,whichareasfollows:
printf:Thisisdefinedinapreloadedsnippetmain:This,wedefined
Ifyouwanttoseeeverythingyoutyped,youhavetoissuethe/historycommandforallthesnippetsandcommandsthatyoutyped.(Iwillnotcopytheoutputhere;Idonotwanttoshamemyself.Youshouldtryyourselfandseeyourownhistory.)
Recallthatwecandeleteallthesnippetsissuingthe/resetcommand.Youcanalsodeletesnippetsindividually.Todoso,youshouldissuethe/dropncommand,wherenisthesnippednumber:
jshell>/drop1
|Thiscommanddoesnotacceptthesnippet'1':System.out.println("HelloWorld!")
|See/types,/methods,/vars,or/list
Oops!Somethingwentwrong.Thereisnothingdefinedwhensnippetnumber1wasexecutedandthe/drop
commandactuallydropsthedefinedvariable,type,ormethod.Thereisnothingtobedroppedinthefirstsnippet.But,ifwereissuethe/listcommand,wewillgetthefollowingresults:
jshell>/list
1:System.out.println("HelloWorld!")
2:inta=13;
3:System.out.println(a)
4:voidmain(String[]args){
System.out.println("HelloWorld");
}
Wecanseethatwecandropthesecondorthefourthsnippet,too:
jshell>/drop2
|droppedvariablea
jshell>/drop4
|droppedmethodmain(String[])
Thejshellerrormessagesaystoseetheoutputofthe/types,/methods,/vars,or/listcommands.Theproblemwiththisisthat/types,/methods,and/varsdonotdisplaythenumberofthesnippet.ThisismostprobablyasmallbuginthejshellprereleaseversionandmaybefixedbythetimetheJDKisreleased.
Whenwewereeditingthesnippets,jshellopenedaseparategraphicaleditor.Itmayhappenthatyouarerunningjshellusingsshonaremoteserverandwhereitisnotpossibletoopenaseparatewindow.Youcansettheeditorusingthe/setcommand.Thiscommandcansetquiteafewconfigurationoptionsofthejshell.Tosettheeditortousetheubiquitousvi,issuethefollowingcommand:
jshell>/seteditor"vi"
|Editorsetto:vi
Afterthis,jshellwillopenthesnipped-inviinthesameTerminalwindowwhereyouissuethe/editcommand.
Itisnotonlytheeditorthatyoucanset.Youcansetthestartupfile,andthewayjshellprintsthefeedbacktotheconsoleafteracommandwasexecuted.
Ifyousetthestartupfile,thenthecommandslistedinthestartupfilewillbeexecutedinsteadofthebuilt-incommandsofjshellafterthe/resetcommand.Thisalsomeansthatyouwillnotbeabletousetheclassesdirectlythatareimportedbydefaultandyouwillnothavetheprintfmethodsnippet,unlessyourownstartupfilecontainstheimportsandthedefinitionofthesnippet.
Createthesample.startupfilewiththefollowingcontent:
voidprintln(Stringmessage){System.out.println(message);}
Startingupanewjshellandexecutingitisdoneasfollows:
jshell>/setstartsample.startup
jshell>/reset
|Resettingstate.
jshell>println("wuff")
wuff
jshell>printf("Thiswon'twork...")
|Error:
|cannotfindsymbol
|symbol:methodprintf(java.lang.String)
|printf("Thiswon'twork...")
|^----^
Theprintlnmethodisdefinedbuttheprintfmethod,whichwasdefinedinthedefaultstartup,isnot.
Thefeedbackdefinesthepromptjshellprintsandthenwaitsfortheinput,thepromptforthecontinuationlines,andthemessagedetailsaftereachcommand.Therearepredefinedmodes,whichareasfollows:
NormalSilentConciseVerbose
Normalisselectedbydefault.Ifyouissue/setfeedbacksilent,thenpromptbecomes->andjshellwillnotprintdetailsaboutthecommands.The/setfeedbackconcisecodeprintsabitmoreinformationand/setfeedbackverboseprintsverboseinformationaboutthecommandsexecuted:
jshell>/setfeedbackverbose
|Feedbackmode:verbose
jshell>intz=13
z==>13
|modifiedvariablez:int
|updateoverwrotevariablez:int
Youcanalsodefineyourownmodes,givinganametothenewmodeusingthe/setmodexyzcommandwherexyzisthenameofthenewmode.Afterthis,youcansetprompt,truncation,andformatforthemode.Whentheformatisdefined,youcanuseitthesamewayasthebuilt-inmodes.
Last,butnotleast,themostimportantcommandofjshellis/exit.Thiswilljustterminatetheprogramandyouwillreturntotheoperatingsystemshellprompt.
Now,let'sedittheHelloWorld.javafiletocreateourfirstJavaprogram.Todoso,youcanusevi,notepad,Emacs,orwhateverisavailableonyourmachineandfitsyou.Lateron,wewillusesomeintegrateddevelopmentenvironment(IDE),NetBeans,Eclipse,orIntelliJ;however,fornow,asimpletexteditorisenough.
Editthefilesothatthecontentwillbeasfollows:
publicclassHelloWorld{
publicstaticvoidmain(String[]args){
System.out.println("HelloWorld");
}
}
Tocompilethesourcecodetobytecode,whichisexecutablebyJVM,wehavetousetheJavacompilernamedjavac:
javacHelloWorld.java
Thisgeneratesthejava.classfileinthecurrentdirectory.Thisisacompiledcodethatcanbeexecutedasfollows:
$javaHelloWorld
HelloWorld
Withthisone,youhavecreatedandexecutedyourfirstfullJavaprogram.Youmaystillwonderwhatweweredoing.Howandwhy,Iwillexplainit;butfirst,Iwantedyoutohaveafeelingthatitworks.
Thefileweeditedcontainedonlythesnippetandwedeletedmostofthelines,exceptthedeclarationofthemainmethodandweinsertedthedeclarationoftheclassaroundit.
InJava,youcannothavestandalonemethodsorfunctions,likeinmanyotherlanguages.Everymethodbelongstosomeclassandeveryclassshouldbedeclaredinaseparatefile(well,almost,butfornow,let'sskiptheexceptions).Thenameofthefilehastobethesameasthenameoftheclass.Thecompilerrequiresthisforpublicclasses.Evenfornon-publicclassesweusuallyfollowthisconvention.IfyourenamedthefilefromHelloWorld.javatoHello.java,thecompilerwilldisplayanerrorwhenyoutrytocompilethefilewiththenewname.
$mvHelloWorld.javaHello.java
~/Dropbox/java_9-by_Example$javacHello.java
Hello.java:2:error:classHelloWorldispublic,shouldbedeclaredinafilenamedHelloWorld.java
publicclassHelloWorld{
^
1error
So,let'smoveitbacktotheoriginalname:mvHello.javaHelloWorld.java.
Thedeclarationoftheclassstartswiththekeywordclass,thenthenameoftheclass,anopeningcurlybrace,andlastsuntilthematchingclosingbrace.Everythinginbetweenbelongstotheclass.
Fornow,let'sskipwhyIwrotepublicinfrontoftheclassandfocusonthemainmethodinit.Themethoddoesnotreturnanyvalue,therefore;thereturnvalueofitisvoid.Theargument,namedargs,isastringarray.WhenJVMstartsthemainmethod,itpassesthecommand-lineargumentstotheprograminthisarray.However,thistimewedonotuseit.ThemainmethodcontainsthelinethatprintsoutHelloWorld.Now,let'sexaminethislineabitmore.
Inotherlanguages,printingsomethingtotheconsolerequiresonlyaprintstatementoraverysimilarcommand.IrememberthatsomeBASICinterpretersevenallowedustotype?insteadofprintbecauseprintingtothescreenwassocommon.Thishaschangedalotduringthelast40years.Weusegraphicalscreens,Internet,andmanyotherinputandoutputchannels.Thesedays,itisnotverycommontowritetotheconsole.
Usually,inprofessionallarge-scaleenterpriseapplications,thereisnotevenasinglelinethatdoesthat.Instead,wewilldirectthetexttologfiles,sendmessagestomessagequeues,andsendrequestsandreplywithresponsesoverTCP/IPprotocol.Asthisissoinfrequentlyused,thereisnoreasontocreateashortcutforthepurposeinthelanguage.Afterthefirstfewprograms,whenyougetacquaintedwiththedebuggerandloggingpossibilities,youwillnotprintanythingdirectlytotheconsoleyourself.
Still,Javahasfeaturesthatletyousendtextdirectlytothestandardoutputofaprocessthegoodoldway,
asitwasinventedoriginallyforUNIX.ThisisimplementedinaJavawaywhereeverythinghastobeanobjectorclass.Togetaccesstothesystemoutput,thereisaclassnamedSystemandit,amongotherthings,hasthefollowingthreevariables:
in:Thisisthestandardinputstreamout:Thisisthestandardoutputstreamerr:Thisisthestandarderrorstream
Torefertotheoutputstreamvariable,becauseitisnotinourclassbutinSystem,wewillhavetospecifytheclassnamesowewillrefertoitasSystem.outinourprogram.ThetypeofthisvariableisPrintStream,whichisalsoaclass.ClassandtypearesynonymsinJava.EveryobjectthatisoftypePrintStreamhasamethodnamedprintlnthatacceptsaString.Iftheactualprintstreamisthestandardoutput,andweareexecutingourJavacodefromthecommandline,thenthestringissenttotheconsole.
ThemethodisnamedmainandthisisaspecialnameinJavaprograms.WhenwestartaJavaprogramfromthecommandline,JVMinvokesthemethodnamedmainfromtheclassthatwespecifyonthecommandline.Itcandothatbecausewedeclaredthismethodpublicsothatanyonecanseeandinvokeit.Ifitwasprivate,itwouldbeseenandcallableonlyfromwithinthesameclass,orclasses,thataredefinedinthesamesourcefile.
Themethodisalsodeclaredasstatic,whichmeansthatitcanbeinvokedwithoutanactualinstanceoftheclassthatcontainsthemethods.Usingstaticmethodsisusuallyseenasnotagoodpracticethesedays,unlesstheyareimplementingfunctionsthatcannotreallyeverberelatedtoaninstance,orhavedifferentimplementationssuchasthefunctionsinthejava.lang.Mathclass;but,somewhere,thecodeexecutionhastostartandtheJavaruntimewillnotusuallycreateinstancesofclassesforusautomatically.
Tostartthecode,thecommandlineshouldbeasfollows:
java-cp.HelloWorld
The-cpoptionstandsforclasspath.Theclasspathisafairlycomplexideaforjavabut,fornow,let'smakeitsimpleandsaythatitisalistofdirectoriesandJARfilesthatcontainourclasses.Thelistseparatorfortheclasspathis:(colon)onUNIX-likesystemsand;(semicolon)onWindows.Inourcase,theclasspathistheactualdirectory,asthatistheplacewheretheJavacompilercreatedHelloWorld.class.Ifwedonotspecifyclasspathonthecommandline,Javawillusethecurrentdirectoryasadefault.Thatisthereasonourprogramwasworkingwithoutthe-cpoptioninthefirstplace.
Bothjavaandjavachandlemanyoptions.Togetalistoftheoptionstypejavac-helporjava-help.WeusetheIDEtoeditthecodeand,manytimes,tocompile,build,andrunitduringdevelopment.Theenvironmentinthiscasesetsthereasonableparameters.Forproductionweusebuildtoolsthatalsosupporttheconfigurationoftheenvironment.Becauseofthis,werarelymeetthesecommandlineoptions.Nevertheless,professionalshavetounderstandtheirmeaningsatleastandknowwheretolearntheiractualuseincaseitisneeded.
LookingatthebytecodeTheclassfileisabinaryfile.ThemainroleofthisformatistobeexecutedbytheJVMandtoprovidesymbolicinformationfortheJavacompilerwhenacodeusessomeoftheclassesfromalibrary.WhenwecompileourprogramthatcontainsSystem.out.println,thecompilerlooksatthecompiled.classfilesandnotatthesourcecode.IthastofindtheclassnamedSystem,thefieldnamedout,andthemethodprintln.Whenwedebugapieceofcodeortrytofindoutwhyaprogramdoesnotfindaclassormethod,wewillneedawaytolookintothebinaryofthe.classfiles.Thisisnotaneverydaytaskandittakessomeadvancedknowledge
Todoso,thereisadecompilerthatcandisplaythecontentofa.classfileinamoreorlessreadableformat.Thiscommandiscalledjavap.Toexecuteit,youcanissuethefollowingcommand:
$javapHelloWorld.class
Compiledfrom"HelloWorld.java"
publicclassHelloWorld{
publicHelloWorld();
publicstaticvoidmain(java.lang.String[]);
}
TheoutputoftheprogramshowsthattheclassfilecontainsJavaclassthathassomethingcalledHelloWorld();itseemstobeamethodhavingthesamenameastheclassanditalsocontainsthemethodwehavewritten.
Themethodthathasthesamenameastheclassistheconstructoroftheclass.Aseveryclassinjavacanbeinstantiated,thereisaneedforaconstructor.Ifwedonotgiveone,thentheJavacompilerwillcreateoneforus.Thisisthedefaultconstructor.Thedefaultconstructordoesnothingspecialbutreturnsanewinstanceoftheclass.Ifweprovideaconstructoronourown,thentheJavacompilerwillnothavebotheredcreatingone.
ThejavapdecompilerdoesnotshowwhatisinsidethemethodsorwhatJavacodeitcontainsunlessweprovidethe-coption:
$javap-cHelloWorld.class
Compiledfrom"HelloWorld.java"
publicclassHelloWorld{
publicHelloWorld();
Code:
0:aload_0
1:invokespecial#1//Methodjava/lang/Object."<init>":()V
4:return
publicstaticvoidmain(java.lang.String[]);
Code:
0:getstatic#2//Fieldjava/lang/System.out:Ljava/io/PrintStream;
3:ldc#3//Stringhali
5:invokevirtual#4//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V
8:return
}
Itisverycrypticandisnotforordinaryhumans.Onlyafewexperts,whodealwiththeJavacodegeneration,canfluentlyreadthat.But,tohavealookatithelpsyougetaglimpseofwhatbytecodemeans.Itissomethinglikeagoodoldassembly.Althoughthisisbinarycode,thereisnothingsecretinit:Javaisopensource,theclassfileformatiswelldocumentedanddebuggablefortheexperts.
PackagingclassesintoaJARfileWhenyoudeliveraJavaapplication,usuallythecodeispackagedintoJAR,WAR,EAR,orsomeotherpackagedformat.Welearnsomethingagainthatseemstobeobscureatfirstsight,butinreality,thisisnotthatcomplex.TheyareallZIPfiles.YoucanopenanyofthesefilesusingWinZiporsomeotherzipmanagerthatyouhavealicensefor.Theextrarequirementisthat,forexample,inthecaseofaJARfile,thearchiveshouldcontainadirectorynamedMETA-INFandinsideitafilenamedMANIFEST.MF.Thisfileisatextfileandcontainsmetainformationintheformat,whichisasfollows:Manifest-Version:1.0Created-By:9-ea(OracleCorporation)
Therecanbealotofotherinformationinthefile,butthisistheminimumthattheJavaprovidedtooljarputsthereifwepackageourclassfileintoajarissuingthefollowingcommand:
jar-cfhello.jarHelloWorld.class
The-coptiontellstheJARarchivertocreateanewJARfileandtheoptionfisusedtospecifythenameofthenewarchive.Theonewespecifiedhereishello.jarandthefileaddedtoitistheclassfile.
ThepackagedJARfilecanalsobeusedtostarttheJavaapplication.JavacanreaddirectlyfromJARarchivesandloadclassesfromthere.Theonlyrequirementisthattheyareontheclasspath.
Notethatyoucannotputindividualclassesontheclasspath,onlydirectories.AsJARfilesarearchiveswithaninternaldirectorystructureinthem,theybehavelikeadirectory.
CheckthattheJARfilewascreatedusinglshello.jarandremovethermHelloWorld.classclassfilejusttoensurethatwhenweissuethecommandline,thecodeisexecutedfromtheJARfileandnottheclass.
$java-cphello.jarHelloWorld
HelloWorld
ToseethecontentoftheJARfile,however,itisrecommendedthatyouusetheJARtoolandnotWinZipeventhoughthatmaybecozier.RealprofessionalsusetheJavatoolstohandleJavafiles.
$jar-tfhello.jar
META-INF/
META-INF/MANIFEST.MF
HelloWorld.class
ManagingtherunningJavaapplicationTheJavatoolsetthatcomeswiththeJDKsupportstheexecutionandmanagementofrunningJavaapplicationsaswell.Tohavesomeprogramthatwecanmanagewhileexecuting,wewillneedacodethatrunsnotonlyforafewmillisecondsbut,whileitruns,italsoprintssomethingtotheconsole.Let'screateanewprogramcalledHelloWorldLoop.javawiththefollowingcontent:
publicclassHelloWorldLoop{
publicstaticvoidmain(String[]args){
for(;;){
System.out.println("HelloWorld");
}
}
}
Theprogramcontainsaforloop.Loopsallowrepeatedexecutionofacodeblock,andwewilldiscusstheminChapter2,TheFirstRealJavaProgram-SortingNames.Theloopwecreatedhereisaspecialonethatneverterminatesbutrepeatstheprintingmethodcall,printingHelloWorlduntilwekilltheprogrambypressingCtrl+corissuingakillcommandonLinuxoronOSX,orterminatetheprograminthetaskmanagerunderWindows.
CompileandstartitinonewindowandopenanotherTerminalwindowtomanagetheapplication.
Thefirstcommandthatweshouldgetfamiliarwithisjps.http://docs.oracle.com/javase/7/docs/technotes/tools/share/jps.htmlItliststheJavaprocessesthatrunonthemachine,whichareasfollows:
$jps
21873sun.tools.jps.Jps
21871HelloWorldLoop
Youcanseethattherearetwoprocesses—oneistheprogramweexecuteandtheotheristhejpsprogramitself.Notsurprisingly,thejpstoolisalsowritteninJava.Youcanalsopassoptionstojps,whicharedocumentedontheweb.
Therearemanyothertoolsandwewillexamineoneofthem,whichisaverypowerfulandeasy-to-usetool—JavaVisualVM.
VisualVMisacommand-linegraphicaltoolthatconnectstotherunningJavaprocessanddisplaysthedifferentperformanceparameters.TostarttheVisualVMtool,youwillissuethejvisualvmcommandwithoutanyparameters.Soon,awindowwillappearwithanexploringtreeontheleft-handsideandawelcomepaneontheright.TheleftsideshowsalltherunningJavaprocessesunderthebranchnamedLocal.IfyoudoubleclickonHelloWorldLoop,itwillopenthedetailsoftheprocessontherightpane.Ontheheadertabs,youcanselectOverview,Monitor,Threads,Sampler,andProfiler.ThefirstthreetabsarethemostimportantandgiveyouagoodviewofwhatishappeninginJVMregardingthenumberofthreads,CPUusage,memoryconsumption,andsoon.
UsinganIDEIntegrateddevelopmentenvironmentsareoutstandingtoolsthathelpthedevelopmentbyoffloadingthemechanicaltasksfromthedeveloper'sshoulders.Theyrecognizemanyoftheprogrammingerrorsaswetypethecode,helpusfindtheneededlibrarymethods,displaythedocumentationofthelibraries,andprovideextratoolsforstylechecking,debugging,andsoon.
Inthissection,wewilllookatsomeIDEsandhowtoleveragethefunctionstheyprovide.
TogetanIDE,youwillhavetodownloadandinstallit.ItdoesnotcomewiththeJavadevelopmenttoolsbecausetheyarenotpartofthelanguageenvironment.But,don'tworry.Theycanbedownloadedfreeofchargeandareeasytoinstall.Theymaybemorecomplextostartupthananotepadeditor,butevenafterafewhoursofwork,theywillpaybackthetimeyoudevotetolearningthem.Afterall,itisnotwithoutreasonthatnodeveloperiscodingJavainnotepadorvi.
ThethreetopmostIDEsareNetBeans,Eclipse,andIntelliJ.Allareavailableincommunityversions,whichmeansthatyouneednotpayforthem.IntelliJhasafullversionthatyoucanalsobuy.Thecommunityeditionwillbeusableforlearningthelanguage.IncaseyoudonotlikeIntelliJ,youcanuseEclipseorNetBeans.Theseareallfreeofcharge.Personally,IusetheIntelliJcommunityeditionformostofmyprojectsandthescreensamplesthatshowanIDEinthisbookwillfeaturethisIDE.But,itdoesnotnecessarilymeanthatyouhavetosticktothisIDE.
Inthedevelopercommunity,therearetopicsthatcanbeheavilydebated.Thesetopicsareaboutopinions.Weretheyaboutfactsthedebatewouldeasilybesoonover.Onesuchtopicis:"WhichisthebestIDE?"Itisamatteroftaste.Thereisnodefiniteanswer.Ifyoulearnhowtouseone,youwilllikethatandyouwillbereluctanttolearnanotherone,unlessyouseethattheotheroneissomuchbetter.ThatisthereasondeveloperslovetheIDEtheyuse(orjusthate,dependingontheirpersonality),buttheykeepusingthesameIDEusuallyforalongtime.ThereisnobestIDE.
TodownloadtheIDEofyourchoice,youcanvisiteitheroneofthefollowingwebsites:
https://netbeans.org/forNetBeanshttp://www.eclipse.org/forEclipsehttps://www.jetbrains.com/idea/forIntelliJ
NetBeans
NetBeansissupportedbyOracleandiscontinuouslydeveloped.Itcontainscomponents,suchastheNetBeansprofiler,thatbecamepartoftheOracleJavadistribution.YoumaynoticethatwhenyoustartVisualVMandstarttheprofiling,theJavaprocessstartedhasnetbeansinitsname.
Generally,NetBeansisaframeworktodeveloprichclientapplicationsandtheIDEisonlyoneapplicationofthemanythatarebuiltontopoftheframework.Itsupportsmanylanguages,notonlyJava.YoucandevelopPHP,C,orJavaScriptcodeusingNetBeansandhavesimilarservicesforJava.Forthesupportofdifferentlanguages,youcandownloadpluginsoraspecialversionofNetBeans.ThesespecialversionsareavailablefromthedownloadpageoftheIDEandtheyarenothingmorethanthebasicIDEwithsomepreconfiguredplugins.IntheCpackage,thedevelopersconfigurethepluginsthatareneededwhenyouwanttodevelopC;inthePHPversion,theypluginforPHP.
Eclipse
EclipseissupportedbyIBM.SimilartoNetBeans,itisalsoaplatformforrichclientapplicationanditisbuiltaroundtheOSGicontainerarchitecture,whichitselfisatopicthatcanfillabooklikethis.MostofthedevelopersuseEclipseand,almostexclusively,itisthechoicewhendeveloperscreatecodefortheIBMWebSphereapplicationserver.TheEclipsespecialversioncontainsadeveloperversionofWebSphere.
EclipsealsohaspluginstosupportdifferentprogramminglanguagesandalsohasdifferentvariationssimilartoNetBeans.ThevariationsarepluginsprepackagedwiththebasicIDE.
IntelliJ
ThelastoneintheprecedingenumerationisIntelliJ.ThisIDEistheonlyonethatdoesnotwanttobeaframework.IntelliJisanIDE.Italsohasplugins,butmostofthepluginsthatyouwillneedtodownloadtouseinNetBeansorEclipsearepreconfigured.Whenyouwanttousesomemoreadvancedplugin,itmayhoweverbesomethingyouhavetopayfor,whichshouldnotbeaproblemwhenyouaredoingprofessional,paidwork,shouldit?Thesethingsarenotthatexpensive.Forlearningthesubjectsinthisbook,youwillnotneedanypluginthatisnotinthecommunityedition.Asinthisbook,IwilldevelopthesamplesusingIntelliJandIrecommendthatyoufollowmeduringyourlearningexperience.
IwanttoemphasizethattheexamplesinthisbookareindependentoftheactualIDEtobeused.YoucanfollowthebookusingNetBeans,Eclipse,orevenEmacs,notepad,orvi.
IDEservices
Integrateddevelopmentenvironmentsprovideuswithservices.Themostbasicserviceisthatyoucaneditfileswiththem,buttheyalsohelpbuildthecode,findbugs,runthecode,deploytotheapplicationserverindevelopmentmode,debug,andsoon.Inthefollowingsections,wewilllookatthesefeatures.IwillnotgiveanexactandpreciseintroductiononhowtouseoneortheotherIDE.Abooklikethisisnotagoodmediumforsuchatutorial.
IDEsdifferonmenuplacement,keyboardshortcuts,andtheymayevenchangeasnewerversionsarereleased.ItisbesttolookattheactualIDEtutorialvideooronlinehelp.Theirfeatures,ontheotherhand,areverysimilar.IntelliJhasthevideodocumentationathttps://www.jetbrains.com/idea/documentation/.
IDEscreenstructureThedifferentIDEslooksimilar,andhavethesamescreenstructuremoreorless.Inthefollowingscreenshot,youcanseeanIntelliJIDE:
Ontheleftside,youcanseethefilestructureofaJavaproject.AJavaprojecttypicallycontainsmanyfilesindifferentdirectorieswhichwewilldiscussinthenextchapter.ThesimpleHelloWorldapplicationcontainsapom.xmlprojectdescriptionfile.ThisfileisneededfortheMavenbuildtool,whichisalsoatopicforthenextchapter.Fornow,youshouldonlyknowthatitisafilethatdescribestheprojectstructureformaven.TheIDEalsokeepstrackofsomeadministrativedataforitself.ItisstoredinHelloWorld.iml.Themainprogramfileisstoredinthesrc/main/javadirectoryandnamedHelloWorld.java.
Ontherightside,youcanseethefiles.Inthescreenshot,wehaveonlyonefileopened.Incasethereismorethanonefileopened,thentherearetabs-oneforeachfile.Now,theactivefileisHelloWorld.javathatcanbeeditedinthesourcecodeeditor.
EditingfilesWhenediting,youcantypeincharactersordeletecharacters,words,andlines,butthisissomethingthatalleditorscando.IDEsofferextra.IDEsanalyzethesourcecodeandformatit,which,inturn,automaticallyindentsthelines.Italsocontinuouslycompilesthecodeinthebackgroundwhileyouedititandifthereissomesyntaxerror,thenitunderlinesitwitharedwaivingline.Whenyoufixtheerror,theredunderliningdisappears.
Theeditoralsoautomaticallygivessuggestionsforfurthercharactersasyoutype.Youcanignorethewindowthatpopsupandcontinuetyping.However,manytimes,itiseasiertostopafteracharacterandusetheupanddownarrowstoselectthewordthatneedsfinishingbeforepressingEnter:thewordwillbeinsertedintothesourcecodeautomatically.
Inthescreenshot,youcanseethatIwroteSystem.oandtheeditorimmediatelysuggestedthatIwantedtowriteout.TheotheralternativesaretheotherstaticfieldsandmethodsthatareintheclassSystemandwhichcontainthelettero.
TheIDEeditorgivesyouhintsnotonlywhenitcantypeforyou,butalsowhenitcannottypeinsteadofyou.Inthescreenshot,theIDEtellsyoutotypesomeexpressionasargumenttotheprintln()methodthatisboolean,char,int,andsoon.TheIDEhasabsolutelynoideawhattotypethere.Youhavetoconstructtheexpression.Still,itcantellyouthatitneedstobeofacertaintype.
Itisnotonlythebuilt-intypesthattheeditorknows.TheeditorintegratedwiththeJDKcontinuouslyscansthesourcefilesandknowswhatclasses,methods,andfieldsarethereinthesourcecodewhichare
usableattheplaceofediting.
Thisknowledgeisalsoheavilyusedwhenyouwanttorenameamethodorvariable.Theoldmethodwastorenamethefieldormethodinthesourcefileandthendoanexhaustivesearchforallreferencestothevariable.UsingtheIDE,themechanicalworkisdonebyit.Itknowsalltheusesofafieldormethodandautomaticallyreplacestheoldidentifierwiththenewone.Italsorecognizeswhetheralocalvariablehappenstohavethesamenameastheonethatwerename,andtheIDEonlyrenamesthoseoccurrencesthatarereallyreferringtotheonewearerenaming.
Youcanusuallydomorethanjustrenaming.Therearemoreorlessmechanicaltasksthatprogrammerscallrefactoring.ThesearesupportedbytheIDEsusingsomekeyboardshortcutandcontextsensitivemenuintheeditor—rightclickonthemouseandclickMenu.
TheIDEalsohelpsyoutoreadthedocumentationofthelibrariesandsourcecodeasshowninthefollowingimage:
LibrariesprovideJavadocdocumentationforthepublicmethodsandyoushouldalsowriteJavadocforyourownmethod.JavadocdocumentationisextractedfromspecialcommentsinthesourcecodeandwewilllearnhowtocreatethoseinChapter4,Mastermind-CreatingaGame.Thesearelocatedincommentsinfrontoftheactualmethodhead.Ascreatingcompileddocumentationispartofthecompilationflow,theIDEalsoknowsthedocumentationanditdisplaysasahoveringboxoverthemethodnames,classnames,orwhateverelementyouwanttouseinthesourcefilewhenyoupositionthecursorontheelement.
ManagingprojectsOntheleftsideoftheIDEwindow,youcanseethedirectorystructureoftheproject.TheIDEknowsthedifferenttypesoffilesandshowstheminawaythatismeaningfulfromtheprogrammingpointofview.Forexample,itdoesnotdisplayMain.javaasafilename.Instead,itdisplaysMainandaniconthatsignalsthatMainisaclass.ItcanalsobeaninterfacestillinafilenamedMain.javabut,inthatcase,theiconwillshowthatthisisaninterface.ThisisagaindonebytheIDEcontinuouslyscanningandcompilingthecode.
ThefilesarestructuredintosubdirectorieswhenwedevelopaJavacode.Thesesubdirectoriesfollowthepackagingstructureofthecode.Manytimes,inJava,weusecompoundandlongpackagenames,anddisplayingitasadeepandnesteddirectorystructurewillnotbesoeasytohandle.
Packagesareusedtogroupthesourcefiles.Thesourcefilesforclassesthatarerelatedinsomewayshouldgointoonepackage.Wewilldiscussthenotionofpackagesandhowtousetheminthenextchapter
TheIDEiscapableofshowingthepackagestructureinsteadofthenesteddirectoriesforthosedirectoriesoftheprojectthatcontainsourcefiles.
Whenyoumoveaclassoraninterfacefromonepackagetoanother,ithappensinasimilarwayasrenamingorotherrefactoring.Allreferencestotheclassorinterfaceinthesourcefilesgetrenamedtothenewpackage.Ifafilecontainsanimportstatementreferringtotheclass,thenameoftheclassinthestatementiscorrected.Tomoveaclass,youcanopenthepackageandusethegoodolddraganddrop.
PackagehierarchyisnottheonlyhierarchydisplayedintheIDE.Theclassesareinpackagesbut,atthesametime,thereisaninheritancehierarchy.Classesmayimplementinterfacesandcanextendotherclasses.TheJavaIDEshelpusbyshowingtypehierarchieswhereyoucannavigateacrossagraphicalinterfacealongtheinheritancerelations.
ThereisanotherhierarchythatIDEscanshowtohelpuswithdevelopment:methodcallhierarchy.Afteranalyzingthecode,theIDEcanshowusthegraphdisplayingtherelationsbetweenthemethods:whichmethodcallswhichothermethods.Sometimes,thiscallgraphisalsoimportantinshowingthe
dependenciesofmethodsoneachother.
BuildthecodeandrunitTheIDEsusuallycompilethecodeforanalysistohelpusspotsyntaxerrorsorundefinedclassesandmethodsonthefly.Thiscompilationisusuallypartial,coveringapartofthecode,andasitrunsallthetime,thesourcecodechangesandisneveractuallycomplete.Tocreatethedeployablefile,thatis,thefinaldeliverablecodeoftheproject,aseparatebuildprocesshastobestarted.MostoftheIDEshavesomebuilt-intoolforthat,butit'snotrecommendedtousetheseexceptforthesmallestprojects.ProfessionaldevelopmentprojectsuseAnt,Maven,orGradleinstead.HereisanexampleofMaven.
TheIDEsarepreparedtousesuchanexternaltool,andtheycanhelpusinstartingthem.Thisway,thebuildprocesscanrunonthedevelopermachinewithoutstartinganewshellwindow.IDEscanalsoimportthesettingsfromtheconfigurationfileoftheseexternalbuildtoolstorecognizetheprojectstructure,wheresourcefilesare,andwhattocompiletosupporttheerrorcheckingwhileediting.
Thebuildingprocessusuallycontainstheexecutionofcertainchecksonthecode.AbunchoftheJavasourcefilemaycompilesmoothlyandthecodemaystillcontainalotofbugsandmaybewritteninbadstyle,whichwillmaketheprojectbecomesunmaintainableinthelongrun.Toavoidsuchproblems,wewilluseunittestsandstaticcodeanalysistools.Thesedonotguaranteeerrorfreecodebutthechancesaremuchbetter.
IDEshavepluginstorunthestaticcodeanalysistoolsaswellasunittests.BeingintegratedintotheIDEhasahugeadvantage.Whenthereisanyproblemidentifiedbytheanalysistool,orbysomeunittests,theIDEprovidesanerrormessagethatalsofunctionslikealinkonawebpage.Ifyouclickonthemessage,usuallyblueandunderlined,exactlylikeonawebpage,theeditoropenstheproblematicfileandplacesthecursorwheretheissueis.
DebuggingJavaDevelopingcodeneedsdebugging.Javahasverygoodfacilitiestodebugcodeduringdevelopment.JVMsupportsdebuggersviatheJavaPlatformDebuggerArchitecture.ThisletsyouexecutecodeindebugmodeandJVMwillacceptexternaldebuggertoolstoattachtoitviaanetwork,oritwilltrytoattachtoadebuggerdependingoncommand-lineoptions.JDKcontainsaclient,thejdbtool,whichcontainsadebugger;however,itissocumbersometousewhencomparedtothegraphicalclientbuiltintotheIDEsthatIhaveneverheardofanyoneusingitforrealwork.
TostartaJavaprogramindebugmodesothatJVMwillacceptadebuggerclienttoattachtheoptionstoit,executethefollowingcommand:-Xagentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=7896
TheXagentliboptioninstructstheJavaruntimetoloadthejdwpagent.Thepartoftheoptionthatfollows-Xagentlib:jdwp=isinterpretedbythedebuggeragent.Theseoptionsareasfollows:
transport:Thisshouldspecifywhichtransporttouse.Itcanbeasharedmemory(dt_shmem)socketoraTCP/IPsockettransportbut,inpractice,youwillalwaysusethelatter.Thisisspecifiedintheprecedingdt_socketsample.server:ThisspecifiesifthedebuggedJVMstartsinservermodeorclientmode.WhenyoustarttheJVMinservermode,itstartstolistenonasocketandacceptsthedebuggertoconnecttoit.Ifitisstartedinclientmode,thenittriestoconnectadebuggerthatissupposedtobestartedinservermode,listeningonaport.Thevalueoftheoptionisymeaningservermodeornmeaningnonserver,a.k.a.clientmode.suspend:Thiscanalsobeyorn.IfJVMisstartedinsuspendmode,itwillnotstarttheJavacodeuntiladebuggerisattachedtoit.Ifitisstartedwithsuspend=n,thentheJVMstartsandwhenadebuggerattaches,itstopsassoonasabreakpointisreached.IfyoustartastandaloneJavaapplication,youwillusuallystartthedebuggingwithsuspend=y,whichisthedefault.Ifyouwanttodebuganapplicationinanapplicationserverorservlet-containerenvironment,thenitisbettertostartwithsuspend=n;otherwise,theserverdoesnotstartuntilthedebuggerattachestoit.StartingtheJavaprocessinsuspend=ymodeincaseservletapplicationisonlyusefulwhenyouwanttodebugtheservletstaticinitializercode,whichisexecutedwhentheserverisstartingup.Withoutsuspendmode,youwillberequiredtoattachthedebuggerveryfast.ItisbetterthatJVMjustwaitsforyouinthatsituation.address:ThisshouldspecifytheaddressthatJVMcommunicateswith.IftheJVMstartedinclientmode,thenitwillstarttoconnecttothisaddress.IftheJVMrunsinservermode,thenitwillacceptconnectionsfromthedebuggeronthataddress.Theaddressmayspecifyonlytheport.Inthiscase,theIPaddressisthatofthelocalmachine.
Theotheroptionsthedebuggeragentmayhandleareforspecialcases.Forthetopicscoveredinthisbook,theprecedingoptionsareenough.
ThefollowingscreenshotshowsatypicaldebuggingsessionwherewedebugthesimplestprograminIntelliJIDE:
WhenyoustartaprogramfromtheIDEindebugmode,alltheseoptionsareautomaticallysetforyou.Youcansetbreakpointjustbyclickingonthesourcecodeintheeditor.Youcanhaveaseparateformtoadd,remove,andeditbreakpoints.Breakpointscanbeattachedtospecificlinesorspecificevents,likewhenanexceptionisthrown.Breakpointsattachedtoaspecificlinecanalsohaveconditionsthattellthedebuggertostoptheexecutionofthecodeonlywhentheconditionistrue;forexample,ifavariablehassomepredefinedvalue.
Summary
InthischapterwewereintroducedtoeachotherwithJava.Wedonotknowtoomuchfromeachotherbutwegotacquainted.WehaveinstalledtheJavaenvironment:Java,JDKandintegrateddevelopmentenvironment.Wehavewrittenasmallprogramandhadabrieflookatwhatcanbedoneusingthedevelopmenttools.Thisisfarfrommasterybuteventhelongestjourneystartswithafirststep,whichissometimesthehardesttomake.WehavedoneitinourJavajourney.Westartedrollingandfortheenthusiaststhatweare,nothingcanstopuswalkingallthewaylong.
TheFirstRealJavaProgram-SortingNames
Inthepreviouschapter,wegotacquaintedwithJava,andespeciallywithusingtheREPLtoolandinteractivelyexecutingsomesimplecode.Thatisagoodstart,butweneedmore.Inthischapter,wewilldevelopasimplesortprogram.Usingthiscodeasanexample,wewilllookatdifferentbuildtools,whicharefrequentlyusedforJavaprojects,andlearnthebasicfeaturesoftheJavalanguage.Thischapterwillcoverthefollowingtopics:
ThesortingproblemTheprojectstructureandbuildtoolsTheMake,Ant,Maven,andGradlebuildtoolsJavalanguagefeaturesrelatedtothecodeexample
GettingstartedwithsortingThesortingproblemisoneoftheoldestprogrammingtasksthatanengineerdealswith.Wehaveasetofrecordsandweknowthatwewanttofindaspecificonesometimelater,andwewanttofindthatonefast.Tofindit,wesorttherecordsinaspecificorderthathelpsusfindtherecordwewantquickly.
Asanexample,wehavethenamesofstudentswiththeirmarksonsomecards.Whenstudentscometotheofficeaskingfortheirresults,welookthroughallofthecardsoneaftertheothertofindthenameoftheenquiringstudent.However,itisbetterifwesortthecardsbythenamesofthestudentsalphabetically.Whenastudentmakesanenquiry,wecansearchthemarkattachedtothenamemuchfaster.
Wecanlookatthemiddlecard;ifitshowsthenameofthestudent,thenwearehappytohavefoundthenameandthemark.Ifthecardprecedesthenameofthestudentalphabetically,thenwewillcontinuesearchinginthesecondhalf;otherwise,wewillcheckthefirsthalf.
Followingthatapproach,wecanfindthenameofthestudentinafewsteps.Thenumberofstepscannotbemorethanthenumberasmanytimesthepackofcardscanbehalved.Ifwehavetwocards,thenitistwostepsatmost.Ifitisfour,thenwewillneedthreestepsatmost.Ifthereareeightcards,thenwemayneedfoursteps,butnotmore.Ifthereare1,000cards,thenwemayneedatmost11steps,whiletheoriginal,non-sortedsetwillneed1,000steps,worstcase.Thatis,approximately,itspeedsupthesearch100times,sothisisworthsortingthecards,unlessthesortingitselftakestoomuchtime.Thealgorithmfindinganelementinthealreadysortedsetwejustdescribediscalledbinarysearch(https://en.wikipedia.org/wiki/Binary_search_algorithm).
Inmanycases,itisworthsortingthedataset,andtherearemanysortingalgorithmstodothat.Therearesimplerandmorecomplexalgorithms,and,asinmanycases,morecomplexalgorithmsaretheonesthatrunfaster.
AswearefocusingontheJavaprogrammingpartandnotthealgorithmforging,inthischapter,wewilldevelopaJavacodethatimplementsasimpleandnot-that-fastalgorithm.
Bubblesort
Thealgorithmthatwewillimplementinthischapteriswell-knownasbubblesort.Theapproachisverysimple.Beginatthestartofthecardsandcomparethefirstandthesecondcard.Ifthefirstcardislaterinlexicographicorderthanthesecondone,thenswapthetwocards.Thenrepeatthisforthecardthatisatthesecondplacenow,thenthethird,andsoon.Thereisacardthatislexicographicallythelatest,sayWilson.Whenwegetthiscardandstarttocompareitwiththenextone,wewillalwaysswapthem;thisway,Wilson'scardwilltraveltothelastplacewhereithastobeafterthesort.Allwehavetodoisrepeatthistravellingfromthestartanddotheoccasionalswappingofcardsagain,butthistimeonlytothelastbutoneelement.Thistime,thesecondlatestelementwillgettoitsplace—say,WilkinsonwillberightbeforeWilson.Ifwehavencards,andwerepeatthisn-1times,allcardswillgettotheirplace.
Inthefollowingsections,wewillcreateaJavaprojectthatimplementsthisalgorithm.
GettingstartedwithprojectstructureandbuildtoolsWhenaprojectismorecomplexthanasingleclass,anditusuallyis,thenitiswisetodefineaprojectstructure.Wewillhavetodecidewherewestorethesourcefiles,wheretheresourcefiles(thosethatcontainsomeresourcefortheprogram,butarenotJavasource)are,wherethe.classfilesshouldbewrittenbythecompiler,andsoon.Generally,thestructureismainlythedirectorysetupandtheconfigurationofthetoolsthatperformthebuild.
Thecompilationofcomplexprogramscannotbefeasiblydoneusingthecommandlineissuingjavaccommands.Ifwehave100Javasourcefiles,thecompilationwillrequirethatmanyjavaccommandstobeissued.Itcanbeshortenedusingwildcards,suchasjavac*.java,orwecanwriteasimplebashscriptoraBATcommandfilethatdoesthat.First,itwillbejust100lines,eachcompilingonesourceJavafiletoclassfile.Then,wewillrealizethatitisonlytime,CPU,andpowerconsumingtocompilethefilesthatarenotchangedsincethelastcompilationssowecanaddsomebashprogrammingthatchecksthetimestamponthesourceandgeneratedfiles.Then,wewillprobablyrealizethat...whatever.Attheend,wewillendupwithatoolthatisessentiallyabuildtool.Buildtoolsareavailablereadymade;itisnotworthreinventingthewheel.
Insteadofcreatingone,wewilluseabuildtoolthatisready.Thereareafewofthemthatcanbefoundathttps://en.wikipedia.org/wiki/List_of_build_automation_software.Inthischapter,wewilluseonecalledMaven;however,beforejumpingintothedetailsofthistool,wewilllookatsomeothertoolsthatyouarelikelytomeetasaJavaprofessionalinenterpriseprojects.
Inthefollowingsections,wewilldiscussabitofthefourbuildtools:
MakeAntMavenGradle
WewillmentionMakeonlybrieflybecauseitisnotusedinJavaenvironmentsthesedays.However,Makewasthefirstbuildtool,andmanyideasthatmodernJavabuildtoolsarebasedoncomefromthegoodoldmake.You,asaprofessionalJavadeveloper,shouldalsobefamiliarwithMakesothatyouwillnotfreakoutifyouhappentoseetheuseofitinaprojectforsomepurpose,andcanknowwhatitisandwhereitsdetaileddocumentationcanbefound.
AntwasthefirstbuildtoolwidelyusedforJavamanyyearsago,anditisstillusedinmanyprojects.
MavenisnewerthanAnt,anditusesadifferentapproach.Wewilllookatitindetail.MavenisalsotheofficialbuildtooloftheApachesoftwarefoundationfortheJavaproject.WewillalsouseMavenasabuildtoolinthischapter.
Gradleisevennewer,andithasstartedtocatchuptoMaventhesedays.Wewillusethistoolinlater
chaptersinmoredetail.
MakeThemakeprogramwasoriginallycreatedinApril1976,sothisisnotanewtool.ItisincludedintheUnixsystem,sothistoolisavailablewithoutanyextrainstallationonLinux,MacOSX,oranyotherUnix-basedsystem.Additionally,therearenumerousportsofthetoolonWindows,andsomeversionis/wasincludedintheVisualStudiocompilertoolset.
TheMakeisnottiedtoJava.ItwascreatedwhenthemajorprogramminglanguagewasC,butitisnottiedtoCoranyotherlanguage.Themakeisadependencydescriptionlanguagethathasaverysimplesyntax.Themake,justlikeanyotherbuildtool,iscontrolledbyaprojectdescriptionfile.Inthecaseofmake,thisfilecontainsaruleset.ThedescriptionfileisusuallynamedMakefile,butincasethenameofthedescriptionfileisdifferent,itcanbespecifiedasacommand-lineoptiontothemakecommand.
RulesinMakefilefolloweachotherandconsistofoneormorelines.Thefirstlinestartsatthefirstposition(thereisnotaborspaceatthestartoftheline)andthefollowinglinesstartwithatabcharacter.Thus,Makefilemaylooksomethinglikethefollowingcode:
run:hello.jar
java-cphello.jarHelloWorld
hello.jar:HelloWorld.class
jar-cfhello.jarHelloWorld.class
HelloWorld.class:HelloWorld.java
javacHelloWorld.java
Thisfiledefinesthreeso-calledtargets:run,hello.jar,andHelloWorld.class.TocreateHelloWorld.class,typethefollowinglineatthecommandprompt:
makeHelloWorld.class
MakewilllookattheruleandseethatitdependsonHelloWorld.java.IftheHelloWorld.classfiledoesnotexist,orHelloWorld.javaisnewerthantheJavaclassfile,makewillexecutethecommandthatiswrittenonthenextlineanditwillcompiletheJavasourcefile.IftheclassfilewascreatedfollowingthelastmodificationofHelloWorld.java,thenmakeknowsthatthereisnoneedtorunthecommand.
InthecaseofcreatingHelloWorld.class,themakeprogramhasaneasytask.Thesourcefilewasalreadythere.Ifyouissuethemakehello.jarcommand,theprocedureismorecomplex.Themakecommandseesthatinordertocreatehello.jar,itneedsHelloWorld.class,whichitselfisalsoatargetonanotherrule.Thus,itmayneedtobecreated.
First,itstartstheproblemthesamewayasbefore.IfHelloWorld.classisthere,andisolderthanhello.jar,thereisnothingtodo.Ifitisnotthere,orisnewerthanhello.jar,thenthejar-cfhello.jarHelloWorld.classcommandneedstobeexecuted,althoughnotnecessarilyatthemomentwhenitrealizesthatithastobeperformed.ThemakeprogramremembersthatthiscommandhastobeexecutedsometimeinthefuturewhenallthecommandsthatareneededtocreateHelloWorld.classarealreadyexecutedsuccessfully.Thus,itcontinuestocreatetheclassfileexactlythesamewayasIdescribedearlier.
Ingeneral,arulecanhavethefollowingformat:
target:dependencies
command
Themakecommandcancreateanytargetusingthemaketargetcommandbyfirstcalculatingwhichcommandstoexecuteandthenexecutingthemonebyone.ThecommandsareshellcommandsexecutinginadifferentprocessandmayposeproblemsunderWindows,whichmayrendertheMakefilefiles'operatingsystemdependent.
Notethattheruntargetisnotanactualfilethatmakecreates.Atargetcanbeafilenameorjustanameforthetarget.Inthelattercase,makewillneverconsiderthetargettobereadilyavailable.
AswedonotusemakeforaJavaproject,thereisnoreasontogetintomoredetails.Additionally,Icheatedabitbymakingthedescriptionofarulesimplerthanitshouldbe.Themaketoolhasmanypowerfulfeaturesoutofthescopeofthisbook.Therearealsoseveralimplementationsthatdifferalittlefromeachother.YouwillmostprobablymeettheonemadebytheFreeSoftwareFoundation—theGNUmake.And,ofcourse,justincaseofanyUnixcommand-linetool,manisyourfriend.Themanmakecommandwilldisplaythedocumentationofthetoolonthescreen.
Themainpointsthatyoushouldrememberaboutmakeareasfollows:
Itdefinesthedependenciesoftheindividualartifacts(targets)inadeclarativewayItdefinestheactionstocreatethemissingartifactsinanimperativeway
Thisstructurewasinventeddecadesagoandhassurvivedupuntilnowformostofthebuildtools,asyouwillseeinthenextfewchapters.
AntTheantbuildtoolwasbuiltespeciallyforJavaprojectsaroundtheyear2000.TheaimofJavatobeawrite-once-run-anywherelanguageneededatoolthatcanalsobeusedindifferentenvironments.AlthoughmakeisavailableonUnixmachines,andWindowsaswell,Makefileswerenotalwayscompatible.Therewasasmallproblemwiththeuseofthetabcharacterthatsomeeditorsreplacedwithspace,renderingMakefileunusable,butthiswasnotthemajorreason.ThemainproblemwithmakethatignitedthedevelopmentofAntisthatthecommandsareshellcommands.Eveniftheimplementationofthemakeprogramwasmadetobecompatibleondifferentoperatingsystems,theusedcommandsweremanytimesincompatible,andthatwassomethingmakeitselfcouldnotchange.Becausemakeissuesexternalcommandstobuildthetargets,developersarefreetouseanyexternaltoolthatisavailableforthemonthedevelopmentmachine.Anothermachineusingthesameoperatingsystemjustmaynothavethesamesetoftoolsinvokedbymake.Thisunderminestheportabilityofthemakebuiltprojects.
Atthesametime,Antisfollowingthemajorprinciplesofmake.Therearetargetsthatmaydependoneachotherandtherearecommandsthatneedtobeexecutedinanappropriatesequencetocreatethetargetsoneaftertheother,followingthedependencyorder.ThedescriptionofthedependenciesandthecommandsisXML(tabissuesolved)andthecommandsareimplementedinJava(systemdependencyissolved,well...moreorless).
AsAntisneitherpartoftheoperatingsystemnortheJDK,youwillhavetodownloadandinstallitseparatelyifyouwanttouseit.
InstallingAntAntcanbedownloadedfromitsofficialwebsite(http://ant.apache.org).Youcandownloadthesourceortheprecompiledversion.Theeasiestwayistodownloadthebinaryinatar.gzformat.
WheneveryoudownloadsoftwarefromtheInternet,itishighlyrecommendedthatyouchecktheintegrityofthedownloadedfile.TheHTTPprotocoldoesnotcontainerrorchecking,anditmayhappenthatanetworkerrorremainshiddenoramalevolentinternalproxymodifiesthedownloadedfile.Downloadsitesusuallyprovidechecksumsforthedownloadablefiles.TheseareusuallyMD5,SHA1,SHA512,orsomeotherchecksums.
WhenIdownloadedtheApacheAnt1.9.7versionintar.gzformat,IalsoopenedthepagethatledtotheMD5checksum.Thechecksumvalueisbc1d9e5fe73eee5c50b26ed411fb0119.
Thedownloadedfilecanbecheckedusingthefollowingcommandline:$md5apache-ant-1.9.7-bin.tar.gz
MD5(apache-ant-1.9.7-bin.tar.gz)=bc1d9e5fe73eee5c50b26ed411fb0119
ThecalculatedMD5checksumisthesameastheoneonthewebsite,whichsaysthatthefileintegrityisnotharmed.OntheWindowsoperatingsystem,notooltocalculateMD5digestisincluded.ThereisatoolthatMicrosoftprovides,calledFileIntegrityChecksumVerifierUtility,whichisavailableviathepagehttps://support.microsoft.com/en-us/help/841290/availability-and-description-of-the-file-checksum-integrity-verifier-utility.IfyouuseLinux,itmayhappenthatthemd5ormd5sumutilityisnotinstalled.Inthatcase,youcaninstallitusingthecommandapt-getorwhateverinstallationtoolyourLinuxdistributionsupports.
Afterthefileisdownloaded,youcanexplodeittoasubdirectoryusingthefollowingcommand:
tarxfzapache-ant-1.9.7-bin.tar.gz
ThecreatedsubdirectoryistheusablebinarydistributionofAnt.Usually,Imoveitunder~/bin,makingitavailableonlyformyuseronOSX.Afterthat,youshouldsettheenvironmentvariableasANT_HOMEtopointtothisdirectoryandalsoaddthebindirectoryoftheinstallationtothePATH.Todothat,youshouldeditthe~/.bashrcfileandaddthefollowinglinestoit:
exportANT_HOME=~/bin/apache-ant-1.9.7/
exportPATH=${ANT_HOME}bin:$PATH
Then,restarttheterminalapplication,orjusttype.~/.bashrcandtesttheinstallationofAntbytypingthefollowingcommand:
$ant
Buildfile:build.xmldoesnotexist!
Buildfailed
Iftheinstallationwascorrect,youshouldseetheprecedingerrormessage.
UsingAntWhenyouseeaprojecttobebuiltbyAnt,youwillseeabuild.xmlfile.Thisistheprojectbuildfile,theonethatAntwasmissingwhenyoucheckedthattheinstallationwascorrect.Itcanhaveanyothername,andyoucanspecifythenameofthefileasacommand-lineoptionforAnt,butthisisthedefaultfilename,asMakefilewasformake.Abuild.xmlsamplelookslikethefollowing:
<projectname="HelloWorld"default="jar"basedir=".">
<description>
ThisisasampleHelloWorldprojectbuildfile.
</description>
<propertyname="buildDir"value="build"/>
<propertyname="srcDir"value="src"/>
<propertyname="classesDir"value="${buildDir}/classes"/>
<propertyname="jarDir"value="${buildDir}/jar"/>
<targetname="dirs">
<mkdirdir="${classesDir}"/>
<mkdirdir="${jarDir}"/>
</target>
<targetname="compile"depends="dirs">
<javacsrcdir="${srcDir}"destdir="${classesDir}"/>
</target>
<targetname="jar"depends="dirs,compile">
<jardestfile="${jarDir}/HelloWorld.jar"basedir="${classesDir}"/>
</target>
</project>
Thetop-levelXMLtagisproject.Eachbuildfiledescribesoneproject,hencethename.Therearethreepossibleattributestothetag,whichareasfollows:
name:ThisdefinesthenameoftheprojectandisusedbysomeIDEstodisplayitintheleftpanelidentifyingtheprojectdefault:ThisnamesthetargettousewhennotargetisdefinedonthecommandlinestartingAntbasedir:Thisdefinestheinitialdirectoryusedforanyotherdirectorynamecalculationinthebuildfile
Thebuildfilecancontainadescriptionfortheproject,aswellaspropertiesinpropertytags.Thesepropertiescanbeusedasvariablesintheattributesofthetasksbetweenthe${and}characters,andplayanimportantroleinthebuildprocess.
ThetargetsaredefinedintargetXMLtags.Eachtagshouldhaveanamethatuniquelyidentifiesthetargetinthebuildfileandmayhaveadependstagthatspecifiesoneormoreothertargetsthatthistargetdependson.Incasethereismorethanonetarget,thetargetsarecommaseparatedintheattribute.Thetasksbelongingtothetargetsareexecutedinthesameorderasthetargetsdependencychainrequires,inaverysimilarwayaswesawinthecaseofmake.
YoucanalsoaddadescriptionattributetoatargetthatisprintedbyAntwhenthecommand-lineoption,-projecthelp,isused.Thishelpstheusersofthebuildfiletoknowwhattargetsarethereandwhichdoeswhat.Buildfilestendtogrowlargewithmanytargets,andwhenyouhavetenormoretargets,itishardtoremembereachandeverytarget.
ThesampleprojectwithHelloWorld.javaisnowarrangedinthefollowingdirectories:
build.xmlintherootfolderoftheprojectHelloWorld.javainthesrcfolderoftheprojectThebuild/folderdoesnotexist;itwillbecreatedduringthebuildprocessThebuild/classesandbuild/jaralsodonotexistyet,andwillbecreatedduringthebuildprocess
WhenyoustartthebuildfortheHelloWorldprojectthefirsttime,youwillseethefollowingoutput:
$ant
Buildfile:/Users/verhasp/Dropbox/java_9-by_Example/sources/ch02/build.xml
dirs:
[mkdir]Createddir:/Users/verhasp/Dropbox/java_9-by_Example/sources/ch02/build/classes
[mkdir]Createddir:/Users/verhasp/Dropbox/java_9-by_Example/sources/ch02/build/jar
compile:
...
[javac]Compiling1sourcefileto/Users/verhasp/Dropbox/java_9-by_Example/sources/ch02/build/classes
jar:
[jar]Buildingjar:/Users/verhasp/Dropbox/java_9-by_Example/sources/ch02/build/jar/HelloWorld.jar
BUILDSUCCESSFUL
Totaltime:0seconds
Someunimportantlinesaredeletedfromtheactualoutput.
Antrealizesthatfirstithastocreatethedirectories,thenithastocompilethesourcecode,andfinallyitcanpackthe.classfilesintoa.jarfile.NowitisuptoyoutorememberthecommandtoexecutetheHelloWorldapplication.Itwaslistedalreadyinthefirstchapter.Notethatthistime,theJARfileisnamedHelloWorld.jar,anditisnotinthecurrentdirectory.YoucanalsotrytoreadtheonlinedocumentationofAntandcreateatargetrunthatexecutesthecompiledandpackedprogram.
Anthasabuilt-intasknamedjavathatexecutesaJavaclassinalmostthesamewayasyoutypedthejavacommandintheterminal.
MavenAsAntwascreatedtoovercometheshortagesofmake,Mavenwascreatedwithasimilarintention—toovercometheshortagesofAnt.Youmayrecallthatmakecouldnotguaranteebuildportabilitybecausethecommandsmakeexecutesarearbitraryshellcommandsthatmaybesystemspecific.AnAntbuild,ifallthetasksareavailableontheclasspath,isportableaslongasJavarunsthesamewayonthedifferentplatforms.
TheproblemwithAntisabitdifferent.Whenyoudownloadthesourcecodeofaprojectandyouwanttobuild,whatwillthecommandbe?YoushouldaskAnttolistallthetargetsandselecttheonethatseemstobethemostsuitable.Thenameofthetaskdependsontheengineerwhocraftedthebuild.xmlfile.Therearesomeconventions,buttheyarenotstrictrules.
WherewillyoufindtheJavasourcefiles?Aretheyinthesrcdirectoryornot?WilltherealsobesomeGroovyorotherprogramminglanguagefilesincasetheprojectispolyglot?Thatdepends.Again,theremaybesomeconventionsthatsomegroupsorcompanyculturessuggest,butthereisnogeneralbestindustrypractice.
WhenyoustartanewprojectwithAnt,youwillhavetocreatethetargetsforcompilation,testexecution,andpackaging.Itissomethingthatyouwillhavealreadydoneforotherprojects.Afterthesecondorthirdproject,youwilljustcopyandpasteyourpreviousbuild.xmltoyournewproject.Isthataproblem?Yes,itis.Itiscopy/pasteprogramming,evenifitisonlysomebuildfiles.
DevelopersrealizedthatasignificanteffortoftheprojectsutilizingAntisdevotedtoprojectbuildtoolconfiguration,includingrepetitivetasks.Whenanewjoinercomestotheteam,theywillfirsthavetolearnhowthebuildisconfigured.Ifanewprojectisstarted,thebuildconfigurationhastobecreated.Ifitisarepetitivetask,thenbetterletthecomputersdoit.Thatisgenerallywhatprogrammingisallabout,isn'tit?
Mavenapproachesthebuildissueabitdifferently.WewanttobuildJavaprojects.Sometimes,someGroovyorJythonthings,buttheyarealsoJVMlanguages;thus,sayingthatwewanttobuildJavaprojectsisnotreallyahugerestriction.JavaprojectscontainJavafiles,sometimessomeotherprogramminglanguage'ssourcefiles,resourcefiles,andgenerally,thatisit.Antcandoanything,butwedonotwanttodojustanythingwithabuildtool.Wewanttobuildprojects.
Okay,afterwerestrictedourselvesandacceptedthatwedonotneedabuildtoolthatcanbeusedforanything,wecangoon.Wecanrequirethatthesourcefilesbeunderthesrcdirectory.Therearefilesthatareneededfortheoperationalcodeandtherearefilesthatcontainsometestcodeanddata.Therefore,wewillhavetwodirectories,src/testandsrc/main.Javafilesareinsrc/main/javaaswellassrc/test/java.Resourcefilesareundersrc/main/resourcesandsrc/test/resources.
Ifyouwanttoputyoursourcefilessomewhereelse,thendon't.Imeanit.Itispossible,butIwillnoteventellyouhow.Nobodydoesit.IdonotevenhaveanyideawhyMavenmakesitpossible.WheneveryouseeaprojectthatisusingMavenasabuildtool,thesourcesareorganizedlikethat.Thereisnoneedtounderstandthedirectorystructureenvisionedbytheproject'sbuildengineer.Itisalwaysthesame.
Howaboutthetargetsandthetasks?TheyarealsothesameforallMaven-basedprojects.WhatelsewouldyouliketodowithaJavaprojectotherthancompile,test,package,ordeployit?Mavendefinestheseprojectlifecyclesforus.WhenyouwanttocompileaprojectusingMavenasabuildtool,youwillhavetotype$mvncompiletocompiletheproject.Youcandothatevenbeforeunderstandingwhattheprojectactuallyis.
Aswehavethesamedirectorystructureandthesamegoals,theactualtasksleadingtothegoalsarealsoallthesame.WhenwecreateaMavenproject,wedonothavetodescribewhatthebuildprocesshastodoandhowithastodoit.Wewillhavetodescribetheproject,andonlythepartsthatareprojectspecific.
ThebuildconfigurationofaMavenprojectisgiveninanXMLfile.Thenameofthisfileisusuallypom.xml,anditshouldbeintherootdirectoryoftheproject,whichshouldbethecurrentworkingdirectorywhenfiringupMaven.ThewordPOMstandsforProjectObjectModel,anditdescribestheprojectsinahierarchicalway.Thesourcedirectories,thepackaging,andotherthingsaredefinedinaso-calledsuperPOM.ThisPOMispartoftheMavenprogram.AnythingthatthePOMdefines,overridesthedefaultsdefinedinthesuperPOM.Whenthereisaprojectwithmultiplemodules,thePOMsarearrangedintoahierarchy,andtheyinherittheconfigurationvaluesfromtheparentdowntothemodules.AswewilluseMaventodevelopoursortingcode,wewillseesomemoredetailslater.
InstallingMavenMavenisneitherapartoftheoperatingsystemnortheJDK.IthastobedownloadedandinstalledinaverysimilarwaytoAnt.YoucandownloadMavenfromitsofficialwebsite(https://maven.apache.org/)underthedownloadsection.Currently,thelateststableversionis3.3.9.Whenyoudownloadit,theactualreleasemaybedifferent;instead,usethelateststableversion.Youcandownloadthesourceortheprecompiledversion.Theeasiestwayistodownloadthebinaryintar.gzformat.
Icannotskipdrawingyourattentiontotheimportanceofcheckingthedownloadintegrityusingchecksums.IhavedetailedthewaytodoitinthesectionaboutAntinstallation.
Afterthefileisdownloaded,youcanexplodeittoasubdirectoryusingthefollowingcommand:
tarxfzapache-maven-3.3.9-bin.tar.gz
ThecreatedsubdirectoryistheusablebinarydistributionofMaven.Usually,Imoveitunder~/bin,makingitavailableonlyformyuseronOSX.Afterthat,youshouldaddthebindirectoryoftheinstallationtothePATH.Todothat,youshouldeditthe~/.bashrcfileandaddthefollowinglinestoit:
exportM2_HOME=~/bin/apache-maven-3.3.9/
exportPATH=${M2_HOME}bin:$PATH
Then,restarttheterminalapplication,orjusttype.~/.bashrcandtesttheinstallationofMaventyping,asfollows:
$mvn-v
ApacheMaven3.3.9(bb52d8502b132ec0a5a3f4c09453c07478323dc5;2015-11-10T17:41:47+01:00)
Mavenhome:/Users/verhasp/bin/apache-maven-3.3.9
Javaversion:9-ea,vendor:OracleCorporation
Javahome:/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home
Defaultlocale:en_US,platformencoding:UTF-8
OSname:"macosx",version:"10.11.6",arch:"x86_64",family:"mac"
YoushouldseeasimilarmessageonthescreenthatdisplaystheinstalledMavenversionandotherinformation.
UsingMavenUnlikeAnt,Mavenhelpsyoucreatetheskeletonofanewproject.Todothat,youwillhavetotypethefollowingcommand:
$mvnarchetype:generate
Mavenwillfirstdownloadtheactuallyavailableprojecttypesfromthenetworkandpromptyoutoselecttheoneyouwanttouse.ThisapproachseemedtobeagoodideawhileMavenwasnew.WhenIfirststartedMaven,thenumberoflistedprojectswassomewherebetween10and20.Today,asIwritethisbook,itlists1,635differentarchetypes.Thisnumberseemsmorelikeahistoricaldate(theconstitutionoftheFrenchAcademyofScience)thanausablesizelistofdifferentarchetypes.However,donotfreakout.Mavenoffersadefaultvaluewhenitasksforyourchoice,anditisgoodfortheHelloWorldwegofor.
Chooseanumber:817:
Theactualnumbermaybedifferentonyourinstallation.Whateveritis,acceptthesuggestionandpressEnter.Afterthat,Mavenwillaskyoufortheversionoftheproject:
Chooseversion:
1:1.0-alpha-1
2:1.0-alpha-2
3:1.0-alpha-3
4:1.0-alpha-4
5:1.0
6:1.1
Chooseanumber:6:5
Selectthe1.0versionthatislistedasnumber5.ThenextthingMavenasksforisthegroupIDandtheartifactIDoftheproject.Thedependencymanagementthatwewilldiscusslaterusesthese.IselectedagroupIDbasedonthebookandthepublisher.TheartifactoftheprojectisSortTutorialaswewillstartourchapterexampleinthisproject.
Definevalueforproperty'groupId'::packt.java9.by.example
Definevalueforproperty'artifactId'::SortTutorial
Thenextquestionisthecurrentversionoftheproject.Wehavealreadyselected1.0andMavenoffers1.0-SNAPSHOT.Here,Iselected1.0.0-SNAPSHOTbecauseIprefersemanticversioning.
Definevalueforproperty'version':1.0-SNAPSHOT::1.0.0-SNAPSHOT
Semanticversioning,definedonhttp://semver.org/,isaversioningschemethatsuggeststhreedigitversionnumbersasM.m.p.forMajor,minor,andpatchversionnumbers.Thisisveryusefulforlibraries.Youwillincrementthelastversionnumberifthereisonlyabugfixsincethepreviousrelease.Youwillincrementtheminornumberwhenthenewreleasealsocontainsnewfeatures,butthelibraryiscompatiblewiththepreviousversion;inotherwords,anyprogramthatisusingtheolderversioncanstillusethenewerversion.Themajorreleasenumberisincreasedwhenthenewversionissignificantlydifferentfromthepreviousone.Inthecaseofapplicationprograms,thereisnocodethatusestheapplicationAPI;thus,theminorversionnumberisnotthatimportant.Itdoesnothurt,though,anditoften
provestobeusefultosignalsmallerchangesintheapplication.Wewilldiscusshowtoversionsoftwareinthelastchapter.
Mavenhandlestheversionsthathavethe-SNAPSHOTpostfixasnon-releaseversions.Whilewedevelopthecode,wewillhavemanyversionsofourcode,allhavingthesamesnapshotversionnumber.Ontheotherhand,non-snapshotversionnumberscanonlybeusedonlyforasingleversion.
Definevalueforproperty'package':packt.java9.by.example::
ThelastquestionfromtheprogramskeletongenerationisthenameoftheJavapackage.ThedefaultisthevaluewegaveforgroupId,andwewillusethis.Itisarareexceptiontousesomethingelse.
Whenwehavespecifiedalltheparametersthatareneeded,thefinalrequestistoconfirmthesetting:
Confirmpropertiesconfiguration:
groupId:packt.java9.by.example
artifactId:SortTutorial
version:1.0.0-SNAPSHOT
package:packt.java9.by.example
Y::Y
AfterenteringY,Mavenwillgeneratethefilesthatareneededfortheprojectanddisplaythereportaboutthis:
[INFO]-----------------------------------------------------------
[INFO]UsingfollowingparametersforcreatingprojectfromOld(1.x)Archetype:maven-archetype-quickstart:1.0
[INFO]-----------------------------------------------------------
[INFO]Parameter:basedir,Value:.../mavenHelloWorld
[INFO]Parameter:package,Value:packt.java9.by.example
[INFO]Parameter:groupId,Value:packt.java9.by.example
[INFO]Parameter:artifactId,Value:SortTutorial
[INFO]Parameter:packageName,Value:packt.java9.by.example
[INFO]Parameter:version,Value:1.0.0-SNAPSHOT
[INFO]***EndofdebuginfofromresourcesfromgeneratedPOM***
[INFO]projectcreatedfromOld(1.x)Archetypeindir:.../mavenHelloWorld/SortTutorial
[INFO]-----------------------------------------------------------
[INFO]BUILDSUCCESS
[INFO]-----------------------------------------------------------
[INFO]Totaltime:01:27min
[INFO]Finishedat:2016-07-24T14:22:36+02:00
[INFO]FinalMemory:11M/153M
[INFO]-----------------------------------------------------------
Youcantakelookatthefollowinggenerateddirectorystructure:
Youcanalsoseethatitgeneratedthefollowingthreefiles:
SortTutorial/pom.xmlthatcontainstheProjectObjectModel
SortTutorial/src/main/java/packt/java9/by/example/App.javathatcontainsaHelloWorldsampleapplicationSortTutorial/src/test/java/packt/java9/by/example/AppTest.javathatcontainsaunittestskeletonutilizingthejunit4library
Wewilldiscussunittestsinthenextchapter.Fornow,wewillfocusonthesortingapplication.AsMavenwassokindandgeneratedasampleclassfortheapp,wecancompileandrunitwithoutactualcoding,justtoseehowwecanbuildtheprojectusingMaven.ChangethedefaultdirectorytoSortTutorialissuingcdSortTutorialandissuethefollowingcommand:
$mvnpackage
Wewillgetthefollowingoutput:
Mavenfiresup,compiles,andpackagestheprojectautomatically.Ifnot,pleasereadthenextinfobox.
WhenyoufirststartMaven,itdownloadsalotofdependenciesfromthecentralrepository.Thesedownloadstaketime,andarereportedonthescreen,sotheactualoutputmaybedifferentfromwhatyousawintheprecedingcode.MavencompilescodewiththedefaultsettingsforJavaversion1.5.Itmeansthatthe
generatedclassfileiscompatiblewithJavaversion1.5,andalsothatthecompileronlyacceptslanguageconstructsthatwereavailablealreadyinJava1.5.Ifwewanttousenewerlanguagefeatures,andinthisbookweusealot,thepom.xmlfileshouldbeeditedtocontainthefollowinglines:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.9</source>
<target>1.9</target>
</configuration>
</plugin>
</plugins>
</build>
WhenusingJava9'sdefaultsettingsforMaven,itbecomesevenmorecomplex,becauseJava9doesnotgenerateclassformatnorrestrictsourcecompatibilityearlierthanJava1.6.Atthisverymoment,asIwritetheselines,thelatestMavenreleaseis3.3.9.WhenItrytocompiletheprecedingcodewithoutthemodifications,theJavacompilerstopswithanerrordisplayingthefollowing:[ERROR]Sourceoption1.5isnolongersupported.Use1.6orlater.
[ERROR]Targetoption1.5isnolongersupported.Use1.6orlater.Later,Mavenreleasesmaybehavedifferentlyinthefuture.
Now,youcanstartthecodeusingthefollowingcommand:
$java-cptarget/SortTutorial-1.0.0-SNAPSHOT.jarpackt.java9.by.example.App
Youcanseetheresultofasampleruninthefollowingpicture:
GradleAntandMavenaretwoworlds,andusingoneortheothermayleadtoheateddebatesonInternetforums.Antgivesfreedomtodeveloperstocreateabuildprocessthatfitstheirtaste.Mavenrestrictstheteamtouseabuildprocessthatismorestandard.Somespecialprocessesthatdonotmatchanystandardbuild,butwhicharesometimesneededinsomeenvironments,arehardtoimplementusingMaven.InAnt,youcanscriptalmostanythingusingthebuilt-intasks,almostthesamewayasyoucanprogrambash.UtilizingMavenisnotthatsimple,and,itoftenrequireswritingaplugin.Eventhoughwritingapluginisnotrocketscience,developersusuallyliketohavethepossibilityofmakingthingsinasimplerway:Scripting.Wehavetwoapproaches,twomindsetsandstyles,andnotasingletooltofulfillalltheneeds.NosurprisethatbytheJavatechnologiesweredeveloped,anewbuildtoolwasemerging.
Gradletriestousethebestofbothworlds,utilizingtechniquesthatwerenotavailablebythetimeMavenandAntwerefirstdeveloped.
Gradlehasbuilt-intargetsandlifecycle,butatthesametime,youcanalsowriteyourowntargets.Youcanconfigureaproject,justlikeusingMaven,withoutscriptingthetaskstodoso,butatthesametime,youcanalsoscriptyourowntargetjustlikeinAnt.Whatismore,GradleintegratedAnt,soanytaskimplementedforAntisavailableforGradleaswell.
MavenandAntuseXMLfilestodescribethebuild.Today,XMLisatechnologyofthepast.Westilluseit,andadevelopershouldbefluentinhandling,reading,andwritingXMLfiles,butamoderntooldoesnotuseXMLforconfiguration.New,fancyformatssuchasJSONaremorepopular.Gradleisnoexception.TheconfigurationfileofGradleusesadomain-specificlanguage(DSL)basedonGroovy.Thislanguageismorereadableforprogrammersandgivesmorefreedomtoprogrambuildprocesses.And,thisisalsothedangerofGradle.
HavingthepowerfulJVMlanguageGroovyinthehandsofdeveloperstocreatebuildtoolsgivesafreedomandtemptationtocreatecomplexbuildprocessesthatseemtobeagoodideaatthestart,butlatermayprovetobejusttoocomplexandhard,and,therefore,expensivetomaintain.ThisisexactlywhyMavenwasimplementedinthefirstplace.
Ihavetostopbeforegettingintoanotherareathatisthegroundforheatedandpointlessdebates.Gradleisanextremelypowerfulbuildtool.Youshoulduseitcarefully,justlikeyouwoulduseaweapon—don'tshootyourlegs.
InstallingGradleToinstallGradle,youwillhavetodownloadthecompiledbinariesfromthehttps://gradle.org/gradle-download/website.
Again,I'dliketoemphasizetheimportanceofcheckingthedownloadintegrityusingchecksums.IhavegivenadetailedwaytodoitinthesectionaboutAntinstallation.Unfortunately,theGradlewebsitedoesnotprovidethechecksumvaluesforthedownloadablefiles.
GradleisdownloadableintheZIPformat.Tounpackthefile,youwillhavetousetheunzipcommand:
$unzipgradle-3.3-bin.zip
ThecreatedsubdirectoryistheusablebinarydistributionofGradle.Usually,Imoveitunder~/bin,makingitavailableonlyformyuseronOSX.Afterthat,youshouldaddthebindirectoryoftheinstallationtothePATH.Todothat,youshouldeditthe~/.bashrcfileandaddthefollowinglines:
exportGRADLE_HOME=~/bin/gradle-3.3/
exportPATH=${GRADLE_HOME}bin:$PATH
Then,restarttheterminalapplication,orjusttype.~/.bashrcandtesttheinstallationofGradle,typingthefollowing:
$gradle-version
Wegettothefollowingoutput,ascanbeseeninthisscreenshot:
SettinguptheprojectwithMavenTostarttheproject,wewillusethedirectorystructureandpom.xmlthatwascreatedbyMavenitselfwhenwestartedwiththefollowingcommandline:$mvnarchetype:generate
Itcreatedthedirectories,thepom.xmlfile,andanApp.javafile.Now,wewillextendthisprojectbycreatingnewfiles.Wewillcodethesortingalgorithmfirstinthepackt.java9.by.example.stringsortpackage:
WhenwecreatethenewpackageintheIDE,theeditorwillautomaticallycreatethestringsortsubdirectoryunderthealreadyexistingsrc/main/java/packt/java9/by/exampledirectory:
CreatingthenewSortclassusingtheIDEwillalsoautomaticallycreateanewfilenamedSort.javainthisdirectory,anditwillfillintheskeletonoftheclass:packagepackt.java9.by.example.stringsort;
publicclassSort{}
WewillnowhaveApp.javacontainingthefollowingcode:
packagepackt.java9.by.example;
publicclassApp
{
publicstaticvoidmain(String[]args)
{
System.out.println("HelloWorld!");
}
}
Mavencreateditasastartingversion.Wewilleditthisfiletoprovideasamplelistthatthesortingalgorithmcansort.IrecommendthatyouusetheIDEtoeditthefileandalsotocompileandrunthecode.TheIDEprovidesashortcutmenutostartthecodeandthisisabiteasierthantypingthecommandinTerminal.Generally,itisrecommendedthatyougetacquaintedwiththeIDEfeaturestosavetimeavoidingrepetitivetasks,suchastypingterminalcommands.Professionaldevelopersusethecommandlinealmostexclusivelytotestcommand-linefeaturesandusetheIDEwheneveritispossible.
CodingthesortMavenandtheIDEcreatedthefilesforthesortprogram.Theyformtheskeletonforourcode,andnowitistimetogrowsomemusclesonthemtoletitmove.Wespentquitesometimetosetuptheprojectbyvisitingthedifferentbuildtools,onlytolearnhowtocompilethecode.Ihopethatthisdidnotdistractyoumuch,butanyhow,wedeservetoseesomerealcode.
First,wewillcreatethecodeforthesortingcode,andafterthat,thecodethatinvokesthesorting.Thecodethatinvokesthesortingisakindoftestingcode.Forsimplicity,wewillnowsimplyuseapublicstaticvoidmainmethodtostartthecode.Wewillusethetestframeworkinlaterchapters.
Asfornow,thecodeforthesortingwilllooklikethis:
packagepackt.java9.by.example.stringsort;
publicclassSort{
publicvoidsort(String[]names){
intn=names.length;
while(n>1){
for(intj=0;j<n-1;j++){
if(names[j].compareTo(names[j+1])>0){
finalStringtmp=names[j+1];
names[j+1]=names[j];
names[j]=tmp;
}
}
n--;
}
}
}
Thisistheclassthatdoesthesorting.Thereisonlyonemethodinthisclassthatdoesthesorting.Theargumenttothemethodisanarraycontainingthestrings,andthemethodsortsthisarray.Themethodhasnoreturnvalue.Thisisdenotedinthedeclarationusingthepseudotypevoid.Methodsusetheirargumentstoperformsometasks,andmayreturnonevalue.Theargumentstothemethodarepassedbyvalue,whichmeansthatthemethodcannotmodifythevariablepassedasargument.However,itcanmodifytheobjectstheargumentscontain.Inthiscase,thearrayismodifiedandwewillsortit.Ontheotherhand,theactualNamesvariablewillpointtothesamearrayandthesortmethodcannotdoanythingtomakethisvariablepointtoadifferentarray.
Thereisnomainmethodinthisclass,whichmeansthatitcannotbestartedfromthecommandlineonitsown.Thisclasscanonlybeusedfromsomeotherclass,aseveryJavaprogramshouldhaveaclassthathasapublicstaticvoidmainmethodthatwecreatedseparately.
Icouldalsoputamainmethodintotheclasstomakeitexecutable,butthatisnotagoodpractice.Realprogramsarecomposedofmanyclasses,andoneclassshouldnotdomanythings.Rather,it'stheopposite.Thesingleresponsibilityprinciplesaysthatasingleclassshouldberesponsibleforonesinglething;therefore,classsortdoesthesorting.Executingtheapplicationisadifferenttask,andthusithastobeimplementedinadifferentclass.
Often,wedonotimplementtheclasscontainingthemainmethod.Often,aframeworkprovidesit.For
example,writingaservletthatrunsinaservletcontainerrequirescontainingaclassthatimplementsthejavax.servlet.Servletinterface.Inthiscase,theprogramseeminglydoesnothaveamainmethod.Theactualimplementationoftheservletcontainerdoes.TheJavacommandlinestartsthecontainerandthecontainerloadstheservletswhentheyareneeded.
Inthefollowingexamplecode,weimplementedtheAppclasscontainingthemainmethod:
packagepackt.java9.by.example;
importpackt.java9.by.example.stringsort.Sort;
publicclassApp{
publicstaticvoidmain(String[]args){
String[]actualNames=newString[]{
"Johnson","Wilson",
"Wilkinson","Abraham","Dagobert"
};
finalSortsorter=newSort();
sorter.sort(actualNames);
for(finalStringname:actualNames){
System.out.println(name);
}
}
}
Thiscodecontainsastringarrayinitializedtocontainconstantvalues,createsanewinstanceoftheSortclass,invokesthesortmethod,andthenprintsoutthecodetothestandardoutput.
Inrealprograms,wealmostneverhavesuchconstantsinprogramcodes;weputthemintoresourcefilesandhavesomecodetoreadtheactualvalues.Thisseparatesthecodefromdataandeasesmaintenance,eliminatingtheriskofaccidentalmodificationofcodestructurewhenonlythedataistobechanged.Similarly,wewillalmostneverwriteanythingtostandardoutputusingSystem.out.Usually,wewilluseloggingpossibilitiesthatareavailablefromdifferentsources.TherearedifferentlibrariesthatprovideloggingfunctionalitiesandloggingisalsoavailablefromtheJDKitself.
Asfornow,wewillfocusonsimplesolutionssoastonotdistractyourfocusfromJavabytheplethoraofdifferentlibrariesandtools.Inthefollowingsection,wewilllookattheJavalanguageconstructsthatweusedtocodethealgorithm.First,wewilllookatthemgenerally,andthen,inabitmoredetail.Theselanguagefeaturesarenotindependentofeachother:onebuildsupontheother,andtherefore,theexplanationwillfirstbegeneral,andwewillgointodetailsinthesubsections.
intn=names.length;<br/>while(n>1){<br/>for(intj=0;j<n-1;j++){<br/>if(names[j].compareTo(names[j+1])>0){<br/>finalStringtmp=names[j+1];<br/>names[j+1]=names[j];<br/>names[j]=tmp;<br/>}<br/>}<br/>n--;<br/>}
Thenvariableholdsthelengthofthearrayatthestartofthesorting.ArraysinJavaalwayshaveapropertythatgivesthelengthanditiscalledlength.Whenwestartthesorting,wewillgofromthestartofthearraytotheendofitand,asyoumayrecall,thelastelement,Wilson,willwalkuptothelastpositionduringthisfirstiteration.Subsequentiterationswillbeshorterand,therefore,thevariablenwillbedecreased.
Blocks
ThecodeinJavaiscreatedincodeblocks.Anythingthatisbetweenthe{and}charactersisablock.Intheprecedingexample,thecodeofthemethodisablock.Itcontainscommands,andsomeofthem,likethewhileloop,alsocontainablock.Insidethatblock,therearetwocommands.Oneofthemisaforloop,againwithablock.Althoughwecanhavesingleexpressionstoformthebodyofaloop,weusuallyuseblocks.Wewilldiscussloopsindetailinjustafewpages.
Aswecouldseeintheprecedingexample,theloopscanbenested,andthusthe{and}charactersformpairs.Ablockcanbeinsideanotherblock,buttwoblockscannotoverlap.Whenthecodecontainsa}character,itisclosingtheblockthatwasopenedlast.
Variables
InJava,justlikeinalmostanyprogramminglanguage,weusevariables.ThevariablesinJavaaretyped.Itmeansthatavariablecanholdavalueofasingletype.ItisnotpossibleforavariabletoholdaninttypeatsomepointintheprogramandlateraStringtype.Whenvariablesaredeclared,theirtypeiswritteninfrontofthevariablename.
Variablesalsohavevisibilityscope.Localvariablesinmethodscanonlybeusedinsidetheblockinwhichtheyaredefined.Avariablecanbeusedinsidemethodsortheycanbelongtoaclassoranobject.Todifferentiatethetwo,weusuallycallthesevariablesfields.
TypesEachvariablehasonetype.InJava,therearetwomajorgroupsoftype:primitiveandreferencetypes.Theprimitivetypesarepredefined,andyoucannotdefineorcreateanewprimitivetype.Thereareeightprimitivetypes:byte,short,int,long,float,double,boolean,andchar.
Thefirstfourtypes,byte,short,int,andlong,aresignednumericintegertypes,capableofstoringpositiveandnegativenumberson8,16,32,and64bits.
Thefloatanddoubletypesstorefloatingpointnumberson32and64bitsintheIEEE754floating-pointformat.
Thebooleantypeisaprimitivetypethatcanonlybetrueorfalse.
Thechartypeisacharacterdatatypethatstoresasingle16-bitUnicodecharacter.
Foreachprimitivetype,thereisaclassthatcanstorethesametypeofvalue.Whenaprimitivetypehastobeconvertedtothematchingclasstypeitisdoneautomatically.Itiscalledautoboxing.ThesetypesareByte,Short,Integer,Long,Float,Double,Boolean,andCharacter.Take,forexample,thefollowingvariabledeclaration:
Integera=113;
Thisconvertsthevalue113,whichisanintnumber,toanIntegerobject.
Thesetypesarepartoftheruntime,andalsopartofthelanguage.Althoughthereisnoprimitivecounterpartofit,thereisaveryimportantandubiquitousclassthatwehavealreadyused:String.Astringcontainscharacters.
Themajordifferencesbetweenprimitivetypesandobjectsarethatprimitivetypescannotbeusedtoinvokemethods,buttheyconsumelessmemory.Thedifferencebetweenthememoryconsumptionanditsconsequencesforspeedisimportantinthecaseofarrays.
ArraysVariablescanbeaprimitivetypeaccordingtotheirdeclaration,ortheymayholdareferencetoanobject.Aspecialobjecttypeisanarray.Whenavariableholdsareferencetoanarray,thenitcanbeindexedwiththe[and]characters,alongwithanintegralvalueconsistingof0orapositivevaluerangingtoonelessthanthearray'slength,toaccessacertainelementofthearray.Multi-dimensionalarraysarealsosupportedbyJavawhenanarrayhaselementsthatarealsoarrays.ArraysareindexedfromzeroinJava.Underoroverindexingischeckedatruntime,andtheresultisanexception.
Anexceptionisspecialconditionthatinterruptsthenormalexecutionflowandstopstheexecutionofthecodeorjumpstotheclosestenclosingcatchstatement.Wewilldiscussexceptionsandhowtohandletheminthenextchapter.
Whenacodehasanarrayofaprimitivetype,thearraycontainsmanymemoryslots,eachholdingthevalueofthetype.Whenthearrayhasareferencetype,inotherwords,whenitisanarrayofobjects,thenthearrayelementsarereferencestoobjects,eachcontainingthetype.Inthecaseofintforexample,eachelementofthearrayis32-bit,whichis4bytes.IfthearrayisatypeofInteger,thentheelementsarereferencestoobjects,pointers,sotosay,whichisusually64-bitusing64-bitJVMand32-biton32-bitJVM.Inadditiontothat,thereisanIntegerobjectsomewhereinmemorythatcontainsthe4-bytevalueandalsoanobjectheaderthatmaybeasmuchas24bytes.
Theactualsizeoftheextrainformationneededtoadministereachobjectisnotdefinedinthestandard.ItmaybedifferentondifferentimplementationsoftheJVM.Theactualcoding,oreventheoptimizationofthecodeinanenvironment,shouldnotdependontheactualsize.However,thedevelopersshouldbeawarethatthisoverheadexistsandisintherangeofaround20orsobytesforeveryobject.Objectsareexpensiveintermsofmemoryconsumption.
Memoryconsumptionisoneissue,butthereissomethingelse.Whentheprogramworkswithalargeamountofdataandtheworkneedstheconsecutiveelementsofthearray,thentheCPUloadsachunkofmemoryintotheprocessorcache.ItmeansthattheCPUcanaccesselementsofthearraythatareconsecutivelyfaster.Ifthearrayisofaprimitivetype,itisfast.Ifthearrayisofsomeclasstype,thentheCPUhastoaccessmemorytogettheactualvalue,whichmaybeasmuchas50timesslower.
ExpressionsExpressionsinJavaareverymuchlikeinotherprogramminglanguages.YoucanusetheoperatorsthatmaybesimilarfromlanguagessuchasCorC++.Theyareasfollows:
Unaryprefixandpostfixincrementoperators(--and++beforeandafteravariable)Unarysign(+and-)operatorsLogical(!)andbitwise(~)negationMultiplication(*),division(/),andmodulo(%)Additionandsubtraction(+and-again,butthistimeasbinaryoperators)Shiftoperatorsmovethevaluesbitwise,andthereisleft(<<)andright(>>)shiftandunsignedrightshift(>>>)Thecomparingoperatorsare<,>,<=,>=,==,!=andinstanceofthatresultinbooleanvalueTherearebitwiseor(|),and(&),exclusiveor(^)operators,andsimilarlylogicalor(||),and(&&)operators
Whenlogicaloperatorsareevaluated,theyareshortcutevaluated.Itmeanstheright-handoperandisevaluatedonlyiftheresultcannotbeidentifiedfromtheresultoftheleftoperand.
Theternaryoperatorisalsosimilartotheone,likeitisonC,selectingfromoneoftheexpressionsbasedonsomecondition:condition?expression1:expression2.Usually,thereisnoproblemwiththeternaryoperator,butsometimesyouhavetobecarefulasthereisacomplexrulecontrollingthetypeconversionsincasethetwoexpressionsarenotofthesametype.It'salwaysbettertohavethetwoexpressionsbeofthesametype.
Finally,thereisanassignmentoperator(=)thatassignsthevalueofanexpressiontoavariable.Foreachbinaryoperator,thereisanassignmentversionthatcombines=withabinaryoperatortoperformanoperationinvolvingtherightoperandandassigntheresulttotheleftoperand,whichmustbeavariable.Theseare+=,-=,*=,/=,%=,&=,^=,|=,<<=,>>=,and>>>=.
Theoperatorshaveprecedenceandcanbeoverriddenbyparentheses,asusual.
Animportantpartofexpressionsisinvokingmethods.Staticmethodscanbeinvokedbythenameoftheclassandthenameofthemethod.Forexample,tocalculatethesineof1.22,wecanwritethefollowingline:
doublez=Math.sin(1.22);
Here,Mathistheclassfromthepackagejava.lang.ThemethodsinisinvokedwithoutusinganyinstanceofMath.Thismethodisstatic,anditisnotlikelythatwewilleverneedanyotherimplementationofitthantheoneprovidedintheclassMath.
Non-staticmethodscanbeinvokedusinganinstanceandthenameofthemethodwithadotseparatingthetwo.Forexample,takethefollowingcodelineasanexample:
System.out.println("HelloWorld");
TheprecedingcodeusesaninstanceoftheclassPrintStreamthatisreadilyavailablethroughastaticfieldintheclassSystem.Thisvariableiscalledout,andwhenwewriteourcode,wehavetoreferenceitasSystem.out.ThemethodprintlnisdefinedintheclassPrintStreamandweinvokeitontheobjectreferencedbythevariableout.Thisexamplealsoshowsthatstaticfieldscanalsobereferencedthroughthenameoftheclassandthefieldseparatedbyadot.Similarly,whenweneedtoreferenceanon-staticfield,wecandoitthroughaninstanceoftheclass.
Staticmethodsdefinedinthesameclassfromwhereitisinvokedorinheritedcanbeinvokedwithouttheclassname.Invokinganon-staticmethoddefinedinthesameclassorbeinginheritedcanbeinvokedwithoutaninstance.Inthiscase,theinstanceisthecurrentobjecttheexecutionisin.Thisobjectisalsoavailablethroughthethiskeyword.Similarly,whenweuseafieldofthesameclasswhereourcodeis,wesimplyusethename.Incaseofastaticfield,theclassweareinbydefault.Inthecaseofanon-staticfield,theinstanceistheobjectreferencedbythethiskeyword.
Youcanalsoimportastaticmethodintoyourcodeusingtheimportstaticlanguagefeature,inwhichcaseyoucaninvokethemethodwithoutthenameoftheclass.
Theargumentsofthemethodcallsareseparatedusingcommas.Methodsandmethodargumentpassingisanimportanttopicthatwewillmentionindetailinaseparatesubsection.
LoopsTheforloopinsidethewhileloopwillgothroughalltheelementsfromthefirst(indexedwithzeroinJava)uptillthelast(indexedwithn-1).Generally,theforloophasthesamesyntaxasinC:
for(initialexpression;condition;incrementexpression)
block
First,theinitialexpressionisevaluated.Itmaycontainvariabledeclaration,asinourexample.Thevariablejintheprecedingexampleisvisibleonlyinsidetheblockoftheloop.Afterthis,theconditionisevaluated,andaftereachexecutionoftheblock,theincrementexpressionisexecuted.Thelooprepeatssolongastheconditionistrue.Iftheconditionisfalserightaftertheexecutionoftheinitialexpression,theloopdoesnotexecuteatall.Theblockisalistofcommandsseparatedbysemicolonsandenclosedbetweenthe{and}characters.
Insteadof{and},enclosedblockJavaletsyouuseasinglecommandfollowingtheheadoftheforloop.Thesameistrueinthecaseofthewhileloop,andalsofortheif...elseconstructs.Practiceshowsthatthisisnotsomethingaprofessionalshoulduse.Professionalcodealwaysusescurlybraces,evenwhenthereisonlyasinglecommandwheretheblockisinplace.Thispreventsthedanglingelseproblemandgenerallymakesthecodemorereadable.ThisissimilartomanyC-likelanguages.Mostofthemallowasinglecommandattheseplaces,andprofessionalprogrammersavoidusingasinglecommandintheselanguagesforreadabilitypurposes.Itisironicthattheonlylanguagethatstrictlyrequirestheuseofthe{and}bracesattheseplacesisPerl—theonelanguageinfamousforunreadablecode.
Theloopinthefor(intj=0;j<n-1;j++){samplestartsfromzeroandgoeston-2.Writingj<n-1isthesame,inthiscase,asj<=n-2.Wewilllimitjtostopintheloopbeforetheendofthesectionofthearray,becausewereachbeyondtheindexjbyonecomparingandconditionallyswappingtheelementsindexedbyjandj+1.Ifwewentoneelementfurther,wewouldtrytoaccessanelementofthearraythatdoesnotexist,anditwouldcausearuntimeexception.Tryandmodifytheloopconditiontoj<norj<=n-1andyouwillgetthefollowingerrormessage:
ItisanimportantfeatureofJavathattheruntimechecksmemoryaccessandthrowsanexceptioninthecaseofbadarrayindexing.Inthegoodolddays,whilecodinginC,often,wefacedunexplainableerrorsthatstoppedourcodemuchlaterandattotallydifferentcodelocationsfromwheretherealerrorwas.ArrayindexinCsilentlycorruptedthememory.Javastopsyouassoonasyoumakeamistake.Itfollowsthefail-fastapproachthatyoualsoshoulduseinyourcode.Ifsomethingiswrong,theprogramshouldfail.Nocodeshouldtrytolivewithorovercomeanerrorthatcomesfromacodingerror.Codingerrorsshouldbefixedbeforetheycauseevenmoredamage.
TherearealsotwomoreloopconstructsinJava:thewhileloopandthedoloop.Theexamplecontainsawhileloop:itistheouterloopthatrunssolongasthereareatleasttwoelementsthatmayneedswappinginthearray:
while(n>1){
Thegeneralsyntaxandsemanticsofthewhileloopisverysimple,asseenhere:
while(condition)block
Repeattheexecutionoftheblocksolongastheconditionistrue.Iftheconditionisnottrueattheverystartoftheloop,thendonotexecutetheblockatall.Thedoloopisalsosimilar,butitcheckstheconditionaftereachexecutionoftheblock:
doblockwhile(condition);
Forsomereason,programmersrarelyusedoloops.
ConditionalexecutionTheheartofthesortistheconditionandthevalueswappinginsidetheloop.
if(names[j].compareTo(names[j+1])>0){
finalStringtmp=names[j+1];
names[j+1]=names[j];
names[j]=tmp;
}
ThereisonlyoneconditionalcommandinJava,theifcommand.Ithasthefollowingformat:
if(condition)blockelseblock
Themeaningofthecodestructureisquitestraightforward.Iftheconditionistrue,thenthefirstblockisexecuted,otherwise,thesecondblockisexecuted.Theelsekeyword,alongwiththesecondblock,isoptional.Ifthereisnothingtobeexecutedincasethattheconditionisfalse,thenthereisnoneedfortheelsebranch,justlikeintheexample.Ifthearrayelementindexedwithjislaterinthesortorderthantheelementj+1,thenweswapthem,butiftheyarealreadyinorder,thereisnothingtodowiththem.
Toswapthetwoarrayelements,wewilluseatemporaryvariablenamedtmp.ThetypeofthisvariableisString,andthisvariableisdeclaredtobefinal.ThefinalkeywordhasdifferentmeaningsdependingonwhereitisusedinJava.Thismaybeconfusingforbeginnersunlessyouarewarnedaboutit,justlikenow.Afinalclassormethodisatotallydifferentthingthanafinalfield,whichisagaindifferentthanafinallocalvariable.
FinalvariablesInourcase,tmpisafinallocalvariable.Thescopeofthisvariableislimitedtotheblockfollowingtheifstatement,andinsidethisblock,thisvariablegetsavalueonlyonce.Theblockisexecutedmanytimesduringthecodeexecution,andeachtimethevariablegetsintoscope,itgetsavalue.However,thisvaluecannotbechangedintheblock.Thismaybeabitconfusing.Youcanthinkaboutitashavinganewtmpeachtimetheblockexecutes.Thevariablegetsdeclaredandhasanundefinedvalueandcangetavalueonlyonce.
Finallocalvariablesdonotneedtogetthevaluewheretheyaredeclared.Youcanassignavaluetoafinalvariablesometimelater.Itisimportantthatthereshouldnotbeacodeexecutionthatassignsavaluetoafinalvariablethatwasalreadyassignedavaluebefore.Thecompilerchecksitanddoesnotcompilethecodeifthereisapossibilityofthereassignmentofafinalvariable.
Todeclareavariabletobefinalisgenerallytoeasereadabilityofthecode.Whenyouseeavariableinacodedeclaredtobefinal,youcanassumethatthevalueofthevariablewillnotchangeandthemeaningofthevariablewillalwaysbethesamewhereveritwasusedinthemethod.ItwillalsohelpyouavoidsomebugswhenyoutrytomodifysomefinalvariablesandtheIDEwillimmediatelycomplainaboutit.Insuchsituations,itislikelytobeaprogrammingmistakethatisdiscoveredextremelyearly.
Inprinciple,itispossibletowriteaprogramwhereallvariablesarefinal.Itisgenerallyagoodpracticetodeclareallfinalvariablesthatcanbedeclaredtobefinaland,incasesomevariablemaynotbedeclaredfinal,thentrytofindsomewayofcodingthemethodabitdifferently.
Ifyouneedtointroduceanewvariabletodothat,itprobablymeansyouwereusingonevariabletostoretwodifferentthings.Thesethingsareofthesametypeandstoredinthesamevariableatdifferenttimesbut,logically,theystillaredifferentthings.Donottrytooptimizetheuseofvariables.Neveruseavariablebecauseyoualreadyhaveavariableofthetypeinyourcodethatisavailable.Ifitislogicallyadifferentthing,thendeclareanewvariable.Whilecoding,alwaysprefersourcecodeclarityandreadability.InJava,especially,theJustInTimecompilerwilloptimizeallthisforyou.
Althoughwedonotexplicitlytendtousethefinalkeywordontheargumentlistofamethod,itisgoodpracticetomakesurethatyourmethodscompileandworkiftheargumentsaredeclaredfinal.Someexperts,includingme,believethatthemethodparametersshouldhavebeenmadefinalbydefaultinthelanguage.ThisissomethingthatwillnothappeninanyversionofJava,solongasJavafollowsthebackwardcompatibilityphilosophy.
ClassesNowthatwehavelookedattheactualcodelinesandhaveunderstoodhowthealgorithmworks,let'slookatthemoreglobalstructuresofthecodethatbringsittogether:classesandpackagesenclosingthemethods.
EveryfileinaJavaprogramdefinesaclass.AnycodeinaJavaprogramisinsideaclass.ThereisnothinglikeglobalvariablesorglobalfunctionsasinC,Python,Go,orotherlanguages.Javaistotallyobjectoriented.
Therecanbemorethanoneclassinasinglefile,butusuallyonefileisoneclass.Later,wewillseethatthereareinnerclasseswhenaclassisinsideanotherclass,but,fornow,wewillputoneclassintoonefile.
TherearesomefeaturesintheJavalanguagethatwedonotuse.Whenthelanguagewascreated,thesefeaturesseemedtobeagoodidea.CPU,memory,andotherresources,includingmediocredevelopers,werealsomorelimitedthantoday.Someofthefeatures,perhaps,mademoresensebecauseoftheseenvironmentalconstraints.Sometimes,Iwillmentionthese.Inthecaseofclasses,youcanputmorethanoneclassintoasinglefilesolongasonlyoneispublic.Thatisbadpractice,andwewillneverdothat.Javaneverobsoletesthesefeatures.ItisaphilosophyofJavatoremaincompatiblewithallpreviousversions.Thisphilosophyisgoodforthealreadywritten,hugeamountoflegacycode.Javacodewrittenandtestedwithanoldversionwillworkinanewerenvironment.Atthesametime,thosefeatureslurebeginnerstoawrongstyle.Forthisreason,sometimes,Iwillnotevenmentionthesefeatures.Forexample,here,Icouldsay:Thereisoneclassinafile.Thiswouldnotbeabsolutelycorrect.Atthesametime,itismoreorlesspointlesstoexplainingreatdetailafeaturethatIrecommendnottobeused.Later,Imaysimplyskipthemand"lie".Therearenottoomanyofthosefeatures.
Aclassisdefinedusingtheclasskeywordandeachclasshastohaveaname.Thenameshouldbeuniquewithinthepackage(seethenextsection)andhastobethesameasthenameofthefile.Aclasscanimplementaninterfaceorextendanotherclass,forwhichwewillseeanexamplelater.Aclasscanalsobeabstract,final,andpublic.Thesearedefinedwiththeappropriatekeywords,asyouwillseeinexamples.
Ourprogramhastwoclasses.Bothofthemarepublic.Thepublicclassesareaccessiblefromanywhere.Classesthatarenotpublicarevisibleonlyinsidethepackage.Innerandnestedclassescanalsobeprivatevisibleonlyinsidethetop-levelclassdefinedonthefilelevel.
ClassesthatcontainamainmethodtobeinvokedbytheJavaenvironmentshouldbepublic.ThatisbecausetheyareinvokedbytheJVM.
Theclassstartsatthebeginningofthefilerightafterthepackagedeclarationandeverythingbetweenthe{and}charactersbelongtotheclass.Themethods,fields,innerornestedclasses,andsoonarepartoftheclass.Generally,curlybracesdenotesomeblockinJava.ThiswasinventedintheClanguage,andmany
languagesfollowthisnotation.Classdeclarationissomeblock,methodsaredefinedusingsomeblock,loops,andconditionalcommandsuseblocks.
Whenweusetheclasses,wewillhavetocreateinstancesofclasses.Theseinstancesareobjects.Inotherwords,objectsarecreatedinstantiatingaclass.Todothat,thenewkeywordisusedinJava.WhenthelinefinalSortsorter=newSort();isexecutedintheAppclass,itcreatesanewobjectinstantiatingtheSortclass.WewillalsosaythatwecreatedanewSortobjectorthatthetypeoftheobjectisSort.Whenanewobjectiscreated,aconstructoroftheobjectisinvoked.Abitsloppy,Imaysay,thattheconstructorisaspecialmethodintheclassthathasthesamenameastheclassitselfandhasnoreturnvalue.Thatisbecauseitreturnsthecreatedobject.Tobeprecise,constructorsarenotmethods.Theyareinitializersandtheydonotreturnthenewobject.Theyworkonthenot-ready-yetobject.Whenaconstructorexecutingtheobjectisnotfullyinitialized,someofthefinalfieldsmaynotbeinitializedandtheoverallinitializationstillcanfailiftheconstructorthrowsanexception.Inourexample,wedonothaveanyconstructorinthecode.Insuchacase,Javacreatesadefaultconstructorthatacceptsnoargumentanddoesnotmodifythealreadyallocatedbutuninitializedobject.IftheJavacodedefinesaninitializer,thentheJavacompilerdoesnotcreateadefaultone.
Aclasscanhavemanyconstructors,eachhavingdifferentparameterlist.
InadditiontoconstructorsJavaclassescancontaininitializerblocks.Theyareblocksontheclasslevel,thesamelevelastheconstructorandmethods.Thecodeintheseblocksiscompiledintotheconstructorsandisexecutedwhentheconstructorisexecuting.
Itisalsopossibletoinitializestaticfieldsinstaticinitializerblocks.Thesearetheblocksonthetoplevelinsidetheclasswiththestatickeywordinfrontofthem.Theyareexecutedonlyoncewhentheclassisloaded.
WenamedtheclassesinourexampleAppandSort.ThisisaconventioninJavatonamealmosteverythinginCamelCase.
CamelCaseiswhenthewordsarewrittenwithoutspacesbetweenthem.Thefirstwordmaystartwithlowercaseoruppercase,and,todenotethestartofthesecondandsubsequentwords,theystartwithuppercase.ForExampleThisIsALongCamelCasename.
Classnamesstartwithanuppercaseletter.Thisisnotarequirementofthelanguageformally,butthisisaconventionthateveryprogrammershouldfollow.Thesecodingconventionshelpyoucreatecodethatiseasiertounderstandbyotherprogrammers,andleadtoeasiermaintenance.Staticcodeanalyzertools,suchasCheckstyle(http://checkstyle.sourceforge.net/),alsocheckthattheprogrammersfollowtheconventions.
Inner,nested,local,andanonymousclassesIhavealreadymentionedinnerandnestedclassesintheprevioussection.Nowwelookattheminbitmoredetail.
Thedetailsofinnerandnestedclassesatthispointmaybedifficult.Don'tfeelashamedifyoudonotunderstandthissectionfully.Ifitistoodifficult,skiptothenextsectionandreadaboutpackagesandreturnherelater.Nested,inner,andlocalclassesarerarelyused,thoughtheyhavetheirrolesanduseinJava.AnonymousclasseswereverypopularinGUIprogrammingwiththeSwinguserinterfacethatalloweddeveloperstocreateJavaGUIapplications.WithJava8andthelambdafeature,anonymousclassesarenotsoimportantthesedays,andwiththeemergingJavaScriptandbrowsertechnology,theJavaGUIbecamelesspopular.
Whenaclassisdefinedinafileonitsown,itiscalledatop-levelclass.Classesthatareinsideanotherclassare,obviously,nottop-levelclasses.Iftheyaredefinedinsideaclassonthesamelevelasfields(variablesthatarenotlocaltosomemethodorotherblock),theyareinnerornestedclasses.Therearetwodifferencesbetweenthem.Oneisthatnestedclasseshavethestatickeywordbeforetheclasskeywordattheirdefinition,andinnerclassesdon't.
Theotherdifferenceisthatinstancesofnestedclassescanexistwithoutaninstanceofthesurroundingclass.Innerclassinstancesalwayshaveareferencetoaninstanceofthesurroundingclass.
Becauseinnerclassinstancescannotexistwithoutaninstanceofthesurroundingclass,theirinstancecanonlybecreatedbyprovidinganinstanceoftheouterclass.Wewillseenodifferenceifthesurroundingclassinstanceistheactualthisvariable,butifwewanttocreateaninstanceofaninnerclassfromoutsidethesurroundingclass,thenwehavetoprovideaninstancevariablebeforethenewkeywordseparatedbyadot,justlikeifnewwereamethod.Forexample,wecouldhaveaclassnamedTopLevelthathasaclassnamedInnerClass,likeinthefollowingcodesnippet:
publicclassTopLevel{
classInnerClass{}
}
ThenwecancreateaninstanceoftheInnerClassfromoutsidewithonlyaTopLevelobject,likeinthissnippet:
TopLeveltl=newTopLevel();
InnerClassic=tl.newInnerClass();
Asinnerclasseshaveanimplicitreferencetoaninstanceoftheenclosingclass,thecodeinsidetheinnerclasscanaccessthefieldsandthemethodsoftheenclosingclass.
Nestedclassesdonothaveanimplicitreferencetoanyinstanceoftheenclosingclass,andtheymaybeinstantiatedwiththenewkeywordwithoutanyreferencetoanyinstanceofanyotherclass.Becauseofthat,theycannotaccessthefieldsoftheenclosingclassunlesstheyarestaticfields.
Localclassesareclassesthataredefinedinsideamethod,constructor,oraninitializerblock.Wewillsoontalkaboutinitializerblocksandconstructors.Localclassescanbeusedinsidetheblockwheretheyaredefined.
Anonymousclassesaredefinedandinstantiatedinasinglecommand.Theyareashortformofanested,inner,orlocalclass,andtheinstantiationoftheclass.Anonymousclassesalwaysimplementaninterfaceorextendanamedclass.Thenewkeywordisfollowedbythenameoftheinterfaceortheclasswiththeargumentlisttotheconstructorbetweenparentheses.Theblockthatdefinesthebodyoftheanonymousclassstandsimmediatelyaftertheconstructorcall.Inthecaseofextendinganinterface,theconstructorcanonlybetheonewithoutargument.Theanonymousclasswithnonamecannothaveitsownconstructors.InmodernJavaweusuallyuselambdainsteadofanonymousclasses.
Lastbutnotleast—well,actually,leastIshouldmentionthatnestedandinnerclassescanalsobenestedindeeperstructures.Innerclassescannotcontainnestedclasses,butnestedclassescancontaininnerclasses.Why?Ihavenevermetanyonewhocouldreliablytellmetherealreason.Thereisnoarchitecturalreason.Itcouldbelikethat.Javadoesnotpermitthat.However,itisnotreallyinteresting.Ifyouhappentowritecodethathasmorethanonelevelofclassnestingthenjuststopdoingit.Mostprobablyyouaredoingsomethingwrong.
PackagesClassesareorganizedintopackagesandthefirstcodelineinafileshouldspecifythepackagethattheclassisin.
packagepackt.java9.by.example.stringsort;
Ifyoudonotspecifythepackage,thentheclasswillbeinthedefaultpackage.Thisshouldnotbeused,exceptinthesimplestcasewhenyouwanttotrysomecode.WithJava9,youcanusejshellforthispurpose,so,asopposedtopreviousversionsofJava,nowthesuggestionbecomesverysimple—neverputanyclassinthedefaultpackage.
Thenameofthepackagesishierarchical.Thepartsofthenamesareseparatedbydots.Usingpackagenameshelpsyouavoidnamecollisions.Namesoftheclassesareusuallykeptshortandputtingthemintopackageshelpstheorganizationoftheprogram.Thefullnameofaclassincludesthenameofthepackagetheclassisin.Usually,wewillputthoseclassesintoapackagethatareinsomewayrelated,andaddsomethingtoasimilaraspectofaprogram.Forexample,controllersinanMVCpatternprogramarekeptinasinglepackage.Packagesalsohelpyouavoidnamecollisionofclasses.However,thisonlypushestheproblemfromclassnamecollisiontopackagenamecollision.Wehavetomakesurethatthenameofthepackageisuniqueanddoesnotcauseanyproblemwhenourcodeisusedtogetherwithanyotherlibrary.Whenanapplicationisdeveloped,wejustcannotknowwhatotherlibrarieswillbeusedinlaterversions.Tobepreparedfortheunexpected,theconventionistonamethepackagesaccordingtosomeInternetdomainnames.Whenadevelopmentcompanyhasthedomainnameacmecompany.com,thentheirsoftwareisusuallyunderthecom.acmecompany...packages.Itisnotastrictlanguagerequirement.Itisonlyaconventiontowritethedomainnamefromrighttoleft,anduseitaspackagename,butthisprovestobefairlygoodinpractice.Sometimes,likeIdointhisbook,onecandeviatefromthispracticesoyoucanseethatthisruleisnotcarvedinstone.
Whentherubberhitstheroad,andthecodeiscompiledintobytecode,thepackagebecomesthenameoftheclass.Thus,thefullnameoftheSortclassispackt.java9.by.example.stringsort.Sort.Whenyouuseaclassfromanotherpackage,youcanusethisfullnameorimporttheclassintoyourclass.Again,thisisonthelanguagelevel.UsingthefullyqualifiednameorimportingmakesnodifferencewhenJavabecomesbytecode.
MethodsWehavealreadydiscussedmethods,butnotindetail,andtherearestillsomeaspectsthatweshouldmeetbeforewegoon.
Therearetwomethodsinthesampleclasses.Therecanbemanymethodsinaclass.Methodnamesarealsocamelcasedbyconvention,andthenamestartswithalowercaseletter,asopposedtoclasses.Methodsmayreturnavalue.Ifamethodreturnsavalue,themethodhastodeclarethetypeofthevalueitreturnsand,inthatcase,anyexecutionofthecodehastofinishwithareturnstatement.Thereturnstatementhasanexpressionafterthekeyword,whichisevaluatedwhenthemethodisexecutedandisreturnedbythemethod.Itisgoodpracticetohaveonlyonesinglereturnfromamethodbut,insomesimplecases,breakingthatcodingconventionmaybeforgiven.Thecompilerchecksthepossiblemethodexecutionpaths,anditisacompile-timeerrorifsomeofthepathsdonotreturnavalue.
Whenamethoddoesnotreturnanyvalue,ithastobedeclaredtobevoid.Thisisaspecialtypethatmeansnovalue.Methodsthatarevoid,suchasthepublicstaticvoidmainmethod,maysimplymissthereturnstatementandjustend.Ifthereisareturnstatement,thereisnoplaceforanyexpressiondefiningareturnvalueafterthereturnkeyword.Again,thisisacodingconventiontonotusethereturnstatementincaseofamethodthatdoesnotreturnanyvalue,butinsomecodingpatterns,thismaynotbefollowed.
Methodscanbeprivate,protected,public,andstatic,andwewilldiscusstheirmeaninglater.
Wehaveseenthatthemainmethodthatwasinvokedwhentheprogramstartedisastaticmethod.Suchamethodbelongstotheclassandcanbeinvokedwithouthavinganyinstanceoftheclass.Staticmethodsaredeclaredwiththestaticmodifier,andtheycannotaccessanyfieldormethodthatisnotstatic.
Inourexample,thesortmethodisnotstatic,butasitdoesnotaccessanyfieldanddoesnotcallanynon-staticmethod(asamatteroffact,itdoesnotcallanymethodatall),itcouldjustaswellbestatic.Ifwechangethedeclarationofthemethodtopublicstaticvoidsort(String[]names){(notethewordstatic),theprogramstillworks,buttheIDEwillgiveawarningwhileediting,forexample:
Staticmember'packt.java9.by.example.stringsort.Sort.sort(java.lang.String[])'accessedviainstancereference
ThatisbecauseyoucanaccessthemethodwithoutaninstancedirectlythroughthenameoftheSort.sort(actualNames);classwithouttheneedofthesortervariable.CallingastaticmethodviaaninstancevariableispossibleinJava(againsomethingthatseemedtobeagoodideaatthegenesisofJava,butisprobablynot),butitmaymisleadthereaderofthecodeintothinkingthatthemethodisaninstancemethod.
Makingthesortmethodstatic,themainmethodcanbeasfollows:
publicstaticvoidmain(String[]args){
String[]actualNames=newString[]{
"Johnson","Wilson",
"Wilkinson","Abraham","Dagobert"
};
Sort.sort(actualNames);
for(finalStringname:actualNames){
System.out.println(name);
}
}
Itseemstobemuchsimpler(itis),and,incasethemethoddoesnotuseanyfield,youmaythinkthatthereisnoreasontomakeamethodnon-static.DuringthefirsttenyearsofJava,staticmethodswereinheavyuse.Thereisevenaterm,utilityclass,whichmeansaclassthathasonlystaticmethodsandshouldnotbeinstantiated.WiththeadventofInversionofControlcontainers,wetendtouselessstaticmethods.Whenstaticmethodsareused,itishardertousedependencyinjection,anditisalsomoredifficulttocreatetests.Wewilldiscusstheseadvancedtopicsinthenextfewchapters.Fornow,youareinformedastowhatstaticmethodsareandthattheycanbeused;however,usually,unlessthereisaveryspecialneedforthem,wewillavoidthem.
Later,wewilllookathowclassesareimplementedinthehierarchy,andhowclassesmayimplementinterfacesandextendotherclasses.Whenthesefeaturesarelookedat,wewillseethatthereareso-calledabstractclassesthatmaycontainabstractmethods.Thesemethodshavetheabstractmodifier,andtheyarenotdefined—onlythename,argumenttypes(andnames),andreturntypearespecified.Aconcrete(non-abstract)classextendingtheabstractclassshoulddefinethem.
Theoppositeofabstractmethodisthefinalmethoddeclaredwiththefinalmodifier.Afinalmethodcannotbeoverriddeninsubclasses.
InterfacesMethodsarealsodeclaredininterfaces.Amethoddeclaredinaninterfacedoesnotdefinetheactualbehaviorofthemethod;theydonotcontainthecode.Theyhaveonlytheheadofthemethod;inotherwords,theyareabstractimplicitly.Althoughnobodydoes,youmayevenusetheabstractkeywordinaninterfacewhenyoudefineamethod.
Interfaceslookverysimilartoclasses,butinsteadofusingtheclasskeyword,weusetheinterfacekeyword.Becauseinterfacesaremainlyusedtodefinemethods,themethodsarepublicifnomodifierisused.
Interfacescanalsodefinefields,butsinceinterfacescannothaveinstances(onlyimplementingclassescanhaveinstances),thesefieldsareallstaticandtheyalsohavetobefinal.Thisisthedefaultforfieldsininterfaces,thuswedonotneedtowritetheseifwedefinedfieldsininterfaces.
Itwasacommonpracticetodefineonlyconstantsinsomeinterfacesandthenusetheseinclasses.Todothat,theeasiestwaywastoimplementtheinterface.Sincetheseinterfacesdonotdefineanymethod,theimplementationisnothingmorethanwritingtheimplementskeywordandthenameoftheinterfaceintotheheaderoftheclassdeclaration.Thisisbadpracticebecausethiswaytheinterfacebecomespartofthepublicdeclarationoftheclass,althoughtheseconstantsareneededinsidetheclass.Ifyouneedtodefineconstantsthatarenotlocaltoaclassbutareusedinmanyclasses,thendefinetheminaclassandimportthefieldsusingimportstaticorjustusethenameoftheclassandthefield.
Interfacescanalsohavenestedclasses,buttheycannothaveinnerclasses.Theobviousreasonforthatisthatinnerclassinstanceshaveareferencetoaninstanceoftheenclosingclass.Inthecaseofaninterface,therearenoinstances,soaninnerclasscouldnothaveareferencetoaninstanceofanenclosinginterface,becausethatjustdoesnotexist.Thejoyfulpartofitisthatwedonotneedtousethestatickeywordinthecaseofnestedclassesbecausethatisthedefault,justasinthecaseoffields.
WiththeadventofJava8,youcanalsohavedefaultmethodsininterfacesthatprovidedefaultimplementationofthemethodfortheclassesthatimplementtheinterface.TherecanalsobestaticandprivatemethodsininterfacessinceJava9.
Methodsareidentifiedbytheirnameandtheargumentlist.Youcanreuseanameforamethodandhavedifferentargumenttypes;Javawillidentifywhichmethodtousebasedonthetypesoftheactualarguments.Thisiscalledmethodoverloading.Usually,itiseasytotellwhichmethodyoucall,butwhentherearetypesthatextendeachother,thesituationbecomesmorecomplex.Thestandarddefinesverypreciserulesfortheactualselectionofthemethodthatthecompilerfollows,sothereisnoambiguity.However,fellowprogrammerswhoreadthecodemaymisinterpretoverloadedmethodsor,atleast,willhavehardtimeidentifyingwhichmethodisactuallycalled.Methodoverloadingmayalsohinderbackwardcompatibilitywhenyouwanttoextendyourclass.Thegeneraladviceistothinktwicebeforecreatingoverloadedmethods.Theyarelucrative,butmaysometimesbecostly.
ArgumentpassingInJava,argumentsarepassedbyvalue.Whenthemethodmodifiesanargumentvariable,thenonlythecopyoftheoriginalvalueismodified.Anyprimitivevalueiscopiedduringthemethodcall.Whenanobjectispassedasanargument,thenthecopyofthereferencetotheobjectispassed.
Thatway,theobjectisavailabletobemodifiedforthemethod.Inthecaseofclassesthathavetheirprimitivecounterpart,andalsointhecaseofStringandsomeotherclasstypes,theobjectssimplydonotprovidemethodsorfieldstomodifythestate.Thisisimportantfortheintegrityofthelanguage,andtonotgetintotroublewhenobjectsandprimitivevaluesautomaticallygetconverted.
Inothercases,whentheobjectismodifiable,themethodcaneffectivelyworkontheveryobjectitwaspassedto.Thisisalsothewaythesortmethodinourexampleworksonthearray.Thesamearray,whichisalsoanobjectitself,ismodified.
Thisargumentpassingismuchsimplerthanitisinotherlanguages.Otherlanguagesletthedevelopermixthepassbyreferenceandthepassbyvalueargumentpassing.InJava,whenyouuseavariablebyitselfasanexpressiontopassaparametertoamethod,youcanbesurethatthevariableitselfisnevermodified.Theobjectitrefersto,however,incaseitismutable,maybemodified.
Anobjectismutableifitcanbemodified,alteringthevalueofsomeofitsfielddirectlyorviasomemethodcall.Whenaclassisdesignedinawaythatthereisnonormalwaytomodifythestateoftheobjectafterthecreationoftheobject,theobjectisimmutable.TheclassesByte,Short,Integer,Long,Float,Double,Boolean,Character,aswellasString,aredesignedintheJDKsothattheobjectsareimmutable.Itispossibletoovercomethelimitationofimmutabilityimplementationofcertainclassesusingreflection,butdoingthatishackingandnotprofessionalcoding.Doingthatcanbedoneforonesinglepurpose—gettingabetterknowledgeandunderstandingoftheinnerworkingsofsomeJavaclasses,butnothingelse.
FieldsFieldsarevariablesontheclasslevel.Theyrepresentthestateofanobject.Theyarevariables,withdefinedtypeandpossibleinitialvalue.Fieldscanbestatic,final,transient,andvolatile,andtheaccessmaybemodifiedwiththepublic,protected,andprivatekeywords.
Staticfieldsbelongtotheclass.Itmeansthatthereisoneofthemsharedbyalltheinstancesoftheclass.Normal,non-staticfieldsbelongtotheobjects.Ifyouhaveafieldnamedf,theneachinstanceoftheclasshasitsownf.Iffisdeclaredstatic,thentheinstanceswillsharetheverysameffield.
Thefinalfieldscannotbemodifiedaftertheyareinitialized.Initializationcanbedoneonthelinewheretheyaredeclared,inaninitializerblockorintheconstructorcode.Thestrictrequirementisthattheinitializationhastohappenbeforetheconstructorreturns.Thisway,themeaningofthefinalkeywordisverydifferent,inthiscase,fromwhatitmeansinthecaseofaclassoramethod.Afinalclasscannotbeextendedandafinalmethodcannotbeoverriddeninanextendingclass,aswewillseeinthenextchapter.Thefinalfieldsareeitheruninitializedorgetavalueduringinstancecreation.Thecompileralsochecksthatthecodedoesinitializeallfinalfieldsduringtheobject-instancecreationorduringtheclassloading,incasethefinalfieldisstatic,andthatthecodeisnotaccessing/readinganyfinalfieldthatwasnotyetinitialized.
Itisacommonmisconceptionthatthefinalfieldshavetobeinitializedatthedeclaration.Itcanbedoneinaninitializercodeorinaconstructor.Therestrictionisthat,nomatterwhichconstructoriscalledincasetherearemore,thefinalfieldshavetobeinitializedexactlyonce.
Thetransientfieldsarenotpartoftheserializedstateoftheobject.Serializationisanactofconvertingtheactualvalueofanobjecttophysicalbytes.Deserializationistheoppositewhentheobjectiscreatedfromthebytes.Itisusedtosavethestateinsomeframeworks.Thecodethatdoestheserialization,java.lang.io.ObjectOutputStream,worksonlywithclassesthatimplementtheSerializableinterface,andusesonlythefieldsfromthoseobjectsthatarenottransient.Veryobviously,transientfieldsarealsonotrestoredfromthebytesthatrepresenttheserializedformoftheobjectbecausetheirvalueisnotthere.
Serializationisusuallyusedindistributedprograms.Agoodexampleisthesessionobjectofaservlet.Whentheservletcontainerrunsonaclusterednode,somefieldsofobjectsstoredintothesessionobjectmaymagicallydisappearbetweenHTTPhits.Thatisbecauseserializationsavesandreloadsthesessiontomovethesessionbetweenthenodes.Serialization,insuchasituation,mayalsobeaperformanceissueifadeveloperdoesnotknowthesideeffectsofthestoredlargeobjectsinthesession.
Thevolatilekeywordisakeywordthattellsthecompilerthatthefieldmaybeusedbydifferentthreads.Whenavolatilefieldisaccessedbyanycode,theJITcompilergeneratescodewhichensuresthatthevalueofthefieldaccessedisuptodate.Whenafieldisnotvolatile,thecompiler-generatedcodemaystorethevalueofthefieldinaprocessorcacheorregistryforfasteraccesswhenitseesthatthevaluewillbeneededsoonbysomesubsequentcodefragment.Inthecaseofvolatilefields,thisoptimizationcannotbedone.Additionally,notethatsavingthevaluetomemoryandloadingfromthereallthetimemaybe50ormoretimesslowerthanaccessingavaluefromaregistryorcache.
ModifiersMethods,constructors,fields,interfaces,andclassescanhaveaccessmodifiers.Thegeneralruleisthatincasethereisnomodifier,thescopeofthemethod,constructor,andsoon,isthepackage.Anycodeinthesamepackagecanaccessit.
Whentheprivatemodifierisused,thescopeisrestrictedtotheso-calledcompilationunit.Thismeanstheclassthatisinonefile.Whatisinsideonefilecanseeanduseanythingdeclaredtobeprivate.Thisway,innerandnestedclassescanhaveaccesstoeachother'sprivatevariables,whichmaynotreallybeagoodprogrammingstyle,butJavapermitsthat.
Theoppositeofprivateispublic.ItextendsthevisibilitytothewholeJavaprogram,oratleasttothewholemodule,iftheprojectisaJava9module.
Thereisamiddleway:protected.Anythingwiththismodifierisaccessibleinsidethepackageandalsoinclassesthatextendtheclass(regardlessofpackage)thattheprotectedmethod,field,andsoon,isin.
ObjectinitializersandconstructorsWhenanobjectisinstantiated,theappropriateconstructoriscalled.Theconstructordeclarationlookslikeamethodwiththefollowingdeviation:theconstructordoesnothaveareturnvalue.Thatisbecausetheconstructorsworkonthenot-fully-readyinstancewhenthenewcommandoperatorisinvokedanddoesnotreturnanything.Constructors,havingthesamenameastheclass,cannotbedistinguishedfromeachother.Ifthereisaneedformorethanoneconstructor,theyhavetobeoverloaded.Constructors,thus,cancalleachother,almostasiftheywerevoidmethodswithdifferentarguments.However,thereisarestriction—whenaconstructorcallsanother,ithastobetheveryfirstinstructionintheconstructor.Youusethis()syntaxwithanappropriateargumentlist,whichmaybeempty,toinvokeaconstructorfromanotherconstructor.
Theinitializationoftheobjectinstancealsoexecutesinitializerblocks.Theseareblockscontainingexecutablecodeinsidethe{and}charactersoutsidethemethodsandconstructors.Theyareexecutedbeforetheconstructorintheordertheyappearinthecode,togetherwiththeinitializationofthefieldsincasetheirdeclarationscontainvalueinitialization.
Ifyouseethestatickeywordinfrontofaninitializerblock,theblockbelongstotheclassandisexecutedwhentheclassisloadedalongwiththestaticfieldinitializers.
CompilingandrunningtheprogramFinally,wewillcompileandexecuteourprogramfromthecommandline.Thereisnothingnewinthisone;wewillonlyapplywhatwehavelearnedinthischapterusingthefollowingtwocommands:$mvnpackage
Thiscompilestheprogram,packagestheresultintoaJARfile,andfinallyexecutesthefollowingcommand:
$java-cptarget/SortTutorial-1.0.0-SNAPSHOT.jarpackt.java9.by.example.App
Thiswillprintthefollowingresultonthecommandline:
Summary
Inthischapter,wehavedevelopedaverybasicsortalgorithm.ItwasmadepurposefullysimplesothatwecouldreiteratethebasicandmostimportantJavalanguageelements,classes,packages,variables,methods,andsoon.Wealsolookedatbuildtools,sowearenotemptyhandedinthenextchapterswhenprojectswillcontainmorethanjusttwofiles.WewilluseMavenandGradleinthefollowingchapters.
Intheverynextchapter,wewillmakethesortprogrammorecomplex,implementingmoreeffectivealgorithmsandalsomakingourcodeflexible,givingustheopportunitytolearnmoreadvancedJavalanguagefeatures.
OptimizingtheSort-MakingCodeProfessionalInthischapter,wewilldevelopthesortingcodeandmakeitmoregeneral.WewanttosortnotonlyanarrayofStrings.Essentially,wewillwriteaprogramthatcansortanythingthatissortable.Thatway,wewillbringthecodingtoitsfullextenttowardoneofthemajorstrengthsofJava:abstraction.
Abstraction,however,doesnotcomewithoutapricetag.Whenyouhaveaclassthatsortsstringsandyouaccidentallymixanintegerorsomethingelse,whichisnotastring,intothesortabledata,thenthecompilerwillcomplainaboutit:JavadoesnotallowyoutoputanintintoaStringarray.Whenthecodeismoreabstract,suchprogrammingerrorsmayslipin.WewilllookathowtohandlesuchexceptionalcasescatchingandthrowingExceptions.
Toidentifythebugs,wewilluseunittesting,applyingtheindustrystandardJUnitversion4.AsJUnitheavilyusesannotation,andbecauseannotationsareimportant,youwilllearnaboutitabit.
Afterthat,wewillmodifythecodetousethegenericsfeatureofJavathatwasintroducedintothelanguageinversion5.Usingthatpossibility,wewillcatchthecodingerrorduringcompilationtime,whichisbetterthanduringruntime.Theearlierabugisidentified,thecheaperitistofix.
Forthebuild,wewillstilluseMaven,butthistime,wewillsplitthecodeintosmallmodules.Thus,wewillhaveamulti-moduleproject.Wewillhaveseparatemodulesforthedefinitionofasortingmoduleandforthedifferentimplementations.Thatway,wewilllookathowclassescanextendeachotherandimplementinterfaces,and,generally,wewillreallystarttoprogramintheobject-orientedway.
WewillalsodiscussTestDrivenDevelopment(TDD),andattheendofthesection,wewillstartusingthebrandnewfeatureofJava9:modulesupport.
Inthischapter,wewillcoverthefollowingtopics:
Object-orientedprogrammingprinciplesUnittestingpracticesAlgorithmiccomplexityandquicksortExceptionhandlingRecursivemethodsModulesupport
ThegeneralsortingprogramInthepreviouschapter,weimplementedasimplesortalgorithm.ThecodecansortelementsofaStringarray.Wedidthistolearn.Forpracticaluse,thereisareadycookedsortsolutionintheJDKthatcansortmembersofcollections,whicharecomparable.
TheJDKcontainsautilityclasscalledCollections.ThisclasscontainsastaticCollections.sortmethodthatiscapableofsortinganyListthathasmembersthatareComparable.ListandComparableareinterfacesdefinedintheJDK.Thus,ifwewanttosortalistofStrings,thesimplestsolutionisasfollows:
publicclassSimplestStringListSortTest{
@Test
publicvoidcanSortStrings(){
ArrayListactualNames=newArrayList(Arrays.asList(
"Johnson","Wilson",
"Wilkinson","Abraham","Dagobert"
));
Collections.sort(actualNames);
Assert.assertEquals(newArrayList<String>(Arrays.<String>asList(
"Abraham","Dagobert","Johnson","Wilkinson","Wilson")),actualNames);
}
}
ThiscodefragmentisfromasampleJUnittest,whichisthereasonwehavethe@Testannotationinfrontofthemethod.Wewilldiscussthatindetaillater.Toexecutethattest,youcanissuethefollowingcommand:
$mvn-Dtest=SimplestStringListSortTesttest
Thissortimplementation,however,doesnotfitourneeds.Firstofall,becauseitisthereready(noneedtocode)andusingitdoesnotneedanythingnewthatyouhavenotlearnedinthepreviouschapters.Exceptfortheannotationinfrontofthemethod,thereisnothingnewinthecodethatyoucannotunderstand.YoumayrefreshBYturningsomepagesback,orelseconsulttheoracleonlinedocumentationoftheJDK(https://docs.oracle.com/javase/8/docs/api/),butthatisall.Youalreadyknowthesethings.
YoumaywonderwhyIwrotetheURLfortheJavaversion8APItothelink.Well,thenthisisthemomentofhonestyandtruth-whenIwrotethisbook,theJava9JDKwasnotavailableinitsfinalform.IcreatedmostoftheexamplesonmyMacBookusingJava8andIonlytestedthefeaturesthatareJava9specific.SupportatthemomentforJava9intheIDEsisnotperfect.Whenyoureadthisbook,Java9willbeavailable,soyoucantryandchangethatonesingledigitfrom8to9intheURLandgettothedocumentationoftheversion9.Atthemoment,IgetHTTPERROR404.Sometimes,youmayneedthedocumentationofolderversions.Youcanuse3,4,5,6,or7insteadof8intheURL.Documentationfor3and4isnotavailabletoreadonline,butitcanbedownloaded.Hopefully,youwillneverneedthatanymore.Version5,perhaps.Version6isstillwidelyusedatlargecorporations.
Althoughyoucanlearnalotfromreadingcodethatwaswrittenbyotherprogrammers,IdonotrecommendtryingtolearnfromtheJDKsourcecodeatthisearlystageofyourstudies.Theseblocksofcodeareheavilyoptimized,notmeanttobetutorialcodes,andold.Theydonotgetrustedduringtheyears,buttheywerenotrefactoredtofollowthecodingstylesofJavaasitmatured.Atsomeplaces,you
canfindreallyuglycodeintheJDK.
Okay,sayingthatweneedtodevelopanewsortcodebecausewecanlearnfromit,isabitcontrived.TherealreasonwhyweneedasortimplementationisthatwewantsomethingthatcansortnotonlyListdatatypesandaListofsomethingthatimplementstheComparableinterface.Wewanttosortabunchofobjects.Allwerequireisthatthebunchcontainingtheobjectsprovidessimplemethodsthatarejustenoughtosortthemandhaveasortedbunch.
OriginallyIwantedtousethewordcollectioninsteadofbunch,butthereisaCollectioninterfaceinJavaandIwantedtoemphasizethatwearenottalkingaboutjava.util.Collectionofobjects.
WealsodonotwanttheobjectstoimplementtheComparableinterface.IfwerequiretheobjecttoimplementtheComparableinterface,itmayviolatetheSingleResponsibilityPrinciple(SRP).
Whenwedesignaclass,itshouldmodelsomeobjectclassoftherealworld.Wewillmodeltheproblemspacewithclasses.Theclassshouldimplementthefeaturesthatrepresentthebehavioroftheobjectsthatitmodels.Ifwelookattheexampleofstudentsfromthesecondchapter,thenaStudentclassshouldrepresentthefeaturesthatallstudentsshare,andisimportantfromthemodelingpointofview.AStudentobjectshouldbeabletotellthenameofthestudent,theage,theaveragescoresofthelastyear,andsoon.Allstudentshavefeet,andcertainlyeachofthosefeethavesizesowemaythinkthataStudentclassshouldalsoimplementamethodthatreturnsthesizeofthestudent'sfoot(onefortheleftandonefortherightjusttobeprecise),butwedonot.Wedonot,becausethesizeofthefootisirrelevantfromthemodelpointofview.IfwewanttosortalistcontainingStudentobjects,theStudentclasshastoimplementtheComparableinterface.Butwait!Howdoyoucomparetwostudents?Bynames,byage.orbytheaveragescoreofthem?
ComparingastudenttoanotherisnotafeatureoftheStudent.Everyclass,orforthatmatter,package,library,orprogrammingunitshouldhaveoneresponsibilityanditshouldimplementonlythatandnothingelse.Itisnotexact.Thisisnotmathematics.Sometimes,itishardtotellifafeaturefitsintotheresponsibilityornot.Therearesimpletechniques.Forexample,incaseofastudent,youcanasktherealpersonabouthisnameandage,andprobablytheycanalsotellyoutheiraveragescore.IfyouaskoneofthemtocompareTo(anotherstudent),astheComparableinterfacerequiresthismethod,theywillprobablyaskback,butbywhatattribute?Orhow?Orjust,what?Insuchacase,youcansuspectthatimplementingthefeatureisprobablynotintheareaofthatclassandthisconcern;thecomparisonshouldbesegregatedfromtheimplementationoftheoriginalclass.ThisisalsocalledSegregationofConcerns,whichiscloselyrelatedtoSRP.
JDKdeveloperswereawareofthis.Collections.sortthatsortsaListofComparableelementsisnottheonlysortingmethodinthisclass.ThereisanotherthatjustsortsanyListifyoupassasecondargumentandobjectthatimplementstheComparatorinterfaceandiscapableofcomparingtwoelementsofList.Thisisacleanpatterntoseparatetheconcerns.Insomecases,separatingthecomparisonisnotneeded.Inothercases,itisdesirable.TheComparatorinterfacedeclaresonesinglemethodthattheimplementingclasseshavetoprovide:compare.Ifthetwoargumentsareequal,thenthismethodreturns0.Iftheyaredifferent,itshouldreturnanegativeorapositiveintdependingonwhichargumentprecedeswhichone.
TherearealsosortmethodsintheJDKclass,java.util.Arrays.Theysortarrays,oronlyasliceofanarray.Themethodisagoodexampleofmethodoverloading.Therearemethodswiththesamename,butwithdifferentargumentstosortawholearrayforeachprimitivetype,forasliceofeach,andalsotwoforobjectarrayimplementingtheComparableinterface,andalsoforobjectarraytobesortedusingComparator.Asyousee,thereisawholerangeofsortimplementationsavailableintheJDK,andin99percentofthecases,youwillnotneedtoimplementasortyourself.Thesortsusethesamealgorithm,astablemergesortwithsomeoptimization.
Whatwewillimplementisageneralapproachthatcanbeusedtosortlists,arrays,orjustanythingthathaselementsanditispossibletoswapanytwoelementsofit;thesolutionwillbeabletousethebubblesortthatwehavealreadydevelopedandalsootheralgorithms.
AbriefoverviewofvarioussortingalgorithmsTherearemanydifferentsortingalgorithms.AsIsaid,therearesimplerandmorecomplexalgorithmsand,inmanycases,morecomplexalgorithmsaretheonesthatrunfaster.Inthischapter,wewillimplementthebubblesortandquicksort.Wehavealreadyimplementedthebubblesortforstringsinthepreviouschapter,sointhiscase,theimplementationwillmainlyfocusontherecodingforgeneralsortableobjectsorting.Implementingquicksortwillinvolveabitofalgorithmicinterest.
Bewarnedthatthissectionisheretogiveyouonlyatasteofalgorithmiccomplexity.ItisfarfrompreciseandIaminthevainhopethatnomathematicianreadsthisandputsacurseonme.Someoftheexplanationsarevague.Ifyouwanttolearncomputerscienceindepth,thenafterreadingthisbook,findsomeotherbooksorvisitonlinecourses.
Whenwetalkaboutthegeneralsortingproblem,wewillthinkaboutsomegeneralsetofobjectsthatcanbecomparedandanytwoofthemcanbeswappedwhilewesort.Wewillalsoassumethatthisisanin-placesort;thus,wedonotcreateanotherlistorarraytocollecttheoriginalobjectsinsortedorder.Whenwetalkaboutthespeedofanalgorithm,wearetalkingaboutsomeabstractthingandnotmilliseconds.Whenwewanttotalkaboutmilliseconds,actualreal-worldduration,weshouldalreadyhavesomeimplementationinsomeprogramminglanguagerunningonarealcomputer.
Algorithms,intheirabstractform,don'tdothatwithoutimplementation.Still,itisworthtalkingaboutthetimeandmemoryneedofanalgorithm.Whenwedothat,wewillusuallyinvestigatehowthealgorithmbehavesforalargesetofdata.Forasmallsetofdata,mostalgorithmsarejustfast.Sortingtwonumbersisusuallynotanissue,isit?
Incaseofsorting,wewillusuallyexaminehowmanycomparisonsareneededtosortacollectionofnelements.Bubblesortneedsapproximatelyn2(ntimesn)comparisons.Wecannotsaythatthisisexactlyn2becauseincaseofn=2,theresultis1,forn=3itis3,forn=4itis6,andsoon.However,asnstartstogetlarger,theactualnumberofcomparisonsneededandn2willasymptoticallybeofthesamevalue.WesaythatthealgorithmiccomplexityofthebubblesortisO(n2).Thisisalsocalledthebig-Onotation.IfyouhaveanalgorithmthatisO(n2)anditworksjustfinefor1,000elementsfinishinginasecond,thenyoushouldexpectthesamealgorithmfinishingfor1millionelementsinaroundtendaysorinamonth.Ifthealgorithmislinear,sayO(n),thenfinishing1,000elementinonesecondshouldmakeyouexpect1milliontobefinishedin1,000seconds.Thatisabitlongerthanacoffeebreakandtooshortforlunch.
Thismakesitfeasiblethatifwewantsomeseriousbusinesssortingobjects,wewillneedsomethingbetterthanbubblesort.Thatmanyunnecessarycomparisonsarenotonlywastingourtime,butalsoCPUpower,consumingenergy,andpollutingtheenvironment.Thequestion,however,is:howfastcanasortbe?Isthereaprovableminimumthatwecannotovercome?
Theanswerisyes.
Whenweimplementanysortingalgorithm,theimplementationwillexecutecomparisonsandelementswaps.Thatistheonlywaytosortacollectionofobjects.Theoutcomeofacomparisoncanhavetwovalues.Say,thesevaluesare0or1.Thisisonebitofinformation.Iftheresultofthecomparisonis1,
thenweswap,iftheresultis0,thenwedonotswap.
Wecanhavetheobjectsindifferentordersbeforewestartthecomparisonandthenumberofdifferentordersisn!(nfactorial).Thatis,thenumbersmultipliedfrom1ton,inotherwordsn!=1*2*3*...*(n-1)*n.
Let'sassumethatwestoredtheresultoftheindividualcomparisonsinanumberasaseriesofbitsforeachpossibleinputforthesort.Now,ifwereversetheexecutionofthesortandrunthealgorithmstartingfromthesortedcollection,controltheswappingusingthebitsthatdescribedtheresultsofthecomparison,andweusethebitstheotherwayarounddoingthelastswapfirstandtheonethatwasdonefirstduringthesortingfirst,weshouldgetbacktheoriginalorderoftheobjects.Thisway,eachoriginalorderisuniquelytiedtoanumberexpressedasanarrayofbits.
Now,wecanexpresstheoriginalquestionthisway:howmanybitsareneededtodescribenfactorialdifferentnumbers?Thatisexactlythenumberofcomparisonswewillneedtosortnelements.Thenumberofbitsislog2(n!).Usingsomemathematics,wewillknowthatlog2(n!)isthesameaslog2(1)+log2(2)+...+log2(n).Ifwelookatthisexpression'sasymptoticvalue,thenwecansaythatthisisthesameO(n*logn).Weshouldnotexpectanygeneralsortingalgorithmtobefaster.
Forspecialcases,therearefasteralgorithms.Forexample,ifwewanttosort1millionnumbersthatareeachbetweenoneand10,thenweonlyneedtocountthenumberofthedifferentnumbersandthencreateacollectionthatcontainsthatmanyones,twos,andsoon.ThisisanO(n)algorithm,butthisisnotapplicableforthegeneralcase.
Again,thiswasnotaformalmathematicalproof.
QuicksortSirCharlesAntonyRichardHoaredevelopedthequicksortalgorithmin1959.Itisatypicaldivideandconqueralgorithm.Tosortalongarray,pickanelementfromthearraythatwillbethepivotelement.Then,partitionthearraysothattheleftsidewillcontainalltheelementsthataresmallerthanthepivotandtherightsidewillcontainalltheelementsthatarelargerthan,orequaltothepivot.Whenthisisdone,theleftsideandtherightsideofthearraycanbesortedbycallingthesortrecursively.Tostoptherecursion,whenwehaveonesingleelementinthearray,wewilldeclareitsorted.
Wetalkaboutarecursivealgorithmwhenthealgorithmisdefinedpartiallyusingitself.ThemostfamousrecursivedefinitionistheFibonacciseriesthatis0and1forthefirsttwoelementsandanylaterelementthenthelementisthesumofthe(n-1)thandthe(n-2)thelement.Recursivealgorithmsaremanytimesimplementedinmodernprogramminglanguagesimplementingamethodthatdoessomecalculationbutsometimescallsitself.Whendesigningrecursivealgorithms,itisofutmostimportancetohavesomethingthatstopstherecursivecalls;otherwise,recursiveimplementationwillallocateallmemoryavailablefortheprogramstackandstoptheprogramwitherror.
Thepartitioningalgorithmgoesthefollowingway:wewillstarttoreadthearrayusingtwoindicesfromthestartandend.Wewillfirststartwiththeindexthatissmallandincreasetheindexuntilitissmallerthanthelargeindex,oruntilwefindanelementthatisgreaterthanorequaltothepivot.Afterthis,wewillstarttodecreasethelargerindexsolongasitisgreaterthanthesmallindexandtheelementindexedisgreaterthanorequaltothepivot.Whenwestop,weswapthetwoelementspointedbythetwoindices,iftheindicesarenotthesame,andwewillstartincreasinganddecreasingthesmallandlargeindices,respectively.Iftheindicesarethesame,thenwearefinishedwiththepartitioning.Theleftsideofthearrayisfromthestarttotheindexwheretheindicesmetminusone;therightsidestartswiththeindexandlastsuntiltheendoftheto-be-sortedarray.
ThisalgorithmisusuallyO(nlogn),butinsomecasesitcandegradetobeO(n2),dependingonhowthepivotischosen.Therearedifferentapproachesfortheselectionofthepivot.Inthisbook,wewillusethesimplest:wewillselectthefirstelementofthesortablecollectionasapivot.
<project><br/>...<br/><modules><br/><module>SortInterface</module><br/><module>bubble</module><br/><module>quick</module><br/></modules><br/></project>
$tree<br/>|-SortInterface<br/>|---src/main/java/packt/java9/by/example/ch03<br/>|-bubble<br/>|---src<br/>|-----main/java/packt/java9/by/example/ch03/bubble<br/>|-----test/java/packt/java9/by/example/ch03/bubble<br/>|-quick/src/<br/>|-----main/java<br/>|-----test/java
MavendependencymanagementDependenciesarealsoimportantinthePOMfile.Thepreviousprojectdidnothaveanydependency,butthistimewewilluseJUnit.Dependenciesaredefinedinpom.xmlusingthedependenciestag.Forexample,thebubblesortmodulecontainsthefollowingpieceofcode:
<dependencies>
<dependency>
<groupId>packt.java9.by.example</groupId>
<artifactId>SortInterface</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
Theactualpom.xmlinthecodesetyoucandownloadwillcontainmorecodethanthis.Inprint,weoftenpresentaversionoronlyafractionthathelpstheunderstandingofthetopicthatwearediscussingatthatpoint.
IttellsMaventhatthemodulecodeusesclasses,interfaces,andenumtypesthataredefinedinthesemodulesthatareavailablefromsomerepository.
WhenyouuseMaventocompilethecode,thelibrariesthatareusedbyyourcodeareavailablefromrepositories.WhenAntwasdeveloped,thenotionofrepositorieswasnotinvented.Atthattime,thedeveloperscopiedtheusedversionofthelibraryintoafolderinthesourcecodestructure.Usually,thedirectorylibwasusedforthepurpose.Thereweretwoproblemswiththisapproach.Oneisthesizeofthesourcecoderepository.If,forexample,100differentprojectsusedJUnit,thentheJARfileoftheJUnitlibrarywascopiedthere100times.Theotherproblemwastogatherallthelibraries.Whenalibraryusedanotherlibrary,thedevelopershadtoreadthedocumentationofthelibrarythatdescribed(manytimesoutdatedandnotprecise)whatotherlibrariesareneededtousethislibrary.Thoselibrarieshadtobedownloadedandinstalledthesameway.Thiswastimeconsuminganderrorprone.Whenalibrarywasmissingandthedevelopersjustdidnotnoticeit,theerrorwasmanifestedduringcompiletimewhenthecompilercouldnotfindtheclassorevenonlyatruntimewhentheJVMwasnotabletoloadtheclass.
Tosolvethisissue,Mavencomeswithabuilt-inrepositorymanagerclient.Therepositoryisastoragethatcontainsthelibraries.Astherecanbeothertypesoffilesinarepository,notonlylibraries,Maventerminologyisartifact.ThegroupId,theartifactId,andtheversionnumberidentifyanartifact.Thereisaverystrictrequirementthatanartifactcanonlybeputintoarepositoryonce.Evenifthereisanerrorduringthereleaseprocessthatisidentifiedaftertheerroneousreleasewasuploaded,theartifactcannotbeoverwritten.ForthesamegroupId,artifactId,andversion,therecanonlybeonesinglefilethatwillneverchange.Iftherewasanerror,thenanewartifactistobecreatedwithnewversionnumberandtheerroneousartifactmaybedeletedbutnotreplaced.
Iftheversionnumberendswith-SNAPSHOT,thenthisuniquenessisnotguaranteedorrequired.Snapshotsareusuallystoredinseparaterepositoryandarenotpublishedfortheworld.
Repositoriescontaintheartifactsindirectoriesthatareorganizedinadefinedway.WhenMavenruns,itcanaccessdifferentrepositoriesusinghttpsprotocol.
Formerly,thehttpprotocolwasalsoused,andfornon-payingcustomers,thecentralrepositorywasavailableviahttponly.However,itwasdiscoveredthatmodulesdownloadedfromtherepositorycouldbetargetsformen-in-the-middlesecurityattacksandSonatype(http://www.sonatype.com)changedthepolicyandusedhttpsprotocolonly.Neverconfigureorusearepositorywiththehttpprotocol.NevertrustafilethatyoudownloadedfromHTTP.
Thereisalocalrepositoryonthedevelopermachine,usuallyunderthe~/.m2/repositorydirectory.Whenyouissuethemvninstallcommand,Mavenstoresthecreatedartifacthere.MavenalsostoresanartifactherewhenitisdownloadedfromarepositoryviaHTTPS.Thisway,subsequentcompilationsdonotneedtogoouttothenetworkfortheartifacts.
Companiesusuallysetuptheirownrepositorymanager(theonethatSonatype,thecompanybackingMaven,isprovidingNexus).Theseapplicationscanbeconfiguredtocommunicatewithseveralotherrepositoriesandcollecttheartifactsfromthereondemand,essentiallyimplementingproxyfunctionality.Artifactstraveltothebuildfromthefarendrepositoriestothecloseronesinahierarchicalstructuretothelocalrepoandessentiallytothefinalartifactifthepackagingtypeoftheprojectiswar,ear,orsomeotherformatthatenclosesthedependentartifacts.Thisisessentiallyfilecachingwithoutrevalidationandcacheeviction.Thiscanbedonebecauseofthestrictrulesofartifactuniqueness.Thisisthereasonforsuchastrictrule.
Iftheprojectbubblewereastandaloneproject,andnotpartofamulti-moduleone,thenthedependencywouldlooklikethis:
<dependencies>
<dependency>
<groupId>packt.java9.by.example</groupId>
<artifactId>SortInterface</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
Ifversionisnotdefinedforadependency,Mavenwillnotbeabletoidentifywhichartifacttouse.Inthecaseofamulti-moduleproject,versioncanbedefinedintheparentandthemodulescaninherittheversion.Astheparentisnotdependentontheactualdependency,itonlydefinestheversionattachedtothegroupIdandartifactId;theXMLtagisnotdependencies,butdependencyManagement/dependenciesunderthetop-levelprojecttagasinthefollowingexample:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>packt.java9.by.example</groupId>
<artifactId>SortInterface</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
IftheparentPOMusesthedependenciestagdirectly,Mavenisnotabletodecideiftheparentdependsonthatartifactorsomemodules.Whenthemoduleswanttousejunit,theyneednotspecifytheversion.Theywillgetitfromtheparentprojectdefinedas4.12,whichisthelatestfromJUnit4.Ifevertherewillbeanewversion4.12.1,withsomeseriousbugsfixed,thentheonlyplacetomodifytheversionnumberistheparentPOM,andthemoduleswillusethenewversionstartingwiththenextexecutionoftheMavencompilation.
Whenthenewversion,JUnit5,comesout,however,themoduleswillallhavetobemodifiedbecauseJUnitisnotjustanewversion.Version5ofJUnitissplitintoseveralmodulesand,thisway,groupIdandartifactIdwillalsochange.
ItisalsoworthnotingthatthemodulesthatimplementtheinterfacesfromtheSortInterfacemoduleareeventuallydependentonthismodule.Inthiscase,theversionisdefinedasfollows:
<version>${project.version}</version>
Thatseemstobeabittautological(itis,actually).The${project.version}propertyistheversionoftheprojectanditisinheritedbytheSortInterfacemodule.Thisistheversionoftheartifactthattheothermodulesdependon.Inotherwords,themodulesalwaysdependontheversionthatwearecurrentlydeveloping.
Codethesort
Toimplementthesort,first,wewilldefinetheinterfacesthatasortlibraryshouldimplement.Definingtheinterfacebeforetheactualcodingisagoodpractice.Whentherearemanyimplementations,itissometimesrecommendedtofirstcreateasimpleoneandstartusingitsothattheinterfacemayevolveduringthephase,andwhenthemorecompleximplementationsaredue,thentheinterfacetobeimplementedisalreadyfixed,moreorless.
packagepackt.java9.by.example.ch03;<br/><br/>publicinterfaceSort{<br/>voidsort(SortableCollectioncollection);<br/>}
packagepackt.java9.by.example.ch03;<br/><br/>publicinterfaceSortableCollection{<br/>}
CreatingBubbleSortNow,wecanstartcreatingthebubblesortthatimplementstheSortinterface:
packagepackt.java9.by.example.ch03.bubble;
importpackt.java9.by.example.ch03.*;
importjava.util.Comparator;
publicclassBubbleSortimplementsSort{
@Override
publicvoidsort(SortableCollectioncollection){
intn=collection.size();
while(n>1){
for(intj=0;j<n-1;j++){
if(comparator.compare(collection.get(j),
collection.get(j+1))>0){
swapper.swap(j,j+1);
}
}
n--;
}
}
Normally,thealgorithmtoexecuteneedstwooperationsthatweimplementedinthecodelasttimespecifictoaStringarray:comparingtwoelementsandswappingtwoelements.Asthistimethesortimplementationitselfdoesnotknowwhattypetheelementsareusedandalsodoesnotknowifthesomethingitsortsisanarray,alistsorsomethingelse,itneedssomethingthatdoesitforthesortwhenneeded.Moreprecisely,itneedsacomparatorobjectcapableofcomparingtwoelementsanditneedsaswapperobjectthatiscapableofswappingtwoelementsinthecollection.
Togetthose,wecanimplementtwosettermethodsthatcansettheobjectsforthepurposebeforesortisinvoked.Asthisisnotspecifictothebubblesortalgorithmbutisrathergeneral,thesetwomethodsshouldalsobemadeapartoftheinterface,sotheimplementationisoverridingit.
privateComparatorcomparator=null;
@Override
publicvoidsetComparator(Comparatorcomparator){
this.comparator=comparator;
}
privateSwapperswapper=null;
@Override
publicvoidsetSwapper(Swapperswapper){
this.swapper=swapper;
}
}
The@OverrideannotationsignalsfortheJavacompilerthatthemethodisoverridingamethodoftheparentclass,or,asinthiscase,oftheinterface.Amethodcanoverrideaparentmethodwithoutthisannotation;however,ifweusetheannotation,thecompilationfailsifthemethoddoesactuallynotoverridesomething.Thishelpsyoudiscoverduringcompiletimethatsomethingwaschangedintheparentclassorintheinterfaceandwedidnotfollowthatchangeintheimplementation,orthatwejustmadesomemistakethinkingthatwewilloverrideamethodwhenweactuallydonot.Asannotationsareheavilyusedinunittests,wewilltalkaboutannotationsinabitmoredetaillater.
AmendingtheinterfacesThemodifiedSortinterfacewilllooklikethis:
publicinterfaceSort{
voidsort(SortableCollectioncollection);
voidsetSwapper(Swapperswap);
voidsetComparator(Comparatorcompare);
}
Thisalsomeansthatwewillneedtwonewinterfaces:SwapperandComparator.WeareluckythattheJavaruntimealreadydefinesaComparatorinterfacethatjustfitsthepurpose.Youmayhaveguessedthatfromthefollowingimportstatement:
importjava.util.Comparator;
Whenyouneedsomethingverybasic,likeacomparatorinterface,itismostprobablydefinedintheruntime.Itisadvisabletoconsulttheruntimebeforewritingyourownversion.TheSwapperinterface,however,wewillhavetocreate.
packagepackt.java9.by.example.ch03;
publicinterfaceSwapper{
voidswap(inti,intj);
}
AsitisusedtoswaptwoelementsspecifiedbytheindicesinSortableCollection,thereisamethod,quitetriviallynamedswapforthepurpose.But,wearenotreadyyet.Ifyoutrytocompiletheprecedingcode,thecompilerwillcomplainaboutthegetandsizemethods.Theyareneededbythealgorithmtoimplementthesort,buttheyarenotinherentlypartofthesortingitself.Thisisaresponsibilitythatshouldnotbeimplementedinthesort.Aswedonotknowwhattypeofcollectionswewillsort,itisnotonlyunadvisablebutalsoimpossibletoimplementthesefunctionalitiesinsidethesort.Itseemsthatwejustcannotsortanything.Therearesomerestrictionswewillhavetoset.Thesortingalgorithmmustknowthesizeofthecollectionwesortandalsoshouldhaveaccesstoanelementbyindexsothatitcanpassitontothecomparator.
TheserestrictionsareexpressedintheSortableCollectioninterfacethatwejustleftemptynotknowingbeforethefirstsortimplementationwhatisrequiredtobethere.
packagepackt.java9.by.example.ch03;
publicinterfaceSortableCollection{
Objectget(inti);
intsize();
}
Now,wearereadywiththeinterfacesandtheimplementationandwecangoontestingthecode.But,beforethat,wewillbrieflyreiteratewhatwedidandwhywedidthat.
ArchitecturalconsiderationsWecreatedaninterfaceandasimpleimplementationofit.Duringtheimplementation,wediscoveredthattheinterfaceneedsotherinterfacesandmethodsthatareneededtosupportthealgorithm.Thisusuallyhappensduringthearchitecturaldesignofthecode,beforeimplementation.Fordidacticalreasons,Ifollowedthebuild-upoftheinterfaceswhilewedevelopedthecode.Inreallife,whenIcreatedtheinterfaces,IcreatedthemallinonestepasIhaveenoughexperience.Iwrotemyfirstquicksortcodearound1983inFortran.However,itdoesnotmeanthatIhitthebull'seyewithjustanyproblemandcomeoutwiththefinalsolution.Itjusthappensthatsortisatoowellknownproblem.Ifyouneedtomodifytheinterfacesorotheraspectsofyourdesignduringdevelopment,donotfeelembarrassed.Itisanaturalconsequenceandaproofthatyouunderstandthingsbetterandbetterastimegoesby.Ifthearchitectureneedschange,itisbettertobedonethannot,andthesooneritis,thebetter.Inreallifeenterpriseenvironments,wewilldesigninterfacesjusttolearnduringdevelopmentthatthereweresomeaspectsthatweforgot.Theyareverytrueandbitmorecomplexoperationsthansortingacollection.
Inthecaseofthesortingproblem,weabstractedthesomethingwewanttosorttothemostpossibleextreme.TheJavabuildinsortcansortarraysorlists.Ifyouwanttosortsomethingthatisnotalistoranarray,youhavetocreateaclassthatimplementsthejava.util.Listinterfacewithmorethan24methodsitrequirestowrapyoursortableobjecttomakeitsortablebytheJDKsort.Tobehonest,thatisnottoomany,andinareal-worldproject,Iwouldconsiderthatasanoption.
However,wedonot,andcannotknow,whatmethodsoftheinterfacethebuilt-insortuses.Thosethatareusedshouldbefunctionallyimplementedandthosethatarenot,cancontainasimplereturnstatementbecausetheyarejustneverinvoked.AdevelopercanconsultthesourcecodeoftheJDKandseewhatmethodsareactuallyused,butthatisnotthecontractofthesearchimplementation.Itisnotguaranteedthatanewversionwillstilluseonlythosemethods.Ifanewversionstartstouseamethodthatweimplementedwithasinglereturnstatement,thesortwillmagicallyfail.
ItisalsoaninterestingperformancequestionhowtheswappingoftwoelementsisimplementedbythesearchusingonlytheListinterface.Thereisnoput(int,Object)methodintheListinterface.Thereisadd(intObject),butthatinsertsanewelementanditmaybeextremelycostly(burningCPU,disk,energy)topushallelementsofthelistupiftheobjectsarestored,forexample,ondisk.Furthermore,thenextstepmayberemovingtheelementaftertheonewejustinserted,doingthecostlyprocessofmovingthetailofthelistagain.Thatis,thetrivialimplementationofput(int,Object)thatthesortmayormaynotfollow.Again,thisissomethingthatshouldnotbeassumed.
Whendevelopersuselibraries,classes,andmethodsfromtheJDK,opensource,orcommerciallibraries,thedevelopersmayconsultthesourcecodebuttheyshouldnotrelyontheimplementation.YoushouldrelyonlyonthecontractandthedefinitionoftheAPIthatthelibrarycomeswith.Whenyouimplementaninterfacefromsomeexternallibrary,andyoudonotneedtoimplementsomepartofit,andcreatesomedummymethods,feelthedangerintheair.Itisanambush.Itislikelythateitherthelibraryispoorqualityoryoudidnotunderstandhowtouseit.
Inourcase,weseparatedtheswappingandthecomparisonfromthesort.Thecollectionshouldimplementtheseoperationsandprovidethemforthesort.Thecontractistheinterface,andtousethesort,
youhavetoimplementallmethodsoftheinterfaceswedefined.
TheinterfaceofSortdefinessettersthatsetSwapperandComparator.HavingdependenciessetthatwaymayleadtoacodethatcreatesanewinstanceofaclassimplementingtheSortinterface,butdoesnotsetSwapperandComparatorbeforeinvokingSort.ThiswillleadtoNullPointerExceptionthefirsttimetheComparatorisinvoked(orwhentheSwapperisinvokedincasetheimplementationinvokesthatfirst,whichisnotlikely,butpossible).Thecallingmethodshouldinjectthedependenciesbeforeusingtheclass.Whenitisdonethroughsetters,itiscalledsetterinjection.ThisterminologyisheavilyusedwhenweuseframeworkssuchasSpring,Guice,orsomeothercontainer.Creatingtheseserviceclassesandinjectingtheinstanceintoourclassesisfairlysimilarallthetime.
Containerimplementationscontainthefunctionalityinageneralwayandprovideconfigurationoptionstoconfigurewhatinstancesaretobeinjectedintowhatotherobjects.Usually,thisleadstoshorter,moreflexible,andmorereadablecode.However,dependencyinjectionisnotexclusivetocontainers.Whenwewritethetestingcodeinthenextsection,andinvokethesetters,weactuallydodependencyinjection.
Thereisanotherwayofdependencyinjectionthatavoidstheproblemofdependenciesnotbeingset.Thisiscalledconstructorinjection.Thedependenciesarefinalprivatefieldswithnovalues.Rememberthatthesefieldsshouldgettheirfinalvaluesbythetimetheconstructorfinishes.Constructorinjectionpassestheinjectedvaluestotheconstructorasargumentsandtheconstructorsetsthefields.Thisway,thefieldsareguaranteedtobesetbythetimetheobjectwasconstructed.Thisinjection,however,cannotbedefinedinaninterface.
Now,wealreadyhavethecode,andweknowtheconsiderationsofhowtheinterfaceswerecreated.Thisisthetimetodosometesting.
CreatingunittestsWhenwewritecode,weshouldtestit.Nocodehasevergoneintoproductionbeforeatleastdoingsometestruns.Therearedifferentlevelsoftestshavingdifferentaims,technologies,industrypractices,andnames.
Unittests,asthenamesuggests,testaunitofcode.Integrationteststesthowtheunitsintegratetogether.Smoketeststestalimitedsetofthefeaturesjusttoseethatthecodeisnottotallybroken.Thereareothertests,untilthefinaltest,whichistheproofofthework:useracceptancetest.Proofofthepuddingiseatingit.Acodeisgoodiftheuseracceptsit.
Manytimes,Itelljuniorsthatthenameuseracceptancetestisabitmisleading,becauseitisnottheuserwhoacceptstheresultofaproject,butthecustomer.Bydefinition,thecustomeristhepersonwhopaysthebill.Professionaldevelopmentispaid;otherwise,itisnotprofessional.Theterminologyis,however,useracceptancetest.Itjusthappensthatcustomersaccepttheprojectonlyiftheuserscanusetheprogram.
WhenwedevelopinJava,unittestistestingstandaloneclasses.Inotherwords,inJavadevelopment,aunitisaclasswhenwetalkaboutunittests.Tofurnishunittests,weusuallyusetheJUnitlibrary.Thereareotherlibraries,suchasTestNG,butJUnitisthemostwidelyused,sowewilluseJUnit.Touseitasalibrary,first,wewillhavetoaddittotheMavenPOMasadependency.
AddingJUnitasdependencyRecallthatwehaveamulti-moduleproject,andthedependencyversionsaremaintainedintheparentPOMunderthedependencyManagementtag.
<dependencyManagement>
<dependencies>
...
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
Thescopeofthedependencyistest,whichmeansthatthislibraryisneededonlytocompilethetestcodeandduringtheexecutionofthetest.TheJUnitlibrarywillnotmakeitswaytothefinalreleasedproduct;thereisnoneedforit.IfyoufindtheJUnitlibraryinsomedeployedproductionWebArchive(WAR)file,suspectthatsomebodywasnotproperlymanagingthescopesofthelibraries.
MavensupportsthecompilationandtheexecutionofJUnittestsinthelifecycleoftheproject.Ifwewanttoexecutethetests,onlywecanissuethemvntestcommand.TheIDEsalsosupporttheexecutionoftheunittests.Usually,thesamemenuitemthatcanbeusedtoexecuteaclassthathasapublicstaticmainmethodcanbeused.IftheclassisaunittestutilizingJUnit,theIDEwillrecognizeitandexecutethetestsandusuallygiveagraphicalfeedbackonwhattestwasexecutingfineandwhichonesfailed,andhow.
WritingtheBubbleSortTestclassThetestclassesareseparatedfromtheproductionclasses.Theygointothesrc/test/javadirectory.Whenwehaveaclassnamed,forexample,BubbleSort,thenthetestwillbenamedBubbleSortTest.Thisconventionhelpstheexecutingenvironmenttoseparatethetestsfromthoseclassesthatdonotcontaintestsbutareneededtoexecutethetests.Totestthesortimplementationwehavejustcreated,wecanfurnishaclassthatcontains,fornow,asinglecanSortStringsmethod.
Unittestmethodnamesareusedtodocumentthefunctionalitybeingtested.AstheJUnitframeworkinvokeseachandeverymethodthathasthe@Testannotation,thenameofthetestisnotreferencedanywhereinourcode.Wecanbravelyusearbitrarylongmethodnames;itwillnothinderreadabilityattheplacewherethemethodisinvoked.
packagepackt.java9.by.example.ch03.bubble;
//importsdeletedfromprint
publicclassBubbleSortTest{
@Test
publicvoidcanSortStrings(){
ArrayListactualNames=newArrayList(Arrays.asList(
"Johnson","Wilson",
"Wilkinson","Abraham","Dagobert"
));
ThemethodcontainsArrayListwiththeactualnamesthatwehavealreadygottenfamiliarwith.AswehaveasortimplementationandinterfacethatneedsSortableCollection,wewillcreateonebackedupbyArrayList.
SortableCollectionnamesCollection=newSortableCollection(){
@Override
publicObjectget(inti){
returnactualNames.get(i);
}
@Override
publicintsize(){
returnactualNames.size();
}
};
WedeclaredanewobjectthathastheSortableCollectiontype,whichisaninterface.ToinstantiatesomethingthatimplementsSortableCollection,wewillneedaclass.Wecannotinstantiateaninterface.Inthiscase,definetheclassintheplaceoftheinstantiation.ThisiscalledananonymousclassinJava.Thenamecomesfromthefactthatthenameofthenewclassisnotdefinedinthesourcecode.TheJavacompilerwillautomaticallycreateanameforthenewclass,butthatisnotinterestingfortheprogrammers.WewillsimplywritenewSortableCollection()andprovidetheneededimplementationimmediatelyfollowingbetween{and}.Itisveryconvenienttodefinethisanonymousclassinsidethemethodas,thisway,itcanaccessArrayListwithoutpassingareferencetoArrayListintheclass.
Asamatteroffact,thereferenceisneeded,buttheJavacompilerautomaticallydoesthis.TheJavacompiler,inthiscase,alsotakescarethatautomaticreferencepassingthiswaycanonlybedoneusingvariablesthatwereinitializedandwillnotchangeduringtheexecutionofthecodeaftertheinstantiationoftheanonymousclass.ThevariableactualNameswassetanditshouldnotbechangedinthemethodlater.
Asamatteroffact,wecanevendefineactualNamestobefinalandthiswouldhavebeenarequirementifweusedJava1.7orearlier.Startingwith1.8,therequirementisthatthevariableiseffectivelyfinal,butyouneednotdeclareittobefinal.
ThenextthingthatweneedisaSwapperimplementationforArrayList.Inthiscase,wewilldefineawholeclassinsidethemethod.Itcanalsobeananonymousclass,butthistimeIdecidedtouseanamedclasstodemonstratethataclasscanbedefinedinsideamethod.Usually,wedonotdothatinproductionprojects.
classSwapActualNamesArrayElementsimplementsSwapper{
@Override
publicvoidswap(inti,intj){
finalObjecttmp=actualNames.get(i);
actualNames.set(i,actualNames.get(j));
actualNames.set(j,tmp);
}
}
Last,butnotleast,wewillneedacomparatorbeforewecaninvokethesort.AswehaveStringstocompare,thisiseasyandstraightforward.
ComparatorstringCompare=newComparator(){
@Override
publicintcompare(Objectfirst,Objectsecond){
finalStringf=(String)first;
finalStrings=(String)second;
returnf.compareTo(s);
}
};
Havingeverythingpreparedforthesorting,wewillfinallyneedaninstanceoftheSortimplementation,setthecomparatorandtheswapper,andinvokethesort.
Sortsort=newBubbleSort();
sort.setComparator(stringCompare);
sort.setSwapper(newSwapActualNamesArrayElements());
sort.sort(namesCollection);
Thelast,butmostimportantpartofthetestistoassertthattheresultistheonethatweexpect.JUnithelpsusdothatwiththeaidoftheAssertclass.
Assert.assertEquals(Arrays.asList("Abraham","Dagobert","Johnson","Wilkinson","Wilson"),actualNames);
}
}
ThecalltoassertEqualschecksthatthefirstargument,theexpectedresult,equalsthesecondargument,thesortedactualNames.Iftheydiffer,thenAssertionErroristhrown;otherwise,thetestjustfinishesfine.
GoodunittestsIsthisagoodunittest?Ifyoureaditinatutorialbooklikethis,ithastobe.Actually,itisnot.ItisagoodcodetodemonstratesomeofthetoolsthatJUnitprovidesandsomeJavalanguagefeatures,butasarealJUnittest,Iwillnotuseitinareallifeproject.
Whatmakesaunittestgood?Toanswerthisquestion,wewillhavetofindwhattheunittestisgoodforandwhatitisthatweuseitfor.
Wewillcreateunitteststovalidatetheoperationoftheunitsandtodocument.
Unittestsarenottofindbugs.Developerseventuallyuseunittestsduringdebuggingsessionsbut,manytimes,thetestingcodecreatedforthedebuggingisatemporaryone.Whenthebugisfixed,thecodeusedtofinditwillnotgetintothesourcecode.Foreverynewbug,thereshouldbeanewtestcreatedthatcoversthefunctionalitythatwasnotproperlyworking,butitishardlythetestcodethatisusedtofindthebug.Thisisbecauseunittestsaremainlyfordocumentation.YoucandocumentaclassusingJavaDoc,buttheexperienceshowsthatthedocumentationoftenbecomesoutdated.Thedevelopersmodifythecode,buttheydonotmodifythedocumentation,andthedocumentationbecomesobsoleteandmisleading.Unittests,however,areexecutedbythebuildsystemandifContinuousIntegration(CI)isinuse(anditshouldbe,inaprofessionalenvironment),thenthebuildwillbebrokenifatestfails,alldeveloperswillgetmailnotificationaboutit,anditwilldrivethedeveloperbreakingthebuildtofixthecodeorthetest.Thisway,thetestsverifythatcontinuousdevelopmentdidnotbreakanythinginthecodeor,atleast,notsomethingthatcanbediscoveredusingunittests.
AgoodunittestisreadableOurtestisfarfrombeingreadable.Atestcaseisreadableifyoulookatitandin15secondsyoucantellwhatitdoes.Itassumes,ofcourse,someexperienceinJavaonbehalfofthereader,butyougetthepoint.Ourtestisclutteredwithsupportclassesthatarenotcoretothetest.
Ourtestalsohardlyvalidatesthatthecodeisworkingproperly.Itactuallydoesnot.TherearesomebugsinitthatIputtheredeliberately,whichwewilllocateandzapinthefollowingsections.OnesingletestthatsortsasingleStringarrayisfarfromvalidatingasortimplementation.IfIweretoextendthistesttoareal-worldtest,wewouldneedmethodsthatwouldhavethenamecanSortEmptyCollection,canSortOneElementCollection,canSortTwoElements,canSortReverseOrder,orcanSortAlreadySorted.Ifyoulookatthenames,youwillseewhattestsweneed.Comingfromthenatureofthesortproblem,animplementationmaybereasonablysensitivetoerrorsinthesespecialcases.
Whatarethegoodpointsinourunittest,inadditiontoitbeinganacceptabledemonstrationtool?
Unittestsarefast
Ourunittestrunsfast.Asweexecuteunittestseachtime,theCIfiresupabuildandtheexecutionofthetestsshouldnotlastlong.Youshouldnotcreateaunittestsortingbillionsofelements.Thatisakindofstabilityorloadtestandtheyshouldruninseparatetestperiodsandnoteverytimethebuildisrunning.Ourunittestsortsfiveelementsthatarereasonable.
Unittestsaredeterministic
Ourunittestisdeterministic.Non-deterministicunittestsarethenightmareofthedevelopers.IfyouareinagroupwheresomebuildsbreakontheCIserver,andwhenabuildbreaks,yourfellowdevelopersaysthatyoujusthavetotryitagain;noway!Ifaunittestruns,itshouldrunalltimes.Ifitfails,itshouldfailnomatterhowmanytimesyoustartit.Anon-deterministicunittest,inourcase,willbetorenderrandomnumbersandhavethemsorted.Wewillendupwithdifferentarraysineachtestrunand,incasethereissomebuginthecodethatmanifestsforsomearray,wewillnotbeabletoreproduceit.Nottomentionthattheassertionthatthecodewasrunningfineisalsodifficult.
Ifwesortedarandomarrayinaunittest(somethingwedonot),wecould,hypothetically,assertthatthearrayissorted,comparingtheelementsoneaftertheothercheckingthattheyareinascendingorder.Itwouldalsobeatotallywrongpractice.
Assertionsshouldbeassimpleaspossible
Iftheassertioniscomplex,theriskofintroducingbugsintheassertionishigher.Themorecomplextheassertion,thehighertherisk.Wewillwritetheunitteststoeaseourlivesandnottohavemorecodetodebug.
Additionally,onetestshouldassertonlyonething.ThisoneassertionmaybecodedwithmultipleAssertclassmethods,oneaftertheother.Still,theaimoftheseistoassertthecorrectnessofonesinglefeatureoftheunit.RemembertheSRP:onetest,onefeature.Agoodtestislikeagoodsniper:oneshot,onekill.
UnittestsareisolatedWhenwetestaunitA,anychangeinanotherunitB,orabuginadifferentunitshouldnotaffectourunittestthatisfortheunitA.Inourcase,itwaseasybecausewehaveonlyoneunit.Later,whenwedevelopthetestforthequicksort,wewillseethatthisseparationisnotthatsimple.
Iftheunittestsareproperlyseparated,afailingunittestclearlypointsoutthelocationoftheproblem.Itisintheunitwheretheunittestfailed.Iftestsdonotseparatetheunits,thenafailureinonetestmaybecausedbyabuginadifferentunitthanweexpect.Inthiscase,thesetestsarenotreallyunittests.
Inpractice,youshouldmakeabalance.Iftheisolationoftheunitswillbetoocostly,youcandecidetocreateintegrationtests;and,iftheystillrunfast,havethemexecutedbytheCIsystem.Atthesametime,youshouldalsotrytofindoutwhytheisolationishard.Ifyoucannoteasilyisolatetheunitsinthetests,itmeansthattheunitsaretoostronglycoupled,whichmaynotbeagooddesign.
UnittestscoverthecodeUnittestsshouldtestallusualandalsoallspecialcasesofthefunctionality.Ifthereisaspecialcaseofcodethatisnotcoveredbytheunittest,thecodeisindanger.Incaseofasortimplementation,thegeneralcaseissorting,sayfiveelements.Thespecialcasesaremuchmorenumeroususually.Howdoesourcodebehaveifthereisonlyoneelementoriftherearenoelements?Whatiftherearetwo?Whatiftheelementsareinreverseorder?Whatiftheyarealreadysorted?
Usually,thespecialcasesarenotdefinedinthespecification.Theprogrammerhastothinkaboutitbeforecoding,andsomespecialcasesarediscoveredduringcoding.Thehardthingisthatyoujustcannottellifyoucoveredallspecialcasesandthefunctionalityofthecode.
Whatyoucantellisifallthelinesofcodewereexecutedduringthetestingornot.If90%ofthecodelinesareexecutedduringthetests,thenthecodecoverageis90%,whichisfairlygoodinreallife,butyoushouldneverbecontentwithanythinglessthan100%.
Codecoverageisnotthesameasfunctionalcoverage,butthereisacorrelation.Ifthecodecoverageislessthan100%,thenatleastoneofthefollowingtwostatementsistrue:
Thefunctionalcoverageisnot100%Thereisunusedcodeinthetestedunit,whichcanjustbedeleted
Thecodecoveragecanbemeasured,thefunctionalcoveragecannot.ThetoolsandIDEssupportcodecoveragemeasurement.Thesemeasurementsareintegratedintotheeditorsoyouwillnotonlygetthepercentageofthecoverage,buttheeditorwillshowyouexactlywhichlinesarenotcoveredbythecoveragecoloringthelines(inEclipse,forexample)orthegutterontheleftsideoftheeditorwindow(IntelliJ).ThepictureshowsthatinIntelliJ,thetestscoverthelinesindicatedbyagreencoloronthegutter.(Intheprintversionthisisjustagreyrectangle).
RefactorthetestNowthatwehavediscussedwhatagoodunittestis,let'simproveourtest.Thefirstthingistomovethesupportingclassestoseparatefiles.WewillcreateArrayListSortableCollection:
packagepackt.java9.by.example.ch03.bubble;
importpackt.java9.by.example.ch03.SortableCollection;
importjava.util.ArrayList;
publicclassArrayListSortableCollectionimplementsSortableCollection{
finalprivateArrayListactualNames;
ArrayListSortableCollection(ArrayListactualNames){
this.actualNames=actualNames;
}
@Override
publicObjectget(inti){
returnactualNames.get(i);
}
@Override
publicintsize(){
returnactualNames.size();
}
}
ThisclassencapsulatesArrayListandthenimplementsthegetandsizemethodstoArrayListaccess.ArrayListitselfisdeclaredasfinal.Recallthatafinalfieldhastobedefinedbythetimetheconstructorfinishes.Thisguaranteesthatthefieldistherewhenwestarttousetheobjectandthatitdoesnotchangeduringtheobjectlifetime.Note,however,thatthecontentoftheobject,inthiscase,theelementsofArrayList,maychange.Ifitwerenotthecase,wewouldnotbeabletosortit.
ThenextclassisStringComparator.ThisissosimplethatIwillnotlistithere;Iwillleaveittoyoutoimplementthejava.util.ComparatorinterfacethatcancomparetwoStrings.Itshouldnotbedifficult,especiallyasthisclasswasalreadyapartofthepreviousversionoftheBubbleSortTestclass(hint:itwasananonymousclassthatwestoredinthevariablenamedstringCompare).
WealsohavetoimplementArrayListSwapper,whichalsoshouldnotbeabigsurprise.
packagepackt.java9.by.example.ch03.bubble;
importpackt.java9.by.example.ch03.Swapper;
importjava.util.ArrayList;
publicclassArrayListSwapperimplementsSwapper{
finalprivateArrayListactualNames;
ArrayListSwapper(ArrayListactualNames){
this.actualNames=actualNames;
}
@Override
publicvoidswap(inti,intj){
Objecttmp=actualNames.get(i);
actualNames.set(i,actualNames.get(j));
actualNames.set(j,tmp);
}
}
Finally,ourtestwilllookthis:
packagepackt.java9.by.example.ch03.bubble;
//...importsdeletedfromprint...
publicclassBubbleSortTest{
@Test
publicvoidcanSortStrings(){
ArrayListactualNames=newArrayList(Arrays.asList(
"Johnson","Wilson",
"Wilkinson","Abraham","Dagobert"
));
ArrayListexpectedResult=newArrayList(Arrays.asList(
"Abraham","Dagobert",
"Johnson","Wilkinson","Wilson"
));
SortableCollectionnames=
newArrayListSortableCollection(actualNames);
Sortsort=newBubbleSort();
sort.setComparator(
newStringComparator());
sort.setSwapper(
newArrayListSwapper(actualNames));
sort.sort(names);
Assert.assertEquals(expectedResult,actualNames);
}
}
Nowthisisalreadyatestthatcanbeunderstoodin15seconds.Itdocumentswellhowtouseasortimplementationthatwedefined.Itstillworksanddoesnotrevealanybug,asIpromised.
CollectionswithwrongelementsThebugisnottrivial,andasusual,thisisnotintheimplementationofthealgorithm,butratherinthedefinition,orthelackofit.Whatshouldtheprogramdoiftherearenotonlystringsinthecollectionthatwesort?
IfIcreateanewtestthatstartswiththefollowinglines,itwillthrowClassCastException:
@Test
publicvoidcanNotSortMixedElements(){
ArrayListactualNames=newArrayList(Arrays.asList(
42,"Wilson",
"Wilkinson","Abraham","Dagobert"
));
...therestofthecodeisthesameastheprevioustest
TheproblemhereisthatJavacollectionscancontainanytypeofelements.Youcannoteverbesurethatacollection,suchasArrayList,containsonlythetypesthatyouexpect.Evenifyouusegenerics(wehavenotlearnedthat,butwewillinthischapter),thechancesofabugsomehowconjuringupsomeobjectofaninappropriatetypeintoacollection,aresmallerbutarestillthere.Don'taskmehow;Icannottellyou.Thisisthenatureofthebugs—youcannottellhowtheyworkuntilyouzapthem.Thethingisthatyouhavetobepreparedforsuchanexceptionalcase.
HandlingexceptionsExceptionalcasesshouldbehandledinJavausingexceptions.TheClassCastExceptionisthereandithappenswhenthesorttriestocompareStringtoIntegerusingStringComparator,andtodothat,ittriestocastanIntegertoString.
Whenanexceptionisthrownbytheprogramusingthethrowcommand,orbytheJavaruntime,theexecutionoftheprogramstopsatthatpoint,andinsteadofexecutingthenextcommand,itcontinueswheretheexceptioniscaught.Itcanbeinthesamemethod,orinsomecallingmethodupinthecallchain.Tocatchanexception,thecodethrowingtheexceptionshouldbeinsideatryblock,andthecatchstatementfollowingthetryblockshouldspecifyanexceptionthatiscompatiblewiththeexceptionthrown.
Iftheexceptionisnotcaught,thentheJavaruntimewillprintoutthemessageoftheexceptionalongwithastacktracethatwillcontainalltheclasses,methods,andlinenumbersonthecallstackatthetimeoftheexception.Inourcase,themvntestcommandwillproducethefollowingtraceintheoutput:
java.lang.ClassCastException:java.lang.Integercannotbecasttojava.lang.String
atpackt.java9.by.example.ch03.bubble.StringComparator.compare(StringComparator.java:9)
atpackt.java9.by.example.ch03.bubble.BubbleSort.sort(BubbleSort.java:13)
atpackt.java9.by.example.ch03.bubble.BubbleSortTest.canNotSortMixedElements(BubbleSortTest.java:49)
atsun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod)
atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
atjava.lang.reflect.Method.invoke(Method.java:498)
...somelinesdeletedfromtheprint
atorg.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
atorg.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
atsun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod)
atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
...somelinesdeletedfromtheprint
atorg.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
atorg.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
atorg.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Thisstacktraceisnotreallylong.Intheproductionenvironmentinanapplicationthatrunsonanapplicationserver,thestacktracemaycontainafewhundredelements.Inthistrace,youcanseethatMavenwasstartingthetestexecution,involvedMavensurefireplugin,andthentheJUnitexecutor,untilwegetthroughthetesttothecomparator,wheretheactualexceptionwasthrown.
ThisexceptionwasnotprintedbytheJavaruntimetotheconsole.ThisexceptioniscaughtbytheJUnitlibrarycodeandthestacktraceisloggedouttotheconsoleusingMavenloggingfacility.
Theproblemwiththisapproachisthattherealissueisnottheclasscastingfailure.Therealissueisthatthecollectioncontainsmixedelements.ItisonlyrealizedbytheJavaruntimewhenittriestocasttwoincompatibleclasses.Ourcodecanbesmarter.Wecanamendthecomparator.
packagepackt.java9.by.example.ch03.bubble;
importjava.util.Comparator;
publicclassStringComparatorimplementsComparator{
@Override
publicintcompare(Objectfirst,Objectsecond){
try{
finalStringf=(String)first;
finalStrings=(String)second;
returnf.compareTo(s);
}catch(ClassCastExceptioncce){
thrownewNonStringElementInCollectionException(
"Therearemixedelementsinthecollection.",cce);
}
}
}
ThiscodecatchestheClassCastExceptionandthrowsanewone.Theadvantageofthrowinganewexceptionisthatyoucanbesurethatthisexceptionisthrownfromthecomparatorandthattheproblemreallyisthattherearemixedelementsinthecollection.Classcastingproblemsmayhappenatotherplacesofthecodeaswell,insidesomeofthesortimplementations.Someapplicationcodemaywanttocatchtheexceptionandwanttohandlethecase;forexample,sendinganapplication-specificerrormessageandnotdumpingonlyastacktracetotheuser.ThiscodecancatchClassCastExceptionaswell,butitcannotbesurewhattherealcauseoftheexceptionis.Ontheotherhand,NonStringElementInCollectionExceptionisdefinite.
TheNonStringElementInCollectionExceptionisanexceptionthatdoesnotexistintheJDK.Wewillhavetocreateit.ExceptionsareJavaclassesandourexceptionlooksasfollows:
packagepackt.java9.by.example.ch03.bubble;
publicclassNonStringElementInCollectionExceptionextendsRuntimeException{
publicNonStringElementInCollectionException(Stringmessage,Throwablecause){
super(message,cause);
}
}
Javahasthenotionofcheckedexceptions.ItmeansthatanyexceptionthatisnotextendingRuntimeExceptionshouldbedeclaredinthemethoddefinition.Supposeourexceptionwasdeclaredasfollows:
publicclassNonStringElementInCollectionExceptionextendsException
Then,wewillhavetodeclarethecomparemethodasfollows:
publicintcompare(Objectfirst,Objectsecond)throwsNonStringElementInCollectionException
Theproblemisthattheexceptionamethodthrowsispartofthemethodsignature,andthiswaycomparewillnotoverridethecomparemethodoftheinterface,and,thatway,theclasswillnotimplementtheComparatorinterface.Thus,ourexceptionhastobearuntimeexception.
Therecanbeahierarchyofexceptionsinanapplication,andoften,noviceprogrammerscreatehugehierarchiesofthem.Ifthereissomethingyoucando,itdoesnotmeanthatyoushoulddoit.Hierarchiesshouldbekeptasflataspossible,andthisisespeciallytrueforexceptions.IfthereisanexceptionintheJDKthatdescribestheexceptionalcase,thenusethereadymadeexception.Justaswellasforanyotherclass:ifitisready,donotimplementitagain.
Itisalsoimportanttonotethatthrowinganexceptionshouldonlybedoneinexceptionalcases.Itisnottosignalsomenormaloperationalcondition.DoingthathindersreadabilityofthecodeandalsoeatsCPU.ThrowinganexceptionisnotaneasytaskfortheJVM.
Itisnotonlytheexceptionthatcanbethrown.Thethrowcommandcanthrow,andthecatchcommandcancatchanythingthatextendstheThrowableclass.TherearetwosubclassesofThrowable:Error,andException.TheErrorexceptionisthrownifsomeerrorhappenedduringtheexecutionoftheJavacode.ThetwomostinfamouserrorsareOutOfMemoryErrorandStackOverflowError.Ifanyofthesehappens,youcannotdoanythingreliablytocatchtheerror.
ThereisalsoInternalErrorandUnknownErrorintheJVM,butsinceJVMisfairlystable,youwillhardlyevermeettheseerrors.
Whenanyofthoseerrorshappen,trytodebugthecodeandtrytofindoutwhyyouusethatmuchmemoryorsuchdeepmethodcallsandtrytooptimizeyoursolution.WhatIhavejustsaidaboutcreatingexceptionhierarchiesistrueagaintocatcherrors.Thefactthatyoucancatcherrorsdoesnotmeanthatyoushould.Onthecontrary,youshouldnevercatchanerrorand,especially,neverevercatchaThrowable.
Thisway,wehandledthisspecialcasewhensomeprogrammeraccidentallywrites42amongthenames,butwillitbeniceriftheerrorwasidentifiedduringcompiletime?Todothat,wewillintroducegenerics.
Justalastthoughtbeforewegothere.WhatclassbehaviordowetestwiththecanNotSortMixedElementsunittest?ThetestisinsidetheBubbleSortTesttestclass,butthefunctionalityisinthecomparatorimplementation,StringComparator.Thistestcheckssomethingthatisoutofthescopeoftheunittestclass.Icanuseitfordemonstrationpurposes,butthisisnotaunittest.Therealfunctionalityofthesortimplementationcanbeformulizedthisway:whateverexceptionthecomparatorthrowsisthrownbythesortimplementation.Youcantrytowritethisunittest,orreadon;wewillhaveitinthenextsection.
TheStringComparatorclassdoesnothaveatestclassbecauseStringComparatorispartofthetestandwewillneverwriteatestforatest.Otherwise,wewillsinkintoanendlessrabbithole.
GenericsThegenericsfeaturewasintroducedintoJavainversion5.Tostartwithanexample,ourSortableinterfaceuntilnowwasthis:
packagepackt.java9.by.example.ch03;
publicinterfaceSortableCollection{
Objectget(inti);
intsize();
}
Afterintroducinggenerics,itwillbeasfollows:
packagepackt.java9.by.example.ch03;
publicinterfaceSortableCollection<E>{
Eget(inti);
intsize();
}
TheEidentifierdenotesatype.Itcanbeanytype.Itsaysthataclassisasortablecollectionifitimplementstheinterface,namelythetwomethods—sizeandget.ThegetmethodshouldreturnsomethingthatisoftypeE,whateverEis.Thismaynotmaketoomuchsenseupuntilnow,butyouwillsoongetthepoint.Afterall,genericsisadifficulttopic.
TheSortinterfacewillbecomethefollowing:
packagepackt.java9.by.example.ch03;
importjava.util.Comparator;
publicinterfaceSort<E>{
voidsort(SortableCollection<E>collection);
voidsetSwapper(Swapperswap);
voidsetComparator(Comparator<E>compare);
}
Thisstilldoesnotprovidemuchmorevaluethanthepreviousversionwithoutgenerics,but,atleast,itdoessomething.IntheactualclassimplementingtheSortinterface,ComparatorshouldacceptthesametypethatSortableCollectionuses.ItisnotpossiblethatSortableCollectionworksonstringsandweinjectacomparatorforintegers.
TheimplementationofBubbleSortisasfollows:
packagepackt.java9.by.example.ch03.bubble;
importpackt.java9.by.example.ch03.*;
importjava.util.Comparator;
publicclassBubbleSort<E>implementsSort<E>{
@Override
publicvoidsort(SortableCollection<E>collection){
...sortcodesameasbefore
}
privateComparator<E>comparator=null;
@Override
publicvoidsetComparator(Comparator<E>comparator){
this.comparator=comparator;
}
...methodswappersameasbefore
}
Therealpowerofgenericswillcomewhenwewillwritethetests.Thefirsttestdoesnotchangemuch,
althoughwiththegenerics,itismoredefinite.
@Test
publicvoidcanSortStrings(){
ArrayList<String>actualNames=newArrayList<>(Arrays.asList(
"Johnson","Wilson",
"Wilkinson","Abraham","Dagobert"
));
ArrayList<String>expectedResult=newArrayList<>(Arrays.asList(
"Abraham","Dagobert",
"Johnson","Wilkinson","Wilson"
));
SortableCollection<String>names=
newArrayListSortableCollection<>(actualNames);
Sort<String>sort=newBubbleSort<>();
sort.setComparator(String::compareTo);
sort.setSwapper(newArrayListSwapper<>(actualNames));
sort.sort(names);
Assert.assertEquals(expectedResult,actualNames);
}
WhenwedefineArrayList,wewillalsodeclarethattheelementsofthelistwillbestrings.WhenweallocatethenewArrayList,thereisnoneedtospecifyagainthattheelementsarestringsbecauseitcomesfromtheactualelementsthere.Eachofthemisastring;therefore,thecompilerknowsthattheonlythingthatcancomebetweenthe<and>characterisString.
Thetwocharacters<and>,withoutthetypedefinitioninbetween,iscalleddiamondoperator.Thetypeisinferred.Ifyougetusedtogenerics,thiscodebringsyoumoreinformationonthetypesthatthecollectionsworkonandthecodebecomesmorereadable.Thereadabilityandtheextrainformationisnottheonlypoint.
AsweknowthattheComparatorargumentisComparator<String>now,wecanuseadvancedfeaturesofJavaavailablesinceJava8andcanpasstheString::compareTomethodreferencetothecomparatorsetter.
Thesecondtestistheimportantoneforusnow.ThisisthetestwhichensuresthatSortdoesnotinterferewiththeexceptionthatthecomparatorthrows.
@Test(expected=RuntimeException.class)
publicvoidthrowsWhateverComparatorDoes(){
ArrayList<String>actualNames=newArrayList<>(Arrays.asList(
42,"Wilson",
"Wilkinson","Abraham","Dagobert"
));
SortableCollection<String>names=
newArrayListSortableCollection<>(actualNames);
Sort<String>sort=newBubbleSort<>();
sort.setComparator((Stringa,Stringb)->{
thrownewRuntimeException();
});
finalSwapperneverInvoked=null;
sort.setSwapper(neverInvoked);
sort.sort(names);
}
Thethingis,thatitdoesnotevencompile.ThecompilersaysthatitcannotinferthetypeofArrayList<>onthethirdline.WhenalltheargumentsoftheasListmethodwerestrings,themethodreturnedalistofStringelementsandthereforethenewoperatorwasknowntogenerateArrayList<String>.Thistime,thereisaninteger,andthus,thecompilercannotinferthatArrayList<>isforStringelements.
TochangethetypedefinitionfromArrayList<>toArrayList<String>isnotacure.Inthatcase,thecompiler
willcomplainaboutthevalue42.Thisisthepowerofgenerics.Whenyouuseclassesthathavetypeparameters,thecompilercandetectwhenyouprovideavalueofthewrongtype.TogetthevalueintoArrayListtocheckthattheimplementationreallythrowsanexception,wewillhavetoconjurethevalueintoit.Wecantrytoreplacethevalue42withanemptyStringandthenaddthefollowinglinewhichwillstillnotcompile:
actualNames.set(0,42);
ThecompilerwillstillknowthatthevalueyouwanttosetinArrayListissupposedtobeString.TogetthearraywiththeIntegerelement,youwillhavetoexplicitlyunlockthesafetyhandleandpullthetrigger,shootingyourself:
((ArrayList)actualNames).set(0,42);
Now,thetestlookslikethis:
@Test(expected=RuntimeException.class)
publicvoidthrowsWhateverComparatorDoes(){
ArrayList<String>actualNames=newArrayList<>(Arrays.asList(
"","Wilson",
"Wilkinson","Abraham","Dagobert"
));
((ArrayList)actualNames).set(0,42);
SortableCollection<String>names=
newArrayListSortableCollection<>(actualNames);
Sort<String>sort=newBubbleSort<>();
sort.setComparator((a,b)->{
thrownewRuntimeException();
});
finalSwapperneverInvoked=null;
sort.setSwapper(neverInvoked);
sort.sort(names);
}
WewillsettheSwappertobenullbecauseitisneverinvoked.WhenIfirstwrotethiscode,itwasevidenttome.Afewdayslater,IreadthecodeandIstopped.Whyisswappernull?ThenIrememberedinasecondortwo.Butanytime,whenreadingandunderstandingthecodehicksup,Itendtothinkaboutrefactoring.Icanaddacommenttothelinesaying//neverinvoked,butcommentstendtoremainthereevenwhenfunctionalitychanges.Ilearneditthehardwayin2006,whenawrongcommentpreventedmefromseeinghowthecodewasexecuting.Iwasreadingthecommentwhiledebugging,insteadofthecode,andbugfixingtooktwodayswhilethesystemwasdown.Insteadofacomment,Itendtouseconstructsthatmakethecodeexpresswhathappens.Theextravariablemaymaketheclassfileafewbytesbigger,butitisoptimizedoutbytheJITcompilersothefinalcodedoesnotrunslower.
Thecomparatorthatthrowsanexceptionwasprovidedasalambdaexpression.Lambdaexpressionscanbeusedincaseswhereananonymousclassornamedclasswillbeusedhavingonlyonesimplemethod.Lambdaexpressionsareanonymousmethodsstoredinvariablesorpassedinargumentforlaterinvocation.WewilldiscussthedetailsoflambdaexpressionsinChapter8,ExtendingourE-CommerceApplication.
Fornow,wewillgoonimplementingQuickSort,andtodothat,wewillusetheTDDmethodology.
TestDrivenDevelopmentTestDrivenDevelopment(TDD)isacodewritingapproachwhenthedevelopersfirstwriteatestbasedonthespecificationandthenwritethecode.Thisisjusttheoppositethatthedevelopercommunitygotusedto.Theconventionalapproachthatwefollowedwastowritethecodeandthenwritetestsforit.Tobehonest,therealpracticemanytimeswastowritethecodeandtestitwithad-hoctestsandnounittestsatall.Beingaprofessional,youwillneverdothat,bytheway.Youalwayswritetests.(Andnow,writeitdownahundredtimes:Iwillalwayswritetests.)
OneoftheadvantagesofTDDisthatthetestsdonotdependonthecode.Asthecodedoesnotexistatthecreationofthetest,developerscannotrelyontheimplementationoftheunitand,thus,itcannotinfluencethetestcreationprocess.Thisisgenerallygood.Unittestsshouldbeblackboxtestsasmuchaspossible.
Blackboxtestisatestthatdoesnottakeintoaccounttheimplementationofthetestedsystem.Ifasystemisrefactored,implementedinadifferentway,buttheinterfaceitprovidestowardtheexternalworldisthesame,thentheblackboxtestsshouldrunjustfine.Awhiteboxtestdependsontheinternalworkingofthesystemtested.Whenthecodechangesthewhiteboxtest,thecodemayalsoneedtuningtofollowthechange.Theadvantageofawhiteboxtestcanbethesimplertestcode.Notalways.Grayboxtestisamixtureofthetwo.
Unittestsshouldbeblackboxtests,but,manytimes,itisnotsimpletowriteablackboxtest.Developerswillwriteatestthattheythinkisblackbox,butmanytimes,thisbeliefprovestobefalse.Whentheimplementationchanges,somethingisrefactoredandthetestdoesnotworkanymoreanditneedstobecorrected.Itjusthappensthatknowingtheimplementation,thedevelopers,especiallythosewhowrotetheunit,willwriteatestthatdependsontheinternalworkingofthecode.Writingthetestbeforethecodeisatooltopreventthis.Ifthereisnocode,youcannotdependonit.
TDDalsosaysthatthedevelopmentshouldbeaniterativeapproach.Youwriteonlyonetestatthestart.Ifyourun,itfails.Ofcourseitfails!Asthereisnocodeyet,ithastofail.Then,youwillwritethecodethatfulfillsthistest.Nothingmore,onlythecodethatmakesthistestpass.Then,youwillgoonwritinganewtestforanotherpartofthespecification.Youwillrunitanditfails.Thisprovesthatthenewtestdoestestsomethingthatwasnotdevelopedyet.Then,youwilldevelopthecodetosatisfythenewtestand,possibly,youwillalsomodifyablockofcodethatyouhavealreadywritteninthepreviousiterations.Whenthecodeisready,thetestswillpass.
Manytimes,developersarereluctanttomodifythecode.Thisisbecausetheyareafraidofbreakingsomethingthatwasalreadyworking.WhenyoufollowTDD,youshouldnot,andatthesametime,youneednotbeafraidofthis.Therearetestsforallfeaturesthatwerealreadydeveloped.Ifsomeofthecodemodificationbreakssomefunctionality,thetestswillimmediatelysignaltheerror.Thekeyisthatyourunthetestsasoftenaspossiblewhenthecodeismodified.
ImplementingQuickSort
Quicksort,aswehavealreadydiscussed,ismadeoftwomajorparts.Oneispartitioningandtheotheroneisdoingthepartitioningrecursivelyuntilthewholearrayissorted.TomakeourcodemodularandreadytodemonstratetheJava9module-handlingfeature,wewilldevelopthepartitioningandtherecursivesortingintoseparateclassesandinaseparatepackage.Thecomplexityofthecodewillnotjustifythisseparation.
ThepartitioningclassThepartitioningclassshouldprovideamethodthatmovestheelementsofthecollectionbasedonapivotelement,andwewillneedtoknowthepositionofthepivotelementafterthemethodfinishes.Thesignatureofthemethodshouldlooksomethinglikethis:
publicintpartition(SortableCollection<E>sortable,intstart,intend,Epivot);
TheclassshouldalsohaveaccesstoSwapperandComparator.Inthiscase,wedefinedaclassandnotaninterface;therefore,wewilluseconstructorinjection.
Theseconstructs,likesettersandconstructorinjectors,aresocommonandhappensofrequentlythatIDEssupportthegenerationofthese.Youwillneedtocreatethefinalfieldsinthecodeandusethecodegenerationmenutocreatetheconstructor.
Thepartitioningclasswilllooklikethefollowing:
packagepackt.java9.by.example.ch03.qsort;
importpackt.java9.by.example.ch03.SortableCollection;
importpackt.java9.by.example.ch03.Swapper;
importjava.util.Comparator;
publicclassPartitioner<E>{
privatefinalComparator<E>comparator;
privatefinalSwapperswapper;
publicPartitioner(Comparator<E>comparator,Swapperswapper){
this.comparator=comparator;
this.swapper=swapper;
}
publicintpartition(SortableCollection<E>sortable,intstart,intend,Epivot){
return0;
}
}
Thiscodedoesnothing,butthatishowTDDstarts.Wewillcreatethedefinitionofarequirementprovidingtheskeletonofthecodeandthetestthatwillcallit.Todothat,wewillneedsomethingthatwecanpartition.ThesimplestchoiceisanIntegerarray.ThepartitionmethodneedsaobjectoftypeSortableCollection<E>,andwewillneedsomethingthatwrapsthearrayandimplementsthisinterface.WenamethatclassArrayWrapper.Thisclassservesageneralpurposeanditisnotonlyforthetest.Becauseofthat,wecreateitasproductioncodeandassuchweputitinthedirectorymainandnotinthedirectorytest.AsthiswrapperisindependentfromtheimplementationofSort,theproperpositionofthisclassisinanewSortSupportClassesmodule.Wewillcreatethenewmoduleasitisnotpartoftheinterface.Implementationsdependontheinterface,butnotonthesupportclasses.Therecanalsobesomeapplicationthatusesourlibrariesandmayneedtheinterfacemoduleandsomeoftheimplementationbutstilldoesnotneedthesupportclasseswhentheydeliverthewrappingfunctionalitythemselves.Afterall,wecannotimplementallpossiblewrappingfunctionality.TheSRPalsoholdsforthemodules.
Javalibrariestendtocontainunrelatedfunctionalities.Fortheshortrun,itmakestheuseofthelibrarysimpler.YouwillonlyneedtospecifyonedependencyinyourPOMfileandyouwillhavealltheclassesandAPIsthatyouneed.Inthelongrun,theapplicationgetsbigger,carryingalotofclassesthatarepart
ofsomeofthelibrariesbuttheapplicationneverusesthem.
Toaddthenewmodule,themoduledirectoryhastobecreatedalongwiththesourcedirectoriesandthePOMfile.ThemodulehastobeaddedtotheparentPOManditalsohastobeaddedtothedependencyManagementsectionsothatthetestcodeoftheQuickSortmodulecanuseitwithoutspecifyingtheversion.Thenewmoduledependsontheinterfacemodule,sothisdependencyhastobeaddedtothePOMofthesupportclasses.
TheArrayWrapperclassissimpleandgeneral.
packagepackt.java9.by.example.ch03.support;
importpackt.java9.by.example.ch03.SortableCollection;
publicclassArrayWrapper<E>implementsSortableCollection<E>{
privatefinalE[]array;
publicArrayWrapper(E[]array){
this.array=array;
}
publicE[]getArray(){
returnarray;
}
@Override
publicEget(inti){
returnarray[i];
}
@Override
publicintsize(){
returnarray.length;
}
}
TheArraySwapperclass,whichwealsoneed,comesintothesamemodule.Itisjustassimpleasthewrapper.
packagepackt.java9.by.example.ch03.support;
importpackt.java9.by.example.ch03.Swapper;
publicclassArraySwapper<E>implementsSwapper{
privatefinalE[]array;
publicArraySwapper(E[]array){
this.array=array;
}
@Override
publicvoidswap(intk,intr){
finalEtmp=array[k];
array[k]=array[r];
array[r]=tmp;
}
}
Havingtheseclasses,wecancreateourfirsttest.
packagepackt.java9.by.example.ch03.qsort;
//importsdeletedfromprint
publicclassPartitionerTest{
Beforecreatingthe@Testmethod,wewillneedtwohelpermethodsthatmakeassertions.Assertionsarenotalwayssimple,andinsomecases,theymayinvolvesomecoding.Thegeneralruleisthatthetestandtheassertionsinitshouldbeassimpleaspossible;otherwise,theyarejustpossiblesourceofprogrammingerrors.Additionally,wecreatedthemtoavoidprogrammingerrors,nottocreatenewones.
TheassertSmallElementsmethodassertsthatallelementsbeforecutIndexaresmallerthanpivot.
privatevoidassertSmallElements(Integer[]array,intcutIndex,Integerpivot){
for(inti=0;i<cutIndex;i++){
Assert.assertTrue(array[i]<pivot);
}
}
TheassertLargeElementsmethodmakessurethatallelementsfollowingcutIndexareatleastaslargeaspivot.
privatevoidassertLargeElemenents(Integer[]array,intcutIndex,Integerpivot){
for(inti=cutIndex;i<array.length;i++){
Assert.assertTrue(pivot<=array[i]);
}
}
ThetestusesaconstantarrayofIntegersandwrapsitintoanArrayWrapperclass.
@Test
publicvoidpartitionsIntArray(){
Integer[]partitionThis=newInteger[]{0,7,6};
Swapperswapper=newArraySwapper<>(partitionThis);
Partitioner<Integer>partitioner=
newPartitioner<>((a,b)->a<b?-1:a>b?+1:0,swapper);
finalIntegerpivot=6;
finalintcutIndex=partitioner.partition(newArrayWrapper<>(partitionThis),0,2,pivot);
Assert.assertEquals(1,cutIndex);
assertSmallElements(partitionThis,cutIndex,pivot);
assertLargeElemenents(partitionThis,cutIndex,pivot);
}
}
ThereisnoComparatorforIntegertypeintheJDK,butitiseasytodefineoneasalambdafunction.Nowwecanwritethepartitionmethod,asfollows:
publicintpartition(SortableCollection<E>sortable,intstart,intend,Epivot){
intsmall=start;
intlarge=end;
while(large>small){
while(comparator.compare(sortable.get(small),pivot)<0&&small<large){
small++;
}
while(comparator.compare(sortable.get(large),pivot)>=0&&small<large){
large--;
}
if(small<large){
swapper.swap(small,large);
}
}
returnlarge;
}
Ifwerunthetest,itrunsfine.However,ifwerunthetestwithcoverage,thentheIDEtellsusthatthecoverageisonly92%.Thetestcoveredonly13ofthe14linesofthepartitionmethod.
Thereisaredrectangleonthegutteratline28.Thisisbecausethetestarrayisalreadypartitioned.Thereisnoneedtoswapanyelementinitwhenthepivotvalueis6.Itmeansthatourtestisgood,butnotgood
enough.Whatifthereisanerroronthatline?
Toamendthisproblem,wewillextendthetest,changingthetestarrayfrom{0,7,6}to{0,7,6,2}.Runthetestanditfails.Why?Aftersomedebugging,wewillrealizethatweinvokethemethodpartitionwiththefixedparameter2asthelastindexofthearray.But,wemadethearraylonger.Whydidwewriteaconstantthereinthefirstplace?Itisabadpractice.Let'sreplaceitwithpartitionThis.length-1.Now,itsaysthatcutIndexis2,butweexpected1.Weforgottoadjusttheassertiontothenewarray.Let'sfixit.Nowitworks.
Thelastthingistorethinktheassertions.Thelesscodethebetter.Theassertionmethodsarequitegeneral,andwewilluseitforonesingletestarray.Theassertionmethodsaresocomplexthattheydeservetheirowntest.But,wedonotwritecodetotest.Insteadofthat,wecansimplydeletethemethodsandhavethefinalversionofthetest.
@Test
publicvoidpartitionsIntArray(){
Integer[]partitionThis=newInteger[]{0,7,6,2};
Swapperswapper=newArraySwapper<>(partitionThis);
Partitioner<Integer>partitioner=
newPartitioner<>((a,b)->a<b?-1:a>b?+1:0,swapper);
finalIntegerpivot=6;
finalintcutIndex=partitioner.partition(newArrayWrapper<>(partitionThis),0,partitionThis.length-1,pivot);
Assert.assertEquals(2,cutIndex);
finalInteger[]expected=newInteger[]{0,2,6,7};
Assert.assertArrayEquals(expected,partitionThis);
}
Andthenagain,isthisablack-boxtest?Whatifthepartitioningreturns{2,1,7,6}?Itfitsthedefinition.Wecancreatemorecomplexteststocoversuchcases.Butamorecomplextestmayalsohaveabuginthetestitself.Asadifferentapproach,wecancreateteststhatmaybesimplerbutrelyontheinternalstructureoftheimplementation.Thesearenotblack-boxtestsandthusnotidealunittests.Iwillgoforthesecondone,butIwillnotargueifsomeonechoosestheother.
RecursivesortingWewillimplementthequicksortwithanextraclassthatisintheqsortpackagealongwiththepartitioningclass,whichisasfollows:
packagepackt.java9.by.example.ch03.qsort;
//importsdeletedfromtheprint
publicclassQsort<E>{
//constructorinjectedfinalfieldsdeletedfromtheprint
publicvoidqsort(SortableCollection<E>sortable,intstart,intend){
if(start<end){
finalEpivot=sortable.get(start);
finalPartitioner<E>partitioner=newPartitioner<>(comparator,swapper);
intcutIndex=partitioner.partition(sortable,start,end,pivot);
if(cutIndex==start){
cutIndex++;
}
qsort(sortable,start,cutIndex-1);
qsort(sortable,cutIndex,end);
}
}
}
ThemethodgetsSortableCollection<E>andtwoindexparameters.Itdoesnotsortthewholecollection;itsortsonlytheelementsbetweenthestartandtheendindex.
Itisalwaysimportanttobeextremelyprecisewiththeindexing.Usually,thereisnoproblemwiththestartindexinJava,butalotofbugssourcefromhowtheendindexisinterpreted.Inthismethod,thevalueofendcanmeanthattheindexisalreadynotpartoftheto-be-sortedinterval.Inthatcase,thepartitionmethodshouldbeinvokedwithend-1andthefirstrecursivecallwithcutIndexaslastparameter.Itisamatteroftaste.Theimportantthingistobepreciseanddefinetheinterpretationofindexparameters.
Ifthereisonlyoneelement(start==end),thenthereisnothingtobesortedandthemethodreturns.Thisistheendcriterionoftherecursion.Themethodalsoassumesthattheendindexisneversmallerthanthestartindex.Asthismethodisusedonlyinsidethelibrarythatwearedevelopingatthemoment,suchanassumptionisnottooriskytomake.
Ifthereissomethingtobesorted,thenthemethodtakesthefirstelementoftheto-be-sortedintervalandusesitaspivotandcallsthepartitionmethod.Whenthepartitionisdone,themethodrecursivelycallsitselfforthetwohalves.
Thisalgorithmisrecursive.Thismeansthatthemethodcallsitself.Whenamethodcallisexecuted,theprocessorallocatessomememoryinanareacalledstackanditstoresthelocalvariablesthere.Thisareathatbelongstothemethodinthestackiscalledstackframe.Whenthemethodreturns,thisareaisreleasedandthestackisrestored,simplymovingthestackpointerwhereitwastothepreviousstate.Thiswayamethodcancontinueitsexecutionaftercallinganothermethod;thelocalvariablesarethere.
Whenamethodcallsitself,itisnotdifferent.Thelocalvariablesarelocaltotheactualcallofthemethod.Whenthemethodcallsitself,itallocatesspaceforthelocalvariablesagainonthestack.Inother
words,thesearenewinstancesofthelocalvariables.
WewilluserecursivemethodsinJava,andinotherprogramminglanguages,whenthedefinitionofthealgorithmisrecursive.Itisextremelyimportanttounderstandthatwhentheprocessorcoderuns,itisnotrecursiveanymore.Onthatlevel,thereareinstructions,registerstores,andmemoryloadsandjumps.Thereisnothinglikefunctionormethodandtherefore,onthatlevel,thereisnothinglikerecursion.
Ifyougetthat,itiseasytounderstandthatanyrecursioncanbecodedasaloop.
Asamatteroffact,itisalsotruetheotherwayaround—everyloopcanbecodedasrecursionbutthatisnotreallyinterestinguntilyoustartfunctionalprogramming.
TheproblemwiththerecursioninJava,andinmanyotherprogramminglanguages,isthatitmayrunoutofstackspace.Inthecaseofquicksort,thisisnotthecase.YoucansafelyassumethatthestackformethodcallinginJavaisafewhundredsoflevels.Quicksortneedsastackthatisapproximatelylog2ndeep,wherenisthenumberofelementstobesorted.Inthecaseofonebillionelements,thisis30thatshouldjustfit.
Whyisthestacknotmovedorresized?Thatisbecausethecodethatrunsoutofthestackspaceisusuallybadstyle.Theycanbeexpressedmorereadableinformofsomeloop.Amorerobuststackimplementationwouldonlylurethenoviceprogrammertodosomelessreadablerecursivecoding.
Thereisaspecialcaseofrecursionnamedtailrecursion.Atailrecursivemethodcallsitselfasthelastinstructionofthemethod.Whentherecursivecallreturnsthecode,executingthemethoddoesnothingelsebutreleasethestackframethatwasusedforthismethodinvocation.Inotherwords,wewillkeepthestackframeduringtherecursivecalljusttothrowitawayafterwards.Whynotthrowitawaybeforethecall?Inthatcase,theactualframe,whichhasthesamesizeandcall,willallocatebecausethisisjustthesamemethodthatiskeptandtherecursivecallistransformedintoajumpinstruction.ThisisanoptimizationthatJavadoesnotdo.Functionallanguagesaredoingit,butJavaisnotreallyafunctionallanguageandthereforetail-recursivefunctionsshouldratherbeavoidedandtransformedtoaloopintheJavasourcelevel.
Non-recursivesortingTodemonstratethatevennon-tailrecursivemethodscanbeexpressedinanon-recursiveway,hereisthequicksortthatway:
publicclassNonRecursiveQuickSort<E>{
//injectedfinalfieldsandconstructordeletedfromprint
privatestaticclassStack{
finalintbegin;
finalintfin;
publicStack(intbegin,intfin){
this.begin=begin;
this.fin=fin;
}
}
publicvoidqsort(SortableCollection<E>sortable,intstart,intend){
finalList<Stack>stack=newLinkedList<>();
finalPartitioner<E>partitioner=newPartitioner<>(comparator,swapper);
stack.add(newStack(start,end));
inti=1;
while(!stack.isEmpty()){
Stackiter=stack.remove(0);
if(iter.begin<iter.fin){
finalEpivot=sortable.get(iter.begin);
intcutIndex=partitioner.partition(sortable,iter.begin,iter.fin,pivot);
if(cutIndex==iter.begin){
cutIndex++;
}
stack.add(newStack(iter.begin,cutIndex-1));
stack.add(newStack(cutIndex,iter.fin));
}
}
}
}
ThiscodeimplementsastackontheJavalevel.Whileitseesthatthereisstillsomethingscheduledtobesortedinstack,itfetcheditfromthestackanddoesthesortpartitioning,andschedulesthetwopartsforbeingsorted.
ThiscodeismorecomplexthanthepreviousoneandyouhavetounderstandtheroleoftheStackclassandhowitworks.Ontheotherhand,theprogramusesonlyoneinstanceofthePartitionerclassanditisalsopossibletouseathreadpooltoschedulethesubsequentsortsinsteadofhandlingthetasksinasingleprocess.Thismayspeedupthesortwhenitisexecutedonamulti-CPUmachine.However,thisisabitmorecomplextaskandthischaptercontainsalotofnewthingswithoutmultitasking;therefore,wewilllookatmultithreadcodeintwochapterslateronly.
Intheveryfirstversionofthesort,IwascodingitwithoutthethreelinesthatcomparecutIndexagainsttheintervalstartandincrementsitintheifbranch.Itisneededverymuch.But,theunittestswecreatedinthisbookdonotdiscoverthebugifwemissthoselines.Irecommendthatyoujustdeletethoselinesandtrytowritesomeunitteststhatfail.Thentrytounderstandwhatthespecialcaseiswhenthoselinesarevitalandtrytomodifyyourunittestsothatitisthesimplestpossiblethatstilldiscoversthatbug.(Finally,putthefourlinesbackandseeifthecodeworks.)Additionally,findsomearchitecturalreasonwhynottoputthismodificationintothemethodpartition.Thatmethodcouldjustreturnlarge+1incaselarge==start.
ImplementingtheAPIclassHavingdoneallthis,thelastthingwewillneedistohaveQuickSortasasimpleclass(alltherealworkwasalreadydoneindifferentclasses).
publicclassQuickSort<E>implementsSort<E>{
publicvoidsort(SortableCollection<E>sortable){
intn=sortable.size();
Qsort<E>qsort=newQsort<>(comparator,swapper);
qsort.qsort(sortable,0,n-1);
}
//...setterinjectorsweredeletedfromtheprint
}
Donotforgetthatwealsoneedatest!But,inthiscase,thatisnotmuchdifferentthanthatofBubbleSort.
@Test
publicvoidcanSortStrings(){
finalString[]actualNames=newString[]{
"Johnson","Wilson",
"Wilkinson","Abraham","Dagobert"
};
finalString[]expected=newString[]{"Abraham","Dagobert","Johnson","Wilkinson","Wilson"};
Sort<String>sort=newQuickSort<>();
sort.setComparator(String::compareTo);
sort.setSwapper(newArraySwapper<String>(actualNames));
sort.sort(newArrayWrapper<>(actualNames));
Assert.assertArrayEquals(expected,actualNames);
}
Thistime,weusedStringarrayinsteadofArrayList.Thismakesthistestsimplerand,thistime,wealreadyhavethesupportclasses.
Youmayrecognizethatthisisnotaunittest.InthecaseofBubbleSort,thealgorithmwasimplementedinasingleclass.Testingthatsingleclassisaunittest.InthecaseofQuickSort,weseparatedthefunctionalityintoseparateclasses,andevenintoseparatepackages.ArealunittestoftheQuickSortclasswilldisclosethedependencyofthatclassonotherclasses.Whenthistestruns,itinvolvestheexecutionofPartitionerandalsoQsort;therefore,itisnotreallyaunittest.
Shouldwebotheraboutthat?Notreally.Wewanttocreateunitteststhatinvolveasingleunittoknowwheretheproblemiswhenaunittestfails.Iftherewereonlyintegrationtests,afailingtestcasewouldnothelpalotinpointingoutwheretheproblemis.Allitsaysisthatthereissomeproblemintheclassesthatareinvolvedinthetest.Inthiscase,thereareonlyalimitednumberofclasses(three)thatareinvolvedinthistestandtheyaretiedtogether.Theyareactuallytiedtogetherandrelatedtoeachothersocloselythatintherealproductioncode,Iwouldhaveimplementedtheminasingleclass.IseparatedthemheretodemonstratehowtotestasingleunitandalsotodemonstrateJava9modulesupportthatneedsabitmorethanasingleclassinaJARfile.
Creatingmodules
Modulehandling,alsoknownasprojectJigsaw,isafeaturethatwasmadeavailableonlyinJava9.Itwasalongplannedfeaturethatthedeveloperswerewaitingfor.FirstitwasplannedforJava7,butitwassocomplexthatitgotpostponedtoJava8andthentoJava9.Ayearago,itseemedthatitwouldgetpostponedagain,butfinally,theprojectcodegotintotheearlyreleasesandnownothingcanstopfrombeingpartoftherelease.
WhymodulesareneededWehavealreadyseenthattherearefourlevelsofaccessinJava.Amethodorfieldcanbeprivate,protected,public,ordefault(alsoknownaspackageprivate)whennomodifierissupplied.Whenyoudevelopacomplexlibrarytobeusedinseveralprojects,thelibraryitselfwillcontainmanyclassesinmanypackages.Therewillcertainlybeclassesandmethods,fieldsinthosethatareusedinsidethelibrarybyotherclassesfromdifferentpackages,butclassesthatarenottobeusedbythecodeoutsidethelibrary.Makingthemanythinglessvisiblethanpublicwillrenderthemunusableinsidethelibrary.Makingthempublicwillmakethemvisiblefromoutside.
Inourcode,theMavenmodulequickcompiledtoaJARcanonlybeusedifthemethodsortcaninvokeqsort.But,wedonotwantqsorttobeuseddirectlyfromoutside.Inthenextversion,wemaywanttodevelopaversionofthesortthatusesqsortfromtheNonRecursiveQuickSortclassandwedonotwantcomplainingcustomerswhosecodedoesnotcompileorworkbecauseofaminorlibraryupgrade.Wecandocumentthattheinternalmethodsandclassesarestillpublicbutnotforuse,butinvain.Developersusingourlibrarydonotreaddocumentation.Thisisalsowhywedonotwriteexcessivecomments.Nobodywillreadit,noteventheprocessorexecutingthecode.
Themostwell-knownandinfamousexampleofthisproblemisthesun.misc.UnsafeclassintheJDK.Thereissomereallyunsafecodeinit,asthenameimplies.Youcanaccessmemoryoutofheap,createobjectswithoutinitialization,andsoon.Youshouldnot.Whybother?Youareawell-behavingdeveloperandyoujuststicktotherulesandyoudonotusethatpackage.WheneveritchangesinanewversionoftheJDK,yourprogramissafeusingonlypublicandwell-documentedJDKAPI.Right?
Wrong!Withoutbeingawareofthis,youmayusesomelibrariesthatdependonotherlibrariesthatusethepackage.MockitoandSpringFrameworkareonlytwoofthenumerousindanger.Inaddition,Java9willdefinitelycomewithanewversionofthispackage.However,itwillalsocomewithmodulehandling.WhileJava9willprovidesomeusefulAPIforthelibrariesthatwereusingtheUnsafepackagebecausetherewasnoprovidedAPIforthefunctionalitytheyneeded,itwilldelivermodulesnottorecreatethesameproblemagain.
WhatisaJavamoduleAJavamoduleisacollectionofclassesinaJARorinadirectorythatalsocontainaspecialclassnamedmodule-info.IfthereisthisfileinaJARordirectorythenitisamodule,otherwiseitisjustacollectionofclassesthatareontheclasspath(ornot).Java8,andtheearlierversions,willjustignorethatclassasitisneverusedascode.Thisway,usingolderJava,causesnoharmandbackwardcompatibilityismaintained.
Themoduleinformationdefineswhatthemoduleexportsandwhatitrequires.Ithasaspecialformat.Forexample,wecanplacemodule-info.javainourSortInterfaceMavenmodule.
modulepackt.java9.by.example.ch03{
exportspackt.java9.by.example.ch03;
}
Thismeansthatanyclass,whichispublicandinsidethepackt.java9.by.example.ch03package,canbeusedfromoutside.Thispackageisexportedfromthemodule,butotherclassesfromotherpackagesarenotvisiblefromoutsideofthemoduleeveniftheyarepublic.Thenameofthemoduleissameasthepackage,butthisismereconventionincasethereisonlyonepackageexported.Therequirementisthesameasinthecaseofpackages:thereshouldbeanamethatisnotlikelytocollidewithothermodulenames.Thereverseddomainnameisagoodchoicebutitisnotamustasyoucanseeinthisbook.Thereisnotop-leveldomainpackt,yet.
WeshouldalsoconfiguretheparentPOMtoensurethatthecompilerweuseisJava9,
<build>...
<plugins>...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.9</source>
<target>1.9</target>
</configuration>
</plugin>
...
Olderversionswouldbeconfusedwiththemodule-info.javafile.(Bytheway,eventheearlyaccessversionofJava9Iuseforthisbooksometimesgivesahardtime.)
Wealsocreateamodule-info.javafileintheMavenmodule,quick,whichisasfollows:
modulepackt.java9.by.example.ch03.quick{
exportspackt.java9.by.example.ch03.quick;
requirespackt.java9.by.example.ch03;
}
Thismoduleexportsanotherpackageandrequiresthepackt.java9.by.example.ch03modulethatwehavejustcreated.Now,wecancompilethemodulesandthecreatedJARsinthe./quick/targetand./SortInterface/targetdirectoriesarenowJava9modules.
AsMavendoesnotfullysupportthemodulesyet,whenIissuethemvninstallcommand,I
getthefollowingerrormessage:[ERROR].../genericsort/quick/src/main/java/module-info.java:[3,40]modulenotfound:
packt.java9.by.example.ch03
Mavenputsthecompiledmodulesonclasspath,butJava9seeksmodulepathformodules.Mavendoesnothandlemodulepathyet.Tohackmodulepathtothecompiler,wewillhavetoaddthefollowingconfigurationlinestotheparentPOMtotheconfigurationofthecompilerplugin:<compilerArgs><arg>-modulepath</arg>
<arg>${project.parent.basedir}/SortInterface/target/SortInterface-1.0.0-SNAPSHOT.jar:...</arg>
</compilerArgs>
TheactualfileshouldlistallthecolonseparatedJARfilesthatMavengenerates,andonwhichsomeofthemodulesdepend.ThesearetheSortInterface,quick,andSortSupportClasses.
Totestthefunctionalityofmodulesupport,wewillcreateanotherMavenmodulecalledMain.Ithasonlyoneclass,calledMain,withapublicstaticvoidmainmethod:
packagepackt.java9.by.example.ch03.main;
//...importsdeletedfromtheprint
publicclassMain{
publicstaticvoidmain(String[]args)throwsIOException{
StringfileName=args[0];
BufferedReaderbr=newBufferedReader(newInputStreamReader(newFileInputStream(newFile(fileName))));
List<String>lines=newLinkedList<>();
Stringline;
while((line=br.readLine())!=null){
lines.add(line);
}
br.close();
String[]lineArray=lines.toArray(newString[0]);
Sort<String>sort=newQuickSort<>();
Qsort<String>qsort=newQsort<>(String::compareTo,newArraySwapper<>(lineArray));
sort.setComparator(String::compareTo);
sort.setSwapper(newArraySwapper<>(lineArray));
sort.sort(newArrayWrapper<>(lineArray));
for(finalStringoutLine:lineArray){
System.out.println(outLine);
}
}
}
Ittakesthefirstargument(withoutcheckingthatthereisone,whichweshouldnotuseinaproductioncode)andusesthatasafilename.Then,itreadsthelinesofthefileintoaStringarray,sortsit,andprintsittothestandardoutput.
Asthemodulesupportonlyworksformodules,thisMavenmodulealsohastobeaJavamoduleandhaveamodule-info.javafile.
modulepackt.java9.by.example.ch03.main{
requirespackt.java9.by.example.ch03.quick;
requirespackt.java9.by.example.ch03;
requirespackt.java9.by.example.ch03.support;
}
Additionally,wewillhavetocreateamodule-info.javafileforthesupportmodule;otherwise,wewillnotbeabletouseitfromourmodule.
Aftercompilingthemodulesusingmvninstall,wecanrunittoprintouttheparentPOM.
java-cpMain/target/Main-1.0.0-SNAPSHOT.jar:SortInterface/target/SortInterface-1.0.0-SNAPSHOT.jar:quick/target/quick-1.0.0-SNAPSHOT.jar:SortSupportClasses/target/SortSupportClasses-1.0.0-SNAPSHOT.jarpackt.java9.by.example.ch03.main.Mainpom.xml
Notethatthisisonelineofcommandthatprintbreaksintoseverallines.
Now,ifwetrytoaccessQsortdirectlyinsertingthefollowinglineQsort<String>qsort=newQsort<>(String::compareTo,newArraySwapper<>(lineArray));intothemainmethod,MavenwillcomplainbecausethemodulesystemhidesitfromourMainclass:
[ERROR]Failedtoexecutegoalorg.apache.maven.plugins:maven-compiler-plugin:3.5.1:compile(default-compile)onprojectMain:Compilationfailure:Compilationfailure:
[ERROR].../Main/src/main/java/packt/java9/by/example/ch03/main/Main.java:[4,41]packagepackt.java9.by.example.ch03.qsortdoesnotexist
[ERROR].../Main/src/main/java/packt/java9/by/example/ch03/main/Main.java:[25,9]cannotfindsymbol
Themodulesystemalsosupportsthejava.util.ServiceLoaderbasedclass-loadingmechanism,whichwewillnotdiscussinthisbook.ThisisanoldtechnologythatisrarelyusedinanenterpriseenvironmentwhenSpring,Guice,orsomeotherdependencyinjectionframeworkisused.Ifyouseeamodule-info.javafilethatcontainstheusesandprovideskeywords,thenfirstconsultwiththeJavadocumentationabouttheServiceLoaderclassathttp://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html,andthentheJava9languagedocumentationonmodulesupport(http://openjdk.java.net/projects/jigsaw/quick-start).
Summary
Inthischapter,wedevelopedageneralsortingalgorithmimplementingquicksort.Wemodifiedourprojecttobeamulti-moduleMavenprojectandalsotouseJavamoduledefinitions.WewereusingJUnittodevelopunittests,andwedevelopedthecodeusingTDD.WeconvertedthecodefromoldstyleJavatonewusinggenerics,andweusedexceptionhandling.Thesearethebasictoolsthatareneededforthecomingchapters,wherewewilldevelopaguessinggame.Firstwewilldevelopasimplerversionandinthesubsequentchapterwewilldevelopaversionthatusesparallelcomputing,andmultipleproccessors.
Mastermind-CreatingaGameInthischapter,wewillstarttodevelopasimplegame.ThegameistheMastermindgamefortwoplayers.Playeroneselectsfourdifferentlycoloredpinsoutofsixpossiblecolorsandarrangesthemonaboardinarowhiddenfromtheotherplayer.Theotherplayertriestoguessthecolorsofthepinsanditspositions.Aftereachtry,playeronetellsthenumberofmatchingcolorsandthepinsmatchingbothcolorandposition.Theprogramwillactasbothplayeroneandplayertwo.Ourcodewillplayalone.However,whatremainsforustoplaywithisthemostimportant:thecode.
ThisexampleiscomplexenoughtodeepentheOOprinciplesandhowwedesignclassesandmodeltherealworld.WehavealreadyusedclassesprovidedintheJavaruntime.Thistime,wewillusecollectionsanddiscussthisimportantarea.TheseclassesandinterfacesarewidelyusedandavailableintheJDKandasimportantforaprofessionalJavadeveloperasthelanguageitself.
ThebuildtoolthistimeisGradle.
Inthischapterwewillcover:
JavacollectionsDependencyinjectionHowtocommentourcodeandtocreateJavaDocdocumentationHowtocreateintegrationtests
TheGameMastermind(https://en.wikipedia.org/wiki/Mastermind_(board_game))isanoldgame.Theplasticversionthatwasubiquitousineveryhousewithchildrenwasinventedin1970.Igotaboardaround1980asaChristmasgiftandsolvingthegamepuzzleinBASIClanguagewasoneofthefirstprogramsthatIcreatedaround1984.
Thegameboardcontainsholesinseveralrowsinfourcolumns.Thereareplasticpinsofsixdifferentcolorsthatcanbeinsertedintotheholes.Eachpinhasonecolor.Theyareusuallyred,green,blue,yellow,black,andwhite.Thereisaspecialrowthatishiddenfromoneoftheplayers(theguesser).
Toplaythegame,oneoftheplayers(hider)hastoselectfourpinsfromasetofpins.Theselectedpinsshouldhavedifferentcolors.Thepinsareplacedinthehiddenrowonebyone,eachintoaposition.
Theguessertriestofindoutwhatcolorsareinwhichpositionguessing.Eachguesstakesplaceselectingfourpinsandplacingtheminarow.Thehidertellstheguesserhowmanypinsareincorrectpositionandhowmanyhaveacolorthatisonthetable,butarenotinthepositionwherethatcolorishidden.
Asampleplaymaygolikethis:
Thehiderhidesfourpinswithcolorblue,yellow,white,andblack.Guesserguessesyellow,blue,green,andred.Thehidertellstheguesserthattherearetwocolorsmatching,butnoneofthemisinthepositioninthehiddenrow.Thehidersaysthisbecauseyellowandblueareinthehiddenrowbutnotinthepositionsastheguesserguessed.Theyareactuallyswapped,butthisinformationthehiderkeepsasecret.Allshesaysisthattherearetwocolorsmatching,noneinthecorrectposition.Thenextguessis...
Thegamefinisheswhentheguesserfindsthecorrectcolorsinthecorrectorder.Thesamegame,asonthefigure,canalsobedescribedwithtextualnotation,Bforblue,Yforyellow,Gforgreen,Wforwhite,Rforred,andbforblack(luckywehaveupperandlowercaselettersonthecomputer).
RGBY0/0
GRWb0/2
YBbW0/2
BYGR0/4
RGYB2/2
RGBY4/0
Guesswhat!Thisistheactualoutputoftheprogramthatwedevelopinthischapter.
ThemodelofthegameWhenwedevelopapieceofcodewithanobject-orientedmindset,wetrytomodeltherealworldandmapreal-worldobjectstoobjectsintheprogram.Youcertainlyhaveheardofobjectorientationexplainedwiththeverytypicalexamplesofgeometricobjects,orthecarandthemotorthingtoexplaincomposition.Personally,Ibelievethattheseexamplesaretoosimpletogetagoodunderstanding.Theymaybegoodforstarters,butwearealreadyinthefourthchapterofthebook.TheMastermindgameismuchbetter.Itisabitmorecomplexthanjustrectanglesandtriangles,butnotascomplexasatelecombillingapplicationoranatomicpowerplantcontrol.
Whatarethereal-worldobjectsthatwehaveinthatgame?Wehaveatableandwehavepinsofdifferentcolors.TherearetwoJavaclassesthatwecertainlywillneed.Whatisinatable?Therearerowseachhavingfourpositions.Perhapswewillneedaclassforarow.Atablewillhaverows.Wewillalsoneedsomethingthathidesthesecret.Thisalsomaybearowandeachrowmayalsoholdtheinformationabouthowmanypositionsandhowmanycolorsarematching.Incaseofthesecretrow,thisinformationisobvious:4and0.
Whatisapin?Eachpinhasacolorandgenerally,thatisit.Therearenootherfeaturesofapin,exceptthatitcanbeinsertedintoaholeonthetable,butthisisareallifefeaturewewillnotmodel.Essentially,apinisacolorandnothingelse.Thisway,wecaneliminatethepinclassfromourmodelearlyon,evenbeforewecreateditinJava.Instead,wehavecolors.
Whatisacolor?Thisissomethingthatmaybehardtoimmerseintothefirsttime.Weallknowwellwhatacoloris.Itisamixtureofdifferentfrequencyoflights,asoureyesperceiveit.Wecanhavepaintsandprintsindifferentcolors,andsoon.Thereareverymanythingsthatwedonotmodelinthisprogram.Itisreallyhardtotellwhatwemodelaboutcolorinourcodebecausethesefeaturesaresoobviousthatwetakeitforgrantedinreallife;wecantellabouttwocolorsthattheyaredifferent.Thisistheonlyfeatureweneed.Todothis,thesimplestclassofJavacanbeused:
packagepackt.java9.by.example.mastermind;
publicclassColor{}
IfyouhavetwovariablesofthetypeColor,youcantelliftheyarethesameornot.Youcanuseobjectidentitycomparingaandbusingtheexpressiona==boryoucanusetheequalsmethodinheritedfromtheObjectclass,a.equals(b).Itistemptingtoencodethecolorswithletters,oruseStringconstantstodenotethem.Itmaybeeasierfirst,butthereareseriousdrawbackslater.Whenthecodebecomescomplex,itleadstobugs;itwillbeeasytopasssomethingalsoencodedasStringinsteadofacolorandonlyunittestsmaysavetheday.Better,thecompileralreadycomplainsintheIDEwhenyoutypethewrongargument.
Whenweplaythegame,thepinsareinsmallboxes.Wepullpinsoutoftheboxes.Howdowegetthecolorsintheprogram?Weneedsomethingfromwherewecanfetchcolorsorlookingattheotherwaysomethingthatcangiveuscolors.WewillcallitColorManager.ColorManagerknowshowmanydifferentcolorswehaveandanytimeweneedacolor,wecanaskforit.
Again,thereisatemptationtodesigntheColorManagerthatitcanserveacolorbyitsserialnumber.Ifwe
havefourcolors,wecouldaskforcolornumber0,1,2,or3.Butthenagain,itwouldjustimplicitlyencodethecolorsasintegernumbers,whichweagreedwewillnot.Weshouldfindtheminimumfeaturethatwewillneedtomodelthegame.
Todescribethestructureoftheclasses,professionaldevelopersusuallyuseUMLclassdiagrams.UMLisadiagramnotationthatisstandardizedandisalmostexclusivelyusedtovisualizesoftwarearchitecture.TherearemanydiagramtypesinUMLtodescribethestaticstructureandthedynamicbehaviorofaprogram.Thistime,wewilllookataverysimplifiedclassdiagram.
WehavenoroomtogetintothedetailsofUMLclassdiagrams.Rectanglesdenotetheclasses,normalarrowsdenotetherelationswhenaclasshasfieldoftheotherclasstype,andtriangleheadedarrowmeansthataclassextendsanother.Thearrowpointstothedirectionoftheclassbeingextended.
AGamecontainsasecretRowandaTable.TheTablehasaColorManagerandaList<>ofRow.TheColorManagerhasafirstcolorandhasaMap<>ofColor.Wehavenotdiscussedwhythatisthedesign,wewillgetthereandthediagramhelpsuswalkingthatroad.ARowisessentiallyanarrayofColor.
Theonewhoplaysthegamehasonefunction:ithastoguessmanytimesuntilitfindsthehiddensecret.TogettothemodeloftheColorManager,wewillhavetodesignthealgorithmoftheGuesser.
Whentheplayermakesthefirstguess,anycombinationofcolorsisjustasgoodasanyother.Later,theguessesshouldconsidertheresponsesthatweregivenforpreviousguesses.Itisareasonableapproachtotryonlycolorvariationsthatcanbetheactualsecret.Theplayerselectsavariationandlooksatallpreviousguessesassumingthattheselectedvariationisthesecret.Iftheresponsestotherowshehasalreadymadearethesameforthisvariationasfortheunknownsecretinthegame,thenitisreasonabletotrythisvariation.Ifthereisanydifferenceintheresponses,thenthisvariationiscertainlynotthe
variationthatwashidden.
Tofollowthisapproach,theguesserhastogenerateallpossiblecolorvariationsoneaftertheotherandcompareitagainstthetable.Theguessercodewillnotcreateandstoreallthepossiblevariationsahead,butithastoknowwhereitwasandhastobeabletocalculatethenextvariationthatcomes.Thisassumesanorderofthevariations.Forashortwhile,let'sforgetthatnocolormayappeartwiceinavariation.Asimpleorderingcanbemadethesamewayaswesortdecimalnumbers.Ifwehaveathree-digitnumber,thenthefirstoneis000,thenextoneis001,andsoonuntil009,alwaysfetchingthenextdigitforthelastposition.Afterthat,010comes.Weincreasedadigitnexttothelastoneandwesetthelastoneto0again.Now,wehave011,012,andsoon.Youknow,howwecountnumbers.Now,replacethedigitswithcolorsandwehaveonlysixandnotten.Or,wehaveasmanyaswewantwhenweinstantiateaColorManagerobject.
ThisleadstothefunctionalityoftheColorManager.Ithastodothefollowingtwothings:
GivethefirstcolortothecallerGivethenextcolorthatfollowsagivencolor(wewillnamethemethodnextColor)
Thelatterfunctionalityshouldalsosignalsomewaywhenthereisnonextcolor.Thiswillbeimplementedusinganothermethod,namedthereIsNextColor.
ItisaconventiontostartthemethodnamesthatreturnaBooleanvaluewithis.ThatwouldleadtothenamefollowingthisconventionisThereNextColor,orisNextColor.Eitherofthesenamesexplainsthefunctionalityofthemethod.IfIaskthequestionisThereNextColor,themethodwillanswermetrueorfalse.But,thisisnothowwewillusethemethod.Wewilltalkinsimplesentences.Wewilluseshortsentences.Wewillavoidunnecessary,gibberishexpressions.Wewillalsoprogramthatway.Mostprobably,thecallerwillusethismethodinanifstatement.Theywillwritethefollowing:If(thereIsNextColor(currentColor)){...}
andnotif(isThereNextColor(currentColor)){...}
Ithinkthefirstversionismorereadableandreadabilitycomesfirst.Last,butnotleast,nobodywillblameyouifyoufollowtheoldconvention,andincasethatisthecompanystandard,youhavetoanyway.
Todothese,theColorManageralsohastocreatethecolorobjectsandshouldstoretheminastructurethathelpstheoperationsbeingperformed.
packagepackt.java9.by.example.mastermind;
importjava.util.HashMap;
importjava.util.Map;
publicclassColorManager{
finalprotectedintnrColors;
finalprotectedMap<Color,Color>successor=newHashMap<>();
finalprivateColorfirst;
publicColorManager(intnrColors){
this.nrColors=nrColors;
first=newColor();
ColorpreviousColor=first;
for(inti=1;i<nrColors;i++){
finalColorthisColor=newColor();
successor.put(previousColor,thisColor);
previousColor=thisColor;
}
successor.put(previousColor,Color.none);
}
publicColorfirstColor(){
returnfirst;
}
booleanthereIsNextColor(Colorcolor){
returnsuccessor.get(color)!=Color.none;
}
publicColornextColor(Colorcolor){
returnsuccessor.get(color);
}
}
ThestructureweuseisaMap.MapisaninterfacedefinedintheJavaruntimeandisavailablesincetheveryearlyreleasesofJava.AMaphaskeysandvalue,andforanykey,youcaneasilyretrievethevalueassignedtothekey.
Youcanseeontheline,wherethevariablesuccessorisdefinedthatwedefinethetypeofthevariableasaninterface,butthevalueisaninstanceofaclass.Obviously,thevaluecannotbeaninstanceofaninterfacebecausesuchbeastsdonotexist.But,whydowedefinethevariabletobeaninterface?Thereasonisabstractionandcodingpractice.Ifweneedtochangetheimplementationweuseforsomereason,thevariabletypestillmayremainthesameandthereisnoneedtochangethecodeelsewhere.ItisalsoagoodpracticetodeclarethevariabletobeaninterfacesothatwewillnothavethetemptationtousesomespecialAPIoftheimplementationthatisnotavailableintheinterfacejustbyconvenience.Whenitisreallyneeded,wecanchangethetypeofthevariableandusethespecialAPI.Afterall,thereisareasonthatAPIisthere,butthemeretemptationtousesomespecialthingjustbecauseitisthereishindered.Thishelpstowritesimplerandcleanerprogram.
MapisonlyoneoftheinterfacesdefinedintheJavaruntimebelongingtotheJavacollections.Therearemanyotherinterfacesandclasses.Although,theJDKandalltheclassesareavastamountandalmostnobodyknowsalltheclassesthatarethere,collectionsisaspecialareathataprofessionaldevelopershouldbeknowledgeableabout.BeforegettingintodetailsonwhyHashMapisusedinthiscode,wewillhaveanoverviewofthecollectionclassesandinterfaces.Thiswillhelpusalsounderstandtheothercollectionsusedinthisprogram.
JavacollectionsCollectionsareinterfacesandclassesthathelpusstoremorethanoneobject.Wehavealreadyseenarraysthatcandothat,andalsoArrayListinthepreviouschapters,butwedidnotdiscussindetailwhatotherpossibilitiesthereareintheJDK.Here,wewillgointomoredetail,butleavethestreamsandthefunctionalmethodsforlaterchapters,andwewillalsorefraintogointodetailsthatisratherthetaskofareferencebook.
Usingimplementationofthecollectionclassesandinterfacesreducestheprogrammingeffort.Firstofall,youdonotneedtoprogramsomethingthatisalreadythere.Secondly,theseclassesarehighlyoptimized,bothinimplementationandintheirfeatures.TheyhaveverywelldesignedAPIaswellasthecodeisfastandusessmallmemoryfootprint.Sorrytosaythattheircodewaswrittenlongtimeagoandmanytimesitisnotagoodstyle,hardtoread,andunderstand.
WhenyouuseacollectionfromtheJDK,itismorelikelythatyoucaninteroperatewithsomelibrary.Ifyoucookyourownversionoflinkedlists,itisnotlikelythatyouwillfindareadymadesolutionthatwillsortyourlist.IfyouusetheLinkedListclassintheJDK'sstandardclasslibrary,youwillgetareadymadesolutionfromtheCollectionsclass,rightfromtheJDK.ItisalsoworthmentioningthattheJavalanguageitselfsupportstheseclasses,forexample,youcaneasilyiteratethroughtheelementsofaCollectionwithashortenedspecialsyntax.
ThecollectionsinJDKcontaininterfacesthatdefinethebehaviorofthedifferentcollectiontypes,implementationclasses,andalgorithmsthatperformcertainactionssuchassorting.Manytimes,thesealgorithmsworkondifferentimplementationversions,gettingthesameresult,butoptimizedfortheimplementationspecificclass.
YoucanusetheAPIgivenbytheinterface,andifyouchangetheimplementationinyourcode,youwillgetanoptimizedversionfittingtheimplementation.
Thecollectioninterfacescanbecategorizedintwobags.OnebagcontainstheinterfacesthatextendtheCollectioninterface,andtheotheronecontainsMap,andaSortedMapextendingMap.Thisway,Mapisnotreallyacollection,asitdoesnotsimplycontainotherobjectsbutalsopairvaluestokeys.
InterfacecollectionCollectionisthetopoftheinterfacehierarchy.Thisinterfacedefinesthemethodsthatallimplementationsshouldprovide,nomatteriftheyimplementtheSet,SortedSet,List,Queue,orDequeinterfacedirectly.AsCollectionsimplysaysthatanobjectthatimplementstheCollectioninterfaceisonlyanobjectthatcollectsotherobjectstogether,themethodsitdefinesarelikeaddinganewobjecttothecollection,clearingallelementsfromthere,checkingthatanobjectisalreadyamemberofthecollection,anditeratingthroughtheelements.
Foranup-to-datedefinitionoftheinterface,consulttheJavapidocumentation(http://download.java.net/java/jdk9/docs/api/overview-summary.html).YoucanconsulttheonlineAPIanytime,anditisrecommendedtodoso.
TheJavalanguageitselfdirectlysupportstheinterface.YoucaniteratethroughtheelementsoftheCollectionwiththeenhancedforloopsyntax,thesamewayasyoucaniterateovertheelementsofanarraywherethecollectionshouldbeanexpressionthatresultsanobjectthatimplementstheCollectioninterface:for(Eelement:collection){...}
Intheprecedingcode,EiseitherObjectorthegenerictypeoftheelementsoftheCollection.
TheinterfaceCollectionisnotdirectlyimplementedintheJDK.ClassesimplementoneofthesubinterfacesofCollection.
SetTheSetisaspecialcollectionthatcannotcontainduplicateelements.Whenyouwanttoaddanobjectintoasetthatalreadyhasanobjectthatisthesameorequaltotheactualone,thentheaddmethodwillnotaddtheactualobject.Theaddmethodwillreturnfalseindicatingthefailure.
YoucanuseSetinyourprogramwhenyouneedacollectionofuniqueelementswhereyousimplywanttocheckthatanelementisamemberofasetornot,whetheranobjectbelongstoacertaingroupornot.
Aswewillreturntoourprogramcode,wewillseethattheUniqueGuesserclasshastoimplementanalgorithmthatchecksthatacolorinaguessispresentonlyonce.ThisalgorithmistheidealcandidateforaSettobeused:
privatebooleanisNotUniqueWithSet(Color[]guess){
finalSet<Color>alreadyPresent=newHashSet<>();
for(Colorcolor:guess){
if(alreadyPresent.contains(color)){
returntrue;
}
alreadyPresent.add(color);
}
returnfalse;
}
Thecodecreatesaset,whichisemptywhenthemethodstarts.Afterthat,itchecksforeachcolor(noticetheenhancedforloopoverthearrayelements)ifitwasalreadypresentbefore.Todothat,thecodechecksifthecolorisalreadyintheset.Ifitisthere,theguessisnotuniqueaswehavefoundacolorthatispresentatleasttwice.Ifthecolorwasnotintheset,thentheguesscanstillbeuniqueincolors.Tobeabletodetectthatlater,thecodeputsthecolorintotheset.
TheactualimplementationofSetthatwewilluseisHashSet.IntheJDK,therearemanyclassesimplementingtheSetinterface.ThemostwidelyusedisHashSet,anditisalsoworthmentioningEnumSet,LinkedHashSet,andTreeSet.ThelastonealsoimplementstheSortedSetinterface,sowewilldetailitthere.
TounderstandwhatHashSet(andlaterHashMap)areandhowtheywork,wewillhavetodiscusswhathashesare.Theyplayveryimportantandcentralroleinmanyapplications.TheydotheirjobunderthehoodintheJDKbuttherearesomeveryimportantconstraintsthatprogrammershavetofolloworelsereallyweirdandextremelyhardtofindbugswillmaketheirlifemiserable.IdaretosaythatviolationofthehashcontractinHashSetandHashMaparethecauseofthesecondmostdifficulttofindbugsnexttomultithreadissues.
Thus,beforegoingonwiththedifferentcollectionimplementations,wewillvisitthistopic.Wearealreadyoneleveldeepfromourexampleinthisdetourdiscussingcollectionsandnowwewillgooneleveldeeper.Ipromisethisisthelastin-depthlevelofdetours.
HashfunctionsAhashisamathematicalfunctionthatassignsanumbertoanelement.SayyouworkatauniversityadministrationandyouhavetotellifWilkinsonisastudentatyourclass.Youcanstorethenamesonsmallpapersinenvelopesoneforeachstartingletter.Insteadofsearchingthroughthe10thousandstudents,youcanlookatthepapersintheenvelopetitledW.Thisverysimplehashfunctionassignsthefirstletterofthenametothename(ortheordinalnumberoftheletter,aswesaidthatahashfunctionresultsanumber).Thisisnotreallyagoodhashfunctionbecauseitputsonlyafewelements,ifany,intotheenvelopedenotedXandmanytoAforexample.
Agoodhashfunctionresultseachpossibleordinalnumberwithsimilarprobability.Inhashtables,weusuallyhavemorebuckets(envelopesinthepreviousexample)thanthenumberofelementstobestored.Therefore,whenanelementissearchedfor,itislikelythatthereisonlyoneelementthere.Atleastthatiswhatwewouldliketohave.Iftherearemultipleelementsinasinglebucket,itiscalledcollision.Agoodhashfunctionhasaslittlecollisionsaspossible.
Forbackwardcompatibility,thereisaHashtableclassintheJDK.ThiswasoneofthefirsthashtableimplementationsinJavarightintheveryfirstversion,andasJavaisbackwardcompatible,itwasnotthrownaway.TheMapinterfacewasintroducedinversion1.2only.Hashtablehasmanydrawbacksanditsuseisnotrecommended.(EventhenameisviolatingtheJavanamingconventions.)Wedonotdiscussthisclassinthisbook.Wheneverwetalkabouthashtables,itisreferringtotheactualarraythatisinsidetheimplementationofHashSet,HashMap,oranyothercollectionthatusessomehashindexedtable.
Hashtablesarearraysthatusetheresultofthehashfunctiontoindexthearray.Usually,linkedlistsmanagecollisions.Hashtableimplementationsalsoimplementastrategytoresizethearraywhenthenumberofelementstobestoredbecomestoohighandthelikelihoodofcollisionsincrease.Thisoperationmaytakeconsiderabletimeand,duringthis,theindividualelementsaremovedbetweenthebuckets.
Duringthisoperation,thehashtablecannotreliablybeusedandthismaybesomesourceofissuesinamultithreadenvironment.Insinglethreadcode,youdonotmeetthisproblem.Whenyoucalltheaddmethod,thehashtable(setormap)decidesthatthetablehastoberesized.Theaddmethodcallstheresizingmethodanddoesnotreturnuntilitisfinished.Singlethreadcodehasnopossibilitytousethehashtableduringthisperiod:theoneandsinglethreadisexecutingtheresizingitself.Inamultithreadenvironment,however...
HashSetandHashMapusethehashfunctionprovidedbytheObjectthatisstoredinthecollection.TheObjectclassimplementsthehashCodeandequalsmethods.Youcanoverridethemandifyoudo,youshouldoverridebothinaconsistentmanner.First,wewillseewhattheyareandthenhowtooverridethemconsistently.
Methodequals
Thedocumentationofsetsays"setscontainnopairofelementse1ande2suchthate1.equals(e2)".Theequalsmethodreturnstrueifthee1ande2areinsomewayequal.Itmaybedifferentfromtwoobjectsbeingidentical.Therecanbetwodistinctobjectsthatareequal.Forexample,wecouldhaveacolorimplementationthathasthenameofthecolorsasanattributeandtwocolorobjectsmayreturntruecallingtheequalsmethodononeofthemandpassingtheargumentastheotherwhenthetwostringsareequal.ThedefaultimplementationoftheequalsmethodisinthecodeoftheObjectclassandthisreturnstrueifandonlyife1ande2areexactlythesameandsingleobject.
Itseemstobeobvious,butmyexperienceshowsthatitcannotbestressedenoughthattheimplementationofequalsinanobjecthastobeasfollows:
Reflexive:ThismeansthatanobjectthatalwaysequalsitselfSymmetric(commutative):Thismeansife1.equals(e2)istrue,thene2.equals(e1)shouldalsobetrueTransitive:Thismeansife1.equals(e2)ande2.equals(e3),thene1.equals(e3)Consistent:Thismeansthatthereturnvalueshouldnotchangeiftheobjectswerenotchangedbetweentheinvocations
MethodhashCodeThehashCodemethodreturnsanint.Thedocumentationsaysthatanyclassredefiningthismethodshouldprovidethefollowingimplementation:
ConsistentlyreturnthesamevalueiftheobjectwasnotmodifiedResultthesameintvaluefortwoobjectsthatareequal(theequalsmethodreturnstrue)
Thedocumentationalsomentionsthatthisisnotarequirementtoresultdifferentintvaluesforobjectsthatarenotequal,butitisdesirabletosupporttheperformanceofthehashimplementingcollections.
IfyouviolateanyoftheserulesintheimplementationofequalsandhashCode,thentheJDKclassesusingthemwillfail.AsyoucanbesurethatHashSet,HashMap,andsimilarclasseswerefullydebugged,seeingthatyouaddedanobjecttoasetandthenthesetreportingthatitisnottherewillbeabewilderingexperience.However,onlyuntilyoufindoutthatthetwoobjectsbeingequalandstoredinthesethavedifferenthashCodevalues,HashSetandHashMapwilllookfortheobjectonlyinthebucketthatisindexedbythehashCodevalue.
ItisalsoacommonmistaketostoreanobjectinaHashSetorHashMapandthenmodifyit.TheobjectisinthecollectionbutyoucannotfinditbecausethehashCodereturnsadifferentvalue.Objectsstoredinacollectionshouldnotbemodifiedunlessyouknowwhatyouaredoing.
Manytimes,objectscontainfieldsthatarenotinterestingfromtheequalitypointofview.ThehashCodeandequalsmethodsshouldbeidempotenttothosefieldsandyoucanalterthosefieldsevenafterstoringtheobjectinaHashSetorinHashMap.
Asanexample,youmayadministertrianglesinobjectsmaintainingthecoordinatesoftheverticesandthecolorofthetriangle.However,youdonotcareaboutthecolorforequality,onlythatthetwotrianglesareattheexactsamelocationinthespace.Inthatcase,theequalsandhashCodemethodshouldnottakethefieldcolorintoaccount.Thisway,wecanpaintourtriangles;theywillstillbefoundinHashSetorHashMapnomatterwhatthecolorfieldis.
ImplementingequalsandhashCodeImplementingthesemethodsisfairlysimple.Asthisisaverycommontask,theIDEssupportthegenerationofthesemethods.ThesemethodsaretiedtogethersomuchthatthemenuitemsintheIDEsarenotseparate;theyofferyoutogeneratethesemethodsatonce.
AskingtheIDEtogeneratetheequalsmethodwillresultinsomethinglikethefollowingcode:
@Override
publicbooleanequals(Objecto){
if(this==o)returntrue;
if(o==null||getClass()!=o.getClass())returnfalse;
MyObjectJava7that=(MyObjectJava7)o;
returnObjects.equals(field1,that.field1)&&
Objects.equals(field2,that.field2)&&
Objects.equals(field3,that.field3);
}
Forthissample,wehavethreeObjectfieldsnamedfield1,field2,andfield3.Thecodewithanyothertypesandfieldswilllookverysimilar.
First,themethodchecksforobjectidentity.OneObjectalwaysequalsitself.Ifthereferencepassedasargumentisnullandnotanobject,ortheyareofdifferentclass,thenthisgeneratedmethodwillreturnfalse.Inothercases,thestaticmethodoftheclassObjects(notetheplural)willbeusedtocompareeachofthefields.
TheutilityclassObjectswasintroducedinJava7,hencethenameofthesampleclass.Thestaticmethods,equalsandhash,supporttheoverrideoftheObjectequalsandhashCodemethods.ThehashCodecreationbeforeJava7wasfairlycomplexandrequiredtheimplementationofmoduloarithmeticwithsomemagicnumbersthatishardtoexplainjustlookingatthecodewithoutknowingthemathematicsbehindit.
ThiscomplexityisnowhiddenbehindthefollowingObjects.hashmethod.
@Override
publicinthashCode(){
returnObjects.hash(field1,field2,field3);
}
ThegeneratedmethodsimplycallstheObjects.hashmethodpassingtheimportantfieldsasarguments.
HashSet
Now,weknowessentiallyalotofthingsabouthashessowecanbravelydiscusstheHashSetclass.HashSetisanimplementationoftheSetinterfacethatinternallyuseshashtable.Generally,thatisit.Youstoreobjectsthereandyoucanseeifanobjectisalreadythereornot.WhenthereisaneedforaSetimplementation,almostalwaysHashSetisthechoice.Almost...
EnumSet
EnumSetcancontainelementsfromacertainenumeration.Recallthatenumerationsareclassesthathavefixedanumberofinstancesdeclaredinsidetheenumitself.Asthislimitsthenumberofthedifferentobjectinstances,andthisnumberisknownduringcompilationtime,theimplementationoftheEnumSetcodeisfairlyoptimized.Internally,EnumSetisimplementedasabitfieldandisagoodchoicewherebitfieldmanipulationscanbeused.
LinkedHashSet
LinkedHashSetisaHashSetthatalsomaintainsadoublylinkedlistoftheelementsitholds.WhenweiteratethoughaHashSet,thereisnoguaranteedorderoftheelement.WhentheHashSetismodified,thenewelementsareinsertedintooneofthebucketsand,possibly,thehashtablegetsresized.Thismeansthattheelementsgetrearrangedandgetintototallydifferentbuckets.IterationovertheelementsinHashSetjusttakesthebucketsandtheelementsinitinsomeorderthatisarbitraryfromthecallerpointofview.
LinkedHashSet,however,iteratesovertheelementsusingthelinkedlistitmaintainsandtheiterationisguaranteedtohappenintheordertheelementswereinserted.
SortedSet
TheSortedSetisaninterfacethatguaranteesthattheclassesimplementingitwilliterateoverthesetinasortedorder.TheordermaybethenaturalorderingoftheobjectsiftheobjectsimplementtheComparableinterfaceoraComparatorobjectmaydriveit.ThisobjectshouldbeavailablewhentheinstanceoftheclassimplementingtheSortedSetiscreated;inotherwords,ithastobeaconstructorparameter.
NavigableSet
NavigableSetextendstheSortedSetinterfacewithmethodsthatletyoudoproximitysearchintheset.Thisessentiallyletsyousearchforanelementthatisinthesearchandislessthanthesearchedobject,lessorequaltothesearchedelement,greaterorequal,orgreaterthanthesearchedobject.
TreeSet
TreeSetisanimplementationofNavigableSetand,thiswaythisisalsoaSortedSetand,asamatteroffact,isalsoaSet.AsaSortableSetdocumentationimpliestherearetwotypesoftheconstructors,eachhavingmultipleversionsthough.OnerequiressomeComparator,theotheronereliesonthenaturalorderingoftheelements.
List
Listisaninterfacethatrequiresimplementingclasstokeeptrackoftheorderoftheelements.TherearealsomethodsthataccessanelementbyindexanditerationdefinedbytheCollectioninterfacethatguaranteestheorderoftheelements.TheinterfacealsodefinesthelistIteratormethodthatreturnsanIteratoralsoimplementingtheListIteratorinterface.Thisinterfaceprovidesmethodsthatletthecallerinsertelementstothelistwhileiteratingthroughitandalsogoingbackandforthintheiteration.ItisalsopossibletosearchforacertainelementintheListbutmostimplementationsoftheinterfaceprovidepoorperformancewhilethesearchingissimplygoingthroughallelementsuntiltheelementsearchedforisfound.TherearemanyclassesimplementingthisinterfaceintheJDK.Here,wewillmentiontwo.
LinkedList
Thisisadoubly-linkedlistimplementationoftheListinterfacethathasareferencetotheprevious,andalsotothenextelementinthelistforeachelement.TheclassalsoimplementstheDequeinterface.Itisfairlycheaptoinsertordeleteanelementfromthelistbecauseitneedsonlytheadjustmentoffewreferences.Ontheotherhand,theaccesstoanelementbyindexwillneediterationfromthestartofthelist,orfromtheendofthelist,whicheverisclosertothespecifiedindexedelement.
ArrayListThisclassisanimplementationoftheListinterfacethatkeepsthereferencestotheelementsinanarray.Thatway,thisisfairlyfasttoaccessanelementbyindex.Ontheotherhand,insertinganelementtoArrayListcanbecostly.Itneedsmovingallreferencesabovetheinsertedelementoneindexhigher,anditmayalsorequireresizingthebackingarrayincasethereisnoroomintheoriginalonetostorethenewelement.Essentially,thismeansallocatinganewarrayandcopyingallreferencestoit.
ThereallocationofthearraymaybeoptimizedifweknowhowlargethearraywillgrowandcalltheensureCapacitymethod.Thiswillresizethearraytothesizeprovidedasargument,evenifthecurrentlyusedslotsarelessnumbered.
MyexperienceisthatnoviceprogrammersuseArrayListwhentheyneedalistwithoutconsideringthealgorithmicperformanceofthedifferentimplementations.IdonotactuallyknowwhythereisthispopularityofArrayList.Theactualimplementationusedinaprogramshouldbebasedonproperdecisionandnothabit.
QueueQueueisacollectionthatusuallystoreselementforlateruse.Youcanputelementsintoaqueueandyoucanpullthemout.Animplementationmayspecifythegivenorder,thatmaybefirstinfirstout(FIFO)orlastinfirstout(LIFO)orsomeprioritybasedordering.
Onaqueue,youcaninvoketheaddmethodtoaddanelement,removetoremovetheheadelement,andtheelementmethodtoaccesstheheadelementwithoutremovingitfromthequeue.Theaddmethodwillthrowanexceptionwhenthereisacapacityproblemandtheelementcannotbeaddedtothequeue.Whenthequeueisempty,andthereisnoheadelement,theelementandremovemethodsthrowexception.
Asexceptionscanonlybeusedinexceptionalcases,andthecallingprogrammayhandlethesesituationsinthenormalcourseofthecode,thusallthesemethodshaveaversionthatjustreturnsomespecialvaluesignalingthesituation.Insteadofadd,acallermaycalloffertoofferanelementforstorage.Ifthequeuecannotstoretheelement,itwillreturnfalse.Similarly,peekwilltrytogetaccesstotheheadelementorreturnnullifthereisnone,andpollwillremoveandreturntheheadelementorjustreturnnullifthereisnone.
Notethatthesemethodsreturningnulljustmakethesituationambiguouswhentheimplementation,suchasLinkedList,allowsnullelements.Neverstoreanullelementinaqueue.
DequeDequeisaninterfacewhichisadouble-endedqueue.ItextendstheQueueinterfacewiththemethodsthatallowaccesstobothendsofthequeuetoadd,lookat,andremoveelementsfrombothends.
FortheQueueinterfaceweneededsixmethods.Dequeuehavingtwomanageableendsneeds12methods.InsteadofaddwehaveaddFirstandaddLast.SimilarlywecanofferFirst,offerLastaswellaspeekFirst,peekLastandpollFirst,pollLast.ForsomereasonthemethodsthatimplementthefunctionalityoftheelementmethodonQueuearenamedgetFirstandgetLast.
SincethisinterfaceextendstheQueueinterfacethemethodsdefinedtherecanalsobeusedtoaccesstheheadofthequeue.InadditiontothesethisinterfacealsodefinesthemethodsremoveFirstOccurrenceandremoveLastOccurrencethatcanbeusedtoremoveaspecificelementinsidethequeue.Wecannotspecifytheindexoftheelementtoremoveandwealsocannotaccessanelementbasedonindex.TheremoveFirst/LastOccurrencemethods'argumentistheobjectthatistoberemoved.IfweneedthisfunctionalitywecanuseDequeevenifweaddandremoveelementsfromthesameendofthequeue.
WhyaretherethesemethodsinDequeandnotinQueue?ThesemethodshavenothingtodowithdoubleheadednessofDeque.Thereasonisthatmethodscannotbeaddedtointerfacesaftertheywerereleased.Ifweaddamethodtoaninterfacewebreakthebackwardcompatibilitybecauseallclassesthatimplementthatinterfacehavetoimplementthenewmethod.Java8introduceddefaultmethodsthateasedthisconstraint,buttheQueueinterfacewasdefinedinJava1.5andtheDequeinterfacewasdefinedinJava1.6.Therewasnowayatthattimetoaddthenewmethodstothealreadyexistinginterfaces.
Map
AMappairskeysandvalues.IfwewanttoapproachaMapfromtheCollectionpointofviewthenaMapisasetofkey/valuepairs.YoucanputkeyvaluepairsintoaMapandyoucangetavaluebasedonakey.KeysareuniquethesamewayaselementsinaSet.IfyoulookatthesourcecodeofthedifferentimplementationsoftheSetinterface,youmayseethatsomeofthemareimplementedasawrapperaroundaMapimplementationwherethevaluesaresimplydiscarded.
UsingMapsiseasyandalluring.Manylanguages,suchasPython,Go,JavaScript,Perl,andsoon,supportthisdatastructureonthelanguagelevel.However,usingaMapwhenanarraywouldbesufficientisabadpracticethatIhaveseenmanytimes,especiallyinscriptinglanguages.JavaisnotpronetothatnoviceprogrammererrorbutyoumaystillfindyourselfinasituationwhenyouwanttouseaMap,andstillthereisabettersolution.Itisageneralrulethatthesimplestdatastructureshouldbeusedthatissufficientfortheimplementationofthealgorithm.
HashMap
HashMapisahashtablebasedimplementationoftheMapinterface.Asthemapisbasedonahashtable,thebasicputandgetmethodsareperformedinconstanttime.Additionally,asMapisveryimportant,andbecausethemostfrequentlyusedimplementationintheJDKisHashMap,theimplementationisfairlyconfigurable.YoucaninstantiateHashMapusingthedefaultconstructorwithoutargument,butthereisalsoaconstructorthatdefinestheinitialcapacityandtheloadfactor.
IdentityHashMap
IdentityHashMapisaspecialMapthatimplementstheMapinterfaceliterally,butasamatteroffact,itviolatesthecontracttheMapinterfacedocumentationdefines.Itdoesitwithgoodreason.TheimplementationusesahashtablejustasHashMap,buttodecidetheequalityofthekeyfoundinthebucketcomparingwiththekeyelementprovidedasargumenttothegetmethoditusesObjectreference(==operator)andnotthemethodequals,whichisrequiredbydocumentationofMapinterface.
TheuseofthisimplementationisreasonablewhenwewanttodistinguishdifferentObjectinstancesaskeysthatotherwiseequaltoeachother.Usingthisimplementationforperformancereasonsisalmostcertainlyawrongdecision.Also,notethatthereisnoIdentityHashSetimplementationintheJDK.ProbablysuchcollectionissorarelyusedthatitsexistenceintheJDKwouldcausemoreharmthangoodalluringnoviceprogrammerstomisuse.
DependencyinjectionInthepreviouschapterwebrieflyalreadydiscusseddependencyinjection(DI).Nowwewilldigintoitabitmoredetail.
Objectsusuallydonotworkontheirown.Mostofthetimetheimplementationdependsontheservicesofotherclasses.WhenwewanttowritesomethingtotheconsoleweusetheSystemclass.WhenwemanagethetableofguessesweneedColorobjectsandColorManager.
IncaseofwritingtotheconsolewemaynotrealizethedependencybecausetheclassbeingpartoftheJDKclasslibraryisavailableallthetimeandallweneedtodoistowriteSystem.out.println.Inthiscasethisdependencyiswiredintothecode.Wecannotsendtheoutputsomewhereelseunlesswechangethecode.Thisisnottooflexibleandinmanycasesweneedasolutionthatcanworkwithdifferentoutput,differentcolormanagerordifferentwhateverserviceourcodedependson.Thefirststeptodothatistohaveafieldthathasareferenceoftheobjectthatgivesourclasstheservice.IncaseofoutputthetypeofthefieldcanbeoftypeOutputStream.Thenext,moreinterestingstepishowthisfieldgetsvalue.
OneofthesolutionistouseDI.Inthisapproachsomeexternalcodepreparesthedependenciesandinjectsthemintotheobject.Whenthefirstcalltoamethodoftheclassisissuedallthedependenciesarealreadyfilledandreadytobeused.
Inthisstructure,wehavefourdifferentplayers:
TheclientobjectistheonethatgetstheinjectedserviceobjectsduringtheprocessServiceobjectorobjectsareinjectedintotheclientobjectInjectoristhecodethatperformstheinjectionInterfacesdefinetheservicethattheclientneeds
Ifwemovethelogicofthecreationoftheserviceobjectsfromtheclientcodethecodebecomesshorterandcleaner.Theactualcompetencyoftheclientclassshouldhardlyevercoverthecreationoftheserviceobjects.ForexampleaGameclasscontainsaTableinstancebutagameisnotresponsibletocreatetheTable.Itisgiventoittoworkwithit,justasinreallifethatwemodel.
Thecreationofserviceobjectsissometimesassimpleasissuingthenewoperator.Sometimesserviceobjectsalsodependonotherserviceobjectsandthatwayalsoactasclientsintheprocessofdependencyinjection.Inthiscasethecreationoftheserviceobjectsmaybealotoflines.Thestructureofthedependenciescanbeexpressedinadeclarativefashionthatdescribeswhichserviceobjectneedswhichotherserviceobjectsandalsowhatimplementationoftheserviceinterfacesaretobeused.Dependencyinjectioninjectorsworkwithsuchdeclarativedescriptions.Whenthereisaneedforanobjectthatneedsserviceobjectsthatthemselvesneedagainotherserviceobjectstheinjectorcreatestheserviceinstancesintheappropriateorderusingtheimplementationsthatarematchingthedeclarativedescriptions.Theinjectordiscoversallthedependenciestransitivelyandcreatesatransitiveclosuregraphofthedependencies.
ThedeclarativedescriptionoftheneededdependenciescanbeXML,oraspeciallanguagedeveloped
especiallyforthedependencyinjectionoritcanevenbeJavaitselfusingspeciallydesignedfluentAPI(https://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course/).XMLwasfirstusedinDIinjectors.LaterGroovybasedDomainSpecificLanguage(https://martinfowler.com/books/dsl.html)cameintopictureandJavafluentAPIapproach.WewilluseonlythelastonebeingthemostmodernandwewilluseSpringandGuiceDIcontainerssincetheyarethemostwell-knowninjectorimplementations.
ImplementingthegameCollectionswithoutexamplesareboring.Fortunately,wehaveourgamewhereweuseafewcollectionclassesandalsootheraspectsthatwewillexamineinthischapter.
ColorManagerWejumpedintothepoolfilledwithcollectionclassesfromtheimplementationoftheColorManagerclass.Let'srefreshthepartoftheclassthatisinterestingforusnow—theconstructor:
finalprotectedintnrColors;
finalprotectedMap<Color,Color>successor=newHashMap<>();
finalprivateColorfirst;
publicColorManager(intnrColors){
this.nrColors=nrColors;
first=newColor();
ColorpreviousColor=first;
for(inti=1;i<nrColors;i++){
finalColorthisColor=newColor();
successor.put(previousColor,thisColor);
previousColor=thisColor;
}
successor.put(previousColor,Color.none);
}
WewilluseHashMaptokeepthecolorsinanorderedlist.Atfirst,thechoiceofHashMapseemstobestrange.Verytrue,thatduringthecodingofColorManager,IalsoconsideredaList,whichseemedtobeamoreobviouschoice.WhenwehaveaList<Color>colorsvariable,thenthenextColormethodissomethinglikethis:
publicColornextColor(Colorcolor){
if(color==Color.none)
returnnull;
else
returncolors.get(colors.indexOf(color)+1);
}
Theconstructorwillbemuchsimpler,asshowninthefollowingpieceofcode:
finalList<Color>colors=newArrayList<>();
publicColorManager(intnrColors){
this.nrColors=nrColors;
for(inti=0;i<nrColors;i++){
colors.add(newColor());
}
colors.add(Color.none);
}
publicColorfirstColor(){
returncolors.get(0);
}
WhydidIchoosethemorecomplexsolutionandtheunobviousdatastructure?Thethingisperformance.WhenthenextColormethodisinvoked,thelistimplementationfirstfindstheelementcheckingalltheelementsinthelistandthenfetchesthenextelement.Thetimeisproportionaltothenumberofcolors.Whenournumberofcolorsincreases,thetimewillalsoincreasetojustgetthenextcolorhavingone.
Atthesametime,ifwefocusonnotthedatastructurethatcomesfromtheverbalexpressionofthetaskwewanttosolve(getthecolorsinasortedorder)butratherfocusontheactualmethodthatwewanttoimplement,nextColor(Color),thenwewilleasilycometotheconclusionthataMapismorereasonable.WhatweneedisexactlyaMap:havingoneelementwewantanotherrelatedtotheonewehave.Thekeyandthe
valueisalsoColor.GettingthenextelementisconstanttimeusingHashMap.ThisimplementationisprobablyfasterthantheonebasedonArrayList.
Theproblemisthatitisonlyprobablyfaster.Whenyouconsiderrefactoringacodetohavebetterperformance,yourdecisionshouldalwaysbebasedonmeasurements.Ifyouimplementacodethatyouonlythinkisfaster,practiceshows,youwillfail.Inbestcase,youwilloptimizeacodetobeblazingfastandrunsduringtheapplicationserversetup.Atthesametime,optimizedcodeisusuallylessreadable.Somethingforsomething.Optimizationshouldneverbedoneprematurely.Codeforreadabilityfirst.Then,assesstheperformance,andincasethereisproblemwiththeperformance,thenprofiletheexecutionandoptimizethecodewhereithurtsthemostoftheoverallperformance.Micro-optimizationswillnothelp.DidIdoprematureoptimizationselectingtheHashMapimplementationinsteadofList?IfIactuallyimplementedthecodeusingListandthenrefactored,thenyes.IfIwasthinkingabouttheListsolutionandthenitcametomethatMapsolutionisbetterwithoutpriorcoding,thenIdidnot.Byyears,suchconsiderationswillcomeeasier,asyouwillalsoexperience.
TheclasscolorWehavealreadylookedatthecodefortheclasscodeanditwasthesimplestclassintheworld.Inreality,asitisintheGitHubrepository(https://github.com/j9be/chapter04orhttps://github.com/PacktPublishing/Java-9-Programming-By-Example/tree/master/Chapter04),thecodeisabitmorecomplex:
packagepackt.java9.by.example.mastermind;
/**
*RepresentsacolorintheMasterMindtable.
*/
publicclassColor{
/**
*Aspecialobjectthatrepresentsa
*valuethatisnotavalidcolor.
*/
publicstaticfinalColornone=newColor();
}
WehaveaspecialcolorconstantnamednonethatweusetosignalareferencethatisoftypeColorbutisnotavalidColor.Inprofessionaldevelopment,weusedthenullvalueforalongtimetosignalinvalidreference,andbecausewearebackwardcompatible,westilluseit.However,itisrecommendedtoavoidthenullreferencewhereverpossible.
TonyHoare(https://en.wikipedia.org/wiki/Tony_Hoare),whoinventedthenullreferencein1965,admittedonetimethatthiswasamistakethatcostbillionsofdollarsintheITindustry.
Theproblemwiththenullvalueisthatittakesthecontrolawayfromtheclass,andthus,opensencapsulation.Ifamethodreturnsnullinsomesituation,thecallerisstrictlyrequiredtocheckthenullityandactaccordingtothat.Forexample,youcannotcallamethodonanullreferenceandyoucannotaccessanyfield.Ifthemethodreturns,aspecialinstanceoftheobjecttheseproblemsarelessserious.Ifthecallerforgetstocheckthespecialreturnvalueandinvokesmethodsonthespecialinstance,themethodsinvokedstillhavethepossibilitytoimplementsomeexceptionorerrorhandling.Theclasshasthecontrolencapsulatedandcanthrowaspecialexceptionthatmaygivemoreinformationabouttheerrorcausedbytheprogrammaticmistakebythecallernotcheckingthespecialvalue.
JavaDocandcodecommentsThereisalsoanotherdifferencebetweenwhatwepresentedhereearlierandthelisting.Thisisthecommentingofthecode.Codecommentsarepartoftheprogram,whichareignored,filteredoutbythecompiler.Thesecommentsaresolelyforthosewhomaintainorusethecode.
InJava,therearetwodifferentcomments.Thecodeenclosedbetween/*and*/arecomments.Thestartandtheendofthecommentdonotneedtobeonthesameline.Theothertypeofcommentstartswiththe//charactersandendsattheendoftheline.
Todocumentthecode,theJavaDoctoolcanbeused.JavaDocisaspecialtoolthatreadsthesourcecodeandextractsHTMLdocumentationabouttheclasses,methods,fields,andotherentitiesthathaveacommentstartingwiththe/**characters.ThedocumentationwillcontaintheJavaDoccommentsinaformattedwayandalsotheinformationthatisextractedfromtheprogramcode.
ThedocumentationalsoappearsasonlinehelpintheIDEwhenyoumovethemouseoveramethodcallorclassname,ifthereisany.TheJavaDoccommentcancontainHTMLcodes,butitgenerallyshouldnot.Ifreallyneeded,youcanuse<p>tostartanewparagraphorthe<pre>tagstoincludesomepreformattedcodesampleintothedocumentation,butnothingmoregivesrealbenefit.Documentationshouldbeasshortaspossibleandcontainasfewformattingaspossible.
TherearespecialtagsthatappearintheJavaDocdocumentation.TheseareprefilledbytheIDEswhenyoustarttotypeaJavaDocas/**andthenpressEnter.Theseareinsidethecommentandstartwiththe@character.Thereareapredefinedsetoftags:@author,@version,@param,@return,@exception,@see,@since,@serial,[email protected]@paramand@return.Theyareusedtodescribethemethodargumentsandthereturnvalue.Althoughwearenotthereyet,let'speekaheadtotheguessMatchmethodfromtheGuesserclass.
/**
*Aguessmatchesifallrowsinthetablematchestheguess.
*
*@paramguesstomatchagainsttherows
*@returntrueifallrowsmatch
*/
protectedbooleanguessMatch(Color[]guess){
for(Rowrow:table.rows){
if(!row.guessMatches(guess)){
returnfalse;
}
}
returntrue;
}
ThenameoftheparameterisautomaticallygeneratedbytheIDE.Whenyoucreatethedocumentation,writesomethingthatismeaningfulandnottautology.Manytimes,noviceprogrammersfeeltheurgetowriteJavaDoc,andthatsomethinghastobewrittenabouttheparameters.Theycreatedocumentationslikethis:
*@paramguessistheguess
Really?Iwouldneverhaveguessed.Ifyoudonotknowwhattowritetheretodocumenttheparameter,it
mayhappenthatyouwerechoosingthenameoftheparameterexcellent.Thedocumentationofourprecedingexamplewilllookasfollows:
Focusonwhatthemethod,class,andinterfacedoesandhowitcanbeused.Donotexplainhowitworksinternally.JavaDocisnottheplacefortheexplanationofthealgorithmorthecoding.Itisusedtohelpusethecode.However,ifsomebodyhappenstoexplainhowamethodworks,itisnotadisaster.Commentscaneasilybedeleted.
Thereis,however,acommentthatisworsethannothing:outdateddocumentationthatisnotvalidanymore.Whenthecontractoftheelementhaschanged,butthedocumentationdoesnotfollowthechangeandismisleadingtheuserwhowantstocallthemethod,interface,orclasswhateverwillfaceseriousbugsandwillbeclueless.
Fromnowon,JavaDoccommentswillnotbelistedinprinttosavetrees,andelectronsintheeBookversion,buttheyarethereintherepositoryandcanbeexamined.
RowNow,wehaveColorsandeveninstancesifweneedhavingaColorManager.ThisisthetimetostoreColorsinRows.TheRowclassisabitlonger,butnottoocomplex.
packagepackt.java9.by.example.mastermind;
importjava.util.Arrays;
publicclassRow{
finalColor[]positions;
privateintmatchedPositions;
privateintmatchedColors;
ARowcontainsthreefields.Oneisthepositionsarray.EachelementofthearrayisaColor.ThematchedPositionsisthenumberofpositionsthatarematchedandmatchedColorsisthenumberofcolorsthatmatchacolorinthehiddenrowbutisnotonthepositionasinthehiddenrow.
publicstaticfinalRownone=newRow(Guesser.none);
ThenoneisaconstantthatcontainsaspecialRowinstancethatwewillusewhereverwewouldusenull.Theconstructorgetsthecolorsinanarraythatshouldbeintherow.
publicRow(Color[]positions){
this.positions=Arrays.copyOf(positions,positions.length);
}
Theconstructormakesacopyoftheoriginalarray.Thisisanimportantcodethatwewillexamineabit.Let'sreiteratethatJavapassesargumentsbyvalue.Itmeansthatwhenyoupassanarraytoamethod,youwillpassthevalueofthevariablethatholdsthearray.However,anarrayinJavaisanObjectjustaswellasanythingelse(exceptprimitiveslikeint).Therefore,whatthevariablecontainsisareferencetoanobjectthathappenstobeanarray.Ifyouchangetheelementsofthearray,youactuallychangetheelementsoftheoriginalarray.Thearrayreferenceiscopiedwhentheargumentpasses,butthearrayitself,andtheelements,arenot.
Thejava.util.Arraysutilityclassprovidesalotofusefultools.WecaneasilycodethearraycopyinginJavabutwhytoreinventthewheel?Inadditiontothat,arraysarecontinuousareaofmemorythatcanveryeffectivelybecopiedfromoneplacetoanotherusinglow-levelmachinecode.ThecopyOfmethodthatweinvokecallsthemethodSystem.arraycopywhichisanativemethodandassuchexecutesnativecode.
NotethatthereisnoguaranteethatArrays.copyOfinvokesthenativeimplementationsandthatthiswillbeextremelyfastincaseoflargearrays.TheveryversionIwastestinganddebuggingwasdoingitthatway,andwecanassumethatagoodJDKdoessomethingsimilar,effectiveandfast.
Afterwecopiedthearray,itisnotaproblemifthecallermodifiesthearraythatwaspassedtotheconstructor.Theclasswillhaveareferencetoacopythatwillcontainthesameelements.However,notethatifthecallerchangesanyoftheobjectsthatarestoredinthearray(notthereferenceinthearray,buttheobjectitselfthatisreferencedbyanarrayelement),thenthesameobjectismodified.Arrays.copyOfdoesnotcopytheobjectsthatarereferencedbythearray,onlythearrayelements.
Therowiscreatedalongwiththecolorsandthus,weusedafinalfieldfortheColorarray.Thematches,however,cannotbeknownwhenaRowiscreated.OneoftheplayerscreatestheRowandafterthat,theotherplayerwilltellthetwointvalues.Wedonotcreatetwosettersforthetwovalues,however,becausetheyarealwaysdefinedatthesametimeinthegametogether.
publicvoidsetMatch(intmatchedPositions,intmatchedColors){
if(matchedColors+matchedPositions>positions.length){
thrownewIllegalArgumentException(
"Numberofmatchescannotbemorethattheposition.");
}
this.matchedColors=matchedColors;
this.matchedPositions=matchedPositions;
}
ThesetMatchmethoddoesnotonlysetthevalues,butalsochecksthatthevaluesareconsistent.Thesumofthetwovaluescannotbemorethanthenumberofthecolumns.Thischeckensuresthatthecaller,whousestheAPIoftheRowclass,doesnotuseitinconsistently.IfthisAPIisusedonlyfrominsideourcode,thisassertionshouldnotbepartofthecode.Agoodcodingstyle,inthatcase,willensurethatthemethodisneverinvokedinconsistentlyusingunittests.WhenwecreateAPItouseoutofourcontrol,weshouldcheckthattheuseisconsistent.Failingtodoso,ourcodemaybehavejustweirdwhenusedinconsistently.Whenthecallersetsmatchestovaluesthatdonotmatchanypossibleguess,thegamemayneverfinishandthecallermayhaveahardtimefiguringoutwhatisgoingon.Thisfiguringoutprobablywillneedthedebugexecutionofourcode.
Ifwethrowanexceptioninthiscase,theprogramstopswherethebugis.Thereisnoneedtodebugthelibrary.
publicbooleanguessMatches(Color[]guess){
returnnrMatchingColors(guess)==matchedColors&&
nrMatchingPositions(guess)==matchedPositions;
}
Thenextmethoddecidesifaguess,givenasanargument,matchestheactualrow.Thismethodchecksthattheanswerstotheguessintherowcanbevalidifthecurrentguesswasinthehiddenrow.Theimplementationisfairlyshortandsimple.Aguessmatchesarowifthenumberofthecolorsmatchingandthenumberofpositionsmatchingarethesameasthenumbergivenintherow.Donotbeshytowriteshortmethods.Donotthinkthataone-linemethodthatessentiallycontainsonestatementisuseless.Whereverweusethismethod,wecouldalsowritetheexpression,whichisrightafterthereturnstatement,butwedonotfortworeasons.Thefirstandmostimportantreasonisthatthealgorithm,whichdecidesthatarowmatchesaguessbelongstotheimplementationoftheclassRow.Ifevertheimplementationchanges,theonlylocationwherethecodeistobechangedishere.Theotherreasonisalsoimportant,andthatisreadability.Inourcodebase,wecallthismethodfromabstractclassGuesser.Itcontainsanifstatementwiththefollowingexpression:
if(!row.guessMatches(guess)){
Woulditbemorereadableinthefollowingway:
if(!(nrMatchingColors(guess)==matchedColors&&nrMatchingPositions(guess)==matchedPositions)){
Iamcertainthatthemajorityoftheprogrammersunderstandtheintentionofthefirstversioneasier.IwouldevenrecommendimplementingthedoesNotMatchGuessmethodtoimprovethereadabilityofthecode
evenmore.
publicintnrMatchingColors(Color[]guess){
intcount=0;
for(inti=0;i<guess.length;i++){
for(intj=0;j<positions.length;j++){
if(i!=j&&guess[i]==positions[j]){
count++;
}
}
}
returncount;
}
Thenumberofmatchingcolorsisthatwhichappearsbothintherowandtheguess,butnotinthesameposition.Thedefinition,andhowwecalculateit,isfairlysimpleandunambiguousincasenocolorcanappeartwiceinthehiddenrow.Incaseacolormayappearmultipletimesinthehiddenrow,thisimplementationwillcountalloccurrencesofthatcolorintheguessasmanytimesasitappearsinthehiddenrow.Ifwe,forexample,haveahiddenRRGBrowandtheguessisbYRR,thecalculationwillsay4.Itisamatterofagreementbetweentheplayershowtheycountinthiscase.Theimportantaspectisthattheyusethesamealgorithm,whichshouldbetrueinourcase,becausewewillasktheprogramtoplaybothplayers.Aswewillprogramthecodeourselves,wecantrustthatitwillnotcheat.
publicintnrMatchingPositions(Color[]guess){
intcount=0;
for(inti=0;i<guess.length;i++){
if(guess[i]==positions[i]){
count++;
}
}
returncount;
}
CountingthecolorsthatareOK,andalsoonthepositionwheretheyaresupposedtobe,isevensimpler.
publicintnrOfColumns(){
returnpositions.length;
}
ThismethodtellsthenumberofcolumnsintheRow.ThismethodisneededintheGameclassthatcontrolstheflowofawholegame.AsthisclassisinthesamepackageasRow,itcanaccessthefieldpositions.Icreatedthecodetogetthenumberofcolumnsasrow.positions.length.Butthen,Iwasreadingthecodenextdayandtoldmyself:Thisisuglyandunreadable!WhatIaminterestedinhereisnotsomemysteriouspositions'length;itisthenumberofcolumns.AndthenumberofcolumnsistheresponsibilityoftheRowclassandnotthebusinessofanyotherclass.IfIstarttostorethepositionsinaList,whichdoesnothavelength(ithasmethodsize),itisthesoleresponsibilityofRowandshouldnotaffectanyothercode.So,IcreatedthenrOfColumnsmethodtoimprovethecode.
Therestoftheclasscontainssomemoreverysimplemethodsthatareneededonlytodisplaythegameandnotforthealgorithmtoplay:
publicintnrColumns(){
returnpositions.length;
}
publicColorposition(inti){
returnpositions[i];
}
publicintmatchedPositions(){
returnmatchedPositions;
}
publicintmatchedColors(){
returnmatchedColors;
}
}
Ifyouareapurist,youcanencapsulatethesemethodsintoaninnerclassnamedOutputorPrintandcallthemthroughafinalinstanceofitcreatedasafieldintheRowclass.ItisalsopossibletochangethevisibilityofthesefieldsfromprivatetoprotectedandimplementthesemethodsinaPrintableRowthatcanbeinstantiatedfromanalreadyexistingRowandimplementthesemethods.
ThefirstversionofPrintableRowwilllooklikethis:
publicclassPrintableRowextendsRow{
publicPrintableRow(Rowrow){
super(row.positions);
super.setMatch(row.matchedPositions,row.matchedColors);
}
//themethodsaredeletedfromtheprint...
}
Themethodsareexactlythesameasintheprecedingprint;theyarecutandpasted,orrathermoved,usingtheIDErefactoringsupportfromoneclasstotheother.
Whenyouwriteacode,pleaseneverusecopyandpaste.Howeveryoucanusecutandpastetomovecodefragmentsaround.Thedangerisinthecopypasteuse.Manydevelopersclaimthattheiruseofactualcopyandpasteisnotcopypasteprogramming.Theirreasoningisthattheychangethepastedcodesomuchthatithaspracticallynothingtodowiththeoriginalcode.Really?Inthatcasewhydidyouneedthecopiedcodewhenyoustartedthemodificationofit?Whynotstartfromscratch?ThatisbecauseifyouusetheIDE'scopyandpastefunctionalitythen,nomatterwhat,youdocopypasteprogramming.
ClassPrintableRowisprettyneatandseparatestheoutputconcernfromthecorefunctionality.Whenyouneedaninstance,itisnotaproblemthatyouhaveaRowinstancealreadyinhand.Theconstructorwillessentiallyclonetheoriginalclassandreturnaprintableversion.Whatbothersmeistheimplementationofthecloning.ThecodeintheconstructorcallsthesuperconstructorandthenamethodandalltheseworkwiththeoriginalfunctionalityoftheRowclass.TheyhavenothingtodowiththeprintabilitythatPrintableRowimplements.ThisfunctionalityactuallybelongstotheRowclass.Weshouldcreateaprotectedconstructorthatdoesthecloning:
protectedRow(RowcloneFrom){
this(cloneFrom.positions);
setMatch(cloneFrom.matchedPositions,cloneFrom.matchedColors);
}
TheconstructorofPrintableRowshouldsimplycallsuper(row)andthatisit.
Codeisneverfinishedandneverperfect.Inaprofessionalenvironment,programmersmanytimestendtofinishpolishingthecodewhenitisgoodenough.Thereisnocodethatcannotbemadebetter,butthereisadeadline.Thesoftwarehastobepassedontothetestersandusersandhastobeusedtohelpeconomy.Afterall,thatisthefinalgoal
ofaprofessionaldeveloper:haveacodethatsupportsthebusiness.Acodethatneverrunsisworthnothing.IdonotwantyoutothinkthattheexamplesthatIprovidedherewerecreatedperfectupfront.Thereasonforthatis(didyoureadcarefully?)becausetheyarenotperfect.AsIsaid,codeisneverperfect.WhenIfirstcreatedRow,itcontainedtheprintingmethodsinaninnerclass.Ididnotlikeit.Thecodewassmelly.So,IdecidedtomovethefunctionalitytotheRowclass.However,Istilldidnotlikethesolution.Then,Iwenttobed,slept,worked,andreturnedtoitafewdayslater.WhatIcouldnotcreatethedaybeforenowseemedobvious—thesemethodshavetobemovedtoasubclass.Nowcomesanotherdilemma.ShouldIpresentthisfinalsolutionorshouldIhaveherethedifferentversions?Insomecases,Iwilljustpresentthefinalversion.Inothercases,likethis,therearethingstolearnfromthedevelopmentstep.Inthesecases,Ipresentnotonlythecode,butpartofitsevolutiononhowitwascreated.IfyouwanttoseethosethatIdidnotdarepublishing,lookattheGithistory.Iadmit,sometimes,Icreatecodethatevenmakesmefacepalmadaylater.
TableTableisasimpleclassthathasonlyoneverysimplefunctionality.
publicclassTable{
finalColorManagermanager;
finalintnrColumns;
finalList<Row>rows;
publicTable(intnrColumns,ColorManagermanager){
this.nrColumns=nrColumns;
this.rows=newLinkedList<>();
this.manager=manager;
}
publicvoidaddRow(Rowrow){
rows.add(row);
}
}
Thereisonethingtomention,whichisnothingnew,butworthrepeating.Therowsvariableisdeclaredasfinalanditgetsthevalueintheconstructor.ThisisaList<Row>typevariable.Thefactthatitisfinalmeansthatitwillholdthesamelistobjectduringitslifetime.Thelength,members,andotherfeaturesofthelistmayandwillchange.Wewilladdnewrowstothislist.Finalobjectvariablesreferenceanobject,butitdoesnotguaranteethattheobjectitselfisimmutable.Itisonlythevariablethatdoesnotchange.
Whenyoudocodereviewandexplaintoyourcolleagueswhataclassdoes,andyoufindyourselfstartingtheexplanation"thisclassisverysimple"manytimes,itmeansthecodeisgood.Well,itmaybewronginotheraspects,buttheclass'granularityseemstobeokay.
GuesserGuesserandtheUniqueGuesserandGeneralGuessersubclassesarethemostinterestingclassesoftheprogram.Theyactuallyperformthetaskthatisthecoreofthegame.GivenaTablewithahiddenrow,theguesserhastocreatenewerandnewerguesses.
Todothis,aGuesserneedstogetaTablewhenitiscreated.Thisispassedasaconstructorargument.Theonlymethoditshouldimplementisguess,whichreturnsanewguessbasedonthetableandonitsactualstate.
Aswewanttoimplementaguesserthatassumesthatallcolorsinthehiddenrowaredifferent,andalsoonethatdoesnotmakethisassumption,wewillimplementthreeclasses.Guesserisanabstractclassthatimplementsonlythelogicthatisindependentfromtheassumptions.Thesemethodswillbeinheritedbybothactualimplementations:UniqueGuesserandGeneralGuesser.
Let'sgothroughtheactualcodeoftheclass:
packagepackt.java9.by.example.mastermind;
publicabstractclassGuesser{
protectedfinalTabletable;
privatefinalColorManagermanager;
publicGuesser(Tabletable){
this.table=table;
this.lastGuess=newColor[table.nrColumns];
this.manager=table.manager;
}
Thestateoftheguesseristhelastguessitmade.Althoughthisisonthelastrowofthetable,itismoreofaninternalmatteroftheguesser.Theguesserhasallthepossibleguesses,oneaftertheother;lastGuessistheonewhereitleftofflasttimeanditshouldcontinuefromtherewhenitisinvokedagain.
abstractprotectedvoidsetFirstGuess();
Settingthefirstguessverymuchdependsontheassumptionofcoloruniqueness.Thefirstguessshouldnotcontainduplicatedcolorsincasethehiddenrowdoesnot(atleastinourimplementation),whileGeneralGuesserisfreetoguessanytime,evenasfirstGuessallcolorstobethesame.
protectedfinalColor[]lastGuess;
publicstaticfinalColor[]none=newColor[]{Color.none};
Again,noneinthisclassisjustanobjectthatwetrytouseinsteadofnull,wheneverweneedtoreturnsomethingthatisareferencetoaGuessbutisnotreallyaguess.
protectedColor[]nextGuess(){
if(lastGuess[0]==null){
setFirstGuess();
returnlastGuess;
}else{
returnnextNonFirstGuess();
}
}
ThenextGuessmethodisaninternalmethodthatgeneratesthenextguess,whichjustcomesasweorderthe
possibleguesses.ItdoesnotcheckanythingagainsttheTable;itonlygeneratesthenextguessalmostwithoutthinking.Theimplementationonhowwedothefirstguessandhowwedotheconsecutiveguessesaredifferent.Thus,wewillimplementthesealgorithmsindifferentmethodsandinvokethemfromhere.
ThenextNonFirstGuessmethodrepresentsthenextguessinthespecialcasewhentheguessisnotthefirstone:
privateColor[]nextNonFirstGuess(){
inti=0;
booleanguessFound=false;
while(i<table.nrColumns&&!guessFound){
if(manager.thereIsNextColor(lastGuess[i])){
lastGuess[i]=manager.nextColor(lastGuess[i]);
guessFound=true;
}else{
lastGuess[i]=manager.firstColor();
i++;
}
}
if(guessFound){
returnlastGuess;
}else{
returnnone;
}
}
Lookbackafewpageswherewedetailedhowthealgorithmworks.Wemadethestatementthatthiswayofworkingisverymuchlikethewaywecountwithdecimalnumbers.Bynow,youhaveenoughJavaknowledgeandprogrammingskilltounderstandwhatthemethoddoes.Itismoreinterestingtoknowwhyitiscodedthatway.
Hint:asalways,tobereadable.
ThereisthetemptationtoeliminatetheguessFoundvariable.Woulditnotbesimplertoreturnfromthemiddleofthemethodwhenwefindtheblessedguesses?Ifwedid,therewouldbenoneedtochecktheguessFoundvaluebeforereturningnonevalue.Thecodewouldnotgetthereifwereturnedfromthemiddleoftheloop.
Yes,itwouldbesimplertowrite.But,wecreatecodetobereadableandnotwritable.Yes,butlesscodeismorereadable.Notinthiscase!Returningfromaloopdegradesthereadability.Nottomention,thereturnstatementsarescatteredaroundinthemethodatdifferentstagesofexecution.
privateColor[]nextNonFirstGuess(){
inti=0;
while(i<table.nrColumns){
if(manager.thereIsNextColor(lastGuess[i])){
lastGuess[i]=manager.nextColor(lastGuess[i]);
returnlastGuess;
}else{
lastGuess[i]=manager.firstColor();
i++;
}
}
returnnone;
}
Whensomebodywritesacodeoptimizedinthatway,itissimilartoatoddlerwhomakeshisfirststepsandthenlooksproudlyatthemother.Okayboy/girl,youaregreat.Nowgoonandstartwalking.When
youarethepostman,walkingwillbeboring.Thatwillbeyourprofession.So,slideasidetheprideandwriteboringcode.Professionalswriteboringcode.Won'titbeslow?
No!Itwillnotbeslow.Firstofall,itisnotslowuntiltheprofilerprovesthatthecodedoesnotmeetthebusinessrequirements.Ifitdoes,itisfastenough,nomatterhowslowitis.Slowisgoodaslongasitisokayforthebusiness.Afterall,JITshouldhavesometaskoptimizingthecodetorun.
ThenextmethodchecksiftheguessmatchesthepreviousguessesandtheirresultsontheTable:
privatebooleanguessMatch(Color[]guess){
for(Rowrow:table.rows){
if(!row.guessMatches(guess)){
returnfalse;
}
}
returntrue;
}
privatebooleanguessDoesNotMatch(Color[]guess){
return!guessMatch(guess);
}
AswehavetheguessmatchingalreadyimplementedintheclassRow,allwehavetodoisinvokethatmethodforeachrowinthetable.Ifallrowsmatch,thentheguesscanbegoodforthetable.Ifanyoftheformerguessesdonotmatch,thenthisguessgoesdownthedrain.
Aswecheckthenegatedexpressionofmatching,wecreatedanEnglishversionofthemethod.
Insituationslikethis,itcouldbeenoughtocreatetheguessDoesNotMatchversionofthemethod.However,thelogicalexecutionofthecodeismorereadableifthemethodisnotnegated.Therefore,itismoreerrorpronetowritetheguessDoesNotMatchmethodalone.Instead,wewillimplementtheoriginal,readableversionandtheauxmethodtobenothingmorethananegation.
Afteralltheauxmethods,hereweareimplementingthepublicmethodoftheGuesser.
publicRowguess(){
Color[]guess=nextGuess();
while(guess!=none&&guessDoesNotMatch(guess)){
guess=nextGuess();
}
if(guess==none){
returnRow.none;
}else{
returnnewRow(guess);
}
}
}
ItjusttakesthenextGuessandagainandagainuntilitfindsonethatmatchesthehiddenrow,orthereisnomoreguess.Ifitfindsaproperguess,itencapsulateittoaRowobjectandreturnitsothatitcanlaterbeaddedtotheTablebytheGameobjects.
UniqueGuesserClassUniqueGuesserhastoimplementsetFirstGuess(allconcreteclassesextendinganabstractclassshouldimplementtheabstractmethodoftheparent)anditcanandwilloverridetheprotectednextGuessmethod:
packagepackt.java9.by.example.mastermind;
importjava.util.HashSet;
importjava.util.Set;
publicclassUniqueGuesserextendsGuesser{
publicUniqueGuesser(Tabletable){
super(table);
}
@Override
protectedvoidsetFirstGuess(){
inti=lastGuess.length-1;
for(Colorcolor=table.manager.firstColor();
i>=0;
color=table.manager.nextColor(color)){
lastGuess[i--]=color;
}
}
ThesetFirstGuessmethodselectsthefirstguessinsuchawaythatanypossiblecolorvariationsthatcomeafterthefirstonecreatetheguessesoneaftertheotherifwefollowthealgorithm.
TheauxisNotUniquemethodreturnstrueiftheguesscontainsduplicatecolors.Itisnotinterestingtoseehowmany.Ifallcolorsarethesame,oronlyonecolorappearstwice,itdoesnotmatter.Theguessisnotuniqueanddoesnotfitourguesser.Thismethodjudgesthat.
privatebooleanisNotUnique(Color[]guess){
finalSet<Color>alreadyPresent=newHashSet<>();
for(Colorcolor:guess){
if(alreadyPresent.contains(color)){
returntrue;
}
alreadyPresent.add(color);
}
returnfalse;
}
Todothis,itusesaSet,andanytimeanewcolorisfoundintheguessarray,thecolorisstoredintheset.Ifthesetcontainsthecolorwhenwefinditinthearray,itmeansthatthecolorwasalreadyusedbefore;theguessisnotunique.
@Override
protectedColor[]nextGuess(){
Color[]guess=super.nextGuess();
while(isNotUnique(guess)){
guess=super.nextGuess();
}
returnguess;
}
TheoverridingnextGuessmethodissimple.ItasksthesupernextGuessimplementationtomakeguessesbutthrowsawaythosethatitdoesnotlike.
packagepackt.java9.by.example.mastermind;<br/><br/>publicclassGeneralGuesserextendsGuesser{<br/><br/>publicGeneralGuesser(Tabletable){super(table);}<br/><br/>@Override<br/>protectedvoidsetFirstGuess(){<br/>inti=0;<br/>for(Colorcolor=table.manager.firstColor();<br/>i<lastGuess.length;){<br/>lastGuess[i++]=color;<br/>}<br/>}<br/><br/>}
SettingthelastGuessitjustputsthefirstcoloronallcolumns.Guesscouldnotbesimpler.EverythingelseisinheritedfromtheabstractclassGuesser.
packagepackt.java9.by.example.mastermind;<br/><br/>publicclassGame{<br/><br/>finalTabletable;<br/>finalprivateRowsecretRow;<br/>booleanfinished=false;<br/><br/>publicGame(Tabletable,Color[]secret){<br/>this.table=table;<br/>this.secretRow=newRow(secret);<br/>}<br/><br/>publicvoidaddNewGuess(Rowrow){<br/>if(isFinished()){<br/>thrownewIllegalArgumentException(<br/>"Youcannotguessonafinishedgame.");<br/>}<br/>finalintpositionMatch=secretRow.<br/>nrMatchingPositions(row.positions);<br/>finalintcolorMatch=secretRow.<br/>nrMatchingColors(row.positions);<br/>row.setMatch(positionMatch,colorMatch);<br/>table.addRow(row);<br/>if(positionMatch==row.nrOfColumns()){<br/>finished=true;<br/>}<br/>}<br/><br/>publicbooleanisFinished(){<br/>returnfinished;<br/>}<br/>}
ThinkaboutwhatIwroteearlieraboutshortmethods,andwhenyoudownloadthecodefromGitHubtoplaywithit,trytomakeitlookmorereadable.Youcan,perhaps,createanduseamethodnamedbooleanitWasAWinningGuess(intpositionMatch).
CreatinganintegrationtestWehavecreatedunittestsinthepreviouschapterandthereareunittestsforthefunctionalitiesimplementedintheclassesofthischapteraswell.Wewilljustnotprinttheseunittestshere.Insteadoflistingtheunittests,wewilllookatanintegrationtest.
Integrationtestsneedtheinvocationofmanyclassesworkingtogether.Theycheckthatthefunctionalitycanbedeliveredbythewholeapplication,oratleastalargerpartoftheapplication,anddonotfocusonasingleunit.Theyarecalledintegrationtestsbecausetheytesttheintegrationbetweenclasses.TheclassesaloneareallOK.Theyshouldnothaveanyproblemasitwasalreadyverifiedbytheunittests.Integrationfocusesonhowtheyworktogether.
IfwewanttotesttheGameclass,wewilleitherhavetocreatemocksthatmimicthebehavioroftheotherGameclasses,orwewilljustwriteanintegrationtest.Technically,anintegrationtestisverysimilartoaunittest.Manytimes,theverysameJUnitframeworkisusedtoexecutetheintegrationtests.Thisisthecasefortheintegrationtestofthisgame.
Thebuildtool,however,needstobeconfiguredtoexecutetheintegrationtestsonlywhenitisrequired.Usually,integrationtestexecutionsneedmoretime,andsometimesresources,suchasexternaldatabasethatmaynotbeavailableateachandeverydeveloperdesktop.Unittestsruneverytimetheapplicationiscompiledsotheyhavetobefast.Toseparatetheunitandintegrationtests,therearedifferenttechniquesandconfigurationoptions,butthereisnosuchmoreorlessde-factostandardlikethedirectorystructureintroducedbyMaven(lateradaptedbyGradle).
Inourcase,theintegrationtestdoesnotneedanyextraresourceanddoesnottakeenormoustimetorun.Itplaysagamefromthestarttotheendandplaystheroleofboththeplayers.Itisverymuchlikesomebodyplayingchesswiththemselves,makingastepandthenturningthetable.
Theaimofthiscodeistwofold.Ononehand,wewanttoseethatthecoderunsandplaysawholegame.Ifthegamefinishes,thenitisjustOK.Thisisaveryweakassertionandrealintegrationtestsperformlotsofassertions(onetesttestsonlyoneassertionthough).Wewillfocusontheotheraim—deliversomejoyandvisualizethegameontheconsoleintextformatsothatthereaderdoesnotgetbored.
Todothat,wewillcreateautilityclassthatprintsoutacolorandassignsletterstotheColorinstancesonthefly.ThisisthePrettyPrintRowclass.Thereareseverallimitationsinthisclassthatwehavetotalkaboutafterwelookatthecode.I'dsaythatthiscodeishereonlytodemonstratewhatnottodo,toestablishsomereasoningforthenextchapter,andwhyweneedtorefactorthecodewecreatedinthisone.
packagepackt.java9.by.example.mastermind;
importjava.util.HashMap;
importjava.util.Map;
publicclassPrettyPrintRow{
privatestaticfinalMap<Color,Character>
letterMapping=newHashMap<>();
privatestaticfinalStringletters="RGBYWb";
privatestaticintcounter=0;
privatestaticcharcolorToChar(Colorcolor){
if(!letterMapping.containsKey(color)){
letterMapping.put(color,letters.charAt(counter));
counter++;
}
returnletterMapping.get(color);
}
Thisistheheartofthisclass.Whenacoloristobeprinted,itgetsaletterassignedunlessitalreadyhasone.AstheMapcontainingtheassignmentsineachandeverygamethatisrunningintheJVMwillusethesamemapping,anewGameisstarted.ItallocatesnewColorsandwillsoonrunoutofthesixcharactersthatweallocatedhereintheStringconstant.
IftheGameinstancesarerunparallel,thenweareinevenmoretrouble.Theclassisnotthreadsafeatall.IftwothreadsconcurrentlycallthecolorToCharmethodforthesameColorinstance,(whichisnotlikelybecauseeachGameusesitsowncolor,butnotethatnotlikelyinprogrammingisverymuchlikeafamouslastwordsquoteonatombstone)thenboththreadsmayseeatthesametimethatthereisnoletterassignedtothecolorandbothwillassigntheletter(thesameletterortwodifferentletters,basedonluck)andincreasethecounteronceortwice.Atleast,whatwecansayisthattheexecutionisnondeterministic.
YoumayrecallthatIsaidviolatingthehashcontractisthesecondmostdifficulttofindbugaftermultithreadissues.Suchanondeterministiccodeisexactlythat:amultithreadissue.Thereisnoprizetofindthemostdifficultbug.Whentheapplicationdoesnotrun,andabugaffectstheproductionsystemforhoursordays,nobusinesspersonwillbehappy,andtheywillnotbeamazedafteryoufindthebug.Itmaybeanintellectualchallenge,buttherealvalueisnotcreatingthebugsinthefirstplace.
Asasummary,thiscodecanonlybeusedonceinaJVMbyasinglethread.Forthischapter,itisgood,thoughasmellyandshamefulcode,butitwillbeagoodexampleforthenextchapter,inwhichwewillsee,howtorefactortheapplicationsothatitwillnotneedsuchahackingtoprintoutthecolors.
CodesmellisatermmintedbyKentBack,accordingtoMartinFowler(http://martinfowler.com/bliki/CodeSmell.html).Itmeansthatsomecodelooksnotgood,norapparentlybad,butsomeconstructsmakethefeelinginthedeveloperthatitmaynotbegood.Asitisdefinedonthewebpage,"Acodesmellisasurfaceindicationthatusuallycorrespondstoadeeperprobleminthesystem."Thetermiswidelyacceptedandusedinsoftwaredevelopmentforthelast10years.
Therestofthecodeisplainandsimple:
publicstaticStringpprint(Rowrow){
Stringstring="";
PrintableRowpRow=newPrintableRow(row);
for(inti=0;i<pRow.nrOfColumns();i++){
string+=colorToChar(pRow.position(i));
}
string+="";
string+=pRow.matchedPositions();
string+="/";
string+=pRow.matchedColors();
returnstring;
}
Theintegrationtest,orratherthedemonstrationcode(asitdoesnotcontainanyassertionsotherthanitrunswithoutexception),definessixcolorsandfourcolumns.Thisisthesizeoftheoriginalgame.It
createsacolormanager,andthenitcreatesatableandasecret.Thesecretcouldbejustanyrandomcolorselectionfromthesixcolorsthatisavailable(thereare360differentpossibilitiestestedintheUniqueGuesserTestunittestavailablefromGitHub).AsweknowthattheGuesserimplementationstartsfromoneendofthecolorsetandcreatesthenewguessessystematically,wewanttosetasecretthatitwillguessthelast.Thisisnotbecauseweareevil,butratherbecausewewanttoseethatourcodereallyworks.
ThedirectorystructureofthecodeisverysimilartotheoneweusedincaseoftheMavenbuildtool,ascanbeseenonthefollowingscreenshotcreatedonaWindowsmachine:
Thesourcecodeisunderthedirectorysrcandthemainandtestsourcecodefilesareseparatedintotwosubdirectorystructures.ThecompiledfileswillbegeneratedinthedirectorybuildwhenweuseGradle.Thecodeoftheintegrationtestclassisthefollowing:
packagepackt.java9.by.example.mastermind.integration;
importorg.junit.Assert;
importorg.junit.Test;
importpackt.java9.by.example.mastermind.*;
publicclassIntegrationTest{
finalintnrColors=6;
finalintnrColumns=4;
finalColorManagermanager=newColorManager(nrColors);
privateColor[]createSecret(){
Color[]secret=newColor[nrColumns];
intcount=0;
Colorcolor=manager.firstColor();
while(count<nrColors-nrColumns){
color=manager.nextColor(color);
count++;
}
for(inti=0;i<nrColumns;i++){
secret[i]=color;
color=manager.nextColor(color);
}
returnsecret;
}
@Test
publicvoidtestSimpleGame(){
Tabletable=newTable(nrColumns,manager);
Color[]secret=createSecret();
System.out.println(
PrettyPrintRow.pprint(newRow(secret)));
System.out.println();
Gamegame=newGame(table,secret);
Guesserguesser=newUniqueGuesser(table);
while(!game.isFinished()){
Rowguess=guesser.guess();
if(guess==Row.none){
Assert.fail();
}
game.addNewGuess(guess);
System.out.println(PrettyPrintRow.pprint(guess));
}
}
}
TheeasiestwaytorunthetestisstartitfrominsidetheIDE.WhentheIDEimportstheprojectbasedonthebuildfile,beitaMavenpom.xmlorGradlebuild.gradle.IDEusuallyprovidesarunbuttonormenutostartthecode.Runningthegamewillprintoutthefollowingpieceofcodethatweworkedsohardoninthischapter:
RGBY0/0
GRWb0/2
YBbW0/2
BYGR0/4
RGYB2/2
RGBY4/0
Summary
Inthischapter,weprogrammedatablegame:Mastermind.Wenotonlyprogrammedthemodelofthegame,butalsocreatedanalgorithmthatcanguess.WerevisitedsomeOOprinciplesanddiscussedwhythemodelwascreatedthewayitwas.Whilewecreatedthemodelofthegame,whichwewillrefineinthenextchapter,youhavelearnedaboutJavacollections,whatanintegrationtestis,andhowtocreateJavaDoc.
ExtendingtheGame-RunParallel,RunFasterInthischapter,wewillextendtheMastermindgame.Asitisnow,itcanguessthesecretthatwashiddenandalsohidethepegs.Thetestcodecanevendobothatthesametime.Itcanplayagainstitselfleavingusonlywiththefunofprogramming.Whatitcannotdoismakeuseofalltheprocessorsthatwehaveintoday'snotebooksandservers.Thecoderunssynchronousandutilizesonlyasingleprocessorcore.
Wewillalterthecodeextendingtheguessingalgorithmtosliceuptheguessingintosubtasksandexecutethecodeinparallel.Duringthis,wewillgetacquaintedwithJavaconcurrentprogramming.Thiswillbeahugetopicwithmanysubtlecornersandcaveatslurkinginthedark.Wewillgetintothosedetailsthatarethemostimportantandwillformafirmbaseforfurtherstudieswheneveryouneedconcurrentprograms.
Astheoutcomeofthegameisthesameasitwas,onlyfaster,wehavetoassesswhatfasteris.Todothat,wewillutilizeanewfeatureintroducedinJava9:microbenchmarkingharness.
Inthischapter,wewillcoverthefollowingtopics:
Themeaningofprocesses,threadsandfibersMultithreadinginJavaIssueswithmultithreadprogrammingandhowtoavoidthemLocking,synchronization,andblockingqueuesMicrobenchmarking
HowtomakeMastermindparallelTheoldalgorithmwastogothroughallthevariationsandtrytofindaguessthatmatchesthecurrentstateofthetable.Assumingthatthecurrentlyexaminedguessisthesecret,willwegetthesameanswersfortheguessesthatarealreadyonthetableastheanswersareactuallyonthetable?Ifyes,thenthecurrentguesscanbethesecret,anditisjustasgoodaguessasanyotherguesses.
Amorecomplexapproachcanimplementthemin-maxalgorithm(https://en.wikipedia.org/wiki/Minimax).Thisalgorithmdoesnotsimplygetthenextpossibleguessbutalsolooksatallthepossibleguessesandselectstheonethatshortenstheoutcomeofthegamethemost.Ifthereisaguessthatcanbefollowedbythreemoreguessesintheworstcase,andthereisanotherforwhichthisnumberisonlytwo,thenmin-maxwillchoosethelatter.Itisagoodexercisefortheinterestedreaders.Inthecaseofthesixcolorsandfourcolumnsforthepegs,themin-maxalgorithmsolvesthegameinnomorethan5steps.Thesimplealgorithmweimplementedalsosolvesthegamein5steps.However,wedonotgointhatdirection.
Instead,wewanttohaveaversionofthegamethatutilizesmorethanoneprocessor.Howcanyoutransformthealgorithmintoaparallelone?Thereisnosimpleanswertothis.Whenyouhaveanalgorithm,youcananalyzethecalculationsandpartsofthealgorithm,andyoucantrytofinddependencies.IfthereissomecalculationBthatneedsthedata,whichistheresultofanothercalculationA,thenitisobviousthatAcanonlybeperformedwhenBisready.Iftherearepartsofthealgorithmthatdonotdependontheoutcomeoftheothers,thentheycanbeexecutedinparallel.
Forexample,thequick-sorthastwomajortasks:partitioningandthensortingofthetwoparts.Itisfairlyobviousthatthepartitioninghastofinishbeforewestartsortingthetwopartitionedparts.However,thesortingtasksofthetwopartsdonotdependoneachother,theycanbedoneindependently.Youcangivethemtotwodifferentprocessors.Onewillbehappysortingthepartcontainingthesmallerelements;theotheronewillcarrytheheavier,largerones.
IfyouturnthepagesbacktoChapter3,OptimizingtheSort-MakingCodeProfessionalwhereweimplementedquick-sortinanon-recursiveway,youcanseethatwescheduledsortingtasksintoastackandthenperformedthesortingbyfetchingtheelementsfromthestackinawhileloop.Insteadofexecutingthesortrightthereinthecoreoftheloop,wecouldpassthetasktoanasynchronousthreadtoperformitandgobackforthenextwaitingtask.Wejustdonotknowhow.Yet.Thatiswhywearehereinthischapter.
Processors,threads,andprocessesarecomplexandabstractthingsandtheyarehardtoimagine.Differentprogrammershavedifferenttechniquestoimagineparallelprocessingandalgorithms.IcantellyouhowIdoitbutitisnotaguaranteethatthiswillworkforyou.Othersmayhavedifferenttechniquesintheirmind.Asamatteroffact,IjustrealizedthatasIwritethis,Ihaveactuallynevertoldthistoanyonebefore.Itmayseemchildish,butanyway,hereitgoes.
WhenIimaginealgorithms,Iimaginepeople.Oneprocessorisoneperson.Thishelpsmeovercomethefreakingfactthataprocessorcanmakebillionsofcalculationsinasecond.Iactuallyimagineabureaucratwearingabrownsuitanddoingthecalculations.WhenIcreateacodeforaparallelalgorithm,Iimaginemanyofthemworkingbehindtheirdesks.Theyworkaloneandtheydonottalk.Itisimportant
thattheydonottalktoeachother.Theyareveryformal.Whenthereisaneedforinformationexchange,theystandupwithapieceofpapertheyhavewrittensomethingon,andtheybringittoeachother.Sometimes,theyneedapieceofpaperfortheirwork.Thentheystandup,gototheplacewherethepaperis,takeit,bringitbacktotheirdesk,andgoonworking.Whentheyareready,theygobackandbringthepaperback.Ifthepaperisnottherewhentheyneedit,theyqueueupandwaituntilsomeonewhohasthepaperbringsitthere.
HowdoesithelpwithMastermind?
Iimagineabosswhoisresponsiblefortheguesses.Thereisatableonthewallintheofficewiththepreviousguessesandtheresultsforeachrow.Thebossistoolazytocomeupwithnewguessessohegivesthistasktosubordinates.Whenasubordinatecomesupwithaguess,thebosscheckswhethertheguessisvalidornot.Hedoesnottrustthesubordinates,andiftheguessisgood,hemakesitasanofficialguess,puttingitonthetablealongwiththeresult.
Thesubordinatesdelivertheguesseswrittenonsmallpost-itnotes,andtheyputtheminaboxonthetableoftheboss.Thebosslooksattheboxfromtimetotime,andifthereisanote,thebosstakesit.Iftheboxisfullandasubordinatewantstoputapaperthere,thesubordinatestopsandwaitsuntilthebosstakesatleastonenotesothatthereissomeroomintheboxforanewnote.Ifthesubordinatesqueueuptodepositguessesinthebox,theyallwaitfortheirtime.
Thesubordinatesshouldbecoordinated;otherwise,theywilljustcomeupwiththesameguesses.Eachofthemshouldhaveanintervalofguesses.Forexample,thefirstoneshouldchecktheguessesfrom1234upuntil2134,thesecondshouldcheckfrom2134upuntil3124,andsoon,ifwedenotethecolorswithnumbers.
Willthisstructurework?Commonsensesaysthatitwill.However,bureaucrats,inthiscase,aremetaphorsandmetaphorsarenotexact.Bureaucratsarehuman,evenwhentheydonotseemlikeitmuchmorethanthreadsorprocessors.Theysometimesbehaveextremelystrangely,doingthingsthatnormalhumansdon'treallydooften.However,wecanstillusethismetaphorifithelpsusimaginehowparallelalgorithmswork.
Wecanimaginethatthebossgoesonholidayanddoesnottouchtheheapofpaperpilinguponthetable.Wecanimaginethatsomeoftheworkersareproducingresultsmuchfasterthantheothers.Asthisisonlyimagination,thespeedupcanbe1000times(thinkofatime-lapsevideo).Imaginingthesesituationsmayhelpusdiscoverspecialbehaviorthatrarelyhappens,butmaycauseproblems.Asthethreadsworkinparallel,manytimessubtledifferencesmayinfluencethegeneralbehaviorgreatly.
Insomeearlyversion,asIcodedtheparallelMastermindalgorithm,thebureaucratsstartedworkingandfilledtheboxofthebosswithguessesbeforethebosscouldputanyofthemonthetable.Astherewerenoguessesonthetable,thebureaucratssimplyfoundallpossiblevariationsintheirintervalbeingapossiblygoodguess.Thebossgainednothingbythehelpoftheparallelhelpers;theyhadtoselectthecorrectonesfromallpossibleguesses,whiletheguesserswerejustidle.
Anothertime,thebureaucratswerecheckingguessesagainstthetablewhilethebosswasputtingaguessononeofthemcreatedbeforehand.Andsomeofthebureaucratsfreakedoutsayingthatitisnotpossible
tocheckaguessagainstatableifsomeoneischangingit.Moreprecisely,thecodeexecutinginonethread,threwConcurrentModificationExceptionwhentheListofthetablewasmodified.
Anothertime,Itriedtoavoidthetoofastworkofbureaucrats,andIlimitedthesizeoftheboxwheretheycouldputtheirpaperscontainingtheguesses.Whenthebossfinallyfoundthesecret,andthegamefinished,thebosstoldthebureaucratsthattheycouldgohome.Thebossdidthatbycreatingasmallpaperwiththeinstruction:youcangohome,andputitonthetablesofthebureaucrats.Whatdidthebureaucratsdo?Keptwaitingfortheboxtohavespaceforthepaper!(Untiltheprocesswaskilled.ThisiskindofequivalentonMacOSandonLinuxasendingtheprocessfromthetaskmanageronWindows.)Suchcodingerrorshappenand,toavoidasmanyaspossible,wehavetodoatleasttwothings.Firstly,wehavetounderstandhowJavamultithreadingworksandsecondly,haveacodeascleanaspossible.Forthesecond,wewillcleanupthecodeevenmoreandthenwewilllookathowtheparallelalgorithmdescribedearliercanbeimplementedinJava,runningontheJVMinsteadofutilizingbureaucrats.
RefactoringWhenwefinishedthepreviouschapter,wehadtheclassesoftheMastermindgamedesignedandcodedinaniceandperfectlyobjectorientedwaythatdidnotbreakanyoftheOOprinciples.Didwe?Absurd.Thereisnocode,exceptsometrivialexamples,thatcannotbemadetolooknicerorbetter.Usually,whenwedevelopcodeandfinishthecoding,itlooksgreat.Itworks,thetestsallrun,anddocumentationisready.Fromtheprofessionalpointofview,itreallyisperfect.Well,itisgoodenough.Thebigquestionthatwehavenottestedyetismaintainability.Whatisthecosttoalterthecode?
Thatisnotaneasyquestion,especiallybecauseitisnotadefiniteone.Altertowhat?Whatisthemodificationthatistobemadetothecode?Wedonotknowthatwhenwecreatethecodeinthefirstplace.Ifthemodificationistofixabug,thenitisobviousthatwedidnotknowthatbeforehand.Ifweknew,wewouldnothaveintroducedthebuginthefirstplace.Ifthisisanewfeature,thenthereisapossibilitythatthefunctionwasforeseen.However,usuallyitisnotthecase.Whenadevelopertriestopredictthefuture,andwhatfeaturestheprogramwillneedinthefuture,theyusuallyfail.Itisthetaskofthecustomertoknowthebusiness.Featuresneededaredrivenbythebusinessincaseofprofessionalsoftwaredevelopment.Afterall,thatiswhatitmeanstobeprofessional.
Eventhoughwedonotexactlyknowwhatneedstobealteredlaterinthecode,therearecertainthingsthatmaygivehintstoexperiencedsoftwaredevelopers.Usually,theOOcodeiseasiertomaintainthanthead-hoccode,andtherearecodesmellsthatonecanspot.Forexample,takealookatthefollowingcodelines:
while(guesser.guess()!=Row.none){
while(guesser.nextGuess()!=Guesser.none){
publicvoidaddNewGuess(Rowrow){
Color[]guess=super.nextGuess();
Wemaysensetheodorofsomethingstrange.(EachoftheselinesisinthecodeoftheapplicationaswefinisheditinChapter4,Mastermind-CreatingaGame.)ThereturnvalueoftheguessmethodiscomparedtoRow.none,whichisaRow.Then,wecomparethereturnvalueofnextGuesstoGuesser.none,whichshouldbeaGuesser.Whenweaddanewguesstosomething,weactuallyaddaRow.Finally,wecanrealizethatnextGuessreturnsaguessthatisnotanobjectwithitsowndeclaredclass.Aguessisjustanarrayofcolors.
ShouldweintroduceanotherlayerofabstractioncreatingaGuessclass?Willitmakethecodemoremaintainable?Orwillitonlymakethecodemorecomplex?Itisusuallytruethatthelesscodelineswehave,thelesspossibilitywehaveforbugs.However,sometimes,thelackofabstractionwillmakethecodecomplexandtangled.Whatisthecaseinthissituation?Howcanwedecidethatgenerally?
Themoreexperienceyouhave,theeasieryouwilltellbylookingatthecodeandacutelyknowingwhatmodificationsyouwanttomake.Manytimes,youwillnotbothermakingthecodemoreabstract,andmanyothertimes,youwillcreatenewclasseswithouthesitation.Whenindoubt,docreatethenewclassesandseewhatcomesout.Theimportantthingisnottoruinthealreadyexistingfunctionality.Youcandothatonlyifyouhavesufficientunittests.
Whenyouwanttointroducesomenewfunctionalityorfixabug,butthecodeisnotappropriate,youwillhavetomodifyitfirst.Whenyoumodifythecodesothatthefunctionalitydoesnotchange,theprocessis
namedrefactoring.Youchangeasmallpartofthecodeinalimitedtime,andthenyoubuildit.Ifitcompilesandallunittestsrun,thenyoucangoon.Thehintistorunthebuildfrequently.Itislikebuildinganewroadnearanexistingone.Onceineveryfewmiles,youshouldmeettheoldline.Failingtodoso,youwillendupsomewhereinthemiddleofthedesertinatotallywrongdirection,andallyoucandoisreturntothestartingpoint—youroldto-be-refactoredcode.Effortwasted.
Itisnotonlythesafetythatadvisesustorunthebuildfrequently,itisalsotimelimitation.Refactoringdoesnotdirectlydeliverrevenue.Thefunctionalityoftheprogramistieddirectlytoincome.Nobodywillpayusforinfiniterefactoringwork.Refactoringhastostopsometimeanditisusuallynotthetimewhenthereisnothingtoberefactoredanymore.Thecodewillneverbeperfect,butyoumaystopwhenitisgoodenough.And,manytimes,programmersareneversatisfiedwiththequalityofthecode,andwhentheyarestoppedbysomeexternalfactor(usuallycalledprojectmanager),thecodeshouldcompileandtestsshouldrunsothatthenewfeatureandbugfixingcanbeperformedontheactualcodebase.
Refactoringisahugetopicandtherearemanytechniquesthatcanbefollowedduringsuchanactivity.ItissocomplexthatthereisawholebookaboutitbyMartinFowler(http://martinfowler.com/books/refactoring.html).
Inourcase,themodificationwewanttoapplytoourcodeistoimplementaparallelalgorithm.ThefirstthingwewillmodifyistheColorManager.Whenwewantedtoprintguessesandrowsontheterminal,wehadtoimplementsomebadtricks.Whynothavecolorimplementationsthatcanbeprinted?WecanhaveaclassthatextendstheoriginalColorclassandhasamethodthatreturnssomethingthatrepresentsthatcolor.Doyouhaveanycandidatenameforthatmethod?ItisthetoStringmethod.ItisimplementedintheObjectclassandanyclasscanfreelyoverrideit.Whenyouconcatenateanobjecttoastring,automatictypeconversionwillcallthismethodtoconverttheobjecttoString.Bytheway,itisanoldtricktouse""+objectinsteadofobject.toString()toavoidnullpointerexception.Needlesstosay,wedonotusetricks.ThetoStringmethodisalsoinvokedbytheIDEswhenthedebuggerwantstodisplaythevalueofsomeobject,soitisgenerallyrecommendedtoimplementtoStringiffornothingelse,thentoeasedevelopment.IfwehaveaColorclassthatimplementstoString,thenthePrettyPrintRowclassbecomesfairlystraightforwardandtricksless:
packagepackt.java9.by.example.mastermind;
publicclassPrettyPrintRow{
publicstaticStringpprint(Rowrow){
Stringstring="";
PrintableRowpRow=newPrintableRow(row);
for(inti=0;i<pRow.nrOfColumns();i++){
string+=pRow.pos(i);
}
string+="";
string+=pRow.full();
string+="/";
string+=pRow.partial();
returnstring;
}
}
Weremovedtheproblemfromtheprintingclass,butyoumayarguethattheissueisstillthere,andyouareright.Manytimes,whenthereisaprobleminaclassdesign,thewaytothesolutiontomovetheproblemfromtheclasstoanother.Ifitisstillaproblemthere,thenyoumaysplitthedesignmoreandmoreand,atthelaststage,youwillrealizethatwhatyouhaveisanissueandnotaproblem.
ToimplementaLetteredColorclassisalsostraightforward:
packagepackt.java9.by.example.mastermind.lettered;
importpackt.java9.by.example.mastermind.Color;
publicclassLetteredColorextendsColor{
privatefinalStringletter;
publicLetteredColor(Stringletter){
this.letter=letter;
}
@Override
publicStringtoString(){
returnletter;
}
}
Again,theproblemwaspushedforward.But,inreality,thisisnotaproblem.ItisanOOdesign.PrintingisnotresponsibleforassigningStringtocolorsfortheirrepresentation.Andthecolorimplementationitselfisalsonotresponsibleforthat.Theassignmenthastobeperformedwherethecolorismade,andthentheStringhastobepassedtotheconstructoroftheLetteredColorclass.ThecolorinstancesarecreatedinColorManagersowehavetoimplementthisintheColorManagerclass.Ornot?WhatdoesColorManagerdo?Itcreatesthecolorsand...
Whenyoucometoanexplanationordescriptionofaclassthatliststhefunctionalities,youmayimmediatelyseethatthesingleresponsibilityprinciplewasignored.ColorManagershouldmanagethecolors.Managingisprovidingawaytogetthecolorsinadefiniteorderandgettingthefirstandthenextwhenweknowonecolor.Weshouldimplementtheotherresponsibility—thecreationofacolorinaseparateclass.
Aclassthathasthesolefunctionalitytocreateaninstanceofanotherclassiscalledfactory.Thatisalmostthesameasusingthenewoperatorbutunlikenew,thefactoriescanbeusedmoreflexibly.Wewillseethatimmediately.TheColorFactoryinterfacecontainsasinglemethod,asfollows:
packagepackt.java9.by.example.mastermind;
publicinterfaceColorFactory{
ColornewColor();
}
Interfacesthatdefineonlyonemethodarenamedfunctionalinterfacesbecausetheirimplementationcanbeprovidedasalambdaexpressionattheplacewhereyouwoulduseanobjectthatisaninstanceofaclasswhichimplementsthefunctionalinterface.TheSimpleColorFactoryimplementationcreatesthefollowingColorobjects:
packagepackt.java9.by.example.mastermind;
publicclassSimpleColorFactoryimplementsColorFactory{
@Override
publicColornewColor(){
returnnewColor();
}
}
Itisverymuchlikehowwecreateaninterface,andthenanimplementation,insteadofjustwritingnewColor()inthecodeinColorManager.LetteredColorFactoryisabitmoreinteresting:
packagepackt.java9.by.example.mastermind.lettered;
importpackt.java9.by.example.mastermind.Color;
importpackt.java9.by.example.mastermind.ColorFactory;
publicclassLetteredColorFactoryimplementsColorFactory{
privatefinalStringletters="0123456789ABCDEFGHIJKLMNOPQRSTVWXYZabcdefghijklmnopqrstvwxzy";
privateintcounter=0;
@Override
publicColornewColor(){
Colorcolor=newLetteredColor(letters.substring(counter,counter+1));
counter++;
returncolor;
}
}
Now,herewehavethefunctionalitythatassignsStringstotheColorobjectswhentheyarecreated.Itisveryimportantthatthecountervariablethatkeepstrackofthealreadycreatedcolorsisnotstatic.Thesimilarvariableinthepreviouschapterwasstaticanditmeantthatitcouldrunoutofcharactersasever-newerColorManagerscreatedtoomanycolors.ItactuallydidhappentomeduringtheunittestexecutionwhenthetestseachcreatedColorManagersandnewColorinstances,andtheprintingcodetriedtoassignnewletterstothenewcolors.ThetestswererunninginthesameJVMunderthesameclassloaderandtheunfortunatestaticvariablehadnocluewhenitcouldjuststartcountingfromzeroforthenewtests.Thedrawbackisthatsomebody,somewhere,hastoinstantiatethefactoryanditisnottheColorManager.ColorManageralreadyhasaresponsibilityanditisnottocreateacolorfactory.TheColorManagerhastogettheColorFactoryinitsconstructor:
packagepackt.java9.by.example.mastermind;
importjava.util.HashMap;
importjava.util.List;
importjava.util.Map;
publicclassColorManager{
finalprotectedintnrColors;
finalprotectedMap<Color,Color>successor=newHashMap<>();
privateColorfirst;
privatefinalColorFactoryfactory;
publicColorManager(intnrColors,ColorFactoryfactory){
this.nrColors=nrColors;
this.factory=factory;
createOrdering();
}
privateColor[]createColors(){
Color[]colors=newColor[nrColors];
for(inti=0;i<colors.length;i++){
colors[i]=factory.newColor();
}
returncolors;
}
privatevoidcreateOrdering(){
Color[]colors=createColors();
first=colors[0];
for(inti=0;i<nrColors-1;i++){
successor.put(colors[i],colors[i+1]);
}
}
publicColorfirstColor(){
returnfirst;
}
publicbooleanthereIsNextColor(Colorcolor){
returnsuccessor.containsKey(color);
}
publicColornextColor(Colorcolor){
returnsuccessor.get(color);
}
publicintgetNrColors(){
returnnrColors;
}
}
YoumayalsonoticethatIcouldnotresistrefactoringthecreateColorsmethodintotwomethodstofollowthesingleresponsibilityprinciple.
Now,thecodethatcreatesColorManagerhastocreateafactoryandpassittotheconstructor.Forexample,theunittest'sColorManagerTestclasswillcontainthefollowingmethod:
@Test
publicvoidthereIsAFirstColor(){
ColorManagermanager
=newColorManager(NR_COLORS,Color::new);
Assert.assertNotNull(manager.firstColor());
}
Thisisthesimplestwayevertoimplementafactorydefinedbyafunctionalinterface.Justnametheclassandreferencethenewoperatorlikeitwasamethodbycreatingamethodreference.
ThenextthingwewillrefactoristheGuessclass,which,actually,wedidnothavesofar.AGuessclasscontainsthepegsoftheguessandcancalculatethenumberoffull(coloraswellasposition)andpartial(colorpresentbutinwrongposition)matches,andcanalsocalculatethenextGuessthatcomesafterthisguess.ThisfunctionalitywasimplementedintheGuesserclasssofar,butthisisnotreallythefunctionalityforhowweselecttheguesseswhencheckingthealreadymadeguessesonthetable.Ifwefollowthepatternwesetupforthecolors,wemayimplementthisfunctionalityinaseparateclassnamedGuessManager,butasfornow,itisnotneeded.Again,thisisnotblackandwhite.
ItisimportanttonotethataGuessobjectcanonlybemadeatonce.Ifitisonthetable,theplayerisnotallowedtochangeit.IfwehaveaGuessthatisnotyetonthetable,itisstilljustaGuessidentifiedbythecolorsandordersofthepegs.AGuessobjectneverchangesafteritwascreated.Suchobjectsareeasytouseinmultithreadprogramsandarecalledimmutableobjects:
packagepackt.java9.by.example.mastermind;
importjava.util.Arrays;
importjava.util.HashSet;
importjava.util.Set;
publicclassGuess{
finalstaticpublicGuessnone=newGuess(newColor[0]);
finalprivateColor[]colors;
privatebooleanuniquenessWasNotCalculated=true;
privatebooleanunique;
publicGuess(Color[]colors){
this.colors=Arrays.copyOf(colors,colors.length);
}
Theconstructoriscreatingacopyofthearrayofcolorsthatwerepassed.AsaGuessisimmutable,thisisextremelyimportant.Ifwejustkeeptheoriginalarray,anycodeoutsideoftheGuessclasscouldalterthe
elementsofthearray,essentiallychangingthecontentofGuessthatisnotsupposedtobechanging:
publicColorgetColor(inti){
returncolors[i];
}
publicintnrOfColumns(){
returncolors.length;
}
/**
*CalculatethenextguessandreturnanewGuessobject.
*Theguessesareorderedintheorderofthecolorsas
*specifiedbythecolormanager.
*
*@parammanagerthatspecifiestheorderofthecolors
*canreturnthenextcolorafteronecolor.
*@returntheguessthatcomesafterthisguess.
*/
publicGuessnextGuess(ColorManagermanager){
finalColor[]colors=Arrays.copyOf(
this.colors,nrOfColumns());
inti=0;
booleanguessFound=false;
while(i<colors.length&&!guessFound){
if(manager.thereIsNextColor(getColor(i))){
colors[i]=manager.nextColor(colors[i]);
guessFound=true;
}else{
colors[i]=manager.firstColor();
i++;
}
}
if(guessFound){
returnnewGuess(colors);
}else{
returnGuess.none;
}
}
Inthismethod,westarttocalculatethenextGuessstartingwiththecolorarraythatiscontainedintheactualobject.Weneedaworkarraythatismodified,sowewillcopytheoriginal.Thefinalnewobjectcan,thistime,usethearrayweuseduringthecalculation,sothatwillneedaseparateconstructorthatdoesnotcreateacopy.Itispossibleextracode,butweshouldconsidermakingthatonlyifweseethatthatisthebottleneckinthecodeandwearenotsatisfiedwiththeactualperformance.
ThenextmethodjustchecksifthepassedGuesshasthesamenumberofcolorsastheactualone.Thisisjustasafetycheckusedbythenexttwomethodsthatcalculatethematches:
privatevoidassertCompatibility(Guessguess){
if(nrOfColumns()!=guess.nrOfColumns()){
thrownewIllegalArgumentException("Cannotcomparedifferentlengthguesses");
}
}
/**
*Countthenumberofcolorsthatarepresentontheguess
*butnotontheposwheretheyareintheotherguess.
*Ifthesamecolorisonmultiplepositiscounted
*foreachposonce.Forexamplethesecretis
*<pre>
*RGRB
*</pre>
*andtheguessis
*<pre>
*YRPR
*</pre>
*thenthismethodwillreturn2.
*
*@paramguessistheactualguessthatweevaluate
*@returnthenumberofgoodcolorsnotinpos
*/
publicintnrOfPartialMatches(Guessguess){
assertCompatibility(guess);
intcount=0;
for(inti=0;i<nrOfColumns();i++){
for(intj=0;j<nrOfColumns();j++){
if(i!=j&&
guess.getColor(i)==this.getColor(j)){
count++;
}
}
}
returncount;
}
/**
*Countthenumberofcolorsthatarecorrectandareinpos.
*
*@paramguessistheactualguessthatweevaluate
*@returnthenumberofcolorsthatmatchinpos
*/
publicintnrOfFullMatches(Guessguess){
assertCompatibility(guess);
intcount=0;
for(inti=0;i<nrOfColumns();i++){
if(guess.getColor(i)==this.getColor(i)){
count++;
}
}
returncount;
}
TheisUniquemethodchecksifthereisanycolormorethanonceintheGuess.AstheGuessisimmutable,itmaynothappenthataGuessisuniqueonetimeandnotuniqueatanothertime.Thismethodshouldreturnthesameresultwheneveritiscalledonaspecificobject.Becauseofthat,itispossibletocachetheresult.Thismethoddoesthis,savingthereturnvaluetoaninstancevariable.
Youmaysaythatthisisprematureoptimization.Yes,itis.Idecidedtodoitforonereason.Itisdemonstration,andbasedonthat,youcantrytomodifythenextGuessmethodtodothesame:
/**
*@returntrueiftheguessdoesnot
*containanycolormorethanonce
*/
publicbooleanisUnique(){
if(uniquenessWasNotCalculated){
finalSet<Color>alreadyPresent=newHashSet<>();
unique=true;
for(Colorcolor:colors){
if(alreadyPresent.contains(color)){
unique=false;
break;
}
alreadyPresent.add(color);
}
uniquenessWasNotCalculated=false;
}
returnunique;
}
Methodsthatreturnthesameresultforthesameargumentsarecalledidempotent.Cachingthereturnvalueforsuchamethodcanbeveryimportantifthemethodiscalledmanytimesandthecalculationisusingalotofresources.Whenthemethodhasarguments,theresultcachingisnotsimple.Theobjectmethodhastoremembertheresultforallargumentsthatwerealreadycalculated,andthisstoragehastobeeffective.Ifittakesmoreresourcestofindthestoredresultthanthecalculationofit,thentheuseofcachenotonly
usesmorememorybutalsoslowsdowntheprogram.Ifthemethodiscalledforseveralargumentsduringthelifetimeoftheobject,thenthestoragememorymayjustgrowtoolarge.Someoftheelementshavetobepurged—thosethatwillnotbeneededanymoreinthefuture.However,wecannotknowwhichelementsofthecachearenotneeded,sowewillhavetoguess.
Asyoucansee,cachingcangetcomplexveryfastand,todothatprofessionally,itisalmostalwaysbettertousesomereadilyavailablecacheimplementation.Thecachingweusehereisonlythetipoftheiceberg.Or,itisevenonlythesunshineglimpsingonit.
Therestoftheclassisfairlystandardandsomethingwehavetalkedaboutindetail—agoodcheckofyourknowledgeistounderstandhowtheequals,hashCode,andtoStringmethodsareimplementedthisway.IimplementedthetoStringmethodtohelpmeduringdebugging,butitisalsousedinthefollowingexampleoutput:
@Override
publicbooleanequals(Objectother){
if(this==other)returntrue;
if(other==null||!(otherinstanceofGuess))
returnfalse;
Guessguess=(Guess)other;
returnArrays.equals(colors,guess.colors);
}
@Override
publicinthashCode(){
returnArrays.hashCode(colors);
}
@Override
publicStringtoString(){
if(this==none){
return"none";
}else{
Strings="";
for(inti=colors.length-1;i>=0;i--){
s+=colors[i];
}
returns;
}
}
}
ThisismainlythemodificationthatIneededwhileIdevelopedtheparallelalgorithm.Now,thecodeisfairlyup-to-dateanddescribedtofocusonthemaintopicofthischapter:howtoexecutecodeinJavainparallel.
TheparallelexecutionofthecodeinJavaisdoneinthreads.YoumayknowthatthereisaThreadobjectinJavaruntime,butwithoutunderstandingwhatathreadinthecomputeris,itmakesnosense.Inthefollowingsubsections,wewilllearnwhatthesethreadsare,howtostartanewthread,howtosynchronizedataexchangebetweenthreads,andfinallyputallthistogetherandimplementtheMastermindparallelguessingalgorithm.
ProcessesWhenyoustartyourcomputer,theprogramthatstartsistheoperatingsystem(OS).TheOScontrolsthemachinehardwareandtheprogramsthatyoucanrunonthemachine.Whenyoustartaprogram,theOScreatesanewprocess.ItmeansthattheOSallocatesanewentryinatable(array)whereitadministerstheprocessesandfillsintheparametersthatitknows,andneedstoknow,abouttheprocess.Forexample,itregisterswhatmemorysegmenttheprocessisallowedtouse,whattheIDoftheprocessis,andwhichuserstartedfromwhichotherprocess.Youcannotstartaprocessjustoutofthinair.Whenyoudouble-clickonanEXEfile,youactuallytellthefileexplorer,whichisaprogramrunningasaprocess,tostarttheEXEfileasaseparateprocess.TheexplorercallsthesystemviasomeAPIandkindlyaskstheOStodothat.TheOSwillregistertheexplorerprocessastheparentofthenewprocess.TheOSdoesnotactuallystarttheprocess,butcreatesallthedatathatitneedstostartitand,whenthereissomefreeCPUresource,thentheprocessgetsstarted,andthenitgetspausedverysoon.YouwillnotnoticeitbecausetheOSwillstartitagainandagainandisalwayspausingtheprocessrepeatedly.Itneedstodoittoproviderunpossibilitiestoallprocesses.Thatway,weexperienceallprocessesrunningatthesametime.Inreality,processesdonotrunatthesametimeonasingleprocessor,buttheygettimeslotstorunoften.
IfyouhavemorethanoneCPUinthemachine,thenprocessescanactuallyrunatthesametime,asmanyCPUsasthereare.Astheintegrationgetsmoreadvancedtoday,desktopcomputershaveCPUsthatcontainmultiplecoresthatfunctionalmostlikeseparateCPUs.Onmymachine,Ihavefourcores,eachcapableofexecutingtwothreadssimultaneously;so,myMacisalmostlikean8CPUmachine.
Processeshaveseparatememories.Theyareallowedtouseonepartofthememoryandifaprocesstriestouseanotherpartthatdoesnotbelongtoit,theprocessorwillstopdoingso.TheOSwillkilltheprocess.
JustimaginehowfrustratedthedevelopersoftheoriginalUNIXcouldhavebeenthattheynamedtheprogramtostopaprocesstokill,andstoppingaprocessiscalledkillingit.Itislikemedievalageswhentheycutoffthehandofafelon.Youtouchthewrongpartofthememoryandgetkilled.Iwouldnotliketobeaprocess.
Thememoryhandlingbytheoperatingsystemisverycomplexinadditiontoseparatingtheprocessesfromeachother.Whenthereisnotenoughmemory,theOSwritespartofthememorytodiskfreeingupthememoryandreloadingthatpartwhenitisneededagain.Thisisaverycomplex,low-levelimplementedandhighlyoptimizedalgorithmthatistheresponsibilityoftheOS.
ThreadsWhenIsaidthattheOSexecutestheprocessesintimeslots,Iwasnotabsolutelyprecise.Everyprocesshasoneormorethreads,andthreadsareexecuted.Athreadisthesmallestexecutionmanagedbyanexternalscheduler.Olderoperatingsystemsdidnothavethenotionofathreadandwereexecutingprocesses.Asamatteroffact,thefirstthreadimplementationsweresimplyduplicationsofprocessesthatweresharingthememory.
Youmayheartheterminology,lightweightprocess—itmeansathread.
Theimportantthingisthatthethreadsdonothavetheirownmemory.Theyusethememoryoftheprocess.Inotherwords,thethreadsthatruninthesameprocesshaveundistinguishedaccesstothesamememorysegment.Itisanextremelypowerfulpossibilitytoimplementparallelalgorithmsthatmakeuseofthemultiplecoresinthemachine,butatthesametime,itmayleadtobugs.
Imaginethattwothreadsincrementthesamelongvariable.Theincrementfirstcalculatestheincrementedvalueofthelower32bitsandthentheupper,iftherewereanyoverflowbits.ThesearetwoormorestepsthatmaybeinterruptedbytheOS.Itmayhappenthatonethreadincrementsthelower32bits,
remembersthatthereissomethingtodototheupper32bits,startsthecalculation,buthasnotimetostoretheresultbeforeitgetsinterrupted.Then,anotherthreadincrementsthelower32bits,theupper32bits,andthenthefirstthreadjustsavestheupper32bitsthatitcalculated.Theresultgetsgarbled.Onanolder32-bitJavaimplementation,itwasextremelyeasytodemonstratethiseffect.Ona64-bitJavaimplementation,allthe64bitsareloadedintoregistersandsavedbacktothememoryinonestepsoitisnotthateasytodemonstratemultithreadissues,butitdoesnotmeanthattherearenone.
Whenathreadispausedandanotherthreadisstarted,theoperatingsystemhastoperformacontextswitch.Itmeansthat,amongotherthings,theCPUregistershavetobesavedandthensettothevaluethattheyshouldhavefortheotherthread.Acontextswitchisalwayssavingthestateofthethreadandloadingthepreviouslysavedstateofthethreadtobestarted.ThisisonaCPUregisterlevel.Thiscontextswitchistimeconsuming;therefore,themorecontextswitchesthataredone,themoreCPUresourceisusedforthethreadadministrationinsteadoflettingthemrun.Ontheotherhand,iftherearenotenoughswitches,somethreadsmaynotgetenoughtimeslotstoexecute,andtheprogramhangs.
FibersJavadoesnothavefibers,butastherearesomelibrariesthatsupportfiberhandlings,itisworthmentioning.Afiberisafinerunitthanathread.Aprogramcodeexecutinginathreadmaydecidetogiveuptheexecutionandtellthefibermanagertojustexecutesomeotherfiber.Whatisthepointandwhyisitbetterthanusinganotherthread?Thereasonisthatthisway,fiberscanavoidpartofthecontextswitch.AcontextswitchcannotbeavoidedtotallybecauseadifferentpartofthecodethatstartstoexecuteitmayusetheCPUregistersinatotallydifferentway.Asitisthesamethread,thecontextswitchingisnotthetaskoftheOS,buttheapplication.
TheOSdoesnotknowifthevalueofaregisterisusedornot.Therearebitsintheregisters,andnoonecantellseeingonlytheprocessorstatewhetherthosebitsarerelevantforthecurrentcodeexecutionorjusthappentobethereinthatway.Theprogramgeneratedbyacompilerdoesknowwhichregistersareimportantandwhicharethosethatcanjustbeignored.Thisinformationchangesfromplacetoplaceinthecode,butwhenthereisaneedforaswitch,thefiberpassestheinformationofwhatisneededtobeswitchedatthatpointtothecodethatdoestheswitching.
Thecompilercalculatesthisinformation,butJavadoesnotsupportfibersinthecurrentversion.ThetoolsthatimplementfibersinJavaanalyzeandmodifythebytecodeoftheclassestodothisafterthecompilationphase.
Golang'sgoroutinesarefibersandthatiswhyyoucaneasilystartmanythousandgoroutinesinGo,butyoubetterlimitthenumberofthreadsinJavatoalowernumber.Theyarenotthesamethings.
Astheterminologylightweightprocessisfadingoutandusedbylessandlessfibers,manytimesarereferredtoaslightweightthreads.
java.lang.ThreadAseverythinginJava(well,almost)isobject,ifwewanttostartanewthread,wewillneedaclassthatrepresentsthethread.Thisclassisjava.lang.ThreadbuiltintotheJDK.WhenyoustartaJavacode,theJVMautomaticallycreatesafewThreadobjectsandusesthemtorundifferenttasksthatareneededbyit.IfyoustartupVisualVM,youcanselecttheThreadstabofanyJVMprocessandseetheactualthreadsthatareintheJVM.Forexample,theVisualVMasIstartedithas29livethreads.Oneofthemisthethreadnamedmain.Thisistheonethatstartstoexecutethemainmethod(surprise!).Themainthreadstartedmostoftheotherthreads.Whenwewanttowriteamultithreadapplication,wewillhavetocreatenewThreadobjectsandstartthem.ThesimplestwaytodothatisnewThread(),andthencallingthestartmethodonthethread.ItwillstartanewThreadthatwilljustfinishimmediatelyaswedidnotgiveitanythingtodo.TheThreadclass,asitisintheJDK,doesnotdoourbusinesslogic.Thefollowingarethetwowaystospecifythebusinesslogic:
CreatingaclassthatimplementstheRunnableinterfaceCreatingaclassthatextendstheThreadclassandoverridestherunmethod
Thefollowingblockofcodeisaverysimpledemonstrationprogram:
publicclassThreadIntermingling{
staticclassMyThreadextendsThread{
privatefinalStringname;
MyThread(Stringname){
this.name=name;
}
@Override
publicvoidrun(){
for(inti=1;i<1000;i++){
System.out.print(name+""+i+",");
}
}
}
publicstaticvoidmain(String[]args){
Threadt1=newMyThread("t1");
Threadt2=newMyThread("t2");
t1.start();
t2.start();
System.out.print("started");
}
}
Theprecedingcodecreatestwothreadsandstartsthemoneaftertheother.Whenthestartmethodiscalled,itschedulesthethreadobjecttobeexecutedandthenreturns.Asaresult,thenewthreadwillsoonstartexecutingasynchronouslywhilethecallingthreadcontinuesitsexecution.Thetwothreads,andthemainthread,runparallelinthefollowingexampleandcreateanoutputthatlookssomethinglikethis:
startedt21,t22,t23,t24,t25,t26,t27,t28,t11,t29,t210,t211,t212,...
Theactualoutputchangesfromruntorun.Thereisnodefiniteorderoftheexecutionorhowthethreadsgetaccesstothesinglescreenoutput.Thereisnotevenguaranteethatineachandeveryexecution,themessagestartedisprintedbeforeanyofthethreadmessages.
Togetabetterunderstandingofthis,wewillhavetolookatthestatediagramofthreads.AJavaThread
canbeinoneofthefollowingstates:
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
ThesestatesaredefinedintheenumThread.State.Whenyoucreateanewthreadobject,itisintheNEWstate.Atthismoment,thethreadisnothingspecial,itisjustanobjectbuttheoperatingsystemexecution-schedulingdoesnotknowaboutit.Insomesense,itisonlyapieceofmemoryallocatedbytheJVM.
Whenthestartmethodisinvoked,theinformationaboutthethreadispassedtotheoperatingsystemandtheOSschedulesthethreadsoitcanbeexecutedbyitwhenthereisanappropriatetimeslot.Doingthisisaresourcefulactionandthatisthereasonwhywedonotcreateand,especially,donotstartnewThreadobjectsonlywhenitisneeded.InsteadofcreatingnewThreads,wewillkeeptheexistingthreadsforawhile,eveniftheyarenotneededatthemoment,andreuseanexistingoneifthereisonesuitable.
AthreadintheOScanalsobeinarunningstateaswellasrunnablewhentheOSschedulesandexecutesitatthemoment.JavaJDKAPIdoesnotdistinguishbetweenthetwoforgoodreason.Itwouldbeuseless.WhenathreadisintheRUNNABLEstateaskingifitisactuallyrunningfromthethreaditself,itwillresultinanobviousanswer:ifthecodejustreturnedfromthegetStatemethodimplementedintheThreadclass,thenitruns.Ifitwerenotrunning,itwouldnothavereturnedfromthecallinthefirstplace.IfthegetStatemethodwascalledfromanotherthread,thentheresultabouttheotherthreadbythetimethemethodreturnswouldbemeaningless.TheOSmayhavestopped,orstarted,thequeriedthreadseveraltimesuntilthen.
AthreadisinaBLOCKEDstatewhenthecodeexecutinginthethreadtriestoaccesssomeresourcethatisnotcurrentlyavailable.Toavoidconstantpollingofresources,theoperatingsystemprovideseffectivenotificationmechanismsothethreadsgetbacktotheRUNNABLEstatewhentheresourcetheyneedbecomesavailable.
AthreadisintheWAITorTIMED_WAITINGstatewhenitwaitsforsomeotherthreadorlock.TIMED_WAITINGisthestatewhenthewaitingstartedcallingaversionofamethodthathastimeout.
Finally,theTERMINATEDstateisreachedwhenthethreadfinishesitsexecution.Ifyouappendthefollowinglinestotheendofourpreviousexample,thenyouwillgetaTERMINATEDprintoutandalsoanexceptionthrownuptothescreencomplainingaboutillegalthreadstate,whichisbecauseyoucannotstartanalreadyterminatedthread:
System.out.println();
System.out.println(t1.getState());
System.out.println();
t1.start();
InsteadofextendingtheThreadclasstodefinewhattoexecuteasynchronously,wecancreateaclassthatimplementsRunnable.DoingthatismoreinlinewiththeOOprogrammingapproach.Thesomethingthatwe
implementintheclassisnotafunctionalityofathread.Itismoreofasomethingthatcanbeexecuted.Itissomethingthatcanjustrun.
Ifthisexecutionisasynchronousinadifferentthread,oritisexecutedinthesamethreadthatwascallingtherunmethod,isadifferentconcernthathastobeseparated.Ifwedoitthatway,wecanpasstheclasstoaThreadobjectasaconstructorargument.CallingstartontheThreadobjectwillstarttherunmethodoftheobjectwepassed.Thisisnotthegain.ThegainisthatwecanalsopasstheRunnableobjecttoanExecutor(dreadfulname,huhh!).Executorisaninterface,andimplementationsexecuteRunnable(andalsoCallable,seelater)objectsinThreadsinanefficientway.ExecutorsusuallyhaveapoolofThreadobjectsthatareprepared,andintheBLOCKEDstate.WhentheExecutorhasanewtasktoexecute,itgivesittooneoftheThreadobjectsandreleasesthelockthatisblockingthethread.TheThreadgetsintotheRUNNABLEstate,executestheRunnable,andgetsblockedagain.ItdoesnotterminateandthuscanbereusedtoexecuteanotherRunnablelater.Thatway,Executorsavoidtheresourceconsumingprocessofthreadregistrationintotheoperatingsystem.
ProfessionalapplicationcodenevercreatesanewThread.ApplicationcodeusessomeframeworktohandletheparallelexecutionofthecodeorusesExecutorsprovidedbysomeExecutorServicetostartRunnableorCallableobjects.
Pitfalls
Wehavealreadydiscussedmanyoftheproblemsthatwemayfacewhendevelopingparallelprogram.Inthissection,wewillsummarizethemwiththeusualterminologyusedfortheproblems.Terminologyisnotonlyinteresting,butitisalsoimportantwhenyoutalkwithcolleaguestoeasilyunderstandeachother.
DeadlocksDeadlockisthemostinfamousparallelprogrammingpitfall,andforthisreason,wewillstartwiththisone.Todescribethesituation,wewillfollowthemetaphorofbureaucrats.
Thebureaucrathastostampapaperhehasinhishand.Todothat,heneedsthestamp,andhealsoneedstheinkpad.First,hegoestothedrawerwherethestampisandtakesit.Then,hewalkstothedrawerwheretheinkpadisandtakestheinkpad.Heinksthestamp,pushesonthepaper.Then,heputsthestampbacktoitsplaceandthentheinkpadbackinitsplace.Everythingisnice,weareoncloud9.
Whathappensifanotherbureaucrattakestheinkpadfirstandthenthestampsecond?Theymaysoonendupasonebureaucratwiththestampinhandwaitingfortheinkpadandanotheronewiththeinkpadinhandwaitingforthestamps.And,theymayjuststaythere,frozenforever,andthenmoreandmorestarttowaitfortheselocks,thepapersnevergetstamped,andthewholesystemsinksintoanarchy.
Toavoidsuchsituations,thelockshavetobeorderedandthelocksshouldalwaysbeacquiredintheorder.Intheprecedingexample,thesimpleagreementthattheinkpadisacquiredfirstandthestampsecondsolvestheproblem.Whoeveracquiredthestampcanbesurethattheinkpadisfreeorwillsoonbefree.
RaceconditionsWetalkaboutraceconditionswhentheresultofacalculationmaybedifferentbasedonthespeedandCPUaccessofthedifferentparallelrunningthreads.Let'stakealookatthefollowingtwocodelines:
voidmethod1(){
1a=b;
2b=a+1;
}
voidmethod2(){
3c=b;
4b=c+2;
}
Ifthevalueofbatthestartoftheexecutionis0,andtwodifferentthreadsexecutethetwomethods,thentheorderofthelinescanbe1234,1324,1342,3412,3142,or3142.Anyexecutionorderofthefourlinesmayhappenwhichassuresthat1runsbefore2and3runsbefore4,butnootherrestrictions.Theoutcome,thevalueofb,iseither1or2attheendoftheexecutionofthesegments,whichmaynotbegoodandwhatwewantedwhencoding.
NotethattheimplementationoftheparallelMastermindgamealsohassomethinglikethis.Theactualguessesverymuchdependonthespeedofthedifferentthreads,butthisisirrelevantfromthefinalresultpointofview.Wemayhavedifferentguessesindifferentrunsandthatwaythealgorithmisnotdeterministic,butweareguaranteedtofindthefinalsolution.
Overusedlocks
Inmanysituations,itmayhappenthatthethreadsarewaitingonalock,whichprotectsaresourcefromconcurrentaccess.Iftheresourcecannotbeusedbymultiplethreadssimultaneously,andtherearemorethreadsthancanbeserved,thenthethreadsarestarving.However,inmanycases,theresourcecanbeorganizedinawaysothatthethreadscangetaccesstosomeoftheservicesthattheresourceprovides,andthelockingstructurecanbelessrestrictive.Inthatcase,thelockisoverusedandthesituationcanbemendedwithoutallocatingmoreresourceforthethreads.Itmaybepossibletouseseverallocksthatcontroltheaccesstothedifferentfunctionalityoftheresource.
Starving
Starvingisthesituationwhenseveralthreadsarewaitingforaresourcetryingtoacquirealockandsomethreadsgetaccesstothelockonlyafterextremelylongtimeornever.Whenthelockisreleasedandtherearethreadswaitingforit,thenoneofthethreadscangetthelock.Thereisusuallynoguaranteethatathreadgetsthelockifitwaitslongenough.Suchamechanismwouldrequireintensiveadministrationofthethreads,sortingtheminthewaitingqueue.Aslockingshouldbealowlatencyandhighperformanceaction,evenafewCPUclockcyclesaresignificant;therefore,thelocksdonotprovidethistypeoffairaccessbydefault.Notwastingtimewithfairnessinthreadschedulingisagoodapproach,incasethelockshaveonethreadwaiting.Themaingoaloflocksisnotschedulingthewaitingthreads,butratherpreventingparallelaccesstoresources.
Itislikeinashop.Ifthereissomebodyatthecashier,youwait.Itisalockbuiltinimplicitly.Itisnotaproblemifpeopledonotqueueupforthecashier,solongaslongthereisalmostalwaysonefree.However,whenthereareseveralqueuesbuiltupinfrontofthecashiers,thenhavingnoqueueandwaitingorderwillcertainlyleadtosomeverylongwaitingorderforsomeonewhoisslowtogetaccesstothecashier.Generally,thesolutionoffairnessandcreatingqueueofwaitingthreads(customers)isnotagoodsolution.Thegoodsolutionistoeliminatethesituationthatleadstowaitingqueues.Youcanemploymorecashiers,oryoucandosomethingtotallydifferentthatmakesthepeakloadsmaller.Inashop,youcangivediscounttodrivecustomerswhocomeinatoff-peakhours.Inprogramming,severaltechniquescanbeapplied,usually,dependingontheactualbusinesswecodeandfairschedulingoflocksisusuallyaworkaround.
ExecutorServiceExecutorServiceisaninterfaceintheJDK.AnimplementationoftheinterfacecanexecuteaRunnableorCallableclassinanasynchronousway.TheinterfaceonlydefinestheAPIfortheimplementationanddoesnotrequirethattheinvocationisasynchronousbut,inreality,thatisthemainpointimplementingsuchaservice.InvokingtherunmethodofaRunnableinterfaceinasynchronouswayissimplycallingamethod.Wedonotneedaspecialclassforthat.
TheRunnableinterfacedefinesonerunmethod.Ithasnoargumentsreturnsnovalueanddoesnotthrowanyexception.TheCallableinterfaceisparameterizedandtheonlymethoditdefines,call,hasnoargumentbutreturnsagenericvalueandmayalsothrowException.Inourcode,wewillimplementRunnableifwejustwanttorunsomething,andCallablewhenwewanttoreturnsomething.Bothoftheseinterfacesarefunctionalinterfaces,therefore,theyaregoodcandidatestobeimplementedusinglambda.
TohaveaninstanceofanimplementationofanExecutorService,wecanusetheutilityclassExecutors.ManytimeswhenthereisanXYZinterfaceintheJDK,therecanbeanXYZs(plural)utilityclassthatprovidesfactoryfortheimplementationsoftheinterface.Ifwewanttostartthet1taskmanytimes,wecandosowithoutcreatinganewThread.Weshouldusethefollowingexecutorservice:
publicclassThreadIntermingling{
staticclassMyThreadimplementsRunnable{
privatefinalStringname;
MyThread(Stringname){
this.name=name;
}
@Override
publicvoidrun(){
for(inti=1;i<1000;i++){
System.out.print(name+""+i+",");
}
}
}
publicstaticvoidmain(String[]args)
throwsInterruptedException,ExecutionException{
ExecutorServicees=Executors.newFixedThreadPool(2);
Runnablet1=newMyThread("t1");
Runnablet2=newMyThread("t2");
Future<?>f1=es.submit(t1);
Future<?>f2=es.submit(t2);
System.out.print("started");
f1.get();
f2.get();
System.out.println();
f1=es.submit(t1);
es.shutdown();
}
}
Thistime,wedonotgetanyexception.Instead,thet1taskrunssecondtime.Inthisexample,weareusingafixedsizethreadpoolthathastwoThreads.Aswewanttostartonlytwothreadssimultaneously,itisenough.Thereareimplementationsthatgrowandshrinkthesizeofthepooldynamically.Fixedsizepoolshouldbeusedwhenwewanttolimitthenumberofthethreadsorweknowfromsomeotherinformationsourcethenumberofthea-priorythreads.Inthiscase,itisagoodexperimenttochangethesizeofthepooltooneandseethatthesecondtaskwillnotstartinthiscaseuntilthefirstonefinishes.Theservicewillnothaveanotherthreadfort2andwillhavetowaituntiltheoneandonlyThreadinthepoolisfreed.
Whenwesubmitthetasktotheservice,itreturnsevenifthetaskcannotcurrentlybeexecuted.Thetasksareputinaqueueandwillstartexecutionassoonasthereisenoughresourcetostartthem.ThesubmitmethodreturnsaFutureobject,aswecanseeintheprecedingsample.
Itislikeaserviceticket.Youbringyourcartotherepairmechanic,andyougetaticket.Youarenotrequiredtostaythereuntilthecarisfixed,butatanytime,youcanaskifthecarisready.Allyouneedistheticket.Youcanalsodecidetowaituntilthecarisready.AFutureobjectisalsosomethinglikethat.Youdonotgetthevaluethatyouneed.Itwillbecalculatedasynchronously.However,thereisaFuturepromisethatitwillbethereandyourtickettoaccesstheobjectyouneedistheFutureobject.
WhenyouhaveaFutureobject,youcancalltheisDonemethodtoseeifitisready.Youcanstartwaitingforittocallgetwith,orwithout,sometimeout.Youcanalsocancelthetaskexecutingit,butinthatcase,theoutcomemaybequestionable.Justlike,incaseofyourcar,ifyoudecidetocancelthetask,youmaygetbackyourcarwiththemotordisassembled.Similarly,cancellingataskthatisnotpreparedforitmayleadtoresourceloss,openedandinaccessibledatabaseconnection(thisisapainfulmemoryforme,evenafter10years),orjustagarbledunusableobject.Prepareyourtaskstobecancelledordonotcancelthem.
Intheprecedingexample,thereisnoreturnvalueforFuturebecausewesubmittedaRunnableobjectandnotaCallableone.InthatcasethevaluepassedtotheFutureisnottobeused.Itisusuallynull,butthatisnothingtoleanon.
Thefinalandmostimportantthingthatmanydevelopersmiss,evenme,afternotwritingmultithreadJavaAPIusingcodeforyears,isshuttingdowntheExecutorService.TheExecutorServiceiscreatedandithasThreadelements.TheJVMstopswhenallnon-daemonthreadsarestopped.Itain'tovertillthefatladysings.
Athreadisadaemonthreadifitwassettobedaemon(invokingsetDaemon(true))beforeitwasstarted.Athreadisautomaticallydaemonofthestartingthreadisadaemonthread.DaemonthreadsarestoppedbytheJVMwhenallotherthreadsarefinishedandtheJVMwantstofinish.SomeofthethreadstheJVMexecutesitselfaredaemonthreads,butitislikelythatthereisnopracticaluseofcreatingdaemonthreadsinanapplicationprogram.
NotshuttingdowntheservicesimplypreventstheJVMfromstopping.Thecodewillhangafterthemainmethodfinishes.TotelltheExecutorServicethatthereisnoneedforthethreadsithas,wewillhavetoshutdowntheservice.Thecallwillonlystarttheshutdownandreturnimmediately.Inthiscase,wedonotwanttowait.TheJVMdoesanyway.Ifweneedtowait,wewillhavetocallawaitTermination.
ForkJoinPoolTheForkJoinPoolisaspecialExecutorServicethathasmethodstoexecuteForkJoinTaskobjects.Theseclassesareveryhandywhenthetaskthatwewanttoperformcanbesplitintomanysmalltasksandthentheresults,whentheyareavailable,aggregated.Usingthisexecutor,weneednotcareaboutthesizeofthethreadpoolandshuttingdowntheexecutor.Thesizeofthethreadpoolisadjustedtothenumberofprocessorsonthegivenmachinetohaveoptimalperformance.AstheForkJoinPoolisaspecialExecutorServicethatisdesignedforshortrunningtasks,itdoesnotexpectanytasktobetherelongerorbeingneededwhentherearenomoretaskstorun.Therefore,itisexecutedasadaemonthread;whentheJVMshutsdown,theForkJoinPoolautomaticallystopsandtheladydoesnotsinganymore.
Tocreateatask,theprogrammershouldextendeitherRecursiveTaskorRecursiveAction.Thefirstoneistobeusedwhenthereissomereturnvaluefromthetask,thesecondwhenthereisnocomputedvaluereturned.Theyarecalledrecursivebecausemanytimes,thesetaskssplittheproblemtheyhavetosolvesmallerproblemsandinvokethesetasksasynchronouslythroughthefork-joinAPI.
AtypicalproblemtobesolvedusingthisAPIisthequick-sort.IntheChapter3,OptimizingtheSort-MakingCodeProfessionalwecreatedtwoversionsofthequick-sortalgorithm.Oneusingrecursivecallsandonewithoutusingit.Wecanalsocreateanewone,which,insteadofcallingitselfrecursively,schedulethetasktobeexecuted,perhapsbyanotherprocessor.TheschedulingisthetaskoftheForkJoinPoolimplementationofExecutorService.
YoumayrevisitthecodeofQsort.javainChapter3,OptimizingtheSort-MakingCodeProfessional.HereistheversionthatisusingForkJoinPool:
publicclassFJQuickSort<E>{
finalprivateComparator<E>comparator;
finalprivateSwapperswapper;
publicFJQuickSort(Comparator<E>comparator,Swapperswapper){
this.comparator=comparator;
this.swapper=swapper;
}
publicvoidqsort(SortableCollection<E>sortable,
intstart,intend){
ForkJoinPoolpool=newForkJoinPool();
pool.invoke(newRASort(sortable,start,end));
}
privateclassRASortextendsRecursiveAction{
finalSortableCollection<E>sortable;
finalintstart,end;
publicRASort(SortableCollection<E>sortable,
intstart,intend){
this.sortable=sortable;
this.start=start;
this.end=end;
}
publicvoidcompute(){
if(start<end){
finalEpivot=sortable.get(start);
finalPartitioner<E>partitioner=
newPartitioner<>(comparator,swapper);
intcutIndex=partitioner.partition(
sortable,start,end,pivot);
if(cutIndex==start){
cutIndex++;
}
RecursiveActionleft=
newRASort(sortable,start,cutIndex-1);
RecursiveActionright=
newRASort(sortable,cutIndex,end);
invokeAll(left,right);
left.join();
right.join();
}
}
}
Wheneveryoucansplityourtasksintosubtaskssimilartothewayitwasdoneintheprecedingquick-sortexample,IrecommendthatyouuseForkJoinPoolasanExecutorService.YoucanfindgooddocumentationontheAPIandtheuseontheJavaDocdocumentationofOracle.
VariableaccessNowthatwecanstartthreadsandcreatecodethatrunsparallel,itistimetotalkalittlebitabouthowthesethreadscanexchangedatabetweeneachother.Atfirstglimpse,itseemsfairlysimple.Thethreadsusethesamesharedmemory;therefore,theyallcanreadandwriteallthevariablesthattheJavaaccessprotectionallowsthem.Thisistrue,exceptthatsomethreadsmayjustdecidenottoreadthememory.Afterall,iftheyhavejustrecentlyreadthevalueofsomevariable,whyreaditagainfromthememorytotheregistersifitwasnotmodified?Whowouldhavemodifiedthem?Let'sseethefollowingshortexample:
packagepackt.java9.by.example.thread;
publicclassVolatileDemonstrationimplementsRunnable{
privateObjecto=null;
privatestaticfinalObjectNON_NULL=newObject();
@Override
publicvoidrun(){
while(o==null);
System.out.println("oisnotnull");
}
publicstaticvoidmain(String[]args)
throwsInterruptedException{
VolatileDemonstrationme=newVolatileDemonstration();
newThread(me).start();
Thread.sleep(1000);
me.o=NON_NULL;
}
}
Whatwillhappen?Youmayexpectthatthecodestartsup,startsthenewthread,andoneminute,whenthemainthreadsetstheobjecttosomethingnotnull,willitstop?Itwillnot.
ItmaystoponsomeJavaimplementations,butinmostofthem,itwilljustkeepspinning.ThereasonforthatisthattheJITcompileroptimizesthecode.Itseesthattheloopdoesnothingandalsothatthevariablewilljustneverbenon-null.Itisallowedtoassumethatbecausethevariablesnotdeclaredvolatilearenotsupposedtobemodifiedbyanyotherthread,theJITiseligibletooptimize.IfwedeclaretheObjectovariabletobevolatile(withthevolatilekeyword),thenthecodewillstop.
Incaseyoutrytoremovethecalltosleep,thecodewillalsostop.This,however,doesnotfixtheissue.ThereasonisthatJIToptimizationkicksinonlyafterabout5000loopsofthecodeexecution.Beforethat,thecoderunsnaiveandstopsbeforetheoptimizationwilleliminatetheextraandregularlynotneededaccesstothenon-volatilevariable.
Ifthisissogruesome,thenwhydon'twedeclareallvariablestobevolatile?WhydoesJavanotdothatforus?Theanswerisspeed,andtounderstanditdeeper,wewilluseourmetaphor,theoffice,andthebureaucrat.
TheCPUheartbeatThesedaysCPUsrunon2to4GHzfrequencyprocessors.Itmeansthataprocessorgets2to4times109clocksignalstodosomethingeverysecond.Aprocessorcannotdoanyatomicoperationfasterthanthis,andalsothereisnoreasontocreateaclockthatisfasterthanwhataprocessorcanfollow.ItmeansthataCPUperformsasimpleoperation,suchasincrementingaregisterinhalforquarterofananosecond.Thisistheheartbeatoftheprocessor,andifwethinkofthebureaucratashumans,whotheyare,thenitisequivalenttoonesecond,approximately,ifandastheirheartbeat.
Processorshaveregistersandcachesonthechipondifferentlevels,L1,L2,andsometimesL3;thereismemory,SSD,disk,network,andtapesthatmaybeneededtoretrievedata.
AccessingdatathatisintheL1cacheisapproximately0.5ns.Youcangrabapaperthatisonyourdesk—halfofasecond.L2cacheis7ns.Thisisapaperinthedrawer.Youhavetopushthechairabitback,benditinasittingposition,pulloutthedrawer,takethepaper,pushthedrawerback,andraiseandputthepaperonthedesk;ittakes10seconds,giveortake.
Mainmemoryreadis100ns.Thebureaucratstandsup,goestothesharedfileatthewall,hewaitswhileotherbureaucratsarepullingtheirpapersorputtingtheirsback,selectsthedrawer,pullsitout,takesthepaper,andwalksbacktothedesk.Thisistwominutes.Thisisvolatilevariableaccesseverytimeyouwriteasinglewordonadocumentandithastobedonetwice.Oncetoread,andoncetowrite,evenifyouhappentoknowthatthenextthingyouwilldoisjustfillanotherfieldoftheformonthesamepaper.
Modernarchitectures,wheretherearenomultipleCPUsbutrathersingleCPUswithmultiplecores,areabitfaster.Onecoremaychecktheothercore'scachestoseeiftherewasanymodificationonthesamevariable,butthisspeedsthevolatileaccessto20nsorso,whichisstillamagnitudeslowerthannonvolatile.
Althoughtherestislessfocusedonmultithreadprogramming,itisworthmentioninghere,becauseitgivesgoodunderstandingonthedifferenttimemagnitudes.
ReadingablockfromanSSD(4Kblockusually)is150,000ns.Inhumanspeed,thatisalittlebitmorethan5days.ReadingorsendingsomethingtoaserveroverthenetworkontheGblocalEthernetis0.5ms,whichislikewaitingforalmostamonthforthemetaphoricbureaucrat.Ifthedataoverthenetworkisonaspinningmagneticdisk,thenseektimeaddsup(thetimeuntilthediskrotatessothatthepartofthemagneticsurfacegetsunderthereadinghead)to20ms.Itis,approximately,ayearinhumanterms.
IfwesendanetworkpacketovertheAtlanticontheInternet,itisapproximatelyis150ms.Itislike14years,andthiswasonlyonesinglepackage;ifwewanttosenddataovertheocean,itmaybesecondsthatcountuptohistorictimes,thousandsofyears.Ifwecountoneminuteforamachinetoboot,itisequivalenttothetimespanofourwholecivilization.
WeshouldconsiderthesenumberswhenwewanttounderstandwhattheCPUisdoingmostofthetime:itwaits.Additionally,italsohelpscoolyournerveswhenyouthinkaboutthespeedofareal-lifebureaucrat.Theyarenotthatslowafterall,ifweconsidertheirheartbeat,whichimpliestheassumption
thattheyhaveaheart.However,let'sgobacktoreallife,CPUs,andL1,L2cachesandvolatilevariables.
VolatilevariablesLet'smodifythedeclarationoftheovariableinoursamplecodeasfollows:
privatevolatileObjecto=null;
Theprecedingcoderunsfineandstopsafterasecondorso.AnyJavaimplementationhastoguaranteethatmultiplethreadscanaccessvolatilefieldsandthevalueofthefieldisconsistentlyupdated.Thisdoesnotmeanthatvolatiledeclarationwillsolveallsynchronizationissues,butguaranteesthatthedifferentvariablesandtheirvaluechangerelationsareconsistent.Forexample,let'sconsiderwehavethefollowingtwofieldsincrementedinamethod:
privatevolatileinti=0,j=0;
publicvoidmethod(){
i++;j++;
}
Intheprecedingcode,readingiandjfromanotherthreadwillneverresultani>j.Withoutthevolatiledeclaration,thecompilerisfreetoreorganizetheexecutionoftheincrementoperationsifitneedsandthus,itwillnotguaranteethatanasynchronousthreadreadsconsistentvalues.
SynchronizedblockDeclaringvariablesarenottheonlytooltoensuretheconsistencybetweenthreads.ThereareothertoolsintheJavalanguageandoneofthemisthesynchronizedblock.Thesynchronizedkeywordispartofthelanguageanditcanbeusedinfrontofamethodoraprogramblockinsideamethod.
EveryobjectintheJavaprogramhasamonitorthatcanbelockedandunlockedbyanyrunningthread.Whenathreadlocksamonitor,itissaidthatthatthreadholdsthelock,andnotwothreadscanholdthelockofamonitoratatime.Ifathreadtriestolockamonitorthatisalreadylocked,itgetsBLOCKEDuntilthemonitorisreleased.Asynchronizedblockstartswiththesynchronizedkeyword,andthenanobjectinstancespecifiedbetweenparenthesesandtheblockcomes.Thefollowingsmallprogramdemonstratesthesynchronizedblock:
publicclassSynchronizedDemoimplementsRunnable{
publicstaticfinalintN=1000;
publicstaticfinalintMAX_TRY=1_000_000;
privatefinalcharthreadChar;
privatefinalStringBuffersb;
publicSynchronizedDemo(charthreadChar,StringBuffersb){
this.threadChar=threadChar;
this.sb=sb;
}
@Override
publicvoidrun(){
for(inti=0;i<N;i++){
synchronized(sb){
sb.append(threadChar);
sleep();
sb.append(threadChar);
}
}
}
privatevoidsleep(){
try{
Thread.sleep(1);
}catch(InterruptedExceptionignored){}
}
publicstaticvoidmain(String[]args){
booleanfailed=false;
inttries=0;
while(!failed&&tries<MAX_TRY){
tries++;
StringBuffersb=newStringBuffer(4*N);
newThread(newSynchronizedDemo('a',sb)).start();
newThread(newSynchronizedDemo('b',sb)).start();
failed=sb.indexOf("aba")!=-1||
sb.indexOf("bab")!=-1;
}
System.out.println(failed?
"failedafter"+tries+"tries":"notfailed");
}
}
Thecodestartstwodifferentthreads.OneofthethreadsappendsaatotheStringBuffer.Theotheroneappendsbb.Thisappendingisdoneintwoseparatestepswithasleepinbetween.ThesleepisneededtoavoidJITthatoptimizesthetwoseparatestepsintoone.Eachthreadexecutestheappend1000timeseachtimeappendingaorbtwotimes.AsthetwoappendsoneaftertheotherareinsideasynchronizedblockitcannothappenthatanabaorbabsequencegetsintotheStringBuffer.Whileonethreadexecutesthesynchronizedblock,theotherthreadcannotexecuteit.
IfIremovethesynchronizedblock,thentheJVMIusedtotestJavaHotSpot(TM)64-BitServerVM(build9-ea+121,mixedmode)printsoutthefailurewithatry-countaroundafewhundreds.
Itclearlydemonstrateswhatthesynchronizationmeans,butitdrawsourattentiontoanotherimportantphenomena.Theerroroccursonlyaroundeveryfewhundredthousandexecutionsonly.Itisextremelyrare,eventhoughthisexamplewasfurnishedtodemonstratesuchamishap.Ifabugappearssorare,itisextremelyhardtoreproduceand,evenmore,todebugandfix.Mostofthesynchronizationerrorsmanifestinmysteriouswaysandtheirfixingusuallyistheresultofmeticulouscodereviewratherthandebugging.Therefore,itisextremelyimportanttoclearlyunderstandthetruenatureofJavamultithreadbehaviorbeforestartingcommercialmultithreadapplication.
Thesynchronizedkeywordcanalsobeusedinfrontofamethod.Inthiscase,theobjecttoacquirethelockofistheobject.Incaseofastaticmethod,thesynchronizationisdoneonthewholeclass.
WaitandnotifyTherearefivemethodsimplementedintheclassObjectthatcanbeusedtogetfurthersynchronizationfunctionality:waitwiththreedifferenttimeoutargumentsignature,notify,andnotifyAll.Tocallwait,thecallingthreadshouldhavethelockoftheObjectonwhichwaitisinvoked.Itmeansthatyoucanonlyinvokewaitfrominsideasynchronizedblock,andwhenitiscalled,thethreadgetsBLOCKEDandreleasesthelock.WhenanotherthreadcallsnotifyallonthesameObject,thethreadgetsintotheRUNNABLEstate.Itcannotcontinueexecutionimmediatelyasitcannotgetthelockontheobject.ThelockisheldatthatmomentbythethreadthatjustcallednotifyAll.However,sometimeaftertheotherthreadreleases,thelockgetsoutofthesynchronizedblock,andthewaitingthreadcontinuestheexecution.
Iftherearemorethreadswaitingonanobject,allofthemgetoutoftheBLOCKEDstate.Thenotifymethodwakesonlyoneofthewaitingthreads.Thereisnoguaranteewhichthreadisawakened.
Thetypicaluseofwait,notify,andnotifyAlliswhenoneormorethreadsarecreatingObjectsthatareconsumedbyotherthread,orthreads.Thestoragewheretheobjectstravelbetweenthethreadsissomekindofqueue.Theconsumerwaitsuntilthereissomethingtoreadfromthequeue,andtheproducerputstheobjectsintothequeueoneaftertheother.Theproducernotifiestheconsumerswhenitstoressomethingintothequeue.Ifthereisnoroomleftinthequeue,theproducerhastostopandwaituntilthequeuehassomespace.Inthiscase,theproducercallsthewaitmethod.Towaketheproducerup,theconsumercallsnotifyAllwhenitreadssomething.
Theconsumerconsumestheobjectsfromthequeueinaloopandcallswaitonlyifthereisnothingtobereadfromthequeue.WhentheproducercallsnotifyAll,andthereisnoconsumerwaiting,thenotificationisjustignored.Itfliesaway,butthisisnotaproblem;consumersarenotwaiting.WhentheconsumerconsumesanobjectandcallsnotifyAllandthereisnoproducerwaiting,thesituationisthesame.Itisnotaproblem.
Itcannothappenthattheconsumerconsumes,callsnotifyAll,andafterthenotificationwasflyingintheairnotfindinganywaitingproducer,aproducerstartstowait.Thiscannothappenbecausethewholecodeisinasynchronizedblockanditensuresthatnoproducerisinthecriticalsection.Thisisthereasonwhywait,notify,andnotifyAllcanonlybeinvokedwhenthelockoftheObjectclassisacquired.
Iftherearemanyconsumers,whichareexecutingthesamecodeandareequivalentlygoodinconsumingtheobjects,thenitisanoptimizationtocallnotifyinsteadofnotifyAll.Inthatcase,notifyAllwilljustawakeallconsumerthreadsandall,buttheluckyonewillrecognizethattheywerewokenupbutsomebodyelsealreadygotawaywiththebait.
IrecommendthatyoupracticeatleastoncetoimplementablockingqueuethatcanbeusedtopassObjectsbetweenthreads.However,neverusethatcodeinproduction:startingwithJava1.5,thereareimplementationsoftheBlockingQueueinterface.Useonethatfitsyourneeds.Wewilltoo,inourexamplecode.
FeelluckythatyoucancodeinJava9.IstartedusingJavaprofessionallywhenitwas1.4andonceIhadtoimplementablockingqueue.Lifegetsjustbetterandeasierallthe
timewithJava.
Inprofessionalcode,weusuallyavoidusingsynchronizedmethodsorblocksandvolatilefieldsaswellasthewaitandnotifymethods,notifyAlltoo,ifpossible.Wecanuseasynchronouscommunicationbetweenthreads,orpassthewholemultithreadingtotheframeworkforhandling.Synchronizedandvolatilecannotbeavoidedinsomespecialcaseswhentheperformanceofthecodeisimportant,orwecannotfindabetterconstruct.Sometimes,thedirectsynchronizationonspecificcodeanddatastructuresismoreefficientthantheapproachdeliveredbyJDKclasses.Itistonote,however,thatthoseclassesalsousetheselow-levelsynchronizationconstructs,soitisnotmagichowtheywork;andtodevelopyourself,youcanlookintothecodeoftheJDKclassesbeforeyouwanttoimplementyourownversion.Youwillrealizethatitisnotthatsimpletoimplementthesequeues;thecodeoftheclassesisnotcomplexandcompoundwithoutreason.Ifyoufindthecodesimple,itmeansthatyouareseniorenoughtoknowwhatnottoreimplement.Or,perhaps,youdonotevenrealizewhatcodeyouread.
LockLocksarebuiltinJava;everyObjecthasalockthatathreadmayacquirewhenitentersasynchronizedblock.Wediscussedthatalready.Insomeprogrammingcode,therearesituationswhenthiskindofstructureisnotoptimal.
Insomesituations,thestructureoflocksmaybelineduptoavoiddeadlock.ItmaybeneededtoacquirelockAbeforeBandtoacquireBbeforeC.However,Ashouldbereleasedassoonaspossible,nottopreventaccesstoresourceprotectedbylockD,butalsoneedinglockAbeforeit.Incomplexandhighlyparallelstructures,thelocksarestructuredmanytimesintotreeswhereaccessingaresourceathreadshouldclimbdownalongthetreetoaleafrepresentingtheresource.Inthisclimbing,thethreadgetsholdofalockonanode,thenalockonanodebelowit,andthenreleasesthelockabove,justlikearealclimberdescending(orclimbingupifyouimaginethetreewiththeleafsatthetop,whichismorerealistic,neverthelessgraphsusuallyshowtreesupsidedown).
Youcannotleaveasynchronizedblockremaininginanotherthatisinsidethefirstone.Synchronizedblocksarenested.Thejava.util.concurrent.LockinterfacedefinesmethodstohandlethatsituationandtheimplementationsarealsothereintheJDKtobeusedinourcode.Whenyouhavealock,youcancallthemethodslockandunlock.Theactualorderisinyourhandandyoucanwritethefollowinglineofcodetogetthelockingsequence:
a.lock();b.lock();a.unlock();c.lock()
Thefreedom,however,comeswithresponsibilityaswell.Thelocksandunlocksarenottiedtotheexecutionsequenceofthecode,likeincaseofsynchronizedblock,anditmaybeveryeasytocreatecodethatinsomecasejustlosesalocknotunlockingitrenderingsomeresourceunusable.Thesituationissimilartoamemoryleak:youwillallocate(lock)somethingandforgettorelease(unlock)it.Afterawhile,theprogramwillrunoutofresource.
Mypersonalrecommendationistoavoidusinglocksifpossibleandusehigher-levelconstructsandasynchronouscommunicationsbetweenthreads,suchasblockingqueues.
Condition
Thejava.util.concurrent.Conditioninterfaceinfunctionalityissimilartothebuilt-inwait,notify,andnotifyAll.AnyimplementationofLockshouldcreatenewConditionobjectsandreturnasaresulttotheinvocationofthenewConditionmethod.WhenthethreadhasaCondition,itcancallawait,signal,andsignalAllwhenthethreadhasthelockthatcreatedtheconditionobject.
ThefunctionalityisverysimilartothemethodsofObjectmentioned.However,thebigdifferenceisthatyoucancreatemanyConditionforasingleLockandtheywillworkindependentofeachother,butnotindependentoftheLock.
ReentrantLock
ReentrantLockisthesimplestimplementationoftheinterfacelockintheJDK.Therearetwowaystocreatethistypeoflock:withandwithoutfairnesspolicy.IftheReentrantLock(Booleanfair)constructoriscalledwiththetrueargument,thenthelockwillbeassignedtothethreadthatiswaitingforthelockthelongesttimeincasetherearemanythreadswaiting.Thiswillavoidathreadmadetowaitforinfinitetimeandstarving.
ReentrantReadWriteLockThisclassisanimplementationofReadWriteLock.ReadWriteLockisalockthatcanbeusedforparallelreadaccessandexclusivewriteaccess.Itmeansthatseveralthreadscanreadtheresourceprotectedbythelock,butwhenathreadwritestheresource,nootherthreadcangetaccesstoit,notevenreadduringthatperiod.AReadWriteLockissimplytwoLockobjectsreturnedbythereadLockandwriteLockmethods.TogetreadaccessonReadWriteLock,thecodehastoinvokemyLock.readLock().lock(),andtogetaccesstowritelock,myLock.writeLock().lock().Acquiringoneofthelocksandreleasingitintheimplementationiscoupledwiththeotherlock.Toacquireawritelock,nothreadshouldhaveanactivereadlock,forexample.
Thereareseveralintricaciesintheuseofthedifferentlock.Forexample,youcanacquireareadlock,butyoucannotgetawritelocksolongasyouhavethereadlock.Youhavetoreleasethereadlockfirsttoacquireawritelock.Thisisjustoneofthesimpledetails,butthisistheonethatnoviceprogrammershavetroublewithmanytimes.Whyisitimplementedthisway?Whyshouldtheprogramgetawritelock,whichismoreexpensive—insenseofhigherprobabilitylockingotherthreads—whenitstillisnotsurethatitwantstowritetheresource?Thecodewantstoreaditand.basedonthecontent.itmaylaterdecidethatitwantstowriteit.
Theissueisnotwiththeimplementation.Thedevelopersofthelibrarydecidedthisrule,notbecausetheyjustlikeditthatwayorbecausetheywereawareofparallelalgorithmsanddeadlockpossibilities.Whentwothreadshavereadlockandeachdecidestoupgradethelocktowritelock,thentheywouldintrinsicallycreateadeadlock.Eachwouldholdthereadlockwaitingforthewriteandnoneofthemwouldgetitever.
Ontheotherend,youcandowngradeawritelocktoareadlockwithoutriskingthatinthemeantimesomebodyacquiresawritelockandmodifiestheresource.
AtomicclassesAtomicclassesencloseprimitivevaluesintoobjectsandprovideatomicoperationsonthem.Wediscussedraceconditionsandvolatilevariables.Forexample,ifwehaveanintvariabletobeusedasacounterandwewanttoassignauniquevaluetoobjectsthatweworkwith,wecanincrementthevalueandusetheresultasauniqueID.However,whenmultiplethreadsusethesamecode,wecannotbesureaboutthevaluewereadaftertheincrement.Itmayhappenthatanotherthreadalsoincrementedthevalueinthemeantime.Toavoidthat,wewillhavetoenclosetheincrementandtheassignmentoftheincrementedvaluetoanobjectintoasynchronizedblock.ThiscanalsobedoneusingAtomicInteger.
IfwehaveavariableofAtomicInteger,thencallingincrementAndGetincrementsthevalueofintenclosedintheclassandreturnstheincrementedvalue.Whydoitinsteadofusingsynchronizedblock?ThefirstansweristhatifthefunctionalityisthereintheJDK,thenusingitislesslinethanimplementingitagain.DevelopersmaintainingthecodeyoucreateareexpectedtoknowtheJDKlibrariesbuthavetostudyyourcode,andthistakestimeandtimeismoney.
Theotherreasonisthattheseclassesarehighlyoptimizedand,manytimes,theyimplementthefeaturesusingplatformspecificnativecodethatgreatlyoverperformstheversionwecanimplementusingsynchronizedblocks.Worryingaboutperformancetooearlyisnotgood,butparallelalgorithmsandsynchronizationbetweenthreadsareusuallyusedwhenperformanceiscrucial;thus,thereisagoodchancethattheperformanceofthecodeusingtheatomicclassesisimportant.
Inthejava.util.concurrent.atomicpackage,thereareseveralclasses,AtomicInteger,AtomicBoolean,AtomicLong,andAtomicReferenceamongthem.Theyallprovidemethodsthatarespecifictotheencapsulatedvalue.
Themethod,whichisimplementedbyeveryatomicclass,iscompareAndSet.Thisisaconditionalvalue-settingoperationthathasthefollowingformat:
booleancompareAndSet(expectedValue,updateValue);
Whenitisappliedonanatomicclass,itcomparestheactualvaluewiththeoneexpectedValue,andiftheyarethesame,thenitsetsthevaluetoupdateValue.Ifthevaluewasupdated,themethodreturnstrueanditdoesallthisinanatomicaction.
Youmayaskthequestionthatifthismethodisinalloftheseclasses,whyistherenoInterfacedefiningthismethod?Thereasonforthisisthattheargumenttypesaredifferentbasedontheencapsulatedtype,andthesetypesareprimitives.Asprimitivescannotbeusedasgenerictypes,notevenagenericinterfacecanbedefined.IncaseofAtomicXXXArray,themethodhasanextrafirstargument,whichistheindexofthearrayelementhandledinthecall.
Thevariablesencapsulatedarehandledthesamewayasvolatile,asfarasthereorderingisconcerned,buttherearespecialmethodsthatloosentheconditionsabittobeusedwhenpossible,andperformanceiskey.
Thegeneraladviceistoconsiderusingatomicclasses,ifthereisoneusable,andyouwillfindyourself
creatingasynchronizedblockforcheck-and-set,atomicincrement,oradditionoperations.
BlockingQueue
BlockingQueueisaninterfacethatextendsthestandardQueueinterfacewithmethodsthataresuitabletobeusedbymultithreadapplications.Anyimplementationofthisinterfaceprovidesmethodsthatallowdifferentthreadstoputelementintothequeue,pullelementsoffthequeue,andwaitforelementsthatareinthequeue.
Whenthereisanewelementthatistobestoredinthequeue,youcanaddit,offerit,orputit.Thesearethenameofthemethodsthatstoreelementsandtheydothesamething,butabitdifferently.Theaddelementthrowsanexceptionifthequeueisfullandthereisnoroomfortheelement.Theofferelementdoesnotthrowexceptionbutreturnseithertrueorfalse,basedonthesuccess.Ifitcanstoretheelementinthequeue,itreturnstrue.Thereisalsoaversionofofferthatspecifiesatimeout.Thatversionofthemethodwaits,andreturnsonlyfalseifitcannotstorethevalueintothequeueduringtheperiod.Theputelementisthedumbestversion;itwaitsuntilitcandoitsjob.
Whentalkingaboutavailableroominaqueue,donotgetpuzzledandmixitwithgeneralJavamemorymanagement.Ifthereisnomorememory,andthegarbagecollectorcanalsonotreleaseany,youwillcertainlygetOutOfMemoryError.Exceptionisthrownbyadd,andfalseisreturnedbyoffer,whenthequeuelimitsarereached.SomeoftheBlockingQueueimplementationscanlimitthenumberofelementsthatcanbestoredatatimeinaqueue.Ifthatlimitisreached,thenthequeueisfullandcannotacceptmoreelements.
FetchingelementsfromaBlockingQueueimplementationalsohasfourdifferentways.Inthisdirection,thespecialcaseiswhenthequeueisempty.Inthatcase,removethrowsanexceptioninsteadofreturningtheelement,pollreturnsnullifthereisnoelement,andtakejustwaitsuntilitcanreturnanelement.
Finally,therearetwomethodsinheritedfromtheinterfaceQueuesthatdonotconsumetheelementfromthequeueonlylookat.Theelementreturntheheadofthequeueandthrowsanexceptionifthequeueisempty,andpeekreturnsnullifthereisnoelementinthequeue.Thefollowingtablesummarizestheoperationsborrowedfromthedocumentationoftheinterface:
Throwsexception Specialvalue Blocks Timesout
Insert add(e) offer(e) put(e) offer(e,time,unit)
Remove remove() poll() take() poll(time,unit)
Examine element() peek() notapplicable notapplicable
LinkedBlockingQueue
ThisisanimplementationoftheBlockingQueueinterface,whichisbackedupbyalinkedlist.Thesizeofthequeueisnotlimitedbydefault(tobeprecise,itisInteger.MAX_VALUE)butitcanoptionallybelimitedinaconstructorargument.Thereasontolimitthesizeinthisimplementationistoaidtheusewhentheparallelalgorithmperformsbetterwithlimitedsizequeue,buttheimplementationdoesnothaveanyrestrictiononthesize.
LinkedBlockingDeque
ThisisthesimplestimplementationoftheBlockingQueueandalsoitssubinterfaceBlockingDeque.Aswediscussedinthepreviouschapter,aDequeisadouble-endedqueuethathasadd,remove,offer,andsoon,typeofmethodsintheformofxxxFirstandxxxLasttodotheactwithoneortheotherendofthequeue.TheDequeinterfacedefinesgetFirstandgetLastinsteadofconsistentlynamingelementFirstandelementLast,sothisissomethingyoushouldgetusedto.Afterall,theIDEshelpwithautomaticcodecompletionsothisshouldnotbeareallybigproblem.
ArrayBlockingQueue
ArrayBlockingQueueimplementstheBlockingQueueinterface,hencetheQueueinterface.Thisimplementationmanagesaqueuewithfixedsizeelements.ThestorageintheimplementationisanarrayandtheelementsarehandledinaFIFOmanner:first-infirst-out.ThisistheclassthatwewillalsouseintheparallelimplementationofMastermindforthecommunicationbetweenthebossandthesubordinatedbureaucrats.
LinkedTransferQueue
TheTransferQueueinterfaceisextendingBlockingQueueandtheonlyimplementationofitintheJDKisLinkedTransferQueue.ATransferQueuecomeshandywhenathreadwantstohandoversomedatatoanotherthreadandneedstobesurethatsomeotherthreadtakestheelement.ThisTransferQueuehasamethodtransferthatputsanelementonthequeuebutdoesnotreturnuntilsomeotherthreadremoves(orpolls)it.Thatwaytheproducingthreadcanbesurethattheobjectputonthequeueisinthehandsofanotherprocessingthreadanddoesnotwaitinthequeue.ThemethodtransferalsohasaformattryTransferinwhichyoucanspecifysometimeoutvalue.Ifthemethodtimesouttheelementisnotputintothequeue.
IntervalGuesserWediscussedthedifferentJavalanguageelementsandJDKclassesthatareallavailabletoimplementparallelalgorithms.Now,wewillseehowtousetheseapproachestoimplementtheparallelguesserfortheMasterrmindgame.
TheclassthatperformsthecreationoftheguessesisnamedIntervalGuesser.ItcreatestheguessesbetweenastartandanendguessandsendsthemtoaBlockingQueue.TheclassimplementsRunnablesoitcanruninaseparateThread.ThepuristimplementationwillseparatetheRunnablefunctionalityfromtheintervalguessing,butasthewholeclassishardlymorethan50lines,itisforgivablesinimplementingthetwofunctionalitiesinasingleclass.
publicclassIntervalGuesserextendsUniqueGuesserimplementsRunnable{
privatefinalGuessstart;
privatefinalGuessend;
privateGuesslastGuess;
privatefinalBlockingQueue<Guess>guessQueue;
publicIntervalGuesser(Tabletable,Guessstart,Guessend,BlockingQueue<Guess>guessQueue){
super(table);
this.start=start;this.end=end;
this.lastGuess=start;
this.guessQueue=guessQueue;
nextGuess=start;
}
@Override
publicvoidrun(){
Guessguess=guess();
try{
while(guess!=Guess.none){
guessQueue.put(guess);
guess=guess();
}
}catch(InterruptedExceptionignored){
}
}
@Override
protectedGuessnextGuess(){
Guessguess;
guess=super.nextGuess();
if(guess.equals(end)){
guess=Guess.none;
}
lastGuess=guess;
returnguess;
}
publicStringtoString(){
return"["+start+","+end+"]";
}
}
TheimplementationisverysimpleasmostofthefunctionalityisalreadyimplementedintheabstractGuesserclass.ThemoreinterestingcodeistheonethatinvokedtheIntervalGuesser.
ParallelGamePlayerTheParallelGamePlayerclassimplementsthePlayerinterfacethatdefinestheplaymethod:
@Override
publicvoidplay(){
Tabletable=newTable(NR_COLUMNS,manager);
Secretsecret=newRandomSecret(manager);
GuesssecretGuess=secret.createSecret(NR_COLUMNS);
Gamegame=newGame(table,secretGuess);
finalIntervalGuesser[]guessers=createGuessers(table);
startAsynchronousGuessers(guessers);
finalGuesserfinalCheckGuesser=newUniqueGuesser(table);
try{
while(!game.isFinished()){
finalGuessguess=guessQueue.take();
if(finalCheckGuesser.guessMatch(guess)){
game.addNewGuess(guess);
}
}
}catch(InterruptedExceptionie){
}finally{
stopAsynchronousGuessers(guessers);
}
}
ThismethodcreatesaTable,aRandomSecretthatcreatestheguessusedasasecretinarandomway,aGameobject,IntervalGuessers,andaUniqueGuesser.TheIntervalGuessersarethebureaucrats;theUniqueGuesseristhebosswhocrosscheckstheguessesthattheIntervalGuesserscreate.ThemethodstartsofftheasynchronousguessersandthenreadstheguessesinaloopfromthemandputsthemonthetableiftheyareOKuntilthegamefinishes.Attheendofthemethod,inthefinallyblock,theasynchronousguessersarestopped.
ThestartandthestopmethodfortheasynchronousguessersuseExecutorService.
privateExecutorServiceexecutorService;
privatevoidstartAsynchronousGuessers(
IntervalGuesser[]guessers){
executorService=Executors.newFixedThreadPool(nrThreads);
for(IntervalGuesserguesser:guessers){
executorService.execute(guesser);
}
}
privatevoidstopAsynchronousGuessers(
IntervalGuesser[]guessers){
executorService.shutdown();
guessQueue.drainTo(newLinkedList<>());
}
Thecodeisquitestraightforward.Theonlythingthatmayneedmentionisthatthequeueoftheguessesisdrainedintoacollectionthatwedonotuseafterward.ThisisneededtohelpanyIntervalGuesserthatiswaitingwithasuggestedguessinhand,tryingtoputitintothequeue.Whenwedrainthequeue,theguesserthreadreturnsfromthemethodputintheguessQueue.put(guess);lineinIntervalGuesserandcancatchtheinterrupt.TherestofthecodedoesnotcontainanythingthatwouldberadicallydifferentfromwhatwehavealreadyseenandyoucanfinditonGitHub.
Thelastquestionthatwestillwanttodiscussinthischapterishowmuchspeeddidwegainmakingthecodeparallel?
MicrobenchmarkingMicrobenchmarkingismeasuringtheperformanceofasmallcodefragment.Whenwewanttooptimizeourcode,wewillhavetomeasureit.Withoutmeasurement,codeoptimizationislikeshootingblindfolded.Youwillnothitthetarget,butyoulikelywillshootsomebodyelse.
Shootingisagoodmetaphorbecauseyoushouldusuallynotdoit,butwhenyoureallyhavetothenyouhavenochoice.Ifthereisnoperformanceissueandthesoftwaremeetstherequirements,thenanyoptimization,includingspeedmeasurement,isawasteofmoney.Thisdoesnotmeanthatyouareencouragedtowriteslowandsloppycode.Whenwemeasureperformance,wewillcompareitagainstarequirement,andtherequirementisusuallyontheuserlevel.Somethinglike,theresponsetimeoftheapplicationshouldbelessthan2seconds.Todosuchameasurement,weusuallycreateloadtestsinatestenvironmentandusedifferentprofilingtoolsthattelluswhatisconsumingthemosttimeandwhereweshouldoptimize.Manytimes,itisnotonlyJavacode,butconfigurationoptimization,usinglargerdatabaseconnectionpool,morememory,andsimilarthings.
Microbenchmarkingisadifferentstory.ItisabouttheperformanceofasmallJavacodefragmentand,assuch,closertotheJavaprogramming.
Itisrarelyused,andbeforestartingtodoamicrobenchmarkforrealcommercialenvironment,wewillhavetothinktwice.Microbenchmarkisaluringtooltooptimizesomethingsmallwithoutknowingifitisworthoptimizingthatcode.Whenwehaveahugeapplicationthathasseveralmodulesrunonseveralservers,howcanwebesurethatimprovingsomespecialpartoftheapplicationdrasticallyimprovestheperformance?Willitpaybackinincreasedrevenuethatgeneratessomuchprofitthatwillcoverthecostweburnedintotheperformancetestinganddevelopment?Statistically,almostsurethatsuchanoptimizationincludingmicrobenchmarkingwillnotpayoff.
OnceIwasmaintainingthecodeofasenior'scolleague.Hecreatedahighlyoptimizedcodetorecognizeconfigurationkeywordsthatwerepresentinafile.Hecreatedaprogramstructurethatrepresentedadecisiontreebasedonthecharactersinthekeystring.Iftherewasakeywordintheconfigurationfilethatwasmisspelled,thecodethrewanexceptionattheveryfirstcharacterwhereitcoulddecidethatthekeywordcouldnotbecorrect.Toinsertanewkeyword,itneededtogetthroughthecodestructuretofindtheoccasioninthecodewherethenewkeywordwasfirstdifferentfromalreadyexistingonesandextendthedeeplynestedif/elsestructures.Toreadthelistofthehandledkeywordswaspossiblefromthecommentsthatlistedallthekeywordsthathedidnotforgettodocument.Thecodewasworkingblazinglyfast,probablysavingafewmillisecondsoftheservletapplicationstartuptime.Theapplicationwasstarteduponlyaftersystemmaintenanceeveryfewmonth.Youfeeltheirony,don'tyou?Seniorityisnotalwaysthenumberofyears.Luckyonescansavetheirinnerchild.
Sowhentousemicrobenchmarking?Icanseetwoareas:
YouidentifiedthecodesegmentthateatsmostoftheresourcesinyourapplicationandtheimprovementcanbetestedbymicrobenchmarksYoucannotidentifythecodesegmentthatwilleatmostoftheresourcesinanapplicationbutyoususpectit
Thefirstistheusualcase.Thesecondiswhenyoudevelopalibrary,andyoujustdonotknowalltheapplicationsthatwilluseit.Inthiscase,youwilltrytooptimizethepartthatyouthinkisthemostcrucialformostoftheimagined,suspectedapplications.Eveninthatcase,itisbettertotakesomesampleapplicationsthatarecreatedbyusersofyourlibraryandcollectsomestatisticsabouttheuse.
Whyshouldwetalkaboutmicrobenchmarkingindetails?Whatarethepitfalls?Benchmarkingisanexperiment.ThefirstprogramsIwrotewasaTIcalculatorcodeandIcanjustcountthenumberofstepstheprogrammadetofactortwolarge(10digitsthosedays)primenumbers.Evenatthattime,IwasusinganoldRussianstopwatchtomeasurethetime,beinglazytocalculatethenumberofsteps.Experimentandmeasurementwaseasier.
Today,youcannotcalculatethenumberofstepstheCPUmakesevenifyouwanted.Therearesomanysmallfactorsthatmaychangetheperformanceoftheapplicationsthatareoutofcontroloftheprogrammer,whichmakesitimpossibletocalculatethesteps.Wehavethemeasurementleftforus,andwewillgainalltheproblemsofmeasurements.
Whatisthebiggestproblem?Weareinterestedinsomething,sayX,andweusuallycannotmeasurethat.So,wewillmeasureYinsteadandhopethatthevaluesofYandXarecoupledtogether.Wewanttomeasurethelengthoftheroom,butinsteadwemeasurethetimeittakesforthelaserbeamtotravelfromoneendtotheother.Inthiscase,thelengthXandthetimeYarestronglycoupled.Manytimes,XandYonlycorrelatemoreorless.Mostofthetimes,whenpeopledomeasurement,theXandYvalueshavenorelationtoeachotheratall.Still,peopleputtheirmoneyandmoreondecisionsbackedbysuchmeasurements.
Microbenchmarkingisnodifferent.Thefirstquestionishowtomeasuretheexecutiontime?SmallcoderunsshorttimesandSystem.currentTimeMillis()mayjustreturnthesamevaluewhenthemeasurementstartsandwhenitends,becausewearestillinthesamemillisecond.Eveniftheexecutionis10ms,theerrorofthemeasurementisstillatleast10%purelybecauseofthequantizationofthetimeaswemeasure.Luckily,thereisSystem.nanoTime().Butisthere?Justbecausethenamesaysitreturnsthenumberofnanosecondsfromaspecificstarttime,itdoesnotnecessarilymeanitreallycan.
ItverymuchdependsonthehardwareandtheimplementationofthemethodintheJDK.Itiscallednanobecausethisistheprecisionthatwecannotcertainlyreach.Ifitwasmicroseconds,thensomeimplementationmaybelimitedbythedefinition,evenifonthespecifichardware,thereisamorepreciseclock.However,thisisnotonlytheprecisionofanavailablehardwareclock;itisabouttheprecisionofthehardware.
Let'sremembertheheartbeatofthebureaucrats,andthetimeneededtoreadsomethingfrommemory.Callingamethod,suchasSystem.nanoTime(),islikeaskingthebellboyinahoteltorundownfromthesecondfloortothelobbyandpeekouttolookattheclockonthetowerontheothersideoftheroad,comeback,andtellsecondsprecisionwhatthetimewasitwhenweasked.Nonsense.Weshouldknowthe
precisionoftheclockonthetowerandthespeedofthebellboyrunningfromthefloortothelobbyandback.ThisisabitmorethanjustcallingnanoTime.Thisiswhatamicrobenchmarkingharnessdoesforus.
TheJavaMicrobenchmarkingHarness(JMH)isavailableforsometimeasalibrary.ItisdevelopedbyOracleandusedtotunetheperformanceofsomecoreJDKclasses,andwithJava9,theseperformancemeasurementsandresultsbecomepartofthedistributedJDK.ThisisgoodnewsforthosewhodevelopJavaplatformfornewhardware,butalsofordevelopers,becauseitmeansthattheJMHisandwillbesupportedbyOracle.
"JMHisaJavaharnesstobuild,run,andanalyzenano/micro/milli/macrobenchmarkswritteninJavaandotherlanguagestargetingtheJVM."(quotefromtheofficialsiteofJMH,http://openjdk.java.net/projects/code-tools/jmh/).
Youcanrunjmhasaseparateprojectindependentfromtheactualprojectyoumeasure,oryoucanjuststorethemeasurementcodeinaseparatedirectory.Theharnesswillcompileagainsttheproductionclassfilesandwillexecutethebenchmark.Theeasiestway,asIsee,istousetheGradleplugintoexecuteJMH.Youcanstorethebenchmarkcodeinadirectorycalledjmh(thesamelevelasmainandtest)andcreateamainthatcanstartthebenchmark.
TheGradlebuildscriptisextendedwiththefollowinglines:
buildscript{
repositories{
jcenter()
}
dependencies{
classpath"me.champeau.gradle:jmh-gradle-plugin:0.2.0"
}
}
applyplugin:"me.champeau.gradle.jmh"
jmh{
jmhVersion='1.13'
includeTests=true
}
Andthemicrobenchmarkclassisasfollows:
publicclassMicroBenchmark{
publicstaticvoidmain(String...args)
throwsIOException,RunnerException{
Optionsopt=newOptionsBuilder()
.include(MicroBenchmark.class.getSimpleName())
.forks(1)
.build();
newRunner(opt).run();
}
@State(Scope.Benchmark)
publicstaticclassThreadsAndQueueSizes{
@Param(value={"1","4","8"})
StringnrThreads;
@Param(value={"-1","1","10","100","1000000"})
StringqueueSize;
}
@Benchmark
@Fork(1)
publicvoidplayParallel(ThreadsAndQueueSizest3qs)throwsInterruptedException{
intnrThreads=Integer.valueOf(t3qs.nrThreads);
intqueueSize=Integer.valueOf(t3qs.queueSize);
newParallelGamePlayer(nrThreads,queueSize).play();
}
@Benchmark
@Fork(1)
publicvoidplaySimple(){
newSimpleGamePlayer().play();
}
}
ParallelGamePlayeriscreatedtoplaythegamewith-1,1,4,and8IntervalGuesserthreads,andineachcase,thereisatestrunningwithaqueueoflength1,10,100,and1million.Theseare16testexecutions.Whenthenumberofthreadsisnegative,thentheconstructorusesLinkedBlockingDeque.Thereisanotherseparatemeasurementthatmeasuresthenonparallelplayer.Thetestwasexecutedwithuniqueguessesandsecrets(nocolorusedmorethanonce)andtencolorsandsixcolumns.
Whentheharnessstarts,itdoesallthecalibrationsautomaticallyandrunsthetestsformanyiterationstolettheJVMstartup.Youmayrecallthecodethatjustneverstoppedunlessweusedthevolatilemodifierinforthevariablethatwasusedtosignalthecodetostop.ThathappenedbecausetheJITcompileroptimizedthecode.Thisisdoneonlywhenthecodewasalreadyrunafewthousandtimes.TheharnessmakestheseexecutionstowarmupthecodeandensurethatthemeasurementisdonewhenJVMisatfullspeed.
Runningthisbenchmarktakesapproximately15minutesonmymachine.Duringtheexecution,itisrecommendedtostopallotherprocessesandletthebenchmarkuseallavailableresources.Ifthereisanythingusingresourcesduringthemeasurement,thenitwillbereflectedintheresult.
Benchmark(nrThreads)(queueSize)ScoreError
playParallel1-115,636±1,905
playParallel1115,316±1,237
playParallel11015,425±1,673
playParallel110016,580±1,133
playParallel1100000015,035±1,148
playParallel4-125,945±0,939
playParallel4125,559±1,250
playParallel41025,034±1,414
playParallel410024,971±1,010
playParallel4100000020,584±0,655
playParallel8-124,713±0,687
playParallel8124,265±1,022
playParallel81024,475±1,137
playParallel810024,514±0,836
playParallel8100000016,595±0,739
playSimpleN/AN/A18,613±2,040
Theactualoutputoftheprogramisabitmoreverbose;itwaseditedforprintingpurposes.TheScorecolumnshowshowmanytimesthebenchmarkcanruninasecond.TheErrorshowsthatthemeasurementshowslessthan10%scattering.
Thefastestperformancewehaveiswhenthealgorithmrunsoneightthreads,whichisthenumberofthreadstheprocessorcanindependentlyhandleonmymachine.Itisinterestingthatlimitingthesizeofthequeuedidnothelptheperformance.Iactuallyexpectedittobedifferent.Usingaonemillionlengtharrayasablockingqueuehasahugeoverheadandthisisnotasurprisethat,inthiscase,theexecutionisslowerthanwhenwehaveonly100elementsinthequeue.Theunlimitedlinkedlist-basedqueuehandling,ontheotherhand,fairlyfastandclearlyshowsthattheextraspeedatthelimitedqueuefor100elementsdoesnotcomefromthefactthatthelimitdoesnotallowtheIntervalThreadstoruntoofar.
Whenwestartonethread,thenweexpectsimilarresults,aswhenweruntheserialalgorithm.Thefactthattheserialalgorithmbeatstheparallelalgorithmrunningononethreadisnotasurprise.Thethreadcreationandthecommunicationbetweenthemainthreadandtheextraonethreadhaveoverhead.Theoverheadissignificant,especiallywhenthequeueisunnecessarilylarge.
SummaryInthischapter,youlearnedalotofthings.Firstofall,werefactoredthecodetobereadyforfurtherdevelopmentthatusesparallelguessing.Wegotacquaintedwithprocessesandthreads,andweevenmentionedfibers.Afterthat,welookedathowJavaimplementsthreadsandhowtocreatecodethatrunsonmultiplethreads.Additionally,wesawthedifferentmeansthatJavaprovidestoprogrammersneedingparallelprograms,startingthreads,orjuststartingsometasksinalreadyexistingthreads.
Perhapsthemostimportantpartofthischapterthatyoushouldrememberisthemetaphorofbureaucratsandthedifferentspeeds.Thisisextremelyimportantwhenyouwanttounderstandtheperformanceofconcurrentapplications.AndIhopethatthisisacatchypicture,whichiseasytoremember.
TherewasahugetopicaboutthedifferentsynchronizationmeansthatJavaprovides,andyouhavealsolearnedaboutthepitfallsthatprogrammerscanfallintowhenprogrammingconcurrentapplications.
Lastbutnotleast,wecreatedtheconcurrentversionoftheMastermindguesserandalsomeasuredthatitisindeedfasterthantheversionthatusesonlyoneprocessor(atleastonmymachine).WeusedtheJavaMicrobenchmarkHarnesswiththeGradlebuildtoolanddiscussed,abit,howtoperformmicrobenchmarking.
Thiswasalongchapterandnotaneasyone.Imaytendtothinkthatthisisthemostcomplexandmosttheoreticalone.Ifyouunderstoodhalfofitatfirstread,youcanbeproud.Ontheotherhand,beawarethatthisisonlyagoodbasetostartexperimentingwithconcurrentprogrammingandthereisalongwaytobeingseniorandprofessionalinthisarea.And,itisnotaneasyone.Butfirstofall,beproudofyourselfattheendofthischapter.
Inthefollowingchapterswewilllearnmoreaboutwebandwebprogramming.Intheverynextchapterwewilldevelopourlittlegamesothatitcanruninaserverandtheplayercanplaywithitusingawebbrowser.Thiswillestablishthebasicknowledgeforwebprogramming.Laterwewillbuildonthisdevelopingwebbasedserviceapplications,reactiveprogrammingandallthetoolsandareasthatwillmakeaprofessionalJavadeveloper.
MakingOurGameProfessional-DoitasaWebappInthischapter,wewillprogramawebapplication.WewillbuildonwhatwehaveachievedalreadyandcreateawebversionoftheMastermindgame.Thistime,itwillnotonlyrunalone,guessingandansweringthenumberofpositionsandmatchedcolors,butalsocommunicatewiththeuseraskingfortheanswerstotheguesses.Thiswillbearealgame.WebprogrammingisextremelyimportantforJavaprogrammers.Mostoftheprogramsarewebapplications.TheuniversalclientavailableontheInternetisthewebbrowser.Thethin-client,webbrowser-basedarchitectureiswidelyacceptedinenterprisesaswell.Thereareonlysomeexceptionswhenthearchitecturehassomethingelsebutthewebclient.IfyouwanttobecomeaprofessionalJavadeveloper,youmustbefamiliarwithwebprogramming.Anditisalsofun!
Therearealotoftechnicaltopicsthatwewillvisitduringthedevelopment.Firstofall,wewilldiscussnetworkingandwebarchitecture.Thisistheconcretebaseofthewholebuilding.Itisnottoosexy,justlikewhenyouconstructthebuilding.Youspendalotofmoneyandeffortdiggingtrenches,andthenyouburytheconcreteandendupattheendofthephasewithwhatyouseeminglyhadbefore:flatground.Exceptthatthereisthebase.Buildingwithoutthisbase,thehousewouldeithercollapsesoonafterorduringtheprocessofbuilding.Networkingisjustasimportantforwebprogramming.Therearealotoftopicsthatseeminglyhavenothingtodowithprogramming.Still,itisthebaseofthebuildingandwhenyouprogramwebapplications,youwillalsofindthefunpartinit.
WewillalsotalkabitaboutHTML,CSS,andJavaScript,butnottoomuch.Wecannotavoidthembecausetheyarealsoimportantforwebprogramming,buttheyaretopicsthatyoucanlearnfromsomewhereelseaswell.Incaseyouarenotanexpertinsomeoftheseareas,thereareusuallyotherexpertsinenterpriseprojectteamswhocanextendyourknowledge.(Inthecaseofnetworking,thereisnomercy.)Inadditiontothat,JavaScriptisatopicsocomplexandhugethatitdeservesawholebooktostartwithit.ThereareonlyveryfewexpertswhodeeplyunderstandbothJavaandJavaScript.Iunderstandthegeneralstructureofthelanguageandtheenvironmentitrunsin,butIcannotkeepupwiththenewframeworksthatarereleasedeveryweekthesedays,havingmyfocusonotherareas.
YouwilllearnhowtocreateJavaapplicationsthatruninanapplicationserver,thistimeinJetty,andwewillseewhataservletis.Wewillcreateawebhelloworldapplicationtostartupfast,andthenwewillcreatetheservletversionofMastermind.Notethatwehardlyeverprogramservletsdirectlywithouttheaidofsomeframeworkthatimplementsthecodetohandleparameters,authentication,andmanyotherthingsthatarenotapplication-specific.Wewillstillsticktoanakedservletinthischapterbecauseitisnotpossibletoeffectivelyuseframeworks,suchasSpring,withoutfirstunderstandingwhataservletis.Springwillcomeinthenextchapter.
WewillmentionJavaServerPages(JSP)onlybecauseyoumaymeetsomelegacyapplication,whichwasdevelopedusingthattechnology,butmodernwebapplicationsdonotuseJSP.Still,JSPisapartoftheservletstandardandisavailableforuse.Thereareothertechnologiesthatweredevelopedintherecentpastbutdonotseemtobefuture-proofthesedays.Theyarestillusablebutappearonlyinlegacy
applications,andchoosingthemforanewprojectisfairlyquestionable.Wewilltalkaboutthesetechnologiesshortlyinaseparatesection.
Bytheendofthischapter,youwillunderstandhowthebasicwebtechnologyworksandwhatthemajorarchitecturalelementsare,andyouwillbeabletocreatesimplewebapplications.ThisisnotenoughtobeaprofessionalJavawebdeveloperbutwillbeagoodgroundingforthenextchapter,wherewewillhavealookattheprofessionalframeworksusedintoday'senterprisesforrealapplicationdevelopments.
Webandnetwork
Programsrunoncomputers,andcomputersareconnectedtotheInternet.Thisnetworkwasdevelopedinthelast60years,firsttoprovidemilitarydatacommunicationthatisresilienttorocketattack,thenitwasextendedtobeanacademicnetwork,andlateritbecameacommercialnetworkusedbyanyoneandavailablealmostubiquitouslyallovertheEarth.
Thedesignofthenetwork,andtheresearch,startedasaresponsetotheflightofGagarinovertheEarthinthefifties.SendingGagarintospaceandtravellingovertheEarthwasademonstrationthatRussiacouldsendarocketanywhereontheglobe,possiblywithatomicexplosives.Itmeantthatanydatanetworkthatneededsomecentralcontrolwasnotresilienttosuchanattack.Itwasnotfeasibletohaveanetworkwithacentrallocationasasinglepointoffailure.Therefore,researchwasstartedtocreateanetworkthatgoesonworkingevenifanypartofitisbroughtdown.
IPThenetworkdeliversdatapacketsbetweenanytwocomputersconnectedtoit.TheprotocolusedonthenetworkisIP,whichissimplyanabbreviationofInternetProtocol.UsingIP,acomputercansendadatapackettoanother.Thepackagecontainsaheaderandthedatacontent.TheheadercontainstheInternetaddressesofthesenderandthetargetmachine,otherflags,andinformationaboutthepackage.Sincethemachinesarenotconnectedtoeachotherdirectly,routersforwardthepackets.Itislikepostofficessendingmailstoeachothertillitgetsintothehandsofthepostmanyouknow,whocandirectlydeliverittoyourmailbox.Todothat,theroutersusetheinformationintheheader.ThealgorithmandorganizationofhowtheroutersinteractarecomplexandsomethingweneednotknowtobeJavaprofessionals.
IfyoueverneedtoprograminordertosendIPpacketsdirectly,youshouldlookatjava.net.DatagramPacket,andtherestisimplementedintheJDK,theoperatingsystem,andonthefirmwareofthenetworkcard.Youcancreateadatapacket;sendingitandchangingthemodulatedvoltageonthenetworkcardoremittingphotonstothefiberisnotyourheadache.However,youwillallknowwhetheryoureallyneedtoprogramdatagramsdirectly.
IPhastwoversions.TheoldversionstillinuseisIPv4.ThenewversionthatcoexistswiththeoldoneisIPv6orIPng(ngstandsfornewgeneration).ThemajordifferencethatmayconcernaJavadeveloperisthatversion4uses32-bitaddressesandversion6uses128-bitaddresses.Whenyouseeaversion-4address,youwillseesomethinglike192.168.1.110,whichcontainsthefourbytesinadecimalformatseparatedbydots.IPv6addressesareexpressedas2001:db8:0:0:0:0:2:1,aseight16-bitnumbersexpressedinhexadecimalseparatedbycolons.
TheWebisabitmorecomplexthansendingdatapackets.Ifsendingadatapacketislikesendingaone-pageletter,thenawebpagedownloadislikediscussingacontractinpapermail.Thereshouldbeanagreementintheinitialpapermailastowhattosend,whattoanswer,andsoon,untilthecontractissigned.OntheInternet,thatprotocoliscalledTransmissionControlProtocol(TCP).Whileitishighlyunlikely(butpossible)thatyouwillmeetIProutingissues,beingaJavadeveloper,youcertainlymaymeetTCPprogramming.Therefore,wewillcovershortlyhowtheTCPworks.Beawarethatthisisverybrief.Really.YouwillnotbecomeaTCPexpertreadingthenextsection,butyouwillgetaglimpseofthemostimportantissuesthataffectwebprogramming.
TCP/IPTheTCPprotocolisimplementedintheoperatingsystemandprovidesahigherlevelofinterfacethanIP.WhenyouprogramTCP,youdonotdealwithdatagrams.Instead,youhaveachannelofbytestreamswhereyoucanputbytestobedeliveredtotheothercomputer,andyoucanreadbytesfromthechannelthatweresentbytheothercomputer,exactlyintheorderastheyweresent.Thisisakindofconnectionbetweentwocomputersand,what'smore,betweentwoprograms.
ThereareotherprotocolsthatareimplementedoverIPandwhicharenotconnection-oriented.OneofthemisUserDatagramProtocol(UDP),usedforserviceswhenthereisnoneedforconnections,whenthedatamaybelostanditismoreimportantthatthedatagetstothedestinationinatimelymannerthanlosingsomeofthepackets(videostreaming,telephony).Whenthedataamountissmallandincaseitisnotdelivered,itcanberequestedagain;thecostoflosingitischeap(DNSrequest,seethenextsection).
Whenapacketislostonthenetwork,orwhenitissenttwice,orwhenitisdeliveredsoonerthanalaterpackage,itishandledbytheTCPsoftwarelayerimplementedbytheoperatingsystem.ThislayerisalsopopularlycalledtheTCPstack.
SincetheTCPisaconnectedprotocol,thereisaneedforsomethingthattellstheTCPstackwhichstreamadatagrambelongstowhenitarrives.Thestreamisidentifiedbytwoports.Aportisa16-bitinteger.Oneidentifiestheprogramthatinitiatestheconnection,calledthesourceport.Theotheroneidentifiesthetargetprogram:thedestinationport.ThesearecontainedineachandeveryTCPpacketdelivered.WhenamachinerunsaSecureShell(SSH)serverandawebserver,theyusedifferentports,usuallyport22and80.Whenapackagecomesthatcontainsthedestinationportnumber22intheTCPheader,theTCPstackknowsthatthedatainthepacketbelongstothestreamhandledbytheSSHserver.Likewise,ifthedestinationportis80,thenthedatagoestothewebserver.
Whenweprogramaserver,weusuallyhavetodefinetheportnumber;otherwise,thereisnowaytheclientswillfindtheserverprogram.Webserversareusuallylistenonport80,andclientstrytoconnecttothatport.Theclientportisusuallynotimportantandnotspecified;itisallocatedbytheTCPstackautomatically.
Toconnectfromaclientcodetoaserveriseasy:onlyafewlinesofcode.Sometimes,itisonlyonelineofcode.However,underthehood,thereisalotofworkthattheTCPstackdoesthatweshouldcareabout—ittakestimetobuildupaTCPconnection.
Tohaveaconnection,theTCPstackhastosendadatagramtothedestinationtoknowthatitexists.Ifthereisnoserverlisteningontheport,sendingthedataoverthenetworkhasnoresult,exceptforwastingthenetworkbandwidth.Forthisreason,theclientfirstsendsanemptydatapacketcalledSYN.Whentheothersidereceivesit,itsendsbackasimilarpackagecalledSYN-ACK.Finally,theclientsendsapackagecalledACK.IfthepacketsgothroughtheAtlantic,thisisapproximately45msforeachpackage,whichisequivalentto45millionsecondsinbureaucrattime.Thisisalmostoneandahalfyears.Weneedthreeofthosetosetuptheconnection,andthereismore.
WhenaTCPconnectionstarts,theclientdoesnotstarttosendthedatawithoutcontrol.Itsendssomedatapacketsandthenitwaitsfortheservertoacknowledgetheirreceipt.Itwouldnotonlybeuseless,butalsonetworkwasting,tosenddatathattheserverisnotpreparedtoacceptandhastothrowaway.TheTCPisdesignedtooptimizethenetworkusage.Therefore,theclientsendssomedata,andthenitwaitsfortheacknowledgement.TheTCPstackautomaticallymanagesthis.Iftheacknowledgementarrives,itsendsmorepackets,andifacarefullydesignedoptimizationalgorithm,implementedintheTCPstack,believesthatitisgoodtosendmore,itwillsendabitmoredatathaninthefirststep.Iftherearenegativeacknowledgementstellingtheclientthattheservercouldnotacceptsomeofthedataandhadtothrowitaway,thentheclientwilllowerthenumberofpacketsitsendswithoutacknowledgement.Butfirstitstartsslowandcautious.ThisiscalledTCPslowstartandwehavetobeawareofit.AlthoughitisalowlevelnetworkingfeatureithasconsequencesthatwehavetoconsiderinourJavacode:weusedatabaseconnectionpoolsinsteadofcreatinganewconnectiontothedatabaseeachtimethereisaneedforsomedata;wetrytomanagetohaveasfewconnectionstowebserversaspossibleusingtechniquessuchaskeep-alive,SPDYprotocol,orhttp/2.0(alsoreplacingSPDY).
Forastart,itisenoughthatTCPisconnection-orientedwhereyoubuildupaconnectiontoaserver,sendandreceivebytes,andfinallyclosetheconnection.Whenyouhaveanetworkperformanceproblem,youhavetolookattheissuesIlisted.
DNS
TheTCPprotocolcreatesachannelusingtheIPaddressesofmachines.WhenyoutypeaURLinthebrowser,itusuallydoesnotcontainIPnumbers.Itcontainsmachinenames.ThenameisconvertedtoIPnumbersusingadistributeddatabasecalledDomainNameSystem(DNS).Thisdatabaseisdistributed,andwhenaprogramneedstoconvertanametoanaddress,itsendsDNSrequesttooneoftheDNSserversitknows.Theseserversqueryeachotherortelltheclientwhomtoask,untiltheclientknowstheIPaddressassignedtothename.Theserversandtheclientalsocachetherecentlyrequestednames,soansweringisfast.Ontheotherhand,whentheIPaddressofaserverchangesthisname,notallclientswillimmediatelyseetheaddressassignmentovertheglobe.TheDNSlookupcanbeeasilyprogrammed,andthereareclassesandmethodsinJDKthatsupportthis,butusuallyweneednotcareaboutthat;whenweprogram,itisdoneautomaticallyinwebprogramming.
TheHTTPprotocolTheHypertextTransportProtocol(HTTP)isbuiltontopoftheTCP.WhenyoutypeaURLinabrowser,thebrowseropensaTCPchanneltotheserver(afterDNSlookup,ofcourse)andsendsaHTTPrequesttothewebserver.Theserver,afterreceivingtherequest,producesaresponseandsendsittotheclient.Afterthat,theTCPchannelmaybeclosedorkeptaliveforfurtherHTTPrequest-responsepairs.
Boththerequestandtheresponsecontainaheaderandanoptional(possiblyzero-length)body.Theheaderisinthetextformat,anditisseparatedfromthebodybyanemptyline.
Morepreciselytheheaderandthebodyareseparatedbyfourbytes:0x0D,0x0A,0x0D,and0x0A,whicharetwoCR,LFlineseparators.TheHTTPprotocolusescarriagereturnandlinefeedtoterminatelinesintheheader,andthus,anemptylineistwoCRLFfollowingeachother.
Thestartoftheheaderisastatuslineplusheaderfields.ThefollowingisasampleHTTPrequest:
GET/html/rfc7230HTTP/1.1
Host:tools.ietf.org
Connection:keep-alive
Pragma:no-cache
Cache-Control:no-cache
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0(Macintosh;IntelMacOSX10_11_6)AppleWebKit/537.36(KHTML,likeGecko)Chrome/52.0.2743.116Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
DNT:1
Referer:https://en.wikipedia.org/
Accept-Encoding:gzip,deflate,sdch,br
Accept-Language:en,hu;q=0.8,en-US;q=0.6,de;q=0.4,en-GB;q=0.2
Thefollowingistheresponse:
HTTP/1.1200OK
Date:Tue,04Oct201613:06:51GMT
Server:Apache/2.2.22(Debian)
Content-Location:rfc7230.html
Vary:negotiate,Accept-Encoding
TCN:choice
Last-Modified:Sun,02Oct201607:11:54GMT
ETag:"225d69b-418c0-53ddc8ad0a7b4;53e09bba89b1f"
Accept-Ranges:bytes
Cache-Control:max-age=604800
Expires:Tue,11Oct201613:06:51GMT
Content-Encoding:gzip
Strict-Transport-Security:max-age=3600
X-Frame-Options:SAMEORIGIN
X-Xss-Protection:1;mode=block
X-Content-Type-Options:nosniff
Keep-Alive:timeout=5,max=100
Connection:Keep-Alive
Transfer-Encoding:chunked
Content-Type:text/html;charset=UTF-8
<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<htmlxmlns="http://www.w3.org/1999/xhtml"xml:lang="en"lang="en">
<headprofile="http://dublincore.org/documents/2008/08/04/dc-html/">
<metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/>
<metaname="robots"content="index,follow"/>
Therequestdoesnotcontainabody.Thestatuslineisasfollows:
GET/html/rfc7230HTTP/1.1
Itcontainstheso-calledmethodoftherequest,theobjectthatisrequested,andtheprotocolversionusedbytherequest.Therestoftherequestoftheheadercontainsheaderfieldsthathavetheformat,label:value.Someofthelinesarewrappedintheprintedversion,butthereisnolinebreakinaheaderline.
Theresponsespecifiestheprotocolituses(usuallythesameastherequest),thestatuscode,andthemessageformatofthestatus:HTTP/1.1200OK
Afterthis,theresponsefieldscomewiththesamesyntaxasintherequest.Oneimportantheaderisthecontenttype:
Content-Type:text/html;charset=UTF-8
Itspecifiesthattheresponsebody(truncatedintheprintout)isHTMLtext.
TheactualrequestwassenttotheURL,https://tools.ietf.org/html/rfc7230,whichisthestandardthatdefinesthe1.1versionofHTTP.Youcaneasilylookintothecommunicationyourself,startingupthebrowserandopeningthedevelopertools.Suchatoolisbuiltintoeverybrowserthesedays.YoucanuseittodebugtheprogrambehavioronthenetworkapplicationlevellookingattheactualHTTPrequestsandresponsesonthebytelevel.Thefollowingscreenshotshowshowthedevelopertoolshowsthiscommunication:
HTTPmethodsThemethodthatisthefirstwordinthestatuslineoftherequesttellstheserverwhattodowiththerequest.Thestandarddefinesdifferentmethods,suchasGET,HEAD,POST,PUT,DELETE,andsomeothers.
TheclientusestheGETmethodwhenitwantstogetthecontentofaresource.InthecaseofaGETrequest,thebodyoftherequestisempty.Thisisthemethodusedbythebrowserwhenwedownloadawebpage.Itisalso,manytimes,themethodusedwhensomeprogramimplementedinJavaScriptandrunninginthebrowserasksforsomeinformationfromawebapplication,butitdoesnotwanttosendmuchinformationtotheserver.
WhentheclientusesPOST,theintentionisusuallytosendsomedatatotheserver.Theserverdoesreplyand,manytimes,thereisalsoabodyinthereply,butthemainpurposeoftherequest/replycommunicationistosendsomeinformationfromtheclienttotheserver.ThisistheoppositeoftheGETmethodinsomesense.
TheGETandPOSTmethodsarethemostfrequentlyusedmethods.AlthoughthereisageneralguidelinetouseGETtoretrievedataandPOSTtosenddatatotheserver,itisonlyarecommendation,andthereisnocleanseparationofthetwocases.Manytimes,GETisusedtosendsomedatatotheserver.Afterall,itisanHTTPrequestwithastatuslineandheaderfields,andalthoughthereisnobodyintherequest,theobject(partoftheURL)thatfollowsthemethodinthestatuslineisstillabletodeliverparameters.Manytimes,itisalsoeasytotestaservicethatrespondstoaGETrequestbecauseyouonlyneedabrowserandtotypeintheURLwiththeparameters,andlookattheresponseinthebrowserdevelopertools.YoushouldnotbesurprisedifyouseeanapplicationthatusesGETrequeststoexecuteoperationsthatmodifythestateonawebserver.However,notbeingsurpriseddoesnotmeanapproval.Youshouldbeawarethatinmostcases,thesearenotgoodpractices.WhenwesendsensitiveinformationusingtheGETrequest,theparametersintheURLareavailabletotheclientintheaddresslineofthebrowser.WhenwesendusingPOST,theparametersarestillreachablebytheclient(afterall,theinformationtheclientsendsisgeneratedbytheclientand,assuch,cannotbeunavailable),butnotthateasyforasimplesecurity-unawareusertocopy-pastetheinformationandsend,perhaps,toamalevolentthirdparty.ThedecisionbetweenusingGETandPOSTshouldalwaysconsiderpracticalitiesandsecurityissues.
TheHEADmethodisidenticaltoaGETrequest,buttheresponsewillnotcontainabody.Thisisusedwhentheclientisnotinterestedintheactualresponse.Itmayhappenthattheclientalreadyhastheobjectandwantstoseeifitwaschanged.TheLast-Modifiedheaderwillcontainthetimewhentheresourcewaslastchanged,andtheclientcandecideifithasaneweroneorneedstoaskfortheresourceinanewrequest.
ThePUTmethodisusedwhentheclientwantstostoresomethingontheserverandDELETEwhentheclientwantstoerasesomeresource.ThesemethodsareusedonlybyapplicationsusuallywritteninJavaScriptandnotdirectlybythebrowser.
Thereareothermethodsdefinedinthestandard,butthesearethemostimportantandfrequentlyusedones.
Statuscodes
Theresponsestartswiththestatuscode.Thesecodesarealsodefinedandtherearealimitednumberofcodesusableinaresponse.Themostimportantis200,whichsaysallisOK;theresponsecontainswhattherequestwanted.Thecodesarealwaysintherangeof100to599,containthreedigits,andaregroupedbythefirstdigit.
1xx:Thesecodesareinformationcodes.Theyarerarelyusedbutcanbeveryimportantinsomecases.Forexample,100meanscontinue.AservercansendthiscodewhenitgetsaPOSTrequestandtheserverwantstosignaltheclienttosendthebodyoftherequestbecauseitcanprocessit.Usingthiscode,andtheclientwaitingforthiscode,maysavealotofbandwidthifproperlyimplementedontheserverandalsoontheclient.2xx:Thesecodesmeansuccess.Therequestisansweredproperly,ortherequestedservicewasdone.Therearecodes,suchas200,201,202,andsoon,definedinthestandardandthereisadescriptionaboutwhentouseoneortheother.3xx:Thesecodesmeanredirection.OneofthesecodesissentwhentheservercannotdirectlyservicetherequestbutknowstheURLthatcan.Theactualcodescandistinguishbetweenapermanentredirect(whenitisknownthatallfuturerequestsshouldbesenttothenewURL)andtemporaryredirect(whenanylaterrequestshouldbesenthereandpossiblyservedorredirected),butthedecisioniskeptontheserverside.4xx:Theseareerrorcodes.Themostfamouscodeis404,whichmeansNotFound,thatis,theserverisnotabletorespondtotherequestbecausetheresourceisnotfound.401meansthattheresourcetoservetherequestmaybeavailablebutitrequiresauthentication.403isacodethatsignalsthattherequestwasvalidbutisstillrefusedtobeservedbytheserver.5xx:Thesecodesareservererrorcodes.Whenaresponseholdsoneoftheseerrorcodes,themeaningisthatthereissomeerrorontheserver.Thiserrorcanbetemporary,forexample,whentheserverisprocessingtoomanyrequestsandcannotrespondtoanewrequestwithacalculation-intensiveresponse(thisisusuallysignaledbyerrorcode503)orwhenthefeatureisnotimplemented(code501).Thegeneralerrorcode500isinterpretedasInternalError,whichmeansthatnoinformation,whatsoever,isavailableaboutwhatwasgoingwrongontheserver,butitwasnotgoingwellandhence,nomeaningfulresponse.
HTTP/2.0Afteralmost20yearssincethelastreleaseofHTTP,thenewversionofHTTPwasreleasedin2015.Thisnewversionoftheprotocolhasseveralenhancementsoverthepreviousversions.Someoftheseenhancementswillalsoaffectthewayserverapplicationswillbedeveloped.
ThefirstandmostimportantenhancementisthatthenewprotocolwillmakeitpossibletosendseveralresourcesparallellyinasingleTCPconnection.Thekeep-aliveflagisavailabletoavoidtherecreationoftheTCPchannel,butitdoesnothelpwhenaresponseiscreatedslowly.Inthenewprotocol,otherresourcescanalsobedeliveredinthesameTCPchannelevenbeforeonerequestisfullyserved.Thisrequirescomplexpackagehandlingintheprotocol,butthisishiddenfromtheserverapplicationprogrammeraswellasthebrowserprogrammer.Theapplicationserver,servletcontainer,andbrowserimplementthistransparently.
HTTP/2.0willalwaysbeencrypted,thereforeitwillnotbepossibletousehttpasaprotocolinthebrowserURL.Itwillalwaysbehttps.
Thefeaturethatwillneedchangesinservletprogrammingtoleveragetheadvantagesofthenewversionoftheprotocolisserverpush.Version4.0oftheservletspecificationincludessupportforHTTP/2.0,andthisversionisstillindraft.
ServerpushisanHTTPresponsetoarequestthatwillcomeinthefuture.Howcanaserveranswerarequestthatisnotevenissued?Well,theserveranticipates.Forexample,theapplicationsendsanHTMLpagethathasreferencestomanysmallpicturesandicons.TheclientdownloadstheHTMLpage,buildstheDOMstructure,analyzesit,andrealizesthatthepicturesareneeded,andsendstherequestforthepictures.Theapplicationprogrammerknowswhatpicturesarethereandmaycodetheservertosendthepicturesevenbeforethebrowserrequestsforit.EverysuchresponseincludesaURLthatthisresponseisfor.Whenthebrowserwantstheresource,itrealizesthatitisalreadythereanddoesnotissueanewrequest.InHttpServlet,theprogramshouldaccessPushBuilderviatherequest'snewgetPushBuildermethodandusethattopushdownresourcestotheclient.
CookiesCookiesaremaintainedbythebrowserandaresentintheHTTPrequestheaderusingtheCookieheaderfield.Eachcookiehasaname,value,domain,path,expirationtime,andsomeotherparameters.WhenarequestissenttoaURLthatmatchesthedomain,thepathofanon-expiredcookie,theclientsendsthecookietotheserver.Cookiesareusuallystoredinsmallfilesontheclientbythebrowserorinalocaldatabase.Theactualimplementationisthebusinessofthebrowser,andweneednotworryaboutit.Itisjustthetextinformationthatisnotexecutedbytheclient.Itisonlysentbacktotheserverwhensomerules(mainlydomainandpath)match.CookiesarecreatedbyserversandaresenttotheclientinHTTPresponsesusingtheSet-Cookieheaderfield.Thus,essentiallytheservertellstheclient,Hey,hereisthiscookie,wheneveryoucometomenexttime,showmethispieceofinformation,soIwillknowitisyou.
Cookiesareusuallytorememberclients.Advertisersandonlineshopsthatneedtorememberwhotheyaretalkingtoheavilyuseit.Butthisisnottheonlyuse.Thesedays,anyapplicationthatmaintainsusersessionsusescookiestochainuptheHTTPrequeststhatcomefromthesameuser.Whenyoulogintoanapplication,theusernameandpasswordyouusetoidentifyyourselfaresenttotheserveronlyonce,andinsubsequentrequests,onlyaspecialcookieissenttotheserverusedtoidentifythealreadyloggedinuser.Thisuseofcookiesemphasizeswhyitisimportanttousecookievaluesthatcannotbeeasilyguessed.Ifthecookieusedtoidentifyauseriseasilyguessable,thenanattackercouldjustcreateacookieandsendittotheservermimickingtheotheruser.Cookievalues,forthepurpose,areusuallylongrandomstrings.
Cookiesarenotalwayssentbacktotheserverwheretheyoriginate.Whenthecookieisset,theserverspecifiesthedomainoftheURLwherethecookieshouldbesentback.Thisisusedwhenadifferentserverfromtheoneprovidingtheservicesneedingauthenticationdoestheuserauthentication.
Applicationssometimesencodevaluesintocookies.Thisisnotnecessarilybad,thoughinmostactualcases,itis.Whenencodingsomethingintoacookie,weshouldalwaysconsiderthefactthatthecookietravelsthroughthenetworkandcangohugeasmoreandmoredataisencodedinitandcancreateunnecessaryburdenonthenetwork.Usually,itisbettertosendonlysomeunique,otherwisemeaningless,randomkey,andstorethevaluesinsomedatabase,beitondiskorinthememory.
ClientserverandwebarchitectureTheapplicationswedevelopedsofarwererunningonasingleJVM.Wealreadyhavesomeexperiencewithconcurrentprogrammingandthisissomethingthatwillcomehandynow.Whenweprogramawebapplication,apartofthecodewillrunontheserverandapartoftheapplicationlogicwillexecuteinthebrowser.TheserverpartwillbewritteninJava,thebrowserpartwillbeimplementedinHTML,CSS,andJavaScript.SincethisisaJavabookwewillfocusmainlyontheserverpart,butweshouldstillbeawareofthefactthatmanyofthefunctionalitiescanbeandshouldbeimplementedtoruninthebrowser.ThetwoprogramscommunicatewitheachotherovertheIPnetwork,thatis,theInternet,orinthecaseofanenterpriseinternalapplication,thenetworkofthecompany.
Today,abrowseriscapableofrunningverypowerfulapplications,allimplementedinJavaScript.Afewyearsago,suchapplicationsneededclientapplicationimplementedinDelphi,C++,orJava,usingthewindowingcapabilitiesoftheclientoperatingsystem.
Originally,theclient-serverarchitecturemeantthatthefunctionalityoftheapplicationwasimplementedontheclient,andtheprogramwasusinggeneralservicesonlyfromtheserver.Theserverprovideddatabaseaccessandfilestoragebutnothingmore.Later,thethree-tierarchitectureputthebusinessfunctionalityontheserversthatusedotherserversfordatabaseandothergeneralservices,andtheclientapplicationimplementedtheuserinterfaceandlimitedbusinessfunctionality.
Whenthewebtechnologystartedtopenetrateenterprisecomputing,thewebbrowserstartedtoreplacetheclientapplicationsinmanyusecases.Previously,thebrowsercouldnotruncomplexJavaScriptapplications.TheapplicationwasexecutedonthewebserverandtheclientdisplayedtheHTMLthattheservercreatedasapartoftheapplicationlogic.Everytimesomethingwaschangedontheuserinterface,thebrowserstartedacommunicationwiththeserver,andinaHTTPrequest-responsepair,thebrowsercontentwasreplaced.Awebapplicationwasessentiallyaseriesofformfillingandformdatasendingtotheserver,andtheserverrespondedwithHTML-formattedpages,presumablycontainingnewforms.
JavaScriptinterpretersweredevelopedandbecamemoreandmoreeffectiveandstandardized.Today,modernwebapplicationscontainHTML(whichisapartoftheclientcodeandisnotgeneratedbytheserveronthefly),CSS,andJavaScript.Whenthecodeisdownloadedfromthewebserver,theJavaScriptstartstoexecuteandcommunicatewiththeserver.ItisstillHTTPrequestsandresponses,buttheresponsesdonotcontainHTMLcode.Itcontainspuredata,usuallyintheJSONformat.ThisdataisusedbytheJavaScriptcodeandsomeofthedata,ifneeded,isdisplayedonthewebbrowserdisplayalsocontrolledbyJavaScript.Thisisfunctionallyequivalenttoathree-tierarchitecturewithsomeslightbutveryimportantdifferences.
Thefirstdifferenceisthatthecodeisnotinstalledontheclient.Theclientdownloadstheapplicationfromawebserver,andtheonlythingthatisinstalledisthemodernbrowser.Thisremovesalotofenterprisemaintenanceburdenandcost.
Theseconddifferenceisthattheclientisnotable,orislimited,toaccesstheresourcesoftheclientmachine.Thickclientapplicationscouldsaveanythinginalocalfileoraccessalocaldatabase.Thisisverylimited,forsecurityreasons,comparedtoaprogramrunningonthebrowser.Atthesametimethisis
ahandylimitationbecauseclientsaren'tandshouldn'tbeatrustedpartofthearchitecture.Thediskintheclientcomputerishardandexpensivetobackup.Itcanbestolenwithanotebook,andencryptingitiscostly.Therearetoolstoprotectclientstorage,butmostofthetime,storingthedataontheserveronlyisamoreviablesolution.
Itisalsoacommonprogramdesignerrortotrusttheclientapplication.Theclientphysicallycontrolstheclientcomputerandalthoughitcanbemadetechnicallyverydifficult,theclientcanstillovercomethesecuritylimitationsoftheclientdeviceandclientcode.Ifitisonlytheclientapplicationthatchecksthevalidityofsomefunctionalityordata,thenthephysicalsecurityprovidedbythephysicalcontroloftheserverisnotused.Wheneverdataissentfromtheclienttotheserver,thedatahastobecheckedinregardsofvalidity,nomatterwhattheclientapplicationis.Actually,sincetheclientapplicationcanbechanged,wejustdon'treallyknowwhattheclientapplicationreallyis.
Inthischapterand,asamatteroffact,intheentirebook,wefocusonJavatechnologies;thereforethesampleapplicationwillnotcontainalmostanyclienttechnology.IcouldnothelpbutcreatesomeCSS.Ontheotherhand,IdefinitelyavoidedJavaScript.Therefore,Ihavetoemphasizeagainthattheexampleistodemonstratetheprogrammingoftheserversideandstillprovidingsomethingthatreallyworks.AmodernapplicationwoulduseRESTandJSONcommunicationsandwouldnotplayaroundcreatingHTMLontheflyontheserverside.Originally,IwantedtocreateaJavaScriptclientandRESTserverapplication,butthefocuswasmovedsomuchfromserver-sideJavaprogrammingthatIdroppedthisidea.Ontheotherhand,youcanextendtheapplicationtobeonelikethat.
WritingservletsServletsareJavaclassesthatareexecutedinawebserverthatimplementstheservletcontainerenvironment.ThefirstwebserverscouldonlydeliverstaticHTMLfilestothebrowsers.ForeachURL,therewasanHTMLpageonthewebserverandtheserverdeliveredthecontentofthisfile,inresponsetoarequestsentbythebrowser.Verysoon,therewasaneedtoextendthewebserverstobeabletostartsomeprogramthatcalculatesthecontentoftheresponse,onthefly,whentherequestisprocessed.
ThefirststandardtodothatdefinedCGI.Itstartedanewprocesstorespondtoarequest.Thenewprocessgottherequestonitsstandardinput,andthestandardoutputwassentbacktotheclient.Thisapproachwastesalotofresources.Startinganewprocess,asyoulearnedinthepreviouschapter,iswaytoocostlyjusttorespondtoanHTTPrequest.Evenstartinganewthreadseemstobeunnecessary,butwiththat,weranabitahead.
ThenextapproachwasFastCGI,executingtheexternalprocesscontinuallyandreusingit,andthencamedifferentotherapproaches.Theapproachesafter
FastCGIallusein-processextensions.Inthesecases,thecodecalculatingtheresponserunsinsidethesameprocessasthewebserver.SuchstandardsorextensioninterfaceswereISAPIfortheMicrosoftIISserver,NSASPIfortheNetscapeserver,andtheApachemoduleinterface.Eachofthesemadeitpossibletocreateadynamicallyloadedlibrary(DLLonWindowsorSOfilesonUnixsystems)tobeloadedbythewebserverduring
startupandtomapcertainrequeststobehandledbythecodeimplementedintheselibraries.
WhensomebodyprogramsPHP,forexample,theApachemoduleextensionisthePHPinterpreterthatreadsthePHPcodeandactsuponit.WhensomebodyprogramsASPpagesfortheMicrosoftIIS,theISAPIextensionimplementingtheASPpageinterpreterisexecuted(well,thisisabitsloppyandoversimplifiedtosaybutworksasanexample).
ToJava,theinterfacedefinitionisaservletdefinedinJSR340asofversion3.1.
JSRstandsforJavaSpecificationRequest.ThesearerequestsformodificationoftheJavalanguage,libraryinterfaces,andothercomponents.Therequestsgothroughanevaluationprocess,andwhentheyareaccepted,theybecomeastandard.TheprocessisdefinedbytheJavaCommunityProcess(JCP).JCPisalsodocumentedandhasversions.Thecurrentversionis2.10andcanbefoundathttps://jcp.org/en/procedures/overview.TheJSR340standardcanbefoundathttps://jcp.org/en/jsr/detail?id=340.
Aservletprogramimplementstheservletinterface.UsuallythisisdoneviaextendingHttpServlet,theabstractimplementationoftheServletinterface.Thisabstractclassimplementsmethods,suchasdoGet,doPost,doPut,doDelete,doHead,doOption,anddoTrace,freetobeoverriddenbytheactualclassextendingit.Ifaservletclassdoesnotoverrideoneofthethesemethods,sendingthecorrespondingHTTPmethod,GET,POST,andsoon,willreturnthe405NotAllowedstatuscode.
HelloworldservletBeforegettingintothetechnicaldetails,let'screateanextremelysimplehelloworldservlet.Todoit,wesetupaGradleprojectwiththebuildfile,build.gradle,theservletclassinthefile,src/main/java/packt/java9/by/example/mastermind/servlet/HelloWorld.java,andlastbutnotleast,wehavetocreatethefilesrc/main/webapp/WEB-INF/web.xml.Thegradle.buildfilewilllookthefollowing:
applyplugin:'java'
applyplugin:'jetty'
repositories{
jcenter()
}
dependencies{
providedCompile"javax.servlet:javax.servlet-api:3.1.0"
}
jettyRun{
contextPath'/hello'
}
TheGradlebuildfileusestwoplugins,javaandjetty.Wehavealreadyusedthejavaplugininthepreviouschapter.ThejettypluginaddstaskssuchasjettyRunthatloadtheJettyservletcontainerandstartuptheapplication.ThejettypluginisalsoanextensionofthewarpluginthatcompileswebapplicationsintoaWebArchive(WAR)packagingformat.
TheWARpackagingformatispracticallythesameasJAR;itisazipfileanditcontainsalibdirectorythatcontainsalltheJARfilesthatthewebapplicationdependson.Theclassesoftheapplicationareinthedirectory,WEB-INF/classes,andthereisaWEB-INF/web.xmlfilethatdescribesservletURLmapping,whichwewillexploreindetailsoon.
Sincewewanttodevelopanextremelysimpleservlet,weaddtheservletAPIasadependencytotheproject.Thisis,however,notacompiledependency.TheAPIisavailablewhentheservletrunsinthecontainer.Still,ithastobeavailablewhenthecompilercompilesourcode;therefore,adummyimplementationisprovidedbytheartifactspecifiedasprovidedCompile.Becauseitisspecifiedthatway,thebuildprocesswillnotpackagethelibraryintothegeneratedWARfile.ThegeneratedfilewillcontainnothingthatisspecifictoJettyoranyotherservletcontainer.
Theservletcontainerwillprovidetheactualimplementationoftheservletlibrary.WhentheapplicationisdeployedandstartedinaJetty,theJetty-specificimplementationoftheservletlibrarywillbeavailableontheclasspath.WhentheapplicationisdeployedtoaTomcat,theTomcatspecificimplementationwillbeavailable.
Wecreateaclassinourproject,asfollows:
packagepackt.java9.by.example.mastermind.servlet;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjava.io.IOException;
importjava.io.PrintWriter;
publicclassHelloWorldextendsHttpServlet{
privateStringmessage;
@Override
publicvoidinit()throwsServletException{
message="Hello,World";
}
@Override
publicvoiddoGet(HttpServletRequestrequest,
HttpServletResponseresponse)
throwsServletException,IOException{
response.setContentType("text/html");
PrintWriterout=response.getWriter();
out.println("<h1>"+message+"</h1>");
}
@Override
publicvoiddestroy(){
}
}
Whentheservletisstarted,theinitmethodisinvoked.Whenitisputoutofservice,thedestroymethodiscalled.Thesemethodscanbeoverriddenandprovideamorefine-grainedcontrolthantheconstructorandotherfinalizationpossibilities.Aservletobjectmaybeputintoservicemorethanonce,andaftercallingdestroy,theservletcontainermayinvokeinitagain;thus,thiscycleisnotstrictlytiedtothelifecycleoftheobject.Usually,thereisnotmuchthatwedointhesemethods,butsometimes,youmayneedsomecodeinthem.
Also,notethatasingleservletobjectmaybeusedtoservemanyrequests,evenatthesametime;thus,theservletclassesandmethodsinitshouldbefairlythread-safe.Thespecificationdemandsthataservletcontainerusesonlyoneservletinstanceincasethecontainerrunsinanon-distributedenvironment.Incasethecontainerrunsonthesamemachineinseveralprocesses,eachexecutingaJVM,orevenondifferentmachines,therecanbemanyservletinstancesthathandletherequests.Generally,theservletclassesshouldbedesignedsuchthattheydonotassumethatonlyonethreadisexecutingthem,butatthesametime,theyshouldalsonotassumethattheinstanceisthesamefordifferentrequests.Wejustcannotknow.
Whatdoesitmeaninpractice?Youshouldnotuseinstancefieldsthatarespecifictoacertainrequest.Intheexample,thefieldinitializedtoholdthemessageholdsthesamevalueforeachandeveryrequest;essentially,thevariableisalmostafinalconstant.Itisusedonlytodemonstratesomefunctionalityfortheinitmethod.
ThedoGetmethodisinvokedwhentheservletcontainergetsanHTTPrequestwiththeGETmethod.Themethodhastwoarguments.Thefirstonerepresentstherequest,andthesecondonerepresentstheresponse.Therequestcanbeusedtocollectallinformationthatcomesintherequest.Intheprecedingexample,thereisnothinglikethat.Wedonotuseanyoftheinputs.Ifarequestcomestoourservlet,thenweanswertheHello,Worldstring,nomatterwhat.Later,wewillseeexampleswhenwereadtheparametersfromtherequest.Theresponsegivesmethodsthatcanbeusedtohandletheoutput.Intheexample,wefetchPrintWriter,whichistobeusedtosendcharacterstothebodyoftheHTTPresponse.Thisisthecontentthatappearsinthebrowser.Themimetypewesendistext/html,andthisissetbycallingthesetContentTypemethod.ThiswillgetintotheHTTPheaderfield,Content-Type.ThestandardandtheJavaDocdocumentationoftheclassesdefineallthemethodsthatcanbeused,andalsohowthese
shouldbeused.
Finally,wehaveaweb.xmlfilethatdeclarestheservletsthatareimplementedinourcode.Thisis,justasthenameofthefileindicates,anXMLfile.Itdeclarativelydefinesalltheservletsthatareincludedinthearchiveandalsootherparameters.Intheexample,theparametersarenotdefined,onlytheservletandthemappingtotheURL.Sincewehaveonlyonesingleservletinthisexample,theWARfile,itismappedtotherootcontext.AllandeveryGETrequestthatarrivestotheservletcontainerandtothisarchivewillbeservedbythisservlet:
<?xmlversion="1.0"encoding="UTF-8"?>
<web-appversion="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<display-name>HelloWorldServlet</display-name>
<servlet-name>HelloWorldServlet</servlet-name>
<servlet-class>packt.java9.by.example.mastermind.servlet.HelloWorld</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
JavaServerPagesIpromisedyouthatIwouldnotboreyouwithJavaServerPagesbecausethatisatechnologyofthepast.Eventhoughitisthepast,itisstillnothistoryastherearemanyprogramsrunningthatstilluseJSPandcontainJSPcode.
JSPpagesarewebpagesthatcontainHTMLandJavacodemixed.WhenanHTTPrequestisservedbyaJSPpage,theservletcontainerreadstheJSPpage,executestheJavaparts,takestheHTMLpartsastheyare,andinthisway,mixingthetwotogether,createsanHTMLpagethatissenttothebrowser.
<%@pagelanguage="java"
contentType="text/html;charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<body>
<%for(inti=0;i<5;i++){%>
hallo<br/>
<%}%>
</body>
</html>
TheprecedingpagewillcreateanHTMLpagethatcontainsthetexthallofivetimes,eachinanewlineseparatedbythetagbr.Behindthescenes,theservletcontainerconvertstheJSPpagetoaJavaservlet,thencompilestheservletusingtheJavacompiler,andthenrunstheservlet.ItdoesiteverytimethereissomechangeinthesourceJSPfile;therefore,itisveryeasytoincrementallycraftsomesimplecodeusingJSP.ThecodethatisgeneratedfromtheprecedingJSPfileis138lineslong(ontheTomcat8.5.5version),whichissimplylongandboringtolisthere,butthepartthatmayhelptounderstandhowtheJavafilegenerationworksisonlyafewlines.
Ifyouwanttoseeallthelinesofthegeneratedservletclass,youcandeploytheapplicationintoaTomcatserverandlookatthedirectorywork/Catalina/localhost/hello/org/apache/jsp/.Itisararelyknownfactamongdevelopersthatthiscodeisactuallysavedtodiskandisavailable.SometimesithelpswhenyouneedtodebugsomeJSPpages.
Herearethefewinterestinglinesgeneratedfromtheprecedingcode:
out.write("\n");
out.write("<html>\n");
out.write("<body>\n");
for(inti=0;i<5;i++){
out.write("\n");
out.write("hallo<br/>\n");
}
out.write("\n");
out.write("</body>\n");
out.write("</html>\n");
TheJSPcompilermovestheinsideoftheJSPcodeoutandtheoutsidein.IntheJSPcode,JavaissurroundedbyHTML,andinthegeneratedservletJavasource,theHTMLissurroundedbyJava.Itislikewhenyouwanttomendclothes:thefirstthingistoturnthedressinsideout.
ItisnotonlytheJavacodethatyoucanmixintoHTMLintheJSPpagesbutalsotheso-calledtags.Tags
arecollectedintotaglibraries,implementedinJava,andpackagedintoJARfiles,andtheyshouldbeavailableontheclasspathtobeused.TheJSPpageusingthetagsfromsomelibraryshoulddeclaretheuse:
<%@taglibprefix="c"
uri="http://java.sun.com/jsp/jstl/core"%>
ThetagslooklikeHTMLtags,buttheyareprocessedbytheJSPcompilerandexecutedbythecodeimplementedinthetagliblibrary.JSPmayalsorefertothevalueoftheJavaobjectsthatareavailableinthescopeoftheJSP.TodothisinsidetheHTMLpage,theJSPexpressionlanguagecouldbeused.
JSPwasoriginallycreatedtoeasethedevelopmentofawebapplication.Themainadvantageisthefaststartupofdevelopment.Thereisnolengthytimeforconfiguration,setup,andsooninthedevelopment,andwhenthereisanychangeintheJSPpage,thereisnoneedtocompilethewholeapplicationagain:theservletcontainergeneratestheJavacode,compilesittoclassfile,loadsthecodeintomemory,andexecutes.JSPwasacompetitorofMicrosoftASPpages,whichmixedHTMLwithVisualBasiccode.
Whentheapplicationstartstogrowbig,usingtheJSPtechnologycausesmoreproblemsthanaregood.Thecodethatmixesthebusinesslogicandtheviewoftheapplication,howitisrenderedinthebrowser,becomesmessy.DevelopingJSPrequiresfrontendtechnologyknowledge.AJavadeveloperisexpectedtoknowsomefrontendtechnologybutisrarelyadesignexpertandCSSguru.ModerncodealsocontainsJavaScript,manytimesembeddedintheHTMLpage.Afterall,thebigadvantageofJSPisthatitcontainscodethatrunsontheserveraswellasontheclient-sidecode.Thedevelopersfollowtheparadigmmanytimes,sodonotbesurprisedtoseesomelegacycodethatcontainsJava,HTML,CSS,andJavaScriptallmixedinaJSPfile.SinceJavaandJavaScriptaresyntacticallysimilarsometimes,itisnotobvioustoseewhatisexecutedontheserverandwhatisexecutedontheclient.IhaveevenseencodethatcreatedJavaScriptcodefromJavacodeinaJSPfile.Thatisatotalmixofdifferentresponsibilitiesandamessthatisnearlyimpossibletomaintain.ThisledtothetotaldeprecationofJSPasoftoday.
ThedeprecationofJSPisnotofficial.Itismyexpertopinion.YoumaymeetsomeexperienceddeveloperswhoarestillinlovewithJSP,andyoumayfindyourselfinprojectswhereyouarerequiredtodevelopprogramsinJSP.Itisnotshamefuldoingthat.Somepeopledoworseformoney.
Tomendthemessysituation,thereweretechnologiesthatadvocatedtheseparationoftheservercodeandtheclientfunctionalitymoreandmore.ThesetechnologiesincludeWicket,Vaadin,JSF,anddifferentJavatemplatingengines,suchasFreemarker,ApacheVelocity,andThymeleaf.TheselattertechnologiescanalsobeinterestingwhenyougeneratetextualoutputfromJavaevenwhenthecodeisnotweb-relatedatall.
Thesetechnologies,withdiscipline,helpedcontrolthedevelopmentandmaintenancecostsofmoderateandlargewebprojects,butthebasicproblemofthearchitecturewasstillthere:noclearseparationofconcerns.
Today,modernapplicationsimplementthecodeofawebapplicationinseparateprojects:onefortheclient,usingHTML,CSSandJavaScript,andaseparateonetoimplementserverfunctionalityinJava(orinsomethingelse,butwefocushereonJava).ThecommunicationbetweenthetwoistheRESTprotocol,
whichwewillcoverinthesubsequentchapters.
HTML,CSS,andJavaScriptHTML,CSS,andJavaScriptareclient-sidetechnologies.Theseareextremelyimportantforwebapplications,andaprofessionalJavadevelopershouldhavesomeknowledgeaboutthem.NobodyexpectsyoutobeanexpertinJavaandinweb-clienttechnologiesatthesametime,thoughthisisnotimpossible.Acertainunderstandingisdesirable.
HTMListhetextualrepresentationofastructuredtext.Thetextisgivenascharacters,asinanytextfile.Tagsrepresentthestructure.Astarttagstartswitha<character,thenthenameofthetag,then,optionally,name="value"attributes,andfinallyaclosing>character.Anendtagstartswith</,thenthenameofthetag,andthen>.Tagsareenclosedintohierarchies;thus,youshouldnotcloseatagsoonerthantheonethatwasopenedlater.First,thetagthatwasopenedlasthastobeclosed,thenthenext,andsoon.Thisway,anyactualtagintheHTMLhasalevel,andalltagsthatarebetweenthestartandendtagsarebelowthistag.Sometagsthatcannotencloseothertagsortextdonothaveendtagsandstandontheirown.Considerthefollowingsample:
<html>
<head>
<title>thisisthetitle</title>
</head>
</html>
Thetagheadisunderhtml,andtitleisunderhead.Thiscanbestructuredintoatree,asfollows:
html
+head
+title
+"thisisthetitle"
ThebrowserstorestheHTMLtextinatreestructure,andthistreeistheobjectmodelofthewebpagedocument,thusthename,DocumentObjectModel(DOM)tree.
TheoriginalHTMLconceptmixedformattingandstructure,andevenwiththecurrentversionofHTML5,westillhavetagssuchasb,i,ttthatsuggestthebrowsertodisplaythetextbetweenthestartandendtagsinbold,italics,andteletype,respectively.
AsthenameHTML,standingforHypertextMarkupLanguage,suggests,thetextcancontainreferencestootherwebpagesintheformofhyperlinks.Theselinksareassignedtotextsusingtheatag(standingforanchor)ortosomeformthatmayconsistofdifferentfields,andwhenthesubmitbuttonoftheformispressed,thecontentofthefieldsissenttotheserverinaPOSTrequest.Whentheformissent,thecontentofthefieldsisencodedintheso-calledapplication/x-www-form-urlencodedform.
TheHTMLstructurealwaystriedtopromotetheseparationofstructureandformatting.Todoso,formattingwasmovedtostyles.StylesdefinedinCascadingStyleSheets(CSS)providemuchmoreflexibilityforformattingthanHTML;theformatofaCSSismoreeffectiveforformatting.TheaimtocreateCSSwasthatthedesigncanbedecoupledfromthestructureofthetext.IfIhadtochooseoneofthethree,IwouldoptforCSSastheonethatisleastimportantforJavaserver-sidewebdevelopersand,atthesametime,themostimportantfortheusers(thingsshouldlooknice).
JavaScriptisthethirdpillarofclient-sidetechnologies.JavaScriptisafullyfunctional,interpretedprogramminglanguageexecutedbythebrowser.ItcanaccesstheDOMtree,andreadandmodifyit.WhentheDOMtreeismodified,thebrowserautomaticallydisplaysthemodifiedpage.JavaScriptfunctionscanbescheduledandregisteredtobeinvokedwhensomeeventoccurs.Forexample,youcanregisterafunctiontobeinvokedwhenthedocumentisfullyloaded,whentheuserpressesabutton,clicksonalink,orjusthoversthemouseoversomesection.AlthoughJavaScriptwasfirstonlyusedtocreatefunnyanimationsonthebrowser,todayitispossible,andisausualpractice,toprogramfullyfunctionalclientsusingthecapabilitiesofthebrowser.TherearereallypowerfulprogramswritteninJavaScript,evensuchpower-hungryapplicationsasPCemulators.
Inthisbook,wefocusonJavaandusetheclient-sidetechnologiesasmuchasisneededfordemonstrationtechnologies.However,beingaJavawebdeveloperprofessional,youhavetolearnthesetechnologiesaswell,tosomeextentatleast,tounderstandwhataclientcandoandtobeabletocooperatewiththeprofessionalsresponsibleforfrontendtechnologies.
MastermindservletPlayingtheMastermindgameviatheWebisabitdifferentfromwhatitusedtobe.Tillnow,wedidnothaveanyuserinteractionandourclassesweredesignedaccordingly.Forexample,wecouldaddanewguesstothetable,alongwiththepartialandfullmatchescalculatedbytheprogram.Nowwehavetoseparatethecreationofanewguess,addittothegame,andsetthefullandpartialmatches.Thistime,wehavetodisplaythetablefirst,andtheuserhastocalculateandprovidethenumberofmatches.
Wehavetomodifysomeoftheclassestobeabletodothat.WeneedtoaddanewmethodtoGame.java:publicRowaddGuess(Guessguess,intfull,intpartial){assertNotFinished();finalRowrow=newRow(guess,full,partial);table.addRow(row);if(itWasAWinningGuess(full)){finished=true;}returnrow;}
Tillnow,wehadonlyonemethodthatwasaddinganewguess,andsincetheprogramknewthesecret,itwasimmediatelycalculatingthevalueoffullandpartial.ThenameofthemethodcouldbeaddNewGuess,overloadingtheoriginalmethod,butthistime,themethodisusednotonlytoaddanewguessbutalsotoaddoldguessestorebuildthetable.
Whentheprogramstarts,therearenoguesses.Theprogramcreatesone,thefirstone.Lateron,whentheusertellstheprogramthefullandpartialmatches,theprogramneedstheGamestructurewithTableandRowobjectscontainingGuessobjectsandthefullandpartialmatchvalues.Thesewerealreadyavailable,butwhenthenewHTTPhitcomesin,wehavetopullitfromsomewhere.Programmingaservlet,wehavetostorethestateofthegamesomewhereandrestoreitwhenanewHTTPrequesthitstheserver.
StoringstateStoringthestatecanbedoneintwoplaces.Oneplace,whichwewillfirstdoinourcode,istheclient.Whentheprogramcreatesanewguess,itaddsittothetableandsendsanHTMLpagethatcontainsnotonlythenewguessbutalsoallthepreviousguessesandthefullandpartialmatchvaluesthattheusergaveforeachoftherows.Tosendthedatatotheserver,thevaluesarestoredinthefieldsofaform.Whentheformissubmitted,thebrowsergatherstheinformationinthefields,createsanencodedstringfromthecontentofthefields,andputsthecontentintothebodyofaPOSTrequest.
Theotherpossibilityforstoringtheactualstateisintheserver.Theservercanstorethestateofthegame,anditcanreconstructthestructurewhenitcreatesanewguess.Theprobleminthiscaseisknowingwhichgametouse.Theservercanandshouldstoremanygames,oneforeachuser,andusersmayusetheapplicationconcurrently.Itdoesnotnecessarilymeanstrongconcurrencyinthesamemeaningasweexaminedinthepreviouschapter.
Eveniftheusersarenotservedatthesametimeinmultiplethreads,therecanbegamesthatareactive.Imaginecnn.comtellingyouthatyoucannotreadthenewsatthemomentbecausesomebodyelseisreadingit.Therecanbemultipleusersplayingmultiplegames,andwhileservinganHTTPrequest,weshouldknowwhichuserweareserving.
Servletsmaintainsessionsthatcanbeusedforthispurposeaswewillseeinthenextsection.
HTTPsessionWhenaclientsendsrequestsfromthesamebrowsertothesameservlet,theseriesofrequestsbelongtoonesession.Toknowthattherequestsbelongtothesamesession,theservletcontainerautomaticallysendsacookienamedJSESSIONIDtotheclient,andthiscookiehasalong,random,hard-to-guessvalue(tkojxpz9qk9xo7124pvanc1zasIruntheapplicationinJetty).TheservletmaintainsasessionstorethatcontainstheHttpSessioninstances.ThekeystringthattravelsinthevalueoftheJSESSIONIDcookieidentifiestheinstances.WhenanHTTPrequestarrivesattheservlet,thecontainerattachesthesessiontotherequestobjectfromthestore.Ifthereisnosessionforthekey,thenoneiscreated,andthecodecanaccessthesessionobjectbycallingtherequest.getSession()method.
AHttpSessionobjectcanstoreattributes.TheprogramcancallthesetAttribute(String,Object),getAttribute(String),andremoveAttribute(String)methodstostore,retrieve,ordeleteanattributeobject.EachattributeisassignedtoaStringandcanbeanyObject.
AlthoughthesessionattributestoreessentiallylooksassimpleasaMap<String,?>object,itisnot.Thevaluesstoredinthesessioncanbemovedfromonenodetoanotherwhentheservletcontainerrunsinaclusteredorotherdistributedenvironment.Todothat,thevaluesareserialized;therefore,thevaluesstoredinthesessionshouldbeSerializable.Failingtodosoisaverycommonnoviceerror.Duringdevelopment,executingthecodeinasimpledevelopmentTomcatorJettycontainerpracticallyneverserializesthesessiontodiskandneverloadsitfromtheserializedform.ThismeansthatthevaluessetusingsetAttributewillbeavailablebycallinggetAttribute.Werunintotroublethefirsttimetheapplicationgetsinstalledinaclusteredenvironment.AssoonasaHTTPrequestarrivesondifferentnodesgetAttributemayreturnnull.ThemethodsetAttributeiscalledononenodeandduringtheprocessingofthenextrequestgetAttributeonadifferentnodecannotdeserializetheattributevaluefromthedisksharedamongthenodes.Thisisusually,andsadly,theproductionenvironment.
You,asadeveloper,shouldalsobeawarethatserializingandde-serializinganobjectisaheavyoperationthatcostsseveralCPUcycles.IfthestructureoftheapplicationusesonlyapartoftheclientstateservingmostoftheHTTPrequests,thenthisisawasteofCPUtocreatethewholestateinmemoryfromaserializedformandthenserializingitagain.Insuchcases,itismoreadvisabletostoreonlyakeyinthesessionandusesomedatabase(SQLorNoSQL)orsomeotherservicetostoretheactualdatareferencedbythekey.Enterpriseapplicationsalmostexclusivelyusethisstructure.
packagepackt.java9.by.example.mastermind.servlet;<br/><br/>importpackt.java9.by.example.mastermind.Color;<br/>importpackt.java9.by.example.mastermind.Table;<br/><br/>importjavax.inject.Inject;<br/>importjavax.inject.Named;<br/><br/>publicclassHtmlTools{<br/>@Inject<br/>Tabletable;<br/><br/>@Inject<br/>@Named("nrColumns")<br/>privateintNR_COLUMNS;<br/><br/>publicStringtag(StringtagName,String...attributes){<br/>StringBuildersb=newStringBuilder();<br/>sb.append("<").append((tagName));<br/>for(inti=0;i<attributes.length;i+=2){<br/>sb.append("").<br/>append(attributes[i]).<br/>append("=\"").<br/>append(attributes[i+1]).<br/>append("\"");<br/>}<br/>sb.append(">");<br/>returnsb.toString();<br/>}<br/><br/>publicStringinputBox(Stringname,Stringvalue){<br/>returntag("input","type","text","name",name,"value",value,"size","1");<br/>}<br/><br/>publicStringcolorToHtml(Colorcolor,introw,intcolumn){<br/>returntag("input","type","hidden","name",paramNameGuess(row,column),<br/>"value",color.toString())+<br/>tag("div","class","color"+color)+<br/>tag("/div")+<br/>tag("div","class","spacer")+<br/>tag("/div");<br/>}<br/><br/><br/>publicStringparamNameFull(introw){<br/>return"full"+row;<br/>}<br/><br/>publicStringparamNamePartial(introw){<br/>return"partial"+row;<br/>}<br/><br/>publicStringparamNameGuess(introw,intcolumn){<br/>return"guess"+row+column;<br/>}<br/><br/>publicStringtableToHtml(){<br/>StringBuildersb=newStringBuilder();<br/>sb.append("<html><head>");<br/>sb.append("<linkrel=\"stylesheet\"type=\"text/css\"href=\"colors.css\">");<br/>sb.append("<title>Mastermindguessing</title>");<br/>sb.append("<body>");<br/>sb.append(tag("form","method","POST","action","master"));<br/><br/>for(introw=0;row<table.nrOfRows();row++){<br/>for(intcolumn=0;column<NR_COLUMNS;column++){<br/>sb.append(colorToHtml(table.getColor(row,column),row,column));<br/>}<br/><br/>sb.append(inputBox(paramNameFull(row),""+table.getFull(row)));<br/>sb.append(inputBox(paramNamePartial(row),""+table.getPartial(row)));<br/>sb.append("<p>");<br/>}<br/>returnsb.toString();<br/>}<br/>}
<html><br/><head><br/><linkrel="stylesheet"type="text/css"href="colors.css"><br/><title>Mastermindguessing</title><br/><body><br/><formmethod="POST"action="master"><br/><inputtype="hidden"name="guess00"value="3"><br/><divclass="color3"></div><br/><divclass="spacer"></div><br/><inputtype="hidden"name="guess01"value="2"><br/><divclass="color2"></div><br/><divclass="spacer"></div><br/><inputtype="hidden"name="guess02"value="1"><br/><divclass="color1"></div><br/><divclass="spacer"></div><br/><inputtype="hidden"name="guess03"value="0"><br/><divclass="color0"></div><br/><divclass="spacer"></div><br/><inputtype="text"<br/>name="full0"value="0"size="1">
<br/><inputtype="text"<br/>name="partial0"value="2"size="1"><br/><p><br/><inputtype="hidden"name="guess10"value="5"><br/><divclass="color5"></div><br/><br/>...deletedcontentthatjustlooksalmostthesame...<br/><br/><p><br/><inputtype="submit"value="submit"><br/></form><br/></body><br/></head><br/></html>
.color0{<br/>background:red;<br/>width:20px;<br/>height:20px;<br/>float:left<br/>}<br/>.color1{<br/>background-color:green;<br/>width:20px;<br/>height:20px;<br/>float:left<br/>}<br/>....color2to.color5isdeleted,contentisthesameexceptdifferentcolors...<br/><br/>.spacer{<br/>background-color:white;<br/>width:10px;<br/>height:20px;<br/>float:left<br/>}
DependencyinjectionwithGuiceTheservletclassisverysimpleasshowninthefollowing:
packagepackt.java9.by.example.mastermind.servlet;
importcom.google.inject.Guice;
importcom.google.inject.Injector;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjava.io.IOException;
publicclassMastermindextendsHttpServlet{
privatestaticfinalLoggerlog=LoggerFactory.getLogger(Mastermind.class);
publicvoiddoGet(HttpServletRequestrequest,
HttpServletResponseresponse)
throwsServletException,IOException{
doPost(request,response);
}
publicvoiddoPost(HttpServletRequestrequest,
HttpServletResponseresponse)
throwsServletException,IOException{
Injectorinjector=
Guice.createInjector(newMastermindModule());
MastermindHandlerhandler=
injector.getInstance(MastermindHandler.class);
handler.handle(request,response);
}
}
Becausemanythreadsuseservletsconcurrently,andthuswecannotuseinstancefieldsholdingdataforasinglehit,theservletclassdoesnothingelsebutcreateanewinstanceofaMastermindHandlerclassandinvokeitshandlemethod.SincethereisanewinstanceofMastermindHandlerforeachrequest,itcanstoreobjectsinfieldsspecifictotherequest.Tocreateahandler,weusetheGuicelibrarycreatedbyGoogle.
Wehavealreadytalkedaboutdependencyinjection.ThehandlerneedsaTableobjecttoplay,aColorManagerobjecttomanagethecolors,andaGuesserobjecttocreateanewguess,butcreatingtheseorfetchingsomeprefabricatedinstancesfromsomewhereisnotthecorefunctionalityofthehandler.Thehandlerhastodoonething:handletherequest;theinstancesneededtodothisshouldbeinjectedfromoutside.ThisisdonebyaGuiceinjector.
TouseGuice,wehavetolistthelibraryamongthedependenciesinbuild.gradle:
applyplugin:'java'
applyplugin:'jetty'
repositories{
jcenter()
}
dependencies{
providedCompile"javax.servlet:javax.servlet-api:3.1.0"
testCompile'junit:junit:4.12'
compile'org.slf4j:slf4j-api:1.7.7'
compile'ch.qos.logback:logback-classic:1.0.11'
compile'com.google.inject:guice:4.1.0'
}
jettyRun{
contextPath'/hello'
}
Thenwehavetocreateaninjectorinstancethatwilldotheinjection.Theinjectoriscreatedwiththefollowinglineintheservlet:
Injectorinjector=Guice.createInjector(newMastermindModule());
TheinstanceofMastermindModulespecifieswhattoinjectwhere.ThisisessentiallyaconfigurationfileintheJavaformat.Otherdependencyinjectorframeworksused,anduse,XMLandannotationstodescribetheinjectionbindingandwhattoinjectwhere,butGuicesolelyusesJavacode.ThefollowingistheDIconfigurationcode:
publicclassMastermindModuleextendsAbstractModule{
@Override
protectedvoidconfigure(){
bind(int.class)
.annotatedWith(Names.named("nrColors")).toInstance(6);
bind(int.class)
.annotatedWith(Names.named("nrColumns")).toInstance(4);
bind(ColorFactory.class).to(LetteredColorFactory.class);
bind(Guesser.class).to(UniqueGuesser.class);
}
}
ThemethodsusedintheconfiguremethodarecreatedinafluentAPImannersothatthemethodscanbechainedoneaftertheotherandthatthecodecanbereadalmostlikeEnglishsentences.AgoodintroductiontofluentAPIcanbefoundathttps://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course/.Forexample,thefirstconfigurationlinecouldbereadinEnglishas
Bindtotheclassintwhereveritisannotatedwiththe@Nameannotationhavingvalue"nrColor"totheinstance6.
(Notethattheintvalue6isautoboxedtoanIntegerinstance.)
TheMastermindHandlerclasscontainsfieldsannotatedwith@Injectannotation:
@Inject
@Named("nrColors")
privateintNR_COLORS;
@Inject
@Named("nrColumns")
privateintNR_COLUMNS;
@Inject
privateHtmlToolshtml;
@Inject
Tabletable;
@Inject
ColorManagermanager;
@Inject
Guesserguesser;
ThisannotationisnotGuice-specific.@Injectisapartofthejavax.injectpackageandisastandardpartofJDK.JDKdoesnotprovidethedependencyinjector(DI)frameworkbutsupportsthedifferentframeworkssothattheycanusestandardJDKannotations,andincasetheDIframeworkisreplaced,theannotationsmayremainthesameandnotframework-specific.
WhentheinjectoriscalledtocreateaninstanceofMastermindHandler,itlooksattheclassandseesthatithasanintfieldannotatedwith@Injectand@Named("nrColors"),andfindsintheconfigurationthatsuchafieldshouldhavethevalue6.ItinjectsthevaluetothefieldbeforereturningtheMastermindHandlerobject.Similarly,italsoinjectsthevaluesintotheotherfields,andifitshouldcreateanyoftheobjectstobeinjected,itdoes.Iftherearefieldsintheseobjects,thentheyarealsocreatedbyinjectingotherobjectsandsoon.
ThiswaytheDIframeworkremovestheburdenfromtheprogrammers'shouldertocreatetheinstances.Thisissomethingfairlyboringandisnotthecorefeatureoftheclassesanyway.Instead,itcreatesalltheobjectsneededtohaveafunctionalMastermindHandlerandlinksthemtogetherviatheJavaobjectreferences.Thisway,thedependenciesofthedifferentobjects(MastermindHandlerneedsGuesser,ColorManager,andTable;ColorManagerneedsColorFactory;andTablealsoneedsColorManager,andsoon)becomeadeclaration,specifiedusingannotationsonthefields.Thesedeclarationsareinsidethecodeoftheclasses,anditistherightplaceforthem.Whereelsecouldwespecifywhattheclassneedstoproperlyfunctionthanintheclassitself?
TheconfigurationinourexamplespecifiesthatwhereverthereisaneedforColorFactory,wewilluseLetteredColorFactory,andthatwhereverweneedGuesser,wewilluseUniqueGuesser.Thisisseparatedfromthecodeandithastobelikethat.Ifwewanttochangetheguessingstrategy,wereplacetheconfigurationandthecodeshouldworkwithoutmodifyingtheclassesthatusetheguesser.
GuiceiscleverenoughandyouneednotspecifythatwhereverthereisaneedforTable,wewilluseTable:thereisnobind(Table.class).to(Table.class).FirstIcreatedalinelikethatintheconfiguration,butGuicerewardedmewithanerrormessage,andnow,writingitagaininplainEnglish,Ifeelreallystupid.IfIneedatableIneedatable.Really?
TheMastermindHandlerclassWehavealreadystartedthelistingoftheMastermindHandlerclass,andsincethisclassismorethanahundredlines,Iwillnotincludeithereasawhole.Themostimportantmethodofthisclassishandle:
publicvoidhandle(HttpServletRequestrequest,
HttpServletResponseresponse)
throwsServletException,IOException{
Gamegame=buildGameFromRequest(request);
GuessnewGuess=guesser.guess();
response.setContentType("text/html");
PrintWriterout=response.getWriter();
if(game.isFinished()||newGuess==Guess.none){
displayGameOver(out);
}else{
log.debug("Addingnewguess{}tothegame",newGuess);
game.addGuess(newGuess,0,0);
displayGame(out);
}
bodyEnd(out);
}
Weperformthreesteps.Step1iscreatingthetableandwedoitfromtherequest.Ifthisisnotthestartofthegame,thereisalreadyatableandtheHTMLformcontainsallpreviousguesscolorsandtheanswerstothose.Then,asthesecondstep,wecreateanewguessbasedonthat.Step3istosendthenewHTMLpagetotheclient.
Again,thisisnotamodernapproach,creatingHTMLontheservletcode,butdemonstratingpureservletfunctionalitywithREST,JSON,andJavaScriptwithsomeframeworkwouldmakethischapteraloneafewhundredpageslong,anditwoulddefinitelydistractourfocusfromJava.
PrintingHTMLtexttoaPrintWriterisnotsomethingthatshouldbenewtoyouatthispointinthisbook;therefore,wewillnotlistthatcodehere.YoucandownloadtheworkingexampleonGitHub.Thebranchforthisversionofthecodeisnosession.Insteadofprinting,wewillfocusontheservletparameterhandling.
TherequestparametersareavailableviathegetParametermethod,whichreturnsthestringvalueofaparameter.Thismethodassumesthatanyparameter,beitGETorPOST,appearsonlyonceintherequest.Incasethereareparametersthatappearmultipletimes,thevalueshouldhavebeenastringarray.Insuchacase,weshouldusegetParameterMap,whichreturnsthewholemapwiththeStringkeysandString[]values.Eventhoughwedonothavemultiplevaluesforanykeythistime,andwealsoknowthevaluesofthekeyscomingasPOSTparameters,wewillstillusethelattermethod.Thereasonforthisisthatwewilllaterusethesessiontostorethesevalues,andwewanttohaveamethodthatisreusableinthatcase.
IfyoulookattheearliercommitsintheGitrepository,youwillseethatthefirstversionusedgetParameterandIrefactoreditonlylaterwhenIcreatedthesecondversionoftheprogram,whichstoresthestateinasession.Neverbelieveifanyonetellsyouthatprogramsarecreatedperfectlyupfrontwithoutanyrefactoringduringdevelopment.Donotfeelashamedtocreatefoolishcodeandrefactoritlater.Itisonlyshamefulifyoudonotrefactorit.
Togettothatweconverttherequest'sMap<String,String[]>toMap<String,String>:
privateGamebuildGameFromRequest(HttpServletRequestrequest){
returnbuildGameFromMap(toMap(request));
}
privateMap<String,String>toMap(HttpServletRequestrequest){
log.debug("convertingrequesttomap");
returnrequest.getParameterMap().entrySet().
stream().collect(
Collectors.toMap(
Map.Entry::getKey,
e->e.getValue()[0]));
}
Then,weusethatmaptore-createthegame:
privateGamebuildGameFromMap(Map<String,String>params){
finalGuesssecret=newGuess(newColor[NR_COLUMNS]);
finalGamegame=newGame(table,secret);
for(introw=0;
params.containsKey(html.paramNameGuess(row,0));
row++){
Color[]colors=getRowColors(params,row);
Guessguess=newGuess(colors);
finalintfull=Integer.parseInt(params.get(html.paramNameFull(row)));
finalintpartial=Integer.parseInt(params.get(html.paramNamePartial(row)));
log.debug("Addingguesstogame");
game.addGuess(guess,full,partial);
}
returngame;
}
TheconversionfromStringtointisdoneviathemethodparseInt.ThismethodthrowsNumberFormatExceptionwhentheinputisnotanumber.Trytorunthegame,usethebrowser,andseehowJettyhandlesthecasewhentheservletthrowsanexception.Howmuchvaluableinformationdoyouseeinthebrowserthatcanbeusedbyapotentialhacker?Fixthecodesothatitaskstheuseragainifanyofthenumbersarenotwellformatted!
StoringstateontheserverTheapplicationstateshouldusuallynotbesavedontheclient.Theremaybesomespecialcaseinadditiontotheonewhereyouwriteeducationalcodeandwanttodemonstratehowtodoit.Generally,thestateoftheapplicationrelatedtotheactualuseisstoredinthesessionobjectoronsomedatabase.Thisisespeciallyimportantwhentheapplicationrequeststheusertoenteralotofdataanddoesnotwanttheusertolosetheworkifthereissomehiccupintheclientcomputer.
Youspendalotoftimeselectingtheappropriateitemsinanonlineshop,choosingtheappropriateitemsthatworktogether,creatingaconfigurationofyournewmodelairplane,andallofasudden,thereisablackoutinyourhome.Ifthestatewerestoredontheclientyou'dhavehadtostartfromscratch.Ifthestateisstoredontheserver,thestateissavedtodisk;theserversareduplicated,fedbybattery-backedpowersupplies,andwhenyourebootyourclientmachinewhenthepowercomesbackinyourhome,youlogin,andmiraculously,theitemsareallthereinyourshoppingbasket.Well,itisnotamiracle;itiswebprogramming.
Inourcase,thesecondversionwillstorethestateofthegameinthesession.Thiswilllettheuserhavethegamerestoresolongasthesessionisthere.Iftheuserquitsandrestartsthebrowser,thesessiongetslostandanewgamecanstart.
Sincethereisnoneedtosendtheactualcolorsandmatchinginhiddenfieldsthistime,theHTMLgenerationismodifiedabit,andthegeneratedHTMLwillalsobesimpler:
<html>
<head>
<linkrel="stylesheet"type="text/css"href="colors.css">
<title>Mastermindguessing</title>
<body>
<formmethod="POST"action="master">
<divclass="color3"></div>
<divclass="spacer"></div>
<divclass="color2"></div>
<divclass="spacer"></div>
<divclass="color1"></div>
<divclass="spacer"></div>
<divclass="color0"></div>
<divclass="spacer"></div>0
<divclass="spacer"></div>2<p>
<divclass="color5"></div>
...
<divclass="spacer"></div>
<divclass="color1"></div>
<divclass="spacer"></div>
<inputtype="text"name="full2"value="0"size="1"><inputtype="text"name="partial2"value="0"size="1">
<p>
<inputtype="submit"value="submit">
</form>
</body>
</head></html>
Thenumberoffullandpartiallymatchingcolorsisdisplayedasasimplenumber,sothisversiondoesnotallowcheatingorchangingpreviousresults.(Thesearethenumbers0and2afterthedivtagsthathavetheCSSclassspacer.)
ThehandlemethodinMastermindHandleralsochanges,asshowninthefollowing:
publicvoidhandle(HttpServletRequestrequest,
HttpServletResponseresponse)
throwsServletException,IOException{
Gamegame=buildGameFromSessionAndRequest(request);
GuessnewGuess=guesser.guess();
response.setContentType("text/html");
PrintWriterout=response.getWriter();
if(game.isFinished()||newGuess==Guess.none){
displayGameOver(out);
}else{
log.debug("Addingnewguess{}tothegame",newGuess);
game.addGuess(newGuess,0,0);
sessionSaver.save(request.getSession());
displayGame(out);
}
bodyEnd(out);
}
ThisversionoftheclassgetsaSessionSaverobjectinjectedbytheGuiceinjector.Thisisaclassthatwecreate.ThisclasswillconvertthecurrentTableintosomethingthatisstoredinthesession,anditalsorecreatesthetablefromthedatastoredinthesession.ThehandlemethodusesthebuildGameFromSessionAndRequestmethodtorestorethetableandtoaddthefullandpartialmatchanswersthattheuserjustgaveintherequest.Whenthemethodcreatesanewguessandfillsitinthetable,andalsosendsittotheclientintheresponse,itsavesthestateinthesessionbycallingthesavemethodviathesessionSaverobject.
ThebuildGameFromSessionAndRequestmethodreplacestheotherversion,whichwenamedbuildGameFromRequest:
privateGamebuildGameFromSessionAndRequest(HttpServletRequestrequest){
Gamegame=buildGameFromMap(sessionSaver.restore(request.getSession()));
Map<String,String>params=toMap(request);
introw=getLastRowIndex(params);
log.debug("lastrowis{}",row);
if(row>=0){
finalintfull=Integer.parseInt(params.get(html.paramNameFull(row)));
finalintpartial=Integer.parseInt(params.get(html.paramNamePartial(row)));
log.debug("settingfull{}andpartial{}forrow{}",full,partial,row);
table.setPartial(row,partial);
table.setFull(row,full);
if(full==table.nrOfColumns()){
game.setFinished();
}
}
returngame;
}
NotethatthisversionhasthesameillnessofusingtheparseIntmethodfromtheIntegerclassinJDK,whichthrowsanexception.
TheGameSessionSaverclassThisclasshasthreepublicmethods:
save:Thissavesatabletotheusersessionrestore:Thisgetsatablefromtheusersessionreset:Thisdeletesanytablethatmaybeinthesession
Thecodeoftheclassisthefollowing:
publicclassGameSessionSaver{
privatestaticfinalStringSTATE_NAME="GAME_STATE";
@Inject
privateHtmlToolshtml;
@Inject
Tabletable;
@Inject
ColorManagermanager;
publicvoidsave(HttpSessionsession){
Map<String,String>params=convertTableToMap();
session.setAttribute(STATE_NAME,params);
}
publicvoidreset(HttpSessionsession){
session.removeAttribute(STATE_NAME);
}
publicMap<String,String>restore(HttpSessionsession){
Map<String,String>map=
(Map<String,String>)
session.getAttribute(STATE_NAME);
if(map==null){map=newHashMap<>();}
returnmap;
}
privateMap<String,String>convertTableToMap(){
Map<String,String>params=newHashMap<>();
for(introw=0;row<table.nrOfRows();row++){
for(intcolumn=0;
column<table.nrOfColumns();column++){
params.put(html.paramNameGuess(row,column),
table.getColor(row,column).toString());
}
params.put(html.paramNameFull(row),
""+table.getFull(row));
params.put(html.paramNamePartial(row),
""+table.getPartial(row));
}
returnparams;
}
}
Whenwesavethesessionandconvertthetabletoamap,weuseaHashMap.Theimplementationinthiscaseisimportant.TheHashMapclassimplementstheSerializableinterface;therefore,wecanbesafeputtingittothesession.ThisalonedoesnotguaranteethateverythinginHashMapisSerializable.ThekeysandthevaluesinourcaseareStrings,andfortunately,theStringclassalsoimplementstheSerializableinterface.Thisway,theconvertedHashMapobjectcanbesafelystoredinthesession.
Alsonotethat,althoughserializationcanbeslow,storingHashMapinasessionissofrequentthatitimplementsitsownserializationmechanism.Thisimplementationisoptimizedandavoidsserializationbeingdependentontheinternalstructureofthemap.
ItistimetothinkaboutwhywehavetheconvertTableToMapmethodinthisclassandbuildGameFromMapinMastermindHandler.ConvertingthegameandthetableinittoaMapandtheotherwayroundshouldbeimplementedtogether.Theyarejusttwodirectionsofthesameconversion.Ontheotherhand,theimplementationoftheTabletoMapdirectionshoulduseaMapversionthatisSerializable.Thisisverymuchrelatedtosessionhandling.ConvertingaMapobject,ingeneral,toaTableobjectisonelevelhigher,restoringthetablefromwhereveritwasstored:client,session,database,orinthemoistureofthecloud.Sessionstorageisonlyonepossibleimplementation,andmethodsshouldbeimplementedintheclassthatmeetstheabstractionlevel.Thebestsolutioncouldbetoimplementtheseinaseparateclass.Youhavehomework!
Theresetmethodisnotusedfromthehandler.ThisisinvokedfromtheMastermindclass,thatis,theservletclasstoresetthegamewhenwestartit:
publicvoiddoGet(HttpServletRequestrequest,
HttpServletResponseresponse)
throwsServletException,IOException{
GameSessionSaversessionSaver=newGameSessionSaver();
sessionSaver.reset(request.getSession());
doPost(request,response);
}
Withoutthis,playingthegameagainstthemachineoncewouldjustdisplaythefinishedgameeverytimewewanttostartitagain,untilweexitthebrowserandrestartitorexplicitlydeletetheJSESSIONIDcookiesomewhereintheadvancedmenuofthebrowser.Callingresetdoesnotdeletethesession.Thesessionremainsthesame,andthusthevalueofJSESSIONIDtoo,butthegameisdeletedfromthesessionobjectthattheservletcontainermaintains.
RunningtheJettywebservletSincewehaveincludedtheJettypluginintoourGradlebuild,thetargetsofthepluginareavailable.TostartJettyisaseasyastypingthefollowing:gradlejettyRun
Thiswillcompilethecode,buildtheWARfile,andstarttheJettyservletcontainer.Tohelpusremember,italsoprintsthefollowingonthecommandline:Runningathttp://localhost:8080//hello
WecanopenthisURLandseetheopeningscreenofthegamewiththecolorsthattheprogramcreatedasafirstguess:
Nowitistimetohavesomefunandplaywithourgame,givinganswerstotheprogram.Donotmakeit
easyforthecode!Refertothefollowingscreenshot:
Atthesametime,ifyoulookattheconsolewhereyouhavetypedgradlejettyRun,youwillseethatthecodeisprintingoutlogmessages,asshowninthefollowingscreenshot:
Theseprintoutscomethroughtheloggerthatwehaveinourcode.Inthepreviouschapters,weusedtheSystem.out.printlnmethodcallstosendinformationalmessagestotheconsole.Thisisapracticethatshouldnotbefollowedinanyprogramthatismorecomplexthanahelloworld
Logging
ThereareseveralloggingframeworksavailableforJavaandeachhasadvantagesanddisadvantages.ThereisonebuiltintoJDKinthejava.util.loggingpackageandaccessingtheloggerissupportedbytheSystem.getLoggermethod:theSystem.LoggerandSystem.LoggerFinderclasses.Eventhoughjava.util.logginghasbeenavailableinJavasinceJDK1.4,alotofprogramsuseotherloggingsolutions.Inadditiontothebuilt-inlogging,wehavetomentionlog4j,slf4jandApacheCommonsLogging.Beforegettingintothedetailsofthedifferentframeworks,let'sdiscusswhyitisimportanttouselogginginsteadofjustprintingtothestandardoutput.
ConfigurabilityThemostimportantreasonisconfigurabilityandeaseofuse.Weuseloggingtorecordinformationabouttheoperationofcode.Thisisnotthecorefunctionalityoftheapplicationbutisinevitabletohaveaprogramthatcanbeoperated.Therearemessagesweprintouttothelog,whichcanbeusedbytheoperatingpersonneltoidentifyenvironmentalissues.Forexample,whenanIOExceptionisthrownanditgetslogged,theoperationmaylookatthelogsandidentifythatthediskgotfull.Theymaydeletefiles,oraddanewdiskandextendthepartition.Withoutthelogs,theonlyinformationwouldbethattheprogramdoesnotwork.
Thelogsarealsousedmanytimestohuntdownbugs.Someofthebugsdonotmanifestinthetestenvironmentandareverydifficulttoreproduce.Insuchacase,thelogsthatprintoutdetailedinformationabouttheexecutionofthecodearetheonlysourceoffindingtherootcauseofsomeerror.
SinceloggingneedsCPU,IObandwidth,andotherresources,itshouldbecarefullyexaminedwhatandwhentolog.Thisexaminationandthedecisionscouldbedoneduringprogramming,andasamatteroffact,thatistheonlypossibilityifweusedSystem.out.printlnforlogging.Ifweneedtofindabug,weshouldlogalot.Ifwelogalot,theperformanceofthesystemwillgodown.Theconclusionisthatwehavetologonlyifitisneeded.Ifthereisabuginthesystemthatcannotbereproduced,thedevelopersasktheoperationtoswitchondebugloggingforashortperiod.SwitchingonandoffdifferentpartsofloggingisnotpossiblewhenSystem.out.printlnisused.Whenthedebuglevellogisswitchedon,theperformancemaygodownforawhile,butatthesametime,thelogsbecomeavailableforanalysis.Atthesametime,theanalysisissimplerwhenwehavetofindtheloglinesthatarerelevant(andyoudonotknowbeforehandwhicharerelevant)ifthereisasmall(afewhundredmegabyteslogfile)ratherthanalotof2-GBcompressedlogfilestofindthelinesin.
Usingalogframework,youcandefineloggersthatidentifythesourceofthelogmessagesandloglevels.Astringusuallyidentifiesthelogger,anditisacommonpracticetousethenameoftheclassfromwhichthelogmessageiscreated.Thisissuchacommonpracticethatthedifferentlogframeworksprovidefactoryclassesthatgettheclassitself,insteadofitsname,togetalogger.
Thepossiblelogginglevelsmaybeslightlydifferentindifferentloggingframeworks,butthemostimportantlevelsareasfollows:
FATAL:Thisisusedwhenthelogmessageisaboutsomeerrorthatpreventstheprogramfromcontinuingitsexecution.ERROR:Thisisusedwhenthereissomesevereerror,buttheprogramcanstillgoonfunctioningalthough,probably,insomelimitedmanner.WARNING:Thisisusedwhenthereissomeconditionthatisnotadirectproblembutmaylaterleadtoanerrorifnotattended.Forexample,theprogramrecognizesthatadiskisnearfull,somedatabaseconnectionsanswerwithinlimitsbutclosetothetimeoutvalue,andsimilarsituations.INFO:Thisisusedtocreatemessagesaboutnormaloperationsthatmaybeinterestingtooperateandarenotanerrororwarning.Thesemessagesmayhelptheoperationtodebugtheoperationalenvironmentsettings.DEBUG:Thisisusedtologinformationabouttheprogramthatisdetailedenough(hopefully)tofinda
buginthecode.Thetrickisthatwhenweputthelogstatementintothecode,wedonotknowwhatbugitcouldbe.Ifweknew,webetterfixedit.TRACE:Thisisevenmoredetailedinformationabouttheexecutionofthecode.
Thelogframeworksareusuallyconfiguredusingsomeconfigurationfile.Theconfigurationmaylimitthelogging,switchingoffcertainlevels.Inanormaloperationalenvironment,thefirstthreelevelsareusuallyswitchedon,andINFO,DEBUG,andTRACEareswitchedonwhenreallyneeded.Itisalsopossibletoswitchonandoffcertainlevelsonlyforcertainloggers.IfweknowthattheerroriscertainlyintheGameSessionSaverclass,thenwecanswitchontheDEBUGlevelonlyforthatclass.
Logfilesmayalsocontainotherinformationthatwedidnotdirectlycodeandwouldbeverycumbersometoprinttothestandardoutput.Usually,eachlogmessagecontainstheprecisetimewhenthemessagewascreated,thenameofthelogger,and,manytimes,theidentifierofthethread.Imagineifyouwereforcedtoputallthistoeachandeveryprintlnargument;youwouldprobablysoonwritesomeextraclasstodothat.Don't!Ithasalreadybeendoneprofessionally:itistheloggerframework.
Loggerscanalsobeconfiguredtosendthemessagetodifferentlocations.Loggingtotheconsoleisonlyonepossibility.Loggingframeworksarepreparedtosendmessagestofiles,database,WindowsEventRecorder,syslogservice,ortoanyothertarget.Thisflexibility,whichmessagetoprint,whatextrainformationtoprint,andwheretoprintisreachedbyseparatingthedifferenttasksthattheloggerframeworkdoesintoseveralclassesfollowingthesingleresponsibilityprinciple.
Theloggerframeworksusuallycontainloggersthatcreatethelogs,formattersthatformatthemessagefromtheoriginalloginformation,manytimes,addinginformationsuchasthreadIDandtimestamp,andappendersthatappendtheformattedmessagetosomedestination.Theseclassesimplementinterfacesdefinedintheloggingframeworkandnothingbutthesizeofthebookstopsusfromcreatingourownformattersandappenders.
Whenalogisconfigured,theappendersandformattersareconfigured,giventheclassthatimplementsthem.Therefore,whenyouwanttosendsomelogstosomespecialdestination,youarenotlimitedtotheappendersthatareprovidedbytheauthorsoftheframework.Therearealotofindependentopen-sourceprojectsfordifferentloggingframeworksprovidingappendersfordifferenttargets.
PerformanceThesecondreasontousealoggingframeworkisperformance.Althoughitisnotgoodtooptimizeforperformancebeforeweprofilethecode(prematureoptimization),usingsomemethodologyknowntobeslowandinsertingseverallinesintoourperformance-criticalcode,invokingslowmethodsisnotreallyprofessionaleither.Usingawell-established,highlyoptimizedframeworkinawaythatisindustrybestpracticeshouldnotbequestionable.
UsingSystem.out.printlnsendsthemessagetoastreamandreturnsonlywhentheIOoperationisdone.Usingreallogginghandlestheinformationtotheloggerandletstheloggerdotheloggingasynchronously,anditdoesnotwaitforcompletion.Itisreallyadrawbackthatloginformationmaybelostifthereissomesystemfailure,butthisisusuallynotaseriousissueconsideringhowrarelythathappensandwhatisontheothersideofthewage:performance.Whatdoweloseifthereisamissingdebugloglinewhenthediskgotfull,anywayrenderingthesystemunusable?
Thereisoneexceptiontothis:auditlogging—whensomeloginformationaboutthetransactionsofthesystemhastobesavedforlegalreasonssothattheoperationandtheactualtransactionscanbeaudited.Insuchacase,theloginformationissavedinatransactionalmanner,makingthelogpartofthetransaction.Becausethatisatotallydifferenttypeofrequirement,auditloggingisnotusuallydonewithanyoftheseframeworks.
Also,System.out.printlnisnotsynchronizedandthatwaydifferentthreadsmayjustgarbletheoutput.Logframeworkspayattentiontothisissue.
LogframeworksThemostwidelyusedloggingframeworkisApachelog4j.Itcurrentlyhasasecondversionthatisatotalrewriteofthefirstversion.Itisveryversatileandhasmanyappendersandformatters.Theconfigurationoflog4jcanbeinXMLorpropertiesfileformat,anditcanalsobeconfiguredthroughAPI.
Theauthoroflog4jversion1createdanewloggingframework:slf4j.Thislogginglibraryisessentiallyafaçadethatcanbeusedtogetherwithanyotherloggingframework.Thus,whenyouuseslf4jinalibraryyoudevelop,andyourcodeisaddedtoaprogramasadependencythatusesadifferentloggingframework,itiseasytoconfigureslf4jtosendthelogstotheloggersoftheotherframework.Thus,thelogswillbehandledtogetherandnotinseparatefile,whichisdesirabletodecreasethecostofoperation.Whendevelopingyourlibrarycodeoranapplicationthatusesslf4j,thereisnoneedtoselectanotherlogframeworktoslf4j.Ithasitsownsimpleimplementationcalledbacklog.
ApacheCommonsLoggingisalsoafaçadewithitsownloggingimplementationifnothingelsefails.Themajordifferencefromslf4jisthatitismoreflexibleinconfigurationandwhatunderlyingloggingtouse,anditimplementsarun-timealgorithmtodiscoverwhichloggingframeworkisavailableandistobeused.Theindustrybestpracticeshowsthatthisflexibility,whichalsocomeswithhighercomplexityandcost,isnotneeded.
Java9loggingJava9includesafacadeimplementationforlogging.Theuseisverysimpleandwecanexpectthatloggingframeworkswillverysoonstarttosupportthisfaçade.ThefactthatthisfaçadeisbuiltintotheJDKhastwomajoradvantage:
Thelibrariesthatwanttologdonotneedtohaveanydependencyonanyloggingframeworkorloggingfaçadeanymore.TheonlydependencyistheJDKlogfaçadethatisthereanyway.TheJDKlibrariesthatlogthemselvesusethisfaçadeandthustheywilllogintothesamelogfileastheapplication.
IfweusetheJDK-providedloggingfaçadethestartoftheColorManagerclasswillbechangedtothefollowing:
packagepackt.java9.by.example.mastermind;
importjavax.inject.Inject;
importjavax.inject.Named;
importjavax.inject.Singleton;
importjava.util.HashMap;
importjava.util.Map;
importjava.lang.System.Logger;
importstaticjava.lang.System.Logger.Level.DEBUG;
@Singleton
publicclassColorManager{
protectedfinalintnrColors;
protectedfinalMap<Color,Color>successor=newHashMap<>();
privateColorfirst;
privatefinalColorFactoryfactory;
privatestaticfinalLoggerlog=System.getLogger(ColorManager.class.getName());
@Inject
publicColorManager(@Named("nrColors")intnrColors,
ColorFactoryfactory){
log.log(DEBUG,"creatingcolorManagerfor{0}colors",
nrColors);
Inthisversionwedonotimporttheslf4jclasses.Insteadweimportthejava.lang.System.Loggerclass.
NotethatwedonotneedtoimporttheSystemclass,becausetheclassesfromthejava.langpackageareautomaticallyimported.ThisisnottruefortheclassesthatarenestedclassesintheSystemclass.
TogetaccesstotheloggertheSystem.getLoggerstaticmethodiscalled.Thismethodfindstheactualloggerthatisavailableandreturnsoneforthenamethatwepassasargument.ThereisnoversionofthemethodgetLoggerthatacceptstheclassastheargument.IfwewanttosticktotheconventionthenwehavetowriteColorManager.class.getName()togetthenameoftheclassorwecanwritetherethenameoftheclassasastring.Thesecondapproachhasthedrawbackthatitdoesnotfollowthechangeofthenameoftheclass.IntelligentIDEslikeIntelliJ,Eclipse,orNetbeansrenamethereferencestoclassesautomaticallybuttheyhaveahardtimewhenthenameoftheclassisusedinastring.
TheinterfaceSystem.Loggerdoesnotdeclaretheconveniencemethodserror,debug,warning,andsoon,thatarefamiliarfromotherloggingframeworksandfaçade.Thereisonlyonemethodnamedlogandthefirst
argumentofthismethodistheleveloftheactuallogweissue.Thereareeightlevelsdefined:ALL,TRACE,DEBUG,INFO,WARNING,ERROR,andOFF.Whencreatingalogmessagewearesupposedtouseoneofthemiddlesixlevels.ALLandOFFaremeanttobepassedtotheisLoggablemethod.Thismethodcanbeusedtocheckiftheactuallogginglevelgetsloggedornot.Forexample,ifthelevelissettoINFOthenmessagessentwithDEBUGorTRACEwillnotbeprinted.
TheactualimplementationislocatedbytheJDKusingtheserviceloaderfunctionality.Thelogimplementationhastobeinamodulethatprovidestheinterfacejava.lang.System.LoggerFinderviasomeimplementation.InotherwordsthemoduleshouldhaveaclassthatimplementstheLoggerFinderinterfaceandthemodule-info.javashoulddeclarewhichclassitisusingthecode:
providesjava.lang.System.LoggerFinderwith
packt.java9.by.example.MyLoggerFinder;
TheMyLoggerFinderclasshastoextendtheLoggerFinderabstractclasswiththemethodgetLogger.
LoggingpracticeThepracticeofloggingisverysimple.Ifyoudonotwanttospendtoomuchtimeexperimentingwithdifferentloggingsolutionsandyoudonothavesomespecialrequirement,thensimplygowithslf4j,addtheJARtothedependencylistasacompiledependency,andstartusinglogginginthesourcecode.
Sinceloggingisnotinstance-specific,andloggersimplementthreadsafety,thelogobjectsthatweusuallyusearestoredinastaticfield,andsincetheyareusedaslongastheclassisused,theprogramrunningthefieldisalsofinal.Forexampleusingtheslf4jfaçadewecangetaloggerwiththefollowingcommand:privatestaticfinalLoggerlog=LoggerFactory.getLogger(MastermindHandler.class);
Togetthelogger,theloggerfactoryisused,whichjustcreatestheloggerorreturnstheonethatisalreadyavailable.
Thenameofthevariableisusuallylogorlogger,butdonotbesurprisedifyouseeLOGorLOGGER.Thereasonforuppercasingthenameofthevariableisthatsomestaticcodeanalysischeckerstreatstaticfinalvariablesasconstants,astheyreallyare,andtheconventionintheJavacommunityistouseuppercasenamesforsuchvariables.Itisamatteroftaste;manytimeslogandloggerareusedinlowercase.
Tocreatealogitemthemethodstrace,debug,info,warn,anderrorcreateamessagewiththerespectivelevelasthenameimplies.Forexample,considerthefollowingline:
log.debug("Addingnewguess{}tothegame",newGuess);
Itcreatesadebugmessage.Slf4jhassupportforformattingusingthe{}literalinsidestrings.Thisway,thereisnoneedtoappendthestringfromsmallparts,andincasetheactuallogitemisnotsenttothelogdestination,theformattingwillnotperform.IfweuseStringconcatenationinanyformtopassastringasanargument,thentheformattingwillhappenevenifdebugloggingisnotdesiredaspertheexample.
Theloggingmethodsalsohaveaversionthatgetsonlytwoarguments:aStringmessageandThrowable.Inthiscase,theloggingframeworkwilltakecareoftheoutputoftheexceptionandthestacktracealongwithit.Ifyoulogsomethinginexceptionhandlingcode,logtheexceptionandlettheloggerformatit.
OthertechnologiesWediscussedtheservlettechnology,abitofJavaScript,HTML,andCSS.Whenprogramminginarealprofessionalenvironment,thesetechnologiesaregenerallyused.Thecreationoftheuserinterfaceofapplications,however,wasnotalwaysbasedonthesetechnologies.Olderoperatingsystem-nativeGUIapplicationsaswellasSwing,AWT,andSWTuseadifferentapproachtocreateUI.TheybuilduptheUIfacingtheuserfromprogramcode,andtheUIisbuiltasahierarchicalstructureofcomponents.Whenwebprogrammingstarted,Javadevelopershadexperiencewithtechnologiesliketheseandprojectscreatedframeworksthattriedtohidethewebtechnologylayer.
OnetechnologyworthmentioningisGoogleWebToolkit,whichimplementstheserveraswellasthebrowsercodeinJava,butsincethereisnoJavaenvironmentimplementedinthebrowsers,ittranspiles(converts)theclientpartofthecodefromJavatoJavaScript.Thelastreleaseofthetoolkitwascreatedtwoyearsagoin2014andsincethenGooglehasreleasedothertypesofwebprogrammingtoolkitsthatsupportnativeJavaScript,HTML,andCSSclientdevelopment.
Vaadinisalsoatoolkitthatyoumaycomeacross.ItletsyouwriteGUIcodeontheserverinJava.ItisbuiltontopofGWTandiscommerciallysupported.ItmaybeagoodchoiceincasetherearedevelopersavailablewhohaveexperiencewithGUIdevelopmentinJavabutnotinwebnativetechnologies,andtheapplicationdoesnotrequirespecialusabilitytuningontheclientside.Atypicalintranetcorporateapplicationcanselectitasatechnology.
JavaServerFaces(JSF)isatechnologythattriestooffloadtheclient-sidedevelopmentoftheapplicationfromthedevelopersprovidingwidgetsreadytobeusedandtheserverside.ItisacollectionofseveralJavaSpecificationRequests(JSR)andthereareseveralimplementations.ThecomponentsandtheirrelationsareconfiguredinXMLfilesandtheservercreatestheclientnativecode.Inthistechnology,thereisnotranspilationfromJavatoJavaScript.Itismorelikeusingalimitedbuthugesetofwidgets,limitingtheusetothoseonly,andgivingupdirectprogrammingofthewebbrowser.Ifonehastheexperienceandknowledge,however,theycancreatenewwidgetsinHTML,CSS,andJavaScript.
TherearemanyothertechnologiesthatweredevelopedtosupportwebapplicationsinJava.Themodernapproachadvocatedbymostofthebigplayersistodeveloptheserversideandtheclientsideusingseparatetoolsetsandmethodologies,andconnectthetwousingRESTcommunication.
Summary
Inthischapter,youlearntthestructureofwebprogramming.ThiswasnotpossiblewithoutunderstandingthebasicsofTCP/IPnetworking,whichistheprotocoloftheInternet.TheapplicationlevelprotocolthatisusedoverthatisHTTP,currentlyinaverynewversion2.0,whichisstillnotsupportedbytheservletstandard.WecreatedaversionoftheMastermindgamethat,thistime,canreallybeplayedusingthebrowserandwestarteditinadevelopmentenvironmentusingJetty.Weexaminedhowtostorethegamestateandimplementedtwoversions.Finally,welearnedthebasicsofloggingandwelookedatothertechnologies.Atthesametime,wealsolookedatthedependencyinjectionimplementationGuicefromGoogle,andwestudiedhowitworksunderthehood,andwhyandhowtouseit.
Afterthischapter,youwillbeabletostartthedevelopmentofawebapplicationinJavaandwillunderstandthearchitectureofsuchaprogram.YouwillunderstandwhatisunderthehoodwhenyoustartlearninghowtoprogramwebapplicationsusingtheSpringframework,whichhidesmanyofthecomplexitiesofwebprogramming.
BuildingaCommercialWebApplicationUsingRESTWewereplayingaroundtillnow,butJavaisnotatoy.WewanttouseJavaforsomethingrealandserious,commercialandprofessional.Inthischapter,wewilldothat.Theexampleisnotsomethingthatisonlyinterestingtoplaywith,suchasMastermindinthepreviousthreechapters,butratherarealcommercialapplication.Notareal-lifeapplicationactually.Youshouldnotexpectanythinglikethatinabook.Itwouldbetoolongandnoteducatingenough.However,theapplicationthatwewilldevelopinthischaptercanbeextendedandcanbeusedasacoreforareal-lifeapplicationincaseyoudecidedtodoso.
Inthepreviouschapter,wecreatedservlets.Todoso,weusedtheservletspecification,andwehand-implementedservlets.Thatissomethingyouwillrarelydothesedays.Instead,wewilluseareadilyavailableframework,thistime,Spring.ItisthemostwidelyusedframeworkforJavacommercialapplications,andIdaresayitisthedefactostandard.Itwilldoallthetediousworkthatwehadtodo(atleasttounderstandandlearnhowaservletworks)inthepreviouschapter.WewillalsouseSpringfordependencyinjection(whyusetwoframeworkswhenonedoesitall?),andwewilluseTomcat.
Inthepreviouschapter,weusedGuiceasaDIframeworkandJettyasaservletcontainer.Theycanbeaperfectlygoodchoiceforsomeprojects.Forotherprojects,otherframeworksdobetter.Tohavetheopportunitytolookatdifferenttoolsinthisbook,wewillusedifferentframeworkseventhoughalltheexamplescouldbecreatedbysimplyusingTomcatandSpring.
Thecommercialapplicationwewilldevelopwillbeanorderingsystemtargetingresellers.Theinterfacewewillprovidetotheuserswillnotbeawebbrowser;rather,itwillbeREST.Theuserswillthemselvesdevelopapplicationsthatcommunicatewithoursystemandplaceordersfordifferentproducts.Thestructureoftheapplicationwewilldevelopwillbemicroservicesarchitecture,andwewillusesoapUItotesttheapplication,inadditiontothestandardChromedevelopertoolfeatures.
TheMyBusinesswebshop
Imaginethatwehaveahugetradingandlogisticscompany.Therearetensofthousandsofdifferentproductsontheshelves;hundredsoflorriescometoourwarehousebringingnewgoods,andhundredsoflorriesdelivergoodstoourcustomers.Tomanagetheinformation,wehaveaninventorysystemthatkeepstrackofthegoodseveryday,hour,andminutetoknowwhatweactuallyhaveinthewarehouse.Weserveourcustomerswithouthumansmanagingthewarehouseinformation.Formerly,therewerephones,faxmachines,andeventelex,buttoday,allweuseistheInternetandwebservices.Wedonotprovideawebsiteforourcustomers.Wehaveneverdirectlyservedtheendusersinourimaginedbusiness,butthesedays,wehaveasubsidiarythatwestartedoffasaseparatecompanytodojustthat.Theyhaveawebsite,anditistotallyindependentfromus.Theyarejustoneofourhundredsofregisteredpartnerswhoeachuseawebserviceinterfacetoseetheproductswehave,orderproducts,andtracktheorderstatus.
SamplebusinessarchitectureOurpartnersarealsolargecompanieswithautomatedadministration,withseveralprogramsrunningonseveralmachines.Wehavenointerestintheirarchitectureandthetechnologytheyuse,butwewanttointegratetheiroperations.Wewanttoservetheminawaythatdoesn'trequireanyhumaninteractionfortheadministrationtoordergoodsoneitherofoursides.Todoso,awebserviceinterfaceisprovidedthatcanbeutilizednomatterwhatITinfrastructuretheyuse.
Onourside,asweimaginetheexample,werecentlyreplacedourmonolithicapplicationwithmicroservicesarchitecture,andthoughtherearestillsomeSOAP-basedsolutionsinthesystem,mostofthebackendmodulescommunicateusingHTTPSandRESTprotocols.SomeofthemodulesstillrelyonasynchronousfiletransfersdoneonadailybasisusingFTPstartedfromaUNIXcronjob.TheGeneralLedgersystemwasprogrammedinCOBOL.Fortunately,wedonotneedtodealwiththesedinosaurs.
Allthisstructureisanimaginedsetupbutarealisticone.Imadeupanddescribedthesepartstogiveyouapictureofhowyoumayseemixedtechnologiesinalargeenterprise.WhatIdescribedhereisaverysimplesetup.Therearecompaniesthathavemorethanathousandsoftwaremodulesintheirsystemsusingdifferenttechnologiesandtotallydifferentinterfaces,allinterconnectedwitheachother.Thisisnotbecausetheylikethemess,butthatisthewayitbecomesafter30yearsofcontinuousITdevelopment.Newtechnologiescomeandoldtechnologiesfadeout.Thebusinesschangesandyoucannotsticktotheoldtechnologiesifyouwanttostaycompetitive.Atthesametime,youjustcannotreplacetheentireinfrastructureinstantaneously.Theresultisthatweseeinanenterprisefairlyoldtechnologiesstillrunningand,manytimes,newtechnologies.Oldtechnologiesgetrolledoutbytime.Theydonotstayforever,andstill,wearesurprisedsometimeswhenadinosaurcomesinfrontofus.
Whatwehavetodealwithisthetwofrontendcomponentsthatwewilldevelop.Theseareasfollows:
ProductInformationOrderPlacementandTracking
Inthefollowingimage,youcanseethearchitecturalUMLdiagramofthestructurethatwelookat.Thepartswewillhaveinteractionwithareonlythefrontendcomponents,butithelpsunderstandtheworkingandtheirroleifwehaveabiggerpicture:
ProductInformationdeliversinformationaboutasingleproduct,butitcanalsodeliveralistofproductsbasedonthequerycriteria.OrderPlacementandTrackingprovidesfunctionstoplaceanorderandalsoletstheclienttoquerythestateofpastorders.
Toprovideproductinformation,weneedaccesstotheProductCatalogmodulethatholdstheactualproductdetails.
TherecanbealotofothertasksthattheProductCatalogdoes,andthatisthereasonitisaseparatemodule.Itcanhave,forexample,aworkflowandapprovalenginethatletsproductadministratorstoenterproductdataandmanagerstocheckandapprovethedata.Approvalisusuallyacomplexprocess,consideringtyposandlegalquestions(wedonotwanttotradeunlicenseddrugs,explosives,andsoon),andcheckingthequalityandapprovalstateofthesourceofthegoods.Manycomplextasksareincludedthatmakeitabackendmodule.Inlargeenterpriseapplications,thefrontendsystemsrarelydoanythingelseotherthantheverybasicfunctionalityofservingtheoutsideparties.Butthisisgoodforus;wecanfocusontheservicethatwehavetodeliver.Andthisisalsogoodforthearchitecture.Itisthesameprincipleasinobject-orientedprogramming:singleresponsibility.
TheProductInformationmodulealsohastoconsultwiththeAccessControlmoduletoseeifacertainproductcanbedeliveredtotheactualcustomer,andwiththeinventorytoseeifthereisanyproductleft,sowedonotofferaproductthatisoutofstock.
TheOrderPlacementandTrackingalsoneedsaccesstoProductInventoryandAccessControlmodulestocheckwhethertheordercanbefulfilled.Atthesametime,italsoneedsservicesfromthePricingmodule,whichcancalculatethepricefortheorder,andfromtheLogisticsmodule,whichtriggersthecollectionofgoodsfromtheinventorylocationsandshipmenttothecustomer.Logisticsalsohasaconnectionto
invoicing,whichhasaconnectiontotheGeneralLedger,buttheseareonthepictureonlytoshowthatthetravelofinformationdoesnotendthere.Therearemanyothermodulesthatrunthecompany,allofwhicharenoneofourinterestatthemoment.
MicroservicesThearchitecturedescribedinthepreviouschapterisnotacleanmicroservicearchitecture.Youwillnevermeetoneinitspureforminanyenterprise.Itismorelikesomethingthatwereallymeetinarealcompanymovingfrommonolithictomicroservices.
WetalkaboutthemicroservicearchitecturewhentheapplicationisdevelopedintheformofmanysmallservicesthatcommunicatewitheachotherusingsomesimpleAPI,usuallyoverHTTPandREST.Theservicesimplementbusinessfunctionalitiesandcanbedeployedindependently.Manytimes,itisdesirablethattheservicedeploymentisautomated.
Theindividualservicescanbedevelopedusingdifferentprogramminglanguages,canusedifferentdatastorage,andcanrunondifferentoperatingsystems;thus,theyarehighlyindependentofeachother.Theycanbe,andusuallyare,developedbydifferentteams.Theimportantrequirementisthattheycancooperate;thus,theAPIoneserviceimplementsisusablebytheotherservicesthatbuilduponit.
ThemicroservicearchitectureisnottheHolyGrailofallarchitectures.Itgivesdifferentanswerstosomeproblemsfrommonolithicarchitectures,andmanytimes,theseanswersworkbetterusingmoderntools.Theapplicationsstillhavetobetestedanddebugged,performancehastobemanaged,andbugsandissueshavetobeaddressed.Thedifferenceisthattestingcanbeseparatedalongdifferenttechnologies;debuggingmayneedmorenetwork-relatedwork.Thesemaybegood,bad,orbothatthesametime.Forthedevelopers,however,theadvantageisclear.Theycanworkonasmallerunitindependentlyandcanseetheresultoftheirworkfaster.Whendevelopingasinglemoduleofamonolithicapplication,theresultcanbeseenwhentheentireapplicationgetsdeployed.Inthecaseofalargeapplication,thatmayberare.Atypicaldeploymentcycleinalargecorporatedevelopingmonolithiciseveryfewmonths,say3,butitisnotraretoseethereleasedevelopmenttwiceorevenonceayear.Developingmicroservices,thenewmodulecanbedeployedassoonasitisreadyandtested.
Ifyouwanttoreadmoreonmicroservices,thefirstandthemostauthenticsourceisthearticlebyMartinFowler(http://www.martinfowler.com/articles/microservices.html).
ServiceinterfacedesignWedesignthetwointerfacesthatwewillimplement.Whenwedesigninterfaces,wefocusonthefunctionalityfirst.Formattingandprotocolcomelater.Interfacesgenerallyshouldbesimpleand,atthesametime,shouldaccommodatethefuturechange.Thisisahardproblembecausewecannotseethefuture.Business,logistics,andallotherexpertsmayseesomepartofthefuture:howtheworldwillchangeandwhatitwillimposeontheoperationofthecompanyand,especially,ontheinterfaceweprovideforourpartners.
Thestabilityofaninterfaceisofutmostimportancebecausethepartnersareoutsideentities.Wecannotrefactorthecodetheyuse.WhenwechangeaJavainterfaceinourcode,thecompilerwillcomplainatallthecodelocationswherethechangeshouldbefollowed.Incaseofaninterfacethatisusedoutsideofourrealm,thisisnotthecase.EvenifitisonlyaJavainterfacethatwepublishasopensourceonGitHub,weshouldbepreparedthatouruserswillfaceissuesifwechangethelibraryinanincompatibleway.Inthatcase,theirsoftwarewillnotcompileandworkwithourlibrary.Inthecaseofanorderingsystem,itmeansthattheywillnotorderfromusandwewillsoonbeoutofbusiness.
Thisisoneofthereasonswhyinterfacesshouldbesimple.Althoughthisisgenerallytrueformostofthethingsinlife,itisextremelyimportantforsuchinterfaces.Itistemptingtoprovideconveniencefeaturesforthepartnersbecausetheyareeasytoimplement.Inthelongrun,however,thesefeaturesmaybecomeveryexpensiveastheyneedmaintenance,shouldbekeptbackwardcompatible,and,inthelongrun,maynotgainasmuchastheycost.
Toaccessproductinformation,weneedtwofunctions.Oneofthemlistscertainproductsandanotherreturnsthedetailsofaspecificproduct.IfitwereaJavaAPI,itwouldlookasfollows:
List<ProductId>query(Stringquery);
ProductInformationbyId(ProductIdid);
Similarly,orderplacementmaylookasshowninthefollowing:
OrderIdplaceOrder(Orderorder);
Weprovidethesefunctionalitiesinourapplicationviaawebserviceinterfaceand,morespecifically,RESTusingJSON.WewilldiscussthesetechnologiesinabitmoredetailedmanneralongwiththeSpringframeworkandModelViewControllerdesignpattern,butfirstlet'slookattheproductinformationcontrollertogetsomefeelingofhowourprogramwilllook:
packagepackt.java9.by.example.mybusiness.productinformation;
import...
@RestController
publicclassProductInformationController{
@Autowired
ProductLookuplookup;
@RequestMapping("/pi/{productId}")
publicProductInformation
getProductInformation(@PathVariableStringproductId){
ProductInformationproductInformation=
lookup.byId(productId);
returnproductInformation;
}
@RequestMapping("/query/{query}")
publicList<String>lookupProductByTitle(@PathVariableStringquery,HttpServletRequestrequest){
//tobedevelopedlater
}
}
Ifyoucomparethecodeoftheservletwiththeprecedingcode,youcanseethatthisismuchsimpler.WedonotneedtodealwiththeHttpServletRequestobject,callanAPItogetaparameter,orcreateanHTMLoutputandwriteittotheresponse.Theframeworkdoesthis.Weannotatethe@RestControllerclass,tellingSpringthatthisisacontrollerthatutilizestheRESTwebservices;thus,itwillbydefaultcreateaJSONresponsefromtheobjectwereturn.WedonotneedtocareabouttheconversionoftheobjecttoJSON,althoughwecanifthereisreallyaneed.TheobjectwillautomaticallybeconvertedtoJSONusingthefieldnamesusedintheclassandthefieldvaluesoftheinstancewereturn.IftheobjectcontainsmorecomplexstructuresthanjustplainString,int,anddoublevalues,thentheconverterispreparedfornestedstructuresandthemostcommondatatypes.
TohavedifferentcodehandlinganddifferentURLsontheservlet,allweneedtodoistoannotatethemethodwith@RequestMapping,providingthepathpartoftheURL.The{productId}notationinsidethemappingstringisreadableandeasytomaintain.SpringjustcutsthevaluefromthereandputsitforusintheproductIdvariable,asrequestedbythe@PathVariableannotation.
Theactuallookupoftheproductisnotimplementedinthecontroller.Thatisnotthefunctionofthecontroller.Thecontrolleronlydecideswhatbusinesslogictoinvokeandwhatviewtouse.Apartofitisimplementedintheframework,andtheverysmallpartyoucanseetheprecedingcode.Thebusinesslogicisimplementedinaserviceclass.Aninstanceofthisclassisinjectedtothelookupfield.ThisisalsodonebySpring.Theactualworkwehavetodoistoinvokethebusinesslogic,whichthistime,sincewehaveonlyone,isfairlyeasy.
Mostofthesethingsseemmagicwithoutsomemoredetailsaboutwhattheframeworkdoesforus.Therefore,beforegoingon,wewillhavealookatthebuildingblocks:JSON,REST,MVC,andabitoftheSpringframework.
JSONJSONstandsforJavaScriptObjectNotation.Itisdefinedonthesite,http://www.json.org/.ThisisatextualnotationinthesamewayastheobjectliteralsaredefinedinJavaScript.Anobjectrepresentationstartswiththe{characterandendswiththe}character.Thetextinbetweendefinesthefieldsoftheobjectsintheform,string:value.Thestringisthenameofthefield,andsinceJSONwantstobelanguageagnostic,itallowsanycharacterstobeapartofthenameofafield,andthusthisstring(aswellasanystringinJSON)shouldstartandendwiththe"characters.
Thismayseemstrangeand,manytimes,whenyoustartworkingwithJSON,itiseasytoforgetandwrite{myObject:"hasastring"}insteadofthecorrect{"myObject":"hasastring"}notation.
Commasseparatethefields.YoucanalsohavearraysinJSON.Theystartandendwith[and]characters,respectively,andtheycontaincomma-separatedvalues.Thevalueinanobjectfieldorinanarraycanbeastring,anumber,anobject,anarray,oroneoftheconstants,true,false,andnull.
Generallyspeaking,JSONisaverysimplenotationtodescribedatathatcanbestoredinanobject.Itiseasytowriteusingtexteditorsandeasytoread,andthusitiseasiertodebuganycommunicationthatusesJSONinsteadofmorecomplexformats.WaystoconvertJSONtoaJavaobjectandtheotherwayround,arereadilyavailableinlibrariesthatwewilluseinthischapter.AsampleJSONobjectthatdescribesaproductfromoursamplecodeisalsoavailableinthesourcecodeoftheprogram,asfollows:
{"id":"125","title":"BarStool","description":"anotherfurniture","size":[20.0,2.0,18.0],"weight":300.0}
NotethattheformattingofJSONdoesnotrequireanewline,butatthesametime,thisisalsopossible.Program-generatedJSONobjectsareusuallycompactandarenotformatted.Whenweeditsomeobjectusingatexteditor,wetendtoformattheindentationofthefieldsinthesamewayasweusuallydoinJavaprogramming.
RESTThereisnoexactdefinitionoftheRESTprotocol.ItstandsforRepresentationalstatetransfer,whichprobablydoesnotmeanathingtosomeonewhohasneverheardofit.WhenweprogramtheRESTAPI,weusetheHTTP(S)protocol.Wesendsimplerequeststotheserver,andwegetsimpleanswersthatweprogram.Thisway,theclientofthewebserverisalsoaprogram(bytheway,thebrowserisalsoaprogram)thatconsumestheresponsefromtheserver.Theformatoftheresponse,therefore,isnotHTMLformattedusingCSSandenrichedbyclient-sidefunctionalitiesbyJavaScript,butrathersomedatadescriptiveformatsuchasJSON.RESTdoesnotsetrestrictionsontheactualformat,butthesedays,JSONisthemostwidelyused.
ThewikipagethatdescribesRESTisavailableathttps://en.wikipedia.org/wiki/Representational_state_transfer.RESTinterfacesareusuallymadesimple.TheHTTPrequestsalmostalwaysusetheGETmethod.ItalsomakesthetestingofRESTservicessimplesincenothingiseasierthanissuingaGETrequestfromabrowser.POSTrequestsareonlyusedwhentheserviceperformssometransactionorchangeontheserver,andthatway,therequestissendingdatatotheserverratherthangettingsomedata.
Inourapplication,wewillusetheGETmethodtoqueryalistofproductsandgetinformationaboutaproduct,andwewillonlyusePOSTtoorderproducts.Theapplicationthatservestheserequestswillruninaservletcontainer.Youhavelearnthowtocreateanakedservletwithouttheuseofaframework.Inthischapter,wewillusetheSpringframework,whichoffloadsmanyofthetasksfromthedeveloper.Therearemanyprogramconstructsinservletprogrammingthatarejustthesamemostofthetime.Theyarecalledboilerplatecode.TheSpringframeworkutilizestheModelViewControllerdesignpatterntodevelopwebapplications;thus,wewilllookatitinbrief,beforediscussingSpringingeneral.
ModelViewControllerModelViewController(MVC)isadesignpattern.Designpatternsareprogrammingconstructs:simplestructuresthatgivesomehintonhowtosolvesomespecificproblems.Theterm,designpatternwascoinedandformallydescribedinthebook,DesignPatterns,ElementsofReusableObject-OrientedSoftware,writtenbyErichGamma,RichardHelm,RalphJohnson,andJohnVlissides.Thebookdefinesadesignpatternasastructurewithaname,aproblem,andasolution.Thenamedescribesthepatternandgivesthevocabularyforthedevelopercommunitytousewhentalkingaboutthesepatterns.Itisimportantthatdifferentdevelopersusethesamelanguageterminologyinordertounderstandeachother.Theproblemdescribesthesituation,thatis,thedesignproblemwherethepatterncanbeapplied.Thesolutiondescribesclassesandobjectsandtherelationsbetweenthem,whichcontributetoagooddesign.
OneofthemisMVC,whichissuitableforprogrammingwebapplicationsbutgenerallyforanyapplicationthathasauserinterface.Inourcase,wedonothaveaclassicaluserinterfacebecausetheclientisalsoaprogram;still,MVCcanbeandisagoodchoicetouse.
TheMVCpattern,asthenamealsoindicates,hasthreeparts:amodel,aview,andacontroller.Thisseparationfollowsthesingleresponsibilityprinciple,requiringonepartforeachdistinctresponsibility.Thecontrollerisresponsibleforhandlingtheinputsofthesystem,anditdecideswhatmodelandviewtouse.Itcontrolstheexecutionbutusuallydoesnotdoanybusinesslogic.Themodeldoesthebusinesslogicandcontainsthedata.Viewconvertsthemodeldatatoarepresentationthatisconsumablebytheclient.
MVCisawell-knownandwidelyuseddesignpattern,anditisdirectlysupportedbySpringinsucha
waythatwhenyoucreateawebapplication,youprogramthecontrollerbuiltintotheframework,usingannotations;thus,youessentiallyconfigureit.Youcanprogramtheview,butitismorelikelythatyouwilluseonethatisbuiltintotheframework.YouwillwanttosenddatatotheclientinXML,JSON,orHTML.Ifyouareveryexotic,youmaywanttosendYAML,butgenerally,thatisit.Youdonotwanttoimplementanewformatthatneedstobeprogrammedontheserverand,sincethisisnew,alsoontheclient.
Wecreatethemodel,andthistime,wealsoprogramit.Afterall,thatisthebusinesslogic.Frameworkscandomanythingsforus,mainlythethingsthatarethesameformostoftheapplicationsbutforthebusinesslogic.Businesslogicisthecodethatdistinguishesourcodefromotherprograms.Thatiswhatwehavetoprogram.
Ontheotherhand,thatiswhatweliketodo.Focusonthebusinesscodeandavoidallboilerplateprovidedbytheframework.
NowthatweknowwhatJSON,REST,andthegeneralModelViewControllerdesignpatternare,let'slookathowthesearemanagedbySpringandhowwecanputthesetechnologiesintoaction.
Springframework
TheSpringframeworkisahugeonewithseveralmodules.Thefirstversionoftheframeworkwasreleasedin2003,andsincethen,therehavebeenfourmajorreleasesdeliveringnewandenhancedfeatures.Currently,Springisthedefactoenterpriseframeworkused,perhapsmorewidelythanthestandardEJB3.0
Springsupportsdependencyinjection,Aspect-OrientedProgramming(AOP),persistenceforSQLandNoSQLdatabasesinaconventionalandObjectRelationalMappingway,transactionalsupport,messaging,webprogramming,andmanyotherfeatures.YoucanconfigureitusingXMLconfigurationfiles,annotations,orJavaclasses.
ArchitectureofSpringSpringisnotmonolithic.Youcanuseapartofit,oronlysomeofthefeatures.YoucanincludesomeofthemodulesofSpringthatyouneedandleaveoutothers.Somemodulesdependonsomeothers,butGradle,Maven,orsomeotherbuildtoolhandlesthat.
ThefollowingimageshowsyouthemodulesoftheSpringframeworkforversion4:
Springisconstantlydevelopingsinceitsfirstrelease,anditisstillconsideredasamodernframework.Thecoreoftheframeworkisadependencyinjectioncontainersimilartotheonewesawinthepreviouschapter.Astheframeworkdeveloped,italsosupportedAOPandmanyotherenterprisefunctionalities,suchasmessageorientedpatternsandwebprogrammingwithanimplementationofModelViewController,supportingnotonlyservletsbutalsoportletsandWebSockets.SinceSpringtargetstheenterpriseapplicationarena,italsosupportsdatabasehandlinginmanydifferentways.ItsupportsJDBCusingtemplates,ObjectRelationalMapping(ORM),andtransactionmanagement.
Inthesampleprogram,weuseafairlyrecentmodule:Springboot.Thismodulemakesitextremelyeasytostartwritingandrunningapplications,assumingalotofconfigurationsthatareusuallythesameformanyprograms.ItcontainsanembeddedservletcontainerthatitconfiguresfordefaultsettingsandconfiguresSpringwhereveritispossible,sowecanfocusontheprogrammingaspectratherthanontheSpringconfiguration.
SpringcoreThecentralelementofthecoremoduleisthecontext.WhenaSpringapplicationstarts,thecontainerneedsacontextinwhichthecontainercancreatedifferentbeans.Thisisverygeneralandtrueforanydependencyinjectioncontainer.Ifweprogrammaticallycreatetwodifferentcontexts,theymayliveindependentofeachotherinthesameJVM.Ifthereisabeandeclaredasasingletonsothatthereshouldbeonlyonesingleinstanceofit,thenthecontainerwillcreateasingleinstanceofitforacontextwhenweneedoneinstance.Theobjectsrepresentingthecontexthaveareferencetotheobjectthatwehavealreadycreated.Iftherearemultiplecontexts,however,theywillnotknowthatthereisanothercontextintheJVMthatalreadyhasaninstance,andthecontainerwillcreateanewinstanceofthesingletonbeanfortheothercontext.
Usually,wedonotusemorethanonecontextinaprogram,buttherearenumerousexamplesoftherebeingmanycontextsinasingleJVM.Whendifferentservletsruninthesameservletcontainer,theyruninthesameJVMseparatedbytheclassloaderandtheymayeachuseSpring.Inthiscase,thecontextwillbelongtotheservletandeachwillhaveanewcontext.
Inthepreviouschapter,weusedGuice.TheSpringcontextissimilartotheGuiceinjector.Inthepreviouschapter,IwascheatingabitbecauseIwasprogrammingGuicetocreateanewinjectorforeachrequest.Thisisfarfromoptimal,andGuiceprovidesaninjectorimplementationthatcanhandleservletenvironments.ThereasonforcheatingwasthatIwantedtofocusmoreontheDIarchitectureessentials,andIdidnotwanttocomplicatethecodebyintroducingacomplex(well,morecomplex)implementationoftheinjector.
IntheSpringcontextbehavior,whatitdoes,isdefinedbytheinterfaceApplicationContext.Therearetwoextensionsofthisinterfaceandmanyimplementations.ConfigurableApplicationContextextendsApplicationContext,definingsetters,andConfigurableWebApplicationContextdefinesmethodsneededinthewebenvironment.Whenweprogramwebapplications,weusuallydonotneedtointerferedirectlywiththecontext.Theframeworkconfigurestheservletcontainerprogrammatically,anditcontainsservletsthatcreatethecontextandinvokeourmethods.Thisisallboilerplatecodecreatedforus.
Thecontextkeepstrackofthebeanscreated,butitdoesnotcreatethem.Tocreatebeans,weneedbeanfactories(atleastone).ThetopmostinterfaceofbeanfactoriesinSpringisBeanFactory.Thedifferencebetweenanobjectandabeanisthatabeanfactorycreatesthebean,itisregisteredinthecontext,andithasaStringname.Thisway,theprogramcanrefertothebeanbythename.
DifferentbeanscanbeconfiguredinseveraldifferentwaysinSpring.TheoldestapproachistocreateanXMLfilethatdescribesthedifferentbeans,specifyingthenames,theclassthathastobeinstantiatedtocreatethebean,andfieldsincasethebeanneedsotherbeanstobeinjectedforitscreation.
Themotivationbehindthisapproachisthatthisway,thebeanwiringandconfigurationcanbetotallyindependentoftheapplicationcode.Itbecomesaconfigurationfilethatcanbemaintainedseparately.Ifwehavealargeapplicationthatmayworkinseveraldifferentenvironments,theaccesstoinventorydatamaybeavailableinmultitudeways.Inoneenvironment,theinventoryisavailablebycallingSOAP
services.Inanotherenvironment,thedataisaccessibleinanSQLdatabase.Inthethirdenvironment,itcanbeavailableinsomeNoSQLstore.Eachoftheseaccessesisimplementedasaseparateclass,implementingacommoninventoryaccessinterface.Theapplicationcodedependsonlyontheinterface,anditisthecontainerthathastoprovideoneortheotherimplementation.
WhentheconfigurationofthebeanwiringisinXML,thenonlythisXMLfileistobeedited,andthecodecanbestartedwiththeimplementationoftheinterfacethatissuitableforthatenvironment.
Thenextpossibilityistoconfigurethebeansusingannotations.Manytimes,weusebeansandSpringnotbecausetherearemanyimplementationsforabeanfunctionality,butbecausewewanttoseparatethecreationoftheobjectinstancefromthefunctionality.Thisisagoodstyle:separationoftheconcernseveniftheimplementationissinglewithoutalternatives.However,inthiscase,creatingtheXMLconfigurationisredundant.Ifthereisaninterfaceandasingleimplementationofitinourcode,thenwhyshouldIspecifyinanXMLthatbycreatinganobjectwithaclassthatimplementsthatinterface,Ishouldusetheclassthatimplementsthatinterface?Quiteobvious,isn'tit?Wedonotlikeprogrammingthingsthatcanbeautomated.
Tosignalthataclasscanbeusedasabean,andtopossiblyprovideaname,wecanusethe@Componentannotation.Wedonotneedtoprovideanameasanargument.Inthatcase,thenamewillbeanemptystring,butwhyhaveanameifwedonotrefertoit?Springscansalltheclassesthatareontheclasspathandrecognizestheclassesannotated,anditknowsthattheyarethecandidatestobeusedforbeancreation.Whenacomponentneedsanotherbeantobeinjected,thefieldcanbeannotatedwith@[email protected]@AutowiredannotationisaSpringannotationandexistedbeforethe@Injectannotationwasstandardized.IfyouintendtouseyourcodeoutsideoftheSpringcontainer,itisrecommendedtousestandardannotations.Functionally,theyareequivalent.
Inourcode,whenSpringcreatesaninstanceoftheProductInformationControllercomponent,itseesthatitneedsaninstanceofProductLookup.Thisisaninterface,andthus,Springstartstolookforsomeclassthatimplementsthisinterface,createsaninstanceofit,possiblyfirstcreatingotherbeans,andtheninjectsit,settingthefield.Youcandecidetoannotatethesetterofthefieldinsteadofthefielditself.Insuchacase,Springwillinvokethesetterevenifthesetterisprivate.Youcaninjectdependenciesthroughconstructorarguments.Themajordifferencebetweenthesetter,fieldinjection,andconstructorinjectionisthatyoucannotcreateabeanwithoutdependencyincaseyouuseconstructorinjection.Whenthebeanisinstantiated,itshouldandwillhaveallotherbeansinjectedsothatitdependsonusingtheconstructorinjection.Atthesametime,thedependenciesthatneedtobeinjectedthroughthesetterinjection,ordirectlyintoafield,couldbeinstantiatedlaterbythecontainersometimebetweeninstantiatingtheclassandreadyingthebean.
Thisslightdifferencemaynotseeminterestingorimportantuntilyourconstructorcodemaybecomemorecomplexthanthesimpledependencysettingsoruntilthedependenciesbecomecomplex.Inthecaseofacomplexconstructor,thecodeshouldpayattentiontothefactthattheobjectisnotfullycreated.Thisisgenerallytrueforanyconstructorcode,butinthecaseofbeanscreatedbyadependencyinjectioncontainer,itisevenmoreimportant.Thus,itmaybeadvisabletouseconstructorinjection.Inthatcase,thedependenciesarethere;ifaprogrammermakesamistake,forgettingthattheobjectisnotfullyinitialized,andusesitintheconstructororamethodthatiscalledfromaconstructor,thedependencyisthere.Also,itiscleanandwell-structuredtousetheconstructortoinitializethedependenciesandhave
thosefieldsdeclaredfinal.
Ontheotherhand,constructorinjectionhasitsdownsides.
Ifdifferentobjectsdependoneachotherandthereisaringinthedependencygraph,thenSpringwillfaceahardtimeifyouuseconstructordependencies.WhenclassAneedsclassBandtheotherwayround,asthesimplestcircle,thenneitherAnorBcanbecreatedwithouttheotherifthedependencyinjectionisconstructordependency.Insituationslikethis,theconstructorinjectioncannotbeused,andthecircleshouldbebrokenatleastasasingledependency.Insituationslikethis,setterinjectionisinevitable.
Setterinjectionmayalsobebetterwhenthereareoptionaldependencies.Manytimes,someclassmaynotneedallitsdependenciesatthesametime.SomeclassmayuseadatabaseconnectionoraNoSQLdatabasehandlebutnotbothatthesametime.AlthoughitmayalsobeacodesmellandprobablyasignofpoorOOdesign,itmayhappen.ItmaybeadeliberatedecisiontodothatbecausethepureOOdesignwouldresultintoodeepobjecthierarchiesandtoomanyclasses,beyondthemaintainablelimit.Ifsuchisthesituation,theoptionaldependenciesmaybebetterhandledusingsetterinjection.Someareconfiguredandset;someareleftwithdefaultvalues,usuallynull.
Lastbutnotleast,wecanconfigurethecontainerusingJavaclassesincasetheannotationsarenotenough.Forexample,therearemultipleimplementationsoftheProductLookupinterface,asitis,inourcodebase.(Don'tworryifyoudidnotrecognizethat;Ihavenottoldyouaboutthatyet.)ThereisaResourceBasedProductLookupclassthatreadspropertiesfilesfromthepackageandismainlytotesttheapplication,andthereisRestClientProductLookup,whichisaproduction-likeimplementationoftheinterface.IfIhavenootherconfigurationthanannotatingthelookupfieldwith@Autowired,Springwillnotknowwhichimplementationtouseandwillrewardtheuseduringstartupwiththefollowingerrormessage:
ErrorstartingApplicationContext.Todisplaytheauto-configurationreportre-runyourapplicationwith'debug'enabled.
2016-11-0307:25:01.217ERROR51907---[restartedMain]o.s.b.d.LoggingFailureAnalysisReporter:
***************************
APPLICATIONFAILEDTOSTART
***************************
Description:
Parameter0ofconstructorinpackt.java9.by.example.mybusiness.productinformation.ProductInformationControllerrequiredasinglebean,but2werefound:
-resourceBasedProductLookup:definedinfile[/.../sources/ch07/productinformation/build/classes/main/packt/java9/by/example/mybusiness/productinformation/lookup/ResourceBasedProductLookup.class]
-restClientProductLookup:definedinfile[/.../sources/ch07/productinformation/build/classes/main/packt/java9/by/example/mybusiness/productinformation/lookup/RestClientProductLookup.class]
Action:
Considermarkingoneofthebeansas@Primary,updatingtheconsumertoacceptmultiplebeans,orusing@Qualifiertoidentifythebeanthatshouldbeconsumed
Thisisafairlyself-explanatoryerrormessage;ittellsalot.NowisthetimewhenwecanconfigurethebeaninXML,butatthesametime,wecanalsoconfigureitusingJava.
Manydevelopersdonotgetthepointthefirsttime.Ididnotgetiteither.ThewholeXMLconfigurationwastoseparatetheconfigurationfromthecode.Itwastocreatethepossibilitythatasystemadministratorchangestheconfigurationandisfreetoselectoneortheotherimplementationofsomeinterface,wiringtheapplicationtogether.NowSpringtellsmethatitisbettertoreturntotheprogrammaticway?
Atthesametime,IcouldhearconcernsformanyyearsthatXMLisnotreallyanybetterthanJavacode.XMLwritingisessentiallyprogramming,exceptthatthetoolingandIDEsupportisnotasgoodforXML
asitisforJavacode(thelatterdevelopedalotinrecentyears,althoughforSpringXMLconfiguration).
TounderstandtheconceptofreturningtoJavacodefromXML,wehavetogetbacktothepurereasonandaimoftheXMLwayofconfiguration.ThemainadvantageofXMLSpringconfigurationisnotthattheformatisnotprogrammaticbutratherthattheconfigurationcodeisseparatedfromapplicationcode.IfwewritetheconfigurationinJavaandkeepthoseconfigurationclassestothebareminimum,andtheystayastheyshould,thentheseparationofapplicationversusconfigurationcodestillstands.ItisonlytheformatoftheconfigurationthatwechangefromXMLtoJava.Theadvantagesarenumerous.OneisthatthenamesoftheclassesarerecognizedbytheIDEasweeditandwecanhaveautocompleteinJava(notethatthisalsoworksusingXMLinsomeoftheIDEsutilizingsomeoftheextensionsofplugins).InthecaseofJava,IDEsupportisubiquitous.JavaismorereadablethanXML.Well,thisisamatteroftaste,butmostofuslikeJavamorethanXML.
SystemadministratorscanalsoeditJavacode.WhentheyedittheXMLconfiguration,theyusuallyhavetoextractitfromaJARorWARfile,editit,andthenpackagethearchiveagain.InthecaseofJavaediting,theyalsohavetoissueagradlewarcommandorsomethingsimilar.ThisshouldnotbeashowstopperforasystemmanagerwhorunsJavaapplicationsonaserver.Andagain,itisnotJavaprogramming.ItisonlyeditingsomeJavacodefilesandreplacingsomeclassnameliteralsandstringconstants.
Wefollowthisapproachinoursampleapplicationcode.Wehavetwoconfigurationfilesintheapplication:oneforlocaldeploymentandtestingandanotherforproduction.The@Profileannotationspecifieswhichprofiletheconfigurationshoulduse.Theprofile,whenthecodeisexecuted,canbespecifiedonthecommandlineasasystemproperty,asfollows:
$gradle-Dspring.profiles.active=localbootRun
Theconfigurationclassisannotatedwith@Configuration.Themethodsthatarebeanfactoriesareannotatedwith@Bean:
packagepackt.java9.by.example.mybusiness.productinformation;
import...
@Configuration
@Profile("local")
publicclassSpringConfigurationLocal{
@Bean
@Primary
publicProductLookupproductLookup(){
returnnewResourceBasedProductLookup();
}
@Bean
publicProductInformationServiceUrlBuilderurlBuilder(){
returnnull;
}
}
ThebeanfactorysimplyreturnsanewinstanceoftheResourceBasedProductLookupclassthatimplementstheProductLookupinterface.Thisimplementationcanbeusedtoruntheapplicationforlocaltestingwhentherearenoexternalservicestorelyon.ThisimplementationreadstheproductdatafromlocalresourcefilespackagedintotheJARapplication.
Theproductionversionoftheconfigurationisnotmuchdifferent,butasitmaybeexpected,thereareafewmorethingstoconfigure:
@Configuration
@Profile("production")
publicclassSpringConfiguration{
@Bean
@Primary
publicProductLookupproductLookup(){
returnnewRestClientProductLookup(urlBuilder());
}
@Bean
publicProductInformationServiceUrlBuilderurlBuilder(){
returnnewProductInformationServiceUrlBuilder(
"http://localhost");
}
}
ThisversionoftheProductLookupserviceclassusesanexternalRESTservicetoretrievethedatathatitwillpresenttotheclients.Todoso,itneedstheURLsoftheseservices.SuchURLsshouldusuallybeconfigured.Inourexample,weimplementasolutionwheretheseURLscanbecomputedonthefly.Itriedtomakeupasituationwhereitmaybeneededinreallife,butallreasoningwascontortedandIgaveup.Therealreasonisthat,thisway,wecanseecodethatcontainsabeanthatneedsanotherbeantobeinjected.Fornow,notethattheProductInformationServiceUrlBuilderinstancebeanisdefinedinthesamewayastheProductLookupbean,andwhenithastobeinjectedintotheconstructoroftheProductLookupbean,itsdefiningbeanmethodisusedandnotthefollowingexpressiondirectly:
newProductInformationServiceUrlBuilder("http://localhost");
Thelattermaywork,butnotinallsituationsandweshouldnotuseit.Forthereasons,wewillreturnwhenwediscussAOPwithSpringinasubsequentsection.
Alsonotethatthereisnoneedtodefineaninterfacetodefineabean.Thetypethatthebeanmethodreturnscanalsobeaclass.Thecontextwillusethemethodthatfitstheneededtype,andiftherearemorethanonesuitabletypesandtheconfigurationisnotpreciseenough,aswesaw,thecontainerwillloganerrorandwillnotwork.
Intheconfigurationthatservesthelocalprofile,wecreateanullvalueforProductInformationServiceBuilder.Thisisbecausewedonotneeditwhenweuselocaltesting.Also,ifanymethodfromthisclassisinvoked,itwillbeanerror.Errorsshouldbedetectedassoonaspossible;thus,anullvalueisagoodchoice.
TheProductInformationServiceUrlBuilderclassisverysimple:
packagepackt.java9.by.example.mybusiness.productinformation;
publicclassProductInformationServiceUrlBuilder{
privatefinalStringbaseUrl;
publicProductInformationServiceUrlBuilder(StringbaseUrl){
this.baseUrl=baseUrl;
}
publicStringurl(Stringservice,Stringparameter){
finalStringserviceUrl;
switch(service){
case"pi":
serviceUrl=
baseUrl+":8081/product/{id}";
break;
case"query":
serviceUrl=
baseUrl+":8081/query/{query}";
break;
case"inventory":
serviceUrl=
baseUrl+":8083/inventory/{id}";
break;
default:
serviceUrl=null;
break;
}
returnserviceUrl;
}
}
Thisbeanalsoneedsaconstructorparameter,andweusedastringconstantintheconfiguration.Thisclearlyshowsthatitispossibletouseasimpleobjecttoinitializesomeofthedependencies(whatwouldstopus,itispureJavaafterall),butitmayhindertheworkingofsomeSpringfeatures.
ServiceclassesWehavetwoserviceclasses.Theseclassesservethecontrollerswithdataandimplementthebusinesslogic,nomatterhowsimpletheyare.OneoftheserviceclassimplementationscallsREST-basedservices,whiletheotheronereadsdatafrompropertiesfiles.Thelattercanbeusedtotesttheapplicationoffline.TheonethatcallsRESTservicesisusedintheproductionenvironment.BothofthemimplementtheProductLookupinterface:
packagepackt.java9.by.example.mybusiness.productinformation;
importjava.util.List;
publicinterfaceProductLookup{
ProductInformationbyId(Stringid);
List<String>byQuery(Stringquery);
}
ResourceBasedProductLookupstoresthewholedatabaseinamapcalledproducts.Itisfilledfromthepropertiesfileswhenoneoftheservicemethodsisinvoked.TheprivatemethodloadProductsisinvokedfromeachoftheservicemethodswhentheystart,butitloadsthedataonlyifitisnotloadedyet:
packagepackt.java9.by.example.mybusiness.productinformation.lookup;
import...
@Service
publicclassResourceBasedProductLookupimplementsProductLookup{
privatestaticLoggerlog=LoggerFactory.getLogger(ResourceBasedProductLookup.class);
Theclassisannotatedusing@Service.Thisannotationispracticallyequivalenttothe@Componentannotation.Thisisonlyanalternativenametothesameannotation.Springalsohandlesthe@Componentannotationsuchthatifanannotationinterfaceisannotatedusingthe@Componentannotation,theannotationcanalsobeusedtosignalthataclassisaSpringcomponent.Youcanwriteyourownannotationinterfacesifyouwanttosignalforbetterreadabilitythataclassisnotasimplecomponentbutsomeotherspecialtype.
Forexample,startupyourIDEandnavigatetothesourcecodeoftheorg.springframework.stereotype.Serviceinterface:
privateProductInformation
fromProperties(Propertiesproperties){
finalProductInformationpi=newProductInformation();
pi.setTitle(properties.getProperty("title"));
pi.setDescription(properties.getProperty("description"));
pi.setWeight(
Double.parseDouble(properties.getProperty("weight")));
pi.getSize()[0]=
Double.parseDouble(properties.getProperty("width"));
pi.getSize()[1]=
Double.parseDouble(properties.getProperty("height"));
pi.getSize()[2]=
Double.parseDouble(properties.getProperty("depth"));
returnpi;
}
ThefromPropertiesmethodcreatesaninstanceofProductInformationandfillsitfromtheparametersgiveninthePropertiesobject.ThePropertiesclassisanoldandwidelyusedtype.Althoughtherearemoremodernformatsandclasses,thisisstillwidelyusedanditislikelythatyouwillmeetthisclass.Thisistheveryreasonweuseithere.
ProductInformationisasimpleDataTransferObject(DTO)thatcontainsnologic,onlyfields,setters,andgetters.Italsocontainsaconstant,emptyProductInformation,holdingareferencetoaninstanceoftheclasswithemptyvalues.
APropertiesobjectissimilartoaMapobject.ItcontainsStringvaluesassignedtoStringkeys.Therearemethods,aswewillseeinourexamples,thathelptheprogrammertoloadaPropertiesobjectfromaso-calledpropertiesfile.Suchafileusuallyhasthe.propertiesextension,anditcontainskeyvaluepairsinthefollowingformat:
key=value
Forexample,the123.propertiesfilecontainsthefollowing:
id=123
title=BookJava9byExample
description=anewbooktolearnJava9
weight=300
width=20
height=2
depth=18
Thepropertiesfilesareusedtostoresimpleconfigurationvaluesandarealmostexclusivelyusedtocontainlanguage-specificconstants.ThisisaverycontortedusebecausepropertiesfilesareISOLatin-1encodedfiles,andincaseyouneedtousesomespecialUTF-8characters,youhavetotypethemusingthe\uXXXXformatorusingthenative2asciiconverterprogram.YoucannotsavethemsimplyasUTF-8.Nevertheless,thisisthefileformatusedforlanguage-specificstringsusedforprograminternationalization(alsoabbreviatedasi18nbecausethereare18charactersbetweenthestartingiandthelastn).TogetthePropertiesobject,wehavetoreadthefilesintheprojectandgetthempackagedintoaJARfile.TheSpringclass,PathMatchingResourcePatternResolver,helpsusindoingso.
Gosh,yes,Iknow!WehavetogetusedtotheselongnameswhenweuseSpring.Anyway,suchlonganddescriptivenamesarewidelyusedinanenterpriseenvironmentandtheyareneededtoexplainthefunctionalityoftheclasses.
Wedeclareamapthatwillcontainalltheproductsduringthetesting:
finalprivateMap<String,ProductInformation>
products=newHashMap<>();
ThekeyistheproductID,whichisastringinourexample.ThevaluesaretheProductInformationobjectsthatwefillupusingthefromPropertiesmethod:
privatebooleanproductsAreNotLoaded=true;
Thenextfieldsignalsthattheproductsarenotloaded:
NoviceprogrammersusuallyusetheoppositevaluewiththenameproductsAreLoadedandsettofalsebydefault.Inthatcase,theonlyplacewherewewillreadavaluewillnegatethevalue,orthemainbranchoftheifcommandbecomesthedonothingpart.Neitherisabestpractice.
privatevoidloadProducts(){
if(productsAreNotLoaded){
try{
Resource[]resources=
newPathMatchingResourcePatternResolver()
.getResources(
"classpath:products/*.properties");
for(Resourceresource:resources){
loadResource(resource);
}
}
productsAreNotLoaded=false;
}catch(IOExceptionex){
log.error("Testresourcescannotberead",ex);
}
}
}
ThegetResourcesmethodreturnsalltheresources(files)thatareontheclasspathundertheproductsdirectoryandthathavea.propertiesextension:
privatevoidloadResource(Resourceresource)throwsIOException{
finalintdotPos=resource.getFilename().lastIndexOf('.');
finalStringid=resource.getFilename().substring(0,dotPos);
Propertiesproperties=newProperties();
properties.load(resource.getInputStream());
finalProductInformationpi=fromProperties(properties);
pi.setId(id);
products.put(id,pi);
}
TheproductIDisgivenbythenameofthefile.Thisiscalculatedusingsimplestringmanipulation,cuttingofftheextension.TheResourcecanalsoprovideaninputstreamthatthePropertiesclass'sloadmethodcanusetoloadallthepropertiesatoncefromthefile.Finally,wesavethenewProductInformationobjectinthemap.
WealsohaveaspecialnoProductlistthatisempty.Thisisreturnedifthereisnoproductforthequerywhenwewanttosearchforproducts:
privatestaticfinalList<String>noProducts=
newLinkedList<>();
TheproductlookupservicejusttakesaproductfromtheMapandreturnsit,orifitdoesnotexist,itreturnsanemptyproduct:
@Override
publicProductInformationbyId(Stringid){
loadProducts();
if(products.containsKey(id)){
returnproducts.get(id);
}else{
returnProductInformation.emptyProductInformation;
}
}
Thequeryisabitmorecomplex.Itimplementssearchingforaproductbytitle.Real-lifeimplementationsmayimplementamorecomplexlogic,butthisversionisforlocaltestingonly;thus,thesearchbytitleisenough,perhapsevenmorecomplexthanwouldbereallynecessary:
@Override
publicList<String>byQuery(Stringquery){
loadProducts();
List<String>pis=newLinkedList<>();
StringTokenizerst=newStringTokenizer(query,"&=");
while(st.hasMoreTokens()){
finalStringkey=st.nextToken();
if(st.hasMoreTokens()){
finalStringvalue=st.nextToken();
log.debug("processing{}={}query",key,value);
if(!"title".equals(key)){
returnnoProducts;
}
for(Stringid:products.keySet()){
ProductInformationpi=products.get(id);
if(pi.getTitle().startsWith(value)){
pis.add(id);
}
}
}
}
returnpis;
}
Theserviceclassthatimplementstheproductionfunctionalityismuchsimpler.Strange,butmanytimesthetestcodeismorecomplexthantheproductioncode:
packagepackt.java9.by.example.mybusiness.productinformation.lookup;
import...
@Component
publicclassRestClientProductLookupimplementsProductLookup{
privatestaticLoggerlog=LoggerFactory.getLogger(RestClientProductLookup.class);
finalprivateProductInformationServiceUrlBuilderpiSUBuilder;
publicRestClientProductLookup(
ProductInformationServiceUrlBuilderpiSUBuilder){
this.piSUBuilder=piSUBuilder;
}
TheconstructorisusedtoinjecttheURLbuilderbeanandthisisalltheauxiliarycodetheclasshas.Therestarethetwoservicemethods:
@Override
publicProductInformationbyId(Stringid){
Map<String,String>uriParameters=newHashMap<>();
uriParameters.put("id",id);
RestTemplaterest=newRestTemplate();
InventoryItemAmountamount=rest.getForObject(
piSUBuilder.url("inventory"),
InventoryItemAmount.class,
uriParameters);
if(amount.getAmount()>0){
returnrest.getForObject(piSUBuilder.url("pi"),
ProductInformation.class,
uriParameters);
}else{
returnProductInformation.emptyProductInformation;
}
}
ThebyIdmethodfirstcallstheinventoryservicetoseeifthereareanyproductsontheinventory.ThisRESTservicereturnsaJSONthathastheformat,{amount:nnn};thus,weneedaclass(sosimplethatwedonotlisthere)thathastheintamountfield,thesetter,andthegetter.
TheSpringRestTemplateprovidesaneasywaytoaccessaRESTservice.AllitneedsistheURLtemplate,atypethatisusedtoconverttheresult,andaMapobjectwiththeparameters.TheURLtemplatestringmaycontainparametersinthesamewayastherequestmappingintheSpringcontrollers,thenameoftheparameterbeingbetweenthe{and}characters.Thetemplateclassprovidessimplemethodstoaccess
RESTservices.Itautomaticallydoesmarshaling,sendingparameters,andun-marshaling,receivingtheresponse.InthecaseofaGETrequest,themarshalingisnotneeded.ThedataisintherequestURL,andthe{xxx}placeholdersarereplacedwiththevaluesfromthemapsuppliedasathirdargument.Theun-marshalingisreadilyavailableformostoftheformats.Inourapplication,theRESTservicesendsJSONdata,anditisindicatedintheresponseContent-TypeHTTPheader.RestTemplateconvertstheJSONtothetypeprovidedasargument.IfevertheserverdecidestosendtheresponseinXML,anditwillalsobeindicatedintheHTTPheader,RestTemplatewillhandlethesituationautomatically.Asamatteroffact,lookingatthecode,wecannottellhowtheresponseisencoded.Thisisalsonicebecauseitmakestheclientflexibleandatthesametime,wedonotneedtodealwithsuchtechnicaldetails.Wecanfocusonthebusinesslogic.
Atthesametime,theclassalsoprovidesconfigurationparametersinthecaseofmarshalingorsomeotherfunctionalitysothatitautomaticallyneedsthat.Youcan,forexample,providemarshalingmethods,thoughIrecommendthatyouusewhateverisavailablebydefault.Inmostcases,whenadeveloperthinksthatthereisaneedforaspecialversionofanyofthesefunctions,theoriginaldesignoftheircodeisflawed.
Thebusinesslogicisverysimple.Wefirstasktheinventoryifthereisanyproductinstock.Ifthereis(morethanzero),thenwequerytheproductinformationserviceandreturnthedetails.Ifthereisnone,thenwereturnanemptyrecord.
Theotherserviceisevensimpler.Itsimplycallstheunderpinningserviceandreturnstheresult:
@Override
publicList<String>byQuery(Stringquery){
Map<String,String>uriParameters=newHashMap<>();
uriParameters.put("query",query);
RestTemplaterest=newRestTemplate();
returnrest.getForObject(
piSUBuilder.url("query"),
List.class,
uriParameters);
}
}
CompilingandrunningtheapplicationWeusegradletocompileandruntheapplication.Sincetheapplicationdoesnothaveanyspecificconfigurationthatwouldnotappearinmostsimilarapplications,itiswisetousetheSpringboot.TheSpringbootmakesitextremelysimpletocreateandrunawebapplication.WeneedaJavastandardpublicstaticvoidmainmethodthatstartsuptheapplicationviaSpring:packagepackt.java9.by.example.mybusiness.productinformation;import...@SpringBootApplication(scanBasePackageClasses=packt.java9.by.example.mybusiness.SpringScanBase.class)publicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}}
ThemethoddoesnothingbutstarttheStringApplicationclass'srunmethod.Itpassestheoriginalargumentsandalsotheclassthattheapplicationisin.Springusesthisclasstoreadtheannotation.The@SpringBootApplicationannotationsignalsthatthisclassisaSpringbootapplicationandprovidesargumentstoconfigurethepackagesthatcontaintheapplication.Todoso,youcanprovidethenameofthepackagethatcontainstheclasses,butyoucanalsoprovideaclassinthebasepackagethatcontainsalltheclassesthatSpringhastobeawareof.Youmaynotbeabletousetheclassversionoftheannotationparameterbecausetherootpackagemaynotcontainanyclass,onlysub-packages.Atthesametime,providingthenameoftherootpackageasString,willnotrevealanytypoormisalignmentduringcompiletime.SomeIDEmayrecognizethattheparameterissupposedtobeapackagename,oritmayscanthestringsoftheprogramforpackagenameswhenyourefactororrenameapackageandgiveyousupportforthat,butthisismoreheuristicsonly.Itisacommonpracticetocreateaplaceholderclassthatdoesnothingintherootpackageincasethereisnoclassthere.ThisclasscanbeusedtospecifyscanBasePackageClassesasanannotationparameterinsteadofscanBasePackagesthatneedsString.Inourexample,wehaveanemptyinterface,SpringScanBase,asaplaceholder.
Springscansalltheclassesthatareontheclasspath,recognizesthecomponentsandfieldannotationsthatitcaninterpret,andusesthisknowledgetocreatebeanswithoutconfigurationwhenneeded.
Notethattheabstractclass,ClassLoader,includedintheJDKdoesnotprovideanyclassscanningmethod.SinceJavaenvironmentsandframeworkscanimplementtheirownClassLoaders,itispossible(butveryunlikely)thatsomeimplementationdoesnotprovidethescanningfunctionalityprovidedbytheURLClassLoader.URLClassLoaderisanon-abstractimplementationoftheclassloadingfunctionalityandisapartoftheJDKjustaswellasClassLoader.Wewilldiscusstheintricaciesoftheclassloadingmechanisminthesubsequentchapters.
Thegradlebuildfilecontainstheusualthings.Itspecifiestherepositories,thepluginsforJava,theIDEs,
andalsoforSpringboot.ItalsospecifiesthenameoftheJARfilethatitgeneratesduringbuild.Themostimportantpartisthedependencylist:buildscript{repositories{mavenCentral()}dependencies{classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.1.RELEASE")}}
applyplugin:'java'applyplugin:'eclipse'applyplugin:'idea'applyplugin:'spring-boot'
jar{baseName='packt-ch07-microservice'version='1.0.0'}
repositories{mavenCentral()}
bootRun{systemPropertiesSystem.properties}
sourceCompatibility=1.9targetCompatibility=1.9
dependencies{compile("org.springframework.boot:spring-boot-starter-web")compile("org.springframework.boot:spring-boot-devtools")compile("org.springframework:spring-aop")compile("org.springframework:spring-aspects")testCompile("org.springframework.boot:spring-boot-starter-test")}
WedependonSpringbootpackages,sometestpackages,AOPsupport(whichwewilllookatsoon),andalsoonSpringbootdevtools.
Springbootdevtoolsmakeitpossibletorestartawebapplicationwheneveritisrecompiled,withoutrestartingthebuilt-inTomcatserver.Suppose,westarttheapplicationusingthefollowingcommandline:gradle-Dspring.profiles.active=productionbootRun
TheGradlestartsuptheapplicationandwheneveritseesthattheclassesitrunsaremodified,itreloadsthem,andwecantestthemodifiedapplicationwithinafewseconds.
The-Dspring.profiles.active=productionargumentspecifiesthattheproductionprofileshouldbeactive.Tobeabletousethiscommandlineparameter,wewillalsoneedthebootRun{}configurationclosureinthebuildfile.
Testingtheapplication
Theapplicationshouldhaveunittestsforeachandeveryclassithasexcept,perhaps,fortheDTOclassesthatcontainnofunctionality.ThesettersandgettersarecreatedbytheIDEandarenottypedinbytheprogrammer,soitisunlikelythattherewillbeanyerrorsinthose.Ifthereissomeerrorrelatedtothoseclasses,itismorelikelythatitissomeintegrationproblemthatcannotbediscoveredusingunittests.Sincewediscussedunittestsinthepreviouschaptersindetail,wewillfocusmoreonintegrationtestsandapplicationtestshere.
IntegrationtestIntegrationtestsareverysimilartounittests,andmanytimes,noviceprogrammersclaimtheydounittestingwhentheyactuallydointegrationtesting.
Integrationtestsdrivethecodebutdonottesttheindividualclasses(units)inisolation,mockingeverythingthattheclassmayuse.Rather,theytestthefunctionalityofmostoftheclassesthatareneededtoperformatest.Thisway,theintegrationtestdoestestthattheclassesareabletoworktogetherandnotonlysatisfytheirownspecificationsbutalsoensurethatthesespecificationsworktogether.
Inintegrationtest,theexternalworld(likeexternalservices)andaccesstodatabasearemockedonly.Thatisbecausetheintegrationtestsaresupposedtorunonintegrationservers,inthesameenvironmentwheretheunittestsareexecuted,andtheretheseexternalinterfacesmaynotbeavailable.Manytimes,databasesaremockedusingin-memorySQL,andexternalservicesaremockedusingsomemockclasses.
Springprovidesaniceenvironmenttoexecutesuchintegrationtests.Inourproject,wehaveasampleintegrationtest:
packagepackt.java9.by.example.mybusiness.productinformation;
import...
@RunWith(SpringRunner.class)
@SpringBootTest(classes=Application.class)
@AutoConfigureMockMvc
@ActiveProfiles("local")
publicclassProductInformationControllerTest{
@Autowired
privateMockMvcmockMvc;
@Test
publicvoidnoParamGreetingShouldReturnDefaultMessage()
throwsException{
this.mockMvc.perform(get("/pi")).andDo(print())
.andExpect(status().isNotFound());
}
@Test
publicvoidparamGreetingShouldReturnTailoredMessage()
throwsException{
this.mockMvc.perform(get("/pi/123"))
.andDo(print()).andExpect(status().isOk())
.andExpect(jsonPath("$.title")
.value("BookJava9byExample"));
}
}
Thisisfarfrombeingacompleteandfull-fledgedintegrationtest.Therearemanysituationsthatarenottested,buthereitisgoodasanexample.TohaveallthesupportfortheSpringenvironment,wehavetousetheSpringRunnerclass.The@RunWithannotationishandledbytheJUnitframework,allotherannotationsareforSpring.WhentheJUnitframeworkseesthatthereisa@RunWithannotationandarunnerclassspecified,itstartsthatclassinsteadofthestandardrunner.SpringRunnersetsupaSpringcontextforthetestandhandlestheannotations.
@SpringBootTestspecifiestheapplicationsthatweneedtotest.ThishelpsSpringtoreadthatclassandtheannotationonthatclass,identifyingthepackagestobescanned.
@AutoConfigureMockMvctellsSpringtoconfigureamockversionoftheModelViewControllerframework,
whichcanbeexecutedwithoutaservletcontainerandwebprotocol.Usingthat,wecantestourRESTserviceswithoutreallygoingtothenetwork.
@ActiveProfilestellsSpringthattheactiveprofileislocalandthatSpringhastousetheconfigurationthatisdenotedbytheannotation,@Profile("local").Thisisaversionthatusesthe.propertiesfilesinsteadofexternalHTTPservices;thus,thisisappropriateforintegrationtesting.
ThetestperformsGETrequestsinsidethemockingframework,executesthecodeinthecontroller,andteststhereturnedvalueusingthemockingframeworkandfluentAPIinaveryreadableway.
Notethatusingthepropertiesfilesandhavingtheserviceimplementationbasedonpropertiesfileisabitofanoverkill.Icreatedthissothatitispossibletostartuptheapplicationinteractivelywithoutanyrealbackingservice.Considerthefollowingcommand:gradle-Dspring.profiles.active=localbootRun.Ifweissuetheprecedingcommand,thentheserverstartsupusingthislocalimplementation.Ifweonlyaimforintegrationtesting,thenthelocalimplementationoftheserviceclassesshouldbeunderthetestdirectoryandshouldbemuchsimpler,mainlyonlyreturningconstantresponsesforanyexpectedrequestandthrowingerrorsifanynon-expectedrequestcomes.
ApplicationtestConsiderthefollowingcommand:
gradle-Dspring.profiles.active=productionbootRun
IfwestartuptheapplicationissuingtheprecedingcommandandfireupthebrowsertotheURL,http://localhost:8080/pi/123,wewillgetafaterrormessageonthebrowserscreen.Ouch...
ItsaysInternalServerError,status=500orsomethingsimilar.Thatisbecauseourcodewantstoconnecttothebackingservices,butwedonothaveanyyet.Tohavesometotesttheapplicationonthislevel,weshouldcreatethebackingservicesoratleastsomethingthatmocksthem.TheeasiestwayistousethesoapUIprogram.
ThesoapUIisaJavaprogramavailablefromhttps://www.soapui.org/.Thereisanopensourceandfreeversionofit,andthereisacommercialversion.Forourpurposes,thefreeversionisenough.Wecaninstallitinthesimplestclick-forwardwayasithasasetupwizard.Afterthat,wecanstartitupandusethegraphicaluserinterface.
Wecreateanewtestproject,Catalogandinventory,andsetuptwoRESTmockservicesinit:CatalogandInventory,asshowninthefollowingscreenshot:
Wesetup,foreachofthemockservices,requeststobematchedandresponses.Thecontentoftheresponseistextandcanbetypedintothetextfieldontheuserinterface.Itisimportantthatwedonotforgettosetthemediatypeoftheresponsetoapplication/json(thedefaultisXML).
Beforestartingtheservices,wehavetosettheportnumbersbyclickingonthecogwheeltosomethingthatisavailableontheserver.Since8080isusedbytheTomcatserverexecutedbyGradle,and8082isusedbysoapUItolistthemockservicesthatarecurrentlyrunning,Isetthecatalogtolistenon8081andinventoryon8083.YoucanalsoseetheseportnumbersinthelistingoftheProductInformationServiceUrlBuilderclass.
ThesoapUIsavestheprojectinanXMLfile,anditisavailableforyouonGitHubintheprojectdirectory.
Afterstartingthemockservices,theerrormessagedisappearsfromthebrowserscreenwhenwepressrefresh:
WhatweseeisexactlywhatwetypedintosoapUI.
IfnowIchangetheinventorymockservicetoreturn0insteadof100,asintheoriginalversion,whatIgetisthefollowingemptyrecord:
{"id":"","title":"","description":"","size":[0.0,0.0,0.0],"weight":0.0}
Thetestingevenonthislevelcanbeautomated.Now,wewereplayingaroundusingthebrowserandthisissomethingnice.Somehow,IfeelIamproducingsomethingwhenthereisaprogramthatisreallydoingsomething,whenIcanseethatthereissomeresponseinthebrowserwindow.However,afterawhile,thisbecomesboringandtestingmanuallythattheapplicationisstillworkingiscumbersome.Itisespeciallyboringforthosefunctionsthatwerenotchanged.Thefactisthattheydochangemiraculouslymanytimesevenwhenwedonottouchthecodethatinfluencesthem.Wetouchthecodethatdoesinfluencethefunctionexceptthatwearenotawareofit.Poordesign,poorcoding,ormaybewejustforgot,butithappens.Regressiontestisinevitable.
Althoughbrowsertestinguserinterfacescanalsobeautomated,thistime,wearehavingaRESTservicethatwecantestandthatiswhatsoapUIisfor.Wehavealreadyinstalledthetool,wehavealreadystartedit,andwehavesomemockservicesrunninginit.ThenextthingistoaddaNewRESTservicefromURItotheprojectandspecifytheURL,http://localhost:8080/pi/{id},exactlythesamewayaswedidforSpring:
WhenwehaveaRESTservicedefinedintheproject,wecancreateanewTestSuiteandaTestCaseinsidethesuite.WecanthenaddasteptotheTestCasethatwillcalltheRESTserviceusingtheparameter123ifwemodifythedefaultvalue,whichisthesameasthenameoftheparameter,inthiscase,id.Wecanruntheteststepusingthegreentriangleontheupper-leftcornerofthewindow,andsincewehavethetestedapplicationandthesoapUImockservicesrunning,weshouldgetananswerinJSON.WehavetoselectJSONontheresponseside;otherwise,soapUItriestointerprettheresponseasXML,andsincewehaveaJSONresponse,itisnottoofruitful.Whatweseeisthefollowingwindow:
Itisthesameresponsethatwesawinthebrowser.Therearenomiracleswhenweprogramcomputers.Sometimes,wedonotunderstandwhathappens,andsomethingsaresocomplexthattheyseemtobeamiracle,buttheyareactuallynot.Thereisanexplanationforeverything,itmayjustnotbeknowntous.Inthiscase,wecertainlyknowwhatishappening,butwhyisitanybettertoseetheJSONonthescreenofsoapUIthanitisonthebrowser?ThereasonisthatsoapUIcanexecuteassertionsandinsomecases,furtherteststepsbasedontheresultoftheRESTinvocation,andthefinalresultisasimpleYESorNO.ThetestisOK,oritFAILS.
Toaddanassertion,clickontheAssertionstextonthelower-leftcornerofthewindow.Asyoucanseeintheprecedingscreenshot,Ihavealreadyaddedonethatcomparesthe"title"fieldofthereturnedJSONwiththetext"BarStool".Whenweaddtheassertion,thedefaultvalueitsuggestsistheonethatwasactuallyreturned,whichisjustaveryhandyfeature.
Afterthis,runningthewholetestsuiteagainwillrunallthetestcases(wehaveonlyone),andalltheteststeps,oneaftertheother(weagainhaveonlyone),andfinallyitwilldisplayagreenFINISHEDbarontheUI,asshowninthefollowingscreenshot:
ThisisnotallthatsoapUIcando.Thisisawell-developedtesttoolthathasbeeninthemarketformanyyears.soapUIcantestSOAPservicesandRESTservices,anditcanhandleJMSmessages.Youcancreatetestsofmanystepswiththesecalls,loops,andassertionsincallsorinseparatetests,andincaseallelsefails,youcandojustanythingbycreatingprogrammedstepsintheGroovylanguageorcreatingextensionsinJava.
ServletfiltersTheservicesworkfinebynowandanyonecanquerythedetailsofourproducts.Thatmaybeaproblem.Thedetailsoftheproductsarenotnecessarilypublicinformation.Wehavetoensurethatweservethedataonlytopartnerswhoareeligibletoseeit.
Toensurethat,weneedsomethingintherequestthatprovesthattherequestcomesfromapartner.Thisinformationistypicallyapasswordorsomeothersecret.ItcouldbeplacedintotheGETrequestparametersorintotheHTTPrequestheader.Itisbettertoputitintotheheaderbecausetheinformationissecretandnottobeseenbyanybody.
TheGETparametersareapartoftheURL,andthebrowserhistoryremembersthat.Itisalsoveryeasytoenterthisinformationintothebrowserlocationwindow,copypasteit,andsenditoverachatchannelorovere-mail.Thisway,auseroftheapplication,whoisnotsoeducatedandconcernedaboutsecurity,maydisclosesecretinformation.AlthoughitisnotimpossibletodothesamewithinformationthatissentinanHTTPheader,itisnotlikelytohappen.Iftheinformationisintheheaderandsomebodysendstheinformationinane-mail,theyprobablyknowwhattheyaredoing;theycrossasecurityborderwillinglyandnotbysimplenegligence.
TosendauthenticationinformationalongtheHTTPrequest,SpringprovidesasecuritymodulethatcaneasilybeconfiguredwithannotationsandconfigurationXMLsand/orclasses.Thistime,wewilldoitabitdifferentlytointroduceservletfilters.
WewillrequirethatthevendorsinserttheX-PartnerSecretheaderintotherequest.Thisisanon-standardheader,andthusitmusthavetheX-prefix.Followingthisapproachisalsosomeextrasecurityfeature.Thisway,wecanpreventtheuserfromreachingtheserviceusingasimplebrowser.Thereis,atleast,aneedforsomeextrapluginthatcaninsertacustomheaderorsomeotherprogramsuchassoapUI.Thisway,itwillensurethatourpartnerswillusetheinterfaceprogrammatically,orifevertheyneedtotesttheinterfaceadhoc,onlyuserswithacertainleveloftechnologycandoso.Thisisimportanttokeepthesupportcostscontrolled.
Sincethissecrethastobecheckedinthecaseofeachandeveryservice,webetternotinsertthecheckingcodeintoeachandeveryservicecontroller.Evenifwecreatethecodeproperlyandfactorthecheckforthesecretintoaseparateclass,theinvocationofthemethodassertingthatthesecretisthereandiscorrectwillhavetobeinsertedineachandeverycontroller.Thecontrollerdoestheservice;checkingtheclientauthenticityisaninfrastructureissue.Theyaredifferentconcerns,andthus,theyhavetobeseparated.
Thebestwaythattheservletstandardprovidesforusisaservletfilter.Aservletfilterisaclassinvokedbytheservletcontainerbeforetheservletitselfifthefilterisconfigured.Thefiltercanbeconfiguredintheweb.xmlconfigurationfileoftheservletcontainerorbyusinganannotationwhenweusetheSpringboot.ThefilterdoesnotonlygettherequestandresponseasparametersbutalsoathirdargumentoftheFilterChaintypethatitshouldusetocalltheservletorthenextfilterinthechain.
Therecanbemorethanonefilterdefinedandtheygetchainedup.Thefiltermay,atitsdiscretion,decide
tocallornottocallthenextinthechain.
Weputourservletfilterintotheauthsub-packageofourapplication:
packagepackt.java9.by.example.mybusiness.productinformation.auth;
import...
@Component
publicclassAuthFilterimplementsFilter{
privatestaticLoggerlog=
LoggerFactory.getLogger(AuthFilter.class);
publicstaticfinalintNOT_AUTHORIZED=401;
@Override
publicvoidinit(FilterConfigfilterConfig)
throwsServletException{
}
@Override
publicvoiddoFilter(ServletRequestrequest,
ServletResponseresponse,
FilterChainchain)
throwsIOException,ServletException{
HttpServletRequesthttpRequest=
(HttpServletRequest)request;
finalStringsecret=
httpRequest.getHeader("X-PartnerSecret");
log.info("Partnersecretis{}",secret);
if("packt".equals(secret)){
chain.doFilter(request,response);
}else{
HttpServletResponsehttpResponse=
(HttpServletResponse)response;
httpResponse.sendError(NOT_AUTHORIZED);
}
}
@Override
publicvoiddestroy(){
}
}
ThefilterimplementstheFilterinterfacethatdefinesthreemethods.Inourcase,wedonothaveanyparameterstoconsiderinthefilter,andwedonotallocateanyresourcestorelease;thus,bothinitanddestroymethodsareempty.ThemainworkofthefilteristhedoFiltermethod.Ithasthreeparameters,twoofthemarethesameastheparametersofaservletandthethirdisFilterChain.
TherequestisconvertedtoHttpServletRequest,sowecangetaccesstotheX-PartnerSecretheaderthroughthegetHeadermethod.Ifthevaluesentinthisheaderfieldisgood,wecallthenextinthechain.Inourapplication,therearenomorefiltersconfigured;therefore,thenextinthechainistheservlet.Ifthesecretisnotacceptable,thenwedonotcallthenextinthechain.Instead,wereturnthe401NotAuthorizedHTTPerrortotheclient.
Inthisapplication,thesecretisverysimple.Thisistheconstantstringpackt.Thisisnotreallyabigsecret,especiallynowthatitispublishedinthisbook.Areal-lifeapplicationwouldrequiresomethingmorecrypticandlessknown.Itisveryprobablethateachpartnerwouldusedifferentsecretsandthatthesecrethastochangefromtimetotime.
Whenthereisanerrorconditioninaservletthatourprogramhandles,itisagoodpracticetousetheHTTPerrorhandlingmechanism.Insteadofsendingbackamessagewiththestatuscode200OKandexplaining,forexample,inaJSONformatthattheauthenticationwasnotsuccessful,wehavetosendbackthe401code.Thisisdefinedbythestandardanddoesnotneedanyfurtherexplanationordocumentation.
Thereisonethingleftinourprogram,andthatisauditlogging.
AuditloggingandAOPWehavelogginginoursamplecodeandforthatweuseslf4j,whichwecoveredinthepreviouschapter.Loggingismoreorlessthedecisionofthedeveloperandsupportstechnicallevelsofoperation.There,wealsotouchedonafewsentenceauditloggings.Thistypeofloggingisusuallyexplicitlyrequiredinafunctionalrequirement.
Generally,AOPisseparatingthedifferentaspectsofcodefunctionalityintoseparatecodefragments,andimplementingthemindependentofeachother.Thisisverymuchthesingleresponsibilityprinciple.Thistime,itisimplementedinawaythatnotonlythedifferentfunctionalitiesareimplementedseparatelybutalsohowweconnectthemtogetherisdefinedseparately.WhatisexecutedbeforeandafterwhatotherpartsareencodedseparatelygetstotheSpringconfiguration.Wehaveseensomethingsimilaralready.Thedependenciesthataclassneedstoproperlyoperatearedefinedinaseparatesegment(XMLorJavacode).ItisnotasurprisethatinthecaseofAOP,thesameisdoneusingSpring.Aspectsareconfiguredintheconfigurationfileorclass.
Atypicalaspectisauditlogging,andwewillusethisasanexample.Therearemanytopicsthatcanbeimplementedusingaspects,andsomeofthemareevenworthimplementingthatway.
Wedonotwanttoimplementtheauditloggingcodeineachbusinessmethodorclassthatneedsit.Instead,weimplementageneralaspectandconfigurethewiringsuchthatwheneverabeanmethodthatneedsauditloggingisinvoked,Springinvokestheauditlogging.
ThereareotherimportantterminologiesthatweshouldunderstandforAOPandespeciallyhowAOPcanbeconfiguredinSpring.
Thefirstandmostimportantistheaspect.Thisisthefunctionalitythatwewanttoimplement,inourexample,theauditlogging.
Joinpointisthepointinexecutionwhenanaspectisinvoked.Whenusingafull-scaleaspectsolutioninJavathatmodifiesthebytecodeofthegeneratedclass,ajoinpointcanbealmostanything.Itcanbeaccesstoafield,readorwrite;itcanbetheinvocationofamethodorexceptionthrowing.InthecaseofSpring,theclassbytecodeisnotmodified;thus,Springisnotabletoidentifytheaccessofafieldoranexceptionthrowing.UsingSpring,ajoinpointisalwaysusedwhenamethodisinvoked.
Anadviceishowtheaspectisinvokedatthejoinpoint.Itcanbebeforeadvice,afteradvice,oraroundadvice.Whentheadviceisbefore,theaspectisinvokedbeforethemethodiscalled.Whentheadviceisafter,theaspectisinvokedafterthemethodisinvoked.Aroundmeansthattheaspectisinvokedbeforethemethodcall,andtheaspectalsohasanargumenttocallthemethodandstillperformsomeactionsafterthemethodiscalled.Thisway,thearoundadviceisverysimilartoservletfilters.
Thebeforeadviceiscalledbeforethemethodcall,andafteritreturns,theframeworkwillinvokethemethod.Thereisnowayfortheaspecttopreventtheinvocationoftheoriginalmethod.Theonlyexceptioniswhentheaspect,well,throwsanexception.
Theafteradviceisalsoaffectedbyexceptions.Therecanbeanafterreturningadvicethatisinvokedwhenthemethodisreturning.Theafterthrowingisinvokedonlyifthemethodwerethrowinganexception.Afterfinallyisinvokedinthecaseofanexceptionorreturn.
Pointcutisaspecialstringexpressionthatidentifiesjoinpoints.Apointcutexpressionmaymatchzero,one,ormorejoinpoints.Whentheaspectisassociatedwithapointcutexpression,theframeworkwillknowthejoinpointsandwhenandwheretoinvoketheaspect.Inotherwords,pointcutisthestringthattellswhenandforwhichmethodtoinvoketheaspect.
EventhoughSpringimplementationofAOPdoesnotuseAspectJanddoesnotmodifythebytecodethatwascreatedfortheclasses,itsupportsthepointcutexpressionlanguage.AlthoughthisexpressionlanguageprovidesmorefeaturesthanwhatSpringimplements,itisawell-establishedandwidelyusedandacceptedexpressionlanguagetodescribepointcuts,anditjustwouldnotmakesensetoinventsomethingnew.
Introductionisaddingmethodsorfieldstoatypethatalreadyexistsanddoingitduringruntime.SpringallowsthisAOPfunctionalitytoaddaninterfacetoanexistingtypeandaddanimplementationoftheinterfaceintheformofanadviceclass.Inourexample,wedonotusethisfunctionality.
Targetobjectistheobjectthatisbeingadvisedbytheaspect.Thisisthebeanthatcontainsthemethodaroundtheaspect,thatis,beforeoraftertheaspectisinvoked.
Thatwasjustacondensedsetofdefinitions,almostlikeinamathbook.Ifyoudidnotgetthepointjustreadingit,don'tworry.Ididnotunderstanditeither.Thatiswhywehavethefollowingexample,afterwhichallwejustcoveredwillmakemoresense:
packagepackt.java9.by.example.mybusiness.productinformation;
import...
@Configuration
@Aspect
publicclassSpringConfigurationAspect{
privatestaticLoggerlog=
LoggerFactory.getLogger("AUDIT_LOG");
@Around("execution(*byId(..))")
publicProductInformationbyIdQueryLogging(
ProceedingJoinPointjp)
throwsThrowable{
log.info("byIdqueryisabouttorun");
ProductInformationpi=
(ProductInformation)jp.proceed(jp.getArgs());
log.info("byIdquerywasexecuted");
returnpi;
}
@Around("execution(*url(..))")
publicStringurlCreationLogging(ProceedingJoinPointjp)
throwsThrowable{
log.info("urlistobecreated");
Stringurl=(String)jp.proceed(jp.getArgs());
log.info("urlcreatedwas"+url);
returnurl;
}
}
Theclassisannotatedwiththe@ConfigurationannotationsothatSpringknowsthatthisclasscontainstheconfiguration.The@Aspectannotationdenotesthatthisconfigurationmayalsocontainaspectdefinitions.
The@Aroundannotationonthemethodsgivesthetypeofadvice,andtheargumentstringfortheannotationisthepointcutexpression.Ifthetypeofadviceisdifferent,oneoftheannotations,@Before,@After,@AfterReturning,or@AfterThrowing,shouldbeused.
Inourexample,weusethe@Aroundaspecttodemonstratethemostcomplexscenario.Welogtheexecutionofthetargetmethodbeforeandaftertheexecutionofthemethod,andwealsocalltheoriginalmethodthroughtheProceedingJoinPointobject.Becausethetwoobjectsreturndifferenttypesandwewanttologdifferently,wedefinetwoaspectmethods.
Theargumentoftheadviceannotationisthepointcutstring.Inthiscase,itisasimpleone.Thefirstone,execution(*byId(..)),saysthattheaspectshouldbeinvokedforanyexecutionofanymethodthathasthenamebyIdandhasanyarguments.Thesecondisverysimilar,exceptthenameofthemethodisdifferent.Thesearesimplepointcutexpressions,butinalargeapplicationthatheavilyusesAOP,theycanbeverycomplex.
ThepointcutexpressionsyntaxinSpringmainlyfollowsthesyntaxusedbyAspectJ.Theexpressionusesthenotionofpointcutdesignator(PCD)thatisusuallyexecution.Itisfollowedbythepatternthatdefineswhichmethodtointercept.Thegeneralformatisasfollows:
execution(modifiers-pattern?ret-type-patterndeclaring-type-pattern?name-pattern(param-pattern)throws-pattern?)
Exceptforthereturntypepart,allotherpartsareoptional.Forexample,wecanwritethefollowing:
execution(public**(..))
Thiswillinterceptallpublicmethods.Thefollowingexpressioninterceptsallmethodsthathaveanamestartingwithset:
execution(*set*(..))
Wecanusethe*characterasajokerinthesamewayaswecanuseitonthecommandlineinWindowsorUnixshell.Theargumentmatchingdefinitionisabitmorecomplex.(..)meansanyarguments,()meansnoarguments,and(*)meansexactlyoneargumentofanytype.Thelastonecanalsobeusedwhentherearemorearguments;forexample,(*,Integer)meansthattherearetwoarguments,thesecondbeinganInteger,andwejustdonotcarewhatthetypeofthefirstoneis.
Pointcutexpressionscanbemorecomplex,joiningtogethermatchexpressionswiththe&&(and)and||(or)logicaloperators,orusingthe!(negation)unaryoperator.
Usingthe@Pointcut()annotation,theconfigurationcandefinepointcutsputtingtheannotationsonmethods.Forexample,considerthefollowing:
@Pointcut("execution(*packt.java.9.by.example.service.*.*(..))")
publicvoidbusinessService(){}
Itwilldefineajoinpointforanymethodthatisimplementedinanyclassinthepackt.java.9.by.example.servicepackage.ThismerelydefinesthepointcutexpressionandassignsittothenamebusinessService,whichisgivenbythenameofthemethod.Later,wecanrefertothisexpressioninaspectannotations,forexample:
@After("businessService()")
Notethattheuseofthemethodispurelyforitsname.ThismethodisnotinvokedbySpring.Itisonlyusedtoborrowitsnametotheexpressionthatisdefinedonitusingthe@Pointcutannotation.Thereisaneedforsomething,suchasamethod,toputthisannotationon,andsincemethodshavenames,whynotuseit:Springdoesit.Whenitscanstheconfigurationclassesandseestheannotation,itassignsitinitsinternalstructurestothenameofthemethod,andwhenthatname(alongwiththeparenthesis,toconfusethenoviceprogrammermimickingamethodcall)isused,itlooksuptheexpressionforthatname.
AspectJdefinesotherdesignators.SpringAOPrecognizessomeofthem,butitthrowsIllegalArgumentExceptionbecauseSpringimplementsonlymethodexecutionpointcuts.AspectJ,ontheotherhand,canalsointerceptobjectcreationforwhichthePCDisinitialization,asanexample.SomeotherPCDs,inadditiontoexecution,canlimitanexecutionPCD.Forexample,thePCD,within,canbeusedtolimittheaspecttojoinpointsbelongingtoclasseswithincertainpackages,orthe@targetPCDcanbeusedtolimitthematchingtomethodsinobjectsthathavetheannotationgivenbetween(and)afterthekeyword@targetinthepointcutexpression.
ThereisaPCDthatSpringusesthatdoesnotexistinAspectJ.Thisisabean.Youcandefineapointcutexpressionthatcontainsbean(namepattern)tolimitthejoinpointtomethodexecutionsthatareinthenamedbean.Thepatterncanbetheentirenameoritcanbe,asalmostanyPCDexpressionmatching,*asajokercharacter.
Dynamicproxy-basedAOPSpringAOP,whenfirstpresentedtoJavaprogrammers,seemslikemagic.HowdoesithappenthatwehaveavariableofclassXandwecallsomemethodonthatobject,butinstead,itexecutessomeaspectbeforeorafterthemethodexecution,orevenaroundit,interceptingthecall
ThetechniquethatSpringdoesiscalleddynamicproxy.Whenwehaveanobject,whichimplementsaninterface,wecancreateanotherobject—theproxyobject—thatalsoimplementsthatinterface,buteachandeverymethodimplementationinvokesadifferentobjectcalledhandler,implementingtheJDKinterface,InvocationHandler.Whenamethodoftheinterfaceisinvokedontheproxyobject,itwillcallthefollowingmethodonthehandlerobject:
publicObjectinvoke(Objecttarget,Methodm,Object[]args)
Thismethodisfreetodoanything,evencallingtheoriginalmethodonthetargetobjectwiththeoriginalormodifiedargument.
Whenwedonothaveaninterfaceathandthattheclasstobeproxiedimplements,wecannotuseJDKmethods.Luckily,therearewidelyusedlibraries,suchascglib,whicharealsousedbySpringandthatcandosomethingsimilar.Cglibcancreateaproxyobjectthatextendstheoriginalclassandimplementsitsmethods,invokingthehandlerobject'sinvokemethodinawaysimilartohowtheJDKversiondoesfortheinterfacemethods.
ThesetechnologiescreateandloadclassesintotheJavamemoryduringruntime,andtheyareverydeeptechnicaltools.Theyareadvancedtopics.IdonotsaynottoplaywiththemwhilebeinganoviceJavaprogrammer.Afterall,whatcanhappen?Javaisnotaloadedgun.Itis,however,importantthatyoudonotloseyourinterestwhenyoudonotunderstandsomeofthedetailsorsomethingdoesnotworkfirst.Orsecond.Orthird...Keepswimming.
AOPimplementationinSpringworksbygeneratingproxyobjectsforthetargetobjects,andthehandlersinvoketheaspectsthatwedefineintheSpringconfiguration.Thisisthereasonyoucannotputaspectsonfinalclassesoronfinalmethods.Also,youcannotconfigureaspectsonprivateorprotectedmethods.Theprotectedmethodscouldbeproxiedinprinciple,butthisisnotagoodpractice,andthusSpringAOPdoesnotsupportit.Similarly,youcannotputaspectsonclassesthatarenotSpringbeans.Theyarecreatedby
thecodedirectlyandnotthroughSpringandhavenochancetoreturnaproxyinsteadoftheoriginalobjectwhentheobjectiscreated.Simplyput,ifSpringisnotaskedtocreatetheobject,itcannotcreateacustomone.Thelastthingwewanttodoistoexecutetheprogramandseehowtheaspectsperform.Theimplementationofourauditloggingisextremelysimple.Weusethestandardlogging,whichisnotreallysufficientforareal-lifeapplicationofauditlogging.Theonlyspecialthingwedoisthatweusealoggeridentifiedbythename,AUDIT_LOGandnotbythenameofaclass.Thisisalegitimateuseoftheloggersinmostoftheloggingframeworks.Inspiteofthefactthatweusuallyusetheclasstoidentifythelogger,itisabsolutelypossibletouseastringtoidentifyalogger.Inthecaseofourlogging,thisstringwillalsobeprintedontheconsoleintheloglines,anditwillvisuallystandout.
Considerthefollowingcommand:
gradle-Dspring.profiles.active=productionbootRun
Ifweagainstarttheapplicationwiththeprecedingcommand,startsoapUIfortheproject,startthemockservices,andexecutethetest,wewillseeontheconsolethefollowingloglinesthattheaspectsprint:
2016-11-1019:14:09.559INFO74643---[nio-8080-exec-1]o.a.c.c.C.[Tomcat].[localhost].[/]:InitializingSpringFrameworkServlet'dispatcherServlet'
2016-11-1019:14:09.567INFO74643---[nio-8080-exec-1]o.s.web.servlet.DispatcherServlet:FrameworkServlet'dispatcherServlet':initializationstarted
2016-11-1019:14:09.626INFO74643---[nio-8080-exec-1]o.s.web.servlet.DispatcherServlet:FrameworkServlet'dispatcherServlet':initializationcompletedin59ms
2016-11-1019:14:09.629INFO74643---[nio-8080-exec-1]p.j.b.e.m.p.auth.AuthFilter:Partnersecretispackt
2016-11-1019:14:09.655INFO74643---[nio-8080-exec-1]AUDIT_LOG:byIdqueryisabouttorun
2016-11-1019:14:09.666INFO74643---[nio-8080-exec-1]AUDIT_LOG:urlistobecreated
2016-11-1019:14:09.691INFO74643---[nio-8080-exec-1]AUDIT_LOG:urlcreatedwashttp://localhost:8083/inventory/{id}
2016-11-1019:14:09.715INFO74643---[nio-8080-exec-1]p.j.b.e.m.p.l.RestClientProductLookup:amount{id:123,amount:100}.
2016-11-1019:14:09.716INFO74643---[nio-8080-exec-1]p.j.b.e.m.p.l.RestClientProductLookup:Thereitemsfrom123.Weareoffering
2016-11-1019:14:09.716INFO74643---[nio-8080-exec-1]AUDIT_LOG:urlistobecreated
2016-11-1019:14:09.716INFO74643---[nio-8080-exec-1]AUDIT_LOG:urlcreatedwashttp://localhost:8081/product/{id}
2016-11-1019:14:09.725INFO74643---[nio-8080-exec-1]AUDIT_LOG:byIdquerywasexecuted
SummaryInthischapter,webuiltasimplebusinessapplicationthatsupportsbusiness-to-businesstransactions.WeimplementedaRESTserviceinamicroservices(almost)architectureusingthefeaturesthatareprovidedbythedefactostandardenterpriseframework:Spring.Lookingbackatthechapter,itisamazinghowfewlinesofcodewewrotetoachieveallthefunctionality,andthatisgood.Thelesscodeweneedtodevelopwhatwewant,thebetter.Thisprovesthepoweroftheframework.
Wediscussedmicroservices,HTTP,REST,JSON,andhowtousethemusingtheMVCdesignpattern.WelearnedhowSpringisbuiltup,whatmodulesarethere,howdependencyinjectionworksinSpring,andweeventouchedabitofAOP.ThiswasveryimportantbecausealongwithAOP,wediscoveredhowSpringworksusingdynamicproxyobjects,andthisissomethingthatisveryvaluablewhenyouneedtodebugSpringorsomeotherframeworkthatusesasimilarsolution(andthereareafewfrequentlyused).
Westartedtotestourcodeusingasimplebrowser,butafterthatwerealizedthatRESTservicesarebettertestedusingsomeprofessionaltestingtool,andforthatweusedsoapUIandbuiltupasimpleRESTtestsuitewithRESTteststepsandmockservices.
Havinglearntallthat,nothingwillstopusfromextendingthisapplicationusingverymodernandadvancedJavatechnologies,suchasreflection(whichwehavealreadytouchedonabitwhenwediscussedtheJDKdynamicproxy),Javastreams,lambdaexpressions,andscriptingontheserverside.
ExtendingOurE-CommerceApplication
Inthelastchapter,westarteddevelopingane-commerceapplicationandwecreatedthefunctionalitytolookupproductsbasedontheirIDand,also,bysomeparameters.Inthischapter,wewillextendthefunctionalitysothatwecanalsoordertheproductsweselected.Whiledoingso,wewilllearnnewtechnologies,focusingonfunctionalprogramminginJavaandonsomeotherlanguagefeatures,suchasreflectionandannotationhandlingduringruntime,andscriptinginterface.
Aswedidinthepreviouschapters,wewilldeveloptheapplicationstepbystep.Aswediscoverthenewlylearnttechnologies,wewillrefactorthecodetoenrollthenewtoolsandmethodstoproducemorereadableandeffectivecode.Wewillalsomimicthedevelopmentofreal-lifeprojectsinthesensethatatthestart,wewillhavesimplerequirements,andlater,newrequirementswillbesetasourimaginedbusinessdevelopsandsellsmoreandmoreproducts.Wewillbecomeimaginedmillionaires.
Wewillusethecodebaseofthepreviouschapter,andwewilldevelopitfurther,though,inanewproject.WewilluseSpring,Gradle,Tomcat,andsoapUI,whicharenotnewafterwegotacquaintedwiththeseinthepreviouschapter.Inthischapter,youwilllearnthefollowingtopics:
AnnotationprocessingUsingreflectionFunctionalprogramminginJavausing:
LambdaexpressionsStreamsInvokingscriptsfromJava
TheMyBusinessorderingTheorderingprocessisalittlebitmorecomplicatedthanjustlookingupproducts.Theorderformitselflistsproductsandamounts,andidentifieswhothecustomerforthatorderis.Identifiersgivetheproducts.Allthatwehavetodoischeckthattheproductsareavailableinourstore,andwecandeliverthemtothegivencustomer.Thisisthesimplestapproach;however,withsomeproducts,therearemorerestrictions.Forexample,whensomebodyordersadesk-sidelamp,wedeliverthepowercordseparately.Thereasonforthisisthatthepowercordisspecifictothecountry.WedeliverdifferentpowercordstotheUnitedKingdomandtoGermany.Onepossibleapproachcouldbetoidentifythecountryofthecustomer.Butthisapproachdoesnottakeintoaccountthatourcustomersareresellers.AllcustomerscouldbelocatedintheUnitedKingdom,andatthesametimetheymaywanttodeliverthelampwiththepowercabletoGermany.Toavoidsuchsituationsandambiguity,itwouldbeaptthatourcustomersorderthedesk-sidelampandthepowercordasseparateitemsinthesameorder.Insomecases,wedeliverthedesk-sidelampwithoutthepowercord,butthisisaspecialcase.Weneedsomelogictoidentifythesespecialcases.Therefore,wehavetoimplementlogictoseeifthereisapowercordforadesk-sidelampandifthereisnoautomatichandlingoftheorder,itisrefused.Itdoesnotmeanthatwewillnotdelivertheproduct.Wewillonlyputtheorderinaqueueandsomeoperatorwillhavetolookatit.
Theproblemwiththisapproachisthatthedesk-sidelampisonlyoneproductthatneedsconfigurationsupport.Themoreproductswehave,themorespecialitiestheymayhave,andthepieceofcodethatcheckstheconsistencyofanorderbecomesmoreandmorecomplexuntilitreachesalevelofcomplexitythatisnotmanageable.Whenaclassormethodbecomestoocomplex,theprogrammersrefactorit,splittingupthemethodorclassintosmallerpieces.Wehavetodothesamewiththeproductchecking.Weshouldn'ttrytocreateonehugeclassthatchecksfortheproductandallthepossibleorderconstellations,butratherweshouldhavemanysmallercheckssothateachchecksonlyonesmallset.
Checkingforconsistencyissimplerinsomecases.Checkingwhetherthelamphasapowercordhasacomplexityanynoviceprogrammercanprogram.Weusethisexampleinourcodebecausewewanttofocusontheactualstructureofthecode,andnotonthecomplexnatureofthecheckitself.Inreallife,however,thecheckscanbefairlycomplex.Imagineashopthatsellscomputers.Itputsaconfigurationtogether:powersupply,graphiccards,andmotherboard,theappropriateCPU,andthememory.Therearemanychoicesandsomeofthemmaynotworktogether.Inareal-lifesituation,weneedtocheckthatthemotherboardiscompatiblewiththememoryselected,thatithasasmanybanksasareintheorder,thattheyareappropriatelypaired(somememoriescanonlybeinstalledinpairs),thatthereisacompatibleslotforthegraphicscard,andthatthepowerhasenoughwattstoreliablyrunthewholeconfiguration.Thisisverycomplexandisbetternotmixedupwiththecodethatchecksifthereisapowercordforalamp.
Settinguptheproject
SincewearestillusingSpringboot,thebuildfiledoesnotneedanymodification;wewilluseitaswewillusethesamefileasinthelastchapter.Thepackagestructure,however,isabitdifferent.Thistime,wedosomethingmorecomplicatedthangettingarequestandrespondingtowhateverthebackendservicesdelivertous.Now,wehavetoimplementcomplexbusinesslogicthat,aswewillsee,needsmanyclasses.Whenwehavemorethan10classes,giveortake,inacertainpackage,itistimetothinkaboutputtingthemintoseparatepackages.Theclassesthatarerelatedtoeachotherandhaveasimilarfunctionalityshouldbeputintoonepackage.Thisway,wewillhaveapackageforthefollowing:
Thecontrollers(thoughwehaveonlyoneinthisexample,butusuallytherearemore)Datastoringbeansthathavenomorefunctionalitythanstoringdata,thus,fields,setters,andgettersCheckersthatwillhelpuscheckpowercordswhenadesk-sidelampisorderedServicesthatperformdifferentservicesforthecontrollerThemainpackageforourprogramthatcontainstheApplicationclass,SpringConfiguration,andsomeinterfaces
OrdercontrollerandDTOsWhenarequestcomestotheservertoorderabunchofproducts,itcomesinanHTTPSPOSTrequest.ThebodyoftherequestisencodedinJSON.Tillnow,wehadcontrollersthatwerehandlingGETparameters,buthandlingPOSTrequestsisnotmuchmoredifficultwhenwecanrelyonthedatamarshallingofSpring.Thecontrollercodeitselfissimple:
packagepackt.java9.by.example.mybusiness.bulkorder.controllers;
import...
@RestController
publicclassOrderController{
privateLoggerlog=
LoggerFactory.getLogger(OrderController.class);
privatefinalCheckerchecker;
publicOrderController(@AutowiredCheckerchecker){
this.checker=checker;
}
@RequestMapping("/order")
publicConfirmationgetProductInformation(@RequestBodyOrderorder){
if(checker.isConsistent(order)){
returnConfirmation.accepted(order);
}else{
returnConfirmation.refused(order);
}
}
}
Thereisonlyonerequestthatwehandleinthiscontroller:order.ThisismappedtotheURL,/order.TheorderisautomaticallyconvertedfromJSONtoanorderobjectfromtherequestbody.Thisiswhatthe@RequestBodyannotationasksSpringtodoforus.Thefunctionalityofthecontrollersimplycheckstheconsistencyoftheorder.Iftheorderisconsistent,thenweaccepttheorder;otherwise,werefuseit.Thereal-lifeexamplewillalsocheckthattheorderisnotonlyconsistentbutalsocomesfromacustomerwhoiseligibleforbuyingthoseproductsandthattheproductsareavailableinthewarehouseor,atleast,canbedelivered,basedonthepromisesandleadtimefromtheproducers.
Tochecktheconsistencyoftheorder,weneedsomethingthatdoesthisjobforus.Asweknowthatwehavetomodularizethecodeandnotimplementtoomanythingsinasingleclass,weneedacheckerobject.Thisisprovidedautomaticallybasedontheannotationontheclassandalsoontheconstructorofthecontrollerby@Autowired.
TheOrderclassisasimplebean,simplylistingtheitems:
packagepackt.java9.by.example.mybusiness.bulkorder.dtos;
import...;
publicclassOrder{
privateStringorderId;
privateList<OrderItem>items;
privateStringcustomerId;
...settersandgetters...
}
Thenameofthepackageisdtos,whichstandsforthepluralofDataTransferObject(DTO).DTOsareobjectsthatareusedtotransferdatabetweendifferentcomponents,
usuallyoverthenetwork.Sincetheothersidecanbeimplementedinanylanguage,themarshalingcanbeJSON,XML,orsomeotherformatthatiscapableofdeliveringnothingbutdata.Theseclassesdonothaverealmethods.DTOsusuallyhaveonlyfields,setters,andgetters.
Thefollowingistheclassthatcontainsoneiteminanorder:
packagepackt.java9.by.example.mybusiness.bulkorder.dtos;
publicclassOrderItem{
privatedoubleamount;
privateStringunit;
privateStringproductId;
...settersandgetters...
}
Theorderconfirmationisalsointhispackage,andthoughthisisalsoatrueDTO,ithassomesimpleauxiliarymethods:
packagepackt.java9.by.example.mybusiness.bulkorder.dtos;
publicclassConfirmation{
privatefinalOrderorder;
privatefinalbooleanaccepted;
privateConfirmation(Orderorder,booleanaccepted){
this.order=order;
this.accepted=accepted;
}
publicstaticConfirmationaccepted(Orderorder){
returnnewConfirmation(order,true);
}
publicstaticConfirmationrefused(Orderorder){
returnnewConfirmation(order,false);
}
publicOrdergetOrder(){
returnorder;
}
publicbooleanisAccepted(){
returnaccepted;
}
}
Weprovidetwofactorymethodsfortheclass.Thisisalittleviolationofthesingleresponsibilityprinciplethatpuristshate.Mostofthetime,whenthecodebecomesmorecomplex,suchshortcutsbiteback,andthecodehastoberefactoredtobecleaner.Thepuristsolutionwouldbetocreateaseparatefactoryclass.Theuseofthefactorymethodseitherfromthisclassorfromaseparatedclassmakesthecodeofthecontrollermorereadable.
Themajortaskwehaveistheconsistencycheck.Thecode,tillthispoint,isalmosttrivial.
ConsistencycheckerWehaveaconsistencycheckerclass,andaninstanceofitisinjectedintothecontroller.Thisclassisusedtochecktheconsistency,butitdoesnotactuallyperformthecheckitself.Itonlycontrolsthedifferentcheckersthatweprovideandinvokesthemonebyonetodotherealwork.
Werequirethataconsistencychecker,suchastheonethatcheckswhethertheordercontainsapowercordwhenadesk-sidelampisordered,implementstheConsistencyCheckerinterface:
packagepackt.java9.by.example.mybusiness.bulkorder;
import...
publicinterfaceConsistencyChecker{
booleanisInconsistent(Orderorder);
}
ThemethodisInconsistentshouldreturntrueiftheorderisinconsistent.Itreturnsfalseifitdoesnotknowwhethertheorderisinconsistentornot,butfromtheaspectthattheactualcheckerexaminestheorder,thereisnoinconsistency.HavingseveralConsistencyCheckerclasses,wehavetoinvokeoneaftertheotheruntilonereturnstrueorweareoutofthem.Ifnoneofthemreturnstrue,thenwecansafelyassume,atleastfromtheautomatedcheckers'pointofview,thattheorderisconsistent.
Weknowatthestartofthedevelopmentthatwewillreallyhavealotofconsistencycheckersandnotallarerelevantforalloftheorders.Wewanttoavoidtheinvocationofeachcheckerforeachorder.Todoso,weimplementsomefiltering.Weletproductsspecifywhattypeofcheckstheyneed.Thisisapieceofproductinformation,suchasthesizeorthedescription.Toaccommodatethis,weneedtoextendtheProductInformationclass.
WewillcreateeachConsistencyCheckerinterface,implementingtheclasstobeaSpringbean(annotatedwiththe@Componentannotation),andatthesametime,wewillannotatethemwithanannotationthatspecifieswhattypeofcheckstheyimplement.Atthesametime,ProductInformationisextended,containingasetofAnnotationclassobjectsthatspecifywhichcheckerstoinvoke.Wecouldsimplylistthecheckerclassesinsteadoftheannotations,butthisgivesussomeextrafreedominconfiguringthemappingbetweentheproductsandtheannotations.Theannotationspecifiesthetypeoftheproducts,andthecheckerclassesareannotated.Thedesk-sidelamphasthePoweredDevicetype,andthecheckerclass,NeedPowercord,isannotatedwiththe@PoweredDeviceannotation.Ifthereisanyothertypeofproductsthatalsoneedsapowercord,thentheannotationofthattypeshouldbeaddedtotheNeedPowercordclass,andourcodewillwork.Sincewestartdivingdeepintoannotationsandannotationhandling,wehavetofirstlearnwhatannotationsreallyare.WehavealreadyusedannotationssinceChapter3,OptimizingtheSort,MakingCodeProfessionalbutallweknewwashowtousethem,andthatisusuallydangerouswithoutunderstandingwhatwedid.
AnnotationsAnnotationsareusedwiththe@characterinfrontofthemandcanbeattachedtopackages,classes,interfaces,fields,methods,methodparameters,generictypedeclarationanduse,and,finally,toannotations.Annotationscanbeusedalmosteverywhereandtheyareusedtodescribesomeprogrammetainformation.Forexample,the@RestControllerannotationdoesnotdirectlyalterthebehavioroftheOrderControllerclass.ThebehavioroftheclassisdescribedbytheJavacodethatisinside.TheannotationhelpsSpringtounderstandwhattheclassisandhowitcanandshouldbeused.WhenSpringscansallthepackagesandclassestodiscoverthedifferentSpringbeans,itseestheannotationontheclassandtakesitintoaccount.TherecanbeotherannotationsontheclassthatSpringdoesnotunderstand.Theymaybeusedbysomeotherframeworkorprogramcode.Springignoresthemasanywell-behavingframework.Forexample,aswewillseelater,wehaveinourcodebase,theNeedPowercordclass,whichisaSpringbeanand,assuch,[email protected],itisalsoannotatedwiththe@PoweredDeviceannotation.Springhasnoideaaboutwhatapowereddeviceis.Thisissomethingthatwedefineanduse.Springignoresthis.
Packages,classes,interfaces,fields,andsoon,canhavemanyannotationsattachedtothem.Theseannotationsshouldsimplybewritteninfrontofthedeclarationofthesyntacticalunittheyareattachedto.
Inthecaseofpackages,theannotationhastobewritteninfrontofthepackagenameinthepackage-info.javafile.ThisfilecanbeplacedinthedirectoryofthepackageandcanbeusedtoedittheJavaDocforthepackageandalsotoaddanannotationtothepackage.ThisfilecannotcontainanyJavaclasssincethename,package-info,isnotavalididentifier.
Wecannotjustwriteanythinginfrontofanythingasanannotation.Annotationsshouldbedeclared.TheyareintheruntimeofJavaspecialinterfaces.TheJavafilethatdeclaresthe@PoweredDeviceannotation,forexample,lookslikethis:
packagepackt.java9.by.example.mybusiness.bulkorder.checkers;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public@interfacePoweredDevice{
}
The@characterinfrontoftheinterfacekeywordshowsusthatthisisaspecialone:anannotationtype.Therearesomespecialrules;forexample,anannotationinterfaceshouldnotextendanyotherinterface,notevenanannotationone.Ontheotherhand,thecompilerautomaticallymakestheannotationinterfacesothatitextendstheJDKinterface,java.lang.annotation.Annotation.
Annotationsareinthesourcecode,andthus,theyareavailableduringthecompilationprocess.Theycanalsoberetainedbythecompilerandputintothegeneratedclassfiles,andwhentheclassloaderloadstheclassfile,theymayalsobeavailableduringruntime.Thedefaultbehavioristhatthecompilerstorestheannotationalongwiththeannotatedelementintheclassfile,buttheclassloaderdoesnotkeepitavailableforruntime.
Tohandleannotationsduringthecompilationprocess,theJavacompilerhastobeextendedusingannotationprocessors.ThisisafairlyadvancedtopicandthereareonlyafewexamplesyoucanmeetwhileworkingwithJava.AnannotationprocessorisaJavaclassthatimplementsaspecialinterfaceandisinvokedbythecompilerwhenitprocessesanannotationinthesourcefilethattheprocessorisdeclaredtohaveaninterestin.
AnnotationretentionSpringandotherframeworksusuallyhandleannotationsduringruntime.Thecompilerandtheclassloaderhavetobeinstructedthattheannotationistobekeptavailableduringruntime.Todoso,theannotationinterfaceitselfhastobeannotatedusingthe@Retentionannotation.ThisannotationhasoneparameteroftheRetentionPolicytype,whichisanenum.Wewillsoondiscusshowannotationparametersshouldbedefined.
Itisinterestingtonotethatthe@Retentionannotationontheannotationinterfacehastobeavailableintheclassfile;otherwise,theclassloaderswouldnotknowhowtotreatanannotation.Howdowesignalthatanannotationistobekeptbythecompilerafterthecompilationprocess?Weannotatetheannotationinterfacedeclaration.Thus,thedeclarationof@Retentionisannotatedbyitselfanditisdeclaredtobeavailableinruntime.
Theannotationdeclarationcanbeannotatedusing@Retention(RetentionPolicy.SOURCE),@Retention(RetentionPolicy.CLASS),or@Retention(RetentionPolicy.RUNTIME).
Annotationtarget
Thelastretentiontypewillbethemostfrequentused.Therearealsootherannotationsthatcanbeusedonannotationdeclarations.The@Targetannotationcanbeusedtorestricttheuseoftheannotationtocertainlocations.Theargumenttothisannotationiseitherasinglejava.lang.annotation.ElementTypevalueoranarrayofthesevalues.Thereisagoodreasontorestricttheuseofannotations.Itismuchbettertogetacompilationtimeerrorwhenweplaceanannotationinthewrongplacethanhuntingduringruntimewhytheframeworkignoresourannotation.
AnnotationparametersAnnotations,aswesaw,canhaveparameters.Todeclaretheseparametersinthe@interfacedeclarationoftheannotation,weusemethods.Thesemethodshaveanameandareturnvalue,buttheyshouldnothaveanargument.Youmaytrytodeclaresomeparameters,buttheJavacompilerwillbestrictandwillnotcompileyourcode.
Thevaluescanbedefinedattheplacewheretheannotationisused,usingthenameofthemethodandwiththe=character,assigningtothemsomevaluethatiscompatiblewiththetypeofthemethod.Forexample,let'ssupposethatwemodifythedeclarationoftheannotationPoweredDevicetothefollowing:
public@interfaceParameteredPoweredDevice{
StringmyParameter();
}
Insuchacase,attheuseoftheannotation,weshouldspecifysomevaluefortheparameter,suchasthefollowing:
@Component
@ParameteredPoweredDevice(myParameter="1966")
publicclassNeedPowercordimplementsConsistencyChecker{
...
Ifthenameoftheparameterisavalueandattheplaceofuseoftheannotationthereisnootherparameterdefined,thenthename,"value",maybeskipped.Forexample,modifyingthecodeasfollowsisahandyshorthandwhenwehaveonlyoneparameter:
public@interfaceParameteredPoweredDevice{
Stringvalue();
}
...
@Component
@ParameteredPoweredDevice("1966")
publicclassNeedPowercordimplementsConsistencyChecker{
...
Wecandefineoptionalparametersalsousingthedefaultkeywordfollowingthemethoddeclaration.Inthiscase,wehavetodefineadefaultvaluefortheparameter.Modifyingthesampleannotationwehavefurther,westillcan,butneednot,specifythevalue.Inthelattercase,itwillbeanemptystring:
public@interfaceParameteredPoweredDevice{
Stringvalue()default"";
}
Sincethevaluewespecifyshouldbeconstantandcalculableduringcompiletime,thereisnotmuchuseofcomplextypes.Annotationparametersareusuallystrings,integers,andsometimes,doubles,orotherprimitivetypes.Theexactlistofthetypesgivenbythelanguagespecificationisasfollows:
Primitive(double,int,andsoon)StringClassAnenumAnotherannotation
Anarrayofanyoftheaforementionedtypes
WehaveseenexamplesofStringandalsothatenum:RetentionandTargetbothhaveenumparameters.Theinterestingpartwewanttofocusonisthelasttwoitemsoftheprecedinglist.
Whenthevalueoftheparameterisanarray,thevaluecanbespecifiedascomma-separatedvaluesbetweenthe{and}characters.Forexample:
String[]value();
Thiscanthenbeaddedtothe@interfaceannotationwecanwrite:
@ParameteredPoweredDevice({"1966","1967","1991"})
However,incasethereisonlyonevaluewewanttopassastheparametervalue,wecanstillusetheformat:
@ParameteredPoweredDevice("1966")
Inthiscase,thevalueoftheattributewillbeanarrayoflength1.Whenthevalueofanannotationisanarrayofannotationtypes,thingsgetabitmorecomplex.Wecreatean@interfaceannotation(notethepluralinthename):
@Retention(RetentionPolicy.RUNTIME)
public@interfacePoweredDevices{
ParameteredPoweredDevice[]value()default{};
}
Theuseofthisannotationcouldbeasfollows:
@PoweredDevices(
{@ParameteredPoweredDevice("1956"),@ParameteredPoweredDevice({"1968","2018"})}
)
NotethatthisisnotthesameashavingtheParameteredPoweredDeviceannotationwiththreeparameters.Thisisanannotationthathastwoparameters.Eachparameterisanannotation.Thefirsthasonestringparameterandthesecondhastwo.
Asyoucansee,annotationscanbefairlycomplex,andsomeoftheframeworks(orrathertheprogrammerswhocreatedthem)ranamokusingthem.Beforeyoustartwritingaframework,researchtoseewhetherthereisalreadyaframeworkthatyoucanuse.Also,checkwhetherthereissomeotherwaytosolveyourproblem.99%ofannotationhandlingcodecouldbeavoidedandmadesimpler.Thelesscodewewriteforthesamefunctionality,thehappierweare.Weprogrammersarethelazytypesandthisisthewayithastobe.
Thelastexample,wheretheparameteroftheannotationisanarrayofannotations,isimportanttounderstandhowwecancreaterepeatableannotations.
RepeatableannotationsAnnotatethedeclarationoftheannotationwith@Repeatabletodenotethattheannotationcanbeappliedmultipletimesatoneplace.Theparametertothisannotationisanannotationtypethatshouldhaveaparameteroftype,whichisanarrayofthisannotation.Don'ttrytounderstand!I'llgiveanexampleinstead.Ialreadyhave,infact:[email protected]@ParameteredPoweredDevice.Considerthatwenowannotatethis@interfaceasthefollowing:
...
@Repeatable(PoweredDevices.class)
public@interfaceParameteredPoweredDevice{
...
Then,wecansimplifytheuseof@ParameteredPoweredDevice.WecanrepeattheannotationmultipletimesandtheJavaruntimewillautomaticallyencloseitinthewrappingclass,which,inthiscase,[email protected],thefollowingtwowillbeequivalent:
...
@ParameteredPoweredDevice("1956")
@ParameteredPoweredDevice({"1968","2018"})
publicclassNeedPowercordimplementsConsistencyChecker{
...
@PoweredDevices(
{@ParameteredPoweredDevice("1956"),@ParameteredPoweredDevice({"1968","2018"})}
)
publicclassNeedPowercordimplementsConsistencyChecker{
...
ThereasonforthiscomplexapproachisagainanexampleofbackwardcompatibilitythatJavastrictlyfollows.AnnotationswereintroducedinJava1.5andrepeatableannotationshavebeenavailableonlysinceversion1.8.WewillsoontalkaboutthereflectionAPIthatweusetohandletheannotationsduringruntime.ThisAPIinthejava.lang.reflect.AnnotatedElementinterfacehasagetAnnotation(annotationClass)method,whichreturnsanannotation.Ifasingleannotationcanappearmorethanonceonaclass,method,andsoon,thenthereisnowayofcallingthismethodtogetallthedifferentinstanceswithallthedifferentparameters.Backwardcompatibilitywasensuredbyintroducingthecontainingtypethatwrapsthemultipleannotations.
Annotationinheritance
Annotations,justlikemethodsorfields,canbeinheritedbetweenclasshierarchies.Ifanannotationdeclarationismarkedwith@Inherited,thenaclassthatextendsanotherclasswiththisannotationcaninheritit.Theannotationcanbeoverriddenincasethechildclasshastheannotation.BecausethereisnomultipleinheritanceinJava,annotationsoninterfacescannotbeinherited.Evenwhentheannotationisinherited,theapplicationcodethatretrievestheannotationofacertainelementcandistinguishbetweentheannotationsthatareinheritedandthosethataredeclaredontheentityitself.Therearemethodstogettheannotationsandseparatemethodstogetthedeclaredannotationsthataredeclaredontheactualelement,andnotinherited.
@Documentedannotations
The@Documentedannotationexpressestheintentthattheannotationispartofthecontractoftheentityand,thisway,ithastogetintothedocumentation.ThisisanannotationthattheJavaDocgeneratorlooksatwhencreatingthedocumentationforanelementthatreferencesthe@Documentedannotation.
JDKannotationsThereareotherannotationsdefinedintheJDKinadditiontothosethataretobeusedtodefineannotations.Wehavealreadyseensomeofthese.Themostfrequentlyusedisthe@Overrideannotation.Whenthecompilerseesthisannotation,itchecksthatthemethodreallyoverridessomeinheritedmethod.Failingtodosowillresultinanerror,savingusfrommiserableruntimedebugging.
The@Deprecatedannotationsignalsinthedocumentationofamethod,class,orsomeotherelementthattheelementisnottobeused.Itisstillthereinthecode,becausesomeusersmaystilluseit,butinthecaseofanewdevelopmentthatdependsonthelibrarycontainingtheelement,thenewlydevelopedcodeshouldnotuseit.Theannotationhastwoparameters.Oneparameterissince,whichcanhaveastringvalueandmaydeliverversioninformationabouthowlongorsincewhichversionofthemethod,orclassisdeprecated.TheotherparameterisforRemoval,whichshouldbetrueiftheelementwillnotappearinthefutureversionsofthelibrary.Somemethodsmaybedeprecatedbecausetherearebetteralternativesbutthedevelopersdonotintendtoremovethemethodfromthelibrary.Insuchacase,theforRemovalcanbesettofalse.
The@SuppressWarningannotationisalsoafrequentlyusedone,thoughitsuseisquestionable.Itcanbeusedtosuppresssomeofthewarningsofthecompiler.Itisrecommendedtowritecode,ifpossible,whichcanbecompiledwithoutanywarning.
The@FunctionalInterfaceannotationdeclaresthataninterfaceintendstohaveonlyonemethod.Suchinterfacescanbeimplementedaslambdaexpressions.Youwilllearnaboutlambdaexpressionslaterinthischapter.Whenthisannotationisappliedonaninterfaceandthereismorethanonemethoddeclaredintheinterface,thecompilerwillsignalcompilationerror.Thiswillpreventanydeveloperearlyonfromaddinganothermethodtoaninterfaceintendedtobeusedtogetherwithfunctionalprogrammingandlambdaexpressions.
Usingreflection
Nowthatyouhavelearnthowtodeclareannotationsandhowtoattachthemtoclassesandmethods,wecanreturntoourProductInformationclass.Recallthatwewantedtospecifythetypeofproductsinthisclassandthateachproducttypeisrepresentedbyan@interfaceannotation.Wehavealreadylisteditinthepreviousfewpages,theonewewillimplementinour@PoweredDeviceexample.Wewilldevelopthecodeassumingthatlatertherewillbemanysuchannotations,producttypes,andconsistencycheckersthatareannotatedwith@Componentandwithoneormoreofourannotations.
GettingannotationsWewillextendtheProductInformationclasswiththefollowingfield:
privateList<Class<?extendsAnnotation>>check;
SincethisisaDTO,andSpringneedsthesettersandgetters,wewillalsoaddanewgetterandsettertoit.Thisfieldwillcontainthelistofclassesthateachclassimplementoneofourannotationsandalsothebuilt-inJDKinterface,Annotation,becausethatisthewaytheJavacompilergeneratesthem.Atthispoint,thismaybeabitmurkybutIpromisethatthedawnwillbreakandtherewillbelightaswegoon.
Togettheproductinformation,wehavetolookitupbyID.Thisistheinterfaceandservicethatwedevelopedinthelastchapter,except,thistime,wehaveanothernewfield.Thisis,infact,asignificantdifferencealthoughtheProductLookupinterfacedidnotchangeatall.Inthelastchapter,wedevelopedtwoversions.Oneoftheversionswasreadingthedatafromapropertiesfile,theotheronewasconnectingtoaRESTservice.
PropertiesfilesareuglyandoldtechnologybutamustifeveryouintendtopassaJavaintervieworworkonenterpriseapplicationsdevelopedatthestartofthe21stcentury.Ihadtoincludeitinthelastchapter.Itwasmyownurgetoincludeitinthebook.Atthesametime,whilecodingforthischapter,Ididnothavethestomachtokeepusingit.IalsowantedtoshowyouthatthesamecontentcouldbemanagedinaJSONformat.
Now,wewillextendtheimplementationofResourceBasedProductLookuptoreadtheproductinformationfromJSONformattedresourcefiles.Mostofthecoderemainsthesameintheclass;therefore,weonlylistthedifferencehere:
packagepackt.java9.by.example.mybusiness.bulkorder.services;
import...
@Service
publicclassResourceBasedProductLookupimplementsProductLookup{
privatestaticfinalLoggerlog=LoggerFactory.getLogger(ResourceBasedProductLookup.class);
privateProductInformationfromJSON(InputStreamjsonStream)
throwsIOException{
ObjectMappermapper=newObjectMapper();
returnmapper.readValue(jsonStream,
ProductInformation.class);
}
...
privatevoidloadProducts(){
if(productsAreNotLoaded){
try{
Resource[]resources=
newPathMatchingResourcePatternResolver().
getResources("classpath:products/*.json");
for(Resourceresource:resources){
loadResource(resource);
}
productsAreNotLoaded=false;
}catch(IOExceptionex){
log.error("Testresourcescannotberead",ex);
}
}
}
privatevoidloadResource(Resourceresource)
throwsIOException{
finalintdotPos=
resource.getFilename().lastIndexOf('.');
finalStringid=
resource.getFilename().substring(0,dotPos);
finalProductInformationpi=
fromJSON(resource.getInputStream());
pi.setId(id);
products.put(id,pi);
}
...
Intheprojectresources/productsdirectorywehaveafewJSONfiles.Oneofthemcontainsthedesklampproductinformation:
{
"id":"124",
"title":"DeskLamp",
"check":[
"packt.java9.by.example.mybusiness.bulkorder.checkers.PoweredDevice"
],
"description":"thisisalampthatstandsonmydesk",
"weight":"600",
"size":["300","20","2"]
}
ThetypeofproductisspecifiedinaJSONarray.Inthisexample,thisarrayhasonlyoneelementandthatelementisthefullyqualifiednameoftheannotationinterfacethatrepresentsthetypeofproduct.WhentheJSONmarshallerconvertstheJSONtoaJavaobject,itrecognizesthatthefieldthatneedsthisinformationisaList,soitconvertsthearraytoalistand,also,theelementsfromStringtoClassobjectsrepresentingtheannotationinterface.
NowthatwehavetheresourcesloadedfromJSONformattedresourcesandwesawhoweasyitistoreadJSONdatawhenusingSpring,wecangetbacktotheorderconsistencycheck.TheCheckerclassimplementsthelogictocollectthepluggablecheckersandtoinvokethem.Italsoimplementstheannotation-basedscreeningsoasnottoinvokethecheckerswedon'treallyneedfortheactualproductsintheactualorder:
packagepackt.java9.by.example.mybusiness.bulkorder.services;
import...
@Component()
@RequestScope
publicclassChecker{
privatestaticfinalLoggerlog=
LoggerFactory.getLogger(Checker.class);
privatefinalCollection<ConsistencyChecker>checkers;
privatefinalProductInformationCollectorpiCollector;
privatefinalProductsCheckerCollectorpcCollector;
publicChecker(
@AutowiredCollection<ConsistencyChecker>checkers,
@AutowiredProductInformationCollectorpiCollector,
@AutowiredProductsCheckerCollectorpcCollector){
this.checkers=checkers;
this.piCollector=piCollector;
this.pcCollector=pcCollector;
}
publicbooleanisConsistent(Orderorder){
Map<OrderItem,ProductInformation>map=
piCollector.collectProductInformation(order);
if(map==null){
returnfalse;
}
Set<Class<?extendsAnnotation>>annotations=
pcCollector.getProductAnnotations(order);
for(ConsistencyCheckerchecker:
checkers){
for(Annotationannotation:
checker.getClass().getAnnotations()){
if(annotations.contains(
annotation.annotationType())){
if(checker.isInconsistent(order)){
returnfalse;
}
break;
}
}
}
returntrue;
}
}
OneoftheinterestingthingstomentionisthattheSpringauto-wiringisveryclever.WehaveafieldwiththeCollection<ConsistencyChecker>type.Usually,auto-wiringworksifthereisexactlyoneclassthathasthesametypeastheresourcestowire.Inourcase,wedonothaveanysuchcandidatesincethisisacollection,butwehavemanyConsistencyCheckerclasses.AllourcheckersimplementthisinterfaceandSpringrecognizesit,instantiatesthemall,magicallycreatesacollectionofthem,andinjectsthecollectionintothisfield.
Usuallyagoodframeworkworkslogically.IwasnotawareofthisfeatureofSpring,butIthoughtthatthiswouldbelogicaland,magically,itworked.Ifthingsarelogicalandjustwork,youdonotneedtoreadandrememberthedocumentation.Abitofcautiondoesnotharmhowever.AfterIexperiencedthatthisfunctionalityworksthisway,IlookeditupinthedocumentationtoseethatthisisreallyaguaranteedfeatureofSpringandnotsomethingthatjusthappenstoworkbutmaychangeinfutureversionswithoutnotice.Usingonlyguaranteedfeaturesisextremelyimportantbutisneglectedmanytimesinourindustry.
WhentheisConsistentmethodisinvoked,itfirstcollectstheproductinformationintoHashMap,assigningaProductInformationinstancetoeachOrderItem.Thisisdoneinaseparateclass.Afterthis,ProductsCheckerCollectorcollectstheConsistencyCheckerinstancesneededbyoneormoreproductitems.Whenwehavethisset,weneedtoinvokeonlythosecheckersthatareannotatedwithoneoftheannotationsthatareinthisset.Wedothatinaloop.
Inthiscode,weusereflection.Weloopovertheannotationsthateachcheckerhas.Togetthecollectionofannotations,weinvokechecker.getClass().getAnnotations().Thisinvocationreturnsacollectionofobjects.EachobjectisaninstanceofsomeJDKruntimegeneratedclassthatimplementstheinterfacewedeclaredasanannotationinitsownsourcefile.Thereisnoguarantee,though,thatthedynamicallycreatedclassimplementsonlyour@interfaceandnotsomeotherinterfaces.Therefore,togettheactualannotationclass,wehavetoinvoketheannotationTypemethod.
TheProductCheckerCollectorandProductInformationCollectorclassesareverysimple,andwewilldiscussthemlaterwhenwelearnaboutstreams.Theywillserveasagoodexampleatthatplace,whenweimplementthemusingloopsand,rightafterthat,usingstreams.
Havingthemall,wecanfinallycreateouractualcheckerclasses.Theonethathelpsusseethatthereisapowercordorderedforourlampisthefollowing:
packagepackt.java9.by.example.mybusiness.bulkorder.checkers;
import...
@Component
@PoweredDevice
publicclassNeedPowercordimplementsConsistencyChecker{
privatestaticfinalLoggerlog=
LoggerFactory.getLogger(NeedPowercord.class);
@Override
publicbooleanisInconsistent(Orderorder){
log.info("checkingorder{}",order);
CheckHelperhelper=newCheckHelper(order);
return!helper.containsOneOf("126","127","128");
}
}
Thehelperclasscontainssimplemethodsthatwillbeneededbymanyofthecheckers,forexample:
publicbooleancontainsOneOf(String...ids){
for(finalOrderItemitem:order.getItems()){
for(finalStringid:ids){
if(item.getProductId().equals(id)){
returntrue;
}
}
}
returnfalse;
}
InvokingmethodsInthisexample,weusedonlyonesinglereflectioncalltogettheannotationsattachedtoaclass.Reflectioncandomanymorethings.Handlingannotationsisthemostimportantuseforthesecallssinceannotationsdonothavetheirownfunctionalityandcannotbehandledinanyotherwayduringruntime.Reflection,however,doesnotstoptellinguswhatannotationsaclassoranyotherannotableelementhas.Reflectioncanbeusedtogetalistofthemethodsofaclass,thenameofthemethodsasstrings,theimplementedinterfacesofaclass,theparentclassitextends,thefields,thetypesoffields,andsoon.Reflectiongenerallyprovidesmethodsandclassestowalkthroughtheactualcodestructuredowntothemethodlevel,programmatically.
Thiswalkthroughdoesnotonlyallowreadingtypesandcodestructurebutalsomakesitpossibletosetfieldvaluesandcallmethodswithoutknowingthemethods'nameatcompiletime.Wecanevensetfieldsthatareprivateandarenotgenerallyaccessiblebytheoutsideworld.Itisalsotonotethataccessingthemethodsandfieldsthroughreflectionisusuallyslowerthanthroughcompiledcodebecauseitalwaysinvolveslookupbythenameoftheelementinthecode.
Theruleofthumbisthatifyouseethatyouhavetocreatecodeusingreflection,thenrealizethatyouareprobablycreatingaframework(orwritingabookaboutJavathatdetailsreflection).Doesitsoundfamiliar?
Springalsousesreflectiontodiscovertheclasses,methods,andfields,andalsotoinjectanobject.ItusestheURLclassloadertolistalltheJARfilesanddirectoriesthatareontheclasspath,loadsthem,andexaminestheclasses.
Foracontrivedexample,forthesakeofdemonstration,let'sassumethattheConsistencyCheckerimplementationswerewrittenbymanyexternalsoftwarevendors,andthearchitectwhooriginallydesignedtheprogramstructurejustforgottoincludetheisConsistentmethodintheinterface.(Atthesametime,tosaveourmentalhealth,wecanalsoimaginethatthispersonisnotworkinganymoreinthecompanyfordoingso.)Asaconsequence,thedifferentvendorsdeliveredJavaclassesthat"implement"thisinterfacebutwecannotinvokethemethod,notonlybecausewedonothaveacommonparentinterfacethathasthismethodbutalsobecausethevendorsjusthappenedtousedifferentnamesfortheirmethods.
Whatcanwedointhissituation?Business-wise,askingallthevendorstorewritetheircheckersisruledoutbecausethemknowingweareintroubleattachesaheftypricetagtothetask.Ourmanagerswanttoavoidthatcostandwedevelopersalsowanttoshowthatwecanmendthesituationanddomiracles.(Later,Iwillhaveacommentonthat.)
Wecouldjusthaveaclassthatknowseverycheckerandhowtoinvokeeachoftheminmanydifferentways.Thiswouldrequireustomaintainthesaidclasswheneveranewcheckerisintroducedtothesystem,andwewanttoavoidthat.Thewholepluginarchitectureweareusingwasinventedforthisverypurposeinthefirstplace.
Howcanweinvokeamethodonanobjectthatweknowhasonlyonedeclaredmethod,whichacceptsan
orderasaparameter?Thatiswherereflectioncomesintothepicture.Insteadofcallingchecker.isInconsistent(order),weimplementasmallprivatemethod,isInconsistent,whichcallsthemethod,whateveritsnameis,viareflection:
privatebooleanisInconsistent(ConsistencyCheckerchecker,Orderorder){
Method[]methods=checker.getClass().getDeclaredMethods();
if(methods.length!=1){
log.error(
"Thechecker{}haszeroormorethanonemethods",
checker.getClass());
returnfalse;
}
finalMethodmethod=methods[0];
finalbooleaninconsistent;
try{
inconsistent=(boolean)method.invoke(checker,order);
}catch(InvocationTargetException|
IllegalAccessException|
ClassCastExceptione){
log.error("Callingthemethod{}onclass{}threwexception",
method,checker.getClass());
log.error("Theexceptionis",e);
returnfalse;
}
returninconsistent;
}
WecangettheclassoftheobjectbycallingthegetClassmethod,andontheobjectthatrepresentstheclassitself,wecancallgetDeclaredMethods.Fortunately,thecheckerclassesarenotlitteredbymanymethods,sowecheckthatthereisonlyonemethoddeclaredinthecheckerclass.NotethatthereisalsoagetMethodsmethodinthereflectionlibrarybutitalwayswillreturnmorethanonemethod.Itreturnsthedeclaredandtheinheritedmethods.Becauseeachandeveryclassinheritsfromjava.lang.Object,atleastthemethodsoftheObjectclasswillbethere.
Afterthis,wetrytoinvoketheclassusingtheMethodobjectthatrepresentsthemethodinthereflectionclass.NotethatthisMethodobjectisnotdirectlyattachedtoaninstance.Weretrievedthemethodfromtheclass,andthus,whenweinvokeit,weshouldpasstheobjectitshouldworkonasafirstparameter.Thisway,x.y(z),becomesmethod.invoke(x,z).ThelastparameterofinvokeisavariablenumberofargumentsthatarepassedasanObjectarray.Inmostcases,whenweinvokeamethod,weknowtheargumentsinourcodeevenifwedonotknowthenameofthemethodandhavetousereflection.Wheneventheargumentsarenotknownbutareavailableasamatterofcalculation,thenwehavetopassthemasanObjectarray.
Invokingamethodviareflectionisariskycall.Ifwetrytocallamethodthenormalway,whichisprivate,thenthecompilerwillsignalanerror.Ifthenumberofargumentsortypesarenotappropriate,thecompilerwillagainwillgiveusanerror.Ifthereturnedvalueisnotboolean,orthereisnoreturnvalueatall,thenweagaingetacompilererror.Inthecaseofreflection,thecompilerisclueless.Itdoesnotknowwhatmethodwewillinvokewhenthecodeisexecuting.Theinvokemethod,ontheotherhand,canandwillnoticeallthesefailureswhenitisinvoked.Ifanyoftheaforementionedproblemsoccur,thenwewillgetexceptions.Iftheinvokemethoditselfseesthatitcannotperformwhatweaskofit,thenitwillthrowInvocationTargetExceptionorIllegalAccessException.Iftheconversionfromtheactualreturnvaluetobooleanisnotpossible,thenwewillgetClassCastException.
Aboutdoingmagic,itisanaturalurgethatwefeellikemakingsomethingextraordinary,somethingoutstanding.Thisisokaywhenweareexperimentingwithsomething,doingahobbyjob.Ontheotherhand,thisisstronglynotokaywhenweareworkingonaprofessionaljob.Averageprogrammers,whodo
notunderstandyourbrilliantsolution,willmaintainthecodeinanenterpriseenvironment.Theywillturnyournicelycombedcodeintohaystackwhilefixingsomebugsorimplementingsomeminornewfeatures.EvenifyouaretheMozartofprogramming,theywillbe,atbest,no-namesingers.Abrilliantcodeinanenterpriseenvironmentcanbearequiem,withalltheimplicationsofthatmetaphor.
Lastbutnotleast,thesadrealityisthatweareusuallynottheMozartsofprogramming.
Notethatincasethereturnvalueoftheoriginalvalueisprimitive,thenitwillbeconvertedtoanobjectbyreflection,andthenwewillconvertitbacktotheprimitivevalue.Ifthemethoddoesnothaveareturnvalue,inotherwords,ifitisvoid,thenthereflectionwillreturnajava.lang.Voidobject.TheVoidobjectisonlyaplaceholder.Wecannotconvertittoanyprimitivevalueoranyothertypeofobjects.ItisneededbecauseJavaisstrictandinvokehastoreturnanObject,sotheruntimeneedssomethingthatitcanreturn.AllwecandoischeckthatthereturnedvalueclassisreallyVoid.
Let'sgoonwiththestorylineandoursolution.Wesubmittedthecodeanditworksinproductionforawhiletillanewupdatefromasoftwarevendorbreaksit.Wedebugthecodeinthetestenvironmentandseethattheclassnowcontainsmorethanonemethod.Ourdocumentationclearlystatesthattheyshouldonlyhaveonepublicmethod,andtheyprovidedacodethathas...hmm...werealizethattheothermethodsareprivate.Theyareright;theycanhaveprivatemethodsaccordingtothecontract,sowehavetoamendthecode.Wereplacethelinesthatlookuptheoneandonlymethod:
Method[]methods=checker.getClass().getDeclaredMethods();
if(methods.length!=1){
...
}
finalMethodmethod=methods[0];
Thenewcodewillbeasfollows:
finalMethodmethod=getSingleDeclaredPublicMethod(checker);
if(method==null){
log.error(
"Thechecker{}haszeroormorethanonemethods",
checker.getClass());
returnfalse;
}
Thenewmethodwewritetolookuptheoneandonlypublicmethodisasfollows:
privateMethodgetSingleDeclaredPublicMethod(
ConsistencyCheckerchecker){
finalMethod[]methods=
checker.getClass().getDeclaredMethods();
MethodsingleMethod=null;
for(Methodmethod:methods){
if(Modifier.isPublic(method.getModifiers())){
if(singleMethod!=null){
returnnull;
}
singleMethod=method;
}
}
returnsingleMethod;
}
Tocheckwhetherthemethodispublicornot,weuseastaticmethodfromtheModifierclass.Therearemethodstocheckallpossiblemodifiers.ThevaluethatthegetModifiersmethodreturnsisanintbitfield.
Differentbitshavedifferentmodifiersandthereareconstantsthatdefinethese.Thissimplificationleadstoinconsistency,whichyoucancheckifamethodisaninterfaceorvolatile,thatis,actuallynonsense.Thefactisthatbitsthatcanonlybeusedforothertypesofreflectionobjectswillneverbeset.
Thereisoneexception,whichisvolatile.Thisbitisreusedtosignalbridgemethods.Bridgemethodsarecreatedbythecompilerautomaticallyandcanhavedeepandcomplexissuesthatwedonotdiscussinthisbook.Thereuseofthesamebitdoesnotcauseconfusionbecauseafieldcanbevolatile,butasafield,itcannotbeabridgemethod.Obviously,afieldisafieldandnotamethod.Inthesameway,amethodcannotbeavolatilefield.Thegeneralruleis:donotusemethodsonreflectionobjectswheretheydonothaveameaning;orelse,knowwhatyoudo.
Makingthestorylineevenmoreintricate,anewversionofacheckeraccidentallyimplementsthecheckingmethodasapackageprivate.Theprogrammersimplyforgottousethepublickeyword.Forthesakeofsimplicity,let'sassumethattheclassesdeclareonlyonemethodagain,butitisnotpublic.Howdowesolvethisproblemusingreflection?
Obviously,thesimplestsolutionistoaskthevendorstofixtheproblem:itistheirfault.Insomecases,however,wemustcreateaworkaroundoversomeproblems.Thereisanothersolution:creatingaclasswithapublicmethodinthesamepackage,invokingthepackageprivatemethodsfromtheotherclass,thusrelayingtheotherclass.Asamatteroffact,thissolution,asaworkaroundforsuchabug,seemstobemorelogicalandcleaner,butthistime,wewanttousereflection.
Toavoidjava.lang.IllegalAccessException,wehavetosetthemethodobjectasaccessible.Todoso,wehavetoinsertthefollowinglineinfrontoftheinvocation:
method.setAccessible(true);
Notethatthiswillnotchangethemethodtopublic.Itwillonlymakethemethodaccessibleforinvocationthroughtheveryinstanceofthemethodobjectthatwesetasaccessible.
IhaveseencodethatcheckswhetheramethodisaccessibleornotbycallingtheisAccessiblemethodandsavesthisinformation;itsetsthemethodasaccessibleifitwasnotaccessibleandrestorestheoriginalaccessibilityaftertheinvocation.Thisistotallyuseless.Assoonasthemethodvariablegoesoutofscope,andthereisnoreferencetotheobjectwesettheaccessibilityflagto,theeffectofthesettingwearsoff.Also,thereisnopenaltyforsettingtheaccessibilityofapublicoranotherwisecallablemethod.
SettingfieldsWecanalsocallsetAccessibleonFieldobjectsandthenwecanevensetthevalueofprivatefieldsusingreflection.Withoutfurtherfakestories,justforthesakeoftheexample,let'smakeaConsistencyCheckernamedSettableChecker:
@Component
@PoweredDevice
publicclassSettableCheckerimplementsConsistencyChecker{
privatestaticfinalLoggerlog=LoggerFactory.getLogger(SettableChecker.class);
privatebooleansetValue=false;
publicbooleanisInconsistent(Orderorder){
returnsetValue;
}
}
Thischeckerwillreturnfalse,unlesswesetthefieldtotrueusingreflection.Wedosetitassuch.WecreateamethodintheCheckerclassandinvokeitfromthecheckingprocessforeachchecker:
privatevoidsetValueInChecker(ConsistencyCheckerchecker){
Field[]fields=checker.getClass().getDeclaredFields();
for(finalFieldfield:fields){
if(field.getName().equals("setValue")&&
field.getType().equals(boolean.class)){
field.setAccessible(true);
try{
log.info("Settingfieldtotrue");
field.set(checker,true);
}catch(IllegalAccessExceptione){
log.error("SNAFU",e);
}
}
}
}
ThemethodgoesthroughallthedeclaredfieldsandifthenameissetValueandthetypeisboolean,thenitsetsittotrue.Thiswillessentiallyrenderallordersthatcontainapowereddeviceasrejected.
Notethatalthoughbooleanisabuilt-inlanguageprimitive,whichisnotaclassbyanymeans,itstillhasaclasssothatreflectioncancomparethetypeofthefieldgainstheclassthatbooleanartificiallyhas.Nowboolean.classisaclassliteralinthelanguage,andforeachprimitive,asimilarconstantcanbeused.Thecompileridentifiestheseasclassliteralsandcreatestheappropriatepseudoclassreferencesinthebytecodesothatprimitivescanalsobecheckedinthisway,asdemonstratedinthesamplecodeofthesetValueInCheckermethod.
Wecheckedthatthefieldhastheappropriatetype,andwealsocalledthesetAccessiblemethodonthefield.EventhoughthecompilerdoesnotknowthatwereallydideverythingtoavoidIllegalAccessException,itstillbelievesthatcallingsetonfieldcanthrowsuchanexception,asitisdeclared.However,weknowthatitshouldnothappen.(Famouslastwordsofaprogrammer?)Tohandlethissituation,wesurroundthemethodcallwithatryblock,andinthecatchbranch,welogtheexception.
FunctionalprogramminginJavaSincewehavecreatedalotofcodeinourexampleforthischapter,wewilllookatthefunctionalprogrammingfeaturesofJava,whichwillhelpusdeletemanylinesfromourcode.Thelesscodewehave,theeasieritistomaintaintheapplication;thus,programmerslovefunctionalprogramming.Butthisisnottheonlyreasonwhyfunctionalprogrammingissopopular.Itisalsoanexcellentwaytodescribecertainalgorithmsinamorereadableandlesserrorpronemannerthanconventionalloops.
Functionalprogrammingisnotanewthing.Themathematicalbackgroundwasdevelopedforitinthe1930s.Oneofthefirst(ifnotthefirst)functionalprogramminglanguagesisLISP.Itwasdevelopedinthe1950sanditisstillinuse,somuchthatthereisaversionofthelanguageimplementedontheJVM(Clojure).
Functionalprogramming,inshort,meansthatweexpresstheprogramstructureintermsoffunctions.Inthismeaning,weshouldthinkoffunctionsasinmathematicsandnotasthetermisusedinprogramminglanguagessuchasC.InJava,wehavemethods,andwhenwearefollowingthefunctionalprogrammingparadigm,wecreateandusemethodsthatbehavelikemathematicalfunctions.Amethodisfunctionalifitgivesthesameresultnomatterhowmanytimesweinvokeit,justassin(0)isalwayszero.Functionalprogrammingavoidschangingthestateofobjects,andbecausethestateisnotchanging,theresultsarealwaysthesame.Thisalsoeasesdebugging.
Ifafunctionhasoncereturnedacertainvalueforthegivenarguments,itwillalwaysreturnthesamevalue.Wecanalsoreadthecodeasadeclarationofthecalculationmorethanascommandsthatareexecutedoneaftertheother.Iftheexecutionorderisnotimportant,thenthereadabilityofthecodemayalsoincrease.
Javahelpsfunctionalprogrammingstylewithlambdaexpressionsandstreams.NotethatthesestreamsarenotI/Ostreamsanddonotreallyhaveanyrelationtothose.
Wewillfirsttakeashortlookatlambdaexpressionsandwhatstreamsare,andthen,wewillconvertsomepartsofourprogramtousetheseprogrammingconstructs.Wewillalsoseehowmuchmorereadablethesecodesbecome.
Readabilityisadebatabletopic.Acodemaybereadabletoonedeveloperandmaybelessreadabletoanother.Itverymuchdependsonwhattheygotusedto.Iexperiencemanytimesthatdevelopersgetdistractedwithstreams.Whendevelopersfirstmeetstreams,thewaytothinkaboutthemandhowtheylookisjuststrange.Butthisisthesameasstartingtolearnusingabicycle.Whileyouarestilllearningitsuseandyoufallmorethanyouroll,itisdefinitelyslowerthanwalking.Ontheotherhand,onceyouhavelearnthowtorideabike...
LambdaWehavealreadyusedlambdaexpressionsinChapter3,OptimizingtheSort-MakingCodeProfessionalwhenwewrotetheexception-throwingtest.Inthatcode,wesetthecomparatortoaspecialvaluethatwasthrowingRuntimeExceptionateachinvocation:
sort.setComparator((Stringa,Stringb)->{
thrownewRuntimeException();
});
TheargumenttypeisComparator;therefore,whatwehavetosetthereshouldbeaninstanceofaclassthatimplementsthejava.util.Comparatorinterface.Thatinterfacedefinesonlyonemethodthatimplementationshavetodefine:compare.Thus,wecandefineitasalambdaexpression.Withoutlambda,ifweneedaninstance,wehavetotypealot.Wehavetocreateaclass,nameit,declarethecomparemethodinit,andwritethebodyofthemethod,asshowninthefollowingcodesegment:
publicclassExceptionThrowingComparatorimplementsComparator{
publicintcompare(To1,To2){
thrownewRuntimeException();
}
}
Atthelocationofuse,weshouldinstantiatetheclassandpassitasanargument:
sort.setComparator(newExceptionThrowingComparator());
Wemaysaveafewcharactersifwedefinetheclassasananonymousclassbuttheoverheadisstillthere.Whatwereallyneedisthebodyoftheoneandsinglemethodthatwehavetodefine.Thisiswherelambdacomesintothepicture.
Wecanusealambdaexpressioninanyplacewherewewouldotherwiseneedaninstanceofaclassthathastodefineonlyonemethod.ThemethodsthataredefinedandinheritedfromObjectdonotcount,andwealsodonotcareaboutthemethodsthataredefinedasdefaultmethodsintheinterface.Theyarethere.Lambdadefinestheonethatisnotyetdefined.Inotherwords,lambdaclearlydepicts,withmuchlessoverheadasananonymousclass,thatthevalueisafunctionalitythatwepassasaparameter.
Thesimpleformofalambdaexpressionisasfollows:
parameters->body
Theparameterscanbeenclosedbetweenparenthesesorcanonlystandwithout.Thebodysimilarlycanbeenclosedbetweenthe{and}charactersoritcanbeasimpleexpression.Thiswayalambdaexpressioncanreducetheoverheadtoaminimum,usingtheparenthesesonlywheretheyarereallyneeded.
Itisalsoanextremelyusefulfeatureoflambdaexpressionsthatwedonotneedtospecifythetypesoftheparametersincaseitisobviousfromthecontextwhereweusetheexpression.Thus,theprecedingcodesegmentcanevenbeshorter,asfollows:
sort.setComparator((a,b)->{
thrownewRuntimeException();
});
Theparameters,aandb,willhavethetypeasneeded.Tomakeitevensimpler,wecanalsoomitthe(and)charactersaroundtheparametersincasethereisonlyone.
Theparenthesesarenotoptionalifthereismorethanoneparameter.Thisistoavoidambiguityinsomesituations.Forexample,themethodcall,f(x,y->x+y)couldhavebeenamethodwithtwoarguments:xandalambdaexpressionthathasoneparameter,y.Atthesametime,itcouldalsobeamethodcallwithalambdaexpressionthathastwoparameters,xandy.
Lambdaexpressionsareveryhandywhenwewanttopassfunctionalityasanargument.Thedeclarationofthetypeofargumentattheplaceofthemethoddeclarationshouldbeafunctionalinterfacetype.Theseinterfacescanoptionallybeannotatedusing@FunctionalInterface.TheJavaruntimehasmanysuchinterfacesdefinedinthejava.util.functionpackage.Wewilldiscusssomeoftheminthenextsectionalongwiththeiruseinstreams.Fortherest,thestandardJavadocumentationisavailablefromOracle.
StreamsStreamswerealsonewinJava8,justlikelambdaexpressions.Theyworktogetherverystrongly,sotheirappearanceatthesametimeisnotasurprise.Lambdaexpressionsaswellasstreamssupportthefunctionalprogrammingstyle.
Theveryfirstthingtoclarifyisthatstreamsdonothaveanythingtodowithinputandoutputstreams,exceptthename.Theyaretotallydifferentthings.Streamsaremorelikecollectionswithsomesignificantdifferences.(Iftherewerenodifferences,theywouldjusthavebeencollections.)Streamsareessentiallypipelinesofoperationsthatcanrunsequentiallyorinparallel.Theyobtaintheirdatafromcollectionsorothersources,includingdatathatismanufacturedon-the-fly.
Streamssupporttheexecutionofthesamecalculationonmultipledata.ThisstructureisreferredtoasSingleInstructionMultipleData(SIMD).Don'tbeafraidoftheexpression.Thisisaverysimplething.Wehavealreadydonethatmanytimesinthisbook.LoopsarealsokindofSIMDstructures.Whenweloopthroughthecheckerclassestoseewhetheranyofthoseopposestheorder,weperformthesameinstructionforeachandeverychecker.Multiplecheckersaremultipledata.
Oneproblemwithloopsisthatwedefinetheorderofexecutionwhenitisnotneeded.Inthecaseofcheckers,wedonotreallycarewhatorderthecheckersareexecutedin.Allwecareaboutisthatallareokaywiththeorder.Westillspecifysomeorderwhenweprogramtheloop.Thiscomesfromthenatureofloops,andthereisnowaywecouldchangethat.Thatishowtheywork.However,itwouldbeniceifwecouldjust,somehow,say"dothisandthatforeachandeverychecker".Thisisonepointwherestreamscomeintothepicture.
Anotherpointisthatcodethatusesloopsismoreimperativeratherthandescriptive.Whenwereadtheprogramofaloopconstruct,wefocusontheindividualsteps.Wefirstseewhatthecommandsintheloopdo.Thesecommandsworkontheindividualelementsofthedataandnotonthewholecollectionorarray.
Laterputtingtheindividualstepstogetherinourbrainwerealizewhatthebigpictureis,whattheloopisfor.Inthecaseofstreams,thedescriptionofoperationsisalevelhigher.Oncewelearnthestreammethods,itiseasiertoreadthem.Streammethodsworkonthewholestreamandnotontheindividualelements,andthusaremoredescriptive.
java.lang.Streamisaninterface.Anobjectwithatypeimplementingthisinterfacerepresentsmanyobjectsandprovidesmethodsthatcanbeusedtoperforminstructionsontheseobjects.Theobjectsmayormaynotbeavailablewhenwestarttheoperationononeofthem,ormayjustbecreatedwhenneeded.ThisisuptotheactualimplementationoftheStreaminterface.Forexample,supposewegenerateastreamthatcontainsintvaluesusingthefollowingcode:
IntStream.iterate(0,(s)->s+1)
Intheprecedingcodesnippet,alltheelementscannotbegeneratedbecausethestreamcontainsaninfinitenumberofelements.Thisexamplewillreturnthenumbers0,1,2,andsoonuntilfurtherstreamoperations,whicharenotlistedhere,terminatethecalculation.
WhenweprogramStream,weusuallycreateastreamfromaCollection—notalways,butmanytimes.TheCollectioninterfacewasextendedinJava8toprovidethestreamandparallelStreammethods.Bothofthemreturnstreamobjectsthatrepresenttheelementsofthecollection.Whilestreamreturnstheelementsinthesameorderastheyareinthecollectionincasethereisanaturalorder,theparallelStreamcreatesastreamthatmaybeworkedoninaparallelmanner.Inthiscase,ifsomeofthemethodsthatweuseonthestreamareimplementedinthatway,thecodecanusethemultipleprocessorsavailableinthecomputer.
Assoonaswehaveastream,wecanusethemethodsthattheStreaminterfacedefines.TheonetostartwithisforEach.Thismethodhasoneargument,whichisusuallyprovidedasalambdaexpressionandwillexecutethelambdaexpressionforeachelementofthestream.
IntheCheckerclass,wehavetheisConsistentmethod.Inthismethod,thereisaloopthatgoesthroughtheannotationsofthecheckerclass.Ifwewantedtologtheinterfacesthattheannotationintheloopimplements,wecouldaddthefollowing:
for(ConsistencyCheckerchecker:checkers){
for(Annotationannotation:
checker.getClass().getAnnotations()){
Arrays.stream(annotation.getClass().getInterfaces())
.forEach(
t->log.info("annotationimplementedinterfaces{}",t)
);
...
Inthisexample,wecreateastreamfromanarrayusingthefactorymethodfromtheArraysclass.Thearraycontainstheinterfacesreturnedbythereflectionmethod,getInterfaces.Thelambdaexpressionhasonlyoneparameter;thus,wedonotneedtouseparenthesesaroundit.Thebodyoftheexpressionisamethodcallthatreturnsnovalue;thus,wealsoomitthe{and}characters.
Whyallthishassle?Whatisthegain?Whycouldn'twejustwriteasimpleloopthatlogstheelementsofthearray?Thegainsarereadabilityandmaintainability.Whenwecreateaprogram,wehavetofocusonwhattheprogramshoulddoandnotonhowitshoulddoit.Inanidealworld,aspecificationwouldjustbeexecutable.Wemayactuallygetthereinthefuturewhenprogrammingworkwillbereplacedbyartificialintelligence.(Nottheprogrammers,though.)Wearenotthere,yet.Wehavetotellthecomputershowtodowhatwewanttoachieve.WeusedtohavetoenterbinarycodesontheconsoleofPDP-11togetmachinecodedeployedintothememorytohaveitexecuted.Later,wehadassemblers;stilllater,wehadFORTRANandotherhigh-levelprogramminglanguagesthathavereplacedmuchoftheprogrammingworkasitwas40yearsago.Allthesedevelopmentsinprogrammingshiftthedirectionfromhowtowardswhat.Today,weprograminJava9,andtheroadstillhasmilestogo.Themorewecanexpresswhattodoinsteadofhowtodo,theshorterandmoreunderstandableourprogramswillbe.Itwillcontaintheessenceandnotsomeartificiallitterthatisneededbythemachinestojustdowhatwewant.WhenIseealoopinacodeIhavetomaintain,Iassumethatthereissomeimportanceoftheorderinwhichtheloopisexecuted.Theremaybenoimportanceatall.Itmaybeobviousafterafewseconds.Itmayneedminutesormoretorealizethattheorderingisnotimportant.Thistimeiswastedandcanbesavedwithprogrammingconstructsthat
betterexpressthewhattodopartinsteadofthehowtodo.
FunctionalinterfacesTheargumenttothemethodshouldbejava.util.function.Consumer.Thisisaninterfacethatrequirestheacceptmethodtobedefined,andthismethodisvoid.Thelambdaexpressionoraclassthatimplementsthisinterfacewillconsumetheargumentoftheacceptmethodanddoesnotproduceanything.
Thereareseveralotherinterfacesdefinedinthatpackage,eachservingasafunctionalinterfaceusedtodescribesomemethodargumentsthatcanbegivenaslambdaexpressionsintheactualparameters.
Forexample,theoppositeofConsumerisSupplier.ThisinterfacehasamethodnamedgetthatdoesnotneedanyargumentbutgivessomeObjectasareturnvalue.
Ifthereisanargumentandalsoareturnedvalue,theinterfaceiscalledFunction.Ifthereturnedvaluehastobethesametypeastheargument,thentheUnaryOperatorinterfaceisourfriend.Similarly,thereisaBinaryOperatorinterface,whichreturnsanobjectofthesametypeasthearguments.JustaswegotfromFunctiontoUnaryOperator,wecanseethatintheotherdirection,thereisalsoBiFunctionincasetheargumentsandthereturnvaluesdonotsharethetype.
Theseinterfacesarenotdefinedindependentlyofeachother.IfamethodrequiresFunctionandwehaveUnaryOperatortopass,itshouldnotbeaproblem.UnaryOperatorisnothingelsebutFunctionthathasthesametypeofarguments.AmethodthatcanworkwithFunction,whichacceptsanobjectandreturnsanobject,shouldnothaveaproblemiftheyhavethesametype.Thosecanbe,butneednotbe,different.Toletthathappen,theUnaryOperatorinterfaceextendsFunctionandthuscanbeusedintheplaceofFunction.
Theinterfacesinthisclasswemetsofararedefinedusinggenerics.Becausegenerictypescannotbeprimitives,theinterfacesthatoperateonprimitivevaluesshouldbedefinedseparately.Predicate,forexample,isaninterfacethatdefinesbooleantest(Tt).Itisafunctionthatreturnsabooleanvalueandisusedmanytimesinstreammethods.
Therearealsointerfaces,suchasBooleanSupplier,DoubleConsumer,DoubleToIntFunction,andmore,thatworkwithprimitiveboolean,double,andint.Thenumberofpossiblecombinationsofthedifferentargumenttypesandreturnvaluesisinfinite...almost.
Funfact:Tobeveryprecise,itisnotinfinite.Amethodcanhaveatmost254arguments.ThislimitisspecifiedintheJVMandnotintheJavalanguagespecification.Ofcourse,oneisuselesswithouttheother.Thereare8primitivetypes(plusObject,plusthepossibilitythattherearelessthan254arguments),whichmeansthatthetotalnumberofpossiblefunctionalinterfacesis10254,giveortake,afewmagnitudes.Practically,infinite!
WeshouldnotexpecttohaveallthepossibleinterfacesdefinedintheJDKinthispackage.Theseareonlythoseinterfacesthatarethemostuseful.Thereisnointerface,forexample,thatusesshortorchar.Ifweneedanythinglikethat,thenwecandefinetheinterfaceinourcode.Orjustthinkhardandfindouthow
touseanalreadydefinedone.(Ihaveneverusedtheshorttypeduringmyprofessionalcarrier.Itwasneverneeded.)
Howarethesefunctionalinterfacesusedinstreams?TheStreaminterfacedefinesthemethodsthathavesomefunctionalinterfacetypesasarguments.Forexample,theallMatchmethodhasaPredicateargumentandreturnsaBooleanvalue,whichistrueifalltheelementsinthestreammatchPredicate.Inotherwords,thismethodreturnstrueifandonlyifPredicate,suppliedasanargument,returnstrueforeachandeveryelementofthestream.
Inthefollowingcode,wewillrewritesomeofthemethodsthatweimplementedinoursamplecodeusingloopstousestreams,andthroughtheseexamples,wewilldiscussthemostimportantmethodsthatstreamsprovide.Wesaveduptwoclasses,ProductsCheckerCollectorandProductInformationCollector,todemonstratethestreamusage.Wecanstartwiththese.ProductsCheckerCollectorgoesthroughalltheproductsthatarecontainedintheOrderandcollectstheannotationsthatarelistedintheproducts.Eachproductmaycontainzero,one,ormanyannotations.Theseareavailableinalist.Thesameannotationmaybereferencedmultipletimes.Toavoidduplicates,weuseHashSet,whichwillcontainonlyoneinstanceoftheelementseveniftherearemultipleinstancesintheproducts:
publicclassProductsCheckerCollector{
privatefinalProductInformationCollectorpic;
publicProductsCheckerCollector(@Autowired
ProductInformationCollectorpic){this.pic=pic;}
publicSet<Class<?extendsAnnotation>>
getProductAnnotations(Orderorder){
Map<OrderItem,ProductInformation>piMap=
pic.collectProductInformation(order);
finalSet<Class<?extendsAnnotation>>
annotations=newHashSet<>();
for(OrderItemitem:order.getItems()){
finalProductInformationpi=piMap.get(item);
if(pi!=null&&pi.getCheck()!=null){
for(Class<?extendsAnnotation>check:
pi.getCheck()){
annotations.addAll(pi.getCheck());
}
}
returnannotations;
}
}
Now,let'sseehowthismethodlookswhenwerecodeitusingstreams:
publicSet<Class<?extendsAnnotation>>
getProductAnnotations(Orderorder){
Map<OrderItem,ProductInformation>piMap=
pic.collectProductInformation(order);
returnorder.getItems().stream()
.map(piMap::get)
.filter(Objects::nonNull)
.peek(pi->{
if(pi.getCheck()==null){
log.info("Product{}hasnoannotation",
pi.getId());
}
})
.filter(pi->pi.getCheck()!=null)
.peek(pi->log.info("Product{}isannotatedwithclass{}",pi.getId(),pi.getCheck()))
.flatMap(pi->pi.getCheck().stream())
.collect(Collectors.toSet());
}
Themajorworkofthemethodgetsintoasingle,thoughhuge,streamexpression.Wewillcovertheelementsoftheexpressioninthecomingpages.Listreturnedbyorder.getItemsisconvertedcallingthestreammethod:
returnorder.getItems().stream()
Aswehavealreadymentioneditbriefly,thestreammethodispartoftheCollectioninterface.AnyclassthatimplementstheCollectioninterfacewillhavethismethod,eventhosethatwereimplementedbeforestreamswereintroducedinJava8.Thisisbecausethestreammethodisimplementedintheinterfaceasadefaultmethod.Thisway,ifwehappentoimplementaclassimplementingthisinterface,evenifwedonotneedstreams,wegetitforfreeasanextra.
ThedefaultmethodsinJava8wereintroducedtosupportbackwardcompatibilityofinterfaces.SomeoftheinterfacesoftheJDKweretobemodifiedtosupportlambdaandfunctionalprogramming.Oneexampleisthestreammethod.Withthepre-Java8featureset,theclassesimplementingsomeofthemodifiedinterfacesshouldhavebeenmodified.Theywouldhavebeenrequiredtoimplementthenewmethod.Suchachangeisnotbackwardcompatible,andJavaasalanguageandJDKwaspayingkeenattentiontobebackwardcompatible.Thus,defaultmethodswereintroduced.Theseletadeveloperextendaninterfaceandstillkeepitbackwardcompatible,providingadefaultimplementationforthemethods,whicharenew.Contrarytothisphilosophy,brandnewfunctionalinterfacesofJava8JDKalsohavedefaultmethods,though,havingnopriorversionintheJDK,theyhavenothingtobecompatiblewith.InJava9,interfaceswerealsoextendedandnowtheycancontainnotonlydefaultandstaticmethodsbutalsoprivatemethods.Thisway,interfacesbecamekindofequivalenttoabstractclasses,thoughtherearenofieldsinaninterfaceexceptconstantstaticfields.Thisinterfacefunctionalityopenupisamuchcriticizedfeature,whichjustposestheprogrammingstyleandstructuralissuesthatotherlanguagesallowingmultipleclassinheritanceface.JavawasavoidingthistillJava8andJava9.Whatisthetake-awayfromthis?Becarefulwithdefaultmethodsandalsowithprivatemethodsininterfaces.Usethemwiselyifatall.
TheelementsofthisstreamareOrderItemobjects.WeneedProductInformationforeachOrderItem.
MethodreferencesLuckythatwehaveMap,whichpairsorderitemswithproductinformation,sowecaninvokegetonMap:
.map(piMap::get)
ThemapmethodisagainsomethingthathasthesamenameassomethingelseinJavaandshouldnotbeconfused.WhiletheMapclassisadatastructure,themapmethodintheStreaminterfaceperformsmappingofthestreamelements.TheargumentofthemethodisaFunction(recallthatthisisafunctionalinterfacewerecentlydiscussed).Thisfunctionconvertsavalue,T,whichisavailableastheelementoftheoriginalstream(Stream<T>)toavalue,R,andthereturnvalueofthemapmethodisStream<R>.ThemapmethodconvertsStream<T>toStream<R>usingthegivenFunction<T,R>,callingitforeachelementoftheoriginalstreamandcreatinganewstreamfromtheconvertedelements.
WecansaythattheMapinterfacemapskeystovaluesinadatastructureinastaticway,andtheStreammethod,map,mapsonetypeofvaluestoanother(orthesame)typeofvaluesdynamically.
Wehavealreadyseenthatwecanprovideaninstanceofafunctionalinterfaceintheformofalambdaexpression.Thisargumentisnotalambdaexpression.Thisisamethodreference.ItsaysthatthemapmethodshouldinvokethegetmethodonMappiMapusingtheactualstreamelementasanargument.Weareluckythatgetalsoneedsoneargument,aren'twe?Wecouldalsowriteasfollows:
.map(orderItem->piMap.get(orderItem))
However,thiswouldhavebeenexactlythesameaspiMap::get.
Thisway,wecanreferenceaninstancemethodthatworksonacertaininstance.Inourexample,theinstanceistheonereferencedbythepiMapvariable.Itisalsopossibletoreferencestaticmethods.Inthiscase,thenameoftheclassshouldbewritteninfrontofthe::characters.Wewillsoonseeanexampleofthiswhenwewillusethestaticmethod,nonNull,fromtheObjectsclass(notethattheclassnameisinplural,anditisinthejava.utilpackageandnotjava.lang).
Itisalsopossibletoreferenceaninstancemethodwithoutgivingthereferenceonwhichitshouldbeinvoked.Thiscanbeusedinplaceswherethefunctionalinterfacemethodhasanextrafirstparameter,whichwillbeusedastheinstance.WehavealreadyusedthisinChapter3,OptimizingtheSort-MakingCodeProfessional,whenwepassedString::compareTo,whentheexpectedargumentwasaComparator.ThecompareTomethodexpectsoneargument,butthecomparemethodintheComparatorinterfaceneedstwo.Insuchasituation,thefirstargumentwillbeusedastheinstanceonwhichcomparehastobeinvokedandthesecondargumentispassedtocompare.Inthiscase,String::compareToisthesameaswritingthelambdaexpression(Stringa,Stringb)->a.compareTo(b).
Lastbutnotleast,wecanusemethodreferencestoconstructors.WhenweneedaSupplierof(let'sbesimple)Object,wecanwriteObject::new.
Thenextstepistofilteroutthenullelementsfromthestream.Notethat,atthispoint,thestreamhasProductInformationelements:
.filter(Objects::nonNull)
ThefiltermethodusesPredicateandcreatesastreamthatcontainsonlytheelementsthatmatchthepredicate.Inthiscase,weusedthereferencetoastaticmethod.Thefiltermethoddoesnotchangethetypeofstream.Itonlyfiltersouttheelements.
Thenextmethodweapplyisabitanti-functional.Purefunctionalstreammethodsdonotalterthestateofanyobject.Theycreatenewobjectsthattheyreturnbut,otherthanthat,thereisnosideeffect.peekitselfisnodifferentbecauseitonlyreturnsastreamofthesameelementsastheoneitisappliedon.However,thisno-operationfeatureluresthenoviceprogrammertodosomethingnon-functionalandwritecodewithside-effects.Afterall,whyuseitifthereisno(side)effectincallingit?
.peek(pi->{
if(pi.getCheck()==null){
log.info("Product{}hasnoannotation",pi.getId());
}
})
Whilethepeekmethoditselfdoesnothaveanysideeffects,theexecutionofthelambdaexpressionmayhave.However,thisisalsotrueforanyoftheothermethods.Itisjustthefactthat,inthiscase,itismoretemptingtodosomethinginadequate.Don't.Wearedisciplinedadults.Asthenameofthemethodsuggests,wemaypeekintothestreambutwearenotsupposedtodoanythingelse.Withprogrammingbeingaparticularactivity,inthiscase,peeking,isadequate.Andthatiswhatweactuallydoinourcode:welogsomething.
Afterthis,wegetridoftheelementsthathavenoProductInformation;wealsowanttogetridoftheelementsthathave,butthereisnocheckerdefined:
.filter(pi->pi.getCheck()!=null)
Inthiscase,wecannotusemethodreferences.Instead,weusealambdaexpression.Asanalternativesolution,wemaycreateabooleanhasCheckmethodinProductInformation,whichreturnstrueiftheprivatefieldcheckisnotnull.Thiswouldthenreadasfollows:
.filter(ProductInformation::hasCheck)
Thisistotallyvalidandworks,althoughtheclassdoesnotimplementanyfunctionalinterfaceandhasmanymethods,notonlythisone.However,themethodreferenceisexplicitandspecifieswhichmethodtoinvoke.
Afterthissecondfilter,welogtheelementsagain:
.peek(pi->log.info(
"Product{}isannotatedwithclass{}",pi.getId(),
pi.getCheck()))
ThenextmethodisflatMapandthisissomethingspecialandnoteasytocomprehend.Atleastforme,itwasabitmoredifficultthanunderstandingmapandfilterwhenIlearnedfunctionalprogramming:
.flatMap(pi->pi.getCheck().stream())
Thismethodexpectsthatthelambda,methodreference,orwhateverispassedtoitasanargument,
createsawholenewstreamofobjectsforeachelementoftheoriginalstreamthemethodisinvokedon.Theresultis,however,notastreamofstreams,whichalsocouldbepossible,butratherthereturnedstreamsareconcatenatedintoonehugestream.
Ifthestreamweapplyittoisastreamofintegernumbers,suchas1,2,3,...,andthefunctionforeachnumbernreturnsastreamofthreeelementsn,n+1,andn+2,thentheresultingstream,flatMap,producesastreamcontaining1,2,3,2,3,4,3,4,5,4,5,6,andsoon.
Finally,thestreamwehaveshouldbecollectedtoaSet.Thisisdonebycallingthecollectormethod:
.collect(Collectors.toSet());
Theargumenttothecollectormethodis(againanameoveruse)Collector.Itcanbeusedtocollecttheelementsofthestreamintosomecollection.NotethatCollectorisnotafunctionalinterface.Youcannotjustcollectsomethingusingalambdaorasimplemethod.Tocollecttheelements,wedefinitelyneedsomeplacewheretheelementsarecollectedastheever-newerelementscomefromthestream.TheCollectorinterfaceisnotsimple.Fortunately,thejava.util.streams.Collectorsclass(againnotetheplural)hasalotofstaticmethodsthatcreateandreturnObjectthatcreateandreturnCollectorobjects.
OneoftheseistoSet,whichreturnsaCollectorthathelpscollecttheelementsofthestreamintoaSet.ThecollectmethodwillreturntheSetwhenalltheelementsarethere.Thereareothermethodsthathelpcollectthestreamelementsbysumminguptheelements,calculatingtheaverage,ortoaList,Collection,ortoaMap.CollectingelementstoaMapisaspecialthing,sinceeachelementofaMapisactuallyakey-valuepair.WewillseetheexampleforthatwhenwelookatProductInformationCollector.
TheProductInformationCollectorclasscodecontainsthecollectProductInformationmethod,whichwewillusefromtheCheckerclassaswellasfromtheProductsCheckerCollectorclass:
privateMap<OrderItem,ProductInformation>map=null;
publicMap<OrderItem,ProductInformation>
collectProductInformation(Orderorder){
if(map==null){
map=newHashMap<>();
for(OrderItemitem:order.getItems()){
finalProductInformationpi=
lookup.byId(item.getProductId());
if(!pi.isValid()){
map=null;
returnnull;
}
map.put(item,pi);
}
}
returnmap;
}
ThesimpletrickistostorethecollectedvalueinMap,andifthatisnotnull,thenjustreturnthealreadycalculatedvalue,whichmaysavealotofservicecallsincasethismethodiscalledmorethanoncehandlingthesameHTTPrequest.
Therearetwowaysofcodingsuchastructure.Oneischeckingthenon-nullityoftheMapandreturningiftheMapisalreadythere.Thispatterniswidelyusedandhasaname.Thisiscalledguardingif.Inthiscase,thereismorethanonereturnstatementinthemethod,
whichmaybeseenasaweaknessoranti-pattern.Ontheotherhand,thetabulationofthemethodisonetabshallower.Itisamatteroftasteandincaseyoufindyourselfinthemiddleofadebateaboutoneortheothersolution,justdoyourselfafavorandletyourpeerwinonthistopicandsaveyourstaminaformoreimportantissues,forexample,whetheryoushouldusestreamsorjustplainoldloops.
Now,let'sseehowwecanconvertthissolutionintoafunctionalstyle:
publicMap<OrderItem,ProductInformation>collectProductInformation(Orderorder){
if(map==null){
map=
order.getItems()
.stream()
.map(item->tuple(item,item.getProductId()))
.map(t->tuple(t.r,lookup.byId((String)t.s)))
.filter(t->((ProductInformation)t.s).isValid())
.collect(
Collectors.toMap(t->(OrderItem)t.r,
t->(ProductInformation)t.s
)
);
if(map.keySet().size()!=order.getItems().size()){
log.error("Someoftheproductsintheorderdonothaveproductinformation,{}!={}",map.keySet().size(),order.getItems().size());
map=null;
}
}
returnmap;
}
Weuseahelperclass,Tuple,whichisnothingbuttwoObjectinstancesnamedrands.Wewilllistthecodeforthisclasslater.Itisverysimple.
Inthestreamsexpression,wefirstcreatethestreamfromthecollection,andthenwemaptheOrderItemelementstoastreamofOrderItemandproductIdtuples.ThenwemapthesetuplestotuplesthatnowcontainOrderItemandProductInformation.Thesetwomappingscouldbedoneinonemappingcall,whichwouldperformthetwostepsonlyinone.Idecidedtocreatethetwotohavesimplerstepsineachlineinavainhopethattheresultingcodewillbeeasiertocomprehend.
Thefilterstepisalsonothingnew.Itjustfiltersoutinvalidproductinformationelements.Thereshouldactuallybenone.IthappensiftheordercontainsanorderIDtoanon-existentproduct.Thisischeckedinthenextstatementwhenwelookatthenumberofcollectedproductinformationelementstoseethatalltheitemshaveproperinformation.
TheinterestingcodeishowwecollecttheelementsofthestreamintoaMap.Todoso,weagainusethecollectmethodandalsotheCollectorsclass.Thistime,thetoMapmethodcreatestheCollector.ThisneedstwoFunctionresultingexpressions.ThefirstoneshouldconverttheelementofthestreamtothekeyandthesecondshouldresultinthevaluetobeusedintheMap.Becausetheactualtypeofthekeyandthevalueiscalculatedfromtheresultofthepassedlambdaexpressions,weexplicitlyhavetocastthefieldsofthetupletotheneededtypes.
Finally,thesimpleTupleclassisasfollows:
publicclassTuple<R,S>{
finalpublicRr;
finalpublicSs;
privateTuple(Rr,Ss){
this.r=r;
this.s=s;
}
publicstatic<R,S>Tupletuple(Rr,Ss){
returnnewTuple<>(r,s);
}
}
Therearestillsomeclassesinourcodethatdeservetobeconvertedtofunctionalstyle.ThesearetheCheckerandCheckerHelperclasses.
IntheCheckerclass,wecanrewritetheisConsistentmethod:
publicbooleanisConsistent(Orderorder){
Map<OrderItem,ProductInformation>map=
piCollector.collectProductInformation(order);
if(map==null){returnfalse;}
finalSet<Class<?extendsAnnotation>>annotations=
pcCollector.getProductAnnotations(order);
return!checkers.stream().anyMatch(
checker->Arrays.stream(
checker.getClass().getAnnotations()
).filter(
annotation->
annotations.contains(
annotation.annotationType())
).anyMatch(
x->
checker.isInconsistent(order)
));
}
Sinceyouhavealreadylearntmostoftheimportantstreammethods,thereishardlyanynewissuehere.WecanmentiontheanyMatchmethod,whichwillreturntrueifthereisatleastoneelementsothatthePredicateparameterpassedtoanyMatchistrue.Itmayalsoneedsomeaccommodationsothatwecoulduseastreaminsideanotherstream.Itverywellmaybeanexamplewhenastreamexpressionisovercomplicatedandneedstosplitupintosmallerpiecesusinglocalvariables.
Finally,beforeweleavethefunctionalstyle,werewritethecontainsOneOfmethodintheCheckHelperclass.Thiscontainsnonewelementsandwillhelpyoucheckwhatyouhavelearnedaboutmap,filter,flatMap,andCollector.Notethatthismethod,aswediscussed,returnstrueifordercontainsatleastoneoftheorderIDsgivenasstrings:
publicbooleancontainsOneOf(String...ids){
returnorder.getItems().stream()
.map(OrderItem::getProductId)
.flatMap(itemId->Arrays.stream(ids)
.map(id->tuple(itemId,id)))
.filter(t->Objects.equals(t.s,t.r))
.collect(Collectors.counting())>0;
}
WecreatethestreamoftheOrderItemobjects,andthenwemapittoastreamoftheIDsoftheproductscontainedinthestream.ThenwecreateanotherstreamforeachoftheIDswiththeelementsoftheIDandoneofthestringIDsgivenastheargument.Then,weflattenthesesubstreamsintoonestream.Thisstreamwillcontainorder.getItems().size()timesids.lengthelements:allpossiblepairs.WewillfilteroutthosepairsthatcontainthesameIDtwice,andfinally,wewillcountthenumberofelementsinthestream.
ScriptinginJava9Wearealmostreadywithoursampleprogramforthischapter.Thereisoneissue,thoughitisnotprofessional.Whenwehaveanewproductthatneedsanewchecker,wehavetocreateanewreleaseofthecode.
Programsinprofessionalenvironmentshavereleases.Whenthecodeismodified,bugsarefixed,oranewfunctionisimplemented,therearenumerousstepsthattheorganizationrequiresbeforetheapplicationcangointoproduction.Thesestepscomposethereleaseprocess.Someenvironmentshavelightweightreleaseprocesses;othersrequirerigorousandexpensivechecks.Itisnotbecauseofthetasteofthepeopleintheorganization,though.Whenthecostofanon-workingproductioncodeislowanditdoesnotmatterifthereisanoutageorwrongfunctioningintheprogram,thenthereleaseprocesscanbesimple.Thisway,releasesgetoutfasterandcheaper.Anexamplecanbesomechatprogramthatisusedforfunbytheusers.Insuchasituation,itmaybemoreimportanttoreleasenewfancyfeaturesthanensuringbug-freeworking.Ontheotherendofthepalette,ifyoucreatecodethatcontrolsanatomicpowerplant,thecostoffailurecanbeprettyhigh.Serioustestingandcarefulcheckingofallthefeatures,evenafterthesmallestchange,canpayoff.
Inourexample,simplecheckersmaybeanareathatisnotlikelytoinduceseriousbugs.Itisnotimpossiblebutthecodeissosimple...Yes,Iknowthatsuchanargumentisabitfishy,butlet'sassumethatthesesmallroutinescouldbechangedwithlesstestingandinaneasierwaythantheotherpartsofthecode.Howtoseparatethecodefortheselittlescripts,then,sothattheydonotrequireatechnicalrelease,anewversionoftheapplication,andnotevenrestartingtheapplication?Wehaveanewproductthatneedsanewcheckandwewanttohavesomewaytoinjectthischeckintotheapplicationenvironmentwithoutanyservicedisruption.
Thesolutionwechooseisscripting.JavaprogramscanexecutescriptswritteninJavaScript,Groovy,Jython(whichistheJVMversionofthelanguagePython),andmanyotherlanguages.ExceptJavaScript,thelanguageinterpretersoftheselanguagesarenotapartoftheJDK,buttheyallprovideastandardinterface,whichisdefinedintheJDK.Theconsequenceisthatwecanimplementscriptexecutioninourcodeandthedevelopers,whoprovidethescripts,arefreetochooseanyoftheavailablelanguages;wedonotneedtocaretoexecuteaJavaScriptcode.WewillusethesameAPIastoexecuteGroovyorJython.Theonlythingweshouldknowiswhatlanguagethescriptisin.Thisisusuallysimple:wecanguessthatfromthefileextension,andifguessingisnotenough,wecandemandthatthescriptdevelopersputJavaScriptintofileswiththe.jsextension,Jythonintofileswith.jyor.py,Groovyintofileswith.groovy,andsoon.Itisalsoimportanttonotethatifwewantourprogramtoexecuteoneoftheselanguages,weshouldmakesurethattheinterpreterisontheclasspath.InthecaseofJavaScript,thisisgiven;therefore,asademonstrationinthischapter,wewillwriteourscriptsinJavaScript.Therewillnotbealot;thisisaJavabookandnotaJavaScriptbookafterall.
Scriptingisusuallyagoodchoicewhenwewanttopasstheabilityofprogrammaticallyconfiguringorextendingourapplication.Thisisourcasenow.
Thefirstthingwehavetodoistoextendtheproductioninformation.Incasethereisascriptthatcheckstheconsistencyofanorderthataproductisin,weneedafieldwherewecanspecifythenameofthe
script:
privateStringcheckScript;
publicStringgetCheckScript(){
returncheckScript;
}
publicvoidsetCheckScript(StringcheckScript){
this.checkScript=checkScript;
}
Wedonotwanttospecifymorethanonescriptperproduct;therefore,wedonotneedalistofscriptnames.Wehaveonlyonescriptspecifiedbythename.
Tobehonest,thedatastructureforthecheckerclassesandtheannotations,allowingmultipleannotationsperproductandalsopercheckerclass,wastoocomplicated.Wecouldnotavoidthat,though,tohaveacomplexenoughstructurethatcoulddemonstratethepowerandcapabilityofstreamexpressions.Nowthatweareoverthatsubject,wecangoonusingsimplerdatastructuresfocusingonscriptexecution.
WealsohavetomodifytheCheckerclasstonotonlyusethecheckerclassesbutalsothescripts.Wecannotthrowawaythecheckerclassesbecause,bythetimewerealizethatwebetterneedscriptsforthepurpose,wealreadyhavealotofcheckerclassesandwehavenofinancingtorewritethemtobescripts.Wellyes,weareinabookandnotinreallife,butinanenterprise,thatwouldbethecase.Thatiswhyyoushouldbeverycarefulwhiledesigningsolutionsforacorporate.Thestructuresandthesolutionswillbethereforalongtimeanditisnoteasytothrowapieceofcodeoutjustbecauseitistechnicallynotthebest.Ifitworksandisalreadythere,thebusinesswillbeextremelyreluctanttospendmoneyoncodemaintenanceandrefactoring.
Summary:wemodifytheCheckerclass.Weneedanewclassthatcanexecuteourscripts;thus,theconstructorismodified:
privatefinalCheckerScriptExecutorexecutor;
publicChecker(
@AutowiredCollection<ConsistencyChecker>checkers,
@AutowiredProductInformationCollectorpiCollector,
@AutowiredProductsCheckerCollectorpcCollector,
@AutowiredCheckerScriptExecutorexecutor){
this.checkers=checkers;
this.piCollector=piCollector;
this.pcCollector=pcCollector;
this.executor=executor;
}
WealsohavetousethisexecutorintheisConsistentmethod:
publicbooleanisConsistent(Orderorder){
finalMap<OrderItem,ProductInformation>map=
piCollector.collectProductInformation(order);
if(map==null){
returnfalse;
}
finalSet<Class<?extendsAnnotation>>annotations=
pcCollector.getProductAnnotations(order);
Predicate<Annotation>annotationIsNeeded=annotation->
annotations.contains(annotation.annotationType());
Predicate<ConsistencyChecker>productIsConsistent=
checker->
Arrays.stream(checker.getClass().getAnnotations())
.parallel().unordered()
.filter(annotationIsNeeded)
.anyMatch(
x->checker.isInconsistent(order));
finalbooleancheckersSayConsistent=!checkers.stream().
anyMatch(productIsConsistent);
finalbooleanscriptsSayConsistent=
!map.values().
parallelStream().
map(ProductInformation::getCheckScript).
filter(Objects::nonNull).
anyMatch(s->
executor.notConsistent(s,order));
returncheckersSayConsistent&&scriptsSayConsistent;
}
Notethatinthiscode,weuseparallelstreamsbecause,whynot?Wheneveritispossible,wecanuseparallelstreams,evenunordered,totelltheunderlyingsystemandalsototheprogrammerfellowsmaintainingthecodethatorderisnotimportant.
WealsomodifyoneofourproductJSONfilestoreferenceascriptinsteadofacheckerclassthroughsomeannotation:
{
"id":"124",
"title":"DeskLamp",
"checkScript":"powered_device",
"description":"thisisalampthatstandsonmydesk",
"weight":"600",
"size":["300","20","2"]
}
EvenJSONissimpler.NotethataswedecidedtouseJavaScript,wedonotneedtospecifythefilenameextensionwhenwenamethescript.
Wemaylaterconsiderfurtherdevelopmentwhenwewillallowtheproductcheckerscriptmaintainerstousedifferentscriptinglanguages.Insuchacase,wemaystillrequirethattheyspecifytheextension,andincasethereisnoextension,itwillbeaddedbyourprogramas.js.Inourcurrentsolution,wedonotcheckthat,butwemaydevoteafewsecondstothinkaboutittobesurethatthesolutioncanbefurtherdeveloped.Itisimportantthatwedonotdevelopextracodeforthesakeoffurtherdevelopment.Developersarenotfortunetellersandcannottellreliablywhatthefutureneedswillbe.Thatisthetaskofthebusinesspeople.
Weputthescriptintotheresourcedirectoryofourprojectunderthescriptsdirectory.Thenameofthefilehastobepowered_device.jsbecausethisisthenamewespecifiedintheJSONfile:
functionisInconsistent(order){
isConsistent=false
items=order.getItems()
for(iinitems){
item=items[i]
print(item)
if(item.getProductId()=="126"||
item.getProductId()=="127"||
item.getProductId()=="128"){
isConsistent=true
}
}
return!isConsistent
}
ThisisanextremelysimpleJavaScriptprogram.Asasidenote,whenyouiterateoveralistoranarrayinJavaScript,theloopvariablewilliterateovertheindexesofthecollectionorthearray.SinceIrarelyprograminJavaScript,IfellintothistrapandittookmemorethanhalfanhourtodebugtheerrorImade.
Wehavepreparedeverythingweneedtocallthescript.Westillhavetoinvokeit.Todoso,weusetheJDKscriptingAPI.First,weneedaScriptEngineManager.ThismanagerisusedtogetaccesstotheJavaScriptengine.AlthoughtheJavaScriptinterpreterhasbeenapartoftheJDKsinceJava7,itisstillmanagedinanabstractway.ItisoneofthemanypossibleinterpretersthataJavaprogramcanusetoexecutescript.ItjusthappenstobethereintheJDK,sowedonotneedtoaddtheinterpreterJARtotheclasspath.ScriptEngineManagerdiscoversalltheinterpretersthatareontheclasspathandregistersthem.
ItdoessousingtheServiceProviderspecification,whichhasbeenapartoftheJDKforalongtime,andbyJava9,italsogotextrasupportinmodulehandling.ThisrequiresthescriptinterpreterstoimplementtheScriptEngineFactoryinterfaceandalsotolisttheclassthatdoesitintheMETA-INF/services/javax.script.ScriptEngineFactoryfile.Thesefiles,fromalltheJARfilesthatarepartoftheclasspath,arereadasresourcesbyScriptEngineManager,andthroughthis,itknowswhichclassesimplementscriptinterpreters.TheScriptEngineFactoryinterfacerequiresthattheinterpretersprovidemethodssuchasgetNames,getExtensions,andgetMimeTypes.Themanagercallsthesemethodstocollecttheinformationabouttheinterpreters.WhenweaskaJavaScriptinterpreter,themanagerwillreturntheonecreatedbythefactorythatsaidthatoneofitsnamesisJavaScript.
Togetaccesstotheinterpretersthroughthename,filenameextensionormime-typeisonlyoneofthefunctionsofScriptEngineManager.TheotheroneistomanageBindings.
WhenweexecuteascriptfromwithintheJavacode,wedon'tdoitbecausewewanttoincreaseourdopaminelevels.Inthecaseofscripts,itdoesnothappen.Wewantsomeresults.Wewanttopassparametersandaftertheexecutionofthescript,wewantvaluesbackfromthescriptthatwecanuseintheJavacode.Thiscanhappenintwoways.Oneisbypassingparameterstoamethodorfunctionimplementedinthescriptandgettingthereturnvaluefromthescript.Thisusuallyworks,butitmayevenhappenthatsomescriptinglanguagedoesnotevenhavethenotionofthefunctionormethod.Insuchacase,itisnotapossibility.Whatispossibleistopasssomeenvironmenttothescriptandreadvaluesfromtheenvironmentafterthescriptisexecuted.ThisenvironmentisrepresentedbyBindings.
BindingsisamapthathasStringkeysandObjectvalues.
Inthecaseofmostscriptinglanguages,forexample,inJavaScript,Bindingsisconnectedtoglobalvariablesinthescriptweexecute.Inotherwords,ifweexecutethefollowingcommandinourJavaprogrambeforeinvokingthescript,thentheJavaScriptglobalvariable,globalVariable,willreferencethemyObjectobject:
myBindings.put("globalVariable",myObject)
WecancreateBindingsandpassittoScriptEngineManagerbutjustaswellwecanusetheonethatitcreatesautomatically,andwecancalltheputmethodontheengineobjectdirectly.
TherearetwoBindingswhenweexecutescripts.OneissetontheScriptEngineManagerlevel.Thisisnamedglobalbinding.ThereisalsoonemanagedbyScriptEngineitself.ThisisthelocalBindings.Fromthescript
pointofview,thereisnodifference.Fromtheembeddingside,thereissomedifference.IncaseweusethesameScriptEngineManagertocreatemultipleScriptEngineinstances,thentheglobalbindingsaresharedbythem.Ifonegetsavalue,allofthemseethesamevalue;ifonesetsavalue,allotherswilllaterseethatchangedvalue.Thelocalbindingisspecifictotheengineitismanagedby.SinceweonlyintroduceJavascriptingAPIinthisbook,wedonotgetintomoredetailsandwewillnotuseBindings.WearegoodwithinvokingaJavaScriptfunctionandtogettheresultfromit.
TheclassthatimplementsthescriptinvocationisCheckerScriptExecutor:
packagepackt.java9.by.example.mybusiness.bulkorder.services;
import...
@Component
publicclassCheckerScriptExecutor{
privatestaticfinalLoggerlog=...
privatefinalScriptEngineManagermanager=
newScriptEngineManager();
publicbooleannotConsistent(Stringscript,Orderorder){
try{
finalReaderscriptReader=getScriptReader(script);
finalObjectresult=
evalScript(script,order,scriptReader);
assertResultIsBoolean(script,result);
log.info("Script{}wasexecutedandreturned{}",
script,result);
return(boolean)result;
}catch(ExceptionwasAlreadyHandled){
returntrue;
}
}
Theonlypublicmethod,notConsistent,getsthenameofthescripttoexecuteandalsoorder.Thelatterhastobepassedtothescript.FirstitgetsReader,whichcanreadthescripttext,evaluatesit,andfinallyreturnstheresultincaseitisbooleanorcanatleastbeconvertedtoboolean.Ifanyofthemethodsinvokedfromherethatweimplementedinthisclassiserroneous,itwillthrowanexception,butonlyafterappropriatelyloggingit.Insuchcases,thesafewayistorefuseanorder.
Actually,thisissomethingthatthebusinessshoulddecide.Ifthereisacheckscriptthatcannotbeexecuted,itisclearlyanerroneoussituation.Inthiscase,acceptinganorderandlaterhandlingtheproblemsmanuallyhascertaincosts.Refusinganorderorconfirmationbecauseofsomeinternalbugisalsonotahappypathoftheorderprocess.Wehavetocheckwhichapproachcausestheleastdamagetothecompany.Itiscertainlynotthedutyoftheprogrammer.Inoursituation,weareinaneasysituation.
Weassumethatthebusinessrepresentativessaidthattheorderinsuchasituationshouldberefused.Inreallife,similardecisionsaremanytimesrefusedbythebusinessrepresentativessayingthatitjustshouldnothappenandtheITdepartmenthastoensurethattheprogramandthewholeoperationistotallybugfree.Thereisapsychologicalreasonforsucharesponse,butthisreallyleadsusextremelyfarfromJavaprogramming.
EnginescanexecuteascriptpassedthroughReaderorasString.Becausenowwehavethescriptcodeinaresourcefile,itseemstobeabetterideatolettheenginereadtheresourceinsteadofreadingittoaString:
privateReadergetScriptReader(Stringscript)
throwsIOException{
finalReaderscriptReader;
try{
finalInputStreamscriptIS=newClassPathResource(
"scripts/"+script+".js").getInputStream();
scriptReader=newInputStreamReader(scriptIS);
}catch(IOExceptionioe){
log.error("Thescript{}isnotreadable",script);
log.error("Scriptopeningexception",ioe);
throwioe;
}
returnscriptReader;
}
Toreadthescriptfromaresourcefile,weusetheSpringClassPathResourceclass.Thenameofthescriptisprependedwiththescriptsdirectoryandappendedbythe.jsextension.Therestisfairlystandardandnothingwehavenotseeninthisbook.Thenextmethodthatevaluatesthescriptismoreinteresting:
privateObjectevalScript(Stringscript,
Orderorder,
ReaderscriptReader)
throwsScriptException,NoSuchMethodException{
finalObjectresult;
finalScriptEngineengine=
manager.getEngineByName("JavaScript");
try{
engine.eval(scriptReader);
Invocableinv=(Invocable)engine;
result=inv.invokeFunction("isInconsistent",order);
}catch(ScriptException|NoSuchMethodExceptionse){
log.error("Thescript{}thruwup",script);
log.error("Scriptexecutingexception",se);
throwse;
}
returnresult;
}
Toexecutethemethodinthescript,firstofall,weneedascriptenginethatiscapableofhandlingJavaScript.Wegettheenginefromthemanagerbyitsname.IfitisnotJavaScript,weshouldcheckthatthereturnedengineisnotnull.InthecaseofJavaScript,theinterpreterispartoftheJDKandcheckingthattheJDKconformstothestandardwouldbeparanoid.
IfeverwewanttoextendthisclasstohandlenotonlyJavaScriptbutalsoothertypesofscripts,thischeckhastobedone,andalsothescriptengineshouldprobablyberequestedfromthemanagerbythefilenameextension,whichwedonothaveaccesstointhisprivatemethod.Butthatisfuturedevelopment,notinthisbook.
Whenwehavetheengine,wehavetoevaluatethescript.Thiswilldefinethefunctioninthescriptsothatwecaninvokeitafterwards.Toinvokeit,weneedsomeInvocableobject.InthecaseofJavaScript,theenginealsoimplementsanInvocableinterface.Notallscriptenginesimplementthisinterface.Somescriptsdonothavefunctionsormethods,andthereisnothingtoinvokeinthem.Again,thisissomethingtodolater,whenwewanttoallownotonlyJavaScriptscriptingbutalsoothertypesofscripting.
Toinvokethefunction,wepassitsnametotheinvokeFunctionmethodandalsotheargumentsthatwewanttopasson.Inthiscase,thisistheorder.InthecaseofJavaScript,theintegrationbetweenthetwolanguagesisfairlydeveloped.Asinourexample,wecanaccessthefieldandthemethodsoftheJavaobjectsthatarepassedasargumentsandthereturnedJavaScripttrueorfalsevalueisalsoconvertedtoBooleanmagically.Therearesomesituationswhentheaccessisnotthatsimplethough:
privatevoidassertResultIsBoolean(Stringscript,
Objectresult){
if(!(resultinstanceofBoolean)){
log.error("Thescript{}returnednonboolean",
script);
if(result==null){
log.error("returnedvalueisnull");
}else{
log.error("returnedtypeis{}",
result.getClass());
}
thrownewIllegalArgumentException();
}
}
}
Thelastmethodoftheclasschecksthatthereturnedvalue,whichcanbeanythingsincethisisascriptengine,isconvertibletoboolean.
Itisimportanttonotethatthefactthatsomeofthefunctionalityisimplementedinscriptdoesnotguaranteethattheapplicationworksseamlessly.Theremaybeseveralissuesandscriptsmayaffecttheinnerworkingoftheentireapplication.Somescriptingenginesprovidespecialwaystoprotecttheapplicationfrombadscripts,othersdonot.Thefactthatwedonotpassbutordertothescriptdoesnotguaranteethatascriptcannotaccessotherobjects.Usingreflection,staticmethods,andothertechniquestherecanbewaystoaccessjustanythinginsideourJavaprogram.Wemaybeabiteasierwiththetestingcyclewhenonlyascriptchangesinourcodebase,butitdoesnotmeanthatweshouldblindlytrustanyscript.
Inourexample,itprobablywouldbeaverybadideatolettheproducersoftheproductsuploadscriptstooursystem.Theymayprovidetheircheckscripts,butthesescriptshavetobereviewedfromthesecuritypointofviewbeforebeingdeployedintothesystem.Ifthisisproperlydone,thenscriptingisanextremelypowerfulextensiontotheJavaecosystem,givinggreatflexibilitytoourprograms.
Summary
Inthischapter,wehavedevelopedtheorderingsystemofourenterpriseapplication.Alongwiththedevelopmentofthecode,wemetmanynewthings.Youlearnedaboutannotationsandhowtheycanbehandledbyreflections.Althoughnotstronglyrelated,youlearnedhowtouselambdaexpressionsandstreamstoexpresssomeprogrammingconstructssimplerthanconventionalloops.Inthelastpartofthechapter,weextendedtheapplicationusingscripting,byinvokingJavaScriptfunctionsfromJavaandalsobyinvokingJavamethodsfromJavaScript.
Infact,withallthisknowledge,wematuredtoaJavalevelthatisneededforenterpriseprogramming.Therestofthetopicsthebookcoversarefortheaces.Butyouwanttobeone,don'tyou?ThisiswhyIwrotetherestofthechapters.Readon!
BuildinganAccountingApplicationUsingReactiveProgramming
Inthischapter,wewilldevelopasampleprogramthatdoestheinventorymanagementpartofthecompanywecreatedtheorderhandlingcodefor.Donotexpectafullydeveloped,ready-to-use,professionalapplication,andalso,donotexpectthatwewillgetintothedetailsofaccountingandbookkeeping.Thatisnotouraim.Wewillfocusmoreontheprogrammingtechniquethatisofourinterest—reactiveprogramming.Sorrypals,Iknowthatbookkeepingandaccountingisfun,butthisisnotthatbook.
Reactiveprogrammingisanold(well,whatisoldincomputerscience?)approachthathascomerecentlytoJava.Java9isthefirstreleasethatsupportssomeoftheaspectsofreactiveprogramminginthestandardJDK.Inonesentence,reactiveprogrammingisaboutfocusingmoreonhowthedataflowsandlessonhowtheimplementationhandlesthedataflow.Asyoumayrecall,thisisalsoasteptowardsdescribingwhatwewanttodofromthedescriptionofhowtodoit.
Aftergoingthroughthischapter,youwillunderstandwhatreactiveprogrammingisandwhattoolsthereareinJavathatyoucanutilize.Youwillalsounderstandwhatreactiveprogrammingisgoodforandwhenandhowyoucanutilizethisprincipleinthefuture,astherewillbemoreandmoreframeworkssupportingreactiveprogramminginJava.Inthischapter,youwilllearnthefollowingtopics:
ReactiveprogrammingingeneralReactivestreamsinJavaHowtoimplementoursamplecodeinareactiveway
Reactive...what?Therearereactiveprogramming,reactivesystems,andreactivestreams.Thesearethreedifferentthingsrelatedtoeachother.Itisnotwithoutreasonthatallthethreearecalledreactive.
Reactiveprogrammingisaprogrammingparadigmsimilartoobject-orientedprogrammingandfunctionalprogramming.Areactivesystemisasystemdesignthatsetscertainaimsandtechnologicalconstraintsonhowacertaintypeofinformationsystemsshouldbedesignedtobereactive.Therearealotofresemblancestoreactiveprogrammingprinciplesinthis.Areactivestreamisasetofinterfacedefinitionsthathelpstoachievesimilarcodingadvantagetoreactivesystemsandwhichcanbeusedtocreatereactivesystems.ReactivestreaminterfacesareapartofJDK9,butareavailablenotonlyinJava,butalsoinotherlanguages.
Wewilllookattheseinseparatesections,attheendofwhich,youwillpresumablyhaveabetterunderstandingofwhyeachofthemiscalledreactive.
ReactiveprogramminginanutshellReactiveprogrammingisaparadigmthatfocusesmoreonwherethedataflowsduringcomputationthanonhowtocomputetheresult.Iftheproblemisbestdescribedasseveralcomputationsthatdependontheoutputofeachotherbutseveralmaybeexecutedindependentoftheother,reactiveprogrammingmaycomeintothepicture.Asasimpleexample,wecanhavethefollowingcomputationthatcalculatesthevalueofhfromsomegivenb,c,e,andfvalues,usingf1,f2,f3,f4,andf5assimplecomputationalsteps:
a=f1(b,c)
d=f2(e,f)
k=f3(e,c)
g=f4(b,f,k)
h=f5(d,a,g)
IfwewritetheseinJavaintheconventionalway,themethodsf1tof5willbeinvokedoneaftertheother.Ifwehavemultipleprocessorsandareabletoparallelizetheexecution,wemayperformsomeofthemethodsinparallel.This,ofcourse,assumesthatthesemethodsarepurelycomputationalmethodsanddonotchangethestateoftheenvironment,and,inthisway,canbeexecutedindependentofeachother.Forexample,f1,f2,andf3canbeexecutedindependentofeachother.Theexecutionofthefunctionf4dependsontheoutputoff3,andtheexecutionoff5dependsontheoutputoff1,f2,andf4.
Ifwehavetwoprocessors,wecanexecutef1andf2together,followedbytheexecutionoff3,thenf4,andfinally,f5.Thesearefoursteps.Ifwelookattheprecedingcalculationnotascommandsbutratherasexpressionsandhowthecalculationsdependoneachother,thenwedonotdictatetheactualexecutionorderandtheenvironmentmaydecidetocalculatef1andf3together,thenf2andf4,andfinallyf5,savingonestep.Thisway,wecanconcentrateonthedataflowandletthereactiveenvironmentactuponitwithoutputtingextraconstraints.
Thisisaverysimpleapproachofreactiveprogramming.Thedescriptionofthecalculationintheformofexpressionsgivesthedataflow,butintheexplanation,westillassumedthatthecalculationisexecutedsynchronously.Ifthecalculationsareexecutedondifferentprocessorsthatareondifferentmachines
connectedtoanetwork,thenthecalculationmaynotanddoesnotneedtobesynchronous.Reactiveprogramscanbeasynchronouslyexecutediftheenvironmentisasynchronous.Itmayhappenthatthedifferentcalculations,f1tof4,areimplementedanddeployedondifferentmachines.Insuchacase,thevaluescalculatedaresentfromonetotheotheroverthenetworkandthenodesexecutethecalculationeverytimethereisachangeintheinputs.Thisisverysimilartogoodoldanalogcomputersthatwerecreatedusingsimplebuildingblocksandthecalculationsweredoneusinganaloguesignals.
Theprogramwasimplementedasanelectroniccircuit,andwhentheinputvoltageorcurrent(usuallyvoltage)changedintheinputs,theanalogcircuitsfolloweditinlight'sspeed,andtheresultappearedintheoutput.Insuchacase,thesignalpropagationwaslimitedbythespeedoflightonthewiresandanalogcircuitryspeedinthewiredmodules,whichwasextremelyfastandmaybeatdigitalcomputers.
Whenwetalkaboutdigitalcomputersthepropagationofthesignalisdigital,andthisway,itneedstobesentfromonecalculationnodetotheotherone,beitsomeobjectinJVMorsomeprogramonthenetwork.Anodehastoexecuteitscalculationif:
SomeofthevaluesintheinputhavechangedTheoutputofthecalculationisneeded
Iftheinputhasnotchanged,thentheresultshouldeventuallybethesameasthelasttime;thus,thecalculationdoesnotneedtobeexecutedagain—itwouldbeawasteofresources.Iftheresultofthecalculationisnotneeded,thenthereisnoneedtoperformthecalculationeveniftheresultwouldnotbethesameasthelastone.Noonecares.
Toaccommodatethis,reactiveenvironmentsimplementtwoapproachestopropagatethevalues.Thenodesmaypullthevaluesfromtheoutputofothermodules.Thiswillensurethatnocalculationthatisnotneededwillbeexecuted.Themodulesmaypushtheiroutputtothenextmodulethatdependsonthem.Thisapproachwillensurethatonlychangedvaluesignitecalculation.Someoftheenvironmentsmayimplementahybridsolution.
Whenvalueschangeinthesystem,thechangeispropagatedtowardstheothernodesthatagainpropagatethechangestoanothernodeandsoon.Ifweimaginethecalculationdependenciesasadirectedgraph,thenthechangestraveltowardsthetransitiveclosureofthechangedvaluesalongthenodesconnected.Thedatamaytravelwithallthevaluesfromonenodeoutputtotheothernodeinputoronlythechangemaytravel.Thesecondapproachismorecomplexbecauseitneedsthechangeddataandalsometainformationthatdescribeswhathaschanged.Ontheotherhand,thegainmaybesignificantwhentheoutputandinputsetofdataishugeandonlyasmallportionofitischanged.Itmayalsobeimportanttocalculateandpropagateonlytheactualdeltaofthechangewhenthereisahighprobabilitythatsomeofthenodesdonotchangetheoutputformanyofthedifferentinputs.Insuchacase,thechangepropagationmaystopatthenodewherethereisnorealchangeinspiteofthechangedinputvalues.Thiscansaveupalotofcalculationinsomeofthenetworks.
Intheconfigurationofthedatapropagation,thedirectedacyclicgraphcanbeexpressedinthecodeoftheprogram,itcanbeconfiguredoritcanevenbesetupandchangedduringtheexecutionofthecodedynamically.Whentheprogramcodecontainsthestructureofthegraph,theroutesandthedependenciesarefairlystatic.Tochangethedatapropagation,thecodeoftheprogramhastobechanged,recompiled,
anddeployed.Inthecaseofmultiplenetworknodeprograms,thismayevenneedmultipledeploymentsthatshouldbecarefullyfurnishedtoavoiddifferentincompatibleversionsrunningondifferentnodes.Thereshouldbesimilarconsiderationswhenthegraphisdescribedinsomeconfiguration.Insuchacase,thecompilationoftheprogram(s)maynotbeneededwhenonlythewiringofthegraphischanged,buttheburdentohavecompatibleconfigurationondifferentnodesinthecaseofanetworkexecutionisstillthere.
Lettingthegraphchangedynamicallyalsodoesnotsolvethisproblem.Thesetupandthestructurearemoreflexibleand,atthesametime,morecomplex.Thedatapropagatedalongtheedgesofthegraphmaycontainnotonlycomputationaldatabutalsodatathatdriveschangeinthegraph.Manytimes,thisleadstoaveryflexiblemodelcalledhigher-orderreactiveprogramming.
Reactiveprogramminghasalotofbenefitsbut,atthesametime,maybeverycomplex,sometimestoocomplex,forsimpleproblems.Itistobeconsideredwhentheproblemtobesolvedcaneasilybedescribedusingdatagraphandsimpledatapropagations.Wecanseparatethedescriptionoftheproblemandtheorderoftheexecutionofthedifferentblocks.Thisisthesameconsiderationthatwediscussedinthepreviouschapter.Wedescribemoreaboutthewhattodopartandlessaboutthehowtodopart.
Ontheotherhand,whenthereactivesystemdecidestheorderofexecution,whatischanged,andhowthatshouldbereflectedontheoutputofotherblocks,itshoulddosowithoutknowingthecoreoftheproblemthatitissolving.Insomesituations,codingtheexecutionordermanuallybasedontheoriginalproblemcouldperformbetter.
Thisissimilartothememorymanagementissue.Inmodernruntimeenvironments,suchastheJVM,Pythonruntime,Swiftprogramming,orevenGolang,thereissomeautomatedmemorymanagement.WhenprogramminginC,theprogrammerhasfullcontrolovermemoryallocationandmemoryrelease.Inthecaseofreal-timeapplications,wheretheperformanceandresponsetimeisoftheutmostimportance,thereisnowaytoletanautomatedgarbagecollectortaketimeanddelaytheexecutionfromtimetotime.Insuchacase,theCcodecanbeoptimizedtoallocatememorywhenneeded;thereisaresourcefortheallocationandreleaseofmemorywhenpossibleandthereistimetomanagememory.Theseprogramsarebetterperformingthantheonescreatedforthesamepurposeusingagarbagecollector.Still,wedonotuseCinmostoftheapplicationsbecausewecanaffordtheextraresourceneededforautomatedmemorycollection.Eventhoughitwouldbepossibletowriteafastercodemanagingthememorymanually,automatedcodeisfasterthanwhatanaverageprogrammerwouldhavecreatedusingC,andalso,thefrequencyofprogrammingerrorsismuchlower.
Justastherearesomeissuesthatwehavetopayattentiontowhenusingautomatedmemorymanagement,wehavetopayattentiontosomeissuesinareactiveenvironment,whichwouldnotexistinthecaseofmanualcoding.Still,weusethereactiveapproachforitsbenefits.
Themostimportantissueistoavoidloopsinthedependencygraph.Althoughitisabsolutelyperfecttowritethedefinitionofcalculations,areactivesystemwouldprobablynotbeabletocopewiththesedefinitions.Somereactivesystemsmayresolveinsomesimple-casecyclicredundancy,butthatissomeextrafeatureandwegenerallyjusthavetoavoidthat.Considerthefollowingcomputations:
a=b+3
b=4/a
Here,adependsonb,sowhenbchanges,aiscalculated.However,balsodependsona,whichisrecalculatedand,inthisway,thesystemgetsintoaninfiniteloop.Theprecedingexampleseemstobesimple,butthatisthefeatureofagoodexample.Real-lifeproblemsarenotsimple,andinadistributedenvironment,itisextremelyhardsometimestofindcyclicredundancy.
Anotherproblemiscalledglitch.Considerthefollowingdefinition:
a=b+3
q=b+a
Whentheparameterbischanged,forexample,from3to6,thevalueofawillchangefrom6to9,andthus,qwillchangefrom9to15.Thisisverysimple.However,theexecutionorderbasedontherecognitionofthechangesmayfirstalterthevalueofqfrom9to12beforemodifyingitto15inasecondstep.Thiscanhappenifthecalculatingnoderesponsibleforthecalculationofqrecognizesthechangeinbbeforethevalueofaasaconsequenceofthechangeinthevalueofb.Forashortperiodoftime,thevalueofqwillbe12,whichdoesnotmatchthepreviousandalsodoesnotthechangedstate.Thisvalueisonlyaglitchinthesystemthathappensafteraninputchangesandalsodisappearswithoutanyfurtherchangeintheinputinthesystem.
Ifyouhaveeverlearntthedesignoflogicalcircuits,thenstatichazardsmayringabell.Theyareexactlythesamephenomenon.
Reactiveprogrammingalsoassumesthatthecalculationsarestateless.Theindividualnodesthatperformthecalculationmayhaveastateinpracticeand,manytimes,theydo.Itisnotinherentlyeviltohaveastateinsomecalculation.However,debuggingsomethingthathasastateissignificantlymorecomplexthandebuggingsomethingthatisstateless,functional.
Itisalsoanimportantaidtothereactiveenvironment,lettingitperformdifferentoptimizationsbasedonthefactthatthecalculationsarefunctional.Ifthenodeshaveastate,thenthecalculationsmaynotbe
rearrangedfreelybecausetheoutcomemaydependontheactualevaluationorder.Thesesystemsmaynotreallybereactive,or,atleast,itmaybedebated.
Reactivesystems
Reactivesystemisdefinedinthereactivemanifestoathttp://www.reactivemanifesto.org/.Thecreatorsofthemanifestorealizedthatwiththechangeoftechnology,newsystempatternswillneedtobedevelopedinenterprisecomputingtoleveragethenewtechnologyandyieldbetteroutcomes.Themanifestoenvisionssystemsthatare:
ResponsiveResilientElasticMessage-driven.
Thefirstthreefeaturesareuservalues;thelastoneismoreofatechnologicalapproachtogetthevalues.
ResponsiveAsystemisresponsiveifitgivesresultsinareliablemanner.Ifyoutalktome,Iwillansweryourquestionor,atleast,tellyouthatIdonotknowtheanswerorthatIwasnotabletounderstandthequestion.Betterifyougettheanswer,butifasystemcannotgivethattoyouitisstillexpectedtogivesomethingback.Ifyouhavepastexperiencewithclientoperatingsystemsfromjusttenyearsagoandsomeoldcomputers,youcanunderstandthis.Gettingarotatinghourglassisfrustrating.Youjustdonotknowwhetherthesystemisworkingtogetyoutheansweroristotallyfrozen.
Areactivesystemhastoberesponsive.Theresponseshouldcomeinatimelymanner.Theactualtimingdependsontheactualsystem.Itmaybemilliseconds,seconds,orevenhoursincasethesystemisrunningonaspaceshiptravellingtowardstheothersideofJupiter.Theimportantthingisthatthesystemshouldguaranteesomesoftupperlimitfortheresponsetime.Thisdoesnotnecessarilymeanthatthesystemshouldbeareal-timesolution,whichisamuchstricterrequirement.
Theadvantageofresponsivenessisnotonlythattheuserdoesnotbecomenervousinfrontofthecomputer.Afterall,mostoftheseservicesareusedbyotherservicesthatmainlycommunicatewitheachother.Therealadvantageisthaterrordiscoveryismorereliable.Ifareactivesystemelementbecomesnonresponsive,itiscertainlyanerrorcondition,andsomethingshouldbedoneaboutit,outofthescopeofnormaloperations(replaceafaultycommunicationcard,restartasystem,andsoon).Thesoonerwecanidentifyanerrorstate,thecheaperitistofixit.Themorewecanidentifywheretheproblemis,thelesstimeandmoneywecouldspendlocalizingtheerror.Responsivenessisnotaboutspeed.Itisaboutbetteroperation,betterquality.
ResilientResilientsystemskeepworkingevenwhenthereissomeerror.Well,notanyerror.Thatwouldbeamiracle,orsimplynonsense!Anerrorgenerallyisanerror.IftheArmageddoncomesanditistheendoftheworldasweknowit,evenresilientsystemswillnotberesponsive.Forsmallerdisruptions,however,theremaybesomecuretomakethesystemsresilient.
Therearetechniquesthatmayhelpifonlyadiskfails,thereisapoweroutage,orthereisaprogrammingerror.Systemsmaybereplicated,sowhenoneoftheinstancesstopsresponding,someotherinstancemaytakeupthetaskofthefailingoneandcangoonworking.Systemspronetoerrorsmaybeisolatedfromeachotherintermsofspaceortime.Whenthereisanearthquakeorfloodatonelocation,theotherlocationmaystillgoonworking.Ifdifferentcomponentsdonotneedtocommunicateinrealtimeandmessagesarestoredandforwardedinareliablemanner,thenthisisnotaproblemevenifthetwosystemsareneveravailableatthesametime.Theycanstillcooperatetakingupthemessages,performingthetasktheyaresupposedto,andsendingouttheresultingmessageafterwards.
Errorsinthesystemhavetobeaddressedevenifthesystemremainsresponsive.Errorsdonotaffecttheresponsivenessofaresilientsystem,butthelevelofresiliencedecreasesandshouldberestored.
ElasticElasticitymeansthatthesystemisadaptingtotheload.Wecanhaveahugesystem,withlotsofprocessorscapableofservingthelargestanticipateddemand.Thatisnotelasticity.Sincethedemandisnotconstantand,mostofthetime,issmallerthanthemaximum,theresourcesofsuchasystemareidle.Thiscauseswasteoftime,CPUcycle,energy,and,thus,ecologicalfootprint.
Havingsystemsrunonthecloudcanavoidsuchlosses.Thecloudisnothingbutmanycomputersthatsomebodyoperatesformultipleapplications,formultiplecorporationseven,andeachrentsonlytheCPUcyclesthatitreallyneedsandonlywhenitneeds.Othertimes,whentheloadissmallertheCPUandtheelectricpowercanbeusedbysomeoneelse.Sincedifferentapplicationsanddifferentcorporationshavedifferentpeaktimes,thelossofresourcesislesswiththismodel.Therearemanyissuesthathavetobesolved,suchasdataisolationandprotectionofinformationfromeavesdropping,butthesearemainlysolved.Secretservicecorporationswillnotrentresourcesfromacloudservicetoruntheircomputations(perhaps,they'ddoforsomeotherpurpose)andsomeotherparanoidcompaniesmayalsorefrainfromdoingthat,butmostofthecompanieswilldo.Itismoreeffectiveandisthuscheaperevenafterconsideringallthesideeffectsonecanconsider.
Elasticitymeansthattheallocatedresourcesfollow,orratheranticipatethecomingneeds.Whenthesystemanticipateshighercapacityneeds,itallocatesmoreresourcesandatoff-peaktime,itreleasestheresourcessothatothercloudcustomerscanuseit.
Elasticityalsoassumesthatthesystemisscalable.Thetwothings,elasticityandscalability,arecloselyrelatedbutarenotthesame.Scalabilitymeansthattheapplicationcanaccommodatehigherload,allocatingmoreresources.Scalabilitydoesnotcarewhetherthisallocationisstaticbuyingandpoweringofhugecomputerboxesinacomputingcenterdedicatedtotheapplicationordynamicallocationofresourcesfromthecloudondemand.Scalabilitysimplymeansthatifthedemanddoubles,thentheresourcescanalsobemultipliedtomeetthedemand.Ifthemultiplicationfactorintheresourcesneededisthesameorisnotmorethanthefactorindemand,thentheapplicationisscalable.Ifweneedmoreresourcestomeetthedemand,orifwecannotmeetthedemandevenifthedemandincreasesonlymoderately,thentheapplicationisnotscalable.Elasticapplicationsarealwaysscalable;otherwise,theycannotbeelastic.
Message-drivenReactivesystemsaremessage-driven;notbecauseweneedmessage-drivensystemsbutmuchratherbecausemessage-drivensystemsarethosethatcandeliverresponsiveness,resilience,andelasticityatthesametime.
Message-drivenarchitecturemeansthattheinformationtravelsbetweenthecomponentsdisconnected.Onecomponentsendsamessageandthenforgetsit.Itdoesnotwaitfortheothercomponenttoactuponthemessage.Whenthemessageissent,allthetasksonbehalfofthesendingcomponentareperformedandalltheresourcesneededtohandlethetasksarereleased,resultinginthemessagebeingreleasedandreadytobeusedforthenexttask.
Message-drivendoesnotnecessarilymeannetworking.Messagescantravelbetweenobjects,threads,andprocessesinsidethesamemachine.Ontheotherhand,iftheinterfacestothemessagingarchitecturearewell-designed,thenthecomponentsdonotneedtobemodifiediftheinfrastructurechangesandthemessagesthatwerepreviouslypassingbetweenthreadswillnowhavetotravelthroughtheoceaninIPpackets.
Sendingmessagesmakesitpossibletoisolatethesenderandthereceiverinspaceandtime,justaswedescribed,asameansforelasticity.Thereceivermaypickupthemessagesometimeafteritarrived,whenithastheresourcestodoso.Responsiveness,though,requiresthatthistimeisnotintheunreachabledistantfuturebutinsomelimiteddistance.Ifthemessagecannotbeprocessedsuccessfully,anothermessagemaysignaltheerror.Anerrormessageisnottheresultweexpect,butitisstillsomeresponseandthesystemremainsresponsivewithallthebenefitsitmeans.
Back-pressureMessagehandling,withtheappropriatemessaginginterfacesandimplementation,supportsback-pressure.Back-pressureisameanstolessentheburdenonacomponentwhenitcannotorcanbarelyhandlemoremessages.Messagesmaybequeuingforprocessing,butnoreal-lifequeuehasunlimitedcapacityandreactivesystemsshouldnotloseamessageuncontrolled.Back-pressuresignalstheloadofthecomponenttothemessageproducers,askingthemtolessentheproduction.Itislikeawaterpipe.Ifyoustartclosingtheoutletofthepipe,thepressurestartstoincreaseinthepipebackward,thewatersourceforcingittodeliverlessandlesswater.
Back-pressureisaneffectivewayofhandlingloadbecauseitmovesloadhandlingtothecomponentthatcanreallydoit.Inold-fashionedqueuingsystems,thereisaqueuethatstorestheitemstillthecomponentreceivingthemcanconsumethem,doingitsjob.Aqueuedesigncanbegoodifthereisawell-definedlimitforthesizeoftheloadandforthemaximumsizeofthequeue.Ifeverthequeueisfull,theitemscannotbedeliveredandthesystemstalls.
Applyingback-pressureisabitdifferent.Aqueuemaystillbeusedinfrontofthecomponentsforperformanceoptimizationandtoensureresponsiveness.Theproduceroftheitemcanstillputtheproducediteminthequeueandreturntoattendingtoitsowndutiesanddoesnotneedtowaittilltheconsumercanattendtotheitem.Thisisdecoupling,aswementionedearlier.Seeingthatthequeueisfulloralmostfullcanalsoactasaverysimpleback-pressure.Itisnottrueifsomeonesaysthatqueuesaretotallymissingthisfeature.Attimes,itmaysimplybetotallysufficientjusttolookatthecapacityofaqueue,andalsotheitemsinit,toseeifthereissomeneedtolessentheloadonthereceiverthequeuebelongsto.Buttheproducerdoesthis,notthereceiver,andthatisanessentialproblem.
Theproducerseesthatthereceiverisnotkeepingpacewiththesupplybuttheproducerdoesnothaveanyinformationaboutthecause,andnotknowingthecausecannotpredictthefuturebehavior.Havingaback-pressureinformationchannelfromthereceivertotheproducermakesthestorymorefinegrained.
Theproducermayseethatthereare,say,10slotsinthequeueanditthinksthatthereisnoproblem;theproducerdecidestodelivereightmoreitemsinthenext150ms.Oneitemusuallytakes10mstoprocess,giveortake;thustheitemsareexpectedtobeprocessedinlessthan100ms,whichisjustbetterthantherequired200msmaximum.Theproduceronlyknowsthatanitemusuallytakes10mstoprocess.Thereceiver,ontheotherhand,seesthatthelastitemitgotintothequeuerequiressomuchprocessingthat,byitself,itwillrequire200ms.Tosignalthis,itcantelltheproducerovertheback-pressurenottodelivernewitemstillfurthernotice.Thereceiverknowsthattheitemswouldhavefitinthequeuefinebutwouldnotbeprocessedinatimelymanner.Usingthisinformation,theproducerwillissuesomecommandstothecloudcontroltoallocateanotherprocessingandsendsthenexteightitemstothenewreceiver,lettingtheoldonedoitscumbersomejobithastowiththatfarabovethanaverageitem.
Back-pressureletsyouaidthedataloadcontrol,withinformationcreatedbythereceiversthathavethemostinformationaboutprocessingtheitems.
ReactivestreamsReactivestreamsstartedasaninitiativetoprovideastandardofhandlingdatastreamsinanasynchronousmodebyregulatingthepushofthedatausingback-pressure.Theoriginalsiteoftheprojectishttp://www.reactive-streams.org/.
ReactivestreamsarenowimplementedinJDK9inthejava.util.concurrentpackage.
Theaimofthedefinitionofreactivestreamsistodefinetheinterfacethatcanhandlethepropagationofthegenerateddatainatotallyasynchronouswaywithouttheneedonthereceivingsidetobuffertheunlimitedcreateddata.Whendataiscreatedinastreamandismadeavailabletobeworkedontheworkerthatgetsthedata,hastobefastenoughtohandleallthedatathatisgenerated.Thecapacityshouldbehighenoughtohandlethehighestproduction.Someintermediatebuffersmayhandlepeaks,butifthereisnocontrolthatstopsordelaysproductionwhentheconsumerisatthetopofitscapacity,thesystemwillfail.Reactivesysteminterfacesaredesignedtoprovideawaytosupportback-pressure.Back-pressureisaprocesstosignaltheproducerofthedatatoslowdownoreventostoptheproductiontothelevelthatfitstheconsumer.Everycalltheinterfacesdefineisasynchronoussothattheperformanceofonepartisnotaffectedbythedelaysintheexecutionofotherparts.
Theinitiativedidnotaimtodefinethewayinwhichdataistransferredbetweenproductionandconsumption.ItfocusesontheinterfacestogiveaclearstructurefortheprogramsandalsotogiveanAPIthatwillworkwithalltheimplementations.
ReactiveprogramminginJavaJavaisnotareactivelanguage.ThisdoesnotmeanthatwecannotcreatereactiveprogramsinJava.Therearelibrariesthatsupportdifferentreactiveprogrammingapproaches.ItistomentiontheAkkaframeworkandtheReactiveXthatalsoexistforotherlanguagesaswell.WithJava9,theJDKstartstosupportreactiveprogramming,providingafewclassesandinterfacesforthepurpose.Wewillfocusonthesefeatures.
TheJDKcontainsthejava.util.concurrent.Flowclass,whichcontainsrelatedinterfacesandsomestaticmethodstosupportflowcontrolledprograms.ThemodelthatthisclasssupportsisbasedonPublisher,Subscriber,andSubscription.
Asaverysimpleexplanation,aPublisheracceptsasubscriptionfromaSubscriber.ASubscribergetsthedataitsubscribedtowhenthedataisavailable.Theinterfacesfocusontheverycoreofthedataflowcontrolofthecommunicationandareabitabstract.Nosurprise,theyareinterfaces.However,itmaynotbesimpletounderstandtheirworkingatfirst.
ThePublisherinterfacedefinesthesubscribemethod.Thisistheonlymethodthisinterfacedefinesandthatisbecausethisistheonlythingthatarealpublishercanbeasked.Youcansubscribetothepublications.TheargumentofthemethodisaSubscriberthatsubscribestothepublications:
voidsubscribe(Flow.Subscriber<?superT>subscriber)
ThereisareadilyavailablePublisherclassintheJDKthatwewilllookatlater.WhenthesubscribemethodofthePublisheriscalled,ithastodecideifthesubscribercangetthesubscriptionornot.Usually,thesubscriptionisacceptedbuttheimplementationhasthefreedomtorefuseasubscriptionattempt.Publishermayrefuseasubscriptionif,forexample,thesubscriptionfortheactualsubscriberwasalreadyperformedandthePublisherimplementationdoesnotallowmultiplesubscriptionsfromthesamesubscriber.
TheimplementationofthemethodisrequiredtocalltheonErrormethodofsubscriber,withThrowableastheargument.Inthecaseofmultiplesubscriptions,IllegalStateExceptionseemstobesuitable,astheJDKdocumentationdefinesatthemoment.
Ifthesubscriptionissuccessful,PublisherisexpectedtocalltheonSubscribemethodofsubscriber.TheargumenttothismethodisaSubscriptionobject(aninstanceofaclassthatimplementstheinterfaceSubscription).Thisway,thePublishernotifiestheSubscriberthatthesubscriptionrequestwasaccepted,andalsopassesanobjecttomanagethesubscription.
Managingthesubscriptionasanabstractioncouldbeimaginedasacomplextask,butinthecaseofreactivestreams,itisverysimple.Allthesubscribercanandshoulddoistosetthenumberofitemsitcanreceiveatthemoment,andthesubscriptioncanbecancelled.
WhyshouldthePublishercallbacktheonSubscribemethodofSubscriber?Whydoesn'titsimplyreturnthesubscriptionorthrowsomeerror?ThereasonforthiscomplexbehavioristhatitmaynotbetheSubscriberthatinvokesthesubscribemethod.Justasinreallife,I
cansubscribeandpayforayearofamagazinesubscriptionasaChristmasgift.(ThisistheseasonwhenIamwritingthispartofthebook.)Inourcode,somewiringcomponentresponsibleforwhoisnotifiedaboutacertaindatachangecallssubscribeandnotnecessarilythesubscriber.TheSubscriberisonlyresponsiblefortheminimalthingsthatasubscribershouldberesponsiblefor.Theotherreasonisthatthewholeapproachisasynchronous.Whenwesubscribetosomething,thesubscriptionmaynotbeavailableandreadyimmediately.Therecouldbesomelong-runningprocessesthatneedtofinishtillthesubscriptionwillbeavailableandthecallerthatiscallingsubscribedoesnotneedtowaitforthecompletionoftheprocess.Whenthesubscriptionisreadyitispassedtothesubscriber,totheveryentitythatreallyneedsit.
TheSubscriberinterfacedefinestheonSubscribe,onError(wehavealreadytalkedaboutthese),onCompleteandonNextmethods.
ItisimportantinthedefinitionoftheseinterfacesthatthesubscribergetstheitemsfromPublisherorfromsomeotherobjecttowhichthePublisherdelegatesthistaskviasomepush.Thesubscriberdoesnotneedtogotothenewsstandtogetthenextissue;someonecallingtheonNextmethoddeliverstheissuetoitdirectly.
ThisalsobearstheconsequencethatunlesstherearesomecontrolsinthehandsoftheSubscriber,itcouldhappenthatthePublisherfloodstheSubscriberwithitems.NoteverySubscriberiscapableofhandlingunlimiteditems.TheSubscribergetsaSubscriptionobjectuponperformingthesubscriptionandthisobjectcanbeusedtocontroltheflowoftheitemobjects.
ThePublishercreatestheSubscriptionobjectandtheinterfacedefinestwomethods:cancelandrequest.ThecancelmethodshouldbecalledbytheSubscribertonotifythePublisherthatitshouldnotdelivermoreitems.Thesubscriptioniscancelled.Therequest(longn)methodspecifiesthatthesubscriberispreparedtogetatmostnitemsviasubsequentcallstotheonNextmethod:
Ifthesubscriberhasalreadyinvokedtherequestmethod,thespecifiednumberisaddedtothesubscriptioncounter.Inotherwords,thespecifiedlongvaluedoesnotreflecttheactualstateofthesubscriber.Itisa
delta,increasingsomecountersmaintainedbythePublisherthatcountsthenumberofitemsthatcanbedeliveredaddingthevalueofthelongargumentanddecrementingbyoneoneachitemdeliveredtotheSubscriber.Themostusualapproachistocallrequest(1)eachtimetheSubscriberhasfinishedprocessingarequest.
IftherequestmethodisinvokedwiththeLong.MAX_VALUEargument,thePublishermayjustsendanyitemthatitcanwithoutcountingandwithoutlimit.Thisisessentiallyswitchingofftheback-pressuremechanism.
Thespecificationalsomentionsthatthecalltocanceldoesnotnecessarilymeanthattherewillbenomoreissuesdeliveredatall.Cancellationisdoneonbesteffort.Justasinreallife,whenyousendyourmailtothedailypaperwithyourintenttocancelthesubscription,thepublisherwillnotsendanagenttostopthepostmanbeforehedropstheissuetoyourmailbox.Ifsomethingwasalreadyonitswaywhenthecancellationarrivedtothepublisheritgoesitsway.IfthePublisherhasalreadystartedsomeasynchronousprocessthatcannotreasonablybestopped,thenthemethodonNextmethodwillbeinvokedwithsomeoftheelements.
ThePublisherandSubscriberinterfaceshaveagenericparameter,T.ThisisthetypeofitemsthatthePublisherinterfacepublishesandtheSubscriberinterfacegetsintheonNextmethod.Tobeabitmoreprecise,theSubscriberinterfacecanhaveanRtype,whichisasuperclassofT;thus,itiscompatiblewiththePublisherinterface.Forexample,ifPublisherpublishesLongvalues,thentheSubscriberinterfacecanacceptLong,Number,orObjectintheargumentoftheonNextmethod,dependingonthedeclarationoftheclassthatimplementsSubscriber.
TheFlowclassalsocontainsaProcessorinterfacethatextendsbothPublisherandSubscriber.Thisinterfaceistheretobeimplementedbyclassesthatalsoacceptdataandsenddatatoothercomponentsinthereactiveflow.Suchelementsareverycommoninreactivestreamprogramsbecausemanyelementsthatperformsometasksgettheitemstoworkonfromotherreactivestreamelements;thus,theyareSubscribersand,atthesametime,theysenditaftertheyhavefinishedtheirtasks;thus,theyarePublishers.
ImplementinginventoryNowthatwehavediscussedalotoftechnologiesandprogrammingapproach,itisverymuchthetimetoimplementsomesamplecode.Wewillimplementinventorykeepinginourapplicationusingreactivestreams.Fortheexample,theinventorywillbeverysimple.ItisaMap<Product,InventoryItem>thatholdsthenumberofitemsforeachproduct.TheactualmapisConcurrentHashMapandtheInventoryItemclassisabitmorecomplexthanaLongnumbertoproperlyhandleconcurrencyissues.Whenwedesignaprogramthatisbuiltonresponsivestreams,wedonotneedtodealwithmuchconcurrencylocking,butwestillshouldbeawarethatthecoderunsinamultithreadenvironmentandmayexhibitstrangebehaviorifwedonotfollowsomerules.
ThecodefortheInventoryclassisfairlysimplesinceithandlesonlyamap:
packagepackt.java9.by.example.mybusiness.inventory;
import...;
@Component
publicclassInventory{
privatefinalMap<Product,InventoryItem>inventory=
newConcurrentHashMap<>();
privateInventoryItemgetItem(Productproduct){
inventory.putIfAbsent(product,newInventoryItem());
returninventory.get(product);
}
publicvoidstore(Productproduct,longamount){
getItem(product).store(amount);
}
publicvoidremove(Productproduct,longamount)
throwsProductIsOutOfStock{
if(getItem(product).remove(amount)!=amount)
thrownewProductIsOutOfStock(product);
}
}
Theinventoryitemmaintainingtheclassisabitmorecomplexsincethisisthelevelwherewehandleabitofconcurrencyor,atleast,thisistheclasswherewehavetopaysomeattention:
packagepackt.java9.by.example.mybusiness.inventory;
importjava.util.concurrent.atomic.AtomicLong;
publicclassInventoryItem{
privatefinalAtomicLongamountOnStock=
newAtomicLong(0);
voidstore(longn){
amountOnStock.accumulateAndGet(n,
(stock,delta)->stock+delta);
}
longremove(longdelta){
classClosureData{
longactNr;
}
ClosureDatad=newClosureData();
amountOnStock.accumulateAndGet(delta,
(stock,n)->
stock>=n?
stock-(d.actNr=n)
:
stock-(d.actNr=0)
);
returnd.actNr;
}
}
Whenweaddproductstotheinventory,wehavenolimit.Thestorageshelvesareextremelyhugeandwedonotmodelthattheyoncemaygetfullandtheinventorymaynotbeabletoaccommodatemoreitems.Whenwewanttoremoveitemsfromtherepository,however,wehavetodealwiththefactthattheremaynotbeenoughitemsfromtheproduct.Insuchacase,wedonotremoveanyitemsfromtherepository.Weservethecustomertofullsatisfactionorwedonotserveatall.
Tomaintainthenumberoftheitemsintheinventory,weuseAtomicLong.ThisclasshastheaccumulateAndGetmethod.ThismethodgetsaLongparameterandaLongBinaryOperatorthatweprovideinourcodeasalambda.ThiscodeisinvokedbytheaccumulateAndGetmethodtocalculatethenewvalueofthestock.Ifthereareenoughitems,thenweremovetherequestednumberofitems.Iftherearenotenoughitemsonstock,thenweremovezero.Themethodreturnsthenumberofitemsthatweactuallyreturn.Sincethatnumberiscalculatedinsidethelambda,ithastoescapefromthere.Todoso,weuseClosureDatadefinedinsidethemethod.
Notethat,forexample,inGroovywecouldsimplyuseaLongdvariableandalterthevariableinsidetheclosure.Groovycallslambdatoclosures,sotosay.InJavawecannotdosobecausethevariablesthatwecanaccessfrominsidethemethodshouldbeeffectivelyfinal.However,thisisnothingmorethanabitmoreexplicitnotationthatbelongstotheclosureenvironment.TheClosureDatadobjectisfinalasopposedtothefieldtheclasshas,whichcanbemodifiedinsidethelambda.
ThemostinterestingclassthatwearereallyinterestedinthischapterisInventoryKeeper.ThisclassimplementstheSubscriberinterfaceandiscapableofconsumingorderstomaintaintheinventory:
packagepackt.java9.by.example.mybusiness.inventory;
import...
publicclassInventoryKeeperimplementsFlow.Subscriber<Order>{
privatestaticfinalLoggerlog=
LoggerFactory.getLogger(InventoryKeeper.class);
privatefinalInventoryinventory;
publicInventoryKeeper(@AutowiredInventoryinventory){
this.inventory=inventory;
}
privateFlow.Subscriptionsubscription=null;
privatestaticfinallongWORKERS=3;
@Override
publicvoidonSubscribe(Flow.Subscriptionsubscription){
log.info("onSubscribewascalled");
subscription.request(WORKERS);
this.subscription=subscription;
}
TheonSubscribemethodisinvokedaftertheobjectissubscribed.Thesubscriptionispassedtotheobjectandisalsostoredinafield.Sincethesubscriberneedsthissubscriptioninsubsequentcalls,whenanitempassedinonNextisprocessedandanewitemisacceptable,afieldisagoodplacetostorethisobjectin.Inthismethod,wealsosettheinitialrequesttothreeitems.Theactualvalueissimplydemonstrative.Enterpriseenvironmentsshouldbeabletoconfiguresuchparameters:
privateExecutorServiceservice=
Executors.newFixedThreadPool((int)WORKERS);
ThemostimportantpartofthecodeistheonNextmethod.Whatitdoesisactuallygoesthroughalltheitemsoftheorderandremovesthenumberofitemsfromtheinventory.Ifsomeoftheitemsareoutofstock,thenitlogsanerror.Thisistheboringpart.Theinterestingpartisthatitdoesthisthroughanexecutorservice.ThisisbecausethecalltoonNextshouldbeasynchronous.ThepublishercallsonNexttodelivertheitem,butweshouldnotmakeitwaitfortheactualprocessing.Whenthepostmanbringsyourfavoritemagazine,youdon'tstartreadingitimmediatelyandmakethepostmanwaitforyoursignatureapprovingacceptance.AllyouhavetodoinonNextisfetchthenextorderandmakesurethatthiswillbeprocessedinduetime:
@Override
publicvoidonNext(Orderorder){
service.submit(()->{
intc=counter.incrementAndGet();
for(OrderItemitem:order.getItems()){
try{
inventory.remove(item.getProduct(),
item.getAmount());
}catch(ProductIsOutOfStockexception){
log.error("Productoutofstock");
}
}
subscription.request(1);
counter.decrementAndGet();
}
);
}
@Override
publicvoidonError(Throwablethrowable){
log.info("onErrorwascalledfor{}",throwable);
}
@Override
publicvoidonComplete(){
log.info("onCompletewascalled");
}
}
TheactualimplementationinthiscodeusesThreadPoolwiththreethreadsinit.Also,thenumberofrequireditemsisthree.Thisisalogicalcoincidence:eachthreadworksonasingleitem.Itdoesnotneedtobelikethat,evenifinmostcasesitis.Nothingcanstopusfrommakingmorethreadsworkingonthesameitemifthatmakessense.Theoppositeisalsotrue.Onesinglethreadmaybecreatedtoworkonmultipleitems.Thesecodeswillprobablybemorecomplexandthewholeideaofthesecomplexexecutionmodelsistomakethecodingandthelogicsimpler,movethemultithreading,coding,andimplementationissuesintotheframework,andfocusonthebusinesslogicintheapplicationcode.ButIcannottellthattheremaynotbeanexampleforasubscriberworkingmultiplethreadsonmultipleitems,intermingled.
Thelastcodewehavetolookatinthischapteristheunittestthatdrivesthecodewithsomeexamples:
publicvoidtestInventoryRemoval(){
Inventoryinventory=newInventory();
SubmissionPublisher<Order>p=
newSubmissionPublisher<>();
WecreatePublisherusingtheJDKclass,SubmissionPublisher,whichneatlyimplementsthisinterfacedeliveringmultithreadfunctionalityforuswithoutmuchhassle:
p.subscribe(newInventoryKeeper(inventory));
Wecreateaninventorykeeperandwesubscribetothepublisher.Thisdoesnotstartdeliveringanythingbecausetherearenopublicationsyet,butitcreatesabondbetweenthesubscriberandthepublishertelling,themthatwheneverthereisaproductsubmitted,thesubscriberwantsit.
Afterthat,wecreatetheproductsandstorethemintheinventory,20piecesaltogether,andwealsocreateanorderthatwants10productstobedelivered.Wewillexecutethisordermanytimes.Thisisabitofsimplification,butforthetest,thereisnoreasontocreateseparateorderobjectsthathavethesameproductsandthesameamountsinthelistofitems:
Productproduct=newProduct();
inventory.store(product,20);
OrderItemitem=newOrderItem();
item.setProduct(product);
item.setAmount(10);
Orderorder=newOrder();
List<OrderItem>items=newLinkedList<>();
items.add(item);
order.setItems(items);
Afterallthishasbeendone,wesubmittheordertothePublisher10times.Itmeansthatthereare10ordersforthesameproduct,eachaskingfor10pieces,thatis,100piecestogether.Thoseare100piecesagainstthewarehousewherewehaveonly20ofit.Whatweshouldexpectisthatonlythefirsttwoorderswillbefulfilledandtherestwillberejectedandthatiswhatwillactuallyhappenwhenweexecutethiscode:
for(inti=0;i<10;i++)
p.submit(order);
log.info("Allordersweresubmitted");
Afteralltheordersarepublished,wewaitforhalfasecondsothattheotherthreadshavetimetoexecuteandthenwefinish:
for(intj=0;j<10;j++){
log.info("Sleepingabit...");
Thread.sleep(50);
}
p.close();
log.info("Publisherwasclosed");
}
Notethatthisisnotaregularunittestfile.Itissometestcodetoplayaround,whichIalsorecommendforyoutoexecute,debug,andlookatthedifferentlogoutputs.
SummaryInthisshortchapter,wehadalookatreactiveprogramming,reactivesystems,andreactivestreams.Wediscussedthesimilaritiesandthedifferencesbetweenthesethatmayleadtoconfusions.WepaidspecialattentiontoJava9reactivestreamsthathavepracticallynothingtodowithStreamclassesandmethods.
Inthesecondhalfofthechapter,wediscussedaverysimpleexamplethatusesreactivestreams.
Afterreadingthischapter,youhavelearnedalotabouttheJavalanguageandprogramming.WedidnotdetailallthesmallbitsofJava,butthatisnotpossibleinabook.Idaresaythatthereisnoman(orwomanforthatmatter)ontheEarthorarounditonanorbitalroute,whereverhumansare,whoknowseverythingaboutJava.We,however,knowenoughbynowtostartcodinginanenterpriseenvironmentandtolearnmoreandmoreonthegotillweretire,orevenafterthat.Whatisstillleftisalittlebitofprogramming.IntheprevioussentenceIsaidcodingtomakesomedistinction.Codingisnotthesameasprogramming.Codingisatechniqueusedintheprofessionofprogramming.Duringthenext,andlast,chapterwewillseetheaspectsofprogrammingandhowitcan,andshould,bedoneinprofessionalmanner.Thisisrarelyapartofanintroductorybook,butIamhappythatwecouldagreeonthistopicwiththepublisher.Thisway,youcanfinishthebooknotonlywiththeknowledgethatyoulearnfromthisbook,butalsowithavision,lookingaheadontheroadyouwillwalkupthehillsidetothetop.Youwillknowthetopics,areas,andsubjectsthatyoucangoonlearning.
FinalizingJavaKnowledgetoaProfessionalLevel
Bynow,youhavelearnedthemostimportantareasandtopicsneededforaprofessionalJavadeveloper.Whatwestillhaveaheadofusinthisbookistodiscusssometopicsthatwillleadyoufrombeingajuniordevelopertoaseniordeveloper.Readingthischapterwillnotmakeanyoneaseniordeveloper,though.Thepreviouschaptersweretheroadsthatwewalkedthrough.Thischapterisonlythemap.Ifeachofthepreviouschapterscoveredashortwalkofafewmilesinthejourneyofcodingtoreachtheharbor,thenthischapteristhenauticalmaptodiscoveranewcontinent.
Wewillbrieflybiteintosomeverydeepandhigh-levelprofessionalareas,suchascreatingaJavaagent,compile-timeannotationprocessing,polyglotprogramming,abitofarchitecturedesignandtools,andtechniquestoworkinteams.We'lldoitjustforthetaste.Now,youhaveenoughknowledgetounderstandtheimportanceofthesetopics,andgettingatastewillcreateanappetiteforthecomingyearsofself-development,or,atleast,thatismyintentiontomakeyou,thereader,addicted.
Javadeeptechnologies
Inthissection,wewilllistthreetechnologies:
JavaagentPolyglotprogrammingAnnotationprocessing
KnowingthemisnotamustforaJavaprofessional.Knowingaboutthemis.Javaagentsareusedmainlyindevelopmentenvironmentsandinoperation.TheyarecomplexruntimetechnologiesthatinteractwiththealreadyrunningJVM.Annotationprocessingistheotherend.AnnotationprocessorsarepluggedintotheJavacompiler.Polyglotprogrammingisinthemiddle.ItisJVMprogramming,justlikeprogramminginJava,butbyusingsomedifferentlanguageor,perhaps,somedifferentlanguageandJavatogether.Orevenmanylanguages,suchasJython,Groovy,Clojure,andJavatogether.
Wewilldiscussthesetechnologiessothatwewillgetsomeideaaboutwhattheyareandwheretolookforfurtherinformationincasewewanttolearnmoreaboutthem.
JavaagentAJavaagentisaJavaprogramthatisloadedbytheJavaruntimeinaspecialwayandcanbeusedtointerferewiththebytecodeoftheloadedclasses,alteringthem.Theycanbeusedto:
Listorlog,andreporttheloadedclassesduringruntime,astheyareloadedModifytheclassessothatthemethodswillcontainextracodetoreportruntimebehaviorSupportdebuggerstoalterthecontentofaclassasthedevelopermodifiesthesourcecode
Thistechnologyisusedin,forexample,theproductsJRebelandXRebelfromhttps://zeroturnaround.com/.
AlthoughJavaagentsworkinthedeepdetailsofJava,theyarenotmagic.TheyareabitcomplexandyouneedadeepunderstandingofJava,butanyonewhocanprograminJavacanwriteaJavaagent.Allthatisrequiredisthattheclass,whichistheagent,hassomepredefinedmethodspackagedintoaJARfilealongwiththeotherclassesoftheagentandhasaMETA-INF/MANIFEST.MFfilethatdefinesthenamesoftheclassesimplementingthepremainand/oragentmainmethods,andsomeotherfields.
ThedetailedandprecisereferencedocumentationispartoftheJDKJavaDocavailableathttp://download.java.net/java/jdk9/docs/api/inthedocumentationofthejava.lang.instrumentpackage.
WhenaJavaapplicationisstartedwithaJavaagent,thecommandlinehastocontainthefollowingoption:
-javaagent:jarpath[=options]
Here,jarpathpointstotheJARfilethatcontainstheagentclassandthemanifestfile.Theclassmusthaveamethodnamedpremainoragentmain.Itmayhaveoneortwoarguments.TheJVMtriestocallthetwo-argumentversionfirstaftertheJVMisinitialized:
publicstaticvoidpremain(StringagentArgs,Instrumentationinst);
Ifatwo-argumentversiondoesnotexist,thentheone-argumentversionisused,whichisessentiallythesameasthetwo-argumentversionbutmissestheinstrumentationargument,which,inmyopinion,doesnotmaketoomuchsensesinceaJavaagentcannotdomuchwithouttheInstrumentationobject:
publicstaticvoidpremain(StringagentArgs);
TheagentArgsparameteristhestringpassedasanoptiononthecommandline.Thesecondargument,Instrumentation,providesmethodstoregisterclasstransformersthatcanmodifyclassbytecodesandalsomethodsthatcanasktheJVMtoperformredefinitionorretransformationofclassesduringruntime.
Javaapplicationscanalsoloadanagentaftertheprogramhasalreadystarted.Insuchacase,theagentcannotbeinvokedbeforethemainmethodoftheJavaapplication,sinceithasalreadystartedbythattime.Toseparatethetwocases,JVMcallsagentmaininsuchascenario.Notethateitherpremainoragentmainisinvokedforanagentandneverboth.Asingleagentcanimplementbothsothatitiscapableofperformingitstaskloadedatthestartup,specifiedonthecommandlineoraftertheJVMstarted.
Ifagentmainisused,ithasthesameargumentsaspremain.
Thereisonemajorandimportantdifferencebetweentheinvocationofpremainandagentmain.Ifanagentcannotbeloadedduringstartup,forexample,ifitcannotbefound,iftheJARfiledoesnotexist,iftheclassdoesnothavethepremainmethod,orifitthrowsanexception,theJVMwillabort.IftheagentisloadedaftertheJVMisstarted(inthiscase,agentmainistobeused),theJVMwillnotabortifthereissomeerrorintheagent.
Thisapproachisfairlyreasonable.ImaginethatthereisaserverapplicationthatrunsontheTomcatservletcontainer.Whenanewversionisstarted,thesystemisdownforamaintenanceperiod.Ifthenewversioncannotbestartedbecausetheagentisnotbehavingwell,thenitisbetternotstarted.Thedamagetodebugthesituationandfixit,orrollbacktheapplicationtotheoldversionandcallforalongerfixingsessionmaybelessthanstartinguptheapplicationandnothavingtheproperagentfunctionality.Iftheapplicationstartsuponlywithouttheagent,thenthesuboptimaloperationmaynotimmediatelyberecognized.Ontheotherhand,whenanagentisattachedlater,theapplicationisalreadyrunning.Anagentisattachedtoanalreadyrunningapplicationtogetinformationfromanalreadyrunninginstance.Tostopthealreadyrunninginstanceandfailit,especiallyinanoperationalenvironment,ismoredamagingthanjustnotattachingtheagent.Itmaynotgounnoticedanywaybecausetheagentthatismostprobablyattachedisusedbyoperationalpersonnel.
ApremainoragentmainagentgetsanInstrumentationobjectasthesecondargument.Thisobjectimplementsseveralmethods.Oneofthemis:
voidaddTransformer(ClassFileTransformertransformer)
Theagentimplementsthetransformer,andithasthetransformmethodsignature:
byte[]transform(Modulemodule,ClassLoaderloader,
StringclassName,
Class<?>classBeingRedefined,
ProtectionDomainprotectionDomain,
byte[]classfileBuffer)
throwsIllegalClassFormatException
ThismethodiscalledbytheJVMwhenaclassisloadedorwhenitistobetransformed.Themethodgetstheclassobjectitself,but,moreimportantly,itgetsthebytearraycontainingthebytecodeoftheclass.Themethodisexpectedtoreturnthebytecodeofthetransformedclass.Modifyingthebytecodeneedssomeknowledgeofhowthebytecodeisbuiltandwhatthestructureofaclassfileis.Therearelibrariesthathelptodothat,suchasJavassist(http://www.javassist.org/)orASM(http://asm.ow2.org/).Nevertheless,Iwillnotstartcodingbeforegettingacquaintedwiththestructureofthebytecode.
Agents,runninginaseparatethreadandpresumablyinteractingwiththeuserorthefilesystemandbaseduponsomeexternalobservationatanytime,maycallthefollowingmethodtoperformtheretransformationoftheclassesusingtheregisteredtransformers:
voidretransformClasses(Class<?>...classes)
Theagentcanalsocallthefollowingmethod,whichwillredefinetheclassesgivenasarguments:
voidredefineClasses(ClassDefinition...definitions)
TheClassDefinitionclassissimplyaClassandabyte[]pair.ThiswillredefinetheclassesthroughtheclassmaintainingmechanismoftheJVM.
NotethatthesemethodsandJavaagentsinteractwiththedeep,low-levelpartoftheJVM.ThisalsobearstheconsequencethatitisveryeasytodestroythewholeJVM.Thebytecodeisnotchecked,unlikeduringtheloadingoftheclass,andthus,ifthereissomeerrorinit,theconsequencemaynotonlybeanexceptionbutalsothecrashingoftheJVM.Also,theredefinitionandthetransformationsshouldnotalterthestructureoftheclasses.Theyshouldnotchangetheirinheritancefootprint,add,rename,orremovemethods,orchangethesignatureofthemethods,andthisisalsotrueforfields.
Alsonotethatthealreadycreatedobjectswillnotbeaffectedbythechanges;theywillstillusetheolddefinitionoftheclassandonlynewinstanceswillbeaffected.
PolyglotprogrammingPolyglotprogrammingisthetechniquewhentherearedifferentprogramminglanguagesusedinthesameapplication.Suchanapproachisnotonlyappropriatewhenadifferentpartoftheapplicationrunsonadifferentenvironment.Forexample,theclientexecutesinthebrowserusingJavaScript,CSS,andHTMLwhiletheserverisprogrammedtoruninaTomcatenvironmentinJava.Thisisadifferentstory,and,usually,thisisnotthetypicalusewhensomeoneisspeakingaboutpolyglotprogramming.
WhentheapplicationthatrunsontheserverpartiallyrunsinJavaandalsoinsomeotherlanguage,thenwecanspeakaboutpolyglotprogramming.Forexample,wecreatetheorderhandlingapplicationinJavaandsomeofthecodethatchecksthecorrectnessoftheorderbasedontheproduct-specificcodesthattheordercontainsiswritteninJavaScript.Doesitringabell?WehavealreadydonethatinthisbooktodemonstratethescriptingAPIoftheJDK.Thatwasrealpolyglotprogramingevenifwedidnotmentionitthatway.
TheJVMthatrunsthecompiledJavacodeisaverygoodtargetfordifferentlanguagecompilers,andthus,therearemanylanguagesthatcompileforit.WhentheJVMrunsthebytecodeofaclass,itdoesnotknowwhatthesourcelanguagewas,anditdoesnotreallycare;somecompilercreatedthebytecodeanditjustexecutesthat.
Wecanusedifferentlanguages,suchasJython,Groovy,andScala,tonameafewpopularonesthatcompilefortheJVM.Wecanwriteoneclassusingonelanguageandtheotheroneusinganother.WhentheyareputtogetherintoaJAR,WAR,oranEARfile,theruntimesystemwilljustrunthem.
Whendoweusepolyglotprogramming?
PolyglotconfigurationUsually,weturntowardspolyglotprogrammingwhenwewanttocreateanapplicationthatismoreflexibleandmoreconfigurable.Applicationsthatgetinstalledinmanyinstances,usually,atdifferentcustomersiteshavesomeconfigurations.TheseconfigurationscanbeXMLfiles,propertiesfiles,andINIfiles(thosecomefromWindows).Astheprogramsdevelopsoonerorlater,thesestaticconfigurationpossibilitiesreachtheirlimits.Applicationdeveloperssoonseethattheyneedtoconfiguresomefunctionalitythatiscumbersometodescribeusingthesetechnologies.Configurationfilesstartbeinglargerand,also,thecodethatreadsandinterpretstheconfigurationfilesgrowlarge.Gooddevelopershavetorealizethatthisisthesituation,andbeforetheconfigurationfilesandthecodehandlingthembecomeunmanageable,somescriptingconfiguration,polyglotprogramminghastobeimplemented.
Decentdeveloperteamsmayreachapointwhentheydeveloptheirconfigurationlanguageandtheinterpreterofthatlanguage.ItcanbebasedonXML,oritcanjustbeanyotherlanguage.Afterall,writingalanguageisfun;Ihavedoneitafewtimesmyself.Mostofthesewere,however,hobbiesandnotprofessionalprojects.Usually,thereisnocustomervalueincraftinganotherlanguage.Wecanbetteruseanexistingone.
Inthecaseofconfiguration,Groovyisaveryhandylanguagethatsupportscomplexclosureandmeta-classsyntaxandimplementation.Thisway,thelanguageisextremelysuitabletocreateadomain-specificlanguage.SinceGroovyiscompiledtoJVM,GroovyclassescanbeinvokeddirectlyfromJava,andintheotherwayround,readingtheconfigurationisessentiallyinvokingtheclasscompiledfromtheconfigurationfile.Thecompilationcanbeduringapplicationbuildtime,butinthecaseofconfiguration,itmakesmoresensetodoitduringapplicationstartup.WehavealreadyseenthattheGroovyimplementationofthescriptingAPIorthespecialAPIthatGroovyprovidesisabsolutelycapableofdoingthat.
Haveweseenexamplesofthisinourbook?Itmaybeasurprisetoyou,butwehaveinfactusedGroovytodescribesomeconfigurationmanytimes.GradlebuildfilesarenothingmorethanGroovyDSLdevelopedmainlyinGroovytosupportprojectbuildconfiguration.
PolyglotscriptingConfigurationisnottheonlyapplicationofpolyglotprogramming.Configurationisexecutedattheprogramstartupandtheconfigurationdataisusedasstaticdataafterwards.Wecanexecutescriptsduringtheapplication'sexecutionanytimeandnotonlyduringitsstartup.Thiscanbeusedtoprovideextrafunctionalitytotheprogram'suserwithinstallationsthatusethesameapplicationbutarefurnishedwithdifferentscripts.
Oneofthefirstapplicationsthatprovidedsuchscriptingcapabilitywastheemacseditor.ThecoreoftheapplicationwaswritteninClanguageanditcontainedaLispinterpreterthatlettheusertowritescripts,whichwereexecutedintheeditorenvironment.Theengineeringprogram,AutoCAD,alsousedaLispinterpreterforsimilarpurposes.WhywasLispusedforthispurpose?Lisphasverysimplesyntax,andtherefore,itiseasytoparseLispcode.Atthesametime,thelanguageispowerful,andlastbutnotleast,therewereopensourceLispinterpreters(atleastone)availablebythetime.
Togetthiskindofflexibility,applications,manytimes,providepluginAPIs,whichadevelopercanusetoextendtheapplication.This,however,requiresthatthedevelopersetsupcodingtools,includingIDE,buildtool,continuousintegration,andsoon,thatis,aprofessionalprogrammingenvironment.Whenthetasktobesolvedbythepluginissimple,theoverheadissimplytoolarge.Insuchacase,ascriptingsolutionishandier.
Scriptingisnotasolutionforeverything.Whenthescriptsextendingtheapplicationtendtobecometoocomplex,itmeansthatthescriptingpossibilityisjusttoomuch.Itisdifficult,however,totakebackatoyfromachild.Ifusersgetusedtothescriptingpossibility,thentheywillnottakeiteasyifthenextversionoftheapplicationwereleasedoesnotprovidethatpossibility.Thus,itisextremelyimportanttoassessthepossibleuseofthescriptingcapabilityinourapplication.Scriptingand,moregenerally,anyfeatureofourprogramwillnotbeusedforwhatweintendedthemfor.Theywillbeusedforwhateveritispossibletousethemfor.Userscangobeyondallimaginationwhenitcomestoabusingsomefeature.Itmaybeagoodideatothinkaboutlimitingthescriptingpossibilitybeforehand,limitingtherunningtimeofthescriptsorthesizeofthescriptourprogramagreestoworkwith.Iftheselimitationsaresetreasonably,andtheusersunderstandandacceptthese,apluginstructureinadditiontothescriptingcapabilityhastobeconsidered.
Thesecurityofanapplication,includingpluginorscriptingextension,isalsoveryimportant.ThescriptsorpluginsrunonthesameJVMasthecoreapplication.Somescriptinglanguagesprovidesomefencearoundthescriptsthatlimitstheaccesstothecoreapplication'sobjectsandclasses,butthisisanexception.Usually,scriptsrunwiththesameprivilegeasthecoreapplicationandthatwaytheycandojustanything.Thus,scriptsshouldbetrustedthesamewayasthecoreapplication.Scriptinstallationormodificationshouldneverbepossibleforanunprivilegeduseroftheapplication.Suchanactionisalmostalwaysreservedforthesystemadministrator.
Ifanunprivilegedusercanuploadascripttotheserverandthenhaveitexecuted,wejustopenedasecurityholeinourapplication.Sinceaccessrestrictionsareenforcedbytheapplication,itiseasyto
overridetheselimitationsusinganuncontrolledscript.Thehackercanjustaccessotherusers'dataeasily,whichheisnotentitledto,andreadandmodifyourdatabase.
BusinessDSLPolyglotprogrammingmayalsocomeintothepicturewhentheapplication'scodecanbeseparatedintobusinesscodeandtechnologycode.Thebusinesscodecontainsthetop-levelbusinesslogicthatweactuallywritetheapplicationfor,andthisisthecodethatcontainsthelogicthatthecustomerpaysfor.ThetechnologycodeistosupportthealgorithmscodedinthebusinessDSL.
Mostoftheenterpriseapplicationscontainthesetwotypesofcodebutmanydonotseparatethem.Thisleadstoamonolithicapplicationthatcontainsrepetitivecode.Whenyoufeelthatyouarewritingthesametypeofcodewhenyouneedpersistenceornetworking,andagainthesametypeofcodewhilecodingsomebusinessrules,thenthisisthecodesmellthatsuggeststhatthetwocodetypesarenotseparated.DSLandscriptingarenotamagicwandanddonotsolvealltheproblemsthatstemfromawrongapplicationstructure.Insuchasituation,thecodehastoberefactoredfirsttoseparatethebusinesslogicandtheinfrastructurecode,anditisonlythesecondsteptoimplementaDSLandabusinessAPIsupportingitandtorewritethebusinesscodeintotheDSL.EverystepofsuchaprojectdeliversvaluefortheapplicationandevenifitnevergetstoDSLandscripting,theeffortinvestedisnotwasted.
ThebusinessDSLscriptingisverysimilartopluggablescripts,exceptthatthistimeitisnottheapplicationthatcallsthescriptsfromtimetotimetoexecutesomespecialextensionfunctionality.Instead,theDSLcodecallstheapplicationthroughthebusinessAPIthatitprovides.TheadvantageofprovidingtheAPIandusingaDSListhatthecodethatimplementsthebusinesslogicgetsridofthetechnicaldetails,canbeveryabstract,and,thisway,bemuchclosertoabusiness-leveldescriptionoftheproblemratherthanjustprogramcode.EvensomebusinesspersoncanunderstandabusinessDSL,andthoughitisnotagoalinreal-lifeexamples,theycouldevenwritecode.
AtTUVienna,wealsousedasimilarapproachtomakesemiconductorsimulationmoreusableforthesemiconductordesignengineer.ThecorecalculatingcodewaswritteninFortran.AClanguageframeworkthathandledthemassivesimulationdatainputandoutputandthatembeddedtheXLISPinterpreterexecutedtheseprograms.TheLispcodecontainedthesimulationconfigurationdataandcouldalsocontainsimpleloopswhenthesimulationwastobeexecutedformanyconfigurationpoints.Itwaspolyglotprogramming,exceptthatwedidnotknowthatthisisgoingtobethenameyearsafterthisapplicationcodingstyle.
ProblemswithpolyglotPolyglotprogrammingisnotonlyallaboutadvantages.Beforejumpingintothisdirection,developersmakingthedecisionhavetoconsideralotofthings.
Usinganotherlanguagefortheapplicationneedsknowledge.FindingpeoplewhocancodeinthelanguagesthatareusediseventuallymoredifficultthanfindingdeveloperswhoonlyknowJava.(ThisisalsotrueifthekernelapplicationlanguageisnotJava.)Differentlanguagesrequiredifferentmindsetsand,manytimes,differentpeople.Theteamshouldalsohavesomememberswhoareproficientinbothlanguages,anditisalsoanadvantageifmostofthepeopleknowatleastabitabouttheotherlanguage.
ThetoolsetsupportingJavaisoutstanding.Thebuildtools,integrateddevelopmentenvironment,libraries,debuggingpossibilities,andloggingframeworks,tonameafew,areallextremelygoodcomparedwithotherlanguages.Polyglotdevelopmentneedssupportfortheotherlanguageaswell,whichmaynotbeasadvancedasthesupportforJava.Manytimes,itisreallyanissuetodebugDSLsolutionsandIDEsupportmayalsobelagging.
WhenweprograminJava,manytimes,wetakeforgrantedthattheIDEreadsthemeta-dataofthelibrariesandwheneverweneedtocallamethod,orreferenceaclass,theIDEsuggeststhebestpossibility.XMLandpropertiesfilesmayalsobesupportedandtheIDEmayknowsomeofthemostusedframeworks,suchasSpring,andunderstandtheXMLconfigurationhandlingthenamesoftheclassesashyperlinks,evenwhentheclassnamesareinsidesomeattributestrings.
Thisisfarfrombeingthiseasyinthecaseofotherlanguages.Forthelanguagesthathaveawideuserbase,thetoolingsupportmaybegood,butifyoupicksomeexoticlanguage,youareonyourown.Themoreexoticthelanguagethelesssupportyoumayhave.
YoucancreatesometooltosupportyourDSLthatyoudevelop.Itisnothardtodosousingtoolssuchashttp://www.eclipse.org/Xtext/.Insuchacase,youaretiedtoEclipse,whichmayormaynotbeaproblem.Youcanpickaspeciallanguage,forexample,Kotlin,whichisextensivelysupportedbyIntelliJ,becausethesamecompanysupportsthelanguageandtheIDE,butagain,youbuyintoaspecialtechnologythatcanbeexpensivetoreplaceincaseyouhaveto.Itisgenerallytruenotonlyforlanguagesbutalsoforanytechnologyyouincludeintoyourdevelopment.Whenyouselectone,youshouldconsiderthesupportandthecostofgettingoffthehorseiforwhenitstartsdying.
AnnotationprocessingWehavealreadydiscussedannotationsingreatdetail.Youmayrecallthatwedefinedourannotationinterfacesusingthefollowingannotation:
@Retention(RetentionPolicy.RUNTIME)
ThistoldtheJavacompilertokeeptheannotationandputitintotheJVMcodesothatthecodecanaccessitduringruntimeusingreflection.ThedefaultvalueisRetentionPolicy.CLASS,whichmeansthattheannotationgetsintothebytecode,buttheJVMdoesnotmakeitavailablefortheruntimesystem.IfweuseRetentionPolicy.SOURCE,theannotationdoesnotevengetintotheclassfile.Inthiscase,thereisonlyonepossibilitytodoanythingwiththeannotation:compiletime.
Howcanwewritecodethatrunsduringcompiletime?Javasupportsthenotionofannotationprocessors.Ifthereisaclassontheclasspathofthecompilerthatimplementsthejavax.annotation.processing.Processorinterface,thenthecompilerwillinvoketheimplementedmethodsoneormoretimes,passinginformationaboutthesourcefilethatthecompilerisactuallyprocessing.Themethodswillbeabletoaccessthecompiledmethods,classes,orwhateverisannotated,andalsotheannotationthattriggeredtheprocessorinvocation.Itisimportant,however,thatthisaccessisnotthesameasinruntime.Whattheannotationprocessoraccessesisneitheracompilednoraloadedclass,thatis,itisavailablewhenthecodeusesreflection.Thesourcefileatthistimeisundercompilation;thus,thedatastructuresthatdescribethecodeareactuallystructuresofthecompiler,aswewillseeinournextexample.
Theannotationprocessoriscalledoneormoretimes.Thereasonitisinvokedmanytimesisthatthecompilermakesitpossiblefortheannotationprocessorstogeneratesourcecodebasedonwhatitseesinthepartiallycompiledsourcecode.IftheannotationprocessorgeneratesanyJavasourcefile,thecompilerhastocompilethenewsourcecodeandperhapscompilesomeofthealreadycompiledfilesagain.Thisnewcompilationphaseneedsannotationprocessorsupportuntiltherearenomoreroundstoexecute.
Annotationprocessorsareexecutedoneaftertheother,andtheyworkonthesamesetofsourcefiles.Thereisnowaytospecifytheorderoftheannotationprocessorexecutions;thus,twoprocessorsworkingtogethershouldperformtheirtasks,nomatterinwhatordertheyareinvoked.Also,notethatthesecodesruninsidethecompiler.Ifanannotationprocessorthrowsanexception,thenthecompilationprocesswillmostprobablyfail.Thus,throwinganexceptionoutoftheannotationprocessorshouldonlybedoneifthereisanerrorthatcannotberecoveredandtheannotationprocessordecidesthatthecompilationafterthaterrorcannotbecomplete.
Whenthecompilergetstothephasetoexecutetheannotationprocessors,itlooksattheclassesthatimplementthejavax.annotation.processing.Processorinterfaceandcreatesinstancesoftheseclasses.Theseclasseshavetohaveapublicno-argumentconstructor.Tostreamlinetheexecutionoftheprocessorsandtoinvokeaprocessoronlyfortheannotationsthatitcanhandle,theinterfacecontainstwomethods:
getSupportedSourceVersiontoreturnthelatestversiontheannotationprocessorcansupportgetSupportedAnnotationTypestoreturnasetofStringobjectscontainingthefullyqualifiedclassnameof
theannotationsthatthisprocessorcanhandle
IfanannotationprocessorwascreatedforJava1.8,itmayworkwithJava9,butitmayalsonotwork.Ifitdeclaresthatthelatestsupportedversionis1.8,thenthecompilerinaJava9environmentwillnotinvokeit.Itisbetternottoinvokeanannotationprocessorthancallingitandmessingupthecompilationprocess,whichmayevencreatecompiledbuterroneouscode.
Thevaluesreturnedbythesemethodsarefairlyconstantforanannotationprocessor.Anannotationprocessorwillreturnthesamesourceversionitcanhandleandwillreturnthesamesetofannotations.Therefore,itwouldbeclevertohavesomewaytodefinethesevaluesinthesourcecodeinadeclarativemanner.
Itcanbedonewhenweextendthejavax.annotation.processing.AbstractProcessorclassinsteadofdirectlyimplementingtheProcessorinterface.Thisabstractclassimplementsthesemethods.Bothofthemgettheinformationfromtheannotationsothatwecandecoratetheclassthatextendstheabstractclass.Forexample,thegetSupportedAnnotationTypesmethodlooksattheSupportedAnnotationTypesannotationandreturnsanarrayofannotationtypestringsthatarelistedintheannotation.
Now,thisisabitbraintwistingandcanalsobeconfusingatfirst.Weareexecutingourannotationprocessorduringcompiletime.ButthecompileritselfisaJavaapplication,andinthisway,thetimeisruntimeforthecodethatrunsinsidethecompiler.ThecodeofAbstractProcessoraccessestheSupportedAnnotationTypesannotationasaruntimeannotationusingreflectionmethods.Thereisnomagicinit.ThemethodintheJDK9isasfollows:
publicSet<String>getSupportedAnnotationTypes(){
SupportedAnnotationTypessat=this.getClass().getAnnotation
(SupportedAnnotationTypes.class);
if(sat==null){
...errormessageissenttocompileroutput...
returnCollections.emptySet();
}
else
returnarrayToSet(sat.value());
}
(Thecodehasbeeneditedforbrevity.)
Tohaveanexample,wewillsortoflookatthecodeofapolyglotannotationprocessor.Ourverysimpleannotationprocessorwillprocessonesimpleannotation:com.javax0.scriapt.CompileScript,whichcanspecifyascriptfile.TheannotationprocessorwillloadthescriptfileandexecuteitusingthescriptinginterfaceofJava9.
ThiscodewasdevelopedasademonstrationcodebytheauthorofthisbookafewyearsagoandisavailablewiththeApachelicensefromGitHub.Thus,thepackageoftheclassesisretained.
Theannotationprocessorcontainstwocodefiles.Oneoftheannotationitselfthattheprocessorwillworkon:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public@interfaceCompileScript{
Stringvalue();
Stringengine()default"";
}
Asyoucansee,thisannotationwillnotgetintotheclassfileaftercompilation;thus,therewillbenotraceduringruntimesothatanyclasssourcemayoccasionallyusethisannotation.TargetoftheannotationisElementType.TYPE,meaningthatthisannotationcanonlybeappliedtothoseJava9languageconstructsthataresomekindoftypes:class,interface,andenum.
Theannotationhastwoparameters.Thevalueshouldspecifythenameofthescriptfile,andtheenginemayoptionallydefinethetypeofthescriptthatisinthatfile.Theimplementationwe'llcreatewilltrytoidentifythetypeofthescriptfromthefilenameextension,butifsomebodywouldliketoburysomeGroovycodeintoafilethathasthe.jyextension(whichisusuallyforJython),sobeit.
TheprocessorextendsAbstractProcessorand,inthisway,someofthemethodsareinheritedattheexpenseofsomeannotationsusedintheclass:
packagecom.javax0.scriapt;
import...
@SupportedAnnotationTypes("com.javax0.scriapt.CompileScript")
@SupportedSourceVersion(SourceVersion.RELEASE_9)
publicclassProcessorextendsAbstractProcessor{
ThereisnoneedtoimplementthegetSupportedAnnotationTypesandgetSupportedSourceVersionmethods.Thesearereplacedbytheuseoftheannotationsontheclass.Wesupportonlyoneannotationinthisprocessor,theonethatwedefinedinthepreviouslylistedsourcefile,andwearepreparedtomanagethesourcecodeuptoJavaversion9.Theonlymethodwehavetooverrideisprocess:
@Override
publicbooleanprocess(
finalSet<?extendsTypeElement>annotations,
finalRoundEnvironmentroundEnv){
for(finalElementrootElement:
roundEnv.getRootElements()){
try{
processClass(rootElement);
}
catch(Exceptione){
thrownewRuntimeException(e);
}
}
returnfalse;
}
Thismethodgetstwoarguments.Thefirstisthesetofannotationsthatitwasinvokedfor.Thesecondistheroundenvironment.Becausetheprocessorcanbeinvokedmanytimes,thedifferentinvocationsmayhavedifferentenvironments.EachinvocationisinaroundandtheRoundEnvironmentargumentisanobjectthatcanbeusedtogetinformationaboutthegivenround.Itcanbeusedtogettherootelementsoftheroundforwhichthisannotationisinvoked.Inourcase,thiswillbeasetofclasselementsthathavetheCompileScriptannotation.Weiterateoverthisset,andforeachclass,weinvoketheprocessClassmethod(seethenextcodesnippet).Themethodmaythrowsomecheckedexceptionandthemethodprocesscannotbecauseitshouldmatchthesamemethodoftheinterface.Thus,wecatchanyexceptionthatmaybethrownandwere-throwtheseencapsulatedinRunTimeException.Ifanyoftheseexceptionsarethrownbythecalledmethod,thenthecompilationcouldnotrunthescriptsanditshouldbetreatedasfailed.Thecompilationshouldnotsucceedinsuchacase:
privatevoidprocessClass(finalElementelement)
throwsScriptException,FileNotFoundException{
for(finalAnnotationMirrorannotationMirror:
element.getAnnotationMirrors()){
processAnnotation(annotationMirror);
}
}
Theactualannotationisnotavailableduringcompiletimeaswealreadymentioned.Hence,whatwehaveavailableisonlyacompiletimemirrorimageoftheannotation.IthastheAnnotationMirrortype,whichcanbeusedtogettheactualtypeoftheannotationand,also,thevaluesoftheannotation.Thetypeoftheannotationisavailableduringcompiletime.Thecompilerneedsit;otherwise,itcouldnotcompiletheannotation.Thevaluesareavailablefromtheannotationitself.OurprocessAnnotationmethodhandleseachannotationitgetsasanargument:
privatevoidprocessAnnotation(
finalAnnotationMirrorannotationMirror)
throwsScriptException,FileNotFoundException{
finalStringscript=
FromThe.annotation(annotationMirror).
getStringValue();
finalStringengine=
FromThe.annotation(annotationMirror).
getStringValue("engine");
execute(script,engine);
}
Our@CompileScriptannotationdefinestwoparameters.Thefirstvalueisthescriptfilenameandthesecondoneisthescriptingenginename.Ifthesecondoneisnotspecified,thenanemptystringissetasthedefaultvalue.Theexecutemethodiscalledforeachandeveryoccasionoftheannotation:
privatevoidexecute(finalStringscriptFileName,
finalStringengineName)
throwsScriptException,FileNotFoundException{
finalScriptEngineManagerfactory=
newScriptEngineManager();
finalScriptEngineengine;
if(engineName!=null&&engineName.length()>0){
engine=factory.getEngineByName(engineName);
}
else{
engine=
factory.getEngineByExtension
(getExtensionFrom(scriptFileName));
}
ReaderscriptFileReader=newFileReader
(newFile(scriptFileName));
engine.eval(scriptFileReader);
}
Themethodtriestoloadthescript,basedonthefilename,andtriestoinstantiatethescriptengine,basedonthegivenname.Ifthereisnonamegiven,thenthefilenameextensionisusedtoidentifythescriptingengine.Bydefault,theJavaScriptengineisontheclasspathasitispartoftheJDK.IfanyotherJVM-basedscriptingengineisinuse,thenithastobemadeavailableontheclasspathoronthemodulepath.
Thelastmethodoftheclassisasimplescriptmanipulationmethod,nothingspecial.Itjustchopsoffthefilenameextensionsothattheenginecanbeidentifiedbasedontheextensionstring:
privateStringgetExtensionFrom(finalStringscriptFileName){
finalintindexOfExtension=scriptFileName.lastIndexOf('.');
if(indexOfExtension==-1){
return"";
}
else{
returnscriptFileName.substring(indexOfExtension+1);
}
}
Andjustforthesakeofcompleteness,wehavetheclosingbraceoftheclass:
}
ProgrammingintheenterpriseWhenaprofessionalworksforanenterprise,shedoesnotworkalone.Therearealotofpeople,developersaswellasothercoworkers,wehavetocooperatewith.TheoldertheITdepartmentoftheenterpriseis,andthelargertheenterpriseis,themorespecializedrolespeoplearein.Youwillcertainlymeetbusinessanalysts,projectmanagers,testengineers,buildengineers,subject-matterexperts,testers,architects,scrummasters,andautomationengineers,tonameafewroles.Someoftheserolesmayoverlap,nopersonmayhavemorethanoneresponsibility,andwhileinothercases,somerolescouldevenbemorespecialized.Someoftherolesareverytechnicalandrequirelessbusiness-relatedknowledge;othersaremorebusinessoriented.
Workingtogetherasateamwithsomanypeopleandwithsomanydifferentrolesisnotsimple.Thecomplexityofthetaskmaybeoverwhelmingforanovicedeveloperandcannotbedonewithoutdefinitepoliciesthatallmembersoftheoperationfollow,moreorless.Perhapsyourexperiencewillshowthatitismoretimeslessthanmore,butthatisadifferentstory.
Forthewaydevelopersworktogether,therearewell-establishedindustrypractices.ThesesupporttheSoftwareDevelopmentLifecycle(SDLC)usingwaterfall,agile,oramixofthetwomodelsinsomeway.Inthefollowingsections,wewilllookattoolsandtechniquesthatare,oratleastshouldhavebeen,usedineverysoftwaredevelopmentorganization.Theseare:
StaticcodeanalysistoolsthatcontrolthequalityofthecodeexaminingthesourcecodeSourcecodeversioncontrolthatstoresalltheversionsofthesourcecodeandhelpgetthesourcecodeforanyoldversionofthedevelopmentSoftwareversioningtokeepsomeorderofhowweidentifythedifferentversionsanddonotgetlostamongthedifferentversionsCodereviewandtoolsthathelpinpin-pointingbugsthatarenotrevealedbytestsandaidknowledgesharingKnowledgebasetoolstorecordanddocumentthefindingsIssuetrackingtoolsthatrecordbugs,customerissues,andothertasksthatsomebodyhastoattendtoSelectionprocessandconsiderationsforexternalproductsandlibrariesContinuousintegrationthatkeepsthesoftwareinaconsistentstateandreportsimmediatelyifthereissomeerrorinitbeforetheerrorpropagatestootherversionsorothercode,dependingonhowtheerroneouscodegetsdevelopedReleasemanagement,whichkeepstrackofthedifferentreleaseversionsofthesoftwareCoderepository,whichstoresthecompiledandpackedartifacts
Thefollowingdiagramshowsthemostwidelyusedtoolsforthesetasks:
StaticcodeanalysisStaticcodeanalysistoolsreadthecodejustlikethecompilerandanalyzeit,butinsteadofcompilation,theytrytofinderrorsormistakesinit.Notthesyntaxerrors.Forthat,wealreadyhavetheJavacompiler.Mistakes,suchasusingaloopvariableoutsidealoop,whichmaybeabsolutelyvalidbutisusuallybadstyleand,manytimes,suchusagecomesfromsomesimplemistakes.Theyalsocheckthatthecodefollowsthestylingrulesthatweset.
Staticcodeanalyzershelpidentifymanysmallandobviouserrorsinthecode.Sometimes,theyareannoying,warningaboutsomethingthatmaynotbereallyaproblem.Insuchacase,itisbettertocodetheprogramabitdifferently,notbecausewewantthestaticcodeanalysistorunwithoutwarning.Weshouldnevermodifythecodebecauseofatool.Ifwecodesomethinginsuchawaythatitpassessomequalitychecktoolandnotbecauseitisbetterthatway,thenweareservingthetoolsinsteadofthetoolsservingus.
Thereasontochangethecodetopassthecodeanalysisisthatitisveryprobablethatthecodeismorereadabletoanaverageprogrammerifitdoesnotviolatethecodingstyle.Youortheotherteammemberscanbeexcellentprogrammerswhounderstandthecodeveryeasilyevenifitusessomespecialconstruct.However,youcannotsaythataboutalltheprogrammerswhowillmaintainyourcodeinthefuture.Thecodelivesalonglife.Iworkwithsomeprogramsthathavebeenwritten50yearsago.Theyarestillrunningandmaintainedbyyoungprofessionalsaroundtheageof30.Itmeansthattheywerenotevenbornwhenthecodewasdeveloped.Itcaneasilyhappenthatthepersonmaintainingyourcodeisnotevenbornbythetimeyouwritethecode.Youcannottellanythingabouttheirclevernessandcodingpractices.Thebestwecandoistopreparefortheaverageandthatisexactlywhatstaticcodeanalysistoolsaresetfor.
Thechecksthatthesetoolsperformarenothardwiredintothetools.Somespeciallanguageinsidethetoolsdescribestherulesandtheycanbedeleted,otherrulescanbeadded,andrulescanbemodified.Thisway,youcanaccommodatethecodingstandardsoftheenterpriseyouworkfor.Thedifferentrulescanbecategorizedascosmetic,minor,major,andcritical.Cosmeticthingsaremainlywarningsandwedonotreallycareaboutthem,eventhoughitisnicetofixeventheseissues.Sometimes,thesesmallthingsmaysignalsomereallybigissue.Wecansetlimitsforthenumberofminorandmajorbugsbeforethecheckisdeclaredasfailingandalsoforthecriticalerrors.Inthelastcase,thislimitisusuallyzero.Ifacodingerrorseemstobecritical,thenbetternothaveanyinthecode.
ThemostfrequentlyusedtoolsareCheckstyle,FindBugs,andPMD.Theexecutionofthesetoolsisusuallyautomated,andthoughtheycanbeexecutedfromtheIDEorfromthedeveloper'scommandline,theirmainuseisonthecontinuousintegration(CI)server.Duringthebuild,thesetoolsareconfiguredontheCIservertorun,anditcanbeconfiguredsuchthatthebuildshouldbebrokenifthestaticcodeanalysisfailswithsomelimit.Executingthestaticcodeanalysisisusuallythenextstepaftercompilationandunittestexecution,andbeforetheactualpackaging.
TheSonarQubetool(https://www.sonarqube.org/)isaspecialtoolinadditiontobeingastaticcodeanalysistool.SonarQubemaintainsthehistoryofthepreviouschecksaswellassupportsunittestcodecoverageandcanreportthechangeofthequalityovertime.Thisway,youcanseehowthequality,coveragepercentage,andnumberofdifferentqualificationsofcodestyleerrorshavechanged.Manytimes,youcan
seethatwhenapproachingthereleasedate,thecodequalitydecreasesaspeopleareinarush.Thisisverybadbecausethisisthetimewhenmostofthebugsshouldbeeliminated.Havingastatisticaboutthequalitymayhelpchangethepracticebyseeingthetrendsbeforethequality,andthusthemaintainabilityofthecodegetsoutofhand.
SourcecodeversioncontrolSourcecodeversioncontrolsystemsstoredifferentversionsofthesourcecode.Thesedays,wecannotimagineprofessionalsoftwaredevelopmentwithoutit.Thiswasnotalwaysthecase,buttheavailabilityoffreeonlinerepositoriesencouragedhobbydeveloperstousesomeversioncontrol,andwhenthesedevelopersworkedforenterpriseslater,itwasevidentthattheuseofthesesystemsiskindofamust.
Therearemanydifferentrevisioncontrolsystems.ThemostwidelyusedoneisGit.TheversioncontrolthatwaspreviouslywidelyusedwasSVNand,evenbeforethat,CVS.Thesearelessandlessusedthesedays.WecanseeSVNasasuccessorofCVSandGitasasuccessorofSVN.Inadditiontothese,thereareotherversioncontrolsystemssuchasMercurial,Bazaar,orVisualStudioTeamServices.Foracomprehensivelistoftheavailabletools,visittheWikipediapageathttps://en.wikipedia.org/wiki/List_of_version_control_software.
MybetisthatyouwillmeetGitinthefirstplaceandthereisahighprobabilityofyoucomingacrossSVNwhenprogrammingforanenterprise.Mercurymayappearinyourpracticebutanyoftheothersthatcurrentlyexistareveryrare,areusedforaspecificarea,oraresimplyextinct.
Versioncontrolsystemsallowthedevelopmentteamtostorethedifferentversionsofthesoftwareinanorganizedmanneronastoragethatismaintained(backedupregularlyinareliablemanner).Thisisimportantfordifferentpurposes.
Thefirstthingisthatdifferentversionsofthesoftwaremaybedeployedtodifferentinstances.Ifwedevelopsoftwareforclientsandwehavemanyclientswithwhomwehopetohavetomakeaterrificbusiness,thendifferentclientsmayhavedifferentversions.Thisisnotonlybecausesomeclientsarereluctanttopayfortheupgrade,andwejustdonotwanttogivethenewversionforfree.Manytimes,thecoststhatriseonthesideofthecustomerpreventtheupgradeforalongtime.Softwareproductsdonotworkontheirowninanisolatedenvironment.Differentclientshavedifferentintegratedenvironments;thesoftwarecommunicateswithdifferentotherapplications.Whenanewversionistobeintroducedinanenterpriseenvironment,ithastobetestedforwhetheritworkswithallthesystemsithastocooperatewith.Thistestingtakesalotofeffortandmoney.Ifthenewfeaturesorothervaluesthatthenewversiondeliversovertheoldonedonotjustifythecost,thenitwouldbewasteofmoneytodeploythenewversion.Thefactthatthereisanewversionofoursoftwaredoesnotmeanthattheoldversionsarenotusable.
Ifthereissomebugatthecustomer'send,thenitisvitalthatwefixthebuginthatversion.Todoso,thebughastobereproducedinthedevelopmentenvironment,whicheventuallymeansthatthesourcecodeforthatversionhastobeavailableforthedevelopers.
Thisdoesrequirethecustomerdatabasetocontainreferencestothedifferentversionsofoursoftwareproductsthatareinstalledatthecustomersite.Tomakeitmorecomplicated,acustomermayhavemorethanoneversionatatimeindifferentsystemsandmayalsohavedifferentlicenses,sotheissueismorecomplexthanitfirstseems.Ifwedonotknowwhichversiontheclienthas,thenweareintrouble.Sincethedatabaseregisteringtheversionsforthecustomersandreallifemayget
unsynchronized,softwareproductslogtheirversionatstartup.Wehaveaseparatesectionaboutversioninginthischapter.
Ifthebugisfixedintheversionthattheclienthas,theincidentatthecustomer'sendmaybesolvedafterdeployment.Theproblem,though,stillremainsiftheversionisnotthepreviousversionofthesoftware.Thebugfixintroducedtoanoldversionofthesoftwaremaystillbelurkingaroundinthelateror,forthatmatter,earlierversions.Thedevelopmentteamhastoidentifywhichversionsarerelevanttoclients.Forexample,anoldversionthatisnotinstalledanymoreatanyoftheclients'sitesdoesnotdeservetheinvestigation.Afterthat,therelevantversionshavetobeinvestigatedtocheckwhethertheyexhibitthebug.Thiscanonlybedoneifwehavethesourceversion.Someoldversionsmaynothavethebugifthecodecausingthebugisintroducedinlaterversions.Somenewversionsmayalsobeimmunetothebugbecausethebugwasalreadyfixedinthepreviousversion,orsimplybecausethepieceofcodethatcausedthebugwasrefactoredevenbeforethebugmanifested.Somebugsmayevenaffectaspecificversioninsteadofarangeofproducts.Bigfixingmaybeappliedtodifferentversionsandtheymayneedslightlydifferentfixes.Allthisneedsamaintainedsourceversionrepository.
Evenwhenwedonothavedifferentcustomerswithdifferentversions,itismorethanlikelythatwehavemorethanoneversionofoursoftwareindevelopment.Thedevelopmentofamajorreleaseiscomingtoanend,andtherefore,onepartoftheteamresponsiblefortestingandbugfixingfocusesonthoseactivities.Atthesametime,thedevelopmentoffeaturesforthenextversionstillgoeson.Thecodeimplementingthefunctionalitiesforthenextversionshouldnotgetintotheversionthatisabouttobereleased.Thenewcodemaybeveryfresh,untested,andmayintroducenewbugs.Itisverycommontointroducefreezetimesduringthereleaseprocess.Forexample,itmaybeforbiddentoimplementanynewfeatureoftheupcomingrelease.Thisiscalledfeaturefreeze.
Revisioncontrolsystemsdealwiththesefreezeperiods,maintainingdifferentbranchesofthecode.Thereleasewillbemaintainedinonebranchandtheversionforlaterreleasesinadifferentone.Whenthereleasegoesout,thebugfixesthatwereappliedtoitshouldalsobepropagatedtothenewerversion;otherwise,itmightsohappenthatthenextversionwillcontainbugsthatwerealreadyfixedinthepreviousversion.Todoso,thereleasebranchismergedwiththeongoingone.Thus,versioncontrolsystemsmaintainagraphoftheversions,whereeachversionofthecodeisanodeinthegraphandthechangesarevertices.
Gitgoesveryfarinthisdirection.Itsupportsbranchcreationandmergingsowellthatdeveloperscreateseparatebranchesforeachchangethattheycreateandthentheymergeitbackwiththemasterbranchwhenthefeaturedevelopmentisdone.Thisalsomakesforagoodopportunityforcodereview.ThedevelopermakingthefeaturedevelopmentorbugfixcreatesapullrequestintheGitHubapplication,andanotherdeveloperisrequestedtoreviewthechangeandperformthepull.Thisisakindoffour-eyedprincipleappliedtocodedevelopment.
Someoftherevisioncontrolsystemskeeptherepositoryonaserverandanychangegetstotheserver.Theadvantageofthisisthatanychangecommittedgetstoaserverdiskthatisregularlybackedupandisthussafe.Sincetheserver-sideaccessiscontrolled,anycodesenttotheservercannotberolledbackwithouttrace.Allversions,eventhewrongversions,arestoredontheserver.Thismayberequiredbysomelegalcontrol.Ontheotherhand,ifcommitrequiresnetworkaccessandserverinteraction,itmaybeslowandthiswill,inthelongrun,motivatedevelopersnottocommittheirchangesfrequently.Thelonger
achangeremainsonthelocalmachine,themoreriskwehaveoflosingsomeofthecode,andmergingbecomesmoreandmoredifficultwithtime.Tohealthissituation,Gitdistributestherepositoryandthecommithappenstothelocalrepository,whichisexactlythesameastheremoteoneonsomeserver.Therepositoriesaresynchronizedwhenonerepositorypushesthechangestoanotherone.Thisencouragesthedeveloperstomakefrequentcommitstotherepository,givingshortcommitmessages,whichhelpsintrackingthechangemadetothecode.
Someolderversioncontrolsystemssupportfilelocking.Thisway,whenadeveloperchecksoutacodefile,otherscannotworkonthesamepieceofcode.Thisessentiallyavoidsthecollisionsduringcodemerging.Overtheyears,thisapproachdidnotseemtofitthedevelopmentmethodologies.Mergeissuesarelessofaproblemthanfilesthatarecheckedoutandforgotten.SVNsupportsfilelockingbutthisisnotreallyseriousanddoesnotpreventonedevelopertocommitchangestoafilethatsomebodyelselocked.Itismoreofonlyasuggestionthanreallocking.
Sourcecoderepositoriesareveryimportantbutshouldnotbeconfusedwithreleaserepositories,whichstorethecompiledreleasedversionofthecodeinbinary.Sourceandreleaserepositoriesworktogether.
SoftwareversioningSoftwareversioningismagic.ThinkaboutthedifferentversionsofWindowsorStarWarsmovies.Well,thelatterisnotreallysoftwareversioningbutitshowsthattheissueisverygeneral.InthecaseofJava,versioningisnotthatcomplex.Firstofall,theversionofJavaweusenowis9.Thepreviousversionwas1.8,beforethat1.7,andsoon,downto1.0.EarlierversionsofJavawerecalledOakbutthatishistory.Afterall,thatis,whocantellwhatJava2was?
Fortunately,whenwecreateaJavaapplication,thesituationissimpler.TherehasbeenasuggestionfromOracle,fromthetimeofJava1.3,abouthowtoversionJARs:
http://docs.oracle.com/javase/7/docs/technotes/guides/extensions/versioning.html
Thisdocumentdistinguishesbetweenspecificationversionandimplementationversion.IfthespecificationofaJARcontentchanges,thecodehastobehavedifferentlyfromhowitwasbehavingtillthen;thespecificationversionshouldchange.Ifthespecificationisnotchangedbuttheimplementationdoes--forexample,whenwefixabug--thentheimplementationversionchanges.
Inpractice,nobodyhasusedthisscheme,althoughitisabrilliantideatoseparatetheimplementationandspecificationversions,atleast,intheory.Ievenbetthatmostofyourcolleagueshavenoteveneverheardaboutthisversioning.Whatweuseinpracticeissemanticversioning.
Semanticversioning(http://semver.org/)mixesthespecificationandimplementationversionsintoonesingleversionnumbertriplet.Thistriplethastheformatofmmp,thatis:
m:majorversionnumberm:minorversionnumberp:patchnumber
Thespecificationsaysthatthesenumbersstartwithzeroandincreasebyone.Ifthemajornumberiszero,itmeansthatthesoftwareisstillindevelopment.Inthisstate,theAPIisnotstableandmaychangewithoutanewmajorversionnumber.Themajorversionnumbergetsto1whenthesoftwareisreleased.LaterithastobeincreasedwhentheAPIoftheapplication(library)haschangedfromthepreviousversionandtheapplicationisnotbackwardcompatiblewiththepreviousversion.Theminorversionnumberisincreasedwhenthechangeeffectsonlytheimplementationbutthechangeissignificant,perhaps,eventheAPIisalsochangingbutinabackward-compatiblemanner.Thepatchversionisincreasedwhensomebugisfixed,butthechangeisnotmajorandtheAPIdoesnotchange.Theminorandthepatchlevelshavetoberesettozeroifanyversionnumberinthetripletinfrontofanyofthemisincreased:majorversionnumberincreaseresetsbothminorandpatchversion;minorversionnumberincreaseresetspatchnumber.
Thisway,semanticversioningkeepsthefirstelementofthetripletforthespecificationversion.Theminorisamixofthespecificationandimplementationversions.Apatchversionchangeisclearlyanimplementationversionchange.
Inadditiontothese,semanticversioningallowsappendingapre-releasestring,suchas-RC1and-RC2.Italsoallowstheappendingofmetadata,suchasadateafteraplussign,forexample,+20160120asadate.
Theuseofsemanticversioninghelpsthosethatusethesoftwaretoeasilyspotcompatibleversionsandtoseewhichversionisolderandwhichisnewer.
CodereviewWhenwecreateprogramsinaprofessionalway,itisdoneinteams.Thereisnoone-manshowprogrammingotherthaninahobbyorgoingalongwiththetutorials.Itisnotonlybecauseitismoreeffectivetoworkinteamsbutalsobecauseonepersonisvulnerable.Ifyouworkaloneandgethitbythebusoryouhitthelotteryandloseyourabilityormotivationtoworkontheproject,yourcustomerisintrouble.Thatisnotprofessional.Professionalprojectsshouldberesilienttoanymemberfallingoff.
Teamworkneedscooperationandoneformofcooperationiscodereview.Thisistheprocesswhenadeveloperoragroupofdevelopersreadsapartofthecodethatsomeotherteammembershavewritten.Therearedirectgainsfromthisactivity;
Thedevelopersreadingthecodegetmoreknowledgeaboutthecode;theylearnthecode.Thisway,ifthedevelopercreatingthecodegetsoutoftheprocessforanyreason,theotherscancontinuetheworkwithminimalbump.Codingstylescanbealigned.Developers,evenseniors,payingcarefulattentionmakecodingmistakes.Thismaybeabugoritmaybeacodingstyleviolation.Codingstyleisimportantbecausethemorereadablethecodeis,thelesspossibilityofithavingunnoticedbugs.(Alsoseethenextbulletpoint.)Itisalsoimportantthatthecodingstyleisthesamefortheteam.Allteammembersshouldusethesamestyle.LookingatacodethathasadifferentstylefromtheoneIwroteisabithardertofollowandunderstand.Thedifferencesmaydistractthereaderandtheteammembershavetobeabletoreadthecode.Thecodebelongstotheteamandnotasingledeveloper.Anyteammembershouldknowthecodeandbeabletomodifyit.Duringcodereview,alotofbugscanbediscovered.Thepartieslookingatthecodeandtryingtounderstandtheworkingofitmayoccasionallydiscoverbugsfromthestructureofthecode,whichareotherwisehardtodiscoverusingtests.Ifyouwant,codereviewisthewhitestwhiteboxtest.Peoplethinkdifferentlyanddifferentmindsetscatchdifferentbugs.
Codereviewcanbedoneonlineandoffline.Itcanbedoneinteamsorpeer-to-peer.
MostteamsfollowthecodereviewprocessthatGitHubsupports,whichisthesimplest.Changestothecodearecommittedtoabranchandarenotmergedwiththecodedirectlybut,rather,apullrequestiscreatedonthewebinterface.Thelocalpolicymayrequirethatadifferentdeveloperperformthepull.Thewebinterfacewillhighlightthechangesandwecanaddcommentstothechangedcode.Ifthecommentsaresignificant,thentheoriginaldeveloperrequestingthepullshouldmodifythecodetoanswerthecommentsandrequestthepullagain.Thisensuresthatatleasttwodevelopersseeanychange;theknowledgeisshared.
Feedbackispeer-to-peer.Itisnotaseniorteachingajunior.Thatneedsadifferentchannel.CommentsinGitHubarenotgoodforthispurpose;atleast,therearebetterchannels.Perhapstalkingfacetoface.Commentsmaycomefromaseniortoajuniororfromajuniortoasenior.Inthiswork,givingfeedbackonthequalityofthecode,seniorsandjuniors,areequal.
Thesimplestandperhapsthemostfrequentcommentisthefollowing:IcanseethatXyz.javawaschangedinthemodificationbutIseenochangemadeto
XyzTest.java.Thisisalmostaninstantrefusalforthemerge.Ifanewfeatureisdeveloped,unittestshavetobecreatedtotestthatfeature.Ifabugisfixed,thenunittestshavetobecreatedtopreventthebugfromcomingback.Ipersonallygotthiscommentmanytimes,evenfromjuniors.Oneofthemtoldme,"Weknowthatyouweretestingusifwedaredtogivefeedback."GodknowsIwasnot.Theydidnotbelieve.
WhilechangereviewandGitHubisagoodtoolduringdevelopment,itmaynotbeappropriatewhenalargerchunkofcodehastobereviewed.Insuchacase,othertools,suchasFishEye,havetobeused.Inthistool,wecanselectthesourcefilesforrevieweveniftheywerenotrecentlychanged.Wecanalsoselectreviewersanddeadlines.CommentingissimilartoGitHub.Finally,thistypeofcodereviewfinisheswithacodereviewsession,wherethedevelopersgatheranddiscussthecodeinperson.
Whileorganizingsuchasession,itisimportantthatapersonwhohasexperiencemanagingotherpeoplemediatesthesesessions.Codeanddiscussiononstylescangetverypersonal.Atthesametime,whenattendingsuchameeting,youshouldalsopayattentionsoasnottogetpersonal.Therewillbeenoughparticipantswhomaynotknowthisorarelessdisciplined.
Neverattendareviewsessionwithoutreviewingthecodefirstusingtheonlinetools.Whenyoumakecomments,thelanguageshouldbeverypoliteforthereasonIhavealreadymentioned.Finally,themediatorofthemeetingshouldbeabletoseparateimportantandnotsoimportantissuesandtostopdebateonbagatellethings.Somehow,thelessimportantissuesaremoresensitive.Ipersonallydonotcareaboutformattingthetabsizeifitistwoorfourspacesandifthefileshouldcontainonlyspacesoriftabcharactersareallowed,butpeopletendtoliketowastetimeonsuchissues.
ThemostimportantissueduringcodereviewsessionsisthatweareprofessionalanditmayhappenthatIreviewandcommentyourcodetoday,buttomorrow,itwillbejusttheopposite,andweworktogetherandwehavetoworktogetherasateam.
KnowledgebaseKnowledgebasewasabuzzwordafewyearsago.Fewcompanieswereevangelizingtheideaofwikitechnologyandnobodywasusingit.Today,thelandscapeofknowledgebaseistotallydifferent.Allenterprisesusesomekindofwikiimplementationthatistheretoshareknowledge.TheymostlyuseConfluence,buttherearealsootherwikisolutionsavailable,commercialandfreeaswell.
Knowledgebasesstoreinformationthatyou,asadeveloper,wouldwritedowninapapernotebookforyourlaterreference,forexample,theIPaddressofthedevelopmentserver,directorieswheretoinstalltheJARfiles,whatcommandstouse,whatlibrariesyouhavecollected,andwhyyouusethem.Themajordifferenceisthatyouwriteitinaformattedwayintoawikianditisavailableimmediatelyforotherdevelopers.Itisabitofaburdenonthedevelopertowritethesepages,anditneedssomeself-disciplinefirst.StickingtotheexampleoftheIPaddressofthedevelopmentserverandtheinstalldirectories,youhavetowritenotonlytheIPaddressoftheserverbutalsosometextexplainingwhattheinformationis,becausetheothersmaynotunderstanditotherwise.Itisalsoabitofworktoplacethepagewiththeinformationinthewikisystemwithagoodname,linkingittootherpages,orfindingtheappropriatepositionofthepageinthetreeofpages.Ifyouwereusingthepapernotebook,youcouldjustwritedowntheIPaddressandthedirectoriesonthefirstfreepageofthebookandyouwouldjustrememberallothers.
Thewikiapproachwillpaybackwhencoworkersdonotneedtofindtheinformationthemselves;youcanfindtheinformationinaneasierwaybecauseothercoworkershavealsorecordedtheirfindingsintheknowledgebaseand,lastbutnotleast,afewmonthslater,youfindtheinformationyourecordedyourself.Inthecaseofapapernotebook,youwouldturnthepagestofindtheIPaddressandyoumayormaynotrememberwhichoneistheprimaryandwhichisthesecondaryserver.Youmayevenforgetbythenthattherearetwoservers(orwasitadoublecluster?).
Tohavealonglistofavailablewikisoftware,visithttps://en.wikipedia.org/wiki/Comparison_of_wiki_software.
IssuetrackingIssuetrackingsystemskeeptrackofissues,bugs,andothertasks.Thefirstissuetrackingsystemswerecreatedtomaintainthelistofbugsandalsothestateofthebugfixingprocesstoensurethatabug,identifiedandrecorded,willnotgetforgotten.Later,thesesoftwaresolutionsdevelopedandbecamefull-fledgedissuetrackersandareunavoidableprojectmanagementtoolsineveryenterprise.
ThemostwidelyusedissuetrackingapplicationusedinmanyenterprisesisJira,butonthehttps://en.wikipedia.org/wiki/Comparison_of_issue-tracking_systemspage,youcanfindmanyotherapplicationslisted.
Themostimportantfeatureofanissuetrackerapplicationisthatithastorecordanissueindetailinaneditablemanner.Ithastorecordthepersonwhorecordedtheissueincasemoreinformationisneededduringissuehandling.Thesourceoftheissueisimportant.Similarly,issueshavetobeassignedtosomeresponsibleperson,whoisaccountablefortheprogressofissuehandling.
Modernissuetrackingsystemsprovidecomplexaccesscontrol,workflowmanagement,relationmanagement,andintegrationwithothersystems.
Accesscontrolwillonlyallowthepersonwhohassomethingtodowithanissueaccesstoit,sootherscannotalterthestateofanissueorevenreadtheinformationattachedtoit.
Anissuemaygothroughdifferentworkflowstepsdependingonthetypeofissue:abugmaybereportedorreproduced,arootcauseanalyzed,afixdevelopedortested,apatchcreated,afixmergedwiththenextreleaseversionorpublishedintherelease.Thisisasimpleworkflowwithafewstates.
Relationmanagementallowssettingdifferentrelationsbetweenissuesandallowingtheusertonavigatefromissuetoissuealongtheserelations.Forexample,aclientreportsabug,andthebugisidentifiedasbeingthesameasanotheralreadyfixed.Insuchacase,itwouldbeinsanetogothroughtheoriginalworkflowandcreatinganewpatchforthesamebug.Instead,theissuegetsarelationpointingtotheoriginalissueandsetsthestatetobeclosed.
Integrationwithothersystemsisalsousefultokeepaconsistentdevelopmentstate.Versioncontrolmayrequirethat,foreverycommit,thecommitmessagecontainsareferencetotheissuethatdescribestherequirement,bug,orchangethatthecodemodificationsupports.Issuesmaybelinkedtoknowledgebasearticlesoragileprojectmanagementsoftwaretoolsusingweblinks.
TestingWehavealreadydiscussedtestingwhenwetalkedaboutunittesting.Unittestingisextremelyimportantinagiledevelopmentandithelpskeepthecodecleanandreducethenumberoferrors.Butthisisnottheonlytypeoftestingthatyouwillseeinenterprisedevelopment.
TypesoftestsTestingisperformedformanyreasonsbutthereareatleasttworeasonsthatwehavetomention.Oneistohuntthebugsandcreateerror-freecodeasmuchaspossible.Theotheristoprovethattheapplicationisusableandcanbeutilizedforthepurposeitwasmeantfor.Itisimportantfromtheenterprisepointofviewandconsidersalotofaspectsthatunittestdoesnot.Whileunittestfocusesononeunitand,thus,isanextremelygoodtooltopointoutwheretheerroris,itistotallyunusablewhenitcomestodiscoveringbugsthatcomefromerroneousinterfacesbetweenmodules.Theunittestsmockexternalmodulesand,thus,testthattheunitworksasexpected.However,ifthereisanerrorinthisexpectationandtheothermodulesdonotbehaveinthesamewayastheunittestmock,theerrorwillnotbediscovered.
Todiscovertheerrorsonthislevel,whichisthenextlevelaboveunittest,wehavetouseintegrationtests.Duringintegrationtests,wetesthowindividualunitscanworktogether.WhenweprograminJava,theunitsareusuallyclasses;thus,theintegrationtestwilltesthowthedifferentclassesworktogether.Whilethereisaconsensus(moreorless)aboutwhataunittestisinJavaprogramming,thisislesssointhecaseofintegrationtests.
Inthisregard,theexternaldependencies,suchasothermodulesreachableviathenetworkordatabaselayersmaybemocked,ormaybesetupusingsometestinstanceduringintegrationtesting.Theargumentisnotaboutwhetherthesepartsshouldbemockedornot,onlytheterminology.Mockingsomecomponentssuchasthedatabasehasadvantagesaswellasdrawbacks.Asinthecaseofanymock,thedrawbackisthecostofsettingupthemockaswellasthefactthatthemockbehavesdifferentlyfromtherealsystem.Suchadifferencemayresultinsomebugsstillremaininginthesystemandlurkingthereuntilalatercaseoftestingor,Godforgive,productionisused.
Integrationtestsareusuallyautomatedinawaysimilartounittests.However,theyusuallyrequiremoretimetoexecute.Thisisthereasonwhythesetestsarenotexecutedateachsourcecodechange.Usually,aseparatemavenorGradleprojectiscreatedthathasadependencyontheapplicationJARandcontainsonlyintegrationtestcode.Thisprojectisusuallycompiledandexecuteddaily.
Itmayhappenthatdailyexecutionisnotfrequentenoughtodiscovertheintegrationissuesinatimelymanner,butamorefrequentexecutionoftheintegrationtestsisstillnotfeasible.Insuchacase,asubsetoftheintegrationtestcasesisexecutedmorefrequently,forexample,everyhour.Thistypeoftestingiscalledsmoketesting.Thefollowingdiagramshowsthepositionofthedifferenttestingtypes:
Whentheapplicationistestedinafullysetupenvironment,thetestingiscalledsystemtesting.Suchtestingshoulddiscoveralltheintegrationbugsthatmayhavebeenlurkingandcoveredduringtheprevioustestingphases.Thedifferenttypeofsystemtestscanalsodiscovernon-functionalissues.Bothfunctionaltestingandperformancetestingaredoneonthislevel.
Functionaltestingchecksthefunctionsoftheapplication.Itensuresthattheapplicationfunctionsasexpectedoratleasthasfunctionsthatareworthinstallingintheproductionenvironmentandcanleadtocostsavingorprofitincrease.Inreallife,programsalmostneverdeliverallthefunctionsthatwereenvisionedinanyrequirementdocumentation,butiftheprogramisusableinasanemanner,itisworthinstallingit,assumingthattherearenosecurityissuesorotherissues.
Incasetherearealotoffunctionsintheapplication,functionaltestingmaycostalot.Insuchacase,somecompaniesperformasanitytest.Thistestdoesnotcheckthefullfunctionalityoftheapplication,onlyasubsettoensurethattheapplicationreachesaminimalqualityrequirementanditisworthspendingthemoneyonthefunctionaltesting.
Theremaybesometestcasesthatarenotenvisionedwhentheapplicationwasdesignedandthusthereisnotestcaseinthefunctionaltestplan.Itmaybesomeweirduseraction,auserpressingabuttononthescreenwhennobodythoughtitwaspossible.Users,evenifbenevolent,happentopressortouchanythingandenterallpossibleunrealisticinputsintoasystem.Ad-hoctestingtriestoamendthisshortage.Atesterduringad-hoctestingtriesallthepossiblewaysofuseoftheapplicationthatheorshecanimagineatthemomentthetestisexecuted.
Thisisalsorelatedtosecuritytesting,alsocalledpenetrationtestingwhenthevulnerabilitiesofthesystemarediscovered.Thesearespecialtypesofteststhatareperformedbyprofessionalswhohavetheircoreareaofexpertiseinsecurity.Developersusuallydonothavethatexpertise,butatleast,thedevelopersshouldbeabletodiscussissuesthatarediscoveredduringsuchatestandamendtheprogramtofixthesecurityholes.ThisisextremelyimportantinthecaseofInternetapplications.
Performancetestingchecksthattheapplication,inareasonableenvironment,canhandletheexpectedloadthattheuserputsonthesystem.Aloadtestemulatestheuserswhoattackthesystemandmeasurestheresponsetimes.Iftheresponsetimeisappropriate,thatis,lowerthantherequiredmaximumunderthemaximumload,thenthetestpasses;otherwise,itfails.Ifaloadtestfails,itisnotnecessarilyasoftwareerror.Itmaysohappenthattheapplicationneedsmoreorfasterhardware.Loadtestsusuallytestthefunctionalityoftheapplicationinonlyalimitedwayandonlytestusescenariosthatposereadloadontheapplication.
Manyyearsago,weweretestingawebapplicationthathadtohavearesponsetimeof2seconds.Theloadtestwasverysimple:issueGETrequestssothatthereareamaximumof10,000requestsactiveatthesametime.Westartedwith10clients,andthenascriptwasincreasingtheconcurrentusersto100,then1,000,andthensteppingupbythousandeveryminute.Thisway,theloadtestwas12minuteslong.Thescriptprintedtheaverageresponsetime,andwewerereadytoexecutetheloadtestat4:40pmonaFriday.Theaverageresponsetimestartedfromafewmillisecondsandwentupto1.9secondsastheloadwasincreasedto5,000concurrentusers,andfromthere,itwasdescendingdownto1secondastheloadwasincreasedto10,000users.YoucanunderstandtheattitudeofthepeopleonaFridayafternoon,beinghappythatwemettherequirements.Mycolleaguesleftfortheweekendhappily.IremainedtestingabitmorebecauseIwasbotheredbythephenomenonthattheresponsetimedecreasesasweincreasetheloadabove5,000.First,Ireproducedthemeasurementandthenstartedlookingatthelogfiles.At7pm,Ialreadyknewwhatthereasonwas.Whentheloadwentabove5,000,theconnectionstheApacheserverwasmanagingstartedtoexhaustandthewebserverstartedtosendback500internalerrorcodes.ThatissomethingthatApachecanveryeffectivelydo.Itisveryfastintellingyouthatyoucannotbeserved.Whentheloadwasaround10,000concurrentusers,70%oftheresponsesalreadyhad500errors.Theaveragewentdown,buttheuserswereactuallynotserved.IreconfiguredtheApacheserversothatitcouldservealltherequestsandforwardeachtoourapplicationjusttolearnthattheresponsetimeofourapplicationwasaround10secondsatthemaximumload.Around10pm,whenmywifewascallingmymobilethethirdtime,IalsoknewhowlargeamemoryIshouldsetintheTomcatstartupfileintheoptionsfortheJVMtogetthedesired2-secondresponsetimeincaseof10,000concurrentusers.
Stresstestisalsoatypeofperformancetestthatyoumayalsoface.Thistypeoftestincreasestheloadonthesystemuntilitcannothandletheload.Thattestshouldensurethatthesystemcanrecoverfromtheextremeloadautomaticallyormanuallybut,innocase,willdosomethingthatitshouldn'tatall.Forexample,abakingsystemshouldnotevercommitanunconfirmedtransaction,nomatterhowbigtheloadthereis.Iftheloadistoohigh,thenitshouldleavethedoughrawbutshouldnotbakeextrabread.
Themostimportanttestatthetopofthehierarchyistheuseracceptancetest.Thisisusuallyanofficialtestthatthecustomer,whobuysthesoftware,executesandinthecaseofsuccessfulexecution,paysthepriceforthesoftware.Thus,thisisextremelyimportantinprofessionaldevelopment.
TestautomationTestscanbeautomated.Itisnotaquestionofwhetheritispossibletoautomatizeatest,onlywhetheritisworthdoingso.Unittestsandintegrationtestsareautomated,andastimeadvances,moreandmoretestsgetautomatedaswemovealongtohigherstepstowardstheuseracceptancetest(UAT).UATisnotlikelytobeautomated.Afterall,thistestcheckstheintegrationbetweentheapplicationandtheuser.Whiletheuser,asanexternalmodule,canbemockedusingautomationinlowerlevels,weshouldreachthelevelwhentheintegrationtesthappenswithoutmocks.
Therearemanytoolsthathelptheautomationoftests.Theblockerfortestautomation,thesedays,isthecostofthetoolstodoso,thecostoflearninganddevelopingthetests,andthefearthattheautomatedtestsarenotdiscoveringsomeoftheerrors.
Itistruethatitiseasiertodosomethingwrongwithaprogramthanwithout.Thisissotrueforalmostanythingnotonlyfortesting.Andstillwedouseprograms;whyelsewouldyoureadthisbook?Someoftheerrorsmaynotbediscoveredduringautomatedfunctionaltesting,whichwouldotherwisehavebeendiscoveredusingmanualtests.Atthesametime,whenthesametestisexecutedthehundredthtimebythesamedeveloper,itisextremelyeasytoskipanerror.Anautomatedtestwillnoteverdothat.Andmostimportantly,thecostoftheautomatedtestisnot100timesthecostofrunningitonce.
Wehaveusedtestautomationtoolsinthisbook.SoapUIisatoolthathelpsyoucreateteststhatcanbeexecutedautomatically.OthertestingtoolsthatareworthlookingatareCucumber,Concordion,Fintnesse,andJBehave.Thereisagoodcomparisonoftoolsathttps://www.qatestingtools.com/.
BlackboxversuswhiteboxYoumayhaveheardmanytimesthatatestisablackboxtest.Thissimplymeansthatthetestdoesnotknowanythingabouthowthesystemundertest(SUT)isimplemented.ThetestreliesonlyontheinterfaceoftheSUTthatisexportedfortheoutsideworld.Awhiteboxtest,ontheotherendofthescale,teststheinternalworkingoftheSUTandverymuchreliesontheimplementation:
Boththeapproacheshaveadvantagesanddisadvantages.Weshoulduseone,orthemixtureofthetwoapproaches,awaythatfitsthepurposeoftheactualtestingneedsthemost.Ablackboxtestnotrelyingontheimplementationdoesnotneedtochangeiftheimplementationchanges.Iftheinterfaceofthetestedsystemchanges,thenthetestshouldalsobechanged.Awhiteboxtestmayneedchangesiftheimplementationchanges,eveniftheinterfaceremainsthesame.Theadvantageofthewhiteboxtestisthat,manytimes,itiseasiertocreatesuchatestandthetestingcanbemoreeffective.
Togetthebestofbothworlds,systemsaredesignedtobetestable.Becareful,though.Itmeansmanytimesthatthefunctionalityinternaltothetestedsystemispropagatedtotheinterface.Thatway,thetestwilluseonlytheinterfaceand,thus,canbedeclaredtobeablackbox,butitdoesnothelp.Ifsomethingchangesintheinternalworkingofthetestedsystem,thetesthastofollowit.Theonlydifferenceisthatyoumaycallitablackboxtestiftheinterfacealsochanges.Thatdoesnotsaveanywork.Rather,itincreasesit:wehavetocheckallthemodulesthatrelyontheinterfaceiftheyalsoneedanychange.
Idonotsaythatweshouldnotpayattentiontocreatingtestablesystems.Manytimesmakingasystemtestableresultsincleanerandsimplercode.Ifthecode,however,getsmessierandmuchlongerbecausewewanttomakeittestable,thenweareprobablynotgoingintherightway.
SelectinglibrariesProgrammingfortheenterpriseorevenprogrammingamoderatelysizedprojectcannotbedonewithouttheuseofexternallibraries.IntheJavaworld,mostofthelibrariesthatweuseareopensourceand,moreorless,freetouse.Whenwebuyalibrarythatissoldformoney,thereisusuallyastandardprocessenforcedbythepurchasingdepartment.Insuchacase,thereisawrittenpolicyabouthowtoselectthevendorandthelibrary.Inthecaseof"free"software,theydonotusuallycare,thoughtheyshould.Insuchacase,theselectionprocessmainlylieswiththeITdepartmentanditisthereforeimportanttoknowthemajorpointstobeconsideredbeforeselectingalibraryevenifforfree.
Inthepreviousparagraph,Iputthewordfreebetweenquotes.Thatisbecausethereisnosoftware,whichisfree.Thereisnosuchthingasafreelunch,astheysay.Youhaveheardthismanytimesbutitmaynotbeobviousinthecaseofanopensourcecodelibraryorframeworkyouaregoingtoselect.Themajorselectionfactorforanypurchaseorimplementationisthecost,theprice.Ifthesoftwareisfree,itmeansthatyoudonotneedtopayanupfrontfeeforthesoftware.However,thereisacostinintegratingitandusingit.Supportcostsmoney.Somebodymaysaythatthesupportiscommunitysupportandalsoavailablefreeofcharge.Thethingisthatthetimeyouspendhuntingforaworkaroundthathelpsyoutogetoverabugisstillmoney.Itisyourtime,orincaseyouareamanager,itisthetimeoftheprofessionalinyourdepartmentwhosetimeyoupayfor,or,asamatteroffact,itcanbeanexternalcontractorwhowillhandyouahugebillincaseyoudonothavetheexpertisein-housetosolvetheissue.
Sincefreesoftwaredoesnothaveapricetagattached,wehavetolookattheotherfactorsthatareimportantintheselectionprocess.Attheendoftheday,theyallwillaffectthecostinsomeway.Sometimes,thewayacriterionaltersthecostisnotobviousoreasilycalculable.However,foreachone,wecansetno-golevelsthatarebasedontechnologydecisions,andwecancomparelibrariesforbeingbetterorworsealongwitheachofthecriteria.
Fitforthepurpose
Perhaps,thisisthemostimportantfactor.Otherfactorsmaybearguedaboutintermsofthescaleofimportance,butifalibraryisnotappropriateforthepurposewewanttouse,thenthisiscertainlynotsomethingtoselect,nomatterwhat.Itmaybeobviousinmanycases,butyoumaybesurprisedhowmanytimesIhaveseenaproductselectedbecauseitwasthefavoriteofapersoninsomeotherprojectandthelibrarywasforcedforuseinthenewprojecteventhoughtherequirementsweretotallydifferent.
LicenseThelicenseisanimportantquestionasnotallfreesoftwareisfreeforalluses.Someofthelicensesallowfreeuseforhobbyprojectsandeducationbutrequireyoutopurchasethesoftwareforprofessional,profit-orienteduse.
Themostwidelyusedlicensesandtheirexplanation(andthewholetextofthelicense)isavailableonthewebpageoftheOpenSourceInitiative(https://opensource.org/licenses).Itlistsninedifferentlicenses,andtomakethesituationabitmorecomplex,theselicenseshaveversions.
OneoftheoldestlicensesistheGeneralPublicLicense(GPL)standingforGNU.Thislicensecontainsthefollowingsentences:
Forexample,ifyoudistributecopiesofsuchaprogram,whethergratisorforafee,youmustpassontotherecipientsthesamefreedomsthatyoureceived.Youmustmakesurethatthey,too,receiveorcangetthesourcecode.
Ifyoucreatesoftwareforafor-profitenterpriseandthecompanyintendstosellsoftware,youprobablycannotuseanylineofcodethatisfromaGPL-licensedsoftware.Itwouldimplythatyouarerequiredtopassonyourownsourcecode,whichmaynotbethebestsalesstrategy.Apachelicense,ontheotherhand,maybeokayforyourcompany.Thisissomethingthatthelawyersshoulddecide.
Eventhoughthisisthelawyers'work,thereisoneimportantpointthatwedevelopersmustbeawareofandpaycloseattentionto.Sometimes,thelibrariescontaincodefromotherprojectsandtheirlicense,asadvertised,maynotbetherealone.AlibrarymaybedistributedundertheApachelicensebutcontainscodethatisGPL-licensed.ThisisobviouslyaviolationoftheGPLlicense,whichwascommittedbysomeopensourcedevelopers.Whywouldyoucare?Herecomestheexplanationviaanimaginedsituation.
Youdevelopsoftwareforanenterprise.Let'ssaythatthiscompanyisoneofthelargestcarmanufacturersoftheworld,oritisoneofthelargestbanks,pharma,whatever.TheowneroftheGPLsoftwareseeksremediesforthemisuseofhersoftware.Willshesuethesoftwaredeveloper,JohnDoe,whohasatotalwealthof200K,oryourcompany,claimingthatyoudidnotdulycheckthelicenseofthecode?Shecertainlywillnotdigforgoldwherethereisnone.Suingthecompanyyouworkformaynotbesuccessful,butcertainlynotagoodprocessyouoranyoneatthecompanywants.
Whatcanweassoftwareprofessionalsdo?
Wehavetouselibrariesthatarewellknown,usedwidely.Wecancheckthesourcecodeofthelibrarytoseewhetherthereissomecopiedcode.Somepackagenamesmaypresentsomeclue.YoucanGooglesomepartofthesourcecodetofindmatches.Lastbutnotleast,thecompanycansubscribetoservicesthatprovidesimilarresearchforthelibraries.
Documentation
Documentationisanimportantaspect.Ifthedocumentationisnotappropriate,itwillbehardtolearnhowtousethelibrary.Someoftheteammembersmayhavealreadyknownthelibrary,but,again,thismaynotbethecaseforlaterteammembers.Weshouldconsiderourcolleagues,whoareexpectedtobeaverageprogrammers,andtheywillhavetolearntheuseofthelibrary.Thusdocumentationisimportant.
Whenwespeakaboutdocumentation,weshouldnotonlythinkabouttheJavaDocreferencedocumentationbutalsotutorialsandbooksiftheyareavailable.
ProjectaliveItisimportantnottoselectalibraryforusethatisnotalive.Havealookattheroadmapofthelibrary,thelasttimeareleasewasshipped,andthefrequencyofthecommits.Ifthelibraryisnotalive,weshouldconsidernotusingit.Librariesworkinanenvironmentandtheenvironmentchanges.Thelibrarymayconnecttoadatabase.Thenewversionofthedatabasemayprovidenewfeaturesthatgiveusbetterperformanceonlyifthelibraryismodifiedtoaccommodatethesenewfeatures.ThelibrarycommunicatesoverHTTP;willitsupportthenew2.0versionoftheprotocol?Ifnothingelse,theversionoftheJavaenvironmentwillchangeovertheyearsandthelibraryweuseshouldsoonerorlaterfollowittoleveragethenewfeatures.
Thereisnoguaranteethatanalivelibrarywillalwaysstayalive.However,alibrarythatisalreadydeadwillcertainlynotresurrect.
Eveniftheprojectisaliveatthemoment,therearesomepointsthatmaygivesomehintsaboutthefutureofthelibrary.Ifthecompanydevelopingitiswell-establishedandfinanciallystable,andthelibraryisdevelopedwithareasonablebusinessmodel,thenthereisalowriskthattheprojectdies.Iftherearealotofcompanieswhousethelibrary,thenitislikelythattheprojectwillstayaliveeveniftheoriginalteamstopsworkingonitortheoriginalfinancingstructurechanges.However,theseareonlysmallfactorsandnotwell-establishedfacts.Thereisnoguarantee,andtellingthefutureismoreanartthanascience.
MaturityMaturityissimilartothepreviouscriterion.Aprojectmayverywellbealivejuststartingup,butifitisinitsinfancy,webetternotusethelibraryforalargeproject.Whenaprojectisinitsearlyphase,alotofbugscanbeinthecode,theAPImaychangeradically,andpresumably,theremayonlybeasmallnumberofcompaniesrelyingonthecode.Thisalsomeansthatthecommunitysupportislower.
Ofcourse,ifalltheprojectsselectonlymatureopensourcecode,thennoopensourceprojectwouldevergettothematurestate.Weshouldassesstheimportanceoftheproject.Istheprojectbusiness-critical?Willtheprojectbecomebusiness-critical?
Iftheprojectisnotbusiness-critical,thecompanymayaffordtoinventafreshlibrarythatisnotthatmature.Itmaybereasonableiftherearenomaturelibrariesforthepurposebecausethetechnologyyouaregoingtouseisrelativelynew.Insuchacase,theprojectinthecompanyisprobablyalsonewandnotbusiness-criticalyet.Itwillbebusiness-critical,wehope,aftersometime,butbythattime,thelibrarywillbemature,ormayjustdieandwecanselectacompetingsolutionbeforetheprojectbecomestooexpensivetoswitch.
Judgingthematurityofalibraryisalwaysdifficultandhastobealignedwiththematurityandimportanceoftheprojectthatwewanttousethelibraryfor.
Numberofusers
Ifthelibraryisaliveandmaturebuttherearenotmanyusers,thensomethingissmelly.Whydon'tpeopleusethelibraryifitisgood?Ifthenumberofusersforalibraryorframeworkislowandtherearenolargecorporationsamongtheusers,thenitisprobablynotagoodone.Nobodyusingitmaysignalthatourassessmentoftheothercriteriamaynotbeappropriate.
Alsonotethatifthereareonlyafewusersofthelibrary,thentheknowledgeinthecommunityisalsoscarceandwemaynotbeabletogetcommunitysupport.
The"Ilikeit"factor
Lastbutnotleast,theIlikeitfactorisextremelyimportant.Thequestionisnotwhetheryoulikethelibrarybutratherhowmuchthedeveloperslikeit.Developerswilllikealibrarythatiseasytouseandfuntoworkwith,andthiswillresultinlowcost.Ifthelibraryishardtouseanddevelopersdonotlikeit,thentheywillnotlearntouseittothelevelofprofessionrequiredforgoodquality,onlytothelevelthatisjustneeded.Theendresultwillbesuboptimalsoftware.
ContinuousintegrationanddeploymentContinuousintegrationmeansthatwheneveranewversionispushedtothesourcecoderepository,thecontinuousintegrationserverkicksin,pullsthecodetoitsdisk,andstartsthebuild.Itcompilesthecodefirst,thenrunstheunittests,firesthestaticcodeanalysistools,and,ifallgoesright,itpackagesasnapshotreleaseanddeploysitonadevelopmentserver.
CIservershavewebinterfacesthatcanbeusedtocreatearelease.Insuchacase,thedeploymentcanevengotothetestserversoreventoproductiondependingonlocalbusinessneedsandonthepolicythatwascreatedaccordingly.
Automatingthebuildanddeploymentprocesshasthesameadvantagesasanyotherautomation:repeatedtaskscanbeperformedwithoutmanualintervention,whichistedious,boring,and,thus,error-proneifdonebyahuman.Theoutstandingadvantageisthatifthereissomeerrorinthesourcecodethatcanbediscoveredbytheautomatedbuildprocess,itwillbediscovered.Novicedeveloperssaythatitischeaperandeasiertobuildthecodelocally,whichthedevelopersdoanyway,andthenpushthecodetotheserverifthebuildprocessisalreadychecked.Itispartlytrue.Developershavetocheckthatthecodeisofgoodqualityandbuildswell,beforesendingittothecentralrepo.However,thiscannotalwaysbeachieved.Someerrorsmaynotmanifestonlocalenvironments.
ItmaysohappenthatonedeveloperaccidentallyusesanewerversionofJavathantheonesupportedandusesanewfeatureofthenewversion.Enterprisesdonotgenerallyusethelatesttechnology.Theytendtouseversionsthatareproven,havemanyusers,andaremature.Thisyear,in2017,whenJava9isgoingtobereleasedinJuly,hugeenterprisesstilluseJava1.6and1.7.SinceJava9hasmanynewfeaturesthatarenottrivialtoimplement,IexpectthattheadoptionofthetechnologymaytakeevenlongerthantheadoptionofJava1.8,whichgaveusfunctionalprogrammingandlambda.
Itmayalsohappenthatanewlibraryisaddedtothedependenciesofthebuildandthedeveloperwhoaddedittothebuildfile(pom.xml,orbuild.gradle)coulduseitwithoutanyproblemonherlocalmachine.Itdoesnotmeanthatthelibraryisofficiallyaddedtotheproject,anditmaynotbeavailableinthecentralcoderepository(Artifactory,Nexus,orotherimplementationsofthecoderepository).Thelibrarymayhaveonlybeenonthelocalrepositoryofthedeveloper,andshemayhaveassumedthatsincethecodecompiles,thebuildisOK.
Somelargeorganizationsusedifferentcoderepositoriesfordifferentprojects.Thelibrariesgetintotheserepositoriesfollowingmeticulousexaminationanddecisions.Somelibrariesmaygetthere,whileothersmaynot.Thereasontohavedifferentrepositoriescouldbenumerous.Someprojectisdevelopedforonecustomerwhohasadifferentpolicyaboutanopensourceprojectthantheother.Iftheenterprisedevelopscodeforitself,itmaysohappenthatsomelibraryisphasedoutornotsupportedanymore,andcanonlybeusedforprojectsthatareold.Amaintenancereleasemaynotneedtoreplacealibrary,butnewprojectsmaybenotbeallowedtouseadyingsoftwarelibrary.
TheCIservercanrunonasinglemachineoritcanrunonseveralmachines.Incaseitservesmany
projects,itmaybesetupasacentralserverwithmanyagentsrunningondifferentmachines.Whensomebuildprocesshastobestarted,thecentralserverdelegatesthistasktooneoftheagents.Theagentsmayhavedifferentloads,runningseveraldifferentbuildprocesses,andmayhavedifferenthardwareconfiguration.Thebuildprocessmayhaverequirementsregardingthespeedoftheprocessororabouttheavailablememory.Someagentmayrunsimplerbuildsforsmallerprojectsbutwouldfailtoexecutethebuildofalargeprojectorofsomesmallprojectthatstillhasahugememoryrequirementtoexecutesometests.
Whenabuildfails,thebuildserversendsoute-mailstothedevelopers,andthepersonwhosentthelastupdatetothecoderepositoryisobligatedtofixthebugwithoutdelay.Thisencouragesthedeveloperstocommitfrequently.Thesmallerthechange,thefewerchancesthereareofabuildproblem.Thebuildserverwebinterfacecanbeusedtoseetheactualstateoftheprojects,whichprojectisfailingtobuild,andwhichisjustfine.Ifabuildfails,thereisaredsigninthelineofthebuild,andifthebuildisOK,thesignisgreen.
Manytimes,thesereportsarecontinuallydisplayedonsomeoldmachineusingahugedisplaysothateverydeveloperorjustanybodywhoenterstheroomcanseetheactualstateofthebuilds.Thereisevenspecialhardwarethatyoucanbuythathasred,yellow,andgreenlampstofollowthestateofthebuildandringabellwhenthebuildfails.
ReleasemanagementDevelopingsoftwaremeansacontinuouslychangingcodebase.Noteveryversionofthesoftwareissupposedtobeinstalledinproduction.Mostoftheversionsarepushedtotherepositoryonabranchhalfcomplete.Someversionsaremeantonlyfortestingandafewaremeanttobeinstalledinproductionevenifonlysomeofthosewillfinallygettoproduction.
Almostallthetime,thereleasesfollowthesemanticversioningthatwediscussedinanearliersection.Theversionsthataremeantonlytobetestedusuallyhavethe-SNAPSHOTmodifierattheendoftheversionnumber.Forexample,the1.3.12-SNAPSHOTversionistheversionthatwasoncedebugged,andisgoingtobecomethe1.3.12version.Thesnapshotversionsarenotdefiniteversions.Theyarethecodeasitisbythen.Becauseasnapshotreleasenevergetsinstalledinproduction,itisnotneededtoreproduceasnapshotversionformaintenance.Thus,thesnapshotversionsarenotincreasedcontinually.Sometimes,theymaybechanged,butthatisarareexception.
Itmaysohappenthatweworkonabugfix,1.3.12-SNAPSHOT,andduringthedevelopment,wechangesomuchcodethatwedecidethatithastobe1.4.0whenitisreleased,andwerenamethesnapshotas1.4.0-SNAPSHOT.Thisisararecase.Manytimes,thereleasecreationcreatesa1.4.0versionfrom1.3.12-SNAPSHOTasthedecisionaboutthenewreleasenumberistakenbythetimethereleaseiscreated.
Whenthereleaseprocessisstarted,usuallyfromthewebinterfaceoftheCIserver,thedevelopercreatingthereleasehastospecifythereleaseversion.Thisisusuallythesameasthesnapshotversionwithoutthe-SNAPSHOTpostfix.Thebuildprocessnotonlycreatesthebuildinthiscasebutalsotagsthesourcecoderepositoryversionitwasusingandloadsthepackagedprogram(artifact)tothecoderepository.Thetagcanbeusedlatertoaccesstheexactversionofthesourcecodethatwasusedtocreatetherelease.Ifthereisabuginaspecificversion,thenthisversionhastobecheckedoutonadevelopermachinetoreproducethebugandfindtherootcause.
Ifthebuildofareleasefails,itcanberolledback,oryoubetterjustskipthatreleasenumberandnoteitasafailedreleasebuild.Anexistingreleasecanneverhavetwoversions.Thesourcecodeistheonlyonethatisforthatreleaseandthegeneratedcodehastobeexactlytheoneinanystorage.Subsequentcompilationofthesamesourcemayresultinslightlydifferentcode,forexample,ifadifferentversionofJavaisusedtocreatethelatterone.Eveninsuchacase,theonethatwascreatedbythebuildserverinthefirstplaceistheversionthatbelongstotherelease.Whenabugisreproducedandthecodeisrecompiledfromtheexactsamesource,itisalreadyasnapshotversion.Multiplereleasesmaybepossiblefromthesamesourceversion,forexample,compiledwithJavaversionsfrom1.5to1.8andversion9butasinglereleasealwaysbelongstotheexactsamesourcecode.
IfareleasethatwassupposedtobeareleaseversionfailsduringQAchecks,thenanewreleasehastobecreatedandthefailedreleasehastobenotedassuch.Theversionthatmarketingusestonamethedifferentversionsshouldnothavearelationtothetechnicalversionnumbersweworkwith.Manytimes,itis,anditcausesmuchheadache.Ifyourealizethatthetwoaretotallydifferentthingsandonedoesnothavetodoanythingwiththeother,lifegetssimpler.LookatthedifferentversioningoftheWindowsoperatingsystemorJava.Asmarketing,Javaused1.0then1.1,butJava1.2wasadvertisedasJava2andstillthecodecontained1.2(whichnowsevenmajorreleaseslateralsobecomes9insteadof1.9)
Thelastpartofreleasemanagementisthatdeploymentsshouldregistertheversionnumbers.Thecompanyhastoknowwhichreleaseisinstalledonwhichserver,andofwhichclient.
CoderepositoryCoderepositorystoresthelibrariesandhelpsmanagethedependenciesofthedifferentlibraries.Intheoldtimes,whenJavaprojectsusedANTasabuildtoolandwithoutthelateraddedIvydependencymanagement,thelibrariesthatwereneededbyaprojectweredownloadedtothesourcecode,usuallytotheliblibrary.Ifalibraryneededanotherlibrary,thenthosewerealsodownloadedandstoredmanually,andthiscontinueduntilallthelibrariesthatoneofthealreadydownloadedlibrariesneededwerecopiedtothesourcecodetree.
Thiswasalotofmanualworkand,also,thelibrarycodewasstoredinthesourcecoderepositoryinmanycopies.Acompiledlibraryisnotsourcecodeandhasnothingtodointhesourcecoderepository.Manualworkthatcanbeautomatedhastobeautomated.Notbecausedevelopersarelazy(yes,weareandwehavetobe)butbecausemanualworkiserrorproneand,thus,expensive.
ThiswaswhenApacheIvywasinventedandMaven,followingANT,alreadysupportedrepositorymanagementbuiltin.Theyallstoredthelibrariesstructuredindirectoriesandsupportedmetadatathatdescribedthedependenciestootherlibraries.LuckythatGradledidnotinventitsowncoderepository.Instead,itsupportsbothMavenandIvyrepositories.
Usingtherepository,thebuildtoolsautomaticallydownloadthelibrariesthatareneeded.Incasealibraryhasanewversion,thenthedeveloperonlyhastoupdatetheversionoftheneededlibraryinthebuildconfigurationandalltasks,includingdownloadingallthenewversionsoftheotherlibrariesthatareneededbythatversion,aredoneautomatically.
WalkinguptheladderAtthispoint,youhavegotalotofinformationthatwillrocketyourstartasanenterpriseJavadeveloper.Youhavegotabaseknowledgethatyoucanbuildon.ThereisalongwaytobecomeaprofessionalJavadeveloper.Thereisalotofdocumentationtoread,alotofcodetoscanandunderstand,andalsoalotofcodetowritetillyoucanclaimtobeaprofessionalJavadeveloper.Youmayprobablyfacemanyyearsofcontinuouseducation.Thegoodthingisthatevenafterthat,youcancontinueyourjourneyandyoucaneducateyourself,asbeingaprofessionalJavadeveloperisrarelyajobpeopleretirefrom.No,no!Notbecausetheydiewhileatit!Rather,professionalsoftwaredevelopersgainingexperiencestarttocodelessandlessandsupportthedevelopmentprocessindifferentways,whichleveragesmoreoftheirexperience.Theycanbecomebusinessanalysts,projectmanagers,testengineers,subject-matterexperts,architects,scrummasters,automationengineers,andsoon.Isitafamiliarlist?Yes,thesearethepeopleyouwillworkwithasadeveloper.Manyofthemmayhavestartedasadeveloperthemselves.Thefollowingdiagramshowstherelativepositionoftheseroles:
Let'stakeabitmoredetailedlookintowhattheserolesperforminenterprisedevelopment:
Businessanalystsworkwiththeclientandcreatethedocuments,specifications,usecases,anduserstoriesneededbythedeveloperstodevelopthecode.Projectmanagersadministertheprojectsandhelptheteamingettingthingsdoneincooperationwithotherteams,caringforalltheprojectmattersthatdeveloperscannotattendtoorwouldunnecessarilyburntheirtimethattheyshouldhavedevotedtocoding.Subject-matterexpertsaremoreadvancedinknowingthebusinessneeds,soitisabitrareforadevelopertobecomeone,butincasetheindustryyouworkinistechnologyoriented,itmaynotbeincredibletobecomeone.TestengineerscontroltheQAprocessandunderstandnotonlythetestmethodologiesandrequirementsoftestingbutalsothedevelopmentprocesssothattheycansupportbugfixesandnot
onlyidentifythem,whichwouldbepoor.ArchitectsworkwithBAsanddesignahigh-levelstructureoftheapplicationsandcode,anddocumentitinawaythathelpsthedeveloperstofocusontheactualtaskstheyhavetoperform.Architectsarealsoresponsibleforthesolutiontousetechnologies,solutions,andstructureswhichfitthepurpose,arefutureproof,affordable,andsoon.Scrummateshelpthedevelopmentteamtofollowtheagilemethodologyandhelptheteamincontrollingtheadministrationandresolvingproblems.
TherearemanywaystogoasasoftwaredeveloperandIonlylistedsomeofthepositionsthatyoucanfindinanenterprisetoday.Astechnologydevelops,Icanimaginethatin20yearsfromtoday,softwaredeveloperswillteachandcurateartificialintelligencesystemsandthatwillbewhatwerefertoasprogrammingtoday.Whocantell?
Summary
Goinginthisdirectionisagoodchoice.BeingaJavadeveloperandbecomingaprofessionalinitisaprofessionthatwillpaywellinthecoming10to20yearsforsureandperhapsevenlater.Atthesametime,Ipersonallyfindthistechnologyfascinatingandinteresting,andaftermorethan10yearsofJavaprogrammingandmorethan35yearsofprogramming,Istilllearnsomethingnewiniteveryday.
Inthisbook,youlearnedthebasicsofJavaprogramming.Fromtimetotime,Ialsomentionedissues,suggesteddirections,andwarnedyouaboutpitfallsthatarenotJava-specific.However,wealsodidthehomeworkoflearningtheJavalanguage,theinfrastructure,thelibraries,developmenttools,andnetworkinginJava.YoualsolearnedthemostmodernapproachesthatcameonlywithJava8and9,suchasfunctionalprogramminginJava,streams,andreactiveprogramming.IfyouknowallthatIhavewritteninthisbook,youcanstartworkingasaJavadeveloper.What'snext?Go,andfindyourtreasureinprogrammingandinJava!