1、JVM、JRE、JDK之间的关系
JVM负责将Java字节码翻译成机器码,以便在特定平台上执⾏Java程序。
JDK是Java的开发⼯具包,它包含了开发和编译Java程序所需的⼯具,以及运⾏Java程序的JRE
JRE是Java应⽤程序运⾏的环境,它包含了运⾏Java程序所需的库、Java虚拟机(JVM)以及其他⽀持⽂件。
简⽽⾔之,JVM提供了Java程序运⾏的虚拟环境,JDK是⽤于开发Java程序的⼯具包,⽽JRE是⽤于运⾏Java程序的
环境。在开发阶段,你需要JDK;⽽在部署和运⾏阶段,你只需要JRE。
JVM:是Java程序的运行环境,负责解释和执行Java字节码。
JRE:是Java程序的运行环境,包含了JVM以及运行Java程序所需的其他所有组件。
JDK:是Java程序员用来开发Java程序的一整套工具,包含了JRE以及额外的开发工具。
可以这样理解:
JVM是“发动机”,负责驱动Java程序运行。
JRE是“汽车”,包含了JVM这个“发动机”,以及一些其他必要的组件,如轮胎、方向盘等,使得Java程序可以在上面运行。
JDK是“汽车制造厂”,不仅包含了制造汽车(JRE)所需的所有部件,还包含了一些额外的工具(如编译器、调试器等),用于制造和调试汽车(JRE)。
Java基础数据类型
Java有⼏种数据类型
整型: byte 、short、int、long (int默认)
浮点型: float 、double (double默认)
布尔型: boolean
字符型: char
对应的包装类为 Byte 、 Short 、 Integer 、 Long 、 Float 、 Double 、 Character 、 Boolean
基础数据类型的转换⽅式
⾃动类型转换:⼩–>⼤ byte --> short --> int --> long --> float --> double
强制类型转换:⼤–>⼩ ⼩类型 变量名 = (⼤类型) 值
注意:⾃增/⾃减运算符、复合赋值运算符底层做了优化,内部⾃动强制类型转换; 如:++, --, +=, -=, ......
基本类型和包装类型的区别
为了在对象环境中使⽤基本类型,Java提供了对应的包装类型。
基本类型直接存储数据值,在栈上分配内存,占⽤固定的内存空间,
包装类型是对象动态分配内存,在堆上创建,包含额外的信息,例如⽅法和字段,同时也占⽤更多的内存空
间。
不过Java提供了⾃动装箱(autoboxing)和拆箱(unboxing)的功能,使得基本类型和包装类型之间的转换更加⽅便。
什么是⾃动装箱和⾃动拆箱
⾃动装箱是指将基本类型转换为对应的包装类型,⽽⾃动拆箱是指将包装类型转换为对应的基本类型。这些转换通常由编译器⾃动处理。
// ⾃动装箱
Integer intValue = 42; // 将基本类型int⾃动转换为Integer包装类型
// ⾃动拆箱
int primitiveValue = intValue; // 将Integer包装类型⾃动转换为基本类型int
装箱其实就是调⽤了 包装类的 valueOf() ⽅法,拆箱其实就是调⽤了 xxxValue() ⽅法。
注意:所有整型包装类对象之间值的⽐较,全部使⽤ equals ⽅法⽐较。
变量和⽅法
成员变量和局部变量的区别
1. 声明位置:
成员变量: 声明在类中,但在⽅法、构造函数或块之外。它们是类的⼀部分,因此可以被整个类的⽅法访
问。
局部变量: 声明在⽅法、构造函数或块中。它们只在声明它们的⽅法、构造函数或块中可⻅。
2. 作⽤域
成员变量: 具有类的整个实例的⽣命周期,可以在整个类中使⽤。
局部变量: 仅在声明它们的⽅法、构造函数或块中可⻅,并且只在该⽅法、构造函数或块的执⾏期间存在。
3. ⽣命周期
成员变量: 随着对象的创建⽽创建,随着对象的销毁⽽销毁。
局部变量: 在声明时分配内存,在⽅法调⽤结束时销毁。
4. 默认值
成员变量: 如果不显式初始化,会有默认值(例如,整数默认为0,对象引⽤默认为null等)。
局部变量: 不会⾃动初始化,必须在使⽤之前显式初始化。
静态变量是什么?
静态变量是被 static 关键字修饰的变量,被类的所有实例共享,⽆论⼀个类创建了多少个对象,它们都共享同⼀份静态变量。静态变量通过类名来访问。
值传递和引⽤传递的区别
参数传递⽅式主要有值传递和引⽤传递两种,但需要注意的是 Java 中的参数传递是始终按值传递的。
值传递:在值传递中,传递给函数的是实际参数的值的副本。当在函数内修改这个副本时,不会影响到原始值
引⽤传递:⽅法接收的直接是实参所引⽤的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。
但在 Java 中,虽然传递的是引⽤的值(对象的地址),但仍然是按值传递。实际上,传递的是引⽤的副本,因此在函数内对引⽤的修改会影响到原始的引⽤,但⽆法修改引⽤指向的对象。
⾯向对象的三⼤特征
1. 封装
为了提⾼代码的安全性,隐藏对象的内部细节,封装将对象的内部状态(字段、属性)隐藏起来,并通过定义公共的⽅法(接⼝)来操作对象,外部代码只需要知道如何使⽤这些⽅法⽽⽆需了解内部实现。
2. 继承
允许⼀个类(⼦类)继承另⼀个类(⽗类)的属性和⽅法的机制。⼦类可以重⽤⽗类的代码,并且可以通过添加新的⽅法或修改(重写)已有的⽅法来扩展或改进功能,提⾼了代码的可重⽤性和可扩展性。
3. 多态
多态是指相同的操作或⽅法可以在不同的对象上产⽣不同的⾏为,通过⽅法的重载和重写实现。多态允许以⼀致的⽅式处理不同类型的对象,提⾼了代码的灵活性。
说⼀说你对多态的理解
因为⼦类其实是⼀种特殊的⽗类,因此Java允许把⼀个⼦类对象直接赋给⼀个⽗类引⽤变量,⽆须任何类型转换,或者被称为向上转型,向上转型由系统⾃动完成。
当把⼀个⼦类对象直接赋给⽗类引⽤变量时,例如 BaseClass obj = new SubClass(); ,这个 obj 引⽤变量的编译时类型是 BaseClass ,⽽运⾏时类型是 SubClass ,当运⾏时调⽤该引⽤变量的⽅法时,其⽅法⾏为总是表现出⼦类⽅法的⾏为特征,⽽不是⽗类⽅法的⾏为特征,这就可能出现:相同类型的变量、调⽤同⼀个⽅法时呈现出多种不同的⾏为特征,这就是多态。
编译时多态和运⾏时多态的区别
1. 编译时多态
编译时多态也被称为静态多态或早期绑定。它是指在编译阶段,编译器就能够确定调⽤哪个⽅法,这是通过⽅法的重载来实现的。编译时多态主要依赖于⽅法的签名(⽅法名和参数列表),根据调⽤时提供的参数类型和数量来确定具体调⽤的⽅法。
2. 运⾏时多态
运⾏时多态也被称为动态多态或晚期绑定。指的是在程序运⾏时,根据实际对象的类型来确定调⽤的⽅法,这是通过⽅法的重写来实现的。运⾏时多态主要依赖于对象的实际类型,⽽不是引⽤类型。
总结
编译时多态是通过⽅法的重载在编译阶段确定调⽤的⽅法。
运⾏时多态是通过⽅法的重写在程序运⾏时确定调⽤的⽅法,实现动态绑定。
接⼝和抽象类有什么区别
1. 从定义上来说
接⼝是⼀种抽象类型,它定义了⼀组⽅法(⽅法签名)但没有实现任何⽅法的具体代码。接⼝中的⽅法默认是
抽象的,且接⼝中只能包含常量(static final变量)和抽象⽅法。
抽象类是⼀个类,可以包含抽象⽅法和具体⽅法。抽象类中的抽象⽅法是没有实现的⽅法,⽽具体⽅法则包含
实现代码。抽象类不能直接实例化,通常需要⼦类继承并实现其中的抽象⽅法。
2. 继承
接⼝⽀持多继承,⼀个类可以实现多个接⼝
Java中不⽀持多继承,⼀个类只能继承⼀个抽象类。如果⼀个类已经继承了⼀个抽象类,就不能再继承其他
类。
3. 构造器
接⼝不能包含构造器,因为接⼝不能被实例化。类实现接⼝时,必须实现接⼝中定义的所有⽅法。
抽象类可以包含构造器,⽤于初始化抽象类的实例。当⼦类实例化时,会调⽤⽗类的构造器。
4. 访问修饰符
接⼝中的⽅法默认是 public abstract 的。接⼝中的变量默认是 public static final 的。
抽象类中的抽象⽅法默认是 protected 的,具体⽅法的访问修饰符可以是 public 、 protected
或 private 。
5. 实现限制
类可以同时实现多个接⼝,实现接⼝的类必须提供接⼝中定义的所有⽅法。
⼀个类只能继承⼀个抽象类,继承抽象类的⼦类必须提供抽象类中定义的所有抽象⽅法的实现。
构造⽅法能不能重写
构造⽅法不能重写。因为构造⽅法需要和类保持同名,⽽重写的要求是⼦类⽅法要和⽗类⽅法保持同名。如果允许
重写构造⽅法的话,那么⼦类中将会存在与类名不同的构造⽅法,这与构造⽅法的要求是⽭盾的。
Java堆栈方法区
说⼀说你对Java访问权限的了解
在Java中,访问权限通过访问修饰符来控制。主要的访问修饰符有四个: public 、 protected 、 default (默
认修饰符)和 private 。这些修饰符可以⽤于类、变量、⽅法和构造⽅法。
修饰符 public 表示对所有类可⻅。⼀个类如果被声明为 public ,那么它可以被其他任何类访问。
修饰符 protected 表示对同⼀包内的类和所有⼦类可⻅。⼦类可以访问⽗类中声明为 protected 的成员,
⽽不管⼦类与⽗类是否在同⼀包中。
如果没有使⽤任何访问修饰符(即没有写 public 、 protected 、 private ),则默认为包级别访问。这意
味着只有同⼀包中的类可以访问。
修饰符 private 表示对同⼀类内可⻅。私有成员只能在声明它们的类中访问。
你对static关键字有哪些认识
static 是Java中的关键字,⽤于声明静态成员(静态变量或静态⽅法)
1. 静态变量
使⽤ static 关键字声明的变量称为静态变量,也叫类变量。它们属于类⽽不是实例,因此所有实例共享相同的静态变量。
2. 静态⽅法
使⽤ static 关键字声明的⽅法称为静态⽅法。静态⽅法属于类⽽不属于实例,可以通过类名调⽤,⽽不需要创建类的实例。
3. 静态代码块
使⽤ static 关键字声明的代码块称为静态代码块。它在类加载时执⾏,并且只执⾏⼀次。
4. 静态内部类
在类中使⽤ static 关键字声明的内部类称为静态内部类静态成员可以被类直接访问,⽽不需要创建类的实例。然⽽,静态成员⽆法直接访问⾮静态成员,因为⾮静态成员依赖于类的实例。
static和final有什么区别
static ⽤于修饰成员时,该成员成为类级别的,⽽不是实例级别的。静态成员属于类,⽽不是属于类的实例。
当 final ⽤于修饰变量、⽅法或类时,表示它是不可变的。对于变量,⼀旦赋值后就不能再修改;对于⽅法,表示⽅法不能被⼦类重写;对于类,表示类不能被继承。
final、finally、finalize的区别
(1)final就是不可变的意思,可以修饰变量、⽅法和类。修饰变量时,这个变量必须初始化,所以也称为常量。
(2)finally是异常处理的⼀部分,只能⽤在try/catch中,并且附带⼀个语句块表示这段语句⼀定会被执⾏,⽆论是否抛出异常。
(3)finalize是java.lang.Object中的⽅法,也就是每⼀个对象都有这个⽅法,⼀个对象的finalize⽅法只会调⽤⼀次,调⽤了不⼀定被回收,因为只有对象被回收的时候才会被回收,就会导致前⾯调⽤,后⾯回收的时候出现问题,不推荐使⽤。
finalize()方法的作用
在Java中,垃圾回收器负责回收不再使用的对象,释放它们占用的内存空间。在对象被垃圾回收器回收之前,可以使用finalize()方法执行一些清理工作,例如关闭文件、释放资源等。
由于Java中的垃圾回收器是自动执行的,并且无法控制它的执行时间和顺序,因此finalize()方法并不能保证一定会执行。如果一个对象没有被垃圾回收器回收,那么finalize()方法也不会被调用。因此,finalize()方法通常只用于释放一些非内存资源,而不是释放内存。
⽅法的重载和重写有什么区别
1. 重载
⽅法的重载指的是在同⼀个类中,可以有多个⽅法具有相同的名称,但是它们的参数列表不同(参数的类型、个数、顺序),具有以下特点。
⽅法的返回类型可以相同也可以不同。
⽅法的重载与⽅法的访问修饰符和返回类型⽆关。
编译器根据⽅法的参数列表来区分不同的重载⽅法
2. 重写
⽅法的重写指的是在⼦类中重新定义⽗类中已经定义的⽅法,⽅法名、参数列表和返回类型都必须相同。
重写是实现多态的⼀种⽅式,提供了⼀种⼦类可以提供⾃⼰特定实现的机制。
重写的⽅法不能⽐被重写的⽅法有更低的访问权限。
⼦类⽅法不能抛出⽐⽗类⽅法更多的异常(⼦类⽅法可以不抛出异常或抛出⽗类异常的⼦类)。
3. 总结
重载是在同⼀个类中定义多个⽅法,⽅法名相同但参数列表不同。
重写是在⼦类中重新定义⽗类中已有的⽅法,⽅法名和参数列表必须相同。
重载与返回类型和访问修饰符⽆关,⽽重写要求⽅法签名相同。
重载是编译时多态,⽽重写是运⾏时多态。
深拷⻉和浅拷⻉有什么区别
浅拷⻉:浅拷⻉创建⼀个新对象,然后将原对象的⾮静态字段复制到新对象。如果字段是基本数据类型,那么就复制其值;如果字段是引⽤类型,复制的就是引⽤⽽不是实际对象。
深拷⻉:创建⼀个新对象,并递归复制原对象中的所有引⽤类型的字段指向的对象,⽽不是共享引⽤。因此,新对象和原对象中的引⽤类型字段引⽤的是两组不同的对象。
浅拷贝:只复制对象的顶层数据,对于对象中的子对象(如数组、对象等引用类型),它只复制引用地址,不复制实际的对象。这意味着,两个对象将引用同一个子对象。
深拷贝:不仅复制对象的顶层数据,还会递归地复制对象中所有的子对象。这样,两个对象是完全独立的,修改其中一个对象的子对象不会影响另一个对象。
对this关键字的理解
原因:
JVM会给每个对象分配一个this,来代表当前对象
创建对象的几种方法
String
1. 使用new关键字
这是最常见也是最简单的创建对象的方式了。通过这种方式,我们可以调用任意的构造函数(无参的和带参数的)。
Student s = new Student();
2. 使用Class类的newInstance方法(反射)
我们也可以使用Class类的newInstance方法创建对象。这个newInstance方法调用无参的构造函数创建对象。
3. 使用Constructor类的newInstance方法
和Class类的newInstance方法很像, java.lang.reflect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。
这两种newInstance方法就是大家所说的反射。事实上Class的newInstance方法内部调用Constructor的newInstance方法。
4. 使用clone方法
无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。
要使用clone方法,我们需要先实现Cloneable接口并实现其定义的clone方法。
Employee emp4 = (Employee) emp3.clone();
5. 使用反序列化
当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。
为了反序列化一个对象,我们需要让我们的类实现Serializable接口。
从上面的例子可以看出来,除了使用new关键字之外的其他方法全部都是转变为invokevirtual(创建对象的直接方法),使用被new的方式转变为两个调用,new和invokespecial(构造函数调用)。
String
String存储原理
String 类型是不可变的
Java 中⽤双引号括起来的字符串,例如:"abc"、"def",都是直接存储在“⽅法区”的“字符串常量池”当中的。
为什么把字符串存储在⼀个“字符串常量池”当中呢?
因为字符串在实际的开发中使⽤太频繁
为了提⾼执⾏效率,所以把字符串放到了⽅法区的“字符串常量池”当中
String类能被继承吗
不能被继承,因为String类有final修饰符,⽽final修饰的类是不能被继承的。
Java中对String类的定义
final修饰的特点
1. 修饰类: final修饰的类不可被继承,是最终类.
2. 修饰⽅法: 明确禁⽌该⽅法在⼦类中被覆盖的情况下才将⽅法设置为 final
3. 修饰变量:
final修饰 基本数据类型的变量,其数值⼀旦在初始化之后便不能更改, 称为常量;
final修饰 引⽤类型的变量,则在对其初始化之后便不能再让其指向另⼀个对象。虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
String为什么是不可变的
1. 保存字符串的数组被 final 修饰且为私有的,并且 String 类没有提供/暴露修改这个字符串的⽅法。
2. String 类被 final 修饰导致其不能被继承,进⽽避免了⼦类破坏 String 不可变。
String底层使⽤的什么类型
在JDK1.8之前,底层使用的是char类型的数组
在JDK1.9之后,底层使用的是byte类型数组
String/StringBuffer/StringBuilder的区别
三者的区别主要集中在不可变性、线程安全和性能⽅⾯
1. String(不可变,线程安全)
String 是不可变的,⼀旦创建了字符串对象,其值就不能被修改。对字符串的任何修改都会创建⼀个新的字符串对象。
String 是线程安全的,因为它的不可变性使得多个线程可以同时访问同⼀个字符串对象⽽不会发⽣竞争条件。
由于不可变性,对字符串的修改操作(例如连接、截取)可能会导致创建新的字符串对象,影响性能。
2. StringBuffer(可变、线程安全)
StringBuffer 是可变的,它允许在同⼀对象上执⾏修改操作,⽽不创建新的对象。
StringBuffer 是线程安全的,它的⽅法是同步的,可以安全地在多个线程中使⽤。
由于可变性, StringBuffer 适⽤于在多线程环境中进⾏字符串操作,但在单线程环境中,相⽐StringBuilder ,可能会有⼀些性能开销。
3. StringBuilder(可变、⾮线程安全)
StringBuilder 也是可变的,类似于 StringBuffer
StringBuilder 是⾮线程安全的,它的⽅法没有同步,因此在多线程环境中使⽤时需要注意同步问题。
由于可变性, StringBuilder 适⽤于在单线程环境中进⾏字符串操作,且通常⽐ StringBuffer 性能更好,因为它不需要同步。
综合来说, String 适⽤于不经常修改的情况,⽽ StringBuffer 和 StringBuilder 适⽤于需要频繁修改字符串的情况,具体选择取决于是否需要线程安全以及性能的考虑。