有没有办法减少这个 class 中的循环数量?
Is there a way to reduce the amount of loops in this class?
我有一个 class,其中有几种方法循环遍历 产品列表 。
我想知道是否有办法减少循环次数,因为它们真的只做一次检查?
某种实现函数并传递谓词的方法?我昨天发现了 Lambdas,但不确定它是否适用于此。
public class Stock {
private ArrayList<Products> _productList;
public Stock(){}
public Boolean addProduct(Products product){
return _productList.contains(product) && _productList.add(product);
}
public int obtainPos(int code){
for(int i=0; i < _productList.size(); i++)
if(_productList.get(i).getCode() == code)
return i;
return -1;
}
public void removeProduct(int code){
int pos = obtainPos(code);
if(pos >=0)
_productList.remove(pos);
else
System.out.println("Error - Product not found.");
}
public int productAmount(){ return _productList.size(); }
public int amountType(String type){
int i = 0;
for(Products pr : _productList)
if(pr.getClass().getSimpleName().equals(type))
i++;
return i;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
for(Products pr : _productList)
sb.append(pr.getName()).append(" \n");
return sb.toString();
}
public void itemsToRemove(String reason){
StringBuilder st = new StringBuilder();
for(Products pr : _productList)
st.append(pr.getName()).append(" - ").append(pr.withdraw(reason)).append("\n");
System.out.println(st.toString());
}
}
您可以在所需的单循环中使用类似 if 语句的语句来决定您要执行的操作,例如:
public void loopClass(int decision) {
for(Products pr : _productList) {
if (decision == choice1) {
//do something
}
if (decision == choice2) {
//do something else
}
//etc etc
}
}
查看您的代码,我没有发现您设置循环的方式有任何问题。您现在的设置方式看起来非常简洁明了。 我建议保持原样。
您可以使用 HashMap<Integer,Products>
而不是 ArrayList<Products>
。这将允许您消除 obtainPos
并将 addProduct
和 removeProduct
减少到 O(1) 操作。
a way to reduce the amount of loops
Some sort of way to implement a function and pass predicate? I
discovered Lambdas yesterday, but not sure it would be applicable
here.
如果您正在考虑使用 lambdas 作为可能的选项,您可以使此代码更加简洁。
只需要非常基础的知识关于Java8lambda表达式 和 流 。作为第一步,我建议您熟悉这个 tutorial.
让我们一一重构这些方法。
removeProduct()
您可以将方法 removeProduct()
和 obtainPos()
中的所有代码替换为 单行 通过使用 Collection
接口的方法 removeIf()
,像这样:
products.removeIf(product -> product.getCode() == code);
方法removeIf()
需要一个Predicate
(一个产生布尔值的函数),它会删除与给定谓词匹配的每个元素.
此方法 returns true
如果集合被修改并且 false
否则。
getAmountByType()
可以使用仅包含两个操作的微小流来实现此方法:filter()
和 count()
。
为了创建以产品列表作为源的流,您必须调用列表中的方法stream()
。
方法filter()
是一个中间操作(产生新流的操作)。与 removeIf()
一样,它需要一个 谓词 ,与 removeIf()
相反,它会在结果流中只保留与给定谓词匹配的元素。
而方法count()
是终端操作(关闭流的操作,return是一个值或执行最终操作,如 forEach
) 即 return 流中元素的数量为 long
.
toString()
这个方法也归结为一个 stream,它包含两个操作:map()
和 collect()
。
方法map()
是一个中级操作。它需要一个 Function
( 一个接受一个对象并生成另一个对象的函数,通常是不同类型的 )。我们可以通过使用 lambda 表达式( 如下代码所示)或使用 method reference Product::getName
来实现从产品中提取名称的函数.
所以在这种情况下,map()
会将产品流 Stream<Product>
转换为产品名称流 Stream<String>
。
方法count()
是终端操作,它需要一个Collector
(一个特殊的对象,它用元素填充可变容器流并产生流管道执行的结果).
这里我们可以使用 built-in 收集器 Collectors.joining()
,它旨在将 String
个元素连接成一个字符串。
addProduct()
虽然此方法可以添加新产品,但其中有一个逻辑流程:
- 如果产品列表已经包含给定产品,它将 return
true
,false
新 产品(这有点违反直觉).
- 它将始终将给定产品添加到列表中,无论它是否重复。因此,检查给定产品是否已存在于列表中的过程变得毫无意义。
要修复它,可以用两种方式重新实现该方法:
- 允许重复并添加每个给定的产品;
- 丢弃重复的产品。
在这两种情况下,实现都是一条语句:
return products.add(product);
但是为了拒绝重复,您需要将基础集合设为 HashSet
(请注意,此更改 不会影响 无论如何,此处列出的其余代码 )。假设 hashCode/equals
合同在 Product
class 中正确实施,方法 add
将 return false
如果重复,并且 true
如果修改了一组(即成功添加了产品)。
public class ProductInStock {
private Set<Product> products = new HashSet<>(); // if you change it to List<Products> products = new ArrayList<>(); nothing will break
public boolean addProduct(Product product) {
return products.add(product);
}
public boolean removeProduct(int code){
return products.removeIf(product -> product.getCode() == code);
}
public int productAmount(){
return products.size();
}
public int getAmountByType(String type) {
return (int) products.stream()
.filter(product -> product.getType().equals(type))
.count();
}
@Override
public String toString() {
return products.stream()
.map(product -> product.getName())
.collect(Collectors.joining("\n"));
}
}
main()
- 一个小演示。
public static void main(String[] args) {
ProductInStock productInStock = new ProductInStock();
productInStock.addProduct(new Product("Milk", 11, "dairyProducts"));
productInStock.addProduct(new Product("Milk", 11, "dairyProducts")); // duplicate
productInStock.addProduct(new Product("Milk", 11, "dairyProducts")); // duplicate
productInStock.addProduct(new Product("Cottage cheese", 12, "dairyProducts"));
productInStock.addProduct(new Product("Cream cheese", 13, "dairyProducts"));
productInStock.addProduct(new Product("Bread", 21, "bakedProducts"));
productInStock.addProduct(new Product("Muffin", 22, "bakedProducts"));
productInStock.addProduct(new Product("Cookies", 23, "bakedProducts"));
System.out.println("Amount by type");
System.out.println("Baked Products:\t" + productInStock.getAmountByType("bakedProducts"));
System.out.println("Dairy Products:\t" + productInStock.getAmountByType("dairyProducts"));
productInStock.removeProduct(12);
productInStock.removeProduct(21);
System.out.println("\nProduct Stock after removal:\n" + productInStock);
}
输出
Amount by type
Baked Products: 3
Dairy Products: 3
Product Stock after removal:
Muffin
Cookies
Milk
Cream cheese
旁注:
- 下划线的使用
_
如果 Java 中变量名的前面不符合 命名约定 。在需要区分共享相同名称的参数和字段的情况下,我们使用关键字 this
.
- 让 产品类型 由
enum
表示而不是依赖字符串值会更方便。
正如您提到的 lambda,是的,结合 Stream
或 List.removeIf
它们会有所帮助。不幸的是,它们是一项高级功能,可能会稍后处理。
展示它会对 i.a 做什么。你的 for-loops:
public class Stock {
private final List<Product> productList = new ArrayList<>();
public Stock() {
}
public Stock(List<Product> productList) {
this.productList.addAll(productList);
}
public boolean addProduct(Product product) {
return productList.contains(product) || productList.add(product);
}
//public int obtainPos(int code){
public Optional<Product> obtainByCode(int code) {
productList.stream()
.filter(pr -> pr.getCode() == code)
.findAny();
}
public void removeProduct(int code) {
obtainByCode(code).ifPresentOrElse(pr -> productList.remove(pr),
() -> System.out.println("Error - Product not found."));
}
public int productAmount() {
return productList.size();
}
public int amountType(String type) {
return (int) productList.stream()
.filter(pr -> pr.getClass().getSimpleName().equals(type))
.count();
}
@Override
public String toString() {
return productList.stream()
.map(Product::getName)
.map(nm -> nm + " \n")
.collect(Collectors.joining());
// return productList.stream()
// .map(Product::getName)
// .collect(Collectors.joining(" \n"); // At the end without \n.
}
public void itemsToRemove(String reason) {
String items = productList.stream()
.map(Product::getName)
.map(nm -> String.format("%s - %s\n", nm, reason))
.collect(Collectors.joining(" \n");
System.out.println(items);
}
}
备注:
- 我假设名字
Products
应该是 Product
。
addProduct
使用了 AND (THEN) &&
,我认为你的意思是 OR (ELSE) ||
。当产品已经存在时,添加的产品将被丢弃。不确定是否需要这样做。 Product.equals
(在代码上?)必须存在。
obtainPos
引入了一个内部动态特性(当调用 removeProduct
时)。更好的 return Optional<Product>
这是一个 type-safe 包装器;见 ifPresent
.
- 像下划线这样的前缀在 java 中没有使用。您可以使用
this.field
. 消除相同命名参数的歧义
- 未更改(=未替换)的字段可以是
final
。
- 针对接口 (
List
) 而不是实现 class (ArrayList
) 进行编程更具表现力。
- 有像
int, boolean, char, long
这样的原始类型。还有 wrapper classes Integer, Boolean, Character, Long
。使用原始类型更符合逻辑。只有通用参数类型不能这样做:List<Integer>
.
- lambda 是
pr -> pr.getName()
或方法引用 Product::getName
。
已经提到 Map<Integer, Product> productsByCode = new HashMap<>();
证明可以通过代码快速访问产品。
你们的for
循环都是完全不同的,如此多的收获是难以置信的。但是 Stream 隔离条件等,并且可能更灵活。
我有一个 class,其中有几种方法循环遍历 产品列表 。
我想知道是否有办法减少循环次数,因为它们真的只做一次检查?
某种实现函数并传递谓词的方法?我昨天发现了 Lambdas,但不确定它是否适用于此。
public class Stock {
private ArrayList<Products> _productList;
public Stock(){}
public Boolean addProduct(Products product){
return _productList.contains(product) && _productList.add(product);
}
public int obtainPos(int code){
for(int i=0; i < _productList.size(); i++)
if(_productList.get(i).getCode() == code)
return i;
return -1;
}
public void removeProduct(int code){
int pos = obtainPos(code);
if(pos >=0)
_productList.remove(pos);
else
System.out.println("Error - Product not found.");
}
public int productAmount(){ return _productList.size(); }
public int amountType(String type){
int i = 0;
for(Products pr : _productList)
if(pr.getClass().getSimpleName().equals(type))
i++;
return i;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
for(Products pr : _productList)
sb.append(pr.getName()).append(" \n");
return sb.toString();
}
public void itemsToRemove(String reason){
StringBuilder st = new StringBuilder();
for(Products pr : _productList)
st.append(pr.getName()).append(" - ").append(pr.withdraw(reason)).append("\n");
System.out.println(st.toString());
}
}
您可以在所需的单循环中使用类似 if 语句的语句来决定您要执行的操作,例如:
public void loopClass(int decision) {
for(Products pr : _productList) {
if (decision == choice1) {
//do something
}
if (decision == choice2) {
//do something else
}
//etc etc
}
}
查看您的代码,我没有发现您设置循环的方式有任何问题。您现在的设置方式看起来非常简洁明了。 我建议保持原样。
您可以使用 HashMap<Integer,Products>
而不是 ArrayList<Products>
。这将允许您消除 obtainPos
并将 addProduct
和 removeProduct
减少到 O(1) 操作。
a way to reduce the amount of loops
Some sort of way to implement a function and pass predicate? I discovered Lambdas yesterday, but not sure it would be applicable here.
如果您正在考虑使用 lambdas 作为可能的选项,您可以使此代码更加简洁。
只需要非常基础的知识关于Java8lambda表达式 和 流 。作为第一步,我建议您熟悉这个 tutorial.
让我们一一重构这些方法。
removeProduct()
您可以将方法 removeProduct()
和 obtainPos()
中的所有代码替换为 单行 通过使用 Collection
接口的方法 removeIf()
,像这样:
products.removeIf(product -> product.getCode() == code);
方法removeIf()
需要一个Predicate
(一个产生布尔值的函数),它会删除与给定谓词匹配的每个元素.
此方法 returns true
如果集合被修改并且 false
否则。
getAmountByType()
可以使用仅包含两个操作的微小流来实现此方法:filter()
和 count()
。
为了创建以产品列表作为源的流,您必须调用列表中的方法stream()
。
方法filter()
是一个中间操作(产生新流的操作)。与 removeIf()
一样,它需要一个 谓词 ,与 removeIf()
相反,它会在结果流中只保留与给定谓词匹配的元素。
而方法count()
是终端操作(关闭流的操作,return是一个值或执行最终操作,如 forEach
) 即 return 流中元素的数量为 long
.
toString()
这个方法也归结为一个 stream,它包含两个操作:map()
和 collect()
。
方法map()
是一个中级操作。它需要一个 Function
( 一个接受一个对象并生成另一个对象的函数,通常是不同类型的 )。我们可以通过使用 lambda 表达式( 如下代码所示)或使用 method reference Product::getName
来实现从产品中提取名称的函数.
所以在这种情况下,map()
会将产品流 Stream<Product>
转换为产品名称流 Stream<String>
。
方法count()
是终端操作,它需要一个Collector
(一个特殊的对象,它用元素填充可变容器流并产生流管道执行的结果).
这里我们可以使用 built-in 收集器 Collectors.joining()
,它旨在将 String
个元素连接成一个字符串。
addProduct()
虽然此方法可以添加新产品,但其中有一个逻辑流程:
- 如果产品列表已经包含给定产品,它将 return
true
,false
新 产品(这有点违反直觉). - 它将始终将给定产品添加到列表中,无论它是否重复。因此,检查给定产品是否已存在于列表中的过程变得毫无意义。
要修复它,可以用两种方式重新实现该方法:
- 允许重复并添加每个给定的产品;
- 丢弃重复的产品。
在这两种情况下,实现都是一条语句:
return products.add(product);
但是为了拒绝重复,您需要将基础集合设为 HashSet
(请注意,此更改 不会影响 无论如何,此处列出的其余代码 )。假设 hashCode/equals
合同在 Product
class 中正确实施,方法 add
将 return false
如果重复,并且 true
如果修改了一组(即成功添加了产品)。
public class ProductInStock {
private Set<Product> products = new HashSet<>(); // if you change it to List<Products> products = new ArrayList<>(); nothing will break
public boolean addProduct(Product product) {
return products.add(product);
}
public boolean removeProduct(int code){
return products.removeIf(product -> product.getCode() == code);
}
public int productAmount(){
return products.size();
}
public int getAmountByType(String type) {
return (int) products.stream()
.filter(product -> product.getType().equals(type))
.count();
}
@Override
public String toString() {
return products.stream()
.map(product -> product.getName())
.collect(Collectors.joining("\n"));
}
}
main()
- 一个小演示。
public static void main(String[] args) {
ProductInStock productInStock = new ProductInStock();
productInStock.addProduct(new Product("Milk", 11, "dairyProducts"));
productInStock.addProduct(new Product("Milk", 11, "dairyProducts")); // duplicate
productInStock.addProduct(new Product("Milk", 11, "dairyProducts")); // duplicate
productInStock.addProduct(new Product("Cottage cheese", 12, "dairyProducts"));
productInStock.addProduct(new Product("Cream cheese", 13, "dairyProducts"));
productInStock.addProduct(new Product("Bread", 21, "bakedProducts"));
productInStock.addProduct(new Product("Muffin", 22, "bakedProducts"));
productInStock.addProduct(new Product("Cookies", 23, "bakedProducts"));
System.out.println("Amount by type");
System.out.println("Baked Products:\t" + productInStock.getAmountByType("bakedProducts"));
System.out.println("Dairy Products:\t" + productInStock.getAmountByType("dairyProducts"));
productInStock.removeProduct(12);
productInStock.removeProduct(21);
System.out.println("\nProduct Stock after removal:\n" + productInStock);
}
输出
Amount by type
Baked Products: 3
Dairy Products: 3
Product Stock after removal:
Muffin
Cookies
Milk
Cream cheese
旁注:
- 下划线的使用
_
如果 Java 中变量名的前面不符合 命名约定 。在需要区分共享相同名称的参数和字段的情况下,我们使用关键字this
. - 让 产品类型 由
enum
表示而不是依赖字符串值会更方便。
正如您提到的 lambda,是的,结合 Stream
或 List.removeIf
它们会有所帮助。不幸的是,它们是一项高级功能,可能会稍后处理。
展示它会对 i.a 做什么。你的 for-loops:
public class Stock {
private final List<Product> productList = new ArrayList<>();
public Stock() {
}
public Stock(List<Product> productList) {
this.productList.addAll(productList);
}
public boolean addProduct(Product product) {
return productList.contains(product) || productList.add(product);
}
//public int obtainPos(int code){
public Optional<Product> obtainByCode(int code) {
productList.stream()
.filter(pr -> pr.getCode() == code)
.findAny();
}
public void removeProduct(int code) {
obtainByCode(code).ifPresentOrElse(pr -> productList.remove(pr),
() -> System.out.println("Error - Product not found."));
}
public int productAmount() {
return productList.size();
}
public int amountType(String type) {
return (int) productList.stream()
.filter(pr -> pr.getClass().getSimpleName().equals(type))
.count();
}
@Override
public String toString() {
return productList.stream()
.map(Product::getName)
.map(nm -> nm + " \n")
.collect(Collectors.joining());
// return productList.stream()
// .map(Product::getName)
// .collect(Collectors.joining(" \n"); // At the end without \n.
}
public void itemsToRemove(String reason) {
String items = productList.stream()
.map(Product::getName)
.map(nm -> String.format("%s - %s\n", nm, reason))
.collect(Collectors.joining(" \n");
System.out.println(items);
}
}
备注:
- 我假设名字
Products
应该是Product
。 addProduct
使用了 AND (THEN)&&
,我认为你的意思是 OR (ELSE)||
。当产品已经存在时,添加的产品将被丢弃。不确定是否需要这样做。Product.equals
(在代码上?)必须存在。obtainPos
引入了一个内部动态特性(当调用removeProduct
时)。更好的 returnOptional<Product>
这是一个 type-safe 包装器;见ifPresent
.- 像下划线这样的前缀在 java 中没有使用。您可以使用
this.field
. 消除相同命名参数的歧义
- 未更改(=未替换)的字段可以是
final
。 - 针对接口 (
List
) 而不是实现 class (ArrayList
) 进行编程更具表现力。 - 有像
int, boolean, char, long
这样的原始类型。还有 wrapper classesInteger, Boolean, Character, Long
。使用原始类型更符合逻辑。只有通用参数类型不能这样做:List<Integer>
. - lambda 是
pr -> pr.getName()
或方法引用Product::getName
。
已经提到 Map<Integer, Product> productsByCode = new HashMap<>();
证明可以通过代码快速访问产品。
你们的for
循环都是完全不同的,如此多的收获是难以置信的。但是 Stream 隔离条件等,并且可能更灵活。