Spring杂谈 | 你真的了解泛型吗?从java的Type到Spring的ResolvableType(1)

简介: Spring杂谈 | 你真的了解泛型吗?从java的Type到Spring的ResolvableType(1)

Type


简介


Type是Java 编程语言中所有类型的公共高级接口(官方解释),也就是Java中所有类型的“爹”;其中,“所有类型”的描述尤为值得关注。它并不是我们平常工作中经常使用的 int、String、List、Map等数据类型,而是从Java语言角度来说,对基本类型、引用类型向上的抽象;


Type体系中类型的包括:原始类型(Class)、参数化类型(ParameterizedType)、数组类型(GenericArrayType)、类型变量(TypeVariable)、基本类型(Class);


原始类型,不仅仅包含我们平常所指的类,还包括枚举、数组、注解等;


参数化类型,就是我们平常所用到的泛型List、Map<String,Integer>这种;


数组类型,并不是我们工作中所使用的数组String[] 、byte[],而是带有泛型的数组,即T[] ;


基本类型,也就是我们所说的java的基本类型,即int,float,double等

Type体系的出现主要是为了解决泛型的一系列问题。


接口定义


public interface Type {
   // 返回这个类型的名称
    default String getTypeName() {
        return toString();
    }
}

可以看到Type接口内只定义了一个方法,这个方法会返回该类型的名称


UML类图


微信图片_20221112213339.png

在上面的图中对于Class我相信大家都已经很了解了。我们主要对其余四个子接口进行测试分析


ParameterizedType


简介


参数化类型,也就是我们所说的泛型。像List就是一个参数化类型,但是List并不是,因为没有使用泛型。


接口定义

public interface ParameterizedType extends Type {
  // 对于一个参数化类型而言,必定是带有泛型的,所有这里是为了获取到其中的泛型的具体类型,也就是<>中的内容
    // 返回一个数组是因为,有时候会定义多个泛型,比如Map<String,String>
    Type[] getActualTypeArguments();
  // 获取原始类型,这里不带泛型,就是class
    Type getRawType();
  // 获取这个类所在类的类型,这里可能比较拗口,举个例子,假如当前这个ParameterizedType的类型为
    // O<T>.I<U>,那么调用这个方法所返回的就是一个O<T>类型
    Type getOwnerType();
}

使用示例

public class Main extends OwnerTypeDemo<String> {
    private List<String> stringList;
    private Map<String, String> stringStringMap;
    private Map.Entry<String, ?> entry;
    private OwnerTypeDemo<String>.Test<String> testOwnerType;
    private List list;
    private Map map;
    public void test(List<String> stringList, List list) {
    }
    public static void main(String[] args) {
        Class<Main> mainClass = Main.class;
        Field[] fields = mainClass.getDeclaredFields();
        for (Field field : fields) {
            Type genericType = field.getGenericType();
            String typeName = genericType.getTypeName();
            String name = field.getName();
            if (genericType instanceof ParameterizedType) {
                System.out.println(name + "是一个参数化类型,类型名称为:" + typeName);
                ParameterizedType parameterizedType = (ParameterizedType) genericType;
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                System.out.println(name + "的actualTypeArguments:" + Arrays.toString(actualTypeArguments));
                Type ownerType = parameterizedType.getOwnerType();
                System.out.println(name + "的ownerType:" + ownerType);
                Type rawType = parameterizedType.getRawType();
                System.out.println(name + "的rawType:" + rawType);
            } else {
                System.out.println(name + "不是一个参数化类型,类型名称为:" + typeName);
            }
        }
        System.out.println("===================开始测试方法中的参数=========================");
        Method[] declaredMethods = mainClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            String methodName = declaredMethod.getName();
            Type[] genericParameterTypes = declaredMethod.getGenericParameterTypes();
            for (int i = 0; i < genericParameterTypes.length; i++) {
                Type parameterType = genericParameterTypes[i];
                String typeName = parameterType.getTypeName();
                System.out.println("打印" + methodName + "方法的参数," + "第" + (i + 1) + "个参数为:" + parameterType);
                if (parameterType instanceof ParameterizedType) {
                    System.out.println("第" + (i + 1) + "个参数是一个参数化类型, 类型名称为 : " + typeName);
                } else {
                    System.out.println("第" + (i + 1) + "个参数不是一个参数化类型, 类型名称为 : " + typeName);
                }
            }
        }
        System.out.println("===================开始测试父类中的泛型=========================");
        // 获取带有泛型的父类
        Type genericSuperclass = mainClass.getGenericSuperclass();
        if (genericSuperclass instanceof ParameterizedType) {
            System.out.println("父类是一个参数化类型,类型名称为:" + genericSuperclass.getTypeName());
        }
    }
}
class OwnerTypeDemo<T> {
    class Test<T> {
    }
}

