Java核心技术学习笔记第六章接口、lambad表达式与内部类

第六章接口、lambda表达式与内部类

接口

1.接口用来描述类应该做什么,而不指定他们具体应该如何做。
2.接口不是类,而是对希望符合这个接口的类的一组要求
3.接口中所有办法自动都是public,声明方法时不必提供关键字public。但在实现类中必须声明public。
4.在Java8之前,接口不会有实现方法
5.接口没有实例字段,但是有常量,实例字段都在实现的类中

Comparable接口

1.在Java5中,Comparable接口已经提升成为一个泛型类型。但是仍然可以使用不带参数类型的原始Comparable类型。
2.compareTo方法必须有确实能比较的两个对象
3.Comparable接口的compareTo方法将返回一个整数
4.如果能确信比较的数为非负整数或者它们的绝对值不会超过(Integer.MAX_VALUE-1)/2就不会出问题。否则,调用Integer.compare方法,相减的技巧不适合运用在浮点型,因为它们的差经过四舍五入之后可能变成0
5.compareTo应当与equals方法兼容,当compareTo等于0的时候,equals也要等于true,大多数都满足这个要求,但是BigDecimal是一个例外,equals为false因为精度不同,compareTo没有更好的方法表示它们谁比较大
6.要让一个类使用排序服务必须要让他实现compareTo,因为Java程序语言是一种强类型语言。在调用方法时,编译器要能检查这个方法确实存在,如果a是一个Comparable对象的数组,就可以确保拥有compareTo方法,因为每个实现Comparable接口的类必须有这个方法的定义。
7.你可能会认为Array类中的sort方法定义为接受一个Comparable[]数组,倘若有人调用了sort方法时所提供的元素类型没有实现Comparable接口,编译器就会报错,事实上,sort方法可以接受一个Object[]数组,并使用一个笨拙的强制类型转换,如果a[i]不属于一个实现了Comparable接口的类,虚拟机就会抛出异常
8.对于任意的x和y,实现者必须能够保证sgn(x.compareTo(y))=-sgn(y.compareTo(x))。也就是说x.compareTo(y)抛出异常的话y.compareTo(x)也应该抛出异常。sgn(n)的意思时若n等于1时sgn(n)=1,若n等于-1时sgn(n)=-1,若n等于0时sgn(n)=0。所以如果翻转参数的话,结果的符号也应该翻转(但具体值不一定)
9.与equals方法一样,在继承中有可能会出现问题,如果子类覆盖父类的compareTo方法就必须要加getClass()进行判断,决不能仅仅进行强制转换进行比较,这样使用子类调用比较时会报错

接口的属性

1.接口不是类,不能new运算符实例化一个几口
2.接口能声明变量必须引用这个接口的类对象
3.使用instance of属于一个对象是否实现类某个特定的接口
4.与继承层次一样,也可以扩展接口。允许有多条接口链,从通用性较高的扩展到专用性较高的
5.虽然在接口中不能包含实例对象,但是可以包含类常量,接口中字段总是public static final,Java建议省略关键字。
6.任何实现接口的类都自动继承了这些常量,并可以在方法中直接引用NORTH,而不必采用接口名.常量名使用。不过这样使用接口更像是退化,所以我们建议不要这样使用
7.每个类只有一个超类,但可以实现多个接口,这就为我们定义类的行为提供极大的灵活性,如果想要实现多个接口,接口名可以用逗号隔开。
8.抽象类表示通用属性存在一个严重的问题,每个类只能扩展一个类,继承一个类之后,就不能再扩展第二个类了。
9.如果一个类有多个超类,我们将这个特性称为多重继承,Java设计者选择了不支持多重继承,主要原因可能是多重继承可能会让语言变得复杂,或者效率降低。接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。

静态和私有方法

1.允许接口中增加静态方法,没有任何理由认为这是不合法的,只是违背了接口设计的初衷
2.目前为止,通常做法是将静态方法放在伴随类中,但在Java11中在接口提供等价的方法。
3.在Java9中,接口中的方法可以是praivate。private方法可以使静态方法或实例方法,由于private方法只能在本接口方法中使用,所以它们的用法很有限,只能作为接口中其他方法的辅助方法

默认方法

1.可以为接口方法提供一个默认的实现。必须用default修饰符标记这样一个方法
2.默认方法的一个重要用法是"接口演化",如果为一个接口作为Java一部分已经很多年了,现在为这个接口增加一个新的方法,如果这个方法不是一个默认的方法,那么类不能编译,因为它没有实现这个新方法,为接口增加一个非默认方法不能保证“源代码兼容”,
3.不过,假设不重新编译这个类,而只是使用原先的一个包含这个类的JAR文件。这个类仍能加载。程序仍然能正常构造Bag实例,不会有意外发生(为接口增加方法可以保证“二进制兼容”)不过如果程序在Bag实例上调用stream方法,就会产生异常
4.如果实现了一个默认方法,就可以解决这两个问题。

解决默认方法冲突

1.如果一个接口将一个方法定义为默认方法,然后又在超类或另一个接口中定义同样的方法,就会产生冲突。
2.超类优先。如果超类提供一个具体方法,同名而且有相同参数的默认方法会被忽略
3.接口冲突,如果一个接口提供一个默认方法,另外一个接口提供了一个同名而且参数一样的相同方法,必须覆盖这个方法来解决冲突,只需要在实现的类提供一个实现方法就可以。
4.如果两个接口都没有提供为共享方法提供默认实现,那么就与Java8之前的情况一样,这里不存在冲突。有两种选择,实现这个方法,或者干脆不实现,如果不实现的话,这个类是抽象的。
5.不要让一个默认方法重新定义Object类中的某个方法。例如,不能为toString或equals方法定义默认方法,尽管对于List之类的接口很有吸引力,由于类优先,这样的方法无法超越Object.toString或Object。equals。

接口与回调

1.回调是一种常见的程序设计模式。在这种情况下,可以指定某个特定事情发生时应该采取的动作。
2.如果想在特定时间执行一个动作,可以使用ActionListener接口,可以定义一个实现ActionListener的类,然后将想要执行的语句放在actionPerformed中。

comparator接口

1.如果我们想改变已经实现了Comparable接口的类对象的排序方式,肯定让类用两种不同的方式实现compareTo方法
2.Array.sort方法还有第二种方法,有一个数组和一个比较器(comparator)作为参数
3.创建一个实现Comparator接口的类,然后创建这个类的实例,这个实例没有状态,但是要用他来调用compare方法,不是在对象本身调用方法。

对象克隆

1.Cloneable接口,这个接口指示一个类提供一个安全的clone方法
2.clone方法是Object的一个protected方法,这说明你的代码不能直接调用这个方法。只有重新定义clone为public才能让所有方法克隆对象。
3.只有Employee类可以克隆Employee对象,因为它对这个类一无所知,所以只能一个字段地进行拷贝。
4.拷贝的字段是基本数据,拷贝这些字段没有任何问题,但是如果对象包含子对象的引用,拷贝字段就会得到相同子对象的另一个引用,这样一来,原来对象和克隆的对象仍然会共享一些信息
5.Object类的clone方法是浅拷贝,并没有克隆对象中引用的其他对象
6.浅拷贝的影响:如果原对象与浅拷贝共享的是一个不可变的对象或者子对象包含不变的常量,没有更改器方法会改变它,也没有编发会生成它的引用,这种情况安全。如果对象是可变的,必须重新定义一个clone方法来建立深拷贝。
7.对于每一个类,需要确定:一、默认的clone方法是否能满足需求;二、是否可以在可变的子对象调用clone来修补默认的clone方法;三、是否不该使用clone;实际上第3个选项是默认选项。如果选择第1项或第2项,类必须:1、实现Cloneable接口;2、重新定义clone方法,并指定public访问修饰符;
8.Cloneable接口的出现于接口的正常使用没有关系,它没有指定clone克隆方法,这个方法是从Object继承的。这个接口是一个标记接口,指示类设计者了解克隆过程,它还可以允许类型查询中使用instanceof;如果没实现这个接口调用clone方法会抛出异常
9.在Java1.4之前,clone方法的返回类型总是Object,而现在可以为clone方法提供返回类型。
10.任何数组类型中都有一个公共的clone方法,可以用这个方法建立一个新数组,包含原数组所有元素的副本。

lambda表达式语法

