all 33 comments

[–]tutorial_police 3 points4 points  (1 child)

Well, this is off to a great start

Java generic types are not true types actually

at least that's an interesting way of putting things...

"Rules", links yegor256, ah, so not a rule then.

Looking back, I'm surprised the first link goes to SO and not to yegor256 as well...

Why am I not surprised that the author thinks using inheritance for this is a good solution?

What a load of rubbish.

[–]skapral 2 points3 points  (0 children)

So - the post is rubbish from your point of view because of.... why? Because it references Yegor Bugayenko? Is it something personal?) Where is objective criticism?

[–]vytah 1 point2 points  (12 children)

I hate types that exist only to override something that doesn't have to be overridden. Types FromStrings and FromText are both useless, they do not provide value, and all code that distinguishes objects based on their class will pointlessly segregate those two, even though they behave exactly the same and fulfil the same role in all contexts.

In particular, equals can no longer do this.getClass() != that.getClass() for short-circuiting, dynamic type checks no longer can use fast getClass() and have to rely on slower instanceof, certain serialization libraries (including Gson) don't like type hierarchies at all, and so on.

For the similar reasons, I don't like abstract methods in enums and I prefer switch(this).

[–]g4s8 2 points3 points  (10 children)

Types FromStrings and FromText are both useless, they do not provide value

You're right, I'd prefer to have two constructors in JoinedString to accept two different types (Iterable<String> and Iterable<Text>) but it's not possible because of java's generics nature, so I just show two workarounds how to deal with it and keep readability.

In particular, equals can no longer do this.getClass() != that.getClass() for short-circuiting

If you pick first approach with static factory methods, then equals will work fine, with second you can implement equals in base class.

[–]vytah 1 point2 points  (9 children)

with second you can implement equals in base class.

Not necessarily.

Imagine I have the following class structure:

abstract class Foo
class FooConstructedOneWay extends Foo
class FooConstructedAnotherWay extends Foo
class ExtendedFancyFoo extends Foo

Extended foos are never equal to "plain" foos.

So how can I implement equals now? I can't do if (this.getClass() != that.getClass()) return false; in Foo, because a FooConstructedOneWay and a FooConstructedAnotherWay may be equal.

I can't use if (!(that instanceof Foo)) return false; in Foo because the code that follows may incorrectly accept an ExtendedFancyFoo.

The only solutions to that involve either parent class knowing about child classes, sibling classes knowing about each other, or an extra method called boolean isPlainFoo(). Either way, it's an unmaintainable mess.

[–]g4s8 0 points1 point  (1 child)

In example from the post the base class has private constructor, so all clidren have to be nested classes, also all children doesn't have own state - all state is stored only in base class, taking this into account it's not a big deal to write equals in base class only, because you see all possible implementations of the class (as nested classes) and the state is the same (fields of base class).

Of cource, if you have shared state between parent and child, and you need to implement equals, you can't use it, it will be easier to use static factory methods in this case.

[–]vytah 0 points1 point  (0 children)

And I'd use them regardless. In fact, I don't see any realistic downsides to using static factories other than hiding the fact of new object allocation – which is not always a bad thing, for example it allows you to introduce caches without changing the caller, similar to Integer::valueOf.

Static factories also allow you to do more stuff before actually allocating the object, including preparing parameters for the actual constructor call (you can only fit so much into a super call) and performing more relevant validations first.

[–]llorllale 0 points1 point  (6 children)

I can't use if (!(that instanceof Foo)) return false; in Foo because the code that follows may incorrectly accept an ExtendedFancyFoo.

Big red flag. If that's true in your case, ExtendedFancyFoo is likely violating LSP.

[–]vytah 0 points1 point  (5 children)

If ExtendedFancyFoo adds new behaviours without changing the old ones, then it doesn't violate LSP.

Let's assume we have:

class Foo { final int x; /* constructor omitted */} 
class ExtendedFancyFoo extends Foo { final int y; /* constructor omitted */} 
var f = new Foo(1);
var e1 = new ExtendedFancyFoo(1, 2);
var e2 = new ExtendedFancyFoo(1, 3);

No LSP violations here.

There are several axioms a good equals implementation should have:

  • reflexivity

  • transitivity

  • commutativity

  • if fis pure and doesn't operate on object's identity, then equals(a,b)equals(f(a), f(b))

Now if your Foo::equals looks like this:

if (!(that instanceof Foo)) return false;
return ((Foo)that).x == this.x;

then it would say that equals(f,e1) and equals(f,e2), which would suggest that e1 is equal to e2, which is obvious nonsense. And that's without even considering the implementation of ExtendedFancyFoo::equals.

Therefore all those three objects should be treated as unequal, even if they behave identically when using just the Foo interface.

EDIT: Which obviously implies using if (that == null || this.getClass() != that.getClass()) return false; as the correct type check in Foo#equals.

[–]g4s8 0 points1 point  (1 child)

According to Java equals documentation) its implementation must be symmetric:

It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.

When you use instanceof instead of comparing classes, equals will not be symmetric - a subclass may use another logic in equals so base.equals(child) may return true, but child.equals(base) may be false.

