Java编程基础

1 基本数据类型及应用

1.1 基本数据类型的概念、取值范围和转换

  • 根据存储形式,Java数据类型可分为基本类型(primitive types)和引用数据类型(reference types)
    在这里插入图片描述

  • 基本数据类型:占用的空间固定,直接在变量中存储值和传递值

  • 注意:Java基本类型长度固定和系统位宽无关

    数据类型类型存储空间取值(包装类中MIN_VALUE和MAX_VALUE)默认值精度
    byte整数型1字节-128(-27)~127(27-1)0
    short整数型2字节-32768(-215)~ 32767(215-1)0
    int整数型4字节-2,147,483,648(-231)~ 2,147,483,647(231-1)0
    long整数型8字节(-264)~(264-1)0L
    float浮点型14字节-3.41038~ 3.410380.0f23bit(6~7位)
    double浮点型18字节-1.710308 ~ 1.7103080.0d/0.052bit(15~16位)
    char字符型2字节0(1u0000)~ 65535(\uffff)(Unicode)0(\u0000)
    boolean布尔型1字节true or falsefalse
  • 取值陷阱

    • 基本数据类型在使用时,如忽略取值范围,会造成代码异常
    • 浮点数不能直接==比较
  • 字符转义

    • char和String赋值时需要注意转义,尤其八进制/十六进制表示方法
  • 类型转换

    • 自动类型转换:Java所有的数值类型之间可以相互转换。如果系统可以自动把一种类型的值赋给另一种类型,称为自动类型转换。允许自动类型转换的标准是表数范围小的类型赋值给表数范围大的类型。就如同把水从小瓶子倒入大瓶子,不会有数值损失。尽管如此,从整数型往浮点型转换时,仍可能有精度损失。另外如果转换和计算同时进行,也要小心数据的溢出。
    • 强制类型转换:强制类型转换可用于所有基础数值类型之间的转换,很多场合特指表数范围大的类型给表数范围小的类型赋值。
      • 语法格式:(targetType) value 运算符:圆括号()
      • 例子:long longValue = 1000L;int i =(int)longValue;强转如同把水从大瓶往小瓶倒,系统也无法保障不溢出,所以必须由编码者明确指定,自求多福。
    • 类型提升:类型提升:当一个算数表达式包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升。
      提升规则:
      • byte,char,short自动提升至int
      • 整个表达式的数据类型自动提升至表达式中的最高数据类型

1.2 引用类型

  • 引用数据类型:类似C的指针,引用类型存放的是对象的引用。
  • 引用变量在声明时被指定为一个特定的类型,一旦声明后,类型就不能被改变
  • 引用类型包括:类、接口、数组、枚举、注解
  • 所有引用类型的默认值都是null。
  • 引用变量可以用来引用任何与之兼容的类型
    在这里插入图片描述

1.3 包装类、拆装箱和常量池

1.3.1 包装类

  • 包装类就是把基本数据类型和其辅助方法的封装到类中

  • 包装类的作用:基本类型不是类,所以无法获得类的基本特性,无法参与转型、泛型、集合、反射等过程。包装类的出现就是为了弥补这个缺陷。

  • 包装类有哪些

    包装类包装类转基本类型基本类型转包装类
    ByteByte.valueOf(byte)bytelnstance.byteValue()
    ShortShort.valueOf(short)shortinstance.shortValue()
    IntegerInteger.valueOf(int)integerlnstance.intValue()
    LongLong. valueOf(long)longlnstance. longValue)
    FloatFloat. valueOf(float)floatInstance. floatValue)
    DoubleDouble. valueOf(double)doublelnstance. doubleValue()
    CharacterCharacter. valueOf(char)charlnstance. charValue()
    booleanBoolean. valueOf(booleann)booleanlnstance. boolean Value()

1.3.2 拆装箱

  • 包装类装箱是指把基本类型装入包装类的过程,拆箱是从包装类对象中取出基本类型

    	Integer a = Integer.valueOf(5);//装箱
    	int b = a.intValue();//拆箱
    
  • 自动拆装箱(一种违反常规的“语法糖”,可让包装类的使用接近基本类型)

    	Integer a =5;//装箱
    	int b = a;//拆箱
    
  • 自动拆装箱的弊端

    • 由于包装类的拆装箱过于无感,混用时很可能造成性能问题。
    • 有更匹配的重载方法时,不自动拆装箱

