参考 Java核心技术第十版 Java编程思想
还有网上的各位大大的博客
在尾部会一一列出
方法
接口与静态方法
在JAVASE8中 允许在接口中增加静态方法 理论 没有任何理由认为这是不合法的 只是这有违于将接口作为抽象规范的初衷
1 | public interface TestStaticInterace { |
这里插点自己的一些想法
关于接口与静态方法
在JAVASE8之前的版本中 接口是用来定义规范的 接口中的方法默认都是public abstract前缀的 是不能进 static修饰
在抽象类中 同样的 abstract修饰的方法也是不能进行static(SE8中也不能)
在编程思想中有这么两句话描述
The interface keyword produces a completely abstract class, one that provides no implementation at all. It allows the creator to determine method names, argument lists, and return types, but no method bodies. An interface provides only a form, but no implementation
When you say something is static, it means that particular field or method is not tied to any particular object instance of that class
总体的意思就是
- 接口是完全抽象的 只是用来定义规范的 就是没有方法体 要让实现类去实现
- 一个静态的什么东西 方法或者字段之类 它是不能绑定到某个特定的实例对象上去的
这个意思就很明显了 在接口中定义了这个static 要去某个特定类实现 但是这个静态方法是不能绑定到某个特定的实例对象上的 这就是自相矛盾了 也是之前没有在接口中加入static修饰的原因之一吧
至于为什么现在加入了 更多我的觉得是一个API简洁与维护 扩展(默认方法同) 而不用到一个特定的实现类中
比如以下代码
1 | void ceshi(String s);//实现于某特定对象实例 |
如果放到类中的话 事实上给人的感觉却是此API的用户并不是面向这个接口进行编程,而是面向这个接口的实现类在编程
这样子在一些代码中维护起来就会方便很多
另外的话单独直接在接口中定义的话也会使用起来方便很多
也保持了API的简洁 最重要还是方便 这个具体用处还是自己体会比较好
接口与默认方法
注意 默认方法不是静态方法 静态方法我们直接点击接口名之后就能点击出来 但是默认方法不是这样 是在实现类中进行引用的
下面是例子
1 | interface iTest{ |
关于其意义何在
引用俩段话
在 java 8 之前,接口与其实现类之间的 耦合度 太高了(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为 java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。
在Java Api中 很多的接口都会有其伴随类 这个伴随类实现了这个接口的部分或者全部方法 比如 Collection/AbstractCollection
在Java SE8中 可以直接在接口中实现方法 这个方式已经过时
和其他方法一样 默认方法也可以被继承的
如果在一个接口中将一个方法定义为默认方法 然后又在超类或另一个接口中定义同样Java 就会发生冲突
JAVA的相应规则就简单多了
- 超类优先 如果超类提供一个具体方法 同名而且有相同参数类型的默认方法被忽略
1 | public interface Person { |
- 接口冲突 如果一个超接口提供了一个默认方法 另一个接口提供了一个同名而且参数类型(不论是否默认参数的相同方法) 必须覆盖这个方法来解决冲突
1 | public interface Named { |
类会继承Person和Named接口提供的俩个不一致的getName方法 并不是从中选择一个
Java编译器会报告一个错误 让程序员解决这个二义性
只需要在Student类中提供一个getName方法
1 | public class Student implements Person,Named{ |
注意 在默认方法冲突中 是类优先原则
类优先原则可以确保与JAVA SE7的兼容性 如果为一个接口增加默认方法 这对于这个默认方法之前能正常工作的代码不会有任何影响
lambda表达式
终于到关键部分了 lambda是一个希腊字母
(大写Λ,小写λ,中文音译:兰布达)
为什么起这个名字 在以前没有计算机的时候 逻辑学家Alonzo Church想要形式化的表示能有效计算的数学函数 他使用了希腊字母lambda来标记参数
lambda:
1 | (参数)->表达式 |
lambda表达式的形式:
参数 箭头(->)以及一个表达式 如果代码要完成的计算无法放在一个表达式中 就可以像写方法一样 把这些代码放在{}中 并包含显示的return语句
比如:
1 | (String o1,String o2)->{ |
即使lambda表达式没有空参数 也要提供空括号 就像无参数方法一样
1 | ()->{system.out.println("test");} |
无需指定lamdab表达式的返回类型 lambda表达式的返回类型总是会有上下文推导得出
1 | (String o1,String o2)->o1.length()-o2.length() |
可以在需要int类型结构的上下文中使用
可以省略参数类型声明
1 | (o1,o2)->o1.length()-o2.length(); |
下面是lambda的重要特征
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值
Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据
在没有lambda的时候 一般都是采用匿名类来代替lambda
比如Thread类
1 | /*Thread 匿名内部类*/ |
下面是对Thread类的模拟实践
1 | @FunctionalInterface |
对于只有一个抽象方法的接口 需要这种接口的对象时 就可以提供一个lambda表达式 这种接口称为函数式接口 在本例中 我们将TestLambda接口声明@FunctionalInterface(这个在没有的情况也是可以的)
下面是lambda表达式的其他示例
1 | public static void main(String[] args){ |
最好将lambda看成一个函数 而不是一个对象 这样方便理解
在java中 对lambda表达式所能做的也只是能转换为函数结构 在其他支持函数字面量的语言中 可以声明函数类型(String,String)->int 声明这些类型的变量 还可以使用变量保存函数表达式
Java API中在java.util.funtion包中定义了很多通用的函数式接口
方法引用
方法引用有如下几种形式
1 | Class::new //初始化Class对象 |
在前面三种情况 方法引用等价于提供方法参数的lambda表达式
如:
1 | Class::new 等价于()->new Class() |
在使用构造引用时 选择哪个构造器是取决于上下文的
1 | Class::static_method 等价于(参数)->Class.static_method |
至于第4种情况 第一个参数会成为方法的目标
比如
1 | String::compareToIgnoreCase |
等同于
1 | (x,y)->x.compareToIgnoreCase(y) |
也可以在方法引用中使用super,this参数
1 | super::instanceMethod |
变量作用域
1 | public static void repeatMessage(String text, int delay) { |
这个lambda表达式中变量text 注意这个比那里并不是在这个lambda表达式中定义的 实际上 这是repeatMessage方法的一个参数变量
lambda表达式有3个部分
- 一个代码块
- 参数
- 自由变量的值 这里值非参数而不在代码中定义的变量
在这个例子中 这个lambda表达式有1个自由变量text 表示lambda表达式的数据结构必须存储自由变量的值 在这里就是字符串 我们说它被lambda表达式捕获了
这其实就是闭包
lambda表达式可以捕获外围作用域中变量的值 在Java中要确保所捕获的值是明确定义的 这里有一个重要的限制 在lambda表达式中 只能引用值而不会改变的变量
1 | public static void repeatMessage(String text, int delay) { |
lambda表达式中捕获的对象实际上是最终变量 实际上的最终变量是指 这个变量初始化之后就不会再为它赋新值 在这里 text总是只是同一个String对象 所以捕获这个变量是合法的 不过i的值会改变 因此不会捕获
处理lambda表达式
在Java.util.function中提供一了一系列的函数式接口
如
1 | BooleanSupplier |
这里就不说了 太多了
Streams API
发现了一篇好的Streams API介绍博客
珠玉在前 没有必要写了