前言

这篇文章主要记录Java回炉重造的基础阶段的学习。

Java基础语法

基本数据类型

java主要分为基本数据类型和引用数据类型,其中基本数据类型分为数值型(整数和浮点数)、字符型、布尔型,引用数据类型分为类、接口、数组

基本数据类型 占用的字节数 数据范围
byte 1 -128 ~ 127
short 2 -32768 ~ 32767
int 4 -2147483648 ~ 2147483647
long 8 -2^63 ~ 2^63-1
float 4 -3.403E38 ~ 3.403E38
double 8 -1.798e308 ~ 1.798E308
char 2 -

整型

Java中整型默认为int型,声明long型常量需要在后面加上”l”或者”L”,变量一般声明为int型,除非不足才用long以节省资源。

1
2
3
int n1 = 1;//四个字节 正确的
int n2= 1L;// 错误写法,从long到int会丢失数据
long n3 = 1L;//正确写法 定义long

浮点数

由于二进制转换的问题,计算机中的浮点数均为近似数,Java中的浮点数默认是double,声明float变量的时候后面要加上”f”或者”F”,浮点数的常量通常用十进制如5.12 .512 512.0f或者科学计数法如5.12e2 5.12E-1 分别代表5.1210^2 和5.1210^-2,通常来说应该使用double以此来保留精度。

1
2
3
4
5
6
7
8
9
10
11
12
13
double m = 2.1234567851;//正确 
double n = 2.1234567851F;//错误 因为float最多七位数 输出是2.1234567
double n1 = 1.1f;//正确 因为double是双精度 可以存的下单精度的数字

double n2 = 2.7;//正确
double n3 = 8.1/3;//输出2.6999999999997 不是2.7 精度损失问题

if (n2 == n3){
System.out.println("相等");//错误写法 精度损失 不执行
}
if(Math.abs(n2 - n3) < 0.00001){
System.out.println("相等");//正确写法 绝对值只差小于允许的精度范围
}

字符类型

字符类型可以存放的是单个字符,char类型占用两个字节这样可以存中文,多个字符使用String类型。char的本质是一个编码的整数。

1
2
3
4
5
char c1 = 'a';//正确的 存单个字符
char c2 = '\t';//正确的 可以存转义字符 输出一个制表位 啥都看不到
char c3 = '王';//正确的 可以存中文
char c4 = 97;//正确的 可以存数字 存的是ascii码 输出小写a
char c5 = "a";//错误 字符类型用单引号:''

布尔类型

布尔类型只有true和false,没有null,boolean类型适合用于逻辑运算。注意Java和C语言不一样,不可以用0和非0数来表示true和false。编译阶段就会报错。

自动类型转换

Java可以对数据类型进行自动转换,精度小的类型自动转换为精度大的类型,转换规则有两种方式:
char -> int -> long -> float -> double
byte -> short -> int -> long -> float -> double

注意:(byte short)与char 不能自动转换,但是三者之间可以计算,计算之前会自动转换成int类型。boolean不参与运算。所有数据类型参与运算时,会把结果提升为所有数据类型中精度最大的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
float d = 1 + 1.1;//直接报错 右边是double类型 左边是float
float d1 = 1 + 1.1f;//正确 转成了float类型

byte b1 = 10;//正确的
byte b3 = 1;
int n2 = 1;//依然是正确的
byte b2 = n2;//错误 变量赋值从高精度到低精度
char c1 = b1;//错误 byte不能自动转成char

short s1 =1;
short s2 = b2 + s1;//错误 short + byte => int

byte b4 = b3 + b1 ;//错误 b3 + b1后已经转换成int类型

boolean pass = true;
int num1 = pass;//直接报错 因为boolean不能转成任何类型

强制类型转换

自动类型转换的逆过程,将精度大的类型转换成精度小的类型,使用时候要加上强制转换符(),但可能会造成精度降低或溢出,格外注意!

1
2
3
4
5
6
7
8
int n1 = (int)1.9;//正确 但是输出的结果是1 精度丢失
int x = (int)10*3.5 + 6*1.5;//直接报错 因为强制转换符只作用在最近的数字(10)上 右边已经转换成了double类型
int x = (int)(10*3.5+6*1.5);//正确 整体转换

