夏热握火

导航

201871010125 王玉江 《面向对象程序设计(Java)》第八周实验总结

 

项目

内容

这个作业属于哪个课程

https://www.cnblogs.com/nwnu-daizh/

这个作业的要求在哪里

https://www.cnblogs.com/nwnu-daizh/p/11703678.html

作业学习目标

(1) 掌握接口定义方法;

(2) 掌握实现接口类的定义要求;

(3) 掌握实现了接口类的使用要求;

(4) 掌握程序回调设计模式;

(5) 掌握Comparator接口用法;

(6) 掌握对象浅层拷贝与深层拷贝方法;

(7) 掌握Lambda表达式语法;

(8) 了解内部类的用途及语法要求。

 

第一部分:总结第六章理论知识(30分) 

6.1 接口

6.1.1 接口概念
1.Comparable接口:都需要包含compareTo方法,注意在Java SE 5.0中,Comparable接口已经改进为泛型类型。

2.(1)接口的所有方法自动属于public。因此,在接口中声明方法时不必提供关键字public。

   (2)接口绝不能含有实例域,在Java SE 8之前,也不能在接口中实现方法。

   (3)提供实例域和方法实现的任务应该由实现接口的那个类来完成。

3.为了让类实现一个接口,通常需要下面两个步骤:

       1)将类声明为实现给定接口

       2)对接口中的所有方法进行定义

4.Double.compare静态方法,如果第一个参数小于第二个参数,它会返回一个负值;如果二者相等则返回0;否则返回一个正值。

      注意:在实现接口时,必须把方法声明为public;否则编译器将认为这个方法的访问属性是包可见性,即类的默认访问属性,之后编译器就会给出试图提供更严格的访问权限的警告信息。

      提示:Comparable接口中的compareTo方法将返回一个整型数值。如果两个对象不相等,则返回一个正值或者一个负值。

   主要原因在于 Java 程序设计语言是一种强类型 (strongly typed) 语言。在调用方法的时候, 编译器将会检查这个方法是否存在。

注释: 有人认为, 将 Arrays 类中的 sort 方法定义为接收一个 Comparable[ ] 数组就可以在使用元素类型没有实现 Comparable 接口的数组作为参数调用 sort 方法时, 由编译器给出错误报告。但事实并非如此。 在这种情况下, sort 方法可以接收一个 Object[ ] 数组, 并对其进行笨拙的类型转换。

         【API】java.lang.Comparable<T> 1.0 :

int compareTo(T other) 用这个对象与 other 进行比较。 如果这个对象小于 other 则返回负值; 如果相等则返回0;否则返回正值。

         【API】java.util.Arrays 1.2 :

static void sort(Object[] a) 使用 mergesort 算法对数组 a 中的元素进行排序。要求数组中的元素必须属于实现了Comparable 接口的类, 并且元素之间必须是可比较的 。
         【API】java.lang.Integer 1.0 :

static int compare(int x, int y) 7

如果 x < y 返回一个负整数;如果 x 和 y 相等,则返回 0; 否则返回一个负整数。
        【API】java.lang.Double 1.0 :

static int compare(double x, double y) 1.4

5.如果 x < y 返回一个负数;如果 x 和 y 相等则返回 0; 否则返回一个负数,与 equals 方 法 一 样, 在 继 承 过 程 中 有 可 能 会 出 现 问 题。

6.1.2 接口的特性
1.接口不是类,尤其不能使用 new 运算符实例化一个接口然而, 尽管不能构造接口的对象,却能声明接口的变量接口变量必须引用实现了接口的类对象接下来, 如同使用 instanceof 检查一个对象是否属于某个特定一一样, 也可以使用instanceof 检查一个对象是否实现了某个特定的接口,与可以建立类的继承关系一样,接口也可以被扩展。这里允许存在多条从具有较高通用性的接口到较高专用性的接口的链 。虽然在接口中不能包含实例域或静态方法,但却可以包含常量。

2.与接口中的方法都自动地被设置为 public—样,接口中的域将被自动设为 public static final。

注意:可以将接口方法标记为 public, 将域标记为 public static final

3.有些接口只定义了常量, 而没有定义方法。

例如, 在标准库中有一个 SwingConstants就是这样一个接口, 其中只包含 NORTH、 SOUTH 和 HORIZONTAL 等常量。 任何实现SwingConstants 接口的类都自动地继承了这些常量, 并可以在方法中直接地引用 NORTH,而不必采用 SwingConstants.NORTH 这样的形式。

4. 尽管每个类只能够拥有一个超类, 但却可以实现多个接口。 如果某个类实现了这个 Cloneable 接口,Object 类中的 clone 方法就可以创建类对象的一个拷贝。 

 

1 )因为java不支持多重继承,所以有了接口,一个类只能继承一个父类,但可以实现多个接口,接口本身也可以继承多个接口。
2 )接口里面的成员变量默认都是public static final类型的。必须被显示的初始化。
3 )接口里面的方法默认都是public abstract类型的。隐式声明。
4 )接口没有构造方法,不能被实例化。
5 )接口不能实现另一个接口,但可以继承多个接口。
6 )类如果实现了一个接口,那么必须实现接口里面的所有抽象方法,否则类要被定义为抽象类。

 

6.1.3 接口和抽象类
1.用abstract来声明,没有具体实例对象的类,不能用new来创建对象。可包含常规类所包含的任何东西。抽象类必须由子类继承,如果abstract类的子类不是抽象类,那么子类必须重写父类中所有的abstract方法。

2.接口:用interface声明,是抽象方法和常量值定义的集合。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的定义。接口中只能定义抽象方法,而且这些方法默认为是public的。只要类实现了接口,就可以在任何需要该接口的地方使用这个类的对象。此外,一个类可以实现多个接口。

3.接口与抽象类的区别:(1)接口不能实现任何方法,而抽象类可以。(2)类可以实现许多接口,但只有一个父类。(3)接口不是类分级结构的一部分,无任何联系的类可以实现相同的接口

6.1.4 静态方法

在 Java SE 8 中,允许在接口中增加静态方法。理论上讲,没有任何理由认为这是不合法的。 只是这有违于将接口作为抽象规范的初衷。目前为止, 通常的做法都是将静态方法放在伴随类中。在标准库中, 你会看到成对出现的接口和实用工具类,不过整个 Java 库都以这种方式重构也是不太可能的, 但是实现你自己的接口时,不再需要为实用工具方法另外提供一个伴随类。

6.1.5 默认方法
1.可以为接口方法提供一个默认实现。 必须用 default 修饰符标记这样一个方法。当然, 这并没有太大用处, 因为 Comparable 的每一个实际实现都要覆盖这个方法。不过有些情况下, 默认方法可能很有用。默认方法可以调用任何其他方法。

2.默认方法的一个重要用法是“‘ 接口演化” (interface evolution)。 以 Collection 接口为例,这个接口作为 Java 的一部分已经有很多年了。 假设很久以前你提供了这样一个类:public class Bag implements Collection

