[Java] 如何自动生成简单的 Mermaid 类图

396 阅读2分钟

如何自动生成简单的 Mermaid 类图

背景

我在 [Java] 一些类图 这篇文章里画了一些类图,虽说手动生成类图的过程有利于加深自己的理解,但是查看各个类/接口的信息毕竟比较麻烦,如果可以把生成类图的过程自动化,就可以大大提升画类图的效率了。

本文展示了我自己写的可以生成简单类图的 java 代码。文中展示了用它生成的以下类的类图

  1. ArrayList
  2. LinkedList
  3. java.util.Arrays$ArrayListArrays.asList(...) 方法返回的是它的实例
  4. List/Set/Deque
  5. HashMap/LinkedHashMap/ConcurrentHashMap
  6. Set12/SetNSet.of(...) 方法返回的是它们的实例
  7. java.util.JumboEnumSet/java.util.RegularEnumSet (EnumSet.of(...) 方法返回的是它们的实例)
  8. java.util.concurrent.ThreadPoolExecutor
  9. 自己手写一些类/接口

代码

我写了如下的 java 代码,它可以自动生成简单的 Mermaid 类图。 请将以下代码保存为 ClassDiagramGenerator.java ⬇️

import java.lang.reflect.AccessFlag;
import java.util.*;

public class ClassDiagramGenerator {
    private final Map<Class<?>, Class<?>[]> realizationRelations = new HashMap<>();
    private final Map<Class<?>, Class<?>> inheritanceRelations = new HashMap<>();

    private final Set<Class<?>> analyzedClasses = new LinkedHashSet<>();

    private final Set<Class<?>> ignoredClasses = new HashSet<>();

    private static final Class<Object> OBJECT_CLASS = Object.class;
    private static final String IGNORE_OPTION = "-i";
    private static final String USAGE = "Usage: java ClassDiagramGenerator [-i 'java.io.Serializable'] [-i 'java.lang.Cloneable'] 'java.util.ArrayList' 'java.util.LinkedList'";

    public static void main(String[] args) {
        ClassDiagramGenerator generator = new ClassDiagramGenerator();
        generator.convert(args).forEach(generator::analyzeHierarchy);

        generator.showIgnoredClasses();
        generator.generateClassDiagram();
        generator.generateNameMappingTable();
    }

    public ClassDiagramGenerator() {
        ignoredClasses.add(OBJECT_CLASS);
    }

    private void ignoreClass(String className) {
        try {
            Class<?> clazz = Class.forName(className);
            ignoredClasses.add(clazz);
        } catch (ClassNotFoundException e) {
            String message = String.format("Class with name=%s can't be found, please check!", className);
            throw new RuntimeException(message);
        }
    }

    /**
     * Convert class name to the corresponding class object.
     * The "-i" option (it means some class should be ignored) will be handled in this method.
     *
     * @param classNames give class names
     * @return a list that contains corresponding class objects
     */
    private List<Class<?>> convert(String[] classNames) {
        int index = 0;
        while (index < classNames.length && classNames[index].equals(IGNORE_OPTION)) {
            if (index + 1 >= classNames.length) {
                String hint = "Please refer to below usage and specify correct parameter with the '-i' option!";
                throw new IllegalArgumentException(String.join(System.lineSeparator(), hint, USAGE));
            }
            ignoreClass(classNames[index + 1]);
            index += 2;
        }

        if (index == classNames.length) {
            String hint = "Please refer to below usage and specify at least ONE class name!";
            throw new IllegalArgumentException(String.join(System.lineSeparator(), hint, USAGE));
        }

        List<Class<?>> classList = new ArrayList<>(classNames.length);
        for (; index < classNames.length; index++) {
            String className = classNames[index];
            try {
                Class<?> clazz = Class.forName(className);
                classList.add(clazz);
            } catch (ClassNotFoundException e) {
                String message = String.format("Class with name=%s can't be found, please check!", className);
                throw new RuntimeException(message);
            }
        }
        return classList;
    }

    private void showIgnoredClasses() {
        List<Class<?>> classes = ignoredClasses.stream().
                filter(c -> c != OBJECT_CLASS).
                toList();
        if (!classes.isEmpty()) {
            System.out.println("**请注意:以下的类/接口在类图中被忽略了**");
            classes.forEach(c -> System.out.println("* `" + c.getName() + "`"));
            System.out.println();
        }
    }

    /**
     * Generate header for Mermaid class diagram
     */
    private void generateHeader() {
        System.out.println("```mermaid");
        System.out.println("classDiagram");
        System.out.println();
    }