char c1 = 100;//正确
int m = 100;//正确
char c2 = m;//错误 char类型可以保留int的常量值,但不能是变量值
char c3 = (char)m;//正确

String和基本数据类型转换

String转成基本数据类型时,必须确保能正确转换,例如”hello”就不能转成int类型,否则会抛出异常。

1
2
3
4
5
6
7
8
9
10
11
int n1 = 100;
String str1 = n1+"";//正确 输出字符串的100

String str2 = "123";
int num1 = Integer.parseInt(str2);//正确的
double num2 = Double.parseDouble(str2);//正确的 包装类解析

char c = str.charAt(0); //得到第一个字符
char c1 = '男';
char c2 = '女';
char c3 = c1 + c2 ;//这里不是男女 而是一个整数转来的值

运算符

运算符主要包含算数运算符、赋值运算符、关系(比较)运算符、逻辑运算符、位运算符、三元运算符。

算术运算符

算数运算符包含+、-、*、/、%、++、–。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
System.out.println(10 / 4); //左右都是整数 结果是2 不是2.5
System.out.println(10.0/4); //2.5 高精度转换m
double d = 10 / 4 ; //2.0 2 => 2.0

// %的本质是一个公式 a % b = a - [(a / b) *b]
System.out.println(10 % 3); // 1完全正常
System.out.println(-10 % 3); // -1 负数 完全正常
System.out.println(10 % -3); // 1 10 -[(10/-3)*-3] = 1
System.out.println(-10.5%3); //-1.5 -10-[[(int)(-10.5)]/3 * 3 ] 注意此时得到的-1.5是近似值 例如-10.4%3 得到的结果就不是-1.4而是 -1.4000000001

int j = 8;
int k = ++j; //j和k 都是9

int i = 1;
i = i++;
System.out.println(i);//1 处理过程是 tmp = i;i=i+1;i=tmp
int i = 1;
i = ++i;
System.out.println(i);//2 处理过程是 i=i+1; tmp = i; i = tmp

double m = 5 / 9 * (259 - 100);//直接等于0 完全不对 因为 5/9取整是0
double m1 = 5.0 / 9 * (259 -100);//正确 需要一个浮点数 只需要一个就行

关系运算符

关系运算符的结果都是boolean类型,主要用在循环或者判断条件中。> < != ==

逻辑运算符

逻辑运算符用于连接多个条件,结果也是boolean类型。短路于&&,短路或||,取反!,逻辑于&,逻辑或|,^异或

a b a&b a&&b a|b a||b !a a^b
true true true true true true false false
true false false false true true false true
false true false false true true true true
false false false false false false true false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//&& 和 & 的区别:
//&&: 第一个条件是false最终结果直接确定为false
//&: 不管第一个条件如何,都会验证第二个条件 效率较低
int a = 4; int b = 9;
if(a < 1 && ++b < 50){
System.out.println)("ok"); //第一部分为假 不会输出 此时b依然是9
}
if(a < 1 & ++b < 50){
System.out.println)("ok,b="+b); //不会输出 但要判断第二部分b已经变成了10
}
// || 和 | 的区别:
// ||: 两个条件只要有一个成立,结果就是true,第一个是true,第二个就不会判断
// | : 只要有一个条件成立,结果就是true,不管第一个如何都会判断第二个
int a = 4; int b = 9;
if(a > 1 || ++b >4){
System.out.println("ok,b="+b); //会输出 但是b是9
}
if(a > 1 || ++b >4){
System.out.println("ok,b="+b); //会输出 但是b是10
}

System.out.println(!( 60 > 20)); //false
//^异或 a^b 当a和b不同的时候结果才是true 否则false
boolean b = ( 10 > 1 ) ^ ( 3 < 5 );//false

赋值运算符

赋值运算符分为基本赋值运算符(=)以及复合赋值运算符: +=、-=、*=、/=、%=等。赋值运算符顺序是从右往左,左边只能是变量,右边可以是变量、表达式、常量值。

1
2
3
4
byte b = 3; //复合赋值运算符会进行自动类型转换
b += 2; //不是b = b+2 而是会进行类型转换 b = (byte)( b + 2 )
b = b + 2; //直接报错右边是int 左边是byte
b++; // b = (byte)(b+1)

三元运算符

