欢迎光临
我们一直在努力

JAVA基础篇——入门知识汇总

目录

前言:

1. 初识Java

1.1 简介

1.2 Java的使用场合

1.3 java语言的特性

2. 编写第一个Java程序

2.1 初识Java的main方法

2.2 运行Java程序

2.3 注释

2.4 标识符

2.5 关键字

3. 数据类型与变量

3.1 字面常量

3.2 数据类型(8种)

3.3 变量

3.4 字符串类型

4. 运算符

4.1 算术运算符

4.2 关系运算符

4.3 逻辑运算符

4.4 位运算符

4.5 移位运算符

4.6 条件运算符

4.7 运算符优先级

5. 逻辑控制

5.1 顺序结构

5.2 分支结构

5.3 循环结构

5.4 输入输出

6.方法

6.1 方法定义

6.2 方法重载(overload)

6.3 递归

7.数组

7.1 数组的基本概念

7.2 数组是引用类型

7.3 数组的练习

7.4 二维数组

结语:


前言:

本篇文章是对 Java 一些基础语法的汇总,融入了个人理解、对比总结和易错点拆解来介绍Java语法的特性,旨在帮助零基础或是有像C语言之类编程语言基础的人群快速上手 Java 。


1. 初识Java

1.1 简介

  Java语言起源于1991年4月,其前身 Oak 由Sun公司 James Gosling 博士领导的 Green Project 计划研发,但项目初期发展受阻一度停滞,但是随着1995年互联网的发展,Sun公司看到了Oak 在互联网的前景,于是经过改造,在1995年5月以 Java 的名称正式发布,命运的齿轮开始转动

## 附图James Gosling帅照


1.2 Java的使用场合

  Java作为当下主流的编程语言之一,具备一系列计算机软件和规范形成的技术体系,这个技术体系提供了完整的用于软件开发和跨平台部署的支持环境,因此得以广泛使用在各种场景。

## 例如老少皆宜的联机沙盒游戏MC也有Java的身影


1.3 java语言的特性

## 核心体现于 “Write once,Run anywhere”,这里仅挑出一部分做解释

1.3.1 简单性

  java语法相当于C++语法做减法之后的“纯净版本”,例如:Java中没有head文件、指针运算(指针语法)、联合等等。此外,Java开发环境还远远超出大多数其他编程语言的开发环境,即:Java 配套开发体系、工具、库、框架、部署环境,规模与完备度远超多数编程语言

1.3.2 面向对象(OOP)

在Java的世界中,一切皆对象。Java的面向对象特性与C++旗鼓相当,区别在于C++侧重继承,而Java则是更简单的接口概念。不仅如此,Java还具备更优秀的运行时自省功能,能主动探查自身类、方法、属性、注解等内部结构,还能动态调用、创建对象,Java 该能力表现出众。

1.3.3 分布式(微服务)

Java
有丰富的例程库,用于处理像
HTTP

FTP
之类的
TCP/IP
协议。
Java
应用程序能够通过
URL
打开和访问网络上的对象,其便捷程度就好像访问本地文件一样

1.3.4 多线程

Java
在当时很超前。它是第一个支持并发程序设计的主流语言。多线程可以带来更好的交互响应和实时行为。并发 程序设计绝非易事,但是Java
在这方面表现出色,可以很好的管理这个工作。

1.3.5 体系结构中立

编译器生成一个体系结构中立的目标文件格式,按照该种规范生成的文件,只要有
Java
运行时系统,这些编译后的代码就可以在许多处理器上运行。Java
编译器通过生成与特定计算机体系结构无关的字节码指令来实现
这一特性。
精心设计的字节码不仅可以很容易的在任何机器上解释执行,而且还可以动态地翻译成本地机器代码。这就是为什么可以:“Write once

Run anywhere”。

此外

健壮性、可移植性、解释性、高性能、安全性、动态性 这里就不一一介绍了,有兴趣可以自行了解


2. 编写第一个Java程序

2.1 初识Java的main方法

如上就是在IDEA上创建的第一个Java程序:完成“hello world”打印,通过上述代码,我们就能了解一个完整的Java程序的结构

主要由以下三部分组成:

源文件(.java后缀的文件):源文件带有类的定义,这里的“Demo”就是一个类,而类的内容必须包含在花括号内

:关键字为class,类中带有一个或者多个方法,方法必须声明在类的内部

方法:在Java中的方法类似c语言中的“函数”,在方法的花括号中编写相应的功能

注意:

① public class 是固定写法,一个源文件里只能有一个被public修饰的类,并且这个类的类名与源文件的文件名相同

② 方法是不能游离于类外的

③ 统一书写规范:类名以大驼峰的形式(单词组合,每个首字母大写)书写;左括号一定是在当前行的

回到main方法上,main也是一个方法,它是一个程序的入口,而这里的 System.out.println()则是执行打印操作的方法,类似C语言中的printf函数,除此之外还有一些看似陌生的关键字以及语法书写格式,在这里鄙人先简单做个介绍,大部分都会在后续为大家讲解到

public: public 属于权限修饰符

static: 静态修饰

String: 字符串引用数据类型

String[] arg: 这个是Java中数组语法书写格式,区别于C语言,以创建一个int的数组

// C 创建一个数组
int arr[/*数组长度*/]
//java 创建一个数组
int[] arr

在这里就表示一个字符串类型的数组,负责接收运行时的命令行参数


2.2 运行Java程序

首先要引入JVM,JRE,JDK三个概念

JVM(Java Virtual Mechine)java虚拟机,是运行 Java 代码的虚拟容器软件,其底层核心是由C/C++实现的;