1.3.3 常量池

  • 包装类的比较
    • 包装类比较必须使用equals(),==比较的是两个引用是否指向一个对象

      	Integer il = 200;
      	Integer i2 = 200;
      	System.out.println(il =i2);//false
      
  • 常量池
    • 包装类的创建很浪费性能,因此Java对简单数字(-128~127)对应的包装类进行了缓存,称为常量池。
    • 通过直接量赋值的包装类如果在此范围内,会直接使用常量池中的引用,因此==返回true。
      	Integer i1 = 100;
      	Integer i2 = 100;
      	System.out.println(i1 == i2); //true 
      	Integer i1 = 100;
      	Integer i2 = new Integer(i:100);
      	System.out.println(i1 = i2);//false
      

3 Java基本语法

3.1 程序结构

  • Java的程序结构如图,由不同的包组成,包由.Java文件组成,每个.Java文件有且只能有一个和文件同名的公共类/接口。
    在这里插入图片描述
    • 包声明:每个文件至多一句,声明类所在的包
    • Import声明:文件中引入的所有外部类声明
    • 公共类/接口:每个文件只能有一个公共类,类名必须和文件名一致。所有的内部类、成员、方法等都写在公共类内。
    • 程序入口:Java程序的入口方法。形式必须为public static void main(String[]args)

3.2 标志符和关键字

3.2.1 标志符

  • 在java语言中,用来标志类名、对象名、变量名、方法名、类型名、数组名、包名的有效字符序列,称为“标识符”;
  • 标识符由字母、数字、下划线、美元符号组成,且第一个字符不能是数字;
  • java语言区分大小写;
  • 命名规则:类名首字母大写,变量名和方法名采用驼峰标志法,常量全大写,多个单词之间用"_"隔开,包名全小写

3.2.2 关键字

  • 在java语言中,有一些专门的词汇已经被赋予了特殊的含义,不能再使用这些词汇来命名标识符,这些专有词汇,称为“关键字”。

    类别关键字
    访问控制private protected public
    修饰符abstract class extends final implements interface native new static strictfp synchronized transient volatile
    程序控制break continue return do.while if else for instanceof switch case default
    错误处理try catchthrow throws finally
    包相关import package
    基本类型boolean bytechar double float int long short nulltrue false
    变量引用super thisvoid
    保留字goto const

3.3 运算符与表达式

3.3.1 运算符

  • 算术运算符用在数学表达式中,它们的作用和在数学中的作用一样
  • 关系运算符:用作条件判断
  • 位运算符:Java定义了位运算符,应用于整数类型(int),长整型(long),短整型(short),字符型(char),和字节型(byte)等类型。位运算符作用在所有的位上,并且按位运算
  • 逻辑运算符:逻辑运算符如果第一个条件已经能决定判断,则不会执行后面的条件逻辑
  • 赋值运算符:对变量进行赋值操作
  • 条件运算符:条件运算符也被称为三元运算符。该运算符有3个操作数,并且需要判断布尔表达式的值。该运算符的主要是决定哪个值应该赋值给变量。
    variable x =(expression)?(value if true):(value if false)
  • instanceof 运算符:该运算符用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型)
    • instanceof运算符使用格式如下:
    • (Object reference variable) instanceof(class/interface type)

3.3.2 逻辑与流程控制

  • 循环结构:for,while,do…while
  • 条件语句:if…else,switch
  • Java的流程控制和C语言非常相似。注意代码规范和逻辑清晰即可。
  • Java1.5引入了增强型的for循环,可用于数组和集合,书写更加清晰简便。
  • Switch陷阱:漏写break语句的后果,平时代码中尽量不要通过省略break来控制流程

3.4 异常处理

3.4.1 异常分类

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
  • 运行时异常:运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
  • 错误:错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

3.4.2 异常处理语法

  • try 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
  • try异常匹配规则:
    • 如果抛出的异常对象属于catch子句的异常类,或者属于该异常类的子类,则认为生成的异常对象与catch块捕获的异常类型相匹配。
    • 每个catch子句依次检查,一旦有子句匹配,则后面的被旁路。由此,编写多重catch语句时,需要先小后大,即先子类再父类。
  • catch-用于捕获异常。catch用来捕获try语句块中发生的异常。
  • finally–finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件),只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
  • finally规则
    • finally语句必定会执行(除非程序/线程提前退出)
    • finally执行时机在try/catch代码块退出前。即,所有代码块后,跳出逻辑前。
    • finally执行语句不会影响原有返回值。
    • finally中如执行返回(return或抛异常),会代替原有返回逻辑。
  • throw用于抛出异常。
  • throws-用在方法签名中,用于声明该方法可能抛出的异常。

