Spring Boot集成vavr快速入门demo

1.什么是vavr?

初闻vavr,感觉很奇怪,咋这个名字,后面看到它的官网我沉默了,怀疑初创团队付费资讯了UC震惊部如何取名字,好家伙,vavr就是java这四个字倒过来,真的是’颠覆’了java…..  

tuchuang20211121131800

官网截图

 

tuchuang20211121134906

官网截图倒置处理后

接下来我会介绍vavr的一些简单特性,为了避免成为官方文档的翻译,我会提炼一下加一些demo,不会深入源码细节,重在使用。如果看到现在你还不知道vavr有啥用,这里先简单说一下这个库我觉得三个重要的‘颠覆’:

  1. vavr提供通过增强函数接口(提供比jdk自带更加强大便利的接口)。
  2. 提供众多依赖函数式接口的特性(方法)。
  3. 提供接近于scala的集合库(符合函数式编程特性的不可变集合)。

2.vavr知识点介绍

Function接口的增强

jdk自带的函数式接口上篇介绍了,其实无论是需求还是功能都稍稍有点弱,Function记得吧,Function的抽象方法是apply,它的函数作用是传入一个类型转换成另外一个类型。那如果我想要传入两个不同类型转成第三种类型呢,如果看过java.util.function包以后你肯定会说有BiFunction,那三个呢,四个呢,那是不是要自己扩展了。

Fcuntion(0….8)接口

vavr给我们提供了能扩展更多的函数,例如Function类,就提供Function0到 Function8,也就是最多可接受8个参数的函数。例如下面展示的一个拼接:

  @Test
  public void multiFunctionTest() {
    Function4<String, String, Boolean, Integer, String> func =
        (country, name, isMan, score) -> String.format("%s-%s-%s-%d", country, name, isMan ? "男" : "女", score);
    System.out.println(func.apply("中国", "小明", true, 10));
  }
// 中国-小明-男-10
更多函数式特性

vavr还对函数做了增强,除了jdk也有的andThen()和compose()。vavr的接口还有函数编程的真闭包特性,例如科里化、Lifting、Memoization等,下面一一介绍

Composition

这个jdk其实也自带,其实就是数学中的复合函数概念,f(x)的y可以是g(x)的xg(f(x))。 其中有两个方法都可以完成,一个是andThen(),一个是个compose(), demo一下就知道了

@Test
public void andThenTest() {
  Function4<String, String, Boolean, Integer, String> func1 =
      (country, name, isMan, score) -> String.format("%s-%s-%s-%d", country, name, isMan ? "男" : "女", score);
  // andThen就是把fun1的返回值然后在进行接下来的func2操作
  Function4<String, String, Boolean, Integer, String> func2 = func1
      .andThen(str -> String.join(":", StrUtil.split(str, '-')));
  System.out.println(func2.apply("中国", "小明", true, 10));
}
// 中国:小明:男:10

于此类似还有compose,但是这个方法只有Function1才有,本质其实就是把执行顺序换一下,其实都是做到类似符合函数

@Test
public void composeTest() {
  Function1<Long, String> func1 = num -> num + "%";
  // 先执行分compose里面的apply, 然后把结果放入func1的apply中
  Function1<Double, String> func2 = func1.compose((Double num) -> Math.round(num));
  System.out.println(func2.apply(12.25));
}
// 12%
PartialApply

部分应用是指假如的Fcuntion入参有5个,你apply()中传入了2个,那么编译器不会报错,但是apply也不会正常执行你的函数,而是再生成一个函数,这个函数的入参只有3个,是由原来五个参数其中两个被写固定值转换而来的。show code

@Test
public void partialApplyTest() {
  Function4<String, String, Boolean, Integer, String> func1 =
      (country, name, isMan, score) -> String.format("%s-%s-%s-%d", country, name, isMan ? "男" : "女", score);

  Function3<String, Boolean, Integer, String> func2 = func1.apply("中国");
  System.out.println(func2.apply("小明", true, 10));

  Function2<Boolean, Integer, String> func3 = func1.apply("中国", "小明");
  System.out.println(func3.apply(true, 10));

  Function1<Integer, String> func4 = func1.apply("中国", "小明", true);
  System.out.println(func4.apply(10));

  System.out.println(func1.apply("中国", "小明", true, 10));
}
// 中国-小明-男-10
// 中国-小明-男-10
// 中国-小明-男-10
// 中国-小明-男-10
科里化

科里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。只需要调用func的curried()方法就可以把科里化函数,接下来你每次apply()只能传入一个值,他的返回值还是一个科里化的函数。具体看下面代码。

@Test
public void curriedTest() {
  Function4<String, String, Boolean, Integer, String> func1 =
      (country, name, isMan, score) -> String.format("%s-%s-%s-%d", country, name, isMan ? "男" : "女", score);

  Function1<String, Function1<Boolean, Function1<Integer, String>>> func2 = func1.curried().apply("中国");
  Function1<Boolean, Function1<Integer, String>> func3 = func2.apply("小明");
  Function1<Integer, String> func4 = func3.apply(true);
  String result = func4.apply(10);

  System.out.println(result);
}
// 中国-小明-男-10

这样上面任意一个函数都可以进行扩展,复用率大大提升,调用起来方便。

Memorization

见名知意,就是把一个函数的结果存起来,下次再次调用函数直接返回第一次计算的结果。使用方法是只需要调用接口的memoized()方法即可。 Emmm…实际作用不多,我如果要演示都只能找个随机数来操作,感觉项目事件中的场景不多。

@Test
public void memorizeTest() {
  Function0<Double> hashCache = Function0.of(Math::random).memoized();

  double randomValue1 = hashCache.apply();
  System.out.println(randomValue1);
  double randomValue2 = hashCache.apply();
  System.out.println(randomValue2);

}
// 0.6590067689384973
// 0.6590067689384973

利用函数式接口完成的新特性

模式匹配

好消息好消息,java14已经支持模式匹配了,什么?你们公司还没吃上14,哦,我们公司也没….但是使用vavr可以体验到scala般的模式匹配。Java的switch只能对常量起作用,而且限制非常多,虽然jdk7加入的string,但是居然底层用equals()去比较,这意味着你传入一个null,直接NPE抛出来而不是走到default。而模式匹配不仅仅可以规避种种问题,还可以对另外一个函数的返回值起作用代码函数也能节省不少。

  • 语法基础演示,

Match(里面放入要匹配的变量).of后面就开始进行case匹配,其中$()里面放入匹配预期值,后面放入匹配后需要返回的值。注意模式匹配自动会break,如果$()啥也不写就是类似switch的default

@Test
public void showTest() {
  int input = 2;
  String result = Match(input).of(
      Case($(1), "one"),
      Case($(2), "two"),
      Case($(3), "three"),
      Case($(), "?"));
  System.out.println(result);
}
// two
  • 语法高级匹配演示

$()其实还有一个重载方法,是传入一个predicate函数,vavr有自己的predicate函数式接口,里面有很多方法,例如下面代码块的isIn()就是predicate里面的方法,他的返回值就是一个predicate函数,其作用是可以匹配多个值。

  • $(): 类似于 switch 语句中的 default case 的通配符模式。它处理找不到匹配项的情况。
  • $(value): 这是等值模式,其中一个值只是简单地与输入进行比较。
  • $(predicate): 这是条件模式,其中predicate函数应用于输入,结果布尔值用于做出决定。
@Test
public void isInTest() {
  int input = 1;
  String result = Match(input).of(
      Case($(isIn(0, 1)), "zero or one"),
      Case($(2), "two"),
      Case($(3), "three"),
      Case($(), "?"));
  System.out.println(result);
}
// zero or one
@Test
public void anyOfTest() {
  Integer year = 1990;
  String result = Match(year).of(
      Case($(anyOf(isIn(1990, 1991, 1992), is(1986))), "Age match"),
      Case($(), "No age match"));
  System.out.println(result);
}
// Age match
@Test
public void customTest() {
  int i = 5;
  List<Integer> container = Lists.newArrayList(1, 2, 3, 4);

  String result = Match(i).of(
    // 这里可以换成方法引用, 为了更加好理解,就使用lambda写了
      Case($(e -> container.contains(e)), "Even Single Digit"),
      Case($(), "Out of range"));
  System.out.println(result);
}
// Out of range
  • 副作用展示

看到上面的例子,其实每一个case都返回了一个值,有时候我们匹配到,但是没有东西返回,仅仅通过副作用来dosomething。下面代码看起来比较绕,我稍稍解释一下,因为Case的第二个参数我们最开始是放返回值,现在如果要使用副作用必须放一个Supplier,别问我为什么这是人家要求的,所以必须使用() ->,那返回什么呢,这里放入run()的方法,run()方法的入参是一个Runnable,出参是一个Void,那么这个Void是可以忽略掉,注意这个Void是vavr提供的,不是jdk的关键字void。只需要把副作用的代码放入构建一个Runnable接口就可以啦。Runnable就是lang包下的Runnable,这个不用我多说吧。

@Test
public void sideEffectsTest() {
  int i = 4;
  Match(i).of(
      Case($(isIn(2, 4, 6, 8)), () -> run(() -> System.out.println("这是第一类"))),
      Case($(isIn(1, 3, 5, 7, 9)), () -> run(() -> System.out.println("这是第二类"))),
      Case($(), o -> run(() -> System.out.println("没有找到"))));
}

Try

Try类似于jdk的try catch。在Try中执行的代码不会抛异常,异常和正常返回值都会被vavr接管,然后通过Try的在进行返回。具体用法就是Try.of()。 然后of里面传入一个supplier,入参固定是() ->,返回值就是你的函数产生的结果。

  • 基本演示
@Test
public void tryTest() {
  Try<Integer> result = Try.of(() -> 1 / 0);
  // 返回是否失败
  System.out.println(result.isSuccess());
  // 返回异常原因, 如果没有异常进行获取则会UOE
  System.out.println(result.getCause());
  // 获取返回值, 如果有异常则返回null
  System.out.println(result.getOrNull());
  // 获取返回值, 如果有异常则返回设置的默认值
  System.out.println(result.getOrElse(0));
}
// false
// java.lang.ArithmeticException: / by zero
// null
// 0

里面其实还自带了很多方法,有点类似jdk的optional,也是类似于一个’‘容器’‘,只不过它容纳的是可能出错的行为,可以让你进行接下的处理或者兜底方案。一般简单处理我会使用Try,因为真的很方便。 例如原来在JSON.parseObject()的时候我一般都会用try catch包一下,希望能够健壮一点,鬼知道上游传过来的是什么串,但是try catch写的挺难看的,如果使用Try包一下就看起来舒服一些。

  @Test
  public void trySeniorTest() {
    List<Integer> list = Try.of(() -> JSON.parseArray("json", Integer.class))
        .getOrElse(Collections.emptyList());
    System.out.println(list);
  }
// []

不可变的集合类

Tuple

众所周知,java是没有元祖的,但是有时候元祖是真的好用,vavr通过泛型实现了元祖,可以使用Tuple的静态工厂创建元祖,并且使用idea的自动推断或者java10的var类型推断直接效率高到爆有没有。使用方法也和scala差不多,

元祖(Tuple)由不同元素组成,每个元素可以储存不同类型的数据。有点像多个泛型的List,例如List这个list就只能放Integer, 元祖是Tuple<Integer, String>这就表示里面可以放Integer和String,但是往往是需要指定数量的,因为需要指定那个位置的元素是哪个类型。
  • 基础使用

通过Tuple.of可以初始化,你只需要在of里面放入元素,idea会自动帮你推断出Tuple几,然后你使用元素只需要_几号就可以了,例如1号元素就是_1

@Test
public void tupleTest() {
  Tuple2<Integer, String> t2 = Tuple.of(1, "1");
  System.out.println(t2._1);
  System.out.println(t2._2);
}
  • 其他使用

Tuple是不可变的,你可以对它进行修改或者添加,但是进行更改操作它都会返回一个新的元祖。更改很简单调用update加位置()方法,增加也很简单调用append()方法

@Test
public void tupleSeniorTest() {
  Tuple2<Integer, String> t2 = Tuple.of(1, "1");
  System.out.println(t2);

  Tuple2<Integer, String> t2s = t2.update1(2);
  System.out.println(t2s);
  
  Tuple3<Integer, String, Double> t3 = t2.append(1.0);
  System.out.println(t3);
}

我很喜欢元祖,因为有时候我很懒,不想干啥都创建一个创建pojo,更不想map到处乱飞,元祖用起来方便也很清晰明了,是两者的权衡,尤其是配合匹配模式使用优雅度直接起飞。但是!!!请注意,vavr的Tuple不支持jackson和json的序列化,这个坑我已经替大家踩过了,http返回值或者是rpc通信时请不要使用。

List/Set/Map

函数式编程很重要一个特性就是不变性,jdk的Collections可以让一个集合类成为不可变,但是….show code

@Test
public void collectionsTest() {
  List<Integer> list = Lists.newArrayList(1, 2, 3);
  System.out.println(list);
  List<Integer> unmodifiableList = Collections.unmodifiableList(list);
  System.out.println(unmodifiableList);

  list.add(1);
  System.out.println(list);
  System.out.println(unmodifiableList);

  unmodifiableList.add(1);
}
// [1, 2, 3]
// [1, 2, 3]
// [1, 2, 3, 1]
// [1, 2, 3, 1]
// 
// java.lang.UnsupportedOperationException

上面代码可以看到,Collections不可变的list是原来list的一个浅拷贝,原来的list进行元素的更改依然会改动这个所谓的’不可变’list。

  • vavr的list

vavr的list使用List.of()来创建,创建后不可变,但是可以增加或者删除元素,聪明的你肯定知道了,每次改动以后都会生成一个新的不可变list。

@Test
public void collectionsTest() {
  io.vavr.collection.List<Integer> list = io.vavr.collection.List.of(1, 2);
  // 增加一个元素
  io.vavr.collection.List<Integer> appendList = list.append(3);
  // 丢掉一个元素
  io.vavr.collection.List<Integer> dropList = list.drop(1);
  
  // 变成java的可变list
  List<Integer> javaList = list.asJava();
  
}

另外vavr的list可以直接使用stream算子,不许用在通过stream()转换成Stream流,然后再使用算子,不能说和scala一模一样,只能说毫无区别。类似的也提供提供了更多 Functional 的 API,比如

  • take(Integer) 取前 n 个值
  • tail() 取除了头结点外的集合
  • zipWithIndex() 使得便利时可以拿到索引(不用 fori)
  • find(Predicate) 基于条件查询值,在 Java 标准库得使用 filter + findFirst 才能实现 …..

其他函数式编程特性

Option

不装了,我摊牌,这个option和jdk的optional是一样的,应该灵感都是来自于guava的Optional。不过vavr的Otion是一个接口,它有两个实现类,分别是Some和None。前者有值的状态,后者无值的状态。食用方法是Option.of()

@Test
public void multiFunctionTest() {
  Integer num = null;
  Option<Integer> opt = Option.of(num);

  // 这个和optional一样
  Integer result = opt.getOrElse(0);
  System.out.println(result);

  // 如果是None则会返回ture
  boolean isEmpty = opt.isEmpty();
  System.out.println(isEmpty);

  // 变成java的optional
  Optional<Integer> optional = opt.toJavaOptional();
}
// 0
// true

因为很多方法所以都没放,大部分方法都是和optional一样的,还有一些是vavr通用的,并不是option独有。

Lazy

延迟计算也是函数式编程里面一个特性,尤其是在scala中用的很多,并且第一次计算后会把值进行缓存。对节省内存和提升性能都有很大的帮助。 scala中是通过关键字来做的,但是vavr在java中怎么做到呢。类似于option一样的把变量装载一个‘’容器‘’中,取值加载。

@Test
public void lazyTest() {
  // 生成一个随机数给到lazy容器
  Lazy<Double> lazy = Lazy.of(Math::random);

  // 判断是否已经获取过了
  System.out.println(lazy.isEvaluated());

  // 正式获取lazy的值
  System.out.println(lazy.get());

  // 看看现在是否计算了
  System.out.println(lazy.isEvaluated());

  // 再次获取lazy的值
  System.out.println(lazy.get());
}
// false
// 0.896267693320266
// true
// 0.896267693320266

当然如果是真的感兴趣,推荐大家看一下resilience4j,这是用vavr写的限流熔断降级中间件,用来代替Hystrix。代码质量真的是非常高,用它来学习函数式编程我认为是目前最好的材料,唯独就是比较难啃,因为函数编程本身就是写起来很爽但是对viewer不是很友好。

3.代码工程

实验目标

使用vavr来编写查询github用户信息的接口

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springboot-demo</artifactId>
        <groupId>com.et</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>vavr</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--vavr-->
        <dependency>
            <groupId>io.vavr</groupId>
            <artifactId>vavr</artifactId>
            <version>0.10.4</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.20</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>
</project>

controller

   

package com.et.vavr.controller;

import com.et.vavr.domain.User;
import com.et.vavr.service.GithubService;

import io.vavr.control.Try;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 *
 * @author armena
 */
@RestController
@RequestMapping("/api/v1/github")
public class GithubController {

    @Autowired
    private GithubService githubService;

    @GetMapping(path = "/{username}", produces = "application/json;charset=UTF-8")
    public ResponseEntity<?> get(@Valid @PathVariable String username
    ) {
        Try<User> githubUserProfile = githubService.findGithubUser(username);

        if (githubUserProfile.isFailure()) {
            return ResponseEntity.status(HttpStatus.FAILED_DEPENDENCY).body(githubUserProfile.getCause().getMessage());
        }

        if (githubUserProfile.isEmpty()) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Response is empty");
        }

        if (githubUserProfile.isSuccess()) {
            return ResponseEntity.status(HttpStatus.OK).body(githubUserProfile.get());
        }

        return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body("username is not valid");

    }

    @GetMapping(path = "/fail/{username}", produces = "application/json;charset=UTF-8")
    public ResponseEntity<?> getFail(@Valid @PathVariable String username
    ) {
        Try<User> githubUserProfile = githubService.findGithubUserAndFail(username);

        if (githubUserProfile.isFailure()) {
            System.out.println("Fail case");
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(githubUserProfile.getCause().getMessage());
        }

        if (githubUserProfile.isEmpty()) {
            System.out.println("Empty case");
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Response is empty");
        }

        if (githubUserProfile.isSuccess()) {
            System.out.println("Success case");
            return ResponseEntity.status(HttpStatus.OK).body(githubUserProfile.get());
        }

        return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body("username is not valid");

    }

}

service

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.et.vavr.service;

import com.et.vavr.domain.User;
import io.vavr.control.Try;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 *
 * @author Admin
 */
@Service
public class GithubService {

    @Autowired
    private RestTemplate restTemplate;

    public Try<User> findGithubUser(String username) {
        return Try.of(() -> restTemplate.getForObject("https://api.github.com/users/{username}", User.class, username));

    }

    public Try<User> findGithubUserAndFail(String username) {
        return Try.of(() -> restTemplate.getForObject("https://api.twitter.com/users/fail/{username}", User.class, username));
    }

}

entity

package com.et.vavr.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;


@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class User {
        
    private String id;
    private String login;
    private String location;

   
}

config

package com.et.vavr.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class AppConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

  • GitHub - Harries/springboot-demo: a simple springboot demo with some components for example: redis,solr,rockmq and so on.

4.测试

启动Spring Boot应用

测试查询接口

  • 访问http://127.0.0.1:8088/api/v1/github/{username}
  • 返回相应的用户信息

5.引用

  • https://www.vavr.io/
  • java8函数式编程-下 - Oreoft's blog

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/752255.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

音视频基础

音视频基础 一、音视频录制原理二、音视频播放原理三、图像表示RGB-YUVV1.图像基础概念1.1 像素1.2 分辨率1.3 位深1.4 帧率1.5 码率1.6 Stride跨距 2.RGB、YUV深入讲解2.1 RGB2.2 YUV2.2.1 YUV采样表示法2.2.2 YUV数据存储 2.3 RGB和YUV的转换(了解)为什么解码出错显示绿屏&am…

百问网全志D1h开发板投屏功能实现

投屏功能实现 D1系列号称点屏神器&#xff0c;不仅能点屏&#xff0c;还能用于投屏。 源码准备 百问网为 【百问网D1h开发板】提供了投屏功能需要使用的源码&#xff0c;直接git下载即可&#xff1a; git clone https://github.com/DongshanPI/DongshannezhaSTU_DLNA_Scree…

嵌入式Linux系统编程 — 4.3 strcat、strcpy函数拼接与复制字符串

目录 1 字符串拼接 1.1 strcat函数 1.2 strncat函数 1.3 示例程序 2 字符串复制 2.1 strcpy函数 2.2 strncpy函数 2.3 示例程序 1 字符串拼接 1.1 strcat函数 在Linux系统中&#xff0c;strcat 函数是C语言标准库中的一个函数&#xff0c;用于将一个字符串追加到另一…

MySQL内存使用率高且不释放问题排查与总结

背景 生产环境mysql 5.7内存占用超过90%以上&#xff0c;且一直下不来。截图如下&#xff1a; 原因分析 1、确定mysql具体的占用内存大小&#xff0c;通过命令&#xff1a;cat /proc/Mysql进程ID/status查看 命令执行后的结果比较多&#xff08;其他参数的含义想了解可参考这…

【Python】已解决:(SqlServer报错)SQL错误(208):对象名‘string_split’无效

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;&#xff08;SqlServer报错&#xff09;SQL错误&#xff08;208&#xff09;&#xff1a;对象名‘string_split’无效 一、分析问题背景 在使用Python连接SqlSe…

泰迪智能科技携手广州番禺职业技术学院共建上进双创工作室

为充分发挥校企双方的优势&#xff0c;促进产教融合&#xff0c;发挥职业教育为社会、行业、企业服务的作用&#xff0c;为企业培养更多高素质、高技能的应用型人才的同时也为学生实习、就业提供更大空间。6月26日&#xff0c;“泰迪广州番禺职业技术学院上进双创工作室签约授牌…

体验过可道云teamOS这些人性化设置,就再也回不去了

公司出于对文件安全的看重&#xff0c;让数据不落地&#xff0c;更安全&#xff0c;使用企业网盘办公已经是我们的办公常态了。 作为一款每天都要使用的软件&#xff0c;良好的使用体验是非常重要的。 今天就来谈一谈在使用可道云teamOS过程中&#xff0c;它那些令用户好感上升…

高中数学:复数-基础概念及运算法则

一、定义 规定 复数集与实数集之间的关系 二、复数的几何意义 第一种几何意义 第二种几何意义 复数向量的模 共轭复数 三、四则运算 加法 复向量加法 减法 两复数的距离 乘法 除法 四、总结 复数的所有运算法则和实数相同。 向量运算和实数向量运算相同。 怎么简便记忆了&a…

3D Wizard(巫师法师人物角色模型)

3D Wizard低多边形模型,可用于RPG射击游戏和其他项目。角色顶点数:44154 无HDRP 仅默认着色器 顶点数:44154 装配有人形骷髅。 下载:​​Unity资源商店链接资源下载链接 效果图:

企业“出海”动向 | 香港电讯助力企业勇闯东南亚市场

众所周知&#xff0c;随着中国人口红利及经营成本的上升&#xff0c;越来越多跨国企业的制造工厂往东南亚、印度等国家迁移。而国内的一些中低端制造业也由于地缘优势&#xff0c;纷纷在东南亚地区建立“代工厂”。 近几年&#xff0c;接过 “世界代工厂”重任的东南亚逐步向数…

