Java - robbiehume/CS-Notes GitHub Wiki

Links


Java tips and tricks

  • How to make System.out.println() shorter
  • Print collection elements one by one: numList.forEach(num -> System.out.println(num))
  • Can use lombok Getter / Setter annotations to automatically create getters and or setters without having to write them out and take up code space; link
    • Can also specify @Getter(lazy = true) to do a lazy getter for static data that you only want to retrieve once and cache

Things to look into

  • How multiple files are compiled together
  • Streams
  • Lambdas
  • Functional interfaces (Function interface)
  • Method references

General

  • JVM (Java Virtual Machine) ensures the same Java code can be run on different OS and platforms
  • Java is a compiled language, the source code is transformed to byte code by a compiler before being executed by the JVM
  • Compile: javac file_name.java
    • It will compile the executable as file_name.class
  • Run: java file_name
  • Maven can also be used to build Java code; Maven notes wiki
  • Compile multiple Java source files

Naming conventions

  • camelCase for variables and methods
  • PascalCase for classes and interfaces
  • SNAKE_CASE (uppercase) for constants

Advanced

  • Local variable type inference (introduced in Java 10)
    • Allows you to create local variables without explicit type declarations (using the var keyword)
    • The compiler will figure out the types based on the variable's initial values
  • Ternary operator
    • <condition> ? <value if true> : <value if false>
    • Ex: person = age >= 18 ? "Adult" : "Child"
  • "Try-with-resources"
    • Can have a file be closed automatically, by opening it using a try statement
    • try(FileInputStream input = new FileInputStream("file.txt")) {
          ... // working with file data 
      }

JDK vs JVM vs JRE

  • https://www.digitalocean.com/community/tutorials/difference-jdk-vs-jre-vs-jvm)
  • JDK (develop / compile programs)
    • Provides all the tools, executables, and binaries required to compile, debug, and execute a Java Program
    • JDK contains JRE with Java compiler, debugger, and core classes; it's a superset of JRE
  • JVM
    • Responsible for converting the byte code to the machine-specific code
    • Provides core Java functions (mem management, garbage collection, security, etc.)
    • JVM is platform-dependent
  • JRE (run program with JVM)
    • JRE is the implementation of JVM; it provides a platform to execute Java programs

Program file structure

  • Most Java programs utilize multiple classes
  • Each class has its own file, but even though they are in different files, various classes can have access to each other
    • Can have multiple classes in a .java file, but only one public top-level class (which would need to be the same name as the file)
    • There can be as many public inner classes as you want though
  • Only one class file in an app / program needs a main() method, that's the file to run

Compiling

Packages vs Modules vs Libraries

  • Comparison overview
  • A package contains many classes, a module contains many packages, and a library contains many modules

Packages: link

  • A package in Java is a namespace used to group related classes and interfaces
  • The package name matches the directory / folder path hierarchy starting from the project root directory, with each directory separated by a .
  • Think of it as a folder in a file directory
    • Each part of a package path corresponds to a subdirectory
  • There are built-in packages (packages from the Java API), and user-defined packages
  • Package naming conventions
    • Packages are named in reverse order of domain names, i.e., org.geeksforgeeks.practice
    • The package name should be all lowercase to avoid conflict with class names (ex: package_name)
  • Each class file that's in a package must define the package on the first line

How to do specific things

  • Get user input: use Scanner class

Classes

  • Every app has a main() method: public static void main(String[] args)
  • Define a public class: public class ClassName{}
  • Public class constructor: public ClassName(){}
  • Because of constructor overloading, a class can have multiple constructors as long as they have different parameter value types
  • An object instance of a class is created with the new keyword: Car ferrari = new Car("Ferrari")
    • This creates a reference data type, which holds the value of the instance's memory address
  • In order to print out an object, it should have a toString() method that will automatically be called when the object is passed to print() or println()
  • this keyword can be used to reference attributes and methods of the current object
  • Packages:
    • A package in Java is a namespace that organizes a set of related classes and interfaces
  • Multiple & nested classes in one file

Records (Java 14 and later)

  • Records are a special type of class that's great for simple objects that will only need fields and methods to access those fields
  • All getter methods are automatically created, but you can also add additional methods
    • Instead of <record_name>.get<Record_variable>(), you call <record_name>.<record_variable>()
  • Records are immutable and can't be changed after creation
  • Ex:
       public record Account (int id, int customerId, double balance){}
    
       Account account = new Account(1, 100, 432.45);
       account.balance();  // instead of account.getBalance()

