Framist's Little House

◇ 自顶而下 - 面向未来 ◇

0%

【Java】Java 期末考复习

Java 复习

2020 秋学期高级语言程序设计复习要点

复习范围:

  1. 本课程课堂讲授的所有课件的内容
  2. 本课程在线学时中的以下内容:
    1. 容器类的所有内容;
    2. 常用预定义类中的字符串操作、Wrapper 类

本次考试的一些重点内容:

  1. 方法参数的传递
  2. 子类对象的创建与初始化
  3. 动态绑定(非动态绑定的情况)
  4. 成员变量的隐藏
  5. 静态变量的创建过程(与子类对象创建过程结合)
  6. 静态方法的重写规则
  7. 对象串行化
  8. 字符串的操作与比较

本课程课堂讲授的所有课件的内容

修饰符 当前类 同一包内 子孙类 (同一包) 子孙类 (不同包) 其他包
public Y Y Y Y Y
protected Y Y Y Y/N(说明 N
default Y Y Y N N
private Y N N N N

protected 需要从以下两个点来分析说明:

  • 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
  • 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的 protected 方法。

容器类

泛型的基本概念

泛型:又称为参数化类型,通过定义含有一个或多个类型参数的
类或接口,对具有类似特征与行为的类进行抽象

类型参数:可以指代任何具体类型
例:JDK 中 java.util.ArrayList<E>的定义,ArrayList<E>定义了
一个泛型,E 为类型参数,它代表了能够放入 ArrayList 中的对象的

类型

如何使用一个预定义的泛型?
对泛型中的类型参数进行具体化,即可得到具体的类

例:如果希望创建一个能够存放 String 对象的 ArrayList, 应使用
ArrayList<String>进行声明。ArrayList<String>总是可以被看作
一个具体的类,该类的实例作为容器可以存放 String 类型的对象

容器类

一个容器类的实例(容器、容器对象)表示了一组对象,
容器对象存放指向其他对象的引用

Set

三种接口实现:HashSet, TreeSet, LinkedHashSet

三种接口实现:HashSet, TreeSet, LinkedHashSet

HashSet:采用 Hash 表实现 Set 接口,元素无固定顺序,对元素的
访问效率高

TreeSet:实现了 SortedSet 接口,采用有序树结构存储集合元素,
元素按照比较结果的升序排列

LinkedHashSet:采用 Hash 表和链表结合的方式实现 Set 接口,元
素按照被添加的先后顺序保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.*;

public class TestSet {
public static void main(String[] args) {
Random rand = new Random(47);
Set<Integer> s = new HashSet<Integer>();
for (int i = 0; i < 5000; i++) {
s.add(rand.nextInt(40));
}
System.out.println(s);

s = new TreeSet<Integer>();
for (int i = 0; i < 5000; i++) {
s.add(rand.nextInt(40));
}
System.out.println(s);

s = new LinkedHashSet<Integer>();
for (int i = 0; i < 5000; i++) {
s.add(rand.nextInt(40));
}
System.out.println(s);
}
}
1
2
3
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
[28, 15, 39, 14, 16, 29, 24, 37, 6, 25, 2, 1, 32, 3, 36, 38, 5, 22, 23, 7, 8, 21, 33, 17, 18, 30, 31, 0, 27, 4, 19, 9, 12, 35, 34, 10, 26, 20, 13, 11]
  • HashSet 为什么最后输出又是有序的?参考

当循环次数改小了之后,又是无序的了:

1
2
3
4
5
6
Random rand = new Random(47);
Set<Integer> s = new HashSet<Integer>();
for (int i = 0; i < 10; i++) {
s.add(rand.nextInt(40));
}
System.out.println(s);
1
[0, 1, 18, 2, 35, 21, 7, 28, 13, 29]
  • equals() 方法与对象等价性

如何测试对象的等价性?

“==”和“!=”比较的是对象的引用而非对象的内容

(引用相等:指向同一块堆空间)

所有对象都拥有从 Object 类继承的 equals() 方法,

equals() 方法默认也是比较对象的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Value {
int i;

public boolean equals(Value v) {
return (this.i == v.i) ? true : false;
}
}

public class Equivalence {
public static void main(String[] args) {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println("n1==n2: " + (n1 == n2)); // 对引用的比较
System.out.println("n1!=n2: " + (n1 != n2));
System.out.println("n1.equals(n2): " + n1.equals(n2));// Integer 已重写 equals()
Value v1 = new Value();
Value v2 = new Value();
v1.i = v2.i = 10;
System.out.println("v1.equals(v2): " + v1.equals(v2));
}
}
1
2
3
4
n1==n2: true
n1!=n2: false
n1.equals(n2): true
v1.equals(v2): true

image-20201129170004250

List

两种接口实现:ArrayList, LinkedList

ArrayList:采用可变大小的数组实现 List 接口
无需声明上限,随着元素的增加,长度自动增加
对元素的随机访问速度快,插入/移除元素较慢
该类是非同步的,相对于 Vector(legacy)效率高

LinkedList:采用链表结构实现 List 接口
实际上实现了 List 接口、Queue 接口和双端队列 Deque 接口,
因此可用来实现堆栈、队列或双端队列
插入/移除元素快,对元素的随机访问较慢
该类是非同步的

Queue

两种接口实现:LinkedList, PriorityQueue

Map

接口实现:HashMap, TreeMap, LinkedHashMap

把键映射到某个值
一个键最多只能映射一个值
一个值可对应多个键

接口实现:HashMap, TreeMap, LinkedHashMap
把键映射到某个值
一个键最多只能映射一个值
一个值可对应多个键
常用:HashMap(无序)和 TreeMap(有序)
HashMap:使用 Hash 表实现 Map 接口
无序,非同步且允许空的键与值
相比 Hashtable(legacy)效率高
TreeMap:与 TreeSet 类似,使用有序树实现 SortedMap 接口
保证“键”始终处于排序状态

迭代器 (Iterator)

Iterator 是一个轻量级对象,用于遍历并选择序列中的对象

用法

  • 使用容器的 iterator() 方法返回容器的迭代器,该迭代器准备返回容器的第一个元素
  • 迭代器只能单向移动
  • next():获得序列的下一个元素
  • hasNext():检查序列中是否还有元素
  • remove():将迭代器新近返回的元素(即由 next() 产生的最后一个
  • 元素)删除,因此在调用 remove() 之前必须先调用 next()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.*;

public class TestIterator {
public static void main(String[] args) {
String sentence = "I believe I can fly, I believe I can touch the sky.";
String[] strs = sentence.split(" ");
List<String> list = new ArrayList<String>(Arrays.asList(strs));
Iterator<String> it = list.iterator();

while (it.hasNext())
System.out.print(it.next() + "_");
System.out.println();

it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("I"))
it.remove();
}

it = list.iterator();
while (it.hasNext())
System.out.print(it.next() + " ");
System.out.println();
}
}
1
2
I_believe_I_can_fly,_I_believe_I_can_touch_the_sky._
believe can fly, believe can touch the sky.