程序会做如下输出:

stringList是一个参数化类型,类型名称为:java.util.List<java.lang.String>
stringList的actualTypeArguments:[class java.lang.String]
stringList的ownerType:null
stringList的rawType:interface java.util.List
stringStringMap是一个参数化类型,类型名称为:java.util.Map<java.lang.String, java.lang.String>
stringStringMap的actualTypeArguments:[class java.lang.String, class java.lang.String]
stringStringMap的ownerType:null
stringStringMap的rawType:interface java.util.Map
entry是一个参数化类型,类型名称为:java.util.Map$Entry<java.lang.String, ?>
entry的actualTypeArguments:[class java.lang.String, ?]
entry的ownerType:interface java.util.Map
entry的rawType:interface java.util.Map$Entry
testOwnerType是一个参数化类型,类型名称为:main.java.OwnerTypeDemo<java.lang.String>$Test<java.lang.String>
testOwnerType的actualTypeArguments:[class java.lang.String]
testOwnerType的ownerType:main.java.OwnerTypeDemo<java.lang.String>
testOwnerType的rawType:class main.java.OwnerTypeDemo$Test
list不是一个参数化类型,类型名称为:java.util.List
map不是一个参数化类型,类型名称为:java.util.Map
===================开始测试方法中的参数=========================
打印main方法的参数,第1个参数为:class [Ljava.lang.String;
第1个参数不是一个参数化类型, 类型名称为 : java.lang.String[]
打印test方法的参数,第1个参数为:java.util.List<java.lang.String>
第1个参数是一个参数化类型, 类型名称为 : java.util.List<java.lang.String>
打印test方法的参数,第2个参数为:interface java.util.List
第2个参数不是一个参数化类型, 类型名称为 : java.util.List
===================开始测试父类中的泛型=========================
父类是一个参数化类型,类型名称为:main.java.OwnerTypeDemo<java.lang.String>

通过上面的例子可以看出,ParameterizedType可以让我们明确字段或者方法参数上是否使用了泛型,并获取到泛型的具体类型。那是不是依赖ParameterizedType就能解决所有的泛型问题了呢?答案显然是不是的,我们看一个特殊的例子:

public class SpecialDemo<T extends Type> {
    T t;
    public static void main(String[] args) {
        Class<SpecialDemo> specialDemoClass = SpecialDemo.class;
        Field[] declaredFields = specialDemoClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Type genericType = declaredField.getGenericType();
            if (genericType instanceof ParameterizedType) {
                System.out.println("t是一个参数化类型");
            } else {
                System.out.println("t不是一个参数化类型");
            }
        }
    }
    // 程序输出:t不是一个参数化类型
}

运行上面的程序,会发现字段t不是一个参数化类型,这就意味着没办法通过ParameterizedType来解决这一类泛型问题。我们分析<T extends Type>,会发现其实T类似于一个变量,我们可以在使用时可以传入具体的类,比如我们可以这样:

SpecialDemo<ParameterizedType> specialDemo = new SpecialDemo<>();

同时这个基于这个<T extends Type>表达式,我们知道这个变量是具有属性的,最直观的就是T是有上界的,所有的T都继承了Type。基于这种情况,Java对其进行了抽象,得到了一个新的类型TypeVariable。


TypeVariable


简介


