Why shouldn't you remove the 'final' attribute from the Vector and Quaternion classes

I got the idea from the Ardor3D engine (which basically came from a fork of JME) to implement the ReadOnlyVector and ReadOnlyQuaternion classes. The current final constants Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z, Quaternion.IDENTITY etc… are not really unmodifiable.

It is easy for a beginner to make a mistake and write e.g. Vector3f.UNIT_XYZ.multLocal(5f)
This changes the value of the constant irreparably.

Instead, with the proposed change, I can extend the VectorX and Quaternion classes with their respective ReadOnlyX versions, whose internal variables are truly immutable.

By overriding the xxxLocal methods so that an UnsupportedOperationException is raised, the code is clearer and safer.

What do you guys think? If you agree I will open an issue on git.

Use Case:

public class ReadOnlyVector3f extends Vector3f { 

    @Override
    public Vector3f addLocal(Vector3f vec) {
        throw new UnsupportedOperationException();
    }

    // throw UnsupportedOperationException for all xxxLocal methods
}
public class ReadOnlyQuaternion extends Quaternion {

    @Override
    public Quaternion addLocal(Quaternion q) {
        throw new UnsupportedOperationException();
    }

    // throw UnsupportedOperationException for all xxxLocal methods
}

I wondered about this, and did something like this with some of my own vector classes. But are there issues with java deep magic. In particular virtual method calls. I know java is able to autodetect if a virtual method call is necessary or if it can use a cheap direct call. Might the presence of a ReadOnlyVector3f on the classpath cause performance issues as all those now polymorphic method calls become virtual. I’m not an expert in this but something to think about.

I would also question the possible optimization downsides of adding more virtual implementations of the modifier methods. I’d probably go the other way - make an “unmodifiable” (protected fields with no mutator methods) vector class that Vector3f extends from, with mutating methods only existing on Vector3f. I don’t believe that introduces potential loss of de-virtualization.

1 Like

Final is faster. That’s the reason.

Virtual methods on non-final classes are slower.

JME is absolutely not not not not designed to protect you from every possible mistake you might make. It’s meant to be as fast as it can be in the language it’s written.

2 Likes

Regarding this specifically, I thought someone wrote an app state that would constantly check all of the ‘constants’ to see if they drifted. That way the beginners can just take the performance hit for some extra safety and everyone else can be as fast as possible.

1 Like

This is trickier than it sounds.

The public fields of Vector3f are also faster than methods. For mutable classes, this is also a lot simpler than having to call v.getX() everywhere you really want v.x.

And in Java, fields are not ‘overridable’… so you can’t have a private field in one class and a public one that overrides it, or vice versa. So basically, there is no way to extend Vector3f to have read-only fields.

It might be helpful someday for someone to put together a wiki page of the threads where this topic comes up every 2 years or so.

Edit: corrected “fields are overridable” to “fields are NOT overridable” because that important typo turned my sentence to nonsense. lol

That’s what I was thinking. In my opinion, if classes like VectorX, Quaternion, ColorRGBA, etc have to be immutable, there is no point on extending the existing ones – local methods lose their purpose. Creating new classes makes more sense to me (e.g. ImmutableVector3f, ImmutableQuaternion).

BTW, not being pedantic, but I think that the technical term for what we are talking about here is “immutable objects”, not read-only, not unmodifiable.

Yeah… but then they can’t be used in all of the places that the regular classes can.

This is kind of an unsolvable problem.

Choice 1: Immutable Classes

  • add a bunch of classes and complexity
  • lose the performance benefits of final classes
  • lose the performance benefits of public fields
  • lose the serialization benefits of private classes.
  • make the classes a bit more confusing to use.

Only benefits: save a few new users from themselves… leaving 100 other major pitfalls that there is nothing you can do anything about because at the end of the day on some level “game development is hard”.

Choice 2: Do Nothing

  • some unaware users stumble over a standard OOP problem.

