目录
一、Java发展简史
- 1991年,James Gosling(高司令)和他的团队在Sun Microsystems公司开始了一个名为“Green Project”的项目,旨在开发一种用于家庭电器的嵌入式系统编程语言。
- 1995年,这个项目正式发布,语言被命名为Java,并随之推出了Java 1.0版本。
- Java 1.0 (1995)
- 推出了“Write Once, Run Anywhere”(一次编写,到处运行)的理念。
- 包括了核心API和虚拟机(JVM),支持图形用户界面(GUI)和网络编程。
- Java 1.1 (1997)
- 增加了内部类、JavaBeans、RMI(远程方法调用)和JDBC(Java数据库连接)等特性。
- 加强了安全性和性能。
- Java 2(J2SE 1.2,1998)
- 引入了Swing库,用于构建更丰富的图形界面。
- 分离为三个版本:J2SE(标准版)、J2EE(企业版)、J2ME(微型版)。
- Java 2(J2SE 1.3和1.4)
- J2SE 1.3 (2000):提升了性能,加入了Java Sound API、RMI-IIOP等。
- J2SE 1.4 (2002):引入了assertions、NIO(新的I/O库)和日志API。
- Java 5(J2SE 5.0,2004)
- 引入了大量新特性:泛型、增强for循环、自动装箱/拆箱、枚举类型、可变参数、注解等。
- 将版本命名方式从J2SE改为Java SE。
- Java 6(2006)
- 增强了脚本语言支持(包括JavaScript),集成了Web服务的支持。
- 提高了对桌面应用和Web应用的支持。
- Java 7(2011)
- 增加了新特性如Switch语句中使用字符串、try-with-resources语句、二进制字面量等。
- 提高了对动态语言的支持。
- Java 8(2014)
- 引入了Lambda表达式和Stream API,极大地简化了集合操作和并行处理。
- 增加了新的日期和时间API(java.time包)。
- Java 9(2017)
- 引入了模块化系统(Project Jigsaw),使得JDK更加模块化。
- 提供了JShell(交互式命令行工具)。
- Java 10(2018)
- 引入了局部变量类型推断(var关键字)。
- Java 11(2018)
- 成为LTS(长期支持)版本。
- 引入了一些新的API和特性,如新的字符串方法和HTTP客户端。
- Java 12至Java 14
- 增强了性能和安全性,引入了一些预览特性,如Switch表达式。
- Java 15至Java 17
- 增加了Sealed Classes、Records等特性。
- Java 17成为LTS版本,提供长期支持。
- 持续快速发布周期
- 自Java 9以来,Java采用了每6个月发布一个新版本的快速发布周期。
- 不同的版本带来了许多新的特性和改进,使得Java更加现代化和高效。
二、JDK、JRE、JVM
JDK:java development kit (Java开发工具)
JRE:java runtime environment (Java运行时环境)
JVM:Java Virtual Machine (Java虚拟机)
2.1 JDK
官方JDK下载地址: 点我跳转下载。
JDK 包含了 JRE,同时还包含了编译 Java 源码的编译器 javac,以及其他的一些重要工具:
- javap:class 类文件的最基础的反编译器;
- jstack:用于打印 Java 线程栈踪迹的工具;
- jconsole:用于监视 Java 程序的工具;
- jhat:用于 Java 堆分析的工具
- jar:用于打包 Java 程序的工具;
- javadoc:用于生成 Java 文档的工具;
2.2 JRE
JRE面向Java程序的使用者,而不是开发者,可以支撑Java程序的运行,包括JVM虚拟机(java.exe等)和基本的类库(rt.jar等)
2.3 JVM
JVM 是 Java Virtual Machine 的简称,意为 Java 虚拟机。JVM 主要通过分为以下 4 个部分,来执行 Java 程序:
- 类加载器( ClassLoader )
- 运行时数据区( Runtime Data Area )
- 执行引擎( Execution Engine )
- 本地库接口( Native Interface )
三、编程基础
3.1 常量与变量
在JVM的运转中,承载的是数据,而数据的一种变现形式就是“量”,量分为:常量与变量。
1.常量
所谓常量,即在作用域内保持不变的值,一般用final关键字进行修饰,通常分为全局常量、类内常量、局部常量。例如一年有四个季节、一年有12个月、一个星期有7天等。
常量分类:
- 字符串常量:凡是用双引号引起来的部分,都可以称之为字符串常量。(双引号中可跟多个字符)例如:"abc"、"Hello World"、"123"等;
- 整形常量:数学中的整数,例如:100、200、0、-150;
- 浮点数常量:数学中的小数,例如:2.5、-3.14、0.0;
- 字符常量:凡是用单引号引起来的单个字符,都可以称之为字符常量。(单引号中只能有一个字符)例如:'A'、'b'、'9'、'中'等 ;
- 布尔常量:只有true、false;
- 空常量:null 代表没有任何数据。
2.变量
所谓变量,即在程序运行过程中可以改变的值,通常分为分为局部变量、成员变量和静态变量。比如年龄、身高、体重等。
变量注意事项:
局部变量:
- 局部变量声明在方法、构造方法或者语句块中;
- 局部变量在方法、构造方法、或者语句块被执行的时候创建,执行完毕后销毁;
- 访问修饰符不能用于局部变量;
- 局部变量只在声明它的方法、构造方法或者语句块中可见;
- 局部变量存储在栈;
- 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。
成员变量:
- 成员变量声明在一个类中,但在方法、构造方法和语句块之外;
- 成员变量在对象创建时创建,在对象被销毁时销毁;
- 成员变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;
- 访问修饰符可以修饰成员变量;
- 成员变量存储在堆;
- 成员变量对于类中的方法、构造方法或者语句块是可见的;一般情况下应该把成员变量设为私有。通过使用访问修饰符可以使成员变量对子类可见;成员变量具有默认值,数值型变量的默认值是0,布尔型变量的默认值是 false,引用类型变量的默认值是 null,变量的值可以在声明时指定,也可以在构造方法中指定。
静态变量:
- 静态变量在类中以 static 关键字声明,且必须在方法、构造方法和语句块之外;
- 一个类不管创建了多少个对象,该类仅有一份对应的静态变量;
- 静态变量除了被声明为常量外很少使用;
- 静态变量储存在静态存储区;
- 静态变量在程序开始时创建,在程序结束时销毁;
- 静态变量还可以在静态语句块中初始化。
3.2 数据类型
1.基本数据类型
整数型 byte short int long
浮点型 float double
字符型 char
布尔型 boolean
2.引用数据类型
字符串、数组、类、接口、Lambda
3.四类八种数据类型
注意事项:
- 1byte=8bite 1KB=1024byte 1MB=1024KB 1GB=1024MB
- 字符串不是基本类型,而是引用类型;
- 浮点型只是一个近似值,并非精确值;
- 数据范围与字节不一定相关,例如float的数据范围比long更加广泛但float是四字节,long是八字节;
- 浮点数默认类型是double,如果一定要使用float类型,需加后缀F;
- 整数默认为int,如果一定要使用long类型,需要加上一个后缀L。
4.数据类型转换
当数据类型不一样时,将会发生数据类型转换
自动类型转换(隐式)
1. 特点:代码不需要进行特殊处理,自动完成转换。
2. 规则:数据范围从小到大。
强制类型转换(显式)
1. 特点:代码需要进行特殊的格式处理,不能自动完成。
2. 格式:范围小的类型 范围小的变量名 = (范围小的类型)范围大的数据。
注意事项:
1. 强制类型转换一般不推荐使用,因为有可能发生精度损失、数据溢出;
2. byte/short/char这三种数据类型都可以发生数学运算,例如+ - * /等;
3. byte/short/char这三种数据类型在运算的时候,都会被首先提升称为int类型,然后再计算;
4. Boolean类型不能发生数据转换。
public static void main(String[]args){
System.out.println(1024); // 这就是一个整数,默认是int类型
System.out.println(3.14); // 这就是一个浮点数,默认是double类型
// 左边是long类型, 右边是默认的int类型,左右不一样
// 一个等号代表赋值,将右侧的int常量, 交给左侧的long变量进行存储
// int --> long, 符合了数据范围从小到大的要求
// 这行代码发生了自动类型转换。
long num1 = 100;
System.out.println(num1); // 100
// 左边是double类型,右边是float类型,左右不一样
// float --> double,符合从小到大的规则
// 也发生了自动类型转换
double num2 = 2.5F;
System.out.println(num2); // 2.5
// 左边是float类型,右边是long类型,左右不一样
// long --> float,范围是float更大一些,符合从小到大的规则。
// 也发生了自动类型转换
float num3 = 30L;
System.out.println(num3); // 30.0
System.out.println("========");
// 左边是int类型,右边是long类型,不一样
// long --> int,不是从小到大
// 不能发生自动转换!需要强制转换!
// 格式:范围小的类型 范围小的变量名 = (范围小的类型)原本范围大的数据;
int num = (int) 100L;
System.out.println(num); // 100
int num4 = (int)6000000000L;
System.out.println(num4); // 结果不会是6000000000,结果为:1705032704,此时发生数据溢出
// double --> int,强制类型转换
int num5 = (int) 3.99;
System.out.println(num5); //3,这并不是四舍五入,所有的小数位都会被舍弃掉
char zif1 = 'A'; // 这是一个字符型变量,里面是大写字母A
System.out.println(zif1 + 1); // 66, 也就是大写字母A被当作65处理。
// 计算机的底层会用一个数字(二进制)来代表字符A,也就是65
// 一旦char类型进行了数学运算,那么字符就会按照一定的规则翻译成为一个数字
byte num6 = 40; // 注意!右侧的数值大小不能超过左侧的类型范围
byte num7 = 50;
// byte + byte --> int + int --> int
int result1 = num6 + num7;
System.out.println(result1); // 90
short num8 = 60;
// byte + short --> int + int --> int
// int强制转换为short: 注意必须保证逻辑上真实大小本来就没有超过short范围,否则会发生数据溢出
short result2 = (short) (num6 + num8);
System.out.println(result2); // 100
}
3.3 运算符
- 算术运算符: 基本的数学运算,加法、减法、乘法、除法和取模(取余)等;
算数运算符包括: + 加法运算,字符串连接运算 - 减法运算 * 乘法运算 / 除法运算 % 取模运算,两个数字相除取余数 ++、-- 自增自减运算 - 关系运算符: 比较两个值的关系,如等于、不等于、大于、小于等;以布尔值(
true
或false
)返回比较结果,用于条件判断; - 逻辑运算符: 执行逻辑运算,如逻辑与、逻辑或和逻辑非等,以布尔值(
true
或false
)返回比较结果,例如,&&
表示逻辑与,||
表示逻辑或,!
表示逻辑非; - 赋值运算符: 将值赋给变量,如
=
表示赋值运算,+=
表示加并赋值,a = 10,即将10赋值给变量a,A = A+20可直接写成A += 20; - 自增和自减运算符: 增加或减少变量的值,例如
++
表示自增,--
表示自减;例:++num,num++ - 位运算符: 执行位级别的操作,如按位与、按位或、按位异或和位移操作等。位运算符通常用于整数数据类型。例如,
&
表示按位与,|
表示按位或,<<
表示左移。 - 条件运算符(三元运算符):
格式:关系表达式?表达式1:表达式2;
范例:a > b ? a : b; - 实例关系运算符: 比较对象引用,如
instanceof
用于检查对象是否是特定类的实例。 - 类型转换运算符: 将值从一种数据类型转换为另一种数据类型,如强制类型转换。
3.4 流程控制语句
在Java中,流程控制语句是用来控制程序中语句的执行顺序的。根据条件改变代码的执行流程,包括循环执行某段代码、根据条件选择性地执行某些代码块,以及按顺序执行代码。Java中的流程控制语句主要分为三大类:顺序结构、选择结构(也称为条件结构)和循环结构。
1. 顺序结构
顺序结构是最简单的流程控制结构,按照程序中语句的书写顺序,从上到下依次执行。
2. 选择结构
选择结构(条件结构)允许程序根据一个或多个条件来执行不同的代码块。Java中主要的选择结构语句有:
- if 语句:如果条件为真(true),则执行if语句块中的代码。
- if...else 语句:如果条件为真,则执行if语句块中的代码;如果条件为假(false),则执行else语句块中的代码。
- if...else if...else 语句:这是if语句的扩展,允许在多个条件之间进行选择。
- switch 语句:基于不同的case值选择执行不同的代码块。
-
switch (表达式) { case 1: 语句体1; break; case 2: 语句体2; break; ... default: 语句体n+1; break; }
3. 循环结构
循环结构允许程序重复执行某段代码,直到满足特定的条件为止。Java中主要的循环结构语句有:
- for 循环:在给定条件为真时,重复执行一段代码块。通常用于知道循环需要执行的确切次数时使用。
-
for (初始化语句;条件判断语句;条件控制语句) { 循环体语句; }
- while 循环:只要给定条件为真,就重复执行一段代码块。通常用于不确定循环需要执行多少次时使用。
-
while (条件判断语句) { 循环体语句; 条件控制语句; }
- do...while 循环:至少执行一次代码块,然后只要给定条件为真,就继续重复执行。与while循环的主要区别在于,do...while循环至少会执行一次代码块,而while循环可能一次都不执行。
-
do { 循环体语句; 条件控制语句; }while(条件判断语句);
4. 跳转控制语句
-
跳转控制语句(break)
-
跳出循环,结束循环
-
-
跳转控制语句(continue)
-
跳过本次循环,继续下次循环
-
-
注意: continue只能在循环中进行使用!
3.5 方法
方法,也称函数,如果想要重复一段或者多段代码块的使用,可以将这些代码封装成一个方法。
1.方法的声明
2.访问修饰符
3.静态方法
若方法的声明中加上了static关键字,那么这个方法就是静态方法。静态方法是属于类的,而不是属于类创建的对象或实例的,故在调用时无需通过对象实例。
注意事项:
-
静态方法不能存在成员变量
-
静态方法不能调用非静态方法
-
静态方法可以调用静态方法
-
非静态方法可以调用静态方法
4.抽象方法
若方法的声明中加上了abstract关键字,且没有方法体,那么这个方法就是抽象方法。抽象方法往往出现在抽象类和接口中。
注意事项:
抽象类中不一定必须要有抽象方法,但是有抽象方法的类必须是抽象类
若一个类继承了抽象类,则必须实现抽象类中的抽象方法
抽象类不能被实例化
抽象方法不能被声明为静态
抽象方法不能用 private 修饰
抽象方法不能用 final 修饰
5.构造方法
-
作用
给对象的属性进行初始化
-
格式
-
构造方法的方法名必须与类名相同
-
构造方法没有返回类型,也不能定义为void
-
没有具体的返回值
-
-
标准类组成
-
为所有成员变量用private关键字修饰
-
为每一个成员变量提供set、get方法
-
提供一个无参构造
-
提供一个有参构造
-
6.方法的重载与重写
1.重载
-
重载就是在同一个类中,方法名称相同,但形参不同的方法。
-
方法重载的规则:
-
方法名称必须相同;
-
参数列表必须不同(个数不同、类型不同、参数排列顺序不同);
-
方法的返回值可以相同也可以不相同;
-
仅返回类型不同不足以称为方法的重载。
-
2.重写
- 重写就是父类的方法与子类的方法相同(方法名和参数列表完全一致)。
- 方法重写的规则:
- 参数列表与被重写方法的参数列表必须完全相同;
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected;
- 声明为 final 的方法不能被重写;
- 声明为 static 的方法不能被重写,但是能够被再次声明;
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以;
- 构造方法不能被重写;
- 如果不能继承一个类,则不能重写该类的方法。
3.6 数组
1.数组定义
-
数组是相同类型数据的有序集合;
-
数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成;
-
其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标(编号、标记)来访问它,下标是从 0 开始的,例:如果存 10 个数据,即下标为 0 ~ 9 。
2.数组的声明
首先必须声明数组变量,才能在程序中使用数组。
语法:
- dataType[] arrayRefVar; //首选的方法
- dataTypr arrayRefVar[]; //效果相同,但不建议
数组格式:
- 数组类型[] 数组名 = new 数组类型[元素个数或数组长度];
int[] arr = new int[5];
arr[0] = 1;
arr[1] = 2;
- 数组类型[]数组名 = new 数组类型[]{元素,元素,……};
int[] arr = new int[]{3,5,1,7};
int[] arr = {3,5,1,7};
3.数组的初始化
数组的三种初始化及区别
public static void main(String[] args) {
// 1.方式一 静态初始化
int[] a = {1, 2, 3}; //正常的静态初始化
Man[] mans = {new Man(1, 1), new Man(2, 2)}; //引入的静态初始化
// 2.方式二 动态初始化
int[] a = new int[3];
a[0] = 1;
a[1] = 2;
System.out.println(a[0]); //1
System.out.println(a[1]); //2
System.out.println(a[3]); //0 未赋值则为 0 默认初始化
// 3.方式三 默认初始化
int[] a = new int[1];
System.out.println(a[1]); //0 未赋值则为 0
}
区别:
静态初始化:创建的同时赋值
动态初始化:包含默认初始化,未赋值的元素默认为 0
数组的默认初始化:数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化(0)
4.数组的特点
- 数组一旦被创建,那么它的大小就是不可改变的;
- 同一数组内的元素必须是相同类型;
- 数组中的元素可以是任何数据类型,包括基本类型和引用类型
- 数组变量属于引用类型,数组本身就是对象,Java 中对象是在堆中的,因此数组无论保护原始类型还是其他对象类型,数组对象本身是在堆中的
5.数组的常见异常
- 数组角标越界异常,注:数组的角标从0开始。
public static void main(String[] args) { int[] x = { 1, 2, 3 }; System.out.println(x[3]); //java.lang.ArrayIndexOutOfBoundsException }
- 空指针异常
public static void main(String[] args) { int[] x = { 1, 2, 3 }; x = null; System.out.println(x[1]); // java.lang.NullPointerException }
6.数组的常见用法
- 求最大值
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 void selectSort(int[] arr){ for(int x=0; x<arr.length-1; x++){ for(int y=x+1; y<arr.length; y++){ //为什么y的初始化值是 x+1?因为每一次比较,都用x角标上的元素和下一个元素进行比较。 if(arr[x]>arr[y]){ int temp = arr[x]; arr[x] = arr[y]; arr[y] = temp; } } } }
- 冒泡排序
public static void bubbleSort(int[] arr){ for(int x=0; x<arr.length-1; x++){ //-x:让每次参与比较的元减-1:避免角标越界。 for(int y=0; y<arr.length-x-1; y++){ if(arr[y]>arr[y+1]){ int temp = arr[y]; arr[y] = arr[y+1]; arr[y+1] = temp; } } } }
- 折半查找
public static int halfSeach(int[] arr,int key){ int min,mid,max; 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 void reverseArray(int[] arr){ for(int start=0,end=arr.length-1; start<end; start++,end--){ swap(arr,start,end); } } //对数组的元素进行位置的置换。 public static void swap(int[] arr,int a,int b){ int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; }
7.多维数组
多维数组可以看成是数组的数组。
二维数组:
语法:
int a[][] = new int[2][5];
int[][] a = {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}};
示例:
public static void main(String[] args) {
int[][] array = {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}};
//int array[][] = new int[2][5]; //相当于一个数组内嵌套了 2 层的数组
/*
//解析如下:
a =
{
{1, 2}, //2 层嵌套的数组
{3, 4},
{5, 6},
{7, 8},
{9, 10},
}
*/
//打印指定元素的值
System.out.println(array[0][0]); //1
System.out.println(array[0][1]); //2
System.out.println(array[1][1]); //4
System.out.println(array.length); //5 5列
System.out.println(array[0].length); //2 2行
}
上述二维数组 array 可以看成一个两行五列的数组
遍历二维数组:
public static void main(String[] args) {
int[][] array = {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}};
//遍历
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
System.out.println(array[i][j]);
}
}
}
三维数组:
语法:
int a[][][] = new int[][][];
int[][][] a = {{{1, 2}, {3, 4}}};
示例:
public static void main(String[] args) {
int[][][] array = {{{1, 2}, {3, 4}},{{5, 6}, {7, 8}}, {{9, 10}, {11, 12}}};
//int array[][][] = new int[2][2][3]; //相当于一个数组内嵌套了 3 层的数组
/*
//解析如下:
a =
{
{
{1, 2},
{3, 4},
},
{
{5, 6},
{7, 8},
},
{
{9, 10},
{11, 12},
},
//3 层嵌套的数组
}
*/
//打印指定元素的值
System.out.println(array[0][0][0]); //1
System.out.println(array[0][0][1]); //2
System.out.println(array[1][1][1]); //8
System.out.println(array.length); //3 3组
System.out.println(array[0].length); //2 2列
System.out.println(array[0][1].length); //2 2行
}
上述三维数组 array 可以看成一个两行两列三组的数组
遍历三维数组:
public static void main(String[] args) {
int[][][] array = {{{1, 2}, {3, 4}},{{5, 6}, {7, 8}}, {{9, 10}, {11, 12}}};
//遍历
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
for (int k = 0; k < array[i][j].length; k++) {
System.out.println(array[i][j][k]);
}
}
}
}