初识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-…...
竞赛 基于深度学习的动物识别 - 卷积神经网络 机器视觉 图像识别
文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…...
数据结构之【泛型】
泛型:定义阶段不明确具体类型,产生对象时明确具体类型。 //Object是Java中的最高参数统一化,能够接受所有的引用类型; //有了包装类的自动拆装箱之后,Object还能够接收基本类型数值(自动装箱) …...
华为ac无线侧命令行配置思路和步骤
无线侧配置思路: Ap和ac在同一个广播域内,不用配置 option 43 source 源ip回包哪个模式都得配置 Cli配置业务模版流程: 1、 AC控制器上全局配置capwap回包接口地址 1、配置ssid:wifi名称 2、配置安全模版:用户连接密码…...
十六)Stable Diffusion教程:出图流程化
今天说一个流程化出图的案例,适用很多方面。 1、得到线稿,自己画或者图生图加线稿lora出线稿;如果想sd出图调整参数不那么频繁细致,则线稿的素描关系、层次、精深要表现出来,表现清楚。 2、文生图,seed随机…...
SpringBoot全局异常处理源码
SpringBoot全局异常处理源码 一、SpringMVC执行流程二、SpringBoot源码跟踪三、自定义优雅的全局异常处理脚手架starter自定义异常国际化引入封装基础异常封装基础异常扫描器,并注册到ExceptionHandler中项目分享以及改进点 一、SpringMVC执行流程 今天这里叙述的全…...
设计模式——7. 装饰者模式
1. 说明 装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许你在不改变对象接口的前提下,动态地将新行为附加到对象上。这种模式是通过创建一个包装(或装饰)对象,将要被装饰的对象包裹起来,从而实现对原有对象功能的增强和扩展。 装饰者模式的主要特点包括:…...
安卓玩机-----反编译apk 修改apk 去广告 去弹窗等操作中的一些常识
安卓机型app的编译与反编译 apk文件的简单说明与解析 -安卓修改apk apk的组成和编译 一 电脑端几种反编译apk工具操作步骤解析 前面几个博文有说明关于反编译apk和apk架构等有些常识.今天对以上做个补充。初学者记住一点。对于一个apk文件使用压缩软件7zip打开可以查看到文件…...
Hoeffing不等式
在李航老师的统计学习方法(第一版中) H o e f f i n g 不等式 Hoeffing不等式 Hoeffing不等式是这样子给出的 设 X 1 , X 2 , . . . , X N X_1,X_2,...,X_N X1,X2,...,XN是独立随机变量,且 X i ∈ [ a i , b i ] , i 1 , 2 , . . . ,…...
ffmpeg解复用指定pid转推udp
命令 ffmpeg -re -i udp://224.2.2.2:4003?fifo_size1024000 -map #5001 -acodec copy -flush_packets 1 -f mpegts udp://192.168.2.62:5161 ffmpeg -re -i udp://224.2.2.2:4003?fifo_size1024000 -map #5001 -acodec copy -flush_packets 1 -f mpegts udp://192.16…...
Vue组件通信方式
1.props通信 1.1在 Vue 2 中使用 props 通信 注意:props传递的数据是只读的,子组件修改,不会影响父组件 1.1.1.定义 props 在子组件中使用 props 选项来定义要接收的属性 // 子组件 <script> export default {props: {message: String} } </script>1.1.2.传递…...
泰安网站建设步骤/会计培训班要多少钱
以太坊(ETH)24小时活跃用户:9553,24小时交易额:15586.03 ETH,热门榜单前三:加密英雄(日活1309)、质押借 Dai(日活 910)、IDEX(日活872…...
app软件网站建设/付费恶意点击软件
linux环境下查看日志必不可少,简单整理了一些常用的命令,若有需要可直接拿来使用,如果还有其他觉得不错的需要补充的,请在微信公众号测试架构师后 台回复给我,谢谢!tail -f 87testing.log#默认查看最新10条…...
wordpress文本自动分页/上海百度推广
Cuda安装 安装cuda是比较麻烦的一步,以下安装说明来自cuda的安装说明文件。遇到问题找官方解决方案,最便捷有效。Perform the following steps to install CUDA and verify the installation.Disable the Nouveau drivers:首先需要屏蔽ubuntu默认的显卡驱…...
亚马逊做code的网站/seo优化团队
学习机器视觉需要掌握哪些知识 机器视觉系统 1、视觉成像部分:包含几个典型组件:光源,镜头,工业相机 光源和镜头需要我们掌握光学知识,不同的打光方式,可以让相机对物体产生完全不同的成像;而…...
成都网站排名优化/定制网站
微软的SQL Server是一种广泛使用的数据库,很多电子商务网站、企业内部信息化平台等都是基于SQL Server上的,多数管理员认为只要把网络和操作系统的安全搞好了,那么所有的应用程序也就安全了。大多数系统管理员对数据库不熟悉,而数…...
菏泽网站建设公司/小程序
实战需求 我正在尝试将 Picker 和 macOS SwiftUI 应用程序中的 Menu 中的一些按钮组合起来。不幸的是 Picker 会自动折叠到子菜单中,我很难找到解决方案。如何防止 Picker 弃牌,或者可能有更好的解决方案? Menu("Budgets") {Picker("Budgets", select…...