从零开始学Java之旅 Part1 基本概念部分
从零学习Java之旅 Part1 基本概念部分
Java学前了解知识
本人说明
1、9月1日开始学习 8月底提了辞职,开始搜集相关资料,包括知乎、B站等,9月底办完离职手续后开始了正式学习,第一部分学习了一个月时间,使用的是WizNote Lite记笔记(因为可以实时渲染markdown,还可以在手机上随时查看),但是在两台电脑的数据传输时有问题,遂逐渐搬到csdn上来,方便自己查看,同时也方便同跟我一样的人相互学习。
2、使用ieda写代码
3、使用jdk版本:jdk8
Why?因为目前国内绝大部分公司,使用的jdk版本仍然是jdk8。商业公司求稳,从jdk9开始每半年发布一次,分长期支持版本(至少3年)和短期支持版本(半年)
LTS:long term support长期支持版
学习优先级:Jdk8->jdk11->jdk17
相关细节和代码会在后续迁移补上
Java语言版本
语言版本即jdk(oracle) vs openjdk
2009年被oracle收购之前,将jdk源代码开源,形成了openjdk。但是,在sun开源jdk源代码的时候,其中有一部分源代码(小部分非核心功能),因为产权问题,无法开源,就被其他有同样功能的开源代码取代。
Openjdk中,只包含jdk最最核心的功能,还有其他一些第三方实现的功能或者是插件,openjdk是jdk的极简版本。
关于openjdk和jdk源代码是有关系的:包含在openjdk源代码中的绝大部分代码和oreclejdk一模一样。Jdk可以理解为openjdk的一个分支:不仅两者的代码是相同的,而且,oraclejdk还会和openjdk保持同步。同时,一旦oraclejdk发现openjdk中的一些bug,oracle在修复之后,把这些修复bug的代码同步到openjdk。
IBM、Google、Facebook等都是从openjdk中拿到源代码,然后经过修改,增加自己的功能,形成自己的jdk版本。
Java语言平台版本
JAVASE(Java platform standard edition)标准版。为开发普通桌面和商务应用程序提供的解决方案。
JAVAME(Java platform to micro edition)小型版。为开发电子消费产品和嵌入式设备提供的解决方案。
JAVAEE(Java platform to enterprise edition)企业版。为开发企业环境下的应用程序提供的一套解决方案。
Java语言特点
- 跨平台
通过Java语言编写的应用程序在不同的系统平台上都可以运行。Java程序是在Java虚拟机上运行,而非直接运行于操作系统。不同系统的jvm不一样,所有的jvm都能运行Java语言,所以jvm不是跨平台。 - 面向对象-解释型
- 健壮-动态
- 分布式-高效
- 多线程-结构中立(字节码)
- 开源
高级语言计算机本身是不认识的,计算机只认识二进制01。一定有一个工具,帮助我们将代码转换为计算机可以识别的对应到机器指令级别的二进制序列。
编译型语言:用这种语言写出的代码,首先通过编译器的编译,转换成目标代码(二进制可执行文件.exe),然后依次在操作系统中执行。
解释型语言:转换一句,执行一句。
Java是解释型:.java->.class->java虚拟机(jvm)。
JDK和JRE
JRE(Java runtime environment)包括Java虚拟机,运行时核心类库(rt.jar),JRE主要是给已经写好的Java程序使用,换句话说Java程序能在操作系统中运行,必须有JRE。
JDK(Java develop kit)包含JRE,除此之外,JDK中海包含一些供开发者使用的工具,比如javac、javap等,仅仅只供开发者在开发阶段使用的工具。
Java编写注意事项
在java语言中,一个Java文件中只能定义一个被public修饰的类,且被public修饰的类的类名必须和Java文件的文件名相同。(没有public可以不一样)
计算机程序本质
不管计算机中运行的什么样的程序,本质上都是在运算(广义的运算)。
计算机中储存的所有东西都是二进制01数据。
运算—运算对象和运算符
运算对象(参与运算的数量),Java语言中数量分为常量和变量。
程序基本概念
HelloWorld
只需记住格式就行,后面会慢慢学习。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("HelloWorld!");
}
}
//第一:public表示公开的(关键字,固定写法)
//第二:class用来声明一个类(关键字,固定写法)
//第三:HelloWorld是一个类名(既然是一个名字,就可以改成其它的名字)
//第四:public class HelloWorld表示声明一个公共的类HelloWorld
//第五:在java编程中,一定要注意成对儿的符号要成对儿写,以上HelloWorld当中成对儿的符号包括:小括号(),中括号[],大括号{},双引号""。这些符号在编写的时候建议成对儿编写。
注释
用于解释说明的文字。千万不要忽视注释的作用,尤其是对于初学者。方便写代码和项目维护、阅读代码。初学者最好先写注释再写代码。
单行注释格式//
多行注释格式 /* 注释内容 */,多行注释不能嵌套
文档注释格式 /** 文档注释内容 */
标识符与关键字
关键字
关键字是被Java语言赋予特殊含义的单词。
关键字的特点:组成关键字的字母全部小写。
注意事项:高级的编辑器或者专门的开发工具中,关键字会有高亮效果;goto和const作为保留字存在,目前并不使用。
用于定义数据类型的关键字: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、import
其他修饰符关键字:native、strictfp、transient、assert
标识符
标识符就是给类、接口、方法、变量等起名字时使用的字符序列。
组成规则:英文大小写字母、数字字符、$和_
注意事项:不能以数字开头、不能是Java中的关键字(保留字)、区分大小写。
常见命名规则:驼峰命名
- 包命名:就类似于在操作系统中,以文件夹的形式组织文件,在Java语言中,以包来组织Java中的类。关于包来说,为了防止类的命名冲突,一个包下不可以定义同名的类,但是不同的包可以定义同名的类,如果,不同的coder,定义了相同的类名时,只要保证同名的类在不同的包下就可以了。也就是说,解决类的同名问题依靠的是包名的不同,为了保证包名的唯一,我们以域名(唯一的)反转的形式命名包。
单极:一个全部小写的单词,如test
多级:以域名反转的方式来命名,单词全部小写,单词之间以.来分隔,如com.csdn.name - 类和接口命名:
单个单词:首字母大写,其余字母全部小写,如Student;
多个单词:每个单词首字母大写,其余字母全部小写,如JavaBasic、MaxAge - 变量和方法的命名:
单个单词:所有字母小写,如value;
多个单词:第一个单词首字母小写,从第二个单词开始,每个单词首字母大写,如intValue。 - 常量的命名:
单个单词:所有字母全部大写,如MAX、IP、NONE;
多个单词:每个单词大写,单词之间以_来分隔,如MAX_AGE、IP_ADDRESS。
在实际开发当中,命名要遵行的一个核心原则:见名知意。
数据类型
常量
常量:在运行过程中其值不会发生改变的量。
- 字符串常量
双引号引起来的内容,如"wangdao"代表固定的字符序列 - 整型常量
所有整数,1,2,3 - 小数常量
所有小数,0.1、0.2、3.2 - 字符常量
用单引号引起来的内容,如’a’,‘我’ - 布尔常量
只有ture 或 false - 空常量
null
Java中整数型常量的表现形式
- 二进制:由0,1组成,以0b开头,比如0b1100
- 八进制:由0-7组成,以0开头,比如014
- 十进制:由0-9组成,默认10进制,比如12
- 十六进制:由0-9,A-F(或a-f)组成,以0x开头
进制转化一般用趋于法计算。
负数在计算机中如何表示呢?
约定0表示正号,1表示负号,最高位假设字长为8位,8位二进制来表示一个数0,0001100。
计算机中如何计算?
引入补码来计算,一旦二进制用补码表示,符号位和数值位可以一起参与数值运算。
二进制的补码表示和原码表示
1.对于一个正数的原码表示,其原码表示就是补码表示。
2.对于一个负数的原码表示,其补码表示:原码表示的符号位不变,其余各位依次取反(0变1,1变0),末尾+1。
3.原码和补码互补,对补码求补就得到了原码。
所以整数在计算机中其实都是以补码形式存在。
变量
变量定义的格式:数据类型 变量名(标识符) = 变量值
数据类型:一个数据集合和基于这个数据集合一组操作。
变量数据类型:基本数据类型、引用数据类型。
基本数据类型
- 整数类型
byte 占用1字节(8位),表示范围-128~127,默认值0
short 占用2字节(16位),表示范围-32768~32767,默认值0
int 占用4字节(32位),表示范围-231~231-1,默认值0
long 占用8字节(64位),表示范围-263~263-1,默认值0 - 浮点数类型
float 占用4字节(32位),表示范围-3.403E38~3.403E38,默认值0.0
double 占用8字节(64位),表示范围-1.798E308~1.798E308,默认值0.0 - 字符型
char 占用2字节(16位),表示范围0~255,默认值\u0000
- 布尔型
boolean true或false,默认值false
引用数据类型
- 类class
- 接口interface
- 数组[]
类型转化
- 1.boolean类型的值不能直接转化为其他类型的值。
- 2.默认(自动)的转化规则:byte,short,char->int->long->float->double
byte,short,char相互之间不转化,他们参与运算首先转化为int类型。 - 3.强制转化:目标类型 变量名 = (目标类型)(被转化的类型)
比如:byteValue = (byte)(intValue + byteValue);
输入的整数默认类型是int,当输入的数超过int表示范围就会报错,通过给整数字面量加后缀L或l(不推荐小写l,容易与数字1混淆),来声明字面量为long类型。
输入的小数默认类型是double,超过表示范围时,通过给小数字面量加F或f来声明类型是float类型。
关于字符的问题,一个字符要被保存到计算机中,只能以数值的形式。一个字符对应的数值,也就是字符和字符对应的数值关系,就是编码表,ASCII码表。比如(‘a’ 97)。
使用变量注意事项
1.作用域:变量有效的时间范围,这个范围通常用{}来界定。
2.初始化值:变量在使用之前,由编译器强制必须赋值。
3.建议一行定义一个变量(从代码的可读性角度来考虑)
键盘录入数据
如何实现键盘录入数据呢?
导包语句(放到class定义的上面):import java.util.Scanner;
创建对象:Scanner sc = new Scanner(System.in);
从键盘读取数据:int x = sc.nextInt();
运算符
算术运算符
+正号,-负号,+加,-减,*乘
/除(整数相除只能得到整数),%取模,++自增,--自减,+字符串相加
1、对于++
和--
:单独使用效果,若参与运算,位置不同,效果不同。++
和--
既可以放在变量之后,也可以放在变量之前。当++
和--
不参与任何其他运算时,++
和--
在变量之前和之后时没有区别的;但是一旦++
和--
参与其他运算的时候,他们就会有一点区别。
2、当++
在前时,参与运算的时候,首先是自增,再参与运算。对于--
而言,在前和在后与++
相同
赋值运算符
符号:=、+=、-=、*=、/=、%=
比较运算符
==相等于、!=不等于、<小于、>大于、<=小于等于、>=大于等于
instanceof检查是否是类的对象
关系运算符的结果只有两种true or false,注意和“=”区分,不要把“==”写成“=”。
关系运算符的结果:true or false都是boolean类型。
逻辑运算符
& AND(与) 全真为真
| OR(或) 全假为假
^ XOR(异或) 相同为false不同为true
! NOT(非)
&& AND(短路)
|| ORtrue(短路)
&&有短路效果。即当第一个操作数为false,直接得出与运算的结果。
位运算符
<<左移
空位补0,被移除的高位丢弃
相当于给操作数*2(左移后的结果仍然在相应数据类型的表示范围内)
>>右移
被移位的二进制空缺位补符号位
最高位补符号位,相当于给操作数/2(只针对未溢出且能被除尽的情况)
>>>无符号右移
被移位的二进制空缺位补0
移位的动作和带符号的一模一样,右移之后,高位补0。无符号右移通常只用来处理正整数,对正整数而言,无符号右移一位相当于/2
&与运算
任何二进制位和0进行&结果是0,和1进行&是原值
只针对二进制的一位,0或1,只有两个二进制都为1时结果为1,其余为0
|或运算
任何二进制位和0进行|结果是原值,和1进行|是1
只有两个全为0的时候结果才为0
^异或运算
任何相同二进制位进行^结果是0,不同二进制位^结果是1
相同为0,不同为1。两个性质:任何数与本身异或为0(a^a=0),0和任何数做异或运算都是本身(0^a=a)。满足交换律
~按位取反
包括符号位
三目运算符
格式:关系表达式?表达式1 : 表达式2
1.如果关系表达式结果为true,运算后的结果是表达式1
2.如果关系表达式结果为false,运算后的结果是表达式2
流程控制
顺序结构
顺序结构描述的是Java语言之间,从上到下(从左到右)依次执行的执行顺序。
选择结构
选择结构中的代码顺序和顺序结构中代码的执行顺序有明显的不同
1.选择结构与特定的语法规则,代码要执行具体的逻辑运算进行判断。
2.逻辑运算的结果有两个,所以产生选择,按照不同的选择执行不同的代码。
3.Java中,选择结构有两种实现形式,if语句和switch语句。
if (条件判断语句) {
语句;
}
if语句如果满足条件就执行。
switch (表达式) {
case 常量表达式或枚举常量:
语句;
break;
case 常量表达式或枚举常量:
语句;
break;
......
default: 语句;
break;
}
switch语句首先计算出表达式的值,其次和case依次比较,一旦有对应的值,就执行相应语句,在执行的过程中,遇到break就会结束。最后,如果所有的case都和表达式的值不匹配,就会执行default语句体部分,然后结束程序。
循环结构
for (初始化语句; 判断条件语句; 控制条件语句) {
循环体语句;
}
for循环适合针对一个范围判断进行操作(操作次数比较明确)。
while (条件判断语句) {
循环体语句(包含条件控制语句)
}
while循环适合判断次数不明确操作。
do {
循环体语句(包含条件控制语句)
} while (条件判断)
do while执行流程和for、while稍有不同:1.执行初始化语句;2.不管是否满足循环条件,都首先执行一次循环体;3.之后执行流程就几乎一样。
for循环和while循环等价,do while循环与其他两种循环结构相比,可保证循环体至少执行一次。
跳转控制语句
break(中断)
break使用场景:在选择结构switch语句中、在循环语句中使用。离开使用场景的存在是没有意义的。
break的作用:跳出(终止)单层循环(如果有多层循环,只跳出内层),结束switch语句。
continue(继续)
continue使用场景:在循环语句中使用,离开使用场景的存在是没有意义的。
continue的作用:退出循环的一次迭代过程。也可以使用标签。
return(返回)
return关键字不是为了跳出循环体,更常用的功能是结束一个方法(函数),也就是退出一个方法。跳转到上层调用的方法。
数组
概念:相同数据类型的数据元素的有序集合。
一维数组
数据类型[] 数组名 = new 数据类型[数组长度];
声明数组int[] arr;是一个声明在方法体中的局部变量,存储了一个内存首地址。
比如System.out.println(arr);
输出的是内存地址[I@4554617c,其中[表示一维数组,I表示int,@没意义起分割作用,后面是16进制内存地址
根据索引进行访问数据。数组必须先初始化,然后才能使用。
所谓初始化:为数组中的数据元素分配内存空间,并给每一个数据元素赋初值。
动态初始化:初始化时只指定数组长度,由系统为数组分配初始值。
静态初始化:初始化时指定每个数组元素的初始值,由系统分配长度。
数组操作中,常见的两个问题
- 数组索引越界ArrayIndexOutOfBoundsException(访问了数组中,并不存在的存储单元,不能直接使用未开辟空间的数组)
- 空指针异常 NullPointerException
要想理解这两个问题需要理解jvm虚拟机内存模型:(重要)
1、一个Java程序在虚拟机运行的过程中,在内存中需要保存很多种类型的数据。
2、比如局部变量(声明在方法体中变量),数组等等。不同类型的数据,其使用方式和生命周期都不相同。
3、为了更好的管理这些不同类型的数据,jvm将自己的内存空间划分为不同的内存区域,各个区域针对不同类型的数据,其内存空间有不同的管理方式。
目前先只了解栈
和堆
,而方法区
、还有其他的空间在后面学习。
栈和堆的区别:
栈堆区别 | 栈 | 堆 |
---|---|---|
存储内容 | 存储局部变量 | new出来的东西 |
初值 | 手动赋初值 | jvm自动赋予默认初值 |
生命周期 | 局部变量的内存,会随着方法的执行完毕而被回收(销毁) | 堆上数据的内存空间的回收,和方法的执行没有直接关系 |
栈:每个方法在执行的时候会创建一个栈帧,主要存储局部变量。
堆:是被所有线程共享的一块区域,在虚拟机启动时创建。
方法区:存储已被虚拟机加载的类信息、常量、静态变量。
数组操作练习
- 获取数值长度:数值名.length
int[] a = new int[] {
1, 2, 3, 4, 5};
System.out.println(a.length);
- 字符串拼接:String 名 = Arrays.toString(数组名);获得每个元素
int[] a = new int[] {
1, 2, 3, 4, 5};
String st = Arrays.toString(a);
System.out.println(st);
- 计数排序counting sort
//使用的是方法,在下面一节有讲
//假设知道一个数组中的值都小于等于100
public static void countingSort(int[] targetArr) {
//1.遍历原数组
int[] countingArray = new int[101];
for (int i = 0; i <targetArr.length; i++) {
countingArray[targetArr[i]]++;
}
//2.利用辅助数组排序
//排序位置
int originIndex = 0;
for (int i = 0; i < countingArray.length; i++) {
if (countingArray[i] != 0) {
for (int j = 0; j < countingArray[i]; j++) {
targetArr[originIndex] = i;
originIndex++; //每赋一个值,排序位置就+1
}
}
}
String reverse = Arrays.toString(targetArr);
System.out.println(reverse);
}
二维数组
初始化格式
数据类型[][] 变量名 = new 数据类型[m][n];
数据类型[][] 变量名 = new 数据类型[m][];
数据类型[][] 变量名 = new 数据类型[][]{
{元素…},{元素…},{元素…}};
简化版格式:数据类型[][] 变量名 = {
{元素…},{元素…},{元素…}};
二维数组的实质:一维数组的数组
练习
- 数组遍历(依次输出数组中的每一个元素)
- 数组元素逆序
- 对取值范围在1~100的数据集合排序
- 数组获取最值(获取数组中的最大值最小值)
- 数组查表法(根据键盘录入索引,查找对应星期)
- 数组元素查找(查找指定元素第一次在数组中出现的索引)
- 打印杨辉三角形(行数可以键盘录入)
方法/函数
public static 返回值类型 方法名(参数类型 参数变量, ...) {
函数体(本方法要执行的若干操作);
return 返回值;
}
修饰符:现在先认为是固定的 public static
函数体内要与一个返回值,要有return语句,需要跟上面的返回值类型一致。(特例:void不需要)
返回值:返回该方法的运算结果。基本的数据类型;引用数据类型(类);void没有返回一个值。
方法名:一个标识符,作用是调用的时候根据方法名进行调用。见名知意。
参数/参数列表:有0个或多个,每一参数先写类型,再写参数名。多个参数用逗号分隔开
方法定义:方法就是完成特定功能的代码块(在有些其他语 言中,也被成为函数)。
方法调用:接收调用,输出调用,直接调用
方法调用注意事项:
- 方法不调用不执行
- 方法与方法是平级关系,不能嵌套定义
- 方法定义的时候参数之间用逗号隔开
- 方法调用的时候不用在传递数据类型
- 如果方法有明确的返回值,一定要有return带回一个值
方法重载:同一个类中,可以定义多个同名方法,同名方法之间相互重载,便于调用。
- 发生条件:只要满足a.参数个数不同b.参数类型不同c.参数顺序不同
方法的区分:编译器按方法签名来唯一确定一个方法。即方法名+参数列表。
因为没有返回值类型的判断,可以直接调用。
当调用重载方法的时候,如果没有完全匹配的方法,此时会选择最近的方法匹配调用(这种情况极少,不用)。
方法的参数传递:研究的问题是:修改形式参数,会不会影响实际参数。1、方法参数为基本数据类型;2、方法参数为引用数据类型。
方法练习
- 键盘录入一个数据n(1<=n<=9),输出对应的nn乘法表
- 方法重载练习:比较两个数据是否相等。参数类型分别为两个byte类型,两个short类型,两个int类型,两个long类型,并在main方法中进行测试
递归
递归方法的定义:方法定义中调用方法本身的现象。
public class Demo1 {
public static void main(String[] args) {
//计算1~100的和
int sum = sum(100);
System.out.println(sum);
}
public static int sum(int i) {
if (i == 1) {
return 1;
}
return i + sum(i - 1);
}
}
递归容易产生的问题:递归一定要有出口!!次数不能太多,否则就出现stackoverflow
要理解这个问题先理解:栈空间内存管理
1、栈内存中,存储定义在方法体中的局部变量
2、一定是,运行中的方法,在运行过程中,其局部变量才会占用栈内存
3、所以,即一个运行中的方法,才会占用栈内存
4、所以栈空间,会给每一个运行中的方法,分配一个其独有的内存空间(栈帧stack frame)
栈空间:
- 基本单位:栈帧
- 每个运行中的方法都对应一个栈帧
- 栈帧用来存储方法运行时所需要存储的局部变量等信息
生命周期:
- 随方法的执行而创建
- 随方法执行完毕而销毁
stackoverflow产生的原因
- 递归方法没有递归出口,一直不停地调用自己
- 方法的每一次执行都会在栈上创建对应的栈帧
- 又因为,每个方法都不停地自己调用自己,每个方法都没有运行结束,其对应的栈帧也不会被销毁
- 同时每一次调用,又会在栈空间上申请新的栈帧,直到栈空间被占满
所以递归的注意事项:1、递归一定要有递归出口;2、递归的深度不能太深,否则还是可能出现,stackoverflow
递归算法的核心思想:把一个大规模的问题的解决,转化为若干相似规模更小的子问题的求解。
递归算法练习
- 汉诺塔问题
- 第几排问题
- 求n的阶乘
- 不死神兔问题(斐波那契数列求解)
1、使用递归 警惕重复计算问题(子问题重叠)
2、非递归 可以使用数组来储存数据,避免重复计算