假设 stream 方法不是一个默认方法。那么 Bag 类将不能编译, 因为它没有实现这个新方法。 后来, 在 JavaSE 8 中, 又为这个接口增加了一个 stream 方法。 为接口增加一个非默认方法不能保证“ 源代码兼容 ”(source compatible)。

不过, 假设不重新编译这个类, 而只是使用原先的一个包含这个类的 JAR 文件。这个类仍能正常加载,尽管没有这个新方法。程序仍然可以正常构造 Bag 实例, 不会有意外发生。不过, 如果程序在一个 Bag 实例上调用 stream方法,就会出现一个 AbstractMethodError。

将方法实现为一个默认方法就可以解决这两个问题。 Bag 类又能正常编译了。另外如果没有重新编译而直接加载这个类, 并在一个 Bag 实例上调用 stream 方法, 将调用 Collection.stream 方法。

6.1.6 解决默认方法冲突
1.如果先在一个接口中将一个方法定义为默认方法, 然后又在超类或另一个接口中定义了同样的方法, 会发生的情况规则如下:

1 ) 超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。

2 ) 接口冲突。 如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型 (不论是否是默认参数)相同的方法, 必须覆盖这个方法来解决冲突。

2.第二个规则。

Java 设计者更强调一致性。两个接口如何冲突并不重要。 如果至少有一个接口提供了一个实现, 编译器就会报告错误。

注释:当然,如果两个接口都没有为共享方法提供默认实现, 那么就与 Java SE 8之前的情况一样,这里不存在冲突。 实现类可以有两个选择: 实现这个方法, 或者干脆不实现。如果是后一种情况, 这个类本身就是抽象的。

3.另一种情况, 一个类扩展了一个超类,同时实现了一个接口,并从超类和接口继承了相同的方法。在这种情况下, 只会考虑超类方法, 接口的所有默认方法都会被忽略。 这正是“ 类优先” 规则。“ 类优先” 规则可以确保与 Java SE 7 的兼容性。 如果为一个接口增加默认方法,这对于有这个默认方法之前能正常工作的代码不会有任何影响。

   注意:千万不要让一个默认方法重新定义 Object 类中的某个方法。 例如, 不能为 toString或 equals 定义默认方法, 尽

6.2 接口示例
6.2.1 接口和回调
1.回调( callback) 是一种常见的程序设计模式。在这种模式中, 可以指出某个特定事件发生时应该采取的动作。

              【API】javax.swing.JOptionPane 1.2 :

static void showMessageDialog(Component parent, Object message) 显示一个包含一条消息和 OK 按钮的对话框。 这个对话框将位于其 parent 组件的中央。如果 parent 为 null , 对话框将显示在屏幕的中央。
              【API】javax.swing.Timer 1.2 :

               Timer(int interval, ActionListener listener) 构造一个定时器, 每隔 interval 毫秒通告 listener—次 。
void start() 启动定时器。一旦启动成功, 定时器将调用监听器的 actionPerformed。
void stop() 停止定时器。一旦停止成功, 定时器将不再调用监听器的 actionPerformed。
              【API】java.awt.Toolkit 1.0 :

static Toolkit getDefaultToolKit() 获得默认的工具箱。 工具箱包含有关 GUI 环境的信息。


6.2.2 Comparator接口
1.我们希望按长度递增的顺序对字符串进行排序,而不是按字典顺序进行排序。肯定不能让 String 类用两种不同的方式实现 compareTo 方法—更何况,String 类也不应由我们来修改。要处理这种情况,Arrays.Sort 方法还有第二个版本, 有一个数组和一个比较器 ( comparator ) 作为参数, 比较器是实现了 Comparator 接口的类的实例。将这个调用与 words[i].compareTo(words[j]) 做比较。这个 compare 方法要在比较器对象上调用, 而不是在字符串本身上调用。

注意:尽管 LengthComparator 对象没有状态, 不过还是需要建立这个对象的一个实例。我们需要这个实例来调用 compare 方法——它不是一个静态方法, 利用 lambda 表达式可以更容易地使用 Comparator。

6.2.3 对象克隆
1.如果希望 copy 是一个新对象,它的初始状态与 original 相同, 但是之后它们各自会有自己不同的状态, 这种情况下就可以使用 clone 方法。不过,clone 方法是 Object 的一个 protected 方法, 这说明你的代码不能直接调用这个方法。可以看到, 默认的克隆操作是“ 浅拷贝”,并没有克隆对象中引用的其他对象。

2.浅拷贝的影响

  如果原对象和浅克隆对象共享的子对象是不可变的, 那么这种共享就是安全的。如果子对象属于一个不可变的类, 如 String, 就是这种情况。或者在对象的生命期中, 子对象一直包含不变的常量, 没有更改器方法会改变它, 也没有方法会生成它的引用,这种情况下同样是安全的。不过, 通常子对象都是可变的, 必须重新定义 clone 方法来建立一个深拷贝, 同时克隆所有子对象。

3.对于每一个类,需要确定:

1 ) 默认的 clone 方法是否满足要求;
2 ) 是否可以在可变的子对象上调用 clone 来修补默认的 clone 方法;
3 ) 是否不该使用 clone 。

实际上第 3 个选项是默认选项。 如果选择第 1 项或第 2 项,类必须:

1 ) 实现 Cloneable 接口;
2 ) 重新定义 clone 方法,并指定 public 访问修饰符 。

注释:Cloneable 接口是 Java 提供的一组标记接口 ( tagging interface ) 之一, Comparable 等接口的通常用途是确保一个类实现一个或一组特定的方法。

          标记接口不包含任何方法; 它唯一的作用就是允许在类型查询中使用 instanceof。建议你自己的程序中不要使用标记接口。

 即使 clone 的默认(浅拷贝)实现能够满足要求, 还是需要实现 Cloneable 接口, 将 clone重新定义为 public, 再调用 super.clone()。

4.与 Object.clone 提供的浅拷贝相比, 前面的 clone 方法并没有为它增加任何功能。这里只是让这个方法是公有的。 要建立深拷贝, 还需要做更多工作,克隆对象中可变的实例域。

    如果在一个对象上调用 clone, 但这个对象的类并没有实现 Cloneable 接口, Object 类的 clone 方法就会拋出一个 CloneNotSupportedException。必须当心子类的克隆。

5.在自己的类中实现 clone , 如果需要建立深拷贝,可能就需要实现这个方法。有些人认为应该完全避免使用 clone, 而实现另一个方法来达到同样的目的。 

注意:所有数组类型都有一个 public 的 clone 方法, 而不是 protected: 可以用这个方法建立一个新数组, 包含原数组所有元素的副本。 

6.浅层拷贝与深层拷贝浅层拷贝:被拷贝对象的所有常量成员和基本类型属性都有与原来对象相同的拷贝值,而若成员域是一个对象,则被拷贝对象该对象域的对象引用仍然指向原来的对象。

    深层拷贝:被拷贝对象的所有成员域都含有与原来对象相同的值,且对象域将指向被复制过的新对象,而不是原有对象被引用的对象。换言之,深层拷贝将拷贝对象内引用的对象也拷贝一遍。

