Java Generics Interview Questions (+Answers) – Java泛型的面试问题(+答案)

最后修改: 2017年 3月 27日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this article, we’ll go through some example Java generics interview questions and answers.

在这篇文章中,我们将通过一些Java泛型面试问题和答案的例子。

Generics are a core concept in Java, first introduced in Java 5. Because of this, nearly all Java codebases will make use of them, almost guaranteeing that a developer will run into them at some point. This is why it’s essential to understand them correctly, and is why they are more than likely to be asked about during an interview process.

泛型是Java的一个核心概念,在Java 5中首次引入。正因为如此,几乎所有的Java代码库都会使用它们,几乎可以保证开发人员在某些时候会遇到它们。这就是为什么正确理解它们是至关重要的,也是为什么它们在面试过程中更有可能被问及的原因。

2. Questions

2.问题

Q1. What Is a Generic Type Parameter?

Q1.什么是通用类型参数?

Type is the name of a class or interface. As implied by the name, a generic type parameter is when a type can be used as a parameter in a class, method or interface declaration.

类型是一个接口的名称。正如其名称所暗示的,通用类型参数是指当一个类型可以被用作类、方法或接口声明中的一个参数。

Let’s start with a simple example, one without generics, to demonstrate this:

让我们从一个简单的例子开始,一个没有泛型的例子,来证明这一点。

public interface Consumer {
    public void consume(String parameter)
}

In this case, the method parameter type of the consume() method is String. It is not parameterized and not configurable.

在这种情况下,consume()方法的方法参数类型是String。它没有参数化,也不能配置。

Now let’s replace our String type with a generic type that we will call T. It is named like this by convention:

现在让我们用一个通用类型来替换我们的String 类型,我们将称之为T。按照惯例,它是这样命名的。

public interface Consumer<T> {
    public void consume(T parameter)
}

When we implement our consumer, we can provide the type that we want it to consume as an argument. This is a generic type parameter:

当我们实现我们的消费者时,我们可以提供我们希望它消费的类型作为一个参数。这是一个通用的类型参数。

public class IntegerConsumer implements Consumer<Integer> {
    public void consume(Integer parameter)
}

In this case, now we can consume integers. We can swap out this type for whatever we require.

在这种情况下,现在我们可以消费整数。我们可以把这个类型换成我们需要的任何类型。

Q2. What Are Some Advantages of Using Generic Types?

Q2.使用通用类型的一些优势是什么?

One advantage of using generics is avoiding casts and provide type safety. This is particularly useful when working with collections. Let’s demonstrate this:

使用泛型的一个好处是避免了转换和提供类型安全。这在处理集合的时候特别有用。让我们来演示一下。

List list = new ArrayList();
list.add("foo");
Object o = list.get(0);
String foo = (String) o;

In our example, the element type in our list is unknown to the compiler. This means that the only thing that can be guaranteed is that it is an object. So when we retrieve our element, an Object is what we get back. As the authors of the code, we know it’s a String, but we have to cast our object to one to fix the problem explicitly. This produces a lot of noise and boilerplate.

在我们的例子中,我们列表中的元素类型对编译器来说是未知的。这意味着唯一可以保证的是,它是一个对象。所以当我们检索我们的元素时,我们得到的是一个Object。作为代码的作者,我们知道它是一个String,,但是我们必须把我们的对象铸成一个对象来明确地解决这个问题。这产生了大量的噪音和模板。

Next, if we start to think about the room for manual error, the casting problem gets worse. What if we accidentally had an Integer in our list?

接下来,如果我们开始考虑人工出错的空间,铸造问题就会变得更糟。如果我们不小心在列表中出现了一个Integer怎么办?

list.add(1)
Object o = list.get(0);
String foo = (String) o;

In this case, we would get a ClassCastException at runtime, as an Integer cannot be cast to String.

在这种情况下,我们会在运行时得到一个ClassCastException,因为一个Integer不能被投到String.

Now, let’s try repeating ourselves, this time using generics:

现在,让我们试着重复一下自己,这次使用泛型。

