3

I'm writing a program in Java where I need to represent the position, scale, and other 3-dimensional properties of objects in a world using vectors.

I can use either of these two approaches:

  1. Represent vectors as an array of doubles with 3 elements (procedural approach)
  2. Represent vectors using a dedicated class, with doubles x, y, and z as members, along with methods that can create or manipulate vectors. (object-oriented approach)

What are the advantages and disadvantages of each approach (i.e, how do they affect performance, readability, efficiency, etc.)?

4
  • 3
    Do you want to work with vecs of arbitrary dimensionality? Use an array. This is common when you're not working with geometrical vectors, but e.g. with feature vectors as in statistics or ML. Or is your code inherently 3D? Then, explicit x, y, z fields will be much easier to use since they provide clear meaning. And if you're already defining a class, you can add helpful methods like norm() or dot() to it. But this isn't really OOP, and probably shouldn't be. Sometimes, classes are just a helpful way to structure your data, no fancy principles involved.
    – amon
    Commented Jan 10 at 18:43
  • 2
    If you are going to use a preexisting linear algebra library (for matrix multiplication) so that you can transform your vectors, and if you are going to do this a lot in a tight loop (e.g., if you're making a game or something like that), then look up the docs, and see if it requires an array, or if it already provides its own vector data structure, and use that - otherwise you'll likely have to copy the values to convert between the formats, or resort to some other similarly ugly workaround. Commented Jan 11 at 2:03
  • Array of Structs vs Struct of Arrays could be relevant here. See for example: stackoverflow.com/q/40163722/1051292
    – Kevin
    Commented Jan 11 at 5:40
  • I would go with vectors and never look back. I wonder, how you do linear algebra primitive operations, like dot product. If then you need point, you can always wrap vector into Point structure
    – RedEyed
    Commented Jan 18 at 16:30

4 Answers 4

7

A lot of this depends on where the data is coming from and what you need to do with it. For example, if all you were doing is reading it in from a file and passing it to a calculation, you might not need an object.

The problem with the first option is that it can be very difficult to keep track of which index refers to what. There's a lot of cognitive overhead of understanding such code especially when there are a lot of properties. E.g., What was index 7, again? Was it width or depth?

The problem with the second option is that if you need to represent the data as a vector, you take on the overhead of creating these objects and copying the data to and from the arrays. If you have a large dataset, this could be problematic. If you are working with a limited number of arrays, it probably won't matter.

There's a third option that you don't mention. Instead of a class definition with dedicated fields for each element in a given vector, you can create a class which accepts (a reference to) an array of doubles and 'understands' the context and meaning of each index in the array. A simplified example follows:

class PointVector {
    private final double[] data;

    /* A: to create a new vector with all elements initialized to 0 */
    PointVector() {
        this.data = new double[3];
    }

    /* B: To construct a new vector from parameters */
    PointVector(dobule x, double y, double z) {
        this.data = new double[]{x, y, z};
    }

    /* To create an object wrapping an existing vector */
    PointVector(double[] data) {
        if (data.length < 3) throw new AnExceptionOfSomeType("bad input");
        this.data = data;
    }

    public double getX() {
        return data[0];
    }

    public double getY() {
        return data[1];
    }

    public double getZ() {
        return data[2];
    }

    /* if you want/need to modify data ... */
    public double setX(double value) {
        data[0] = value;
    }

    public double setY(double value) {
        data[1] = value;
    }

    public double setZ(double value) {
        data[2] = value;
    }

    /* 
     * if you uses constructors like A and B,
     * you'll probably need a way pass the array out.
     */
    public double[] asArray() {
        return data;
    }
}

Again, this is a simplified example and you would adapt it to your needs. For example, you might want to create brand new vectors from this class instead of simply accepting them.

The idea here is that you can avoid the overhead of converting to and from the vector representation while being able to work with the data in an a more self-documenting and understandable way.

This should be common knowledge to any Java developer but it's key to understand that, in Java, and other similar languages, the elements of arrays are always mutable. If your requirement is that you must work with raw arrays at some level and you want to use this approach, you should avoid manipulating the data outside of these objects if possible. Doing so will make the program much more difficult to reason about.

One thing that may or may not be obvious here is that, instead of an immutable reference to the data array, you could make it possible to use the same instance and repoint it on different arrays. This would mean you don't need to create an object for every array you need to work with. This might make sense for your purposes, but I would caution that this kind of thing can have some unintuitive impacts on performance. In order for that to be effective, you would need to keep one or more instances of this object around for a while and objects that live for long time before they are eligible for GC can be more expensive to manage that objects that have very short lifespans. So, if you were to use one or more 'sliding window' objects, I would make them permanent for the life of the application. Java tends to work best with fruit flies and highlanders. Try to avoid objects that live for many GC cycles and then go away.

12
  • I'm not quite convinced that the class in the snippet needs to use an array internally, isn't your only concern here that the constructor takes in an array, and it doesn't matter whether the class internally stores it as an array or already deconstructs it into individual values?
    – Flater
    Commented Jan 10 at 22:31
  • @Flater The point in the snippet is that the values are never copied into other registers. I thought I made that clear in the explanation but if you are still unsure, I can try to elaborate. There's no additional overhead for the data elements.
    – JimmyJames
    Commented Jan 10 at 22:35
  • 1
    Sorry I should've elaborated my core point better: you're making it a final, which implies that you think of it as immutable, but arrays are passed by reference, therefore tainting the immutability as anyone with a prior reference can still mutate the array. When you then consider instantiating a new array in this instance and copying the content over (thus breaking the original array reference and making the new array truly immutable), the question is raised whether there's a need to use an array internally, instead of separate ints.
    – Flater
    Commented Jan 10 at 22:56
  • 1
    The one caveat to my observation would be if you intended for your array reference to be final but somehow being okay with the array itself still being mutable by outside actors; which seems unlikely. Additionally, proponents of FP, who are generally the pro-array people in this scenario, tend to be very allergic to this kind of mutability as well.
    – Flater
    Commented Jan 10 at 22:59
  • 1
    @Flater is there an expectation in Java that final is deeply immutable? C++ const is known to not flow through pointers, so IME the expectation isn't there. I would have thought because most things are passed by reference in Java the expectation would be that final isn't immutable
    – Caleth
    Commented Jan 12 at 9:35
5

Math is a very deterministic field that often focuses on the ability to figure something out, not the effort of having to actually do so. A mathematician will be perfectly happy to see a 3D vector represented as (X,Y,Z) coordinates. "Direction and magnitude can be trivially deduced from the coordinates", they will say. They won't be interested in writing down the direction and magnitude, because if they ever want to adjust one of the coordinate values, they run the risk of forgetting to update the direction or magnitude accordingly.

But OOP developers have a different focus. They're trying to thematically group all things that relate to eachother. They are also aware that they can group these concepts in a way that one will always be calculated on the fly from another, so they're not particularly worried about data contradictions (provided they design the system correctly).
"I want to be able to trivially convert these coordinates to a direction and magnitude so I can easily have it both ways", they will say. They won't mind writing down both the cartesian coordinates (X/Y/Z) and the polar coordinates (direction/magnitude) because they are able to ensure that consistency between the two is guaranteed to be maintained.

So, are you a mathematician or an OOP developer? What I mean by that is that hardcore functional programmers often take the mathematician's approach here, whereas OOP developers tend to favor the latter approach.

What are the advantages and disadvantages of each approach (i.e, how do they affect performance, readability, efficiency, etc.)?

Efficiency

  • Just a coordinate array takes less time to design. This positively impacts the time needed to deliver the first release, but becomes relatively irrelevant in terms of delivering future releases.
  • A dedicated class maximizes reusability. This takes more design (and testing) effort, but once built and validated, it can significantly speed up future work by being able to rely on the reusable behaviors. This will negatively impact the time to deliver the first release but can pay back exponential dividends for any and all future releases.

Performance

  • Before asking the impact on performance, establish that you actually have a performance problem. The vast majority of cases will not care about this level of performance optimization, and this should not be your primary focus.

Readability

  • OOP is a double edged sword. When done well, it can dramatically help the readability of the code by compartmentalizing the individual behaviors and self-documenting them. When done badly, it can make things dramatically more enigmatic when compared to the raw formulae.
  • It mainly hinges on what you're familiar with. Mathematicians will innately expect to see coordinate sets, OOP developers will innately expect a dedicated type that holds the related logic.

Overall, I want to conclude that you're putting the cart before the horse here. You're asking us to give you the universally correct and objective pros and cons list, so that you can then decide what makes sense for you. The risk you're running here is that online strangers do not know anything about your use case and specific context, so any advice can be unknowingly misguided.

I strongly suggest doing it the other way. Figure out what makes the most sense to you, in your specific use case and in your specific context. When you have that list of expectations, then look for the approach that best matches your expectations.

Instead of asking:

I've got these two approaches, which one is better?

you're going to get a better result if you rephrase that train of thought to something along the lines of:

I need [this], [that] and [those], what approach would best suit these requirements?

It will help ensure that you make the right decision for the right reason, instead of potentially getting distracted with a pro/con that doesn't actually matter much for your current use case.

2
  • Actually, mathematicians avoid representing vectors as coordinates. They instead work with abstract vector spaces as much as possible for the interesting derivations, and only expand in a basis at the end when going to concrete examples. It's in many ways similar to OO thinking. Except OO isn't really great for expressing mathematical abstractions like vector spaces, this is far more convenient in functional programming languages. Commented Jan 11 at 10:04
  • @leftaroundabout: You're not wrong but you've glossed over the context of the posted question.
    – Flater
    Commented Jan 11 at 23:24
2

This is a classic problem. The answer lies with change.

As your solution grows will you be adding more and more operations between these objects? If so a functional solution will likely serve you better (doesn't have to be procedural).

If you know the operations now, so they are unlikely to change, then a object oriented solution will likely serve you better.

An object oriented solution makes it easy to add new types of data. Not new operations. That doesn't mean object oriented code can't handle new operations at all (see visitor pattern). It just means it's more of a hassle.

2

I am a library author for general purpose 2D and 3D vector math (in C#, which is comparable to Java) by myself. We used and evolved this lib for almost two decades very successfully in a lot of different applications, with a business context of measurements, surveying and CAD modeling.

When I see this requirement

I'm writing a program in Java where I need to represent the position, scale, and other 3-dimensional properties of objects in a world using vectors.

my first choice would usually be your approach #2 of creating a dedicated class:

Represent vectors using a dedicated class, with doubles x, y, and z as members, along with methods that can create or manipulate vectors. (object-oriented approach)

If that approach isn't sufficient in regards to performance, one could still switch to something more optimized.

If you know for sure you need to process hundreds of millions of such points in parallel with some GPU, maybe for a simulation or some 3D virtual reality application, then you will probably start different (but then I guess Java would also not be your first choice, or you were using already something specialized like an OpenGL API which tells you how to represent the vectors).

Some lessons learned:

  1. It is really beneficial to make such a class immutable! The ability of passing points around as references without any need to care for side effects will save you hours and days of debugging time.

  2. There is usually no point in making two different classes for 3D vectors and 3D points. Mathematically, vectors can be often interpreted as points and vice versa, there is no strict separation. With two classes, there is some risk of having to implement a certain amount of duplicate or very similar code. One will also require conversions between points and vectors all over the code which can be avoided with having just one class.

Note also at the time when we started with our lib, there was no adequate 3D vector lib available for the .Net world. Today, you may first check for existing libraries. In the Java world, this Vector3D class seems to be pretty standard (though they decided to use two different classes, there is also Point3D, both derived from Tuple3D - I think is unneccessary).

So it is usually a good idea to check if this (or another) existing library is sufficient for most of your typical use cases.

See also my answer here:

Maybe also of interest:

2
  • Actually, this project is a 3D game engine, and Java was not my first choice. I tried to make it in C++, but that felt like a waste of time, so I switched to Java. (I'm starting to regret that decision, considering the lack of high-level graphics libraries for Java, but at least the actual engine part is easier to make now.)
    – Bunabyte
    Commented Jan 11 at 16:35
  • @AcinonX: still you should have an idea about the kind of games you want to support and the typical use cases for you lib. Those should form your design.
    – Doc Brown
    Commented Jan 11 at 16:54

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.