6.3 lambda表达式
6.3.1 为什么引入lambda表达式
1.lambda 表达式是一个可传递的代码块, 可以在以后执行一次或多次。

2.在 Java 中传递一个代码段并不容易, 不能直接传递代码段 。Java 是一种面向对象语言, 所以必须构造一个对象,这个对象的类需要有一个方法能包含所需的代码 。

6.3.2 lambda表达式的语法

1.Lambda表达式的语法基本结构(arguments)->body

   有如下几种情况: 参数类型可推导时,不需要指定类型,如(a)->System.out.println(a)

                               只有一个参数且类型可推导时,不强制写(),如a->System.out.println(a)

                               参数指定类型时,必须有括号,如(inta)>System.out.println(a)

                               参数可以为空,如()->System.out.println(“hello”)

                               body需要用包含语句,当只有一条语句时&可省略;

2.Java Lambda表达式是Java8引入的一个新的功能,主要用途是提供一个函数化的语法来简化编码。

   Lambda表达式本质上是一个匿名方法。

     public int add(int x,int y){returnx+y;}

        转成Lambda表达式后是这个样子:(int x,int y)->x+y;

           参数类型也可以省略,Java编译器会根据上下文推断出来:(x,y)->x+y;//返回两数之和或者(x,y)->{returnx+y;}//显式指明返回值

6.3.3 函数式接口
1.对于只有一个抽象方法的接口, 需要这种接口的对象时, 就可以提供一个 lambda 表达式。这种接口称为函数式接口 (functional interface )。

   注意: 接口完全有可能重新声明 Object 类的方法, 如 toString 或 clone,这些声明有可能会让方法不再是抽象的。

2. Arrays.sort 方法。它的第二个参数需要一个Comparator 实例, Comparator 就是只有一个方法的接口, 所以可以提供一个 lambda 表达式 。

   在底层, Arrays.sort 方法会接收实现了 Comparator<String> 的某个类的对象。 在这个对象上调用 compare 方法会执行这个 lambda 表达式的体。

   这些对象和类的管理完全取决于具体实现, 与使用传统的内联类相比, 这样可能要高效得多。最好把 lambda 表达式看作是一个函数, 而不是一个对象, 另外要接受 lambda 表达式可以传递到函数式接口。

 3. lambda 表达式可以转换为接口,在 Java 中, 对 lambda 表达式所能做的也只是能转换为函数式接口。在其他支持函数字面量的程序设计语言中,可以声明函数类型(如(String, String) -> int )、 声明这些类型的变量,还可以使用变量保存函数表达式。

注意:(1)甚至不能把lambda 表达式赋给类型为 Object 的变量,Object 不是一个函数式接口。 *

   (2)Java API 在 java.util.function 包中定义了很多非常通用的函数式接口。其中一个接口BiFunction<T, U, R> 描述了参数类型为 T 和 U 而且返回类型为 R 的函数。可以把我们的字符串比较 lambda 表达式保存在这个类型的变量中 。

  (3)java.util.function 包中有一个尤其有用的接口 Predicate 。

  (4 )ArrayList 类有一个 removeIf 方法, 它的参数就是一个 Predicate。这个接口专门用来传递lambda 表达式。

6.3.4 方法引用
1.表达式 System.out::println 是一个方法引用( method reference ), 它等价于 lambda 表达式x -> System.out.println(x)

2.要用 :: 操作符分隔方法名与对象或类名。 主要有3种情况:

object::instanceMethod
Class::staticMethod
Class::instanceMethod
在前 2 种情况中, 方法引用等价于提供方法参数的 lambda 表达式。前面已经提到,System.out::println 等价于 x -> System.out.println(x) 。类似地, Math::pow 等价于(x,y) ->Math.pow(x, y)。

对于第 3 种情况, 第 1 个参数会成为方法的目标。例如,String::compareToIgnoreCase 等

3.如果有多个同名的重载方法, 编译器就会尝试从上下文中找出你指的那一个方法。例如, Math.max 方法有两个版本, 一个用于整数, 另一个用于 double 值。选择哪一个版本取决于 Math::max 转换为哪个函数式接口的方法参数。 类似于 lambda 表达式, 方法引用不能独立存在,总是会转换为函数式接口的实例。 *

4.可以在方法引用中使用 this 参数。 例如, this::equals 等同于 x-> this.equals(x)。 使用super 也是合法的。Super::instanceMethod使用this为目标,会调用给定方法的超类版本。

6.3.5 构造器引用
1.构造器引用与方法引用很类似,只不过方法名为 new。

2.可以用数组类型建立构造器引用。例如, int[]::new 是一个构造器引用,它有一个参数:即数组的长度。这等价于 lambda 表达式 x-> new int[x] 。

3.Java 有一个限制,无法构造泛型类型 T 的数组。数组构造器引用对于克服这个限制很有用。表达式 new T[n] 会产生错误,因为这会改为 new Object[n] 。对于开发类库的人来说,这是一个问题。例如,假设我们需要一个 Person 对象数组。 Stream 接口有一个 toArray 方法可以返回 Object 数组。

6.3.6 变量作用域
1.对 lambda 表达式的理解 lambda 表达式有 3个部分:

1 ) 一个代码块;
2 ) 参数;
3 ) 自由变量的值, 这是指非参数而且不在代码中定义的变量。

2.表示 lambda 表达式的数据结构必须存储自由变量的值,(如: 可以把一个 lambda 表达式转换为包含一个方法的对象,这样自由变量的值就会复制到这个对象的实例变量中。)

3.lambda 表达式可以捕获外围作用域中变量的值。 在 Java 中, 要确保所捕获的值是明确定义的,这里有一个重要的限制。在 lambda 表达式中, 只能引用值不会改变的变量。之所以有这个限制是有原因的。 如果在 lambda 表达式中改变变量, 并发执行多个动作时就会不安全。对于目前为止我们看到的动作不会发生这种情况,不过一般来讲,这确实是一个严重的问题。另外如果在 lambda 表达式中引用变量, 而这个变量可能外部改变,这也是不合法的。

3.(1)lambda 表达式中捕获的变量必须实际上是最终变量 ( effectively final)。实际上的最终变量是指, 这个变量初始化之后就不会再为它赋新值。

    (2)lambda 表达式的体与嵌套块有相同的作用域。这里同样适用命名冲突和遮蔽的有关规则。在 lambda 表达式中声明与一个局部变量同名的参数或局部变量是不合法的。

    (3)在方法中,不能有两个同名的局部变量, 因此, lambda 表达式中同样也不能有同名的局部变量。

