|
Equality should either be the default object identity (for anything mutable), or should encompass the entire public state of the object. If two objects are "equal", calling a particular public method on them should return the same result. If you're doing anything else that's absolutely insane, how do you even use your equality semantics in a way that makes sense? If you're trying to model business logic like "these two things represent the same customer", that should absolutely not be equality, just write a comparator or something for the cases where you specifically want that instead of junking up equality and making your objects unusable in any other context
|
# ? Sep 9, 2019 01:06 |
|
|
# ? Jun 6, 2024 16:52 |
|
Back at a previous job, somebody decided long ago to make the hashcode of our database entities be the ID. Somebody then correctly noticed the problem of trying to hash an entity that hadn't been persisted into the database yet and, naturally, made hashcode throw a RuntimeException if the ID was zero. Then somebody decided an efficient way to implement equals() was simply to compare the two hashcodes. So we had RuntimeExceptions, forever lurking.
|
# ? Sep 9, 2019 01:27 |
|
CPColin posted:Back at a previous job, somebody decided long ago to make the hashcode of our database entities be the ID. Somebody then correctly noticed the problem of trying to hash an entity that hadn't been persisted into the database yet and, naturally, made hashcode throw a RuntimeException if the ID was zero. Then somebody decided an efficient way to implement equals() was simply to compare the two hashcodes. Haha. I mean, yes, one can get stupid with this of course. It is important to correctly implement hashcode and equals (it's not rocket science, really, and this example is how to not do it). With that being said, if the ID is the only discriminator, the only problem that may arise is when trying to use the object in a Set or as a key in a Map. If you absolutely need to use 2 unsaved objects in a set (maybe some big import operation) then you should find some other discriminator, something that for that particular operation makes the objects unique. Unless that's all you do with the objects all the time: handle tons of them unpersisted in a Set or a Map, then by all means, use all the properties you have. Lombok baby all the way, it's not like it matters.
|
# ? Sep 9, 2019 02:07 |
|
Volguus posted:Haha. I mean, yes, one can get stupid with this of course. It is important to correctly implement hashcode and equals (it's not rocket science, really, and this example is how to not do it). With that being said, if the ID is the only discriminator, the only problem that may arise is when trying to use the object in a Set or as a key in a Map. If you absolutely need to use 2 unsaved objects in a set (maybe some big import operation) then you should find some other discriminator, something that for that particular operation makes the objects unique. Unless that's all you do with the objects all the time: handle tons of them unpersisted in a Set or a Map, then by all means, use all the properties you have. Lombok baby all the way, it's not like it matters. You can't use different hash/equality semantics with the same class in different contexts, though - everything has to use the same equals and hashCode methods. if you settle on any non-default hash/equality semantics that don't encompass all observable instance state, it's probably going to hamstring you or produce unintuitive behavior at some point in the future. When you do need to compare instances based on specific properties (e.g. ID), it's easy and more clear to do that explicitly: entityA.equals(entityB) becomes entityA.getId().equals(entityB.getId()), and a Set<Entity> becomes a Map<Id, Entity>. e: the map-keyed-by-ID approach also makes it a bit harder to accidentally mutate the contents of a hash table 205b fucked around with this message at 02:38 on Sep 9, 2019 |
# ? Sep 9, 2019 02:34 |
|
205b posted:You can't use different hash/equality semantics with the same class in different contexts, though - everything has to use the same equals and hashCode methods. if you settle on any non-default hash/equality semantics that don't encompass all observable instance state, it's probably going to hamstring you or produce unintuitive behavior at some point in the future. What kind of unintuitive behaviour do you envision? I can envision a ton of very unintuitive behaviour when you do encompass all observable state: I change the name of a person, now it's suddenly no longer the same person? That is, and you know it, everyone knows it, false. It should replace the object with the same ID in the Set, it should be found by key in the Map because it is the same Person after all. To use different hash/equality semantics in different contexts with the same class one could go the inheritance route. I've never done it since I've never needed it, but it one could have a CSVPerson extends Person, where, all state is present in the hash/equals for that particular operation, at that particular point in time. Sure, one can go stupid and abuse the poo poo out of it and make a mess, but hey, let's not pretend that anything has really stopped those people.
|
# ? Sep 9, 2019 03:28 |
|
If your thing is mutable you shouldn't be overriding equals/hashcode at all, because object identity is more important. If you have two immutable objects that are mostly the same but the Name field is different then yes they absolutely should not compare equal, seriously what the gently caress? If you need specialized comparisons in some special context then write a comparator that compares things that way, don't junk up your general equality semantics. Everything you've described is an unmaintainable shitpile and I hope you don't really consider it to be best practice.
|
# ? Sep 9, 2019 04:23 |
|
Jabor posted:If your thing is mutable you shouldn't be overriding equals/hashcode at all, because object identity is more important. That's insane.
|
# ? Sep 9, 2019 04:50 |
|
Volguus posted:That's insane. Do you want to be more specific about which parts you disagree with? If you're a novice looking for more information on how to handle equality semantics correctly, I'd recommend reading through Effective Java, which contains a wealth of information and best practices sourced from decades of real-world experience in large software projects. e: actually, even if you're not a novice, reading Effective Java is probably still worthwhile. I just reread the section on equals/hashcode as a refresher. Jabor fucked around with this message at 05:37 on Sep 9, 2019 |
# ? Sep 9, 2019 05:05 |
|
Jabor posted:If your thing is mutable you shouldn't be overriding equals/hashcode at all, because object identity is more important. Agreed.
|
# ? Sep 9, 2019 06:19 |
|
Certainly, darling. One piece of advice though: speaking in absolutes, making blanket statements does not do you any favours. Jabor posted:If your thing is mutable you shouldn't be overriding equals/hashcode at all, because object identity is more important. One requirement (don't mutate) does not imply the other (do not provide hash/equals). This would be insane, it doesn't solve anything. Jabor posted:If you have two immutable objects that are mostly the same but the Name field is different then yes they absolutely should not compare equal, seriously what the gently caress? Now, you can come and say, for a particular operation, for a particular business requirement, two person objects with fields X, Y and Z equal they shall be considered equal. The advantages outweigh the risks. That is fine. But you cannot come and make it a true statement for all operations, for all systems that handle people. That's just insane. Jabor posted:If you need specialized comparisons in some special context then write a comparator that compares things that way, don't junk up your general equality semantics. Jabor posted:Everything you've described is an unmaintainable shitpile and I hope you don't really consider it to be best practice.
|
# ? Sep 9, 2019 12:24 |
|
Volguus posted:How can you come and say, without knowing of anything about the business logic, that if two objects have a different name field therefore they must be not equal? They may, then again they may not. Your point of view already falls apart if you have two sections of your application that treat equality differently. How would you implement equals/hashCode in that case?
|
# ? Sep 9, 2019 12:39 |
|
Volguus posted:Certainly, darling. One piece of advice though: speaking in absolutes, making blanket statements does not do you any favours. This post is really funny, because you're the one who's been advocating encoding special business logic in your class's equals and hashcode methods, and I've just been saying "putting business logic in your equals method is dumb and unmaintainable, don't do that". If it's an immutable value object, override equals and hashcode to define whether they're completely identical and interchangable. If it's not an immutable value object, stick with the default identity-based implementation, and write a separate Comparator to use in cases where you need to express a different kind of equivalence relation. (Pro tip: You can use mutable objects as map keys just fine as long as you leave them with the default identity-based equals and hashcode!)
|
# ? Sep 9, 2019 13:08 |
|
Jabor posted:This post is really funny, because you're the one who's been advocating encoding special business logic in your class's equals and hashcode methods, and I've just been saying "putting business logic in your equals method is dumb and unmaintainable, don't do that". Jabor posted:If it's an immutable value object, override equals and hashcode to define whether they're completely identical and interchangable. If it's not an immutable value object, stick with the default identity-based implementation, and write a separate Comparator to use in cases where you need to express a different kind of equivalence relation. Jabor posted:(Pro tip: You can use mutable objects as map keys just fine as long as you leave them with the default identity-based equals and hashcode!) Sagacity posted:You can argue all you want, but equals/hashCode is not designed to handle object identity in the 'real world' sense. If an object is referring to the same person in the real world, but they have different hair color properties, equals/hashCode should reflect that. Sagacity posted:Your point of view already falls apart if you have two sections of your application that treat equality differently. How would you implement equals/hashCode in that case? Like we established before, with a separate comparator.
|
# ? Sep 9, 2019 13:42 |
|
Volguus posted:Where the gently caress did I say that? The business logic dictates what fields participate in the hash/equals not ... put business logic in the hash/equals. Jesus. What the flying gently caress, you're putting words in my mouth now? Either business concerns are dictating what you put in your equals method (hence, there is business logic in your equals method), or they do not, and your equals method is determined entirely by technical stuff. There's not actually any middle ground here? quote:You know you can have a mutable object with the only fields that provide its identity be immutable, right? It doesn't have to be an all or nothing. Actually, reading your posts, it does have to be all or nothing for you. It's all black or white in your universe. Have you read Effective Java yet? It's honestly a bargain in terms of what you'll get out of it. Heck, you could even use your OReilly free trial and just read it for free. (Or perhaps your workplace already has a subscription you can use). You might think that you don't need it or it wouldn't be helpful if you're already an experienced programmer, but it's actually quite the opposite - it's most useful to someone who has some familiarity with the language and wants to know more about modern best practices. The short version is that in the situation you describe, you're already making a terrible mistake. quote:Even pro-er tip: you can do that, but the results may not be what you want since the default identity based equals and hashcode may not reflect the correct identity of your objects. It reflects object identity, which is almost certainly what you want in most cases, and far more importantly is consistent and predictable instead of being whatever arbitrary equivalence relation someone wanted at some point. quote:equals/hasCode have to be designed to handle object identity in business sense. Which may or may not mimic the real world. If in your application the hair colour is the key differentiatior of people, the yes, equals/hashCode should reflect that and it should be made immutable. Otherwise, no. How do you decide which semantics get the privileged position of being the equality semantics, and which are merely equivalence relations defined in comparators? Do you end up with multiple similar classes that do it totally differently, based on what use case they were originally written for (which may not even be a use case that your application handles any more)? Overall, what you're describing is unmaintainable horseshit. It seems like it would break down as soon as you had more than about 5 developers, it would be a complete joke at 500.
|
# ? Sep 9, 2019 14:13 |
|
I got all excited when I saw fifteen unread posts in the Java questions thread. I should have known it'd be nerds flexing their epeens.
|
# ? Sep 9, 2019 16:02 |
|
Jabor posted:Either business concerns are dictating what you put in your equals method (hence, there is business logic in your equals method), or they do not, and your equals method is determined entirely by technical stuff. There's not actually any middle ground here? Jabor posted:Have you read Effective Java yet? It's honestly a bargain in terms of what you'll get out of it. Heck, you could even use your OReilly free trial and just read it for free. (Or perhaps your workplace already has a subscription you can use). You might think that you don't need it or it wouldn't be helpful if you're already an experienced programmer, but it's actually quite the opposite - it's most useful to someone who has some familiarity with the language and wants to know more about modern best practices. Page 38 quote:So when is it appropriate to override equals? It is when a class has a notion of Hmm, "has a notion of logical equality that differs from mere object identity". Strange. Don't you think a Person entity, a domain object qualifies? I'd say it does. It certainly, unmistakably does. But lets go on . So we go through all the rules for writing a proper equals method, which is all fine and dandy and we reach page 47 quote:ITEM 10: OBEY THE GENERAL CONTRACT WHEN OVERRIDING EQUALS (page) 47 Hmm, "significant". That's strange. Who determines what is "significant"? What fields make the business keys? The tooth faerie? The business logic? Nah, surely it cannot be the business logic, after all you've all been singing praises to the tooth faerie so far. The rest of the chapter goes on about hashCode (same remark, strangely, about those pesky "significant" fields, unbelievable), toString and clone. Nowhere did I see any of your claims, namely: Jabor posted:If your thing is mutable you shouldn't be overriding equals/hashcode at all, because object identity is more important. Jabor posted:If you have two immutable objects that are mostly the same but the Name field is different then yes they absolutely should not compare equal, seriously what the gently caress? Jabor posted:It reflects object identity, which is almost certainly what you want in most cases, and far more importantly is consistent and predictable instead of being whatever arbitrary equivalence relation someone wanted at some point. Hahaha, this is getting hilarious.
|
# ? Sep 9, 2019 19:22 |
|
A Person object isn't a person though, it's a representation - and two representations with different data are probably not considered identical. If someone changes their name, arguably an object with the old name isn't valid, isn't interchangeable with an updated object, and will cause problems if you pick one over the other (say if you throw them both in a Set) It's possible you'd have some business logic where this isn't an issue, but what people are saying is that's an unusual situation, and it could create issues as development continues and those objects start to get used in other places. Wouldn't it be better to just create comparators for any business logic and leave the usual language semantics alone?
|
# ? Sep 9, 2019 19:48 |
|
baka kaba posted:A Person object isn't a person though, it's a representation - and two representations with different data are probably not considered identical. If someone changes their name, arguably an object with the old name isn't valid, isn't interchangeable with an updated object, and will cause problems if you pick one over the other (say if you throw them both in a Set) A person object is a representation of most likely a real person, isn't it? That would have a Name, an Address, a work position. Surely you don't assign a printer to be a Person. right? And my argument is that it's a logical, usually, desirable situation, where only the "significant" fields of a person are considered not the identity and not all of them. You (and others) are saying how this can lead to issues when used in other places: which ones? How? Shouldn't the business keys be the same throughout the entire application? Why would you change the semantics of a Person? And let's just assume that you really really really want to change the semantics of a person, for a particular situation, for a particular operation: The book itself provides examples like I have before (me: CSVPerson extends Person, them: ColorPoint extends Point) although it makes a very good point to prefer composition over inheritance and have the Person object as opposed to be a person object. Yes, that is certainly the more desirable approach. Any way you slice it, you have the problem solved. Now, if you come and say: 99% of the use cases of the Person object throughout the system are these special kinds of operations where it would have to be various things therefore defining any business keys is a waste of time, I would understand. But even there, the implementation of equals in hashCode only matters if you're using the object in data structures that ask for them (namely a Key in a Map or an element in a Set). Now, if you do not override equals/hash why in the universe would you put those objects in a Set? What kind of logic would lead one to do that? Put them in a List, you're done, it's simple, it's more efficient, don't sweat it. If you don't care if they're equal, then you don't. There is no point in arguing over anything.
|
# ? Sep 9, 2019 20:38 |
|
You seem to think that "significant fields" mean "fields that I think are significant for the identity of the person". The author of the book is simply saying you should ignore things like calculated fields. This seems to be where your fundamental misunderstanding comes from. It's unfortunate, because your phrasing seem to suggest you assume you are on the right side of this argument.
|
# ? Sep 9, 2019 20:50 |
|
Sagacity posted:You seem to think that "significant fields" mean "fields that I think are significant for the identity of the person". I know that "significant fields" means "fields that I think are significant for the identity of the person" and that I am on the right side of the argument. The book's author does not say "you should ignore things like calculated fields". The only thing the author says about calculated fields is (page 47): quote:You need not compare derived fields, Where in the book does your statement even remotely looks to be valid and supported by the author?
|
# ? Sep 9, 2019 21:59 |
|
Volguus posted:A person object is a representation of most likely a real person, isn't it? That would have a Name, an Address, a work position. Surely you don't assign a printer to be a Person. right? The objects might represent the same, unique thing (person or printer or whatever) but that doesn't mean those representations themselves are identical, and that often matters when you're creating and manipulating these objects. They're an abstraction. Should two objects containing different data be considered identical? I mean sure, maybe there's a situation where you'd want that, but the general understanding is that if two things say different things then they're two different representations Like if you have two Persons with different names that are meant to represent the same person... well, why are the names different? Is that correct, are they both valid? Should the logic behind this be hidden away in the class's equals method, the discrepancy (and the potential for it occurring) invisible to the rest of the system unless you know it's happening? Or should it be handled explicitly as part of the business logic that decides two disagreeing data objects can be considered identical in certain, specific cases? I'm not saying it has to be one way or the other, but one way keeps it closer to standard, expected behaviour which reduces the potential for weird gotcha side effects rippling out. "btw don't use Sets" is definitely something that could catch people out down the line
|
# ? Sep 9, 2019 22:46 |
|
baka kaba posted:one way keeps it closer to standard, expected behaviour which reduces the potential for weird gotcha side effects rippling out. "btw don't use Sets" is definitely something that could catch people out down the line I'm sure people caught out by this during a production outage are very open to pedantic discussions about why it *is* in fact better and they (and most existing libraries) were doing it wrong
|
# ? Sep 9, 2019 22:55 |
|
All of this from a mention of project Lombok. I love Lombok, saves so much time automating pojos and logging. It also makes collaborators angry when you start using val everywhere you don't care about knowing types.
|
# ? Sep 9, 2019 22:55 |
|
I'm not wild about val / var. I shy away from it in C# code too, that feels like something that should stay in javascript and interpreted languages. Having strong types is good and knowing those types makes your code more readable. Don't make me guess based on your bad variable names. (Are we going to return to Hungarian Notation?)
|
# ? Sep 10, 2019 00:14 |
|
Zaphod42 posted:I'm not wild about val / var. I shy away from it in C# code too, that feels like something that should stay in javascript and interpreted languages. var in c# is still strongly typed. e: It takes on a type when the variable is initialized. It's implicitly typed: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/var
|
# ? Sep 10, 2019 00:37 |
|
RandomBlue posted:var in c# is still strongly typed. I know, its all compiler-time, not run-time. But still, readability. Lombok in Java does the same thing.
|
# ? Sep 10, 2019 00:44 |
|
Volguus posted:A person object is a representation of most likely a real person, isn't it? That would have a Name, an Address, a work position. Surely you don't assign a printer to be a Person. right? I don't care about this dumb argument but I have actually worked in an application that expanded a database literally named "person" to also include workstations, faxes, and bots because they were too lazy to implement a general purpose access control system and instead made anything that needed managed permissions people. Never underestimate the poo poo lazy enterprise programmers on crunch timelines will do
|
# ? Sep 10, 2019 05:30 |
|
All characters in Tony Hawk Pro Skater derive from a class called Bruce because originally the code was written for a game starring Bruce Willis as the protagonist. Nobody ever bothered to rename the class.
|
# ? Sep 10, 2019 06:23 |
|
Zaphod42 posted:I know, its all compiler-time, not run-time. But still, readability. I limit it's use for when I'm in the same function and I just told you the type on the RHS of the =. Never as the assignment of a method call that returns something, that's just killing your readability as you said.
|
# ? Sep 10, 2019 23:49 |
|
Sagacity posted:All characters in Tony Hawk Pro Skater derive from a class called Bruce because originally the code was written for a game starring Bruce Willis as the protagonist. Nobody ever bothered to rename the class. Shadow of Mordor players have an open and close method since the base game object (Called CLTObject inherits from CLTDoor...)
|
# ? Sep 11, 2019 00:05 |
|
So anyhow, thanks to some help from the course's Slack group, I got it sorted out. I have to exclude Recipe from equals and hashCode to avoid circular references.
|
# ? Sep 11, 2019 01:38 |
|
Zaphod42 posted:I'm not wild about val / var. I shy away from it in C# code too, that feels like something that should stay in javascript and interpreted languages. Actually they’re good and cool and everyone should use them whenever possible. They don’t take anything away, they just make it less cluttered. The type is still there and if you aren’t sure what it is then you can just ask your ide and it’ll tell you, no guessing required. Verbose types everywhere make code harder to read, not easier, and are the reason so many people started using dynamically typed languages to begin with.
|
# ? Sep 11, 2019 08:56 |
|
You know you have a good java code base when you have auto generated boiler plate Javadoc comments for all your auto generated getters and setters. I like Lombok.
|
# ? Sep 11, 2019 12:56 |
|
Soricidus posted:Verbose types everywhere make code harder to read, not easier, and are the reason so many people started using dynamically typed languages to begin with. Well that's obviously pretty subjective, and I don't agree at all (code is more readable to me with explicit typing). You may have a point about more people preferring dynamic typing, but I'd argue that, first, JavaScript is popular for reasons that have nothing to do with the language itself (e.g. being the literal only option for client-side scripting on the web), and second, there's a reason TypeScript was invented.
|
# ? Sep 11, 2019 14:03 |
|
rujasu posted:Well that's obviously pretty subjective, and I don't agree at all (code is more readable to me with explicit typing). You may have a point about more people preferring dynamic typing, but I'd argue that, first, JavaScript is popular for reasons that have nothing to do with the language itself (e.g. being the literal only option for client-side scripting on the web), and second, there's a reason TypeScript was invented. Yeah, but TypeScript doesn't force declaration of type for variables if they're assigned right away, just like Kotlin, Swift, C#, and now Java.
|
# ? Sep 11, 2019 14:12 |
|
carry on then posted:Yeah, but TypeScript doesn't force declaration of type for variables if they're assigned right away, just like Kotlin, Swift, C#, and now Java. Well, TypeScript doesn't force declaration of type at all, since it's compatible with plain old JavaScript. Personally, I'd like to keep that sort of sloppiness out of Java, but sadly I realize that's not the current trend.
|
# ? Sep 11, 2019 16:03 |
|
*bursts into thread* Kotlin! (Seriously, if Lombok is being a pain, try out Kotlin.)
|
# ? Sep 11, 2019 17:08 |
|
rujasu posted:Well, TypeScript doesn't force declaration of type at all, since it's compatible with plain old JavaScript. It's not sloppiness, it's brevity. Please stop holding this language back in 2000.
|
# ? Sep 11, 2019 17:37 |
|
carry on then posted:It's not sloppiness, it's brevity. Please stop holding this language back in 2000. Like I said, I know what the trend is, so I'm certainly not going to stop it. That said, newer isn't always better.
|
# ? Sep 11, 2019 18:09 |
|
|
# ? Jun 6, 2024 16:52 |
|
*stares at screen* var usersByUsername = new HashMap<String, User>(); *frowns, sweat beading on forehead as he struggles in vain to guess what type this variable might have*
|
# ? Sep 11, 2019 22:51 |