Java语言特点
- 面向对象(封装、继承、多态)
- 平台无关性(一次编写,随处运行)
- 健壮性(垃圾回收机制)
- 安全性(防止直接操作内存,多种异常处理机制)
- 多线程能力
JVM vs JRE vs JDK
JVM:Java虚拟机,运行Java字节码的虚拟机。字节码和JVM是实习Java平台无关性的关键。
所有程序都会被编译为.class文件,然后交给JVM执行
JRE:JVM+核心类库,是Java程序运行的必要条件。
JRE是Java的运行环境,如果只运行不开发那么只安装JRE即可。
JDK:JRE+Java工具+编译器+调试器。
JDK是功能齐全的Java SDK,是提供给开发者使用的。
Java是编译型还是解释型语言
编译型语言:程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++、Delphi等.
解释型语言:编写的程序不进行预先编译,以文本方式存储程序代码。在发布程序时,看起来省了道编译工序。但是,在运行程序的时候,解释性语言必须先解释再运行。
而Java则是编译与解释共存:Java程序需要先将.java文件编译成.class文件,.class文件又需要解释器来解释执行,翻译为机器能够理解的代码。
面向对象与面向过程的区别
面向对象:把当前问题先分解为各个对象,然后用对象执行方法解决问题。
面向过程:把解决问题的过程拆分为一个一个的方法,通过一个一个方法的执行来解决问题。
面试时候可举例说明,例如把大象放进冰箱
面向对象
- 冰箱(open(){} close(){})
- 大象(enter(冰箱){})
- 人(open(冰箱){冰箱.open()} putIn(大象,冰箱){大象.enter(冰箱)} close(冰箱){冰箱.close()})
面向过程
- 人打开冰箱门
- 人把大象放进去
- 人关上冰箱门
优缺点
面向对象
- 易维护,易复用,易拓展,由于面向对象的三个特性,可以设计出低耦合的系统
- 性能比面向过程低
面向过程
- 性能高,但比较消耗资源
- 没有面向对象那么易维护,易复用,易拓展
面向对象的特性
- 封装:将类的信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类的方法实现对隐藏信息的操作和访问。
- 继承:从已有的类中派生出新的类,新的类继承父类的属性和行为,并能扩展新的能力,提高程序的重用性和易维护性(ps. Java中只能继承一个父类)。
- 多态:一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。多态需要以下几个特点
- 对象之间具有继承(父类)/实现(接口)的关系
- 只有在程序运行的时候才知道调用的是那个子类的方法。
Java的基本数据类型
基本类型 | 字节 | 包装类型 |
---|---|---|
byte | 1 | Byte |
short | 2 | Short |
int | 4 | Integer |
float | 4 | Float |
double | 8 | Double |
Long | 8 | Long |
char | 2 | Character |
boolean | 1个bit | Boolean |
为什么不能用浮点型来表示金额
因为不是所有的小数都能用二进制表示,所以,为了解决这个问题,IEEE提出了一种使用近似值表示小数的方式,并且引入了精度的概念。这就是我们所熟知的浮点数。所以就会导致小数精度发生损失的情况。
所以金额可使用Long或者BigDecimal来表示。
基本类型与包装类型区别
- 默认值:包装类默认值为null,基本类型有自己的默认值。
- 比较方式:基本类型用==,包装类型使用equals()。
- 存储位置:基本类型的局部变量存放到虚拟机栈的局部变量表中,而成员变量(未static修饰)存放到JVM堆中,而包装类型属于对象,大多数存放到堆中(ps.大多数是因为看它是否进行逃逸分析)。
包装类型的缓存机制
Byte,Short,Integer,Long 默认创建了数值 [-128,127] 的相应类型的缓存数据。
Character 创建了数值在 [0,127] 范围的缓存数据。
Boolean 直接返回 True or False。
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
自动拆箱与自动装箱
装箱:将基本类型转为包装类型,其实也就是自动帮我们调用了valueOf()方法。
拆箱:将包装类型转为基本类型,这是自动帮我们调用xxxValue()方法。
当基本类型与包装类型发生以下几种情况就会帮助我们自动装箱与拆箱。
- 赋值操作
- 进行加减乘除混合运算
- 进行<,>,=比较时候
- 集合类添加基本数据类型时
==与equals区别
如果是基本类型,==比较的是他们的值,基本数据类型没有equals()。
如果是对象类型,==比较的是它们在内存中存放的地址,equals()默认比较也是内存地址,重写的话会按照重写逻辑去判断。
continue/break/return区别
continue:跳出当前这一次循环,继续下一次循环。
break:跳出当前这个循环体。
return:跳出当前方法,直接结束返回。
重载与重写的区别
同个类中的多个方法可以有相同方法名称,但有不同的参数列表,这就是方法重载。
- 方法名必须相同
- 参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同
方法重写发生在父类与子类之间,是子类对父类的允许访问的方法的实现过程进行重新编写。
- 方法名、参数列表必须相同
- 子类返回值小等于父类返回值
- 子类访问修饰符大等于父类
- 子类抛出异常范围小等于父类
- 构造方法无法重写
- 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法
ps. 参数列表即参数的类型、参数的个数、参数的顺序
接口与抽象类区别
- 抽象类中的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型。
- 抽象类可以有方法实现,而接口的方法中只能是抽象方法(Java 8 之后接口方法可以有默认实现)。
- 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法(Java 8之后接口可以有静态方法)。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
- 接口是对行为进行约束,实现了某个接口就具有相对应能力,抽象类主要用于代码复用,强调的是所属关系。
引用拷贝/浅拷贝/深拷贝
引用拷贝:两个不同的引用指向同一个对象。
浅拷贝:浅拷贝会在堆中创建一个新的对象,如果原对象内部属性存在引用类型的话,浅拷贝会直接复用内部对象的引用地址。
深拷贝:深拷贝完全创建一个新的对象,对象里面的引用类型对象也自己创建。
实现深拷贝只需要将对象内部的对象也实现clone()方法即可
Object类常用方法
/**
* 返回当前运行时对象的 Class 对象
*/
public final native Class<?> getClass()
/**
* 返回对象的哈希码
*/
public native int hashCode()
/**
* 比较 2 个对象的内存地址是否相等
*/
public boolean equals(Object obj)
/**
* 创建并返回当前对象的一份拷贝。
*/
protected native Object clone() throws CloneNotSupportedException
/**
* 返回类的名字实例的哈希码的 16 进制的字符串
*/
public String toString()
/**
* 唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
*/
public final native void notify()
/**
* 跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
*/
public final native void notifyAll()
/**
* 暂停线程的执行。
*/
public final native void wait(long timeout) throws InterruptedException
/**
* 实例被垃圾回收器回收的时候触发的操作
*/
protected void finalize() throws Throwable { }
重写equals时为什么要重写hashCode
因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。
String为什么不可变
保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
ps. Java 9中将底层char[] 改成了byte[],主要是为了节约String占用内存。
String/StringBuffer/StringBuilder区别
-
可变性
- String不可变
- StringBuffer/StringBuilder可变
-
线程安全
-
String不可变,线程安全
-
StringBuffer线程安全,内部有锁机制
-
StringBuilder线程不安全
-
使用方面
- 操作少量数据,使用String
- 单线程操作多量数据,使用StringBuilder
- 多线程操作多量数据,使用StringBuffer
什么是字符串常量池
字符串常量池(String Pool)保存着所有字符串字面量,这些字面量在编译时期就确定。字符串常量池位于堆内存中,专门用来存储字符串常量。在创建字符串时,JVM首先会检查字符串常量池,如果该字符串已经存在池中,则返回其引用,如果不存在,则创建此字符串并放入池中,并返回其引用。
其是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
final/finally/finalize区别
- final 用于修饰属性、方法和类, 分别表示属性不能被重新赋值,方法不可被覆盖,类不可被继承。
- finally 是异常处理语句结构的一部分,一般以try-catch-finally出现,finally代码块表示总是被执行。
- finalize 是Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,由垃圾回收器调用finalize()方法,回收垃圾,JVM并不保证此方法总被调用。
Exception与Error区别
Exception:程序本身可以处理的异常,可以通过catch捕获。其又分为Checked Exception(受检查异常,必须处理)和Unchecked Exception(不受检查异常,可以不处理)。
- Checked Exception:ClassNotFoundException、IOException。
- Unchecked Exception:NullPointerException、ArrayIndexOutOfBoundsException。
Error:属于程序无法处理的错误,我们无法处理。如OOM等问题
什么是泛型
泛型:泛型就是参数化类型。允许在定义类和接口的时候使⽤类型参数。声明的类型参数在使⽤时⽤具体的类型来替换。
作用:提高代码可读性与安全性。如果没有泛型,我们一个List,我们可以往里面放各种各样的对象,同时取出时候我们也还需要判断属于啥类型对象。使用泛型我们则可以限制放入对象是哪一类对象,对于后续时候则更加清晰明了,也避免其他人乱放入其他对象。
泛型使用
- 泛型类 public class P
- 泛型接口 public interface P
- 泛型方法 public < E > void P()
通配符
- 上界 <? extends T> 限制只能为T或者T的子类
- 下界 <? super T> 限制为T或者T的父类
什么是反射
反射:动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。在运行状态中,对于任意一个类,能够知道这个类的所有属性和方法。对于任意一个对象,能够调用它的任意一个方法和属性。
反射优缺点
- 优点:代码更灵活,解耦,同时能随时获取类或者对象的属性,方法,类名等。
- 缺点:代码可读性变差,会产生一些安全问题(越过泛型检查、获取到私有变量),同时性能也会带来一定的降低。
序列化与反序列化
序列化:将数据结构或对象转换成二进制字节流的过程
反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
ps. 对于不想进行序列化的变量,使用 transient 关键字修饰。
为什么不推荐JDK自带的序列化
- 性能差:与其他序列化框架相比,JDK自动的性能较差。
- 不支持跨语言使用:如果调用其他语言的序列化就不支持了。
- 安全问题:反序列化的数据可能会被用户篡改导致一定的风险。
Java新特性
- Java 8
- Lambda 表达式:Lambda允许把函数作为一个方法的参数。
- Stream API:新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Optional 类:Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Date-Time API:对java.util.Date强有力的补充,解决了 Date 类的大部分痛点。
- Java 9
- G1成为默认回收器。
- 快速创建不可变集合:List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合。
- String存储结构优化:String 的实现改用 byte[] 数组存储字符串,节省了空间。
- Java 11
- HTTP Client API:JDK11引入了一个全新的HTTP客户端API,用于替代老旧的HttpURLConnection API,提供了更高效和灵活的HTTP通信能力。
- Stream API增强:JDK11对Stream API进行了增强,增加了一些新的操作,如takeWhile()和dropWhile()等,使得流处理更加灵活和高效。
- Java 17
- 增强伪随机数生成器:增加了伪随机数生成器的相关类和接口,提供了更高质量的随机数生成能力。
- Switch类型匹配:新增了类型模式匹配和守卫模式,使得Switch语句的使用更加灵活和强大。
- Java 21
- 虚拟线程:JDK21引入了虚拟线程(Virtual Threads)的概念,这是一种轻量级的线程实现,能够显著减少高并发应用程序的编写、维护和观察的工作量。
11引入了一个全新的HTTP客户端API,用于替代老旧的HttpURLConnection API,提供了更高效和灵活的HTTP通信能力。 - Stream API增强:JDK11对Stream API进行了增强,增加了一些新的操作,如takeWhile()和dropWhile()等,使得流处理更加灵活和高效。
- 虚拟线程:JDK21引入了虚拟线程(Virtual Threads)的概念,这是一种轻量级的线程实现,能够显著减少高并发应用程序的编写、维护和观察的工作量。
- Java 17
- 增强伪随机数生成器:增加了伪随机数生成器的相关类和接口,提供了更高质量的随机数生成能力。
- Switch类型匹配:新增了类型模式匹配和守卫模式,使得Switch语句的使用更加灵活和强大。
- Java 21
- 虚拟线程:JDK21引入了虚拟线程(Virtual Threads)的概念,这是一种轻量级的线程实现,能够显著减少高并发应用程序的编写、维护和观察的工作量。
- 分代ZGC:在内存管理和垃圾收集方面进行了优化,提供了更高效的序列集合和分代ZGC,提升了应用程序的性能。