4.在一个 lambda 表达式中使用 this 关键字时, 是指创建这个 lambda 表达式的方法的 this参数。 在 lambda 表达式中, this 的使用并没有任何特殊之处。lambda 表达式的作用域嵌套在 init 方法中,与出现在这个方法中的其他位置一样, lambda 表达式中 this 的含义并没有变化 。

6.3.7 处理lambda表达式
1.使用 lambda 表达式的重点是延迟执行( deferred execution )。 之所以希望以后再执行代码, 这有很多原因, 如:在一个单独的线程中运行代码;多次运行代码;在算法的适当位置运行代码 (例如, 排序中的比较操作;)发生某种情况时执行代码 (如, 点击了一个按钮, 数据到达, 等等;)只在必要时才运行代码。

2.(1)Runnable接口,(2)基本类型的函数式接口:

    假设要编写一个方法来处理满足某个特定条件的文件。 对此有一个遗留接口 java.io.FileFilter, 不过最好使用标准的Predicate<File> , 只有一当已经有很多有用的方法可以生成 FileFilter 。

    注意:(1)大多数标准函数式接口都提供了非抽象方法来生成或合并函数。 例如, Predicate.isEqual(a) 等同于 a::equals, 不过如果 a 为 null 也能正常工作。 已经提供了默认方法 and、or 和 negate 来合并谓词。               (2)如果设计你自己的接口,其中只有一个抽象方法,可以用 @FunctionalInterface 注解来标记这个接口。 这样做有两个优点。 如果你无意中增加了另一个非抽象方法, 编译器会产生一个错误消息。 另外 javadoc 页里会指出你的接口是一个函数式接口。并不是必须使用注解根据定义, 任何有一个抽象方法的接口都是函数式接口。

6.3.8 再谈Comparator
1.Comparator 接口包含很多方便的静态方法来创建比较器。 这些方法可以用于 lambda 表达式或方法引用。

2.(1)静态 comparing 方法取一个“ 键提取器” 函数, 它将类型 T 映射为一个可比较的类型( 如 String )。 对要比较的对象应用这个函数, 然后对返回的键完成比较。 

   (2)另外, comparing 和 thenComparing 方法都有变体形式,可以避免 int、 long 或 double 值的装箱。

   (3)如果键函数可以返回 null, 可能就要用到 nullsFirst 和 nullsLast适配器。 这些静态方法会修改现有的比较器,从而在遇到 null 值时不会抛出异常, 而是将这个值标记为小于或大于正常值。

3.nullsFirst 方法需要一个比较器, 在这里就是比较两个字符串的比较器。 naturalOrder 方法可以为任何实现了 Comparable 的类建立一个比较器。

4.静态 reverseOrder 方法会提供自然顺序的逆序。要让比较器逆序比较, 可以使用 reversed实例方法 。

6.4 内部类

内部类(inner class)是定义在一个类内部的类。外层的类成为外部类(outer class).内部类主要用于事件处理。使用内部类的原因:

内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据。
内部类可以对同一个包中的其他类隐藏起来。
当想要定义一个回调函数且不想编写大量代码时,使用匿名 (anonymous) 内部类比较便捷。

6.4.1 使用内部类访问对象状态
1.内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域为了能够运行这个程序, 内部类的对象总有一个隐式引用, 它指向了创建它的外部类对象。 这个引用在内部类的定义中是不可见的。外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器, 添加一个外围类引用的参数。

注意:只有内部类可以是私有类,而常规类只可以具有包可见性,或公有可见性。

6.4.2 内部类的特殊语法规则

1.使用外围类引用:OuterClass.this

if(TalkingCLock.this.beep) ...

2.编写内部类对象的构造器:outerObject.new InnerClass(contruction parameters)

ActionListener listener = this.new TimerPrinter();

3.在外围类的作用域之外引用内部类:

OuterClass.InnerClass

注意: 内部类中声明的所有静态域都必须是 final。因为我们希望一个静态域只有一个实例, 不过对于每个外部对象, 会分别有一个单独的内部类实例。如果这个域不是 final, 它可能就不是唯一的。
内部类不能有 static 方法。Java 语言规范对这个限制没有做任何解释。也可以允许有静态方法,但只能访问外围类的静态域和方法。

6.4.3 内部类是否有用、必要和安全

 1.编译器为了引用外围类, 生成了一个附加的实例域 this$0 (名字this$0 是由编译器合成的,在自己编写的代码中不能够引用它)。另外,还可以看到构造器的TalkingClock(外围类) 参数 。

    由于内部类拥有访问特权, 所以与常规类比较起来功能更加强大。  

注意:假设将 TimePrinter 转换为一个内部类。在虚拟机中不存在私有类, 因此编译器将会利用私有构造器生成一个包可见的类:private TalkingClock$TimePrinter(TalkingClock);

          当然,没有人可以调用这个构造器, 因此, 存在第二个包可见构造器TalkingClock$TimePrinter(TalkingGock, TalkingClock$1);它将调用第一个构造器。

          编译器将 TalkingClock 类 start 方法中的构造器调用翻译为:new TalkingClock$TimePrinter(this, null)

6.4.4 局部内部类

1.可以在一个方法中定义局部类,并且不能用public或private访问说明符进行声明,它的作用域被限定在声明这个局部类的块中。

2.局部类可以对外部世界完全隐藏起来,即使方法所在类中的其他代码也不能访问。除了定义它的方法外,没有任何方法知道它的存在。

3.局部类的另一个优势:不仅可以访问包含它们的外部类,还可以访问局部变量,但那些局部变量必须被声明为final

 

复制代码
public void start(int interval,final boolean beep)
{
    class TimePrinter implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            if(beep)//局部类访问局部变量
           Toolkit.getDefaultToolkit().beep();
        }
    }
    ActionListener listener = new TimePrinter();
    Time t = new Timer(interval,listener);
    t.start();
}
复制代码

 

4.局部类不能用 public 或 private 访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。

6.4.5 由外部方法访问变量

1.与其他内部类相比较, 局部类的优点。它们不仅能够访问包含它们的外部类, 还可以访问局部变量。不过, 那些局部变量必须事实上为 final。这说明, 它们一旦赋值就绝不会改变。

注意:在 JavaSE 8 之前, 必须把从局部类访问的局部变量声明为 final。有时, final 限制显得并不太方便。 补救的方法是使用一个长度为 1 的数组

          在内部类被首次提出时, 原型编译器对内部类中修改的局部变量自动地进行转换。不过, 后来这种做法被废弃。。同时在多个线程中执行内部类中的代码时, 这种并发更新会导致竞态条件

6.4.6 匿名内部类
1.将局部内部类的使用再深人一步。 假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类(anonymous inner class)。

2.由于构造器的名字必须与类名相同, 而匿名类没有类名, 所以, 匿名类不能有构造器。取而代之的是,将构造器参数传递给超类 ( superclass) 构造器。尤其是在内部类实现接口的时候, 不能有任何构造参数。如果构造参数的闭小括号后面跟一个开大括号, 正在定义的就是匿名内部类 。

