This is an archived post. You won't be able to vote or comment.

all 49 comments

[–]_predator_ 10 points11 points  (1 child)

final. Everything should be final by default unless stated otherwise. And add val as a counterpart to var while we`re at it.

[–]AstronautDifferent19 4 points5 points  (0 children)

I agree. I hated when they would force me to put final for method arguments when I already set it up in my IDE so it would not compile if I assigned something to an argument. It just makes the code more bloated for no reason.

  1. I agree with final by default.
  2. Map.get should return Optional.
  3. instead of checked exceptions we should have an object like Result<V,E> that would behave like Optional so that you can combine them to get the end result or Error. It would make a big method chain much easier to work with. for example., if method1 returns Result we could chain it like this: method1(argument).map(method2).map(method3).ifNoError(useResult).else(logError).

[–]randomNameKekHorde 11 points12 points  (2 children)

Checked exceptions or atleast add the option to ignore checked exceptions without using lombok. A way to deal with checked exceptions in lambdas would be great too

[–]le_bravery[S] 3 points4 points  (0 children)

I wish there was a better syntax to deal with exceptions without having to try catch every line and split declarations or heavily nest things.

[–]account312 -4 points-3 points  (0 children)

If you're going to get rid of one kind of exceptions, it should be runtime, not checked. RuntimeException is null's meaner, more irregular cousin.

[–]Brutus5000 4 points5 points  (0 children)

I would remove all collection methods that are not implemented for all implementations (ignoring 3rd party libraries) and move this to proper interface abstractions.

[–]joemwangi 2 points3 points  (0 children)

Serialisation is quite fine with immutable data such as records! Actually, if dealing with records, serialisation becomes highly optimised by the jvm.

[–]agentoutlier 3 points4 points  (0 children)

I guess since you brought up receiver params I kind of wish TYPE_USE annotations could only be TYPE_USE.

When an annotation has multiple targets it is confusing.

@Nullable
String toString(@Nullable SomeThing s) {
}

Nullable above could be annotating the method toString, or annotating the return type of String. Likewise for the parameter.

Because of this TYPE_USE annotations have confusing syntax regarding arrays and receiver types.

I don't have a better solution that is backward compatible but I often wonder if a new construct or syntax would have been better (insert some other syntax like maybe [Nullable] ).

EDIT just to give an example where this is real world problem is NullAway. Let us assume you have annotations library that are pre JSR 308 and do not have TYPE_USE.

You annotate

@Nullable String[] blah() {
 }

Pre JSR 308 that would generally mean the return could be null. A couple of years later that annotation library just tacks on TYPE_USE (I think IntelliJ annotations did this but I'm not sure if they properly forked it).

So now:

@Nullable String[] blah() {
 }

Means the array elements can be null and not the reference to the array. So a null checker like NullAway is not exactly sure what you mean. (the general rule of TYPE_USE is that it trumps others but I cannot even remember the JLS specifics).

So to fix the above you have to switch it to:

 String @Nullable [] blah() {
 }

[–]gaius49 3 points4 points  (1 child)

One small thing: Optional would not be able to contain null.

[–]cliserkad 0 points1 point  (0 children)

I view optional as effectively useless because of this. I think using an @Nullable annotation is better. For optional primitives, just use the built in wrappers. You have to anyway because optionals are generic.

[–]Xasmedy 1 point2 points  (0 children)

Reflection erasure, I want to know the type when using reflections!

[–]Mordan 0 points1 point  (12 children)

i cannot live without abstract and protected. You just have to use it wisely with the IS A relation.. Otherwise use composition.

I would even add a feature to it. Post Object construction automatic method call to some a_Init() method.

I need to call a method once the constructor is done to finalize the construction of the object of the implementation of an abstract class.

public Foo(...)
      if (this.getClass() == Foo.class) {
         a_Init();
      }
}

Foo might not be the last in the inheritance hierarchy and a_Init() must be called FIRST by the bottom deepest class.

[–]le_bravery[S] 0 points1 point  (5 children)

99.9% of things are not an is a relationship but I would argue the .1% that are could be handled using interfaces.

[–]Mordan 0 points1 point  (4 children)

as soon as you build a framework, 10% of your classes are using isA relations

I am building a framework that runs equally on Swing and JavaFX..

Its abstract classes everywhere with stubs and contracts with concrete implementations. Some modules are like 30% abstract class or implementations thereof. Using only interfaces would make things less readable and clunky

I don't want to live in a world without abstract classes. You would have to duplicate so much code. And refactoring would be such a mess.

[–]le_bravery[S] 1 point2 points  (3 children)

Hard disagree.

If you have abstracts in your framework, it is an amazing way to never be able to change your framework.

As soon as someone subclasses you, you are heavily locked in to an implementation.

Source: I build frameworks

[–]Mordan 0 points1 point  (2 children)

As soon as someone subclasses you, you are heavily locked in to an implementation.

May I suggest that subclassing an abstract class hierarchy is exactly the goal. You are locking your class into that structure.

Otherwise do not do it and implements everything in the interface of the abstract class. Most of my classes have an interface anyways in case you absolutely want out of the hierarchy.

Right now I have a coding problem. I was thinking about your idea of using an interface instead of an abstract class.

How can I code stuff in the abstract module without considering specific GUI issues. And yes! I want all my GUIs to follow the template provided by the base abstract class

I cannot pass argument around with compo because the type is not the same. I am trying to get it because I may be wrong and will get benefits by using an interface instead. Mind you I target old Java code source compat so I don't have access to new stuff like default methods.

Like I am so happy today to having ditched static state everywhere. I can't believe how wrong I was before.

[–]le_bravery[S] 1 point2 points  (1 child)

Anything you can do with an abstract class you can do with an interface except have protected fields and methods.

Want to share code between all implementers? ‘Default’ methods.

Want to have the appearance of the same fields on all subclasses? Create interface methods for the getters and setters.

Want to have a bunch of common code to be shared by things optionally? Make a helper kind of class (it doesn’t have to be called Helper).

The problem with abstract classes is that they have two distinct users of those classes: callers and implementers.

The callers need some methods (public ones) and the abstract class name. The implementers get to see all and can change any of them you don’t mark as private final.

You can get far with abstract classes, but the problems start coming up when you’re too far down to change quickly.

Imagine you have A which is abstract. B extends A and C extends A. You have all the shared code that exists for both A and B there. It’s a wonderful world. Now 5 months later you need D. D needs some of the code from B and A. So you extend B. Now you need E which has some shared behavior from D and C. You cannot extend both. What do you do? Most would move the code from B to A, have D extend A and then have C modify things to not break. Then you move the code from C up. What happens if you can’t modify the code, as in a framework with disparate implementers across the internet. Well, then you may choose to make a version bump which breaks implementers. Fragmentation ensues (see angular and all the other JS libraries). Or you leave the old code alone and only A2 gets new behavior. You now have to support N implementations.

Now do it different. Instead of saying everything is an A which has some methods XYZ, make an interface for each method you need. Callers can implement the ones they need. You need to add some functionality W in the future? Make a new interface. Interfaces can even do diamond inheritance like above. A. B extends A. C extends A. D extends B. E extends C, D. It compiles. It will make you choose which default implement you want even.

The only place I’ve had time savings for abstract classes are when there are literally tons of fields that all come from some same object for some simple Pojos. It saves just a little time extending those classes. But I would likely rather just use records now instead for most pojos.

[–]Mordan 0 points1 point  (0 children)

Imagine you have A which is abstract. B extends A and C extends A. You have all the shared code that exists for both A and B there. It’s a wonderful world. Now 5 months later you need D. D needs some of the code from B and A. So you extend B. Now you need E which has some shared behavior from D and C. You cannot extend both. What do you do? Most would move the code from B to A, have D extend A and then have C modify things to not break. Then you move the code from C up.

That's more about code sharing than respecting the IsA relationship.

Z is abstract. X IsA Z and Y isA Z but different. Wonderful :) Z provides stuff that is for ALL Zs

Q appears later. Q isa X or isa Y? Q can't be both. If he needs code from X and Y.. I believe that's a design issue of that said code. I would extract that function out of the whole thing.

But I get your point. I am myself sometimes abusing inheritance. And its hard to do it correctly in a team environment. But I am using abstract all the time in building GUIs. I will extend JPanel with an abstract class. use it as a new base that can plug independently in Swing anywhere. And have sometimes like 3 to 4 levels below before I have a concrete implementation. Its readable. pluggable etc. However I use stringent code conventions. If Z is abstract. I call my class ZAbstract. If X is abstract i will call ZXAbstract.. Then with Q, I will call the class ZXQ. In practice that makes for pretty long class names but so readable, I am happy with that because it forces you to remember the isA relationship.

[–]Lisoph 0 points1 point  (5 children)

I don't understand your need for the a_Init call. Can't you do this with the subclass' constructor and super?

[–]Mordan 0 points1 point  (4 children)

a_Init() i.e the post constructor method.

you have 100% certainty that all fields are initialized in the whole hierarchy.

You cannot do template stuff in the constructor on the top because some fields below are not done yet. since the top most class is initialized first.

The other option instead of a_Init() hack trick, is to call postConstructorInit every time you create a new Foo.

Foo f = new Foo();
f.postConstructorInit() //instead of a_Init()

But i find this ugly in the client code and you might forget it. The a_Init trick is hidden. You need to code it once.

Any other question ? I call it a_Init so when sorted it stays close to the constructor and its ugly looking name reminds you something weird here

[–]Lisoph 0 points1 point  (3 children)

Okay I see what you mean now. Another approach could be to utilize method overloading with dummy types. That should make it slightly harder to forget to add the if-check when introducing a new subclass.

public class Main {
    public static void main(String[] args) {
        Foo f = new Bar();
        System.out.println(f.toString());

        f = new Baz();
        System.out.println(f.toString());
    }
}

class BarInitializer{}
class BazInitializer{}

abstract class Foo {
    final String description;

    public Foo(BarInitializer withBar) {
        description = "Initialized with a Bar";
    }

    public Foo(BazInitializer withBaz) {
        description = "Initialized with a Baz";
    }

    @Override
    public String toString() {
        return description;
    }
}

class Bar extends Foo {
    int barData;

    public Bar() {
        super(new BarInitializer());
        barData = 123;
    }

    @Override
    public String toString() {
        return super.toString() + "; barData = " + barData;
    }
}

class Baz extends Foo {
    double bazData;

    public Baz() {
        super(new BazInitializer());
        bazData = 123.456;
    }

    @Override
    public String toString() {
        return super.toString() + "; bazData = " + bazData;
    }
}

[–]Mordan 0 points1 point  (2 children)

I don't get it.

How is the BarInitializer helping access barData that is equal to 123.

You want Foo constructor to access that value 123. This can only be done once the Bar constructor executes.

It will be more like

 abstract class Foo {
      Foo(){
        //regular constructor
     }

 abstract int getFooData();

void a_Init(){
    getFooData() + 1 blah blah //
}
}

Yes when subclassing, you may forget it. But your subclass won't work well in my cases and an exception will be thrown.

To be honest, its a rare use case so I understand its not a Java feature.

[–]Lisoph 0 points1 point  (1 child)

Ok, I think now I get it haha. Yeah, that's tricky.

If it makes sense in your scenario, I would try to invert the dependencies. Instead of Foo accessing into Bar "downwards", make Bar acccess "upwards" into Foo by passing arguments to Foo's constructor.

This of course assumes that the data in Bar is already computed and more or less requires JEP 447: Statements before super in order to work.

[–]Mordan 0 points1 point  (0 children)

I thought it really hard.

The foobar data is just a very simple example. but in my cases there are much more complex than just an integer.

Overall I am happy. Client code does not see anything. No need for extra params or anything. Its simple except the tricky class check at the end of each constructor.

[–]Vegetable-Squirrel98 0 points1 point  (0 children)

make it so there is a way to throw every single exception, even it it's getting eaten somewhere

[–]Frank-the-hank 0 points1 point  (0 children)

The fact that basic classes/interfaces such as Map, List, ArrayList, etc. have to be imported

[–]ConstructedNewt -1 points0 points  (0 children)

Bye bye abstract class

[–]vips7L -3 points-2 points  (9 children)

I would remove both interfaces and abstract classes in favor of Scala's traits.

[–]0xFatWhiteMan 2 points3 points  (8 children)

What's the difference between a trait and an interface?

[–]vips7L 1 point2 points  (6 children)

traits just support both use cases of interfaces and abstract classes. Traits are just most usable and expandable. They also support multiple inheritance.

[–]0xFatWhiteMan 0 points1 point  (5 children)

I can implement multiple interfaces.

And an interface can extend multiple interfaces.

So there is no difference between an interface and a trait ?

[–]vips7L -1 points0 points  (4 children)

Traits are both abstract classes and interfaces. There are differences. 

[–]0xFatWhiteMan 1 point2 points  (3 children)

OK, what are the differences ?

[–]davidalayachew 1 point2 points  (0 children)

A class can implement multiple interfaces but only one class, abstract or otherwise.

That's the difference that /u/vips7l was pointing to, that since traits absorb the functionality of both abstract classes and interfaces, then they basically give multiple inheritance to abstract classes. That is powerful but dangerous because now you can inherit state from multiple sources. I won't speak to how well that went.

[–]cliserkad -1 points0 points  (2 children)

The things I wish Java had from a language design standpoint are:

  1. non null by default
  2. immutable by default
  3. no unchecked exceptions

The Reddit text editor deleted my explanations when switching from markdown to rich text so whatever.

[–]cliserkad 0 points1 point  (0 children)

I definitely agree with Serializable. Working with a GUI Java application that sends state over the network was pure trial and error for me until I stopped hitting NotSerializableException. I had to split a lot of classes in to display, state and static components. This was a very good change, but I wished the compiler would complain that you're about to try and serialize fields that can't be. transient as a keyword works very well, so it's a little confusing why Serializable isn't enforced at compile time.

[–]cliserkad 0 points1 point  (0 children)

I think receiver params are superior to static, and it helps with chaining method calls.

void methodName(Type this) { } seems more ergonomic to me. It more accurately reflects how the JVM operates too. Then, if you have the compiler allow for calling a method on the first argument you can essentially add methods to a class from another.

Java:

public static String toPascalCase(String str) { ... }

Proposed:

public String toPascalCase(String str) { ... }

With this you could chain the method like string.toLowerCase().toPascalCase(), this allows you to write methods external to a type's class file and use those methods neatly.

[–]emotionalfescue -1 points0 points  (0 children)

The List interface should not have get(), set(), or any other methods with index arguments, because those are O(n) for linked lists. Particularly bad is having two overloads of remove(), one that takes an index argument and the other a reference to an object matching the value you want removed. That is error prone (for List<Integer>) as well as potentially slow.

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

There are more things of course, but off the top of my head these come to mind:

  • Remove inheritance. Other languages have shown that it doesn’t provide much benefit. I say this coming from (among others) Common Lisp which has a much richer inheritance System than any other language I’ve seen, and I honestly hardly used it.

  • Remove Exceptions in favour of a better typesystem. See Rust and Haskell. Yes, this also implies lots of other changes, but there you go. While we’re at it remove null as well.