0
点赞
收藏
分享

微信扫一扫

[Java 学习笔记] 一.部分基础细节

开学以来对Java方面的学习有些懈怠,遂决定趁国庆假期前后把Java从基础到泛型,集合冲一遍(边学边练,有详有略), link start!

Java入口程序规定的方法必须是静态方法,方法名必须为​​main​​,括号内的参数必须是String数组

//命令行参数类型是​​String[]​​数组;

//命令行参数由JVM接收用户输入并传给​​main​​方法;

//如何解析命令行参数需要由程序自己实现

定义浮点数的例子:

float f1 = 3.14f;
float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
double d = 1.79e308;
double d2 = -1.79e308;
double d3 = 4.9e-324; // 科学计数法表示的4.9x10^-324

对于​​float​​​类型,需要加上​​f​​后缀。

浮点数可表示的范围非常大,​​float​​​类型可最大表示3.4x1038,而​​double​​类型可最大表示1.79x10308。

Java语言对布尔类型的存储并没有做规定,因为理论上存储布尔类型只需要1 bit,但是通常JVM内部会把​​boolean​​表示为4字节整数。

var关键字

有些时候,类型的名字太长,写起来比较麻烦。例如:

StringBuilder sb = new StringBuilder();

这个时候,如果想省略变量类型,可以使用​​var​​关键字:

var sb = new StringBuilder();

编译器会根据赋值语句自动推断出变量​​sb​​​的类型是​​StringBuilder​​。对编译器来说,语句:

var sb = new StringBuilder();

实际上会自动变成:

StringBuilder sb = new StringBuilder();

因此,使用​​var​​定义变量,仅仅是少写了变量类型而已。

关系运算符的优先级

从高到低依次是:

  • ​!​
  • ​>​​​,​​>=​​​,​​<​​​,​​<=​
  • ​==​​​,​​!=​
  • ​&&​
  • ​||​

如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。

因为​​false && x​​​的结果总是​​false​​​,无论​​x​​​是​​true​​​还是​​false​​​,因此,与运算在确定第一个值为​​false​​​后,不再继续计算,而是直接返回​​false​​。

类似的,对于​​||​​​运算,只要能确定第一个值为​​true​​​,后续计算也不再进行,而是直接返回​​true​​:

boolean result = true || (5 / 0 > 0); // true

三元运算​​b ? x : y​​​后面的类型必须相同,三元运算也是“短路运算”,只计算​​x​​​或​​y​​ 

执行​​String s = "hello";​​​时,JVM虚拟机先创建字符串​​"hello"​​​,然后,把字符串变量​​s​​指向它:

s


┌───┬───────────┬───┐
│ │ "hello" │ │
└───┴───────────┴───┘

紧接着,执行​​s = "world";​​​时,JVM虚拟机先创建字符串​​"world"​​​,然后,把字符串变量​​s​​指向它:

s ──────────────┐


┌───┬───────────┬───┬───────────┬───┐
│ │ "hello" │ │ "world" │ │
└───┴───────────┴───┴───────────┴───┘

原来的字符串​​"hello"​​​还在,只是我们无法通过变量​​s​​访问它而已。

因此,字符串的不可变是指字符串内容不可变。

空值null

引用类型的变量可以指向一个空值​​null​​,它表示不存在,即该变量不指向任何对象。例如:

String s1 = null; // s1是null
String s2; // 没有赋初值值,s2也是null
String s3 = s1; // s3也是null
String s4 = ""; // s4指向空字符串,不是null

注意要区分空值​​null​​和空字符串​​""​​,空字符串是一个有效的字符串对象,它不等于​​null​

数组的初始化

int[] ns = new int[5];

也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小

int[] ns = new int[] { 68, 79, 91, 85, 62 };
System.out.println(ns.length); // 编译器自动推算数组大小为5

还可以进一步简写为:

int[] ns = { 68, 79, 91, 85, 62 };

 注意数组是引用类型,并且数组大小不可变。我们观察下面的代码:

// 5位同学的成绩:
int[] ns;
ns = new int[] { 68, 79, 91, 85, 62 };
System.out.println(ns.length); // 5
ns = new int[] { 1, 2, 3 };
System.out.println(ns.length); // 3

对于数组​​ns​​​来说,执行​​ns = new int[] { 68, 79, 91, 85, 62 };​​时,它指向一个5个元素的数组:

ns


┌───┬───┬───┬───┬───┬───┬───┐
│ │68 │79 │91 │85 │62 │ │
└───┴───┴───┴───┴───┴───┴───┘

执行​​ns = new int[] { 1, 2, 3 };​​时,它指向一个新的3个元素的数组:

ns ──────────────────────┐


┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ │68 │79 │91 │85 │62 │ │ 1 │ 2 │ 3 │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

但是,原有的5个元素的数组并没有改变,只是无法通过变量​​ns​​引用到它们而已。

字符串数组

如果数组元素不是基本类型,而是一个引用类型,那么,修改数组元素会有哪些不同?

字符串是引用类型,因此我们先定义一个字符串数组:

String[] names = {
"ABC", "XYZ", "zoo"
};

对于​​String[]​​​类型的数组变量​​names​​,它实际上包含3个元素,但每个元素都指向某个字符串对象:

┌─────────────────────────┐
names │ ┌─────────────────────┼───────────┐
│ │ │ │ │
▼ │ │ ▼ ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┐
│ │░░░│░░░│░░░│ │ "ABC" │ │ "XYZ" │ │ "zoo" │ │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┘
│ ▲
└─────────────────┘

对​​names[1]​​​进行赋值,例如​​names[1] = "cat";​​,效果如下:

┌─────────────────────────────────────────────────┐
names │ ┌─────────────────────────────────┐ │
│ │ │ │ │
▼ │ │ ▼ ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┬───────┬───┐
│ │░░░│░░░│░░░│ │ "ABC" │ │ "XYZ" │ │ "zoo" │ │ "cat" │ │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┴───────┴───┘
│ ▲
└─────────────────┘

这里注意到原来​​names[1]​​​指向的字符串​​"XYZ"​​​并没有改变,仅仅是将​​names[1]​​​的引用从指向​​"XYZ"​​​改成了指向​​"cat"​​​,其结果是字符串​​"XYZ"​​​再也无法通过​​names[1]​​访问到了。

数组的遍历

        int[] ns = { 1, 4, 9, 16, 25 };
        for (int n : ns) {
            System.out.println(n);
        }

        int[] ns = { 1, 4, 9, 16, 25 };
        for (int n : ns) {
            System.out.println(n);
        }

在​​for (int n : ns)​​​循环中,变量​​n​​​直接拿到​​ns​​数组的元素,而不是索引。

显然​​for each​​循环更加简洁。但是,​for each​​循环无法拿到数组的索引,因此,到底用哪一种​​for​​循环,取决于我们的需要

使用​​for each​​循环打印也很麻烦。幸好Java标准库提供了​Arrays.toString()​,可以快速打印数组内容

        int[] ns = { 1, 1, 2, 3, 5, 8 };
        System.out.println(Arrays.toString(ns));

对数组排序实际上修改了数组本身。例如,排序前的数组是:

int[] ns = { 9, 3, 6, 5 };

在内存中,这个整型数组表示如下:

┌───┬───┬───┬───┐
ns───>│ 9 │ 3 │ 6 │ 5 │
└───┴───┴───┴───┘

当我们调用​​Arrays.sort(ns);​​后,这个整型数组在内存中变为:

┌───┬───┬───┬───┐
ns───>│ 3 │ 5 │ 6 │ 9 │
└───┴───┴───┴───┘

即变量​​ns​​指向的数组内容已经被改变了。

如果对一个字符串数组进行排序,例如:

String[] ns = { "banana", "apple", "pear" };

排序前,这个数组在内存中表示如下:

┌──────────────────────────────────┐
┌───┼──────────────────────┐ │
│ │ ▼ ▼
┌───┬─┴─┬─┴─┬───┬────────┬───┬───────┬───┬──────┬───┐
ns ─────>│░░░│░░░│░░░│ │"banana"│ │"apple"│ │"pear"│ │
└─┬─┴───┴───┴───┴────────┴───┴───────┴───┴──────┴───┘
│ ▲
└─────────────────┘

调用​​Arrays.sort(ns);​​排序后,这个数组在内存中表示如下:

┌──────────────────────────────────┐
┌───┼──────────┐ │
│ │ ▼ ▼
┌───┬─┴─┬─┴─┬───┬────────┬───┬───────┬───┬──────┬───┐
ns ─────>│░░░│░░░│░░░│ │"banana"│ │"apple"│ │"pear"│ │
└─┬─┴───┴───┴───┴────────┴───┴───────┴───┴──────┴───┘
│ ▲
└──────────────────────────────┘

原来的3个字符串在内存中均没有任何变化,但是​​ns​​数组的每个元素指向变化了。

二维数组

的每个数组元素的长度并不要求相同,例如,可以这么定义​​ns​​数组:

int[][] ns = {
{ 1, 2, 3, 4 },
{ 5, 6 },
{ 7, 8, 9 }
};

这个二维数组在内存中的结构如下:

┌───┬───┬───┬───┐
┌───┐ ┌──>│ 1 │ 2 │ 3 │ 4 │
ns ─────>│░░░│──┘ └───┴───┴───┴───┘
├───┤ ┌───┬───┐
│░░░│─────>│ 5 │ 6 │
├───┤ └───┴───┘
│░░░│──┐ ┌───┬───┬───┐
└───┘ └──>│ 7 │ 8 │ 9 │
└───┴───┴───┘

要打印一个二维数组,可以使用两层嵌套的for循环:

for (int[] arr : ns) {
for (int n : arr) {
System.out.print(n);
System.out.print(', ');
}
System.out.println();
}

或者使用Java标准库的​​Arrays.deepToString()​​:

int[][] ns = {
{ 1, 2, 3, 4 },
{ 5, 6, 7},
{ 9, 10, 11, 12 }
};
System.out.println(Arrays.deepToString(ns));

三维数组

三维数组就是二维数组的数组。可以这么定义一个三维数组:

int[][][] ns = {
{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
},
{
{10, 11},
{12, 13}
},
{
{14, 15, 16},
{17, 18}
}
};

它在内存中的结构如下:

┌───┬───┬───┐
┌───┐ ┌──>│ 1 │ 2 │ 3 │
┌──>│░░░│──┘ └───┴───┴───┘
│ ├───┤ ┌───┬───┬───┐
│ │░░░│─────>│ 4 │ 5 │ 6 │
│ ├───┤ └───┴───┴───┘
│ │░░░│──┐ ┌───┬───┬───┐
┌───┐ │ └───┘ └──>│ 7 │ 8 │ 9 │
ns ────>│░░░│──┘ └───┴───┴───┘
├───┤ ┌───┐ ┌───┬───┐
│░░░│─────>│░░░│─────>│10 │11 │
├───┤ ├───┤ └───┴───┘
│░░░│──┐ │░░░│──┐ ┌───┬───┐
└───┘ │ └───┘ └──>│12 │13 │
│ └───┴───┘
│ ┌───┐ ┌───┬───┬───┐
└──>│░░░│─────>│14 │15 │16 │
├───┤ └───┴───┴───┘
│░░░│──┐ ┌───┬───┐
└───┘ └──>│17 │18 │
└───┴───┘

如果我们要访问三维数组的某个元素,例如,​​ns[2][0][1]​​​,只需要顺着定位找到对应的最终元素​​15​​即可。

理论上,我们可以定义任意的N维数组。但在实际应用中,除了二维数组在某些时候还能用得上,更高维度的数组很少使用。


举报

相关推荐

0 条评论