CodeGym /Java 博客 /随机的 /Java Core 的前 50 个工作面试问题和答案。第1部分
John Squirrels
第 41 级
San Francisco

Java Core 的前 50 个工作面试问题和答案。第1部分

已在 随机的 群组中发布
大家好,女士们,先生们,软件工程师们!说说面试题吧。关于您需要准备什么以及您需要了解什么。这是第一次回顾或研究这些要点的好时机。 Java Core 的前 50 个工作面试问题和答案。 第 1 - 1 部分 最后,我收集了相当广泛的关于 OOP、Java 语法、Java 异常、集合和多线程的常见问题集,为了方便起见,我将把它们分成几个部分。很难一次涵盖所有内容,但我希望这些材料能为那些准备以程序员身份找到第一份工作的人提供良好的基础。为了最好地理解和保留,我建议也通过其他来源进行梳理。您可以通过从几个不同的角度来处理一个概念,从而更深入地掌握它。 重要的:我们只会讨论版本 8 之前的 Java。这里不会考虑版本 9、10、11、12 和 13 中出现的所有创新。欢迎任何关于如何改进答案的想法/意见。享受你的阅读。我们走吧!

Java面试:关于OOP的问题

1、Java有什么特点?

回答:
  1. 面向对象的概念:

    1. 面向对象
    2. 遗产
    3. 封装
    4. 多态性
    5. 抽象
  2. 跨平台: Java 程序可以在任何平台上运行而无需任何更改。当然,这需要安装 JVM(Java 虚拟机)。

  3. 高性能:即时 (JIT) 编译器使高性能成为可能。JIT 编译器将字节码转换为机器码,然后 JVM 开始执行。

  4. 多线程: JVM 创建一个称为main thread. 程序员可以通过从 Thread 类派生或实现Runnable接口来创建多个线程。

2、什么是继承?

继承意味着一个类可以继承另一个类(使用extends关键字)。这意味着您可以重用您继承的类中的代码。现有类称为 ,superclass新创建的类是subclass。人们还说使用术语 parent 和child

public class Animal {
   private int age;
}

public class Dog extends Animal {

}
Animal和在 哪里。 parent_Dogchild

3.什么是封装?

这个问题在 Java 开发人员职位的面试中经常被问到。封装通过使用访问修饰符、getter 和 setter 来隐藏实现。这样做是为了防止在开发人员认为有必要的地方进行外部访问。现实生活中的一个简单例子就是汽车。我们无法直接访问引擎的运行情况。我们所需要做的就是将钥匙插入点火开关并启动发动机。引擎盖下发生的过程与我们无关。此外,如果我们干预发动机的活动,可能会导致不可预测的情况,可能会损坏汽车并造成人身伤害。编程中也会发生完全相同的事情。这在维基百科上有很好的描述. CodeGym上也有一篇关于封装的文章。

4.什么是多态?

多态性是程序以相同方式处理具有相同接口的对象的能力,而无需有关对象特定类型的信息。俗话说,“一个接口——许多实现”。使用多态性,您可以根据共享行为组合和使用不同类型的对象。例如,我们有一个 Animal 类,它有两个后代:Dog 和 Cat。通用 Animal 类具有所有人共享的行为,即发出声音的能力。当我们需要收集所有继承 Animal 类的东西并执行“make sound”方法时,我们使用多态能力。这是它的样子:

List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
换句话说,多态性是有帮助的。这也适用于多态(重载)方法。如何使用多态

Java语法面试题

5. 什么是 Java 中的构造函数?

构造器具有以下特点:
  1. 创建新对象时,程序使用适当的构造函数来创建它。
  2. 构造函数就像一个方法。它的显着特点是没有返回值(包括void),而且它的名字和类的名字一样。
  3. 如果没有显式创建构造函数,则会自动创建一个空的构造函数。
  4. 可以覆盖构造函数。
  5. 如果您声明了一个带参数的构造函数,但又需要一个不带参数的构造函数,那么您必须单独创建它,因为它不会自动创建。

6.哪两个类不继承Object?

不要被技巧问题所迷惑——没有这样的课程。所有类都直接或通过祖先继承 Object 类!

7. 什么是局部变量?