List<String> list = new ArrayList<>();
list.add("foo");
String o = list.get(0);    // No cast
Integer foo = list.get(0); // Compilation error

As we can see, by using generics we have a compile type check which prevents ClassCastExceptions and removes the need for casting.

正如我们所看到的,通过使用泛型,我们有一个编译类型检查,可以防止ClassCastExceptions,并消除了铸造的需要。

The other advantage is to avoid code duplication. Without generics, we have to copy and paste the same code but for different types. With generics, we do not have to do this. We can even implement algorithms that apply to generic types.

另一个优点是避免了代码的重复。如果没有泛型,我们必须为不同的类型复制和粘贴相同的代码。有了泛型,我们就不必这样做了。我们甚至可以实现适用于泛型的算法。

Q3. What Is Type Erasure?

Q3.什么是类型清除?

It’s important to realize that generic type information is only available to the compiler, not the JVM. In other words, type erasure means that generic type information is not available to the JVM at runtime, only compile time.

重要的是要认识到,通用类型信息只对编译器有用,而不是对JVM有用。换句话说,类型清除意味着JVM在运行时无法获得通用类型信息,只能在编译时获得

The reasoning behind major implementation choice is simple – preserving backward compatibility with older versions of Java. When a generic code is compiled into bytecode, it will be as if the generic type never existed. This means that the compilation will:

主要实现选择背后的理由很简单–保留对旧版本Java的向后兼容性。当泛型代码被编译成字节码时,就像泛型类型从未存在过一样。这意味着,编译会。

  1. Replace generic types with objects
  2. Replace bounded types (More on these in a later question) with the first bound class
  3. Insert the equivalent of casts when retrieving generic objects.

It’s important to understand type erasure. Otherwise, a developer might get confused and think they’d be able to get the type at runtime:

理解类型擦除是很重要的。否则,开发人员可能会感到困惑,认为他们能够在运行时获得类型。

public foo(Consumer<T> consumer) {
   Type type = consumer.getGenericTypeParameter()
}

The above example is a pseudo code equivalent of what things might look like without type erasure, but unfortunately, it is impossible. Once again, the generic type information is not available at runtime.

上面的例子是一个伪代码,相当于没有类型擦除时的情况,但不幸的是,这是不可能的。再一次,通用类型信息在运行时是不可用的

Q4. If a Generic Type Is Omitted When Instantiating an Object, Will the Code Still Compile?

Q4.如果在实例化一个对象时省略了一个通用类型,那么代码是否仍然可以编译?

As generics did not exist before Java 5, it is possible not to use them at all. For example, generics were retrofitted to most of the standard Java classes such as collections. If we look at our list from question one, then we will see that we already have an example of omitting the generic type:

由于泛型在Java 5之前并不存在,所以有可能根本就不使用它们。例如,泛型被加装到大多数标准的Java类中,如集合。如果我们看一下问题一中的列表,那么我们会发现,我们已经有一个省略泛型的例子。

List list = new ArrayList();

Despite being able to compile, it’s still likely that there will be a warning from the compiler. This is because we are losing the extra compile-time check that we get from using generics.

尽管能够编译,但仍有可能出现编译器的警告。这是因为我们失去了从使用泛型得到的额外的编译时检查。

The point to remember is that while backward compatibility and type erasure make it possible to omit generic types, it is bad practice.

需要记住的一点是,虽然向后兼容和类型清除使省略通用类型成为可能,但这是不好的做法。

Q5. How Does a Generic Method Differ from a Generic Type?

Q5.泛型方法与泛型类型有何不同?

A generic method is where a type parameter is introduced to a method, living within the scope of that method. Let’s try this with an example:

泛型方法是指在一个方法中引入一个类型参数,在该方法的范围内生存。让我们用一个例子来试试。

public static <T> T returnType(T argument) { 
    return argument; 
}

We’ve used a static method but could have also used a non-static one if we wished. By leveraging type inference (covered in the next question), we can invoke this like any ordinary method, without having to specify any type arguments when we do so.

