当前位置: 首页 > news >正文

Java 函数式编程

1.Lambda

1.1 格式

JDK 从 1.8 版本开始支持 Lambda 表达式,通过 Lambda 表达式我们可以将一个函数作为参数传入方法中。在 JDK 1.8 之前,我们只能通过匿名表达式来完成类似的功能,但是匿名表达式比较繁琐,存在大量的模板代码,不利于将行为参数化,而采用 Lamdba 则能很好的解决这个问题。Lambda 表达式的基本语法如下:

(parameters) -> expression

或采用花括号的形式:

(parameters) -> { statements; }

Lambda 表达式具有如下特点:

可选的参数:不需要声明参数类型,编译器会依靠上下文进行自动推断;

可选的参数圆括号:当且仅当只有一个参数时,包裹参数的圆括号可以省略; 

可选的花括号:如果主体只有一个表达式,则无需使用花括号; 

可选的返回关键字:如果主体只有一个表达式,则该表达式的值就是整个 Lambda 表达式的返回值,此时不需要使用 return 关键字进行显式的返回。

1.2 行为参数化

 上面我们说过,Lambda 表达式主要解决的是行为参数化的问题,而什么是行为参数化?下面给出一个具体的示例:

/*** 定义函数式接口* @param <T> 参数类型*/
@FunctionalInterface
public interface CustomPredicate<T> {boolean test(T t);
}
/*** 集合过滤* @param list 待过滤的集合* @param predicate 函数式接口* @param <T> 集合中元素的类型* @return 满足条件的元素的集合*/
public static <T> List<T> filter(List<T> list, CustomPredicate<T> predicate) {ArrayList<T> result = new ArrayList<>();for (T t : list) {// 将满足条件的元素添加到返回集合中if (predicate.test(t)) result.add(t);}return result;
}

针对不同类型的集合,我们可以通过传入不同的 Lambda 表达式作为参数来表达不同的过滤行为,这就是行为参数化:

List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
filter(integers, x -> x % 2 == 0);  // 过滤出所有偶数List<Employee> employees = Arrays.asList(new Employee("张某", 21, true),new Employee("李某", 30, true),new Employee("王某", 45, false));
filter(employees, employee -> employee.getAge() > 25); // 过滤出所有年龄大于25的员工

需要注意的是上面我们声明接口时,使用了 @FunctionalInterface 注解,它表示当前的接口是一个函数式接口。函数式接口就是只含有一个抽象方法的接口;即一个接口不论含有多少个默认方法和静态方法,只要它只有一个抽象方法,它就是一个函数式接口。使用 @FunctionalInterface 修饰后,当该接口有一个以上的抽象方法时,编译器就会进行提醒。

任何使用到函数式接口的地方,都可以使用 Lambda 表达式进行简写。例如 Runnable 接口就是一个函数式接口,我们可以使用 Lambda 表达式对其进行简写:

new Thread(() -> {System.out.println("hello");
});

1.3 方法引用和构造器引用

紧接上面的例子,如果我们需要过滤出所有的正式员工,除了可以写成下面的形式外:

filter(employees, employee -> employee.isOfficial());

还可以使用方法引用的形式进行简写:

filter(employees, Employee::isOfficial);

除了方法引用外,还可以对构造器进行引用,示例如下:

Stream<Integer> stream = Stream.of(1, 3, 5, 2, 4);
stream.collect(Collectors.toCollection(ArrayList::new));  //等价于 toCollection(()->new ArrayList<>())

方法引用和构造器引用的目的都是为了让代码更加的简洁。

2. 函数式接口

通常我们不需要自定义函数式接口,JDK 中内置了大量函数式接口,基本可以满足大多数场景下的使用需求,最基本的四种如下:

2.1. Consumer<T>消费型接口

消费输入的变量,没有返回值:

@FunctionalInterface
public interface Consumer<T> {void accept(T t);...
}

2.2 Consumer<T>:供给型接口

供给变量:

@FunctionalInterface
public interface Supplier<T> {T get();
}

2.3 Function<T, R>:

对输入类型为 T 的变量执行特定的转换操作,并返回类型为 R 的返回值:

@FunctionalInterface
public interface Function<T, R> {R apply(T t);...
}

2.4 Predicate<T>:

判断类型为 T 的变量是否满足特定的条件,如果满足则返回 true,否则返回 false:

@FunctionalInterface
public interface Predicate<T> {boolean test(T t);...
}

其他函数式接口都是这四种基本类型的扩展和延伸。以 BiFunction 和 BinaryOperator 接口为例:

BiFunction<T, U, R>:是函数型接口 Function<T, R> 的扩展,Function 只能接收一个入参;而 BiFunction 可以用于接收两个不同类型的入参;

BinaryOperator<T>:是 BiFunction 的一种特殊化情况,即两个入参和返回值的类型均相同,通常用于二元运算。定义如下:

@FunctionalInterface
public interface BiFunction<T, U, R> {R apply(T t, U u);
}@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {....
}

下面演示一下 BinaryOperator 的用法:

/* 执行归约操作*/
public static <T> T reduce(List<T> list, T initValue, BinaryOperator<T> binaryOperator) {for (T t : list) {initValue = binaryOperator.apply(initValue, t);}return initValue;
}public static void main(String[] args) {List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);reduce(integers, 0, (a, b) -> a + b); // 求和  输出:15reduce(integers, 1, (a, b) -> a * b); // 求积  输出:120
}

