I. 基础概念
原文链接: java基础概念
1. java语言特性
- 面向对象(封装、继承、多态)
- 跨平台(jvm实现平台无关性,write once,run everywhere)
- 网络编程
- 并发编程
- 多线程
- 编译与解释并存(编译器将源码编译为字节码,解释器将字节码解释为机器码)
2. JVM &JRE &JDK概念和区别
JVM
jvm:java虚拟机,用于运行字节码
。jvm针对不同操作系统,有特定的实现。目的是实现相同的字节码在不同平台得到相同的结果。jvm不止一种,只要符合jvm规范
,个人或企业组织都能开发jvm。
JRE
JRE: Java Runtime Environment(java运行时环境)。包括jvm、java类库、java命令等基础组件,它是java程序运行所必需的环境。
JDK
JDK:Dava Development Kit(java开发套件)。它包括JRE,还有编译器(javac)和一些开发工具,如javadoc等,用于开发和编译java程序。
3. 字节码
字节码是jvm可以识别的代码。.class文件就是经过编译的java字节码。字节码不面向任何特定平台、处理器,只面向JVM。因此,java程序只需一次编译,就能移植到其它平台。字节码的存在,在一定程度上解决了传统的解释性语言效率低下的问题,同时也保留了解释性语言可移植的特点。
JIT:just-in-time compilation。JIT属于运行时编译器。它会将字节码对应的机器码保存,下次可直接使用机器码。.class文件由JVM ClassLoader
进行加载,然后由解释器逐行解释成机器码,效率低下。而且,有些代码是频繁被调用的(即 热点代码)。如果每次都要进行解释,将会很影响运行效率。因此引入JIT编译器。
4. 编译型&解释型并存
-
编译型:
编译型语言
通过编译器
将源码一次性翻译成机器码,执行效率较高、但开发效率低,通常面向过程开发。如C、C++、Go…… -
解释性:
解释型语言
通过解释器
逐行源码解释(interpret)成机器码,执行效率较低,但开发效率高、通常面向对象开发。如java、JS、C#……
5. OpenJDK & OracleJDK
- OpenJDK:完全开源。
- OracleJDK:部分关键代码不开源,是OpenJDK的一个实现。相比OpenJDK,更加稳定,性能更好。
II. Java语法
基本数据类型
基本类型 | 大小 | 最小值 | 最大值 | 包装器类型 |
---|---|---|---|---|
boolean | - | - | - | Boolean |
char | 16bit | Unicode 0 | Unicode 2^16^-1 | Character |
byte | 8bit | -128 | 127 | Byte |
short | 16bit | -2^15^ | 2^15^-1 | Short |
int | 32bit | -2^31^ | 2^31^-1 | Integer |
long | 64bit | -2^63^ | 2^63^-1 | Long |
float | 32bit | IEEF~754~ | IEEF~754~ | Float |
double | 64bit | IEEF~754~ | IEEF~754~ | Double |
《深入理解Java虚拟机》: 局部变量表主要存放了编译期可知的基本数据类型 (boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
泛型
什么是泛型
Java泛型(generics)
就是参数化类型,即把所操作的数据类型作为一个参数。
java泛型是伪泛型,因为在java运行期间,所有泛型信息会被擦除,这就是所谓的擦除类型。
List<Integer> list = new ArrayList<>();
list.add(12);
//这里直接添加会报错
list.add("a");
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
//但是通过反射添加是可以的
//这就说明在运行期间所有的泛型信息都会被擦掉
add.invoke(list, "kl");
System.out.println(list);
泛型一般包括泛型类
,泛型方法
,泛型接口
- 泛型类:
public class Generic<T> {
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
}
- 泛型方法:
public static <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
- 泛型接口:
// 声明泛型接口
public interface Generator<T> {
public T method();
}
// 实现泛型接口,不指定类型
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}
// 实现泛型接口,指定类型
class GeneratorImpl implements Generator<String>{
@Override
public String method() {
return "hello";
}
}
==和equals()
==
- 对于基本数据类型,
==
比较的是值 - 对于引用类型,
==
比较的是对象引用(内存地址)
java 只有值传递,无论是基本数据类型,还是引用类型,==
比较的都是值。只是引用类型变量的值是对象的内存地址。
equals()
不能用于比较基本数据类型,编译器会报错提示,找不到equals(int)。只能用来判断两个对象是否相等,eqals()
存在Object
类中,Object
是所有类的父类。
equals()
的使用存在两种情况
- 类没有重写(Override)
equals()
方法,此时与==
等效 - 类重写了
equals
方法,如果两个类的属性相同,则返回true
分析:因为a 和 b 都是 new出来的对象,它们在内存中的地址是不一样的,通过==
比较的是它们的内存地址,因此返回false。通过eqals()
比较的是它们的属性值,都是123,因此返回true
hashCode() & equals()
- 为什么重写
hashCode()
必须重写equals()
?
hashCode()
的作用是获取哈希值(int整数),也称散列码。这个哈希值的作用是确定该对象在哈希表中的索引位置。hashCode()
定义在Objeect
中,所以java中的所有类都有hashCode()
方法。Object
的hashCode()
是本地方法,由C/C++实现,同时用来将对象的内存地址转为整数返回。
哈希表存储的是key-value
键值对,能快速检索对应的值,这其中就用到了哈希值。
HashSet
可以用来去重,原理是什么?
将对象加入HashSet
时,会先计算对象的hashCode
值来判断对象加入的位置(内存地址),同时会与其它对象的hashCode
比较,如果没有相同的hashCode
,此时HashSet
假定没有重复对象。如果有重复的hashCode
出现,这时会调用equals()
来判断对象的属性值是否相等,如果相等。HashSet
会阻止这个添加操作。如果不相等,则将当前对象散列到其它位置。这样先用hashCode
进行第一步检查,会减少equals
调用次数,提升效率。
- 重写
equals()
为什么必须重写hashCode()
?
hashCode()
默认是对堆
上的对象产生哈希值,如果没有重写hashCode
,那么这个class的对象永不相等。,及时它们指向相同数据。
简而言之,equals()
判断两个对象相等,前提是它们的hashCode
也要相等。
- 两个对象的
hashCode
相等,这两个对象也不一定相等,为什么?
因为hashCode
使用的哈希算法可能会让多个对象返回相同的哈希值。越糟糕的算法越容易出现碰撞
(不同对象产生了相同的哈希值),也和数据值域分布有关。
自动装箱拆箱
- 装箱:将基本数据类型用它们的引用类型包装起来
- 拆箱:将包装类型转为基本数据类型
Integer a=10; // 装箱 等价于 Integer a = Integer.valueOf(10);
int i=a; // 拆箱 等价于 int i = a.intValue();
为什么只有值传递
Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
- 方法不能修改一个基本数据类型的参数
- 方法可以改变一个对象参数的状态
- 方法不能让对象参数引用一个新的对象
深浅拷贝
- 深拷贝:Deep Clone,对基本数据类型进行值传递,对引用类型,创建一个新对象,复制其属性值,为深拷贝
- 浅拷贝:Shallow Clone,对基本数据类型进行值传递,对引用类型进行引用传递,为浅拷贝
III. 面向对象
-
面向对象
和面向过程
区别: -
面向对象:易于维护、易于扩展、易于复用。因为封装、继承、多态的特点,可以设计出低耦合的系统。由于类的调用需要实例化,资源开销较大,因此其性能要低于面向过程。
-
面向过程:性能较高。在性能最重要的场景下,采用面向过程开发,例如单片机、嵌入式……
-
如果没有构造方法,程序能否正常执行?
可以正常执行,因为类有默认无参构造方法,且自动调用。构造方法不能被重写,但能重载。
-
面向对象特征:
-
封装:把一个对象的全部属性隐藏在对象内部,不允许被外界直接访问,但可以提供一些方法让外界可以操作属性,例如getter ,setter方法
- 继承:不同类型的对象,通常会有一些共同属性,这些公共的属性可作为一个建立新类型的基础。基于此基础建立的新类型,也具有这些属性。子类拥有父类所有属性和方法,但是只能访问非私有属性和方法。子类可拥有自己的属性和方法。子类可对父类方法重新实现(重写)
- 多态:即对象的多种状态。
IV. 反射
什么是反射
反射,在程序运行期间,对任意的对象,都能访问其全部属性和方法(无论是否私有),并且能修改属性。
通过反射来访问对象的私有属性:
反射优缺点:
- 优点:代码更加灵活。
- 缺点:有安全问题;在编译期间无法进行参数检查;存在性能问题(问题不大)
反射应用场景:各种开箱即用的框架,如大名鼎鼎的Spring/SpringBoot……此外还有常见的注解
V. 序列化&反序列化
- 序列化:将对象或数据结构转换成二进制字节流的过程。目的是将对象存储到文件、数据库、内存中。
- 反序列化:将二进制字节流转换成数据结构或对象的过程
对于不想参与序列化过程的字段,可以用transient
关键字修饰,作用是阻止字段被序列化,在反序列化时,被修饰的字段值不会被持久化和恢复。
tansient
只能修饰变量、不能修饰类、方法;
transient
修饰的变量,在反序列化后会被重置为类型的默认值,如int类型默认值0;
static
修饰的变量不属于任何对象(通过类名直接调用),无论是否有transient
修饰,均不参与序列化;