跳到主要内容

☀️Java 语言精要

准备

安装 JDK

可以选择到 Oracle 下载官方 JDK,或者安装 OpenJDK,如 Zulu JDK

根据电脑的操作系统下载对应的安装包安装即可。

配置环境变量

Mac 系统在安装JDK时通常会自动配置环境变量,无需我们手动配置。可以使用下面的命令验证:

java -version

如果终端显示 JDK 的版本号等信息,说明 JDK 的环境变量配置是有效的。


下面是 Windows 系统配置环境变量的过程:

首先,找到 JDK 的安装目录,复制 JDK 的 /bin 路径。如下图:

然后,打开环境变量配置窗口,Win10 系统配置环境变量的位置在:右键此电脑 -> 属性 -> 高级系统设置 -> 环境变量。

系统变量中找到 Path 双击或点编辑按钮,在编辑环境变量窗口中新建一个条目,将复制的 JDK 的 /bin 路径粘贴进去,粘贴后点击确定按钮。

环境变量配置好后,可以在 cmd 或者 powershell 通过命令验证是否配置成功。

打开(或重新打开) cmd 或者 powershell,使用 java -version 命令,显示 JDK 版本号的信息说明环境变量配置成功。

安装 IDE

工欲善其事,必先利其器。程序员敲代码也有利器,它就是IDE,中文意思是"集成开发环境"。它是程序员编写代码的重要工具,里面包含了各种开发、调试所需要的工具和插件,可以大大提高开发效率。

目前常用的 IDE 软件有:Jetbrains 的 Intellij IDEA,Eclipse。

这里推荐使用 Intellij IDEA,它相比 Eclipse 更加方便和智能。

点开下载页面会有两个版本可选,前者是终结版(付费的),包含企业级开发所需要都各种插件;后者是社区版(免费的)。初学者可以安装社区版。

认识 JDK

JDK,全称是 Java Development Kit,中文意思就是 "Java 开发工具包",它是我们开发 Java 程序必不可少的工具。

JDK 中包含了 JRE(Java 运行时环境) 以及 Java 开发中所需要的工具,例如 Java 编译器(javac) 等等。

上面提到了 JRE,JRE 的全称是 Java Runtime Environment,中文意思是 "Java 运行时环境",它内置了 Java 程序运行所必备的环境,如 JVM(Java 虚拟机) 以及 Java 提供好的各种类库。如果我们单单要运行 Java 程序的话,只需要安装 JRE 即可。

最后说下 JVM, 即 Java Virtual Machine(Java虚拟机),它是实现 Java "一次编写,到处运行" 的关键。

我们编写的 Java 代码会通过编译器(javac)编译成符合 JVM 规范的以 .class 后缀的二进制文件,也叫"字节码"文件。在运行 Java 程序时,将编译好的字节码文件加载进 JVM 虚拟机中运行。

这一过程就是实现了 Java 语言的平台无关性,在语言和机器之前抽象出了 JVM(Java 虚拟机) 这一中间层,我们只需面向虚拟机编程,无需考虑对应的平台。从而实现代码的一次编写,随处运行。

关键字和标识符

关键字 是编程中的通用概念,不单单在 Java 语言中。它是指那些在编程语言中赋予了特定含义(用法)的单词,这些单词已经拥有固定的用途。例如, 在 Java 语言中使用 class 关键字声明一个类,使用 int 声明一个整型变量等等。

📢有一点需要注意,Java 语言是 严格区分大小写 的,而 Java 中的关键字都是小写的。例如:class 是关键字,而 Class 却不是关键字。

就目前来说,Java 中有48个关键字,2个保留字,3个特殊的字面值:(随着版本迭代,可能会有调整)

  • 48个关键字: abstract、assert、boolean、break、byte、case、catch、char、class、continue、default、do、double、else、enum、extends、final、 finally、float、for、if、implements、import、int、interface、instanceof、long、native、new、package、private、protected、 public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、void、volatile、while ;

  • 2个保留字(没有被使用的关键字):goto、const ;

  • 3个特殊字面量:true、false、null ;


标识符 指所有能够自己定义名字的地方都叫做标识符。例如:类名、变量名、方法名、接口名、枚举名

