第一章 Java语言概述与基础语法

1.1 数据类型

Java是强类型语言,每个变量必须声明其类型。

Java的数据类型分为两大类:

基本数据类型(Primitive Type)和引用数据类型(Reference Type)。

基本数据类型共有8种 :

整型( byte、short、int、long)

浮点型(float、double)

字符型(char)

布尔型(boolean)

引用数据类型:

类(Class)

接口(Interface)

数组(Array)等

理解数据类型的取值范围、默认值以及它们之间的转换规则 ,是编写正确Java程序的基础,也是软考中频繁出现的考点。

数据类型

关键字

占用字节

取值范围

默认值

字节型

byte

1

-128 ~ 127

0

短整型

short

2

-32768 ~ 32767

0

整型

int

4

-231 ~ 231-1

0

长整型

long

8

-263 ~ 263-1

0L

单精度浮点

float

4

约+-3.4E38

0.0f

双精度浮点

double

8

约+-1.8E308

0.0d

字符型

char

2

0 ~ 65535(Unicode)

'\u000

0'

布尔型

boolean

1(bit)

true / false

false

表1-1 Java基本数据类型一览

1.1.1 类型转换

Java中的类型转换分为自动类型转换(隐式转换)和强制类型转换(显式转换)两种。

自动类型转换发生在从小范围类型向大范围类型赋值时,

转换顺序为:byte -> short -> int -> long -> float -> double。

需要注意的是,虽然float只占4字节而long占8字节 ,但float的表示范围远大于long,因此long可以自动转换为float。

强制类型转换则需要显式使用括号指定目标类型,如(int)3.14,但可能导致精度丢失或数据溢出。

在软考中 ,类型转换的优先级和精度问题是常见考点,尤其是byte、short参与运算时自动提升为int这一规则。

//  自动类型转换 
int  a  =  100;  long  b  =  a;  //  int  ->  long,  自动转换  
double  c  =  b;  //  long  ->double,  自动转换  
//  强制类型转换 
double pi = 3.14159; int i =  (int)  pi; // i = 3, 精度丢失 
// byte运算提升为int
byte x = 1, y = 2; byte z =  (byte)(x  + y); // 必须强转, x+y已提升为int

1.2 变量与常量

变量是程序中存储数据的命名内存空间。

Java中变量必须先声明后使用,声明时需要指定数据类型和变量名。

根据声明位置的不同,变量分为成员变量(实例变量和类变量)和局部变量。

成员变量定义在类中、方法外 ,有默认初始值;

局部变量定义在方法内,没有默认初始值  ,必须先赋值后使用。

类变量使用static修饰 ,属于类而非实例,所有对象共享同一份类变量。

常量使用final关键字修饰 ,表示一旦赋值就不能再修改。

常量命名惯例采用全大写字母加下划线的方式,如MAX_VALUE。

对于基本类型,final保证值不可变 ;

对于引用类型,final保证引用不可变(不能指向新对象),但对象内部状态仍可修改。

软考中常考final修饰变量、方法和类的不同含义 :

final变量不可修改、final方法不可重写、final类不可继承。

public class VariableDemo { 
// 成员变量(实例变量) - 有默认值 
int instanceVar; // 默认0 
// 类变量(static) - 有默认值 
static int classVar; // 默认0 
// 常量 
final double PI = 3.14159; 
public void method() { 
// 局部变量 - 无默认值,必须初始化 
int localVar = 10; 
// final局部变量(常量) 
final int MAX = 100;  }  
}

1.3 运算符

Java运算符按照功能可分为算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符和条件运算符等。

算术运算符包括+、-、 *、/、%、++、-- ,其中整数除法截断小数部分,%为取模运算。

关系运算符包括==、!=、>、<、>=、<=,返回boolean值。

逻辑运算符包括&&(短路与) 、 ||(短路或) 、!(非) 、&(逻辑与) 、 |(逻辑或),其中&&和||具有短路特性 ,即当第一个操作数已能确定结果时 ,不再计算第二个操作数。

位运算符包括&(按位与) 、 |(按位或) 、 ^(按位异或) 、~(按位取反) 、<<(左移)、 >>(右移) 、>>>(无符号右移),在软考中偶有涉及。

条件运算符(三目运算符)格式为: 条件?表达式1:表达式2,是if-else的简写形式。

运算符优先级从高到低大致为:单目 > 算术 >  移位 > 关系 > 逻辑 > 三目 > 赋值 ,建议在复杂表达式中使用括号明确优先级。

1.4 控制结构

1.4.1 条件语句

Java提供了if-else和switch两种条件语句。

if-else语句用于根据条件执行不同的代码分支 ,支持if、if-else、if-else if-else三种形式。

switch语句用于多值匹配,从Java 7开始支持String类型。

switch中每个case分支必须以break结尾 ,否则会发生"穿透"现象(fall-through),即继续执行后续case的代码直到遇到break或switch结束。

软考中常考switch穿透和default分支的执行时机。

// if-else if-else 
int score = 85; 
if  (score >= 90) { 
System.out.println("优秀");  
} else if(score >= 80) { 
System.out.println("良好");  
} else { 
System.out.println("一般");  
} 
// switch语句(支持String) 
String day =  "MON";  
switch  (day)  {  
case  "MON":  
case  "TUE":  
System.out.println("工作日" ); 
break; 
case  "SAT": 
case  "SUN": 
System.out.println("周末"); 
break; 
default:
System.out.println("未知");  
}

1.4.2 循环语句

Java提供三种循环结构:for、while和do-while。

for循环最常用,适合已知循环次数的场景  ,其语法为for(初始化;条件;迭代){循环体}。

while循环先判断条件再执行 ,适合循环次数不确定的场景。

do-while循环先执行一次循环体再判断条件,保证循环体至少执行一次。

Java 5引入了增强for循环(for-each),用于遍历数组和集合,语法为for(元素类型 变量名:数组或集合){循环体}。

循环控制语句break用于跳出当前循环(可配合标签跳出外层循环),continue用于跳过本次迭代进入下一次。

在嵌套循环中 ,可以使用带标签的break和continue来控制外层循环,标签格式为"标签名:", 写在循环之前。

软考中常考break和continue在嵌套循环中的执行流程。

