Java Flashcards
Private Methods in Java Interfaces?
Private methods can be implemented static or non-static. This means that in an interface we are able to create private methods to encapsulate code from both default and static public method signatures.
static void buzz() {
System.out.print(“Hello”);
staticBaz();
}
private static void staticBaz() { System.out.println(" static world!"); } default void bar() { System.out.print("Hello"); baz(); } private void baz() { System.out.println(" world!"); } }
Benefits of Private Methods in Interfaces
Let’s talk about the benefits of private methods now that we have defined them.
interfaces are able to use private methods to hide details on implementation from classes that implement the interface. As a result, one of the main benefits of having these in interfaces is encapsulation.
Another benefit is hat there is less duplication and more re-usable code added to interfaces for methods with similar functionality.
What’s an Immutable Object?
An immutable object is an object whose internal state remains constant after it has been entirely created.
This means that the public API of an immutable object guarantees us that it will behave in the same way during its whole lifetime.
If we take a look at the class String, we can see that even when its API seems to provide us a mutable behavior with its replace method, the original String doesn’t change
Differences between final and immutability
- Final means that you can’t change the object’s reference to point to another reference or another object, but you can still mutate its state (using setter methods e.g). Whereas immutable means that the object’s actual value can’t be changed, but you can change its reference to another one.
- final modifier is applicable for variable but not for objects, Whereas immutability applicable for an object but not for variables.
- By declaring a reference variable as final, we won’t get any immutability nature, Even though reference variable is final. We can perform any type of change in the corresponding Object. But we can’t perform reassignment for that variable.
- final ensures that the address of the object remains the same whereas the Immutable suggests that we can’t change the state of the object once created.
What is a functional interface?
A functional interface is an interface that contains only one abstract
method. In other words, a functional interface contains only one method that is not implemented. So, a functional interface wraps a function as an interface and the function is represented by a single abstract method on the interface.
Optionally, besides this abstract method, a functional interface can have default and/or static methods as well.
Collections versus streams
- Conceptual differences: The main difference between collections and streams consists of the fact that they are conceptually two different things. While collections are meant to store data (for example, List, Set, and Map), streams are meant to apply operations (for example, filtering, mapping, and matching) on that data. In other words, streams apply complex operations on a view/source represented by data stored on a collection. Moreover, any modification/change performed on a stream is not reflected in the original collection.
- Data modification: While we can add/remove elements from a collection, we cannot add/remove elements from a stream. Practically, a stream consumes a view/ source, performs operations on it, and returns a result without modifying the view/ source.
- Iteration: While a stream consumes a view/source, it automatically and internally performs the iteration of that view/source. The iteration takes place depending on the chosen operations that should be applied to the view/source. On the other hand, collections must be iterated externally.
- Traversal: While collections can be traversed multiple times, streams can be traversed only once. So, by default, Java streams cannot be reused. Attempting to traverse a stream twice will lead to an error reading Stream has already been operated on or closed.
- Construction: Collections are eagerly constructed (all the elements are present right from the beginning). On the other hand, streams are lazily constructed (the so-called intermediate operations are not evaluated until a terminal operation is invoked).
What is the difference between intermediate and terminal operations?
The distinction between this operations is that an intermediate operation is lazy while a terminal operation is not.
When you invoke an intermediate operation on a stream, the operation is not executed immediately.
It is executed only when a terminal operation is invoked on that stream.
In a way, an intermediate operation is memorized and is recalled as soon as a terminal operation is invoked. You can chain multiple intermediate operations and none of them will do anything until you invoke a terminal operation. At that time, all of the intermediate operations that you invoked earlier will be invoked along with the terminal operation.
Terminal operations produces a non-stream (cannot be chained) result such as primitive value, a collection or no value at all.
Map vs Flatmap?
The main difference between map() and flatMap() is that map() transforms each element of a stream into a single element of a new stream, while flatMap() transforms each element of a stream into a stream of multiple elements.
What are the main differences between Iterator and Spliterator?
Iterator was created for the Collection API, while Spliterator was
created for the Stream API.
An Iterator can traverse the elements of a collection only via hasNext()/next() because it doesn’t have a size. On the other hand, a Spliterator can provide the size of the collection either by approximating it via estimateSize() or exactly via getExactSizeIfKnown().
A Spliterator can use several flags for internally disabling unnecessary operations (for example, CONCURRENT, DISTINCT, and IMMUTABLE). An Iterator doesn’t have such flags.
What is the Optional class?
Optional is intended to provide a limited mechanism for library method return types where there needed to be a clear way to represent no result, and using null for such was overwhelmingly likely to cause errors.
What is a Scoped Value?
Scoped values are a form of implicit method parameters that allow one or more values (i.e., arbitrary objects) to be passed to one or more remote methods without having to add them as explicit parameters to each method in the call chain.
Scoped values are usually created as public static fields, so they can be retrieved from any method.
If multiple threads use the same ScopedValue field, then it may contain a different value from the point of view of each thread.
If you are familiar with ThreadLocal variables, this will sound familiar. In fact, scoped values are a modern alternative for thread locals.
What Is the Difference Between ScopedValue and ThreadLocal?
They are only valid during the lifetime of the Runnable passed to the run(…) method, and they are released for garbage collection immediately afterward (unless further references to them exist). A thread-local value, on the other hand, remains in memory until either the thread is terminated (which may never be the case when using a thread pool) or it is explicitly deleted with ThreadLocal.remove(). Since many developers forget to do this (or don’t do it because the program is so complex that it’s not obvious when a thread-local value is no longer needed), memory leaks are often the result.
A scoped value is immutable – it can only be reset for a new scope by rebinding, as mentioned above. This improves the understandability and maintainability of the code considerably compared to thread locals, which can be changed at any time using set().
The child threads created by StructuredTaskScope have access to the scoped value of the parent thread. If, on the other hand, we use InheritableThreadLocal, its value is copied to each child thread so that a child thread cannot change the thread local value of the parent thread. This can significantly increase the memory footprint.
What is Unstructured Concurrency?
means that our tasks run in a web of tangled threads whose start and end is hard to see in the code. Clean error handling is usually not present, and orphaned threads often occur when a control structure (in the example above: the createInvoice(…) method) ends
What Are Virtual Threads?
Virtual threads solve the problem in a way that again allows us to write easily readable and maintainable code. Virtual threads feel like normal threads from a Java code perspective, but they are not mapped 1:1 to operating system threads.
Instead, there is a pool of so-called carrier threads onto which a virtual thread is temporarily mapped (“mounted”). As soon as the virtual thread encounters a blocking operation, the virtual thread is removed (“unmounted”) from the carrier thread, and the carrier thread can execute another virtual thread (a new one or a previously blocked one).
The carrier thread pool is a ForkJoinPool – that is, a pool where each thread has its own queue and “steals” tasks from other threads’ queues should its own queue be empty. Its size is set by default to Runtime.getRuntime().availableProcessors() and can be adjusted with the VM option jdk.virtualThreadScheduler.parallelism.
What Are Annotations? What Are Their Typical Use Cases?
Annotations are metadata bound to elements of the source code of a program and have no effect on the operation of the code they operate.
Their typical uses cases are:
Information for the compiler – with annotations, the compiler can detect errors or suppress warnings
Compile-time and deployment-time processing – software tools can process annotations and generate code, configuration files, etc.
Runtime processing – annotations can be examined at runtime to customize the behavior of a program