JavaRush /Java Blog /Random EN /Analysis of questions and answers from interviews for Jav...

Analysis of questions and answers from interviews for Java developer. Part 14

Published in the Random EN group
Firework! The world is constantly moving and we are constantly moving. Previously, in order to become a Java developer, it was enough to know a little Java syntax, and the rest would come. Over time, the level of knowledge required to become a Java developer has grown significantly, as has competition, which continues to push the lower bar of required knowledge upward. Analysis of questions and answers from interviews for Java developer.  Part 14 - 1If you really want to become a developer, you need to take it for granted and prepare thoroughly to stand out among beginners like you. What we will do today, namely, we will continue to analyze the 250+ questions . In previous articles, we examined all junior level questions, and today we will take on middle level questions. Although I note that these are not 100% middle-level questions, you can meet most of them at a junior-level interview, because it is at such interviews that a detailed probing of your theoretical base takes place, while for a middle student the questions are more focused on probing his experience . Analysis of questions and answers from interviews for Java developer.  Part 14 - 2But, without further ado, let's get started.

Middle

Are common

1. What are the advantages and disadvantages of OOP when compared to procedural/functional programming?

There was this question in the analysis of questions for Juinior, and accordingly I already answered it. Look for this question and its answer in this part of the article, questions 16 and 17.

2. How does aggregation differ from composition?

In OOP, there are several types of interaction between objects, united under the general concept of "Has-A Relationship". This relationship indicates that one object is a component of another object. At the same time, there are two subtypes of this relationship: Composition - one object creates another object and the lifetime of another object depends on the lifetime of the creator. Aggregation - an object receives a link (pointer) to another object during the construction process (in this case, the lifetime of the other object does not depend on the lifetime of the creator). For better understanding, let's look at a specific example. We have a certain car class - Car , which in turn has internal fields of the type - Engine and a list of passengers - List<Passenger> , it also has a method for starting movement - startMoving() :
public class Car {

 private Engine engine;
 private List<Passenger> passengers;

 public Car(final List<Passenger> passengers) {
   this.engine = new Engine();
   this.passengers = passengers;
 }

 public void addPassenger(Passenger passenger) {
   passengers.add(passenger);
 }

 public void removePassengerByIndex(Long index) {
   passengers.remove(index);
 }

 public void startMoving() {
   engine.start();
   System.out.println("Машина начала своё движение");
   for (Passenger passenger : passengers) {
     System.out.println("В машине есть пассажир - " + passenger.getName());
   }
 }
}
In this case, the Composition is the connection between Car and Engine , since the performance of the car directly depends on the presence of the engine object, because if engine = null , then we will receive a NullPointerException . In turn, an engine cannot exist without a machine (why do we need an engine without a machine?) and cannot belong to several machines at one point in time. This means that if we delete the Car object, there will be no more references to the Engine object , and it will soon be deleted by the Garbage Collector . As you can see, this relationship is very strict (strong). Aggregation is the connection between Car and Passenger , since the performance of Car in no way depends on objects of the Passenger type and their number. They can either leave the car - removePassengerByIndex(Long index) or enter new ones - addPassenger(Passenger passenger) , despite this, the car will continue to function properly. In turn, Passenger objects can exist without a Car object . As you understand, this is a much weaker connection than we see in the composition. Analysis of questions and answers from interviews for Java developer.  Part 14 - 3But that's not all, an object that is connected by aggregation to another can also have a given connection with other objects at the same point in time. For example, you, as a Java student, are enrolled in English, OOP and logarithms courses at the same point in time, but at the same time you are not a critically necessary part of them, without which normal functioning is impossible (such as a teacher).

3. What GoF patterns have you used in practice? Give examples.

I have already answered this question before, so I’ll just leave a link to the analysis , see the first question. I also found a wonderful cheat sheet article on design patterns, which I highly recommend keeping on hand.

4. What is a proxy object? Give examples