类型变量,或者也可以叫泛型变量。具体就是指我们在申明泛型时定义的T,K,U这种变量。在之前的例子中,SpecialDemo<T extends Type>,T就是一个类型变量。


接口定义

public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
    // 获取泛型的边界
    Type[] getBounds();
  // 获取申明所在的具体对象
    D getGenericDeclaration();
  // 获取具体类型变量的名称
    String getName();
  // 获取类型变量边界上添加的注解
    AnnotatedType[] getAnnotatedBounds();
}

可以看到,TypeVariable本身也使用了泛型,并且泛型的上界为GenericDeclaration。在了解TypeVariable之前,有必要先对GenericDeclaration做一个简单的说明。GenericDeclaration这个接口主要限定了哪些地方可以定义TypeVariable,换言之,也就是定义了哪些地方可以申明泛型。这个接口只有3个实现类(忽略Executable抽象类)。如下:

image.png

从这里我们也能看到,我们只能在方法(包括普通方法跟构造方法)以及类上申明泛型。


这里需要对接口定义的方法做进一步的说明:


getBounds()会返回泛型的边界,但是这里的边界跟我们在参数化类型中定义的边界不同,这里的边界只有上界。即我们不通通过super关键字来申明一个泛型,例如下面这种:

class A<T super classA>{}

在申明泛型时,我们要明确一点,申明是为了使用,而在上面的例子中,我们不能使用T来干任何事情,因为我们不能确定T中的任何方法(只能确定它是一个Object,但是这没有任何意义)。所以对于泛型变量来说,只存在上界,也就是只能使用extends关键字进行申明


  1. getGenericDeclaration(),返回泛型申明时所在的类或者方法
  2. 返回泛型变量的名称,也就是我们定义泛型时采用的T,K,U这一类的名称
  3. getAnnotatedBounds(),此方法返回一个AnnotatedType类型的数组,获取的是我们在类型变量的上界。不同于getBounds()方法的是,这个方法可以获取到边界上添加的注解


使用示例


public class TypeVariableMain<T, K extends @TypeAnnotation Integer & Type> {
    public <U extends Long, V> void testTypeVariable(Map<U, V> map) {
    }
    public static void main(String[] args) {
        Class<TypeVariableMain> typeVariableMainClass = TypeVariableMain.class;
        TypeVariable<Class<TypeVariableMain>>[] typeParameters = typeVariableMainClass.getTypeParameters();
        for (int i = 0; i < typeParameters.length; i++) {
            TypeVariable<Class<TypeVariableMain>> typeParameter = typeParameters[i];
            Type[] bounds = typeParameter.getBounds();
            String name = typeParameter.getName();
            AnnotatedType[] annotatedBounds = typeParameter.getAnnotatedBounds();
            Class<TypeVariableMain> genericDeclaration = typeParameter.getGenericDeclaration();
            System.out.println("第" + (i + 1) + "个类型变量的名称为:" + name);
            System.out.println("通过getBounds方法获取到,第" + (i + 1) + "个类型变量的边界为:" + Arrays.toString(bounds));
            System.out.println("第" + (i + 1) + "个类型变量的申明的位置为:" + genericDeclaration);
            System.out.println("通过getAnnotatedBounds方法获取到,第" + (i + 1) + "个类型变量的边界为:"
                    + Arrays.stream(annotatedBounds).map(AnnotatedType::getType).collect(Collectors.toList()));
            for (AnnotatedType annotatedType : annotatedBounds) {
                Annotation[] annotations = annotatedType.getAnnotations();
                if (annotations.length > 0) {
                    System.out.println("第" + (i + 1) + "个类型变量的上界上添加了注解,注解为" + annotations[0]);
                }
            }
        }
        System.out.println("===================基于方法获取类型变量====================");
        Method[] declaredMethods = typeVariableMainClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            String methodName = declaredMethod.getName();
            if (methodName.equals("main")) {
                // 为了方便,直接排除main函数了
                continue;
            }
            TypeVariable<Method>[] typeVariables = declaredMethod.getTypeParameters();
            int i = 1;
            for (TypeVariable<Method> typeVariable : typeVariables) {
                System.out.println("方法:\"" + methodName + "\"的第" + (i++) + "个类型变量为" + typeVariable.getName());
            }
        }
    }
}

