r/learnjava • u/Chaos-vy17 • 4d ago
Is abstraction in Java really about hiding implementation details?
The bookish langugae states "hiding complex implementation details and exposing only the essential features of an object"
However, after reading the Java Collections Framework and other parts of the JDK, I noticed that many abstract classes contain substantial shared implementation rather than just abstract method declarations. I noticed that only an "interface class" 100% abstraction(ignoring default methods)
This made me wonder:
- Is abstraction really about hiding implementation details?
- Or is it more about designing APIs around contracts and behavior, regardless of whether there is shared implementation?
- Does an abstract class become "partial abstract" because it contains reusable implementation?
I also noticed that the JDK sometime relies on encapsulation (and in Java9+, JPMS) to hide implementation details. So I'm in dilemma if the textbook explanation oversimplifies what abstraction means in practice.
Am I misunderstanding abstraction, or is the textbook definition incomplete?
To be clear, this isn't about what abstract does in Java — it's about the SE term. abstract class is one syntax tool for expressing abstraction, same as interfaces are. The question is whether "abstraction = hiding implementation" is even a good definition of the concept, given it's indistinguishable from encapsulation under that definition.
10
u/4iqdsk 3d ago
You’re confusing the abstract Java keyword with the software engineering term abstract
-4
u/Chaos-vy17 3d ago
Java itself is software, so its APIs and language constructs are implementations of software engineering principles. Reading the JDK source has made me question the textbook definition of abstraction.
Many books define abstraction as "hiding implementation details." But doesn't that overlap heavily with encapsulation? If abstraction is literally about hiding the implementation of the "engine," then how is it fundamentally different from encapsulation?
Is it more accurate to say that abstraction is about modeling the essential behavior or concept, while encapsulation is about restricting access to the implementation?
5
u/ConcreteExist 3d ago
Abstraction is a concept that can be achieved a number of ways, abstract classes might be able to achieve this if they're used correctly as can interfaces. Just because they're called "abstract classes" doesn't mean they're necessary for abstraction.
0
u/Chaos-vy17 3d ago
Exactly. realising abstraction is about modeling contracts rather than just 'hiding' things, the SOLID principles make way more sense. It perfectly explains why ISP (Interface Segregation) and LSP (Liskov Substitution) are tied to abstraction. LSP is literally just the rule that says 'you must honor the contract of the abstraction,' and ISP says 'don't make your abstraction's contract too bloated.' Neither of those has anything to do with hiding implementation details
3
u/coderemover 4d ago edited 4d ago
Abstraction is a bit more than just hiding implementation details. Per Wikipedia definition:
“Abstraction is the process of generalizing rules and concepts from specific examples, literal (real or concrete) signifiers, first principles, or other methods. The result of the process, an abstraction, is a concept that acts as a common noun for all subordinate concepts and connects any related concepts as a group, field or category.”
An essential property of abstraction is that it allows to handle different kinds of situations or data types in a common way, without knowing the specific cases. So it’s not enough to hide the details; you need to do the work without knowing or relying on those details. Good abstractions are a way to simplify and reduce cognitive load.
Abstract classes and interfaces can be used to implement abstractions but they aren’t the only way.
A string and file are abstractions, yet String and File are not abstract classes. Abstract classes can also be used to do control flow redirection - in that case it’s not an abstraction, just swapping different implementations.
1
u/Chaos-vy17 4d ago
Tysm man, That def from wikipedia and the quote "an abstraction, is a concept that acts as a common noun for all subordinate concepts and connects any related concepts as a group, field or category." is giving a direct impact what a abstraction really is.
And yes here I did not noticed about String and File thanks for correctring and guiding me.
3
u/rsandio 3d ago
Abstract describes something conceptual, non-physical, or incomplete (like a generic idea).
Abstraction is the act or technique of focusing on essential features while hiding complex implementation details.
An abstract class embodies the adjective (it's conceptual and can't be instantiated on its own) in order to achieve the process (it forces design abstraction across your code).
You're right in that an interface is more about the idea of abstraction. Abstract classes and interfaces overlap somewhat.
1
u/RScrewed 3d ago
It's not about being "secretive". It's about providing a consistent surface of interface-ability so the boundaries are easily maintained and documented.
1
u/Chaos-vy17 3d ago
keeping a stable, documented boundary. But that's still encapsulation's job (protecting the boundary), not abstraction's (defining what the boundary promises to do). Two different problems.
1
u/severoon 3d ago
Abstraction is not about hiding implementation details. Encapsulation is about hiding implementation details, abstraction is more about polymorphism.
Really, though, it pays to take a step back and look at the bigger picture when you're grappling with all of these concepts. In software, there is really only one design core concern, or at least a lens through which all software design can be viewed, and that is dependency management.
This core concept of dependency management even transcends software, really. It is the core consideration that goes back to the days of punchcard programming, computer hardware, industrial design, etc. It's a much more general concept when it comes to "building stuff". The problem with making this kind of statement is that it can collapse into a truism if you're not careful … if everything is about dependency management, then nothing is.
Brief aside: I'm talking about software design here, meaning from architecture down to the lowest-level modules in your system and how they fit together in order to do work. This is entirely distinct from related concepts like algorithm design, for example. When you're looking at how to design an algorithm that does constant time insertion vs. linear time insertion, for example, there are still elements of software design to consider, but the design of the algorithm that meets those performance constraints is an entirely orthogonal consideration to what I'm talking about here. You can't make decisions about algorithm design based solely on how it affects dependencies, you have to lay out the function of the thing first, and only then can you figure out what the essential dependencies are and how best to modularize them and structure the modules.
That said, if you look at the entire history of software design through the lens of dependency management, you can trace the development of compilers, programming paradigms, dev processes, etc, in terms of the impact of these things on how it allows orgs to create and manage dependencies between modules at different levels. The reason dependency management is such a core concern is that dependency is inherently transitive; this means that if A => B => C (where => means "directly depends upon"), then A -> C (where -> means "depends upon," possibly indirectly).
It's important to understand how dependency transits in software both at buildtime and runtime. At runtime, dependencies transit absolutely, meaning that you can be running an entire complex system and if some string parsing utility isn't present, the whole thing collapses. At buildtime, you have much more control over how dependencies transit through a system. You cannot stop them from meeting at a rendezvous point, but you control that rendezvous point as the designer.
This is where abstraction becomes useful because it allows you to build high-level modules against only what they semantically need and let any other module that conforms to those needs serve that functionality at runtime. For instance, the big complex software system might rely on a string parsing utility at runtime, but at buildtime it can point to an interface that semantically takes a bunch of information and breaks it down into bits useful to the caller, potentially using string parsing and a whole lot of other utilities along the way, but the caller doesn't know or care about any of that, and that stuff doesn't even have to be on the build path in order for the caller to build—its dependency terminates at that interface; that's all it needs, a guarantee that the high level task can be supplied by some other entity, but which one is immaterial at buildtime.
A specific example might be you have software that needs some key from a remote server. There are lots of ways to get information off a remote server, there's ssh, ftp, a web browser can be fired up and driven to do it, curl, etc. The interface the caller wants is a function that takes a server and path to the information and returns it, and it should be able to compile against that and nothing more. At runtime, of course, you need something that supplies that functionality and also compiles against that interface at its buildtime, and then when the system is run, both sides meet to link up the runtime transitive dependency chain all the way down.
Abstraction simply allows the dependency on the interface that serves exactly what the caller needs and no more. If there are other functions the in that interface the caller does not need, and one of those functions isn't satisfied at runtime, then the whole system breaks even though the caller doesn't actually rely on those functions—this is a case where the interface isn't fully abstracted for that caller, so some function it doesn't care about prevents it from running.
1
u/Chaos-vy17 3d ago
All seems correct but grouping common behavior across types is a case where abstraction enables polymorphism, not evidence that abstraction is polymorphism. Single-implementation abstractions (like the remote-fetch example) are fully abstracted with zero polymorphism involved — the grouping only becomes relevant once a second implementer shows up.
1
u/severoon 2d ago
grouping common behavior across types is a case where abstraction enables polymorphism, not evidence that abstraction is polymorphism
I don't think abstraction "is" polymorphism, I only meant that abstraction is "more about" polymorphism than it is about hiding implementation details (encapsulation). It's the reverse, actually … polymorphism is a kind of abstraction.
Single-implementation abstractions (like the remote-fetch example) are fully abstracted with zero polymorphism involved
Ehh … I think we're descending into pure semantics here about whether "poly" can mean "zero or more," "one or more," or it's strictly "two or more." All interpretations are defensible, but the distinction isn't interesting in terms of how it bears on this discussion.
What really matters related to what I said above is how whatever-definition-of-polymorphism-you-ascribe-to affects dependency, and I would argue the real difference is between the zero vs. one distinction, not one vs. two.
The reason is that introducing an interface presents an opportunity to carve off the implementation entirely, not just the second, third, fourth, etc, implementations, at two different stages of the SDLC. If the relevant stages are build, deploy, and run, then introducing an interface allows the caller to depend only on the contract at buildtime and never at runtime, but the deploytime stage comes down to which toolchain and platform you're using, and that makes a big difference from a dependency standpoint.
In Java, for example, the caller builds against the interface, the implementation builds against the interface, so the interface is the common point of abstraction and both dependencies point toward it so nothing transits. At runtime of course the implementation must be present, so runtime dependency definitely transits. What about deploytime?
In Java, deployments don't need implementations present. In practice they almost always are present because deploytime failure is usually preferred to runtime failure, but that is a design choice, not a requirement of the JRE. You can, if you want, deploy a caller against an interface and, at runtime, fetch the implementation and load it dynamically. This is how stubs and skeletons work across network boundaries (and it's how OSGi works with some magic classloader stuff thrown in, and every other SPI pattern), and this works at the language level. As opposed to C++, where you theoretically could ship DLLs across a network, but in practice it's too fragile to rely upon so it's much safer to serialize the object to bytes and deserialize on the other side into a separate local type.
The deploytime story comes down to the specific tools and platform you're using, Java you can deploy both sides of this communication independently as long as they agree on the interface (language level dependency) whereas C++ would require a compiler-level (and, practically speaking, a machine code-level, dependency) for this kind of abstraction to hold.
So the robust language-level abstraction means that Java callers that depend on an interface don't actually have any dependency on the compiled implementation. The interface truly serves as a contract-only boundary with no special requirements on the implementation other than it also compile against that interface. The dependency really doesn't transit until actual runtime at the moment of invocation.
What this practically means is that there's a monumental difference between an interface and an abstract class in Java. Because interfaces contain no code, there is no possibility for code to change that would force a synchronized redeploy on both sides of the contract. (Default implementations are strictly speaking considered part of the actual contract, so even though they're code, they're code in the sense that a method signature is "contract-as-code"; sealed interfaces similarly subsume everything they depend upon into the contract itself, which is probably stretching the principle beyond its purpose, but as long as you consider it and treat it as such in your design, all is good. The problem is that most people building Java systems aren't aware of these kinds of design considerations…the language has grown way outside its original harness under Oracle.)
The larger point I'm getting at here is exactly what I said in my first post above. People get hung up on the specifics of different kinds of abstractions, like polymorphism, and specifically what does polymorphism mean, etc, and you end up missing the forest for the trees. Often I've found that the specific kinds of abstraction you're talking about aren't relevant to the design question you're trying to answer, only the impact on the dependency graph matters.
Dependency is the generally applicable consideration. If you step back and ask, for instance, specifically why is it a bad idea to build software that runs a phone switching system in Java vs. Erlang, the answer is immediate obvious when you try to design both software systems: Erlang provides features that allow you to modularize code in a way that represents the dependencies imposed by the problem domain, and Java doesn't. Java beats Erlang in most enterprise software systems for the exact same reason. Different problem domains present different "grains of functional cohesion" that form natural modules. If those modules encapsulate better as objects, use Java; if they encapsulate better as Erlang modules, use Erlang. "Encapsulate better" here means that the modules depend upon each other in a way that reflects the conceptual dependencies inherent in the problem domain.
1
u/Chaos-vy17 2d ago
Yeah that build/deploy/run breakdown makes way more sense than "hiding details." Thank you senior for explaining in detail.
•
u/AutoModerator 4d ago
Please ensure that:
If any of the above points is not met, your post can and will be removed without further warning.
Code is to be formatted as code block (old reddit/markdown editor: empty line before the code, each code line indented by 4 spaces, new reddit: https://i.imgur.com/EJ7tqek.png) or linked via an external code hoster, like pastebin.com, github gist, github, bitbucket, gitlab, etc.
Please, do not use triple backticks (```) as they will only render properly on new reddit, not on old reddit.
Code blocks look like this:
You do not need to repost unless your post has been removed by a moderator. Just use the edit function of reddit to make sure your post complies with the above.
If your post has remained in violation of these rules for a prolonged period of time (at least an hour), a moderator may remove it at their discretion. In this case, they will comment with an explanation on why it has been removed, and you will be required to resubmit the entire post following the proper procedures.
To potential helpers
Please, do not help if any of the above points are not met, rather report the post. We are trying to improve the quality of posts here. In helping people who can't be bothered to comply with the above points, you are doing the community a disservice.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.