3. 创建流

JDK 1.8 中另一个大的改进是引入了流,通过流、Lamda 表达式以及函数式接口,可以高效地完成数据的处理。创建流通常有以下四种方法:

3.1 由值创建

使用静态方法 Stream.of() 由指定的值进行创建:

Stream<String> stream = Stream.of("a", "b", "c", "d");

3.2 由集合或数组创建

使用静态方法 Arrays.stream() 由指定的数组进行创建:

String[] strings={"a", "b", "c", "d"};
Stream<String> stream = Arrays.stream(strings);

调用集合类的 stream() 方法进行创建:

List<String> strings = Arrays.asList("a", "b", "c", "d");
Stream<String> stream = strings.stream();

stream() 方法定义在 Collection 接口中,它是一个默认方法,因此大多数的集合都可以通过该方法来创建流:

public interface Collection<E> extends Iterable<E> {default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}
}

3.3 由文件创建

try (Stream<String> lines = Files.lines(Paths.get("pom.xml"), StandardCharsets.UTF_8)) {lines.forEach(System.out::println);
} catch (IOException e) {e.printStackTrace();
}

3.4 由函数创建

除了以上方法外,还可以通过 Stream.iterate() 和 Stream.generate() 方法来来创建无限流:

Stream.iterate() 接受两个参数:第一个是初始值;第二个参数是一个输入值和输出值相同的函数型接口,主要用于迭代式地产生新的元素,示例如下:

// 依次输出09
Stream.iterate(0, x -> x + 1).limit(10).forEach(System.out::print);
Stream.generate()  接收一个供应型函数作为参数,用于按照该函数产生新的元素:
// 依次输出随机数Stream.generate(Math::random).limit(10).forEach(System.out::print);

4. 操作流

4.1 基本操作

当流创建后,便可以利用 Stream 类上的各种方法对流中的数据进行处理,常用的方法如下:

操作

作用

返回类型

使用的类型/函数式接口

filter

过滤符合条件的元素

Stream<T>

Predicate<T>

distinct

过滤重复元素

Stream<T>

skip

跳过指定数量的元素

Stream<T>

long

limit

限制元素的数量

Stream<T>

long

map

对元素执行特定转换操作

Stream<T>

Function<T,R>

flatMap

将元素扁平化后执行特定转换操作

Stream<T>

Function<T,Stream<R>>

sorted

对元素进行排序

Stream<T>

Comparator<T>

anyMatch

是否存在任意一个元素能满足指定条件

boolean

Predicate<T>

noneMatch

是否所有元素都不满足指定条件

boolean

Predicate<T>

allMatch

是否所有元素都满足指定条件

boolean

Predicate<T>

findAny

返回任意一个满足指定条件的元素

Optional<T>

findFirst

返回第一个满足指定条件的元素

