当前位置: 首页 > 科技观察

Java高级特性介绍:这三个你必须了解(泛型、反射和注解)

时间:2023-03-13 13:11:13 科技观察

1.泛型介绍在日常编程过程中,泛型是这三个特性中使用频率最高的。“generic”一词中的“generic”一词可以理解为泛化的意思,即从特殊的、个别的扩展到一般的。Oracle对泛型的官方定义是:泛型类型是由类型参数化的泛型类或接口。简而言之,泛型通过类型参数化解决了程序的一般设计和实现中的几个问题。Java泛型是1.5版本之后引入的特性。它主要用来解决三类问题:1.编译器类型检查。比如上图中的例子1,设计了一个简单的Box类,里面定义了一个私有对象。属性,同时定义了get()和set()两种行为,其中set()用于保存Box中的对象,set()用于获取Box中的object对象。从抽象的角度看,Box类抽象了一个在盒子中存储和访问object对象的行为,访问方法接受或返回Object类型的对象。在这种抽象的基础上,可以存储除原始类型之外的任何类型的对象,Object类型的声明体现了面向对象的继承概念。示例2实现了Box在不同业务场景下的使用。它列出了两种不同的业务场景。场景一需要在Box中存储String类型的对象,场景二需要在Box中存储Integer类型的对象。这种情况,在实际开发中,很有可能会误传入一个String对象,导致运行时错误,而这恰恰是因为Box只能传入任何类型的对象,这一点在集合类操作。比如例3的情况:首先声明了一个List类型的boxes对象,里面存放了两个对象,一个是String类型的“aaaaa”,一个是Integer类型的11111。在业务场景中,用户认为boxes中存储的所有对象都是String类型,所以在取出第二个对象进行类型转换时出现错误。这种情况常常使用户感到困惑。编译时没有问题,但是运行时出现异常。也就是说,在这个面向对象的抽象过程中,无法通过编译来验证类型是如何使用的。那么泛型是如何解决这类问题的呢?Oracle意识到了上述问题。引入泛型后,它通过将代码中的“publicclassBox”改为“publicclassBox”创建了一个泛型类型声明,而这个声明的背后本质上是引入了一个可以在类中任意位置使用的类型变量T班级。如例4所示:可以看出,除了新增的泛型类型声明外,将原代码中出现的所有Object都替换为类型变量T。乍一看,类型变量这个词好像是一个有点晦涩,但其实仔细想想,你会发现并不难理解。上面的例子4可以理解为“使用泛型时,可以通过类型参数T给Box类型本身”,结合Oracle官方给出的定义“泛型的本质是类型参数化”会有更深刻的理解。在示例5中,当声明和初始化对象时,指定了类型参数T。在第一种情况下,T是String;在第二种情况下,T是整数。这样,在场景2中,当String类型的数据“aaaaa”传给IntegerBox时,程序就会报错。例6中泛型集合对象的操作也类似。在声明了一个Listboxes对象后,如果将Integer对象11111传入boxes,程序会报错。可以看到,通过使用泛型,解决了之前多服务场景下的问题,因为现在类型不匹配的问题可以在编译阶段解决,而不是在运行时暴露问题,只要合理的是使用泛型可以在很大程度上避免这种风险。对于泛型的使用,这个参数化类型的作用看似是一个声明,其实是背后的契约。2.强制类型转换回过头来看示例3,List类型的boxes对象中存储了两个对象,分别是String类型的“aaaaa”和Integer类型的11111。有一个问题。在boxes的声明中,用户并不知道boxes列表中应该存储什么类型的对象,编译器也不知道集合中存储的数据类型。这只能通过实际的业务场景来确定。盒子的类型是什么?使用强制将Object转成String的方法,达到业务需要的效果。使用泛型后,这种场景下强制类型转换的问题就解决了。如例7,通过泛型声明,指定集合中元素的类型参数为String类型,这样编译器就可以直接知道元素的类型,而不需要依赖实际的业务逻辑进行转换,从而解决了这种类型的胁迫问题。3.可读性和灵活性除了进行编译器类型检查和避免类型强制转换,泛型还可以有效提高代码的可读性。比如3,如果不使用泛型,一个不了解业务场景的人在对集合进行操作的时候,是无法知道列表中存放的是什么类型的对象。如果使用泛型,可以通过其类型参数来判断,勾勒出当前的业务场景,也增加了代码的可读性,同时在抽象继承的基础上大胆发展。泛型使用的灵活性体现在很多方面,因为它本质上是对继承使用的增强。因为泛型是专门工作的,所以编译器在编译源代码的时候,首先要检查泛型的类型参数,检查出类型不匹配等问题,然后进行类型擦除,同时插入强制转换指令,从而实现泛型.除了上述基本用法外,泛型还有几个特殊的高级用法:通配符的设计中有一些场景,比如使用泛型后,先声明一个Animal类,然后再声明一个类继承自Animal类的Cat类,很明显Cat类是Animal类的子类,但是List并不是List的子类,往往需要在类中表达这样的逻辑关系程序。为了解决这种类似的场景,在泛型参数类型的基础上增加了通配符的使用。具体有以下三种用法:,,。其中,前两者称为有限通配符,称为非限定通配符。1.上界通配符上界通配符顾名思义,表示类型的上界(包括自身),所以通配符参数化类型可能是T或者T的子类。正因为无法确定具体是什么类型,所以add方法是有限制的(null可以添加,因为null表示任何类型),但它可以从列表中获取元素并将它们分配给父类型。与上面的第一个示例一样,第三个add()操作将受到限制,因为List和List是List。2.下界通配符下界通配符表示参数化类型是T的超类型(包括它自己),一层一层向上到Object,编译器没有办法判断get()返回的对象是什么类型,所以get()方法受限。但是,可以使用add()方法。add()方法可以添加T类型和T类型的子类型。比如第二个例子,先添加一个Cat类型对象,然后添加两个Cat子类型对象。这种方法是可行的,但是如果你添加一个Animal类型的对象,显然会颠倒继承关系,这是不可行的。3.无界通配符了解了上界通配符和下界通配符之后,其实自然而然就理解了无界通配符。无限通配符用,?表示任何类型,只有null可以表示任何类型(Object本身也是一种类型,但是不能表示任何类型,所以List和List的含义不同,前者的类型是Object,这是继承树的顶层,而后者的类型是完全未知的)。2、反射机制反射是Java语言本身的一个重要的动态机制。一句话解释反射的定义:自我控制,自我描述。即可以通过反射动态获取类、属性和方法的信息,也可以构造对象并控制其属性和行为。上图中有一个Apple类,它有两个构造函数,一个属性,get()和set()两个行为。左侧的“自我描述”中,主要是尝试通过反射的方式获取Apple类的构造函数信息及对应的参数个数、类的属性信息、类的方法信息动态过程。其中有一个Class类型,可以生成Class对象供ClassLoader加载,从而实现在jvm中对它的调用。在这个程序中,打印了一些类信息、类属性信息和类方法信息。右边的“自控”代码,在运行过程中创建了一些对象,并触发了对象的一些行为,最后尝试给对象的属性赋值。反射的基本用法比较简单,但是这种机制增强了Java语言的灵活性。如上图所示,非反射Java类的大致运行过程是:编写源文件Apple.java,然后由编译器编译成字节码文件Apple.class,最后加载到jvm中,运行。在使用反射时,编译器一开始对它的类型(编译类型和动态类型)一无所知,只有在运行之后,编译器才知道它的真实类型。反射的优势主要在于两点:在某些场景下,这种“未知类型”实际上极大地增强了程序在运行时的灵活性,但其性能会受到一些损失;可以在运行时判断对象的类型,所以从本质上大大增强了多态的特性,进一步将上层的抽象与下层的具体实现解耦。这两点在JDBCDriver中体现的非常明显。例如上图的例子,通过反射机制实现了JDBC驱动的加载方式,从而保证了在运行时可以动态选择要加载的驱动,大大增强了程序的灵活性。.另外,JDBC只设计驱动需要实现的接口,并不关心驱动厂商的数量和实现方式。只需安装统一规格即可。至于类型的判断和具体方法的触发,留给运行时动态判断,这种反射机制的使用充分体现了多态性,降低了类之间的耦合度。3、注解的使用注解是在1.5版本引入的,现在已经成为日常程序开发中非常重要的一部分。Annotation是一种元数据,它本身没有任何作用。如果有的话,它必须附加到一个特定的对象。日常使用中最常见的两个注解是@Override和@Deprecated。不管注解的具体概念、用法和工作原理如何,注解都非常类似于“标签”的概念。@Override可以理解为给方法加了一个标签,意思是“这是一种继承关系,已经被子类重写的方法”。进一步理解,在方法上加上这个标签后,如果父类中不存在该方法,编译时会报错,可以解决一些继承场景下的问题。注意方法名的拼写错误,提高部分程序的可读性。如上图所示,同样以@Override为例,进一步抽取和抽象注解。具体抽象了四个方面:第一,在作用域上,只能作用于子类重写的方法;其次,在生命周期方面,注解只在编译时检查,编译后没有作用;另外,在文档支持方面,作为解决可读性问题的例子,@Documented注解旨在表明注释是否包含在JavaDoc中;在层次结构设计上,@inherited被设计用来表示该注解是否可以被子类继承。上图中定义了一个Apple描述注解,包括@Target、@Retention、@Inherited和@Documented四个注解,说明它的生命周期就是程序运行的生命周期,可以被子类继承,文档可以被包括在内。设计好这个注解后,就可以在上一篇文章的Apple实例上使用了。如图所示,为每个类和方法添加一个注解。添加后可以通过反射看到注解的效果,这样可以更好的增强其自描述能力和配置灵活性。