1. 什么是常量
2. 控制台输出,快捷键
3. 数值数据类型有哪些,占多少字节
4. 数据类型转换
5. 赋值运算符用法
6. 算术运算符
7. 自增自减运算符特点,前置后置
8. 逻辑运算符有哪些,特点
9. 控制台获取整数、字符串输入注意事项
10. 说一下运算符优先级
11. 运算符中的目什么意思?
12. switch语法,和if区别
13. 三目运算符的作用,怎么用
14. 数字、字符串相加特点
15. 变量的作用域
16. 数组内存分配
17. 数组使用常见的错误有哪些
18. 什么是深拷贝,浅拷贝,数组如何深拷贝
19. 选择排序的思路
20. 冒泡排序的思路
21. 二维数组的内存分配是怎样的
22. 什么是二分查找,怎么用
23. 什么是不定参数?方法参数定义和调用时数量必须一致吗
24. 返回值的用法
25. 什么是递归,有什么注意事项
26. 什么是面向对象编程
27. 什么是构造方法,有什么特点
28. 重写是什么,和重载的区别
29. 继承的弊端是什么
30. 权限修饰符有哪些,作用是什么
31. final关键字作用
32. static关键字作用
33. abstract作用
34. 什么是代码块,分类
35. 特殊的接口
36. 接口和抽象类异同点
37. 什么是枚举,为什么要用?
38. 如何定义枚举,枚举成员本质是什么
39. 枚举和switch连用注意事项
40. 什么是内部类,有哪些分类
41. 外部类内、外部创建内部类实例
42. 内部类成员隐藏现象是什么
43. 什么是匿名内部类
- 什么是常量?
常量是在程序运行过程中值不会改变的量。在Java中,通常使用final关键字来定义常量。常量通常用大写字母表示,多个单词间用下划线分隔。
示例代码:
public class ConstantExample {
// 定义一个常量
public static final int MAX_SIZE = 100;
public static void main(String[] args) {
System.out.println("常量值: " + MAX_SIZE);
// MAX_SIZE = 200; // 这行代码会导致编译错误,因为常量不能被修改
}
}
- 控制台输出,快捷键
在Java中,控制台输出通常使用System.out.println()方法。许多IDE提供了快捷键来简化输入。
示例代码:
public class ConsoleOutputExample {
public static void main(String[] args) {
// 使用System.out.println()输出到控制台
System.out.println("Hello, World!");
// 在许多IDE中,可以使用"sout"快捷键来快速输入System.out.println()
// sout + Tab
System.out.println("This is a shortcut example");
}
}
- 数值数据类型有哪些,占多少字节?
Java中的数值数据类型包括:
- byte: 1字节
- short: 2字节
- int: 4字节
- long: 8字节
- float: 4字节
- double: 8字节
示例代码:
public class NumericDataTypesExample {
public static void main(String[] args) {
byte b = 127;
short s = 32767;
int i = 2147483647;
long l = 9223372036854775807L;
float f = 3.14f;
double d = 3.14159265359;
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);
}
}
- 数据类型转换
数据类型转换分为自动转换(隐式转换)和强制转换(显式转换)。
- 自动转换:从小类型到大类型,不会损失精度。
- 强制转换:从大类型到小类型,可能会损失精度。
示例代码:
public class TypeConversionExample {
public static void main(String[] args) {
// 自动转换
int i = 100;
long l = i; // int 自动转换为 long
float f = l; // long 自动转换为 float
System.out.println("自动转换结果:");
System.out.println("i = " + i);
System.out.println("l = " + l);
System.out.println("f = " + f);
// 强制转换
double d = 3.14159;
int i2 = (int) d; // double 强制转换为 int,小数部分会被截断
System.out.println("\n强制转换结果:");
System.out.println("d = " + d);
System.out.println("i2 = " + i2);
}
}
- 赋值运算符用法
赋值运算符用于给变量赋值。基本的赋值运算符是"=“,此外还有复合赋值运算符如”+=", “-=”, “*=”, “/=”, "%="等。
示例代码:
public class AssignmentOperatorsExample {
public static void main(String[] args) {
int a = 10;
int b = 5;
// 简单赋值
int c = a;
System.out.println("c = " + c);
// 复合赋值
a += b; // 等同于 a = a + b
System.out.println("a += b: " + a);
a -= b; // 等同于 a = a - b
System.out.println("a -= b: " + a);
a *= b; // 等同于 a = a * b
System.out.println("a *= b: " + a);
a /= b; // 等同于 a = a / b
System.out.println("a /= b: " + a);
a %= b; // 等同于 a = a % b
System.out.println("a %= b: " + a);
}
}
- 算术运算符
算术运算符用于执行基本的数学运算。主要包括:+(加), -(减), *(乘), /(除), %(取余)。
示例代码:
public class ArithmeticOperatorsExample {
public static void main(String[] args) {
int a = 10;
int b = 3;
System.out.println("a + b = " + (a + b)); // 加法
System.out.println("a - b = " + (a - b)); // 减法
System.out.println("a * b = " + (a * b)); // 乘法
System.out.println("a / b = " + (a / b)); // 整数除法,结果为3
System.out.println("a % b = " + (a % b)); // 取余
// 浮点数除法
double c = 10.0;
double d = 3.0;
System.out.println("c / d = " + (c / d)); // 浮点数除法,结果为3.3333...
}
}
- 自增自减运算符特点,前置后置
自增(++)和自减(–)运算符用于将变量的值加1或减1。它们有前置和后置两种形式,区别在于返回值的时机。
- 前置(++i 或 --i):先执行自增或自减操作,然后返回值。
- 后置(i++ 或 i–):先返回当前值,然后执行自增或自减操作。
示例代码:
public class IncrementDecrementExample {
public static void main(String[] args) {
int i = 5;
System.out.println("初始值:i = " + i);
// 后置自增
System.out.println("i++ = " + (i++)); // 输出5,然后i变为6
System.out.println("自增后:i = " + i); // 输出6
// 前置自增
System.out.println("++i = " + (++i)); // i先变为7,然后输出7
System.out.println("再次自增后:i = " + i); // 输出7
// 后置自减
System.out.println("i-- = " + (i--)); // 输出7,然后i变为6
System.out.println("自减后:i = " + i); // 输出6
// 前置自减
System.out.println("--i = " + (--i)); // i先变为5,然后输出5
System.out.println("再次自减后:i = " + i); // 输出5
}
}
- 逻辑运算符有哪些,特点
主要的逻辑运算符包括:
- &&(与)
- ||(或)
- !(非)
特点:
- 短路特性:&&和||具有短路特性,即如果第一个操作数已经能够确定结果,就不会评估第二个操作数。
示例代码:
public class LogicalOperatorsExample {
public static void main(String[] args) {
boolean a = true;
boolean b = false;
System.out.println("a && b = " + (a && b)); // 与运算
System.out.println("a || b = " + (a || b)); // 或运算
System.out.println("!a = " + (!a)); // 非运算
// 短路特性演示
int x = 5;
int y = 3;
// 由于x > 10为false,右侧的y++不会执行
if (x > 10 && (y++ < 5)) {
System.out.println("这不会被打印");
}
System.out.println("y = " + y); // y仍然是3
// 由于x < 10为true,右侧的y++会执行
if (x < 10 || (y++ < 5)) {
System.out.println("这会被打印");
}
System.out.println("y = " + y); // y现在是4
}
}
- 控制台获取整数、字符串输入注意事项
使用Scanner类来获取用户输入。注意事项:
- 使用nextInt()后,如果要读取字符串,需要先用nextLine()消除换行符。
- 使用next()读取不包含空格的字符串,nextLine()读取整行包括空格的字符串。
示例代码:
import java.util.Scanner;
public class InputExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入一个整数:");
int number = scanner.nextInt();
System.out.println("你输入的整数是:" + number);
// 消除换行符
scanner.nextLine();
System.out.print("请输入一个字符串:");
String str = scanner.nextLine();
System.out.println("你输入的字符串是:" + str);
scanner.close();
}
}
- 说一下运算符优先级
运算符优先级从高到低大致顺序为:
- 单目运算符(++, --, !, ~)
- 算术运算符(*, /, %, +, -)
- 移位运算符(<<, >>, >>>)
- 关系运算符(<, >, <=, >=, instanceof)
- 相等运算符(==, !=)
- 位运算符(&, ^, |)
- 逻辑运算符(&&, ||)
- 三目运算符(? :)
- 赋值运算符(=, +=, -=, *=, /=, %=, etc.)
示例代码:
public class OperatorPrecedenceExample {
public static void main(String[] args) {
int result = 5 + 3 * 2; // 乘法优先级高于加法
System.out.println("5 + 3 * 2 = " + result); // 输出11
result = (5 + 3) * 2; // 括号内的操作优先执行
System.out.println("(5 + 3) * 2 = " + result); // 输出16
boolean logicalResult = true && false || true; // &&优先级高于||
System.out.println("true && false || true = " + logicalResult); // 输出true
int a = 5;
int b = 3;
int c = 2;
result = a + b * c++; // 后置自增的优先级高于乘法,但自增在乘法后执行
System.out.println("a + b * c++ = " + result); // 输出11
System.out.println("c = " + c); // c现在是3
}
}
- 运算符中的"目"是什么意思?
在运算符中,"目"指的是操作数的数量。常见的有:
- 一元(单目)运算符:只需要一个操作数,如 ++、–、!
- 二元(双目)运算符:需要两个操作数,如 +、-、*、/、%
- 三元(三目)运算符:需要三个操作数,Java中只有一个三元运算符 ?:
示例代码:
public class OperandExample {
public static void main(String[] args) {
int a = 5;
// 一元运算符示例
int b = -a; // 负号是一元运算符
System.out.println("一元运算符结果:" + b);
// 二元运算符示例
int c = a + 3; // 加号是二元运算符
System.out.println("二元运算符结果:" + c);
// 三元运算符示例
int d = (a > 3) ? 1 : 0; // 问号冒号是三元运算符
System.out.println("三元运算符结果:" + d);
}
}
- switch语法,和if区别
switch语句用于多分支选择,基于一个变量的值来执行不同的代码块。
语法:
switch(expression) {
case value1:
// 代码块
break;
case value2:
// 代码块
break;
// ...
default:
// 默认代码块
}
与if的区别:
- switch适合基于单个变量的多分支选择
- if可以处理更复杂的条件判断
- switch通常比多个if-else语句更高效和易读
示例代码:
public class SwitchExample {
public static void main(String[] args) {
int dayOfWeek = 3;
switch (dayOfWeek) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
case 5:
System.out.println("星期五");
break;
case 6:
case 7:
System.out.println("周末");
break;
default:
System.out.println("无效的日期");
}
// 等效的if-else结构
if (dayOfWeek == 1) {
System.out.println("星期一");
} else if (dayOfWeek == 2) {
System.out.println("星期二");
} else if (dayOfWeek == 3) {
System.out.println("星期三");
} // ... 其他情况
}
}
- 三目运算符的作用,怎么用
三目运算符(也称为条件运算符)是一种简洁的条件语句,用于根据条件选择两个值之一。
语法:condition ? value_if_true : value_if_false
作用:简化简单的if-else语句,使代码更加简洁。
示例代码:
public class TernaryOperatorExample {
public static void main(String[] args) {
int a = 10;
int b = 20;
// 使用三目运算符
int max = (a > b) ? a : b;
System.out.println("较大的数是:" + max);
// 等效的if-else语句
int maxIfElse;
if (a > b) {
maxIfElse = a;
} else {
maxIfElse = b;
}
System.out.println("使用if-else,较大的数是:" + maxIfElse);
// 三目运算符可以嵌套
int c = 30;
int absoluteMax = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
System.out.println("三个数中最大的是:" + absoluteMax);
}
}
- 数字、字符串相加特点
- 数字相加:执行算术加法运算
- 字符串相加:执行字符串拼接操作
- 当数字和字符串混合相加时,会将数字转换为字符串然后进行拼接
示例代码:
public class AdditionExample {
public static void main(String[] args) {
// 数字相加
int a = 5;
int b = 3;
int sum = a + b;
System.out.println("5 + 3 = " + sum); // 输出:5 + 3 = 8
// 字符串相加(拼接)
String str1 = "Hello";
String str2 = "World";
String result = str1 + " " + str2;
System.out.println(result); // 输出:Hello World
// 数字和字符串混合
String mixed = "The sum is: " + a + b;
System.out.println(mixed); // 输出:The sum is: 53
// 使用括号改变运算顺序
String correctSum = "The sum is: " + (a + b);
System.out.println(correctSum); // 输出:The sum is: 8
}
}
- 变量的作用域
变量的作用域是指变量可以被访问的代码范围。主要有以下几种:
- 局部变量:方法或代码块内部定义的变量
- 实例变量(成员变量):在类中、方法外定义的变量
- 类变量(静态变量):使用static关键字定义的变量
示例代码:
public class ScopeExample {
// 实例变量
private int instanceVar = 1;
// 类变量(静态变量)
private static int classVar = 2;
public void method1() {
// 局部变量
int localVar = 3;
System.out.println("局部变量: " + localVar);
System.out.println("实例变量: " + instanceVar);
System.out.println("类变量: " + classVar);
{
// 代码块内的局部变量
int blockVar = 4;
System.out.println("代码块变量: " + blockVar);
}
// System.out.println(blockVar); // 错误,blockVar在这里不可访问
}
public static void main(String[] args) {
ScopeExample example = new ScopeExample();
example.method1();
// System.out.println(localVar); // 错误,localVar在这里不可访问
System.out.println("通过实例访问实例变量: " + example.instanceVar);
System.out.println("直接访问类变量: " + classVar);
}
}
- 数组内存分配
在Java中,数组是对象,它们存储在堆(Heap)内存中。数组的引用存储在栈(Stack)中,而实际的数组元素存储在堆中的连续内存空间里。
示例代码:
public class ArrayMemoryAllocationExample {
public static void main(String[] args) {
// 声明并初始化一个整型数组
int[] numbers = new int[5];
// 给数组元素赋值
for (int i = 0; i < numbers.length; i++) {
numbers[i] = i * 10;
}
// 打印数组元素
System.out.println("数组元素:");
for (int num : numbers) {
System.out.println(num);
}
// 打印数组引用的内存地址
System.out.println("数组引用的内存地址:" + numbers);
}
}
- 数组使用常见的错误有哪些
常见的数组使用错误包括:
- 数组越界访问
- 使用未初始化的数组
- 忘记数组索引是从0开始的
示例代码:
public class ArrayCommonErrorsExample {
public static void main(String[] args) {
// 1. 数组越界访问
int[] arr1 = new int[5];
try {
System.out.println(arr1[5]); // 错误:索引5超出了数组范围
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界错误:" + e.getMessage());
}
// 2. 使用未初始化的数组
int[] arr2;
// System.out.println(arr2[0]); // 错误:arr2未初始化
// 3. 忘记数组索引是从0开始的
String[] days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
System.out.println("一周的第一天是:" + days[0]); // 正确:索引0表示第一个元素
// System.out.println("一周的第一天是:" + days[1]); // 错误:这会打印"Tuesday"
}
}
- 什么是深拷贝,浅拷贝,数组如何深拷贝
- 浅拷贝:创建一个新对象,但新对象的属性和原始对象的属性引用相同的内存地址。
- 深拷贝:创建一个新对象,并且递归地复制原始对象的所有嵌套对象,使得新对象和原始对象完全独立。
对于数组的深拷贝,可以使用以下方法:
- 使用
System.arraycopy()
方法 - 使用
Arrays.copyOf()
方法 - 使用克隆方法
clone()
示例代码:
import java.util.Arrays;
public class ArrayCopyExample {
public static void main(String[] args) {
// 原始数组
int[] original = {1, 2, 3, 4, 5};
// 浅拷贝
int[] shallowCopy = original;
// 深拷贝方法1:使用System.arraycopy()
int[] deepCopy1 = new int[original.length];
System.arraycopy(original, 0, deepCopy1, 0, original.length);
// 深拷贝方法2:使用Arrays.copyOf()
int[] deepCopy2 = Arrays.copyOf(original, original.length);
// 深拷贝方法3:使用clone()
int[] deepCopy3 = original.clone();
// 修改原始数组
original[0] = 99;
System.out.println("原始数组: " + Arrays.toString(original));
System.out.println("浅拷贝: " + Arrays.toString(shallowCopy));
System.out.println("深拷贝1: " + Arrays.toString(deepCopy1));
System.out.println("深拷贝2: " + Arrays.toString(deepCopy2));
System.out.println("深拷贝3: " + Arrays.toString(deepCopy3));
}
}
- 选择排序的思路
选择排序的基本思路是:
- 从未排序的元素中找到最小(或最大)的元素
- 将该元素与未排序部分的第一个元素交换位置
- 重复这个过程,直到所有元素都被排序
示例代码:
public class SelectionSortExample {
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
int minIndex = i;
// 在未排序部分找到最小元素
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 将找到的最小元素与未排序部分的第一个元素交换
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}
public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
System.out.println("排序前:" + Arrays.toString(arr));
selectionSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
}
- 冒泡排序的思路
冒泡排序的基本思路是:
- 比较相邻的两个元素,如果第一个比第二个大,就交换它们
- 对每一对相邻元素执行相同的操作,从开始第一对到结尾的最后一对
- 重复这个过程,每次循环后,未排序的最大元素会"冒泡"到数组的末尾
- 重复上述步骤,直到没有任何元素需要交换
示例代码:
public class BubbleSortExample {
public static void bubbleSort(int[] arr) {
int n = arr.length;
boolean swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
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;
swapped = true;
}
}
// 如果内层循环未发生交换,则数组已经有序
if (!swapped) {
break;
}
}
}
public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
System.out.println("排序前:" + Arrays.toString(arr));
bubbleSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
}
- 二维数组的内存分配是怎样的
在Java中,二维数组实际上是一个数组的数组。它在内存中的分配是这样的:
- 首先分配一个一维数组来存储每个子数组的引用。
- 然后为每个子数组分配内存。
示例代码:
public class TwoDimensionalArrayExample {
public static void main(String[] args) {
// 创建一个3x4的二维数组
int[][] matrix = new int[3][4];
// 填充数组
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
matrix[i][j] = i * 10 + j;
}
}
// 打印数组
for (int[] row : matrix) {
System.out.println(Arrays.toString(row));
}
// 打印每个子数组的内存地址
for (int i = 0; i < matrix.length; i++) {
System.out.println("子数组 " + i + " 的地址: " + matrix[i]);
}
}
}
- 什么是二分查找,怎么用
二分查找是一种在有序数组中查找特定元素的高效算法。其基本思路是:
- 将数组分成两半。
- 如果要查找的元素小于中间元素,则在左半部分继续查找。
- 如果大于中间元素,则在右半部分继续查找。
- 重复这个过程,直到找到元素或确定元素不存在。
示例代码:
public class BinarySearchExample {
public static int binarySearch(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; // 找到目标,返回索引
}
if (arr[mid] < target) {
left = mid + 1; // 目标在右半部分
} else {
right = mid - 1; // 目标在左半部分
}
}
return -1; // 目标不存在于数组中
}
public static void main(String[] args) {
int[] arr = {2, 3, 4, 10, 40};
int target = 10;
int result = binarySearch(arr, target);
if (result == -1) {
System.out.println("元素不存在于数组中");
} else {
System.out.println("元素找到于索引 " + result);
}
}
}
- 什么是不定参数?方法参数定义和调用时数量必须一致吗
不定参数允许方法接受任意数量的参数。在Java中,使用省略号(…)来表示不定参数。方法参数的定义和调用时的数量不必一致。
示例代码:
public class VariableArgumentsExample {
// 使用不定参数的方法
public static int sum(int... numbers) {
int total = 0;
for (int num : numbers) {
total += num;
}
return total;
}
public static void main(String[] args) {
System.out.println("Sum of 1, 2, 3: " + sum(1, 2, 3));
System.out.println("Sum of 1, 2, 3, 4, 5: " + sum(1, 2, 3, 4, 5));
System.out.println("Sum of no arguments: " + sum());
int[] arr = {1, 2, 3, 4, 5};
System.out.println("Sum of array elements: " + sum(arr));
}
}
- 返回值的用法
返回值是方法执行后返回给调用者的结果。方法可以返回任何数据类型,包括基本类型和对象类型。如果方法不需要返回值,则使用void关键字。
示例代码:
public class ReturnValueExample {
// 返回int类型的方法
public static int add(int a, int b) {
return a + b;
}
// 返回String类型的方法
public static String greet(String name) {
return "Hello, " + name + "!";
}
// 无返回值的方法
public static void printMessage(String message) {
System.out.println(message);
}
public static void main(String[] args) {
int sum = add(5, 3);
System.out.println("Sum: " + sum);
String greeting = greet("Alice");
System.out.println(greeting);
printMessage("This method doesn't return anything.");
}
}
- 什么是递归,有什么注意事项
递归是一种方法调用自身的编程技巧。它通常用于解决可以分解为相似子问题的问题。
注意事项:
- 必须有基本情况(终止条件)以防止无限递归。
- 每次递归调用应该向基本情况靠近。
- 递归可能导致栈溢出,特别是对于深度递归。
- 有时迭代解决方案可能更高效。
示例代码(计算阶乘):
public class RecursionExample {
public static int factorial(int n) {
// 基本情况
if (n == 0 || n == 1) {
return 1;
}
// 递归调用
return n * factorial(n - 1);
}
public static void main(String[] args) {
int number = 5;
int result = factorial(number);
System.out.println(number + "! = " + result);
}
}
- 什么是面向对象编程
面向对象编程(OOP)是一种编程范式,它基于"对象"的概念,这些对象包含数据和代码。OOP的主要特征包括:
- 封装:将数据和操作数据的方法绑定在一起。
- 继承:允许创建新类,继承现有类的属性和方法。
- 多态:允许使用一个接口来引用不同类型的对象。
示例代码:
// 封装示例
class Car {
private String model;
private int year;
public Car(String model, int year) {
this.model = model;
this.year = year;
}
public void displayInfo() {
System.out.println("Car: " + model + ", Year: " + year);
}
}
// 继承示例
class ElectricCar extends Car {
private int batteryCapacity;
public ElectricCar(String model, int year, int batteryCapacity) {
super(model, year);
this.batteryCapacity = batteryCapacity;
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Battery Capacity: " + batteryCapacity + " kWh");
}
}
public class OOPExample {
public static void main(String[] args) {
Car regularCar = new Car("Toyota Corolla", 2020);
ElectricCar electricCar = new ElectricCar("Tesla Model 3", 2021, 75);
regularCar.displayInfo();
electricCar.displayInfo();
}
}
- 什么是构造方法,有什么特点
构造方法是一种特殊的方法,用于创建和初始化对象。构造方法的特点包括:
- 方法名与类名相同。
- 没有返回类型,甚至不需要void。
- 在创建对象时自动调用。
- 可以重载,即可以有多个构造方法。
示例代码:
public class Person {
private String name;
private int age;
// 无参构造方法
public Person() {
name = "Unknown";
age = 0;
}
// 带参数的构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void displayInfo() {
System.out.println("Name: " + name + ", Age: " + age);
}
public static void main(String[] args) {
Person person1 = new Person();
Person person2 = new Person("Alice", 30);
person1.displayInfo();
person2.displayInfo();
}
}
- 重写是什么,和重载的区别
重写(Override)和重载(Overload)是两个不同的概念:
重写:
- 发生在子类和父类之间。
- 方法名、参数列表和返回类型必须相同。
- 用于提供父类方法的特定实现。
重载:
- 发生在同一个类中。
- 方法名相同,但参数列表不同。
- 返回类型可以相同也可以不同。
示例代码:
class Animal {
public void makeSound() {
System.out.println("The animal makes a sound");
}
// 方法重载
public void makeSound(String sound) {
System.out.println("The animal makes a " + sound + " sound");
}
}
class Dog extends Animal {
// 方法重写
@Override
public void makeSound() {
System.out.println("The dog barks");
}
}
public class OverrideOverloadExample {
public static void main(String[] args) {
Animal animal = new Animal();
Dog dog = new Dog();
animal.makeSound();
animal.makeSound("loud");
dog.makeSound();
}
}
- 继承的弊端是什么
继承虽然powerful,但也有一些潜在的弊端:
- 紧耦合:子类与父类紧密耦合,父类的修改可能影响所有子类。
- 脆弱的基类问题:对基类的修改可能无意中破坏子类。
- 层次结构可能变得复杂:多层继承可能导致难以理解和维护的代码。
- 可能违反封装:子类可能需要访问父类的protected成员,这可能破坏封装。
- 多重继承的问题:Java不支持多重继承,这可能限制设计灵活性。
示例代码(展示紧耦合问题):
class Parent {
public void doSomething() {
System.out.println("Parent doing something");
}
}
class Child extends Parent {
@Override
public void doSomething() {
super.doSomething();
System.out.println("Child doing extra stuff");
}
}
public class InheritanceDrawbackExample {
public static void main(String[] args) {
Child child = new Child();
child.doSomething();
// 如果Parent类的doSomething方法发生变化,可能会影响Child类的行为
}
}
- 权限修饰符有哪些,作用是什么
Java中有四种权限修饰符,它们控制类、方法和变量的访问范围:
- public:可以被任何其他类访问。
- protected:可以被同一包内的类和子类访问。
- default(无修饰符):只能被同一包内的类访问。
- private:只能在声明它的类内部访问。
示例代码:
package com.example;
public class AccessModifierExample {
public int publicVar = 1;
protected int protectedVar = 2;
int defaultVar = 3;
private int privateVar = 4;
public void publicMethod() {
System.out.println("Public method");
}
protected void protectedMethod() {
System.out.println("Protected method");
}
void defaultMethod() {
System.out.println("Default method");
}
private void privateMethod() {
System.out.println("Private method");
}
}
// 同一包内的另一个类
class AnotherClass {
public void accessExample() {
AccessModifierExample obj = new AccessModifierExample();
System.out.println(obj.publicVar);
System.out.println(obj.protectedVar);
System.out.println(obj.defaultVar);
// System.out.println(obj.privateVar); // 编译错误
obj.publicMethod();
obj.protectedMethod();
obj.defaultMethod();
// obj.privateMethod(); // 编译错误
}
}
- final关键字作用
final关键字在Java中有多种用途:
- 修饰变量:变量成为常量,不能被修改。
- 修饰方法:方法不能被子类重写。
- 修饰类:类不能被继承。
示例代码:
public class FinalExample {
// final变量(常量)
final int MAX_VALUE = 100;
// final方法
final void displayInfo() {
System.out.println("This method cannot be overridden");
}
}
// final类
final class ImmutableClass {
// 这个类不能被继承
}
class SubClass extends FinalExample {
// 不能重写displayInfo方法
// void displayInfo() { } // 这会导致编译错误
void testFinal() {
// MAX_VALUE = 200; // 这会导致编译错误,final变量不能被修改
}
}
- static关键字作用
static关键字用于创建属于类的成员,而不是实例。它可以用于:
- 变量:创建类变量,被所有实例共享。
- 方法:创建类方法,可以直接通过类名调用。
- 代码块:在类加载时执行。
- 内部类:创建静态内部类。
示例代码:
public class StaticExample {
// 静态变量
static int count = 0;
// 非静态变量
int instanceVar;
// 静态方法
static void incrementCount() {
count++;
}
// 静态代码块
static {
System.out.println("Static block executed");
}
// 静态内部类
static class InnerClass {
void display() {
System.out.println("Inside static inner class");
}
}
public StaticExample() {
instanceVar = count;
incrementCount();
}
public static void main(String[] args) {
StaticExample obj1 = new StaticExample();
StaticExample obj2 = new StaticExample();
System.out.println("obj1 instanceVar: " + obj1.instanceVar);
System.out.println("obj2 instanceVar: " + obj2.instanceVar);
System.out.println("Total count: " + StaticExample.count);
StaticExample.InnerClass innerObj = new StaticExample.InnerClass();
innerObj.display();
}
}
- abstract作用
abstract关键字用于创建抽象类和抽象方法:
- 抽象类:不能被实例化,用作其他类的基类。
- 抽象方法:没有方法体,必须在子类中实现。
示例代码:
abstract class Shape {
// 抽象方法
abstract double getArea();
// 具体方法
void display() {
System.out.println("This is a shape");
}
}
class Circle extends Shape {
double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
double getArea() {
return Math.PI * radius * radius;
}
}
public class AbstractExample {
public static void main(String[] args) {
// Shape shape = new Shape(); // 这会导致编译错误,抽象类不能被实例化
Circle circle = new Circle(5);
System.out.println("Circle area: " + circle.getArea());
circle.display();
}
}
- 什么是代码块,分类
代码块是在大括号 {} 中的一组语句。Java中的代码块主要有以下几类:
- 实例初始化块:在创建对象时执行。
- 静态初始化块:在类加载时执行,只执行一次。
- 局部代码块:在方法内部,用于控制变量的作用域。
- 同步代码块:用于多线程同步。
示例代码:
public class CodeBlockExample {
// 静态初始化块
static {
System.out.println("Static initialization block");
}
// 实例初始化块
{
System.out.println("Instance initialization block");
}
public CodeBlockExample() {
System.out.println("Constructor");
}
public void method() {
// 局部代码块
{
int localVar = 10;
System.out.println("Local block variable: " + localVar);
}
// localVar 在这里不可访问
// 同步代码块
synchronized(this) {
System.out.println("Synchronized block");
}
}
public static void main(String[] args) {
CodeBlockExample obj = new CodeBlockExample();
obj.method();
}
}
- 特殊的接口
Java中有一些特殊类型的接口:
- 标记接口:没有任何方法的接口,用于标记某个类属于特定类型。
- 函数式接口:只有一个抽象方法的接口,可以用lambda表达式实现。
- 默认方法接口:包含默认实现的方法的接口(Java 8+)。
示例代码:
// 标记接口
interface Serializable {
// 空接口
}
// 函数式接口
@FunctionalInterface
interface Calculable {
int calculate(int a, int b);
}
// 默认方法接口
interface Greetable {
default void greet() {
System.out.println("Hello!");
}
void greetWithName(String name);
}
public class SpecialInterfacesExample {
public static void main(String[] args) {
// 使用函数式接口
Calculable add = (a, b) -> a + b;
System.out.println("5 + 3 = " + add.calculate(5, 3));
// 使用默认方法接口
Greetable greeter = name -> System.out.println("Hello, " + name + "!");
greeter.greet();
greeter.greetWithName("Alice");
}
}
- 接口和抽象类异同点
相同点:
- 都不能被实例化
- 都可以包含抽象方法
- 都可以被其他类实现或继承
不同点:
- 抽象类可以有构造方法,接口不能
- 抽象类可以有非抽象方法,接口在Java 8之前只能有抽象方法(Java 8后可以有默认方法和静态方法)
- 一个类只能继承一个抽象类,但可以实现多个接口
- 抽象类可以有成员变量,接口只能有常量(public static final)
示例代码:
abstract class AbstractAnimal {
protected String name;
public AbstractAnimal(String name) {
this.name = name;
}
abstract void makeSound();
public void eat() {
System.out.println(name + " is eating.");
}
}
interface Swimmable {
void swim();
default void float() {
System.out.println("Floating in water.");
}
}
class Duck extends AbstractAnimal implements Swimmable {
public Duck(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println("Quack!");
}
@Override
public void swim() {
System.out.println(name + " is swimming.");
}
}
public class AbstractClassAndInterfaceExample {
public static void main(String[] args) {
Duck duck = new Duck("Donald");
duck.makeSound();
duck.eat();
duck.swim();
duck.float();
}
}
- 什么是枚举,为什么要用?
枚举是一种特殊的类,用于定义一组固定的常量。使用枚举的原因包括:
-
增加代码的可读性和类型安全性
-
限制变量的可能值
-
可以在switch语句中使用
-
可以添加方法和构造函数,使常量更加强大
-
如何定义枚举,枚举成员本质是什么
枚举的定义使用 enum
关键字。枚举成员本质上是该枚举类型的实例。
示例代码:
public enum DayOfWeek {
MONDAY(1),
TUESDAY(2),
WEDNESDAY(3),
THURSDAY(4),
FRIDAY(5),
SATURDAY(6),
SUNDAY(7);
private final int dayNumber;
DayOfWeek(int dayNumber) {
this.dayNumber = dayNumber;
}
public int getDayNumber() {
return dayNumber;
}
public boolean isWeekend() {
return this == SATURDAY || this == SUNDAY;
}
}
public class EnumExample {
public static void main(String[] args) {
DayOfWeek today = DayOfWeek.WEDNESDAY;
System.out.println("Today is " + today);
System.out.println("Day number: " + today.getDayNumber());
System.out.println("Is it weekend? " + today.isWeekend());
}
}
- 枚举和switch连用注意事项
使用枚举和switch连用时,主要注意事项是不需要使用枚举类名作为前缀。
示例代码:
public class EnumSwitchExample {
public static void main(String[] args) {
DayOfWeek day = DayOfWeek.FRIDAY;
switch (day) {
case MONDAY:
System.out.println("Start of the work week");
break;
case FRIDAY:
System.out.println("End of the work week");
break;
case SATURDAY:
case SUNDAY:
System.out.println("Weekend");
break;
default:
System.out.println("Midweek");
}
}
}
- 什么是内部类,有哪些分类
内部类是定义在另一个类内部的类。主要分类有:
- 成员内部类:定义在类内部,与成员变量同级
- 局部内部类:定义在方法内部
- 匿名内部类:没有名字的局部内部类,用于创建接口或抽象类的实例
- 静态内部类:使用static修饰的内部类
示例代码:
public class OuterClass {
private int outerField = 1;
// 成员内部类
class MemberInnerClass {
void display() {
System.out.println("Outer field: " + outerField);
}
}
// 静态内部类
static class StaticInnerClass {
void display() {
System.out.println("This is a static inner class");
}
}
void methodWithLocalInnerClass() {
// 局部内部类
class LocalInnerClass {
void display() {
System.out.println("This is a local inner class");
}
}
LocalInnerClass lic = new LocalInnerClass();
lic.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
// 创建成员内部类实例
MemberInnerClass mic = outer.new MemberInnerClass();
mic.display();
// 创建静态内部类实例
StaticInnerClass sic = new StaticInnerClass();
sic.display();
outer.methodWithLocalInnerClass();
// 匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("This is an anonymous inner class");
}
};
runnable.run();
}
}
- 外部类内、外部创建内部类实例
创建内部类实例的方式取决于内部类的类型和位置。
示例代码:
public class OuterClass {
private int outerField = 1;
// 成员内部类
class MemberInnerClass {
void display() {
System.out.println("Outer field: " + outerField);
}
}
// 静态内部类
static class StaticInnerClass {
void display() {
System.out.println("This is a static inner class");
}
}
// 在外部类内部创建内部类实例
void createInnerClassInstances() {
MemberInnerClass mic = new MemberInnerClass();
mic.display();
StaticInnerClass sic = new StaticInnerClass();
sic.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.createInnerClassInstances();
// 在外部类外部创建内部类实例
OuterClass.MemberInnerClass mic = outer.new MemberInnerClass();
mic.display();
OuterClass.StaticInnerClass sic = new OuterClass.StaticInnerClass();
sic.display();
}
}
- 内部类成员隐藏现象是什么
内部类成员隐藏现象指的是当内部类的成员(变量或方法)与外部类的成员同名时,内部类的成员会隐藏外部类的成员。要访问被隐藏的外部类成员,可以使用"外部类名.this"语法。
示例代码:
public class OuterClass {
private int x = 10;
class InnerClass {
private int x = 20;
void printX() {
System.out.println("Inner x: " + x);
System.out.println("Outer x: " + OuterClass.this.x);
}
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
InnerClass inner = outer.new InnerClass();
inner.printX();
}
}
- 什么是匿名内部类
匿名内部类是一种没有名字的局部内部类,通常用于创建接口或抽象类的实例。它们在定义的同时被实例化。
示例代码:
interface Greeting {
void greet();
}
public class AnonymousInnerClassExample {
public static void main(String[] args) {
// 使用匿名内部类实现接口
Greeting englishGreeting = new Greeting() {
@Override
public void greet() {
System.out.println("Hello!");
}
};
Greeting spanishGreeting = new Greeting() {
@Override
public void greet() {
System.out.println("Hola!");
}
};
englishGreeting.greet();
spanishGreeting.greet();
// 使用匿名内部类作为方法参数
greetInLanguage(new Greeting() {
@Override
public void greet() {
System.out.println("Bonjour!");
}
});
}
static void greetInLanguage(Greeting greeting) {
greeting.greet();
}
}