注意:建立一个与超类大体类似(但不完全相同)的匿名子类通常会很方便。不过, 对于 equals 方法要特别当心。

3.SuperType可以是接口,内部类就要实现这个接口;也可以是一个类,内部类就要扩展它。

复制代码
new SuperType(construction parameters)
{
   inner class methods and data
}
new InterfaceType()
{
   methods and data
}

public void start(int interval, final boolean beep)
{
   //创建一个实现AL接口的类的新对象
   ActionListener listener =  new ActionListener()
   {
       ...
   }
   ...
}
复制代码

6.4.7 静态内部类
1.有时使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为 static, 以便取消产生的引用。 只有内部类可以声明为 static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外, 与其他所冇内部类完全一样。在我们列举的示例中, 必须使用静态内部类,这是由于内部类对象是在静态方法中构造的

注意:(1)在内部类不需要访问外围类对象的时候, 应该使用静态内部类。 有些程序员用嵌套类 (nested class ) 表示静态内部类

          (2)与常规内部类不同,静态内部类可以有静态域和方法。

        (3)声明在接口中的内部类自动成为 static 和 public 类

6.5 代理
代理 ( proxy)。 利用代理可以在运行时创建一个实现了一组给定接口的新类 : 这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。

6.5.1 何时使用代理
1.假设有一个表示接口的 Class 对象(有可能只包含一个接口,) 它的确切类型在编译时无法知道。这确实有些难度。要想构造一个实现这些接口的类, 就需要使用 newlnstance 方法或反射找出这个类的构造器。但是, 不能实例化一个接口,需要在程序处于运行状态时定义一个新类。为了解决这个问题, 有些程序将会生成代码;将这些代码放置在一个文件中;调用编译器;然后再加载结果类文件。很自然, 这样做的速度会比较慢,并且需要将编译器与程序放在一起。而代理机制则是一种更好的解决方案。代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。

2、不能在运行时定义这些方法的新代码。而是要提供一个调用处理器(invocation handler)。调用处理器是实现了 InvocationHandler 接口的类对象。在这个接口中只有一个方法:
Object invoke(Object proxy, Method method, Object[] args)
无论何时调用代理对象的方法, 调用处理器的 invoke 方法都会被调用, 并向其传递Method 对象和原始的调用参数。 调用处理器必须给出处理调用的方式。

6.5.2 创建代理对象
1.创建一个代理对象, 需要使用 Proxy 类的 newProxylnstance 方法。 这个方法有三个参数:

一个类加载器(class loader)。作为 Java 安全模型的一部分, 对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。有关类加载器的详细内容将在卷 II 第 9章中讨论。目前, 用 null 表示使用默认的类加载器。
一个 Class 对象数组, 每个元素都是需要实现的接口。
一个调用处理器。
还有两个需要解决的问题。 如何定义一个处理器? 能够用结果代理对象做些什么?

(1)路由对远程服务器的方法调用。(2)在程序运行期间,将用户接口事件与动作关联起来。(3)为调试, 跟踪方法调用 。

注意: Integer 类实际上实现了 Comparable<Integer>。 然而, 在运行时, 所有的泛型类都被取消, 代理将它们构造为原 Comparable 类的类对象。

6.5.3 代理类的特性

1. 代理类是在程序运行过程中创建的。 然而, 一旦被创建, 就变成了常规类, 与虚拟机中的任何其他类没有什么区别。

2.(1)所有的代理类都扩展于 Proxy 类。一个代理类只有一个实例域—调用处理器,它定义在 Proxy 的超类中。 为了履行代理对象的职责, 所需要的任何附加数据都必须存储在调用处理器中。

  (2)所有的代理类都覆盖了 Object 类中的方法 toString、 equals 和 hashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的 invoke。Object 类中的其他方法没有被重新定义。

  (3)没有定义代理类的名字,Sun 虚拟机中的 Proxy类将生成一个以字符串 $Proxy 开头的类名。

3.对于特定的类加载器和预设的一组接口来说, 只能有一个代理类。 也就是说, 如果使用同一个类加载器和接口数组调用两次 newProxylnstance 方法的话, 那么只能够得到同一个类的两个对象,也可以利用 getProxyClass方法获得这个类

4.代理类一定是 public 和 final。 如果代理类实现的所有接口都是 public, 代理类就不属于某个特定的包;否则, 所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。可以通过调用 Proxy 类中的 isProxyClass 方法检测一个特定的 Class 对象是否代表一个代理类。

            【API】java.Iang.reflect.InvocationHandler 1.3 :

Object invoke(Object proxy,Method method,0bject[] args)   定义了代理对象调用方法时希望执行的动作。

            【API】 java.Iang.reflect.Proxy 1.3 :

static Class<?> getProxyClass(Cl assLoader loader, Class<?>... interfaces) 返回实现指定接口的代理类。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) 构造实现指定接口的代理类的一个新实例。所有方法会调用给定处理器对象的 invoke 方法。
static boolean isProxyClass(Class<?> cl)
如果 cl 是一个代理类则返回 true

 

第二部分:实验部分

实验1 导入第6章示例程序,测试程序并进行代码注释。

 

测试程序1:6-1,6-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
package interfaces;
 
import java.util.*;
 
/**
 * This program demonstrates the use of the Comparable interface.
 * @version 1.30 2004-02-27
 * @author Cay Horstmann
 */
public class EmployeeSortTest    //EmployeeSortTest关联Employee;
{
   public static void main(String[] args)
   {
      var staff = new Employee[3];  //局部对象数组;
 
      staff[0] = new Employee("Harry Hacker", 35000);
      staff[1] = new Employee("Carl Cracker", 75000);
      staff[2] = new Employee("Tony Tester", 38000);
 
      Arrays.sort(staff);      //进行排序;
 
      // print out information about all Employee objects
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
   }
}

 

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
package interfaces;
 
public class Employee implements Comparable<Employee>//Employee实现JDK内置接口Comparable
{
   private String name;
   private double salary;
   //构造方法
   public Employee(String name, double salary)
   {
      this.name = name;
      this.salary = salary;
   }
   //访问器
   public String getName()
   {
      return name;
   }
 
   public double getSalary()
   {
      return salary;
   }
   //调用方法
   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }
 
   /**
    * Compares employees by salary
    * @param other another Employee object
    * @return a negative value if this employee has a lower salary than
    * otherObject, 0 if the salaries are the same, a positive value otherwise
    */
   public int compareTo(Employee other)
   {
      return Double.compare(salary, other.salary);//静态Double.compare方法
   }
}

  

  运行截图:

 

1.接口的实现
接口在定义后,就可以在类中实现该接口。在类中实现接口可以使用关键字implements,其基本格式如下:
[修饰符] class <类名> [extends 父类名] [implements 接口列表]{
}
修饰符:可选参数,用于指定类的访问权限,可选值为public、abstract和final。
类名:必选参数,用于指定类的名称,类名必须是合法的Java标识符。一般情况下,要求首字母大写。
extends 父类名:可选参数,用于指定要定义的类继承于哪个父类。当使用extends关键字时,父类名为必选参数。
implements 接口列表:可选参数,用于指定该类实现的是哪些接口。当使用implements关键字时,接口列表为必选参数。当接口列表中存在多个接口名时,各个接口名之间使用逗号分隔。
在类中实现接口时,方法的名字、返回值类型、参数的个数及类型必须与接口中的完全一致,并且必须实现接口中的所有方法。