标识符的命名要遵循下面的规则:

  1. 标识符只能使用:字母、数字、下划线_、美元符$
  2. 标识符不能以数字开头;
  3. 标识符中不能有空格;
  4. 标识符不能是Java中的关键字或保留字。

以上是语言层面的规则,在实际开发过程中,标识符的命名还有一些约定俗成的规范:

  • 标识符的名称要有意义,尽量做到见名知义,一般使用英文单词,不要使用拼音;
  • 类名、接口名、枚举名使用大驼峰的命名规则(每个单词首字母大写);
    public class ArrayList {}
  • 变量名、方法名使用小驼峰的命名规则(第一个单词首字母小写,其余单词首字母大写);
    String gender = "男";
    String firstName = "李";
  • 常量名遵循 snake_case 规范,单词要大写,使用下划线连接多个单词;
    final String COMMON_KEY_PREFIX = "key";
  • 包名使用域名的反写形式:cn.surcode.entity

变量和常量

变量是计算机中的一个通用概念,一般指存储在计算机内存中的数据。但是如果让我们直接通过内存地址来访问数据的话,显然是不太明智的,因为这明显不是人类能容易做到都事情。

为了让我们更容易的访问计算机内存中的数据,可以通过在编程语言中定义一个变量来访问这一内存,从而获取到内存中的数据。

例如下面这段代码,声明了一个 int 类型变量 a,它的值是 10 。10 实际上存储在内存中,我们在代码中可以通过变量 a 可以读取或者修改内存中的值。

int a = 10;

现在我们已经知道变量是做什么的,那在 Java 语言中如何声明一个变量呢?

定义变量

在 Java 语言中声明变量必须要显式地指定变量的数据类型,就像上面这行代码一样,声明一个 int 类型变量 a,并将 10 这个值赋值给变量 a 。 这一行代码可以看成是两步操作,变量的声明变量的赋值。通常把变量的首次赋值叫做变量的初始化

这两步操作也可以像下面这样分来开做:

// 声明 int 类型变量 a
int a;
// 将 int类型字面值 10 赋值给 a
a = 10;

访问变量中的数据

可以使用变量名访问变量中存储的数据,就像下面这样,在控制台打印变量 a 的值。

System.out.println(a);

📢这里有一点需要注意,局部变量(在方法或者代码块中定义的变量)必须要初始化后才能访问,否则编译器会报错。

变量赋值

变量的值可以被修改,就像下面这样。但是只能为变量赋值相同数据类型的值,因为 Java 是强类型的语言,会在编译时检查变量的数据类型是否合法。

// 声明一个 int 类型变量 a,值为 10
int a = 10;
// 修改变量 a 的值为 20
a = 20;

变量的作用域

变量的作用域可以理解为是变量的生命周期,它的生命周期只在声明它的花括号{}中,出了这个花括号,就无法使用这个变量。具体的效果如下图:

变量根据声明位置不同,分为全局变量局部变量

在类里面、方法或者代码块外面声明的变量属于全局变量(在面向对象中,也叫成员变量),全局变量在整个类内都可以被访问,在没有初始化时有默认值。

在方法或者代码块中声明的变量属于局部变量,局部变量只能在自己的作用域被访问,访问前需要初始化,在离开作用域时,局部变量会被回收。

常量

变量的值可以被修改,而常量初始化后就不能被修改了,通常把一些固定不变、并且需要经常访问的值定义成常量。

声明常量的方式和声明变量类似,只需要在前面加上 final 这个关键字。

final 字面意思表示最终的,也就是告诉 Java 编译器,这个变量无法修改,不能再次被赋值。

final int a = 10;

数据类型

基本数据类型

声明变量时必须指定具体的数据类型,在 Java 中提供了 8 中基本的数据类型,可分为四类:整数类型、浮点数类型(小数值)、字符型、布尔类型。

整型:

数据类型说明内存大小取值范围相当于某些语言的
byte字节1byte-2^7 ~ 2^7-1int8/i8
short短整型2byte-2^15 ~ 2^15-1int16/i16
int整型4byte-2^31 ~ 2^31-1int32/i32
long长整型8byte-2^63 ~ 2^63-1int64/i64

