6
\$\begingroup\$

I've seen in Java a class vector implemented, it's very useful because you can declare vectors without say the size of the vector explicitly, and it has a lot of functions operating with its elements. But it hasn't implemented mathematical operations: sum, subtract, inner and outer products and so on. So I've made and extended class with these math operators and functions. Here's the code:

package vectors.com;

import java.util.Collection;
import java.util.Vector;

public class Vectors extends Vector<Double> {
    private static final long serialVersionUID = 1L;
    
    public Vectors() {super();}

    public Vectors(Collection<? extends Double> c) {super(c);}

    public Vectors(int initialCapacity, int capacityIncrement) {super(initialCapacity, capacityIncrement);}

    public Vectors(int initialCapacity) {super(initialCapacity);}

    public static long getSerialversionuid() {return serialVersionUID;}

    public Vectors opposite() {
        for(int i = 0; i < this.size(); ++i) this.set(i, -this.get(i));
        return this;
    }
    
    public Vectors sum(Vectors w) {
        if(this.size() != w.size()) throw new IllegalArgumentException("The dimmensions must be equals.");
        Vector<Double> v = new Vector<Double>();
        Vectors z = new Vectors(v);
        Double f = 0.;
        for(int i = 0; i < this.size(); ++i) {
            f = this.get(i) + w.get(i);
            z.add(i, f);
        }
        return z;
    }
    
    public Vectors subtract(Vectors w) {
        if(this.size() != w.size()) throw new IllegalArgumentException("The dimmensions must be equals.");
        return this.sum(w.opposite());
    }
    
    public Vectors externProduct(Double lambda) {
        for(int i = 0; i < this.size(); ++i) {
            this.set(i, lambda * this.get(i));
        }
        return this;
    }
    
    public Double scalarProduct(Vectors w) {
        if(this.size() != w.size()) throw new IllegalArgumentException("The dimmensions must be equals.");
        Double s = 0.;
        for(int i = 0; i < this.size(); ++i) {
            s += this.get(i) * w.get(i);
        }
        return s;
    }
    
    public Double absolute() {
        Double radicand = 0.;
        for(int i = 0; i < this.size(); ++i) radicand += Math.pow(this.get(i), 2);
        return Math.sqrt(radicand);
    }
    
    public Double angle(Vectors w) {
        return Math.acos(this.scalarProduct(w) / (this.absolute() * w.absolute()));
    }
    
    public Vectors makeVector(Vectors w) {
        return w.subtract(this);
    }
    
    private Double determinant(Double[][] A) {
        int rows = A.length;
        int columns = A[0].length;
        if(rows != columns) throw new IllegalArgumentException("Rows and Columns must be equals.");
        if(rows == 1) return A[0][0];
        if(rows == 2) return A[0][0] * A[1][1] - A[1][0] * A[0][1];
        return A[0][0] * A[1][1] * A[2][2] + A[1][0] * A[2][1] * A[0][2] + A[0][1] * A[1][2] * A[2][0] -
                ( A[0][2] * A[1][1] * A[2][0] + A[0][1] * A[1][0] * A[2][2] + A[1][2] * A[2][1] * A[0][0] );
    }
    
    public Vectors vectorialProduct(Vectors w) {
        if(this.size() != w.size()) throw new IllegalArgumentException("Number of components must be equals.");
        if(this.size() != 3) throw new IllegalArgumentException("Sorry, only for 3D vectors.");
        Double[][] A = {{this.get(1), this.get(2)}, {w.get(1), w.get(2)}};
        Double[][] B = {{this.get(0), this.get(2)}, {w.get(0), w.get(2)}};
        Double[][] C = {{this.get(0), this.get(1)}, {w.get(0), w.get(1)}};
        Vector<Double> z = new Vector<Double>();
        Vectors z1 = new Vectors(z);
        z1.set(0, determinant(A));
        z1.set(1, determinant(B));
        z1.set(2, determinant(C));
        return z1;
    }
    
    public Double mixProduct(Vectors v, Vectors w) {
        if(this.size() != v.size() || this.size() != w.size() || v.size() != w.size())
            throw new IllegalArgumentException("Number of components must be equals.");
        if(this.size() != 3 || v.size() != 3 || this.size() != 3)
            throw new IllegalArgumentException("Sorry, only for 3D vectors.");
        Double[][] A = new Double[3][3];
        for(int i = 0; i < this.size(); ++i) {
            A[0][i] = this.get(i);
            A[1][i] = v.get(i);
            A[2][i] = w.get(i);
        }
        return determinant(A);
    }

    @Override
    public int hashCode() {return super.hashCode();}

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!super.equals(obj)) return false;
        if (getClass() != obj.getClass()) return false;
        return true;
    }
}

Thanks

\$\endgroup\$
1
  • \$\begingroup\$ This Vector class has nothing to do with mathematical vectors, but more with vector class from C++. \$\endgroup\$
    – convert
    Commented Feb 7, 2022 at 23:44

4 Answers 4

10
\$\begingroup\$

In addition to Doi9t's valuable answer, I'd like to comment on overall design.

Your Vectors class defines methods for mathematical vector operations. I question wether it's a good idea to extend java.util.Vector.

How many of the methods you inherit from Vector are meaningful to your interpretation as a mathematical vector? Some examples:

  • How often have you extended a 5-dimensional vector to become 6-dimensional, by adding some number at the last position (add() method)?
  • How often have you searched a vector to contain some given value, regardless of the position (contains() method)?

In my opinion, methods like these don't make sense for mathematical vectors.

