最近没怎么学习,忙了点其他事情。两周时间,竟然忘的差不多了。赶快回顾一下
接口不是类,而是对类的一组需求描述,类要遵从。
接口中的方法默认为public,可以省略。
接口中可以定义常量,但不能有实例域
SE8后,可在接口中实现简单方法(但是方法中不可引用实例域)
方法的实现和实例域都应该由类来完成
不少人喜欢说接口是没有实例域的抽象类,但是我觉得对于刚学习的同学来讲,还是不要搞这么复杂,因为容易搞混,直接区分开两个东西最好,接口是接口、类是类。忽略前面那个拗口的解释。
在类实现接口时,必须把方法声明为public
要让一个类使用排序服务必须让它实现compareTo方法
java是强类型语言,在调用方法时,编译器会检查这个方法是否存在,而但对象实现了Comparable接口时就一定会有compareTo方法;这是一种约定好的方法,只要按规则做就好,每个接口方法都有既定的规则。
Array.sort()方法直接影响数组内部元素的排序;
下来不是关键,可略
(在看书的时候大脑的思维也是挺活跃的,不知道为什么会突然想到,之前在书上看到过的这样的话,如果面向对象,面向过程用一句话来说是怎样的区别? 借用书上的话:一个是算法+数据,一个是数据+算法)get the point
为什么会突然冒出句话,不知道,暂时还没有研究过大脑是怎么运行的。
接口不是类,不能用new;
接口变量必须引用实现了接口的类对象;
可用instanceof检查是否实现了接口;
接口也可以扩展、接口链,extends;
接口中的域即常量 自动设置为public static final;
接口为定义类的行为提供了极大的灵活性;
接口解决了类的扩展问题:因为extends只能一个,而 implements 可以多个;
避免了复杂性和低效性
接口的默认方法
public interface Comparable<T> { default int comparaTo(T other){return 0;} //by default , all elements are the same; }
接口演化: 很久以前你定义了一个public class Bag implements Collection{},但是SE8在Collection添加了stream()方法,因为接口的规定,类必须实现方法。所以Bag就会出错,即接口新增加一个非默认方法不能保证“源代码兼容”,而默认方法可以解决这个问题,Bag类内,不实现此方法时调用Bag.stream()实际上是调用Collection.stream()中的默认实现
默认方法冲突(二义性):接口中将一个方法定义为默认方法,在超类或者另一个接口中也定义了同样的方法,那么一个类在同时extends超类和implements接口时,到底选择哪一种的方法实现呢?
1.超类优先(选择超类的实现)
2.接口冲突:implements两个接口,两个接口都同时有相同的默认方法实现;
解决:在定义类内必须重写此方法,至于选择哪个接口,由程序员决定。
接口的实际使用
回调callback常见的设计模式
定时器Timer ,如何告知定时器?提供一个函数定时器周期性调用,将某个带有需要调用的函数的类传给定时器;定时器知道调用哪个方法;
这一方法存在于ActionListener接口,所以要求传进去的类必须实现这个接口
定时器抵用ActionPerformed方法
Timer类主要用于后台调度任务
接口是个好东西;
对象克隆,细节技术性很强;
clone是Object的一个protected方法
浅拷贝:如果对象有包含子对象的引用,拷贝域会得到子对象另一个引用(共享子对象)
因为子对象是可变的所以必须重新定义clone方法来建立一个深拷贝;
LocalDate是不可变的,无需任何处理;
因为Object类的clone是protected所以子类必须重新定义clone方法为public ,才能anObject.clone() 这样调用。
cloneable接口的出现与clone方法的正常使用并没有多大关系
Cloneable接口没有指定clone方法,因为方法是从Object类中继承,所以像Cloneable接口这样的又被称为标记接口;
作为标记指示类设计者了解克隆过程;
但是未实现此接口会出异常。
浅拷贝只是重写Object的clone方法并改为public修饰;
创建深拷贝的clone方法:克隆对象可变的实例域;
// 深拷贝 class Employee implements Cloneable{ public Employee clone() throws CloneNotSupportedException{ Employee cloned = (Employee) super.clone(); cloned.hireDay = (Date) hireDay.clone(); } }
未实现Cloneable接口,Object的clone方法会抛出一个cloneNotSupportException异常,实现就不会,但为了通过编译还是要再重写方法加上throws CloneNotSupportException;
try{}catch(){} 非常适用于final类,否则还是保留throws说明符
克隆并不常用;
所有数组类型都有一个public clone方法;
不可变类型 VS 可变类型
lambda表达式:可传递的代码块
class Worker implements ActionListeners{ public void actionPerformed(ActionEvent event){ //do some work (重点关键) } }
将一个代码块传递到某个对象,代码块会在将来调用;
但是以往采取的方法都是不能直接传递代码块,必须像上面代码一样 构造类后创建对象(对象包含所需要的代码块)
java设计者长期以来拒绝直接传递代码块这种特性。
但是又不得不找一种合适的java设计
所以有了lambda表达式语法,lambda表达式就是一个代码块,以及代码的变量规范:
参数+“->”+表达式; (返回类型由上下文推导得出)
// Lambda
(String first,String second)->first. length ()-second.length();
java有一个特别的地方,比如if语句
if(a>b)return 0 ;//如果没写另一个分支else的返回值 ,就会出错 不合法。
函数式接口-》封装代码块的接口;
lambda表达式与这些接口是兼容的
对于只有一个抽象方法的接口(必须有一个抽象方法),需要这种接口对象时就可以传递一个lambda表达式
SE8 后 接口可以声明非抽象方法
lambda表达式的实现,在底层实现某个类的对象,在对象上调用方法,方法内执行lambda表达式的体
lambda表达式可以转换为接口
要接受lambda表达式可以传递到函数式接口,换句话说 lambda表达式仅仅可传给函数式接口;
想要用lambda表达式还是需要为它建立一个特定的函数式接口;
// 如果可以推导出一个lambda表达式的参数类型,可以忽略其类型: Comparator<String> comp = (first,second)->first.length()-second.length();
方法引用 : 表达式System.out::println是一个方法引用,等价于 lambda表达式 x->System.out.println(x); 表示已有现成的方法可以完成你想要传递到其他代码的某个动作;
用::分隔方法名与对象或类名;
方法引用又是比lambda表达式更进一步的简化;
和lambda表达式一样总是会转化为函数式接口的实例;
可以在方法中使用this参数
this::equals() ==> (x)->this.equals(x);
构造器 引用 如:Person::new (和方法引用类似,只是方法名变成了new)
字符串列表转换为一个Person对象数组
ArrayList<String> names = ...; Stream<Person> stream = names.stream().map(Person::new); List<Person> people = stream.collect(Collectors.toList());
变量 作用域
lambda有三个部分
1.一个代码块
2.参数
3.自由变量的值(不是2中lambda自己的参数,也不在lambda代码块中定义的变量)
lambda表达式必须存储自由变量的值 —叫作—->捕获captured
实现细节:可以把一个lambda表达式装换为包含一个方法的对象,这样自由变量的值就会包含在这个对象的中。
//变量作用域 public static void repeatMessage(String text, int delay){ ActionListener listener = event->{ System.out.println(text); Toolkit.getDefaultTollkit().beep(); }; new Timer(delay,listener).start(); } repeatMessage("hello",1000);
闭包:代码块+自由变量
限制:要确保捕获的值是明确的,只能引用值不会改变的变量,因为并发执行时 如i–这种操作是不安全的。
规定:lambda表达式中捕获的变量必须是 最终变量(初始化后就不会再为它赋新值)
会变的量不能捕获
//this 指向 public class Application(){ public void init(){ ActionListener listener = event->{ System.out.println(this.toString());//this 会调用Application 的toString 方法 }; } }
处理lambda表达式,编写方法处理
lambda表达式的重点是延迟执行
例子:重复一个动作n次
repeat(10,()->System.out.println("xxx"));//第二个参数要选择一个函数式接口 比如Runnable接口 public static void repeat(int n, Runnable action){ for(int i=0; i<n; i++) action.run(); }
我们希望告诉这个动作它出现在哪一次迭代中。 为此,需要选择一个合适的函数式接口,其中要包含一个方法,这个方法有一个 int 参数而且返回类型为 void
public interface IntConsumer{ void accept(int value); } public static void repeat(int n, IntConsumer action){ for(int i=0;i<n;i++){ action.accept(i); } } repeat(10,(i)->System.out.println("Countdown:"+(9-i)));
常用的函数式接口
基本类型的函数式接口
如果是自己设计的接口,可以用@FunctionalInterface注解来标记这个接口
Comparator接口包含很多方便的 静态方法 用来创建比较器,这些方法可以用于lambda表达式或方法引用
静态方法就可以直接用. 比如Comparator.comparing()
而非静态方法就用:: 比如Person::getName
2019年9月29日13:09:55
内部类innerClass
为什么使用内部类
1.内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据;
2.内部类可以对一个包中其他类隐藏;
3.想要定义一个回调函数,但不想编写大量代码时,使用匿名内部类比较便捷;
回调函数:参数是函数
使用内部类访问对象的状态
内部类既可以访问自身的数据域也可以访问创建它的外围类对象的数据域(内部类有一个隐式的引用outer,指向创建它的外部类对象)
外围类的引用在构造器中设置,编译器会修改内部类的构造器,添加外围类的引用参数
只有内部类可以是私有的,而常规类只可以具有包可见性或公有可见性
内部类特殊的语法规则
外围类的引用outer
外围类引用的正式语法OuterClass.this表示外围类的引用,在外围类的作用域之外,可以这样引用内部类OuterClass.InnerClass
内部类是一种变异现象域虚拟机无关,虚拟机对此一无所知
由于内部类拥有访问特权,所以与常规类比较起来功能更强大;
局部内部类
可以在一个方法中定义局部类
局部类不能用public或private声明,作用域被限定在这个局部类的块中,对外部世界完全隐藏
局部类:1.不仅能访问外部类;2.也能访问包含它的方法的 局部变量 ;
但这些局部变量事实为final
编译器必须检测对局部变量的访问,为每一个变量建立相应的数据域,并将局部变量拷贝到构造器,以便将这些数据域初始化为局部变量的副本
局部类的方法只可以引用定义为final的局部变量(因为并发会导致竞态条件)
匿名内部类
将局部内部类的使用再深入一步:只创建这个类的一个对象,就不必命名类
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(); } new SuperClass(Construction parameters){ inner class method and data; }
匿名类物构造器,必须将参数传递给超类 superclass构造器
内部类实现接口时不能有任何构造参数(因为使用默认的构造器)
// 构造一个类的对象和构造一个拓展了那个类的匿名内部类对象之间有什么区别 Person queen = new Person("Mary"); Person count = new Person("Dracula"){。。。}
与平常构造新对象多了{}
习惯用匿名内部类实现时间监听器和其他回调。
这样看来lambda表达式和匿名内部类的区别,lambda表达式连new和方法名都省略了
// 匿名列表 ArrayList<String> friends = new ArrayList<>(); friends.add("Harry"); friends.add("Tony"); invite(friends); invite(new ArrayList<String>(){{add("Harry");add("Tony");}});
双括号初始化:上面一行代码,{}外层建立了ArrayList一个匿名子类,内括号则是一个对象构造块。
静态内部类
class ArrayAlg{
public static class Pair{
...
}
}
有时候使用内部类只是把一个类隐藏在另一个类的内部,并不需要内部类引用外围类对象
可以声明为static, 以便取消产生的引用;
大众化的名字会产生命名冲突,将其定义为内部公有类;
Array.Pair p = Array.minmax(d);
与之前的子类不同的是:Pair类不用引用其他任何对象;
为此可以将内部类声明为static
特征:
1.可以解决一些类命名的冲突
2.可取消对外围类对象域的应用(不需要访问外围类的时候)
静态内部类生成的对象除了没有对生成它的外围类对象的应用特权外
内部类对象是在静态方法中构造,必须使用静态内部类*
类内部的静态方法只能引用类内部是static修饰的东西;
静态方法中构造对象,对象所属的类必须是static修饰;
静态内部类可以由静态域和方法
匿名内部类实际上是省去了 类继承超类或实现接口的步骤*