Java8————Lambda表达式(二)

译者注:文中内容均来自于官方教程《Lambda Expressions》,但是由于英汉语言的差异,部分语句官方描述过于冗余,因此译者根据通常状况的理解做了微调,但不会影响表达的含义。比如:

原文:You want to create a feature that enables an administrator to perform any kind of action, such as sending a message, on members of the social networking application that satisfy certain criteria. 

精准翻译:你想要创建一个可以让管理员表现某种行为的功能,比如发送一个消息给你的社交网络应用中满足具体条件的用户。

译者翻译: 你希望开发一个可以让管理员执行某种行为的功能,比如发送消息给那些满足某种条件的用户。


 Lambda表达式

使用匿名类的时候有一个问题是,如果你的匿名类(译者注:匿名内部类就是为了实现某些接口而存在的)实现非常简单,比如一个只包含一个方法的接口,那么匿名类的语法可能会有些笨拙和不清晰。这种情况下,通常要尝试传入一个函数作为另一个方法的参数,比如,当某人点击一个按钮时会执行什么动作?Lambda表达式允许你将一个函数作为方法的参数,或以代码作为数据。

在前面的部分,Anonymous Classes,展示了如何以不命名的方式实现一个基础类。尽管这比一个已命名的类更加简洁,但对于仅有一个方法的类,即便是匿名类似乎也有点冗余和笨重。Lambda表达式可以让你更简洁的描述一个“单方法(single-method)”类的实例。

这部分涵盖了以下主题:

一、理想的Lambda表达式使用情况

方案1:创建一个用于搜索满足某一特征的成员的方法

方案2:创建一个更通用的搜索方法

方案3:在本地类(Local Class)中指定一段搜索条件代码

方案4:在匿名类中指定一段搜索条件代码

方案5:通过Lambda表达式指定一段搜索条件代码

方案6:使用标准函数接口(Functionl Interfaces)的Lambda表达式

方案7:在你的应用中全面使用Lambda表达式

方案8:更广泛的使用通用化

方案9:使用以Lambda表达式作为参数的聚合操作

二、GUI应用中的Lambda表达式

三、Lambda表达式语法

四、在封闭域中访问局部变量

五、目标类型

        目标类型和方法参数

六、序列化

一、理想的Lambda表达式使用情况

假设你正在开发一个社交网络的应用。你希望开发一个可以让管理员执行某种行为的功能,比如发送消息给那些满足某种条件的用户。下面的表格描述了这种使用情况的细节。

属性   描述
名称         给选中的用户执行动作
主要执行人Administrator
先决条件             Administrator已经登录
后验条件    动作只对满足特定条件的用户生效
成功的关键
  1. Administrator规定用于执行某一动作的用户需满足的条件
  2. Administrator规定要对选中的用户执行的动作。
  3. Administrator选择提交按钮
  4. 系统找出所有满足条件的用户
  5. 执行动作
扩展

管理员可以在指定的动作执行前或提交按钮点击前预览到所有符合条件的用户。

出现频率一天很多次

假设你社交应用中的成员可以用下面的Person类来表示:

public class Person {public enum Sex {MALE, FEMALE}String name;LocalDate birthday;Sex gender;String emailAddress;public int getAge() {// ...}public void printPerson() {// ...}
}