3.5 匿名内部类和Lamda表达式

  • 匿名内部类:省略了内部类名
  • Lamda表达式:省略了接口名、函数名
    • Lamda的依据
      • Lamda只能作用在函数接口上,即只含有一个方法的接口
      • Lamda依赖于参数推断
    • Lamda语法格式
      • (params) -> expr 或者 (params) -> {statements; }
      • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
      • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
      • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
      • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
  • 复杂形式:带参数和多行语句

4 IO编程

4.1 流

  • 在Java IO中,流是一个核心的概念。流从概念上来说是一个连续的数据流。你既可以从流中读取数据,也可以往流中写数据。流与数据源或者数据流向的媒介相关联。在Java IO中流既可以是字节流(以字节为单位进行读写),也可以是字符流(以字符为单位进行读写)。
  • 典型媒介:文件、管道、网络、内存缓存、Java标准流(System.in,System.out,System.error)
  • IO基本分类
    • 字节流:以字节(byte)为单位处理数据,适合处理二进制数据

    • 字符流:以Unicode码元(char)为单位处理数据,适合处理文本数据

      流通形式字节流字符流
      输入流InputStreamReader
      输出流OutputStreamWriter

4.2 NIO(Non-blocking I/O,在Java领域,也称为New I/O)

  • 一种同步非阻塞的I/O模型,也是1/0多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、1/O处理问题的有效方式。
  • Buffer(缓冲区)、Channel(通道)、Selector(选择器)
    在这里插入图片描述

4.2.1 Buffer(缓冲区)

  • 缓冲区本质为一块可读写数据的内存,Java中包装为Buffer对象,并提供了一组操作方法
  • Java NIO不同于IO的一点:NIO面向缓冲区,而IO面向流
  • Buffer读写步骤
    • 写入数据到Buffer
    • flip0切换读模式
    • 从Buffer中读取数据
    • clear()/compact0方法清数据
      在这里插入图片描述

4.2.2 Channel(通道)

  • Channel:类似流,但有区别
    • 通道是双向的,既可读亦可写,和物理的IO系统非常相似
    • 通道可以异步地读写
    • 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入
      在这里插入图片描述

4.2.3 Selector(选择器)

  • Selector:用于侦听多个通道是否已做好读写准备,配合非阻塞IO实现单个线程管理多个通道。
    在这里插入图片描述

  • 基本用法

    	// 注册通道侦听
    	channel. configureBlocking(false); 
    	SelectionKey key = channel. register(selector, SelectionKey. OP_READ);
    	// 可侦听的Channel状态有四种
    	// 1 SelectionKey.OP_CONNECT 
    	// 2 SelectionKey.OP_ACCEPT 
    	// 3 SelectionKey.OP_READ
    	// 4 SelectionKey.OP_WRITE
    	// 获取就绪通道
    	int readyChannels = selector.select(); 
    	if(readyChannels > 0){
    		Set<SelectionKey> selectedKeys = selector.selectedKeys();
    	}
    

4.2.4 IO多路复用

  • IO多路复用:单个线程进行多个通道的IO,现代操作系统和多核CPU已越来越适应多任务处理,线程开销也越来越小。IO多路复用已不是提高并发IO性能的必须手段。
  • 阻塞IO、非阻塞IO、IO多路复用比较
    • 阻塞IO:线程只能空闲等待,只有靠增加线程数提升效率
    • 非阻塞IO:虽可同时尝试多个通道,但轮询会消耗过多CPU或错过IO时机
    • IO多路复用:触发式IO,可实现单线程管理多个通道,且不必担心错过读写时机。
      在这里插入图片描述

5 正则表达式

