项目 |
内容 |
这个作业属于哪个课程 |
https://www.cnblogs.com/nwnu-daizh/ |
这个作业的要求在哪里 |
https://www.cnblogs.com/nwnu-daizh/p/11605051.html |
作业学习目标 |
学习以下学习资源:教材第5章和第5章教学课件,完成学习 (1) 理解继承的定义; (2) 掌握子类的定义要求 (3) 掌握多态性的概念及用法; (4) 掌握抽象类的定义及用途。 |
随笔博文正文内容包括:
第一部分:总结第五章理论知识(30分)
继承(inheritance):用已有类来构建新类的一种机制。当定义了一个新类继承了一个类时,这个新类就继承了这个类的方法和域,同时在新类中添加新的方法和域以适应新情况。继承是Java程序设计中的一项核心技术,也是面向对象特征之一。继承的特点具有层次结构子类继承了父类的域和方法。
5.1 类、超类和子类
- 类继承的格式:class 新类名 extends 已有类名 。
- 在Java中,所有的继承都是公有继承,而没有C++中的私有继承和保护继承。
- 关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)、基类(base class)或父类(parent class);-来自系统类库-用户自定义类新类称作:为子类(subclass)、派生类(derived class)或者孩子类(child class)。一般来说,子类比超类拥有的功能更加丰富。
- 关键字this有两个用途:一是引用隐式参数,而是调用该类其他的构造器。同样,super关键字也有两个用途:一是调用超类的方法,而是调用超类的构造器。调用构造器的语句只能作为另一个构造器的第一条语句出现。构造参数既可以传递给本类(this)的其他构造器,也可以传递给超类(super)的构造器。
- 一个对象变量可以指示多种实际类型的现象被称为多态(polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)。
- 在Java中,不需要将方法声明为虚拟方法。动态绑定是默认的处理方式。如果不希望让一个方法具有虚拟特征,可以将它标记为final。
5.1.1 继承层次
- 由一个公共超类派生出来的所有类的集合被称为继承层次(inheritance hierarchy)。在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链(inheritance chain)。
- java不支持多继承。
5.1.2 多态性
- 多态性泛指在程序中同一个符号在不同的情况下具有不同解释的现象。
- 超类中定义的域或方法,被子类继承之后,可以具有不同的数据类型或表现出不同的行为。
- 这使得在超类及其各个子类中同名的域或方法具有不同的语义。
- 超类中的方法在子类中可方法重写。
- 在Java程序设计语言中,对象变量是多态的。不能将一个超类的引用赋给子类变量,可以在子类对象赋给超类变量。
5.1.3 抽象类
观察类的继承层次结构,位于上层的类更具通用性,甚至可能更加抽象。从某种角度看,祖先类更加通用,人们只将它作为派生其他类的基类,而不作为特定的实例类。
定义抽象类:abstract class Person{
public abstract String getDescription();
···}
注意:abstract方法,只能声明,不能实现;
- 为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。
- 除了抽象方法之外,抽象类还可以包含具体数据和具体方法。
- 扩展抽象类可以有两种选择。一种是在子类中定义部分抽象方法或抽象方法也不定义,这样就必须将子类也标记为抽象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。
- 类即使不含抽象方法,也可以将类声明为抽象类。抽象类不能被实例化。
- 需要注意,可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。
5.1.4 动态绑定
概念:又称为运行时绑定。即程序在运行时会自动选择调用哪个方法。
示例 public class Son extends Father
{…...}
Son son=new Son(); son.method();
调用对象方法的执行过程:
1.首先,编译器检查对象的声明类型和方法名,搜索相应类(Son)及其父类(Father)的“方法表”,找出所有访问属性为public的method方法。
接下来,编译器检查方法调用中提供的参数类型,找出一个完全匹配的方法,这个过程称为重载解析。
如果方法是private、static、final修饰的,或者是构造器,那么编译器能准确地判断应该调用哪个方法,这称为静态绑定。
2.程序运行时,如果子类Son中定义了method()方法,则直接调用子类中的相应方法;如果子类Son中没有定义相应的方法,则到其父类中寻找method()方法。
动态绑定中每次调用方法都要进行搜索,时间开销相当大。因此虚拟机预先为每个类创建了一个方法表,其中列出了所有方法的签名和实际调用的方法。
方法的名称和参数列表称为方法的签名。
3.动态绑定有一个非常重要的特性:无需对现存在代码进行修改,就可以对程序进行扩展。
4.在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。特别是,如果超类方法是public,子类方法一定要声明为public。经常会发生这类错误:在声明子类方法的时候,遗漏了public修饰符。此时,编译器将会把他解释为试图降低访问权限。
5.1.5 阻止继承:final类和方法
阻止继承:final类和方法不允许继承的类称为final类,在类的定义中用final修饰符加以说明。
final class Executive extends Manager
{......}
类中的方法可定义为final的。这时子类就不能覆盖该方法。如果一个类声明为final,属于它的方法会被自动设为final,但不包括域(如果域定义为final,在对象构造以后,final域就不能再修改了)
- 有时候,可能希望阻止人们利用某个类定义子类。不允许扩展的类被称为final类。如果在定义类的时候使用了final修饰符就表明这个类是final类。
- 类中的特定方法也可以声明为final。如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动地成为final方法)。
- 域也可以被声明为final。对于final域来说,构造对象之后就不允许改变它们的值了。不过,如果将一个类声明为final,只有其中的方法自动地成为final,而不包括域。
- 将方法或域声明为final主要的目的是:确保它们不会在子类中改变语义。
- 在早期的Java中,有些程序员为了避免动态绑定带来的系统开销而使用final关键字。如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理,这个过程称为内联(inlining)。例如,内联调用e.getName()将被替换为访问e.name域。这是一项很有意义的改进,这是由于CPU在处理调用方法的指令时,使用的分支转移会扰乱预取指令的策略,所以,这被视为不受欢迎的。然而,如果getName在另外一个类中被覆盖,那么编译器就无法知道覆盖的代码将会做什么操作,因此也就不能对它进行内联处理了。
- 幸运的是,虚拟机中的即时编译器比传统编译器的处理能力强得多。这种编译器可以准确地知道类之间的继承关系,并能够检测出类中是否真正地存在覆盖给定的方法。如果方法很简短、被频繁调用且没有真正地被覆盖,那么即时编译器就会将这个方法进行内联处理。如果虚拟机加载了另外一个子类,而在这个子类中包含了对内联方法的覆盖,那么将会发生什么情况呢?优化器将取消对覆盖方法的内联。这个过程很慢,但却很少发生。
5.1.6 强制类型转换
如果要把一个超类对象赋给一个子类对象变量,就必须进行强制类型转换。
其格式为:子类 对象 =(子类)(超类对象)
Manager boss=(Manager)staff[0];
类型转换必须在继承层次内进行;而且在超类转换为子类之前,应先使用instanceof操作符进行继承链检查。 Manager boss=(Manager)staff[1];//error!
if(staff[1]instanceof Employee)
{ boss=(Manager)staff[1];...} 应该尽量少用类型转换和instanceof运算符;
- 将一个类型强制转换成另外一个类型的过程被称为类型转换。
- 进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后,使用对象的全部功能。
- 在Java中,每个对象变量都属于一个类型。类型描述了这个变量所引用的以及能够引用的对象类型。
- 将一个值存入变量时,编译器将检查是否允许该操作。将一个子类的引用赋给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量,必须进行类型转换,这样才能够通过运行时的检查。
- 只有在继承层次内进行类型转换。在将超类转换成子类之前,应该使用instanceof进行检查。
5.1.7 受保护访问
如果希望超类的某些方法或域允许被子类直接访问,就需要在超类定义时,将这些方法或域声明为protected。
protected违背了OOP提倡的数据封装原则。实际中要谨慎使用protected的访问属性。若定义类时要限制类中某个方法的使用,就可以将它声明为protected。这表明子类得到信任,可以使用这个方法,而其他类则不行
信息隐藏目的:1.对类中任何实现细节的更改不会影响使用该类的代码 2.防止用户意外删除数据易于使用类
Java中的受保护部分对所有子类及同一个包中的所有其他类都可见。
Java用于控制可见性的4个访问修饰符:
- 仅对本类可见—-private。
- 对所有类可见—-public。
- 对本包和所有子类可见—-protected。
- 对本包可见—-默认,不需要修饰符。
5.2 Object:所有类的超类
Object:所有类的超类Object类是Java中所有类的祖先——每一个类都由它扩展而来。在不给出超类的情况下,Java会自动把Object作为要定义类的超类。可以使用类型为Object的变量指向任意类型的对象。但要对它们进行专门的操作都要进行类型转换。
Object是Java中所有类的始祖,在Java中每个类都是由它扩展而来的。如果没有明确地指出超类,Object就被认为是这个类的超类。
Object类型的变量只能用于作为各种值的通用持有者。要想对其中的内容进行具体的操作,还需要清楚对象的原始类型,并进行相应的类型转换。
在Java中,只有基本类型(pimitive types)不是对象,例如,数组、字符和布尔类型的值都不是对象。所有的数组类型,不管是对象数组还是基本类型的数组都扩展于Object类。
5.2.1 equals方法
- Object类中的equals方法用于检测一个对象是佛福等于另一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用,他们一定是相等的。
- getClass方法将返回一个对象所属的类。在检测中,只有两个对象属于同一个类时,才有可能相等。
如果需要检测两个对象状态的相等性,就需要在新类的定义中需要覆盖equals方法。定义子类的equals方法时,可调用超类的equals方法。super.equals(otherObject)
5.2.2 相等测试与继承
- Java语言规范要求equals方法具有下面的特性:
(1)自反性:对于任何非空引用x,x.equals(x)应该返回true。
(2)对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true。
(3)传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true
,x.equals(z)也应该返回true。
(4)一致性:如果x和y引用的对象没有发送变化,反复调用x.equals(y)应该返回同样的结果。
(5)对于任意非空引用x,x.equals(null)应该返回false。 - 编写一个完美的equals方法的建议:
(1)显示参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量。
(2)检测this与otherObject是否引用同一个对象:if(this==otherObject) return true;
这条语句只是一个优化。实际上,这是一种经常采用的形式。因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多。
(3)检测otherObject是否为null,如果为null,返回false。这种检测是很必要的。if(otherObject==null) return false;
。
(4)比较this与otherObject是否属于同一个类。如果equals的语义在每个子类中有所变化,就使用getClass检测:if(getClass() != otherObject.getClass()) return false;
,如果所有的子类都拥有统一的语义,就使用instanceof检测:if(!(otherObject instanceof ClassName)) return false;
。
(5)将otherObject转换为相应的类类型变量:ClassName other = (ClassName)otherObject
。
(6)现在开始对所有需要比较的域进行比较了。使用 比较基本类型域,使用equals比较对象域。如果所有的域都匹配,就返回true;否则返回false。`return field1 other.field1 && Objects.equals(fiels2,other.field2)&&…;`
如果在子类中重新定义equals,就要在其中包含调用super.equals(other)。 - 对于数组类型的域,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等。
- java.utils.Arrays 1.2
- static Boolean equals(type[] a, type[] b) 5.0
如果两个数组长度相同,并且在对应的位置上数据元素有均相同,将返回true。数组的元素类型可以是Object、int、long、short、char、byte、boolean、float或double。
- static Boolean equals(type[] a, type[] b) 5.0
- java.util.Object 7
- static boolean equals(Object a,Object b)
如果a和b都为null,
返回true;如果只有其中之一为null,则返回false;否则返回a.equals(b)。
- static boolean equals(Object a,Object b)
5.2.3 hashCode方法
- 散列码(hash code)是由对象导出的一个整型值。散列码是没有规律的。如果x和y是两个不同的对象。x.hashCode()与y.hashCode()基本上不会相同。
- 由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列值,其值为对象的存储地址。
- 字符串的散列值是由内容导出的,s与t的散列值是一样的。字符串缓存sb与tb是不同的。
- 如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。
- hashCode方法应该返回一个整型数值(也可以是负数),并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。
- 需要组合多个散列值时,可以调用Objects.hash并提供多个参数。这个方法会对各个参数调用Objects.hashCode,并组合这些散列值。
- Equals与hashCode的定义必须一致:如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值。
- 如果存在数组类型的域,那么可以使用静态的Ayyas.hashCode方法计算一个散列码,这个散列码由数组元素的散列码组成。
- java.lang.Object 1.0
- int hashCode()
返回对象的散列码。散列码可以是任意的整数,包括正数或负数。两个想等的对象要求返回相等的散列码。
- int hashCode()
- java.lang.Objects 7
- int hash(Object… objects)
返回一个散列码,由提供的所有对象的散列码组合而得到。 - static int hashCode(Object a)
如果a为null返回0,否则返回a.hashCode()。
- int hash(Object… objects)
- java.util.Arrays 1.2
- static int hashCode(type[] a) 5.0
计算数组a的散列码。组成这个数组的元素类型可以是object,int,long,short,char,byte,boolean,float或double。
- static int hashCode(type[] a) 5.0
5.2.4 toString方法
- toString方法用于返回表示对象值得字符串。
- java.lang.Object 1.0
- Class getClass()
返回包含对象信息的类对象。 - boolean equals(Object otherObject)
比较两个对象是否相等,如果两个对象指向同一块存储区域,方法返回true;否则方法返回false。在自定义的类中,应该覆盖这个方法。 - String toString()
返回描述该对象的字符串。在自定义的类中,应该覆盖这个方法。
- Class getClass()
- java.lang.Class 1.0
- String getName()
返回这个类的名字。 - Class getSuperclass()
以Class对象的形式返回这个类的超类信息。
- String getName()
5.3 泛型数组列表
- ArrayList是一个采用类型参数(type parameter)的泛型类(generic class)。为了指定数组列表保存的元素对象类型,需要用一对尖括号将类名括起来加在后面。
- 如果调用add且内部数组已经满了,数组列表就将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。如果已经清楚或能够估计出数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity方法。还可以把初始容量传递给ArrayList构造器,
ArrayList<Employee> staff = new ArrayList<>(100)
。 - 一旦能够确认数组列表的大小不再发生变化,就可以调用trimToSize方法。这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余的存储空间。
- 一旦整理了数组列表的大小,添加新元素就需要花时间再次移动存储块,所以应该在确认不会添加任何元素时,再调用trimToSize。
- java.util.ArrayList<T> 1.2
- ArrayList<T>()
构造一个空数组列表。 - ArrayList<T>(int initialCapacity)
用指定容量构造一个空数组列表。
参数:initalCapacity 数组列表的最初容量 - boolean add(T obj)
在数组列表的尾端添加一个元素。永远返回true。
参数:obj 添加的元素 - int size()
返回存储在数组列表中的当前元素数量。(这个值将小于或等于数组列表的容量。) - void ensureCapacity(int capacity)
确保数组列表在不重新分配存储空间的情况下就能够保存给定数量的元素。 - void trimToSize()
将数组列表的存储容量消减到当前尺寸。
- ArrayList<T>()
5.3.1 访问数组列表元素
- 数组列表自动扩展容量的便利增加了访问元素语法的复杂程度。其原因是ArrarList类并不是Java程序设计语言的一部分;它只是一个由某些人编写且被放在标准库中的一个实用类。
- 使用get和set方法实现访问或改变数组元素的操作,而不使用[]语法格式。
- 只有i小于或等于数组列表的大小时,才能够调用list.set(i,x)。使用add方法为数组添加新元素,而不要使用set方法,它只能替换数组中已经存在的元素内容。
- java.util.ArrayList<T> 1.2
- void set(int index,T obj)
设置数组列表指定位置的元素值,这个操作将覆盖这个位置的原有内容。
参数:index 位置(必须介于0~size()-1之间); obj 新的值 - T get(int index)
获得指定位置的元素值。
参数:index 获得的元素位置(必须介于0~size()-1之间) - void add(int index,T obj)
向后移动元素,以便插入元素。 - T remove(int index)
删除一个元素,并将后面的元素向前移动。被删除的元素由返回值返回。
参数:index 被删除的元素位置(必须介于0~size()-1之间)
- void set(int index,T obj)
5.3.2 类型化与原始数组列表
- 在程序运行时,所有的数组列表都是一样的,即没有虚拟机中的类型参数。因此,类型转换(ArrayList)和(ArrayList<Employee>)将执行相同的运行时检查。
5.4 对象包装器与自动装箱
对象包装器与自动打包所有基本数据类型都有着与之对应的预定义类,它们被称为对象包装器(wrapper)。对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。且对象包装器类还是final,因此不能定义它们的子类。
使用对象包装器的好处:1.基本类型转化为对象 2.定义一些有用的基本方法(static方法)
- 所有的基本雷士都有一个与之对应的类。这些类称为包装器(wrapper)。这些对象包装器类拥有很鲜明的名字:Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean(前6个类派生于公共的超类Number)。对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是final,因此不能定义它们的子类。
- 由于每个值分别包装在对象中,所以ArrayList<Integer>的效率远远低于int[]数组。因此,应该用它构造小型集合,其原因是此时程序员操作的方便性要比执行效率更加重要。
- Java SE 5.0的另一个改进之处是更加便于添加或获取数组云阿苏。下面这个调用
list.add(3)
将自动地变换成list.add(Integer.valueOf(3));
这种变换被称为自动装箱(autoboxing)。 - 相反地,当将一个Integer对象赋给一个int值时,将会自动地拆箱。也就是说,编译器将下列语句:
int n=list.get(i)
翻译成int n = list.get(n).intValue();
。 - 在两个比较器对象比较时调用equals方法。
- 自动装箱规范要求boolean、byte、char<=127,介于-128~127之间的short和int被包装到固定的对象中。
- 装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些自己吗。
- java.lang.Integer 1.0
- int intValue()
以int的形式返回Integer对象的值(在Number类中覆盖了intValue方法)。 - static String toString(int i)
以一个新String对象的形式返回给定数值i的十进制表示。 - static String toString(int i,int radix)
返回数值i的基于给定radix参数进制的表示。 - static int parseInt(String s)
- static int parseInt(String s,int radix)
返回字符串s表示的整型数值,给定字符串表示的是十进制的整数(第一种方法),或者是radix参数进制的整数(第二种方法)。 - static Integer valueOf(String s)
- static Integer valueOf(String s,int radix)
返回用s表示的整型数值进行初始化后的一个新Integer对象,给定字符串表示的是十进制的整数(第一种方法),或者是radix参数进制的整数(第二种方法)。
- int intValue()
- java.text.NumberFormat 1.1
- Number parse(String s)
返回数字值,假设给定的String表示了一个数值。
- Number parse(String s)
5.5 参数数量可变的方法
参数数量可变的方法在Java SE5.0以前的版本中,每个Java方法都有固定数量的参数。然而,现在的版本提供了可以用可变的参数数量调用的方法(称为“可变参”方法)。
用户自己可以定义可变参数的方法,并将参数指定为任意类型,甚至是基本类型。
5.6 枚举类
枚举类说明:1.枚举类是一个类,它的隐含超类是java.lang.Enum。 2.枚举值并不是整数或其它类型,是被声明的枚举类的自身实例,例如A是Grade的一个实例。-
3.枚举类不能有public修饰的构造函数,构造函数都是隐含private,编译器自动处理。4. 枚举值隐含都是由public、static、final修饰的,无须自己添加这些修饰符。
5.在比较两个枚举类型的值时,永远不需要调用equals方法,直接使用”==”进行相等比较。
- 在比较两个枚举类型的值时,永远不需要调用equals,而直接使用“==”就可以了。
- 如果需要的话,可以在枚举类型中添加一些构造器、方法和域。当然,构造器只是在构造枚举常量的时候被调用。
- 所有的枚举类型都是Enum类的子类。
- 每个枚举类型都有一个静态的values方法,它将返回一个包含全部枚举值的数组。
- java.lang.Enum <E> 5.0
- static Enum valueOf(Class enumClass,String name)
返回指定名字、给定类的枚举常量。 - String toString()
返回枚举常量名。 - int ordinal()
返回枚举常量在enum声明中的位置,位置从0开始计数。 - int compareTo(E other)
如果枚举常量出现在other之前,则返回一个负值;如果this==other,则返回0;否则,返回正值。枚举常量的出现次序在enum声明中给出。
- static Enum valueOf(Class enumClass,String name)
5.7 反射
- 反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。特别是在设计或运行中添加新类时,能够快速地应用开发工具动态地查询新添加类的能力。
- 能够分析类能力的程序称为反射(reflaction)。反射机制的功能及其强大,在下面可以看到,反射机制可以用来:
- 在运行中分析类的能力。
- 在运行中查看对象。
- 实现通用的数组操作代码。
- 利用Method对象,这个对象很像C++中的函数指针。
5.7.1 Class类
- 在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。然而,可以通过专门的Java类访问这些信息。保存这些信息的类被称为Class。Object类中的getClass()方法将会返回一个Class类型的实例。
- 一个Class对象将表示一个特定类的属性。
- 可以调用Class类的静态方法forName获得类名对应的Class对象。如果类名保存在字符串中,并可在运行中改变,就可以使用这个方法。当然,这个方法只有在className是类名或接口名时才能执行。否则,forName方法将抛出一个checkedexception(已检查异常)。无论何时使用这个方法,都应该提供一个异常处理器(exception handler)。·
- 一个Class对象实际上表示的是一个类型,而这个类型未必是一种类。
- 虚拟机为每个类型管理一个Class对象。因此,可以利用==运算符实现两个类对象比较的操作。
- Class类的方法newInstance(),可以用来快速地创建一个类的实例。newInstance方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器,就会抛出一个异常。
5.7.2 捕获异常
- 异常有两种类型:未检查异常和已检查异常。对于已检查异常,编译器将会检查是否提供了处理器。未检查异常编译器不会查看是否为这些错误提供了处理器。
- java.lang.Class 1.0
- static Class forName(String className)
返回描述类名为className的Class对象。 - Object newInstance()
返回这个类的一个新实例。
- static Class forName(String className)
- java.lang.reflect.Constructor 1.1
- Object newInstance(Object[] args)
构造一个这个构造器所属类的新实例。
参数:args 这是提供给构造器的参数。
- Object newInstance(Object[] args)
- java.lang.Throwable 1.0
- void printStackTrace()
将Throwable对象和栈的轨迹输出到标准错误流。
- void printStackTrace()
5.7.3 利用反射分析类的能力
- 在java.lang.reflact包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器、这三个类都有一各叫做getName的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属类型的Class对象。Method和Constructor类有能够报告参数类型的方法,它将返回一个整型数值,用不同的位开关描述public和static这样修饰符使用状况。另外,还可以利用java.lang.reflect包中的Modifier类的静态方法分析getModifiers返回的整型数值。还可以利用Modifier.toString方法将修饰符打印出来。
- Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。Class类的getDelareFields、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。
- java.lang.Class 1.0
- Field[] getFields() 1.1
- Field[] getDeclaredFields() 1.1
getFields方法将返回一个包含Field对象的数组,这些对象记录了这个类或其超类的公有域。getDeclaredField方法也将返回包含Filed对象的数组,这些对象记录了这个类的全部域。如果类中没有域,或者Class对象描述的是基本类型或数组类型,这些方法将返回一个长度为0的数组。 - Method[] getMethods() 1.1
- Method[] getDeclaredMethods() 1.1
返回包含Methos对象的数组:getMethods将返回所有的公有方法,包括从超类继承来的公有方法;getDeclaredMethods返回这个类或接口的全部方法,但不包括超类继承了的方法。 - Constructor[] getConstructors() 1.1
- Constructor[] getDeclaredConstructors() 1.1
返回包含Constructor对象的数组,其中包含了Class对象所描述的类的所有公有构造器(getConstructors)或所有构造器(getDeclaredConstructors)。
- java.lang.reflact.Field 1.1 java.lang.reflect.Method 1.1 java.lang.reflact.Constructor 1.1
- Class getDeclaringClass()
返回一个用于描述类中定义的构造器、方法或域的Class对象。 - Class[] getExceptionTypes()(在Constructor和Method类中)
返回一个用于描述方法抛出的异常类型的Class对象数组。 - int getModifiers()
返回一个用于描述构造器、方法或域的修饰符的整型数值。使用Modifier类总的这个方法可以分析这个返回值。 - String getName()
返回一个用于描述构造器、方法或域名的字符串。 - Class[] getParameterTypes()(在Constructor和Methos类中)
返回一个用于描述参数类型的Class对象数组。 - Class getReturnType()(在Method类中)
返回一个用于描述返回类型的Class对象。
- Class getDeclaringClass()
- java.lang.reflact.Modifier 1.1
- static String toString(int modifiers)
返回对应modifiers中位设置的修饰符的字符串表示。 - static boolean isAbstract(int modifiers)
- static boolean isFinal(int modifiers)
- static boolean isInterface(int modifiers)
- static boolean isNative(int modifiers)
- static boolean isPrivate(int modifiers)
- static boolean isProtected(int modifiers)
- static boolean isPublic(int modifiers)
- static boolean isStatic(int modifiers)
- static boolean isStrict(int modifiers)
- static boolean isSynchronized(int modifiers)
- static boolean isVolatile(int modifiers)
这些方法将检测方法名中对象的修饰符在modifiers值中的位。
- static String toString(int modifiers)
5.7.4 在运行时使用反射分析对象
- 查看对象域的关键方法是Field类中的get方法。
- 除非拥有访问权限,否则Java安全机制只允许查看任意对象有哪些域,而不允许读取他们的值。
- 反射机制的默认行为受限于Java的访问机制。然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。
- setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的公共超类。这个特性是为调试、持久存储和相似机制提供的。
- 调用Field的f.set(obj,value)可以将obj对象的f域设置成新值。
- 使用Class的getDeclaredFields获取所有的数据域,然后使用setAccessible将所有的域设置为可访问的。对于每个域,获得了名字和值。
- java.lang.reflect.AccessibleObject 1.2
- void setAccessible(boolean flag)
为反射对象设置可访问标志。flag为true表明屏蔽Java语言的访问检查,使得对象的私有属性也可以被查询和设置。 - boolean isAccessible()
返回反射对象的可访问标志的值。 - static void setAccessible(AccessibleObject[] array,boolean flag)
是一种设置对象数组可访问标志的快捷方法。
- void setAccessible(boolean flag)
- java.lang.Class 1.1
- Field getField(String name)
- Field[] getField()
返回指定名称的公有域,或返回所有域的数组。 - Field getDeclaredField(String name)
- Field[] getDeclaredFields()
返回类中声明的给定名称的域,或者包含声明的全部域的数组。
- java.lang.reflect.Field 1.1
- Object get(Object obj)
返回obj对象中用Field对象表示的域值。 - void set(Object obj,Object newValue)
用一个新值设置Obj对象中Field对象表示的域。
- Object get(Object obj)
5.7.5 使用反射编写泛型数组代码
- java.lang.reflect包中的Array类允许动态地创建数组。
- 可以通过调用Array.getLength(a)获得数组的长度,也可以通过Array类的静态getLength方法的返回值得到任意数组的长度。而要获得新数组元素类型,就需要进行以下工作:
1)首先获得数组的类对象。
2)确认它是一个数组。
3)使用Class类(只能定义表示数组的类对象)的getComponentType方法确定数组对应的类型。 - java.lang.reflect.Array 1.1
- static Object get(Object array,int index)
- static xxx getxxx(Object array,int index)
(xxx是boolean、byte、char、double、float、int、long、short之中的一种基本类型。)
这些方法将返回存储在给定位置上的给定数组的内容。 - static void set(Object array,int index,Object value)
- static setxxx(Object array,int index,xxx newValue)
(xxx是boolean、byte、char、double、float、int、long、short之中的一种基本类型。)
这些方法将一个新值存储到给定位置上的给定数组中。 - static int getLength(Object array)
返回数组的长度。 - static Object newInstance(Class componentType, int length)
- static Object newInstance(Class componentType, int[] lengths)
返回一个具有给定类型、给定维数的新数组。
5.7.6 调用任意方法
- 在Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是:
Object invoke(Object obj,Object... args)
,第一个参数是隐式参数,其余的对象提供了显式参数。对于静态方法,第一个参数可以被忽略,即可以将它设置为null。 - 如何得到Method对象呢?当然,可以通过调用getDeclareMethods方法,然后对返回的Method对象数组进行查找,直到发现想要的方法为止。也可以调用Class类中的getMethod方法得到想要的方法。它与getField方法类似。getField方法根据表示域名的字符串,返回一个Field对象。然而,有可能存在若干个相同名字的方法,因此要格外小心,以确保能够准确地得到想要的那个方法。有鉴于此,还必须提供想要的方法的参数类型。getMethod的签名是:
Method getMethod(String name,Class... parameterTypes)
。 - 如果在调用方法的时候提供了一个错误的参数,那么invoke方法将会抛出一个异常。
- invoke的参数和返回值必须是Object类型的。这就意味着必须进行多次的类型转换。这样做将会使编译器错过检查代码的机会。因此,等到测试阶段才会发现这些错误,找到并改正它们将会更加困难。不仅如此,使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些。有鉴于此,建议仅在必要的时候才使用Method对象,而最好使用接口和内部类。特别要重申:建议Java开发者不要使用Method对象的回调功能。使用接口进行回调会使得代码的执行速度更快,更易于维护。
- java.lang.reflect.Method 1.1
- public Object invoke(Object implicitParameter,Object[] explicitParamenters)
调用这个对象所描述的方法,传递给定参数,并返回方法的返回值。对于静态方法,把null作为隐式参数传递。在使用包装器传递基本类型的值时,基本类型的返回值必须是未包装的。
5.8 继承设计的技巧
- 将公共操作和域放在超类。
- 不要使用受保护的域。
protected机制并不能够带来更好的保护,其原因主要有两点。第一,子类集合是无限制的,任何一个人都能够由某个类派生一个子类,并编写代码以直接访问protected的示例域,从而破坏了封装性。第二,在Java程序设计语言中,在同一个包中的所有类都可以访问protected域,而不管它是否为这个类的子类。
protected方法对于指示那些不提供一般用途而应在子类中重新定义的方法很有用。 - 使用继承实现“is-a”关系。
- 除非所有继承的方法都有意义,否则不要使用继承。
- 在覆盖方法时,不要改变预期的方法。
- 使用多态,而非类型信息。
使用多态方法或接口编写的代码比使用对多种类型进行检测的代码更加易于维护和扩展。 - 不要过多地使用反射。
第二部分:实验部分
实验内容和步骤
实验1:测试程序1(10分)
5-1代码如下:
package inheritance;
/** * This program demonstrates inheritance. * @version 1.21 2004-02-21 * @author Cay Horstmann */ public class ManagerTest { public static void main(String[] args) { // construct a Manager object 构造一个manager对象 var boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000); //由boss.setBonus单独设置津贴,其中setBonus是Manager的特有方法; var staff = new Employee[3]; //定义一个包含三个雇员的数组; // fill the staff array with Manager and Employee objects staff[0] = boss; // 父类可以引用子类; staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1); //将经理和雇员都放到数组中去; staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15); // print out information about all Employee objects for (Employee e : staff) //for循环打印出各个雇员对象的信息; System.out.println("name=" + e.getName() + ",salary=" + e.getSalary()); } }
5-2代码如下:
package inheritance; import java.time.*; public class Employee { private String name; private double salary; private LocalDate hireDay; //类的实例域定义来存放的需要操作的数据; public Employee(String name, double salary, int year, int month, int day) { this.name = name; this.salary = salary; hireDay = LocalDate.of(year, month, day); //根据参数设置日期,参数分别为年月日; } public String getName() { return name; //取得name属性的值; } public double getSalary() { return salary; //取得salary属性的值; } public LocalDate getHireDay() { return hireDay; //取得hireDay属性的值; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; //调用方法的对象salary实例域设置为新值; } }
5-3代码如下:
package inheritance;
public class Manager extends Employee //由继承Employee类来定义Manager类的格式,关键字extend表示继承; { private double bonus; /** * @param name the employee's name * @param salary the salary * @param year the hire year * @param month the hire month * @param day the hire day */ public Manager(String name, double salary, int year, int month, int day)//创建Employee类的实例 { super(name, salary, year, month, day); //调用超类Employee中含有这些参数的构造器; bonus = 0; } public double getSalary() { double baseSalary = super.getSalary(); //用关键字super调用超类Employee中的getsalary方法 return baseSalary + bonus; } public void setBonus(double b) { bonus = b; } }
5-1,5-2,5-3运行截图:
删除程序中Manager类、ManagerTest类,背录删除类的程序代码,在代码录入中理解父类与子类的关系和使用特点。
删除Manager类:
package inheritance; public class Manager extends Employee //由继承Employee类来定义Manager类的格式,关键字extend表示继承; { private double bonus; public Manager(String name, double salary, int year, int month, int day) { super(name, salary, year, month, day); // TODO Auto-generated constructor stub bonus=0; } @Override public double getSalary() { // TODO Auto-generated method stub double baseSalary = super.getSalary(); return baseSalary + bonus; } public void setBonus(double b) { // TODO Auto-generated method stub bonus=b; } }
删除ManagerTest类:
package inheritance; /** * This program demonstrates inheritance. * @version 1.21 2004-02-21 * @author Cay Horstmann */ public class ManagerTest { public static void main(String[] args) { // construct a Manager object var boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000); var staff = new Employee[3]; // fill the staff array with Manager and Employee objects staff[0] = boss; staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1); staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15); // print out information about all Employee objects for (Employee e : staff) System.out.println("name=" + e.getName() + ",salary=" + e.getSalary()); } }
删除后运行结果:
父类和子类的关系是继承关系。
有三种继承方式
1.public 继承; 2.protect 继承; 3.private 继承;
总结:
1、public继承不改变基类成员的访问权限;
2、private继承使得基类所有成员在子类中的访问权限变为private;
3、protected继承将基类中public成员变为子类的protected成员,其它成员的访问 权限不变;
4、基类中的private成员不受继承方式的影响,子类永远无权访问。
此外,在使用private继承时,还存在另外一种机制:准许访问 。
实验1:测试程序2(10分)
5-4代码如下:
package abstractClasses; /** * This program demonstrates abstract classes. * @version 1.01 2004-02-21 * @author Cay Horstmann */ public class PersonTest { public static void main(String[] args) { var people = new Person[2]; //定义一个包含2个雇员的数组; // fill the people array with Student and Employee objects people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1); people[1] = new Student("Maria Morris", "computer science"); //将雇员和学生的对象填充到Person引用数组; // print out names and descriptions of all Person objects for (Person p : people) System.out.println(p.getName() + ", " + p.getDescription()); // 输出对象的姓名和信息描述; } }
5-5代码如下:
package abstractClasses; public abstract class Person // 使用abstract关键字; { public abstract String getDescription(); // 包含一个或多个抽象方法的类本身必须被声明为抽象的; private String name; public Person(String name) { this.name = name; } public String getName() { return name; //Person类中还保存着姓名和一个返回姓名的方法; } }
5-6代码如下:
package abstractClasses; import java.time.*; public class Employee extends Person //由继承Person类来定义Employee类的格式,关键字extend表示继承; { private double salary; private LocalDate hireDay; //类的实例域定义来存放的需要操作的数据; public Employee(String name, double salary, int year, int month, int day) { super(name); this.salary = salary; hireDay = LocalDate.of(year, month, day); //根据参数设置日期,参数分别为年月日; } public double getSalary() { return salary; //返回salary属性的值; } public LocalDate getHireDay() { return hireDay; //返回hireDay的值; } public String getDescription() { return String.format("an employee with a salary of $%.2f", salary); } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; //调用方法的对象salary实例域设置为新值; } }
5-7代码如下:
package abstractClasses; public class Student extends Person { private String major; /** * @param name the student's name * @param major the student's major */ public Student(String name, String major) { // pass name to superclass constructor super(name); this.major = major; //将名称传递给超类构造函数; } public String getDescription() { return "a student majoring in " + major; // 返回学生信息; } }
5-4,5-5,5-6,5-7运行结果:
删除程序中Person类,PerTest类,背景录入删除类的程序代码,在代码录入中理解抽象类与子类的关系和使用特点。
删除Person类:
package abstractClasses; public abstract class Person // 使用abstract关键字; { public abstract String getDescription(); // 包含一个或多个抽象方法的类本身必须被声明为抽象的; private String name; public Person(String name) {
// TODO Auto-generated constructor stub this.name = name; } public String getName() { return name; } }
删除PersonTest类:
package abstractClasses; /** * This program demonstrates abstract classes. * @version 1.01 2004-02-21 * @author Cay Horstmann */ public class PersonTest { public static void main(String[] args) { var people = new Person[2]; people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1); people[1] = new Student("Maria Morris", "computer science"); for (Person p : people) System.out.println(p.getName() + ", " + p.getDescription()); } }
删除后的运行结果:
用abstract关键字声明的方法和类是抽象方法和抽象类
1)抽象类不能够实例对象,既不能有构造方法,抽象类可以继承,其中的抽象方法也继承下去,必须非抽象子类实现
2)抽象方法不能有方法体,其方法体必须非抽象子类实现
3)子类用extends关键字继承父类,子类不能直接访问父类的私有领域
抽象类的特点
1.抽象类和抽象方法必须用abstract关键字修饰
2.抽象类中不一定有抽象方法,但是有抽象方法的类必须定位为抽象类
3.抽象类不能实例化
因为他不是具体的,抽象类也有构造方法,但是不能实例化,那么构造方法的作用是什么呢?
用于子类访问父类数据的初始化。
4.抽象类的子类问题
(1)如果不想重写抽象方法,该子类是一个抽象类
(2)重写所有的抽象方法,这个时候子类是一个具体的类。
5.抽象类的实例化其实是靠具体的子类来实现的,是使用多态的方式 Animal a = new Cat();
实验1:测试程序3(11分)
5-8代码如下:
package equals; /** * This program demonstrates the equals method. * @version 1.12 2012-01-26 * @author Cay Horstmann */ public class EqualsTest // 实现Employee类和Manager类的equals,hashCode方法 { public static void main(String[] args) { var alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15); //初始化alice1 var alice2 = alice1; //将Alice1的值赋给alice2 var alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15); //初始化alice3 var bob = new Employee("Bob Brandson", 50000, 1989, 10, 1); System.out.println("alice1 == alice2: " + (alice1 == alice2)); System.out.println("alice1 == alice3: " + (alice1 == alice3)); System.out.println("alice1.equals(alice3): " + alice1.equals(alice3)); System.out.println("alice1.equals(bob): " + alice1.equals(bob)); System.out.println("bob.toString(): " + bob); var carl = new Manager("Carl Cracker", 80000, 1987, 12, 15); var boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000); System.out.println("boss.toString(): " + boss); System.out.println("carl.equals(boss): " + carl.equals(boss)); System.out.println("alice1.hashCode(): " + alice1.hashCode()); System.out.println("alice3.hashCode(): " + alice3.hashCode()); System.out.println("bob.hashCode(): " + bob.hashCode()); //在这里实现Employee类和Manager类的hashCode方法 System.out.println("carl.hashCode(): " + carl.hashCode()); } }
5-9代码如下:
package equals; import java.time.*; import java.util.Objects; public class Employee { private String name; private double salary; private LocalDate hireDay; //类的实例域定义来存放的需要操作的数据; public Employee(String name, double salary, int year, int month, int day) { this.name = name; this.salary = salary; hireDay = LocalDate.of(year, month, day); } public String getName() { return name; // 返回name属性的值; } public double getSalary() { return salary; //返回salary属性的值; } public LocalDate getHireDay() { return hireDay; // 返回hireDay属性的值; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; //调用方法的对象salary实例域设置为新值; } public boolean equals(Object otherObject) { // a quick test to see if the objects are identical if (this == otherObject) return true; //检测this与otherObject是否引用同一个对象; // must return false if the explicit parameter is null if (otherObject == null) return false; //检测otherObject是否为null,如果为null返回false; // if the classes don't match, they can't be equal if (getClass() != otherObject.getClass()) return false; //getClass方法将返回一个对象所属的类; // now we know otherObject is a non-null Employee var other = (Employee) otherObject; //知道otherObject是一个非空的Employee; // test whether the fields have identical values 测试字段是否具有相同的值; return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay); } public int hashCode() { return Objects.hash(name, salary, hireDay); } public String toString() { return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]"; } }
5-10代码如下:
package equals; public class Manager extends Employee //由继承Employee类来定义Manager类的格式,关键字extend表示继承 { private double bonus; public Manager(String name, double salary, int year, int month, int day) { super(name, salary, year, month, day); bonus = 0; //自动生成构造函数存根; } public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; } public void setBonus(double bonus) { this.bonus = bonus; } public boolean equals(Object otherObject) { if (!super.equals(otherObject)) return false; var other = (Manager) otherObject; // super.equals checked that this and other belong to the same class return bonus == other.bonus; //检查这个和其他是否属于同一个类; } public int hashCode() { return java.util.Objects.hash(super.hashCode(), bonus); } public String toString() { return super.toString() + "[bonus=" + bonus + "]"; } }
5-8,5-9,5-10运行截图:
实验2:编程练习(20分)
定义抽象类Shape:
属性:不可变常量double PI,值为3.14;
方法:public double getPerimeter();public double getArea())。
让Rectangle与Circle继承自Shape类。
编写double sumAllArea方法输出形状数组中的面积和和double sumAllPerimeter方法输出形状数组中的周长和。
main方法中
1)输入整型值n,然后建立n个不同的形状。如果输入rect,则再输入长和宽。如果输入cir,则再输入半径。
2) 然后输出所有的形状的周长之和,面积之和。并将所有的形状信息以样例的格式输出。
3) 最后输出每个形状的类型与父类型,使用类似shape.getClass()(获得类型),shape.getClass().getSuperclass()(获得父类型);
思考sumAllArea和sumAllPerimeter方法放在哪个类中更合适?
输入样例:
输出样例:
实验代码如下:
import java.util.*; public class Main { public static double sumAllArea(double areaall) { return areaall; } public static double sumAllPerimeter(double perimeterall) { return perimeterall; } public static void main(String[] args) { // TODO Auto-generated method stub Scanner sc = new Scanner(System.in); int n = sc.nextInt(); sc.nextLine(); Shape []xz = new Shape[n]; double sumAllArea = 0, sumAllPerimeter = 0; for(int i = 0;i < n;i++) { String ss = sc.nextLine(); if(ss.equals("rect")) { int a = sc.nextInt(),b = sc.nextInt(); sc.nextLine(); xz[i] = new Rectangle(a,b); } if(ss.equals("cir")) { int r = sc.nextInt(); sc.nextLine(); xz[i] = new Circle(r); } sumAllArea += xz[i].getArea(); sumAllPerimeter += xz[i].getPerimeter(); } System.out.println(sumAllPerimeter(sumAllPerimeter)); System.out.println(sumAllArea(sumAllArea)); System.out.print("["); for(int i = 0;i < n;i++) { if(i != 0) System.out.print(", "); System.out.print(xz[i].toString()); } System.out.println("]"); for(int i = 0;i < n;i++) { System.out.println(xz[i].getClass()+","+xz[i].getClass().getSuperclass()); } sc.close(); } } abstract class Shape { final double PI = 3.14; public abstract double getPerimeter(); public abstract double getArea(); } class Rectangle extends Shape { public int width; public int length; public Rectangle(int width, int length) { super(); this.width = width; this.length = length; } @Override public String toString() { return "Rectangle [width=" + width + ", length=" + length + "]"; } public double getPerimeter() { // TODO Auto-generated method stub return 2*(width+length); } public double getArea() { // TODO Auto-generated method stub return width*length; } } class Circle extends Shape { public int radius; public Circle(int radius) { super(); this.radius = radius; } @Override public String toString() { return "Circle [radius=" + radius + "]"; } public double getPerimeter() { // TODO Auto-generated method stub return 2*PI*radius; } public double getArea() { // TODO Auto-generated method stub return PI*radius*radius; } }
实验运行结果:
3. 实验总结:(10分)
对继承进行小结:封装、继承和多态是面向对象的主要特征;e继承可提高代码重用性,用extends关键字来实现;除构造方法之外,父类的所有方法和属性都被子类继承;继承建立了类与类间的关系,同时也是多态特征的前提;java只支持单继承,不直接支持多继承(避免两个父类出现同名方法的调用选择困难);abstract修饰的抽象类不能被实例化为对象,只能扩展子类;抽象类中的抽象方法充当着占位的角色,它们的具体实现在子类中;final类不允许被继承;类中final方法不允许被子类重写。等这些重要的基础知识需要熟记并在代码上加以理解;通过前一周的学习和在国庆期间的自学,我初步掌握了有关继承类的知识,使用继承类更能简化程序。了解了子类和父类的关系。另外,学习了抽象类的用法,在运行程序的过程中,加深了对它的理解。将会程序分装模块化,使得我对问题有了一定的了解并且尝试解决。并且在实验课上,进行了小测试实验,在测试中我们意识到了自己的不足,在测试中我们对例题的导入以及错误的修改更加深入的了解,对继承的相关知识也有了更进一步的掌握,在接下来的学习中要更加努力。