1.lambda表达式是一个可传递的代码块,可以在以后执行一次或者多次
2.lambda表达式就是一个代码块,以及必须传入代码的变量规范
3.lambda表达式形式:参数,箭头(->)以及一个表达式,如果无法放在一个表达式中,就可以把它放在一个{}中,并包含显示的return。
4.即使lambda没有参数,仍要提供空括号,就像无参数一样
5.如果可以推到出一个参数类型,就可以忽略参数类型
6.如果只有一个参数,而且可以推导出参数类型,那么就可以省略小括号
7.无须指定lambda表达式的返回类型。lambda表达式的返回类型总是会由上下文推到得出
8.lambda表达式只在某些分支返回一个值,而另外一些分支不返回值,这是不允许的

函数式接口

1.对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口。
2.在底层,Array.sort方法会接收实现Comparator<String>某个类对象,在这个对象上调用compare方法会执行这个lambda表达式的体。这个传统使用的内联类相比,这可能要高效得多。
3.lambda表达式所能做的也只是转换为函数接口,不能把lambda表达式赋值给Object变量,Object不是函数式接口。

方法引用

1.var timer=new Timer(1000,System.out::println)是一个方法引用,它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法。
2.类似于lambda表达式,方法引用也不是一个对象。不过,为一个类型为函数接口的变量赋值时会产生一个对象。
3.方法引用有三种情况:object::instanceMethod、Class::instanceMethod、Class::staticMethod
4.只有当lambda表达式的体只调用一个方法而不做其他操作时才能把lamdba表达式重写为方法引用。如s->s.length()==0,这里有一个方法调用。但还有一个比较,所以这里不能使用方法引用。
5.方法引用选用哪个版本的方法取决于转换为哪个函数式接口的方法参数,类似于lambda表达式,方法引用不能独立存在,总是转换为函数式接口的实例。
6.有一个API包含一些专门用作方法引用。例如Object类有一个方法isNull,用于测试一个对象是否为null。不过看上去好像没什么用,因为测试obj=null比Object.isNull(obj)更有可读性,不过可以把方法引用传递到任何有Predicate参数的方法
7.如果sparator为null,构造sparator::equals时就会立即抛出一个NullPointerException异常。而lambda表达式x->separator.equals(x)只在调用时才会抛出异常。
8.方法引用中也可以使用this参数。例如this::equals等同于x->this.equals(x),super::instanceMethod方法会使用this作为目标,会调用给定方法的超类版本

构造器引用

1.构造器引用与方法引用很类似,只不过方法为new。Person::new是一个构造器引用。调用哪个构造器,这取决于上下文
2.Java有一个限制,无法构造泛型类型T数组,数组构造器引用对于克服这个限制很有用。表达式newT[n]会产生错误,因为这会改成new Object[n],Stream接口只有一个toArray方法可以返回数组Object[] people=stream.toArray();我们希望返回一个Person引用数组,而不是Object引用数组。可以把Person[]::new传入toArray方法,这样就可以得到有一个正确的类型数组。

变量作用域

1.lamdab表达式可以访问外围的变量
2.lambda表达式有3个部分:1、一个代码块,2、参数,3、自由变量的值,这里指非参数而且不在代码中定义的变量。
3.lambda表达式的数据接口必须存储自由变量的值,我们说lambda表达式捕获
4.lambda表达式捕获的变量必须实际上是事实最终变量,事实最终变量是指,这个变量初始化之后就不会再为它赋新值
5.lambda表达式与嵌套块有相同的作用域。这里同样适用命名冲突和遮蔽的有关规则。在lambda表达式中声明与一个局部变量同名的参数或局部变量是不合法的。
6.表达式this.toString会调用Application对象的方法,在lambda中this的使用方法没有什么特殊之处,this方法是指嵌套在那个方法的作用域。

处理lambda表达式

1.使用lambda表达式的重点是延迟执行。毕竟,如果想要立即执行代码,完全可以直接执行,而无须把他包装在一个lambda表达式当中。
2.之所以希望以后再执行代码,这有很多原因如:1、在一个单独的线程中运行代码;2、多次运行代码;3、在算法的适当位置运行代码(例如,排序中的比较操作);4、发生某种情况时运行代码(如,点击了一个按钮,数据传达);5、只在必要时才运行代码
3.如果设计自己的接口,其中只有一个抽象方法,可以用@FunctionalInterface注解来标记这个接口。这样有两个优点,如果你无意中增加了另一个抽象方法,编译器会产生一个错误消息,javadoc页里会指出你的接口是一种函数式接口。

