Determining whether classes are disjoint

This is a follow up of a long discussion on clojurians.
The question is given to classes how to determine whether they are disjoint.
By class I mean a Clojure value for which class? returns true. As I understand, from a Java perspective, these may not all be considered classes, because class? returns true for Java interfaces. There are some cases for which we can definitively determine they ARE disjoint. For example, two distinct final classes are disjoint. E.g., Integer and String. And two distinct abstract classes are disjoint unless one is an ancestor of the other, and the function isa? can be used to determine this relation.

I’m using the function clojure.reflect/type-reflect to determine whether a class is final, abstract, or an interface.

(require '[clojure.reflect :as refl])
(:flags (refl/type-reflect java.lang.Integer)) ;; --> #{:public :final}
(:flags (refl/type-reflect java.lang.Number)) ;; --> #{:public :abstract}
(:flags (refl/type-reflect clojure.lang.IHashEq)) ;; --> #{:interface :public :abstract}

However, there are some cases I don’t know how to handle. For example neither java.lang.Object nor clojure.lang.PersistentList advertise being :final or :abstract.

(:flags (refl/type-reflect java.lang.Object)) ;; --> #{:public}
(:flags (refl/type-reflect clojure.lang.PersistentList)) ;; --> #{:public}

From a Java perspective which does it mean for a class to be public but not abstract, not interface and not final? Is this just an idiosyncrasy of the Object class which must be treated special?
I have found what seems like 4 classifications of classes.

clojure-rte.core> (:flags (refl/type-reflect  String))
#{:public :final}
clojure-rte.core> (:flags (refl/type-reflect  Object))
#{:public}
clojure-rte.core> (:flags (refl/type-reflect  Number))
#{:public :abstract}
clojure-rte.core> (:flags (refl/type-reflect java.lang.CharSequence ))
#{:interface :public :abstract}
clojure-rte.core> 

About 99.44% of classes in the Java ecosystem are public, non-final, non-abstract, non-interface. A few more samples from Java’s own standard library: java.io.File, java.io.FilterInputStream, java.net.Socket.

Clojure is getting these from the Java getModifier method from the object of type Class that represents the class, documented briefly here: https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getModifiers--

That documentation refers for more details to The Java Virtual Machine Specification, table 4.1, which you can find by searching for “Table 4.1” on this page: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

That last page has text after the table with some restrictions on what combinations of flags can be set, or not. For example, there it says: “If the ACC_INTERFACE flag is set, the ACC_ABSTRACT flag must also be set, and the ACC_FINAL , ACC_SUPER , and ACC_ENUM flags set must not be set.”

I would recommend reading that and seeing if it raises any further questions.

From a Java perspective which does it mean for a class to be public but not abstract, not interface and not final?

You may want to read deeply of some Java documentation, or something like Head First Java. It will answer all these questions: the clojure APIs are clojure-y sugar on top of them, as is mentioned above. The answers to these questions I think are invaluable for someone interested in being productive with Clojure, since Clojure gains so much by the Java ecosystem, but I think when you understand all these concepts you will see that only a subset are strictly necessary to solve your problem.

However, having said that, and given that I like to explain these kinds of things…

These are all properties that a class can have, and govern what can be done with the class. They come in a few different categories, but I think they revolve around three key concepts: accessibility, extensibility, and instantiability.

These concepts correspond to basic syntax in the Java language that one encounters in even the simplest examples. Here are two examples for discussion:

package com.jingibus.example;

public class MyClass extends Object implements MyInterface {
  public String getName() {
    return "MyClass";
  }
}
package com.jingibus.example;

public interface MyInterface {
  String getName();
}

First, notice that the interface has no implementation. This is the key difference between classes and interfaces: an interface is nothing more than a set of methods that an instance must implement to satisfy the interface. A class, on the other hand, always describes a concrete object in memory. Even non-instantiable classes contain their own data and implementation details, and so conceptually are set apart from interfaces, which never do.

Now to address the aforementioned three concepts. First: accessibility.

Acessibility

This class’s accessibility is public. Accessibility defines where one can refer to this class; public is “everyone, everywhere”, private is “only within this class file,” and so on. These accessibilities are all mutually exclusive. Accessibility guarantees can be bypassed if necessary. This is discouraged, but it is possible by using Java’s mutable reflection APIs.

Next up:

Extensibility

Extensibility is which types can extend which other types. There are two sides to this question: who can extend us, and who can we extend?

Who can extend my type?

You won’t see anything in the example above that pertains to this, because this behavior is implicit. By default, classes are open for extension, as are abstract classes and interfaces. (“Abstract” is an instantiability notion addressed later.) A class may only be extended by another class, but an interface may be extended by either an interface or a class.

final modifies this behavior. If I wrote public final class MyClass { ... }, no other class could extend my class. Non-instantiable types like interfaces and abstract classes cannot be used without extending them, so final has no meaning for them and is not valid.

So much for the question of which types can extend my class or interface. Now for the other side of the coin:

What types can I extend?

To frame the answer to this question, it may help to consider the class not as a description of a set of objects, but as a description of a concrete implementation. In that light, the design of Java chose to avoid any confusion that might be introduced by defining complicated rules on how to inherit that implementation from two or more superclasses.

However, they wanted to retain the programming advantages that come from multiple type inheritance. So they introduced the concept of the interface. An interface has no implementation, so it can safely extend multiple superinterfaces without defining any rules for merging implementations. And therefore it is also safe for a class to implement as many interfaces as it likes.

So, having said that, here are the rules:

For a class, every class must extend exactly one other class, and may extend as many interfaces as it like. In the example above, no base class is provided, and so the base class defaults to Object. Object is the one exception to this rule: it is the root class, and does not extend any other class.

For an interface, every interface may extend as many interfaces as it likes.

Since the rules are so different between classes and interfaces, Java uses the word “implement” for the case where a class extends an interface, and “inherit” or “extend” for every other case. As a result, in Java code class and interface inheritance are easily distinguished (see the API for java.lang.Class). In Clojure’s reflect it is not immediately apparent which entries in :bases are classes and which are interfaces, though.

Whew. Now for the last bit:

Instantiability

Instantiability is whether one can create an instance of a type or not. Java uses the term “abstract” to refer to types that may not be instantiated.

As discussed above, interfaces never have an implementation, so interfaces are always abstract. A class may also be marked as abstract, though. This done for classes that are designed for use by subclassing.

I think that covers everything I wanted to write about. I hope it was useful.

1 Like

Hi Bill, thanks for the detailed explanation. From what you described it sounds like a java programmer can indeed instantiate Object directly? Is that correct? I.e., it is not marked as abstract. Or is Object a special case?

Like this?

The Object base class mainly gets you identity semantics and some locking tools IIRC. So in addition to the above, it is idiomatic to create a (new Object) instance and use it as an object to lock/synchronize on for lower level synchronization work.

Our Java backend people make somewhat heavy use of default implementations on interfaces - regardless of the merit, where are those stored? How are they invoked internally?

The above discussion reflects the Java world before Java 8. (In many ways, Clojure is still stuck thinking about Java as it existed in Java 6.) There have been some really fundamental changes in the language and virtual machine specification since then. Starting with Java 8, interfaces can include default implementations as way to avoid breaking old code when a new method is added to the interface. And the module system introduced in Java 9 makes accessibility rules a lot more rich, powerful, and enforced. But it has taken a tremendously long time for people to adopt because it breaks a lot of assumptions and approaches that were previously popular.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.