Optional<T>

forEach

对所有元素执行特定的操作

void

Cosumer<T>

collect

使用收集器

R

Collector<T, A, R>

reduce

执行归约操作

Optional<T>

BinaryOperator<T>

count

计算流中元素的数量

long

注:上表中返回类型为 Stream<T> 的操作都是中间操作,代表还可以继续调用其它方法对流进行处理。返回类型为其它的操作都是终止操作,代表处理过程到此为止。

使用示例如下:

Stream.iterate(0, x -> x + 1)       // 构建流.limit(20)                        // 限制元素的个数.skip(10)                        // 跳过前10个元素.filter(x -> x % 2 == 0)         // 过滤出所有偶数.map(x -> "偶数:" + x)            // 对元素执行转换操作.forEach(System.out::println);    // 打印出所有元素输出结果如下:shell
偶数:10
偶数:12
偶数:14
偶数:16
偶数:18

 上表的 flatMap() 方法接收一个参数,该参数是一个函数型接口 Function<? super T, ? extends Stream<? extends R>> mapper,主要用于将流中的元素转换为 Stream ,从而可以将原有的元素进行扁平化,示例如下:

String[] strings = {"hello", "world"};Arrays.stream(strings).map(x -> x.split(""))              // 拆分得到: ['h','e','l','l','o'],['w','o','r','l','d'].flatMap(x -> Arrays.stream(x))  // 将每个数组进行扁平化处理得到:'h','e','l','l','o','w','o','r','l','d'.forEach(System.out::println);

而上表的 reduce() 方法则接收两个参数:第一个参数表示执行归约操作的初始值;第二个参数是上文我们介绍过的函数式接口 BinaryOperator<T> ,使用示例如下:

Stream.iterate(0, x -> x + 1).limit(10).reduce(0, (a, b) -> a + b); //进行求和操作

4.2 数值流

上面的代码等效于对 Stream 中的所有元素执行了求和操作,因此我们还可以调用简便方法 sum() 来进行实现,但是需要注意的是 Stream.iterate() 生成流中的元素类型都是包装类型:

Stream<Integer> stream = Stream.iterate(0, x -> x + 1); //包装类型Integer
而 sum() 方法则是定义在 IntStream 上,此时需要将流转换为具体的数值流,对应的方法是 mapToInt()
Stream.iterate(0, x -> x + 1).limit(10).mapToInt(x -> x).sum();

类似的方法还有 mapToLong() 和 mapToDouble() 。如果你想要将数值流转换为原有的流,相当于对其中的元素进行装箱操作,此时可以调用 boxed() 方法:

IntStream intStream = Stream.iterate(0, x -> x + 1).limit(10).mapToInt(x -> x);
Stream<Integer> boxed = intStream.boxed();

5.收集器

5.1 常用流收集器

Stream 中最强大一个终止操作是 collect() ,它接收一个收集器 Collector 作为参数,可以将流中的元素收集到集合中,或进行分组、分区等操作。Java 中内置了多种收集器的实现,可以通过 Collectors 类的静态方法进行调用,常用的收集器如下:

工厂方法

返回类型

用于

toList

List<T>

把流中所有元素收集到 List 中

toSet

Set<T>

把流中所有元素收集到 Set 中

toCollection

Collection<T>

把流中所有元素收集到指定的集合中

counting

Long

计算流中所有元素的个数

summingInt

Integer

将流中所有元素转换为整数,并计算其总和

averagingInt

Double

将流中所有元素转换为整数,并计算其平均值

summarizingInt

IntSummaryStatistics

将流中所有元素转换为整数,并返回统计结果,包含最大值、最小值、

总和与平均值等信息

joining

String

将流中所有元素转换为字符串,并使用给定连接符进行连接

maxBy

Optional<T>

查找流中最大元素的 Optional

minBy

Optional<T>

查找流中最小元素的 Optional

reducing

规约操作产生的类型

对流中所有元素执行归约操作

collectingAndThen

转换返回的类型

先把流中所有元素收集到指定的集合中,再对集合执行特定的操作

groupingBy

Map<K,List<T>>

对流中所有元素执行分组操作

