201871010132-张潇潇《面向对象程序设计(java)》第八周学习总结html
项目 | 内容 |
这个做业属于哪一个课程 | https://www.cnblogs.com/nwnu-daizh/ |
这个做业的要求在哪里 | http://www.javashuo.com/article/p-fglwhfdo-hz.html |
做业学习目标 |
|
第一部分:总结第六章理论知识java
抽象类是指定义时有 abstract 修饰的类,例如:程序员
public abstract class Person{ ... public abstract String getDescription();//注意末尾没有花括号,而有分号 }
在定义中有abstract
修饰符的方法是抽象方法。抽象类中能够包含实例变量和实例方法,甚至能够没有抽象方法,可是有抽象方法的类必定要定义为抽象类。算法
抽象方法充当着占位的角色,它们的具体实如今子类中。抽象方法不能有方法体,即没有花括号,但必须有分号,方法定义变成了方法声明。扩展抽象类能够有两种选择:express
抽象类不能实例化(由于抽象方法没有具体实现,即便抽象类中不包含抽象方法),能够定义抽象类的变量,但它只能引用其实现类的对象。编程
抽象类能够包含实例变量、实例方法、类变量、静态方法、构造器、静态初始化块、普通初始化块、内部类。抽象类的构造器不能用于建立实例,主要是用于被其子类调用。在抽象类中,实例方法能够调用抽象方法。如下面的代码为例,由于子类CarSpeedMeter
实现了抽象方法getRadius
,至关于子类覆盖了该方法,从而具备多态性,子类对象调用getSpeed
时,getSpeed
中调用的getRadius
方法是子类中的getRadius
。设计模式
public abstract class SpeedMeter{ private double turnRate; public abstract double getRadius(); public double getSpeed(){ return Math.PI * 2 * getRadius(); } } public class CarSpeedMeter extends SpeedMeter{ public double getRadius(){ ... } }
Java 8
中接口的定义定义接口时再也不使用class
关键字,而使用interface
关键字。接口定义的基本语法以下:数组
[修饰符] interface 接口名 extends 父接口1,父接口2,...{ 零个到多个静态常量定义。。。 零个到多个抽象方法定义... 零个到多个默认方法定义...//仅在java8中容许 零个到多个内部类、接口、枚举定义 }
因为接口里定义的是多个类共同的公共行为规范,所以接口里全部成员都是默认public
访问权限。接口里不包含成员变量,构造器和初始化块。接口里只能包含静态常量、抽象实例方法、类方法、默认方法、内部类、内部接口、内部枚举。安全
静态常量能够省略public static final
,系统默认添加。且静态常量只能在定义时初始化。闭包
对于抽象方法,系统默认添加public abstract
修饰,所以,抽象方法不能有方法体。从而,实现类覆盖这些抽象方法时,访问权限必须是public
。
默认方法须要加default
修饰符,但不能有static
修饰符,不然,和类方法没有区别。系统默认添加public
修饰。以鼠标监听接口MouseListener
来讲明默认方法存在的目的,MouseListener
包含5个接口:
interface MouseListener{ void mouseClicked(MouseEvent event); void mousePressed(MouseEvent event); void mouseReleased(MouseEvent event); void mouseEntered(MouseEvent event); }
大多数状况下,只须要关心前两个接口,在Java8
中能够把全部方法声明为默认方法,这些方法什么都不作:
interface MouseListener{ default void mouseClicked(MouseEvent event){} default void mousePressed(MouseEvent event){} default void mouseReleased(MouseEvent event){} default void mouseEntered(MouseEvent event){} }
从而,实现此接口的程序员只须要重写他们真正关心的方法。默认方法能够调用其余的默认或抽象方法。
静态方法是java8
增长的功能,在此以前,Java多会给接口实现一个伴随类中,并将静态方法放在伴随类中。当容许在接口中定义静态方法时,这些伴随类将再也不须要。
接口里的内部类、内部接口、内部枚举,系统默认添加public static
修饰,由于不能建立接口实例。
定义接口的示例以下:
public interface Output{ int MAX_CACHE_LINE = 50; void out(); static String staticTest(){ return "类方法"; } default void test(){ System.out.println("默认方法"); } }
接口支持多继承,一个接口能够有多个直接父接口,但接口只能继承接口,不能继承类。子接口扩展父接口时,将会得到父接口全部的默认方法和抽象方法。
一个接口继承多个接口时,多个接口排在extends
关键字以后,多个父接口之间以英文逗号隔开。示例以下:
interface A{ int PRO_A = 5; void testA(); } interface B{ int PRO_B = 6; void testB(); } interface C extends A,B{ int PRO_C = 7; void testC(); }
接口不能建立实例,但接口能够声明变量。但接口声明的变量必须引用到其实现类的对象。除此以外,接口的主要功能是被实现类实现。接口的主要用途概括以下:
一个类能够实现一个或多个接口,继承使用extends
,实现则使用implements
。容许一个类实现多个接口,能够弥补java
单继承的不足,同时避免多继承的复杂性和低效性。类实现接口的语法格式以下:
[修饰符] class 类名 extends 父类 implements 接口1,接口2...{ 类体部分 }
实现接口与继承父类类似,同样能够得到所实现接口里定义的静态常量、方法(抽象方法和默认方法)。
让类实现接口须要类定义后增长implements
部分,当须要实现多个接口时,多个接口之间以英文逗号隔开,一个类能够继承父类,并同时实现多个接口,implements
部分必须方法extends
部分以后。
一个类实现类一个或多个接口以后,这个类必须彻底实现这些接口里定义的所有抽象方法(即重写这些抽象方法,包括接口从父接口继承获得的抽象方法);不然,该类必须定义为抽象类。
接口不能显式继承任何类,但全部接口类型的变量均可以直接赋给Object
类型的变量,由于编译器知道接口类型变量的运行时类型一定是实现类对象,而任何Java
对象都必须是Object
或其子类的实例。
接口和抽象类很像,他们的共同点有:
但接口和抽象类的差异很大,主要体如今二者的设计目的上:
接口和抽象类在用法上的区别以下:
public
,而抽象类中各类访问权限均可以。java8
以前的代码兼容。回调指对象调用某个方法时,此方法须要的实参是一个接口,而这个接口和对象有关,从而,对象调用方法后,方法反过来调用这个接口。
Java
中经常使用的接口- Comparable<T>
接口中定义了:
public int comPareTo(T other); - Comparator<T>(比较器) 接口中定义了 public int compare(T first, T second); - Clonable<T> public T clone();
深克隆与浅克隆。
Object
类实现了基本的clone()
方法,但因为不了解对象的域,因此只能逐个域地进行拷贝,对于引用类型的域,拷贝域就会获得相同子对象的另外一个引用,从而,原对象和克隆获得的对象仍会共享一些信息,此为浅克隆。
深克隆的作法是:对于基本数据类型,直接拷贝,对于引用类型的域,递归拷贝引用对象的每一个域。使得原对象和克隆获得的对象再也不共享任何信息(对于不可变类,可直接复制值,只有可变类,才须要这么处理)。调用此类型的clone()
进行拷贝。
自定义克隆方法时,须要肯定:
Object
类的克隆方法是否知足要求。若是选择第1项或第2项,类必须:
Cloneable
接口clone()
,并指定public
访问修饰符。因为Object
类中的clone()
声明为protected
,因此子类只能调用此方法来克隆本身的对象,必须从新定义clone()
为public
才能容许全部方法克隆对象。
深克隆示例以下:
class Employee implements Cloneable{ ... public Employee clone() throws CloneNotSupportedException{ Employee cloned = (Employee) super.clone(); cloned.hireDay = (Date) hireDay.clone(); return cloned; } }
全部数组类型都有一个public
的clone
方法。
在一个类内部定义的另外一个类称为内部类,此处的“类内部”包括类中的任何位置,甚至在方法中也能够定义内部类(方法里定义的内部类称为局部内部类和匿名内部类)。包含内部类的类称为外部类。内部类的主要做用有:
定义内部类与定义外部类的语法大体相同,内部类除了须要定义在其余类里面以外,还存在以下两点区别:
private、protected、static
。 多数状况下,内部类做为成员内部类定义,而不是做为局部内部类。成员内部类是一种与成员变量、方法、构造器和初始化块类似的类成员;局部内部类和匿名内部类不是类成员。
成员内部类分为两种:静态内部类和非静态内部类,使用static修饰的成员内部类是静态内部类,没有使用static修饰的成员内部类是非静态内部类。
由于内部类是做为其外部类的成员,因此可使用任意访问控制符如private,protected 和 public 等修饰。编译器生成的成员内部类的class文件格式为:OuterClass$InnerClass.class。
在非静态内部类里能够直接访问外部类的private成员。这是由于在非静态内部类对象里,保存了一个它所寄生的外部类对象的引用,同时编译器会在外部类中添加对相应私有域的访问器和更改器(当调用非静态内部类的实例方法时,必须有一个非静态内部类实例,非静态内部类实例必须寄生在外部类实例里)。
当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,若是存在就使用改变了;若是不存在,则到该方法所在的内部类中查找是否存在该名字的成员变量,若是存在则使用该成员变量;若是不存在,则到该内部类所在的外部类中查找是否存在该名字的成员变量,若是存在则使用该成员变量,若是依然不存在,系统将出现编译错误,提示找不到该变量。
所以,若是外部类成员变量、内部类成员变量与内部类方法的局部变量同名,则可经过使用外部类名.this,this
做为限定区分。以下程序所示:
public class TestVar{ private String prop = "外部类的实例变量"; private class InClass{ private String prop = "内部类的实例变量"; public void info(){ String prop = "局部变量"; System.out.println("输出的是外部实例变量" + TestVar.this.prop); System.out.println("输出的是内部类的实例变量" + this.prop); System.out.println("输出的是局部变量" + prop); } } public void test(){ Inclass in = new InClass(); in.info(); } public static void main(String[] args){ new TestVar.test(); } }
非静态内部类的成员能够访问外部类的private成员,但反过来不成立。由于外部类对象存在时,非静态内部类对象不必定存在。若是外部类须要访问非静态内部类的成员,必须显式地建立内部类对象来访问其实例成员,外部类方法能够访问内部类的私有成员。(和普通的一个类的方法访问另外一个类的方法存在区别,外部类访问内部类的实例成员,不须要经过内部类的公有方法来访问,这仍是由于,你们都是外部类的成员,成员之间能够相互访问)。以下所示:
public class Outer{ private int outprop = 9; class Inner{ private int inprop = 5; public void accessOuterprop{ System.out.println("外部类的outprop值:" + outprop); } } public void accessInnerProp(){ //System.out.println("内部类的inprop值" + inprop); System.out.println("内部类的inprop值" + new().inprop); } public static void main(String[] args){ Outer out = new Outer();//注释2 out.accessInnerProp(); } }
第一处注释试图在外面类方法里访问非静态内部类的实例变量,将引发编译错误。外部类不容许访问非静态内部类的实例成员的缘由是:上面main方法的第二处注释代码只建立了一个外部类对象,并调用外部类对象的accessInnerProp
方法。此时非静态内部类对象根本不存在,若是容许accessInnerProp
方法访问非静态内部类对象,将引发错误。
非静态内部类对象和外部类对象的关系
若是存在一个非静态内部类对象,则必定存在一个被它寄生的外部类对象。但当外部类对象存在时,外部类对象里不必定寄生了非静态内部类对象。因此外部类对象访问非静态内部类对象时,可能非静态内部类对象根本不存在,而非静态内部类对象访问外部类对象时,外部类对象必定存在。
根据静态成员不能访问非静态成员的规则,外部类的静态方法、静态代码块不能访问非静态内部类,尤为是不能使用非静态内部类建立实例等(缘由是:使用非静态内部类时,外部类的对象并不存在,此时,非静态内部类对象无处寄生):
public class StaticTest{ private class In{} piblic static void main(String[] args){ //没法访问非静态成员 In类 new In(); } }
java不容许在非静态内部类里定义静态成员、静态方法、静态初始化块。不然能够经过OutClass.InClass
的方法来调用,此时,外部类对象并未建立。
非静态内部类里不能有静态初始化块,但能够有普通初始化块,非静态内部类的普通初始化块的做用与外部类初始化块的做用相同。
若是使用static
来修饰一个内部类,则这个内部类就属于外部类自己,而不属于外部类的某个对象,至关于外部类的类成员。所以使用static修饰的内部类被称为静态内部类。
static关键字的做用是把类的成员变成类相关,而不是实例相关,即static修饰的成员属于整个类,而不属于单个对象。对象类的上一级程序单元是包,因此不可以使用static修饰;而内部类的上一级程序单元是外部类,使用static修饰能够将内部类变成外部类相关,而不是外部类实例相关。
静态内部类能够包含静态成员,也能够包含非静态成员。静态内部类不能访问外部类的实例成员,只能访问外部类的静态成员。即便是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类中的静态成员。缘由是:静态内部类不须要寄生在外部类实例中,静态内部类的实例建立时,外部类的实例不必定存在。
由于静态内部类是外部类的一个静态成员,所以外部类的全部方法,全部初始化块中可使用静态内部类来定义变量、建立对象等。
外部类依然不能直接访问静态内部类的成员,但可使用静态内部类的类名做为调用者访问静态内部类的静态成员,也可使用静态内部类的对象访问静态内部类的实例成员。
public lass AccessStaticInnerClass{ static class StaticInnerClass{ private static int prop1 = 5; private int prop2 = 9; } public void accessInnerProp(){ System.out.println(StaticIneerClass.prop1); System.out.println(new StaticIneerClass().prop2); } }
在外部类里面能够调用内部类对象的私有成员,可是须要先建立内部类对象。
定义内部类变量
内部类名 变量名
建立内部类对象
new 内部类名(实参列表)
访问内部类成员
内部类对象名.成员名
定义内部类变量
内部类名 变量名
建立内部类对象
在外部类静态方法中建立内部类实例时,须要先有外部类实例。而后:外部类实例名.new 内部类名(实参列表)
访问内部类成员
内部类对象名.成员名
若是但愿在外部类外面使用内部类,则内部类不能使用private访问控制权限,private修饰的内部类只能在外部类里面使用,内部类的访问权限以下:
在外部类外访问内部类的格式以下:
定义内部类变量
外部类名.内部类名 变量名
建立内部类对象
在外部类外面建立内部类实例时,须要先有外部类实例。而后:外部类实例名.new 内部类名(实参列表)//此处不须要再写外部类.内部类。定义变量须要这么作是保证类的惟一性。
访问内部类成员
内部类对象名.成员名。此时只能访问内部类的公有成员
因为静态内部类是外部类相关的。因此内部类对象无需寄生于外部类对象。从而在外部类的静态方法和非静态方法中使用静态内部类格式相同,均为:
定义内部类变量
内部类名 变量名
建立内部类对象
new 内部类名(实参列表)
访问内部类成员
内部类对象名.成员名
在外部类外使用静态内部类的格式以下:
定义内部类变量
外部类名.内部类名 变量名
建立内部类对象
new 外部类名.内部类名(实参列表)
访问内部类成员
内部类对象名.成员名。此时只能访问内部类的公有成员
内部类的子类不必定是内部类,能够是一个外部类。
当建立一个非静态内部类的子类时,子类构造器总会调用父类的构造器,而调用非静态内部类的构造器时,必须存在一个外部类对象。所以在建立非静态内部类的子类时,必须给子类构造器传一个外部类对象做为参数。因此定义非静态内部类子类的格式为:
class 子类名 extends 外部类名.内部类名{ [修饰符] 子类名(外部类名 外部类实例,实参){ 外部类实例名.super(实参); ... } ... }
示例以下:
public class SubClass extends Out.In{ public SubClass(Out obj){ obj.super("hello"); } }
非静态内部类对象和其子类对象都必须持有寄生的外部类对象的引用,区别是建立两种对象时传入外部类对象的方式不一样:当建立非静态内部类对象时,经过外部类对象来调用new关键字;当建立内部类子类对象时,将外部类对象做为子类构造器的参数。
非静态内部类的子类实例仍然须要保留一个引用,即若是一个非静态内部类的子类的对象存在,也必定存在一个寄生的外部类对象。
由于调用静态内部类的构造器时无需使用外部类对象,因此建立静态内部类的子类比较简单,格式以下:
clsss 子类名 extends 外部类名.内部类名{ ... }
能够看出,当定义一个静态内部类时,其外部类很是像一个包空间。
相比之下,使用静态内部类比使用非静态内部类简单不少,只要把外部类当成静态内部类的包空间便可,所以当程序须要使用内部类时,应该优先考虑使用静态内部类。
外部类的子类中若是定义一个与父类内部类同名的内部类时,子类建立的是子类内部类的对象,父类建立的是父类内部类的对象,若是把子类对象赋给父类引用,再建立内部类对象,此时建立的是父类内部类的对象。能够把内部类当作事外部类的成员变量,经过静态分派肯定符号引用。
若是在方法里定义内部类,则这个内部类是一个局部内部类,局部内部类仅在该方法里有效。因为局部内部类不能在此方法之外的地方使用,所以局部内部类不须要访问控制符和static修饰符修饰。
对于局部成员而言,无论是局部变量仍是局部类,他们的上一级程序单元都是方法,而不是类,使用static修饰他们没有任何意义;不只如此,由于局部成员的做用域是所在方法,其余程序单元永远也不可能访问一个方法中的局部成员,因此,全部的局部成员不能使用访问控制符和static修饰符。
若是须要用局部内部类定义变量、建立实例或派生子类,那么都只能在局部内部类所在的方法内进行。
public class LocalInnerClass{ public static void main(String args){ class InnerBase{ int a; } class InnerSub extends InnerBase{ int b; } InnerSub is = new InnerSub(); is.a = 5;///////////////////////////////方法中能够直接访问局部内部类的域。 is.b = 8; System.out.println(is.b + " " + is.a); } }
编译程序,生成三个class文件:LocalInnerClass.class、LocalInnerClass$InnerBase.class、LocalInnerClass$InnerSub.class
。注意到局部内部类的class文件的文件名比成员内部类的class文件的文件名多了一个数字,这是由于同一个类里不可能有两个同名的成员内部类,而同一个类里可能有两个以上的同名的局部内部类(处于不一样的方法中)。
局部内部类在实际开发中不多使用,由于局部内部类的做用域过小了,只能在当前方法中使用。
匿名内部类适合建立只须要使用一次的类,建立匿名内部类时会当即建立一个该类的实例。定义格式以下:
new 实现接口() |父类构造器(实参列表) { //匿名内部类的类体部分 }
从定义可知,匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类或实现一个接口。
关于匿名内部类有以下两条规则:
匿名内部类不能是抽象类,由于系统在建立匿名内部类时,会当即建立匿名内部类的对象。
匿名内部类不能定义构造器。因为匿名内部类没有类名,因此没法定义构造器。取而代之的是,将构造器参数传递给父类构造器。同时匿名内部类能够定义初始化块,能够经过实例初始化块完成构造器须要完成的事情。
最经常使用的建立匿名内部类的方式是须要建立某个接口类型的对象。
interface Product{ public String getName(); } public class AnonyTest{ public void test(Product p){ System.out.println("购买了" + p.getName()); } public static void main(String[] args){ AnonyTest obj = new AnonyTest(); obj.test( new Product(){ public String getName(){ return "tom"; } }); } }
上述程序中的AnonyTest类定义了一个test方法,该方法须要一个Product对象做为参数,但Product只是一个接口,没法直接建立对象,所以考虑建立一个Product接口实现类的对象传入该方法---若是这个Product接口实现须要重复使用,则应该将实现类定义成一个独立类;若是这个Product接口实现类只须要一次使用,就能够定义一个匿名内部类。
因为匿名内部类不能是抽象类,因此匿名内部类必须实现它的抽象父类或接口里包含的全部抽象方法。
当经过实现接口来建立匿名内部类时,因为结构没有构造器。所以new接口名后的括号里不能传入参数值。
可是若是经过继承父类来建立匿名内部类时,匿名内部类将拥有和父类类似的构造器,此处的类似指的是拥有相同的形参列表。
匿名内部类继承抽象父类的示例:
abstract class Device{ private String name; public abstract double getPrice(); public Device(){} public Device(String name){ this.name = name; } //省略name的访问器和修改器 } public class AnonyTest{ public void test(Device p){ System.out.println("花费" + p.getPrice()); } public static void main(String[] args){ AnonyTest obj = new AnonyTest(); obj.test(new Device("honey"){ public double getPrice(){ return 56.3; } }); Device p = new Device(){ {//初始化块 System.out.println("匿名内部类的初始化块:"); } //实现抽象方法 public double getPrice(){ return 56.3; } //覆盖父类方法 public String getName(){ return "键盘"; } } obj.test(p); } }
当建立匿名内部类时,必须实现接口或抽象父类里的全部抽象方法,若是有须要,也能够重写父类的普通方法。
在java8以前,java要求被局部内部类、匿名内部类访问的局部变量,在方法中定义时必须用final修饰,从java8开始这个限制被取消了,由编译器进行处理:若是局部变量被匿名内部类访问,那么该局部变量至关于自动使用了final修饰符。此局部变量在第一次赋值后,值不能再修改,不然编译器将报错。例如:
public class PairTest{ public static void main(String[] args){ int age = 0 ; age =3; class Device{ void test(){ System.out.println(age); } } Device d = new Device(); d.test(); } }
age在初始化为0后,被赋值为3,因此编译器将会报错。
java8将这个功能称为“effective final”,意思是对于匿名内部类访问的局部变量,能够用final修饰,也能够不一样final修饰,但必须按照有final修饰的方式来用,也就是一次赋值后,之后不能从新赋值。
内部类是一种编译器现象,与虚拟机无关。编译器会把内部类翻译成用$
分隔外部类名与内部类名的常规类文件,而这个操做对虚拟机是透明的。编译阶段,编译器会对内部类进行处理,转化为外部类,内部类对外部类对象的访问是由于编译器会在内部类的构造器中添加一个外部类引用的参数。内部类对外部类实例域的访问:编译器会在外部类中添加相关实例域的访问器方法,从而内部类对外部类实例域的访问将转化为调用外部类的访问器方法来实现。
局部内部类对方法内的局部变量的访问:因为方法在建立局部内部类实例后,可能程序执行结束,局部变量会释放,此时局部内部类中将没法访问到局部变量,因此编译器会在局部内部类中添加实例域,在建立局部内部类实例时,将局部变量的值保存到添加的实例域中。
在内部类不须要访问外部类对象时,应该使用静态内部类。
函数式接口:只有一个抽象方法的接口。函数式接口能够包含多个默认方法、类方法,但只能声明一个抽象方法。
匿名内部类的实现,以及Lambda表达式使用示例:
public class CommandTest{ public static void main(String[] args){ ProcessArray pa = new ProcessArray(); int[] target = {3, -4, 6, 4}; pa.process(target, new Command(){ public int process(int[] target){ int sum = 0; for(int tmp : target){ sum += temp; } return sum; } }); } }
Lambda表达式能够对上述代码进行简化:
public class CommandTest{ public static void main(String[] args){ ProcessArray pa = new ProcessArray(); int[] target = {3, -4, 6, 4}; pa.process(target, (int[] target)->{ int sum = 0; for(int tmp : target){ sum += temp; } return sum; }); } }
能够看出,Lambda表达式的做用就是简化匿名内部类的繁琐语法。它有三部分构成:
总结起来,lambda共有以下几种省略状况:
return
语句时,能够省略return
关键字,语句末尾的分号也省略。示例以下:
interface Flyable{ void fly(String weather); } public class LambdaQs{ public void drive(Flyable f){ System.out.println("我正在驾驶:" + f); f.fly("晴天"); } public static void main(String[] args){ LambdaQs lq = new LambdaQs(); lq.drive(weather -> { System.out.println("今每天气是:" + weather); System.out.println("直升机飞行平稳"); }); } }
定义Lambda表达式时即建立了一个对象,对象的类型要求是一个函数式接口,具体由赋值号左边的操做数类型决定。可使用Lambda表达式进行赋值。用Lambda表达式进行赋值的示例以下:
Runnable r = ()->{
for(int i = 0;i < 100 ; i++){ System.out.println(); } };
Lambda表达式的限制以下:
Object
类型的变量,不然,没法肯定lambda
表达式的运行时类型。示例以下:
Object obj = ()->{
for(int i = 0;i < 100 ; i++){ System.out.println(); } };
上述代码的Lambda表达式赋给的是Object对象而不是函数式接口。因此,编译器会报错。
为了保证Lambda表达式的目标类型是明确的函数式接口,能够有以下三种常见方式:
所以上述代码,可修改
Object obj = (Runnable)()->{
for(int i = 0;i < 100 ; i++){ System.out.println(); } };
易知,Lambda表达式的目标类型彻底多是变化的(便可能会利用强制类型转换,将Lambda表达式赋给另外一个抽象方法相同的接口变量),惟一的要求是,Lambda表达式实现的匿名方法与函数式接口中的抽象方法有相同的形参列表和返回值。示例以下:
interface FKTest{ public void run(); } Runnable obj = ()->{ for(int i = 0;i < 100 ; i++){ System.out.println(); } }; FKTest fk = (FKTest)obj;//赋值合法
Java 8在java.util.function
包下预约义了大量的函数式接口,典型 地包含以下4类接口。
综上所述,不难发现Lambda表达式的本质很简单,就是使用简洁的语法来建立函数式接口的实例------这种语法避免了匿名函数类的繁琐。
有时,现有方法能够完成抽象方法的功能,此时能够直接调用 现有类的方法或构造器,称为方法引用和构造器引用。
方法引用和构造器引用可让Lambda表达式的代码块更加简洁。方法引用和构造器引用都须要使用两个英文冒号。Lambda表达式支持以下表所示的几种引用方式。
种类 | 示例 | 说明 | 对应的Lambda表达式 |
---|---|---|---|
引用类方法 | 类名::类方法 | 函数式接口中被实现方法的所有参数传给该类方法做为参数 | (a,b,...)->类名.类方法(a,b,...) |
引用特定对象的实例方法 | 特定对象::实例方法 | 函数式接口中被实现方法的所有参数传给该类方法做为参数 | (a,b,...)->特定对象.实例方法(a,b,...) |
引用某类对象的实例方法 | 类名::实例方法 | 函数式接口中被实现方法的第一个参数做为调用者,后面的参数所有传给该方法做为参数 | (a,b,...)->a.实例方法(a,b,...) |
引用构造器 | 类名::new | 函数式接口中被实现方法的所有参数传给该构造器做为参数 | (a,b,...)->new 类名(a,b,...) |
@FunctionInterface interface Converter{ Ingeter convert(String from); } //使用Lambda表达式建立Conveter对象 Converter converter1 = from -> Integer.ValueOf(from); Integer val = converter1.convert("99");
上面代码调用converter1对象的convert()方法时------因为converter1对象是由Lambda表达式建立的,convert()方法执行体就是Lambda表达式的代码部分。
上述的Lambda表达式的代码块只有一行调用类的方法的代码,所以能够替换为:
Converter converter1 = Integer::ValueOf;
Converter converter2 = from -> "fkit.org".indexOf(from); Integer value = converter1.convert("it");
上述的Lambda表达式的代码块只有一行调用"fkit.org"的indexOf()实例方法的代码,所以能够替换为:
Converter converter2 = "fkit.org"::indexOf;
@FunctionalInterface interface MyTest{ String test(String a, int b, int c); } MyTest mt = (a, b, c)-> a.subString(b, c); String str = mt.test("Java I love you", 2,9);
上述的Lambda表达式的代码块只有一行a.subString(b, c);所以能够替换为:
MyTest mt = String::subString;
@FunctionInterface interface YourTest{ JFrame win(String title); } YourTest yt = (String a)->new JFrame(a); JFrame jf = yt.win("个人窗口");
上述Lambda表达式的代码块只有一行new JFrame(a);所以能够替换为:
YourTest yt = JFrame::new;
方法引用和构造器引用中,若是有多个同名的重载方法,编译器会依据表达式实际转换的函数式接口中声明的方法进行选择。可使用数组类型创建构造器引用,例如int[]::new,它有一个参数,即数组的长度,这等价于lambda表达式x->new int[x]
能够在方法中使用this参数,例如this::equals等同于x-> this.equals(x),使用super也是合法的。this
表示lambda
表达式所在方法的对象。例如:
class Greeter{ public void greet(){ System.out.println("hello world!"); } } class TimedGreeter extends Greeter{ public void greet(){ Timer t = new Timer(1000, super::greet); t.start(); } }
Lambda 表达式是匿名内部类的一种简化,所以它能够部分取代匿名内部类的做用,Lambda 表达式与匿名内部类存在如下相同点:
interface Displayable{ void display(); default int add(int a, int b){ return a + b; } } public class LabdaAndInner{ private int age = 12; private static String name = "i'm here"; public void test(){ String book = "疯狂java"; Displayable dis = ()->{ System.out.println("book 局部变量为:" + book); System.out.println("age 局部变量为:" + age); System.out.println("name 局部变量为:" + name); } dis.display(); System.out.println(dis.add(3, 5)); } }
上述代码示范了Lambda表达式分别访问“effective final”的局部变量、外部类的实例变量和类变量。Lambda表达式访问局部变量时,编译器隐式为Lambda表达式添加一个私有域常量,并将局部变量放入Lambda表达式的默认构造器中以初始化私有域常量。Lambda表达式对于外部类实例域的访问是编译器将外部类实例引用做为参数传入Lambda表达式的默认构造器,同时在Lambda表达式中定义一个实例域保存外部类实例引用实现的。
Lambda表达式与匿名内部类的主要区别:
例如在Lambda表达式的代码块中增长以下一行,编译器将会报错。
System.out.println(add(3, 5));
在java
中,lambda表达式就是闭包,若是在lambda表达式中使用了所在方法中的局部变量,称lambda表达式捕获了此局部变量。易知被捕获的局部变量都必须是effectively final(最终变量,即变量初始化以后就不会再为它赋新值),且在lambda表达式中也不能改变,不然,当多个变量同时引用此lambda表达式时,会出现并发的安全问题。
lambda表达式的体与嵌套块有相同的做用域。一样适用命名冲突和遮蔽的规则。所以lambda表达式中不能声明与局部变量同名的参数或局部变量。
使用lambda表达式的重点是延迟执行。
在设计接口时,若是接口中只有一个抽象方法,就能够用@FunctionInterface来标记这个接口。这样,若是无心中增长了另外一个非抽象方法,编译器会产生一个错误的信息。
垃圾回收机制具备以下特色:
finalize()
方法,该方法可能使该对象复活(让一个引用变量从新引用该对象),从而致使垃圾回收机制取消回收。当一个对象在堆内存中运行时,根据它被引用变量所引用的状态,能够把它所处的状态分红以下三种:
finalize()
方法进行资源清理。若是系统在调用全部可恢复对象的finalize()
时从新让一个引用变量引用该对象,则这个对象再次变为可达状态;不然该对象将进入不可达状态。finalize()
方法后,依然没有使该对象变为可达状态,那么该对象将永久性地失去引用,变成不可达状态。只有当对象处于不可达状态时,系统才会真正回收该对象占有的资源。当一个对象失去引用后,系统什么时候调用它的finalize()
对其进行资源清理,它什么时候变为不可达状态,系统什么时候回收它占有的内存,对于程序彻底透明。程序只能控制一个对象什么时候再也不被任何引用变量引用,决不能控制它什么时候被回收。
程序没法精确控制Java
垃圾回收的时间,但依然能够强制系统进行垃圾回收--这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不肯定。大部分时候,程序强制系统垃圾回收后总会有一些效果。强制系统垃圾回收有以下两种方式:
System
类的静态方法gc()
:System.gc()
Runtime
对象的实例方法gc()
:Runtime.getRuntime().gc()
。示例以下:
public class GcTest{ public static void main(String[] args){ for(int i = 0; i < 4; i++){ new GcTest(); //下面两种方法彻底相同 System.gc(); //Runtime.getRuntime().gc(); } } public void finalize(){ System.out.println("系统正在清理"); } }
finalize()
finalize()
是定义在Object
类里的实例方法,方法原型为:
protected void finalize() throws Throwable
当finalize()
方法返回后,对象消失,垃圾回收机制开始执行。方法原型中的throws Throwable
表示能够抛出任何异常。
任何java
类均可以重写Object
类的finalize()
方法,在该方法中清理对象占用的资源。只有当程序认为须要更多的额外内存时,垃圾回收机制才会进行垃圾回收。
finalize()
具备以下4个特色:
finalize()
方法,该方法应交给垃圾回收机制调用。finalize()
方法什么时候被调用,是否被调用具备不肯定性,不要把finalize()
当成必定会被执行的方法。JVM
执行可恢复对象的finalize()
方法时,可能使该对象或系统中其余对象从新变为可达状态。JVM
执行finalize()
方法出现异常时,垃圾回收机制不会报告异常,程序继续执行。示例以下:
public class FinalizeTest{ private static final FinalizeTest ft = null; public void info(){ System.out.println("测试finalize方法"); } public static void main(String[] args){ new FinalizeTest(); System.gc(); System.runFinalization(); ft.info(); } public void finalize(){ ft = this; } }
代码中的finalize()
把须要清理的可恢复对象从新赋给静态变量,从而让该可恢复对象从新变成可达状态。一般finalize()方法的最后一句是调用父类的finalize():super.finalize()
对大部分对象而言,程序里会有一个引用变量引用该对象,这是最多见的引用方式。除此以外,java.lang.ref
包下提供了3个类:SoftReference、PhantomReference
和WeakReference
,他们分别表明了系统对对象的3种引用方式:软引用、弱引用和虚引用。所以Java
语言对对象的引用有以下4种方式:
强引用(StrongReference
)
这是Java
程序中最多见的引用方法。程序建立一个对象,并把这个对象赋给一个引用变量。当一个对象被引用变量引用时,它处于可达状态,不可能被垃圾回收机制回收。
软引用
软引用须要经过SoftReference
类来实现,当一个对象只有软引用时,它可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存足够时,它不会被回收,程序也可以使用该对象;当系统内存不足时,系统可能会回收它。
弱引用
弱引用经过WeakReference
类实现,弱引用和软引用相似,但弱引用的引用级别更低。对于只有弱引用的对象而言,当垃圾回收机制运行时,无论系统内存是否足够,总会回收该对象的内存。
虚引用
虚引用经过PhantomReference
类实现,虚引用彻底相似于没有引用。虚引用对对象自己没有太大影响,对象甚至感受不到虚引用的存在。若是一个对象只有虚引用时,那么它和没有引用效果大体相同。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列(ReferenceQueue
)联合使用。
上面三个引用类都包含一个get()
方法,用于获取他们引用的
对象。但虚引用太弱了,没法获取到引用的对象。
引用队列ReferenceQueue
由java.lang.ref.ReferenceQueue
类表示,用于保存被回收后对象的引用。当联合使用软引用、弱引用和虚引用时,系统在回收被引用的对象以后,将把被回收对象的引用添加到关联的引用队列中。
软引用和弱引用能够单独使用,但虚引用不能单独使用,单独使用虚引用没有意义。虚引用的主要做用就是跟踪对象被垃圾回收的状态,程序经过检查与虚引用关联的引用队列是否包含了该虚引用,从而了解虚引用所引用的对象是否即将被回收。
弱引用用法示例:
class Test{ public static void main(String[] args){ String str = new String("java"); //建立弱引用,使其引用str对象 WeakReference wr = new WeakReference(str); str = null; //取出弱引用wr所引用的对象 System.out.println(wr.get()); System.gc(); System.runFinalization(); //输出结果为null,表示对象已被回收 System.out.println(wr.get()); } }
虚引用和引用队列用法示例:
class Test{ public static void main(String[] args){ String str = new String("java"); RefenceQueue rq = new RefenceQueue(); //建立虚引用,使其引用str对象 PhantomReference pr = new PhantomReference(str,rq); str = null; //取出虚引用wr所引用的对象,此处并不能获取虚引用所引用的对象 System.out.println(pr.get()); System.gc(); System.runFinalization(); //垃圾回收后,虚引用将被放入引用队列 //取出引用队列中最早进入队列的引用与pr比较 System.out.println(rq.poll() == pr); } }
使用这些引用类就能够避免在程序执行期间将对象留在内存中。若是以软引用、弱引用或虚引用的方式引用对象,垃圾回收器就能随机地释放对象。
因为垃圾回收的不肯定性,当程序但愿从软、弱引用中取出引用对象时,可能这个对象已经被释放。若是程序须要使用被引用的对象,则必须从新建立该对象。这个过程有以下两种方式:
obj = wr.get();
if(obj == null){ wr = new WeakRefence(recreatIt());//// 1 obj = wr.get();//////// 2 } //操做对象obj //再次切断obj与对象的关联 obj = null //方法二: obj = wr.get(); if(obj == null){ obj = recreatIt(); wr = new WeakRefence(obj); //操做对象obj //再次切断obj与对象的关联 obj = null }
第一种方法,若垃圾回收机制在代码1和2之间回收了弱引用的对象,那么obj
仍可能为null
。而方法二不会出现这种状况。
第二部分:实验部分
实验1: 导入第6章示例程序,测试程序并进行代码注释。
编辑、编译、调试运行阅读教材214页-215页程序6-一、6-2,理解程序并分析程序运行结果;
l 在程序中相关代码处添加新知识的注释。
l 掌握接口的实现用法;
l 掌握内置接口Compareable的用法。
程序以下:
import java.util.*; /** * This program demonstrates the use of the Comparable interface. * @version 1.30 2004-02-27 * @author Cay Horstmann */ public class EmployeeSortTest { public static void main(String[] args) { Employee[] 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);//调用Arrays类的sort方法(只有被static方法修饰了才能够这样调用) //输出全部employee对象的信息 for (Employee e : staff) System.out.println("name=" + e.getName() + ",salary=" + e.getSalary()); } }
用户自定义模块:
public class Employee implements Comparable<Employee>//实现接口类 { 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)//比较Employee与其余对象的大小 { return Double.compare(salary, other.salary);//调用double的compare方法 } }
程序运行结果以下:
测试程序2:
l 编辑、编译、调试如下程序,结合程序运行结果理解程序:
interface A//接口A { double g=9.8; void show( ); } class C implements A//C实现接口A { public void show( ) {System.out.println("g="+g);} } class InterfaceTest { public static void main(String[ ] args) { A a=new C( ); a.show( ); System.out.println("g="+C.g);//C实现了接口A,因此能够用C调用A中的变量 } }
程序运行结果以下:
测试程序3:
l 在elipse IDE中调试运行教材223页6-3,结合程序运行结果理解程序;
l 26行、36行代码参阅224页,详细内容涉及教材12章。
l 在程序中相关代码处添加新知识的注释。
l 掌握回调程序设计模式;
程序以下:
/** @version 1.01 2015-05-12 @author Cay Horstmann */ import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.Timer; // to resolve conflict with java.util.Timer public class TimerTest { public static void main(String[] args) { ActionListener listener = new TimePrinter();//实现ActionListener类接口 //建立一个名为listener的timer数组 // 每十秒钟一次 Timer t = new Timer(10000, listener);//生成内置类对象 t.start();//调用t中的start方法 JOptionPane.showMessageDialog(null, "Quit program?");//窗口显示信息“Quit program?” System.exit(0); } } class TimePrinter implements ActionListener//用户自定义类:实现接口 { public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + new Date()); Toolkit.getDefaultToolkit().beep();//返回Toolkit方法,借助Toolkit对象控制响铃 } }
程序运行结果以下:
测试程序4:
调试运行教材229页-231页程序6-四、6-5,结合程序运行结果理解程序;
在程序中相关代码处添加新知识的注释。
掌握对象克隆实现技术;
掌握浅拷贝和深拷贝的差异。
程序以下:
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 { // 调用对象克隆 Employee cloned = (Employee) super.clone(); // 克隆易变字段 cloned.hireDay = (Date) hireDay.clone(); return cloned; }//可能产生异常,放在try子句中 /** * 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(); // 实例字段突变 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 + "]"; } }
/** * This program demonstrates cloning. * @version 1.10 2002-07-01 * @author Cay Horstmann */ public class CloneTest { public static void main(String[] args) { try//try子句,后面是有可能会产生错误的代码 { Employee original = new Employee("John Q. Public", 50000); original.setHireDay(2000, 1, 1); Employee copy = original.clone(); copy.raiseSalary(10); copy.setHireDay(2002, 12, 31); System.out.println("original=" + original); System.out.println("copy=" + copy); } catch (CloneNotSupportedException e)//没有实现cloneable接口,抛出一个异常 { e.printStackTrace(); } } }
程序运行结果以下:
实验2: 导入第6章示例程序6-6,学习Lambda表达式用法。
l 调试运行教材233页-234页程序6-6,结合程序运行结果理解程序;
l 在程序中相关代码处添加新知识的注释。
将27-29行代码与教材223页程序对比,将27-29行代码与此程序对比,体会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) { String[] planets = new String[] { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" }; System.out.println(Arrays.toString(planets)); System.out.println("Sorted in dictionary order:"); Arrays.sort(planets);//调用Arrays类的sort方法 System.out.println(Arrays.toString(planets)); System.out.println("Sorted by length:"); Arrays.sort(planets, (first, second) -> first.length() - second.length());//lambda表达式 System.out.println(Arrays.toString(planets)); Timer t = new Timer(1000, event -> System.out.println("The time is " + new Date())); t.start(); // keep program running until user selects "Ok" JOptionPane.showMessageDialog(null, "Quit program?");//窗口显示信息“Quit program?” System.exit(0); } }
程序运行结果以下:
实验3: 编程练习
l 编制一个程序,将身份证号.txt 中的信息读入到内存中;
l 按姓名字典序输出人员信息;
l 查询最大年龄的人员信息;
l 查询最小年龄人员信息;
输入你的年龄,查询身份证号.txt中年龄与你最近人的姓名、身份证号、年龄、性别和出生地;
查询人员中是否有你的同乡。
程序以下:
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.Collections; import java.util.Scanner; public class Test{ private static ArrayList<Person> Personlist1; public static void main(String[] args) { Personlist1 = new ArrayList<>(); Scanner scanner = new Scanner(System.in); File file = new File("C:\\Users\\lenovo\\Documents\\身份证"); try { FileInputStream F = new FileInputStream(file); BufferedReader in = new BufferedReader(new InputStreamReader(F)); 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 place =linescanner.nextLine(); Person Person = new Person(); Person.setname(name); Person.setid(id); Person.setsex(sex); int a = Integer.parseInt(age); Person.setage(a); Person.setbirthplace(place); Personlist1.add(Person); } } 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.输入你的年龄,查询身份证号.txt中年龄与你最近人的姓名、身份证号、年龄、性别和出生地"); System.out.println("4:按省份找你的同乡;"); System.out.println("5:退出"); int type = scanner.nextInt(); switch (type) { case 1: Collections.sort(Personlist1); System.out.println(Personlist1.toString()); break; case 2: int max=0,min=100;int j,k1 = 0,k2=0; for(int i=1;i<Personlist1.size();i++) { j=Personlist1.get(i).getage(); if(j>max) { max=j; k1=i; } if(j<min) { min=j; k2=i; } } System.out.println("年龄最大:"+Personlist1.get(k1)); System.out.println("年龄最小:"+Personlist1.get(k2)); break; case 3: System.out.println("place?"); String find = scanner.next(); String place=find.substring(0,3); String place2=find.substring(0,3); for (int i = 0; i <Personlist1.size(); i++) { if(Personlist1.get(i).getbirthplace().substring(1,4).equals(place)) { System.out.println("你的同乡:"+Personlist1.get(i)); } } break; case 4: System.out.println("年龄:"); int yourage = scanner.nextInt(); int close=ageclose(yourage); int d_value=yourage-Personlist1.get(close).getage(); System.out.println(""+Personlist1.get(close)); break; case 5: isTrue = false; System.out.println("再见!"); break; default: System.out.println("输入有误"); } } } public static int ageclose(int age) { int m=0; int max=53; int d_value=0; int k=0; for (int i = 0; i < Personlist1.size(); i++) { d_value=Personlist1.get(i).getage()-age; if(d_value<0) d_value=-d_value; if (d_value<max) { max=d_value; k=i; } } return k; } }
public class Person implements Comparable<Person> { private String name; private String id; private int age; private String sex; 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 int getage() { return age; } public void setage(int age) { // int a = Integer.parseInt(age); this.age= age; } public String getsex() { return sex; } public void setsex(String sex) { this.sex= sex; } public String getbirthplace() { return birthplace; } public void setbirthplace(String birthplace) { this.birthplace= birthplace; } public int compareTo(Person o) { return this.name.compareTo(o.getname()); } public String toString() { return name+"\t"+sex+"\t"+age+"\t"+id+"\t"; } }
程序运行结果以下:
实验4:内部类语法验证明验
实验程序1:
l 编辑、调试运行教材246页-247页程序6-7,结合程序运行结果理解程序;
l 了解内部类的基本用法。
程序以下:
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); 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 { public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + new Date()); if (beep) Toolkit.getDefaultToolkit().beep(); } } }
程序运行结果以下:
实验程序2:
l 编辑、调试运行教材254页程序6-8,结合程序运行结果理解程序;
了解匿名内部类的用法。
程序以下:
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(); 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(); } }
程序运行结果以下:
实验程序3:
l 在elipse IDE中调试运行教材257页-258页程序6-9,结合程序运行结果理解程序;
l 了解静态内部类的用法。
程序以下:
/** * 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); } }
程序运行结果以下:
3、实验总结:
在本周的学习过程当中,主要了解了接口,接口和继承在某些方面比较类似,可是接口又在继承的基础上发展了一些优势,克服了java单继承的缺点。在学习过程当中,多是由于接口并非具体的类,它只是实现,因此感受接口比继承抽象一些,不太容易理解。但经过这周的学习以及实验中对具体程序的运行,对接口有了必定的掌握。本身编写饰演的过程当中,在以前的基础上有的接口等新内容,本身仍是不能独立完成,在同窗的帮助下才勉强完成了实验。在实验课上老师讲的克隆以及函数接口等,本身尚未太掌握,在以后的学习中,必定会继续深刻学习。