这是 Java 开发人员的另一个热门面试问题。局部变量是在方法内部定义的变量,只要方法正在执行就存在。一旦执行结束,局部变量就不复存在。这是一个在 main() 方法中使用名为 helloMessage 的局部变量的程序:

public static void main(String[] args) {
   String helloMessage;
   helloMessage = "Hello, World!";
   System.out.println(helloMessage);
}

8. 什么是实例变量?

实例变量是在类内部声明的变量。只要一个对象存在,它就存在。例如,我们有一个 Bee 类,它有两个实例变量——nectarLoad 和 maxNectarLoad:

public class Bee {

   /**
    * Current nectar load
    */
   private double nectarLoad;

   /**
    * Maximum nectar that can the bee can collect.
    */
   private double maxNectarLoad = 20.0;
 
  ...
}

9. 什么是访问修饰符?

访问修饰符是一种用于自定义对类、方法和变量的访问的机制。存在以下修饰符,按访问权限递增的顺序列出:
  1. private— 此访问修饰符用于方法、字段和构造函数。访问仅限于声明它们的类。
  2. package-private (default)— 这是类的默认访问级别。访问仅限于声明类、方法、变量或构造函数的特定包。
  3. protectedpackage-private— 此访问修饰符提供与为继承具有修饰符的类的类添加访问权限相同的访问级别protected
  4. public— 此访问级别也用于类。此访问级别意味着在整个应用程序中具有完全访问权限。
Java Core 的前 50 个工作面试问题和答案。 第 1 - 2 部分

10. 什么是方法覆盖?

当子类想要更改其父类的行为时,我们会覆盖方法。如果我们还需要执行父方法中的操作,我们可以在子方法中使用 super.methodName() ,它将执行父方法。之后我们可以添加额外的逻辑。必须遵守的要求:
  • 方法签名必须相同
  • 返回值必须相同

11. 什么是方法签名?

Java Core 的前 50 个工作面试问题和答案。 第 1 - 3 部分方法签名是方法名称和方法采用的参数的组合。方法签名是重载方法时方法的唯一标识符。

12.什么是方法重载?

方法重载是多态性的一个特性,我们可以通过更改方法签名来创建执行相同操作的多个方法:
  • 同名
  • 不同的论点
  • 可以有不同的返回类型
例如,ArrayList类的add()方法可以被重载,允许我们根据输入参数以不同的方式添加:
  • add(Object o)— 此方法只是添加一个对象
  • add(int index, Object o)— 此方法在特定索引处添加一个对象
  • add(Collection<Object> c)— 此方法添加对象列表
  • add(int index, Collection<Object> c)— 此方法添加从特定索引开始的对象列表。

13.什么是接口?

Java 不支持多重继承。为了克服这个限制,接口被添加到我们熟悉和喜爱的形式中;)很长一段时间以来,接口只有方法而没有任何实现。在这个答案的上下文中,让我们谈谈它们。例如:


public interface Animal {
   void makeSound();
   void eat();
   void sleep();
}
一些细节如下:
  • 接口中的所有方法都是公共的和抽象的
  • 所有变量都是 public static final
  • 类不继承接口(即我们不使用 extends 关键字)。相反,类实现它们(即我们使用 implements 关键字)。此外,您可以根据需要实现任意数量的接口。
  • 实现接口的类必须提供接口中所有方法的实现。
像这样:

public class Cat implements Animal {
   public void makeSound() {
       // Method implementation
   }

   public void eat() {
       // Implementation
   }

   public void sleep() {
       // Implementation
   }
}

14. 什么是接口中的默认方法?

现在让我们谈谈默认方法。他们有什么用?他们是为了谁?添加这些方法是为了“双手”服务。我在说什么?好吧,一方面,需要添加新功能:lambdas 和 Stream API。另一方面,有必要保留 Java 著名的特性——向后兼容性。为此,接口需要一些新的现成解决方案。这就是默认方法来找我们的方式。默认方法是接口中已实现的方法,用default关键字标记。stream()例如,接口中众所周知的方法Collection。相信我,这个界面并不像看起来那么简单。或者还有同样著名的forEach()方法Iterable界面。在添加默认方法之前它也不存在。顺便说一句,您还可以在此处的CodeGym 上阅读有关它的信息。

15. 那么如何继承两个相同的默认方法呢?

