Remember OO <> Class Inheritance (or indeed Inheritance at all)
OO is at it's core "messages being passed between objects which can have
data and
code associated with them"
Alan Kay gets to decide that because he invented it and the term
If you have types with OO programming then really the
LSP is the bit that really starts to matter. Which is basically, what objects can I put into these holes (variables) correctly - where the type should mean that this is correct if the programmer did the right thing (and
should have done so unless they are a numpty).
You can have purely interface based "Inheritance" in OO languages at which point the terminology of inherit doesn't actually make sense - there's nothing inherited unless you can have an interface extend another one. You are just committing your type T to implement the contract implied by interface I, no data or executable code is transferred from I to T.
VB6 is an example of this.
Many languages that are OO centric have class based Inheritance, but big ones like JavaScript did not (having prototype based forms instead) so classes themselves are in no way foundational to OOP (though some people incorrectly assert they are).
One aspect of java worth knowing is, an often derisory phrase (that is also not strictly true
1).
"In Java everything is a class"
However it does have quite a kernel of truth to it because, in java (and many other languages) all code and data needs to be associated with a class (and if not static and
instance of that class).
This is just how the sepcification of those languages compiled forms work. So you'll end up needing to define a class to hold a bunch of constants, and that's fine.
C# had the same thing and the pattern was sufficiently well defined that syntax and compiler support were added to let you define "static" classes which would refuse under any circumstances to be extended or be instantiated - even by reflection. But it's basically the same technique, you define a private constructor and mark the class abstract.
Some things in java do not require
explicit classes, but will be compiled into them anyway (with special names which the user cannot collide with as the names are reserved for the compiler use only). Lambda closures (where previously you needed anonymous inner classes) are one of the earliest examples of this, though they compile to pretty much the same thing under the hood in most cases. Thus "classes"
can be a reification of the need to hold compile time determined pieces of state associated with code that uses them where the values and numbers of them are known only at runtime.
1. For a counter example an int is not a class, but an Integer is (and they are not the same thing, though sometimes the compiler will try to pretend they are for you to varying degrees)