Java语法总结——软考备战最终篇

第一章 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)、多态调用、集合操作、设计模式角色识别。
注意代码风格与题目保持一致 ,变量命名遵循驼峰命名法。
更多推荐

所有评论(0)