chenjiancheng
第一章:初识Java与面向对象程序设计
一、Java语言的特点
Java语言迄今为止依然火热的原因,离不开它的显著特点。Java是一门简单的、面向对象的优秀编程语言,它具有跨平台性、可移植性、安全性、健壮性、编译和解释性、高性能和动态性等特点,支持多线程、分布式计算与网络编程等高级特性。
二、面向对象程序设计思想
1、面向过程程序设计
面向过程是程序设计的一种思想,它的核心是分析出问题的解决步骤,“先干什么后干什么”,然后用函数把这些步骤一个一个实现,最后按照流程调用。
2、面向对象程序设计
面向对象程序设计的思维方式是一种更符合人们思考习惯的思想。面向对象将构成问题的事物分解成各个对象,这些对象是为了描述某个事物在整个问题解决步骤中的行为。
面向对象以对象为核心,强调事件的主角、主体。在宏观使用面向对象进行把控,而微观上依然是面向过程。如果说面向过程的思想是执行者,那么面向对象的思想就是指挥者。
面向对象具有抽象、封装、继承、多态的特性,更符合程序设计中“高内聚、低耦合”的主旨,其编写的代码的可维护性、可读性、复用性、可扩展性远比面向过程高,但是性能相比面向过程偏低一些。
封装、继承、多态是面向对象的三大特性,这是任何一门面向对象编程语言都要具备的。
第二章:Java编程基础
一、选择结构
if语句
从结构化程序设计角度出发,Java有三种结构:顺序结构、选择结构、循环结构。
Java的基本结构就是顺序结构,除非特别指明,否则就按照顺序从上往下一句一句执行。顺序结构是最简单的算法结构,语句与语句之间,框与框之间是按从上到下的顺序进行的。顺序结构不必做过多说明,前面的程序都是顺序结构。
选择结构用于在代码中做一些逻辑判断,当满足某些条件时,执行某段代码。if语句就是选择结构的代表。通过if语句,能够实现各种各样的逻辑判断。
程序执行到if语句会进行判断:当条件表达式1为ture时,执行代码块1;否则,当条件表达式2为true时,执行代码块2;否则,当条件表达式3为true时,执行代码块3;否则,执行代码块n。其中,一个if语句之后可以有0至多个else if语句,可以有0或1个else语句。
接下来编写程序,接收用户输入的分数,对分数进行判断:90(包含)~ 100(包含)分为优秀,70(包含)~ 90分为良好,60(包含)~ 70分为及格,60分以下为不及格,代码如图所示:
if语句在使用过程中还需要注意以下两点。
(1)如果if选择结构秩序执行一条语句,那么可以省略{}。为了提高代码的易读性,建议不省略{}。
(2){}中的代码语句也称为代码块,在代码块定义的敞亮或变量的作用域仅限于代码块中,在代码块之外不能使用。
Switch语句
除了if语句外,switch语句也是选择结构。switch语句一般用于做一些精确值的判断。switch语句会根据表达式的值从相匹配的case标签处开始执行,一直执行到break语句处或者switch语句的末尾。如果case全都不匹配,则进入default语句。
接下来编写一个简单的加减乘除计算器。用户输入两个数字和计算符号,根据计算符号决定执行加法、减法、乘法、除法运算,如图代码所示:
switch语句判断的变量中,类型只能是byte、short、int、char、string和枚举,因此它的适用范围较窄,但对于精确值的判断,switch依然是非常方便的。
选择结构的嵌套
选择结构在使用上可以嵌套,if中的代码块也可以是switch语句,switch语句中的代码块也可以是if语句。通过嵌套,可以判断更加复杂的逻辑。
编写程序,判断一个年份是否为闰年。闰年的判断方法为:如果一个年份能被400整除,那么该年是闰年。否则,如果这个年份不能被100整除,但可以被4整除,也是闰年,如图代码所示:
两种结构对比
if语句和switch语句都可以实现逻辑判断,但他们的使用场景有所不同。if语句一般用于区间值的判断,而switch语句只能用于确定值的判断。凡是switch语句能够实现的,if语句都可以实现,反之则不行。
二、循环结构
for语句
循环结构是Java三大结构之一,它可以在满足一定条件下一直执行某一段程序,从而简化代码。for循环是最常见的循环结构。
首先执行一次循环初始化表达式,接着判断循环条件表达式,如果为false,则结束循环。如果为true,则执行循环体,之后执行循环后的操作表达式,重复以上操作,知道条件表达式的值为false为止。
接下来编写一个输出1~1000既能被5整除又能被3整除的数,并且每行输出5个,代码如图所示:
while语句
while语句相较于for循环更加简单,它的语法格式有点类似于if语句。 while语句的执行规则也很简单,只要条件表达式的值为true,就会执行循环体,直到条件表达式的值为false时才退出循环。while循环一般用于不确定循环次数的场景。接下来编写程序,找出前10个既能被7整除又能被11整除的数字,代码如图所示:
public class Test{
public static void main(String[] args){
int count = 0;
int num = 11;
while(count < 10){
if(num % 7 == 0 && num & 11 == 0){
System.out.println(num);
count++;
}
num++;
}
}
}
do...while语句
不管是for循环还是while循环,都会在循环之前判断循环条件,如果条件刚开始就为false,那么循环体就不会被执行。实际开发中,可能存在需要循环体至少执行一次的场景,此时就可以使用do...while循环语句。
首先执行循环体,之后再判断条件表达式,如果结果为true,就重复上述步骤,直到条件表达式的值为false为止。do…while语句的语法比较简单,但需要注意的细节是条件表达式最后有一个分号。
下面编写程序模拟登录,用户名为admin,密码为123456,如果用户输入的用户名和密码不匹配,就重新登录,直到用户名和密码完全匹配为止,代码如图所示:
break和continue语句
在任何循环语句的主体部分,均可用break控制循环的流程。break用于强行退出循环,不执行循环中剩余的语句。而 continue 则只能终止某次循环,继续下一次循环。
韩信点兵,三人一组余两人,五人一组余三人,七人一组余四人,且为了便于编队,人数不能是奇数,编写程序,计算最少需要多少名士兵,代码如图所示:
循环语句的嵌套
同选择结构一样,循环结构也可以任意嵌套。
接下来编写程序,寻找前20个素数,并且每5个为一行输出(如果一个数字不能被1和
它本身以外的数字整除,那么这个数字就称作素数),代码如图所示:
三、数组
数组的排序算法
实际开发中接触到的数组顺序可能比较乱,有时不得不对数组进行排序。常见的数组排序方法一共有8种:冒泡排序、选择排序、插入排序、堆排序、基数排序、希尔排序、快速排序、归并排序。这里介绍前两种排序方法,其他排序方法可参考数据结构等课程。
1、冒泡排序
冒泡排序的核心思想是,在要排序的序列中,对当前还未排好序的全部元素,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的数往上冒,就好像水泡上浮一样,如果它们的顺序错误,就把它们交换过来。
接下来编写代码,实现冒泡排序算法,代码如图所示:
2、选择排序
选择排序的核心思想是:在要排序的一组数中选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数中再找最小(或者最大)的与第2个位置的数交换,以此类推,直到第n-1个元素(倒数第2个数)和第n个元素(最后一个数)比较为止,每一轮排序都是找出它的最小值或者最大值的过程。
接下来通过代码实现选择排序,代码如下图所示:
四、二分查找法
前面通过遍历的方式查找元素所在的索引,这样虽然能够实现目标,但当数组过大时性能可能偏低。而二分查找法是性能更高效的查找算法。二分查找法又称折半查找法,其算法思想是每次查找数组最中间的值,通过比较大小关系,决定再从左边还是右边查询,直到查找到为止。二分查找法的优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插人、删除操作困难。因此,折半查找法适用于不经常变动而查找频繁的有序列表。
二分查找法依然需要使用到循环,但由于不知道循环次数,所以最好使用while循环实现,代码如图所示:
第三章 :面向对象程序设计(基础)
一、构造方法
定义:构造方法是类中的一种特殊成员方法,其方法名与类名相同,且没有返回值类型声明,包括 void 也不能有。
作用:主要用于在创建对象时完成对象的初始化工作,比如为对象的成员变量赋初值、进行一些必要的资源分配或执行其他一次性的初始化任务等,确保对象在创建后处于一个可使用的状态。
特点1、方法名与类名相同:这是构造方法的重要标识,编译器通过方法名来识别构造方法。例如,在 Java 中,如果有一个类名为 Person
,那么其构造方法的名字也必须是 Person
。
特点2、没有返回值类型:构造方法不能有返回值类型声明,甚至不能写 void
。这是因为构造方法的作用是初始化对象,而不是返回一个值。
特点3、可以有参数:构造方法可以接受参数,通过参数可以在创建对象时传递初始值给对象的成员变量,从而实现不同的初始化方式。
特点4、在创建对象时自动调用:当使用 new
关键字创建对象时,会自动调用相应的构造方法。如果类中没有显式定义构造方法,编译器会自动提供一个默认的无参构造方法;如果类中显式定义了构造方法,编译器则不会再提供默认构造方法。下面是代码示例:
public class Person {
private String name;
private int age;
// 无参构造方法
public Person() {
this.name = "unknown";
this.age = 0;
}
// 有参构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public static void main(String[] args) {
// 使用无参构造方法创建对象
Person person1 = new Person();
System.out.println("person1的姓名:" + person1.getName() + ",年龄:" + person1.getAge());
// 使用有参构造方法创建对象
Person person2 = new Person("哈哈", 23);
System.out.println("person2的姓名:" + person2.getName() + ",年龄:" + person2.getAge());
}
}
二、静态方法
静态方法是属于类本身而不是类的实例的方法,它可以在不创建类的实例的情况下被调用,通常用于执行与类相关但不依赖于特定实例的操作。
在不同的编程语言中,静态方法的定义语法有所不同。例如在 Java 中,使用 static
关键字来修饰方法,示例如下:
public class MathUtils {
public static int add(int a, int b) {
return a + b;
}
}
它的特点则是不依赖于实例,静态方法可以直接通过类名调用,无需创建类的实例。如上述代码中,可以直接使用 MathUtils.add(3, 5)
来调用 add
方法,而不需要先创建 MathUtils
的对象。
在静态方法内部,只能直接访问类的静态成员变量和静态方法,不能直接访问非静态成员变量和非静态方法,因为非静态成员是属于实例的,而静态方法在调用时可能不存在具体的实例。
静态方法在类被加载到内存时就已经存在,而不是在创建实例时才生成。这使得静态方法可以在程序的任何地方被调用,只要类已经被加载。
第四章:面向对象程序设计(进阶)
一、封装
封装是指将对象的状态信息(属性)和行为(方法)隐藏在类的内部,对外只提供有限的访问接口,以控制对对象内部数据和方法的访问。通过封装,可以将类的实现细节隐藏起来,只暴露必要的接口给外部使用,提高了代码的安全性和可维护性。
在大多数面向对象编程语言中,通过访问修饰符来控制类的成员(属性和方法)的访问权限。例如在 Java 中,有 public
、private
、protected
和默认(无修饰符)四种访问修饰符。
private:被修饰的成员只能在类的内部访问,外部类无法直接访问,从而实现了对数据的隐藏。
public:被修饰的成员可以在任何地方被访问,通常用于对外提供访问接口。
protected:被修饰的成员可以在类的内部、子类以及同一个包中的其他类中访问。
默认:在没有修饰符的情况下,成员可以在同一个包中的其他类中访问。
二、多态
多态指的是同一个行为具有多种不同表现形式或形态的能力。在面向对象编程中,多态主要体现在通过继承和接口实现,不同的类可以对相同的方法进行不同的实现,使得在运行时可以根据对象的实际类型来调用相应的方法。
方法重写则是在继承关系中,子类可以重写父类的方法。当通过子类对象调用被重写的方法时,会优先调用子类中重写后的方法,而不是父类中的方法。如:
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵喵");
}
}
而方法重载则是在同一个类中,可以定义多个同名的方法,但它们的参数列表不同,包括参数的类型、个数或顺序。在调用时,编译器会根据实际传入的参数来确定调用哪个方法。如:
class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
多态的实现条件则通常是在有继承或接口实现的情况下发生的,子类继承父类或实现接口,并重写父类或接口中的方法,从而实现多态。 子类需要重写父类的方法或在同一个类中进行方法重载,以提供不同的方法实现。在程序中,可以将子类对象赋值给父类引用变量,通过父类引用变量来调用子类重写的方法,实现运行时多态。
三、抽象类
抽象类是一种不能被实例化的类,它通常用于定义一些抽象的方法和属性,供子类继承和实现。抽象类使用 abstract
关键字进行声明,例如在代码中:
public abstract class Shape {
// 抽象类中的抽象方法,没有方法体
public abstract double getArea();
// 抽象类中可以有普通方法
public void printInfo() {
System.out.println("This is a shape.");
}
}
而它的特点则是有以下几点:
不能实例化:抽象类本身不能被直接创建对象,因为它可能包含未实现的抽象方法,其目的是为了被继承和扩展,而不是直接使用。
包含抽象方法:抽象类中可以包含抽象方法,抽象方法是一种没有方法体的方法,只有方法签名,它的具体实现由子类来完成。这使得抽象类可以定义一组通用的行为规范,供子类遵循和实现。
可以有普通方法和属性:抽象类中除了抽象方法外,还可以有普通的方法和属性,这些普通方法和属性可以被子类继承和使用,提供了一些通用的功能和状态。
可以被继承:抽象类的主要目的是被其他类继承,子类必须实现抽象类中的所有抽象方法,除非子类也是抽象类。通过继承抽象类,子类可以获得抽象类中定义的通用行为和属性,并根据自身的需求进行扩展和实现。
第五章:异常
异常是指在程序运行过程中发生的、违反正常程序流程的事件,如除数为零、数组越界、文件不存在、网络连接失败等。这些异常情况会导致程序无法正常继续执行,如果不进行处理,可能会使程序崩溃或产生不可预期的结果。
异常可以分为一下几点:
检查性异常:这类异常是程序在编译时就必须进行处理的异常,通常是由外部因素引起的,如文件读取错误、网络连接异常等。在 Java 中,检查性异常通常是 Exception 类的子类,但不是 RuntimeException 类的子类,例如 IOException、SQLException 等。
运行时异常:运行时异常是在程序运行期间可能发生的异常,这类异常通常是由程序逻辑错误引起的,如除数为零、数组越界、空指针引用等。在 Java 中,运行时异常是 RuntimeException 类的子类,例如 ArithmeticException、ArrayIndexOutOfBoundsException、NullPointerException 等。
错误:错误是指严重的、不可恢复的系统错误,如内存溢出、栈溢出、虚拟机错误等。错误通常是由系统环境或硬件问题引起的,程序一般无法处理这类错误,只能终止运行。在 Java 中,错误是 Error 类的子类,例如 OutOfMemoryError、StackOverflowError 等。
而异常的处理机制则是抛出异常;当程序中发生异常情况时,可以使用 throw
语句抛出一个异常对象,将异常传递给调用者。
异常的处理作用:
高程序的健壮性:通过异常处理机制,程序可以在发生异常情况时进行适当的处理,避免程序崩溃或产生不可预期的结果,从而使程序更加健壮和可靠。
分离错误处理代码和业务逻辑代码:异常处理机制可以将错误处理代码与业务逻辑代码分离,使业务逻辑代码更加清晰和易于理解。在业务逻辑代码中,只需要关注正常的业务流程,而将异常处理交给专门的异常处理代码来完成。
提供统一的错误报告和处理机制:异常处理机制可以提供统一的错误报告和处理机制,使程序中的所有异常情况都能够得到统一的处理和报告。这有助于提高程序的可维护性和可管理性,方便开发人员快速定位和解决问题。
第六章:Java常用类
一、StringBuffer类和StringBuilder类
在面向对象程序设计中,Java 的StringBuffer类和StringBuilder类都用于处理字符串,它们有相似的功能,但在某些方面也存在差异。
他们的相同点是两者都用于表示可变的字符序列,在需要频繁对字符串进行修改操作时,如拼接、插入、删除等,它们比不可变的String
类更高效,因为不需要每次操作都创建新的字符串对象。它们提供了许多相似的方法,如append()
用于追加字符或字符串,insert()
用于在指定位置插入字符或字符串,delete()
和deleteCharAt()
用于删除字符或字符串的一部分,replace()
用于替换指定范围内的字符等。
而他们的的不同点则是线程安全性和性能,其中线程安全性:
StringBuffer:是线程安全的,其内部方法大多使用了synchronized
关键字进行同步修饰,这意味着在多线程环境下,多个线程可以安全地访问和操作同一个StringBuffer对象,不会出现数据不一致或并发冲突的问题。
StringBuilder:是非线程安全的,在单线程环境下,它的性能比StringBuffer要高,因为不需要进行同步操作的额外开销。但在多线程环境中,如果多个线程同时访问和修改同一个StringBuilder对象,可能会导致数据错误或其他不可预期的结果。
性能不同在于在单线程环境下,由于StringBuilder不需要进行同步操作,所以其性能通常比StringBuffer
更好,尤其是在进行大量字符串拼接或修改操作时,性能差异会比较明显。在多线程环境中,如果对性能要求不是特别高,且需要保证线程安全,那么StringBuffer是更好的选择;如果确定是在单线程环境中使用,并且对性能有较高要求,那么StringBuilder更合适。代码如下:
public class StringBufferAndStringBuilderExample {
public static void main(String[] args) {
// 使用StringBuffer
StringBuffer stringBuffer = new StringBuffer("Hello");
stringBuffer.append(", World!");
System.out.println(stringBuffer.toString());
// 使用StringBuilder
StringBuilder stringBuilder = new StringBuilder("Hello");
stringBuilder.append(", World!");
System.out.println(stringBuilder.toString());
}
}