 假设所有成员都存储于List<Person> 的实例中。

针对上述使用情形,这部分(译者注:指本章内容)以一个朴实的解决方案作为开始。以本地类和匿名类作为改进方案,最后以一种有效的、简洁的使用Lambda表达式的方案为结尾。更多信息请查看:RosterTest

方案1:创建一个用于搜索满足某一特征的成员的方法

一个最傻瓜式的方案是创建多个方法,每个方法用于搜索符合一种特征的用户,比如性别或者年龄。下面的方法会打印出大于某个指定年龄的所有成员。

public static void printPersonsOlderThan(List<Person> roster, int age) {for (Person p : roster) {if (p.getAge() >= age) {p.printPerson();}}
}

注意:List 是一个有序的Collection,Collection是一个可以把许多元素放入到单独的单元中的对象。Collection用于存储、检索、操作、传递集合数据。Collection相关的更多信息,请参考Collection系列。

这种解决方案可能使你的应用非常脆弱,由于某些修改的加入(比如新的数据类型)就可能会使应用无法正常工作。假设你升级了你的应用,并改变了Person类的结构,比如它包含了不同的成员变量,再或者采用了一种不同的数据类型或算法来记录和比较年龄。你就不得不重写大量的API来适应这种变化。另外,这种解决办法本身就是没必要的限制,要是你希望打印小于具体年龄的成员怎么办?

方案2:创建一个更通用的搜索方法

下面的方法要比printPersonsOlderThan更通用一些,它打印指定年龄范围内的所有成员。

public static void printPersonsWithinAgeRange(List<Person> roster, int low, int high) {for (Person p : roster) {if (low <= p.getAge() && p.getAge() < high) {p.printPerson();}}
}

要是你想要打印指定性别的成员,或指定性别和年龄范围的组合条件呢?要是你决定改变Person类并且加入其他的属性比如情感状况或地理位置呢?尽管这种方法比printPersonsOlderThan更通用一些,但创建一个单独的方法来满足每个可能的查询仍然会导致脆弱的代码。你可以在另外一个类中为你希望搜索的条件单独编码。

方案3:在本地类(Local Class)中指定一段搜索条件代码

下面的方法打印出满足指定搜索条件的所有成员。

public static void printPersons(List<Person> roster, CheckPerson tester) {for (Person p : roster) {if (tester.test(p)) {p.printPerson();}}
}

这个方法通过调用参数tester的test()方法校验roster中的每一个Person对象不论它是否满足已经被指定在CheckPerson中的搜索条件。如果tester.test()返回一个true,那么Person对象就会调用printPerson。

为了指定一个搜索条件,你需要实现CheckPerson接口:

interface CheckPerson {boolean test(Person p);
}

下面的类实现了CheckPerson接口并实现了test()方法。这个方法筛选出符合美国的义务兵役条件的人:如果Person参数是男性且年龄在18到25之间,那么这个方法将会返回true。

class CheckPersonEligibleForSelectiveService implements CheckPerson {public boolean test(Person p) {return p.gender == Person.Sex.MALE &&p.getAge() >= 18 &&p.getAge() <= 25;}
}

为了使用这个类,你创建了它的新的实例,并且调用了printPerson方法。

printPersons(roster, new CheckPersonEligibleForSelectiveService());

虽然这个解决方案并不脆弱——你不用非得在你改变Person结构的时候重写方法,但是依然存在额外的编码:为每一个你计划在系统中执行的搜索创建一个新的接口和本地类。因为CheckPersonEligibleForSelectiveService实现了一个接口,你可以使用匿名内部类来取代本地类,从而绕开为搜索创建新的类的需要。

方案4:在匿名类中指定一段搜索条件代码

下面的printPersons方法的调用中其中一个参数是一个匿名内部类,以筛选出符合美国义务兵役条件的人:男性且年龄在18到25岁之间。

printPersons(roster,new CheckPerson() {public boolean test(Person p) {return p.getGender() == Person.Sex.MALE&& p.getAge() >= 18&& p.getAge() <= 25;}}
);

这种解决方案减少了所必须的代码量,因为你不必为每个搜索的执行创建新的类。然而,考虑到CheckPerson接口仅仅包含一个方法,匿名类的语法是非常笨重的。这种情况下,你可以使用Lambda表达式而不是匿名内部类,就如同下面展示的。

方案5:通过Lambda表达式指定一段搜索标准代码

CheckPerson 接口是一个functional interface(函数接口),函数接口是仅有一个抽象方法的任意接口。(一个函数接口可能包含一个或多个default方法(译者注:default方法是java8 加入的默认方法,它是一个在接口中存在的非抽象方法)或静态方法)因为函数接口仅有一个抽象方法,你可以在实现这个接口的时候省略方法名。为了做到这一点,你是用了一个Lambda表达式,而不是一个匿名内部类表达式,正如下面高亮部分所示:

printPersons(roster,(Person p) -> p.getGender() == Person.Sex.MALE&& p.getAge() >= 18&& p.getAge() <= 25
);

参考《Java8————Lambda表达式(一)》来获得更多关于如何定义Lambda表达式的信息。

你可以使用标准的函数接口来取代CheckPerson的实例,这样会大大将降低所需的代码量。

方案6:使用标准函数接口(Functionl Interfaces)的Lambda表达式

重新思考一下CheckPerson 接口:

interface CheckPerson {boolean test(Person p);
}

这是一个非常简单的接口。它是一个函数接口,因为仅仅含有一个抽象方法。这个方法接收一个参数并且返回一个Boolean值。这个接口太简单了以至于有点不值得你在程序中去定义它。因此,JDK定义了许多标准的函数接口,你可以在java.util.function.包下找到他们。

例如,你可以使用Predicate<T>接口来取代CheckPerson。这个接口包含一个方法:boolean test(T t):

interface Predicate<T> {boolean test(T t);
}

Predicate<T> 接口是一个泛型接口的例子。(想获得更多关于泛型的信息,参考 Generics (Updated) 课程)泛型类型(比如泛型接口)会在尖括号(<>)中定义一个或多个类型参数。上述接口包含了一个类型参数 T 。当你用正是的类型参数声明或实例化一个泛型,你会拥有一个参数化的类型。例如,参数化的类型Predicate<Person>如下所示:

interface Predicate<Person> {boolean test(Person t);
}

这个参数化类型包含一个具有与CheckPerson.boolean test(Person p)一样的返回值类型和参数的方法。因此,你可以使用Predicate<T> 来代替CheckPerson 如下面的示例:

public static void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) {for (Person p : roster) {if (tester.test(p)) {p.printPerson();}}
}