浮点型:

数据类型说明内存大小相当于某些语言的
float单精度浮点型4bytef32
double双精度浮点型8bytef64

字符型:

数据类型说明内存大小
char字符2byte

布尔型:

数据类型说明内存大小字面值
boolean布尔类型1bytetruefalse

类型转换

数据类型之间可以进行转换,例如将 int 类型的数据转换为 long 类型。类型之间的转换有两种形式,隐式类型转换显式类型转换,也叫 自动类型转换强制类型转换

例如下图,当 短类型长类型 转换时,编译器会自动进行转换,这属于 隐式类型转换。当 长类型短类型 转换时需要手动进行转换,这种情况属于 显式类型转换

显式类型转换会存在数据精度丢失的情况,例如 long 类型在内存中占用 8 个字节,而 int 类型在内存中占用 4 个字节,把 long 类型数据转换成 int 类型时,超出的部分会被丢弃掉,所以在使用时需要注意这个问题。

public static void main(String[] args) {
short a = 1000;
byte b = (byte) a;
System.out.println(b); // -24
}

字面值常量

在变量赋值时,可以直接使用字面值赋值给变量。字面值是指是字面意义上的值,例如:101.0'A'true"Hello" 这些整数字面值、浮点数字面值、字符型字面值、布尔型字面值、字符串字面值。

字面值在内存中具有不可变性,无法通过代码来修改已经存在内存中的字面值,但是可以将一个新的值赋值给变量。

字面值具有默认的数据类型,例如整数类型字面值默认是 int 类型,浮点数字面值默认是 double 类型。

public static void main(String[] args) {
int a = 100;
byte b = 100;
}

像上面这段代码,这两个变量声明时编译器处理过程是不同的。第一个是将整型字面值 100 赋值给 int 类型变量 a,因为 100 这个字面值默认是 int 类型,所以是直接赋值。 而第二个是将字面值 100 赋值给 byte 类型变量 b,字面值 100 默认是 int 类型,将 int 类型赋值给 byte 类型变量就会发生类型转换。

按类型转换的机制,将长类型转换成短类型需要显式地进行转换,并且可能发生精度损失。但是对于字面值这种赋值方式,Java 编译器进行了特殊处理,因为编译器知道这个字面值具体是什么, 当字面值在这个变量的取值范围内时,编译器会自动类型转换,如果字面值不在变量的取值范围内,编译器会报错,提示无法将一个 int 类型的值赋值给 byte 类型,如果想转换只能我们显式地进行转换,这时就一定会发生精度损失。

提示

对于这种处理,Java语言规范中是这么解释的:如果=的右边是常量表达式,而且类型是byte、short、char或int,那么Java在必要时会自动执行narrowing conversion,只要这个常量表达式的值在=左边变量的取值范围之内。

if the expression is a constant expression of type byte, short, char, or int: A narrowing primitive conversion may be used if the type of the variable is byte, short, or char, and the value of the constant expression is representable in the type of the variable.

Java 中还可以指定另外两种类型的字面值,long 类型字面值和 float 字面值。如果要表示 long 类型的字面值,需要在字面值后面加上 L;float 类型字面值要在后面加上 F。(后缀不区分大小写)

运算符

赋值运算符

赋值运算符是一个等于号 =,在赋值操作中,它是赋值符号。(不要叫做等于号)

做变量赋值操作时,程序会把赋值符号 = 右侧表达式的值赋值给左侧的变量。

public static void main(String[] args) {
// 将字面值 10 赋值给变量 a
int a = 10;
}

算术运算符

运算符+-*/%
说明取余

语法糖形式

运算符+=-=*=/=%=
说明加等减等乘等除等取余等

小栗子:

public static void main(String[] args) {
int a = 1;
a += 1; // 相当于 a = a + 1;
}

提示

a += 1 和 a = a + 1 这两种操作并不完全等价,编译器对 += 运算时会隐式地进行类型转换,而 a = a + 1 运算时编译器不会进行类型转换。

自增、自减运算:

运算符说明
++自增运算,+1
--自减运算,-1

