1. 编程思维和算法构建
能够综合运用 Java 语言各种特性,编写程序实现业务要求。
- 面向对象编程(OOP)
- 平台无关性
- 自动内存管理
- 多线程
- 同步和锁机制
能够通过阅读现有代码,分析理解调用关系和处理过程,进而形成有效的修改方案
掌握对代码进行重构和优化的常见方法:抽出方法、复杂判断优化、定义共通处理、抽象基类
掌握 SOLID 原则
-
单一职责原则
一个类应该只有一个单一的职责,即该类应该只有一个引起它变化的原因。
// 不符合 SRP public class Report { private String title; private String content; public void generateReport() { // 生成报告内容 } public void saveToFile() { // 保存报告到文件 } } // 符合 SRP public class Report { private String title; private String content; public void generateReport() { // 生成报告内容 } } public class ReportSaver { public void saveToFile(Report report) { // 保存报告到文件 } } -
开放封闭原则
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
// 不符合 OCP public class Rectangle { public void draw() { // 绘制矩形 } } public class Circle { public void draw() { // 绘制圆形 } } public class DrawingApp { public void drawShapes() { Rectangle rectangle = new Rectangle(); Circle circle = new Circle(); rectangle.draw(); circle.draw(); } } // 符合 OCP public interface Shape { void draw(); } public class Rectangle implements Shape { public void draw() { // 绘制矩形 } } public class Circle implements Shape { public void draw() { // 绘制圆形 } } public class DrawingApp { private List<Shape> shapes; public void addShape(Shape shape) { shapes.add(shape); } public void drawShapes() { for (Shape shape : shapes) { shape.draw(); } } } -
里氏替换原则
子类必须能够完全实现父类的方法,并且不改变其原有的行为。允许代码使用父类引用来操作子类对象。
// 不符合 LSP public class Bird { public void fly() { // 飞行 } } public class Penguin extends Bird { @Override public void fly() { throw new UnsupportedOperationException("Penguins can't fly"); } } // 符合 LSP public interface Bird { void move(); } public class Sparrow implements Bird { public void move() { // 飞行 } } public class Penguin implements Bird { public void move() { // 行走 } } -
接口隔离原则
将大而复杂的接口拆分成多个小的、专门的接口,使得实现这些接口的类只需实现客户端所需的方法。
// 不符合 ISP public interface Worker { void work(); void eat(); } public class Employee implements Worker { public void work() { // 工作 } public void eat() { // 吃饭 } } public class Robot implements Worker { public void work() { // 工作 } public void eat() { throw new UnsupportedOperationException("Robots don't eat"); } } // 符合 ISP public interface Workable { void work(); } public interface Eatable { void eat(); } public class Employee implements Workable, Eatable { public void work() { // 工作 } public void eat() { // 吃饭 } } public class Robot implements Workable { public void work() { // 工作 } } -
依赖倒置原则
高层模块(业务逻辑)和低层模块(实现细节)都应该依赖于抽象(接口或抽象类),而不是直接依赖于具体实现。
// 不符合 DIP public class LightBulb { public void turnOn() { // 开灯 } } public class Switch { private LightBulb bulb; public Switch(LightBulb bulb) { this.bulb = bulb; } public void operate() { bulb.turnOn(); } } // 符合 DIP public interface Switchable { void turnOn(); } public class LightBulb implements Switchable { public void turnOn() { // 开灯 } } public class Switch { private Switchable device; public Switch(Switchable device) { this.device = device; } public void operate() { device.turnOn(); } }
掌握常见设计模式:工厂模式、单例模式、装饰器模式、模板方法模式、适配器模式
-
工厂模式
- 简单工厂模式
- 定义了一个工厂类,该工厂类根据提供的信息(如类型)创建不同类型的对象。
- 通过一个静态方法创建对象,简单但不适应于复杂场景
- 工厂方法模式
- 定义了一个创建对象的接口,但由子类决定实例化哪一个类。工厂方法模式将创建对象的责任委托给子类
- 适合于有多个产品且产品的具体类需要根据需求变化的情况。
- 抽象工厂模式
- 提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。抽象工厂模式涉及到多个工厂方法,每个工厂方法负责创建一类产品。
- 适合于需要创建一系列相互依赖的产品的情况。
-
简单工厂模式
// 产品接口 public interface Product { void use(); } // 具体产品 A public class ConcreteProductA implements Product { public void use() { System.out.println("Using Product A"); } } // 具体产品 B public class ConcreteProductB implements Product { public void use() { System.out.println("Using Product B"); } } // 简单工厂 public class ProductFactory { public static Product createProduct(String type) { if (type.equals("A")) { return new ConcreteProductA(); } else if (type.equals("B")) { return new ConcreteProductB(); } else { throw new IllegalArgumentException("Unknown product type"); } } } // 客户端代码 public class Client { public static void main(String[] args) { Product product = ProductFactory.createProduct("A"); product.use(); } } -
工厂方法模式
// 产品接口 public interface Product { void use(); } // 具体产品 A public class ConcreteProductA implements Product { public void use() { System.out.println("Using Product A"); } } // 具体产品 B public class ConcreteProductB implements Product { public void use() { System.out.println("Using Product B"); } } // 工厂接口 public interface Factory { Product createProduct(); } // 具体工厂 A public class ConcreteFactoryA implements Factory { public Product createProduct() { return new ConcreteProductA(); } } // 具体工厂 B public class ConcreteFactoryB implements Factory { public Product createProduct() { return new ConcreteProductB(); } } // 客户端代码 public class Client { public static void main(String[] args) { Factory factory = new ConcreteFactoryA(); Product product = factory.createProduct(); product.use(); } } -
抽象工厂模式
// 产品 A1 接口 public interface ProductA { void use(); } // 产品 B1 接口 public interface ProductB { void use(); } // 具体产品 A1 public class ConcreteProductA1 implements ProductA { public void use() { System.out.println("Using Product A1"); } } // 具体产品 B1 public class ConcreteProductB1 implements ProductB { public void use() { System.out.println("Using Product B1"); } } // 具体产品 A2 public class ConcreteProductA2 implements ProductA { public void use() { System.out.println("Using Product A2"); } } // 具体产品 B2 public class ConcreteProductB2 implements ProductB { public void use() { System.out.println("Using Product B2"); } } // 抽象工厂 public interface AbstractFactory { ProductA createProductA(); ProductB createProductB(); } // 具体工厂 1 public class ConcreteFactory1 implements AbstractFactory { public ProductA createProductA() { return new ConcreteProductA1(); } public ProductB createProductB() { return new ConcreteProductB1(); } } // 具体工厂 2 public class ConcreteFactory2 implements AbstractFactory { public ProductA createProductA() { return new ConcreteProductA2(); } public ProductB createProductB() { return new ConcreteProductB2(); } } // 客户端代码 public class Client { public static void main(String[] args) { AbstractFactory factory = new ConcreteFactory1(); ProductA productA = factory.createProductA(); ProductB productB = factory.createProductB(); productA.use(); productB.use(); } }
- 简单工厂模式
-
单例模式
创建型设计模式,确保一个类在整个应用程序中只有一个实例,并提供全局访问点。
单例模式通常用于管理全局状态、配置文件、数据库连接等资源。
-
饿汉式
在类加载时就创建实例,这样可以避免线程安全问题,但可能会在应用启动时就创建实例,增加了初始加载的时间。
public class Singleton { // 立即创建实例 private static final Singleton instance = new Singleton(); // 私有构造函数,防止外部直接创建实例 private Singleton() {} // 提供公共的静态方法获取实例 public static Singleton getInstance() { return instance; } } -
懒汉式
只有在第一次使用时才创建实例。需要注意线程安全问题。
public class Singleton { // 初始化时实例为 null private static Singleton instance; // 私有构造函数,防止外部直接创建实例 private Singleton() {} // 提供公共的静态方法获取实例 public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } -
双重检查锁定
优化懒汉式,通过双重检查机制来减少同步开销。
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; } } -
枚举方式
用枚举的特性实现单例模式,既简单又安全,避免了序列化问题。
public enum Singleton { INSTANCE; // 可以添加其他方法 public void doSomething() { // 实现方法 } }
-
-
装饰器模式
结构型设计模式,用于动态地将职责附加到对象上。装饰器模式允许用户在不改变对象的结构的情况下,扩展对象的功能。它通常用于需要动态地添加功能的情况,并且当功能需要组合使用时尤其有用。
假设我们有一个
Coffee类和一个CoffeeDecorator类来动态地扩展Coffee类的功能-
组件(Component):定义一个接口,声明了可以被装饰的对象的共同操作。
public interface Coffee { String getDescription(); double cost(); } -
具体组件(ConcreteComponent):实现了
Component接口的具体对象,它是被装饰的对象。public class SimpleCoffee implements Coffee { @Override public String getDescription() { return "Simple coffee"; } @Override public double cost() { return 5.00; } } -
装饰器(Decorator):持有一个
Component对象的引用,并实现Component接口,从而可以在不改变Component对象的基础上扩展功能。public abstract class CoffeeDecorator implements Coffee { protected Coffee decoratedCoffee; public CoffeeDecorator(Coffee decoratedCoffee) { this.decoratedCoffee = decoratedCoffee; } @Override public String getDescription() { return decoratedCoffee.getDescription(); } @Override public double cost() { return decoratedCoffee.cost(); } } -
具体装饰器(ConcreteDecorator):继承自
Decorator,实现了具体的附加功能。public class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee decoratedCoffee) { super(decoratedCoffee); } @Override public String getDescription() { return decoratedCoffee.getDescription() + ", with milk"; } @Override public double cost() { return decoratedCoffee.cost() + 1.00; } } public class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee decoratedCoffee) { super(decoratedCoffee); } @Override public String getDescription() { return decoratedCoffee.getDescription() + ", with sugar"; } @Override public double cost() { return decoratedCoffee.cost() + 0.50; } } -
调用
public class DecoratorPatternDemo { public static void main(String[] args) { Coffee coffee = new SimpleCoffee(); System.out.println(coffee.getDescription() + " $" + coffee.cost()); Coffee milkCoffee = new MilkDecorator(new SimpleCoffee()); System.out.println(milkCoffee.getDescription() + " $" + milkCoffee.cost()); Coffee sugarMilkCoffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee())); System.out.println(sugarMilkCoffee.getDescription() + " $" + sugarMilkCoffee.cost()); } }
-
-
模板方法模式
行为型设计模式,它定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。
假设我们有一个制作咖啡和制作茶的过程,这两个过程大部分步骤是相似的,但具体的步骤有所不同。我们可以使用模板方法模式来定义这个流程。
-
抽象类(AbstractClass):定义了模板方法以及一些基本方法的实现,其中模板方法定义了算法的骨架,基本方法则是算法中的步骤,这些步骤可能由子类实现。
public abstract class CaffeineBeverage { // 模板方法,定义算法的骨架 public final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } // 基本方法,由子类实现 protected abstract void brew(); protected abstract void addCondiments(); // 共同的步骤 private void boilWater() { System.out.println("Boiling water"); } private void pourInCup() { System.out.println("Pouring into cup"); } } -
具体子类(ConcreteClass):继承抽象类并实现抽象类中定义的抽象方法,以提供具体的实现。
public class Coffee extends CaffeineBeverage {
@Override
protected void brew() {
System.out.println(“Dripping coffee through filter”);
}@Override protected void addCondiments() { System.out.println("Adding sugar and milk"); }}
public class Tea extends CaffeineBeverage {
@Override
protected void brew() {
System.out.println(“Steeping the tea”);
}@Override protected void addCondiments() { System.out.println("Adding lemon"); }}
3. 调用 ```java public class TemplateMethodDemo { public static void main(String[] args) { CaffeineBeverage coffee = new Coffee(); coffee.prepareRecipe(); System.out.println(); CaffeineBeverage tea = new Tea(); tea.prepareRecipe(); } } -
-
适配器模式
结构型设计模式,它允许将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而无法一起工作的类能够一起工作。这个模式常用于解决类接口不兼容的问题。
-
目标接口(Target):客户端期望的接口,定义了客户端能够使用的接口。
public interface Target { void request(); } -
源接口(Adaptee):已有的接口,客户端不期望的接口。
public class Adaptee { public void specificRequest() { System.out.println("Specific request"); } } -
适配器(Adapter):实现目标接口,并将请求委托给源接口中的对象,从而将两个接口适配起来。
public class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void request() { adaptee.specificRequest(); } } -
客户端
public class AdapterPatternDemo { public static void main(String[] args) { Adaptee adaptee = new Adaptee(); Target target = new Adapter(adaptee); target.request(); // 输出: Specific request } }
-
2. Java 基础语法
掌握 Java 数据类型的分类
- 基本数据类型和引用数据类型。
掌握基本数据类型的种类
-
基本数据类型包括四类八种:
-
整数类型:
byte:8 位,范围是 -128 到 127。short:16 位,范围是 -32,768 到 32,767。int:32 位,范围是 -2^31 到 2^31-1。long:64 位,范围是 -2^63 到 2^63-1。
-
浮点类型:
float:32 位,单精度,约 6-7 位有效数字。double:64 位,双精度,约 15-16 位有效数字。
-
字符类型:
char:16 位,表示一个 Unicode 字符,范围是 0 到 65,535。
-
布尔类型:
boolean:表示真假值,只有两个取值:true和false。
-
-
引用数据类型(Reference Data Types)包括类(Class)、接口(Interface)和数组(Array)。
掌握 Java 基本类型变量的定义和使用
public class BasicDataTypes {
public static void main(String[] args) {
// 整数类型
byte b = 100;
short s = 10000;
int i = 100000;
long l = 10000000000L;
// 浮点类型
float f = 234.5f;
double d = 123.456;
// 字符类型
char c = 'A';
// 布尔类型
boolean bool = true;
// 输出变量值
System.out.println("byte: " + b);
System.out.println("short: " + s);
System.out.println("int: " + i);
System.out.println("long: " + l);
System.out.println("float: " + f);
System.out.println("double: " + d);
System.out.println("char: " + c);
System.out.println("boolean: " + bool);
}
}
掌握 Java 整数类型分类、长度、字节大小和表示范围
byte:8 位,范围是 -128 到 127。short:16 位,范围是 -32,768 到 32,767。int:32 位,范围是 -2^31 到 2^31-1。long:64 位,范围是 -2^63 到 2^63-1。
掌握 Java 浮点数类型遵从标准的浮点规则
-
遵从 IEEE 754 标准
-
浮点数遵循以下规则:
表示方式:
- 浮点数由符号位、指数位和尾数位(也称为有效位)组成。
- float 类型有 1 位符号位,8 位指数位,23 位尾数位。
- double 类型有 1 位符号位,11 位指数位,52 位尾数位。
舍入方式:
- IEEE 754 定义了几种舍入方式,Java 默认使用最近舍入(也称为四舍五入),当遇到舍入位为 5 的情况,舍入到最接近的偶数。
特殊值:
- 正零(+0.0)和负零(-0.0)
- 无穷大(正无穷大和负无穷大)
- NaN(Not a Number,用于表示无法表示的数值,如 0 除以 0)
精度和范围:
- float 的范围大约为 1.4×10−451.4 \times 10^{-45}1.4×10−45 到 3.4×10383.4 \times 10^{38}3.4×1038
- double 的范围大约为 4.9×10−3244.9 \times 10^{-324}4.9×10−324 到 1.8×103081.8 \times 10^{308}1.8×10308
掌握 Java 基本数据类型转换
-
自动类型转换:是指小范围的数据类型可以自动转换为大范围的数据类型
public class AutomaticTypeConversion { public static void main(String[] args) { int intVal = 100; double doubleVal = intVal; // int 自动转换为 double System.out.println("intVal: " + intVal); System.out.println("doubleVal: " + doubleVal); } } -
强制类型转换:需要使用显式转换操作符,将大范围的数据类型转换为小范围的数据类型,可能会丢失数据或精度。
public class ExplicitTypeConversion { public static void main(String[] args) { double doubleVal = 100.04; int intVal = (int) doubleVal; // double 强制转换为 int System.out.println("doubleVal: " + doubleVal); System.out.println("intVal: " + intVal); } } -
整数类型与字符类型的转换
public class IntCharConversion { public static void main(String[] args) { int intVal = 65; char charVal = (char) intVal; // int 强制转换为 char System.out.println("intVal: " + intVal); System.out.println("charVal: " + charVal); // 输出字符 'A' } } -
浮点类型与整数类型的转换
public class FloatIntConversion { public static void main(String[] args) { float floatVal = 10.5f; int intVal = (int) floatVal; // float 强制转换为 int System.out.println("floatVal: " + floatVal); System.out.println("intVal: " + intVal); // 输出 10,舍弃小数部分 } } -
自动类型提升:表达式计算中,较小的数据类型会自动提升为较大的数据类型以进行计算
public class TypePromotion { public static void main(String[] args) { byte b = 40; char c = 'A'; int i = 50; float f = 10.5f; double d = 20.5; double result = (b * c) + (i / f) - (d * i); System.out.println("结果: " + result); } } -
包装类的类型转换
public class WrapperConversion { public static void main(String[] args) { String str = "123"; int intVal = Integer.parseInt(str); // 将字符串转换为 int String doubleStr = "123.45"; double doubleVal = Double.parseDouble(doubleStr); // 将字符串转换为 double System.out.println("intVal: " + intVal); System.out.println("doubleVal: " + doubleVal); } }
掌握布尔类型的转换
- Java 不允许将布尔类型隐式或显式转换为其他数据类型,这样可以避免潜在的逻辑错误和混淆。
- 可以使用
Boolean.parseBoolean(String s)方法将字符串转换为布尔类型。 - 可以使用
Boolean.toString(boolean b)方法或直接通过字符串连接操作将布尔类型转换为字符串。
掌握 Java 变量的声明
- 基本数据类型变量
- 引用类型变量
- 局部变量
- 实例变量
- 类变量(静态变量)
掌握 Java 变量的作用域
-
变量的作用域指的是变量在程序中可以被访问的范围
-
局部变量:在方法、构造方法或块中声明的变量,它们只在其所在的方法、构造方法或块内有效
public class LocalVariableScope { public static void main(String[] args) { int localVar = 10; // 局部变量 if (localVar > 5) { int blockVar = 20; // 块级局部变量 System.out.println("blockVar: " + blockVar); // 有效 } // System.out.println("blockVar: " + blockVar); // 错误,blockVar 超出作用范围 System.out.println("localVar: " + localVar); // 有效 } } -
实例变量: 类中声明的变量,作用域是整个类,类中所有方法和构造方法都可以访问
public class InstanceVariableScope { int instanceVar; // 实例变量 public void display() { System.out.println("instanceVar: " + instanceVar); // 有效 } public static void main(String[] args) { InstanceVariableScope obj = new InstanceVariableScope(); obj.instanceVar = 30; obj.display(); // 有效 } } -
类变量(静态变量):使用
static关键字声明,属于整个类,而不是类的某个实例。作用域是整个类,可以通过类名或实例名进行访问。public class ClassVariableScope { static int classVar; // 静态变量 public static void display() { System.out.println("classVar: " + classVar); // 有效 } public static void main(String[] args) { ClassVariableScope.classVar = 50; ClassVariableScope.display(); // 有效 } } -
方法参数:在方法声明中定义的变量,其作用域是整个方法。方法参数在方法调用时被初始化,只在方法内有效。
public class MethodParameterScope { public void display(int param) { // 方法参数 System.out.println("param: " + param); // 有效 } public static void main(String[] args) { MethodParameterScope obj = new MethodParameterScope(); obj.display(100); // 有效 } } -
构造方法参数:在构造方法声明中定义的变量,其作用域是整个构造方法。
public class ConstructorParameterScope { int instanceVar; public ConstructorParameterScope(int param) { // 构造方法参数 instanceVar = param; // 有效 } public void display() { System.out.println("instanceVar: " + instanceVar); } public static void main(String[] args) { ConstructorParameterScope obj = new ConstructorParameterScope(200); obj.display(); // 有效 } }
掌握 Java中定义常量的关键字
-
final。使用final关键字修饰的变量一旦被赋值,就不能再修改其值。通常在类中定义为static final,这样就可以在不创建类实例的情况下访问它们。public class Constants { public static final int MAX_VALUE = 100; public static final String ERROR_MESSAGE = "An error has occurred."; }
掌握 Java 中基本的运算符使用
-
算术运算符
int a = 10; int b = 5; int sum = a + b; // 加法,结果是15 int difference = a - b; // 减法,结果是5 int product = a * b; // 乘法,结果是50 int quotient = a / b; // 除法,结果是2 int remainder = a % b; // 取余,结果是0 -
赋值运算符
int c = 10; // 赋值运算符,将10赋值给变量c c += 5; // 等价于c = c + 5,结果是15 c -= 5; // 等价于c = c - 5,结果是10 c *= 2; // 等价于c = c * 2,结果是20 c /= 2; // 等价于c = c / 2,结果是10 c %= 3; // 等价于c = c % 3,结果是1 -
关系运算符
int x = 10; int y = 5; boolean result1 = (x == y); // 等于,结果是false boolean result2 = (x != y); // 不等于,结果是true boolean result3 = (x > y); // 大于,结果是true boolean result4 = (x < y); // 小于,结果是false boolean result5 = (x >= y); // 大于等于,结果是true boolean result6 = (x <= y); // 小于等于,结果是false -
逻辑运算符
boolean a = true; boolean b = false; boolean andResult = a && b; // 逻辑与,结果是false boolean orResult = a || b; // 逻辑或,结果是true boolean notResult = !a; // 逻辑非,结果是false -
位运算符
int m = 5; // 二进制:0101 int n = 3; // 二进制:0011 int andResult = m & n; // 按位与,结果是1(二进制:0001) int orResult = m | n; // 按位或,结果是7(二进制:0111) int xorResult = m ^ n; // 按位异或,结果是6(二进制:0110) int notResult = ~m; // 按位非,结果是-6(二进制:1111 1010,对于32位整数) int leftShift = m << 2; // 左移位,结果是20(二进制:10100) int rightShift = m >> 2; // 右移位,结果是1(二进制:0001) -
条件运算符(三目运算符)
int a = 10;
int b = 20;
int max = (a > b) ? a : b; // 如果a大于b,max的值是a,否则是b,结果是20
掌握 Java 中运算符的优先级
-
Java中运算符的优先级列表,从高到低排列:
- 圆括号:
(),用于改变默认的运算符优先级 - 一元运算符:
+-++--!~(正负号,递增递减,逻辑非,按位非) - 乘法和除法:
*/% - 加法和减法:
+- - 位移运算符:
<<>>>>> - 关系运算符:
<<=>>=instanceof - 相等运算符:
==!= - 按位与:
& - 按位异或:
^ - 按位或:
| - 逻辑与:
&& - 逻辑或:
|| - 三元运算符:
? : - 赋值运算符:
=+=-=*=/=%=<<=>>=>>>=&=^=|=
int a = 10; int b = 5; int c = 20; int result; // 示例1:圆括号优先 result = a + b * c; // 结果是110,因为b*c先计算,结果是100,然后加上a result = (a + b) * c; // 结果是300,因为(a + b)先计算,结果是15,然后乘以c // 示例2:一元运算符 result = -a + b; // 结果是-5,因为-a先计算,结果是-10,然后加上b // 示例3:乘法和加法 result = a + b * c / a; // 结果是15,因为b*c先计算,结果是100,然后/10,结果是10,最后加上a // 示例4:逻辑运算符 boolean flag = (a > b) && (c > a); // 结果是true,因为(a > b)和(c > a)都为true // 示例5:三元运算符 result = (a > b) ? a : b; // 结果是10,因为a大于b // 示例6:赋值运算符 a += b * c; // a = a + (b * c),结果是10 + 100 = 110 - 圆括号:
掌握泛型定义
- 泛型允许类、接口和方法操作参数化类型,以实现类型安全和代码复用。
- 通过使用泛型,可以创建可以处理任何类型的类、接口和方法,而不必重复编写相同的代码逻辑。
了解泛型接口和泛型方法,泛型的使用规则,了解泛型的使用方式
-
泛型类
public class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } } Box<String> stringBox = new Box<>(); stringBox.setContent("Hello, Generics"); String content = stringBox.getContent(); -
泛型方法
public class GenericMethods { public static <T> void printArray(T[] array) { for (T element : array) { System.out.println(element); } } } Integer[] intArray = {1, 2, 3, 4, 5}; String[] stringArray = {"A", "B", "C", "D"}; GenericMethods.printArray(intArray); GenericMethods.printArray(stringArray); -
泛型接口
public interface Pair<K, V> {
K getKey();
V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
Pair<String, Integer> pair = new OrderedPair<>("Age", 30);
System.out.println("Key: " + pair.getKey());
System.out.println("Value: " + pair.getValue());
-
通配符
无界通配符:
<?>,表示任何类型。public static void printList(List<?> list) { for (Object element : list) { System.out.println(element); } }有界通配符:
<? extends T>,表示类型的上界。public static void processElements(List<? extends Number> list) { for (Number element : list) { System.out.println(element.doubleValue()); } }下界通配符:
<? super T>,表示类型的下界。public static void addElements(List<? super Integer> list) { list.add(1); list.add(2); list.add(3); }示例:
import java.util.List; import java.util.ArrayList; public class Main { public static void main(String[] args) { // 泛型类示例 Box<String> stringBox = new Box<>(); stringBox.setContent("Hello, Generics"); System.out.println("Content: " + stringBox.getContent()); // 泛型方法示例 Integer[] intArray = {1, 2, 3, 4, 5}; GenericMethods.printArray(intArray); // 泛型接口示例 Pair<String, Integer> pair = new OrderedPair<>("Age", 30); System.out.println("Key: " + pair.getKey()); System.out.println("Value: " + pair.getValue()); // 通配符示例 List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); printList(list); processElements(list); addElements(list); } public static void printList(List<?> list) { for (Object element : list) { System.out.println(element); } } public static void processElements(List<? extends Number> list) { for (Number element : list) { System.out.println(element.doubleValue()); } } public static void addElements(List<? super Integer> list) { list.add(4); list.add(5); list.add(6); } }
掌握流程控制分支结构
if-else
int num = 10;
if (num > 0) {
System.out.println("Number is positive.");
} else if (num < 0) {
System.out.println("Number is negative.");
} else {
System.out.println("Number is zero.");
}
-
switch-caseint dayOfWeek = 3; String day; switch (dayOfWeek) { case 1: day = "Monday"; break; case 2: day = "Tuesday"; break; case 3: day = "Wednesday"; break; case 4: day = "Thursday"; break; case 5: day = "Friday"; break; case 6: day = "Saturday"; break; case 7: day = "Sunday"; break; default: day = "Invalid day"; break; } System.out.println("Today is: " + day); -
三目运算符
int num = 10; String result; result = (num > 0) ? "Number is positive" : "Number is not positive"; System.out.println(result);
掌握循环语句使用
-
循环结构允许重复执行一段代码,直到某个条件为 false或达成某种条件或使用循环控制语句跳出循环。
-
for循环通常用于已知次数的循环
for (int i = 0; i < 5; i++) { System.out.println("i is: " + i); } -
增强型
for循环(for-each循环)通常用于遍历数组或集合。
int[] numbers = {1, 2, 3, 4, 5}; for (int num : numbers) { System.out.println("Number: " + num); } -
while循环通常用于死循环
int count = 0; while (true) { System.out.println("Count is: " + count); count++; }int count = 0; while (count < 5) { System.out.println("Count is: " + count); count++; } -
do-while循环
do-while 循环会先执行一次循环体,然后再检查条件。意味着至少会执行一次。
int count = 0;
do {
System.out.println("Count is: " + count);
count++;
} while (count < 5);
掌握跳转语句
-
循环控制语句
-
break语句:用于终止循环。for (int i = 0; i < 10; i++) { if (i == 5) { break; // 退出循环 } System.out.println("i is: " + i); } -
continue语句:用于跳过当前迭代,继续下一次迭代。for (int i = 0; i < 10; i++) { if (i % 2 == 0) { continue; // 跳过偶数 } System.out.println("i is: " + i); }
掌握方法定义内容
-
方法定义的基本结构:
修饰符:如
public、private、protected、static等,定义了方法的访问权限和特性。public: 公开方法,任何类都可以访问。private: 私有方法,仅在定义该方法的类内可访问。protected: 受保护的方法,同一包中的类和子类可以访问。default(无修饰符):包级私有,同一包中的类可以访问。static: 静态方法,可以通过类名直接调用,不需要实例化对象。
返回类型:定义方法返回的值的类型。如果方法不返回任何值,使用
void。方法名:标识方法的名称,不能以数字开头,不能使用Java关键字,通常使用小驼峰命名法。
参数列表:包含零个或多个参数,每个参数包括类型和名称,参数用逗号分隔。
方法体:用大括号括起来的代码块,包含方法执行的具体逻辑。
public class MathUtils { // 无参数无返回值的方法 public void printHello() { System.out.println("Hello, World!"); } // 带参数无返回值的方法 public void printNumber(int number) { System.out.println("The number is: " + number); } // 带参数并返回结果的方法 public int add(int a, int b) { return a + b; } // 静态方法 public static int multiply(int a, int b) { return a * b; } // 重载方法(方法名相同,参数列表不同) public int add(int a, int b, int c) { return a + b + c; } // 私有方法 private void privateMethod() { System.out.println("This is a private method"); } public static void main(String[] args) { MathUtils utils = new MathUtils(); // 调用无参数无返回值的方法 utils.printHello(); // 调用带参数无返回值的方法 utils.printNumber(5); // 调用带参数并返回结果的方法 int sum = utils.add(3, 7); System.out.println("Sum: " + sum); // 调用静态方法 int product = MathUtils.multiply(4, 5); System.out.println("Product: " + product); // 调用重载方法 int sumThree = utils.add(1, 2, 3); System.out.println("Sum of three numbers: " + sumThree); // 私有方法只能在类内部调用 utils.privateMethod(); } }
掌握方法调用方式
-
实例(对象)方法调用和静态方法调用
-
静态方法调用:直接通过类名调用,使用点操作符(
.)通过类名调用静态方法。public class MathUtils { // 静态方法 public static int add(int a, int b) { return a + b; } public static void main(String[] args) { // 直接通过类名调用静态方法 int sum = MathUtils.add(5, 3); System.out.println("Sum: " + sum); } } -
实例(对象)方法调用:对象实例相关联的方法,必须通过对象实例来调用。
public class Person { // 实例变量 private String name; // 构造方法 public Person(String name) { this.name = name; } // 实例方法 public void greet() { System.out.println("Hello, my name is " + name); } public static void main(String[] args) { // 创建对象实例 Person person = new Person("Alice"); // 调用实例方法 person.greet(); } } -
递归调用:递归调用自身来解决问题。递归调用需要有一个明确的终止条件,否则会导致栈溢出错误。
public class RecursionExample { public static int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } } public static void main(String[] args) { int result = factorial(5); System.out.println("Factorial of 5: " + result); // 输出120 } }
掌握调用方法时传参类型
-
基本数据类型参数
-
引用数据类型参数
-
对象
-
数组
-
字符串参数
-
-
可变参数
-
null
-
final 参数
public class MethodParameterExample {
// 基本数据类型参数
public void printBasicType(int number) {
System.out.println("Basic type number: " + number);
}
// 引用数据类型参数
static class Person {
String name;
Person(String name) {
this.name = name;
}
}
public void printPerson(Person person) {
System.out.println("Person's name: " + person.name);
}
// 字符串参数
public void printString(String message) {
System.out.println("Message: " + message);
}
// 数组参数
public void printArray(int[] array) {
for (int num : array) {
System.out.print(num + " ");
}
System.out.println();
}
// 可变参数
public void printVarargs(int... numbers) {
for (int num : numbers) {
System.out.print(num + " ");
}
System.out.println();
}
public static void main(String[] args) {
MethodParameterExample example = new MethodParameterExample();
// 调用方法传递基本数据类型参数
example.printBasicType(10);
// 调用方法传递引用数据类型参数
Person person = new Person("Alice");
example.printPerson(person);
// 调用方法传递字符串参数
example.printString("Hello, Java!");
// 调用方法传递数组参数
int[] numbers = {1, 2, 3, 4, 5};
example.printArray(numbers);
// 调用方法传递可变参数
example.printVarargs(1, 2, 3);
example.printVarargs(4, 5, 6, 7, 8);
}
}
掌握构造方法的特点
-
构造方法:用于在创建对象时初始化对象的状态。
-
特点:
- 构造方法的名称与类名相同
- 构造方法没有返回类型
- 构造方法在对象创建时自动调用
- 构造方法可以重载
- 默认构造方法
- 使用
this调用其他构造方法 - 使用
super调用父类构造方法 - 构造方法不能被继承,但可以被调用
public class Main { public static void main(String[] args) { Person person = new Person("John", 25); person.displayInfo(); Employee employee = new Employee("Alice", 30, 12345); employee.displayInfo(); } } class Person { protected String name; protected int age; // 无参构造方法 public Person() { this.name = "Unknown"; this.age = 0; } // 带参数的构造方法 public Person(String name, int age) { this.name = name; this.age = age; } public void displayInfo() { System.out.println("Name: " + name + ", Age: " + age); } } class Employee extends Person { private int employeeId; // 调用父类的无参构造方法 public Employee() { super(); this.employeeId = 0; } // 调用父类的带参数构造方法 public Employee(String name, int age, int employeeId) { super(name, age); this.employeeId = employeeId; } @Override public void displayInfo() { super.displayInfo(); System.out.println("Employee ID: " + employeeId); } }
掌握重载定义
-
方法名相同,参数列表不同
public class OverloadingExample { // 无参数的方法 public void print() { System.out.println("Print with no arguments"); } // 一个参数的方法 public void print(int a) { System.out.println("Print with one argument: " + a); } // 两个参数的方法 public void print(int a, int b) { System.out.println("Print with two arguments: " + a + ", " + b); } // 不同类型参数的方法 public void print(String message) { System.out.println("Print with a string: " + message); } // 不同类型和数量参数的方法 public void print(String message, int a) { System.out.println("Print with a string and an int: " + message + ", " + a); } public static void main(String[] args) { OverloadingExample example = new OverloadingExample(); // 调用不同的重载方法 example.print(); example.print(10); example.print(10, 20); example.print("Hello"); example.print("Number", 30); } }
了解重载和重写的区别
-
方法重载:指在同一个类中定义多个同名的方法,这些方法的参数列表不同。
-
方法重写:指子类重新定义父类中的方法,方法名称、参数列表和返回类型都必须相同。方法重写是在继承关系中发生的,用于实现多态性,使子类能够提供特定的实现。
class Animal { public void makeSound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("Dog barks"); } // 重载方法 public void makeSound(String sound) { System.out.println("Dog makes sound: " + sound); } } public class OverloadingAndOverridingExample { public static void main(String[] args) { Animal animal = new Animal(); animal.makeSound(); // 调用父类方法 Dog dog = new Dog(); dog.makeSound(); // 调用子类重写的方法 dog.makeSound("Woof"); // 调用子类重载的方法 Animal animalRef = new Dog(); animalRef.makeSound(); // 调用子类重写的方法,体现多态性 } }
了解方法重载的特点
- 方法名称相同
- 参数列表不同
- 参数的类型不同。
- 参数的数量不同。
- 参数的顺序不同。
- 返回类型可以相同也可以不同
- 可以有不同的访问修饰符
- 可以抛出不同的异常
- 与继承无关
- 编译时多态性
- 根据方法调用时提供的参数确定要调用的具体方法。
掌握重载使用场景
-
方法重载在 Java 中具有广泛的应用场景,主要用于:
- 提供灵活的初始化选项
- 处理不同类型和数量的输入
- 模拟默认参数值
- 实现多种数据转换方式
- 处理不同类型的集合
通过合理使用方法重载,可以提高代码的灵活性、可读性和可维护性。
3. Java 面向对象编程
掌握什么是 Java 包,如何声明和使用包,以及它们在代码组织中的作用
-
什么是:包是一个用来组织相关类、接口和子包的命名空间。
-
声明:在 Java 源文件的顶部使用
package关键字声明。package语句必须是文件的第一行,且只能出现一次。package 包名; -
使用:使用
import语句来导入这些类。可以导入整个包中的所有类,也可以只导入特定的类。package com.example.main; // 导入 com.example.myapp 包中的 MyClass 类 import com.example.myapp.MyClass; public class MainClass { public static void main(String[] args) { // 创建 MyClass 的实例并调用方法 MyClass myClass = new MyClass(); myClass.display(); } } -
作用:
- 代码组织:将相关的类和接口组织在一起,从而使代码更有条理
- 避免命名冲突:包允许在不同的包中使用相同的类名。例如,
com.example.myapp.User和com.example.admin.User是两个不同的类,因为它们属于不同的包。 - 访问控制:Java 的访问修饰符可以控制类、字段和方法的可见性。
public:对所有类可见。protected:同包的类和子类可见。default:同包的类可见。private:同类可见。
掌握访问修饰符(public、private、protected 和默认)的作用和区别,以及它们在不同作用域中的使用
| 作用 | 类 | 方法和字段 | 构造函数 | |
|---|---|---|---|---|
| public | public 修饰符表示该成员或类可以被任何其他类访问,无论这些类是否在同一个包中。 | 一个 public 类可以被任何其他类访问,前提是其他类能访问到该类所在的包。 | 可以被任何其他类访问,只要这些类能访问到包含这些方法和字段的类。 | 可以被任何类访问,用于创建类的实例。 |
| private | private 修饰符表示该成员或类只能在其定义的类内部访问。外部类无法访问 private 成员。 | private 只适用于内部类(嵌套类),顶级类不能是 private。 | 只能在定义它们的类内部访问,不能在类外部直接访问。 | 只能在定义它们的类内部调用,用于控制实例化过程,如实现单例模式。 |
| protected | protected 修饰符表示该成员或类可以被同一个包中的其他类以及不同包中的子类访问。 | protected 只适用于内部类(嵌套类),顶级类不能是 protected。 | 可以被同一个包中的其他类以及不同包中的子类访问,但不能被包外的非子类访问。 | 可以被同一个包中的其他类以及不同包中的子类访问,用于允许子类创建父类的实例。 |
| 默认 | 如果没有指定任何访问修饰符,即为默认访问权限(也称为包私有)。这表示该成员或类只能在同一个包中访问。 | 一个默认访问修饰符的类只能被同一个包中的其他类访问。顶级类只能是 public 或默认(包私有),不能是 protected 或 private。 | 只能被同一个包中的其他类访问,不能被其他包中的类访问。 | 只能被同包中的其他类访问。 |
掌握什么是类和对象,如何定义类和创建对象
-
类:类是对象的蓝图或模板,是创建对象的基础。它定义了一组属性(字段)和方法,这些属性和方法描述了一个对象的状态和行为。
-
属性:描述对象的状态或特征。例如,人类的属性可以包括名字、年龄等。
方法:定义对象的行为。例如,人类的方法可以包括吃饭、睡觉等。
构造函数:用于创建对象并初始化其属性。
public class Person { // 属性 private String name; private int age; // 构造函数 public Person(String name, int age) { this.name = name; this.age = age; } // 方法 public void greet() { System.out.println("Hello, my name is " + name + " and I am " + age + " years old."); } // 访问器方法(getter) public String getName() { return name; } public int getAge() { return age; } // 修改器方法(setter) public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } }
-
-
对象:对象是类的实例。它是实际存在的、根据类定义的蓝图创建的实体。每个对象都有自己的属性值和可以调用的方法。
-
声明对象:声明一个类类型的变量。
实例化对象:使用
new关键字和类的构造函数创建对象。public class Main { public static void main(String[] args) { // 创建对象 Person person = new Person("Alice", 30); // 使用对象的方法 person.greet(); // 访问和修改对象的属性 System.out.println("Name: " + person.getName()); System.out.println("Age: " + person.getAge()); person.setName("Bob"); person.setAge(25); person.greet(); } }
-
熟悉实例化对象的过程,如何使用构造方法来初始化对象
-
实例化对象的过程
- 定义一个类:其中包含字段、构造函数和方法。
- 创建对象:调用类的构造函数来创建对象。
- 初始化对象:构造函数用于设置对象的初始状态。
-
构造函数初始化对象
无参数构造函数:
Car car1 = new Car();有参数构造函数:
Car car2 = new Car("Toyota", "Corolla", 2020);
掌握继承,如何使用 Extends 关键字来实现继承以及如何处理 Java 中的单继承
-
extends :从父类继承属性和方法。子类可以扩展父类的功能,并且可以重写父类的方法。
class SubClass extends SuperClass { // 子类的字段和方法 } -
单继承: Java 只支持单继承,一个类只能有一个直接父类。
Dog继承自Animal,Dog类不能继承其他类。如果需要更复杂的功能,可以使用接口(interface)来实现多重继承的效果。public interface Pet { void play(); } public class Cat extends Animal implements Pet { public Cat(String name) { super(name); } @Override public void play() { System.out.println(name + " is playing."); } }public class Main { public static void main(String[] args) { Cat cat = new Cat("Whiskers"); cat.eat(); // 输出: Whiskers is eating. cat.play(); // 输出: Whiskers is playing. } }
掌握封装,如何使用访问修饰符实现封装,以及它在 Java 中的重要性和优势
- 封装:将数据(字段)和对数据的操作(方法)封装在一个类中,并隐藏其内部实现细节,只暴露必要的接口给外部。
- 定义类和字段
- 将类的字段设为
private,这样这些字段只能在类的内部访问。通过公共的方法(通常称为 getter 和 setter 方法)来访问和修改这些字段。
- 将类的字段设为
掌握多态性的概念,包括编译时多态和运行时多态,以及如何实现方法重写和方法重载
-
多态:一种物质的多种形态,Java中体现为一个方法或对象可以以不同的方式表现。多态性通常分为编译时多态和运行时多态。
-
编译时多态(静态多态):编译时多态性是通过方法重载实现的。
-
运行时多态(动态多态):运行时多态性是通过方法重写实现的。
实现方法重载
public class Calculator { // 方法重载:计算两个整数的和 public int add(int a, int b) { return a + b; } // 方法重载:计算三个整数的和 public int add(int a, int b, int c) { return a + b + c; } // 方法重载:计算两个双精度数的和 public double add(double a, double b) { return a + b; } }实现方法重写
public class Animal { // 父类方法 public void makeSound() { System.out.println("Some sound"); } } public class Dog extends Animal { // 重写父类方法 @Override public void makeSound() { System.out.println("Bark"); } } public class Cat extends Animal { // 重写父类方法 @Override public void makeSound() { System.out.println("Meow"); } }
掌握抽像类,如何声明和使用抽像类,抽像类和普通类之间有哪些区别
-
抽象类:不能直接实例化。抽象类可以包含抽象方法和非抽象(具体方法)方法。抽象方法是没有实现的方法,只定义了方法的签名,具体的实现由子类提供
-
声明抽象类:使用
abstract关键字。抽象类可以包含抽象方法和非抽象方法(具体方法)。// 抽象类 public abstract class Shape { // 抽象方法(没有方法体) public abstract void draw(); // 非抽象方法(有方法体) public void display() { System.out.println("Displaying shape"); } } -
使用抽象类:抽象类不能被实例化,只能通过继承来使用。子类必须实现抽象类中的所有抽象方法,除非子类也是抽象类。
// 继承抽象类并实现抽象方法的子类 public class Circle extends Shape { @Override public void draw() { System.out.println("Drawing a circle"); } } // 另一个继承抽象类的子类 public class Rectangle extends Shape { @Override public void draw() { System.out.println("Drawing a rectangle"); } } // 使用抽象类和子类 public class Main { public static void main(String[] args) { // 不能实例化抽象类 // Shape shape = new Shape(); // 编译错误 // 创建子类对象 Shape circle = new Circle(); Shape rectangle = new Rectangle(); // 调用抽象类的方法 circle.draw(); // 输出: Drawing a circle rectangle.draw(); // 输出: Drawing a rectangle circle.display(); // 输出: Displaying shape rectangle.display(); // 输出: Displaying shape } } -
抽象类与普通类的区别
-
- 实例化:
- 抽象类:不能直接实例化。只能通过子类来实例化。
- 普通类:可以直接实例化。
- 抽象方法:
- 抽象类:可以包含抽象方法(没有方法体),子类必须实现这些抽象方法。
- 普通类:不能包含抽象方法(所有方法都有实现)。
- 继承:
- 抽象类:可以包含具体方法和抽象方法。子类必须实现抽象方法,除非子类也是抽象类。
- 普通类:不涉及抽象方法的实现,子类可以继承普通类并直接使用它的方法和属性。
- 目的:
- 抽象类:用于提供一个共同的接口和部分实现,供多个子类继承和扩展。它通常用于定义通用的行为和属性。
- 普通类:用于创建具体的对象,通常实现所有的行为和属性。
- 设计模式:
- 抽象类:常用于实现设计模式中的模板方法模式和工厂方法模式。
- 普通类:用于实现具体的业务逻辑和对象行为。
-
掌握接口,理解接口的概念和作用,以及如何声明、实现和使用接口
-
概念和作用
接口:用于定义类应遵循的一组方法。接口定义了行为的契约,但不提供具体实现。
作用:提供行为契约、支持多重继承、提高代码灵活性和可扩展性、促进多态性。
-
声明接口:接口使用
interface关键字声明。public interface Drawable { // 接口中的方法 void draw(); } -
实现接口:类通过
implements关键字实现接口。实现接口时,类必须提供接口中所有方法的具体实现。public class Circle implements Drawable { @Override public void draw() { System.out.println("Drawing a circle"); } } public class Rectangle implements Drawable { @Override public void draw() { System.out.println("Drawing a rectangle"); } } -
使用接口:接口可以用作方法参数、返回类型或变量类型。
public class Main { public static void main(String[] args) { // 使用接口类型的引用 Drawable circle = new Circle(); Drawable rectangle = new Rectangle(); // 调用实现的方法 circle.draw(); // 输出: Drawing a circle rectangle.draw(); // 输出: Drawing a rectangle // 你也可以创建一个方法,接受接口类型的参数 drawShape(circle); // 输出: Drawing a circle drawShape(rectangle); // 输出: Drawing a rectangle } // 接受接口类型的参数 public static void drawShape(Drawable drawable) { drawable.draw(); } }
掌握静态变量和静态方法,如何声明和使用它们,与实例变量、实例方法有何区别
-
静态变量
-
静态变量属于类,而不是类的实例。它们被所有实例共享,不同实例之间的静态变量值是相同的。静态变量通常用于存储类级别的信息,例如计数器、常量等。
-
声明:
public class MyClass { // 静态变量 public static int staticVariable = 0; // 实例变量 public int instanceVariable = 1; } -
使用:
public class Main { public static void main(String[] args) { // 访问静态变量 System.out.println(MyClass.staticVariable); // 输出: 0 // 修改静态变量 MyClass.staticVariable = 10; System.out.println(MyClass.staticVariable); // 输出: 10 // 实例化对象 MyClass obj1 = new MyClass(); MyClass obj2 = new MyClass(); // 通过对象访问静态变量 System.out.println(obj1.staticVariable); // 输出: 10 System.out.println(obj2.staticVariable); // 输出: 10 // 静态变量在所有实例中共享 obj1.staticVariable = 20; System.out.println(obj2.staticVariable); // 输出: 20 } }
-
-
静态方法
-
静态方法属于类,而不是类的实例。它们可以在没有实例化对象的情况下被调用。静态方法只能访问静态变量和调用其他静态方法。
-
声明
public class MyClass { // 静态变量 public static int staticVariable = 0; // 静态方法 public static void staticMethod() { System.out.println("This is a static method."); } // 实例方法 public void instanceMethod() { System.out.println("This is an instance method."); } } -
使用
public class Main { public static void main(String[] args) { // 访问静态方法 MyClass.staticMethod(); // 输出: This is a static method. // 实例化对象 MyClass obj = new MyClass(); // 通过对象访问静态方法(不推荐,但可以) obj.staticMethod(); // 输出: This is a static method. // 实例方法必须通过对象调用 obj.instanceMethod(); // 输出: This is an instance method. } }
-
-
区别
-
静态变量和静态方法属于类,所有实例共享。静态变量和方法可以通过类名直接访问,也可以通过对象访问,但推荐通过类名访问。
-
实例变量和实例方法属于对象,每个对象有自己的实例变量和方法。实例方法可以访问静态变量和方法,但静态方法不能访问实例变量和方法。
-
理解静态声明和静态导入的概念,以及它们的用法
-
静态声明:类中使用
static关键字声明静态变量和静态方法。静态变量和静态方法属于类而不是类的实例,因此它们在所有实例之间共享。public class Example { // 静态变量 public static int count = 0; // 静态方法 public static void incrementCount() { count++; } public static void main(String[] args) { // 调用静态方法 Example.incrementCount(); System.out.println(Example.count); // 输出: 1 // 静态变量和方法可以通过实例访问,但推荐使用类名 Example obj = new Example(); obj.incrementCount(); System.out.println(Example.count); // 输出: 2 } } -
静态导入:允许直接访问类中的静态成员(静态变量和静态方法),而无需通过类名进行访问。它使用
import static语句来引入静态成员。// 假设有一个类 MathUtil public class MathUtil { public static final double PI = 3.141592653589793; public static int square(int x) { return x * x; } }import static MathUtil.PI; // 导入静态变量 import static MathUtil.square; // 导入静态方法 public class TestStaticImport { public static void main(String[] args) { // 使用静态导入后的成员,无需类名前缀 System.out.println("Value of PI: " + PI); // 输出: Value of PI: 3.141592653589793 System.out.println("Square of 5: " + square(5)); // 输出: Square of 5: 25 } }
掌握内部类概念,包括静态内部类、非静态内部类、局部内部类和匿名内部类,以及如何创建和使用它们
-
内部类:定义在一个类内部的类。
-
静态内部类:使用
static关键字声明的内部类。创建和使用:
public class OuterClass { private static String outerStaticField = "Outer static field"; // 静态内部类 public static class StaticNestedClass { public void display() { // 访问外部类的静态字段 System.out.println(outerStaticField); } } public static void main(String[] args) { // 创建静态内部类的实例 StaticNestedClass nestedClass = new StaticNestedClass(); nestedClass.display(); // 输出: Outer static field } } -
非静态内部类:没有使用
static关键字声明的内部类。创建和使用:
public class OuterClass { private String outerField = "Outer field"; // 非静态内部类 public class InnerClass { public void display() { // 访问外部类的实例字段 System.out.println(outerField); } } public static void main(String[] args) { // 创建外部类的实例 OuterClass outerClass = new OuterClass(); // 创建非静态内部类的实例 InnerClass innerClass = outerClass.new InnerClass(); innerClass.display(); // 输出: Outer field } } -
局部内部类:在方法、构造函数或代码块内部定义的类。局部内部类只能在其所在的方法、构造函数或代码块中访问。它不能有访问修饰符和
static修饰符。创建和使用:
public class OuterClass { public void outerMethod() { // 局部内部类 class LocalInnerClass { public void display() { System.out.println("Inside LocalInnerClass"); } } // 创建局部内部类的实例 LocalInnerClass localInner = new LocalInnerClass(); localInner.display(); // 输出: Inside LocalInnerClass } public static void main(String[] args) { OuterClass outerClass = new OuterClass(); outerClass.outerMethod(); } } -
匿名内部类:没有名字的内部类,通常用于实现接口或继承抽象类。匿名内部类在创建时定义并实例化,只能在创建时使用,不可以再被引用或创建其他实例。
创建和使用:
public class OuterClass { public void outerMethod() { // 匿名内部类实现接口 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("Inside anonymous inner class"); } }; // 执行匿名内部类的方法 new Thread(runnable).start(); } public static void main(String[] args) { OuterClass outerClass = new OuterClass(); outerClass.outerMethod(); } }
掌握如何实现单继承和多重继承
-
单继承:一个类只能继承一个直接父类。
// 父类(超类) public class Animal { public void eat() { System.out.println("Animal is eating"); } } // 子类(派生类) public class Dog extends Animal { public void bark() { System.out.println("Dog is barking"); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.eat(); // 从父类继承的方法 dog.bark(); // 子类的方法 } } -
多重继承:继承一个直接父类,实现多个接口。
// 接口A public interface InterfaceA { void methodA(); } // 接口B public interface InterfaceB { void methodB(); } // 实现多个接口的类 public class MyClass implements InterfaceA, InterfaceB { @Override public void methodA() { System.out.println("Method A from InterfaceA"); } @Override public void methodB() { System.out.println("Method B from InterfaceB"); } } public class Main { public static void main(String[] args) { MyClass myClass = new MyClass(); myClass.methodA(); // 输出: Method A from InterfaceA myClass.methodB(); // 输出: Method B from InterfaceB } }
掌握方法的 final 修饰符和作用与使用场景
-
防止方法被重写:当一个方法被声明为
final时,这意味着该方法不能被任何子类重写。 -
使用场景:
-
防止方法重写,设计不允许被更改的行为。
public class BaseClass { // final 方法,不能被重写 public final void finalMethod() { System.out.println("This is a final method."); } // 非 final 方法,可以被重写 public void nonFinalMethod() { System.out.println("This is a non-final method."); } } public class DerivedClass extends BaseClass { // 尝试重写 final 方法会导致编译错误 // @Override // public void finalMethod() { // System.out.println("Attempt to override final method."); // } @Override public void nonFinalMethod() { System.out.println("This is an overridden method."); } public static void main(String[] args) { DerivedClass obj = new DerivedClass(); obj.finalMethod(); // 输出: This is a final method. obj.nonFinalMethod(); // 输出: This is an overridden method. } } -
安全的类库设计,保护方法不被修改。
public class SecureLibrary { // final 方法,提供稳定的接口 public final void processRequest() { System.out.println("Processing request in a secure manner."); } // 非 final 方法,允许子类提供不同实现 public void logMessage(String message) { System.out.println("Logging message: " + message); } }
-
掌握 Super 关键字调用父类的构造方法与方法的用法和注意事项
-
子类构造方法可以使用
super()关键字调用父类的构造方法。如果父类有多个构造方法,子类可以选择适当的父类构造方法进行调用。如果子类构造方法中没有显式调用super(),则默认调用父类的无参构造方法(前提是父类有一个无参构造方法)。 -
调用父类构造方法:
-
super()必须是构造方法的第一行 -
父类构造方法必须存在
public class Parent { public Parent() { System.out.println("Parent constructor"); } public Parent(String message) { System.out.println("Parent constructor with message: " + message); } } public class Child extends Parent { public Child() { // 调用父类的无参构造方法 super(); System.out.println("Child constructor"); } public Child(String message) { // 调用父类的有参构造方法 super(message); System.out.println("Child constructor with message"); } public static void main(String[] args) { new Child(); // 输出: Parent constructor\nChild constructor new Child("Hello"); // 输出: Parent constructor with message: Hello\nChild constructor with message } } -
-
调用父类的方法
- 不能通过
super调用父类中的私有方法 - 当调用父类方法时,确保方法签名在子类中被正确重写,并且方法在父类中存在
public class Parent { public void display() { System.out.println("Display from Parent"); } } public class Child extends Parent { @Override public void display() { // 调用父类的 display 方法 super.display(); System.out.println("Display from Child"); } public static void main(String[] args) { Child child = new Child(); child.display(); // 输出: Display from Parent\nDisplay from Child } } - 不能通过
理解动态绑定(动态多态性)的概念,包括什么时候发生动态绑定和它的优势
-
动态绑定:指的是在程序运行时决定调用某个方法的具体实现,而不是在编译时确定。它使得程序能够根据对象的实际类型调用适当的方法。
public class Parent { public void display() { System.out.println("Display from Parent"); } } public class Child extends Parent { @Override public void display() { System.out.println("Display from Child"); } } public class TestDynamicBinding { public static void main(String[] args) { Parent obj = new Child(); // 父类引用指向子类对象 obj.display(); // 输出: Display from Child } } -
动态绑定的优势
- 增强代码的灵活性和可扩展性。
- 提高代码的可维护性。
- 支持通用接口和插件化设计。
掌握如何使用 Final 关键字来定义常量、阻止方法被重写和类被继承
-
定义常量:
public class Constants { // 定义常量 public static final int MAX_VALUE = 100; public static final String GREETING_MESSAGE = "Hello, World!"; public static void main(String[] args) { System.out.println("Max Value: " + MAX_VALUE); // 输出: Max Value: 100 System.out.println("Greeting: " + GREETING_MESSAGE); // 输出: Greeting: Hello, World! } } -
阻止方法被重写:定义
final方法public class BaseClass { // final 方法,不能被重写 public final void finalMethod() { System.out.println("This is a final method."); } public void normalMethod() { System.out.println("This is a normal method."); } } public class DerivedClass extends BaseClass { // 尝试重写 final 方法会导致编译错误 // @Override // public void finalMethod() { // System.out.println("Attempting to override final method."); // } @Override public void normalMethod() { System.out.println("This is an overridden method."); } public static void main(String[] args) { DerivedClass obj = new DerivedClass(); obj.finalMethod(); // 输出: This is a final method. obj.normalMethod(); // 输出: This is an overridden method. } } -
阻止类被继承:定义
final类public final class FinalClass { public void display() { System.out.println("This is a final class."); } } // 尝试继承 final 类会导致编译错误 // public class SubClass extends FinalClass { // // 编译错误 // } public class Main { public static void main(String[] args) { FinalClass obj = new FinalClass(); obj.display(); // 输出: This is a final class. } }
掌握接口与抽像类之间的差异,以及在什么情况下使用它们更适合
-
接口
public interface Animal { void eat(); void sleep(); } public class Dog implements Animal { @Override public void eat() { System.out.println("Dog is eating."); } @Override public void sleep() { System.out.println("Dog is sleeping."); } } -
抽象类
public abstract class Animal { public abstract void eat(); // 抽象方法,需要子类实现 public void sleep() { // 实现方法 System.out.println("Animal is sleeping."); } } public class Dog extends Animal { @Override public void eat() { System.out.println("Dog is eating."); } }特性 接口 (Interface) 抽象类 (Abstract Class) 方法 只能包含 public和abstract方法(Java 8+ 可以有default和static方法)可以有 abstract和非abstract方法变量 只能包含 public static final变量可以包含任何访问修饰符的字段(包括 public、protected、private)多重继承 支持类实现多个接口 不支持多重继承,一个类只能继承一个抽象类 构造方法 不能有构造方法 可以有构造方法 实现细节 实现完全由子类负责 可以有默认实现,也可以要求子类实现抽象方法 -
使用场景
- 使用接口
- 设计灵活的 API:当需要设计一个 API 或者框架,允许实现多种不同的行为时,可以使用接口。例如,Java 的标准库中有很多接口(如
List、Runnable)。 - 支持多重继承:当需要类实现多种行为时,可以使用多个接口。例如,一个类可以实现
Serializable和Cloneable接口。 - 定义共同行为:当多个类需要共享一组方法签名但不需要共享实现时,可以使用接口。
- 设计灵活的 API:当需要设计一个 API 或者框架,允许实现多种不同的行为时,可以使用接口。例如,Java 的标准库中有很多接口(如
- 使用抽象类
- 提供默认实现:当多个子类需要共享一些公共的实现细节时,可以使用抽象类。例如,
AbstractList提供了List的部分实现。 - 类的层次结构:当你有一个明显的继承层次结构,并且希望在基类中提供一些通用的实现时,使用抽象类是合适的。
- 需要构造方法:当需要在类层次结构中共享构造方法时,可以使用抽象类。
- 提供默认实现:当多个子类需要共享一些公共的实现细节时,可以使用抽象类。例如,
- 使用接口
掌握构造方法的作用和特点,以及默认构造方法的使用
-
构造方法的作用:
- 初始化对象,保证对象的完整性。
-
构造方法的特点:
- 与类同名,没有返回类型,可以重载,隐式调用父类构造方法。j
-
默认构造方法:
-
如果没有定义任何构造方法,编译器会生成一个默认的无参构造方法。
-
如果定义了带参构造方法而没有定义无参构造方法,编译器不会生成无参构造方法,需要显式定义无参构造方法。
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; } // Getter 方法 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()); // 输出: Person1: Unknown, 0 // 使用带参构造方法 Person person2 = new Person("Alice", 30); System.out.println("Person2: " + person2.getName() + ", " + person2.getAge()); // 输出: Person2: Alice, 30 } }public class Animal { private String species; // 显式定义无参构造方法 public Animal() { this.species = "Unknown"; } // 带参构造方法 public Animal(String species) { this.species = species; } public String getSpecies() { return species; } public static void main(String[] args) { // 使用无参构造方法 Animal animal1 = new Animal(); System.out.println("Animal1: " + animal1.getSpecies()); // 输出: Animal1: Unknown // 使用带参构造方法 Animal animal2 = new Animal("Dog"); System.out.println("Animal2: " + animal2.getSpecies()); // 输出: Animal2: Dog } }
-
掌握对象之间的关系,包括关联、聚合和组合
-
关联:
- 普通的对象之间的关系。
- 可以是单向或双向的。
- 例如,学生与教师的关系。
public class Teacher { private String name; public Teacher(String name) { this.name = name; } public String getName() { return name; } } public class Student { private String name; private Teacher teacher; // 关联关系 public Student(String name, Teacher teacher) { this.name = name; this.teacher = teacher; } public String getName() { return name; } public Teacher getTeacher() { return teacher; } public static void main(String[] args) { Teacher teacher = new Teacher("Mr. Smith"); Student student = new Student("Alice", teacher); System.out.println("Student: " + student.getName() + ", Teacher: " + student.getTeacher().getName()); } }聚合:
- 表示整体与部分的关系,但部分可以独立存在。
- 被包含对象可以在其他地方重用。
- 例如,大学与院系的关系。
public class Department { private String name; public Department(String name) { this.name = name; } public String getName() { return name; } } public class University { private String name; private List<Department> departments; // 聚合关系 public University(String name) { this.name = name; this.departments = new ArrayList<>(); } public void addDepartment(Department department) { departments.add(department); } public List<Department> getDepartments() { return departments; } public String getName() { return name; } public static void main(String[] args) { Department cs = new Department("Computer Science"); Department ee = new Department("Electrical Engineering"); University university = new University("ABC University"); university.addDepartment(cs); university.addDepartment(ee); System.out.println("University: " + university.getName()); for (Department dept : university.getDepartments()) { System.out.println("Department: " + dept.getName()); } } }组合:
- 表示整体与部分的关系,但部分不能独立存在。
- 被包含对象的生命周期依赖于整体对象。
- 例如,汽车与发动机的关系。
public class Engine { private String type; public Engine(String type) { this.type = type; } public String getType() { return type; } } public class Car { private String model; private Engine engine; // 组合关系 public Car(String model, String engineType) { this.model = model; this.engine = new Engine(engineType); } public String getModel() { return model; } public Engine getEngine() { return engine; } public static void main(String[] args) { Car car = new Car("Tesla Model S", "Electric"); System.out.println("Car: " + car.getModel()); System.out.println("Engine: " + car.getEngine().getType()); } }
掌握重截构造方法,如何在一个类中实现多个构造方法
-
重载构造方法:通过提供多个构造方法,可以灵活地初始化对象,以满足不同的需求。每个构造方法的参数列表必须不同。
public class Person { private String name; private int age; private String address; // 无参构造方法 public Person() { this.name = "Unknown"; this.age = 0; this.address = "Unknown"; } // 带一个参数的构造方法 public Person(String name) { this.name = name; this.age = 0; this.address = "Unknown"; } // 带两个参数的构造方法 public Person(String name, int age) { this.name = name; this.age = age; this.address = "Unknown"; } // 带三个参数的构造方法 public Person(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } public String getName() { return name; } public int getAge() { return age; } public String getAddress() { return address; } public static void main(String[] args) { // 使用不同的构造方法创建对象 Person person1 = new Person(); Person person2 = new Person("Alice"); Person person3 = new Person("Bob", 25); Person person4 = new Person("Charlie", 30, "123 Main St"); // 输出对象信息 System.out.println("Person1: " + person1.getName() + ", " + person1.getAge() + ", " + person1.getAddress()); System.out.println("Person2: " + person2.getName() + ", " + person2.getAge() + ", " + person2.getAddress()); System.out.println("Person3: " + person3.getName() + ", " + person3.getAge() + ", " + person3.getAddress()); System.out.println("Person4: " + person4.getName() + ", " + person4.getAge() + ", " + person4.getAddress()); } } -
使用
this()调用其他构造方法:可以减少代码重复,确保构造方法的一致性和可维护性。public class Person { private String name; private int age; private String address; // 无参构造方法 public Person() { this("Unknown", 0, "Unknown"); } // 带一个参数的构造方法 public Person(String name) { this(name, 0, "Unknown"); } // 带两个参数的构造方法 public Person(String name, int age) { this(name, age, "Unknown"); } // 带三个参数的构造方法 public Person(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } public String getName() { return name; } public int getAge() { return age; } public String getAddress() { return address; } public static void main(String[] args) { // 使用不同的构造方法创建对象 Person person1 = new Person(); Person person2 = new Person("Alice"); Person person3 = new Person("Bob", 25); Person person4 = new Person("Charlie", 30, "123 Main St"); // 输出对象信息 System.out.println("Person1: " + person1.getName() + ", " + person1.getAge() + ", " + person1.getAddress()); System.out.println("Person2: " + person2.getName() + ", " + person2.getAge() + ", " + person2.getAddress()); System.out.println("Person3: " + person3.getName() + ", " + person3.getAge() + ", " + person3.getAddress()); System.out.println("Person4: " + person4.getName() + ", " + person4.getAge() + ", " + person4.getAddress()); } }
掌握类加载器和类加载机制在 Java 中的作用
-
类加载器:
- 负责将类文件加载到内存中并转换为
Class对象。 - 常见的类加载器包括启动类加载器、扩展类加载器和应用程序类加载器。
类加载机制:
- 包括加载、验证、准备、解析和初始化五个阶段。
- 双亲委派模型确保类的加载过程安全且有序,防止重复加载和类的混乱。
类加载器工作原理:
public class CustomClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 自定义类加载逻辑 byte[] classData = loadClassData(name); if (classData == null) { throw new ClassNotFoundException(); } return defineClass(name, classData, 0, classData.length); } private byte[] loadClassData(String name) { // 从文件或网络加载类数据 return null; // 示例中返回 null,实际实现需加载实际数据 } public static void main(String[] args) throws Exception { CustomClassLoader classLoader = new CustomClassLoader(); Class<?> clazz = classLoader.loadClass("TestClass"); Object instance = clazz.newInstance(); System.out.println(instance.getClass().getName()); } } - 负责将类文件加载到内存中并转换为
掌握泛型的概念和作用,在类和方法中使用泛型
-
提供类型安全、代码重用和增强可读性。
-
在类中使用泛型
public class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } public static void main(String[] args) { Box<String> stringBox = new Box<>(); stringBox.setContent("Hello, Generics!"); System.out.println("String content: " + stringBox.getContent()); Box<Integer> integerBox = new Box<>(); integerBox.setContent(123); System.out.println("Integer content: " + integerBox.getContent()); } } -
方法中使用泛型
public class GenericMethods { // 泛型方法 public static <T> void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); } public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5}; String[] stringArray = {"A", "B", "C", "D", "E"}; System.out.print("Integer Array: "); printArray(intArray); System.out.print("String Array: "); printArray(stringArray); } }
掌握使用枚举类型来定义常量和限制变量的取值范围
-
定义常量
public enum Season { SPRING, SUMMER, FALL, WINTER } -
限制变量的取值范围
假设我们有一个简单的交通信号灯系统,我们可以使用枚举类型来定义信号灯的状态,并限制变量只能取这些状态值。
public enum TrafficLight { RED, GREEN, YELLOW }public class TrafficLightController { private TrafficLight currentLight; public TrafficLightController(TrafficLight initialLight) { this.currentLight = initialLight; } public void changeLight() { switch (currentLight) { case RED: currentLight = TrafficLight.GREEN; break; case GREEN: currentLight = TrafficLight.YELLOW; break; case YELLOW: currentLight = TrafficLight.RED; break; } } public TrafficLight getCurrentLight() { return currentLight; } public static void main(String[] args) { TrafficLightController controller = new TrafficLightController(TrafficLight.RED); System.out.println("Initial light: " + controller.getCurrentLight()); controller.changeLight(); System.out.println("After change: " + controller.getCurrentLight()); controller.changeLight(); System.out.println("After change: " + controller.getCurrentLight()); controller.changeLight(); System.out.println("After change: " + controller.getCurrentLight()); } }
掌握序列化和反序列化的概念,以及如何在 Java 中实现对象的序列化
-
序列化:指将对象的状态转换为字节流,以便将该对象保存到文件、数据库或通过网络传输。反序列化:指从字节流中恢复对象的状态,即将字节流转换回对象。
-
Java 中的序列化和反序列化
实现对象的序列化和反序列化主要通过
java.io.Serializable接口和ObjectOutputStream、ObjectInputStream类来完成。-
实现Serializable接口
import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = 1L; // 推荐定义serialVersionUID private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + '}'; } } -
序列化对象:使用
ObjectOutputStream将对象写入文件:import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.io.IOException; public class SerializeDemo { public static void main(String[] args) { Person person = new Person("Alice", 30); try (FileOutputStream fileOut = new FileOutputStream("person.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut)) { out.writeObject(person); System.out.println("Serialized data is saved in person.ser"); } catch (IOException i) { i.printStackTrace(); } } } -
反序列化对象:使用
ObjectInputStream从文件读取对象:import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.IOException; public class DeserializeDemo { public static void main(String[] args) { Person person = null; try (FileInputStream fileIn = new FileInputStream("person.ser"); ObjectInputStream in = new ObjectInputStream(fileIn)) { person = (Person) in.readObject(); System.out.println("Deserialized Person: " + person); } catch (IOException i) { i.printStackTrace(); } catch (ClassNotFoundException c) { System.out.println("Person class not found"); c.printStackTrace(); } } }
-
掌握反射,如何在 Java 中使用反射机制获取类的信息和调用类的方法
-
获取类的信息
- 获取
Class对象 - 获取类的名称
- 获取类的构造函数、方法和字段
- 获取
-
调用类的方法
- 获取方法对象
- 调用方法
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectionExample { public static void main(String[] args) { try { // 获取 Class 对象 Class<?> cls = Class.forName("com.example.MyClass"); // 获取类名称 System.out.println("Class name: " + cls.getName()); // 获取构造函数 Constructor<?>[] constructors = cls.getConstructors(); for (Constructor<?> constructor : constructors) { System.out.println("Constructor: " + constructor); } // 获取方法 Method[] methods = cls.getDeclaredMethods(); for (Method method : methods) { System.out.println("Method: " + method); } // 获取字段 Field[] fields = cls.getDeclaredFields(); for (Field field : fields) { System.out.println("Field: " + field); } // 创建对象实例 Constructor<?> constructor = cls.getConstructor(String.class); Object obj = constructor.newInstance("Example"); // 调用方法 Method method = cls.getMethod("printMessage"); method.invoke(obj); } catch (Exception e) { e.printStackTrace(); } } }
4. Java 异常处理
掌握日常工作或学习过程中常见的异常,理解异常原因
- NullPointerException (NPE)
- 原因:尝试对null对象调用方法或访问其成员。
- 解决:确保对象在使用前已被正确初始化。
- ArrayIndexOutOfBoundsException
- 原因:访问数组时,使用的索引超出了数组的范围。
- 解决:检查数组的长度,确保索引在合法范围内。
- ClassCastException
- 原因:尝试将对象强制转换为不兼容的类类型。
- 解决:在进行类型转换前,使用
instanceof检查对象是否为目标类型的实例。
- NumberFormatException
- 原因:试图将一个字符串解析为数字,但字符串的格式不正确。
- 解决:在解析字符串之前,确保它是一个有效的数字格式。
- ArithmeticException
- 原因:数学运算中出现非法操作,例如除以零。
- 解决:在执行运算之前,检查分母是否为零。
- FileNotFoundException
- 原因:试图访问的文件不存在。
- 解决:确保文件路径正确,文件存在且可访问。
- IOException
- 原因:在进行I/O操作时发生错误,如读取或写入文件失败。
- 解决:捕获并处理IOException,提供备用方案或错误提示。
- IllegalArgumentException
- 原因:方法接收到非法参数。
- 解决:检查传递给方法的参数,确保它们是合法的。
- IllegalStateException
- 原因:方法在不适当的状态下被调用。
- 解决:检查对象的状态是否适合调用该方法。
- InterruptedException
- 原因:线程在活动期间被中断。
- 解决:捕获并处理该异常,采取适当的响应措施,如重新中断线程或执行清理操作。
- OutOfMemoryError
- 原因:JVM内存不足。
- 解决:检查代码,避免内存泄漏,优化内存使用,或增加JVM的内存分配。
- StackOverflowError
- 原因:方法调用栈溢出,通常是由于递归调用过多。
- 解决:检查递归调用,确保递归终止条件正确。
掌握运行时异常 RuntimeException 和非运行时异常的概念和区别
-
运行时异常(RuntimeException)
运行时异常是指在程序运行过程中可能会发生的异常,这些异常通常是由于编程错误导致的。例如,数组越界访问、空指针引用、算术运算异常等。
- 特点:
- 不需要强制捕获或声明:方法中可以不声明抛出运行时异常,调用者也不需要强制捕获这些异常。例如,下面的代码不会强制要求捕获
ArrayIndexOutOfBoundsException - 继承自RuntimeException:所有运行时异常都继承自
RuntimeException类,RuntimeException又继承自Exception。 - 编程错误导致:这些异常通常是由于编程中的逻辑错误或不正确的使用导致的。程序员应该通过修改代码来避免这些错误。
- 不需要强制捕获或声明:方法中可以不声明抛出运行时异常,调用者也不需要强制捕获这些异常。例如,下面的代码不会强制要求捕获
- 特点:
-
非运行时异常(也称为检查异常,Checked Exception)
非运行时异常是在程序正常运行过程中可以预见并需要捕获的异常。它们通常表示程序外部的异常情况,例如文件未找到、网络连接失败等。
- 特点:
- 需要强制捕获或声明:方法必须声明所有可能抛出的非运行时异常,调用者必须处理这些异常。否则,编译器会报错。例如:
- 继承自Exception但不继承自RuntimeException:所有非运行时异常都继承自
Exception类,但不继承自RuntimeException。 - 表示合理的异常情况:这些异常通常是表示一种可以预见并需要合理处理的异常情况。程序员应该在代码中提供适当的处理措施。
- 特点:
理解 Checked Exceptions 和 Unchecked Exceptions 的概念和区别
- Checked Exceptions(已检查异常)
- 概念:编译时强制要求处理,必须捕获或声明。
- 继承自
Exception,但不继承自RuntimeException。 - 用于表示可以预见并需要合理处理的异常情况,通常与外部资源或环境相关。
- Unchecked Exceptions(未检查异常)
- 概念:编译时不强制要求处理,可以选择不捕获或声明。
- 继承自
RuntimeException。 - 用于表示编程错误或逻辑错误,通常在程序运行过程中可能出现的异常。
掌握异常栈信息的分析,判断根异常(… Caused by …)
Exception in thread "main" java.lang.RuntimeException: Outer exception
at com.example.MyClass.method1(MyClass.java:10)
at com.example.MyClass.main(MyClass.java:5)
Caused by: java.lang.IllegalArgumentException: Inner exception
at com.example.MyClass.method2(MyClass.java:20)
at com.example.MyClass.method1(MyClass.java:8)
-
找到顶层异常
顶层异常是异常栈信息的第一部分,它是程序最终捕获的异常。在上面的例子中,顶层异常是
java.lang.RuntimeException。 -
找到"Caused by"部分
"Caused by"部分表明当前异常是由另一个异常引起的。每个"Caused by"部分提供了导致其上一级异常的根本原因。在上面的例子中,
java.lang.RuntimeException是由java.lang.IllegalArgumentException引起的。 -
识别根异常
根异常(Root Cause)是最底层的"Caused by"部分,是所有异常链条的起点。在上面的例子中,根异常是
java.lang.IllegalArgumentException。
掌握 try … catch … finally… return 各语句块,理解其执行顺序掌握多个 catch 语句的匹配顺序(继承关系顺序)
- try块:尝试执行代码,如果抛出异常立即跳到对应的catch块。
- catch块:捕获并处理异常,按声明顺序匹配异常类型。
- finally块:总是执行,用于清理操作。
- return语句:在执行return之前会先执行finally块的代码,如果finally中有return语句,会覆盖前面的return。
掌握 finally 语句作用和用途
-
作用:
- 保证执行:无论
try块中是否发生异常,以及是否有catch块捕获异常,finally块中的代码总是会被执行。这保证了finally块中的资源清理操作总能得到执行。 - 清理操作:
finally块通常用于执行必须要执行的清理操作,例如关闭文件流、释放数据库连接、释放锁等资源。 - 一致的程序状态:通过在
finally块中执行清理操作,可以确保程序在异常后仍然处于一致的状态,避免资源泄漏和其他潜在问题。
- 保证执行:无论
-
用途:
-
关闭文件流或其他IO资源:在处理文件操作时,确保文件流被关闭是非常重要的。使用
finally块可以保证文件流即使在发生异常时也能被正确关闭。import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class Example { public static void main(String[] args) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader("example.txt")); String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } } -
释放数据库连接:在数据库操作中,确保数据库连接被关闭,以防止连接泄漏。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class DatabaseExample { public static void main(String[] args) { Connection conn = null; Statement stmt = null; try { conn = DriverManager.getConnection("jdbc:myDriver:myDatabase", "username", "password"); stmt = conn.createStatement(); stmt.executeUpdate("INSERT INTO myTable VALUES (1, 'example')"); } catch (SQLException e) { e.printStackTrace(); } finally { try { if (stmt != null) stmt.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } -
释放锁:在多线程编程中,确保锁在使用后被正确释放,以避免死锁。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private final Lock lock = new ReentrantLock(); public void doSomething() { lock.lock(); try { // Critical section code System.out.println("Locked section"); } finally { lock.unlock(); } } public static void main(String[] args) { LockExample example = new LockExample(); example.doSomething(); } } -
重置状态或清理临时数据:如果在
try块中分配了某些资源或设置了某些状态,可以在finally块中重置状态或清理临时数据。public class StateExample { private int state; public void changeState() { try { state = 1; // Some code that might throw an exception } finally { state = 0; // Reset state } } public static void main(String[] args) { StateExample example = new StateExample(); example.changeState(); } }
-
掌握 try-with-resource 语法,以及各语句块执行顺序
-
在
try-with-resources语句块结束时,无论是否发生异常,try块中声明的资源都会被自动关闭。try (ResourceType resource = new ResourceType()) { // 使用资源的代码 } catch (ExceptionType e) { // 处理异常 } finally { // 执行最后的清理操作 } -
顺序:
- 执行
try块中的代码。 - 捕获并处理异常(如果有
catch块)。 - 自动关闭资源,按声明的相反顺序调用
close()方法。 - 执行
finally块(如果存在)。
- 执行
理解 Throwable、Error、Exception 的概念和区别(异常继承关系)
-
异常根类是
Throwable,其子类分为:Error和Exception。-
Throwable:Java中所有错误和异常的父类。- 任何可以作为异常抛出的对象都必须是
Throwable类或其子类的实例。 Throwable类提供了打印异常栈信息的方法,如printStackTrace()。
- 任何可以作为异常抛出的对象都必须是
-
Error:Throwable的子类,用于表示严重的错误,这些错误通常不需要或不应该被应用程序捕获和处理。Error表示系统级错误,如JVM运行时错误,内存不足等。- 这些错误通常是不可恢复的,应用程序不应试图处理这些错误。
- 常见的子类包括
OutOfMemoryError、StackOverflowError、VirtualMachineError等。
-
Exception:Throwable的子类,用于表示程序中的异常情况,这些情况可以被应用程序捕获和处理。-
Exception表示程序中可以预见和处理的异常。Exception又分为两类:Checked Exceptions(已检查异常)和 Unchecked Exceptions(未检查异常)。
-
java.lang.Object └── java.lang.Throwable ├── java.lang.Error │ ├── java.lang.VirtualMachineError │ │ ├── java.lang.OutOfMemoryError │ │ ├── java.lang.StackOverflowError │ │ └── ... │ └── ... └── java.lang.Exception ├── java.lang.RuntimeException │ ├── java.lang.NullPointerException │ ├── java.lang.ArrayIndexOutOfBoundsException │ └── ... ├── java.io.IOException ├── java.lang.ClassNotFoundException └── ... -
理解 0utOfMemoryError,能够说明该异常发生的可能原因和相应的处理措施
-
OutOfMemoryError是一种常见的 Java 错误,表示 Java 虚拟机(JVM)在试图为对象分配内存时无法获得足够的内存资源。这种错误通常表示系统内存不足。 -
原因和处理:
-
内存泄漏:程序中存在内存泄漏,即对象被持有引用而无法被垃圾回收器回收。典型的例子包括集合类(如
ArrayList、HashMap)中不断添加元素但从未清除。处理:使用内存分析工具(如 VisualVM、Eclipse MAT)分析堆内存,找出占用内存最多的对象及其引用链,确定是否存在内存泄漏。
-
大对象分配:尝试分配非常大的对象,例如大数组或大字符串。JVM 堆空间不足以容纳该大对象。
处理:优化代码
-
定期清理不再使用的对象,特别是大集合类中的对象。
-
尽量避免大对象的分配,可以通过分块处理大数据来减少单个对象的大小。
-
-
过多的线程:创建了过多的线程,每个线程都会占用一定的栈空间,可能导致内存耗尽。
处理:减少线程数
-
优化线程池的使用,避免创建过多的线程
-
使用适当的并发编程模型,如 ForkJoinPool 或 ExecutorService。
-
-
PermGen或Metaspace内存不足:元数据过多导致内存不足。
处理:调整 JVM 参数
-
增大堆内存:通过调整 JVM 启动参数增加堆内存大小,例如
-Xmx参数。java -Xmx2g MyApplication -
增大 PermGen 或 Metaspace 区域内存:
# Java 7 及之前 java -XX:MaxPermSize=256m MyApplication # Java 8 及之后 java -XX:MaxMetaspaceSize=256m MyApplication
-
-
Native内存不足:非 Java 对象(如直接字节缓冲区或其他使用本地内存的资源)占用了过多的内存,导致 JVM 无法获得足够的本地内存。
处理:使用本地内存监控工具本地内存监控工具(如 Native Memory Tracking,NMT)来分析内存分配情况。
-
理解 Stack0verflowError,能够说明该异常发生的可能原因和相应的处理措施
-
StackOverflowError是 Java 中一种常见的错误,表示 Java 虚拟机(JVM)的调用栈空间耗尽。 -
原因:
- 无限递归:函数调用自身且没有基准条件来终止递归,导致无限递归。
- 过深的递归调用:虽然有终止条件,但递归调用的深度太大,导致调用栈耗尽。例如,计算斐波那契数列时,如果使用简单递归方法且输入较大,会导致栈溢出。
-
处理:
-
检查和修改递归逻辑
-
使用迭代替代递归
-
增加栈大小
java -Xss2m DeepRecursion -
使用尾递归优化
-
理解 NoSuchMethodError、NoSuchMethodException 区别,能够说明该异常发生的可能原因和相应的处理措施
-
NoSuchMethodError:
- 类型:Error
- 发生时机:运行时
- 原因:类版本不一致、方法在当前类版本中不存在、类加载器问题
- 处理措施:确保类版本一致,检查类路径,更新和重新编译
-
NoSuchMethodException:
- 类型:Exception
- 发生时机:反射操作时
- 原因:方法名或参数类型不匹配、方法不存在
- 处理措施:验证方法签名,确保正确的类版本,调试和日志
理解 NoSuchFieldError、NoSuchFieldException 区别,能够说明该异常发生的可能原因和相应的处理措施
-
NoSuchFieldError:
- 类型:Error
- 发生时机:运行时
- 原因:类版本不一致、字段在当前类版本中不存在、类加载器问题
- 处理措施:确保类版本一致,检查类路径,更新和重新编译
-
NoSuchFieldException:
- 类型:Exception
- 发生时机:反射操作时
- 原因:字段名或类型不匹配、字段不存在
- 处理措施:验证字段名,确保正确的类版本,调试和日志
理解NoClassDefFoundError,能够说明该异常发生的可能原因和相应的处理措施
- NoClassDefFoundError:
- 类型:Error
- 发生时机:运行时
- 原因:类路径配置错误、类文件缺失、类依赖问题、类加载器问题、动态生成的类缺失
- 处理措施:检查类路径配置,验证部署文件,确认类依赖,调试类加载器问题
理解 ClassNotFoundException,能够分析异常发生的可能原因和相应的处理措施
- ClassNotFoundException:
- 类型:Exception
- 发生时机:运行时
- 原因:类路径配置错误、类文件缺失、错误的类名、类的依赖问题、类加载器问题、版本不匹配
- 处理措施:检查类路径配置,验证部署文件,确保类名正确,检查类的依赖,调试类加载器问题,确保版本一致
掌握 ArrayIndex0ut0fBoundsException 异常原因和解决方法
- ArrayIndexOutOfBoundsException:
- 类型:RuntimeException
- 发生时机:运行时
- 原因:索引超出数组边界、负索引、错误的循环边界、数组长度计算错误、动态数组问题
- 处理措施:验证索引范围、使用调试和日志、确保正确的循环边界、动态数组处理、使用更安全的数据结构
掌握 ConcurrentModificationException 异常原因和解决方法
- ConcurrentModificationException:
- 类型:RuntimeException
- 发生时机:运行时
- 原因:迭代过程中修改集合、多线程环境中的非线程安全集合、使用不支持修改的迭代器
- 解决方法:使用线程安全的集合类、在迭代过程中避免修改集合、使用
Iterator的remove()方法、使用synchronized关键字同步访问
掌握 NullPointerException 异常发生的原因、如何排査、避免措施
- NullPointerException:
- 类型:RuntimeException
- 发生时机:运行时
- 原因:访问
null对象的字段、调用null对象的方法、数组中的null元素、集合操作中的null、方法参数为null、方法返回值为null - 排查方法:检查异常栈信息、使用调试工具、添加日志信息、进行空值检查
- 避免措施:初始化对象、进行空值检查、使用
Optional类、确保方法返回值不为null、使用Objects.requireNonNull
掌握 NumberFormatException 异常原因和解决方法
- NumberFormatException:
- 类型:RuntimeException
- 发生时机:运行时
- 原因:字符串包含非数字字符、字符串为空或
null、数字格式不正确、超出数值范围 - 解决方法:确保字符串是有效数字、捕获异常并处理、使用适当的解析方法、处理非标准数字格式、处理空字符串和
null
掌握 FileNotFoundException 异常原因和解决方法
- FileNotFoundException:
- 类型:IOException
- 发生时机:运行时
- 原因:文件路径错误、文件不存在、文件权限问题、路径问题、目录操作
- 解决方法:检查文件路径、验证文件存在性、检查文件权限、使用绝对路径、捕获并处理异常、调试路径问题
能够对 SocketException、ConnectException 网络编程中常见异常进行分析
-
SocketException:
- 类型:SocketException
- 发生时机:网络 I/O 操作期间
- 原因:网络连接重置、网络接口关闭、网络超时、端口不可用、DNS 解析失败
- 解决方法:检查网络连接、增加超时设置、捕获并处理异常、检查端口使用情况、检查 DNS 配置
-
ConnectException:
- 类型:ConnectException (SocketException 的子类)
- 发生时机:建立连接期间
- 原因:目标主机不可达、连接被拒绝、网络阻塞、端口号错误
- 解决方法:检查目标主机和端口、检查服务器状态、检查防火墙和网络策略、验证端口配置、捕获并处理异常
能够对 ConnectTimeoutException、ReadTimeoutException 网络编程中常见异常进行分析
-
ConnectTimeoutException:
- 类型:SocketTimeoutException(SocketException 的子类)
- 发生时机:尝试建立连接期间
- 原因:网络延迟或拥塞、目标主机不可达、防火墙或网络策略、端口不可用
- 解决方法:增加超时时间、检查目标主机和端口、检查防火墙和网络策略、捕获并处理异常
-
ReadTimeoutException:
- 类型:SocketTimeoutException(SocketException 的子类)
- 发生时机:从网络读取数据期间
- 原因:网络延迟或拥塞、服务器响应缓慢、网络问题、不正确的超时设置
- 解决方法:增加读取超时时间、优化服务器响应、捕获并处理异常、检查网络问题
掌握断言关键字 assert 用法、优点、注意事项
-
assert是 Java 编程语言中的一个关键字,用于在代码中插入断言语句。断言是一种调试工具,可以帮助开发者验证程序在运行时的假设,确保代码的正确性和稳定性。 -
用法:在 Java 中,
assert关键字用于进行条件检查。如果断言失败,则会抛出一个AssertionError。assert condition; assert condition : expression; -
优点:
-
调试工具:断言用于在开发和测试阶段检查程序的假设和不变量,帮助发现和修复程序中的错误。
-
性能开销低:断言在默认情况下是禁用的,因此在生产环境中不会有性能开销。只有在启用断言时,它们才会生效。
-
代码自文档化:使用断言可以使代码中的假设和不变量显式化,使代码更具可读性和可维护性。
-
简化错误检测:通过断言可以在代码中快速检测到错误,而不必编写额外的错误检查代码。
-
-
注意事项
-
断言不能替代正常的错误处理:断言应该仅用于验证程序的假设和不变量,而不是用来处理用户输入错误或运行时异常。错误处理逻辑应使用正常的异常机制。
-
在生产环境中禁用:断言在生产环境中通常是禁用的。可以通过 JVM 参数
-ea或
-enableassertions来启用断言。例如:
bash java -ea -jar MyApp.jar -
不依赖于断言进行程序逻辑控制:不要将断言用作程序逻辑的一部分。断言应仅用于调试和验证假设。
-
注意表达式的副作用:断言中的
expression不应该有副作用,因为在禁用断言时,expression不会被计算。副作用可能导致不可预测的行为。 -
断言的使用范围:断言适用于开发和测试阶段,用于发现和修复代码中的潜在问题。在生产环境中,应该依赖于其他方式来保证程序的正确性。
-
掌握自定义异常,编写静态工具类,实现对象非空校验(0bject、数组、集合)
-
自定义异常类,用于在校验失败时抛出。
// 定义自定义异常类 public class ValidationException extends RuntimeException { // 构造函数接受错误信息 public ValidationException(String message) { super(message); } } -
静态工具类,该类包含方法来检查对象、数组和集合是否为非空,并在校验失败时抛出自定义异常。
import java.util.Collection; import java.util.Map; public class ValidationUtils { // 检查对象是否为非空 public static <T> void assertNotNull(T object, String message) { if (object == null) { throw new ValidationException(message); } } // 检查数组是否为非空 public static <T> void assertNotEmpty(T[] array, String message) { if (array == null || array.length == 0) { throw new ValidationException(message); } } // 检查集合是否为非空 public static <T> void assertNotEmpty(Collection<T> collection, String message) { if (collection == null || collection.isEmpty()) { throw new ValidationException(message); } } // 检查映射是否为非空 public static <K, V> void assertNotEmpty(Map<K, V> map, String message) { if (map == null || map.isEmpty()) { throw new ValidationException(message); } } } -
使用
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class ValidationUtilsExample { public static void main(String[] args) { try { // 示例对象非空校验 String str = null; ValidationUtils.assertNotNull(str, "String must not be null"); } catch (ValidationException e) { System.err.println("Validation error: " + e.getMessage()); } try { // 示例数组非空校验 String[] array = new String[0]; ValidationUtils.assertNotEmpty(array, "Array must not be empty"); } catch (ValidationException e) { System.err.println("Validation error: " + e.getMessage()); } try { // 示例集合非空校验 List<String> list = new ArrayList<>(); ValidationUtils.assertNotEmpty(list, "List must not be empty"); } catch (ValidationException e) { System.err.println("Validation error: " + e.getMessage()); } try { // 示例映射非空校验 Map<String, String> map = new HashMap<>(); ValidationUtils.assertNotEmpty(map, "Map must not be empty"); } catch (ValidationException e) { System.err.println("Validation error: " + e.getMessage()); } } }
5. Java 数组与集合类型
掌握数组的特点
数组的主要特点
- 固定大小:创建一个数组时,必须指定其大小并且在整个生命周期内不能改变。
- 同类型元素:数组中的所有元素必须是相同的数据类型。
- 索引访问:数组使用索引来访问元素。索引从
0开始,到n-1(其中n是数组的大小)。 - 连续内存分配数组在内存中是连续存储的。这使得数组访问非常快,因为可以直接通过计算偏移量来访问元素。
- 固定类型和长度:数组的类型和长度在创建时定义,不可改变。
- 初始化:组在创建时可以被初始化。初始化时,所有元素会被设置为默认值(如
0、null或false),或者可以在声明时指定初始值。 - 遍历:可以通过循环结构来遍历数组中的所有元素,通常使用
for或for-each循环。 - 内存效率:由于数组元素是连续存储的,数组在内存中的开销较小,因此访问速度较快。
- 不可扩展:一旦创建,数组的大小不能动态调整。如果需要调整大小,必须创建一个新的数组并将元素复制过去。
- 支持多维数组:数组可以是多维的,例如二维数组、三维数组等。多维数组的每个维度都是数组的数组。
掌握数组的遍历和排序算法
-
遍历:
for循环- 增强型
for循环(for-each循环)
-
排序:
-
冒泡排序
简单的排序算法,重复交换相邻的元素来将最大或最小的元素 “冒泡” 到数组的一端。
public class BubbleSort { public static void main(String[] args) { int[] arr = {5, 2, 9, 1, 5, 6}; bubbleSort(arr); for (int num : arr) { System.out.print(num + " "); } } public static void bubbleSort(int[] arr) { int n = arr.length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { // 交换 arr[j] 和 arr[j + 1] int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } } -
选择排序
不断选择未排序部分的最小(或最大)元素并将其放到已排序部分的末尾。
public class BubbleSort { public static void main(String[] args) { int[] arr = {5, 2, 9, 1, 5, 6}; bubbleSort(arr); for (int num : arr) { System.out.print(num + " "); } } public static void bubbleSort(int[] arr) { int n = arr.length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { // 交换 arr[j] 和 arr[j + 1] int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } } -
插入排序
插入排序将每个元素插入到已排序的部分中,适用于小规模数据集。
public class InsertionSort { public static void main(String[] args) { int[] arr = {12, 11, 13, 5, 6}; insertionSort(arr); for (int num : arr) { System.out.print(num + " "); } } public static void insertionSort(int[] arr) { int n = arr.length; for (int i = 1; i < n; i++) { int key = arr[i]; int j = i - 1; while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j = j - 1; } arr[j + 1] = key; } } } -
快速排序
选择一个“基准”元素,将数组分成比基准小和比基准大的两部分,然后递归排序。
public class QuickSort { public static void main(String[] args) { int[] arr = {10, 7, 8, 9, 1, 5}; quickSort(arr, 0, arr.length - 1); for (int num : arr) { System.out.print(num + " "); } } public static void quickSort(int[] arr, int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } } private static int partition(int[] arr, int low, int high) { int pivot = arr[high]; int i = (low - 1); for (int j = low; j < high; j++) { if (arr[j] <= pivot) { i++; // 交换 arr[i] 和 arr[j] int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // 交换 arr[i + 1] 和 arr[high] int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp; return i + 1; } } -
归并排序
通过将数组分成两半分别排序后合并的方式实现排序。
public class MergeSort { public static void main(String[] args) { int[] arr = {12, 11, 13, 5, 6, 7}; mergeSort(arr, 0, arr.length - 1); for (int num : arr) { System.out.print(num + " "); } } public static void mergeSort(int[] arr, int l, int r) { if (l < r) { int m = (l + r) / 2; mergeSort(arr, l, m); mergeSort(arr, m + 1, r); merge(arr, l, m, r); } } private static void merge(int[] arr, int l, int m, int r) { int n1 = m - l + 1; int n2 = r - m; int[] L = new int[n1]; int[] R = new int[n2]; System.arraycopy(arr, l, L, 0, n1); System.arraycopy(arr, m + 1, R, 0, n2); int i = 0, j = 0; int k = l; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k++] = L[i++]; } else { arr[k++] = R[j++]; } } while (i < n1) { arr[k++] = L[i++]; } while (j < n2) { arr[k++] = R[j++]; } } }
-
掌握数组的动态扩容、插入、删除
-
动态扩容
由于数组大小固定,动态扩容通常需要创建一个新的更大的数组,并将旧数组的元素复制到新数组中。或使用其他数据结构,如
ArrayList。import java.util.Arrays; public class DynamicArrayExample { private int[] array; private int size; public DynamicArrayExample(int initialCapacity) { array = new int[initialCapacity]; size = 0; } // 添加元素到数组 public void add(int element) { if (size == array.length) { // 扩容 array = Arrays.copyOf(array, array.length * 2); } array[size++] = element; } // 打印数组内容 public void printArray() { for (int i = 0; i < size; i++) { System.out.print(array[i] + " "); } System.out.println(); } public static void main(String[] args) { DynamicArrayExample dynamicArray = new DynamicArrayExample(2); dynamicArray.add(1); dynamicArray.add(2); dynamicArray.add(3); // 触发扩容 dynamicArray.add(4); dynamicArray.printArray(); } } -
插入操作
数组中插入元素,需要将指定位置及其后的元素向后移动,然后将新元素放入指定位置。这可能需要调整数组的大小以便容纳新元素。
import java.util.Arrays; public class DynamicArrayInsertExample { private int[] array; private int size; public DynamicArrayInsertExample(int initialCapacity) { array = new int[initialCapacity]; size = 0; } // 插入元素到指定位置 public void insert(int index, int element) { if (index < 0 || index > size) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } if (size == array.length) { // 扩容 array = Arrays.copyOf(array, array.length * 2); } // 移动元素 System.arraycopy(array, index, array, index + 1, size - index); array[index] = element; size++; } // 打印数组内容 public void printArray() { for (int i = 0; i < size; i++) { System.out.print(array[i] + " "); } System.out.println(); } public static void main(String[] args) { DynamicArrayInsertExample dynamicArray = new DynamicArrayInsertExample(3); dynamicArray.add(1); dynamicArray.add(2); dynamicArray.add(3); dynamicArray.insert(1, 99); // 在索引1处插入99 dynamicArray.printArray(); } // 添加元素到数组 public void add(int element) { if (size == array.length) { // 扩容 array = Arrays.copyOf(array, array.length * 2); } array[size++] = element; } } -
删除操作
删除数组中的元素需要将指定位置后的所有元素向前移动一个位置,然后更新数组大小。如果删除的元素是最后一个元素,可以简单地减少数组的大小,而无需移动元素。
import java.util.Arrays; public class DynamicArrayDeleteExample { private int[] array; private int size; public DynamicArrayDeleteExample(int initialCapacity) { array = new int[initialCapacity]; size = 0; } // 删除指定位置的元素 public void delete(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } // 移动元素 System.arraycopy(array, index + 1, array, index, size - index - 1); size--; } // 打印数组内容 public void printArray() { for (int i = 0; i < size; i++) { System.out.print(array[i] + " "); } System.out.println(); } public static void main(String[] args) { DynamicArrayDeleteExample dynamicArray = new DynamicArrayDeleteExample(5); dynamicArray.add(1); dynamicArray.add(2); dynamicArray.add(3); dynamicArray.add(4); dynamicArray.add(5); dynamicArray.delete(2); // 删除索引2的元素 dynamicArray.printArray(); } // 添加元素到数组 public void add(int element) { if (size == array.length) { // 扩容 array = Arrays.copyOf(array, array.length * 2); } array[size++] = element; } }
结合代码片段,理解数组的引用
数组是对象类型,因此数组变量存储的是对数组对象的引用,而不是数组本身。
-
数组引用:数组变量存储的是对数组对象的引用。修改数组变量会影响原始数组,因为它们指向同一个内存地址。
public class ArrayReferenceExample { public static void main(String[] args) { int[] array1 = {1, 2, 3}; int[] array2 = array1; // array2 引用与 array1 相同的数组对象 // 修改 array2 中的元素也会影响 array1 array2[0] = 99; System.out.println("array1[0]: " + array1[0]); // 输出: 99 System.out.println("array2[0]: " + array2[0]); // 输出: 99 } } -
数组的复制
-
浅复制:只复制数组的引用,即新数组和原数组指向相同的内存位置。
import java.util.Arrays; public class ArrayShallowCopyExample { public static void main(String[] args) { int[] originalArray = {1, 2, 3}; int[] shallowCopyArray = originalArray; // 浅复制 shallowCopyArray[0] = 99; // 修改浅复制的数组 System.out.println("Original Array: " + Arrays.toString(originalArray)); // 输出: [99, 2, 3] System.out.println("Shallow Copy Array: " + Arrays.toString(shallowCopyArray)); // 输出: [99, 2, 3] } } -
深复制:创建一个新数组并将原数组的元素逐一复制到新数组中。这样,两个数组指向不同的内存位置。
import java.util.Arrays; public class ArrayShallowCopyExample { public static void main(String[] args) { int[] originalArray = {1, 2, 3}; int[] shallowCopyArray = originalArray; // 浅复制 shallowCopyArray[0] = 99; // 修改浅复制的数组 System.out.println("Original Array: " + Arrays.toString(originalArray)); // 输出: [99, 2, 3] System.out.println("Shallow Copy Array: " + Arrays.toString(shallowCopyArray)); // 输出: [99, 2, 3] } }
-
-
方法传递:传递数组作为方法参数时,传递的是数组的引用,因此方法内对数组的修改会影响原数组。
public class ArrayReferenceInMethod { public static void main(String[] args) { int[] numbers = {1, 2, 3}; modifyArray(numbers); // 传递数组的引用 System.out.println("Modified Array: " + Arrays.toString(numbers)); // 输出: [10, 20, 30] } public static void modifyArray(int[] array) { array[0] = 10; array[1] = 20; array[2] = 30; } } -
数组的比较:使用
Arrays.equals方法比较数组内容,使用==比较的是数组的引用。import java.util.Arrays; public class ArrayComparisonExample { public static void main(String[] args) { int[] array1 = {1, 2, 3}; int[] array2 = {1, 2, 3}; int[] array3 = array1; System.out.println("array1 == array2: " + (array1 == array2)); // 输出: false System.out.println("array1 == array3: " + (array1 == array3)); // 输出: true System.out.println("Arrays.equals(array1, array2): " + Arrays.equals(array1, array2)); // 输出: true } }
掌握数组和集合的区别
| 区别 | 数组 | 集合 |
|---|---|---|
| 大小 | 固定大小:数组在创建时必须指定其大小,且大小在创建后无法改变。 | 动态大小:集合(如 ArrayList、HashSet)的大小可以动态调整,根据需要自动扩展或缩减。 |
| 类型 | 同类型元素:数组中的所有元素必须是相同的数据类型。 | 泛型:集合可以使用泛型来指定元素类型,允许存储指定类型的对象,但也可以存储不同类型的对象(在没有泛型的情况下,如 ArrayList)。 |
| 操作 | 本操作:支持基本的索引操作,可以通过索引直接访问和修改元素。 | 复杂操作:支持更多的操作,如动态添加、删除、搜索元素,以及排序和去重等。具体操作取决于集合的实现(如 ArrayList、HashSet、TreeSet)。 |
| 性能 | 由于数组的元素在内存中是连续存储的,因此访问速度非常快。插入和删除操作效率低,因为可能需要移动元素。 | 集合的性能取决于具体实现。例如,ArrayList 使用动态数组,插入和删除操作可能涉及到数组的扩展和缩减,而 HashSet 使用哈希表来提供快速的元素查找和插入操作。支持更多操作,但可能涉及额外的开销(如哈希计算、排序等)。 |
| 初始化 | 可以在声明时初始化,也可以通过代码进行初始化。默认值会根据数组的类型进行填充。 | 通常通过构造函数创建集合对象,可以选择性地传递初始容量或其他参数。 |
| 线程安全 | 数组本身不是线程安全的。如果多个线程访问同一个数组,需要自己进行同步管理。 | 某些集合类(如 Vector、ConcurrentHashMap)是线程安全的。对于非线程安全的集合(如 ArrayList、HashSet),可以使用 Collections.synchronizedList 等方法进行包装。 |
| 应用场景 | 知道数据的数量不会变化且需要快速的随机访问时。 | 需要动态管理数据(如插入、删除、查找),或者需要使用高级功能(如排序、去重)时。 |
掌握 Map、Collection、List、Set、Queue、Stack 体系结构
- Collection
- List
- Arraylist:Object 数组
- LinkedList:双向循环链表
- Vector:Object 数组
- Set
- HashSet:基于 HashMap 实现
- LinkedHashSet:基于LinkedHashMap实现
- TreeSet:红黑树(自平衡的排序二叉树)
- HashSet:基于 HashMap 实现
- List
- Map
- HashMap:数组+链表
- LinkedHashMap:HashMap+双向链表
- ConcurrentHashMap:哈希表
- HashTable:数组+链表
- Properties
- TreeMap:红黑树(自平衡的排序二叉树)
- HashMap:数组+链表
掌握 Set 和 List 的区别
- Set:无序、不可重复、查找和插入快。哈希表实现。
- List:有序、可重复、动态大小、查询快。数组实现。
掌握 ArrayList、HashMap、HashSet、Vector 概述和区别
| 特性 | ArrayList | HashMap | HashSet | Vector |
|---|---|---|---|---|
| 实现 | 动态数组 | 哈希表 | 哈希表 | 动态数组 |
| 接口 | List | Map | Set | List |
| 线程安全 | 否 | 否 | 否 | 是 |
| 元素顺序 | 按插入顺序 | 无序 | 无序 | 按插入顺序 |
| 重复元素 | 允许 | 键唯一,值可以重复 | 不允许 | 允许 |
| 查找效率 | O(1)(随机访问) | O(1)(平均) | O(1)(平均) | O(1)(随机访问) |
掌握 ArrayList 的遍历和删除
-
遍历:
-
for 循环
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } -
for-each 循环
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); for (String item : list) { System.out.println(item); } -
迭代器(Iterator)
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } -
forEach方法配合 Lambda 表达式ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); list.forEach(item -> System.out.println(item));
-
-
删除元素:
-
使用
remove方法-
删除指定的元素: 通过元素值删除。
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); list.remove("C"); // 删除元素 "C" -
删除指定的索引位置的元素: 通过索引删除。
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); list.remove(2); // 删除索引位置 2 的元素 "C"
-
-
使用迭代器的
remove方法ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if ("C".equals(item)) { iterator.remove(); // 删除元素 "C" } } -
使用
removeIf方法ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); list.removeIf(item -> "C".equals(item)); // 删除所有值为 "C" 的元素
并发问题: 如果在多线程环境下对
ArrayList进行修改(例如删除),需要使用线程安全的集合类或进行同步处理。ConcurrentModificationException可能会在并发修改时抛出。性能问题: 删除元素时,
ArrayList需要移动元素,删除操作的时间复杂度是 O(n),在频繁删除的情况下,性能可能会受到影响。 -
掌握ArrayList 和ArrayIndex0utOfBoundsException
-
ArrayList
ArrayList是 Java Collections Framework 中的一个类,基于动态数组实现。-
动态大小:可以根据需要自动调整大小。
-
快速随机访问:通过索引访问元素的时间复杂度是 O(1)。
-
不线程安全:如果多个线程同时访问和修改
ArrayList,可能会导致数据不一致的问题在这种情况下,可以使用
Collections.synchronizedList或CopyOnWriteArrayList。
-
-
ArrayIndexOutOfBoundsException
ArrayIndexOutOfBoundsException是 Java 中的一个运行时异常,表示尝试访问数组或集合时使用了非法的索引。- 访问超出范围的索引:当尝试访问一个超出合法索引范围的元素时,会抛出这个异常。
- 负索引:尝试使用负数作为索引也会抛出这个异常。
-
防止
ArrayIndexOutOfBoundsException- 检查索引范围: 在访问
ArrayList或其他集合类之前,确保索引在合法范围内。ArrayList的合法索引范围是从0到size() - 1 - 使用
size()方法: 在访问或修改元素时,通过size()方法获取集合的当前大小,并确保索引在合法范围内。
- 检查索引范围: 在访问
掌握 ArrayList 的排序
- 使用
Collections.sort方法 - 使用
List.sort方法
掌握 ArrayList 和数组之间转换
- 从数组转换为
ArrayList,使用Arrays.asList。 - 从
ArrayList转换为数组,使用toArray方法。
结合 stream 流,掌握对 ArrayList 的数据做排序、聚合、分组、去重、取值0
-
排序:使用
Stream的sorted方法对ArrayList进行排序。import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; public class Main { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(Arrays.asList("Banana", "Apple", "Orange", "Grapes")); // 自然顺序排序 List<String> sortedList = list.stream() .sorted() .toList(); System.out.println("Sorted list (natural order): " + sortedList); // 自定义比较器排序(按长度排序) List<String> sortedByLength = list.stream() .sorted(Comparator.comparingInt(String::length)) .toList(); System.out.println("Sorted list (by length): " + sortedByLength); } } -
聚合:使用
Stream的collect方法进行聚合操作。例如,计算元素总数、求和、平均值等。import java.util.ArrayList; import java.util.Arrays; import java.util.IntSummaryStatistics; import java.util.List; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); // 求和 int sum = list.stream() .mapToInt(Integer::intValue) .sum(); System.out.println("Sum: " + sum); // 平均值 double average = list.stream() .mapToInt(Integer::intValue) .average() .orElse(0); System.out.println("Average: " + average); // 聚合统计 IntSummaryStatistics stats = list.stream() .mapToInt(Integer::intValue) .summaryStatistics(); System.out.println("Statistics: " + stats); } } -
分组:使用
Stream的collect方法结合Collectors.groupingBy进行分组操作。import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(Arrays.asList("Apple", "Banana", "Avocado", "Blueberry", "Cherry")); // 按首字母分组 Map<Character, List<String>> groupedByFirstLetter = list.stream() .collect(Collectors.groupingBy(s -> s.charAt(0))); System.out.println("Grouped by first letter: " + groupedByFirstLetter); } } -
取值:使用
Stream的findFirst或findAny方法来获取第一个或任意一个元素。import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; public class Main { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry")); // 获取第一个元素 Optional<String> firstElement = list.stream() .findFirst(); System.out.println("First element: " + firstElement.orElse("None")); // 获取任意元素(如果流不为空) Optional<String> anyElement = list.stream() .findAny(); System.out.println("Any element: " + anyElement.orElse("None")); } }
理解 Properties 的用途
Properties 是 Java 中的一个类,专门用于处理键值对形式的配置数据。它继承自 Hashtable,因此具有 Hashtable 的所有功能,同时提供了额外的用于处理配置数据的方法。主要用于读取和写入配置文件,存储应用程序的设置参数或系统属性。
- 用途:
- 配置管理:管理应用程序的配置信息。例如,数据库连接信息、应用程序参数等。配置文件通常是
.properties格式,存储了应用程序的键值对配置。 - 国际化:
Properties类可用于存储不同语言的资源文件。 - 系统属性:通过
System.getProperty和System.setProperty方法进行访问和设置JVM 的配置参数。
- 配置管理:管理应用程序的配置信息。例如,数据库连接信息、应用程序参数等。配置文件通常是
掌握 HashMap 和 HashTable 的区别
-
初始容量和负载因子:
HashMap: 默认初始容量为 16,负载因子为 0.75。Hashtable: 默认初始容量为 11,负载因子为 0.75。 -
线程安全:
Hashtable是线程安全的,而HashMap不是。 -
键和值:
HashMap允许null键和值,而Hashtable不允许。 -
方法:
HashMap提供了更多现代方法,Hashtable的方法较为陈旧。 -
性能:
HashMap通常性能更好,尤其是在单线程环境下。
掌握 HashMap 的常用方法和讲解
-
基本操作:
put:put(K key, V value): 将指定的值与指定的键关联。如果键已存在,则更新其值。get:get(Object key): 根据键获取对应的值。如果键不存在,则返回null。remove:remove(Object key): 移除与指定键相关联的键值对。如果键不存在,则什么都不做。- ``containsKey
:containsKey(Object key): 检查HashMap` 是否包含指定的键。 containsValue:containsValue(Object value): 检查HashMap是否包含指定的值。size:size(): 返回HashMap中键值对的数量。isEmpty:isEmpty(): 检查HashMap是否为空。clear:clear(): 清空HashMap中的所有键值对。
-
遍历:
-
keySet:通过keySet()遍历键for (String key : map.keySet()) { System.out.println("Key: " + key); } -
values:通过values()遍历值for (String value : map.values()) { System.out.println("Value: " + value); } -
entrySet:通过entrySet()遍历键值对for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); }
-
-
高级操作:
-
putIfAbsent:putIfAbsent(K key, V value): 如果指定的键尚未存在,则将指定的值与该键关联。此操作是原子的。 -
replace:replace(K key, V value): 替换指定键的值,如果该键存在。replace(K key, V oldValue, V newValue): 仅当键的当前值与oldValue相等时,才将其替换为newValue。 -
computeIfAbsent:computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction): 如果指定的键尚未存在,则使用提供的函数计算其值并插入。 -
computeIfPresent:computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction): 如果指定的键存在,则使用提供的函数计算其新值,并更新。 -
merge:
merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction): 如果指定的键存在,则使用提供的函数合并现有值和新值。如果键不存在,则插入新值。 -
理解 HashMap 底层实现原理
-
哈希表结构
HashMap底层主要由一个数组和链表(或红黑树)构成。这个数组称为哈希表,用于存储键值对的引用。- 数组: 哈希表的数组称为
table。每个数组元素称为桶(bucket),用来存储哈希冲突的元素。 - 链表: 如果多个键的哈希值相同,它们会被存储在同一个桶中,形成一个链表。每个链表节点包含一个键值对。
- 数组: 哈希表的数组称为
-
哈希值
- 哈希值:
HashMap使用hashCode()方法计算键的哈希值,然后使用该哈希值来确定元素在哈希表中的位置。计算桶的位置通常是通过哈希值与数组长度的模运算来实现的。 - 桶位置:
HashMap通过将哈希值与数组长度减一(通常是 2 的幂)进行按位与运算来确定元素的桶位置。这可以确保哈希值映射到数组的有效索引范围内。
- 哈希值:
-
处理哈希冲突
- 链表: 如果两个或多个键的哈希值相同,它们会被存储在同一个桶中,形成一个链表。在这种情况下,
HashMap会遍历链表来查找相应的键值对。 - 红黑树: 在 Java 8 及以后版本中,为了提高性能,
HashMap在链表长度超过一定阈值时(默认是 8)将链表转换为红黑树。红黑树是一种自平衡的二叉搜索树,它可以提供更高效的查找操作(O(log n) 时间复杂度)。
- 链表: 如果两个或多个键的哈希值相同,它们会被存储在同一个桶中,形成一个链表。在这种情况下,
-
扩容机制
HashMap会根据实际存储的元素数量和负载因子(默认是 0.75)来决定何时扩容。扩容会增加哈希表的数组长度,并重新分配已有元素到新的数组中。- 计算新容量: 新的容量是当前容量的两倍
- 重新哈希: 扩容后,
HashMap需要重新计算每个键的哈希值,并将其放到新的桶中。这一过程称为“再哈希”。
掌握 HashMap 的遍历方法
-
keySet(): 获取所有的键,遍历键。 -
values(): 获取所有的值,遍历值。 -
entrySet(): 获取所有的键值对,遍历键值对。 -
Iterator: 使用迭代器遍历keySet()、values()或entrySet()。import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class HashMapTraversal { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); // 使用迭代器遍历所有的键 Iterator<String> keyIterator = map.keySet().iterator(); while (keyIterator.hasNext()) { String key = keyIterator.next(); System.out.println("Key: " + key); } } } import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class HashMapTraversal { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); // 使用迭代器遍历所有的值 Iterator<String> valueIterator = map.values().iterator(); while (valueIterator.hasNext()) { String value = valueIterator.next(); System.out.println("Value: " + value); } } } import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class HashMapTraversal { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); // 使用迭代器遍历所有的键值对 Iterator<Map.Entry<String, String>> entryIterator = map.entrySet().iterator(); while (entryIterator.hasNext()) { Map.Entry<String, String> entry = entryIterator.next(); System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); } } } -
forEach: 使用 Java 8 的forEach方法和 lambda 表达式遍历HashMap。import java.util.HashMap; import java.util.Map; public class HashMapTraversal { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); // 使用 forEach 和 lambda 表达式遍历所有的键值对 map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value)); } }
理解 HashMap 如何处理 hash 冲突
- 链表法: 处理哈希冲突的初始方法,所有冲突的元素在桶中形成链表。
- 红黑树法: 当链表过长时,将链表转换为红黑树,以提高查找效率。
- 负载因子和扩容: 使用负载因子和扩容机制来控制哈希表的填充程度和减少冲突。
- 哈希函数优化: 使用优化的哈希函数和扰动函数来减少哈希冲突。
理解 HashMap 容量扩展原理
-
触发扩展
HashMap的扩展通常由负载因子决定。负载因子(默认值为 0.75)表示哈希表的填充程度,即当哈希表中存储的元素数量超过当前容量与负载因子的乘积时,会触发扩展。例如,如果当前容量为 16,则当存储的元素数量超过 16 * 0.75 = 12 时,HashMap会进行扩展。 -
扩展步骤
-
计算新容量:新容量通常是当前容量的两倍。
int newCapacity = table.length * 2; -
创建新的哈希表:创建一个新的数组(新的哈希表),其大小为新容量。
Node<K, V>[] newTable = new Node[newCapacity]; -
重新分配元素:遍历旧的哈希表,将每个桶中的元素重新计算哈希值,并将其放入新的哈希表中。每个元素的哈希值在新容量的范围内进行重新计算,以确定新的桶位置。
for (Node<K, V> node : table) { while (node != null) { int newIndex = (node.hash & (newCapacity - 1)); Node<K, V> next = node.next; node.next = newTable[newIndex]; newTable[newIndex] = node; node = next; } } -
更新哈希表引用:将哈希表的引用更新为新的哈希表。
table = newTable;
-
掌握 ConcurrentHashMap 原理、常见用途、注意事项
- 原理:
ConcurrentHashMap使用了分段锁(Java 7 及之前版本)或锁分离(Java 8 及之后版本)的机制,结合了无锁数据结构和多锁机制来实现高效的并发操作。 - 用途: 适用于高并发数据访问、大规模并发应用和线程池任务管理等场景。
- 注意事项: 避免长时间持有锁,确保线程安全操作,了解迭代器的弱一致性,监控性能瓶颈。
掌握 Collections、Arrays 工具类常用方法
Collections
- 创建不可变集合
Collections.unmodifiableList(List<T> list): 返回一个不可变的List。Collections.unmodifiableSet(Set<T> set): 返回一个不可变的Set。Collections.unmodifiableMap(Map<K, V> map): 返回一个不可变的Map。
- 排序和查找
Collections.sort(List<T> list): 对List进行排序。Collections.binarySearch(List<? extends Comparable<? super T>> list, T key): 在已排序的List中进行二分查找。
- 反转和填充
Collections.reverse(List<?> list): 反转List中的元素。Collections.fill(List<? super T> list, T obj): 用指定的对象填充List。
- 混洗和交换
Collections.shuffle(List<?> list): 随机打乱List中的元素。Collections.swap(List<?> list, int i, int j): 交换List中指定位置的元素。
- 查找最值
Collections.max(Collection<? extends T> coll): 返回集合中的最大元素。Collections.min(Collection<? extends T> coll): 返回集合中的最小元素。
Arrays
- 排序
Arrays.sort(T[] a): 对数组进行排序。Arrays.sort(int[] a): 对整数数组进行排序。
- 查找
Arrays.binarySearch(T[] a, T key): 在已排序的数组中进行二分查找。
- 填充
Arrays.fill(T[] a, T val): 用指定的值填充整个数组。Arrays.fill(int[] a, int val): 用指定的值填充整数数组。
- 比较
Arrays.equals(T[] a, T[] a2): 比较两个数组是否相等。Arrays.deepEquals(Object[] a1, Object[] a2): 递归比较两个数组是否相等(适用于嵌套数组)。
- 转换为列表
Arrays.asList(T... a): 将数组转换为List。
1371

被折叠的 条评论
为什么被折叠?