    /**
     * Generate footer for Mermaid class diagram
     */
    private void generateFooter() {
        System.out.println("```");
    }

    /**
     * Generate main content in Mermaid class diagram
     */
    private void generateClassDiagram() {
        generateHeader();
        doGenerateClassDiagram();
        generateFooter();
    }

    /**
     * Generate a Markdown table that contains name mapping
     */
    private void generateNameMappingTable() {
        Map<String, String> classNames = new TreeMap<>();
        analyzedClasses.forEach(c -> {
            String simpleName = c.getSimpleName();
            if (classNames.containsKey(simpleName)) {
                String prevName = classNames.get(simpleName);
                String currName = c.getName();
                String message = String.format("Duplicated simple class name detected! (%s and %s have the same simple name)", prevName, currName);
                throw new IllegalArgumentException(message);
            }
            if (!simpleName.equals(c.getName())) {
                classNames.put(simpleName, c.getName());
            }
        });

        if (!classNames.isEmpty()) {
            System.out.println();
            System.out.println("| 在上图中的类名/接口名 | `Fully Qualified Name` |");
            System.out.println("| --- | --- |");
            classNames.forEach((simpleName, name) -> {
                String row = String.format("| `%s` | `%s` |", simpleName, name);
                System.out.println(row);
            });
        }
    }

    private void doGenerateClassDiagram() {
        analyzedClasses.forEach(c -> {
            if (inheritanceRelations.containsKey(c)) {
                System.out.printf("%s <|-- %s%n", inheritanceRelations.get(c).getSimpleName(), c.getSimpleName());
            }
            if (realizationRelations.containsKey(c)) {
                String type = c.isInterface() ? "<|--" : "<|..";
                Arrays.stream(realizationRelations.get(c)).forEach(item -> {
                    System.out.printf("%s %s %s%n", item.getSimpleName(), type, c.getSimpleName());
                });
            }
        });

        generateSpecialClassAnnotation();
    }

    /**
     * This method generates annotation for
     * 1. Abstract classes
     * 2. Interfaces
     */
    private void generateSpecialClassAnnotation() {
        Set<Class<?>> abstractClasses = new LinkedHashSet<>();
        Set<Class<?>> interfaces = new LinkedHashSet<>();

        analyzedClasses.forEach(c -> {
            if (c.isInterface()) {
                interfaces.add(c);
            } else if (c.accessFlags().contains(AccessFlag.ABSTRACT)) {
                abstractClasses.add(c);
            }
        });

        if (!abstractClasses.isEmpty() || !interfaces.isEmpty()) {
            System.out.println();
            abstractClasses.forEach(c -> System.out.println("<<Abstract>> " + c.getSimpleName()));
            interfaces.forEach(c -> System.out.println("<<interface>> " + c.getSimpleName()));
        }
    }

    private void analyzeHierarchy(Class<?> currClass) {
        if (!analyzedClasses.contains(currClass) && !ignoredClasses.contains(currClass)) {
            analyzeSuperClass(currClass);
            analyzeInterfaces(currClass);

            analyzedClasses.add(currClass);
        }
    }

    private void analyzeSuperClass(Class<?> currClass) {
        Class<?> superclass = currClass.getSuperclass();
        if (superclass == null || ignoredClasses.contains(superclass)) {
            return;
        }
        analyzeHierarchy(superclass);
        if (!inheritanceRelations.containsKey(currClass)) {
            inheritanceRelations.put(currClass, superclass);
        }
    }

    private void analyzeInterfaces(Class<?> currClass) {
        Class<?>[] interfaces = currClass.getInterfaces();
        for (Class<?> item : interfaces) {
            analyzeHierarchy(item);
        }

        if (!realizationRelations.containsKey(currClass)) {
            Class<?>[] filteredInterfaces = Arrays.stream(interfaces).
                    filter(c -> !ignoredClasses.contains(c)).
                    toArray(Class<?>[]::new);
            realizationRelations.put(currClass, filteredInterfaces);
        }
    }
}

用以下命令可以编译 ClassDiagramGenerator.java

javac ClassDiagramGenerator.java

注意事项

请注意,ClassDiagramGenerator 生成的类图中,不包含任何泛型信息,而且也不展示任何字段/方法。

例子

ClassDiagramGenerator 的使用有如下两种方式 ⬇️

  • java ClassDiagramGenerator C1 C2 C3
  • java ClassDiagramGenerator -i EC1 -i EC2 C1 C2 C3