结果,下面的方法调用和你在方案3中调用printPersons 来获取符合义务兵役的成员是一样的:

printPersonsWithPredicate(roster,p -> p.getGender() == Person.Sex.MALE&& p.getAge() >= 18&& p.getAge() <= 25
);

这并不是唯一可能使用到Lambda表达式的地方。下面的方案给出了其他使用Lambda表达式的途径。

方案7:在你的应用中全面使用Lambda表达式

重新思考printPersonsWithPredicate 方法,看看Lambda表达式还可以用在什么地方。

public static void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) {for (Person p : roster) {if (tester.test(p)) {p.printPerson();}}
}

这个方法会校验roster中的每个Person实例,不论它是否满足在Predicate类型参数tester中指定的条件。如果Person实例满足条件,那么printPersron 方法将会被调用。

除了调用printPerson方法,你可以为那些满足条件的Person对象指定不同的行为。你可以使用Lambda表达式来指定这个行为。假设你想要一个和printPerson类似的Lambda表达式,一个只接收一个参数,且没有返回值的Lambda表达式。记住,要想使用Lambda表达式,必须实现一个函数接口。为此,你需要一个仅包含一个抽象方法的函数接口,这个方法可以接受一个Person实例作为对象,并且没有返回值。Consumer<T>接口包含一个方法 void accept(T t) ,正好具备这些特征。下面的方法使用Consumer<Person>的实例调用accept()方法来取代 p.printPerson()的调用。

public static void processPersons(List<Person> roster,Predicate<Person> tester,Consumer<Person> block) {for (Person p : roster) {if (tester.test(p)) {block.accept(p);}}
}

结果,下面的方法调用和你在方案3中调用printPersons 是一样的。用于打印成员的Lambda表达式已经被高亮显示:

processPersons(roster,p -> p.getGender() == Person.Sex.MALE&& p.getAge() >= 18&& p.getAge() <= 25,p -> p.printPerson()
);

要是你想对成员的概要信息进行处理而不是打印他们怎么办?假如你想要验证成员的概要信息或查询他们的联系方式呢?这是,你就需要一个包含可以返回一个值的抽象方法的函数接口了。Function<T,R> 接口包含这样的方法 R apply(T t)。下面的方法获取由参数mapper指定的信息,然后对其执行一个由block参数指定的行为。