5.1 定义与转义

  • 定义
    • 正则表达式定义了字符串的模式。
    • 正则表达式可以用来搜索、编辑或处理文本。
    • 正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
  • 转义
    • 在Java中,\\表示:我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。
    • 在其他的语言中(如Perl),一个反斜杠\就足以具有转义的作用,而在Java中正则表达式中则需要有两个反斜杠才能被解析为其他语言中的转义作用。
    • Java的正则表达式中,两个\\代表其他语言中的一个\,这也就是为什么表示一位数字的正则表达式是\\d,而表示一个普通的反斜杠是\\\\
  • 常用字符
    • ^:匹配输入字符串开始的位置。如果设置了RegExp对象的Multiline属性,^还会与"\n""r"之后的位置匹配。
    • $:匹配输入字符串结尾的位置。如果设置了RegExp对象的Multiline属性,$还会与"\n""\r"之前的位置匹配。
    • *:零次或多次匹配前面的字符或子表达式。例如,zo*匹配"z""zoo"*等效于(0,)。
    • +:一次或多次匹配前面的字符或子表达式。例如,"zo+""zo""zoo"匹配,但与"z"不匹配。+等效于(1,)。
    • ?:零次或一次匹配前面的字符或子表达式。例如,"do(es)?"匹配"do""does"中的"do"。?等效于{0,1}。
    • [a-z]字符范围:匹配指定范围内的任何字符。例如,"[a-z]"匹配"a""z"范围内的任何小写字母。
    • \w :匹配任何字类字符,包括下划线。与"[A-Za-z0-9_]"等效。
    • \W:与任何非单词字符匹配,与"[^A-Za-z0-9_]"等效。

5.2 调用类与捕获组

  • 调用类
    • Pattern类:一个正则表达式的编译表示。Pattern类没有公共构造方法。要创建一个 Pattern对象,必须首先调用其公共静态编译方法,它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数。
    • Matcher类:对输入字符串进行解释和匹配操作的引擎。与Pattern类一样,Matcher也没有公共构造方法。你需要调用Pattern对象的matcher方法来获得一个Matcher对象。
    • PatternSyntaxException:一个非强制异常类,它表示一个正则表达式模式中的语法错误。
  • 捕获组
    • 捕获组是把多个字符当一个单独单元进行处理的方法,它通过对括号内的字符分组来创建。
    • 捕获组是通过从左至右计算其开括号来编号。例如,在表达式((A)(B©)),有四个这样的组:((A)(B©))、(A)、(B©)、©.
    • Matcher类的groupCount方法返回一个int值,表示matcher对象当前有多个捕获组。
    • group(0)是一个特殊的组,代表整个表达式。该组不包括在groupCount的返回值中。

5.3 正则注入(regex injection)

  • 定义
    • 攻击者可能会通过恶意构造的输入对初始化的正则表达式进行修改,比如导致正则表达式不符合程序规定要求;可能会影响控制流,导致信息泄漏,或导致ReDos攻击。
    • 我司JAVA安全规范规定,禁止直接使用不可信数据构造正则表达式
  • 利用方式
    • 匹配标志:不可信的输入可能覆盖匹配选项,然后有可能会被传给Pattern.compile()方法。
    • 贪梦:一个非受信的输入可能试图注入一个正则表达式,通过它来改变初始的那个正则表达式,从而匹配尽可能多的字符串,从而暴露敏感信息。
    • 分组:程序员会用括号包括一部分的正则表达式以完成一组动作中某些共同的部分。攻击者可能通过提供非受信的输入来改变这种分组。
  • 输入校验
    • 非受信的输入应该在使用前净化,从而防止发生正则表达式注入。
    • 当用户必须指定正则表达式作为输入时,必须注意需要保证初始的正则表达式没有被无限制修改。
    • 在用户输入字符串提交给正则解析之前,进行白名单字符处理(比如字母和数字)
    • 开发人员必须仅仅提供有限的正则表达式功能给用户,从而减少被误用的可能。

5.4 ReDos攻击

  • ReDos攻击概述
    • JDK中提供的正则匹配使用的是NFA引擎
    • NFA引擎具有回溯机制,匹配失败时话费时间很大
    • 当使用简单的非分组正则表达式时,是不会导致ReDos攻击的
  • 潜在危险
    • 包含具有自我重复的重复性分组的正则
      • 举例:^(\d+)+$、^(\d*)*$、^(\d+)*$.^(\d+/\s+)*$,当输入字符串为1111111111111111111x1时,正则表达式^(\d+)+$就会不断进行失败重试,从而耗死CPU计算。
    • 包含替换的重复性分组
      • 举例:^(\d|\dl\d)+$、^(\d]\d?)+$
  • 规避措施
    • 进行正则匹配前,先对匹配的文本的长度进行校验;
    • 在编写正则时,尽量不要使用过于复杂的正则,越复杂越容易有缺陷;
    • 在编写正则时,尽量减少分组的使用;
    • 避免动态构建正则,当使用不可信数据构造正则时,要使用白名单进行严格校验。

6 Java面向对象特性

