精华内容
下载资源
问答
  • java中字符串比较函数和操作函数,详细解析
    2021-02-28 14:29:09

    java编程语言中关于字符的内容也是极其丰富的,所以学习这方面的知识也十分重要的。今天就来为大家介绍一些与java字符串有关的内容,也就是java中字符串比较函数和操作函数,并为大家进行详细的解析,一起来看看吧。

    一、首先介绍一下字符串比较函数

    ⑴、CHARINDEX()

    返回字符串中某个指定的子串出现的开始位置。

    CHARINDEX(,)

    其中substring _expression是所要查找的字符表达式,expression可为字符串也可为列名表达式。如果没有发现子串,则返回0值。

    此函数不能用于TEXT和IMAGE数据类型。

    ⑵、PATINDEX()

    返回字符串中某个指定的子串出现的开始位置。

    PATINDEX(,)其中子串表达式前后必须有百分号“%”否则返回值为0。

    与CHARINDEX函数不同的是,PATINDEX函数的子串中可以使用通配符,且此函数可用于CHAR、VARCHAR和TEXT数据类型。

    二、然后描述一下,字符串操作函数

    ⑴、QUOTENAME()

    返回被特定字符括起来的字符串。

    QUOTENAME([, quote_ character])其中quote_

    character标明括字符串所用的字符,缺省值为“[]”。

    ⑵、REPLICATE()

    返回一个重复character_expression指定次数的字符串。

    REPLICATE(character_expression integer_expression)如果integer_expression值为负值,则返回NULL 。

    ⑶、REVERSE()

    将指定的字符串的字符排列顺序颠倒。

    REVERSE()其中character_expression可以是字符串、常数或一个列的值。

    ⑷、STUFF()

    用另一子串替换字符串指定位置、长度的子串。

    STUFF (,,,)

    如果起始位置为负或长度值为负,或者起始位置大于character_expression1的长度,则返回NULL 值。

    如果length长度大于character_expression1中start_ position以右的长度,则character_expression1只保留首字符。

    ⑸、REPLACE()

    返回被替换了指定子串的字符串。

    REPLACE (,,)

    用string_expression3替换在string_expression1中的子串string_expression2。

    ⑹、SPACE()

    返回一个有指定长度的空白字符串。

    SPACE()如果integer_expression值为负值,则返回NULL 。

    以上就是关于java中字符串比较函数和操作函数的详细解析了。如果你对java知识感兴趣,想要了解更多java基础,敬请关注奇Q工具网。

    推荐阅读:

    更多相关内容
  • 本文给大家介绍的是从Java和PHP进行对比复习了下日期时间的处理函数,并给出了一些示例,希望对大家能够有所帮助
  • 主要介绍了Kotlin 与 Java基本语法对比的相关资料,需要的朋友可以参考下
  •  C++中的函数的重载要求的是 函数名相同 参数列表必须不同 返回值类型可以相同也可以不相同;只有参数列表不相同,在函数调用时,编译环境才能准确抉择调用的是哪个函数。例如:void display();void display...
  • Java8函数式编程

    万次阅读 多人点赞 2019-06-13 15:18:48
    于是决心花点时间深入地去研究一下java8的函数式。 (想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!) 一、lambda表达式 先po一个最经典的例子——线程 public static...

    最近使用lambda表达式,感觉使用起来非常舒服,箭头函数极大增强了代码的表达能力。于是决心花点时间深入地去研究一下java8的函数式。

    (想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)

    一、lambda表达式

    先po一个最经典的例子——线程

    public static void main(String[] args) {
      // Java7
      new Thread(new Runnable() {
        @Override
        public void run() {
          for (int i = 0; i < 100; i++) {
            System.out.println(i);
          }
        }
      }).start();
    
      // Java8
      new Thread(() -> {
        for (int i = 0; i < 100; i++) {
          System.out.println(i);
        }
      }).start();
    }
    

    第一次接触lambda表达式是在创建线程时,比较直观的感受就是lambda表达式相当于匿名类的语法糖,emm~,真甜。不过事实上,lambda表达式并不是匿名类的语法糖,而且经过一段时间的使用,感觉恰恰相反,在使用上匿名类更像是Java中lambda表达式的载体。

    使用场景

    下面的一些使用场景均为个人的一些体会,可能存在不当或遗漏之处。

    1. 简化匿名类的编码

    上面的创建线程就是一个很好简化编码的例子,此处就不再重复。

    2. 减少不必要的方法创建

    在Java中,我们经常会遇到这样一种场景,某个方法只会在某处使用且内部逻辑也很简单,在Java8之前我们通常都会创建一个方法,但是事实上我们经常会发现这样写着写着,一个类中的方法可能会变得非常庞杂,严重影响阅读体验,进而影响编码效率。但是如果使用lambda表达式,那么这个问题就可以很容易就解决掉了。

    一个简单的例子,如果我们需要在一个函数中多次打印时间。(这个例子可能有些牵强,但是实际上还是挺常遇见的)

    public class FunctionMain {
        
        public static void main(String[] args) {
            TimeDemo timeDemo = new TimeDemo();
            timeDemo.createTime = System.currentTimeMillis();
            timeDemo.updateTime = System.currentTimeMillis() + 10000;
            outputTimeDemo(timeDemo);
        }
    
        private static void outputTimeDemo(TimeDemo timeDemo) {
            Function timestampToDate = timestamp -> {
                DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                return df.format(new Date(timestamp));
            };
    
            System.out.println(timestampToDate.apply(timeDemo.createTime));
            System.out.println(timestampToDate.apply(timeDemo.updateTime));
        }
    
    
        interface Function {
            String apply(long timestamp);
        }
    }
    
    class TimeDemo {
        long createTime;
        long updateTime;
    }
    

    在这段代码的outputTimeDemo中我们可以看到,对于时间戳转换的内容,我们并没有额外创建一个方法,而是类似于创建了一个变量来表达。不过,这个时候出现了另一个问题,虽然我们少创建了一个方法,但是我们却多创建了一个接口Function,总有种因小失大的感觉, 不过这个问题,我们在后面的java.util.function包部分可以找到答案。

    3. 事件处理

    一个比较常见的例子就是回调。

    public static void main(String[] args) {
        execute("hello world", () -> System.out.println("callback"));
    }
    
    private static void execute(String s, Callback callback) {
        System.out.println(s);
        callback.callback();
    }
    
    @FunctionalInterface
    interface Callback {
        void callback();
    }
    

    在这里,可以发现一点小不同,就是Callback多了一个注解@FunctionalInterface,这个注解主要用于编译期检查,如果我们的接口不符合函数式接口的要求,那编译的时候就会报错。不加也是可以正常执行的。

    4. stream中使用

    这个在后面的stream中详解。

    java.util.function包

    在之前的例子中,我们发现使用lambda表达式的时候,经常需要定义一些接口用来辅助我们的编码,这样就会使得本应轻量级的lambda表达式又变得重量级。那是否存在解决方案呢?其实Java8本身已经为我们提供了一些常见的函数式接口,就在java.util.function包下面。

    接口描述
    Function<T,R>接受一个输入参数,返回一个结果
    Supplier无参数,返回一个结果
    Consumer接受一个输入参数,并且不返回任何结果
    BiFunction<T,U,R>接受两个输入参数的方法,并且返回一个结果
    BiConsumer<T,U>接受两个输入参数的操作,并且不返回任何结果

    此处列出最基本的几个,其他的都是在这些的基础上做了一些简单的封装,例如IntFunction就是对Function<T,R>的封装。上面的这些函数式接口已经可以帮助我们处理绝大多数场景了,如果有更复杂的情况,那就得我们自己定义接口了。不过遗憾的是在java.util.function下没找到无参数无返回结果的接口,目前我找到的方案就是自己定义一个接口或者直接使用Runnable接口。

    使用示例

    public static void main(String[] args) {
        Function<Integer, Integer> f = x -> x + 1;
        System.out.println(f.apply(1));
    
        BiFunction<Integer, Integer, Integer> g = (x, y) -> x + y;
        System.out.println(g.apply(1, 2));
    }
    

    lambda表达式和匿名类的区别

    lambda表达式虽然使用时和匿名类很相似,但是还是存在那么一些区别。

    1. this指向不同

    lambda表达式中使用this指向的是外部的类,而匿名类中使用this则指向的是匿名类本身。

    public class FunctionMain {
    
        private String test = "test-main";
    
        public static void main(String[] args) {
            new FunctionMain().output();
        }
    
        private void output() {
            Function f = () -> {
                System.out.println("1:-----------------");
                System.out.println(this);
                System.out.println(this.test);
            };
            f.outputThis();
    
            new Function() {
                @Override
                public void outputThis() {
                    System.out.println("2:-----------------");
                    System.out.println(this);
                    System.out.println(this.test);
                }
            }.outputThis();
        }
    
        interface Function {
            String test = "test-function";
    
            void outputThis();
        }
    }
    

    如上面这段代码,输出结果如下
    在这里插入图片描述
    所以如果想使用lambda表达式的同时去访问原类中的变量、方法的是做不到的。

    2. 底层实现不同

    编译

    从编译结果来看,两者的编译结果完全不同。

    首先是匿名类的方式,代码如下:

    import java.util.function.Function;
    
    public class ClassMain {
    
        public static void main(String[] args) {
            Function<Integer, Integer> f = new Function<Integer, Integer>() {
                @Override
                public Integer apply(Integer integer) {
                    return integer + 1;
                }
            };
            System.out.println(f.apply(1));
        }
    }
    

    编译后的结果如下:
    在这里插入图片描述
    可以看到ClassMain在编译后生成了两个class,其中ClassMain$1.class就是匿名类生成的class。

    那么接下来,我们再来编译一下lambda版本的。代码和编译结果如下:

    import java.util.function.Function;
    
    public class FunctionMain {
    
        public static void main(String[] args) {
            Function<Integer, Integer> f = x -> x + 1;
            System.out.println(f.apply(1));
        }
    }
    

    在这里插入图片描述
    在这里我们可以看到FunctionMain并没有生成第二个class文件。

    字节码

    更进一步,我们打开他们的字节码来寻找更多的细节。首先依然是匿名类的方式
    在这里插入图片描述
    在Code-0这一行,我们可以看到匿名类的方式是通过new一个类来实现的。

    接下来是lambda表达式生成的字节码,
    在这里插入图片描述
    在lambda表达式的字节码中,我们可以看到我们的lambda表达式被编译成了一个叫做lambda$main$0的静态方法,接着通过invokedynamic的方式进行了调用。

    3. lambda表达式只能替代部分匿名类

    lambda表达式想要替代匿名类是有条件的,即这个匿名类实现的接口必须是函数式接口,即只能有一个抽象方法的接口。

    性能

    由于没有实际测试过lambda表达式的性能,且我使用lambda更多是基于编码简洁度的考虑,因此本文就不探讨性能相关问题。
    关于lambda表达式和匿名类的性能对比可以参考官方ppt https://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf

    二、Stream API

    Stream API是Java8对集合类的补充与增强。它主要用来对集合进行各种便利的聚合操作或者批量数据操作。

    1. 创建流

    在进行流操作的第一步是创建一个流,下面介绍几种常见的流的创建方式

    从集合类创建流

    如果已经我们已经有一个集合对象,那么我们可以直接通过调用其stream()方法得到对应的流。如下

    List<String> list = Arrays.asList("hello", "world", "la");
    list.stream();
    

    利用数组创建流

    String[] strArray = new String[]{"hello", "world", "la"};
    Stream.of(strArray);
    

    利用可变参数创建流

    Stream.of("hello", "world", "la");
    

    根据范围创建数值流

    IntStream.range(0, 100);         // 不包含最后一个数
    IntStream.rangeClosed(0, 99);    // 包含最后一个数
    

    BufferReader.lines()

    对于BufferReader而言,它的lines方法也同样可以创建一个流

    File file = new File("/Users/cayun/.m2/settings.xml");
    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
    br.lines().forEach(System.out::println);
    br.close();
    

    2. 流操作

    在Stream API中,流的操作有两种:Intermediate和Terminal

    Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
    Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

    除此以外,还有一种叫做short-circuiting的操作

    对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
    对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

    常见的流操作可以如下归类:
    Intermediate
    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

    Terminal
    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

    Short-circuiting
    anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

    常见的流操作详解

    1. forEach

    forEach可以说是最常见的操作了,甚至对于List等实现了Collection接口的类可以不创建stream而直接使用forEach。简单地说,forEach就是遍历并执行某个操作。

    Stream.of("hello", "world", "a", "b").forEach(System.out::println);
    

    2. map

    map也同样是一个非常高频的流操作,用来将一个集合映射为另一个集合。下面代码展示了将[1,2,3,4]映射为[1,4,9,16]

    IntStream.rangeClosed(1, 4).map(x -> x * x).forEach(System.out::println);
    

    除此之外,还有一个叫做flatMap的操作,这个操作在映射的基础上又做了一层扁平化处理。这个概念可能比较难理解,那举个例子,我们需要将[“hello”, “world”]转换成[h,e,l,l,o,w,o,r,l,d],可以尝试一下使用map,那你会惊讶地发现,可能结果不是你想象中的那样。如果不信可以执行下面这段代码,就会发现map与flatMap之间的区别了,

    Stream.of("hello", "world").map(s -> s.split("")).forEach(System.out::println);
    System.out.println("--------------");
    Stream.of("hello", "world").flatMap(s -> Stream.of(s.split(""))).forEach(System.out::println);
    

    3. filter

    filter则实现了过滤的功能,如果只需要[1,2,3,4,5]中的奇数,可以如下,

    IntStream.rangeClosed(1, 5).filter(x -> x % 2 == 1).forEach(System.out::println);
    

    4. sorted和distinct

    其中sorted表示排序,distinct表示去重,简单的示例如下:

    Integer[] arr = new Integer[]{5, 1, 2, 1, 3, 1, 2, 4};    // 千万不要用int
    Stream.of(arr).sorted().forEach(System.out::println);
    Stream.of(arr).distinct().forEach(System.out::println);
    Stream.of(arr).distinct().sorted().forEach(System.out::println);
    

    5. collect

    在流操作中,我们往往需求是从一个List得到另一个List,而不是直接通过forEach来打印。那么这个时候就需要使用到collect了。依然是之前的例子,将[1,2,3,4]转换成[1,4,9,16]。

    List<Integer> list1= Stream.of(1, 2, 3, 4).map(x -> x * x).collect(Collectors.toList());
            
    // 对于IntStream生成的流需要使用mapToObj而不是map
    List<Integer> list2 = IntStream.rangeClosed(1, 4).mapToObj(x -> x * x).collect(Collectors.toList());
    

    3. 补充

    并行流

    除了普通的stream之外还有parallelStream,区别比较直观,就是stream是单线程执行,parallelStream为多线程执行。parallelStream的创建及使用基本与stream类似,

    List<Integer> list = Arrays.asList(1, 2, 3, 4);
    // 直接创建一个并行流
    list.parallelStream().map(x -> x * x).forEach(System.out::println);
    // 或者将一个普通流转换成并行流
    list.stream().parallel().map(x -> x * x).forEach(System.out::println);
    

    不过由于是并行执行,parallelStream并不保证结果顺序,同样由于这个特性,如果能使用findAny就尽量不要使用findFirst。

    使用parallelStream时需要注意的一点是,多个parallelStream之间默认使用的是同一个线程池,所以IO操作尽量不要放进parallelStream中,否则会阻塞其他parallelStream。

    三、Optional

    Optional的引入是为了解决空指针异常的问题,事实上在Java8之前,Optional在很多地方已经较为广泛使用了,例如scala、谷歌的Guava库等。

    在实际生产中我们经常会遇到如下这种情况,

    public class FunctionMain {
    
        public static void main(String[] args) {
            Person person = new Person();
            String result = null;
            if (person != null) {
                Address address = person.address;
                if (address != null) {
                    Country country = address.country;
                    if (country != null) {
                        result = country.name;
                    }
                }
            }
            System.out.println(result);
        }
    }
    
    class Person {
        Address address;
    }
    
    class Address {
        Country country;
    }
    
    class Country {
        String name;
    }
    

    每每写到这样的代码,作为编码者一定都会头皮发麻,满心地不想写,但是却不得不写。这个问题如果使用Optional,或许你就能找到你想要的答案了。

    Optional的基本操作

    1. 创建Optional

    Optional.empty();          // 创建一个空Optional
    Optional.of(T value);      // 不接受null,会报NullPointerException异常
    Optional.ofNullable(T value);     // 可以接受null
    

    2. 获取结果

    get();                                   // 返回里面的值,如果值为null,则抛异常
    orElse(T other);                         // 有值则返回值,null则返回other
    orElseGet(Supplier other);               // 有值则返回值,null则由提供的lambda表达式生成值
    orElseThrow(Supplier exceptionSupplier); // 有值则返回值,null则抛出异常
    

    3. 判断是否为空

    isPresent();       // 判断是否为空
    

    到这里,我们可能会开始考虑怎么用Optional解决引言中的问题了,于是思考半天,写出了这样一段代码,

    public static void main(String[] args) {
        Person person = new Person();
        String result = null;
        Optional<Person> per = Optional.ofNullable(person);
        if (per.isPresent()) {
            Optional<Address> address = Optional.ofNullable(per.get().address);
            if (address.isPresent()) {
                Optional<Country> country = Optional.ofNullable(address.get().country);
                if (country.isPresent()) {
                    result = Optional.ofNullable(country.get().name).orElse(null);
                }
            }
         }
         System.out.println(result);
    }
    

    啊嘞嘞,感觉不仅没有使得代码变得简单,反而变得更加复杂了。那么很显然这并不是Optional的正确使用方法。接下来的部分才是Optional的正确使用方式。

    4. 链式方法

    在Optional中也有类似于Stream API中的链式方法map、flatMap、filter、ifPresent。这些方法才是Optional的精髓。此处以最典型的map作为例子,可以看看map的源码

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
    

    源码很简单,可以看到对于null情况仍然返回null,否则返回处理结果。那么此再来思考一下引言的问题,那就可以很简单地改写成如下的写法,

    public static void main(String[] args) {
        Person person = new Person();
        String result = Optional.ofNullable(person)
                .map(per -> per.address)
                .map(address -> address.country)
                .map(country -> country.name).orElse(null);
        System.out.println(result);
    }
    

    哇哇哇,相比原先的null写法真真是舒服太多了。

    map与flatMap的区别

    这两者的区别,同样使用一个简单的例子来解释一下吧,

    public class FunctionMain {
    
        public static void main(String[] args) {
            Person person = new Person();
            String name = Optional.ofNullable(person).flatMap(p -> p.name).orElse(null);
            System.out.println(name);
        }
    }
    
    class Person {
        Optional<String> name;
    }
    

    在这里使用的不是map而是flatMap,稍微观察一下,可以发现Person中的name不再是String类型,而是Optional类型了,如果使用map的话,那map的结果就是Optional<Optional>了,很显然不是我们想要的,flatMap就是用来将最终的结果扁平化(简单地描述,就是消除嵌套)的。
    至于filter和ifPresent用法类似,就不再叙述了。

    四、其他一些函数式概念在Java中的实现

    由于个人目前为止也只是初探函数式阶段,很多地方了解也不多,此处只列举两个。(注意:下面的部分应用函数与柯里化对应的是scala中的概念,其他语言中可能略有偏差)

    部分应用函数(偏应用函数)

    部分应用函数指的是对于一个有n个参数的函数f,但是我们只提供m个参数给它(m < n),那么我们就可以得到一个部分应用函数,简单地描述一下,如下
    在这里插入图片描述
    在这里插入图片描述
    在这里g就是f的一个部分应用函数。

    BiFunction<Integer, Integer, Integer> f = (x, y) -> x + y;
    Function<Integer, Integer> g = x -> f.apply(1, x);
    System.out.println(g.apply(2));
    

    柯里化

    柯里化就是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。换个描述,如下
    在这里插入图片描述
    Java中对柯里化的实现如下,

    Function<Integer, Function<Integer, Integer>> f = x -> y -> x + y;
    System.out.println(f.apply(1).apply(2));
    

    因为Java限制,我们不得不写成f.apply(1).apply(2)的形式,不过视觉上的体验与直接写成f(1)(2)相差就很大了。
    柯里化与部分应用函数感觉很相像,不过因为个人几乎未使用过这两者,因此此处就不发表更多见解。

    展开全文
  • Java 8 函数式的思考

    千次阅读 2021-10-22 10:43:28
    编写函数Java的准则 迭代和递归 1.实现和维护系统 1.1 共享的可变数据    最终,我们刚才讨论的无法预知的变量修改问题,都源于共享的数据结构被你所维护的代码中的多个方法读取和更新。假设几个...

    本篇内容

    • 为什么要进行函数式编程
    • 什么是函数式编程
    • 声明式编程以及引用透明性
    • 编写函数式Java的准则
    • 迭代和递归

    1.实现和维护系统

    1.1 共享的可变数据

       最终,我们刚才讨论的无法预知的变量修改问题,都源于共享的数据结构被你所维护的代码中的多个方法读取和更新。假设几个类同时都保存了指向某个列表的引用。那么到底谁对这个列表拥有所属权呢?如果一个类对它进行了修改,会发生什么情况?其他的类预期会发生这种变化吗?其他的类又如何得知列表发生了修改呢?我们需要通知使用该列表的所有类这一变化吗?抑或是不是每个类都应该为自己准备一份防御式的数据备份以备不时之需呢?换句话说,由于使用了可变的共享数据结构,我们很难追踪你程序的各个组成部分所发生的变化。

    副作用:
       函数的效果已经超出了函数自身的范畴。

    • 除了构造器内的初始化操作,对类中数据结构的任何修改,包括字段的赋值操作(一个典型的例子是setter方法)
    • 抛出一个异常。
    • 进行输入/输出操作,比如向一个文件写数据。

       从另一个角度来看“无副作用”的话,我们就应该考虑不可变对象。不可变对象是这样一种对象,它们一旦完成初始化就不会被任何方法修改状态。这意味着一旦一个不可变对象初始化完毕,它永远不会进入到一个无法预期的状态。你可以放心地共享它,无需保留任何副本,并且由于它们不会被修改,还是线程安全的。
       “无副作用”这个想法的限制看起来很严苛,你甚至可能会质疑是否有真正的生产系统能够以这种方式构建。我们希望结束本章的学习之后,你能够确信这一点。一个好消息是,如果构成系统的各个组件都能遵守这一原则,该系统就能在完全无锁的情况下,使用多核的并发机制,因为任何一个方法都不会对其他的方法造成干扰。此外,这还是一个让你了解你的程序中哪些部分是相互独立的非常棒的机会。

    1.2 声明式编程

    命令式
       一般通过编程实现一个系统,有两种思考方式。一种专注于如何实现,比如:“首先做这个,紧接着更新那个,然后……”

    声明式编程

    Optional<Transaction> mostExpensive = 
     transactions.stream() 
                 .max(comparing(Transaction::getValue));
    

       这个查询把最终如何实现的细节留给了函数库。我们把这种思想称之为内部迭代。它的巨大优势在于你的查询语句现在读起来就像是问题陈述,由于采用了这种方式,我们马上就能理解它的功能,比理解一系列的命令要简洁得多。
       采用这种“要做什么”风格的编程通常被称为声明式编程。你制定规则,给出了希望实现的目标,让系统来决定如何实现这个目标。它带来的好处非常明显,用这种方式编写的代码更加接近问题陈述了。

    1.3 为什么要采用函数式编程

       函数式编程具体实践了前面介绍的声明式编程(“你只需要使用不相互影响的表达式,描述想要做什么,由系统来选择如何实现”)和无副作用计算。正如我们前面所讨论的,这两个思想能帮助你更容易地构建和维护系统。
       为了让你有更直观的感受,我们会结合Java 8介绍这些语言的新特性,现在我们会具体给出函数式编程的定义,以及它在Java语言中的表述。我们希望表达的是,使用函数式编程,你可以实现更加健壮的程序,还不会有任何的副作用

    2 什么是函数式编程

       对于“什么是函数式编程”这一问题最简化的回答是“它是一种使用函数进行编程的方式”。那什么是函数呢?
       在函数式编程的上下文中,一个“函数”对应于一个数学函数:它接受零个或多个参数,生成一个或多个结果,并且不会有任何副作用。你可以把它看成一个黑盒,它接收输入并产生一些输出。
       这种类型的函数和你在Java编程语言中见到的函数之间的区别是非常重要的(我们无法想象,log或者 sin这样的数学函数会有副作用)。尤其是,使用同样的参数调用数学函数,它所返回的结果一定是相同的。这里,我们暂时不考虑Random.nextInt这样的方法,稍后我们会在介绍引用透明性时讨论这部分内容。
       当谈论“函数式”时,我们想说的其实是“像数学函数那样——没有副作用”。由此,编程上的一些精妙问题随之而来。我们的意思是,每个函数都只能使用函数和像if-then-else这样的数学思想来构建吗?或者,我们也允许函数内部执行一些非函数式的操作,只要这些操作的结果不会暴露给系统中的其他部分?换句话说,如果程序有一定的副作用,不过该副作用不会为其他的调用者感知,是否我们能假设这种副作用不存在呢?调用者不需要知道,或者完全不在意这些副作用,因为这对它完全没有影响。
       当我们希望能界定这二者之间的区别时,我们将第一种称为纯粹的函数式编程,后者称为函数式编程。

    2.1 函数式 Java 编程

       编程实战中,你是无法用Java语言以纯粹的函数式来完成一个程序的。比如,Java的I/O模型就包含了带副作用的方法(调用Scanner.nextLine就有副作用,它会从一个文件中读取一行,通常情况两次调用的结果完全不同)。不过,你还是有可能为你系统的核心组件编写接近纯粹函数式的实现。在Java语言中,如果你希望编写函数式的程序,首先需要做的是确保没有人能觉察到你代码的副作用,这也是函数式的含义。假设这样一个函数或者方法,它没有副作用,进入方法体执行时会对一个字段的值加一,退出方法体之前会对该字段减一。对一个单线程的程序而言,这个方法是没有副作用的,可以看作函数式的实现。换个角度而言,如果另一个线程可以查看该字段的值——或者更糟糕的情况,该方法会同时被多个线程并发调用——那么这个方法就不能称之为函数式的实现了。当然,你可以用加锁的方式对方法的方法体进行封装,掩盖这一问题,你甚至可以再次声称该方法符合函数式的约定。但是,这样做之后,你就失去了在你的多核处理器的两个核上并发执行两个方法调用的能力。它的副作用对程序可能是不可见的,不过对于程序员你而言是可见的,因为程序运行的速度变慢了!
       我们的准则是,被称为“函数式”的函数或方法都只能修改本地变量。除此之外,它引用的对象都应该是不可修改的对象。通过这种规定,我们期望所有的字段都为final类型,所有的引用类型字段都指向不可变对象。后续的内容中,你会看到我们实际也允许对方法中全新创建的对象中的字段进行更新,不过这些字段对于其他对象都是不可见的,也不会因为保存对后续调用结果造成影响。

       我们前述的准则是不完备的,要成为真正的函数式程序还有一个附加条件,不过它在最初时不太为大家所重视。要被称为函数式,函数或者方法不应该抛出任何异常

       最后,作为函数式的程序,你的函数或方法调用的库函数如果有副作用,你必须设法隐藏它们的非函数式行为,否则就不能调用这些方法(换句话说,你需要确保它们对数据结构的任何修改对于调用者都是不可见的,你可以通过首次复制,或者捕获任何可能抛出的异常实现这一目的)。

    2.2 引用透明性

       “没有可感知的副作用”(不改变对调用者可见的变量、不进行I/O、不抛出异常)的这些限制都隐含着引用透明性。如果一个函数只要传递同样的参数值,总是返回同样的结果,那这个函数就是引用透明的。
       换句话说,函数无论在何处、何时调用,如果使用同样的输入总能持续地得到相同的结果,就具备了函数式的特征。

       换句话说,函数无论在何处、何时调用,如果使用同样的输入总能持续地得到相同的结果,就具备了函数式的特征。

       通常情况下,在函数式编程中,你应该选择使用引用透明的函数。

    2.3 面向对象的编程和函数式编程的对比

       我们由函数式编程和(极端)典型的面向对象编程的对比入手进行介绍,最终你会发现Java 8认为这些风格其实只是面向对象的一个极端。

    3 递归和迭代

       纯粹的函数式编程语言通常不包含像while或者for这样的迭代构造器。为什么呢?因为这种类型的构造器经常隐藏着陷阱,诱使你修改对象。比如,while循环中,循环的条件需要更新;否则循环就一次都不会执行,要么就进入无限循环的状态。但是,很多情况下循环还是非常有用的。我们在前面的介绍中已经声明过,如果没有人能感知的话,函数式也允许进行变更,这意味着我们可以修改局部变量。我们在Java中使用的for-each循环,for(Apple a : apples { }如果用迭代器方式重写,代码如下:

    Iterator<Apple> it = apples.iterator(); 
    while (it.hasNext()) { 
     Apple apple = it.next(); 
     // ... 
    }
    

       这并不是问题,因为改变发生时,这些变化(包括使用next方法对迭代器状态的改变以及在while循环内部对apple变量的赋值)对于方法的调用方是不可见的。但是,如果使用for-each循环,比如像下面这个搜索算法就会带来问题,因为循环体会对调用方共享的数据结
    构进行修改:

    public void searchForGold(List<String> l, Stats stats){ 
     for(String s: l){ 
    	 if("gold".equals(s)){ 
    	    stats.incrementFor("gold"); 
         } 
     } 
    }
    

       实际上,对函数式而言,循环体带有一个无法避免的副作用:它会修改stats对象的状态,而这和程序的其他部分是共享的。

    4 小结

    • 从长远看,减少共享的可变数据结构能帮助你降低维护和调试程序的代价。
    • 函数式编程支持无副作用的方法和声明式编程。
    • 函数式方法可以由它的输入参数及输出结果进行判断。
    • 如果一个函数使用相同的参数值调用,总是返回相同的结果,那么它是引用透明的。采用递归可以取得迭代式的结构,比如while循环。
    • 相对于Java语言中传统的递归,“尾递”可能是一种更好的方式,它开启了一扇门,让我
      们有机会最终使用编译器进行优化。
    展开全文
  • 更有一种情况是单单比较字符串大小,达不到我们预定的需求,比如照常理 10.gif 会比 5.gif 大,但如果应用上面几个函数,就会返回 -1,即表示 10.gif比5.gif,针对这种情况,php提供了两个自然对比函数strnatcmp,...

    比较两个字符串是否相等,最常见的方法就是使用“===”来判断,至于它和“==”的区别,简单来说就是前PHP

    比较两个字符串是否相等,最常见的方法就是使用“===”来判断,至于它和“==”的区别,简单来说就是前者强调“identical”类型也要求一样;后者要求“equal”,值相同就可以了,参考【1】。或者使用strcmp来判断,但是这个能够告诉你两个字符串是否相等,但是无法告诉你在那里不同。我的思路是单字符串分割为一个个字母(character),这样比较就能精确知道在那个位置不同了。分隔字符串,使用“str_split”就可以了,语法参考【2】。然后输出结果数组,好处是连空格也会作为数组的元素。我之前的例子就是因为前一个字符串包含2个空格,而后一个只有一个。但是输出的时候看到的显示都是一样的。也可以按照其他分隔符进行分割,如“explode”或者“preg_split”,

    一般能用 !=, == 比较两个对象是否相等,只所以说是两个对象,是因为他们不一定全部为字符串,也能为整型等等。比如

    $a = "joe";

    $b = "jerry";

    if ($a != $b)

    {

    echo "不相等";

    }

    else

    {

    echo "相等";

    }

    如果用 !==,===(能看到多了一个等号)比较的话,两个对象的类型要严格相等才能返回true;否则用==,!=则会将字符串自动转换成相应的类型,以便进行比较.

    22 == "22"; // 返回 true

    22 === "22"; // 返回false   正因为这样,所以我们的程式时常会发生一些想不到的“意外”:

    0 == "我爱你"; // 返回true

    1 == "1 我爱你";// 返回true

    php教程里更有这样一组用于字符串比较的函数:strcmp,strcasecmp,strncasecmp(), strncmp(),他们都是如果前者比后者大,则返回大于0的整数;如果前者比后者小,则返回小于0的整数;如果两者相等,则返回0.他们比较的原理和其他语言的规则都是相同的。

    strcmp是用于区分大小写(即大小写敏感)的字符串比较:

    echo strcmp("abcdd", "abcde"); // 返回 1 (>0), 比较的是 "b"和"b"

    strcasecmp用于不区分大小写的字符串比较:

    echo strcasecmp("abcdd", "abcde"); // 返回 -1 (<0), 比较的是"d"和"e"

    strncmp用于比较字符串的一部分,从字符串的开头开始比较,第三个参数,为要比较的长度:

    echo strncmp("abcdd", "abcde", 3); // 返回 1 (>0), 比较了 abc 和 abc

    strncasecmp用于不区分大小写的比较字符串的一部分,从字符串的开头开始比较,第三个参数,为要比较的长度:

    echo strncasecmp("abcdd", "abcde", 3); // 返回 0, 比较了 abc 和 abc, 由于不区分大小写,所以两者是相同的。

    更有一种情况是单单比较字符串大小,达不到我们预定的需求,比如照常理 10.gif 会比 5.gif 大,但如果应用上面几个函数,就会返回 -1,即表示 10.gif比5.gif,针对这种情况,php提供了两个自然对比的函数strnatcmp,strnatcasecmp:

    echo strnatcmp("10.gif", "5.gif"); // 返回 1 (>0)

    echo strnatcasecmp("10.gif", "5.gif"); // 返回 1 (>0)

    本文由来源 21aspnet,由 system_mush 整理编辑,其版权均为 21aspnet 所有,文章内容系作者个人观点,不代表 Java架构师必看 对观点赞同或支持。如需转载,请注明文章来源。

    展开全文
  • 主要介绍了简单了解java函数式编码结构及优势,本文将探讨三种下一代 JVM 语言:Groovy、Scala 和 Clojure,比较并对比新的功能和范例,让 Java 开发人员对自己近期的未来发展有大体的认识。,需要的朋友可以参考下
  • 例如if和else里面都有一个return,但是只会有一个return会被执行到 方法调用四步骤 第一步:找到方法 第二步:参数传递 第三步:执行方法体 第四步:带着返回值回到方法的调用处 有返回值和无返回值的对比: 方法...
  • 主要介绍了java用静态工厂代替构造函数使用方法和优缺点,需要的朋友可以参考下
  • Kotlin与Java语法对比总结

    千次阅读 2022-03-22 16:22:43
    文章目录前言一、变量二、函数三、程序的逻辑控制1、条件语句2、循环语句四、面向对象编程1、类与对象2、继承3、构造函数4、接口5、数据类与单例类五、Lambda编程1、集合的创建与遍历2、集合的函数式API3、Java函数...
  • Java8函数式编程(一)

    千次阅读 2017-08-03 22:53:28
    函数式编程详解: 前言: 现在有很多公司都用了jdk8,但是函数式编程也许没有用上,jdk8也提供了很多API,比喻Stream API,等等。流式编程是它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的...
  • Kotlin 与 Java 对比

    2018-11-01 15:19:00
    Kotlin 解决了一些 Java 中的问题: Kotlin 通过以下措施修复了 Java 中一...相对于 Java 的 SAM-转换,Kotlin 有更合适的函数类型 没有通配符的使用处型变 Kotlin 没有受检异常 Java 有而 Kotlin 没有的东西 受检异...
  • 对比java和python对比

    2020-12-02 10:54:58
    对比java和python对比java和python2011年04月18日1.难易度而言。python远远简单于java。2.开发速度。Python远优于java3.运行速度。java远优于标准python,pypy和cython可以追赶java,但是两者都没有成熟到可以做项目...
  • kotlin和java对比

    千次阅读 2021-02-28 06:47:50
    Kotlin是功能非常强大的编程语言,在java以及多种语言的基础上,去掉了冗余代码,方便开发者用较少的模板编写更多的代码。尤其是在Android开发中。...API请求:Java 与 Kotlin对比使用android studio的k...
  • java与go对比(go与java语言区别)

    万次阅读 2021-01-30 21:46:47
    Go和Java作为在世界上影响巨大的两门开发语言,在语言特点和应用领域上都存在共通和相似之处。Go从2009年开源至今,在docker、K8s、企业后台等领域都取得了非凡的影响。本文以Golang的主要feature为研究对象,共分为...
  • 主题PHPJAVA函数的定义function 函数名([参数1, 参数2...参数n]){ 函数体; return 返回值;}与java形式上区别较大,java是不需要写function的,并且java是强类型的。变量的范围局部变量:函数内部声明的变量,...
  • 1 java是如何通过JNI调用本地代码的? 主要分两步,首先通过java的System.loadLibrary()方法在java代码中加载动态库,
  • java函数式编程之Consumer

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

    千次阅读 2017-10-26 09:44:51
    Java中的入口函数是main函数,在一个运行的程序中应该只有一个main函数,运行程序之后执行的第一个方法也是main方法。main方法是固定的写法,public static void main(String[] args){} ,方法名必须是 main() ,也...
  • Golang与Java各方面使用对比(下)

    千次阅读 2020-10-05 17:25:01
    本文是Golang与Java对比的第二篇,主要对比Golang与Java在面向对象、异常处理、并发编程及垃圾回收方面的差异。
  • Java中的Random()函数

    千次阅读 2021-02-12 12:26:58
    刚开始只是知道这个函数具有随机取值的作用,于是上网搜索了资料一番,做了一下一些关于Random函数的总结:Java中存在着两种Random函数:一、java.lang.Math.Random;调用这个Math.Random()函数能够返回带正号的...
  • Java实现图片对比功能

    千次阅读 2020-12-31 03:03:56
    现在,Java来实现这个函数类似的功能。算法描述:屏幕截图,得到图A,(查找的目标图片为图B);遍历图A的像素点,根据图B的尺寸,得到图B四个角映射到图A上的四个点;得到的四个点与图B的四个角像素点的值比较。如果...
  • java笔记系列》hash函数

    千次阅读 2018-06-07 15:17:47
    hashcode详解 序言 写这篇文章是因为在看hashMap源码时遇到有什么hashcode值,然后就去查,脑袋里面是有映像的,不就是在Object中有equals和hashcode方法嘛,这在学java基础的时候就遇到过,不过那时候无所谓,不...
  • Java 11 对比Java 8 做了哪些改动,哪些优化

    千次阅读 热门讨论 2021-06-11 18:17:20
    文章目录Java 11 对比Java 8 做了哪些改动,哪些优化...,新增了一些常用方法`Process` 函数式编程增强 , Stream 支持截止结算`Process` 文件读写增强 , 支持直接把文件读写为字符串`Process` 可以直接运行 Java
  • java 函数式接口与lambda表达式的关系

    千次阅读 2017-08-27 01:40:39
    java中,lambda表达式与函数式接口是不可分割的,都是结合起来使用的。 对于函数式接口,我们可以理解为只有一个抽象方法的接口,除此之外它和别的接口相比并没有什么特殊的地方。为了确保函数式接口的正确性,...
  • Golang与Java各方面使用对比(上)

    千次阅读 2020-10-04 16:42:54
    本文只对比Golang与Java的基本情况、基本使用、结构体函数及指针三块内容,下一篇文章会对比面向对象、异常处理、并发编程及垃圾回收的差异。
  • 深入对比 Python 与Java 的区别,包括两种语言的发展历程、Hello World、语法特点、执行效率以及 各自的执行机制(PVM与JVM)。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 161,772
精华内容 64,708
关键字:

java 对比函数

java 订阅