A proxy is a structural design pattern that allows you to substitute special substitute objects, or in other words, proxy objects, instead of real objects. These proxy objects intercept calls to the original object, allowing some logic to be inserted before or after the call is passed to the original. Analysis of questions and answers from interviews for Java developer.  Part 14 - 4Examples of using a proxy object:
  • As a remote proxy - used when we need a remote object (an object in a different address space) that needs to be represented locally. In this case, the proxy will handle connection creation, encoding, decoding, etc., while the client will use it as if it were the original object located in local space.

  • As a virtual proxy - used when a resource-intensive object is needed. In this case, the proxy object serves as something like an image of a real object that actually does not exist yet. When a real request (method call) is sent to this object, only then does the original object load and the method execute. This approach is also called delayed initialization; this can be very convenient, because in some situations the original object may not be useful, and then there will be no cost to create it.

  • As a security proxy - used when you need to control access to some object based on client rights. That is, if a client with missing access rights tries to access the original object, the proxy will intercept it and not allow it.

Let's look at an example of a virtual proxy: We have some handler interface:
public interface Processor {
 void process();
}
The implementation of which uses too many resources, but at the same time it may not be used every time the application is launched:
public class HiperDifficultProcessor implements Processor {
 @Override
 public void process() {
   // некоторый сверхсложная обработка данных
 }
}
Proxy class:
public class HiperDifficultProcessorProxy implements Processor {
private HiperDifficultProcessor processor;

 @Override
 public void process() {
   if (processor == null) {
     processor = new HiperDifficultProcessor();
   }
   processor.process();
 }
}
Let's run it in main :
Processor processor = new HiperDifficultProcessorProxy();
// тут тяжеловсеного оригинального an object, ещё не сущетсвует
// но при этом есть an object, который его представляет и у которого можно вызывать его методы
processor.process(); // лишь теперь, an object оригинал был создан
I note that many frameworks use proxying, and for Spring this is a key pattern (Spring is stitched with it inside and out). Read more about this pattern here . Analysis of questions and answers from interviews for Java developer.  Part 14 - 5

5. What innovations have been announced in Java 8?

The innovations brought by Java 8 are as follows:
  • Functional interfaces have been added, read about what kind of beast this is here .

  • Lambda expressions, which are closely related to functional interfaces, read more about their use here .

  • Added Stream API for convenient processing of data collections, read more here .

  • Added links to methods .

  • The forEach() method has been added to the Iterable interface .

  • Added a new date and time API in the java.time package , detailed analysis here .

  • Improved Concurrent API .

  • Adding an Optional wrapper class , which is used to correctly handle null values, you can find an excellent article on this topic here .

  • Adding the ability for interfaces to use static and default methods (which, in essence, brings Java closer to multiple inheritance), more details here .

  • Added new methods to the Collection(removeIf(), spliterator()) class .

  • Minor improvements to Java Core.

Analysis of questions and answers from interviews for Java developer.  Part 14 - 6

6. What are High Cohesion and Low Coupling? Give examples.

High Cohesion or High Cohesion is the concept when a certain class contains elements that are closely related to each other and combined for their purpose. For example, all methods in the User class should represent user behavior. A class has low cohesion if it contains unrelated elements. For example, the User class containing an email address validation method:
public class User {
private String name;
private String email;

 public String getName() {
   return this.name;
 }

 public void setName(final String name) {
   this.name = name;
 }

 public String getEmail() {
   return this.email;
 }

 public void setEmail(final String email) {
   this.email = email;
 }