2.Comparable 是排序接口。

(1)若一个类实现了Comparable接口,就意味着“该类支持排序”。 即然实现Comparable接口的类支持排序,假设现在存在“实现Comparable接口的类的对象的List列表(或数组)”,则该List列表(或数组)可以通过 Collections.sort(或 Arrays.sort)进行排序。
此外,“实现Comparable接口的类的对象”可以用作“有序映射(如TreeMap)”中的键或“有序集合(TreeSet)”中的元素,而不需要指定比较器

(2)Comparable 接口仅仅只包括一个函数,它的定义如下
package java.lang;
import java.util.*;
public interface Comparable<T> {
public int compareTo(T o);
}
Comparable 可以让实现它的类的对象进行比较,具体的比较规则是按照 compareTo 方法中的规则进行。这种顺序称为 自然顺序。
(3)compareTo 方法的返回值有三种情况:
e1.compareTo(e2) > 0 即 e1 > e2
e1.compareTo(e2) = 0 即 e1 = e2
e1.compareTo(e2) < 0 即 e1 < e2
注意:
1.由于 null 不是一个类,也不是一个对象,因此在重写 compareTo 方法时应该注意 e.compareTo(null) 的情况,即使 e.equals(null) 返回 false,compareTo 方法也应该主动抛出一个空指针异常 NullPointerException。
2.Comparable 实现类重写 compareTo 方法时一般要求 e1.compareTo(e2) == 0 的结果要和 e1.equals(e2) 一致。将来使用 SortedSet 等根据类的自然排序进行排序的集合容器时保证保存的数据的顺序和想象中一致。

测试程序二:

l编辑、编译、调试以下程序,结合程序运行结果理解程序;代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package test;
interface  A
{
  double g=9.8;
  void show( );
}
class C implements A
{
  public void show( )
  {System.out.println("g="+g);}
}
 
public class InterfaceTest
{
  public static void main(String[ ] args)
  {
       A a=new C( );
       a.show( );
       System.out.println("g="+C.g);
  }
}

  运行截图:

 

 

测试程序三:

6-3代码如下:

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
package timer;
 
/**
   @version 1.02 2017-12-14
   @author Cay Horstmann
*/
 
import java.awt.*;
import java.awt.event.*;
import java.time.*;
import javax.swing.*;
 
public class TimerTest
   public static void main(String[] args)
   
      var listener = new TimePrinter();  //创建类对象;
 
      // construct a timer that calls the listener
      // once every second        //时间间隔为10秒;
      var timer = new Timer(1000, listener);   //创建Timer类对象;
      timer.start();
 
      // keep program running until the user selects "OK"   //显示一个包含一条消息和OK按钮的对话框;
      JOptionPane.showMessageDialog(null"Quit program?"); //parent为null时对话框显示在屏幕的中央;
      System.exit(0);
   }
}
 
class TimePrinter implements ActionListener  //接口定义在implement包中;
{   
   public void actionPerformed(ActionEvent event)//入口参数为ActionEvent event;
   
      System.out.println("At the tone, the time is "
         + Instant.ofEpochMilli(event.getWhen()));
      Toolkit.getDefaultToolkit().beep();    //工具箱包含有关GUI环境的信息;
   }
}

  运行截图:

 

 

 经测试可知:

回调
在A类中调用B类的C方法,然后B类调用A类中的D方法。方法D被称为回调方法。回调是实现异步的基础。经典的回调方式如下:

Class A实现回调接口CallBack——背景1
class A中包含一个class B的引用b ——背景2
class B有一个参数为callback的方法f(CallBack callback) ——背景3
A的对象a调用B的方法 f(CallBack callback) ——A类调用B类的某个方法 C
然后b就可以在f(CallBack callback)方法中调用A的方法 ——B类调用A类的某个方法D

测试程序4

6-4,6-5代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package clone;
 
/**
 * This program demonstrates cloning.
 * @version 1.11 2018-03-16
 * @author Cay Horstmann
 */
public class CloneTest
{
   public static void main(String[] args) throws CloneNotSupportedException
   {
      var original = new Employee("John Q. Public", 50000);
      original.setHireDay(2000, 1, 1);
      Employee copy = original.clone();  //新对象copy初始状态与original相同,之后会有各自不同的状态;
      copy.raiseSalary(10);
      copy.setHireDay(2002, 12, 31);
      System.out.println("original=" + original);
      System.out.println("copy=" + copy);
   }
}

  

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package clone;
 
import java.util.Date;
import java.util.GregorianCalendar;
 
public class Employee implements Cloneable
{
   private String name;
   private double salary;
   private Date hireDay;
 
   public Employee(String name, double salary)
   {
      this.name = name;
      this.salary = salary;
      hireDay = new Date();
   }
 
   public Employee clone() throws CloneNotSupportedException //重新定义clone为public,创建深拷贝的clone的方法;
   {
      // call Object.clone()   // 创建深拷贝的clone方法;
      Employee cloned = (Employee) super.clone();
 
      // clone mutable fields    //克隆可变的字段,
      cloned.hireDay = (Date) hireDay.clone();
 
      return cloned;
   }
 
   /**
    * Set the hire day to a given date.
    * @param year the year of the hire day
    * @param month the month of the hire day
    * @param day the day of the hire day
    */
   public void setHireDay(int year, int month, int day)
   {
      Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();
       
      // example of instance field mutation  实例字段突变的例子;
      hireDay.setTime(newHireDay.getTime());
   }
 
   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }
 
   public String toString()
   {
      return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
   }
}

  运行截图:

 

经测试可知:

实现克隆:
两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。 
在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制。 
有两种方式: 
1). 实现Cloneable接口并重写Object类中的clone()方法; 
2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆;
实现clone方法的步骤 
(1)实现Cloneable接口 
(2)重载Object类中的clone()方法,重载时需定义为public 
(3)在重载方法中,调用super.clone()

解释: 
(1)clone()方法是定义在java.lang.Object类中,该方法是一个protected的方法,所以重载时要把clone()方法的属性设置为public,这样其它类才能调用这个clone类的clone()方法 
(2)实现Cloneable接口:Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出 CloneNotSupportedException异常。

区别:
浅拷贝和深拷贝的区别:

浅拷贝

对一个已知对象进行拷贝,编译系统会自动调用一种构造函数——拷贝构造函数,如果用户未定义拷贝构造函数,则会调用默认拷贝构造函数,调用一次构造函数,调用两次析构函数,两个对象的指针成员所指内存相同,但是程序结束时该内存却被释放了两次,会造成内存泄漏问题。

