1. 类型转换
1.1 数据类型
1.1.1 内置数据类型
4 个整数型, 2 个浮点型,1 个字符,1 个布尔
1. 整型(有符号)1byte = 8bit
byte: 8 位 / 1 字节 ,默认 0,表示范围:-128 到 127 (-2⁷ 到 2⁷ - 1),用在大型数组中节约空间,主要代替整数。
short: 16 位 / 2 字节 ,默认 0,表示范围:-32,768 到 32,767 (-2¹⁵ 到 2¹⁵ - 1)。
int: 32 位 / 4 字节 ,默认 0,表示范围:-2³¹ 到 2³¹ - 1,大约 ±21亿。
long: 64 位 / 8 字节 ,默认 0L,表示范围:-2⁶³ 到 2⁶³ - 1
2. 浮点型(符合 IEEE 754 标准),可以精确地表示数字 0,还能区分 正零 (+0.0 或 0.0) 和 负零 (-0.0)。
float: 32 位 / 4 字节 ,单精度,默认0.0f,大约 ±3.4E+38F = ± 3.4 × 10³⁸,存大型浮点数组的时候可节省内存。(有效位数 6-7 位小数)
double:64 位 / 8 字节,双精度,默认0.0d,取值范围大约 ±1.7E+308,
注意:double 的范围关于零点对称——
最大的负数和最大的正数对称:~ -1.7 × 10³⁰⁸ 和 ~ +1.7 × 10³⁰⁸
最小的负数和最小的正数对称:~ -4.9 × 10⁻³²⁴ 和 ~ +4.9 × 10⁻³²⁴
它们要这样获取:
最小规格化正数(最小正正规数):常量 Double.MIN_VALUE 表示最小的正数 4.9 × 10⁻³²⁴
对应的负数叫 ”最接近 0 的负规格化数“ :- Double.MIN_VALUE = - 4.9 × 10⁻³²⁴
最大有限正数:常量 Double.MAX_VALUE = 1.7 × 10³⁰⁸
最大有限负数:常量 -Double.MAX_VALUE = -1.7 × 10³⁰⁸
注意 .MIN_VALUE 指的是最接近零的那俩;.MAX_VALUE 指的是最外边那俩边界
float 也是一样的:
最大有限值对称: -3.4 × 10³⁸ (-Float.MAX_VALUE) 到 +3.4× 10³⁸ (Float.MAX_VALUE)
最接近零的值对称: -1.4 × 10⁻⁴⁵ (-Float.MIN_VALUE) 到 +1.4 × 10⁻⁴⁵ (Float.MIN_VALUE)
特殊值对称:正零 (+0.0f) 和 负零 (-0.0f)、正无穷大 (Float.POSITIVE_INFINITY) 和 负无穷大 (Float.NEGATIVE_INFINITY)
他们的最大MAX、最小MIN 都是指跟 0 的距离最大、最小
3. boolean: 1 位(理论上),仅 true、false 两个取值,默认值是 false 。在 JVM 实现层面,因为需要内存对齐和寻址,所以占 1 字节 或 4 字节 。
4. char: 无符号,在 Java 内存中总是占用 2 个字节,取值范围是(0 到 65,535,2^16),可存任何字符。 它本质上是一个 UTF-16 代码单元。
对大多数常见字符(如英文字母、常用汉字),一个 char 就是一个完整的字符,占用 2 个字节。
少数扩展字符,如一些 emoji、非常生僻的汉字,需要一个 char 对(两个 char,4字节)来表示。
String s = "😊"; // 字符串内部用char数组存储,对于"😊",这个数组长度是2。
为什么 boolean 型规定只占 1 位,但在 JAVA 中,实际却大于 1 位 呢?
由于内存对齐和寻址规则,用 1 字节的空间 换 高速的处理时间,在 JVM 实现层面,boolean 型占 1 字节(局部变量、对象成员、布尔数组) 或 4 字节(栈帧的数据区操作数栈最小 4 字节)
最小可寻址单元:
CPU 的基本内存寻址和操作单位是字节(byte)。直接访问一个比特,在硬件层面非常复杂且效率极低。
具体来讲,可以把内存理解为一个大的字节数组,每个字节有一个唯一的内存地址。CPU 通过地址总线来读写数据,而地址总线指向字节,而非比特。所以 CPU 可以一次读取或写入1 个字节(单字节)、双字节、4 字节等,却无法直接寻址到单个比特。
内存对齐:
为了有最佳性能,CPU 要求数据在内存中的地址与其自身大小对齐。例如 int(32位,4字节)最好放在地址是 4 的倍数的位置上。
一个 boolean 规定只占 1 比特,那么它可能放在任何位置,比如一个字节的中间。
CPU 要想读写这个比特位,要么读整个字节,要么用位操作(如掩码和移位)来读,要想写,就得把改完的整个字节写回内存。整个“读-修改-写”的过程不是原子性的(在多线程环境下会引发问题),且比直接读写一个字节要慢得多。
硬件原生支持直接操作一个字节,这个速度是很快的。而操作一个比特。则需要多条 CPU 指令。用 1 字节的空间 换 高速的处理时间,典型的 以空间换时间 的权衡,绝大多数情况下,都是值得的。
Java 没有明确规定 boolean 在内存中的确切大小,而是将决定权交给了JVM。而 JVM 的设计目标之一是高效执行,因此它遵循了底层硬件的特性。
- 在 局部变量 或 对象成员 中,将 boolean 型存为 1个字节,是因为将其对齐到字节边界处理起来最简单、最快。
- 在数组中,JVM 规范明确指出,boolean 数组中的每个元素应被编码为 1个字节。保证每个数组元素都可以被直接寻址
- 在栈帧的操作数栈中,JVM运行时,数据区的操作数栈最小单位是 4 字节,小于4字节的类型会被转为4字节(int)计算(通过符号扩展或零扩展),计算完再截断存回。
1.1.2 引用数据类型
不直接存对象本身,而是存了一个指向 对象在内存中位置 的 “地址”或“指针”。默认值都是null。就像一个快捷方式或一张写着“文件在哪个柜子、哪个抽屉”的地址卡片。通过卡片能找到真正的文件(对象)。
Java 的引用数据类型主要分为四类:
- 类引用
// String, Integer, Scanner等的 new 对象都是引用数据类型
MyClass myObject;
// 使用new在堆上创建一个 MyClass 对象,并将其地址赋给 myObject
myObject = new MyClass();
// 通过引用 ‘myObject’ 来操作对象
myObject.doSomething();
- 接口引用
//接口本身不能被实例化,但可以声明一个接口类型的引用变量,指向实现了该接口的类的对象。这是实现多态的关键。
List<String> myList; // 声明一个接口类型的引用
myList = new ArrayList<>(); // 指向一个实现了List接口的ArrayList对象
// 只能调用List接口中定义的方法
myList.add("Hello");
- 数组引用
//数组在 Java 中也是对象,因此数组变量也是引用类型。
int[] arr;
// 在堆上创建一个包含5个int的数组对象,并将其地址赋给 ‘arr‘
arr = new int[5];
// 另一种初始化方式,数组内容 {1, 2, 3} 也在堆上
int[] arr2 = {1, 2, 3};
/*
int[] arr1 = new int[]{1,2,3,4};
System.out.println(arr1);//会输出 arr1 的内存地址
// [I@10f87f66 : [ 表示当前空间是个数组元素
I 表示元素的数据类型
@ 分隔符
10f87f66 十六进制内存地址
*/
- 枚举引用
enum Day { MONDAY, TUESDAY, WEDNESDAY }
Day today = Day.MONDAY; // ‘today’ 是一个指向枚举常量 ‘MONDAY‘ 的引用
1.1.3 关键特性
- 内存分配
- 栈内存:方法运行时,进入栈内存。 局部变量(包括基本类型变量和引用类型变量)都存在这。方法执行完,栈帧被清除,这些变量自动销毁。
- 堆内存:用于存储所有对象实例(包括数组),所有 new 出来的内容会在堆内存中开辟空间,每 new 一次,开辟一块,不重复,并产生地址值。由垃圾回收器管理。
内存状态:
- 方法区:一运行,字节码文件(.class文件)就会加载进方法区,main方法被 JVM 自动调用,进入栈内存执行
- 本地方法栈: 调用操作系统相关文件
- 寄存器:CPU 用的
- 是否相等
==:对于引用类型,比较的是内存地址,是否指向同一个对象。
.equals():比较的是内容是否逻辑上相等,通常需要重写此方法。
String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println(s1 == s2); // false! 两个不同的对象,地址不同。
System.out.println(s1.equals(s2)); // true! String类重写了equals方法,比较内容。
- 特殊值:null
是引用变量,但不指向任何对象。若用值为 null 的引用来访问方法或字段,会抛出错误 NullPointerException - 参数传递:Java 中只有值传递
引用类型传递的是引用的副本(即地址的拷贝),而非对象本身。在方法内修改引用所指向对象的状态(如修改成员变量),会影响原始对象。但若使引用参数指向新对象,不会影响原始引用。
public static void modify(StringBuilder sb) {
sb.append(" World"); // 1. 修改了指向的对象,原始对象被改变
sb = new StringBuilder(); // 2. 引用指向新对象,原始引用不受影响
}
public static void main(String[] args) {
StringBuilder original = new StringBuilder("Hello");
modify(original);
System.out.println(original); // 输出: "Hello World"
}
- 比较总结
1.2 类型转换
1.2.1 隐式转换
- 范围小的可以直接赋给范围大的;
- 小的和大的做运算,小变大,再运算;
- byte、short、char 运算时,不论是否有更高的数据类型,都会先变 int ,再运算
如:int(32 bit) < double(64 bit)
int a = 10;
double b = a; // 系统自动将 a 提升为 10.0
//打印 b 会输出 10.0
double c = 12.3;
double d = a + c; // a 先变 double
byte e = 3;
byte f = 4;
byte g = e + f; //报错,因为 e、f 运算前先变 int,算完还是 int,要强转后才能赋给 byte
byte g = (byte)(e + f); //正确
byte h = 3 + 4; //正确,在这里3、4是常量,JAVA有常量优化机制,算完若不在 byte 范围内才会报错,若在范围内,则编译通过。
long i = 123; // 123 默认是 int, 赋给 long, 小给大,自动转了,正确
long j = 123456789123456789; //错了,默认 int,自动转,但是超过 int 范围了,它转不明白,会报错:整数太大
long k = 123456789123456789L; //这样 OK
范围从小到大如下:
long(8 字节):-2⁶³ 到 2⁶³ - 1
float (4 字节): -3.4 × 10³⁸ 到 3.4 × 10³⁸
2⁶³ - 1 远小于 3.4 × 10³⁸
1.2.2 强制转换
大范围的赋给小范围的,要强转(溢出或损失精度),浮点数强转到整数,小数全舍
int a = 10;
byte b = (byte)a;
1.3 计组补充知识
1.3.1 原码补码
计算机中的数据,均以 2 进制补码形式运算,忘了就回去翻书翻课件!
计算机的运算器中只有加法器,所以减法要转换成加法来计算。
左高位为符号位,0 正 1 负
这里用人家的图,你快速回顾一下咋求原码!!大神 up 主页在这:计算机知识星球
正数的原、反、补码均是原码本身。所以求这些东西,先看正负数!
负数原码求反码:符号位不变,其他取反
负数求补码,在反码基础上 + 1
// 写的不大规范,重在理解逻辑
-21D = [ 10010101 B]原 = [ 11101010 B]反 = [ 11101011 B]补
2. 其他基础语法
2.1 键盘输入/输出 和 Random
import java.util.Scanner;
Scanner sc = new Scanner(System.in);
int i = sc.nextInt();
System.out.println(i);
import java.util.Random;
Random r = new Random();
int num = r.nextInt(10); //[0,10),注意不含 10
2.2 运算符
% 取余
/ 除法(若为整数除法,则结果取整)
& 按位与:全 1 为 1,其余为 0
| 按位或:全 0 为 0,其余为 1
^ 按位异或:同 0 异 1
〜 按位取反
<< 按位左移,并在右侧补0。 A << 2 按位左移2位
>> 按位右移(有符号右移):高位补符号位(正数补0,负数补1)
>>> 无符号右移:高位一律补0,结果总是非负数。
如果上面这些右边带一个 = ,如 a %= b :a = a%b, 都是左跟右算了,再赋给左
条件运算符(三元运算符):主要是决定哪个值应该赋值给变量。
int b = (a == 1) ? 10 : 20
// 若 a == 1成立,则 b = 10 ;’:‘左边成立; 若表达式为 false,则 b = 20; ’:‘右边成立;
instanceof 检查该对象是否是一个特定类型(类类型或接口类型)。
String name = "James";
boolean result = name instanceof String; // 由于 name 是 String 类型,所以返回真
Vehicle a = new Car();
boolean result = a instanceof Car;
2.3 跳转控制语句
- continue : 在循环内部,基于条件控制使用,跳过当前循环体内容的执行,继续下一次的执行
- break : 在循环内部或switch中,基于条件控制使用,结束整个循环
3. 面向对象编程
3.1 访问修饰符
定义类、方法或变量时,要注意。
- 不写访问修饰符:同一包可见,使用对象:类、接口、变量、方法;
- public:所有类可见,使用对象:类、接口、变量、方法;
- private:最严格!同一类可见,使用对象:变量、方法。不能修饰类(外部类);
- protected:同一包内 类和所有子类 可见。使用对象:变量、方法。不能修饰类(外部类)。
非访问修饰符:
- static:修饰类方法和类变量;
- final:修饰类、方法和变量。final 修饰的类不能被继承,方法不能被继承类重新定义,修饰的变量为常量,不可修改。
先到这,后面再补