基本语法:条件表达式 ? 表达式1 : 表达式2,如果条件表达式为true,则运算后的结果为表达式1,否则为表达式2。表达式1和表达式2要为可以赋给接受变量的类型或者可以自动转换

1
2
3
4
5
6
7
8
int a = 3; int b = 4;
int c = a > b ? a : b ; //完全正确
int c = a > b ? 1.1 :3.4 ; //不行 double不能传给int
double d = a > b ? a : b + 3 ; //可以 int往上转换

//求三个数中最大值
int a = 55; int b = 33; int c = 123;
int max1 = a > b ? (a > c ? a : c) : (b > c ? b : c)

运算优先级

忘了的时候再查表,但是记住从右往左运算的只有单目运算符和赋值运算符。

标识符

Java中对各种变量、方法和类等明明时使用的字符序列称为标识符。标识符的基本规则:

  1. 由26个英文字母大小写,0-9,_或$组成
  2. 数字不可以开头,例如int 3ab;是错误写法
  3. 不可以用关键字和保留字,但能包含关键字和保留字
  4. Java中严格区分大小写,长度没有限制
  5. 标识符不可以包含空格

标识符规范

包名:多单词组成时所有字母都小写:com.hsp.com
类名、接口名:多单词组成时,所有单词的首字母大写:TankShotGame
变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:tankShotGame
常量名:所有字母都大写。多单词时每个单词用下划线链接:TAX_RATE

关键字和保留字

关键字是被Java赋予了特殊的含义,用作专门用途的字符串,所有关键字都是小写单词,比较多,在idea中会变色提示。保留字是当前的Java版本还没有使用,但以后的版本可能会用,依然无法作为标识符。

进制

对于整数,有四种的表示方式:

  1. 二进制:0,1,满2进1,以0b或0B开头
  2. 十进制:0-9,满10进1
  3. 八进制:0-7,满8进1
  4. 十六进制:0-9及A(10)-F(15),满16进1,以0x或0X开头,注意此处的A-F不区分大小写

进制转换

第一组:

  1. 二进制转十进制
    规则:从最低位(最右边)开始,将每个位上的数提取出来,乘以2的(位数-1)次方,然后再求和。
    案例:把0b1011转成十进制
    0b1011 = 1* 2^(1-1) + 1 * 2^(2-1) + 0 * 2^(3-1) + 1 * 2^(4-1) = 11 (十进制数)

  2. 八进制转十进制
    规则:从最低位(最右边)开始,将每个位上的数提取出来,乘以8的(位数-1)次方,然后再求和。
    案例:把0234转换成10进制数 注意0开头表示8进制数
    0234 = 4 * 8^0 + 3 * 8^1 + 2 * 8^2 = 4 + 24 + 128 = 156

  3. 十六进制转十进制
    规则:从最低位(最右边)开始,将每个位上的数提取出来,乘以16的(位数-1)次方,然后再求和。
    案例:0x23A转换成十进制数
    0x23A = 10 * 16^0 + 3 * 16^1 + 2 * 16^2 = 10 + 48 + 512 = 570

第二组:

  1. 十进制转二进制
    规则:将该数不断除以2,直到商为0为止,然后将每步得到的余数倒序输出,就是对应的二进制
    案例:把34转成二进制
    34 / 2 = 17-0 17 / 2 = 8-1 8 / 2 = 4-0 4 / 2 = 2-0 2 / 2 = 1 -0 => OB00100010 (倒序输出但整数4字节存储 不满1字节的前面补0)

  2. 十进制转八进制
    规则:将该数不断除以8,直到商为0为止,然后将每步得到的余数倒序输出,就是对应的八进制

  3. 十进制转十六进制
    规则:将该数不断除以16,直到商为0为止,然后将每步得到的余数倒序输出,就是对应的十六进制

第三组:

  1. 二进制转八进制
    规则:从地位开始,将二进制数每三位一组,转成对应的八进制数即可。
    案例:把0b11010101 => 101(5) 010(2) 11(3) => 0325

  2. 二进制转十六进制
    规则:从地位开始,将二进制数每四位一组,转成对应的十六进制数即可。
    案例:把0b11010101 => 0101(5) 1101(D) => 0xD5

