文章目录
- 基础
- java SE 、 EE 、 ME 的区别
- jdk 和 jre 区别?
- java 的日志级别
- 基本数据类型
- 特性
- 关键字
- final
- abstract
- super
- switch
- for
- try catch
- 接口和抽象类的区别
- 接口
- 抽象类
- 适用场景
- 类的加载循序
- 静态代码块
- 传参问题
- 访问修饰符
- 运算符
- 反射
- java 里的应用
- 为什么反射的性能不好
- 类的加载过程
- 获取属性
- 获取注解
- 异常
- 异常的分类
- throw 和 throws 的区别
- 异常处理示例
- try 里有 return,finally 还执行吗?
基础
java SE 、 EE 、 ME 的区别
SE(Standard Edition):即Java平台标准版,是Java技术的核心和基础。它包含了Java语言的所有基本功能和API,主要用于开发和部署桌面应用程序、服务器应用程序和嵌入式设备应用程序
EE(Enterprise Edition):即Java平台企业版,是Java SE的一个扩展,用于开发和部署企业级应用程序。它提供了一组技术规范和API,用于构建复杂的分布式网络应用程序。
ME(Micro Edition):即Java平台微型版,是专门为嵌入式设备和移动设备设计的Java版本。它提供了一个精简的Java API,可以在有限的资源和计算能力下运行。
jdk 和 jre 区别?
jdk:(最小开发环境)
1、jvm
2、java api 类库
3、java编程语言
4、一些系列辅助工具,javac
jre:(最小运行环境)
1、jvm
2、java api
java 的日志级别
日志级别 | 说明 |
---|---|
debug | 程序调试bug时使用 |
info | 程序正常运行时使用 |
warning | 程序未按预期运行时使用,但并不是错误,如:用户登录密码错误 |
error | 程序出错误时使用,如:IO操作失败 |
critical | 特别严重的问题,导致程序不能再继续运行时使用,如:磁盘空间为空,一般很少使用 |
基本数据类型
数据类型 | 字节数 | 位数(bit) | 范围 | 注意 |
---|---|---|---|---|
byte (字节) | 1 | 8 | ||
char | 2 | 16 | 可存一个汉字 | |
short | 2 | 16 | -32768~32767 | |
int | 4 | 32 | -2147483648 ~ 2147483647 | |
long | 8 | 64 | 最大存 19 位二进制数 | l 或 L 结尾 |
float | 4 | 32 | f 或 F 结尾 | |
double | 8 | 64 | d 或 D 结尾 | |
boolean | 4 | 32 |
注意:boolean 类型是4个字节,但这是在 .class 文件中表示时的情况。实际上,在虚拟机中,boolean类型的存储可能会根据具体的实现而有所不同。在某些情况下,boolean 可能只占用1/8个字节(因为一个 boolean值可以用1位来表示,但考虑到计算机存储的基本单位是字节,所以通常会用更多的空间来存储)
如果 boolean 类型被声明为 static final 并且被初始化,它可能会被存储在类的常量池中,这时它可能会占用4个字节的空间(因为常量池中的整数类型通常是以4个字节的形式存储的)
- bit(比特): 一位二机制数
- byte(字节):存储的基本单位, = 8 bit , 之后就是 KB,MB,GB,TB
- 十六进制的数字,以 0X 或者 0x 开头
- 八进制的数字,以 0 开头
- Mybatis plus 生成的雪花 id 是19 位,就是用 long 类型接收的
特性
关键字
transient : 带有它的属性,不会被序列化
native :调用本地方法 (c,c++)
volatile :多个线程资源共享
static:静态成员变量(在data seg里存储),不在堆内存中了
final
它可以保证变量的可见性
- 修饰的变量不能被改变
- 修饰的方法不能被重写
- 修饰的类不能被继承
abstract
- 含有抽象方法的必须是抽象类,抽象的方法是必须被重写的
- 抽象类不能被实例化
- 抽象方法在抽象类中只需声明,不能实现
super
- super可以用来引用直接父类的实例变量。
- super可以用来调用直接父类方法。
- super可以用于调用直接父类构造函数。
switch
它支持的数据类型:byte、short、int、char、String、enum
它为什么不支持 long?
switch 语句在 Java 中不支持 long 类型的主要原因是与 switch 语句的设计初衷和性能考虑有关。
switch 语句最初是为了提供一种在编译时能够进行优化以支持快速查找的语句结构。对于整型(byte、short、char、int)和枚举类型(enum),编译器可以生成一个查找表(通常是一个跳转表或者散列表),这样可以快速跳转到对应的 case 语句,从而提高执行效率。
然而,对于 long 类型,由于它占用的内存空间是 int 类型的两倍(64位 vs 32位),如果 switch 支持 long 类型,那么查找表的大小也会相应地增大,这会导致以下问题:
- 内存占用:查找表会占用更多的内存空间,这在内存受限的环境(如嵌入式系统)中可能是一个问题。
- 性能下降:更大的查找表意味着更长的查找时间,这可能会降低 switch 语句的执行效率。
- 编译器实现复杂度:为了支持 long 类型的 switch 语句,编译器需要实现更复杂的查找算法和数据结构,这会增加编译器的实现难度和复杂度。
for
for-i、 for、 foreach 的区别与联系?
List<String> list = Arrays.asList("1", "2", "3");
// [for-i]
// 最原始的,但是操作性很强,合适知道当前索引或需要在循环中控制迭代次数的场景
// 遍历的时候,可以通过索引来修改集合
for (int i = 0; i < list.size(); i++) {
}
// [for]
// java 5 引入的,更简洁
// 遍历的时候,不能修改,否则会发生 `ConcurrentModificationException`
for (String s : list) {
}
// [foreach]
// 本质就是 for 循环
// 上边两个 continue 退出本次循环 ,这个 return 退出本次循环
list.forEach(s -> {
});
break 和 continue 的区别和联系?
break:中止循环
continue:跳出本次循环
都可以用于:for 、 while 、 do-while 循环
循环起别名
// 起名字:此循环的名字就叫eric
eric:for(int I = 0;I < 1 ; i++)
// 就会跳出这个循环
break eric;
try catch
为什么重写 equals 的时候,需要重写 hashcode?
潜规则:
1、两个对象相等,它们的 hashCode 一定相等
2、object 的 hashCode 方法是通过哈希算法对对象内存地址进行计算获取其 hash 值
但是你只重写了 equals ,没有重写 hashCode,就会出现两个对象相等,但是 hashCode 不相等的情况
接口和抽象类的区别
接口
一个完全抽象的类,不能包含任何方法实现。接口定义了一组方法,任何实现该接口的类都必须提供这些方法的具体实现。接口用于定义类的能力和行为,而不涉及具体的实现细节。
- 接口的属性都是常量 public static final (必须有初始值)
- 接口的方法都是抽象方法 public static (不能实现)
- 方法和字段不可以用反问修饰符号
- 没有构造函数
- 一个接口可以继承多个接口
- java 8 以后,可以包含默认方法(default)和静态方法
抽象类
抽象类是一种不能实例化的类,可以包含抽象方法和具体方法。抽象类用于捕捉子类的公共行为,并允许子类共享这些行为。抽象类通常用于表示“is-a”关系
- 可以拥有实例变量
- 方法可以是抽象方法,也可以是非抽象方法
- 方法和字段可用反问修饰符号
- 可以有构造函数
- 单继承
- 可以包含静态方法,不能包含默认方法
适用场景
使用接口:
- 需要定义一组无关的行为,而多个类可以实现这些行为。
- 需要支持多重继承。
- 定义契约或能力,而不涉及实现细节。
使用抽象类:
- 需要共享代码和行为(具体方法)在多个相关类之间。
- 需要定义类的基本属性和行为,而允许子类扩展和覆盖这些行为。
- 需要使用构造器来初始化公共字段。
类的加载循序
-
父类的类初始化(clinit):
a. 静态变量
b. 静态代码块
(它俩顺序执行) -
对象初始化(init):
a. 父类构造方法
b. 非静态变量
c. 子类的非静态代码块
d. 子类的无参构造
(b c 按顺序执行)
静态代码块
静态代码块,没有名字,类加载的时候执行,只执行一次,优先于主函数。
静态代码块什么时候进入内存?
1.创建该类对象的实例(对象)
2. 调用该类的静态方法
注意:
- 优先级:静态代码块(给类初始化的) > 构造代码块(给对象初始化的) > 构造函数(给对应对象初始化的)
- 子类被创建的时候,先走父类的构造方法
public class ClassLoadTest {
// 1--静态代码块
static {
System.out.println("ClassLoadTest static block");
}
// 2--构造代码块
{
System.out.println("ClassLoadTest block");
}
// 3--构造函数
public ClassLoadTest() {
System.out.println("ClassLoadTest constructor");
}
// 4--静态方法
public static void show(){
System.out.println("ClassLoadTest show");
}
}
传参问题
java 传参数,都是值传递:
1、基本数据类型,传值的
2、引用类型,则是传递是引用的内存地址值
public static void main(String[] args){
String a = new String ("123");
changeString(a);
System.out.println(a);
}
public static void changeString(String s){
s = new String("456");
}
// 输出 123,s 指向了新的 456, 但是 a 还是指向 123
public static void main(String[] args){
Person p = new Person();
p.setId(0L);
changePerson(p);
System.out.println(p.getId());
}
public static void changePerson(Person p){
p.setId(p.getId() + 10);
}
// 输出 10
访问修饰符
修饰符 | 类内部 | 同一个包 | 子类 | 任何地方 |
---|---|---|---|---|
private | ✓ | |||
default | ✓ | ✓ | ||
protected | ✓ | ✓ | ✓ | |
public | ✓ | ✓ | ✓ | ✓ |
运算符
运算符 | 描述 |
---|---|
& | 按位与。当两位同时为1时才返回1。 |
| | 按位或。只要有一位为1即可返回1。 |
~ | 按位非。单目运算符,将操作数的每个位(包括符号位)全部取反。 |
^ | 按位异或。当两位相同时返回0,不同时返回1。 |
<< | 左移运算符。 |
>> | 右移运算符。 |
>>> | 无符号右移运算符。 |
一般来说,位运算符只能操作整数类型的变量或值
优先级
优先级 | 运算符 | 结合性 |
---|---|---|
最高 | . () {} ; | 从左到右 |
++ -- ~ ! (data type) | 从右到左 | |
* / % | 从左到右 | |
+ - | 从左到右 | |
<< >> >>> | 从左到右 | |
< > <= >= instanceof | 从左到右 | |
== != | 从左到右 | |
& | 从左到右 | |
^ | 从左到右 | |
| | 从左到右 | |
&& | 从左到右 | |
|| | 从左到右 | |
? : | 从右到左 | |
= *= /= %= += -= <<= >>= | 从右到左 | |
最低 | >>>= &= ^= |= | 从右到左 |
反射
反射的本质,推迟对象实例化到对象运行中,而不是编译阶段;也就是说,你实例化的类可以在编译的时候,根本就不存在。
它是一种动态类加载(new的方式加载类,是一种静态类加载的方式)
java 里的应用
- 对象实例化
- spring ioc
- 动态代理 aop
为什么反射的性能不好
它属于解释操作(告诉 jvm 要做些什么),会比 jvm 直接操作慢一些
类的加载过程
获取属性
// 获取所有属性包括私有
Field[] fields = person.getClass().getDeclaredFields();
// 获取属性名
field.getName()
// 私有属性获取值
field.setAccessible(true);
// 获取属性值
field.get(person);
获取注解
// 自定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldCanEmpty {
}
// 获取某个字段是否包含此注解
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof FieldCanEmpty) {
continue fieldFor;
}
}
异常
异常的分类
一、被检查的异常 (Checked Exceptions)
受检异常是在编译时由编译器检查的异常,必须在代码中显式处理,否则编译器会报错。
这类异常通常是由于外部原因(如I/O错误、数据库访问错误等)引起的。
此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。
常见的被检查的异常 :
- IOException:输入/输出操作失败时抛出,例如文件未找到 (FileNotFoundException)。
- SQLException:数据库操作失败时抛出。
- ClassNotFoundException:尝试加载类时类不存在时抛出。
- InterruptedException:线程在等待、睡眠或其他活动时被中断时抛出
二、运行时异常 (Runtime Exceptions)
它是Exception中特殊的异常子类,它方法内部,方法名可以不去标识。
运行时异常是在程序运行期间可能发生的异常,不是编译器强制要求处理的异常。这类异常通常是由编程错误(如逻辑错误或不正确的API使用)引起的。
常见的运行时异常:
- NullPointerException:当应用程序尝试在需要对象的地方使用 null 时抛出。
- ArrayIndexOutOfBoundsException:数组访问越界时抛出。
- ArithmeticException:算术运算出错时抛出,例如除以零。
- ClassCastException:试图将对象强制转换为不兼容的类型时抛出。
- IllegalArgumentException:方法接收到非法参数时抛出
三、错误 (Errors)
错误是严重的运行时异常,表示应用程序的运行环境出现了问题。一般来说,错误是由Java虚拟机抛出的,程序不应该也无法通常地去捕获和处理这些错误。
常见的错误:
- StackOverflowError:方法调用栈溢出。
- OutOfMemoryError:JVM内存不足。
- VirtualMachineError:虚拟机操作出错。
- NoClassDefFoundError:类在编译时存在但在运行时找不到
throw 和 throws 的区别
throw:抛出的是一个异常类对象
throws:作为方法声明和签名的一部分,方法会对应抛出
异常处理示例
class DivDemo{
// 在功能上通过throws的关键字声明该功能可能出现的问题
int div(int a,int b) throws Exception{
return a/b;
}
}
class Start{
// 如果方法内部不捕捉,也可以将异常抛出 throws Exception
public static void main(String[] args){
DivDemo dd = new DivDemo();
// 捕捉调用该功能时可能会出现的问题
try{
dd.div(4,0);
System.out.println();
}
catch (Exception e){
// 异常信息
System.out.println(e.getMessage());
// 异常名:异常信息
System.out.println(e.toString());
// 异常名,异常信息,异常出现的位置(堆栈跟踪信息)
// jvm 默认的异常处理机制,及时调用的此方法
e.printStackTrace();
}
}
}
try 里有 return,finally 还执行吗?
执行,并且 finally 的执行早于 try 里面的 return
- 是否出现异常,finally块中代码都会执行;
- 当 try 和 catch 中有 return 时,finally 仍然会执行;
- finally 是在 return 后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保
存起来,管finally中的代码怎么样,返回的值都不会改变,扔然是之前保存的值),所以函数返回值是
在finally执行前确定的; - finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
public static void main(String[] args) {
System.out.println(test());
}
public static int test() {
int a = 1;
try {
a = a + 1;
return a;
} finally {
a = a + 1;
System.out.println("finally a = " + a);
}
}
a=2 会被暂存起来,然后 进行 finall 里的计算