我们使用了一个静态方法,但如果我们愿意,也可以使用一个非静态方法。通过利用类型推理(在下一个问题中涉及),我们可以像其他普通方法一样调用这个方法,而不必在调用时指定任何类型参数。

Q6. What Is Type Inference?

Q6.什么是类型推理?

Type inference is when the compiler can look at the type of a method argument to infer a generic type. For example, if we passed in T to a method which returns T, then the compiler can figure out the return type. Let’s try this out by invoking our generic method from the previous question:

类型推断是指编译器可以通过查看方法参数的类型来推断出一个通用类型。例如,如果我们向一个返回T的方法传递了T,那么编译器就可以找出返回类型。让我们通过调用前一个问题中的泛型方法来试试。

Integer inferredInteger = returnType(1);
String inferredString = returnType("String");

As we can see, there’s no need for a cast, and no need to pass in any generic type argument. The argument type only infers the return type.

正如我们所看到的,没有必要进行转换,也没有必要传入任何通用类型的参数。参数类型只推断出返回类型。

Q7. What Is a Bounded Type Parameter?

Q7.什么是有边界的类型参数?

So far all our questions have covered generic types arguments which are unbounded. This means that our generic type arguments could be any type that we want.

到目前为止,我们所有的问题都涉及到了通用类型的参数,这些参数是无界的。这意味着我们的通用类型参数可以是我们想要的任何类型。

When we use bounded parameters, we are restricting the types that can be used as generic type arguments.

当我们使用有界参数时,我们限制了可以作为通用类型参数的类型。

As an example, let’s say we want to force our generic type always to be a subclass of animal:

作为一个例子,假设我们想强制我们的泛型总是动物的子类。

public abstract class Cage<T extends Animal> {
    abstract void addAnimal(T animal)
}

By using extends, we are forcing T to be a subclass of animal. We could then have a cage of cats:

通过使用 extends我们迫使T成为 animal的一个子类。然后我们可以有一个猫的笼子。

Cage<Cat> catCage;

But we could not have a cage of objects, as an object is not a subclass of an animal:

但我们不可能有一个物体的笼子,因为物体不是动物的一个子类。

Cage<Object> objectCage; // Compilation error

One advantage of this is that all the methods of animal are available to the compiler. We know our type extends it, so we could write a generic algorithm which operates on any animal. This means we don’t have to reproduce our method for different animal subclasses:

这样做的一个好处是,动物的所有方法对编译器都是可用的。我们知道我们的类型扩展了它,所以我们可以写一个通用算法,对任何动物进行操作。这意味着我们不需要为不同的动物子类重现我们的方法。

public void firstAnimalJump() {
    T animal = animals.get(0);
    animal.jump();
}

Q8. Is It Possible to Declared a Multiple Bounded Type Parameter?

Q8.是否有可能声明一个多界限的类型参数?

Declaring multiple bounds for our generic types is possible. In our previous example, we specified a single bound, but we could also specify more if we wish:

为我们的通用类型声明多个边界是可能的。在我们前面的例子中,我们指定了一个边界,但如果我们愿意,我们也可以指定更多的边界。

public abstract class Cage<T extends Animal & Comparable>

In our example, the animal is a class and comparable is an interface. Now, our type must respect both of these upper bounds. If our type were a subclass of animal but did not implement comparable, then the code would not compile. It’s also worth remembering that if one of the upper bounds is a class, it must be the first argument.

在我们的例子中,动物是一个类,可比较的是一个接口。现在,我们的类型必须尊重这两个上界。如果我们的类型是animal的一个子类,但没有实现comparable,那么代码就不能编译了。同样值得记住的是,如果其中一个上界是一个类,它必须是第一个参数。

Q9. What Is a Wildcard Type?

Q9.什么是通配符类型?

A wildcard type represents an unknown type. It’s detonated with a question mark as follows:

通配符类型代表一个未知的类型。它是用问号引爆的,如下所示。

public static void consumeListOfWildcardType(List<?> list)

Here, we are specifying a list which could be of any type. We could pass a list of anything into this method.

这里,我们指定了一个列表,它可以是任何类型。我们可以向这个方法传递一个任何类型的列表。