内部类

使用内部类两个原因
1.内部类是定义在另外一个类中的其他类。
2.内部类可以对同一个包中的其他类隐藏
3.内部类方法可以访问定义这个类的作用域中的数据,包括原本私有数据。
4.内部类原先对于简洁地实现回调非常重要,不过如今lambda表达式在这方面可以做的更好。但内部类对于构建代码还是很有用的
5.内部类的对象总有一个隐式引用,指向创建它的外部类对象
6.外围类的引用在构造器中设置。编译器会修改所有的内部类构造器,添加一个对应外围类引用的参数,在构建内部类时会把外部类的引用传入
7.只有内部类可以是私有的,而常规类可以有包可见性或公共可见性

内部类的特殊语法规则

1.事实上我们应该用OuterClass.this表示外部类引用
2.可以用outerObject.new InnerClass(construction parameters)来创建对象
3.最新构造内部类对象的外围类引用被设置为创建内部类对象方法的this引用,通常this.限定词是多余的
4.内部类声明的所有静态字段必须都是final,并初始化为一个编译时常量,如果不是一个常量,就可能不唯一
5.内部类不能有static方法,也可以允许有静态方法,但只能访问外部类的静态字段和方法

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

1.内部类是一个编译器现象与虚拟机无关。编译器将会把内部类转换为常规类文件,用$(美元符号)分隔外部类名与内部类名。
2.编译器生成一个额外的实例字段this$0,对应外围类的引用($this0是编译合成的,在你自己编写的代码中不能引用这个字段)
3.内部类拥有更大的访问权限,所以天生就比常规类功能强大
4.内部类会在编译器外围添加静态方法。它将返回作为参数传递的那个对象的beep字段。(方法名可能不同,这取决于你的编译器),所以内部类有了这样的权限
5.这种方法会存在风险,黑客可以使用十六进制编辑器创建类文件,其中利用虚拟机指令调用。由于隐秘方法需要由包可见性,所以攻击代码需要与被攻击类放在同一个包中。

局部内部类

1.如果只是在一个方法使用这个类的对象时使用一次,当遇到这类情况时,可以在一个方法中局部地定义这个类
2.声明类时不能有访问说明符(public或private)。局部类的作用域被限定在这个局部类块中。除start方法之外,就没有任何方法知道TimePrinter类的存在

由外部方法访问局部变量

1.与其他内部类相比,局部类还有一个优点。它们不仅能够访问外部类的字段,还可以访问局部变量!不过,那些局部变量必须是事实最终变量。这说明它们一旦赋值就不会改变
2.局部内部类可以访问局部变量是因为当创建一个对象时,局部变量的值会传递给构造器,并存储在一个字段中。编译器检测对局部变量访问,为每一个变量建立相应的实例字段,并将局部变量复制到构造器,从而初始化这些字段

匿名内部类

1.假如只想创建这个类的一个对象,甚至不需为类指定名字,这样的类可以称为匿名内部类
2.语法为:new SuperType(construction parameters){inner class methods and data}
new SuperType可以是接口,这样内部类就实现这个接口,也可以是一个类,如果是这样,内部类就要扩展这个类。
3.由于构造器的名字必须与类名相同,而匿名内部类没有类名,所以,匿名内部类不能拥有构造器。实际上,构造参数要传递给超类构造器。具体地,只要内部类实现一个接口,就不能有任何构造参数。不过仍要提供一组小括号。
4.如果构造参数列表的结束小括号后面跟一个大括号,就是在定义匿名内部类。
5.尽管匿名内部类不嫩更有构造器,但可以提供一个对象初始化块
6.双括号初始化外围的括号为匿名内部类,里面的括号为初始化
7.建立一个与超类大体类似的匿名内部类很简单,但是对于equals方法是要特别当心
8.生成日志时,往往希望加上类名,但是对于静态方法不奏效,但可以使用new Object(){}.getClass().getEnclosingClass()得到外围类。

静态内部类

1.使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类有外围对象的一个引用。为此,可以将内部类声明为static,这样就不会产生外部类引用。
2.只要内部类不需要访问外围类对象,就应该使用静态内部类。有些程序员使用嵌套类表示静态内部类
3.与常规内部类不同,静态内部类可以有静态字段和方法
4.在接口中声明的内部类自动是static和public

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值