1. Java 内存管理面试指南一
  2. Java 基础面试指南一
  3. Java 基础面试指南二
  4. Java 基础面试指南三
  5. Java 基础面试指南四
  6. Java 线程面试指南一
  7. Java 线程面试指南二
  8. Redis 面试指南一
  9. Kafka 面试指南一
  10. Spring 面试指南一
  11. SpringBoot 面试指南一
  12. 微服务面试指南一

1. Java 是否完全面向对象?

Java 支持面向对象编程范式(Object-Oriented Programming Paradigm),但它并不是完全面向对象的语言。Java 包含一组原始数据类型(Primitive Types):byteshortcharintlongfloatdoubleboolean。这些类型的变量不是对象,这就是 Java 不是纯粹面向对象语言的原因。

2. 包装类的目的是什么?

包装类(Wrapper Class)用于将原始数据类型包装为对象。原始值不是对象,开发人员通常需要编写大量样板代码才能在集合中使用它们或进行转换。为了克服这些问题,Java 引入了包装类。这些类提供了用于数据类型转换的多态 API,以及诸如 hashCode()equals() 之类的实用方法,使得值在面向对象的环境中更加有用。

3. 什么是多态性?

多态性(Polymorphism)是面向对象编程范式的一个属性,表示对象或方法在不同的上下文中可以具有不同的形式。在 Java 中,我们可以根据参数在具有不同实现的类中定义同名方法。这样,当客户端代码使用具有不同参数集的相同接口调用该方法时,内部会决定需要调用哪种实现。

示例如下:

class AreaCalculator {
    double calculate(Circle c) {
        return 3.14 * c.getRadius() * c.getRadius();
    }

    double calculate(Square s) {
        return s.getLength() * s.getLength();
    }
}

// Client code
AreaCalculator ac = new AreaCalculator();
ac.calculate(new Circle(10));
ac.calculate(new Square(5)); 

如上所示,在 AreaCalculator 类中,calculate() 有单独的实现,但从客户端的角度来看,接口是相同的。

4. 用 Java 有多少种方法可以创建对象?

我们可以通过以下几种方法在 Java 中创建对象:

  1. 使用 new 运算符:在类中使用 new 运算符创建新对象,这将调用构造函数。

    MyClass o = new MyClass(); 
  2. 使用反射 API:如果类具有默认构造函数,可以使用 Class.newInstance() 创建对象。如果类具有带参数的构造函数,可以使用 Class.getConstructor() 获取相应的构造函数然后调用。

    MyClass o = MyClass.class.newInstance();
    MyClass o = MyClass.class.getConstructor(int.class).newInstance(10);
  3. 使用 clone 方法:在一个对象上调用 clone 方法来创建一个重复的对象。

    MyClass o = new MyClass(); 
    MyClass b = (MyClass) o.clone(); 
  4. 使用反序列化:如果对象的状态以序列化形式可用,我们可以将其反序列化为一个具有相同状态的新对象。

    ObjectInputStream is = new ObjectInputStream(anIStream); 
    MyClass o = (MyClass) is.readObject(); 

5. 什么是运行时多态?

编译时多态(静态多态)决定在编译时需要调用哪个方法,并将方法调用与调用绑定在一起。但是,在某些情况下静态绑定不起作用。众所周知,父类引用可以指向父对象以及子对象。如果在父类和子类中都存在一个具有相同签名的方法,并且我们从父类引用中调用该方法,编译器无法确定该方法与该调用绑定。这将取决于引用在运行时所指向的对象类型。

  • 如果引用指向父对象,则将调用父类中的方法。
  • 如果指向的对象是子类的实例,则将调用子类实现。

这就是为什么将其称为动态绑定(Dynamic Binding)或运行时多态性(Runtime Polymorphism),并且据说子类方法已覆盖(Override)父类方法。

示例如下:

class Animal {
    public void makeSound() {
        System.out.println("My sound varies based on my type");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("I bark");
    }
}

Animal a = new Animal();
a.makeSound();
a = new Dog();
a.makeSound(); 

如果运行代码段,我们会发现 a.makeSound() 根据引用指向的对象类型打印不同的消息。请注意,子类中需要 @Override 注解,以通知编译器此方法将覆盖父实现。

6. StringBuffer 和 StringBuilder 有什么区别?

StringBufferStringBuilder 公开相同类型的 API 以构建 String,它们都是可变类。但是,它们之间有很大的不同:

  • StringBuffer 是线程安全的,这意味着它可以用作多个线程之间的共享对象。
  • StringBuilder 不是线程安全的,如果没有适当的同步技术,则不允许多个线程修改 StringBuilder

这就是 StringBuilderStringBuffer 更快的原因。在需要构建方法本地或特定线程本地的 String 对象的情况下,我们应该使用 StringBuilder 而不是 StringBuffer

7. 接口和抽象类之间有什么区别?什么时候我们应该比抽象类更喜欢接口?

一个类既可以继承抽象类又可以实现接口,但是有一些区别:

  • 一个类只能扩展一个抽象类,而可以实现多个接口。
  • 接口不能具有构造函数,而抽象类可以具有子类需要在其构造函数中调用的构造函数。
  • 抽象类可能包含子类可以访问以更改其状态的字段。另一方面,接口只能包含最终变量(final)。
  • 因此,抽象类允许子类继承状态和行为,而接口主要用于在子类中实现一组行为。

使用建议:

  • 如果父级和子级之间存在直接 IS-A 关系,则应该首选抽象类。
  • 当不同的实现具有大多数常见行为并由抽象类定义时,我们将使用抽象类。
  • 在将公共 API 公开给客户端代码以及类在不同上下文中的行为不同时,最好使用接口。

8. Java 是否支持多重继承?