第四组:

  1. 八进制转二进制
    规则:将八进制数每一位,转成对应的一个3位的二进制数即可
    案例:0237转成二进制数
    0237 = 7(111) 3 (011) 2(010) = 0b10011111(8bit直接不要前面的0)

  2. 十六进制转二进制
    规则:将十六进制数每一位,转成对应的一个4位的二进制数即可
    案例:0x23B转成二进制数
    0x23B = B(1011) 3(0011) 2(0010) = 0b001000111011

原码 反码 补码(重难点)

  1. 二进制的最高位是符号位:0表示正数,1表示负数(口诀:0->0 1-> -)
  2. 正数的原码、反码、补码都一样(三码合一)
  3. 负数的反码 = 它的原码符号位不变,其他位取反(0->1,1->0)
  4. 负数的补码 = 它的反码+1,负数的反码 = 负数的补码-1
  5. 0的反码、补码都是0
  6. java没有无符号数,换言之,java中的数都是有符号的
  7. 在计算机运算的时候,都是以补码的方式来运算的 (这样可以把正数和负数统一起来)
  8. 当我们看运算结果的时候,要看他的原码 (重点)

位运算符

Java中有7个运算符,它们的运算规则是:
按位与 & : 两位全是1,结果为1,否则为0
按位或 | : 两位有一个为1,结果为1,否则为0
按位异或^ :两位为0,一个为1,结果为1,否则为0
按位取反~ :0->1,1->0
例如:2&3
首先得到2的补码 => 2是正数只需要原码 00000000 00000000 00000000 00000010
同理得到3的补码 => 3 00000000 00000000 00000000 00000011
运算后的补码为00000000 00000000 00000000 00000010 由于是正数这也是补码,转成十进制是2,同理,负数与正数的区别是在最高位为1
案例:2
1.得到2的补码 00000000 00000000 00000000 00000010
2.
2操作得到运算补码 11111111 11111111 11111111 11111101
3.运算后的反码(补码-1) 11111111 11111111 11111111 11111100
4.运算后的原码(最高位不变) 10000000 00000000 00000000 00000011
5.~2 = -3

算术左移、右移、逻辑右移 >>、<<、>>>
算术右移>>:低位溢出,符号位不变,并用符号位补溢出的高位
算数左移<<:符号位不变,低位补0
逻辑右移>>>:低位溢出,高位补0
特别说明是没有<<<这个符号

案例:

1
2
int a = 1 >>2;// => 00000001  => 00000000 (01被扔掉) 本质是 1 / 2 / 2 =0
int c = 1 <<2;// => 00000001 => 00000100 每次都向前1位 用0补 本质1 * 2 * 2 = 4

控制结构

控制结构分为顺序控制、分支控制(if,else,switch)、循环控制(for,while,dowhile,多重循环)、break、continue、return

顺序控制

程序从上到下逐行的执行,中间没有任何判断和跳转。即执行语句1->执行语句2->执行语句n

1
2
3
4
5
int num1 =12;
int num2 = num1 + 2;//正确

int num2 = num1 + 2; //错误,顺序在前
int num1 = 12;

分支控制

分支控制是让程序有选择的执行,分支控制有三种:单分支、双分支、多分支,分支之间可以嵌套,嵌套尽可能不要超过三层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//单分支:
if(条件表达式){
//条件表达式;(可以多条)
}
//双分支:
if(条件表达式1){
//执行代码块1;
}else{
//执行代码块2;
}
//多分支:
if(条件表达式1){
//执行代码块1;
}else if(条件表达式2){
//执行代码块2;
if (条件表达式3){
//执行代码块3,分支嵌套;
}
}
......
else{
//执行代码块n,最后这个else可以没有;
}
//switch分支结构
//注意事项:表达式和后面case的常量类型一致或者可以自动转换的类型,例如输入的是字符,而常量是int类型。表达式中的值只能是:(byte,short,int,char,enum[枚举],String),case中的值(结果)只能是常量不能是变量,
switch(表达式){
case 常量1:
//语句块1;
berak;
case 常量2:
//语句块2;
break;
default:
//default语句块;
break;
}

循环控制

for循环控制:循环条件是返回一个布尔值的表达式,for循环中的初始化和迭代可以写到循环体内,但两边的分号不可省略。

1
2
3
for(循环变量初始化;循环条件;循环变量迭代){
//循环操作;
}

while和dowhile循环:while和for循环一样也有四要素,只是位置不同

