JavaSE基础篇
知识点普及:一个程序和它的数据在被CPU执行前必须移到计算机的内存中,因为内存读取数据的速度比硬盘的存取数据快10倍。内存解决了一部分CPU运行过快,而硬盘数据存取太慢的问题,提高了我们的电脑运行速度。内存就像一条“高速公路”,数据由传输速度较慢的硬盘通过这条高速公路传至CPU处理。
内存起到了两个作用:1.保存从硬盘读取的数据,提供给CPU使用 2.保存CPU的一些临时执行结果,以便CPU下次使用或保存到硬盘(缓存的作用)
java语言概述
java基础知识图谱
本文主要介绍关于Java基础程序设计的知识。因为这些是java语言的基础!特别是面向对象是java基础中的核心内容!
java技术体系平台
Java一开始富有吸引力是因为Java程序可以在Web浏览器中运行。 这些Java程序被称为Java小程序(applet)。 applet使用现代的图形用户界面与Web用户进行交互。 applet内嵌在HTML代码中。
现在我们一般不用JavaSE开发桌面应用了,所以JavaSE一般说的就是我们的Java基础!
Java语言运行机制及运行过程
java的两种核心机制是:
- java虚拟机(Java Virtal Machine)
- 垃圾回收机制 (Garbage Collection)
java语言的特点
特点一: 面向对象
- 两个基本概念:类、对象
- 三大特性:封装、继承、多态
特点二: 健壮性
- 吸收了C/C++语言的优点,但去掉了其影响程序健壮性的部分(如指针、内存的申请与
释放等),提供了一个相对安全的内存管理和访问机制
特点三: 跨平台性
- 跨平台性:通过Java语言编写的应用程序在不同的系统平台上都可以运行。 “Write
once , Run Anywhere” - 原理:只要在需要运行 java 应用程序的操作系统上,先安装一个Java虚拟机 (JVM Java
Virtual Machine) 即可。由JVM来负责Java程序在该系统中的运行。
因为有了JVM(Java虚拟机),同一个Java 程序在三个不同的操作系统中都可以执行。这样就实现了Java 程序的跨平台性
核心机制—java虚拟机(JVM)
- JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器。
- 对于不同的平台,有不同的虚拟机。
- 只有某平台提供了对应的java虚拟机, java程序才可在此平台运行
- java虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行
核心机制—垃圾回收
什么是垃圾回收:不再使用的内存空间应回收
在C/C++等语言中,由程序员负责回收无用内存。
Java 语言消除了程序员回收无用内存空间的责任:它提供一种系统级线程跟踪存储空间的分配情况。并在JVM空闲时,检查并释放那些可被释放的存储空间。
垃圾回收在Java程序运行过程中自动进行,程序员无法精确控制和干预。
思考:Java程序还会出现内存泄漏和内存溢出问题吗?会!
java的运行过程
java程序编写-编译-运行过程:
编写:我们将编写的java代码保存在以“.java”结尾的源文件中
编译:使用javac.exe命令编译我们的java源文件。格式:javac 源文件名.java。编译以后会生成一个或者多个字节码文件,字节码文件的文件名与java源文件中的类名相同
解释运行:使用java.exe命令去解释运行我们的字节码文件。格式:java 类名
PS:在一个Java源文件中可以声明多个class。但是,只能最多有一个类声明为public,而且要求声名为public的类的类名必须与源文件相同
JDK与JRE
JDK、JRE、JVM的关系:
注释(comment)
注释:用于注解说明解释程序的文字就是注释
Java中的注释类型:
- 单行注释
- 多行注释
- 文档注释 (java特有)
单行注释
- 格式: //注释文字
多行注释
- 格式: /* 注释文字 */
注:
-
对于单行和多行注释,被注释的文字,不会被JVM(java虚拟机)解释执行。
-
多行注释里面不允许有多行注释嵌套
-
单行注释和多行注释的作用:
- 对缩写的程序进行解释说明,增强可读性(方便自己,方便别人)
- 调试所写的代码(编译出错,可以注释某一行,检查是哪个部分出错了)
单行注释和多行注释的内容不参与编译。换句话:编译以后生成的.class结尾的字节码文件不包含注释的信息
文档注释(Java特有)
- 格式: /**
@author 指定java程序的作者
@version 指定源文件的版本
*/ - 注释内容可以被JDK提供的工具 javadoc 所解析,生成一套以网页文件形式体现的该程序的说明文档。
- 操作方式:
JAVA的API文档
API (Application Programming Interface,应用程序编程接口)是 Java 提供的基本编程接口。习惯上将语言提供的类库,都叫做API
API文档:针对于提供的类库如何使用,给的一个说明书。类似于<新华字典>。Java语言提供了大量的基础类,因此 Oracle 也为这些基础类提供了相应的API文档,用于告诉开发者如何使用这些类,以及这些类里包含的方法。
在学习过程中遇到不明白的类方法可以去查看我们的API文档!
下载API:点击Additional Resources-Java SE 8 Documentation下载
Java语言核心结构
不仅是Java,所有编程语言都包含有下面要介绍到的属于自己的核心结构!
Java基本语法
关键字和保留字
关键字(keyword)
关键字的定义和特点:
- 定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词)
- 特点:关键字中所有字母都为小写!
保留字(reserved word)
Java保留字:现有Java版本尚未使用, 但以后版本可能会作为关键字使用。
自己命名标识符时要避免使用这些保留字:goto 、 const
标识符(identifier)
标识符:Java 对各种变量、 方法和类等要素命名时使用的字符序列称为标识符号
ps:凡是自己可以起名字的地方都叫标识符,比如:类名、变量名、方法名、接口名、包名...
命名标识符要遵从下面几个规则(如果不遵守,编译不通过!):
- 由26个英文字母大小写, 0-9 , _或 $ 组成
- 数字不可以开头。
- 不可以使用关键字和保留字,但能包含关键字和保留字。
- Java中严格区分大小写,长度无限制。
- 标识符不能包含空格
java中的命名规范(不遵守,编译也可以通过):
- 包名:多单词组成时所有字母都小写: xxxyyyzzz
- 类名、接口名:多单词组成时,所有单词的首字母大写: XxxYyyZzz
- 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写: xxxYyyZzz
- 常量名:所有字母都大写。多单词时每个单词用下划线连接: XXX_YYY_ZZZ
- 注:
这个规范是我们人为定义的一个规范,不遵守这个规范也可以编译运行
作出规范可以在起名字时,提高阅读性,使命名尽量有意义,“见名知意”。
java采用unicode字符集,因此标识符也可以使用汉字声明,但是不建议使用。
变量
变量的概念:
- 内存中的一个存储区域
- 该区域的数据可以在同一类型范围内不断变化
- 变量是程序中最基本的存储单元。包含变量类型、变量名和存储的值
变量的作用:用于在内存中保存数据
使用变量时要注意:
- Java中每个变量必须先声明且赋值最后才能使用
- 使用变量名来访问这块区域的数据
- 变量的作用域:其定义所在的一对{ }内
- 变量只有在其作用域内才有效,换句话说,出了作用域就失效了
- 同一个作用域内,不能定义重名的变量
变量的使用
java定义变量的格式:数据类型 变量名 = 变量值;
声明变量:
- 语法: <数据类型> <变量名称>
- 例如: int var
变量的赋值:
- 语法: <变量名称> = <值>
- 例如: var = 10;
声明和赋值变量:
- 语法: 语法: <数据类型> <变量名> = <初始化值>
- 例如: int var = 10
/*
变量的使用
1. java定义变量的格式:数据类型 变量名 = 变量值;
2. 说明:
① 变量必须先声明,后使用
② 变量都定义在其作用域内。在作用域内,它是有效的。换句话说,出了作用域,就失效了
③ 同一个作用域内,不可以声明两个同名的变量
*/
class VariableTest {
public static void main(String[] args) {
//变量的定义
int myAge = 12;
//变量的使用
System.out.println(myAge);
//编译错误:使用myNumber之前并未定义过myNumber
//System.out.println(myNumber);
//变量的声明
int myNumber;
//编译错误:使用myNumber之前并未赋值过myNumber
//System.out.println(myNumber);
//变量的赋值
myNumber = 1001;
//编译不通过
//System.out.println(myClass);
//不可以在同一个作用域内定义同名的变量
//int myAge = 22;
}
public void method(){
//跟上面作用域不同,可以定义与上面作用域同名变量
int myAge
int myClass = 1;
}
}
变量的分类
按数据类型分类
java对于每一种数据都定义了明确的具体数据类型(强类型语言),在内存中分配了不同大小的内存空间。
按声明的位置不同分类
在方法体外,类体内声明的变量称为成员变量。
在方法体内部声明的变量称为局部变量。
我们说的java变量类型大多数指的的是数据类型,这也是java核心结构中的重中之中,成员变量和局部变量的知识点在另外一篇关于javase学习心得之面向对象篇中再来介绍!下面我们来学习一下java的数据类型!
基本数据类型
上面的可以看到
java中有八种基本数据类型:
- 数值型
- 整型:byte、short、int、long
- 浮点类:float、double
- 字符型:char
- 布尔型:boolean
三种引用数据类型:
- 类:class
ps:字符串类型属于class - 接口:interface
- 数组:([])
下面就让我们深入的看一下java的八种基本数据类型:
整数类型
java中的整型有:byte、short、int、long
Java各整数类型有固定的表数范围和字段长度,不受具体OS的影响,以保证java程序的可移植性。
Java的整型常量默认为 int 型,声明long型常量须后加‘l’或‘L’
java程序中变量通常声明为int型,除非不足以表示较大的数,才使用long
代码示例如下:
class VariableTest1 {
public static void main(String[] args) {
//1. 整型:byte(1字节=8bit) \ short(2字节) \ int(4字节) \ long(8字节)
//① byte范围:-128 ~ 127
byte b1 = 12;
byte b2 = -128;
//b2 = 128;//超过范围编译不通过
System.out.println(b1);
System.out.println(b2);
// ② 声明long型变量,必须以"l"或"L"结尾
// ③ 通常,定义整型变量时,使用int型。
short s1 = 128;
int i1 = 1234;
long l1 = 3414234324L;
System.out.println(l1);
}
}
通常,定义整型变量时,使用int型。
默认情况下,整型常量默认类型是Int型
浮点类型
java中的浮点型有:float、double,所谓浮点型就是带小数点的数字
与整数类型类似, Java 浮点类型也有固定的表数范围和字段长度,不受具体操作系统的影响。
浮点型常量有两种表示形式:
- 十进制数形式:如: 5.12 512.0f .512 (必须有小数点)
- 科学计数法形式:如: 5.12e2 512E2 100E-2
- float:单精度,尾数可以精确到7位有效数字。很多情况下,精度很难满足需求。
- double:双精度,精度是float的两倍。通常采用此类型。
- Java 的浮点型常量默认为double型, 声明float型常量,须后加‘f’或‘F’。
PS: 可以看到float表示数值的范围比long还大,这是因为内部存储的时候并非直接存储的数值,浮点型在内存中存储数值的实际方式是:有一部分存储的是数值,一部分存储的是次幂
代码示例如下:
class VariableTest1 {
public static void main(String[] args) {
//2. 浮点型:float(4字节) \ double(8字节)
//① 浮点型,表示带小数点的数值
//② float表示数值的范围比long还大
double d1 = 8.88
System.out.println(d1 + 1);
//③ 定义float类型变量时,变量要以"f"或"F"结尾
float f1 = 12.3F;
System.out.println(f1);
//④ 通常,定义浮点型变量时,使用double型。
}
}
通常,定义浮点型变量时,使用double型。
默认情况下浮点型常量的默认类型就是double型
字符型
char 型数据用来表示通常意义上“字符”(2字节)
Java中的所有字符都使用Unicode编码,故一个字符可以存储一个字母,一个汉字,或其他书面语的一个字符。
字符型变量的三种表现形式:
- 字符常量是用单引号(‘ ’)括起来的单个字符。 例如: char c1 = 'a'; char c2= '中'; char c3 = '9';
- Java中还允许使用转义字符‘\’来将其后的字符转变为特殊字符型常量。
例如: char c3 = ‘\n’; // '\n'表示换行符 - 可以直接使用 Unicode 值来表示字符型常量:‘\uXXXX’。其中, XXXX代表一个十六进制整数。如: \u000a 表示 \n。
PS:char类型是可以进行运算的。因为它都对应有Unicode码。
class VariableTest1 {
public static void main(String[] args) {
//3. 字符型:char (1字符=2字节)
//① 定义char型变量,通常使用一对'',内部只能写一个字符
char c1 = 'a';
//c1 = 'AB';//编译不通过,内部只能写一个字符
System.out.println(c1);
char c2 = '1';
char c3 = '中';
char c4 = 'ス';
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
//② 表示方式:1.声明一个字符 2.转义字符 3.直接使用 Unicode 值来表示字符型常量
char c5 = '\n';//换行符
c5 = '\t';//制表符 相等于tab键的效果
System.out.print("hello" + c5);
System.out.println("world");
//直接使用 Unicode 值来表示字符型常量
char c6 = '\u0043';
System.out.println(c6);
}
}
拓展:
说到字符,总是会离不开字符编码,这里了解一下在实际运用中常用到的字符编码:ASCII码、Unicode、UTF-8:
- ASCII码:
- Unicode:
- UTF-8:
布尔类型
boolean 类型用来判断逻辑条件,一般用于程序流程控制:
- if条件控制语句;
- while循环控制语句;
- do-while循环控制语句;
- for循环控制语句;
boolean类型数据只允许取值true和false,无null。
注意:
-
不可以使用0或非 0 的整数替代false和true,这点和C语言不同。
-
Java虚拟机中没有任何供boolean值专用的字节码指令, Java语言表达所操作的boolean值,在编译之后都使用java虚拟机中的int数据类型来代替: true用1表示, false用0表示。 ———《java虚拟机规范 8版》
class VariableTest1 {
public static void main(String[] args) {
//4.布尔型:boolean
//① 只能取两个值之一:true 、 false
//② 常常在条件判断、循环结构中使用
boolean bb1 = true;
System.out.println(bb1);
boolean isMarried = true;
if(isMarried){
System.out.println("你就不能参加\"单身\"party了!\\n很遗憾");
}else{
System.out.println("你可以多谈谈女朋友!");
}
}
}
基本数据类型变量间转换
前提:这里讨论只是7种基本数据类型变量间的运算。不包含boolean类型的。
-
自动类型提升:当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型
ps:容量大小指的是,表示数的范围的大和小而非占内存空间的大小。比如:float容量要大于long的容量有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算。所以接收计算后的数的变量类型一定要至少是参与计算的数据类型中容量最大的那个类型!
注:- char类型是可以进行运算的。因为它都对应有Unicode码。
- byte,short,char之间不会相互转换,他们三者在计算时首先转换为int类型(即使是byte+byte,short+short也只能拿int的容量去接收)。
- boolean类型不能与其它数据类型运算。
- 当把任何基本数据类型的值和字符串(String)进行连接运算时(+), 基本数据类型的值将自动转化为字符串(String)类型。
容量小的类型自动转换为容量大的数据类型。数据类型按容量大小排序为:
class VariableTest2 { public static void main(String[] args) { byte b1 = 2; int i1 = 129; //编译不通过 //byte b2 = b1 + i1; int i2 = b1 + i1; long l1 = b1 + i1; System.out.println(i2); float f = b1 + i1; System.out.println(f); short s1 = 123; double d1 = s1; System.out.println(d1);//123.0 //*********特别的:当byte、char、short三种类型的变量做运算时,结果为int型******** char c1 = 'a';//97 int i3 = 10; int i4 = c1 + i3; System.out.println(i4); short s2 = 10; //char c2 = c1 + s2;//编译不通过 byte b2 = 10; //char c3 = c1 + b2;//char+byte->char接收,编译不通过 //short s3 = b2 + s2;//byte+short->short接收,编译不通过 //short s4 = b1 + b2;//byte+byte->short接收,编译不通过 //**************************************** } }
-
强制类型转换:自动类型提升运算的逆运算。
如果有一个容量大的变量我们就像给它赋值到容量小的变量上,比如一个double类型的变量我们想把它赋值Int类型的变量上。或者就想要某一类型的变量,如char转换为整型就可以使用强制类型转换
需要使用强转符:()
注意点:强制类型转换,可能导致精度损失:class VariableTest3 { public static void main(String[] args) { double d1 = 12.9; //精度损失举例1,不要小数,就损失了精度 int i1 = (int)d1;//截断操作:只取整,小数部分不要了。 System.out.println(i1); //没有精度损失 long l1 = 123; short s2 = (short)l1; //精度损失举例2 int i2 = 128; byte b = (byte)i2; System.out.println(b);//-128 //为什么是-128?因为计算机底层存储的是二进制补码且最高位表示符号位,int型占4个字节,变成byte占1个字节,就变成了1 000 0000,它把最高位当作了符号位,所以就变成了-128 } }
-
拓展:
//编译中可能会遇到的问题 class VariableTest4 { public static void main(String[] args) { //1.编码情况1: long l = 123213; //没加L也编译成功,因为会当作int型 System.out.println(l); //编译失败:过大的整数,int容量不够 //long l1 = 21332423235234123; long l1 = 21332423235234123L; //**************** //float f1 = 12.3;//编译失败,默认是double类型,float容量不够所以失败 float f1 = (float)12.3; //2.编码情况2: //整型常量,默认类型为int型 //浮点型常量,默认类型为double型 byte b = 12; //byte b1 = b + 1;//编译失败:byte+int->byte接收,失败 //float f1 = b + 12.3;//编译失败:byte+double->float接收,失败 } }
String类型
String不是基本数据类型,属于引用数据类型,翻译为:字符串,在这里这是简单提一下,因为在后面的内容经常需要用到。更多的学习心得会写在java进阶篇中写到。
字符串常量是用一对双引号(”“)括起来表示。它可以是空的,而char不可以
使用方式与基本数据类型一致。例如: String str = “abcd”;
一个字符串可以串接另一个字符串
也可以直接串接其他类型的数据即String可以和8种基本数据类型变量做运算,且只能是连接运算(+)基本数据类型的值将自动转化为字符串(String)类型。
例如:
String str = "str";
str = str + “xyz” ; int n = 100; str = str + n//"str100";
记住:只要遇到String和其他数据类型的+运算都是连接运算!
/*
String类型变量的使用
1. String属于引用数据类型,翻译为:字符串
2. 声明String类型变量时,使用一对""
3. String可以和8种基本数据类型变量做运算,且运算只能是连接运算:+
4. 运算的结果仍然是String类型
*/
class StringTest {
public static void main(String[] args) {
String s1 = "Hello World!";
System.out.println(s1);
String s2 = "a";
String s3 = "";
//char c = '';//编译不通过
//***********************
int number = 1001;
String numberStr = "学号:";
String info = numberStr + number;// +:连接运算
boolean b1 = true;
String info1 = info + b1;// +:连接运算
System.out.println(info1);
//***********************
//练习1
char c = 'a';//97 A:65
int num = 10;
String str = "hello";
System.out.println(c + num + str);//107hello
System.out.println(c + str + num);//ahello10
System.out.println(c + (num + str));//a10hello
System.out.println((c + num) + str);//107hello
System.out.println(str + num + c);//hello10a
//练习2
//* *
System.out.println("* *");
System.out.println('*' + '\t' + '*'); //加法
System.out.println('*' + "\t" + '*'); //连接
System.out.println('*' + '\t' + "*");//前面加法后面连接
System.out.println('*' + ('\t' + "*"));//先连接变成String再连接
//***********************
//String str1 = 123;//没有双引号,编译不通过
String str1 = 123 + "";
System.out.println(str1);//"123"
//int num1 = str1;
//int num1 = (int)str1;//编译不通过,string不能使用()强转化
int num1 = Integer.parseInt(str1);//String转化为Int型的方法
System.out.println(num1);//123
}
}
运算符
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等
算术运算符
/*
运算符之一:算术运算符
+ - + - * / % (前)++ (后)++ (前)-- (后)-- +
*/
class AriTest{
public static viod main(String[] args){
//除号:/
int num1 = 12;
int num2 = 5;
int result = num1 / num2; //int和int运算结果还是int
System.out.println(result);//2,因为是整型所以没有小数
int result2 = num1 / num2 *num2;
System.out.println(result2);//10,从前往后算
double result3 = num1 / num2//先整型运算得2然后赋值给double所以是2.0
System.out.println(result3);//2.0
double result4 = num1 / num2 + 0.0;//2.0
double reuslt5 = num1 / (num2 +0.0);//2.4
double result6 = (double)num1 / num2;//2.4
double result7 = (double)(num1 / num2);//2.0
System.out.println(resut5);
System.out.println(resut6);
// %:取余运算
//结构的符号与被模数的符号相同,即下例中的与m1符号相同
//开发中,经常使用%来判断能否被除尽的情况
int m1 = 12;
int n1 = 5;
System.out.println("m1 % n1 =" + m1 % n1);//m1 % n1 =2
int m2 = -12;
int n2 = 5;
System.out.println("m2 % n2 = " + m2 % n2);//m1 % n1 =-2
int m3 = 12;
int n3 = -5;
System.out.println("m3 % n3 = " + m3 % n3);//m1 % n1 =2
int m4 = -12;
int n4 = -5;
System.out.println("m4 % n4 = " + m4 % n4);//m1 % n1 =-2
//(前)++:先自增1,然后再运算
//(后)++:先运算,后自增1
int a1 = 10;
int b1 = ++a1;//前++,先加1后运算,=是赋值运算,所以b1=11
System.out.println("a1 = " + a1 + ",b1 = " + b1);
int b2 = 10;
int b2 = a2++;//后++,先运算,=是赋值运算,所以b1=10,然后再+1
System.out.println("a2 = " + a2 + ",b2 = " + b2);
//注意点:
short s1 = 10;
//s1 = s1 + 1;编译失败,因为常量1默认是int型,short型接收不了
//s1 = (short)(s1 + 1);//正确
s1++;//自增1不会改变本身变量的数据类型
//问题:
byte bb1 = 127;
bb1++;//超出容量了,会怎么样?就变成10000000 --> -128了
System.out.print('bb1 = ' + bb1);//-128
//(前)-- :先自减1,后运算
//(后)-- :先运算,后自减1
int a4 = 10;
int b4 = a4--;//int b4 = --a4;
System.out.println("a4 = " + a4 + ",b4 = " + b4);
}
}
PS:
- 如果对负数取模,可以把模数负号忽略不记,如: 5%-2=1。 但被模数是负数则不可忽略。此外,取模运算的结果不一定总是整数。
- 对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。 例如: int x=3510;x=x/1000*1000; x的结果是?3000
- “+”除字符串相加功能外,还能把非字符串转换成字符串.例如:
System.out.println(“5+5=”+5+5); //打印结果是? 5+5=55
赋值运算符
符号: =
当“=”两侧数据类型不一致时, 可以使用自动类型转换或使用强制类型转换原则进行处理。
赋值运算符支持连续赋值。
扩展赋值运算符: +=, -=, *=, /=, %=
PS:类型与类型之间赋值操作要满足两个条件才可以编译成功:
- 相同类型
- 自动类型转换
/*
运算符之二:赋值运算符
= += -= *= /= %=
*/
class SetValueTest{
public static void main(String[] args){
//赋值符号:=
//给变量赋值的三种写法:
int i1 = 10;
int j1 = 10;
int i2,j2;
//连续赋值
i2 = j2 = 10;
int i3 = 10,j3 = 20;
//***********************
int num 10;
num1 += 2;//结果等同num1 = num1 + 2;但是并没有类型转换
System.out.println(num1);//12
int num2 = 12;
num2 %= 5;//结果等同num2 = num2 % 5;但是并没有类型转换
System.out.println(num2);
short s1 = 10;
//s1 = s1 + 2;//编译失败,有类型转换
s1 += 2;//结论:不会改本变量本身的数据类型
System.out.println(s1);//编译通过
//开发中,如果希望变量实现+2的操作,有几种方法?
/*
方式一:num = num + 2;
方式二:num += 2;(推荐):不会改变变量本身的数据类型
*/
}
}
比较运算符
PS:
- 比较运算符的结果都是boolean型,也就是要么是true,要么是false。
- 比较运算符“==”不能误写成“=
/*
运算符之三:比较运算符
== != > < >= <= instanceof
结论:
1.比较运算符的结果是boolean类型
2.区分 == 和 =
*/
class CompareText{
public static void main(String[] args){
int i = 10;
int j = 20;
System.out.println(i == j);//fasle 比较
System.out.println(i = j);//20 赋值操作
boolean b1 = true;
boolean b2 = false;
System.out.println(b2 == b1);//false
System.out.println(b2 = b1);//true:将b1赋值给b2然后输出b2
}
}
逻辑运算符
逻辑运算符只适用于布尔类型的变量!
思考:逻辑与和短路与、逻辑或与短路或的运算结果是一样的,那么它们有什么不同呢?
/*
运算符之四:逻辑运算符
& && | || ! ^
说明:
1.逻辑运算符操作的都是boolean类型的变量!
*/
class LogicTest{
pubilc static void main(String[] args){
//区分& 与 &&
//相同点1:& 与 && 的运算结果相同
//相同点2:当符号左边是true时,二者都会执行符号右边的运算
//不同点:当符号左边是false时,&继续执行符号右边的运算,&&不再执行符号右边的运算
//开发中:推荐使用短路与&&
boolean b1 = false;
int num1 = 10;
if(b1 & (num1++ > 0)){
System.out.println("我在广州");
}else{
System.out.println("我在深圳");
}
System.out.println("num1 = " + num1);//11
boolean b2 = false;
int num2 = 10;
//短路与,前面b1已经是false,所以符号后面的运算不执行
if(b1 && (num1++ > 0)){
System.out.println("我在广州");
}else{
System.out.println("我在深圳");
}
System.out.println("num2 =" + num2);//10
// 区分:| 与 ||
//相同点1:| 与 || 的运算结果相同
//相同点2:当符号左边是false时,二者都会执行符号右边的运算
//不同点3:当符号左边是true时,|继续执行符号右边的运算,而||不再执行符号右边的运算
//开发中,推荐使用||
boolean b3 = false;
b3 = true;
int num3 = 10;
if(b3 | (num3++ > 0)){
System.out.println("我现在在北京");
}else{
System.out.println("我现在在南京");
}
System.out.println("num3 = " + num3);
boolean b4 = false;
b4 = true;
int num4 = 10;
if(b4 || (num4++ > 0)){
System.out.println("我现在在北京");
}else{
System.out.println("我现在在南京");
}
System.out.println("num4 = " + num4);
}
}
位运算符
&与|符号是逻辑运算符还是位运算符:取决于符号两边的数据类型是boolean还是整数类型!,如果是整数类型就是位运算符
位运算的执行效果如下:
举个栗子:
/*
运算符之五:位运算符 (了解)
结论:
1. 位运算符操作的都是整型的数据
2. << :在一定范围内,每向左移1位,相当于 * 2
>> :在一定范围内,每向右移1位,相当于 / 2
思考:最高效方式的计算2 * 8 ? 2 << 3 或 8 << 1
*/
class BitTest {
public static void main(String[] args) {
int i = 21;
i = -21;
System.out.println("i << 2 :" + (i << 2));
System.out.println("i << 3 :" + (i << 3));
System.out.println("i << 27 :" + (i << 27));
int m = 12;
int n = 5;
System.out.println("m & n :" + (m & n));
System.out.println("m | n :" + (m | n));
System.out.println("m ^ n :" + (m ^ n));
//练习:交换两个变量的值
int num1 = 10;
int num2 = 20;
System.out.println("num1 = " + num1 + ",num2 = " + num2);
//方式一:定义临时变量的方式
//推荐的方式
int temp = num1;
num1 = num2;
num2 = temp;
//方式二:好处:不用定义临时变量
//弊端:① 相加操作可能超出存储范围 ② 有局限性:只能适用于数值类型
//num1 = num1 + num2;
//num2 = num1 - num2;
//num1 = num1 - num2;
//方式三:使用位运算符
//m = k ^ n = (m ^ n) ^ n
//有局限性:只能适用于数值类型
//num1 = num1 ^ num2;
//num2 = num1 ^ num2;
//num1 = num1 ^ num2;
System.out.println("num1 = " + num1 + ",num2 = " + num2);
}
}
三元运算符
/*
运算符之六:三元运算符
1.结构:(条件表达式)? 表达式1 : 表达式2
2. 说明
① 条件表达式的结果为boolean类型
② 根据条件表达式真或假,决定执行表达式1,还是表达式2.
如果表达式为true,则执行表达式1。
如果表达式为false,则执行表达式2。
③ 表达式1 和表达式2要求是一致的。
④ 三元运算符可以嵌套使用
3.
凡是可以使用三元运算符的地方,都可以改写为if-else
反之,不成立。
4. 如果程序既可以使用三元运算符,又可以使用if-else结构,那么优先选择三元运算符。原因:简洁、执行效率高。
*/
class SanYuanTest {
public static void main(String[] args) {
//获取两个整数的较大值
int m = 12;
int n = 5;
int max = (m > n)? m : n;
System.out.println(max);
double num = (m > n)? 2 : 1.0;
//(m > n)? 2 : "n大";//编译错误
//**************************
n = 12;
String maxStr = (m > n)? "m大" : ((m == n)? "m和n相等" : "n大");
System.out.println(maxStr);
//*****************************
//获取三个数的最大值
int n1 = 12;
int n2 = 30;
int n3 = -43;
int max1 = (n1 > n2)? n1 : n2;
int max2 = (max1 > n3)? max1 : n3;
System.out.println("三个数中的最大值为:" + max2);
//不建议
//int max3 = (((n1 > n2)? n1 : n2) > n3)? ((n1 > n2)? n1 : n2) : n3;
System.out.println("三个数中的最大值为:" + max3);
//该写成if-else:
if(m > n){
System.out.println(m);
}else{
System.out.println(n);
}
}
}
运算符的优先级
实际开发中没必要死记硬背,只要在想提前运算的地方使用()就完事
流程控制
流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。
Java流程控制方式采用结构化程序设计中规定的三种基本流程结构,即:
- 顺序结构
- 分支结构
- 循环结构
顺序结构
分支结构
分支语句1:if-else结构
if-else结构又称条件判断结构
if语句三种格式:
注意:
- 条件表达式必须是布尔表达式(关系表达式或逻辑表达式)、布尔变量
- 语句块只有一条执行语句时,一对{}可以省略,但建议保留
- if-else语句结构,根据需要可以嵌套使用
- 当if-else结构是“多选一”时,最后的else是可选的,根据需要可以省略
- 当多个条件是“互斥”关系时,条件判断语句及执行语句间顺序无所谓
- 当多个条件是“包含”关系时,“小上大下 / 子上父下”
/*
分支结构中的if-else(t条件判断结构)
一、三种结构
第一种:
if(条件表达式){
执行表达式
}
第二种:二选一
if(条件表达式){
执行表达式1
}else{
执行表达式2
}
第三种:n选一
if(条件表达式){
执行表达式1
}else if(条件表达式){
执行表达式2
}else if(条件表达式){
执行表达式3
}
...
else{
执行表达式n
}
注意:
1.else结构是可选的(可写可不写)
2.针对于条件表达式:
>如果多个条件表达式之间是"互斥"关系(或没有交集的关系):哪个判断和执行语句声明在上面还是下面都无所谓。
>如果多个条件表达式之间有交集的关系,需要根据实际情况,考虑清楚应该将哪个结构声明在上面
>如果多个条件表达式有包含的关系,通常情况下,需要将范围小的声明在范围大的上面。否则范围小的就没有机会执行了!
3.if-else结构是可以相互嵌套的
4.如果if-else结构中的执行语句只有一行时,对应的一对{}是可以省略的,但是不建议省略
*/
class IfTest{
public static void main(String[] args){
//栗子1:体检测心率:执行or不执行
int heartBeats = 78;
if(heartBeats) < 60 || heartBeats > 100){
System.out.println("需要做进一步检查");
}
System.out.println("检查结束");
//栗子2:二选一
int age = 23;
if(age < 18){
System.out.println("回去看动画片吧");
}else{
System.out.println("你可以看xx电影了")
}
//栗子3:多选一
if(age < 0){
System.out.println("您输入的数据非法");
}else if(age < 18){
System.out.println("青少年时期");
}else if(age < 35){
System.out.println("青壮年时期");
}else if(age < 60){
System.out.println("中年时期");
}else if(age < 120){
System.out.println("老年时期");
}else{
System.out.println("恭喜你,得道成仙~");
}
//拓展:如何获取一个随机数:10 ~ 99
//PS:random返回的数据类型是一个double型
int value = (int)(Math.random() * 90 + 10);
//[0.0,1.0) --> [0.0,90.0)-->[10.0,100.0) --> [10,99]
//公式:[a,b] : (int)(Math.random() * (b - a + 1) + a)
}
}
拓展:
在使用条件判断语句时候我们经常会根据用户的输入来执行不同的分支代码,这就涉及到如何从控制台输入并获取其输入的数据的问题!
开发中我们会需要使用Scanner类去从控制台/键盘获取不同类型的变量:
/*
如何从键盘获取不同类型的变量:需要使用Scanner类
具体实现步骤:
1.导包:import java.util.Scanner;
2.Scanner的实例化:Scanner scan = new Scanner(System.in);
3.调用Scanner类的相关方法(next() / nextXxx()),来获取指定类型的变量
注意:
需要根据相应的方法,来输入指定类型的值。如果输入的数据类型与要求的类型不匹配时,会报异常:InputMisMatchException
导致程序终止。
*/
//1.导包:import java.util.Scanner;
import java.util.Scanner;
class ScannerTest{
public static void main(String[] args){
//2.Scanner的实例化
Scanner scan = new Scanner(System.in);
//3.调用Scanner类的相关方法
System.out.println("请输入你的姓名:");
String name = scan.next();
System.out.println(name);
System.out.println("请输入你的芳龄:");
int age = scan.nextInt();
System.out.println(age);
System.out.println("请输入你的体重:");
double weight = scan.nextDouble();
System.out.println(weight);
System.out.println("你是否相中我了呢?(true/false)");
boolean isLove = scan.nextBoolean();
System.out.println(isLove);
//对于char型的获取,Scanner没有提供相关的方法。只能获取一个字符串
System.out.println("请输入你的性别:(男/女)");
String gender = scan.next();//"男"
//如果头铁非得想获取char类型,可以使用Srting类的索引去取
char genderChar = gender.charAt(0);//获取索引为0位置上的字符
System.out.println(genderChar);
}
}
分支语句2:switch-case结构
/*
分支结构之二:switch-case
1.格式:
switch(表达式){
case 常量1:
执行语句1;
//break;
//break关键字如果不加会继续往下执行,而且不会去判断case的条件直接执行下面所有case中的语句
case 常量2:
执行语句2;
//break;
...
default: //类似if-else中的else,当上面的case没有满足条件的分支,则执行default
执行语句n;
//break;
}
2.说明:
① 根据switch表达式中的值,依此匹配各个case中的常量。一旦匹配成功,则进入相应的case结构中,调用其执行语句。当调用完执行语句以后,则仍然继续向下执行其他case结构中的执行语句,直到遇到break关键字或此switch-case结构末尾结束为止(即想实现多选一的情况要在每个case结构最后加break关键字)
② break,可以使用在swich-case结构中,表示一旦执行到此关键字,就跳出switch-case结构
③ switch结构中的表达式,只能是如下的6种数据类型之一:
byte、short、char、int、枚举类型(jdk5.0新增)、String类型(jdk7.0新增)
④ case 之后只能声明常量,不能声明范围
⑤ break关键字是可选的。
⑥ default:相当于if-else结构中的else.
default结构是可选的,而且位置是灵活的。
⑦ 如果swich-case结构中的多个case的执行语句相同,则可以考虑进行合并
⑧ 凡是可以使用switch-case的结构,都可以换成if-else。反之,不成立
⑨ 当我们写分支结构时,当发现既可以使用switch-case(同时,switch中表达式的取值情况不太多,如果太多就要写很多的case,很崩溃呀~),又可以使用if-else时,我们优先使用switch-case。原因:switch-case执行效率稍高
*/
class SwitchCaseTest{
public static void main(String[] args){
int nunber = 2;
switch(number){
case 0:
System.out.println("zero");
case 1:
System.out.println("one");
case 2:
System.out.println("two");
case 3:
System.out.println("three");
default:
System.out.println("other");
}
//**************如下的两种情况都编译不通过*********************
//情况一
/*
boolean isHandsome = true;
switch(isHandsome){
case true:
System.out.println("我好帅啊~~~");
break;
case false:
System.out.println("我好丑啊~~~");
break;
default:
System.out.println("输入有误~~~");
}
*/
//情况二
/*
int age = 10;
switch(age){
case age > 18:
System.out.println("成年了");
break;
default:
System.out.println("未成年");
}
*/
//如果swich-case结构中的多个case的执行语句相同,则可以考虑进行合并
int score = 78;
switch(score / 10){
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
System.out.println("不及格");
break;
case 6:
case 7:
case 8:
case 9:
case 10:
System.out.println("及格");
break;
}
//break关键字在switch-case中是可选的例子
//从键盘上输入2019年的“month”和“day”,要求通过程序输出输入的日期为2019年的第几天。
import java.util.Scanner;
Scanner scan = new Scanner(System.in);
System.out.println("请输入year:");
int year = scan.nextInt();
System.out.pirntln("请输入month:");
int month = scan.nextInt();
System.out.pirntln("请输入day:");
int day = scan.nextInt();
switch(month){
case 12:
sumDays += 30;
case 11:
sumDays += 31;
case 10:
sumDays += 30;
case 9:
sumDays += 31;
case 8:
sumDays += 31;
case 7:
sumDays += 30;
case 6:
sumDays += 31;
case 5:
sumDays += 30;
case 4:
sumDays += 31;
case 3:
//判断year是否是闰年
if((year % 4 == 0 & year % 100 != 0) || year % 400 ==0){
sumDays += 29;
}else{
sumDays += 28;
}
case 2:
sumDays += 31;
case 1:
sumDays += day;
}
System.out.println("2019年" + month + "月" + day + "日是当年的第" + sumDays + "天");
}
}
循环结构
循环结构:在某些条件满足的情况下,反复执行特定代码的功能
循环语句分类
- for 循环
- while 循环
- do-while 循环
for循环
/*
For循环结构的使用
一、循环结构的4个要素
① 初始化条件
② 循环条件 --->是boolean类型
③ 循环体
④ 迭代条件
注意:通常情况下,循环结束是因为②中循环条件返回fasle了(也可能在循环体中break)
二、for循环的结构
for(①;②;④){
③
}
执行过程: ① -> ② -> ③ -> ④ -> ② -> ③ -> ④ -> ... ->不满足②退出循环
*/
class ForTest{
public static void main(String[] args){
for(int i = 1;i <= 5;i++){//i:1,2,3,4,5
System.out.println("Hello World");
}
//i:在for循环内有效,除了for循环就失效了。
//System.out.println(i);//编译错误
//练习1:
int num = 1;
for(System.out.print('a');num <= 3;System.out.print('c'),num++){
System.out.print('b');
}
//输出结果:abcbcbc
//练习2:输入两个正整数m和n,求其最大公约数和最小公倍数。
Scanner scan = new Scanner(System.in);
System.out.println("请输入第一个正整数:");
int m = scan.nextInt();
System.out.println("请输入第二个正整数:");
int n = scan.nextInt();
//获取最大公约数
//1.获取两个数中的较小值
int min = (m <= n)? m : n;
//2.遍历
for(int i = min;i >= 1 ;i--){
if(m % i == 0 && n % i == 0){
System.out.println("最大公约数为:" + i);
break;//一旦在循环中执行到break,就跳出循环
}
}
//获取最小公倍数
//1.获取两个数中的较大值
int max = (m >= n)? m : n;
//2.遍历
for(int i = max;i <= m * n;i++){
if(i % m == 0 && i % n == 0){
System.out.println("最小公倍数:" + i);
break;
}
}
while循环
注意:
说明:
-
注意不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环。
-
for循环和while循环可以相互转换
/*
While循环的使用
一、循环结构的4个要素
① 初始化条件
② 循环条件 --->是boolean类型
③ 循环体
④ 迭代条件
二、while循环的结构
①
while(②){
③;
④;
}
执行过程:① - ② - ③ - ④ - ② - ③ - ④ - ... - ②循环条件为false退出循环
注意:
1.写While循环千万小心不要丢了迭代条件。一旦丢了,就可能导致死循环
2.我们写程序,要避免出现死循环(算法的特性:有限性)。
3.for循环和while循环是可以相互转换的!
区别:for循环和while循环的初始化条件的作用范围不同
*/
class WhileTest{
public static void main(String[] args){
//遍历100以内的所有偶数
int i = 1;
while(i <= 100){
if(i % 2 = 0){
System.out.println(i);
}
i++;
}
//for循环中定义的i只在内部有效,而whil出了循环以后,仍可以调用。
System.out.println(i);//101
}
}
do-while循环
注意:do-while循环至少执行一次循环体。
/*
do-while循环的使用
一、循环结构的4个要素
① 初始化条件
② 循环条件 --->是boolean类型
③ 循环体
④ 迭代条件
二、do-while循环结构:
①
do{
③;
④;
}while(②);
执行过程:① - ③ - ④ - ② - ③ - ④ - ② - ③ - ④ - ... - ②循环条件为false退出循环
注意:
1.do-while至少执行一次循环体
2.开发中,使用for和while更多一些。较少使用do-while
*/
class DoWhileTest{
public static void main(String[] args){
//遍历100以内的偶数,并计算所有的偶数和及偶数的个数
int num = 1;
int sum = 0;//记录总和
int count = 0;//记录个数
do{
if(num % 2 == 0){
System.out.println(num);
sum += num;
count++;
}
num++;
}while(num <= 10);
}
}
循环结构知识拓展
死循环
在程序设计中我们经常会使用到死循环,然后在循环中通过条件判断语句来break掉循环。让一段代码死循环一般我们有两种循环结构的写法:for(;;)和while(true)
/*
题目:
从键盘读入个数不确定的整数,并判断读入的正数和负数的个数,输入为0时结束程序。
说明:
1. 不在循环条件部分限制次数的结构:for(;;) 或 while(true)
2. 结束循环有几种方式?
方式一:循环条件部分返回false
方式二:在循环体中,执行break
*/
import java.util.Scanner;
class ForWhileTest {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int positiveNumber = 0;//记录正数的个数
int negativeNumber = 0;//记录负数的个数
for(;;){//while(true){
int number = scan.nextInt();
//判断number的正负情况
if(number > 0){
positiveNumber++;
}else if(number < 0){
negativeNumber++;
}else{
//一旦执行break,跳出循环
break;
}
}
System.out.println("输入的正数个数为:" + positiveNumber);
System.out.println("输入的负数个数为:" + negativeNumber);
}
}
嵌套循环
/*
嵌套循环的使用:
1.嵌套循环:将一个循环结构A声明在另一个循环结构B中,就构成了嵌套循环
2.
外层循环:循环结构B
内层循环:循环结构A
3.
① 内层循环结构遍历一遍,只相当于外层循环循环体执行了一次
② 假设外层循环需要被执行m次,内层循环需要被执行n次。此时内层循环的循环体一共执行了m*n次
4.技巧:
外层循环控制行数
内层循环控制列数
5.补充:衡量一个功能代码的优劣性
1)正确性
2)可读性
3)健壮性
4)高效率与低存储:时间复杂度和空间复杂度(衡量算法的好坏)
*/
class ForForTest{
public static void main(String[] args){
//输出:******
for(int = 1; i <= 5;i++){
System.out.print('*');
}
System.out.println('\n')
/*输出:
******
******
******
******
*/
for(int j = 1;j <= 4;j++){
for(int = 1; i <= 5;i++){
System.out.print('*');
}
System.out.println();
}
/*输出:
*
**
***
****
*****
tips:外层循环控制行数,内层循环控制列数
*/
for(int j = 1;j <= 5;j++){ //控制行数
for(int i = 1;i <= j;i++){ //控制列数
System.out.print('*');
}
System.out.println();
}
/* i(行号) j(*的个数) 规律:i+j=5换句话说:j=5-i;
**** 1 4
*** 2 3
** 3 2
* 4 1
*/
for(int i = 1;i <= 4;i++){
for(int j = 1;j <= 5 - i;j++){
System.out.print("*");
}
System.out.println();
}
//嵌套循环例题1:九九乘法表
for(int j = 1;j < 9;i++){
for(int i = 1;i< = j;i++){
System.out.print(j + "*" + i + "=" + (i * j)+ " ");
}
System,out.println();
}
//嵌套循环例题2:100000以内的所有质数的输出
//质数:素数:“只能”被1和它本身整除的自然数-->从2开始,到这个数-1结束为止都不能被这个数本身整除
//最小的质数是:2,所以从以2为循环的初始条件
boolean isFlag = true;//标识i是否被除尽过,一旦除尽就修改其值
//获取当前时间距离1970-01-01 00:00:00的毫秒数
long start = System.currentTimeMillis();
for(int = 2;i <= 100000;i++){//遍历100以内的自然数
/*优化二:对本身是质数的自然数是有效的
对范围质数进行开方(Math.sprt),因为除以前面的数能除尽那么后面的数也能除尽
可以想象是把数分成两份,前面的数能除尽那么后面对应的数当然也能出尽,它们的商就是彼此
z= x*y x=y时候就是x平方,进行开方就可以取到质数的范围*/
for(int j = 2;j <= Math.sprt(i);i++){//j:被i去除
if(i % j == 0){//i被j除尽了
isFlag = false;
break;//优化算法一:只对本身非质数的自然数是有效的
}
}
if(isFlag == true){
System.out.println(i);
}
//重置isFlag
isFlag = true;
}
//获取当前时间距离1970-01-01 00:00:00的毫秒数
long end = System.currentTimeMillis();
//用来验证优化算法的执行效率
System.out.println("所花费的时间为:" + (end - start);
//未优化时间20105 -优化一:break:2585 -优化二:1014
}
}
break和continue关键字的使用
class BreakContinueTest {
public static void main(String[] args) {
for(int i = 1;i <= 10;i++){
if(i % 4 == 0){
break;//123
//continue;//123567910
//System.out.println("今晚迪丽热巴要约我!!!");
}
System.out.print(i);
}
System.out.println("\n");
//******************************
label:for(int i = 1;i <= 4;i++){
for(int j = 1;j <= 10;j++){
if(j % 4 == 0){
//break;//默认跳出包裹此关键字最近的一层循环。
//continue;
//break label;//结束指定标识的一层循环结构
continue label;//结束指定标识的一层循环结构当次循环
}
System.out.print(j);
}
System.out.println();
}
}
}/*
使用范围 循环中的使用的作用(不同点) 相同点
break: switch-case
循环结构中 结束当前循环 关键字后面不能声明执行语句
continue: 循环结构中 结束当次循环 关键字后面不能声明执行语句
*/
class BreakContinueTest {
public static void main(String[] args) {
for(int i = 1;i <= 10;i++){
if(i % 4 == 0){
break;//123
//continue;//123567910
//编译失败,关键字后面不能声明执行语句
//System.out.println("今晚迪丽热巴要约我!!!");
}
System.out.print(i);
}
System.out.println("\n");
//******************************
label:for(int i = 1;i <= 4;i++){
for(int j = 1;j <= 10;j++){
if(j % 4 == 0){
//break;//默认跳出包裹此关键字最近的一层循环。
//continue;
//break label;//结束指定标识的一层循环结构
continue label;//结束指定标识的一层循环结构当次循环
}
System.out.print(j);
}
System.out.println();
}
}
}
附加:
数组
数组的概述
数组(Array): 是多个相同类型数据按一定顺序排列的集合, 并使用一个名字命名, 并通过编号的方式对这些数据进行统一管理
编号就是我们的索引,通过索引可以快速的找到我们数组中的元素。
数组的常见概念:
- 数组名
- 下标(或索引)
- 元素
- 数组的长度
数组的特点:
- 数组本身是引用数据类型, 而数组中的元素可以是任何数据类型, 包括基本数据类型和引用数据类型。
- 创建数组对象会在内存中开辟一整块连续的空间, 而数组名中引用的是这块连续空间的首地址。
- 数组的长度一旦确定, 就不能修改。
- 我们可以直接通过下标(或索引)的方式调用指定位置的元素, 速度很快。
数组的分类:
- 按照维度:一维数组、 二维数组、 三维数组、 …
- 按照元素的数据类型分:基本数据类型元素的数组、 引用数据类型元素的数组(即对象数组)
看了一大堆的概念依旧很懵逼?没关系,知识就是需要在使用的时候才能深入了解。下面让我们来学习下数组的使用吧~
一维数组
一维数组基本使用
数组的基本使用见下面代码
/*
* 一、数组的概述
* 1.数组的理解:数组(Array),是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名, 并通过编号的方式对这些数据进行统一管理。
*
* 2.数组相关的概念:
* >数组名:
* >元素:
* >索引(角标、下表):
* >数组的长度:元素的个数
*
* 3.数组的特点:
* 1)数组是有序排列的
* 2)数组属于引用数据类型的变量。数组的元素,既可以是基本数据类型,也可以是引用数据类型
* 3)创建数组对象会在内存中开辟一整块连续的空间
* 4)数组的长度一旦确定,就不能修改。
*
* 4. 数组的分类:
* ① 按照维数:一维数组、二维数组、。。。
* ② 按照数组元素的类型:基本数据类型元素的数组、引用数据类型元素的数组
*
* 5. 一维数组的使用
* ① 一维数组的声明和初始化
* ② 如何调用数组的指定位置的元素
* ③ 如何获取数组的长度
* ④ 如何遍历数组
* ⑤ 数组元素的默认初始化值 :
* >数组元素是整型:0
* >数组元素是浮点型:0.0
* >数组元素是char型:0或'\u0000',而非'0'(字符对应的编码值0,而非字符‘0’)
* >数组元素是boolean型:false
*
* >数据元素是引用数据类型:null
*
* ⑥ 数组的内存解析 :
*
*
*/
public class AarryTest {
public static void main(String[] args) {
// 什么是声明和初始化?
int num;// 声明
num = 10;// 初始化
int id = 1001;// 声明+初始化
// 1.一维数组的声明和初始化
int[] ids;// 声明
// 1.1 静态初始化:数组的初始化和数组元素的赋值操作同时进行
ids = new int[] { 1001, 1002, 1003, 1004 };// 初始化
// 1.2 动态初始化:数组的初始化和数组元素的赋值操作分开进行
String[] names = new String[5];
// 总结:数组一旦初始化完成,其长度就确定了
// 2.如何调用数组的指定位置的元素:通过索引的方式调用
// 数组的索引从0开始的,到数组的长度-1结束
names[0] = "狗蛋";
names[1] = "二狗子";
names[2] = "二愣子";
names[3] = "大柱子";
names[4] = "小毛驴";
// 3.如何获取数组的长度
// 属性:length
System.out.println(names.length);// 5
// 4.如何遍历数组元素(遍历:从头到尾经历一遍)
/*血low操作:
System.out.println(name[0]);
System.out.println(name[1]);
System.out.println(name[2]);
System.out.println(name[3]);
System.out.println(name[4]);*/
for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}
// 5.数组元素的默认初始化值的
int[] arr = new int[4]; // 动态初始化,没有赋值
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);// 发现是0 0 0 0
}
System.out.println("*************");
float[] arr1 = new float[4]; // 动态初始化,没有赋值
for (int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);// 发现是0.0 0.0 0.0 0.0
}
System.out.println("*************");
char[] arr2 = new char[4]; // 动态初始化,没有赋值
for (int i = 0; i < arr2.length; i++) {
System.out.println(arr2[i]);
// 发现是四个空格的效果,因为编码值0对应的字符就是这个空格的效果
}
System.out.println("*************");
boolean[] arr3 = new boolean[4]; // 动态初始化,没有赋值
for (int i = 0; i < arr3.length; i++) {
System.out.println(arr3[i]);// false false false flase
}
System.out.println("*************");
String[] arr4 = new String[4]; // 动态初始化,没有赋值
for (int i = 0; i < arr4.length; i++) {
System.out.println(arr4[i]);// null null null null
}
}
}
数组是引用类型,它的元素相当于类的成员变量,因此数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化。
关于数组元素的初始化值也可以通过下图来记忆:
PS:
- 对于基本数据类型而言,默认初始化值各有不同
- 对于引用数据类型而言,默认初始化值为null(注意与0不同! )
一维数组的内存解析
首先让我们来了解下内存的简化结构(栈、堆、方法区):
Java的内存分配上,主要分4个块!
一块是用来装代码的,就是编译的东西。
一块是用来装静态变量的,例如用static关键字的变量,例如字符串常量。
一块是stack,也就是栈,是用来装变量和引用类型的!但区别在于,装了变量以后,变量上是有值的,而引用类型本身在stack上是没有值的。
一块是heap,也就是堆!堆可以一句话概括,装new出来的东西!
所以综上所述,基本数据类型都在stack中,而引用类型,变量是放在stack中,真正有内容的东西放在heap中,也就是当new了一个新的引用类型,他就会放在堆中,同时栈中的引用类型变量会指向堆中你new出来的东西!
我们把放在方法中的变量都叫做局部变量,而我们上面写的代码基本上都放在main方法里面,所以里面的所有变量实际上都放在了栈里面!
下面我们通过一组代码图解来看看它在内存中是怎么分配和解析的
在方法当中的变量(局部变量)放在栈里面,对象放在堆中。声明数组变量是在栈里开辟空间,而new则是在堆开辟空间来存放数组对象。使用”=“赋值符号把它们关联起来后变量存放的就是堆中数组对象的引用(内存地址)
变量找到存储的数组的内存地址,然后通过索引找到对应元素的存储空间修改其值
ps:这里的String其实并不是直接放到数组内存空间中的,对于引用类型的变量,实际上数组的存储空间放的只是它们的地址值(如果没有引用就为null)。具体它们在内存中是怎么解析的我会写在下一篇JavaSE之面向对象篇中~
此时sysout(arr1[1]);的结果是null,java的垃圾回收机制使用的是引用技术算法,当栈空间的变量arr1的引用指向另外一块内存地址的时候,原来的堆中的那块内存的引用值就记为0,在之后的某个不确定的时候会被java当做垃圾回收掉
拓展:简单的垃圾回收过程:我们常写的main方法执行完后,栈里面的变量就全都没用了,所以所有变量会依次出栈,那么所对应变量的指针也会消失,那么堆中开辟的内存也会被在某个时间全被回收掉
多维数组
多维数组其实并不可怕,无论多少维,它在内存中分配的原理都是一样的。在这里通过介绍二维数组让我们来揭开多维数组的神秘面纱。
二维数组的使用
二维数组初始化(声明+赋值):
还是通过代码来理解比较实在:
/*
* 二维数组的使用
* 规定:二维数组分为外层数组的元素,内层数组的元素
* 如: int[][] arr = new int[4][3]
* 外层元素:arr[0]、arr[1]等
* 内层元素:arr[0][0]、arr[1][2]等
*
* 1. 理解
* 对于二维数组的理解,我们可以看成是一维数组array1又作为另一个一维数组array2的元素而存在。其实, 从数组底层的运行机制来看,其实没有多维数组。
*
* 2.二维数组的使用:
* ① 二维数组的声明和初始化
* ② 如何调用数组的指定位置的元素
* ③ 如何获取数组的长度
* ④ 如何遍历数组
* ⑤ 数组元素的默认初始化值
针对于初始化方式一:比如:int[][] arr = new int[4][3];
* 外层元素的初始化值为:地址值
* 内层元素的初始化值为:与一维数组初始化情况相同
*
* 针对于初始化方式二:比如:int[][] arr = new int[4][];
* 外层元素的初始化值为:null
* 内层元素的初始化值为:未初始化,故不能调用,否则报错。
*
* ⑥ 数组的内存解析
*/
public class ArrayTest1 {
public static void main(String[] args) {
//1. 二维数组的声明和初始化
int[] arr = new int[] {1,2,3};//一维数组
//二维数组静态初始化
int[][] arr1 = new int[][] {{1,2,3},{4,5},{6,7,8}};
//二维数组动态初始化1
String[][] arr2 = new String[3][2];//三行两列
//二维数组动态初始化2
String[][] arr3 = new String[3][];//三行,列没有规定有多长
//错误情况
// String[][] arr4 = new String[][4];
// String[4][3] arr5 = new String[][];
//其他正确情况(还是建议使用上面的标准写法,别花里胡哨的):
int[] arr4[] = new int[][]{{1,2,3,7},{4,5},{6,7,8}};//虽然很诡异但是也可以
//类型推断:前面int数组,那后面也不可能new其他类型,所以编译器能自己推断出来
int[] arr5[] = {{1,2,3},{4,5},{6,7,8}};
//2.如何调取数组的指定位置的元素
System.out.println(arr1[0][1]); //2
System.out.println(arr2[1][1]);//null
// System.out.println(arr3[1][0]);//报空指针的异常,没有给引用
//把指针指向新创建的数组对象就不会空指针了
arr3[1] = new String[4];
System.out.println(arr3[1][0]);//null
//3. 获取数组的长度
//arr4指向的就是一个数组,数组里面的元素指向哪个数组对它本身元素个数判断没有影响
System.out.println(arr4.length);//3
System.out.println(arr4[0].length);//4
System.out.println(arr4[1].length);//2
//4.如何遍历二维数组(两层循环)
for(int i =0; i < arr4.length; i++) {
for(int j = 0; j < arr4[i].length; j++) {
System.out.print(arr4[i][j] + " ");
}
System.out.println();
}
//由此可以推理出,三维,四维数组的遍历要分别使用三层,四层循环
//5.数组元素的默认初始化值的
int[][] a = new int[4][3];
System.out.println(a[0]);//[I@15db9742地址值(也不完全是地址),[表一维,I表int
System.out.println(a[0][0]);//0,内层数组就是int型故默认值是0
System.out.println(a);//[[I@6d06d69c,[[表二维数组,I表Int
System.out.println("*****************");
float[][] a1 = new float[4][3];
System.out.println(a1[0]);//地址值
System.out.println(a1[0][0]);//0.0
System.out.println("*****************");
String[][] a2 = new String[4][2];
System.out.println(a2[1]);//地址值Ljava.lang.String;@4e25154f
System.out.println(a2[1][1]);//null
System.out.println("*****************");
double[][] a3 = new double[4][];
System.out.println(a3[1]);//null,数组是引用数据类型,默认值是null
// System.out.println(a3[1][0]);//报错,空指针异常
}
}
可以从下面三点去理解二维数组:
- 数组属于引用数据类型
- 数组的元素也可以是引用数据类型
- 一个一维数组A的元素如果还是一个一维数组类型的,则,此数组A称为二维数组
所以二维数组其实在内存中实际上也只是一维数组而已。还有点懵?看下面的内存解析你应该就更明白是咋滴回事了!
二维数组的内存解析
我认为内存解析还是通过图解去理解是最好的,这样在写代码的时候能做到”胸中有图“,那才能写的舒服!下面通过一组代码的图解演示二维数组的内存解析过程:
前面看过一维数组的内存解析过程后,这张图应该很容易的理解,所以就不过多去说了。
拓展:数组变量的赋值和数组的复制:
数组变量的赋值:
如何理解:将array1保存的数组的地址赋给array2,使得array1和array2共同指向堆空间中的同一个数组实体。
数组的复制:
如何理解:我们通过new的方式,给array2在堆空间中开辟了数组的空间,将array1数组中的元素值一个个的赋值到arrary2数组中
数组中涉及到的常见算法
通过前面的学习再来看看数组的概念:
数组是多个相同类型数据按一定顺序排列的集合, 并使用一个名字命名, 并通过编号的方式对这些数据进行统一管理
是否清晰很多,谈到数组就离不开它的顺序排序。对于排序衍生了许多算法,不同的算法执行的效率。说到算法总是离不开数据结构,在上面对数组内存解析的学习上,实际上都是数据结构层面上的内容,了解数据结构对我们理解内存解析是杠杠的!所以接下来简单的从宏观角度去介绍一下数据结构和算法(只是作为兴趣了解,并不会很深入去说),然后再来介绍几种常见的数组算法来去使用我们的数组,以此来加深我们对数组的了解。
首先先来从宏观来快速了解下常见的数据结构与算法:
数据结构:
- 数据与数据之间的逻辑关系:集合、一对一、一对多、多对多
- 数据的存储结构:数据在内存和硬盘上怎么按逻辑关系去存储
- 线性表:主要刻画的是一对一的逻辑关系,主要分两类
- 顺序表,比如数组(在内存中是一段连续的内存空间)
- 链表
- 栈(先进后出)
- 队列(先进先出)
- 树形结构:主要刻画的是一对多的逻辑关系,主要的典型应用是二叉树
- 图形结构
- 线性表:主要刻画的是一对一的逻辑关系,主要分两类
算法
常见的应用数组数据结构的算法有两类
- 排序算法
- 搜索(检索)算法
搜索算法
在这里只介绍两种常见的搜索算法:线性查找和二分法查找。还是以代码的形式演示才是最容易上头的~
线性查找
字面理解,线性查找(或搜索)就是从前往后找,找到匹配的就返回,类似现实生活中的地毯式搜索
class ArrayTest{
public static void main(String[] args){
String[] arr = new String[] {"a","b","c","d","e","f","g"};
//查找(或搜索)
//线性查找
String dest = "b";
boolean isFlag = true
for(int i = 0; i < arr.length; i++){
if(dest.equals(arr[i])){
//equals方法比较的是括号里面的内容是否与字符串相等
System.out.println("找到了指定的元素,位置为:" + i);
isFlag = false;
break;
}
}
if(isFlag){
System.out.println("很遗憾,没有找到");
}
}
}
二分查找
二分查找的实现思想很简单,把元素与数组中的中间元素进行比较,如果比它小的话就与左边序列的中间元素做对比;比它大就往右边序列中间元素做对比。看起来好像是数组中分成了许多个中间元素与之对比直到匹配成功或所有元素被作为”中间元素“比较后结束。看着好像有点懵?原来我的表述能力,来!国舅上图:
不难想到,二分法查找并非适用所有的数组,它要求数组必须是有序的!
public class ArrayTest{
public static void main(String[] args){
//查找(或搜索)
//二分查找
//前提:所要查找的数组必须有序
int[] arr = new int[] {-99,-34,2,3,4,55,66,88,111,222,333};//有序
int dest = -34;
int head = 0;//初始的首索引
int end = arr2.length -1; //初始的末索引
boolean isFlag = true;
while(head <= end){
int middle = (head + end) / 2;//获取中间值
if(dest == arr[middle]){
System.out.println("找到了指定的元素,位置为:" + middle);
isFlag = false;
break;
}else if(arr[middle] > dest){//比中间元素值小,从左边找
end = middle -1;
}else{//arr[middle] < dest,比中间元素值小,从左边找
head = middle + 1;
}
}
if(isFlag){
system.out.println("很遗憾,没有找到");
}
}
}
请细细品味。
排序算法
算法的概念其实挺抽象,排序属于算法的一类。我理解的是:凡是完成了具体的功能,有固定的几个步骤实现的这样的一个结构就可以叫做算法!精华总结就是:理解就是算法就是解决问题的步骤!
PS:关键字可以理解为对象的属性的意思,这里的序列里面放到都是对象,对象的排序可以根据他们的属性去排,如果无法理解关注我的下一篇关于面向对象的文章!!!
十种常见的内部排序算法:
- 选择排序
- 直接选择排序、堆排序
- 交换排序
- 冒泡排序、快速排序
- 插入排序
- 直接插入排序、折半插入排序、Shell排序
- 归并排序
- 桶式排序
- 基数排序
这里只简单的介绍两种排序算法:冒泡和快速排序,来感受感受,毕竟本篇还是javaSE基础学习的主题!
冒泡排序
概念看不明白?上gif:
不多说,直接上代码了解
public class BubbleSortTest{
public static void main(String[] args){
int[] arr = new int[]{55,11,33,66,2,23,8,-10};
//冒泡排序
//外层循环控制要比较的数组中的元素(因为最后一个不用比所以-1)
for(int i =0; i < arr.length -1; i++){
//内层循环控制比较的次数
//因为每次一大轮后都有一个元素排序完成所以-i
for(int j =0; j < arr.length -1 -i; j++){
if(arr[j] < arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i] + "\t");
}
}
}
快速排序
快排这里只介绍一下它实现的思想
上面的思想看起来有点乱,我们整理一下:
文字还是有点乱,上图解:
我们要实现的其实就是一大轮比较完后实现基准值的左边的元素值都比它小,右边的都比它大!以该基准值为界限左右分为两个序列重复执行这样的一个过程。不难理解通过这样的排序思想把基准值每轮比较后放在正确排序位置上的值!实现最终每个元素都在正确的排序位置!就跟细胞分裂似的,每次分出2次方个序列,越分越快越分越快这样的排序效率是相当快的!
还看不懂?来!秀儿,再上gif:
拓展:排序算法性能比较(经常比较面试会问到所有我就放出来了解了解就好了)
Arrays工具类的使用
数组中的操作那么多像查找、反转、排序等等这些功能每次都要自己去写那开发效率多么的低。Java作为一门强大的语言,给我们提供了Arrays工具类,用来给我们快速方便的操作数组!
java.util.Arrays类即为操作数组的工具类,包含了用来操作数组(比如排序和搜索)的各种方法。
这里只列举了相对来说常用到的方法,更多的方法可以在API文档中去查找。
import java.util.Arrays;
/*
java.util.Arrays:操作数组的工具类,里面定义了很多操作数组的方法
*/
public class ArrayTest{
public static void main{string[] args}{
//1.boolean equals(int[] a,int[] b):判断两个数组是否相等。
int[] arr1 = new int[]{1,2,3,4};
int[] arr2 = new int[]{1,3,2,4};
boolean isEquals = Arrays.equals(arr1,arr2);
System.out.println(isEquals);
//2.String toString(int[] a):输出数组信息。
System.out.println(Arrays.toString(arr1));//字符串[1,2,3,4]
//3.void fill(int[] a,int val):将指定值填充到数组之中,说是替换元素更贴切
Arrays.fill(arr1,10);
System.out.println(Arrays.toString(arr1));//[10,10,10,10]
//4.void sort(int[] a):对数组进行排序.
Arrays.sort(arr2);//底层用的快排
System.out.println(Arrays.toString(arr2));//[1,2,3,4,]
//5.int binarySearch(int[] a,int key):对排序后的数组进行二分法检索指定的值
//注意:搜索的前提是数组是有序的!
int index = Arrays.binarySearch(arr1,2);
System.out.println(index);//返回的是负数表示没找到,找到的话返回的是索引值
}
}
以后开发中遇到涉及数组的操作的问题的使用可以先去API文档中看一下Arrays工具类中是否有现成的结构可以调用,那就用不着我们自己写了
数组中常见的异常
异常是我们在编写代码或执程序的时候常常遇到的,多说无用。还是用我们代码去说话:
/*
* 数组中的常见异常:
* 1. 数组角标越界的异常:ArrayIndexOutOfBoundsExcetion
*
* 2. 空指针异常:NullPointerException
*
*/
public class ArrayExceptionTest {
public static void main(String[] args) {
//1. 数组角标越界的异常:ArrayIndexOutOfBoundsExcetion
int[] arr = new int[]{1,2,3,4,5};
// for(int i = 0;i <= arr.length;i++){
// System.out.println(arr[i]);
// }
// System.out.println(arr[-2]);
// System.out.println("hello");
//2.2. 空指针异常:NullPointerException
//情况一:
// int[] arr1 = new int[]{1,2,3};
// arr1 = null;
// System.out.println(arr1[0]);
//情况二:
// int[][] arr2 = new int[4][];
// System.out.println(arr2[0][0]);
//情况三:
String[] arr3 = new String[]{"AA","BB","CC"};
arr3[0] = null;
System.out.println(arr3[0].toString());
}
}
自此JavaSE的基础篇就结束了,在本文中只是初步认识Java的已经掌握它的核心语法结构。然而Java中最重要的内容就是面向对象了!Java是一门面向对象的语言,故无对象不Java。所以修行才刚刚开始!感兴趣的童鞋,请关注我写的下一篇JavaSE之面向对象的文章!
本笔记记录的学习资源来自于尚硅谷_宋康红老师的学习资料,讲的特别好!强烈推荐