自增自减运算符放在变量前时,会先进行自增运算,再执行其它运算;自增自减运算符放在变量后时,会先进行其它运算,再执行自增运算。

思考下下面这段代码的输出结果:

public static void main(String[] args) {
int a = 12;
int b = 45;
int c;

c = a + b--;

System.out.println("a的值:"+a);
System.out.println("b的值:"+b);
System.out.println("c的值:"+c);
}

关系运算符

也关系运算符也叫做"比较运算符",用来比较值的大小。关系运算符的结果是个 布尔 值。

关系运算符><>=<===!=
说明大于小于大于等于小于等于等于不等于

逻辑运算符

逻辑运算符用来判断多个布尔表达式从而得到一个 布尔 值。

逻辑运算符说明
&&只有运算符左右两边表达式都是 true 的情况时,结果才为 true,否则结果为 false
||只要运算符左右两边有一边表达式为 true 时,结果就为 true,否则结果为false
!布尔表达式取反

还有一种 运算符 &,它和 && 的区别是:

  • &&,叫做"短路与运算符",当它左侧的表达式值为 false 时,就 不执行右侧 的表达式;
  • &,即便运算符左侧为 false,依然 执行右侧 的表达式。

运算符也同上,有 ||| 两种:

  • ||,叫做"短路或运算符",当它左侧的表达式为 true 时,就 不执行右侧 的表达式;
  • | ,即便运算符左侧为 true,依然 执行右侧 的表达式。

三元运算符

三元运算写法是这样:布尔表达式 ? 值1 : 值2;

当问号 ? 前面的布尔表达式值为 true 时,返回冒号 : 前面的值,否则返回冒号 : 后面的值。例如像下面这段代码这样:

public static void main(String[] args) {
int a = 12;
int b = 45;

int max = a > b ? a : b;
}

流程控制

正常代码是从上至下顺序执行,如果想改变代码的执行流程,就要用到流程控制语句了。流程控制分为两种:条件循环

条件语句

Java 中的条件语句有 if 语句 和 switch 语句两种。

if 语句

if 语句是比较常用的条件控制语句。

if 关键字后面跟着一对儿小括号 (),小括号中是一个布尔表达式,再后面跟着一对儿花括号 {},花括号中是当满足条件时执行的代码。 当布尔表达式为 true 时,执行花括号中的代码。

public static void main(String[] args) {
int a = 12;
int b = 45;

int max = 0;

if (a > b) {
max = a;
}
}

if 语句后还可以接着一个 else 分支,当 if 的布尔表达式为 false 时,会执行 else 分之中的代码。

public static void main(String[] args) {
int a = 12;
int b = 45;

int max = 0;

if (a > b) {
max = a;
} else {
max = b;
}
}

if 语句还可以加入更多的分支,在 if 分支后面跟着 else if 分支,else ifelse 的区别在于它有判断条件,并且 else if 分支可以写多个,而 else 分支只能有一个。

public static void main(String[] args) {
int score = 90;

if (score >= 85) {
System.out.println("优秀");
} else if (score >= 60) {
System.out.println("及格");
} else if (score >= 50) {
System.out.println("争取及格");
} else {
System.out.println("不及格");
}
}

switch 语句

switch 语句常用来做值匹配,当匹配到值时,执行对应分支的代码。

switch 关键字后面跟着一对儿小括号 (),小括号中是要匹配的值,后面跟着一对儿花括号 {}, 花括号中用 case 关键字定义分支,case 后面是具体的值。

case 分支中,可以使用 break 关键字结束整个 switch 语句。如果没有 break 关键字,这个分支执行结束后会继续执行后面分支中的代码, 即便没有满足后面分支的 case 条件。

public static void main(String[] args) {
int week = 1;

switch (week) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
case 5:
System.out.println("星期五");
break;
case 6:
System.out.println("星期六");
break;
case 7:
System.out.println("星期七");
break;
default:
System.out.println("都不匹配");
}
}

📢在使用时要注意,在目前 Java8 版本中,switch 只支持 byte、short、int、char、及对应的包装类、String、Enum 类型的值匹配。

那么,思考下下面这段代码会输出什么结果?