其中

  • C1/C2/C3 表示要分析的类/接口的全限定类名(不必恰好是 3 个,但是至少要指定一个)
  • EC1/EC2 表示要排除的类/接口的全限定类名(可以不排除任何类/接口,也可以排除若干个类/接口)

这样说还是比较抽象,下面举一些例子,来说明 ClassDiagramGenerator 的用法。

1: 生成 ArrayList 的类图

请运行以下命令以生成对应的内容 ⬇️

java ClassDiagramGenerator 'java.util.ArrayList'

运行结果是 Markdown 格式的,而掘金文档的编辑区支持相关格式。您可以通过下图所示的方式来体验在掘金的文档中如何画类图 ⬇️

jj.png
运行结果
classDiagram

Iterable <|-- Collection
Collection <|.. AbstractCollection
Collection <|-- SequencedCollection
SequencedCollection <|-- List
AbstractCollection <|-- AbstractList
List <|.. AbstractList
AbstractList <|-- ArrayList
List <|.. ArrayList
RandomAccess <|.. ArrayList
Cloneable <|.. ArrayList
Serializable <|.. ArrayList

<<Abstract>> AbstractCollection
<<Abstract>> AbstractList
<<interface>> Iterable
<<interface>> Collection
<<interface>> SequencedCollection
<<interface>> List
<<interface>> RandomAccess
<<interface>> Cloneable
<<interface>> Serializable
在上图中的类名/接口名Fully Qualified Name
AbstractCollectionjava.util.AbstractCollection
AbstractListjava.util.AbstractList
ArrayListjava.util.ArrayList
Cloneablejava.lang.Cloneable
Collectionjava.util.Collection
Iterablejava.lang.Iterable
Listjava.util.List
RandomAccessjava.util.RandomAccess
SequencedCollectionjava.util.SequencedCollection
Serializablejava.io.Serializable
变通的方式

如果您无法在掘金的文档中使用 Mermaid,那么也可以前往 mermaid.live/ 来查看对应的类图,我在 mermaid.live/ 看到的效果如下 ⬇️ (需要自行将结果复制到那个网页去)

image.png

如何忽略某些类/接口

有的用户并不关心 java.lang.Cloneablejava.io.Serializable,此时可以通过 -i 选项忽略指定的类/接口,具体的命令如下 ⬇️

java ClassDiagramGenerator -i 'java.lang.Cloneable' -i 'java.io.Serializable' 'java.util.ArrayList'
运行结果

请注意:以下的类/接口在类图中被忽略了

  • java.lang.Cloneable
  • java.io.Serializable
classDiagram

Iterable <|-- Collection
Collection <|.. AbstractCollection
Collection <|-- SequencedCollection
SequencedCollection <|-- List
AbstractCollection <|-- AbstractList
List <|.. AbstractList
AbstractList <|-- ArrayList
List <|.. ArrayList
RandomAccess <|.. ArrayList

<<Abstract>> AbstractCollection
<<Abstract>> AbstractList
<<interface>> Iterable
<<interface>> Collection
<<interface>> SequencedCollection
<<interface>> List
<<interface>> RandomAccess
在上图中的类名/接口名Fully Qualified Name
AbstractCollectionjava.util.AbstractCollection
AbstractListjava.util.AbstractList
ArrayListjava.util.ArrayList
Collectionjava.util.Collection
Iterablejava.lang.Iterable
Listjava.util.List
RandomAccessjava.util.RandomAccess
SequencedCollectionjava.util.SequencedCollection

2: 生成 LinkedList 的类图

请运行以下命令以生成对应的内容 ⬇️

java ClassDiagramGenerator 'java.util.LinkedList'

运行结果是 Markdown 格式的,展示如下

运行结果
classDiagram

Iterable <|-- Collection
Collection <|.. AbstractCollection
Collection <|-- SequencedCollection
SequencedCollection <|-- List
AbstractCollection <|-- AbstractList
List <|.. AbstractList
AbstractList <|-- AbstractSequentialList
Collection <|-- Queue
Queue <|-- Deque
SequencedCollection <|-- Deque
AbstractSequentialList <|-- LinkedList
List <|.. LinkedList
Deque <|.. LinkedList
Cloneable <|.. LinkedList
Serializable <|.. LinkedList