partitionBy

Map<Boolean,List<T>>

对流中所有元素执行分区操作

使用示例如下:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 4, 5, 6); stream.collect(Collectors.toSet());  // [1, 2, 3, 4, 5, 6]
stream.collect(Collectors.toList()); // [1, 2, 3, 4, 4, 5, 6]
stream.collect(Collectors.toCollection(ArrayList::new)); // [1, 2, 3, 4, 4, 5, 6]
stream.collect(Collectors.counting()); // 7 等效于 stream.count();
stream.collect(Collectors.summarizingInt(x -> x)); // IntSummaryStatistics{count=7, sum=25, min=1, average=3.571429, max=6}
stream.collect(Collectors.maxBy((Integer::compareTo))); // Optional[6]
stream.collect(Collectors.reducing(1, (a, b) -> a * b)); // 等效于 stream.reduce(1, (a, b) -> a * b);
collect(Collectors.collectingAndThen(Collectors.toSet(), Set::size)); // 先把所有元素收集到Set中,再计算Set的大小

注意:以上每个终止操作只能单独演示,因为对一个流只能执行一次终止操作。并且执行完终止操作后,就不能再对这个流进行任何操作,否则将抛出 java.lang.IllegalStateException: stream has already been operated upon or closed 的异常。

5.2 分组收集器

分组收集器可以实现类似数据库 groupBy 子句的功能。假设存在如下员工信息:

Stream<Employee> stream = Stream.of(new Employee("张某", "男", "A公司", 20),new Employee("李某", "女", "A公司", 30),new Employee("王某", "男", "B公司", 40),new Employee("田某", "女", "B公司", 50));
public class Employee {private String name;private String gender;private String company;private int age;@Overridepublic String toString() {return "Employee{" + "name='" + name + '\'' + '}';}
}

此时如果需要按照公司进行分组,则可以使用 groupingBy() 收集器: 

stream.collect(Collectors.groupingBy(Employee::getCompany));对应的分组结果如下:
{ B公司=[Employee{name='王某'}, Employee{name='田某'}], A公司=[Employee{name='张某'}, Employee{name='李某'}]
}

如果想要计算分组后每家公司的人数,还可以为 groupingBy() 传递一个收集器 Collector 作为其第二个参数,调用其重载方法:

stream.collect(Collectors.groupingBy(Employee::getCompany, Collectors.counting()));对应的结果如下:
{B公司=2, A公司=2
}    

因为第二个参数是一个 Collector,这意味着你可以再传入一个分组收集器来完成多级分组,示例如下:

stream.collect(Collectors.groupingBy(Employee::getCompany, Collectors.groupingBy(Employee::getGender)));先按照公司分组,再按照性别分组,结果如下:
{ B公司={=[Employee{name='田某'}], 男=[Employee{name='王某'}]}, A公司={=[Employee{name='李某'}], 男=[Employee{name='张某'}]}
}

除此之外,也可以通过代码块来自定义分组条件,示例如下:

Map<String, List<Employee>> collect = stream.collect(Collectors.groupingBy(employee -> {if (employee.getAge() <= 30) {return "青年员工";} else if (employee.getAge() < 50) {return "中年员工";} else {return "老年员工";}
}));对应的分组结果如下:
{ 
  中年员工=[Employee{name='王某'}], 
  青年员工=[Employee{name='张某'}, Employee{name='李某'}], 
  老年员工=[Employee{name='田某'}]
}    

5.3 分区

分区是分组的一种特殊情况,即将满足指定条件的元素分为一组,将不满足指定条件的元素分为另一组,两者在使用上基本类似,示例如下:

stream.collect(Collectors.partitioningBy(x -> "A公司".equals(x.getCompany())));对应的分区结果如下:
{false=[Employee{name='王某'}, Employee{name='田某'}], true=[Employee{name='张某'}, Employee{name='李某'}]
}    

6. 并行流

想要将普通流转换为并行流非常简单,只需要调用 Stream 的 parallel() 方法即可:

stream.parallel();

