精华内容
下载资源
问答
  • java 函数
    千次阅读
    2022-04-08 15:03:39

    JNI调用Java函数,主要是在JNI中使用反射调用Java中的函数。

    1、Java代码:

    package com.my.hawk.jni2;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import static java.lang.String.format;
    
    public class MainActivity extends AppCompatActivity {
    
        TextView tv;
        // Used to load the 'native-lib' library on application startup.
        static {
            System.loadLibrary("native-lib");
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // Example of a call to a native method
            tv = findViewById(R.id.sample_text);
            tv.setText(stringFromJNI());
    
            nativeInitilize();
    
            Button startBt = findViewById(R.id.button);
            startBt.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    nativeThreadStart();
                }
            });
    
            Button stopBt = findViewById(R.id.button2);
            stopBt.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    nativeThreadStop();
                }
            });
        }
    
        public void onNativeCb(int count) {
            Log.d("Native", "onNativeCb count=" + count);
    //        TextView tv = findViewById(R.id.sample_text);
    //        tv.setText(format("%s%d", stringFromJNI(), count));
            tv.post(new Runnable() {
                @Override
                public void run() {
                    tv.setText(format("%s%d", stringFromJNI(), count));
                }
            });
        }
    
        /**
         * A native method that is implemented by the 'native-lib' native library,
         * which is packaged with this application.
         */
        public native String stringFromJNI();
    
        public native void nativeInitilize();
        public native void nativeThreadStart();
        public native void nativeThreadStop();
    }

    2、JNI代码

    #include <jni.h>
    #include <string>
    #include <sstream>
    #include <android/log.h>
    #include <unistd.h>
    
    JavaVM *gJavaVm;
    jobject gJaveObj;
    static volatile int gIsThreadExit = 0;
    
    #define LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "Native", __VA_ARGS__)
    
    static const char *classPath = "com/my/hawk/jni2/MainActivity";
    
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_my_hawk_jni2_MainActivity_stringFromJNI(
            JNIEnv* env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_my_hawk_jni2_MainActivity_nativeInitilize(JNIEnv *env, jobject thiz) {
        env->GetJavaVM(&gJavaVm);
        gJaveObj = env->NewGlobalRef(thiz);
    }
    
    static void* native_thread_exec(void *arg) {
        JNIEnv *env;
        gJavaVm->AttachCurrentThread(&env, nullptr);
    
    //    jclass javaClass = env->FindClass(classPath);
        jclass javaClass = env->GetObjectClass(gJaveObj);
        if (javaClass == nullptr) {
            LOG("Fail to find javaClass");
            return nullptr;
        }
    
        jmethodID javaCallback = env->GetMethodID(javaClass, "onNativeCb", "(I)V");
        if (javaCallback == nullptr) {
            LOG("Fail to find method onNativeCb");
            return nullptr;
        }
    
        LOG("native_thread_exec loop enter");
    
        int count = 0;
        while (!gIsThreadExit) {
            env->CallVoidMethod(gJaveObj, javaCallback, count++);
            sleep(1);
        }
        gJavaVm->DestroyJavaVM();
        LOG("native_thread_exec loop leave");
    
        return nullptr;
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_my_hawk_jni2_MainActivity_nativeThreadStart(JNIEnv *env, jobject thiz) {
        gIsThreadExit = 0;
        pthread_t threadId;
        if (pthread_create(&threadId, nullptr, native_thread_exec, nullptr) != 0) {
            LOG("native_thread_start pthread_create fail!");
            return;
        }
        LOG("native_thread_start success");
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_my_hawk_jni2_MainActivity_nativeThreadStop(JNIEnv *env, jobject thiz) {
        gIsThreadExit = 1;
        LOG("native_thread_stop success");
    }

    其中的关键,获取方法,然后通过反射调用native_thread_exec

    初始化的时候保存全局JVM和class对象 初始化的时候保存全局

        env->GetJavaVM(&gJavaVm);
        gJaveObj = env->NewGlobalRef(thiz);

    1、Android环境中,每个进程只能诞生一个JavaVM对象,被所有线程共享。在VM加载*.so程序库时,会先调用JNI_OnLoad()函数,在JNI_OnLoad()函数中会将JavaVM指针对象保存到c层JNI的全局变量中。
    2、JNIEnv对象和线程是一一对应的关系;
    3、Jvm和JNIEnv释放问题?JVM 中 Java Heap 的内存泄漏?JVM 内存中 native memory 的内存泄漏?
    4、从操作系统角度看,JVM 在运行时和其它进程没有本质区别。在系统级别上,它们具有同样的调度机制,同样的内存分配方式,同样的内存格局。JVM 进程空间中,Java Heap 以外的内存空间称为 JVM 的 native memory。进程的很多资源都是存储在 JVM 的 native memory 中,例如载入的代码映像,线程的堆栈,线程的管理控制块,JVM 的静态数据、全局数据等等。也包括 JNI 程序中 native code 分配到的资源。
    Local Reference 导致的内存泄漏?
     

    参考:Android开发实践:JNI层线程回调Java函数示例 - 指针空间 - 博客园

    JNI开发:JNI层新起的函数中(C回调函数中)调用JAVA层的接口_tingzhushaohua的博客-CSDN博客_jni 回调函数

    C++通过JNI层回调java函数 - 百度文库

    Android NDK开发(一) - 简书

    jni java 函数指针_java native interface JNI 调用Java方法_我是XiaoYang呀的博客-CSDN博客


    JNI数据传递

    Android:JNI调用C++自定义类的详细方法_chaoqiangscu的博客-CSDN博客_jni调用c++类

    Java代码与Jni层之间传递数组(byte[])_xiao慕r的博客-CSDN博客_jni传递数组

    Android-JNI之数据类型转换_zhezi521的博客-CSDN博客_android jni 类型转换

    android ndk 返回字符串,android ndk返回String(字符串)_天才娜娜ln的博客-CSDN博客

    小心ReleaseByteArrayElements 中的参数问题_普通网友的博客-CSDN博客

    JNIEnv*的常用函数详解

    java jni 手册_Java中JNI的使用详解第二篇:JNIEnv类型和jobject类型的解释_发条粽子的博客-CSDN博客


    native和static native区别_飞鸟_的博客-CSDN博客_jni static

    更多相关内容
  • Java函数式编程

    千次阅读 2022-01-26 15:03:02
    Java函数式编程和


    函数式接口

    有且只有一个抽象方法的接口被称为函数式接口,该接口中可以包含其他的方法(默认,静态,私有)。函数式接口用@FunctionalInterface注解。

    引入函数式编程后Java接口的定义发生了一些变化:

    1. 函数式接口:函数式接口中只能有一个抽象方法,这也是为了函数调用时避免带来二义性。使用@FunctionInterface标注接口为函数式接口(@FunctionInterface并不是一定要标注但若是标注可以在编译时就给你提示错误)

    2. 支持静态方法:支持静态方法目的完全出于编写类库,对某些行为进行抽象,但是接口中的静态方法不能被继承。

    3. 支持默认实现:这是不得已而为之,因为Java8引入了函数式接口,许多像Collection这样的基础接口中增加了方法,如果还是一个传统的抽象方法的话,那么可能很多第三方类库就会变得完全无法使用。新增一个方法所有实现类都要实现一次。被default修饰的方法–默认实现。其主要思想就是如果子类中没有实现,那么采用父类提供的默认实现。

    定义了这种类型的接口,使得以其为参数的方法,可以在调用时,使用一个lambda表达式作为参数。从另一个方面说,一旦我们调用某方法,可以传入lambda表达式作为参数,则这个方法的参数类型,必定是一个函数式的接口。

    关于函数式接口只有一个抽象方法
    关于函数式接口只有一个抽象方法的理解:

    1. 一个函数式接口有且只有一个抽象方法。

    2. 默认方法不是抽象方法,因为它们已经实现了。

    3. 重写了超类Object类中任意一个public方法的方法并不算接口中的抽象方法。

      如果一个接口中声明的抽象方法是重写了超类Object类中任意一个public方法,那么这些抽象方法并不会算入接口的抽象方法数量中。因为任何接口的实现都会从其父类Object或其它地方获得这些方法的实现。

    所以比如Comparator接口,有两个抽象方法compare和equals,但equals并不算入接口中的抽象方法,所以Comparator接口还是满足函数式接口的要求,Comparator接口是一个函数式接口

    Lambda表达式

    Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

    Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

    在Lambda表达式中无需像传统写法那样声明参数和返回值类型,它会根据你的上下文通过类型推导你实现的是哪一个接口,从而跟具这个接口的定义知道你变量的类型。这也是为什么函数式接口只能声明一个方法的原因。

    增加Lambda的目的,其实就是为了支持函数式编程,而为了支持Lambda表达式,才有了函数式接口。

    另外,为了在面对大型数据集合时,为了能够更加高效的开发,编写的代码更加易于维护,更加容易运行在多核CPU上,java在语言层面增加了Lambda表达式。

    语法特征

    (parameters) -> expression
    
    (parameters) -> { statements; }
    

    是lambda表达式的重要特征:

    • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
    • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。

    方法引用

    方法的引用让你可以重复使用现有的方法定义,并像lambda一样传递他们,在一些情况下,比起使用lambda表达式,它们似乎更易读,感觉也更自然。

    语法:

    • 类::静态方法名

      指向静态方法的方法引用,例如Integer的compare方法 ,可以写成Integer::compare

      Comparator<Integer> c = Integer::compare;
      
    • 类::实例方法名

      指向任意类型实例方法的方法引用,例如String的equals方法,写成String::equals

      BiPredicate<String, String> b = String::equals;
      
    • 对象::实例方法名

      指向现有对象的实例方法的方法引用,例如System.out的println方法,写成System.out::println

      Consumer<String> con1 = System.out::println;
      User user = new User();
      Consumer<String> con2 = user::setUserName;
      

    构造器引用

    • 构造器的引用:ClassName::new

      对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用,例如:String::new。

      Supplier<String> supplier = String::new;
      

    Java8之前的函数式接口

    Java8之前已经存在的函数式接口

    函数式接口输入参数输出参数描述
    Runnable--多线程自定义执行逻辑,无返回值
    Callabler<V>-V多线程自定义执行逻辑,有返回值
    PrivilegedAction<T>-T
    Comparator<T>T、Tint比较器,常用语比较和排序
    FileFilterFileboolean文件过滤器
    PathMatcherPathboolean路径匹配器
    InvocationHandlerObject、Method、Object[]Object动态代理,生成代理对象
    PropertyChangeListenerPropertyChangeEvent-属性改变监听器
    ActionListenerActionEvent-动作监听器
    ChangeListenerChangeEvent-变动监听器

    Java8新增的函数式接口

    Java8新增的java.util.function包定义的函数式接口

    函数式接口输入参数输出参数描述
    Supplier<T>-T生产接口

    指定接口的泛型是什么类型,那么接口的get方法就会生产什么类型的数据
    IntSupplier-int
    LongSupplier-long
    DoubleSupplier-double
    BooleanSupplier-boolean
    Consumer<T>T-消费接口

    指定接口的泛型是什么类型,那么接口的accept方法就会消费什么类型的数据
    IntConsumerint-
    LongConsumerlong-
    DoubleConsumerdouble-
    BiConsumer<T,U>T、U-
    ObjIntConsumer<T>T、int-
    ObjLongConsumer<T>T、long-
    ObjDoubleConsumer<T>T、double-
    Predicate<T>Tboolean判断接口

    对接口的泛型类型数据进行判断,接口的test方法就是用来得到判断结果
    IntPredicateintboolean
    LongPredicatelongboolean
    DoublePredicatedoubleboolean
    BiPredicate<T, U>T、Uboolean
    Function<T, R>TR转换接口

    接口的apply方法把T数据类型的数据转换为R数据类型的数据
    UnaryOperator<T>TT
    ToIntFunction<T>Tint
    ToLongFunction<T>Tlong
    ToDoubleFunction<T>Tdouble
    IntFunction<R>intR
    IntToLongFunctionintlong
    IntToDoubleFunctionintdouble
    IntUnaryOperatorintint
    LongFunction<R>longR
    LongToIntFunctionlongint
    LongToDoubleFunctionlongdouble
    LongUnaryOperatorlonglong
    DoubleFunction<R>doubleR
    DoubleToIntFunctiondoubleint
    DoubleToLongFunctiondoublelong
    DoubleUnaryOperatordoubledouble
    BiFunction<T, U, R>T、UR
    ToIntBiFunction<T, U>T、Uint
    ToLongBiFunction<T, U>T、Ulong
    ToDoubleBiFunction<T, U>T、Udouble
    BinaryOperator<T>T、TT
    IntBinaryOperatorint、intint
    LongBinaryOperatorlong、longlong
    DoubleBinaryOperatordouble、doubledouble

    Supplier(生产)

    Supplier<T>
    抽象方法

    public T get():用来获取一个泛型参数指定类型的对象数据
    

    Supplier< T > 接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据。

    举例:
    求数组的最大值最小值

    public static void main(String[] args) {
        Integer[] arr = {2, 7, 3, 4, 5};
        //求最大值
        Integer max = get(() -> {
            Integer a = arr[0];
            for (Integer integer : arr) {
                a = integer >= a ? integer : a;
            }
            return a;
        });
        System.out.println("max:"+max);
        // 求最小值
        Integer min = get(() -> {
            Integer a = arr[0];
            for (Integer integer : arr) {
                a = integer <= a ? integer : a;
            }
            return a;
        });
        System.out.println("min:"+min);
    }
    
    public  static <T> T get(Supplier<T> supplier) {
        return supplier.get();
    }
    

    日志

    max:7
    min:2
    

    Consumer(消费)

    Consumer<T>

    抽象方法

    public void accept(T t):对给定的参数执行此操作。   
    

    默认方法

    public default Consumer<T> andThen(Consumer<? super T> after):返回一个组合的 Consumer ,按顺序执行该操作,然后执行 after操作。 
    

    Consumer<T> 接口被称之为消费型接口,指定接口的泛型是什么类型,那么就可以使用接口中的accept方法消费什么类型的数据,具体怎么消费就需要自定义。

    而其默认方法则是可以把两个Consumer接口组合在一起对数据进行消费,谁写在前边就先消费谁,其调用格式为con1.andThen(con2).accept(s)。

    举例:

    public static void main(String[] args) {
        User user = new User();
        user.setId("1");
        // 打印用户信息
        consumer(user, (u) -> System.out.println(JSON.toJSONString(u)));
        // 修改用户信息
        consumer(user, (u) -> u.setId("2"));
        System.out.println(JSON.toJSONString(user));
    }
    
    public static void consumer(User user, Consumer<User> consumer) {
        consumer.accept(user);
    }
    

    日志

    {"id":"1"}
    {"id":"2"}
    

    BiConsumer<T, U>
    和Consumer<T>类似

    举例:

    public static void main(String[] args) {
        User user = new User();
        user.setId("1");
        // 修改用户ID为指定值
        biConsumer(user,"2", (u,id) ->u.setId(id));
        System.out.println(JSON.toJSONString(user));
    }
    
    public static void biConsumer(User user, String id, BiConsumer<User, String> biConsumer) {
        biConsumer.accept(user, id);
    }
    

    日志

    {"id":"2"}
    

    Predicate(判断)

    Predicate<T>

    抽象方法

    public boolean test(T t):在给定的参数上评估这个谓词。
    

    默认方法

    public default Predicate<T> and(Predicate<? super T> other):返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑AND。
    public default Predicate<T> or(Predicate<? super T> other):返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑或。  
    public default Predicate<T> negate():返回表示此谓词的逻辑否定的谓词。
    

    Predicate< T > 接口就是用来对某种数据类型的数据进行判断,接口中的test方法就是用来制定判断规则并把结果返回

    其三个默认方法其实就是相对三个逻辑——与、或、非。

    举例:

    public static void main(String[] args) {
        Integer integer = 2;
        Predicate<Integer> predicate1 = (i) -> i > 1;
        Predicate<Integer> predicate2 = (i) -> i < 2;
        Predicate<Integer> predicate3 = (i) -> i < 0;
    
        System.out.println(predicate1.test(integer));
        System.out.println(predicate1.and(predicate2).test(integer));
        System.out.println(predicate1.negate().or(predicate3).test(integer));
    }
    

    日志:

    true
    false
    false
    

    BiPredicate<T, U>
    和Predicate<T>类似
    举例:

    public static void main(String[] args) {
        Integer integer1 = 2;
        Integer integer2 = 2;
        BiPredicate<Integer, Integer> predicate1 = (i1, i2) -> i1.compareTo(i2) > 0;
        BiPredicate<Integer, Integer> predicate2 = (i1, i2) -> i1.compareTo(i2) == 0;
    
        System.out.println(predicate1.test(integer1, integer2));
        System.out.println(predicate1.or(predicate2).test(integer1, integer2));
        System.out.println(predicate2.negate().test(integer1, integer2));
    }
    

    日志:

    false
    true
    false
    

    Function(转换)

    Function<T,R>
    抽象方法

    public R apply(T t):将此函数应用于给定的参数。 
    

    默认方法

    public default <V> Function<T,V> andThen(Function<? super R,? extends V> after):返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果。  
    

    Function< T , R > 接口被用作转换数据类型的,可调用其apply方法把T数据类型的数据转换为R数据类型的数据,但转换规则需要自定义

    另外,其默认方法也是把两个Function接口连接起来,第二个Function接口会把第一个Function接口的输出结果作为输入,也可认为这是链式组合(类似方法的链式调用)

    举例:

    public static void main(String[] args) {
        Integer integer = 2;
        Function<Integer, Integer> function1 =  (i) -> i+1;
        Function<Integer, Integer> function2 =  (i) -> i*10;
        System.out.println(function1.apply(integer));
        System.out.println(function2.apply(integer));
        System.out.println(function1.andThen(function2).apply(integer));
    }
    

    日志:

    3
    20
    30
    

    BiFunction<T, U, R>
    和Function<T,R>类似
    举例:

    public static void main(String[] args) {
        Integer integer1 = 2;
        Integer integer2 = 5;
        BiFunction<Integer, Integer, Integer> biFunction = (i1,i2) -> i1+i2;
        Function<Integer, Integer> function = (i) -> i*10;
        System.out.println(biFunction.apply(integer1, integer2));
        System.out.println(biFunction.andThen(function).apply(integer1, integer2));
    }
    

    日志:

    7
    70
    



    参考文章:常用函数式接口的介绍与使用

    展开全文
  • java函数式接口

    千次阅读 2021-07-12 19:04:32
    Java中的函数式编程体现就是Lambda,所以函数式接口就是可 以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。 备注:“语法糖”是指使用更加方便,但是原理...

    一、函数式接口

    概念

    函数式接口在Java中是指:有且仅有一个抽象方法的接口。 当然接口中可以包含其他的方法(默认,静态,私有)

    函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可

    以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

    备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实

    底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部

    类的“语法糖”,但是二者在原理上是不同的。

    格式

    只要确保接口中有且仅有一个抽象方法即可:

    修饰符 interface 接口名称 {

    public abstract 返回值类型 方法名称(可选参数信息);

    // 其他非抽象方法内容

    }

    @FunctionalInterface注解

    与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注

    解可用于一个接口的定义上:

    一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要

    的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

    作用:可以检测接口是否是一个函数式接口

    是:编译成功 否:编译失败(接口中没有抽象方法或者抽象方法的个数多于一个)

    函数式接口的使用:一般作为方法的参数和返回值类型

    当参数是一个接口时,我们可以采用下面三种方法来进行操作

    image-20210712144949790

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M22jJDjQ-1626087851590)(…/…/…/…/AppData/Roaming/Typora/typora-user-images/image-20210712144840832.png)]

    性能浪费的日志案例

    注:日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。

    一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:

    image-20210712150811186

    发现以下代码存在的一些性能浪费的问题:
    调用showLog方法,传递的第二个参数是一个拼接后的字符串,先把字符串拼接好,然后在调用showlog方法,
    showLog方法中如果传递的日志等级不是1级,那么就不会是如此拼接后的字符串,所以感觉字符串就白拼接了,存在了浪费.

    使用Lambda优化日志案例

    使用L ambda优化日志案例
    Lambda的特点:延迟加载
    Lambda的使用前提,必须存在函数式接口

    使用Lambda必然需要一个函数式接口:

    然后对 log 方法进行改造:

    @FunctionalInterface 
    
    public interface MessageBuilder { 
    
    String buildMessage(); 
    
    }
    

    对 log 方法进行改造:

    public class Demo02LoggerLambda { 
    
    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"; 
        log(1, ()> msgA + msgB + msgC ); 
    } 
    }
    

    这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。

    使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中。
    只有满足条件,日志的等级是1级,才会调用接口MessageBuilder中的方法builderMessage,才会进行字符串的拼接。如果条件不满足,日志的等级不是1级
    那么MessageBuilder接口中的方法builderMessage也不会执行
    所以拼接字符串的代码也不会执行
    所以不会存在性能的浪费
    

    使用Lambda作为参数和返回值

    如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数 式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式 接口作为方法参数。

    例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就 可以使用Lambda进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。

    public class Demo04Runnable {
        private static void startThread(Runnable task) { 
            new Thread(task).start(); }
        
        public static void main(String[] args) { 
            startThread(()> System.out.println("线程任务执行!"));
        } 
    }
    

    Lambda表达式作为参数传递

    使用前提:方法的参数必须时函数式接口(是接口且接口中有且只有一个抽象方法)

    image-20210712152443239

    Lambda表达式作为返回值

    如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一 个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取。

    package com.itheima.demo01.Lambda;
    
    import java.util.Arrays;
    import java.util.Comparator;
    
    public class Demo01 {
        //定义一个方法,方法的返回值类型使用函数式接口Comparator
        public static Comparator<String> getComparator(){
            //方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
           /* return new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    //按照字符串的降序排序
                    return o2.length()-o1.length();
                }
            };*/
           
           //使用lambda表达式进行优化
            return ( o1,  o2)-> o2.length()-o1.length();
        }
        public static void main(String[] args) {
            String[] arr={"aaaa","nnnnnnn","oooooooo"};
            System.out.println("排序前:");
            System.out.println(Arrays.toString(arr));
    
            System.out.println("排序后:");
            Arrays.sort(arr,getComparator());
            System.out.println(Arrays.toString(arr));
    
    
    
        }
    }
    
    

    二、常用函数式接口

    JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供。

    下面是最简单的几个接口及使用示例。

    Supplier接口

    java.util.function.Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

    Supplier接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据

    image-20210712154951738

    练习:求数组元素最大值

    题目

    使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用 java.lang.Integer 类。

    package com.itheima.demo01.Lambda;
    
    import java.util.function.Supplier;
    
    public class Demo02Test {
        //定义一个方法,方法的参数传递Supplier,泛型使用Integer
        public static Integer  getMax(Supplier<Integer> sup){
            return sup.get();
        }
        public static void main(String[] args) {
            int[] arr={12,9,8,3,30};
            //调用getMax方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
            int maxValue=getMax(()->{
                int max=arr[0];
                for (int i = 0; i <arr.length ; i++) {
                    if (max<arr[i]){
                        max=arr[i];
                    }
                }
                return max;
    
            });
            System.out.println(maxValue);
    
    
        }
    }
    
    

    Consumer接口

    java.util.function.Consumer 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据(至于具体怎么消费(使用), 需要自定义(输出,计算…) 其数据类型由泛型决定。

    抽象方法:accept

    Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:

    package com.itheima.demo01.Lambda;
    
    import java.util.function.Consumer;
    
    public class Demo03Consumer {
        /*定义一个方法
    方法的参数传递一个字符串的姓名
    方法的参数传递Consumer接口,泛型使用String
    可以使用Consumer接口消费字符串的姓名*/
        public static void method(String name,Consumer<String> con){
            con.accept(name);
        }
    
        public static void main(String[] args) {
         //调用method方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以传递Lombda表达式
            method("不放弃",(String name)->{
                //1.最简单的消费,直接输出
                System.out.println(name);
                //2.消费方式:把字符串进行翻转输出
                
                //StringBuffer里面有个字符反转的方法
                String s=new StringBuffer(name).reverse().toString();
                System.out.println(s);
            });
        }
    }
    
    

    默认方法:andThen

    如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,

    然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。

    image-20210712162839420

    要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组

    合的情况:

    package com.itheima.demo01.Lambda;
    
    import java.util.function.Consumer;
    
    public class Demo04 {
        ///定义一个方法,方法的参数传递一个字符串和两个Consumer接口, Consumer接口的泛型使用字符串
        public static void method(String s, Consumer<String> con1,Consumer<String> con2){
           // con1.accept(s);
            //con2.accept(s);
            con1.andThen(con2).accept(s);//con1连接con2,先执行con1消费数据,再执行con2消费数据
        }
    
        public static void main(String[] args) {
            method("BeiJing",
                    (s)->{
                    //消费方式:将字符串变成小写
                        System.out.println(s.toLowerCase());
                    },
                    (s)->{
                     //消费方式:将字符串变成大写
                        System.out.println(s.toUpperCase());
    
                    }
                    );
        }
    
    }
    
    

    练习:格式化打印信息

    题目

    下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。 ”的格式将信息打印出来。要求将打印姓

    名的动作作为第一个 Consumer 接口的Lambda实例,将打印性别的动作作为第二个 Consumer 接口的Lambda实

    例,将两个 Consumer 接口按照顺序“拼接”到一起。

    public static void main(String[] args) { 
    
    String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" }; 
    
    }
    

    代码演示:

    package com.itheima.demo01.Lambda;
    
    import java.util.function.Consumer;
    
    public class Demo05Test {
        public static void method(String[] arr, Consumer<String> con1,Consumer<String> con2){
            //遍历字符串数组
            for (String s : arr) {
                //使用andThen方法连接连个Consume接口,消费字符串
                con1.andThen(con2).accept(s);
            }
        }
    
        public static void main(String[] args) {
            String[] arr={"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
            method(arr,
                    (t)->{
                        //将字符串数据进行切割
                        String name = t.split(",")[0];
                        //消费一:打印出姓名:XXX
                        System.out.print("姓名:"+name);
                    },
                    (t)->{
                        //将字符串数据进行切割
                        String age = t.split(",")[1];
                        //消费二:打印出性别:XXX
                        System.out.println(","+"年龄:"+age+"。");
    
    
                    });
        }
    }
    
    
    image-20210712170009157

    Predicate接口

    有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用

    java.util.function.Predicate 接口。

    抽象方法:test

    Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景:

    import java.util.function.Predicate; 
    public class Demo15PredicateTest { 
        private static void method(Predicate<String> predicate) { 
            boolean veryLong = predicate.test("HelloWorld"); 
            System.out.println("字符串很长吗:" + veryLong); }
        public static void main(String[] args) { 
            method(s ‐> s.length() > 5); 
        }
    }
    

    条件判断的标准是传入的Lambda表达式逻辑,只要字符串长度大于5则认为很长。

    默认方法:and

    相当于&&

    既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实

    现“并且”的效果时,可以使用default方法 and 。

    如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:

    import java.util.function.Predicate; 
    public class Demo16PredicateAnd { 
        private static void method(Predicate<String> one, Predicate<String> two) {
            boolean isValid = one.and(two).test("Helloworld"); 
            System.out.println("字符串符合要求吗:" + isValid); 
        }
        public static void main(String[] args) { 
            method(s ‐> s.contains("H"), s ‐> s.contains("W")); 
        }
    }
    

    默认方法:or

    与 and 的“与”类似,默认方法 or 实现逻辑关系中的“”。

    如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可,其他都不

    变:

    import java.util.function.Predicate; 
    public class Demo16PredicateAnd { 
        private static void method(Predicate<String> one, Predicate<String> two) { 
            boolean isValid = one.or(two).test("Helloworld"); 
            System.out.println("字符串符合要求吗:" + isValid); 
        }
        public static void main(String[] args) {
            method(s ‐> s.contains("H"), s ‐> s.contains("W"));
        }
    }
    

    默认方法:negate

    “与”、“或”已经了解了,剩下的“非”(取反)也会简单。

    从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前

    调用 negate 方法,正如 and 和 or 方法一样:

    import java.util.function.Predicate; 
    public class Demo17PredicateNegate { 
        private static void method(Predicate<String> predicate) { 
            boolean veryLong = predicate.negate().test("HelloWorld"); 
            System.out.println("字符串很长吗:" + veryLong); 
        }
        public static void main(String[] args) { 
            method(s ‐> s.length() < 5);
        }
    }
    

    练习:集合信息筛选

    题目

    数组当中有多条“姓名+性别”的信息如下,请通过 Predicate 接口的拼装将符合要求的字符串筛选到集合

    ArrayList 中,需要同时满足两个条件:

    1. 必须为女生;

    2. 姓名为4个字

      代码演示:

      package com.itheima.demo01.Lambda;
      
      import java.util.ArrayList;
      import java.util.function.Predicate;
      
      public class Demo06Test {
          public static void main(String[] args) {
              String [] arr={ "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女"};
             ArrayList<String> arry= method(arr,
                      (t)->{
                          //切割字符串数组,判断名字长度
                          String name = t.split(",")[0];
                         return name.length()==4;
      
                      },
                      (t)->{
                          //切割字符串数组,判断性别
                          String sex = t.split(",")[1];
                          return sex.equals("女");
                      });
              System.out.println(arry);
          }
      
          public static ArrayList<String> method(String[] arr, Predicate<String> p1, Predicate<String> p2) {
              //定义一个集合,存储过滤之后的信息
              ArrayList<String> list = new ArrayList<>();
              //遍历字符串数组
              for (String s : arr) {
                  //使用and方法连接连个Predicate接口,比较
                  boolean b = p1.and(p2).test(s);
                  //对得到的布尔值进行判断
                  if (b){
                      //条件成立,存储信息
                      list.add(s);
                  }
      
              }
              return list;
      
          }
      }
      
      //结果:[迪丽热巴,女, 古力娜扎,女]
      

      Function接口

    java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,

    后者称为后置条件。

    抽象方法:apply

    Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。

    使用的场景例如:将 String 类型转换为 Integer 类型。

    代码演示:

    import java.util.function.Function; 
    public class Demo11FunctionApply {
        private static void method(Function<String, Integer> function) { 
            int num = function.apply("10");
            System.out.println(num + 20); 
        }
        public static void main(String[] args) { 
            method(s ‐> Integer.parseInt(s));
        }
    }
    

    默认方法:andThen

    Function 接口中有一个默认的 andThen 方法,用来进行组合操作

    需求:
    把string类型的"123",转换为Inteter类型,把转换后的结果加10
    把增加之后的Integer类型的数据。转换为String类型

    分析:
    转换了两次
    第一次是把string类型转换为了Integer类型
    所以我们可以使用Function<String, Integer> funI
    Integer i - fuq1. apply(.“123”)+10;
    第二次是把Integer类型转换为string类型
    所以我们可以使用Function<Integer. String> fun2
    string s = fun2. opply(i);

    我们可以使用andThen方法,把两次转换组合在一起使用
    String S = fun1 pndThen(fun2). apply(“123”);

    fun1先调用apply方法,把字符串转换为Integer
    fun2再调用apply方法,把Integer转换为字符串

    image-20210712185321379

    练习:自定义函数模型拼接

    题目

    请使用 Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:

    String str = “赵丽颖,20”;

    1. 将字符串截取数字年龄部分,得到字符串;

    2. 将上一步的字符串转换成为int类型的数字;

    3. 将上一步的int数字累加100,得到结果int数字。

    代码演示:

    package com.itheima.demo01.Lambda;
    
    import java.util.function.Function;
    
    public class Demo07Test {
        /**/
        public static void method(String s, Function<String,Integer> fun){
            Integer it = fun.apply(s);
            System.out.println(it+100);
    
        }
    
        public static void main(String[] args) {
            String str = "赵丽颖,20";
            String age = str.split(",")[1];
            method(age,
                    (ss)->{
                return  Integer.parseInt(ss);
    
            });
        }
    }
    
    //120
    
    
    
    展开全文
  • Java函数式编程详解

    万次阅读 多人点赞 2019-05-05 21:46:49
    1.Java函数式编程的语法: 使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出也就是说这个方法无返回值。 现在我们要定义一个Consumer接口的实例化对象,传统的方式是....

    Java从1.8以后引入了函数式编程,这是很大的一个改进。函数式编程的优点在提高编码的效率,增强代码的可读性。本文历时两个多月一点点写出来,即作为心得,亦作为交流。

    1.Java函数式编程的语法:

    使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出也就是说这个方法无返回值。 
    现在我们要定义一个Consumer接口的实例化对象,传统的方式是这样定义的:

    public static void main(String[] args) {
            //JDK1.8版本之前的做法
            Consumer<Integer> con = new Consumer<Integer>() {
                @Override
                public void accept(Integer t) {
                    System.out.println(t);
                }
            };
            //调用方法
            con.accept(3);
        }

    这里可以打印出3.

    上面是JDK1.8以前的做法,在1.8以后引入函数式的编程可以这样写:

    public static void main(String[] args) {
            //第一种写法:
            Consumer<Integer> con = (param) -> {System.out.println(param);}; 
            //第二种写法:
            Consumer<Integer> con1 = (param) -> System.out.println(param);
            //第三种写法:
            Consumer<Integer> con2 = System.out::println;
            
            con2.accept(3);
        }

    上面的con、con1、con2这个Consumer对象的引用照样可以打印出3.

    在上面的第二种写法是第一种写法的精简,但是只有在函数题中只有一条语句时才可以这么使用,做进一步的简化。

    在上面的第三种写法是对第一、二种写法的进一步精简,由于第三种写法只是进行打印,调用了System.out中的println静态方法对输入参数直接进行打印。它表示的意思就是针对输入的参数将其调用System.out中的静态方法println进行打印。

    上面已说明,函数式编程接口都只有一个抽象方法,因此在采用这种写法时,编译器会将这段函数编译后当作该抽象方法的实现。 
    如果接口有多个抽象方法,编译器就不知道这段函数应该是实现哪个方法的了。 
    因此,=  后面的函数体我们就可以看成是accept函数的实现。

    输入:-> 前面的部分,即被()包围的部分。此处只有一个输入参数,实际上输入是可以有多个的,如两个参数时写法:(a, b);当然也可以没有输入,此时直接就可以是()。
    函数体:->后面的部分,即被{}包围的部分;可以是一段代码。
    输出:函数式编程可以没有返回值,也可以有返回值。如果有返回值时,需要代码段的最后一句通过return的方式返回对应的值。但是Consumer这个函数式接口不能有具体的返回值。

    Java 8 中我们可以通过 `::` 关键字来访问类的构造方法,对象方法,静态方法。

    好了,到这一步就可以感受到函数式编程的强大能力。 
    通过最后一段代码,我们可以简单的理解函数式编程,Consumer接口直接就可以当成一个函数了,这个函数接收一个输入参数,然后针对这个输入进行处理;当然其本质上仍旧是一个对象,但我们已经省去了诸如老方式中的对象定义过程,直接使用一段代码来给函数式接口对象赋值。 
    而且最为关键的是,这个函数式对象因为本质上仍旧是一个对象,因此可以做为其它方法的参数或者返回值,可以与原有的代码实现无缝集成!


    2.Java函数式接口

    java.util.function.Consumer;
    java.util.function.Function;
    java.util.function.Predicate;

    2.1 Consumer是一个函数式编程接口; 顾名思义,Consumer的意思就是消费,即针对某个东西我们来使用它,因此它包含有一个有输入而无输出(无返回值)的accept接口方法; 
    除accept方法,它还包含有andThen这个方法; 

    JDK源码定义如下:

    default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };

     }

    andThen这个方法是作用是:指定在调用当前Consumer后是否还要调用其它的Consumer; 

    public static void main(String[] args) {
            //定义第一个Consumer
            Consumer<Integer> consumer1 = (param) -> {System.out.println(param);};
            
            //定义第二个Consumer
            Consumer<Integer> consumer2 = (param) -> {System.out.println(param * param);};
            
            //consumer1可以连续的调用自己
            //consumer1.andThen(consumer1).andThen(consumer1).accept(3);
            //打印出 3 3 3
            
            //consumer1可以调用自己后调用consumer2
            consumer1.andThen(consumer1).andThen(consumer2).accept(3);
            //打印出3 3 9
        }

    注意:当一个Consumer接口调用另外一个Consumer对象时两个Counsumer对象的泛型必须一致。

    2.2  Function也是一个函数式编程接口;它代表的含义是“函数”,它是个接受一个参数并生成结果的函数,而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出; 
    除apply方法外,它还有compose与andThen及indentity三个方法,其使用见下述示例;

    apply的用法:

    public static void main(String[] args) {
            Function<Integer, Integer> fun = res -> res + 1;
            Integer  i = fun.apply(2);
            System.out.println(i);
    }

    上面打印出2。

    Function编程接口有两个泛型Function<T, R> T表示:函数的输入类型,R表示:函数的输出类型。

    compose的用法:

    JDK源码定义:

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));

     }

    public static void main(String[] args) {
            Function<Integer, Integer> fun = res -> res + 1;
            Function<Integer, Integer> fun1 = res -> res * 10;
            Integer i = (Integer) fun.compose(fun1).apply(2);
            System.out.println(i);
    }

    上面打印出21;

    上面表示了fun2接收到2以后先计算,然后将fun2计算的结果再交给fun再计算。

    andThen方法的用法:

    JDK源码定义:

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));

     }

    public static void main(String[] args) {
            Function<Integer, Integer> fun = res -> res + 1;
            Function<Integer, Integer> fun1 = res -> res * 10;
            Integer i = (Integer) fun.andThen(fun1).apply(3);
            System.out.println(i);
     }

    上面打印出40

    上面表示了fun先计算得到4,然后将计算结果4再给fun1计算得到40;

    indentity方法的用法:

    JDK源码定义:

    static <T> Function<T, T> identity() {
            return t -> t;
     }

    public static void main(String[] args) {
            System.out.println(Function.identity().apply("总分"));

     }

    上面打印出“总分“;就是说传入什么参数输出什么参数。

    2.3 Predicate为函数式接口,predicate的中文意思是“断定”,即判断的意思,判断某个东西是否满足某种条件; 因此它包含test方法,根据输入值来做逻辑判断,其结果为True或者False。 

    test方法的用法:

    JDK源码定义: boolean test(T t);

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            boolean rest = pre.test("1234");
            System.out.println(rest);
     }

    上面打印出true。

    and方法的用法:

    JDK源码定义:

    default Predicate<T> and(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) && other.test(t);
     }

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            Predicate<String> pre1 = res -> res.equals("1234");
            Predicate<String> pre3 = res -> res.equals("1234");
            boolean rest = pre.and(pre1).test("1234");
            boolean rest1 = pre.and(pre1).test("12341");
            System.out.println(rest);
            System.out.println(rest1); 

    }
    上面先打印出true,后打印出false

    根据源码,"1234"先和pre比较,如果为true,再和pre1比较。不为true直接返回false,不再和pre1比较。当“1234”和pre比较后为true,再和pre1比较结果为true,所以最终返回true。

    negate()的用法:

    JDK源码定义:

    default Predicate<T> negate() {
            return (t) -> !test(t);
     }

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            boolean rest = pre.negate().test("1234");
            System.out.println(rest);
    }

    上面打印出false,上面的意思是如果比较的结果是true,那么返回false,如果为false,就返回true。

    or的方法的用法:

    JDK源码定义:

    default Predicate<T> or(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) || other.test(t);
    }

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            Predicate<String> pre1 = res -> res.equals("12341");
            boolean rest = pre.or(pre1).test("12341");
            System.out.println(rest);
     }

    上面打印出true;

    or方法的意思是:只要一个为true就返回true。

    isEqual方法用法:

    JDK源码定义:

    static <T> Predicate<T> isEqual(Object targetRef) {
            return (null == targetRef)
                    ? Objects::isNull
                    : object -> targetRef.equals(object);
     }

    public static void main(String[] args) {
            System.out.println(Predicate.isEqual("12345").test("12345"));

    }

    上面打印出true;isEqual里的参数是要比较的目标对象。

    3.函数式编程接口的使用

    通过Stream以及Optional两个类,可以进一步利用函数式接口来简化代码。

    3.1 Stream

    Stream可以对多个元素进行一系列的操作,也可以支持对某些操作进行并发处理。

    3.1.1 Stream对象的创建

    Stream对象的创建途径有以下几种:

    a. 创建空的Stream对象

    Stream str = Stream.empty();

    b. 通过集合类中的stream或者parallelStream方法创建;

    List<String> list = Arrays.asList("a", "b", "c", "d");
    Stream listStream = list.stream(); //获取串行的Stream对象
    Stream parallelListStream = list.parallelStream(); //获取并行的Stream对象  

    c. 通过Stream中的of方法创建:

    Stream s = Stream.of("test");

    Stream s1 = Stream.of("a", "b", "c", "d");

    d. 通过Stream中的iterate方法创建: 
        iterate方法有两个不同参数的方法:

    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);  
    public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
    其中第一个方法将会返回一个无限有序值的Stream对象:它的第一个元素是seed,第二个元素是f.apply(seed); 第N个元素是f.apply(n-1个元素的值);生成无限值的方法实际上与Stream的中间方法类似,在遇到中止方法前一般是不真正的执行的。因此无限值的这个方法一般与limit等方法一起使用,来获取前多少个元素。 
    当然获取前多少个元素也可以使用第二个方法。 
    第二个方法与第一个方法生成元素的方式类似,不同的是它返回的是一个有限值的Stream;中止条件是由hasNext来断定的。
    第二种方法的使用示例如下:

    /**
     * 本示例表示从1开始组装一个序列,第一个是1,第二个是1+1即2,第三个是2+1即3..,直接10时中止;
     * 也可简化成以下形式:
     *        Stream.iterate(1,
     *        n -> n <= 10,
     *        n -> n+1).forEach(System.out::println);
     * 写成以下方式是为简化理解
     */
    Stream.iterate(1,
            new Predicate<Integer>() {
                @Override
                public boolean test(Integer integer) {
                    return integer <= 10;
                }
            },
        new UnaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer) {
                return integer+1;
            }
    }).forEach(System.out::println);

    e. 通过Stream中的generate方法创建 
    与iterate中创建无限元素的Stream类似,不过它的每个元素与前一元素无关,且生成的是一个无序的队列。也就是说每一个元素都可以随机生成。因此一般用来创建常量的Stream以及随机的Stream等。 
    示例如下:

    /**
     * 随机生成10个Double元素的Stream并将其打印
     */
    Stream.generate(new Supplier<Double>() {
        @Override
        public Double get() {
            return Math.random();
        }
    }).limit(10).forEach(System.out::println);

    //上述写法可以简化成以下写法:
    Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);


    f. 通过Stream中的concat方法连接两个Stream对象生成新的Stream对象 
    这个比较好理解不再赘述。

    3.1.2 Stream对象的使用
    Stream对象提供多个非常有用的方法,这些方法可以分成两类: 
    中间操作:将原始的Stream转换成另外一个Stream;如filter返回的是过滤后的Stream。 
    终端操作:产生的是一个结果或者其它的复合操作;如count或者forEach操作。

    其清单如下所示,方法的具体说明及使用示例见后文。 
    所有中间操作:

    所有的终端操作:

    下面就几个比较常用的方法举例说明其用法:

    3.1.2.1 filter
    用于对Stream中的元素进行过滤,返回一个过滤后的Stream 
    其方法定义如下:

    Stream<T> filter(Predicate<? super T> predicate);

    使用示例:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
    //查找所有包含t的元素并进行打印
    s.filter(n -> n.contains("t")).forEach(System.out::println);

    3.1.2.2 map
    元素一对一转换。 
    它接收一个Funcation参数,用其对Stream中的所有元素进行处理,返回的Stream对象中的元素为Function对原元素处理后的结果 
    其方法定义如下:

    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    示例,假设我们要将一个String类型的Stream对象中的每个元素添加相同的后缀.txt,如a变成a.txt,其写法如下:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
    s.map(n -> n.concat(".txt")).forEach(System.out::println);

    3.1.2.3 flatMap
    元素一对多转换:对原Stream中的所有元素使用传入的Function进行处理,每个元素经过处理后生成一个多个元素的Stream对象,然后将返回的所有Stream对象中的所有元素组合成一个统一的Stream并返回; 
    方法定义如下:

    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

    示例,假设要对一个String类型的Stream进行处理,将每一个元素的拆分成单个字母,并打印:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
    s.flatMap(n -> Stream.of(n.split(""))).forEach(System.out::println);

    3.1.2.4 takeWhile
    方法定义如下:

    default Stream<T> takeWhile(Predicate<? super T> predicate)

    如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。 
    与Filter有点类似,不同的地方就在当Stream是有序时,返回的只是最长命中序列。 
    如以下示例,通过takeWhile查找”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”这几个元素中包含t的最长命中序列:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
    //以下结果将打印: "test", "t1", "t2", "teeeee",最后的那个taaa不会进行打印 
    s.takeWhile(n -> n.contains("t")).forEach(System.out::println);

    3.1.2.5 dropWhile
    与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream;其定义如下:

    default Stream<T> dropWhile(Predicate<? super T> predicate)

    如以下示例,通过dropWhile删除”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”这几个元素中包含t的最长命中序列:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
    //以下结果将打印:"aaaa", "taaa"  
    s.dropWhile(n -> n.contains("t")).forEach(System.out::println);

    3.1.2.6 reduce与collect
    关于reduce与collect由于功能较为复杂,在后续将进行单独分析与学习,此处暂不涉及。

    3.2 Optional用于简化Java中对空值的判断处理,以防止出现各种空指针异常。 
    Optional实际上是对一个变量进行封装,它包含有一个属性value,实际上就是这个变量的值。

    3.2.1 Optional对象创建
    它的构造函数都是private类型的,因此要初始化一个Optional的对象无法通过其构造函数进行创建。它提供了一系列的静态方法用于构建Optional对象:

    3.2.1.1 empty
    用于创建一个空的Optional对象;其value属性为Null。 
    如:

    Optional o = Optional.empty();

    3.2.1.2 of
    根据传入的值构建一个Optional对象; 
    传入的值必须是非空值,否则如果传入的值为空值,则会抛出空指针异常。 
    使用:

    o = Optional.of("test"); 
    1
    3.2.1.3 ofNullable
    根据传入值构建一个Optional对象 
    传入的值可以是空值,如果传入的值是空值,则与empty返回的结果是一样的。

    3.2.2 方法
    Optional包含以下方法:

    3.2.3 使用场景
    常用的使用场景如下:

    3.2.3.1 判断结果不为空后使用
    如某个函数可能会返回空值,以往的做法:

    String s = test();
    if (null != s) {
        System.out.println(s);
    }

    现在的写法就可以是:

    Optional<String> s = Optional.ofNullable(test());
    s.ifPresent(System.out::println);

    乍一看代码复杂度上差不多甚至是略有提升;那为什么要这么做呢? 
    一般情况下,我们在使用某一个函数返回值时,要做的第一步就是去分析这个函数是否会返回空值;如果没有进行分析或者分析的结果出现偏差,导致函数会抛出空值而没有做检测,那么就会相应的抛出空指针异常! 
    而有了Optional后,在我们不确定时就可以不用去做这个检测了,所有的检测Optional对象都帮忙我们完成,我们要做的就是按上述方式去处理。

    3.2.3.2 变量为空时提供默认值
    如要判断某个变量为空时使用提供的值,然后再针对这个变量做某种运算; 
    以往做法:

    if (null == s) {
        s = "test";
    }
    System.out.println(s);

    现在的做法:

    Optional<String> o = Optional.ofNullable(s);
    System.out.println(o.orElse("test"));

    3.2.3.3 变量为空时抛出异常,否则使用
    以往写法:

    if (null == s) {
        throw new Exception("test");
    }
    System.out.println(s);


    现在写法:

    Optional<String> o = Optional.ofNullable(s);
    System.out.println(o.orElseThrow(()->new Exception("test")));
     

    展开全文
  • java函数的定义以及使用方法介绍

    千次阅读 2021-02-25 19:08:15
    java函数的定义以及使用方法介绍发布时间:2020-04-24 16:28:40来源:亿速云阅读:116作者:小新今天小编给大家分享的是java函数的定义以及使用方法介绍,相信很多人都不太了解,为了让大家更加了解java函数,所以给...
  • Java函数参数传递方式

    千次阅读 2021-12-09 15:08:59
    在阅读本文之前,根据自己的经验和理解,大家可以先思考并选择一下Java函数的参数传递方式: A. 是按值传递的? B. 按引用传递的? C. 部分按值部分按引用? 此处暂不宣布正确答案,我们通过一个简单的例子让大家...
  • JAVA函数的重载和重写

    千次阅读 2021-02-12 10:32:57
    一、什么是重载(overlording)在JAVA中,可以在同一个类中存在多个函数函数名称相同但参数列表不同。这就是函数的重载(overlording)。这是类的多太性表现之一。二、重载的作用:举个现实生活中的实例。假如你是个...
  • 小伙伴们知道在java中要怎么使用函数求和吗?数学函数一直是java逻辑上的重点,下面让我们通过一个实例来看看java是如何求和的吧。实例:packagecom;importjava.util.Scanner;publicclassAddadd{publicstaticvoidmain...
  • java函数参数默认值

    千次阅读 2021-02-27 08:17:54
    Java与C++不同不支持方法中的参数带默认值,但是可以通过重载、可变参数来实现该功能方法一:重载public class Test{/*** @param args*/public String getName(String firstName,String secondName){return ...
  • Java函数、参数及传参方式详解

    千次阅读 2020-05-26 22:25:52
    Java中的参数传递方式 Java中没有真正的引用传递 只有值传递!传引用参数指的还是原来的那个引用,但是Java里面参数类型是对象时是复制了原来的引用到一块新的内存,两者无关。 1、按值传递指的是在方法调用时,传递...
  • java函数式编程的好处

    千次阅读 2020-05-05 11:28:59
    Java引入了函数式编程,这表示Java从此不在是一个单纯的面向对象语言,现在他同时混合了函数式编程。这是巨大的改变,需要我们调整面对对象的编程习惯,以适应这些变化。 但是为什么我们需要去适应这些改变?为什么...
  • Java函数式编程(一)–Function的使用

    千次阅读 2019-08-15 00:50:52
    Java函数式编程(一)–Function的使用 在函数式编程中,我们用的最多的往往是Function接口.通常来说,我们很少会直接使用这个接口,但是在Java的函数式编程中,许多组件都会与这个接口有关.需要注意的是,很多人会混淆Java...
  • JAVA函数的创建和调用

    千次阅读 2020-06-14 11:44:51
    函数的创建: public static+返回类型+函数名(参数表){ 函数体 } 函数的调用: public static+返回类型+main{(String[] argd ){ 被调用的函数 函数名+(传递的参数); } public class test{ public static void ...
  • JAVA函数(方法)

    万次阅读 多人点赞 2018-07-26 10:38:51
    Java中,函数又被称为方法。 函数的主要作用是为了提高代码的复用性。 函数都是在栈内存中运行;运行的函数处在栈顶。 函数格式:修饰符 返回值类型 函数名 ( [ 参数类型1 参数名1,参数类型2 参数名2.... ] ){ ...
  • Java函数中改变变量值

    千次阅读 2019-11-07 11:49:29
    当在主函数外定义一个函数时,想在一个函数里改变一个变量的值,不能直接当形参传入,否则不会改变变量的值。 可以定义在外面,然后在函数里直接改变, 也可以放在数组中,然后将数组当形参传入,可以改变数组中的...
  • delphi调用Java函数

    热门讨论 2009-07-29 10:27:34
    delphi调用Java函数示例 delphi调用Java函数示例 delphi调用Java函数示例 delphi调用Java函数示例
  • 一、函数函数中可以直接写成员变量的名字来访问成员变量 那么究竟访问的是哪个对象的呢? 函数是通过对象来调用的 v.insertMoney() 此次调用临时简历了insertMoney()和v之间的关系 让insertMoney()内部的成员...
  • 第六章:认识Java的API-使用Java函数库 Java内置有数百个类:  如果你知道如何从统称Java API的Java的函数库中查找所需功能,那就不用再造轮子了;  核心Java函数库是由一堆等着被你当做组件使用的类集合而...
  • [java函数]判断字符串是否都是数字

    千次阅读 2018-10-23 16:37:10
    函数: isNumeric() javaAPI 用法: 1、引入依赖包 compile 'org.apache.commons:commons-lang3:3.8.1' 2、伪代码 String s=&quot;123456&quot;; String a=&quot;av12&quot;; if...
  • Java函数调用

    万次阅读 2019-09-26 19:44:23
    函数调用 ...举例说明Java函数的调用 public class test { public static void main(String[] args) { int i = 0; func(i); i = i++; System.out.println(i); String str = "hello wo...
  • java函数参数不能设为null

    千次阅读 2019-05-26 15:51:09
    package t1; public class HelloWorld { public static void main(String[] args) { } class A{ } void S(A a=null) { } } 报错 建议,建一个静态变量
  • java函数式编程之Consumer

    万次阅读 2016-12-21 23:51:40
    描述:Consumer接口接受一个T类型参数,没有返回值。 源码如下: ...public interface Consumer<T> { ... * Performs this operation on the given argument. ...两相对比,使用函数式确实是要优雅一点。
  • java函数定义

    千次阅读 2018-07-26 21:23:53
    import java.util.Scanner;//导入类 /*  * 函数的功能:简化了代码,增加了代码的复用性,提高了代码的安全性,简化了相应的操作  *   *   * 函数的构成:修饰词 返回值 函数名(参数列表){//函数体  * ...
  • C/C++调用Java函数传递多个参数并返回String类型; Java的CCallJavaTools类: package com.niubashaoye.ndk.jni; public class CCallJavaTools { /** * C/C++调用Java函数传递多个参数 * @param num1 * ...
  • C/C++调用Java函数参数和返回值均为String类型; Java的CCallJavaTools类: package com.niubashaoye.ndk.jni; public class CCallJavaTools { /** * C/C++调用Java函数传递String参数并返回String类型值 *...
  • java 方法(函数)详解

    千次阅读 2021-01-30 21:02:41
    java 函数详解基本概念注意:主函数main() 解释public static void main(String[] args) 含义函数的参数传递java自定义方法(函数)1. 无参无返回值方法2. 无参带返回值方法3. 有参无返回值方法有参有返回值方法完整...
  • java函数返回类型

    万次阅读 2016-03-28 15:26:16
    Java有8种基本类型,均可以作为函数的返回类型,除此之外还可以返回特殊类型——类。如下面的代码: public static ClassA getObject(){  ClassA newObject = new ClassA();  newObject.value+= 10;  
  • JAVA 函数的重载

    万次阅读 2018-10-17 12:20:57
    重载(overload):在同一个类中,允许存在一个以上的同名函数,只要他们的参数个数或者参数类型不同即可。比如,如果没有重载,我们在写求和这个方法时,必须写一个对整数的,再写一个对小数的。这样非常麻烦且不易...
  • Java 函数式编程

    千次阅读 2018-07-08 23:30:01
    前些年 Scala 大肆流行,打出来 Java 颠覆者的旗号,究其底气来源,无非是函数式和面向对象的“完美结合”,各式各样的“语法糖”,但其过高的学习门槛,又给了新来者当头一棒。 随着 Java8 的发布,Lambda 特性的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,123,118
精华内容 849,247
关键字:

java 函数