public static void main(String[] args) {
char c = 'A';

switch (c) {
case 'A':
System.out.println("A");
case 'B':
case 'C':
System.out.println("B or C");
default:
System.out.println("other");
}
}

循环语句

Java 中循环语句有 while 循环,do while 循环,for 循环 3 种。循环多用于遍历数字或集合。

while 循环

while 关键字后面跟着一个布尔表达式,当布尔表达式的值 true 时,执行循环体中的代码。

public static void main(String[] args) {
int i = 0;
while (i < 10) {
System.out.println(i++);
}
}

do while 循环

do while循环 和 while 循环类似,不同的是一个 do 关键字后面先跟着循环体,然后是 while 循环条件。所以 do while 循环是先执行循环体再判断是否继续执行循环。

public static void main(String[] args) {
int i = 0;
do {
System.out.println(i++);
} while (i < 10);
}

for 循环

for 循环是相对常用的一种循环方式,它相比上面两种循环更加灵活。for 循环在写法上稍微有点儿复杂,for 关键字后面的小括号中包含三个语句,之间使用分号 ; 分隔。

其中第一个语句是初始化语句,在整个 for 循环开始时执行变量初始化操作,并且只会执行一次。 然后会执行第二个语句,第二个语句是 for 循环的判断条件,必须是布尔表达式,表达式为 true 时,开始执行循环体中的代码。 每次执行完循环体会执行小括号中第三个语句,然后继续判断循环条件。

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}

break 和 continue 关键字

在循环中还有两个比较重要的关键字,breakcontinuebreak 关键字用来结束整个循环,continue 关键字会结束本次循环,继续下一次循环。

如果要结束整个循环,使用 break 关键字。但在嵌套循环的情况下,break 只会结束所在的循环结构,不会结束全部循环。

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i == 5) {
break;
}
System.out.println(i);
}
}

如果只想跳过某次循环,使用 continue 关键字,程序会结束本次循环,继续判断循环条件,满足条件就继续下一次循环。

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i == 5) {
continue;
}
System.out.println(i);
}
}

数组

前面提到的 8 种基本数据类型只能存储单个值,而数组可以存储多个相同类型的值。数组是计算机中比较基础的数据结构之一,它的逻辑结构和物理结构相同,在计算机内存中占用一块连续的内存空间。

因为它在内存中占用一块连续的内存空间,所以在初始化时必须指定好数组的长度,这样程序才会在内存中找到一块连续内存存储数组。

数组的这种物理结构决定了它的特点,随机访问速度非常快,通过内存地址加上索引偏移就可以快速访问指定索引位置上的数据。但是它大小固定,无法动态改变数组大小,所以使用起来比较局限。Java 提供了动态数组(基于数组实现的集合类型)类型 ArrayList,后面会学到。

Java 中数组总结起来可以概括:固定大小,存储相同类型数据的数据集合。

在 Java 语言中,除了那 8 种基本数据类型以外,其余的类型都属于 引用类型,引用类型数据存储在堆内存中,变量存储的是堆内存的引用,相当于一个指针,通过引用访问堆内存中的实际数据。

数组声明

数组在声明时需要指定存储元素的数据类型,在数据类型后跟着中括号 [] 表示数组类型。数组类型[] 数组名;

public static void main(String[] args) {
// 中括号放在数据类型后面或者变量名后面都可以,通常放在数据类型后面
int[] a1;
int a2[];
}

数组初始化

数组初始化方式有两种,一种是使用 new 关键字,另一种是使用字面值赋值的方式。

第一种方式是通过 new 关键字创建一个数组,执行 new 操作会实际在堆内存中开辟一块空间用来存储数组。在使用 new 创建数组时,需要指定数组的长度。

public static void main(String[] args) {
int[] a = new int[10];
}

第二种方式是通过字面值赋值。数组的长度是字面值中的元素个数。

public static void main(String[] args) {
int[] a1 = new int[]{1, 2, 3, 4, 5};
// 可以省略 new ,编译器会自动处理
int[] a2 = {1, 2, 3, 4, 5};
}

访问数组元素

数组是使用索引来访问某一索引位置上的值。数组的索引是从 0 开始,表示第一个元素。📢需要注意的是,不能访问不存在的索引,否则在运行时会报错。

