Hello friend! How much time does it take to become a developer? I asked many different people and heard many different answers. For something and someone, a month may be enough, but for someone, even a year will not be enough. But what I do know for sure is that becoming a Java developer is a thorny and long road, regardless of your initial ability. After all, it is not so much ability that matters, but obstinacy and diligence. Analysis of questions and answers from interviews for a Java developer.  Part 16 - 1Therefore, today we still purposefully continue to analyze the most popular questions from interviews for a Java developer. Their study will gradually bring you closer to your cherished goal. Let's get started!

17. Give examples of good and bad use of Optional

Suppose we have some series of values ​​that we pass through with a stream, and as a result we get some Optional as a result:
Optional<String> stringOptional = Stream.of("a", "ab", "abc", "abcd")
   .filter(str -> str.length() >= 3)
   .findAny();
We, as expected, need to get a value from this Optional . Just using get() is a bad way:
String result = stringOptional.get();
But this method should get the value from Optional and return it to us, right? This, of course, is true, but if it has a value. Well, if the values ​​in the stream were different, and as a result we got an empty Optional , when trying to take a value from it using the get() method , it will be thrown: Analysis of questions and answers from interviews for a Java developer.  Part 16 - 2Which is not good. In this case, it is better to use the constructions:
  1. String result = null;
    if (stringOptional.isPresent()) {
     stringOptional.get();
    }

    In this case, we are checking to see if the element is in the Optional . If not, the resulting string has its old value.

  2. String result = stringOptional.orElse("default value");

    In this case, we specify some default value that will be set to the resulting string in the case of an empty Optional .

  3. String result = stringOptional.orElseThrow(() -> new CustomException());

    In this case, we ourselves throw an exception with an empty Optional .

This comes in handy in an application when, for example, the Spring JPA method findById() is used , which returns Optional values. In this case, with this method we are trying to take the value, and if it does not exist, we throw some Runtime exception, which is processed at the controller level using the ExceptionHandler and converted into an HTTP response with a 404 - NOT FOUND status . Analysis of questions and answers from interviews for a Java developer.  Part 16 - 3

18. Is it possible to declare the main method as final?

Yes, of course, nothing prevents us from declaring the main() method as final . The compiler will not throw errors. But it is worth remembering that any method after declaring it as final will become the last method - not overridden. Although, who will override main ??? Analysis of questions and answers from interviews for a Java developer.  Part 16 - 4

19. Is it possible to import the same package/class twice? What could be the consequences?

Yes, you can. Consequences? We will have a couple of unnecessary imports that Intelijj IDEA will display as gray, i.e. unused. Analysis of questions and answers from interviews for a Java developer.  Part 16 - 5Analysis of questions and answers from interviews for a Java developer.  Part 16 - 6

20. What is Casting? When can we get a ClassCastException?

Casting, or type casting , is the process of converting one data type to another data type: manually (implicit cast) or automatically (explicit type casting). Analysis of questions and answers from interviews for a Java developer.  Part 16 - 7The automatic conversion is performed by the compiler, while the manual conversion is performed by the developer. Type casting for primitives and classes is somewhat different, so we will consider them separately. Primitive types An example of automatic casting of primitive types:
int value = 17;
double convertedValue = value;
As you can see, no additional manipulations besides the = sign are needed here. An example of manual casting of primitive types:
double value = 17.89;
int convertedValue = (int)value;
In this case, we can observe a manual cast, which is implemented using (int) , in which case the comma part will be discarded, and the convertedValue will have a value of - 17. Read more about casting primitive types in this article . Now, let's move on to objects. Reference types For reference types, automatic casting is possible for descendant classes to parent classes. This is also called polymorphism . Let's say we have a Lion class that inherits from a Cat class . In this case, the automatic conversion will look like this:
Cat cat = new Lion();
But with an explicit cast , everything is somewhat more complicated, because there is no functionality for cutting off the excess, like primitives. And by simply explicitly converting the view:
Lion lion= (Lion)new Cat();
You will get an error: Analysis of questions and answers from interviews for a Java developer.  Part 16 - 8In fact, you can add methods to the Lion successor class that were not originally in the Cat class , and then try to call them, because the object type will become Lion . Well, there is no logic in this. Therefore, type narrowing is only possible when the original object is of type Lion , but was later cast to the parent class:
Lion lion = new Lion();
Cat cat = lion;
Lion newLion = (Lion)cat;
Also, for greater reliability, narrowing casts for objects is recommended using the instanceOf construct :
if (cat instanceof Lion) {
 newLion = (Lion)new Cat();
}
Read more about reference type casts in this article .

21. Why do modern frameworks mostly use only unchecked exceptions?

I think this is all because the handling of checked exceptions is still that spaghetti code that is repeated everywhere, while not really needed in all cases. Analysis of questions and answers from interviews for a Java developer.  Part 16 - 9In such cases, it is easier to do the processing inside the framework, so as not to once again shift it onto the shoulders of developers. Yes, of course, an emergency situation can arise, but these very uncheked exceptions can be handled in a more convenient way, without bothering with processing in try-catch and without passing further through the methods. It is enough just to convert the exception into some HTTP response in the exceptionHandler .

22. What is static import?

When using static data (methods, variables), you can not create the object itself, but do it by the class name, but in this case, we need a reference to the class. With it, everything is simple: it is added using normal imports. But what if we go to use a static method without writing the class name, as if it were a static method of the current class? This is possible with static imports! In this case, we must write static import and a link to that method. Like, for example, a static method of the Math class for calculating the cosine value:
import static java.lang.Math.cos;
As a result, we can use the method without specifying the class name:
double result = cos(60);
Also elementarily, we can load all the static methods of the class at once using static import:
import static java.lang.Math.*;
Analysis of questions and answers from interviews for a Java developer.  Part 16 - 10