先前关于什么是默认方法的回答引出了另一个问题。如果可以在接口中实现方法,那么理论上可以用同一个方法实现两个接口。我们该怎么做?下面是两个不同的接口,使用相同的方法:

interface A {
   default void foo() {
       System.out.println("Foo A");
   }
}

interface B {
   default void foo() {
       System.out.println("Foo B");
   }
}
我们有一个实现这两个接口的类。但是我们如何在接口 A 或 B 中选择特定的方法呢?以下特殊构造允许这样做A.super.foo()::

public class C implements A, B {
   public void fooA() {
       A.super.foo();
   }

   public void fooB() {
       B.super.foo();
   }
}
这样,fooA()方法将使用接口foo()的默认方法A,而fooB()方法将使用接口foo()的方法B

16.什么是抽象方法和类?

在 Java 中,abstract是一个保留字。它用于表示抽象类和方法。首先,我们需要定义。abstract抽象方法是在抽象类中没有实现的情况下使用关键字声明的方法。也就是说,这是一个接口中的方法,但添加了一个关键字,例如:

public abstract void foo();
抽象类是一个也标有关键字的类abstract

public abstract class A {

}
抽象类有几个特点:
  • 您不能创建抽象类的对象
  • 它可以有抽象方法
  • 它也可能没有抽象方法
抽象类需要具有一组通用行为和状态(即方法和变量)的抽象(抱歉重言式)。现实生活中充满了例子。我们周围的一切。“动物”、“汽车”、“几何图形”等。

17.String、StringBuilder、StringBuffer有什么区别?

String值存储在常量字符串池中。一旦创建了一个字符串,它就会出现在这个池中。而且你不能删除它。例如:

String name = "book";
该变量将指向常量字符串池 Java Core 的前 50 个工作面试问题和答案。 第 1 - 4 部分将 name 变量设置为不同的值,我们有:

name = "pen";
常量字符串池如下所示: Java Core 的前 50 个工作面试问题和答案。 第 1 - 5 部分换句话说,两个值都保留在那里。 字符串缓冲区:
  • String值存储在堆栈中。如果更改了值,则新值将替换旧值。
  • String Buffer是同步的,因此是线程安全的。
  • 由于线程安全,其性能较差。
例子:

StringBuffer name = “book”;
Java Core 的前 50 个工作面试问题和答案。 第 1 - 6 部分name 变量的值一变,栈中的值就变了: Java Core 的前 50 个工作面试问题和答案。 第 1 - 7 部分StringBuilder和 完全一样StringBuffer,只是它不是线程安全的。结果,它明显快于StringBuffer.

18. 抽象类和接口有什么区别?