此时流中的所有元素会被均匀的分配到多个线程上进行处理。并行流内部使用的是 ForkJoinPool 线程池,它默认的线程数量就是处理器数量,可以通过 Runtime.getRuntime().availableProcessors() 来查看该值,通常不需要更改。

当前也没有办法为某个具体的流指定线程数量,只能通过修改系统属性 java.util.concurrent.ForkJoinPool.common.parallelism 的值来改变所有并行流使用的线程数量,示例如下:

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12"); 

如果想将并行流改回普通的串行流,则只需要调用 Stream 的 sequential() 方法即可:

stream.sequential();

相关文章:

Java 函数式编程

1.Lambda 1.1 格式 JDK 从 1.8 版本开始支持 Lambda 表达式&#xff0c;通过 Lambda 表达式我们可以将一个函数作为参数传入方法中。在 JDK 1.8 之前&#xff0c;我们只能通过匿名表达式来完成类似的功能&#xff0c;但是匿名表达式比较繁琐&#xff0c;存在大量的模板代码&…...

类的成员函数总结

前言&#xff1a; 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成的…...

java高级之单元测试、反射

1、Junit测试工具 Test定义测试方法 1.被BeforeClass标记的方法,执行在所有方法之前 2.被AfterCalss标记的方法&#xff0c;执行在所有方法之后 3.被Before标记的方法&#xff0c;执行在每一个Test方法之前 4.被After标记的方法&#xff0c;执行在每一个Test方法之后 public …...

MSQL系列(十三) Mysql实战-left/right/inner join 使用详解及索引优化

Mysql实战-left/right/inner join 使用详解及索引优化 前面我们讲解了BTree的索引结构&#xff0c;也详细讲解下Join的底层驱动表 选择原理&#xff0c;今天我们来了解一下为什么会出现内连接外连接&#xff0c;两种连接方式&#xff0c;另外实战一下内连接和几种最常用的join…...

前端面试题之HTML篇

1、src 和 href 的区别 具有src的标签有&#xff1a;script、img、iframe 具有href的标签有&#xff1a;link、a 区别 src 是source的缩写。表示源的意思&#xff0c;指向资源的地址并下载应用到文档中。会阻塞文档的渲染&#xff0c;也就是为什么js脚本放在底部而不是头部的…...

Django ORM:数据库操作的Python化艺术

Django的对象关系映射器&#xff08;ORM&#xff09;是其核心功能之一&#xff0c;允许开发者使用Python代码来定义、操作和查询数据库。这篇文章将带你深入了解Django ORM的强大之处&#xff0c;从基本概念到高级查询技巧&#xff0c;提供丰富的示例帮助你掌握使用Django ORM进…...

react受控组件与非受控组件

React中的组件可以分为受控组件和非受控组件&#xff1a; 受控组件&#xff1a;受控组件是指组件的值受到React组件状态的控制。通常在组件中&#xff0c;我们会通过state来存储组件的值&#xff0c;然后再将state的值传递给组件的props&#xff0c;从而实现组件的双向数据绑定…...

小米产品面试题:淘宝为何需要确认收货?京东为何不需要?

亲爱的小米粉丝们&#xff0c;大家好&#xff01;我是小米&#xff0c;一个热爱技术、热衷于分享的小编。今天&#xff0c;我要和大家聊聊一个有趣的话题&#xff1a;为什么淘宝购物需要确认收货&#xff0c;而京东不需要&#xff1f;这可是一个让很多人纳闷的问题&#xff0c;…...

(1)上位机底部栏 UI如何设置

上位机如果像设置个多页面切换&#xff1a; 位置&#xff1a; 代码如下&#xff1a; "tabBar": {"color": "black","selectedColor": "#d43c33","borderStyle":"black","backgroundColor": …...

中国多主数据库:压强投入,期待破茧

拿破仑曾说&#xff1a;“战争的艺术就是在某一点上集中最大优势兵力”&#xff0c;强调了力量集中的重要性。 如今&#xff0c;国际形势风云变幻&#xff0c;西方世界对中国的围剿不再仅仅体现在军事和地缘政治上&#xff0c;而更多表现在经济与科技上。在科技领域&#xff0…...

JavaScript在ES6及后续新增的常用新特性

