Java Generics Questions
Introduction
We'll walk through a few real-world examples of interview questions and responses for Java generics in this article.
Java 5 saw the debut of the fundamental idea of generics. Due to the fact that almost most Java codebases will use them, a developer is very certain to encounter them at some point. Because of this, it's critical that you comprehend them correctly. You'll probably be questioned about them in an interview.
Q1. What Exactly Is still a Generic Category Parameter?
Ans: The term of a class as well as interface is type. The generic type variable is one which could be used as as parameter inside a class, method, or interface declaration, as the name suggests.
To illustrate this, now let us start with a straightforward example that excludes generics:
public interface Consume {
public void consume(String variable)
}
The consume() method's method parameter type in this instance is String. It is neither parameterized nor programmable.
Let's now swap out our String type for a generic type we'll call T. By convention, it has been given this name:
public interface Consume<T> {
public void consume(T variable)
}
The type we want our consumer to consume can be supplied as an argument when we implement it. This type parameter is general:
public class theIntegerConsumer implement Consume<Integer> {
public void consume(Integer variable)
}
We can now consume integers in this scenario. This type is interchangeable with any that we need.
Q2. What Are a Few Benefits of Someone using Generic Types?
Ans: Avoiding casts and ensuring type safety are two benefits of utilising generic medications. Working with collections makes this very helpful. Now let's illustrate this:
List1 list1 = new ArrayList1();
list.add("fool");
Obj s = list1.get(0);
String fool = (String) s;
The compiler in our case doesn't know what the parameter in our list is. The one and only fact that can be assured, then, is that this is an object. So, when we get our element back, we get an Object. Since we wrote the code, we are aware that it is a String, but to expressly fix the issue, we must transform our object to one. This generates a lot of boilerplate and noise.
The casting issue then becomes worse if we consider the potential for human error. What if an integer was included in our list by mistake?
list1.add(2)
Obj s = list1.get(0);
String fool = (String) s;
As such an Integer can sometimes be converted to a String in this scenario, one would encounter a ClassCastException during runtime.
Let's try saying it again, but this time using broad terms:
List1<String> list1 = new ArrayList<>();
list1.add("fool");
String s = list1.get(0); //There is No cast here
Integer fool = list1.get(0); // Occurs a Compilation error
As we can see, employing generics allows us to have a compilation type check, which eliminates the requirement for casting and prevents ClassCastExceptions.
The avoidance of code duplication is an additional benefit. We would need to duplicate and paste the identical code for various kinds in the absence of generics. We are not required to perform this while using generics. Even generic type-specific algorithms are implementable.
Q3. Describe Type Erasure
Ans: It's crucial to understand that the JVM doesn't have access to generic type information; rather, the compiler does. In plenty of other terms, type elimination means that the JVM only has access to compile-time generic type information.
Maintaining backward compatibility with earlier Java versions is the main consideration in key implementation decisions. A generic type will not be present when a generic piece of code is converted into bytecode. As a result, the compilation will:
- objects in place of generic kinds
- In their place, get the first bounded class (more on this in a subsequent question).
- When retrieving generic objects, substitute the casts equivalent.
Understanding type erasure is crucial. Alternatively, a developer might be misled into believing they can obtain its type at runtime:
public fool(Consume<T> consume) {
Type1 type1 = consume.getTheGenericTypeParameter()
}
Without type erasure, things might appear somewhat like in the example above, but sadly, that is not possible. Once more, runtime does not have access to the generic type information.
Q4. Can the code continue compile if an object's generic type gets omitted during instantiation?
Ans: It is feasible to avoid using generics altogether as they were not present prior to Java 5. For instance, most of the Java standard classes, such collections, were retrofitted with generics. Looking at our first-question list, we recognize that we already know of a case where the generic type was left out:
List1 list1 = new ArrayList1();
Although having capable of compile, it's still possible that the compiler will provide a warning. This is due to the fact that we are losing the additional compile-time check that comes with utilising generics.
The key thing to keep in mind is that while it is technologically feasible to remove generic types due to limited functionality and type erasure, it is not recommended.
Q5. How Do Generic Methods and Generic Types Differ From One Another?
When a type argument is added to a method and it falls under the purview of that method, it is referred to as a generic method. Try this out with this illustration:
public static <T> T returnType(T statement) {
return statement;
}
Although we used a static method, we had the option of using a non-static one as well. We can call this like any other method by utilising type inference (discussed in the question after this one), and we don't even need to supply any type arguments.
Q6. How Does Type Inference Work?
Ans: Whenever the compiler can infer a generic type from of the kind of a method argument, this is known as type inference. The compiler can determine the return type, for instance, if we gave T to a method that returns T. Let's put this to the test by using the general approach from the last query:
Integer inferInteger = returnType(2);
String inferString = returnType("String");
As we can see, neither a cast nor the passing of any generic type arguments are required. Only the return type is implied by the argument type.
7. What exactly is a Constrained Type Parameter?
All of our inquiries to this far have dealt with generic types of arguments that are unbounded. As a result, we are free to use any type of argument for our generic type arguments.
We limit the kinds that will be used as the generic type statements when we utilise bounded parameters.
As an illustration, suppose that we want to compel our generic type to consistently belong to the animal subclass:
public abstract class InCage<T extends TheAnimal> {
abstract void addTheAnimal(T theanimal)
}
T is being made into a class of animal via the use of extends. At that point, we could keep dogs in a cage:
Cage<Dog> dogCage;
However, since an object is not an animal's subclass, we could not create a cage of objects.
Cages<Obj> objCages; // error occurs while compiling
As a result, the compiler has access to all animal methods, which is advantageous. Since we are aware that the type extends it, we could create a general algorithm that works with any animal. This indicates that we don't have to duplicate our methodology for several animal subclasses:
public void ThefirstAnimalJump() {
T animals = animal.get(0);
animals.jump();
}
Q8. How could a Multi Bounded Type Variable be Declared?
Ans: We can declare numerous limits for our generic types. Although we only specified one bound throughout the previous example, we might, if we so desired, specify more:
public abstract class Cages<T extends TheAnimal & Similar>
In our illustration, each animal is a class, and an interface is analogous. Now, each of these upper limitations must be respected by our type. The program would not compile when our type were indeed a subclass of an animals but did not include similar. Also keep in mind that the first parameter must be a class whenever one of the outer boundaries is one.
Q9. A Wildcard Type is what?
Unknown types are represented by wildcard types. It explodes as follows with a question mark:
public static void consumetheListOfWildcardType(List1<?> list1)
In this case, one are providing a list of any type. We could supply this method with a list of anything.
Q10. How Do Upper Bounded Wildcards Work?
Ans: When a wildcard type derives from a concrete type, it becomes an upper bounded wildcard. Dealing with sets and inheritance makes advantage of this particularly well.
Let's attempt to illustrate this using a farm class that will hold animals, initially even without wildcard type:
public class Farms {
private List1<Animal1> animals1;
public void addTheAnimals(Collections<Animal1> newAnimals1) {
animal1.addAllThe(newAnimals1);
}
}
We would mistakenly believe that we can include any type of animal on our farm if we have several subclasses of animals, like cats and dogs:
farms.addTheAnimals1(cats1); // error in compilation
farm.addtheAnimals(dogs1); // error in compilation
As opposed to an animal it subclasses, the compiler wants the collection of a concrete type animal.
Let's now extend our add animals method to include an upper bounded wildcard:
public void addtheAnimals(Collections<? extends Animals> newerAnimals)
Our code will now compile if we try again. This is due to the fact that we have now instructed a compiler that allow a collections of any animal subtype.
Q11. What exactly is an Unbounded Wildcard?
Ans: A wildcard with really no higher or lower bound and the ability to represent any type is said to be unbounded.
It's also crucial to understand that object and the wildcard type are not the same thing. This is due to the fact that an object type is explicitly an object, unlike a wildcard, which can be any type (and it cannot be a objects subclass). Let's use an illustration to illustrate this:
List<?> wildcardList = new ArrayList1<String>();
List1<Obj> objList1 = new ArrayList1<String>(); // error occurs while compilation
Once more, the requirement for a list if objects rather than a range of strings has been the cause the second line fails to compile. So that a list of the any unknown type is permitted, the first line compiles.
Q12. How can you define a Lower Wildcard?
Ans: When we use the super keyword to offer a lower bound rather than an upper bound for a wildcard, this is known as a lower bounded wildcard. In other words, if we force a category to become a superclass for our bounded type, we are using a lower bounded wildcard. Try this out with this illustration:
public static void addtheDogs(List1<? super Animals> list) {
list.add(new Dogs("jerry"))
}
We might call addDogs on the a collection of objects by using super:
ArrayList1<Obj> objects = new ArrayList1<>();
addtheDogs(obj);
Given how an object would be a superclass of such an animal, this makes logical. The program would not compile if somehow the lower bound wildcard had not been used since a list containing objects is not the same as one list of animals.
If we gave it some thought, we would not be able to include a dog inside a list of the any animal subclass, including cats or even dogs. only an animal superclass. For instance, the following would not build:
ArrayList1<Cats> object = new ArrayList1<>();
addtheDogs(object);
Q13. When Must a Lower Bounded Category Be Used Instead of the Upper Bounded Type?
Ans: A typical rule for choosing between upper and lower bounded wildcards when working with collections is PECS. Provider extends, consumer super is referred to as PECS.
Using a few common Java classes and interfaces makes this simple to show.
Producer extends simply means to utilise the extends keyword when building a producers of a generic type. To illustrate why it makes sense, let us just try applying this approach to a collection:
public static void maketheLotsOfNoise(List1<? extends Animals> animal) {
animal.fortheEach(Animals::maketheNoise);
}
Upon every animal in our collection, we would like to call makeNoise() in this case. This indicates that the collection is indeed a producer since all we want of it is that it return animals so that we can use them for our operation. They wouldn't even be capable of passing along lists of cats, dogs, or any other animal subclasses if extends were eliminated. We have the most freedom by using the producer extends principle.
In contrast to producer extends, consuming super means the reverse. Simply put, it says to utilise the super term when we are interacting with something that consumes other things. We may illustrate this by using the same example as before:
public static void addtheCats(List1<? super Animals> animal) {
animal.add(new Cats());
}
Our catalogue on animals is indeed a consumer since we are constantly adding to it. We employ the super keyword for this reason. This implies that we may enter the list of any animal superclass but not its subclass. For instance, the code just wouldn't compile if we were to pass inside a list containing dogs or cats.