Java 不完全支持多重继承,因为一个类只能扩展一个类。不支持此功能是因为如果另一个父类中存在相同的成员,则这可能导致在访问继承的字段或方法时产生歧义。但是,该功能是通过接口部分提供的(一个类可以实现多个接口)。

9. Class 上的静态成员对您有什么了解?

如果成员不依赖于该类的任何实例,即独立于任何对象,则将其声明为静态成员(Static Member)。这些成员绑定到类型,通常使用类型名称(而不是对象引用)进行访问。

  • 静态方法和字段在类的所有对象之间共享,并且可以从任何一个对象中访问。
  • 我们不能从静态方法访问非静态成员。
  • 由于静态方法未绑定到对象,因此无法覆盖它们。
  • 静态字段是通过静态块初始化的,该静态块在类加载器加载类时执行。

示例如下:

public class Countable {
    private static int count;
    private int x;

    static {
        count = 0; // initialize static member
    }

    public Countable(int x) {
        this.x = x;
        count++; // non-static can access static member
    }

    public int getX() {
        return x;
    }

    public static int getCount() {
        return count; // only static can access static member
    }
}

Countable c1 = new Countable(10);
Countable c2 = new Countable(20);
System.out.println("Object count " + Countable.getCount());
// should print 2 

10. ArrayList 和 LinkedList 有什么区别?

ArrayListLinkedList 都是列表,但内部实现不同:

  • ArrayList:在内部使用一个数组来存储添加到其中的元素。当元素的数量即将超过数组的大小时,它将分配一个新的数组并将元素复制到新的位置。它为添加(如果不需要扩展)和获取元素提供恒定的时间访问时间,但是对于删除,由于需要将其元素向左移动,它提供了线性的时间复杂度。
  • LinkedList:在内部维护用于存储元素的一系列链接节点。因此,以线性时间检索元素,而添加和删除则需要一定的时间才能执行(取决于位置)。

11. HashTable 和 HashMap 有什么区别?

HashTableHashMap 都存储键 - 值对,并花费固定的时间进行放置或获取操作。

  • 同步性Hashtable 是同步的,可以共享以供多个线程修改;而 HashMap 不同步且性能更好,但不适合作为共享对象的多线程环境。
  • Null 值HashTable 不允许 Null 键或值,但 HashMap 允许 Null 键和多个 Null 值。

12. 如何从 HashMap 检索值?

为了检索或存储值,HashMap 使用其 Key 类的两个方法:hashCode()equals()

  1. HashMap 将其条目存储在大量的存储桶(Bucket)中,可以使用索引对其进行随机访问。
  2. 要检索值,首先调用 Key 的 hashCode() 方法以获取哈希值。此哈希值用于标识将从中检索值的存储桶。
  3. 在存储条目时,可能存在某些情况,其中多个密钥计算出的哈希值相同。这导致在同一存储桶中输入多个键值对。
  4. 存储桶将其条目保留为链表(Java 8+ 中超过阈值会转为红黑树)。因此,在检索时,在找到适当的存储桶之后,需要遍历此链表以找到密钥的实际条目。
  5. 这次,equals() 方法用于比较列表中每个条目的键。一旦找到相等的键,则返回条目中的值。

约定:如果两个对象基于 equals() 方法相等,则它们的 hashCode() 值必须相同。因此,如果我们计划将类的对象用作 HashMap 的键,则应该覆盖方法 hashCode()equals(),以便保持此协定。

13. 什么是 Java 中的 public static void main(String args [])

public static void main(String args []) 是 Java 程序的主要方法。它是任何 Java 程序的入口。没有 main 方法,则无法执行 Java 程序。

main 方法的语法如下:
public static void main(String args[])

只能将 args 更改为不同的名称,但是该方法的其余部分具有相同的语法。让我们通过分解来分析主要方法:

  • public:这是 main 方法的访问说明符/访问修饰符。应该公开允许类访问它。如有违反,程序将显示错误。
  • static:在运行时开始时,该类没有任何对象。因此,main 方法必须是静态的,以便 Java 虚拟机可以将类设置为内存并调用 main 方法。如果 main 方法不是静态的,那么由于缺少对象,JVM 将无法调用它。
  • void:这是 main 方法的返回类型。Java 中的每个方法都必须具有返回类型。main 方法不返回任何其他方法可以访问的值,因为 main 方法不能在任何其他方法中调用。一旦 main 方法完成执行,程序就会终止。因此,返回值没有任何用处。
  • main:这是 main 方法的名称。此名称已设置,无法更改。如果对 main 关键字进行了任何更改,则编译器会告知找不到 main 方法。
  • String args []main 方法可以接受一个 String 类型的参数。它们也称为命令行参数。在 main 方法编写的代码中,可以将它们用作常规参数来使用和操纵。它们与执行语句一起编写。

14. 在 Java 中创建自定义异常

自定义异常只不过是用户定义的异常。Java 自定义异常用于根据用户的要求进行和修改异常。

在创建自定义异常之前,让我们看一下它的先决条件:

  • 创建的所有异常必须是 Throwable 类的子类。
  • 使用 extends 关键字并扩展 Exception 类。
  • 如果要编写运行时异常,则需要扩展 RuntimeException 类。

现在让我们创建一个父自定义异常,如果数字不能被 3 整除,则该异常将生成一个异常:

class CustomException extends Exception {
    CustomException(String errormsg) {
        super(errormsg);
    }
}

public class Example {
    static void validate(int num) throws CustomException {
        if (num % 3 != 0)
            throw new CustomException("Not divisible by 3");
        else
            System.out.println("Divisible by 3");
    }