JRE(Java RunTime Environment)java运行环境:包含了JVM,Java基础类库;

JDK(Java Development kit)java开发者工具:包含了JRE,同时还包含了编译器javac和调试工具Jconsole、Jstack等

三者之间的关系:

JDK = JRE + 开发工具集

JRE = JVM + JavaSE标准类库

## 包括后续经常使用的一些受关键字native修饰的java方法,也是由C/C++实现,native 就等价于Java 调用 C/C++ 底层代码的标记

接下来,将通过cmd(命令行) + 记事本的方式,逐步演示程序执行过程:

## 这里要事先安装好IDEA并配置好环境变量,就不在此处作相关演示,to be continued……

过程图解:

注意:

可能javac第一次执行含中文字符的.class文件可能会出现这种情况,此时我们在执行javac时加上

 -encoding UTF-8 即可;


2.3 注释

Java的注释分为三种:单行注释、多行注释、文档注释

//这是单行注释

/*这是多行注释*/

/**这是文档注释
*/


2.4 标识符

在程序中由用户给类名、方法名或者变量所取的名字,标识符中可以包含字母、数字、下划线和$符号等

注意

标识符不能关键字不能以数字开头且严格区分大小写

② main 也可以作为标识符


2.5 关键字

这里只罗列出常见的关键字

用于定义访问权限修饰符的关键字 private protected public default
用于定义类,函数,变量修饰符的关键字 abstract final static synchronized
用于定义类与类之间关系的关键字 extends implements
用于定义建立实例及引用实例,判断实例的关键字 new this super instanceof
用于异常处理的关键字 try catch finally throw throws
用于包的关键字 package import
其他修饰符关键字 native strictfp transient volatile assert

3. 数据类型与变量

3.1 字面常量

字面常量就是程序运行时期,固定不变的量,例如

System.out.println("hello world");
System.out.println(110);
System.out.println(true);
System.out.println(false);
System.out.println('A');
System.out.println(3.14159);

这里的“hello world”,110,true/false,‘A’,3.14159 都是字面常量

这里要引入一个关键字 final,在这里做一个简单的介绍后续也会不断提及它

被final修饰的变量也可以称为常(变)量,不能在对它进行修改(一般大写)

推敲final修饰变量

基本数据类型:final修饰基本数据变量时,变量栈内存直接存真实数值,final 限制这块内存里的数据不可改写,没法重新赋值。

引用数据类型:引用类型,变量栈内存存放对象堆内存地址,final 锁住这个地址值,不能更换指向,对象内部数据不受约束,即:引用指向的对象可以改变,但是这个引用不能变,因为引用是存储的地址final限定的是地址。

## 引用类型就先做了解,后续会再介绍


3.2 数据类型(8种)

在Java中的数据类型主要分为:基本数据类型和引用数据类型

3.2.1 基本数据类型(关键字-字节大小):

整型(byte-1、short-2、int-4、long-8)、浮点型(float-4、double-8)、字符型(char-2) 和 布尔类型(boolean-没有明确规定)

注意:

不论是多少位的系统,Java中的数据类型占用的字节数是固定的—-可移植性高

3.2.2 引用数据类型 :

数组、类、接口、String


3.3 变量

3.3.1 语法格式:

数据类型 变量名 = 初始值

这里引入包装类的概念,具体也是下文分解。通过包装类我们就可以通过调用包装类的方法获取基本数据类型的字面赋值范围

例如:int的包装类就是Integer

代码示例

System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);

运行结果

注意:

① 赋值不能超过类型能表达的范围

② 变量在使用之前必须初始化

③ 在Java中是不认“0”为false,“非0”为true的用法,Boolean就只有两个取值,true或false;

④ 像long 、float这种数据类型可以在初始值加‘L’、‘F’(小写亦可)来标识类型;

⑤ 小数是不能精确表示每一位的,它只能保证小数点后几位且符合IEE754标准;

⑥ 在计算机中字符本质就是一个整数,在C语言种ASCII表示字符,char的大小只有1个字节;但是在Java中使用Unicode表示字符,因此一个字符占用两个字节,并且还能表示中文;

## 问题提出:大家经过或多或少的编程语言学习,试问一个汉字占几个字节呢?

答:没有明确大小

其中,gbk是Windows中文默认(如:CMD、记事本),utf-8是现代互联网、跨平台、网页常用的编码格式,Unicode则是Java中使用的,而strlen则是C语言中计算字符串长度的函数。

事实上,基本数据类型的包装类除了int(Integer)和char(Character)有特殊,其余的包装类都是在基本数据类型关键字名首字母大写就能得到,例如 long(Long)

⑦ Java中浮点型是可以进行取模操作的

float a = 1.7f;
System.out.println(a % 0.5);

3.3.2 类型转换

隐式转换:

int a = 100;
long b = 100;
b = a; //int的数据范围小,编译器会先把a转换成long再赋值给b
System.out.println(3.14); //这里的浮点数类型也是默认double

显式转换:

用数据范围小的接收数据范围大,很有可能导致数据精度丢失,所以会报错,需要将范围大的类型强转;但是,转换完了就高枕无忧了?答案是非也,且看运行结果

int a = 128;
byte b = 127;
//b = a;
b = (byte)a;

## 我们预想的是打印128,可结果却是-128,这是何为?

##  那为什么编译器在运行时做了这个操作,但是没有直接判错,反而多此一举?

答:常量的值是在编译的时候确定的,而变量的值是在运行时确定的,要硬说就是强转只能关闭编译期范围检查,无法改变运行时截断溢出的行为。

