Who is this article for? The material is designed for developers with little experience with , and the main reason is to refresh their knowledge. Java Table of Contents: Variables and types It will be better to use these Switch and case. Try, catch, and finally. Lambdas, streams, optional? Record class Variables and types. Let’s start with the general and important things that you really should have learned earlier. Java, like many languages, has two types of variables: Primitives: Non-Primitive: Classes and arrays! Usually, we need to compare variables, but what methods should we use? “equals” or “== “ ? Let’s look at all the comparison methods available to us: == equal to != not equal to > greater than >= greater than or equal to < less than <= less than or equal to This tricky question was asked in the interview. We can use it with classes, but: // Integer != int int a = 1, b = 1; System.out.println(a == b); // true int c = 1, j = c; System.out.println(c == j); // true Integer ai = Integer.valueOf(1), bi = Integer.valueOf(1); System.out.println(ai == bi); // true Integer ci = Integer.valueOf(128), ji = Integer.valueOf(128); System.out.println(ci == ji); // false /* You can always count on the fact that * for values between -128 and 127, * you get the identical Integer objects after autoboxing, * and on some implementations you might get identical objects * even for higher values. */ It’s important to learn that the Integer class is not a primitive value because it is a regular class. Class is the root of the class hierarchy. Every class has as a superclass. All objects, including arrays, implement the methods of this class. Object Object The “==” operator compares the values of two object references to see if they refer to the same thing. For an object, we should use “equals”: // Indicates whether some other object is "equal to" this one. public boolean equals(Object obj) Or interface for natural ordering (if you need to sort your collection with your own type of object): Comparable public class HugePoint implements Comparable<HugePoint> { // same as before @Override public int compareTo(HugePoint otherHugePoint) { return Integer.compare(getSize(), otherHugePoint.getSize()); } } The interface defines a method: Comparator compare(arg1, arg2) public class HugePointComparator implements Comparator<HugePoint> { @Override public int compare(HugePoint firstPoint, HugePoint secondPoint) { return Integer.compare(firstPoint.getSize(), secondPoint.getSize()); } } It’s easy to use Java 8+ this way: Comparator<Person> byAge = Comparator.comparing(Person::getAge); Comparator byAge = (Person person1, Person person2) -> Integer.compare(person1.getAge(), person2.getAge()); Which one do you need to use and when? I would recommend using “Comparable” interface for ordering (if you have a sorting method, etc.), and “Comparator” if you need to compare objects by a special field. It will be better to use these Date and time Why? For the current timestamp, just use . We don’t need to convert to milliseconds. Instant.now() // Instant class, one of the main classes of the Date-Time API, encapsulates a point on the timeline. Instant.now(); Instant now = Instant.parse("2021-02-09T11:19:42.12Z"); Instant before = now.minus(Duration.ofDays(1)); For the local date format use the following: // LocalDateTime is an immutable date-time object // that represents a date-time, often viewed // as year-month-day-hour-minute-second. LocalDateTime.now(); LocalDateTime.of(2001, Month.FEBRUARY, 10, 03, 01); UUID // uuid UUID uuid = UUID.randomUUID(); UUID uuid = new UUID(mostSignificant64Bits, leastSignificant64Bits); Random // random for int int randomWithMathRandom = (int) ((Math.random() * (max - min)) + min); // or easy way Random random = new Random(); int randomWithNextInt = random.nextInt(); Switch and case. Try, catch, and finally When we have too many conditions, and they are all tied to the same type of variable in “if-then-else,” it is worth using a switch-case. // case int expression = 9; switch(expression) { case 2: System.out.println("Small Size"); break; case 3: System.out.println("Large Size"); break; // default case default: System.out.println("Unknown Size"); } Also, up Java to version 19 or more, and we can use the expression: Day day = Day.WEDNESDAY; var resultDay = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> throw new IllegalStateException("Invalid day: " + day); } System.out.println(resultDay); Don’t forget to use try/catch with methods that can throw exceptions. Try/catch and try with resources: // old public int find(String playerFile) { try { Scanner contents = new Scanner(new File(playerFile)); return Integer.parseInt(contents.nextLine()); } catch (FileNotFoundException noFile) { throw new IllegalArgumentException("Couldn't find the file"); } } // with resources public int find(String playerFile) { try (Scanner contents = new Scanner(new File(playerFile))) { return Integer.parseInt(contents.nextLine()); } catch (FileNotFoundException e ) { logger.warn("Couldn't find the file"); return 0; } } // don't do it! public int find(String playerFile) { try { // action } catch (Exception e) {} // <== catch and swallow return 0; } Use , especially with channels: finally // close it! public int find(String playerFile) throws FileNotFoundException { Scanner contents = null; try { contents = new Scanner(new File(playerFile)); return Integer.parseInt(contents.nextLine()); } finally { if (contents != null) { contents.close(); } } } // Always close it! public int find(String playerFile){ MessageChannel channel = buildChannel(port); try { // do some action with channel } finally { channel.close(); // close! } } Lambdas, streams, optional? The main reason for switching to Java 8 and higher is to be able to work with threads, streams, and functional interfaces. In this article, we will cover only working with streams, and we will deal with threads on the next page. First point — collections: Also, it would be good to go through the algorithms’ complexity table. I strongly recommend using this link: or the full article . gist here Let’s analyze the two approaches and see the difference. // old way: List<String> old = new ArrayList<>(); old.add("one"); // new way: List<String> list = Arrays.asList("one", "two"); List<String> list = List.of("one", "two", "three"); Set<String> set = Set.of("one", "two", "three"); We can simplify working with the map. // old: HashMap<Integer, String> hm = new HashMap<Integer, String>(); hm.put(1, "foo"); // new: Map<String, String> map = Map.of("foo", "one", "bar", "two"); As you know, we have only two types of operations: and operations. intermediate terminal // Intermediate Operations var stream = ...; stream.filter(); stream.map(); stream.flatMap(); stream.distinct(); stream.sorted(); stream.peek(); stream.limit(); stream.skip(); // Terminal stream.reduce(); stream.forEach(); stream.min(); stream.max(); stream.anyMatch(); stream.allMatch(); stream.noneMatch(); stream.findAny(); stream.findFirst(); stream.toArray(); stream.collect(); stream.count(); stream.forEachOrdered(); Just code: // Java 7 way ArrayList results = new ArrayList(); for (Score score: scores) { if (score.getVal() > 10) { results.add(score.getVal()); } } System.out.println(results); // Java 8+ way ArrayList results = scores.stream() .filter(score -> score.getVal() > 10) .collect(Collectors.toList()); System.out.println(results); If you have a duplicate code, just use functional interfaces: // Functional Consumer<Integer> consumer = (n) -> { System.out.println(n); }; numbers.forEach(consumer); Throwing exceptions every time an object is not found is not a good idea; you should use the “Optional” class for this: var empty = Optional.empty(); empty.isPresent(); // false var notEmpty = Optional.of(name); notEmpty.isPresent(); // true String name = null; Optional<String> opt = Optional.ofNullable(name); opt.isPresent(); // false String name = null; String result = Optional.ofNullable(nullName).orElse("Default"); // get result var name = Optional.of("Name"); name.get(); And if you really need to throw an exception, you should use “orElseThrow”: String name = Optional.ofNullable(name) .orElseThrow(IllegalArgumentException::new); // throw NoSuchElementException.class Optional.ofNullable(name).orElseThrow(); Record classes A typical situation: API had been written with “none thread safe” methods, and we didn’t know about the magic inside the library. What exactly do I mean? Any variables are mutable by default, which means we can change the value they hold. We don’t have read-only methods for classes inside; we can change them inside any threads: public class TestObject { private String string; // public contructor // getters and setters } In action: // create new object ExecutorService executor = Executors.newFixedThreadPool(10); var testObject = new UsefulObject(); executor.submit(() -> testObject.setTestString("test_1")); executor.submit(() -> testObject.setTestString("test_2")); executor.submit(() -> testObject.setTestString("test_3")); executor.submit(() -> testObject.setTestString("test_4")); And what about the result? // the result might be anything! // test_1 or test_2 // or test_3 System.out.println(testObject.getTestString()); . They are immutable classes and are implicitly classes, which means they can’t be extended. Just use a record class instead of a class: Record classes are special classes that act as transparent carriers for immutable data final record MainObject(int x) { } The compiler implicitly provides the constructor, getters, & , and . equals hashCode toString public final class MainObject { private final int x; public Rectangle(int x) { this.x = x; } double x() { return this.x; } // Implementation of equals() and hashCode(), which specify // that two record objects are equal if they // are of the same type and contain equal field values. public boolean equals... public int hashCode... // An implementation of toString() that returns a string // representation of all the record class's fields, // including their names. public String toString() {...} } If we need to share the class now, we can do it without synchronization. That’s it for the first part! Thank you, and good luck! Notes Java primitives https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html Class object https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html How we can work with collections after Java 7: https://docs.oracle.com/javase/8/docs/technotes/guides/collections/overview.html Java intermediate and terminal operation: https://javaconceptoftheday.com/java-8-stream-intermediate-and-terminal-operations/