<<Abstract>> AbstractCollection
<<Abstract>> AbstractList
<<Abstract>> AbstractSequentialList
<<interface>> Iterable
<<interface>> Collection
<<interface>> SequencedCollection
<<interface>> List
<<interface>> Queue
<<interface>> Deque
<<interface>> Cloneable
<<interface>> Serializable
在上图中的类名/接口名Fully Qualified Name
AbstractCollectionjava.util.AbstractCollection
AbstractListjava.util.AbstractList
AbstractSequentialListjava.util.AbstractSequentialList
Cloneablejava.lang.Cloneable
Collectionjava.util.Collection
Dequejava.util.Deque
Iterablejava.lang.Iterable
LinkedListjava.util.LinkedList
Listjava.util.List
Queuejava.util.Queue
SequencedCollectionjava.util.SequencedCollection
Serializablejava.io.Serializable

如果想忽略 java.lang.Cloneablejava.io.Serializable,可以使用 -i 选项,具体的命令如下 ⬇️

java ClassDiagramGenerator -i 'java.lang.Cloneable' -i 'java.io.Serializable'  'java.util.LinkedList'

运行结果如下 ⬇️

请注意:以下的类/接口在类图中被忽略了

  • java.lang.Cloneable
  • java.io.Serializable
classDiagram

Iterable <|-- Collection
Collection <|.. AbstractCollection
Collection <|-- SequencedCollection
SequencedCollection <|-- List
AbstractCollection <|-- AbstractList
List <|.. AbstractList
AbstractList <|-- AbstractSequentialList
Collection <|-- Queue
Queue <|-- Deque
SequencedCollection <|-- Deque
AbstractSequentialList <|-- LinkedList
List <|.. LinkedList
Deque <|.. LinkedList

<<Abstract>> AbstractCollection
<<Abstract>> AbstractList
<<Abstract>> AbstractSequentialList
<<interface>> Iterable
<<interface>> Collection
<<interface>> SequencedCollection
<<interface>> List
<<interface>> Queue
<<interface>> Deque
在上图中的类名/接口名Fully Qualified Name
AbstractCollectionjava.util.AbstractCollection
AbstractListjava.util.AbstractList
AbstractSequentialListjava.util.AbstractSequentialList
Collectionjava.util.Collection
Dequejava.util.Deque
Iterablejava.lang.Iterable
LinkedListjava.util.LinkedList
Listjava.util.List
Queuejava.util.Queue
SequencedCollectionjava.util.SequencedCollection

3: 生成 java.util.Arrays$ArrayList 的类图

调用 Arrays.asList(...) 方法,得到的是 java.util.Arrays$ArrayList 的实例。 请运行以下命令以生成对应的内容 ⬇️

java ClassDiagramGenerator 'java.util.Arrays$ArrayList'

运行结果是 Markdown 格式的,展示如下

运行结果

请注意下图中的 ArrayListjava.util.Arrays 中的一个嵌套类,而不是我们平时常用的 java.util.ArrayList

classDiagram

Iterable <|-- Collection
Collection <|.. AbstractCollection
Collection <|-- SequencedCollection
SequencedCollection <|-- List
AbstractCollection <|-- AbstractList
List <|.. AbstractList
AbstractList <|-- ArrayList
RandomAccess <|.. ArrayList
Serializable <|.. ArrayList

<<Abstract>> AbstractCollection
<<Abstract>> AbstractList
<<interface>> Iterable
<<interface>> Collection
<<interface>> SequencedCollection
<<interface>> List
<<interface>> RandomAccess
<<interface>> Serializable
在上图中的类名/接口名Fully Qualified Name
AbstractCollectionjava.util.AbstractCollection
AbstractListjava.util.AbstractList
ArrayListjava.util.Arrays$ArrayList
Collectionjava.util.Collection
Iterablejava.lang.Iterable
Listjava.util.List
RandomAccessjava.util.RandomAccess
SequencedCollectionjava.util.SequencedCollection
Serializablejava.io.Serializable

4: 生成 List/Set/Deque 的类图

请运行以下命令以生成对应的内容 ⬇️

java ClassDiagramGenerator 'java.util.List' 'java.util.Set' 'java.util.Deque'

运行结果是 Markdown 格式的,展示如下

运行结果
classDiagram

Iterable <|-- Collection
Collection <|-- SequencedCollection
SequencedCollection <|-- List
Collection <|-- Set
Collection <|-- Queue
Queue <|-- Deque
SequencedCollection <|-- Deque