深拷贝

在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生,调用一次构造函数,一次自定义拷贝构造函数,两次析构函数。两个对象的指针成员所指内存不同。

总结:浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

实验2 导入第6章示例程序6-6,学习Lambda表达式用法。

6-6代码如下:

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
package lambda;
 
import java.util.*;
 
import javax.swing.*;
import javax.swing.Timer;
 
/**
 * This program demonstrates the use of lambda expressions.
 * @version 1.0 2015-05-12
 * @author Cay Horstmann
 */
public class LambdaTest
{
   public static void main(String[] args)
   {
      var planets = new String[] { "Mercury""Venus""Earth""Mars",
         "Jupiter""Saturn""Uranus""Neptune" };  //定义数组plants;
      System.out.println(Arrays.toString(planets));
      System.out.println("Sorted in dictionary order:");
      Arrays.sort(planets);//Arrays.sort方法接受Lambda类的对象;
      System.out.println(Arrays.toString(planets));
      System.out.println("Sorted by length:");
      Arrays.sort(planets, (first, second) -> first.length() - second.length());//检查一个字符串是否比另一个短;
      System.out.println(Arrays.toString(planets));//提供lanbda表达式在底层,Arrays.sort方法会接收实现Comparator<string>某各类的对象;
             
      var timer = new Timer(1000, event ->
         System.out.println("The time is " new Date()));//用已有的方法完成要传递到其他代码的某个动作;
      timer.start();  
          
      // keep program running until user selects "OK"
      JOptionPane.showMessageDialog(null"Quit program?");   //保持程序运行,直到用户选择“OK"
      System.exit(0);        
   }
}

  运行截图:

经测试可知:

1.lambda 表达式的语法格式如下:

(parameters) -> expression或(parameters) ->{ statements; }

以下是lambda表达式的重要特征:

可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
使用 Lambda 表达式需要注意以下两点:
Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
变量作用域
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

 

实验3: 编程练习

l 编制一个程序,将身份证号.txt 中的信息读入到内存中;

l 按姓名字典序输出人员信息;

l 查询最大年龄的人员信息;

l 查询最小年龄人员信息;

l 输入你的年龄,查询身份证号.txt中年龄与你最近人的姓名、身份证号、年龄、性别和出生地;

l 查询人员中是否有你的同乡。

实验代码如下:

 

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package ID;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
import java.util.Collections;//对集合进行排序、查找、修改等;
 
public class Main {
    private static ArrayList<Citizen> citizenlist;
 