Inheritance:

  • Use extends keyword: class Triangle extends Shape
  • super() method acts like the parent constructor and is used inside the child constructor
    • Don't necessarily have to call the super() constructor, could just set the inherited attributes directly
  • If final keyword is added after a parent class method's access modifier, we disallow any child classes from changing that method
    • Ex: public final void printVar(){}

Abstraction (Abstract Classes vs Interfaces):

  • Data abstraction is the process of hiding certain details and showing only essential information to the user. Abstraction can be achieved with either abstract classes or interfaces
  • Abstract class vs interface in Java
  • Using an Interface vs. Abstract Class in Java
  • The abstract keyword is a non-access modifier, used for classes and methods:
    • Abstract class: is a restricted class that cannot be used to create objects (to access it, it must be inherited from another class)
      • It can have both abstract and regular methods
    • Abstract method: can only be used in an abstract class, and it does not have a body. The body is provided by the subclass (inherited from)
    • Abstract classes and methods can be used to achieve security, by hiding certain details and only showing the important details of an object
  • An interface is a completely "abstract class" that's used to group related methods with empty bodies
    • To access the interface methods, the interface must be "implemented" (kinda like inherited) by another class with the implements keyword (instead of extends). The body of the interface method is provided by the "implemented" class
    • In an interface, all methods are abstract
  • A class can implement multiple interfaces, but only inherit from one class
    • To implement multiple interfaces, just include a comma-separated list after the implements keyword
  • Need to add @Override keyword above any child class method that is overridden from the abstract class or interface
  • When to use each:
    • Consider using abstract classes and inheritance when our problem makes the evidence “A is a B”. For example, “Dog is an Animal”, “Lamborghini is a Car”, etc.
    • Consider using the interface when our problem makes the statement “A is capable of [doing this]”. For example, “Clonable is capable of cloning an object”, “Drawable is capable of drawing a shape”, etc.

Polymorphism:

  • Polymorphism allows a child class to share the info and behavior of its parent class while also incorporating its own functionality
  • One common use of polymorphism is overriding parent class methods in a child class and can give a single method slightly different meanings for different subclasses
    • The override method in the child class should have the same name, return type, and parameters as the parent method
  • Need to add the @Override above the child method definition
  • Can use super keyword if wanting to specifically call the parent method
    • super.methodName()
  • An important facet of polymorphism is the ability to use a child class object where an object of its parent class is expected
    • One way to do this is to explicitly instantiate a child class object as a member of the parent class:
      • Shape triangle = new Triangle();
    • Even if triangle was instantiated normally as a Triangle object, it could still be used whenever a Shape object is expected, but the explicit child as parent syntax above is most helpful when we want to declare objects in bulk
    • If explicit instantiation is used, then if the child has a method that the parent doesn't, then it won't be able to use it; i.e. it views it as if it is an instance of the parent object
  • Child classes in Arrays and ArrayLists:
    • Usually all items of an array or ArrayList need to be the same type, but polymorphism allows instances of different classes that share a parent class to be allowed to be put in the same array or ArrayList with the type being the parent object

Access modifiers (public, private, protected):

  • link1, link2
  • public: any part of the code can access it
  • protected: can also be accessed by a subclass or package member
  • private: only the class object itself can access it
  • default (no modifier provided): can be accessed by class or package

Dependency Injection (DI)

  • Dependency Injection (YouTube)
  • Dependencies are other classes (or packages) that a class needs in order to run
  • It's best to not instantiate these dependencies within the class itself that uses them
  • With dependency injection, the class gets provided with the dependency objects in its constructor
    • These dependency objects can be instantiated manually (usually in main()) or automatically by a dependency injector framework
  • DI allows for classes to be loosely coupled which means a class may be able to still partially work even if one of the dependencies are broken.
  • Also makes testing easier and allows you to test each component (class) individually, which you should do in addition to testing the program as a whole
  • Singleton pattern:
    • With a large codebase, instantiating many classes can take up a lot of heap space and lots of code lines
    • So if multiple classes use a class, you can make use of the singleton pattern where they all use the same instance of that class instead of each creating / using a new instance that takes up memory

Functional interfaces / lambda expressions

Functional interfaces: link

  • A functional interface in Java is an interface that contains only a single abstract (unimplemented) method
    • It can also contain default and static methods which do have an implementation, in addition to the single unimplemented method
  • The functional interface can be implemented by providing a definition for the abstract method in a class or lambda expression
  • Implementing it with lambdas is much cleaner and simpler because you don't have to create a class
  • Providing the lambda automatically applies it to the abstract method of the functional interface
  • Ex:
    Function<Integer, Integer> add3 = (value) -> value + 3;   // Function interface has an input type and return type
    Integer result = add3.apply(4);   // 7