Wrapper 类

Wrapper 类实例的构造:将基本数据类型值传递给 Wrapper 类的
构造方法
Wrapper 类的常用方法和常量
数值型 Wrapper 类中的 MIN_VALUE, MAX_VALUE
byteValue()/shortValue()/longValue(), … :
将当前 Wrapper 类型的值作为 byte/short/long/…返回
valueOf():将字符串转换为 Wrapper 类型的实例
toString():将基本类型值转换为字符串
parseByte()/parseShort()/parseInt(), … :
将字符串转换为 byte/short/int/…类型值

Autoboxing:在应该使用对象的地方使用基本类型的数据时,
编译器自动将该数据包装为对应的 Wrapper 类对象
Autounboxing:在应该使用基本类型数据的地方使用 Wrapper
类的对象时,编译器自动从 Wrapper 类对象中取出所包含的基
本类型数据

image-20201129164210111


方法参数的传递

将方法参数指明为 final,则无法在方法中更改参数的值

final 的使用位置
在类声明中使用:表示类不能被继承
在成员方法声明及方法参数中使用:成员方法不能被重写,
参数变量值不能更改
在成员变量和局部变量声明中使用:表示变量的值不能更改

被定义成 final 的成员变量,一旦被赋值就不能改变
对于基本类型的成员变量:
数值不变
用来定义常量:声明 final 变量并同时赋初值
对于引用类型的成员变量:
引用不变,但被引用对象可被修改
这一约定同样适用于数组

