diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml index 554faa49..0544efae 100644 --- a/.github/workflows/linter.yaml +++ b/.github/workflows/linter.yaml @@ -3,21 +3,20 @@ name: Linter on: [pull_request] jobs: - markdown-linter: - name: "Markdown Linter" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 +# markdown-linter: +# name: "Markdown Linter" +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# - name: markdownlint-cli +# uses: nosborn/github-action-markdown-cli@v3.2.0 +# with: +# files: . +# config_file: .markdownlint.yaml - - name: markdownlint-cli - uses: nosborn/github-action-markdown-cli@v3.2.0 - with: - files: . - config_file: .markdownlint.yaml - - markdown-link-check: - name: "Markdown Link Checker" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: gaurav-nelson/github-action-markdown-link-check@1.0.14 +# markdown-link-check: +# name: "Markdown Link Checker" +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# - uses: gaurav-nelson/github-action-markdown-link-check@1.0.14 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..32cfc61d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.watcherExclude": { + "**/target": true + } +} \ No newline at end of file diff --git a/README.md b/README.md index 2bf6e44d..5c3c3a40 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,50 @@ # Modern Java [![codefactor](https://img.shields.io/codefactor/grade/github/together-java/modernjava)](https://www.codefactor.io/repository/github/together-java/modernjava) -![Java](https://img.shields.io/badge/Java-19-ff696c) [![license](https://img.shields.io/github/license/Together-Java/ModernJava)](https://github.com/Together-Java/ModernJava/blob/master/LICENSE) -Book teaching how to write modern and effective Java. It is maintained by the community, anyone can contribute. +Greetings. -Feel free to join our [discord server](https://discord.gg/together-java-272761734820003841) -if you have any questions, or require assistance with the project. :relaxed: +This is a book intended to teach someone the Java language, from scratch. + +You will find that the content makes heavy use of recently released and, for the moment, +preview features. This is intentional as much of the topic ordering doesn't work +without at least Java 21. + +Right now I have several key areas where I could use some help: + +* Writing Challenges. Most of the early sections have challenges students can do to test +their understanding of the topics covered and for practice. I've shifted my focus away from +these to make more progress on the main content of the book. Any assistance would be appreciated. +* Theming. A lot of the chapters are...bland. Purely technical. I find that when I have the imagination to "theme" the subjects they become higher quality and more engaging overall. See +an anime you liked recently and think you can make the math chapters use the characters from it? +Give it a shot! +* Fixing Mechanical Issues. I don't have an editor and I don't often proofread. If you find mechanical errors +in my grammar or find issues with the way topics are ordered I would welcome fixes. + +Notably I do not want to open the floodgates for contributions on the main chapter content +just yet. This has the downside of slower progress but the upside of a more coherent result. + +My primary goals with this are + +* Get the ordering of topics right. By this I mean that every topic covered should have had its pre-requitsites covered in the topics previous. While "lesson 1: Inheritance" is clearly wrong +in this regard, some things are more subtle. +* Be a template for other people. This is a book. Not everyone likes books, some like youtube videos, some like over priced udemy courses, some attend College, etc. Everyone has different learning paths. I hope this to be of use to anyone looking to make a more up to date Java +curriculum and hope that the vague order of things (which I consider superior to the content +produced with the Java of years' past) as carried through. +* Write as if the newest Java wasn't new. Its obvious when a book was written before Java 8 +because it always has newer additions with "addendum: brand new stuff in Java 8." But +the order language features were introduced is hardly a good order to teach them. You have +to pretend that Java 21 has always been *the* Java. Does it really make sense to show terrible +C-style switch statements way before switch expressions? +* Write as if the words Object Oriented Programming, Functional Programming, etc. didn't exist. +While I understand that these all have definitions and are useful concepts to know about, introducing them early seems to lead to either dogma, rejection of said dogma, or some +mix thereof. None of them are actually needed to understand the mechanics of and motivation +behind what we would call "object oriented" or "functional" techniques. They certainly don't +work as justification for adding getters and setters to every class. + +Feel free to join our [discord server](https://discord.gg/xSdQBHfTun) +if you have any questions, or require assistance with the project. ## Getting started @@ -18,3 +55,11 @@ first time submitting a PR to an open-source project. Head over to the [Wiki](https://github.com/Together-Java/ModernJava/wiki) as general entry point to the project. It provides lots of tutorials, documentation and other information. + +To run this locally you will need `mdbook`. The instructions for doing so are [here](https://rust-lang.github.io/mdBook/guide/installation.html). + +Then just run the following. + +``` +mdbook serve +``` diff --git a/book.toml b/book.toml index 06662481..3f8360d6 100644 --- a/book.toml +++ b/book.toml @@ -1,9 +1,12 @@ [book] title = "Modern Java" -description = " Book teaching how to write modern and effective Java. It is maintained by the community, anyone can contribute." -authors = ["Together Java", "Contributions from the Java Community"] +description = "Book teaching how to write modern and effective Java." +authors = [ + "Ethan McCue", + "Together Java", + "Contributions from the Java Community", +] language = "en" -multilingual = false [output.html] git-repository-url = "https://github.com/Together-Java/ModernJava" @@ -11,15 +14,16 @@ edit-url-template = "https://github.com/Together-Java/ModernJava/edit/develop/{p mathjax-support = true additional-css = ["ferris.css"] additional-js = ["ferris.js"] +heading-split-level = 2 +default-theme = "light" +preferred-default-theme = "light" [output.html.fold] enable = true # whether or not to enable section folding -level = 0 # the depth to start folding +level = 1 # the depth to start folding [output.html.playground] editable = true [output.html.code.hidelines] java = "~" -[output.html.code.hidelines] -java = "~" diff --git a/ferris.js b/ferris.js index f78ce4a5..e74c34c2 100644 --- a/ferris.js +++ b/ferris.js @@ -53,8 +53,21 @@ function createFerris(type, size) { // a.setAttribute('href', 'ch00-00-introduction.html#ferris') a.setAttribute('target', '_blank') + var toFileName = function (t) { + // return '../img/' + t.attr + '.svg'; + if (t.attr == 'panics') { + return '../img/duke_armcross.webp' + } + else if (t.attr == 'does_not_compile') { + return '../img/duke_computer.webp' + } + else if (t.attr == 'not_desired_behavior') { + return '../img/duke_finger.webp' + } + } + var img = document.createElement('img') - img.setAttribute('src', '../img/' + type.attr + '.svg') + img.setAttribute('src', toFileName(type)) img.setAttribute('title', type.title) img.classList.add('ferris') img.classList.add('ferris-' + size) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 725b3cde..17f9431f 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -12,15 +12,144 @@ - [Tour of Scala](./examples/tour_of_scala.md) - [tutorialspoint](./examples/tutorialspoint.md) - [tutorialspoint paid course](./examples/tutorialspoint_paid.md) - - [w3schools](./examples/w3schools.md) --> + - [w3schools](./examples/w3schools.md) + +Project ideas: + tic tac toe + chess + go + + swing game - maybe have a simplified game engine + + fixed time loop game + + opengl/ffm + +version control + +something with statistics +newton raphston + +airplane physics program +ball throw physics program +chemical stuff + + +snake +2048 +colors +battleship + +CLI Hangman +Tic Tac Toe AI + +TODO App + +Task Description - data formats + +Approximate PI + +Pomodoro timer () +Terminal ANKI (might require threads) + +Simple REST API +HTML + +- [Tic-Tac-Toe]() +- [Chicken Nugget Numbers]() + +* Argument Objects (InputArgs) +* Cursor Objects (sql) +* Exception Hierarchies +* Interior Iteration + +Make your own iterator + +# Projects II + + https://michaelxing.com/UltimateTTT/v3/ +- [Ultimate Tic-Tac-Toe]() + +Circuit Builder Resistance Solver + +Draw a circuit. + +Adjacency Matrix + +BFS + +Have a project that is just klarna but the your goal is "never write this program." + +- [Journal Entries]() + +sealed interfaces +lambdas + +easy vs. simple + +enshittification state machine example (Acquire -> Make good for business bad for users -> Bad for everyone) + +Zombie Game + +try to trick someone into making that. +LegBeanProviderFactory + + +stuff in mooc i do not have +Streams +Intro to testing +Gui stuff +--> -# Modern Java - [Prelude](./prelude.md) - [Asking for Help](./prelude/asking_for_help.md) - [Toy Problems](./prelude/toy_problems.md) - [Lies](./prelude/lies.md) -- [Getting Started](./getting_started.md) + - [AI](./prelude/ai.md) + - [Java](./prelude/java.md) + - [Testimonials](./prelude/testimonials.md) + + + +# Modern Java + +- [Set Up Your Computer](./text_editors/set_up_your_computer.md) + - [Text Editors](./text_editors.md) + - [IDEs](./text_editors/ides.md) + - [VSCodium](./text_editors/vscodium.md) + - [Install VSCodium](./text_editors/install_vscodium.md) + - [Install Java](./text_editors/install_java.md) +- [The Terminal](./the_terminal.md) + - [Bash](./the_terminal/bash.md) + - [Windows Subsystem for Linux](./the_terminal/windows_subsystem_for_linux.md) + - [Chromebooks and School Computers](./the_terminal/chromebooks_and_school_computers.md) + - [Commands](./the_terminal/commands.md) + - [Directories](./the_terminal/directories.md) + - [Listing Files](./the_terminal/listing_files.md) + - [Creating Directories](./the_terminal/creating_directories.md) + - [Changing Directories](./the_terminal/changing_directories.md) + - [Creating Files](./the_terminal/creating_files.md) + - [Run Java Programs](./the_terminal/run_java_programs.md) + - [Getting Used to it](./the_terminal/getting_used_to_it.md) + + + - [First Steps](./first_steps.md) - [Comments](./first_steps/comments.md) - [Semicolons](./first_steps/semicolon.md) @@ -38,9 +167,9 @@ # Data Types I - [Booleans](./boolean.md) + - [Not](./boolean/not.md) - [And](./boolean/and.md) - [Or](./boolean/or.md) - - [Not](./boolean/not.md) - [Operator Precedence](./boolean/operator_precedence.md) - [Challenges](./boolean/challenges.md) - [Integers](./integers.md) @@ -92,18 +221,25 @@ - [Access Individual Characters](./strings/access_individual_characters.md) - [Challenges](./strings/challenges.md) +# Interactive Programs + +- [Standard Input](./standard_input.md) + - [Prompting](./standard_input/prompting.md) + - [Interpreting Input](./standard_input/interpreting_input.md) + - [Integers](./standard_input/integers.md) + - [Floating Point Numbers](./standard_input/floating_point_numbers.md) + - [Other Types](./standard_input/other_types.md) + - [Challenges](./standard_input/challenges.md) + # Control Flow I - [Branching Paths](./branching_paths.md) - - [If](./branching_logic/if.md) - [Nested Ifs](./branching_logic/nested_ifs.md) - [Else](./branching_logic/else.md) - - [Else](./branching_logic/else.md) - [Else If](./branching_logic/else_if.md) - [Relation to Delayed Assignment](./branching_logic/relation_to_delayed_assignment.md) - [Scoped Variables](./branching_logic/scoped_variables.md) - - [Scoped Variables](./branching_logic/scoped_variables.md) - [Conditional Operator](./branching_logic/conditional_operator.md) - [Boolean Expressions](./branching_logic/boolean_expressions.md) - [Challenges](./branching_logic/challenges.md) @@ -125,6 +261,12 @@ - [Iterate over a String](./loops/iterate_over_a_string.md) - [Challenges](./loops/challenges.md) +# Projects + + +- [Prelude](./projects/prelude.md) +- [Calorie Tracker](./projects/calorie_tracker.md) + # Data Types II - [Arrays](./arrays.md) @@ -138,6 +280,7 @@ - [Printing the Contents of an Array](./arrays/printing_the_contents_of_an_array.md) - [Empty Array](./arrays/empty_array.md) - [Difference between Initializer and Literal](./arrays/difference_between_initializer_and_literal.md) + - [Initialization with new](./arrays/initialization_with_new.md) - [Challenges](./arrays/challenges.md) # Control Flow II @@ -174,7 +317,6 @@ - [Challenges](./methods/challenges.md) - [Arguments](./arguments.md) - - [Declaration](./arguments/declaration.md) - [Invocation with Arguments](./arguments/invocation_with_arguments.md) - [Reassignment](./arguments/reassignment.md) @@ -192,14 +334,35 @@ - [Return in void methods](./return_values/return_in_void_methods.md) - [Conversion](./return_values/conversion.md) - [Unreachable Statements](./return_values/unreachable_statements.md) + - [Challenges](./return_values/challenges.md) + # Data Types III +- [Multi-Dimensional Arrays](./multi_dimensional_arrays.md) + - [Declaration](./multi_dimensional_arrays/declaration.md) + - [Array Initializers](./multi_dimensional_arrays/array_initializers.md) + - [Initialization with new](./multi_dimensional_arrays/initialization_with_new.md) + - [Access Individual Elements](./multi_dimensional_arrays/access_individual_elements.md) + - [Set Individual Elements](./multi_dimensional_arrays/set_individual_elements.md) + - [Initialization with Size](./multi_dimensional_arrays/initialize_with_size.md) + - [Default Values](./multi_dimensional_arrays/default_values.md) + - [Populate Values](./multi_dimensional_arrays/populate_values.md) + - [Ragged Arrays](./multi_dimensional_arrays/ragged_arrays.md) + - [Challenges](./multi_dimensional_arrays/challenges.md) + +# Projects + +- [ASCII Art Generator](./projects/ascii_art.md) + +# Data Types IV + - [null](./null.md) - [Null as Absence](./null/null_as_absence.md) - [Null as Unknown](./null/null_as_unknown.md) - [Checking for null](./null/checking_for_null.md) - [NullPointerException](./null/null_pointer_exception.md) + - [Challenges](./null/challenges.md) - [Boxed Primitives](./boxed_primitives.md) - [Integer](./boxed_primitives/integer.md) - [Double](./boxed_primitives/double.md) @@ -209,11 +372,12 @@ - [Boxing Conversion](./boxed_primitives/boxing_conversion.md) - [Arrays of Boxed Primitives](./boxed_primitives/arrays_of_boxed_primitives.md) - [Challenges](./boxed_primitives/challenges.md) - - [Challenges](./boxed_primitives/challenges.md) - [Arrays II](./arrays_ii.md) - [Initializion with Size](./arrays_ii/initialization_with_size.md) - [Default Values](./arrays_ii/default_values.md) - [Populate Arrays](./arrays_ii/populate_arrays.md) + - [Challenges](./arrays_ii/challenges.md) + # Code Structure II @@ -226,8 +390,10 @@ - [Field Initialization](./classes/field_initialization.md) - [Field Access](./classes/field_access.md) - [Field Default Values](./classes/field_default_values.md) + - [Multiple Instances](./classes/multiple_instances.md) - [Aliasing](./classes/aliasing.md) - [Return Multiple Values](./classes/return_multiple_values.md) + - [Challenges](./classes/challenges.md) - [Instance Methods](./instance_methods.md) - [Invocation](./instance_methods/invocation.md) @@ -239,8 +405,17 @@ - [this](./instance_methods/this.md) - [Disambiguation](./instance_methods/disambiguation.md) - [Clarity](./instance_methods/clarity.md) + - [Challenges](./instance_methods/challenges.md) + -# Data Types IV + +# Projects + + +- [Point of Sale System](./projects/point_of_sale_system.md) + + +# Data Types V - [Enums](./enums.md) - [Declaration](./enums/declaration.md) @@ -249,6 +424,15 @@ - [Usage](./enums/usage.md) - [Equality](./enums/equality.md) - [Comparison to boolean](./enums/comparison_to_boolean.md) + - [Challenges](./enums/challenges.md) +- [Strings II](./strings_ii.md) + - [lowercase](./strings_ii/lowercase.md) + - [UPPERCASE](./strings_ii/UPPERCASE.md) + - [Equality ignoring case](./strings_ii/equality_ignoring_case.md) + - [Check if empty](./strings_ii/check_if_empty.md) + - [Check if blank](./strings_ii/check_if_blank.md) + - [Strip extra whitespace](./strings_ii/strip_extra_whitespace.md) + - [Challenges](./strings_ii/challenges.md) # Control Flow III @@ -257,15 +441,29 @@ - [Messages](./exceptions/messages.md) - [Stack Traces](./exceptions/stack_traces.md) - [try/catch](./exceptions/try_catch.md) + - [Challenges](./exceptions/challenges.md) - [Switch](./switch.md) - [Case and Default](./switch/case_and_default.md) - [Strings](./switch/strings.md) - [ints](./switch/ints.md) - [Enums](./switch/enums.md) - [Omitted Default](./switch/omitted_default.md) - - [Exhaustiveness](./switch/exhaustiveness.md) - [Combining Cases](./switch/combining_cases.md) - [null](./switch/null.md) + - [Exhaustiveness](./switch/exhaustiveness.md) + - [Challenges](./switch/challenges.md) + + +# Interactive Programs II + +- [Standard Input II](./standard_input_ii.md) + - [Reprompting](./standard_input_ii/reprompting.md) + - [Enums](./standard_input_ii/enums.md) + - [Delayed Assignment](./standard_input_ii/delayed_assignment.md) + - [Leniency](./standard_input_ii/leniency.md) + - [Aggregating Data](./standard_input_ii/aggregating_data.md) + - [Challenges](./standard_input_ii/challenges.md) + # Code Structure III @@ -277,38 +475,676 @@ - [Invariants](./constructors/invariants.md) - [Overloads](./constructors/overloads.md) - [Delegation](./constructors/delegation.md) + - [Challenges](./constructors/challenges.md) +- [Global Fields](./global_fields.md) + - [Default Values](./global_fields/default_values.md) + - [Final Fields](./global_fields/final_fields.md) + - [Field Access](./global_fields/field_access.md) + - [Inferred Types](./global_fields/inferred_types.md) + - [Challenges](./global_fields/challenges.md) + +# Projects + + +- [Tic-Tac-Toe](./projects/tic_tac_toe.md) + + +# Concepts + +- [Code is Read more than Written](./code_is_read_more_than_written.md) + - [Meaning](./code_is_read_more_than_written/meaning.md) + - [Implications](./code_is_read_more_than_written/implications.md) + - [Information Density](./code_is_read_more_than_written/information_density.md) + - [Audience](./code_is_read_more_than_written/audience.md) + - [Practice](./code_is_read_more_than_written/practice.md) + + +# Control Flow IV + +- [Exceptions II](./exceptions_ii.md) + - [Checked Exceptions](./exceptions_ii/checked_exceptions.md) + - [Unchecked Exceptions](./exceptions_ii/unchecked_exceptions.md) + - [throws](./exceptions_ii/throws.md) + - [Propagating Exceptions](./exceptions_ii/propagating_exceptions.md) + - [Exception](./exceptions_ii/exception.md) + - [RuntimeException](./exceptions_ii/runtime_exception.md) + - [Rethrowing Exceptions](./exceptions_ii/rethrowing_exceptions.md) + - [main](./exceptions_ii/main.md) + - [Challenges](./exceptions_ii/challenges.md) +- [Switch II](./switch_ii.md) + - [Yield](./switch_ii/yield.md) + - [Omitted Yield](./switch_ii/omitted_yield.md) + - [Exhaustiveness](./switch_ii/exhaustiveness.md) + - [Return a Switch](./switch_ii/return_a_switch.md) + - [Challenges](./switch_ii/challenges.md) + +# Code Structure IV + +- [Multi-File Programs](./multi_file_programs.md) + - [The Sources folder](./multi_file_programs/the_sources_folder.md) + - [The Main file](./multi_file_programs/the_main_file.md) + - [A Second file](./multi_file_programs/a_second_file.md) + - [File names](./multi_file_programs/file_names.md) + - [The Anonymous Main Class](./multi_file_programs/the_anonymous_main_class.md) + - [Global Fields](./multi_file_programs/global_fields.md) + - [Challenges](./multi_file_programs/challenges.md) +- [Visibility](./visibility.md) + - [Private Methods](./visibility/private_methods.md) + - [Private Fields](./visibility/private_fields.md) + - [Invariants](./visibility/invariants.md) + - [Accessors](./visibility/accessors.md) + - [Getters and Setters](./visibility/getter_and_setters.md) + - [Challenges](./visibility/challenges.md) +- [Static Fields](./static_fields.md) + - [Declaration](./static_fields/declaration.md) + - [Initialization](./static_fields/initialization.md) + - [Usage](./static_fields/usage.md) + - [Constants](./static_fields/constants.md) + - [Controversy](./static_fields/controversy.md) + - [Naming](./static_fields/naming.md) + - [Challenges](./static_fields/challenges.md) +- [Static Methods](./static_methods.md) + - [Declaration](./static_methods/declaration.md) + - [Scope](./static_methods/scope.md) + - [Naming](./static_methods/naming.md) + - [Usage](./static_methods/usage.md) + - [Math](./static_methods/math.md) + - [Factories](./static_methods/factories.md) + - [Challenges](./static_methods/challenges.md) +# Data Structures & Algorithms +- [Growable Arrays](./growable_arrays.md) + - [Concept](./growable_arrays/concept.md) + - [Simple Implementation](./growable_arrays/simple_implementation.md) + - [Usage](./growable_arrays/usage.md) + - [Performance Problems](./growable_arrays/performance_problems.md) + - [Performance Solutions](./growable_arrays/performance_solutions.md) + - [Optimized Implementation](./growable_arrays/optimized_implementation.md) + - [Challenges](./growable_arrays/challenges.md) +# Interactive Programs III + +- [Command Line Arguments](./command_line_arguments.md) + - [Accessing Arguments](./command_line_arguments/accessing_arguments.md) + - [Conventions](./command_line_arguments/conventions.md) + - [Challenges](./command_line_arguments/challenges.md) + +# Code Structure V + +- [Inner Classes](./inner_classes.md) + - [Type](./inner_classes/type.md) + - [Instances](./inner_classes/instances.md) + - [New Operator](./inner_classes/new_operator.md) + - [Scope](./inner_classes/scope.md) + - [Disambiguation](./inner_classes/disambiguation.md) + - [The anonymous main class](./inner_classes/the_anonymous_main_class.md) + - [Static Inner Classes](./inner_classes/static_inner_classes.md) + - [Private Inner Classes](./inner_classes/private_inner_classes.md) + - [Challenges](./inner_classes/challenges.md) +- [Packages](./packages.md) + - [Declaration](./packages/declaration.md) + - [Visibility](./packages/visibility.md) + - [Public Classes](./packages/public_classes.md) + - [Fully Qualified Class Name](./packages/fully_qualified_class_name.md) + - [Import](./packages/import.md) + - [Package Imports](./packages/package_imports.md) + - [The Default Package](./packages/the_default_package.md) + - [The Anonymous Main Class](./packages/the_anonymous_main_class.md) + - [Public Methods](./packages/public_methods.md) + - [Package-Private Methods](./packages/package_private_methods.md) + - [Public Fields](./packages/public_fields.md) + - [Package-Private Fields](./packages/package_private_fields.md) + - [The Default Constructor](./packages/the_default_constructor.md) + - [Public Constructors](./packages/public_constructors.md) + - [Package-Private Constructors](./packages/package_private_constructors.md) + - [Subpackages](./packages/subpackages.md) + - [Reverse Domain Name Notation](./packages/reverse_domain_name_notation.md) + - [Challenges](./packages/challenges.md) + +# Data Types VI + +- [Records](./records.md) + - [Declaration](./records/declaration.md) + - [The Canonical Constructor](./records/the_canonical_constructor.md) + - [Component Accessors](./records/component_accessors.md) + - [Component Accessor Visibility](./records/component_accessor_visibility.md) + - [Printing a Record](./records/printing_a_record.md) + - [Check for Equality](./records/check_for_equality.md) + - [Return Multiple Values](./records/return_multiple_values.md) + - [Shorthand](./records/shorthand.md) + - [Challenges](./records/challenges.md) + +- [Integers II](./integers_ii.md) + - [Integer from a String](./integers_ii/integer_from_a_string.md) + - [Integer to a String](./integers_ii/integer_to_a_string.md) + - [Base 16 Integer Literals](./integers_ii/base_16_integer_literals.md) + - [Integer from a Base 16 String](./integers_ii/integer_from_a_base_16_string.md) + - [Integer to a Base 16 String](./integers_ii/integer_to_a_base_16_string.md) + - [Underscores in Integer Literals](./integers_ii/underscores_in_integer_literals.md) + - [Challenges](./integers_ii/challenges.md) + + +# Interactive Programs IV + +- [Files](./files.md) + - [Paths](./files/paths.md) + - [IOException](./files/ioexception.md) + - [UncheckedIOException](./files/uncheckedioexception.md) + - [Write to a File](./files/write_to_a_file.md) + - [Read from a File](./files/read_from_a_file.md) + - [Create a Folder](./files/creating_a_folder.md) + - [Challenges](./files/challenges.md) + + +# Projects + + +- [Data Visualization](./projects/data_visualization.md) + + + -- [Multiple Source Files]() + +# Code Structure VI + +- [Object](./objects.md) + - [Subtypes](./objects/subtypes.md) + - [instanceof](./objects/instanceof.md) + - [toString](./objects/toString.md) + - [Override toString](./objects/override_toString.md) + - [@Override](./objects/override.md) + - [equals and hashCode](./objects/equals_and_hashCode.md) + - [Override equals and hashCode](./objects/override_equals_and_hashCode.md) + - [Challenges](./objects/challenges.md) + + +- [Generics](./generics.md) + - [Type Variables](./generics/type_variables.md) + - [Naming](./generics/naming.md) + - [Instantiation](./generics/instantiation.md) + - [Inference](./generics/inference.md) + - [Soundness](./generics/soundness.md) + - [Raw Types](./generics/raw_types.md) + - [Challenges](./generics/challenges.md) +- [Interfaces](./interfaces.md) + - [Interface Declaration](./interfaces/interface_declaration.md) + - [Implementation](./interfaces/implementation.md) + - [@Override](./interfaces/override.md) + - [Naming](./interfaces/naming.md) + - [Subtypes](./interfaces/subtypes.md) + - [Multiple Implementations](./interfaces/multiple_implementations.md) + - [Challenges](./interfaces/challenges.md) +# Data Types VII + + +- [Time](./time.md) + - [Instant](./time/instant.md) + - [Duration](./time/duration.md) + - [LocalDate](./time/local_date.md) + - [LocalTime](./time/local_time.md) + - [LocalDateTime](./time/local_date_time.md) + - [Time Zones](./time/time_zones.md) + - [ZonedDateTime](./time/zoned_date_time.md) + - [OffsetDateTime](./time/offset_date_time.md) + - [Date](./time/date.md) + - [Challenges](./time/challenges.md) +- [ArrayList](./array_list.md) + - [Ubiquity](./array_list/ubiquity.md) + - [Add an item](./array_list/add_an_item.md) + - [Size](./array_list/size.md) + - [Get an item](./array_list/get_an_item.md) + - [Loop over items](./array_list/loop_over_items.md) + - [Set an item](./array_list/set_an_item.md) + - [Remove an item](./array_list/remove_an_item.md) + - [Challenges](./array_list/challenges.md) +- [HashMap](./hash_maps.md) + - [Filing Cabinets](./hash_maps/filing_cabinets.md) + - [Keys and Values](./hash_maps/keys_and_values.md) + - [Put Items](./hash_maps/put_items.md) + - [Get Items](./hash_maps/get_items.md) + - [Hash Functions](./hash_maps/hash_functions.md) + - [Hash Collision](./hash_maps/hash_collision.md) + - [Hash Distribution](./hash_maps/hash_distribution.md) + - [Reference Based Identity](./hash_maps/reference_based_identity.md) + - [Value Based Identity](./hash_maps/value_based_identity.md) + - [Appropriate Keys](./hash_maps/appropriate_keys.md) + - [Ubiquity](./hash_maps/ubiquity.md) + - [Challenges](./hash_maps/challenges.md) + + +# Concepts II +- [Hyrum's Law](./hyrums_law.md) + - [Authority](./hyrums_law/authority.md) + - [Validity](./hyrums_law/validity.md) + - [Emergent Properties](./hyrums_law/emergent_properties.md) + - [Importance](./hyrums_law/importance.md) + +# Control Flow V + +- [Switch III](./switch_iii.md) + - [break](./switch_iii/break.md) + - [fallthrough](./switch_iii/fallthrough.md) + - [return](./switch_iii/return.md) + - [default](./switch_iii/default.md) + - [yield](./switch_iii/yield.md) + - [Challenges](./switch_iii/challenges.md) +- [Recursion](./recursion.md) + - [Disclaimer](./recursion/disclaimer.md) + - [Base Case](./recursion/base_case.md) + - [Comparison to Delegation](./recursion/comparison_to_delegation.md) + - [Comparison to Loops](./recursion/comparison_to_loops.md) + - [Counting Down](./recursion/counting_down.md) + - [Accumulators](./recursion/accumulators.md) + - [Recurse Over a String](./recursion/recursing_over_strings.md) + - [Recurse Over an Array](./recursion/recursing_over_arrays.md) + - [Challenges](./recursion/challenges.md) + +- [Loops III](./loops_iii.md) + - [For-each loops](./loops_iii/for_each_loops.md) + - [Arrays](./loops_iii/arrays.md) + - [Iterable and Iterator](./loops_iii/iterable_and_iterator.md) + - [ArrayList](./loops_iii/arraylist.md) + - [String](./loops_iii/string.md) + - [Concurrent Modifications](./loops_iii/concurrent_modifications.md) + - [Inferred Types](./loops_iii/inferred_types.md) + - [Challenges](./loops_iii/challenges.md) + +# Concepts III + +- [Encapsulation](./encapsulation.md) + - [Implementation Details](./encapsulation/implementation_details.md) + - [Implicit Interfaces](./encapsulation/implicit_interfaces.md) + - [Methods](./encapsulation/methods.md) + - [Classes](./encapsulation/classes.md) + - [Abstraction](./encapsulation/abstractions.md) + - [Coupling](./encapsulation/coupling.md) + - [Leaky Abstractions](./encapsulation/leaky_abstractions.md) + - [Information Hiding](./encapsulation/information_hiding.md) + + +# Data Types VIII + +- [Collections](./collections.md) + - [List](./collections/list.md) + - [Map](./collections/map.md) + - [Set](./collections/set.md) + - [Arrays](./collections/arrays.md) + - [UnsupportedOperationException](./collections/unsupported_operation_exception.md) + - [Factories](./collections/factories.md) + - [Specificity](./collections/specificity.md) + - [Challenges](./collections/challenges.md) + + +# Metaprogramming + +- [Reflection](./reflection.md) + - [Class Objects](./reflection/class_objects.md) + - [Get all Fields](./reflection/get_all_fields.md) + - [Get a Field](./reflection/get_a_field.md) + - [Read from a Field](./reflection/read_from_a_field.md) + - [Write to a Field](./reflection/write_to_a_field.md) + - [Get all Methods](./reflection/get_all_methods.md) + - [Get a Method](./reflection/get_a_method.md) + - [Invoke a Method](./reflection/invoke_a_method.md) + - [Get a Constructor](./reflection/get_a_constructor.md) + - [Get all Constructors](./reflection/get_all_constructors.md) + - [Invoke a Constructor](./reflection/invoke_a_constructor.md) + - [Challenges](./reflection/challenges.md) +- [Annotations](./annotations.md) + - [Declaration](./annotations/declaration.md) + - [Usage](./annotations/usage.md) + - [Elements](./annotations/elements.md) + - [Usage with Elements](./annotations/usage_with_elements.md) + - [Defaults](./annotations/defaults.md) + - [@Target](./annotations/target.md) + - [@Retention](./annotations/retention.md) + - [Reflective Access](./annotations/reflective_access.md) + - [@Override](./annotations/override.md) + - [Challenges](./annotations/challenges.md) + + + + +# Code Structure VII +- [Interfaces II](./interfaces_ii.md) + - [Default Methods](./interfaces_ii/default_methods.md) + - [Interface Extension](./interfaces_ii/interface_extension.md) + - [Static Methods](./interfaces_ii/static_methods.md) + - [Static Fields](./interfaces_ii/static_fields.md) + - [Challenges](./interfaces_ii/challenges.md) +- [Class Extension](./class_extension.md) + - [Extend a Class](./class_extension/extend_a_class.md) + - [Inheritance](./class_extension/inheritance.md) + - [Override](./class_extension/override.md) + - [Protected](./class_extension/protected.md) + - [Abstract Classes](./class_extension/abstract_classes.md) + - [Abstract Methods](./class_extension/abstract_methods.md) + - [Relation to Interfaces](./class_extension/relation_to_interfaces.md) + - [Relation to Encapsulation](./class_extension/relation_to_encapsulation.md) + - [Final Classes](./class_extension/final_classes.md) + - [Challenges](./class_extension/challenges.md) + + + + +# Data Types IX + +- [Niche Numerics](./niche_numerics.md) + - [byte](./niche_numerics/byte.md) + - [short](./niche_numerics/short.md) + - [long](./niche_numerics/long.md) + - [Unsigned Operations](./niche_numerics/unsigned_operations.md) + - [float](./niche_numerics/float.md) + - [Challenges](./niche_numerics/challenges.md) + +# Projects + + +- [Music Maker](./projects/music_maker.md) + +# Code Structure VIII + +- [Modules](./modules.md) + - [Declaration](./modules/declaration.md) + - [Restrictions](./modules/restrictions.md) + - [Exports](./modules/exports.md) + - [Requires](./modules/requires.md) + - [Module Imports](./modules/module_imports.md) + - [java.base](./modules/java.base.md) + - [The Unnamed Module](./modules/the_unnamed_module.md) + - [Multi-Module Directory Layout](./modules/multi_module_directory_layout.md) + - [Purpose](./modules/purpose.md) + - [Challenges](./modules/challenges.md) + +- [Lambdas](./lambdas.md) + - [Functional Interfaces](./lambdas/functional_interfaces.md) + - [@FunctionalInterface](./lambdas/functional_interface_annotation.md) + - [Lambda Expressions](./lambdas/lambda_expressions.md) + - [Arguments](./lambdas/arguments.md) + - [Return](./lambdas/return.md) + - [Method References](./lambdas/method_references.md) + - [Constructor References](./lambdas/constructor_references.md) + - [Inference](./lambdas/inference.md) + - [Built-In Functional Interfaces](./lambdas/built_in_functional_interfaces.md) + - [Checked Exceptions](./lambdas/checked_exceptions.md) + - [Challenges](./lambdas/challenges.md) + + + +# Sharing Code + +- [Compilation](./compilation.md) + - [javac](./compilation/javac.md) + - [Class Files](./compilation/class_files.md) + - [Modules](./compilation/modules.md) + - [Compile Multiple Files](./compilation/multiple_files.md) + - [Clean](./compilation/clean.md) + - [-g](./compilation/g.md) + - [Running Compiled Code](./compilation/running_compiled_code.md) + - [Challenges](./compilation/challenges.md) +- [Packaging](./packaging.md) + - [jar](./packaging/jar.md) + - [Jar Files](./packaging/jar_files.md) + - [--module-path](./packaging/module_path.md) + - [--main-class](./packaging/main_class.md) + - [Libraries](./packaging/libraries.md) + - [Challenges](./packaging/challenges.md) + +# Tools + +- [just](./just.md) + - [Installation](./just/installation.md) + - [Justfile](./just/justfile.md) + - [Recipes](./just/recipes.md) + - [Dependencies](./just/dependencies.md) + - [Documentation Comments](./just/documentation_comments.md) + - [Further Reading](./just/further_reading.md) + - [Challenges](./just/challenges.md) + +# Sharing Code II + + +- [Documentation](./documentation.md) + - [Documentation Comments](./documentation/documentation_comments.md) + - [Format](./documentation/format.md) + - [javadoc](./documentation/javadoc.md) + - [Challenges](./documentation/challenges.md) + +# Data Types X + +- [Streams](./streams.md) + - [stream](./streams/stream.md) + - [map](./streams/map.md) + - [filter](./streams/filter.md) + - [Terminal Operations](./streams/terminal_operations.md) + - [Collectors](./streams/collectors.md) + - [Purpose](./streams/purpose.md) + - [Challenges](./streams/challenges.md) + + + + + +# Conclusion + +- [What Now?](./conclusion/what_now.md) + + + + + + + + + +- [🚧 (More Chapters Planned) 🚧]() \ No newline at end of file diff --git a/src/abbreviations.md b/src/abbreviations.md new file mode 100644 index 00000000..4c8846ad --- /dev/null +++ b/src/abbreviations.md @@ -0,0 +1,14 @@ +# Abbreviations + +In the 1940s Franklin Delano Rosevelt (FDR) founded the AAA, CCC, FDIC, NRA, and many more "[Alphabet Agencies](https://en.wikipedia.org/wiki/Alphabet_agencies)." + +People called them the alphabet agencies because there were so many and their + + as part of an effort to lift the United States out of the [Great Depression](https://en.wikipedia.org/wiki/Great_Depression). + +NASA is an acronym + +FDIC is an initialism + +[^mrdoyle]: One of my high school history teachers, who loved FDR and named his kid after him, is banned from a at least one local museum due to an incident. + diff --git a/src/abbreviations/ambiguity.md b/src/abbreviations/ambiguity.md new file mode 100644 index 00000000..b1116ba4 --- /dev/null +++ b/src/abbreviations/ambiguity.md @@ -0,0 +1 @@ +# Ambiguity diff --git a/src/abbreviations/elaboration.md b/src/abbreviations/elaboration.md new file mode 100644 index 00000000..84f4a9d4 --- /dev/null +++ b/src/abbreviations/elaboration.md @@ -0,0 +1 @@ +# Elaboration diff --git a/src/abbreviations/familiarity.md b/src/abbreviations/familiarity.md new file mode 100644 index 00000000..c59e312f --- /dev/null +++ b/src/abbreviations/familiarity.md @@ -0,0 +1 @@ +# Familiarity diff --git a/src/abbreviations/niches.md b/src/abbreviations/niches.md new file mode 100644 index 00000000..6924136f --- /dev/null +++ b/src/abbreviations/niches.md @@ -0,0 +1,2 @@ +# Niches + diff --git a/src/abbreviations/usage_contexts.md b/src/abbreviations/usage_contexts.md new file mode 100644 index 00000000..3e334e96 --- /dev/null +++ b/src/abbreviations/usage_contexts.md @@ -0,0 +1 @@ +# Usage Contexts diff --git a/src/annotations.md b/src/annotations.md new file mode 100644 index 00000000..c1b31784 --- /dev/null +++ b/src/annotations.md @@ -0,0 +1,60 @@ +# Annotations + + + + +Comments are useful when reading code. Since they can contain any text, +you can use them to clarify your intent, make note of issues to address +later, etc. + +```java +class Main { + // TODO: Make this actually add one + int addOne(int x) { + return x + 2; + } + + // The return value here will always be non-negative + int absoluteValue(int x) { + if (x < 0) { + return -x; + } + else { + return x; + } + } + + void main() { + IO.println(addOne(5)); + IO.println(absoluteValue(-25)); + } +} +``` + +The problem with comments is that they are just text. It would be difficult to write a program +that acts upon or uses information in a comment. + +Annotations, which you can think of as "structured comments", are useful for this purpose. + +```java +class Main { + @TODO("Make this actually add one") + int addOne(int x) { + return x + 2; + } + + @NonNegative int absoluteValue(int x) { + if (x < 0) { + return -x; + } + else { + return x; + } + } + + void main() { + IO.println(addOne(5)); + IO.println(absoluteValue(-25)); + } +} +``` \ No newline at end of file diff --git a/src/annotations/challenges.md b/src/annotations/challenges.md new file mode 100644 index 00000000..b6699d6b --- /dev/null +++ b/src/annotations/challenges.md @@ -0,0 +1,48 @@ +# Challenges + + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Declare your own `@Favorite` and `@LeastFavorite` +annotations. + +Use them to mark the code you have written so far that is your +favorite and the code that is your least favorite. + +## Challenge 2. + +Declare a `@LoFi` annotation. It should have an element for the `author` +and another element for the `song` that you were listening to on +the "[24/7 Lofi Hip Hop Radio - Beats to Relax/Study To](https://www.youtube.com/watch?v=jfKfPfyJRdk)" +stream when you were writing the annotated code. + +Chill out to some tunes, write some code, and use that annotation. + +## Challenge 3. + +Write a method that takes in an `Object`. Use reflection to print out the values in that `Object`'s +declared fields. Skip any that are non-public but also any marked with a `@Skip` annotation +that you should also define. + +```java +// CODE HERE + +class Table { + public boolean flowerPot = true; + public String scissors = "green"; + + @Skip + public String cat = "tabby"; +} + +class Main { + void main() { + // CODE HERE + } +} +``` diff --git a/src/annotations/declaration.md b/src/annotations/declaration.md new file mode 100644 index 00000000..d276869e --- /dev/null +++ b/src/annotations/declaration.md @@ -0,0 +1,12 @@ +# Declaration + +To declare an annotation you use `@interface` followed by a type name. + +```java,no_run +@interface NonNegative { +} +``` + +We call these "annotation interfaces."[^shared] + +[^shared]: They share some properties with regular interfaces but it's best to think of them as their own thing. \ No newline at end of file diff --git a/src/annotations/defaults.md b/src/annotations/defaults.md new file mode 100644 index 00000000..33ccf94e --- /dev/null +++ b/src/annotations/defaults.md @@ -0,0 +1,63 @@ +# Defaults + +You can give a default value for an element by writing `default` followed by a value. + +You don't need to explicitly specify a value for any elements that have a default. + +```java +enum Priority { + HIGH, + LOW +} + +@interface Todo { + String description(); + Priority priority() default Priority.LOW; +} + +// Priority does not need to be specified since +// it has a default specified +@Todo(description="Write the code") +class Code { + // But you can still specify it in situations where you want to + @Todo(description="Write main method", priority=Priority.HIGH) + void main() { + } +} +``` + +If all values have defaults then you don't need to specify anything. + +```java +enum Priority { + HIGH, + LOW +} + +@interface Todo { + String description() default "Do Stuff"; + Priority priority() default Priority.LOW; +} + +@Todo +class Code { +} +``` + +And if the only element that doesn't have a default value is named `value`, and thats the only one you want to specify, you don't need to give its name. + +```java +enum Priority { + HIGH, + LOW +} + +@interface Todo { + String value(); + Priority priority() default Priority.LOW; +} + +@Todo("Really need to write something in here") +class Code { +} +``` \ No newline at end of file diff --git a/src/annotations/deprecated b/src/annotations/deprecated new file mode 100644 index 00000000..d57b74bf --- /dev/null +++ b/src/annotations/deprecated @@ -0,0 +1 @@ +# @Deprecated diff --git a/src/annotations/elements.md b/src/annotations/elements.md new file mode 100644 index 00000000..1ec5d9dd --- /dev/null +++ b/src/annotations/elements.md @@ -0,0 +1,66 @@ +# Elements + +Annotation interfaces can contain any number of elements. + +```java,no_run +@interface Todo { + String description(); +} +``` + +These are declared in the same way as methods in an interface, save for some +important restrictions. + +The method should take no arguments. + +```java,no_run,does_not_compile +@interface Todo { + // Cannot take arguments + String description(int x); +} +``` + +And the return value needs to be one of a few built-in types like `Class`, `String`, `int`, `boolean`, etc.[^fulllist], an `enum`, another annotation interface, or an array of one of those. + +```java,no_run,does_not_compile +enum Priority { + HIGH, + LOW +} + +@interface Deadline { + int year(); + int month(); + int day(); +} + +class PersonInCharge {} + +@interface Todo { + // String βœ… + String description(); + + // int βœ… + int someNumber(); + + // boolean βœ… + boolean isImportant(); + + // Class βœ… + Class someRelatedClass(); + + // Arrays βœ… + String[] notes(); + + // Enums βœ… + Priority priority(); + + // Other annotations βœ… + Deadline deadline(); + + // Other classes ❌ + PersonInCharge personInCharge(); +} +``` + +[^fulllist]: You can find the full list [here](https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6). It is a short list. diff --git a/src/annotations/header.png b/src/annotations/header.png new file mode 100644 index 00000000..ace3923d Binary files /dev/null and b/src/annotations/header.png differ diff --git a/src/annotations/override.md b/src/annotations/override.md new file mode 100644 index 00000000..bdbc3f05 --- /dev/null +++ b/src/annotations/override.md @@ -0,0 +1,33 @@ +# @Override + +The `@Override` you can put on methods to have Java check that you are properly overriding them +is an annotation. + +There's not much else to say besides "hey, thats what that was!" but I think its +worth noting. + +```java,no_run +interface Num { + boolean odd(); + + boolean even(); +} + +class IntNum implements Num { + final int x; + + IntNum(int x) { + this.x = x; + } + + @Override // This is an annotation? + public boolean odd() { + return x % 2 != 0; + } + + @Override // Always has been. + public boolean even() { + return !odd(); + } +} +``` \ No newline at end of file diff --git a/src/annotations/reflective_access.md b/src/annotations/reflective_access.md new file mode 100644 index 00000000..f889f624 --- /dev/null +++ b/src/annotations/reflective_access.md @@ -0,0 +1,56 @@ +# Reflective Access + +If something is annotated with a runtime retained annotation, +you can get an object holding that data using `.getAnnotation`. + +This will either return `null` if the field, method, class, etc. is not +annotated with that annotation or it will give you an object +with methods you can call to see the values specified in the annotation. + +```java +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; + +class Main { + void main() { + Class drinkClass = Drink.class; + + Field[] fields = drinkClass.getFields(); + for (var field : fields) { + IO.print(field.getName()); + IO.print(" - "); + + Special annotationValue = field.getAnnotation(Special.class); + if (annotationValue == null) { + IO.println("is not special."); + } + else if (annotationValue.isSuperDuperSpecial()) { + IO.println("is super-duper special."); + } + else { + IO.println("is special."); + } + } + } +} + +@Retention(RetentionPolicy.RUNTIME) +@interface Special { + boolean isSuperDuperSpecial() default false; +} + +class Drink { + public String name; + + @Special + public boolean caffeinated; + + @Special(isSuperDuperSpecial = true) + public String cost; + + public int height; +} +``` + +You can make use of this to implement a **very** wide variety of programs. \ No newline at end of file diff --git a/src/annotations/retention.md b/src/annotations/retention.md new file mode 100644 index 00000000..b60851c2 --- /dev/null +++ b/src/annotations/retention.md @@ -0,0 +1,25 @@ +# @Retention + +The next important meta-annotation is `@Retention`. + +If you want to be able to access an annotation using reflection +it needs to be marked with a retention policy of `RUNTIME`. + +```java,no_run +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@interface Special { +} +``` + +The other two options are `SOURCE` - which will throw away any evidence of the annotation after Java +compiles your code - and `CLASS` - which will save the annotation, but not in a way you can use at runtime. + +If you are making your own programs that make use of annotations, chances are you will be working with them at runtime. This means you will most likely be using `RUNTIME` more than the other options. + +That being said, there is nothing particularly wrong with `SOURCE` and `CLASS`. Its just that writing programs that use the annotations at those steps require some more work and there aren't too many downsides to `RUNTIME`. \ No newline at end of file diff --git a/src/annotations/target.md b/src/annotations/target.md new file mode 100644 index 00000000..ffd8fc95 --- /dev/null +++ b/src/annotations/target.md @@ -0,0 +1,24 @@ +# @Target + +Annotations can mark most parts of your program. Annotations are a part of your program. +Therefore annotations can have annotations. + +The first of these "meta-annotations" you are likely to use is `@Target`. +By default an annotation can mark anything, but you can use `@Target` to +restrict what can be marked.[^typeuse] + +```java,no_run +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +// @Even can now only be used on fields and methods, +// but not classes +@Target({ + ElementType.FIELD, + ElementType.METHOD +}) +@interface Even { +} +``` + +[^typeuse]: The exception to this is the `TYPE_USE` target. That one needs to be explicitly specified. \ No newline at end of file diff --git a/src/annotations/usage.md b/src/annotations/usage.md new file mode 100644 index 00000000..fa1d9af0 --- /dev/null +++ b/src/annotations/usage.md @@ -0,0 +1,27 @@ +# Usage + +Once you have defined an annotation you can use it to mark different elements +of your program. + +```java,no_run +@interface Even { +} + +@interface NumberWrapper { +} + +@NumberWrapper // Here @NumberWrapper is annotating the EvenNumber class +class EvenNumber { + final @Even int x; // And @Even is annotating the "x" field + + EvenNumber(int x) { + if (x % 2 != 0) { + throw new IllegalArgumentException(Integer.toString(x)); + } + this.x = x; + } +} +``` + +You can place an annotation on nearly any "structural" element of a program. +This includes classes, method definitions, fields, and more. diff --git a/src/annotations/usage_with_elements.md b/src/annotations/usage_with_elements.md new file mode 100644 index 00000000..4ae05183 --- /dev/null +++ b/src/annotations/usage_with_elements.md @@ -0,0 +1,67 @@ +# Usage with Elements + +If an annotation is declared with elements then you must specify +values for those elements when annotating a part of your program. + +```java,no_run +@interface Todo { + String description(); +} + +@Todo(description="Actually write code") +class Code { +} +``` + +For `String`, `int`, and `boolean` elements you do this using their corresponding literals. +For `Class` elements you use `ClassName.class`. And for arrays you use array initializers. + +```java,no_run +enum Priority { + HIGH, + LOW +} + +@interface Todo { + String description(); + + int someNumber(); + + boolean isImportant(); + + Class someRelatedClass(); + + String[] notes(); + + Priority priority(); +} + +@Todo( + description = "Write some code", + someNumber = 444, + isImportant = false, + someRelatedClass = ArrayList.class, + notes = { + "this example is potentially confusing", + "it isn't high priority enough to fix" + }, + priority = Priority.LOW +) +class Code { + +} +``` + +If the only element you need to provide is named `value`, then you don't need to give its name. + + +```java,no_run +@interface Todo { + // The name "value" is special + String value(); +} + +@Todo("Write some code") +class Code { +} +``` diff --git a/src/arguments.md b/src/arguments.md index 026bd526..220a84c2 100644 --- a/src/arguments.md +++ b/src/arguments.md @@ -1,12 +1,15 @@ # Arguments + + + If methods always had to do the same thing each time they were run, they wouldn't be that useful. The way to customize what happens when a method is called is to have them take "arguments." ```java void sayHello(String name) { - System.out.println("Hello " + name + "!"); + IO.println("Hello " + name + "!"); } void main() { diff --git a/src/arguments/aliasing.md b/src/arguments/aliasing.md index a27b6dc0..3c8b7d50 100644 --- a/src/arguments/aliasing.md +++ b/src/arguments/aliasing.md @@ -12,14 +12,14 @@ void main() { int[] nums = new int[] { 8 }; // The first number is 8 - System.out.println( + IO.println( "The first number is " + nums[0] ); incrementFirst(nums); // Now it is 9 - System.out.println( + IO.println( "Now it is " + nums[0] ); } diff --git a/src/arguments/challenges.md b/src/arguments/challenges.md index cc834883..810facd5 100644 --- a/src/arguments/challenges.md +++ b/src/arguments/challenges.md @@ -16,16 +16,16 @@ The `size` argument should control how big of a square is output. void main() { printSquare(4); - System.out.println(); + IO.println(); printSquare(3); - System.out.println(); + IO.println(); printSquare(2); - System.out.println(); + IO.println(); printSquare(1); - System.out.println(); + IO.println(); } ``` @@ -41,15 +41,51 @@ was given. void main() { printSquare(3); - System.out.println(); + IO.println(); printSquare(-3); - System.out.println(); + IO.println(); - System.out.println(); + IO.println(); printSquare(-2); - System.out.println(); + IO.println(); printSquare(2); } ``` ## Challenge 3. + +Write a method with four overloads such that +the code in `main` can run unchanged. + +```java,editable +// CODE HERE + +void main() { + f(2); + f("b"); + f('9'); + f(new String[] { "s" }); +} +``` + +## Challenge 4. + +Call the defined methods in a way that outputs "I did it!" + +```java,editable +void i() { + IO.print("I"); +} + +void did(String what) { + IO.println("did " + what); +} + +void space() { + IO.print(" "); +} + +void main() { + // Code here +} +``` \ No newline at end of file diff --git a/src/arguments/declaration.md b/src/arguments/declaration.md index fad2eb41..d162200a 100644 --- a/src/arguments/declaration.md +++ b/src/arguments/declaration.md @@ -9,14 +9,14 @@ Each argument declaration looks the same as a variable declaration and has both // This declares a single argument named "food" that // has a type of "String". void eat(String food) { - System.out.println("I ate " + food); + IO.println("I ate " + food); } // This declares two arguments // "to", which is a String and // "age", which is an int. void happyBirthday(String to, int age) { - System.out.println( + IO.println( "Happy " + age + "th birthday " + to + "!" ); } diff --git a/src/arguments/final_arguments.md b/src/arguments/final_arguments.md index 70903f6e..60aedd9a 100644 --- a/src/arguments/final_arguments.md +++ b/src/arguments/final_arguments.md @@ -5,7 +5,7 @@ Just like normal variable declarations, arguments can be marked ```java void eat(final String food) { - System.out.println("I ate " + food); + IO.println("I ate " + food); } void main() { @@ -17,10 +17,10 @@ If you try to reassign a final argument, Java will not accept your program. ```java,panics void eat(final String food) { - System.out.println("I ate " + food); + IO.println("I ate " + food); // Will not work food = "toast"; - System.out.println(food); + IO.println(food); } void main() { diff --git a/src/arguments/header.png b/src/arguments/header.png new file mode 100644 index 00000000..ef2b4b5c Binary files /dev/null and b/src/arguments/header.png differ diff --git a/src/arguments/inferred_types.md b/src/arguments/inferred_types.md index 54261180..cfd507ad 100644 --- a/src/arguments/inferred_types.md +++ b/src/arguments/inferred_types.md @@ -20,7 +20,7 @@ You must always explicitly write out the types of arguments. ```java,no_run void makeHorchata(double milkFatPercent) { - System.out.println( + IO.println( "Making a horchata with " + milkFatPercent + "% milk." ); } diff --git a/src/arguments/invocation_with_arguments.md b/src/arguments/invocation_with_arguments.md index 1c8800f1..79ce887e 100644 --- a/src/arguments/invocation_with_arguments.md +++ b/src/arguments/invocation_with_arguments.md @@ -6,11 +6,11 @@ of literals or variable names ending with `)`. ```java void eat(String food) { - System.out.println("I ate " + food); + IO.println("I ate " + food); } void happyBirthday(String to, int age) { - System.out.println( + IO.println( "Happy " + age + "th birthday " + to + "!" ); } diff --git a/src/arguments/overloading.md b/src/arguments/overloading.md index c96dcf56..fbc57eb0 100644 --- a/src/arguments/overloading.md +++ b/src/arguments/overloading.md @@ -6,15 +6,15 @@ or different numbers of arguments. ```java,no_run void doThing(int x) { - System.out.println(x); + IO.println(x); } void doThing(String name) { - System.out.println("Hello " + name); + IO.println("Hello " + name); } void doThing(int x, int y) { - System.out.println(x + y); + IO.println(x + y); } ``` @@ -24,15 +24,15 @@ you are passing. ```java void doThing(int x) { - System.out.println(x); + IO.println(x); } void doThing(String name) { - System.out.println("Hello " + name); + IO.println("Hello " + name); } void doThing(int x, int y) { - System.out.println(x + y); + IO.println(x + y); } void main() { diff --git a/src/arguments/reassignment.md b/src/arguments/reassignment.md index df04c0d2..0c898de5 100644 --- a/src/arguments/reassignment.md +++ b/src/arguments/reassignment.md @@ -6,9 +6,9 @@ within the method body; ```java void eat(String food) { - System.out.println("I ate " + food); + IO.println("I ate " + food); food = "nothing"; - System.out.println("Now I have " + food); + IO.println("Now I have " + food); } void main() { @@ -21,15 +21,15 @@ any variables passed to the method by the caller. ```java void eat(String food) { - System.out.println("I ate " + food); + IO.println("I ate " + food); food = "nothing"; - System.out.println("Now I have " + food) + IO.println("Now I have " + food); } void main() { String fruit = "apple"; eat(fruit); - System.out.println( + IO.println( "But in the caller I still have an " + fruit ); } diff --git a/src/array_list.md b/src/array_list.md new file mode 100644 index 00000000..51f8b5ff --- /dev/null +++ b/src/array_list.md @@ -0,0 +1,18 @@ +# ArrayList + + + + +Java comes with a generic growable array. It is called +an `ArrayList`. + +```java +import java.util.ArrayList; + +void main() { + ArrayList names = new ArrayList<>(); + names.add("John Wick"); + + IO.println(names); +} +``` \ No newline at end of file diff --git a/src/array_list/add_an_item.md b/src/array_list/add_an_item.md new file mode 100644 index 00000000..4f1d0a7f --- /dev/null +++ b/src/array_list/add_an_item.md @@ -0,0 +1,23 @@ +# Add an item + +To add an item to an `ArrayList` you use the `.add` +method. + +```java +import java.util.ArrayList; + +void main() { + ArrayList names = new ArrayList<>(); + names.add("The Bowery King"); + names.add("Caine"); + + IO.println(names); +} +``` + +The way this works is conceptually the +same as the growable array that we went over earlier. + +All you need to know though is that you can add +as many items as you will and the container will grow +dynamically. diff --git a/src/array_list/challenges.md b/src/array_list/challenges.md new file mode 100644 index 00000000..34ee38e1 --- /dev/null +++ b/src/array_list/challenges.md @@ -0,0 +1,81 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Replace at least one custom usage of a growable array in the code you have written so far +with an `ArrayList`. + +## Challenge 2. + +Capitalize every `String` in the `ArrayList`. + + +```java +class Main { + void main() { + ArrayList things = new ArrayList<>(); + things.add("movies"); + things.add("television"); + things.add("video games"); + + // CODE HERE + + // Should output + // [MOVIES, TELEVISION, VIDEO GAMES] + IO.println(things); + } +} +``` + +## Challenge 3. + +Every time John Wick assassinates someone he gets one crime coin +(to spend at a crime hotel). + +Watch the first John Wick movie. For each named character you can remember +John Wick assassinating add one crime coin to the `johnWick` ArrayList. + +```java +record CrimeCoin(String target) {} + +class Main { + void main() { + ArrayList johnWick = new ArrayList<>(); + + // CODE HERE + + IO.println(johnWick); + } +} +``` + +If you need to cheat you can find [the list here](https://listofdeaths.fandom.com/wiki/John_Wick#John_Wick_(2014)). + +## Challenge 4. + +Using the `ArrayList` of `CrimeCoin`s, construct an `ArrayList` with all the characters' +names. + +```java +record CrimeCoin(String target) {} + +class Main { + void main() { + ArrayList johnWick = new ArrayList<>(); + + // CODE FROM LAST CHALLENGE + + ArrayList names = new ArrayList<>(); + + // CODE HERE + + IO.println(names); + } +} +``` + diff --git a/src/array_list/get_an_item.md b/src/array_list/get_an_item.md new file mode 100644 index 00000000..05830af5 --- /dev/null +++ b/src/array_list/get_an_item.md @@ -0,0 +1,31 @@ +# Get an item + +To get an item at a given index in an `ArrayList` +you should use `.get` + +```java +import java.util.ArrayList; + +void main() { + ArrayList names = new ArrayList<>(); + names.add("Winston Scott"); + + String name = names.get(0); + IO.println(name); +} +``` + +If the index you picked is greater than the number of elements in the `ArrayList` +it will throw an `IndexOutOfBoundsException` + +```java,panics +import java.util.ArrayList; + +void main() { + ArrayList names = new ArrayList<>(); + names.add("Killa Harkan"); + + String name = names.get(10); + IO.println(name); +} +``` \ No newline at end of file diff --git a/src/array_list/header.png b/src/array_list/header.png new file mode 100644 index 00000000..f4688b55 Binary files /dev/null and b/src/array_list/header.png differ diff --git a/src/array_list/loop_over_items.md b/src/array_list/loop_over_items.md new file mode 100644 index 00000000..cd2444b7 --- /dev/null +++ b/src/array_list/loop_over_items.md @@ -0,0 +1,25 @@ +# Loop over Contents + +Just like the `.length` and `[]` operations on arrays, +you can loop over all the elements in an `ArrayList` +with the combination of `.size()` and `.get(idx)` + +```java +import java.util.ArrayList; + +void main() { + ArrayList names = new ArrayList<>(); + names.add("Sofia Al-Azwar"); + names.add("Viggo Tarasov"); + names.add("Iosef Tarasov"); + + for (int i = 0; i < names.size(); i++) { + String name = names.get(i); + + IO.println("NAME: " + name); + } +} +``` + +Its not a rule, but this is also a convenient use of for loops. +You can use all the same tricks you learned when looping over arrays here. \ No newline at end of file diff --git a/src/array_list/remove_an_item.md b/src/array_list/remove_an_item.md new file mode 100644 index 00000000..5ad38269 --- /dev/null +++ b/src/array_list/remove_an_item.md @@ -0,0 +1,91 @@ +# Remove an item + +You can use `.remove` to remove an item +from an `ArrayList`. + +To do this you need to provide it the value you want to remove. +It will do all the array resizing required internally, even +if the item is in the middle of a list. + +```java +import java.util.ArrayList; + +void main() { + ArrayList names = new ArrayList<>(); + names.add("The Bowry King"); + names.add("The Elder"); + names.add("The Harbinger"); + + IO.println(names); + + names.remove("The Elder"); + + IO.println(names); +} +``` + +Alternatively you can remove an item by its index. + +```java +import java.util.ArrayList; + +void main() { + ArrayList names = new ArrayList<>(); + names.add("The Bowry King"); + names.add("The Elder"); + names.add("The Harbinger"); + + IO.println(names); + + names.remove(2); + names.remove(0); + + IO.println(names); +} +``` + +You need to be careful about that though. If your `ArrayList` holds `Integer`s then +Java can get confused between the implementation of remove that removes using the item itself +and the one that removes by index. + +```java,not_desired_behavior +import java.util.ArrayList; + +void main() { + ArrayList numbers = new ArrayList<>(); + numbers.add(1); + numbers.add(2); + numbers.add(3); + + IO.println(numbers); + + // Notice that this removes "2" which is at index 1! + numbers.remove(1); + + IO.println(numbers); +} +``` + +This comes down to `int` and `Integer` being slightly different. When Java +sees an integer literal it assumes it is an `int`. It is only when circumstances +are such that it cannot possibly be an `int` that it automatically boxes it +into an `Integer`. + +It is rare, but if you encounter this issue you can use `Integer.valueOf` to deal with it. + +```java +import java.util.ArrayList; + +void main() { + ArrayList numbers = new ArrayList<>(); + numbers.add(1); + numbers.add(2); + numbers.add(3); + + IO.println(numbers); + + numbers.remove(Integer.valueOf(1)); + + IO.println(numbers); +} +``` \ No newline at end of file diff --git a/src/array_list/set_an_item.md b/src/array_list/set_an_item.md new file mode 100644 index 00000000..edca5f8f --- /dev/null +++ b/src/array_list/set_an_item.md @@ -0,0 +1,30 @@ +# Set an item + +You can set an item at an index with `.set` + +```java +import java.util.ArrayList; + +void main() { + ArrayList names = new ArrayList<>(); + names.add("John Wick"); + + names.set(0, "Baba Yaga"); + + IO.println(names); + IO.println(names.get(0)); +} +``` + +If the index you provide is out of bounds, you will get an `IndexOutOfBoundsException`. + +```java,panics +import java.util.ArrayList; + +void main() { + ArrayList names = new ArrayList<>(); + names.add("John Wick"); + + names.set(15, "Baba Yaga"); +} +``` \ No newline at end of file diff --git a/src/array_list/size.md b/src/array_list/size.md new file mode 100644 index 00000000..45625aa0 --- /dev/null +++ b/src/array_list/size.md @@ -0,0 +1,27 @@ +# Size + +Then you can access the number of elements in an +`ArrayList` with the `.size()` method. + +```java +import java.util.ArrayList; + +void main() { + ArrayList names = new ArrayList<>(); + + IO.println(names.size()); + + names.add("Vincent Bisset de Gramont"); + IO.println(names.size()); + + names.add("Mr. Nobody"); + IO.println(names.size()); +} +``` + +This will tell you the number of elements in the `ArrayList` but +not the amount of space allocated by the underlying array. + +This is fine because, if you aren't the one making a growable array, +it doesn't matter. All you need to concern yourself with is what is actually +there, not any book keeping. \ No newline at end of file diff --git a/src/array_list/ubiquity.md b/src/array_list/ubiquity.md new file mode 100644 index 00000000..cef161b0 --- /dev/null +++ b/src/array_list/ubiquity.md @@ -0,0 +1,14 @@ +# Ubiquity + +Java comes with many classes. Some of these +you might use a few times (like `LocalTime`), some you +will see every day of your coding life (like `String`). + +`ArrayList` is in the second category. It turns out +that a growable list of things is needed for a lot +of different tasks. It is ubiquitous in code that +you will see in the real world. + +I mention this mostly to make sure you are paying attention. +Its not particuarly hard, but try not to zone out on these sorts +of chapters. \ No newline at end of file diff --git a/src/arrays.md b/src/arrays.md index 328ab334..d59f0b90 100644 --- a/src/arrays.md +++ b/src/arrays.md @@ -1,5 +1,8 @@ # Arrays + + + Arrays are used to represent a fixed-size collection of things. ```java,no_run diff --git a/src/arrays/access_individual_elements.md b/src/arrays/access_individual_elements.md index 17eb48ec..61ce8175 100644 --- a/src/arrays/access_individual_elements.md +++ b/src/arrays/access_individual_elements.md @@ -11,13 +11,13 @@ The first element can be accessed by using `0`, the second by using `1`, and so String[] lyrics = { "you", "say", "goodbye" }; String you = lyrics[0]; -System.out.println(you); +IO.println(you); String say = lyrics[1]; -System.out.println(say); +IO.println(say); String goodbye = lyrics[2]; -System.out.println(goodbye); +IO.println(goodbye); ~} ``` @@ -28,7 +28,7 @@ The index of the element can also come from a variable. int index = 2; String[] lyrics = { "I", "say", "hello" }; -System.out.println(lyrics[index]); +IO.println(lyrics[index]); ~} ``` @@ -39,7 +39,7 @@ you will get an error. ~void main(){ String[] lyrics = { "I", "say", "hi" }; // Crash! -System.out.println(lyrics[999]); +IO.println(lyrics[999]); ~} ``` @@ -47,6 +47,6 @@ System.out.println(lyrics[999]); ~void main(){ String[] lyrics = { "you", "say", "low" }; // Crash! -System.out.println(lyrics[-1]); +IO.println(lyrics[-1]); ~} ``` diff --git a/src/arrays/aliasing.md b/src/arrays/aliasing.md index 8ac06229..008c6237 100644 --- a/src/arrays/aliasing.md +++ b/src/arrays/aliasing.md @@ -12,23 +12,59 @@ char[] lettersOne = { 'B', 'a', 't', 'm', 'a', 'n' }; char[] lettersTwo = lettersOne; // Batman -System.out.println(lettersOne); +IO.print(lettersOne[0]); +IO.print(lettersOne[1]); +IO.print(lettersOne[2]); +IO.print(lettersOne[3]); +IO.print(lettersOne[4]); +IO.print(lettersOne[5]); +IO.println(); // Batman -System.out.println(lettersTwo); +IO.print(lettersTwo[0]); +IO.print(lettersTwo[1]); +IO.print(lettersTwo[2]); +IO.print(lettersTwo[3]); +IO.print(lettersTwo[4]); +IO.print(lettersTwo[5]); +IO.println(); lettersOne[0] = 'C'; // Catman -System.out.println(lettersOne); +IO.print(lettersOne[0]); +IO.print(lettersOne[1]); +IO.print(lettersOne[2]); +IO.print(lettersOne[3]); +IO.print(lettersOne[4]); +IO.print(lettersOne[5]); +IO.println(); // Catman -System.out.println(lettersTwo); +IO.print(lettersTwo[0]); +IO.print(lettersTwo[1]); +IO.print(lettersTwo[2]); +IO.print(lettersTwo[3]); +IO.print(lettersTwo[4]); +IO.print(lettersTwo[5]); +IO.println(); lettersTwo[0] = 'R'; // Ratman -System.out.println(lettersOne); +IO.print(lettersOne[0]); +IO.print(lettersOne[1]); +IO.print(lettersOne[2]); +IO.print(lettersOne[3]); +IO.print(lettersOne[4]); +IO.print(lettersOne[5]); +IO.println(); // Ratman -System.out.println(lettersTwo); +IO.print(lettersTwo[0]); +IO.print(lettersTwo[1]); +IO.print(lettersTwo[2]); +IO.print(lettersTwo[3]); +IO.print(lettersTwo[4]); +IO.print(lettersTwo[5]); +IO.println(); ~} ``` diff --git a/src/arrays/challenges.md b/src/arrays/challenges.md index c210ab03..da73b000 100644 --- a/src/arrays/challenges.md +++ b/src/arrays/challenges.md @@ -13,13 +13,13 @@ Edit the following program so that the output is zero. void main() { // Only change this line String[] words = { "Sam", "I", "Am" }; - System.out.println(array.length); + IO.println(words.length); } ``` ## Challenge 2 -Using only `System.out.println` and array accesses, +Using only `IO.println` and array accesses, print `hello world` to the screen. ```java,editable @@ -59,7 +59,7 @@ void main() { total += numbers[index]; index += 1; } - System.out.println(total); + IO.println(total); } ``` @@ -78,6 +78,11 @@ void main() { // ----------- char[] toPrint = name; - System.out.println(toPrint); + int index = 0; + while (index < toPrint.length) { + IO.print(toPrint[index]); + index++; + } + IO.println(); } ``` diff --git a/src/arrays/difference_between_initializer_and_literal.md b/src/arrays/difference_between_initializer_and_literal.md index b3c71b03..eb12780e 100644 --- a/src/arrays/difference_between_initializer_and_literal.md +++ b/src/arrays/difference_between_initializer_and_literal.md @@ -7,30 +7,38 @@ When you have a literal, like a `String` literal, you can assign that to a varia then use that `String` afterwards. ```java +~void main() { String name = "Alana"; // l -System.out.println(name.charAt(1)); +IO.println(name.charAt(1)); +~} ``` But you can also perform those operations using the literal itself, without an intermediate variable. ```java +~void main() { // l -System.out.println("Alana".charAt(1)); +IO.println("Alana".charAt(1)); +~} ``` Array initializers work in the case where you first assign them to a variable before using the array. ```java +~void main() { char[] name = { 'A', 'm', 'a', 'n', 'd', 'a' }; // m -System.out.println(name[1]); +IO.println(name[1]); +~} ``` But they do not work to perform operations on directly. ```java +~void main() { // Will not run -System.out.println({ 'A', 'm', 'a', 'n', 'd', 'a' }[1]); +IO.println({ 'A', 'm', 'a', 'n', 'd', 'a' }[1]); +~} ``` diff --git a/src/arrays/empty_array.md b/src/arrays/empty_array.md index 12f8da9c..531962fe 100644 --- a/src/arrays/empty_array.md +++ b/src/arrays/empty_array.md @@ -3,7 +3,7 @@ If you use an array initializer that has no elements between the `{` and `}` you can create an empty array. -```java +```java,no_run char[] emptyCharArray = {}; ``` @@ -11,12 +11,14 @@ An empty array is very similar to an empty `String`. It has a length of 0, it ha and it is generally useful only as a placeholder value for when you have no data yet but will be able to reassign the variable holding it when you get some. -```java +```java,panics +~void main() { char[] emptyCharArray = {}; // 0 -System.out.println(emptyCharArray.length); +IO.println(emptyCharArray.length); // Crash -System.out.println(emptyCharArray[0]); +IO.println(emptyCharArray[0]); +~} ``` \ No newline at end of file diff --git a/src/arrays/header.png b/src/arrays/header.png new file mode 100644 index 00000000..4cb55c50 Binary files /dev/null and b/src/arrays/header.png differ diff --git a/src/arrays/initialization_with_new.md b/src/arrays/initialization_with_new.md new file mode 100644 index 00000000..107b2b77 --- /dev/null +++ b/src/arrays/initialization_with_new.md @@ -0,0 +1,51 @@ +# Initialization with new + +Before the initializer for an array, you are allowed to write +`new` followed by a space, the type of thing in the array, +and an empty `[]`. + +```java +~void main() { +char[] mainCharacter = { 'A', 'a', 'n', 'g' }; +IO.println(mainCharacter[0]); +IO.println(mainCharacter[1]); +IO.println(mainCharacter[2]); +IO.println(mainCharacter[3]); +IO.println(); + +char[] sideCharacter = new char[] { 'A', 'a', 'n', 'g' }; +IO.println(sideCharacter[0]); +IO.println(sideCharacter[1]); +IO.println(sideCharacter[2]); +IO.println(sideCharacter[3]); +IO.println(); +~} +``` + +This is required for performing delayed initialization of a variable +holding an array. + +```java +~void main() { +char[] element; + +element = new char[] { 'f', 'i', 'r', 'e' }; +IO.println(element[0]); +IO.println(element[1]); +IO.println(element[2]); +IO.println(element[3]); +IO.println(); + +// This would not work +// element = { 'f', 'i', 'r', 'e' }; +~} +``` + +One ability this gives you is to use an array in an expression. I.E. +the initializer coupled with the `new char[]` is akin to an "array expression." + +```java +~void main() { +IO.println(new char[]{ 'K', 'a', 't', 'a', 'r', 'a' }[1]); +~} +``` \ No newline at end of file diff --git a/src/arrays/initialization_without_initializer b/src/arrays/initialization_without_initializer deleted file mode 100644 index 90f0f138..00000000 --- a/src/arrays/initialization_without_initializer +++ /dev/null @@ -1 +0,0 @@ -# Initializion without Initializer diff --git a/src/arrays/initialization_without_initializer.md b/src/arrays/initialization_without_initializer.md deleted file mode 100644 index e69de29b..00000000 diff --git a/src/arrays/length.md b/src/arrays/length.md index 82c269f8..1cb840e1 100644 --- a/src/arrays/length.md +++ b/src/arrays/length.md @@ -8,7 +8,7 @@ String[] veggies = { "brussels", "cabbage", "carrots" }; int numberOfElements = veggies.length; // veggies is 3 elements long -System.out.println( +IO.println( "veggies is " + numberOfElements + " characters long" ); ~} diff --git a/src/arrays/printing_the_contents_of_an_array.md b/src/arrays/printing_the_contents_of_an_array.md index eed6ad4a..a44186f3 100644 --- a/src/arrays/printing_the_contents_of_an_array.md +++ b/src/arrays/printing_the_contents_of_an_array.md @@ -1,6 +1,6 @@ # Printing the Contents of an Array -If you try to use `System.out.println` to output a `String[]` +If you try to use `IO.println` to output a `String[]` you won't see the contents of the array. Instead you will see something like `[Ljava.lang.String;@1c655221`. @@ -8,7 +8,7 @@ something like `[Ljava.lang.String;@1c655221`. ~void main() { String[] shout = { "fus", "ro", "dah" }; // [Ljava.lang.String;@5a07e868 -System.out.println(shout); +IO.println(shout); ~} ``` @@ -18,26 +18,15 @@ A similar thing will happen with `int[]`, `boolean[]`, and `double[]`.[^gibberis ~void main() { int[] nums = { 11, 11, 11 }; // [I@5a07e868 -System.out.println(nums); +IO.println(nums); boolean[] bools = { true, false }; // [Z@5a07e868 -System.out.println(bools); +IO.println(bools); double[] doubles = { 1.1, 1.1, 1.1 }; // [D@5a07e868 -System.out.println(bools); -~} -``` - -The only kind of array which will include its contents when printed is a `char[]`. -It will be printed as if it were a `String`. - -```java -~void main() { -char[] continent = { 'T', 'a', 'm', 'r', 'i', 'e', 'l' }; -// Tamriel -System.out.println(continent); +IO.println(bools); ~} ``` @@ -45,14 +34,17 @@ If you want to actually see the contents of an array, you should use a loop.[^future] ```java +~void main() { String[] factions = { "empire", "stormcloaks", "dragons" }; int index = 0; while (index < factions.length) { - System.out.println(factions[index]) - index++ + IO.println(factions[index]); + index++; } +~} ``` [^gibberish]: What `[I@5a07e868` and co. mean isn't really important. Try not to get too distracted by it. + [^future]: Later on, there will be easier ways to do this sort of inspection. This is just the one I can demonstrate now. diff --git a/src/arrays/reassignment.md b/src/arrays/reassignment.md index 2b09a02d..94b8303c 100644 --- a/src/arrays/reassignment.md +++ b/src/arrays/reassignment.md @@ -12,15 +12,15 @@ So to reassign an `int[]` you need to write something like `new int[] { 1, 2, 3 ~void main() { int[] numbers = { 1, 2 }; // 2 -System.out.println(numbers.length); +IO.println(numbers.length); numbers = new int[] { numbers[0], numbers[1], 3 }; // 3 -System.out.println(numbers.length); +IO.println(numbers.length); ~} ``` -This reassignment will not be affect any variables which +This reassignment will not affect any variables which are aliases for the variable's old value. ```java @@ -28,29 +28,75 @@ are aliases for the variable's old value. char[] wordOne = { 'g', 'o' }; char[] wordTwo = wordOne; // go -System.out.println(wordOne); +IO.print("wordOne Length: "); +IO.println(wordOne.length); +IO.print(wordOne[0]); +IO.print(wordOne[1]); +IO.println(); // go -System.out.println(wordTwo); +IO.print("wordTwo Length: "); +IO.println(wordTwo.length); +IO.print(wordTwo[0]); +IO.print(wordTwo[1]); +IO.println(); + +IO.println("-------"); wordOne = new char[] { wordOne[0], wordOne[1], 's', 'h' }; // gosh -System.out.println(wordOne); +IO.print("wordOne Length: "); +IO.println(wordOne.length); +IO.print(wordOne[0]); +IO.print(wordOne[1]); +IO.print(wordOne[2]); +IO.print(wordOne[3]); +IO.println(); // go -System.out.println(wordTwo); +IO.print("wordTwo Length: "); +IO.println(wordTwo.length); +IO.print(wordTwo[0]); +IO.print(wordTwo[1]); +IO.println(); + + +IO.println("-------"); wordTwo[0] = 'n'; // gosh -System.out.println(wordOne); +IO.print("wordOne Length: "); +IO.println(wordOne.length); +IO.print(wordOne[0]); +IO.print(wordOne[1]); +IO.print(wordOne[2]); +IO.print(wordOne[3]); +IO.println(); // no -System.out.println(wordTwo); +IO.print("wordTwo Length: "); +IO.println(wordTwo.length); +IO.print(wordTwo[0]); +IO.print(wordTwo[1]); +IO.println(); + + +IO.println("-------"); wordOne[0] = 'p'; // posh -System.out.println(wordOne); +IO.print("wordOne Length: "); +IO.println(wordOne.length); +IO.print(wordOne[0]); +IO.print(wordOne[1]); +IO.print(wordOne[2]); +IO.print(wordOne[3]); +IO.println(); // no -System.out.println(wordTwo); +IO.print("wordTwo Length: "); +IO.println(wordTwo.length); +IO.print(wordTwo[0]); +IO.print(wordTwo[1]); +IO.println(); ~} ``` diff --git a/src/arrays/relation_to_final_variables.md b/src/arrays/relation_to_final_variables.md index 3472c8b2..193bbdcb 100644 --- a/src/arrays/relation_to_final_variables.md +++ b/src/arrays/relation_to_final_variables.md @@ -9,10 +9,15 @@ that the array's contents cannot be changed directly or through an alias. ~void main() { final char[] catchphrase = { 'w', 'o', 'a', 'h', '!' }; // woah! -System.out.println(catchphrase); +IO.print(catchphrase[0]); +IO.print(catchphrase[1]); +IO.print(catchphrase[2]); +IO.print(catchphrase[3]); +IO.print(catchphrase[4]); +IO.println(); // Cannot reassign -// catchphrase = { 'e', 'g', 'a', 'd', 's' } +// catchphrase = new char[] { 'e', 'g', 'a', 'd', 's' } // but can set elements directly catchphrase[0] = 'e'; catchphrase[1] = 'g'; @@ -24,6 +29,11 @@ alias[3] = 'd'; alias[4] = 's'; // egads -System.out.println(catchphrase); +IO.print(catchphrase[0]); +IO.print(catchphrase[1]); +IO.print(catchphrase[2]); +IO.print(catchphrase[3]); +IO.print(catchphrase[4]); +IO.println(); ~} ``` diff --git a/src/arrays/set_individual_elements.md b/src/arrays/set_individual_elements.md index 3b5661a9..e518484f 100644 --- a/src/arrays/set_individual_elements.md +++ b/src/arrays/set_individual_elements.md @@ -9,10 +9,26 @@ the new value.[^strings] ```java ~void main() { String[] sentence = { "you", "are", "found", "guilty" }; -System.out.println(sentence); +IO.println( + sentence[0] + + " " + + sentence[1] + + " " + + sentence[2] + + " " + + sentence[3] +); sentence[1] = "aren't"; -System.out.println(sentence); +IO.println( + sentence[0] + + " " + + sentence[1] + + " " + + sentence[2] + + " " + + sentence[3] +); ~} ``` @@ -22,16 +38,36 @@ The index of the element to set can also come from a variable. ~void main() { int index = 2; String[] response = { "and", "it", "isn't", "opposite", "day" }; -System.out.println(response); +IO.println( + response[0] + + " " + + response[1] + + " " + + response[2] + + " " + + response[3] + + " " + + response[4] +); -response[2] = "is"; -System.out.println(response); +response[index] = "is"; +IO.println( + response[0] + + " " + + response[1] + + " " + + response[2] + + " " + + response[3] + + " " + + response[4] +); ~} ``` If you give a number equal to or greater than the length of the array or a number less than zero, you will get an error. -```java +```java,panics ~void main() { String[] response = { "objection" }; // Crash diff --git a/src/arrays_ii.md b/src/arrays_ii.md index 9586633f..d083eb6f 100644 --- a/src/arrays_ii.md +++ b/src/arrays_ii.md @@ -1,5 +1,8 @@ # Arrays II + + + Fairly often you will want to have arrays in your program which you either do not know the initial values for or which are too big to physically type out every value in an initializer. diff --git a/src/arrays_ii/challenges.md b/src/arrays_ii/challenges.md new file mode 100644 index 00000000..ca4817cc --- /dev/null +++ b/src/arrays_ii/challenges.md @@ -0,0 +1,111 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Make an empty `String` array without using an empty initializer. + +This means you cannot write `String[] empty = {}` or `String[] empty = new String[] {}`. + +```java,editable +void main() { + String[] empty = ???; + + // Should be 0 + IO.println(empty.length); +} +``` + +## Challenge 2. + +What will the following code output? Change the code +between the lines so it instead outputs the following. + +``` +1.0 +2.0 +3.0 +4.0 +5.0 +``` + + +```java,editable +void main() { + Double[] prices = new Double[5]; + + // ---------- + // CODE HERE + // ---------- + + for (int i = 0; i < prices.length; i++) { + double price = prices[i]; + IO.println(price); + } +} +``` + +## Challenge 3. + +Only writing code between the lines and without reassigning the `sandwich` variable, +make the following code output `egg and cheese`. + +```java,editable +void main() { + char[] sandwich = new char[14]; + + // ---------- + // CODE HERE + // ---------- + + IO.println(sandwich); +} +``` + +## Challenge 4. + +Populate the `triangle` array such that the code +prints a right triangle that looks like the following. + +``` +* +** +*** +``` + +```java,editable +void main() { + char[] triangle = new char[8]; + + // ---------- + // CODE HERE + // ---------- + + IO.println(triangle); +} +``` + +## Challenge 5. + +Make a method named `buildTriangle` which returns a `char[]` +that can be printed out to display a right triangle of any height. + +You can ignore the possibility that a negative or zero height is given. + +```java,editable +char[] buildTriangle(int height) { + // CODE HERE +} + +void main() { + IO.println(buildTriangle(3)); + IO.println("--------------"); + IO.println(buildTriangle(5)); + IO.println("--------------"); + IO.println(buildTriangle(2)); +} +``` \ No newline at end of file diff --git a/src/arrays_ii/default_values.md b/src/arrays_ii/default_values.md index 7345f27e..edf86c66 100644 --- a/src/arrays_ii/default_values.md +++ b/src/arrays_ii/default_values.md @@ -9,11 +9,11 @@ For primitive types like `int` and `double`, each element will be initialized to ~void main() { int[] digits = new int[10]; // 0 -System.out.println(digits[0]); +IO.println(digits[0]); double[] readings = new double[5]; // 0.0 -System.out.println(readings[0]); +IO.println(readings[0]); ~} ``` @@ -23,7 +23,7 @@ For `boolean`, each element will be initialized to `false`.[^funfact] ~void main() { boolean[] pokedex = new boolean[10]; // false -System.out.println(pokedex[0]); +IO.println(pokedex[0]); ~} ``` @@ -34,11 +34,11 @@ the default value will be `null`. ~void main() { String[] names = new String[10]; // null -System.out.println(names[0]); +IO.println(names[0]); Integer[] scores = new Integer[26]; // null -System.out.println(scores[0]); +IO.println(scores[0]); ~} ``` diff --git a/src/arrays_ii/header.png b/src/arrays_ii/header.png new file mode 100644 index 00000000..2618ac37 Binary files /dev/null and b/src/arrays_ii/header.png differ diff --git a/src/arrays_ii/initialization_with_size.md b/src/arrays_ii/initialization_with_size.md index 463ea053..109c9f7e 100644 --- a/src/arrays_ii/initialization_with_size.md +++ b/src/arrays_ii/initialization_with_size.md @@ -1,11 +1,11 @@ -# Initializion with Size +# Initialization with Size The Nintendo GameBoy had a screen resolution of 160 x 144. To store the value of each pixel[^bw] you would need an array 23,040 items long. To support this without you writing the word `false` 23,040 times, -arrays can be made with just by giving a size and skipping the initializer. +arrays can be made just by giving a size and skipping the initializer. ```java,no_run boolean[] pixels = new boolean[23040]; @@ -13,4 +13,5 @@ boolean[] pixels = new boolean[23040]; So you have to say `new` followed by the type of element in the array, `[`, the size of the array and `]`. -[^bw]: The original GameBoy was just black and white, so a `boolean` works just fine to represent a pixel's state. \ No newline at end of file + +[^bw]: The original GameBoy wasn't actually just black and white. It supported 7 shades of gray, so a `boolean` wouldn't technically be enough to represent a pixel's state. You'd have to use something with at least 8 states, not just 2. diff --git a/src/arrays_ii/populate_arrays.md b/src/arrays_ii/populate_arrays.md index c494e076..effe3d42 100644 --- a/src/arrays_ii/populate_arrays.md +++ b/src/arrays_ii/populate_arrays.md @@ -11,23 +11,6 @@ char[] letters = new char[26]; for (int i = 0; i < letters.length; i++) { letters[i] = (char) ('a' + i); } -System.out.println(letters); +IO.println(letters); ~} ``` - -But if you are just writing some value to every index -without any other interesting logic, you can use `Arrays.fill`. - -```java -~void main() { -int[] allNines = new char[123]; -Arrays.fill(allNines, 9); - -for (int i = 0; i < allNines.length; i++) { - System.out.println(allNines[i]); -} -~} -``` - -You give that the array and the value to fill it with. In the example above, the array starts -out with everything defaulted to `0` and is then filled with `9`s. diff --git a/src/boolean.md b/src/boolean.md index a5ac1282..46ae8a43 100644 --- a/src/boolean.md +++ b/src/boolean.md @@ -1,5 +1,7 @@ # Booleans + + A `boolean` is either `true` or `false`. ```java,no_run diff --git a/src/boolean/challenges.md b/src/boolean/challenges.md index 5e204f30..e209490d 100644 --- a/src/boolean/challenges.md +++ b/src/boolean/challenges.md @@ -18,7 +18,7 @@ void main() { boolean result = a || b && c || !d; - System.out.println(result); + IO.println(result); } ``` @@ -35,7 +35,7 @@ void main() { boolean result = !(a || b && c || !d) || (a && b || c); - System.out.println(result); + IO.println(result); } ``` @@ -51,7 +51,7 @@ void main() { boolean validChoice = < YOUR CODE HERE >; - System.out.println(validChoice); + IO.println(validChoice); } ``` diff --git a/src/boolean/header.png b/src/boolean/header.png new file mode 100644 index 00000000..289abe26 Binary files /dev/null and b/src/boolean/header.png differ diff --git a/src/boolean/not.md b/src/boolean/not.md index 0b20f8c1..925d5f80 100644 --- a/src/boolean/not.md +++ b/src/boolean/not.md @@ -4,7 +4,7 @@ Booleans can also be "negated" using the "not" operator - `!`. ```java,no_run boolean haveOreosInHouse = true; -boolean stuckToCalorieLimit = !haveOreos; +boolean stuckToCalorieLimit = !haveOreosInHouse; ``` So in this case, I have stuck to my calorie limit if there are _not_ Oreos in the house. diff --git a/src/boolean/operator_precedence.md b/src/boolean/operator_precedence.md index dfec44e8..78662d51 100644 --- a/src/boolean/operator_precedence.md +++ b/src/boolean/operator_precedence.md @@ -2,14 +2,14 @@ The operators that work on booleans have a "precedence order." -This is defines an order of operations similar to mathematics, where multiplication and division happen before +This defines an order of operations similar to mathematics, where multiplication and division happen before addition and subtraction. For booleans `!` always happens first. This is followed by `&&` and then by `||`. ```java,no_run boolean a = true; -boolean b = false +boolean b = false; boolean c = false; // just as 2 + 5 * 3 "evaluates" 5 * 3 before adding 2 diff --git a/src/boolean/or.md b/src/boolean/or.md index 59984d97..62d4a859 100644 --- a/src/boolean/or.md +++ b/src/boolean/or.md @@ -20,7 +20,7 @@ walking the dog looks nice. ## Exclusive vs. Inclusive -It is important too note that this is not an "exclusive" OR. +It is important to note that this is not an "exclusive" OR. An exclusive OR would be something like a child being allowed to have ice cream _or_ a cookie, but not both. diff --git a/src/boxed_primitives.md b/src/boxed_primitives.md index d28e9788..29040b00 100644 --- a/src/boxed_primitives.md +++ b/src/boxed_primitives.md @@ -1,5 +1,8 @@ # Boxed Primitives + + + The fact that `int`, `double`, `char`, and `boolean` cannot have `null` values can be limiting. @@ -8,10 +11,10 @@ For this reason there are versions of those primitive[^primitive] types which do ```java void sayAge(Integer age) { if (age == null) { - System.out.println("Age is not yet known"); + IO.println("Age is not yet known"); } else { - System.out.println("Age is " + age); + IO.println("Age is " + age); } } @@ -24,7 +27,7 @@ void main() { } ``` -We call these primitives which might be null "Boxed Primitives" because you they are made by taking +We call these primitives which might be null "Boxed Primitives" because they are made by taking the underlying thing and putting it in a "box."[^boxing] diff --git a/src/boxed_primitives/arrays_of_boxed_primitives.md b/src/boxed_primitives/arrays_of_boxed_primitives.md index 5cece930..6e3509ea 100644 --- a/src/boxed_primitives/arrays_of_boxed_primitives.md +++ b/src/boxed_primitives/arrays_of_boxed_primitives.md @@ -13,5 +13,31 @@ numbersOne = numbersTwo; numbersTwo = numbersOne; ``` +This means that to turn something like a `boolean[]` into a `Boolean[]` or vice-versa, +you must manually make a new array and copy over elements. Doing this in either +direction will work because boxing and unboxing conversions exist between the primitives +and their boxed variants. + +```java +~void main() { +boolean[] yesAndNo = new boolean[] { true, false }; + +Boolean[] yesAndNoCopy = new Boolean[] { false, false }; +for (int i = 0; i < yesAndNo.length; i++) { + // Here a boxing conversion takes place + yesAndNoCopy[i] = yesAndNo[i]; +} + +boolean[] yesAndNoCopyCopy = new boolean[] { false, false }; +for (int i = 0; i < yesAndNoCopy.length; i++) { + // And here an unboxing conversion + yesAndNoCopyCopy[i] = yesAndNoCopy[i]; +} +~} +``` + + + + [^interesting]: The reasons for this are deeply interesting and have to do with the nitty gritty of how Java is actually implemented. It might also change in the future. \ No newline at end of file diff --git a/src/boxed_primitives/boolean.md b/src/boxed_primitives/boolean.md index 2de4fc64..23a878a2 100644 --- a/src/boxed_primitives/boolean.md +++ b/src/boxed_primitives/boolean.md @@ -6,7 +6,7 @@ The type to use for a `boolean` that might be null is `Boolean`. ```java ~void main() { Boolean b = null; -System.out.println(b); +IO.println(b); b = true; -System.out.println(true); +IO.println(b); ~} \ No newline at end of file diff --git a/src/boxed_primitives/boxing_conversion.md b/src/boxed_primitives/boxing_conversion.md index 13e24494..6e1f7a30 100644 --- a/src/boxed_primitives/boxing_conversion.md +++ b/src/boxed_primitives/boxing_conversion.md @@ -9,8 +9,8 @@ automatically do that conversion.[^obvious] int x = 5; Integer y = x; -System.out.println(x); -System.out.println(y); +IO.println(x); +IO.println(y); ~} ``` diff --git a/src/boxed_primitives/challenges.md b/src/boxed_primitives/challenges.md index 9358534c..53908acf 100644 --- a/src/boxed_primitives/challenges.md +++ b/src/boxed_primitives/challenges.md @@ -1 +1,137 @@ # Challenges + + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + + +## Challenge 1. + +Will this code run? Why or why not. + +```java +int compute(int x) { + if (x == 0) { + return null; + } + else { + return x * x; + } +} + +void main() { + IO.println(compute(5)); +} +``` + +## Challenge 2. + +Write a method which takes in an `Integer[]` representing +a series of distances and prints out every distance +followed by ` kilometers`. + +So if the array has `1`, `2`, and `3` you should output + +``` +1 kilometers +2 kilometers +3 kilometers +``` + +If this method is given `null`, it should act as if it +was given an empty array. + + +```java,editable +void printDistances(Integer[] distances) { + +} + +void main() { + printDistances(new Integer[] { + 45, + 99, + 23 + }); +} +``` + +## Challenge 3. + +Write a method called `onlyPositive` which takes in an `int` and returns +the same value out if the number is greater than zero. + +If the number is less than or equal to zero, return `null`. + +```java,editable +// Write onlyPositive here + +void main() { + // 45 + IO.println( + onlyPositive(45) + ); + + // 46 + IO.println( + onlyPositive(45) + 1 + ); + + // null + IO.println( + onlyPositive(0) + ); + + // null + IO.println( + onlyPositive(-1) + ); +} +``` + +## Challenge 4. + +Will the following code work? Why or why not? + +```java +void main() { + int ducks = 5; + Integer sparrows = 3; + + int birds = ducks + sparrows; + + IO.println(birds); +} +``` + +## Challenge 4. + +Will the following code work? Why or why not? + +```java +void main() { + char[] face = new char[] { ':', ')' }; + Character[] smile = face; + + IO.println(smile); +} +``` + +## Challenge 5. + +Will the following code work? Why or why not? + +```java +void main() { + char[] face = new char[] { ':', ')' }; + + Character[] smile = new Character[face.length]; + for (int i = 0; i < face.length; i++) { + smile[i] = face[i]; + } + + IO.println(smile); +} +``` \ No newline at end of file diff --git a/src/boxed_primitives/character.md b/src/boxed_primitives/character.md index 7ec17ce8..1c4dbe40 100644 --- a/src/boxed_primitives/character.md +++ b/src/boxed_primitives/character.md @@ -5,7 +5,8 @@ The type to use for a `char` that might be null is `Character`. ```java ~void main() { Character c = null; -System.out.println(c); +IO.println(c); c = '%'; -System.out.println(c); -~} \ No newline at end of file +IO.println(c); +~} +``` diff --git a/src/boxed_primitives/double.md b/src/boxed_primitives/double.md index 6655c41d..7cc1e6dd 100644 --- a/src/boxed_primitives/double.md +++ b/src/boxed_primitives/double.md @@ -5,7 +5,18 @@ The type to use for a `double` that might be null is `Double`. ```java ~void main() { Double d = null; -System.out.println(d); +IO.println(d); d = 3.14; -System.out.println(d); -~} \ No newline at end of file +IO.println(d); +~} +``` + +If you try to do any math on a `Double` which holds `null` you will +get a `NullPointerException`. + +```java,panics +~void main() { +Double d = null; +IO.println(d + 1); +~} +``` \ No newline at end of file diff --git a/src/boxed_primitives/header.png b/src/boxed_primitives/header.png new file mode 100644 index 00000000..82799080 Binary files /dev/null and b/src/boxed_primitives/header.png differ diff --git a/src/boxed_primitives/integer.md b/src/boxed_primitives/integer.md index 95f8cf04..aa1de817 100644 --- a/src/boxed_primitives/integer.md +++ b/src/boxed_primitives/integer.md @@ -5,8 +5,18 @@ The type to use for an `int` that might be null is `Integer`. ```java ~void main() { Integer i = null; -System.out.println(i); +IO.println(i); i = 5; -System.out.println(i); +IO.println(i); +~} +``` + +If you try to do any math on an `Integer` which holds `null` you will +get a `NullPointerException`. + +```java,panics +~void main() { +Integer i = null; +IO.println(i * 5); ~} ``` \ No newline at end of file diff --git a/src/boxed_primitives/unboxing_conversion.md b/src/boxed_primitives/unboxing_conversion.md index c0005f9e..6a066433 100644 --- a/src/boxed_primitives/unboxing_conversion.md +++ b/src/boxed_primitives/unboxing_conversion.md @@ -11,7 +11,7 @@ Integer x = 5; int y = 3; int z = x * y; -System.out.println(z); +IO.println(z); ~} ``` @@ -21,7 +21,7 @@ As well as `Boolean`s in logical expressions. ~void main() { Boolean hasHat = true; if (hasHat) { - System.out.println("You have a hat!"); + IO.println("You have a hat!"); } ~} ``` diff --git a/src/branching_logic/boolean_expressions.md b/src/branching_logic/boolean_expressions.md index be6542ff..754584d9 100644 --- a/src/branching_logic/boolean_expressions.md +++ b/src/branching_logic/boolean_expressions.md @@ -18,7 +18,7 @@ else { // or // boolean canRent = age > 25 ? true : false; -System.out.println(canRent); +IO.println(canRent); ~} ``` @@ -30,6 +30,6 @@ itself already evaluates to a `boolean`. You can directly assign the variable to int age = 22; boolean canRent = age > 25; -System.out.println(canRent); +IO.println(canRent); ~} ``` diff --git a/src/branching_logic/challenges.md b/src/branching_logic/challenges.md index aa102ec8..452b2fac 100644 --- a/src/branching_logic/challenges.md +++ b/src/branching_logic/challenges.md @@ -41,8 +41,8 @@ void main() { ## Challenge 4 -Write code that will assign the string `The number is {x} even` to `message` if `x` is an even number -and `The number is {x} odd` if `x` is an even number. +Write code that will assign the string `The number {x} is even` to `message` if `x` is an even number +and `The number {x} is odd` if `x` is an odd number. So if `x` is 12 the string you should assign `The number 12 is even` to `message`. @@ -57,6 +57,6 @@ void main() { // < YOUR CODE HERE > - System.out.println(message); + IO.println(message); } ``` diff --git a/src/branching_logic/conditional_operator.md b/src/branching_logic/conditional_operator.md index de895a00..91c2b284 100644 --- a/src/branching_logic/conditional_operator.md +++ b/src/branching_logic/conditional_operator.md @@ -12,7 +12,7 @@ String message = age < 25 ? "You cannot rent a car!" : "You might be able to rent a car"; -System.out.println(message); +IO.println(message); ~} ``` diff --git a/src/branching_logic/else.md b/src/branching_logic/else.md index 940fa271..33cef235 100644 --- a/src/branching_logic/else.md +++ b/src/branching_logic/else.md @@ -7,10 +7,10 @@ and another when that same condition evaluates to `false` you can use `else`. ~void main() { int age = 30; // πŸ™Žβ€β™€οΈ if (age < 25) { - System.out.println("You cannot rent a car!"); + IO.println("You cannot rent a car!"); } else { - System.out.println("You might be able to rent a car."); + IO.println("You might be able to rent a car."); } ~} ``` @@ -34,7 +34,7 @@ When the condition evaluates to `false`, the code inside of `else`'s `{` and `}` ```java,does_not_compile ~void main() { else { - System.out.println("No if."); + IO.println("No if."); } ~} ``` diff --git a/src/branching_logic/else_if.md b/src/branching_logic/else_if.md index 1c0a04db..db6ae231 100644 --- a/src/branching_logic/else_if.md +++ b/src/branching_logic/else_if.md @@ -8,14 +8,14 @@ If you have an `if` nested in an `else` branch, you can simplify that by using boolean cool = true; // πŸ•ΆοΈ int age = 30; // πŸ™Žβ€β™€οΈ if (age < 25) { - System.out.println("You cannot rent a car!"); + IO.println("You cannot rent a car!"); } else { if (!cool) { - System.out.println("You failed the vibe check."); + IO.println("You failed the vibe check."); } else { - System.out.println("You are rad enough to rent a car."); + IO.println("You are rad enough to rent a car."); } } ~} @@ -29,13 +29,13 @@ boolean cool = true; // πŸ•ΆοΈ int age = 30; // πŸ™Žβ€β™€οΈ if (age < 25) { - System.out.println("You cannot rent a car!"); + IO.println("You cannot rent a car!"); } else if (!cool) { - System.out.println("You failed the vibe check."); + IO.println("You failed the vibe check."); } else { - System.out.println("You are rad enough to rent a car."); + IO.println("You are rad enough to rent a car."); } ~} ``` @@ -49,19 +49,19 @@ boolean cool = true; // πŸ•ΆοΈ int age = 100; // πŸ‘΄ if (age < 25) { - System.out.println("You cannot rent a car!"); + IO.println("You cannot rent a car!"); } else if (!cool) { - System.out.println("You failed the vibe check."); + IO.println("You failed the vibe check."); } else if (age > 99) { - System.out.println("You are too old to safely drive a car!"); + IO.println("You are too old to safely drive a car!"); } else if (age > 450) { - System.out.println("There can only be one! βš”οΈπŸ΄σ §σ ’σ ³σ £σ ΄σ Ώ"); + IO.println("There can only be one! βš”οΈπŸ΄σ §σ ’σ ³σ £σ ΄σ Ώ"); } else { - System.out.println("You are rad enough to rent a car."); + IO.println("You are rad enough to rent a car."); } ~} ``` diff --git a/src/branching_logic/header.png b/src/branching_logic/header.png new file mode 100644 index 00000000..d82370eb Binary files /dev/null and b/src/branching_logic/header.png differ diff --git a/src/branching_logic/if.md b/src/branching_logic/if.md index 93e1a5db..3ec3a743 100644 --- a/src/branching_logic/if.md +++ b/src/branching_logic/if.md @@ -6,7 +6,7 @@ The way to represent a branching path in Java is by using an `if` statement. ~void main() { int age = 5; // πŸ‘Ά if (age < 25) { - System.out.println("You are too young to rent a car!"); + IO.println("You are too young to rent a car!"); } ~} ``` @@ -31,7 +31,7 @@ and you will be told that you cannot rent a car. ~void main() { int age = 80; // πŸ‘΅ if (age < 25) { - System.out.println("You are too young to rent a car!"); + IO.println("You are too young to rent a car!"); } ~} ``` diff --git a/src/branching_logic/nested_ifs.md b/src/branching_logic/nested_ifs.md index a4e04d24..ee366796 100644 --- a/src/branching_logic/nested_ifs.md +++ b/src/branching_logic/nested_ifs.md @@ -6,9 +6,9 @@ The code inside of the `{` and `}` can be anything, including more `if` statment ~void main() { int age = 5; // πŸ‘Ά if (age < 25) { - System.out.println("You are too young to rent a car!"); + IO.println("You are too young to rent a car!"); if (age == 24) { - System.out.println("(but it was close)"); + IO.println("(but it was close)"); } } ~} @@ -17,7 +17,7 @@ if (age < 25) { When an `if` is inside another `if` we say that it is "nested". If you find yourself nesting more than a few `if`s that might be a sign that -you should reach out for help. +you should reach out for help[^or]. ```java,no_run if (...) { @@ -30,3 +30,5 @@ if (...) { } } ``` + +[^or]: Or a sign that you should keep reading. There are things I will show you that can help you avoid this - like "methods." diff --git a/src/branching_logic/relation_to_delayed_assignment.md b/src/branching_logic/relation_to_delayed_assignment.md index 570d54b5..c9106d37 100644 --- a/src/branching_logic/relation_to_delayed_assignment.md +++ b/src/branching_logic/relation_to_delayed_assignment.md @@ -17,7 +17,7 @@ else { message = "You cannot rent a car!"; } -System.out.println(message); +IO.println(message); ~} ``` @@ -35,6 +35,6 @@ if (age > 25) { // message is not always given an initial value // so you cannot use it. -System.out.println(message); +IO.println(message); ~} ``` diff --git a/src/branching_logic/scoped_variables.md b/src/branching_logic/scoped_variables.md index a9125802..096443fd 100644 --- a/src/branching_logic/scoped_variables.md +++ b/src/branching_logic/scoped_variables.md @@ -9,12 +9,12 @@ int age = 5; if (age == 5) { int nextAge = age + 1; - System.out.println(nextAge); + IO.println(nextAge); } // If you uncomment this line, there will be an issue // `nextAge` is not available to the scope outside of the `if` -// System.out.println(nextAge); +// IO.println(nextAge); ~} ``` @@ -34,7 +34,7 @@ else { // This will not work, because although `message` is declared // in all branches, it is not declared in the "outer scope" -System.out.println(message); +IO.println(message); ~} ``` diff --git a/src/branching_paths.md b/src/branching_paths.md index d7603bea..77b33ea0 100644 --- a/src/branching_paths.md +++ b/src/branching_paths.md @@ -1,5 +1,7 @@ # Branching Paths + + All the code I have shown you so far has run from top to bottom. That is, it has followed a single "path." Not all programs can follow a single path though. diff --git a/src/characters.md b/src/characters.md index 398e3d86..bbf465da 100644 --- a/src/characters.md +++ b/src/characters.md @@ -1,5 +1,7 @@ # Characters + + A character, represented by the data type `char`, is a single letter or symbol. diff --git a/src/characters/challenges.md b/src/characters/challenges.md index 3f207994..94544fc1 100644 --- a/src/characters/challenges.md +++ b/src/characters/challenges.md @@ -15,7 +15,7 @@ Try to work it out on paper before running the program below. void main() { char x = 'x'; - System.out.println(x * x); + IO.println(x * x); } ``` @@ -31,7 +31,7 @@ void main() { boolean isLetter = ???; - System.out.println(isLetter); + IO.println(isLetter); } ``` diff --git a/src/characters/conversion_from_integers.md b/src/characters/conversion_from_integers.md index 7ae90563..76211fb1 100644 --- a/src/characters/conversion_from_integers.md +++ b/src/characters/conversion_from_integers.md @@ -9,7 +9,7 @@ int x = 120; char xAsChar = (char) x; -System.out.println(xAsChar); +IO.println(xAsChar); ~} ``` @@ -21,6 +21,6 @@ The initial value of a `char` can also be given by an integer literal if the int ~void main() { char z = 122; -System.out.println(z); +IO.println(z); ~} ``` diff --git a/src/characters/conversion_to_integers.md b/src/characters/conversion_to_integers.md index d3dccb6e..1e2d8f17 100644 --- a/src/characters/conversion_to_integers.md +++ b/src/characters/conversion_to_integers.md @@ -1,6 +1,6 @@ # Conversion to Integers -All `char`s have a matching numeric value. `'a'` is `97`, `'b'` is `98`, +All `char`s have a matching numeric value[^ascii]. `'a'` is `97`, `'b'` is `98`, `'&'` is `38`, and so on. Same as assigning an `int` to a `double`, you can perform a widening conversion @@ -10,7 +10,7 @@ by attempting to assign a `char` to an `int`. ~void main() { int valueOfA = 'a'; -System.out.println(valueOfA); +IO.println(valueOfA); ~} ``` @@ -23,11 +23,13 @@ char gee = 'g'; // all the letters from a to z have consecutive numeric values. boolean isLetter = gee >= 'a' && gee <= 'z'; -System.out.println(isLetter); +IO.println(isLetter); ~} ``` This can be useful if you are stranded on Mars[^onmars] or if you want to see if a character is in some range. -[^onmars]: https://www.youtube.com/watch?v=k-GH3mbvUro +[^ascii]: You can find some of these values in an "[ASCII Table](https://www.ascii-code.com/)." + +[^onmars]: [https://www.youtube.com/watch?v=0xkP_FQUsuM](https://www.youtube.com/watch?v=0xkP_FQUsuM) diff --git a/src/characters/header.png b/src/characters/header.png new file mode 100644 index 00000000..bbd23621 Binary files /dev/null and b/src/characters/header.png differ diff --git a/src/class_extension.md b/src/class_extension.md new file mode 100644 index 00000000..7199be5d --- /dev/null +++ b/src/class_extension.md @@ -0,0 +1,11 @@ +# Class Extension + + + +Just like interfaces can extend other interfaces, classes can extend other classes. + +```java +class BodyOfWater {} + +class Ocean extends BodyOfWater {} +``` \ No newline at end of file diff --git a/src/class_extension/abstract_classes.md b/src/class_extension/abstract_classes.md new file mode 100644 index 00000000..3a39f4f4 --- /dev/null +++ b/src/class_extension/abstract_classes.md @@ -0,0 +1,50 @@ +# Abstract Classes + +Abstract classes are classes which you cannot make an instance of directly.[^concrete] + +```java,does_not_compile +abstract class Plant {} + +void main() { + // You cannot make an instance because + // it is an abstract class. + var p = new Plant(); +} +``` + +The use-case for these is making classes which are designed to be subclassed. +An example of this that comes with Java is `AbstractList`. + +`AbstractList` defines most of the methods required by the `List` interface and is intended to +lower the effort required to make a custom `List` implementation. + +```java +import java.util.AbstractList; + +// This subclass is a List containing the numbers 5 and 7 +class FiveAndSeven + // Almost all of the implementation is inherited from AbstractList + extends AbstractList { + + @Override + public Integer get(int index) { + return switch (index) { + case 0 -> 5; + case 1 -> 7; + default -> throw new IndexOutOfBoundsException(); + }; + } + + @Override + public int size() { + return 2; + } +} + +void main() { + var l = new FiveAndSeven(); + IO.println(l); +} +``` + +[^concrete]: "Abstract" as a term here means something close to "not in reality." You will hear people refer to non-abstract classes as "concrete" classes. \ No newline at end of file diff --git a/src/class_extension/abstract_methods.md b/src/class_extension/abstract_methods.md new file mode 100644 index 00000000..991c02a2 --- /dev/null +++ b/src/class_extension/abstract_methods.md @@ -0,0 +1,74 @@ +# Abstract Methods + +Methods on an `abstract` class may themselves be marked `abstract`. + +```java +abstract class BodyOfWater { + long depth; + + BodyOfWater(long depth) { + if (this.depth < 0) { + throw new IllegalArgumentException(); + } + this.depth = depth; + } + + // All classes which extend BodyOfWater must + // define what happens when you swim. + abstract void swim(); +} +``` + +Any non-abstract class extending from an `abstract` class must give a definition +for `abstract` methods. + +```java +abstract class BodyOfWater { + long depth; + + BodyOfWater(long depth) { + if (this.depth < 0) { + throw new IllegalArgumentException(); + } + this.depth = depth; + } + + abstract void swim(); +} + +class Lake extends BodyOfWater { + // If you didn't define this it wouldn't work + @Override + void swim() { + IO.println("Relaxing time"); + } +} +``` + +But if an abstract class is being extended by another abstract class, you don't need to. + +```java,no_run +abstract class Liquid { + abstract double viscosity(); +} + +// Water doesn't need to define volume() +// because Water is abstract +abstract class Water extends Liquid { + abstract double purity(); +} + +// But as soon as you get to a "concrete" implementation +// you need to provide definitions +class TownWater extends Water { + @Override + double viscosity() { + return 0.01; + } + + @Override + double purity() { + return 0.99; + } +} +``` diff --git a/src/class_extension/challenges.md b/src/class_extension/challenges.md new file mode 100644 index 00000000..bc07c920 --- /dev/null +++ b/src/class_extension/challenges.md @@ -0,0 +1,128 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Write a class called `Tree` which extends `ArrayList`. + +Add a `.grow()` method which adds two apples to itself +of arbitrary size. + +```java,editable +record Apple(double size) {} + +// CODE HERE + +class Main { + Tree tree = new Tree(); + tree.grow(); + tree.grow(); + + // You should have inherited the toString + // from ArrayList + IO.println(tree); + + // As well as all the other methods + tree.add(new Apple(100)); + IO.println(tree); + + for (var apple : tree) { + IO.println(apple); + } +} +``` + +## Challenge 2. + +Rewrite your `Tree` class from above to instead extend `AbstractList`. +You can find the documentation for `AbstractList` [here](https://docs.oracle.com/javase/25/docs/api/java/util/AbstractList.html). + +## Challenge 3. + +Make an `abstract` class called `Fish`. It should provide a method named +`swim` and a method named `howLongSwim` that returns how many times `swim` +was called. + +This +```java +// CODE HERE + +class Dory extends Fish { + void justKeepSwimming() { + IO.println("Just keep swimming"); + this.swim(); + + IO.println("Just keep swimming"); + this.swim(); + + IO.println("Just keep swimming"); + this.swim(); + + IO.println("swimming"); + this.swim(); + + IO.println("swimming"); + this.swim(); + } +} + +class Main { + void main() { + var dory = new Dory(); + dory.justKeepSwimming(); + + IO.println(dory.howLongSwim()); + } +} +``` + +## Challenge 4. + +Make the field you use to track how many times +the fish swam `protected`. Also make the `swim` +method `protected` and `abstract`. + +Write comments in your code such that the "contract" +between your class and classes implementing it +is that they must keep the `swam` field up to date. + +This +```java +// CODE HERE + +class Dory extends Fish { + + + @Override + void swim() { + IO.println("Just keep swimming"); + this.swam++; + + IO.println("Just keep swimming"); + this.swam++; + + IO.println("Just keep swimming"); + this.swam++; + + IO.println("swimming"); + this.swam++; + + IO.println("swimming"); + this.swam++; + } +} + +class Main { + void main() { + var dory = new Dory(); + dory.swim(); + + IO.println(dory.howLongSwim()); + } +} +``` + diff --git a/src/class_extension/extend_a_class.md b/src/class_extension/extend_a_class.md new file mode 100644 index 00000000..ea5c39ba --- /dev/null +++ b/src/class_extension/extend_a_class.md @@ -0,0 +1,47 @@ +# Extend a Class + +For a class to extend another one you need to write `extends` and then the name of the class +which is being extended. + +```java +class BodyOfWater {} + +class Ocean extends BodyOfWater {} +``` + +If the class being extended has non-zero argument constructors, +the subclass must pass arguments to those constructors in it's constructor +using `super()`. + +```java +class BodyOfWater { + long depth; + + BodyOfWater(long depth) { + if (this.depth < 0) { + throw new IllegalArgumentException(); + } + this.depth = depth; + } +} + +class Ocean extends BodyOfWater { + String name; + + Ocean(String name, long depth) { + this.name = name; + // Before you exit, you must pass + // arguments to the super-class constructor. + super(depth); + } +} + +void main() { + Ocean pacific = new Ocean("Pacific", 36201L); + IO.println( + "The " + pacific.name + + " ocean is " + pacific.depth + + "' deep." + ); +} +``` \ No newline at end of file diff --git a/src/class_extension/final_classes.md b/src/class_extension/final_classes.md new file mode 100644 index 00000000..6b378c1c --- /dev/null +++ b/src/class_extension/final_classes.md @@ -0,0 +1,42 @@ +# Final Classes + +If a class is marked `final` it cannot be extended. + +```java,does_not_compile +final class Apple {} + +// Cannot extend a final class +class RedApple extends Apple {} +~void main() {} +``` + +Because thinking about what the implications are if someone extends a class is hard, +it is reasonable to always declare your classes as `final` unless you have some reason not to. + +```java +// It's not that something bad would happen if someone +// extended this class. It's that not having to think +// about it is useful. +final class Saltine { + int saltiness; + + Saltine(int saltiness) { + this.saltiness = saltiness; + } +} +``` + +Records and Enums are always implicitly final. + +```java,does_not_compile +record Pos(int x, int y) {} + +// Records are final +class Pos2 extends Pos {} + +enum StopLight { RED, YELLOW, GREEN } + +// Enums are final +class StopLight2 extends StopLight {} +~class Main {void main() {}} +``` \ No newline at end of file diff --git a/src/class_extension/header.png b/src/class_extension/header.png new file mode 100644 index 00000000..5ee2ce3e Binary files /dev/null and b/src/class_extension/header.png differ diff --git a/src/class_extension/inheritance.md b/src/class_extension/inheritance.md new file mode 100644 index 00000000..ce87ee5c --- /dev/null +++ b/src/class_extension/inheritance.md @@ -0,0 +1,43 @@ +# Inheritance + +When one class extends another we say that it is "inheriting" +from the superclass. + +What this means is that all the fields and methods of the class being extended +carry over into the extending class. + +```java +class BodyOfWater { + long depth; + + BodyOfWater(long depth) { + if (this.depth < 0) { + throw new IllegalArgumentException(); + } + this.depth = depth; + } + + void swim() { + if (depth > 50) { + IO.println("You have drowned"); + } + else { + IO.println("You are fine"); + } + } +} + +class Ocean extends BodyOfWater { + String name; + + Ocean(String name, long depth) { + this.name = name; + super(depth); + } +} + +void main() { + BodyOfWater pacific = new Ocean("Pacific", 36201L); + pacific.swim(); // The swim method is "inherited" +} +``` \ No newline at end of file diff --git a/src/class_extension/override.md b/src/class_extension/override.md new file mode 100644 index 00000000..18d5768a --- /dev/null +++ b/src/class_extension/override.md @@ -0,0 +1,188 @@ +# Override + +Just as you can provide an alternative implementation for a default method +in an interface, you can override a method inherited from a superclass. + +```java +class BodyOfWater { + long depth; + + BodyOfWater(long depth) { + if (this.depth < 0) { + throw new IllegalArgumentException(); + } + this.depth = depth; + } + + void swim() { + if (depth > 50) { + IO.println("You have drowned"); + } + else { + IO.println("You are fine"); + } + } +} + +class Ocean extends BodyOfWater { + String name; + + Ocean(String name, long depth) { + this.name = name; + super(depth); + } + + @Override + void swim() { + IO.println("You have been eaten by a shark"); + } +} + +void main() { + BodyOfWater pacific = new Ocean("Pacific", 36201L); + pacific.swim(); +} +``` + +This can be prevented by marking a method as `final`. This explicitly disallows +overriding it in a subclass. + +```java,does_not_compile +class BodyOfWater { + long depth; + + BodyOfWater(long depth) { + if (this.depth < 0) { + throw new IllegalArgumentException(); + } + this.depth = depth; + } + + // Cannot be overriden + final void swim() { + if (depth > 50) { + IO.println("You have drowned"); + } + else { + IO.println("You are fine"); + } + } +} + +class Ocean extends BodyOfWater { + String name; + + Ocean(String name, long depth) { + this.name = name; + super(depth); + } + + @Override + void swim() { // We cannot override a final method + IO.println("You have been eaten by a shark"); + } +} + +void main() { + BodyOfWater pacific = new Ocean("Pacific", 36201L); + pacific.swim(); +} +``` + +Another way to prevent this is to make it so the method is not visible to the subclass. +This can be done either by marking the method `private` or by having it be package-private +and simply in a different package. + +```java,does_not_compile +class BodyOfWater { + long depth; + + BodyOfWater(long depth) { + if (this.depth < 0) { + throw new IllegalArgumentException(); + } + this.depth = depth; + } + + // Cannot be override what you cannot see + private void swim() { + if (depth > 50) { + IO.println("You have drowned"); + } + else { + IO.println("You are fine"); + } + } +} + +class Ocean extends BodyOfWater { + String name; + + Ocean(String name, long depth) { + this.name = name; + super(depth); + } + + @Override + void swim() { // Cannot be override what you cannot see + IO.println("You have been eaten by a shark"); + } +} + +void main() { + BodyOfWater pacific = new Ocean("Pacific", 36201L); + pacific.swim(); +} +``` + +If you want to use the definition of the method you are overriding you can access it by writing `super.` +before the name of the method. + +```java +class BodyOfWater { + long depth; + + BodyOfWater(long depth) { + if (this.depth < 0) { + throw new IllegalArgumentException(); + } + this.depth = depth; + } + + void swim() { + if (depth > 50) { + IO.println("You have drowned"); + } + else { + IO.println("You are fine"); + } + } +} + +class Ocean extends BodyOfWater { + String name; + + Ocean(String name, long depth) { + this.name = name; + super(depth); + } + + @Override + void swim() { + if ("Pacific".equals(name)) { + IO.println("You have been eaten by a shark"); + } + else { + // Calls the definition on BodyOfWater + super.swim(); + } + } +} + +void main() { + BodyOfWater pacific = new Ocean("Pacific", 36201L); + pacific.swim(); + BodyOfWater atlantic = new Ocean("Atlantic", 28232L); + atlantic.swim(); +} +``` \ No newline at end of file diff --git a/src/class_extension/protected.md b/src/class_extension/protected.md new file mode 100644 index 00000000..9bca086a --- /dev/null +++ b/src/class_extension/protected.md @@ -0,0 +1,44 @@ +# Protected + +If you want code to be accessible by a subclass defined in another package +but not be fully `public`, that is where the `protected` modifier is useful. + +`protected` fields, methods, and constructors have the same visibility as package-private ones. +This means classes in the same package can see them but classes in other packages cannot. +The only addition is that it is visible to extending classes regardless of package. + +```java +package oceanography; + +class BodyOfWater { + // Both the depth field and the following + // constructor are only visible to subclasses + // and classes in the "oceanography" package. + protected long depth; + + protected BodyOfWater(long depth) { + if (this.depth < 0) { + throw new IllegalArgumentException(); + } + this.depth = depth; + } +} +``` + +```java +package bigwater; + +class Ocean extends BodyOfWater { + String name; + + Ocean(String name, long depth) { + this.name = name; + // The super(depth) constructor is only usable because it is + // visible. + super(depth); + } +} +``` + +This is useful for providing methods that you really only expect to be useful for +producing subclasses but that you don't want to be available to everyone. \ No newline at end of file diff --git a/src/class_extension/relation_to_encapsulation.md b/src/class_extension/relation_to_encapsulation.md new file mode 100644 index 00000000..36f27865 --- /dev/null +++ b/src/class_extension/relation_to_encapsulation.md @@ -0,0 +1,142 @@ +# Relation to Encapsulation + +It takes deliberate effort to properly encapsulate implementation details +in the presence of inheritance. + +Say there was a class which tracked passengers who booked flights. + +```java +class Airplane { + private final List passengers = new ArrayList<>(); + + void bookOne(String passenger) { + this.passengers.add(passenger); + } + + void bookMany(List passengers) { + for (var passenger : passengers) { + bookOne(passenger); + } + } +} +``` + +A reasonable extension to this class would be to print a message every time someone books a flight.[^well] + +```java +class Airplane { + private final List passengers = new ArrayList<>(); + + void bookOne(String passenger) { + this.passengers.add(passenger); + } + + void bookMany(List passengers) { + for (var passenger : passengers) { + bookOne(passenger); + } + } +} + +class PrintingAirplane extends Airplane { + @Override + void bookOne(String passenger) { + IO.println(passenger); + super.bookOne(passenger); + } +} + +class Main { + void main() { + var airplane = new PrintingAirplane(); + airplane.bookOne("Ned Ludd"); + airplane.bookMany(List.of("Robin Hood", "Friar Tuck")); + } +} +``` + +But a subtle problem with this is that in Airplane `bookMany` is defined in terms of `bookOne`. +So in our subclass when we override `bookOne` we get the behavior we want. Every time someone is booked +we print their name. + +But a small innocuous change in `Airplane` could break this. If `bookMany` was redefined to not +use `bookOne` then you would miss names. + +```java +class Airplane { + private final List passengers = new ArrayList<>(); + + void bookOne(String passenger) { + this.passengers.add(passenger); + } + + void bookMany(List passengers) { + for (var passenger : passengers) { + // Only change + this.passengers.add(passenger); + } + } +} +``` + +A reasonable extension to this class would be to print a message every time someone books a flight.[^well] + +```java +class Airplane { + private final List passengers = new ArrayList<>(); + + void bookOne(String passenger) { + this.passengers.add(passenger); + } + + void bookMany(List passengers) { + for (var passenger : passengers) { + // Only change + this.passengers.add(passenger); + } + } +} + +class PrintingAirplane extends Airplane { + @Override + void bookOne(String passenger) { + IO.println(passenger); + super.bookOne(passenger); + } +} + +class Main { + void main() { + var airplane = new PrintingAirplane(); + airplane.bookOne("Ned Ludd"); + airplane.bookMany(List.of("Robin Hood", "Friar Tuck")); + } +} +``` + +This can be adjusted for by overriding both `bookOne` and `bookMany` in `PrintingAirplane`, but you are equally vulnerable to the opposite change. + +```java +class PrintingAirplane extends Airplane { + @Override + void bookOne(String passenger) { + IO.println(passenger); + super.bookOne(passenger); + } + + @Override + void bookMany(List passengers) { + for (var passenger : passengers) { + IO.println(passenger); + } + super.bookMany(passengers); + } +} +``` + +As a general rule, changing a class which may have been extended by other classes requires more effort than if doing so were not an option. This is because in a few ways "inheritance breaks encapsulation." +At least in the sense of there being an interplay between mechanics you wouldn't otherwise have to consider. + +If you don't want to deal with these issues, you can disallow extending a class. + +[^well]: Well, reasonable in the world of contrived programming examples. \ No newline at end of file diff --git a/src/class_extension/relation_to_interfaces.md b/src/class_extension/relation_to_interfaces.md new file mode 100644 index 00000000..00ae02c0 --- /dev/null +++ b/src/class_extension/relation_to_interfaces.md @@ -0,0 +1,83 @@ +# Relation to Interfaces + +When you implement an interface, the implementing class guarentees a contract +and may inherit some default implementations of methods. + +```java +// All implementing classes must define a run method +interface Runner { + void run(); + + default void runFar() { + run(); + run(); + run(); + } +} + +class RoadRunner { + @Override + public void run() { + IO.println("meep meep"); + } +} + +class Main { + void main() { + Runner r = new RoadRunner(); + r.runFar(); + } +} +``` + +This is also true when extending a class. The difference is that you may inherit +fields, behavior dependent on fields, and actual constructors. + +```java +abstract class Swimmer { + private int laps = 0; + + public abstract void swim(); + + // The inherited behavior may depend on fields + public void swimFar() { + IO.println("lap: " + laps); + swim(); + laps++; + + IO.println("lap: " + laps); + swim(); + laps++; + + IO.println("lap: " + laps); + swim(); + laps++; + } +} + +class Person extends Swimmer { + @Override + public void swim() { + IO.println("*gasps for air*"); + } +} + +class Main { + void main() { + Swimmer s = new Person(); + s.swimFar(); + } +} +``` + +Because of these differences while it is possible to implement many interfaces, a +class can only extend from exactly one other class.[^somelangs] + +Because of these differences, the general consensus is that interfaces are the mechanism to use +for abstracting behavior. Abstract classes, by contrast, should be used primarily to reduce duplicated code.[^tricky] + +[^somelangs]: Some languages do allow for "multiple inheritance." Doing so leads to some wacky and hard to think about things so Java doesn't allow it. + +[^tricky]: It's a little tricky because both interfaces and abstract classes can be used for similar things. A "pure abstract class" - a class with nothing but a bunch of abstract methods - is very close in +concept to an interface. If its all still confusing to you then just ignore everything but interfaces. + diff --git a/src/classes.md b/src/classes.md index 94c7d403..8f8bad2b 100644 --- a/src/classes.md +++ b/src/classes.md @@ -1,5 +1,8 @@ # Classes + + + Up until now all the data types you have used - `int`, `String`, etc. - came with Java. This works for awhile, but eventually you will need to define your own types. diff --git a/src/classes/aliasing.md b/src/classes/aliasing.md index 56195696..75baf720 100644 --- a/src/classes/aliasing.md +++ b/src/classes/aliasing.md @@ -19,8 +19,8 @@ void main() { kermit.name = "Kermit The Frog"; // Kermit The Frog - System.out.println(kermit.name); + IO.println(kermit.name); // Kermit The Frog - System.out.println(darkKermit.name); + IO.println(darkKermit.name); } ``` \ No newline at end of file diff --git a/src/classes/challenges.md b/src/classes/challenges.md new file mode 100644 index 00000000..61e46417 --- /dev/null +++ b/src/classes/challenges.md @@ -0,0 +1,150 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +The following classes are named "wrong." Name them correctly. + +```java,editable +class gonzo {} + +class fozzie_the_bear {} + +class MSPIGGY {} + +void main() { + IO.println(new gonzo()); + IO.println(new fozzie_the_bear()); + IO.println(new MSPIGGY()); +} +``` + +## Challenge 2. + +Make a variable named `movie` which is an instance of the `Movie` class. +Set the value of its `title` field to `Muppets in Space`. + +```java,editable +class Movie { + String title; +} + +void main() { + // --------------- + // CODE HERE + // --------------- + + IO.println( + movie.title + ); +} +``` + +## Challenge 3. + +Alter the `ThemePark` class so that the default value +for its `entranceFee` field is `35.24`. + +```java,editable +class ThemePark { + double entranceFee; +} + +void main() { + ThemePark themePark = new ThemePark(); + + IO.println( + themePark.entranceFee + ); +} +``` + +## Challenge 4. + +Changing only the indicated line, +make it so the word `Kermit` only appears twice in the +program. + +Hint: Remember that inferred types exist. + +```java,editable +class Kermit { + boolean angry = true; +} + +void main() { + // ------------------------ + // CHANGE ONLY THIS LINE v + Kermit kermit = new Kermit(); + // ------------------------ + + IO.println(kermit.angry); +} +``` + +## Challenge 5. + +Make a method named `squareRoot` which returns an +instance of the `SquareRoot` class containing both +the `positiveRoot` and `negativeRoot` of the given `double`. + +So if you are given `4` you should return a positive root +of `2` and a negative root of `-2`. + +You do not have to account for the possibility of being given a negative +number. You should use `Math.sqrt` to find the positive root and common +sense to find the negative root. + +```java,editable +class SquareRoot { + double positiveRoot; + double negativeRoot; +} + +SquareRoot squareRoot(double value) { + // ----------- + // CODE HERE + // ----------- +} + +void main() { + SquareRoot sqrtOfFour = squareRoot(4); + // 2 + IO.println(sqrtOfFour.positiveRoot); + // -2 + IO.println(sqrtOfFour.negativeRoot); + + SquareRoot sqrtOfFifteen = squareRoot(15); + // 3.872983346207417 + IO.println(sqrtOfFifteen.positiveRoot); + // -3.872983346207417 + IO.println(sqrtOfFifteen.negativeRoot); +} +``` + +## Challenge 6. + +Only writing code between the lines and without directly accessing any fields on or reassigning the `actor` variable, make the program output `Tim Curry`. + +Hint: The key word is "directly." + +```java,editable +class Actor { + String name; +} + +void main() { + Actor actor = new Actor(); + actor.name = "Frog, Kermit the"; + + // -------------------------- + // CODE HERE + // -------------------------- + + IO.println(actor.name); +} +``` \ No newline at end of file diff --git a/src/classes/default_values.md b/src/classes/default_values.md new file mode 100644 index 00000000..05415326 --- /dev/null +++ b/src/classes/default_values.md @@ -0,0 +1 @@ +# Default Values diff --git a/src/classes/field_access.md b/src/classes/field_access.md index 15706c6d..09865939 100644 --- a/src/classes/field_access.md +++ b/src/classes/field_access.md @@ -1,6 +1,6 @@ # Field Access -You can access the value of any field on a class by writing the name of a variable holding an instance +You can access the value of any field in a class by writing the name of a variable holding an instance of that class, `.`, then the name of that field. ```java @@ -13,6 +13,6 @@ void main() { kermit.name = "Kermit The Frog"; // The .name accesses the "name" field - System.out.println(kermit.name); + IO.println(kermit.name); } ``` diff --git a/src/classes/field_default_values.md b/src/classes/field_default_values.md index c80d269e..ed3f042e 100644 --- a/src/classes/field_default_values.md +++ b/src/classes/field_default_values.md @@ -17,12 +17,12 @@ void main() { Muppet kermit = new Muppet(); // 0 - System.out.println(kermit.age); + IO.println(kermit.age); // 0.0 - System.out.println(kermit.salary); + IO.println(kermit.salary); // false - System.out.println(kermit.talented); + IO.println(kermit.talented); // null - System.out.println(kermit.name); + IO.println(kermit.name); } ``` \ No newline at end of file diff --git a/src/classes/field_initialization.md b/src/classes/field_initialization.md index 0a3461f7..43394880 100644 --- a/src/classes/field_initialization.md +++ b/src/classes/field_initialization.md @@ -2,7 +2,7 @@ You can set an initial value for a field in a few ways. -One is to access to assign the field directly on the instance created. +One is to assign the field directly on the instance created. ```java class Muppet { diff --git a/src/classes/header.png b/src/classes/header.png new file mode 100644 index 00000000..98b4ac00 Binary files /dev/null and b/src/classes/header.png differ diff --git a/src/classes/instances.md b/src/classes/instances.md index 0546560d..6ebcce53 100644 --- a/src/classes/instances.md +++ b/src/classes/instances.md @@ -11,12 +11,12 @@ class Muppet { void main() { Muppet kermit = new Muppet(); - System.out.println(kermit); + IO.println(kermit); } ``` -Very similarly to arrays, the output from printing an instance of a class might seem like gibberish (`Main$Muppet@1be6f5c3`). +Very similar to arrays, the output from printing an instance of a class might seem like gibberish (`Main$Muppet@1be6f5c3`). You will learn how to make it nicer later. [^var]: I haven't used it in many code samples thus far, but if you remember `var` this is one of the times -where it can be aethetically convenient. `var kermit = new Muppet();` +where it can be aesthetically convenient. `var kermit = new Muppet();` diff --git a/src/classes/multiple_instances.md b/src/classes/multiple_instances.md new file mode 100644 index 00000000..174c783a --- /dev/null +++ b/src/classes/multiple_instances.md @@ -0,0 +1,26 @@ +# Multiple Instances + +If you make multiple instances of a class with `new` +those classes will each have their own values for +fields. + +```java +class Muppet { + String name; +} + +void main() { + Muppet kermit = new Muppet(); + kermit.name = "Kermit The Frog"; + + Muppet animal = new Muppet(); + animal.name = "animal"; + + // kermit and animal are distinct muppets + // and thus each have their own name. + IO.println(kermit.name); + IO.println(animal.name); +} +``` + +Each instance has what we would call its own "identity." \ No newline at end of file diff --git a/src/classes/return_multiple_values.md b/src/classes/return_multiple_values.md index a78b9d48..16745174 100644 --- a/src/classes/return_multiple_values.md +++ b/src/classes/return_multiple_values.md @@ -15,18 +15,18 @@ class Location { Location findTreasureIsland() { Location location = new Location(); - location.latitude = 40.2085; - location.longitude = -3.713; + location.latitude = 51.4075; + location.longitude = 0.4636; return location; } void main() { Location treasureIsland = findTreasureIsland(); - System.out.println( + IO.println( "Treasure island is located at " + - location.latitude + + treasureIsland.latitude + " " + - location.longitude + + treasureIsland.longitude + "." ); } diff --git a/src/classes/the_meaning_of_the_word_class.md b/src/classes/the_meaning_of_the_word_class.md index 2e42e04f..45b1252d 100644 --- a/src/classes/the_meaning_of_the_word_class.md +++ b/src/classes/the_meaning_of_the_word_class.md @@ -9,7 +9,7 @@ a `String` is classified by the set of things you can do to it and the data it stores. The `String` class would specify all of that.[^heady] -[^idiot]: What an [idiot](https://www.shardcore.org/spx/2015/05/15/diogenes-and-the-chicken/) +[^idiot]: What an [idiot](https://penelope.uchicago.edu/encyclopaedia_romana/greece/hetairai/diogenes.html) -[^heady]: If thats a bit too heady of an explanation don't fret. You will likely get it intuitively eventually. +[^heady]: If that's a bit too heady of an explanation don't fret. You will likely get it intuitively eventually. There are like 50 different ways to explain it and eventually one will land for you. \ No newline at end of file diff --git a/src/classes/user_defined_types.md b/src/classes/user_defined_types.md deleted file mode 100644 index 10d59774..00000000 --- a/src/classes/user_defined_types.md +++ /dev/null @@ -1,3 +0,0 @@ -# User Defined Types - -Declaring a class diff --git a/src/classes/zero_values.md b/src/classes/zero_values.md deleted file mode 100644 index fd09e2ab..00000000 --- a/src/classes/zero_values.md +++ /dev/null @@ -1 +0,0 @@ -# Zero Values diff --git a/src/code_is_read_more_than_written.md b/src/code_is_read_more_than_written.md new file mode 100644 index 00000000..14fd731a --- /dev/null +++ b/src/code_is_read_more_than_written.md @@ -0,0 +1,9 @@ +# Code is Read more than Written + +Now that you more or less can write a full program, it won't be long +before you write a program of "signficant size." + +This makes it a good time to start drip feeding some of the higher level +concepts around software development. + +The first of these is a statement: Code is read more then it is written. \ No newline at end of file diff --git a/src/code_is_read_more_than_written/audience.md b/src/code_is_read_more_than_written/audience.md new file mode 100644 index 00000000..ffe37473 --- /dev/null +++ b/src/code_is_read_more_than_written/audience.md @@ -0,0 +1,20 @@ +# Audience + +Consider this opening from an astrophysics paper I picked at random.[^arxiv] + +> The dearth of planets with sizes around 1.8 RβŠ• is a key demographic feature discovered by the Kepler mission. Two theories have emerged as potential explanations for this valley: photoevaporation and core-powered mass-loss. However, Rogers et al. (2021) shows that differentiating between the two theories is possible using the three-dimensional parameter space of planet radius, incident flux, and stellar mass. + +If you aren't an astrophysicist, this probably requires some explanation. If you are an astrophysicist, you +probably understood that without issue. + +Whenever anyone writes anything, it is important to consider the audience you are writing to. This applies to code just as much as to any other form of writing. + +This is why shorthands like `for (int i = 0; i < length; i++)` are generally considered okay even though +in most other situations `i` is pretty non-descript. Within the audience of programmers it is a known idiom. + +Its also why explaining every single line of code with comments is generally not okay. Programmers know what code +is, you don't need to baby them through how loops work.[^offense] + +[^arxiv]: https://arxiv.org/abs/2302.00009 + +[^offense]: No offense intended if you are still having trouble with loops. Its common, you will eventually get past it. \ No newline at end of file diff --git a/src/code_is_read_more_than_written/implications.md b/src/code_is_read_more_than_written/implications.md new file mode 100644 index 00000000..0abfb5f1 --- /dev/null +++ b/src/code_is_read_more_than_written/implications.md @@ -0,0 +1,10 @@ +# Implications + +This means that, sometimes, it makes sense to write code in a way that +is "harder" than is absolutely needed. + +That's a bit of a fuzzy statement, but on the more obvious side it means +doing things like spending extra time on code formatting, variable naming, +function contracts, writing comments, etc. + +All of those things make it easier to read that code later on. \ No newline at end of file diff --git a/src/code_is_read_more_than_written/information_density.md b/src/code_is_read_more_than_written/information_density.md new file mode 100644 index 00000000..b93722bd --- /dev/null +++ b/src/code_is_read_more_than_written/information_density.md @@ -0,0 +1,25 @@ +# Information Density + +An important related concept is information density. + +Yes, you can do things like add a comment on every line of code. +This does mean that a later reader will have context on exactly what +you were thinking as you were writing the code.[^literate] + +The downside is that there can be way more "extra" information than any +educated person needs to understand the code. That extra information takes up space +and spreads out the "important" information. + +Put another way, if something has a high information density it can be hard to read. + +> M.M.A.T.B.C + +Same as if it is has too low an information density. + +> Dearest sir, it would behoove you to meetest mineself posthaste after the school hours. I would perchance suggest that the location be betwixt the basketball courts. + +You want an information density that is "just right" + +> Meet me at the basketball court + +[^literate]: There is a whole sect of folks who write their programs as actual books. Look up "Literate Programming" to dive deeper in to that. \ No newline at end of file diff --git a/src/code_is_read_more_than_written/meaning.md b/src/code_is_read_more_than_written/meaning.md new file mode 100644 index 00000000..c18eedaf --- /dev/null +++ b/src/code_is_read_more_than_written/meaning.md @@ -0,0 +1,14 @@ +# Meaning + +While I'm sure there are some statistics behind this[^ormaybe], its generally accepted that +the majority of a programmer's time is taken up reading existing code rather than writing new code. + +This should make sense intuitively. If you get hired at Microsoft, chances are you won't be given +a blank slate to make a brand new thing. Often you will be thrust into existing codebases. To make a change +to an existing codebase, you need to understand the exisiting code. + +It gets to the point where you might spend a whole day reading a dozen or so files only to make a 6 line change +to one of them. + + +[^ormaybe]: Software Development is still a relatively new field. Some things we take for granted may end up not being true after all. It doesn't help that its also pretty poorly researched. Keep an eye out for that. \ No newline at end of file diff --git a/src/code_is_read_more_than_written/practice.md b/src/code_is_read_more_than_written/practice.md new file mode 100644 index 00000000..366da34b --- /dev/null +++ b/src/code_is_read_more_than_written/practice.md @@ -0,0 +1,16 @@ +# Practice + +If you want to do programming as your job or as part of your job, +and you want to be competent, it isn't enough to just practice writing code. +You also need to practice reading. + +There are a lot of ways to go about that, but an easy one is to +read the code of your classmates or of strangers on the internet. + +Read their code, try to understand all the (maybe wacky!) choices they made +while writing it, and try to change it up a little bit. + +This will help you get an intuitive sense for what you like in +a codebase as well as what you do not. It will also, more so than +the early struggles you go through when learning to write, help you +understand if you would actually like to do this for work. \ No newline at end of file diff --git a/src/collections.md b/src/collections.md new file mode 100644 index 00000000..22440297 --- /dev/null +++ b/src/collections.md @@ -0,0 +1,14 @@ +# Collections + + + + +Arrays, `ArrayList`, and `HashMap` are all "collections" +of objects. Importantly, they also aren't the only possible +kinds of collections. + +Java provides support for a wide range of different collection types +through "The Collections Framework."[^marketing] + +[^marketing]: There is nothing really special about these interfaces and classes that mean they deserve such a name. I view it as a marketing term. There was a period of time before which Java did not have `ArrayList`, `HashMap`, etc. and when they were added they said "this is the new collections framework" +instead of "we've added these dozen classes." \ No newline at end of file diff --git a/src/collections/arrays.md b/src/collections/arrays.md new file mode 100644 index 00000000..74df3d37 --- /dev/null +++ b/src/collections/arrays.md @@ -0,0 +1,72 @@ +# Arrays + +Arrays are the odd duck out in the world of collections. They are basically a `List` but aren't `List`s.[^history] + +You can make a `List` which is a view over an array with `Arrays.asList`. + +```java +import java.util.List; +import java.util.Arrays; + +class Main { + void main() { + String[] furniture = new String[] { + "Ottoman", + "Table", + "Dresser" + }; + + List furnitureList = Arrays.asList(furniture); + + IO.println(furnitureList); + } +} +``` + +Changes made to the `List` returned from `Arrays.asList` will be reflected in the underlying array. + +```java +import java.util.List; +import java.util.Arrays; + +class Main { + void main() { + String[] furniture = new String[] { + "Ottoman", + "Table", + "Dresser" + }; + + List furnitureList = Arrays.asList(furniture); + + furnitureList.set(0, "Recliner"); + + IO.println(Arrays.toString(furniture)); + } +} +``` + +Accordingly, any methods on `List` which try to perform operations that an array cannot support (such as `.add`) will throw exceptions. + +```java,panics +import java.util.List; +import java.util.Arrays; + +class Main { + void main() { + String[] furniture = new String[] { + "Ottoman", + "Table", + "Dresser" + }; + + List furnitureList = Arrays.asList(furniture); + + // Cannot add to an array! + furnitureList.add("Shelf"); + } +} +``` + +[^history]: Arrays are unique beasts. This is true both in Java the language and in the virtual machine Java code runs on. +This is partially attributable to arrays coming first in the history - `List` and friends were not in the first version of Java. \ No newline at end of file diff --git a/src/collections/challenges.md b/src/collections/challenges.md new file mode 100644 index 00000000..d55bdd0b --- /dev/null +++ b/src/collections/challenges.md @@ -0,0 +1,84 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Replace uses of the "concrete" collection types (`ArrayList`, `HashMap`, `HashSet`) +in the following code with the corresponding collection interfaces. + +You want to keep the calls like `new ArrayList` - only change how variables are typed. + +```java,editable +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +class Main { + void main() { + ArrayList jedi = new ArrayList<>(); + jedi.add("Luke"); + jedi.add("Anakin"); + jedi.add("Qui-Gon"); + jedi.add("Obi-Wan"); + + HashSet sith = new HashSet<>(); + sith.add("Palpatine"); + + HashMap winningMatchups + = new HashMap<>(); + winningMatchups.put("Anakin", "Palpatine"); + winningMatchups.put("Obi-Wan", "Jar-Jar"); + + for (var j : jedi) { + IO.println(j + " is a jedi"); + + var matchup = winningMatchups.get(j); + if (matchup != null) { + if (sith.contains(matchup)) { + IO.println(j + " would win against " + matchup); + } + else { + IO.println(j + " would win against " + matchup + " (but they aren't sith)"); + } + } + + } + } +} +``` + +## Challenge 2. + +Call `methodB` using the array returned from `methodA`. + +```java +class Main { + String[] methodA() { + return new String[] { + "Chewbacca", + "Attichitcuk", + "Mallatobuck", + "Lumpawaroo" + } + } + + void methodB(List character) { + IO.println("Characters in the Star Wars Christmas Special:"); + IO.println("----------------"); + for (item : character) { + IO.println(item); + } + } + + void main() { + // CODE HERE + } +} +``` \ No newline at end of file diff --git a/src/collections/collection.md b/src/collections/collection.md new file mode 100644 index 00000000..e9e3a5fa --- /dev/null +++ b/src/collections/collection.md @@ -0,0 +1 @@ +# Collection diff --git a/src/collections/factories.md b/src/collections/factories.md new file mode 100644 index 00000000..0405515f --- /dev/null +++ b/src/collections/factories.md @@ -0,0 +1,89 @@ +# Factories + +There are methods on `List`, `Set`, and `Map`[^interface_static_methods] which can give +you instances of their corresponding collection. + +```java +import java.util.List; +import java.util.Set; +import java.util.Map; + +class Main { + void main() { + List weapons = List.of("Lightsaber", "Blaster"); + Set ships = Set.of("Tie Fighter", "X-Wing"); + Map midichlorians = Map.of( + "Anakin", 27000, + "Jar-Jar Binks", 0 + ); + + IO.println(weapons); + IO.println(ships); + IO.println(midichlorians); + } +} +``` + +The collections returned by these `of` methods are immutable. This means methods +which would change the underlying collection will throw an `UnsupportedOperationException`.[^fine] + +```java,panics +import java.util.List; + +class Main { + void main() { + List weapons = List.of("Lightsaber", "Blaster"); + + // Unsupported + weapons.add("A winning smile?") + + IO.println(weapons); + } +} +``` + +If you want the convenience of the factory methods but actually want an `ArrayList`, `HashMap`, or +a similar collection which supports `.add`, `.remove`, etc. you are in luck. Those classes generally +have a constructor which will copy another `List`, `Map`, or `Set`. + +```java +import java.util.List; + +class Main { + void main() { + // Reads better than a bunch of .add calls + List weapons = new ArrayList<>(List.of("Lightsaber", "Blaster")); + + // Will work! + weapons.add("A winning smile?") + + IO.println(weapons); + } +} +``` + + +If you want the opposite - if you want to make a copy of something like an `ArrayList` +which does not support `.add`, `.remove`, etc. - you can use `copyOf`. + +```java +import java.util.List; + +class Main { + void main() { + List weapons = new ArrayList<>(List.of("Lightsaber", "Blaster")); + weapons.add("A winning smile?") + IO.println(weapons); + + // Similar methods exist for Map and Set + List unchangable = List.copyOf(weapons); + IO.println(unchangable); + } +} +``` + +[^interface_static_methods]: Interfaces can have static methods. We'll cover it in a bit. For now all you need to know is that these methods exist, not how to define similar ones yourself. + +[^fine]: This is often fine. When something doesn't change after construction its one less thing to +have to think about when reading code. If you pass an `ArrayList` to a method you do need to wonder +if it is only going to be read or if something that you forgot about will call `.add`, `.remove`, etc. \ No newline at end of file diff --git a/src/collections/header.png b/src/collections/header.png new file mode 100644 index 00000000..89f27687 Binary files /dev/null and b/src/collections/header.png differ diff --git a/src/collections/list.md b/src/collections/list.md new file mode 100644 index 00000000..2de9d214 --- /dev/null +++ b/src/collections/list.md @@ -0,0 +1,57 @@ +# List + +A `List` is an ordered collection of elements that generally allows duplicates. +Lists that come with Java implement the `java.util.List` interface +which provides myriad methods for working with such a collection. + +```java,no_run +interface List { + int size(); + boolean isEmpty(); + E get(int index); + // and more +} +``` + +`ArrayList` implements `List` and is likely the named class you will see +most often, but there are other implementations you will encounter as time goes on. + +```java +import java.util.List; +import java.util.ArrayList; + +class Main { + void main() { + List names = new ArrayList<>(); + names.add("Andor"); + names.add("Bix"); + names.add("Luthen"); + + IO.println(names); + } +} +``` + +Every method and capability that `ArrayList` has is available from the `List` interface.[^saveafew] +This includes being able to be used in for-each loops. + +```java +import java.util.List; +import java.util.ArrayList; + +class Main { + void main() { + List names = new ArrayList<>(); + names.add("Andor"); + names.add("Bix"); + names.add("Luthen"); + + for (var name : names) { + IO.println(name); + } + } +} +``` + +[^saveafew]: Save a really niche one + diff --git a/src/collections/map.md b/src/collections/map.md new file mode 100644 index 00000000..3a3b8a93 --- /dev/null +++ b/src/collections/map.md @@ -0,0 +1,33 @@ +# Map + +A `Map` is a collection that maps keys to values. Maps that come with +Java implement the `java.util.Map` interface. + +```java +interface Map { + V get(Object key); + V put(K key, V value); + // and more +} +``` + +`HashMap` is one implementation of `Map` you are likely to see and use. + +```java +import java.util.Map; +import java.util.HashMap; + +class Main { + void main() { + Map ages = new HashMap<>(); + ages.put("Andor", 26); + ages.put("Bix", 27); + ages.put("Luthen", 59); + + IO.println(ages); + } +} +``` + +Just like `ArrayList` and `List`, all the useful capabilities of `HashMap` are available +via the `Map` interface. \ No newline at end of file diff --git a/src/collections/set.md b/src/collections/set.md new file mode 100644 index 00000000..d1010732 --- /dev/null +++ b/src/collections/set.md @@ -0,0 +1,39 @@ +# Set + +A `Set` is a collection of elements that does not allow duplicates. +Sets that come with Java implement the `java.util.Set` interface. + +```java,no_run +interface Set { + boolean contains(Object o); + boolean add(E e); + boolean remove(Object o); + // and more +} +``` + +`HashSet` provides an implementation of `Set` that works via the same mechanics - `.hashCode` and `.equals` - as a `HashMap`. + +```java +import java.util.Set; +import java.util.HashSet; + +class Main { + void main() { + Set agents = new HashSet<>(); + agents.add("Syril"); + agents.add("Dedra"); + + IO.println(agents); + + // If you try to add a duplicate to a set it won't + // crash, but there won't be two in the collection + agents.add("Syril"); + + IO.println(agents); + } +} +``` + +Checking membership in a large set is generally fast for the same reasons adding a new element +to a large map is fast. \ No newline at end of file diff --git a/src/collections/specificity.md b/src/collections/specificity.md new file mode 100644 index 00000000..7638d222 --- /dev/null +++ b/src/collections/specificity.md @@ -0,0 +1,30 @@ +# Specificity + +When you write methods that accept or return collections, the social rule is to +use the interface, not a specific collection type. + +```java,no_run +// You are generally expected to write this +List process(Set elements) { + // ... +} + +// And not this +ArrayList process(HashSet elements) { + // ... +} +``` + +There are valid reasons for this. + +* When you take a collection as an argument +the code that follows probably would work for any implementation of that collection. Why make someone +use any specific one? +* When you return a collection it is possible you might want to change the exact kind of +collection you return later. Why make things harder for future you? + +But there is also a dimension to it which is similar to how we choose to name classes, variables, and +methods. + +Socially it will cause you trouble if you make something which works in terms of a "concrete" +collection. Writing `List` instead of `ArrayList` where possible doesn't make your programs worse and it avoids frustrating interactions. \ No newline at end of file diff --git a/src/collections/unsupported_operation_exception.md b/src/collections/unsupported_operation_exception.md new file mode 100644 index 00000000..ffd7c62f --- /dev/null +++ b/src/collections/unsupported_operation_exception.md @@ -0,0 +1,34 @@ +# UnsupportedOperationException + +`List`, `Map`, and `Set` are interfaces with a lot of methods. +There are circumstances - like a `List` directly wrapping an array - +where it isn't possible to provide an implementation +for all those methods. + +In these situations, the specific exception that will be thrown is an `UnsupportedOperationException`. + +```java +import java.util.List; +import java.util.Arrays; + +class Main { + void main() { + String[] furniture = new String[] { + "Ottoman", + "Table", + "Dresser" + }; + + List furnitureList = Arrays.asList(furniture); + + try { + furnitureList.add("Shelf"); + } catch (UnsupportedOperationException e) { + IO.println("Tried to do an unsupported operation"); + } + } +} +``` + +For each collection some operations are mandatory to implement - like `.size` on `List` - +while others are allowed to be unsupported and still be a "valid" implementation. \ No newline at end of file diff --git a/src/collections_ii.md b/src/collections_ii.md new file mode 100644 index 00000000..da6ed7d4 --- /dev/null +++ b/src/collections_ii.md @@ -0,0 +1 @@ +# Collections II diff --git a/src/collections_ii/abstract_list.md b/src/collections_ii/abstract_list.md new file mode 100644 index 00000000..93122f49 --- /dev/null +++ b/src/collections_ii/abstract_list.md @@ -0,0 +1 @@ +# AbstractList diff --git a/src/collections_ii/collection.md b/src/collections_ii/collection.md new file mode 100644 index 00000000..e9e3a5fa --- /dev/null +++ b/src/collections_ii/collection.md @@ -0,0 +1 @@ +# Collection diff --git a/src/collections_ii/sequenced_collection.md b/src/collections_ii/sequenced_collection.md new file mode 100644 index 00000000..8db89591 --- /dev/null +++ b/src/collections_ii/sequenced_collection.md @@ -0,0 +1 @@ +# SequencedCollection diff --git a/src/command_line_arguments.md b/src/command_line_arguments.md new file mode 100644 index 00000000..12945680 --- /dev/null +++ b/src/command_line_arguments.md @@ -0,0 +1,18 @@ +# Command Line Arguments + + + +When you run a program like so + +``` +java src/Main.java +``` + +Anything you put to the right of `src/Main.java` will be available to your program as a +"command line argument."[^name] + +``` +java src/Main.java example +``` + +[^name]: They come from the command line and they are arguments to your program. \ No newline at end of file diff --git a/src/command_line_arguments/accessing_arguments.md b/src/command_line_arguments/accessing_arguments.md new file mode 100644 index 00000000..67a281fe --- /dev/null +++ b/src/command_line_arguments/accessing_arguments.md @@ -0,0 +1,41 @@ +# Accessing Arguments + +To access the command line arguments given to your program you need to change your main method. + +Instead of + +```java +void main() { + +} +``` + +Have your main method take a string array as an argument. + +```java +void main(String[] args) { + +} +``` + +This `String[]` will hold all the arguments passed. + +``` +java src/Main.java Duck Squirrel +``` + +```java +~class Main { +void main(String[] args) { + for (int i = 0; i < args.length; i++) { + IO.println( + "Hello Mr. " + args[i] + ); + } +} +~} +~// This is a little ugly +~void main() { +~ new Main().main(new String[] { "Duck", "Squirrel" }); +~} +``` diff --git a/src/command_line_arguments/challenges.md b/src/command_line_arguments/challenges.md new file mode 100644 index 00000000..154a78a8 --- /dev/null +++ b/src/command_line_arguments/challenges.md @@ -0,0 +1,50 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Make a program under `src/CLI.java`. The rest of these challenges will all be adding +features to that program. + +```java +class CLI { + // CODE HERE + + void main(String[] args) { + // CODE HERE + } +} +``` + +First, make it so that `java src/CLI.java` prints "No arguments provided" +and immediately exits. + +## Challenge 2. + +Make it so that `java src/CLI.java 8` will print `64` and then exit. +If you ran `java src/CLI.java 5` it would print `25` and so on. +For any number it should print it's square. + +## Challenge 3. + +Make the operation to perform overridable. `java src/CLI.java --operation square 8` +should do the same thing as `java src/CLI.java 8`. `java src/CLI.java --operation factorial 8` +should print out `8` factorial (which is `40320`) instead of `64`. + +## Challenge 4. + +If a `-d` argument is provided then, instead of printing the result, the program +should write the result to a file at the specified path. + +So `java src/CLI.java -d out.txt 9` should write `81` into a file named `out.txt`. +The order that `-d` and `--operation` are provided should not matter. `java src/CLI.java -d out.txt --operation factorial 3` and `java src/CLI.java --operation factorial -d out.txt 3` should behave exactly the same. + +## Challenge 5. + +Make `java src/CLI.java --help` print out a help message describing the different flags +you can provide and their meanings. To see what this should look somewhat like +first run `java --help`. \ No newline at end of file diff --git a/src/command_line_arguments/conventions.md b/src/command_line_arguments/conventions.md new file mode 100644 index 00000000..fc919e3a --- /dev/null +++ b/src/command_line_arguments/conventions.md @@ -0,0 +1,17 @@ +# Conventions + +As it is just a `String[]`, you can interpret the arguments passed to your +program in any way you choose. + +What you will notice, however, from using other tools in your terminal +is that there are a few socially accepted conventions. + +If your tool takes "options" then you would expect `--option-name VALUE` to +be how you specify it. So two dashes followed by the argument name. + +If you need a shorter version then you allow for one dash and sometimes a single +letter value `-d VALUE`. + +And most importantly, if someone types `--help` you should give them the +available options. Try `java --help` for an example. + diff --git a/src/command_line_arguments/header.png b/src/command_line_arguments/header.png new file mode 100644 index 00000000..430b35c2 Binary files /dev/null and b/src/command_line_arguments/header.png differ diff --git a/src/compilation.md b/src/compilation.md new file mode 100644 index 00000000..f71f1bac --- /dev/null +++ b/src/compilation.md @@ -0,0 +1,17 @@ +# Compilation + + + +The first step to sharing code you've written with other people is to +compile it. + +Compilation in the context of programming languages means that a program +reads all your code, ensures it abides by the rules of the language, +and translates your code to some other form. Usually this other form +is in some manner more directly "runnable" than the code you started with. + +We call programs that do this reading, ensuring, and translation "compilers." + +This happens behind the scenes when you run `java src/Main.java` +but for sharing code you will need to do it yourself as a separate +step. diff --git a/src/compilation/challenges.md b/src/compilation/challenges.md new file mode 100644 index 00000000..7405d034 --- /dev/null +++ b/src/compilation/challenges.md @@ -0,0 +1,30 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Take your code from one of the previous projects and compile all of it +to class files. + +Make sure you can then run it using those class files. + +## Challenge 2. + +Take the code for one of the previous projects and put it in +the multi-module directory layout. + +Compile it using the `--module-source-path` and `--module` +options instead of listing the files out explicitly. + +Make sure you can then run it using the `--module-path` and `--add-modules ALL-MODULE-PATH` +flags. + + +## Challenge 3. + +Run the command from the previous command again, this time making sure to pass `-g`. +Be sure to also clean whatever directory you used for output. \ No newline at end of file diff --git a/src/compilation/class_files.md b/src/compilation/class_files.md new file mode 100644 index 00000000..b462843c --- /dev/null +++ b/src/compilation/class_files.md @@ -0,0 +1,27 @@ +# Class Files + +The format that the Java compiler outputs is called a "class file." + +These class files are what is loaded into the "Java Virtual Machine" to actually run your program. This is +in part because the Java Virtual Machine was conceived as a general tool not technicially specific to Java. + +There are other languages besides Java - such as [Clojure](https://clojure.org/) and [Kotlin](https://kotlinlang.org/) - which +also can be compiled into class files. + +```text + +Java Source Code --> javac -------------\ + ----> Class Files --> Java Virtual Machine +Kotlin Source Code --> kotlinc ---------/ | + / +Lombok Source Code --> lombokc ----------/ + +... and more +``` + +This lets those languages make use of the Java Virtual Machine and all the millions +of dollars and decades of work put into making it run code fast.[^bringup] + +[^bringup]: I bring this up because if you are only thinking about Java it might seem like a +pointless extra step. It somewhat is, but at the same time it lets other languages "compete" +on more or less even ground. The JVM is a labor of fill-in-the-blank, that is for sure. \ No newline at end of file diff --git a/src/compilation/clean.md b/src/compilation/clean.md new file mode 100644 index 00000000..1afa87f6 --- /dev/null +++ b/src/compilation/clean.md @@ -0,0 +1,22 @@ +# Clean + +If you compile code multiple times you should "clean" +your working directory before each compilation. + +This is because when you run `javac -d output ...` the compiler +will simply dump class files into the destination folder. +It will not remove any "stale" class files from earlier compiler runs. + +This can lead you to be in a state where you think your code is working +but if you ever compiled it again, from scratch, you would get errors. + +The simplest way to handle this in bash is to use the `rm` tool. `rm` **r**e**m**oves +files. Before you run the compiler clear out any old output with `rm -rf output`. +`-rf` stands for "**r**ecursive" and "**f**orce." This is what you need to delete whole +folders.[^simple] + +[^simple]: This technique - just deleting folders and doing the work from scratch again - can be slow. +The upside of this approach is that it is simple to understand. +It also probably doesn't matter since your computer is fast. When you need to really efficiently compile +huge codebases you turn to a special kind of program called a build tool. These are complex beasts but can +avoid unneccesary work. \ No newline at end of file diff --git a/src/compilation/g.md b/src/compilation/g.md new file mode 100644 index 00000000..6b4aa4a6 --- /dev/null +++ b/src/compilation/g.md @@ -0,0 +1,39 @@ +# -g + +There are a lot of options you can pass to `javac` to alter its behavior. +One of the ones that I find useful is `-g`. The `g` stands for "**g**enerate debug info."[^obvious] + +This makes sure the generated class files have information about things like what variables were named +what. This, in turn, helps Java give better error messages + +For example, if you have a program like the following: + +```java +class Main { + void main() { + String nocturne = null; + IO.println(nocturne.length()); + } +} +``` + +While in all situations it will ultimately crash with a `NullPointerException`, if +you did not compile with `-g` you might get a stack trace like the following. +You will probably need to scroll to the right to see the relevant bit. + +```text,no_run +Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "" is null + at Main.main(Main.java:4) +``` + +Whereas using `-g` will get you an exception that includes the variable name of what was `null`. + +```text,no_run +Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "nocturne" is null + at Main.main(Main.java:4) +``` + +So, with rare exceptions, you should always use `-g`. + + +[^obvious]: Obviously. \ No newline at end of file diff --git a/src/compilation/header.png b/src/compilation/header.png new file mode 100644 index 00000000..117bacc2 Binary files /dev/null and b/src/compilation/header.png differ diff --git a/src/compilation/javac.md b/src/compilation/javac.md new file mode 100644 index 00000000..04703364 --- /dev/null +++ b/src/compilation/javac.md @@ -0,0 +1,36 @@ +# javac + +The compiler used for compiling Java code is called `javac`. +This stands for "**Java** **C**ompiler". + +Its job is to take a list of `.java` files and compile +them into `.class` files. + +For a single file Java program you can do this by running +a command similar to the following. + +```bash +javac -d output src/Main.java +``` + +The `-d output` in that example means "put the compiled class files in a folder called `output`." +After running the command above you would expect to see something like the following. + +```text +src/ + Main.java +output/ + Main.class +``` + +You aren't guarenteed that any given `.java` file will produce only one `.class` file +as output. Inner classes are one reason for this, but there are others. + +```text +src/ + Main.java +output/ + Main.class + Main$1.class + Main$Thing.class +``` diff --git a/src/compilation/modules.md b/src/compilation/modules.md new file mode 100644 index 00000000..7ee68770 --- /dev/null +++ b/src/compilation/modules.md @@ -0,0 +1,11 @@ +# Modules + +For reasons that will become apparent as we proceed, +it is best if all the code you intend to share +is contained within a named module. + +This means at minimum your classes would need to be in packages +and would need to provide a `module-info.java`. + +Many things will work even without that but some important ones +will not. \ No newline at end of file diff --git a/src/compilation/multiple_files.md b/src/compilation/multiple_files.md new file mode 100644 index 00000000..7c08c069 --- /dev/null +++ b/src/compilation/multiple_files.md @@ -0,0 +1,43 @@ +# Compile Multiple Files + +To have `javac` compile multiple files you have a few options. + +The first is to simply list every file you want to compile one after +the other when calling `javac`. + +```bash +javac -d output src/Main.java src/Other.java src/Another.java +``` + +This has the obvious downside of needing you to add new files to what can +become a large list over time. + +If you get far enough in learning bash you can paper over this +with commonly available tools like `find`. + +```bash +javac -d output $(find ./src -name "*.java" -type f) +``` + +Where the `$()` is bash syntax that runs the command in the parentheses and uses its +output as arguments to the command being run. `find` is a tool that lists all files that +match some criteria. In this case all files (as opposed to folders) that have a name that +ends with `.java`. + +The other option is to structure your project using the multi-module directory layout. + +``` +your.project/ + src/ + code/ + Main.java + Other.java + module-info.java +``` + +If you do this then you can compile all the code in a module by using the `--module-source-path` and `--module` options. + +```bash +javac -d output --module-source-path "./*/src" --module your.project +``` + diff --git a/src/compilation/running_compiled_code.md b/src/compilation/running_compiled_code.md new file mode 100644 index 00000000..01680836 --- /dev/null +++ b/src/compilation/running_compiled_code.md @@ -0,0 +1,43 @@ +# Running Compiled Code + +The way you ultimately run your compiled code depends on +whether or not all your code is in packages. + +If you have any classes in the unnamed package - which +will only be the case when said code is also not in a named +module - you should run your code like so: + +```bash,no_run +java --class-path output Main +``` + +Where you can substitute `Main` for whatever the class you want to run is. +So if you want to run a class named `Impromptu` in the `chopin` package +you would run `java --class-path output chopin.Impromptu`. + +`--class-path` should be self-explanatory. It is the path where `java` +will look for class files. + +But if you do not have any classes in the unnamed package - which will hopefully be the case when you share code with others[^conflicts] - +you instead want to run your code like this. + +``` +java \ + --module-path output \ + --add-modules ALL-MODULE-PATH + composers.Main +``` + +The `--module-path` option is very similar to the `--class-path` option. The difference +is that all the code on the `--module-path` will be loaded with more strict rules.[^before] + +The `--add-modules ALL-MODULE-PATH` bit just means "load all the code on the module path."[^auth] + +[^conflicts]: Remember the social convention of reverse domain name notation (`com.google`, etc) + +[^before]: You can think of `--class-path` as sort of a "compatibility mode" for libraries +and code written before Java had a concept of modules. The difference isn't super important +other than the very specific case of wanting to compile and run classes in the unnamed package. +Well that and "troublesome" libraries, but we will get to that later. + +[^auth]: Chances are Java will make this the default in the future. For now you must write it though. \ No newline at end of file diff --git "a/src/conclusion/Screenshot 2025-08-25 at 8.09.39\342\200\257PM.png" "b/src/conclusion/Screenshot 2025-08-25 at 8.09.39\342\200\257PM.png" new file mode 100644 index 00000000..c56ff811 Binary files /dev/null and "b/src/conclusion/Screenshot 2025-08-25 at 8.09.39\342\200\257PM.png" differ diff --git a/src/conclusion/cici_c_rat.png b/src/conclusion/cici_c_rat.png new file mode 100644 index 00000000..8ebb036d Binary files /dev/null and b/src/conclusion/cici_c_rat.png differ diff --git a/src/conclusion/csharp_mascot.png b/src/conclusion/csharp_mascot.png new file mode 100644 index 00000000..8e407be1 Binary files /dev/null and b/src/conclusion/csharp_mascot.png differ diff --git a/src/conclusion/go_mascot.png b/src/conclusion/go_mascot.png new file mode 100644 index 00000000..f0f80ac0 Binary files /dev/null and b/src/conclusion/go_mascot.png differ diff --git a/src/conclusion/java_mascot.png b/src/conclusion/java_mascot.png new file mode 100644 index 00000000..9047082e Binary files /dev/null and b/src/conclusion/java_mascot.png differ diff --git a/src/conclusion/javascript_mascot.png b/src/conclusion/javascript_mascot.png new file mode 100644 index 00000000..0ce0ee23 Binary files /dev/null and b/src/conclusion/javascript_mascot.png differ diff --git a/src/conclusion/kotlin_mascot.png b/src/conclusion/kotlin_mascot.png new file mode 100644 index 00000000..ba2b3a09 Binary files /dev/null and b/src/conclusion/kotlin_mascot.png differ diff --git a/src/conclusion/minecraft_duke.png b/src/conclusion/minecraft_duke.png new file mode 100644 index 00000000..6e85fb60 Binary files /dev/null and b/src/conclusion/minecraft_duke.png differ diff --git a/src/conclusion/python_logo.png b/src/conclusion/python_logo.png new file mode 100644 index 00000000..f80ddb58 Binary files /dev/null and b/src/conclusion/python_logo.png differ diff --git a/src/conclusion/ruby_logo.png b/src/conclusion/ruby_logo.png new file mode 100644 index 00000000..bcca018f Binary files /dev/null and b/src/conclusion/ruby_logo.png differ diff --git a/src/conclusion/what_now.md b/src/conclusion/what_now.md new file mode 100644 index 00000000..8b17eb33 --- /dev/null +++ b/src/conclusion/what_now.md @@ -0,0 +1,268 @@ +# What Now? + +If you have made it this far: + +1. Congratulations, you have now learned Java! +2. You are probably wondering what you should do next. + +## Go Deeper + +No matter how much I write there is no chance I will have covered all of the Java +language nor all of what you might want to know to write software in Java. + +With what you've learned so far you should have a solid enough foundation +to go off and learn from other sources. I'll try and paint a picture of the landscape for you +before you run off though. + +### Build Tools + +First you probably are going to want to learn a build tool. +I haven't covered how to get dependencies yet and that is on purpose. + +In the Java world - due to the ability to launch a program with `java src/Main.java` being +a pretty recent development - all the tools that help you automatically download +libraries written by other people are married with tools that "build" - i.e. run `javac`, `jar`, etc. +for you - your code. + +There are two major build tools (meaning widely used) in the Java world (Maven and Gradle) +as well as many more niche ones (bld, mill, etc.). + +If you want a gentle introduction to this world you can start with bld, though be aware this +will be a road less travelled. + +([bld tutorial here](https://github.com/rife2/bld/wiki)) + +If you want to learn the one that will probably be the most useful to you +in a professional setting you should learn Maven first. + +(["Maven By Example" book here](https://books.sonatype.com/mvnex-book/reference/index.html)) + +If you are angling to get into Android development you should learn Gradle. +Hop ahead and check out the resources for Kotlin too because Kotlin is the +language you will use for Gradle build scripts. + +([Gradle Documentation Here](https://docs.gradle.org/current/userguide/userguide.html)) + +### Minecraft + + + +If your age begins with the number `1` you are either near death or statistically +very interested in Minecraft. + +A lot of people who learn Java do so in order to be able to write Minecraft +mods or plugins for Minecraft servers, it is normal. + +Just a few words of caution: + +* The world of Minecraft development can be deeply exploitative. If you are not +a full adult please do not try to "work" for anyone. Be careful. Talk to a +parent or an adult you trust when people online seem to want something from you. +* The kinds of code you write to make mods work will be pretty different than the +kind of code you would write for most other kinds of software. This is partially +because of what modding is (adjusting software whose evolution you do not control) +and partially because of peculiarities around Minecraft in particular. + +With that all said: there are two basic "mod compatibility layers." These are +"Fabric" and "Forge." + +The point of these is to give you code to program against that isn't the direct Minecraft source +code, which can change very frequently, and to give your mod a fighting chance of being compatible +across multiple Minecraft versions. + +Of the two: this book probably prepared you the best for Forge. + +Forge requires you to use Gradle which in turn will require at least a little knowledge of Kotlin. +You don't need to take a full detour through that to get started, but you should put both of +those on your list of things to learn. + +[Forge Getting Started here](https://docs.minecraftforge.net/en/latest/gettingstarted/) + +["Modded Minecraft" Discord here](https://discord.gg/moddedmc) + +["Minecraft Mod Development" Discord here](https://discord.com/invite/wpMz4AtAhn) + +Fabric pretty quickly requires you to interact with a concept called a "Mixin." +This is a mechanism the Minecraft modding world made for magically editing the code inside Minecraft +among other things. If you go this path just be ready for that. + +[Fabric Getting Started here](https://docs.fabricmc.net/develop/getting-started/introduction-to-fabric-and-modding) + +[Fabric community Discord here]([https://discord.gg/v6v4pMv](https://discord.gg/v6v4pMv)) + +For making plugins that run on a custom Minecraft server - so things that handle custom +chat commands and things of that nature - you have to use the plugin required by whatever +server you are using. I am not the most up to date with Minecraft, but I know there +is both Spigot and PaperMC. I have been told that Spigot is the preferred option +as it allows for Bedrock players to play as well. + +[Docs for Spigot here](https://www.spigotmc.org/wiki/spigot-plugin-development/) + +[Spigot community Discord here](https://discord.gg/spigotmc) + +[Docs for PaperMC here](https://docs.papermc.io/) + +[PaperMC community Discord here](https://discord.gg/papermc) + + +### Websites + +Making websites is a profitable career path. At least it is at the time of writing. +There are a few essential things you will need to learn about to get started with that +path. + +The first is how to make an HTTP Server. HTTP Servers are what web browsers talk +to in order to render websites. + +There are a lot of tools for this in Java. A lot a lot. I would recommend +starting with the one that comes built-in: the `jdk.httpserver` module. + +[Docs for `jdk.httpserver` here](https://javadoc.mccue.dev/api/jdk.httpserver/module-summary.html) + +Then you will need to learn about SQL databases and how to query from/insert into them from Java. + +A good start for that is SQLite - it is a database that runs self-contained in a single file. + +[SQLite tutorial here](https://www.sqlitetutorial.net/) + +After that - or as part of that - you should learn about JDBC. This is the way you interact with +a database from Java. + +[I have a primer for that here](https://mccue.dev/pages/1-17-24-java-sql) + +Then from there you should learn about Dependency Injection + +[Good video on that here](https://www.youtube.com/watch?v=J1f5b4vcxCQ) + +And finally you can dive into the world of Spring - which is likely the +most employable one of the many HTTP server options out there. + +[Spring Academy Courses here - the basic ones are free](https://spring.academy/) + +### Desktop Applications + +If you want to learn how to make desktop applications in Java you have basically +two paths. + +Path #1 is to learn Java Swing. This is an old crusty GUI framework that is kinda difficult to use +but has the pro of coming with Java and being able to run on every potato in existence. + +[Docs for `java.desktop` (Swing) here](https://javadoc.mccue.dev/api/java.desktop/module-summary.html) + +Path #2 is the learn JavaFX. By all accounts JavaFX is better software than Swing, but it was cursed +by coming out at a point in history where desktop apps were no longer big business to develop. It +was eventually removed from the JDK and you will need to procure it like any other dependency. + +[Docs for JavaFX here](https://openjfx.io/openjfx-docs/) + +## Learn a New Language + +An unfortunate truth is that you cannot use Java for everything. + +Sometimes this is intrinsic - Java would not be a good choice for code +driving a pacemaker - and some of it is just how the ecosystems around languages +are right now. + +Even if you could get away with only using Java, there is significant value in knowing multiple +languages. Particuarly languages that are different than Java. + +### JavaScript + + + +To make highly interactive programs that run inside a browser you +will need JavaScript. JavaScript and languages that compile to JavaScript +are basically the only language it is practical +to use for the frontend of a website[^wasm]. + +It is probably worth your time to learn how to write JavaScript. + +[Mozilla Developer Network's JavaScript Guide here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide) + +The most popular language that compiles to JavaScript is TypeScript. After you've +learned JavaScript that is a good one to touch next. + +[TypeScript tutorial here](https://www.typescripttutorial.net/) + + +There are languages out there like TypeScript that compile to JavaScript - +and you can find some projects out there that do much the same for Java - +but just practically speaking learning JavaScript is going to be something +you have to do at some point if you get into making websites. + +### C# + + + +C# is a language broadly similar to Java. It has a lot of features Java doesn't - for better or worse - +but the basics are comparable. + +C# is very prevalent in the game development world. The Unity game engine, +[corporate blunders notwithstanding](https://www.theverge.com/2024/9/12/24242937/unity-runtime-fee-cancelled-subscription-pricing), is still very big and still has you use C# for game scripts. Competitors like Godot [have C# Scripting as well](https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/index.html). + +You will also find C# being used to make websites and desktop apps but it doesn't have +as much unique pull there as it does in game development. + +[Microsoft's C# tutorial here](https://learn.microsoft.com/en-us/training/paths/get-started-c-sharp-part-1/) + +### Kotlin + + + +Kotlin is one of a family of languages that try to be a "better Java." +Better is relative, but you are likely to learn _something_ when diving into it. + +Regardless, Kotlin is what you nowadays use to write Gradle build scripts in and +Kotlin is the de-facto language for writing Android apps. + +This means that, while Java in some form is still technically an option, +all the documentation for Android will be using Kotlin for their examples +and most new frameworks will assume you are using Kotlin in preference to Java. + +And Gradle is a build tool that many Java projects choose to use. You probably don't need +as deep of an understanding of Kotlin to work with Gradle build scripts as +you do to make a full app in it, but it can't hurt. + +[Getting Started with Kotlin tutorial here](https://kotlinlang.org/docs/getting-started.html) + +[Kotlin Android Tutorial here](https://kotlinlang.org/docs/android-overview.html) + +If you want to make Desktop, Mobile, or Web apps in Kotlin it is probably also worth checking out +Jetbrains Compose Multiplatfrom. + +[Getting Started for Jetbrains Compose Multiplatfrom here](https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-multiplatform-create-first-app.html) + + +### Others + +Other languages you might want to learn that I haven't written up +context for quite yet: + +* C + + + +* C++ +* Clojure +* Elm +* Haskell +* Python + + + +* Ruby + + + + +* Go + + + + +* Rust +* Zig +* Clojure + + +[^wasm]: Fight me, WebAssembly fans. \ No newline at end of file diff --git a/src/constr b/src/constr deleted file mode 100644 index a902009c..00000000 --- a/src/constr +++ /dev/null @@ -1 +0,0 @@ -# Declaration diff --git a/src/constructors.md b/src/constructors.md index c4cbb0a9..98a28e0b 100644 --- a/src/constructors.md +++ b/src/constructors.md @@ -1,5 +1,8 @@ # Constructors + + + When defining a class, you are allowed to make a special kind of method called a constructor. diff --git a/src/constructors/arguments.md b/src/constructors/arguments.md index 7a7073a7..fb26d309 100644 --- a/src/constructors/arguments.md +++ b/src/constructors/arguments.md @@ -19,7 +19,7 @@ void main() { Muppet gonzo = new Muppet("Gonzo"); // "Gonzo" - System.out.println(gonzo.name); + IO.println(gonzo.name); } ``` diff --git a/src/constructors/challenges.md b/src/constructors/challenges.md new file mode 100644 index 00000000..2974181c --- /dev/null +++ b/src/constructors/challenges.md @@ -0,0 +1,149 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Write a `Shoe` class. Give each shoe a `name` field and a `quality` field. +Have these fields be initialized inside of a constructor. + +```java,editable +enum Quality { + SUPA_FINE, + FINE, + SUB_FINE +} + +// CODE HERE + +void main() { + Shoe nike = new Shoe("Nikes", Quality.SUB_FINE); + IO.println( + "SHOE: " + nike.name + ", " + nike.quality + ); + + + Shoe moccasin = new Shoe("Moccasins", Quality.SUPA_FINE); + IO.println( + "SHOE: " + moccasin.name + ", " + moccasin.quality + ); +} +``` + +## Challenge 2. + +Add a new `price` field to the `Shoe` class you wrote above. + +Add a new constructor which accepts a third argument to set the `price`. +Keep the old two argument constructor around as well. When that one is +used set `price` to `null`. + +Hint: Use `Double` to represent a nullable price. + +```java,editable +enum Quality { + SUPA_FINE, + FINE, + SUB_FINE +} + +// CODE HERE + +void main() { + Shoe jays = new Shoe("Air Jordans", Quality.FINE, 130.0); + IO.println( + "SHOE: " + jays.name + ", " + jays.quality + ", $" + jays.price + ); + + Shoe nike = new Shoe("Nikes", Quality.SUB_FINE, 25); + IO.println( + "SHOE: " + nike.name + ", " + nike.quality + ", $" + jays.price + ); + + + Shoe moccasin = new Shoe("Moccasins", Quality.SUPA_FINE); + IO.println( + "SHOE: " + moccasin.name + ", " + moccasin.quality + ", $" + jays.price + ); +} +``` + +## Challenge 3. + +Alter the `Shoe` class so that the `price` field is final. +Alter its constructors so that if they are given a negative +value they throw an exception instead of finishing normally. + +Keep in mind that while `null` is allowed (you might not know the price) +a negative number wouldn't be. Nobody is paying you to take their shoes.[^holes] + +```java,editable +enum Quality { + SUPA_FINE, + FINE, + SUB_FINE +} + +// CODE HERE + +void main() { + Shoe jays = new Shoe("Air Jordans", Quality.FINE, 130.0); + IO.println( + "SHOE: " + jays.name + ", " + jays.quality + ", $" + jays.price + ); + + Shoe nike = new Shoe("Nikes", Quality.SUB_FINE, 25); + IO.println( + "SHOE: " + nike.name + ", " + nike.quality + ", $" + jays.price + ); + + + Shoe moccasin = new Shoe("Moccasins", Quality.SUPA_FINE); + IO.println( + "SHOE: " + moccasin.name + ", " + moccasin.quality + ", $" + jays.price + ); + + Shoe shouldCrash = new Shoe("Base Ball Cleats", Quality.SUPA_FINE, -10); +} +``` + +[^holes]: If they do, you are going to dig holes in the desert in search of treasure that rightfully belongs +to you. + +## Challenge 4. + +If you haven't yet, rewrite your `Shoe` constructors so only one of them actually sets fields +and the other just delegates to that one. + +```java,editable +enum Quality { + SUPA_FINE, + FINE, + SUB_FINE +} + +// CODE HERE + +void main() { + Shoe jays = new Shoe("Air Jordans", Quality.FINE, 130.0); + IO.println( + "SHOE: " + jays.name + ", " + jays.quality + ", $" + jays.price + ); + + Shoe nike = new Shoe("Nikes", Quality.SUB_FINE, 25); + IO.println( + "SHOE: " + nike.name + ", " + nike.quality + ", $" + jays.price + ); + + + Shoe moccasin = new Shoe("Moccasins", Quality.SUPA_FINE); + IO.println( + "SHOE: " + moccasin.name + ", " + moccasin.quality + ", $" + jays.price + ); + + Shoe shouldCrash = new Shoe("Base Ball Cleats", Quality.SUPA_FINE, -10); +} +``` \ No newline at end of file diff --git a/src/constructors/declaration.md b/src/constructors/declaration.md index 2ae1b365..27236a7c 100644 --- a/src/constructors/declaration.md +++ b/src/constructors/declaration.md @@ -25,7 +25,7 @@ class Muppet { void main() { Muppet gonzo = new Muppet(); - System.out.println(gonzo.talented); + IO.println(gonzo.talented); } ``` diff --git a/src/constructors/final_fields.md b/src/constructors/final_fields.md index 66542273..9c248266 100644 --- a/src/constructors/final_fields.md +++ b/src/constructors/final_fields.md @@ -17,7 +17,7 @@ class Muppet { void main() { Muppet gonzo = new Muppet("Gonzo"); - System.out.println(gonzo.name); + IO.println(gonzo.name); // Cannot update the .name field later // gonzo.name = "Gonzo, the great"; @@ -34,7 +34,7 @@ class Muppet { void main() { Muppet gonzo = new Muppet(); - System.out.println(gonzo.talented); + IO.println(gonzo.talented); } ``` diff --git a/src/constructors/header.png b/src/constructors/header.png new file mode 100644 index 00000000..69142a5d Binary files /dev/null and b/src/constructors/header.png differ diff --git a/src/constructors/invariants.md b/src/constructors/invariants.md index c09a7bcc..13b96a25 100644 --- a/src/constructors/invariants.md +++ b/src/constructors/invariants.md @@ -25,7 +25,7 @@ class Muppet { void main() { Muppet bigBird = new Muppet("Big Bird", 6); - System.out.println( + IO.println( bigBird.name + " is " + bigBird.age + " years old." ); } diff --git a/src/constructors/overloads.md b/src/constructors/overloads.md index 7ef5c4e4..bb309ea5 100644 --- a/src/constructors/overloads.md +++ b/src/constructors/overloads.md @@ -40,14 +40,14 @@ class Muppet { } void announce(Muppet muppet) { - System.out.print(muppet.name); + IO.print(muppet.name); if (muppet.talented) { - System.out.print(" is "); + IO.print(" is "); } else { - System.out.print(" is not "); + IO.print(" is not "); } - System.out.println("talented."); + IO.println("talented."); } void main() { diff --git a/src/constructors/the_default_constructor.md b/src/constructors/the_default_constructor.md index cd75169c..45a8573c 100644 --- a/src/constructors/the_default_constructor.md +++ b/src/constructors/the_default_constructor.md @@ -16,8 +16,8 @@ void main() { Muppet gonzo = new Muppet(); // null - System.out.println(gonzo.name); + IO.println(gonzo.name); // false - System.out.println(gonzo.talented); + IO.println(gonzo.talented); } ``` \ No newline at end of file diff --git a/src/distribution.md b/src/distribution.md new file mode 100644 index 00000000..4a44067a --- /dev/null +++ b/src/distribution.md @@ -0,0 +1,12 @@ +# Distribution + +If you are intending to run code on your own machine there isn't much reason to +compile or package it. Compiling and packaging become useful when sharing +code with other developers or when "deploying" code to a machine you (in some manner) control. + +But if you are making code intended to be used by a layperson it is insane to expect them +to install Java special just for your program. It can also become burden on you +since if you do get them to install Java, they won't keep it up to date. + +The solution is to bundle your code with the Java runtime itself. This gives you +an "artifact" you can freely distribute and have someone "just run." diff --git a/src/distribution/jars.md b/src/distribution/jars.md new file mode 100644 index 00000000..f38d94b9 --- /dev/null +++ b/src/distribution/jars.md @@ -0,0 +1 @@ +# jars diff --git a/src/documentation.md b/src/documentation.md new file mode 100644 index 00000000..81a0fb42 --- /dev/null +++ b/src/documentation.md @@ -0,0 +1,16 @@ +# Documentation + + + +When sharing code that is intended to be used as a library it is important to explain +how exactly your code should be used. + +If you can physically or digitally converse with the people you are sharing with this is a solved problem. +They can ask you questions and you can answer them. Rinse and repeat until they no longer need to ask you +questions. + +But seeing as there are millions of developers in the world that isn't always practical.[^grouptext] + +One tool we use to combat this reality is documentation. Documentation is where we write down what things are, what they do and why. + +[^grouptext]: Would make for one hell of a hectic group text. \ No newline at end of file diff --git a/src/documentation/challenges.md b/src/documentation/challenges.md new file mode 100644 index 00000000..f413b408 --- /dev/null +++ b/src/documentation/challenges.md @@ -0,0 +1,24 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Run `javadoc` on some of your code. Add the command for doing so to a `Justfile`. +Make sure to include cleaning stale of output. + +## Challenge 2. + +When you ran `javadoc` on your code you were almost certainly hit by a deluge of `warning: no comment` +messages. + +Fix these by documenting that code enough that `javadoc` no longer gives you warnings. + +## Challenge 3. + +Read the [documentation for java.util.StringJoiner](https://javadoc.mccue.dev/api/java.base/java/util/StringJoiner.html). + +If you can, alter one of your projects to use it. \ No newline at end of file diff --git a/src/documentation/documentation_comments.md b/src/documentation/documentation_comments.md new file mode 100644 index 00000000..dc7ccd4a --- /dev/null +++ b/src/documentation/documentation_comments.md @@ -0,0 +1,21 @@ +# Documentation Comments + +To document our code we can use "documentation comments." + +These work the same as regular comments but instead of using two slashes (`//`) +we use three slashes (`///`).[^old] + +You put these documentation comments above different elements of your program +with text describing what the purpose of those elements are. + +```java +/// This class represents a ninja +public class Ninja { + /// Says a catchphrase + public void forsooth() { + // .. + } +} +``` + +[^old]: There is an older way to make documentation comments using `/**` and `*/` (note the extra `*` in `/**`), but we prefer this one because the way you write the actual content of the comment is easier. You are sure to see these around though. \ No newline at end of file diff --git a/src/documentation/format.md b/src/documentation/format.md new file mode 100644 index 00000000..7249b2e0 --- /dev/null +++ b/src/documentation/format.md @@ -0,0 +1,80 @@ +# Format + +```java,no_run +import java.io.IOException; + +/// This class serves as a place to put examples +/// of the different kinds of documentation as well +/// as how to write documentation properly. +/// +/// When specified after the three slashes +/// you can write documentation using [Markdown](https://en.wikipedia.org/wiki/Markdown). +/// +/// In Markdown you can just write text as you would in any file, +/// line after line. +/// +/// # For headings you can use a single hash +/// +/// ## For subheadings you can use two +/// +/// ### And so on +/// +/// You can make unordered lists using hyphens +/// +/// - A +/// - B +/// - C +/// +/// And numbered lists like so +/// +/// 1. One +/// 2. Two +/// 3. Thre +/// +/// And so on. Definitely peruse up [tutorial on markdown](https://www.markdownguide.org/getting-started/) +/// when you have the time. +/// +/// There are some additions specific to Java though. +/// We call these additions "tags." +/// +/// One notable tag is the author tag. It lets you mark who worked +/// on a given unit of code +/// +/// @author Ethan McCue +/// @see [https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html](https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html) +public class DocumentationExample { + /// You can document the purpose of parameters + /// to constructors and methods with the param tag + /// + /// @param o Demonstrates a param tag on a constructor + public DocumentationExample(Object o) { + + } + + /// Generic parameters can also be documented using the param tag + /// as well. + /// + /// @param item The item to just return back + /// @param The type of the item. + public static T identity(T item) { + return item; + } + + /// The exceptions thrown by a method can also be documented + /// to explain under what conditions the exceptions might be thrown + /// + /// @param s A parameter to show that throws can be used alongside params + /// @throws IOException whenever the method is called, no matter what + public void crash(String s) throws IOException { + throw new IOException(); + } + + /// You can reference other classes and methods on those classes with the + /// link tag. + /// + /// For instance {@link String} and {@link String#length()}. + public void seeMore() { + + } +} +``` \ No newline at end of file diff --git a/src/documentation/header.png b/src/documentation/header.png new file mode 100644 index 00000000..3ba43300 Binary files /dev/null and b/src/documentation/header.png differ diff --git a/src/documentation/javadoc.md b/src/documentation/javadoc.md new file mode 100644 index 00000000..cf2c0f53 --- /dev/null +++ b/src/documentation/javadoc.md @@ -0,0 +1,22 @@ +# javadoc + +The way to take code with documentation comments and produce a documentation website +is with the `javadoc` tool. + +There are multiple ways to run this[^theme], but if you have your code in the +multi-module directory layout you can use `--module-path` and `--module` the +same as `javac` does. + +```bash,no_run +javadoc \ + -d output/javadoc \ + --module-source-path "./*/src" \ + --module one.piece +``` + +This will produce a directory full of HTML files that contain all +the documentation from the specified modules. + +This is what is used to make [the official Java documenation](https://javadoc.mccue.dev/api/index.html) as well as at least part of the documentation for most Java libraries. + +[^theme]: As is a theme with command-line tools \ No newline at end of file diff --git a/src/easy_vs_simple.md b/src/easy_vs_simple.md new file mode 100644 index 00000000..852ff533 --- /dev/null +++ b/src/easy_vs_simple.md @@ -0,0 +1 @@ +# Easy vs. Simple diff --git a/src/encapsulation.md b/src/encapsulation.md new file mode 100644 index 00000000..350ed27b --- /dev/null +++ b/src/encapsulation.md @@ -0,0 +1,11 @@ +# Encapsulation + + + +One way of combatting the effects of Hyrum's Law +is encapsulation. + +To encapsulate something is to hide it from the larger world. By doing +so you lower the number of people who are able to observe what we would call +"implementation details." + diff --git a/src/encapsulation/abstractions.md b/src/encapsulation/abstractions.md new file mode 100644 index 00000000..b7f2969b --- /dev/null +++ b/src/encapsulation/abstractions.md @@ -0,0 +1,8 @@ +# Abstraction + +A term closely related to encapsulation is abstraction. + +When you encapsulate something you have made an abstraction on top of it. + +You could say that "abstraction" is the noun and "encapsulation" is the verb. +You encapsulate things, thereby making abstractions. \ No newline at end of file diff --git a/src/encapsulation/classes.md b/src/encapsulation/classes.md new file mode 100644 index 00000000..d1a53df2 --- /dev/null +++ b/src/encapsulation/classes.md @@ -0,0 +1,65 @@ +# Classes + +One step above methods is classes. + +When classes have private fields and all interactions with those fields +are mediated through methods + +The `ArrayList` class that comes with Java looks something like this.[^liberties] + +```java +public class ArrayList extends AbstractList + implements List, RandomAccess, Cloneable, java.io.Serializable +{ + Object[] elementData; + + private int size; + + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + private void add(E e, Object[] elementData, int s) { + if (s == elementData.length) + elementData = grow(); + elementData[s] = e; + size = s + 1; + } + + private Object[] grow(int minCapacity) { + int oldCapacity = elementData.length; + if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + int newCapacity = ArraysSupport.newLength(oldCapacity, + minCapacity - oldCapacity, /* minimum growth */ + oldCapacity >> 1 /* preferred growth */); + return elementData = Arrays.copyOf(elementData, newCapacity); + } else { + return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)]; + } + } + + private Object[] grow() { + return grow(size + 1); + } + + public boolean add(E e) { + modCount++; + add(e, elementData, size); + return true; + } + + // ... +} +``` + + +People who write programs that depend on calling `.add` on an `ArrayList` do not need +to understand how or when the internal `elementData` and `size` fields are updated. Those also +need not be the only fields that exist. All that is required is "calling `.add` will add an element to the list." + +It is in this way that classes provide encapsulation. They can hide the fields which +are needed and the way in which they are used - "implementation details" - in order +to provide some implicit interface. + + +[^liberties]: Creative liberties taken. \ No newline at end of file diff --git a/src/encapsulation/coupling.md b/src/encapsulation/coupling.md new file mode 100644 index 00000000..2aad6dbc --- /dev/null +++ b/src/encapsulation/coupling.md @@ -0,0 +1,18 @@ +# Coupling + + + +If two things are "coupled" it means that a change in one +will mandate a change in the other. Or, at the very least, +active consideration of whether such a change is neccesary. + +In a way "coupling" is the opposite of "abstraction." Instead +of hiding implementation details you freely intermingle them. + +This does not mean that coupling is bad. On the contrary, so +long as code that is coupled is also "co-located" making changes +to that code can be easier than if it were spread over multiple files. + +A not insignificant part of practice in the coding world is deciding when +pieces of a program deserve to be abstracted from each other (to minimize interdependence) +and when they deserve to be coupled together (to keep logic in one place.) \ No newline at end of file diff --git a/src/encapsulation/header.png b/src/encapsulation/header.png new file mode 100644 index 00000000..d9faaf2d Binary files /dev/null and b/src/encapsulation/header.png differ diff --git a/src/encapsulation/implementation_details.md b/src/encapsulation/implementation_details.md new file mode 100644 index 00000000..1d6a88d7 --- /dev/null +++ b/src/encapsulation/implementation_details.md @@ -0,0 +1,21 @@ +# Implementation Details + +Just as there is almost always more than one way +to remove the epidermis of a feline, +there is often more than one way to implement +a program. + +We call properties and choices made when writing a program +that are not related to what that program ultimitely needs +to do "implementation details." + +As an example, imagine you are about to type +a query into Microsoft Bing.[^bingit] The website you go to +and the search box you see are intended parts of the program. +The fact that if you click search you will see results is also +just what the program needs to do. + +But all the mechanics of *how* that query is answered are +implementation details. + +[^bingit]: You know, when you have a question and just have to "bing it?" \ No newline at end of file diff --git a/src/encapsulation/implicit_interfaces.md b/src/encapsulation/implicit_interfaces.md new file mode 100644 index 00000000..62fec3bb --- /dev/null +++ b/src/encapsulation/implicit_interfaces.md @@ -0,0 +1,20 @@ +# Implicit Interfaces + +There are many kinds of "interfaces." + +There are `interface`s the Java language construct - these let you +specify methods that different classes will share and write code +that will work against anything which implements that interface. + +But there are also "interfaces" that you encounter in the real world. +The classic example is an automobile. Once you understand +the "interface" of a steering wheel, brake pedal, and gas pedal +you can drive most any car. + +Then back within Java there are things which form "an interface" +which may or may not use the `interface` keyword. The static methods available on a class, +the constructors that are public, the set of classes that come with a library, etc. + +I think a more general concept is "the implicit interface." Everything you can observe about a thing forms its "implicit interface."[^implicit] + +[^implicit]: I call it "implicit" because there is no place where you write down "all the properties of a thing" that is not the thing itself. And by thing I mean field, method, class, group of classes, package names - everything. \ No newline at end of file diff --git a/src/encapsulation/information_hiding.md b/src/encapsulation/information_hiding.md new file mode 100644 index 00000000..b0e2e051 --- /dev/null +++ b/src/encapsulation/information_hiding.md @@ -0,0 +1,102 @@ +# Information Hiding + +When encapsulating, your fundamental activity is finding +ways to hide information that you consider to be +implementation details. + +If you did not hide this information and +you have a large number of consumers[^if], you would never +be able to change those implementation details. Your consumers +can be coupled to them. + +Something to be careful of with respect to this is "side channels." +If you use the mechanisms Java gives you to hide a field from people +you can get pretty far, but you still need to be wary of accidentally allowing +people to do things you don't want. + +```java,no_run +import java.util.ArrayList; + +class Total { + private final ArrayList nums; + + Total() { + this.nums = new ArrayList<>(); + } + + void add(int num) { + nums.add(num); + } + + int total() { + int sum = 0; + for (int num : nums) { + sum += num; + } + return sum; + } + + @Override + public String toString() { + return "Total[" + nums + "]"; + } +} +``` + +In the code above the `Total` class hides a field holding an `ArrayList` +and just exposes the total of the numbers added to it. + +```java,no_run +void main() { + var total = new Total(); + total.add(3); + total.add(2); + total.add(1); + + IO.println(total.total()); +} +``` + +This lets it later on decide to maybe not store the list of numbers at all and instead keep a running total. + +```java,no_run +import java.util.ArrayList; + +class Total { + private int runningTotal; + + Total() { + this.runningTotal = 0; + } + + void add(int num) { + runningTotal += num; + } + + int total() { + return runningTotal; + } + + @Override + public String toString() { + return "Total[" + runningTotal + "]"; + } +} +``` + +The problem is that if you had called `.toString()` on the previous implementation +you might have gotten a `String` that looks like `Total[[3, 2, 1]]`. The information +of what numbers are in the list was exposed there. + +With enough people using this code some motivated moron will work backwards +from that `String` representation to get at the underlying list. Now if you changed the implementation +you would break their code. + +We would call that a "side channel." The information is exposed, just through an API you didn't consider. +How you handle that is heavily dependent on the exact situation, but its a category of issue you should know about. + + +[^if]: Again, if. These concerns do not apply as much to +programs written at smaller scales or programs written +_within_ some encapsulation boundary. Don't get too paranoid +about needing to hide things. \ No newline at end of file diff --git a/src/encapsulation/leaky_abstractions.md b/src/encapsulation/leaky_abstractions.md new file mode 100644 index 00000000..6cd69618 --- /dev/null +++ b/src/encapsulation/leaky_abstractions.md @@ -0,0 +1,14 @@ +# Leaky Abstractions + +If an abstraction doesn't fully encapsulate what it is trying to we call +that abstraction "leaky." + +As an example, say we define a person as having a first name and a given name. That +might work for a large number of people. But the moment our program needs to represent +Plato, who did not have a given name, that abstraction "leaks." + +Now suddenly the code that would work with people needs to account for an "empty" given name +where it didn't before and we need to pick a special value to represent such a name (empty string or `null` generally). The abstraction has leaked. + +There are maybe more illuminating examples, but thats the general concept. Its not the end of the +world if an abstraction leaks. It might just be a sign of a changing world or of not having thought through a problem fully. You can adapt to that. It's just worth knowing about. \ No newline at end of file diff --git a/src/encapsulation/methods.md b/src/encapsulation/methods.md new file mode 100644 index 00000000..1b3b663c --- /dev/null +++ b/src/encapsulation/methods.md @@ -0,0 +1,43 @@ +# Methods + +The simplest mechanism for encapsulating implementation details +is a method. + +The `Math.sin` function that comes with Java has a definition that looks +something like this.[^liberties] + +```java,no_run +double sin(double x) { + double[] y = new double[2]; + double z = 0.0; + int n, ix; + + // High word of x. + ix = __HI(x); + + // |x| ~< pi/4 + ix &= EXP_SIGNIF_BITS; + if (ix <= 0x3fe9_21fb) { + return __kernel_sin(x, z, 0); + } else if (ix >= EXP_BITS) { // sin(Inf or NaN) is NaN + return x - x; + } else { // argument reduction needed + n = RemPio2.__ieee754_rem_pio2(x, y); + // ... + } +} +``` + +People who write programs that depend on the `sin` function do not generally +understand how this works. I have a math minor[^notimpressive] and I certainly don't. + +All they need to know to use `sin` effectively is what it does, not how it does it. +In this way methods are one way to provide encapsulation. You reduce +a potentially complicated body of code to inputs required and outputs produced.[^news] + +[^liberties]: I took some creative liberties here, roll with it. + +[^notimpressive]: Laugh it up. + +[^news]: This shouldn't be news to you at this point, but I think its helpful to point +out that property in the context of this topic. \ No newline at end of file diff --git a/src/enums.md b/src/enums.md index 6d5b3a8c..feead6cf 100644 --- a/src/enums.md +++ b/src/enums.md @@ -1,5 +1,8 @@ # Enums + + + While you can use `String`, `int`, `boolean`, and friends alongside your own custom classes to represent many situations, that is not always enough. @@ -17,4 +20,4 @@ enum StopLight { } ``` -We call them enums because they enumerate multiple different possibilities. \ No newline at end of file +Enums are types with fixed sets of allowed values. We call them enums because they enumerate multiple different possibilities. \ No newline at end of file diff --git a/src/enums/challenges.md b/src/enums/challenges.md new file mode 100644 index 00000000..b8928824 --- /dev/null +++ b/src/enums/challenges.md @@ -0,0 +1,107 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Make an enum named `Response` which +has three variants. `YES`, `NO`, and `MAYBE_SO`. + +```java,editable +// ------------- +// CODE HERE +// ------------- + +void main() { + IO.println( + Response.YES + ); + + IO.println( + Response.NO + ); + + IO.println( + Response.MAYBE_SO + ); +} +``` + +## Challenge 2. + +Write a method named `goodPerformer` which takes +in a `String` representing the name of an artist. + +If that `String` is equal to `Pitbull` or `Billy Joel` +return `YES`. If it is equal to `Shaggy` return `NO`. +Otherwise return `MAYBE_SO`. + +Use the enum you defined above. + +```java,editable +// ------------ +// CODE HERE +// ------------ + +void main() { + Response pitbull = goodPerformer("Pitbull"); + IO.println(pitbull); + + Response billyJoel = goodPerformer("Billy Joel"); + IO.println(billyJoel); + + Response shaggy = goodPerformer("Shaggy"); + IO.println(shaggy); + + Response chappelRoan = goodPerformer("Chappell Roan"); + IO.println(chappelRoan); +} +``` + +## Challenge 3. + +Make a method named `transition` which takes in a `StopLight` +and returns the next light it will transition to. + +For those who don't drive cars: red lights go to green, +green lights go to yellow, and yellow lights go to red. + +```java,editable +enum StopLight { + RED, + YELLOW, + GREEN +} + +StopLight transition(StopLight current) { + // ------------ + // CODE HERE + // ------------ +} + +void main() { + var light = StopLight.RED; + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); +} +``` \ No newline at end of file diff --git a/src/enums/comparison_to_boolean.md b/src/enums/comparison_to_boolean.md index c889bd79..da139065 100644 --- a/src/enums/comparison_to_boolean.md +++ b/src/enums/comparison_to_boolean.md @@ -21,10 +21,10 @@ void main() { Power power = Power.ON; if (power == Power.ON) { - System.out.println("The power is on"); + IO.println("The power is on"); } else { - System.out.println("The power is off"); + IO.println("The power is off"); } } ``` diff --git a/src/enums/equality.md b/src/enums/equality.md index 23b43757..84f4da80 100644 --- a/src/enums/equality.md +++ b/src/enums/equality.md @@ -14,10 +14,10 @@ void main() { StopLight light = StopLight.RED; if (light == StopLight.RED) { - System.out.println("You must stop"); + IO.println("You must stop"); } else { - System.out.println("Full speed ahead!"); + IO.println("Full speed ahead!"); } } ``` \ No newline at end of file diff --git a/src/enums/header.png b/src/enums/header.png new file mode 100644 index 00000000..f91a992e Binary files /dev/null and b/src/enums/header.png differ diff --git a/src/enums/switch.md b/src/enums/switch.md index 5e103c97..9749d924 100644 --- a/src/enums/switch.md +++ b/src/enums/switch.md @@ -11,10 +11,10 @@ void main() { StopLight light = StopLight.RED; if (light == StopLight.RED) { - System.out.println("You must stop"); + IO.println("You must stop"); } else { - System.out.println("Full speed ahead!"); + IO.println("Full speed ahead!"); } } ``` \ No newline at end of file diff --git a/src/enums/usage.md b/src/enums/usage.md index 51f91ebb..3dffd1c6 100644 --- a/src/enums/usage.md +++ b/src/enums/usage.md @@ -16,7 +16,7 @@ enum StopLight { void main() { StopLight light = StopLight.RED; - System.out.println( + IO.println( "The light is " + light ); } diff --git a/src/ex b/src/ex deleted file mode 100644 index f0aebb41..00000000 --- a/src/ex +++ /dev/null @@ -1 +0,0 @@ -# Exception Messages diff --git a/src/exceptions.md b/src/exceptions.md index b37b4725..73e86bc4 100644 --- a/src/exceptions.md +++ b/src/exceptions.md @@ -1,5 +1,8 @@ # Exceptions + + + When you do something that Java doesn't know how to handle, like dividing a number by zero, your program will fail. @@ -11,7 +14,7 @@ In "exceptional conditions" code will no longer be able to proceed. ```java,panics void main() { int x = 5 / 0; - System.out.println("Won't get here, an exception will occur"); + IO.println("Won't get here, an exception will occur"); } ``` diff --git a/src/exceptions/challenges.md b/src/exceptions/challenges.md new file mode 100644 index 00000000..017c57bb --- /dev/null +++ b/src/exceptions/challenges.md @@ -0,0 +1,166 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Write a method named `arise` which accepts a `String` parameter +representing someone's name and prints `Awake `. + +If this function is given an empty string throw a `RuntimeException`. + +```java,editable +// ------------ +// CODE HERE +// ------------ + +void main() { + arise("Lion El'Jonson"); + arise("Roboute Guilliman"); + + arise(""); +} +``` + +## Challenge 2. + +Starting with the code you wrote above, make the thrown runtime exception +include a message saying why it was thrown ("given an empty string" or something like that.) + +```java,editable +// ------------ +// CODE HERE +// ------------ + +void main() { + arise("Lion El'Jonson"); + arise("Roboute Guilliman"); + + arise(""); +} +``` + +## Challenge 3. + +The following code is written in an intentionally confusing way. +Instead of reading the code and trying to figure it out that way, +run the code and read the stack trace to figure out which method originally +throws an exception. + +```java,panics +void a(int x) { + if (x == 0) { + throw new RuntimeException(); + } + else { + b(x / 2); + } +} + +void b(int x) { + if (x == 0) { + throw new RuntimeException(); + } + else { + c(x * 3 + 5); + } +} + +void c(int x) { + if (x == 0) { + throw new RuntimeException(); + } + else { + d(x / 4); + } +} + +void d(int x) { + if (x == 0) { + throw new RuntimeException(); + } + else { + e(x / 3); + } +} + +void e(int x) { + if (x == 0) { + throw new RuntimeException(); + } + else { + a(x / 10); + } +} + +void main() { + a(1215135236); +} +``` + +## Challenge 4. + +Write a method named `command` which takes in a `SpaceMarine`. +If the space marine is corrupted throw a `RuntimeException`. +Otherwise print out their name. + +```java,editable +class SpaceMarine { + boolean corrupted; + String name; +} + +// --------------------- +// CODE HERE +// --------------------- + +void main() { + SpaceMarine titus = new SpaceMarine(); + titus.corrupted = false; + titus.name = "Demetrian Titus"; + + command(titus); + + SpaceMarine imurah = new SpaceMarine(); + imurah.corrupted = true; + imurah.name = "Imurah"; + + command(imurah); +} +``` + +## Challenge 5. + +Alter your code above by adding a new method named `safeCommand`. It should +call `command` in a `try/catch` block. If a `RuntimeException` is thrown +it should print `Unable to command`. + +```java,editable +class SpaceMarine { + boolean corrupted; + String name; +} + +// --------------------- +// CODE HERE +// --------------------- + +void main() { + SpaceMarine titus = new SpaceMarine(); + titus.corrupted = false; + titus.name = "Demetrian Titus"; + + command(titus); + safeCommand(titus); + + SpaceMarine imurah = new SpaceMarine(); + imurah.corrupted = true; + imurah.name = "Imurah"; + + safeCommand(imurah); +} +``` + diff --git a/src/exceptions/exception_messages b/src/exceptions/exception_messages deleted file mode 100644 index f0aebb41..00000000 --- a/src/exceptions/exception_messages +++ /dev/null @@ -1 +0,0 @@ -# Exception Messages diff --git a/src/exceptions/header.png b/src/exceptions/header.png new file mode 100644 index 00000000..ba60a784 Binary files /dev/null and b/src/exceptions/header.png differ diff --git a/src/exceptions/messages.md b/src/exceptions/messages.md index 7364aa60..2785b062 100644 --- a/src/exceptions/messages.md +++ b/src/exceptions/messages.md @@ -15,9 +15,9 @@ void crashesOnFive(int x) { void main() { crashesOnFive(1); - System.out.println("Made it to step 1"); + IO.println("Made it to step 1"); crashesOnFive(5); - System.out.println("Will not make it to step 2"); + IO.println("Will not make it to step 2"); } ``` \ No newline at end of file diff --git a/src/exceptions/throw.md b/src/exceptions/throw.md index ba0bf638..d90dc910 100644 --- a/src/exceptions/throw.md +++ b/src/exceptions/throw.md @@ -1,6 +1,6 @@ # throw -In order to throw an exception from your own code, you say `throw` then the name +In order to throw an exception from your own code, you say `throw`, `new`, then the name of the exception and `()`. `RuntimeException` is one of many kinds of exceptions, but you can make do with only @@ -15,9 +15,9 @@ void crashesOnFive(int x) { void main() { crashesOnFive(1); - System.out.println("Made it to step 1"); + IO.println("Made it to step 1"); crashesOnFive(5); - System.out.println("Will not make it to step 2"); + IO.println("Will not make it to step 2"); } ``` \ No newline at end of file diff --git a/src/exceptions/try_catch.md b/src/exceptions/try_catch.md index f4f2e135..562d8fbe 100644 --- a/src/exceptions/try_catch.md +++ b/src/exceptions/try_catch.md @@ -36,7 +36,7 @@ void main() { try { doThing(x); } catch (RuntimeException e) { - System.out.println("Something went wrong doing a thing."); + IO.println("Something went wrong doing a thing."); } } ``` @@ -46,7 +46,7 @@ Just as you cannot have an `else` without an `if`, you cannot have a `catch` wit ```java,does_not_compile void main() { catch (RuntimeException e) { - System.out.println("Hello"); + IO.println("Hello"); } } ``` @@ -56,7 +56,7 @@ Nor can you have a `try` without a `catch`.[^trywithresources] ```java,does_not_compile void main() { try { - System.out.println("Hello"); + IO.println("Hello"); } } ``` diff --git a/src/exceptions_ii.md b/src/exceptions_ii.md new file mode 100644 index 00000000..fd862df7 --- /dev/null +++ b/src/exceptions_ii.md @@ -0,0 +1,9 @@ +# Exceptions II + + + +Perhaps unsurprisingly, `RuntimeException` is not the only kind of exception +that can be thrown. + +There is a huge variety of exception types out there, but the most important thing to understand is the distinction between checked and unchecked +exceptions. \ No newline at end of file diff --git a/src/exceptions_ii/challenges.md b/src/exceptions_ii/challenges.md new file mode 100644 index 00000000..f2c2d898 --- /dev/null +++ b/src/exceptions_ii/challenges.md @@ -0,0 +1,76 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1 + +Make a method named `parseIntChecked` that works the same as `Integer.parseInt` +but throws a checked exception instead of an unchecked one. + +```java,editable +// CODE HERE + +void main() throws Exception { + int x = parseIntChecked("45"); + IO.println(x); + + parseIntChecked("abc"); +} +``` + +## Challenge 2 + +The following program will not compile. Make it compile by propagating the checked exception +thrown by the `doesSomethingDangerous` method. + +```java,no_run +int doesSomethingDangerous(int value) { + if (value == 1) { + throw new Exception("1 does not work"); + } + return 3 * value + 2; +} + +int compute(int start) { + return square(doesSomethingDangerous(start)); +} + +int square(int x) { + return x * x; +} + +void main() { + int x = Integer.parseInt(IO.readln("Give a starting number: ")); + IO.println(compute(x)); +} +``` + +## Challenge 3 + +The following program is the same as the last one. Instead of propagating the exception, make it so that +`compute` catches and rethrows the checked exception as an unchecked one. + +```java,no_run +int doesSomethingDangerous(int value) throws Exception { + if (value == 1) { + throw new Exception("1 does not work"); + } + return 3 * value + 2; +} + +int compute(int start) { + return square(doesSomethingDangerous(start)); +} + +int square(int x) { + return x * x; +} + +void main() { + int x = Integer.parseInt(IO.readln("Give a starting number: ")); + IO.println(compute(x)); +} +``` \ No newline at end of file diff --git a/src/exceptions_ii/checked_exceptions.md b/src/exceptions_ii/checked_exceptions.md new file mode 100644 index 00000000..4d8ff2e6 --- /dev/null +++ b/src/exceptions_ii/checked_exceptions.md @@ -0,0 +1,24 @@ +# Checked Exceptions + +When a part of your code might throw a "checked" exception, other +parts of your code need to account for that possibility. + +```java,does_not_compile +int divide(int x, int y) { + if (y == 0) { + // Will not work because nothing handles the exception + throw new Exception(); + } + + return x / y; +} +~void main() { +~ +~} +``` + +We call them "checked" because you need to "check" for them. + +These are _generally_[^thisrule] used when you expect calling code to be able to do something intelligent to recover if the exception is thrown. + +[^thisrule]: This rule is merely a suggestion and people's definitions of "something intelligent" and "recover" vary wildly. Expect some things to throw checked exceptions and others to not and just know that you need to check for the checked ones. diff --git a/src/exceptions_ii/exception.md b/src/exceptions_ii/exception.md new file mode 100644 index 00000000..a33829eb --- /dev/null +++ b/src/exceptions_ii/exception.md @@ -0,0 +1,13 @@ +# Exception + +The most vanilla[^vanillacopypasta] checked exception is `Exception`. + +If you want a checked exception and do not know of a better one, `Exception` will do. + +```java,panics +void main() throws Exception { + throw new Exception("Crash!"); +} +``` + +[^vanillacopypasta]: people have the audacity to equate vanilla with β€œplain”. the fruit of a delicate orchid pollinated by hand. worth its weight in solid gold and beyond. the fussy black-and-cream jewel of the american continent. you sick son of a bitch. imagine a world without vanilla. no blondies. no pound cakes. no crΓ¨me brΓ»lΓ©e, no coke floats. no cream soda. no satiny new york-style cheesecakes. no warm apple pie Γ  la mode. no velvety complexity to bring out complex notes in chocolate desserts. no depth of flavour in your cakes and cookies and milkshakes. all in just a few precious seeds or grams of paste or perfumed teaspoons of liquid black platinum. what you don’t understand could fill the library of alexandria seven times over and then some. you ungrateful bastard i’m going to kill you - [Tumblr user "kirbyofthestars"](https://www.tumblr.com/peaceoutofthepieces/684810307611377664) \ No newline at end of file diff --git a/src/exceptions_ii/header.png b/src/exceptions_ii/header.png new file mode 100644 index 00000000..50e15a95 Binary files /dev/null and b/src/exceptions_ii/header.png differ diff --git a/src/exceptions_ii/main.md b/src/exceptions_ii/main.md new file mode 100644 index 00000000..e9091ff7 --- /dev/null +++ b/src/exceptions_ii/main.md @@ -0,0 +1,12 @@ +# main + +The `main` method can be declared to throw any kind of checked exception. + +It is assumed that if an exception makes it all the way there that the way to handle it is crash +the program. + +```java,panics +void main() throws Exception { + throw new Exception("crash"); +} +``` \ No newline at end of file diff --git a/src/exceptions_ii/propagating_exceptions.md b/src/exceptions_ii/propagating_exceptions.md new file mode 100644 index 00000000..bebd84b0 --- /dev/null +++ b/src/exceptions_ii/propagating_exceptions.md @@ -0,0 +1,36 @@ +# Propagating Exceptions + +Say we started out with code like this. + +```java +void dream() { + IO.println("Shin Godzilla's Jaw unhinging like a snake...") +} + +void sleep() { + dream(); +} + +void main() { + sleep(); +} +``` + +If a function is declared to throw an exception, that exception will have to "propagate" - meaning spread - to all calling functions. + +```java +void dream() throws Exception { + throw new Exception("Something went wrong") +} + +void sleep() throws Exception { + dream(); +} + +void main() throws Exception { + sleep(); +} +``` + +`dream` declares that it might throw `Exception` so `sleep` must declare that it might throw `Exception`. Because `sleep` might throw `Exception` now `main` might throw `Exception`. + diff --git a/src/exceptions_ii/rethrowing_exceptions.md b/src/exceptions_ii/rethrowing_exceptions.md new file mode 100644 index 00000000..c1c9835c --- /dev/null +++ b/src/exceptions_ii/rethrowing_exceptions.md @@ -0,0 +1,47 @@ +# Rethrowing Exceptions + +When you catch an exception you have the option of again throwing an exception. + +This is useful if you want to have behavior that occurs when something goes wrong but still +ultimately throw an exception for the rest of the code to deal with.[^examples] + +```java,no_run +void dream() throws Exception { + throw new Exception("Oh no"); +} + +void sleep() throws Exception { + try { + dream(); + } + catch (Exception e) { + IO.println("Something went wrong while dreaming"); + throw e; + } +} +``` + +It is also an opportunity to "wrap" checked exceptions into unchecked ones. + +```java +void dream() throws Exception { + throw new Exception("Oh no"); +} + +void sleep() { + try { + dream(); + } + catch (Exception e) { + throw new RuntimeException(e); + } +} + +void main() { + sleep(); +} +``` + +Which seems crazy, but remember that "the point" of a checked exception is to have the calling code make a choice about how to handle an error condition. If you are okay with just crashing, throwing an unchecked exception is a perfectly valid choice. + +[^examples]: These examples are all silly, I know. Once we get to files the mechanics will become useful. \ No newline at end of file diff --git a/src/exceptions_ii/runtime_exception.md b/src/exceptions_ii/runtime_exception.md new file mode 100644 index 00000000..74b094c9 --- /dev/null +++ b/src/exceptions_ii/runtime_exception.md @@ -0,0 +1,17 @@ +# RuntimeException + +`RuntimeException` is an unchecked exception.[^naming] + +If you want a unchecked exception and do not know of a better one, `RuntimeException` will do. + +```java,panics +void main() { + throw new RuntimeException("Crash!"); +} +``` + +[^naming]: You may be wondering why the names "`Exception`" and "`RuntimeException`" instead of the +probably easier to understand "`CheckedException`" and "`UncheckedException`." Java was made by humans +and initially - as I understand it - "normal" exceptions were expected to be checked. It was only +exceptions that came from "The Java Runtime" that would be unchecked. So that is where the name "`RuntimeException`" comes from. It just very quickly became a choice that was too late to change +without breaking all the Java code in the world. \ No newline at end of file diff --git a/src/exceptions_ii/throws.md b/src/exceptions_ii/throws.md new file mode 100644 index 00000000..1cd2716b --- /dev/null +++ b/src/exceptions_ii/throws.md @@ -0,0 +1,57 @@ +# throws + +One way to handle a checked exception is to add a `throws` to the end +of your method declaration. + +```java +int divide(int x, int y) throws Exception { + if (y == 0) { + throw new Exception(); + } + + return x / y; +} + +~void main() { +~ +~} +``` + +This will make it so that calling code needs to check for the possibility of that +exception being thrown. + +```java,does_not_compile +int divide(int x, int y) throws Exception { + if (y == 0) { + throw new Exception(); + } + + return x / y; +} + +void doStuff() { + divide(1, 0); +} + +~void main() { +~ +~} +``` + +You can also declare unchecked exceptions using `throws` but you are never required to.[^doc] + +```java +int divide(int x, int y) throws RuntimeException { + if (y == 0) { + throw new RuntimeException(); + } + + return x / y; +} + +~void main() { +~ +~} +``` + +[^doc]: This is one of many things that you might choose to do for the benefit of a human reader that isn't strictly needed for correct Java. \ No newline at end of file diff --git a/src/exceptions_ii/unchecked_exceptions.md b/src/exceptions_ii/unchecked_exceptions.md new file mode 100644 index 00000000..065cb886 --- /dev/null +++ b/src/exceptions_ii/unchecked_exceptions.md @@ -0,0 +1,21 @@ +# Unchecked Exceptions + +When a part of your code might throw an "unchecked" exception, other +parts of your code do not need to account for that possibility. + +```java +int divide(int x, int y) { + if (y == 0) { + // Will work because you are not forced to handle + // an unchecked exception + throw new RuntimeException(); + } + + return x / y; +} +~void main() { +~ +~} +``` + +We call them unchecked because you don't need to check for them. \ No newline at end of file diff --git a/src/files.md b/src/files.md new file mode 100644 index 00000000..f9a011ee --- /dev/null +++ b/src/files.md @@ -0,0 +1,11 @@ +# Files + + + +Files are how you store information +on a[^normal] computer so that it can still be there when your program +is done running. + +As such, reading files and writing files are tasks you will often want to do. + +[^normal]: *normal \ No newline at end of file diff --git a/src/files/challenges.md b/src/files/challenges.md new file mode 100644 index 00000000..9d33aa6d --- /dev/null +++ b/src/files/challenges.md @@ -0,0 +1,82 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Write a file named `hello.txt` and give it `Hello, world` +as contents. + +## Challenge 2. + +Write a program that asks the user for a number and writes it into +a file named `numbers.txt`. + +## Challenge 3. + +Update the previous program so that the list of numbers entered by a user +is stored in the file. So if they give `1`, `2`, and `3` the file should contain +something like the following. + +``` +1 +2 +3 +``` + +Hint: `\n` is how you embed a newline character in `String`. You might find it useful. + +## Challenge 4. + +Update the previous program to also display the biggest number given so far +if the user types `biggest` instead of a number. + + +## Challenge 5. + +Make the previous program behave sensibly if the file contains data that is not numbers. + +## Challenge 6. + +Complete this program. + +```java +import java.nio.file.Path; +import java.io.IOException; + +class Main { + record Person(String name, int age) {} + + void main() throws IOException { + var people = new Person[] { + new Person("Steve Smith", 15), + new Person("Stan Smith", 42), + new Person("Rodger", 1601) + }; + + var path = Path.of("people.txt"); + + save(path, people); + + people = load(path); + + IO.println(people[0]); + IO.println(people[1]); + IO.println(people[2]); + + } + + void save(Path path, Person[] people) throws IOException { + // Save to a file + } + + Person[] load(Path path) throws IOException { + return null; // Make actually return an array + } +} +``` + + diff --git a/src/files/creating_a_folder.md b/src/files/creating_a_folder.md new file mode 100644 index 00000000..697e2db0 --- /dev/null +++ b/src/files/creating_a_folder.md @@ -0,0 +1,19 @@ +# Creating a Folder + +To create a folder, you can use `Files.createDirectory`. + +```java +import java.nio.file.Files; +import java.nio.file.Path; + +class Main { + void main() throws IOExeption { + Path folderPath = Path.of("example1"); + Files.createDirectory(folderPath); + } +} +``` + +This, like the other methods in `Files`, might throw an `IOException`. + +`Files.createDirectory` will fail if the folder already exists. diff --git a/src/files/deleting_a_file.md b/src/files/deleting_a_file.md new file mode 100644 index 00000000..ac636a41 --- /dev/null +++ b/src/files/deleting_a_file.md @@ -0,0 +1 @@ +# Delete a File diff --git a/src/files/deleting_a_folder.md b/src/files/deleting_a_folder.md new file mode 100644 index 00000000..2bb51457 --- /dev/null +++ b/src/files/deleting_a_folder.md @@ -0,0 +1 @@ +# Delete a Folder diff --git a/src/files/header.png b/src/files/header.png new file mode 100644 index 00000000..fd815711 Binary files /dev/null and b/src/files/header.png differ diff --git a/src/files/ioexception.md b/src/files/ioexception.md new file mode 100644 index 00000000..e96e72f4 --- /dev/null +++ b/src/files/ioexception.md @@ -0,0 +1,17 @@ +# IOException + +If some code is "doing IO" - by which we mean while it is trying to read some Input or write some Output - you should expect it to throw an `IOException`. + +This class lives in the `java.io` package so to use it by its simple name you need an import. + +```java +import java.io.IOException; + +class Main { + void main() throws IOException { + throw new IOException("Something went wrong"); + } +} +``` + +Since reading a file is reading some input and writing to a file is writing some output, this exception is relevant to reading and writing files. \ No newline at end of file diff --git a/src/files/paths.md b/src/files/paths.md new file mode 100644 index 00000000..17fa2b84 --- /dev/null +++ b/src/files/paths.md @@ -0,0 +1,22 @@ +# Paths + +All files and folders on your computer can be located by a "path" +like "`Downloads/movie.mp4`." + +To store a path in a Java program we use the `Path` class from the `java.nio.file`[^nio] +package. You create these `Path`s by giving a `String` to the `Path.of` +static method. + +```java +import java.nio.file.Path; + +class Main { + void main() { + Path pathToNotes = Path.of("notes.txt"); + + IO.println(pathToNotes); + } +} +``` + +[^nio]: `nio` stands for "new IO." So yes, there was an old IO. We will use that too. Its one of many artifacts from Java's interesting and sordid history. \ No newline at end of file diff --git a/src/files/read_from_a_file.md b/src/files/read_from_a_file.md new file mode 100644 index 00000000..c47ae6a0 --- /dev/null +++ b/src/files/read_from_a_file.md @@ -0,0 +1,58 @@ +# Read from a File + +To read a file's contents as a `String` you can use the `readString` static method +from `java.nio.file.Files`. + +```java +Path tasksPath = Path.of("tasks.txt"); +String tasks = Files.readString(tasksPath); +``` + +Similarly to `Files.writeString`, this can throw an `IOException` if +something goes wrong[^filenotfound]. + +This can be dealt with in the same way. Either declare the `IOException` in a `throws` +clause or re-throw an unchecked exception. + +```java +import java.nio.file.Files; +import java.nio.file.Path; + +class Main { + void main() throws IOExeption { + Path tasksPath = Path.of("tasks.txt"); + String tasks = Files.readString(tasksPath); + + IO.println(tasks); + } +} +``` + +If you choose to re-throw any `IOException` as an unchecked exception then +it might be helpful to remember that delayed assignment is allowed in that context.[^mostly] + +```java +import java.nio.file.Files; +import java.nio.file.Path; + +class Main { + void main() { + Path tasksPath = Path.of("tasks.txt"); + + String tasks; + try { + tasks = Files.readString(tasksPath); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + + IO.println(tasks); + } +} +``` + +[^filenotfound]: Usually we can hand-wave this as "when something goes wrong it throws `IOException`," but this is one of those cases where you might care about what exactly went wrong. Specifically, whether the reason you couldn't read a file because the file wasn't there. We'll use this method as an example when we go deeper into Exceptions +so there will be a chance to talk about that. + +[^mostly]: Mostly just because it can let you "shrink the scope" of the try-catch. \ No newline at end of file diff --git a/src/files/uncheckedioexception.md b/src/files/uncheckedioexception.md new file mode 100644 index 00000000..0a6407ef --- /dev/null +++ b/src/files/uncheckedioexception.md @@ -0,0 +1,26 @@ +# UncheckedIOException + +The unchecked version of an `java.io.IOException` is `UncheckedIOException`. + +You can use this if you have a method which you don't want to propagate `IOException` but +also want something more specific than `RuntimeException` to re-throw. + + +And just like `IOException`, if you don't want to write +out `java.io.UncheckedIOException` more than once you need to add an import. + +```java,no_run +import java.io.IOException; +import java.io.UncheckedIOException; + +class Main { + void main() { + try { + doStuff(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} +``` diff --git a/src/files/write_to_a_file.md b/src/files/write_to_a_file.md new file mode 100644 index 00000000..9f35a270 --- /dev/null +++ b/src/files/write_to_a_file.md @@ -0,0 +1,58 @@ +# Write to a File + +To write a `String` to a file you can use the `Files` class from the `java.nio.file` +package. It has a static method named `writeString`. + +```java,no_run +Path tasksPath = Path.of("tasks.txt"); +String tasks = """ + 1. Do dishes + 2. Do laundry + """; + +Files.writeString(tasksPath, tasks); +``` + +This method can throw an `IOException`, so to handle that you +can have a `throws` declaration on the method calling it. + +```java,no_run +import java.nio.file.Files; +import java.nio.file.Path; + +class Main { + void main() throws IOException { + Path tasksPath = Path.of("tasks.txt"); + String tasks = """ + 1. Do dishes + 2. Do laundry + """; + + Files.writeString(tasksPath, tasks); + } +} +``` + +The other option is to catch the `IOException` and re-throw it as an unchecked exception. + +```java,no_run +import java.nio.file.Files; +import java.nio.file.Path; + +class Main { + void main() { + Path tasksPath = Path.of("tasks.txt"); + String tasks = """ + 1. Do dishes + 2. Do laundry + """; + + try { + Files.writeString(tasksPath, tasks); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} +``` \ No newline at end of file diff --git a/src/files_ii.md b/src/files_ii.md new file mode 100644 index 00000000..29ef6dde --- /dev/null +++ b/src/files_ii.md @@ -0,0 +1 @@ +# Files II diff --git a/src/files_ii/challenges.md b/src/files_ii/challenges.md new file mode 100644 index 00000000..9358534c --- /dev/null +++ b/src/files_ii/challenges.md @@ -0,0 +1 @@ +# Challenges diff --git a/src/files_ii/input_stream.md b/src/files_ii/input_stream.md new file mode 100644 index 00000000..446a71c5 --- /dev/null +++ b/src/files_ii/input_stream.md @@ -0,0 +1 @@ +# InputStream diff --git a/src/files_ii/lines.md b/src/files_ii/lines.md new file mode 100644 index 00000000..a294fde6 --- /dev/null +++ b/src/files_ii/lines.md @@ -0,0 +1 @@ +# Lines diff --git a/src/files_ii/list_directory.md b/src/files_ii/list_directory.md new file mode 100644 index 00000000..f3beb897 --- /dev/null +++ b/src/files_ii/list_directory.md @@ -0,0 +1 @@ +# Paths diff --git a/src/files_ii/list_files.md b/src/files_ii/list_files.md new file mode 100644 index 00000000..5f6e07da --- /dev/null +++ b/src/files_ii/list_files.md @@ -0,0 +1 @@ +# List Files diff --git a/src/files_ii/list_files_in_a_directory.md b/src/files_ii/list_files_in_a_directory.md new file mode 100644 index 00000000..f3beb897 --- /dev/null +++ b/src/files_ii/list_files_in_a_directory.md @@ -0,0 +1 @@ +# Paths diff --git a/src/files_ii/output_stream.md b/src/files_ii/output_stream.md new file mode 100644 index 00000000..3903842d --- /dev/null +++ b/src/files_ii/output_stream.md @@ -0,0 +1 @@ +# OutputStream diff --git a/src/files_ii/paths.md b/src/files_ii/paths.md new file mode 100644 index 00000000..f3beb897 --- /dev/null +++ b/src/files_ii/paths.md @@ -0,0 +1 @@ +# Paths diff --git a/src/files_ii/read_lines.md b/src/files_ii/read_lines.md new file mode 100644 index 00000000..6b6a5b54 --- /dev/null +++ b/src/files_ii/read_lines.md @@ -0,0 +1 @@ +# Read Lines diff --git a/src/files_ii/reader.md b/src/files_ii/reader.md new file mode 100644 index 00000000..284bef67 --- /dev/null +++ b/src/files_ii/reader.md @@ -0,0 +1 @@ +# Reader diff --git a/src/files_ii/writer.md b/src/files_ii/writer.md new file mode 100644 index 00000000..8e619c48 --- /dev/null +++ b/src/files_ii/writer.md @@ -0,0 +1 @@ +# Writer diff --git a/src/first_steps.md b/src/first_steps.md index d8bbf5b8..b5612c4b 100644 --- a/src/first_steps.md +++ b/src/first_steps.md @@ -1,10 +1,14 @@ # First Steps -If you made it through the [Getting Started section](./getting_started/hello_world.md) you've successfully run this program. + + + + +If you made it through the ["Set Up Your Computer" section](./text_editors/set_up_your_computer.md) you've successfully run this program. ```java void main() { - System.out.println("Hello, World!"); + IO.println("Hello, World!"); } ``` @@ -26,21 +30,21 @@ So for all intents and purposes, this is the whole program. ```java ~void main() { -System.out.println("Hello, World!"); +IO.println("Hello, World!"); ~} ``` -This bit of magic here - `System.out.println` - is a "statement" that "prints" the text inside the `(` and `)` as well as a "new line" to the screen. +This bit of magic here - `IO.println` - is a "statement" that "prints" the text inside the `(` and `)` as well as a "new line" to the screen. -`print` with new `l`i`n`e. +The "`IO`" part stands for "Input/Output" then it is **print** with new **l**i**n**e. -If you were to replace it with `System.out.print`, then the output would lack that new line. This makes the following program be functionally identical to the first. +If you were to replace it with `IO.print`, then the output would lack that new line. This makes the following program be functionally identical to the first. ```java ~void main() { -System.out.print("Hello, "); -System.out.print("World"); -System.out.println("!"); +IO.print("Hello, "); +IO.print("World"); +IO.println("!"); ~} ``` @@ -48,9 +52,9 @@ Which, when we add back `void main()`, looks like this. ```java void main() { - System.out.print("Hello, "); - System.out.print("World"); - System.out.println("!"); + IO.print("Hello, "); + IO.print("World"); + IO.println("!"); } ``` @@ -58,4 +62,6 @@ You should get in the habit of, whenever you see some bit of code, trying to phy tweak it, and play with it. So try playing around with this program. If you actively engage then you will retain information better. -So try playing around with this program. If you actively engage then you will retain information better. + +A lot of the code samples in the book are runnable in your browser. Hover +your mouse on the one above. You should see a run button in the top right. \ No newline at end of file diff --git a/src/first_steps/challenges.md b/src/first_steps/challenges.md index 0584a1bc..d22cb2e7 100644 --- a/src/first_steps/challenges.md +++ b/src/first_steps/challenges.md @@ -23,14 +23,14 @@ What will this program output when run? Write down your guess and then try actua ```text void main() { - System.out.println("A"); - //System.out.println("B"); - System.out.println("C");// - System.out.println("D"); + IO.println("A"); + //IO.println("B"); + IO.println("C");// + IO.println("D"); /* - System.out.println("E"); - System.out.println("F");*/ - System.out.println("G"); + IO.println("E"); + IO.println("F");*/ + IO.println("G"); } ``` @@ -42,9 +42,9 @@ How could you use that error to figure out where you might have forgotten to put ```java,editable void main() { - System.out.println("Apple"); - System.out.println("Banana"); - System.out.println("Clementine"); - System.out.println("Durian"); + IO.println("Apple"); + IO.println("Banana"); + IO.println("Clementine"); + IO.println("Durian"); } ``` diff --git a/src/first_steps/comments.md b/src/first_steps/comments.md index 46b12a59..ee5b53d0 100644 --- a/src/first_steps/comments.md +++ b/src/first_steps/comments.md @@ -9,7 +9,7 @@ words. ```java void main() { // This prints hello world! - System.out.println("Hello, World!"); + IO.println("Hello, World!"); } ``` @@ -20,9 +20,9 @@ If you put `//` in front of something that is "code" and not an English explanat ```java void main() { - System.out.println("Hello, World!"); + IO.println("Hello, World!"); // The line that prints out goodbye is "commented out" - // System.out.println("Goodbye!"); + // IO.println("Goodbye!"); } ``` @@ -50,7 +50,7 @@ void main() { so sweet and so cold */ - System.out.println("Hello, World!"); + IO.println("Hello, World!"); } ``` diff --git a/src/first_steps/formatting.md b/src/first_steps/formatting.md index 1bbbaf11..bbe65255 100644 --- a/src/first_steps/formatting.md +++ b/src/first_steps/formatting.md @@ -4,7 +4,7 @@ You may have noticed that after each `{` all the code that comes after it is "in ```java void main() { - System.out.println("Hello, World!"); + IO.println("Hello, World!"); } ``` @@ -18,7 +18,7 @@ This is easier to show than to explain in detail. Just try to make your code loo ```java void main() { - System.out.println("Hello, World!"); + IO.println("Hello, World!"); } ``` @@ -29,7 +29,7 @@ And not like this. ```java void main() { -System.out.println("Hello, World!");} +IO.println("Hello, World!");} ``` And keep in mind that this rule of thumb applies to every language construct that requires a `{` and `}` many of which I will introduce later. diff --git a/src/first_steps/header.png b/src/first_steps/header.png new file mode 100644 index 00000000..b899f11c Binary files /dev/null and b/src/first_steps/header.png differ diff --git a/src/first_steps/semicolon.md b/src/first_steps/semicolon.md index 676991bf..4099b4b2 100644 --- a/src/first_steps/semicolon.md +++ b/src/first_steps/semicolon.md @@ -4,8 +4,8 @@ The `;` at the end of each of those lines is a "semicolon". ```java ~void main(){ -System.out.print("Hello, "); // <-- this thing - // ^ +IO.print("Hello, "); // <-- this thing + // ^ ~} ``` @@ -15,7 +15,7 @@ more complicated than these examples. ```java ~void main(){ -System.out.print( +IO.print( "Hello, " ); ~} @@ -29,7 +29,7 @@ If you happen to have an extra semi-colon or two that is technically okay. It ju ```java ~void main() { -System.out.print( +IO.print( "Hello, " );; ~} @@ -39,7 +39,7 @@ Or even ```java ~void main() { -System.out.print( +IO.print( "Hello, " ); diff --git a/src/floating_point_numbers.md b/src/floating_point_numbers.md index 39d365fb..6ecaf08a 100644 --- a/src/floating_point_numbers.md +++ b/src/floating_point_numbers.md @@ -1,5 +1,8 @@ # Floating Point Numbers + + + Floating point numbers are used to represent numbers which cannot be stored as Integers like `2.5` or `3.14`. diff --git a/src/floating_point_numbers/addition.md b/src/floating_point_numbers/addition.md index d0e4e5a6..e093163f 100644 --- a/src/floating_point_numbers/addition.md +++ b/src/floating_point_numbers/addition.md @@ -8,7 +8,7 @@ double x = 5.1; // y will be 14.2 double y = x + 9.1; -System.out.println(y); +IO.println(y); ~} ``` @@ -22,7 +22,7 @@ Because of the previously mentioned inaccuracy, the results of additions might n // z will be 19.299999999999997 double z = x + y; -System.out.println(z); +IO.println(z); ~} ``` @@ -35,9 +35,9 @@ double y = 4.4; // z will be 9.4 double z = x + y; -System.out.println(x); -System.out.println(y); -System.out.println(z); +IO.println(x); +IO.println(y); +IO.println(z); ~} ``` @@ -51,8 +51,8 @@ double y = 4; // this will not work. The result of the expression is a double. int z = x + y; -System.out.println(x); -System.out.println(y); -System.out.println(z); +IO.println(x); +IO.println(y); +IO.println(z); ~} ``` diff --git a/src/floating_point_numbers/challenges.md b/src/floating_point_numbers/challenges.md index ecf069f5..259b4aac 100644 --- a/src/floating_point_numbers/challenges.md +++ b/src/floating_point_numbers/challenges.md @@ -13,7 +13,7 @@ What will this program output when run? Write down your guess and then try runni void main() { double x = 5.1; double y = 2.4; - System.out.println(x + y); + IO.println(x + y); } ``` @@ -25,7 +25,7 @@ What will this program output when run? Write down your guess and then try runni void main() { double x = 5.1; double y = 2.1; - System.out.println(x + y); + IO.println(x - y); } ``` @@ -38,7 +38,7 @@ How can you make it give the "right" answer? ```java,editable void main() { double x = 5 / 2; - System.out.println(x); + IO.println(x); } ``` @@ -51,8 +51,8 @@ void main() { double resultOne = (int) 5.0 / 2 + 5.0 / 2; double resultTwo = (int) (5.0 / 2 + 5.0 / 2); - System.out.println(resultOne); - System.out.println(resultTwo); + IO.println(resultOne); + IO.println(resultTwo); } ``` @@ -86,8 +86,8 @@ void main() { double resultOne = ???; double resultTwo = ???; - System.out.println(resultOne); - System.out.println(resultTwo); + IO.println(resultOne); + IO.println(resultTwo); } ``` diff --git a/src/floating_point_numbers/comparison.md b/src/floating_point_numbers/comparison.md index 076c068f..13213efb 100644 --- a/src/floating_point_numbers/comparison.md +++ b/src/floating_point_numbers/comparison.md @@ -11,8 +11,8 @@ double x = 1.5; double y = 0.2; // true -System.out.println(x > y); +IO.println(x > y); // false -System.out.println(x < y); +IO.println(x < y); ~} ``` diff --git a/src/floating_point_numbers/conversion_from_integers.md b/src/floating_point_numbers/conversion_from_integers.md index 54e39301..ff10c270 100644 --- a/src/floating_point_numbers/conversion_from_integers.md +++ b/src/floating_point_numbers/conversion_from_integers.md @@ -9,8 +9,8 @@ by Java when performing an assignment. int x = 5; double y = x; -System.out.println(x); -System.out.println(y); +IO.println(x); +IO.println(y); ~} ``` @@ -24,7 +24,7 @@ int y = 2; // integer division of 7 and 2 gives 3. double z = x / y; -System.out.println(z); +IO.println(z); ~} ``` @@ -39,6 +39,6 @@ int y = 2; // so the result will be 3.5. double z = (double) x / y; -System.out.println(z); +IO.println(z); ~} ``` diff --git a/src/floating_point_numbers/conversion_to_integers.md b/src/floating_point_numbers/conversion_to_integers.md index 57f9f3fd..e0dfeb8f 100644 --- a/src/floating_point_numbers/conversion_to_integers.md +++ b/src/floating_point_numbers/conversion_to_integers.md @@ -29,7 +29,7 @@ double x = 5.0; // will be 5 int y = (int) x; -System.out.println(y); +IO.println(y); ~} ``` @@ -42,9 +42,24 @@ int x = (int) 2.1; int y = (int) 2.5; int z = (int) 2.9; -System.out.println(x); -System.out.println(y); -System.out.println(z); +IO.println(x); +IO.println(y); +IO.println(z); +~} +``` + +Negative numbers will also have their decimal part dropped. So numbers like `-7.2`, `-7.6`, and `-7.9` will +all be converted into `-7`. + +```java +~void main() { +int x = (int) -7.2; +int y = (int) -7.6; +int z = (int) -7.9; + +IO.println(x); +IO.println(y); +IO.println(z); ~} ``` @@ -53,11 +68,11 @@ Any number that is too big to store in an `int` will be converted to the biggest ```java ~void main() { // 2147483647 -System.out.println((int) 4207483647.0); +IO.println((int) 4207483647.0); double positiveInfinity = 5.0 / 0.0; // 2147483647 -System.out.println((int) positiveInfinity); +IO.println((int) positiveInfinity); ~} ``` @@ -66,11 +81,11 @@ Any number that is too small to store in an `int` will be converted to the small ```java ~void main() { // -2147483648 -System.out.println((int) -9999999999.0); +IO.println((int) -9999999999.0); double negativeInfinity = -5.0 / 0.0; // -2147483648 -System.out.println((int) negativeInfinity); +IO.println((int) negativeInfinity); ~} ``` @@ -79,7 +94,7 @@ And `NaN` will be converted to zero. ```java ~void main() { double nan = 0.0 / 0.0; -System.out.println((int) nan); +IO.println((int) nan); ~} ``` @@ -88,4 +103,4 @@ a place in the operator precedence order just like `+`, `-`, `==`, etc. The main difference is that instead of appearing between two expressions like the `+` in `2 + 5`, it appears to the left of a single expression. -[^cast]: https://english.stackexchange.com/questions/220001/etymology-of-type-cast +[^cast]: [https://english.stackexchange.com/questions/220001/etymology-of-type-cast](https://english.stackexchange.com/questions/220001/etymology-of-type-cast) diff --git a/src/floating_point_numbers/division.md b/src/floating_point_numbers/division.md index 7563a95c..cd4c5528 100644 --- a/src/floating_point_numbers/division.md +++ b/src/floating_point_numbers/division.md @@ -10,9 +10,9 @@ double y = x / 2; // z will be 1.3333333333333333 double z = y / 3; -System.out.println(x); -System.out.println(y); -System.out.println(z); +IO.println(x); +IO.println(y); +IO.println(z); ~} ``` diff --git a/src/floating_point_numbers/equality.md b/src/floating_point_numbers/equality.md index fa72695b..81876162 100644 --- a/src/floating_point_numbers/equality.md +++ b/src/floating_point_numbers/equality.md @@ -9,7 +9,7 @@ double numberOfFingers = 10.0; boolean humanGenerated = numberOfToes == numberOfFingers; -System.out.println(humanGenerated); +IO.println(humanGenerated); ~} ``` @@ -25,7 +25,27 @@ double z = x + y; // this will be false. boolean doesWhatYouExpect = z == 0.3; -System.out.println(doesWhatYouExpect); +IO.println(doesWhatYouExpect); +~} +``` + +To account for that you can check if a number is "close enough" to another one +by seeing if the "absolute value" of the difference is a small number. + +```java +~void main() { +double x = 0.1; +double y = 0.2; +// z will be 0.30000000000000004 +double z = x + y; + +double compare = 0.3; + +// this will be true. +boolean doesWhatYouExpect = + Math.abs(z - 0.3) < 0.00001; + +IO.println(doesWhatYouExpect); ~} ``` @@ -39,6 +59,6 @@ double y = 5.0; // will be true boolean fiveIsFive = x == y; -System.out.println(fiveIsFive); +IO.println(fiveIsFive); ~} ``` diff --git a/src/floating_point_numbers/header.png b/src/floating_point_numbers/header.png new file mode 100644 index 00000000..678252b1 Binary files /dev/null and b/src/floating_point_numbers/header.png differ diff --git a/src/floating_point_numbers/multiplication.md b/src/floating_point_numbers/multiplication.md index 24c126ea..5d6066a1 100644 --- a/src/floating_point_numbers/multiplication.md +++ b/src/floating_point_numbers/multiplication.md @@ -10,9 +10,9 @@ double y = x * 9; // z will be 13.5 double z = y * 0.5; -System.out.println(x); -System.out.println(y); -System.out.println(z); +IO.println(x); +IO.println(y); +IO.println(z); ~} ``` @@ -23,6 +23,6 @@ multiplication on doubles. So long as any number being used is a `double` the ov ~void main() { // a will be 3.0 double a = 1.5 * 2; -System.out.println(a); +IO.println(a); ~} ``` diff --git a/src/floating_point_numbers/nan.md b/src/floating_point_numbers/nan.md index cf22e188..fdd3f94d 100644 --- a/src/floating_point_numbers/nan.md +++ b/src/floating_point_numbers/nan.md @@ -16,7 +16,7 @@ double nan = 0.0 / 0.0; // will be false boolean equalToItself = nan == nan; -System.out.println(equalToItself); +IO.println(equalToItself); ~} ``` @@ -28,7 +28,7 @@ System.out.println(equalToItself); // will be false boolean greaterThanItself = nan > nan; -System.out.println(greaterThanItself); +IO.println(greaterThanItself); ~} ``` @@ -40,7 +40,7 @@ System.out.println(greaterThanItself); // will be false boolean lessThanItself = nan < nan; -System.out.println(lessThanItself); +IO.println(lessThanItself); ~} ``` @@ -50,9 +50,9 @@ System.out.println(lessThanItself); ~void main() { ~double nan = 0.0 / 0.0; // will all be false -System.out.println(nan < 5); -System.out.println(nan > 5); -System.out.println(nan == 5); +IO.println(nan < 5); +IO.println(nan > 5); +IO.println(nan == 5); ~} ``` diff --git a/src/floating_point_numbers/positive_and_negative_infinity.md b/src/floating_point_numbers/positive_and_negative_infinity.md index d87468e3..570c544e 100644 --- a/src/floating_point_numbers/positive_and_negative_infinity.md +++ b/src/floating_point_numbers/positive_and_negative_infinity.md @@ -22,10 +22,10 @@ As you might expect, positive infinity is greater than any number and negative i ~double positiveInfinity = 1.0 / 0.0; ~double negativeInfinity = -1.0 / 0.0; // true -System.out.println(positiveInfinity > 99999999); +IO.println(positiveInfinity > 99999999); // true -System.out.println(negativeInfinity < -99999999); +IO.println(negativeInfinity < -99999999); ~} ``` @@ -38,9 +38,9 @@ Except for `NaN`, of course. double nan = 0.0 / 0.0; // false -System.out.println(positiveInfinity > nan); +IO.println(positiveInfinity > nan); // false -System.out.println(negativeInfinity < nan); +IO.println(negativeInfinity < nan); ~} ``` diff --git a/src/floating_point_numbers/shorthands_for_reassignment.md b/src/floating_point_numbers/shorthands_for_reassignment.md index 42ece4cd..d57d5744 100644 --- a/src/floating_point_numbers/shorthands_for_reassignment.md +++ b/src/floating_point_numbers/shorthands_for_reassignment.md @@ -6,30 +6,30 @@ All the same shorthands for reassignment work with `double`s the same as they do ~void main() { double x = 0.5; // 0.5 -System.out.println(x); +IO.println(x); x += 3; // 3.5 -System.out.println(x); +IO.println(x); x -= 1; // 2.5 -System.out.println(x); +IO.println(x); x++; // 3.5 -System.out.println(x); +IO.println(x); x--; // 2.5 -System.out.println(x); +IO.println(x); x *= 5; // 12.5 -System.out.println(x); +IO.println(x); x /= 2; // 6.25 -System.out.println(x); +IO.println(x); ~} ``` diff --git a/src/floating_point_numbers/square_root.md b/src/floating_point_numbers/square_root.md index dca62212..82f49565 100644 --- a/src/floating_point_numbers/square_root.md +++ b/src/floating_point_numbers/square_root.md @@ -11,7 +11,7 @@ double x = 4; double y = Math.sqrt(x); // This will output 2 -System.out.println(y); +IO.println(y); ~} ``` @@ -24,7 +24,7 @@ double y = 13; double z = Math.sqrt(9 * x + y); // This will output 7.615773105863909 -System.out.println(z); +IO.println(z); ~} ``` @@ -33,6 +33,6 @@ If you try to take the square root of a negative number, the result will be `NaN ```java ~void main() { // will output NaN -System.out.println(Math.sqrt(-5.2)); +IO.println(Math.sqrt(-5.2)); ~} ``` diff --git a/src/floating_point_numbers/subtraction.md b/src/floating_point_numbers/subtraction.md index cc89f7bb..4e535206 100644 --- a/src/floating_point_numbers/subtraction.md +++ b/src/floating_point_numbers/subtraction.md @@ -5,11 +5,11 @@ You can subtract any two `double`s using the `-` operator. ```java ~void main() { double x = 5.1; -// y will be 4.1 +// y will be -4.1 double y = x - 9.2; -System.out.println(x); -System.out.println(y); +IO.println(x); +IO.println(y); ~} ``` @@ -18,12 +18,12 @@ Because of the previously mentioned inaccuracy, the results of subtractions migh ```java ~void main() { ~double x = 5.1; -~// y will be 4.1 +~// y will be -4.1 ~double y = x - 9.2; // z will be -4.199999999999999 double z = y - 0.1; -System.out.println(z); +IO.println(z); ~} ``` @@ -36,9 +36,9 @@ double y = 4.5; // z will be 0.5 double z = x - y; -System.out.println(x); -System.out.println(y); -System.out.println(z); +IO.println(x); +IO.println(y); +IO.println(z); ~} ``` diff --git a/src/floating_point_numbers_ii.md b/src/floating_point_numbers_ii.md new file mode 100644 index 00000000..57ad3ca3 --- /dev/null +++ b/src/floating_point_numbers_ii.md @@ -0,0 +1 @@ +# Floating Point Numbers II diff --git a/src/generic_growable_arrays.md b/src/generic_growable_arrays.md new file mode 100644 index 00000000..9b5525a8 --- /dev/null +++ b/src/generic_growable_arrays.md @@ -0,0 +1 @@ +# Generic Growable Arrays diff --git a/src/generic_growable_arrays/arraylist.md b/src/generic_growable_arrays/arraylist.md new file mode 100644 index 00000000..6b95c2f0 --- /dev/null +++ b/src/generic_growable_arrays/arraylist.md @@ -0,0 +1 @@ +# ArrayList diff --git a/src/generic_hash_maps/hash_map b/src/generic_hash_maps/hash_map new file mode 100644 index 00000000..96e549de --- /dev/null +++ b/src/generic_hash_maps/hash_map @@ -0,0 +1 @@ +# HashMap diff --git a/src/generics.md b/src/generics.md new file mode 100644 index 00000000..14ca196f --- /dev/null +++ b/src/generics.md @@ -0,0 +1,17 @@ +# Generics + + + +Certain types of classes, like growable arrays, are simply holders of data. + +That is, almost none of how they work has to change to +store different kinds of data. + +Generics help us make these generically useful containers. + +```java +class Box { + T value; +} +``` + diff --git a/src/generics/challenges.md b/src/generics/challenges.md new file mode 100644 index 00000000..f1659abb --- /dev/null +++ b/src/generics/challenges.md @@ -0,0 +1,89 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + + + +## Challenge 1. + + +Will the following code work? Why or why not? + +```java +class Thing { + T value; +} + +class Cool { +} +class NotCool { +} + +class Main { + void main() { + int n = Integer.parseInt( + IO.readln("Give a number: ") + ); + + Thing o = (n % 2 == 0) + ? new Thing() + : new Thing(); + + } +} +``` + +## Challenge 2. + +Make a class that holds a `NotNull` value. This class should be generic over the kind of +data that it holds but it should throw an exception if the provided `value` is `null`. + +```java,editable +// CODE HERE + +class Main { + void main() { + NotNull s = new NotNull<>("abc"); + IO.println(s.value); + + NotNull i = new NotNull<>(123); + IO.println(i.value); + + // This should throw an exception + // NotNull d = new NotNull<>(null); + } +} +``` + + +## Challenge 3. + +The following class has 5 generic parameters. + +Correct the ones which do not follow expected naming conventions + + +```java,editable +class Organism { + name name; + h h; + T t; + Cat cat; + inch_worm inchWorm; +} + +class Main { + void main() { + var o = new Organism(); + o.name = "abc"; + o.h = 123; + o.t = 5; + o.cat = "..."; + o.inchWorm = "\\_/-\\--(*)"; + } +} +``` + diff --git a/src/generics/header.png b/src/generics/header.png new file mode 100644 index 00000000..a6a567b1 Binary files /dev/null and b/src/generics/header.png differ diff --git a/src/generics/inference.md b/src/generics/inference.md new file mode 100644 index 00000000..99fc1321 --- /dev/null +++ b/src/generics/inference.md @@ -0,0 +1,45 @@ +# Inference + +Sometimes Java has enough information to know +what the right values for type variables would be without you +specifying them. + +In these cases you can rely on the compiler to infer them by writing `<>` with no type variables +in between. + +```java +class Box { + T data; +} + +void main() { + // It is being assigned to a Box on the left + // so Java can figure out what should be on the right. + Box boxOfString = new Box<>(); + + boxOfString.data = "abc"; + + String s = boxOfString.data; + + IO.println(s); +} +``` + +This inference does not work on the left-hand side of an `=`. + +```java,does_not_compile +class Box { + T data; +} + +void main() { + // Use var if you want inference for local variables. + Box<> boxOfString = new Box(); + + boxOfString.data = "abc"; + + String s = boxOfString.data; + + IO.println(s); +} +``` \ No newline at end of file diff --git a/src/generics/instantiation.md b/src/generics/instantiation.md new file mode 100644 index 00000000..485f422a --- /dev/null +++ b/src/generics/instantiation.md @@ -0,0 +1,23 @@ +# Instantiation + +When you make an instance of a class that takes generic parameters +you are expected to provide the actual concrete types you want to be used +in place of any type variables. + +You do this by writing the name of the types inside a `<>` alongside the call to `new`. + +```java +class Box { + T data; +} + +void main() { + var boxOfString = new Box(); + + boxOfString.data = "abc"; + + String s = boxOfString.data; + + IO.println(s); +} +``` \ No newline at end of file diff --git a/src/generics/naming.md b/src/generics/naming.md new file mode 100644 index 00000000..da1ce4b0 --- /dev/null +++ b/src/generics/naming.md @@ -0,0 +1,10 @@ +# Naming + +Type variables don't have to be only a single letter, though that is common. If you pick a longer name +you are generally expected to name them as if they were classes. + +```java +class Box { + Data data; +} +``` \ No newline at end of file diff --git a/src/generics/raw_types.md b/src/generics/raw_types.md new file mode 100644 index 00000000..ebf803e7 --- /dev/null +++ b/src/generics/raw_types.md @@ -0,0 +1,50 @@ +# Raw Types + +If generics are cramping your style, you are +technically allowed to turn them off. + +If you make an instance of a generic class without +any `<>` we call that a "raw type." + +```java +class Box { + T data; +} + +void main() { + Box b = new Box(); +} +``` + +When you have a raw type you will see `Object` in any place +you put[^unbounded] a type variable. + +This lets you do whatever you want without the burden of having to make sense to Java. + +```java +class Box { + T data; +} + +void main() { + Box b = new Box(); + b.data = 123; + b.data = "abc"; + + if (b.data instanceof String s) { + IO.println(s); + } +} +``` + +Raw types exist for two basic reasons + +1. Every now and then Java isn't smart enough. Trust that there are valid reasons to turn off generics, even if I haven't shown you any yet. Avoid doing so yourself - at least for awhile. +2. Generics weren't always in Java! Classes that later were made generic had to stay compatible with old "raw" +usages somehow. + +All that is to say: Be aware raw types exist. Make sure you are always putting `<>` otherwise you are falling +into that mode. Avoid that mode. + + +[^unbounded]: An "unbounded" type variable to be exact. We'll visit generic bounds later. \ No newline at end of file diff --git a/src/generics/soundness.md b/src/generics/soundness.md new file mode 100644 index 00000000..6f4f00a5 --- /dev/null +++ b/src/generics/soundness.md @@ -0,0 +1,22 @@ +# Soundness + +Even though every `String` is assignable to `Object`, +a `Box` is not assignable to `Box`. + +If that was allowed then you could do something like the following. + +```java,no_run,does_not_compile +Box stringBox = new Box<>(); +// This might feel innocent +Box objectBox = stringBox; +// But now we can put anything, +// like an Integer, in the Box +objectBox.data = 123; +// Which is a problem since that affects +// stringBox as well +String s = stringBox.data; // But its actually an Integer! Crash! +``` + +We call this property - that the types don't let you accidentally make +silly situations - soundness. Java isn't _fully_ sound, but its sound enough +for most people. \ No newline at end of file diff --git a/src/generics/type_variables.md b/src/generics/type_variables.md new file mode 100644 index 00000000..f1144fcd --- /dev/null +++ b/src/generics/type_variables.md @@ -0,0 +1,25 @@ +# Type Variables + +After the name of a class in the class definition you can put one or more "type variables." +These look like `<` followed by a comma separated list of "type names" and ended by a `>`. + +```java,no_run +class Box { + +} +``` + +Inside a class definition you can use these type variables on fields, method arguments, +and method return types. + +```java,no_run +class Box { + T data; + + void setData(T newData) { + this.data = newData; + } +} +``` + +This indicates that you would be okay with any type being used in place of the type variable (in the above code `T`). diff --git a/src/getting_started.md b/src/getting_started.md index 4b356a49..26b98e76 100644 --- a/src/getting_started.md +++ b/src/getting_started.md @@ -1,6 +1,21 @@ # Getting Started -There are a lot of ways to "get set up" to run Java code. +There are a lot of ways to "get set up" to run Java code. + + + +For the first chunk of this book it will only matter that you have the +ability to run Java code. The guides below will be aiming to get you +to that point. + +After the section + +## Chromebooks / Mobile Phones + + + +One is to get set up "for real." This means Java is fully set up on your system +and all the comman For at least the first chunk of this, you should be able to get away with using the editor on [https://run.mccue.dev](https://run.mccue.dev). That might be the easiest. @@ -11,71 +26,31 @@ edit the following code. ```java void main() { - System.out.println("Hello, World"); + IO.println("Hello, World"); } ``` -## repl.it - -[replit.com](https://replit.com) is a pretty common choice for teachers because -they will be able to give you assignments and have you share back your results. -It is also a decent option if your school only provides you with Chromebooks -or similar. - -It requires an internet connection and you will have to make an account, but -otherwise it is fairly convenient. - -If you are in school and your teacher has helped you get set up in some other -way it is okay to skip this section and just do it the way you were shown. - - -### Step 1. Make an account - -Go to [replit.com](https://replit.com) and find the "Sign Up" button. -Websites change every now and then so these screenshots might be out of date. - -Picture of the sign up button on replit's website - -Click it and sign up for an account. - -Picture of the sign up form on replit's website +## Windows -### Step 2. Create a Java REPL +Download the "JDK MSI" from [adoptium.net](https://adoptium.net/temurin/releases/?version=25&os=windows). -Find the "Create REPL" button and click it. +Run the installer, selecting all the default options. -Picture of the create repl button on replit's website +Then you should download -Then you should be presented with a menu that lets you search for the type of REPL to create. -Find the Java template and click "Create". -Unfilled in create from template menu on replit +## Mac OS -Filled in create from template menu on replit +Download the "JDK .pkg" from [adoptium.net](https://adoptium.net/temurin/releases/?version=25&os=mac). -### Step 3. Run code +Run the installer, selecting all the default options. -You should land on a screen with a big green run button, an open file called -"Main.java", and a blank window labeled "console". - +## Linux -Click it and you should see the text `Hello, world!` appear under the console window. +Linux is a little annoying. If you are using it you are likely used to it +by now, but you can use [adoptium.net](https://adoptium.net/temurin/releases/?version=25&os=linux) like everyone else, but there is no universal installer there. - +You can either download the `.tar.gz` file that matches your machine, extract it, +and add the `bin` folder to your `PATH`, or you can try to find an installer for your +specific linux distribution. diff --git a/src/getting_started/repl_1.png b/src/getting_started/repl_1.png deleted file mode 100644 index 6ebb1222..00000000 Binary files a/src/getting_started/repl_1.png and /dev/null differ diff --git a/src/getting_started/repl_2.png b/src/getting_started/repl_2.png deleted file mode 100644 index d8d9e5b9..00000000 Binary files a/src/getting_started/repl_2.png and /dev/null differ diff --git a/src/getting_started/repl_3.png b/src/getting_started/repl_3.png deleted file mode 100644 index 4c3a36cd..00000000 Binary files a/src/getting_started/repl_3.png and /dev/null differ diff --git a/src/getting_started/repl_4.png b/src/getting_started/repl_4.png deleted file mode 100644 index 01817426..00000000 Binary files a/src/getting_started/repl_4.png and /dev/null differ diff --git a/src/getting_started/repl_4_voidmain.png b/src/getting_started/repl_4_voidmain.png deleted file mode 100644 index 10b12687..00000000 Binary files a/src/getting_started/repl_4_voidmain.png and /dev/null differ diff --git a/src/getting_started/repl_5.png b/src/getting_started/repl_5.png deleted file mode 100644 index d6d2c030..00000000 Binary files a/src/getting_started/repl_5.png and /dev/null differ diff --git a/src/getting_started/repl_5_voidmain.png b/src/getting_started/repl_5_voidmain.png deleted file mode 100644 index 598700ec..00000000 Binary files a/src/getting_started/repl_5_voidmain.png and /dev/null differ diff --git a/src/getting_started/repl_signup_0.png b/src/getting_started/repl_signup_0.png deleted file mode 100644 index cc0fec9d..00000000 Binary files a/src/getting_started/repl_signup_0.png and /dev/null differ diff --git a/src/getting_started/repl_signup_1.png b/src/getting_started/repl_signup_1.png deleted file mode 100644 index 1c0a0ec9..00000000 Binary files a/src/getting_started/repl_signup_1.png and /dev/null differ diff --git a/src/global_fields.md b/src/global_fields.md new file mode 100644 index 00000000..15b55a2b --- /dev/null +++ b/src/global_fields.md @@ -0,0 +1,23 @@ +# Global Fields + + + + +You can make field declarations outside of a class. +This makes these fields "global" to the program. + +We call things global when they are available at every point +in the "world" of our program.[^wonthold] + +```java +int number = 0; + +void main() { + IO.println(number); + number++; + IO.println(number); +} +``` + +[^wonthold]: This explanation is I think a correct description of what it means +to be global, but what I am showing won't really hold up as a global thing the more you learn. \ No newline at end of file diff --git a/src/global_fields/challenges.md b/src/global_fields/challenges.md new file mode 100644 index 00000000..bd60e726 --- /dev/null +++ b/src/global_fields/challenges.md @@ -0,0 +1,83 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1 + +Write a method named `anothaOne`. Each time this method is +called it should return a number one larger than the last time +it was called. + +`anothaOne` should take no arguments. + +```java,editable +// CODE HERE + +int anothaOne() { + // CODE HERE +} + +void main() { + IO.println(anothaOne()); // 1 + IO.println(anothaOne()); // 2 + IO.println(anothaOne()); // 3 + IO.println(anothaOne()); // 4 + IO.println(anothaOne()); // 5 +} +``` + +## Challenge 2 + +Make a class named `DJKhaled`. If you ask DJ Khaled to `produceMusic` +some lyrics should be printed out but also `anothaOne` should be called at least 7 times.[^drake] + + +```java,editable +// CODE FROM LAST SECTION + +class DJKhaled { + void produceMusic() { + // CODE HERE + } +} + +void main() { + IO.println(anothaOne()); // 1 + var dj = new DJKhaled(); + dj.produceMusic(); + IO.println(anothaOne()); // 8+, at least +} +``` + +## Challenge 3 + +Make a method named `nextLyric`. It should take no arguments and return a `String`. +Each time it is called it should return a subsequent line from [Kendrick Lamar's 2025 Superbowl +Halftime Show](https://www.youtube.com/watch?v=KDorKy-13ak). + +You should be able to find a transcription of the lyrics [here](https://genius.com/Kendrick-lamar-and-nfl-super-bowl-lix-halftime-show-lyrics). + +Once all the lyrics are exhausted `nextLyric` should start returning `null`. + +```java,editable +// CODE HERE + +String nextLyric() { + // CODE HERE +} + +void main() { + for ( + String lyric = nextLyric(); + lyric != null; + lyric = nextLyric() + ) { + IO.println(lyric); + } +} +``` + +[^drake]: We are just going to have fun with the "anotha one" meme here, lets not think too hard about DJ Khaled's relationship with Drake. \ No newline at end of file diff --git a/src/global_fields/default_values.md b/src/global_fields/default_values.md new file mode 100644 index 00000000..9c27322d --- /dev/null +++ b/src/global_fields/default_values.md @@ -0,0 +1,14 @@ +# Default Values + +Just like regular fields in classes, global fields will initialize to their default +value unless explicitly initialized. + +```java +int x; +int y = 5; + +void main() { + IO.println(x); + IO.println(y); +} +``` diff --git a/src/global_fields/field_access.md b/src/global_fields/field_access.md new file mode 100644 index 00000000..76c054cd --- /dev/null +++ b/src/global_fields/field_access.md @@ -0,0 +1,25 @@ +# Field Access + + +You can access global fields by writing their name. This works from within +top level methods as well as instance methods of classes.[^different] + +```java +final String monster = "Dracula"; + +class Mash { + void itWasThe() { + IO.println(monster + " and his son"); + } +} + +void main() { + IO.println(monster + " was there"); + + Mash mash = new Mash(); + mash.itWasThe(); +} +``` + +[^different]: This is so convenient it is actually going to be a bummer once you learn what is going on here +and can't do it for most of your programs anymore. \ No newline at end of file diff --git a/src/global_fields/final_fields.md b/src/global_fields/final_fields.md new file mode 100644 index 00000000..fe7bdced --- /dev/null +++ b/src/global_fields/final_fields.md @@ -0,0 +1,14 @@ +# Final Fields + +And just like regular final fields, you always need to give an initial value to +global final fields. + +```java +final int x = 0; + +void main() { + IO.println(x); +} +``` + +Final fields of course cannot be changed. \ No newline at end of file diff --git a/src/global_fields/header.png b/src/global_fields/header.png new file mode 100644 index 00000000..248799a4 Binary files /dev/null and b/src/global_fields/header.png differ diff --git a/src/global_fields/inferred_types.md b/src/global_fields/inferred_types.md new file mode 100644 index 00000000..cc514d21 --- /dev/null +++ b/src/global_fields/inferred_types.md @@ -0,0 +1,12 @@ +# Inferred Types + +Just like other field declarations, `var` cannot be used +for inferring the type of a global field. You need to explicitly write out the type. + +```java,does_not_compile +var x = 5; + +void main() { + IO.println(x); +} +``` \ No newline at end of file diff --git a/src/growable_arrays.md b/src/growable_arrays.md new file mode 100644 index 00000000..ad736d3b --- /dev/null +++ b/src/growable_arrays.md @@ -0,0 +1,17 @@ +# Growable Arrays + + + +Arrays are fixed size collections of elements. This means when we make an array +that is 5 elements big it will always be 5 elements big. + +```java +~void main() { +int[] numbers = new int[5]; +~} +``` + +Something that turns out to be extremely useful is to have something with most of the properties of an array - such as being able quickly get and set arbitrary elements by index - but that can grow over time. + +The rest of this section I will walk you through how we can accomplish that. + diff --git a/src/growable_arrays/challenges.md b/src/growable_arrays/challenges.md new file mode 100644 index 00000000..b7bef59d --- /dev/null +++ b/src/growable_arrays/challenges.md @@ -0,0 +1,27 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Make your own implementation of a growable array that holds `String`s and not +`int`s. + +Validate that `.size`, `.add`, `.get`, `.set`, etc. all work as you would expect. + +## Challenge 2. + +Go back to your calorie tracker program from that project. +Make use of a growable array of some kind to keep the full history +of calorie amounts entered by the user. + +## Challenge 3. + +When implementing the point of sale system project you may or may not have +gone down the path of discovering growable arrays for yourself. Either way +refactor that project to make use of a growable array to store the items +ordered for the purpose of making a receipt. + diff --git a/src/growable_arrays/concept.md b/src/growable_arrays/concept.md new file mode 100644 index 00000000..03dd7323 --- /dev/null +++ b/src/growable_arrays/concept.md @@ -0,0 +1,10 @@ +# Concept + +The concept behind a growable array is that we +store an array as a field and use it for operations like getting +and setting elements. + +The extra wrinkle is that when someone wants to "add" an element +we make a new, bigger, array and copy all the existing elements to it. +Then we put the element you wanted to add at the end. + diff --git a/src/growable_arrays/header.png b/src/growable_arrays/header.png new file mode 100644 index 00000000..9a41a189 Binary files /dev/null and b/src/growable_arrays/header.png differ diff --git a/src/growable_arrays/optimized_implementation.md b/src/growable_arrays/optimized_implementation.md new file mode 100644 index 00000000..38ce10d0 --- /dev/null +++ b/src/growable_arrays/optimized_implementation.md @@ -0,0 +1,65 @@ +# Optimized Implementation + +So with that context we can update the simple implementation as follows. + +Note the use of `IndexOutOfBoundsException`. It is an unchecked exception that +comes with Java specifically for when an index is out of bounds.[^obvious] + +```java +class GrowableIntArray { + private int[] data; + // We need a different variable to store the size + // since the array's size will different than we want. + private int size; + + GrowableIntArray() { + this.data = new int[0]; + this.size = 0; + } + + int get(int index) { + // Now that we manage the size, we need to do + // "bounds checks" ourselves + if (index >= size) { + throw new IndexOutOfBoundsException(index); + } + return this.data[index]; + } + + void set(int index, int value) { + // For setting as well + if (index >= size) { + throw new IndexOutOfBoundsException(index); + } + this.data[index] = value; + } + + int size() { + // We use the size we store rather than the length of the array + return this.size; + } + + void add(int value) { + // if we won't have enough room in the array, make a new one. + if (size >= this.data.length - 1) { + // Overallocate by 2x + int newLength = this.data.length * 2; + if (newLength == 0) { + newLength = 2; // Unfortunately 0 * 2 is 0, so account for that + } + + int[] newArray = new int[newLength]; + for (int i = 0; i < this.data.length; i++) { + newArray[i] = this.data[i]; + } + this.data = newArray; + } + + // And at this point we know the array is big enough + this.data[size] = value; + this.size++; + } +} +``` + +[^obvious]: I love obvious and circular statements like this, if you haven't noticed. \ No newline at end of file diff --git a/src/growable_arrays/performance_problems.md b/src/growable_arrays/performance_problems.md new file mode 100644 index 00000000..c138de92 --- /dev/null +++ b/src/growable_arrays/performance_problems.md @@ -0,0 +1,73 @@ +# Performance Problems + +While the code as is _works_, it will not perform well. + +Since we copy the underlying array every time someone adds a new element it can be expensive to add a lot of elements. + +Pretend we were going to add a hundred elements to the growable array. + +```java +~class GrowableIntArray { +~ // Store an int[] internally +~ private int[] data; +~ +~ GrowableIntArray() { +~ // Make sure to initialize it correctly +~ this.data = new int[0]; +~ } +~ +~ // When someone wants to get an item, get it from the array +~ int get(int index) { +~ return this.data[index]; +~ } +~ +~ // Same deal when someone wants to set an item at an index. +~ void set(int index, int value) { +~ this.data[index] = value; +~ } +~ +~ // And we need an accessor for the size so that someone +~ // can loop over the array. +~ int size() { +~ return this.data.length; +~ } +~ +~ void add(int value) { +~ // Copy the old array to a new, bigger one +~ int[] newArray = new int[this.data.length + 1]; +~ for (int i = 0; i < this.data.length; i++) { +~ newArray[i] = this.data[i]; +~ } +~ +~ // Then put the new element at the end +~ newArray[newArray.length - 1] = value; +~ +~ // And swap the array +~ this.data = newArray; +~ } +~} +~ +~ +class Main { + void main() { + var array = new GrowableIntArray(); + + for (int i = 0; i < 100; i++) { + array.add(i); + } + + IO.println(array.size()); + } +} +``` + +For the each element we need to make a copy of an array. So when we add the first element we need to make an array 1 element big. The second, copy that 1 element array and make a new +2 element one. + +So if you do napkin math on the things that need to happen you get this + +``` +1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 ... +``` + +Which is just a crazy number of copies. It means calling `.add` on an already big list will be slow and calling `.add` a lot of times to make a big list will be very slow. \ No newline at end of file diff --git a/src/growable_arrays/performance_solutions.md b/src/growable_arrays/performance_solutions.md new file mode 100644 index 00000000..cf1ef3ec --- /dev/null +++ b/src/growable_arrays/performance_solutions.md @@ -0,0 +1,14 @@ +# Performance Solutions + +If the problem with the simple implementation is that we make too many copies, +the solution is to make fewer copies. + +The easiest way to do this is to "over-allocate." Make our internal array bigger than +we actually need it to be. That way most of the time when you call `.add` it will be +fast. + +We can't - or at least shouldn't - over allocate right away. If every growable array secretly has enough room for millions of elements that would be silly. Better to over-allocate as we go. + +Exactly how much we should make the internal array bigger than we need is more art than science. People have found that doubling the size each time is a pretty good tradeoff[^important]. + +[^important]: This data structure is crazy important. Maybe the most common one used in the world. Java has it built-in and we'll get to that later. \ No newline at end of file diff --git a/src/growable_arrays/simple_implementation.md b/src/growable_arrays/simple_implementation.md new file mode 100644 index 00000000..0cba1418 --- /dev/null +++ b/src/growable_arrays/simple_implementation.md @@ -0,0 +1,48 @@ +# Simple Implementation + +Following the previous description to the letter will get you +something like this. + +Take some time to read it through. + +```java +class GrowableIntArray { + // Store an int[] internally + private int[] data; + + GrowableIntArray() { + // Make sure to initialize it correctly + this.data = new int[0]; + } + + // When someone wants to get an item, get it from the array + int get(int index) { + return this.data[index]; + } + + // Same deal when someone wants to set an item at an index. + void set(int index, int value) { + this.data[index] = value; + } + + // And we need an accessor for the size so that someone + // can loop over the array. + int size() { + return this.data.length; + } + + void add(int value) { + // Copy the old array to a new, bigger one + int[] newArray = new int[this.data.length + 1]; + for (int i = 0; i < this.data.length; i++) { + newArray[i] = this.data[i]; + } + + // Then put the new element at the end + newArray[newArray.length - 1] = value; + + // And swap the array + this.data = newArray; + } +} +``` \ No newline at end of file diff --git a/src/growable_arrays/usage.md b/src/growable_arrays/usage.md new file mode 100644 index 00000000..dcdea5bd --- /dev/null +++ b/src/growable_arrays/usage.md @@ -0,0 +1,65 @@ +# Usage + +Using the class we defined would look something like this. + +```java +~class GrowableIntArray { +~ // Store an int[] internally +~ private int[] data; +~ +~ GrowableIntArray() { +~ // Make sure to initialize it correctly +~ this.data = new int[0]; +~ } +~ +~ // When someone wants to get an item, get it from the array +~ int get(int index) { +~ return this.data[index]; +~ } +~ +~ // Same deal when someone wants to set an item at an index. +~ void set(int index, int value) { +~ this.data[index] = value; +~ } +~ +~ // And we need an accessor for the size so that someone +~ // can loop over the array. +~ int size() { +~ return this.data.length; +~ } +~ +~ void add(int value) { +~ // Copy the old array to a new, bigger one +~ int[] newArray = new int[this.data.length + 1]; +~ for (int i = 0; i < this.data.length; i++) { +~ newArray[i] = this.data[i]; +~ } +~ +~ // Then put the new element at the end +~ newArray[newArray.length - 1] = value; +~ +~ // And swap the array +~ this.data = newArray; +~ } +~} +~ +~ +class Main { + void main() { + // To start we don't know how many elements there are + var array = new GrowableIntArray(); + + // Each time we add an element the array "grows" + array.add(1); + array.add(2); + array.add(3); + + // And we can loop over it like so + for (int i = 0; i < array.size(); i++) { + IO.println(array.get(i)); + } + } +} +``` + +Important to note that while we see it as if it was a growable array, no actual arrays are "growing." We are just faking it. \ No newline at end of file diff --git a/src/hardware.md b/src/hardware.md new file mode 100644 index 00000000..e0f646cd --- /dev/null +++ b/src/hardware.md @@ -0,0 +1,10 @@ +# Hardware + + +Before we get into Java, It might be helpful to take a detour into +general computing knowledge. + +I won't be offended if you skim or skip this and the next section. Depending on who you are, +this might be a bit obvious. + +First lets cover hardware. The actual physical components that make up your computer. \ No newline at end of file diff --git a/src/hardware/cpu.md b/src/hardware/cpu.md new file mode 100644 index 00000000..19eb9196 --- /dev/null +++ b/src/hardware/cpu.md @@ -0,0 +1,9 @@ +# CPU + +At the core of every computer is a CPU. + +This stands for "central processing unit." This is the rock that wizards taught how to think. + +If you send the right electrical signals into a CPU it can do all sorts of math and it can do it quickly. + +Inside a computer this looks like a little metal square. \ No newline at end of file diff --git a/src/hardware/hard_drives.md b/src/hardware/hard_drives.md new file mode 100644 index 00000000..28f19107 --- /dev/null +++ b/src/hardware/hard_drives.md @@ -0,0 +1,15 @@ +# Hard Drives + +RAM sticks have a few issues in the memory department. + +First, they can only hold around 8-64 gigabytes of +info. Any more than that and they can get pretty slow.[^latency] + +Second, when you turn off the computer they lose anything they are storing. This is a problem when +you are writing an essay and need to turn off the computer half way through. + +Hard drives are like RAM in that they remember stuff. The difference is that hard drives +can store a lot more of it (terrabytes) and the data isn't lost when power is turned off. + +[^latency]: The key word here is "latency". When the CPU needs info in RAM it needs to wait for RAM +to find it. It takes longer to find something in a bigger pile of stuff, no matter how smart you are about it. \ No newline at end of file diff --git a/src/hardware/header.png b/src/hardware/header.png new file mode 100644 index 00000000..d69100c5 Binary files /dev/null and b/src/hardware/header.png differ diff --git a/src/hardware/motherboard.md b/src/hardware/motherboard.md new file mode 100644 index 00000000..4a445d62 --- /dev/null +++ b/src/hardware/motherboard.md @@ -0,0 +1,8 @@ +# Motherboard + +All of these parts, and the parts I didn't mention, are connected to +a big circuit board called a "motherboard." + +The motherboard connects the CPU, RAM, and any hard drive(s) such that they +can "talk" to each other. The details of how it does this aren't super important, so just imagine +that a tiny wizard lives inside it. \ No newline at end of file diff --git a/src/hardware/ram.md b/src/hardware/ram.md new file mode 100644 index 00000000..09bf39ea --- /dev/null +++ b/src/hardware/ram.md @@ -0,0 +1,13 @@ +# RAM + +CPUs can do math real fast, but they can't remember much stuff.[^likeme] + +This is what RAM is for. RAM stands for "random access memory." The random access +part just means that a CPU can ask for any bit of info at any time. + +Most computers nowadays have an even number of RAM sticks. This is for interesting reasons I +don't fully understand. + +[^likeme]: In 8th grade I had someone physically hand me a piece of paper with their name and +fun facts about them because I had kept forgetting their name the entire year. I don't know what +Sam is up to now, but I remember that she likes pistachio ice cream. \ No newline at end of file diff --git a/src/hash_map.md b/src/hash_map.md new file mode 100644 index 00000000..96e549de --- /dev/null +++ b/src/hash_map.md @@ -0,0 +1 @@ +# HashMap diff --git a/src/hash_maps.md b/src/hash_maps.md new file mode 100644 index 00000000..013da259 --- /dev/null +++ b/src/hash_maps.md @@ -0,0 +1,76 @@ +# HashMap + + + + +Arrays and their growable cousin `ArrayList` store a sequence of elements. +This is often enough, but if you want to find an element in an array you usually +need to check every element one by one. + +```java +record Character( + String name, + boolean protaganist +) {} + +Character findCharacter( + Character[] cast, + String name +) { + for (var character : cast) { + if (character.name().equals(name)) { + return character; + } + } + return null; +} + +void main() { + Character[] carsCharacters = new Character[] { + new Character("Tow Mater", false), + new Character("Lightning McQueen", true), + new Character("Doc Hudson", false) + }; + + IO.println(findCharacter(carsCharacters, "Lightning McQueen")); + IO.println(findCharacter(carsCharacters, "Blade Ranger")); +} +``` + +For small arrays, this is no biggie. A computer can check over a few dozen things pretty quickly. + +But for big arrays, this stinks. + +What we want is some way to quickly look up something, regardless of how many things there are to +check over. This is what a HashMap gives us. + +```java +import java.util.HashMap; + +record Character( + String name, + boolean protaganist +) {} + +void main() { + HashMap carsCharacters = new HashMap<>(); + + carsCharacters.put( + "Tow Mater", + new Character("Tow Mater", false) + ); + + carsCharacters.put( + "Lightning McQueen", + new Character("Lightning McQueen", false) + ); + + carsCharacters.put( + "Doc Hudson", + new Character("Doc Hudson", false) + ); + + IO.println(carsCharacters.get("Lightning McQueen")); + IO.println(carsCharacters.get("Blade Ranger")); +} +``` \ No newline at end of file diff --git a/src/hash_maps/appropriate_keys.md b/src/hash_maps/appropriate_keys.md new file mode 100644 index 00000000..310773be --- /dev/null +++ b/src/hash_maps/appropriate_keys.md @@ -0,0 +1,75 @@ +# Appropriate Keys + +Both objects with reference based and value based definitions of `equals` and `hashCode` +are "appropriate" to use as keys in `HashMap`s. + + +The most important thing to be careful of is using objects where `equals` and `hashCode` +are value based, but the object itself is mutable. + +```java +import java.util.Objects; +import java.util.HashMap; +~ +~void main() { +~ new Main().main(); +~} +~ +class Pos { + int x; + int y; + + Pos(int x, int y) { + this.x = x; + this.y = y; + } + + // equals and hashCode here are defined in terms of X and Y + @Override + public int hashCode() { + return Objects.hash(x, y); + } + + @Override + public boolean equals(Object o) { + return o instanceof Pos p && + this.x == p.x && + this.y == p.y; + } +} + +class Main { + void main() { + var m = new HashMap(); + + // Therefore we can get and put values using a Pos + // as a key. + var p = new Pos(4, 5); + m.put(p, "Slippery Ice"); + + IO.println( + m.get(p) + ); + + // But if we were to mutate the object + p.x = 99; + + // then the original object might be in the wrong bucket + // inside the hash map! + IO.println( + m.get(p) + ); + + // And because the key is stored, even if it is in the right bucket + // the equals check might not function correctly + IO.println( + m.get(new Pos(4, 5)) + ); + } +} +``` + +So while `Pos` has a "value based identity," you can muck it up by directly changing values. `HashMap`s assume +that when something is used as a key its `equals` and `hashCode` are stable and will not change later in the program. + +If something has a value-based definition of `equals` and `hashCode` but can be changed, that is inappropriate to use as a key. \ No newline at end of file diff --git a/src/hash_maps/buckets.md b/src/hash_maps/buckets.md new file mode 100644 index 00000000..d3f26a83 --- /dev/null +++ b/src/hash_maps/buckets.md @@ -0,0 +1,79 @@ +# Buckets + +Once you know the range of your hash function, +the next step is to divide that range into buckets. + +A bucket is the name for the place where you will store +all the values that match that hash key. + + +```java,no_run +class Bucket { + +} +``` + +One way to divide your range into buckets is to make an array +then pick an index into that array by the hash. + +```java +record CarsCharacter( + String firstName, + String lastName +) {} + +// Like before, we define our hash function +// to be the first letter of the last name. +char hashFunction( + CarsCharacter carsCharacter +) { + return carsCharacter.lastName().charAt(0); +} + +class Bucket { + +} + +// Then, assuming we have some array of buckets +Bucket[] buckets = new Bucket[] { + new Bucket(), + new Bucket(), + new Bucket() +}; + +// We can make logic that takes the result of the hash +// and gives an index for a bucket. +int indexFor(char hash) { + if (hash >= 'A' && hash <= 'F') { + return 0; + } + else if (hash >= 'G' && hash <= 'N') { + return 1; + } + else { + return 2; + } +} + + +void main() { + // These two have different hashes but end up in the + // same bucket + var sally = new CarsCharacter("Sally", "Carrera"); + IO.println(hashFunction(sally)); + IO.println(indexFor(hashFunction(sally))); + + var doc = new CarsCharacter("Doc", "Hudson"); + IO.println(hashFunction(doc)); + IO.println(indexFor(hashFunction(doc))); + + var lightning = new CarsCharacter("Lightning", "McQueen"); + IO.println(hashFunction(lightning)); + IO.println(indexFor(hashFunction(lightning))); + + +} +``` + +These are the drawers in our filing cabinet analogy. + diff --git a/src/hash_maps/challenges.md b/src/hash_maps/challenges.md new file mode 100644 index 00000000..563c2fa3 --- /dev/null +++ b/src/hash_maps/challenges.md @@ -0,0 +1,145 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + + +Make a `Library` class. It should have four exposed methods + +- `add` which takes a `Book` and adds it to the library. +- `list` which returns an `ArrayList` of all the `Book`s in the library. +The `String`s in this list should be the [ISBN](https://en.wikipedia.org/wiki/ISBN) +of the book. +- `find` which takes a `String` representing the ISBN and returns a full `Book` record. +- `remove` which takes a `String` representing the ISBN and removes that `Book` from the +library. + +Use a `HashMap` for storing this info in the `Library`. + +```java,editable +record Book(String isbn, String title, String author) {} + +class Library { + // CODE HERE +} + +class Main { + void main() { + Library publicLibrary = new Library(); + publicLibrary.add(new Book("978-0-8041-3902-1", "The Martian", "Andy Weir")); + publicLibrary.add(new Book("978-0062060624", "The Song of Achilles", "Madeline Miller")); + + IO.println(publicLibrary.list()); + + Book b1 = publicLibrary.find("978-0062060624"); + IO.println(b1); // The Song of Achilles + + Book b2 = publicLibrary.find("123"); + IO.println(b2); // null + + publicLibrary.remove("978-0062060624"); + + Book b3 = publicLibrary.find("978-0062060624"); + IO.println(b3); // null + + IO.println(publicLibrary.list()); + } +} +``` + +## Challenge 2. + +Write a method which takes as an argument an `ArrayList` +and returns a `HashMap` where each key is +a `String` from the original `ArrayList` and each value is the number +of times it appeared in that `ArrayList`. + +```java,editable +class Main { + HashMap count(ArrayList words) { + // CODE HERE + } + + void main() { + ArrayList w = new ArrayList<>(); + w.add("duck"); + w.add("duck"); + w.add("duck"); + w.add("goose"); + w.add("duck"); + w.add("duck"); + w.add("duck"); + w.add("duck"); + w.add("duck"); + w.add("duck"); + w.add("duck"); + w.add("duck"); + w.add("goose"); + w.add("zebra"); + + // {duck=11,goose=2,zebra=1} + IO.println( + count(w) + ); + } +} +``` + +If you feel like this is hard to do with just `.put` and `.get`, you can +check for [other methods on HashMap that can help you](https://javadoc.mccue.dev/api/java.base/java/util/HashMap.html). + +## Challenge 3. + +Without calling any methods on the `HashMap`, make it so that `map.get(person)` returns `null`. + +```java +class Person { + int age; + + Person(int age) { + this.age = age; + } + + @Override + public String toString() { + return "Person[age=" + age + "]"; + } + + @Override + public boolean equals(Object o) { + if (o instanceof Person p) { + return age == p.age; + } + else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(age); + } +} + +class Main { + void main() { + var person = new Person("Patrocolus"); + var map = new HashMap(); + map.put(person, "Achilles"); + + // ------------- + + // CODE HERE + // Do not directly touch "map" + + // ------------- + + // Should output `null` + IO.println(map.get(person)); + } +} +``` \ No newline at end of file diff --git a/src/hash_maps/filing_cabinets.md b/src/hash_maps/filing_cabinets.md new file mode 100644 index 00000000..8c0be0b3 --- /dev/null +++ b/src/hash_maps/filing_cabinets.md @@ -0,0 +1,44 @@ +# Filing Cabinets + +The basic concept behind a Hash Map is the same concept as a filing cabinet. + +If you grew up in a doctor's office like I did this will make sense. If you didn't, look up +what a filing cabinet looks like. + +Basically, if you have folders for a bunch of people you can separate them into different cabinets. + +You start by putting everyone in one cabinet. If you want to find someone's records, you look through +everything in that cabinet until you find it.[^sorted] + +| Cabinet | +| -------- | +| Lightning McQueen | +| Tow Mater | +| Doc Hudson | + +Once you have enough records you separate your folders by something like the patient's last name. So you would +have your `A-M` and `N-Z` cabinets. + +| A-M | N-Z | +| -------- | | +| Lightning McQueen | Strip Weathers | +| Tow Mater | | +| Doc Hudson | | +| Sally Carrera | | +| Chick Hicks | | + +Then when you want to find someone, you can quicky pick the right cabinet by looking at their last name. + +If you got too many people in one cabinet you would then subdivide that cabinet further. + +| A-H | A-M | N-Z | +| -------- | ---- |-- | +| Doc Hudson | Lightning McQueen | Strip Weathers | +| Sally Carrera | Tow Mater | | +| Chick Hicks | | | + +And this basic system can scale to thousands of names. + + + +[^sorted]: For a real filing cabinet system the contents of each cabinet would also be sorted. Ignore that for now. \ No newline at end of file diff --git a/src/hash_maps/function_range.md b/src/hash_maps/function_range.md new file mode 100644 index 00000000..8d569d3d --- /dev/null +++ b/src/hash_maps/function_range.md @@ -0,0 +1,11 @@ +# Function Range + +Once you have a hash function, you need to determine its range. + +The range of a function is the set of values it might return. + +If you have a function that might return any `int` then the range will be +all the values between `Integer.MIN_VALUE` and `Integer.MAX_VALUE`. + +If your hash function only returns a number between `0` and `5`, `0` - `5` +is the range. diff --git a/src/hash_maps/get_items.md b/src/hash_maps/get_items.md new file mode 100644 index 00000000..5d170c2d --- /dev/null +++ b/src/hash_maps/get_items.md @@ -0,0 +1,19 @@ +# Get Items + +You can get the value associated with a key by using `.get`. If there is no +value associated with that key it will return `null`. + +```java +import java.util.HashMap; + +class Main { + void main() { + var wins = new HashMap(); + wins.put("Lightning McQueen", 2); + wins.put("Tow Mater", 0); + + IO.println(wins.get("Tow Mater")); + IO.println(wins.get("Doc Hudson")); + } +} +``` diff --git a/src/hash_maps/hash_collision.md b/src/hash_maps/hash_collision.md new file mode 100644 index 00000000..b1c22f1b --- /dev/null +++ b/src/hash_maps/hash_collision.md @@ -0,0 +1,14 @@ +# Hash Collision + +It is possible for a hash function to give the same +result for two distinct elements. + +The first letter of both +"Smith" and "Sanders" is "S", so if the hash function takes the first letter +of the last name they will have the same hash. + +We call this situation a "hash collision." All it means is that these two objects +will definitely go into the same bucket. + +That's allowed to happen, but if _everything_ goes into the same bucket then there +isn't much point to using a `HashMap` over an `ArrayList`. \ No newline at end of file diff --git a/src/hash_maps/hash_distribution.md b/src/hash_maps/hash_distribution.md new file mode 100644 index 00000000..d229ed30 --- /dev/null +++ b/src/hash_maps/hash_distribution.md @@ -0,0 +1,16 @@ +# Hash Distribution + +When picking a hash function you also need to be worried +about "hash distribution." + +If you open up a doctor's office in South Boston, you might have an issue +ordering charts by last name only. Your `M` cabinet will be overflowing.[^irish] + +For that scenario, using the first letter of the last name is a non-ideal hash function +because when so many people have last names starting with the same letter, they will +not be evenly distributed amongst the buckets. + +Making a hash function with a good distribution is hard. `Objects.hash` will do a decent job of it +and thats why we use it. + +[^irish]: "Mc" and "Mac" are common irish surnames and Boston has a sizable irish population. \ No newline at end of file diff --git a/src/hash_maps/hash_functions.md b/src/hash_maps/hash_functions.md new file mode 100644 index 00000000..bb8a5ce5 --- /dev/null +++ b/src/hash_maps/hash_functions.md @@ -0,0 +1,31 @@ +# Hash Functions + +A hash function is something that takes a piece of data +and extracts a smaller, but predictable, piece of data from it. + +```java +record CarsCharacter( + String firstName, + String lastName +) {} + +char hashFunction( + String lastName +) { + return lastName.charAt(0); +} + +void main() { + var lightning = new CarsCharacter("Lightning", "McQueen"); + + char firstOfLast = hashFunction(lightning.lastName()); + IO.println(firstOfLast); +} +``` + +Taking the first letter of a last name is an example of this. You can't +reverse `M` into `McQueen`, but you can take `McQueen` and know to look +in the bucket labeled `M`. + +We need hash functions to decide what "filing cabinet" +a thing should go in. \ No newline at end of file diff --git a/src/hash_maps/header.png b/src/hash_maps/header.png new file mode 100644 index 00000000..894dff91 Binary files /dev/null and b/src/hash_maps/header.png differ diff --git a/src/hash_maps/keys_and_values.md b/src/hash_maps/keys_and_values.md new file mode 100644 index 00000000..09af8bce --- /dev/null +++ b/src/hash_maps/keys_and_values.md @@ -0,0 +1,6 @@ +# Keys and Values + +A Hash Map "maps" keys to values. + +In the filing cabinet analogy we are mapping names to charts +full of all sorts of other information on the person. \ No newline at end of file diff --git a/src/hash_maps/put_items.md b/src/hash_maps/put_items.md new file mode 100644 index 00000000..7ac24427 --- /dev/null +++ b/src/hash_maps/put_items.md @@ -0,0 +1,20 @@ +# Put Items + +You can associate a key with a value by calling the `.put` method.[^watchcars] + +```java +import java.util.HashMap; + +class Main { + void main() { + var wins = new HashMap(); + wins.put("Lightning McQueen", 2); + wins.put("Tow Mater", 0); + + IO.println(wins); + } +} +``` + +[^watchcars]: Cars was a delightful movie. Go to your local second hand store, get +a copy on DVD, and make a night of it. \ No newline at end of file diff --git a/src/hash_maps/redistribution.md b/src/hash_maps/redistribution.md new file mode 100644 index 00000000..81463ec2 --- /dev/null +++ b/src/hash_maps/redistribution.md @@ -0,0 +1 @@ +# Redistribution diff --git a/src/hash_maps/reference_based_identity.md b/src/hash_maps/reference_based_identity.md new file mode 100644 index 00000000..a851e4eb --- /dev/null +++ b/src/hash_maps/reference_based_identity.md @@ -0,0 +1,84 @@ +# Reference Based Identity + +The hash function that `HashMap` uses is `.hashCode()`. It uses the number +returned from this method to decide which of the buckets it maintains +to put an item into. + +Instead of making buckets like `A-G` and `H-Z`, it will use ranges of numbers. The concept is the +same though + +For classes you make yourself, their `hashCode` will be based on what we call an object's +"identity." This means that every individual instance of a class is likely to give you a different +value, regardless of if they hold identical fields. + + +```java +~void main() { +~ new Main().main(); +~} +~ +class LivingRaceCar { + int cursedness; + + // God, could you imagine being judged by a Honda? + LivingRaceCar(int cursedness) { + this.cursedness = cursedness; + } +} + +class Main { + void main() { + var carA = new LivingRaceCar(10); + // Car B is a reference to Car A + var carB = carA; + // Car C is a distinct object with its own identity + var carC = new LivingRaceCar(10); + + // Accordingly, Car C will probably have a different + // hashCode. This is despite it having the same + // values in its fields. It is a "distinct" object. + IO.println("A: " + carA.hashCode()); + IO.println("B: " + carB.hashCode()); + IO.println("C: " + carC.hashCode()); + } +} +``` + +Identity is also what the default `.equals` implementation is based off of. +If two variables reference the same object then `.equals` will return `true`. + +```java +~void main() { +~ new Main().main(); +~} +~ +class LivingRaceCar { + int cursedness; + + LivingRaceCar(int cursedness) { + this.cursedness = cursedness; + } +} + +class Main { + void main() { + var carA = new LivingRaceCar(10); + // Car B is a reference to Car A + var carB = carA; + // Car C is a distinct object with its own identity + var carC = new LivingRaceCar(10); + + // Car C therefore will only equal itself + // Car A and B will equal each other + IO.println("A.equals(A): " + carA.equals(carA)); + IO.println("A.equals(B): " + carA.equals(carB)); + IO.println("A.equals(C): " + carA.equals(carC)); + IO.println("B.equals(A): " + carB.equals(carA)); + IO.println("B.equals(B): " + carB.equals(carB)); + IO.println("B.equals(C): " + carB.equals(carC)); + IO.println("C.equals(A): " + carC.equals(carA)); + IO.println("C.equals(B): " + carC.equals(carB)); + IO.println("C.equals(C): " + carC.equals(carC)); + } +} +``` \ No newline at end of file diff --git a/src/hash_maps/ubiquity.md b/src/hash_maps/ubiquity.md new file mode 100644 index 00000000..fd80ce00 --- /dev/null +++ b/src/hash_maps/ubiquity.md @@ -0,0 +1,9 @@ +# Ubiquity + +HashMaps are very common both in Java and the wider programming world +as a whole. + +You can mostly get away with just knowing how to use them and what they do, +but if you want to take a deeper dive this video is a good start. + + \ No newline at end of file diff --git a/src/hash_maps/value_based_identity.md b/src/hash_maps/value_based_identity.md new file mode 100644 index 00000000..b9a53fa9 --- /dev/null +++ b/src/hash_maps/value_based_identity.md @@ -0,0 +1,58 @@ +# Value Based Identity + + +While reference based identity can be useful, it's often not what you want for keys in a `HashMap`. +Ideally if you are looking up `"Tow Mater"` you shouldn't have to be careful to ensure it's the *same* +instance of `String`, all you care about is that it contains the right characters. + +We call this notion of identity "value based." Two things are the same if they contain the same data - i.e. if they represent the same value. + +```java +class Main { + void main() { + // Strings A and B are distinct instances + var stringA = new String(new char[] { 'a', 'b', 'c' }); + var stringB = new String(new char[] { 'a', 'b', 'c' }); + + // but they will give the same hashCode + IO.println(stringA.hashCode()); + IO.println(stringB.hashCode()); + + // and will be equal to eachother + IO.println(stringA.equals(stringB)); + IO.println(stringB.equals(stringA)); + } +} +``` + +`String`s, all the numeric types like `Integer` and `Double`, as well as `Boolean`s are defined +in this way.[^difference] So will any `record`s and `enum`s you make[^bydefault]. + +```java +~void main() { +~ new Main().main(); +~} +~ +record Pos(int x, int y) {} + +class Main { + void main() { + // Positions A and B are distinct instances but hold the same values + var posA = new Pos(5, 5); + var posB = new Pos(5, 5); + + // therefore they will give the same hashCode + IO.println(posA.hashCode()); + IO.println(posB.hashCode()); + + // and will be equal to eachother + IO.println(posA.equals(posB)); + IO.println(posB.equals(posA)); + } +} +``` + + +[^difference]: There is an important distinction here between things where `.equals` and `.hashCode` are simply defined in terms of value based equality and whether the objects themselves have identity. Operations like `==` operate on what we might call an "intrinsic identity." Problem for later. + +[^bydefault]: At least by default. \ No newline at end of file diff --git a/src/hyrums_law.md b/src/hyrums_law.md new file mode 100644 index 00000000..17d8adf0 --- /dev/null +++ b/src/hyrums_law.md @@ -0,0 +1,8 @@ +# Hyrum's Law + +This is Hyrum's Law. + +> With a sufficient number of users of an API, +> it does not matter what you promise in the contract: +> all observable behaviors of your system +> will be depended on by somebody. \ No newline at end of file diff --git a/src/hyrums_law/authority.md b/src/hyrums_law/authority.md new file mode 100644 index 00000000..09bf6347 --- /dev/null +++ b/src/hyrums_law/authority.md @@ -0,0 +1,11 @@ +# Authority + +Hyrum's Law, and many other "laws" in software development, +are not really laws in the same way as "The Law of Conservation of Energy." + +Those sorts of laws are observed truths about a field. Best we can tell they are always true, no matter what. + +Hyrum's Law is just an aphorism. It's one specific person's view on the field. + +This sounds sketchy, and it is, but the state +of the computing field is such that aphorisms and personal anecdotes are often the best information we have. diff --git a/src/hyrums_law/emergent_properties.md b/src/hyrums_law/emergent_properties.md new file mode 100644 index 00000000..aa71453b --- /dev/null +++ b/src/hyrums_law/emergent_properties.md @@ -0,0 +1,11 @@ +# Emergent Properties + +Emergent properties are things that simply "emerge" +as a consequence of other factors. + +Hyrum's law is one of those sorts of things. Nobody sat down and agreed +that they will use an API in wacky ways. It just happens when you throw enough +monkeys in a pile and they all reach for a keyboard. + +As such, the way to deal with it isn't to put blame upon the monkeys. It's +to accept it as a naturally occurring phenominon and plan accordingly. \ No newline at end of file diff --git a/src/hyrums_law/importance.md b/src/hyrums_law/importance.md new file mode 100644 index 00000000..5cf8cbdf --- /dev/null +++ b/src/hyrums_law/importance.md @@ -0,0 +1,20 @@ +# Importance + +So why is this person's take important for you to know about? + +The reason is that the core concept - that +every observable property will be depended on +by somebody with enough consumers - is essential +context for understanding why Java is the way that it is. + +Many of Java's features are intended to give you ways +to minimize the number of observable properties of +the software you produce. + +Private fields and methods are the easiest examples of this so far. If you +make fields or methods private then you can start to expect that +consumers of that class do not observe them. Thus, if needed, you +will not break anyone by changing them.[^setAccessible] + +[^setAccessible]: There are asterisks to this particular point, unfortunately. +There are ways to magically get at private fields like criminal scum. \ No newline at end of file diff --git a/src/hyrums_law/validity.md b/src/hyrums_law/validity.md new file mode 100644 index 00000000..fe321174 --- /dev/null +++ b/src/hyrums_law/validity.md @@ -0,0 +1,23 @@ +# Validity + +Now is this law literally true? No. It can't be. There are more +"properties" you can observe about a piece of software than +people on the planet. + +The specific number of degrees your CPU heats up when running a program can, hypothetically, be relied on. Chances are that +won't happen though. + +Personally I think that the expanded explanation on his website ([https://www.hyrumslaw.com/](https://www.hyrumslaw.com/)) is +more nuanced than the short version, if a lot less snappy and easy to remember. + +> Over the past couple years of doing low-level infrastructure migrations in one of the most complex software systems on the planet, I’ve made some observations about the differences between an interface and its implementations. We typically think of the interface as an abstraction for interacting with a system (like the steering wheel and pedals in a car), and the implementation as the way the system does its work (wheels and an engine). This is useful for a number of reasons, foremost among them that most useful systems rapidly become too complex for a single individual or group to completely understand, and abstractions are essential to managing that complexity. +> +> Defining the correct level of abstraction is a completely separate discussion (see Mythical Man-Month), but we like to think that once an abstraction is defined, it is concrete. In other words, an interface should theoretically provide a clear separation between consumers of a system and its implementers. In practice, this theory breaks down as the use of a system grows and its users start to rely upon implementation details intentionally exposed through the interface, or which they divine through regular use. Spolsky’s β€œLaw of Leaky Abstractions” embodies consumers’ reliance upon internal implementation details. +> +> Taken to its logical extreme, this leads to the following observation, colloquially referred to as β€œThe Law of Implicit Interfaces”: Given enough use, there is no such thing as a private implementation. That is, if an interface has enough consumers, they will collectively depend on every aspect of the implementation, intentionally or not. This effect serves to constrain changes to the implementation, which must now conform to both the explicitly documented interface, as well as the implicit interface captured by usage. We often refer to this phenomenon as "bug-for-bug compatibility." +> +> The creation of the implicit interface usually happens gradually, and interface consumers generally aren’t aware as it’s happening. For example, an interface may make no guarantees about performance, yet consumers often come to expect a certain level of performance from its implementation. Those expectations become part of the implicit interface to a system, and changes to the system must maintain these performance characteristics to continue functioning for its consumers. +> +> Not all consumers depend upon the same implicit interface, but given enough consumers, the implicit interface will eventually exactly match the implementation. At this point, the interface has evaporated: the implementation has become the interface, and any changes to it will violate consumer expectations. With a bit of luck, widespread, comprehensive, and automated testing can detect these new expectations but not ameliorate them. +> +> Implicit interfaces result from the organic growth of large systems, and while we may wish the problem did not exist, designers and engineers would be wise to consider it when building and maintaining complex systems. So be aware of how the implicit interface constrains your system design and evolution, and know that for any reasonably popular system, the interface reaches much deeper than you think. \ No newline at end of file diff --git a/src/img/armcross.webp b/src/img/armcross.webp new file mode 100644 index 00000000..eb03db07 Binary files /dev/null and b/src/img/armcross.webp differ diff --git a/src/img/computer_bad.webp b/src/img/computer_bad.webp new file mode 100644 index 00000000..99eaeb90 Binary files /dev/null and b/src/img/computer_bad.webp differ diff --git a/src/img/duke_armcross.webp b/src/img/duke_armcross.webp new file mode 100644 index 00000000..0401d35f Binary files /dev/null and b/src/img/duke_armcross.webp differ diff --git a/src/img/duke_computer.webp b/src/img/duke_computer.webp new file mode 100644 index 00000000..f3a11f78 Binary files /dev/null and b/src/img/duke_computer.webp differ diff --git a/src/img/duke_finger.webp b/src/img/duke_finger.webp new file mode 100644 index 00000000..41873294 Binary files /dev/null and b/src/img/duke_finger.webp differ diff --git a/src/img/eyeclose.webp b/src/img/eyeclose.webp new file mode 100644 index 00000000..1e7c9571 Binary files /dev/null and b/src/img/eyeclose.webp differ diff --git a/src/inner_classes.md b/src/inner_classes.md new file mode 100644 index 00000000..2132d922 --- /dev/null +++ b/src/inner_classes.md @@ -0,0 +1,15 @@ +# Inner Classes + + + +You can declare a class within another class. + +```java,no_run +class Car { + class Speedometer { + + } +} +``` + +We call these inner classes. diff --git a/src/inner_classes/challenges.md b/src/inner_classes/challenges.md new file mode 100644 index 00000000..d1f7f264 --- /dev/null +++ b/src/inner_classes/challenges.md @@ -0,0 +1,108 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +As written the code in `GameConsole.Controller` +looks at its own `isPoweredOn` field. + +Disambiguate the usage so that it is uses the `isPoweredOn` field from +the `GameConsole` instance wrapping it in its `status` method. + +```java,editable +class GameConsole { + boolean isPoweredOn; + + GameConsole() { + this.isPoweredOn = false; + } + + class Controller { + boolean isPoweredOn; + + Controller() { + this.isPoweredOn = false; + } + + String status() { + "Controller[" + + isPoweredOn ? "ON" : "OFF" + "] - GameConsole[" + + isPoweredOn ? "ON" : "OFF" + "]"; + } + } +} + +class Main { + void main() { + var console = new GameConsole(); + var controller = console.new Controller(); + + IO.println(controller.status()); + + console.isPoweredOn = true; + IO.println(controller.status()); + + controller.isPoweredOn = true; + IO.println(controller.status()); + + console.isPoweredOn = false; + IO.println(controller.status()); + } +} +``` + +## Challenge 2. + +Make the `Controller` class from the previous example a `static` inner class. +The `status` method should keep the same behavior. This means you will need +to explicitly pass an instance of `GameConsole` to the constructor of `Controller` +and store it in a field. + +## Challenge 3. + +Successfully make an instance of `Fly` from the `Main` class. + +```java,editable +class OldLady { + class Horse { + class Cow { + class Goat { + class Dog { + class Cat { + class Bird { + class Spider { + class Fly { + Fly() { + IO.println("She's dead, of course"); + } + } + } + } + } + } + } + } + } +} + +class Main { + Fly f = /* CODE HERE */; + IO.println(f); +} +``` + +## Challenge 4. + +Go back to some of the programs you wrote entirely within the anonymous +main class. Turn that main class into a named class. + +First make sure your program works the same as it did. Then +go through all the inner classes in your program and mark them +as many of them as you can `static`. + +Which ones can't be trivially made `static`? Note what state they +access in the enclosing instance. \ No newline at end of file diff --git a/src/inner_classes/disambiguation.md b/src/inner_classes/disambiguation.md new file mode 100644 index 00000000..e0f1a126 --- /dev/null +++ b/src/inner_classes/disambiguation.md @@ -0,0 +1,63 @@ +# Disambiguation + +If you are within an inner class and want to use a field +from the instance it was created in but that field has the same +name as a field in the inner class - like the following. + +```java,no_run +class Car { + int speed = 0; + + class Speedometer { + // Speed is declared here, but it is + // a different field + int speed = 5; + } +} +``` + +You can disambiguate between the fields by using the name of the containing class +followed by `.this`. + +```java,no_run +class Car { + int speed = 0; + + class Speedometer { + // Speed is declared here, but it is + // a different field + int speed = 5; + + void saySpeed() { + IO.println(speed); // 5 + IO.println(this.speed); // 5 + IO.println(Car.this.speed); // 0 + } + } +} +``` + +```java +~class Car { +~ int speed = 0; +~ +~ class Speedometer { +~ // Speed is declared here, but it is +~ // a different field +~ int speed = 5; +~ +~ void saySpeed() { +~ IO.println(speed); // 5 +~ IO.println(this.speed); // 5 +~ IO.println(Car.this.speed); // 0 +~ } +~ } +~} +class Main { + void main() { + var car = new Car(); + var speedometer = car.new Speedometer(); + speedometer.saySpeed(); + } +} +``` \ No newline at end of file diff --git a/src/inner_classes/header.png b/src/inner_classes/header.png new file mode 100644 index 00000000..2888653e Binary files /dev/null and b/src/inner_classes/header.png differ diff --git a/src/inner_classes/instance_new.md b/src/inner_classes/instance_new.md new file mode 100644 index 00000000..7db5f905 --- /dev/null +++ b/src/inner_classes/instance_new.md @@ -0,0 +1,3 @@ +# Instance New + + diff --git a/src/inner_classes/instances.md b/src/inner_classes/instances.md new file mode 100644 index 00000000..025eb87e --- /dev/null +++ b/src/inner_classes/instances.md @@ -0,0 +1,46 @@ +# Instances + +To make an instance of an inner class you can use `new` to invoke its constructor +like any other class. + +```java,no_run +class Car { + class Speedometer { + + } + + Speedometer getSpeedometer() { + return new Speedometer(); + } +} +``` + +The restriction is that an inner class can only be constructed from an instance +method of the containing class. + +This means that, in the example above, you cannot make an instance of `Speedometer` +unless you first have an instance of `Car`. + +```java +class Main { + void main() { + var car = new Car(); + var speedometer = car.getSpeedometer(); + + IO.println(speedometer); + + // But this will not work + // var speedometer = new Car.Speedometer(); + } +} +~ +~class Car { +~ class Speedometer { +~ +~ } +~ +~ Speedometer getSpeedometer() { +~ return new Speedometer(); +~ } +~} +``` \ No newline at end of file diff --git a/src/inner_classes/new_operator.md b/src/inner_classes/new_operator.md new file mode 100644 index 00000000..976e2120 --- /dev/null +++ b/src/inner_classes/new_operator.md @@ -0,0 +1,33 @@ +# New Operator + +If you want to make an instance of an inner class +without making a method on the class containing it, +you can use the "new operator." + +Whereas you make an instance of a regular class by saying +something like `new ClassName()`, you can make an instance of an +inner class by using `.new` on a variable that holds an instance +of the containing class. + +That's a confusing verbal description, but it kinda makes sense once you see it. + +```java +class Car { + class Speedometer { + } +} +``` + +```java +~class Car { +~ class Speedometer { +~ } +~} +class Main { + void main() { + Car car = new Car(); + Car.Speedometer speedometer = car.new Speedometer(); + IO.println(speedometer); + } +} +``` diff --git a/src/inner_classes/private_inner_classes.md b/src/inner_classes/private_inner_classes.md new file mode 100644 index 00000000..7f31cc7b --- /dev/null +++ b/src/inner_classes/private_inner_classes.md @@ -0,0 +1,20 @@ +# Private Inner Classes + +Both `static` and regular inner classes can be marked as `private`. + +```java +class Human { + // No other class can see this human's thoughts + private class Thoughts { + + } + + // Nor can they see their feelings + private static class Feelings { + + } +} +``` + +Within the class they are defined, a private inner class works as normal. The +difference is that code outside the class cannot make instances of them. \ No newline at end of file diff --git a/src/inner_classes/scope.md b/src/inner_classes/scope.md new file mode 100644 index 00000000..68b13a2d --- /dev/null +++ b/src/inner_classes/scope.md @@ -0,0 +1,61 @@ +# Scope + +Within an inner class all fields and methods of the instance it was created in +are available. + +```java,no_run +class Car { + int speed = 0; + + class Speedometer { + int getSpeed() { + return speed; + } + } + + Speedometer getSpeedometer() { + return new Speedometer(); + } +} +``` +```java,no_run +~class Car { +~ int speed = 0; +~ +~ class Speedometer { +~ int getSpeed() { +~ return speed; +~ } +~ } +~ +~ Speedometer getSpeedometer() { +~ return new Speedometer(); +~ } +~} +class Main { + void main() { + Car car = new Car(); + Car.Speedometer = car.getSpeedometer(); + } +} +``` +One mental model for this is that its as if the inner class +holds a reference to the one it was created in. + +```java,no_run +class Car { + int speed = 0; +} + +class CarSpeedometer { + private final Car madeBy; + + CarSpeedometer(Car madeBy) { + this.madeBy = madeBy; + } + + int getSpeed() { + return madeBy.speed; + } +} +``` \ No newline at end of file diff --git a/src/inner_classes/static_inner_classes.md b/src/inner_classes/static_inner_classes.md new file mode 100644 index 00000000..90133ef5 --- /dev/null +++ b/src/inner_classes/static_inner_classes.md @@ -0,0 +1,37 @@ +# Static Inner Classes + +If you mark an inner class as `static` then it becomes +much closer to a normal class. + +```java,no_run +class Car { + static class Speedometer { + + } +} +``` + +You can make instances of it directly without an instance of the outer class. + +```java,no_run +Car.Speedometer speedometer = new Car.Speedometer(); +``` + +And it cannot access fields of the instance it was made in, because it was not made in an instance. + +```java,no_run +class Car { + int speed; // Speedometer can't magically get this anymore + + static class Speedometer { + + } +} +``` + +I would wager that this is the most common kind of inner class to see +in real code, despite requiring more words to define[^theme]. + +[^theme]: A theme that will start to emerge is that the "best" code sometimes has +a few extra modifiers on it and the "default" behavior isn't what you want. Static +inner classes are way less magic. \ No newline at end of file diff --git a/src/inner_classes/the_anonymous_main_class.md b/src/inner_classes/the_anonymous_main_class.md new file mode 100644 index 00000000..792ac79a --- /dev/null +++ b/src/inner_classes/the_anonymous_main_class.md @@ -0,0 +1,53 @@ +# The anonymous main class + +If you remember when I first showed you classes, you were working inside +the anonymous main class. + +```java,no_run +class Muppet { + String name; +} + +void main() { + Muppet kermit = new Muppet(); + kermit.name = "Kermit The Frog"; +} +``` + +This means that all the classes you made were, in reality, inner classes. + +```java,no_run +class Main { + class Muppet { + String name; + } + + void main() { + Muppet kermit = new Muppet(); + kermit.name = "Kermit The Frog"; + } +} +``` + +Which is how they would have access to any "global fields" you declared +and why static factory methods would not work. + +```java,no_run,does_not_compile +class Main { + class Muppet { + String name; + + static Muppet fromName(String name) { + // You cannot make an instance of an inner class + // from within a static method, so this wouldn't work. + Muppet muppet = new Muppet(); + muppet.name = name; + return muppet; + } + } + + void main() { + Muppet kermit = Muppet.fromName("Kermit The Frog"); + } +} +``` \ No newline at end of file diff --git a/src/inner_classes/type.md b/src/inner_classes/type.md new file mode 100644 index 00000000..ffd1409d --- /dev/null +++ b/src/inner_classes/type.md @@ -0,0 +1,30 @@ +# Type + +The type of an inner class, when used as a variable or as a field, +is the name of the containing class followed by a `.` and the +name of the inner class. + +```java,no_run +class Car { + class Speedometer {} +} +``` + +So a field containing an instance of `Speedometer` would have the type `Car.Speedometer`. + +```java,no_run +Car.Speedometer speedometer = ...; +``` + +The exception is if the inner class is referenced within the class that declares it. +In that context you just need to write the name of the class; + +```java +class Car { + // Car.Speedometer is not required + // (it will work though) + Speedometer speedometer; + + class Speedometer {} +} +``` \ No newline at end of file diff --git a/src/instanc b/src/instanc deleted file mode 100644 index 7eff5f5c..00000000 --- a/src/instanc +++ /dev/null @@ -1 +0,0 @@ -# Invoke Other Methods diff --git a/src/instance b/src/instance deleted file mode 100644 index d7422205..00000000 --- a/src/instance +++ /dev/null @@ -1 +0,0 @@ -# this diff --git a/src/instance_methods.md b/src/instance_methods.md index e499cb06..08dc2c1c 100644 --- a/src/instance_methods.md +++ b/src/instance_methods.md @@ -1,5 +1,8 @@ # Instance Methods + + + In addition to having fields, classes can also have their own method definitions. @@ -11,7 +14,7 @@ class Muppet { String name; void freakOut() { - System.out.println("**ANGRY KERMIT NOISES**") + IO.println("**ANGRY KERMIT NOISES**") } } ``` @@ -19,5 +22,5 @@ class Muppet { We call these instance methods because you need an instance of the class in order to call the method. -[^kermitangry]: If you haven't seen the muppets this might have go over your head, +[^kermitangry]: If you haven't seen the muppets this might go over your head, but Kermit [randomly gets really mad.](https://www.youtube.com/watch?v=SVDgHEg2jnY) \ No newline at end of file diff --git a/src/instance_methods/arguments.md b/src/instance_methods/arguments.md index b0f6f780..54817ff5 100644 --- a/src/instance_methods/arguments.md +++ b/src/instance_methods/arguments.md @@ -9,13 +9,13 @@ class Muppet { void singLyric(int verse) { if (verse == 1) { - System.out.println("Why are there so many"); + IO.println("Why are there so many"); } else if (verse == 2) { - System.out.println("Songs about rainbows"); + IO.println("Songs about rainbows"); } else { - System.out.println("And what's on the other side?"); + IO.println("And what's on the other side?"); } } } diff --git a/src/instance_methods/challenges.md b/src/instance_methods/challenges.md new file mode 100644 index 00000000..45cc118f --- /dev/null +++ b/src/instance_methods/challenges.md @@ -0,0 +1,206 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Make a class named `PirateShip` which has one `int` field +named `crewSize`. + +Add an instance method to that class named `sail` which outputs + +``` +N sailors, ready to sail! +``` + +Where *N* is the size of the crew. + +```java,editable +// ---------------- +// CODE HERE +// ---------------- + +void main() { + PirateShip ship = new PirateShip(); + ship.crewSize = 25; + ship.sail(); +} +``` + +## Challenge 2. + +Make a class named `StringArrayView` which has +one `String[]` field named `value` + and two methods named `get` and `length`. + +`get` should take in an index and return the matching element +of the array. + +`length` should take no arguments and give the length of the array. + +```java,editable +// ---------------- +// CODE HERE +// ---------------- + +void main() { + StringArrayView view = new StringArrayView(); + view.value = new String[] { "A", "B", "C" }; + + // 3 + IO.println(view.length()); + + // A + IO.println(view.get(0)); + + // C + IO.println(view.get(2)); +} +``` + +## Challenge 3. + +Alter the `VoiceActor` class so that it has a method named `fullName` +that returns their `firstName` followed by their `lastName` and separated +by a space. + +If their `lastName` is `null`, you should have no trailing space. +If their `firstName` is `null`, you should have no leading space. + +If both their `firstName` and `lastName` are `null`, you should +return `"No Name"`. + +```java,editable +class VoiceActor { + String firstName; + String lastName; + + // ----------------- + // CODE HERE + // ----------------- +} + +void main() { + VoiceActor goku = new VoiceActor(); + goku.firstName = "Masako"; + goku.lastName = "Nozawa"; + + // "Masako Nozawa" + String gokuFullName = goku.fullName(); + IO.println(gokuFullName); + + // "Nozawa" + goku.firstName = null; + gokuFullName = goku.fullName(); + IO.println(gokuFullName); + + // "No Name" + goku.lastName = null; + gokuFullName = goku.fullName(); + IO.println(gokuFullName); + + // "Horikawa" + VoiceActor vegeta = new VoiceActor(); + vegeta.lastName = "Horikawa"; + IO.println(vegeta.fullName()); +} +``` + +## Challenge 4. + +Make a `Rectangle` class which has a `width` field and a `height` +field. Give it an instance method named `toCharArray` which gives +a `char[]` that can be printed to display a rectangle of the given +width and height. + +```java,editable +// ------------ +// CODE HERE +// ------------ + +void main() { + Rectangle rectangle = new Rectangle(); + rectangle.width = 3; + rectangle.height = 4; + + /* + *** + *** + *** + *** + */ + char[] c = rectangle.toCharArray(); + IO.println(c); +} +``` + +## Challenge 5. + +Update the definition for the `Taco` class so that it has a method named +`deluxe`. This should set the taco to have beef, sour cream, cheese, +and onion. Use the existing instance methods instead of directly accessing +fields. + +```java,editable +class Taco { + boolean beef; + boolean sourCream; + boolean cheese; + boolean onion; + + void addBeef() { + this.beef = true; + } + + void addSourCream() { + this.sourCream = true; + } + + void addCheese() { + this.cheese = true; + } + + void addOnion() { + this.onion = true; + } + + void deluxe() { + // ------------ + // CODE HERE + // ------------ + } +} + +void main() { + var taco = new Taco(); + taco.deluxe(); + + IO.println("Has Beef: " + taco.beef); + IO.println("Has Sour Cream: " + taco.sourCream); + IO.println("Has Cheese: " + taco.cheese); + IO.println("Has Onion: " + taco.onion); +} +``` + +## Challenge 6. + +Why doesn't this code function as you'd expect? Fix it by changing one line. + +```java,editable +class Oscar { + boolean grouchy; + + void setGrouchy(boolean grouchy) { + grouchy = grouchy; + } +} + +void main() { + var oscar = new Oscar(); + oscar.setGrouchy(true); + IO.println(oscar.grouchy); +} +``` \ No newline at end of file diff --git a/src/instance_methods/clarity.md b/src/instance_methods/clarity.md index dd1085ee..a0e9f41f 100644 --- a/src/instance_methods/clarity.md +++ b/src/instance_methods/clarity.md @@ -11,10 +11,10 @@ class Elmo { int age; void sayHello() { - System.out.println("Hi, I'm Elmo"); - System.out.print("I am "); - System.out.print(this.age); - System.out.println(" years old."); + IO.println("Hi, I'm Elmo"); + IO.print("I am "); + IO.print(this.age); + IO.println(" years old."); } } diff --git a/src/instance_methods/derived_values.md b/src/instance_methods/derived_values.md index 8228ee02..41b869e0 100644 --- a/src/instance_methods/derived_values.md +++ b/src/instance_methods/derived_values.md @@ -16,9 +16,29 @@ void main() { Elmo elmo = new Elmo(); elmo.age = 3; - System.out.println("Elmo is " + elmo.age + " right now,"); - System.out.println("but next year Elmo will be " + elmo.nextAge()); + IO.println("Elmo is " + elmo.age + " right now,"); + IO.println("but next year Elmo will be " + elmo.nextAge()); } ``` -Which is useful for situations like where you store someones first and last name \ No newline at end of file +Which is useful for situations like where you store someones first and last name +but need to ask "what is their full name?" + +```java +class Elmo { + String firstName; + String lastName; + + String fullName() { + return firstName + " " + lastName; + } +} + +void main() { + Elmo elmo = new Elmo(); + elmo.firstName = "Elmo"; + elmo.lastName = "Furchester"; + + IO.println("Elmo's full name is " + elmo.fullName()); +} +``` \ No newline at end of file diff --git a/src/instance_methods/disambiguation.md b/src/instance_methods/disambiguation.md index d768fa51..ab70db4f 100644 --- a/src/instance_methods/disambiguation.md +++ b/src/instance_methods/disambiguation.md @@ -17,7 +17,7 @@ void main() { elmo.age = 3; // true - System.out.println(elmo.isOlderThan(2)); + IO.println(elmo.isOlderThan(2)); } ``` diff --git a/src/instance_methods/field_access.md b/src/instance_methods/field_access.md index b0626627..7e6e25d2 100644 --- a/src/instance_methods/field_access.md +++ b/src/instance_methods/field_access.md @@ -8,12 +8,12 @@ class Elmo { int age; void sayHello() { - System.out.println("Hi, I'm Elmo"); - System.out.print("I am "); + IO.println("Hi, I'm Elmo"); + IO.print("I am "); // You can use elmo's age by just writing "age" - System.out.print(age); - System.out.println(" years old."); + IO.print(age); + IO.println(" years old."); } } diff --git a/src/instance_methods/field_updates.md b/src/instance_methods/field_updates.md index 9f030079..2ef4c4f7 100644 --- a/src/instance_methods/field_updates.md +++ b/src/instance_methods/field_updates.md @@ -8,10 +8,10 @@ class Elmo { int age; void sayHello() { - System.out.println("Hi, I'm Elmo"); - System.out.print("I am "); - System.out.print(age); - System.out.println(" years old."); + IO.println("Hi, I'm Elmo"); + IO.print("I am "); + IO.print(age); + IO.println(" years old."); } void haveBirthday() { diff --git a/src/instance_methods/header.png b/src/instance_methods/header.png new file mode 100644 index 00000000..90623014 Binary files /dev/null and b/src/instance_methods/header.png differ diff --git a/src/instance_methods/invocation.md b/src/instance_methods/invocation.md index cd86fc3c..5f0d9ff4 100644 --- a/src/instance_methods/invocation.md +++ b/src/instance_methods/invocation.md @@ -7,7 +7,7 @@ You then write `.` followed by the name of the instance method and a list of arg ```java class Elmo { void talkAboutRocko() { - System.out.println("ROCKO'S NOT ALIVE!!") + IO.println("ROCKO'S NOT ALIVE!!") } } diff --git a/src/instance_methods/invoke_other_methods.md b/src/instance_methods/invoke_other_methods.md index d15d11cc..e8b42de9 100644 --- a/src/instance_methods/invoke_other_methods.md +++ b/src/instance_methods/invoke_other_methods.md @@ -8,15 +8,15 @@ class Elmo { int age; void sayHello() { - System.out.println("Hi, I'm Elmo"); - System.out.print("I am "); - System.out.print(age); - System.out.println(" years old."); + IO.println("Hi, I'm Elmo"); + IO.print("I am "); + IO.print(age); + IO.println(" years old."); } void startTheShow(String showName) { sayHello(); - System.out.println("Welcome to " + showName); + IO.println("Welcome to " + showName); } } diff --git a/src/instance_methods/this.md b/src/instance_methods/this.md index 5b56b552..c121fdfb 100644 --- a/src/instance_methods/this.md +++ b/src/instance_methods/this.md @@ -11,15 +11,15 @@ class Elmo { int age; void sayHello() { - System.out.println("Hi, I'm Elmo"); - System.out.print("I am "); - System.out.print(this.age); - System.out.println(" years old."); + IO.println("Hi, I'm Elmo"); + IO.print("I am "); + IO.print(this.age); + IO.println(" years old."); } void startTheShow(String showName) { this.sayHello(); - System.out.println("Welcome to " + showName); + IO.println("Welcome to " + showName); } } diff --git a/src/integers.md b/src/integers.md index 92087ebd..4f274145 100644 --- a/src/integers.md +++ b/src/integers.md @@ -1,5 +1,8 @@ # Integers + + + [An integer is any number in the set `{ ..., -2, -1, 0, 1, 2, ... }.`](https://www.khanacademy.org/math/cc-sixth-grade-math/cc-6th-expressions-and-variables/whole-numbers-integers/a/whole-numbers-integers) ```java @@ -7,3 +10,4 @@ int x = 1; int y = 8; int z = -4; ``` + diff --git a/src/integers/addition.md b/src/integers/addition.md index ca2bade3..06c1d2da 100644 --- a/src/integers/addition.md +++ b/src/integers/addition.md @@ -10,9 +10,9 @@ int y = x + 1; // z will be 11 int z = x + y; -System.out.println(x); -System.out.println(y); -System.out.println(z); +IO.println(x); +IO.println(y); +IO.println(z); ~} ``` @@ -24,7 +24,7 @@ int x = 5; // y will be 1 int y = x + -4; -System.out.println(x); -System.out.println(y); +IO.println(x); +IO.println(y); ~} ``` diff --git a/src/integers/chained_comparisons.md b/src/integers/chained_comparisons.md index 334e0104..f73d44ee 100644 --- a/src/integers/chained_comparisons.md +++ b/src/integers/chained_comparisons.md @@ -8,7 +8,7 @@ in the middle of both operators like so. 0 < x < 5 ``` -This does not work in Java. In order to "chain" comparisons like this, you should combine +This does not work in Java. In order to "chain" comparisons like this, you have to combine the results of comparisons using the `&&` operator. ```java,no_run diff --git a/src/integers/challenges.md b/src/integers/challenges.md index a4be09fb..eb7246e5 100644 --- a/src/integers/challenges.md +++ b/src/integers/challenges.md @@ -13,7 +13,7 @@ What will this program output when run? Write down your guess and then try runni void main() { int x = 5; int y = 8; - System.out.println(x + y); + IO.println(x + y); } ``` @@ -27,7 +27,7 @@ void main() { x--; x--; x = x + x; - System.out.println(x); + IO.println(x); } ``` @@ -45,13 +45,13 @@ void main() { int z = 98; boolean xIsEven = < CODE HERE >; - System.out.println(xIsEven); + IO.println(xIsEven); boolean yIsEven = < CODE HERE >; - System.out.println(yIsEven); + IO.println(yIsEven); boolean zIsEven = < CODE HERE >; - System.out.println(zIsEven); + IO.println(zIsEven); } ``` @@ -63,7 +63,7 @@ Write down your guess and then try running the program below to see. ```java,editable void main() { - System.out.println(5 / 0); + IO.println(5 / 0); } ``` @@ -75,7 +75,7 @@ What can you write in the spot marked that will make the program output 2? void main() { int x = 5; int y = ; - System.out.println(x + y); + IO.println(x + y); } ``` @@ -85,7 +85,7 @@ What is the output of this code.[^fbarticle] ```java,editable void main() { - System.out.println( + IO.println( 6 / 2 * (1 + 2) ); } diff --git a/src/integers/division.md b/src/integers/division.md index 87f8ce69..7a1a3893 100644 --- a/src/integers/division.md +++ b/src/integers/division.md @@ -8,8 +8,8 @@ int x = 8; // y will be 4 int y = x / 2; -System.out.println(x); -System.out.println(y); +IO.println(x); +IO.println(y); ~} ``` @@ -24,7 +24,7 @@ int x = 5 / 2; // 13 / 3 is not 4.3333, but instead 4. int y = 13 / 3; -System.out.println(x); -System.out.println(y); +IO.println(x); +IO.println(y); ~} ``` diff --git a/src/integers/equality.md b/src/integers/equality.md index 931f100f..a2c726c8 100644 --- a/src/integers/equality.md +++ b/src/integers/equality.md @@ -10,14 +10,14 @@ and produces a `boolean` as its result. // 1 is never equal to 2 // this will be false boolean universeBroken = 1 == 2; -System.out.println(universeBroken); +IO.println(universeBroken); int loneliestNumber = 1; -int otherLonelyNumber = 2; +int canBeAsBadAsOne = 2; // this will be true -boolean bothLonely = loneliestNumber == (otherLonelyNumber - 1); -System.out.println(bothLonely); +boolean bothLonely = loneliestNumber == (canBeAsBadAsOne - 1); +IO.println(bothLonely); ~} ``` @@ -30,6 +30,6 @@ The opposite check, whether things are not equal, can be done with `!=`. // 1 is never equal to 2 // this will be true boolean universeOkay = 1 != 2; -System.out.println(universeOkay); +IO.println(universeOkay); ~} ``` diff --git a/src/integers/header.png b/src/integers/header.png new file mode 100644 index 00000000..c2380181 Binary files /dev/null and b/src/integers/header.png differ diff --git a/src/integers/limits.md b/src/integers/limits.md index 425b7552..413130a8 100644 --- a/src/integers/limits.md +++ b/src/integers/limits.md @@ -28,9 +28,12 @@ int atLimit = 2147483647; // The value will "loop around" to -2^31 int beyondLimit = atLimit + 1; // This will output -2147483648 -System.out.println(beyondLimit); +IO.println(beyondLimit); ~} ``` +When a value loops around because it got too big we call that "overflow." When it +loops around because it got too small we call that "underflow." + There are other types which can represent a larger range of integers, as well as types which do not have any limits, but for now `int` is the only one you will need. diff --git a/src/integers/multiplication.md b/src/integers/multiplication.md index bd20c08f..e59cefcb 100644 --- a/src/integers/multiplication.md +++ b/src/integers/multiplication.md @@ -11,8 +11,8 @@ int y = x * 5; // z will be 1125 int z = x * y; -System.out.println(x); -System.out.println(y); -System.out.println(z); +IO.println(x); +IO.println(y); +IO.println(z); ~} ``` diff --git a/src/integers/operator_precedence.md b/src/integers/operator_precedence.md index 6384a875..cfe5fef4 100644 --- a/src/integers/operator_precedence.md +++ b/src/integers/operator_precedence.md @@ -1,4 +1,4 @@ -# Operator Precedance +# Operator Precedence Just like boolean operators, `+`, `-`, `*`, `/`, and `%` have a defined precedence order. @@ -28,7 +28,7 @@ None of this should be a surprise if you learned [PEMDAS](https://www.khanacadem // and the final result is 17; int result = 2 * 3 + 3 * 9 / 2 - 2; -System.out.println(result); +IO.println(result); ~} ``` @@ -39,7 +39,7 @@ put them in the middle of any two math expressions. ~void main() { // The == check happens last. boolean areThingsSame = 3 * (4 - 1 + 3) * 4 == 5 * 3 + 1 * 3 * 9; -System.out.println(areThingsSame); +IO.println(areThingsSame); ~} ``` diff --git a/src/integers/reassignment.md b/src/integers/reassignment.md index b00359a1..3d3708af 100644 --- a/src/integers/reassignment.md +++ b/src/integers/reassignment.md @@ -8,17 +8,17 @@ This is true for all data types, but it is easiest to demonstrate with numbers. ```java ~void main() { int x = 1; -System.out.println(x); +IO.println(x); // x starts as 1, 1 + 1 is 2. // 2 is the new value of x. x = x + 1; -System.out.println(x); +IO.println(x); // x is now 2, 2 * 2 * 3 is 12 // 12 is the new value of x. x = x * x * 3; -System.out.println(x); +IO.println(x); ~} ``` diff --git a/src/integers/remainder.md b/src/integers/remainder.md index 97250df1..8196943e 100644 --- a/src/integers/remainder.md +++ b/src/integers/remainder.md @@ -3,19 +3,24 @@ To get the remainder of the division between two integers you can use the `%` operator. This is called the "modulo operator." +With `int`s `7 / 2` will give you `3`. That `3` is the "quotient" from the division +and is the number of times `2` can be taken out of `7`. This leaves a "remainder" of `1`. + +The modulo operator gives you that remainder. + ```java ~void main() { int x = 5; -// The remainder of 5 / 2 is 1 +// 5 / 2 is 2 with a remainder of 1 // y will be 1 int y = x % 2; -// The remainder of 5 / 3 is 2 +// 5 / 3 is 1 with a remainder of 2 // z will be 2 int z = x % 3; -System.out.println(x); -System.out.println(y); -System.out.println(z); +IO.println(x); +IO.println(y); +IO.println(z); ~} ``` @@ -26,31 +31,43 @@ For instance, say you wanted to count from 0 up to 3 and then go back to 0. ```java ~void main() { int value = 0; -System.out.println(value); +IO.println(value); // the remainder of (0 + 1) divided by 3 is 1 // value will be 1 value = (value + 1) % 3; -System.out.println(value); +IO.println(value); // the remainder of (1 + 1) divided by 3 is 2 // value will be 2 value = (value + 1) % 3; -System.out.println(value); +IO.println(value); // the remainder of (2 + 1) divided by 3 is 0 -// value will again be 0! +// value will again be 0. +// +// We never reach 3 because 3 divided by 3 +// always has a remainder of zero. value = (value + 1) % 3; -System.out.println(value); +IO.println(value); // the remainder of (0 + 1) divided by 3 is 1 // value will be 1 value = (value + 1) % 3; -System.out.println(value); +IO.println(value); // and so on. +// +// If you did this process with 5 you would go +// 0, 1, 2, 3, 4, 0, 1, ... +// +// If you did this process with 7 you would go +// 0, 1, 2, 3, 4, 5, 6, 0, 1, ... +// +// You always go back to the start just before you reach +// the number you are getting the remainder by. ~} ``` diff --git a/src/integers/shorthands_for_reassignment.md b/src/integers/shorthands_for_reassignment.md index 112a4027..f9229fad 100644 --- a/src/integers/shorthands_for_reassignment.md +++ b/src/integers/shorthands_for_reassignment.md @@ -6,10 +6,10 @@ computed value back into the variable. ```java ~void main() { int x = 2; -System.out.println(x); +IO.println(x); x = x * 5; // 10 -System.out.println(x); +IO.println(x); ~} ``` @@ -40,7 +40,7 @@ x /= 6; x %= 3; // Pop quiz! -System.out.println(x); +IO.println(x); ~} ``` @@ -50,18 +50,18 @@ has its own special shorthand. ```java ~void main() { int x = 0; -System.out.println(x); +IO.println(x); // Same as // x = x + 1; // x += 1; x++; -System.out.println(x); +IO.println(x); // Same as // x = x - 1; // x -= 1; x--; -System.out.println(x); +IO.println(x); ~} ``` diff --git a/src/integers/subtraction.md b/src/integers/subtraction.md index 109a0978..1342a66a 100644 --- a/src/integers/subtraction.md +++ b/src/integers/subtraction.md @@ -10,9 +10,9 @@ int y = x - 1; // z will be 1 int z = x - y; -System.out.println(x); -System.out.println(y); -System.out.println(z); +IO.println(x); +IO.println(y); +IO.println(z); ~} ``` @@ -24,7 +24,7 @@ int x = 5; // y will be 9 int y = x - -4; -System.out.println(x); -System.out.println(y); +IO.println(x); +IO.println(y); ~} ``` diff --git a/src/integers_ii.md b/src/integers_ii.md new file mode 100644 index 00000000..2bc833e8 --- /dev/null +++ b/src/integers_ii.md @@ -0,0 +1,7 @@ +# Integers II + + + +Integers never stop being relevant and, now that we +have covered static methods, we are ready to cover +some of the more useful ones that exist for working with integers. \ No newline at end of file diff --git a/src/integers_ii/base_16_integer_literals.md b/src/integers_ii/base_16_integer_literals.md new file mode 100644 index 00000000..06995481 --- /dev/null +++ b/src/integers_ii/base_16_integer_literals.md @@ -0,0 +1,36 @@ +# Base 16 Integer Literals + +In polite society we use a base 10 number system[^everything]. This means +we start counting at `0` and go up by one until we reach `9`. + +`0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9` + +Then when we go up one more we put a `1` at the front and "wrap around." + +`10`, `11`, `12`, etc. + +In a base 16 number system, sometimes called "hexadecimal", you keep going a little +bit more before wrapping around. + +`0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `A`, `B`, `C`, `D`, `E`, `F`, `10`, `11`, ..., `A5`, ..., `5F`, ... etc. + +To write an integer literal in Java which contains a hexadecimal number you write `0x` before the number. + +```java +~void main() { +int sixteen = 0x10; +IO.println(sixteen); + +int twoHundredFiftyFive = 0xFF; +IO.println(twoHundredFiftyFive); +~} +``` + +Hexadecimal numbers like this are very common when you are writing code that +deals with colors in RGB - Red, Green, Blue - format. `0xFFFFFF` is white, `0xFF0000` is red, `0xCCCCFF` is periwinkle, etc. + + + + +[^everything]: Okay to be real with you, every number system is a base 10 system. +Even if what you call "10" is what we would call "sixteen", you always wrap around your base when you write "10." If that doesn't make sense it doesn't matter, but it's fascinating to me. \ No newline at end of file diff --git a/src/integers_ii/challenges.md b/src/integers_ii/challenges.md new file mode 100644 index 00000000..0361a932 --- /dev/null +++ b/src/integers_ii/challenges.md @@ -0,0 +1,52 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +RGB Colors can be represented as base 16 numbers. + +The following numbers represent colors. Print them out in base 16 +to figure out what colors they represent. Rename the variables to be +the color names. + +You should be able to just look up the hex string. + +```java,editable +class Main { + void main() { + int colorA = 255; + int colorB = 65280; + int colorC = 16711935; + int colorD = 16776960; + + // CODE HERE + } +} +``` + +## Challenge 2. + +Rewrite the integer literals in the code from the previous challenge +to be base 16 integer literals. + +## Challenge 3. + +Add underscores in the following integer literals in the same places +you would write commas in regular math (every 3 digits 13,550,145). + +```java,editable +class Main { + void main() { + int a = 515326326; + int b = 32523522; + int c = 1221415133; + IO.println(a); + IO.println(b); + IO.println(c); + } +} +``` \ No newline at end of file diff --git a/src/integers_ii/header.png b/src/integers_ii/header.png new file mode 100644 index 00000000..9ea7e3b7 Binary files /dev/null and b/src/integers_ii/header.png differ diff --git a/src/integers_ii/integer_from_a_base_16_string.md b/src/integers_ii/integer_from_a_base_16_string.md new file mode 100644 index 00000000..08982afe --- /dev/null +++ b/src/integers_ii/integer_from_a_base_16_string.md @@ -0,0 +1,32 @@ +# Integer from a Base 16 String + +If you have a `String` which contains text that can be interpreted as a base 16 integer, you can convert it into an `int` by using `parseInt` and giving the +number `16` as an extra argument. + +```java +~void main() { +String text = "C"; + +int twelve = Integer.parseInt(text, 16); + +IO.println(twelve); +~} +``` + +This will not work if the number is prefixed by `0x` like it would be in your code. + +```java,panics +~void main() { +Integer.parseInt("0xC", 16); +~} +``` + +If you want to handle both hexadecimal numbers and regular base 10 numbers you should instead use `Integer.decode`. + +```java +~void main() { +IO.println(Integer.decode("0xC")); +IO.println(Integer.decode("0x19")); +IO.println(Integer.decode("19")); +~} +``` \ No newline at end of file diff --git a/src/integers_ii/integer_from_a_string.md b/src/integers_ii/integer_from_a_string.md new file mode 100644 index 00000000..f76e3a9b --- /dev/null +++ b/src/integers_ii/integer_from_a_string.md @@ -0,0 +1,45 @@ +# Integer from a String + +If you have a `String` which contains text that can be interpreted +as an integer you can convert it to an `int` using the `parseInt` +static method on `Integer`.[^already] + +```java +~void main() { +String text = "123"; + +int oneTwoThree = Integer.parseInt(text); + +IO.println(oneTwoThree); +~} +``` + +If what is in the `String` cannot be converted to an `int` that method +will throw a `NumberFormatException`. + +```java,panics +~void main() { +String word = "music"; + +int value = Integer.parseInt(word); +~} +``` + +If you want to handle input from a user that might not be interpretable +as an integer, you can use `try`/`catch` alongside delayed assignment. + +```java +~void main() { +String word = "seltzer"; + +int value; +try { + value = Integer.parseInt(word); +} catch (NumberFormatException e) { + value = 8; // Default value +} +~} +``` + +[^already]: You should actually already know this. I just never explained explicitly that parseInt was a static method or showed that you could catch _only_ the `NumberFormatException`. In my defense, `IO.readln` +at one point came far later in the book. \ No newline at end of file diff --git a/src/integers_ii/integer_to_a_base_16_string.md b/src/integers_ii/integer_to_a_base_16_string.md new file mode 100644 index 00000000..36e1e401 --- /dev/null +++ b/src/integers_ii/integer_to_a_base_16_string.md @@ -0,0 +1,14 @@ +# Integer to a Base 16 String + +If you have an integer you want to turn into a `String` in base 16 +integer you can use the `toHexString` method on `Integer`. + +```java +~void main() { +int x = 29411; +String xStr = Integer.toHexString(x); + +// 72e3 +IO.println(xStr); +~} +``` \ No newline at end of file diff --git a/src/integers_ii/integer_to_a_string.md b/src/integers_ii/integer_to_a_string.md new file mode 100644 index 00000000..15a5f4d3 --- /dev/null +++ b/src/integers_ii/integer_to_a_string.md @@ -0,0 +1,28 @@ +# Integer to a String + +If you have an integer you want to turn into a `String` you have two options. + +One is to "add it" to an empty string. + +```java +~void main() { +int x = 4; +String xStr = "" + x; + +IO.println(xStr); +~} +``` + +The other is to use the `toString` static method on `Integer`. + + +```java +~void main() { +int x = 4; +String xStr = Integer.toString(x); + +IO.println(xStr); +~} +``` + +I personally find the second one more direct, but opinions can reasonably vary. \ No newline at end of file diff --git a/src/integers_ii/underscores_in_integer_literals.md b/src/integers_ii/underscores_in_integer_literals.md new file mode 100644 index 00000000..e2189e67 --- /dev/null +++ b/src/integers_ii/underscores_in_integer_literals.md @@ -0,0 +1,27 @@ +# Underscores in Integer Literals + +When you are writing large numbers `1000000000` isn't very +visually distinct from `10000000000`. + +To help with the legibility you are allowed to insert underscores +between digits in an integer literal. + +```java +~void main() { +int x = 1_000_000_000; +int y = 10_000_000_000; + +IO.println(x); +IO.println(y); +~} +``` + +This works with hexadecimal integer literals as well. + +```java +~void main() { +int white = 0xFF_FF_FF; + +IO.println(Integer.toHexString(white)); +~} +``` \ No newline at end of file diff --git a/src/interface_extension.md b/src/interface_extension.md new file mode 100644 index 00000000..fbd7f536 --- /dev/null +++ b/src/interface_extension.md @@ -0,0 +1 @@ +# Interface Extension diff --git a/src/interfaces.md b/src/interfaces.md new file mode 100644 index 00000000..ec7a73b3 --- /dev/null +++ b/src/interfaces.md @@ -0,0 +1,17 @@ +# Interfaces + + + + +Generics let you write code that doesn't care +what is different between different things - you would accept a `String` +or an `Integer`, doesn't matter what is different between them. + +Interfaces do a related thing. They let you write code that takes advantage +of commonalities. + +```java +interface Dog { + void bark(); +} +``` diff --git a/src/interfaces/cat_dog.mp4 b/src/interfaces/cat_dog.mp4 new file mode 100644 index 00000000..d5f6c53c Binary files /dev/null and b/src/interfaces/cat_dog.mp4 differ diff --git a/src/interfaces/challenges.md b/src/interfaces/challenges.md new file mode 100644 index 00000000..1ebf6503 --- /dev/null +++ b/src/interfaces/challenges.md @@ -0,0 +1,111 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Declare an interface named `DoubleArray` which requires +two methods of classes that implementing classes: `length` and `get`. + +These methods should be specified to work the same as how `[]` and `.length` +work on a `double[]`. + +```java,no_run +interface DoubleArray { + // CODE HERE +} +``` + +## Challenge 2. + +Make a class that implements your `DoubleArray` interface +using a `double[]` in a field to perform all the operations. + +```java,editable +interface DoubleArray { + // CODE FROM PREVIOUS CHALLENGE +} + +class RealDoubleArray /* CODE HERE */ { + // CODE HERE +} + +class Main { + void main() { + DoubleArray arr = new RealDoubleArray(new double[] { + 1.0, 1.5, 2.0, 2.5, 3.0 + }); + + for (int i = 0; i < arr.length(); i++) { + IO.println("Got double value: " + arr.get(i)); + } + } +} +``` +## Challenge 3. + +Make a second class that implements `DoubleArray` but have this one +be backed by an `int[]` and perform widening conversions when returning values. + +```java,editable +interface DoubleArray { + // CODE FROM PREVIOUS CHALLENGE +} + +class FauxDoubleArray /* CODE HERE */ { + // CODE HERE +} + +class Main { + void main() { + DoubleArray arr = new FauxDoubleArray(new int[] { + 1, 2, 3, 4, 5 + }); + + for (int i = 0; i < arr.length(); i++) { + IO.println("Got double value: " + arr.get(i)); + } + } +} +``` + +## Challenge 4. + +Make an implementation of the following `Tarot` interface for [each tarot card +featured in JoJo's Bizzare Adventure](https://jojowiki.com/Tarot_Cards). + +```java +interface Tarot { + String symbolism(); + + String standUser(); +} +``` + +## Challenge 5. + +Make a method named `promptGeneric` which can prompt the user for information +but, based on if what they typed is properly interpretable, can reprompt them. + +As part of this make a `Parser` interface and at least two implementations: `IntParser` +and `DoubleParser`. + +```java +// CODE HERE + +class Main { + // CODE HERE + + void main() { + int x = promptGeneric( + "Give me an x: ", new IntParser() + ); + double y = promptGeneric( + "Give me a floating point y: ", new DoubleParser() + ); + } +} +``` \ No newline at end of file diff --git a/src/interfaces/fields.md b/src/interfaces/fields.md new file mode 100644 index 00000000..cb06804d --- /dev/null +++ b/src/interfaces/fields.md @@ -0,0 +1 @@ +# Fields diff --git a/src/interfaces/header.png b/src/interfaces/header.png new file mode 100644 index 00000000..0b46b5aa Binary files /dev/null and b/src/interfaces/header.png differ diff --git a/src/interfaces/implementation.md b/src/interfaces/implementation.md new file mode 100644 index 00000000..a96578dd --- /dev/null +++ b/src/interfaces/implementation.md @@ -0,0 +1,45 @@ +# Implementation + +All interfaces do[^fornow] is hold method declarations. + +If you want to have a class which "implements" +the interface you can do so by writing `implements` followed by the interface +name. + +```java +interface Dog { + void bark(); + + String fetch(String ball); +} + +class Mutt implements Dog { + +} +``` + +Then all you need to do is declare methods which match up with the methods defined in the interface. +Keep in mind that while you didn't write `public` in the interface, you need to write `public` +when implementing a method from an interface.[^all] + +```java +interface Dog { + void bark(); + + String fetch(String ball); +} + +class Mutt implements Dog { + public void bark() { + IO.println("Bark"); + } + + public String fetch(String ball) { + return ball + " (with drool)"; + } +} +``` + +[^fornow]: For now* + +[^all]: All methods that come from an interface must be `public`. \ No newline at end of file diff --git a/src/interfaces/instances.md b/src/interfaces/instances.md new file mode 100644 index 00000000..c2b105a8 --- /dev/null +++ b/src/interfaces/instances.md @@ -0,0 +1 @@ +# Instances diff --git a/src/interfaces/interface_declaration.md b/src/interfaces/interface_declaration.md new file mode 100644 index 00000000..a88ad8c0 --- /dev/null +++ b/src/interfaces/interface_declaration.md @@ -0,0 +1,19 @@ +# Interface Declaration + +To declare an interface you write the word `interface` followed by the name of the interface +and `{}`. + +```java +interface Dog {} +``` + +Inside of the `{}` you can write the signatures for methods followed by a `;`. That means +no method body and no `public` or `private` modifiers. + +```java,no_run +interface Dog { + void bark(); + + String fetch(String ball); +} +``` diff --git a/src/interfaces/methods.md b/src/interfaces/methods.md new file mode 100644 index 00000000..8f834183 --- /dev/null +++ b/src/interfaces/methods.md @@ -0,0 +1 @@ +# Methods diff --git a/src/interfaces/multiple_implementations.md b/src/interfaces/multiple_implementations.md new file mode 100644 index 00000000..d6c0784e --- /dev/null +++ b/src/interfaces/multiple_implementations.md @@ -0,0 +1,57 @@ +# Multiple Implementations + +Interfaces can be implemented any number of times. This means +that code which accepts an interface can't truly know the specifics +of how methods will work. + +This is a problem if you want maximum predictability but its +also the whole point of using an interface over a regular class. +You can write code that depends on a few key methods being defined +and be flexible to different ways of defining those methods. + +```java +interface Dog { + void bark(); + + String fetch(String ball); +} + +class Mutt implements Dog { + @Override + public void bark() { + IO.println("Bark"); + } + + @Override + public String fetch(String ball) { + return ball + " (with drool)"; + } +} + +class Cat implements Dog { + @Override + public void bark() { + IO.println("Meow"); + } + + @Override + public String fetch(String ball) { + return "no."; + } +} + +void barkAndFetch(Dog dog) { + dog.bark(); + IO.println(dog.fetch("Ball")); +} + +void main() { + barkAndFetch(new Mutt()); + barkAndFetch(new Cat()); +} +``` + +
+ Think a cat won't behave like a dog? + +
diff --git a/src/interfaces/naming.md b/src/interfaces/naming.md new file mode 100644 index 00000000..15a7eefa --- /dev/null +++ b/src/interfaces/naming.md @@ -0,0 +1,16 @@ +# Naming + +Interfaces are named in the same way as classes - `LikeThis`. + +In the wild west of the real world you might see people prefix any interface +name with `I`, for interface. + +In these cases instead of `Dog` you would see `IDog`. Instead of `PartyAnimal` you would +see `IPartyAnimal` and so on. + +The reason someone might do this is if they think it is worthwhile to have a visual indicator +of whether a type represents an interface or an actual class. Personally, I don't think that is +too useful, but there is nothing horrible about it. + +Just judge the social context you are in and don't hold it against people too hard if they do it a way +you don't like. \ No newline at end of file diff --git a/src/interfaces/override.md b/src/interfaces/override.md new file mode 100644 index 00000000..1c180204 --- /dev/null +++ b/src/interfaces/override.md @@ -0,0 +1,30 @@ +# @Override + +Just like when defining your own `equals`, `hashCode`, and `toString` you +can use `@Override` when implementing methods that come from an interface. + +```java +interface Dog { + void bark(); + + String fetch(String ball); +} + +class Mutt implements Dog { + @Override + public void bark() { + IO.println("Bark"); + } + + @Override + public String fetch(String ball) { + return ball + " (with drool)"; + } +} +``` + + +Right now there isn't a mechanical use for this since Java will yell at you if +you defined the interface method wrong anyways, but there will be later. A small benefit +is that it makes it easier to tell at a glance which methods come from an interface +and which do not. \ No newline at end of file diff --git a/src/interfaces/subtypes.md b/src/interfaces/subtypes.md new file mode 100644 index 00000000..b6207185 --- /dev/null +++ b/src/interfaces/subtypes.md @@ -0,0 +1,63 @@ +# Subtypes + +Just as everything is a subtype of `Object`, anything which +implements an interface is a subtype of that interface. + +For the following code this means that any field or variable +which holds a `Dog` can be assigned to an instance of `Mutt`. + +```java +interface Dog { + void bark(); + + String fetch(String ball); +} + +class Mutt implements Dog { + @Override + public void bark() { + IO.println("Bark"); + } + + @Override + public String fetch(String ball) { + return ball + " (with drool)"; + } +} + +void main() { + Dog dog = new Mutt(); +} +``` + +Through a `Dog` variable you will be able to call any methods defined on the interface. +These will use the actual underlying implementation - in this case from `Mutt`. + +```java +interface Dog { + void bark(); + + String fetch(String ball); +} + +class Mutt implements Dog { + @Override + public void bark() { + IO.println("Bark"); + } + + @Override + public String fetch(String ball) { + return ball + " (with drool)"; + } +} + +void main() { + Dog dog = new Mutt(); + + dog.bark(); + + IO.println(dog.fetch("Ball")); +} +``` + diff --git a/src/interfaces_ii.md b/src/interfaces_ii.md new file mode 100644 index 00000000..d153bf43 --- /dev/null +++ b/src/interfaces_ii.md @@ -0,0 +1,10 @@ +# Interfaces II + + + +Interfaces let you describe a common set of methods shared +by different implementing classes. + + +They can do slightly more than this though and it's helpful +to know about. \ No newline at end of file diff --git a/src/interfaces_ii/challenges.md b/src/interfaces_ii/challenges.md new file mode 100644 index 00000000..dd4294c4 --- /dev/null +++ b/src/interfaces_ii/challenges.md @@ -0,0 +1,100 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Create an interface named `TabbyCat`. It should +extend the provided `Cat` interface and provide +a `lounge` method along with a default implementation of that +method. + +```java +interface Cat { + void purr(); +} + +// CODE HERE + +class Garfield implements TabbyCat { + @Override + public void purr() { + IO.println("mmmm lasagna"); + } +} + +class Main { + void main() { + TabbyCat c = new Garfield(); + // Should come in via a default method. + c.lounge(); + } +} +``` + +## Challenge 2. + +Add a static field to the `Cat` interface +which indicates the healthy amount of lasagna for a cat to +consume.[^zero] + +```java +interface Cat { + void purr(); +} + +class Main { + void main() { + // 0 + IO.println(Cat.HEALTHY_AMOUNT_OF_LASAGNA); + } +} +``` + +## Challenge 3. + +Put a static method on the `Cat` interface named `garfield" +which returns an instance of `TabbyCat`. + +```java,no_run +public interface Cat { + // CODE HERE +} +``` + +```java,no_run +public interface TabbyCat extends Cat { + // CODE FROM PREVIOUS CHALLENGES +} +``` + +```java,no_run +class Garfield implements TabbyCat { + @Override + public void purr() { + IO.println("mmmm lasagna"); + } +} +``` + +```java +public class Main { + void main() { + TabbyCat tc = Cat.garfield(); + tc.lounge(); + } +} +``` + +Note that this gives you a way to expose a `Garfield` instance +to other packages, even if the `Garfield` class itself +is `non-public`.[^unrunnable] + +[^zero]: Zero, it can literally kill them. + +[^unrunnable]: Apologies for the inconvenience. To make the point about +package visibility I had to make the code in browser non runnable +or editable. You should be able to manage at this point though. \ No newline at end of file diff --git a/src/interfaces_ii/default_methods.md b/src/interfaces_ii/default_methods.md new file mode 100644 index 00000000..fc5b2489 --- /dev/null +++ b/src/interfaces_ii/default_methods.md @@ -0,0 +1,78 @@ +# Default Methods + +Interfaces can specify a `default` implementation +for a method. + +```java,no_run +interface Dog { + void bark(); + + default void barkLoudly() { + IO.print("(loudly) "); + bark(); + } +} +``` + +Classes which implement interfaces do not need an explicit implementation +for methods which have a default.[^all] + +```java,no_run +interface Dog { + void bark(); + + default void barkLoudly() { + IO.print("(loudly) "); + bark(); + } +} + +class Poodle implements Dog { + @Override + public void bark() { + IO.println("bark!") + } +} +``` + +If the default implementation is not what you want, then that implementation +can be overrided. + +```java,no_run +interface Dog { + void bark(); + + default void barkLoudly() { + IO.print("(loudly) "); + bark(); + } +} + +class Poodle implements Dog { + @Override + public void bark() { + IO.println("bark!") + } + + @Override + public void barkLoudly() { + IO.println("BARK!") + } +} +``` + +If all of the methods on an interface have a default then you don't need to provide an implementation for any of them.[^allmethods] + +```java +interface Cat { + default void meow() { + IO.println("meow"); + } +} + +class Tabby implements Cat { + // Nothing needed to implement Cat +} +``` + +[^allmethods]: This is rarely useful, but not never. \ No newline at end of file diff --git a/src/interfaces_ii/header.png b/src/interfaces_ii/header.png new file mode 100644 index 00000000..5368221d Binary files /dev/null and b/src/interfaces_ii/header.png differ diff --git a/src/interfaces_ii/interface_extension.md b/src/interfaces_ii/interface_extension.md new file mode 100644 index 00000000..d435c6b3 --- /dev/null +++ b/src/interfaces_ii/interface_extension.md @@ -0,0 +1,70 @@ +# Interface Extension + +Interfaces can extend other interfaces. + +```java,no_run +interface Dog { + void bark(); +} + +record Color(int r, int g, int b) {} + +interface ColoredDog extends Dog { + Color color(); +} +``` + +This means a few things. First an implementing class +must specify all the methods from both. + +```java,no_run +class Clifford implements ColoredDog { + @Override + public void bark() { // Must define the methods on Dog + IO.println("BARK BARK"); + } + + @Override + public Color color() { // As well as on ColoredDog + return new Color(255, 0, 0); // Red + } +} +``` + +Second, interface extension "establishes a subtyping relationship." +Something which implements a sub-interface can be used in a +place expecting the super-interface. + +```java,no_run +void main() { + Clifford clifford = new Clifford(); + clifford.bark(); + IO.println(clifford.color()); + + IO.println("-------"); + // clifford is a "ColoredDog" + ColoredDog coloredDog = clifford; + // So all the methods on ColoredDog are available + coloredDog.bark(); + IO.println(coloredDog.color()); + + + IO.println("-------"); + // all "ColoredDog"s are also "Dog"s + Dog dog = coloredDog; + // So you can use the methods from Dog, but not any + // from ColoredDog + dog.bark(); + // IO.println(dog.color()); - Won't work + + IO.println("-------"); + // and all "Dog"s are "Object"s + Object o = dog; + // So you only have access to the methods from Object unless you + // use instanceof to recover the actual type of the object + if (o instanceof ColoredDog c) { + c.bark(); + IO.println(c.color()); + } +} +``` diff --git a/src/interfaces_ii/static_fields.md b/src/interfaces_ii/static_fields.md new file mode 100644 index 00000000..996b9afb --- /dev/null +++ b/src/interfaces_ii/static_fields.md @@ -0,0 +1,18 @@ +# Static Fields + +If there are constants associated with or useful for an interface, +those can be attached to the interface declaration as `public static final` fields. + +The declaration of these looks the same as declaring a normal field in a class. +The `public static final` are implied. + +```java +interface Biped { + // This does the same as + // "public static final int NUMBER_OF_FEET = 2" + // in a normal class. + int NUMBER_OF_FEET = 2; + + void walk(); +} +``` \ No newline at end of file diff --git a/src/interfaces_ii/static_methods.md b/src/interfaces_ii/static_methods.md new file mode 100644 index 00000000..cdd780e7 --- /dev/null +++ b/src/interfaces_ii/static_methods.md @@ -0,0 +1,67 @@ +# Static Methods + +Interfaces may specify `static` methods. These work similarly to static methods +on classes in that they can be used without an actual instance of a class. + +```java +interface Animal { + static boolean allowedLooseInHouse(String species) { + if ("dog".equals(species) || "cat".equals(species)) { + return true; + } + else { + // My cousin has a Pig that we were all afraid + // was going to straight up eat her child. + // + // The child got old enough that it's not a concern, + // but good God. + return false; + } + } +} + +void main() { + IO.println(Animal.allowedLooseInHouse("dog")); + IO.println(Animal.allowedLooseInHouse("cat")); + IO.println(Animal.allowedLooseInHouse("pig")); +} +``` + +You may use this mechanic for any reason, but often it is most convenient +for "factory methods" - methods which make it easy to construct objects +related to the interface hierarchy. + +A prime example of this is `List.of`. `of` is a static method defined on the `List` +interface. + +```java +void main() { + // of will return a List, but code using this + // factory doesn't see the actual implementing class + List critters = List.of("dog", "cat", "bat"); + IO.println(critters); +} +``` + +If the logic in a static interface method gets complex enough, it is also allowed +to define a private static method. + +This is unique for interfaces, as usually +everything in them is considered `public`. +Even without writing `public`, a `static` interface method is by default a public method. + +```java,no_run +interface Animal { + private static void eat() { + } + + private static void sleep() { + } + + static void live() { + eat(); + sleep(); + } +} +``` + diff --git a/src/just.md b/src/just.md new file mode 100644 index 00000000..63830999 --- /dev/null +++ b/src/just.md @@ -0,0 +1,36 @@ +# just + + + + +As you might be noticing, commands in the terminal can get quite long. + +Not only are you liable to make a mistake typing out `java --module-path a:bunch:of:files --add-modules ALL-MODULE-PATH src/Main.java` for the 20th time, you are also just going to get annoyed doing so.[^nature] + +To remember what commands to run to do certain tasks I recommend using a tool called "[just](https://github.com/casey/just)." + +```justfile,no_run +help: + just --list + +clean: + rm -rf output + +compile: clean + javac \ + -d output/javac \ + --module-source-path "./*/src" \ + --module dan.da.dan + +package: compile + jar \ + --create \ + --file output/jar/dan.da.dan.jar \ + -C output/javac/dan.da.dan . +``` + +This section is ultimately optional, but you will quickly see a need for _something_ +that helps you remember commands. + +[^nature]: Generally when something is "your job" or "the right way" you do need to just suck it up +and do the manual labor. But there are limits to this, human nature being what it is. \ No newline at end of file diff --git a/src/just/challenges.md b/src/just/challenges.md new file mode 100644 index 00000000..2d92f1dc --- /dev/null +++ b/src/just/challenges.md @@ -0,0 +1,34 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Make a Justfile with a recipe named `hello` that runs `echo "hello"`. + +Test it by running `just hello`. + +## Challenge 2. + +Make a `run` recipe that launches one of your programs. + +## Challenge 3. + +Take the commands you used to compile one of your projects +and put them into a `Justfile`. + +Ensure that `just compile` will compile your code and `just clean` will +delete any output directories. + +## Challenge 4. + +Expand the `Justfile` from the previous challenge to also package +your code into a JAR. + +## Challenge 5. + +Expand the `Justfile` from the previous challenge to compile +and package multiple modules, not just one. \ No newline at end of file diff --git a/src/just/dependencies.md b/src/just/dependencies.md new file mode 100644 index 00000000..6d89d977 --- /dev/null +++ b/src/just/dependencies.md @@ -0,0 +1,34 @@ +# Dependencies + +If there are recipes you want to run before other recipies +you specify them after the `:` of the recipe declaration. + +This can be useful for things like always removing an output +directory before compiling or packaging. + +```justfile,no_run +help: + just --list + +clean: + rm -rf output + +compile: clean + javac \ + -d output/javac \ + --module-source-path "./*/src" \ + --module dan.da.dan + +package: compile + jar \ + --create \ + --file output/jar/dan.da.dan.jar \ + -C output/javac/dan.da.dan . +``` + +So in the example above, all the commands in the "`compile`" recipe +run before the commands in the "`package`" recipe. Before "`compile`" +the commands in the "`clean`" recipe run. + +This is useful for making these sorts of "chains" of commands, but isn't strictly required. + diff --git a/src/just/documentation_comments.md b/src/just/documentation_comments.md new file mode 100644 index 00000000..41f6f11a --- /dev/null +++ b/src/just/documentation_comments.md @@ -0,0 +1,40 @@ +# Documentation Comments + +You can describe what a recipe does by putting a `#` followed by the description +above the recipe + +```justfile,no_run +# Lists available recipes +help: + just --list + +# Cleans build output +clean: + rm -rf output + +# Compiles the code +compile: clean + javac \ + -d output/javac \ + --module-source-path "./*/src" \ + --module dan.da.dan + +# Packages the code +package: compile + jar \ + --create \ + --file output/jar/dan.da.dan.jar \ + -C output/javac/dan.da.dan . +``` + +These documentation comments will appear alongside the recipe name with `just --list`, which +is the command our `help` recipe runs. + +```text,no_run +$ just +Available recipes: + clean # Cleans build output + compile # Compiles the code + help # Lists available recipes + package # Packages the code +``` \ No newline at end of file diff --git a/src/just/further_reading.md b/src/just/further_reading.md new file mode 100644 index 00000000..6ec51755 --- /dev/null +++ b/src/just/further_reading.md @@ -0,0 +1,9 @@ +# Further Reading + +What has been covered so far should be enough for our stated goal of +remembering what commands to run to do stuff. + +But there is more to `just` than that. If you are interested, you should start +by reading the documentation put out by the people who develop it. + +[https://just.systems/man/en/](https://just.systems/man/en/) \ No newline at end of file diff --git a/src/just/header.png b/src/just/header.png new file mode 100644 index 00000000..abbfe973 Binary files /dev/null and b/src/just/header.png differ diff --git a/src/just/installation.md b/src/just/installation.md new file mode 100644 index 00000000..fa60c05d --- /dev/null +++ b/src/just/installation.md @@ -0,0 +1,9 @@ +# Installation + +As you might have noted, `just` is not a tool that comes with Java. + +This means that you will need to install it yourself. The process +for doing so differs based on what kind of computer you have but, +if you've made it this far, you should have it in you to figure it out. + +You can find instructions from the authors starting at [https://just.systems/man/en/prerequisites.html](https://just.systems/man/en/prerequisites.html). \ No newline at end of file diff --git a/src/just/justfile.md b/src/just/justfile.md new file mode 100644 index 00000000..cef47437 --- /dev/null +++ b/src/just/justfile.md @@ -0,0 +1,24 @@ +# Justfile + +To use `just`, first make a file named `Justfile` and put it at the top of your project. + +```text,no_run +project/ + dan.da.dan/ + src/ + module-info.java + characters/ + MoMo.java + JiJi.java + Justfile +``` + +In this file put the following contents. + +```justfile,no_run +help: + just --list +``` + +This makes it so that if you run `just` or `just help` +it will list the "recipes" available for the project. \ No newline at end of file diff --git a/src/just/recipes.md b/src/just/recipes.md new file mode 100644 index 00000000..226e6216 --- /dev/null +++ b/src/just/recipes.md @@ -0,0 +1,37 @@ +# Recipes + +A Justfile contains multiple "recipes." +These are lists of commands to run when you type `just `. + +```justfile,no_run +help: + just --list + +compile: + rm -rf output + javac \ + -d output \ + --module-source-path "./*/src" \ + --module dan.da.dan +``` + +So in the example above `just compile` will first run `rm -rf output` followed by `javac <...>`. + +The first recipe in a file is run by default when you type `just`. This is why we put +the `help` recipe first in the file - it is convenient. + +```text,no_run +$ just +just --list +Available recipes: + compile + help +$ just help +just --list +Available recipes: + compile + help +``` + + + diff --git a/src/lambdas.md b/src/lambdas.md new file mode 100644 index 00000000..3f091742 --- /dev/null +++ b/src/lambdas.md @@ -0,0 +1,37 @@ +# Lambdas + + + + +Making an implementation of `interface`s that +only require one method turns out to be a common +task for a Java developer. + +```java,no_run +interface Band { + void playHitSong(); +} + +class Starcadian implements Band { + @Override + public void playHitSong() { + IO.println("ultralove"); + } +} +``` + +As such there is a mechanism called "lambdas" +which lowers the effort required to do so. + +```java +interface Band { + void playHitSong(); +} + +class Main { + void main() { + Band starcadian = () -> IO.println("ultralove"); + starcadian.playHitSong(); + } +} +``` diff --git "a/src/lambdas/Screenshot 2025-08-25 at 7.04.29\342\200\257AM.png" "b/src/lambdas/Screenshot 2025-08-25 at 7.04.29\342\200\257AM.png" new file mode 100644 index 00000000..4f8cffe1 Binary files /dev/null and "b/src/lambdas/Screenshot 2025-08-25 at 7.04.29\342\200\257AM.png" differ diff --git a/src/lambdas/arguments.md b/src/lambdas/arguments.md new file mode 100644 index 00000000..ffcdba21 --- /dev/null +++ b/src/lambdas/arguments.md @@ -0,0 +1,70 @@ +# Arguments + +If the method on the functional interface takes arguments +you can include those in the parentheses before the `->`. + +```java +@FunctionalInterface +interface Singer { + void sing(String title, double volume); +} + +class Main { + void main() { + Singer woodkid = (String title, double volume) -> { + IO.println( + title + + " by woodkid is now " + + (volume > 10 ? "BLASTING" : "playing" + ) + ); + }; + + // AC Revelations wasn't the best game + // but that trailer was awesome. + woodkid.sing("Iron", 11); + } +} +``` + +If it won't lead to any ambiguity Java also allows you to omit the types +from the argument list. + +```java +@FunctionalInterface +interface Singer { + void sing(String title, double volume); +} + +class Main { + void main() { + Singer twrp = (title, volume) -> { + IO.println( + title + + " by twrp is now " + + (volume > 10 ? "BLASTING" : "playing" + ) + ); + }; + + woodkid.sing("Pets", 7); + } +} +``` + +Further, if there is only one argument you may omit even the `()`. + +```java +@FunctionalInterface +interface Conductor { + void conduct(String tempo); +} + +class Main { + void main() { + Conductor jkSimmons = tempo -> IO.println("Not my tempo.") + + jkSimmons.conduct("4/4"); + } +} +``` \ No newline at end of file diff --git a/src/lambdas/built_in_functional_interfaces.md b/src/lambdas/built_in_functional_interfaces.md new file mode 100644 index 00000000..0a89e277 --- /dev/null +++ b/src/lambdas/built_in_functional_interfaces.md @@ -0,0 +1,58 @@ +# Built-In Functional Interfaces + +Java comes with many functional interfaces as part of its standard library. + +You are under no obligation to use any of these. They are convenient because +they are already at hand, but they tend to have very "generic" +names. It is often better to make your own. That being said: + +One functional interface that comes with Java is `Runnable`[^javaone]. +This describes a method that takes no arguments and returns no values. + +```java,no_run +@FunctionalInterface +public interface Runnable { + void run(); +} +``` + +Another is `Function`. This is a generic interface that describes +a function taking only one argument. `Function` is notable +in that it is an example of a functional interface that contains +both default methods and static methods. + +```java,no_run +@FunctionalInterface +public interface Function { + R apply(T t); + + default Function compose(Function before) { + Objects.requireNonNull(before); + return (V v) -> apply(before.apply(v)); + } + + default Function andThen(Function after) { + Objects.requireNonNull(after); + return (T t) -> after.apply(apply(t)); + } + + static Function identity() { + return t -> t; + } +} +``` + +Then there is `Consumer` for something that takes an argument and returns something. `BiFunction` +and `BiConsumer` for things that take two arguments, `Supplier` for something +that takes nothing and returns a value... and so on. + +Really the important thing is just to know that some of these exist. There are only +slim benefits to using them over interfaces you make yourself. Lambdas and method +references work the same for everyone. + + +[^javaone]: I think it is really neat that Runnable came with Java 1.0 - nearly two +decades before lambdas were in the language - and yet works perfectly fine with them. +That's design bay-bee[^powers]. + +[^powers]: Read that in the Austin Powers voice. \ No newline at end of file diff --git a/src/lambdas/challenges.md b/src/lambdas/challenges.md new file mode 100644 index 00000000..4af22ee0 --- /dev/null +++ b/src/lambdas/challenges.md @@ -0,0 +1,125 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Replace the `Sunflower` class with a lambda expression. + +```java,editable +@FunctionalInterface +interface Flower { + void bloom(); +} + +class Sunflower implements Flower { + @Override + public void bloom() { + IO.println("bright yellow flower."); + } +} + +class Main { + void bloomField(Flower flower) { + for (int i = 0; i < 15; i++) { + flower.bloom(); + } + } + + void main() { + Flower sunflower = new Sunflower(); + bloomField(sunflower); + } +} +``` + +## Challenge 2. + +Add a new method to the `Flower` interface above while +keeping the `@FunctionalInterface` annotation. What error does Java +give you? + +What errors do you see if you remove the annotation but still have +lambdas in your code? + +## Challenge 3. + +Change the lambda expression you used for the `Flower` interface +to a method reference. + +## Challenge 4. + +One of these snippets of code will throw a `NullPointerException`. The other +will not. Which do you think will do which? Why do you think that is? + +```java +@FunctionalInterface +interface Screamer { + void scream(); +} + +class Lemon { + void grab() { + IO.println("UNACCEPTABLE!"); + } +} + +class Main { + void main() { + Lemon lemon = null; + Screamer s = lemon::grab; + } +} +``` + +```java +@FunctionalInterface +interface Screamer { + void scream(); +} + +class Lemon { + void grab() { + IO.println("UNACCEPTABLE!"); + } +} + +class Main { + void main() { + Lemon lemon = null; + Screamer s = () -> lemon.grab(); + } +} +``` +## Challenge 4. + +You should have done this challenge before: + +> Make a method named `promptGeneric` which can prompt the user for information +> but, based on if what they typed is properly interpretable, can reprompt them. +> +> As part of this make a `Parser` interface and at least two implementations: `IntParser` +> and `DoubleParser`. + +If not, do so now. Then in either case replace the usages of `IntParser` and `DoubleParser` +with method references. + +```java +// CODE HERE + +class Main { + // CODE HERE + + void main() { + int x = promptGeneric( + "Give me an x: ", new IntParser() + ); + double y = promptGeneric( + "Give me a floating point y: ", new DoubleParser() + ); + } +} +``` \ No newline at end of file diff --git a/src/lambdas/checked_exceptions.md b/src/lambdas/checked_exceptions.md new file mode 100644 index 00000000..9f645363 --- /dev/null +++ b/src/lambdas/checked_exceptions.md @@ -0,0 +1,99 @@ +# Checked Exceptions + +One thing that might catch you off guard is how lambdas +and method references interact with checked exceptions. + +Normally if the implementation of a functional interface throws +an exception that is allowed. + +```java +class Main { + void main() { + Function parseObject + = Integer::parseInt; + + int x = parseObject.apply("1657"); + + IO.println(x); + } +} +``` + +But if the implementation throws a checked exception Java +will complain. + +This is because checked exceptions need to be declared in a method signature. +If the functional interface you are making an implementation +does not declare a checked exception is thrown in its single method + +```java,does_not_compile +import module java.base; + +@FunctionalInterface +interface Loader { + T load(); +} + +class Main { + void main() throws IOException { + Files.writeString(Path.of("one.txt"), "One and only one"); + // Files.readString can throw an IOException and that is checked + Loader loader = () -> Files.readString(Path.of("one.txt")); + IO.println(loader.load()); + } +} +``` + +One solution is to edit the functional interface such that it declares +the thrown exception. + + +```java +import module java.base; + +@FunctionalInterface +interface Loader { + // If we add this throws it will be fine. + T load() throws IOException; +} + +class Main { + void main() throws IOException { + Files.writeString(Path.of("one.txt"), "One and only one"); + Loader loader = () -> Files.readString(Path.of("one.txt")); + IO.println(loader.load()); + } +} +``` + +The other solution is to catch and rethrow any exceptions as unchecked. + + +```java,does_not_compile +import module java.base; + +@FunctionalInterface +interface Loader { + T load(); +} + +class Main { + void main() throws IOException { + Files.writeString(Path.of("one.txt"), "One and only one"); + Loader loader = () -> { + try { + return Files.readString(Path.of("one.txt")); + } catch (IOException e) { + // No issue throwing unchecked exceptions + throw new UncheckedIOException(e); + } + }; + IO.println(loader.load()); + } +} +``` + +Which of those two solutions you pick will be context dependent. +If you don't control the implementation of the functional interface +(such as with the built-in `Runnable`, `Function`, `Supplier`, etc.) +your best option is the second one. \ No newline at end of file diff --git a/src/lambdas/constructor_references.md b/src/lambdas/constructor_references.md new file mode 100644 index 00000000..8c9225e5 --- /dev/null +++ b/src/lambdas/constructor_references.md @@ -0,0 +1,25 @@ +# Constructor References + +You can make a reference to the constructor +of a class by using `::new`. + +```java,no_run +// interface Supplier { +// T get(); +// } + +Supplier> listSupplier = + ArrayList::new; +``` + +This will try and select a constructor overload that +matches the method expected on the interface. + +If you need to pass arguments to the constructor then +you should use a regular lambda. + +```java,no_run +Supplier> listSupplier = () -> { + return new ArrayList<>(10); +}; +``` \ No newline at end of file diff --git a/src/lambdas/function.md b/src/lambdas/function.md new file mode 100644 index 00000000..331a5644 --- /dev/null +++ b/src/lambdas/function.md @@ -0,0 +1 @@ +# Function diff --git a/src/lambdas/functional_interface_annotation.md b/src/lambdas/functional_interface_annotation.md new file mode 100644 index 00000000..819e64b4 --- /dev/null +++ b/src/lambdas/functional_interface_annotation.md @@ -0,0 +1,25 @@ +# @FunctionalInterface + +If an interface is marked with the `@FunctionalInterface` annotation, +Java will verify that it fulfils the requirements for a functional interface. + +```java +@FunctionalInterface +interface BankRunner { + void runOnBank(); +} +~void main() {} +``` + +This is similar to the `@Override` annotation in that it doesn't affect how code works +but just adds in an extra guard rail. + +```java,does_not_compile +@FunctionalInterface +interface BankRunner { + // More than one required method, will error + void runOnBank(); + int applyInflation(int money); +} +~void main() {} +``` \ No newline at end of file diff --git a/src/lambdas/functional_interfaces.md b/src/lambdas/functional_interfaces.md new file mode 100644 index 00000000..e59ab326 --- /dev/null +++ b/src/lambdas/functional_interfaces.md @@ -0,0 +1,40 @@ +# Functional Interfaces + +If an interface has only one method that needs to be implemented we would call that a "functional interface."[^SAM] + +```java +interface Band { + // Only one method to implement + // "single abstract method" + void playHitSong(); +} +``` + + +Any other methods on the interface must be `default` or `static`. + +```java +interface Band { + void playHitSong(); + + // Neither the default or static method are considered + default void encore() { + this.playHitSong(); + this.playHitSong(); + } + + static int turnDial(int level) { + if (level == 10) { + return 11; + } + else { + return level; + } + } +} +``` + +Functions take input and return an output. We call them functional interfaces because you can treat them as being functions whose input and output are the same as that one method to be implemented. + + +[^SAM]: You might also see these referred to as SAM interfaces. SAM for Single Abstract Method. \ No newline at end of file diff --git a/src/lambdas/header.png b/src/lambdas/header.png new file mode 100644 index 00000000..066402cb Binary files /dev/null and b/src/lambdas/header.png differ diff --git a/src/lambdas/inference.md b/src/lambdas/inference.md new file mode 100644 index 00000000..209db3b9 --- /dev/null +++ b/src/lambdas/inference.md @@ -0,0 +1,33 @@ +# Inference + +If Java cannot figure out exactly what functional interface you want +to use it will not allow you to use a lambda or a method reference. + +```java,does_not_compile +class Main { + void main() { + // Does not know what type the "var" should be + var ride = () -> IO.println("cruisin'"); + } +} +``` + +To resolve this you need to give Java a hint as to what +interface it should resolve to. In addition to simply +giving explicit types to variables and fields +you can do this by "casting" the expression +to a functional interface. + +```java +@FunctionalInterface +interface Trip { + void takeOff(); +} + +class Main { + void main() { + var ride = (Trip) () -> IO.println("cruisin'"); + ride.takeOff(); + } +} +``` \ No newline at end of file diff --git a/src/lambdas/lambda_expressions.md b/src/lambdas/lambda_expressions.md new file mode 100644 index 00000000..39b47a42 --- /dev/null +++ b/src/lambdas/lambda_expressions.md @@ -0,0 +1,39 @@ +# Lambda Expressions + +To make an implementation of a functional interface +you can use a "lambda expression." + +If the method on the functional interface +takes no arguments, you write `() ->` followed +by the code to run when that method is called. + +```java +@FunctionalInterface +interface Band { + void playHitSong(); +} + +class Main { + void main() { + Band twrp = () -> { + IO.println("Got no commitments today"); + IO.println("No work all play"); + } + } +} +``` + +Just like with `switch` if there is only one line to run you may omit the `{}`s. + +```java +@FunctionalInterface +interface Band { + void playHitSong(); +} + +class Main { + void main() { + Band theProtomen = () -> IO.println("Light up the Night!"); + } +} +``` \ No newline at end of file diff --git a/src/lambdas/method_references.md b/src/lambdas/method_references.md new file mode 100644 index 00000000..ae472e66 --- /dev/null +++ b/src/lambdas/method_references.md @@ -0,0 +1,101 @@ +# Method References + +Often it will make sense to separate the code +for a lambda into its own method. + +```java +interface Drummer { + void drum(); +} + +class Main { + void doDrum() { + IO.println("ratatatatat"); + IO.println("crash!"); + IO.println("crash!"); + IO.println("ratatatatat"); + } + + void main() { + Drummer drummer = () -> doDrum(); + drummer.drum(); + } +} +``` + +When you have a method that matches[^exactly] the method required by a functional interface +you can use a "method reference." + +For instance methods this will look like `instance::methodName`. So inside a method +this can be `this::methodName` but you can also have a `variableName::methodName` + +```java +interface Drummer { + void drum(); +} + +interface Musician { + void play(); +} + +class Main { + void doDrum() { + IO.println("ratatatatat"); + IO.println("crash!"); + IO.println("crash!"); + IO.println("ratatatatat"); + } + + void main() { + Drummer drummer = this::doDrum; + drummer.drum(); + + Musician musician = drummer::drum; + musician.play(); + } +} +``` + +For static methods it will look like `ClassName::doDrum`. + +```java +interface Drummer { + void drum(); +} + +class Main { + static void doDrum() { + IO.println("ratatatatat"); + IO.println("crash!"); + IO.println("crash!"); + IO.println("ratatatatat"); + } + + void main() { + Drummer drummer = Drummer::doDrum; + drummer.drum(); + } +} +``` + +And finally for instance methods where the the functional interface's method takes the instance +explicitly as an argument it will look the same as a static method reference. + +```java +interface StringTransformer { + String transform(String s); +} + +class Main { + void main() { + // Instance method, but looks like a reference + // to a static method that just so happens to + // take a String as a first argument + StringTransformer t = String::toUpperCase; + IO.println(t.transform("ratatatatat")); + } +} +``` + + +[^exactly]: It doesn't have to match _exactly_ but describing the exact matching mechanism isn't that important. Play it by ear. \ No newline at end of file diff --git a/src/lambdas/return.md b/src/lambdas/return.md new file mode 100644 index 00000000..75c5a70b --- /dev/null +++ b/src/lambdas/return.md @@ -0,0 +1,56 @@ +# Return + + +If the method on the functional interface has a return value +that is not `void` you can return a value from a lambda +the same as from a method. + +This will not return from any enclosing method. + +```java +@FunctionalInterface +interface Farmer { + String farm(String item); +} + +class Main { + void main() { + Farmer jeremyClarkson = item -> { + if (Math.random() < 0.1) { + return "failure"; + } + else { + return item; + } + }; + + IO.println(jeremyClarkson.farm("potato")); + IO.println(jeremyClarkson.farm("sheep")); + } +} +``` + +If the value is returned by a single expression you +do not need `return` or `{}`. + +```java +@FunctionalInterface +interface Farmer { + String farm(String item); +} + +class Main { + void main() { + Farmer caleb = item -> item; + + IO.println(caleb.farm("potato")); + IO.println(caleb.farm("sheep")); + + caleb = item -> item.toUpperCase(); + + IO.println(caleb.farm("potato")); + IO.println(caleb.farm("sheep")); + } +} +``` + diff --git a/src/lambdas/runnable.md b/src/lambdas/runnable.md new file mode 100644 index 00000000..69f7c256 --- /dev/null +++ b/src/lambdas/runnable.md @@ -0,0 +1 @@ +# Runnable diff --git a/src/loops.md b/src/loops.md index 69e42d6f..51e198a9 100644 --- a/src/loops.md +++ b/src/loops.md @@ -1,5 +1,8 @@ # Loops + + + `if` and `else` let you write programs which can take branching paths, but they will still run from the beginning to the end. diff --git a/src/loops/break.md b/src/loops/break.md index fbb9c03c..bdb48a5e 100644 --- a/src/loops/break.md +++ b/src/loops/break.md @@ -15,7 +15,7 @@ while (x > 0) { x--; } -System.out.println( +IO.println( "Final value of x is " + x ); ~} @@ -30,7 +30,7 @@ from an otherwise endless loop. ```java ~void main() { while (true) { - System.out.println( + IO.println( "The people started singing it not knowing what it was" ); diff --git a/src/loops/challenges.md b/src/loops/challenges.md index 8ab05c3a..f70f187b 100644 --- a/src/loops/challenges.md +++ b/src/loops/challenges.md @@ -35,7 +35,7 @@ What will this program output when run? Write down your guess and then try runni void main() { int x = 0; while (x < 10) { - System.out.println(x); + IO.println(x); x++; } } @@ -50,7 +50,7 @@ What will this program output when run? Write down your guess and then try runni void main() { int x = 0; while (x <= 10) { - System.out.println(x); + IO.println(x); x++; } } @@ -67,7 +67,7 @@ void main() { if (x % 3 == 0) { break; } - System.out.println(x); + IO.println(x); x++; } } @@ -85,7 +85,7 @@ void main() { if (x % 3 == 0) { continue; } - System.out.println(x); + IO.println(x); x++; } } @@ -102,7 +102,7 @@ void main() { while (x < 10) { int y = 2; while (y < 5) { - System.out.println(x * y); + IO.println(x * y); y++; } @@ -111,22 +111,12 @@ void main() { } ``` -## Challenge 7 - -What will this program output when run? Write down your guess and then try running it. - -```java,editable -void main() { - -} -``` - -## Challenge 8 +## Challenge 7 Write code that will output each character of `name` on its own line. -So for if `name` is equal to `"Bridget"`, I would expect the following as output. +So if `name` is equal to `"Bridget"`, I would expect the following as output. ```text B @@ -145,7 +135,7 @@ void main() { ``` -## Challenge 9 +## Challenge 8 Write code that will output each character of `name` on its own line, starting with the last character and going backwards. @@ -173,7 +163,7 @@ void main() { ``` -## Challenge 10 +## Challenge 9 Write code that will take a number and if it is divisible by two, divides it by two. If it is not, multiplies it by three and adds one. @@ -211,6 +201,7 @@ If the initial number is `15` you should have this as output. 10 5 16 +8 4 2 1 @@ -226,7 +217,7 @@ void main() { ``` -## Challenge 11 +## Challenge 10 Write code that outputs every third number from `37` to `160`. @@ -237,7 +228,7 @@ void main() { ``` -## Challenge 12 +## Challenge 11 Write code that outputs the number of vowels in `name`. Treat `y` as a vowel. @@ -253,7 +244,7 @@ void main() { ``` -## Challenge 13 +## Challenge 12 Write code that outputs `{name} is mostly vowels` if the number of vowels in `name` is greater than the number of consonants. and `{name} is mostly consonants` if the opposite is true. @@ -272,7 +263,7 @@ void main() { ``` -## Challenge 14 +## Challenge 13 Rewrite the following code to not have the `shouldBreak` variable and instead to use a labeled break. @@ -284,10 +275,10 @@ void main() { int y = 0; boolean shouldBreak = false; - while (shouldBreak && x < 100) { + while (!shouldBreak && x < 100) { while (y < 100) { - System.out.println("x is " + x); - System.out.println("y is " + y); + IO.println("x is " + x); + IO.println("y is " + y); x = x * y; if (x == 0) { shouldBreak = true; @@ -297,7 +288,7 @@ void main() { } } - System.out.println("Done"); + IO.println("Done"); } ``` diff --git a/src/loops/continue.md b/src/loops/continue.md index fc4f7003..87de0c94 100644 --- a/src/loops/continue.md +++ b/src/loops/continue.md @@ -10,9 +10,14 @@ The only other situation this will not happen is if a `continue` statement is re int x = 5; while (x > 0) { if (x == 4) { + x--; // Make sure the loop continues to 3 + // When this line is reached it will skip + // all the lines after this in the function + // and immediately go back to the top of the + // loop continue; } - System.out.println(x + " is a good number"); + IO.println(x + " is a good number"); x--; } ~} diff --git a/src/loops/counting_down.md b/src/loops/counting_down.md index 41107332..0a584584 100644 --- a/src/loops/counting_down.md +++ b/src/loops/counting_down.md @@ -11,7 +11,7 @@ to stop at, and a line at the bottom of the loop which decrements the current nu ~void main() { int currentNumber = 100; while (currentNumber >= 1) { - System.out.println(currentNumber); + IO.println(currentNumber); currentNumber--; } ~} @@ -25,7 +25,7 @@ Similar to when counting up if the condition was not `currentNumber >= 1` and in int currentNumber = 100; // Stops at 2 while (currentNumber > 1) { - System.out.println(currentNumber); + IO.println(currentNumber); currentNumber--; } ~} diff --git a/src/loops/counting_up.md b/src/loops/counting_up.md index 030fc74a..e29db0be 100644 --- a/src/loops/counting_up.md +++ b/src/loops/counting_up.md @@ -10,7 +10,7 @@ to stop at, and a line at the bottom of the loop which increments the current nu ~void main() { int currentNumber = 1; while (currentNumber <= 100) { - System.out.println(currentNumber); + IO.println(currentNumber); currentNumber++; } ~} @@ -25,7 +25,7 @@ it would stop at `99`. int currentNumber = 1; // Stops at 99 while (currentNumber < 100) { - System.out.println(currentNumber); + IO.println(currentNumber); currentNumber++; } ~} diff --git a/src/loops/do_while.md b/src/loops/do_while.md index 711b1194..39fa1d07 100644 --- a/src/loops/do_while.md +++ b/src/loops/do_while.md @@ -6,7 +6,7 @@ One variation on a `while` loop is a "do-while loop." ~void main() { int x = 0; do { - System.out.println(x); + IO.println(x); x++; } while(x < 5); ~} @@ -28,11 +28,11 @@ is that the first time the loop is reached the condition for the loop is not che ~void main() { int x = 0; do { - System.out.println("this will run"); + IO.println("this will run"); } while (x != 0); while (x != 0) { - System.out.println("this will not run"); + IO.println("this will not run"); } ~} ``` diff --git a/src/loops/endless_loops.md b/src/loops/endless_loops.md index 94b7fad6..b2ef9a05 100644 --- a/src/loops/endless_loops.md +++ b/src/loops/endless_loops.md @@ -1,13 +1,13 @@ # Endless Loops -If a while loop will never end, we call that an endless loop. +If a while loop will never end, we call that an endless loop[^hot]. This can happen if the condition is a constant like `while (true)` ```java,no_run ~void main() { while (true) { - System.out.println("This is the song that never ends"); + IO.println("This is the song that never ends"); } ~} ``` @@ -19,10 +19,12 @@ Or if the variables tested in the condition are not updated inside of the loop. // x is never changed int x = 0; while (x != 1) { - System.out.println("It goes on and on my friends"); + IO.println("It goes on and on my friends"); } ~} ``` Many games should never really "finish" so at the very start of that sort of program it is not uncommon to see a `while (true)`. + +[^hot]: If you run an endless loop that does nothing but loop your computer might start overheating. This is a natural part of the process and ultimately healthy for your CPU fans. diff --git a/src/loops/header.png b/src/loops/header.png new file mode 100644 index 00000000..a2463545 Binary files /dev/null and b/src/loops/header.png differ diff --git a/src/loops/iterate_over_a_string.md b/src/loops/iterate_over_a_string.md index 4aecc602..f80e840a 100644 --- a/src/loops/iterate_over_a_string.md +++ b/src/loops/iterate_over_a_string.md @@ -10,7 +10,7 @@ String name = "Avril"; int index = 0; while (index < name.length()) { - System.out.println(name.charAt(index)); + IO.println(name.charAt(index)); index++; } ~} diff --git a/src/loops/iteration.md b/src/loops/iteration.md index 2f4ec384..6761ea06 100644 --- a/src/loops/iteration.md +++ b/src/loops/iteration.md @@ -11,7 +11,7 @@ while (x < 5) { // On the 2nd iteration x will be 1 // ... // On the final iteration x will be 4 - System.out.println(x); + IO.println(x); x++ } ~} diff --git a/src/loops/labeled_break.md b/src/loops/labeled_break.md index 8329ddbd..8d769e41 100644 --- a/src/loops/labeled_break.md +++ b/src/loops/labeled_break.md @@ -44,10 +44,10 @@ while (x != 0) { break xLoop; } - System.out.println( + IO.println( "x is " + x ); - System.out.println( + IO.println( "y is " + y ); @@ -56,7 +56,7 @@ while (x != 0) { } } -System.out.println("done."); +IO.println("done."); ~} ``` diff --git a/src/loops/labeled_continue.md b/src/loops/labeled_continue.md index 6bffe932..cb36649a 100644 --- a/src/loops/labeled_continue.md +++ b/src/loops/labeled_continue.md @@ -10,9 +10,9 @@ You just write `continue` followed by the label name. // Will keep going back to the top of the outer loop outerLoop: while (true) { - System.out.println("inside outer loop"); + IO.println("inside outer loop"); while (true) { - System.out.println("inside inner loop"); + IO.println("inside inner loop"); continue outerLoop; } } diff --git a/src/loops/nested_loops.md b/src/loops/nested_loops.md index 7059fc94..2de9c342 100644 --- a/src/loops/nested_loops.md +++ b/src/loops/nested_loops.md @@ -9,10 +9,10 @@ int y = 3; while (x != 0) { while (y != 0) { - System.out.println( + IO.println( "x is " + x ); - System.out.println( + IO.println( "y is " + y ); @@ -36,10 +36,10 @@ while (x != 0) { break; } - System.out.println( + IO.println( "x is " + x ); - System.out.println( + IO.println( "y is " + y ); @@ -62,10 +62,10 @@ while (x != 0) { while (y != 0) { - System.out.println( + IO.println( "x is " + x ); - System.out.println( + IO.println( "y is " + y ); diff --git a/src/loops/unreachable_code.md b/src/loops/unreachable_code.md index 64618aed..192afeb1 100644 --- a/src/loops/unreachable_code.md +++ b/src/loops/unreachable_code.md @@ -11,7 +11,7 @@ Java knows this and so won't let any code like that run. while (true) { continue; - System.out.println("this is unreachable"); + IO.println("this is unreachable"); } ~} ``` diff --git a/src/loops/while.md b/src/loops/while.md index 181ed7dd..364b00f0 100644 --- a/src/loops/while.md +++ b/src/loops/while.md @@ -6,7 +6,7 @@ One way to make a loop in code is to use `while`. ~void main() { int x = 5; while (x != 0) { - System.out.println(x); + IO.println(x); x--; } ~} @@ -30,7 +30,7 @@ This will continue until the code in the condition evaluates to `false`. ~void main() { int glassesOfMilk = 99; while (glassesOfMilk > 0) { - System.out.println( + IO.println( glassesOfMilk + " glasses of milk left" ); diff --git a/src/loops_ii.md b/src/loops_ii.md index 7557a903..be296f85 100644 --- a/src/loops_ii.md +++ b/src/loops_ii.md @@ -1,5 +1,8 @@ # Loops II + + + `while` loops are enough to make any looping logic that you might want, but they aren't the only kind of loops you will see. diff --git a/src/loops_ii/break.md b/src/loops_ii/break.md index a5e3d9fc..d86dfcb4 100644 --- a/src/loops_ii/break.md +++ b/src/loops_ii/break.md @@ -9,9 +9,9 @@ for (int i = 0; i < 1000; i++) { if (i == 5) { break; } - System.out.println(i); + IO.println(i); } -System.out.println("Over"); +IO.println("Over"); // 0 // 1 diff --git a/src/loops_ii/comparison_to_while.md b/src/loops_ii/comparison_to_while.md index 5877ab9e..289422b6 100644 --- a/src/loops_ii/comparison_to_while.md +++ b/src/loops_ii/comparison_to_while.md @@ -8,19 +8,19 @@ and the code needed with a `while` loop, there might not seem like much of a dif double[] numbers = { 4.4, 1.1, 4.1, 4.7 }; for (int index = 0; index < numbers.length; index++) { - System.out.println(numbers[index]); + IO.println(numbers[index]); } int index = 0; while (index < numbers.length) { - System.out.println(numbers[index]); + IO.println(numbers[index]); index++; } ~} ``` This is doubly true when we are looking at toy examples where the only thing done -with the element is `System.out.println`. +with the element is `IO.println`. The biggest benefit to a `for` is subtle. With a `while` based loop, the initializer and boolean expression can potentially be many lines from the statement which updates the variable. @@ -54,10 +54,10 @@ while (index < numbers.length) { ~} ``` -Us humans, with our tiny monkey brains, can get very lost when things that are related to eachother are separated +Us humans, with our tiny monkey brains, can get very lost when things that are related to each other are separated by long distances. -In this dimension, for loops are superior. All the bits of code that "control the loop" can be right at the top. +In this dimension, `for` loops are superior. All the bits of code that "control the loop" can be right at the top. ```java ~void main() { diff --git a/src/loops_ii/continue.md b/src/loops_ii/continue.md index e1e8dc82..d815603a 100644 --- a/src/loops_ii/continue.md +++ b/src/loops_ii/continue.md @@ -12,7 +12,7 @@ for (int i = 0; i < 5; i++) { // i++ will still run continue; } - System.out.println(i); + IO.println(i); } // 0 @@ -31,7 +31,7 @@ while (i < 5) { if (i == 2) { continue; } - System.out.println(i); + IO.println(i); i++; } @@ -52,7 +52,7 @@ while (i < 5) { i++ continue; } - System.out.println(i); + IO.println(i); i++; } diff --git a/src/loops_ii/delayed_assignment.md b/src/loops_ii/delayed_assignment.md index 1da5f07c..8272ac9f 100644 --- a/src/loops_ii/delayed_assignment.md +++ b/src/loops_ii/delayed_assignment.md @@ -7,7 +7,7 @@ declared outside of the loop. ~void main() { int number; for (number = 0; number < 5; number++) { - System.out.println("At: " + number); + IO.println("At: " + number); } ~} ``` @@ -18,11 +18,11 @@ You might choose to do this so that after the loop is finished, you can still ac ~void main() { int number; for (number = 0; number < 5; number++) { - System.out.println("At: " + number); + IO.println("At: " + number); } // This will work, we can access the variable still. -System.out.println("Ended at: " + number); +IO.println("Ended at: " + number); ~} ``` @@ -32,10 +32,10 @@ to use that variable after the loop ```java ~void main() { for (int number = 0; number < 5; number++) { - System.out.println("At: " + number); + IO.println("At: " + number); } // This will not work. number is no longer available -System.out.println("Ended at: " + number); +IO.println("Ended at: " + number); ~} ``` diff --git a/src/loops_ii/drawing_isosceles_triangles.md b/src/loops_ii/drawing_isosceles_triangles.md index 64fb14a5..93109cf5 100644 --- a/src/loops_ii/drawing_isosceles_triangles.md +++ b/src/loops_ii/drawing_isosceles_triangles.md @@ -8,7 +8,7 @@ Another fun shape is the isosceles triangle. ***** ``` -For this one, the each row of the triangle needs to have spaces before it to shift it in to the +For this one, each row of the triangle needs to have spaces before it to shift it in to the center. How much each row needs to be shifted depends on how big the trangle will be overall. In this case with three rows of `*`s, the top `*` needs two space characters before it @@ -16,7 +16,7 @@ and the second row needs one space character. ```java ~void main() { -System.out.println(" *\n ***\n*****"); +IO.println(" *\n ***\n*****"); ~} ``` @@ -27,12 +27,12 @@ So any loop we make needs to take this pattern into account. int totalRows = 5; for (int row = 1; row <= totalRows; row++) { for (int i = 0; i < totalRows - row; i++) { - System.out.print(" "); + IO.print(" "); } for (int i = 0; i < row * 2 - 1; i++) { - System.out.print("*"); + IO.print("*"); } - System.out.println(); + IO.println(); } ~} ``` diff --git a/src/loops_ii/drawing_right_triangles.md b/src/loops_ii/drawing_right_triangles.md index 392356ee..514e39df 100644 --- a/src/loops_ii/drawing_right_triangles.md +++ b/src/loops_ii/drawing_right_triangles.md @@ -1,6 +1,6 @@ # Drawing Right Triangles -One of the more fun things to do with `for` loops[^quiz] is to use them to print out shapes. +One of the more fun things to do with `for` loops is to use them to print out shapes. Say you wanted to draw this right triangle. @@ -16,7 +16,7 @@ If you were to write the code out to print this explicitly it would look like th ```java ~void main() { -System.out.print("*\n**\n**\n"); +IO.print("*\n**\n**\n"); ~} ``` @@ -28,10 +28,10 @@ Since counting up `1 -> 2 -> 3` is easy with `for` loops, you can translate this ~void main() { for (int numberOfStars = 1; numberOfStars <= 3; numberOfStars++) { for (int i = 0; i < numberOfStars; i++) { - System.out.print("*"); + IO.print("*"); } - // Same as System.out.print("\n"); - System.out.println(); + // Same as IO.print("\n"); + IO.println(); } ~} ``` @@ -43,9 +43,9 @@ Which makes it easy to make one of these triangles however tall you want. int height = 6; for (int numberOfStars = 1; numberOfStars <= height; numberOfStars++) { for (int i = 0; i < numberOfStars; i++) { - System.out.print("*"); + IO.print("*"); } - System.out.println(); + IO.println(); } ~} ``` diff --git a/src/loops_ii/empty_expressions.md b/src/loops_ii/empty_expressions.md index f62ff5b4..cb25568f 100644 --- a/src/loops_ii/empty_expressions.md +++ b/src/loops_ii/empty_expressions.md @@ -5,7 +5,7 @@ You are also allowed to leave the expression part of a `for` loop blank. ```java ~void main() { for (int i = 0;;i++) { - System.out.println(i); + IO.println(i); } // 0 // 1 @@ -24,7 +24,7 @@ for (int i = 0;;i++) { if (i == 5) { break; } - System.out.println(i); + IO.println(i); } // 0 // 1 diff --git a/src/loops_ii/empty_initializers.md b/src/loops_ii/empty_initializers.md index ca4d59f5..f6e39cc1 100644 --- a/src/loops_ii/empty_initializers.md +++ b/src/loops_ii/empty_initializers.md @@ -7,7 +7,7 @@ so long as you still have the `;`. ~void main() { int number = 0; for (;number < 5; number++) { - System.out.println(number); + IO.println(number); } ~} ``` @@ -21,8 +21,8 @@ This way its initialization and declaration can be on the same line, which might ~void main() { int number = 0; for (;number < 5; number++) { - System.out.println(number); + IO.println(number); } -System.out.println("Still have number: " + number); +IO.println("Still have number: " + number); ~} ``` diff --git a/src/loops_ii/empty_statements.md b/src/loops_ii/empty_statements.md index 1c8076ae..adac4a8c 100644 --- a/src/loops_ii/empty_statements.md +++ b/src/loops_ii/empty_statements.md @@ -1,12 +1,12 @@ # Empty Statements You can even leave the statement part of a `for` loop blank. This means that at -the end of an iteration there is nothing guarenteed to run. +the end of an iteration there is nothing guaranteed to run. ```java ~void main() { for (int i = 6; i > 2;) { - System.out.println(i); + IO.println(i); i--; } @@ -14,7 +14,6 @@ for (int i = 6; i > 2;) { // 5 // 4 // 3 -// 2 ~} ``` @@ -24,14 +23,14 @@ If you leave both the initializer and statement blank, that will be functionally ~void main() { int number = 1; for (;number < 10;) { - System.out.println(number); + IO.println(number); number *= 2; } // Same logic as above int number2 = 1; while (number2 < 10) { - System.out.println(number2); + IO.println(number2); number2 *= 2; } ~} @@ -41,7 +40,7 @@ If you leave the initializer, expression, and statement blank it will be the sam ```java,no_run for (;;) { - System.out.println("The people stated singing it..."); + IO.println("The people stated singing it..."); } // Runs forever ``` diff --git a/src/loops_ii/final_variables.md b/src/loops_ii/final_variables.md index f150dda0..68abcd47 100644 --- a/src/loops_ii/final_variables.md +++ b/src/loops_ii/final_variables.md @@ -6,7 +6,7 @@ The initializer of a `for` loop can also declare `final` variables. ~void main() { int i = 0; for (final String name = "Bob"; i < 5; i++) { - System.out.println(name + ": " + i); + IO.println(name + ": " + i); } ~} ``` @@ -20,9 +20,9 @@ can change without reassigning a variable. for (final char[] letters = { 'I', 'O', 'U' }; letters[0] != 'A';) { for (int i = 0; i < letters.length; i++) { letters[i] -= 1; - System.out.print(letters[i]); + IO.print(letters[i]); } - System.out.println(); + IO.println(); } // HNT diff --git a/src/loops_ii/for.md b/src/loops_ii/for.md index d51447de..ea869427 100644 --- a/src/loops_ii/for.md +++ b/src/loops_ii/for.md @@ -13,7 +13,7 @@ As with many things, this might be easiest to see by looking at an example. ~void main() { // Will run 10 times for (int number = 0; number < 10; number++) { - System.out.println(number); + IO.println(number); } ~} ``` @@ -24,7 +24,7 @@ That `for` loop works about the same as this `while` loop. ~void main() { int number = 0; while (number < 10) { - System.out.println(number); + IO.println(number); number++; } diff --git a/src/loops_ii/for_counting_up_and_down.md b/src/loops_ii/for_counting_up_and_down.md index e88c73f4..93968a2a 100644 --- a/src/loops_ii/for_counting_up_and_down.md +++ b/src/loops_ii/for_counting_up_and_down.md @@ -7,12 +7,12 @@ a given number. ~void main() { // Goes from 1 to 100 for (int currentNumber = 1; currentNumber <= 100; currentNumber++) { - System.out.println(currentNumber); + IO.println(currentNumber); } // Goes from 100 to 1 for (int currentNumber = 100; currentNumber >= 1; currentNumber--) { - System.out.println(currentNumber); + IO.println(currentNumber); } ~} ``` diff --git a/src/loops_ii/header.png b/src/loops_ii/header.png new file mode 100644 index 00000000..c630b54c Binary files /dev/null and b/src/loops_ii/header.png differ diff --git a/src/loops_ii/i.md b/src/loops_ii/i.md index cfce71dd..db3f762c 100644 --- a/src/loops_ii/i.md +++ b/src/loops_ii/i.md @@ -10,7 +10,7 @@ String word = "bird"; for (int i = 0; i < array.length; i++) { char letter = word.charAt(i); - System.out.println(letter); + IO.println(letter); } // b @@ -33,8 +33,8 @@ int[] numbers = { 1, 2 }; for (int i = 0; i < letters.length; i++) { for (int j = 0; j < numbers.length; j++) { - System.out.print(letters[i]); - System.out.println(numbers[j]); + IO.print(letters[i]); + IO.println(numbers[j]); } } diff --git a/src/loops_ii/inferred_types.md b/src/loops_ii/inferred_types.md index bdc72765..6c308829 100644 --- a/src/loops_ii/inferred_types.md +++ b/src/loops_ii/inferred_types.md @@ -6,7 +6,7 @@ you still are allowed to use `var` so that the type of the declared variable is ```java ~void main() { for (var i = 0; i < 10; i++) { - System.out.println(i); + IO.println(i); } ~} ``` @@ -19,7 +19,7 @@ But if your `for` loop is doing something more exotic, it might make sense. ```java ~void main() { for (var repeated = ""; repeated.length() < 5; repeated = repeated + "a") { - System.out.println(repeated); + IO.println(repeated); } // a diff --git a/src/loops_ii/iterate_over_a_string.md b/src/loops_ii/iterate_over_a_string.md index 2dd1a3aa..9ac0273e 100644 --- a/src/loops_ii/iterate_over_a_string.md +++ b/src/loops_ii/iterate_over_a_string.md @@ -8,7 +8,7 @@ you iterate over each character in a `String`. String name = "Lavigne"; for (int index = 0; index < name.length(); index++) { - System.out.println(name.charAt(index)); + IO.println(name.charAt(index)); } ~} ``` diff --git a/src/loops_ii/iterate_over_an_array.md b/src/loops_ii/iterate_over_an_array.md index b9b6dadb..674b15c5 100644 --- a/src/loops_ii/iterate_over_an_array.md +++ b/src/loops_ii/iterate_over_an_array.md @@ -8,7 +8,7 @@ you can use it to go through each element in an array. int[] numbers = { 4, 1, 6, 9 }; for (int index = 0; index < numbers.length; index++) { - System.out.println(numbers[index]); + IO.println(numbers[index]); } ~} ``` diff --git a/src/loops_ii/labeled_break.md b/src/loops_ii/labeled_break.md index 42026f1e..23c9389d 100644 --- a/src/loops_ii/labeled_break.md +++ b/src/loops_ii/labeled_break.md @@ -17,14 +17,14 @@ This applies also to when `while` loops are nested within `for` loops or the oth ~void main() { outerForLoop: for (int i = 0; i < 10; i++) { - System.out.println(i); + IO.println(i); while (i < 100) { if (i == 5) { break outerForLoop; } i++; } - System.out.println(i); + IO.println(i); } // 0 diff --git a/src/loops_ii/labeled_continue.md b/src/loops_ii/labeled_continue.md index f36b82de..850db540 100644 --- a/src/loops_ii/labeled_continue.md +++ b/src/loops_ii/labeled_continue.md @@ -8,7 +8,7 @@ the statement of a `for` loop will always run when you get to the top of it.[^un label: for (int i = 0; i < 4; i++) { for (int j = 0; j < 3; j++) { - System.out.println ("" + i + ", " + j); + IO.println ("" + i + ", " + j); if (i == 2) { // i++ will run continue label; diff --git a/src/loops_iii.md b/src/loops_iii.md new file mode 100644 index 00000000..335c7b9c --- /dev/null +++ b/src/loops_iii.md @@ -0,0 +1,39 @@ +# Loops III + + + + +Counting indexes of elements is, while effective, a tad +exhausting to do every time you want to loop through something. + +```java +~void main() { +String[] shirts = new String[] { + "T-Shirt", + "Polo Shirt", + "Dress Shirt" +}; + +for (int i = 0; i < shirts.length; i++) { + String shirt = shirts[i]; + + IO.println(shirt); +} +~} +``` + +This is where "for-each" loops come in. + +```java +~void main() { +String[] shirts = new String[] { + "T-Shirt", + "Polo Shirt", + "Dress Shirt" +}; + +for (String shirt : shirts) { + IO.println(shirt); +} +~} +``` diff --git a/src/loops_iii/arraylist.md b/src/loops_iii/arraylist.md new file mode 100644 index 00000000..271f2a97 --- /dev/null +++ b/src/loops_iii/arraylist.md @@ -0,0 +1,42 @@ +# ArrayList + +One class which implements `Iterable` is `ArrayList`. + +```java +~import java.util.ArrayList; +~ +~class Main { +~ void main() { +ArrayList donutEaters = new ArrayList<>(); +donutEaters.add("Chief Wiggum"); +donutEaters.add("Homer Simpson"); + +Iterator donutEatersIterator = donutEaters.iterator(); +// Check if there is a next element +while (donutEatersIterator.hasNext()) { + // If there is, get it and advance the iterator + String donutEater = donutEatersIterator.next(); + + IO.println(donutEater + " eats donuts"); +} +~ } +~} +``` + +This means you can loop over it with a for-each loop same as an array. + +```java +~import java.util.ArrayList; +~ +~class Main { +~ void main() { +ArrayList donutEaters = new ArrayList<>(); +donutEaters.add("Chief Wiggum"); +donutEaters.add("Homer Simpson"); + +for (String donutEater : donutEaters) { + IO.println(donutEater + " eats donuts"); +} +~ } +~} +``` \ No newline at end of file diff --git a/src/loops_iii/arrays.md b/src/loops_iii/arrays.md new file mode 100644 index 00000000..4ed25fe6 --- /dev/null +++ b/src/loops_iii/arrays.md @@ -0,0 +1,77 @@ +# Arrays + +When you use a for-each loop on an array, it acts the same as the kind of for-loop +you would have written before with an explicit index. + +```java +record Drink(String name, double mgCaffeinePerCup) {} + +~class Main { +~ void main() { +Drink[] drinks = { + new Drink("Black Coffee", 95), + new Drink("Milk", 0), + new Drink("Green Tea", 35) +}; + +// This loop functions the same as the loop below +for (int i = 0; i < drinks.length; i++) { + Drink drink = drinks[i]; + + IO.println( + drink.name() + + " has " + + drink.mgCaffeinePerCup() + + "mg caffiene per cup" + ); +} + +IO.println("------------------"); + +for (Drink drink : drinks) { + IO.println( + drink.name() + + " has " + + drink.mgCaffeinePerCup() + + "mg caffiene per cup" + ); +} +~ } +~} +``` + +This doesn't mean that the old style of for loop is useless now. You will still want that +if you need to know which element in your array you are dealing with +or if you are doing a more interesting form of iteration. + +```java +record Drink(String name, double mgCaffeinePerCup) {} + +~class Main { +~ void main() { +Drink[] drinks = { + new Drink("Black Coffee", 95), + new Drink("Milk", 0), + new Drink("Green Tea", 35) +}; + + +// If loop actually cares to use `i` beyond just accessing +// the right element in the array +for (int i = 0; i < drinks.length; i++) { + Drink drink = drinks[i]; + + // Which you might want for display logic + IO.println( + "[" + i + "]: " + drink.name() + ); + + // Or to mutate the original array + drinks[i] = new Drink( + drink.name(), + drink.mgCaffeinePerCup() + 100 + ); +} +~ } +~} +``` \ No newline at end of file diff --git a/src/loops_iii/challenges.md b/src/loops_iii/challenges.md new file mode 100644 index 00000000..6f25d12d --- /dev/null +++ b/src/loops_iii/challenges.md @@ -0,0 +1,103 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Make your own class named `OneToTen` which implements `Iterable` +and will yield the numbers from `1` to `10`. + +This will require making a class which implements `Iterator` as well. + +```java,editable +// CODE HERE + +class OneToTen implements Iterable { + // CODE HERE +} + +class Main { + void main() { + var oneToTen = new OneToTen(); + + // Should output + // 1 2 3 4 5 6 7 8 9 10 + // twice + for (int i : oneToTen) { + IO.print(i); + IO.print(" ") + } + IO.println(); + + // If it only happens once, you might be + // mistaken about the difference between + // Iterable and Iterator + for (int i : oneToTen) { + IO.print(i); + IO.print(" ") + } + IO.println(); + } +} +``` + +## Challenge 2. + +Make a class named `StringIterable` which implements `Iterable`. + +Its constructor should take a `String` and it should let you iterate over +each character. + +```java,editable +// CODE HERE + +class Main { + void main() { + var stringIterable = new StringIterable("abc"); + + // Should output + // + // a + // b + // c + // ------- + // a + // b + // c + for (char c : stringIterable) { + IO.println(c); + } + IO.println("-------"); + for (char c : stringIterable) { + IO.println(c); + } + } +} +``` + +## Challenge 3. + +The following code will crash with a `ConcurrentModificationException`. +Rewrite it so that it does not. + +```java,editable +import java.util.ArrayList; + +class Main { + void main() { + var trash = new ArrayList(); + trash.add("gloves"); + trash.add("staff"); + trash.add("glasses"); + + for (var item : trash) { + if (item.equals("glasses")) { + trash.remove(item); + } + } + } +} +``` \ No newline at end of file diff --git a/src/loops_iii/concurrent_modifications.md b/src/loops_iii/concurrent_modifications.md new file mode 100644 index 00000000..d9f99e59 --- /dev/null +++ b/src/loops_iii/concurrent_modifications.md @@ -0,0 +1,81 @@ +# Concurrent Modifications + +If you are looping over a collection with a for-each loop you generally +cannot remove things from that collection at the same time. Doing so should +trigger a `ConcurrentModificationException`.[^should] + +```java,panics +~import java.util.ArrayList; +~ +record Sandwich( + int turkeySlices, + int cheeseSlices, + boolean mayo +) {} + +~class Main { +~ void main() { +ArrayList sandwiches = new ArrayList<>(); +var turkeyAndCheddar = new Sandwich(2, 2, true); +var grilledCheese = new Sandwich(0, 4, false); +var bigTurkeyAndCheddar = new Sandwich(10, 10, true); +var theWisconsinFreak = new Sandwich(0, 20, true); + +sandwiches.add(turkeyAndCheddar); +sandwiches.add(grilledCheese); +sandwiches.add(bigTurkeyAndCheddar); +sandwiches.add(theWisconsinFreak); + +for (Sandwich sandwich : sandwiches) { + if (sandwich.mayo()) { // Some people don't like Mayo + // But we can't get rid of them during the loop + sandwiches.remove(sandwich); + } +} +~ } +~} +``` + +If you want to remove an element at the same time as iterating over specifically an `ArrayList`, you +can get creative with indexes and regular loops.[^remove] + +```java +~import java.util.ArrayList; +~ +record Sandwich( + int turkeySlices, + int cheeseSlices, + boolean mayo +) {} + +~class Main { +~ void main() { +ArrayList sandwiches = new ArrayList<>(); +var turkeyAndCheddar = new Sandwich(2, 2, true); +var grilledCheese = new Sandwich(0, 4, false); +var bigTurkeyAndCheddar = new Sandwich(10, 10, true); +var theWisconsinFreak = new Sandwich(0, 20, true); + +sandwiches.add(turkeyAndCheddar); +sandwiches.add(grilledCheese); +sandwiches.add(bigTurkeyAndCheddar); +sandwiches.add(theWisconsinFreak); + +for (int i = 0; i < sandwiches.size(); i++) { + Sandwich sandwich = sandwiches.get(i); + if (sandwich.mayo()) { // Some people don't like Mayo + sandwiches.remove(sandwich); + i--; // Subtracting one from our current index syncs us back up + } +} + +IO.println(sandwiches); +~ } +~} +``` + +[^should]: The reason I say "should" is that doing the book keeping needed to know when +this has happened can be hard and not all `Iterator`s in the world will do it. The check is what we would call "best effort." + +[^remove]: And, as these footnotes have alluded, there is a `.remove` method on `Iterator`. We'll cover it +later. \ No newline at end of file diff --git a/src/loops_iii/for_each_loops.md b/src/loops_iii/for_each_loops.md new file mode 100644 index 00000000..c5e78ea6 --- /dev/null +++ b/src/loops_iii/for_each_loops.md @@ -0,0 +1,38 @@ +# For-each loops + +Where a normal `for` loop is more or less a shorthand for a certain kind of `while` loop, +a for-each loop[^enhanced] is a shorthand for the general concept of iterating over a collection +of elements. + +To use a for-each loop, write `for` then in the parentheses write a variable declaration, a `:`, and the +collection of elements you are iterating over. + +``` +for ( : ) { + +} +``` + +```java +record Bread(String name, boolean french) {} + +~class Main { +~ void main() { +Bread[] breads = { + new Bread("Croissant", true), + new Bread("Baguette", true), + new Bread("Boston Brown Bread", false) +}; + +for (Bread bread : breads) { + IO.println( + bread.name() + + (bread.french() ? " is french" : " is not french") + ); +} +~ } +~} +``` + + +[^enhanced]: You might see this referred to as an "enhanced for statement." [That is its name in the language spec](https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.14.2) but not the name most people will use. \ No newline at end of file diff --git a/src/loops_iii/header.png b/src/loops_iii/header.png new file mode 100644 index 00000000..0a4384bf Binary files /dev/null and b/src/loops_iii/header.png differ diff --git a/src/loops_iii/inferred_types.md b/src/loops_iii/inferred_types.md new file mode 100644 index 00000000..8d10854f --- /dev/null +++ b/src/loops_iii/inferred_types.md @@ -0,0 +1,17 @@ +# Inferred Types + +A variable declaration in a for-each loop can make use of `var` to infer its type. + +```java +~class Main { +~ void main() { +String[] chairMaterials = { "wicker", "wood", "plastic" } +for (var chairMaterial : chairMaterials) { + IO.println(chairMaterial); +} +~ } +~} +``` + +And this is good to know for the same reasons you would use `var` other places in Java. +Writing `Book book` can be a drain on the soul. diff --git a/src/loops_iii/iterable_and_iterator.md b/src/loops_iii/iterable_and_iterator.md new file mode 100644 index 00000000..ca44383e --- /dev/null +++ b/src/loops_iii/iterable_and_iterator.md @@ -0,0 +1,53 @@ +# Iterable and Iterator + +For things that are not arrays, a for-each loops +are built on top of two interfaces: `java.lang.Iterable` and `java.lang.Iterator`. + +The [`Iterator`](https://javadoc.mccue.dev/api/java.base/java/util/Iterator.html) interface defines two methods: `hasNext` and `next`[^remove]. Iterators let you box up the logic of +how to loop over something. + +```java,no_run +public interface Iterator { + // Will return true if there are more elements + // false otherwise + boolean hasNext(); + + // Gets an element and advances the iterator forwards + T next(); +} +``` + +[`Iterable`](https://javadoc.mccue.dev/api/java.base/java/util/Iterable.html) then +just has one method which gives you an `Iterator`. + +```java,no_run +interface Iterable { + // Gives a "fresh" Iterator + Iterator iterator(); +} +``` + +This is needed because `Iterator`s are "one shot." It starts at the beginning of a collection +and advances across every element each time `next` is called. In order to loop over something multiple +times you need a fresh iterator each time. + +A for-each loop over an `Iterable` object more or less translates to this style of `while` loop.[^important] + +```java,no_run +for (String thing : iterable) { + // ... +} + +// is the same as + +Iterator iter = iterable.iterator(); +while (iter.hasNext()) { + String thing = iter.next(); + // ... +} +``` + + +[^remove]: There is actually one more method: `remove`. Not all `Iterator`s support it so we'll cover it once we've introduced more `Iterable` things. + +[^important]: I think this is important to know because otherwise it won't make sense when you run in to things you can loop over but don't have `.get`/`[]`, ` \ No newline at end of file diff --git a/src/loops_iii/string.md b/src/loops_iii/string.md new file mode 100644 index 00000000..64b8bc49 --- /dev/null +++ b/src/loops_iii/string.md @@ -0,0 +1,28 @@ +# String + +One thing that feels like it _should_ implement the `Iterable` interface but does not is `String`. + +```java,does_not_compile +~class Main { +~ void main() { +String letters = "abc"; +for (char letter : letters) { + IO.println(letter); +} +~ } +~} +``` + +To loop over all the characters in a `String`, you have to use a regular loop. + +```java,does_not_compile +~class Main { +~ void main() { +String letters = "abc"; +for (int i = 0; i < letters.length(); i++) { + char letter = letters.charAt(i); + IO.println(letter); +} +~ } +~} +``` \ No newline at end of file diff --git a/src/methods.md b/src/methods.md index e1ead8b8..e49a717e 100644 --- a/src/methods.md +++ b/src/methods.md @@ -1,10 +1,13 @@ # Methods + + + All the code you have seen up until this point has lived inside of `void main() {}`. ```java void main() { - System.out.println("CODE GO HERE"); + IO.println("CODE GO HERE"); } ``` diff --git a/src/methods/arguments.md b/src/methods/arguments.md index 64eaf15c..2d0fd388 100644 --- a/src/methods/arguments.md +++ b/src/methods/arguments.md @@ -9,7 +9,7 @@ to that method. These arguments let the caller of the method change what happens ```java void sayHello(String name) { - System.out.println("Hello " + name + "!"); + IO.println("Hello " + name + "!"); } void main() { diff --git a/src/methods/declaration.md b/src/methods/declaration.md index 1913d556..f762e8b0 100644 --- a/src/methods/declaration.md +++ b/src/methods/declaration.md @@ -4,7 +4,7 @@ The simplest kind of method is declared by writing `void` followed by some name, ```java void doThing() { - System.out.println("Hello from inside a method!"); + IO.println("Hello from inside a method!"); } ~ ~void main() { diff --git a/src/methods/header.png b/src/methods/header.png new file mode 100644 index 00000000..b5bb4d1f Binary files /dev/null and b/src/methods/header.png differ diff --git a/src/methods/invocation.md b/src/methods/invocation.md index 3c419397..3e5abc9d 100644 --- a/src/methods/invocation.md +++ b/src/methods/invocation.md @@ -5,7 +5,7 @@ name of the method followed by `()` in a statement. ```java void doThing() { - System.out.println("Hello from inside a method!"); + IO.println("Hello from inside a method!"); } void main() { @@ -19,7 +19,7 @@ You can call a method multiple times. If you do, then the code inside of it will ```java void doThing() { - System.out.println("Hello from inside a method!"); + IO.println("Hello from inside a method!"); } void main() { diff --git a/src/methods/main.md b/src/methods/main.md index 6eddbda7..c140373a 100644 --- a/src/methods/main.md +++ b/src/methods/main.md @@ -5,20 +5,8 @@ call it in order to start your programs. ```java void main() { - System.out.println("Java will start here"); + IO.println("Java will start here"); } ``` -This means you can do anything in your `main` method you can do in any other method, including returning early. - -```java -void main() { - int x = 5; - - if (x == 5) { - return; - } - - System.out.println("WONT RUN"); -} -``` +This means you can do anything in your `main` method you can do in any other method. diff --git a/src/methods/return.md b/src/methods/return.md index 2caa3775..5ad9d9be 100644 --- a/src/methods/return.md +++ b/src/methods/return.md @@ -5,14 +5,14 @@ Anywhere inside of a method you can write `return` to immediately exit that meth ```java void willReturnEarly() { for (int i = 0; i < 10; i++) { - System.out.println(i); + IO.println(i); if (i == 5) { // Will stop at 5 because we exit the method return; } } - System.out.println("THIS WONT RUN"); + IO.println("THIS WONT RUN"); } void main() { diff --git a/src/methods/scope.md b/src/methods/scope.md index d20aa283..3bc24e74 100644 --- a/src/methods/scope.md +++ b/src/methods/scope.md @@ -6,9 +6,9 @@ Methods can contain any code, including variable declarations. void sayMathStuff() { int x = 1; int y = 2; - System.out.println("x is " + x); - System.out.println("y is " + y); - System.out.println("x + y is " + (x + y)); + IO.println("x is " + x); + IO.println("y is " + y); + IO.println("x + y is " + (x + y)); } void main() { @@ -23,15 +23,15 @@ Other code cannot see that variable. void sayMathStuff() { int x = 1; int y = 2; - System.out.println("x is " + x); - System.out.println("y is " + y); - System.out.println("x + y is " + (x + y)); + IO.println("x is " + x); + IO.println("y is " + y); + IO.println("x + y is " + (x + y)); } void main() { sayMathStuff(); // Error, x doesn't exist here - System.out.println(x); + IO.println(x); } ``` diff --git a/src/methods/void.md b/src/methods/void.md index 395eccd4..9453c600 100644 --- a/src/methods/void.md +++ b/src/methods/void.md @@ -15,7 +15,9 @@ int views() { // Doesn't return any value. void talkAboutVideo() { - System.out.println(title() + " only has " + views() + " views."); + // Printing something to the screen is different + // than returning a value. + IO.println(title() + " only has " + views() + " views."); } void main() { diff --git a/src/modules.md b/src/modules.md new file mode 100644 index 00000000..d189885f --- /dev/null +++ b/src/modules.md @@ -0,0 +1,8 @@ +# Modules + + + +Every package in Java "lives" in a module. + +Just as classes may be grouped into packages, packages may be grouped into modules. + diff --git a/src/modules/challenges.md b/src/modules/challenges.md new file mode 100644 index 00000000..6ed0ffcf --- /dev/null +++ b/src/modules/challenges.md @@ -0,0 +1,24 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Add a `module-info.java` to one of your projects. + +## Challenge 2. + +Move some of your code to use the multi-module directory layout. +If you need to split one of your projects into multiple modules +to pull this off, do so. + +## Challenge 3. + +Intentionally create a split package situation. What error does Java give you? + +## Challenge 4. + +Intentionally create a cycle in the "module graph." What error does Java give you? diff --git a/src/modules/declaration.md b/src/modules/declaration.md new file mode 100644 index 00000000..d35bf7b7 --- /dev/null +++ b/src/modules/declaration.md @@ -0,0 +1,32 @@ +# Declaration + +To declare a module, you need to create a file named `module-info.java` +and put it at the top of the folder where your code is. + +``` +src/ + example/ + Example.java + some/ + packageName/ + AClass.java + module-info.java +``` + +Within this file you put `module` followed by a name for the grouping of packages and `{}`.[^itsbest] + +```java +module example { +} +``` + +Just like with package names, module names can have multiple parts separated by `.`s. + +```java +module a.longer.name { +} +``` + +Nothing in this declaration says explicitly what packages are part of the module; it is just assumed that it holds the packages it is "next to." + +[^itsbest]: This name doesn't need to be related to the names of the packages, but whenever possible it's best to pick something that makes sense \ No newline at end of file diff --git a/src/modules/exports.md b/src/modules/exports.md new file mode 100644 index 00000000..46433229 --- /dev/null +++ b/src/modules/exports.md @@ -0,0 +1,41 @@ +# Exports + +Inside your `module-info.java` file you declare which packages are +"exported" from that module. + +```java,no_run +module reality { + exports earth; + exports sea; + exports sky; +} +``` + +Any packages that do not have an `exports` declaration are "unexported packages." + +The classes in unexported packages will not be visible to other modules +even if those classes are `public`. + +```java,no_run +// Because the "backrooms" package is not exported +// from the "reality" module, other modules cannot +// observe classes within it. +package backrooms; + +public class YellowCarpet { +} +``` + +```java,no_run +package sky; + + +// But classes within other packages of +// the "reality" module can see it just fine. +public class Cloud { + private final YellowCarpet fabricOfExistence + = new YellowCarpet(); + + // ... +} +``` \ No newline at end of file diff --git a/src/modules/header.png b/src/modules/header.png new file mode 100644 index 00000000..80250445 Binary files /dev/null and b/src/modules/header.png differ diff --git a/src/modules/java.base.md b/src/modules/java.base.md new file mode 100644 index 00000000..57b439a3 --- /dev/null +++ b/src/modules/java.base.md @@ -0,0 +1,50 @@ +# java.base + +The `java.base` module is where all packages like `java.lang`, `java.util`, etc. +are defined. + +This means it contains classes used by nearly every Java program like `java.lang.String`, +`java.lang.Integer`, and `java.util.ArrayList`. + +Because of this, it is the only module you do not need to explicitly require in a `module-info.java` file. + +```java +module cool.code { + // You can leave this line off + // and it will still require java.base + requires java.base; +} +``` + +And when you have a file that makes use of "The Anonymous Main class," +Java will also act as if you had a module import for `java.base`. This +means that you don't actually need an explicit import for classes like `ArrayList`. + +So if a file has the following + +```java +void main() { + var names = new ArrayList(); + names.add("Him"); + names.add("Jim"); + names.add("Bim"); + IO.println(names); +} +``` + +It is equivalent to + +```java +import module java.base; + +class Main { + void main() { + var names = new ArrayList(); + names.add("Him"); + names.add("Jim"); + names.add("Bim"); + IO.println(names); + } +} +``` + diff --git a/src/modules/module_imports.md b/src/modules/module_imports.md new file mode 100644 index 00000000..24f6c6da --- /dev/null +++ b/src/modules/module_imports.md @@ -0,0 +1,32 @@ +# Module Imports + +A special kind of import is the "module import." + +Just like a package import (`import package_name.*`) will +import all the classes in that package, an `import module` +declaration will import all the classes in all the packages +contained within that module. + +```java +// Same as writing +// +// import java.util.*; +// import java.lang.annotation.*; +// import java.lang.reflect.*; +// ... and so on for every package in the java.base module +import module java.base; + +class Main { + void main() { + // You can now freely use any of the classes from the imported module + Collection c = List.of("do", "re", "mi"); + + IO.println(c); + } +} +``` + +This has much the same upsides and downsides as a package import. It is +much easier to write this and get all the classes you want, but in exchange +it might be harder to see where a particular class comes from when you are +reading code later. \ No newline at end of file diff --git a/src/modules/multi_module_directory_layout.md b/src/modules/multi_module_directory_layout.md new file mode 100644 index 00000000..0f7ff543 --- /dev/null +++ b/src/modules/multi_module_directory_layout.md @@ -0,0 +1,59 @@ +# Multi-Module Directory Layout + +If you yourself want to develop a project using multiple of your own modules +there is a special way to layout the files to do so. + +First, make folders with the name of each module. If a module name +has a `.` in it the folder should too. + +```text,no_run +reality/ +backrooms/ +horrible.monsters/ +``` + +Then in those folders under a `src` folder put the `module-info.java` files.[^src] + +```text,no_run +reality/ + src/ + module-info.java +backrooms/ + src/ + module-info.java +horrible.monsters/ + src/ + module-info.java +``` + +From there you can put all the classes you want into each module, so long as they don't conflict +or create split packages. + +```text,no_run +reality/ + src/ + earth/ + Dirt.java + Worm.java + sea/ + Starfish.java + sky/ + Cloud.java + module-info.java +backrooms/ + src/ + backrooms/ + YellowCarpet.java + module-info.java +horrible.monsters/ + src/ + horrible/ + monsters/ + Slime.java + Skeleton.java + module-info.java +``` + +This can be helpful in structuring larger projects. + +[^src]: The `src` folder isn't technically required. You will see what a `--module-source-path` looks like in a bit. I think its a good idea anyways. \ No newline at end of file diff --git a/src/modules/purpose.md b/src/modules/purpose.md new file mode 100644 index 00000000..fa8faf87 --- /dev/null +++ b/src/modules/purpose.md @@ -0,0 +1,22 @@ +# Purpose + +It might be a little unclear at first what reason +modules have for existing. + +After all, don't packages already group code? Why would you +want groupings of a grouping? + +The answer is that[^more] when you share code with other people +you will often want to have that code contain multiple packages. +If you couldn't "hide" packages of code from the people using +the code you[^andby] are sharing it would be a bummer. + +Just as private fields help you encapsulate state in a class +and package-private classes help you encapsulate whole classes, +unexported packages are your mechanism for encapsulating whole package. + + +[^more]: There are more answers, but they mostly involve tales of Java's past +and mistakes that were made there. + +[^andby]: And by "you" I mean you and whoever else is directly working with you. \ No newline at end of file diff --git a/src/modules/requires.md b/src/modules/requires.md new file mode 100644 index 00000000..cc9dc938 --- /dev/null +++ b/src/modules/requires.md @@ -0,0 +1,51 @@ +# Requires + +Code within modules can only use classes defined in packages +that are part of its module or are in exported packages of modules it +"requires." + +```java,no_run +module reality { + exports earth; + exports sea; + exports sky; +} +``` + +```java +module human { + // Code within this "human" module + // will have access to classes in + // the "earth," "sea," and "sky" packages. + requires reality; +} +``` + +Modules can both require other modules and +export packages for other modules to use. + +```java +module human { + requires reality; + + exports sadness; +} +``` + +These `requires` are not allowed to form "cycles." +This means that `cat` cannot require `dog` if `dog` also +requires `cat`, indirectly or otherwise. + +```java,no_run +module dog { + requires cat; +} +``` + +```java,no_run +// Apologies to any hit Nickelodeon shows +// but this is unacceptable. +module cat { + requires dog; +} +``` \ No newline at end of file diff --git a/src/modules/restrictions.md b/src/modules/restrictions.md new file mode 100644 index 00000000..01ef7212 --- /dev/null +++ b/src/modules/restrictions.md @@ -0,0 +1,12 @@ +# Restrictions + +All classes within a module must be in named packages + +Unlike packages, where two packages can have classes of the same name, +two modules cannot contain the same package. + +This means that you cannot have a class in the `java.lang` package defined +in any of your modules, as that already comes with Java. + +If you accidentally make a situation where one package is in multiple modules +we call that having a "split package" and Java will raise an error. \ No newline at end of file diff --git a/src/modules/the_unnamed_module.md b/src/modules/the_unnamed_module.md new file mode 100644 index 00000000..909d2be5 --- /dev/null +++ b/src/modules/the_unnamed_module.md @@ -0,0 +1,18 @@ +# The Unnamed Module + +All packages that are not in a named module are placed in "the unnamed module." +This includes all the code you have written thus far and all code written without +a `module-info.java` alongside it. + +The unnamed module is special in that code within it `requires` every other module +and therefore can see every exported package and every class within. + +It is fine to have your code in the unnamed module, but code you get from other people +should generally be in named modules. This is because modules are mostly +useful for sharing code across "maintenance boundaries." + +If you are not going to be sharing your code with another person then +the ability to have "unexported packages" matters less. If you are +the person who code is shared with, the group sharing it with you +will expect you to not touch classes in packages they did not export. + diff --git a/src/multi_dimensional_arrays.md b/src/multi_dimensional_arrays.md new file mode 100644 index 00000000..208f44a5 --- /dev/null +++ b/src/multi_dimensional_arrays.md @@ -0,0 +1,23 @@ +# Multi-Dimensional Arrays + + + + +When you make an array in which each element is itself an array +we call that array of arrays a "multi-dimensional array." + +```java +class Main { + void main() { + int width = 30; + int height = 30; + int[][] pixels = new int[width][height]; + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + pixels[i][j] = 0; + } + } + } +} +``` diff --git a/src/multi_dimensional_arrays/access_individual_elements.md b/src/multi_dimensional_arrays/access_individual_elements.md new file mode 100644 index 00000000..e1985ef8 --- /dev/null +++ b/src/multi_dimensional_arrays/access_individual_elements.md @@ -0,0 +1,23 @@ +# Access Individual Elements + +Accessing an element in a multi-dimensional array works the same as +accessing the elements of a one-dimensional array. + +Each time you index into it you will get out another array, so you just keep writing `[]` until +you've drilled down to an individual element. + +```java +~void main() { +String[][] ticTacToe = { + { "O", "", "O" }, + { "X", "O", "X" }, + { "O", "X", "X" } +} + +IO.println(ticTacToe[2][1]); + +// This is equivalent to the above +String[] row = ticTacToe[2]; +IO.println(row[1]); +~} +``` \ No newline at end of file diff --git a/src/multi_dimensional_arrays/array_initializers.md b/src/multi_dimensional_arrays/array_initializers.md new file mode 100644 index 00000000..337c3a43 --- /dev/null +++ b/src/multi_dimensional_arrays/array_initializers.md @@ -0,0 +1,16 @@ +# Array Initializers + +To give an initial value to a multi-dimensional array you can use multiple nested +array initializers. + +These are the same as normal array initializers but with potentially more initializers inside.[^moray] + +```java,no_run +String[][] ticTacToe = { + { "O", "", "O" }, + { "X", "O", "X" }, + { "O", "X", "X" } +} +``` + +[^moray]: When array initizalize and more initialize inside that's a moray. \ No newline at end of file diff --git a/src/multi_dimensional_arrays/challenges.md b/src/multi_dimensional_arrays/challenges.md new file mode 100644 index 00000000..342456de --- /dev/null +++ b/src/multi_dimensional_arrays/challenges.md @@ -0,0 +1,101 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + + + + +## Challenge 1. + +Initialize 10x10 2D array of `int`s by size. +Print every element of this new 2D array. + +Will any elements of the outermost array be `null`? +Write down your guess before making the program. + +```java,editable +void main() { + int[][] xs = /* CODE HERE */ + // CODE HERE +} +``` + +## Challenge 2. + +Print out every character in the 2D array of `char`s. After each +row print a new line. + +```java,editable +void main() { + char[][] picture = new char[][] { + new char[] { ' ', ' ', ' ', ' ' }, + new char[] { ' ', '*', '*', ' ' }, + new char[] { '\\', ' ', ' ', '/' }, + new char[] { ' ', '-', '-', ' ' } + }; + + // CODE HERE +} +``` + +## Challenge 3. + +"Draw" a smiley face on the "canvas" provided by a 5x5 2D `char` array. + +Use similar code as the previous challenge to print it out. + +```java,editable +void main() { + final char[][] picture = new char[5][5]; + + // CODE HERE +} +``` + + + +## Challenge 4. + +Write a method named `winner`. It should take in a 2-dimensional +array of `String`s where each character is either `X`, `O`, or an empty string. +This 2D array represents a [game of Tic-Tac-Toe](https://en.wikipedia.org/wiki/Tic-tac-toe). + +If there is a winner of the Tic-Tac-Toe game, it should return `X` or `O` depending on who the winner is. +It should return `null` if nobody won or the game is a tie. + +```java +String winner(String[][] ticTacToe) { + // CODE HERE +} + +void main() { + var winnerA = winner(new String[][] { + new String[] { "X", "X", "" }, + new String[] { "O", "", "" }, + new String[] { "O", "", "" } + }); + + IO.println(winnerA); + + var winnerB = winner(new String[][] { + new String[] { "X", "X", "X" }, + new String[] { "O", "O", "X" }, + new String[] { "O", "", "O" } + }); + + IO.println(winnerB); + + var winnerC = winner(new String[][] { + new String[] { "O", "X", "O" }, + new String[] { "O", "O", "X" }, + new String[] { "O", "X", "O" } + }); + + IO.println(winnerC); +} +``` + + diff --git a/src/multi_dimensional_arrays/declaration.md b/src/multi_dimensional_arrays/declaration.md new file mode 100644 index 00000000..76e587bd --- /dev/null +++ b/src/multi_dimensional_arrays/declaration.md @@ -0,0 +1,16 @@ +# Declaration + +Declaring a multi-dimensional array is similar to declaring a normal array. + +The difference is that instead of only one `[]` following the type declaration, you add an extra `[]` +for each extra dimension. + +```java,no_run +// 2d array +int[][] grid; +// 3d array +int[][][] cube; +// 4d array +int[][][][] hypercube; +// ... and so on +``` \ No newline at end of file diff --git a/src/multi_dimensional_arrays/default_values.md b/src/multi_dimensional_arrays/default_values.md new file mode 100644 index 00000000..00ba2f1e --- /dev/null +++ b/src/multi_dimensional_arrays/default_values.md @@ -0,0 +1,19 @@ +# Default Values + +When a multi-dimensional array is made by just providing a size, its elements +are initialized to the same default values as they would be in a regular array. + +The difference is that each "nested array" will not be initialized to null. + +```java +~void main() { +String[][] ticTacToe = new String[3][3]; + +// Each array will be non-null +IO.println(ticTacToe[0]); + +// But the elements of those arrays will be null +// (or whatever the default value is for the type.) +IO.println(ticTacToe[0][0]); +~} +``` \ No newline at end of file diff --git a/src/multi_dimensional_arrays/header.png b/src/multi_dimensional_arrays/header.png new file mode 100644 index 00000000..13f45b6c Binary files /dev/null and b/src/multi_dimensional_arrays/header.png differ diff --git a/src/multi_dimensional_arrays/initialization_with_new.md b/src/multi_dimensional_arrays/initialization_with_new.md new file mode 100644 index 00000000..a0d45e3f --- /dev/null +++ b/src/multi_dimensional_arrays/initialization_with_new.md @@ -0,0 +1,14 @@ +# Initialization with new + +Just like for one-dimensional arrays, adding `new` and the type of the array before an initializer +will let you use a multi-dimensional array as an expression or for a delayed assignment. + +```java +~void main() { +int[][] numbers; +numbers = new int[25][25]; + +// I think 25 is my favorite number +int length = (new int[25][25]).length; +~} +``` \ No newline at end of file diff --git a/src/multi_dimensional_arrays/initialize_with_size.md b/src/multi_dimensional_arrays/initialize_with_size.md new file mode 100644 index 00000000..4266adae --- /dev/null +++ b/src/multi_dimensional_arrays/initialize_with_size.md @@ -0,0 +1,11 @@ +# Initialization with Size + +Multi-dimensional arrays are used for representing multi-dimensional things. +These tend to also be large things for which writing out every value in an initializer is impractical. + +As such, you can initialize them with only an initial size for each dimension. + +```java +// Will make a 2d array of 160x144 booleans +boolean[][] pixels = new boolean[160][144]; +``` diff --git a/src/multi_dimensional_arrays/populate_values.md b/src/multi_dimensional_arrays/populate_values.md new file mode 100644 index 00000000..457be4d0 --- /dev/null +++ b/src/multi_dimensional_arrays/populate_values.md @@ -0,0 +1,20 @@ +# Populate Values + +Once you've created a multi-dimensional array you can use loops +to give initial values to its elements. + +Since you will end up nesting these loops, its a good example of a place where you should progress +naming your variables `i -> j -> k`. + +```java +// If you prefer true as the default over false +// you need to do that work yourself +boolean[][] booleanField = new boolean[25][25]; +for (int i = 0; i < booleanField.length; i++) { + for (int j = 0; j < booleanField[i].length; j++) { + booleanField[i][j] = true; + } +} +``` + +This is helpful if the default values (`null`, `0`, `false`, etc.) are not what you'd prefer \ No newline at end of file diff --git a/src/multi_dimensional_arrays/ragged_arrays.md b/src/multi_dimensional_arrays/ragged_arrays.md new file mode 100644 index 00000000..f118280b --- /dev/null +++ b/src/multi_dimensional_arrays/ragged_arrays.md @@ -0,0 +1,42 @@ +# Ragged Arrays + +In relatively unique circumstances[^alreadyunique] you might want to make a "ragged array". That is, a multi-dimensional +array where each array might be of a different size. + +You can do this by making the number of elements in nested initializers be different +```java +boolean[][] triangle = { + { false }, + { false, false, false}, + { false, false, false, false, false }, + { false, false, false}, + { false } +} +``` + +Or by using arrays initialized with `new` inside of an initializer. + +```java,no_run +boolean[][] triangle = { + new boolean[1], + new boolean[3], + new boolean[5], + new boolean[3], + new boolean[1] +}; +``` + +Or even by omitting the trailing dimensions on when initializing with new and later filling in each row. + +```java,no_run +boolean[][] triangle = new boolean[5][]; +triangle[0] = new boolean[1]; +triangle[1] = new boolean[3]; +triangle[2] = new boolean[5]; +triangle[3] = new boolean[3]; +triangle[4] = new boolean[1]; +``` + + + +[^alreadyunique]: And if you are using a multi-dimensional array, you are already doing something interesting. \ No newline at end of file diff --git a/src/multi_dimensional_arrays/set_individual_elements.md b/src/multi_dimensional_arrays/set_individual_elements.md new file mode 100644 index 00000000..00c38480 --- /dev/null +++ b/src/multi_dimensional_arrays/set_individual_elements.md @@ -0,0 +1,18 @@ +# Set Individual Elements + +You can set the value for elements in a multi-dimensional array the same way +as for one-dimensional arrays, just with an extra `[]` for each extra dimension. + +```java +String[][] ticTacToe = { + { "", "", "" }, + { "", "", "" }, + { "", "", "" } +} + +ticTacToe[0][0] = "X"; + +// The above is a shorthand for this +String[] row = ticTacToe[0]; +row[1] = "O"; +``` diff --git a/src/multi_file_programs.md b/src/multi_file_programs.md new file mode 100644 index 00000000..d3337a16 --- /dev/null +++ b/src/multi_file_programs.md @@ -0,0 +1,13 @@ +# Multi-File Programs + + + +For many reasons, it makes sense to split programs into multiple files. + +This is one of those things that is easier to show than tell, so to follow along +make a blank folder on your computer. + +``` +$ mkdir program +$ cd program +``` \ No newline at end of file diff --git a/src/multi_file_programs/a_second_file.md b/src/multi_file_programs/a_second_file.md new file mode 100644 index 00000000..ae71b7f0 --- /dev/null +++ b/src/multi_file_programs/a_second_file.md @@ -0,0 +1,43 @@ +# A Second file + +In files that are not `Main.java` you can put other code, but +only in the form of a class. + +By this I mean, while in `Main.java` you are able to write something like this. + +```java +void sayHello() { + IO.println("Hello"); +} + +void main() { + sayHello(); +} +``` + +In a file named `Ball.java` you need to put all code inside a `Ball` class. + +```java +class Ball { + // You can write constructors, methods, fields, etc. + final int size; + + Ball(int size) { + this.size = size; + } +} + +// But you cannot have any "top level" methods or things outside +// of the Ball class +``` + +Then from `Main.java` you can make an instance of `Ball` + +```java +void main() { + var ball = new Ball(10); + IO.println("The ball is " + ball.size + "cm across"); +} +``` + +When you run `java src/Main.java` it will find `src/Ball.java` and use the code in there. \ No newline at end of file diff --git a/src/multi_file_programs/challenges.md b/src/multi_file_programs/challenges.md new file mode 100644 index 00000000..c7062ee1 --- /dev/null +++ b/src/multi_file_programs/challenges.md @@ -0,0 +1,105 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1 + +Put this code in a file named `Pickle.java` + +```java +class Pickle { + boolean dill; +} +``` + +Put this code in a file named `Ramen.java` + +```java +class Ramen { + void mixWith(Pickle pickle) { + IO.println("This is gross."); + if (pickle.dill) { + IO.println("Extra super gross."); + } + } +} +``` + +And finally put this code in `Main.java` + +```java +class Main { + void main() { + var pickle = new Pickle(); + pickle.dill = true; + + var ramen = new Ramen(); + + ramen.mixWith(pickle); + } +} +``` + +Then run the entire program from your terminal by using `java Main.java` +in the directory where you made these files. + +## Challenge 2 + +Move `Ramen.java`, `Pickle.java`, and `Main.java` into a folder named `src`. + +Make sure you can still run the program by typing `java src/Main.java`. + +## Challenge 3 + +The following code uses an anonymous main class. Make it instead use a named class. + +```java,editable +int dogs = 99; + +void main() { + while (dogs > 0) { + IO.println(dogs + " dogs on the floor."); + IO.println("Wake one up, take it for a walk"); + dogs--; + } + IO.println("No more dogs on the floor."); +} +``` + +## Challenge 4 + + +Combine the following programs while keeping them in separate files. +This will require you to give names to both classes as well as +parameterize the "dogs on the floor" program in some way. + +```java +int dogs = 99; + +void main() { + while (dogs > 0) { + IO.println(dogs + " dogs on the floor."); + IO.println("Wake one up, take it for a walk"); + dogs--; + } + IO.println("No more dogs on the floor."); +} +``` + + +```java +void main() { + while (true) { + try { + int dogs = Integer.parseInt(IO.readln("How many dogs are on the floor? ")); + IO.println("TODO"); // Call the first program here somehow. + } catch (RuntimeException e) { + IO.println("Goodbye!"); + } + } + +} +``` \ No newline at end of file diff --git a/src/multi_file_programs/file_names.md b/src/multi_file_programs/file_names.md new file mode 100644 index 00000000..8c6365dc --- /dev/null +++ b/src/multi_file_programs/file_names.md @@ -0,0 +1,17 @@ +# File names + +When you make a file called `Ball.java` you should expect to find a class named `Ball` inside of it. + +This is a general rule. When you split code into multiple files each file should be +a class and the name of the class should match the name of the file. + +So if I want to write a `Position` class + +```java +class Position { + int x; + int y; +} +``` + +That should go in its own file named `Position.java`. \ No newline at end of file diff --git a/src/multi_file_programs/global_fields.md b/src/multi_file_programs/global_fields.md new file mode 100644 index 00000000..6de70787 --- /dev/null +++ b/src/multi_file_programs/global_fields.md @@ -0,0 +1,32 @@ +# Global Fields + +Global fields, accordingly, were always a lie. + +```java +int number = 0; + +void main() { + IO.println(number); + number++; + IO.println(number); +} +``` + +They are just normal fields in the anonymous main class. + +```java +class Main { + int number = 0; + + void main() { + IO.println(number); + number++; + IO.println(number); + } +} +``` + +This means that once you move to programs in multiple files they are no longer global +to your program - just the code in the main class.[^bummer] + +[^bummer]: Huge bummer, but we will learn how to make actually global things later. \ No newline at end of file diff --git a/src/multi_file_programs/header.png b/src/multi_file_programs/header.png new file mode 100644 index 00000000..bd302681 Binary files /dev/null and b/src/multi_file_programs/header.png differ diff --git a/src/multi_file_programs/the_anonymous_main_class.md b/src/multi_file_programs/the_anonymous_main_class.md new file mode 100644 index 00000000..7a1cff66 --- /dev/null +++ b/src/multi_file_programs/the_anonymous_main_class.md @@ -0,0 +1,30 @@ +# The Anonymous Main Class + +Surprise! Everything in Java is actually in a class. + +This includes the code in `Main.java`. + +```java +void main() { + IO.println("What, really?"); +} +``` + +Everything we've written so far has been in what is called "the anonymous main class." +We call it anonymous because we never gave it a name. + +We call it the main class because you are only allowed to skip naming a class if it is the one you use to start your program, and that requires a `void main()` method. + +If you take any code we've produced up until now and wrap it with `class Main {}` it will continue to work as-is. + +```java +class Main { + void main() { + IO.println("yep."); + } +} +``` + +What Java will do is run `new Main().main();` to start your program. + + diff --git a/src/multi_file_programs/the_main_file.md b/src/multi_file_programs/the_main_file.md new file mode 100644 index 00000000..1f155717 --- /dev/null +++ b/src/multi_file_programs/the_main_file.md @@ -0,0 +1,28 @@ +# The Main file + +Inside your `src` folder make a file called `Main.java`. This is the file where +we are going to write the "start" of the program. + +All the Java code you've written up until now will work if you put it into this file. + +```java +void main() { + IO.println("Hello, world!"); +} +``` + +So you should now have something that looks like this. + +``` +project/ + src/ + Main.java +``` + +To run your program, use the `java` command. + +``` +$ java src/Main.java +Hello, world! +``` + diff --git a/src/multi_file_programs/the_sources_folder.md b/src/multi_file_programs/the_sources_folder.md new file mode 100644 index 00000000..03ad7f04 --- /dev/null +++ b/src/multi_file_programs/the_sources_folder.md @@ -0,0 +1,23 @@ +# The Sources folder + +When people work on "a project" - meaning a program made to +accomplish some task - there will often be more than just Java code involved. + +To deal with this its common to make a folder to put Java code into. I prefer the name +`src` for this. + +``` +$ mkdir src +``` + +And inside this `src` folder we will put the code.[^layouts] + +So you should have this + +``` +project/ <- Open this in your text editor + src/ <- Code will go here +``` + +[^layouts]: There are different ways to layout a project. All are valid. You can call the `src` folder `STUFF` - it ultimately doesn't matter. This is just another one of those social conventions. + diff --git a/src/niche_numerics.md b/src/niche_numerics.md new file mode 100644 index 00000000..27c24e0d --- /dev/null +++ b/src/niche_numerics.md @@ -0,0 +1,21 @@ +# Niche Numerics + + + +For a surprisingly wide range of programs `int` and `double` +(along with their boxed counterparts `Integer` and `Double`) +are the only numeric types you will need. + +Every now and then, however, you will want something more exotic[^wording]. + +```java +~void main() { +byte b = 123; + +IO.println(b); +~} +``` + +[^wording]: The reason I am calling these "exotic" and "niche" has nothing to do with how +useful they are. If you want a `byte` then a `byte` is what you want. Its just that most +of the time (in my experience) you can get by with a little help from your `int`s. \ No newline at end of file diff --git a/src/niche_numerics/byte.md b/src/niche_numerics/byte.md new file mode 100644 index 00000000..14689115 --- /dev/null +++ b/src/niche_numerics/byte.md @@ -0,0 +1,70 @@ +# byte + +A `byte` represents a signed value between `-128` +and `127`. + +```java +~void main() { +byte a = 127; +IO.println(a); +byte b = -128; +IO.println(b); +~} +``` + +Operations like `+` and `*` on a `byte` will "promote" the result an `int` +and you will need to cast the result. Going from an `int` to a `byte` +is a narrowing conversion. + +```java +~void main() { +byte a = 5; +byte b = 6; +// Need to cast the result to a (byte) again +byte c = (byte) (a * b); +IO.println(c); +~} +``` + +Conversely, going from a `byte` to an `int` is a widening conversion and you won't +need a cast. + +```java +~void main() { +byte a = 5; +int a2 = a; // Widening conversion +IO.println(a2); +~} +``` + + +And if you have need of a potentially nullable `byte`, `Byte` with a capital `B` is the boxed version. + +```java +~void main() { +// Can't have a null "byte" +// byte b = null; + +// But you can have a null "Byte" +Byte b = null; +IO.println(b); +~} +``` + +You will most often use the `byte` type when working with data as sequences of bytes, such as reading from and writing to binary files. Representing binary data as arrays of `byte` values is more memory-efficient than representing each individual byte as, say, an `int`. + + + + +```java,no_run +// This array of 4 bytes +byte[] bytes = { 1, 2, 3, 4 }; +// Will take up as much space as this +// array with 1 int +int[] oneInt = { 1 }; +``` + + diff --git a/src/niche_numerics/challenges.md b/src/niche_numerics/challenges.md new file mode 100644 index 00000000..081c2170 --- /dev/null +++ b/src/niche_numerics/challenges.md @@ -0,0 +1,103 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Write two static methods in the following `Bytes` class. + +The first should take an `int` and return an array of 4 +bytes. + +The second should take that array of 4 bytes and give back the original int. + +There are a few ways to do this, but you only pass the test if +you can do a "full round trip." + +```java,editable +class Bytes { + static int toInt(byte[] bytes) { + // CODE HERE + } + static byte[] toBytes(int value) { + // CODE HERE + } +} + +class Main { + void main() { + int i = 172089379; + + byte[] bs = Bytes.toBytes(i); + + // 172089379 + IO.println(Bytes.fromInt(bs)); + } +} +``` + +## Challenge 2. + +Turn the `String` `"hello world"` into a `short[]`. Print the contents +of this `short[]` and then turn it back into a `String`. + +```java +class Main { + void main() { + String s = "hello world"; + + short[] shorts; + + // CODE HERE + + String roundTripped; + + // CODE HERE + } +} +``` + +## Challenge 3. + +Make a class named `UnsignedInteger`. It should wrap up an `int` such that +all operations on it are performed using the appropriate unsigned specific methods +when needed. + +At a minimum: + +- Implement `toString` so that the unsigned representation is used +- Implement `equals` and `hashCode` +- Implement `plus` and `minus` +- Implement the `Comparable` interface. + +```java +// CODE HERE + +class Main { + void main() { + var i = new UnsignedInteger(0); + IO.println(i.minus(new UnsignedInteger(1))); + } +} +``` + +## Challenge 4. + +Convert this program to use `float` instead of `double`. + +```java +class Main { + void main() { + double radius = Double.parseDouble( + IO.readln("What is the radius of the circle? ") + ); + double pi = 3.14; + double circumference = 2 * pi * radius; + + IO.println("Circumference: " + circumference); + } +} +``` \ No newline at end of file diff --git a/src/niche_numerics/float.md b/src/niche_numerics/float.md new file mode 100644 index 00000000..39399a37 --- /dev/null +++ b/src/niche_numerics/float.md @@ -0,0 +1,63 @@ +# float + +What `int` is to `long`, `float` is to `double`. Even the type name `double` implicitly means "double the size of a float." So if you want something half the size of a `double` you can use a `float`. + +To write a float in a program you need to write `f` at the end of the floating +point literal. + +```java +~void main() { +float f = 3.5f; +IO.println(f); +~} +``` + +This is because Java sees floating point literals without a trailing `f` as representing a `double`. + +```java,does_not_compile +~void main() { +float f = 3.5; +IO.println(f); +~} +``` + +Conversions from a `double` to a `float` are narrowing and require an explicit cast. +Conversions from a `float` to a `double` are widening and do not require a cast. + +```java +~void main() { +double a = 6.5; +// Need a cast +float b = (float) a; +IO.println(b); + +float c = 9.5f; +// Do not need a cast +double d = c; +IO.println(d); +~} +``` + +And if you have need of a potentially nullable `float`, `Float` with a capital `F` is the boxed version. + +```java +~void main() { +// Can't have a null "float" +// float f = null; + +// But you can have a null "Float" +Float f = null; +IO.println(f); +~} +``` + +You will really only want a `float` when you are trying to save space in memory. +Otherwise its best to just use a `double`. + +```java,no_run +// This array of 2 floats +float[] floats = { 1.0f, 2.0f }; +// Will take up as much space as this +// array with 1 double +double[] oneDouble = { 1.0 }; +``` \ No newline at end of file diff --git a/src/niche_numerics/header.png b/src/niche_numerics/header.png new file mode 100644 index 00000000..ac717c1a Binary files /dev/null and b/src/niche_numerics/header.png differ diff --git a/src/niche_numerics/long.md b/src/niche_numerics/long.md new file mode 100644 index 00000000..154777b8 --- /dev/null +++ b/src/niche_numerics/long.md @@ -0,0 +1,74 @@ +# long + +If an `int` is not big enough for your needs, a `long` is twice as big as an `int` +and can represent numbers from `-2^63` to `2^63 - 1`. + +You can make a `long` from an integer literal, but integer literals do not +normally allow for numbers that an `int` cannot store. + +```java,does_not_compile +~void main() { +// Smaller numbers work without issue +long smallNumber = 5; +IO.println(smallNumber); +// This is too big for an int +long bigNumber = 55555555555; +IO.println(bigNumber); +~} +``` + +For those cases you need to add an `L` to the end of the literal.[^lforlong] + +```java +~void main() { +long smallNumber = 5; +IO.println(smallNumber); +// But with an L at the end, its not too big for a long +long bigNumber = 55555555555L; +IO.println(bigNumber); +~} +``` + +All operations with a `long` will result in a `long`. Conversions to `int` and +other "smaller" integer types will be narrowing and require a cast. + +```java +~void main() { +long a = 5; +int b = 3; +// a long times an int will result in a long +long c = a * b; +IO.println(c); +~} +``` + +And if you have need of a potentially nullable `long`, `Long` with a capital `L` is the boxed version. + +```java +~void main() { +// Can't have a null "long" +// long l = null; + +// But you can have a null "Long" +Long l = null; +IO.println(l); +~} +``` + +The reason you will likely end up using `int` more than `long` is that `int` +works more with other parts of Java. Like array indexing - you can't +get an item in an array with a `long`, you need an `int` for that. + +```java,does_not_compile +~void main() { +String[] sadRobots = { "2B", "9S", "A2" }; +long index = 2; +// Longs can't be used as indexes +String sadRobot = sadRobots[index]; +~} +``` + +But there is nothing wrong with a `long`. If you need to represent a number that is potentially bigger than an `int` then it is useful. + + +[^lforlong]: "L is for long" would be a total cop-out in a children's picture book. \ No newline at end of file diff --git a/src/niche_numerics/short.md b/src/niche_numerics/short.md new file mode 100644 index 00000000..123a9a7a --- /dev/null +++ b/src/niche_numerics/short.md @@ -0,0 +1,87 @@ +# short + +A `short` represents a signed value between `-32768` +and `32767`. Representing a `short` takes twice as much memory as representing +a `byte` and half as much memory as representing an `int`. + +```java +~void main() { +short a = 32767; +IO.println(a); +byte b = -32768; +IO.println(b); +~} +``` + +Operations like `+` and `*` on a `short` will "promote" the result an `int` +and you will need to cast the result. Going from an `int` to a `short` +is a narrowing conversion. + +```java +~void main() { +short a = 5; +short b = 6; +// Need to cast the result to a (byte) again +short c = (short) (a * b); +IO.println(c); +~} +``` + +Conversely, going from a `short` to an `int` is a widening conversion and you won't +need a cast. + +```java +~void main() { +short a = 5; +int a2 = a; // Widening conversion +IO.println(a2); +~} +``` + + +And if you have need of a potentially nullable `short`, `Short` with a capital `S` is the boxed version. + +```java +~void main() { +// Can't have a null "short" +// short b = null; + +// But you can have a null "Short" +Short b = null; +IO.println(b); +~} +``` + + +A `short` also takes up exactly as much space as a `char` and converting between the two +is allowed, but will still require an explicit cast in both directions.[^neither] + +```java +~void main() { +short s = 50; +char c = (char) s; +s = (short) c; +IO.println(c); +~} +``` + +You will most often want a `short` when you are trying to save space in memory but +need to represent numbers beyond what a `byte` can represent.[^rare] + +```java,no_run +// This array of 2 shorts +short[] shorts = { 1, 2 }; + +// Will take up as much space as this +// array with 1 int +int[] oneInt = { 1 }; + +// And as much space as this array with 4 bytes +byte[] bytes = { 1, 2, 3, 4 }; +``` + + +[^neither]: These are neither narrowing or widening conversions. Java just makes you put +the cast. One way to view this is that when you go from a `short` to a `char` you've transformed a "number" into a "character." So while no information is lost, what the information "represents" has changed. + +[^rare]: As you might suspect, this is more rare than the situations where you would want a `byte`. \ No newline at end of file diff --git a/src/niche_numerics/unsigned_operations.md b/src/niche_numerics/unsigned_operations.md new file mode 100644 index 00000000..93d3b106 --- /dev/null +++ b/src/niche_numerics/unsigned_operations.md @@ -0,0 +1,41 @@ +# Unsigned Operations + +While a `byte` represents a signed value between `-128` +and `127`, its not uncommon to instead want to represent a number between `0` and `255`. +We call representations like that, where we repurpose what would be negative numbers to instead be large positive numbers, "unsigned." + +All of Java's built-in numeric types are signed, but you can use static methods +to perform operations on them that work as if they were unsigned. + +```java +~void main() { +// Java sees 255 as being out of range for a byte +// so we have to cast +byte b = (byte) 255; +// And by default this will be seen as -1 +IO.println(b); +// But we can use Byte.toUnsignedInt to see it +// as 255 +IO.println(Byte.toUnsignedInt(b)); +~} +``` + +Operations like addition with `+` don't need special unsigned versions since they work "the same" regardless of whether you ultimately choose to interpret your numbers as unsigned or signed. Other operations like division or comparisons need special logic +to work right when doing "unsigned math." + +```java +~void main() { +int i = -1; +// -1 is actually 4294967295 when viewed as an unsigned int +IO.println(Integer.toUnsignedString(i)); +// So normal comparisons won't do what you want +boolean isFiveBigger = 5 > i; +IO.println(isFiveBigger); +// You'd want to use special unsigned comparisons +isFiveBigger = Integer.compareUnsigned(5, i) > 0; +IO.println(isFiveBigger); +~} +``` + +All these special unsigned operations are `static` methods on each type's corresponding +boxed equivalent class. What static methods are available varies from type to type. \ No newline at end of file diff --git a/src/null.md b/src/null.md index 7b6af596..9b376113 100644 --- a/src/null.md +++ b/src/null.md @@ -1,5 +1,8 @@ # null + + + There is a special value called `null` which is assignable to most types. ```java @@ -7,8 +10,8 @@ void main() { String name = null; int[] numbers = null; - System.out.println(name); - System.out.println(numbers); + IO.println(name); + IO.println(numbers); } ``` diff --git a/src/null/challenges.md b/src/null/challenges.md new file mode 100644 index 00000000..4dcd216e --- /dev/null +++ b/src/null/challenges.md @@ -0,0 +1,143 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Write a method which takes in a `String[]` representing +a series of names and prints out every name in sequence. + +If this method is given `null`, it should act as if it +was given an empty array. + + +```java,editable +void printNames(String[] names) { + +} + +void main() { + printNames(new String[] { + "Joker", + "Batman", + "Alfred" + }); +} +``` + +## Challenge 2. + +Alter the method you wrote in the previous challenge +so that if it is given `null` it outputs the message +`You do not know any names yet`. + +If it is given an empty `String[]` it should continue +to simply output nothing. + +```java,editable +void printNames(String[] names) { + +} + +void main() { + printNames(new String[] { + "Joker", + "Batman", + "Alfred" + }); +} +``` + +## Challenge 3. + +Will the following code throw a `NullPointerException`? +Why or why not? + +```java +void main() { + String[] jobs = new String[] { + "Carpenter", + "Baker", + null, + "Astronomer" + }; + + for (int i = 0; i < jobs.length; i++) { + IO.println(jobs[i]); + } +} +``` + +## Challenge 4. + +The following code won't work. Give your best guess as to why +and then try running it. + +```java +void main() { + int[] numbers = new int[] { + 45, + 32, + null, + 94 + }; + + for (int i = 0; i < numbers.length; i++) { + IO.println(numbers[i]); + } +} +``` + +## Challenge 5. + +Without changing anything in the `main` method, make the `bigness` +method not throw a `NullPointerException` and still have the "correct" +behavior for non-null inputs. + +```java,editable +String bigness(String letters) { + int bigness = 0; + for (int i = 0; i < letters.length(); i++) { + bigness++; + } + + if (bigness < 5) { + return "small"; + } + else if (bigness < 10) { + return "medium"; + } + else { + return "big"; + } +} + +void main() { + IO.println( + bigness("bore") + ); + + IO.println( + bigness("boiler") + ); + + IO.println( + bigness("filter") + ); + + IO.println( + bigness("knower") + ); + + IO.println( + bigness("chrysanthemum") + ); + + IO.println( + bigness(null) + ); +} +``` \ No newline at end of file diff --git a/src/null/checking_for_null.md b/src/null/checking_for_null.md index acf77826..5c36527c 100644 --- a/src/null/checking_for_null.md +++ b/src/null/checking_for_null.md @@ -6,10 +6,10 @@ check by using `==`. ```java void sayHello(String firstName, String lastName) { if (lastName == null) { - System.out.println("Hello " + firstName); + IO.println("Hello " + firstName); } else { - System.out.println("Hello " + firstName + " " + lastName); + IO.println("Hello " + firstName + " " + lastName); } } diff --git a/src/null/header.png b/src/null/header.png new file mode 100644 index 00000000..777882c9 Binary files /dev/null and b/src/null/header.png differ diff --git a/src/null/null_as_absence.md b/src/null/null_as_absence.md index 3b809167..d9f8cc45 100644 --- a/src/null/null_as_absence.md +++ b/src/null/null_as_absence.md @@ -13,7 +13,7 @@ Cher does not have a last name. String firstName = "Cher"; String lastName = null; -System.out.println(firstName); -System.out.println(lastName); +IO.println(firstName); +IO.println(lastName); ~} ``` diff --git a/src/null/null_pointer_exception.md b/src/null/null_pointer_exception.md index 76a46bf3..2799baae 100644 --- a/src/null/null_pointer_exception.md +++ b/src/null/null_pointer_exception.md @@ -8,7 +8,7 @@ crash. void main() { String thing = null; // NullPointerException - System.out.println(thing.length()); + IO.println(thing.length()); } ``` diff --git a/src/objects.md b/src/objects.md new file mode 100644 index 00000000..fb7f172d --- /dev/null +++ b/src/objects.md @@ -0,0 +1,15 @@ +# Object + + + + +You may have heard the phrase "everything is an object" +parroted about. + +It sounds very silly. Of course things are things, what else would they be? + +In Java it has a very specific meaning. There is a class named +`java.lang.Object` (`Object` for short) which all other classes are +derived from. + +Everything "is" an `Object`. \ No newline at end of file diff --git a/src/objects/array_subtypes.md b/src/objects/array_subtypes.md new file mode 100644 index 00000000..642aaa1e --- /dev/null +++ b/src/objects/array_subtypes.md @@ -0,0 +1 @@ +# Array Subtypes diff --git a/src/objects/challenges.md b/src/objects/challenges.md new file mode 100644 index 00000000..ae8cb167 --- /dev/null +++ b/src/objects/challenges.md @@ -0,0 +1,129 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +The `Witch` class has a method named `pullFromHat` +that returns an `Object` that is sometimes a `Spell` and sometimes a `Broom`. + +If it is a `Broom` call the `.fly()` method on it. If it is a `Spell` call `.cast`. + +```java +class Spell { + private final String name; + + Spell(String name) { + this.name = name; + } + + void cast() { + IO.println("Casting " + name + "..."); + } +} + +class Broom { + void fly() { + IO.println("Flying!"); + } +} + +class Witch { + Object pullFromHat() { + double r = Math.random(); + if (r < 0.25) { + return new Spell("Ensmallen"); + } + else if (r < 0.5) { + return new Spell("Embiggen"); + } + else if (r < 0.75) { + return new Spell("Enlongen"); + } + else { + return new Broom(); + } + } +} + +class Main { + void main() { + var witch = new Witch(); + + Object item = witch.pullFromHat(); + + // CODE HERE + } +} +``` + +## Challenge 2. + +Will the following code work? Why or why not? + +```java,editable +class Main { + void main() { + String s = "abc"; + Object o = s; + + IO.println(o.length()); + } +} +``` + +## Challenge 3. + +Will the following code work? Why or why not? + +```java,editable +class Main { + void main() { + Object o = "abc"; + String s = o; + + IO.println(s.length()); + } +} +``` + +## Challenge 4. + +Implement `toString`, `equals`, and `hashCode` +on the following `Ogre` class such that it behaves +the same as an `Ogre` record. + +```java +record Ogre(String name, double strength) {} +``` + +```java,editable +class Ogre { + final String name; + final double strength; + + Ogre(String name, double strength) { + this.name = name; + this.strength = strength; + } + + // CODE HERE +} + +class Main { + void main() { + Ogre o1 = new Ogre("Morihito", 100); + Ogre o2 = new Ogre("Morihito", 100); + + IO.println(o1); // Ogre[name=Morihito, strength=100] + IO.println(o1.equals(o2)); // True + IO.println(o1.hashCode() == o2.hashCode()); // True + IO.println(o1.equals(new Ogre("Reiji", 5))); // False + IO.println(o1.hashCode() == new Ogre("Reiji", 5).hashCode()); // False + } +} +``` + diff --git a/src/objects/equals_and_hashCode.md b/src/objects/equals_and_hashCode.md new file mode 100644 index 00000000..6a45732a --- /dev/null +++ b/src/objects/equals_and_hashCode.md @@ -0,0 +1,79 @@ +# equals and hashCode + +In addition to `toString`, two methods that all `Object`s have defined +are `equals` and `hashCode`. + +`equals` is a method that takes another object and returns a `boolean` based +on whether you would consider those objects to be equivalent. + +By default, `equals` will behave identically to `==`. + +```java +class Thing {} + +class Main { + void main() { + var t1 = new Thing(); + var t2 = new Thing(); + + IO.println(t1 == t1); + IO.println(t1.equals(t1)); + + IO.println(t2 == t2); + IO.println(t2.equals(t2)); + + + IO.println(t1 == t2); + IO.println(t1.equals(t2)); + } +} +``` + +Many types will have `equals` overridden to do things like return equal if +the types represent the same context, as is the case with `Integer`. + +```java +class Main { + void main() { + Integer a = 3; + Integer b = 3; + Integer c = 4; + + IO.println(a.equals(b)); + IO.println(a.equals(c)); + } +} +``` + +`hashCode` is a method that tells you if things _might_ be equal. It returns an `int`. +If two objects give different hash codes then its assumed that the result of `a.equals(b)` +will be `false`. If they give the same hash code, then the result of `a.equals(b)` might +be either `true` or `false`. + +Its sort-of like asking what the first letter of someone's name is. If their names start with different letters +they definitely have different names. If their names both start with `B` they _might_ both be named Bob, but maybe one is Bob and the other is Barry. + +```java +class Thing {} + +class Main { + void main() { + String a = "abc"; + String b = "abc"; + String c = "bca"; + + IO.println(a.hashCode()); + // a.equals(b) will return true, so they will have the same hash code + IO.println(b.hashCode()); + // a.equals(c) will return false, so they may or may not have the same hash code + IO.println(c.hashCode()); + + Thing t1 = new Thing(); + Thing t2 = new Thing(); + + // The default .equals() is the same as == + IO.println(t1.hashCode()); + IO.println(t2.hashCode()); + } +} +``` diff --git a/src/objects/header.png b/src/objects/header.png new file mode 100644 index 00000000..18326ca4 Binary files /dev/null and b/src/objects/header.png differ diff --git a/src/objects/instanceof.md b/src/objects/instanceof.md new file mode 100644 index 00000000..0129b196 --- /dev/null +++ b/src/objects/instanceof.md @@ -0,0 +1,35 @@ +# instanceof + +If you have an `Object` you can recover the actual type +of the data stored in it using `instanceof`. + +```java +~void main() { +Object o = "123"; + +if (o instanceof String) { + IO.println("This object is a String!"); +} +~} +``` + +Inside an `if` you give the name of a field or variable +whose type is `Object`. Then you write `instanceof` +followed by the type you want to see if that object +is an instance of. + +You can also give a variable name after the type. +This will let you call methods from the actual type that are otherwise +unavailable when all Java knows is that you have an `Object`. + +```java +~void main() { +Object o = "123"; + +if (o instanceof String s) { + IO.println( + "Can call String methods after recovering the type: " + s.charAt(0) + ); +} +~} +``` diff --git a/src/objects/override.md b/src/objects/override.md new file mode 100644 index 00000000..35734798 --- /dev/null +++ b/src/objects/override.md @@ -0,0 +1,57 @@ +# @Override + +If you intend to override a method you should put +`@Override` above that method. + +```java +class Position { + int x; + int y; + + Position(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + return "Position[x=" + x + ", y=" + y + "]"; + } +} + +void main() { + Object o = new Position(9, 8); + IO.println(o); +} +``` + +This doesn't change anything about how the program works, +but it is a signal to Java that you intended to be overriding a method. +If you get something wrong with the name, visibility, return type, +or argument types of the method then putting `@Override` +means Java will warn you about those sorts of issues. + +```java,panics +class Position { + int x; + int y; + + Position(int x, int y) { + this.x = x; + this.y = y; + } + + // toString on Object doesn't take in an int + // but this would otherwise be allowed + // since its technically a distinct method + @Override + public String toString(int value) { + return "Position[x=" + x + ", y=" + y + "]"; + } +} + +void main() { + Object o = new Position(9, 8); + IO.println(o); +} +``` \ No newline at end of file diff --git a/src/objects/override_equals_and_hashCode.md b/src/objects/override_equals_and_hashCode.md new file mode 100644 index 00000000..3387cac2 --- /dev/null +++ b/src/objects/override_equals_and_hashCode.md @@ -0,0 +1,139 @@ +# Override equals and hashCode + + +If you want to customize the `equals` method of an object the +general pattern for doing so is the following. + +First, declare an equals method that matches the one that comes from `Object`. +Don't forget `@Override`. + +```java,no_run +class Position { + int x; + int y; + + @Override + public boolean equals(Object o) { + + } +} +``` + +This definition of equals accepts any `Object` as an argument. Therefore we first +need to make sure that the class of that `Object` is the same as the class you are +comparing it to. + +```java,no_run +class Position { + int x; + int y; + + @Override + public boolean equals(Object o) { + if (o instanceof Position otherPosition) { + + } + else { + return false; + } + } +} +``` + +Then you compare all the fields to make sure they are equal to each other as well. + +```java,no_run +class Position { + int x; + int y; + + @Override + public boolean equals(Object o) { + if (o instanceof Position otherPosition) { + return this.x == otherPosition.x && this.y == otherPosition.y; + } + else { + return false; + } + } +} +``` + +How you do those comparisons depends on what is in the fields. For `int` and such you can use `==`. For fields +like `String` you need to use `.equals`. + +```java,no_run +class Tree { + String barkDescription; + + @Override + public boolean equals(Object o) { + if (o instanceof Tree otherTree) { + // + return this.barkDescription.equals(otherTree.barkDescription); + } + else { + return false; + } + } +} +``` + +If you anticipate, or don't take actions to prevent, a field potentially being `null` +then instead of `a.equals(b)` use `java.util.Objects.equals`. All it does special is handle +`null` without crashing. + +```java,no_run +import java.util.Objects; + +class Tree { + String barkDescription; + + @Override + public boolean equals(Object o) { + if (o instanceof Tree otherTree) { + return Objects.equals(otherTree.barkDescription); + } + else { + return false; + } + } +} +``` + +Whenever you override `equals` you are supposed to override `hashCode` as well. +This is because - by default - every `Object` is going to give a `hashCode` consistent +with the default `equals` method. If you change how `equals` works, then you could +violate the contract of "if hashCode gives a different value, they definitely aren't equal." + +Some parts of Java will rely on that, so its worth addressing. + +If you define your `equals` method as above - essentially "they are equal if all their fields are equal" - then you can use `java.util.Objects.hash` to define your `hashCode`.[^exotic] + +```java,no_run +import java.util.Objects; + +class Position { + int x; + int y; + + @Override + public boolean equals(Object o) { + if (o instanceof Position otherPosition) { + return this.x == otherPosition.x && this.y == otherPosition.y; + } + else { + return false; + } + } + + @Override + public int hashCode() { + // Just give it all the fields you have. + return Objects.hash(this.x, this.y); + } +} +``` + +[^exotic]: If you defined a more exotic form of `equals` then how to properly make a `hashCode` is an exercise for you, the reader. If you give up then `return 1;` will always be "correct," if not ideal for the code +that uses `hashCode` as a quick "might be equal" check. diff --git a/src/objects/override_toString.md b/src/objects/override_toString.md new file mode 100644 index 00000000..2b0b0300 --- /dev/null +++ b/src/objects/override_toString.md @@ -0,0 +1,52 @@ +# Override toString + +To customize the behavior of `toString` +in your own classes you need to "override" +the `toString` method from `Object`. + +What this means is that you need to define a method +which has the same name, arguments, return type, and visibility +as the one defined in `Object`. + +That is, a `public` method named `toString` which takes no +arguments and returns a `String`. + +```java +class Window { + public String toString() { + return "Window!"; + } +} + +void main() { + Object o = new Window(); + IO.println(o); +} +``` + +This is how you can customize the output of `IO.println`. + +It is common practice for a class holding data to +include the values of its fields in its `toString` representation. +This can be very helpful for debugging. + +```java +class Position { + int x; + int y; + + Position(int x, int y) { + this.x = x; + this.y = y; + } + + public String toString() { + return "Position[x=" + x + ", y=" + y + "]"; + } +} + +void main() { + Object o = new Position(9, 8); + IO.println(o); +} +``` \ No newline at end of file diff --git a/src/objects/recover_types.md b/src/objects/recover_types.md new file mode 100644 index 00000000..1e4a58d6 --- /dev/null +++ b/src/objects/recover_types.md @@ -0,0 +1 @@ +# Recover Types diff --git a/src/objects/subclasses.md b/src/objects/subclasses.md new file mode 100644 index 00000000..2c6d9e64 --- /dev/null +++ b/src/objects/subclasses.md @@ -0,0 +1,4 @@ +# Subclasses + +In the same way that all types are sub-types of `Object`, +all classes are sub-classes of `Object`. \ No newline at end of file diff --git a/src/objects/subtypes.md b/src/objects/subtypes.md new file mode 100644 index 00000000..2db2de3c --- /dev/null +++ b/src/objects/subtypes.md @@ -0,0 +1,21 @@ +# Subtypes + +We consider most everything to be a "subtype" +of `Object`. + +This means that if you have a variable or field that holds an `Object` +you can assign any data you want into it. + +```java +~void main() { +String oak = "oak"; +Object tree = oak; +IO.println(tree); +~} +``` + +If something is a subtype of `Object`, we would call `Object` +its "supertype."[^super] + +[^super]: Super meaning above and Sub meaning below. God how I feel for people who learn +english as a second language. \ No newline at end of file diff --git a/src/objects/toString.md b/src/objects/toString.md new file mode 100644 index 00000000..4d554e89 --- /dev/null +++ b/src/objects/toString.md @@ -0,0 +1,31 @@ +# toString + +All `Object`s have a `toString` method. + +This is intended to be a suitable representation for debugging, +although one may choose to favor different concerns. + +For built-in classes the result of their `toString` method is likely +what you'd expect. + +```java +~class Apple {} +~void main() { +Object o = "123"; + +// If its already a String, toString() doesn't +// have to do much work +IO.println(o.toString()); + +o = 123; +// Integers, Longs, etc. all have a representation +// which looks the same as they do in literal form. +IO.println(o.toString()); + +o = new Apple(); +// And custom classes will, by default, just have the +// class name followed by gibberish +IO.println(o.toString()); +~} +``` + diff --git a/src/objects/variance_and_casting.md b/src/objects/variance_and_casting.md new file mode 100644 index 00000000..9b838ec5 --- /dev/null +++ b/src/objects/variance_and_casting.md @@ -0,0 +1,5 @@ +# Variance and Casting + +While every `String` is an `Object`, not +all `Object`s are `String`s. + diff --git a/src/operating_systems.md b/src/operating_systems.md new file mode 100644 index 00000000..c4a111e0 --- /dev/null +++ b/src/operating_systems.md @@ -0,0 +1,7 @@ +# Operating Systems + +The exact electrical signals you need to send to make a computer "work" +are, obviously, a bit complicated. + +This is where operating systems come in. Operating systems are programs that "operate" +all the parts that make up your computer. \ No newline at end of file diff --git a/src/operating_systems/abstractions.md b/src/operating_systems/abstractions.md new file mode 100644 index 00000000..e0238417 --- /dev/null +++ b/src/operating_systems/abstractions.md @@ -0,0 +1,19 @@ +# Abstractions + +The most important job of an operating system is to "abstract" +over the hardware. + +You shouldn't need to know what brand network card you have in order +to write code that connects to the internet. You shouldn't need to know +what kind of hard drive will be on the machine to store data. + +We call these abstractions because they take things that are concrete, +like a dozen specific models of hard drive and the exact bits to send to do their operations, +and make an "abstract model" of their commonalities.[^deeper] + +This lets programmers write programs that will run on any machine that has the right operating system. +It doesn't help writing programs that will run on any operating system because different operating systems +provide different abstractions over the same hardware. The code to write to a hard drive in Windows is different than it is in Mac OS. + +[^deeper]: There are deeper and shallower versions of this explanation which we will +get to. There will be plenty of time to talk about this concept as it relates to Java and other things. diff --git a/src/operating_systems/defaults.md b/src/operating_systems/defaults.md new file mode 100644 index 00000000..cabb7c81 --- /dev/null +++ b/src/operating_systems/defaults.md @@ -0,0 +1,22 @@ +# Defaults + +A social aspect of operating systems is that they control the "default" +experience people have with computers. + +Installing an operating system is prohibitvely difficult, so computer manufacturers +install one before selling a device. When they choose Windows and Windows comes +with Internet Explorer built in, people use Internet Explorer.[^lawsuit] + +There are a lot of kinda grim things that follow from this, and you should dig deeper, +but I bring it up to mention the 1980s. + +The personal computers available then, like the [Commodore 64](https://en.wikipedia.org/wiki/Commodore_64), +only had text based interfaces. In many practical ways, people were closer to the world of programming.[^movies] + +So think about that whenever you feel like you have a lot left to learn. A lot of what you do and do not know +about computers was dictated for you by the fact that you grew up interacting with them on a touch screen +instead of on a terminal. Things are not as intimidating as they seem. + +[^lawsuit]: https://en.wikipedia.org/wiki/United_States_v._Microsoft_Corp. + +[^movies]: Ever wonder why the kid in [WarGames](https://en.wikipedia.org/wiki/WarGames) was a hacking wizz? The kids in basically every 80s movie? And yet all most of us can pull off is opening Google. diff --git a/src/operating_systems/game_consoles.md b/src/operating_systems/game_consoles.md new file mode 100644 index 00000000..ed62833c --- /dev/null +++ b/src/operating_systems/game_consoles.md @@ -0,0 +1,11 @@ +# Game Consoles + +Game Consoles generally always do their own wacky thing. + +For the same reasons as mobile devices, you will have a lot of trouble +trying to program on a game console. They are special made to run Halo.[^age] + +[^age]: Yeah, Halo sucks now. Halo 3 was art. Obtuse, beautiful, orchestral art. I am old enough to have lived +in the age of dreams and to know of their dimming. + + diff --git a/src/operating_systems/mobile_phones.md b/src/operating_systems/mobile_phones.md new file mode 100644 index 00000000..26428d54 --- /dev/null +++ b/src/operating_systems/mobile_phones.md @@ -0,0 +1,8 @@ +# Mobile Phones + +Android and iOS are the major players in the mobile device world. + +You will find doing the tasks a programmer needs to exceedingly difficult on these. +This is because the interfaces made by these operating systems are designed for use +by the general public on small devices. Working with a keyboard and editing a Java +program aren't tasks that are prioritized. diff --git a/src/operating_systems/personal_computers.md b/src/operating_systems/personal_computers.md new file mode 100644 index 00000000..30534436 --- /dev/null +++ b/src/operating_systems/personal_computers.md @@ -0,0 +1,11 @@ +# Personal Computers + +For "personal computers", which I am defining as the sort of computers that have a keyboard +and a screen, the most common operating system is Windows. The second most common is Mac OS. + +These are both produced by massive corporations in order to sell pre-installed on computers. +As such, they give you a graphical interface that you navigate with a mouse/touchpad and keyboard. +This is what people have found comfortable and what most are used to. + +To write code and do other programmer things you have to install programs but once you know what +you need to install its pretty easy. \ No newline at end of file diff --git a/src/operating_systems/servers.md b/src/operating_systems/servers.md new file mode 100644 index 00000000..d5766dd9 --- /dev/null +++ b/src/operating_systems/servers.md @@ -0,0 +1,12 @@ +# Servers + +For servers, the kind of machines you run in a datacenter +to host websites, the most common operating system is Linux. + +Because so many jobs are writing websites to host on servers, you will eventually +need to learn how to work with Linux. + +That will be a fun adventure. An adventure I implore you not to explore by bricking[^def] +your family's or a school's computer trying to install Linux on it. Only mess with your own stuff. + +[^def]: I.E. making useless \ No newline at end of file diff --git a/src/packages.md b/src/packages.md new file mode 100644 index 00000000..ca826574 --- /dev/null +++ b/src/packages.md @@ -0,0 +1,16 @@ +# Packages + + + +Every class in Java "lives" in a package. + +A package is a way to group individual classes. + +```java,no_run +package dungeon; + +class BugBear { + +} +``` + diff --git a/src/packages/challenges.md b/src/packages/challenges.md new file mode 100644 index 00000000..058a51d3 --- /dev/null +++ b/src/packages/challenges.md @@ -0,0 +1,34 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Take all the code you have written so far and put into one project. + +To do this put everything under one `src` folder just in different packages. +You will need to convert any usages of the anonymous main class into +named classes, but other than that it should be doable. + +If you have already been doing that, just start making use of packages. +They are, first and foremost, an organizational tool. Use them to organize +your code as you see fit. + +## Challenge 2. + +In the packages above have all the growable array implementations you were +asked to make in one package named `collections`. Import these collections +wherever you want to use them with `import collections.GrowableIntArray;` +or similar declarations. + +This will require marking the classes and many of the methods within `public`. + +## Challenge 3. + +Read through [the list of packages that come with Java](https://javadoc.mccue.dev/api/java.base/module-summary.html). You don't need to understand everything you are looking at; just +browse and try to find at least one class that interests you. + +Import that class and try to use it. diff --git a/src/packages/declaration.md b/src/packages/declaration.md new file mode 100644 index 00000000..bf005fc4 --- /dev/null +++ b/src/packages/declaration.md @@ -0,0 +1,31 @@ +# Declaration + +To put a class into a package you need to do two things. + +First, put a "package declaration" at the top of the file. This looks like the word `package` followed by the name of the package and a `;` + +```java,no_run +package dungeon; + +class BugBear { + +} +``` + +Then you need to make sure that the `.java` file is in a folder matching the name of +that package. + +So, for the example above, if your code was previously laid out like this + +``` +src/ + BugBear.java +``` + +It needs to be changed to this. + +``` +src/ + dungeon/ + BugBear.java +``` \ No newline at end of file diff --git a/src/packages/fully_qualified_class_name.md b/src/packages/fully_qualified_class_name.md new file mode 100644 index 00000000..b366a1af --- /dev/null +++ b/src/packages/fully_qualified_class_name.md @@ -0,0 +1,29 @@ +# Fully Qualified Class Name + +In order to use a public class from a different package you can +write out the "fully qualified class name."[^fqcn] + +This is the name of the class prefixed with the package it is in. + +```java,no_run +package village; + +public class Villager {} +``` + +```java,no_run +package dungeon; + +class Dwarf { + // Works because we write out the full name + // and because Villager is public. + village.Villager meet() { + return new village.Villager(); + } +} +``` + +This hints that the "real name" of a class isn't what you write after `class`, but instead both that and the package name glued together. + +[^fqcn]: People also often call this the "FQCN". Its a fun acronym to write, but I +have no clue how to say it out loud. "FaquacΓΌn?" \ No newline at end of file diff --git a/src/packages/header.png b/src/packages/header.png new file mode 100644 index 00000000..57cfaaf3 Binary files /dev/null and b/src/packages/header.png differ diff --git a/src/packages/import.md b/src/packages/import.md new file mode 100644 index 00000000..b717cdfd --- /dev/null +++ b/src/packages/import.md @@ -0,0 +1,28 @@ +# Import + +If you don't want to write out the fully qualified class name everywhere, and few do, +then you can use an `import`. + +To import a class you write `import` followed by the fully qualified class name. +This makes it so that the "simple" class name will be usable. + +```java,no_run +package village; + +public class Villager {} +``` + +```java,no_run +package dungeon; + +// For the rest of the file, you can +// just write "Villager" and Java will know +// what you mean. +import village.Villager; + +class Dwarf { + Villager meet() { + return new Villager(); + } +} +``` \ No newline at end of file diff --git a/src/packages/package_imports.md b/src/packages/package_imports.md new file mode 100644 index 00000000..d7250012 --- /dev/null +++ b/src/packages/package_imports.md @@ -0,0 +1,41 @@ +# Package Imports + +If you want to import all of the classes in a package +all at once you can use a "package import." + +To do this write `import` followed by the name of the +package and `.*`. + +```java,no_run +package village; + +public class Villager {} +``` + +```java,no_run +package village; + +public class Dog {} +``` + +```java,no_run +package dungeon; + +// Imports Village, Dog, and any other classes +// in the "village" package. +import village.*; + +class Dwarf { + Villager meet() { + return new Villager(); + } + + Dog pet() { + return new Dog(); + } +} +``` + +This has the upside of being only one line of code. This also has the +downside of being only one line of code - it is harder to figure out +from what package any particular class might be coming from. \ No newline at end of file diff --git a/src/packages/package_private_constructors.md b/src/packages/package_private_constructors.md new file mode 100644 index 00000000..0112d715 --- /dev/null +++ b/src/packages/package_private_constructors.md @@ -0,0 +1,28 @@ +# Package-Private Constructors + +A constructor without any other modifier is "package-private" in the +same way as methods and fields.[^private] + +```java +package dungeon; + +public class Slime { + final int size; + + // This constructor is public, + // code in other packages can use it. + public Slime() { + this.size = 5; + } + + // This constructor is package-private, + // code in other packages cannot use it. + Slime(int size) { + this.size = size; + } +} +``` + +[^private]: Your spider-sense might be tingling wondering if `private` constructors +are a thing. They are! I'll talk about them more in-depth later, but they can be +surprisingly useful. \ No newline at end of file diff --git a/src/packages/package_private_fields.md b/src/packages/package_private_fields.md new file mode 100644 index 00000000..3b640952 --- /dev/null +++ b/src/packages/package_private_fields.md @@ -0,0 +1,13 @@ +# Package-Private Fields + +Fields that are marked neither `public` nor `private` are "package-private." + +```java +package village; + +public class Well { + // Neither of these can be used outside of the "village" package + static final int NUMBER_OF_DEMONS = 4; + boolean exorcismPerformed; +} +``` \ No newline at end of file diff --git a/src/packages/package_private_methods.md b/src/packages/package_private_methods.md new file mode 100644 index 00000000..54a37253 --- /dev/null +++ b/src/packages/package_private_methods.md @@ -0,0 +1,22 @@ +# Package-Private Methods + +If a method is neither `public` or `private` then it can +be used only by code in the same package. + +We call these methods "package-private" because they are +"private" to code outside the package. + +```java +package village; + +public class Villager { + void isNotVisible() { + IO.println(""" + This method can be called from code in the 'village' + package, but not from other packages. + """); + } +} +``` + +Which again applies to static methods as well. diff --git a/src/packages/public_classes.md b/src/packages/public_classes.md new file mode 100644 index 00000000..59815427 --- /dev/null +++ b/src/packages/public_classes.md @@ -0,0 +1,11 @@ +# Public Classes + +To be able to use a class in one package from a different package, +you must first mark that class as `public`. + +```java,no_run +package village; + +// Now other packages will be able to see it +public class Villager {} +``` \ No newline at end of file diff --git a/src/packages/public_constructors.md b/src/packages/public_constructors.md new file mode 100644 index 00000000..1b4fc108 --- /dev/null +++ b/src/packages/public_constructors.md @@ -0,0 +1,35 @@ +# Public Constructors + +For a constructor you write to be usable across packages[^youguessed] it needs +to be marked `public`. + +```java,no_run +package dungeon; + +public class Skeleton { + public final int bones; + + public Skeleton() { + this.bones = 206; + } +} +``` + +```java,no_run +package village; + +import dungeon.Skeleton; + +class Main { + void main() { + // Now this works + var skeleton = new Skeleton(); + + // And we get the right number of bones! + IO.println(skeleton.bones); + } +} +``` + + +[^youguessed]: You guessed it! \ No newline at end of file diff --git a/src/packages/public_fields.md b/src/packages/public_fields.md new file mode 100644 index 00000000..c616d520 --- /dev/null +++ b/src/packages/public_fields.md @@ -0,0 +1,14 @@ +# Public Fields + +Similarly to methods, for a field to be used from a different package +it must be marked `public`. + +```java +package village; + +public class Well { + // Both of these you can use from a different package + public static final int DEPTH = 10; + public int remainingWater; +} +``` diff --git a/src/packages/public_methods.md b/src/packages/public_methods.md new file mode 100644 index 00000000..86ca2a62 --- /dev/null +++ b/src/packages/public_methods.md @@ -0,0 +1,35 @@ +# Public Methods + +Even though a class might itself be marked `public`, the methods +within it will not be unless they are also `public`. + +```java,no_run +package village; + +// Now other packages will be able to see it +public class Villager { + public void isVisible() { + IO.println("This method is callable from another package."); + } + + void isNotVisible() { + IO.println("This method is not.") + } +} +``` + +This applies also to static methods. + +```java,no_run +package village; + +public class Well { + public static int drawWater() { + IO.println(""" + You need this to be both public and static to + be able to write Well.drawWater() + """); + return 235; + } +} +``` diff --git a/src/packages/reverse_domain_name_notation.md b/src/packages/reverse_domain_name_notation.md new file mode 100644 index 00000000..ddfce7cd --- /dev/null +++ b/src/packages/reverse_domain_name_notation.md @@ -0,0 +1,24 @@ +# Reverse Domain Name Notation + +Part of the point of putting classes into packages +is to avoid conflicts when using code written by other people. + +If you want a class named `RiceCooker` and you want to use a code that someone +on the internet wrote which just so happens to also have a class named `RiceCooker`, +that only works if you both put your classes in different packages. + +Making sure different people cooperate is a hard problem, so the social convention +that emerged was to name packages using a "reverse domain name notation." + +Only one person could own a domain name - like `google.com` - at a time. So all +code coming out of Google[^shared] would start with a package name which is the reverse +of that - `com.google`.[^thisiswhy] + +Nowadays people also tend to accept unique prefixes based on accounts you might have on a site. So you might see things like `com.github.their_username_here` for people who have an account with a service like `Github`. + +It isn't perfect, nothing would be, but it's socially dominant so you should be aware of it. If the code you are writing won't be shared with others you do not need to do this sort of thing yourself. + + +[^shared]: That might be mixed with other code written by different companies or by different people. + +[^thisiswhy]: This is why many tools will have your starter project have classes in the `com.example` package \ No newline at end of file diff --git a/src/packages/subpackages.md b/src/packages/subpackages.md new file mode 100644 index 00000000..e10a0f28 --- /dev/null +++ b/src/packages/subpackages.md @@ -0,0 +1,26 @@ +# Subpackages + +Packages can also have subpackages. + +What this looks like is for any package with a `.` in its name - say `my.fun.project` - +you need a new folder. + +So if you have a class like this + +```java +package my.fun.project; + +public class Apple {} +``` + +It should be in a folder structure like this + +``` +src/ + my/ + fun/ + project/ + Apple.java +``` + +And the fully qualified class name would be `my.fun.project.Apple`. \ No newline at end of file diff --git a/src/packages/the_anonymous_main_class.md b/src/packages/the_anonymous_main_class.md new file mode 100644 index 00000000..93470107 --- /dev/null +++ b/src/packages/the_anonymous_main_class.md @@ -0,0 +1,34 @@ +# The Anonymous Main Class + +You are only allowed to make an anonymous main class +inside the default package. + +```java,no_run +// Allowed +void main() { + IO.println("Hello, world"); +} +``` + +```java,does_not_compile +// Not Allowed +package myprogram; + +void main() { + IO.println("Hello, world"); +} +``` + +This means that for classes in packages you have to wrap them in +an explicitly named class like everything else. + +```java,no_run +// Allowed +package myprogram; + +class Main { + void main() { + IO.println("Hello, world"); + } +} +``` \ No newline at end of file diff --git a/src/packages/the_default_constructor.md b/src/packages/the_default_constructor.md new file mode 100644 index 00000000..c6c6a3ce --- /dev/null +++ b/src/packages/the_default_constructor.md @@ -0,0 +1,53 @@ +# The Default Constructor + +If a class is `public` and has a default constructor - i.e. the constructor you +get when you don't specify one - then the default constructor you get will be +`public`. + +```java,no_run +package dungeon; + +public class Skeleton { + // No constructor specified +} +``` + +```java,no_run +package village; + +import dungeon.Skeleton; + +class Main { + void main() { + // We can say `new Skeleton()` here + var skeleton = new Skeleton(); + } +} +``` + +If you write out a constructor yourself this will not be the case. + +```java,no_run +package dungeon; + +public class Skeleton { + public final int bones; + + Skeleton() { + this.bones = 206; + } +} +``` + +```java,no_run +package village; + +import dungeon.Skeleton; + +class Main { + void main() { + // Now "new Skeleton()" will not work. + var skeleton = new Skeleton(); + } +} +``` \ No newline at end of file diff --git a/src/packages/the_default_package.md b/src/packages/the_default_package.md new file mode 100644 index 00000000..ec5e05c8 --- /dev/null +++ b/src/packages/the_default_package.md @@ -0,0 +1,27 @@ +# The Default Package + +When your classes don't have a package declaration, we say those are in the "default package." + +```java,no_run +// No package declaration means default package +public class Elf { + +} +``` + +Classes in the default package cannot be imported by classes +in named packages, regardless of if those classes are public. + +```java,no_run +package villager; + +public class Villager { + // No way to reference Elf directly, + // even if Elf is public +} +``` + +Because of this restriction[^andmore] you will mostly use the default package +when you are feeling lazy or are making a smaller program. + +[^andmore]: And more to come! \ No newline at end of file diff --git a/src/packages/visibility.md b/src/packages/visibility.md new file mode 100644 index 00000000..8439d44e --- /dev/null +++ b/src/packages/visibility.md @@ -0,0 +1,34 @@ +# Visibility + +By default, a class can only see the other classes +in its package. + +```java,no_run +package dungeon; + +class BugBear { + +} +``` + +```java,no_run +package village; + +class Villager { + +} +``` + +```java,no_run +package dungeon; + +class Dwarf { + BugBear fight() { + // Can "see" BugBear and thus call its constructor, + // access visible fields and methods, etc. + return new BugBear(); + } + + // Cannot see Villager because it is in a different package. +} +``` \ No newline at end of file diff --git a/src/packaging.md b/src/packaging.md new file mode 100644 index 00000000..1bb078e0 --- /dev/null +++ b/src/packaging.md @@ -0,0 +1,15 @@ +# Packaging + + + +Once you have a folder full of class files you technically have enough +to share your code. + +You can take that folder, put it on a flash drive, and give it to your friend. +They can then run your code, presuming they have Java installed as well. + +But for several reasons sharing a folder of "loose" classes is unideal. +What you really want is to package those classes up into a single file. + +This way you never accidentally forget one of the files and its easier to +share. \ No newline at end of file diff --git a/src/packaging/challenges.md b/src/packaging/challenges.md new file mode 100644 index 00000000..bf5a0ed1 --- /dev/null +++ b/src/packaging/challenges.md @@ -0,0 +1,26 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Package up the code from the previous section's challenges into JAR files. + +Run those JAR files using `--module-path ...` and `--module`. + +## Challenge 2. + +Inspect the contents of the JARs you just created with `jar --list ` +and `jar --describe-module `. + +Ensure that the module names you picked match up with the file names for the jars. + + +## Challenge 3. + +Use `--main-class` to specify the main classes for the previous modules. + +You can make sure you did it right by trying to run them directly. \ No newline at end of file diff --git a/src/packaging/header.png b/src/packaging/header.png new file mode 100644 index 00000000..fb4bcbd4 Binary files /dev/null and b/src/packaging/header.png differ diff --git a/src/packaging/jar.md b/src/packaging/jar.md new file mode 100644 index 00000000..9bf3a635 --- /dev/null +++ b/src/packaging/jar.md @@ -0,0 +1,79 @@ +# jar + +The tool you use to package up class files in Java is called `jar`. +The output from this tool is a "JAR file." + +To make a JAR file, first compile your code into a folder. + +If you do this by listing all the files out one-by-one, your output +folder will be structured something like the following. + +```text,no_run +output/ + some_pkg/ + A.class + nested/ + B.class +``` + +If you did it using the `--module-source-path` your class files will be nested +under a folder with the name of the module. + +```text,no_run +output/ + ex.mod/ + module-info.class + some_pkg/ + A.class + nested/ + B.class +``` + +Then use a command like the following: + +```bash,no_run +jar \ + --create \ + --file ex.mod.jar \ + -C output/ex.mod . +``` + +The `--create` part means "we are creating a new jar file" +and `--file` says what file to put the jar in. For the file name +you should generally use the name of the module followed by `.jar`.[^mods] +This is technically optional though. + +The `-C` flag is where it gets interesting. The `jar` tool works somewhat like how your terminal +does when you `cd` into directories. + +The tool starts at the directory you are running it at. + +```text,no_run +project/ <--- jar "is here" + output/ + ex.mod/ + module-info.class + some_pkg/ + A.class + nested/ + B.class +``` + +Then with `-C` you "**C**hange" into a directory. + +```text,no_run +project/ + output/ + ex.mod/ <--- jar is here after "-C output/ex.mod" + module-info.class + some_pkg/ + A.class + nested/ + B.class +``` + +After that you are meant to specify what files you want to add into the jar. Just putting one period (`.`) +means "take everything in this folder." Hence `-C output/ex.mod .` puts all those files into the final JAR. + +[^mods]: And, like I've mentioned before, you should ideally be packaging up code +in modules. \ No newline at end of file diff --git a/src/packaging/jar_files.md b/src/packaging/jar_files.md new file mode 100644 index 00000000..3f9283e4 --- /dev/null +++ b/src/packaging/jar_files.md @@ -0,0 +1,15 @@ +# Jar Files + +JAR files - short for **J**ava **Ar**chive - +are just ZIP files with a few special bits of "metadata." + +ZIP files are a common way of bundling a bunch of files up into one file.[^compression] + +You don't need to know exactly where this metadata goes or what all of it is for yet, +just that at a high level it's all just files in a ZIP. + +[^compression]: This bundling up also generally includes "compression," where +the single file might be smaller than the combined sizes of its components. Most people don't +need to think about this nowadays. Class files are small and hard drives are big. +I say that knowing full well that 145GB of the 500GB hard drive on my work laptop is Baldurs Gate 3. +I have 4GB left. \ No newline at end of file diff --git a/src/packaging/javac.md b/src/packaging/javac.md new file mode 100644 index 00000000..0a44124c --- /dev/null +++ b/src/packaging/javac.md @@ -0,0 +1 @@ +# javac diff --git a/src/packaging/libraries.md b/src/packaging/libraries.md new file mode 100644 index 00000000..39d3c1b7 --- /dev/null +++ b/src/packaging/libraries.md @@ -0,0 +1,46 @@ +# Libraries + +It is often the case that it isn't practical to produce a program +using only the modules that come with Java. + +To cope with this reality you can use code written by others +as "libraries" of functionality.[^procuring] + +When running a Java program from source you can provide the libraries +using `--module-path --add-modules ALL-MODULE-PATH`. +So if you had a library named `tangerine.jar` you could run something like the following +command. + +```bash,no_run +java \ + --module-path tangerine.jar \ + --add-modules ALL-MODULE-PATH \ + src/Main.java +``` + +You do not need the `--add-modules ALL-MODULE-PATH` if your code is itself inside of a named +module. The `requires` in the module take care of telling Java what to include. + +```bash,no_run +java \ + --module-path tangerine.jar \ + ex.mod/src/Main.java +``` + +If you use libraries you need to provide the same flags to `javac` when compiling +your own code to share. + +```bash,no_run +java \ + -g \ + -d output \ + --module-path tangerine.jar \ + --module-source-path "./*/src" \ + --module example.module +``` + +Now that you can package your own code into JARs you can share code with others +for this purpose. + +[^procuring]: Libraries can depend on libraries which depend on other libraries in sprawling nightmare graphs. For now let us assume that the libraries you want to use are relatively self-contained +and reasonable to download manually. Dependency resolution and procurement can be a topic for a later day. diff --git a/src/packaging/main_class.md b/src/packaging/main_class.md new file mode 100644 index 00000000..b29c3222 --- /dev/null +++ b/src/packaging/main_class.md @@ -0,0 +1,23 @@ +# --main-class + +When making a JAR you can specify a "main class" +for that JAR using the `--main-class` flag. + +```bash,no_run +jar \ + --create \ + --file ex.mod.jar \ + --main-class some.pkg.Main \ + -C output/ex.mod . +``` + +This makes it so that you can run the code in a JAR without knowing which class +in particular you intend to run. You only need to specify the module name. + + +```bash,no_run +java \ + --module-path ex.mod.jar \ + --module ex.mod +``` + diff --git a/src/packaging/module_path.md b/src/packaging/module_path.md new file mode 100644 index 00000000..b12b8aa5 --- /dev/null +++ b/src/packaging/module_path.md @@ -0,0 +1,34 @@ +# --module-path + +After packaging code into a JAR file you can put that +JAR onto the `--module-path` in the same way you would a folder +of classes. + +```bash +java \ + --module-path ex.mod.jar \ + --add-modules ALL-MODULE-PATH + some.pkg.Main +``` + +If you have multiple JARs to put on the module path +you can do so by separating them with a `:`.[^windows] + +```bash +java \ + --module-path ex.mod.jar:other.thing.jar \ + --add-modules ALL-MODULE-PATH + some.pkg.Main +``` + +Instead of `--add-modules ALL-MODULE-PATH` followed by a class name you +can use `--module` followed by `/` + +```bash,no_run +java \ + --module-path ex.mod.jar \ + --module ex.mod/some.pkg.Main +``` + +[^windows]: On Windows instead of a `:` you use a `;`. I am assuming when making these examples +that you are using Windows Subsystem for Linux. If you are not, just adjust as needed. \ No newline at end of file diff --git a/src/prelude/ai.md b/src/prelude/ai.md new file mode 100644 index 00000000..33ff48c3 --- /dev/null +++ b/src/prelude/ai.md @@ -0,0 +1,13 @@ +# AI + +Do not use any form of AI when you are learning. + +I could go into exactly why or shadowbox against why you might think its a good idea, +but instead I am just going to ask that you trust me. + +Don't do it. + +If you find it hard to resist the temptation, install something like [this](https://getcoldturkey.com/) +on your machine to block yourself from accessing AI websites. + +When you get stuck reach out to a person for help. \ No newline at end of file diff --git a/src/prelude/java.md b/src/prelude/java.md new file mode 100644 index 00000000..1c3c321f --- /dev/null +++ b/src/prelude/java.md @@ -0,0 +1,30 @@ +# Java + + + +The bulk of this book will be, as the title suggests, +covering the Java programming language. + +To clear up some common misconceptions: + +* No, Java was not made by or for Minecraft. Minecraft came into existence in 2010. Java has been around since 1996. +* No, you do not _have to_ pay Oracle if you use Java. You can download Java from other organizations +with non-litigious track records like [Adoptium](https://adoptium.net/) if you are nervous. +* No, Java is not the same thing as JavaScript. JavaScript is a very different beast. The naming is very confusing yes. The [history is wild](https://javascript.tm/). + + +The drawing at the top is Java's official[^coffee] mascot.[^what] The [drawing below is JavaScript's](https://javascript-mascot.github.io/). + + + + +The differences between these mascots are representative of the differences between the two languages. + + + +[^coffee]: There is another logo people use for Java - a coffee cup with steam coming off of it - +that is a trademarked symbol of Oracle. If you use it there is a high likelyhood of Oracle +juicero-ing your first-born. + +[^what]: What is that mascot? Well it is named "Duke," but beyond that your guess is as good as mine. +I'll be using that ambiguity to its fullest. \ No newline at end of file diff --git a/src/prelude/java_mascot.png b/src/prelude/java_mascot.png new file mode 100644 index 00000000..9047082e Binary files /dev/null and b/src/prelude/java_mascot.png differ diff --git a/src/prelude/javascript_mascot.png b/src/prelude/javascript_mascot.png new file mode 100644 index 00000000..0ce0ee23 Binary files /dev/null and b/src/prelude/javascript_mascot.png differ diff --git a/src/prelude/testimonials.md b/src/prelude/testimonials.md new file mode 100644 index 00000000..1cf47811 --- /dev/null +++ b/src/prelude/testimonials.md @@ -0,0 +1,18 @@ +# Testimonials + + +> This book really helped me understand Java better. +> At the time, I had tons of homework and barely any time to figure things out. +> When I started high school and had to choose extracurricular activities, I discovered FRC β€” the First Robotics Competition. +> +> Luckily, my school has a team, so I tried to join. +> I wanted to do programming, but in order to be accepted I had to go through selection, where they taught us a two-year syllabus in just two and a half weeks. +> +> During that period, I had an insane amount of homework and didn't understand anything… +> until I found the book. +> +> It helped me finally understand what the hell I was writing. +> The best example was when we had to create a singleton. +> Thanks to the book, I understood private constructors and static methods, instead of just writing code without knowing why. +> +> Yon diff --git a/src/projects/ascii_art.md b/src/projects/ascii_art.md new file mode 100644 index 00000000..4bb2f878 --- /dev/null +++ b/src/projects/ascii_art.md @@ -0,0 +1,164 @@ +# ASCII Art Generator + +## Problem Statement + +Humans like to draw stuff and to look at drawings of stuff. + +We know this is in some manner intrinisic to us as a species +because we've found drawings in caves [dating back at least 51,200 years.](https://en.wikipedia.org/wiki/Cave_painting) + +As such it is a normal impulse to use pictures, drawings, iconography, and other forms +of art as a tool for communication. + +In the early days of the internet the amount of data you could send between computers was extremely limited. +This meant that, in practice, most people would communicate using solely text. + +Instead of making it so that people couldn't send images to eachother, that restriction birthed a +new form of art. Using only the characters available to send as text, people would make and send pictures. + +For example, here is a bat from [a website that archives examples of this form of art](https://www.asciiart.eu/animals/bats). + +``` + /'. .'\ + \( \__/ )/ + ___ / (.)(.) \ ___ + _.-"`_ `-.| ____ |.-` _`"-._ + .-'.-'//||`'-.\ V--V /.-'`||\\'-.'-. +`'-'-.// || / .___. \ || \\.-'-'` + `-.||_.._| |_.._||.-' + \ (( )) / + jgs '. .' + `\/` +``` + +[Here are some cubes](https://www.asciiart.eu/art-and-design/geometries). + +``` ++------+. +------+ +------+ +------+ .+------+ +|`. | `. |\ |\ | | /| /| .' | .'| +| `+--+---+ | +----+-+ +------+ +-+----+ | +---+--+' | +| | | | | | | | | | | | | | | | | | ++---+--+. | +-+----+ | +------+ | +----+-+ | .+--+---+ + `. | `.| \| \| | | |/ |/ |.' | .' + `+------+ +------+ +------+ +------+ +------+' +``` + +[And this one is a dog](https://www.asciiart.eu/animals/dogs). + +``` + __ + \ ______/ V`-, + } /~~ + /_)^ --,r' +|b |b +``` + +We call these drawings "ASCII Art" after the "American Standard Code for Information Interchange" - ASCII. +ASCII defined an English-centric set of characters and how to represent them in a computer. Much of this early +art was made solely using that character set, hence the name. + +Even though sending images is now practical to do over the internet, ASCII art is still +a valid form of expression. Either as a deliberate choice or because of using a text only medium +(they still exist. Think of in-game chats.) ASCII art can be useful. + +If you want to see how far this can be taken +check out this [entirely ASCII art rendition of the Star Wars IV: A New Hope](https://www.asciimation.co.nz/index.php) + + + +## Your Goal + +Make a program that asks a user for a "height" and then prints out an ASCII art christmas tree +that is that many characters tall. + +Here is an example tree you can use as a starting point. You can print this when asked for a height of `3`. + +``` + * + *** +***** + | +``` + +Here is another example, but with a height of `5`. + +``` + * + *** + ***** + ******* +********* + | +``` + +And here it is with a height of `1` + +``` +* +| +``` + + + + +## Future Goals + +When you learn enough to do the following, come back to this project and expand it. + +* Draw something with more varied parts, like a snowman. You might find it convenient to not directly print to the screen, but instead "draw" what you want to print into an array first then print the contents of that array. + +``` + --- + | | + ----- + / _ ^ \ + | * * | + | V | + \^___^/ + ------- + / \ + * | | * + * | | * + * | | * +* \-------/ * + --------- + / \ + | | + | | + | | + | | + \---------/ +``` +* Make sure that if the person using your program gives you a negative number, zero, or something that is +not a number you don't just crash. This means you might need to reprompt them for information. +* Make the christmas tree prettier. This will require "finding the pattern" in a more interesting piece of art, like [this example](https://www.asciiart.eu/holiday-and-events/christmas/trees). + +``` + /\ + /\*\ + /\O\*\ + /*/\/\/\ + /\O\/\*\/\ + /\*\/\*\/\/\ +/\O\/\/*/\/O/\ + || + || + || +``` + +* Draw both the snowman and a tree. Let the user select which goes on the left and which goes on the right. + +* Turn this into a command-line program that works similarly to the [cowsay](https://en.wikipedia.org/wiki/Cowsay#:~:text=cowsay%20is%20a%20program%20that,a%20cow%20with%20a%20message.) tool. + +``` + _______ +< Hello > + ------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +``` + +* Expand or refocus into drawing other kinds of things. \ No newline at end of file diff --git a/src/projects/buy_now_pay_later.md b/src/projects/buy_now_pay_later.md new file mode 100644 index 00000000..3a189137 --- /dev/null +++ b/src/projects/buy_now_pay_later.md @@ -0,0 +1,101 @@ +# Buy Now, Pay Later + +## Problem Statement + +People exchange money for goods and/or services. + +Some of these purchases are non-essential. This means that they are, +strictly speaking, optional. Ordering extra guacamole at a restaurant, +buying a brand new pair of shoes, and buying a diamond ring are all +examples of non-essential spending. + +Other purchases are essential. If you are having a heart attack (within +the United States) medical care is a mandatory expense. You either make that +purchase or you die. If one does not have some minimum level of food, water, and shelter +those are also essential expenses. + +It comes down to needs and wants. If you **need** to have something that is an essential purchase. +If you **want** to have something that is a non-essential purchase. + +Many people do not have enough money for their essential expenses. +This can be due to any number of reasons, but in the United States minimum wage has +not kept up with overall inflation since 1968. Add to that rising costs of housing, +transportation, education, etc. and times are tough. + +Some people have much more money than they could ever need. These people, +sometimes pooling funds via corporations, often choose to loan this money +to those that do not have enough. + +The reason they do this is because they can charge fees. If someone can't afford +a particular expense of ~$100 they could get the money for that expense right away in exchange for agreeing +to a 5% monthly interest rate. This would mean that the amount they owe goes up by 5% every month that they +do not pay the loan issuer back. After 5 months they would owe $127.63.[^compound] + +Banks work this way. You deposit money in the bank and, while the bank and the government +guarentee you that you can go at any time and get your money out, they are also turning around +and lending that money to any number of businesses and individuals. + +So long as you assume most people will be able to eventually pay off their debts, loan issuance +is a very profitable business to be in. It is also something of a public good.[^christmas] But if a large number of people are unable to pay off their debts +you can get financial collapses. This can be at the level of an individual loan issuer up to entire countries. + +Because it is bad when this happens governments generally have laws that restrict the "level of risk" +loan issuers are allowed to take on. If they "take on too much risk" - meaning loan enough money +to people who aren't able to pay it back - there will eventually be a collapse. + +The level of scrutiny that applies to normal debts does not seem to, at least in the United States, +apply when you do it via payment plans and call it "Buy Now, Pay Later." This allows for businesses +to make loans they otherwise wouldn't be allowed to to people they might not otherwise be allowed to loan to. + +This certainly helps people who are struggling to get enough to affort their essential expenses. +It can also help people make non-essential expenses which they deem essential. Human's are imperfect +creatures. Many will take on a payment plan with an interest rate for a burrito. + +In all cases the ability to take on debt can either help someone stabilize their financial situation +or it can ultimately make things worse. The higher the interest rate the more likely the second situation +comes to pass. + +In the modern world computers are required to track both the issuance of these debts as +well as compute interest and facilitate the collection process. This is in part because payments +are often made electronically but also in part because the overhead of paying a person +to manually keep track of all this info would eat in to the profits from interest and flat fees. + +"Buy Now, Pay Later" programs also want to be embedded into online storefronts. People are much more likely +to accept higher interest rates or take on the debt at all the closer the offer is made to "the point of sale." This requires not only business deals but a non-trivial amount of code. + +Never make a program for a "Buy Now, Pay Later" company. + +The only reason they would be using that term would be to dodge regulations +set up to protect both parties in the arrangement. + +"Buy Now, Pay Later" is just another word for debt. If you make a program that +enables desperate people go deeper and deeper into debt to enrich yourself +you are a bad person. + +I put this here to tell you that building software is not +a neutral act. You need to critically consider how what you decide to spend +your time on will affect the world around you. + +Facebook is one of the largest websites in the world. It makes a lot of +people a lot of money. The world would be a better place +if it did not exist. + +There are real companies - at the time of writing the biggest one is called [Klarna](https://klarna.com/) - +which are more than happy to offer predatory loans to the desperate and short-sighted. Their existence makes the +world a worse place. They also could not exist without the efforts of people who write software. + +But loans are not *intrinsically* a bad thing. What makes a loan bad comes down to the terms of the +loan and the circumstances under which it is taken out. + +## Your Goal + +Make a program to track the debts owed by + +## Future Goals + +None. + +[^compound]: Compound interest adds up fast. + +[^christmas]: Watch "It's A Wonderful Life" sometimes. Banking is not intrinsically evil. Loaning people +money so they can buy a home to live in can be a great thing. \ No newline at end of file diff --git a/src/projects/calorie_tracker.md b/src/projects/calorie_tracker.md new file mode 100644 index 00000000..7d478983 --- /dev/null +++ b/src/projects/calorie_tracker.md @@ -0,0 +1,58 @@ +# Calorie Tracker + +## Problem Statement + +A calorie is the amount of energy needed to raise the temperature of +1 gram of water by 1 degree celcius. A kilo-calorie, often shortened to kcal, +is one thousand calories. + +Food consumed by humans provides a certain amount of calories. Somewhat confusingly +when people talk about "calories," as might be reported on food labels, they really mean +kilo-calories. + +For most of the history of the human species food was not an abundant resource. As such +when food is abundant our brains are predisposed to consuming as much of it as possible. +This is a behavior likely evolved in order that one best withstand periods of famine.[^biology] + +The problem is that, while many places in the world still experience regular food shortages, +many people have an extreme abundance of food available to them at all times. Not only that, +the food they do have access to is often designed to be as addictive to consume as possible. + +As such many of these people gain extreme amounts of weight. +This, in turn, lead to many health problems. + +Whether someone gains or loses weight over a given period of time comes down to "CiCo" - Calories In, Calories Out. +If someone eats more in calories than they burn they are at a calorie surplus and will gain weight. +If someone eats fewer calories than they burn they are at a calorie deficit and will lose weight. + +There are also people out there in the world with the opposite problem. Not eating enough leads to starvation. +Whether because of trauma, upbringing, or some other circumstance: some people will not naturally eat enough even +when food is available to them. + +## Your Goal + +Your goal is to make a program that helps someone track the number of calories they have consumed +in a given day. + +The intent is to help them be intentional about the number of calories they are consuming. + +We will count it as a success if the program you produce at least helps them track the total. + +Hint: You will need to use `IO.readln` alongside `Integer.parseInt` and/or `Double.parseDouble`. + +## Future Goals + +When you learn enough to do the following, come back to this project and expand it. + +* Make it so that they can also track a "calorie goal" and see how they are doing with respect to that goal. +* Make it so that they can also track the name of the food. +* Make it so that if the computer running the program is turned off they do not lose information. +* Expand the program to also help them record the macro-nutritional value of the food they ate. +* Make it so that they can more easily track foods they eat often. +* Expand the program to also let them track their weight over time. +* Make it so that they can track their progress over multiple days, months, or years. +* Anything else you can think of. + + + +[^biology]: I am not an evolutionary biologist, but this is my understanding. \ No newline at end of file diff --git a/src/projects/data_visualization.md b/src/projects/data_visualization.md new file mode 100644 index 00000000..a1046ffe --- /dev/null +++ b/src/projects/data_visualization.md @@ -0,0 +1,92 @@ +# Data Visualization + +## Problem Statement + +In 1854 there was a major cholera outbreak on Broad Street in London. + +At the time it was thought that the outbreak was caused by "bad air." +This was in line with the [miasma theory of disease](https://en.wikipedia.org/wiki/Miasma_theory) +which held that plauges were literally caused by bad air coming from rotting +organic matter. + +If you've ever seen a drawing of [a plague doctor](https://en.wikipedia.org/wiki/Plague_doctor) +this is part of why they had that long nosed mask. They would fill it with nice smelling herbs +in order to counter the miasma they thought was causing disease. + +This was basically entirely wrong in terms of the mechanics of disease spread. But it wasn't all bad. Staying +away from dead bodies and wearing a mask around the sick aren't the worst ideas in the world. + +The actual cause of this cholera outbreak was not bad air but instead contaminated water. +The person who figured this out was [Dr. John Snow](https://pmc.ncbi.nlm.nih.gov/articles/PMC11416802/). + +He recorded information about where the people who were getting sick lived. Using this he was able to +show that the people who were getting sick were the ones drinking from a particular water pump +in town. He did this by [overlaying the number of people who got sick onto a map of the town](https://cdn.ncbi.nlm.nih.gov/pmc/blobs/917c/11416802/c06be2237467/cureus-0016-00000067602-i02.jpg). + +This visualization was key to determining the actual cause of the outbreak. Unfortunately it took quite +a bit longer for the germ theory of disease to be widely accepted, but Dr. Snow did at least get that +one contaminated water pump removed. + +One moral of the story - and there are a few - is that making a visual representation of +data can be key to the interpretation of that data. If you have a file with a million +numbers in it that is not exactly interpretable. If you took the data in that file, +plotted it, and saw a straight line then that is useful information. + +Computers are uniquely suited to making visualizations. Modern data sets +are often quite large and already stored on a computer. It is easier for a computer +to turn that data into a chart or diagram of some kind than it is +for a human to sit down and labor with a sheet of paper and a ruler. + +## Your Goal + + +Fill up a file named `data.txt` with a bunch of numbers you type randomly on your keyboard. So something like the following + +``` +124124523950159219359858327587324573258723458342756734 +``` + + +Write a program which reads that file and writes a new file containing a series of [bar charts](https://en.wikipedia.org/wiki/Bar_chart) representing how many times a given digit appears in the file. People aren't that good at picking random numbers so you are bound to +see one bar be higher than the others. + + +This will require you to learn how to create an image from code. The good news is that this is easier than +you would expect. There is a file format called "[PPM](https://en.wikipedia.org/wiki/Netpbm)" which represents images like this. + +```no_run +P3 +# "P3" means this is a RGB color image in ASCII +# "3 2" is the width and height of the image in pixels +# "255" is the maximum value for each color +# This, up through the "255" line below are the header. +# Everything after that is the image data: RGB triplets. +# In order: red, green, blue, yellow, white, and black. +3 2 +255 +255 0 0 + 0 255 0 + 0 0 255 +255 255 0 +255 255 255 + 0 0 0 +``` + +So if you write text like this into a file and give it a `.ppm` extension your computer should be +able to show it to you as an image. + +You will still need to figure out how you are going to represent image data in your program, +how to produce a file like this, and how to arrange the pixels. That should all be within your +ability at this point though.[^believe] + +## Future Goals + +* Expand this program to support other kinds of visualizations such as scatterplots, pie charts, and line graphs. +* Visualize some data that comes from a [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) file. +* Make the program use a different method of data visualization based on a command-line flag. +* For scatterplots, allow adding a "line of best fit." Look up "linear regression" for one way to do this. +* Add labels and axes to your charts. This will require figuring out how to render text in your images. +* Try to make a spacial map similar to the one that Dr. Snow made. This is likely pretty involved to do +in a program but it is definitely possible. + +[^believe]: I believe in you \ No newline at end of file diff --git a/src/projects/music_maker.md b/src/projects/music_maker.md new file mode 100644 index 00000000..aaad45b3 --- /dev/null +++ b/src/projects/music_maker.md @@ -0,0 +1,57 @@ +# Music Maker + +## Problem Statement + +Sounds are formed by waves propagating through the air +at various frequencies. When sound enters your ear [it vibrates your eardrum](https://www.nidcd.nih.gov/health/how-do-we-hear) which in turn vibrates other parts of your ear ultimately culminating +in your brain perceiving a sound. The frequencies +of sound determine in what way your eardrum will vibrate and therefore are what determine how you +will percieve any given sound. + +If you want to recreate a sound you need to in some way record what that sound was. +The oldest example we have of someone doing this dates back [to a thousands of years old stone tablet.](https://www.youtube.com/watch?v=KElPnD-dbkk) The idea being that if someone could read that tablet +and knew what the symbols on it meant, they could use an instrument of some kind to reproduce the song. + +While stone tablets were awesome and paper acceptable for the task, writing down what someone else +should play only lets you record music. Writing down words similarly doesn't help record +the sound of someone's voice.[^lincoln] + +The first of recording of someone's voice was done [in 1860 in Paris by Γ‰douard-LΓ©on Scott de Martinville](https://www.firstsounds.org/sounds/scott.php). He would later have his glory stolen by Thomas Edison; a common occurrence for the time. + +Nowadays computers are generally more convenient for sharing music and recordings than other +methods, if sometimes of lower quality than something like a [vinyl record](https://victrola.com/blogs/articles/does-music-really-sound-better-on-vinyl)[^debate]. + +Storing audio on a computer requires translating audio information into a form +that a computer can store. We call this translation step "digitization." +It requires in some manner storing what frequencies of sounds +and in what proportion need to be produced to "play back" a sound. + + +## Your Goal + +Make a program that produces a WAV file that, when played, +will sound like an instrumental version of [Three Blind Mice](https://en.wikipedia.org/wiki/Three_Blind_Mice)[^haloreach]. + +As a hint: `byte` and `short` can be helpful when representing "binary formats" like WAV. +Reading comprehension as well as reading stamina will also be useful for figuring out +how a WAV file works. You will need to learn a lot about audio. + +## Future Goals + +When you learn enough to do the following, come back to this project and expand it. + +* Make the program produce other songs. +* Expand the program to take as input a text file that in some way describes a song and produce a WAV file +in turn. +* Make a "virtual keyboard" where somebody can play notes by typing and whatever they played can +be "exported" as a WAV file. +* Support a file format other than WAV as the output. + +[^lincoln]: Abraham Lincoln had a really high pitched trill voice apparently. It is a real bummer we don't have recordings. + +[^debate]: This is a subject of much debate. + +[^haloreach]: When Halo Reach came out there was a lot of internet fighting about the "reticle bloom" +on a weapon called the DMR. This meant that if you fired it too quickly it would get +less and less accurate. The advice I heard around that time was to pull the trigger to the tune +of "three blind mice" and that would be about the right timing. \ No newline at end of file diff --git a/src/projects/point_of_sale_system.md b/src/projects/point_of_sale_system.md new file mode 100644 index 00000000..553b449d --- /dev/null +++ b/src/projects/point_of_sale_system.md @@ -0,0 +1,81 @@ +# Point of Sale System + +## Problem Statement + +Consider the operations of a grocery store. + +Prospective customers walk in the store, perhaps acquiring a cart or a basket, +and gather the items they want to purchase. They then walk to the front of the +store, purchase those items using money, and leave. + +Some items are priced by quantity. For example, one [avacado](https://en.wikipedia.org/wiki/Avocado) might cost $0.77. +If they purchased 5 avacados they would have to pay $0.77 five times or $3.85 in total. + +Other items are priced by weight. Bananas are [really cheap for some reason](https://en.wikipedia.org/wiki/Banana_republic) +and so 1 pound of bananas costs $0.39. If they purchase 1.5 pounds of bananas they would have to pay 1.5 times $0.39 or $0.585 in total. Currencies like the dollar do not have a "tenth of a penny" so after this math the amount will be rounded +either to $0.58 or $0.59 depending on the policy of the store in question. + +Either way it is uncommon for someone to have exactly $3.85 or $0.59 on their person. So to facilitate purchases +using physical money they must accept larger bills and offer change for the difference. If someone tries to use a $10 bill +to buy $3.85 of avacados the store will provide $6.15 back to the customer in change. + +While a business can operate this way without any sort of computer involved there are several benefits to having one. + +The price a store wants to charge for any given item is bound to change over time. When training a cashier it can make +more sense to teach them short codes for different items and have the computer system figure out the price to use[^plu]. This would generally include telling them to weigh an item or count the quantity of said item. + +In addition to the price, calculating change is easy for computers to do. While it is not hard for a human to do either, +a computer will make fewer mistakes calculating the difference between $45 and $13.53. This is especially true over the course of an 8 hour shift where mental fatigue can start to set in. + +It is also helpful for a business to know how much they sold in a given day. Having computers in the mix makes it more practical to get these "end of day reports." They might use these reports to know that they need to order more onions because those are selling well recently.[^viral] They can also be used to catch cashiers who are stealing from the register +to support their families.[^common] + +We call these systems that are used at the point products are sold Point of Sale Systems. + +## Your Goal + +Make a program that can be used as a point of sale system for a hypothetical produce stand. + +The products the produce stand sells are as follows: + +| Item | Cost | +|------|------ | +| Banana | $0.39 per pound | +| Avacado | $0.66 each | +| Plantains | $0.99 each | +| Watermelon | $6.99 each | +| Onion | $0.62 each | +| Celery |$0.31 per ounce| +| Carrot |$0.06 per ounce| +| Cabbage |$2.72 each| + +Customers may purchase any amount of any items in any combination. + +Make sure to at least: + +* Track the total for an order. +* Prompt the cashier for the right quantities and weights. +* At the end of an order give the cashier the total and have them enter the amount given to them by the customer. +* Tell the cashier how much in change they need to provide. + +## Future Goals + +When you learn enough to do the following, come back to this project and expand it. + +* Grow your program to support double or triple the number of available items. +* Make it so that the cashier has to "log in" to use the system +* Account for a manager whose job it is to record how much money is in the register before and after a day of operations +and note any unaccounted for funds. +* Make the program not lose information if the computer running it turns off then on again. +* Make the system work for "self check out," where the person entering the items is also the customer. Theft in +these cases is monitored via security cameras and punished via the might of the police state. +* Use a camera attached to the computer to scan a bar code which contains the product number. +* Optionally print a receipt for each order to a physical printer.. + + + +[^plu]: Here is a list of "[PLU](https://www.fsproduce.com/wp-content/uploads/2015/05/2011-PLU-Listing1.pdf)" codes. 4011 is banana. + +[^viral]: Due to a viral onion tart recipie or something. + +[^common]: This is common in no small part because cashiers, like many modern professions, are not paid a livable wage. \ No newline at end of file diff --git a/src/projects/prelude.md b/src/projects/prelude.md new file mode 100644 index 00000000..674bb717 --- /dev/null +++ b/src/projects/prelude.md @@ -0,0 +1,22 @@ +# Prelude + +Separate from the challenges at the end of each section I will be putting some projects +sections throughout the book. + +While the purpose of the challenges is to make you practice what you just read, +the purpose of the projects is for you to put that knowledge into action. + +This is for two reasons + +1. If you don't practice for real, it is very difficult to learn things. +2. I want to drive home that software is an interdisciplinary field. + +The first reason means that, while be some early hand-holding, I largely expect +you to put together projects on your own using what you have been shown. +This means you will struggle. The hope is that in that struggle you are forced to learn, +but you can always ask for help. + +By that second point I mean we make software to do things in the real world. +To do this requires understanding who would use the software, why, and for what purpose. +I am going to try my best to have projects that make you interact with the world outside +of software construction. diff --git a/src/projects/tic_tac_toe.md b/src/projects/tic_tac_toe.md new file mode 100644 index 00000000..1ed19f21 --- /dev/null +++ b/src/projects/tic_tac_toe.md @@ -0,0 +1,48 @@ +# Tic-Tac-Toe + + +## Problem Statement + +During the summer between 3rd grade and 4th grade my parents sent me to "baseball camp." + +This was because despite playing baseball for three years at that point +I had only hit the ball twice[^tball]. I was often put in the left outfield position +where I would pick dandelions. Being literally "left out" made me sad +but the whole display made my father proud. + +Instead of applying myself and learning how to play baseball I had a notebook +where I wrote out all the different games of tic-tac-toe determined to "crack the code" +and never lose again. I thought I was being so smart and, in my defense, so did the +other children. A loser, yes - but smart. + +Even today when I go to dinner with my siblings or parents we ask for the kids' +menu and crayons over 50% of the time. We then +play at least one game of tic-tac-toe at the table of a nice Italian restaurant[^not]. + + +## Your Goal + +Make a program that lets you play [tic-tac-toe](https://en.wikipedia.org/wiki/Tic-tac-toe) against the computer. + +Make sure that the player is not allowed to make invalid moves, that the computer +does not make invalid moves, and that the game will not crash on unexpected +input from the player. + +## Future Goals + +When you learn enough to do the following, come back to this project and expand it. + +* Implement a few different options for the AI including "pick a valid move at random". +* Allow for setting up simulated matches between the different AIs +* Expand the game to have the option to play "[ultimate tic-tac-toe](https://en.wikipedia.org/wiki/Ultimate_tic-tac-toe)."[^better] +* Make the program have a graphical interface and let people click to make a move. +* Allow for playing with other players over the internet. + + + + +[^tball]: Tee Ball excluded. + +[^not]: I am not Italian. + +[^better]: Ultimate tic-tac-toe is actually a much better game. \ No newline at end of file diff --git a/src/rant.md b/src/rant.md deleted file mode 100644 index e69de29b..00000000 diff --git a/src/records.md b/src/records.md new file mode 100644 index 00000000..8591c9ed --- /dev/null +++ b/src/records.md @@ -0,0 +1,10 @@ +# Records + + + +If you have a class whose only purpose is to ferry data around, +you can instead use a `record`. + +```java +record Person(String name, int age) {} +``` \ No newline at end of file diff --git a/src/records/challenges.md b/src/records/challenges.md new file mode 100644 index 00000000..04e0ec6c --- /dev/null +++ b/src/records/challenges.md @@ -0,0 +1,105 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Replace the usage of a normal class in the following code with a record. + +```java,editable +import java.util.Arrays; + +class InventoryItem { + String name; + + InventoryItem(String name) { + this.name = name; + } +} + +class Main { + void main() { + var item1 = new InventoryItem("sword"); + var item2 = new InventoryItem("shield"); + var item3 = new InventoryItem("armor"); + + InventoryItem[] items = { + item1, + item2, + item3 + }; + + IO.println(Arrays.toString(items)); + } +} +``` + +## Challenge 2. + +Make an instance of the `Tien` record and print it out. + +```java,editable +enum TienMove { + DODON_RAY, + TRI_BEAM, + EXPLODE +} + +record Chiaotzu( + Move memorableMove +) {} + +record Tien( + Chiaotzu onlyFriend, + Move firstMove, + Move secondMove +) {} + +class Main { + void main() { + Tien tien; + // CODE HERE + IO.println(tien); + } +} +``` + +## Challenge 3. + +What will this program output when run? + +1. `true` then `true` +2. `true` then `false` +3. `false` then `true` +4. `false` then `false` + +Write down your guess and then try running it. + +```java +class Position { + int x; + int y; + + Position(int x, int y) { + this.x = x; + this.y = y; + } +} + +record Location(int x, int y) {} + +class Main { + void main() { + var p1 = new Position(7, 1); + var p2 = new Position(7, 1); + IO.println(p1.equals(p2)); + + var l1 = new Location(8, 3); + var l2 = new Location(8, 3); + IO.println(l1.equals(l2)); + } +} +``` \ No newline at end of file diff --git a/src/records/check_for_equality.md b/src/records/check_for_equality.md new file mode 100644 index 00000000..9d185bbb --- /dev/null +++ b/src/records/check_for_equality.md @@ -0,0 +1,21 @@ +# Check for Equality + +To check if the components of a record match with another, you can use the `equals` +method. + +```java +record Elf(boolean pretentious) {} +``` +```java +~record Elf(boolean pretentious) {} +class Main { + void main() { + var elfOne = new Elf(true); + var elfTwo = new Elf(true); + + IO.println(elfOne.equals(elfTwo)); + } +} +``` + +This is similar to how you check whether `String`s are equal. \ No newline at end of file diff --git a/src/records/component_accessor_visibility.md b/src/records/component_accessor_visibility.md new file mode 100644 index 00000000..4d9f0721 --- /dev/null +++ b/src/records/component_accessor_visibility.md @@ -0,0 +1,22 @@ +# Component Accessor Visibility + +The accessor methods of a record are always public, so all the packages +that can see the class can access its components. + +```java,no_run +package dungeon; + +public record Dragon(double wingspan) {} +``` + +```java,no_run +import dungeon.Dragon; + +void main() { + var dragon = new Dragon(224.5); + IO.println( + // Method is visible. + dragon.wingspan() + ); +} +``` \ No newline at end of file diff --git a/src/records/component_accessors.md b/src/records/component_accessors.md new file mode 100644 index 00000000..5bfa0f71 --- /dev/null +++ b/src/records/component_accessors.md @@ -0,0 +1,24 @@ +# Component Accessors + +For each record component, an accessor +method will be available that has the name of that component. + +You use these to access the values of components. + +```java,no_run +record Dog(String name) {} +``` + +```java +~record Dog(String name) {} +class Main { + void main() { + var dog = new Dog("Hunter"); + + // .name() accessor is available + String name = dog.name(); + + IO.println(name); + } +} +``` diff --git a/src/records/declaration.md b/src/records/declaration.md new file mode 100644 index 00000000..238e8fe0 --- /dev/null +++ b/src/records/declaration.md @@ -0,0 +1,19 @@ +# Declaration + +To declare a record you write the word `record` followed by a +class name, a list of "record components" in parentheses, and a pair of `{}`. + +The list of record components should be reminiscent of function arguments. + +```java +record Person(String name, int age) {} +``` + +Having an empty list of record components is also allowed.[^astowhy] + +```java +record Pants() {} +``` + +[^astowhy]: As to why you might want to give an empty list of record components, +it's nuanced. For now it's just for fun. \ No newline at end of file diff --git a/src/records/header.png b/src/records/header.png new file mode 100644 index 00000000..53089a62 Binary files /dev/null and b/src/records/header.png differ diff --git a/src/records/printing_a_record.md b/src/records/printing_a_record.md new file mode 100644 index 00000000..245e5cb6 --- /dev/null +++ b/src/records/printing_a_record.md @@ -0,0 +1,29 @@ +# Printing a Record + +When printing out a record, the output will include +each of the components of the record. + +```java +record Goblin(String name, int hp) {} +``` +```java +~record Goblin(String name, int hp) {} +class Main { + void main() { + var goblin = new Goblin("Gobbo", 11); + + IO.println(goblin); + } +} +``` +``` +Goblin[name=Gobbo, hp=11] +``` + +This is more intelligable than what you would get by default from a regular class.[^possible] + +``` +Goblin@609db43b +``` + +[^possible]: It is possible to make a regular class print differently, but we'll get to that later. \ No newline at end of file diff --git a/src/records/return_multiple_values.md b/src/records/return_multiple_values.md new file mode 100644 index 00000000..5937f097 --- /dev/null +++ b/src/records/return_multiple_values.md @@ -0,0 +1,28 @@ +# Return Multiple Values + +One of the initial reasons I gave for wanting to use a class +was returning multiple values from a method. + +A record is likely better for that purpose than a regular class. + +```java +record Location(double latitude, double longitude) {} + +Location findTreasureIsland() { + return new Location(51.4075, 0.4636); +} + +void main() { + Location treasureIsland = findTreasureIsland(); + IO.println( + "Treasure island is located at " + + treasureIsland.latitude() + + " " + + treasureIsland.longitude() + + "." + ); +} +``` + +Regular classes are, of course, still useful. Its just for classes which only +hold some data together (and nothing else interesting) getting a constructor and accessors automatically is very convenient. diff --git a/src/records/shorthand.md b/src/records/shorthand.md new file mode 100644 index 00000000..920a55a6 --- /dev/null +++ b/src/records/shorthand.md @@ -0,0 +1,40 @@ +# Shorthand + +One way to think about records is that they are "shorthand"[^nonnative] +for a regular class. + +So the following record + +```java,no_run +public record Cat(boolean spayed, int weight) {} +``` + +is shorthand for a regular class that looks like this. + +```java,no_run +// There are a few parts that I left off here +// so this isn't 100% accurate. +public class Cat { + private final boolean spayed; + private final int weight; + + public Cat(boolean spayed, int weight) { + this.spayed = spayed; + this.weight = weight; + } + + public boolean spayed() { + return this.spayed; + } + + public int weight() { + return this.weight; + } + + // + the magic that makes it print nicer + // + the magic that lets you use .equals + // + a little more that will be relevant later +} +``` + +[^nonnative]: For you non-native English speakers, a shorthand is a shortened form of something. TTYL is "shorthand" for "Talk to you later." \ No newline at end of file diff --git a/src/records/the_canonical_constructor.md b/src/records/the_canonical_constructor.md new file mode 100644 index 00000000..92a7eae4 --- /dev/null +++ b/src/records/the_canonical_constructor.md @@ -0,0 +1,17 @@ +# The Canonical Constructor + +A record is always given a constructor which matches +its list of record components. + +```java,no_run +record Person(String name, int age) {} + +void main() { + // This call to new Person(...) matches up with + // the record declaration. + var person = new Person("Ancient Dragon Man", 2000); +} +``` + +Similar to the "default constructor" given to regular classes, this +is what you get for "free" with the declaration of a record. \ No newline at end of file diff --git a/src/records/why.md b/src/records/why.md new file mode 100644 index 00000000..110e6a0e --- /dev/null +++ b/src/records/why.md @@ -0,0 +1 @@ +# Why diff --git a/src/recursion.md b/src/recursion.md new file mode 100644 index 00000000..c015b920 --- /dev/null +++ b/src/recursion.md @@ -0,0 +1,40 @@ +# Recursion + + + + +In a method you can call another method. + +```java +void doOtherThing() { + IO.println("B"); +} + +void doThing() { + IO.println("A"); +} + +void main() { + doThing(); +} +``` + +This is at the foundation of most code so should, at this point, +be a given. + +What might not be obvious is that you can call the method currently running. + +```java +void countDown(int value) { + IO.println(value); + if (value > 0) { + countDown(value - 1); + } +} + +void main() { + countDown(10); +} +``` + +This is what we call "recursion." \ No newline at end of file diff --git a/src/recursion/accumulators.md b/src/recursion/accumulators.md new file mode 100644 index 00000000..26cae06c --- /dev/null +++ b/src/recursion/accumulators.md @@ -0,0 +1,66 @@ +# Accumulators + +A common thing to do with loops is to "accumulate" +some value each time you go through the loop. + +```java +class Main { + int timesTwo(int x) { + int total = 0; + while (x > 0) { + total += 2; + x--; + } + + return total; + } + + void main() { + IO.println( + timesTwo(4) + ); + } +} +``` + +To accomplish the same task with recursion you need two things. + +The first is an extra "accumulator" argument to your function. +This is what you will update with the new value each time +you recurse. + +The second is an overload of your method which takes all the +same arguments minus that accumulator. This overload should +immediately delegate to the one that takes all the arguments +with some starting value for the accumulator. + +```java +class Main { + int timesTwo(int x, int accumulator) { + if (x > 0) { + return timesTwo(x - 1, accumulator + 2); + } + else { + return accumulator; + } + } + + int timesTwo(int x) { + return timesTwo(x, 0); + } + + void main() { + IO.println( + timesTwo(4) + ); + } +} +``` + +The reason we need to do this is because, unlike with a loop, it is difficult to share variables +across recursive calls. Adding an extra function parameter lets us have a place +to put what would otherwise be a local variable updated by a loop.[^naming] + +[^naming]: You may have noticed that the name changed from `total` to `accumulator`. There +is no particularly good reason for this other than to highlight that the total being built +up is an example of an "accumulator." \ No newline at end of file diff --git a/src/recursion/base_case.md b/src/recursion/base_case.md new file mode 100644 index 00000000..0b2c4bd8 --- /dev/null +++ b/src/recursion/base_case.md @@ -0,0 +1,20 @@ +# Base Case + +The general structure of a recursive method is that +it will sometimes call itself and sometimes not. + +This makes sense if you think about it. If it always called +itself, it would never stop calling itself and run forever. + +We call the case where the function will not call itself +a "base case" + +```java,no_run +if () { + +} +else { + // The base case + return ; +} +``` \ No newline at end of file diff --git a/src/recursion/base_cases.md b/src/recursion/base_cases.md new file mode 100644 index 00000000..5e9d38f9 --- /dev/null +++ b/src/recursion/base_cases.md @@ -0,0 +1 @@ +# Base Cases diff --git a/src/recursion/challenges.md b/src/recursion/challenges.md new file mode 100644 index 00000000..50e23395 --- /dev/null +++ b/src/recursion/challenges.md @@ -0,0 +1,124 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Write code that outputs every number from `1` to an arbitrary number + +You are not allowed to use `while` or `for` loops. + +```java,editable +// CODE HERE + +void main() { + int n = 30; + + // CODE HERE +} +``` + +## Challenge 2. + +Write code that will output each character of `name` on its own line. + +So if name is equal to "Riyo", I would expect the following as output. + +```text +R +i +y +o +``` + +You are not allowed to use `while` or `for` loops. + +```java,editable +// CODE HERE + +void main() { + String name = "Rudo"; + + // CODE HERE +} +``` + +## Challenge 3. + +Write code that will take a number and if it is divisible by two, divides it by two. If it is not, multiplies it by three and adds one. + +Keep doing this until the number equals one. Output it each time. + +If the initial number is 6 you should have this as output. + +```text +6 +3 +10 +5 +16 +8 +4 +2 +1 +``` + +You are not allowed to use `while` or `for` loops. + +```java,editable +// CODE HERE + +void main() { + // Change this value to test your code. + int n = 15; + + // CODE HERE +} +``` + +## Challenge 4. + +Write code that outputs the number of vowels in name. Treat y as a vowel. + +Treat the characters a, A, e, E, i, I, o, O, u, U, y, and Y as vowels. + +You are not allowed to use `while` or `for` loops. + +```java,editable +// CODE HERE + +void main() { + // Change this value to test your code. + String name = "Zanka"; + + // CODE HERE +} +``` + +## Challenge 5. + +Draw a square. + +Make it so that you can make the square bigger or smaller by changing a variable at the start of the program. + +``` +***** +***** +***** +***** +``` + +You are not allowed to use `while` or `for` loops. + +```java,editable +// CODE HERE + +void main() { + int size = 5; + + // CODE HERE +} +``` \ No newline at end of file diff --git a/src/recursion/comparison_to_delegation.md b/src/recursion/comparison_to_delegation.md new file mode 100644 index 00000000..cc454095 --- /dev/null +++ b/src/recursion/comparison_to_delegation.md @@ -0,0 +1,30 @@ +# Comparison to Delegation + +A related technique to recursion is "delegation." + +This is when you have one method call a different arity +version of itself. + +```java +// This method is delegated to +void seasonFood(int shakes) { + for (int i = 0; i < shakes; i++) { + IO.println("1 shake of pepper"); + } +} + +// by this method, which provides a "default" value of 2 +void seasonFood() { + seasonFood(2); +} + +void main() { + seasonFood(); +} +``` + +This is distinct from recursion since, while the method delegated to +might have the same name as the one delegating, different +overloads of methods are considered distinct methods. + +`void seasonFood()` is therefore a different method than `void seasonFood(int)`. \ No newline at end of file diff --git a/src/recursion/comparison_to_loops.md b/src/recursion/comparison_to_loops.md new file mode 100644 index 00000000..14f63ce4 --- /dev/null +++ b/src/recursion/comparison_to_loops.md @@ -0,0 +1,42 @@ +# Comparison to Loops + +Everything that can be accomplished by using loops can be accomplished +using recursion. This means it is always possible to take some +code using loops and translate it mechanically to code using +recursion. + +```java +void seasonFoodRecursive(int times) { + if (times == 0) { + return; + } + else { + IO.println("seasoning"); + seasonFoodRecursive(times - 1); + } +} + +void seasonFoodIterative(int times) { + for (int i = 0; i < times; i++) { + IO.println("seasoning"); + } +} +~ +~void main() { +~ IO.println("Recursive"); +~ seasonFoodRecursive(2); +~ IO.println("Iterative"); +~ seasonFoodRecursive(2); +~} +``` + +Not everything that can be accomplished by using recursion can be accomplished +using loops. At least without introducing some data structure to track what would +otherwise be stored in Java's call stack. The following video goes a little further in depth +as to why. + + + + +This is part of why its important to power through recursion. There are things +that can only be solved with it and there are things that are more easily solved with it. \ No newline at end of file diff --git a/src/recursion/counting_down.md b/src/recursion/counting_down.md new file mode 100644 index 00000000..3468daf9 --- /dev/null +++ b/src/recursion/counting_down.md @@ -0,0 +1,43 @@ +# Counting Down + +One example of a recursive function is one that counts down. + +You start by taking a number as an argument. If that number +is greater than 0 you "recurse" with a +number one lower than you were given. if it isn't you are done. + +```java +void countDown(int x) { + if (x <= 0) { + IO.println("DONE"); + } + else { + IO.println("x: " + x); + countDown(x - 1); + } +} + +void main() { + countDown(5); +} +``` + +In this situation `x <= 0` is our base case since that is the situation where we don't +recurse. Each time we do recurse, we get closer to that base case. `x - 1` is always going +to be closer to x being less or equal to 0. + +All of this is equivalent to a `while` loop that looks like the following. + +```java +void countDown(int x) { + while (x > 0) { + IO.println("x: " + x); + x = x - 1; + } + IO.println("DONE"); +} + +void main() { + countDown(5); +} +``` diff --git a/src/recursion/disclaimer.md b/src/recursion/disclaimer.md new file mode 100644 index 00000000..d7dd3aa8 --- /dev/null +++ b/src/recursion/disclaimer.md @@ -0,0 +1,12 @@ +# Disclaimer + +Recursion will be annoying to learn. + +Sorry. + +It's not because it's particuarly hard or because it's +beyond your ken. It's just that when you learn loops first, recursion +tends to be harder to learn than if you started with it. + +The good news is that once you get it, after however much +mental anguish, you won't forget it. And it will be useful, however occasional. \ No newline at end of file diff --git a/src/recursion/header.png b/src/recursion/header.png new file mode 100644 index 00000000..4f481660 Binary files /dev/null and b/src/recursion/header.png differ diff --git a/src/recursion/recursing_over_arrays.md b/src/recursion/recursing_over_arrays.md new file mode 100644 index 00000000..b31d90d6 --- /dev/null +++ b/src/recursion/recursing_over_arrays.md @@ -0,0 +1,27 @@ +# Recurse Over an Array + +To write a recursive function which acts over each element +of an array, the technique is the same as with you need to do much the same task as with regular loops, +just by having your "current index" be a parameter to the function. + +```java +class Main { + void printEachTimesEight(int[] nums, int i) { + if (i < nums.length) { + IO.println(nums[i] * 8); + printEachTimesEight(nums, i + 1); + } + } + + void printEachTimesEight(int[] nums) { + printEachTimesEight(nums, 0); + } + + void main() { + printEachTimesEight(new int[] { 1, 2, 3 }); + } +} +``` + +This same general technique can be used to loop over other sorts of collections, like `ArrayList`. In that +case you would use `.get()` and `.size()` instead of `[]` and `.length`, but the concept is the same. \ No newline at end of file diff --git a/src/recursion/recursing_over_strings.md b/src/recursion/recursing_over_strings.md new file mode 100644 index 00000000..ad32aa1f --- /dev/null +++ b/src/recursion/recursing_over_strings.md @@ -0,0 +1,26 @@ +# Recurse Over a String + +To write a recursive function which acts over each character +of a `String` you need to do much the same task as with regular loops, +just by having your "current index" be a parameter to the function. + +```java +class Main { + void printEachUpperCase(String s, int i) { + if (i < s.length()) { + IO.println(Character.toUpperCase(s.charAt(i))); + printEachUpperCase(s, i + 1); + } + } + + void printEachUpperCase(String s) { + printEachUpperCase(s, 0); + } + + void main() { + printEachUpperCase("hello"); + } +} +``` + +This overload with the index is an example of a function taking an accumulator. \ No newline at end of file diff --git a/src/reflection.md b/src/reflection.md new file mode 100644 index 00000000..7899770c --- /dev/null +++ b/src/reflection.md @@ -0,0 +1,13 @@ +# Reflection + + + +Reflection is what we call it when a program +uses information about how it - the program - is structured +in order to do things while running. + +We call it reflection because it's as if your code +looks into a mirror and sees its own reflection.[^whenhumans] + +[^whenhumans]: When a human looks into a mirror they might see their own eye color. +If a mirror weren't there it would be difficult to know such things. \ No newline at end of file diff --git a/src/reflection/challenges.md b/src/reflection/challenges.md new file mode 100644 index 00000000..dca0a269 --- /dev/null +++ b/src/reflection/challenges.md @@ -0,0 +1,253 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Write a method `startsWithVowel` that takes an `Object` +as input and returns if the name of the underlying +class for that `Object` starts with a vowel. + +You might need to consult the documentation +for [Class](https://javadoc.mccue.dev/api/index.html). + +```java,editable +class Apple {} +class Banana {} + +class Main { + boolean startsWithVowel(Object o) { + // CODE HERE + } + void main() { + // Integer -> i -> true + IO.println(startsWithVowel(4)); + + + // String -> s -> false + IO.println(startsWithVowel("abc")); + + + // Apple -> a -> true + IO.println(startsWithVowel(new Apple())); + + // Banana -> b -> false + IO.println(startsWithVowel(new Banana())); + } +} +``` + +## Challenge 2. + +Write a method `toMap` that takes an `Object` +as input and returns a `Map` +with all the object's field names as keys and field values +as the value. + +```java,editable +class HankHill { + public double loveForPropane = 100; + public String shock = "bwhaaha"; +} + +class BobbyHill { + public boolean hasCulinaryAcumen = true; +} + +class CottonHill { + public boolean bitter = true; + public boolean angry = true; + public boolean short = true; + public boolean dead = true; +} + +class Main { + Map toMap(Object o) { + // CODE HERE + } + + void main() { + // {loveForPropane=100, shock=bwhaaha} + IO.println(toMap(new HankHill())); + + // {hasCulinaryAcumen=true} + IO.println(toMap(new BobbyHill())); + + // {bitter=true, angry=true, short=true, dead=true} + IO.println(toMap(new CottonHill())); + + } +} +``` + +## Challenge 3. + +Write a method `fromMap` that takes a `Map` +and a `Class` +and returns an `Object` whose fields are all filled in using +the values in the map. + +Assume that the given `Class` has a zero argument constructor you can +call to get an "empty" instance of the class. + +Add your own `toString` methods to the example classes to debug your work. + +```java,editable +class HankHill { + public double loveForPropane; + public String shock; + + // CODE HERE +} + +class BobbyHill { + public boolean hasCulinaryAcumen; + + // CODE HERE +} + +class CottonHill { + public boolean bitter; + public boolean angry; + public boolean short; + public boolean dead; + + // CODE HERE +} + +class Main { + Object fromMap(Map o, Class klass) { + // CODE HERE + } + + void main() { + IO.println(fromMap(Map.of( + "loveForPropane", 100, + "shock", "bwhaaha" + ), HankHill.class)); + + IO.println(fromMap(Map.of( + "hasCulinaryAccumen", true + ), BobbyHill.class)); + + IO.println(fromMap(Map.of( + "bitter", true, + "angry", true, + "short", true, + "dead", true + ), CottonHill.class)); + } +} +``` + +## Challenge 4. + +Call all the methods declared on the `Dale` +class in alphabetical order using reflection.[^speech] + +Make sure not to call methods inherited from `Object` +such as `toString`, `equals`, and `hashCode`. + +```java +class Dale { + public static void u() { + IO.println("and get yourself out of that tunnel and into some strange woman's bed!"); + } + + public static void t() { + IO.println("wash off some of that cologne,"); + } + + public static void d() { + IO.println("and the only way out is through a long dark tunnel."); + } + + public static void f() { + IO.println("carrying a boxcar full of heartbreak."); + } + + public static void a() { + IO.println("I know how dark it is for you right now"); + } + + public static void k() { + IO.println("I'm fat and I'm old and every day I'm just going to wake up fatter and older."); + } + + public static void q() { + IO.println("Will I be out there next month? If I'm alive, you'd better believe it."); + } + + public static void r() { + IO.println("You've got to get up off that tanning bed,"); + } + + + public static void g() { + IO.println("Well let me tell you something:"); + } + + public static void h() { + IO.println("all you can do is let it hit you and then try to find your legs."); + } + + public static void i() { + IO.println("I know - I've taken that hit more times than I can remember"); + } + + + public static void e() { + IO.println("And you're afraid to go in because there is a train coming at ya"); + } + + public static void j() { + IO.println("Look at me Boomhauer."); + } + + + public static void m() { + IO.println("I'm out there digging holes, falling into 'em, climbing out, trying again"); + } + + public static void c() { + IO.println("You're in Hell now Boomhauer"); + } + + public static void n() { + IO.println("And tomorrow I'm going to hang outside at a ladies' prison,"); + } + + public static void o() { + IO.println("and the first thing those lady cons are going to see after twenty years is me."); + } + + public static void l() { + IO.println("Yet somehow I managed to drag this fat old bald bastard into the alley every day."); + } + + public static void p() { + IO.println("Will I get one? Experience says no."); + } + + public static void s() { + IO.println("slip into a tight T-shirt,"); + } + + public static void b() { + IO.println("curled up lying in your own emotional vomit."); + } +} + +class Main { + void main() { + var dale = Dale.class; + + // CODE HERE + } +} +``` + +[^speech]: https://www.youtube.com/watch?v=7nkrzI9GwNk \ No newline at end of file diff --git a/src/reflection/class_objects.md b/src/reflection/class_objects.md new file mode 100644 index 00000000..06cace61 --- /dev/null +++ b/src/reflection/class_objects.md @@ -0,0 +1,45 @@ +# Class Objects + +You can get an object representing a class in one of a few ways. + +The first is to write the name of the class followed by `.class`. +This works if you know exactly the class you want to reflect on. + +While this looks like accessing a field, it is technically its own +special thing. + +```java +class Main { + void main() { + Class stringClass = String.class; + IO.println(stringClass); + } +} +``` + +Another is to call the `getClass` method on an object. This +is a method available on `java.lang.Object` and so will work for +anything. + +This is what you will use if you don't know up front exactly what +type of thing you will be reflecting over. + +```java +class Main { + void main() { + String s = "Hello"; + Class stringClass = s.getClass(); + IO.println(stringClass); + } +} +``` + +These class objects have a generic parameter that can hold the class the +class object represents.[^ifconfused] When you don't know that information +up front you should use a wildcard. + + +[^ifconfused]: If that seems confusing and useless, you are half +right. It certainly is confusing, but it is pretty useful sometimes. +If it's still beyond your ken just always write `Class` and we'll +loop back to it. \ No newline at end of file diff --git a/src/reflection/get_a_constructor.md b/src/reflection/get_a_constructor.md new file mode 100644 index 00000000..c179d46d --- /dev/null +++ b/src/reflection/get_a_constructor.md @@ -0,0 +1,50 @@ +# Get a Constructor + +Following the pattern, `getConstructor` gets a reference to a `Constructor` object. +Just like `getMethod`, this requires specifying argument types. + +Since `getField` might throw a `NoSuchFieldException` and `getMethod` might +throw a `NoSuchMethodException` you might expect a `getConstructor` to throw +a `NoSuchConstructorException`. It does not. If there is no match for the constructor +you are trying to find it will reuse `NoSuchMethodException`. + +`Constructor` objects are also similar to `Class` objects in that they can carry a generic parameter +specifying what kind of object will be made when they are invoked. + +```java +import java.lang.reflect.Constructor; + +class Main { + void main() throws NoSuchMethodException { + Class airplaneFoodClass = AirplaneFood.class; + + // Zero argument constructor. + // Note that we have Constructor. + // If you have a Class it will give you a Constructor + Constructor constructor + = airplaneFoodClass.getConstructor(); + + IO.println(constructor); + + // One argument constructor + constructor = airplaneFoodClass.getConstructor(boolean.class); + + IO.println(constructor); + } +} + +class AirplaneFood { + public final boolean tastesGood; + + public AirplaneFood() { + this.tastesGood = false; + } + + public AirplaneFood(boolean tastesGood) { + if (tastesGood) { + throw new RuntimeException("Lies"); + } + this.tastesGood = false; + } +} +``` \ No newline at end of file diff --git a/src/reflection/get_a_field.md b/src/reflection/get_a_field.md new file mode 100644 index 00000000..54153515 --- /dev/null +++ b/src/reflection/get_a_field.md @@ -0,0 +1,47 @@ +# Get a Field + +You can retrieve a single field by its name using `getField`. If there +is no field with that name it will throw a `NoSuchFieldException`. + +```java +import java.lang.reflect.Field; + +class Main { + void main() throws NoSuchFieldException { + Class drinkClass = Drink.class; + + Field nameField = drinkClass.getField("name"); + IO.println(nameField); + } +} + +class Drink { + public String name; + public boolean caffeinated; +} +``` + +And if you need to access a field that might be non-public you can use `getDeclaredField`. + +```java,panics +import java.lang.reflect.Field; + +class Main { + void main() throws NoSuchFieldException { + Class soupClass = Soup.class; + + Field hasVeggiesField = soupClass.getDeclaredField("hasVeggies"); + IO.println(hasVeggiesField); + + // Will fail. getField won't see hasVeggies + soupClass.getField("hasVeggies"); + } +} + +class Soup { + public String name; + boolean isChicken; + private boolean hasVeggies; +} +``` + diff --git a/src/reflection/get_a_method.md b/src/reflection/get_a_method.md new file mode 100644 index 00000000..d36b788e --- /dev/null +++ b/src/reflection/get_a_method.md @@ -0,0 +1,87 @@ +# Get a Method + +To get a specific method from a class you can use `getMethod`. If there is no method that matches the name and argument types a `NoSuchMethodException` will be thrown. [^nosuchmethod] + +```java +import java.lang.reflect.Method; + +class Main { + void main() throws NoSuchMethodException { + Class teaClass = Tea.class; + + Method sipMethod = teaClass.getMethod("sip"); + IO.println(sipMethod); + } +} + +class Tea { + public void sip() { + } +} +``` + + +Unlike fields which can be identified only by their name, methods which are distinct overloads of each other +are distinguised by the arguments they take in. + +```java +import java.lang.reflect.Method; + +class Main { + void main() throws NoSuchMethodException { + Class teaClass = Tea.class; + + // There is a sip method which takes zero arguments + Method sipMethod = teaClass.getMethod("sip"); + IO.println(sipMethod); + + // which is a different method than + // sip that takes one int + sipMethod = teaClass.getMethod("sip", int.class); + IO.println(sipMethod); + + // which is a different method than + // sip that takes a String and an int + sipMethod = teaClass.getMethod("sip", String.class, int.class); + IO.println(sipMethod); + } +} + +class Tea { + public void sip() { + } + + public void sip(int numberOfSips) { + } + + public void sip(String baristaName, int numberOfSips) { + } +} +``` + +And, as you might imagine, `getDeclaredMethod` will do the same thing with the distinction +of seeing non-public methods. + +```java +class Main { + void main() throws NoSuchMethodException { + Class fruitClass = Fruit.class; + + IO.println(fruitClass.getDeclaredMethod("bite")); + IO.println(fruitClass.getDeclaredMethod("chew")); + IO.println(fruitClass.getDeclaredMethod("swallow")); + } +} + +class Fruit { + public void bite() { + } + + void chew() { + } + + private void swallow() { + } +} +``` + diff --git a/src/reflection/get_all_constructors.md b/src/reflection/get_all_constructors.md new file mode 100644 index 00000000..27926720 --- /dev/null +++ b/src/reflection/get_all_constructors.md @@ -0,0 +1,41 @@ +# Get all Constructors + +If you want a list of all constructors, `.getConstructors` will give you that. + +What is worth noting is that unlike `.getConstructor` the array returned from this +will not have the generic filled in.[^genericarrays] + +```java +import java.lang.reflect.Constructor; + +class Main { + void main() { + Class airplaneFoodClass = AirplaneFood.class; + + Constructor[] constructors + = airplaneFoodClass.getConstructors(); + + for (Constructor constructor : constructors) { + IO.println(constructor); + } + } +} + +class AirplaneFood { + public final boolean tastesGood; + + public AirplaneFood() { + this.tastesGood = false; + } + + public AirplaneFood(boolean tastesGood) { + if (tastesGood) { + throw new RuntimeException("Lies"); + } + this.tastesGood = false; + } +} +``` + +[^genericarrays]: Arrays of generic objects are, in general, a hairy topic. Java can't really ensure that you use them right, +so there a lot of restrictions on making them. You can't make an `ArrayList[]`, for example. \ No newline at end of file diff --git a/src/reflection/get_all_fields.md b/src/reflection/get_all_fields.md new file mode 100644 index 00000000..d586ebb2 --- /dev/null +++ b/src/reflection/get_all_fields.md @@ -0,0 +1,59 @@ +# Get all Fields + +If you have a class object, you can get all the public +fields of that class using `getFields`. This gives you +an array of `Field` objects. + +```java +import java.lang.reflect.Field; + +class Main { + void main() { + Class drinkClass = Drink.class; + + Field[] fields = drinkClass.getFields(); + for (var field : fields) { + IO.println(field.getName()); + } + } +} + +class Drink { + public String name; + public boolean caffeinated; +} +``` + +Its worth knowing that `getFields` will not list non-public fields. To +get a list of all fields, including private and package-private ones, +you need to use `getDeclaredFields`. + +```java +import java.lang.reflect.Field; + +class Main { + void main() { + Class soupClass = Soup.class; + + IO.println("Using getFields"); + Field[] publicFields = soupClass.getFields(); + for (var field : publicFields) { + IO.println(field.getName()); + } + + IO.println("-------------"); + + IO.println("Using getDeclaredFields"); + Field[] allFields = soupClass.getDeclaredFields(); + for (var field : allFields) { + IO.println(field.getName()); + } + } +} + +class Soup { + public String name; + boolean isChicken; + private boolean hasVeggies; +} +``` \ No newline at end of file diff --git a/src/reflection/get_all_methods.md b/src/reflection/get_all_methods.md new file mode 100644 index 00000000..4845298e --- /dev/null +++ b/src/reflection/get_all_methods.md @@ -0,0 +1,71 @@ +# Get all Methods + + +If you have a class object, you can get all the public +methods of that class using `getMethods`. This gives you +an array of `Method` objects. + +Note that this will also include available methods that come from `java.lang.Object`. +In addition to `toString`, `equals`, and `hashCode` you will see a few you don't recognize. +All in due time. + +```java +import java.lang.reflect.Method; + +class Main { + void main() { + Class teaClass = Tea.class; + + Method[] methods = teaClass.getMethods(); + for (var method : methods) { + IO.println(method); + } + } +} + +class Tea { + public void sip() { + } + + public void gulp() { + } +} +``` + +Just like there is `getDeclaredFields` for seeing non-public fields, `getDeclaredMethods` will +give you all methods, regardless of their visibility. + +```java +import java.lang.reflect.Method; + +class Main { + void main() { + Class fruitClass = Fruit.class; + + IO.println("Using getMethods"); + Method[] publicMethods = fruitClass.getMethods(); + for (var method : publicMethods) { + IO.println(method); + } + + IO.println("-------------"); + + IO.println("Using getDeclaredMethods"); + Method[] allMethods = fruitClass.getDeclaredMethods(); + for (var method : allMethods) { + IO.println(method); + } + } +} + +class Fruit { + public void bite() { + } + + void chew() { + } + + private void swallow() { + } +} +``` \ No newline at end of file diff --git a/src/reflection/get_methods.md b/src/reflection/get_methods.md new file mode 100644 index 00000000..07222f4e --- /dev/null +++ b/src/reflection/get_methods.md @@ -0,0 +1 @@ +# Get Methods diff --git a/src/reflection/header.png b/src/reflection/header.png new file mode 100644 index 00000000..f8644f92 Binary files /dev/null and b/src/reflection/header.png differ diff --git a/src/reflection/invoke_a_constructor.md b/src/reflection/invoke_a_constructor.md new file mode 100644 index 00000000..241bc049 --- /dev/null +++ b/src/reflection/invoke_a_constructor.md @@ -0,0 +1,48 @@ +# Invoke a Constructor + +Just as you can get and set a field using the `.get` and `.set` methods on a `Field` and just as you can invoke +a method by using the `.invoke` method on a `Method`, you may also +invoke constructors with `.newInstance` on a `Constructor` object. + +```java +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +class Main { + void main() throws + InvocationTargetException, + InstantiationException, + IllegalAccessException, + NoSuchMethodException { + Class airplaneFoodClass = AirplaneFood.class; + + Constructor constructor + = airplaneFoodClass.getConstructor(); + + AirplaneFood airplaneFood = constructor.newInstance(); + + IO.println(airplaneFood.tastesGood); + + constructor = airplaneFoodClass.getConstructor(boolean.class); + + airplaneFood = constructor.newInstance(false); + + IO.println(airplaneFood.tastesGood); + } +} + +class AirplaneFood { + public final boolean tastesGood; + + public AirplaneFood() { + this.tastesGood = false; + } + + public AirplaneFood(boolean tastesGood) { + if (tastesGood) { + throw new RuntimeException("Lies"); + } + this.tastesGood = false; + } +} +``` diff --git a/src/reflection/invoke_a_method.md b/src/reflection/invoke_a_method.md new file mode 100644 index 00000000..975e1330 --- /dev/null +++ b/src/reflection/invoke_a_method.md @@ -0,0 +1,70 @@ +# Invoke a Method + +Just as you can get and set a field using the `.get` and `.set` methods on a `Field`, you can invoke +a method by using the `.invoke` method on a `Method`. + + +For instance methods there is a first "hidden" argument that is the instance you would be invoking the method on. +This needs to be the first argument to `.invoke`. + +```java +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +class Main { + void main() throws IllegalAccessException, InvocationTargetException { + Class teaClass = Tea.class; + + // sip taking zero arguments + Method sipMethod; + try { + sipMethod = teaClass.getMethod("sip", int.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + + var tea = new Tea(); + + sipMethod.invoke(tea, 5); + } +} + +class Tea { + public void sip(int numberOfSips) { + IO.println("You made " + numberOfSips + " sips"); + } +} +``` + +For static methods you do not need an instance of the class to invoke them. +Instead, the first argument is ignored. You can pass `null`[^strange]. + +```java +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +class Main { + void main() throws IllegalAccessException, InvocationTargetException { + Class appleClass = Apple.class; + + // sip taking zero arguments + Method biteMethod; + try { + biteMethod = appleClass.getMethod("bite", int.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + + biteMethod.invoke(null, 5); + biteMethod.invoke(null, 1); + } +} + +class Apple { + public static void bite(int times) { + IO.println("You took " + times + " bite" + (times < 1 ? "." : "s.")); + } +} +``` + +[^strange]: I find this strange. It hurts my brain. \ No newline at end of file diff --git a/src/reflection/read_from_a_field.md b/src/reflection/read_from_a_field.md new file mode 100644 index 00000000..ed171383 --- /dev/null +++ b/src/reflection/read_from_a_field.md @@ -0,0 +1,40 @@ +# Read from a Field + +Once you have a `Field` object you can use its `get` method to read the value of that field +from an object. This will throw an `IllegalAccessException` if you try to read a field you +are not allowed to.[^permission] + +```java +import java.lang.reflect.Field; + +class Main { + void main() throws IllegalAccessException { + Class drinkClass = Drink.class; + + Field nameField; + try { + nameField = drinkClass.getField("name"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); // Should have this field + } + + var soda = new Drink("Soda", true); + var water = new Drink("Water", false); + + IO.println(nameField.get(soda)); + IO.println(nameField.get(water)); + } +} + +class Drink { + public String name; + public boolean caffeinated; + + Drink(String name, boolean caffeinated) { + this.name = name; + this.caffeinated = caffeinated; + } +} +``` + +[^permission]: The rules for this go a bit beyond "you cannot read private fields." \ No newline at end of file diff --git a/src/reflection/write_to_a_field.md b/src/reflection/write_to_a_field.md new file mode 100644 index 00000000..5fe056fd --- /dev/null +++ b/src/reflection/write_to_a_field.md @@ -0,0 +1,114 @@ +# Write to a Field + +Similarly, you can write to a field using the `.set` method on a `Field` object. +This will also throw `IllegalAccessException` if its not something you are allowed to do. + +```java,panics +import java.lang.reflect.Field; + +class Main { + void main() throws IllegalAccessException { + Class drinkClass = Drink.class; + + Field caffeinatedField; + try { + caffeinatedField = drinkClass.getField("caffeinated"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + + var water = new Drink("Water", false); + + // You can put drugs in anything you set your mind to, kids + caffeinatedField.set(water, true); + + IO.println(caffeinatedField.get(water)); + } +} + +class Drink { + public String name; + public boolean caffeinated; + + Drink(String name, boolean caffeinated) { + this.name = name; + this.caffeinated = caffeinated; + } +} +``` + +If you try to set a field to the wrong type of value - like setting a `boolean` field to have a `String` value - +you will get an `IllegalArgumentException`. Unlike `NoSuchFieldException` and `IllegalAccessException` this is an +unchecked exception, so you do not need to explicitly account for it. + + +```java,panics +import java.lang.reflect.Field; + +class Main { + void main() throws IllegalAccessException { + Class drinkClass = Drink.class; + + Field caffeinatedField; + try { + caffeinatedField = drinkClass.getField("caffeinated"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + + var soda = new Drink("Soda", true); + + caffeinatedField.set(soda, "yes, very much so"); + + IO.println(caffeinatedField.get(soda)); + } +} + +class Drink { + public String name; + public boolean caffeinated; + + Drink(String name, boolean caffeinated) { + this.name = name; + this.caffeinated = caffeinated; + } +} +``` + +The same will happen if you try to set a `final` field.[^finalfields] + +```java,panics +import java.lang.reflect.Field; + +class Main { + void main() throws IllegalAccessException { + Class drinkClass = Drink.class; + + Field nameField; + try { + nameField = drinkClass.getField("name"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); // Should have this field + } + + var water = new Drink("Water", false); + + // You can put drugs in anything you set your mind to, kids + nameField.set(water, true); + + IO.println(nameField.get(water)); + } +} + +class Drink { + public String name; + public final boolean caffeinated; + + Drink(String name, boolean caffeinated) { + this.name = name; + this.caffeinated = caffeinated; + } +} +``` + +[^finalfields]: There are especially evil ways to actually change `final` fields using reflection. If you want to know how to do that I'm not telling you. \ No newline at end of file diff --git a/src/regular_expressions.md b/src/regular_expressions.md new file mode 100644 index 00000000..920bbd2a --- /dev/null +++ b/src/regular_expressions.md @@ -0,0 +1,42 @@ +# Regular Expressions 🚧 + + + +Phone numbers in the US mostly look like `123-456-7890`. Three numbers, +a dash, three numbers, a dash, then four numbers.[^simple] + +Writing the code to check if a `String` matches this pattern is tricky. + +```java,no_run +boolean matchesPhoneNumberPattern(String s) { + return s.length() == 12 + && Character.isDigit(s.charAt(0)) + && Character.isDigit(s.charAt(1)) + && Character.isDigit(s.charAt(2)) + && s.charAt(3) == '-' + && Character.isDigit(s.charAt(4)) + && Character.isDigit(s.charAt(5)) + && Character.isDigit(s.charAt(6)) + && s.charAt(7) == '-' + && Character.isDigit(s.charAt(8)) + && Character.isDigit(s.charAt(9)) + && Character.isDigit(s.charAt(10)) + && Character.isDigit(s.charAt(11)) +} +``` + +For situations like this a useful tool is something called "regular expressions" +and the `Pattern` class. + +```java,no_run +boolean matchesPhoneNumberPattern(String s) { + return Pattern.compile("(\\d\\d\\d)-(\\d\\d\\d)-(\\d\\d\\d\\d)") + .asMatchPredicate() + .test(s); +} +``` + + +[^simple]: A common, but understandable, mistake is assuming that formats +like phone numbers are actually this simple. Phone numbers and emails are always a little +bit of a nightmare to validate. \ No newline at end of file diff --git "a/src/regular_expressions/Screenshot 2025-08-25 at 3.28.13\342\200\257PM.png" "b/src/regular_expressions/Screenshot 2025-08-25 at 3.28.13\342\200\257PM.png" new file mode 100644 index 00000000..604e6b8f Binary files /dev/null and "b/src/regular_expressions/Screenshot 2025-08-25 at 3.28.13\342\200\257PM.png" differ diff --git a/src/regular_expressions/character_classes.md b/src/regular_expressions/character_classes.md new file mode 100644 index 00000000..52783db6 --- /dev/null +++ b/src/regular_expressions/character_classes.md @@ -0,0 +1 @@ +# Character Classes diff --git a/src/regular_expressions/exact_matches.md b/src/regular_expressions/exact_matches.md new file mode 100644 index 00000000..ad090f05 --- /dev/null +++ b/src/regular_expressions/exact_matches.md @@ -0,0 +1 @@ +# Exact Matches diff --git a/src/regular_expressions/header.png b/src/regular_expressions/header.png new file mode 100644 index 00000000..4d2458d0 Binary files /dev/null and b/src/regular_expressions/header.png differ diff --git a/src/regular_expressions/pattern.md b/src/regular_expressions/pattern.md new file mode 100644 index 00000000..662d3a5f --- /dev/null +++ b/src/regular_expressions/pattern.md @@ -0,0 +1 @@ +# Pattern diff --git a/src/regular_expressions/theoretical_basis.md b/src/regular_expressions/theoretical_basis.md new file mode 100644 index 00000000..61ecbc43 --- /dev/null +++ b/src/regular_expressions/theoretical_basis.md @@ -0,0 +1,17 @@ +# Theoretical Basis + +There are basically two levels you can engage the concept of regular expressions +at. + +The first is as a practical tool for matching patterns in text. You will learn +how to construct special strings that act as a shorthand for code that can +recognize if text meets a certain shape. + +The second is around their place in the theory of computation. A regular expression +is one step above a "deterministic finite automata" and one step below a "turing machine." + +I tend to find that diving into the theoretical makes the practical part a lot less intimidating. +It will be a giant tangent, but i suggest [diving in to a theory of computation course](https://www.youtube.com/watch?v=rJhfQp1jgGI&list=PLRgsEjJNLnh7yx0AAQMrdJssDySazeJzZ)[^random]. When you come out +of it regular expressions will be much easier to understand. + +[^random]: I just searched online and this one popped up. It looks fine, but maybe its not or maybe there is something better. \ No newline at end of file diff --git a/src/retu b/src/retu deleted file mode 100644 index abd5d474..00000000 --- a/src/retu +++ /dev/null @@ -1 +0,0 @@ -# void diff --git a/src/return_values.md b/src/return_values.md index 127b6f88..1a636d37 100644 --- a/src/return_values.md +++ b/src/return_values.md @@ -1,5 +1,8 @@ # Return + + + If the only thing you could do with methods was to call them, they would have limited uses. diff --git a/src/return_values/challenges.md b/src/return_values/challenges.md new file mode 100644 index 00000000..314585dd --- /dev/null +++ b/src/return_values/challenges.md @@ -0,0 +1,85 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Make a method that takes a `String` as an argument and returns an `int` +as a result. + +How the value for that int is determined is up to you. + +```java,editable +// CODE HERE + +void main() { + int x = process("abc"); + IO.println("Got " + x); +} +``` + +## Challenge 2. + +Define three methods such that the given main method will run. + +```java,editable +// CODE HERE + +void main() { + f(g(h(4), "b"), "e", "s"); +} +``` + +## Challenge 3. + +Make the following `multiply` method work for negative numbers. +Do this without simply multiplying using the `*` operator. + +```java,editable +int multiply(int x, int y) { + int total = 0; + for (int i = 0; i < y; i++) { + total += x; + } + return total; +} + +void main() { + IO.println(multiply(3, 5)); + + // IO.println(multiply(-5, 5)); + // IO.println(multiply(-5, -2)); + // IO.println(multiply(9, -2)); +} +``` + +## Challenge 4. + +Define a method, `subtractInt`, which makes the following +code run and produce the "correct" result. + +You will need to perform a narrowing conversion. + +```java,editable +// CODE HERE + +double add(double x, double y) { + return x + y; +} + +double multiply(double x, double y) { + return x * y; +} + +void main() { + int x = 5; + int y = 8; + int z = subtractInt(add(4, 5), multiply(4, 2)); + + IO.println(z); +} +``` + diff --git a/src/return_values/header.png b/src/return_values/header.png new file mode 100644 index 00000000..a09743f9 Binary files /dev/null and b/src/return_values/header.png differ diff --git a/src/return_values/return_in_void_methods.md b/src/return_values/return_in_void_methods.md index 31d53309..aecce210 100644 --- a/src/return_values/return_in_void_methods.md +++ b/src/return_values/return_in_void_methods.md @@ -11,8 +11,8 @@ void doStuff() { return; } - System.out.println(i); + IO.println(i); } } -``` - +~void main() {doStuff();} +``` \ No newline at end of file diff --git a/src/return_values/return_values.md b/src/return_values/return_values.md index e7477db3..8a5b4b61 100644 --- a/src/return_values/return_values.md +++ b/src/return_values/return_values.md @@ -9,7 +9,7 @@ int returnsEight() { void main() { int value = returnsEight(); - System.out.println(value); + IO.println(value); } ``` @@ -21,6 +21,6 @@ String returnsName() { } void main() { - System.out.println(returnsName() + " is my name"); + IO.println(returnsName() + " is my name"); } ``` diff --git a/src/return_values/unreachable_statements.md b/src/return_values/unreachable_statements.md index b2f014b9..f24f7c19 100644 --- a/src/return_values/unreachable_statements.md +++ b/src/return_values/unreachable_statements.md @@ -5,10 +5,10 @@ If Java can figure out that some line of code is unreachable because it will alw ```java void doThing() { - System.out.println("A"); + IO.println("A"); return; // unreachable statement - System.out.println("B"); + IO.println("B"); } void main() { @@ -20,11 +20,11 @@ Java is easy to trick though.[^trick] ```java void doThing() { - System.out.println("A"); + IO.println("A"); if (true) { return; } - System.out.println("B"); + IO.println("B"); } void main() { diff --git a/src/return_values/void.md b/src/return_values/void.md index 3080a069..d38e716a 100644 --- a/src/return_values/void.md +++ b/src/return_values/void.md @@ -15,7 +15,9 @@ int views() { // Doesn't return any value. void talkAboutVideo() { - System.out.println(title() + " only has " + views() + " views."); + // Printing something to the screen is different + // than returning a value. + IO.println(title() + " only has " + views() + " views."); } // This is what the void in "void main()" means diff --git a/src/standard_input.md b/src/standard_input.md new file mode 100644 index 00000000..e528fd38 --- /dev/null +++ b/src/standard_input.md @@ -0,0 +1,9 @@ +# Standard Input + + + + +Programs are pretty boring if they just make a machine warm and do some math. + +There are a lot of ways to make a program interactive, but the easiest is to read +from what is called "standard input." diff --git a/src/standard_input/booleans.md b/src/standard_input/booleans.md new file mode 100644 index 00000000..3d7bb6cf --- /dev/null +++ b/src/standard_input/booleans.md @@ -0,0 +1 @@ +# Booleans diff --git a/src/standard_input/challenges.md b/src/standard_input/challenges.md new file mode 100644 index 00000000..ceb85eaf --- /dev/null +++ b/src/standard_input/challenges.md @@ -0,0 +1,70 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1 + +Write a program that asks a person for their name and then says +"Hello \" back to them. + +```java,no_run +void main() { + // 1. Call IO.readln to get their name + // 2. Call IO.print/IO.println to say hello to them +} +``` + +## Challenge 2 + +Write a program that asks a person their age and tells them +what age they will be this time next year. + +```java,no_run +void main() { + // 1. Call IO.readln to get their age + // 2. Interpret their age as an int + // 3. Add one to that age + // 4. Call IO.print/IO.println to say what age they will be next year +} +``` + +## Challenge 3 + +Write a program that asks a person for two floating point numbers and tells them +what the sum of those two numbers is + +```java,no_run +void main() { + // 1. Call IO.readln to get the first number + // 2. Interpret that first number as a double + // 3. Call IO.readln to get the second number + // 4. Interpret that second number as a double + // 5. Add the two numbers together + // 6. Call IO.print/IO.println to say what the sum is +} +``` + +## Challenge 4 + +["Mad Libs"](https://en.wikipedia.org/wiki/Mad_Libs) are a word game where you ask people for nouns, verbs, adjectives, etc. absent any context +and then fill them in to a template. + +For example + +``` +I saw a today and . +Unfortunately the stopped me at the . +``` + +Can become + +``` +I saw a dog today and flew. +Unfortunately the clown stopped me at the elephant. +``` + +Make a program that asks a user for some nouns, verbs, etc. and prints +a Mad Lib using those words. \ No newline at end of file diff --git a/src/standard_input/floating_point_numbers.md b/src/standard_input/floating_point_numbers.md new file mode 100644 index 00000000..7780a990 --- /dev/null +++ b/src/standard_input/floating_point_numbers.md @@ -0,0 +1,15 @@ +# Floating Point Numbers + +If you expect someone to type a floating point value you can turn the `String` +you get from `IO.readln` into a `double` using `Double.parseDouble`. + +```java,no_run +void main() { + String gpaString = IO.readln("What is your GPA? "); + double gpa = Double.parseDouble(gpaString); + IO.println("You're GPA is " + gpa); +} +``` + +So long as they type something which can be interpreted as a `double` (like `123` or `14.5`) +you will get a value for the `double` variable. Otherwise the program will crash. \ No newline at end of file diff --git a/src/standard_input/header.png b/src/standard_input/header.png new file mode 100644 index 00000000..50b7a879 Binary files /dev/null and b/src/standard_input/header.png differ diff --git a/src/standard_input/integers.md b/src/standard_input/integers.md new file mode 100644 index 00000000..4638268a --- /dev/null +++ b/src/standard_input/integers.md @@ -0,0 +1,16 @@ +# Integers + +If you expect someone to type an integer value you can turn the `String` +you get from `IO.readln` into an `int` using `Integer.parseInt`. + +```java,no_run +void main() { + String ageString = IO.readln("How old are you? "); + int age = Integer.parseInt(ageString); + IO.println("You are " + age + " years old!"); +} +``` + +So long as they type something which can be interpreted as an `int` (like `123`) +you will get a value for the `int` variable. Otherwise +the program will crash. \ No newline at end of file diff --git a/src/standard_input/interpreting_input.md b/src/standard_input/interpreting_input.md new file mode 100644 index 00000000..edeef944 --- /dev/null +++ b/src/standard_input/interpreting_input.md @@ -0,0 +1,20 @@ +# Interpreting Input + +When you call `IO.readln` the human on the other side can type whatever they want. + +This means that, depending on the question you asked, you might need to interpret +what they typed as something other than a `String`. + +In its most basic form this will look like seeing if one `String` equals another `String`. + +```java,no_run +void main() { + String color = IO.readln("What is your favorite color? "); + if (color.equals("green")) { + IO.println("Me too!"); + } + else { + IO.println("neat.") + } +} +``` \ No newline at end of file diff --git a/src/standard_input/other_types.md b/src/standard_input/other_types.md new file mode 100644 index 00000000..877d553a --- /dev/null +++ b/src/standard_input/other_types.md @@ -0,0 +1,36 @@ +# Other Types + +Just as you can turn a `String` into an `int` using `Integer.parseInt` and you can +turn a `String` into a `double` using `Double.parseDouble`, you can use `Boolean.parseBoolean` to turn a `String` into a `boolean`. + +```java,no_run +void main() { + String happyString = IO.readln("Are you happy? "); + boolean happy = Boolean.parseBoolean(happyString); + IO.println("You are happy? " + happy); +} +``` + +But lest you become too comfortable, know that there is no `Character.parseCharacter` to turn a `String` into a character. +It is not always going to be the case that there is just one way to convert a `String` to any given type. + +```java,no_run,panics +void main() { + String gradeString = IO.readln("What is your letter grade? "); + // Does not exist! + char grade = Character.parseCharacter(gradeString); + IO.println("You have a " + grade + " in the class."); +} +``` + +For characters specifically you can get the first character of a `String` with `.charAt(0)`, +but you might also want to check that said `String` is only one character long. Each type +will be special. + +```java,no_run +void main() { + String gradeString = IO.readln("What is your letter grade? "); + char grade = gradeString.charAt(0); + IO.println("You have a " + grade + " in the class."); +} +``` \ No newline at end of file diff --git a/src/standard_input/prompting.md b/src/standard_input/prompting.md new file mode 100644 index 00000000..5a7c91ae --- /dev/null +++ b/src/standard_input/prompting.md @@ -0,0 +1,18 @@ +# Prompting + +To prompt a user for information you use the `IO.readln` function. + +`IO.readln` takes a `String` to output as a prompt. This will +work the same as if the `String` was passed to `IO.print`. + +The program will then wait until a human types some text and clicks the enter key. +Whatever they typed will be returned to the program as a `String`. + +```java,no_run +void main() { + String name = IO.readln("What is your name? "); + IO.println("Hello, " + name); +} +``` + +`readln` stands for "read line." It reads the next line a person types on "standard input". \ No newline at end of file diff --git a/src/standard_input/strings.md b/src/standard_input/strings.md new file mode 100644 index 00000000..78bf9a7e --- /dev/null +++ b/src/standard_input/strings.md @@ -0,0 +1,2 @@ +# Strings + diff --git a/src/standard_input_ii.md b/src/standard_input_ii.md new file mode 100644 index 00000000..63250810 --- /dev/null +++ b/src/standard_input_ii.md @@ -0,0 +1,10 @@ +# Standard Input II + + + + +If you are using a program and you type something slightly wrong +it does not feel good if the program then immediately crashes. + +As such it often makes sense to not only request your users type something +and then interpret what they typed, but to also give feedback and perhaps ask them again. \ No newline at end of file diff --git a/src/standard_input_ii/aggregating_data.md b/src/standard_input_ii/aggregating_data.md new file mode 100644 index 00000000..3b3a6b0e --- /dev/null +++ b/src/standard_input_ii/aggregating_data.md @@ -0,0 +1,101 @@ +# Aggregating Data + + +If you ask someone multiple questions you likely will get multiple variables +worth of information. + +```java,no_run +void main() { + String firstName = IO.readln("What is your first name? "); + String lastName = IO.readln("What is your last name? "); + + IO.println("Hello " + firstName + " " + lastName + "."); +} +``` + +This is fine and dandy so long as you immediately use those variables. But once you add in +reprompting logic code can get pretty lengthy. + +```java,no_run +void main() { + String firstName; + do { + firstName = IO.readln("What is your first name? "); + + if (firstName.isBlank()) { + IO.println("First name cannot be blank."); + } + else { + break; + } + } while (true); + + String lastName; + do { + lastName = IO.readln("What is your first name? "); + + if (lastName.isBlank()) { + IO.println("First name cannot be blank."); + } + else { + break; + } + } while (true); + + IO.println("Hello " + firstName + " " + lastName + "."); +} +``` + +And once code gets lengthy it is sometimes useful to separate it into smaller functions. + +I mention all this as a reminder that when you want to return multiple values from a function +you can use a class.[^dto] + +```java,no_run +class Person { + String firstName; + String lastName; +} + +Person askForName() { + String firstName; + do { + firstName = IO.readln("What is your first name? "); + + if (firstName.isBlank()) { + IO.println("First name cannot be blank."); + } + else { + break; + } + } while (true); + + String lastName; + do { + lastName = IO.readln("What is your first name? "); + + if (lastName.isBlank()) { + IO.println("First name cannot be blank."); + } + else { + break; + } + } while (true); + + var person = new Person(); + person.firstName = firstName; + person.lastName = lastName; + return person; +} + +void main() { + Person person = askForName(); + + IO.println("Hello " + person.firstName + " " + person.lastName + "."); +} +``` + + + +[^dto]: When you make a class just to make objects which transfer data between different parts of your program we +sometimes call those DTOs - data transfer objects. You will learn better ways to make DTOs in the future. \ No newline at end of file diff --git a/src/standard_input_ii/challenges.md b/src/standard_input_ii/challenges.md new file mode 100644 index 00000000..bc32e647 --- /dev/null +++ b/src/standard_input_ii/challenges.md @@ -0,0 +1,86 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1 + +Write a program that asks which Twilight character Bella should have ended up with. + +It should keep asking this question until the user types the magic words "i do not care" + +```java,no_run +void main() { + // CODE HERE +} +``` + +## Challenge 2 + +Update the program above to also accept "I DO NOT CARE", "I Do Not Care", and +any other mix of capital and lower-case letters + + +## Challenge 3 + +Write a method called `askForBirthday` that asks for the year, month, and day that someone was born. +This method should return all three pieces of information with a `Birthday` class. + +```java,no_run +class Birthday { + // CODE HERE +} + +Birthday askForBirthday() { + // CODE ALSO HERE +} + +void main() { + Birthday b = askForBirthday(); + IO.println(b.year + "-" + b.month + "-" + b.day) +} +``` + +## Challenge 4 + +Update the program above to reprompt the user if they enter any nonsensical information +for the year, month, or day. This includes negative numbers for the year, month, or day. + +## Challenge 5 + +Update the birthday program to represent the month with an enum named `Month`. + +Accept both the name of the month with any capitalization and the number of the month (starting with `1` for January) as valid ways to specify the month. + +Make sure to still reprompt on unexpected inputs. + +```java,no_run +enum Month { + JANUARY, + FEBRUARY, + MARCH, + APRIL, + MAY, + JUNE, + JULY, + AUGUST, + SEPTEMBER, + NOVEMBER, + DECEMBER +} + +class Birthday { + // CODE HERE +} + +Birthday askForBirthday() { + // CODE ALSO HERE +} + +void main() { + Birthday b = askForBirthday(); + IO.println(b.year + "-" + b.month + "-" + b.day) +} +``` diff --git a/src/standard_input_ii/delayed_assignment.md b/src/standard_input_ii/delayed_assignment.md new file mode 100644 index 00000000..9e0aad26 --- /dev/null +++ b/src/standard_input_ii/delayed_assignment.md @@ -0,0 +1,66 @@ +# Delayed Assignment + +When you have variables declared inside of that loop cannot be seen from the outside. +This poses a problem when you are asking someone a question in a loop but want their response to be +visible later on in the program. + +One strategy for this is to declare any response-holding variables outside the loop. + +The problem with this is that Java isn't smart enough to know that you always initialize those variables. + +```java,no_run,does_not_compile +void main() { + String name; + while (true) { + name = IO.readln("What is your name? "); + if (name.isBlank()) { + IO.println("Name cannot be blank!"); + continue; + } + + break; + } + + IO.println("Hello " + name); +} +``` + +To get around this you can either give an explicit default value. + +```java,no_run +void main() { + String name = null; + while (true) { + name = IO.readln("What is your name? "); + if (name.isBlank()) { + IO.println("Name cannot be blank!"); + continue; + } + + break; + } + + IO.println("Hello " + name); +} +``` + +Or you can use the `do` version of a while loop. +In this context our old friend delayed assignment becomes an option again. This is because Java *is* smart enough +to see that the code in the loop will run at least once. + +```java,no_run +void main() { + String name; + do { + name = IO.readln("What is your name? "); + if (name.isBlank()) { + IO.println("Name cannot be blank!"); + continue; + } + + break; + } while (true); + + IO.println("Hello " + name); +} +``` \ No newline at end of file diff --git a/src/standard_input_ii/enums.md b/src/standard_input_ii/enums.md new file mode 100644 index 00000000..33528d3c --- /dev/null +++ b/src/standard_input_ii/enums.md @@ -0,0 +1,82 @@ +# Enums + +Just as you might want to interpret what someone typed as an `int` or `double` +there are times you will want to interpret input as an `enum` value. + +To do this you can write `.valueOf` after the name of the enum. So for a `StopLight` enum `StopLight.valueOf` +can interpret a `String` as a `StopLight`. + +```java,no_run +enum StopLight { + RED, + YELLOW, + GREEN +} + +void main() { + String colorString = IO.readln("What color was the stoplight? "); + StopLight color = StopLight.valueOf(colorString); + IO.println("The stop light was " + color); +} +``` + +This will throw an exception if the `String` does not match, so you can reprompt using the same `try`/`catch` structure as you would use with `Integer.parseInt`. + +```java,no_run +enum StopLight { + RED, + YELLOW, + GREEN +} + +void main() { + StopLight color; + while (true) { + String colorString = IO.readln("What color was the stoplight? "); + try { + color = StopLight.valueOf(colorString); + } catch (RuntimeException e) { + continue; + } + + break; + } + + IO.println("The stop light was " + color); +} +``` + +Unfortunately, this only works if what they typed is exactly the name of an enum variant. So +in the example above they need to type `RED` in all capital letters. + +To have a different mapping of strings to enum values you need to write code yourself. + +```java,no_run +enum StopLight { + RED, + YELLOW, + GREEN +} + +StopLight stringToStopLight(String s) { + if (s.equals("r")) { + return StopLight.RED; + } + else if (s.equals("y")) { + return StopLight.YELLOW; + } + else if (s.equals("g")) { + return StopLight.GREEN; + } + else { + throw new RuntimeException("Unknown color.") + } +} + +void main() { + String colorString = IO.readln("What color was the stoplight? "); + StopLight color = stringToStopLight(colorString); + IO.println("The stop light was " + color); +} +``` + diff --git a/src/standard_input_ii/header.png b/src/standard_input_ii/header.png new file mode 100644 index 00000000..d0109d8e Binary files /dev/null and b/src/standard_input_ii/header.png differ diff --git a/src/standard_input_ii/leniency.md b/src/standard_input_ii/leniency.md new file mode 100644 index 00000000..3689af7b --- /dev/null +++ b/src/standard_input_ii/leniency.md @@ -0,0 +1,29 @@ +# Leniency + +It can make sense to be lenient with your users when interpreting their input.[^idiots] + +This means accounting for common mistakes people make like having extra spaces or capitalizing things incorrectly. + +For this purpose, methods like `strip` and `equalsIgnoreCase` are useful. + +```java,no_run +void main() { + while (true) { + String response = IO.readln("Answer me: yes or no").strip(); + if (response.equalsIgnoreCase("yes")) { + IO.println("aight"); + } + else if (response.equalsIgnoreCase("no")) { + IO.println("cool"); + } + else { + IO.println("try again"); + continue; + } + + break; + } +} +``` + +[^idiots]: People are idiots. Their fingers are fat and their wills are weak. \ No newline at end of file diff --git a/src/standard_input_ii/reprompting.md b/src/standard_input_ii/reprompting.md new file mode 100644 index 00000000..79783dd8 --- /dev/null +++ b/src/standard_input_ii/reprompting.md @@ -0,0 +1,55 @@ +# Reprompting + +If you ask someone a yes or no question and they respond with "huh?" you might want to ask them again. + +This is a good use case for loops. You ask a question and, if the answer you get is acceptable, +you proceed as normal. If it is not then you loop back and ask again. + +```java,no_run +void main() { + while (true) { + String response = IO.readln("Answer me: yes or no"); + if (response.equals("yes")) { + IO.println("okay then!"); + } + else if (response.equals("no")) { + IO.println("also fine!"); + } + else { + IO.println("Not a valid response"); + // Will go back to the top of the loop + continue; + } + + // If a "continue" is not hit, exit the loop + break; + } +} +``` + +If the program would normally crash on unexpected input you can use `try` and `catch` to recover +from this and reprompt the user. + +This is applicable to `Integer.parseInt`, `Double.parseDouble`, and any other method that would +throw an exception on unexpected inputs. + +```java,no_run +void main() { + int number; + while (true) { + String response = IO.readln("What is your least favorite number? "); + try { + // Here Integer.parseInt might throw an exception, + number = Integer.parseInt(response); + } catch (RuntimeException e) { + // If that happens, go up to the top and reprompt + continue; + } + + // If a "continue" is not hit, exit the loop + break; + } + + IO.println("Your least favorite number is " + number); +} +``` \ No newline at end of file diff --git a/src/standard_input_iii.md b/src/standard_input_iii.md new file mode 100644 index 00000000..2cfb4bda --- /dev/null +++ b/src/standard_input_iii.md @@ -0,0 +1 @@ +# Standard Input III diff --git a/src/static_fields.md b/src/static_fields.md new file mode 100644 index 00000000..8961067c --- /dev/null +++ b/src/static_fields.md @@ -0,0 +1,12 @@ +# Static Fields + + + +To have a field be truly global for your program you can mark it as static. + +```java +class Count { + static int value; +} +``` + diff --git a/src/static_fields/challenges.md b/src/static_fields/challenges.md new file mode 100644 index 00000000..173aec75 --- /dev/null +++ b/src/static_fields/challenges.md @@ -0,0 +1,90 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1 + +Add a static field to this `Ogre` class that keeps track of +how many Ogres have been made thus far in the program.[^threads] + +```java,editable +class Ogre { + // CODE HERE + + Ogre() { + // CODE HERE + } +} + +class Main { + void main() { + // 0 + IO.println(Ogre.NUMBER_OF_OGRES_MADE); + + // 1 + Ogre o1 = new Ogre(); + IO.println(Ogre.NUMBER_OF_OGRES_MADE); + + // 2 + Ogre o2 = new Ogre(); + IO.println(Ogre.NUMBER_OF_OGRES_MADE); + + // 3 + Ogre o3 = new Ogre(); + IO.println(Ogre.NUMBER_OF_OGRES_MADE); + + // 4 + Ogre o4 = new Ogre(); + IO.println(Ogre.NUMBER_OF_OGRES_MADE); + + // 5 + Ogre o5 = new Ogre(); + IO.println(Ogre.NUMBER_OF_OGRES_MADE); + } +} +``` + +## Challenge 2 + +Initialize the `PI` and `TAU` static final fields inside of a static initializer block. +`TAU` should have twice the value of `PI`. + +```java +class Maths { + static final double PI; + static final double TAU; + + static { + // CODE HERE + } +} + +class Main { + void main() { + IO.println(Maths.PI); + IO.println(Maths.TAU); + } +} +``` + +## Challenge 3 + +Rename the constants in the `Doug` class in the way that would +be expected of you by others. + +```java +class Doug { + static final String pattyMayonnaise = "Patty Mayonnaise"; + static final String sKeEtEr = "Mosquito 'Skeeter' Valentine"; + static final String mosquito_valentine = sKeEtEr; + static final String rodgerMKlotz = "Rodger M. Klotz"; + static final String DOUG = "Douglas Yancy Funnie"; +} +``` + + +[^threads]: Part of why mutable static fields are such a nightmare is that code like this would +not work when you have to write "multi-threaded" Java code. There are things you can do with normal fields to sort of "make unsafe stuff safe in a way," but static fields are a lot harder to wrangle. \ No newline at end of file diff --git a/src/static_fields/constants.md b/src/static_fields/constants.md new file mode 100644 index 00000000..cf3b01c6 --- /dev/null +++ b/src/static_fields/constants.md @@ -0,0 +1,17 @@ +# Constants + +Because static fields are global to the entire program, they are the +preferred mechanism for storing "constants." + +Constants are things that you don't expect to change, so you would also +mark such fields as `final`. + +```java +class MathConstants { + static final double PI = 3.14; +} + +// Then in other parts of your code you can reference MathConstants.PI +``` + + diff --git a/src/static_fields/controversy.md b/src/static_fields/controversy.md new file mode 100644 index 00000000..38714582 --- /dev/null +++ b/src/static_fields/controversy.md @@ -0,0 +1,27 @@ +# Controversy + +Static fields are culturally controversial. Specifically static fields +which can change. + +```java +class Counter { + static int value = 0; +} +``` + +In the example above, any part of the code can change the value at any time by writing to `Counter.value`. + +This is "fine" in small to mid-sized programs, but once you have a hundred thousand lines +it can become difficult to reason about what code changes that field and when. + +For this reason[^and] you will probably get a lot of mean comments if you share code that uses a static field you can change. + +Using static fields for constants is less controverial. + +```java +class Constants { + static final int DAYS_IN_A_WEEK = 7; +} +``` + +[^and]: Well, in addition to the generally rampant immaturity of programmers. \ No newline at end of file diff --git a/src/static_fields/declaration.md b/src/static_fields/declaration.md new file mode 100644 index 00000000..01f9bb79 --- /dev/null +++ b/src/static_fields/declaration.md @@ -0,0 +1,10 @@ +# Declaration + +To declare a field as static, add the `static` keyword to its declaration. + +```java +class Count { + static int value; +} +``` + diff --git a/src/static_fields/header.png b/src/static_fields/header.png new file mode 100644 index 00000000..517baaa4 Binary files /dev/null and b/src/static_fields/header.png differ diff --git a/src/static_fields/initialization.md b/src/static_fields/initialization.md new file mode 100644 index 00000000..182336dd --- /dev/null +++ b/src/static_fields/initialization.md @@ -0,0 +1,57 @@ +# Initialization + +By default, static fields will be given the same default initial value as other fields. + +So a static `int` field will be initialized to zero, a static `String` field +will be initialized to `null`, etc. + +```java +class Main { + static int count; + static String name; + + void main() { + IO.println(count); // 0 + IO.println(name); // null + } +} +``` + +If you want to initialize them to a different value you do not do that in a constructor +like you would a normal field. + +You can give them a value directly with `=`. + +```java +class Main { + static int count = 5; + static String name = "bob"; + + void main() { + IO.println(count); // 5 + IO.println(name); // bob + } +} +``` + +Or you can initialize them in a "static block". This looks like the word `static` +followed by some braces `{}` with code in the middle.[^confusing] + +```java +class Main { + static int count; + static String name; + + static { + count = 5; + name = "bob"; + } + + void main() { + IO.println(count); // 5 + IO.println(name); // bob + } +} +``` + +[^confusing]: The rules for static blocks are actually crazy complicated. Try not to do anything "interesting" in them. \ No newline at end of file diff --git a/src/static_fields/meaning.md b/src/static_fields/meaning.md new file mode 100644 index 00000000..471d7887 --- /dev/null +++ b/src/static_fields/meaning.md @@ -0,0 +1 @@ +# Meaning diff --git a/src/static_fields/naming.md b/src/static_fields/naming.md new file mode 100644 index 00000000..7b278955 --- /dev/null +++ b/src/static_fields/naming.md @@ -0,0 +1,22 @@ +# Naming + +For static final fields people generally name them the same way you would an enum - `LIKE_THIS`. + +```java +class Constants { + static final int DAYS_IN_A_WEEK = 7; + static final int WEEKS_IN_A_YEAR = 52; +} +``` + +Because you will get yelled at for using a non-final static field no matter what you do, the rules there are less strict. You can name them in all caps or like a normal variable +depending on personal preference. + +```java +class Counter { + // The people who would be mad at you for the first + // will probably already be mad at you for the second. + static int value = 0; + static int VALUE = 0; +} +``` \ No newline at end of file diff --git a/src/static_fields/usage.md b/src/static_fields/usage.md new file mode 100644 index 00000000..8cb59986 --- /dev/null +++ b/src/static_fields/usage.md @@ -0,0 +1,27 @@ +# Usage + +To use a static field within the class it is declared you just need to write the name of the field. + +```java +class Main { + static int count = 0; + + void main() { + IO.println(count); + } +} +``` + +To use it from another class or to disambiguate it from a regular field with the same +name, you should prefix it with the name of the class it is declared in plus a `.`. + + +```java +class Main { + static int count = 0; + + void main() { + IO.println(Main.count); + } +} +``` \ No newline at end of file diff --git a/src/static_methods.md b/src/static_methods.md new file mode 100644 index 00000000..6e375863 --- /dev/null +++ b/src/static_methods.md @@ -0,0 +1,27 @@ +# Static Methods + + + +If you want to be able to call a method from anywhere in your program you +can use a `static` method. + +```java,no_run +class MyMath { + static int add(int a, int b) { + return a + b; + } +} +``` +```java +~class MyMath { +~ static int add(int a, int b) { +~ return a + b; +~ } +~} +class Main { + void main() { + int result = MyMath.add(1, 2); + IO.println(result); + } +} +``` \ No newline at end of file diff --git a/src/static_methods/challenges.md b/src/static_methods/challenges.md new file mode 100644 index 00000000..4c988239 --- /dev/null +++ b/src/static_methods/challenges.md @@ -0,0 +1,131 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1 + +Change this position class such that it doesn't only have an `x` and a `y`. It should +also have a `z`. + +Then add the following static methods. `fromZ`, `fromXY`, `fromXZ`, and `fromYZ`. + +```java,editable +class Position { + int x; + int y; + + Position(int x, int y) { + this.x = x; + this.y = y; + } + + static Position fromX(int x) { + return new Position(x, 0); + } + + static Position fromY(int y) { + return new Position(0, y); + } +} + +class Main { + void main() { + var p1 = Position.fromXY(1, 2); + IO.println("(" + p1.x + ", " + p1.y + ", " + p1.z + ")"); + + var p2 = Position.fromYZ(1, 2); + IO.println("(" + p2.x + ", " + p2.y + ", " + p2.z + ")"); + + var p3 = Position.fromXZ(1, 2); + IO.println("(" + p3.x + ", " + p3.y + ", " + p3.z + ")"); + + var p4 = Position.fromZ(4); + IO.println("(" + p4.x + ", " + p4.y + ", " + p4.z + ")"); + } +} +``` + +## Challenge 2 + +Make a static method in the `Maths` class called `quadraticFormula`. +It should take in an `a`, `b`, and `c` and run the [quadradic formula](https://en.wikipedia.org/wiki/Quadratic_formula) on those values. + +This formula will give you either one "real" solution, two real solutions, +or two imaginary solutions. + +For now just throw an exception if the solutions are imaginary +and treat having one solution as having two solutions with the same value. + +```java,editable +class Maths { + // CODE HERE +} + +class Main { + void main() { + var result = Maths.quadraticFormula(6, -17, 12); + IO.println(result.solutionOne); + IO.println(result.solutionTwo); + } +} +``` + +## Challenge 3 + +Update your code above to also communicate if a solution is imaginary. +Use a boolean or an enum field on whatever class you wrote to hold both solutions +for this.[^other] + +[^other]: You will learn other ways later. + +## Challenge 4 + +Make the following code compile. Do so first by changing one of the fields to `static` +then by instead passing an extra argument to the `static` method. + +```java,no_run +class Keychain { + final String[] keys; + + Keychain(String[] keys) { + this.keys = keys; + } + + boolean hasKey(String key) { + for (int i = 0; i < keys.length; i++) { + if (keys[i].equals(key)) { + return true; + } + } + return false; + } +} + + +class Main { + Keychain keychain = new Keychain(new String[] { + "house", + "car", + "shed" + }); + + static void unlock(String thing) { + if (keychain.hasKey(thing)) { + IO.println("You have unlocked my " + thing); + } + else { + IO.println("You don't have a key for my " + thing); + } + } + + void main() { + unlock("house"); + unlock("car"); + unlock("shed"); + unlock("heart"); + } +} +``` \ No newline at end of file diff --git a/src/static_methods/declaration.md b/src/static_methods/declaration.md new file mode 100644 index 00000000..a73c34a8 --- /dev/null +++ b/src/static_methods/declaration.md @@ -0,0 +1,12 @@ +# Declaration + +Same as `static` fields, to mark a method as `static` you write the word `static` +before it. + +```java,no_run +class MyMath { + static int add(int a, int b) { + return a + b; + } +} +``` \ No newline at end of file diff --git a/src/static_methods/factories.md b/src/static_methods/factories.md new file mode 100644 index 00000000..c8a79fa3 --- /dev/null +++ b/src/static_methods/factories.md @@ -0,0 +1,119 @@ +# Factories + +Another interesting use of static methods is what we would call a "factory" +method. + +Say you have a class like the following. + +```java +class Position { + int x; + int y; + + Position(int x, int y) { + this.x = x; + this.y = y; + } +} +~void main() {} +``` + +It would be reasonable to want to add an overloaded constructor for when `y` is `0`. + +```java +class Position { + int x; + int y; + + Position(int x, int y) { + this.x = x; + this.y = y; + } + + Position(int x) { + this.x = x; + this.y = 0; + } +} +~void main() {} +``` + +But now it is impossible to add a constructor that works for when `x` is `0`. + +```java,does_not_compile +class Position { + int x; + int y; + + Position(int x, int y) { + this.x = x; + this.y = y; + } + + Position(int x) { + this.x = x; + this.y = 0; + } + + Position(int y) { + this.x = 0; + this.y = y; + } +} +~void main() {} +``` + +Using a static method to create a `Position` - i.e. as a "factory" - is a way around the issue.[^note] + +```java +class Position { + int x; + int y; + + Position(int x, int y) { + this.x = x; + this.y = y; + } + + static Position fromX(int x) { + return new Position(x, 0); + } + + static Position fromY(int y) { + return new Position(0, y); + } +} +``` +```java +~class Position { +~ int x; +~ int y; +~ +~ Position(int x, int y) { +~ this.x = x; +~ this.y = y; +~ } +~ +~ static Position fromX(int x) { +~ return new Position(x, 0); +~ } +~ +~ static Position fromY(int y) { +~ return new Position(0, y); +~ } +~} +~ +class Main { + void main() { + var p1 = new Position(1, 2); + var p2 = Position.fromX(4); + var p3 = Position.fromY(5); + + IO.println(p1.x + ", " + p1.y); + IO.println(p2.x + ", " + p2.y); + IO.println(p3.x + ", " + p3.y); + } +} +``` + +[^note]: This won't work if you defined `Position` inside the anonymous main class. I'll tell you why later. \ No newline at end of file diff --git a/src/static_methods/header.png b/src/static_methods/header.png new file mode 100644 index 00000000..8b18b032 Binary files /dev/null and b/src/static_methods/header.png differ diff --git a/src/static_methods/math.md b/src/static_methods/math.md new file mode 100644 index 00000000..98f7f17c --- /dev/null +++ b/src/static_methods/math.md @@ -0,0 +1,20 @@ +# Math + +One of the most "obvious" usages for static methods is when doing things +that are math or math-like. + +```java,no_run +class MyMath { + static int add(int a, int b) { + return a + b; + } +} +``` + +These sorts of methods have a result computed solely from their inputs, so needing +an instance of a class to call them would be silly. + +The `Math` class that comes with Java has methods that work in this way. `Math.max(a, b)` +is `static` and therefore usable everywhere you want the maximum of two numbers.[^more] + +[^more]: There are way more on there. [Take a look](https://javadoc.mccue.dev/api/java.base/java/lang/Math.html). \ No newline at end of file diff --git a/src/static_methods/naming.md b/src/static_methods/naming.md new file mode 100644 index 00000000..9b39c192 --- /dev/null +++ b/src/static_methods/naming.md @@ -0,0 +1,12 @@ +# Naming + +Unlike static fields, which get new socially accepted naming rules, +you name static methods the same as any other method. + +```java +class Naming { + static void nameLikeNormal() { + + } +} +``` \ No newline at end of file diff --git a/src/static_methods/scope.md b/src/static_methods/scope.md new file mode 100644 index 00000000..cfbf82fa --- /dev/null +++ b/src/static_methods/scope.md @@ -0,0 +1,36 @@ +# Scope + +In the definition of a static method you can use variables like normal +and you can reference other static fields and methods. + +```java,no_run +class ScopeExample { + static final int CAN_ACCESS = 3.14; + + static void canCall() { + } + + static void doStuff() { + canCall(); + IO.println(ScopeExample.CAN_ACCESS); + } +} +``` + +But you cannot access any non-static methods or fields. They are not in scope. + +```java,no_run,does_not_compile +class ScopeExample2 { + final int CANNOT_ACCESS = 3.14; + + void cannotCall() { + } + + static void doStuff() { + cannotCall(); + IO.println( + CANNOT_ACCESS + ); + } +} +``` diff --git a/src/static_methods/usage.md b/src/static_methods/usage.md new file mode 100644 index 00000000..c317c234 --- /dev/null +++ b/src/static_methods/usage.md @@ -0,0 +1,24 @@ +# Usage + +You use a static method by writing the name of the class where it is +defined followed by `.` and the method name. + +```java +class StuffDoer { + static void doStuff() { + IO.println("Doing stuff"); + } +} +``` + +```java +~class StuffDoer { +~ static void doStuff() { +~ IO.println("Doing stuff"); +~ } +~} +~ +void main() { + StuffDoer.doStuff(); +} +``` \ No newline at end of file diff --git a/src/streams.md b/src/streams.md new file mode 100644 index 00000000..7c93b92e --- /dev/null +++ b/src/streams.md @@ -0,0 +1,60 @@ +# Streams + + + +Programs often have to transform all of the elements in a collection +in order to produce a new collection. + +For instance: take all the people in a `List`, remove any who are +older than 65, and extract their names into a set. + +```java +import module java.base; + +record Person(String name, int age) {} + +class Main { + void main() { + List people = List.of( + new Person("Jess", 29), + new Person("Sally", 72), + new Person("Bess", 41) + ); + + Set names = new HashSet<>(); + for (var person : people) { + if (person.age() < 65) { + names.add(person.name()); + } + } + + IO.println(names); + } +} +``` + +While such transformations can always be done in "normal code," +it can be preferable to use "streams." + +```java +import module java.base; + +record Person(String name, int age) {} + +class Main { + void main() { + List people = List.of( + new Person("Jess", 29), + new Person("Sally", 72), + new Person("Bess", 41) + ); + + Set names = people.stream() + .filter(person -> person.age() < 65) + .map(Person::name) + .collect(Collectors.toSet()); + + IO.println(names); + } +} +``` \ No newline at end of file diff --git a/src/streams/challenges.md b/src/streams/challenges.md new file mode 100644 index 00000000..676abce0 --- /dev/null +++ b/src/streams/challenges.md @@ -0,0 +1,71 @@ +# Challenges + + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Translate the following code using a for-loop to code using streams. + +```java,editable +import module java.base; + +class Main { + void main() { + List numbers = List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + for (int n : numbers) { + IO.println(n); + } + } +} +``` + +## Challenge 2. + +Translate the following code using a for-loop to code using streams. + +```java,editable +import module java.base; + +class Main { + void main() { + List timestamps = List.of(1, 1756137441); + + Set instants = new HashSet<>(); + for (int timestamp : timestamps) { + instants.add(Instant.ofEpochSecond(timestamp)); + } + + IO.println(instants); + } +} +``` + +## Challenge 3. + +Read the documentation on [`Collector`](https://javadoc.mccue.dev/api/java.base/java/util/stream/Collector.html) and [`Collectors`](https://javadoc.mccue.dev/api/java.base/java/util/stream/Collector.html). + +Make an implementation of `Collector` that can collect elements into `MySpecialList`. + +```java,editable +import module java.base; + +class MySpecialList extends ArrayList {} + +class Main { + // CODE HERE + + void main() { + MySpecialList l = Stream.of(1, 2, 3) + .collect(/* CODE HERE */); + } +} +``` + +
+ Hint 1 +

Look at the `Collector.of` static methods

+
\ No newline at end of file diff --git a/src/streams/collectors.md b/src/streams/collectors.md new file mode 100644 index 00000000..a018d943 --- /dev/null +++ b/src/streams/collectors.md @@ -0,0 +1,80 @@ +# Collectors + +The most common kind of terminal operation you will perform on a stream +is "collecting" elements into a new collection. + +For this you use the `.collect` method along with an implemention of the +`Collector` interface. + +```java +~void main() { +List roles = List.of("seer", "clown", "nightmare"); + +Function countVowels = s -> { + int vowels = 0; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') { + vowels++; + } + } + return vowels; +}; + +List vowelCounts = roles.stream() + .map(countVowels) + .collect(Collectors.toList()); + +IO.println(vowelCounts); +~} +``` + +There are implementations available as static methods on the `Collectors` class for +collecting into `List`s, `Set`s, and even `Map`s. + +Because collecting into specifically a `List` is so common, there is +also a `.toList()` method directly on `Stream` that serves as a shortcut +for `.collect(Collectors.toUnmodifiableList())`. + +```java +~void main() { +List roles = List.of("seer", "clown", "nightmare"); + +Function countVowels = s -> { + int vowels = 0; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') { + vowels++; + } + } + return vowels; +}; + +// There is also Collectors.toUnmodifiableList +List vowelCountsList = roles.stream() + .map(countVowels) + .collect(Collectors.toList()); + +IO.println(vowelCountsList); + +vowelCountsList = roles.stream() + .map(countVowels) + .toList(); +IO.println(vowelCountsList); + +// ...and Collectors.toUnmodifiableSet() +Set vowelCountsSet = roles.stream() + .map(countVowels) + .collect(Collectors.toSet()); +IO.println(vowelCountsSet); + +// ...and Collectors.toUnmodifiableMap +Map vowelCountsMap = roles.stream() + .collect(Collectors.toMap( + s -> s, + s -> countVowels.apply(s) + )); +IO.println(vowelCountsMap); +~} +``` \ No newline at end of file diff --git a/src/streams/filter.md b/src/streams/filter.md new file mode 100644 index 00000000..abbc91bf --- /dev/null +++ b/src/streams/filter.md @@ -0,0 +1,20 @@ +# filter + +With a stream of elements you can also drop the +elements of the stream as they flow by[^metaphor] with `.filter`. + +`.filter` will test each element with a predicate. If the +predicate returns `true` the element will be retained. +If it returns `false` the element will be dropped. + + +```java,no_run +var numbers = List.of("1", "2", "3"); + +Stream numberStream = numbers.stream() + .map(Integer::parseInt) // 1, 2, 3 + .filter(x -> x % 2 == 1); // 1, 3 +``` + +[^metaphor]: In the real life stream metaphor, this is akin to rocks stuck along the way and not +continuing to go with the flow of water. diff --git a/src/streams/gatherers.md b/src/streams/gatherers.md new file mode 100644 index 00000000..2bae9a39 --- /dev/null +++ b/src/streams/gatherers.md @@ -0,0 +1 @@ +# Gatherers diff --git a/src/streams/header.png b/src/streams/header.png new file mode 100644 index 00000000..6b8cda4d Binary files /dev/null and b/src/streams/header.png differ diff --git a/src/streams/map.md b/src/streams/map.md new file mode 100644 index 00000000..b11d0c81 --- /dev/null +++ b/src/streams/map.md @@ -0,0 +1,28 @@ +# map + +Once you have a stream of elements you can transform the +elements of the stream as they flow by[^metaphor] with `.map`. + +`.map` applies a `Function` to the elements of the stream +one by one and returns you a new `Stream` containing the new elements. + +```java,no_run +var numbers = List.of("1", "2", "3"); + +Stream numberStream = numbers.stream() + .map(Integer::parseInt); // 1, 2, 3 +``` + +You can also call `.map` multiple times to apply multiple transformations. + +```java,no_run +var numbers = List.of("1", "2", "3"); + +Stream numberStream = numbers.stream() + .map(Integer::parseInt) // 1, 2, 3 + .map(x -> x * 2); // 2, 4, 6 +``` + +[^metaphor]: In the real life stream metaphor, this is akin to rocks getting polished +by sand as they flow. + diff --git a/src/streams/mapMulti.md b/src/streams/mapMulti.md new file mode 100644 index 00000000..76ab5b35 --- /dev/null +++ b/src/streams/mapMulti.md @@ -0,0 +1 @@ +# mapMulti diff --git a/src/streams/purpose.md b/src/streams/purpose.md new file mode 100644 index 00000000..31e702b4 --- /dev/null +++ b/src/streams/purpose.md @@ -0,0 +1,43 @@ +# Purpose + +The purpose of streams is to "de-couple" +the source of data, the transformations to apply on that +data, and the final shape you want that data in. + +This serves one somewhat holistic goal and one practical one. + +The holistic goal is that you are "declaring what you want done" +as opposed to "expressing how you want something done." +This is often referred to as "declarative" vs "imperative" programming. + +Compare these two bits of code. They both add one to every number in a list. + +```java,no_run +List doubles = List.of(1.5, 2.5, 3.9); + +List newDoubles = new ArrayList<>(); +for (double d : doubles) { + newDoubles.add(d + 1); +} +``` + +```java,no_run +List doubles = List.of(1.5, 2.5, 3.9); + +List newDoubles = doubles.stream() + .map(d -> d + 1) + .toList(); +``` + +You can argue that the second code snippet more transparently "reflects the intent" +than the first one. The mechanics of looping and adding to a new list are +hidden and the only things on screen are `doubles.stream()` (using this collection as a source), +`.map(d -> d + 1)` (apply this transformation), and `.toList()` (put the results in a list.) + +This has its upsides and downsides. Part of the trouble is that there isn't a good rule of +thumb as to when you should use a regular loop or use a stream - it often comes down to +personal taste and other "soft" factors. + +The mechanical reason for streams is that, because the operations to perform are separated somewhat +from how to perform them, there are opportunities for Java to be "smart" about how it does them. +The usual example given for this is "parallel streams," which we can get into eventually. \ No newline at end of file diff --git a/src/streams/stream.md b/src/streams/stream.md new file mode 100644 index 00000000..a2cfa3d3 --- /dev/null +++ b/src/streams/stream.md @@ -0,0 +1,48 @@ +# stream + +A Java `Stream` represents a "stream[^flow]" +of elements coming from some "source." + +You can stream the elements of a `List` +or `Set` by using the `.stream()` instance method +on each. + +```java,no_run +Stream heroes = List.of( + "Deku", + "Explosive Hero: Great Explosion Murder God Dynamight", + "Froppy" +).stream(); + +Stream villains = Set.of( + "All for One", + "Muscular" +).stream(); +``` + +For arrays there is a static method on `Arrays` which +can stream their contents. You could also first convert the +array to a collection with `Arrays.asList`. + +```java,no_run +Stream heroes = Arrays.stream(new String[] { + "Lin Ling", + "Lucky Cyan" +}); + +Stream villains = Arrays.asList(new String[] { + "E-Soul" +}).stream(); +``` + +If you legitimately do not have a source collection +you can also create a stream directly with `Stream.of`. + +```java,no_run +Stream letterStream = Stream.of('a', 'b', 'c'); +``` + +[^flow]: In real life a stream flows from some source of water +to some destination. The water can carry rocks and other things +along with it. So that is where the metaphor comes from. Real life streams +carry rocks, Java streams carry chunks of data. \ No newline at end of file diff --git a/src/streams/terminal_operations.md b/src/streams/terminal_operations.md new file mode 100644 index 00000000..8cbe2e73 --- /dev/null +++ b/src/streams/terminal_operations.md @@ -0,0 +1,42 @@ +# Terminal Operations + +We call `.map`, `.filter`, and methods like them "intermediate operations." +This is because they run "in the middle" of the entire process. + +For consuming a stream you use "terminal operations." Terminal operations +"consume" the stream and produce some result. + +The simplest terminal operation is `.forEach`. It consumes the entire stream and does +something for each element in the flow. + +```java +~void main() { +List cities = List.of( + "St. Louis", "Dallas", "London", "Tokyo" +); + +cities.stream() + .filter(city -> !city.startsWith("S")) + .forEach(IO::println); // Dallas, London, Tokyo +~} +``` + +Once a terminal operation has been performed the stream is no longer +usable. + +```java,panics +~void main() { +List cities = List.of( + "St. Louis", "Dallas", "London", "Tokyo" +); + +Stream citiesStream = cities.stream() + .filter(city -> !city.startsWith("S")); + +// Dallas, London, Tokyo +citiesStream.forEach(IO::println); + +// java.lang.IllegalStateException: stream has already been operated upon or closed +citiesStream.forEach(IO::println); +~} +``` \ No newline at end of file diff --git a/src/string_builder.md b/src/string_builder.md new file mode 100644 index 00000000..efc1a74f --- /dev/null +++ b/src/string_builder.md @@ -0,0 +1,19 @@ +# StringBuilder + +When you add `String`s together, the result is a new `String`. + +```java +void main() { + String a = "a"; + String b = "b"; + String ab = a + b; + + IO.println(ab); +} +``` + +This is perfectly fine and what you would expect, but if you are adding strings +together in a loop it can become an issue. + +```java +``` \ No newline at end of file diff --git a/src/stringbuilder.md b/src/stringbuilder.md new file mode 100644 index 00000000..5c972348 --- /dev/null +++ b/src/stringbuilder.md @@ -0,0 +1,47 @@ +# StringBuilder + +`String`s cannot be changed in place. This means that every operation +on a `String` needs to produce a brand new `String` as a result. + +Normally this is fine, but when you do something like that in a loop +it can become a performance problem. + +```java +String removeAllNumbers(String input) { + String result = ""; + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (c < '0' || c > '9') { + result = result + c; + } + } + return result; +} + +void main() { + String screenplay = "5".repeat(1000000); + // There are 1301 characters in the above text, meaning this method + // does around that many copies of the string + IO.println(removeAllNumbers(screenplay)); +} +``` + +```java +String removeAllNumbers(String input) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (c < '0' || c > '9') { + result.append(c); + } + } + return result.toString(); +} + +void main() { + String screenplay = "5".repeat(1000000); + // There are 1301 characters in the above text, meaning this method + // does around that many copies of the string + IO.println(removeAllNumbers(screenplay)); +} +``` \ No newline at end of file diff --git a/src/strings.md b/src/strings.md index 0a64c462..83e3b730 100644 --- a/src/strings.md +++ b/src/strings.md @@ -1,5 +1,8 @@ # Strings + + + The `String` data type is used to represent text. ```java,no_run diff --git a/src/strings/access_individual_characters.md b/src/strings/access_individual_characters.md index 336cbfbe..c1f2978b 100644 --- a/src/strings/access_individual_characters.md +++ b/src/strings/access_individual_characters.md @@ -11,16 +11,16 @@ The second by using `1`, and so on. String spy = "loid"; char l = spy.charAt(0); -System.out.println(l); +IO.println(l); char o = spy.charAt(1); -System.out.println(o); +IO.println(o); char i = spy.charAt(2); -System.out.println(i); +IO.println(i); char d = spy.charAt(3); -System.out.println(d); +IO.println(d); ~} ``` @@ -34,7 +34,7 @@ String assassin = "yor"; int indexOfR = 2; char r = assassin.charAt(indexOfR); -System.out.println(r); +IO.println(r); ~} ``` diff --git a/src/strings/challenges.md b/src/strings/challenges.md index 2a115a22..b0a5da78 100644 --- a/src/strings/challenges.md +++ b/src/strings/challenges.md @@ -15,7 +15,7 @@ void main() { String second = "2"; String result = first + second; - System.out.println(result); + IO.println(result); } ``` @@ -28,7 +28,7 @@ void main() { String first = "1"; int second = 2; - System.out.println(first + second); + IO.println(first + second); } ``` @@ -43,7 +43,7 @@ void main() { String second = "b"; String third = "ab"; - System.out.println((first + second).equals(second)); + IO.println((first + second).equals(third)); } ``` @@ -59,7 +59,7 @@ void main() { char a = 'a'; char b = 'b'; char c = 'c'; - System.out.println(a + b + c); + IO.println(a + b + c); } ``` @@ -78,19 +78,19 @@ void main() { int index = 6; - System.out.print(racecar.charAt(index)); + IO.print(racecar.charAt(index)); index += diff; - System.out.print(racecar.charAt(index)); + IO.print(racecar.charAt(index)); index += diff; - System.out.print(racecar.charAt(index)); + IO.print(racecar.charAt(index)); index += diff; - System.out.print(racecar.charAt(index)); + IO.print(racecar.charAt(index)); index += diff; - System.out.print(racecar.charAt(index)); + IO.print(racecar.charAt(index)); index += diff; - System.out.print(racecar.charAt(index)); + IO.print(racecar.charAt(index)); index += diff; - System.out.println(racecar.charAt(index)); + IO.println(racecar.charAt(index)); } ``` diff --git a/src/strings/common_escape_sequences.md b/src/strings/common_escape_sequences.md index 3199d90d..98611208 100644 --- a/src/strings/common_escape_sequences.md +++ b/src/strings/common_escape_sequences.md @@ -21,11 +21,11 @@ String title = "The \"Honorable\" Judge Judy"; Since the backslash is used to escape characters, it too needs to escaped in order to have it be in a `String`. So to encode `Β―\_(ツ)_/Β―` into a String -you need to escape the first backslash. +you need to escape the first backslash.[^forwardslash] ```java ~void main() { -// The first backslash needs to be escaped. Β―\_(ツ)_/Β― +// The backslash needs to be escaped. Β―\_(ツ)_/Β― String shruggie = "Β―\\_(ツ)_/Β―"; ~} ``` @@ -37,3 +37,5 @@ And much the same as with `char`, you need to use `\n` to write in a newline. String letter = "To Whom It May Concern,\n\nI am writing this letter to complain."; ~} ``` + +[^forwardslash]: We call `\` a "backslash" and `/` a "forward slash." In `Β―\_(ツ)_/Β―` the left arm is drawn using the backslash and the right arm with a forward slash. What makes left "backwards" and right "forwards" is just social norms. \ No newline at end of file diff --git a/src/strings/concatenation.md b/src/strings/concatenation.md index d78e560b..6e7169e9 100644 --- a/src/strings/concatenation.md +++ b/src/strings/concatenation.md @@ -9,7 +9,7 @@ String llo = "llo"; String hello = he + llo; -System.out.println(hello); +IO.println(hello); ~} ``` @@ -27,6 +27,6 @@ double dollars = 1.52; String message = "I have " + numberOfApples + " apples and $" + dollars + " in my pocket."; -System.out.println(message); +IO.println(message); ~} ``` diff --git a/src/strings/equality.md b/src/strings/equality.md index 8d13b24e..034188ec 100644 --- a/src/strings/equality.md +++ b/src/strings/equality.md @@ -10,8 +10,8 @@ String lyricTwo = "Green, Green, Dress"; boolean areSameLyric = lyricOne.equals(lyricTwo); boolean isMyName = lyricOne.equals("Bop Bop"); -System.out.println(areSameLyric); -System.out.println(isMyName); +IO.println(areSameLyric); +IO.println(isMyName); ~} ``` @@ -28,6 +28,10 @@ String wow = "WOW"; boolean areNotSame = !bow.equals(wow); -System.out.println(areNotSame); +IO.println(areNotSame); ~} ``` + +Note that you should **not** use `==`. Java will let you do it but you won't get what you expect.[^inaway] + +[^inaway]: It is confusing in a way that we aren't ready to explain yet. Just remember for `int`, `double`, `char`, etc. you can use `==`. For `String` use `.equals`. diff --git a/src/strings/header.png b/src/strings/header.png new file mode 100644 index 00000000..988d4136 Binary files /dev/null and b/src/strings/header.png differ diff --git a/src/strings/length.md b/src/strings/length.md index 75d09553..2a434002 100644 --- a/src/strings/length.md +++ b/src/strings/length.md @@ -8,8 +8,8 @@ String fruit = "strawberry"; int numberOfChars = fruit.length(); // strawberry is 10 characters long -System.out.println( - fruit + " is " numberOfChars + " characters long" +IO.println( + fruit + " is " + numberOfChars + " characters long" ); ~} ``` diff --git a/src/strings_ii.md b/src/strings_ii.md index 981c15da..bc002fab 100644 --- a/src/strings_ii.md +++ b/src/strings_ii.md @@ -1 +1,10 @@ # Strings II + + + + +If you haven't already, you will eventually realize that `String`s are +one of the most common data types you will use. + +As such, its worth it to keep revisiting the things you can do with them. +Expect more sections like this the deeper you get. \ No newline at end of file diff --git a/src/strings_ii/UPPERCASE.md b/src/strings_ii/UPPERCASE.md new file mode 100644 index 00000000..db62fc13 --- /dev/null +++ b/src/strings_ii/UPPERCASE.md @@ -0,0 +1,28 @@ +# UPPERCASE + +Similarly, if you have a `String` which potentially contains lower-cased letters, you can get a new `String` with everything +transformed into upper-case using the `.toUpperCase()` method. + +```java +void main() { + String message = "Happy Valentines Day"; + + String upperCased = message.toUpperCase(); + IO.println(upperCased); +} +``` + +This does not change the original `String` in place. It just makes a new `String` with all upper-case letters. + +Much like with `.toLowerCase()` you can optionally provide a `Locale` to get consistent behavior +no matter what language the system is set to. + +```java +void main() { + String message = "Happy Valentines Day"; + + // Same as above, no matter what. + String upperCased = message.toUpperCase(Locale.US); + IO.println(upperCased); +} +``` \ No newline at end of file diff --git a/src/strings_ii/challenges.md b/src/strings_ii/challenges.md new file mode 100644 index 00000000..1503c5dc --- /dev/null +++ b/src/strings_ii/challenges.md @@ -0,0 +1,113 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Write a method named `isUpperCase` which tells you if a +given `String` is already made of all upper-case characters. + +Hint: One way to do this is to call `.toUpperCase` and check +if the result is the same as the input you were given. + +```java,editable +boolean isUpperCase(String s) { + // ----------- + // CODE HERE + // ----------- +} + +void main() { + // true + IO.println(isUpperCase("ABC")); + // false + IO.println(isUpperCase("abc")); + // false + IO.println(isUpperCase("AbC")); +} +``` + +## Challenge 2. + +Do the same as above, but check if a given `String` is +made of all lower case letters. + +```java,editable +boolean isLowerCase(String s) { + // ----------- + // CODE HERE + // ----------- +} + +void main() { + // false + IO.println(isLowerCase("ABC")); + // true + IO.println(isLowerCase("abc")); + // false + IO.println(isLowerCase("AbC")); +} +``` + +## Challenge 3. + +Add an instance method named `scream` to the `Muppet` class. This +should replace the name of the muppet with the name in upper case +letters + an exclamation point (`!`) at the end. + +```java,editable +class Muppet { + String name; + + // ------------- + // CODE HERE + // ------------- +} + +void main() { + var kermit = new Muppet(); + kermit.name = "kermit"; + + // kermit + IO.println(kermit.name); + + // KERMIT! + kermit.scream(); + IO.println(kermit.name); +} +``` + +## Challenge 4. + +Write a method called `echo`. + +If `echo` is given a non-blank `String`, it should print `You Said: <...>` +where `<...>` is the `String` they +gave minus any leading or trailing whitespace. + +If `echo` is given a `blank` `String`, it should print `You Didn't Say Anything`. + +```java,editable +void echo(String s) { + // ------------- + // CODE HERE + // ------------- +} + +void main() { + // You Said: Hello + echo("Hello"); + + // You Said: Hello + echo(" Hello "); + + // You Didn't Say Anything + echo(""); + + // You Didn't Say Anything + echo(" "); +} +``` \ No newline at end of file diff --git a/src/strings_ii/check_if_blank.md b/src/strings_ii/check_if_blank.md new file mode 100644 index 00000000..ab31bcf6 --- /dev/null +++ b/src/strings_ii/check_if_blank.md @@ -0,0 +1,22 @@ +# Check if blank + +You can check if a `String` is blank by using the `.isBlank` method. + +The difference is that an empty `String` has actually zero characters. A blank `String` can have characters, so long as those characters are what we would consider whitespace. +That is, things like spaces and newlines. + +```java +void main() { + String brainSounds = """ + + + + """; + + // false + IO.println(brainSounds.isEmpty()); + + // true + IO.println(brainSounds.isBlank()); +} +``` \ No newline at end of file diff --git a/src/strings_ii/check_if_empty.md b/src/strings_ii/check_if_empty.md new file mode 100644 index 00000000..5edfa58f --- /dev/null +++ b/src/strings_ii/check_if_empty.md @@ -0,0 +1,30 @@ +# Check if empty + +You can check if a `String` is empty in a few ways. + +The one you should already have been able to figure out[^noshame] is that you can get the `.length` of a `String` +and see if that is zero. + +```java +void main() { + String textMessages = ""; + IO.println( + textMessages.length() == 0 + ); +} +``` + +But another is to use the explicitly defined `.isEmpty()` method. + +```java +void main() { + String textMessages = ""; + IO.println( + textMessages.isEmpty() + ); +} +``` + +This can be more convenient. Both to write, as it is fewer characters to type, and to read later on. + +[^noshame]: Again, no shame if not. I didn't exactly call attention to it. \ No newline at end of file diff --git a/src/strings_ii/equality_ignoring_case.md b/src/strings_ii/equality_ignoring_case.md new file mode 100644 index 00000000..f7abc7b9 --- /dev/null +++ b/src/strings_ii/equality_ignoring_case.md @@ -0,0 +1,19 @@ +# Equality ignoring case + +If you want to check if two `String`s contain the same contents +but you do not care if those contents differ in the casing of letters, you can use the `.equalsIgnoreCase` +method. + +```java +void main() { + String historicalFigureOne = "St. Valentines"; + String historicalFigureTwo = "st. valentines"; + + IO.println( + historicalFigureOne.equalsIgnoreCase(historicalFigureTwo) + ); +} +``` + +This is different from the result the `.equals` method will give you. That method will return `false` if there +are any differences in the two `String`s. \ No newline at end of file diff --git a/src/strings_ii/header.png b/src/strings_ii/header.png new file mode 100644 index 00000000..1f22864b Binary files /dev/null and b/src/strings_ii/header.png differ diff --git a/src/strings_ii/lowercase.md b/src/strings_ii/lowercase.md new file mode 100644 index 00000000..7672c327 --- /dev/null +++ b/src/strings_ii/lowercase.md @@ -0,0 +1,42 @@ +# lowercase + +In English, letters can be either lower-cased (`a`, `b`, `c`) or upper-cased (`A`, `B`, `C`). + +If you have a `String` which potentially contains upper-cased letters, you can get a new `String` with everything +transformed into lower-case using the `.toLowerCase()` method. + +```java +void main() { + String message = "Happy Valentines Day"; + + String lowerCased = message.toLowerCase(); + IO.println(lowerCased); +} +``` + +This does not change the original `String` in place. It just makes a new `String` with all lower-case letters. + +Many other languages also have a distinction between upper-case and lower-case and, usually, `.toLowerCase()` does the "right thing" for them. + +```java +~void main() { +// Cyrillic +IO.println("ΠŸΠ Π˜Π’Π•Π’".toLowerCase()); // prints "ΠΏΡ€ΠΈΠ²Π΅Ρ‚" +~} +``` + +In different languages the mapping between upper-case and lower-case letters can differ. + +For example, in Turkish the lower-case form of the letter `I` is `Δ±` and not `i`. +By default, Java uses the rules for the language of the computer it is running on. +So `"I".toLowerCase()` will return `"i"` on an English system and `"Δ±"` on a Turkish one. + +To get behavior that is consistent regardless of the machine it is being run on, +you can use an explicit `Locale`. + +```java +~void main() { +IO.println("I".toLowerCase(Locale.US)); // i - on every computer +IO.println("I".toLowerCase(Locale.forLanguageTag("tr-TR"))); // Δ± - on every computer +~} +``` diff --git a/src/strings_ii/strip_extra_whitespace.md b/src/strings_ii/strip_extra_whitespace.md new file mode 100644 index 00000000..5fd937ac --- /dev/null +++ b/src/strings_ii/strip_extra_whitespace.md @@ -0,0 +1,39 @@ +# Strip extra whitespace + +If you have a `String` which might contain some "trailing" whitespace or "leading" +whitespace, you can remove that by using the `.strip` method. + +This will give a new `String` with both the leading and trailing whitespace removed. + +```java +void main() { + String message = " Happy Valentines Day. "; + + IO.print(message.strip()); + IO.println("|"); +} +``` + +If you want to just remove the leading whitespace, you can use `.stripLeading`. + +```java +void main() { + String message = " Happy Valentines Day. "; + + IO.print(message.stripLeading()); + IO.println("|"); +} +``` + +And to remove only trailing whitespace, `.stripTrailing`. + +```java +void main() { + String message = " Happy Valentines Day. "; + + IO.print(message.stripTrailing()); + IO.println("|"); +} +``` + +All of these methods are useful when you get input from human beings. Humans are generally pretty bad at seeing if they hit the spacebar one too many times. diff --git a/src/switch.md b/src/switch.md index 6e65d240..50a2fded 100644 --- a/src/switch.md +++ b/src/switch.md @@ -1,5 +1,8 @@ # Switch + + + `if` and `else` let you branch logic based on whether any arbitrary expression that evaluates to a boolean. @@ -14,38 +17,38 @@ if (isLeapYear && !bloodMoon && (age > 30 || catName.equals("fred"))) { But it can be burdensome if all you are doing is checking if some variable has a particular value. -```java +```java,no_run ~void main() { if (food.equals("apple")) { - System.out.println("Red"); + IO.println("Red"); } -else if (name.equals("grape")) { - System.out.println("Purple"); +else if (food.equals("grape")) { + IO.println("Purple"); } else if (food.equals("orange")) { - System.out.println("Orange"); + IO.println("Orange"); } else { - System.out.println("Other"); + IO.println("Other"); } ~} ``` For these situations, you can use a `switch`. -```java +```java,no_run switch (fruit) { case "apple" -> { - System.out.println("Red"); + IO.println("Red"); } case "grape" -> { - System.out.println("Purple"); + IO.println("Purple"); } case "orange" -> { - System.out.println("Orange"); + IO.println("Orange"); } default -> { - System.out.println("Other"); + IO.println("Other"); } } ``` diff --git a/src/switch/case_and_default.md b/src/switch/case_and_default.md index 163f1e19..faaf441d 100644 --- a/src/switch/case_and_default.md +++ b/src/switch/case_and_default.md @@ -13,16 +13,16 @@ and gives you a place to put "default" behavior. void sayColor(String fruit) { switch (fruit) { case "apple" -> { - System.out.println("Red"); + IO.println("Red"); } case "grape" -> { - System.out.println("Purple"); + IO.println("Purple"); } case "orange" -> { - System.out.println("Orange"); + IO.println("Orange"); } default -> { - System.out.println("Other"); + IO.println("Other"); } } } diff --git a/src/switch/challenges.md b/src/switch/challenges.md new file mode 100644 index 00000000..8d3983d4 --- /dev/null +++ b/src/switch/challenges.md @@ -0,0 +1,217 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Write a method named `isSorcerer`. If the method is given +any of `"yuji"` or `"gojo"` return `true`. Otherwise return `false`. + +Start by writing this with `if` and `else`. Then alter the code so it uses +`switch` instead. + +```java +boolean isSorcerer(String name) { + // CODE HERE +} + +void main() { + IO.println( + isSorcerer("yuji") + ); + + IO.println( + isSorcerer("gojo") + ); + + IO.println( + isSorcerer("yugi") // Wrong series + ); +} +``` + +## Challenge 2. + +Same basic challenge as above, but write a method named `didRedSoxWin` +which takes an `int` representing a year. + +Return `true` if that is a year the Boston Red Sox won a world series. +`false` otherwise. This time start off by using a switch. + +```java +boolean didRedSoxWin(int year) { + // CODE HERE +} + +void main() { + IO.println( + "2004: " + didRedSoxWin(2004) + ); + + IO.println( + "1998: " + didRedSoxWin(1998) + ); + + IO.println( + "2013: " + didRedSoxWin(2013) + ); + + IO.println( + "1903: " + didRedSoxWin(1903) + ); +} +``` + + +## Challenge 3. + +Make a method named `transition` which takes in a `StopLight` +and returns the next light it will transition to. + +For those who don't drive cars: red lights go to green, +green lights go to yellow, and yellow lights go to red. + +This is a duplicate of a challenge from a previous section. This time +do it using a `switch`. + +```java,editable +enum StopLight { + RED, + YELLOW, + GREEN +} + +StopLight transition(StopLight current) { + // ------------ + // CODE HERE + // ------------ +} + +void main() { + var light = StopLight.RED; + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); +} +``` + +## Challenge 4. + +In reality a `StopLight` can also be broken and not function +at all. Alter `transition` so it accounts for a new `BROKEN` +state which transitions to itself. (`BROKEN` goes to `BROKEN`). + +```java,editable +enum StopLight { + RED, + YELLOW, + GREEN, + BROKEN +} + +StopLight transition(StopLight current) { + // ------------ + // CODE HERE + // ------------ +} + +void main() { + var light = StopLight.RED; + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); + + light = transition(light); + IO.println(light); +} +``` + +## Challenge 5. + +Given a type of bear, return the correct course of action +if you run into one in the wild and it attacks you. + +If the bear is `null`, return `null` as the action to take. + +If you don't know what to do when you run into a bear, look it up. Use `switch` +first then try writing the same logic using `if` and `else`. + +```java +enum Bear { + POLAR, + BROWN, + BLACK, + PANDA, + KOALA +} + +enum Action { + LAY_DOWN, + FIGHT_BACK, + RUN_AWAY, + YEET +} + +Action inCaseOfBearAttack(Bear bear) { + // CODE HERE +} + +void main() { + IO.println( + inCaseOfBearAttack(Bear.POLAR) + ); + + IO.println( + inCaseOfBearAttack(Bear.BROWN) + ); + + + IO.println( + inCaseOfBearAttack(Bear.BLACK) + ); + + IO.println( + inCaseOfBearAttack(Bear.PANDA) + ); + + IO.println( + inCaseOfBearAttack(Bear.KOALA) + ); + + IO.println( + inCaseOfBearAttack(null) + ); +} +``` \ No newline at end of file diff --git a/src/switch/combining_cases.md b/src/switch/combining_cases.md index a260f502..5d028165 100644 --- a/src/switch/combining_cases.md +++ b/src/switch/combining_cases.md @@ -20,6 +20,6 @@ String scientificName(String vegetable) { } ~ ~void main() { -~ System.out.println(scientificName("cabbage")); +~ IO.println(scientificName("cabbage")); ~} ``` \ No newline at end of file diff --git a/src/switch/enums.md b/src/switch/enums.md index aea07a4f..524ccc01 100644 --- a/src/switch/enums.md +++ b/src/switch/enums.md @@ -16,13 +16,13 @@ void main() { StopLight light = StopLight.GREEN; switch (light) { case RED -> { - System.out.println("Stop!"); + IO.println("Stop!"); } case YELLOW -> { - System.out.println("Speed up, coward!"); + IO.println("Speed up, coward!"); } case GREEN -> { - System.out.println("Go!"); + IO.println("Go!"); } } } diff --git a/src/switch/exhaustiveness.md b/src/switch/exhaustiveness.md index ac79cfb8..829e771f 100644 --- a/src/switch/exhaustiveness.md +++ b/src/switch/exhaustiveness.md @@ -23,10 +23,36 @@ String describe(int number) { } ``` -When you have something like an enum you don't need a `default` case -because you can handle every variant explicitly. +When you have something like an enum you might think you don't need a `default` case +because you can handle every variant explicitly.[^sometimes] -```java,no_run +```java,no_run,does_not_compile +enum Bird { + TURKEY, + EAGLE, + WOODPECKER +} + +boolean isScary(Bird bird) { + switch (bird) { + case TURKEY -> { + return true; + } + case EAGLE -> { + return true; + } + case WOODPECKER -> { + return false; + } + } +} +``` + +This is, unfortunately, not the case for a switch statement. +You either need a `default` case or to have an explicit `case null` to handle the +possibility that the enum value is `null`.[^lies] + +```java,no_run,does_not_compile enum Bird { TURKEY, EAGLE, @@ -44,6 +70,17 @@ boolean isScary(Bird bird) { case WOODPECKER -> { return false; } + // Need to handle the possibility of null + // or give a "default ->" + case null -> { + // You might want to return a value or just crash + return false; + } } } -``` \ No newline at end of file +``` + +[^sometimes]: This is sometimes the case! It is really just this specific form of switch that has this restriction. + +[^lies]: Remember at the very start when I said I was going to lie to you? This is one of those lies. The real reason for this restriction has to do with how Java compiles switch statements and a concept called "separate compilation." Basically +even though we know you covered all the enum variants, Java still makes you account for if a new enum variant was added later on. It doesn't do this for all forms of `switch` though. \ No newline at end of file diff --git a/src/switch/header.png b/src/switch/header.png new file mode 100644 index 00000000..93a39b67 Binary files /dev/null and b/src/switch/header.png differ diff --git a/src/switch/ints.md b/src/switch/ints.md index db496090..50579198 100644 --- a/src/switch/ints.md +++ b/src/switch/ints.md @@ -7,13 +7,13 @@ You can also use `int`s with switches. int year = 2024; switch (year) { case 2023 -> { - System.out.println("The Chiefs"); + IO.println("The Chiefs"); } case 2024 -> { - System.out.println("The Chiefs"); + IO.println("The Chiefs"); } default -> { - System.out.println("I don't know"); + IO.println("I don't know"); } } ~} diff --git a/src/switch/null.md b/src/switch/null.md index 5b6d77d9..3579b4e7 100644 --- a/src/switch/null.md +++ b/src/switch/null.md @@ -6,13 +6,13 @@ When a switch is given a `null` value a `NullPointerException` will be thrown im void eat(String food) { switch (food) { case "dog food" -> { - System.out.println("Crunch"); + IO.println("Crunch"); } case "cat food" -> { - System.out.println("Slorp"); + IO.println("Slorp"); } default -> { - System.out.println("Other food"); + IO.println("Other food"); } } } @@ -29,16 +29,16 @@ case labels. `default` will not suffice. void eat(String food) { switch (food) { case "dog food" -> { - System.out.println("Crunch"); + IO.println("Crunch"); } case "cat food" -> { - System.out.println("Slorp"); + IO.println("Slorp"); } case null -> { - System.out.println("No food"); + IO.println("No food"); } default -> { - System.out.println("Other food"); + IO.println("Other food"); } } } @@ -54,13 +54,13 @@ A default branch and a null case can be combined by separating them with a comma void eat(String food) { switch (food) { case "dog food" -> { - System.out.println("Crunch"); + IO.println("Crunch"); } case "cat food" -> { - System.out.println("Slorp"); + IO.println("Slorp"); } case null, default -> { - System.out.println("Other food"); + IO.println("Other food"); } } } diff --git a/src/switch/omitted_default.md b/src/switch/omitted_default.md index 17ff0aa4..4c850155 100644 --- a/src/switch/omitted_default.md +++ b/src/switch/omitted_default.md @@ -7,13 +7,13 @@ If you have no logic to put in it, you can omit the `default` case from a `switc void react(String fruit) { switch (fruit) { case "apple" -> { - System.out.println("WOW"); + IO.println("WOW"); } case "orange" -> { - System.out.println("Zoinks!"); + IO.println("Zoinks!"); } case "grape" -> { - System.out.println("Zoopers!"); + IO.println("Zoopers!"); } } } diff --git a/src/switch/strings.md b/src/switch/strings.md index f64ae4c0..7aed3c8e 100644 --- a/src/switch/strings.md +++ b/src/switch/strings.md @@ -7,16 +7,16 @@ As already shown, you can use a case to match `String` values. String veggie = "cucumber"; switch (veggie) { case "cabbage" -> { - System.out.println("A cabbage"); + IO.println("A cabbage"); } case "brussel sprout" -> { - System.out.println("A brussel sprout"); + IO.println("A brussel sprout"); } case "cucumber" -> { - System.out.println("A cucumber"); + IO.println("A cucumber"); } default -> { - System.out.println("Other"); + IO.println("Other"); } } ~} diff --git a/src/switch_ii.md b/src/switch_ii.md new file mode 100644 index 00000000..d96264e9 --- /dev/null +++ b/src/switch_ii.md @@ -0,0 +1,41 @@ +# Switch II + + + +A common thing to do is have a `switch` statement which assigns a value +to a variable in each branch. + +```java +enum StopLight { + RED, + YELLOW, + GREEN +} + +enum Action { + STOP, + SLOW_DOWN, + GO +} + +void main() { + StopLight light = StopLight.GREEN; + + Action action = null; // Delayed assignment rules are funky here. + switch (light) { + case RED -> { + action = Action.STOP; + } + case YELLOW -> { + action = Action.SLOW_DOWN; + } + case GREEN -> { + action = Action.GO; + } + } + + IO.println(action); +} +``` + +For this purpose, you can instead use a switch "as an expression." \ No newline at end of file diff --git a/src/switch_ii/challenges.md b/src/switch_ii/challenges.md new file mode 100644 index 00000000..2ca608d3 --- /dev/null +++ b/src/switch_ii/challenges.md @@ -0,0 +1,52 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1 + +Complete the switch expression such that it yields +the appropriate value for each case. + +In the surfer case, make sure to print out "Radical!" before yielding +the value. + +```java,editable +enum Profession { + FIREFIGHTER, + PLUMBER, + SURFER +} + +enum NaturalEnemy { + FIRE, + LEAKY_PIPES, + BODACIOUS_WAVES +} + +NaturalEnemy enemy(Profession p) { + return switch (p) { + case FIREFIGHTER -> { + // CODE HERE + }; + case PLUMBER -> { + // CODE HERE + } + case SURFER -> { + // CODE HERE + } + } +} + +void main() { + IO.println(enemy(Profession.FIREFIGHTER)); + IO.println(enemy(Profession.PLUMBER)); + IO.println(enemy(Profession.SURFER)); +} +``` + +## Challenge 2 + +Update your program above to omit `yield` in the two cases where it is not needed. \ No newline at end of file diff --git a/src/switch_ii/exhaustiveness.md b/src/switch_ii/exhaustiveness.md new file mode 100644 index 00000000..f78fae27 --- /dev/null +++ b/src/switch_ii/exhaustiveness.md @@ -0,0 +1,61 @@ +# Exhaustiveness + +When a switch is used as an expression it needs to be exhaustive. + +```java +~void main() { +String name = "bob"; + +boolean cool = switch (name) { + case "bob" -> false; + default -> true; +}; + +IO.println(cool); +~} +``` + +If you attempt to make a non-exhaustive switch expression, Java will not let you. + +```java,does_not_compile +~void main() { +String name = "bob"; + +boolean cool = switch (name) { + case "bob" -> false; +}; + +IO.println(cool); +~} +``` + +Unlike in a switch statement, covering all the enum values in a switch expression +is enough for that switch to be considered exhaustive. You do not need an +extra `default` or `case null`[^reason]. + +```java,no_run +enum Species { + ALIEN, + PREDATOR, + HUMAN +} + +void main() { + Species species = Species.valueOf( + IO.readln("What do you see? ").toUpperCase() + ); + String message = switch (species) { + case ALIEN -> "Run"; + case PREDATOR -> "Fight Back"; + case HUMAN -> "RUN" + }; + + IO.println(message); +} +``` + +[^reason]: The nitty gritty of why this is comes down to what Java will do if the code is +run with an unexpected enum variant. With a switch statement the code will just move on and +run none of the switch cases. With a switch expression Java will crash on an unexpected value. +This difference is partially due to the fact that switch statements came first in the language +and switch expressions came later. What a world, huh? \ No newline at end of file diff --git a/src/switch_ii/header.png b/src/switch_ii/header.png new file mode 100644 index 00000000..9e943138 Binary files /dev/null and b/src/switch_ii/header.png differ diff --git a/src/switch_ii/omitted_yield.md b/src/switch_ii/omitted_yield.md new file mode 100644 index 00000000..803abf60 --- /dev/null +++ b/src/switch_ii/omitted_yield.md @@ -0,0 +1,61 @@ +# Omitted Yield + +If a branch of a switch just yields a value but does nothing else interesting you can +omit the `yield` along with the surrounding `{` and `}`. + +```java +~enum StopLight { +~ RED, +~ YELLOW, +~ GREEN +~} +~ +~enum Action { +~ STOP, +~ SLOW_DOWN, +~ GO +~} +~ +~void main() { +StopLight light = StopLight.GREEN; + +Action action = switch (light) { + case RED -> Action.STOP; + case YELLOW -> Action.SLOW_DOWN; + case GREEN -> Action.GO; +}; + +IO.println(action); +~} +``` + +Which can be mixed with cases that have explicit `yield`s and might do other things. + +```java +~enum StopLight { +~ RED, +~ YELLOW, +~ GREEN +~} +~ +~enum Action { +~ STOP, +~ SLOW_DOWN, +~ GO +~} +~ +void main() { + StopLight light = StopLight.GREEN; + + Action action = switch (light) { + case RED -> Action.STOP; + case YELLOW -> { + IO.println("Lemon Light!"); + yield Action.SLOW_DOWN; + } + case GREEN -> Action.GO; + }; + + IO.println(action); +} +``` \ No newline at end of file diff --git a/src/switch_ii/return_a_switch.md b/src/switch_ii/return_a_switch.md new file mode 100644 index 00000000..877bfafe --- /dev/null +++ b/src/switch_ii/return_a_switch.md @@ -0,0 +1,19 @@ +# Return a Switch + +If you choose to, you do not need to assign the result of a switch expression into a variable. You can directly return the result from a method. + +```java,no_run +enum Bird { + TURKEY, + EAGLE, + WOODPECKER +} + +boolean isScary(Bird bird) { + return switch (bird) { + case TURKEY -> true; + case EAGLE -> true; + case WOODPECKER -> false; + }; +} +``` \ No newline at end of file diff --git a/src/switch_ii/yield.md b/src/switch_ii/yield.md new file mode 100644 index 00000000..ad263a23 --- /dev/null +++ b/src/switch_ii/yield.md @@ -0,0 +1,39 @@ +# Yield + +To use a "switch expression" you put the entire switch to the right hand side of an equals sign[^context] and, instead of assigning to a variable, you "`yield`" the value you want to assign. + +```java +enum StopLight { + RED, + YELLOW, + GREEN +} + +enum Action { + STOP, + SLOW_DOWN, + GO +} + +void main() { + StopLight light = StopLight.GREEN; + + Action action = switch (light) { + case RED -> { + yield Action.STOP; + } + case YELLOW -> { + yield Action.SLOW_DOWN; + } + case GREEN -> { + yield Action.GO; + } + }; + + IO.println(action); +} +``` + +`yield` is very similar to `return`. The difference is that `return` will exit the entire method. `yield` just decides what the switch evaluates to. + +[^context]: Technically we are talking about an "expression context." Meaning a place where you are allowed to put an expression. The right hand side of an equals sign is one, but there are many others. \ No newline at end of file diff --git a/src/switch_iii.md b/src/switch_iii.md new file mode 100644 index 00000000..627911ef --- /dev/null +++ b/src/switch_iii.md @@ -0,0 +1,35 @@ +# Switch III + + + + +The switch statement in Java originally came from a little language you might know +about called `C`. + +In `C` switches work slightly differently than the ones you have seen so far in Java. +But, due to this history, there is another kind of switch that doesn't use arrows (`->`) +and instead uses a colon (`:`). + +```java +class Main { + boolean shouldBeMainCharacter(String name) { + switch (name) { + case "Gohan": + return true; + case "Goku": + default: + return false; + } + } + + void main() { + IO.println( + shouldBeMainCharacter("Goku") + ); + } +} +``` + +This "C-Style Switch" is important to learn chiefly because, for a long time, +it was the only switch in Java. Therefore in your coding life you are very likely to +run into it. \ No newline at end of file diff --git a/src/switch_iii/break.md b/src/switch_iii/break.md new file mode 100644 index 00000000..b1d88e60 --- /dev/null +++ b/src/switch_iii/break.md @@ -0,0 +1,78 @@ +# break + +After each case label in a C-style switch, you should write `break;`.[^cookout] + +```java +class Main { + void main() { + String name = "Piccolo"; + switch (name) { + case "Squidward": + IO.println("Invited, but not coming."); + break; + + case "Piccolo": + IO.println("Coming to the cookout."); + break; + + case "Spider-Man": + IO.println("Not coming"); + break; + } + } +} +``` + +This is different from breaking out of a loop and won't break out of any surrounding loops. + +```java +class Main { + void main() { + for (int i = 0; i < 3; i++) { + // Will still run 3 times + String name = "Piccolo"; + switch (name) { + case "Squidward": + IO.println("Invited, but not coming."); + break; + + case "Piccolo": + IO.println("Coming to the cookout."); + break; + + case "Spider-Man": + IO.println("Not coming"); + break; + } + } + } +} +``` + +To break out of surrounding loops you can use a labeled break. + +```java +class Main { + void main() { + outerLoop: + for (int i = 0; i < 3; i++) { + String name = "Piccolo"; + switch (name) { + case "Squidward": + IO.println("Invited, but not coming."); + break; + + case "Piccolo": + IO.println("Coming to the cookout."); + break outerLoop; // Will break out of the loop + + case "Spider-Man": + IO.println("Not coming"); + break; + } + } + } +} +``` + +[^cookout]: [Who should be invited to the cookout?](https://www.youtube.com/watch?v=64SoFWJHSd8) \ No newline at end of file diff --git a/src/switch_iii/challenges.md b/src/switch_iii/challenges.md new file mode 100644 index 00000000..5b72e126 --- /dev/null +++ b/src/switch_iii/challenges.md @@ -0,0 +1,95 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Convert this program which uses a switch without fallthrough +to instead use fallthrough for all the common cases. + +```java,editable +enum Character { + GOKU, + PICCOLO, + FRIEZA, + VEGETA, + PILAF, + GOHAN, + BEERUS +} + +class Main { + String isEvil(Character c) { + switch (c) { + case GOKU -> { + // Man puts the multiverse in danger + // on account of wanting to fight! + return "debatable"; + } + case PICCOLO -> { + return "not_anymore"; + } + case FRIEZA -> { + return "yes"; + } + case VEGETA -> { + return "not_anymore"; + } + case PILAF -> { + return "not_anymore"; + } + case GOHAN -> { + return "no"; + } + case BEERUS -> { + // He's more a force of nature, + // but its relative. + return "debatable"; + } + default -> { + return "unknown"; + } + } + } + + void main() { + IO.println("Goku: " + isEvil(Character.GOKU)); + IO.println("Piccolo: " + isEvil(Character.PICCOLO)); + IO.println("Frieza: " + isEvil(Character.FRIEZA)); + IO.println("Vegeta: " + isEvil(Character.VEGETA)); + IO.println("Pilaf: " + isEvil(Character.PILAF)); + IO.println("Gohan: " + isEvil(Character.GOHAN)); + IO.println("Beerus: " + isEvil(Character.BEERUS)); + } +} +``` + +## Challenge 2. + +Convert the previous program using fallthrough to also +be directly returned as a switch expression using `yield`. + +## Challenge 3. + +Write a method named `aToZ` which, given a character +between `a` and `z`, will print all the letters +from that letter on until `z`. + +Make use of a switch with fallthrough instead of loops or any other mechanism. + +```java,editable +class Main { + void aToZ(char c) { + // CODE HERE + } + + void main() { + aToZ('z'); // z + aToZ('x'); // x y z + aToZ('a'); // a b c d ... x y z + } +} +``` diff --git a/src/switch_iii/default.md b/src/switch_iii/default.md new file mode 100644 index 00000000..5747eb7d --- /dev/null +++ b/src/switch_iii/default.md @@ -0,0 +1,65 @@ +# default + +Just like with normal switches, C-style switches can have a `default` label +that matches when no other label does. + +```java +class Main { + boolean shouldBeMainCharacter(String name) { + switch (name) { + case "Gohan": + return true; + default: + return false; + } + } + + void main() { + IO.println( + shouldBeMainCharacter("Goku") + ); + } +} +``` + +If you have a C-style switch over an enum you need this `default` case for exhaustiveness. Java does not +otherwise accept that you have covered all the cases. + +```java,does_not_compile +enum Technique { + KAMEHAMEHA, + INSTANT_TRANSMISSION, + KAIOKEN, + ULTRA_INSTINCT +} + +class Main { + boolean didGokuStealItFromSomeoneElse(Technique technique) { + switch (technique) { + case KAMEHAMEHA: + IO.println("Master Roshi Taught it to him"); + return true; + case INSTANT_TRANSMISSION: + IO.println("Space aliens"); + return true; + case KAIOKEN: + IO.println("King Kai's name is in it!"); + return true; + case ULTRA_INSTINCT: + IO.println("I'd say not"); + return false; + // Even though we covered every option, Java doesn't trust us. + // You need a default label or to return later in the function + // + // default: + // throw new IllegalStateException("Unexpected: " + technique); + } + } + + void main() { + IO.println( + didGokuStealItFromSomeoneElse(Technique.INSTANT_TRANSMISSION) + ); + } +} +``` \ No newline at end of file diff --git a/src/switch_iii/fallthrough.md b/src/switch_iii/fallthrough.md new file mode 100644 index 00000000..fb2dbb7d --- /dev/null +++ b/src/switch_iii/fallthrough.md @@ -0,0 +1,31 @@ +# Fallthrough + +If the code for a given label does not have a `break` then it will "fall through" +to the cases below. + +This is what makes C-style switches strange. It can occasionaly be useful if the same code should +run for some or all cases, but is annoyingly easy to do on accident.[^history] + +```java +class Main { + void sayWhoTheyFought(String name) { + switch (name) { + case "Goku": + IO.println("Fought Pilaf"); + IO.println("Fought The Red Ribbon Army"); + case "Gohan": // "Goku" will fall through to this case + IO.println("Fought Frieza"); + IO.println("Fought Cell"); + IO.println("Fought Majin Buu"); + } + } + + void main() { + sayWhoTheyFought("Gohan"); + IO.println("----------------------"); + sayWhoTheyFought("Goku"); + } +} +``` + +[^history]: [This StackExchange Post](https://softwareengineering.stackexchange.com/questions/162615/why-dont-languages-use-explicit-fall-through-on-switch-statements) explains how this came about. I don't have a primary source on the "The reason that C did it that way is that the creators of C intended switch statements to be easy to optimize into a jump table." claim, but it lines up with my biases and preconceptions. Therefore it must be true! \ No newline at end of file diff --git a/src/switch_iii/header.png b/src/switch_iii/header.png new file mode 100644 index 00000000..c853b5f2 Binary files /dev/null and b/src/switch_iii/header.png differ diff --git a/src/switch_iii/return.md b/src/switch_iii/return.md new file mode 100644 index 00000000..a33f2232 --- /dev/null +++ b/src/switch_iii/return.md @@ -0,0 +1,30 @@ +# return + +If you explicitly `return` from inside a C-style switch then there is no need to have a `break` +to avoid fallthrough. + +```java +class Main { + void sayWhoTheyFought(String name) { + switch (name) { + case "Launch": + IO.println("Fought Red Ribbon Army"); + IO.println("Fought 3 nameless convicts"); + return; // This will return from the whole method + case "Goku": + IO.println("Fought Pilaf"); + IO.println("Fought The Red Ribbon Army"); + case "Gohan": + IO.println("Fought Frieza"); + IO.println("Fought Cell"); + IO.println("Fought Majin Buu"); + } + } + + void main() { + sayWhoTheyFought("Launch"); + } +} +``` + +This should be intuitive. diff --git a/src/switch_iii/yield.md b/src/switch_iii/yield.md new file mode 100644 index 00000000..5003c9fb --- /dev/null +++ b/src/switch_iii/yield.md @@ -0,0 +1,36 @@ +# yield + +If every branch of a C-style switch `yield`s a value, you can use that switch as an expression. + +```java +enum Technique { + KAMEHAMEHA, + INSTANT_TRANSMISSION, + KAIOKEN, + ULTRA_INSTINCT +} + +class Main { + boolean didGokuStealItFromSomeoneElse(Technique technique) { + boolean answer = switch (technique) { + case KAMEHAMEHA: + case INSTANT_TRANSMISSION: + case KAIOKEN: + yield true; + case ULTRA_INSTINCT: + yield false; + }; + + return answer; + } + + void main() { + IO.println( + didGokuStealItFromSomeoneElse(Technique.INSTANT_TRANSMISSION) + ); + } +} +``` + +In this situation you do not need to have a `default` case for switches over enums. Java will believe that +you covered every possibility. \ No newline at end of file diff --git a/src/tcp_over_ip.md b/src/tcp_over_ip.md new file mode 100644 index 00000000..ca090496 --- /dev/null +++ b/src/tcp_over_ip.md @@ -0,0 +1,13 @@ +# TCP over IP + +Most of the internet is built on top of something called "TCP over IP" +or "Transmission Control Protocol over Internet Protocol." + +If those words don't make it immediately obvious what that means, thats normal. +The short version is that the "IP" part refers to how computers find eachother +on the internet and the "TCP" part refers to the how those computers +speak to eachother once they've found eachother. + +For the long version, I'll defer to this youtube video. + + diff --git a/src/tcp_over_ip/low_level_versus_high_level.md b/src/tcp_over_ip/low_level_versus_high_level.md new file mode 100644 index 00000000..011b1f0c --- /dev/null +++ b/src/tcp_over_ip/low_level_versus_high_level.md @@ -0,0 +1,19 @@ +# Low Level versus High Level + +TCP over IP, and by extension Sockets, are generally +considered to be a "low level" mechanism. + +When we say something is "low level" its usually in reference to +a "high level" thing that is built on top of the lower level thing. +In the case of TCP over IP, there are multiple protocols that use it +behind the scenes but don't generally require a programmer +to know some of the finer details of how that is used. + +We will get to a few of these eventually, but I bring this up +mostly to try and break the cultural taboo of using a low level +thing. There is a cultural bias in the programming field to prefer +higher levels mechanisms. This can sometimes be well-founded +but just as often be conditional on the situation. + +Which is all to say, if someone mocks you for using sockets directly +don't feel bad about yourself. We are building up slowly for a reason. \ No newline at end of file diff --git a/src/tcp_over_ip/sockets.md b/src/tcp_over_ip/sockets.md new file mode 100644 index 00000000..6b8d1b7b --- /dev/null +++ b/src/tcp_over_ip/sockets.md @@ -0,0 +1,7 @@ +# Sockets + +To communicate with a server using TCP over IP you use the `Socket` +class. + +// ---- +Th \ No newline at end of file diff --git a/src/text_editors.md b/src/text_editors.md new file mode 100644 index 00000000..ea65ac58 --- /dev/null +++ b/src/text_editors.md @@ -0,0 +1,12 @@ +# Text Editors + +Code is written in "text files." These are files that just contain +text. + +This is different from something like Microsoft Word or the notes app on a phone. +Those kinds of programs are for "rich text." Meaning you can bold words, change font +colors, embed images - all sorts of stuff. + +Text files only store text. + +To create and edit text files we use programs called "text editors." \ No newline at end of file diff --git a/src/text_editors/challenges.md b/src/text_editors/challenges.md new file mode 100644 index 00000000..ae33bad3 --- /dev/null +++ b/src/text_editors/challenges.md @@ -0,0 +1,2 @@ +# Challenges + diff --git a/src/text_editors/chromebooks_and_phones.md b/src/text_editors/chromebooks_and_phones.md new file mode 100644 index 00000000..a78c5925 --- /dev/null +++ b/src/text_editors/chromebooks_and_phones.md @@ -0,0 +1 @@ +# Chromebooks and Phones diff --git a/src/text_editors/codespace_ai.png b/src/text_editors/codespace_ai.png new file mode 100644 index 00000000..a20d009c Binary files /dev/null and b/src/text_editors/codespace_ai.png differ diff --git a/src/text_editors/codespace_continue_working.png b/src/text_editors/codespace_continue_working.png new file mode 100644 index 00000000..41c65e6a Binary files /dev/null and b/src/text_editors/codespace_continue_working.png differ diff --git a/src/text_editors/codespace_extension_no.png b/src/text_editors/codespace_extension_no.png new file mode 100644 index 00000000..98cbb3b0 Binary files /dev/null and b/src/text_editors/codespace_extension_no.png differ diff --git a/src/text_editors/codespace_extension_prompt.png b/src/text_editors/codespace_extension_prompt.png new file mode 100644 index 00000000..61bfb5bc Binary files /dev/null and b/src/text_editors/codespace_extension_prompt.png differ diff --git a/src/text_editors/codespace_first_view.png b/src/text_editors/codespace_first_view.png new file mode 100644 index 00000000..ef6931c4 Binary files /dev/null and b/src/text_editors/codespace_first_view.png differ diff --git a/src/text_editors/codespace_ignore_all.png b/src/text_editors/codespace_ignore_all.png new file mode 100644 index 00000000..a30d11e5 Binary files /dev/null and b/src/text_editors/codespace_ignore_all.png differ diff --git a/src/text_editors/codespace_instance_size.png b/src/text_editors/codespace_instance_size.png new file mode 100644 index 00000000..c4c44d06 Binary files /dev/null and b/src/text_editors/codespace_instance_size.png differ diff --git a/src/text_editors/codespace_repo_landing.png b/src/text_editors/codespace_repo_landing.png new file mode 100644 index 00000000..2c9f89cd Binary files /dev/null and b/src/text_editors/codespace_repo_landing.png differ diff --git a/src/text_editors/codespace_run_code.png b/src/text_editors/codespace_run_code.png new file mode 100644 index 00000000..a6ca703d Binary files /dev/null and b/src/text_editors/codespace_run_code.png differ diff --git a/src/text_editors/codespace_srcmainjava.png b/src/text_editors/codespace_srcmainjava.png new file mode 100644 index 00000000..e890c930 Binary files /dev/null and b/src/text_editors/codespace_srcmainjava.png differ diff --git a/src/text_editors/ides.md b/src/text_editors/ides.md new file mode 100644 index 00000000..218b38f5 --- /dev/null +++ b/src/text_editors/ides.md @@ -0,0 +1,17 @@ +# IDEs + +A term you might have heard or will hear in the future is "IDE." + +IDE stands for "Integrated Development Environment." It is basically a text editor +with a bunch of features "integrated" that ease "development" of software. + +Programs like [Eclipse](https://eclipseide.org/), [IntelliJ](https://www.jetbrains.com/idea/), and [Netbeans](https://netbeans.apache.org/front/main/index.html) +are all considered Java IDEs. This is because they come integrated with +functionality that makes developing specficially programs in Java easier. + +Text editors that have plugins available can often be juiced up enough that they will also +be considered "IDEs." This is the case for [VSCode](https://code.visualstudio.com/) +which has two Java plugins as well as plugins which support development in other languages.[^matter] + +[^matter]: Now does any of this matter right now? No. I just think its good to not get confused +or get unneeded FOMO. \ No newline at end of file diff --git a/src/text_editors/install_java.md b/src/text_editors/install_java.md new file mode 100644 index 00000000..8ae284aa --- /dev/null +++ b/src/text_editors/install_java.md @@ -0,0 +1,60 @@ +# Install Java + +After you've got VSCodium set up the next step +is to install Java.[^barebones] + +## Chromebooks and School Computers + +
+ Expand + +You can skip this step. The instructions I gave to get set up with Github Codespaces +should have given you an environment where Java is already set up. + +
+ + +## Windows + +
+ Expand + +Download the "JDK MSI" from [adoptium.net](https://adoptium.net/temurin/releases/?version=25&os=windows). +Select whatever the newest version is. You will need at least Java 25 to follow along with the +examples in this book. + +Run the installer, selecting all the default options. This should make the `java` command available to +you. + +
+ + +## Mac OS + +
+ Expand + +Download the "JDK .pkg" from [adoptium.net](https://adoptium.net/temurin/releases/?version=25&os=mac). +Select whatever the newest version is. You will need at least Java 25 to follow along with the +examples in this book. + +Run the installer, selecting all the default options. This should make the `java` command available to +you. + +
+ +## Linux + +
+ Expand + +Linux is a little annoying. If you are using it you are likely used to it +by now, but you can use [adoptium.net](https://adoptium.net/temurin/releases/?version=25&os=linux) like everyone else, but there is no universal installer there. + +You can either download the `.tar.gz` file that matches your machine, extract it, +and add the `bin` folder to your `PATH`, or you can try to find an installer for your +specific linux distribution. + +
+ +[^barebones]: Apologies if you find these instructions a little barebones. \ No newline at end of file diff --git a/src/text_editors/install_vscodium.md b/src/text_editors/install_vscodium.md new file mode 100644 index 00000000..2f419187 --- /dev/null +++ b/src/text_editors/install_vscodium.md @@ -0,0 +1,158 @@ +# Install VSCodium + +To install VSCodium you need to go to this page: + +[https://github.com/VSCodium/vscodium/releases](https://github.com/VSCodium/vscodium/releases). + +Then follow the instructions below specific to what kind of computer you have. + +## Chromebooks and School Computers + +
+ Expand +

Unfortunately the defining characteristic of a Chromebook is that you cannot install + "normal" software on it.

+

This is also generally true for computers provided to you by a school. Sometimes they will + be okay with you running things normally, sometimes not.

+

For these situations VSCodium is not an option so for now I am going to recommend falling + back to using a service called "Github Codespaces." This is a service provided by Microsoft + where they let you use VSCode in a web browser.

+

First, make an account on Github.

+

Then go to https://github.com/bowbahdoe/j25-codespace and press the . key on your keyboard.

+ +

After pressing . you should be redirected to a page that looks like this

+ +

Click the button on the bottom labeled "Continue Working in GitHub Codespaces."

+ +

Select the smallest instance size from the dropdown that appears.

+ +

After it loads for awhile you will see a pop-up in the bottom left of your screen asking + if you want to install an extension for Java files.

+ +

You want to say no to this.

+ +

You will also want to say no to all extension suggestions.

+ +

Next on the chopping block is the AI thing on the right. You want to uncheck the "Show View by Default." option on it then click the `x` to dismiss it.

+ +

After vanquishing evil, your next task is to type `java src/Main.java` into the "Terminal" area at the bottom of your screen.

+ +

Press the enter key and you should see "Hello, world" be printed out.

+ +

You can skip the next section on installing Java for now. You are ready to proceed with the book.

+
+ + +## Windows + +
+ Expand +

If you are on Windows you want to click this link.

+ + + +

This will download a file. Double click it and it will open an installer like this. Follow it through to the end selecting all the default options

+ + + +

When you open the program it installs you should see a screen like this.

+ + +

In the top-left corner select File -> Open Folder

+ + +

Make a folder somewhere on your computer to store your code. It doesn't matter what you call it.

+ + + +

Then make a new file named src/Main.java

+ + + +

Inside of this file put the following contents.

+ +```java,no_run +void main() { + IO.println("Hello, world"); +} +``` + +

Now skip ahead to the next section on installing Java. Come back when you are done.

+ +

Once you are back you want to open a new terminal.

+ + + +

Type java src/Main.java in the terminal and press enter to run your first program. +If this doesn't work you might need to restart your computer or you might have messed up a step.

+ + +
+ +## MacOS + +
+ Expand +

If you have an Apple M1 Mac or newer then you need to scroll down +to the "ARM 64bits" section (of whatever the newest release is) and click this link.

+ + + +

If you have an older Mac then you want to click this link.

+ + + +

This will download a file. Double click it and it will open a window like this.

+ + +

Then drag the VSCodium logo to the Applications folder.

+ + +

Then you should be able to find and open the app from the Launchpad app.

+ + + + + +

When you open it you should see a screen like this.

+ + +

In the top-right corner select File -> Open Folder

+ + +

Make a folder somewhere on your computer to store your code. It doesn't matter what you call it.

+ + +

Then make a new file named src/Main.java

+ + + +

Inside of this file put the following contents.

+ +```java,no_run +void main() { + IO.println("Hello, world"); +} +``` + +

Now skip ahead to the next section on installing Java. Come back when you are done.

+ +

Once you are back you want to open a new terminal.

+ + + +

Type java src/Main.java in the terminal and press enter to run your first program. +If this doesn't work you might need to restart your computer or you might have messed up a step.

+ + + +
+ +## Linux + +
+ Expand +

Select the installer that matches your system and go from there.

+

Sorry I don't have as nice of a walkthrough as I do for Windows and Mac. In my defense, + every Linux machine is its own beast.

+
+ +Early computers didn't have graphical interfaces with windows or buttons you could "click." +Instead, they offered a text based interface. + +We call this sort of interface a "terminal," though you might hear the terms "shell" +and "terminal emulator" used interchangibly. diff --git a/src/the_terminal/bash.md b/src/the_terminal/bash.md new file mode 100644 index 00000000..f695d1f0 --- /dev/null +++ b/src/the_terminal/bash.md @@ -0,0 +1,8 @@ +# Bash + +If you are programming on a computer running either Mac or Linux you should have easy access +to a bash terminal. Just search your applications for something called "terminal" or that has the word "term" in it. + +This is the most common kind of terminal for working professionals to use.[^asterisk] + +[^asterisk]: Asterisks apply, of course. I didn't run or try to find a survey so this is mostly anecdotal. \ No newline at end of file diff --git a/src/the_terminal/challenges.md b/src/the_terminal/challenges.md new file mode 100644 index 00000000..9358534c --- /dev/null +++ b/src/the_terminal/challenges.md @@ -0,0 +1 @@ +# Challenges diff --git a/src/the_terminal/changing_directories.md b/src/the_terminal/changing_directories.md new file mode 100644 index 00000000..ae15af62 --- /dev/null +++ b/src/the_terminal/changing_directories.md @@ -0,0 +1,25 @@ +# Changing Directories + +You can change what your working directory is using the `cd` command. + +After the name of the command, you write the name of the directory you want to change in to. + +```bash +$ pwd +/Users/emccue +$ cd Downloads +$ pwd +/Users/emccue/Downloads +``` + +If you want to go to a "parent" directory, you can write `..`. This means "one directory up". + +```bash +$ pwd +/Users/emccue +$ cd .. +$ pwd +/Users +``` + +[^cd]: Short for "change directory" \ No newline at end of file diff --git a/src/the_terminal/chromebooks_and_school_computers.md b/src/the_terminal/chromebooks_and_school_computers.md new file mode 100644 index 00000000..7015579f --- /dev/null +++ b/src/the_terminal/chromebooks_and_school_computers.md @@ -0,0 +1,12 @@ +# Chromebooks and School Computers + +A very common thing that school systems will do is prevent students from accessing a terminal. + +This is a good thing if your goal is to prevent high schoolers from committing shenanigans. Less so if your goal is to learn how to program. + +There are also computer types, like chromebooks, which aren't really built with programming in mind. While you could access a terminal, the sorts of programs you would generally install to +work with Java will be out of reach. It's a similar case for things like iPads. + +If you are in one of these situations I would recommend getting a "normal" computer if at all possible. You can make do for a time, but eventually it will be required to continue in this field.[^thems] + +[^thems]: Thems the ropes, kid. 🀷 \ No newline at end of file diff --git a/src/the_terminal/commands.md b/src/the_terminal/commands.md new file mode 100644 index 00000000..80d85d2f --- /dev/null +++ b/src/the_terminal/commands.md @@ -0,0 +1,15 @@ +# Commands + +The primary way you interact with a terminal is by running commands. + +You start with a blank prompt and write something like the following[^nottyped]. + +```bash +$ echo Hello +Hello +``` + +The first word is the name of the program to run. In this case `echo`. Everything after +that is an "argument" to the program. + +[^nottyped] You don't actually type the `$`. That is illustrative. Your terminal will give you something like a `$` or a `>` as a "prompt." \ No newline at end of file diff --git a/src/the_terminal/creating_directories.md b/src/the_terminal/creating_directories.md new file mode 100644 index 00000000..368f66d7 --- /dev/null +++ b/src/the_terminal/creating_directories.md @@ -0,0 +1,11 @@ +# Creating Directories + +In order to create a directory you can use the `mkdir`[^mkdir] command. + +```bash +$ mkdir project +$ ls +project +``` + +[^mkdir]: Make Directory. \ No newline at end of file diff --git a/src/the_terminal/creating_files.md b/src/the_terminal/creating_files.md new file mode 100644 index 00000000..546281eb --- /dev/null +++ b/src/the_terminal/creating_files.md @@ -0,0 +1,21 @@ +# Creating Files + +To create a blank file you can use the `touch`[^why] command. + +```bash +$ touch hello.txt +``` + +This is situationally useful, but more often than not you will create files +using a program called a "text editor." + +There are some text editors, like [`vim`](https://www.vim.org/), which run entirely +inside the terminal.[^diehard] + +At the start of this book I showed you [VSCodium](https://vscodium.com/). If you weren't otherwise shown a different option, that is a decent default. You can use it to create and edit files. + +[^why]: This is called `touch` because it will "touch" a file and update its "last updated timestamp" but change nothing else. + +[^diehard]: These have their die hard supporters. The lunatics. + +[^thisbook]: This book has been written inside Visual Studio Code. diff --git a/src/the_terminal/directories.md b/src/the_terminal/directories.md new file mode 100644 index 00000000..ea7c559d --- /dev/null +++ b/src/the_terminal/directories.md @@ -0,0 +1,15 @@ +# Directories + +Directories, also known as folders, are the places where files are in a computer. + +When you open a terminal, you are "inside" a particular directory. + +You can see the directory you are in by running the `pwd`[^pwd] command. + +```bash +$ pwd +/Users/emccue +``` + + +[^pwd]: "Print Working Directory" \ No newline at end of file diff --git a/src/the_terminal/getting_used_to_it.md b/src/the_terminal/getting_used_to_it.md new file mode 100644 index 00000000..f9ad555e --- /dev/null +++ b/src/the_terminal/getting_used_to_it.md @@ -0,0 +1,8 @@ +# Getting Used to it + +So far this is only a shallow dive. It will take a good amount of time for you to get +comfortable using the terminal for things. + +Thats normal. You'll get there eventually. Just know that at least some familiarity with the terminal is going to be needed. + +We'll get back to it eventually, but feel free to seek out some bash/terminal specific resources online in the meantime. \ No newline at end of file diff --git a/src/the_terminal/header.png b/src/the_terminal/header.png new file mode 100644 index 00000000..e680f42e Binary files /dev/null and b/src/the_terminal/header.png differ diff --git a/src/the_terminal/java_programs.md b/src/the_terminal/java_programs.md new file mode 100644 index 00000000..f4ad9fd0 --- /dev/null +++ b/src/the_terminal/java_programs.md @@ -0,0 +1 @@ +# Run Java Programs diff --git a/src/the_terminal/listing_files.md b/src/the_terminal/listing_files.md new file mode 100644 index 00000000..c5b03b91 --- /dev/null +++ b/src/the_terminal/listing_files.md @@ -0,0 +1,18 @@ +# Listing Files + +You can list the files in your current working directory with the `ls`[^ls] command. + +```bash +$ ls +Applications +Camera +Desktop +Development +Documents +Downloads +``` + +This is useful for getting your bearings, but also to find a file you forget the name of or similar. + + +[^ls]: Short for "List" diff --git a/src/the_terminal/run_java_programs.md b/src/the_terminal/run_java_programs.md new file mode 100644 index 00000000..6531ede1 --- /dev/null +++ b/src/the_terminal/run_java_programs.md @@ -0,0 +1,32 @@ +# Run Java Programs + +If you've properly set up Java on your machine you should be able to use the `java` command +to run any code on your machine. + +Just write `java` followed by the path to a file containing Java code.[^eventually] + +Say we had this code in a file named `Main.java` + +```java +void main() { + IO.println("Hello"); +} +``` + +To run this, say `java Main.java`. + +```bash +$ java Main.java +Hello +``` + +If the file is in a directory that is not your current working directory, you +should include the full path. + +```bash +$ java src/Main.java +Hello +``` + +[^eventually]: You can get away with just using your editor's run button for awhile, but this +will eventually become important to know. \ No newline at end of file diff --git a/src/the_terminal/windows_subsystem_for_linux.md b/src/the_terminal/windows_subsystem_for_linux.md new file mode 100644 index 00000000..dce5cb02 --- /dev/null +++ b/src/the_terminal/windows_subsystem_for_linux.md @@ -0,0 +1,10 @@ +# Windows Subsystem for Linux + +If you are using Windows there are two terminals that come preinstalled. One is `cmd.exe`, where +you write in a language called batch, and the other is "PowerShell." + +Both of these differ in significant ways from bash so, if at all possible, you should get set up with the [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/about). + +This will let you follow along with the bash snippets you'll see later in this book.[^other_shells] + +[^other_shells]: It is certainly possible for me to also include instruction for PowerShell and batch but it doesn't feel practical. I spend most of my working hours using bash and can test commands on the machine I use to write this. It would be hard for me to do that with the Windows specific shells \ No newline at end of file diff --git a/src/time.md b/src/time.md new file mode 100644 index 00000000..759a310b --- /dev/null +++ b/src/time.md @@ -0,0 +1,10 @@ +# Time + + + +Wikipedia defines "time" as "The continued sequence of existence and events that occurs in an apparently irreversible succession from the past, through the present, and into the future."[^source] + +Most everything that interacts with the real world needs to understand information about time. +As such, Java has various ways to work with data that holds information about time. + +[^source]: https://en.wikipedia.org/wiki/Time \ No newline at end of file diff --git a/src/time/challenges.md b/src/time/challenges.md new file mode 100644 index 00000000..89c5b840 --- /dev/null +++ b/src/time/challenges.md @@ -0,0 +1,83 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1. + +Print out every day from January 1st to December 31st. When the program reaches your birthday +make it print out "Happy Birthday \" instead of the date. + +```java,editable +class Main { + void main() { + // CODE HERE + } +} +``` + +## Challenge 2. + +Make a `Poison` class which has a `Duration` field which stores how long +the poison will be potent for as well as an `Instant` at which the +poison was brewed. + +Implement a method that takes an `Instant` and returns if the `Poison` +will be expired by that point. + +```java,editable +import java.time.Duration; +import java.time.Instant; + +class Poison { + // CODE HERE +} + +class Main { + void main() { + var hemlock = new Poison(Instant.now(), Duration.ofDays(365 * 3)); + + IO.println(hemlock.isPotentAt(Instant.now())); // true + IO.println(hemlock.isPotentAt(Instant.now().plus(Duration.ofDays(5))); // true + IO.println(hemlock.isPotentAt(Instant.now().plus(Duration.ofDays(365 * 10)))); // false + } +} +``` + +## Challenge 3. + +Get as input using `IO.readln` a `day`, `month`, `year`, and UTC offset. + +Interpret that input as an `OffsetDateTime` then print how many seconds will have +passed between that offset date time and midnight of January 1st 1983 GMT. + +```java,editable +class Main { + void main() { + // CODE HERE + } +} +``` + +## Challenge 4. + +A train leaves Boston at 12:50pm EDT on August 23rd 2025 and arrives in Chicago +at 10:12am CDT August 24th 2025. + +How many minutes long was that train ride? Use the Java's time classes to figure out the answer. + +```java,editable +class Main { + void main() { + // CODE HERE + } +} +``` + +As a small hint, you will first want to represent those events as `ZonedDateTime`s, convert the `ZonedDateTime`s to `Instant`s, and then get the `Duration` between those `Instant`s. Then get the number +of minutes in that `Duration`. + + + diff --git a/src/time/date.md b/src/time/date.md new file mode 100644 index 00000000..7eec0265 --- /dev/null +++ b/src/time/date.md @@ -0,0 +1,59 @@ +# Date + +There is one class related to time that isn't much like the others: `java.util.Date`. + +Java did not originally come with the `java.time` classes. At first it just had `Date`. + +```java +import java.util.Date; + +class Main { + void main() { + Date date = new Date(); + IO.println(date); + } +} +``` + +`Date` is somewhat of a chimera between `Instant` and `ZonedDateTime`. It represents an instant in time but specifically in the UTC timezone.[^gmt] + +It is important to know about because a lot of code, including code that comes with Java, makes use of it. + +Whenever you see `Date` in the wild you should usually turn it into an `Instant` by +calling `.toInstant()`. + +```java +import java.time.Instant; +import java.util.Date; + +class Main { + void main() { + Date date = new Date(); + IO.println(date); + + Instant instant = date.toInstant(); + IO.println(instant); + } +} +``` + +You can also construct a `Date` from an `Instant` using `Date.from`. This is useful if there is some code that wants a `Date` as an argument. + +```java +import java.time.Instant; +import java.util.Date; + +class Main { + void main() { + var instant = Instant.now(); + IO.println(instant); + + Date date = Date.from(instant); + IO.println(date); + } +} +``` + +To be clear though, `Date` has problems. We aren't ready to explain all of them yet. Just treat `Date` as haunted, as in by ghosts, and use the `java.time` alternatives when you can. + +[^gmt]: You will notice that when we print out the date we get GMT. GMT is basically the same as UTC, though [the documentation for `Date`](https://javadoc.mccue.dev/api/java.base/java/util/Date.html) explains the difference. \ No newline at end of file diff --git a/src/time/duration.md b/src/time/duration.md new file mode 100644 index 00000000..3ea152d1 --- /dev/null +++ b/src/time/duration.md @@ -0,0 +1,58 @@ +# Duration + +A `Duration` stores a duration of time. + +You can make these with `Duration.ofMinutes`, `Duration.ofMillis` +and other similarly named methods. + +```java +import java.time.Duration; + +void main() { + var fiveMinutes = Duration.ofMinutes(5); + IO.println(fiveMinutes); + + var twelveMilliSeconds = Duration.ofMillis(12); + IO.println(twelveMilliSeconds); +} +``` + +You can use these to get the duration between two `Instant`s with +`Duration.between`. + +```java +import java.time.Instant; +import java.time.Duration; + +void main() { + var january2nd = Instant.ofEpochMilli(86400000); + IO.println(january2nd); + + var january3rd = Instant.ofEpochMilli(86400000 * 2); + IO.println(january3rd); + + Duration twentyFourHours = Duration.between(january2nd, january3rd); + IO.println(twentyFourHours); +} +``` + +And you can move `Instant`s by a given `Duration` of time using its `.plus` and `.minus` +methods. + +```java +import java.time.Instant; +import java.time.Duration; + +void main() { + var january1st = Instant.ofEpochMilli(0); + IO.println(january1st); + + IO.println( + january1st.plus(Duration.ofHours(45)) + ); + + IO.println( + january1st.minus(Duration.ofHours(1)) + ); +} +``` diff --git a/src/time/header.png b/src/time/header.png new file mode 100644 index 00000000..78904413 Binary files /dev/null and b/src/time/header.png differ diff --git a/src/time/instant.md b/src/time/instant.md new file mode 100644 index 00000000..39106f91 --- /dev/null +++ b/src/time/instant.md @@ -0,0 +1,29 @@ +# Instant + +A `java.time.Instant` stores information on a particular moment in time. + +You can get the current "instant" with `Instant.now`. + +```java +import java.time.Instant; + +void main() { + var now = Instant.now(); + IO.println(now); +} +``` + +But if you happen to know the number of milliseconds since January 1, 1970 0:00 UTC[^epoch], you +can get an `Instant` which represents that point in time with `Instant.ofEpochMilli`. + +```java +import java.time.Instant; + +void main() { + var january2nd = Instant.ofEpochMilli(86400000); + IO.println(january2nd); +} +``` + +[^epoch]: https://en.wikipedia.org/wiki/Unix_time + diff --git a/src/time/local_date.md b/src/time/local_date.md new file mode 100644 index 00000000..64e7ea7f --- /dev/null +++ b/src/time/local_date.md @@ -0,0 +1,30 @@ +# LocalDate + +A local date is something like "January 10th, 2024." + +This just records a day, month, and year. It doesn't know +in what part of the world it was January 10th 2024, just +that somewhere the "local" date was that. + +You can make a `LocalDate` with `LocalDate.of`. + +```java +import java.time.LocalDate; + +void main() { + var jan10 = LocalDate.of(2024, 1, 10); + IO.println(jan10); +} +``` + +And you can get the current `LocalDate` in whatever timezone +your computer is programmed to be in with `LocalDate.now();` + +```java +import java.time.LocalDate; + +void main() { + var now = LocalDate.now(); + IO.println(now); +} +``` \ No newline at end of file diff --git a/src/time/local_date_time.md b/src/time/local_date_time.md new file mode 100644 index 00000000..c7c124dc --- /dev/null +++ b/src/time/local_date_time.md @@ -0,0 +1,22 @@ +# LocalDateTime + +What do you get if you combine a `LocalDate` and a `LocalTime`? A `LocalDateTime`! + +A `LocalDateTime` stores dates and times. + +If you have both a `LocalDate` and a `LocalTime` you can combine them +with `LocalDateTime.of`. + +```java +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.LocalDateTime; + +class Main { + void main() { + var jan10 = LocalDate.of(2024, 1, 10); + var tenTwentyFour = LocalTime.of(10, 24, 0); + IO.println(LocalDateTime.of(jan10, tenTwentyFour)); + } +} +``` \ No newline at end of file diff --git a/src/time/local_time.md b/src/time/local_time.md new file mode 100644 index 00000000..98a98f21 --- /dev/null +++ b/src/time/local_time.md @@ -0,0 +1,29 @@ +# LocalTime + +In the same way a local date leaves off context about where +it was and what time it was, a local time just stores the time. + +This means it will hold the exact time during the day, down to the nanoseconds. + +You can make a `LocalTime` with `LocalTime.of` giving it the hours, minutes, and +seconds into the day. + +```java +import java.time.LocalTime; + +void main() { + var tenTwentyFour = LocalTime.of(10, 24, 0); + IO.println(tenTwentyFour); +} +``` + +And similarly you can get the current time your computer thinks it is with `LocalTime.now()` + +```java +import java.time.LocalTime; + +void main() { + var now = LocalTime.now(); + IO.println(now); +} +``` \ No newline at end of file diff --git a/src/time/offset_date_time.md b/src/time/offset_date_time.md new file mode 100644 index 00000000..07b17adc --- /dev/null +++ b/src/time/offset_date_time.md @@ -0,0 +1,63 @@ +# OffsetDateTime + +An `OffsetDateTime` is similar to a `ZonedDateTime` but with the key difference +that an `OffsetDateTime` doesn't record a moment in a specific time zone, but instead +as an offset from the [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time)[^utc] timezone. + +This is useful because timezones change their rules frequently. If you had to pick a representation +of dates and times to store, this is a good default. + +```java +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.ZoneId; + +class Main { + void main() { + var feb14 = LocalDate.of(2025, 2, 14); + var fiveTwentyThree = LocalTime.of(5, 23, 0); + var est = ZoneId.of("US/Eastern"); + + LocalDateTime localDateTime = LocalDateTime.of(feb14, fiveTwentyThree); + ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, est); + OffsetDateTime offsetDateTime = zonedDateTime.toOffsetDateTime(); + + IO.println(offsetDateTime); + } +} +``` + +You can get the current `OffsetDateTime` for the time zone your computer is running in +with `OffsetDateTime.now()`. + +```java +import java.time.OffsetDateTime; + +class Main { + void main() { + var now = OffsetDateTime.now(); + + IO.println(now); + } +} +``` + +And you can do the same for an arbitrary time zone by giving a `ZoneId` to +`now`. Java knows the UTC offset for every time zone. + +```java +import java.time.OffsetDateTime; +import java.time.ZoneId; + +class Main { + void main() { + var now = OffsetDateTime.now(ZoneId.of("US/Eastern")); + + IO.println(now); + } +} +``` + +[^utc]: UTC stands for "Coordinated Universal Time" in English and "Temps Universel Coordonné" in French. The specific letter order of "UTC" (rather than "CUT" or "TUC") was chosen as [a compromise between the two languages](https://www.nist.gov/pml/time-and-frequency-division/how-utcnist-related-coordinated-universal-time-utc-international#7114136). \ No newline at end of file diff --git a/src/time/time_zones.md b/src/time/time_zones.md new file mode 100644 index 00000000..f81d1a83 --- /dev/null +++ b/src/time/time_zones.md @@ -0,0 +1,64 @@ +# Time Zones + +Partially because of how day night cycles work +and sometimes because of arcane rules made up +to help out farmers, we have time zones. + +This means that while it might be 9am in Boston +it would simultaneously be 10pm in Tokyo. Boston and Tokyo +are in different time zones. + +You can get access to the time zone your computer +is using with `ZoneId.systemDefault()`. This gives you +a `ZoneId` which identifies a time zone. + +```java +import java.time.ZoneId; + +class Main { + void main() { + ZoneId tz = ZoneId.systemDefault(); + + IO.println(tz); + } +} +``` + +If you want to get the identifier for a different time zone +you use `ZoneId.of` and give it a `String` identifying +the time zone. These come from [a big list](https://www.iana.org/time-zones) +published by the Internet Assigned Numbers Authority (IANA). + + +```java +import java.time.ZoneId; + +class Main { + void main() { + // Eastern Standard Time + ZoneId tz = ZoneId.of("US/Eastern"); + + IO.println(tz); + } +} +``` + +While you won't be using this directly, every `ZoneId` +has the information needed to determine what time it would +be accessible via `getRules()`. + +```java +import java.time.ZoneId; + +class Main { + void main() { + // Eastern Standard Time + ZoneId tz = ZoneId.of("US/Eastern"); + + IO.println(tz.getRules()); + } +} +``` + +And it is this machinery used by the parts of the time API which +determine the exact time it would be in a given time zone. diff --git a/src/time/zoned_date_time.md b/src/time/zoned_date_time.md new file mode 100644 index 00000000..80149bc7 --- /dev/null +++ b/src/time/zoned_date_time.md @@ -0,0 +1,59 @@ +# ZonedDateTime + +A `ZonedDateTime` has all the information of +a `LocalDateTime`, but with the addition of a time zone. + +These are useful for recording the time that events took place +in a way that can be communicated. + +```java +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.ZoneId; + +class Main { + void main() { + var jan10 = LocalDate.of(2024, 1, 10); + var tenTwentyFour = LocalTime.of(10, 24, 0); + var est = ZoneId.of("US/Eastern"); + + LocalDateTime localDateTime = LocalDateTime.of(jan10, tenTwentyFour); + ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, est); + + IO.println(zonedDateTime); + } +} +``` + +You can get the current `ZonedDateTime` for the time zone your computer is running in +with `ZonedDateTime.now()`. + +```java +import java.time.ZonedDateTime; + +class Main { + void main() { + var now = ZonedDateTime.now(); + + IO.println(now); + } +} +``` + +And you can do the same for an arbitrary time zone by giving a `ZoneId` to +`now`. + +```java +import java.time.ZonedDateTime; +import java.time.ZoneId; + +class Main { + void main() { + var now = ZonedDateTime.now(ZoneId.of("US/Eastern")); + + IO.println(now); + } +} +``` diff --git a/src/variables.md b/src/variables.md index ceb18228..3b8ed1fa 100644 --- a/src/variables.md +++ b/src/variables.md @@ -1,18 +1,19 @@ # Local Variables + + Mechanically, the next thing to cover is "variables". ```java void main() { String boss = "Jaqueline"; - System.out.println(boss); + IO.println(boss); } ``` A variable declaration has three components - the "type", the "name", and the "initial value". -```java,no_run ```java,no_run String boss = "Jaqueline"; // type name initial value @@ -26,8 +27,20 @@ hand side and you can use that name instead of the value. ```java ~void main() { -// Does the same thing as System.out.println("Jaqueline"); +// Does the same thing as IO.println("Jaqueline"); String boss = "Jaqueline"; -System.out.println(boss); +IO.println(boss); ~} ``` + +You may even use that name to give an initial value to another variable. + +```java +~void main() { +// Does the same thing as IO.println("Jaqueline"); +String boss = "Jaqueline"; +// You can use variables on the right of the = too. +String person = boss; +IO.println(person); +~} +``` \ No newline at end of file diff --git a/src/variables/challenges.md b/src/variables/challenges.md index c098748c..1001e7f6 100644 --- a/src/variables/challenges.md +++ b/src/variables/challenges.md @@ -12,11 +12,11 @@ What will this program output when run? Write down your guess and then try runni ```java,editable void main() { String mascot = "The Noid"; - System.out.println(mascot); + IO.println(mascot); mascot = "Pizza the Hut"; - System.out.println(mascot); + IO.println(mascot); mascot = "Little Caesar"; - System.out.println(mascot); + IO.println(mascot); } ``` @@ -29,18 +29,18 @@ void main() { String fruit; fruit = "apple"; - System.out.println(fruit); + IO.println(fruit); final String vegetable = "carrot"; - System.out.println(fruit); - System.out.println(vegetable); + IO.println(fruit); + IO.println(vegetable); fruit = "orange"; vegetable = "celery"; - System.out.println(fruit); - System.out.println(vegetable); + IO.println(fruit); + IO.println(vegetable); } ``` @@ -58,11 +58,12 @@ void main() { b = a; a = b; - System.out.println(a); - System.out.println(b); + IO.println(a); + IO.println(b); } ``` + ## Challenge 4 Only adding lines in the middle and without writing `"A"` or `"B"` again, @@ -73,6 +74,7 @@ B A ``` + ```java,editable void main() { String a = "A"; @@ -82,15 +84,35 @@ void main() { // You can add code here // Don't touch below this - System.out.println(a); - System.out.println(b); + IO.println(a); + IO.println(b); } ``` +To be clear: you are not allowed to write `b = "A";` or `a = "B";` + +
+ Hint 1: +

You can always make new variables.

+
+ +
+ Hint 2: +

What you need to do is make a "temporary variable" to hold one of the values before you swap them.

+
+ +
+ Solution +

+String temp = a;
+a = b;
+b = temp;
+

+
## Challenge 5 -Some of the variables in this program are named "wrong." Fix them. +Some of the variables in this program are named "wrong."[^byconvention] Fix them. ```java,editable void main() { @@ -100,4 +122,6 @@ void main() { String FASTRunner = "bolt"; String slowRunner = "tortoise"; } -``` \ No newline at end of file +``` + +[^byconvention]: By currently prevalent social conventions. None are actually "wrong" from the perspective of Java. \ No newline at end of file diff --git a/src/variables/delayed_assignment.md b/src/variables/delayed_assignment.md index 1a548b27..c701fb8d 100644 --- a/src/variables/delayed_assignment.md +++ b/src/variables/delayed_assignment.md @@ -8,7 +8,7 @@ void main() { String contestWinner; contestWinner = "Boaty McBoatface"; - System.out.println(contestWinner); + IO.println(contestWinner); } ``` @@ -33,7 +33,7 @@ void main() { String contestWinner; // This will not run, since Java knows that // you never gave contestWinner an initial value. - System.out.println(contestWinner); + IO.println(contestWinner); } ``` diff --git a/src/variables/final_variables.md b/src/variables/final_variables.md index f8dd78a7..057fbefd 100644 --- a/src/variables/final_variables.md +++ b/src/variables/final_variables.md @@ -6,7 +6,7 @@ mark a variable as "final", meaning its value can never be reassigned. ```java ~void main() { final String coolestChef = "Anthony Bourdain"; -System.out.println(coolestChef); +IO.println(coolestChef); ~} ``` @@ -16,11 +16,11 @@ If you try to reassign a final variable, Java will not accept your program. ```java,does_not_compile ~void main() { final String coolestChef = "Anthony Bourdain"; -System.out.println(coolestChef); +IO.println(coolestChef); // I'm sorry, but no. Cool guy, but no. coolestChef = "Gordan Ramsey"; -System.out.println(coolestChef); +IO.println(coolestChef); ~} ``` @@ -41,7 +41,7 @@ final String genie = "Robin Williams"; // ...... // You can still be sure "genie" // has the value of "Robin Williams" -System.out.println(genie); +IO.println(genie); ``` Variables whose assignment is delayed can also be marked final. @@ -50,7 +50,7 @@ Variables whose assignment is delayed can also be marked final. ~void main() { final String mario; mario = "Charles Martinet"; -System.out.println(mario); +IO.println(mario); ~} ``` @@ -65,7 +65,7 @@ mario = "Charles Martinet"; // But you cannot reassign it afterwards mario = "Chris Pratt"; -System.out.println(mario); +IO.println(mario); ~} ``` diff --git a/src/variables/header.png b/src/variables/header.png new file mode 100644 index 00000000..b7038a83 Binary files /dev/null and b/src/variables/header.png differ diff --git a/src/variables/inferred_types.md b/src/variables/inferred_types.md index df19bc27..214f33b8 100644 --- a/src/variables/inferred_types.md +++ b/src/variables/inferred_types.md @@ -11,7 +11,7 @@ be. // of the = is in quotes, Java knows that // it is a String. var theDude = "Lebowski"; -System.out.println(theDude); +IO.println(theDude); ~} ``` @@ -23,7 +23,7 @@ You cannot use `var` with variables whose assignment is delayed. // Java doesn't know enough to infer the type var theDude; theDude = "Lebowski"; -System.out.println(theDude); +IO.println(theDude); ~} ``` @@ -33,7 +33,7 @@ and cannot be reassigned. ```java ~void main() { final var theDude = "Lebowski"; -System.out.println(theDude); +IO.println(theDude); ~} ``` @@ -43,6 +43,6 @@ you might not be yet. There is no shame in writing out the type explicitly. ```java ~void main() { String theDude = "lebowski"; -System.out.println(theDude); +IO.println(theDude); ~} ``` diff --git a/src/variables/naming.md b/src/variables/naming.md index 2c280ee8..7c23ae45 100644 --- a/src/variables/naming.md +++ b/src/variables/naming.md @@ -11,10 +11,10 @@ String apple = "Red Delicious"; If it is multiple words, the first word should be lowercase and the others should start with a capital letter. -This convention is called `camelCase` because the capitals looks like the humps on a Camels back[^camel]. +This convention is called `camelCase` because the capitals looks like the humps on a Camels back. Just like proper formatting, sticking to this style will increase your chances of someone online being able to help you with your code. [^perry]: [Trapped! By societal convention!](https://youtu.be/CnOWLuN37D8?t=64) -[^camel]: And because doing this will increase your body's ability to retain water by ~34% + diff --git a/src/variables/reassignment.md b/src/variables/reassignment.md index 77a0451d..639b761e 100644 --- a/src/variables/reassignment.md +++ b/src/variables/reassignment.md @@ -5,9 +5,9 @@ After a variable is declared and assigned an initial value, that value can be la ```java void main() { String boss = "Jaqueline"; - System.out.println(boss); + IO.println(boss); boss = "Chelsea"; - System.out.println(boss); + IO.println(boss); } ``` @@ -26,9 +26,9 @@ the new value from that point in the program onwards. void main() { String boss = "Jaqueline"; // This will output "Jaqueline" - System.out.println(boss); + IO.println(boss); boss = "Chelsea"; // But this will output "Chelsea" - System.out.println(boss); + IO.println(boss); } ``` diff --git a/src/visibility.md b/src/visibility.md new file mode 100644 index 00000000..e299a597 --- /dev/null +++ b/src/visibility.md @@ -0,0 +1,22 @@ +# Visibility + + + +When code is all in one file, everything is "visible." This means that +if there is a method you are always allowed to call it. + +```java +class Main { + void canCallThis() { + IO.println("of course!") + } + + void main() { + canCallThis(); + } +} +``` + +And if there is a field you can read it, if there is a class you can make an instance of it, etc. + +Once we split into multiple files, you are allowed to make things less visible. diff --git a/src/visibility/accessors.md b/src/visibility/accessors.md new file mode 100644 index 00000000..2c895a55 --- /dev/null +++ b/src/visibility/accessors.md @@ -0,0 +1,61 @@ +# Accessors + +When a field is hidden that is usually because you want to control +how it might be changed. + +To access the current value of a private field you need to go through a non-private method. +If a method just provides access to a field we call that an "accessor." + +```java,no_run +class Dog { + private String name; + + Dog(String name) { + this.name = name; + } + + // The name field is private, but + // you can access it by calling the name method. + String name() { + return this.name; + } +} +``` +```java +class Main { + void main() { + var dog = new Dog("Daisy"); + + // dog.name won't work because the name field is private + // dog.name() will work because the name method is not + IO.println(dog.name()); + } +} +~class Dog { +~ private String name; +~ +~ Dog(String name) { +~ this.name = name; +~ } +~ +~ String name() { +~ return this.name; +~ } +~} +``` + +We would also consider things like the `length` method on `String`s to be "accessors."[^notthat] + +```java +void main() { + String s = "abc"; + IO.println( + // We can't see what fields underly this, + // but we can access the length + s.length() + ); +} +``` + +[^notthat]: Not that the categorization matters much, but socially we expect "accessor"-looking methods to only give you a value and not "do stuff" like +increment a number or mess with a file. \ No newline at end of file diff --git a/src/visibility/challenges.md b/src/visibility/challenges.md new file mode 100644 index 00000000..ec4a65b9 --- /dev/null +++ b/src/visibility/challenges.md @@ -0,0 +1,92 @@ +# Challenges + +Remember the rules for this are + +- Try to use only the information given up to this point in this book. +- Try not to give up until you've given it a solid attempt + +## Challenge 1 + +The following `Ratio` class should have an invariant where `denominator` +cannot be `0`. + +```java,no_run +class Ratio { + int numerator; + int denominator; + + Ratio(int numerator, int denominator) { + if (denominator == 0) { + throw new RuntimeException("Denominator cannot be zero"); + } + } + + double value() { + return ((double) numerator) / denominator; + } +} +``` + +Unfortunately other classes can directly access the `denominator` field. + +Make the denominator field private and add an accessor method for other classes to use. +You can name this accessor `.denominator()` or `.getDenominator()`. + +## Challenge 2 + +Alter the code from the last challenge such that numerator is also private and only +usable via an accessor method. + +## Challenge 3 + +Alter the code from the last challenge such that you can not only access but also +mutate the `numerator` and `denominator` fields via exposed methods. +The method for mutating the `denominator` should throw an exception +if someone tries to set it to zero. + +## Challenge 4 + +Given this `Pineapple` class make every method except `digest` private. + +```java,no_run +class Pineapple { + void digestProtein(String name) { + IO.println("Pineapple juice is digesting the proteins in " + name); + } + + void eatPork() { + digestProtein("pork"); + } + + void eatChicken() { + digestProtein("chicken"); + } + + void eatBeef() { + digestProtein("beef"); + } + + void eatYou() { + IO.println("Pineapple juice is eating you from the inside."); + } + + void digest(String thing) { + switch (thing) { + case "pork" -> eatPork(); + case "chicken" -> eatChicken(); + case "beef" -> eatBeef(); + default -> eatYou(); + } + } +} +``` + +## Challenge 5 + +Rewrite the code from the previous challenge such that the `digest` +method on `Pineapple` does the same thing but no other private methods +exist. + +Note when doing this that you wouldn't need to consider other code. +The behavior of the only exposed method does not change so those private +methods would only be "implementation details." diff --git a/src/visibility/getter_and_setters.md b/src/visibility/getter_and_setters.md new file mode 100644 index 00000000..f825d5eb --- /dev/null +++ b/src/visibility/getter_and_setters.md @@ -0,0 +1,43 @@ +# Getters and Setters + +A very silly thing you are likely to see if you dig around on the internet +is classes that look like this. + +```java +class Person { + private String name; + private int age; + + String getName() { + return this.name; + } + + void setName(String name) { + this.name = name; + } + + String getAge() { + return this.age; + } + + void setAge(String age) { + this.age = age; + } +} +``` + +So people make classes with all private fields and then for each field `thing` they +have two methods - `getThing` and `setThing`. + +To which you might immediately ask - what is the difference between this and just having non-private fields. + +```java +class Person { + String name; + int age; +} +``` + +The answer to that is interesting. We'll get to it, but the short story is that its a bit of a holdover from a very weird period in the early 2000s. + +I mention it specifically so that you know that there isn't any important information you are missing and you are not crazy. \ No newline at end of file diff --git a/src/visibility/header.png b/src/visibility/header.png new file mode 100644 index 00000000..4ae46581 Binary files /dev/null and b/src/visibility/header.png differ diff --git a/src/visibility/invariants.md b/src/visibility/invariants.md new file mode 100644 index 00000000..aa83d239 --- /dev/null +++ b/src/visibility/invariants.md @@ -0,0 +1,70 @@ +# Invariants + +Having private fields and methods is useful when you want +to maintain some invariants. + +Say you wanted a class that holds an even number. + +```java +class EvenNumberHolder { + int value; + + EvenNumberHolder(int value) { + if (value % 2 == 1) { + throw new RuntimeException(value + " is not even"); + } + + this.value = value; + } +} +``` + +You can always make the value `final` to prevent its value from being changed. + +```java +class EvenNumberHolder { + final int value; + + EvenNumberHolder(int value) { + if (value % 2 == 1) { + throw new RuntimeException(value + " is not even"); + } + + this.value = value; + } +} +``` + +But if you actually wanted to change its value that isn't enough. + +By making the field private you can know that other code has to call methods to access or +change the value. That gives you a clean place to "enforce" your invariants. + +```java +class EvenNumberHolder { + private int value; + + EvenNumberHolder(int value) { + // The constructor explicitly rejects an odd value + if (value % 2 == 1) { + throw new RuntimeException(value + " is not even"); + } + + this.value = value; + } + + // There is no way to get an odd value now - you can only + // change it in steps of two. + int value() { + return this.value; + } + + void addTwo() { + this.value += 2; + } + + void subtractTwo() { + this.value -= 2; + } +} +``` \ No newline at end of file diff --git a/src/visibility/private.md b/src/visibility/private.md new file mode 100644 index 00000000..86620ac8 --- /dev/null +++ b/src/visibility/private.md @@ -0,0 +1,2 @@ +# Private + diff --git a/src/visibility/private_constructor.md b/src/visibility/private_constructor.md new file mode 100644 index 00000000..4736b265 --- /dev/null +++ b/src/visibility/private_constructor.md @@ -0,0 +1 @@ +# Private Fields diff --git a/src/visibility/private_fields.md b/src/visibility/private_fields.md new file mode 100644 index 00000000..146f8d61 --- /dev/null +++ b/src/visibility/private_fields.md @@ -0,0 +1,25 @@ +# Private Fields + +Similar to private methods, you can also mark a field as private. + +```java +class Kaiju { + private int timesLostToGodzilla; + + Kaiju() { + this.timesLostToGodzilla = 0; + } + + void fightGodzilla() { + this.timesLostToGodzilla++; + } + + boolean isLoser() { + return this.timesLostToGodzilla > 0; + } +} +``` + +This makes it so that code in other files cannot see or change the field directly. + + diff --git a/src/visibility/private_methods.md b/src/visibility/private_methods.md new file mode 100644 index 00000000..2db9a450 --- /dev/null +++ b/src/visibility/private_methods.md @@ -0,0 +1,28 @@ +# Private Methods + +If a class has a method that it uses but that you do not want other code to see, +you can mark it `private`. + +```java,no_run +class RiceCooker { + int temperature; + + private boolean shouldTurnOff() { + return temperature > 100; + } + + void operate() { + if (shouldTurnOff()) { + turnOff(); + } + else { + // ... + } + } +} +``` + +This makes it so that code in `RiceCooker.java` can see and call the `shouldTurnOff` method but code in other files cannot. + +This is useful if you want to box up some logic but don't want to have to think about +what happens if other code calls it. \ No newline at end of file diff --git a/src/visibility/the_game.md b/src/visibility/the_game.md new file mode 100644 index 00000000..a3a82cbe --- /dev/null +++ b/src/visibility/the_game.md @@ -0,0 +1,11 @@ +# The Game + +The only reason you would care about how visible a particular method or field is +if you were playing what I call "the game[^that]" + +In the game you take some unit of code, like a class, and pretend that someone is going to +try and do every possible thing that you can do with it. + +If you have methods named `stepOne()`, `stepTwo()` and `stepThree()` - what happens if someone calls them in the wrong order. If you have a field named `denominator` - what happens if someone sets it to zero. That sort of thing. + +[^that]: Not the game that you just lost by remembering you were playing the game. \ No newline at end of file diff --git a/src/visibility_ii.md b/src/visibility_ii.md new file mode 100644 index 00000000..56494749 --- /dev/null +++ b/src/visibility_ii.md @@ -0,0 +1 @@ +# Visibility II diff --git a/theme/book.js b/theme/book.js index 0524af90..24a6c552 100644 --- a/theme/book.js +++ b/theme/book.js @@ -115,10 +115,10 @@ function playground_text(playground, hidden = true) { let text = playground_text(code_block); var params = { - release: '21', + release: '25', runtime: 'latest', action: 'run', - preview: true, + preview: false, code: text, }; diff --git a/theme/index.hbs b/theme/index.hbs index bbd3fd6a..bc69a981 100644 --- a/theme/index.hbs +++ b/theme/index.hbs @@ -138,23 +138,23 @@
{{#if print_enable}} - + {{/if}} {{#if git_repository_url}} - + {{/if}} {{#if git_repository_edit_url}} - + {{/if}} - +