Lambda expressions (anonymous methods)

  • Java Lambda Expressions Tutorial (Programiz)
  • Syntax: (p1, p2, ...) -> <return body>
    • They don't have to have a parameter though: () -> System.out.println("Lambdas are great")
    • Don't need the () if it's only one parameter: x -> x + 1
    • Can also have a lambda body with multiple lines, enclosed in {}

Methods (functions):

  • public, static, void
  • Types of argument passing: link1, link2
    • Pass by value: pass the value of the variable; func(1), func("hi")
    • Pass by reference: pass an object reference; func(obj)
      • If the object is modified in the callee function, it is reflected in the variable that was passed from the caller function
      • If a new object is assigned to the argument variable inside the callee function, then it won't be reflected in the caller function
    • Ex:
      • func(MyObject obj):
            obj.num = 1;              // will be reflected in the variable object that was passed to func()
            obj = new MyObject(2);    // won't be reflected in the variable object that was passed to func()

Varargs (passing an unspecified number of variables)

  • Can do it with ... : func(String ... values)
    • It will store all the provided arguments as an array (in this case as the values variable)
    • Valid calls: func("str1"), func("str1", "str2"), func(new String[]{"str1", "str2"}), etc.

Static methods and variables

  • static methods are methods that belong to an entire class, not a specific object of the class
    • They're called using the class name and the . operator (ex: Math.random())
  • static variables belong to the class itself instead of belonging to a specific object of the class
    • They're useful for keeping object instance counts of a class
  • static keyword usually comes after the access modifier (public, private, etc.)
  • Note: static methods can't interact with non-static instance variables
    • Additionally, the this keyword can't be used by a static method since static methods are associated with an entire class, not a specific object of that class
    • Non-static methods can interact with static variables though

Java Generics

  • Java Generics (Programiz);   Java Generics (Digital Ocean)
  • Java Generics allows us to create a single class, interface, or method that can be used with different types of data (objects), which helps us reuse code
  • Use the type parameter (usually T) to indicate that it is a generic and can accept any data type

Generics class:

  •    class GenericsClass<T> {
         private T data; // variable of T type
         public GenericsClass(T data) {this.data = data;}
         public T getData() {  // method that return T type variable
           return this.data;
         }
       }
       GenericsClass<Integer> intObj = new GenericsClass<>(5);  // need to specify the type when initializing the object 

Generics method:

  • Java tutorials link
  •    public <T> void genericsMethod(T data) {System.out.println("Data Passed: " + data);}  // imagine this being in DemoClass 
       // Imagine this part being in a Main class
       DemoClass demo = new DemoClass();
       demo.genericsMethod("Java Programming");
       demo.<Integer>genericMethod(25);  

Control flow statements

  • Any variable defined in a control statement is only accessible in that scope

For(-each) loop:

  • for (int num : numArray){       // equivalent to for (int i=0; I<numArray.length; i++) {int num = numArray[i]; ... }
        ...
    }

