I am currently solving this issue. For example. I have two classes. One for item and second for some kind of records of these items. What I need to do, if I want to have a few methods for get average of different kind from all items in collection. In my example, there is average of price, mass and volume. The methods are repetitive and the only one different thing is the getter. So, is there any option, how to have one private method for counting and another three public, that will use this one private method and somehow put the field as argument?
Item class
public class Item {
private double price;
private double mass;
private double volume;
public double getPrice() {
return price;
}
public double getMass() {
return mass;
}
public double getVolume() {
return volume;
}
}
Stock class
package model;
import java.util.ArrayList;
import java.util.List;
public class Stock {
private List<Item> items;
public Stock() {
items = new ArrayList<>();
}
public double getAveragePrice() {
double sum = 0;
if (!items.isEmpty()) {
for (Item item : items) {
sum += item.getPrice();
}
return sum / items.size();
}
return sum;
}
public double getAverageMass() {
double sum = 0;
if (!items.isEmpty()) {
for (Item item : items) {
sum += item.getMass();
}
return sum / items.size();
}
return sum;
}
public double getAverageVolume() {
double sum = 0;
if (!items.isEmpty()) {
for (Item item : items) {
sum += item.getVolume();
}
return sum / items.size();
}
return sum;
}
}
As Johannes Kuhn proposed in a comment, you can pass the getter as a ToDoubleFunction<Item> to your calculation method:
private double calcAverage(ToDoubleFunction<Item> getter) {
double sum = 0;
if (!items.isEmpty()) {
for (Item item : items) {
sum += getter.applyAsDouble(item);
}
return sum / items.size();
}
return sum;
}
public double getAveragePrice() {
return calcAverage(Item::getPrice);
}
You can then reuse the calculation:
public double getAverageMass() {
return calcAverage(Item::getMass);
}
public double getAverageVolume() {
return calcAverage(Item::getVolume);
}
Here is a solution using stream API
public double getAverageMass() {
return calcAverage(Item::getMass);
}
public double getAverageVolume() {
return calcAverage(Item::getVolume);
}
private double calcAverage(ToDoubleFunction<Item> getter) {
return items.stream().mapToDouble(getter).average().orElse(0);
}
Related
I am trying to understand this clean code practice with an example. Consider a class Product having switch case for discount. I am trying to replace switch statement with polymorphism.
Before code:
class Product {
String priceCode;
int discount;
Product(String priceCode) {
setDiscount(priceCode);
}
public int getDiscount() {
return discount;
}
public void setDiscount(String priceCode) {
switch (priceCode) {
case "CODE1":
discount = // some logic;
case "CODE2":
discount = // some other logic;
case "CODE3":
discount = // some other logic;
}
}
}
In below code as you can see I removed switch statement but I still have if conditions to create an object of discountStrategy.
My question is I still have if conditions which I am trying to remove with Polymorphism.
After code:
class Product {
String priceCode;
DiscountStrategy discountStrategy;
Product(String priceCode) {
setDiscount(priceCode);
}
public int getDiscount() {
return discountStrategy.getDiscount();
}
public void setDiscount(String priceCode) {
if (priceCode.equals("CODE1")) {
discountStrategy = new DiscountStrategy1();
} else if (priceCode.equals("CODE2")) {
discountStrategy = new DiscountStrategy2();
}
// ...
}
}
interface DiscountStrategy {
public int getDiscount();
}
class DiscountStrategy1 implements DiscountStrategy {
public int getDiscount() {
// calculate & return discount;
}
}
class DiscountStrategy2 implements DiscountStrategy {
public int getDiscount() {
// calculate & return discount;
}
}
class DiscountStrategy3 implements DiscountStrategy {
public int getDiscount() {
// calculate & return discount;
}
}
Can you please help me understand this concept with better implementation of this example?
I think that Product class must not be aware about the discount creation process, it should only use a discount. So, my suggestion is to create a discount factory with a Map that will hold different discount implementations:
class DiscountFactory {
private static final Map<String, DiscountStrategy> strategies = new HashMap<>();
private static final DiscountStrategy DEFAULT_STRATEGY = () -> 0;
static {
strategies.put("code1", () -> 10);
strategies.put("code2", () -> 20);
}
public DiscountStrategy getDiscountStrategy(String priceCode) {
if (!strategies.containsKey(priceCode)) {
return DEFAULT_STRATEGY;
}
return strategies.get(priceCode);
}
}
After that, the Product class can be simplified:
class Product {
private DiscountStrategy discountStrategy;
Product(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public int getDiscount() {
return discountStrategy.getDiscount();
}
}
Functional interface will allow you to create different implementations using lambda expressions:
interface DiscountStrategy {
int getDiscount();
}
And finally, example of the use of a product together with discount:
DiscountFactory factory = new DiscountFactory();
Product product = new Product(factory.getDiscountStrategy("code1"));
My two cents:
You will need to pass the parameters to discount() method.
a. Create a static class level HashMap of DiscountStrategy.
E.g :
map.put("CODE1", new DiscountStrategy1());
map.put("CODE2", new DiscountStrategy2());
b. wherever you need, you can simply use:
map.get(priceCode).discount()
Here is what you need to do
class Product {
String priceCode;
DiscountStrategy discountStrategy;
HashMap<String, DiscountStrategy> map=new HashMap();
Product(String priceCode) {
map.put("CODE1", new DiscountStrategy1());
map.put("CODE2", new DiscountStrategy2());
map.put("CODE3", new DiscountStrategy3());
setDiscount(priceCode);
}
public void setDiscount(String priceCode){
discountStrategy=map.get(priceCode);
}
public int getDiscount() {
return discountStrategy.getDiscount();
}
}
When, as is seems to be the case in Your example, the discount strategy is bound to a specific product type, I would compute the discount at the order item level.
For instance:
class Product {
double basePrice;
DiscountStrategy discountStrategy;
...
public double getBasePrice() {
return basePrice;
}
public DiscountStrategy getDiscountStrategy() {
return discountStrategy;
}
}
interface DiscountStrategy {
public double calculate(int quantity, Product product);
}
class OrderItem {
int quantity;
Product product;
public double getAmount() {
DiscountStrategy ds = product.getDiscountStrategy();
double discount = ds.calculate(quantity, product);
return quantity*(product.getBasePrice() - discount);
}
}
Example of discount strategy: quantity discount:
class QuantityRateDiscount implements DiscountStrategy {
static class QuantityRate {
final int minQuantity;
final double rate; // in %
QuantityRate(int minQuantity, double rate) {
this.minQuantity = minQuantity;
this.rate = rate;
}
}
QuantityRate[] rateTable;
// rateTable must be sorted by ascending minQuantity
QuantityRateDiscount(QuantityRate... rateTable) {
this.rateTable = rateRable.clone();
}
#Override
public double calculate(int quantity, Product product) {
QuantityRate qr = null;
for (QuantityRate qr2: rateTable) {
if (qr2.minQuantity > quantity) {
break;
}
qr = qr2;
}
if (qr != null) {
return product.getBasePrice()*qr.rate/100.0;
} else {
return 0;
}
}
}
I am trying to write a program that will create a food order consisting of food items displayed from a menu. Each item is selected and given a certain quantity. I want to display the items that I select in a JTextField but it has not been working correctly.
There are a few problems that I have ran into and cannot seem to figure out,
The JOptionPane is supposed to display all of the items that I added to the deli arraylist, but it only displays the first one which is Nachos.
My getTotalPrice method is not properly calculating the cost and I'm not entirely sure why.
I want the program to determine if an item is already present in the Arraylist and add to the quantity if it does, and if not then add a new entry to the arraylist. However, it always adds a new item, regardless of if it exists already.
The following is my are all of my class files.
import java.util.ArrayList;
public class Menu {
private final ArrayList<Item> menu;
public Menu() {
menu = new ArrayList<>();
}
public void addItem(Item item) {
menu.add(item);
}
public Item getItem(int itemNo) {
if (menu.size() > itemNo) {
return menu.get(itemNo);
}
return null;
}
#Override
public String toString() {
for (int i = 0; i < menu.size(); i++) {
return String.format("%s: %s \n",i+1, menu.get(i));
}
return null;
}
}
public class Item {
private final String name;
private final double price;
public Item(String name, double price) {
this.name = name;
this.price = price;
}
public double getPrice() {
return price;
}
#Override
public String toString() {
return String.format("Name %s # Price $%s", name, price);
}
public boolean equals(Item item) {
return item.name.equals(item.name);
}
}
public class ItemQty {
private final Item item;
private final int quantity;
public ItemQty(Item item, int quantity) {
this.item = item;
this.quantity = quantity;
}
public Item getItem() {
return item;
}
public int getQuantity() {
return quantity;
}
#Override
public String toString() {
return String.format("%s - %s\n", quantity, item);
}
public boolean equals(ItemQty itemQty) {
return itemQty.getItem().equals(itemQty.getItem());
}
}
import java.util.ArrayList;
public class Order {
private final ArrayList<ItemQty> order;
public Order() {
order = new ArrayList<>();
}
public void addToOrder(ItemQty itemQty) {
if (order.contains(itemQty)) {
int amount = itemQty.getQuantity();
amount += 1;
}
else
order.add(itemQty);
}
public double getTotalPrice() {
for (int index = 0; index < order.size(); index++) {
double price = order.get(index).getItem().getPrice();
int quantity = order.get(index).getQuantity();
double sum = price * quantity;
return sum;
}
return 0;
}
#Override
public String toString() {
String str = "";
for (int index = 0; index < order.size(); index++) {
str += order.get(index).toString() + "\n\n";
}
return str;
}
}
Any help or critiques would be appreciated
My getTotalPrice method is not properly calculating the cost and I'm not entirely sure why.
This is due to the fact that you're returning the value of sum only after the first iteration of your loop
public double getTotalPrice() {
for (int index = 0; index < order.size(); index++) {
double price = order.get(index).getItem().getPrice();
int quantity = order.get(index).getQuantity();
double sum = price * quantity;
return sum;
}
return 0;
}
Something like...
public double getTotalPrice() {
double sum = 0;
for (Order item : order) {
double price = item.getItem().getPrice();
int quantity = item.getQuantity();
sum += (price * quantity);
}
return sum;
}
would work better
The JOptionPane is supposed to display all of the items that I added to the deli arraylist, but it only displays the first one which is Nachos.
Since there is no JOptionPane in your code, it's impossible to know what the issue might be
I want the program to determine if an item is already present in the Arraylist and add to the quantity if it does, and if not then add a new entry to the arraylist. However, it always adds a new item, regardless of if it exists already.
Okay, this is a lot more difficult, because you code doesn't really provide enough support to do it.
There's no way for your code to update the quantity information after the ItemQty is created, you will need to supply a setter of some kind to perform this action (or a add method, to which you pass another ItemQty and it does the job for you)
First, I'd add a new method to ItemQty
public class ItemQty {
//...
public void add(int quantity) {
this.quantity += quantity;
}
}
This just makes it possible to increase the quantity.
Second, I'd change the Order#addToOrder, I'd make it so you had to pass an Item and a quantity to it (other classes don't need to make a ItemQty object in this case). In this method, I'd search for a matching item and either update it or add it to the order.
public class Order {
//...
public void addToOrder(Item item, int quantity) {
List<ItemQty> matches = order.stream().filter((itemQty) -> {
return itemQty.getItem().equals(item);
}).collect(Collectors.toList());
if (matches.size() > 0) {
matches.get(0).add(quantity);
} else {
order.add(new ItemQty(item, quantity));
}
}
Okay, that might have you scratching your head, it does me, but basically, it's just a fancy pancy way for saying...
public void addToOrder(Item item, int quantity) {
ItemQty match = null;
for (ItemQty check : order) {
if (check.getItem().equals(item)) {
match = check;
break;
}
}
if (match != null) {
match.add(quantity);
} else {
order.add(new ItemQty(item, quantity));
}
}
So I am having problem with a method I created, called averageCost(). The error I get is:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method doubleValue() is undefined for the type Double
at queue.averageCost(queue.java:38)
at queue.main(queue.java:47)
And previously I tried just adding a double value to a Double object which obviously didn't work. So I found the .doubleValue method but I can't get it to work. I added all my code (everything else works just fine) in case that clears anything up. I have been stuck on this for a couple days now while working on other stuff please help ):
import java.util.LinkedList;
public class queue<Double> {
private LinkedList<Double> linklist;
private String symbol;
private String name;
private int shares;
private Double price;
public queue(String symbol2, String name2, int shares2, Double price2) { //so far, I have a linkedlist that works
linklist = new LinkedList<Double>();
shares = shares2;
price = price2;
symbol = symbol2;
name = name2;
bigAdd(shares, price);
}
public void add(Double e) {
linklist.add(e);
}
public Double take() {
return linklist.poll();
}
public void bigAdd (int shares2, Double price2) {
while(shares2>0) {
linklist.add(price2);
shares2--;
}
}
public double averageCost(int shares2) {
double average = 0;
int sizer = 0;
while(sizer < shares2) {
average = average + linklist.poll().doubleValue(); //this is where the problem lies
sizer++;
}
average = average/shares2;
return average;
}
}
Your mistake comes from the class declaration.
Formal parameter of generic may be T:
public class queue<T> {
Now you see that T has no method named doubleValue because you haven't constrained it.
The following do what you want:
import java.util.LinkedList;
public class queue<T extends Number> {
private final LinkedList<T> linklist;
private final int shares;
private final T price;
public queue( int shares2, T price2 ) {
linklist = new LinkedList<>();
shares = shares2;
price = price2;
bigAdd( shares, price );
}
public void add( T e ) {
linklist.add(e);
}
public T take() {
return linklist.poll();
}
public void bigAdd( int shares2, T price2 ) {
while(shares2>0) {
linklist.add(price2);
shares2--;
}
}
public double averageCost( int shares2 ) {
double average = 0;
int sizer = 0;
while( sizer < shares2 ) {
final T v = linklist.poll();
average = average + v.doubleValue();
sizer++;
}
average = average/shares2;
return average;
}
}
You can just use linklist.poll(). The Double will get unboxed to a double.
I want to do a sum for all over a Collection of numbers. But I want to indicate the return number type. For example I want to do the sum of a double collection, but I want to get back an Integer.
I want to have something like this SumAggregator. Here is the code I develop, but I have a Cast problem.
public class SumAggregator<N1 extends Number, N2 extends Number> {
public SumAggregator() {
}
public N2 sum(Collection<? extends N1> list){
Double sum = 0;
for(Number n : list){
sum += n.doubleValue();
}
return (N2) sum;
}
}
If I want to do SumAggregator<Double, Double> I don't have any problem. But if I want to do SumAggregator<Double, Integer>, once I run I get the following exception:
java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
Any idea of how to solve this?
Not exactly what you wanted but you could do something like:
interface DoubleConverter<N extends Number) {
N2 convert(double d);
}
And add a constructor to your SumAggregator:
private final DoubleConverter<N2> converter;
public SumAggregator(DoubleConverter<N2> converter) {
this.converter = converter;
}
And your method would look like:
public N2 sum(Collection<? extends N1> list){
double sum = 0;
for(Number n : list){
sum += n.doubleValue();
}
return converter.convert(sum);
}
finally, you would create one with:
SumAggregator<Double, Integer> a = new SumAggregator<> (new DoubleConverter<Integer>() {
public Integer convert(double d) { return (int) d; }
});
Finally, you could add some helper methods in the DoubleConverter interface:
DoubleConverter<Integer> INTEGER = new DoubleConverter<Integer>() {
public Integer convert(double d) { return (int) d; }
}
so the calling code can use:
SumAggregator<Double, Integer> a = new SumAggregator<> (DoubleConverter.INTEGER);
This is the solution I choose:
public class SumAggregator<N1 extends Number, N2 extends Number> {
private final NumberConverter<N2> converter;
public SumAggregator(NumberConverter<N2> converter) {
this.converter = converter;
}
public N2 sum(Collection<? extends N1> list){
double sum = 0;
for(Number n : list){
sum += n.doubleValue();
}
return converter.convert(sum);
}
}
And then:
public interface NumberConverter<N2> {
N2 convert(Number d);
NumberConverter<Short> SHORT = new NumberConverter<Short>() {
public Short convert(Number d) {
return d.shortValue();
}
};
NumberConverter<Integer> INTEGER = new NumberConverter<Integer>() {
public Integer convert(Number d) {
return d.intValue();
}
};
NumberConverter<Double> DOUBLE = new NumberConverter<Double>() {
public Double convert(Number d) {
return d.doubleValue();
}
};
NumberConverter<Float> FLOAT = new NumberConverter<Float>() {
public Float convert(Number d) {
return d.floatValue();
}
};
NumberConverter<Long> LONG = new NumberConverter<Long>() {
public Long convert(Number d) {
return d.longValue();
}
};
}
I would like to create a JSpinner which can take every possible Double value between a specified minimum and a specified maximum.
Also, the JSpinner should be able to display a text instead of a specific value. Let's say our JSpinner can take values from -1 to 10. I would like to display a text, e.g. "Auto", instead of -1 .
How to replace by
Here is the Model I wrote, but it seems not to be enough, because it says in JSpinner there is an error because the text is not a Double.
public class SpinnerSpecialModel
extends AbstractSpinnerModel implements SpinnerMinMaxModel {
public static final double DEFAULT_MINIMUM = 0.0;
public static final double DEFAULT_MAXIMUM = Double.POSITIVE_INFINITY;
public static final double DEFAULT_STEP = 1.0;
public static final double DEFAULT_VALUE = 1.0;
public static final double DEFAULT_SPECIAL_NUMBER = -1.0;
public static final String DEFAULT_SPECIAL_TEXT = "Auto";
private double maximum;
private double minimum;
private double stepSize;
private double currentNumber;
private double specialNumber;
private String specialText;
private Object m_Value;
public SpinnerSpecialModel(double max, double min, double step, double num,
double specialNum, String specialTxt) {
maximum = max;
minimum = min;
stepSize = step;
currentNumber = num;
specialNumber = specialNum;
specialText = specialTxt;
setAccurateValue(num);
}
public SpinnerSpecialModel(double specialNum, String specialTxt) {
this(DEFAULT_MAXIMUM, DEFAULT_MINIMUM,
DEFAULT_STEP, DEFAULT_VALUE, specialNum, specialTxt);
}
public SpinnerSpecialModel() {
this(DEFAULT_SPECIAL_NUMBER, DEFAULT_SPECIAL_TEXT);
}
#Override
public Object getValue() {
if (currentNumber == specialNumber) {
m_Value = specialText;
}
else {
m_Value = currentNumber;
}
return m_Value;
}
#Override
public void setValue(Object value) {
setAccurateValue(value);
}
private void setAccurateValue(Object value) {
if (value instanceof Double) {
double doubleValue = (Double) value;
if (doubleValue != currentNumber) {
if (doubleValue == specialNumber) {
currentNumber = specialNumber;
m_Value = specialText;
}
else if (doubleValue > maximum) {
currentNumber = maximum;
m_Value = maximum;
}
else if (doubleValue < minimum) {
currentNumber = maximum;
m_Value = minimum;
}
else {
currentNumber = doubleValue;
m_Value = doubleValue;
}
fireStateChanged();
}
}
if (value instanceof String) {
String stringValue = (String) value;
if (stringValue.equals(specialText)) {
this.currentNumber = specialNumber;
this.m_Value = specialText;
fireStateChanged();
}
}
}
#Override
public Object getNextValue() {
return getNewValue(+1);
}
#Override
public Object getPreviousValue() {
return getNewValue(-1);
}
/**
*
* #param direction
* #return
*/
private Object getNewValue(int direction) {
double newValue = currentNumber + direction * stepSize;
setAccurateValue(newValue);
return m_Value;
}
#Override
public double getMaximum() {
return maximum;
}
#Override
public double getMinimum() {
return minimum;
}
#Override
public double getStepSize() {
return stepSize;
}
#Override
public void setMaximum(double max) {
maximum = max;
}
#Override
public void setMinimum(double min) {
minimum = min;
}
#Override
public void setStepSize(double step) {
stepSize = step;
}
}
The best and proper way to do this is not as simple as just writing a model, but it is not very complicated. You actually need to write an Editor and a Formatter to have a true MVC spinner:
A class that extends JSpinner : SpecialValuesSpinner.
A class that implements SpinnerModel : SpecialValuesSpinnerModel
A class that extends DefaultEditor and implements DocumentListener : SpecialValuesSpinnerEditor
A class that extends NumberFormatter : SpecialValuesSpinnerFormatter
I am not going to show you the code for all classes, but here is basically what you have to do in each :
SpecialValuesSpinner :
public class SpecialValuesSpinner() extends SpinnerNumberModel {
// in your constructor do this
setModel(new SpecialValuesSpinnerModel(YOUR_SPECIAL_VALUES);
setEditor(new SpecialValuesSpinnerEditor());
}
SpecialValuesSpinnerModel :
public class SpinnerSpecialValuesModel() extends JSpinner {
// in this class you handle the fact that now, you have an
// interval of values and a list of special values that are allowed.
// here is what I did :
#Override
public Object getNextValue() {
return incrValue(+1);
}
#Override
public Object getPreviousValue() {
return incrValue(-1);
}
private Object incrValue(int dir) {
// NB : BigDecimal here because this is what I used,
// but use what you want in your model
BigDecimal result = null;
BigDecimal numberBD = new BigDecimal(getNumber().toString());
BigDecimal stepSizeBD = new BigDecimal(getStepSize().toString());
BigDecimal dirBD = new BigDecimal(dir);
BigDecimal nextValue = numberBD.add(stepSizeBD.multiply(dirBD));
TreeSet<BigDecimal> currentAllowedValues = new TreeSet<BigDecimal>();
currentAllowedValues.addAll(m_SpecialValues);
if (getMaximum() != null) {
currentAllowedValues.add((BigDecimal) getMaximum());
}
if (getMinimum() != null) {
currentAllowedValues.add((BigDecimal) getMinimum());
}
if (isIncludedInBounds(nextValue)) {
currentAllowedValues.add(nextValue);
}
if (dir > 0) {
try {
result = currentAllowedValues.higher(numberBD);
}
catch (NoSuchElementException e) {}
}
else if (dir < 0) {
try {
result = currentAllowedValues.lower(numberBD);
}
catch (NoSuchElementException e) {}
}
return result;
}
}
In SpecialValuesSpinnerEditor, we use Document Listener to have autocompletion (easy to do, just search on SO).
public class SpecialValuesSpinnerEditor extends DefaultEditor implements DocumentListener {
// You have to do in your contructor
SpecialValuesSpinnerFormatter formatter =
new SpecialValuesSpinnerFormatter (spinner.getSpecialValues(), format);
getTextField().setFormatterFactory(new DefaultFormatterFactory(formatter));
}
And now, the most important, the Formatter which does conversion between user input (string) and numbers, and handle the model's display :
public class SpecialValuesSpinnerFormatter extends NumberFormatter {
// Just override the methos StringToValue and ValueToString.
// You can check here if the value is special
// i.e you must display its special text instead. e.g. : "Auto" instead of -1
}
I think you can achieve that by implementing your own SpinnerModel and supplying that as argument to the JSpinner constructor.