Java基础

1、JDK、JRE、JVM的区别

  1. JVM(Java Virtual Machine)是 Java 虚拟机的缩写,是 Java 运行环境的核心组成部分,它负责将 Java 代码转换为机器码并执行。JVM 是 Java 跨平台的关键,不同操作系统上的 JVM 会将相同的 Java 代码转换为该操作系统能够执行的机器码。
  2. JRE(Java Runtime Environment)是 Java 运行环境的缩写,包含了 JVM 以及运行 Java 应用程序所需的核心类库和支持文件,但不包含用于开发 Java 应用程序的编译器和调试器等开发工具。
  3. JDK(Java Development Kit)是 Java 开发工具包的缩写,包含了 JRE 以及用于开发 Java 应用程序的编译器(javac)、调试器(jdb)和其他开发工具。JDK 中包含的工具可以帮助开发者创建、编译和调试 Java 应用程序。

因此,JDK 包含 JRE,而 JRE 包含 JVM。

(下面再对JDK、JRE、JVM做进一步介绍)

JDK 包含了以下组件:

  1. 编译器(javac):将 Java 源代码编译成 Java 字节码。
  2. 执行器(java):运行 Java 字节码,并且在 JVM 中加载和执行应用程序。
  3. 调试器(jdb):用于调试 Java 应用程序,支持断点、监视器、单步调试等功能。
  4. Javadoc 工具:用于自动生成 API 文档,可以根据源代码中的注释生成 HTML 格式的 API 文档。
  5. 外部库:JDK 包含了许多标准类库和第三方库,包括 Java SE 标准库、JavaFX 库、Swing 库、AWT 库等等。
  6. 示例代码:JDK 包含了大量的示例代码,可以帮助开发者学习和理解如何使用 JDK 提供的各种功能和组件。

JDK 支持多个操作系统,包括 Windows、Linux 和 macOS 等。在开发 Java 应用程序时,需要先编写 Java 源代码,然后使用 javac 编译器将源代码编译成 Java 字节码。最后使用 java 执行器在 JVM 中加载并运行应用程序。在开发过程中,可以使用 jdb 调试器进行调试,使用 Javadoc 工具自动生成 API 文档。

JRE 包含了以下组件:

  1. Java 虚拟机(JVM):是运行 Java 应用程序的核心组件,可以将 Java 字节码转换成机器码并执行。JVM 是 Java 跨平台的关键,因为它可以将相同的 Java 应用程序在不同的操作系统上运行。
  2. Java 标准类库:包含了大量的类和接口,提供了许多常用的功能和工具,如 I/O、网络、GUI、多线程等等。Java 标准类库是 Java 应用程序的基础,可以方便地使用其中的类和接口来开发应用程序。
  3. Java Web Start:是一种基于 JNLP(Java Network Launching Protocol)的技术,可以通过互联网直接启动 Java 应用程序,而不需要用户先下载并安装应用程序。
  4. Java Plug-in:是一个浏览器插件,可以在浏览器中直接运行 Java 应用程序。使用 Java Plug-in,可以在浏览器中运行 Java 小程序、游戏等等。

JVM 包含了以下组件:

  1. 类加载器:负责将字节码文件加载到 JVM 中,并将其转换成可执行的代码。类加载器可以动态地加载、卸载类,并提供了一种隔离的机制,可以防止不同类之间的冲突。
  2. 运行时数据区:JVM 将内存分成了多个区域,包括方法区、堆、栈、程序计数器等等。不同的区域用于存储不同类型的数据,如类信息、对象实例、方法调用栈、程序计数器等等。
  3. 执行引擎:负责执行字节码指令,将字节码转换成机器码并执行。执行引擎可以根据不同的硬件平台和操作系统,选择不同的编译策略,以获得更好的性能。
  4. 垃圾回收器:负责回收无用的对象实例,释放内存空间。垃圾回收器可以减少内存泄漏和程序崩溃的风险,提高应用程序的稳定性和可靠性。

JVM 是跨平台的关键,因为它可以将相同的 Java 字节码在不同的操作系统上运行。Java 开发者只需要编写一次代码,然后使用 javac 编译器将其编译成字节码,就可以在不同的操作系统上运行。  JVM 还支持多种语言的编译,如 Groovy、Scala、Kotlin 等等。

2、重载和重写的区别

重载(Overloading)指在同一个类中,可以有多个方法具有相同的名称但参数不同,包括参数类型、个数或顺序不同,编译器会根据传入的参数类型、个数、顺序等信息来决定调用哪个方法。重载的方法可以有不同的返回值类型,但不同的返回值类型不能作为方法重载的区分依据。

例如:

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    
    public double add(double a, double b) {
        return a + b;
    }
    
    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

在上面的例子中,Calculator 类中定义了三个名为 add 的方法,它们具有相同的名称但参数不同,编译器会根据传入的参数类型和个数来选择调用哪个方法。其中第一个 add 方法接收两个 int 类型的参数,返回值为 int 类型;第二个 add 方法接收两个 double 类型的参数,返回值为 double 类型;第三个 add 方法接收三个 int 类型的参数,返回值为 int 类型。

重写(Override)指在子类中重新定义一个与父类具有相同名称、参数列表和返回类型的方法,方法体可以不同。重写的目的是为了改变父类方法的行为,使其更适合子类的需求。当子类中定义了与父类中同名的方法时,在使用子类对象调用该方法时,会优先调用子类中的方法,而不是父类中的方法。

需要注意的是,重写的方法的访问修饰符不能比父类中被重写的方法的访问修饰符更严格,即如果父类中的方法是 public 的,那么子类中重写的方法不能是 protected 或 private 的。此外,重写的方法还需要具有相同的参数列表和返回类型,否则会被视为方法重载(Overload)而不是方法重写。

例如:

public class Animal {
    public void makeSound() {
        System.out.println("Animal is making sound.");
    }
}

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

在上面的例子中,Dog 类继承了 Animal 类,并重写了其中的 makeSound 方法。重写后的 makeSound 方法与 Animal 类中的 makeSound 方法具有相同的名称、参数列表和返回类型,但方法体不同,因此在调用 Dog 对象的 makeSound 方法时,会优先调用 Dog 类中的 makeSound 方法。

总之,重载(Overloading)指在同一个类中,可以有多个方法具有相同的名称但参数不同,而重写(Override)指在子类中,重新定义一个与父类具有相同名称、参数列表和返回类型的方法,并且方法体不同。两者的作用和实现方式都不同。

3、Java中==和equals的区别

  1. == 操作符用比较两个变量或表达式的值是否相等,如果操作符两边的值是基本数据类型,它们的值相等且数据类型相同时,== 返回 true,否则返回 false;如果操作符两边的值是对象,它们的地址相同时,== 返回 true,否则返回 false。  例如:
int a = 10, b = 10;
System.out.println(a == b); // true

String str1 = "hello", str2 = "hello";
System.out.println(str1 == str2); // true,因为它们指向字符串常量池中同一个对象
  1. equals 方法用于比较两个对象的内容是否相同。认情况下,equals 方法与 == 操作符的作用相同,即比较两个对象的地址是否相同。但是,大部分类都重写了 equals 方法,以便比较它们自己定义的实例变量是否相等。如果一个类重写了 equals 方法,那么 equals 方法将根据重写后的实现来比较两个对象的内容是否相同。

需要注意的是,如果一个类重写了 equals 方法,那么通常需要同时重写 hashCode 方法,以便在将对象存储在哈希表等容器中时能够正常工作。

String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");

System.out.println(str1 == str2); // true,因为 str1 和 str2 指向同一个字符串常量池中的对象
System.out.println(str1 == str3); // false,因为 str1 和 str3 指向不同的对象
System.out.println(str1.equals(str2)); // true,因为 str1 和 str2 的内容相同
System.out.println(str1.equals(str3)); // true,因为 str1 和 str3 的内容相同

在重写 equals 方法时,需要注意以下几点:

  • equals 方法必须满足自反性、对称性、传递性和一致性。
  • equals 方法必须与 hashCode 方法保持一致性。如果两个对象使用 equals 方法比较相等,则它们的 hashCode 值必须相等。
  • equals 方法不能比较对象的类型。如果两个对象的类型不同,equals 方法应该返回 false。
  • equals 方法应该检查对象是否为 null。如果对象为 null,equals 方法应该返回 false,而不是抛出 NullPointerException 异常。

例如:

public class Person {
    private String name;
    private int age;

    // 构造函数和其它方法省略...

    @Override
    public boolean equals(Object obj) {
        if (obj == this) { // 自反性
            return true;
        }
        if (!(obj instanceof Person)) { // 检查对象是否为null,是否是Person类的实例
            return false;
        }
        Person other = (Person) obj;
        return this.name.equals(other.name) && this.age == other.age; // 比较实例变量是否相等
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Person p1 = new Person("Alice", 20);
Person p2 = new Person("Alice", 20);
System.out.println(p1.equals(p2)); // true
System.out.println(p1 == p2); // false