早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全。
对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常。
泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
有了泛型以后:
代码更加简洁【不用强制转换】。
程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】。
可读性和稳定性【在编写集合的时候,就限定了类型】。
泛型中的
<Object>
并不是像以前那样有继承关系的,也就是说
List<Object>
和
List<String>
是毫无关系的。
Java泛型提供了类型通配符
?
,
?
通配符表示可以匹配任意类型,任意的Java类都可以匹配。
使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。
简单泛型为什么需要左右一致:
如果允许
List<Number>
指向
new ArrayList<Integer>()
,由于简单泛型允许存入子类元素,最终List里会混入Integer、Long、Double等多种类型元素。然后等到
new ArrayList<Integer>()
重新被赋值给
List<Integer>
时就出事了:
编译器看到
List<Integer>
,只会按它的边界类型转,也就是自动转型为Integer,此时会抛ClassCastException。
设置通配符上下限:
设置上限:
如果想接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】。
设定通配符上限
List<? extends Number>
,表示List集合装载的元素只能是Number的子类或自身。
泛型中可以使用 Number 的方法。
设置下限:
传递进来的只能是Type或Type的父类:
<? super Type>
。
无论是设定通配符?的上限还是下限,都是不能操作与对象有关的方法。
泛型中的参数不考虑其继承关系,如果要考虑其继承关系应该使用设置上下限的方法。
带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。
如果使用extends指明类型,List不允许通过
add(E e)
方法添加上限的的对象,唯一例外的是 null。
如果使用super指明类型,List通过
get()
方法取出数据只能往object自动转型。
“存入”指的是形参带泛型的方法,而“取出”则是返回值带泛型的方法。
List<? extends Human>指向:只能指向子类型List,比如
List<Chinese>
,Human是最上边的类。
List<? extends Human>取出:接上一条限制,不论指向什么List元素必然是Human及其子类型,按Human转。
List<? extends Human>存入:简单泛型要是能解决早解决了,还轮得到我?直接禁止存入。
频繁往外读取内容的,适合用<? extends T>:extends返回值稍微精确些。
super小结
List<? super Human>接收:只能指向父类型List,比如
ArrayList<Creature>
、
ArrayList<Primate>
。
List<? super Human>取出:只能转Object。
通过List<? super Human>取值时,由于它可能指向
ArrayList<Primate>
也可能指向
ArrayList<Creature>
,都是Human的父类型,
而且Human的父类型未来还可能增加。
Java所有类型都继承自Object,所以用Object接收万无一失。
List<? super Human>存入:只能存Human及其子类型元素。
经常往里插入的,适合用<? super T>:super允许存入子类型元素。
存:禁止存入
取:只能是Object
T和?的区别:
T是一个确定的类型(类型参数),通常用于泛型类和泛型方法的定义,?是一个 不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
通过T可以确保泛型参数的一致性,而?不能。
类型参数T可以多重限定(用&设置多个上限)而通配符?不行。
通配符?可以使用超类限定(设置下界)而类型参数T不行。
?可作为实例化对象时的类型参数。
如何选择是用通配符还是泛型方法:
大多时候,我们都可以使用泛型方法来代替通配符的:
//使用通配符
public static void test(List<?> list) {
//使用泛型方法
public <T> void test2(List<T> t) {
如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法。
如果没有依赖关系的,就使用通配符,通配符会灵活一些。
泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。
但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
因为有类型擦除的存在,不能通过泛型的不同来重载方法。
泛型的兼容性:
JDK5提出了泛型这个概念,但是JDK5以前是没有泛型的。也就是泛型是需要兼容JDK5以下的集合的。
当把带有泛型特性的集合赋值给老版本的集合时候,会把泛型给擦除了。它保留的就类型参数的上限Object。
如果把没有类型参数的集合赋值给带有类型参数的集合赋值,不会报错,仅仅是提示“未经检查的转换”。
2、泛型实现机制
运行Java代码的两个阶段:编译期、运行期。
Java代码运行有4个要素:源代码、编译器、字节码、虚拟机。
泛型的实现:
为了兼容各种类型,泛型底层还是采用Object接收所有类型。ArrayList也一样,它其实底层还是Object[]存储元素。
Java的泛型存在“编译后泛型擦除”。
而编译器自动类型转换也和“泛型擦除”有关:因为底层还是Object接收,要想返回实际类型,只能强转。
泛型是JDK专门为编译器创造的语法糖,只在编译期,由编译器负责解析,虚拟机不知情。
存入:普通类继承泛型类并给变量类型T赋值后,就能强制让编译器帮忙进行类型校验。
取出:代码编译时,编译器底层会根据实际类型参数自动进行类型转换,无需程序员在外部手动强转。
通过反射,是可以绕过编译器对泛型的检查,存入其他类型的对象。
本身泛型的引入就是为了解决引用类型强转易出错的问题,也就自然不会去考虑基本类型。
JDK1.5不仅引入泛型,还同时发布自动拆装箱特性,所以int完全可以用Integer代替,也就无需支持int。
Java已经尽自己最大的努力让泛型支持基本类型了。只不过它不是从语法上支持,而是从功能上支持。
但归根结底,Java泛型之所以无法支持基本类型,还是因为存在泛型擦除,底层仍是Object,而基本类型无法直接赋值给Object类型,导致JDK只能用自动拆装箱特性来弥补,而自动拆装箱会带来性能损耗。
枚举是一种特殊的类,和class、interface等是一个级别的(其实就是一个类),一般用于表示多种固定的状态。
自定义类山寨枚举:
静态代码块初始化final常量。
构造器私有,防止外部创建。
正版枚举:
反编译后:
与山寨"枚举"的区别在于:
enum关键字修饰的WeekDayEnum,在编译后会自动继承Enum类,且声明为final
比山寨版枚举多了valueOf()方法,传入名称返回对应的枚举实例
values()并不是直接返回WeekDay[],而是$VALUES.clone()
防止外部调用者随意增删枚举对象。
正版枚举重写了toString()方法。
然当初编写WeekDayEnum时,构造器只传入了两个参数,但编译器却给我们额外加了两个,用来为父类Enum中的name和ordinal赋值。name来自MONDAY、TUESDAY等枚举名称,而ordinal则是编写序号。
枚举和常量类有什么区别呢?
作为常量使用时,区别不大。
枚举优点一:限制重复定义常量。枚举作为常量使用时,编译器会自动限制取值不能相同,而常量类做不到,有可能会重复。
枚举优点二:包含更多维度的信息。枚举毕竟是对象,可以包含更多的信息。
枚举优点三:枚举可以直接用于Switch判断,代码通常会比常量+if/else简洁一些。
枚举做统一返回结果,可以有效规范错误码及错误信息。将错误码及错误信息实现绑定。
枚举策略:
根据会员返回打折力度。黄金会员:6折白银会员:7折;青铜会员:8折。
用枚举实现(在枚举类中定义抽象方法,强制子类实现):
这是基于,枚举中的每个具体元素,都是该枚举类引用指向的对象。
public class MemberDemo {
public static void main(String[] args) {
User user = new User(1L, "bravo", MemberEnum.GOLD_MEMBER.getType());
BigDecimal productPrice = new BigDecimal("1000");
BigDecimal discountedPrice = calculateFinalPrice(productPrice, user.getMemberType());
System.out.println(discountedPrice);
* 根据会员身份返回折扣后的商品价格
* @param originPrice
* @param user
* @return
public static BigDecimal calculateFinalPrice(BigDecimal originPrice, Integer type) {
return MemberEnum.getEnumByType(type).calculateFinalPrice(originPrice);
@Data
@AllArgsConstructor
class User {
private Long id;
private String name;
* 会员身份
* 1:黄金会员,6折优惠
* 2:白银会员,7折优惠
* 3:青铜会员,8折优惠
private Integer memberType;
enum MemberEnum {
// ---------- 把这几个枚举当做本应该在外面实现的MemberEnum之类,不要看成MemberEnum内部的 ----------
GOLD_MEMBER(1, "黄金会员") {
@Override
public BigDecimal calculateFinalPrice(BigDecimal originPrice) {
return originPrice.multiply(new BigDecimal(("0.6")));
SILVER_MEMBER(2, "白银会员") {
@Override
public BigDecimal calculateFinalPrice(BigDecimal originPrice) {
return originPrice.multiply(new BigDecimal(("0.7")));
BRONZE_MEMBER(3, "青铜会员") {
@Override
public BigDecimal calculateFinalPrice(BigDecimal originPrice) {
return originPrice.multiply(new BigDecimal(("0.8")));
// ---------- 下面才是MemberEnum类的定义 ---------
private final Integer type;
private final String desc;
MemberEnum(Integer type, String desc) {
this.type = type;
this.desc = desc;
* 定义抽象方法,留个子类实现
* @param originPrice
* @return
protected abstract BigDecimal calculateFinalPrice(BigDecimal originPrice);
public Integer getType() {
return type;
public String getDesc() {
return desc;
public static MemberEnum getEnumByType(Integer type) {
MemberEnum[] values = MemberEnum.values();
for (MemberEnum memberEnum : values) {
if (memberEnum.getType().equals(type)) {
return memberEnum;
throw new IllegalArgumentException("Invalid Enum type:" + type);