    public static void main(String args[]) {
        try {
            validate(10);
        } catch (Exception e) {
            System.out.println("Exception caught: " + e);
        }
        System.out.println("Outside the try-catch block");
    }
} 

上面程序的输出如下:

$javac Example.java
$java Example
Exception caught: CustomException: Not divisible by 3
Outside the try-catch block

15. 公共静态 void main(String args []) 中的"static"是什么意思?

public static void main(String args []) 是 Java 程序的主要方法。这是最重要的 Java 方法。它是任何 Java 程序的入口。没有 main 方法,则无法执行 Java 程序。

static 关键字充当访问修饰符。当 Java 虚拟机调出 main 方法时,它没有对象可以调用。因此,我们使用 static 来允许类调用它。

如果我们不在 public static void main(String args []) 中使用 static,则编译器将给出错误消息。

public class Example {
   public void main(String args[]) {
       System.out.println("Hello");
   }
}

输出窗口如下:

$javac Example.java
$java Example
Error: Main method is not static in class Example, please define the main method as:
   public static void main(String[] args)

16. Java 中的 ByteCode

在编译期间,Java 编译器会将源代码转换为字节码(ByteCode)。

ByteCode 是一组高度发达的指令,提供给 Java 虚拟机以生成机器代码。它是 .class 文件形式的机器代码。可以用 C++ 进行类比。之所以称为 ByteCode,是因为每个指令为 1-2 个字节。

Java 与其他面向对象语言的区别之一是它与平台无关。该平台独立性是通过 Java ByteCode 实现的。

Java 虚拟机提供了执行字节码所需的组件,该虚拟机调用处理器分配所需的资源。JVM 基于堆栈,因此它们实现堆栈来读取和处理字节码。

某些 ByteCode 指令如下:
1:istore_1 2:iload_1 3:sipush 1000 6:if_icmpge 44 9:iconst_2 10:istore_2

代码段是保存 ByteCode 的内存段。

17. Java 中的编译时 vs 运行时多态

以下是比较:

基础编译时多态运行时多态
定义编译时确定方法调用运行时确定方法调用
别名也称为静态多态也称为动态多态
发生它在编译时发生它在运行时发生
实现通过方法重载实现它是通过方法重写实现的
速度方法执行更快方法执行速度较慢
特征它增加了程序的可读性它为程序提供了特定的实现

下面给出了使用方法重载实现编译时多态的示例:

class Overload {
   public void display(char c) {
        System.out.println(c);
   }
   public void display(char c, int num) {
        System.out.println(c + " " + num);
   }
}

public class Example {
  public static void main(String args[]) {
      Overload obj = new Overload();
      obj.display('s');
      obj.display('s', 12);
  }
}

上面程序的输出是:

$javac Example.java
$java Example
s
s 12

给出了使用方法覆盖实现运行时多态的示例,如下所示:

class SuperClass {
   void display() { System.out.println("SuperClass"); }
}

class SubClass extends SuperClass {
   void display() {
       System.out.println("Subclass");
   }
}

public class Example {
   public static void main(String[] args) {
       SuperClass obj1 = new SuperClass();
       obj1.display();
       SuperClass obj2 = new SubClass();
       obj2.display();
   }
}

输出如下:

$javac Example.java
$java Example
SuperClass
Subclass

18. Java 中的字符串池

Java 中的字符串池是存储在 Java 堆内存中的字符串池或字符串集合。使用双引号初始化 String 时,它将首先在字符串池中搜索具有相同值的 String。如果匹配,则仅返回引用,否则,它将在字符串池中生成一个新的字符串,然后返回其引用。

字符串池的可能性仅由于 Java 中字符串的不可变性而存在。

如果使用 new 运算符创建 String,则 String 类有权在字符串池中创建新的 String

例如:

public class Example {
   public static void main(String[] args) {
       String s1 = "Hello";
       String s2 = "Hello";
       String s3 = new String("Hello");
       System.out.println("Do s1 and s2 have the same address? " + (s1 == s2));
       System.out.println("Do s1 and s3 have the same address? " + (s1 == s3));
   }
}

由于字符串池的实现,输出将显示 s1s2 具有相同的地址。但是,由于使用了 new 关键字,因此字符串 s3 将具有不同的地址。

$javac Example.java
$java Example
Do s1 and s2 have the same address? true
Do s1 and s3 have the same address? false

19. 如果我写 System.exit(0); 在 try 块的末尾,是否会继续执行 finally 块?

不,finally 块将不会执行。System.exit() 方法是 java.lang 包的预定义方法。通过终止 Java 虚拟机的运行,它将退出当前程序。

System.exit() 方法具有一个由整数值表示的状态代码。通常状态码为 0。非零状态码表示 Java 程序异常终止。

java.lang 包中 exit 方法的语法如下所示:
public static void exit(int status_code);

Java 中的 finally 块通常执行所有操作,而与 trycatch 块中编写的内容无关。当在 try 块中写入 System.exit() 函数时,会产生例外。

例如:

public class Example {
   public static void main(String[] args) {
       try {
        System.out.println("In try block");
        System.exit(0);
        int a = 1 / 0;
       } catch (ArithmeticException e) {
           System.out.println("Exception Caught");
       } finally {
           System.out.println("In finally block");
       }
   }
}

输出如下:

$javac Example.java
$java Example
In try block

20. 什么时候调用类的构造函数?

创建对象时将调用类的构造函数。每次使用 new 关键字创建对象时,都会调用构造函数。构造函数初始化同一类的数据成员。

class Example {
 Example() // constructor
 {
 }
}
Example obj = new Example(); // invokes above constructor

构造函数用于初始化对象的状态。它包含在创建对象时执行的语句列表。

例如:

public class Example {
   Example()  // constructor of class Example
   {
       System.out.println("This is a constructor");
   }
   public static void main(String []args) {
       Example e = new Example();
       // constructor Example() is invoked by creation of new object e          
   }
}

上面的输出如下:

$javac Example.java
$java Example
This is a constructor

21. Thread 类的 yield 方法

Thread 类的 yield() 方法用于暂时停止线程的执行并执行。

对于 java.lang.Thread 类,yield 方法的语法如下:
public static void yield()

通过三种方式阻止执行线程,即 yield()sleep()join()

在某些情况下,一个线程要花费更多的时间来完成其执行,我们需要找到一种解决方案来延迟线程的执行,如果有重要的事情待解决,则可以在两个线程之间快速完成其执行。yield() 提供了此问题的答案。

例如:

import java.lang.*;

class ExampleThread extends Thread {
   public void run() {
       for (int i = 0; i < 2; i++)
           System.out.println(Thread.currentThread().getName() + " in control");
   }
}

public class Example {
   public static void main(String[] args) {
       ExampleThread t = new ExampleThread();
       t.start();  // this calls the run() method
       for (int i = 0; i < 2; i++) {
           Thread.yield();
           System.out.println(Thread.currentThread().getName() + " in control");
       }
   }
}

上面程序的输出是:

$javac Example.java
$java Example
Thread-0 in control
Thread-0 in control
main in control
main in control

每个系统的输出可能不同,但是 yield() 线程执行的可能性更大。

22. final, finalize 和 finally 之间有什么区别?

finalfinalizefinally 之间有很多差异:

  • final:是用于减少应用类、方法或变量的关键字。最终类不能被子类继承,声明为 final 的方法不能被覆盖,声明为 final 的变量不能被更新。
  • finalize:是一种用于在 Java 中进行垃圾回收之前清理处理的方法。没有对象的进一步引用时,该对象将由该对象的垃圾收集器调用。
  • finally:它是 try-catch-finally 流块的一部分,用于执行代码,而与 trycatch 块中的异常处理无关。

finalfinally 不同,finalize 不是 Java 中的保留关键字。可以显式调用 finalize 方法,这导致它作为常规方法调用执行,并且不会导致对象的破坏。

让我们看一个违反使用 final 关键字的程序:

public class Example {  
   public static void main(String[] args) {
           final int x = 1;  
           x++;
    }
}

该程序将生成一个编译时错误,因为最终变量无法更新:

$javac Example.java
Example.java:6: error: cannot assign a value to final variable x
           x++;//Compile Time Error  
           ^
1 error

现在让我们看看 Java 中的 finally 关键字的用法:

public class Example {
   public static void main(String[] args) {
       try {
        System.out.println("In try block");
        int a = 1 / 0;
       } catch (ArithmeticException e) {
           System.out.println("Exception Caught");
       } finally {
           System.out.println("In finally block");
       }
   }
}

该程序将给出以下输出:

$javac Example.java
$java Example
In try block
Exception Caught
In finally block

现在让我们看一下 Java 中 finalize 方法的重载:

public class Example {
   public static void main(String[] args) {
       String str = new String("Example");
       str = null;
       System.gc(); // prompts the JVM to call the Garbage Collector
       System.out.println("End of main method");
   }
   public void finalize() {
       System.out.println("This is the finalize method");
   }
}

输出如下:

$javac Example.java
$java Example
End of main method

23. 用 Java 生成随机数

Java 为我们提供了三种生成随机数的方法:

  • 使用 java.util.Random
  • 使用 java.util.Math.random() 方法
  • 使用 java.util.concurrent.ThreadLocalRandom

java.util.Random 类

我们创建此类的对象,并使用该对象调用预定义的方法,例如 nextInt()nextDouble()。通过这种方法可以产生几种数据类型的随机数。假设如果将参数 x 传递给 nextInt(),那么我们将获得从 0 到 x-1 的任何随机整数。

例如:

import java.util.Random;

public class Example {
     public static void main(String args[]) {
       Random r = new Random();
       int r1 = r.nextInt(20);   // generates random integers from 0 to 19
       int r2 = r.nextInt(100);  // generates random integers from 0 to 99
       System.out.println("The first random number generated is: " + r1);
       System.out.println("The second random number generated is " + r2);
   }
}

随后的输出如下:

$javac Example.java
$java Example
The first random number generated is: 2
The second random number generated is 23

Math.random() 方法

Math.random()java.util.Math 类的方法。它返回介于 0.0(含) 和 1.0(不含) 之间的正 double 值。

import java.util.*;

public class Example {  
   public static void main(String args[]) {
       double x = Math.random();
       System.out.println("Random number between 0.0 and 1.0 is " + x);
   }
}

以下内容的随机输出如下:

$javac Example.java
$java Example
Random number between 0.0 and 1.0 is 0.7534013549366972

ThreadLocalRandom 类

这是 JDK 1.7 中引入的相对较新的功能。ThreadLocalRandom 给出整数、双精度数、浮点数和布尔值的随机值。

例如:

import java.util.concurrent.ThreadLocalRandom;

public class Example {
   public static void main(String args[]) {
       int intVal = ThreadLocalRandom.current().nextInt();
       System.out.println("A random integer : " + intVal);
       double doubVal = ThreadLocalRandom.current().nextDouble();
       System.out.println("A random double number : " + doubVal);
   }
}

输出如下:

$javac Example.java
$java Example
A random integer : 1700060375
A random double number : 0.24593329857940383

24. JVM 分配的内存区域类型

JVM 代表 Java 虚拟机。是提供运行时环境的驱动力。它将 Java ByteCode 转换为机器代码。它是一种抽象机器,提供了在运行时执行 Java 程序的规范。