Extending Vector means that all the methods from that class also become part of your visible public API, and will only confuse potential users of your class, giving them a hard time sorting out what is useful and what not.

For this reason, I'd recommend not to extend Vector, but to keep your class independent from the Java Collections framework. If it makes implementation easier for you, you can introduce a Vector-typed field to store the elements.

What I like about your code:

  • You throw IllegalArgumentExceptions in all the right places.
  • When you do some math, you return a new vector, and don't modify an existing one, effectively treating Vectors as an immutable class. But this only applies to your own methods, the Vector methods like set(), add() and remove() are still publicly available, breaking that property - another reason not to extend Vector.
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Unfortunalty your last point is not true. For example, opposite() mutates the object. \$\endgroup\$
    – RoToRa
    Commented Nov 13, 2020 at 15:14
  • 1
    \$\begingroup\$ I 100% agree. java.util.Vector has nothing at all to do with the vectors we are talking about here. It's simply the Java designers struggling to find a synonym for "dynamically resizable array". \$\endgroup\$ Commented Nov 14, 2020 at 11:26
  • \$\begingroup\$ @RoToRa You're right, I didn't recognize that. \$\endgroup\$ Commented Nov 16, 2020 at 9:28
6
\$\begingroup\$

To mutate or not to mutate?

One problematic thing you are doing, is being inconsistent in whether you mutate (modify, change) the underlying list of numbers or not, and not telling the user about it.

For example, opposite() modifies the list, but sum() does not. This can be quite confusing for the user of the class and a source of bugs. At the very least you need to clearly document (using JavaDoc) when you modify something and possibly choose methods names that also reflect it (more about the method names later). Better would be, if you were consistent and either allways modify in all methods or never modify in any method. Preferable you should choose to never modify the list (read up on immutable objects and their advantages).

Even worse in some cases such as subtract() you modify the vector passed in as a parameter. Having a class modify it's method parameters is even more unexpected to the user, and should never be done.

Extending Vector

Java's Vector is for one an out of date, legacy class, which long as been replaced by ArrayList. There is no reason to use it anymore.

More importantly it is questionable if you should extend from any class at all. The problem is, aside from Vector and ArrayList there are other list-like objects in Java (and many of its libraries), ad by extending one such class you are limiting the user, so that they may need to copy all the data into your Vectors class and when finished copy them back out.

Instead if would make sense to not extend any class, but just let the user pass in a List object into your constructor (List is an interface that is implemented by many list-like classes) and work with that as the data source.

Better names

Your object and most of your methods are badly named. The object name Vectors suggests it contains multiple vectors, although it contains one.

Also the method names either don't use or incorrectly use mathematical terms. For example, opposite is a relativly uncommon word. What it does is usually refered to as negate or invert. And sum sounds like it sums up all numbers in the list. Something like addToEach would be clearer.

\$\endgroup\$
5
\$\begingroup\$

Always add curly braces to loop & if

In my opinion, it's a bad practice to have a block of code not surrounded by curly braces; I saw so many bugs in my career related to that, if you forget to add the braces when adding code, you break the logic / semantic of the code.

Extract the expression to variables when used multiple times.

In your code, you can extract the similar expressions into variables; this will make the code shorter and easier to read.

Before

if (this.size() != v.size() || this.size() != w.size() || v.size() != w.size())
   throw new IllegalArgumentException("Number of components must be equals.");
if (this.size() != 3 || v.size() != 3 || this.size() != 3)
   throw new IllegalArgumentException("Sorry, only for 3D vectors.");

After

int currentSize = this.size();
int firstSize = first.size();
int secondSize = second.size();

if (currentSize != firstSize || currentSize != secondSize || firstSize != secondSize) {
   throw new IllegalArgumentException("Number of components must be equals.");
}

if (currentSize != 3 || firstSize != 3 || currentSize != 3) {
   throw new IllegalArgumentException("Sorry, only for 3D vectors.");
}

Replace the for loop with an enhanced 'for' loop

In your code, you don’t actually need the index provided by the loop, you can the enhanced version.

Before

for (int i = 0; i < this.size(); ++i) {
   radicand += Math.pow(this.get(i), 2);
}

Before

for (Double currentDouble : this) {
   radicand += Math.pow(currentDouble, 2);
}

Always use the primitives when possible

When you know that it's impossible to get a null value with the number, try to use the primitives; this can prevent the unboxing of the value in some case.

Before

Double f = 0.;

After

double f = 0.;

Parameter names

I suggest that you name your parameters with a meaningful name.

Before

    public Vectors sum(Vectors w) {}

After

    public Vectors sum(Vectors other) {}

Single letter variable tends to make the code harder to read in must case.

Java naming convention

  • Variable and parameter name should be in camelCase style

Before

 private Double determinant(Double[][] A) {}

After

 private Double determinant(Double[][] a) {}

Use the multiplication operator instead of Math.Pow

The Math.Pow tend to be slower in some case when compared with the multiplication operator.

In my opinion, it's best to use the multiplication operator when the exponent < 5.

Before

for (int i = 0; i < this.size(); ++i) {
   radicand += Math.pow(this.get(i), 2);
}

After

for (Double currentDouble : this) {
   radicand += (currentDouble * currentDouble);
}
\$\endgroup\$
0
\$\begingroup\$

I would like to add that this is really pointless:

public static long getSerialversionuid() {return serialVersionUID;}

No one should ever need to call that. This is only for the serialization process.

See https://stackoverflow.com/questions/285793/what-is-a-serialversionuid-and-why-should-i-use-it

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.