 public boolean isValidEmail() {
   // некоторая логика валидации емейла
 }
}
The user class may be responsible for storing the user's email address, but not for validating it or sending the email. Therefore, in order to achieve high coherence, we move the validation method into a separate utility class:
public class EmailUtil {
 public static boolean isValidEmail(String email) {
   // некоторая логика валидации емейла
 }
}
And we use it as needed (for example, before saving the user). Low Coupling or Low Coupling is a concept that describes low interdependence between software modules. Essentially, interdependence is how changing one requires changing the other. Two classes have a strong coupling (or tight coupling) if they are closely related. For example, two concrete classes that store references to each other and call each other's methods. Loosely coupled classes are easier to develop and maintain. Since they are independent of each other, they can be developed and tested in parallel. Moreover, they can be changed and updated without affecting each other. Let's look at an example of strongly coupled classes. We have some student class:
public class Student {
 private Long id;
 private String name;
 private List<Lesson> lesson;
}
Which contains a list of lessons:
public class Lesson {
 private Long id;
 private String name;
 private List<Student> students;
}
Each lesson contains a link to attending students. Incredibly strong grip, don't you think? How can you reduce it? First, let's make sure that students have not a list of subjects, but a list of their identifiers:
public class Student {
 private Long id;
 private String name;
 private List<Long> lessonIds;
}
Secondly, the lesson class does not need to know about all the students, so let’s delete their list altogether:
public class Lesson {
 private Long id;
 private String name;
}
So it became much easier, and the connection became much weaker, don’t you think? Analysis of questions and answers from interviews for Java developer.  Part 14 - 7

OOP

7. How can you implement multiple inheritance in Java?

Multiple inheritance is a feature of object-oriented concept where a class can inherit properties from more than one parent class. The problem arises when there are methods with the same signature in both the super class and the subclass. When calling a method, the compiler cannot determine which class method should be called, and even when calling the class method that takes precedence. Therefore, Java does not support multiple inheritance! But there is a kind of loophole, which we will talk about next. As I mentioned earlier, with the release of Java 8, the ability to have default methods was added to interfaces . If the class implementing the interface does not override this method, then this default implementation will be used (it is not necessary to override the default method, such as implementing an abstract one). In this case, it is possible to implement different interfaces in one class and use their default methods. Let's look at an example. We have some flyer interface, with a default fly() method :
public interface Flyer {
 default void fly() {
   System.out.println("Я лечу!!!");
 }
}
The walker interface, with the default walk() method :
public interface Walker {
 default void walk() {
   System.out.println("Я хожу!!!");
 }
}
The swimmer interface, with the swim() method :
public interface Swimmer {
 default void swim() {
   System.out.println("Я плыву!!!");
 }
}
Well, now let’s implement all this in one duck class:
public class Duck implements Flyer, Swimmer, Walker {
}
And let’s run all the methods of our duck:
Duck donald = new Duck();
donald.walk();
donald.fly();
donald.swim();
In the console we will receive:
I go!!! I'm flying!!! I'm swimming!!!
This means that we have correctly depicted multiple inheritance, even though this is not what it is. Analysis of questions and answers from interviews for Java developer.  Part 14 - 8I will also note that if a class implements interfaces with default methods that have the same method names and the same arguments in these methods, then the compiler will begin to complain about incompatibility, since it does not understand which method really needs to be used. There are several ways out:
  • Rename methods in interfaces so that they differ from each other.
  • Override such controversial methods in the implementation class.
  • Inherit from a class that implements these controversial methods (then your class will use exactly its implementation).

8. What is the difference between final, finally and finalize() methods?

final is a keyword that is used to place a constraint on a class, method, or variable, a constraint meaning:
  • For a variable - after initial initialization, the variable cannot be redefined.
  • For a method, the method cannot be overridden in a subclass (successor class).
  • For a class - the class cannot be inherited.
finally is a keyword before a block of code, used when handling exceptions, in conjunction with a try block , and together (or interchangeably) with a catch block. The code in this block is executed in any case, regardless of whether an exception is thrown or not. In this part of the article, in question 104, exceptional situations in which this block will not be executed are discussed. finalize() is a method of the Object class , called before each object is deleted by the garbage collector, this method will be called (last), and is used to clean up occupied resources. For more information about the methods of the Object class that every object inherits, see question 11 in this part of the article. Well, that’s where we’ll end today. See you in the next part! Analysis of questions and answers from interviews for Java developer.  Part 14 - 9
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION