初识Java 11-1 函数式编程
目录
旧方式与新方式
lambda表达式
方法引用
Runnable
未绑定方法引用
构造器方法引用
函数式接口
带有更多参数的函数式接口
解决缺乏基本类型函数式接口的问题
本笔记参考自: 《On Java 中文版》
函数式编程语言的一个特点就是其处理代码片段的简易性,就像处理数据一样简单。Java 8加入的lambda表达式和方法引用为函数式风格编程做出了一定的支持。
在计算机的早期时代,为了让程序能够适应有限的内存,程序员往往需要在程序执行时修改内存中的代码,让程序做出不同的行为,依此节省空间。这就是自修改代码技术。因为彼时的程序大都足够小,因此维护起来并不会太麻烦。
但随着内存的增大,自修改代码被认为是一个糟糕的想法,它极大地增加了程序的维护成本。尽管如此,这种使用代码以某种方式操纵其他代码的想法依旧十分吸引人:通过组合已经经过良好测试的代码,我们可以生产出更有效率、更加安全的代码。
函数式编程的意义就在于此:通过整合现有代码来产生新的功能,而不是从零开始编写所有内容,由此可以得到更加可靠、实现起来更快的代码。
面向对象编程抽象数据,而函数式编程抽象行为。
纯函数式语言在安全方面规定了额外的约束条件,所有的数据必须是不可变的:设置一次,永不改变。此时函数绝对不会修改现有值,而是只生成新值。(纯函数式语言在面对一些问题时能够提出一个好的解决,但这不代表纯函数式语言就是最好的解决方式)
Python等非函数式编程语言已经将函数式编程的概念纳入其中,并且受益匪浅。Java也加入了类似的特性。
旧方式与新方式
通过将代码传递给方法,我们可以控制方法,使其产生出不同的行为。
旧的方式是创建一个对象(在下例中是Strategy),让其的某个方法包含所需行为,在将这个对象传递给我们想要控制的方法:
package functional;import java.util.Locale;interface Strategy {String approch(String msg);
}class Soft implements Strategy {@Overridepublic String approch(String msg) {return msg.toLowerCase() + "?";}
}class Unrelated {static String twice(String msg) {return msg + " " + msg;}
}public class Strategize {Strategy strategy;String msg;Strategize(String msg) {strategy = new Soft(); // 将Soft()作为一个默认的决策this.msg = msg;}void communicate() {System.out.println(strategy.approch(msg));}void changeStrategy(Strategy strategy) {this.strategy = strategy;}public static void main(String[] args) {Strategy[] strategies = {new Strategy() { // 创建一个匿名内部类来改变行为,虽然依旧会有重复的代码@Overridepublic String approch(String msg) {return msg.toUpperCase() + "!";}},msg -> msg.substring(0, 5), // 这就是Java 8开始提供的lambda表达式Unrelated::twice // 这也是Java 8中出现的方法引用};Strategize s = new Strategize("Hello there");s.communicate();for (Strategy newStrategy : strategies) {s.changeStrategy(newStrategy); // 遍历数组strategies中的每一个决策,并将其放入s中进行决策更换s.communicate(); // 更换决策后,每一次输出都会产生不同的结果:我们传递了行为,而被仅仅是数据}}
}
程序执行的结果是:

Strategy提供的接口包含了唯一的approach()方法。通过创建不同的Strategy对象,就可以创建不同的行为。
在上述程序中,包含了默认的决策Soft()和一个匿名内部类。除此之外,还出现两个了Java 8添加的新内容:
- lambda表达式:
msg -> msg.substring(0, 5)这种表达式的特点是使用箭头->分隔参数和函数体。
- 方法引用:
Unrelated::twice特点是::。其中,::左边是类名或对象名,右边是方法名,但没有参数列表。
在Java 8之前,使用普通的类或者匿名内部类来传递功能,但这种语法的并不方便。lambda表达式和方法引用改变了这种情况,使得传递功能变得更加便捷。
lambda表达式
||| lambda表达式是使用尽可能少的语法编写的函数定义。
换言之,lambda表达式产生的是函数,而不是方法。当然,Java中的一切都是类,之所以lambda表达式会让人产生这种“错觉”,是因为幕后进行了各种各样的操作。作为程序员,我们可以将lambda表达式视为函数。
lambda表达式的语法宽松,且易于编写。例如:
package functional;interface Description {String brief();
}interface Body {String detailed(String head);
}interface Multi {String twoArg(String head, Double d);
}public class LambdaExpressions {static Body bod = h -> h + "No Parens!"; // 本条语句并不需要使用括号(仅限只有一个参数时)static Body bod2 = (h) -> h + "More details"; // 使用了括号。处于一致性的考虑,在只有一个参数时也使用括号static Description desc = () -> "Short info"; // 若没有参数,必须使用括号来指示空的参数列表static Multi mult = (h, n) -> h + n; // 有多个参数,此时必须将它们放在使用括号包裹的参数列表中static Description moreLines = () -> {System.out.println("moreLines()");return "from moreLines()";};public static void main(String[] args) {System.out.println(bod.detailed("Oh!"));System.out.println(bod2.detailed("Hi!"));System.out.println(desc.brief());System.out.println(mult.twoArg("Pi: ", 3.14159));System.out.println(moreLines.brief());}
}
程序执行的结果是:

在上述的3个接口中,每个接口都有一个方法(这是后续会提到的函数式接口)。任何lambda表达式的基本语法如下:

注释中提到过,若没有参数,就必须使用括号来指示空的参数列表。
对一行的lambda表达式而言,方法体中表达式的结果会自动成为lambda表达式的返回值,所以这里使用return关键字是不和法的。另外,若lambda表达式需要多行代码,如上文中的moreLines,就需要将表达式的代码放入到花括号中。此时又会需要使用return从lambda表达式中生成一个值了。
可以看到,lambda表达式可以通过接口更方便地生成行为不同的对象。
递归
递归,即函数调用了自身。Java也允许编写递归的lambda表达式,但需要注意一点:这个lambda表达式必须被赋值给一个静态变量或一个实例变量。通过两个示例说明这些情况:
两个示例会使用一个相同的接口:
interface IntCall {int call(int arg);
}
【示例:静态变量】实现一个阶乘函数,递归计算小于等于n的正整数的乘积:
public class RecursiveFactorial {static IntCall fact;public static void main(String[] args) {fact = n -> n == 0 ? 1 : n * fact.call(n - 1);for (int i = 0; i <= 10; i++)System.out.println(fact.call(i));}
}
程序执行的结果是:

在这个例子中,fact就是一个静态变量。递归函数会不断调用其自身,因此必须有某种停止条件(在上述例子中是n == 0),否则就会陷入无限递归,直到栈空间被耗尽。
下方这种初始化fact的方式是不被允许的:
static IntCall fact = n -> n == 0 ? 1 : n * fact.call(n - 1);
这种处理对Java编译器而言还是太过复杂了,会导致编译错误。
---
【示例:示例变量】实现斐波那契数列:
public class RecursiveFibonacci {IntCall fib;RecursiveFibonacci() {fib = n -> n == 0 ? 0 :n == 1 ? 1 :fib.call(n - 1) + fib.call(n - 2);}int fibonacci(int n) {return fib.call(n);}public static void main(String[] args) {RecursiveFibonacci rf = new RecursiveFibonacci();for (int i = 0; i <= 10; i++)System.out.println(rf.fibonacci(i));}
}
程序执行的结果是:

方法引用
Java 8提供的方法引用,其指向的是方法。方法引用的格式如下:
![]()
interface Callable {void call(String s); // 与hello()和show()的签名了保持一致
}class Describe {void show(String msg) {System.out.println(msg);}
}public class MethodReferences {static void hello(String name) {System.out.println("Hello, " + name);}static class Description { // 定义一个内部类String about;Description(String desc) {about = desc;}void help(String msg) {System.out.println(about + " " + msg);}}static class Helper {static void assist(String msg) { // assist()是静态内部类中的一个静态方法System.out.println(msg);}}public static void main(String[] args) {Describe d = new Describe();Callable c = d::show; // 将Describe对象的show方法赋给了Callablec.call("call()"); // 通过call(),调用了show()c = MethodReferences::hello; // 等号右边是一个静态方法引用c.call("Bob");c = new Description("valuable")::help; // 对某个活跃对象上的方法的方法引用(“绑定方法引用”)c.call("information");c = Helper::assist; // 获得静态内部类中的静态方法的方法引用c.call("Help!");}
}
程序执行的结果是:

在上述程序中,Callable.call()、Describe.show()和MethodReferences.hello(),这三者的签名保持了一致。这解释了为什么语句 Callable c = d::show; 及其之后的语句能够顺利编译。
Runnable
Runnable是一个java.lang包提供的接口。这个包遵循特殊的单方法接口格式:它的run()方法没有参数,也没有返回值。

因此,可以将lambda表达式或方法引用用作Runnable:
class Go {static void go() {System.out.println("方法引用Go::go()");}
}public class RunnableMethodReference {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("定义一个run()方法");}}).start();new Thread(() -> System.out.println("这是一个lambda表达式")).start();new Thread(Go::go).start();}
}
程序执行的结果是:

