之前学习java
记得笔记,但是一直没怎么遇到java
类的题,平时也不用java
,所以经常忘记,今天想起来了再看一遍,再把之前写的笔记整理整理发到博客上。
Java预学习
Jave的优势
从互联网到企业平台,Java是应用最广泛的编程语言,原因在于:
- Java是基于JVM虚拟机的跨平台语言,一次编写,到处运行;
- Java程序易于编写,而且有内置垃圾收集,不必考虑内存管理;
- Java虚拟机拥有工业级的稳定性和高度优化的性能,且经过了长时期的考验;
- Java拥有最广泛的开源社区支持,各种高质量组件随时可用。
PS:JVM则是执行Java字节码的虚拟机。
Java语言常年霸占着三大市场:
- 互联网和企业应用,这是Java EE的长期优势和市场地位;
- 大数据平台,主要有Hadoop、Spark、Flink等,他们都是Java或Scala(一种运行于JVM的编程语言)开发的;
- Android移动平台。
这意味着Java拥有最广泛的就业市场。
Java简介
-
Java最早是由SUN公司(已被Oracle收购)的詹姆斯·高斯林(高司令,人称Java之父)在上个世纪90年代初开发的一种编程语言,最初被命名为Oak,目标是针对小型家电设备的嵌入式应用,结果市场没啥反响。谁料到互联网的崛起,让Oak重新焕发了生机,于是SUN公司改造了Oak,在1995年以Java的名称正式发布,原因是Oak已经被人注册了,因此SUN注册了Java这个商标。随着互联网的高速发展,Java逐渐成为最重要的网络编程语言。
-
Java介于编译型语言和解释型语言之间、编译型语言如C、C++,代码是直接编译成机器码执行,但是由于不同平台(x86、ARM等)CPU的指令集不同,编译的机器码也都不一样。解释型语言如Python、Ruby没有这个问题,可以由解释器直接加载源码然后运行,代价就是运行效率太低。而Java是将代码编译成一种“字节码”,它类似于抽象的CPU指令,然后,针对不同平台编写虚拟机,不同平台的虚拟机负责加载字节码并执行,这样就实现了“一次编写,到处运行”的效果。为了保证不同平台、不同公司开发的虚拟机能够正确执行Java字节码,SUN公司制定了一系列的Java虚拟机规范。
-
随着Java的发展,SUN给Java又分出了三个不同版本:
- Java SE: Standard Edition(标准版)
- Java EE:Enterprise Edition (企业版)
- Java ME:Micro Edition (微型版)
三者区别:
SE: 包含标准的JVM和标准库
EE: 在SE的 基础上加上了大量的API和库
ME:针对嵌入式设备的“瘦身版”,SE的标准库无法再ME上使用,ME的虚拟机也是“瘦身版”
EE>SE>ME
Jdk
jdk(Java development kit):java开发工具包,支持Java程序开发的最小环境,即如果你要进行Java开发,你至少要在你的开发机器上安装JDK。
JDK = Java程序设计语言+Java虚拟机+Java API类库
JDK的一些可执行文件:
- java:这个可执行程序其实就是JVM,运行Java程序,就是启动JVM,然后让JVM执行指定的编译后的代码;
- javac:这是Java的编译器,它用于把Java源码文件(以
.java
后缀结尾)编译为Java字节码文件(以.class
后缀结尾); - jar:用于把一组
.class
文件打包成一个.jar
文件,便于发布; - javadoc:用于从Java源码中自动提取注释并生成文档;
- jdb:Java调试器,用于开发阶段的运行调试。
Jre
jre(Java Runtime Environment):java运行环境,支持Java程序运行的标准环境,如果你不需要进行Java程序开发,只是要运行Java程序,例如运行Jar文件,那么你可以在你的运行机器上只安装JRE。
**JRE = Java虚拟机 +Java API类库中的Java SE API子集 **
jvm
jvm(Java Virtual Machine):java虚拟机 ,其主要任务为将字节码装载到内部,解释/编译为对应平台上的机器指令执行。
下载IDEA
IDEA 全称 IntelliJ IDEA,是java语言开发的集成环境(IDE),IntelliJ在业界被公认为最好的java开发工具之一。
Intellij IDEA 最突出的功能自然是调试(Debug),可以对Java代码,JavaScript,JQuery,Ajax等技术进行调试。
根据官方提示即可正确安装。
第一个Java程序
- 打开刚刚下载好的IDEA
- 选择一个目录并打开文件夹
- 打开左上角File->Settings->Plugins,搜索Chinses选择并下载汉化插件,如下图:
- 在左侧项目栏中右键点击项目文件夹->新建->新模块,如下图
- 选择Java模块和模块SDK,如果电脑上有JDK则会自动写入,如果没有点击下载JDK
- 点击下一步->输入项目名->点击完成
- 右键src文件夹->新建->Java类
- 这里起名为"Hello",然后写入以下代码:
public class Hello {
public static void main(String[] args){
System.out.println("Hello World!");
}
}
- 点击上方运行->运行,即可执行代码
代码解释
在一个Java程序中,你总能找到一个类似:
public class Hello {
...
}
的定义,这个定义被称为class(类),这里的类名是Hello
,大小写敏感,class
用来定义一个类,public
表示这个类是公开的,public
、class
都是Java的关键字,必须小写,Hello
是类的名字,按照习惯,首字母H
要大写。而花括号{}
中间则是类的定义。
注意到类的定义中,我们定义了一个名为main
的方法:
public static void main(String[] args) {
...
}
方法是可执行的代码块,一个方法除了方法名main
,还有用()
括起来的方法参数,这里的main
方法有一个参数,参数类型是String[]
,参数名是args
,public
、static
用来修饰方法,这里表示它是一个公开的静态方法,void
是方法的返回类型,而花括号{}
中间的就是方法的代码。
方法的代码每一行用;
结束,这里只有一行代码,就是:
System.out.println("Hello, world!");
它用来打印一个字符串到屏幕上。
Java规定,某个类定义的public static void main(String[] args)
是Java程序的固定入口方法,因此,Java程序总是从main
方法开始执行。注意到Java源码的缩进不是必须的,但是用缩进后,格式好看。
以上这些C语言是一样的!!!
Java代码的运行过程
Java源码本质上是一个文本文件,我们需要先用javac
把Hello.java
编译成字节码文件Hello.class
,然后,用java
命令执行这个字节码文件:
┌──────────────────┐
│ Hello.java │<─── source code
└──────────────────┘
│ compile
▼
┌──────────────────┐
│ Hello.class │<─── byte code
└──────────────────┘
│ execute
▼
┌──────────────────┐
│ Run on JVM │
└──────────────────┘
因此,可执行文件javac
是编译器,而可执行文件java
就是虚拟机。
我们使用命令行的方式来感受一下它的执行过程吧
- 第一步,在保存
Hello.java
的目录下执行命令javac Hello.java
:
$ javac Hello.java
如果源代码无误,上述命令不会有任何输出,而当前目录下会产生一个Hello.class
文件:
- 执行
Hello.class
,使用命令java Hello
:
$ java Hello
PS:给虚拟机传递的参数Hello
是我们定义的类名,虚拟机自动查找对应的class文件并执行。
如果执行
$ java Hello.class
或者还可以直接运行java Hello.java
也是可以的:
$ java Hello.java Hello, world!
这是Java 11新增的一个功能,它可以直接运行一个单文件源码!
需要注意的是,在实际项目中,单个不依赖第三方库的Java源码是非常罕见的,所以,绝大多数情况下,我们无法直接运行一个Java源码文件,原因是它需要依赖其他的库。
Java程序基础
基本结构
我们先剖析一个完整的Java程序:
public class Hello {
public static void main(String[] args){
System.out.println("Hello World!");
// 输出文本
}
}
因为Java是面向对象的语言,一个程序的基本单位就是class
,class
是关键字,这里定义的class
名字就是Hello
:
类名要求:
- 类名必须以英文字母开头,后接字母,数字和下划线的组合
- 习惯以大写字母开头
注意到public
是访问修饰符,表示该class
是公开的。
在class
内部,可以定义若干方法(method):
这里的方法名是main
,返回值是void
,表示没有任何返回值。
我们注意到public
除了可以修饰class
外,也可以修饰方法。而关键字static
是另一个修饰符,它表示静态方法,,目前,我们只需要知道,Java入口程序规定的方法必须是静态方法,方法名必须为main
,括号内的参数必须是String数组。
方法名也有命名规则,命名和
class
一样,但是首字母小写
在方法内部,语句才是真正的执行代码。Java的每一行语句必须以分号结束:
注释
Java有3种注释,第一种是单行注释,以双斜线开头,直到这一行的结尾结束:
// 这是注释...
而多行注释以/*
星号开头,以*/
结束,可以有多行:
/*
这是注释
blablabla...
这也是注释
*/
还有一种特殊的多行注释,以/**
开头,以*/
结束,如果有多行,每行通常以星号开头:
/**
* 可以用来自动创建文档的注释
*
*/
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
这种特殊的多行注释需要写在类和方法的定义处,可以用于自动创建文档。
变量和数据类型
变量
在Java中,变量分为两种:基本类型的变量
和引用类型的变量
。
基本数据类型
基本数据类型是CPU可以直接进行运算的类型。Java定义了以下几种基本数据类型:
- 整数类型:byte,short,int,long
- 浮点数类型:float,double
- 字符类型:char
- 布尔类型:boolean
整型
public class Main {
public static void main(String[] args) {
int i = 2147483647;
int i2 = -2147483648;
int i3 = 2_000_000_000; // 加下划线更容易识别
int i4 = 0xff0000; // 十六进制表示的16711680
int i5 = 0b1000000000; // 二进制表示的512
long l = 9000000000000000000L; // long型的结尾需要加L
}
}
浮点型
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。
字符型
字符类型char
表示一个字符。Java的char
类型除了可表示标准的ASCII外,还可以表示一个Unicode字符:
char a = 'A';
char zh = '中';
注意char
类型使用单引号'
,且仅有一个字符,要和双引号"
的字符串类型区分开。
引用类型
除了上述基本类型的变量,剩下的都是引用类型。例如,引用类型最常用的就是String
字符串:
String s = "hello";
引用类型的变量类似于C语言的指针,它内部存储一个“地址”,指向某个对象在内存的位置。
常量
定义变量的时候,如果加上final
修饰符,这个变量就变成了常量:
final double PI = 3.14; // PI是一个常量
根据习惯,常量名通常全部大写。
var关键字
有些时候,类型的名字太长,写起来比较麻烦。例如:
StringBuilder sb = new StringBuilder();
这个时候,如果想省略变量类型,可以使用var
关键字:
var sb = new StringBuilder();
编译器会根据赋值语句自动推断出变量sb
的类型是StringBuilder
。对编译器来说,语句:
var sb = new StringBuilder();
实际上会自动变成:
StringBuilder sb = new StringBuilder();
因此,使用var
定义变量,仅仅是少写了变量类型而已。
数组类型
Java的数组有几个特点:
- 数组所有元素初始化为默认值,整型都是
0
,浮点型是0.0
,布尔型是false
; - 数组一旦创建后,大小就不可改变。
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[5];
ns[0] = 68;
ns[1] = 79;
ns[2] = 91;
ns[3] = 85;
ns[4] = 62;
}
}
数组是引用类型
,在使用索引访问数组元素时,如果索引超出范围,运行时将报错
。
可以用数组变量.length
获取数组大小。
也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小。例如:
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[] { 68, 79, 91, 85, 62 };
System.out.println(ns.length); // 编译器自动推算数组大小为5
}
}
还可以进一步简写为:
int[] ns = { 68, 79, 91, 85, 62 };
运算
运算符
- 算术运算符
- 关系运算符
- 位运算符
- 逻辑运算符
- 赋值运算符
- 三元运算符
- instanceof 运算符
运算规则
浮点数运算和整数运算相比,只能进行加减乘除
这些数值计算,不能做位运算
和移位运算
。
如果参与运算的两个数其中一个是整型
,那么整型可以自动提升到浮点型
。
溢出
整数运算在除数为0
时会报错,而浮点数运算在除数为0
时,不会报错,但会返回几个特殊值:
NaN
表示Not a NumberInfinity
表示无穷大-Infinity
表示负无穷大
短路运算
布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。
因为false && x
的结果总是false
,无论x
是true
还是false
,因此,与运算在确定第一个值为false
后,不再继续计算,而是直接返回false
。
字符串连接
Java的编译器对字符串做了特殊照顾,可以使用+
连接任意字符串和其他数据类型,这样极大地方便了字符串的处理。如果用+
连接字符串和其他数据类型,会将其他数据类型先自动转型为字符串。
多行字符串
字符串可以用"""..."""
表示多行字符串。
Java流程控制
输出
在前面的代码中,我们总是使用System.out.println()
来向屏幕输出一些内容。
println
是print line的缩写,表示输出并换行。因此,如果输出后不想换行,可以用print()
:
public class Hello {
public static void main(String[] args){
System.out.print("Hello,");
System.out.print("World!\n");
System.out.println("Hello,World!");
}
}
>>>
Hello,World!
Hello,World!
格式化输出
果要把数据显示成我们期望的格式,就需要使用格式化输出的功能。格式化输出使用System.out.printf()
,通过使用占位符%?
,printf()
可以把后面的参数格式化成指定格式:
public class Hello {
public static void main(String[] args){
double a = 3.1415926;
System.out.printf("%.2f", a);
}
}
>>>
3.14
Java的格式化功能提供了多种占位符,可以把各种数据类型“格式化”成指定的字符串:
占位符 | 说明 |
---|---|
%d | 格式化输出整数 |
%x | 格式化输出十六进制整数 |
%f | 格式化输出浮点数 |
%e | 格式化输出科学计数法表示的浮点数 |
%s | 格式化字符串 |
注意,由于%
表示占位符,因此,连续两个%%
表示一个%
字符本身。
输入
和输出相比,Java的输入就要复杂得多。
我们先看一个从控制台读取一个字符串和一个整数的例子:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 创建Scanner对象
System.out.print("Input your name: "); // 打印提示
String name = scanner.nextLine(); // 读取一行输入并获取字符串
System.out.print("Input your age: "); // 打印提示
int age = scanner.nextInt(); // 读取一行输入并获取整数
System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
}
}
首先,我们通过import
语句导入java.util.Scanner
,import
是导入某个类的语句,必须放到Java源代码的开头,后面我们在Java的package
中会详细讲解如何使用import
。
然后,创建Scanner
对象并传入System.in
。System.out
代表标准输出流,而System.in
代表标准输入流。直接使用System.in
读取用户输入虽然是可以的,但需要更复杂的代码,而通过Scanner
就可以简化后续的代码。
有了Scanner
对象后,要读取用户输入的字符串,使用scanner.nextLine()
,要读取用户输入的整数,使用scanner.nextInt()
。Scanner
会自动转换数据类型,因此不必手动转换。
要测试输入,我们不能在线运行它,因为输入必须从命令行读取,因此,需要走编译、执行的流程:
$ javac Main.java
这个程序编译时如果有警告,可以暂时忽略它,在后面学习IO的时候再详细解释。编译成功后,执行:
$ java Main
Input your name: Bob
Input your age: 12
Hi, Bob, you are 12
根据提示分别输入一个字符串和整数后,我们得到了格式化的输出。
if语句
if
语句的基本语法是:
if (条件1) {
// 条件1满足时执行
}else if(条件2){
// 条件2满足时执行
}else{
// 条件1,2不满足时执行
}
判断引用类型相等
在Java中,判断值类型的变量是否相等,可以使用==
运算符。但是,判断引用类型的变量是否相等,==
表示“引用是否相等”,或者说,是否指向同一个对象。例如,下面的两个String类型,它们的内容是相同的,但是,分别指向不同的对象,用==
判断,结果为false
:
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1);
System.out.println(s2);
if (s1 == s2) {
System.out.println("s1 == s2");
} else {
System.out.println("s1 != s2");
}
}
}
>>>
hello
hello
s1 != s2
要判断引用类型的变量内容是否相等,必须使用equals()
方法:
注意:执行语句s1.equals(s2)
时,如果变量s1
为null
,会报NullPointerException
:
switch语句
switch
语句的基本语法是:
switch(option){
case 1:
// 执行语句
break;
case 2:
// 执行语句
break;
case 3:
// 执行语句
break;
default:
// 执行语句
break
};
从Java 12开始,switch
语句升级为更简洁的表达式语法,使用类似模式匹配(Pattern Matching)的方法,保证只有一种路径会被执行,并且不需要break
语句:
switch(option){
case 1 -> {
// 执行语句
}
case 2 -> // 单个语句;
default -> {
// 执行语句
}
};
使用新的switch
语法,不但不需要break
,还可以直接返回值。
int opt = switch(option){
case 1 -> 1;
case 2 -> 2;
default -> {
int code = 1+1;
yield code; // switch语句返回值
}
};
大多数时候,在switch
表达式内部,我们会返回简单的值。
但是,如果需要复杂的语句,我们也可以写很多语句,放到{...}
里,然后,用yield
返回一个值作为switch
语句的返回值:
while和do while语句
while
语句的基本语法是:
while (条件表达式) {
循环语句
}
// 继续执行后续代码
do while
语句的基本语法是:
do {
执行循环语句
} while (条件表达式);
for循环
for
语句的基本语法是:
for (初始条件; 循环检测条件; 循环后更新计数器) { // 执行语句}
for each 循环
Java还提供了另一种for each
循环,它可以更简单地遍历数组:
public class Main { public static void main(String[] args) { int[] ns = { 1, 4, 9, 16, 25 }; for (int n : ns) { System.out.println(n); } }}
除了数组外,for each
循环能够遍历所有“可迭代”的数据类型,包括后面会介绍的List
、Map
等。
break和continue
在循环过程中,可以使用break
语句跳出当前循环。
break
语句通常都是配合if
语句使用。要特别注意,break
语句总是跳出自己所在的那一层循环。
break
会跳出当前循环,也就是整个循环都不会执行了。而continue
则是提前结束本次循环,直接继续执行下次循环。
Java数组操作
遍历数组
for
方法:
public class Hello {
public static void main(String[] args) {
int[] ns = {1, 3, 5, 7, 9};
for(int i = 0;i < ns.length;i++){
System.out.println(ns[i]);
}
}
}
for each
方法:
public class Hello {
public static void main(String[] args) {
int[] ns = {1, 3, 5, 7, 9};
for(int n : ns){
System.out.println(n);
}
}
}
数组排序
最简单的冒泡排序 :
import java.util.Scanner;
public class Sort {
public static void main(String[] args){
int[] num = new int[100];
var input = new Scanner(System.in);
System.out.print("请输入需要排序数字的数量:");
int sum = input.nextInt();
System.out.print("请输入需要排序的数字:");
for(int i = 0;i < sum;i++){
num[i] = input.nextInt();
}
for(int i = sum - 1;i > 0; i--){
for(int j = 0;j < i;j++){
int temp;
if(num[j] > num[j+1]){
temp = num[j];
num[j] = num[j+1];
num[j+1] = temp;
}
}
}
for(int i = 0;i < sum; i++){
System.out.printf("%d ", num[i]);
}
}
}
多维数组
public class Hello {
public static void main(String[] args) {
int[][] ns = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
System.out.println(ns.length);
for(int[] i : ns){
for(int j: i){
System.out.printf("%d ", j);
}
System.out.println();
}
}
}
>>>
3
1 2 3 4
5 6 7 8
9 10 11 12
命令行参数
Java程序的入口是main
方法,而main
方法可以接受一个命令行参数,它是一个String[]
数组。
这个命令行参数由JVM接收用户输入并传给main
方法:
public class Main {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
}