<<interface>> Iterable
<<interface>> Collection
<<interface>> SequencedCollection
<<interface>> List
<<interface>> Set
<<interface>> Queue
<<interface>> Deque
在上图中的类名/接口名Fully Qualified Name
Collectionjava.util.Collection
Dequejava.util.Deque
Iterablejava.lang.Iterable
Listjava.util.List
Queuejava.util.Queue
SequencedCollectionjava.util.SequencedCollection
Setjava.util.Set

5: 生成 HashMap/LinkedHashMap/ConcurrentHashMap 的类图

请运行以下命令以生成对应的内容 ⬇️

java ClassDiagramGenerator 'java.util.HashMap' 'java.util.LinkedHashMap' 'java.util.concurrent.ConcurrentHashMap'

运行结果是 Markdown 格式的,展示如下

运行结果
classDiagram

Map <|.. AbstractMap
AbstractMap <|-- HashMap
Map <|.. HashMap
Cloneable <|.. HashMap
Serializable <|.. HashMap
Map <|-- SequencedMap
HashMap <|-- LinkedHashMap
SequencedMap <|.. LinkedHashMap
Map <|-- ConcurrentMap
AbstractMap <|-- ConcurrentHashMap
ConcurrentMap <|.. ConcurrentHashMap
Serializable <|.. ConcurrentHashMap

<<Abstract>> AbstractMap
<<interface>> Map
<<interface>> Cloneable
<<interface>> Serializable
<<interface>> SequencedMap
<<interface>> ConcurrentMap
在上图中的类名/接口名Fully Qualified Name
AbstractMapjava.util.AbstractMap
Cloneablejava.lang.Cloneable
ConcurrentHashMapjava.util.concurrent.ConcurrentHashMap
ConcurrentMapjava.util.concurrent.ConcurrentMap
HashMapjava.util.HashMap
LinkedHashMapjava.util.LinkedHashMap
Mapjava.util.Map
SequencedMapjava.util.SequencedMap
Serializablejava.io.Serializable

如果想忽略 java.lang.Cloneablejava.io.Serializable,可以使用 -i 选项,具体的命令如下 ⬇️

java ClassDiagramGenerator -i 'java.lang.Cloneable' -i 'java.io.Serializable' 'java.util.HashMap' 'java.util.LinkedHashMap' 'java.util.concurrent.ConcurrentHashMap'

运行结果如下 ⬇️

请注意:以下的类/接口在类图中被忽略了

  • java.lang.Cloneable
  • java.io.Serializable
classDiagram

Map <|.. AbstractMap
AbstractMap <|-- HashMap
Map <|.. HashMap
Map <|-- SequencedMap
HashMap <|-- LinkedHashMap
SequencedMap <|.. LinkedHashMap
Map <|-- ConcurrentMap
AbstractMap <|-- ConcurrentHashMap
ConcurrentMap <|.. ConcurrentHashMap

<<Abstract>> AbstractMap
<<interface>> Map
<<interface>> SequencedMap
<<interface>> ConcurrentMap
在上图中的类名/接口名Fully Qualified Name
AbstractMapjava.util.AbstractMap
ConcurrentHashMapjava.util.concurrent.ConcurrentHashMap
ConcurrentMapjava.util.concurrent.ConcurrentMap
HashMapjava.util.HashMap
LinkedHashMapjava.util.LinkedHashMap
Mapjava.util.Map
SequencedMapjava.util.SequencedMap

6: 生成 java.util.ImmutableCollections$Set12java.util.ImmutableCollections$SetN 的类图

当我们调用 Set.of(...) 方法时,会得到以下两个类的实例 ⬇️

  • java.util.ImmutableCollections$Set12
  • java.util.ImmutableCollections$SetN

请运行以下命令以生成对应的内容 ⬇️

java ClassDiagramGenerator 'java.util.ImmutableCollections$Set12' 'java.util.ImmutableCollections$SetN'

运行结果是 Markdown 格式的,展示如下

运行结果
classDiagram

Iterable <|-- Collection
Collection <|.. AbstractCollection
AbstractCollection <|-- AbstractImmutableCollection
Collection <|-- Set
AbstractImmutableCollection <|-- AbstractImmutableSet
Set <|.. AbstractImmutableSet
AbstractImmutableSet <|-- Set12
Serializable <|.. Set12
AbstractImmutableSet <|-- SetN
Serializable <|.. SetN

<<Abstract>> AbstractCollection
<<Abstract>> AbstractImmutableCollection
<<Abstract>> AbstractImmutableSet
<<interface>> Iterable
<<interface>> Collection
<<interface>> Set
<<interface>> Serializable
在上图中的类名/接口名Fully Qualified Name
AbstractCollectionjava.util.AbstractCollection
AbstractImmutableCollectionjava.util.ImmutableCollections$AbstractImmutableCollection
AbstractImmutableSetjava.util.ImmutableCollections$AbstractImmutableSet
Collectionjava.util.Collection
Iterablejava.lang.Iterable
Serializablejava.io.Serializable
Setjava.util.Set
Set12java.util.ImmutableCollections$Set12
SetNjava.util.ImmutableCollections$SetN

7: 生成 java.util.JumboEnumSetjava.util.RegularEnumSet 的类图

当我们调用 EnumSet.of(...) 方法时,会得到以下两个类的实例 ⬇️

  • java.util.JumboEnumSet
  • java.util.RegularEnumSet

请运行以下命令以生成对应的内容 ⬇️

java ClassDiagramGenerator 'java.util.JumboEnumSet' 'java.util.RegularEnumSet'

运行结果是 Markdown 格式的,展示如下

运行结果
classDiagram

Iterable <|-- Collection
Collection <|.. AbstractCollection
Collection <|-- Set
AbstractCollection <|-- AbstractSet
Set <|.. AbstractSet
AbstractSet <|-- EnumSet
Cloneable <|.. EnumSet
Serializable <|.. EnumSet
EnumSet <|-- JumboEnumSet
EnumSet <|-- RegularEnumSet

<<Abstract>> AbstractCollection
<<Abstract>> AbstractSet
<<Abstract>> EnumSet
<<interface>> Iterable
<<interface>> Collection
<<interface>> Set
<<interface>> Cloneable
<<interface>> Serializable
在上图中的类名/接口名Fully Qualified Name
AbstractCollectionjava.util.AbstractCollection
AbstractSetjava.util.AbstractSet
Cloneablejava.lang.Cloneable
Collectionjava.util.Collection
EnumSetjava.util.EnumSet
Iterablejava.lang.Iterable
JumboEnumSetjava.util.JumboEnumSet
RegularEnumSetjava.util.RegularEnumSet
Serializablejava.io.Serializable
Setjava.util.Set

8: 生成 ThreadPoolExecutor 的类图

请运行以下命令以生成对应的内容 ⬇️

java ClassDiagramGenerator 'java.util.concurrent.ThreadPoolExecutor'

运行结果是 Markdown 格式的,展示如下

运行结果
classDiagram

Executor <|-- ExecutorService
AutoCloseable <|-- ExecutorService
ExecutorService <|.. AbstractExecutorService
AbstractExecutorService <|-- ThreadPoolExecutor

<<Abstract>> AbstractExecutorService
<<interface>> Executor
<<interface>> AutoCloseable
<<interface>> ExecutorService
在上图中的类名/接口名Fully Qualified Name
AbstractExecutorServicejava.util.concurrent.AbstractExecutorService
AutoCloseablejava.lang.AutoCloseable
Executorjava.util.concurrent.Executor
ExecutorServicejava.util.concurrent.ExecutorService
ThreadPoolExecutorjava.util.concurrent.ThreadPoolExecutor

9: 自己手写一些类/接口,用 ClassDiagramGenerator 生成类图

请将以下代码保存为 E.java

class A {}

class B extends A {}

class C extends B implements I1, I2, I3 {}

class D extends C implements I1, I4 {}

interface I1 {}
interface I2 {}
interface I3 {}
interface I4 {}

public class E extends D implements I1, I4  { }

用如下的命令可以编译 E.java 并为 E 生成类图

javac E.java
java -cp . ClassDiagramGenerator 'E'
运行结果
classDiagram

A <|-- B
B <|-- C
I1 <|.. C
I2 <|.. C
I3 <|.. C
C <|-- D
I1 <|.. D
I4 <|.. D
D <|-- E
I1 <|.. E
I4 <|.. E

<<interface>> I1
<<interface>> I2
<<interface>> I3
<<interface>> I4

如果想忽略掉某些类/接口,例如想忽略 BI4,那么可以使用如下的命令 ⬇️

java -cp . ClassDiagramGenerator -i 'B' -i 'I4' 'E'

运行结果如下 ⬇️ 请注意:以下的类/接口在类图中被忽略了

  • I4
  • B
classDiagram

I1 <|.. C
I2 <|.. C
I3 <|.. C
C <|-- D
I1 <|.. D
D <|-- E
I1 <|.. E

<<interface>> I1
<<interface>> I2
<<interface>> I3