2、接口 、Lamda表达式

接口 、Lamda表达式

  • 接口不是类,而是对类的一组需求描述

  • 接口的所有方法自动属于public,不过在实现接口时,必须将实现方法声明为public,否则,编译器认为试图给出更严格的访问权限的警告,接口可以定义常量,但接口不能有实例变量

  • 类实现一个接口,需要步骤为:

    1. ​ 将类声明为实现给定的接口
    2. ​ 对接口内的所有方法进行定义
  • compare的比较,常规的相减操作时明确比较的值是非负整数或者其值在(Integer.MAX_Value-1)/2的范围内,否则用Integer.compare来替换常规的相减操作,相减技巧也不适用与浮点数,可用Double.compare来替换。

  • 语言标准规定,对于任意X和Y,实现必须能够保证sgn(x.compareTo(y))=-sign(y.compareTo(x)),这里的sign是一个符号,如果n是负值时,sign(n)=-1,如果n是正值时,sign(n)=1,如果n为0时,sign(n)=0

  • 接口的特性:

    1. 接口不是一个类,尤其是不能使用new运算符实例化一个接口
    2. 尽管不能实例化一个接口,但是能声明接口变量,接口变量的引用必须是实现了接口的类对象
    3. 可以使用instanceof检查一个对象是否实现了某个特定接口
    4. 接口可以被扩展
    5. 接口不能包含静态域和实例域,但能有常量,接口的方法自动标记为public,域自动标记为public static final
    6. jdk1.8,允许在接口中增加静态方法,虽然可以,但是不建议,因为有违将接口作为抽象规范的初衷,通常做法是将静态方法放到伴随类中。
  • 可以为接口提供一个默认实现方法(实现类中可重写),必须用default修饰符标记这样的一个方法。

  • 默认方法一个重要用法是接口演变。例如定义了一个接口很久之后,再为其新增一个方法。但是由于这方法是新增的,所以事先改接口的类没有实现该方法,会导致实现类不能通过编译,也即“代码不兼容”的问题,即使用原来实现类的jar文件,但如果调用新增方法,则会报错。

  • 默认方法冲突:即一个接口定义了默认方法,但在超类或另一个接口中也同样定义了一相同的方法:

    1. ​ 超类优先,如果超类提供了一个具体的方法,默认方法会被忽略。类优先的规则确保了与jdk7代码兼容。(注意,不要在接口中的默认方法冲新定义Object的某个方法,因为由于类优先规则,导致没有意义)
    2. ​ 接口冲突,两个接口有完全相同的默认方法(或者有相同方法,但至少有一个接口提供了对方法的实现),java编译器会让程序员自己来选择,即在实现类中冲写该方法,然后在方法内用 接口名.super.方法名
  • 默认的clone是浅拷贝,即拷贝了基本数据类型,但引用对象还是同一个引用。

  • 对于每一个类的拷贝,需要确定一下条件:

    1. 默认的clone方法是否满足需求
    2. 是否可以在可变的子对象上调用clone方法修补默认的clone方法
    3. 是否不该使用clone方法。
    4. 实际上第3个选项是默认选项,如果选择第一项或者第2箱,类必须
    5. 实现Cloneable接口
    6. 重新定义clone方法,并制定public访问修饰符
  • 需要理解的是Object类中的clone为什么定义为protected?既然clone是object类中的方法,为什么还要实现Cloneable接口呢?

  • 上述第一个问题:定义为protected是为了让保证对象用用一定重写了clone,设置为protected,子类只能调用clone来克隆自己的对象,若要克隆其他对象,则必须要其他对象自己调用clone,这样克隆引用对象就必须宝恒该对象重写了clone方法,确保该对象已经深拷贝过了。且必须重新定义clone为public才能允许所有方法克隆该对象(这个理解可能为只有设置为public,对象在其他类中也才能调用克隆,否则,会因为包的局限性问题?)

  • 上述第二个问题:Cloneable接口是java提供的一组标记接口之一,其通常用途是一个类实现一个或一组特定方法。注意标记接口不包含任何方法。除此外的作用允许在类型查询中使用instanceof。

  • 深拷贝是为每个引用对象通过clone克隆,浅拷贝是直接调用super.clone方法。

  • lamda表达式是一个可传递的代码块,可以在以后执行一次或多次。因为java是面向对象的,如果要传递代码块就要构建一个对象来传递。

  • 一个lamda表达式,如果只在某些分支返回一个值,而在另一个分支不返回值,这是不合法的。

(int x) -> {if(x>0) return 1;}这是不合法的
  • 函数式接口(lamda表达式和函数式接口的关系:应该是要有函数式解耦,才能使用lamda表达式):

    1. 对于只有一个抽象方法的接口,需要这种接口对象时,就可以提供lamda表达式,注意是 只有一个抽象方法 而default和静态方法有实现,故不算抽象方法。
    2. lamda表达式不是Object对象的子类,其是一个函数式接口,可以赋给一个函数式接口变量
  • 方法引用是有现成的方法来完成 传递代码 的动作

  • 方法引用的情况:

    1. object::instanceMethod :等价于提供参数的lamda表达式,如System.out::println等价于(x)->{System.out.println(x)}
    2. Class::staticMethod :等价于提供参数的lamda表达式
    3. Class::instanceMethod :这种情况与上两种不同,第一个参数会成为方法的目标,
  • 构造器引用和方法引用类似,只不过方法名为new,即Person::new

  • lamda 表达式可以捕获外围作用域中变量引用不会改变的值,类似final修饰,但不被修饰也行,只要全局中没被改变(外部改变也不行),且lamda表达式中只能读取不能改变引用就行(即lamda表达式中不改变引用,但能改引用对象中的值),因为如果在lamda表达式中改变,并发执行多个动作会不安全?因为lamda表达式是替换匿名内部类的,而匿名内部类 注意解决该问题

  • lamda表达式捕获的变量是最终值,也就是变量初始化后,变量引用不能再更改。

  • 在一个lamda表达式中使用了this关键字,是指创建lamda表达式的方法所对应的的this。

  • 使用lamda表达式的重点是延迟执行,lamda表达式是替换匿名内部类的,而匿名内部类是调用再执行,避免可性能的消耗

    public class LoggerDemo {
        private static void log(int level, MessageBuilder builder) {
            if (level == 1) {
                System.out.println(builder.buildMessage());
            }
        }
        public static void main(String[] args) {
            String msgA = "Hello";
            String msgB = "World";
            String msgC = "Java";
            long start = System.currentTimeMillis();
            //条件不通过的情况,就不会执行lamda表达式
            log(2, () -> {
                System.out.println("Lambda执行!");
                return msgA + msgB + msgC;
            });
            long end = System.currentTimeMillis();
            System.out.println("未延迟执行花费时间:"+(end-start));
        }
    }
    
    /==
     * 下面这个是函数式接口,方便lambda表达式的使用。
     */
    @FunctionalInterface
    interface MessageBuilder {
        String buildMessage();
    }
    
    
  • lamda表达式执行的场景:

    1. 在一个单独的线程中运行代码
    2. 多次运行代码
    3. 在算法适当位置运行代码
    4. 发生某种情况时执行代码
    5. 只在必要时才运行代码
  • 常用的函数是接口,即java提供了函数式接口,可以直接用,无需再重新定义函数式捷库

    1. Runnable :参数为空,返回值为空
    2. Supplier:参数为空,返回值为T(泛型,一般可以任意)
    3. Consumer:参数为T,返回值为空
    4. BiConsumer<T,U> :参数为T,U,返回值为空
    5. Function<T,R>:参数为T,返回值为R
    6. BiFunction<T,U,R>:参数为T,U,返回值为R
    7. UnaryOperator:参数为T,返回值为T
    8. BinaryOperator,参数为T,T,返回值为T
    9. Predicate:参数为T,返回值为boolean
    10. BiPredicate<T,U>:参数为T,U,返回值为boolean
  • 如果有基本类型,为了避免装箱和拆箱的操作,可以使用下列函数式接口(pq为int,long,double.PQ为Integer,Long,Double)

    1. BooleanSupplier 参数为空,返回为boolean
    2. PSupplier 参数为空,返回p(int ,double,long)
    3. PConsumer 参数为p,返回值为void
    4. ObjPConsumer 参数类型为T,p,返回类型为空
    5. PFunction ,参数类型为p,返回类型为T
    6. PToQFunction 参数类型为p,返回类型为q
    7. ToPFunction,参数类型为T,返回类型为p
    8. ToBiFunction<T,U> 参数类型为T,U,返回类型为p
    9. PUnaryOperator 参数类型为p,返回类型为p
    10. PBinaryOperator 参数类型为p,p,返回类型为p
    11. PPredicate 参数类型为p,返回类型为boolean
  • 如果自己设计函数式接口时,可以使用@FunctionalInterface来标注明这是一个函数式接口

  • 使用内部类的原因为:

    1. 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
    2. 内部类可以对同一个包中的其他类隐藏起来
    3. 当想要定义一个回调函数且不想编写大量代码时,可以使用匿名内部类
  • 内部类既可以访问自身的数据域,也可以访问创建他的外围类对象 的数据域

  • 内部类对象总有一个隐式引用,他指向创建他的外围类对象,编译器修改了所有的内部类构造器,添加了一个外围引用参数

  • 只有内部类可以是私有类(但虚拟机中不存在私有类),而常规类只可以具有包可见性或公有可见性

  • 内部类可以用OuterClass.this表示外围类引用,如TalkingClock.this.name,也可以使用outerClass.new InnerClass()表示内部类的构造器引用。外围类作用域外,可以OuterClass.InnerClass来引用内部类

  • 内部类中声明的所有静态域都必须是final,首先用static是表示所有对象共有的,但是对于每个外部对象都会分别有一个自己的单独内部类实例空间,如果不加final修饰,会导致不唯一,

  • java规范表明内部类不能有static 方法,但实际上也可以有static方法,不过只可以访问外围静态域和静态方法,为什么不呢能有呢,因为静态方法加载会在类加载时加载,但是内部类如果不是静态的,加载外部类就不会加载,其类似于外部类的一个实例变量,也就不能只能实例化内部类才会有,这就很矛盾。

  • 内部类是一种编译器现象,与虚拟机无关,编译器会将内部类翻译成用$来分格外部类类名和内部类类名的常规类文件

  • 局部内部类不能使用public或private访问复进行声明,作用域被限制在声明这个局部类的代码块中

  • 局部类可以对外部世界完全隐藏起来,即外部世界不可感知到这类,即使在代码块的类中。

  • 局部类不仅可以访问包含他的外部类,也可以访问局部变量,不过局部变量必须事实上为final,即引用不会更改,外部类实例可以不为final,

  • 局部内部类由于声明周期的不一致性,所以可能会对外部类域通过局部变量进行备份。(这个理解稍弱,以后自己查下资料)

  • 编译器必须检测对局部的访问,为每一个变量建立相应的数据域,并将局部变量拷贝到构造器中,以便将这些数据域初始化为局部变量的副本。和前面提到的局部变量事实上为final照应,只有为final,初始化会引用不改变,这样局部变量与在局部类中建立的拷贝保持一致

  • 匿名内部类不能有构造器,取而代之的是将构造器参数传给超类构造器(不太懂,以后自己去查下资料)

  • 匿名内部类可以使用双括号初始化。如;

    new ArrayList(){{add("hary");add("liko")}};
    
  • 匿名子类对于冲写equals方法的if(getClass() !=otherClass.getCLass()) return false; 会失效

  • 如果要使用getCLass,可以用new Object(){}.getClass().getEnclosingClass()获得其外围类

  • 静态内部类没有生成对他的外围类对象引用,在不需要方位外围类对象时,应该将内部类声明为静态内部类,静态内部类可以有静态域和静态方法

  • 声明在街口的内部类自动转换成为static 和public 修饰的类

  • 代理(理解不清晰,要反复查资料)

  • 动态代理是在编译时无法确定类型,代理类可以在运行时创建全新的类,这样代理类能够实现指定接口,一把需要提供一个调用该处理器,一个代理类只有一个实例域,即调用处理器,代理类一定是static 和final

  • 创建代理对象,需要使用Proxy类的newProxyInstance方法,参数为:一个类加载器 、2,一个Class对象数组,每个元素都是要实现的接口、 3, 一个调用处理器

  • 所有的代理类都覆盖(重写)了object的toString、equals和hashcode,而其他方法美哦与被重写

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值