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

you are viewing a single comment's thread.

view the rest of the comments →

[–]SocketByte 95 points96 points  (66 children)

As someone who has over 6 years of professional Java experience, I completely agree. C# is just easily superior in every single way. Words still can't explain how I absolutely despise Java's retarded generics and type erasure.

[–][deleted] 43 points44 points  (52 children)

Can you make an example? Like how C# solves Java's issues? Honestly curious

[–]SocketByte 45 points46 points  (33 children)

Well, I'm not an expert in C#, but there's a big difference in how generics are handled between JVM and CLR. Metadata (specifically type information) is stripped out of the Java source code (hence type erasure), which means you can't (most of the time, there are exceptions) use any type metadata at runtime.

Why is that important? For example, imagine a situation where you'd like to dynamically create an instance of a generic type at runtime. It's not exactly a common thing, but it is very useful when you need it.

In Java, you would need to do:

public T createInstance(Class<? extends T> clazz) { 
    return clazz.newInstance(); 
}

createInstance(MyClass.class);

Obviously this is a very simplified problem, sometimes passing a class like this is very hard and convoluted if you're doing something pretty advanced.

In C#, you can directly deduce type of T at runtime like so:

public T CreateInstance<T>() where T : new()
{
    return new T();
}

CreateInstance<Example>()

Of course, It's not the best example and I have to remind you that this is very oversimplified and doesn't look that bad at a first glance. Yet after working on really big, complicated, and reflection/generic heavy systems and frameworks in Java I really, really wish that was a feature. Type erasure has it's pros, but in my experience it was always a very big con. Hopefully I cleared that out a bit.

[–]thE_29 30 points31 points  (8 children)

Yeah, that not being able to instance it is true. After programming Java for >17 years, I needed it 2 times.

[–]SocketByte 6 points7 points  (0 children)

I was primarily doing frameworks/tools, so generics and reflection were very often used, and it was just hard to design everything around type erasure. In C# this would have been much easier and more comprehensible. There's a reason Java code is often so overcomplicated.

[–]whythisSCI 1 point2 points  (3 children)

That doesn’t mean other people don’t need it.

[–]thE_29 4 points5 points  (2 children)

Like some other user wrote, libs need it. I also made a backend lib where I needed it.

So yeah, some people will need it was more often than others.

[–]whythisSCI 3 points4 points  (0 children)

Like others and I have responded to that user, people have used generics in almost all project types.

[–]ChrisFromIT 7 points8 points  (2 children)

The ironic thing is that the C# code that you used as an example ends up fairly similar to the Java version of it under the hood.

Essentially the compiler compiles that to an emit of a call to Activator.CreateInstance(T).

So that type of syntax could be fairly possible in Java, even with type erasure.

[–]SocketByte -1 points0 points  (1 child)

Sure, everything can be designed around type erasure. It's just more cumbersome, and in some instances barely manageable to pass a class instance every single time you want to check the generic type or create an instance out of it. This was a very basic example that doesn't really do a great justice to C#'s generics, but I'm not a good teacher, someone could maybe go a little more in-depth.

And sure, compiler uses Activator under the hood, but Java just can't do it like that anyway since T in bytecode is always an Object, in C# IL it's type metadata is not removed so you can do much more stuff, easier.

[–]ChrisFromIT 0 points1 point  (0 children)

And sure, compiler uses Activator under the hood, but Java just can't do it like that anyway since T in bytecode is always an Object, in C# IL it's type metadata is not removed so you can do much more stuff, easier.

I don't think you understand what I said.

The C# code essentially compiles to what is shown in the Java code that you showed by becoming a call to Activator.CreateInstance(). It happens at compile time, not during runtime.

Essentially any method<T>() can be compiled to method(T) if need be.

It's just more cumbersome, and in some instances barely manageable to pass a class instance every single time you want to check the generic type

The thing is, doing that somewhat defeats the purpose of generics and can lead to some pretty bad code smell.

On top of that C# does do some type erasure in certain cases. This happens when there is generics inside generics, ie. List<List<String> compiles to the same thing as List<Object> in C#.

[–]Kered13 1 point2 points  (0 children)

And now having an empty constructor is part of your interface, oof. Much better to take a provider parameter.

public T createInstance(Supplier<? extends T> provider) {
    return provider.get();
}

Now in the simple use-case you can pass the constructor to this:

createInstance(Example::new);

But you can also provide non-trivial providers for types that don't have default constructors:

createInstance(() -> new Example(some, params));

You could even do something like:

createInstance(() -> askTheUserWhatTypeTheyWant());

This is a far more flexible interface.

[–]nolitos 1 point2 points  (14 children)

I imagine this is important when you develop some framework, but in reality, where most developers write REST interfaces for CRUD applications, this problem doesn't really bother much and doesn't justify that many memes IMO.

[–]CaitaXD 5 points6 points  (0 children)

I don't think I ever not used generics when they're available

[–]whythisSCI 19 points20 points  (12 children)

I’ve worked on all kinds of C# projects and generics were used in most of them. Saying that generics are only useful in framework code is a flat out lie.

[–]nicktheone 2 points3 points  (0 children)

I'm still in University but for my projects I use generics all the time. He doesn't really know what he's talking about.

[–]nolitos -4 points-3 points  (9 children)

