目录
目录
3.10.1 静态属性(Static Properties)
3.10.2 动态属性(Instance Properties)
3.11.1 静态代码块(Static Initialization Block)
3.11.2 动态代码块(Instance Initialization Block)
第一章 Java简介
1.1 Java历史
Java的历史可以追溯到20世纪90年代初。当时,太阳微系统(Sun Microsystems)公司的工程师詹姆斯·高斯林(James Gosling)和他的团队开始开发一种新的编程语言,这个项目最初被称为“绿色计划”(Green Project)。他们的目标是创建一种可以在各种不同的计算机平台上运行的编程语言。
1995年,太阳微系统公司正式发布了Java编程语言和Java开发工具包(JDK)。Java的发布引起了广泛的关注和兴趣,因为它具有许多优点,如跨平台性、面向对象、可移植性和安全性。
随着Java的发展,它逐渐成为一种广泛使用的编程语言。它被用于开发各种类型的应用程序,包括桌面应用程序、移动应用程序、Web应用程序和企业级应用程序。
2009年,太阳微系统公司被甲骨文公司(Oracle Corporation)收购。自此以后,Java的发展继续进行,并且在全球范围内得到广泛使用。Java的最新版本是Java 17,于2021年9月发布。
Java的成功可以归因于它的设计原则和特性。它是一种面向对象的语言,具有简单、可读性强的语法。它还具有自动垃圾回收、异常处理和多线程等功能,使得开发人员可以更轻松地编写高质量的代码。
此外,Java还有一个庞大的开发社区和丰富的第三方库和框架,使得开发人员可以更快地构建复杂的应用程序。
总的来说,Java的历史可以追溯到20世纪90年代初,它经过多年的发展和演变,成为一种广泛使用的编程语言,被用于开发各种类型的应用程序。它的成功可以归功于它的设计原则和特性,以及庞大的开发社区和丰富的生态系统。
1.2 JDK
JDK(Java Development Kit)是Java开发工具包,它包含了开发和运行Java应用程序所需的工具和库。下面是JDK的历史:
1. JDK 1.0(1996年):这是第一个公开发布的JDK版本。它包含了Java编译器、Java虚拟机(JVM)和一些基本的类库。
2. JDK 1.1(1997年):这个版本引入了许多新的特性和改进,包括内部类、反射、JavaBeans、RMI(远程方法调用)和JDBC(Java数据库连接)等。
3. JDK 1.2(1998年):这个版本被称为Java 2,它引入了许多重要的特性,如集合框架、Swing图形界面库、Java命名和目录接口(JNDI)等。
4. JDK 1.3(2000年):这个版本引入了许多性能和安全性方面的改进,包括JIT(即时编译器)和Java Web Start等。
5. JDK 1.4(2002年):这个版本引入了许多新的特性,如正则表达式、NIO(非阻塞IO)、XML解析和Web服务等。
6. JDK 5.0(2004年):这个版本引入了许多重要的语言和库的改进,包括泛型、枚举、自动装箱/拆箱、注解和增强的循环等。
7. JDK 6(2006年):这个版本主要是性能和稳定性的改进,没有引入太多新的语言特性。
8. JDK 7(2011年):这个版本引入了许多新的语言特性,如try-with-resources、多异常捕获和类型推断等。
9. JDK 8(2014年):这个版本引入了Java 8的重要特性,如Lambda表达式、函数式接口、流式API和新的日期/时间API等。
10. JDK 9(2017年):这个版本引入了模块化系统(Project Jigsaw)、REPL(交互式编程环境)和改进的JVM性能等。
11. JDK 10(2018年):这个版本主要是一些语言和工具方面的改进,如局部变量类型推断、垃圾回收器接口和增强的Javadoc等。
12. JDK 11(2018年):这个版本是Java的长期支持(LTS)版本,它包含了一些新的特性和改进,如HTTP客户端API、本地变量语法和ZGC(低延迟垃圾回收器)等。
自JDK 11以后,Oracle决定每6个月发布一个新的JDK版本,并且每3年发布一个LTS版本。这种频繁的发布模式使得Java的发展更加快速和灵活。
目前jdk最新的已经更新到了JDK 20,但是JDK 8版本是目前相对比较稳定也是日常开发中用得最多的版。
我们在编译Java代码之前肯定要先进行jdk的安装与配置,JDK8的安装与配置教程如下,可点击下方 1.2.1 链接进入教程
1.2.1 jdk1.8的安装教程
1.3 Java语言的特点
- 面向对象;它对对象中的类、对象、继承、封装、多态、接口、包等均有很好支持。
- 平台无关性;在引入虚拟机之后,Java语言在不同的平台上运行不需要重新编译。3、简单性。
- 解释执行;程序在Java平台运行时会被编译成字节码文件,然后可以在有Java环境的操作系统上运行。
- 支持多线程,并提供多线程之间的同步机制;
- 分布式;
- 健壮性;
- 高性能;
- 安全性。
1.4 第一个Java程序HelloWorld
现在让我们来编写一个" HelloWorld "程序
public class HelloWorld {
public static void main(String[] args) {
System.out.println("HelloWorld");
}
}
System.out.println();是输出语句,将你要打印的内容输入()内,可显示在控制台上
1.5 Java语言规范
Java语言编写规范是一组约定俗成的规则和最佳实践,旨在提高代码的可读性、可维护性和可重用性。以下是一些常见的Java语言编写规范:
命名规范:
- 类名使用驼峰命名法,首字母大写,例如:MyClass。
- 方法名和变量名使用驼峰命名法,首字母小写,例如:myMethod,myVariable。
- 常量名使用全大写,多个单词之间用下划线分隔,例如:MY_CONSTANT。
缩进和空格:
- 使用4个空格进行缩进,不要使用制表符。
- 在运算符和逗号后面添加一个空格,例如:int x = 10 + y;。
- 在逗号前面不要添加空格,例如:method(a, b, c)。
括号和大括号:
- 在条件语句和循环语句中,始终使用大括号,即使只有一行代码。
- 左大括号放在语句块的同一行的末尾,右大括号单独占一行。
注释:
- 使用注释来解释代码的意图和功能。
- 使用Javadoc格式的注释来描述类、方法和字段的作用。
- 避免使用过多的注释,代码应该尽可能自解释。
异常处理:
- 在合适的地方捕获和处理异常。
- 不要捕获异常后不处理,至少应该打印异常信息。
类和方法设计:
- 类应该具有单一责任,只负责一个明确的功能。
- 方法应该短小精悍,只做一件事情。
- 避免使用全局变量,尽量使用局部变量。
其他:
- 遵循面向对象的设计原则,如封装、继承和多态。
- 使用合适的数据结构和算法来提高代码的效率。
- 避免使用魔法数值,使用常量或枚举来代替。
第二章 Java基础
2.1 数据类型
八大数据类型:
- byte占一个字节范围:-128-127
- short占两个字节范围:-32768-32767
- int占4个字节范围
- long占8字节范围
- float占4个字节
- double占8字节
- char占两字节
- boolean占一字节只有true和false两个
整数型:
byte numb1=15;
short numb2=15454;
int numb3=1546875484;
long numb4=4846498881454454L;//long类型要在数字后面加个L
浮点型:
flout numb5=15.1f;//flout要在数字后面加个f;
double numb6=45.124;
字符型:
char name1='a';char name2='李';
字符串:
//string不是关键字,而是类;
string name3="李霍霍";
布尔型:
//只有是非boolean
flag=true;boolean flag1=flase;
2.2 数据类型转换
在Java中,数据类型转换可以分为两种:隐式类型转换和显式类型转换。
1. 隐式类型转换(自动类型转换):
当把一个数据类型的值赋给另一个数据类型的变量时,如果两个数据类型兼容,Java会自动进行类型转换。例如,将一个int类型的值赋给一个double类型的变量,Java会自动将int类型转换为double类型。
示例代码:
int num1 = 10;
double num2 = num1; // 隐式类型转换,将int类型转换为double类型
2. 显式类型转换(强制类型转换):
当需要将一个较大范围的数据类型转换为较小范围的数据类型时,需要使用显式类型转换。需要注意的是,显式类型转换可能会导致数据丢失或溢出。
示例代码:
double num1 = 3.14;
int num2 = (int) num1; // 显式类型转换,将double类型转换为int类型
需要注意的是,在进行显式类型转换时,可能会出现数据丢失或溢出的情况,因此需要谨慎使用。
2.3 运算符
2.3.1 算术运算符
算术运算符用于表达式计算。
运算符 | 描述 | 例子 | 结果 |
+ | 加法 | x=y+1 | x=4 |
- | 简法 | x=y-1 | x=2 |
* | 乘法 | x=y*2 | x=6 |
/ | 除法 | x=y/2 | x=1.5 |
% | 模(求余数() | x=y%2 | x=1 |
++ | 自增 | ++y | y=4 |
y++ | |||
-- | 自减 | --y | y=2 |
++y |
2.3.2 赋值运算符
赋值运算符用于给 JavaScript 变量赋值。
运算符 | 例子 | 等同于 | 结果 |
= | x=y | x=2 | |
+= | x+=y | x=x+y | x=6 |
-= | x-=y | x=x-y | x=2 |
*= | x*=y | x=x*y | x=8 |
/= | x/=y | x=x/y | x=2 |
%= | x%=y | x=x%y | x=0 |
2.3.3 位运算符
运算符 | 说明 | 例子 | 结果 |
& | 按位与 | x&y | 2(0000 0000 0000 0010) |
| | 按位或 | x|y | 3(0000 0000 0000 0011) |
~ | 按位取反 | ~x | -3(0000 0000 0000 0011) |
^ | 按位异或 | x^y | 1(0000 0000 0000 0001) |
按位取反详解:
- JavaScript 中的数字是以补码形式进行表示
- 在计算机中,负数常常使用补码表示。补码是一种表示有符号整数的方法,它可以使加法和减法的运算规则统一,同时避免了使用额外的符号位。
- 在补码表示法中,正数的补码与其本身相同,而负数的补码是其二进制表示的每一位取反后加1。因此,对于一个整数,如果对其进行按位取反操作,实际上是将其补码的每一位取反。
- 在 JavaScript 中,数字被存储为32位有符号整数。当数字被转换为补码形式时,按位取反操作实际上是将其二进制表示的每一位取反。因此,按位取反的结果是补码形式的负数。
- 需要注意的是,JavaScript 中的位运算符(包括按位取反运算符~)只适用于32位有符号整数。对于64位整数或浮点数,位运算符的行为可能会有所不同。
注意:
按位取反操作可能会导致溢出问题。在某些编程语言中,按位取反操作的结果可能与预期不符,特别是对于无符号整数类型。因此,在使用按位取反操作时,需要注意数据类型和溢出问题。
异或运算(XOR)是一种逻辑运算符,用于对两个操作数进行比较。它的运算规则如下:
- 如果两个操作数的对应位相同(都为0或都为1),则结果为0。
- 如果两个操作数的对应位不同(一个为0,一个为1),则结果为1。
异或运算有一些特性:
- 交换律:A ^ B = B ^ A
- 结合律:(A ^ B) ^ C = A ^ (B ^ C)
- 自反性:A ^ A = 0
- 零元素:A ^ 0 = A
异或运算在编程中有很多应用,例如:
- 交换两个变量的值:可以使用异或运算来交换两个变量的值,而无需使用额外的临时变量。
- 检测出现奇数次的元素:如果一个数组中只有一个元素出现了奇数次,而其他元素都出现了偶数次,可以使用异或运算来找出这个元素。
- 加密和解密:异或运算可以用于简单的加密和解密算法。
异或运算在逻辑运算和位运算中都有广泛的应用,可以用于实现各种功能和算法。
2.3.4 逻辑运算符
逻辑运算符用于测定变量或值之间的逻辑。
运算符 | 描述 | 例子 |
&& | 与 | (x<5 && y>1)为true |
|| | 或 | (x<5 || y>1)为true |
! | 非 | (x!=y)为true |
逻辑与(AND)运算符:用符号&&表示。它的运算规则如下:
例如,表达式true && false的结果为false。
- 如果两个操作数都为true,结果为true。
- 如果有一个操作数为false,结果为false。
逻辑或(OR)运算符:用符号||表示。它的运算规则如下:
例如,表达式true || false的结果为true。
- 如果两个操作数都为false,结果为false。
- 如果有一个操作数为true,结果为true。
逻辑非(NOT)运算符:用符号!表示。它的运算规则如下:
例如,表达式!true的结果为false。
- 如果操作数为true,结果为false。
- 如果操作数为false,结果为true。
逻辑运算符可以用于组合多个逻辑表达式,构建更复杂的逻辑条件。它们在条件语句、循环控制、逻辑判断等方面都有广泛的应用。
此外,逻辑运算符还有一些重要的特性:
- 短路求值:逻辑与(AND)和逻辑或(OR)运算符都具有短路求值的特性。当使用逻辑与运算符时,如果第一个操作数为false,那么第二个操作数将不会被计算。当使用逻辑或运算符时,如果第一个操作数为true,那么第二个操作数将不会被计算。这个特性可以提高代码的效率,避免不必要的计算。
- 运算符优先级:逻辑非(NOT)运算符的优先级最高,其次是逻辑与(AND)运算符,最后是逻辑或(OR)运算符。在复杂的逻辑表达式中,可以使用括号来明确运算的顺序。
2.3.5 关系运算符
相等运算符(==):用于检查两个值是否相等。如果相等,则返回true;否则返回false。 例如:
a = 5 b = 5 print(a == b) # 输出 True
不相等运算符(!=):用于检查两个值是否不相等。如果不相等,则返回true;否则返回false。 例如:
a = 5 b = 10 print(a != b) # 输出 True
大于运算符(>):用于检查一个值是否大于另一个值。如果大于,则返回true;否则返回false。 例如:
a = 5 b = 10 print(b > a) # 输出 True
小于运算符(<):用于检查一个值是否小于另一个值。如果小于,则返回true;否则返回false。 例如:
a = 5 b = 10 print(a < b) # 输出 True
大于等于运算符(>=):用于检查一个值是否大于或等于另一个值。如果大于或等于,则返回true;否则返回false。 例如:
a = 5 b = 5 print(a >= b) # 输出 True
小于等于运算符(<=):用于检查一个值是否小于或等于另一个值。如果小于或等于,则返回true;否则返回false。 例如:
a = 5 b = 10 print(b <= a) # 输出 False
比较运算符通常用于条件语句的判断和逻辑运算中。
2.3.6 条件运算符(三元运算符):
用于根据条件选择不同的值,包括条件表达式 ? 值1 : 值2。(条件为真,输出值1;条件为假,输出值2)
示例代码:
int javaScore = 100;
String result = javaScore == 100 ? "恭喜" : "加油" ;
2.4 选择结构语句
2.4.1 if...else语句
在Java中,if语句用于根据条件选择是否执行某段代码块。可以使用if
、else if
和else
来构建多个条件分支。Java中if语句的基本语法如下:
if (condition) {
// 如果条件为真,则执行这里的代码块
} else if (condition) {
// 如果上一个条件为假,且这个条件为真,执行这里的代码
} else {
// 如果条件为假,则执行这里的代码块
}
下面是一个简单的例题,可以供大家学习使用if语句:
通过键盘录入一个年份,判断该年是否为闰年?
示例代码:
public class demo02 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个年份");
int year = sc.nextInt();
if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)){
System.out.println(year+"年,是闰年");
}else {
System.out.println(year+"年,不是闰年");
}
}
}
2.4.2 switch..case
switch
语句:用于根据一个表达式的值选择执行不同的代码块。可以使用case
和default
来定义不同的分支。switch语句的基本语法如下:
switch (expression) {
case value1:
// 如果expression的值等于value1,执行这里的代码
break;
case value2:
// 如果expression的值等于value2,执行这里的代码
break;
default:
// 如果expression的值不等于任何一个case,执行这里的代码
break;
}
下面是一个简单的例题,可以供大家学习使用switch语句:
通过键盘录入一个人名,输出他的事迹?
示例代码:
public class demo03 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个名字");
String name = sc.next();
switch (name){
case "蔡徐坤":
System.out.println("唱跳rap篮球");
break;
case "刘翔":
System.out.println("百米跨栏");
break;
default:
System.out.println("无此人信息");
}
}
}
2.5 循环语句
2.5.1 for循环
for
循环:用于指定循环次数的循环结构。for语句基本语法如下:
for (初始化; 条件; 更新) {
// 循环体
}
下面是一个简单的例题,可以供大家学习使用for语句:
打印5行5列的正三角形?
示例代码:
public class demo07 {
public static void main(String[] args) {
for (int i=1;i<=5;i++){
for (int j=5;j>i;j--){
System.out.print(" ");
}
for (int j=1;j<=i;j++){
System.out.print("*");
}
for (int j=1;j<i;j++){
System.out.print("*");
}
System.out.println();
}
}
}
2.5.2 while循环
while
循环:用于在满足条件时循环执行某段代码。while语句基本语法如下:
while (条件) {
// 循环体
}
下面是一个简单的例题,可以供大家学习使用while语句:
由用户输入多个学员成绩,当输入-1时结束循环,输出一共输入多少人,和输入的这些学员的总分数?
示例代码:
public class demo08 {
public static void main(String[] args) {
System.out.println("请输入成绩");
int totalsource=0;
int i=0;
while (true){
int source = new Scanner(System.in).nextInt();
if (source==-1){
break;
}
i++;
totalsource += source;
}
System.out.println("一共输入"+i+"人,总分数为"+totalsource);
}
}
2.5.3 do-while循环
do-while循环:与while
循环类似,但是先执行一次循环体,然后再判断条件是否满足。
do-while循环语句基本语法如下:
do {
// 循环体
} while (条件);
下面是一个简单的例题,可以供大家学习使用do-while语句:
计算1
到100
之间6
的倍数出现的次数?
示例代码:
public class demo11 {
public static void main(String[] args) {
int count= 0;
int i=1;
do{
i++;
if(i%6==0){
count++;
}
}while(i<=100);
System.out.println("6的倍数出现的次数为:" + count);
}
}
2.6 循环控制语句
2.6.1 break
语句
break
语句:用于终止当前循环或switch
语句的执行。当break
语句执行时,程序将跳出当前循环或switch
语句,继续执行循环或switch
语句后面的代码。
示例代码:
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // 当i等于5时,跳出循环
}
System.out.println(i);
}
输出结果为: 0 1 2 3 4
2.6.2 continue
语句
continue语句用于跳过当前循环的剩余代码,进入下一次循环的执行。当continue
语句执行时,程序将立即跳到循环的下一次迭代。
示例代码:
for (int i = 0; i < 10; i++) {
if (i == 5) {
continue; // 当i等于5时,跳过当前循环的剩余代码,进入下一次循环
}
System.out.println(i);
}
输出结果为: 0 1 2 3 4 6 7 8 9
2.6.3 return
语句
return语句用于结束方法的执行,并返回一个值(如果方法有返回值的话)。当return
语句执行时,程序将立即退出当前方法,并返回指定的值。
示例代码:
public int sum(int a, int b) {
return a + b; // 返回a和b的和,结束方法的执行
}
2.7 方法
2.7.1 方法的定义
- 方法由方法名、参数列表、返回类型和方法体组成。
- 方法名是一个标识符,用于唯一标识方法。
- 参数列表是用括号括起来的一组参数,用于接收输入值。
- 返回类型指定方法返回的值的类型,可以是基本数据类型、引用类型或void(表示没有返回值)。
- 方法体包含一组语句,定义了方法的具体操作。
2.7.2 方法的引用
- 方法可以通过方法名进行引用,用于调用方法并执行其中的代码。
- 方法引用可以通过对象名、类名或通过this和super关键字引用当前对象或父类对象的方法。
2.7.3 方法的位置
- 方法可以定义在类中的任何位置,包括类的顶层、实例初始化块、静态初始化块和构造方法中。
- 通常,方法定义在类的顶层,作为类的成员方法。
2.7.4 方法的参数
- 方法可以接收零个或多个参数。
- 参数是用于传递数据给方法的变量。
- 参数由参数类型和参数名组成,多个参数之间用逗号分隔。
- 方法在调用时需要传递与参数列表匹配的实际参数。
形参和实参:
在Java中,形参(形式参数)和实参(实际参数)是方法中用于传递数据的概念。
形参是在方法定义中声明的参数,用于接收调用该方法时传递的实际参数。形参是方法内部的局部变量,其作用域仅限于方法内部。
实参是在方法调用时传递给方法的实际数值或对象。实参可以是常量、变量或表达式。实参的值会被传递给对应的形参,从而在方法内部进行处理。
下面是一个示例,演示了形参和实参的使用:
public class Example {
public static void main(String[] args) {
int a = 10;
int b = 20;
// 调用方法并传递实参
int sum = calculateSum(a, b);
System.out.println("Sum: " + sum);
}
// 方法定义中的形参
public static int calculateSum(int num1, int num2) {
int sum = num1 + num2;
return sum;
}
}
2.7.5 方法的返回值
- 方法可以返回一个值,也可以不返回值。
- 返回值的类型必须与方法的返回类型匹配。
- 使用关键字
return
来返回一个值,并结束方法的执行。 - 如果方法的返回类型是void,则表示方法没有返回值。
2.7.6 方法的重载
- Java允许在同一个类中定义多个同名但参数列表不同的方法,称为方法的重载。
- 重载方法的区分依据是参数列表的类型、顺序和数量。
- 在调用重载方法时,编译器会根据传递的参数类型和数量来选择合适的方法。
以下是一个示例代码,展示了Java方法的定义、引用、位置、参数和返回值的使用:
public class Example {
// 方法的定义和位置:在类的顶层定义
public static void main(String[] args) {
// 方法的引用:通过方法名进行引用
int sum = calculateSum(10, 20); // 调用calculateSum方法并传递参数
System.out.println("Sum: " + sum); // 打印方法返回的值
}
// 方法的参数:接收两个整数参数
public static int calculateSum(int num1, int num2) {
int sum = num1 + num2;
return sum; // 方法的返回值:返回两个整数的和
}
}
2.7.7 方法的重写
方法重写是指在子类中定义一个与父类中同名、参数列表相同的方法,用于覆盖父类中的方法。子类中的重写方法可以重新定义方法的实现,但是方法的签名(方法名和参数列表)必须与父类中的方法相同。通过方法重写,子类可以对父类的方法进行定制化的实现。
方法重写和方法重载的区别:
- 方法重写要求子类中的方法与父类中的方法具有相同的签名,用于覆盖父类中的方法。方法重载要求在同一个类中定义多个同名的方法,但是参数列表不同。
- 方法重写是针对继承关系的,子类重写父类的方法。方法重载是在同一个类中定义多个同名的方法。
- 方法重写可以实现多态性,通过父类的引用调用子类的方法。方法重载不涉及多态性,只是提供了更多的方法选择。
下面是一个示例代码,演示了方法重写和方法重载的不同:
class Animal {
public void makeSound() {
System.out.println("动物发出声音。");
}
public void makeSound(String sound) {
System.out.println("动物在叫 " + sound);
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("狗在吠叫。");
}
public void makeSound(int times) {
for (int i = 0; i < times; i++) {
System.out.println("狗在吠叫。");
}
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.makeSound(); //输出:动物发出声音。
animal.makeSound("meow"); //输出:动物在喵喵叫
Dog dog = new Dog();
dog.makeSound(); //输出:狗在吠叫。
dog.makeSound(3); //输出:狗在吠叫。狗在吠叫。狗在吠叫。
}
}
在上面的例子中,Animal类中定义了两个方法makeSound(),一个是无参的方法,一个是带有一个参数的方法。Dog类继承自Animal类,并重写了makeSound()方法。此外,Dog类还定义了一个与父类中的方法同名,但是参数列表不同的方法makeSound(int times)。
在Main类中,我们创建了Animal对象和Dog对象,并调用它们的makeSound()方法和makeSound(int times)方法。由于Dog类重写了makeSound()方法,所以当调用dog.makeSound()时,会执行子类中的方法。而当调用dog.makeSound(3)时,会执行子类中的makeSound(int times)方法,因为它的参数列表与父类中的方法不同。
通过方法重写和方法重载,我们可以实现对父类方法的定制化实现和提供更多的方法选择。这样可以增加代码的灵活性和可读性。
2.7.8 递归
递归是一种在方法内部调用自身的编程技巧。通过递归,一个方法可以重复执行自身的代码,从而实现解决问题的过程。
递归方法通常包含两个部分:
- 基本情况(递归终止条件):定义一个或多个条件,当满足这些条件时,递归将停止执行并返回结果。这是递归的终止点,防止无限循环。
- 递归调用:在方法内部调用自身,通常是在解决较小规模问题的基础上解决更大规模问题。
递归的实现需要注意以下几点:
- 确保递归调用能够趋近于基本情况,否则可能会导致无限递归,导致栈溢出。
- 确保每次递归调用时,问题的规模都会减小,否则可能会导致递归深度过大,同样会导致栈溢出。
以下是一个计算阶乘的递归示例代码:
public class Example {
public static void main(String[] args) {
int n = 5;
int factorial = calculateFactorial(n);
System.out.println("Factorial of " + n + " is: " + factorial);
}
public static int calculateFactorial(int n) {
// 基本情况:当n为0或1时,直接返回1
if (n == 0 || n == 1) {
return 1;
}
// 递归调用:计算n的阶乘
return n * calculateFactorial(n - 1);
}
}
2.8 数组
2.81 什么是数组
数组是一种数据结构,用于存储一组相同类型的元素。它是在内存中连续分配的一段存储空间,每个元素都可以通过索引访问和操作。数组可以存储基本数据类型(如整数、浮点数、字符等)或引用数据类型(如对象、字符串等)。
数组的特点包括:
-
相同类型:数组中的元素必须是相同类型的,即具有相同的数据类型。
-
固定长度:数组在创建时需要指定长度,一旦创建后,其长度是固定的,无法更改。长度表示数组中元素的个数。
-
连续存储:数组中的元素在内存中是连续存储的,相邻元素之间的地址是连续的。这使得通过索引可以快速计算出元素在内存中的地址,从而实现快速访问和操作。
-
索引访问:每个数组元素都有一个唯一的索引,用于标识元素在数组中的位置。索引从0开始,依次递增。通过索引,可以读取和修改数组中的元素值。
2.8.2 数组的声明和初始化数组
声明数组:使用数组类型和数组名称来声明一个数组变量,例如:
int[] numbers;
初始化数组:
- 静态初始化:在声明数组时,直接指定数组的元素值,例如:
int[] numbers = {1, 2, 3, 4, 5};
- 动态初始化:在声明数组时,指定数组的长度,然后使用
new
关键字创建数组对象,例如:int[] numbers = new int[5];
初始化数组元素:通过索引访问数组元素,并为其赋值,例如:
numbers[0] = 1;
2.8.3 数组的组成
-
元素:数组由一组相同类型的元素组成。这些元素可以是基本数据类型(如整数、浮点数、字符等)或引用数据类型(如对象、字符串等)。
-
索引:每个数组元素都有一个唯一的索引,用于标识元素在数组中的位置。索引从0开始,依次递增。通过索引,可以访问和操作数组中的元素。
int firstNumber = numbers[0];
- 长度:数组的长度指的是数组中元素的个数。在Java中,可以使用
length
属性来获取数组的长度。数组的长度是固定的,一旦数组被创建,其长度无法更改。
int length = numbers.length;
-
内存分配:数组在内存中是连续存储的。当创建一个数组时,Java会在内存中分配一块连续的空间来存储数组的元素。元素之间的距离是固定的,通过索引可以直接计算出元素在内存中的地址。
-
访问和操作:通过索引,可以访问和操作数组中的元素。可以使用索引来读取和修改数组中的元素值。可以使用循环结构遍历数组,对数组进行操作。
-
多维数组:除了一维数组,Java还支持多维数组。多维数组是由多个一维数组组成的。例如,二维数组是一个由行和列组成的表格,可以通过两个索引来访问和操作二维数组中的元素。
2.8.4 遍历数组:
- 使用
for
循环:通过循环遍历数组的所有元素,例如:for (int i = 0; i < numbers.length; i++) { System.out.println(numbers[i]); }
- 使用增强
for
循环:简化遍历数组的语法,例如:for (int number : numbers) { System.out.println(number); }
例题: 定义一个数组长度为5的数组,通过键盘录入数组元素,并比较数组元素中的最大值,将最大值和该最大值在数组中的下标打印在控制台上。
public class demo01 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int[] arr = new int[5]; //定义一个数组长度为5的数组
for (int i = 0; i < arr.length; i++) {
System.out.println("请录入第" + (i + 1) + "个数组元素"); //提示用户录入数据
int num = sc.nextInt(); //键盘录入数据
arr[i] = num; //将键盘录入的数据存到数组中
}
int max = arr[0]; //假定数组中第一个元素为最大值
int index=0; //定义数组下标
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i]; //循环遍历,如果数组元素大于最大值,则最大值变为该元素
index = i;
}
}
System.out.println("最大值为:" + max + "下标为:" + index);
}
}
2.8.5 数组的常见操作:
数组排序:使用Arrays
类提供的排序方法对数组进行排序,例如:
Arrays.sort(numbers);
定义一个数字,按从小到大排序
import java.util.Arrays;
public class ArraySort {
public static void main(String[] args) {
int[] array = {5, 2, 9, 7, 1, 6};
Arrays.sort(array);
// 打印排序后的数组
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
}
数组的倒序排序
public class OrderSort {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入您要输入的数组的长度");
int length = sc.nextInt();
int[] arr = new int[length]; //数组的长度就等于输入的长度
for (int i = 0; i < arr.length; i++) {
System.out.println("请输入第"+(i+1)+"个元素");
int num = sc.nextInt();
arr[i] = num; //将输入的元素添加到数组中
}
//将一个arr数组变为包装类数组Arr(如Integer[])
Integer[] Arr= Arrays.stream(arr).boxed().toArray(Integer[]::new);
Arrays.sort(Arr, Collections.reverseOrder());
for (int i = 0; i < Arr.length; i++) {
System.out.print(Arr[i]+" ");
}
}
}
数组复制:使用System.arraycopy()
方法将一个数组的元素复制到另一个数组中,例如:
System.arraycopy(Array, 0, newArray, 0, Array.length);
public class ArrayCopy {
public static void main(String[] args) {
int[] Array = {1, 2, 3, 4, 5};
int[] newArray = new int[Array.length];
System.arraycopy(Array, 0, newArray, 0, Array.length);
// 打印目标数组
for (int i = 0; i < Array.length; i++) {
System.out.print(newArray[i] + " ");
}
}
}
数组查找:使用循环遍历数组或使用Arrays.binarySearch()
方法在已排序的数组中查找元素的索引,例如:
int index = Arrays.binarySearch(numbers, 5);
2.8.6 数组的限制
- 数组的长度是固定的,一旦数组被创建,其长度无法更改。
- 数组只能存储相同类型的元素。
- 数组的索引从0开始,最大索引为数组长度减1。
2.8.7 二维数组
二维数组是一个包含多个一维数组的数组,每个一维数组都具有相同的长度。可以将二维数组看作是一个表格或矩阵,其中行和列分别表示数组的第一维和第二维。
在Java中,可以通过以下方式创建和操作二维数组:
声明和初始化二维数组:
// 声明一个二维数组
int[][] matrix;
// 初始化一个二维数组
int[][] matrix = new int[3][4];
上述代码中,我们声明了一个名为matrix
的二维数组,然后使用new
关键字初始化了一个3行4列的二维数组。
访问二维数组元素:
int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
// 访问二维数组的元素
int element = matrix[1][2]; // 获取第2行第3列的元素,值为6
上述代码中,我们创建了一个3行3列的二维数组matrix
,并使用matrix[1][2]
访问了第2行第3列的元素,其值为6。
遍历二维数组:
int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
// 遍历二维数组并打印元素
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
System.out.print(matrix[i][j] + " ");
}
System.out.println(); // 换行
}
例子:
定义一个二维数组,通过键盘录入行列数,向二维数组中,添加随机数.
package com.Sort;
import java.util.Scanner;
public class Two_dimensional_array {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入行数");
int m = sc.nextInt();
System.out.println("请输入列数");
int n = sc.nextInt();
int[][] Arr = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
Arr[i][j] = (int) (Math.random()*10);
}
}
for (int[] ints : Arr) {
for (int anInt : ints) {
System.out.print(anInt + " ");
}
System.out.println();
}
第三章 面向对象
3.1 什么是对象
3.1.1 对象的定义
对象(Object)是面向对象编程中的一个基本概念,它是类的实例化,是类的具体实体。对象具有自己的状态(属性)和行为(方法),可以通过对象来操作和处理数据。
在面向对象编程中,通过定义类来描述对象的属性和行为。类是对象的模板或蓝图,而对象是类的具体实例。通过创建类的实例,也就是实例化对象,可以使用类中定义的属性和方法。
3.1.2 对象的特点:
- 状态(State):对象具有自己的状态,也就是属性。状态可以是对象的数据,比如一个人对象的状态可以包括姓名、年龄、性别等。
- 行为(Behavior):对象具有自己的行为,也就是方法。行为可以是对象的操作和处理数据的能力,比如一个人对象的行为可以包括吃饭、睡觉、工作等。
- 标识(Identity):每个对象都有唯一的标识,可以通过标识来区分不同的对象。对象的标识可以通过引用来表示,每个对象都有一个内存地址。
通过对象,可以将现实世界的事物抽象为代码中的实体,实现代码的复用和可维护性。对象是面向对象编程的核心,它使得程序的设计和实现更加灵活和可扩展。
3.2 什么是类
3.2.1 类的定义
类(Class)是面向对象编程中的一个基本概念,它是对象的模板或蓝图,用于描述具有相同属性和行为的一组对象。
类定义了对象的属性和行为,是对象的抽象表示。通过类,可以实例化对象,即创建类的实例。类定义了对象的共同特征和行为,对象则是类的具体实例。
3.2.2 类的特点
- 属性(Properties):类可以定义对象的属性,也称为成员变量或实例变量。属性表示对象的状态,可以是对象的数据,比如一个人类的属性可以包括姓名、年龄、性别等。
- 方法(Methods):类可以定义对象的行为,也称为成员方法。方法表示对象的操作和处理数据的能力,比如一个人类的方法可以包括吃饭、睡觉、工作等。
- 构造函数(Constructor):类可以定义构造函数,用于创建对象时进行初始化操作。构造函数可以设置对象的初始状态。
- 继承(Inheritance):类可以通过继承来扩展和复用代码。子类可以继承父类的属性和方法,并可以添加自己的属性和方法。
- 封装(Encapsulation):类可以通过封装将数据和操作数据的方法封装在一起,以便于管理和使用。封装可以隐藏实现细节,提供对外的接口。
- 多态(Polymorphism):类可以通过多态实现同一个方法在不同的对象上具有不同的行为。多态提供了代码的灵活性和可扩展性。
通过类的定义,可以创建多个对象,每个对象都具有类定义的属性和行为。类是面向对象编程的基础,它使得程序的设计和实现更加灵活和可扩展。
3.3 封装
3.3.1 封装的定义
封装(Encapsulation)是面向对象编程中的一种重要特性,它将数据和操作数据的方法封装在一个类中,对外部隐藏了具体的实现细节,只提供了公共的接口供其他类使用。
封装的主要目的是隐藏对象的内部实现细节,只向外部提供必要的接口,以保护数据的安全性和完整性。通过封装,可以将数据和方法进行组合,形成一个独立的单元,对外部提供统一的访问方式,避免了直接操作数据的风险。
3.3.2 封装的主要特点
- 数据隐藏:封装将数据隐藏在类的内部,外部无法直接访问和修改数据,只能通过类提供的接口进行操作。
- 接口访问:封装通过公共的接口暴露给外部,外部只能通过接口来访问和操作数据,无法直接修改数据。
- 数据保护:封装可以对数据进行保护,通过限制外部对数据的访问方式,确保数据的安全性和完整性。
- 实现细节隐藏:封装隐藏了对象的内部实现细节,只向外部提供必要的接口,使得对象的使用更加简单和安全。
封装的好处是提高了代码的可靠性和可维护性。通过封装,可以将数据和操作数据的方法封装在一起,形成一个独立的单元,使得代码更加模块化和易于理解。封装还可以隐藏对象的内部实现细节,使得对象的使用更加简单和安全。
封装还可以实现信息隐藏。通过将数据隐藏在类的内部,只暴露必要的接口,可以控制对数据的访问权限,保护数据的安全性和完整性。这样可以防止外部直接访问和修改数据,减少了出错的可能性。
3.4 构造函数
3.4.1 构造函数的定义
构造函数(Constructor)是类中的一种特殊方法,用于在创建对象时进行初始化操作。构造函数的名称与类名相同,没有返回类型,且在创建对象时会自动调用。
构造函数的作用是为对象分配内存空间,并对对象的属性进行初始化。通过构造函数,可以确保对象在创建时具有合适的初始状态,以便于后续的操作和使用。
3.4.2 构造函数的以下特点
- 与类同名:构造函数的名称与类名相同,这样在创建对象时可以通过类名直接调用构造函数。
- 没有返回类型:构造函数没有返回类型,不需要声明返回值,因为它的主要目的是初始化对象,而不是返回值。
- 自动调用:构造函数在创建对象时会自动调用,不需要显式地调用。当使用 new 关键字创建对象时,会自动调用相应的构造函数。
- 可以重载:类可以定义多个构造函数,根据参数的不同来进行重载。在创建对象时,根据传入的参数类型和数量,会自动调用匹配的构造函数。
- 可以包含初始化代码:构造函数可以包含一些初始化代码,用于对对象的属性进行初始化。可以在构造函数中对属性赋初值,或者调用其他方法进行初始化操作。
构造函数的使用可以保证对象在创建时具有正确的初始状态,提高代码的可靠性和可维护性。通过构造函数,可以方便地创建和初始化对象,减少重复的代码。
3.5 继承
3.5.1 继承的定义
继承(Inheritance)是面向对象编程中的一个重要概念,它允许一个类(子类)继承另一个类(父类)的属性和方法,从而扩展和复用代码。
3.5.2 继承的特点
- 父类和子类:继承关系中,被继承的类称为父类(或基类、超类),继承的类称为子类(或派生类)。
- 继承关系:子类继承父类的属性和方法,子类可以直接访问父类的非私有成员(属性和方法)。
- 继承的好处:继承可以实现代码的复用和扩展,子类可以继承父类的属性和方法,无需重复编写相同的代码。
- 子类的特性:子类可以添加自己的属性和方法,也可以重写父类的方法,以实现自己特定的行为。
- 继承的层次:继承可以形成多层次的继承关系,子类可以再次被其他类继承,形成更复杂的继承结构。
- 访问修饰符:通过访问修饰符,可以控制子类对父类成员的访问权限,如 public、protected、private。
继承的主要作用是实现代码的复用和扩展。通过继承,可以从已有的类中派生出新的类,继承父类的属性和方法,并可以添加自己的特定功能。这样可以减少代码的重复编写,提高代码的可维护性和可扩展性。
继承还可以实现多态性。由于子类继承了父类的方法,可以在父类的引用中使用子类的对象,实现同一个方法在不同的对象上具有不同的行为。多态性提供了代码的灵活性和可扩展性。
3.6 多态
3.6.1 多态的定义
多态(Polymorphism)是面向对象编程中的一个重要概念,指的是同一个方法在不同的对象上具有不同的行为。多态性提供了代码的灵活性和可扩展性。
多态性的实现依赖于继承和方法重写。当子类继承了父类的方法,并对该方法进行了重写时,子类的对象可以在父类的引用中使用,调用该方法时会根据实际的对象类型执行相应的方法。
3.6.2 多态性的主要特点
- 同一个方法在不同的对象上具有不同的行为。
- 多态性实现了方法的重写和动态绑定。
- 多态性提供了代码的灵活性和可扩展性。
多态性的好处是提高了代码的灵活性和可扩展性。通过多态性,可以在父类的引用中使用子类的对象,调用相同的方法时,会根据实际的对象类型执行相应的方法。这样可以实现代码的复用和扩展,减少了重复编写相似代码的工作量。
多态性还可以实现接口的统一。通过多态性,可以将一组对象统一视为同一种类型,调用相同的方法,实现对不同对象的统一操作。这样可以提高代码的可读性和可维护性。
总结来说,多态性是面向对象编程中一个重要的特性,通过继承和方法重写实现同一个方法在不同的对象上具有不同的行为。多态性提供了代码的灵活性和可扩展性,实现了代码的复用和接口的统一。
3.7 装箱和拆箱
装箱(Boxing)和拆箱(Unboxing)是Java中基本数据类型和对应的包装类之间的转换过程。
装箱是指将基本数据类型转换为对应的包装类对象。Java提供了一些包装类,如Integer、Double、Boolean等,用于封装基本数据类型的值。通过装箱,可以将基本数据类型的值包装成对应的包装类对象,以便在需要使用对象的场景中进行操作。
拆箱是指将包装类对象转换为对应的基本数据类型。通过拆箱,可以从包装类对象中提取出基本数据类型的值,以便进行计算和其他操作。
装箱和拆箱的过程是自动进行的,不需要显式地调用相应的方法。Java编译器会根据上下文自动进行装箱和拆箱操作。
下面是一些示例代码,演示了装箱和拆箱的过程:
int num = 10; // 基本数据类型
Integer obj = num; // 装箱,将int类型的值转换为Integer对象
double value = 3.14; // 基本数据类型
Double obj2 = value; // 装箱,将double类型的值转换为Double对象
Integer obj3 = 20; // 自动装箱
int num2 = obj3; // 拆箱,将Integer对象转换为int类型的值
Double obj4 = 3.14; // 自动装箱
double value2 = obj4; // 拆箱,将Double对象转换为double类型的值
在上面的代码中,我们定义了一些基本数据类型的变量和对应的包装类对象。通过将基本数据类型的值赋给包装类对象,实现了装箱操作。而将包装类对象赋给基本数据类型的变量,实现了拆箱操作。在这些操作中,Java编译器会自动进行装箱和拆箱的转换。
装箱和拆箱的过程可以方便地在基本数据类型和包装类之间进行转换,使得我们可以在需要使用对象的场景中操作基本数据类型的值。同时,也可以在需要使用基本数据类型的场景中使用包装类对象。这样可以提高代码的灵活性和可读性。
3.8 抽象
3.8.1 抽象的定义
抽象(Abstraction)是面向对象编程中的一个重要概念,指的是将具体的事物抽象成一种更一般、更通用的概念或模型。抽象可以隐藏具体的实现细节,只关注对象的属性和行为。
抽象的主要目的是简化问题,提取出问题的本质和共性,忽略不必要的细节。通过抽象,可以将一组具有相似属性和行为的对象归纳为一个抽象类或接口,定义它们的共同特征和行为,从而实现代码的复用和扩展。
3.8.2 抽象的主要特点
- 忽略细节:抽象忽略了具体对象的实现细节,只关注对象的属性和行为。
- 提取共性:抽象将一组具有相似属性和行为的对象归纳为一个抽象类或接口,定义它们的共同特征和行为。
- 简化问题:抽象简化了问题,提取出问题的本质和共性,忽略不必要的细节。
- 代码复用和扩展:通过抽象,可以实现代码的复用和扩展,定义通用的抽象类或接口,具体对象可以继承或实现该抽象,重写或实现相应的方法。
抽象的好处是提高了代码的可读性和可维护性。通过抽象,可以将具体的事物抽象成一种更一般、更通用的概念或模型,简化了问题,提取出问题的本质和共性。抽象还可以实现代码的复用和扩展,定义通用的抽象类或接口,具体对象可以继承或实现该抽象,重写或实现相应的方法。
总结来说,抽象是面向对象编程中的一个重要概念,通过将具体的事物抽象成一种更一般、更通用的概念或模型,简化问题,提取共性。抽象提高了代码的可读性和可维护性,实现了代码的复用和扩展。
示例:动物类(Animal)。动物是一个广泛的概念,包括了许多不同的具体动物,如狗、猫、鸟等。我们可以将动物抽象成一个抽象类或接口,定义它们的共同特征和行为,如呼吸、移动、发声等。
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public abstract void breathe();
public abstract void move();
public String getName() {
return name;
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void breathe() {
System.out.println(getName() + "在呼吸。");
}
@Override
public void move() {
System.out.println(getName() + "在运动。");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void breathe() {
System.out.println(getName() + "在呼吸");
}
@Override
public void move() {
System.out.println(getName() + "在跳");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog("巴迪");
Animal cat = new Cat("露娜");
dog.breathe();
dog.move();
cat.breathe();
cat.move();
}
}
3.9 this关键字和super关键字
this关键字和super关键字是Java中的两个特殊关键字,用于访问当前对象和父类对象。
3.9.1 this关键字
- this关键字代表当前对象,可以用于访问当前对象的成员变量和成员方法。
- 在一个类的成员方法中,可以直接使用this关键字来引用当前对象。
- 当方法的参数名和成员变量名相同时,可以使用this关键字来区分它们。
- this关键字也可以用于调用当前对象的其他构造方法,实现构造方法的重载。
3.9.2 super关键字
- super关键字代表父类对象,可以用于访问父类的成员变量和成员方法。
- 在子类中,可以使用super关键字来调用父类的构造方法,实现子类构造方法的重载。
- 当子类和父类有同名的成员变量或成员方法时,可以使用super关键字来引用父类的成员。
下面是一些示例代码,演示了this关键字和super关键字的使用:
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void eat() {
System.out.println("动物在吃");
}
}
class Dog extends Animal {
String breed;
Dog(String name, String breed) {
super(name); // 调用父类的构造方法
this.breed = breed;
}
void eat() {
super.eat(); // 调用父类的eat方法
System.out.println("狗在吃");
}
void display() {
System.out.println("名字: " + this.name); // 使用this关键字访问当前对象的成员变量
System.out.println("品种: " + this.breed);
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("汤姆", "拉布拉多");
dog.eat(); // 输出: 动物在吃 \n 狗在吃
dog.display(); // 输出: 名字: 汤姆 \n 品种:拉布拉多
}
}
在上面的代码中,我们定义了Animal类和Dog类。Dog是Animal的子类。
在Dog类的构造方法中,我们使用super关键字调用了父类Animal的构造方法,以初始化父类的成员变量name。
在Dog类的eat方法中,我们使用super关键字调用了父类Animal的eat方法,并在子类中添加了自己的逻辑。
在Dog类的display方法中,我们使用this关键字访问了当前对象的成员变量name和breed。
通过使用this关键字和super关键字,我们可以在子类中访问父类的成员和构造方法,以及在当前对象中访问自己的成员。这可以实现类之间的继承和调用关系,增强了代码的灵活性和可复用性。
3.10 静态属性和动态属性
静态属性和动态属性是面向对象编程中的概念,用于描述类的属性的特性。
3.10.1 静态属性(Static Properties)
- 静态属性是属于类的属性,而不是属于类的实例(对象)的属性。
- 静态属性在类加载时被初始化,且只有一份拷贝,所有的类实例共享同一个静态属性。
- 静态属性可以通过类名直接访问,不需要创建类的实例。
- 静态属性通常用于表示类级别的信息,如常量、计数器等。
3.10.2 动态属性(Instance Properties)
- 动态属性是属于类的实例(对象)的属性,每个对象都有自己的一份属性。
- 动态属性在创建对象时被初始化,可以有不同的值。
- 动态属性必须通过类的实例来访问。
下面是一个示例代码,演示了静态属性和动态属性的使用:
class Car {
static int numberOfCars; // 静态属性
String color; // 动态属性
Car(String color) {
this.color = color;
numberOfCars++; // 每次创建对象时,静态属性加1
}
}
public class Main {
public static void main(String[] args) {
Car passat = new Car("金黑");
Car Range_Rover = new Car("雪山白");
System.out.println("帕萨特的颜色: " + car1.color); // 动态属性,输出:金黑
System.out.println("路虎揽胜的颜色: " + car2.color); // 动态属性,输出:雪山白
System.out.println("汽车数量: " + Car.numberOfCars); // 静态属性,输出:2
}
}
在上面的代码中,我们定义了一个Car类,该类具有一个静态属性numberOfCars和一个动态属性color。
在Car类的构造方法中,每次创建对象时,我们通过对静态属性numberOfCars进行自增操作,来统计创建的Car对象的数量。
在main方法中,我们创建了两个Car对象car1和car2,并分别为它们的color属性赋值。
最后,我们通过实例car1和car2访问了它们各自的动态属性color,以及通过类名Car访问了静态属性numberOfCars。
通过使用静态属性和动态属性,我们可以在类级别和对象级别上存储和访问数据,以满足不同的需求。静态属性适用于表示类级别的信息,而动态属性适用于表示对象级别的信息。
3.11 静态代码块和动态代码块
静态代码块和动态代码块是Java中的两种特殊的代码块,用于在类加载和对象创建时执行一些特定的操作。
3.11.1 静态代码块(Static Initialization Block)
- 静态代码块是在类加载时执行的代码块,只会执行一次。
- 静态代码块使用
static
关键字来修饰,用于初始化静态成员变量或执行一些静态操作。 - 静态代码块在类加载时按照它们在类中的顺序执行。
3.11.2 动态代码块(Instance Initialization Block)
- 动态代码块是在对象创建时执行的代码块,每次创建对象都会执行一次。
- 动态代码块没有使用任何关键字来修饰,直接写在类中。
- 动态代码块在构造方法之前执行,可以用于初始化实例成员变量或执行一些实例操作。
下面是一个示例代码,演示了静态代码块和动态代码块的使用:
class MyClass {
static int staticVar; // 静态成员变量
int instanceVar; // 实例成员变量
static {
System.out.println("静态初始化块"); // 静态代码块
staticVar = 10; // 初始化静态成员变量
}
{
System.out.println("实例初始化块"); // 动态代码块
instanceVar = 20; // 初始化实例成员变量
}
MyClass() {
System.out.println("构造函数"); // 构造方法
}
}
public class Main {
public static void main(String[] args) {
MyClass obj1 = new MyClass(); // 创建对象
MyClass obj2 = new MyClass(); // 创建对象
}
}
在上面的代码中,我们定义了一个MyClass类,该类具有一个静态成员变量staticVar和一个实例成员变量instanceVar。
在MyClass类中,我们使用静态代码块初始化静态成员变量staticVar,并使用动态代码块初始化实例成员变量instanceVar。
在main方法中,我们创建了两个MyClass对象obj1和obj2。当创建对象时,首先执行静态代码块,然后执行动态代码块,最后执行构造方法。
运行上述代码,将会输出以下结果:
静态初始化块
实例初始化块
构造函数
实例初始化块
构造函数
通过使用静态代码块和动态代码块,我们可以在类加载和对象创建时执行一些特定的操作,如初始化成员变量、加载资源、注册驱动程序等。这些代码块可以帮助我们更好地管理类和对象的初始化过程。
3.12 final
在Java中,关键字final用于表示最终的、不可变的实体。它可以用于变量、方法和类。
3.12.1 final变量
- 当用final修饰一个变量时,该变量将成为一个常量,一旦被赋值后就不能再修改。
- final变量必须在声明时或构造方法中进行初始化。
- final变量的命名通常使用大写字母和下划线,多个单词之间用下划线分隔。
3.12.2 final方法
- 当用final修饰一个方法时,该方法不能被子类重写。
- final方法在设计类时可以起到一种限制和保护的作用。
3.12.3 final类
- 当用final修饰一个类时,该类不能被继承。
- final类通常用于表示不可变的实体或工具类,例如String类和Math类。
下面是一些示例代码,演示了final的使用:
class MyClass {
final int constantVar = 10; // final变量
final void finalMethod() { // final方法
System.out.println("这是一个final方法");
}
}
final class FinalClass { // final类
// ...
}
// 无法继承final类
// class SubClass extends FinalClass {
// // ...
// }
public class Main {
public static void main(String[] args) {
final int x = 5; // final变量
// 无法修改final变量的值
// x = 10;
MyClass obj = new MyClass();
obj.finalMethod();
}
}
在上面的代码中,我们定义了一个MyClass类,其中包含一个final变量constantVar和一个final方法finalMethod。我们还定义了一个FinalClass类,并将其声明为final类。
在main方法中,我们创建了一个final变量x,并尝试修改其值,但由于它是final变量,所以无法修改。然后,我们创建了一个MyClass对象,并调用final方法finalMethod。
使用final关键字可以提供一些限制和保护,确保变量、方法或类的不可修改性和不可继承性。这可以在编程中提供更安全和可靠的代码