6.1 面向对象特性

  • 继承
    继承是子类继承父类的特征和行为,能使得:
    • 子类对象(实例)具有父类的实例域和方法。
    • 子类从父类继承方法,使得子类具有父类相同的行为。
  • 多态
    • 多态是同一个行为具有多个不同表现形式或形态的能力。
    • 多态就是同一个接口,使用不同的实例而执行不同操作。
    • 同一个事件发生在不同的对象上会产生不同的结果。
  • 封装
    • 封装是一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
    • 封装被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
    • 封装特性要求,访问该类的代码和数据,必须通过严格的接口控制。
    • 封装的目的是,在修改代码时只需要修改实现代码片段,而不用修改调用代码片段。
    • 适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

6.2 Java面向对象特性

6.2.1 继承

  • Java中的继承体系结构
    • Java中使用extends关键字来表示类继承关系。
    • Java中使用implement关键字来表示接口实现关系。
    • Java的类继承体系是单继承,接口继承体系是多实现。
    • Java也可使用内部类的方式来实现类的多继承特性。
    • Java中的继承初始化顺序为:父类对象属性初始化–>父类对象构造方法->子类对象属性初始化–>子类对象构造方法
  • Java中的方法覆写
    • Java允许子类对父类方法进行重写,当调用方法时会优先调用子类的方法。
    • 如果仅是方法名相同,那么此类操作称为重载,而不是覆写。
    • 只有当返回值类型、方法名、参数类型及个数都与父类继承的方法相同时,才能进行覆写。
    • 对于覆写的方法,我们一般使用@Override注解标注,有助于静态检查。
  • 抽象类 Abstract Class
    • 抽象类里面可以没有抽象方法。
    • 抽象类既可以对方法进行声明也可以对方法进行实现,还能定义final方法。
    • 如果一个类里面有抽象方法,那么这个类一定是抽象类。
    • 抽象类中的方法都要被实现,所以抽象方法不能是静态的static,也不能是私有的private。
    • 抽象类中抽象方法必须被子类全部实现,如果子类不能全部实现,那么子类必须也是抽象类。
    • 不能直接被实例化。
  • 接口Interface
    • Interface可以使用反射的代理方法,但Abstract Class无法实现反射代理。
    • Interface可以继承接口,甚至可以继承多个接口;但是类只能继承一个类。
    • Interface里面的方法只能声明,不能有具体的实现。(但在Java 8中引入了default关键字,此关键字标注接口方法后,允许接口进行默认实现)
    • 接口里面的方法也必须全部被子类实现,如果子类不能实现那么子类必须是抽象类。
    • 不能直接被实例化。
  • 在实际编程中,建议将继承多用于is-a的关系;将接口看做是has-a关系。抽象类功能较之接口虽多,但扩展性不如接口,优先考虑使用接口来解决问题。
  • final关键字
    • fnal修饰类,则该类不允许被继承。
    • final修饰方法,则该方法不允许被覆写。
    • final修饰属性,则该类的该属性不会进行隐式的初始化,构造方法中必须唯一赋值。
    • final修饰变量,则该变量的值只能赋一次值,在声明变量的时候才能赋值,即变为常量。
  • this关键字
    • 在类中作为关键字,可以引用成员变量、方法、以及对当前对象的引用。
    • 作为构造方法,只能在构造方法中通过this来调用其他构造方法且要放在首行,普通方法中不能使用。
    • 不能通过this递归调用构造方法。
  • super关键字
    • 如果自己用super关键字在子类里调用父类的构造方法,则必须在子类的构造方法中的第一行。
    • 如果子类的构造方法中没有显示调用父类的构造方法,则系统默认调用父类无参的构造方法。
    • 如果子类构造方法中既没有显示调用父类的构造方法,而父类没有无参的构造方法,则编译出错。

注意:super.getClass()和this.getClass()返回的都是new对象时指定的类。

6.2.2 多态

  • 多态的特点
    • 可替换性:父类的引用中可以替换成任意子类
    • 可扩充性:增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。
    • 接口性:父类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。.
    • 灵活性:在应用中体现灵活多样的操作,提高了使用效率。
    • 简化性:简化对应用软件的代码编写和修改过程,在处理大量对象的运算和操作时尤为重要。
  • 多态的表现形式
    • 引用多态:向上类型转换(隐式类型转换,父类->子类);向下类型转换(强制类型转换,子类->父类)。
      • 使用instanceof运算符,解决引用对象的类型,避免类型转换的安全性问题。
    • 方法多态:创建本类对象时,调用本类方法;创建子类对象时,调用为子类重写方法或者继承的方法。
      • 重载(Overload):一个同名方法可以传入多个参数组合。
      • 覆写(Override):由于继承关系中的子类有一个和父类同名同参数的方法,会覆盖掉父类的方法。
      • 基本数据类型存在一个方法匹配调用顺序char-> int-> long->double,同时也会进行自动装箱,调用包装类方法。

