Java笔记(一)
1 基础语法
- 大小写敏感:Java 是大小写敏感的,这就意味着标识符
Hello
与hello
是不同的。 - 类名:对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如
MyFirstJavaClass
。 - 方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。
- 源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记 Java 是大小写敏感的),文件名的后缀为 .java。(如果文件名和类名不相同则会导致编译错误)。
- 主方法入口:所有的 Java 程序由
public static void main(String[] args)
方法开始执行。
1.1 关键字(掌握)
- 被Java语言赋予特定含义的单词
- 特点: 全部小写。
- 注意事项:
A:goto
和const
作为保留字存在。
B: 类似于Notepad++这样的高级记事本会对关键字有特殊颜色标记
1.2 标识符(掌握)
- 所有的标识符都应该以字母(
A-Z
或者a-z
),美元符($
)、或者下划线(_
)开始 - 首字符之后可以是字母(
A-Z
或者a-z
),美元符($
)、下划线(_
)或数字的任何字符组合 - 关键字不能用作标识符
- 标识符是大小写敏感的
合法标识符举例:age、$salary、_value、__1_value
非法标识符举例:123abc、-salary
- 常见的命名规则(见名知意)
A:包 全部小写
单级包:小写
举例:liuyi
,com
多级包:小写,并用.隔开
举例:cn.itcast
,com.baidu
B:类或者接口
一个单词:首字母大写
举例:Student
,Demo
多个单词:每个单词首字母大写
举例:HelloWorld,StudentName
C:方法或者变量
一个单词:首字母小写
举例:name
,main
多个单词:从第二个单词开始,每个单词首字母大写
举例:studentAge
,showAllNames()
D:常量
全部大写
一个单词:大写
举例:PI
多个单词:大写,并用_隔开
举例:STUDENT_MAX_AGE
1.3 注释(掌握)
- 就是对程序进行解释说明的文字
- 分类:
A:单行注释//
B:多行注释/* */
C:文档注释(后面讲)/** */
- 把HelloWorld案例写了一个带注释的版本。
后面我们要写一个程序的过程。
需求、分析、实现、代码体现; - 注释的作用
A:解释说明程序,提高了代码的阅读性。
B:可以帮助我们调试程序。
1.4 Java修饰符
像其他语言一样,Java可以使用修饰符来修饰类中方法和属性。主要有两类修饰符:
访问控制修饰符 : default
, public
, protected
, private
非访问控制修饰符 : final
, abstract
, static
, synchronized
1.5 常量(掌握)
- 在程序执行的过程中,其值不发生改变的量
- 分类:
A:字面值常量
B:自定义常量(后面讲) - 字面值常量
A:字符串常量 “hello
”
B:整数常量12
,23
C:小数常量 12.345
D:字符常量'a','A','0'
E:布尔常量true
,false
F:空常量null(
后面讲) - 在Java中针对整数常量提供了四种表现形式
A:二进制 由0,1
组成。以0b
(零)开头。
B:八进制 由0,1,...7
组成。以0
开头。
C:十进制 由0,1,...9
组成。整数默认是十进制。
D:十六进制 由0,1,...9,a,b,c,d,e,f
(大小写均可)组成。以0x
开头。
1.6 变量(掌握)
- 在程序的执行过程中,其值在某个范围内可以发生改变的量
- 变量的定义格式:
A:数据类型 变量名 = 初始化值;
B:数据类型 变量名;
变量名 = 初始化值;
- Java 中主要有如下几种类型的变量
局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
类变量:类变量也声明在类中,方法体之外,但必须声明为 static 类型。
1.7 数据类型(掌握)
变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。
内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。
因此,通过定义不同类型的变量,可以在内存中储存整数、小数或者字符。
Java 的两大数据类型:
内置数据类型
引用数据类型
1.7.1 内置数据类型
Java是一种强类型语言,针对每种数据都提供了对应的数据类型。
Java语言提供了八种基本类型。
六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。
- byte(1):
byte 数据类型是8位、有符号的,以二进制补码表示的整数;
最小值是 -128(-2^7);
最大值是 127(2^7-1);
默认值是 0;
byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
例子:byte a = 100,byte b = -50。
- short(2):
short 数据类型是 16 位、有符号的以二进制补码表示的整数
最小值是 -32768(-2^15);
最大值是 32767(2^15 - 1);
Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
默认值是 0;
例子:short s = 1000,short r = -20000。
- int(4):
int 数据类型是32位、有符号的以二进制补码表示的整数;
最小值是 -2,147,483,648(-2^31);
最大值是 2,147,483,647(2^31 - 1);
一般地整型变量默认为 int 类型;
默认值是 0 ;
例子:int a = 100000, int b = -200000。
- long(8):
long 数据类型是 64 位、有符号的以二进制补码表示的整数;
最小值是 -9,223,372,036,854,775,808(-2^63);
最大值是 9,223,372,036,854,775,807(2^63 -1);
这种类型主要使用在需要比较大整数的系统上;
默认值是 0L;
例子: long a = 100000L,Long b = -200000L。
"L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。
- float(4):
float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
float 在储存大型浮点数组的时候可节省内存空间;
默认值是 0.0f;
浮点数不能用来表示精确的值,如货币;
例子:float f1 = 234.5f。
- double(8):
double 数据类型是双精度、64 位、符合 IEEE 754 标准的浮点数;
浮点数的默认类型为 double 类型;
double类型同样不能表示精确的值,如货币;
默认值是 0.0d;
例子:
double d1 = 7D ;
double d2 = 7.;
double d3 = 8.0;
double d4 = 8.D;
double d5 = 12.9867;
7 是一个 int 字面量,而 7D,7. 和 8.0 是 double 字面量。
- boolean:
boolean数据类型表示一位的信息;
只有两个取值:true 和 false;
这种类型只作为一种标志来记录 true/false 情况;
默认值是 false;
例子:boolean one = true。
- char(2):
char 类型是一个单一的 16 位 Unicode 字符;
最小值是 \u0000(十进制等效值为 0);
最大值是 \uffff(即为 65535);
char 数据类型可以储存任何字符;
例子:char letter = ‘A’;。
1.7.2 引用类型
- 在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如
Employee
、Puppy
等。变量一旦声明后,类型就不能被改变了。 - 对象、数组都是引用数据类型。
- 所有引用类型的默认值都是
null
。 - 一个引用变量可以用来引用任何与之兼容的类型。
例子:Site site = new Site("Runoob")
。
1.8 数据类型转换(掌握)
-
boolean
类型不参与转换 - 默认转换
A:从小到大
B:byte,short,char – int – long – float – double
C:byte,short,char之间不相互转换,直接转成int类型参与运算。 - 强制转换
A:从大到小
B:可能会有精度的损失,一般不建议这样使用。
C:格式:
目标数据类型 变量名 = (目标数据类型) (被转换的数据); - 思考题和面试题:
A. 下面两种方式有区别吗?
float f1 = 12.345f;
float f2 = (float)12.345;
B:下面的程序有问题吗,如果有,在哪里呢?
byte b1 = 3;
byte b2 = 4;
byte b3 = b1 + b2;
byte b4 = 3 + 4;
C:下面的操作结果是什么呢?
byte b = (byte)130;
D:字符参与运算
是查找ASCII里面的值
'a' 97
'A' 65
'0' 48
System.out.println('a');
System.out.println('a' + 1);
E:字符串参与运算
这里其实是字符串的连接
System.out.println("hello"+'a'+1); System.out.println('a'+1+"hello");
System.out.println("5+5="+5+5);
System.out.println(5+5+"=5+5");
1.9 源文件声明规则
在本节的最后部分,我们将学习源文件的声明规则。当在一个源文件中定义多个类,并且还有import
语句和package
语句时,要特别注意这些规则。
- 一个源文件中只能有一个 public 类
- 一个源文件可以有多个非 public 类
- 源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为Employee.java。
- 如果一个类定义在某个包中,那么 package 语句应该在源文件的首行。
- 如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间
- 如果没有 package 语句,那么 import 语句应该在源文件中最前面。
- import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。
- 类有若干种访问级别,并且类也分不同的类型:抽象类和 final 类等。这些将在访问控制章节介绍。
除了上面提到的几种类型,Java 还有一些特殊的类,如:内部类、匿名类。
2 运算符
2.1 运算符(掌握)
- 算术运算符
A:+ - * / % ++ --
B:+
的用法
C:/
和%
的区别
D:++
和--
的用法
a:他们的作用是自增或者自减
b:使用
单独使用
放在操作数据的前面和后面效果一样。
a++或者++a效果一样。
参与操作使用
放在操作数的前面:先自增或者自减,再参与操作
int a = 10;
int b = ++a;
放在操作数的后面:先参与操作,再自增或者自减
int a = 10;
int b = a++;
- 赋值运算符
A:=
,+=
,-=
,*=
,/=
,%=
等
B:=叫做赋值运算符,也是最基本的赋值运算符
int x = 10; 把10赋值给int类型的变量x。
C:扩展的赋值运算符的特点
隐含了自动强制转换。
面试题:
short s = 1;
s = s + 1;
short s = 1;
s += 1;
请问上面的代码哪个有问题?
- 比较运算符
A:==
,!=
,>
,>=
,<
,<=
B:无论运算符两端简单还是复杂最终结果是boolean
类型。
C:千万不要把==写成了= - 逻辑运算符
A:&
,|
,^
,!
,&&
,||
B:逻辑运算符用于连接boolean类型的式子
C:结论
&:有false则false
|:有true则true
^:相同则false,不同则true。
情侣关系。
!:非true则false,非false则true
&&:结果和&是一样的,只不过有短路效果。左边是false,右边不执行。
||:结果和|是一样的,只不过有短路效果。左边是true,右边不执行。
- 位运算符(了解)
A:^的特殊用法
一个数据针对另一个数据位异或两次,该数不变
B:面试题
a:请实现两个变量的交换
**采用第三方变量
**用位异或运算符
左边a,b,a
右边a^b
b:请用最有效率的方式计算出2乘以8的结果
2<<3 - 三元运算符
A:格式
比较表达式?表达式1:表达式2;
B:执行流程:
首先计算比较表达式的值,看是true
还是false
。
如果是true
,表达式1就是结果。
如果是false
,表达式2就是结果。
C:案例:
a:比较两个数据是否相等
b:获取两个数据中的最大值
c:获取三个数据中的最大值
2.2 键盘录入(掌握)
- 实际开发中,数据是变化的,为了提高程序的灵活性,我们加入键盘录入数据。
- 如何实现呢?目前就记住
A:导包
import java.util.Scanner;
位置:在class的上边
B:创建对象
Scanner sc = new Scanner(System.in);
C:获取数据
int x = sc.nextInt();
2.3 流程控制语句
(1)顺序结构 从上往下,依次执行
(2)选择结构 按照不同的选择,执行不同的代码
(3)循环结构 做一些重复的代码
2.4.1 if语句(掌握)
- 三种格式
A:格式1
if(比较表达式) {
语句体;
}
执行流程:
判断比较表达式的值,看是true还是false
如果是true,就执行语句体
如果是false,就不执行语句体
B:格式2
if(比较表达式) {
语句体1;
}else {
语句体2;
}
执行流程:
判断比较表达式的值,看是true还是false
如果是true,就执行语句体1
如果是false,就执行语句体2
C:格式3
if(比较表达式1) {
语句体1;
}else if(比较表达式2){
语句体2;
}
...
else {
语句体n+1;
}
执行流程:
判断比较表达式1的值,看是true还是false
如果是true,就执行语句体1
如果是false,就继续判断比较表达式2的值,看是true还是false
如果是true,就执行语句体2
如果是false,就继续判断比较表达式3的值,看是true还是false
...
如果都不满足,就执行语句体n+1
- 注意事项
A:比较表达式无论简单还是复杂,结果是boolean
类型
B:if语句控制的语句体如果是一条语句,是可以省略大括号的;如果是多条,不能省略。
建议:永远不要省略。
C:一般来说,有左大括号,就没有分号,有分号,就没有左大括号。
D:else
后面如果没有if
,是不会出现比较表达式的。
E:三种if语句其实都是一个语句,只要有一个执行,其他的就不再执行。
3 分支语句
3.1 switch语句(掌握)
- 格式:
switch(表达式) {
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
...
default:
语句体n+1;
break;
}
格式解释说明:
switch:说明这是switch语句。
表达式:可以是byte,short,int,char
JDK5以后可以是枚举
JDK7以后可以是字符串
case:后面的值就是要和表达式进行比较的值
break:表示程序到这里中断,跳出switch语句
default:如果所有的情况都不匹配,就执行这里,相当于if语句中的else
- 面试题
switch语句的表达式可以是byte吗?可以是long吗?可以是String吗?
可以,不可以,JDK7以后可以 - 注意事项
A:case后面只能是常量,不能是变量,而且,多个case后面的值不能出现相同的
B:default可以省略吗?
可以省略,但是不建议,因为它的作用是对不正确的情况给出提示。
特殊情况:
case就可以把值固定。
C:break可以省略吗?
可以省略,但是结果可能不是我们想要的。
会出现一个现象:case穿透。
最终我们建议不要省略
D:default一定要在最后吗?
不是,可以在任意位置。但是建议在最后。
E:switch语句的结束条件
a:遇到break就结束了
b:执行到末尾就结束了
3.2 循环语句(掌握)
- 有三种:for,while,do…while
- for循环语句
A:格式
for(初始化语句;判断条件语句;控制条件语句){
循环体语句;
}
执行流程:
a:执行初始化语句
b:执行判断条件语句
如果这里是true,就继续
如果这里是false,循环就结束
c:执行循环体语句
d:执行控制条件语句
e:回到b
B:注意事项
a:判断条件语句无论简单还是复杂,结果是boolean类型
b:循环体语句如果是一条,可以省略大括号,但是不建议
c:有分号就没有左大括号,有左大括号就没有分号
- while循环
A:基本格式
while(判断条件语句) {
循环体语句;
}
扩展格式:
初始化语句;
while(判断条件语句){
循环体语句;
控制条件语句;
}
- do…while循环
A:基本格式
do {
循环体语句;
}while(判断条件语句);
扩展格式:
初始化语句;
do {
循环体语句;
控制条件语句;
}while(判断条件语句);
- 循环使用的注意事项(死循环)
A:一定要注意修改控制条件,否则容易出现死循环。
B:最简单的死循环格式
a:while(true){...}
b:for(;;){}
3.3 控制跳转语句(掌握)
-
break
:中断的意思
A:用在循环和switch语句中,离开此应用场景无意义。
B:作用:
a:跳出单层循环
b:跳出多层循环,需要标签语句的配合
-
continue
:继续
A:用在循环中,离开此应用场景无意义。
B:作用
a:跳出单层循环的一次,可以继续下一次 -
return
:返回
A:用于结束方法的,后面还会在继续讲解和使用。
B:一旦遇到return
,程序就不会在继续往后执行。
3.4 for-each循环
数组遍历、集合迭代遍历的语法简化
3.5 random
4 方法(就是类似于函数,掌握)
4.1 方法含义
就是完成特定功能的代码块。
注意:在很多语言里面有函数的定义,而在Java中,函数被称为方法。
4.2 格式:
修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2...) {
方法体语句;
return 返回值;
}
修饰符:目前就用 public static
。后面再详细讲解其他修饰符
返回值类型:就是功能结果的数据类型
方法名:就是起了一个名字,方便我们调用该方法。
参数类型:就是参数的数据类型
参数名:就是变量
参数分类:
实参:实际参与运算的数据
形参:方法上定义的,用于接收实际参数的变量
方法体语句:就是完成功能的代码块return
:结束方法
返回值:就是功能的结果,由return带给调用者。
4.3 两个明确
返回值类型:结果的数据类型
参数列表:参数的个数及对应的数据类型
4.4 方法调用
A:有明确返回值的方法
a:单独调用,没有意义
b:输出调用,不是很好,因为我可能需要不结果进行进一步的操作。但是讲课一般我就用了。
c:赋值调用,推荐方案
B:void类型修饰的方法
a:单独调用
4.5 案例
A:求和方案
B:获取两个数中的较大值
C:比较两个数据是否相同
D:获取三个数中的最大值
E:输出m行n列的星形
F:输出nn乘法表
4.6 方法的注意事项
A:方法不调用不执行
B:方法之间是平级关系,不能嵌套定义
C:方法定义的时候,参数是用,隔开的
D:方法在调用的时候,不用在传递数据类型
E:如果方法有明确的返回值类型,就必须有return语句返回。
4.7 方法重载
在同一个类中,方法名相同,参数列表不同。与返回值无关。
参数列表不同:
参数的个数不同。
参数的对应的数据类型不同。
4.8 方法重载案例
不同的类型的多个同名方法的比较。
5 数组(掌握)
5.1 数组
存储同一种数据类型的多个元素的容器。
5.2 特点
每一个元素都有编号,从0
开始,最大编号是长度-1
。
编号的专业叫法:索引
5.3 定义格式
A:数据类型[] 数组名;
B:数据类型 数组名[];
推荐是用A方式
但是要能看懂
5.4 数组的初始化
A:动态初始化
只给长度,系统给出默认值
举例:int[] arr = new int[3];
B:静态初始化
给出值,系统决定长度
举例:int[] arr = new int[]{1,2,3};
简化版:int[] arr = {1,2,3};
5.5 Java的内存分配
A:栈 存储局部变量
B:堆 存储所有new出来的
C:方法区(面向对象部分详细讲解)
D:本地方法区(系统相关)
E:寄存器(CPU使用)
注意:
a:局部变量: 在方法定义中或者方法声明上定义的变量。
b:栈内存和堆内存的区别
栈:数据使用完毕,就消失。
堆:每一个new
出来的东西都有地址
每一个变量都有默认值byte
,short
,int
,long
0float
,double
0.0char
‘\u0000’boolean
false
引用类型 null
数据使用完毕后,在垃圾回收器空闲的时候回收。
5.6 数组内存图
A:一个数组
B:二个数组
C:三个数组(两个栈变量指向同一个堆内存)
5.7 数组的常见操作
A:遍历
方式1:
public static void printArray(int[] arr) {
for(int x=0; x<arr.length; x++) {
System.out.println(arr[x]);
}
}
方式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.println(arr[x]+"]");
}else {
System.out.println(arr[x]+", ");
}
}
}
B:最值
最大值:
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;
}
最小值:
public static int getMin(int[] arr) {
int min = arr[0];
for(int x=1; x<arr.length; x++) {
if(arr[x] < min) {
min = arr[x];
}
}
return min;
}
C:逆序
方式1:
public static void reverse(int[] arr) {
for(int x=0; x<arr.length/2; x++) {
int temp = arr[x];
arr[x] = arr[arr.length-1-x];
arr[arr.length-1-x] = temp;
}
}
方式2:
public static void reverse(int[] arr) {
for(int start=0,end=arr.length-1; start<=end; start++,end--) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
}
}
D:查表
public static String getString(String[] strArray,int index) {
return strArray[index];
}
E:基本查找
方式1:
public static int getIndex(int[] arr,int value) {
for(int x=0; x<arr.length; x++) {
if(arr[x] == value) {
return x;
}
}
return -1;
}
方式2:
public static int getIndex(int[] arr,int value) {
int index = -1;
for(int x=0; x<arr.length; x++) {
if(arr[x] == value) {
index = x;
break;
}
}
return index;
}
5.8 二维数组(理解)
- 元素是一维数组的数组。
- 格式:
A:数据类型[][]
数组名 = new 数据类型[m][n]
;
B:数据类型[][]
数组名 = new 数据类型[m][]
;
C:数据类型[][]
数组名 = new 数据类型[][]{{...},{...},{...}}
;
D:数据类型[][]
数组名 ={{...},{...},{...}}
; - 案例(掌握):
A:二维数组的遍历
B:二维数组的求和
C:杨辉三角形
5.9 数组补充代码
package 二维数组;
/*
二维数组: 就是元素为一维数组的一个数组
格式1:
数据类型[][] 数组名 = new 数据类型[m][n];
m: 表示这个二维数组有多少个一维数组。
n:表示每一个一维数组的元素有多个
注意:
A:以下格式也可以表示二维数组(不推荐):
a:数据类型 数组名[][] = new 数据类型[m][n];
b: 数据类型[] 数组名[] = new 数据类型[m][n];
B:注意下面定义的区别:
int x,y;
int[] x;
int[] y[];
int[] x, y[];
*/
public class Array2Demo {
public static void main(String[] args) {
/*
第一堂课:
*/
//定义一个二维数组
int[][] arr = new int[3][2];
//定义了一个二维数组arr
//这个二维数组有3个一维数组的元素
//每一个一维数组有2个元素
//输出二维数组的名称
System.out.println(arr);//地址值
//输出二维数组的第一个元素一维数组的名称:
System.out.println(arr[0]);//地址值
System.out.println(arr[1]);//地址值
System.out.println(arr[2]);//地址值
//输出二维数组的元素:
System.out.println(arr[0][0]);//0
System.out.println(arr[0][1]);//0
/*
第二堂课:
*/
//定义数组
int[][] arr1 = new int[3][];
//第一个数必须给,第二个可给可不给
System.out.println(arr1);//[[I@15aeb7ab
System.out.println(arr1[0]);//null(为什么是null,因为二维数组的一维循环还没有定义)
System.out.println(arr1[1]);//null
System.out.println(arr1[2]);//null
//动态的为每一个一维数组分空间
arr1[0] = new int[2];
arr1[1] = new int[2];
arr1[2] = new int[2];
System.out.println(arr1[0]);//[I@7b23ec81
System.out.println(arr1[1]);//[I@7b23ec81
System.out.println(arr1[2]);//[I@7b23ec81
System.out.println(arr1[0][0]);//0
System.out.println(arr1[0][1]);//0
//System.out.println(arr1[0][2]);//越界错误
arr1[1][0] = 100;
arr1[1][1] = 200;
/*
第三堂课:
*/
/*
格式3:
基本格式:
数据类型[][] 数组名 = new 数据类型[][]{{元素1, 元素2...},{元素1, 元素2...},{元素1, 元素2...}};
简化版格式:
数据类型[][] 数组名 = {{元素1, 元素2...},{元素1, 元素2...},{元素1, 元素2...}};
举例:
int[][] arr = {{1, 2, 3},{1, 2, 3},{1, 2, 3}};
int[][] arr = {{1, 2, 3},{1 },{1, 2};
*/
int[][] arr2 = {{1, 2, 3}, {1}, {1, 2}};
System.out.println("课3");
System.out.println(arr2[0]);//[I@3feba861
System.out.println(arr2[1]);//[I@5b480cf9
System.out.println(arr2[2]);//[I@6f496d9f
System.out.println(arr2[0][0]);//1
System.out.println(arr2[0][1]);//2
System.out.println(arr2[0][2]);//3
System.out.println(arr2[1][0]);//1
System.out.println(arr2[2][0]);//1
System.out.println(arr2[2][1]);//2
}
}
/*
[[I@16b98e56([[: 二维数组, I: int类型)
[I@7ef20235
[I@27d6c5e0
[I@4f3f5b24
0
0
*/
5.10 java内存机制与内存地址
5.10.1 Java 中的堆和栈
Java把内存划分成两种:一种是栈内存,一种是堆内存。
在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。
当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
堆内存用来存放由new
创建的对象和数组。
在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
具体的说:
栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a = 3;
int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
String是一个特殊的包装类数据。可以用:
String str = new String("abc");
String str = "abc";
两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。
而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。
比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。
方式一:
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一个对象的。
方式二:
String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false
用new的方式是生成不同的对象。每一次生成一个。
因此用第一种方式创建多个”abc”字符串,在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String(“abc”);的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。
另一方面, 要注意: 我们在使用诸如String str = “abc”;的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。