子类对象的创建与初始化

子类构造方法调用父类构造方法
通过显式的 super() 方法调用或编译器隐含插入的 super() 方法调用
这一过程递归地进行,直到根类 Object 的构造方法。在这一过程中,
子类对象所需的所有内存空间被分配,所有成员变量均使用默认值
初始化

从根类 Object 的构造方法开始,自顶向下地对每个类依次执行如
下两步:
显式初始化及使用实例初始化程序块进行初始化
执行构造方法的主体(不包括使用 super() 调用父类构造方法的语
句)

动态绑定(非动态绑定的情况)

Java 中除了 static 方法和 final 方法(private 方法属于 final 方
法),其余方法都采用动态绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class PrivOverride {
private void f() {
System.out.println("private f()");
}

public static void main(String[] args) {
PrivOverride po = new Derived();
po.f();
}
}

class Derived extends PrivOverride {
public int a;

public void f() {
System.out.println("public f()");
}
}

输出:private f()

动态绑定:绑定操作在程序运行时根据变量指向的对象实例的具体
类型找到正确的方法体 (而不是根据变量本身的类型)

成员变量的隐藏

  • 概念:父类中成员变量被子类中同名的成员变量隐藏
  • 成员变量隐藏和成员方法重写的前提
    • 父类成员能够在子类中被访问到
    • 父类中 private 成员,在子类中不能被隐藏(重写)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Sub extends Super {
String s = "sub";

public static void main(String[] args) {
Super sup = new Sub();
System.out.println(sup.s);
}
}

class Super {
String s = "super";
}
//super



class NotOverriding extends Base {
private int i = 2;

public static void main(String[] args) {
NotOverriding no = new NotOverriding();
no.increase();
System.out.println(no.i);
System.out.println(no.getI());
}
}

class Base {
private int i = 100;

public void increase() {
this.i++;
}

public int getI() {
return this.i;
}
}
//2
//201

静态变量的创建过程(与子类对象创建过程结合)

静态变量的创建与实例对象无关
只在系统加载其所在类时分配空间并初始化,且在创建该类的
实例对象时不再分配空间

什么时候加载其所在类?

  • 运行到不得不加载该类的时候

什么是“不得不加载该类的时候”?

  • 即将创建该类的第一个对象时
  • 首次使用该类的静态方法或静态变量时
  • 加载一个类之前要先加载其父类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class T1 {
static int s1 = 1;
static {
System.out.println("static block of T1: " + T2.s2);
}

T1() {
System.out.println("T1(): " + s1);
}
}

class T2 extends T1 {
static int s2 = 2;
static {
System.out.println("static block of T2: " + T2.s2);
}

T2() {
System.out.println("T2(): " + s2);
}
}

public class InheritStaticInit {
public static void main(String[] args) {
new T2();
}
}
1
2
3
4
static block of T1: 0
static block of T2: 2
T1(): 1
T2(): 2

静态块(static{})

(1)static 关键字还有一个比较关键的作用,用来形成静态代码块(static{}(即 static 块))以优化程序性能。

(2)static 块可以置于类中的任何地方,类中可以有多个 static 块。

(3)在类初次被加载的时候执行且仅会被执行一次(这是优化性能的原因!!!),会按照 static 块的顺序来执行每个 static 块,一般用来初始化静态变量和调用静态方法。

https://www.cnblogs.com/ysocean/p/8194428.html

静态方法的重写规则