23. What is the relationship between hashCode() and equals() methods?

According to Oracle , there is the following rule: If two objects are equal (i.e. equals() returns true ), they must have the same hashcode. At the same time, do not forget that two different objects can have the same hash code. To understand why equals() and hashCode() are always overridden as a pair, consider the following cases:
  1. Both methods are overridden.

    In this case , two different objects with the same internal states will return true for equals () , while hashCode() will both return the same number.

    It turns out that everything is OK, because the rule is being fulfilled.

  2. Both methods are not overridden.

    In this case, two different objects with the same internal states will return false with equals() , since the comparison is by reference through the == operator .

    The hashCode() method will also return different values ​​(most likely) since it returns the converted value of the address of the memory location. But for the same object, this value will be the same, just as equals() in this case will return true only when the references point to the same object.

    It turns out, and in this case everything is OK and the rule is fulfilled.

  3. Override equals() , not override hashCode() .

    In such a case, for two different objects with the same internal states, equals() will return true , and hashCode() will (most likely) return different values.

    There is a violation of the rule, so it is not recommended to do this.

  4. Not overridden equals() , overridden hashCode() .

    In this case, for two different objects with the same internal states, equals() will return false , and hashCode() will return the same values.

    There is a violation of the rule, so the approach is wrong.

As you can see, the execution of the rule is only possible when equals() and hashCode() are both overridden, or both are not overridden at all. Read Analysis of questions and answers from interviews for a Java developer.  Part 16 - 11more about equals() and hashCode() in this article .

24. When are the BufferedInputStream and BufferedOutputStream classes used?

An InputStream is used to read data from a resource byte by byte, and an OutputStream is used to write it byte by byte. But byte-by-byte operations can be very inconvenient and require additional processing (in order to read / write texts normally). Actually, to simplify such byte records, we introduced BufferedOutputStream , and for reading BufferedInputStream . These classes are nothing more than buffers that accumulate data, allowing you to work with data not byte by byte, but whole data packets (arrays). When creating a BufferedInputStream , it takes an instance of the InputStream type in the constructor , from which the data is read:
BufferedInputStream bufferedInputStream = new BufferedInputStream(System.in);
byte[] arr = new byte[100];
bufferedInputStream.read(arr);
System.in is an object of type InputStream that reads data from the console. That is, using this BufferedInputStream object , we can read data from the InputStream , writing it to the passed array. It turns out a kind of wrapper for the InputStream class . The arr array from this example is the array that gets the data from the BufferedInputStream . That, in turn, reads data from the InputStream with another array, which by default has a size of 2048 bytes. Similarly for BufferedOutputStream : an instance of the OutputStream type must be passed to the constructor, in which we will write data in whole arrays:
byte[] arr = "Hello world!!!".getBytes();
BufferedOutputStream bufferedInputStream = new BufferedOutputStream(System.out);
bufferedInputStream.write(arr);
bufferedInputStream.flush();
System.out is an object of type OutputStream that writes data to the console. The flush() method sends data from the BufferedOutputStream to the OutputStream , while clearing the BufferedOutputStream . Without this method, nothing will be recorded. And similar to the previous example: arr is an array from which data is written to the BufferedOutputStream . From there, they are written to the OutputStream in another array, which by default has a size of 512 bytes. Read more about these two classes in the article .

25. What is the difference between the java.util.Collection and java.util.Collections classes?

Collection is an interface that is the head of the collection hierarchy. It represents classes that allow you to create, contain and modify entire groups of objects. Many methods are provided for this, like add() , remove() , contains() and others. The main interfaces of the Collection class are:
  • Set is an interface that describes a set that contains unordered unique (non-repeating) elements.

  • List is an interface that describes a data structure that stores an ordered sequence of objects. These objects get their own index (number), using which you can interact with them: take, delete, change, overwrite.

  • Queue is an interface that describes a data structure with storing elements in the form of a queue that follows the rule - FIFO - First In First Out .

Analysis of questions and answers from interviews for a Java developer.  Part 16 - 12Learn more about Collection . Collections is a utility class that provides a variety of utility methods. For example:
  • addAll(Collection<? super T> collection, T...element) - adds the passed elements of type T to the collection .

  • copy(List<? super T> dest, List<? extends T> src) - copies all elements from list src to list in dest .

  • emptyList() - returns an empty list.

  • max(Collection<? extends T> collection, Comparator<? super T> comp) - Returns the maximum element of this collection according to the order specified by the specified comparator.

  • unmodifiableList(List<? extends T> list) - Returns an immutable representation of the given list.

And there are a lot of such various convenience methods in Collections . Analysis of questions and answers from interviews for a Java developer.  Part 16 - 13A complete list of these methods can be found on the Oracle website . I didn't say they are comfortable. After all, they are all static. That is, you do not need to create an object of this class each time in order to call the necessary method on it. You just need to write the name of the class, call the desired method on it and pass all the required arguments. To sum it up, Collection is the root interface of the collections framework. Collections is an auxiliary class for more convenient handling of objects belonging to a type from the collections structure. Well, that's all for today. All the best!Analysis of questions and answers from interviews for a Java developer.  Part 16 - 14 Analysis of questions and answers from interviews for a Java developer.  Part 16 - 15