☰
04面向对象深入和数组深入
2026/11/3 10:13:11 网站建设 项目流程

抽象类

定义与作用

抽象方法是使用abstract修饰且不包含具体实现的方法。其语法形式为:

public abstract 返回类型 方法名(参数列表);

抽象方法没有方法体(即没有{}内容),仅声明方法签名。它主要用于提供一个通用的模板,定义一组共有的属性和行为,供子类继承并实现具体逻辑。

抽象类与抽象方法的关系

  • 一个类如果包含至少一个抽象方法,那么该类必须声明为抽象类。
  • 抽象类可以包含零个或多个抽象方法。。
abstract class Animal { protected String name; public Animal(String name) { this.name = name; } // 抽象方法:子类必须实现 public abstract void makeSound(); // 具体方法:可直接使用 public void sleep() { System.out.println(name + " is sleeping."); } } class Dog extends Animal { public Dog(String name) { super(name); } @Override public void makeSound() { System.out.println("Woof! Woof!"); } } class Cat extends Animal { public Cat(String name) { super(name); } @Override public void makeSound() { System.out.println("Meow! Meow!"); } }
  • 抽象类不能被实例化,只能通过子类创建对象。
  • 子类继承抽象类时,必须实现所有抽象方法,除非子类也声明为抽象类。
  • 抽象类可以有构造函数,用于初始化公共字段。
  • 抽象类支持多态,可通过父类引用指向子类对象。

接口(Interface)

是Java中一种特殊的引用类型,用于定义一组方法的签名,但不包含具体实现。它规定了实现类必须提供哪些方法。接口中的所有方法默认为public abstract,且不能包含实例变量(只能有常量)。

public interface 接口名称 extends 父类接口1, 父类接口2 { // 常量定义(默认为 public static final) 数据类型 常量名 = 初始值; // 抽象方法声明(默认为 public abstract) 返回类型 方法名(参数列表); // 默认方法(default method) default 返回类型 默认方法名(参数列表) { // 方法体 } // 静态方法(static method) static 返回类型 静态方法名(参数列表) { // 方法体 } // 私有方法(private method) private void 私有方法名() { // 方法体 } }

实现接口的类

一个类可以通过implements关键字实现一个或多个接口。一个类实现接口时,必须实现所有抽象方法,否则该类也必须声明为抽象类。

public interface Animal { void makeSound(); void move(); }
public class Dog implements Animal { @Override public void makeSound() { System.out.println("Woof!"); } @Override public void move() { System.out.println("Running on four legs."); } }

实现接口的类可以被当作接口类型引用,实现多态:

Animal animal = new Dog(); animal.makeSound(); // 输出: Woof! animal.move(); // 输出: Running on four legs.

多个接口的实现

一个类可以实现多个接口,使用逗号分隔:

public interface Flyable { void fly(); } public interface Swimmable { void swim(); } public class Duck implements Flyable, Swimmable { @Override public void fly() { System.out.println("Duck is flying."); } @Override public void swim() { System.out.println("Duck is swimming."); } }

内部类

基本概念

在Java中,内部类是指定义在另一个类内部的类。内部类可以访问外部类的所有成员,包括私有成员。根据定义位置和使用方式的不同,内部类分为四种类型:成员内部类、局部内部类、匿名内部类和静态内部类。

成员内部类

成员内部类是定义在外部类中的普通类,与外部类的成员变量和方法处于同一级别。它可以直接访问外部类的任何成员,包括私有成员。

public class Work { public static void main(String[] args) { // 创建内部类对象的两种方式 OutClass out = new OutClass(); OutClass.InClass inner = out.new InClass(); inner.show(); OutClass.InClass a = new OutClass().new InClass(); a.show(); } } class OutClass { private int age = 18; public class InClass { int age = 20; public void show() { int age = 30; System.out.println("外部类变量:" + OutClass.this.age); // 外部类变量:18 System.out.println("内部类变量:" + this.age); // 内部类变量:20 System.out.println("局部变量:" + age); // 局部变量:30 } } }

静态内部类

静态内部类是使用static修饰的内部类,它不依赖于外部类的实例。因此,可以在不创建外部类对象的情况下直接创建静态内部类的实例。

public class OuterClass { static class StaticInnerClass { // 静态内部类的内容 } }
OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();

静态内部类不能直接访问外部类的非静态成员,只能访问静态成员。

局部内部类

局部内部类是在方法或代码块中定义的内部类。它的作用域仅限于该方法或代码块内。

public class OuterClass { public void method() { final int localVar = 30; class LocalInnerClass { public void display() { System.out.println("Local variable: " + localVar); } } LocalInnerClass inner = new LocalInnerClass(); inner.display(); } }

局部内部类只能访问外部方法中被声明为final或等效不可变的局部变量。

匿名内部类

匿名内部类是没有名字的内部类,通常用于简化代码,特别是在需要实现接口或继承类但只使用一次的情况下。

new 父类名或接口名() { // 类体内容:方法重写、变量定义等 };
public interface Greeting { void sayHello(); } public class Main { public static void main(String[] args) { Greeting greeting = new Greeting() { @Override public void sayHello() { System.out.println("Hello from anonymous class!"); } }; greeting.sayHello(); } }

匿名内部类常用于事件监听器、线程创建等场景。


String类

String 类是 Java 中用于表示字符串的不可变类,定义在 java.lang 包中。它封装了一个字符数组,用来存储字符串内容。由于其不可变性,一旦创建,字符串的内容无法更改。

字符串的创建方式

通过字面量创建:

String str1 = "Hello";

通过构造方法创建:

String str2 = new String("World"); String str3 = new String(new char[]{'a', 'b', 'c'});

常用方法

获取字符串长度:

int length = str.length();

获取指定位置字符:

char ch = str.charAt(0);

比较字符串内容是否相等(推荐使用 equals):

boolean isEqual = str1.equals(str2);

忽略大小写比较:

boolean isEqualIgnoreCase = str1.equalsIgnoreCase(str2);

查找子字符串位置:

int index = str.indexOf("world");

截取子字符串:

String sub = str.substring(0, 5);

替换字符或子字符串:

String replaced = str.replace('a', 'b'); String replacedAll = str.replaceAll("old", "new");

分割字符串:

String[] parts = str.split(",");

转换为大写或小写:

String upper = str.toUpperCase(); String lower = str.toLowerCase();

去除首尾空白字符:

String trimmed = str.trim();

判断是否以某前缀或后缀开始/结束:

boolean startsWith = str.startsWith("Hello"); boolean endsWith = str.endsWith("World");

String 的不可变性

String 对象一旦创建,其内容不能被修改。任何看似“修改”的操作实际上都会生成新的字符串对象。例如:

String s = "abc"; s = s + "def"; // 产生新对象,原对象不变

这种设计提高了安全性,尤其在多线程环境下,避免了共享字符串被意外修改的问题。

字符串常量池(String Pool)

Java 在堆内存中维护一个特殊的区域——字符串常量池,用于存放字面量形式的字符串。当使用字面量创建字符串时,如果池中已存在相同内容的字符串,则返回池中的引用,避免重复创建。

String a = "hello"; String b = "hello"; System.out.println(a == b); // true,指向同一对象

使用 new 构造时,不会检查常量池,总是创建新对象:

String c = new String("hello"); String d = new String("hello"); System.out.println(c == d); // false,不同对象

字符串拼接优化

频繁拼接字符串应使用 StringBuilder 或 StringBuffer,避免因大量临时对象导致性能下降。

StringBuilder sb = new StringBuilder(); sb.append("a").append("b").append("c"); String result = sb.toString();

比较字符串的正确方式

使用==比较的是引用地址,仅当两个变量指向同一个对象时才为真。若需比较内容,必须使用.equals()。

String s1 = "test"; String s2 = new String("test"); System.out.println(s1 == s2); // false System.out.println(s1.equals(s2)); // true

数组深入

数组拷贝

System.arraycopy() 方法

System.arraycopy(src, srcPos, dest, destPos, length);
  • src:源数组。
  • srcPos:源数组的起始位置。
  • dest:目标数组。
  • destPos:目标数组的起始位置。
  • length:要复制的元素数量。
int[] original = {1, 2, 3, 4, 5}; int[] copy = new int[original.length]; System.arraycopy(original, 0, copy, 0, original.length);

Arrays.copyOf() 方法

Arrays.copyOf(original, newLength);
  • 创建一个指定长度的新数组,并将原数组的内容复制到新数组中。
  • 如果新长度大于原数组,多余位置用默认值填充(如 0)。
int[] original = {1, 2, 3, 4, 5}; int[] copy = Arrays.copyOf(original, original.length);

Arrays.copyOfRange() 方法进行部分拷贝

Arrays.copyOfRange(original, from, to);
  • 从原数组的from索引开始,到to索引结束(不包含to)。
  • 返回一个新数组。
int[] original = {1, 2, 3, 4, 5}; int[] partialCopy = Arrays.copyOfRange(original, 1, 4); // [2, 3, 4]

clone() 方法进行浅拷贝

int[] copy = original.clone();
  • 对于基本类型数组,clone()方法返回一个新数组,内容与原数组相同。
  • 适用于一维基本类型数组。
int[] original = {1, 2, 3, 4, 5}; int[] copy = original.clone();

多维数组的拷贝

对于多维数组,clone()只进行浅拷贝,即只复制外层数组,内层数组仍指向原对象。

int[][] original = {{1, 2}, {3, 4}}; int[][] copy = original.clone(); // 此时,copy[0] 和 original[0] 指向同一数组,修改会影响彼此

若需深拷贝,需手动遍历并复制每一维:

int[][] deepCopy = new int[original.length][]; for (int i = 0; i < original.length; i++) { deepCopy[i] = original[i].clone(); }
  • System.arraycopy():性能最高,适合大量数据复制。
  • Arrays.copyOf()/copyOfRange():代码简洁,适合一般场景。
  • clone():最简单,但仅限于一维基本类型数组。
  • 多维数组必须手动实现深拷贝以避免引用共享问题。

数组删除

在Java中,数组是固定长度的数据结构,一旦创建便无法直接修改其大小。因此,无法直接从数组中删除元素。但可以通过以下几种方式实现“删除”效果:

使用集合类

将数组转换为List,利用集合类提供的删除方法,再转回数组。

import java.util.*; // 原始数组 Integer[] arr = {1, 2, 3, 4, 5}; // 转换为列表 List<Integer> list = new ArrayList<>(Arrays.asList(arr)); // 删除指定位置的元素(例如索引为2) list.remove(2); // 转回数组 arr = list.toArray(new Integer[0]); System.out.println(Arrays.toString(arr)); // [1, 2, 4, 5]

此方法适用于需要频繁增删操作的场景,灵活性高。

手动创建新数组(适用于删除单个元素)

通过遍历原数组,跳过要删除的元素,创建一个新数组。

int[] arr = {1, 2, 3, 4, 5}; int indexToDelete = 2; // 删除索引为2的元素(值为3) int[] newArr = new int[arr.length - 1]; for (int i = 0, j = 0; i < arr.length; i++) { if (i != indexToDelete) { newArr[j++] = arr[i]; } } arr = newArr; System.out.println(Arrays.toString(arr)); // [1, 2, 4, 5]

适用于已知删除位置的情况,效率较高。

删除指定值的元素(多个匹配)

若需删除所有等于某个值的元素,可使用类似方法二的方式。

int[] arr = {1, 2, 3, 2, 4, 2}; int valueToDelete = 2; List<Integer> list = new ArrayList<>(); for (int num : arr) { if (num != valueToDelete) { list.add(num); } } arr = list.stream().mapToInt(Integer::intValue).toArray(); System.out.println(Arrays.toString(arr)); // [1, 3, 4]

适合删除特定值的所有实例。

使用Arrays.copyOfRange(简化版)

当知道要删除的索引时,可直接使用Arrays.copyOfRange拷贝部分数组。

int[] arr = {1, 2, 3, 4, 5}; int indexToDelete = 2; int[] newArr = Arrays.copyOfRange(arr, 0, indexToDelete); int[] tail = Arrays.copyOfRange(arr, indexToDelete + 1, arr.length); // 合并两个部分 int[] result = new int[newArr.length + tail.length]; System.arraycopy(newArr, 0, result, 0, newArr.length); System.arraycopy(tail, 0, result, newArr.length, tail.length); arr = result; System.out.println(Arrays.toString(arr)); // [1, 2, 4, 5]

适合一次性删除操作。

数组扩容

在 Java 中,原生数组的大小是固定的,一旦创建便无法改变。因此,当需要动态增加元素数量时,必须通过手动实现扩容机制。常见的做法是创建一个更大的新数组,并将原数组中的元素复制到新数组中。

System.arraycopy() 方法

创建一个新的更大容量的数组,使用System.arraycopy()方法高效复制原数组内容。

public class DynamicArray { private int[] data; private int size; public DynamicArray() { data = new int[10]; // 初始容量 size = 0; } public void add(int element) { if (size == data.length) { // 扩容:创建新数组,长度为原数组的 1.5 倍 int newCapacity = data.length + data.length / 2; int[] newData = new int[newCapacity]; System.arraycopy(data, 0, newData, 0, data.length); data = newData; } data[size++] = element; } }

ArrayList()方法

内部已实现自动扩容逻辑

import java.util.ArrayList; ArrayList<Integer> list = new ArrayList<>(); list.add(1); list.add(2); // 底层自动处理扩容,无需手动干预

Arrays工具类

数组排序方法

使用Arrays.sort()对数组进行排序,支持基本类型和对象类型。

int[] numbers = {64, 34, 25, 12, 22, 11, 90}; Arrays.sort(numbers);

支持指定范围排序:

Arrays.sort(numbers, 1, 5); // 对索引1到4的元素排序

数组查找方法

使用Arrays.binarySearch()在已排序数组中查找元素。

int[] arr = {10, 20, 30, 40, 50}; int index = Arrays.binarySearch(arr, 30); // 返回2

若未找到,返回负值,表示插入位置。

数组比较方法

使用Arrays.equals()比较两个数组是否相等。

int[] a = {1, 2, 3}; int[] b = {1, 2, 3}; boolean isEqual = Arrays.equals(a, b); // true

对于多维数组,使用Arrays.deepEquals():

int[][] matrix1 = {{1, 2}, {3, 4}}; int[][] matrix2 = {{1, 2}, {3, 4}}; boolean deepEqual = Arrays.deepEquals(matrix1, matrix2); // true

数组填充方法

使用Arrays.fill()将数组元素设置为指定值。

int[] arr = new int[5]; Arrays.fill(arr, 7); // 所有元素变为7

支持部分填充:

Arrays.fill(arr, 1, 4, 9); // 索引1到3设为9

数组转字符串

使用Arrays.toString()将数组转换为可读字符串。

int[] arr = {1, 2, 3}; String str = Arrays.toString(arr); // "[1, 2, 3]"

多维数组

二维数组的初始化

声明并使用 new 指定大小

// 声明一个 3 行 4 列的二维数组 int[][] matrix = new int[3][4]; // 可以逐个赋值 matrix[0][0] = 1; matrix[0][1] = 2; matrix[1][2] = 3; // ...

直接初始化(静态初始化)

// 初始化一个 2×3 的二维数组 int[][] matrix = { {1, 2, 3}, {4, 5, 6} };

每一行必须用{}包裹,且每行元素数量可以不同(即不规则数组)

混合初始化

int[][] matrix = new int[3][]; matrix[0] = new int[]{1, 2}; matrix[1] = new int[]{3, 4, 5}; matrix[2] = new int[]{6};

使用二维数组保存表格数据

直接初始化创建

// 声明并初始化一个3行4列的二维数组 String[][] table = { {"姓名", "年龄", "城市", "职业"}, {"张三", "25", "北京", "工程师"}, {"李四", "30", "上海", "设计师"} }; // 遍历并输出表格内容 for (int i = 0; i < table.length; i++) { for (int j = 0; j < table[i].length; j++) { System.out.print(table[i][j] + "\t"); } System.out.println(); }

动态创建二维数组

// 创建一个指定行数和列数的二维数组 int rows = 3; int cols = 4; String[][] dynamicTable = new String[rows][cols]; // 为数组赋值 dynamicTable[0][0] = "姓名"; dynamicTable[0][1] = "年龄"; dynamicTable[0][2] = "城市"; dynamicTable[0][3] = "职业"; dynamicTable[1][0] = "王五"; dynamicTable[1][1] = "28"; dynamicTable[1][2] = "广州"; dynamicTable[1][3] = "教师";

处理不规则表格(非矩形)

// 不同行长度不同的情况(非规则二维数组) String[][] irregularTable = { {"产品", "价格"}, {"手机", "5000", "品牌"}, {"电脑", "8000", "配置", "重量"} };
// 使用固定宽度对齐输出 public static void printTable(String[][] table) { for (String[] row : table) { for (String cell : row) { System.out.printf("%-10s", cell); // 左对齐,占10个字符宽度 } System.out.println(); } } // 调用方法 printTable(table);

冒泡排序

冒泡排序是一种简单的排序算法,其核心思想是通过重复遍历数组,比较相邻元素并根据大小关系进行交换,使得较大的元素逐渐“冒泡”到数组的末尾。每一轮遍历都会将当前未排序部分的最大值移动到正确位置。

实现方式一:基础冒泡排序(升序)

public class BubbleSort { 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]) { // 交换元素 int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } public static void main(String[] args) { int[] numbers = {64, 34, 25, 12, 22, 11, 90}; System.out.println("排序前:" + java.util.Arrays.toString(numbers)); bubbleSort(numbers); System.out.println("排序后:" + java.util.Arrays.toString(numbers)); } }

实现方式二:优化版冒泡排序(提前终止)

当某一轮遍历中没有发生任何交换时,说明数组已经有序,可以提前结束排序过程。

public class OptimizedBubbleSort { public static void bubbleSort(int[] arr) { int n = arr.length; for (int i = 0; i < n - 1; i++) { boolean swapped = false; for (int j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; swapped = true; } } // 如果本轮没有交换,说明已排序完成 if (!swapped) break; } } public static void main(String[] args) { int[] numbers = {5, 2, 8, 1, 9}; System.out.println("排序前:" + java.util.Arrays.toString(numbers)); bubbleSort(numbers); System.out.println("排序后:" + java.util.Arrays.toString(numbers)); } }

降序冒泡排序

只需将比较条件从>改为<即可实现降序排列。

if (arr[j] < arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; }
  • 外层循环控制排序轮数,最多需要n-1轮。
  • 内层循环每次减少一个比较次数,因为每轮后最大值已归位。
  • 避免越界访问,注意j < n - i - 1的边界条件。

二分查找

二分查找是一种在有序数组中查找特定元素的高效算法。其核心思想是每次将搜索范围缩小一半,通过比较目标值与中间元素的大小关系,决定下一步在左半部分还是右半部分继续查找。

适用条件

  • 数组必须是有序的(升序或降序)。
  • 支持随机访问(如数组),不适用于链表等结构。

实现方式一:递归实现

public static int binarySearchRecursive(int[] arr, int target, int left, int right) { if (left > right) { return -1; // 未找到 } int mid = left + (right - left) / 2; if (arr[mid] == target) { return mid; } else if (arr[mid] > target) { return binarySearchRecursive(arr, target, left, mid - 1); } else { return binarySearchRecursive(arr, target, mid + 1, right); } }
int[] arr = {1, 3, 5, 7, 9, 11}; int index = binarySearchRecursive(arr, 7, 0, arr.length - 1); System.out.println(index); // 输出: 3

实现方式二:迭代实现(推荐)

public static int binarySearchIterative(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) { return mid; } else if (arr[mid] > target) { right = mid - 1; } else { left = mid + 1; } } return -1; // 未找到 }
int[] arr = {1, 3, 5, 7, 9, 11}; int index = binarySearchIterative(arr, 7); System.out.println(index); // 输出: 3
  • 使用left + (right - left) / 2而非(left + right) / 2可防止整数溢出。
  • 若数组中有重复元素,返回的索引是不确定的,取决于具体实现和数据分布。
  • 若需查找第一个或最后一个重复元素,需对标准二分查找进行扩展。

查找插入位置(左边界)

public static int lowerBound(int[] arr, int target) { int left = 0; int right = arr.length; while (left < right) { int mid = left + (right - left) / 2; if (arr[mid] < target) { left = mid + 1; } else { right = mid; } } return left; }

该方法返回第一个大于等于target的元素位置,可用于插入排序或去重场景。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询