public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5};
int firstElem = a[0]; // 1
}
提示

有些语言即便访问不存在的索引,程序也不会报错,但这明显是很危险的。Java 在这一点上保证了内存的安全。

获取数组长度

使用 length 属性可以获取数组的长度。

public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5};
int len = a.length; // 5
}

遍历数组元素

public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5};
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
}

Java 中还有一种专门用来遍历数组或集合的 for 循环,也叫 foreach 循环。

public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5};
for (int elem : a) {
System.out.println(elem);
}
}

数组下标越界

当访问超出数组长度的索时,在运行时 JVM 会报数组下标越界 ArrayIndexOutOfBoundsException 的异常

public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5};
int elem = a[5]; // ArrayIndexOutOfBoundsException
}

二维数组

二维数组的每一个元素都是一个一维数组。它的声明和初始化方式和一维数组类似。

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

通过索引访问二维数组中的元素。

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

int elem = a[0][1]; // 2
}

使用嵌套循环遍历二维数组中的元素。

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

for (int i = 0; i< a.length; i++) {
for (int j = 0; j < a[i].length; j ++) {
System.out.print(a[i][j] + ", ");
}
System.out.println();
}
}

面向对象编程

面向对象编程是将实际事物抽象成对象的一种编程范式。面向对象编程的三大特点:封装继承多态

类和对象

提示

类是对象的抽象,对象是类的具体实例。

Java 从语言级别上就是面向对象的编程语言,使用 对一类具有相同属性和行为的事物进行抽象。

在 Java 中,除了 8 种基本数据类型之外,其余的类型都属于 引用数据类型,例如:String 这个类,String 类型是 Java 提供的用来表示和处理字符串的类型。还有我们自己定义的 都属于引用数据类型。

使用 class 关键字来声明类,每一个.java文件就是一个类,在 .java 文件中只能有一个 public 类,并且类名必须与文件名相同,否则在编译时编译器会报错。

一个类通常是由 成员属性构造方法成员方法 组成。使用属性来定义这个类要存储的数据;构造方法用来构造类的具体实例;成员方法提供该类的行为。

public class Cat {

// 昵称
String nickname;
// 年龄
int age;
// 性别
String sex;

// 无参构造方法
public Cat() {}

// 有参构造方法
public Cat(String nickname, int age, String sex) {
this.nickname = nickname;
this.age = age;
this.sex = sex;
}

// 方法:吃东西
public void eat() {
System.out.println(nickname + "在吃东西。");
}
}

是对一类具有相同属性和行为事物的抽象,类的具体实例叫做 对象,它真正表示具体的事物。例如用 Cat 类表示猫,假设有一只猫叫"花花",那这个"花花"就是 Cat 类的一个对象。

成员属性

在类中定义的属性叫做 成员属性,例如上面代码中 Cat 类中定义的 nicknameagesex

构造方法

方法一般是用来封装某一功能的具体实现,在 Java 语言中,定义方法需要指明方法的 访问权限返回值类型方法名 以及 参数列表

在类中,方法分为 构造方法成员方法。其中 构造方法方法名和类名相同,没有返回值类型的方法。

构造方法 也叫"构造函数"、"构造器",英文名是 Constructor。它用来构造类的实例对象。构造方法一般分为 无参数构造方法有参数构造方法 两种。

如果在类中没有显式声明构造方法的话,Java 编译器会在编译时默认添加一个无参的构造方法。就像下面这样:

Cat 类中没有定义构造方法,编译后的字节码中添加了一个无参的构造方法。

也可以手动添加无参数的构造方法。除了无参数的构造方法之外,还可以定义有参数的构造方法。

📢需要注意的是,如果在类中定义了有参构造方法,那么编译器就不会自动再添加一个无参构造方法。如果需要无参构造方法,需要我们显式地在代码定义无参构造方法。

无参构造方法可以定义多个,只要同名方法的参数列表不同即可。这里应用的是 方法重载 技术。

package com.shiguangping;