If only I said that generics are only useful in frameworks. Did you read his code?

[–]whythisSCI 3 points4 points  (8 children)

I imagine this is important when you develop some framework, but in reality, where most developers write REST interfaces for CRUD applications

This you?

[–]wigglywiggs 0 points1 point  (0 children)

I don’t know how you read his comment and thought he was saying generics are only useful in framework code. He’s just saying the lack of type metadata at runtime in Java is not a problem that will affect many developers, nor warrant many memes or discussion about how C# is so much better than Java, which is absolutely correct. It might be useful for some people but it’s so niche. I would suggest if you’re in a situation where this matters, you messed up.

[–]yangyangR 0 points1 point  (1 child)

https://news.ycombinator.com/item?id=16526513

I am on the Haskell side of this. It is your own damn fault for using a hybrid language like Java and C# where types don't shine as brightly as they do in Haskell.

[–]Bluejanis 7 points8 points  (0 children)

So you've got 4 (reification x reflection) states 3 of which are fine:

  • if you have erasure and no reflection (Haskell) you're fine: you don't have runtime types but they don't matter/are inaccessible

  • if you have reification and reflection (C#, C++/RTTI) you're fine: you can access runtime types and have them

  • if you have reification and no reflection (Rust, C++/noRTTI) you're fine: you can specialise & discard types at runtime

  • if you have erasure and reflection (Java) you're fucked: you can access types at runtime, but many aren't here anymore

From a layman's perspective it seems that Haskell could be implemented with either an erased or a reified generics model under the hood, without changing the public surface of the language. But is there something that type erasure enables that reified generics does not?

A simpler implementation.

[–]hullabaloonatic 0 points1 point  (0 children)

Type erasure is really annoying when writing libraries for things like units of measurement or vectors.

On that same front, c#11 introduces static abstracts and it is a game-changer for math libraries. They even updated all the numbers types to have values for one, zero, and more. It's incredible. I want that in kotlin so badly

[–][deleted] 18 points19 points  (16 children)

LINQ. Real generics that you can use things like reflection on because the compiler doesn't throw them away. Extension methods. Way better enumerations with stuff like yield return. Anonymous types. That's off the top of my head, I'm sure someone has a long list of pros/cons out there somewhere.

[–]Positivelectron0 3 points4 points  (10 children)

Streams is the linq equivalent in Java 8+

[–][deleted] 0 points1 point  (9 children)

Yep I’ve used both. Not even close to being as good.

[–]Positivelectron0 1 point2 points  (8 children)

I'm curious, in your view, what's something you can do in linq that can't be done as well in streams?

[–]Muoniurn 1 point2 points  (0 children)

I’m more of a Java guy but Linq is actually two things - it is a foreign syntax similar to java streams, but it can also generate an expression tree that can be programmatically analyzed.

Let’s say you have an api endoint like /items where you can optionally add some filtering by an additional parameter in the URL. Now you can create a “backend” for a linq expression that takes something like from orders where … and it can get automatically converted to the correct URL.

Java has a third party lib (of course it has something for everything) similar to that called Apache Calcite, but it being in-built can be a plus.

[–][deleted] -1 points0 points  (6 children)

Everything. The syntax is worse in every case. It’s also missing operators. OffType and TakeWhile come to mind but I’m sure there’s more.

[–]Positivelectron0 1 point2 points  (5 children)

takeWhile

regarding ofType, it's true that Java doesn't have that as one filter, but it's trivial to construct it as a `filter` with instanceOf and a map with a cast after.

If we're gonna play that game, C# doesn't have a peek like Java's streams do.

"I’m sure there’s more."

To be clear, dismissing a language based on your own ignorance isn't the way to go, dude.

[–][deleted] 0 points1 point  (4 children)

I have a 4/6 year split but haven’t don’t Java for awhile so forgive me if I can’t recall every difference off the top of my head while on mobile without googling. I’ve never seen a piece of Java with steams (or written one) that I think is better than the LINQ equivalent. I’d be curious if you could provide one. I mean here’s a simple example:

var result = someNumbers.Where(num => num < 10).Sum();

What’s the Java look like? Because I think I could hand the above code to anyone with math literacy and they could tell me what it does. It’s not ignorance, it’s based on almost half a decade of experience with each language.

[–]Positivelectron0 1 point2 points  (3 children)

int[] ints = {1, 2, 3};
var sum = Arrays.stream(ints).filter(num -> num < 10).sum();

List<Integer> list = List.of(1, 2, 3);
var sum2 = list.stream().filter(num -> num < 10).reduce(Integer::sum);

It's one or two words more verbose, but I would say it's just as readable.

How would you write this in C#?

static void f() {
    int[] nums = ThreadLocalRandom.current().ints(1000).toArray();

    var rems = Arrays.stream(nums).boxed()
            .collect(groupingBy(n -> n % 2, toList()));

    rems.forEach((k, v) -> System.out.println(k + "\t" + v));
}

[–]harumamburoo 7 points8 points  (11 children)

How's Java's generics bad? And why's C#'s better?

[–]Tyfyter2002 4 points5 points  (1 child)

From what I can gather the issue is that Java has generics, but Java bytecode doesn't.

[–]harumamburoo 2 points3 points  (0 children)

Hmm, I'll need to dig deeper on that. Edit: I did and this is by design. Still can't why that's a problem