回顾方法重写的规则:

  • 不改变方法的名称、参数列表和返回值,改变方法的内部实现
  • 子类中重写方法的访问权限不能缩小
  • 子类中重写方法不能抛出新的异常
  • 父类中 private 的成员,在子类中不能被隐藏(重写)

本节加入以下重写规则:

  • 子类不能把父类的静态方法重写为非静态
  • 子类不能把父类的非静态方法重写为静态
  • 子类可以声明与父类静态方法相同的方法
  • 静态方法的重写不会导致多态性
  • 构造方法本质上是 static 方法,不具有多态性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class StaticPolymorphism {
public static void main(String[] args) {
Sup s = new Sub();
s.mtd1();
s.mtd2(); // 静态方法的重写不会导致多态性
}
}

class Sup {
public void mtd1() {
System.out.println("Sup.instanceMethod");
}

public static void mtd2() {
System.out.println("Sup.staticMethod");
}
}

class Sub extends Sup {
// public static void mtd1() {} //静态方法不能隐藏实例方法
// public void mtd2() {} //实例方法不能重写静态方法
public void mtd1() {
System.out.println("Sub.instanceMethod");
}

public static void mtd2() {
System.out.println("Sub.staticMethod");
}
}
1
2
Sub.instanceMethod
Sup.staticMethod

若为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class StaticPolymorphism {
public static void main(String[] args) {
Sup s = new Sub();
s.mtd1();
s.mtd2(); // 静态方法的重写不会导致多态性
}
}

class Sup {
public void mtd1() {
System.out.println("Sup.instanceMethod");
}

public void mtd2() {
System.out.println("Sup.staticMethod");
}
}

class Sub extends Sup {
// public static void mtd1() {} //静态方法不能隐藏实例方法
// public void mtd2() {} //实例方法不能重写静态方法
public void mtd1() {
System.out.println("Sub.instanceMethod");
}

public void mtd2() {
System.out.println("Sub.staticMethod");
}
}

1
2
Sub.instanceMethod
Sub.staticMethod

对象串行化

将对象的状态转换成一个字节序列,这个字节序列能够存储在
外存中,能够在网络上传送,并能够在以后被完全恢复为原来
的对象
对象串行化机制的典型应用场景
Java 远程方法调用 (Remote Method Invocation, RMI)
Java Bean / EJB

对象串行化的方法

通过 ObjectOutputStream 的 writeObject 方法将一个对象写入到
流中

public final void writeObject(Object obj) throws IOException
通过 ObjectInputStream 的 readObject 方法将一个对象从对象流
中读出

public final Object readObject() throws IOException,

ClassNotFoundException

ObjectOutputStream 实现了 java.io.DataOutput 接口

ObjectInputStream 实现了 java.io.DataInput 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;
import java.util.Date;

public class SerializeDate {
public static void main(String args[]) throws IOException {
Date d = new Date();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("date.dat"));
out.writeObject(d);
out.close();
}
}

class UnSerializeDate {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("date.dat"));
Object o = in.readObject();
in.close();
if (o instanceof Date)
System.out.println(((Date) o).toString());
}
}

字符串的操作与比较

boolean equals(Object anObject) boolean equalsIgnoreCase(String anotherString) 比较两个字符串的内容是否相同(不忽略/忽略 大小写)

例题

来源:西安电子科技大学,2020

一、简答题(每题 5 分,共 20 分)

  1. 简述方法重载与方法重写的区别。

https://www.cnblogs.com/zheting/p/7751787.html

  1. 简述如何保证向下转型(downcasting)的正确性。

https://blog.csdn.net/tntzs666/article/details/80274526

向下转型必须是在向上转型之后才能进行。

instanceof 用法:
对象名 instanceof 类名 ,判断这个对象是否是属于这个类或是其子类

  1. 简述抽象类与接口的区别。(6 分)

https://kb.cnblogs.com/page/42159/

二、读程题(每空 2 分,共 40 分)

给出下列 Java 程序代码片段的运行结果