// for循环 
for  (int i = 0; i < 10; i++) { 
if  (i == 3) continue; // 跳过3  
if  (i == 7) break; // 到7停止 
System.out.println(i);  
} 
// 增强for循环(for-each) 
int[] arr = {1, 2, 3, 4, 5}; 
for  (int num  :  arr)  { 
System.out.println(num);  
} 
// 带标签的break outer
for  (int  i = 0; i < 5; i++) { 
for  (int j = 0; j < 5; j++) { 
if  (i * j == 6) break outer;  
}  

1.5 数组

数组是存储同类型数据的固定长度容器。

Java数组是引用类型,通过new关键字创建,下标从0开始。

数组声明有两种格式:类型[] 数组名(推荐)和类型 数组名[]。

数组初始化有三种方式:

静态初始化(直接指定元素 ,如int[] a = {1,2,3})

动态初始化(指定长度 ,如new int[5])

匿名数组(如new int[]{1,2,3})

多维数组本质是数组的数组,如int[][] arr = new int[3][4]创建3行4列的二维数组,各行长度可以不同(锯齿数组)。

数组常用操作包括 :

通过length属性获取长度

通过Arrays工具类进行排序(Arrays.sort())

二分查找(Arrays.binarySearch())

填充(Arrays.fill())

比较(Arrays.equals())

转换为字 符串(Arrays.toString())

数组越界访问会抛出ArrayIndexOutOfBoundsException异常。

软考中 常考数组的声明方式、初始化语法和Arrays工具类的使用。

// 数组声明与初始化 
int[] arr1 = {1, 2, 3}; 
// 静态初始化 
int[] arr2 = new int[5]; 
// 动态初始化,默认0 
int[] arr3 = new int[]{4, 5, 6}; //  匿名数组 
// 二维数组
int[][] matrix = {{1,2}, {3,4,5}, {6}}; 
// Arrays工具类 
import java.util.Arrays; 
Arrays.sort(arr1); 
// 排序 
int idx = Arrays.binarySearch(arr1, 2); 
// 二分查找 
System.out.println(Arrays.toString(arr1)); //  [1, 2, 3]

第二章 面向对象编程核心

2.1 类与对象

类是对象的模板,定义了对象的属性(成员变量)和行为(方法)。

对象是类的实例,通过new关键字创建。

类的定义使用class关键字 ,包含成员变量、构造方法、普通方法和内部类等组成部分。

构造方法用于初始化对象,与类同名、无返回值类型(连void都没有),可以重载。

如果不显式定义构造方法,编译器会自动生成一个无参构造方法 ;

一旦自定义了构造方法,默  认构造方法就不再生成。

this关键字代表:

当前对象的引用,用于区分成员变量和局部变量同名的情况;

在构造方法中调用另一个构造方法(this()必须放在构造方法的第一行);

将当前对象作为参数传递给其他方法。

软考中this的用法是重要考点,尤其是构造方法中this()的调用规则。

public class Student { 
// 成员变量 
private String name; 
private int age; 
// 无参构造 public

Student() { this("未知", 0); // 调用两参构造  } 
// 有参构造 
public Student(String name, int age) { 
this.name = name; // this区分成员变量和参数 
this.age = age;  } 
// 方法 
public void study()  {

System.out.println(name +  "正在学习");  }  
}

2.2 封装

封装是面向对象的核心特征之一 ,指将数据(属性)和操作数据的方法绑定在一起 ,并隐藏对象的内部实现细节 ,仅通过公共接口与外界交互。

在Java中 ,封装通过访问控制修饰符实现:

private(私有 ,仅本类可见)

default/包访问(默认 ,同包可见)

protected(受保护 ,同包及子类可见)

public(公共 ,所有类可见)。

封装的典型做法是将成员变量声明为private , 提供public的getter和setter方法来控制对变量的访问和修改 ,可以在方法中加入合法性校验逻辑。

修饰符

本类

同包

子类(不同包)

不同包非子类

private

可见

不可见

不可见

不可见

default

可见

可见

不可见

不可见

protected

可见

可见

可见

不可见

public

可见

可见

可见

可见

表2-1 Java访问控制修饰符可见性

2.3 继承

继承是面向对象的另一核心特征 ,允许子类(派生类)复用父类(超类)的属性和方法,并可以扩展新的功能。

Java使用extends关键字实现类的继承,一个类只能继承一个直接父类(单继承),但可以通过接口实现多重继承的效果。

子类可以访问父类中非private的成员 ,构造方法不能被继承但可以通过super调用。

super关键字用于引用父类的成员变量、调用父类的方法和构造方法,super()必须放在子类构造方法的第一行。

方法重写(Override)是子类重新定义父类中同名同参的方法,是多态的基础。

重写规则包括 :

方法签名必须相同

返回类型相同或是父类返回类型的子类(协变返回)

访问权限不能缩小

不能抛出比父类更多的受检异常

@Override注解用于标识重写方法,编译器会检查是否正确重写。

重写与重载(Overload)的区别是软考高频考点:

重载是同名不同参(参数个数或类型不同),发生在同一个类中;重写是同名同参 ,发生在父子类之间。

/ 父类 
public class Animal  {  
protected  String name; 
public Animal(String name)  {  this.name  = name;  } 
public void speak()  { System.out.println(name +  "发出声音");  }  
}  
// 子类 
public class Dog  extends Animal  {  
public  Dog(String  name)  {  super(name);  //  调用父类构造方法  }  
@Override
public void speak() { // 方法重写 System.out.println(name +  "汪汪叫");  }  
}

2.4 多态

多态指同一操作作用于不同对象时产生不同的行为。

Java多态分为编译时多态(方法重载)和运行时多态(方法重写)。

运行时多态是软考重点,其核心机制是动态绑定 :父类引用指向子类对象时 ,调用被子类重写的方法,实际执行的是子类的方法。

实现运行时多态的三个前提条件是:有继承关系、有方法重写、父类引用指向子类对象。

多态的应用场景包括 :

方法参数使用父类类型以接收任何子类对象

数组或集合中存储父类类型以容纳多种子类对象

类型转换方面,子类到父类是自动的(向上转型),父类到子类需要强制转换(向下转型),向下转型前应使用instanceof运算符判断对象是否属于目标类型,避免ClassCastException。

// 多态示例 
Animal a = new Dog("旺财"); //  向上转型 
a.speak(); // 输出:  旺财汪汪叫  (运行时绑定)

// instanceof判断 
if  (a  instanceof  Dog)  {  
Dog d =  (Dog)  a; 
//  向下转型  
d.speak();  }  
//  多态作为方法参数 
public void makeSound(Animal animal)  { animal.speak(); // 传入不同子类,行为不同  }

2.5 抽象类与接口

抽象类使用abstract关键字修饰 ,不能被实例化,可以包含抽象方法(只有声明没有实现)和具体方法。

子类继承抽象类必须实现所有抽象方法,否则子类也必须声明为抽象类。

抽象类常用于定义家族类的公共特征 ,提供部分默认实现。

构造方法、静态方法、final方法不能声明为abstract。

接口使用interface关键字定义 ,是纯抽象的契约 ,定义了一组方法规范。

从Java 8开始 ,接口可以包含default方法(有默认实现的方法)和static方法 ;

Java 9引入了private方法。

接口中的成员变量默认为public static final(常量),方法默认为public abstract。

一个类可以实现多个接口(implements),接口之间可以多继承(extends)。

接口与抽象类的核心区别是:

抽象类是"is-a"关系,可以有构造方法和成员变量 ;

接口是"can-do"能力契约 ,不能有构造方法(Java 8之前无实现,Java 8+有default方法)。

// 抽象类 
public abstract class Shape { 
protected String color; 
public Shape(String color) {this.color = color;  } 
public abstract double area(); // 抽象方法 
public void display() { // 具体方法 
System.out.println("颜色:" + color);  }  
} 
// 接口(Java 8+) 
public interface Drawable { 
void draw(); // 默认
public abstract default void  resize() { 
// default方法 System.out.println("调整大小");  
} 
static void info() { // 静态方法 
System.out.println("可绘制接口");  }  
} 
// 实现类 
public class Circle extends Shape implements Drawable  { 
private double  radius;  
public  Circle(String color, double  radius)  { 
super(color); 
this. radius =  radius;  
} 
 @Override  
public double area() { return Math.PI *  radius *  radius;  } 
@Override  
public void  draw()  { System.out.println("绘制圆形" );  }  
}

第三章 内部类与Lambda表达式

3.1 内部类分类

内部类是定义在另一个类内部的类,分为四种 :

成员内部类、静态内部类、局部内部类和匿名内部类。

成员内部类定义在类中方法外 ,可以访问外部类的所有成员(包括private),内部类对象必须通过外部类对象创建:

Outer.Inner inner = outer.new Inner()

静态内部类使用   static修饰 ,不依赖外部类实例,只能访问外部类的静态成员 ,创建方式为:

Outer.StaticInner inner = new Outer.StaticInner()

局部内部类定义在方法或代码块内,只在该方法内可见 ,不能使用访问修饰符,可以访问外部类的成员和方法的final或等效final的局部变量。

匿名内部类是没有名字的局部内部类,在声明的同时实例化,常用于实现只需使用一次的接口或继承只需使用一次的类,是软考中出现频率最高的内部类形式。

匿名内部类不能有构造方法,可以初始化块代替。

// 成员内部类 
class Outer { 
private int x = 10; 
class Inner { 
void show() { System.out.println(x); // 可访问外部private  }  
}  
} 
// 静态内部类 
class Outer2 { 
static int y = 20; 
static class StaticInner { 
void show() { System.out.println(y); // 只能访问static成员  }  
}  
} 
//  匿名内部类(软考高频) 
interface Clickable { void onClick();  } 
Clickable btn = new Clickable() { 
@Override 
public void onClick() { System.out.println("被点击了");  }  
};

3.2 Lambda表达式

Lambda表达式是Java 8引入的简洁语法,用于实现函数式接口(只包含一个抽象方法的接口)。

Lambda的基本语法为:(参数列表)-> {方法体}。

当参数类型可推断时可省略类型,单个参数可省略括号 ,方法体只有一条语句时可省略大括号和return。

Lambda表达式本质上是匿名内部类的语法糖 ,但更简洁高效。

常用的函数式接口包括 :Runnable(无参无返回)

Consumer(有参无返回)

Supplier(无参有返回)

Function(有参有返回)

Predicate(有参返回boolean)。

//  Lambda基本语法 
// 无参:  ()  -> 表达式 
Runnable  r  =  ()  -> System.out.println("Hello"); 
// 有参: (参数) -> 表达式 
Consumer<String> c =  (s)  -> System.out.println(s); 
// 类型推断可省略参数类型
Comparator<Integer>  comp  =  (a,  b)  ->  a - b; 
//  方法引用(更简洁的Lambda) 
Consumer<String> c2 = System.out::println; 
Supplier<List<String>> s = ArrayList::new; 
// 常用函数式接口
Function<String,  Integer>  f  =  Integer::parseInt;  
Predicate<Integer>  isEven  =  n  ->  n  %  2  ==  0; 
Supplier<Double>  random  =  Math::random;

第四章 异常处理机制

4.1 异常体系

Java异常体系以Throwable为根 ,分为Error和Exception两大分支。

Error表示严重的系统级错误(如OutOfMemoryError、StackOverflowError),程序通常无法处理。

Exception分为受检异常(Checked Exception)和非受检异常(Unchecked Exception)。

受检异常在编译时检查,必须使用try-catch捕获或throws声明抛出 ,如IOException、SQLException。

非受检异常即RuntimeException及其子类,编译器不强制处理,如NullPointerException、 ArrayIndexOutOfBoundsException、ClassCastException等。

软考中常考异常的分类和常见异常类型。

受检异常必须处理,否则编译不通过;

非受检异常可以不处理,运行时出错会中断程序。

自定义异常通常继承Exception(受检)或RuntimeException(非受检),建议提供无参构造和带消息字符串的构造方法。

4.2 try-catch-finally

try-catch-finally是Java异常处理的核心语法。

try块中放置可能抛出异常的代码,catch块捕获并处理特定类型的异常,finally块无论是否发生异常都会执行(除非JVM退出),通常用于释放资源。

一个try可以跟多个catch,按异常类型从子类到父类的顺序排列,因为匹配到第一个合适的catch后就不再继续。

Java 7引入了multi-catch语法,可以用|连接多个异常类型:

catch(IOException | SQLException e)。

finally块的执行时机非常重要 :

即使在try或catch中有return语句,finally也会在return之前执行。

但finally中的return会覆盖try/catch中的return值 ,应避免在finally中使用return。

Java 7还引入了try-with-resources语法,实现了AutoCloseable接口的资源会在try结束后自动关闭 ,无需手动在finally中关闭。

// try-catch-finally 
try { 
int  result = 10 / 0;  
} catch  (ArithmeticException e) {

System.out.println("算术异常:" + e.getMessage());  
} catch  (Exception e) { 
System.out.println("其他异常");  
} finally { System.out.println("总是执行");  } 
// try-with-resources  (Java 7+) 
try (FileReader fr = new  FileReader("a.txt"); BufferedReader br = new BufferedReader(fr))  { 
String line = br. readLine();  
} //  自动关闭资源,无需finally 
// multi-catch  (Java 7+) try { //  ...  }
catch  (IOException  |  SQLException  e)  {  e.printStackTrace();  }

4.3 throw与throws

throw关键字用于在方法体内主动抛出一个异常对象,语法为throw new ExceptionType("消息")。

throws关键字用于方法签名中声明该方法可能抛出的受检异常,语法为:

返回类型 方法名(参数) throws ExceptionType1, ExceptionType2。

throw是语句,执行时创建并抛出异常对象 ;

throws是声明,告知调用者需要处理的异常类型。

方法重写时 ,子类方法throws的异常类型不能大于父类方法throws的异常类型。

//  throw: 主动抛出异常  
public void  setAge(int age)  {  
if  (age  <  0  ||  age  >  150)  { 
throw  new IllegalArgumentException(  "年龄不合法:" + age);  }  
this.age = age;  } 
//  throws: 声明可能抛出的异常
public void  readFile(String  path)  throws  IOException,  FileNotFoundException  {  FileReader  fr  = new  FileReader(path); //  ...  }  
//  自定义异常  
public  class  BusinessException  extends Exception  { 
public BusinessException() { super();  } 
public BusinessException(String msg) { super(msg);  }  
}

第五章 常用类库

5.1 String类

String是Java中最常用的类之一 ,代表不可变的字符序列。

String对象一旦创建,其内容不可修改 ,任何看似修改的操作(如拼接、截取)都会产生新的String对象。

字符串常量存储在字符串常量池中 ,相同内容的字符串字面量指向同一个对象。

创建String对象有两种方式:直接赋值(String s = "hello",使用常量池)和new String()(在堆上创建新对象)。

软考中常考String的不可变性和常量池机制。

String常用方法包括 :

length()获取长度

charAt(int index)获取指定位置字符

substring(int begin, int end)截取子串

indexOf(String str)查找子串位置

equals(Object obj)比较内容

equalsIgnoreCase(String str)忽略大小写比较

toUpperCase()/toLowerCase()大小写转换

trim()去除首尾空白

split(String regex)按正则分割

replace(old, new)替换

contains(String str)是否包含子串

startsWith()/endsWith()前缀后缀判断

isEmpty()是否为空

注意 :比较字符串内容必须用equals(),不能用==,==比较的是引用地址。

String s1 =  "hello"; 
String s2 =  "hello"; 
String  s3 = new String("hello"); 
s1 == s2; // true  (常量池同一对象) 
s1 == s3; // false  (堆上不同对象) 
s1 .equals(s3); // true  (内容相同) 
// 常用方法
String s =  "Hello, World!"; 
s.length(); // 13 
s.charAt(0); //  'H'  
s.substring(0, 5); //  "Hello" 
s.indexOf("World"); // 7 
s.toUpperCase(); //  "HELLO, WORLD!" 
s.trim(); // 去首尾空白
s.split(","); //  ["Hello",  " World!"] 
s. replace("World",  "Java"); //  "Hello, Java!"

5.2 StringBuilder与StringBuffer

StringBuilder和StringBuffer是可变的字符序列,用于频繁字符串拼接场景 ,避免String不可变导致的频繁创建对象。

两者API完全相同,区别在于StringBuffer是线程安全的(方法加了synchronized),StringBuilder是线程不安全的但性能更高。

在单线程环境下优先使用StringBuilder。

常用方法:

append()追加

insert()插入

delete()删除

reverse()反转

toString()转为String。

软考中常考三者的区别 :

String不可变、StringBuilder可变非线程安全、StringBuffer可变线程安全。

StringBuilder sb = new StringBuilder("Hello"); 
sb.append(" World"); //  "Hello World"
sb.insert(5,  ","); //  "Hello, World" 
sb.delete(5, 6); //  "Hello World" 
sb. reverse(); //  "dlroW olleH" 
String  result = sb.toString();

5.3 包装类

Java为每个基本数据类型提供了对应的包装类:

Byte、Short、Integer、Long、Float、Double、Character、Boolean。

包装类使基本类型可以参与面向对象的操作(如放入集合)。

自动装箱(Autoboxing)是基本类型自动转为包装类,如Integer i = 10 ;

自动拆箱(Unboxing)是包装类自动转为基本类型,如int n = i。

包装类常用的静态方法有 :

valueOf()将基本类型转为包装类

parseInt()/parseDouble()等将字符串转为基本类型

toString()转为字符串

Integer缓存是软考重要考点:

Integer在-128到127范围内使用缓存 ,此范围内相同值的Integer对象是同一个(==为true),超出范围则创建新对象(==为false)。

因此比较包装类对象的值应使用equals()而非==。

//  自动装箱与拆箱  
Integer  a  = 100; 
//  自动装箱:  
Integer.valueOf(100)  
int  b  = a; 
//  自动拆箱:
a.intValue() 
// Integer缓存陷阱 
Integer x = 127; 
Integer y = 127; 
x == y; // true  (缓存范围内)
Integer m = 128;  
Integer n = 128; 
m == n; // false  (超出缓存范围)  
m.equals(n);  //  true  (比较值) 
// 字符串与基本类型转换 
int num = Integer.parseInt("123"); 
String str = Integer.toString(123);

5.4 Object类

Object是所有Java类的根父类,每个类都直接或间接继承自Object。

Object类定义了所有对象都具有的方法:

equals(Object obj)判断对象是否相等

hashCode()返回对象的哈希码、

toString()返回对象的字符串表示

getClass()返回对象的运行时类

clone()创建并返回对象副本

 finalize()垃圾回收前调用(已过时)

重写equals()时必须同时重写hashCode(),保证相等的对象有相同的哈希码,这是Java规范的要求 ,否则在HashMap等基于哈希的集合中会出现错误行为。

第六章 集合框架

6.1 集合体系概览

Java集合框架位于java.util包中 ,是存储和操作一组对象的统一架构。

集合框架的核心接口分为两大体系:Collection体系和Map体系。

Collection是单列集合的根接口,派生出List(有序可重复) 、Set(无序不可重复)和Queue(队列)三个子接口。

Map是双列集合接口,存储键值对映射关系,键不可重复。

集合框架的设计遵循"接口-实现分离"原则 ,同一接口有多种实现类,适用于不同场景。

软考中集合框架是必考内容 ,需要掌握各接口的特点、常用实现类的区别以及遍历方式。

6.2 List体系

List是有序集合,允许重复元素 ,可以通过索引访问。

主要实现类有ArrayList、LinkedList和Vector。

ArrayList基于动态数组实现,随机访问效率高(O(1)),插入删除效率低(O(n)),是最常用的List实现。

LinkedList基于双向链表实现,插入删除效率高(O(1)),随机访问效率低(O(n)),同时实现了Deque接口可作为双端队列使用。

Vector是线程安全的动态数组(方法加了synchronized),性能较低 ,已被ArrayList取代。

List常用方法:

add()添加

get(int index)按索引获取

set(int index, E element)修改

remove(int index/Object o)删除

 indexOf()/lastIndexOf()查找索引

subList()截取子列表

// ArrayList  
List<String> list = new ArrayList<>(); 
list.add("Java"); list.add("Python");

list.add(1,  "C++"); // 在索引1处插入 
list.get(0); //  "Java" 
list.set(0,  "Go"); // 替换索引0处元素
list. remove(0); // 按索引删除 
list. remove("Python"); // 按元素删除
list.size(); // 元素个数
list.contains("C++"); // true 
//  LinkedList  
LinkedList<String> linked = new  LinkedList<>();
linked.addFirst("First"); // 头部添加 
linked.addLast("Last"); // 尾部添加 
linked.getFirst(); //获取头部 
linked. removeFirst(); // 删除头部

6.3 Set体系

Set是不允许重复元素的集合。

主要实现类有HashSet、LinkedHashSet和TreeSet。

HashSet基于HashMap实现,元素无序,允许null,判断元素是否重复依赖hashCode()和equals()方法,是性能最好的Set实现。

LinkedHashSet在HashSet基础上维护了插入顺序,遍历时按插入顺序输出。

TreeSet基于TreeMap实现(红黑树),元素自然排序或按指定Comparator排序,不允许null。

自定义对象存入HashSet/LinkedHashSet时 ,必须正确重写hashCode()和  equals()方法 ;

存入TreeSet时 ,必须实现Comparable接口或提供Comparator。

//  HashSet  - 无序  
Set<String>  hashSet  =  new  HashSet<>();  
hashSet.add("Java");
hashSet.add("Python"); 
hashSet.add("Java"); // 重复,不添加 

//  LinkedHashSet - 保持插入顺序
Set<String> linkedSet = new  LinkedHashSet<>();  
linkedSet.add("C");  linkedSet.add("A");
linkedSet.add("B"); // 遍历顺序: C, A, B

// TreeSet -  自然排序  
Set<String>  treeSet  = new TreeSet<>(); 
treeSet.add("Banana"); treeSet.add("Apple"); 
treeSet.add("Cherry"); // 遍历顺序:Apple,  Banana,  Cherryv

6.4 Map体系

Map是键值对映射集合,键不可重复 ,每个键最多映射一个值。

主要实现类有HashMap、LinkedHashMap、TreeMap和Hashtable。

HashMap基于哈希表实现,键无序,允许null键和null值 ,是最常用的Map实现。

LinkedHashMap在HashMap基础上维护了插入顺序或访问顺序。

TreeMap基于红黑树实现,键自然排序或按Comparator排序。

Hashtable是线程安全的哈希表(方法加了synchronized),不允许null键和null值 ,已被ConcurrentHashMap取代。

Map常用方法:

put(K key, V value)添加/修改

get(Object key)按键取值

remove(Object key)按键删除

containsKey()/containsValue()判断是否包含

keySet()获取所有键

values()获取所有值

entrySet()获取所有键值对

getOrDefault()获取值或默认值

putIfAbsent()不存在时才添加

merge()合并值

遍历Map推荐使用entrySet()方式,性能优于先keySet()再get()。

//  HashMap  
Map<String,  Integer>  map  =  new  HashMap<>();  
map.put("Java",  1);  map.put("Python",  2); 
map.put("Java", 3); // 覆盖旧值 
map.get("Java"); // 3 
map.getOrDefault("Go", 0); // 0
map.containsKey("Java"); // true 

// 遍历Map(推荐entrySet) 
for  (Map.Entry<String,  Integer> e  : map.entrySet()) { 
System.out.println(e.getKey() +  "=" + e.getValue());  } 

// Java 8+ forEach
map.forEach((k, v) -> System.out.println(k +  "=" + v));

6.5 集合遍历方式

Java集合有多种遍历方式,不同方式适用于不同场景。

对于List,可以使用普通for循环(按索引遍历)、增强for循环(for-each)和Iterator迭代器。

对于Set,可以使用增强for循环和Iterator。

对于Map,可以使用entrySet()遍历、keySet()遍历和forEach方法(Java 8+)。

在遍历过程中删除元素必须使用Iterator的remove()方法,不能直接调用集合的remove()方法,否则会抛出ConcurrentModificationException异常。

// 1. 增强for循环(最简洁) 
for  (String item  : list) { System.out.println(item);  } 

// 2. Iterator遍历(可安全删除) 
Iterator<String> it = list.iterator(); 
while  (it.hasNext()) { 
String item =it.next(); 
if  (item.equals("remove")) { it. remove(); }  // 安全删除
} 

// 3. forEach +  Lambda(Java 8+) 
list.forEach(item -> System.out.println(item)); 
list.forEach(System.out::println); //方法引用

第七章 泛型

7.1 泛型类与泛型方法

泛型是Java 5引入的特性 ,允许在定义类、接口和方法时使用类型参数(Type Parameter) , 在使用时再指定具体类型。

泛型类在类名后用尖括号声明类型参数,如class Box,T是类型参数,可以使用任意标识符(常用T、E、K、V等)。

泛型方法在返回类型前声明类型参数,如public  void print(E[] array),类型参数的作用域限于该方法。泛型的好处是类型安全(编译时检查类型)和代码复用(一套代码适用于多种类型)。

// 泛型类 
public class Box<T> { 
private T value; 
public void set(T value) { 
this.value = value; } 
public T get()  {  return  value;  }  
}  

//  使用泛型类  
Box<String>  strBox  =  new  Box<>();
strBox.set("Hello"); 
String s = strBox.get(); // 无需强转 

// 泛型方法 
public <E> void printArray(E[] array) { 
for  (E element  :  array) { System.out.println(element);  }  
} 

// 多类型参数
public class Pair<K, V> { private K key; private V value; //  ...  }

7.2 通配符与边界

通配符?用于表示未知类型,在泛型中解决类型协变问题。

无界通配符表示任意类型,如  List可以接收任何类型的List。

上界通配符表示T或T的子类,适用于只读场景(生产者),可以从集合中获取T类型的数据,但不能向集合中添加元素(除了null)。

下界通配符表示T或T的父类,适用于只写场景(消费者),可以向集合中添加T及其子类对象,但获取元素时只能当作 Object处理。

PECS原则(Producer Extends, Consumer Super)是选择通配符边界的指导原则。

// 上界通配符 - 只读(生产者) 
public double sum(List<? extends Number>  list)  {  
double  total = 0;
for  (Number n  :  list)  { 
total += n.doubleValue(); // 可以读取  
} 
// list.add(1); // 编译错误! 不能写入  
return  total;  
}  

//  下界通配符 - 只写(消费者) 
public void addNumbers(List<? super  Integer> list) { 
list.add(1); // 可以写入
Integer list.add(2); // Integer 
n = list.get(0); // 编译错误! 只能当Object  
}

7.3 类型擦除

Java泛型采用类型擦除(Type Erasure)机制实现,编译器在编译时使用泛型信息进行类型检查,然后在生成的字节码中擦除所有泛型信息 ,将类型参数替换为其上界(默认为Object)。

这意味着泛型是编译时特性 ,运行时不存在泛型类型信息。

类型擦除的后果包括 :

不能使用instanceof检查泛型类型(如list instanceof List编译错误)

不能创建泛型数组(new T[]非法)

不能在静态上下文中引用类的类型参数

运行时所有泛型类型都是原始类型,理解类型擦除有助于解释泛型的各种限制。

第八章 I/O流与文件操作

8.1 I/O流体系

Java I/O流体系位于java.io包中 ,按照操作单位分为字节流(InputStream/OutputStream)和字符流(Reader/Writer),按照方向分为输入流和输出流。

字节流以byte为单位读写 ,适合处理二进制数据(图片、视频等) ;

字符流以char为单位读写 ,适合处理文本数据,内部包含编码转换。

字节流和字符流之间可以通过InputStreamReader和OutputStreamWriter进行桥接转换。

所有流都实现了Closeable接口,使用后必须关闭以释放系统资源。

分类

字节流(基类)

字符流(基类)

常用实现类

输入流

InputStream

Reader

FileInputStream, BufferedReader

输出流

OutputStream

Writer

FileOutputStream, BufferedWriter

表8-1 Java I/O流体系分类

8.2 常用流类

FileInputStream/FileOutputStream用于读写文件中的字节数据,是文件操作的基础流。

FileReader/FileWriter用于读写文件中的字符数据,使用系统默认编码。

BufferedReader/BufferedWriter是带缓冲区的字符流,提供readLine()方法逐行读取文本,显著提高读写效率 ,是最常用的文本读取方式。

DataInputStream/DataOutputStream可以读写Java基本数据类型。

ObjectInputStream/ObjectOutputStream用于对象的序列化和反序列化。

PrintStream/PrintWriter提供方便的print/println方法。

// 文件读取(BufferedReader)  
try  (
BufferedReader  br  =  new  BufferedReader( new FileReader("input.txt"))) { 
String line; 
while  ((line = br.readLine())  != null) {
System.out.println(line);  }  
} 

// 文件写入(BufferedWriter) 
try  (BufferedWriter bw = new BufferedWriter( new  FileWriter("output.txt"))) { 
bw.write("Hello, World!"); 
bw.newLine(); // 换行
bw.write("Java I/O");  
}

8.3 File类与序列化

File类代表文件或目录路径的抽象表示 ,可以创建、删除、查询文件/目录的属性 ,但不能读写文件内容。

常用方法:exists()判断是否存在、isFile()/isDirectory()判断类型、

createNewFile()创建文件、mkdirs()创建目录、delete()删除、list()/listFiles()列出子文件。

序列化是将对象转换为字节序列的过程,使用ObjectOutputStream的writeObject()方法实现 ;

反序列化是将字节序列恢复为对象,使用ObjectInputStream的readObject()方法实现。

要序列化的类必须实现Serializable接口(标记接口,无需实现方法),serialVersionUID用于版本控制。

transient关键字修饰的成员变量不参与序列化。

//  File操作  
File file = new  File("test.txt"); 
file.exists(); // 是否存在 
file.createNewFile();// 创建文件 
file.delete(); // 删除 

// 序列化 
public class User implements Serializable { 
private static final long serialVersionUID = 1L; 
private String name; private transient String password; // 不序列化  
}  

//  写入对象  
try  (ObjectOutputStream  oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) { 
oos.writeObject(new User("Tom",  "123456"));  
} 

// 读取对象 
try (ObjectInputStream ois = new ObjectInputStream( new  FileInputStream("user.dat")))  {  
User  user  = (User) ois. readObject();  
}

第九章 多线程基础

9.1 线程创建方式

Java中创建线程有两种基本方式:继承Thread类和实现Runnable接口。

继承Thread类需要重写run()方法,然后调用start()方法启动线程(不能直接调用run(),那样只是普通方法调用,不会创建新线程)。

实现Runnable接口需要实现run()方法,然后将Runnable对象传给Thread构造器创建线程。

推荐使用Runnable方式,因为Java单继承限制,实现接口更灵活 ,且Runnable对象可以被线程池复用。

Java 5还引入了Callable接口,可以有返回值和抛出异常,配合FutureTask使用。

// 方式1: 继承Thread类 
class MyThread extends Thread  {  
@Override
public  void  run()  {
System.out.println("线程运行:" + getName());  }  
} 
new MyThread().start(); 

// 方式2: 实现Runnable接口(推荐) 
class  MyRunnable  implements  Runnable  {  
@Override  
public  void  run()  {
System.out.println("Runnable运行");  }  
}  
new  Thread(new  MyRunnable()).start(); 

//  Lambda简化  
new Thread(() -> System.out.println("Lambda线程")).start(); 

// 方式3: Callable +  FutureTask
Callable<Integer> callable =  ()  ->  {  return  42;  };  
FutureTask<Integer>  task  =  new FutureTask<>(callable); 
new Thread(task).start(); 
Integer  result = task.get(); // 获取返回值

9.2 线程同步机制

多线程并发访问共享数据时可能出现数据不一致问题,需要同步机制保证线程安全。

synchronized关键字是Java最基本的同步机制,可以修饰方法(锁为this对象)或代码块(指定锁对象)。

同步方法:public synchronized void method(),同一时刻只有一个线程能执行该对象的同步方法。

同步代码块 :synchronized(obj){...},锁为指定对象obj。

静态同步方法的锁为Class对象。

wait()/notify()/notifyAll()是Object类的方法,用于线程间通信 ,必须在synchronized块内调用(即持有锁时才能调用)。

wait()释放锁并进入等待 ,notify()唤醒一个等待线程,notifyAll()唤醒所有等待线程。

此外 ,Java 5引入了更灵活的Lock接口(ReentrantLock实现),支持可中断锁、

定时锁和公平锁 ,配合Condition实现多条件等待/通知。

软考中synchronized的用法和wait/notify机制是重点。

// synchronized同步方法
public class Counter  { 
private  int count = 0; 
public synchronized void increment()  { count++; // 原子操作保护  }  
public synchronized int getCount()  {  return  count;  }  } 

// synchronized同步代码块 
Object lock = new Object(); 
synchronized  (lock)  {  //  临界区代码  }  

//wait/notify 
synchronized  (lock) { 
lock.wait(); // 释放锁 ,等待 
lock.notify(); // 唤醒一个等待线程
lock.notifyAll(); // 唤醒所有等待线程  
}

9.3 线程生命周期

Java线程的生命周期包含以下状态:

NEW(新建,尚未启动)

RUNNABLE(可运行 ,包括就绪和运行中)

BLOCKED(阻塞 ,等待获取锁)

WAITING(等待 ,无限期等待其他线程唤醒)

TIMED_WAITING(计时等待 ,有限时间等待)

TERMINATED(终止 ,线程执行完毕)

状态转换的关键操作:

start()从NEW到RUNNABLE

sleep()/wait()到TIMED_WAITING/WAITING

获取锁失败到BLOCKED

run()执行完毕到TERMINATED。

软考中线程状态转换图是常见考点。

第十章 软考高频设计模式

设计模式是软考软件设计师考试的核心考点之一 ,尤其在下午大题中经常要求识别或补充设计模式代码。

以下五种模式是历年真题中出现频率最高的,需要重点掌握其结构、角色和   Java实现方式。

10.1 单例模式(Singleton)

单例模式确保一个类只有一个实例,并提供全局访问点。

核心实现要点:私有化构造方法、类内部创建唯一实例、提供公共静态方法获取实例。

常见实现方式有五种 :

饿汉式(类加载时创建,线程安全但可能浪费资源)

懒汉式(首次使用时创建,非线程安全)

同步懒汉式(加synchronized,线程安全但性能差)

双重检查锁定(DCL,推荐)

静态内部类(推荐 ,利用类加载机制保证线程安全)

枚举单例是最安全的实现方式,可以防止反射和序列化破坏单例。

// 1. 饿汉式(线程安全) 
public class Singleton { 
private static final Singleton INSTANCE = new Singleton(); 
private Singleton() {} public static Singleton getInstance() {  return INSTANCE;  }  
} 

// 2. 双重检查锁定(DCL, 推荐) 
public class Singleton { 
private static volatile Singleton instance; 
private Singleton() {} public static Singleton getInstance() { 
if  (instance == null) { 
// 第一次检查 
synchronized  (Singleton.class)  { 
if  (instance == null)  { 
// 第二次检查 
instance = new Singleton();  }  
}  
}  
return instance;  
}  
} 

// 3. 静态内部类(推荐) 
public class Singleton {
private Singleton() {} private static class Holder { 
private static final Singleton INSTANCE = new Singleton();  } 
public static Singleton getInstance()  {  return  Holder.INSTANCE;  }  
}

10.2 工厂模式(Factory)

工厂模式将对象的创建过程封装在工厂类中 ,客户端无需知道具体创建细节。

分为简单工厂、工厂方法和抽象工厂三种。简单工厂:

一个工厂类根据参数决定创建哪种产品 ,违反开闭原则。

工厂方法:定义创建产品的抽象方法,由子类决定创建哪种产品 ,每增加产品需增加工厂子类。

抽象工厂:创建一系列相关产品的接口,由具体工厂实现,适合产品族场景。

软考中最常考的是工厂方法模式,需要识别抽象产品、具体产品、抽象工厂、具体工厂四个角色。

// 工厂方法模式 

// 抽象产品  
interface Document  { void  open();  }  

// 具体产品  
class  PdfDocument implements  Document  {  
public  void  open()  {  System.out.println("打开PDF");  }  }  
class  WordDocument implements Document  {  
public void open()  {  System.out.println("打开Word");  }  }  

//  抽象工厂
abstract class DocumentFactory  {  public abstract  Document createDocument();  }  

//  具体工厂 
class PdfFactory  extends  DocumentFactory  {  
public  Document  createDocument()  {  return  new PdfDocument();  }  }  
class  WordFactory  extends  DocumentFactory  {  
public  Document createDocument() {  return  new  WordDocument();  }  }

10.3 观察者模式(Observer)

观察者模式定义了对象间一对多的依赖关系,当一个对象状态改变时 ,所有依赖它的对象都会收到通知并自动更新。

核心角色 :

Subject(主题/被观察者,维护观察者列表,提供注册、注销和通知方法)

Observer(观察者,定义更新接口)

ConcreteSubject(具体主题,状态改变时通知所有观察者)

ConcreteObserver(具体观察者,收到通知后更新自身状态)

Java内置了java.util.Observable类和Observer接口,现在推荐使用PropertyChangeListener或自定义实现。

// 观察者模式 
interface Observer { void update(String message);  } 
class Subject  { 
private List<Observer> observers = new ArrayList<>(); 
public void attach(Observer o)  { observers.add(o); } 
public void detach(Observer o) { observers. remove(o);  } 
public void notifyAll(String msg)  {
for  (Observer  o  :  observers)  {  o.update(msg);  }  }  
}  
class  ConcreteObserver  implements  Observer  { 
private String  name;  
public  ConcreteObserver(String  name)  {  this.name  =  name;  }  
public  void update(String message)  {System.out.println(name  +  "收到:"  +  message);  } 
 }

10.4 策略模式(Strategy)

策略模式定义一系列算法,将每个算法封装到具有共同接口的独立类中 ,使算法可以互相  替换,让算法的变化独立于使用算法的客户端。

核心角色 :

Strategy(策略接口,定义算法接口)

ConcreteStrategy(具体策略,实现具体算法)

Context(上下文,持有策略引用,将算法委托给策略对象执行)。

策略模式消除了条件判断语句,符合开闭原则 ,新增策略只需新增类,

无需修改上下文代码。软考中常考策略模式与简单工厂的结合使用。

// 策略模式 
interface SortStrategy { void sort(int[] arr);  } 
class BubbleSort implements SortStrategy { public void sort(int[] arr) { /*  冒泡排序 */  }  
} 
class QuickSort implements SortStrategy { public void sort(int[] arr) { /* 快速排序 */  }  } 
class Sorter { 
private SortStrategy strategy; 
public Sorter(SortStrategy strategy) { this.strategy = strategy;  } 
public void setStrategy(SortStrategy s) { this.strategy = s;  } 
public void sort(int[] arr) {strategy.sort(arr); // 委托给策略  }  
}

10.5 适配器模式(Adapter)

适配器模式将一个类的接口转换成客户端期望的另一个接口,使原本不兼容的类可以协同工作。

分为类适配器(通过继承实现)和对象适配器(通过组合实现,推荐)。

类适配器:适配器类继承被适配类并实现目标接口。

对象适配器:适配器类持有被适配类的引用并实现目标接口,将请求委托给被适配类处理。

对象适配器更灵活 ,符合组合优于继承的原则。

软考中常考对象适配器模式:需要识别:

Target(目标接口) 、Adaptee(被适配者) 、Adapter(适配器)三个角色。

// 对象适配器模式(推荐) 
//  目标接口  
interface Target { void  request();  } // 被适配者(已有接口)
class Adaptee { public void specificRequest() { System.out.println("被适配者的方法");  }  
} 

// 适配器(组合方式) 
class Adapter  implements  Target  {  
private  Adaptee adaptee;  
public Adapter(Adaptee adaptee) { this.adaptee = adaptee;  } 
public void  request()  { adaptee.specificRequest(); // 委托 
}  
}

第十一章 代码模式速查

11.1 常考代码模板

软考软件设计师下午题的Java部分通常要求补全代码,涉及设计模式、集合操作、文件读写等。

以下总结了历年真题中反复出现的代码模板,掌握这些模板可以快速定位答题思路。

11.1.1 接口+实现类模板

这是软考中最常见的代码结构,通常题目给出接口定义 ,要求补全实现类。

关键要点:实  现类使用implements关键字、必须实现接口的所有抽象方法、使用@Override注解标识重写方法。

如果接口有default方法,实现类可以选择性重写。

// 典型考题结构 
interface IStrategy { void algorithm();  } 
class ConcreteStrategyA implements IStrategy { @Override public void algorithm() { 
// 补全具体算法实现  
}  
} 
class Context  { 
private IStrategy strategy; 
public Context(IStrategy strategy) { 
this.strategy = strategy; // 多态注入  
} 
public void execute() { 
strategy.algorithm(); // 委托调用  
}  
}

11.1.2 抽象类+子类模板

抽象类模板常与模板方法模式结合考查。

抽象类定义算法骨架(模板方法),子类实现具体步骤。模板方法用final修饰防止子类重写 ,抽象步骤由子类实现。

// 模板方法模式 
abstract class AbstractClass { 
// 模板方法(final防止重写) 
public final void templateMethod() { 
step1(); 
step2(); // 可变步骤 
step3();  
} 
private void step1() { /* 固定步骤*/  }
protected abstract void step2(); // 可变步骤 
private void step3() { /* 固定步骤 */  }  }
class ConcreteClass extends AbstractClass  { 
@Override 
protected void step2()  { 
// 补全具体实现  
}}

11.2 易错点汇总

以下总结了软考Java大题中最容易犯的错误 ,考前务必逐一排查。

易错点

错误写法

正确写法

字符串比较用==

if(s == "yes")

if(s.equals("yes"))

数组声明方式

int arr[];

int[] arr; (推荐)

switch缺少break

case后无break

每个case末尾加break

重写equals未重写

hashCode

只重写equals()

equals()和hashCode()同时重写

Integer缓存范围

new Integer(127)==new Integer(127)

用equals()比较值

构造方法中this()位置

this()不在首行

this()必须是构造方法第一条语句

抽象方法有方法体

abstract void f() { }

abstract void f(); 无方法体

接口方法加修饰符

private void method();

默认public abstract,无需修饰符

遍历时删除元素

for循环中list.remove()

使用Iterator.remove()

线程启动方式

thread.run()

thread.start() 创建新线程

表11-1 软考Java大题常见易错点

提示 :

软考Java大题的答题策略:先通读题目理解设计模式类型,再根据上下文补全代码。

常见填空位置包括 :

接口/抽象类的声明(implements/extends)、方法重写(@Override)、多态调用、集合操作、设计模式角色识别。

注意代码风格与题目保持一致 ,变量命名遵循驼峰命名法。

更多推荐