精华内容
下载资源
问答
  • 参考了这篇文章,本文针对文章进行一些补充说明。 环境: TeXLive2017 TeXstudio2.12.10 提示1:每次改完.tex文件中的错误之后,都要删掉.aux和.bbl两个文件,然后再重新编译。 提示2:修改.bbl文件之后,...

    参考了这篇文章,本文针对文章进行一些补充说明。

    环境:

    TeXLive2017

    TeXstudio2.12.10

    提示1:每次改完.tex文件中的错误之后,都要删掉.aux和.bbl两个文件,然后再重新编译。

    提示2:修改.bbl文件之后,保存并编译即可,不要再删掉了,不然就白改了。

    • 在TeXstudio中,编译文件时不需要像上述文章一样,分别编译Latex文档和Bib文档,只需要按照正常步骤,一键编译,正常编译输出如下,里面的warnings可以不用管。

    开始 : bibtex.exe "document"

     

    This is BibTeX, Version 0.99d (TeX Live 2017/W32TeX)

    The top-level auxiliary file: document.aux

    The style file: unsrt.bst

    Database file #1: ref.bib

    Warning--empty booktitle in Li2018

    Warning--empty journal in Tejwani2017Autism

    Warning--there's a number but no volume in G2018A

    Warning--empty journal in Selvaraju2016Grad

    (There were 4 warnings)

     

    完成

    • .bib文件放在.tex文件的同级目录即可,如上述文章提到的,在正文中添加如下两句话,其中“ref”是指.bib文件名为ref.bib,如果文件为其他名称,则对应修改。
    % 参考文献呈现方式
    \bibliographystyle{unsrt}
    
    % .bib文件名
    \bibliography{ref}
    • 要先添加.bib,再在文章中使用如下语句添加引用。这里容易出现一个问题,就是多篇引用的方法,如下所示,用逗号分隔多篇文献的第一行内容,要注意,不能有空格,否则会报错如下。
    % 单篇文章引用
    \cite{Wille1982}
    
    % 多篇文章引用
    \cite{Turner2016Pallidum,Martino2014The,Zielinski2014Longitudinal}
    
    % 错误的 多篇文章引用
    \cite{Turner2016Pallidum , Martino2014The,Zielinski2014Longitudinal}

    开始 : bibtex.exe "document"

     

    This is BibTeX, Version 0.99d (TeX Live 2017/W32TeX)

    The top-level auxiliary file: document.aux

    White space in argument---line 6 of file document.aux

    : \citation{Turner2016Pallidum

    : ,Martino2014The,Zielinski2014Longitudinal}

    I'm skipping whatever remains of this command

    The style file: unsrt.bst

    Database file #1: ref.bib

    Warning--empty booktitle in Li2018

    Warning--empty journal in Tejwani2017Autism

    Warning--there's a number but no volume in G2018A

    Warning--empty journal in Selvaraju2016Grad

    (There was 1 error message)

     

    出现错误

    • .bib文件当中,author中逗号和and不能直接相连,这仿佛是Bibtex的格式要求,某些网站导出的参考文献格式可能不正确,如下所示,报错信息如下,这时候只需要在逗号和and之间添加一个点将二者分开即可。
    @article{Martino2014The,
      title={The autism brain imaging data exchange: towards a large-scale evaluation of the intrinsic brain architecture in autism},
      author={Martino, A, Di and C-G, Yan and Li, Q. , and Denio, E. ,. and Castellanos, F X,. and Alaerts, K. ,. and Anderson, J S,. and Assaf, M. ,. and Bookheimer, S Y,. and Dapretto, M. ,.},
      journal={Molecular Psychiatry},
      volume={19},
      number={6},
      pages={659-667},
      year={2014},
    }

    开始 : bibtex.exe "document"

     

    This is BibTeX, Version 0.99d (TeX Live 2017/W32TeX)

    The top-level auxiliary file: document.aux

    The style file: unsrt.bst

    Database file #1: ref.bib

    Name 3 in "Martino, A, Di and C-G, Yan and Li, Q. , and Denio, E. ,. and Castellanos, F X,. and Alaerts, K. ,. and Anderson, J S,. and Assaf, M. ,. and Bookheimer, S Y,. and Dapretto, M. ,." has a comma at the end for entry Martino2014The

    while executing---line 944 of file unsrt.bst

    Warning--empty booktitle in Li2018

    Warning--empty journal in Tejwani2017Autism

    Warning--there's a number but no volume in G2018A

    Warning--empty journal in Selvaraju2016Grad

    (There was 1 error message)

     

    出现错误

    • 下面是一个例子,一个文献的journal部分包含了“&”符号,会出现下示的错误信息(这里我放了3条错误信息,因为我编辑的文档的参考文献中有三个这种情况出现),只需要在.bbl文件中将“&”符号添加“\”符号,类似于转义字符,变为“\&”,然后重新编译即可。
    @article{G2018A,
      title={A novel machine learning model to predict autism spectrum disorders risk gene},
      author={Gök, Murat},
      journal={Neural Computing & Applications},
      number={4},
      pages={1-7},
      year={2018},
    }

    Misplaced alignment tab character &. \newblock {\em Neural Computing &

    Misplaced alignment tab character &. &

    Misplaced alignment tab character &. ...m IEEE Transactions on Pattern Analysis &

    展开全文
  • 基于Java8详细介绍了Lambda表达式的语法与使用,以及方法引用、函数式接口、Lambda复合等Java8的新特性!

    基于Java8详细介绍了lambda表达式的语法与使用,以及方法引用、函数式接口、lambda复合等Java8的新特性!

    1 Lambda的概述

    面向对象的语言强调“必须通过对象的形式来做事情”,做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。

    无论什么情况,当我们在一个方法中需要调用另一个方法的时候,传递的参数必须是一个含有该方法的对象,对象作为一等公民!对于某些可以独立的单个方法(行为),比如比较的方法,在面向对象的程序设计中,同样必须使用一个对象来进行封装,比如Comparable、Comparator,虽然Java中已经使用接口这种更加抽象的类型来封装“比较”这种方法(行为),但是在一个方法中调用比较的方法的时候,我们仍然需要传递一个接口的实现类的对象,然后再方法中调用这个对象的方法,就会很麻烦!因为实际上我们只需要进行比较的这个方法(行为),它却必须要传递一个对象进来!

    /**
     * 比较对象的方法
     *
     * @param comparator 比较器
     * @param i          i
     * @param j          j
     */
    public static int cmp(Comparator comparator, int i, int j) {
        return comparator.compare(i, j);
    }
    
    
    @Test
    public void testJava() {
        //传递一个对象
        int cmp = cmp(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        }, 1, 2);
        System.out.println(cmp);
    }
    

    如上图,我们有一个比较对象的方法cmp,在使用传统Java代码编程时,即使最简单的方式,也需要传递一个匿名内部类对象进去,但是我们实际上需要的只是比较的行为而已,并不需要对象!

    面向对象的编程思想自有它的好处,比如封装性,可重用性,多态性。但是程序设计的世界里,想要依靠一种方法打遍天下并且还是最优的解,那几乎是不可能的!Java 8开始,支持lambda表达式,就是为了解决面向对象编程思想在某些时候(比如单个方法的调用)显得很笨重而又啰嗦!

    lambda表达式是一种函数式的编程思想,尽量忽略面向对象的复杂语法。函数式的编程思想中,函数作为一等公民,这里的函数可以类比Java中的方法,描述的是一种行为!当一个方法(函数)中调用另一个方法时,直接将该方法(行为)作为参数传递即可。这样相比于面向对象的编程思想来说,可以让编程更加简单!
    对于上面的代码,我们使用lambda表达式改造之后,如下所示:

    @Test
    public void testLambda() {
        int cmp = cmp((Comparator<Integer>) (o1, o2) -> o1 - o2, 1, 2);
        System.out.println(cmp);
    }
    

    可以看到,lambda表达式的应用让代码编程非常简单明了,我们直接将比较的行为作为参数传递给了cmp方法,连匿名对象都没有了!

    2 函数式接口

    简单的说,函数式接口(Functional Interface)就是只定义一个抽象方法的接口,并且只有在函数式接口中才能使用lambda表达式。为此,Java 8的时候新增加了一个@FunctionalInterface注解,用来表明某个接口是函数式接口。注意一个函数式接口可以选择加上该注解也可以不加上该注解,这个注解简单的说可以作为一种检验!

    lambda表达式实际上就是对函数式接口的唯一抽象方法起作用的,即相当于可以把抽象方法的实现作为函数式接口的具体实现的实例来当作参数传递!这类似于匿名内部类!

    Java8之前就有许多函数式接口,比如Runnable、Callable、Comparator、Comparable……,在Java8的时候,为了更好的支持lambda,新增了一个java.util.function包,这个包下面的有很多的接口,这些接口全部都是函数式接口,它们都用于描述某个行为,方便lambda的使用!

    下面我们将介绍常用的四种接口:Consumer消费型接口、Supplier供给型接口、Function函数型接口、Predicate断言型接口,最后会附上大部分函数式接口的不同行为和功能!可能某些案例的lambda表达式看不太懂,不过没关系,下一节将会讲解lambda的语法!

    2.1 Consumer消费型接口

    @FunctionalInterface
    public interface Consumer<T> {
    
        /**
         * @param t 输入参数
         */
        void accept(T t);
    
        //……
    }
    

    Consumer接口中有一个accept抽象方法,它用于接收一个泛型参数T,然而并没有返回值,顾名思义,就是对传递的参数进行“消费”,没有输出,就像消费者一样!

    我们可以将其应用在对某些输入数据的处理但是不需要输出的情况中!下面的案例中,我们需要对集合中的所有int元素进行+1然后输出的操作:

    /**
     * @author lx
     */
    public class ConsumerTest {
    
        public static void main(String[] args) {
            //一个初始化集合
            List<Integer> objects = new ArrayList<>();
            objects.add(1);
            objects.add(2);
            objects.add(3);
    
            //对集合数据进行  加1然后输出的操作
            consume(objects, i -> System.out.println(i + 1));
        }
    
        /**
         * 使用Consumer对集合元素进行操作的方法
         *
         * @param list     需要操作的集合
         * @param consumer 对元素的具体的操作,在调用的时候传递某个动作就行了
         */
        private static <T> void consume(List<T> list, Consumer<T> consumer) {
            for (T t : list) {
                consumer.accept(t);
            }
        }
    }
    

    2.2 Supplier供给型接口

    @FunctionalInterface
    public interface Supplier<T> {
    
        /**
         * @return 获取一个返回结果
         */
        T get();
    }
    

    Supplier接口中有一个get抽象方法,它不接收任何参数,但是返回一个T类型的结果,顾名思义,就是没有输入,只有输出,就像生产者一样!

    我们可以将其应用在创建某些对象、获取数据数据的情况中。下面的案例中,我们需要用集合收集10个随机数:

    /**
     * @author lx
     */
    public class SupplierTest {
    
        public static void main(String[] args) {
            //一个初始化集合
            List<Integer> objects = new ArrayList<>();
            //我们需要填充10个随机数,Supplier是一个获取随机数的动作
            Random random = new Random();
            supplier(objects, 10, () -> random.nextInt(10));
            //输出集合数据
            System.out.println(objects);
        }
    
        /**
         * 填充集合数据的方法
         *
         * @param list     需要填充的集合
         * @param count    需要填充的数量
         * @param supplier 获取数据的的操作,在调用的时候传递某个动作就行了
         */
        private static <T> void supplier(List<T> list, int count, Supplier<T> supplier) {
            for (int i = 0; i < count; i++) {
                list.add(supplier.get());
            }
        }
    }
    

    2.3 Function< T, R >函数型接口

    @FunctionalInterface
    public interface Function<T, R> {
    
        /**
         * 将指定函数应用于给定的参数。
         *
         * @param t 函数参数
         * @return 函数结果
         */
        R apply(T t);
    }
    

    Function接口中有一个apply抽象方法,它接收T类型的参数,返回一个R类型的结果,顾名思义,就是一个参数T到R的映射操作,就像一个函数一样!

    我们可以将其应用在对某个输入对象进行变换、操作然后输出另一个对象(也可以是自己)的情况中。下面的案例中,我们需要对集合中的所有int元素进行自增1的操作:

    /**
     * @author lx
     */
    public class FunctionTest {
    
        public static void main(String[] args) {
            //一个初始化集合
            List<Integer> objects = new ArrayList<>();
            objects.add(1);
            objects.add(2);
            objects.add(3);
    
            //对集合中的数据进行 自增1的操作
            function(objects, i -> ++i);
            //输出集合数据
            System.out.println(objects);
        }
    
    
        /**
         * 使用Function对集合元素进行操作的方法
         *
         * @param list     需要操作的集合
         * @param function 对元素的具体的函数操作,在调用的时候传递某个动作就行了
         */
        private static <T> void function(List<T> list, Function<T, T> function) {
            for (int i = 0; i < list.size(); i++) {
                //将通过传入的函数操作获取的结果替换原来的集合对应的数据
                list.set(i, function.apply(list.get(i)));
            }
        }
    }
    

    2.4 Predicate断言型接口

    @FunctionalInterface
    public interface Predicate<T> {
    
        /**
         * 对给定的参数进行断言的方法
         *
         * @param t 输入参数
         * @return 如果参数符合规则,那么返回true,否则返回false
         */
        boolean test(T t);
    }
    

    2.5 其他接口以及功能

    java.util.function包中的大多数其他函数式接口都是一个特性化的接口,即它们的功能和上面的四大接口都差不多,区别可能是参数数量和类型以及返回值类型!

    函数描述符:用来描述函数的参数以及返回值的类型,()表示无参,void表示无返回值,中间使用->连接。

    函数式接口函数描述符特性化接口
    Predicate< T >T->booleanIntPredicate,LongPredicate, DoublePredicate
    Consumer< T >T->voidIntConsumer,LongConsumer, DoubleConsumer
    Function< T,R >T->RIntFunction< R >,IntToDoubleFunction,IntToLongFunction,LongFunction< R >,LongToDoubleFunction,LongToIntFunction,DoubleFunction< R >,ToIntFunction< T >,ToDoubleFunction< T >,ToLongFunction< T >
    Supplier< T >()->TBooleanSupplier,IntSupplier, LongSupplier
    UnaryOperator< T >T->TIntUnaryOperator,LongUnaryOperator,DoubleUnaryOperator
    BinaryOperator< T >(T,T)->TIntBinaryOperator,LongBinaryOperator,DoubleBinaryOperator
    BiPredicate< L,R >(L,R)->boolean
    BiConsumer< T,U >(T,U)->voidObjIntConsumer< T >,ObjLongConsumer< T >,ObjDoubleConsumer< T >
    BiFunction< T,U,R >(T,U)->RToIntBiFunction< T,U >,ToLongBiFunction< T,U >,ToDoubleBiFunction< T,U >

    前四个都介绍了,后面的其实都差不多,只是参数数量和类型以及返回值类型有差异:

    1. UnaryOperator:一元操作器,一个参数一个返回值,类似于Function,不过参数和返回值类型一致。
    2. BinaryOperator:二元操作器,两个参数一个返回值,类型一致。
    3. BiPredicate:二元断言,传递两个可以不同类型的参数,返回一个boolean类型。
    4. BiConsumer:二元消费,传递两个可以不同类型的参数,无返回值。
    5. BiFunction:二元函数,传递两个可以不同类型的参数,一个返回值可以是不同类型。

    下面我们正式学习lambda的语法!

    3 Lambda的语法

    3.1 具体格式

    lambda表达式的标准格式为

    (参数类型 参数名称, 参数类型 参数名称) ‐> { 代码语句 }

    小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。-> 是新引入的语法格式,代表指向动作。大括号内的语法与传统方法体要求基本一致。

    比如对Comparator接口使用匿名内部类对象和lambda表达式:

    //使用传统匿名内部类
    Comparator<Integer> comparable1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 - o2;
        }
    };
    
    //使用lambda表达式标准语法
    Comparator<Integer> comparable2 = (Integer o1, Integer o2) -> {
        return o1 - o2;
    };
    

    当然,如果使用idea,那么可能会提示你这个lambda表达式还有更精简的写法。

    //使用lambda表达式优化语法
    Comparator<Integer> comparable3 = (o1, o2) -> o1 - o2;
    

    可以看到,此时我们的lambda表达式更加精简了,同时也更加通俗易懂,那就是通过比较两个数的差值来比较大小!

    在lambda标准格式的基础上,使用省略写法的规则为:

    1. 小括号内参数的类型可以省略;
    2. 如果小括号内有且仅有一个参,则小括号可以省略;
    3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号和return关键字及语句的分号,即无论有没有返回值,都可以省略return。

    由此,我们可以知道,同一个lambda表达式可以对应不同的实际目标类型,比如下面的例子,同样的lambda表达式,却可以赋值给不同的类型!

    //对应Comparator目标类型
    Comparator<Integer> comparable3 = (o1, o2) -> o1 - o2;
    
    //对应BinaryOperator目标类型
    BinaryOperator<Integer> binaryOperator = (o1, o2) -> o1 - o2;
    

    实际上,lambda表达式也有自己的类型,但是它的类型是通过上下文(参数类型、返回值类型、包括泛型类型)推断得来的。在上面的Comparator的精简写法中,参数类型被省略了,因为可以通过返回值的泛型类型Integer推断出来,参数的类型也一定是Integer类型,另外通过返回的类型可以推断出这个lambda一定是Comparator类型。

    在下面的lambda表达式中,参数类型同样被省略了,因为可以通过cmp方法的第一个参数可以推断出,参数类型一定是Byte类型,并且根据第一个参数的目标类型可以推断出这个lambda表达式一定是Comparator类型!

    @Test
    public void test1() {
        cmp((o1, o2) -> (o1 - o2), (byte) 1, (byte) 1);
    }
    
    public int cmp(Comparator<Byte> comparator, byte l1, byte l2) {
        return comparator.compare(l1, l2);
    }
    

    这里的上下文推断就类似于JDK1.7出现的针对集合的类型推断<>符号:

    //JDK1.7开始,右侧构造器可以使用<>当作泛型推断
    List<String> strings = new ArrayList<>();
    

    只不过Java8的时候对类型推断做了进一步增强,使用上下文推断可以在使用Lambda表达式时用来推断合法的Lambda表达式的类型的上下文,而不必在代码中强制转型或者注明类型!可推导即可省略!

    3.2 使用要求

    Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

    1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
    2. 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
    3. 在 Lambda 表达式中不允许声明一个与局部变量同名的参数或者局部变量。
    4. 在 Lambda 表达式中,允许引用最终变量、静态变量、局部变量,但是只允许修改静态变量,以及对象类型的局部变量的属性(这要求后面的代码不会修改这个局部变量的引用指向),对于局部变量本身的引用指向以及基本类型的变量则不允许修改
    5. 对应第四条的另一种解释,lambda表达式的局部变量可以不用声明为final,但是实际上是具有隐式的final的的语义,即必须不可被后面的代码修改,否则会编译错误。

    为什么会对局部变量有这些限制呢?主要是因为对象类型局部变量的引用以及基本类型的局部变量都保存栈上,存在某一个线程之中,如果Lambda可以直接访问并修改栈上的变量,并且Lambda是在另一个线程中使用的,那么使用Lambda的线程可能会在分配该变量的线程将这个变量收回之后,继续去访问该变量。因此,Java在访问栈上的局部变量时,实际上是在访问它的副本,而不是访问原始变量,从而造成线程不安全的可能,特别是并行运算的时候。但是如果局部变量仅仅被最开始赋值一次,以后不会再次变动,那就没有这种隐患了——因此就有了这个限制,即局部变量除了最开始的赋值之后都是读操作,而没有写操作,那么可以读取这个局部变量,相当于final的语义了。

    由于对局部变量的限制,Lambda表达式在 Java 中又称为闭包或匿名函数。它们可以作为参数传递给方法,并且可以访问其作用域之外的变量。但是它们不能修改定义Lambda的方法的局部变量的内容,这些变量必须是隐式最终的。因此可以认为Lambda是对值封闭,而不是对变量封闭,因为可以访问局部变量,但不可修改值。为什么对象类型的额局部变量的属性可以修改呢?因为它们保存在堆中,而堆是在线程之间共享的!因此我们如果需要在lambda中修改某个基本变量,那么可以使用该变量的包装类。然后再修改属性值即可。

    关于变量的测试案例如下:

    //静态全局变量
    static int k = 1;
    static AtomicInteger stinteger = new AtomicInteger(1);
    
    @Test
    public void test3() {
        int i = 1;
        Object o = new Object();
        AtomicInteger integer = new AtomicInteger(1);
        //使用lambda表达式标准语法
        Comparator<Integer> comparable = (Integer o1, Integer o2) -> {
            //在后面的语句中不会修改这个局部变量的值时,可以在lambda中访问基本局部变量,但是不可操作值
            int b = i;
            System.out.println(i);
    
            //在后面的语句中不会修改这个对象局部变量的引用指向时,可以操作或者访问这个对象的属性
            integer.addAndGet(1);
            integer.get();
            integer.set(10);
    
            //静态变量的引用指向可以修改
            stinteger = new AtomicInteger(2);
            //静态变量的值可以修改
            k = 2;
            return o1 - o2;
        };
    
        //在后面的语句中改变基本局部变量的值之后,lambda中对该变量的任何访问操作都将编译不通过
        // i = 2;
    
        //在后面的语句中改变对象局部变量的引用指向之后,lambda中对该变量的任何访问操作都将编译不通过
        //integer= new AtomicInteger(1);
    
        //在后面的语句中可以操作或者访问这个对象的属性
        integer.set(15);
    
        //静态变量的引用指向可以修改
        stinteger = new AtomicInteger(3);
        //静态变量的值可以修改
        k = 3;
    }
    
    static class Run implements Runnable {
    
        @Override
        public void run() {
    
        }
    }
    

    4 方法引用

    到此之前,我们已经会使用Lambda表达式创建匿名方法,自己实现方法体,但是有时候,我们的Lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java 8的方法引用允许我们这样做。方法引用简单的格式通过引用一个已经存在的方法,同时实现了代码的复用,进一步简化了lambda的复杂度。

    方法引用的目标很明显,因为方法可以看作一个已经存在的定义好的函数,当我们要传递的函数已经被某个方法实现了的时候,那么则可以通过双冒号"::"操作符来引用该方法作为 Lambda 的替代者。

    /**
     1. @author lx
     */
    public class User {
        private int age;
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
    
        public static void main(String[] args) {
            //获取User的age
            //lambda标准格式的优化写法,内部只是引用了一个方法
            Function<User, Integer> userIntegerFunction1 = o -> o.getAge();
    
            //获取User的age
            //使用方法引用之后的写法,更加精简
            Function<User, Integer> userIntegerFunction2 = User::getAge;
        }
    }
    

    方法引用同样可以使用传递的参数类型和参数个数进行推导。比如上面的lambda表达式可知道参数o的类型为User,同时它调用了getAge方法,返回一个Inteer。因此可以直接使用方法引用User::getAge,简化了方法的调用与参数的传递。这些都是可以推倒的,lambda遵循” 可推导即可省略”原则,或者说方法引用可以看作针对lambda的语法糖!

    怎么才能将lambda表达式转换为方法引用呢?或者说什么情况才能使用方法引用呢?

    1. 要求Lambda 表达式的方法体中只有一句话,并且这句话就是调用另一个方法,此时就可能使用方法引用代替手动调用该方法。
    2. 特殊情况下,如果抽象方法的第一个参数就是内部调用该方法的实例,那么被调用的方法与函数式接口中的抽象方法的参数个数可以不相同,但是要求后面的参数和方法参数的顺序一致,类型相同(或者兼容)。如果不是这种特殊情况,那么还要求被调用的方法与函数式接口中的抽象方法的参数个数和顺序一致,类型相同(或者兼容)。
    3. 被调用的方法与函数式接口中的抽象方法返回值类型相同(或者兼容),与方法名无关。

    方法引用有很多种,它们的语法如下,都需要遵循上面的原则:

    1. 类型上的静态方法引用:ClassName::methodName
    2. 实例上的实例方法引用:instanceReference::methodName
    3. 类型上的实例方法引用:ClassName::methodName
    4. 超类实例上的实例方法引用:super::methodName
    5. 构造方法引用:ClassName::new
    6. 数组构造方法引用:TypeClassName [ ]::new

    方法引用案例如下:

    /**
     * @author lx
     */
    public class Person {
        private int age;
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
    
        public static void print() {
            System.out.println("静态方法");
        }
    
        public static Person instance() {
            return new Person();
        }
    
    
        public Integer gett(Object str, Integer integer) {
            setAge(integer);
            return getAge();
        }
    
    
        public Integer gett(int integer, Object str) {
            setAge(integer);
            return getAge();
        }
    
        @Test
        public void test() {
            //一个user实例
            Person user = new Person();
    
            //类型上的实例方法引用:ClassName::methodName
            Function<Person, Integer> userIntegerFunction2 = Person::getAge;
    
            //类型上的实例方法引用:ClassName::methodName
            Consumer<Person> userConsumer2 = System.out::println;
    
    
    
            //实例上的实例方法引用:instanceReference::methodName
            Supplier<Integer> supplier = user::getAge;
    
    
    
            //类型上的静态方法引用:ClassName::methodName
            Supplier<Person> userSupplier = Person::instance;
    
            //类型上的静态方法引用:ClassName::methodName
            Print print = Person::print;
    
    
    
            //超类实例上的实例方法引用:super::methodName
            Supplier<Class> SupSupplier = super::getClass;
    
    
    
    
            //构造方法引用:ClassName::new
            Supplier<Object> newSupplier = Person::new;
    
    
    
    
            //数组构造方法引用:TypeClassName[]::new
            Function<Integer, Person[]> arrayFunction = Person[]::new;
    
    
    
            
            //如果内部调用gett(String str, Object integer)方法
            ThFunction<Person, Integer, Integer, String> thFunction1 = new ThFunction<Person, Integer, Integer, String>() {
                @Override
                public Integer apply(Person o, Integer o2, String o3) {
                    return o.gett(o3, o2);
                }
            };
            //那么不能使用方法引用
            //因为虽然抽象方法的第一个参数就是内部调用该方法的实例,后面的参数和方法参数的顺序不一致
            //参数顺序不一致,即  o2 o3  ->  o3 o2
            ThFunction<Person, Integer, Integer, String> thFunction2 = (o, o2, o3) -> o.gett(o3, o2);
    
    
            //如果内部调用gett(Integer integer, Object str)方法
            ThFunction<Person, Integer, Integer, String> thFunction3 = new ThFunction<Person, Integer, Integer, String>() {
                @Override
                public Integer apply(Person o, Integer o2, String o3) {
                    return o.gett(o2, o3);
                }
            };
            //那么能使用方法引用
            //因为抽象方法的第一个参数就是内部调用该方法的实例,后面的参数和方法参数的顺序一致,类型兼容
            //参数顺序一致,即  o2 o3  ->   o2 o3
            ThFunction<Person, Integer, Integer, String> thFunction4 = Person::gett;
        }
    
    
        /**
         * 自定义函数式接口,无参数无返回值
         */
        @FunctionalInterface
        public interface Print {
            /**
             * 输出
             */
            void print();
        }
    
        @FunctionalInterface
        public interface ThFunction<T, U, R, K> {
            R apply(T t, U u, K k);
        }
    }
    

    5 默认方法和静态方法

    5.1 概述

    此前,Java中的接口不能有非抽象方法,并且实现接口的类必须为接口中定义的每个方法提供一个实现,一旦类库的设计者需要更新接口,向其中加入新的方法,这种方式就会出现问题,这会导致所有的实现类必须实现新的方法,虽然我们可以提供一个骨干实现的抽象类,但是仍然不能根本的解决问题,比如其他Guava和Apache Commons提供的集合框架,会同时修改大量代码!

    Java8开始,接口中新增的方法可以不需要实现类必须实现,因为接口支持两种新类型的方法及其实现,一种是静态方法,通过static关键字标识,表示这个方法可以通过接口直接调用,这个方法时是属于该接口的;另一个就是非常重要的默认方法,通过default关键字标识,并且接口提供了方法的默认实现,实现接口的类如果不显式地提供该方法的具体实现,就会自动继承默认的实现。这就类似于继承了一个普通方法,这样每次接口新增的方法可以设置为默认方法,它的实现类也不再需要改动代码,保证新方法和源代码的兼容!

    实际上静态方法和默认方法被大量的用来支持lambda表达式的复杂写法与复合逻辑,后面我们会介绍到!

    5.2 问题及解决

    在Java8之前,一个类可以实现多个接口,即使有同名方法也没关系,因此抽象方法没有具体的行为,子类必须有针对抽象方法自己的实现。Java8之后,由于接口拥有了默认方法,也就是说接口提供了方法的默认行为,子类可以不选择实现而直接使用接口提供的实现。

    实际上Java8接口允许了默认方法之后,Java已经在某种程度上实现了多继承,所以不光带来了多重继承的好处,还带来了多重继承的问题。如果一个类实现的多个接口中都具有相同方法签名的默认方法,那么这个实现类将无法通过方法签名选择具体调用哪一个接口的默认实现,此时就可能会出现问题!

    如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了非抽象方法,通过下面规则尝试判断具体调用哪一个方法:

    1. 本类重写的方法优先级最高。
    2. 否则,一个类同时实现了类或者接口,并且类和接口具有相同的签名的方法,那么父类中声明的方法的优先级高于任何接口中声明的默认方法的优先级。
    3. 否则,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B接口继承了A接口,那么B接口就比A更加具体。
    4. 否则,继承了多个接口的类必须显式指定的调用某个父接口的默认方法实现。

    其他注意:

    1. 如果一个类实现了抽象类和接口,并且接口中具有和抽象类中的抽象方法同样方法签名的默认方法,此时子类任然需要实现这个抽象方法,而不能使用接口的默认方法作为实现!
    2. 如果一个类实现的接口之间存在继承关系,那么该类可以手动选择调用最低级别接口的的默认实现,但是手动选择调用其他级别接口的的默认实现。
    3. Java强大的编译机制帮助我们解决了菱形继承问题,我们自己不需要解决。什么是菱形继承问题:即有个接口A,有个默认方法a(),此时子接口B、C继承了A接口,随后实现类D同时实现了B、C接口,此时在D中调用a()方法不会有问题,但是c++就有问题!
    4. 如果接口B或者C复写了方法a(),那么在D中调用的a()方法,就是B或者C的a()方法。
    5. 如果接口B和C都复写了默认方法a(),那么就会出现冲突。
    6. 如果接口B或者C复写了默认方法a(),但是变成了抽象方法,那么那么在D中必须实现该方法!

    案例:

    /**
     * @author lx
     */
    public class InterfaceTest {
    
        /**
         * 测试1  本类重写的方法优先级最高。
         */
        static class InterfaceTest1 extends InterfaceClass4 implements Interface3, Interface {
            public static void main(String[] args) {
                InterfaceTest1 interfaceTest = new InterfaceTest1();
                System.out.println(interfaceTest.handle());
            }
    
            /**
             * 自己重写的方法优先级最高
             */
            @Override
            public int handle() {
                return 5;
            }
        }
    
        /**
         * 测试2 父类中声明的方法的优先级高于任何接口中声明的默认方法的优先级。
         */
        static class InterfaceTest2 extends InterfaceClass4 implements Interface3, Interface {
            public static void main(String[] args) {
                InterfaceTest2 interfaceTest = new InterfaceTest2();
                //继承了InterfaceClass4父类,因此父类中相同方法签名的方法优先级最高
                System.out.println(interfaceTest.handle());
    
            }
    
        }
    
    
        /**
         * 测试3 最具体实现的默认方法的接口优先级最高,即最底层的子接口
         */
        static class InterfaceTest3 implements Interface3, Interface, Interface0 {
            public static void main(String[] args) {
                InterfaceTest3 interfaceTest = new InterfaceTest3();
                //Interface3接口继承了Interface借口路,因此Interface3的默认方法优先级最高
                System.out.println(interfaceTest.handle());
            }
    
        }
    
    
        /**
         * 测试4 上面的方式无法判断,并且编译不通过,只能手动指定
         */
        static class InterfaceTest4 implements Interface1, Interface2 {
            public static void main(String[] args) {
                InterfaceTest4 interfaceTest = new InterfaceTest4();
                //Interface3接口继承了Interface借口路,因此Interface3的默认方法优先级最高
                System.out.println(interfaceTest.handle());
            }
    
            /**
             * 通过 Interface1.super.handle();指定调用某个接口的默认方法
             */
            @Override
            public int handle() {
                return Interface1.super.handle();
            }
        }
    
        /**
         * 菱形继承问题,编译通过,运行正常
         * 这实际上就是c++的菱形继承问题,但是Java中帮助我们解决了,我们自己不需要解决
         * 它会自动递归向上查找,找到Interface4和Interface5的共同父接口Interface,然后调用里面的方法,而c++则会抛出异常
         */
        static class InterfaceTest5 implements Interface4, Interface5 {
            public static void main(String[] args) {
                InterfaceTest5 interfaceTest = new InterfaceTest5();
                //Interface3接口继承了Interface借口路,因此Interface3的默认方法优先级最高
                System.out.println(interfaceTest.handle());
            }
        }
    
    
        /**
         * 注意1  如果一个类实现了抽象类和接口,并且接口中具有和抽象类中的抽象方法同样方法签名的默认方法
         * 此时子类仍然需要实现这个抽象方法,而不能使用接口的默认方法作为实现,否则编译不通过!
         */
        static class InterfaceTestt1 extends InterfaceClass5 implements Interface3, Interface {
            public static void main(String[] args) {
                InterfaceTestt1 interfaceTest = new InterfaceTestt1();
                //继承了InterfaceClass4父类,因此父类中相同方法签名的方法优先级最高
                System.out.println(interfaceTest.handle());
    
            }
    
    
            //仍然需要实现这个抽象方法
            @Override
            public int handle() {
                return 0;
            }
        }
    
        /**
         * 注意2  2 如果一个类实现的接口之间存在继承关系
         * 那么该类可以手动选择调用最低级别接口的的默认实现,但是手动选择调用其他级别接口的的默认实现。
         */
        static class InterfaceTestt2 implements Interface3, Interface, Interface0 {
            public static void main(String[] args) {
                InterfaceTestt2 interfaceTest = new InterfaceTestt2();
                //继承了InterfaceClass4父类,因此父类中相同方法签名的方法优先级最高
                System.out.println(interfaceTest.handle());
    
            }
    
            /**
             * 可以手动选择调用Interface3的方法
             * 不能可以手动选择调用Interface和Interface0的方法
             */
            @Override
            public int handle() {
                return Interface3.super.handle();
    //            return Interface.super.handle();
    //            return Interface0.super.handle();
            }
    
        }
    
    
    }
    
    interface Interface0 {
        default int handle() {
            return -1;
        }
    }
    
    interface Interface extends Interface0 {
        @Override
        default int handle() {
            return 0;
        }
    }
    
    interface Interface2 {
        default int handle() {
            return 2;
        }
    }
    
    interface Interface1 {
        default int handle() {
            return 1;
        }
    }
    
    interface Interface3 extends Interface {
        @Override
        default int handle() {
            return 3;
        }
    }
    
    interface Interface4 extends Interface {
    
    }
    
    interface Interface5 extends Interface {
    
    }
    
    
    class InterfaceClass4 {
        public int handle() {
            return 4;
        }
    }
    
    abstract class InterfaceClass5 {
        abstract int handle();
    }
    

    6 Lambda的复合

    由于lambda相当于一个函数或者行为,因此Java8允许把多个简单的Lambda复合成复杂的表达式,将简单的函数复合成为复杂的函数,这其中就用到了上面的默认方法和静态方法。

    6.1 Comparator比较器复合

    返回方法描述
    static < T,U extends Comparable<? super U >> Comparator< T >comparing(Function< ? super T,? extends U > keyExtractor)使用指定的keyExtractor提取需要比较的键,返回一个自然排序比较器。
    static < T,U > Comparator< T >comparing(Function< ? super T,? extends U > keyExtractor, Comparator< ? super U > keyComparator)使用指定的keyExtractor提取需要比较的键,返回一个指定排序的比较器。
    default Comparator< T >reversed()返回一个与调用比较器相反的比较器
    static < T extends Comparable<? super T >> ComparatorreverseOrder()返回一个与 自然排序相反的比较器。
    default Comparator< T >thenComparing(Comparator< ? super T > other)当调用比较器比较两个对象相等时使用另一个参数副比较器进行比较。
    default < U extends Comparable<? super U >> Comparator< T >thenComparing(Function< ? super T,? extends U > keyExtractor)当调用比较器比较两个对象相等时使用另一个参数副比较器进行比较。使用指定的keyExtractor提取需要比较的键

    Java8开始,支持Comparator比较器的复合,添加了许多静态方法和默认方法。主要有reversed方法,该方法用于返回一个与调用比较器相反排序顺序的比较器,以及thenComparing方法,该方法类似于复合比较器,调用方法的比较器作为主要比较器,参数比较器作为副比较器,如果主比较器比较相等,那么使用副比较器比较排序!

    /**
     * @author lx
     */
    public class CompositeTest {
    
        ArrayList<User> userArrayList = new ArrayList<>();
    
        @Before
        public void beforeTest() {
            User user4 = new User(20, "da");
            User user1 = new User(25, "张三");
            User user3 = new User(20, "张小三");
            User user2 = new User(25, "tom");
    
            userArrayList.add(user1);
            userArrayList.add(user2);
            userArrayList.add(user3);
            userArrayList.add(user4);
        }
    
    
        /**
         * 比较器复合
         */
        @Test
        public void test() {
    
            /*1 首先是通过user的age比较大小并顺序排序,我们使用JDK8提供的comparingInt静态方法*/
            Comparator<User> userComparator = Comparator.comparingInt(User::getAge);
            userArrayList.sort(userComparator);
            System.out.println("age顺序:" + userArrayList);
    
            //上面的comparingInt方法就相当于下面的两个表达式
            //实际上这里的ToIntFunction用来提取需要比较的两个对象的的属性
            //只不过这两个操作被封装到了comparingInt方法中,因此我们只需要传递一个ToIntFunction的函数即可
            ToIntFunction<User> toIntFunction = value -> value.getAge();
            Comparator<User> userComparator1 = (c1, c2) -> Integer.compare(toIntFunction.applyAsInt(c1), toIntFunction.applyAsInt(c2));
    
    
            /*
             * 2 现在如果我们需要进行逆序排序
             * 此时我们只需要将以前的比较器顺序反过来就行了
             * 比较器调用reversed方法,返回的比较器将会使用和调用比较器相反的顺序。
             */
            Comparator<User> reversed = userComparator.reversed();
            userArrayList.sort(reversed);
            System.out.println("age逆序:" + userArrayList);
    
            /*
             * 3 有时候我们需要进行多个参数的比较
             * 现在我们需要在age相等的基础上再比较姓名长度并顺序排序
             * 此时我们可以调用thenComparing方法,传递一个比较器这表示将两个比较器复合
             * 调用方法的比较器被看作主要比较器,参数的比较器看作副比较器
             * 或者更进一步,我们类似于第一个获取比较器的方式,传递一个能共提取比较的键的ToIntFunction
             */
            Comparator<User> userComparator2 = reversed.thenComparingInt(o -> o.getName().length());
            userArrayList.sort(userComparator2);
            System.out.println("age逆序-name长度顺序:" + userArrayList);
    
    
        }
    
    
        public class User {
            private int age;
            private String name;
    
            public int getAge() {
                return age;
            }
    
            public void setAge(int age) {
                this.age = age;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
    
            public User() {
            }
    
            public User(int age, String name) {
                this.age = age;
                this.name = name;
            }
    
            @Override
            public String toString() {
                return "User{" +
                        "age=" + age +
                        ", name='" + name + '\'' +
                        '}';
            }
        }
    }
    

    6.2 Function函数复合

    返回方法描述
    default < V > Function< T,V > andThen(Function< ? super R,? extends V > after)返回一个复合函数,首先将该函数应用于其输入,然后将 after函数应用于结果。
    default < V > Function< V,R >compose(Function< ? super V,? extends T > before)返回一个复合函数,首先将 before函数应用于其输入,然后将此函数应用于结果。
    static < T > Function< T,T >identity()返回一个总是返回其输入参数的函数。

    andThen和compose的运算顺序是相反的。andThen方法中,先执行调用者函数的计算,然后将结果作为参数传给after参数函数,最后执行after参数函数的计算。compose方法中,先执行before参数函数的计算,然后将结果作为参数传给调用者函数,最后执行调用者函数的计算。

    /**
     * 函数复合 andThen计算
     */
    @Test
    public void andThen() {
        User user1 = new User(20, "小花");
        //这个函数根据传入的user获取age
        Function<User, Integer> f1 = User::getAge;
        //这个函数根据传入的int值创建一个user
        Function<Integer, User> f2 = integer -> new User(integer++);
    
    
        //将函数f1和f2复合,即获取传入user的age创建一个新的user,获得新的函数f3
        //可以看到f3的参数就是f1的参数,f3的返回类型,就是f2的返回类型,相当于将f1和f2串联了起来
        //先执行f1,然后将结果传给f2,最后执行f2
        Function<User, User> f3 = f1.andThen(f2);
        System.out.println(f3.apply(user1));
    }
    
    
    /**
     * 函数复合 compose计算
     */
    @Test
    public void compose() {
        //这个函数根据传入的user获取age
        Function<User, Integer> f1 = User::getAge;
        //这个函数根据传入的int值创建一个user
        Function<Integer, User> f2 = integer -> new User(integer++);
    
    
        //将函数f1和f2复合,即获取传入user的age创建一个新的user,获得新的函数f3
        //可以看到f3的参数就是f2的参数,f3的返回类型就是f1的返回类型,相当于将f2和f1串联了起来
        //先执行f2,然后将结果传给f1,最后执行f1
        Function<Integer, Integer> f3 = f1.compose(f2);
    
        System.out.println(f3.apply(10));
    }
    

    6.3 Consumer消费复合

    返回方法描述
    default Consumer< T > andThen(Consumer< ? super T > after)返回一个复合的 Consumer ,按顺序执行该操作,然后执行 after操作。

    Consumer的andThen方法相当于按照顺序对最开始传递的参数进行一系列计算。先在调用者里面执行参数计算,然后将参数传给after,最后执行在after里面执行参数计算。

    /**
     * 消费复合 Consumer的andThen计算
     */
    @Test
    public void andThenCom() {
        User user = new User(20, "小花");
        //一个Consumer设置名字
        Consumer<User> c1 = o -> o.setName("花小");
        //一个Consumer设置年龄
        Consumer<User> c2 = o -> o.setAge(10);
        //组合
        //先在调用者里面执行参数计算,然后将参数传给after,最后执行在after里面执行参数计算。
        Consumer<User> c3 = c1.andThen(c2);
        c3.accept(user);
        System.out.println(user);
    
        //链式编程写法
        c1.andThen(o -> o.setAge(10)).accept(user);
    }
    

    6.4 Predicate断言复合

    返回方法描述
    default Predicate< T >and(Predicate< ? super T > other)返回一个组合断言,表示两个断言的&&连接
    default Predicate< T >negate()返回一个非断言,表示目前断言的!关系
    default Predicate< T >or(Predicate< ? super T > other)返回一个组合断言,表示两个断言的||连接

    断言型接口Predicate内部提供了and、negate、or默认方法,用于“&&(与)、!(非)、||(或)”的方式连接两个断言!
    调用断言的结果将会先被计算,随后与参数断言的结果进行比较,调用断言在前,参数断言在后!

    /**
     * 断言复合
     */
    @Test
    public void predicate() {
        User user = new User(20, "小花");
        //一个断言用于判断年龄是否大于20
        Predicate<User> p1 = (User u) -> u.getAge() >= 20;
        //一个断言用于判断姓名长度是否大于等于3
        Predicate<User> p2 = (User u) -> u.getName().length() >= 3;
    
        //组合
        //&&
        Predicate<User> and = p1.and(p2);
        System.out.println("年龄大于等于20并且姓名长度大于等于3:---" + and.test(user));
    
        //||
        Predicate<User> or = p1.or(p2);
        System.out.println("年龄大于等于20或者姓名长度大于等于3:---" + or.test(user));
    
        //!
        Predicate<User> negate = p1.negate();
        System.out.println("年龄小于20:---" + negate.test(user));
    
    
        
        User user2 = new User(19, "花花花");
        //多重组合
        //年龄大于等于20并且年龄大于3,或者姓名等于花花花
        System.out.println(p1.and((User u) -> u.getName().length() > 3).or((User u) -> "花花花".equals(u.getName())).test(user2));
        //年龄大于等于20并且年龄大于等于3,或者姓名等于花花花
        System.out.println(p1.and((User u) -> u.getName().length() >= 3).or((User u) -> "花花花".equals(u.getName())).test(user2));
    }
    

    7 Lambda与匿名内部类

    在Java8引入lambda之前,我们使用匿名内部类来完成“避免”类的创建,之后我们使用lambda来代替函数式接口的匿名内部类以及对象的创建,它们之间从上层特性到底层原理都有很多的不同:

    1. 关键字 this
    2. 匿名内部类中的 this 就是代表当前匿名类对象。
    3. 在lambda表达中引用this关键字,和在lambda外部引用的意义一样。因为lambda不是内部类对象,那么在lambda内部引用this也就和内部类没什么关系了。
    /**
     * @author lx
     */
    public class ThisTest {
    
    
        public static void main(String[] args) {
            ThisTest thisTest = new ThisTest();
            System.out.println("thisTest对象:"+thisTest);
            thisTest.thisTest();
        }
    
        public void thisTest() {
            System.out.println("----------------");
            ThisTest th=this;
            System.out.println("当前this对象:"+this);
    
    
            System.out.println("----------------");
            Comparator<Integer> comparator = new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    System.out.println("匿名内部类的this:"+this);
                    return o1.compareTo(o2);
                }
            };
            comparator.compare(1, 2);
    
    
            System.out.println("----------------");
            Comparator<Integer> integerComparator = (x, y) -> {
                System.out.println("lambda的this:"+this);
                System.out.println(th==this);
                return x.compareTo(y);
            };
            integerComparator.compare(1, 2);
        }
    }
    

    应用范围:

    1. 匿名内部类可以为任意接口创建实例。不管接口中包含多少个抽象方法,只要匿名内部类实现所有的抽象方法即可,也可以为抽象类甚至普通类创建实例。
    2. lambda表达式只能为函数式接口创建实例。
    3. 方法调用
    4. 匿名内部类实现抽象方法的方法体中允许调用接口中定义的默认方法;但Lambda表达式的代码块中不允许调用接口中定义的默认方法。
    5. 匿名内部类内调用与外部类有相同签名的方法时,实际调用的是该匿名内部类实例的方法。而lambda调用与外部类有相同签名的方法是,实际调用的是外部类实例的方法。
    6. 底层实现
    7. 虽然匿名内部类的使用避免了我们手动创建类,但实际上仍然会被编译器编译成一个.class文件,相当于是帮我们了一个创建一个独立的类,文件命名方式为:主类名+ + + + + +++(1.2.3…)。而在程序启动的时候,JVM会对用到的全部类进行加载、验证、准备、解析、初始化等操作,匿名内部类生成的类文件也不例外,因此大量的内部类文件的创建将会影响应用程序启动执行的性能。
    8. 对于lambda表达式,Java编译器使用Java7引入的 invokedynamic 字节码指令(为支持动态类型语言新增的指令)。invokedynamic指令不会在编译时就进行类型检查而产生新的类文件,而是将lambda表达式的字节码类型检查转换操作推迟到了第一次调用时,相当于一个调用点,仅在lambda表达式被首次调用的时候(执行到invokedynamic调用点),才会通过反射创建一个匿名的lambda实现类以及对象,之后的调用也都会跳过这一步骤,没有了程序启动时就进行的类加载过程,而是第一次用到的时候才会进行相应的类动态创建工作,自然提升了性能。
    /**
     * @author lx
     */
    public class LambdaInvoke {
        public static void main(String[] args) {
            classTest(new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o1.compareTo(o2);
                }
            });
            lambdaTest((Comparator<Integer>) Integer::compareTo);
        }
        public static void classTest(Comparator comparator) {}
        public static void lambdaTest(Comparator comparator) {}
    }
    

    使用Javap -v 查看class文件的字节码,可以发现匿名内部类对象的创建工作就是使用了一般的new指令而已,这说明这个匿名类在程序启动的时候就被加载进来了,而lambda表达式则使用了invokedynamic指令,对应的类以及对象会在执行时被动态的加载。
    在这里插入图片描述

    8 总结

    lambda为Java这种面向对象的语言带来了函数式编程的写法,改变了Java只能面像对象的局限性,某些情况下能够极大地减少代码量。Java引入lambda的目的并不是为了完全取代面向对象编程,而是为了方便我们使用混合开发方式,在合适的情况下采用合理的编程方式,能够有效的提高开发效率!

    lambda的另一个重要应用就是同样在Java8新增的Stream API中,几乎都可以使用函数式接口与lambda作为参数完成功能强大的流式编程!

    Stream API中才是lambda大展身手的地方!

    相关文章:

    Stream:Java8—两万字的Stream流的详细介绍与应用案例

    如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

    展开全文
  • 但是稍有不慎就会造成不可弥补的损失,这里就这能指针sheard_ptr造成的循环引用及造成循环引用的原因我给大家给出了详细的图解及举例说明,同时也提供了避免这种循环引用的解决办法,就针对弱引用这一点做出了详细的...

        用指针的方式解决shared_ptr造成的循环引用防止内存泄漏!


    《***》循环引用就是由于智能指针shared_ptr造成的,下面就是shared_ptr的使用造成循环引用的图解:

    《****》举个例子来说下shared_ptr造成的循环引用:

    (选题背景双向链表)

    <span style="font-size:18px;">#include<memory>
    #include<iostream>
    using namespace std;
    
    struct Node
    {
    	shared_ptr<Node> _pre;
    	shared_ptr<Node> _next;
    
    	~Node()
    	{
    		cout << "~Node():" << this << endl;
    	}
    	int data;
    };
    
    void FunTest()
    {
    	shared_ptr<Node> Node1(new Node);
    	shared_ptr<Node> Node2(new Node);
    	Node1->_next = Node2;
    	Node2->_pre = Node1;
    
    	cout << "Node1.use_count:"<<Node1.use_count() << endl;
    	cout << "Node2.use_count:"<< Node2.use_count() << endl;
    }
    
    int main()
    {
    	FunTest();
    	system("pause");
    	return 0;
    }</span>


    执行结果:

    (说明:shared_ptr的使用使得一块空间有两个对象管理,即头个结点的_next域和下一个指针共同管理,或者又头一个指针和第二个结点的_ptr域共同管理所以其_pCount=2)

    针对上面出现的由于引用计数和管理空间的对象的个数导致空间不能释放的结果就是循环引用。

    针对循环引用我们有三种解决方案:

          《1》当只剩下最后一个引用的时候需要手动打破循环引用释放对象。

    2       《2》parent的生存期超过children的生存期的时候,children改为使用一个普通指针指向parent

    3       《3》使用弱引用的智能指针打破这种循环引用。虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制

        容易出错切不易操作。

      *什么是强引用和弱引用 

       一个强引用是指当被引用的对象仍活着的话,这个引用也存在(也就是说,只要至少有一个强引用,那么这个对象 就不会也不能被释放)。

        boost::share_ptr就是强引用。

        相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它自身存在的时的一个引用。

        弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理。

        在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存 r

        weak_ptr的出现就是为了辅助shared_ptr的工作,弥补shared_ptr的不足,解决shared_ptr造成的循环引用问题,而weak_ptr的这种解决方法也就是弱引用。

     

    #include<memory>
    #include<iostream>
    using namespace std;
    
    struct Node
    {
    	weak_ptr<Node> _pre;
    	weak_ptr<Node> _next;
    
    	~Node()
    	{
    		cout << "~Node():" << this << endl;
    	}
    	int data;
    };
    
    void FunTest()
    {
    	shared_ptr<Node> Node1(new Node);
    	shared_ptr<Node> Node2(new Node);
    	Node1->_next = Node2;
    	Node2->_pre = Node1;
    
    	cout <<"Node1.use_count:"<< Node1.use_count() << endl;
    	cout <<"Node2.use_count:"<< Node2.use_count() << endl;
    }
    
    int main()
    {
    	FunTest();
    	system("pause");
    	return 0;
    }<
    
    


     

      执行结果:

     

       *从上面的结果看,很明显,在用了智能指针weak_ptr后调用了析构函数,释放了空间,而shead_ptr则没有释放空间.

    《*》在讲循环引用之前首先必须了解下什么是智能指针:

         

    《**》下面就有关循环引用的两个指针shared_ptr和weak_ptr逐一分析下:

    shared_ptr:

           

    shared_ptr完成了你所希望的工作:他负责在不使用实例时删除由它指向的对象(pointee),并且它可以自由的共享它指向的对象(pointee)

    weak_ptr:

            weak_ptr是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用记数的增加或减少。没有重载*->但可以使用lock获得一个可用的shared_ptr对象。

    weak_ptr的一个重要用途是通过lock获得this指针的shared_ptr,使对象自己能够生产shared_ptr来管理自己,但助手类enable_shared_from_thisshared_from_this会返回thisshared_ptr,只需要让想被shared_ptr管理的类从它继承即可



    展开全文
  • const常量引用的使用方法

    千次阅读 2014-11-27 21:07:07
    在这篇文章中,我们将会为大家详细介绍一下有关C++常量引用的相关应用方法,相信大家可以从中学到很多知识。 C++单例模式基础内容详解C++创建Web服务相关方法剖析C++构造函数如何进行初始化C++标识符命名规则相关...

    C++编程语言中,对于常量的引用是一个非常重要的应用技巧。在这篇文章中,我们将会为大家详细介绍一下有关C++常量引用的相关应用方法,相信大家可以从中学到很多知识。

    • C++单例模式基础内容详解
    • C++创建Web服务相关方法剖析
    • C++构造函数如何进行初始化
    • C++标识符命名规则相关应用技巧分享
    • C++函数模板基本概念讲述

    如果是对一个常量进行引用,则编译器首先建立一个临时变量,然后将该常量的值置入临时变量中,对该引用的操作就是对该临时变量的操作。对C++常量引用可以用其它任何引用来初始化;但不能改变。

    关于引用的初始化有两点值得注意:

    (1)当初始化值是一个左值(可以取得地址)时,没有任何问题;

    (2)当初始化值不是一个左值时,则只能对一个const T&(常量引用)赋值。而且这个赋值是有一个过程的:

    首先将值隐式转换到类型T,然后将这个转换结果存放在一个临时对象里,最后用这个临时对象来初始化这个引用变量。在这种情况下,const T&(常量引用)过程中使用的临时对象会和const T&(常量引用)共存亡。

    例子:

     
    1. double& dr = 1; // 错误:需要左值  
    2. const double& cdr = 1; // ok 

    第二句实际的过程如下:

     
    1. double temp = double(1);  
    2. const double& cdr = temp

    作函数参数时:

     
    1. // bc_temp_objects_not_bound_to_nonconst_ref.cpp  
    2. // compile with: /EHsc  
    3. #include "iostream"  
    4. using namespace std;  
    5. class C {};  
    6. void f(C & c) { cout << "C&" << endl; }  
    7. void f(C const & c) { cout << "C const &" << endl; }  
    8. int main() {  
    9. f(C());  

    结果:

    C const &

    更直接的,用基本类型:

     
    1. #include <iostream> 
    2. using namespace std;  
    3. void display(int const &ref) {cout<<ref<<'\n';}  
    4. int main()  
    5. {  
    6. int i=1;  
    7. display(i);  
    8. int const anotheri=2;  
    9. display(anotheri);  
    10. display(2);  
    11. display(1+2);  
    12. display(static_cast<int>(3.14159));  

    通过C++常量引用从函数返回一个局部对象:

    一般从一个函数返回一个局部对象的引用是不对的:
     

     
    1. T & my_op ( void )   
    2. {   
    3. T t;   
    4. return t;   
    5. } // The T object t got destroyed here so the  returned reference is not valid anymore.  

    特殊情况:返回一个常引用

     
    1. const T & my_op ( void )   
    2. {   
    3. T t;   
    4. return t;   
    5. }   
    6. const T & my_t_obj = my_op ();  

    在这个情况下,局部变量 t 不会被直接析构,而是会保留到 my_t_obj 的生命周期结束为止。

    总之,C++常量引用语法上可以引用一个临时变量。这种方法在使用引用作函数参数和返回局部变量时有意义。我目前看来常量引用主要用在作函数参数或保证不修改原变量的时候。


    注释:

    最后再举例说明一下,

    int& rint = 12; //错误

    const int& rint =12;//正常编译通过

    引用rint指向编译器隐式分配内存并创建的匿名int类型临时对象。对rint引用的任何操作都会影响匿名临时对象,而不会影响常量12.同时编译器也会确保这样的匿名临时对象会将生命期扩展到初始化后的引用存在的全部时域。看下面的代码

    short s = 123;

    const int& rint = s;

    s = 321;

    const int* ip = &rint;

    printf("rint is  %d ,s is %d \n",rint,s);

    printf("ip is %d ,&s is %d \n",ip,&s);

    输出结果:

    rint is 123,s is 321

    ip is 2030760 &s is 2030764

    可以看出rint应用默认值并不是s,而是常量引用初始化过程中隐式使用的匿名对象。但是这里看以看出来从s到rint是经过隐式转化的,若果将short修改为int 那么就不会出现上面的情况

    展开全文
  • Java8新特性之Stream流以及方法引用

    千次阅读 2019-03-09 21:33:39
    Java8新特性之Stream流以及方法引用一. String流1. 引言2. 流式思想概述3. 获取流4. 常用方法4.1 逐一处理:forEach4.2 过滤:filter4.3 映射:map4.4 统计个数:count4.5 取前几个:limit4.6 跳过前几个:skip4.7 ...
  • java8新特性(方法引用、构造器引用

    千次阅读 多人点赞 2017-11-22 19:19:09
    而且可读性更强,但是有时Lambda表达式的Lambda体内的功能我们已经实现过了,这个时候就可以使用我们的函数引用、构造器引用了知识点一:方法引用什么是方法引用? 答
  • 我们可以定义一个接口类型的引用变量来引用实现接口的类的实例,当这个引用调用方法时,它会根据实际引用的类的实例来判断具体调用哪个方法,这和上述的超类对象引用访问子类对象的机制相似。//定义接口InterA ...
  • lambda方法引用总结——烧脑吃透

    千次阅读 2017-06-08 15:49:37
    lambda是java8的新特性,基本使用比较容易理解,但有一个环节遇到了坎儿,那就是方法引用,尤其是类的实例方法引用,烧脑之后总结一下。在需要函数参数的方法中,我们可以把另一个同类型的方法直接传入,这称为方法...
  • 重载运算符时的参数和返回类型要用引用说明

    千次阅读 多人点赞 2017-07-12 16:17:04
    一·我们举个必须要用重载运算符和重载运算符时必须要使用引用的例子 #include #pragma pack(8) using namespace std; class Myclass { private: char* str; public: Myclass(char* str1 = "default string") { ...
  • 承接上文,已经简明阐述了使用Struct代替Class的好处,使用Class会使我们的程序出现“意外的共享”以及“循环引用”之类的危险,传统面向对象开发中对Class的依赖主要来自于我们对“继承”的依赖。Swift2.0引入协议...
  • 综合1,2,3以及目前,我们所引用的redis包不过是 org.springframework.boot spring-boot-starter-redis 添加进来后 引用包至少有 spring-boot-starter-redis-1.3.5.RELEASE.jar spring-data-redis-...
  • 背景:收到公众投稿,《从面试题中看Java的Reference(引用)》,分析的很不错,总感觉少了实际的例子和应用场景。于是结合自己工作中场景,小总结一下。看下Agenda如下: 1、强引用 2、软引用 3、弱引用 4、什么时候...
  • Java:强引用,软引用,弱引用和虚引用

    万次阅读 多人点赞 2019-01-02 16:56:19
    一、强引用 二、软引用 三、弱引用 四、虚引用 五、总结 在JDK1.2以前的版本中,当一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。这就像在商店...
  • 在Unity中引用Dll的两种方法

    万次阅读 2016-03-16 21:45:03
    下面会介绍Unity中如何集成Dll的两种方法。 1. 标准引用 这里所使用的语言是C#。 1.1 新建C#类库项目,这里就不多介绍了。 1.2 项目建好后正常的编写代码。 以上是一个简单的测试代码。 1.3 将项目属性...
  • 前言:满纸荒唐言,一把辛酸泪;...而GC垃圾回收器(Garbage Collection)对不同的类型有着不同的处理方法,了解这些处理方式有助于我们写出更高质量的代码。 在Java中,一切被视为对象,引用则是用...
  • 解决两个问题: 1.EndNote的插件不在word中显示 2.在word 2010中采用EndNote X7插入引用方法
  • 引用 相当于 Object obj=new Object()这种引用就是强引用,即使OOM也不会被垃圾回收器进行回收 软引用 如果将要发生了OOM(内存不够用了)则会将对象自动回收 弱引用 只要发生了gc()就会进行回收虚引用的对象 虚...
  • 下面先来了解Java虚拟机垃圾回收的基础内容:如何判断对象是... 介绍相关的垃圾回收基础算法:引用计数算法、可达性分析算法,以及说明finalize()方法作用,最后再来说说HotSpot虚拟机中实现对象可达性分析的一些问题。
  • 承接上一篇博文,上一篇的篇幅有点太长了,我觉得...3.“幽灵架构”为什么不会产生循环引用 4.协议的应用场景与局限性 5.运用面向协议编程思想改造控制器 让我们来简单回顾下这个架构,如果不明白的可以参考上一篇博
  • C++中 引用符&,以int&举例说明

    千次阅读 2017-11-26 10:52:46
    这种办法不仅可以用表达式对引用进行初始化,还可以用不同类型的变量对之初始化;如 double d= 3.1415926 ; const int &a=d; 以上等价为: double d= 3.1415926 ; int temp=d; //用中间变量...
  • 一文看懂引用、const引用、右值引用
  • 可能看起来有点枯燥,但引用是在JNI中最容易出错的一个点,如果使用不当,容易使程序造成内存溢出,程序崩溃等现象。所以讲得比较细,有些地方看起来可能比较啰嗦,还请轻啪!下一篇文章会写一个在Andro
  • 使用pod管理库已经用了3年,当然也做过私有库,但在做私有库的时候有些东西并没有弄得非常清楚,最近项目需要用到将自己的工程打成Framework并将这个Framework做成pod来供其他人去引用,鼓捣了大概几个小时弄了一个...
  • 3.深入PHP中的引用

    千次阅读 2013-11-22 16:04:23
    虽然常说做C/C++编程的程序员转做PHP编程很快可以上手,但是对于PHP中的引用和C++的差别比较大,这种差别更多是由于C++和PHP的变量存储结构不同造成的,本文试图详解一下PHP中的引用,对C++中的引用只是作对比时提及...
  • Java引用数据类型String详解

    万次阅读 2020-07-02 23:54:44
    Java引用数据类型(String)引用数据类型概述引用数据类型与基本数据类型的差别(举例说明引用数据类型概述 说到引用数据类型,那么何为引用数据类型? 引用类型,都可以用null值作为值,也就是说可以在初始化的...
  • java基本数据类型传递与引用传递区别详解

    万次阅读 多人点赞 2016-04-19 17:12:57
    java的值传递和引用传递在面试中一般都会都被涉及到,今天我们就来聊聊这个问题,首先我们必须认识到这个问题一般是相对函数而言的,也就是java中的方法参数,那么我们先来回顾一下在程序设计语言中有关参数传递给...
  • C++引用及常引用

    千次阅读 多人点赞 2016-08-18 20:44:58
    引用的声明方法:类型标识符 &引用名=目标变量名;  例:char ch;  char &rp=ch;  1)引用仅是变量的别名,而不是实实在在地定义了一个变量,因此引用本身并不占用内存,而是和目标变量共同指向目标变量...
  • 为了解决内存操作不灵活这个问题,可以采用软引用方法。 在JDK1.2以前的版本中,当一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。这 就像在日常...
  • 引用的释放管理

    千次阅读 2016-09-29 15:48:00
    释放引用每一个JNI引用被建立时,除了它所指向的JVM中的对象外,引用本身也会消耗掉一定数量的内存。释放局部引用大部分情况下,实现本地方法时不必担心局部引用的...这种情况啃根会导致JNI局部引用表的溢出,所以最好
  • 平时写一些小的Java Demo时我比较喜欢用UltraEdit或...在写JDBC的一些Demo时,由于要利用jar包来加载相应的数据库,每个Demo都用到了外部jar包,所以特地总结了一下利用命令行引用外部jar包的方法,归纳起来有以下4种:

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 590,262
精华内容 236,104
关键字:

引用这种说明方法