所以强制类型转换一般是人为操作,即:已经预先考虑其潜在风险

3.3.3 类型提升

不同类型的数据之间相互运算时,数据类型小的会被提升成数据大的类型;

1.int 和 long

2. byte 和 byte


3.4 字符串类型

## 这里只做引入,后续会再详细介绍

3.4.1 '+' 表示拼接

使用'+'可以对字符串进行拼接

注意:在Java中没有以\\0作为字符串结尾的说法

这里就需要好好考量一番了:

只要参与运算的任意一方是字符串,'+'就会变成字符串拼接运算符,依次把其他所有类型都转成字符串拼接;

只有参与运算的双方都是数据类型时,'+'才会执行算术加法;

拼接小技巧:

根据这个特点我们就可以在一些数据类型后面拼接一个空字符串将其转化为String类型

3.4.2 字符串和整型数字之间的转换

1.int 转 String

2.String 转 int


4. 运算符

对操作数进行操作时的符号,不同的运算符操作的含义不同

4.1 算术运算符

4.1.1 加减乘除取模(+、-、*、/、%)

## 这里与数学上的运算无二般,仅作编程语言上的规则介绍

1. int / int 结果类型还是 int ,并且值会向下取整

代码示例:

System.out.println(5 / 2);
System.out.println(1 / 2);

运行结果:

## 整型整型,当然是要取整数部分

2.除法和取模(求余数)右操作数不能为0

3.除了整型,在Java中double类型也可以进行取模

4. 两次操作数类型不一致时,会向类型大的提升


4.1.2 增量运算符(+=、-=、*=、/=、%=)

该类运算符会将操作完成后的值赋给左操作数

代码示例:

int a = 10;
a += 10; //等价于 a = a + 10
a -= 10; //等价于 a = a – 10
a *= 10; //等价于 a = a * 10
a /= 2; //等价于 a = a / 2
a %= 10; //等价于 a = a % 10

注意:

① 只有变量才能使用该运算符,字面常量不能

② 增量运算符会自动执行类型提升


4.1.3 自增/减

'++'是给变量+1,'–'是给变量-1,使用时注意是前置还是后置即可

单独使用,前置自增/减和后置没区别

混合使用,前置是先执行运算,后取变量的值;后置则是先取变量的值,后执行运算

代码示例

int a = 10;
a++;
System.out.println(a);//11
++a;
System.out.println(a);//12
System.out.println(++a);//13
System.out.println(a++);//13
System.out.println(a);//14

运行结果

## 可以理解成在每行代码执行的时候,运算符单独使用时,这一行代码走完就将运算执行完毕了;而混合使用时是看这行代码先遇到的时变量还是运算符了,前置就是先遇到运算符就先执行运算,后置就是先遇到变量就先取变量的值;


4.2 关系运算符

等于( == ) , 不等( != ),  大于( > ) ,  小于( < ),  大于等于( >= ),   小于等于( <= ) ;结果为boolean类型

注意:

关系运算符是不能连着写的,在这里Java程序是有别于数学的

不妨看这样一个代码:

int a = 10;
System.out.println(3 < a < 5);

事实上在它在Java中出现时,连编译都不能通过

## 一对比就一目了然,在Java与C的区别,这也是初学者容易混淆出错的地方


4.3 逻辑运算符

## 这里的表达式都是boolean表达式

逻辑与( && )

表达式1 && 表达式2 : 表达式全为true时,整个结果才为true,反之为false;

逻辑或( || ) 

表达式1 || 表达式2:表达式全为false时,整个结果才为false,反之为true;

逻辑非( ! ) 

! 表达式 : ! true == false , ! false = true;

短路求值

## 主打一个能不算就不算

逻辑与左边为false时就不算右边表达式;

逻辑或左边为true时就不算右边表达式;

代码示例

System.out.println(10 > 20 && 10 / 0 == 0); //打印false
System.out.println(10 < 20 || 10 / 0 == 0); //打印true

运行结果

注意:

& 和 |如果表达式为boolean也表示逻辑运算,但是它们不支持短路求值

System.out.println(10 > 20 & 10 / 0 == 0);
System.out.println(10 < 20 | 10 / 0 == 0);

这里就会抛出异常了


4.4 位运算符

首先要明确一点,在Java中数据存储的最小单位时字节( byte ), 但是数据操作的最小单位是比特位( bit ) ,1个byte是由8个二进制bit组成,而位运算主要有四种 &    |   ^    ~

## 除了 ‘~ ’是一元运算符,其他全是二元运算符

按位与 & :

如果两个二进制位全为1时结果才取1——–有0为0,全1为1

按位或 | :

如果两个二进制位全为0时结果才取0——–有1为1,全0为0

按位异或 ^ :

如果两个二进制位不相等为1,相等为0——–相同为0,相异为1

代码示例

int a = 10;
int b = 20;
System.out.println(a & b); // 0
System.out.println(a | b); // 30
System.out.println(a ^ b); // 30

图解
按位取反 ~ :

该二进制位上为0就取1,为1取0

注意:

① & 和 | 的操作数是Boolean时,可以表示逻辑运算;

② n ^ n = 0, 0 ^ n = n;

## 根据②特性,我们就可以想想是不是可以用在查询上?试试下面的OJ

136. 只出现一次的数字 – 力扣(LeetCode)


4.5 移位运算符

操作的也是二进制比特位( bit )

左移 << :

最左侧位舍弃, 最右侧位补 0;相当于原数字 × 2的N次方

右移 >> :

最右侧为舍弃,最左侧为补符号位(正数补 0,负数补 0);相当于原数字 / 2的N次方