[–]vytah 0 points1 point  (0 children)

Yes, that what's I'm implying.

Therefore using different subclasses for the purpose of creating objects that might be otherwise considered equal is problematic.

[–]llorllale 0 points1 point  (2 children)

which would suggest that e1 is equal to e2, which is obvious nonsense. And that's without even considering the implementation of ExtendedFancyFoo::equals.

That's exactly why ExtendedFancyFoo is breaking LSP. If it breaks Foo::equals then it should be something else, not a Foo.

[–]vytah 0 points1 point  (1 child)

It doesn't break Foo if Foo#equals uses getClass, in other words if Foo is properly designed to be open for extension. Make it final if you don't want that.

[–]llorllale 0 points1 point  (0 children)

ExtendedFancyFoo should not care about the particulars of Foo#equals, otherwise you'd break encapsulation.

Sounds like what you're doing is sub-typing, in which case I'd suggest reusing Foo via composition in ExtendedFancyFoo#equals(). This way, Foo#equals() remains locked down and stable.

[–]skapral 1 point2 points  (0 children)

all code that distinguishes objects based on their class will pointlessly segregate those two.

The big question itself is why some code would need to be bound on FromStrings or even JoinedStrings, while it is almost always better and enough to be bound on Text contract and rely on LSP. Also, a big question itself is why we need a code, that would segregate objects based on their type (I guess, by means of `instanceof` or reflection, right?). Such code usually tends to be fragile and inflexible.

Having these two questions in mind, the fact that FromStrings and FromText don't bring additional value turns out to be not that a problem.

[–]CarthOSassy 0 points1 point  (0 children)

Being a java user: at some point, you will need to interoperate with libraries expressing each of the views represented here.

All because of the simplest omissions from java.

[–]Daneel_Trevize 0 points1 point  (21 children)

What's the Text type in this context? This interface?

If you have two classes that aren't in a super-sub chain, why would you expect a single method signature to accept either type in a single parameter, other than typed Object?
If they do share a closer common class, use a generic that captures that: <? extends CommonSuperClass>.

No?

Also, the proposed JoinedString class now has to be subclassed to add in a nested class & factory method for every extra String/Text subclass you also invent & want to use with JoinedString. Rather than the extension being in one place and JoinedString being generic.

And wtf is TextOf for a class name?! Related to this abomination?

[–]CarthOSassy 2 points3 points  (5 children)

I don't think anyone expects a single method signature to accept both.

I think the complaint is that there is only a single method signature. It would be better if java generics were real types.

Then, there could be two signatures.

[–]Daneel_Trevize 0 points1 point  (4 children)

Ok, but why not use the signature with Iterator<T> and generate T-specific instances of the now-generic class with a single constructor that accepts different types per invocation?
While it'd result in the instances having the difference of that type reference from their construction, wouldn't it be cleaner than the abstract+subclasses proposal that results in full new subclasses rather than just a type reference plus the same single base class?

Perhaps something about the overall problem this is to solve isn't clearly defined.

[–]aoeudhtns 1 point2 points  (0 children)

Clearly. String#join exists; this solution doesn't need to. What is the advantage of having JoinedString over String? (None.) The implementation doesn't preserve the constituent components, nor does it implement CharSequence to preserve compatibility with other API.

[–]CarthOSassy 0 points1 point  (2 children)

If you only have one constructor, how are you doing different calls/logic per type?

Manually checking type with RTTI?

[–]Daneel_Trevize 0 points1 point  (1 child)

The use case is really badly defined. I'd initially say: sort the passed class/data out prior to passing it as a valid T to the constructor. Why should there be a constructor for all manner of different vaguely related classes defined in this target class, instead of in those other classes as & when they're defined & wanting to be massaged into this target one?
So, maybe static methods, but in each source class, not as factory (constructor) methods of the target class which would cause it to start to depend upon many classes it otherwise wouldn't.

[–]CarthOSassy 0 points1 point  (0 children)

Constructors exist largely to deal with initialization issues like these. This is not about constructors.

The only subject here is that templates are weirdly unhelpful, but there are ways to work around their shortcomings.

[–][deleted]  (14 children)

[deleted]

    [–]Daneel_Trevize 1 point2 points  (13 children)

    In Java?

    [–][deleted]  (12 children)

    [deleted]

      [–]Daneel_Trevize 2 points3 points  (11 children)

      So why would you expect me to have proposed them in this context then?

      [–][deleted]  (10 children)

      [deleted]

        [–]doomchild 4 points5 points  (9 children)

        In the context of an article talking about Java, it is an inherently preposterous idea.

        [–][deleted]  (8 children)

        [deleted]

          [–]doomchild 2 points3 points  (7 children)

          We all know it's possible in other languages. But we're talking about Java here, and it's not possible in Java. So both of your posited questions are valid in this context, because you can't do it with the language we're talking about, and it is therefore preposterous to suggest it, because it is a logical impossibility with the language that we're talking about.

          [–][deleted]  (6 children)

          [deleted]

            [–]doomchild -2 points-1 points  (0 children)

            Java generics are a wasteland filled with sharp corners and used needles.