Q10. What Is an Upper Bounded Wildcard?

Q10 什么是上界通配符?

An upper bounded wildcard is when a wildcard type inherits from a concrete type. This is particularly useful when working with collections and inheritance.

上界通配符是指通配符类型继承自一个具体类型。这在处理集合和继承的时候特别有用。

Let’s try demonstrating this with a farm class which will store animals, first without the wildcard type:

让我们试着用一个将存储动物的农场类来演示一下,首先不使用通配符类型。

public class Farm {
  private List<Animal> animals;

  public void addAnimals(Collection<Animal> newAnimals) {
    animals.addAll(newAnimals);
  }
}

If we had multiple subclasses of animal, such as cat and dog, we might make the incorrect assumption that we can add them all to our farm:

如果我们有多个动物的子类如猫和狗我们可能会做出错误的假设,认为我们可以将它们全部加入我们的农场。

farm.addAnimals(cats); // Compilation error
farm.addAnimals(dogs); // Compilation error

This is because the compiler expects a collection of the concrete type animal, not one it subclasses.

这是因为编译器期望的是一个具体类型animal的集合,而不是它的子类。

Now, let’s introduce an upper bounded wildcard to our add animals method:

现在,让我们为我们的添加动物方法引入一个有上界的通配符。

public void addAnimals(Collection<? extends Animal> newAnimals)

Now if we try again, our code will compile. This is because we are now telling the compiler to accept a collection of any subtype of animal.

现在如果我们再试一次,我们的代码将被编译。这是因为我们现在告诉编译器可以接受任何动物子类型的集合。

Q11. What Is an Unbounded Wildcard?

Q11 什么是无界通配符?

An unbounded wildcard is a wildcard with no upper or lower bound, that can represent any type.

无界通配符是一个没有上下限的通配符,它可以代表任何类型。

It’s also important to know that the wildcard type is not synonymous to object. This is because a wildcard can be any type whereas an object type is specifically an object (and cannot be a subclass of an object). Let’s demonstrate this with an example:

同样重要的是要知道通配符类型不是对象的同义词。这是因为通配符可以是任何类型,而对象类型是特指对象(不能是对象的子类)。让我们用一个例子来证明这一点。

List<?> wildcardList = new ArrayList<String>(); 
List<Object> objectList = new ArrayList<String>(); // Compilation error

Again, the reason the second line does not compile is that a list of objects is required, not a list of strings. The first line compiles because a list of any unknown type is acceptable.

同样,第二行不能编译的原因是需要一个对象的列表,而不是一个字符串的列表。第一行可以编译,因为任何未知类型的列表都可以接受。

Q12. What Is a Lower Bounded Wildcard?

Q12.什么是下限通配符?

A lower bounded wildcard is when instead of providing an upper bound, we provide a lower bound by using the super keyword. In other words, a lower bounded wildcard means we are forcing the type to be a superclass of our bounded type. Let’s try this with an example:

下限通配符是指我们不提供上限,而是通过使用super关键字来提供一个下限。换句话说,下界通配符意味着我们强迫该类型成为我们有界类型的超类。让我们用一个例子来试试。

public static void addDogs(List<? super Animal> list) {
   list.add(new Dog("tom"))
}

By using super, we could call addDogs on a list of objects:

通过使用super,我们可以在一个对象的列表上调用addDogs。

ArrayList<Object> objects = new ArrayList<>();
addDogs(objects);

This makes sense, as an object is a superclass of animal. If we did not use the lower bounded wildcard, the code would not compile, as a list of objects is not a list of animals.

这是有道理的,因为对象是动物的一个超类。如果我们不使用下界通配符,代码就不会被编译,因为对象的列表不是动物的列表。

If we think about it, we wouldn’t be able to add a dog to a list of any subclass of animal, such as cats, or even dogs. Only a superclass of animal. For example, this would not compile:

如果我们仔细想想,我们就不能在任何动物子类的列表中加入狗,比如猫,甚至是狗。只有动物的超类。例如,这将无法编译。

