Java基础
1. Java语言的特点
-
面向对象(封装 继承 多态) 多态:父类引用指向子类对象
-
平台无关性,一处编译,处处运行
-
支持多线程
-
解释与编译并存
解释与编译并存 ?
Java 语言既具有编译型语言的特征,也具有解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class
文件),这种字节码必须由 Java 解释器来解释执行。
2. JVM JDK JRE
JVM
JVM,Java虚拟机,是运行Java字节码的虚拟机,JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。JVM 并不是只有一种!只要满足 JVM 规范,每个公司、组织或者个人都可以开发自己的专属 JVM。
JDK 和 JRE
JDK 是 Java Development Kit 缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
OpenJDK vs OracleJDK
来由:JDK原来是Sun公司开发,后卖给了Oracle公司
区别:OpenJDK可以开做是OracleJDK的一个分支,底层很多源码都取自OracleJDK完全开源免费。而OracleJDK并非完全开源免费,但其稳定性更好,且一般性能更好
3. continue break return的区别
continue: 跳出当前这一次循环,继续下次循环
break: 跳出整个循环体
return: 跳出所在的方法,直接进行返回
4. 成员变量 与 局部变量
- 语法形式 :从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被
public
,private
,static
等修饰符所修饰,而局部变量不能被访问控制修饰符及static
所修饰;但是,成员变量和局部变量都能被final
所修饰。 - 存储方式 :从变量在内存中的存储方式来看,如果成员变量是使用
static
修饰的,那么这个成员变量是属于类的,如果没有使用static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。 - 生存时间 :从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
- 默认值 :从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被
final
修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
5. 静态与非静态
-
调用方式: 静态方法用
类名.方法名
调用,也可以用对象命.方法名
调用,所以调用静态方法无需创建对象 -
访问限制: 静态方法只允许访问静态成员(静态变量和静态方法),不允许访问实例方法,而实例方法可以访问静态成员
-
为什么静态方法不能调用非静态成员? 1. 静态方法属于类,在类加载时就会分配内存,可以直接通过类名.方法名访问。而非静态成员属于实例对象,只有在对象实例化后才会分配内存,才能通过类的实例对象访问。 2.在类的非静态成员还不存在时,静态成员就已经存在,此时调用内存中不存在的非静态成员,属于非法操作。
6. 重载与重写
重载
-
发生在同一个类中
-
方法名必须相同。 参数类型,参数个数,参数顺序不同构成重在(返回值和访问修饰符可以不同,但不是发生重在的原因)
-
重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理
-
构造方法可以重载
重写
-
发生在有基础关系的子父类中
-
方法名,参数列表必须相同
-
子类返回值类型小于等于父类(void或基本数据类型必须相同,引用类型,重写时可以返回该引用类型的子类) 抛出异常范围小于等于父类 访问修饰范围大于等于父类
-
如果父类方法修饰符为
private final static
,子类就不能重写该方法 -
构造方法无法被重写
-
重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变
7. 基本数据类型
-
整数型:
byte short int long
-
浮点型:
float double
-
字符型:
char
-
布尔型:
boolean
基本类型 | 字节 | 位数 | 默认值 | 取值范围 | 包装类 |
---|---|---|---|---|---|
byte | 1 | 8 | 0 | -128~127 | Byte |
short | 2 | 16 | 0 | -32768 ~ 32767 | Short |
int | 4 | 32 | 0 | -2147483648 ~ 2147483647 | Integer |
long | 8 | 64 | 0L | Long | |
float | 4 | 32 | 0f | Float | |
double | 8 | 64 | 0d | Double | |
char | 2 | 16 | ‘u0000’ | Character | |
boolean | 1 | false | Boolean |
精度丢失? 超过long的表示范围?
BigDecimal
BigInteger
8.包装类
与基本数据类型的区别?
-
成员变量如果时包装类,不赋值就是null,而基本数据类型有默认值且不是null
-
包装类可用与泛型,基本数据类型不可以
-
基本数据类型的局部变量存放在堆中,未被static修饰的成员变量存放在堆中,而包装类均存放在堆中
-
相比于包装类,基本数据类型占用的空间非常小
包装类的缓存机制?
-
Byte Shot Integer Long四种整数型包装类,默认创建了
-128到127
的缓存,Character创建了数值在0到127
范围的缓存数据,Boolean直接返回True
或False
-
Integer源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static {
// high value may be configured by property
int h = 127;
}
}
test
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 true
Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// 输出 false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false
自动装箱与拆箱
-
装箱:将基本数据类型用对应的包装类包装起来
Integer i = 10
等价于Integer i = Integer.valueOf(10)
-
拆箱: 将包装类转化为对应的基本数据类型
int n = i
等价于int n = i.intValue()
-
如果频繁拆装箱,会严重影响系统性能,应尽量避免
9. 接口和抽象类
相同点
- 都不能被实例化
- 都可以包含抽象方法
- 都可以有默认实现的方法(Java 8 可以用
default
关键字在接口中定义默认方法)
不同点
-
一个类只能继承一个抽象类,但可以实现多个接口
-
成员变量:接口中必须为
public static final
类型,不能被修改并且必须有初始值,抽象类则默认default
,可在子类中被重新定义,也可以被重新赋值
10. 深拷贝 浅拷贝 引用拷贝
-
深拷贝 完全复制整个对象,包括该对象的内部对象
-
浅拷贝 首先在堆上创建该对象,但该对象的内部对象不会再次创建,而是直接复制该内部对象的引用地址
-
引用拷贝 直接复制对象的引用地址,不生成任何对象
11. == 和equals()
-
对于基本数据类型, == 比较的是值
-
对于引用数据类型,==比较的是对象的地址.但是其实对于引用数据类型,它里面存的就是指向堆空间的地址,其实本质比较的还是值
-
equals() 类没有重写该方法,和==等价,如果重写了该方法则按照重写的来
hashcode
- 作用: 再一些容器,比如
hashmap,hashse
t中,当加入元素时,它会先用hashcode
来进行比较并判断加入对象应该存放的位置,如果与原有对象的hashcode
均不相同,则认为该对象没有重复出现,此时直接放入容器即可。如果有某个对象hashcode
对象与新加入对象相同,则会继续调用equals()
方法来进行判断,如果为true,说明这两对象真的相同,此时会进行处理,比如hashset
就不会加入该对象。如果为false,说明这两对象不相同,只是发生了哈希碰撞
,此时就会将新加入的对象散列到其他位置。如下图:
- 综上,
hashcode
可以减少equals()
方法的调用,提高程序性能,缩小查找成本 - 如果两个对象的
hashCode
值相等,那这两个对象不一定相等(哈希碰撞) - 如果两个对象的
hashCode
值相等并且equals()
方法也返回true
,我们才认为这两个对象相等 - 如果两个对象的
hashCode
值不相等,我们就可以直接认为这两个对象不相等
为什么重写equals()
必须重写hashcode()
?
因为两个相等的对象的 hashCode
值必须是相等。也就是说如果 equals
方法判断两个对象是相等的,那这两个对象的 hashCode
值也要相等
如果重写 equals()
时没有重写 hashCode()
方法的话就可能会导致 equals
方法判断是相等的两个对象,hashCode
值却不相等。那么我们往hashset
重添加某些我们认为相同的元素,却仍然可以添加进去
12. Java异常体系
13. 序列化和反序列化
-
序列化: 将数据结构或对象转换成二进制字节流的过程
-
反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
应用场景
- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化
- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化
- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化
- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化
- 序列化协议位于TCP/IP 4层模型中的应用层
14. String StringBuff(安全) StringBuilder(不安全)
-
string
不可变,可以理解为常量,是线程安全的。 -
AbstractStringBuilder
是StringBuilder
与StringBuffer
的公共父类,定义了一些字符串的基本操作,如expandCapacity
、append
、insert
、indexOf
等公共方法。StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的 -
每次对
String
类型进行改变的时候,都会生成一个新的String
对象,然后将指针指向新的String
对象。StringBuffer
每次都会对StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用。 -
操作少量的数据: 适用
String
-
单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder
,不用担心线程安全问题 -
多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
String为什么不可变?
1.String
类中使用 final
关键字修饰字符数组来保存字符串
2.保存字符串的数组被 final
修饰且为私有的,并且String
类没有提供/暴露修改这个字符串的方法。
3.String
类被 final
修饰导致其不能被继承,进而避免了子类破坏 String
不可变。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
//...
}
15. 字符串常量池
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
// 在堆中创建字符串对象”ab“
// 将字符串对象”ab“的引用保存在字符串常量池中
String aa = "ab";
// 直接返回字符串常量池中字符串对象”ab“的引用
String bb = "ab";
System.out.println(aa==bb);// true