山东大学高级程序设计期末复习
Java
不要像笔者一样,大一不好好学习考的烂,大三又在找补
-
在Java中,
%
是取余运算符:System.out.println(-7 % 3); // 输出 -1 System.out.println(7 % -3); // 输出 1
这是因为余数的符号与被除数相同。
-
静态方法中不能有
this
和super
静态方法属于类,而不是实例,因此不能使用
this
和super
,它们只能用于实例方法:public static void staticMethod() {// this 和 super 在这里不可用 }
-
权限修饰符只能修饰成员变量和成员方法,不能修饰局部变量
权限修饰符(如
public
,private
)只适用于类的成员,不能用于局部变量:public void method() {private int a = 5; // 错误,局部变量不能有访问修饰符 }
-
接口中可以有具体的成员方法
在Java 8之后,接口可以有默认方法和静态方法,这些方法可以有实现:
interface MyInterface {default void defaultMethod() {System.out.println("默认方法");} }
-
Java中只允许父类引用指向子类对象
是Java的多态机制,父类引用可以指向子类对象,但反过来不行:
class Parent {} class Child extends Parent {}Parent p = new Child(); // 合法 Child c = new Parent(); // 错误,子类不能指向父类对象
-
将子类对象强转为父类对象
编译可以通过,但父类引用只能调用父类中的方法:
Child c = new Child(); Parent p = (Parent) c; // 强制转换
-
将父类对象强转为子类对象时会报错
因为父类对象没有子类的额外成员,强转会失败:
Parent p = new Parent(); Child c = (Child) p; // 错误,父类对象不能转为子类对象
-
静态方法的重写和多态
静态方法属于类,而不是类的实例,因此静态方法不能像实例方法那样被重写。即使使用多态,静态方法仍然根据引用类型来调用,而不是对象的实际类型。
class Parent {static void show() {System.out.println("Parent show()");} }class Child extends Parent {static void show() {System.out.println("Child show()");} }public class Test {public static void main(String[] args) {Parent p = new Child();p.show(); // 调用 Parent 类的 show(),静态方法是根据引用类型调用的} }
尽管
p
引用的是Child
类型,但因为是静态方法,实际上调用的是父类Parent
的show()
方法。 -
静态方法可以重载
静态方法是可以重载的,因为方法重载是基于方法签名(即方法名和参数类型)来区分的,而与是否是静态方法无关。
-
final
修饰符的使用final
修饰类:表示该类不能被继承。例如,final class MyClass { }
。final
修饰方法:表示该方法不能被重写。final
修饰变量:表示该变量一旦被赋值就不能更改。
final
不能用于修饰抽象类和接口,也不能用于修饰抽象方法。 -
多维数组的初始化
多维数组的第一维必须初始化,而后续维度可以不初始化,但使用时需要初始化。
int[][][] arr = new int[3][][]; arr[0] = new int[2][]; arr[0][0] = new int[5];
-
String str = "CHASD5SH"; int doub = str.charAt(5); System.out.println(doub);
str.charAt(5)
获取的是字符串str
中索引为5
的字符。str = "CHASD5SH"
,所以str.charAt(5)
返回的是字符'5'
。
在 Java 中,字符是基于 Unicode 编码的。字符
'5'
的 Unicode 编码是 53。char
类型实际上是一个整数,表示该字符的 Unicode 编码。因此,charAt(5)
返回的是字符'5'
,将其赋值给int doub
,结果是字符'5'
的整数值 53。 -
String
类的compareTo()
方法返回的值是基于字符的 Unicode 编码值的差异。返回值的具体含义取决于字符串的字典顺序。- 返回值为 0:
- 表示两个字符串相等。
- 比较过程会依次检查两个字符串的每一个字符,直到找到不同的字符或字符串结束。如果没有发现不同,且两个字符串的长度也相同,
compareTo()
返回0
。
- 返回负数(< 0):
- 表示当前字符串小于另一个字符串。
- 如果第一个不同的字符的 Unicode 值在当前字符串中较小,那么返回一个负数。该负数的大小是当前字符与另一个字符串对应字符的 Unicode 值之差。
- 返回正数(> 0):
- 表示当前字符串大于另一个字符串。
- 如果第一个不同的字符的 Unicode 值在当前字符串中较大,那么返回一个正数。该正数的大小是当前字符与另一个字符串对应字符的 Unicode 值之差。
- 返回值为 0:
-
自动包装(自动装箱与拆箱)
自动包装是指Java语言中的基本数据类型与对应的包装类之间的自动转换。例如,
int
类型自动转换为Integer
类,double
自动转换为Double
类,等等。-
自动装箱(Autoboxing):将基本数据类型转换为对应的包装类对象。
例如:
int a = 5; Integer b = a; // 自动装箱:int 转为 Integer
-
自动拆箱(Unboxing):将包装类对象转换为对应的基本数据类型。
例如:
Integer b = 10; int a = b; // 自动拆箱:Integer 转为 int
Java 会自动完成这些转换,因此我们不需要显式地调用构造函数来进行转换。这个机制使得基本数据类型和包装类之间的转换更加简便。
-
-
包的基本概念:
包是类的组织方式,用来组织类文件,以避免类名冲突,并可以实现访问控制。
- 包不是文件的组织方式:
- 在 Java 中,包主要是用来组织类和接口的逻辑分组,并不直接决定文件如何存储或组织。即使类在同一个包中,它们的源文件可能位于不同的目录。
- 包里的类相互使用:
- 同一个包中的类可以直接相互访问,通常不需要导入(
import
)。如果不同包之间的类需要相互访问,就需要使用import
来导入。
- 同一个包中的类可以直接相互访问,通常不需要导入(
- 包的最大好处:
- 避免类名冲突:不同的包中可以有同名的类而不冲突。
- 访问控制:包内的类可以通过访问修饰符(如
public
、private
、protected
)控制访问权限。
- 包不是文件的组织方式:
-
阅读下面异常处理程序,写出执行结果(6分)
public class ExceptionDemo {public static void main(String[] args) {ExceptionTest et=new ExceptionTest(); et.m1(); } } class ExceptionTest{ int i;int[] a = new int[5]; void m1() {try { while (true) {m2();System.out.println(); }}catch (Exception e) {System.out.println(" m1 runs "); }}void m2() throws Exception {try{ System.out.println(10/i);System.out.println(a[i]);}catch (ArithmeticException e) {i = 10;System.out.println("handle ArithmeticException"); }Finally {System.out.println("finally"); }System.out.println("m2 ends");} }
m1
方法包含一个无限循环while (true)
,每次循环都会调用m2
方法。- 如果
m2
抛出异常,m1
会捕获到该异常并输出" m1 runs "
。 m2
方法首先尝试执行10 / i
,然后尝试打印数组a[i]
的值。- 如果发生
ArithmeticException
(例如,i
为 0 时),就会进入catch
块,打印"handle ArithmeticException"
,并将i
设置为 10。 - 无论是否发生异常,
finally
块都会执行,打印"finally"
。 - 在
finally
块之后,m2
会输出"m2 ends"
。
第一次调用
m2()
:i
默认值是0
(因为它是类的实例变量)。10 / i
会导致ArithmeticException
,因为除以 0 会抛出异常。- 进入
catch
块,打印"handle ArithmeticException"
,并将i
设置为10
。 finally
块执行,打印"finally"
。m2 ends
被打印。
第二次调用
m2()
:- 由于
i
已经被修改为10
,执行10 / i
会输出1
。 - 然后尝试访问
a[i]
,即a[10]
,但是数组a
只有 5 个元素,因此会发生ArrayIndexOutOfBoundsException
异常。 - 因为
m2
中没有捕获ArrayIndexOutOfBoundsException
,这个异常会传递给m1
,m1
会捕获到这个异常并输出" m1 runs "
。
m1
捕获异常:- 当
m2
抛出异常时,m1
捕获到异常并输出" m1 runs "
,然后退出。
执行结果:
handle ArithmeticException finally m2 endsm1 runs
对于类的实例变量(如 int i 和 int[] a),它们会自动初始化为默认值:0 和 null。
-
Java 编译过程从源代码(
.java
)开始,通过编译器生成字节码(.class
文件),然后通过 JVM 来运行。 -
错误分为语法错误、运行时错误和逻辑错误,每种错误都有不同的表现和调试方式。
-
要运行 Java 程序,系统需要安装 JDK(用于开发)或者 JRE(仅用于运行)。
-
在 Java 中,变量可以分为 原型类型(基本类型)和 类类型(引用类型):
原型类型(基本类型):
这些类型是 Java 中最基础的类型,它们直接存储值,而不涉及对象的引用。原型类型有 8 种:
- 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 位单精度浮点数
- double:64 位双精度浮点数
- char:16 位,表示一个字符,范围从 ‘\u0000’ 到 ‘\uffff’
- boolean:表示布尔值,只有两个值:
true
或false
类类型(引用类型):
类类型是通过引用来指向对象的,它们可以是任何类类型(如
String
、Object
)以及数组。类类型的变量保存的是对象在内存中的地址,而不是对象本身。 -
标识符的命名规则
标识符是 Java 中用来命名变量、方法、类等的名称,命名时需要遵守一定的规则:
- 首字符:标识符必须以字母(A-Z,a-z)、下划线(
_
)或美元符号($
)开头。- 例如:
_variable
,$value
,myVar
- 例如:
- 后续字符:标识符可以包含字母、数字(0-9)、下划线(
_
)和美元符号($
)。- 例如:
my_var123
,value$
,num_1
- 例如:
- 不能使用 Java 关键字:
- Java 保留了一些关键字(如
int
、class
、if
、else
等),这些关键字不能作为标识符。
- Java 保留了一些关键字(如
- 可以包含
$
:- 标识符中可以使用美元符号(
$
),例如:$value
,$testVar
。但是它通常用于自动生成的代码,不建议在自定义标识符中使用$
。
- 标识符中可以使用美元符号(
- 符号“-”:不可以作为标识符的一部分。
- 例如:
my-variable
是非法的,my_variable
才是合法的。
- 例如:
- 大小写敏感:
- Java 是区分大小写的,即
myVar
和myvar
是两个不同的标识符。
- Java 是区分大小写的,即
Java 区分大小写:
Return
不是关键字,但是因为它首字母大写,可以作为变量名,虽然这不符合 Java 命名习惯。return
是关键字,表示方法的返回语句,不能作为标识符使用。
- 首字符:标识符必须以字母(A-Z,a-z)、下划线(
-
字符串常用方法:
-
length()
:返回字符串的长度(字符数)。String str = "Hello"; System.out.println(str.length()); // 输出 5
-
toLowerCase()
:将字符串转换为小写字母。String str = "HELLO"; System.out.println(str.toLowerCase()); // 输出 "hello"
-
substring()
:提取字符串的子字符串。可以传入起始和结束索引。String str = "Hello"; System.out.println(str.substring(1, 4)); // 输出 "ell" (从索引1到3的字符)
-
replace()
:替换字符串中的字符或子字符串。String str = "Hello World"; System.out.println(str.replace("World", "Java")); // 输出 "Hello Java"
-
charAt()
:返回指定索引位置的字符。String str = "Hello"; System.out.println(str.charAt(1)); // 输出 "e" (索引从 0 开始)
-
toCharArray()
:将字符串转换为字符数组。String str = "Hello"; char[] chars = str.toCharArray(); System.out.println(chars[0]); // 输出 'H'
-
-
比较两个字符串的长度
在 Java 中,比较字符串长度时,需要使用
length()
方法。length()
方法返回字符串的长度(即字符的数量),而不是equals()
方法,后者是用来比较两个字符串的内容是否相等的。 -
String
在 Java 中是 不可变的(immutable),这意味着一旦创建了一个String
对象,它的内容不能被更改。如果你尝试修改字符串,实际上是创建了一个新的字符串对象。 -
import
语句用于引入外部类库或包中的类,以便在程序中使用它们。可以引入整个包,或者只引入某个具体的类。import java.util.Scanner; // 引入 Scanner 类 import java.util.*; // 引入整个 java.util 包
-
int
和Integer
的转换:int
转为Integer
:使用Integer.valueOf()
或自动装箱。Integer
转为int
:使用intValue()
或自动拆箱。
-
Math
类提供了许多常用的数学方法,包括求绝对值、平方根、最大值、最小值等。常用方法:
-
Math.abs()
:返回绝对值。System.out.println(Math.abs(-10)); // 输出 10
-
Math.sqrt()
:返回平方根。System.out.println(Math.sqrt(16)); // 输出 4.0
-
Math.max()
和Math.min()
:返回两个数中的最大值或最小值。System.out.println(Math.max(5, 10)); // 输出 10
-
Math.pow()
:返回一个数的指数幂。System.out.println(Math.pow(2, 3)); // 输出 8.0
-
-
静态变量(Static Variables)
- 定义:静态变量是属于类的,而不是属于类的某个实例。无论创建多少对象,静态变量只有一个副本。
- 初始化:静态变量由 Java 自动初始化,默认值与实例变量相同。
- 访问方式:可以通过类名直接访问静态变量,也可以通过对象引用访问(不推荐)。
class MyClass {static int count = 0; // 静态变量,初始值为 0 }public class Main {public static void main(String[] args) {System.out.println(MyClass.count); // 通过类名访问静态变量MyClass obj1 = new MyClass();MyClass obj2 = new MyClass();System.out.println(obj1.count); // 通过对象引用访问静态变量obj1.count = 5; // 修改静态变量的值System.out.println(obj2.count); // 由于静态变量是类共享的,obj2.count 会输出 5} }
-
方法中的参数:传值与传址
在 Java 中,方法的参数传递有两种方式:传值(Pass-by-Value) 和 传址(Pass-by-Reference)。
原型类型(基本类型)参数:传值
- 定义:原始类型(如
int
,char
,double
等)传递的是值的副本,即在方法调用时,实参的值会复制给形参。方法内部对形参的修改不会影响到实参。 - 特点:Java 中基本类型变量是传值的,传递的是值的副本。
示例:
class Test {public static void changeValue(int x) {x = 100; // 仅修改了 x 的副本}public static void main(String[] args) {int num = 10;changeValue(num);System.out.println(num); // 输出 10,原始变量没有改变} }
- 解释:在
changeValue
方法中,x
是num
的副本。对x
的修改不会影响原始的num
。
对象参数:传址
- 定义:对于对象参数,Java 是通过传递对象的引用来实现的。这意味着方法中操作的是原始对象的引用,因此如果在方法内修改对象的字段(属性),这些更改会影响到原始对象。
- 特点:Java 传递的是对象引用的副本,即传址。但需要注意的是,传递的引用本身是传值的,不能在方法内修改引用指向的对象。
示例:
class Person {String name;Person(String name) {this.name = name;} }class Test {public static void changeName(Person p) {p.name = "John"; // 修改了 p 对象的属性}public static void main(String[] args) {Person person = new Person("Alice");changeName(person);System.out.println(person.name); // 输出 John,原始对象的属性被修改} }
- 解释:
p
是person
对象的引用副本。通过p
修改了对象的属性,影响到了原始的person
对象。
- 定义:原始类型(如
-
矩阵乘法(嵌套循环)
for (int i = 0; i < A行; i++)for (int j = 0; j < B列; j++)for (int k = 0; k < A列; k++)C[i][j] += A[i][k] * B[k][j];
-
满足条件的数筛选(如个位+十位和等于某值)
for (int i = 1000; i <= 1800; i++) {int ge = i % 10;int shi = (i / 10) % 10;if (ge + shi == 9) System.out.println(i); }
-
重载(Overloading)
同一类中允许多个方法同名但参数不同;
参数不同指的是:数量不同、类型不同、顺序不同;
❌ 不能仅通过返回值不同来区分重载!
-
构造函数的访问修饰符不一定必须是
public
,但一般这么写是为了让类可以被外部实例化。 -
选择排序:
public void selectionSort(int[] a) {for (int i = 0; i < a.length - 1; i++) {int minIndex = i;for (int j = i + 1; j < a.length; j++) {if (a[j] < a[minIndex]) {minIndex = j;}}int temp = a[i];a[i] = a[minIndex];a[minIndex] = temp;} }
-
二分查找(数组需先排序):
public int binarySearch(int[] number, int searchValue) {int low = 0, high = number.length - 1;while (low <= high) {int mid = (low + high) / 2;if (number[mid] == searchValue)return mid;else if (number[mid] < searchValue)low = mid + 1;elsehigh = mid - 1;}return -1; }
-
可以写递归形式的二分查找:
public int recursiveBinarySearch(int[] arr, int low, int high, int key) {if (low > high) return -1;int mid = (low + high) / 2;if (arr[mid] == key) return mid;else if (arr[mid] < key) return recursiveBinarySearch(arr, mid + 1, high, key);else return recursiveBinarySearch(arr, low, mid - 1, key); }
-
static + final 的变量是否必须赋初值?
是的,必须。
final static int MAX = 100; // ✅ 必须立刻赋初值
因为
static final
相当于一个 常量(compile-time constant),一旦定义必须马上确定其值。 -
抽象类与抽象方法(abstract)
抽象方法:
- 仅包含方法签名(如:
abstract void draw();
) - 不包含方法体
- 不能是 private、final 或 static(
static
方法属于类本身,而不是类的实例。抽象方法需要被子类实例实现,因此不能是静态的。)
抽象类:
-
可以没有抽象方法
-
如果至少包含一个抽象方法,必须被声明为
abstract
类 -
可以包含非抽象方法,即普通方法、构造方法、属性等
子类中必须实现所有抽象方法,否则该子类也必须声明为 abstract
abstract 可以修饰:
- 类
- 方法(不能修饰属性和构造方法)
- 仅包含方法签名(如:
-
在动态绑定和多态中,Java 运行时会根据对象的实际类型来决定调用哪个方法
示例代码:
class Z {void f(int x) { System.out.println("Z int"); }void f(double x) { System.out.println("Z double"); } }class P extends Z {void f(int x) { System.out.println("P int"); }// void f(double x) { System.out.println("P double"); } // 可能有也可能没有 }public class TestOverride {public static void main(String[] args) {P t = new P(); t.f(10); // P intt.f(10L); // 10L是long,会优先匹配 f(long) → 没有 → 看 f(double)t.f(2.5); // P 或 Z 的 f(double)} }
- 正常情况下(
P t = new P()
)
-
t.f(10)
10
是int
类型,所以会调用P
中的f(int x)
方法。输出:P int
-
t.f(10L)
10L
是long
类型,会先检查P
类中是否有f(long x)
,但是没有找到该方法,所以会去查找Z
类中的f(double x)
。由于long
可以隐式转换为double
,会调用Z
中的f(double x)
。输出:Z double
-
t.f(2.5)
2.5
是double
类型,会首先调用P
类中的f(double x)
,如果P
中没有这个方法,则会调用Z
中的f(double x)
。在当前代码中,P
类没有f(double x)
,所以会调用Z
中的f(double x)
。输出:Z double
- 删除
P
中的f(double x)
方法后
如果删除
P
中的f(double x)
方法,则调用t.f(10L)
和t.f(2.5)
时都会调用Z
中的f(double x)
,因为P
类没有匹配的方法,且long
和double
可以互相隐式转换。-
t.f(10L)
long
类型会隐式转换为double
,所以调用Z
中的f(double x)
方法。输出:Z double
-
t.f(2.5)
直接调用Z
中的f(double x)
方法。输出:Z double
- 修改
main
方法为Z t = new P();
在这种情况下,变量
t
是Z
类型的引用,指向一个P
类型的对象。此时,方法调用仍然是根据实际对象的类型来决定的(即P
类型的对象),但是Z
类中的方法是最先被查找的。-
t.f(10L)
由于t
的类型是Z
,它会首先查找Z
类中的方法。如果Z
中没有f(long x)
,就会向上查找f(double x)
。由于Z
中有f(double x)
,它会调用Z
中的f(double x)
。输出:Z double
-
t.f(2.5)
同样地,t
会首先查找Z
中的f(double x)
,然后调用它。输出:Z double
- 如果
Z
中没有f(double x)
方法
如果
Z
中删除了f(double x)
方法,且P
中也没有f(double x)
方法,那么编译时会报错,因为没有找到匹配的方法。-
t.f(10L)
和t.f(2.5)
如果Z
和P
都没有f(double x)
,则会发生编译错误,因为 Java 无法找到一个合适的方法来匹配long
或double
类型的参数。编译错误:方法 f(double) 在类 Z 中不存在
- 如果
Z
中添加了f(long x)
方法
如果在
Z
中添加了f(long x)
方法,则当调用t.f(10L)
时会调用Z
中的f(long x)
方法。-
t.f(10L)
会执行Z.f(long x)
,因为long
类型会直接匹配Z
中的f(long x)
。输出:Z long
-
t.f(2.5)
仍然会调用Z.f(double x)
,因为double
类型直接匹配Z
中的f(double x)
方法。输出:Z double
- 正常情况下(
-
Java支持单继承 + 多接口实现
class Parent {} interface A {} interface B {}class Child extends Parent implements A, B {}
-
即使
try
和catch
中return
了,也会先执行 finally 再返回值 -
Java 中的异常处理有两种主要方式:
一种是捕获并处理异常(try-catch-finally):
这是最常见的方式,如果你知道如何处理某种异常,就在方法中使用
try-catch
:try {int a = 10 / 0; // 可能抛出 ArithmeticException } catch (ArithmeticException e) {System.out.println("发生算术异常:" + e.getMessage()); } finally {System.out.println("无论是否发生异常,我都会执行"); }
另一种是声明异常并抛出(throws / throw):
如果你不打算在当前方法中处理异常,就使用
throws
把异常交给调用者去处理(声明),或用throw
主动抛出异常对象:声明异常:
public void readFile(String path) throws IOException {FileReader reader = new FileReader(path); // 可能抛出IOException }
主动抛出异常:
public void checkAge(int age) {if (age < 18) {throw new IllegalArgumentException("未满18岁不允许注册");} }
❗注意:
- try/catch 是处理异常:你知道这个异常可能发生,并且你有办法应对。
- throws 是把异常交给别人处理:你暂时无能为力,就把锅甩给上层调用者。
- 两者可以结合使用:一个方法内部
try/catch
处理部分异常,剩下的用throws
向外声明。
-
链表基本结构回顾
-
ListNode 类(结点类)
public class ListNode {private String data;private ListNode link;public ListNode(String data, ListNode link) {this.data = data;this.link = link;}public String getData() { return data; }public ListNode getLink() { return link; }public void setData(String data) { this.data = data; }public void setLink(ListNode link) { this.link = link; } }
-
StringLinkedList 类中的常用方法实现
length()
public int length() {int count = 0;ListNode position = head;while (position != null) {count++;position = position.getLink();}return count; }
addANodeToStart(String addData)
public void addANodeToStart(String addData) {head = new ListNode(addData, head); }
deleteHeadNode()
public void deleteHeadNode() {if (head != null)head = head.getLink(); }
find(String target)
public boolean find(String target) {ListNode position = head;while (position != null) {if (position.getData().equals(target))return true;position = position.getLink();}return false; }
showList()
public void showList() {ListNode position = head;while (position != null) {System.out.println(position.getData());position = position.getLink();} }
-
将链表所有数据转入
ArrayList
:ArrayList<String> list = new ArrayList<>(); ListNode position = head; while (position != null) {list.add(position.getData());position = position.getLink(); }
-
插入新节点:
public void insertAfter(String target, String newData) {ListNode position = head;while (position != null) {if (position.getData().equals(target)) {ListNode newNode = new ListNode(newData, position.getLink());position.setLink(newNode);return;}position = position.getLink();} }
-
删除目标节点后面的节点:
public void deleteAfter(String target) {ListNode position = head;while (position != null) {if (position.getData().equals(target) && position.getLink() != null) {position.setLink(position.getLink().getLink());return;}position = position.getLink();} }
-
-
基类方法为public int a(int x, int y); 派生类方法为public void a(int x, int y); 可以这样实现 覆盖方法吗?
答: 不可以. 编译将报错,因为这既不是方法的重载(译文版教材: 无法根据返回值的类型来实现重载), 也不是方法的覆盖(方法名之前的修饰符要完全一致, 方法名之后的参数数目, 类型要完全一样 )
-
若在派生类中自己编写缺省构造函数, 而在里面又只初始化了部分的实例变量, 那么另外的实例变量的值是否还被自动初始化?
答: 现在版本的java仍将对缺省构造函数中未能涉及到的实例变量进行初始化
-
构造函数与一般的方法有何区别?
- 专门用来在创建对象时进行初始化.
- 构造函数与所属的类同名.
- 没有返回值类型, 函数头不带关键字void.
- 构造函数不可以有继承关系.
-
表达式“i=4; i+++i+++i++; ”的值是多少 ?
答:结果为15; 上式可以理解为(i++)+(i++)+(i++), 即: 4+5+6=15; 因为++的优先级比+高,java编译器将首先组织优先级较高的运算符组成表达式.
2021-2022学年一学期高级程序设计课程试卷A
-
Java源文件命名时大小写不敏感
-
一个Java程序可以编译成多个class文件
-
抽象类不一定**非得有抽象方法,例如为了防止创建对象,也可定义成 abstract。
-
Java 不允许在类型声明中 指定数组的大小。也就是说:
int[3] a = {1, 2, 3}; // ❌ 错误:不能写 int[3]
-
正确的 Java 数组声明方式是:
方式 1:自动推断长度
int[] a = {1, 2, 3}; // 正确,长度自动为 3
方式 2:使用 new 显式声明并初始化
int[] a = new int[]{1, 2, 3}; // 正确
方式 3:只声明长度,稍后赋值
int[] a = new int[3]; // 创建长度为 3 的数组,初始值全为 0 a[0] = 1; a[1] = 2; a[2] = 3;
-
-
重写(override)时,返回类型必须相同(或协变返回类型)重载是通过参数列表的不同(个数、顺序或类型)来区分的,返回值类型不同不能作为重载的唯一依据。
-
输出结果是什么?
public class Test {int a[] = new int[10];public static void main ( String arg[] ) {System.out.println ( a[6] );} }
正确答案:C. 会出现编译错误
解析:
-
变量
a
是一个非静态成员变量,但main
方法是static
的。 -
在静态方法中不能直接访问非静态成员,所以
a[6]
会导致编译错误。 -
正确写法应该是:
public static void main(String[] args) {Test t = new Test();System.out.println(t.a[6]); // 输出 0 }
-
-
this
表示当前对象的引用。this
只能在 非 static 方法或构造方法中使用,不能在 static 方法中使用。this
是和具体对象绑定的,不是和类绑定。 -
关于 throws 和 throw 的说法错误的是?
选项:
- A. 可以使用 throw 抛出自定义异常
- B. throw 用于方法中手动抛异常,throws 用于声明可能发生的异常
- C. throw 抛出的异常,可以不进行处理
- D. 用 throw 时是说明知道方法中可能有异常但不想马上处理,而希望抛出给上级处理
原因分析:
- A ✅ 正确:你可以定义一个异常类,比如
MyException extends Exception
,然后用throw new MyException();
抛出。 - B ✅ 正确:这是基本定义,没问题。
- C ❌ 错误:如果你用
throw
抛出的是受检异常(checked exception),必须处理(try-catch)或声明(throws)。否则编译报错。这条语句是不准确的。 - D ❌ 错误:因为:
throw
是用来“抛出”异常的,它不能“声明”你不想处理,而是你“现在就要抛”!- 如果你想表达“我不想处理这个异常,扔给别人”,那应该用的是
throws
,而不是throw
。
深入解析 Java 中的 throw 和 throws:异常处理机制的核心_java throw-CSDN博客
-
public class T22A {public static void main(String\[] args) {String\[] codes = { "2021", "202b","202" };try {for (int i = 0; i <= codes.length; i++) {try {System.out.println("Analyzing: " + codes\[i]);analyzeCode(codes\[i]);} catch (NumberFormatException e) {System.out.println("Not a Number: " + codes\[i]);} finally{System.out.println("\*\*\*\*\*\*\*\*");}}} catch (Exception e) {System.out.println("Unknown Exception ");}System.out.println("Analyze Completed ");}public static void analyzeCode(String code) throws Exception {if (code.length() != 4) throw new Exception();System.out.println("successful : " + Integer.parseInt(code));} }
运行结果:
Analyzing: 2021 successful : 2021 ******** Analyzing: 202b Not a Number: 202b ******** Analyzing: 202 Unknown Exception Analyze Completed
- 第一次循环 (i=0):
- 处理
codes[0] = "2021"
。 analyzeCode
检查长度合法(4),并成功解析为整数,输出successful : 2021
。finally
块执行,输出*********
。
- 处理
- 第二次循环 (i=1):
- 处理
codes[1] = "202b"
。 analyzeCode
检查长度合法(4),但解析时遇到非数字字符b
,抛出NumberFormatException
。- 内部
catch
捕获异常,输出Not a Number: 202b
。 finally
块执行,输出*********
。
- 处理
- 第三次循环 (i=2):
- 处理
codes[2] = "202"
。 analyzeCode
检查长度不合法(3),直接抛出Exception
。- 内部
try
块没有匹配的catch
,异常传递到外层。 - 执行
finally
块,输出*********
。 - 外层
catch
捕获通用Exception
,输出Unknown Exception
。
- 处理
- 循环终止与后续流程:
- 外层异常处理后,循环提前终止(不会执行
i=3
,避免数组越界)。 - 最后输出
Analyze Completed
。
- 外层异常处理后,循环提前终止(不会执行
第三次循环(i=2)发生了什么?
- 处理
codes[2] = "202"
,调用analyzeCode("202")
。 analyzeCode
检查code.length() != 4
,发现长度为 3,直接抛出Exception
。- 内部
try-catch
没有捕获Exception
(它只捕获NumberFormatException
),所以异常会向外层传播。 - 异常传播到外层的
try-catch
(main
方法的try
块),被catch (Exception e)
捕获,输出Unknown Exception
。 - 此时,整个外层
try
块会立即终止,包括其中的for
循环。(因为try只按顺序执行一次,外面的try执行完了,里面的for循环自然就不执行了)
- 第一次循环 (i=0):
山东大学 2021-2022 学年 一 学期 高级程序设计语言 课程试卷B
-
在一个 Java 源文件中,只能有一个
public class
。如果尝试在一个源文件中定义多个public class
,编译器会报错。因为 Java 的设计规范规定,每个源文件最多只能有一个公共类,并且源文件的名称必须与该公共类的名称完全相同。不过,可以在一个源文件中定义多个非public class
,这些类可以用private
、protected
或默认的访问修饰符。 -
stu&sdu
- 标识符中不能包含
&
这样的符号。&
用于位运算或表示参数化类型的交界类型等,不是用于组成标识符的有效字符。
- 标识符中不能包含
-
在 Java 中,字符串的长度是通过
length()
方法来获取的,而数组的长度是通过直接访问length
属性(而不是方法)来获取的。例如,对于字符串String str = "hello";
,可以用str.length()
获取长度;对于数组int[] arr = new int[5];
,则通过arr.length
获取长度 -
在 Java 中,二维数组的声明格式为
int a[][]
或int[] a[]
或int[][] a
。正确的初始化方式是int a[][] = { {1,2}, {3,4}, {5,6} };
或者int a[][] = new int[3][2];
然后给数组元素赋值。选项 A 中的[3][2]
放在数组名后面是不正确的,应该将new
关键字放在数组声明前面,例如int a[][] = new int[3][2];
或者直接初始化为具体的值。 -
static
不能用于修饰局部变量(即在方法内部定义的变量)。 -
public class T23B {public static void main(String[] args){new B().m(2022);new A(2021).m(2020);} } class B{public B(){System.out.println("Init B: ");}public void m(int x){ f(g(x)); }public void f(int x){System.out.println("\tf() in B");g(x);}private int g(int x){System.out.println("\tg(int) in B");return x;} } class A extends B {public A(int x){System.out.println("Init A: " + x);}public void f(int x){System.out.println("\tf() in A");g(x);}public int g(int d){System.out.println("\tg(int) in A");return (int)d;} }
运行结果
Init B: g(int) in Bf() in Bg(int) in B Init B: Init A: 2021g(int) in B//这个很奇怪f() in Ag(int) in A
- new A(2021).m(2020)。这里创建了一个A类的实例,构造函数的参数是2021。A类继承自B类,所以首先要调用父类B的构造函数。B的构造函数会打印"Init B: “。然后执行A类的构造函数,打印"Init A: 2021”。接着,调用m方法,参数是2020。注意,A类并没有覆盖m方法,所以调用的是从B类继承来的m方法。在m方法中,同样执行f(g(x))。这里的x是2020,先调用g(x)。但是,在A类中是否存在g方法?因为B类的g方法是private的,A类中的g方法是public的,并且参数类型是int,所以这是一个新的方法,而不是覆盖。但是,在B类的m方法中调用g(x),由于m方法是定义在B类中的,而g是private的,所以即使是在A类中调用m方法,这里的g(x)仍然是指B类的g方法吗?或者因为A类继承了B类的m方法,而在m方法中的g(x)调用是否会被动态绑定到A类的g方法?
- 这里可能存在理解上的误区。因为B类的g方法是private的,所以它只能在B类内部访问,子类A是无法继承或覆盖这个方法的。因此,在A类中定义的g方法是一个全新的方法,与B类的g方法无关。当在B类的m方法中调用g(x)时,由于m方法属于B类,所以这里的g(x)指的是B类的private方法,而不是A类的g方法。因此,当执行new A(2021).m(2020)时,m方法中的g(x)仍然是调用B类的g方法,而之后调用的f方法则是根据实际对象类型来决定的,因为f方法是public的,且A类覆盖了f方法。
-
如果一个数等于它的因子之和,则称该数为“完数”(或“完全数”)。例如,28的因子为1、2、4、7、14,而 28=1+2+4+7+14,因此28是“完数”。写一个程序输出10000以内的所有完数。(8分)
public class PerfectNumber {public static void main(String[] args) {// 输出10000以内的完数for (int i = 1; i <= 10000; i++) {if (isPerfect(i)) {System.out.println(i);}}}// 判断一个数是否为完数public static boolean isPerfect(int number) {int sumOfDivisors = 0;// 计算该数的所有真因子之和for (int i = 1; i <= number / 2; i++) {if (number % i == 0) {sumOfDivisors += i;}}// 如果真因子之和等于该数,则返回truereturn sumOfDivisors == number;} }
-
输入两个小于1~1000位的超大整数,输出它们的和(注意:因为位数很大,会超过java的整数范围,直接用整数输入是不合适的)。(10分)样例: 输入: 917654321454365454445435 87654321987654321987654321 输出:87746087419799758532099756(以上两数相加)
import java.math.BigInteger; import java.util.Scanner;public class LargeNumberAddition {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);// 读取两个大整数System.out.println("请输入第一个超大整数:");BigInteger num1 = new BigInteger(scanner.nextLine()); // 读取第一个数字System.out.println("请输入第二个超大整数:");BigInteger num2 = new BigInteger(scanner.nextLine()); // 读取第二个数字// 计算它们的和BigInteger sum = num1.add(num2);// 输出结果System.out.println("它们的和是:" + sum);} }
-
返回链表中最大值出现的次数
public int maxCount() {if (head == null) return 0; // 如果链表为空,直接返回 0int count = 1; // 初始化计数器,最大值出现次数ListNode maxNode = head; // 设置最大节点为头节点int maxValue = maxNode.data; // 获取头节点的数据作为初始最大值maxNode = head.link; // 移动到下一个节点// 遍历链表while (maxNode != null) {if (maxNode.data > maxValue) { // 如果发现更大的值maxValue = maxNode.data; // 更新最大值count = 1; // 重置计数器为 1,因为找到了新的最大值} else if (maxNode.data == maxValue) { // 如果找到了相等的最大值count++; // 计数器加 1}maxNode = maxNode.link; // 继续遍历下一个节点}return count; // 返回最大值出现的次数 }
-
返回链表倒数第
k
个节点public ListNode findKthToTail(int k) {// 快慢指针法ListNode fast = head;ListNode slow = head;// 移动 fast 指针,前进 k 步for (int i = 0; i < k; i++) {if (fast == null) {return null; // 如果 k 大于链表长度,返回 null}fast = fast.link;}// 快指针和慢指针同时移动,直到快指针到达末尾while (fast != null) {fast = fast.link;slow = slow.link;}return slow; // slow 即为倒数第 k 个节点 }
山东大学 2023-2024 学年 一 学期 高级程序设计语言 课程试卷A
-
方法的返回值可以是基本数据类型,也可以是对象类型,包括自定义类的实例。
-
int
是基本数据类型,不能直接调用equals()
方法 -
continue
语句用于终止当前循环的迭代,跳过本次循环中剩余的代码,然后继续下一次循环迭代。 -
并非程序中所有可能出现的异常都必须在
catch
块中捕获。非检查异常(如RuntimeException
及其子类)不需要显式捕获,而检查异常需要要么被捕获,要么用throws
声明。 -
在方法定义中使用
throws
关键字声明可能出现的异常,即使在方法调用时异常没有实际抛出,也不会影响代码的编译。throws
声明用于告知调用者该方法可能会抛出的异常类型。 -
class Animal{public String name;public Animal(String s){name = s; }public void move(){System.out.println(this + "在运动");}public String toString(){return "动物" + name;} } class Bird extends Animal{public Bird(String s){super(s); }public void move(){System.out.println(this + "在飞行");}public boolean equals(Object other){System.out.print(name + " Object 判断相等 ");return super.equals(other);}public boolean equals(Bird other){System.out.print(name + " Bird 判断相等 ");return true;}public String toString(){return "小鸟" + name;} } public class T24A {public static void main(String\[] args){Bird\[] blist = {new Bird("麻雀"),new Bird("鸿鹄")};Animal\[] alist = {new Animal("大黄"), blist\[0],blist\[1]};for (Animal a : alist) a.move();System.out.println(alist\[0].equals(alist\[2]));;System.out.println(alist\[1].equals(blist\[0]));System.out.println(blist\[1].equals(blist\[0]));} }
答案: 动物大黄在运动 小鸟麻雀在飞行 小鸟鸿鹄在飞行 false 麻雀 Object 判断相等 true 鸿鹄 Bird 判断相等 true
编译时,根据引用类型,去对应的类中找参数列表符合的方法,锁定该方法;运行时,根据实际类型,去对应的子类中找刚刚锁定的方法的重写方法,没有就用刚刚的。
-
n个猴子围成一圈选大王,依次1-7循环报数,报到7的猴子被淘汰,直到最后一只猴子称为大王,写一个方法public static int monkeyKing(int n),返回第几只猴子会成为大王?(10分)
public static int monkeyKing(int n) {boolean[] b=new boolean[n];for(int i=0;i<b.length;i++){b[i]=true;}int num=0;int monkeyLeft=n;int index=0;while(monkeyLeft>1){if(b[index]){num++;if(num==7){b[index]=false;monkeyLeft--;num=0;}}index++;if(index==n){index=0;}}//遍历数组,找到最后活着的那个猴子王for(int i=0;i<b.length;i++){if(b[i]){return i+1;}}return 0;}
-
public void removeRep ( ){ // 删除链表中值重复出现的节点 // 如假设当前链表为1->2->4->4->3->3->2->1->1, // 删除值重复的节点之后为1->2->4->3}
public void removeRep ( ){ListNode cur=head;ListNode pre=null,next=null;while(cur!=null){pre=cur;next=cur.link;while(next!=null){if (cur.data==next.data)pre.link=next.link;elsepre=next;next=next.link;}cur=cur.link;}}
public void removeRep() {Set<Integer> seen = new HashSet<>(); // 用于记录已经遇到过的值ListNode current = head;ListNode previous = null;while (current != null) {if (seen.contains(current.data)) { // 如果值已出现过,删除节点previous.link = current.link; // 跳过当前节点} else {seen.add(current.data); // 记录当前节点的值previous = current; // 继续前进}current = current.link; // 移动到下一个节点} }
-
public void reversePart ( int from , int to ) { //在链表中把第from个节点到第to个节点这一部分进行反转,假设 0<from < to < length// 如假设目前链表为1->2->3->4->5->null,from=2,to=4// 调整结果为1->4->3->2->5 //注意不使用额外的链表、堆栈等结构,不能填写到数组中,操作后再创建链表
public void reversePart(int from, int to) {if (from >= to) return; // 输入参数非法(from < to)ListNode dummy = new ListNode(); // 创建一个虚拟头节点,简化边界情况的处理dummy.link = head;ListNode preFrom = dummy; // preFrom 指向 from 前一个节点// 找到 from 的前一个节点for (int i = 0; i < from - 1; i++) {preFrom = preFrom.link;}ListNode fromNode = preFrom.link; // 从第 from 个节点开始ListNode toNode = fromNode;for (int i = from; i < to; i++) {toNode = toNode.link;}// 保存 toNode 后面的节点ListNode nextToNode = toNode.link;// 将从 from 到 to 的部分反转ListNode prev = null;ListNode current = fromNode;while (current != nextToNode) {ListNode next = current.link;current.link = prev;prev = current;current = next;}// 将反转后的链表重新连接preFrom.link = prev;fromNode.link = nextToNode;if (from == 1) {head = prev; // 如果反转的是从头开始的部分,更新头节点} }
山东大学 2023-2024 学年 一 学期 高级程序设计语言 课程试卷B
-
package
语句定义的包名必须与文件的目录结构一致。例如,如果代码中使用package com.example;
,那么该文件必须位于com/example
目录下。 -
this
关键字只能在实例方法中使用,因为它指向当前对象。而main
方法是静态的,静态方法是与类相关的,而不是与某个特定的对象相关,因此不能在main
方法中使用this
。 -
abstract
方法不能被声明为static
,因为abstract
方法需要被子类实现,而static
方法属于类本身,不能被子类重写。 -
static
方法不能直接访问非static
属性。因为static
方法属于类而不是对象,非static
属性属于对象实例,static
方法无法直接访问对象实例的非静态属性。 -
Object
类定义了许多通用方法,如toString()
、equals()
等。继承自Object
的类不需要强制重写toString()
和equals()
方法,但通常建议重写以提供更有用的实现。 -
使用
final
定义的变量必须在声明时或者在构造方法中初始化 -
abstract class QAnimal{public int distance = 0;int id;static int total = 0;public QAnimal(){id = total++; }public void move(int d){ System.out.println(this + "方式:运动");}public String toString(){return "运动距离" + distance+",";} } class QBird extends QAnimal{public void move(int d){distance = d;System.out.println(this + "方式:飞行");}public String toString(){return id + "号小鸟" + super.toString();} } class QTurtle extends QAnimal{public void move(double s){distance += (int)s;System.out.println(this);}public String toString(){return id + "号海龟" + super.toString();} } public class T24B {public static void main(String[] args){QBird[] blist = {new QBird(),new QBird()};QTurtle t = new QTurtle();QAnimal[] alist = {blist[0],t,blist[1]}; for (QAnimal a : alist) a.move(100);t.move(15.0); } }
答案:(打印this会调用tostring)
0号小鸟运动距离100,方式:飞行 2号海龟运动距离0,方式:运动 1号小鸟运动距离100,方式:飞行 2号海龟运动距离15,
- 循环中每个元素调用move(100)。这里要注意,QAnimal的move方法参数是int,而QTurtle的move方法参数是double,所以在多态调用时,如果实际类型是QTurtle,会调用哪个方法?
- 因为QAnimal的move是public void move(int d),而QTurtle有一个public void move(double s),这并没有覆盖父类的方法,而是重载。因此,当通过QAnimal引用调用move(100)时,由于编译时类型是QAnimal,会调用QAnimal的move方法,而不是QTurtle的move(double)。因此,当处理到alist中的t(QTurtle实例)时,实际上调用的是QAnimal中的move方法,即打印“运动方式”,而distance没有被修改,保持0。而QBird实例覆盖了move(int),所以它们的distance被设置为100,并打印飞行方式。
- 接下来,执行t.move(15.0),这里参数是double,所以调用的是QTurtle自己的move方法,distance增加15,变成15,并调用toString打印。
山东大学 2024-2025 学年 一 学期 高级程序设计语言 课程试卷A
递归的某些读程序题关键点在于读懂方法的作用,层层递归太多的话很容易弄错,也容易弄不完
-
public static
和static public
是等价的,但通常习惯写成public static
。 -
main
方法必须声明为public static void main
,并且接受一个字符串数组作为参数。 -
接口不继承
Object
类,但实现接口的类继承Object
类。 -
如果类没有重写
toString()
方法,则使用Object
类的toString()
方法,默认返回对象的内存地址。 -
写一个高精度减法,方法头如下:public static String sub(String a, String b) ,其中a,b分别为被减数和减数,要求返回一个字符串表示的结果。如:sub(“123456789”,“1”) 返回为“123456788”;sub(“123456788”,“123456789”)返回为“-1”。需要注意:数字位数可能比较多。不能使用现成的如BigInteger、BigDecimal类实现(10分)
public class HighPrecisionSubtraction {public static String sub(String a, String b) {// 判断a, b哪个大,若a < b, 结果为负数boolean isNegative = false;if (compare(a, b) < 0) {// 交换a和b,保证a >= bString temp = a;a = b;b = temp;isNegative = true;}// 将a和b填充至相同的长度int lenA = a.length();int lenB = b.length();int maxLen = Math.max(lenA, lenB);a = padLeft(a, maxLen);b = padLeft(b, maxLen);// 结果的存储,最大可能需要lenA+1位StringBuilder result = new StringBuilder();int borrow = 0;// 从低位到高位进行逐位减法for (int i = maxLen - 1; i >= 0; i--) {int digitA = a.charAt(i) - '0'; // 被减数的当前位int digitB = b.charAt(i) - '0'; // 减数的当前位// 进行减法,考虑借位int diff = digitA - digitB - borrow;if (diff < 0) {diff += 10;borrow = 1; // 发生借位} else {borrow = 0;}result.append(diff);}// 反转结果,去掉前导零result.reverse();// 去掉可能出现的前导零while (result.length() > 1 && result.charAt(0) == '0') {result.deleteCharAt(0);}// 如果结果是负数,添加负号if (isNegative) {result.insert(0, '-');}return result.toString();}// 比较两个字符串表示的数字,返回负数(a < b)、零(a == b)或正数(a > b)private static int compare(String a, String b) {// 先比较长度,长度较大的数字较大if (a.length() > b.length()) return 1;if (a.length() < b.length()) return -1;// 长度相同,逐位比较for (int i = 0; i < a.length(); i++) {if (a.charAt(i) > b.charAt(i)) return 1;if (a.charAt(i) < b.charAt(i)) return -1;}return 0; // 相等}// 将数字字符串填充至指定长度,前面补0private static String padLeft(String str, int length) {StringBuilder sb = new StringBuilder();for (int i = 0; i < length - str.length(); i++) {sb.append('0');}sb.append(str);return sb.toString();}// 测试public static void main(String[] args) {System.out.println(sub("123456789", "1")); // 123456788System.out.println(sub("123456788", "123456789")); // -1System.out.println(sub("500", "200")); // 300System.out.println(sub("1000", "999")); // 1System.out.println(sub("5000", "10000")); // -5000} }
-
deleteDuplicates
方法此方法删除链表中所有重复的节点,使得每个节点的值只出现一次。
public ListNode deleteDuplicates(ListNode head) {if (head == null) {return null; // 如果链表为空,直接返回}ListNode current = head;while (current != null) {ListNode runner = current;// 删除当前节点之后所有重复的节点while (runner.next != null) {if (runner.next.data == current.data) {runner.next = runner.next.next; // 删除重复节点} else {runner = runner.next; // 继续查找}}current = current.next; // 继续处理下一个节点}return head; }
-
hasCycle
方法此方法判断链表中是否存在环。如果链表中存在环,返回
true
;否则,返回false
。public boolean hasCycle(ListNode head) {if (head == null || head.next == null) {return false; // 链表为空或只有一个节点,肯定没有环}ListNode slow = head;ListNode fast = head;while (fast != null && fast.next != null) {slow = slow.next; // 慢指针每次走一步fast = fast.next.next; // 快指针每次走两步if (slow == fast) {return true; // 快慢指针相遇,说明链表有环}}return false; // 快指针走到链表末尾,没有环 }
山东大学 2024-2025 学年 一 学期 高级程序设计语言 课程试卷B
class Element {private String s;public Element(String text) { s = text; }public String toString() { return s; }
}
class B extends Element {protected final Element sub;public B(Element e) { super(""); sub = e; }public String toString() {return "<B>" + sub.toString() + "</B>";}
}
class I extends B {public I(Element e) { super(e); }public String toString() {return "<I>" + sub.toString() + "</I>";}
}
public class T24 {public static void main(String[] args) {
Element e = new Element("OK"), i = new I(e), b = new B(i);System.out.println( e +"\n" + i +"\n" + b +"\n" + new I(b)); }
}
答案:
OK
<I>OK</I>
<B><I>OK</I></B>
<I><B><I>OK</I></B></I>
- 注意看,把前面的对象放进去了,别粗心
山东大学 2018-2019 学年 一 学期 高级程序设计语言 课程试卷
- 在 Java 中,小数默认是
double
类型而不是float
类型 - “r” 是字符串(String),而 char 类型应该用单引号,如
char ca = 'r'
;