ArrayList<Cat> objects = new ArrayList<>();
addDogs(objects);

Q13. When Would You Choose to Use a Lower Bounded Type vs. an Upper Bounded Type?

Q13.你什么时候会选择使用下限类型与上限类型?

When dealing with collections, a common rule for selecting between upper or lower bounded wildcards is PECS. PECS stands for producer extends, consumer super.

在处理集合时,在上界或下界通配符之间进行选择的一个常见规则是PECS。PECS代表生产者扩展,消费者超级。

This can be easily demonstrated through the use of some standard Java interfaces and classes.

通过使用一些标准的Java接口和类,可以很容易地证明这一点。

Producer extends just means that if you are creating a producer of a generic type, then use the extends keyword. Let’s try applying this principle to a collection, to see why it makes sense:

生产者扩展只是意味着,如果你要创建一个通用类型的生产者,那么就使用扩展关键字。让我们试着把这个原则应用于一个集合,看看为什么它有意义。

public static void makeLotsOfNoise(List<? extends Animal> animals) {
    animals.forEach(Animal::makeNoise);   
}

Here, we want to call makeNoise() on each animal in our collection. This means our collection is a producer, as all we are doing with it is getting it to return animals for us to perform our operation on. If we got rid of extends, we wouldn’t be able to pass in lists of cats, dogs or any other subclasses of animals. By applying the producer extends principle, we have the most flexibility possible.

在这里,我们想对我们集合中的每只动物调用makeNoise() 。这意味着我们的集合是一个生产者因为我们对它所做的就是让它返回动物,以便我们执行我们的操作。如果我们摆脱了extends,我们就不能传入猫狗或任何其他动物子类的列表。通过应用生产者扩展原则,我们拥有尽可能多的灵活性。

Consumer super means the opposite to producer extends. All it means is that if we are dealing with something which consumes elements, then we should use the super keyword. We can demonstrate this by repeating our previous example:

消费者超级的意思与生产者扩展相反。它的意思是,如果我们处理的是消耗元素的东西,那么我们应该使用super关键字。我们可以通过重复我们之前的例子来证明这一点。

public static void addCats(List<? super Animal> animals) {
    animals.add(new Cat());   
}

We are only adding to our list of animals, so our list of animals is a consumer. This is why we use the super keyword. It means that we could pass in a list of any superclass of animal, but not a subclass. For example, if we tried passing in a list of dogs or cats then the code would not compile.

我们只是在向我们的动物列表添加,所以我们的动物列表是一个消费者。这就是我们使用super关键字的原因。这意味着我们可以传入任何超类动物的列表,但不能传入子类。例如,如果我们试图传入一个狗或猫的列表,那么代码就不会被编译。

The final thing to consider is what to do if a collection is both a consumer and a producer. An example of this might be a collection where elements are both added and removed. In this case, an unbounded wildcard should be used.

最后要考虑的是,如果一个集合既是消费者又是生产者,该怎么做。这方面的一个例子可能是一个元素既被添加又被移除的集合。在这种情况下,应该使用一个无界通配符。

Q14. Are There Any Situations Where Generic Type Information Is Available at Runtime?

Q14.是否存在在运行时提供通用类型信息的情况?

There is one situation where a generic type is available at runtime. This is when a generic type is part of the class signature like so:

有一种情况下,泛型在运行时是可用的。这就是当一个泛型是类签名的一部分时,像这样。

public class CatCage implements Cage<Cat>

By using reflection, we get this type parameter:

通过使用反射,我们得到这个类型参数。

(Class<T>) ((ParameterizedType) getClass()
  .getGenericSuperclass()).getActualTypeArguments()[0];

This code is somewhat brittle. For example, it’s dependant on the type parameter being defined on the immediate superclass. But, it demonstrates the JVM does have this type information.

这段代码是有点脆的。例如,它依赖于类型参数被定义在直接的超类上。但是,它证明了JVM确实有这个类型信息。

Next »

Java Flow Control Interview Questions (+ Answers)

« Previous

Memory Management in Java Interview Questions (+Answers)