1
2
3
4
5
6
7
8
while(循环条件){
//循环体;
//循环变量迭代;
}
do{
//循环体;
//循环变量迭代;
}while(循环条件);

break细节:当break出现在多层嵌套语句的时候,可以用标签来指明要终止的是那一层的语句块,建议最好别用标签来结束。break不带标签会自动退出最近的一层label

1
2
3
4
5
6
7
8
label1:
for(int j = 0;j < 4 ; j++){
label2:
for(int i = 0; i < 10 ; i++){
if(i == 2) break ;//默认结束label2
}
System.out.println("i="+i); //i=0 i=1 输出四次
}

continue细节:
continue和label搭配使用的时候也是默认结束最近的这一次label,如果没有label就是当前大括号最近的循环层。

数组

数组可以存放多个同一类型的数据,数组也是一种数据结构,是引用类型。数组是解决多个相同类型变量产生的数量自定义的问题。

数组的定义方式有三种:

1
2
3
4
5
6
7
//动态初始化1
double[] array = new double[5];
//动态初始化2
int[] a; //此时还没有分配内存空间 此时是空指针
a = new int[10]; //此时开始开辟连续的10个地址空间 空间总大小等于类型 * 数据个数
//静态初始化
int[] a = {2,3,4,5}; //这种方式等于动态初始化2以后每个地址赋值

数组的注意事项

  1. 数组是多个相同类型的组合,如果不同数据类型之间必须要可以自动转换 例如从int到double 但不能把double放到int数组中。
  2. 数组的元素可以放任何数据类型,包括基本类型和引用类型,但不能混用
  3. 数组创建后如果没有赋值,是带有默认值的,int,short,byte,long都为0,float,double是0.0,chart是\u0000,boolean是false,String是null
  4. 数组的下标从0开始,其自带了length属性表示长度。
  5. 数组下标必须在合理范围使用否则会报越界异常
  6. 数组属于引用类型,数组型数据是对象(Object)

数组赋值机制

基本数据类型是采用的值拷贝的方式,当一个变量赋值给另一个变量的时候,两个变量之间并不存在直接关联关系,第二个变量的变化不会影响第一个变量,但数组的赋值方式是引用传达,其赋值的是一个地址,因此会影响到原数组,基本原理是JVM当中栈和堆的使用。

1
2
3
4
5
6
7
int n1 = 10;
int n2 = n1;
n2 += 10; //n2 = 20 n1 = 10 不影响原变量
d
int[] arr1 = {1,2,3};
int[] arr2 = arr1;
arr[0] = 3; //arr2 = {3,2,3} arr1 = {3,2,3}

数组扩容

数组定义的时候是直接固定了大小的,因此希望可以实现动态的数组扩容。思路:创建一个新数组长度是原数组的长度+1,数据拷贝后最后一个数据放到数组尾部。(后期用集合实现自动扩容)

1
2
3
4
5
6
7
int[] arr = {1,2,3};
int[] arrNew = new int[arr.length + 1];
for(int i = 0 ; i < arr.length; i++){
arrNew[i] = arr[i];//拷贝元素
}
arNew[arrNew.length - 1] = 4; //赋值新的数据
arr = arrNew;//赋值地址

二维数组

很多时候我们需要用到二维数组,例如一维数组代表一行的话,二维数组可以代表N行N列的矩阵,二维数组的创建方式和一维数组类似,二维数组可以看作一维数组,这个一维数组的每一个元素都是另一种一维数组,例如2行3列的二维数组创建的时候首先在栈当中开辟2个地址,然后这两个地址分别指向另外2个长度为3的堆内存的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int[][] arr = {{0,0,0},{1,1,1},{2,2,2}}; 
for(int i = 0;i < arr.length;i++){
for(int j = 0;j < arr[i].length;j++){
System.out.print(arr[i][j]+" ");
}
}
//列数不确定的二维数组 {{1},{2,2},{3,3,3}}
int[][] arr = new int[3][]; //此时的列是没在堆当中开空间的 只有栈有空间
for(int i = 0; i < arr.length; i++){
arr[i] = new int[i+1]; //每一行单独开空间 通过arr[i]拿到一个一维数组
//遍历数组,赋值
for(int j = 0;j < arr[i].length; j++){
arr[i][j] = i + 1;
}
}