1
2
3
4
5
6
7
8
String str1 = "first string";
String str2 = new String("second string");
String str3 = “third string”;
str3 = str2;
str2 = str1;
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
1
2
3
first string
first string
second string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Employee {
public int id;
public static int serialNum = 1;

Employee() {
id = serialNum++;
}
}

public class EmployeeTest {
public static void main(String[] args) {
Employee e1 = new Employee();
Employee e2 = new Employee();
System.out.println("e1.serialNum:" + e1.serialNum);
System.out.println("e1.id:" + e1.id);
System.out.println("e2.serialNum:" + e2.serialNum);
System.out.println("e2.id:" + e2.id);
}
}
1
2
3
4
e1.serialNum:3
e1.id:1
e2.serialNum:3
e2.id:2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Fu {
static int fustatic = 10;
int fus = 100;
static {
System.out.println("static block of Fu: " + Zi.zistatic);
}
{
System.out.println("constructor block of Fu: " + fus);
}
Fu() {
System.out.println("Fu(): " + fustatic);
}
}

class Zi extends Fu {
static int zistatic = 20;
int zis = 50;
static {
System.out.println("static block of Zi: " + Zi.zistatic);
}
{
System.out.println("constructor block of Zi: " + zis);
}

Zi() {
System.out.println("Zi(): " + zistatic);
}
}

public class TestDemo {
public static void main(String[] args) {
new Zi();
}
}

1
2
3
4
5
6
static block of Fu: 0
static block of Zi: 20
constructor block of Fu: 100
Fu(): 10
constructor block of Zi: 50
Zi(): 20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Test extends Base{
private int i=2;
public void printI(){
System.out.println(this.i);
System.out.println(this.getI());
}
public static void main(String[] args){
Base base=new Test();
base.increase();
base.printI();

if(base instanceof Test){
Test sub=(Test)base;
sub.increase();
sub.printI();
}
}
}
class Base{
private int i=100;
public void printI(){
System.out.println(this.i);
System.out.println("do nothing");
}
public final void increase(){
this.i++;
}
public int getI(){
return this.i;
}
}

1
2
3
4
2
101
2
102

假设下列程序中 s1 语句会抛出 EType2 异常

1
2
3
4
5
6
7
8
9
10
11
12
try{
System.out.println("before Exception. ");
s1;
System.out.println("after Exception.");
} catch (Exception e){
System.err.println("In Exception catch process.");
} catch(EType2 e){
System.out.println("In Etype2 catch process.");
} finally{
System.out.println("In finally process.");
}

1
2
3
before Exception. 
In Etype2 catch process.
In finally process.

三、改错题(共 10 分)
键盘录入多个数据,以 0 结束,要求在控制台输出这多个数据中的最大值。
下面 Java 程序中存在五处编译错误,指出错误位置,说明错误原因并更正。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ArrayListDemo {
public static void main(String[] args) {
// 创建键盘录入数据对象
Scanner sc = new Scanner(System.out);

// 键盘录入多个数据,我们不知道多少个,所以用集合存储
ArrayList<Object> array = new ArrayList<int>();

// 以 0 结束。这个简单,只要键盘录入的数据是 0,我就
// 跳出循环,不继续处理了
while (true) {
System.out.println("请输入数据:");
int number = sc.nextInt();
if (number != 0) {
array.add(number);
} else {
break;
}
}

// 把集合转成数组
Integer[] i = new int[array.length()];
array.toArray(i);
// 对数组排序
Arrays.sort(i);
// 获取该数组中的最大索引的值
System.out.println("数组最大值是:" + i[i.length()]);
}
}

1
2
3
4
5
6
7
8
9
import java.util.*;

Scanner sc = new Scanner(System.in);

ArrayList<Integer> array = new ArrayList<Integer>();

System.out.println("数组最大值是:" + i[i.length-1]);

sc.close();

考后总结

复习的一到考场就忘光光,嘤文记不住只能写中文…

都什么年代什么学校了,还笔试写程序,还考谭浩强题,有什么意义[doge]

2020-11-29 20:28:47