6.2.3 封装

  • Java中的访问修饰符
    在这里插入图片描述
  • Java中封装步骤
    • 修改属性可见性为private为属性创建getter和setter方法
    • getter和setter方法中加入合法性判断等校验内部类
    • 内部类可分为静态内部类、成员内部类、方法内部类、匿名内部类
  • 内部类
    • 提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
    • 内部类的方法可以直接访问外部类的所有数据,包括私有的数据。
    • 内部类可以帮助实现多继承功能,但不建议使用;
    • 提倡使用Interface来实现多继承概念

6.2.4 泛型

  • 泛型的概念
    • 泛型类型(类和接口)在定义类、接口和方法时可以作为参数。
    • 类似方法声明时使用的形式参数一样,泛型中提供的是类型参数(types parameters)。
    • 类型参数输入的是类型;类型参数可以为一个,也可以为多个。
    • 泛型机制能使用不同的输入去重用同样的代码。
  • 泛型的优势
    • 编译阶段进行更强的类型检查
    • 消除强制类型转换
    • 实现泛化算法,提高通用性,如集合类ArrayList等
  • 泛型的分类
    • 类泛型 例:public class DemoClass<T>{}
    • 方法泛型 例:public <T> void DemoMethod<T>(T e){}
    • 接口泛型 例:public interface Demolnterface<T>{}
  • 有界类型参数
    • 有界类型 例:public class DemoClass <T extends A>{}
    • 多界类型 例:public class DemoClass<T extends A&B&C> {} A为Class,B.C为Interface
  • 子类化
    以Collection为例,ArrayList<E>实现List<E>List<E>继承Collection<E>,有如下结论:
    • ArrayList<String>List<String >的子类
    • List<String>Collections<String>的子类
    • 特别注意List<String>不是List<Object>的子类
  • 通配符
    Java中使用问号(?)作为通配符,表示未知类型。可分为上界、无界、下界三种类型。
    • 上界通配符 例:public void DemoMethod(List<?extends A> list){}.
    • 无界通配符 例:public void DemoMethod(List<?> list){}
    • 下界通配符 例:public void DemoMethod(List<?super A> list){}

《华为Java语言通用编程规范》建议:Java的泛型可按PECS(Producer Extends Consumer Super)原则来设计上界和下界类型。声明一个带泛型的类或接口的时候,建议限制可以用的泛型类型,避免接口使用者乱用。

  • 类型推断.
    • 泛型方法引入了类型推断,无需在尖括号<>里指定一个类型
    • 典型例子:Map<String,Object> demoMap= new HashMap<>();,此处若省略<>,则编译器会发出未经检查告警
  • 类型擦除
    • 使用泛型类的界或者Object对象来替换所有的类型参数。
    • 类型擦除会保证参数化类型没有新的类被创建;所以泛型不会产生运行时开销
    • 创建桥方法(合成出重载方法)以保证泛型类型继承中的多态。
  • 不可具体化类型
    • 编译期间类型信息被类型擦除机制擦除的类型-未被定义为无界通配符的泛型类型的调用
    • 例如List<String>List<Number> 是不可具体化类型,JVM不能在运行时区别这些类型。
  • 堆污染.
    • 在混合原始类型和参数化类型时,或在执行未检查类型转化时,会发生堆污染
    • 不可具体化类型的可变参数方法存在潜在堆污染风险
    • 添加@SafeVarargs注解来防止不可具体化类型的可变参数方法的警告
  • 泛型的限制
    • 不能使用基本数据类型实例化泛型类型:Pair<int,char> p;
    • 不能创建类型变量实例:E elem = new E();
    • 不能声明类型为类型变量的静态属性:private static T t;
    • 不能使用参数化类型来做类型转换或者做instanceof判断:List<E> list; Boolean flag = list instanceof ArrayList<Integer>;
    • 不能创建参数化类型数组:List<Integer>[] arrayOfLists = new List<Integer>[2];
    • 不能创建,捕获或者抛出参数化类型对象:class MathException<T> extends Exception{}
    • 不能重载形式参数类型擦除后为相同的原始类型的方法
      • public void print(Set<String> strSet){}
      • public void print(Set<Integer> intSet){}

版权声明:本文为zuefeng原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。