无符号右移 >>> :

最右侧为舍弃,最左侧为补 0


4.6 条件运算符

语法:表达式1 ? 表达式2 : 表达式3

使用规则:当表达式1为true,整个表达式的值为表达式2的值;如果表达式1为false,整个表达式的值为表达式3的值;

我们不妨来看看这样一个条件运算符

boolean flg = true == true ? true == false ? false :true :false;
System.out.println(flg);

图解:

## 在这里可以先找到最先出现的'?'和最后出现的':',然后再慢慢分析;


4.7 运算符优先级

运算符之间是存在优先级的,不过具体的规则不用去了解,在模棱两可的地方使用()即可

示例:


5. 逻辑控制

逻辑控制就是顺序中夹杂着循环

5.1 顺序结构

这个就比较简单了,即按照代码书写的循序一行一行执行


5.2 分支结构

5.2.1 if 语句

语法格式

语法格式1

if (布尔表达式){
//执行语句
}

语法格式2

if (布尔表达式){
//执行语句1
}else{
//执行语句2
}

语法格式3

if (布尔表达式){
//执行语句1
}else if (布尔表达式){
//执行语句2
}else{
//执行语句3
}

执行流程

满足某一个布尔表达式(为 true 时)就执行对应{}中的语句,反之就不执行,程序向下执行;

## 不同的if语句是不能同时执行的

代码示例

int a = 10;
if(a > 9){
System.out.println(a + " > 9");
}
if(a > 11){
System.out.println(a + " > 11");
}else{
System.out.println(a + " < 11");
}
int b = 10;
if(a > 20){
System.out.println(b + " > 20");
}else if(b > 15 ){
System.out.println("15 < " + b + " < 20" );
}else if(b > 11){
System.out.println(" 11 < " + b + " < 15" );
} else{
System.out.println(a + " < 11");
}

运行结果
注意:

① 分号问题

由于if()后多写了一个分号,就导致{}中的代码块与if无关了

② 悬垂else问题

这种情况就不知道else该和哪个if语句匹配了,这也是很不规范的写法,所以这里推荐每个if-else语句都要加上{}以避免报错,毕竟被这种错误困扰是很受打击的

③ 方法中if-else语句的全覆盖

## 这个是if-else语句在定义有返回值的方法时常常会忽略的问题

在 Java 中,有返回值的方法( 函数 )必须保证所有可能的执行路径,最终都能通过return语句返回(唯一)一个对应类型的值;

func2:如果a >= b,方法会直接return a;如果不满足a >= b,程序会跳过if语句,执行后面的return b。无论哪种情况,方法都一定能返回值,编译器完全放心。 func3:当a < b时,程序会直接跳过if块,而方法结尾没有任何return语句。编译器会识别出 “存在一条无法返回值的路径”,直接报错。 func1:它和func2本质上是等价的。因为if-else结构对条件做了全覆盖,要么进入if分支,要么进入else分支,没有任何遗漏的情况。编译器确认了 “所有路径都有返回值”,所以也不会报错。


5.2.2 switch 语句

语法格式

switch(表达式){
case 常量值1:{
语句1;
[break;]
}
case 常量值2:{
语句2;
[break;]
}
default:{
内容都不满足时执行语句;
[break;]
}
}

执行流程

1.先计算表达式的值;

2. 和case依次比较,一旦有响应的匹配就执行该项下的语句,直到遇到break时结束;

3. 当表达式的值没有与所列项匹配时,执行default

代码示例

int a = 1;
switch (a){
case 1:
System.out.println("a = " + a);
break;
case 2:
System.out.println("a = " + a);
break;
default:
System.out.println("a = " + a);
break;
}

运行结果

注意:

① 多个case后的常量值不可重复

② switch括号中的只能是一下类型的表达式:

        基本数据类型:byte、char、short、int,不能是long、float、double、boolean类型

        引用数据类型:String常量、枚举类型

③ break不能漏掉,否则会失去"多分支选择"的效果

从这里就能看出来,从case语句匹配到a之后开始的所有case语句都执行了,就是因为没有break;

④ swith不能表示非常复杂的条件


5.3 循环结构

5.3.1 while

语法格式:

while(循环条件){
//循环语句
}

代码示例

//打印5-1
int a = 5;
while(a != 0){
System.out.print(a + " ");
a–;
}

运行结果

## 前面提到的是println而这里是print,区别在于'ln',其实这个相当于'\\n'也就是换行,这与C相向,甚至传统C中的printf也是可以在Java中使用的

注意:

while括号后面不要写分号,否则可能会导致死循环


5.3.2 for

语法格式

for(表达式①;布尔表达式②;表达式③){
//表达式④
}

表达式①:  初始化循环变量,在循环的最初执行且只执行一次

布尔表达式②: 循环条件,满足就循环继续执行,否则循环结束

表达式③: 循环变量更新

表达式④: 循环体

也就是满足判断条件后,执行循环体,最后更新循环量,进入下一次循环的条件判断

代码示例

for(int i = 1;i <= 5;i++){
System.out.print(i + " ");
}

运行结果


5.3.3 for-each

这种循环方式主要运用在遍历数组当中

语法格式

for (数组类型 变量名 : 数组名){
System.out.println(变量名);
}

演示

## 使用for-each遍历就会使遍历操作更加简洁了,不过这个遍历的弊端就很明显了,它无法获取数组元素的下标


5.3.4 do while

这个循环会先执行循环体一次在进行正常的循环条件判定、执行循环体的过程

语法格式

do{
循环体
}while(循环条件);

注意:

do while 循环括号后的分号不能省略


5.3.5 break 和 continue

break:  让循环/switch提前结束

for(int i = 0;i < 100;i += 9){
if(i == 45){
break;
}
System.out.print(i + " ");
}

运行结果

contine:  跳过本次循环,立即进入下一次循环

for(int i = 0;i < 100;i += 9){
if(i == 45){
continue;
}
System.out.print(i + " ");
}

运行结果


5.4 输入输出

5.4.1 输出到控制台

前面已经出现过很多次,只不过没有统一介绍

语法格式

System.out.println("输入一个字符串");

System.sout.print("输入一个字符串不换行");

System.out.printf(format,msg);  //  格式化输出

代码示例

System.out.println("这是一个字符串");

System.out.printf("%s","这也是一个字符串");

运行结果


5.4.2 从键盘输入

要使用 Scanner 读取从键盘输入的字符串 / 整数 / 浮点数

导包

import 类似C语言中的include head文件,这里只做引入,后续会具体介绍

import java.util.Scanner;

## 为什么要导包?

答:在 Java 中,我们使用包(package)来管理类,而类中又封装了各种属性与功能方法。包的作用是避免类名冲突,并对类进行分类存放、统一管理,让程序结构更加清晰。而导包(import)的本质,就是告诉编译器接下来要使用的这个类,具体位于哪个包下。只有明确了类的位置,编译器才能准确找到并加载它。

创建Scanner 对象读取输入

Scanner 变量名 = new Scanner(System.in

具体的读取操作就是对应变量的类型调用 .next() 方法

代码示例

Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
System.out.println("a = "+a);
float b = sc.nextFloat();
System.out.println("b = "+b);
String s = sc.next();
System.out.println("s = "+s);
sc.close();

运行结果
循环接收

.hasNext() 方法

代码示例

int sum = 0;
int num = 0;
Scanner sc = new Scanner(System.in);
while(sc.hasNextInt()){
sum += sc.nextInt();
num++;
}
System.out.println("sum = " + sum + ",num = " + num);

运行结果

注意:

循环读取时,可以使用 Ctrl+D 或者 Ctrl+Z 的形式来停止循环

② 使用完Scanner 要调用 .close () 方法关闭Scanner

## 不调用也行,程序彻底结束退出时,系统会统一回收所有占用资源;其次日常使用的Scanner占用资源并不多,不会造成太大影响

.next().nextLine() 都可以读取字符串,但是使用.nextLine()容易踩坑,在这里就做个总结

.next() :  读取单个单词,遇到空格、Tab、换行符就停止

代码示例

Scanner sc = new Scanner(System.in);
String s = sc.next();
System.out.println("s = " + s);

运行结果

这里就能看出,输入"hello world"(不是"helloworld"中间有空格),而.next() 读到空格就结束了;

.nextLine() 读取整行输入,遇到换行符 \\n 才停止;容易踩坑就是因为它很容易读到上一次的换行或者回车,并且认为已经读到了整个字符串;

代码示例

Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
System.out.println("a = "+a);
float b = sc.nextFloat();
System.out.println("b = "+b);
String s = sc.nextLine();
System.out.println("s = "+s);
sc.close();

运行结果

如图所示,还没有输入s的值 .nextLine()  的读取已经结束了,就是因为读到的上有一条代码在输入数据时的回车

不过也很好解决,只需要.nextLine() 把放在输入时的第一行,或者.nextLine() 放在中间,又或者是酌情地使用.next() 都可以很好的避免这个问题.


6.方法

方法就是一个代码片段,类似C语言的"函数",其存在的意义旨在拆分代码实现复用,让程序条理清晰、方便修改维护。

## 直接调用方法执行某个功能,比自己实现来的快

6.1 方法定义

6.1.1 语法格式

修饰符 返回值类型 方法名([参数类型,形参名]…){

        方法体代码

        return 返回值

}

例如:定义一个add函数,返回两个整数的和

代码示例

public static int add(int a, int b){
return a + b;
}

运行结果

## 原因就是main是受static修饰的,所以要在main中调用类中定义的方法也也必须是受static修饰的,详情后续分解


6.1.2 方法调用的执行过程

调用方法–>传递参数–>找到方法地址–>执行被调用方法的方法体–>被调方法结束返回–>回到主调方法继续向下执行


6.1.3 形参和实参

形参的改变不会影响到实参

如图所示:

## 现阶段究其原因就是在Java中拿不到栈上的局部变量的地址,想要"硬改"实参的值就要使用引用类型,这里就引入一下这个概念,后续会详细介绍(使用引用,可见本篇7.2.2、7.2.3)

注意:

① 方法名采用小驼峰命名,单词组合第一个单词首字母小写,之后的首字母大写

② 在Java中方法不能游离于类外

class 类名{

        //方法定义

}

③ 在Java中,方法不能嵌套定义

④ 没有返回值的方法,返回值为 void 且一定不能省去

⑤ 在Java中,实参的值永远是拷贝到形参中,形参和实参本质是两个实体,并且在方法执行完后会对其内部局部变量进行内存的回收,即:形参会被销毁

## 拷贝拷贝拷贝!!!!!!


6.2 方法重载(overload)

在自然语言中常会出现一词多义的现象,比如:"好人"

       AND       

在Java中,如果多个方法的名字相同,但是参数列表不同,则称这几种方法被重载了

试想这样一个场景,用户实现了一个方法add()来计算两个int类型的和,但是这时候想让你在实现类似的逻辑,不过是让你去计算两个float类型的和、或者两个long类型、shortbyte….这个时候大家可能会想到依葫芦画瓢给add改名改参数和返回值就能很好的区分,这的确可以解决问题,但是不高效,在真实的开发环境下,试问用户能记得那么多的方法名吗?

这里破而后立,方法重载就能会好的规避这种冗余问题


6.2.1 概念

在Java中,如果多个方法的名字相同,参数列表不同,则称这些方法被重载了

如上图,Math是Java提供的工具类用于执行数学运算,此处调用的max()方法就有不同参数的情况,也就是说这些max()构成了重载,以应对实际多样的使用需求。

重载实现

以add()的重载为例

//参数不同
public static int add(int a, int b){
return a + b;
}
public static double add(double a, double b){
return a + b;
}
//参数的数量不同
public static int add(int a, int b, int c){
return a + b + c;
}
//参数类型的顺序不同
public static int add(int b,int a,double c){
return 1;
}
public static int add(int b,double c,int a){
return 1;
}

运行结果

注意:

重载的要求是:方法名相同,参数不同【类型,顺序,数量】,对返回值是没有要求的。

##  毕竟重载的核心目的,就是为了处理不同条件( 参数 )下的业务逻辑;方法内部的具体功能、处理后想要得到的结果( 返回值 ),都是根据实际场景灵活决定的。


6.2.2 方法签名

为什么一个作用域中允许存在两个同名的标识符,如前面提及的重载方法,方法名就是相同的?

答:就是Java中存在方法签名这个概念

表现形式:方法全路径名 + 参数列表 + 返回值类型,这才构成一个方法完整的名字;

如图我们就能清楚地看出,看似方法名一样实则底层逻辑早已区分开


6.3 递归

6.3.1 概念

一个方法在执行过程中调用了自身,就称为“递归”,相当于数学上的“数学归纳法”,有一个起始条件和一个递推公式;

6.3.2 递归执行过程分析

解决递归问题:

① 递推公式,通常需要用户推导

② 找到当前问题的递归“结束条件”,这个结束条件就等同于起始条件

 例如:用递归求N !,递归到最后就是N == 1的时候,而数学上求N!就是从 N == 1开始的

图示:

## 如果没有结束条件就会导致栈上不断地为方法开辟空间最终导致栈溢出( StackOverflow )

递归例举:

求N!

代码示例

public static int Func(int n){
if(n == 1) return 1;
return n * Func(n-1);
}

图解

递归最大的难点:

可以根据例举看得出来,找到了递推公式和结束条件,递归的代码将变得相当简洁,逻辑也十分简单,但是往往这也是我们需要花一定时间去思考的难点。

但是,真正难的地方在于在脑海里展开代码,建议大家遇到递归想不通的时候多画图、多分析,

在学习过程中,不宜妄自菲薄,也勿要刚愎自用,不卑不亢反而行远。

## 随手画的一些简单图案有缓解大脑思维疲劳的作用


7.数组

7.1 数组的基本概念

数组就是相同类型元素地一个集合,在内存中是一段连续的空间;

7.1.1 数组的创建及初始化

动态初始化

在创建数组时,直接指定数组中元素的个数

//动态初始化
T[] 数组名 = new T[数组长度];

T :表示数组中元素的数据类型

T[]:表示数组类型

new 关键字:创建一个引用对象;这里先引入,后续再作介绍,这里只需要明确数组在Java中是一个引用数据类型

代码示例

//动态初始化
int[] arr1 = new int[10];

静态初始化

在创建数组时不直接指定数据元素的个数,而是直接将具体的数据内容进行指定

//静态初始化
T[] 数组名 = {data1,data2,data3…};
T[] 数组名 = new T[]{data1,data2,data3…};

其中第一种写法是对第二种写法的简写

代码示例

//静态初始化
int[] arr2 = {1,2,3,4,5};
int[] arr3 = new int[]{1,2,3,4,5};

注意:

① 动态初始化时就不能将具体的数据内容进行指定给数组

② 动态和静态初始化都能分成两步,但是不能省略格式

③ 在动态初始化的数组还没有设置数组元素时,这时Java会根据数组的类型设置默认值;

## 关于Java中的默认值

基本数据类型:0 值或者 false

引用数据类型: null

明确一点只有成员变量( 类中,方法外定义的属性 )有默认值,局部变量( 方法、代码块、for 循环内定义的变量 )没有


7.1.2 数组的使用

数组中元素的访问

可以通过 数组名[下标值] 的方式访问数组中的元素

可以通过 数组名.length 方式获取数组的长度

需要注意的是,数组的下标是从0开始计算的,所以数组最后一个元素的下标值 = 数组长度 – 1

遍历数组

循环遍历 :for and for-each 

int[] array = new int[]{1,2,3,4,5};
System.out.println(array.length);
for (int i = 0;i < array.length;i++){
System.out.print(array[i] + " ");
}
System.out.println();
for (int x : array){
System.out.print(x + " ");
}

使用Arrays工具类方法 : .toString()

首先要导包

代码示例

int[] array = new int[]{1,2,3,4,5};
System.out.println(Arrays.toString(array));

运行结果 

让我们仔细研究一下这个方法

## 可以得知这个方法的返回值是String类型,那么打印的内容本质就可知是一个字符串,那么我们就能知道Arrays的.toString()其实是将数组的内容转成了一个字符串,然后打印出来了


7.2 数组是引用类型

7.2.1 JVM的内存分布

这里只是简单介绍

本地方法栈(Native Method Stack): 会执行一些底层是由C/C++代码实现的方法,如被native修饰的方法,讲究的是快、高效

Java虚拟机栈(VM Stack):就是我们在学习Java过程中不断提及的“栈”,是方法运行的位置

## 本质是存储的栈帧,而一个栈帧就对应着一个方法

堆( Heap ):Java 里用来存放所有 new 出来的对象的地方

7.2.2 变量的区别

存储的区别:

基本数据类型:基本数据类型创建的变量称为基本变量,该变量空间中直接存放的是其所对应的值

引用数据类型:引用数据类型创建的变量称为对象的引用,该变量空间存储的是这个引用所指向的对象在堆上的地址

如图,我们就能直观的看出基本数据类型和引用数据类型之间的区别

操作的区别:

这时候我们就能回到那个形参和实参的问题了,如果方法参数传递的是基本变量,那形参的改变根本不会对实参有任何影响;但是如果方法参数传递的是引用变量,就有影响了。

这里还是举例为大家讲解:交换两数的值(创建临时变量tmp来达到中转的效果)

a.用方法参数传递的是基本变量:

效果很明显,原因也无需多言

b.用方法参数传递的是引用变量:

这就达到了我们想要的结果了

总结:  

基本数据类型变量直接操作内存中存储的数值,仅作用于自身内存空间,无法获取其他基本变量的内存地址。这也是基本类型的形参和实参相互独立、互不影响的根本原因:无法通过地址访问并修改对方内存中的数据。

② 引用变量存的就是其指向的对象的地址,它就能通过地址来操作对象了

那什么是地址? 什么又是对象?

答:首先得明确内存和地址的关系,内存如同整栋旅馆,地址就是唯一的房间号,凭借房间号就能精准找到对应的房间并开展操作,Java中的引用地址作用,这和 C 语言里的指针逻辑相似。

如图:

## 对象就先当作引入,后续会一一道来,这里大家只需要知道只要引用变量不为 null 就必然对应堆中的一个对象。日常代码中我们看似操作引用变量,本质都是借助引用这个媒介,去访问、修改对象的成员属性与行为。


7.2.3 再谈引用类型

多个引用可以指向一个对象
1.通过直接赋值

可以看出我们通过引用操作的是 new 后面的对象,将 arr2 的值赋给arr1,本质就是让arr1不在指向原来指向的对象转而指向arr2指向的对象,这时操作arr1或是arr2都可以对该对象进行操作;

2.通过方法传参

执行过程:

main方法中创建数组引用array,将该引用作为实参调用func方法,并定义同类型数组引用ret接收方法返回值。func的形参会得到原引用拷贝的副本,该副本与main中的array指向堆中同一个数组对象。方法内先对数组元素执行翻倍操作,再新建一个数组对象存储最终结果并作为返回值返回。当func执行结束,对应栈帧被回收,方法内所有局部引用变量随之销毁。

一个引用不能指向多个对象

我们前面有讲到,方法参数传递引用就可以实现通过形参对实参的修改,但本质是通过不同的引用去找到同一个对象并执行了相关的操作;

再来看下面两种情况:

可见func1方法就实现了对实参array的修改

但是func2方法就没有实现预期的效果,两个方法的逻辑也确实没有什么区别,真正的问题出在了方法内部形参上

总结:

new 就会创建一个新对象,而同一个引用是不能同时指向多个对象的

## 引用很专一;对象持有多个引用的选择权


7.2.4 认识NULL

null在Java中表示“空引用”,也就是一个不指向任何对象的引用;

注意:

null 的作用类似C语言中的空指针,都表示一个无效的内存位置。因此不能对这个内存地址进行读写操作,反之则会抛出 NullPointerException;当抛出这个异常时,就需要用户找到这行异常代码,然后判断这个引用为什么是空的

## 如果用Java写数据结构会很容易遇到它,那时需要好好调试一番了

② Java中并没有约定 null 和 0 号地址有的内存有任何关联


7.3 数组的练习

这里对数组的使用场景做一个汇总,方便大家整理数组的相关知识;

7.3.1 数组转字符串

代码示例

int[] arr = {1,2,3,4,5,6};
System.out.println(Arrays.toString(arr));

运行结果


7.3.2 数组拷贝

代码示例1:引用同一个数组

int[] arr = {1,2,3,4,5,6};
System.out.println(Arrays.toString(arr));
//直接创建一个相同类型的引用变量来接收
int[] newArr1 = arr;
System.out.println(Arrays.toString(newArr1));

代码示例2:使用Arrays.copyOf()

Arrays.copyOf(数组名,拷贝长度),默认从要拷贝的数组0下标起始拷贝

int[] arr = {1,2,3,4,5,6};
//使用.copyOf()方法
int[] newArr2 = Arrays.copyOf(arr,arr.length);
System.out.println(Arrays.toString(newArr2));

代码示例3:拷贝指定范围Arrays.copyOfRange()

Arrays.copyOfRange(数组名,From:, To: ),指定要拷贝的起始位置和终止位置

int[] arr = {1,2,3,4,5,6};
//拷贝指定区间
int[] newArr3 = Arrays.copyOfRange(arr,1,4);
System.out.println(Arrays.toString(newArr3));

代码示例4:数组的扩容

还是使用 Arrays.copyOf(),只是拷贝的长度有所不同

//.copyOf() 还可以用来原数组的扩容
int[] arr2 = new int[5];
System.out.println(Arrays.toString(arr2));
arr2 = Arrays.copyOf(arr2,arr2.length*2);
System.out.println(Arrays.toString(arr2));

运行结果:

我们来推敲一下Array.copyOf()这个方法,鼠标左键 + Ctrl 就能跳转这个方法在Arrays中的实现

通过层层跳转,我们能发现,Array.copyOf()的底层是由arraycopy()这个方法实现的,而这个方法又受native的修饰,说明数组拷贝本质是 C/C++ 实现的

src:原数组

srcPos: 原数组位置

dest:目标数组

destPos: 目标数组位置

length :拷贝长度

##  Arrays.copyOf() 本质上是:先创建目标数组,再调用 JVM 底层用 C/C++ 实现的 System.arraycopy() 完成高效内存拷贝,Java 层只是做了一层封装和安全检查。


7.3.3 数组逆置

代码示例

//交换方法
public static void Swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void reverse(int[] arr){
//两两交换,那只需要交换n/2次就完成(n为数组长度)
for (int i = 0;i < arr.length / 2;i++){
Swap(arr,i,arr.length-1-i);
}
}
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
reverse(arr);
System.out.println(Arrays.toString(arr));
}

运行结果


7.3.4 查找数组中的元素

顺序查找:

通过遍历整个数组元素的方式,来查找目标元素

代码示例

int[] arr = new int[]{1,2,3,4,5,6,7,8,9};
//顺序查找
for (int i = 0; i < arr.length; i++) {
if(arr[i] == 5){
System.out.println("5的下标是"+i);
break;
}
}

运行结果

二分(折半)查找:

二分查找适用于有序数组,核心思路是每次将查找区间对半拆分,逐步缩小范围,最终找到目标数据,又叫折半查找。

代码示例

//二分查找
public static void binarySearch(int[] arr, int toFind){
int left = 0;
int right = arr.length-1;
while(left <= right){
int mid = (left + right) /2;
if(arr[mid] < toFind){
//在右区间
left = mid + 1;
}else if(arr[mid] == toFind){
System.out.println("find it!");
return;
}else{
//在左区间
right = mid-1;
}
}
System.out.println("can't find it!");
}

public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5,6,7,8,9};

binarySearch(arr,5);
binarySearch(arr,10);
}

运行结果


7.3.5 数组排序

冒泡排序:
代码示例

//升序
public static int bubbleSort(int[] arr) {
int count = 0;
//i表示比较的趟数
for (int i = 0; i < arr.length – 1; i++) {
for (int j = 0; j < arr.length – 1; j++) {
if (arr[j + 1] < arr[j]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
count++;
}
}
return count;
}
public static void main(String[] args) {
int[] arr = {9,8,7,6,5,4,3,2,1};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}

运行结果

确实是达到了我们预期的效果但是让我们思考以下这几种问题:

1.当函数跑完一趟排序,是不是意味着已经有一个数已经有序了?

从运行结果来看,即便有的数据已经有序但还是再比较了一遍

2. 当待排序的数组元素本身已经是有序(升 / 降)的,那还有必要再去一遍一遍地比对吗?

优化

1.每一趟少比较 i 次(因为后面已经有序)

//代码片段
for (int j = 0; j < arr.length-1-i; j++)

2.如果某一趟没交换,直接退出(数组已经有序)

//代码片段
boolean flg = false;
//flg 为false 就说明数组元素没有发生交换,即:数组有序->break
if (!flg){
break;
}

完整代码

//升序
public static void bubbleSort(int[] arr){
int count = 0;
for (int i = 0;i < arr.length-1;i++){
boolean flg = false;
for (int j = 0; j < arr.length-1-i; j++) {
if(arr[j+1] < arr[j]){
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flg = true;
}
count++;
}
if (!flg){
break;
}
}
}

上述的操作实现是为了让大家很好的熟悉数组的使用,在Java中已经封装好了一系列排序的工具方法,在实际开发中调用即可,避免"造轮子"的情况


7.4 二维数组

7.4.1 二维的思想:

如果将一个数组元素视作一个“点”,那么一维数组就是若干个“点”组成的一条“线”,而二维数组就是若干条“线”组成的一个面;二维数组本质还是一维数组,只不过每个元素是一维数组而已; 

                                 二维数组 = 存放 “一维数组” 的一维数组

## 从内存结构来看,二维数组在底层是连续存储的,只是逻辑上被分成一段一段的“子”一维数组


7.4.2 语法格式

数据类型[][] 数组名 = new 数据类型[行数][列数]{ 初始化数据 };

int[][] arr1 = new int[2][2];
int[][] arr2 = new int[][]{{1,2},{2,3}};
int[][] arr3 = {{1,2,3},{1,2,3}};

不规则二维数组

数据类型[][] 数组名 = new 数据类型[N][];

这种设置了行数而没有明确规定列数的二维数组,就可以构造不规则二维数组

代码示例

int[][] arr = new int[5][];
arr[0] = new int[]{1};
arr[1] = new int[]{1,2};
arr[2] = new int[]{1,2,3};
arr[3] = new int[]{1,2,3,4};
arr[4] = new int[]{1,2,3,4,5};
for (int i = 0 ;i < arr.length;i++){
for (int j = 0 ;j < arr[i].length;j++){
System.out.print(arr[i][j] + " ");
}
System.out.println();
}

运行结果

图示:

1. 规则二维

2.不规则二维

7.4.3 二维数组转字符串

同样是使用Array

Array.deepToString(数组名);

代码示例

int[][] arr = new int[2][2];
arr[0] = new int[]{1,1};
arr[1] = new int[]{1,1};
System.out.println(Arrays.deepToString(arr));

运行结果


结语:

本篇 Java 基础内容就分享到这里。个人观点难免有疏漏或理解不到位的地方,欢迎大家在评论区交流指正、一起探讨学习。后续也会持续更新相关内容,共同进步!

赞(0)
未经允许不得转载:171主机测评 » JAVA基础篇——入门知识汇总
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址