@liayun
2016-12-01T13:43:24.000000Z
字数 21295
阅读 1962
java基础
关键字的定义和特点
定义:被Java语言赋予了特殊含义的单词。
特点:关键字中所有字母都为小写。
| 用于定义数据类型的关键字 | ||||
|---|---|---|---|---|
| class | interface | byte | short | int |
| long | float | double | char | boolean |
| void | ||||
| 用于定义数据类型值的关键字 | ||||
| true | false | null | ||
| 用于定义流程控制的关键字 | ||||
| if | else | switch | case | default |
| while | do | for | break | continue |
| return | ||||
| 用于定义访问权限修饰符的关键字 | ||||
| private | protected | public | ||
| 用于定义类,函数,变量修饰符的关键字 | ||||
| abstract | final | static | synchronized | |
| 用于定义类与类之间关系的关键字 | ||||
| extends | implements | |||
| 用于定义建立实例及引用实例,判断实例的关键字 | ||||
| new | this | super | instanceof | |
| 用于异常处理的关键字 | ||||
| try | catch | finally | throw | throws |
| 用于包的关键字 | ||||
| package | ||||
| 其他修饰符关键字 | ||||
| native | strictfp | transient | volatile | assert |
注意:在起名字的时候,为了提高阅读性,要尽量有意义。
Java中的名称规范:
xxxyyyzzz。XxxYyyZzz。xxxYyyZzz。XXX_YYY_ZZZ。用于注解说明解释程序的文字就是注释。注释提高了代码的阅读性。
java中的注释格式:
//注释文字/* 注释文字 *//** 注释文字 */对于单行和多行注释,被注释的文字,不会被JVM(java虚拟机)解释执行。
对于文档注释,是java特有的注释,其中注释内容可以被JDK提供的工具javadoc所解析,生成一套以网页文件形式体现的该程序的说明文档。
注释是一个程序员必须要具有的良好编程习惯。
初学者编写程序可以养成习惯:先写注释再写代码。将自己的思想通过注释先整理出来,在用代码去体现,因为代码仅仅是思想的一种体现形式而已。
例,
/**这个类是用于演示hello world.作者:李阿昀版本:V1.0*/class Demo {/*main函数可以保证该类的独立运行它是程序的入口它会被JVM所调用*/public static void main(String[] args) {//这是输出语句用于将括号内的数据打印到控制台。System.out.println("hello java");System.out.println("hello world");}}
常量表示不能改变的数值。Java中常量的分类:
true、false' ' )标识null对于整数,java有四种表现形式:
0-9,满10进10-7,满8进1,用0开头表示0-9、A-F,满16进1,用0x或0X开头表示0、1,满2进1注意:
-。变量的概念:
为什么要定义变量?
用来不断的存放同一类型的常量,并可以重复使用。(即当数据不确定时,需要对数据进行存储时,就定义一个变量来完成存储动作。)
定义变量的格式:数据类型 变量名 = 初始化值;。注:格式是固定的,记住格式,以不变应万变。
理解:变量就如同数学中的未知数。
Java语言是强类型语言,对于每一种数据都定义了明确的具体数据类型,在内存中分配了不同大小的内存空间。如图:

Java基本数据类型
| 数据类型 | 说明 | 所占内存 | 举例 | 备注 |
|---|---|---|---|---|
| byte | 字节型 | 1 byte | 3, 127 | |
| short | 短整型 | 2 bytes | 3, 32767 | |
| int | 整型 | 4 bytes | 3, 21474836 | |
| long | 长整型 | 8 bytes | 3L, 92233720368L | long最后要有一个L字母(大小写无所谓) |
| float | 单精度浮点型 | 4 bytes | 1.2F, 223.56F | float最后要有一个F字母(大小写无所谓) |
| double | 双精度浮点型 | 8 bytes | 1.2, 1.2D, 223.56, 223.56D | double最后最好要有一个D字母(大小写无所谓) |
| char | 字符型 | 2 bytes | 'a', 'A' | 字符型数据只能是一个字符,由单引号包围 |
| boolean | 布尔型 | 1 bit | true, false |
注意:整数默认为int,小数默认为double。
在Java中可以通过Integer.SIZE这样的方法直接查看基本类型所占内存空间的大小。通过以下程序就能够查看了:
class JavaType {public static void main(String[] args) {System.out.println("Byte: " + Byte.SIZE);System.out.println("Short: " + Short.SIZE);System.out.println("Integer: " + Integer.SIZE);System.out.println("Long: " + Long.SIZE);System.out.println("Float: " + Float.SIZE);System.out.println("Double: " + Double.SIZE);System.out.println("Character: " + Character.SIZE);}}
输出结果为:(单位是bit)
Byte: 8(一个字节)
Short: 16(二个字节)
Integer: 32(四个字节)
Long: 64(八个字节)
Float: 32(四个字节)
Double: 64(八个字节)
Character: 16(二个字节)
Boolean类型有点奇怪,官方文档是这么说的:
This data type represents one bit of information, but its "size" isn't something that's precisely defined.
中文翻译:这种数据类型保存一位的信息,但是它的大小却不是精确定义的。
例,Java基本数据类型举例:
int x = 4;byte b = 2; //2是一个int常量,但是会自动判断2是不是在byte类型的范围内(-128~127),=运算符在给b赋值时,自动完成了强转操作byte b1 = 128; //编译未通过,超出byte类型的范围short s = 30000;long l = 4l;float f = 2.3f;double d = 34.56;char ch = '4';char ch1 = 'a';char ch2 = '+';char ch3 = ' ';char ch4 = '昀'; //一个中文在内存中占2个字节,而char就是2个字节空间大小boolean bo = true;boolean bo1 = false;
自动类型转换(也叫隐式类型转换)和强制类型转换(也叫显式类型转换)。
表达式的数据类型自动提升:
试分析System.out.println('a')与System.out.println('a'+1)的区别。
System.out.println('a'); //aSystem.out.println('a'+1); //98System.out.println((char)('a'+1)); //bSystem.out.println('A'+0); //65System.out.println('1'+0); //49
System.out.println((char)5);会输出♣的形状。
自动类型提升:
byte b = 3;int x = 4;x = x + b;//b会自动提升为int类型进行运算。
强制类型转换:
byte b = 3; // 3是一个int常量,但是会自动判断3是不是在byte类型的范围内(-128~127),=运算符在给b赋值时,自动完成了强转操作b = b + 4;//报错b = (byte)(b+4);//强制类型转换,强制将b+4的结果转换为byte类型,再赋值给b。
算术运算符的注意问题
5%-2=1。但被模数是负数就另当别论,如:-5%2=-1。面试的时候可能会考到。对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。如:
int x = 4270;x = x / 1000 * 1000;System.out.println(x); // 4000
字符串数据和任何数据使用+都是相连接,最终都会变成字符串。如:
System.out.println("5+5="+5+5); //"5+5=55"System.out.println("5+5="+(5+5)); // "5+5=10"
转义字符:通过\来转变后面字母或者符号的含义。如:
\n:换行
\b:退格。相当于backspace键
\r:按下回车键。windos系统中,回车符是由两个字符来表示\r\n
\t:制表符。相当于tab键
例,
System.out.println("hello\tworld");System.out.println("\"hello java\"");System.out.println("\\hello java\\");char ch = '\'';
例,
int a, b, c;a = b = c = 5;
一个面试题:
思考如下代码中s = s + 2;与s += 2;的区别?
short s = 3;s = s + 2;s += 2;
解:
short s = 3;s = s + 2; // 编译失败,因为s会被提升为int类型,运算后的结果还是int类型,无法赋值给short类型s += 2; // 编译通过,因为+=运算符在给s赋值时,自动完成了强转操作
注意:
==不能误写成=逻辑运算符用于连接boolean类型的表达式。
&——AND(与)
| boolean类型的表达式 | 逻辑运算符 | boolean类型的表达式 | 结果 |
|---|---|---|---|
| true | & | true | true |
| true | & | false | false |
| false | & | true | false |
| false | & | false | false |
特点:只要两边的布尔表达式结果有一个为false,那么结果就为false。只有两边都为true,结果为true。
|——OR(或)
| boolean类型的表达式 | 逻辑运算符 | boolean类型的表达式 | 结果 |
|---|---|---|---|
| true | | | true | true |
| true | | | false | true |
| false | | | true | true |
| false | | | false | false |
特点:两边只要有一个为true,结果为true,两边都为false,结果为false。
^——XOR(异或),就是和|有点不一样,当true ^ true = false
| boolean类型的表达式 | 逻辑运算符 | boolean类型的表达式 | 结果 |
|---|---|---|---|
| true | ^ | true | false |
| true | ^ | false | true |
| false | | | true | true |
| false | ^ | false | false |
特点:两边相同,结果是false;两边不同,结果为true。
!——Not(非),即!true = false,!fasle = true。
注意:&和&&的区别以及|和||的区别
&:无论左边是true是false,右边都运算。
&&:当左边为false时,右边不运算。
|:两边都参与运算。
||:当左边为true时,右边不运算。
| 位运算符 | 运算 | 范例 |
|---|---|---|
| << | 左移 | 3 << 2 = 12 → 3 * 2 * 2 = 12 |
| >> | 右移 | 3 >> 1 = 1 → 3 / 2 = 1 |
| >>> | 无符号右移 | 3 >>> 1 = 1 → 3 / 2 = 1 |
| & | 与运算 | 6 & 3 = 2 |
| | | 或运算 | 6 |
| ^ | 异或运算 | 6 ^ 3 = 5 |
| ~ | 反码 | ~6 = -7 |
位运算是直接对二进制进行运算。
注意:
<<:其实就是乘以2的移动的位数次幂。>>:其实就是除以2的移动的位数次幂。位运算符的细节
| 位运算符 | 细节 |
|---|---|
| << | 空位补0,被移除的高位丢弃,空缺位补0 |
| >> | 被移位的二进制最高位是0,右移后,空缺位补0;最高位是1,空缺位补1 |
| >>> | 被移位二进制最高位无论是0或者是1,空缺位都用0补 |
| & | 二进制位进行&运算,只有1&1时结果是1,否则是0 |
| | | 二进制位进行|运算,只有0|0时结果是0,否则是1 |
| ^ | 任何相同二进制位进行^运算,结果是0:1^1=0,0^0=0;不相同二进制位^运算结果是1:1^0=1,0^1=1 |
例,
7 ^ 4111^ 100 (加密)------011^ 100 (解密),密钥:100------111 = 7; ----> 7 ^ 4 ^ 4 = 7;
结论:一个数异或同一个数两次,结果还是那个数。
练习:对两个整数变量的值进行互换(不需要第三方变量)。
解:
第一种方式:(使用第三方变量,实际开发时使用)
int n = 3, m = 8;System.out.println("n = " + n + ", m = " + m);int temp;temp = n;n = m;m = temp;System.out.println("n = " + n + ", m = " + m);
第二种方式:(不需要第三方变量)
int n = 3, m = 8;System.out.println("n = " + n + ", m = " + m);n = n + m; // 如果n和m的值非常大,容易超出int范围m = n - m;n = n - m;System.out.println("n = " + n + ", m = " + m);
第三种方式:(不需要第三方变量,炫技型)
int n = 3, m = 8;System.out.println("n = " + n + ", m = " + m);n = n ^ m;m = n ^ m; // (n ^ m) ^ m = n;n = n ^ m; // n ^ (n ^ m) = m;System.out.println("n = " + n + ", m = " + m);
格式:(条件表达式) ? 表达式1 : 表达式2
例,
int x = 2, y;y = (x > 1) ? 'a' : 200;System.out.println("y = " + y); // 97,自动类型提升
if else结构简写格式:变量 = (条件表达式) ? 表达式1 : 表达式2;
三元运算符的特点:
if else代码if语句练习:
练习一:根据用户定义的数值不同,打印对应的星期英文。
解:
//需求1:根据用户定义的数值不同,打印对应的星期英文class IfTest {public static void main(String[] args) {int num = 2;if(num == 1) {System.out.println("monday");} else if(num == 2) {System.out.println("tsd");} else if {...} else {System.out.println("nono");}}}
练习二:根据用户指定的月份,打印该月份所在的季节。
解:
第一种方式:
//需求2:根据用户指定的月份,打印该月份所在的季节// 3,4,5 春季 6,7,8 夏季 9,10,11 秋季 12,1,2 冬季class IfTest {public static void main(String[] args) {int x = 4;if(x == 3 || x == 4 || x == 5) {System.out.println(x + "春季");} else if(x == 6 || x == 7 || x == 8) {System.out.println(x + "夏季");} else if(x == 9 || x == 10 || x == 11) {System.out.println(x + "秋季");} else if(x == 12 || x == 1 || x == 2) {System.out.println(x + "冬季");} else {System.out.println(x + "月份不存在");}}}
第二种方式:
class IfTest {public static void main(String[] args) {int x = 4;if(x > 12 || x < 1) {System.out.println(x + "月份不存在");} else if(x >= 3 && x <= 5) {System.out.println(x + "春季");} else if(x >= 6 && x <= 8) {System.out.println(x + "夏季");} else if(x >= 9 && x <= 11) {System.out.println(x + "秋季");} else {System.out.println(x + "冬季");}}}
switch语句特点:
byte,short,int,char。但JDK7.0对switch语句进行了增强,可以判断字符串,JDK5.0对switch语句也进行了增强,可以对枚举类型进行判断。例,
int x = 3;switch(x) { // byte,short,int,chardefault:System.out.println("d");case 4:System.out.println("a");case 6:System.out.println("b");break;case 2:System.out.println("c");break;}
以上代码输出:
d
a
b
switch语句练习:
练习:根据用户指定的月份,打印该月份所在的季节。
class SwitchTest {public static void main(String[] args) {int x = 4;switch(x) {case 3:case 4:case 5:System.out.println(x + "春季");break;case 6:case 7:case 8:System.out.println(x + "夏季");break;case 9:case 10:case 11:System.out.println(x + "秋季");break;case 12:case 1:case 2:System.out.println(x + "冬季");break;default:System.out.println("nono");}}}
if和switch语句很像,具体什么场景下,应用哪个语句呢?
答:如果判断的具体数值不多,而且符合byte short int char这四种类型,虽然两个语句都可以使用,但建议使用switch语句,因为效率稍高。
其他情况:对区间判断,对结果为boolean类型判断,使用if,if的使用范围更广。
while:先判断条件,只有条件满足才执行循环体。
do while:先执行循环体,再判断条件,条件满足,再继续执行循环体。
简单一句话:do while无论条件是否满足,循环体至少执行一次。
for循环格式:
for(初始化表达式;循环条件表达式;循环后的操作表达式)
{
执行语句;
}
例1,
class ForDemo {public static void main(String[] args) {for (int x = 0; x < 3; x++) {System.out.println("x = " + x);}//System.out.println("x ======= " + x);int y = 0;while (y < 3) {System.out.println("y = " + y);y++;}System.out.println("y ======= " + y);}}
从以上代码中可得:
总结:
什么时候使用循环结构?
答:当要对某些语句执行很多次时,就使用循环结构。
例2,for循环还可以写成:
int x = 1;for (System.out.println("a"); x < 3; System.out.println("c"), x++) {System.out.println("d");}
输出结果为:
a
d
c
d
c
例3,
for (int y = 0; y < 3; y++) {}
以上代码可以写成如下代码:
int y = 0;for ( ; y < 3; ) {y++;}
例4,最简单无限循环格式:
for(;;) {}while(true) {}
无限循环存在的原因是并不知道循环多少次,而是根据某些条件,来控制循环。
练习一:获取1~10的和,并打印。
解:
class ForTest2 {public static void main(String[] args) {int sum = 0;for (int x = 1; x <= 10; x++) {sum += x;}System.out.println("sum = " + sum);}}
其实这就是累加思想。原理:通过变量记录住每次变化的结果,并通过循环的形式,进行累加动作。
练习二:获取1~100之间7的倍数的个数,并打印。
解:
/*获取1~100之间7的倍数的个数,并打印思路:1.先对1~100进行循环(遍历)2.在遍历的过程中,定义条件,只对7的倍数进行操作3.因为7的倍数不确定,只要符合条件,就通过一个变量来记录这个变化的次数。步骤:1.定义循环语句,选择for语句2.在循环中定义判断,只要是7的倍数即可,使用if语句。条件:7的倍数 x % 7 == 0;3.定义变量,该变量随着7的倍数的出现而自增*/class ForTest3 {public static void main(String[] args) {int count = 0;for (int x = 1; x <= 100; x++) {if(x % 7 == 0) {count++;}}System.out.println("count = " + count);}}
其实这就是计数器思想,原理:通过一个变量记录住数据的状态变化,也需要通过循环完成。
for循环嵌套
例1,使用for循环打印如下所示的图形:
****
****
****
解:对于打印长方形,外循环控制行数,内循环控制每一行的列数,也就是一行中元素的个数。代码如下:
class ForForDemo {public static void main(String[] args) {for (int x = 0; x < 3; x++) {for (int y = 0; y < 4; y++) {System.out.print("*");}System.out.println();}}}
例2,使用for循环打印如下所示的图形:
*****
****
***
**
*
解:发现图形有很多行,每一个行有很多列,所以得使用嵌套循环。原理:形象说法,大圈套小圈。代码如下:
class ForForDemo {public static void main(String[] args) {for (int x = 0; x < 5; x++) {for (int y = x; y < 5; y++) {System.out.print("*");}System.out.println();}}}
例3,使用for循环打印如下所示的图形:
*
**
***
****
*****
解:
class ForForDemo {public static void main(String[] args) {for (int x = 0; x < 5; x++) {for (int y = 0; y <= x; y++) {System.out.print("*");}System.out.println();}}}
总结:不是规律的规律:
例4,使用for循环打印如下所示的图形:
1
12
123
1234
12345
解:
class ForForDemo {public static void main(String[] args) {for (int x = 0; x < 5; x++) {for (int y = 1; y <= x + 1; y++) {System.out.print(y);}System.out.println();}}}
例5,打印一个99乘法表。
解:
class ForForDemo {public static void main(String[] args) {for (int x = 1; x <= 9; x++) {for (int y = 1; y <= x; y++) {System.out.print(y + "*" + x + "=" + (y*x) + "\t");}System.out.println();}}}
例6,打印如下所示的图形:
□□□□*
□□□*□*
□□*□*□*
□*□*□*□*
*□*□*□*□*
注意:□表示空格。
解:
class ForForDemo {public static void main(String[] args) {for (int x = 0; x < 5; x++) {for (int y = x + 1; y < 5; y++) {System.out.print(" ");}for (int z = 0; z <= x; z++) {System.out.print("* ");}System.out.println();}}}
break(跳出),应用于选择结构和循环结构。
例,
w:for (int x = 0; x < 3; x++) {for (int y = 0; y < 4; y++) {System.out.println("x = " + x);break w;}}
可以给循环标号,然后通过break指定跳出哪个循环。
continue(继续),只能作用于循环结构,继续循环。特点:结束本次循环,继续下一次循环。
例1,
for (int x = 1; x <= 10; x++) {if(x % 2 == 1)continue;System.out.println("x = " + x);}
例2,也可以给循环标号,然后通过continue指定继续哪个循环。
w:for (int x = 0; x < 3; x++) {for (int y = 0; y < 4; y++) {System.out.println("x = " + x);continue w;}}
记住:
break和continue语句作用的范围break和continue单独存在时,下面不可以有任何语句,因为都执行不到函数的定义
函数就是定义在类中的具有特定功能的一段独立小程序。函数也称为方法。
函数的格式:
修饰符 返回值类型 函数名(参数类型 形式参数1, 参数类型 形式参数2, ... ){执行语句;return 返回值;}
试看以下代码:
int x = 4;System.out.println(x*3+5);x = 6;System.out.println(x*3+5);
发现以上的运算,因为获取不同数据的运算结果,代码出现了重复。为了提高代码的复用性,对代码进行了抽取,将这个部分定义成一个独立的功能,方便于日后使用,java中对该功能的定义是通过函数的形式来体现的。
所以,我们有了一个需求:定义一个功能,完成一个整数*3+5的运算,并打印结果。
public static int getResult(int num) {return num * 3 + 5;}
当函数运算后没有具体的返回值,这时返回值类型用一个特殊的关键字来标识,该关键字就是void。void:代表的是函数没有具体返回值的情况。当函数的返回值类型是void时,函数中的return语句可以省略不写。即:
public static void getResult(int num) {System.out.println(num * 3 + 5);return; // 可以省略}
函数的特点
void表示,那么该函数中的return语句如果在最后一行可以省略不写。注意:
函数的应用
如何定义一个函数呢?
例1,需求:定义一个功能:完成3+4的运算,并将结果返回给调用者。
解:
其实这两个功能就是在明确函数的定义:
public static int getSum() {return 3 + 4;}
以上这个函数的功能,结果是固定的,毫无扩展性而言,为了方便用户需求,由用户来指定加数和被加数,这样,功能才有意义。
例2,定义一个功能,可以实现两个整数的加法运算。
解:
public static int getSum(int x, int y) {return x + y;}
例3,需求:判断两个数是否相同?
解:
public static boolean compare(int a, int b) {if(a == b)return true;elsereturn false;}
第一次优化后:
public static boolean compare(int a, int b) {if(a == b)return true;return false;}
第二次优化后:
public static boolean compare(int a, int b) {return a == b ? true : false;}
最后一次优化:
public static boolean compare(int a, int b) {return a == b;}
例4,需求:定义功能,对两个数进行比较,获取较大的数。
解:
public static int getMax(int a, int b) {return (a > b) ? a : b;}
练习一:定义一个功能,用于打印矩形。
解:
public static void draw(int row, int col) {for (int x = 0; x < row; x++) {for (int y = 0; y < col; y++) {System.out.print("*");}System.out.println();}}
练习二:定义一个打印99乘法表功能的函数。
解:
public static void print99() {for (int x = 1; x <= 9; x++) {for (int y = 1; y <= x; y++) {System.out.print(y+"*"+x+"="+y*x+"\t");}System.out.println();}}
函数的重载(overload)
重载的概念:在同一个类中,允许存在一个以上的同名函数,只要它们的参数个数或者参数类型不同即可。
重载的特点:与返回值类型无关,只看参数列表。
重载的好处:方便于阅读,优化了程序设计。
例,定义一个加法运算,获取两个整数的和。
public static int add(int x, int y) {return x + y;}
定义一个加法运算,获取三个整数的和:
public static int add(int x, int y, int z) {return x + y + z;}
这两段代码就是函数的重载,当然函数中可调用函数,所以以上代码可以写成:
public static int add(int x, int y, int z) {return add(x, y) + z;}
例,定义一个打印99乘法表功能的函数,很简单,我们已经做了,代码如下:
public static void print99() {for (int x = 1; x <= 9; x++) {for (int y = 1; y <= x; y++) {System.out.print(y+"*"+x+"="+y*x+"\t");}System.out.println();}}
此时,如果我们还要打印一个任意数乘法表功能的函数,我们可以这样做:
public static void print99(int num) {for (int x = 1; x <= num; x++) {for (int y = 1; y <= x; y++) {System.out.print(y+"*"+x+"="+y*x+"\t");}System.out.println();}}
同理,打印99乘法表功能的函数,我们还可以写成:
public static void print99() {print99(9);}
什么时候用重载?
答:当定义的功能相同,但参与运算的未知内容不同,那么,这时就定义一个函数名称以表示其功能,方便阅读,而通过参数列表的不同来区分多个同名函数。
注意:
练习:以下哪些函数与函数void show(int a, char b, double c) {}重载?
a.void show(int x, char y, double z) {}b.int show(int a, double c, char b) {}c.void show(int a, double c, char b) {}d.boolean show(int c, char b) {}e.void show(double c) {}f.double show(int x, char y, double z) {}
解:
a.void show(int x, char y, double z) {} // 没有,因为和原函数一样b.int show(int a, double c, char b) {} // 重载,因为参数类型不同。注意:重载和返回值类型没有关系c.void show(int a, double c, char b) {} // 重载,因为参数类型不同。注意:重载和返回值类型没有关系d.boolean show(int c, char b) {} // 重载,因为参数个数不同e.void show(double c) {} // 重载,因为参数个数不同f.double show(int x, char y, double z) {} // 没有,这个函数不可以和给定函数同时存在于一个函数当中。
数组的定义:同一种类型数据的集合。其实数组就是一个容器。
数组的好处:可以自动给数组中的元素从0开始编号,方便操作这些元素。
数组的格式①:元素类型[] 数组名 = new 元素类型[元素个数或数组长度];
例,需求:想定义一个可以存储3个整数的容器。
int[] x = new int[3];
内存结构
Java程序在运行时,需要在内存中分配空间。为了提高运算效率,对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。共划分了5个不同的区域:
new建立的实例都存放在堆内存中数组内存结构
对于代码int x = 3,在内存中的情况为:
对于如下代码:
int[] x = new int[3];x[0] = 59;x = null;
有如下内存分析图:
对于以下代码:
int[] x = new int[3];int[] y = x;y[1] = 89;System.out.println(x[1]); // 89x = null;
内存结构图如下:
对于如下代码:
int[] x = new int[3];int[] y = new int[3];y[1] = 89;System.out.println(x[1]); // 89x = null;
内存结构图如下:
而对于以下代码来说:
int a = 5;int b = a;b = 8;System.out.println(a); // 5
内存结构图如下:
数组的格式②:元素类型[] 数组名 = new 元素类型[]{元素, 元素, ……};
例,
int[] arr = new int[] {3, 1, 6, 5, 4};
又可简写成:
int[] arr = {3, 1, 6, 5, 4};
数组操作常见问题
ArrayIndexOutOfBoundsException)
int[] arr = new int[3];System.out.println(arr[3]); // ArrayIndexOutOfBoundsException: 3:操作数组时,访问到了数组中不存在的角标
NullPointerException)
int[] arr = new int[3];arr = null;System.out.println(arr[1]); // NullPointerException:空指针异常,当引用没有任何指向,值为null的情况,该引用还在用于操作实体。
数组的常见操作
获取数组中的元素,通常会用到遍历,数组中有一个属性可以直接获取到数组的元素个数:length,使用方式:数组名称.length。
例1,
int[] arr = {3, 6, 5, 1, 8, 9, 67};System.out.println("length: "+arr.length);for (int x = 0; x < arr.length; x++) {System.out.println("arr["+x+"]="+arr[x]+";");}
此时,直接输出变量arr:
System.out.println(arr);
会得到诸如[I@139a55的值,[表示是一个一维数组,I表示数组中的元素类型是int,139a55是有哈希算法算出来的地址值。
例2,定义一个功能,用于打印数组中的元素,元素间用逗号隔开。
public static void printArray(int[] arr) {System.out.print("[");for (int x = 0; x < arr.length; x++) {if(x != arr.length - 1)System.out.print(arr[x] + ", ");elseSystem.out.println(arr[x]+"]");}}
例3,给定一个数组,如{5, 1, 6, 4, 2, 8, 9},获取数组中的最大值,以及最小值。
获取最大值的原理图:
用文字描述即为:
解:
步骤:
需要定义一个功能来完成,以便提高复用性。
public static int getMax(int[] arr) {int max = arr[0];for (int x = 1; x < arr.length; x++) {if(arr[x] > max)max = arr[x];}return max;}
获取最大值的另一种方式,可不可以将临时变量初始化为0呢?可以,这种方式其实是在初始化为数组中的任意一个角标。
public static int getMax_2(int[] arr) {int max = 0;for (int x = 1; x < arr.length; x++) {if(arr[x] > arr[max])max = x;}return arr[max];}
同理,获取数组中的最小值,代码如下:
public static int getMin(int[] arr) {int min = 0;for (int x = 1; x < arr.length; x++) {if(arr[x] < arr[min])min = x;}return arr[min];}
那如何获取double类型数组的最大值呢?
因为功能一致,所以定义相同函数名称,以重载形式存在。这里只略写。
public static double getMax(double[] arr) {}
例4,对给定数组进行排序,如{5, 1, 6, 4, 2, 8, 9}。
方式一:选择排序
选择排序的原理图:
内循环结束一次,最值出现在头角标位置上。
public static void selectSort(int[] arr) {for (int x = 0; x < arr.length - 1; x++) {for (int y = x + 1; y < arr.length; y++) {if(arr[x] > arr[y]) {int temp = arr[x];arr[x] = arr[y];arr[y] = temp;}}}}
方式二:冒泡排序
冒泡排序的原理图:
相邻的2个元素进行比较,如果符合条件,换位。第一圈,最值出现在最后位...
public static void bubbleSort(int[] arr) {for (int x = 0; x < arr.length - 1; x++) {for (int y = 0; y < arr.length - x - 1; y++) { // -x:让每一次比较的元素减少,-1:避免角标越界if(arr[y] > arr[y+1]) {int temp = arr[y];arr[y] = arr[y+1];arr[y+1] = temp;}}}}
发现无论什么排序,都需要对满足条件的元素进行位置置换,所以可以把这部分相同的代码提取出来,单独封装成一个函数。
public static void swap(int[] arr, int a, int b) {int temp = arr[a];arr[a] = arr[b];arr[b] = temp;}
那么,选择排序可以写为:
public static void selectSort(int[] arr) {for (int x = 0; x < arr.length - 1; x++) {for (int y = x + 1; y < arr.length; y++) {if(arr[x] > arr[y]) {swap(arr, x, y);}}}}
冒泡排序可以写为:
public static void bubbleSort(int[] arr) {for (int x = 0; x < arr.length - 1; x++) {for (int y = 0; y < arr.length - x - 1; y++) { // -x:让每一次比较的元素减少,-1:避免角标越界if(arr[y] > arr[y+1]) {swap(arr, y, y+1);}}}}
例5,数组的查找操作——折半查找。折半查找,可以提高效率,但是必须要保证该数组是有序的数组。
折半查找原理图:
折半查找的第一种形式:
public static int halfSearch(int[] arr, int key) {int min, max, mid;min = 0;max = arr.length - 1;mid = (max + min) / 2;while(arr[mid] != key) {if(key > arr[mid])min = mid + 1;else if(key < arr[mid])max = mid - 1;if(min > max)return -1;mid = (max + min) / 2;}return mid;}
折半查找的第二种形式:
public static int halfSearch_2(int[] arr, int key) {int min = 0, max = arr.length - 1, mid;while(min <= max) {mid = (max + min) >> 1;if(key > arr[mid])min = mid + 1;else if(key < arr[mid])max = mid - 1;elsereturn mid;}return -1;}
练习:有一个有序的数组,想要将一个元素插入到该数组中,还要保证该数组是有序的。
原理:如何获取该元素在数组中的位置。使用折半查找。
public static int getIndex(int[] arr, int key) {int min = 0, max = arr.length - 1, mid;while(min <= max) {mid = (max + min) >> 1;if(key > arr[mid])min = mid + 1;else if(key < arr[mid])max = mid - 1;elsereturn mid;}return min;}
例6,进制转换。
如,十进制→二进制
public static void toBin(int num) {StringBuffer sb = new StringBuffer();while(num > 0) {// System.out.println(num%2);sb.append(num%2);num /= 2;}System.out.println(sb.reverse());}
该方法有局限性,即转换的十进制数只能是正数,还有此刻我们并不熟悉StringBuffer类。
十进制→十六进制
public static void toHex(int num) {StringBuffer sb = new StringBuffer();for (int x = 0; x < 8; x++) {int temp = num & 15;if(temp > 9)// System.out.println((char)(temp - 10 + 'A'));sb.append((char)(temp - 10 + 'A'));else// System.out.println(temp);sb.append(temp);num = num >>> 4;}System.out.println(sb.reverse());}
我们使用查表法进一步优化。如对于十进制→十六进制。查表法,将所有的元素临时存储起来,建立对应关系,每一次&15后的值作为索引去查建立好的表,就可以找对应的元素,这样比-10+'A'简单的多。
这个表怎么建立呢?可以通过数组的形式来定义。
public static void toHex(int num) {char[] chs = {'0', '1', '2', '3','4', '5', '6', '7','8', '9', 'A', 'B','C', 'D', 'E', 'F'};// 定义一个临时容器char[] arr = new char[8]; // '\u0000',u指代unicode码,空格int pos = arr.length;while(num != 0) {int temp = num & 15;// System.out.println(chs[temp]);arr[--pos] = chs[temp];num = num >>> 4;}System.out.println("pos = " + pos);// 存储数据的arr数组遍历for (int x = pos; x < arr.length; x++) {System.out.print(arr[x]+",");}}
那么接下来,就该十进制→二进制了。
public static void toBin(int num) {// 定义二进制的表char[] chs = {'0', '1'};// 定义一个临时存储容器char[] arr = new char[32];// 定义一个操作数组的指针int pos = arr.length;while(num != 0) {int temp = num & 1;arr[--pos] = chs[temp];num = num >>> 1;}for (int x = pos; x < arr.length; x++) {System.out.print(arr[x]);}}
最后,做进制转换的最优优化:
// 十进制→二进制public static void toBin(int num) {trans(num, 1, 1);}// 十进制→八进制public static void toOtc(int num) {trans(num, 7, 3);}// 十进制→十六进制public static void toHax(int num) {trans(num, 15, 4);}public static void trans(int num, int base, int offset) {if(num == 0) {System.out.println(0);return;}char[] chs = {'0', '1', '2', '3','4', '5', '6', '7','8', '9', 'A', 'B','C', 'D', 'E', 'F'};char[] arr = new char[32];int pos = arr.length;while(num != 0) {int temp = num & base;arr[--pos] = chs[temp];num = num >>> offset;}for (int x = pos; x < arr.length; x++) {System.out.print(arr[x]);}}
二维数组
格式1:int[][] arr = new int[3][2];
定义了名称为arr的二维数组,二维数组中有3个一维数组,每一个一维数组中有2个元素,一维数组的名称分别为arr[0], arr[1], arr[2],给第一个一维数组1脚标位赋值为78写法是: arr[0][1] = 78;
对于如下代码:
int[][] arr = new int[2][3];arr[1][2] = 8;arr[0][3] = 90;System.out.println(arr); // [[I@139a55System.out.println(arr[0]); // [I@1db9742System.out.println(arr[0][1]); // 0
内存图如下:
格式2:int[][] arr = new int[3][];
二维数组中有3个一维数组, 每个一维数组都是默认初始化值null,可以对这三个一维数组分别进行初始化。
如:
int[][] arr = new int[3][];System.out.println(arr[0]); // nullarr[0] = new int[3];arr[1] = new int[1];arr[2] = new int[2];System.out.println(arr); // [[I@139a55System.out.println(arr.length); // 打印的是二维数组的长度:3System.out.println(arr[0].length); // 打印二维数组中第1个一维数组的长度
内存图如下:
格式3:int[][] arr = {{3,8,2},{2,7},{9,0,1,6}};
练习:从以下代码可以看出哪个选项正确与否?
int[] x, y[];
a.x[0] = y;b.y[0] = x;c.y[0][0] = x;d.x[0][0] = y;e.y[0][0] = x[0];f.x = y;
解:一维数组可以写为:
int[] x;int x[];
二维数组可以写为:
int[][] y;int y[][];int[] y[]; // 注意这种特殊写法
所以:
a.x[0] = y; // errorb.y[0] = x; // yesc.y[0][0] = x; // errord.x[0][0] = y; // errore.y[0][0] = x[0]; // yesf.x = y; // error