JavaScript经历了不同标本的迭代&#xff0c;在不断完善中会添加不同的新特性来解决前一个阶段的瑕疵&#xff0c;让我们开发更加便捷与写法更加简洁&#xff01; 1、箭头函数&#xff1a; 箭头函数相比传统的函数语法&#xff0c;具有更简洁的语法、没有自己的this值、不会绑…...

试试流量回放,不用人工写自动化测试case了

大家好&#xff0c;我是洋子&#xff0c;接触过接口自动化测试的同学都知道&#xff0c;我们一般要基于某种自动化测试框架&#xff0c;编写自动化case&#xff0c;编写自动化case的依据来源于接口文档&#xff0c;对照接口文档里面的请求参数进行人工添加接口自动化case 其实…...

密钥管理系统功能及作用简介 安当加密

密钥管理系统的功能主要包括密钥生成、密钥注入、密钥备份、密钥恢复、密钥更新、密钥导出和服务&#xff0c;以及密钥的销毁等。 密钥生成&#xff1a;通过输入一到多组的密钥种子&#xff0c;按照可再现或不可再现的模式生成所需要的密钥。一般采用不可再现模式作为密钥生成…...

vue中watch属性的用法

在Vue中&#xff0c;watch属性用于监听一个数据的变化&#xff0c;并且在数据变化时执行一些操作。它可以观察一个具体的数据对象&#xff0c;从而在该数据对象发生变化时触发对应的回调函数。 使用watch属性的步骤如下&#xff1a; 在Vue实例中添加一个watch对象 new Vue({…...

Redis-使用java代码操作Redis

&#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; ​ &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Linux》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有一定基础的程序员&#xff0c;这…...

0基础学习PyFlink——事件时间和运行时间的窗口

大纲 定制策略运行策略Reduce完整代码滑动窗口案例参考资料 在 《0基础学习PyFlink——时间滚动窗口(Tumbling Time Windows)》一文中&#xff0c;我们使用的是运行时间(Tumbling ProcessingTimeWindows)作为窗口的参考时间&#xff1a; reducedkeyed.window(TumblingProcess…...

Git Rebase 优化项目历史

在软件开发过程中&#xff0c;版本控制是必不可少的一环。Git作为当前最流行的版本控制系统&#xff0c;为开发者提供了强大的工具来管理和维护代码历史。git rebase是其中一个高级特性&#xff0c;它可以用来重新整理提交历史&#xff0c;使之更加清晰和线性。本文将详细介绍g…...

两种MySQL OCP认证应该如何选?

很多同学都找姚远老师说要参加MySQL OCP认证培训&#xff0c;但绝大部分同学并不知道MySQL OCP认证有两种&#xff0c;以MySQL 8.0为例。 一种是管理方向&#xff0c;叫&#xff1a;Oracle Certified Professional, MySQL 8.0 Database Administrator&#xff08;我考试的比较…...

Java用log4j写日志

日志可以方便追踪和调试问题&#xff0c;以前用log4net写日志&#xff0c;换Java了改用log4j写日志&#xff0c;用法和log4net差不多。 到apache包下载下载log4j的包&#xff0c;解压后把下图两个jar包引入工程 先到网站根下加一个log4j2.xml的配置文件来配置日志的格式和参…...

PCTA认证考试-01_TiDB数据库架构概述

TiDB 数据库架构概述 一、学习目标 理解 TiDB 数据库整体结构。了解 TiDB Server&#xff0c;TiKV&#xff0c;TiFlash 和 PD 的主要功能。 二、TiDB 体系架构 1. TiDB Server 2. TiKV OLTP 3. Placement Driver 4. TiFlash OLAP OLTPOLAPHTAP...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

Golang——7、包与接口详解

包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

土建施工员考试:建筑施工技术重点知识有哪些?

《管理实务》是土建施工员考试中侧重实操应用与管理能力的科目&#xff0c;核心考查施工组织、质量安全、进度成本等现场管理要点。以下是结合考试大纲与高频考点整理的重点内容&#xff0c;附学习方向和应试技巧&#xff1a; 一、施工组织与进度管理 核心目标&#xff1a; 规…...