程序打印如下:

第1个类型变量的名称为:T
通过getBounds方法获取到,第1个类型变量的边界为:[class java.lang.Object]
第1个类型变量的申明的位置为:class main.java.TypeVariableMain
通过getAnnotatedBounds方法获取到,第1个类型变量的边界为:[class java.lang.Object]
第2个类型变量的名称为:K
通过getBounds方法获取到,第2个类型变量的边界为:[class java.lang.Integer, interface java.lang.reflect.Type]
第2个类型变量的申明的位置为:class main.java.TypeVariableMain
通过getAnnotatedBounds方法获取到,第2个类型变量的边界为:[class java.lang.Integer, interface java.lang.reflect.Type]
第2个类型变量的上界上添加了注解,注解为@main.java.TypeAnnotation()
===================基于方法获取类型变量====================
方法:"testTypeVariable"的第1个类型变量为U
方法:"testTypeVariable"的第2个类型变量为V

为了让大家加深对ParameterizedType以及TypeVariable理解,这里我额外添加一个Demo

public class TypeVariableMain02<T, K extends @TypeAnnotation Integer & Type> {
    private K k;
    private List<T> list;
    public static void main(String[] args) {
        Class<TypeVariableMain02> typeVariableMain02Class = TypeVariableMain02.class;
        Field[] declaredFields = typeVariableMain02Class.getDeclaredFields();
        for (Field field : declaredFields) {
            Type genericType = field.getGenericType();
            String typeName = genericType.getTypeName();
            String name = field.getName();
            if (genericType instanceof ParameterizedType) {
                System.out.println(name + "是一个参数化类型,类型名称为:" + typeName);
                ParameterizedType parameterizedType = (ParameterizedType) genericType;
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                System.out.println(name + "的actualTypeArguments:" + Arrays.toString(actualTypeArguments));
                Type ownerType = parameterizedType.getOwnerType();
                System.out.println(name + "的ownerType:" + ownerType);
                Type rawType = parameterizedType.getRawType();
                System.out.println(name + "的rawType:" + rawType);
                for (Type actualTypeArgument : actualTypeArguments) {
                    if (actualTypeArgument instanceof TypeVariable) {
                        System.out.println("字段:" + name + "中包含一个类型变量");
                        String name1 = ((TypeVariable) actualTypeArgument).getName();
                        AnnotatedType[] annotatedBounds = ((TypeVariable) actualTypeArgument).getAnnotatedBounds();
                        Type[] bounds = ((TypeVariable) actualTypeArgument).getBounds();
                        GenericDeclaration genericDeclaration = ((TypeVariable) actualTypeArgument).getGenericDeclaration();
                        System.out.println("类型变量的名称为:" + name1);
                        System.out.println("个类型变量的边界为:" + Arrays.toString(bounds));
                        System.out.println("类型变量的申明的位置为:" + genericDeclaration);
                        System.out.println("通过getAnnotatedBounds方法获取到,类型变量的边界为:" + annotatedBounds[0].getType());
                    }
                }
            } else if (genericType instanceof TypeVariable) {
                System.out.println(name + "是一个类型变量,类型名称为:" + typeName);
                TypeVariable typeVariable = (TypeVariable) genericType;
                Type[] bounds = typeVariable.getBounds();
                String name1 = typeVariable.getName();
                AnnotatedType[] annotatedBounds = typeVariable.getAnnotatedBounds();
                GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
                System.out.println("类型变量的名称为:" + name1);
                System.out.println("个类型变量的边界为:" + Arrays.toString(bounds));
                System.out.println("类型变量的申明的位置为:" + genericDeclaration);
                System.out.println("通过getAnnotatedBounds方法获取到,类型变量的边界为:" + annotatedBounds[0].getType() + "  " + annotatedBounds[1].getType());
            }
        }
    }
}

程序输出:

k是一个类型变量,类型名称为:K
类型变量的名称为:K
个类型变量的边界为:[class java.lang.Integer, interface java.lang.reflect.Type]
类型变量的申明的位置为:class main.java.TypeVariableMain02
通过getAnnotatedBounds方法获取到,类型变量的边界为:class java.lang.Integer  interface java.lang.reflect.Type
list是一个参数化类型,类型名称为:java.util.List<T>
list的actualTypeArguments:[T]
list的ownerType:null
list的rawType:interface java.util.List
字段:list 中包含一个类型变量
类型变量的名称为:T
个类型变量的边界为:[class java.lang.Object]
类型变量的申明的位置为:class main.java.TypeVariableMain02
通过getAnnotatedBounds方法获取到,类型变量的边界为:class java.lang.Object

GenericArrayType


简介


GenericArrayType是Type的子接口,用于表示“泛型数组”,描述的是形如:A[]或T[]的类型。其实也就是描述ParameterizedType类型以及TypeVariable类型的数组,即形如:classA[][]、T[]等


接口定义


public interface GenericArrayType extends Type {
    // 返回数组中元素的类型,TypeVariable或者ParameterizedType
    Type getGenericComponentType();
}

使用示例


public class GenericArrayTypeMain<T> {
    T[] t1;
    T[][] t2;
    List<T> list;
    List<String>[] stringListArray;
    String[][] stringArray;
    public static void main(String[] args) {
        Class<GenericArrayTypeMain> genericArrayTypeMainClass = GenericArrayTypeMain.class;
        Field[] declaredFields = genericArrayTypeMainClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            String name = declaredField.getName();
            Type genericType = declaredField.getGenericType();
            if (genericType instanceof GenericArrayType) {
                System.out.println(name + "是一个泛型数组");
                Type genericComponentType = ((GenericArrayType) genericType).getGenericComponentType();
                System.out.println("数组的元素类型为:" + genericComponentType);
            } else {
                System.out.println(name + "不是一个泛型数组");
            }
        }
    }
}

程序输出:

t1是一个泛型数组
数组的元素类型为:T
t2是一个泛型数组
数组的元素类型为:T[]
list不是一个泛型数组
stringListArray是一个泛型数组
数组的元素类型为:java.util.List<java.lang.String>
stringArray不是一个泛型数组

通过上面的Demo我们会发现,无论从左向右有几个[]并列,这个方法仅仅脱去最右边的[]之后剩下的内容就作为这个方法的返回值。


另外,在上面的例子中,大家可以思考以下几个问题:


t1是一个泛型数组,数组的元素类型为:T,那么T是一个什么类型呢?

t2是一个泛型数组,数组的元素类型为:T[],那么T[]又是什么类型?

上述问题留给大家自行思考


了解了ParameterizedType跟TypeVariable以及这两种类型的数组类型GenericArrayType之后,接着我们思考一个问题,我们在定义泛型时,经常会使用来通配符,形如下面这种形式{? extends Number},这个时候即使我们获取到? extends Number也没有办法做进一步的处理。这个时候就要用到我们接下来要介绍的这个接口了,请往下看


WildcardType


简介


专门用来处理泛型中的通配符,需要注意的是,WildcardType并不是JAVA所有类型中的一种,表示的仅仅是类似 {? extends T}、{? super K}这样的通配符表达式。


接口定义


public interface WildcardType extends Type {
    // 获取通配符表达式的上界
    Type[] getUpperBounds();
    // 获取通配符表达式的下界
    Type[] getLowerBounds();
}

上面这两个方法之所以会返回数组是为了保持扩展性,实际上现在返回的数组的大小就是1,JDK8中至少是这样的吗,更高版本的没有去尝试。


使用示例