抽象类:
  • 抽象类有一个默认的构造函数。每次创建抽象类的后代时都会调用它。
  • 它们可以包括抽象方法和非抽象方法。通常,抽象类不必具有抽象方法。
  • 继承抽象类的类必须只实现抽象方法。
  • 抽象类可以有实例变量(见问题 #5)。
界面:
  • 接口没有构造函数,无法初始化。
  • 只能添加抽象方法(默认方法除外)。
  • 实现接口的类必须实现所有方法(默认方法除外)。
  • 接口只能有常量。

19. 为什么访问数组中的元素是 O(1)?

这个问题是在我上次采访中被问到的。后来才知道,这道题的目的是看一个人是怎么想的。显然,这些知识几乎没有实用价值。仅仅知道它就足够了。首先,我们需要澄清 O(1) 是“恒定时间”算法的时间复杂度的表示法。换句话说,这个名称表示最快的执行时间。要回答这个问题,我们需要考虑一下我们对数组的了解。要创建一个int数组,我们必须编写以下内容:

int[] intArray = new int[100];
从这个语法中可以得出几个结论:
  1. 声明数组时,其类型是已知的。如果类型已知,则数组中每个单元格的大小也是已知的。
  2. 整个数组的大小是已知的。
因此,要了解要写入哪个单元格,我们只需要计算要写入的内存区域即可。对于计算机来说,这很容易。计算机知道分配的内存从哪里开始、元素的数量以及每个单元格的大小。这一切意味着写入的位置将等于数组的起始位置 + 每个单元格的大小乘以索引。

那么在访问 ArrayList 中的对象时我们如何达到 O(1) 呢?

这个问题紧接在上一个问题之后。事实是,在处理包含基元的数组时,我们提前(在创建时)知道元素类型的大小。但是如果我们有这种继承层次并且 Java Core 的前 50 个工作面试问题和答案。 第 1 - 8 部分我们想为类型 A 的元素创建一个集合并添加不同的实现(B、C 和 D),我们该怎么办:

List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
在这种情况下,我们如何计算每个单元格的大小呢?毕竟,每个对象都是不同的,可能具有不同的附加字段。该怎么办?在这里提出问题的方式是为了让您感到困惑。我们知道集合并不直接存储对象。它只存储对对象的引用。并且所有引用都具有相同的大小,这是已知的。因此,我们在这里计算地址的方式与上一个问题相同。

21. 自动装箱和拆箱

历史背景:自动装箱和拆箱是 JDK 5 中的一些主要创新。 自动装箱是从原始类型自动转换为相应包装类的过程。 拆箱与自动装箱完全相反。它是将包装类转换为原语的过程。但是如果 wrapper 的值为null,那么NullPointerException在拆箱时会抛出 a 。

基元及其相应的包装器

原始 包装类
布尔值 布尔值
整数 整数
字节 字节
字符 特点
漂浮 漂浮
长的 长的
短的 短的
双倍的 双倍的

// 自动装箱发生:

  • 将原语分配给包装类的引用时:

    在 Java 5 之前:

    
    // Manual boxing (the way it was BEFORE Java 5).
    public void boxingBeforeJava5() {
       Boolean booleanBox = new Boolean(true);
       Integer intBox = new Integer(3);
       // And so on for other types
    }
    
    After Java 5:
    // Automatic boxing (the way it became in Java 5).
    public void boxingJava5() {
       Boolean booleanBox = true;
       Integer intBox = 3;
       // And so on for other types
    }
    
  • 当原语作为参数传递给需要包装器的方法时:

    
    public void exampleOfAutoboxing() {
       long age = 3;
       setAge(age);
    }
    
    public void setAge(Long age) {
       this.age = age;
    }
    

// 拆箱发生:

  • 当我们将包装类的实例分配给原始变量时:

    
    // BEFORE Java 5:
    int intValue = new Integer(4).intValue();
    double doubleValue = new Double(2.3).doubleValue();
    char c = new Character((char) 3).charValue();
    boolean b = Boolean.TRUE.booleanValue();
    
    // And after JDK 5:
    int intValue = new Integer(4);
    double doubleValue = new Double(2.3);
    char c = new Character((char) 3);
    boolean b = Boolean.TRUE;
    
  • 在算术运算过程中。这些操作仅适用于原始类型,因此需要对原始类型进行拆箱。

    
    // BEFORE Java 5:
    Integer integerBox1 = new Integer(1);
    Integer integerBox2 = new Integer(2);
    
    // A comparison used to require this:
    integerBox1.intValue() > integerBox2.intValue()
          
    // In Java 5
    integerBox1 > integerBox2
    
  • 将包装类的实例传递给采用相应原语的方法时:

    
    public void exampleOfAutoboxing() {
       Long age = new Long(3);
       setAge(age);
    }
    
    public void setAge(long age) {
       this.age = age;
    }
    

22.什么是final关键字,用在什么地方?

关键字final可用于变量、方法和类。
  1. final 变量的值在初始化后不能更改。
  2. 最后一堂课是不育的 :) 它不能有孩子。
  3. final 方法不能被后代覆盖。
我们已经介绍了高级内容。现在让我们深入探讨。

最终变量

Java 为我们提供了两种声明变量并为其赋值的方法:
  1. 您可以声明一个变量并稍后对其进行初始化。
  2. 您可以声明一个变量并立即赋值。
这是一个演示最终变量的这些用法的示例:

public class FinalExample {

   // A static final variable that is immediately initialized:
   final static String FINAL_EXAMPLE_NAME = "I'm likely the final one";

   // A final variable that is not initialized, but will only work if you
   // initialize it in the constructor:
   final long creationTime;

   public FinalExample() {
       this.creationTime = System.currentTimeMillis();
   }