public static void processPersonsWithFunction(List<Person> roster,Predicate<Person> tester,Function<Person, String> mapper,Consumer<String> block) {for (Person p : roster) {if (tester.test(p)) {String data = mapper.apply(p);block.accept(data);}}
}

下面的方法获取每一个在roster中符合义务兵役的成员的电子邮件地址,然后打印他们。

processPersonsWithFunction(roster,p -> p.getGender() == Person.Sex.MALE&& p.getAge() >= 18&& p.getAge() <= 25,p -> p.getEmailAddress(),email -> System.out.println(email)
);

方案8:更广泛的使用通用化方案8:更广泛的使用通用化

重新思考processPersonsWithFunction方法。下面的方法是一个通用的版本,它接收一个包含任意数据类型元素的集合作为参数。

public static <X, Y> void processElements(Iterable<X> source,Predicate<X> tester,Function <X, Y> mapper,Consumer<Y> block) {for (X p : source) {if (tester.test(p)) {Y data = mapper.apply(p);block.accept(data);}}
}

为了打印符合条件的成员的电子邮件地址,如下调用processElements 方法:

processElements(roster,p -> p.getGender() == Person.Sex.MALE&& p.getAge() >= 18&& p.getAge() <= 25,p -> p.getEmailAddress(),email -> System.out.println(email)
);

这个方法调用执行了一下行为:

1、用source集合中获得了对象的数据源。在这个例子中,它从roster集合中取得一个Person的数据源。注意roster集合,这个List类型的集合,其本身也是一个Iterable类型的对象。

2、筛选出与Predicate类型对象tester相匹配的对象。在这个例子中,Predicate对象是一个用来指定哪些成员符合义务兵役条件的Lambda表达式。

3、将每一个筛选出的对象映射成一个由Function对象mapper指定的值。在这个例子中,Function对象是一个返回成员电子邮件地址的Lambda表达式。

4、对每个已经映射的对象执行一个由Consumer对象block指定的动作。在这个例子中,Consumer对象是一个打印由Function对象返回的电子邮件地址字符串的Lambda表达式。

你可以用聚合操作来代替每一个动作。

方案9:使用以Lambda表达式作为参数的聚合操作

下面的例子使用了聚合操作来打印集合roster中的每一个符合义务兵役的成员的电子邮件地址。

roster.stream().filter(p -> p.getGender() == Person.Sex.MALE&& p.getAge() >= 18&& p.getAge() <= 25).map(p -> p.getEmailAddress()).forEach(email -> System.out.println(email));

下面的表格展示了每一个processElements 操作与其对应的聚合操作。

processElements ActionAggregate Operation
Obtain a source of objectsStream<E> stream()
Filter objects that match a Predicate objectStream<T> filter(Predicate<? super T> predicate)
Map objects to another value as specified by a Functionobject<R> Stream<R> map(Function<? super T,? extends R> mapper)
Perform an action as specified by a Consumer objectvoid forEach(Consumer<? super T> action)

 操作filter,map和forEach都是聚合操作。聚合操作从一个stream中处理每一个元素,而不是直接在集合中(这也是为什么在这个例子中第一个调用的方法是stream())Stream是元素的一个序列。与集合不同,它不是一个数据结构,并不存储元素。它通过一个管道从一个数据源中(如Collection)搬运每一个值。pipeline 是Stream操作的一个序列,就像上面例子中的filter-map-forEach。另外,聚合操作可以接收Lambda表达式作为参数,允许你自定义他们的表现行为。

二、GUI应用中的Lambda表达式(略)

三、Lambda表达式语法

参考《Java8————Lambda表达式(一)》

四、在封闭域中访问局部变量

如同大多数本地和匿名类,Lambda表达式可以捕捉变量,它们有相同的访问途径访问封闭域中的局部变量。然而,与本地和匿名类不同的是,Lambda表达式不会有任何覆盖问题(shadowing issues)(参考Shadowing)。Lambda表达式是一个词法上的域。这意味着它们不会从父类上继承任何名字或引入一个新的级别的域。声明在Lambda表达式中的变量,会像它们在封闭域中的解释一样。下面的例子,LambdaScopeTest示例如下:

import java.util.function.Consumer;public class LambdaScopeTest {public int x = 0;class FirstLevel {public int x = 1;void methodInFirstLevel(int x) {// The following statement causes the compiler to generate// the error "local variables referenced from a lambda expression// must be final or effectively final" in statement A://// x = 99;Consumer<Integer> myConsumer = (y) -> {System.out.println("x = " + x); // Statement ASystem.out.println("y = " + y);System.out.println("this.x = " + this.x);System.out.println("LambdaScopeTest.this.x = " +LambdaScopeTest.this.x);};myConsumer.accept(x);}}public static void main(String... args) {LambdaScopeTest st = new LambdaScopeTest();LambdaScopeTest.FirstLevel fl = st.new FirstLevel();fl.methodInFirstLevel(23);}
}

这个例子输出如下:

x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0

如果你在myConsumer中的声明里用 y 来代替参数 x ,那么编译器会产生一个错误。

Consumer<Integer> myConsumer = (x) -> {// ...
}

编译器会产生一个错误“variable x is already defined in method methodInFirstLevel(int)”因为Lambda表达式不会引入一个新的级别的域。因此,你可以直接访问封闭域中的属性,方法,局部变量。例如,Lambda表达式可以直接访问methodInFirstLevel方法的参数 x 。要访问内部类中的变量,需使用this关键字。在这个例子中,this.x 引用的是成员变量:FirstLevel.x

然而,和本地及匿名类一样,Lambda表达式只能访问由final或Effectively final修饰的局部变量和参数。例如,假设你定义了methodInFirstLevel 方法后立即增加了如下赋值语句:

void methodInFirstLevel(int x) {x = 99;// ...
}

因为这个赋值语句,变量FirstLevel.x 就不再是一个effectively final了。结果,Java编译器会在Lambda表达式myConsumer尝试访问FirstLevel.x变量时产生一个错误:"local variables referenced from a lambda expression must be final or effectively final" 。

System.out.println("x = " + x);

五、目标类型

你如何决定Lambda表达式的类型呢?再次调用Lambda表达式来筛选年龄在15到25岁之间的男性成员:

p -> p.getGender() == Person.Sex.MALE&& p.getAge() >= 18&& p.getAge() <= 25

这个Lambda表达式在下面两个方法中使用到:

1、public static void printPersons(List<Person> roster, CheckPerson tester) 在方案3中。

2、public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) 在方案6中。

当Java运行时调用printPersons方法,期望的类型是CheckPerson,因此Lambda表达式就对应这个类型。然而,当Java运行时调用printPersonsWithPredicate方法时,它期望的类型是Predicate<Person>,那么Lambda表达式就对应这个类型。这些方法期望的数据类型就叫做目标类型。为了决定Lambda表达式的类型,Java编译器会根据Lambda表达式使用处的上下文或环境来使用目标类型。由此你只需使用Lambda表达式即可,Java编译器会帮你决定它的目标类型。

1、变量声明

2、赋值

3、返回语句

4、数组初始化

5、方法或构造器参数

6、Lambda表达式体

7、条件表达式,?:(译者注:即三目运算符)

8、计算表达式(Cast Expression)

目标类型和方法参数

对于方法参数,Java编译器使用两种语言特性来决定目标类型:重载解决和类型参数推断

考虑如下两个函数接口:

public interface Runnable {void run();
}public interface Callable<V> {V call();
}

方法Runnable.run 没有返回值,Callable<V>.call有返回值。

假设你有一个重载的方法如下:

void invoke(Runnable r) {r.run();
}<T> T invoke(Callable<T> c) {return c.call();
}

哪个方法会被下面的语句调用?

String s = invoke(() -> "done");

invoke(Callable<T>)方法将会被调用,因为它返回一个值,而invoke(Runnable)不会有返回值。这种情况下,Lambda表达式:

() -> "done" 就是Callable<T>。

六、序列化

如果它的目标类型和它捕获的参数是可序列化的,那么你就可以序列化一个Lambda表达式。但是,和内部类一样,序列化一个Lambda表达式强烈不推荐!

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式---观察者模式介绍与理解

设计模式---观察者模式介绍与理解&#xff1a; 观察者模式原理&#xff1a;类似于定牛奶业务 1. 奶站&#xff0c;subject&#xff1a;登记注册&#xff0c;移除&#xff0c;通知&#xff08;register&#xff0c;remove&#xff0c;notify&#xff09; 2. 用户&#xff0c;…

CRS-4995: The command ‘start resource’ is invalid in crsctl.

ntp时间调整后&#xff0c;节点1&#xff0c;advm 和acfs offline 处理办法&#xff1a; /u01/app/12.2.0.1/grid/bin/crsctl stop crs /u01/app/12.2.0.1/grid/bin/crsctl start crs 曾经尝试如下命令不起作用 /u01/app/12.2.0.1/grid/bin/acfsload start /u01/app/12.2…

抽象工厂模式升级版————泛型化实现

引言 今天回看之前总结的抽象工厂模式的实现《Java常用设计模式————抽象工厂模式》&#xff0c;聚焦于抽象工厂模式的缺点&#xff0c;试着改进了一下。 回顾一下抽象工厂模式的缺点&#xff1a; 在添加新的产品类型时&#xff0c;难以扩展抽象工厂来生产新种类的产品。…

发生在“注解”@的那些事儿

注解&#xff1a; 自定义注解&#xff1a; 注解和类&#xff0c;接口一样&#xff0c;属于一种数据类型 注解可以放在类&#xff0c;方法&#xff0c;属性上面 注解可以有属性&#xff0c;也可以没有属性 注解有作用范围 &#xff08; 源码期间&#xff08;String&#…

Java常用设计模式————建造者模式

引言 建造者模式&#xff08;Builder Pattern&#xff09;使用多个简单对象一步一步构建成一个复杂的对象。这种类型的设计模式属于建造型模式&#xff0c;它提供了一种创建对象的最佳方式。 一个Builder会一步步构建最终的对象。该Builder类是独立于其他对象的。 实现概要 …

使用动态代理解决网站字符集编码问题:(之前通过拦截器)

使用动态代理解决网站字符集编码问题&#xff1a;&#xff08;之前通过拦截器&#xff09; 设计模式&#xff1a; 在软件开发的过程中&#xff0c;遇到相识的问题&#xff0c;将问题的解决方式抽象为模型&#xff08;套路&#xff09; 单例模式&#xff08;静态代码只会执行一…

Java 多线程 —— AQS 原理

引言 使用Condition实现生产者-消费者模型&#xff0c;并与wait和notify实现的效果相对比。 wait/notify模拟生产者-消费者 面试题&#xff1a;写一个固定容量同步容器&#xff0c;拥有put和get方法&#xff0c;以及getCount方法能够支持2个生产线程以及10个消费者线程的阻塞…

设计模式---简单工厂设计模式

先定义一个抽象类Animal&#xff1a; 定义两个动物类继承这个类&#xff1a; 定义一个专门生产动物的工程类&#xff1a; 最后定义一个测试类&#xff1a; 按照这个动物工厂类&#xff0c;你会发现&#xff0c;如果动物一多的话&#xff0c;就需要写很多重复的方法&#xff0c;…

Java 多线程 —— ThreadLocal

一、引言 ThreadLocal是Java帮助实现线程封闭性的典型手段。 作用&#xff1a;提供线程内的局部变量&#xff0c;这种变量在线程的生命周期内起作用&#xff0c;减少同一个线程内多个函数或组件之间一些公共变量的传递复杂度。同时也用来维护线程中的变量不被其他线程干扰。 …

Java8————方法引用

译者注&#xff1a;本篇博客翻译自Oracle官方教程《Method References》。作为Java 8 新特性Lambda表达式的引申概念&#xff0c;博主依然采用官方文档的方式来学习这一重要的概念。希望对各位同道有所帮助。 方法引用 使用Lambda表达式创建匿名方法。但是&#xff0c;有时候…

设计模式---适配器设计模式

设计模式---适配器设计模式 什么事适配器&#xff1a; 1. 在使用监听的时候&#xff0c;需要定义一个类事件监听器接口 2. 通常接口中有多个方法&#xff0c;而程序中不一定所有的方法都用到&#xff0c;但又必须重写&#xff0c;很繁琐 3. 适配器简化了这些操作&#xff0c…

Java并发编程实战————售票问题

引言 现有一个需求如下&#xff1a; 有10000张火车票&#xff0c;每张票都有一个编号&#xff0c;同时有10个窗口对外售票&#xff0c;如何确保车票的正常售卖&#xff1f; 程序一&#xff1a;使用List 问题的解决办法都是从我们最最熟悉的角度思考。程序一&#xff0c;我们…

多线程相关知识

多线程相关知识 两个线程进行通信&#xff1a;通过等待&#xff08;wait&#xff09;唤醒&#xff08;notify&#xff09;机制 三个或三个以上线程进行通信&#xff1a;通过notifyAll&#xff08;&#xff09;方法 /* * 1. 在同步代码块中&#xff0c;用哪个对象锁&#xff0c…

Eclipse集成PyDev5.2.0开发插件

引言 在进行Python学习的时候&#xff0c;希望不使用IDLE进行开发&#xff0c;但是其他的IDE如PyCharm可能需要一段短暂时间的上手&#xff0c;因为开发过Java&#xff0c;所以使用能够集成到Eclipse上的PyDev插件进行开发应该会好一些。 但是在安装PyDev的时候发生了一些问题…

PostMan 四种常见的 POST 提交数据方式

HTTP/1.1 协议规定的 HTTP 请求方法有 OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 这几种。其中 POST 一般用来向服务端提交数据&#xff0c;本文主要讨论 POST 提交数据的几种方式。 协议规定 POST 提交的数据必须放在消息主体&#xff08;entity-body&#xff0…

Java并发编程实战————对象的组合

引言 对象的组合&#xff0c;是《Java Concurrency in Practice》中第四章引入的课题。这并不是一个并发的概念。 为了可以将现有的线程安全组件组合为更大规模的组件或程序&#xff0c;而不是每次内存访问都进行分析以确保程序是线程安全的。这一章将介绍一些组合模式&#…

史上最真实行业鄙视链

本文转载自菜鸟教程的微信公众号&#xff0c;原文链接&#xff1a;https://mp.weixin.qq.com/s/d9cdtq8y4Msq-_ZNof-iuw 引言 作为程序员的一份子&#xff0c;掌握好各个生态系统中的鄙视链&#xff0c;可以写出更加符合改变世界要求的代码。掌握了鄙视链&#xff0c;就掌握了…

如何快速理清大型项目业务逻辑

引言 本篇文章为了探讨如何快速上手一个大型项目。针对经验尚浅需要快速接手一个项目的开发人员。 当他们拿到一个大型程序后&#xff0c;他们便开始一句一句的阅读分析&#xff0c;夜以继日&#xff0c;悬梁刺股。可结果依然不理想&#xff0c;往往进入以下状态&#xff1a;…

权限验证框架Shiro

权限验证框架Shiro&#xff1a; Shiro简介 什么是Shiro&#xff1a; shiro是一个强大易用的Java安全框架&#xff0c;提供了认证&#xff0c;授权&#xff0c;加密&#xff0c;回话管理等功能&#xff1b; 认证&#xff08;Authentication&#xff09;&#xff1a;用户身份识别…

Linux——less指令常用操作

引言 对于生产环境、测试环境中的日志文件&#xff0c;我们可以通过less指令来进行查看并通过关键字进行查找。less命令的含义是&#xff1a;分屏查看文件内容。 它要比more命令更加强大&#xff0c;less在显示文件内容时&#xff0c;并不是一次将整个文件加载之后才显示&…