    public static void main(String[] args) {
        citizenlist = new ArrayList<>();
        Scanner scanner = new Scanner(System.in);
        File file = new File("D:/java/身份证号.txt");
        //异常捕获
        try {
            FileInputStream fis = new FileInputStream(file);
            BufferedReader in new BufferedReader(new InputStreamReader(fis));
            String temp = null;
            while ((temp = in.readLine()) != null) {
 
                Scanner linescanner = new Scanner(temp);
 
                linescanner.useDelimiter(" ");
                String name = linescanner.next();
                String id = linescanner.next();
                String sex = linescanner.next();
                String age = linescanner.next();
                String birthplace = linescanner.nextLine();
                Citizen citizen = new Citizen();
                citizen.setName(name);
                citizen.setId(id);
                citizen.setSex(sex);
                // 将字符串转换成10进制数
                int ag = Integer.parseInt(age);
                citizen.setage(ag);
                citizen.setBirthplace(birthplace);
                citizenlist.add(citizen);
 
            }
        catch (FileNotFoundException e) {
            System.out.println("信息文件找不到");
            e.printStackTrace();
        catch (IOException e) {
            System.out.println("信息文件读取错误");
            e.printStackTrace();
        }
        boolean isTrue = true;
        while (isTrue) {
 
            System.out.println("1.按姓名字典序输出人员信息");
            System.out.println("2.查询最大年龄的人员信息、查询最小年龄人员信息");
            System.out.println("3.查询人员中是否有你的同乡");
            System.out.println("4.输入你的年龄,查询文件中年龄与你最近人的姓名、身份证号、年龄、性别和出生地");
            System.out.println("5.退出");
            int nextInt = scanner.nextInt();
            switch (nextInt) {
            case 1:
                Collections.sort(citizenlist);
                System.out.println(citizenlist.toString());
                break;
            case 2:
                int max = 0, min = 100;
                int m, k1 = 0, k2 = 0;
                for (int i = 1; i < citizenlist.size(); i++) {
                    m = citizenlist.get(i).getage();
                    if (m > max) {
                        max = m;
                        k1 = i;
                    }
                    if (m < min) {
                        min = m;
                        k2 = i;
                    }
                }
                System.out.println("年龄最大:" + citizenlist.get(k1));
                System.out.println("年龄最小:" + citizenlist.get(k2));
                break;
            case 3:
                System.out.println("出生地:");
                String find = scanner.next();
                String place = find.substring(0, 3);
                for (int i = 0; i < citizenlist.size(); i++) {
                    if (citizenlist.get(i).getBirthplace().substring(1, 4).equals(place))
                        System.out.println("出生地" + citizenlist.get(i));
                }
                break;
            case 4:
                System.out.println("年龄:");
                int yourage = scanner.nextInt();
                int near = peer(yourage);
                int j = yourage - citizenlist.get(near).getage();
                System.out.println("" + citizenlist.get(near));
                break;
            case 5:
                isTrue = false;
                System.out.println("程序已退出!");
                break;
            default:
                System.out.println("输入有误");
            }
        }
    }
 
    public static int peer(int age) {
        int flag = 0;
        int min = 53, j = 0;
        for (int i = 0; i < citizenlist.size(); i++) {
            j = citizenlist.get(i).getage() - age;
            if (j < 0)
                j = -j;
            if (j < min) {
                min = j;
                flag = i;
            }
        }
        return flag;
    }
}

 

  

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package ID;
public class Citizen implements Comparable<Citizen> {
 
    private String name;
    private String id;
    private String sex;
    private int age;
    private String birthplace;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getId() {
        return id;
    }
 
    public void setId(String id) {
        this.id = id;
    }
 
    public String getSex() {
        return sex;
    }
 
    public void setSex(String sex) {
        this.sex = sex;
    }
 
    public int getage() {
        return age;
    }
 
    public void setage(int age) {
        this.age = age;
    }
 
    public String getBirthplace() {
        return birthplace;
    }
 
    public void setBirthplace(String birthplace) {
        this.birthplace = birthplace;
    }
 
    public int compareTo(Citizen other) {
        return this.name.compareTo(other.getName());
    }
 
    public String toString() {
        return name + "\t" + sex + "\t" + age + "\t" + id + "\t" + birthplace + "\n";
    }
}

运行截图如下:

                   

 实验4:内部类语法验证实验

实验程序1:

l 编辑、调试运行教材246页-247页程序6-7,结合程序运行结果理解程序;

l 了解内部类的基本用法

代码如下:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package innerClass;
 
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
 
/**
 * This program demonstrates the use of inner classes.
 * @version 1.11 2015-05-12
 * @author Cay Horstmann
 */
public class InnerClassTest
{
   public static void main(String[] args)
   {
      TalkingClock clock = new TalkingClock(1000, true);//实现了TalkingClock的类对象
      clock.start();
 
      // keep program running until user selects "Ok"
      JOptionPane.showMessageDialog(null"Quit program?");
      System.exit(0);//
   }
}
 
/**
 * A clock that prints the time in regular intervals.
 */
class TalkingClock
{
   //声明属性
    private int interval;
   private boolean beep;
 
   /**
    * Constructs a talking clock
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public TalkingClock(int interval, boolean beep)
   {
      this.interval = interval;
      this.beep = beep;
   }//构造方法
 
   /**
    * Starts the clock.
    */
   public void start()
   {
      ActionListener listener = new TimePrinter();
      Timer t = new Timer(interval, listener);
      t.start();
   }
 
   public class TimePrinter implements ActionListener//实现ActionListener的公共类TimePrinter
   {
      public void actionPerformed(ActionEvent event)
      {
         System.out.println("At the tone, the time is " new Date());
         if (beep) Toolkit.getDefaultToolkit().beep();
      }
   }
}

  

实验结果:

在主类中无法直接访问或者创建内部类

所以内部类存在原因如下:

内部类可以直接访问外部类的私有域(包括私有属性和私有方法)

内部类是另外一种封装(保护性),对外部的其他类隐藏(心脏包在人身体内部)

内部类可以实现Java单继承的局限。
但是内部类也存在缺点:结构复杂。

创建内部类
在外部类内部创建内部类
内部类 内部类引用=new 内部类();
Inner in=new Inner( ); (可参考上文中不用内部类代码)

在外部类外部创建内部类

在外部类外部创建非静态内部类
外部类.内部类 内部类引用 =new 外部类( ).new 内部类( );

Outter.Inner in =new Outter( ).new Inner( );

 实验程序2:

l 编辑、调试运行教材254页程序6-8,结合程序运行结果理解程序;

了解匿名内部类的用法。

代码如下:

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
41
42
43
44
45
46
47
48
49
50
51
package anonymousInnerClass;
 
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
 
/**
 * This program demonstrates anonymous inner classes.
 * @version 1.11 2015-05-12
 * @author Cay Horstmann
 */
public class AnonymousInnerClassTest
{
   public static void main(String[] args)
   {
      TalkingClock clock = new TalkingClock();//TalkingClock类声明为私有的
      clock.start(1000, true);
 
      // keep program running until user selects "Ok"
      JOptionPane.showMessageDialog(null"Quit program?");
      System.exit(0);
   }
}
 
/**
 * A clock that prints the time in regular intervals.
 */
class TalkingClock
{
   /**
    * Starts the clock.
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public void start(int interval, boolean beep)
   {
      ActionListener listener = new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               System.out.println("At the tone, the time is " new Date());
               if (beep) Toolkit.getDefaultToolkit().beep();
               //外围类引用.
            }
         };
      Timer t = new Timer(interval, listener);
      t.start();
   }
}

  

实验结果:

 

 匿名内部类:

(1).匿名内部类是直接使用 new 来生成一个对象的引用;
(2).对于匿名内部类的使用它是存在一个缺陷的,就是它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,
该类的定义会立即消失,所以匿名内部类是不能够被重复使用;
(3).使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口;
(4).匿名内部类中是不能定义构造函数的,匿名内部类中不能存在任何的静态成员变量和静态方法;

(5).匿名内部类中不能存在任何的静态成员变量和静态方法,匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法

(6).匿名内部类初始化:使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果

(7).匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效

实验程序3:

l 在elipse IDE中调试运行教材257页-258页程序6-9,结合程序运行结果理解程序;

了解静态内部类的用法。

代码如下:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package staticInnerClass;
 
/**
 * This program demonstrates the use of static inner classes.
 * @version 1.02 2015-05-12
 * @author Cay Horstmann
 */
public class StaticInnerClassTest
{
   public static void main(String[] args)
   {
      double[] d = new double[20];
      for (int i = 0; i < d.length; i++)
         d[i] = 100 * Math.random();//算法
      ArrayAlg.Pair p = ArrayAlg.minmax(d);
      System.out.println("min = " + p.getFirst());
      System.out.println("max = " + p.getSecond());
   }//访问器
}
 
class ArrayAlg
{
   /**
    * A pair of floating-point numbers
    */
   public static class Pair
   {
      //声明私有属性      
      private double first;
      private double second;
 
      /**
       * Constructs a pair from two floating-point numbers
       * @param f the first number
       * @param s the second number
       */
      public Pair(double f, double s)
      {
         first = f;
         second = s;
      }
 
      /**
       * Returns the first number of the pair
       * @return the first number
       */
      public double getFirst()
      {
         return first;
      }
     // 访问器
      /**
       * Returns the second number of the pair
       * @return the second number
       */
      public double getSecond()
      {
         return second;
      }
   }
 
   /**
    * Computes both the minimum and the maximum of an array
    * @param values an array of floating-point numbers
    * @return a pair whose first element is the minimum and whose second element
    * is the maximum
    */
   public static Pair minmax(double[] values)
   {
      double min = Double.POSITIVE_INFINITY;
      double max = Double.NEGATIVE_INFINITY;//变量
      for (double v : values)
      {
         if (min > v) min = v;
         if (max < v) max = v;
      }
      return new Pair(min, max);
   }
}

  

实验结果:

 

静态内部类:在定义内部类的时候,可以在其前面加上一个权限修饰符static。此时这个内部类就变为了静态内部类。

通常称为嵌套类,当内部类是static时,意味着:

[1]要创建嵌套类的对象,并不需要其外围类的对象;

[2]不能从嵌套类的对象中访问非静态的外围类对象(不能够从静态内部类的对象中访问外部类的非静态成员);

嵌套类与普通的内部类还有一个区别:普通内部类的字段的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是在嵌套类里可以包含所有这些东西。也就是说,在非静态内部类中不可以声明静态成员,只有将某个内部类修饰为静态类,然后才能够在这个类中定义静态的成员变量与成员方法。

另外,在创建静态内部类时不需要将静态内部类的实例绑定在外部类的实例上。普通非静态内部类的对象是依附在外部类对象之中的,要在一个外部类中定义一个静态的内部类,不需要利用关键字new来创建内部类的实例。静态类和方法只属于类本身,并不属于该类的对象,更不属于其他外部类的对象。

 

实验总结:(10分)

     在这一章学习了接口定义方法,了解和熟悉了接口的使用方法,Lambda表达式语法,Comparator接口用法,还能够区分浅拷贝和深拷贝的异同之处,最后基本了解了内部类的定义和使用。

posted on 2019-10-21 20:05  夏热握火  阅读(168)  评论(1编辑  收藏  举报