Can by mitigated with a training wheels app state that checks their values every frame to see when they’ve driven off into the ditch.

I wrote the above from memory but I feel like I’ve written this exact response at least once before… maybe twice.

3 Likes

@pspeed explanation is certainly clear enough for me to understand that there are more costs than benefits. I choose option 2 and rename the thread title by adding the [wiki] attribute, in case anyone else in the future has doubts about why this classes have the ‘final’ attribute.

Thanks everyone for your support.

Edit:

Here’s that class:

4 Likes

I thought someone wrote an app state that would constantly check all of the ‘constants’ to see if they drifted.

Indeed.

I believe the app state you’re remembering is ConstantVerifierState, and you are credited as the author.

Note that if one bases one’s applications on SimpleApplication and uses the default constructor, a ConstantVerifierState is automatically instantiated and attached:

In other words, the training wheels are included by default!

1 Like

I have no memory of writing this… but it is 100% my style and has my notes in it. So I definitely did.

I’ve finally reached that age, I guess.

1 Like

Here is some additional info:

Yes, virtual methods on non-final classes in Java can be slightly slower compared to non-virtual methods or virtual methods on final classes. However, the difference is usually negligible for most applications and may not be a crucial factor in most cases. Here’s why:

Reason for potential slowness:

  • Dynamic dispatch: Virtual methods rely on dynamic dispatch, where the actual implementing method is determined at runtime based on the object’s type. This requires an additional lookup through the virtual table, which involves memory access and can incur a small overhead compared to direct method calls.
  • Limited compiler optimizations: Since the compiler doesn’t know the exact method implementation until runtime for virtual methods, it might not be able to perform certain optimizations like inlining, which can further impact performance.

Factors affecting the impact:

  • Frequency of virtual calls: The overall performance impact depends on how often virtual methods are called. A few sporadic calls might not make a noticeable difference.
  • Method complexity: Simpler methods likely have a smaller penalty compared to complex methods with heavy calculations.
  • JVM optimizations: Modern JVMs employ techniques like HotSpot’s devirtualization to eliminate dynamic dispatch overhead for frequently executed methods if the target method is always the same.

When to consider performance impact:

While the performance difference in virtual vs. non-virtual methods in Java is generally small, there are specific scenarios where it can be critical:

  • Performance-critical applications: If you’re dealing with time-sensitive operations where every microsecond counts, optimizing virtual calls might be important.
  • Tight loops: If virtual methods are called within tight loops, the accumulated overhead might become significant.

Alternatives and considerations:

  • Final classes: Declaring a class as final prevents inheritance and allows the compiler to perform more optimizations, including potentially resolving virtual calls at compile time.
  • Interface methods: Interfaces have inherent dynamic dispatch but are often used for contracts, not core logic. Consider static methods in interfaces for performance-sensitive operations.
  • Profiling: Always measure and profile your code to identify actual performance bottlenecks before focusing on optimizing virtual methods. The performance gain might not be worth the design trade-offs for most cases.

Remember: Optimizing for performance without a specific need and proper benchmarking can lead to premature optimization and potentially hinder maintainability. Use virtual methods when design principles like polymorphism and inheritance are essential, and only consider performance implications if profiling reveals it’s a critical bottleneck.

2 Likes

And the public fields make any virtualization considerations less relevant anyway.

Whether Vector3f is final or not, the fields are public and so you cannot really make an immutable version of the class.

…and a similar full page of prose could probably be written on the performance difference between public fields versus private fields. Including how they are “minor in some cases”, really effective in “A B C” cases, etc…

And the intersection of both descriptions is that the times where the performance benefits of final methods and public fields are the most relevant are precisely the places where JME benefits.

Quaternion could theoretically be virtualized and given an immutable version because the fields are private. But as pointed out above (in capdevon’s nice summary and other posts), there is the potential for a performance impact by doing so… for almost no benefit.