public class WildcardTypeDemo<T> {
    Map<? super String, ? extends List<T>> map;
    public static void main(String[] args) {
        Class<WildcardTypeDemo> wildcardTypeDemoClass = WildcardTypeDemo.class;
        Field[] declaredFields = wildcardTypeDemoClass.getDeclaredFields();
        for (Field field : declaredFields) {
            Type genericType = field.getGenericType();
            if (genericType instanceof ParameterizedType) {
                // 使用了通配符表达泛型的,必定是一个参数化类型
                // 获取泛型的实际类型,就是获取<>中的内容,这里获取到的是<? super String, ? extends List<T>>
                Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    // 两个泛型都使用了通配符,都会进入这个判断
                    if (actualTypeArgument instanceof WildcardType) {
                        // 分別获取上界跟下界
                        // ? super String,这个表达式的下界为String,上界为Object
                        // ? extends List<T>,这个表达式的下界为Object,上界为List<T>,
                        // 同时List<T>又是一个参数化类型,而T又是一个类型变量
                        Type[] lowerBounds = ((WildcardType) actualTypeArgument).getLowerBounds();
                        Type[] upperBounds = ((WildcardType) actualTypeArgument).getUpperBounds();
                        // 这里我主要处理? extends List<T>
                        for (Type upperBound : upperBounds) {
                            if (upperBound instanceof ParameterizedType) {
                                System.out.println("参数化类型的名称为:" + upperBound.getTypeName());
                                Type[] actualTypeArguments1 = ((ParameterizedType) upperBound).getActualTypeArguments();
                                for (Type type : actualTypeArguments1) {
                                    if (type instanceof TypeVariable) {
                                        String name = ((TypeVariable) type).getName();
                                        System.out.println("类型变量名称为:" + name);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    // 程序输出:
    // 参数化类型的名称为:java.util.List<T>
  // 类型变量名称为:T

我相信如果你对Java中的类型已经完全理解了,上面的代码配合注释应该不难看懂

相关文章
|
6天前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
14天前
|
前端开发 Java 数据库连接
Spring 框架:Java 开发者的春天
Spring 框架是一个功能强大的开源框架,主要用于简化 Java 企业级应用的开发,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,并由Pivotal团队维护。
36 1
Spring 框架:Java 开发者的春天
|
14天前
|
Java 数据库连接 开发者
Spring 框架:Java 开发者的春天
【10月更文挑战第27天】Spring 框架由 Rod Johnson 在 2002 年创建,旨在解决 Java 企业级开发中的复杂性问题。它通过控制反转(IOC)和面向切面的编程(AOP)等核心机制,提供了轻量级的容器和丰富的功能,支持 Web 开发、数据访问等领域,显著提高了开发效率和应用的可维护性。Spring 拥有强大的社区支持和丰富的生态系统,是 Java 开发不可或缺的工具。
|
13天前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
12 0
[Java]泛型
|
14天前
|
JSON Java Maven
实现Java Spring Boot FCM推送教程
本指南介绍了如何在Spring Boot项目中集成Firebase云消息服务(FCM),包括创建项目、添加依赖、配置服务账户密钥、编写推送服务类以及发送消息等步骤,帮助开发者快速实现推送通知功能。
32 2
|
18天前
|
存储 人工智能 Java
将 Spring AI 与 LLM 结合使用以生成 Java 测试
AIDocumentLibraryChat 项目通过 GitHub URL 为指定的 Java 类生成测试代码,支持 granite-code 和 deepseek-coder-v2 模型。项目包括控制器、服务和配置,能处理源代码解析、依赖加载及测试代码生成,旨在评估 LLM 对开发测试的支持能力。
29 1
|
21天前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
107 1
|
22天前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
10 1
|
22天前
|
Java Apache Maven
Java/Spring项目的包开头为什么是com?
本文介绍了 Maven 项目的初始结构,并详细解释了 Java 包命名惯例中的域名反转规则。通过域名反转(如 `com.example`),可以确保包名的唯一性,避免命名冲突,提高代码的可读性和逻辑分层。文章还讨论了域名反转的好处,包括避免命名冲突、全球唯一性、提高代码可读性和逻辑分层。最后,作者提出了一个关于包名的问题,引发读者思考。
Java/Spring项目的包开头为什么是com?
|
26天前
|
安全 Java 编译器
Java基础-泛型机制
Java基础-泛型机制
13 0