sealed
、permits
关键字限制类的继承或接口的实现,提高代码安全性和可维护性。instanceof
运算符中可直接进行类型转换和赋值,简化代码逻辑。instanceof
和switch
中的原始类型(第二次预览)、灵活的构造函数体(第三次预览)、模块导入声明(第二次预览)等。Java是一种广泛使用的高级编程语言,与C/C++、Python、Go相比,它具有独特的特点,以下是详细对比:
Thread
类和Runnable
接口可以方便地创建和管理线程。多线程可以提高程序的并发性能,充分利用多核处理器的资源。malloc
、free
(C)或new
、delete
(C++)来分配和释放内存。而Java有自动垃圾回收机制,程序员无需关心内存的释放,减少了内存管理的复杂性和出错概率。Java SE(Java Standard Edition)和 Java EE(Java Enterprise Edition,现更名为 Jakarta EE)是 Java 技术体系中两个重要的版本,它们在定位、功能、应用场景等方面存在明显差异,以下为你详细介绍:
Java SE 是 Java 技术的基础版本,也被称为标准版。它为 Java 平台提供了核心的类库、开发工具和运行环境,是 Java 技术的基石,为 Java EE 和 Java ME(Java Micro Edition)提供基础支持。
java.util
包中的 ArrayList
、HashMap
等集合类,java.io
包中的 FileInputStream
、FileOutputStream
等用于文件操作的类。javac
(编译器)、java
(解释器)、javadoc
(文档生成器)等,方便开发者进行代码编写、编译、运行和文档生成。Java SE 主要用于开发桌面应用程序、命令行工具、小型系统等。例如,使用 Java SE 开发一个简单的文本编辑器、计算器程序等。
Java EE 是 Java 技术的企业版,它是在 Java SE 的基础上构建的,提供了一系列用于开发企业级应用的 API 和规范,旨在帮助开发者更高效地构建大规模、分布式、高并发的企业应用系统。
Java EE 主要用于开发企业级应用,如企业资源规划(ERP)系统、客户关系管理(CRM)系统、电子商务平台等。这些应用通常需要处理大量的数据、高并发的用户请求和复杂的业务逻辑。
Java 虚拟机(Java Virtual Machine,JVM)是一种能够运行 Java 字节码的虚拟机。它是 Java 平台的核心组件,为 Java 程序提供了一个独立于底层操作系统和硬件的运行环境。通过 JVM,Java 程序实现了“一次编译,随处运行”的特性,即开发人员只需将 Java 源代码编译成字节码,该字节码就可以在任何安装了对应 JVM 的系统上运行,而无需针对不同的操作系统和硬件进行重新编译。
JVM 并不是只有一种,只要满足 JVM 规范,每个公司、组织或者个人都可以开发自己的专属 JVM。以下是一些常见的 JVM 实现:
JVM 规范定义了 JVM 的体系结构、字节码格式、内存管理、垃圾回收等方面的标准和规则。各个版本的 JDK 都有对应的 JVM 规范,开发人员可以在 Java SE Specifications 上找到相关信息。遵循 JVM 规范的 JVM 实现可以确保 Java 程序的兼容性和可移植性。
JDK 是功能齐全的 Java SDK(软件开发工具包),专门提供给开发者使用,是用于创建和编译 Java 程序的开发套件。它不仅包含了运行 Java 程序所必需的环境,还配备了一系列开发和调试工具,让开发者能够高效地进行 Java 应用程序的开发工作。
javac
编译器是核心工具之一,它可以将 Java 源代码(.java
文件)编译成字节码文件(.class
文件),使得 Java 程序能够在 JVM 上运行。javadoc
:文档注释工具,能够根据 Java 源代码中的注释生成详细的 API 文档,方便开发者之间的交流和代码的维护。jdb
:调试器,用于在程序运行过程中查找和解决问题,帮助开发者定位代码中的错误。jconsole
:基于 JMX(Java Management Extensions)的可视化监控工具,可用于监控 Java 应用程序的性能和资源使用情况。javap
:反编译工具,能够将字节码文件反编译成可读的 Java 代码,有助于开发者理解字节码的结构和程序的执行逻辑。如果需要进行 Java 编程工作,比如编写和编译 Java 程序、使用 Java API 文档等,就必须安装 JDK。对于一些需要使用 Java 特性的应用程序,如将 JSP 转换为 Java Servlet、使用反射等,也需要 JDK 来编译和运行 Java 代码。所以,即使不打算进行大规模的 Java 应用程序开发工作,在某些特定场景下也可能需要安装 JDK。
JRE 是 Java 运行时环境,是运行已编译 Java 程序所需的所有内容的集合。它为 Java 程序提供了运行的基础环境,确保 Java 程序能够在各种操作系统上正常执行。
如果只是需要运行已经开发好的 Java 程序,而不需要进行开发工作,那么只需要安装 JRE 即可。例如,普通用户在运行 Java 编写的游戏、工具软件时,系统中安装 JRE 就能保证程序正常运行。
从 JDK 9 开始,JDK 和 JRE 的关系发生了变化。引入了模块系统,JDK 被重新组织成 94 个模块,同时推出了 jlink
工具(随 Java 9 一起发布的新命令行工具)。
jlink
工具创建出只包含所依赖的 JDK 模块的自定义运行时镜像。这样做的好处是可以极大地减少 Java 运行时环境的大小,有助于简化 Java 应用的部署,节省内存,同时增强安全性和可维护性,以满足现代应用程序架构(如虚拟化、容器化、微服务和云原生开发)的需求。从 JDK 11 开始,Oracle 不再提供单独的 JRE 下载。Java是一种强类型语言,所有变量必须显式声明数据类型。数据类型决定了变量的内存大小、取值范围及操作方式。Java数据类型分为两大类:基本数据类型和引用数据类型。以下是 8种基本数据类型 的详细介绍:
字节型 (byte)
短整型 (short)
整型 (int)
长整型 (long)
L
或l
。单精度浮点型 (float)
F
或f
。双精度浮点型 (double)
字符型 (char)
布尔型 (boolean)
true
或false
。用列表展示如下 :
类型名称 | 关键字 | 占用内存 | 取值范围 | 默认值 |
---|---|---|---|---|
字节型 | byte |
1 字节 | -128 ~ 127 | 0 |
短整型 | short |
2 字节 | -32,768 ~ 32,767 | 0 |
整型 | int |
4 字节 | -2,147,483,648 ~ 2,147,483,647 | 0 |
长整型 | long |
8 字节 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | 0L |
单精度浮点型 | float |
4 字节 | ±3.403E38 | 0.0F |
双精度浮点型 | double |
8 字节 | ±1.798E308 | 0.0D |
字符型 | char |
2 字节 | Unicode字符(如 'a' , '家' ) |
'\u0000' |
布尔型 | boolean |
1 字节 | true 或 false |
false |
String
、数组、对象)存储在堆内存中,默认值为null
。BigDecimal
)。char
类型使用Unicode编码,支持国际字符集。- **静态常量**:静态常量使用`static final`修饰,其作用域为整个类。无论在类的哪个方法、代码块中,都可以直接通过类名来访问静态常量。例如:
public class Constants {
public static final int MAX_VALUE = 100;
}
// 在其他类中访问
public class Main {
public static void main(String[] args) {
int value = Constants.MAX_VALUE;
}
}
- **实例常量**:实例常量使用`final`修饰但没有`static`关键字,它的作用域为类的实例对象。只能通过类的实例来访问实例常量,不同的实例对象各自拥有独立的实例常量副本。例如:
public class Circle {
private final double pi = 3.14159;
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double calculateArea() {
return pi * radius * radius;
}
}
- **局部常量**:局部常量在方法或代码块内部声明,使用`final`修饰,其作用域仅限于声明它的方法或代码块。一旦超出这个范围,该局部常量就无法被访问。例如:
public class LocalConstantExample {
public void method() {
final int localVar = 10;
// 以下代码在该方法内可以使用localVar
System.out.println(localVar);
}
// 这里无法访问localVar
}
- **静态常量**:随着类的加载而创建,在整个程序运行期间都存在,只要类被加载到内存中,静态常量就会一直存在,直到程序结束或类被卸载。
- **实例常量**:在创建类的实例对象时被创建,其生命周期与所属的实例对象相同。当实例对象被垃圾回收机制回收时,实例常量也随之消失。
- **局部常量**:在程序执行到声明局部常量的代码块时被创建,当代码块执行结束后,局部常量就超出了作用域,其生命周期结束。它的生命周期与所在的代码块的执行周期一致。
在Java中,&&
和&
都属于逻辑与运算符,但它们存在显著区别:
- **`&&`(短路与)**:具有短路特性。当使用`&&`进行逻辑运算时,从左到右依次计算操作数。一旦发现某个操作数为`false`,则整个表达式立即被判定为`false`,不再计算后续的操作数。例如:
int a = 5;
int b = 3;
if (a > 10 && (b++ > 2)) {
// 这里由于a > 10为false,b++ > 2不会被执行
}
System.out.println(b); // 输出仍为3
- **`&`(非短路与)**:不具备短路特性。无论`&`左边的操作数是`true`还是`false`,都会计算右边的操作数。例如:
int c = 5;
int d = 3;
if (c > 10 & (d++ > 2)) {
// 这里即便c > 10为false,d++ > 2也会被执行
}
System.out.println(d); // 输出为4
- **`&&`的使用场景**:当希望在逻辑判断中利用短路特性来提高效率时,优先选择`&&`。比如在进行复杂的条件判断时,如果前面的条件已经能确定整个表达式的结果为`false`,那么后续可能耗时或有副作用的操作就无需执行了。例如在检查一个对象是否为空,再访问其属性时:
if (obj!= null && obj.getProperty() > 10) {
// 只有obj不为空时,才会去调用getProperty方法
}
- **`&`的使用场景**:当需要确保所有操作数都被计算时,使用`&`。例如在进行位运算(`&`也可用于位运算)或需要强制计算所有条件的逻辑与场景中。在一些测试代码中,可能需要确保每个条件都被执行来验证某些逻辑,此时就适合用`&`。另外,在处理布尔数组或集合时,如果希望对每个元素都进行与运算而不考虑短路,也可使用`&`。例如:
boolean[] boolArray = {true, false, true};
boolean result = true;
for (boolean bool : boolArray) {
result &= bool;
// 这里使用&确保每个元素都参与运算
}
面向对象编程(OOP)的三大特性为封装、继承和多态,它们是OOP的核心概念,相互协作构建出强大且灵活的软件系统:
- **概念**:将数据(属性)和操作数据的方法绑定在一起,隐藏对象的内部实现细节,仅对外提供公共的访问接口。通过封装,对象的内部状态被保护起来,外部代码只能通过特定接口来访问和修改数据。
- **实现方式**:在Java中,使用访问修饰符(`public`、`private`、`protected`以及默认修饰符)来控制类成员的可见性。例如,将类的属性声明为`private`,通过公有的`getter`和`setter`方法来访问和修改属性值。
class BankAccount {
private double balance;
// Getter方法获取余额
public double getBalance() {
return balance;
}
// Setter方法设置余额
public void setBalance(double amount) {
if (amount >= 0) {
this.balance = amount;
} else {
System.out.println("金额不能为负数");
}
}
}
- **优势**:提高了代码的安全性,防止外部非法访问和修改对象内部数据;增强了代码的可维护性,因为内部实现细节的改变不会影响到外部调用代码;降低了代码耦合度,使对象之间的依赖关系更清晰。
- **概念**:一个类(子类)可以继承另一个类(父类)的属性和方法,从而实现代码复用与功能扩展。子类继承父类的非私有成员,并可根据自身需求重写父类方法。
- **实现方式**:在Java中,使用`extends`关键字实现继承。例如,`Animal`类为父类,`Dog`类继承自`Animal`类。
class Animal {
public void eat() {
System.out.println("动物进食");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("狗叫");
}
}
- **优势**:减少代码重复,提高开发效率;通过建立类的层次结构,使程序结构更清晰,便于理解和维护;符合现实世界的分类和层次概念,如动物类下有狗、猫等子类。
- **概念**:同一个方法调用在不同对象上会产生不同的行为。多态有两种实现形式:方法重载和方法重写。
- **方法重载**:在同一个类中,定义多个同名方法,但方法的参数列表(参数个数、类型、顺序)不同。编译器根据调用时传入的参数来决定调用哪个方法,这是编译时多态。例如:
class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
- **方法重写**:子类重新实现父类中已有的方法,要求方法名、参数列表和返回类型与父类方法一致(返回类型可以是父类返回类型的子类型,在Java 5.0及以上版本支持)。在运行时,根据对象的实际类型来决定调用哪个版本的方法,这是运行时多态。例如:
class Shape {
public void draw() {
System.out.println("绘制形状");
}
}
class Circle extends Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
在 Java 中,访问修饰符用于控制类、方法、变量等的访问权限。Java 提供了四种访问修饰符,按照访问权限从大到小依次为:public
、protected
、默认(无修饰符)
和 private
。以下是对这四种访问修饰符的详细介绍:
public
public
修饰的类、方法、变量可以在任何地方被访问,包括不同包中的类。示例代码:
// 定义一个公共类
public class PublicClass {
// 定义一个公共变量
public int publicVariable = 10;
// 定义一个公共方法
public void publicMethod() {
System.out.println("This is a public method.");
}
}
// 在另一个类中访问 PublicClass 的公共成员
public class AnotherClass {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
// 访问公共变量
System.out.println(publicClass.publicVariable);
// 调用公共方法
publicClass.publicMethod();
}
}
protected
protected
修饰的成员可以在同一个包内的任何类中访问,也可以在不同包的子类中访问。protected
修饰符。示例代码:
// 定义一个基类
package package1;
public class BaseClass {
// 定义一个受保护的变量
protected int protectedVariable = 20;
// 定义一个受保护的方法
protected void protectedMethod() {
System.out.println("This is a protected method.");
}
}
// 在同一个包内访问受保护成员
package package1;
public class SamePackageClass {
public static void main(String[] args) {
BaseClass baseClass = new BaseClass();
// 访问受保护变量
System.out.println(baseClass.protectedVariable);
// 调用受保护方法
baseClass.protectedMethod();
}
}
// 在不同包的子类中访问受保护成员
package package2;
import package1.BaseClass;
public class SubClass extends BaseClass {
public void accessProtectedMembers() {
// 访问受保护变量
System.out.println(protectedVariable);
// 调用受保护方法
protectedMethod();
}
}
示例代码:
// 定义一个类
package package3;
class DefaultClass {
// 定义一个默认访问修饰符的变量
int defaultVariable = 30;
// 定义一个默认访问修饰符的方法
void defaultMethod() {
System.out.println("This is a default method.");
}
}
// 在同一个包内访问默认成员
package package3;
public class SamePackageAccess {
public static void main(String[] args) {
DefaultClass defaultClass = new DefaultClass();
// 访问默认变量
System.out.println(defaultClass.defaultVariable);
// 调用默认方法
defaultClass.defaultMethod();
}
}
private
private
修饰的成员只能在定义该成员的类内部访问。示例代码:
// 定义一个类
public class PrivateClass {
// 定义一个私有变量
private int privateVariable = 40;
// 定义一个私有方法
private void privateMethod() {
System.out.println("This is a private method.");
}
// 提供公共的访问方法
public int getPrivateVariable() {
return privateVariable;
}
public void callPrivateMethod() {
privateMethod();
}
}
// 在另一个类中访问 PrivateClass 的私有成员
public class AccessPrivateMembers {
public static void main(String[] args) {
PrivateClass privateClass = new PrivateClass();
// 通过公共方法访问私有变量
System.out.println(privateClass.getPrivateVariable());
// 通过公共方法调用私有方法
privateClass.callPrivateMethod();
}
}
访问修饰符 | 同一类中 | 同一包中(子类与非子类) | 不同包中的子类 | 不同包中的非子类 |
---|---|---|---|---|
public |
√ | √ | √ | √ |
protected |
√ | √ | √ | × |
默认(无修饰符) | √ | √ | × | × |
private |
√ | × | × | × |
方法重载是指在同一个类中,允许存在多个同名的方法,但这些方法的参数列表必须不同(参数的个数、类型或顺序不同),与方法的返回类型和访问修饰符无关。
方法重载可以让程序更加清晰和简洁,通过使用相同的方法名来表示一组相似的操作,提高代码的可读性和可维护性。
public class MethodOverloading {
// 方法重载:参数个数不同
public int add(int a, int b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
// 方法重载:参数类型不同
public double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
MethodOverloading overloading = new MethodOverloading();
// 调用两个整数相加的方法
System.out.println(overloading.add(1, 2));
// 调用三个整数相加的方法
System.out.println(overloading.add(1, 2, 3));
// 调用两个双精度浮点数相加的方法
System.out.println(overloading.add(1.0, 2.0));
}
}
方法重写是指在子类中定义一个与父类中具有相同方法名、相同参数列表和相同返回类型(或兼容的返回类型,即子类方法的返回类型是父类方法返回类型的子类)的方法,用于实现子类对父类方法的具体实现进行修改或扩展。
方法重写是实现多态的重要手段之一,它允许子类根据自身的需求对父类的方法进行个性化的实现,提高了代码的灵活性和可扩展性。
// 定义一个父类
class Animal {
public void makeSound() {
System.out.println("The animal makes a sound.");
}
}
// 定义一个子类
class Dog extends Animal {
// 重写父类的 makeSound 方法
@Override
public void makeSound() {
System.out.println("The dog barks.");
}
}
public class MethodOverriding {
public static void main(String[] args) {
Animal animal = new Animal();
animal.makeSound();
Dog dog = new Dog();
dog.makeSound();
Animal animalDog = new Dog();
animalDog.makeSound();
}
}
public
的,子类重写方法不能是 protected
或 private
的。private
方法不能被重写:因为 private
方法只能在定义它的类内部访问,子类无法访问和重写。@Override
注解:虽然不是强制要求,但建议在子类重写方法时使用 @Override
注解,这样可以让编译器检查该方法是否真的重写了父类的方法,如果没有重写会报错,提高代码的可读性和安全性。