C#使用MJpeg实现视频流发送与显示

1、发送视频流&#xff1a; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.IO; using System.Linq; using System.Net.Sockets; using System.Text; using System.T…

企业级数据库18条黄金设计原则(全网最详细!!!)

前言 1.名字 2.字段类型 3.字段长度 4.字段个数 5. 主键 6.存储引擎 7. NOT NULL 8.外键(不建议使用外健) 9. 索引 10.时间字段 11.金额字段 12. json字段 13.唯一索引 14.字符集 15. 排序规则 16.大字段 17.冗余字段 18.注释 前言 后端开发的日常工作&#…

超高速PCIe EtherCAT运动控制卡在高速异形插件机上的应用

市场应用背景 异形插件机主要应用于电子制造业中&#xff0c;专门用于自动化电子装配过程。它能够按照预设程序将编带或者供料器的电子元件通过多工位多头夹取&#xff0c;将电阻、电容、晶体管、连接器和按键等异形元件自动插装到PCB板的指定通孔上&#xff0c;极大地提高生产…

【PL理论深化】(9) Ocaml 语言:自定义类型 | 异常处理 | 模块

&#x1f4ac; 写在前面&#xff1a;本章我们将继续介绍 OCaml 的基本特性&#xff0c;自定义类型、异常处理和模块。掌握了这些内容后&#xff0c;编写基本程序应该不会有太大困难。接下来的两节将学习函数式编程中常用的两种编程风格 —— 递归函数和高阶函数。 目录 0x00 …

基于盲信号处理的声音分离——最小化增益的ICA算法

基于最小化增益的ICA算法的算法是依据混合信号经过盲信号分离会产生一定的噪声&#xff0c;为了使得分离后的信号与原信号的比值最小时&#xff0c;叫做增益最小。当增益越小时&#xff0c;分离后噪声越小&#xff0c;分离信号越接近原信号&#xff0c;分离算法的效果越好。这是…

防止多次点击,vue的按钮上做简易的防抖节流处理

话不多说,上个视频,看看是不是你要的效果 防抖节流 1.创建一个directive.js // directive.js export default {install(Vue) {// 防重复点击(指令实现)Vue.directive(repeatClick, {inserted(el, binding) {el.addEventListener(click, () > {if (!el.disabled) {el.disabl…

【Stable Diffusion】AI绘画美女,搞副业,赚钱真香!(内附高质量美女提示词)

前言 今天就直接上干货&#xff0c;给铁子们上一些生成高质量美女的提示词&#xff0c;每一种美女类型都附有魔法咒语&#xff0c;可应用于midjourney和stablediffusion&#xff0c;直接复制即可。 话不多说&#xff0c;直接上图&#xff0c;上提示词&#xff0c;请欣赏&…

三大工作流引擎技术Activiti、Flowable、Camunda选型指南

文章目录 前言1 流程引擎发展历程2 流程引擎主要概念BPM (Business Process Management)BPMN (Business Process Model and Notation)CMMN (Case Management Model and Notation)DMN (Decision Model and Notation)事件&#xff08;Event&#xff09;顺序流&#xff08;Sequenc…

控制台扫雷(C语言实现)

目录 博文目的实现思路项目创建文件解释 具体实现判断玩家进行游戏还是退出扫雷棋盘的确定地图初始化埋雷玩家扫雷的实现雷判断函数 源码game.cgame.h扫雷.c 博文目的 相信不少人都学习了c语言的函数&#xff0c;循环&#xff0c;分支那我们就可以写一个控制台的扫雷小游戏来检…

【AI落地应用实战】如何让扫描工具更会思考——智能高清滤镜2.0实战测评

一、引言 在这个信息爆炸的数字化时代&#xff0c;扫描工具已经成为我们日常工作和学习中不可或缺的助手。最近&#xff0c;扫描全能王推出了革命性的“智能高清滤镜2.0”&#xff0c;本次更新后&#xff0c;智能高清滤镜能够智能识别并优化扫描过程中的各种问题。无论是光线不…