当我们编译一个 .java 文件时,Java 编译器会以扩展名 .class 创建与该类具有相同名称的文件。这些文件包含字节码。执行 .class 文件时,涉及许多步骤。这些步骤提供了 Java 虚拟机的详细说明。

JVM 中通常分配 6 种类型的内存区域:

  • Classloader:Classloader 是 JVM 的子系统,并且是 Java Runtime Environment 的一部分,可在 JVM 中动态加载类。
  • 方法区域 (Method Area):方法区域在所有线程中都是公用的。它由每个类的元素组成,例如常量池、字段、方法数据和代码以及构造函数代码。该区域是在 JVM 启动期间创建的。如果内存不足,则会给出 OutOfMemoryError
  • 堆 (Heap):分配给该内存结构的变量可以在运行时进行管理。这导致动态内存分配。
  • 堆栈 (Stack):如果事先知道所需的内存量,则使用堆栈分配内存。
  • 程序计数器寄存器 (Program Counter Register):它包含当前正在执行的 Java 虚拟机指令的地址。程序是计算机执行少量特定任务的一组指令。程序计数器寄存器包含即将到来的指令的地址。
  • 本机方法堆栈 (Native Method Stack):它是应用程序中使用的所有本机方法的集合。

25. Java 中 finalize() 的目的

finalize() 是一种用于在 Java 中进行垃圾回收之前清理处理的方法。没有对象的进一步引用时,该对象将由该对象的垃圾收集器调用。

子类将 finalize() 覆盖,以摆脱系统资源或进行其他清理。此方法引发的异常称为 Throwable 异常。

java.lang.Object.finalize() 方法不带任何参数,也不返回值。

现在让我们看一下 Java 中 finalize() 方法的重载:

public class Example {
   public static void main(String[] args) {
       String str = new String("Example");
       str = null;
       System.gc(); // prompts the JVM to call the Garbage Collector
       System.out.println("End of main method");
   }
   public void finalize() {
       System.out.println("This is the finalize method");
   }
}

输出如下:

$javac Example.java
$java Example
End of main method

26. 什么是 JIT 编译器?

JIT 代表准时制(Just In Time)。JIT 编译器是将 Java ByteCode 转换为处理器级指令的程序。

JIT 编译器在程序启动后运行,并在程序运行到更快、更本地的处理器级别指令集中时编译字节码。

完成编写 Java 程序后,Java 编译器会将源代码编译为字节码。然后,字节码由 JIT 编译器转换为处理器级指令。因此,JIT 编译器充当第二个编译器。

JIT 编译器与程序执行同时运行。它将字节码编译为立即执行的平台特定的可执行代码。一旦代码由 JIT 编译器重新编译,它就可以在系统上相对较快地运行。

27. Java 中的 NumberFormatException

Java 中的 NumberFormatExceptionjava.lang 包的异常,当我们尝试将 String 转换为数字数据类型(例如 int, float, double, longshort)时,抛出该异常。当字符串没有适当的格式时,会发生这种情况。

例如,我们尝试将非数字类型的字符串解析为整数:

public class Example {
   public static void main(String[] args) {
           String str = "Number";
           int intVal = Integer.parseInt(str);
           System.out.println(intVal);
   }
}

上面的代码将引发 NumberFormatException

$javac Example.java
$java -Xmx128M -Xms16M Example
Exception in thread "main" java.lang.NumberFormatException: For input string: "Number"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at Example.main(Example.java:8)

但是,如果 String 为数字类型,它将成功将其解析为整数:

public class Example {
   public static void main(String[] args) {
           String str = "12";
           int intVal = Integer.parseInt(str);
           System.out.println(intVal);
   }
}

输出如下:

$javac Example.java
$java -Xmx128M -Xms16M Example
12

28. 用 Java 打印堆栈跟踪信息

堆栈跟踪是特定时刻调用堆栈的特征,每个元素都描述一个方法调用语句。堆栈跟踪包含从线程开始到生成异常为止的所有调用语句。

打印堆栈跟踪时,首先要打印出生成点是异常,然后打印方法调用语句,这有助于我们确定失败的根本原因。

这是在 Java 中打印堆栈跟踪的示例:

public class Example {
  public static void main (String args[]) {
      int arr[] = {1, 2, 3, 4};
      int num1 = 10, num2 = 0;
      int ans;
      try {
         System.out.println("The output is...");
         ans = num1 / num2;
         System.out.println("The result is " + ans);
       } catch (ArithmeticException ex) {
         ex.printStackTrace();
       }
  }
}

输出如下:

$javac Example.java
$java Example
The output is...
java.lang.ArithmeticException: / by zero
at Example.main(Example.java:11)

29. 在 Java 中将十六进制数转换为十进制数

parseInt() 方法将十六进制转换为十进制。它甚至可以将八进制转换为十进制。您只需要设置基数即可。对于十六进制,基数为 16。

下面是一个示例:

public class Demo {
  public static void main(String args[]) {
      // hexadecimal string
      String str = "298";
      // hex to decimal
      System.out.println("Decimal = " + Integer.parseInt(str, 16));
  }
}

输出:

Decimal = 664

30. 我们如何从 Java 中的当前时间减去小时?

要减去小时数,您需要使用 HOUR_OF_DAY 常量。在其中包括带负号的数字。这就是您要减少的时间。所有这些都是在 Calendar add() 方法下完成的。

以下是一个示例:

import java.util.Calendar;

public class Example {
 public static void main(String[] args) {
   Calendar c = Calendar.getInstance();
   System.out.println("Date : " + c.getTime());
   // 2 hours subtracted
   c.add(Calendar.HOUR_OF_DAY, -2);
   System.out.println("After subtracting 2 hrs : " + c.getTime());
 }
}

这是输出:

Date : Sun Dec 16 16:28:53 UTC 2018
After subtracting 2 hrs : Sun Dec 16 14:28:53 UTC 2018

31. 如何在 Java 中添加前导零?

我们可以在 Java 中将前导零添加到数字中。让我们看看如何:

我们以数字为例:15
我们将使用以下代码将 6 个前导零添加到上述数字中。在这里,我们正在研究 String.format() 方法以实现相同的目的:

import java.util.Formatter;

public class Example {
   public static void main(String args[]) {
     int a = 15;
     System.out.println("Value = " + a);
     // adding leading zeros
     String res = String.format("%08d", a);
     System.out.println("Updated = " + res);
   }
}

输出:

Value = 15
Updated = 00000015

32. 如何在 Java 中用逗号 (,) 分割字符串

split() 方法用于根据正则表达式拆分字符串。第一个参数是相同的正则表达式,而如果第二个参数为零,则它返回所有与正则表达式匹配的字符串。

我们的示例字符串:The TV, and the remote

要用逗号分割字符串,下面是示例:

public class Example {
 public static void main(String[] args) {
     String s = "The TV, and the remote";
     System.out.println("Initial String = " + s);
     String[] str = s.split("[,]", 0);
     System.out.println("\nSplitted string: ");
     for (String val : str) {
       System.out.println(val);
     }
   }
}

输出:

Initial String = The TV, and the remote
Splitted string:
The TV
and the remote

33. Java 中是否存在 sizeof?

不,Java 中不存在 sizeof 运算符。

Java 中的所有原始数据类型(例如 int, char, float, double, long, short)在 Java 中均具有预定义的大小。因此,对 sizeof 运算符没有特殊要求。

同样,在 Java 中,原始数据类型的大小与平台(即 Windows, Linux)无关。在 32 位和 64 位以及 Windows 和 Linux 操作系统中,int 变量都将占用 4 个字节。

布尔值的大小不是固定的,取决于 JVM。不同的 JVM 可能具有不同的布尔值。通常,布尔值的大小为 1 位。

以下是一些具有固定大小的原始数据类型:

Data typeDefault size
char2 bytes
byte1 byte
short2 byte
int4 bytes
long8 bytes
float4 bytes
double8 bytes
boolean1 bit

从 Java 8 开始,所有原始包装器类均以位为单位提供 SIZE 常量。由于 1 个字节 = 8 位,因此我们将常数除以 8,以获得包装类的大小(以字节为单位)。

public class Example {
 public static void main (String[] args) {
   System.out.println(" char: " + (Character.SIZE / 8) + " bytes");
   System.out.println(" byte: " + (Byte.SIZE / 8) + " bytes");
   System.out.println(" short: " + (Short.SIZE / 8) + " bytes");
   System.out.println(" int: " + (Integer.SIZE / 8) + " bytes");
   System.out.println(" long: " + (Long.SIZE / 8) + " bytes");
   System.out.println(" float: " + (Float.SIZE / 8) + " bytes");
   System.out.println(" double: " + (Double.SIZE / 8) + " bytes");
 }
}

该程序的输出如下:

$javac Example.java
$java Example
char: 2 bytes
byte: 1 bytes
short: 2 bytes
int: 4 bytes
long: 8 bytes
float: 4 bytes
double: 8 bytes

34. 什么是 Java 语言环境类

语言环境类(Locale Class)用于执行语言环境操作,并将语言环境信息提供给客户端或用户。

语言环境定义为一组参数,这些参数代表发生某些操作的地理位置或地点。

语言环境类声明如下:

public final class Locale
  extends Object
  implements Cloneable, Serializable

Locale 类使用以下构造函数:

  • Locale(String L):根据作为参数传递的语言代码初始化语言环境。
  • Locale(String L, String C):从作为参数传递的语言、国家/地区代码初始化语言环境。
  • Locale(String L, String C, String V):从作为参数传递的语言、国家/地区、变体初始化语言环境。

以下程序是实现语言环境类的示例:

import java.text.SimpleDateFormat;
import java.util.Locale;

public class Example {
    public static void main(String[] args) {
        Locale arr[] = SimpleDateFormat.getAvailableLocales();
        for (int i = 1; i <= 15; i++) {
            System.out.printf("\n%s (%s) ", arr[i].getDisplayName(), arr[i].toString());
        }
    }
}

下面的输出如下:

javac Example.java
$java Example
Arabic (United Arab Emirates) (ar_AE)
Arabic (Jordan) (ar_JO)
Arabic (Syria) (ar_SY)
Croatian (Croatia) (hr_HR)
French (Belgium) (fr_BE)
Spanish (Panama) (es_PA)
Maltese (Malta) (mt_MT)
Spanish (Venezuela) (es_VE)
Bulgarian (bg)
Chinese (Taiwan) (zh_TW)
Italian (it)
Korean (ko)
Ukrainian (uk)
Latvian (lv)
Danish (Denmark) (da_DK)

35. 如何制作单例类

单例类(Singleton Class)是只有一个对象的类。这意味着您只能实例化该类一次。当我们将该类的构造函数声明为 private 时,它将限制对象创建的范围;如果我们将该对象的实例返回给静态方法,则可以处理类本身内部的对象创建。

我们为创建对象创建一个静态块。

例如:

public class Example {
    private static Example obj;

    static {
        obj = new Example(); // creation of object in a static block
    }

    private Example() {
    }   // declaring the constructor as private

    public static Example getObject() {
        return obj;
    }

    public void print() {
        System.out.println("Just for checking");
    }

    public static void main(String[] args) {
        Example e = getObject();
        e.print();
    }
}

36. Java 异常类的层次结构

Java 中的异常是 java.lang.Exception 类的一部分。这是在程序执行期间出现的一个问题。

Java 中的所有 Exception 类都是 java.lang.Exception 类的从属。java.lang.Exception 类是 Throwable 类的子类。

Throwable 类的另一个子类是 java.lang.Error 类。错误是由于许多失败而在 Java 程序中发生的异常情况。Java 程序无法处理它们。通常,程序无法从错误中恢复。

Java 中的异常 Java 异常类

37. Java 中 InputStream 和 OutputStream 的层次结构

InputStreamjava.io 包的抽象类。它是以字节序列的形式与输入有关的所有子类的父类。

Java 中 InputStream 和 OutputStream 的层次结构

  • FileInputStream:包含来自文件的输入字节。
  • ByteArrayInputStream:从输入流中读取下一个数据字节。
  • FilterInputStream:返回可以从输入流读取的大约字节数。它由 DataInputStream, BufferedInputStreamPushBackInputStream 类组成。
  • PipedInputStream:可用于读取数据,并通过 PipedOutputStream 进行管道传输。据说管道是同时在 JVM 中运行的两个线程之间的链接。
  • ObjectInputStream:用于反序列化先前由 ObjectOutputStream 编写的原始数据类型和对象。

OutputStreamjava.io 包的抽象类。它是父级中与输入有关的所有子类的字节序列形式。

Java 中 InputStream 和 OutputStream 的层次结构

  • FileOutputStream:是用于将数据写入文件的输出流。
  • ByteArrayOutputStream:允许我们抓住写入数组中流的数据。
  • FilterOutputStream:是所有过滤输出流的类的父类。它由许多子类扩展,包括 DataOutputStream, BufferedOutputStreamPrintStream
  • PipedOutputStream:与 PipedInputStream 通过管道传输。
  • ObjectOutputStream:将原始对象、数据类型和图形写入 OutputStream

38. JDK, JRE 和 JVM 的作用

Java 中有关 JDK, JRE 和 JVM 的详细信息如下:

Java 开发套件 (JDK)

Java SE, Jakarta EE 或 Java Me 是平台,其实现由 Java 开发工具包完成。JDK 的基本内容是 Java 应用程序和 JVM 的资源。下面列出了一些 JDK 编程工具:

  1. pack200:这是一个 JAR 压缩工具。
  2. appletviewer:无需运行 Web 浏览器即可运行和调试 Java applet。
  3. javah:用于编写本机方法,它是存根生成器以及 C 头。
  4. JConsole:这是一个图形管理工具。
  5. jstack:打印 Java 线程的堆栈跟踪。

Java 运行时环境 (JRE)

Java 运行时环境由 Java 虚拟机、支持文件和核心类组成。这基本上是执行 Java 应用程序的最低要求。JRE 实际上是 JDK 的一个组件,但可以与其他组件分开下载。

JRE 的某些组件如下所示:

  1. Java 虚拟机和服务器虚拟机。
  2. 用户界面工具包,例如 Java 2D, AWT, 声音,Swing 等。
  3. 基础库,例如 I/O, Bean, JNI, 网络等。
  4. Lang 和 Util 基本库,例如 Zip, Collections, Versioning 等。
  5. 部署技巧。

Java 虚拟机 (JVM)

Java 虚拟机允许计算机运行 Java 程序。它还可以运行其他语言的程序,这些程序事先已编译为 Java 字节码。JVM 是虚拟机。

JVM 执行的一些操作如下:

  1. 该代码可以由 Java 虚拟机加载和执行。
  2. JVM 还提供了运行时环境。
  3. 通过使用 JVM 验证代码。

39. Java 中的堆空间

Java 运行时将 Java 中的堆空间分配给对象和 JRE 类。应用程序中的所有对象都是在堆空间中创建的。堆空间中的对象可从应用程序中的任何位置全局访问,因此它们对于整个应用程序执行都有生命周期。

堆空间的内存模型分为几部分,称为世代。有关这些的详细信息如下:

  1. 年轻代 (Young Generation):所有新对象都分配给了年轻一代,并且在这里老化。当这个地方装满后,便会进行少量垃圾收集。
  2. 老年代 (Old Generation):所有更长的现有对象都存储在旧版本中。当年轻一代中的对象达到某个年龄阈值时,它们将移至老一代。
  3. 永久代 (Permanent Generation):运行时类的 Java 元数据存储在永久代中。(注:Java 8 后已被元空间 Metaspace 取代)

Java 中堆空间的一些重要功能如下:

  1. 堆空间包含年轻代、老年代和永久代。
  2. 堆栈内存比 Java 中的堆空间快。
  3. 如果堆空间已满,将引发错误 java.lang.OutOfMemoryError
  4. 堆空间不是线程安全的。为了安全起见,需要同步方法。

堆栈内存和堆内存之间的一些区别如下:

  1. 堆栈内存比堆内存快,因为它的内存分配过程要简单得多。
  2. 堆栈内存仅由线程使用,而所有应用程序部分均需要堆内存。
  3. 堆栈内存只能由单个线程访问,但是堆内存对象可以全局访问。
  4. 堆栈内存的生存时间相对较短,而堆内存存在于整个应用程序执行过程中。
  5. 堆栈内存管理是使用 LIFO 完成的,但是对于堆内存来说,它更为复杂,因为整个应用程序都在使用它。

40. Java 中的垃圾回收

Java 中的垃圾收集(Garbage Collection)将销毁所有不再使用的对象。因此,基本上,垃圾回收通过删除无法访问的对象(即没有任何引用的对象)来帮助释放堆内存。

程序员使对象符合垃圾收集条件的方式如下:

  1. 对象的引用变量已重新分配。
  2. 该对象的引用变量设置为 null
  3. 孤岛(和跟对象无关联)。
  4. 在方法内部创建对象。