   public static void main(String[] args) {
       FinalExample finalExample = new FinalExample();
       System.out.println(finalExample.creationTime);

       // The final FinalExample.FINAL_EXAMPLE_NAME field cannot be accessed
//    FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";

       // The final Config.creationTime field cannot be accessed
//    finalExample.creationTime = 1L;
   }
}

最终变量可以被视为常量吗?

由于我们不能为 final 变量赋新值,因此这些似乎是常量变量。但乍一看:如果变量的数据类型是immutable,那么,是的,它是一个常量。但是如果数据类型是mutable,即可变的,那么就可以使用方法和变量来改变变量引用的对象的值final。因此,它不能称为常数。下面的例子表明,一些最终变量是真正的常量,而另一些则不是,因为它们可以改变。

public class FinalExample {

   // Immutable final variables
   final static String FINAL_EXAMPLE_NAME = "I'm likely the final one";
   final static Integer FINAL_EXAMPLE_COUNT  = 10;

   // Mutable final variables
   final List<String> addresses = new ArrayList();
   final StringBuilder finalStringBuilder = new StringBuilder("Constant?");
}

本地最终变量

final在方法中创建变量时,它被称为变量local final

public class FinalExample {

   public static void main(String[] args) {
       // You can do this
       final int minAgeForDriveCar = 18;

       // Or you can do this, in a for-each loop:
       for (final String arg : args) {
           System.out.println(arg);
       }
   }

}
我们可以在增强的 for 循环中使用 final 关键字,因为在每次循环迭代后都会创建一个新变量。请记住,这不适用于普通的 for 循环,因此我们会遇到编译时错误。

// The final local j variable cannot be assigned
for (final int i = 0; i < args.length; i ++) {
   System.out.println(args[i]);
}

最后一堂课

final声明为不能扩展的类。更简单地说,没有其他类可以继承它。finalJDK 中类的一个很好的例子是 String。创建不可变类的第一步是将其标记为final,从而防止它被扩展:

public final class FinalExample {
}

// Compilation error!
class WantsToInheritFinalClass extends FinalExample {
}

最终方法

当一个方法被标记为 final 时,它被称为 final 方法(有道理,对吧?)。不能在子类中重写 final 方法。顺便说一句,Object 类的 wait() 和 notify() 方法是最终的,所以我们没有能力覆盖它们。

public class FinalExample {
   public final String generateAddress() {
       return "Some address";
   }
}

class ChildOfFinalExample extends FinalExample {

   // Compilation error!
   @Override
   public String generateAddress() {
       return "My OWN Address";
   }
}

在 Java 中如何以及在何处使用 final

  • 使用final关键字定义一些类级别的常量;
  • 为您不想更改的对象创建最终变量。例如,我们可以用于记录目的的特定于对象的属性。
  • 如果您不想扩展某个类,则将其标记为最终类。
  • 如果您需要创建一个不可变类,则需要将其设为 final。
  • 如果您希望方法的实现在其后代中不发生变化,则将方法标记为final. 这对于确保实现不会改变非常重要。

23.什么是可变类型和不可变类型?

可变的

可变对象是其状态和变量在创建后可以更改的对象。可变类的示例包括 StringBuilder 和 StringBuffer。例子:

public class MutableExample {

   private String address;

   public MutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // This setter can change the name field
   public void setAddress(String address) {
       this.address = address;
   }

   public static void main(String[] args) {

       MutableExample obj = new MutableExample("First address");
       System.out.println(obj.getAddress());

       // We are updating the name field, so this is a mutable object
       obj.setAddress("Updated address");
       System.out.println(obj.getAddress());
   }
}

不变的

不可变对象是在创建对象后其状态和变量无法更改的对象。HashMap 的一个很好的键,你不觉得吗?:) 例如,String、Integer、Double 等。例子:

// We'll make this class final so no one can change it
public final class ImmutableExample {

   private String address;

   ImmutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // We remove the setter

   public static void main(String[] args) {

       ImmutableExample obj = new ImmutableExample("Old address");
       System.out.println(obj.getAddress());

       // There is no way to change this field, so it is an immutable object
       // obj.setName("new address");
       // System.out.println(obj.getName());

   }
}
在下一部分中,我们将考虑有关集合的问题和答案。 我在 GitHub 上的个人资料 Java Core 的前 50 个工作面试问题和答案。第2部分
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION