⭐️JVM vs JDK vs JRE

JVM vs JDK vs JRE

⭐️什么是字节码?采用字节码的好处是什么?

在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以, Java 程序运行时相对来说还是高效的(不过,和 C、 C++,Rust,Go 等语言还是有一定差距的),而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。

⭐️为什么说 Java 语言“编译与解释并存”?

这是因为 Java 语言既具有编译型语言的特征,也具有解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class 文件),这种字节码必须由 Java 解释器来解释执行。

⭐️自增自减运算符

1
2
3
4
5
int a = 9;
int b = a++;
int c = ++a;
int d = c--;
int e = --d;

答案:a = 11 、b = 9 、 c = 10 、 d = 10 、 e = 10。

⭐️基本数据类型

基本类型位数字节默认值取值范围
byte810-128 ~ 127
short1620-32,768 (-2¹⁵) ~ 32,767 (2¹⁵ - 1)
int3240-2,147,483,648 (-2³¹) ~ 2,147,483,647 (2³¹ - 1)
long6480L-9,223,372,036,854,775,808 (-2⁶³) ~ 9,223,372,036,854,775,807 (2⁶³ - 1)
char162'\u0000'0 ~ 65,535 (2¹⁶ - 1)
float3240.0f±1.4E-45 ~ ±3.4028235E38
double6480.0d±4.9E-324 ~ ±1.7976931348623157E308
boolean1-falsetrue / false

⭐️成员变量与局部变量的区别?

  • 语法形式:从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
  • 存储方式:从变量在内存中的存储方式来看,如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
  • 生存时间:从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
  • 默认值:从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
graph TB subgraph "Java 变量分类" A[Java 变量] --> B[成员变量
Member Variable] A --> C[局部变量
Local Variable] end subgraph "成员变量特性" B --> D[语法形式] B --> E[存储方式] B --> F[生存时间] B --> G[默认值] D --> D1["• 属于类
• 可用 public/private/static 修饰
• 可用 final 修饰"] E --> E1["• static: 属于类(方法区)
• 非static: 属于实例(堆内存)"] F --> F1["• 随对象创建而存在
• 随对象销毁而消失"] G --> G1["• 自动赋默认值
• final 成员变量需显式赋值"] end subgraph "局部变量特性" C --> H[语法形式] C --> I[存储方式] C --> J[生存时间] C --> K[默认值] H --> H1["• 在方法/代码块中定义
• 不能用访问修饰符
• 不能用 static 修饰
• 可用 final 修饰"] I --> I1["• 存储在栈内存中"] J --> J1["• 方法调用时创建
• 方法结束时销毁"] K --> K1["• 不会自动赋值
• 使用前必须初始化"] end style A fill:#e1f5fe style B fill:#c8e6c9 style C fill:#ffecb3 style D1 fill:#f3e5f5 style E1 fill:#f3e5f5 style F1 fill:#f3e5f5 style G1 fill:#f3e5f5 style H1 fill:#fff3e0 style I1 fill:#fff3e0 style J1 fill:#fff3e0 style K1 fill:#fff3e0

⭐️静态方法和实例方法有何不同?

1、调用方式

在外部调用静态方法时,可以使用 类名.方法名 的方式,也可以使用 对象.方法名 的方式,而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象 。

不过,需要注意的是一般不建议使用 对象.方法名 的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。

因此,一般建议使用 类名.方法名 的方式来调用静态方法。

2、访问类成员是否存在限制

静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。

⭐️重载和重写有什么区别?

重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理

重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法

重载

发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。

《Java 核心技术》这本书是这样介绍重载的:

如果多个方法(比如 StringBuilder 的构造方法)有相同的名字、不同的参数, 便产生了重载。

1
2
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder("HelloWorld");

编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。 如果编译器找不到匹配的参数, 就会产生编译时错误, 因为根本不存在匹配, 或者没有一个比其他的更好(这个过程被称为重载解析(overloading resolution))。

Java 允许重载任何方法, 而不只是构造器方法。

综上:重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。

重写

重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。

  1. 方法名、参数列表必须相同,子类方法返回值类型应比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
  2. 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
  3. 构造方法无法被重写

总结

综上:重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。

区别点重载 (Overloading)重写 (Overriding)
发生范围同一个类中。父类与子类之间(存在继承关系)。
方法签名方法名必须相同,但参数列表必须不同(参数的类型、个数或顺序至少有一项不同)。方法名、参数列表必须完全相同。
返回类型与返回值类型无关,可以任意修改。子类方法的返回类型必须与父类方法的返回类型相同,或者是其子类。
访问修饰符与访问修饰符无关,可以任意修改。子类方法的访问权限不能低于父类方法的访问权限。(public > protected > default > private)
绑定时期编译时绑定或称静态绑定运行时绑定 (Run-time Binding) 或称动态绑定

方法的重写要遵循“两同两小一大”(以下内容摘录自《疯狂 Java 讲义》,issue#892 ):

  • “两同”即方法名相同、形参列表相同;
  • “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
  • “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。