在对象可以进行垃圾回收之后,可以通过请求 Java 虚拟机来运行垃圾回收器。可以使用以下方法完成此操作:

  1. 可以通过请求 Java 虚拟机来调用 Runtime.getRuntime().gc() 方法来运行垃圾收集器。
  2. 通过请求 Java 虚拟机,可以调用 System.gc() 方法来运行垃圾回收器。

给出了一个通过请求 Java 虚拟机来演示运行垃圾收集器的程序,如下所示:

public class Demo {
   public static void main(String[] args) throws InterruptedException {
       Demo obj = new Demo();
       obj = null;
       System.gc();
   }
   @Override
   protected void finalize() throws Throwable {
       System.out.println("The garbage collector is called...");
       System.out.println("The object that is garbage collected is: " + this);
   }
}

上面程序的输出如下:

The garbage collector is called...
The object that is garbage collected is: Demo@7978f9b4

41. Java equals() 方法 vs == 运算符

Java 中的 equals() 方法和 == 运算符都用于查找两个对象是否相等。但是,虽然 equals() 是一种方法,但 == 是运算符。同样,equals() 比较对象值,而 == 检查对象是否指向相同的内存位置。

演示 == 运算符的程序如下:

public class Demo {
   public static void main(String[] args) {      
       System.out.println(67 == 67);
       System.out.println('u' == 'v');
       System.out.println('A' == 65.0);
       System.out.println(false == true);    
   }
}

上面程序的输出如下:

true
false
true
false

给出了一个演示 equals() 方法的程序,如下所示:

public class Demo {
   public static void main(String[] args) {
       String str1 = new String("apple");
       String str2 = new String("apple");
       String str3 = new String("mango");
       System.out.println(str1.equals(str2));
       System.out.println(str1.equals(str3));
   }
}

上面程序的输出如下:

true
false

42. Java 中的线程生命周期

Java 中的线程生命周期包含 5 个状态。这意味着线程可以处于这 5 种状态中的任何一种。下面给出了一个了解线程生命周期状态的图表:

Java 中的线程生命周期

线程生命周期中的 5 个状态是:

  • New:创建线程类的实例但未调用 start() 方法时,线程将处于新状态。
  • Runnable:如果已经调用了 start() 方法,但是线程调度程序尚未选择要执行的线程,则该线程处于可运行状态。
  • Running:如果线程调度程序选择了一个线程,并且该线程当前正在运行,则它处于运行状态。
  • Blocked(非运行):当一个线程不符合运行条件,但仍处于活动状态时,则处于阻塞状态。
  • Terminated:当线程的 run() 方法退出时,它处于终止状态。

43. Java 中的 Throw vs Throws 关键字

Java 中的 throwthrows 关键字都与异常处理相关。这两个关键字之间的区别如下:

ThrowThrows
使用 throw 关键字显式抛出异常。使用 throws 关键字声明异常。
throw 关键字后跟一个实例。throws 关键字后跟一个类。
throw 关键字仅不能传播已检查的异常。throws 关键字可以传播已检查的异常。
不可能引发多个异常。可以使用 throws 关键字声明多个异常。
throw 关键字在方法中使用。throws 关键字与方法签名一起使用。

给出了一个演示 Java 中 throw 关键字的程序,如下所示:

public class Demo {  
  static void checkMarks(int marks) {
    if (marks < 40)  
     throw new ArithmeticException("You failed");  
    else
     System.out.println("Congratulations! You passed");  
  }
  public static void main(String args[]) {
     System.out.println("Test Report");
     checkMarks(29);  
  }
}

上面程序的输出如下:

Test Report
Exception in thread "main" java.lang.ArithmeticException: You failed
at Demo.checkMarks(Demo.java:6)
at Demo.main(Demo.java:16)

给出了一个在 Java 中演示 throws 关键字的程序,如下所示:

public class Demo {
   public static void main(String[] args) throws InterruptedException {
       Thread.sleep(1000);
       System.out.println("Demonstration of throws keyword");
   }
}

上面程序的输出如下:

Demonstration of throws keyword

44. Java 11 的新功能

Java 11 的一些新功能如下:

1. Lambda 参数的局部变量语法

JDK 10 引入了局部变量类型推断(Local-Variable Type Inference),它简化了代码,因为不需要显式声明局部变量的类型。JEP 32 扩展了此语法,以用于 Lambda 表达式的参数。

2. 单文件源代码程序

Java 被批评为一种非常复杂的语言。但是,JEP 330 通过消除编译单个文件应用程序的需求,将其简化了一点。

3. HTTP 客户端 (标准)

Java SE 11 标准包含 HTTP 客户端 API 作为其一部分。引入了新的模块和包,即 java.net.http。在此定义的一些主要类型如下:

  • HttpRequest
  • HttpResponse
  • HttpClient
  • WebSocket

4. 删除 Java EE 和 CORBA 模块

java.se.ee 元模块中包含 6 个模块,这些模块不属于 Java SE 11 标准。受影响的模块是:

  • transaction
  • activation
  • corba
  • xml.ws
  • xml.ws.annotation
  • xml.bind

5. 新的 API

JDK 11 结果中包含许多 API。由于 HTTP 是标准的一部分,并且因为包含了 Flight Recorder,因此可以使用。


说明:

  • 本文部分内容基于较早期的 Java 版本编写。
  • finalize() 方法在 Java 9 中已被标记为 deprecated(不推荐使用),并在 Java 18 中正式移除,建议使用 Cleanertry-with-resources 替代。
  • Hashtable 是遗留类,新开发中建议优先使用 HashMapConcurrentHashMap
  • 堆内存中的“永久代”在 Java 8 中已被“元空间(Metaspace)”取代。