/**
* @author liyan
* @since 2022/5/29 10:17
*/
public class Cat {

// 昵称
String nickname;
// 年龄
int age;
// 性别
String sex;

// 无参构造方法
public Cat() {}

// 有参构造方法
public Cat(String nickname) {
this.nickname = nickname;
}

// 有参构造方法
public Cat(String nickname, int age, String sex) {
this.nickname = nickname;
this.age = age;
this.sex = sex;
}
}

方法重载

方法重载 允许在类中定义多个 相同名字 的方法,前提是同名方法的参数列表不同(参数类型不同或者参数个数不同)。方法调用时根据传入的参数决定调用哪个重载方法,方法重载属于一种编译时多态。

public void eat() {
System.out.println(nickname + "在吃东西。");
}

public void eat(String food) {
System.out.println(nickname + "在吃" + food);
}

创建对象

构造方法用来构造类的实例对象。创建对象时,使用 new 关键字,后面跟着构造方法。这一过程叫做 对象的实例化。实际上,new 关键字在实例化对象时会在堆内存中开辟一块空间,用来存储这个对象数据, 然后将这块内存的引用返回回来赋值给赋值符号左侧的变量。

创建对象时需要调用构造方法,所以,可以在构造方法中编写需要初始化的操作。

// 通过无参构造方法创建实例对象
Cat cat1 = new Cat();

// 通过有参构造方法创建实例对象
Cat cat2 = new Cat("花花");

成员方法

成员方法一般用来封装和这个类相关的操作,下面是一个方法的定义。

public void hello() {
System.out.println("Hello Java");
}

定义方法需要指明方法的 访问权限,通常使用 public 关键字或者 private 关键字,前者表示该方法是公共的,在其它类中可以访问这个方法;后者表示该方法是私有的, 只能被当前类中的其它方法访问。如果没有显式地指明访问修饰符,默认方法的访问权限是 default,默认的,表示只能被 同一包 中的其它类访问。还有一个访问修饰的关键字是 protected, 表示该方法是受保护的,只能被同一包中的其它类以及该类的所有子类访问,一般用于需要被子类重写的方法

访问修饰后面跟着的是方法的 返回值类型,在定义方法时,必须指明方法的返回值类型,除了构造方法外。如果方法没有返回值,这个位置使用 void 关键字。

返回值后面跟着的是 方法名,方法名的命名要遵循标识符的命名规则,只能使用字母、数字、下划线、美元符,且不能以数字开头。并且要遵循 小驼峰 的命名规则,第一个单词首字母小写,其余单词首字母大写。 命名时不要使用拼音,尽量做到见名知义,养成良好的编码习惯。

方法名后面紧跟着的小括号 () 是方法的参数列表,参数列表中可以定义任意个数的参数。通常把参数列表中定义的参数叫做 形式参数 (Parameters) 或者 形参,通常把调用方法传进来的参数叫做 实际参数 (Arguments) 或者 实参

方法名参数列表 组成了方法的签名,方法重载也是根据方法签名来决定调用哪个方法。

方法的花括号 {} 表示方法体,方法的处理逻辑写在方法体中。

访问权限

访问修饰符类内包内子类其它包
public
protected
default
private

方法调用

和大多数编程语言一样,使用 点标记 法来调用方法。成员属性和成员方法和对象关联,调用方法是需要使用 对象名.方法名() 的形式来调用方法。

public static void main(String[] args) {
Cat cat1 = new Cat("花花");
cat1.eat(); // 花花在吃东西

Cat cat2 = new Cat("蓝蓝");
cat2.eat(); // 蓝蓝在吃东西
}

按值传递

在 Java 语言中,方法参数的传递方式使用的是 按值传递 的方式。也就是说,在方法里,是不能通过形参来修改方法外实参的值的,实际上在调用方法传参时传递的是值拷贝出的副本。

这里也有一个小坑,如果参数是基本数据类型,那很容易就理解上面这句话。但如果参数是引用数据类型,在执行结果上好像与上面的描述不相符。但实际上, 引用数据类型的参数也是按值传递的,因为引用类型的变量中存储的值实际上是对象的引用。将引用类型传给形参后形参指向的也是相同内存的对象,所以可以使用对象引用操作这个对象。 但是在方法中,却无法改变实参的引用,无法使用方法形参让外面的实参指向一个新的对象,所以说 Java 是按值传递的。