Switch statement

  • Switch vs if-else-if: if-else-if checks if the condition is true, while switch checks for equality
  • If the first matching statement has a break, then it will stop and exit
    • If no break in the matching case statement, it will keep going into the next cases until it hits a break;
  • Ex:
       String s = "T"
       String result;
       switch(s){
           case "T":
              result = "Tails";
              break;
           case "H":
              result = "Heads";
              break;
           default:
              result = "Error, please provide T or H";

Switch expressions (introduced in Java 12)

Built-in functions

  • Printing: System.out.println() or System.out.print()
  • .forEach(<action>): performs a provided action on each element of the iterable

Variables / Data types

  • Primitive types: int, double, boolean, char, null; Object types: String
  • Java is statically-typed; each variable type must be declared and the value must match that type
  • Variable names should be in camelCase: boolean isCamelCase = true;
  • For comparing objects, like String, always use .equals()
  • final keyword:
    • Used to define a variable value that can't be changed; final int yearBorn = 1999;
  • int vs Integer:
    • int is a primitive data type that has less flexibility
    • Integer is a wrapper class for int data type and gives us more flexibility in storing, converting, and manipulating an int data
      • Since it's a class, it has various built-in methods that are useful
    • Default value of int is 0, for Integer it's null
  • boolean vs Boolean:
    • boolean is the primitive type and therefore uses much less memory
    • Boolean is a wrapper class and give more useful methods

Strings

  • String literal: any sequence of characters enclosed in double-quotes ("")
    • String str_lit = "Hello";
  • String object: an instance of the String class
    • String str = new String("Hello");
  • String concatenation: use + operator
    • If doing a lot of repeated concatenation (such as in a for loop), StringBuilder is preferred because it will take up less memory
  • Strings are immutable objects so any string methods don't actually change a String object
  • String interning: because of this immutability, the JVM String Pool can store only one reference to multiple variables of the same string literal value
    • Creating a string object with new will always create a new object in heap memory and doesn't allow for string interning so string literals are better to use
  • String methods:
    • Get total number of characters: str.length() // 5
    • Concatenate two strings: str = str.concat(" World); // "Hello World"
      • Need to reassign str value if wanting to store concatenated string
    • Compare strings:
      • Check if equal: str.equals("Hi"); // false
      • Check if equal (ignoring case): str.equalsIgnoreCase("hello"); // true
      • Check lexicographical (dictionary) order: str.compareTo("hi"); // -4
        • 0 : the strings are equal
        • <0 : string object is lex. less than the string argument
        • >0 : string object is lex. greater than the string argument
      • Check lex. order (ignoring case): str.compareToIgnoreCase("Hi"); // -4
    • Get index of a character: str.indexOf(e); // 1
    • Get character at index: str.charAt(1); // 'e'
    • Get substring:
      • From index to end: str.substring(3); // "lo"
      • Index range: str.substring(1, 4); // "ell"
    • Uppercase / Lowercase:
      • Get all uppercase: str.toUpperCase(); // "HELLO"
      • Get all lowercase: str.toLowerCase(); // "hello"

Arrays

  • Should import Arrays package: import java.util.Arrays; if wanting to use helper methods (like .toString(), .copyOf(), etc.)
  • int[] nums = {1, 2, 3}
  • Use Arrays over ArrayLists when you know your Array will be fixed size, or if you want to use primitive data types
  • Can use an array of type Object to create an array with different data types (link)
  • Get array output: Arrays.toString(nums)
  • Creating an empty array: String[] names = new String[5]
    • Then can set each element: names[0] = "Robbie"
  • Get length of array: names.length

ArrayLists

  • ArrayLists allow us to create mutable and dynamic lists, unlike regular arrays
  • It inherits from the List interface
  • Import ArrayList package: import java.util.ArrayList;
  • It's good to instantiate it as a List for portability reasons (allows for easier changes if needing to change to a different List type later on)
  • Create ArrayList: List<Integer> ages = new ArrayList<>();
    • The < > (angle brackets) are used for generics, which are a Java construct that allows us to define classes and objects as parameters of an ArrayList
    • For that reason, can't use primitive types in an ArrayList
      • int, double, and char need to Integer, Double, or Char type inside the angle brackets
  • Create ArrayList with values: List<Integer> ages = new ArrayList<>(Arrays.asList(21, 23, 31, 44));
  • Add item:
    • Add to end: ages.add(21);
    • Add to specific index: ages.add(1, 21);
      • It keeps the order of the rest of the items, i.e. doesn't just replace the item at the index, it inserts it and any element to the right has its index value increased by 1
  • Getting an index element: ages.get(1); // gets element at index 1
  • Setting an index element: ages.set(1, 25); // sets element at index 1 to the value 25
  • Removing an element:
    • By value: ages.remove(25); // removes the first instance of the value
    • By index: ages.remove(1);
  • Get an elements index: ages.indexOf(25);
  • Get size: ages.size();

HashMap

  • import java.util.HashMap in order to use it
  • Declare: HashMap<String, Integer> str_num = new HashMap<>();
  • Add key-value pair: str_num.put('one', 1);
  • Access value: str_num.get('one');
  • Remove key-value pair: str_num.remove('one');
  • Remove all pairs: str_num.clear();
  • Get size: str_num.size();
  • Traverse HashMap: for (String key : str_num.keySet()){...
  • Additional methods: .containsKey(), .replace(), .values()

Set

  • import java.util.Set to use it; need to import the other Set class implementations if using them
  • Multiple implementations: HashSet, TreeSet, LinkedHashSet
  • Declare: Set<String> colors = new HashSet<String>();
  • Adding item: colors.add("red");
    • Any duplicate items aded will be ignored
  • Removing item: colors.remove("red");
  • Check if item is in the set: colors.contains("red");
  • Get size: colors.size();
  • Iterate through Set: for (String item : colors){...
  • Set operations:
    • Union: combine two sets; uses .addAll()
      • set1.addAll(set2); // set1 gets set2's values added to it
    • Intersection: only keep values both sets have; uses .retainAll()
      • set1.retainAll(set2); // set1 gets updated with the intersection of the two sets
    • Complement: remove any overlapping values
      • set1.removeAll(set2); // set1 will remove any values that were in set2

Optionals

  • Optionals video
  • An Optional is a container that either has something in it, or doesn't
  • The main purpose of an Optional is to explicitly tell the user of a method that the value that they're looking for might not exist, and they have to account for that possibility
  • Optionals are useful when you have a method that may return null because there's no valid object to return
    • Instead of checking if the value you got from the method is null, you can just have the method return and Optional and check the Optional if it contains a value or not
  • Can think of Optionals as box, where instead of returning the Cat object itself or null, if it retrieves a valid Cat then it puts it in the box (Optional) and returns it and if not it returns the empty box (Optional). Then the code processing the returned value can just "ask" the Optional if it has the value or not and then take different action depending on if it does or not
  • Syntax: Optional<Class>
  • To create an Optional, do Optional.ofNullable(<object to make Optional>); ex: Optional.ofNullable(String)
  • Check if the Optional contains the object: optionalObject.isPresent() // true if contains object, false if contains null
    • If it's true, can then get the object with optionalObject.get()
  • A better way is to use the .orElse() method
    • This will return the object if the Optional contains it, and if not then it will return the value that you return as a parameter
    • Ex: Cat myCat = optionalCat.orElse(new Cat("Unknown", 0)) // Cat accepts a name and age parameter
  • Convert Optional from one type to another: .map()
    • Can pass in a method to .map(), which it will apply to the Optional to get a different value
    • ex: optionalCat.map(Cat::getAge) // this returns an Optional containing the result of calling the getAge() on the Cat object contained inside optionalCat

Memory management / garbage collector

  • An object will be in memory until there are no longer any variables referencing it
  • ? You can also set the variable to null and the garbage collector will take care of it ?

Errors & exception handling

Try/catch:

  • Code in the try block will run and if an error occurs the catch block can be executed
  • Ex:
       try {
           <statement to try to run>
       } catch (Exception e) {
           <statement to run if the Exception occurs>
       }
  • Can specify specific exceptions to try to catch (e.g. catch (ArrayIndexOutOfBoundsException e))
    • Can also have multiple catch statements for different exceptions
  • Can add a finally block at the end that will get executed no matter what
  • Can use the throw keyword to purposefully invoke an exception (e.g. throw new IOException("Only supported for index 0 to 10");)

Streams

  • Streams tutorial article
  • Java Streams gives us a set of functions that we can perform on certain data structures
  • They allow us to quickly and conveniently perform bulk operations on them
  • Also allow us to not have to write extensive for-loops
  • Streams themselves aren't a data structure, nor do they modify the underlying data structure they're operating on
  • Stream flow: source stream --> intermediate function 1 --> int func 2 --> ... --> Terminal operation
    • Ex: stream --> filter() --> sorted() --> collect()
  • Stream intermediate functions:
    • filter(): allows you to get only items that meet a specific criteria
      • Ex: peopleList.stream().filter(person -> person.age > 18).collect(Collectors.toList()) // get List of all people older than 18
    • sorted(): sort the data by a specified field
      • Ex: peopleList.stream().sorted(Comparator.comparing(p -> p.age)).collect(Collectors.toList()) // sort by lowest to highest age
  • Stream terminal operation:
    • collect(): converts the manipulated stream data to a collection
    • min() / max(): get min or max value of the stream
    • reduce(): combines all the stream values to a single value according to a provided accumulation method (Integer::sum, String::concat, etc.)
      • Can specify the starting value as the first parameter, if not it will infer it and return it as an Optional object
      • Ex: int result = numList.stream().reduce(0, Integer::sum) // if 0 not provided, it would return an Optional instead of an int
      • Can also pass your own lambda (the first
        • Ex: Optional<String> s = strList.stream().reduce((str1, str2) -> str1 + "-" + str2) // ['a', 'b', 'c'] --> 'a-b-c' (after converting Optional to String)
  • Can chain stream functions together:
    • EX: peopleList.stream().filter(p -> p.age > 18).sorted(Comparator.comparing(p -> p.age)).collect(Collectors.toList())
  • Be cognizant of the order you chain them. For example, if you're filtering and sorting, then you should filter first so that you can decrease the sorting runtime

Logging

  • Log4j

Databases

  • JDBC
  • ORM
    • JPA, Hibernate, Spring Data JPA

Testing

  • JUnit
⚠️ **GitHub.com Fallback** ⚠️