Thread类在官方文档中的描述如下:

Thread会接受一个Runnable作为其构造器参数,它的start()方法会调用run()。
只有匿名内部类需要提供run()方法。
未绑定方法引用
未绑定方法引用:指的是尚未关联到某个对象的普通(非静态)方法。对于未绑定引用,必须先提供对象,然后才能使用:
class X {String f() {return "X::f()";}
}interface MakeString {String make();
}interface TransformX {String transform(X x);
}public class UnboundMethodReference {public static void main(String[] args) {
// MakeString ms = X::f; // 无效的方法引用TransformX sp = X::f;X x = new X();// 下列两条语句的效果是相同的System.out.println(sp.transform(x));System.out.println(x.f());}
}
程序执行的结果是:
![]()
在上述例子之前,示例中对方法的引用,方法与其关联接口的签名是相同的。但这里出现了特例:
MakeString ms = X::f;
编译器不允许上述语句的编译,若强制执行,会引发报错(此为IDEA的报错信息):
![]()
这个报错指出X::f是一个未绑定方法引用,因为这里涉及到了一个隐藏的参数:this。若把这条有问题的语句换成下列语句,则没有问题:
X x = new X();
MakeString ms = x::f; // 无效的方法引用
上下两种语句的区别就在于,下方的语句提供了一个可供附着的X的对象x,这使得调动f()变为可能。X::f本身是无法“绑定到”一个对象上的。
显而易见,除了自己生成一个对象外,我们还有另一个方式能解决这个问题。关键在于,我们还需要一个额外的参数,如TransformX中所示:
String transform(X x);
这种做法告诉我们:函数式方法(接口中的单一方法)的签名与方法引用的签名不必完全匹配。
最后在看看这条语句:
System.out.println(sp.transform(x));
在前述知识的基础上,可以推断这条语句执行的过程:println()接受了一个未绑定引用,x作为参数在这个引用中调用了transform(),最终调用了x.f()。
若一个方法具有多个参数,则只需要让第一个参数使用这种this的模式即可:
class This {void two(int i, double d) {}void three(int i, double d, String s) {}void four(int i, double d, String s, char c) {}
}interface TwoArgs {void call2(This athis, int i, double d);
}interface ThreeArgs {void call3(This athis, int i, double d, String s);
}interface FourArgs {void call4(This athis, int i, double d, String s, char c);
}public class MultiUnbound {public static void main(String[] args) {TwoArgs twoargs = This::two;ThreeArgs threeargs = This::three;FourArgs fourargs = This::four;This athis = new This();twoargs.call2(athis, 11, 2.14);threeargs.call3(athis, 11, 3.14, "Three");fourargs.call4(athis, 11, 3.14, "Four", 'Z');}
}
构造器方法引用
同样的,也可以对构造器的引用进行捕获,此后通过这个引用来调用构造器:
class Dog {String name;int age = -1;Dog() {name = "流浪狗";}Dog(String nm) {name = nm;}Dog(String nm, int yrs) {name = nm;age = yrs;}
}interface MakeNoArgs {Dog make();
}interface Make1Arg {Dog make(String nm);
}interface Make2Args {Dog make(String nm, int age);
}public class CtorReference {public static void main(String[] args) {// 所有这3个构造器都只有一个名字 ::newMakeNoArgs mna = Dog::new;Make1Arg m1a = Dog::new;Make2Args m2a = Dog::new;Dog dn = mna.make();Dog d1 = m1a.make("卡卡");Dog d2 = m2a.make("拉尔夫", 4);}
}
注意语句Dog::new。3条相同的语句告诉我们,这些构造器都有(且只有)一个名字 —— ::new。并且每一个引用都被赋予了不同的接口,编译器可以从接口来推断所需使用的构造器。
在这里,调用函数式接口方法(make())意味着调用构造器。
函数式接口
方法引用和lambda表达式都需要先赋值,然后才能进行使用。而这些赋值都需要类型信息,让编译器确保类型的正确性。尤其是lambda表达式。例如:
x -> x.toString()
toString()方法会返回String,但上述语句并没有表示x的类型。这时候就需要进行类型推断了。因此,编译器必须要能够通过某种方式推断出x的类型。
还有其他例子:
(x, y) -> x + y // 需要考虑String类型存在与否
System.out::println
为了解决这种类型推断的问题,Java 8引入了包含一组接口的java.util.function,这些接口是lambda表达式和方法引用的目标类型。其中的每个接口都只包含了一个抽象方法(非抽象方法可以有多个),被称为函数式方法。
使用了这种”函数式方法“模式的接口,可以通过@FunctionalInterface注解来强制执行:
@FunctionalInterface
interface Functional { // 使用了注解String goodbye(String arg);
}interface FunctionNoAnn { // 没有使用注解String goodbye(String arg);
}//@FunctionalInterface
//interface NoFunctional{ // 内置了两个方法,不符合函数式方法定义
// String goodbye(String arg);
// String hello(String arg);
//}public class FunctionalAnnotation {public String goodbye(String arg) {return "Goodbye, " + arg;}public static void main(String[] args) {FunctionalAnnotation fa = new FunctionalAnnotation();Functional f = fa::goodbye;FunctionNoAnn fna = fa::goodbye;
// Functional fac = fa; // 类型不兼容Functional f1 = arg -> "Goodbye, " + arg;FunctionNoAnn fnal = arg -> "Goodbye, " + arg;}
}
@FunctionalInterface注解是可选的。当只有一个方法时,Java把main()中的Functional和FunctionalNoAnn都视为了函数式接口。
现在看向两条赋值语句:
Functional f = fa::goodbye;
FunctionNoAnn fna = fa::goodbye;
这两条赋值语句均把一个方法(这个方法甚至不是接口方法的实现)赋值给了一个接口引用。这是Java 8增加的功能:若把一个方法引用或lambda表达式赋值给某个函数式接口(且类型匹配),匿名Java会调整这次赋值,使其能够匹配目标接口。
在底层的实现中,Java编译器会创建一个实现了目标接口的类的示例,并将我们进行赋值的方法引用或lambda表达式包裹在其中。
使用了@FunctionalInterface注解的接口也叫做单一抽象方法。
命名规则
java.util.function旨在创建一套足够完备的接口。一般来说,可以通过接口的名字了解接口的作用。以下是基本的命名规则(也可以去官方文档进行查看):
- 只处理对象(而不是基本类型):名字较为直接,如Function、Consumer和Predicate等。
- 接受一个基本类型的参数:使用名字的第一部分表示,如LongConsumer、DoubleFunction和InPredicate等(例外:基本的Supplier类型)。
- 返回的是基本类型的结果:用To表示,例如ToLongFunction<T>和IntToLongFunction。
- 返回类型和参数类型相同:被命名为Operator。UnaryOperator表示一个参数,BinaryOperator表示两个参数。
- 接受一个参数并返回boolean:被命名为Predicate。
- 接受两个不同类型的参数:名字中会有一个Bi(比如BiPredicate)。
因为基本类型的存在,Java在设计这些接口时不得不考虑众多的类型,这无疑增加了Java的复杂性。
例如:
import java.util.function.*;class Foo {
}class Bar {Foo f;Bar(Foo f) {this.f = f;}
}class IBaz {int i;IBaz(int i) {this.i = i;}
}class LBaz {long l;LBaz(long l) {this.l = l;}
}class DBaz {double d;DBaz(double d) {this.d = d;}
}public class FunctionVariants {static Function<Foo, Bar> f1 = f -> new Bar(f);static IntFunction<IBaz> f2 = i -> new IBaz(i);static LongFunction<LBaz> f3 = l -> new LBaz(l);static ToLongFunction<LBaz> f4 = lb -> lb.l;static DoubleToIntFunction f5 = d -> (int) d;public static void main(String[] args) {Bar b = f1.apply(new Foo());IBaz ib = f2.apply(11);LBaz lb = f3.apply(12);long l = f4.applyAsLong(lb);int i = f5.applyAsInt(14);}
}
在一些情况下,需要使用类型转换,否则编译器会报出截断错误。上述程序中的每个方法都会调用其关联的lambda表达式。
方法引用还有一些特别的用法:
import java.util.function.BiConsumer;class In1 {
}class In2 {
}public class MethodConversion {static void accept(In1 i1, In2 i2) {System.out.println("accept()");}static void someOtherName(In1 i1, In2 i2) {System.out.println("somwOtherName()");}public static void main(String[] args) {BiConsumer<In1, In2> bic;bic = MethodConversion::accept;bic.accept(new In1(), new In2());bic = MethodConversion::someOtherName;
// bic.someOtherName(new In1(), new In2); //行不通bic.accept(new In1(), new In2());}
}
程序执行的结果是:
![]()
以下是BitConSumer的文档说明:

这个接口有一个accept()方法可以被用作方法引用。并且,即使名字并不相同,如someOtherName(),只要参数类型和返回类型能够与BiConsumer的accept()相同,也没有问题。
使用函数式接口时,名字不重要,重要的是参数类型和返回类型。
Java会负责将我们起的名字映射到函数式方法上。若要调用我们的方法,就需要调用这个函数式方法的名字。
带有更多参数的函数式接口
java.util.function中的接口是有限的,同时也是直观易懂的。因此,当我们需要的接口并没有在java.util.function中被提供时,我们也可以轻松地编写自己的接口:
@FunctionalInterface
public interface TriFunction<T, U, V, R> {R apply(T t, U u, V v);
}
现在这个接口就可以被使用了:
public class TriFunctionTest {static int f(int i, long l, double d) {return 99;}public static void main(String[] args) {TriFunction<Integer, Long, Double, Integer> tf = TriFunctionTest::f;tf = (i, l, d) -> (i + l.intValue() + d.intValue());System.out.println(tf.apply(12, 12l, 12d));}
}
程序执行成功,输出36。
解决缺乏基本类型函数式接口的问题
可以通过使用BiConsumer这种面向对象的接口,开创建java.util.function中没有提供的,涉及int等基本类型的函数式接口:
import java.util.function.BiConsumer;public class BiConsumerPermutations {static BiConsumer<Integer, Double> bicid = (i, d) -> System.out.format("%d, %f%n", i, d);static BiConsumer<Double, Integer> bicdi = (d, i) -> System.out.format("%f, %d%n", d, i);static BiConsumer<Integer, Long> bicil = (i, l) -> System.out.format("%d, %d%n", i, l);public static void main(String[] args) {bicid.accept(11, 45.14);bicdi.accept(11.45, 14);bicil.accept(1, 14L);}
}
程序执行的结果是:

上述程序使用了System.out.format(),这个方法支持%n这种跨平台的字符。
这个例子中发生了自动装箱和自动拆箱,通过这种方式,我们可以获得处理基本类型的接口。同样的,可以在其他函数式接口中使用包装类:
import java.util.function.Function;
import java.util.function.IntToDoubleFunction;public class FunctionWithWrapped {public static void main(String[] args) {Function<Integer, Double> fid = i -> (double) i;IntToDoubleFunction fid2 = i -> i;}
}
需要注意的是使用强制类型转换的时机,否则会出现报错。
可以发现,只需要通过包装类就可以获得一个用来处理基本类型的函数式接口。因此,若存在函数式接口的基本类型变种,其唯一的原因就是防止自动装箱/拆箱过程带来的性能损耗。
相关文章:
初识Java 11-1 函数式编程
目录 旧方式与新方式 lambda表达式 方法引用 Runnable 未绑定方法引用 构造器方法引用 函数式接口 带有更多参数的函数式接口 解决缺乏基本类型函数式接口的问题 本笔记参考自: 《On Java 中文版》 函数式编程语言的一个特点就是其处理代码片段的简易性&am…...
【Ambari】银河麒麟V10 ARM64架构_安装Ambari2.7.6HDP3.3.1问题总结
🍁 博主 "开着拖拉机回家"带您 Go to New World.✨🍁 🦄 个人主页——🎐开着拖拉机回家_大数据运维-CSDN博客 🎐✨🍁 🪁🍁 希望本文能够给您带来一定的帮助🌸文…...
李宏毅机器学习第一课(结尾附作业模型详细分析)
机器学习就是让机器找一个函数f,这个函数f是通过计算机找出来的 如果参数少的话,我们可以使用暴搜,但是如果参数特别多的话,我们就要使用Gradient Descent Regression (输出的是一个scalar数值) Classification (在…...
对日项目工作总结
从18年8月到23年中秋节,目前已经入职主营对日车载项目的公司满5年了,一般来说,在一家公司工作工作超过3年,如果是在比较大型以及流程规范的公司,那么该公司的工作流程,工作思维会深深地烙印在该员工的脑海中…...
设计模式探索:从理论到实践的编码示例 (软件设计师笔记)
😀前言 设计模式,作为软件工程领域的核心概念之一,向我们展示了开发过程中面对的典型问题的经典解决方案。这些模式不仅帮助开发者创建更加结构化、模块化和可维护的代码,而且也促进了代码的复用性。通过这篇文章,我们…...
【内网穿透】在Ubuntu搭建Web小游戏网站,并将其发布到公网访问
目录 前言 1. 本地环境服务搭建 2. 局域网测试访问 3. 内网穿透 3.1 ubuntu本地安装cpolar 3.2 创建隧道 3.3 测试公网访问 4. 配置固定二级子域名 4.1 保留一个二级子域名 4.2 配置二级子域名 4.3 测试访问公网固定二级子域名 前言 网:我们通常说的是互…...
在cesuim上展示二维模型
前提问题:在cesuim上展示二维模型 解决过程: 1.获取或定义所需变量 2.通过window.cesium.viewer.imageryLayers.addImageryProvider和new Cesium.UrlTemplateImageryProvider进行建模 3.传入url路径后拼接{z}/{x}/{y}.png 4.聚焦到此模型window.ces…...
c/c++中如何输入pi
标准的 C/C 语言中没有π这个符号及常量,一般在开发过程中是通过开发人员自己定义这个常量的,最常见的方式是使用宏定义: 方法1:#define pi 3.1415926 方法2:使用反三角函数const double pi acos(-1.0);...
python爬虫:JavaScript 混淆、逆向技术
Python爬虫在面对JavaScript混淆和逆向技术时可能会遇到一些挑战,因为JavaScript混淆技术和逆向技术可以有效地阻止爬虫对网站内容的正常抓取。以下是一些应对这些挑战的方法: 分析网页源代码:首先,尝试分析网页的源代码…...
Vue error:0308010C:digital envelope routines::unsupported
vue项目,npm run dev的时候出现:Error: error:0308010C:digital envelope routines::unsupported vue项目,npm run dev的时候出现:Error: error:0308010C:digital envelope routines::unsupported 这个是node的版本问题。我的nod…...
gitee 远程仓库操作基础(一)
git remote add <远程仓库名> <仓库远程地址> :给远程仓库取个别名,简化一大堆字符串操作 git remote add origin xxx.git :取个Origin名字 git remote -v :查看本地存在的远程仓库 git pull <远程仓库名><远程分支名>:<本地分支名> 相同可取消…...
DRM全解析 —— ADD_FB2(0)
本文参考以下博文: DRM驱动(四)之ADD_FB 特此致谢! 在笔者之前的libdrm全解析系列文章中,讲到了drmIoctl(fd, DRM_IOCTL_MODE_ADDFB, &f)以及其封装函数drmModeAddFB。对应的文章链接为: libdrm全解…...
01Redis的安装和开机自启的配置
安装Redis 单机安装Redis 大多数企业都是基于Linux服务器来部署项目,而且Redis官方也没有提供Windows版本的安装包(此处选择的Linux版本的CentOS 7) Windows版直接下载对应版本的.zip压缩包解压即可使用 第一步: Redis是基于C语言编写的,因此首先需要…...
进入IT行业:选择前端开发还是后端开发?
一、前言 开发做前端好还是后端好?这是一个常见的问题,特别是对于初学者来说。在编程世界中,前端开发和后端开发分别代表着用户界面和数据逻辑,就像城市的两个不同街区一样。但是,究竟哪个街区更适合我们作为开发者呢…...
Java集成Onlyoffice以及安装和使用示例,轻松实现word、ppt、excel在线编辑功能协同操作,Docker安装Onlyoffice
安装Onlyoffice 拉取onlyoffice镜像 docker pull onlyoffice/documentserver 查看镜像是否下载完成 docker images 启动onlyoffice 以下是将本机的9001端口映射到docker的80端口上,访问时通过服务器ip:9001访问,并且用 -v 将本机机/data/a…...
编程面试_动态规划
题目1 最大连续乘积子串 题目描述给一个浮点数序列,取最大乘积连续子串的值,例如 -2.5,4,0,3,0.5,8,-1,则取出的最大乘积连续子串为3,0.5,8。也就…...
ip地址可以精确定位吗
在互联网时代,IP地址的重要性不言而喻。作为网络通信的基础,IP地址用于标识每一台连接到互联网的设备。然而,传统的IP地址定位方式仅能粗略地确定设备的大致位置,无法实现精确定位。那么,IP地址能否实现精确定位呢&…...
Xamarin体验:使用C#开发iOS/Android应用
http://www.cnblogs.com/lwme/p/use-xamarin-develop-Android-iOS-app.html Xamarin是Mono创始人Miguel de Icaza创建的公司,旨在让开发者可以用C#编写iOS, Android, Mac应用程序,也就是跨平台移动开发。 简介 Xamarin是基于Mono的平台,目前主要有以下产品(更具体请见:h…...
聊聊druid连接池的监控
序 本文主要研究一下druid连接池的监控 init com/alibaba/druid/pool/DruidDataSource.java public void init() throws SQLException {//......registerMbean();//...... }DruidDataSource的init方法会执行registerMbean registerMbean com/alibaba/druid/pool/DruidData…...
CentOS 7 安装 Docker 的详细步骤
文章目录 Docker简介1.更新2.安装必要的软件包3.添加Docker仓库4.安装5.安装后的一些常规设置及常用的命令5.1 启动 Docker5.2 Docker 在系统启动时自动运行5.3 运行一个 Hello World 镜像5.4 查看docker运行状态5.5 docker ps5.6 查看docker版本 6.安装种常见的错误错误1:yum-…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
【PX4飞控】mavros gps相关话题分析,经纬度海拔获取方法,卫星数锁定状态获取方法
使用 ROS1-Noetic 和 mavros v1.20.1, 携带经纬度海拔的话题主要有三个: /mavros/global_position/raw/fix/mavros/gpsstatus/gps1/raw/mavros/global_position/global 查看 mavros 源码,来分析他们的发布过程。发现前两个话题都对应了同一…...
【题解-洛谷】P10480 可达性统计
题目:P10480 可达性统计 题目描述 给定一张 N N N 个点 M M M 条边的有向无环图,分别统计从每个点出发能够到达的点的数量。 输入格式 第一行两个整数 N , M N,M N,M,接下来 M M M 行每行两个整数 x , y x,y x,y,表示从 …...
初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)
零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...
中科院1区顶刊|IF14+:多组学MR联合单细胞时空分析,锁定心血管代谢疾病的免疫治疗新靶点
中科院1区顶刊|IF14:多组学MR联合单细胞时空分析,锁定心血管代谢疾病的免疫治疗新靶点 当下,免疫与代谢性疾病的关联研究已成为生命科学领域的前沿热点。随着研究的深入,我们愈发清晰地认识到免疫系统与代谢系统之间存在着极为复…...
