面向对象
类和对象
类的定义
类的组成是由属性和行为两部分组成
-
属性:在类中通过成员变量来体现(类中方法外的变量)
-
行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)
1
2
3
4
5
6
7public void 类名{
//成员变量
//成员方法
//构造器(可选)
//代码块(可选)
//内部类(可选)
}
对象的使用
- 创建对象的格式:
- 类名 对象名 = new 类名();
- 调用成员的格式:
对象名.成员变量
对象名.成员方法();
工具类
- 帮助我们做一些事情,但不描述任何事物的类
私有化构造方法
-
外界不可以创建此类的对象,因为这个类不描述任何事物,因此创建这个类的对象没有意义
1
2
3private class Math {
private Math(){}
}
方法定义为静态
- 因为外界不可以创建此类对象,因此需要将方法全部定义为静态,以供使用
封装
封装思想
-
对象代表什么,就得封装对应的数据,并提供数据对应的行为。eg:人画圆,画圆这种行为应定义在圆中
-
封装代码实现:
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
成员变量private,提供对应的getXxx()/setXxx()方法
private
private是一个修饰符,可以用来修饰成员(成员变量,成员方法)
- 被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
- 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
- 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
成员变量|局部变量|this
成员变量:直接定义在类中
局部变量:在类的某个方法中定义的变量
this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
- this:代表方法调用者的地址值
- 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
- 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
构造方法
1 | //格式 |
- 构造方法的创建
如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法
- 构造方法的重载
如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法
static关键字
- static表示静态,是Java中的一个修饰符,可以修饰成员方法,成员变量
- 被static修饰的成员变量/方法,叫做静态变量/方法
静态变量
特点
- 被该类所有对象所共享,即当所用对象的此变量皆相同时,使用static修饰此成员变量
- 不属于对象,属于类
- 随着类的加载而被加载到静态区中,优先于对象存在
访问
1 | //方法1(推荐) |
静态方法
特点
- 多用在测试类和工具类中
- javabean类中很少使用
访问
1 | //方法1(推荐) |
static注意事项
-
静态方法只能访问静态变量和静态方法
静态随类的加载而加载,在对象之前便存在,故不可以访问非静态的东西
因此测试类中
Main()
方法外的其他方法也必须被static修饰 -
非静态方法可以访问所有变量和方法
-
静态方法中没有this关键字,也没有super关键字
静态方法不属于对象,而属于类,不需要对象来调用,故没有this关键字
继承
概念
-
继承是面向对象三大特征之一,可以让类跟类之间产生子父的关系,子类可以调用父类中的代码,提高复用性
-
格式
1
public class 子类 extends 父类 {}
特点
顶层父类是Object类。所有的类默认继承Object,作为父类。
-
Java只支持单继承,不支持多继承
1
2
3
4
5//一个类只能有一个直接父类,不可以有多个直接父类
class A {}
class B {}
class C1 extends A {} //√
//class C2 extends A,B {} //× -
一个类可以有多个子类
1
2
3
4//A可以有多个子类
class A {}
class C1 extends A {}
class C2 extends B {} -
可以多层继承
1
2
3class A {}
class C1 extends A {}
class D extends C1 {}
字类可以继承父类的哪些内容?
内容 | ||
---|---|---|
构造方法 | 非私有–不能 | private–不能 |
成员变量 | 非私有–能 | private–能 |
成员方法 | 虚方法–能 | 否则–不能 |
虚方法:非private 和 非static 和 非final修饰的方法
注意
- 子类只能访问父类中的非私有成员
- 子类不能继承父类的私有属性,但是如果子类中公有的方法影响到了父类私有属性,那么私有属性是能够被子类使用的。
继承后访问特点
成员变量
就近原则
-
先在局部位置找,本类成员位置找,父类成员位置找,逐级往上
1
2
3
4//出现重名的成员变量怎么办?
System.out.println(name); //从局部位置开始往上找 --通常指的是子类方法中定义的name
System.out.println(this.name); //从本类成员位置开始往上找 --通常指的是子类成员变量的name
System.out.println(super.name); //从父类成员位置开始往上找 --通常指的是父类成员变量的name
成员方法
- this调用:就近原则
- super调用:直接找父类
构造方法
构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super()
,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子)
- 子类不能继承父类的构造方法,但是可以通过super调用
- 默认先访问父类中无参的构造方法,再执行自己
- 子类构造方法的第一行,有一个默认的
super()
,默认先调用父类的无参构造方法,因此super()
可以省略不写,但如果想要调用父类有参构造,必须在第一行手动书写super(参数...)
super()
和this()
调用构造方法
1 | super(...) -- 调用父类的构造方法,根据参数匹配确认 |
注意
-
super(…)是根据参数去确定调用父类哪个构造方法的。
-
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
-
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
方法重写
概念与本质
- 子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。
- 覆盖虚方法表中的方法。因为只有可以添加到虚方法表中的虚方法才可以被继承
使用场景
- 子类继承了父类的方法,但子类觉得父类的这方法不足以满足自己的需求,子类便可以重写了一个与父类同名的方法。
@override注解
- @override可以校验重写是否正确,可读性更好
重写方法基本要求
- 子类重写的方法尽量跟父类中的方法保持一致
- 只有虚方法表里面的方法可以被重写。因为只有可以添加到虚方法表中的虚方法才可以被继承
多态
概念与注意
概念
-
多态是继封装和继承之后,面向对象的第三大特征
-
同种类型的对象,表现出的不同形态;即通过父类指向不同的子类,使传递父类对象后可以调用不同子类的方法
1
2//格式
父类类型 对象名称 = 子类对象;
注意
- 使用父类型作为参数,可以接收所有子类对象
- 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
- 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(后面会学)。
- 而且多态还可以根据传递的不同对象来调用不同类中的方法。
使用前提
- 有继承/实现关系
extends
- 有父类引用指向子类对象
父类类型 对象名称 = 子类对象;
- 有方法重写(因为不重写多态没意义)
@override
多态运行的特点
调用成员变量
-
编译看左边,运行看左边
-
f
是Fu
类型的,所以默认会从Fu
这个类中去找1
2
3
4Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把左边父类name属性的值打印出来
System.out.println(f.name);
调用成员方法
-
编译看左边,运行看右边
-
f
是Fu
类型的,所以默认会从Fu
这个类中去找,但是多态使用时代表子类已经对方法进行了重写,那么虚方法表中是会把父类方法进行覆盖,因此调用的是子类中重写的方法1
2
3
4Fu f = new Zi();
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();
多态的弊端和类型转换
多态的弊端
- 多态编译阶段是看左边父类类型的,如果子类有些独有的功能(方法),此时多态的写法就无法访问子类独有功能了。
类型转换
-
因为多态的弊端,所以想要调用子类的独有功能必须向下转型
-
向上转型(自动转换) – 小–>大,自动转换
1
2
3//当父类引用指向一个子类对象时,便是向上转型。
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat(); -
向下转型(强制转换)
1
2
3
4//一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
Cat c =(Cat) a; -
类型转换的异常
1
2
3
4
5
6
7
8// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
//报出了ClassCastException,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。
instanceof关键字
-
为了避免ClassCastException的发生,Java提供了
instanceof
关键字,给引用变量做类型的校验,格式如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
包
- 使用同一个包中的类时,不需要导包
- 使用
java.lang
包中的类时,不需要导包 - 其他情况都需要导包
- 如果同时使用两个包中的同名类时,需要使用全类名:
包名 + 类名
final关键字
概念
-
final
:不可改变,最终的含义。可以用于修饰类、方法和变量。 -
修饰类:表示该类为最终类,不能被继承
-
修饰方法:表示该方法是最终方法,不能被重写
-
修饰变量:叫做常量,只能被赋值一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//注意:修饰成员变量时,涉及初始化问题,成员变量可以显示初始化or构造方法初始化,一个变量两者只能选择其一
//显示初始化:
public class Student {
final int num = 10;
}
//构造方法初始化
//注意:每个构造方法中都需要赋值一次
public class Student {
final int num;
final int age;
public Student(int num) {
this.num = 20;
}
public Student(int num, int age) {
this.num = name;
}
}
常量
命名规范
- 单个单词:全部大写
- 多个单词:全部大写,单词之间用下划线隔开
细节
- final修饰的是基本数据类型,那么变量存储的数据值是不可改变的
- final修饰的是引用数据类型,那么变量存储的地址值是不能发生改变的,对象内部的属性值是可以改变的
权限修饰符
public | protected | 默认 | private | |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中的类 | √ | √ | √ | |
不同包的子类中 | √ | √ | ||
不同包中的无关类 | √ |
public具有最大权限。private则是最小权限。编写代码时,如果没有特殊的考虑,建议这样使用权限:
- 成员变量使用
private
,隐藏细节。 - 构造方法使用
public
,方便创建对象。 - 成员方法使用
public
,方便调用方法。- 如果类中的某个成员方法是抽取其他成员方法中的共性代码,这个方法一般也私有,因为这些共性代码一般不希望被别的类调用
小贴士:不加权限修饰符,就是默认权限
代码块
- 分类
- 局部代码块,构造代码块,静态代码块
- 局部代码块的作用
- 提前结束变量的声明周期(已淘汰)
- 构造代码块的作用
- 抽取构造方法中的重复代码(不够灵活)
静态代码块:star:
-
格式
-
static { }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 特点
- 随着类的加载而加载,并且自动触发,<u>只执行一次</u>
- 使用场景
- 在类加载时,做一些数据初始化的时候使用
- 执行顺序
- 静态代码块 --> 构造代码块 --> 构造方法
- [(3条消息) java中静态代码块详解_这辈子_安静的努力着的博客-CSDN博客_静态代码块](https://blog.csdn.net/qq_35868412/article/details/89360250)
# 抽象类
## 作用
- 抽取共性时,无法确定方法体,就把方法定义为抽象的。
- 强制让子类按照某种格式重写
- 抽象方法所在的类,必须是抽象类
## abstract格式
```java
//抽象类
public abstract class 类名{}
//抽象方法
public abstract 返回值类型 方法名(参数列表);
-
注意:star:
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
-
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
-
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
-
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
-
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
-
抽象类存在的意义是为了被子类继承。
理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。
接口
作用
- 由继承可知所有子类的共性被抽取到父类中,但当某个共性不是全部子类,只是部分子类的共性时,这时就需要使用接口来定义这一共性(规则),只需让这些部分子类实现接口即可
- 接口是更加彻底的抽象,在JDK7之前,接口中全是抽象方法。接口同样是不能创建对象的
格式
1 | //接口的定义格式,默认在interface前面会有abstract |
接口成员的特点
- 在JDK7,包括JDK7之前,接口中的只有包含:抽象方法和常量
- 接口中没有构造方法,因为接口不能创建对象同时也不需要给子类的成员变量赋值。
- JDK8的新特性,接口中可以定义有方法体的方法
- JDK9的新特性,接口中可以定义私有方法
抽象方法
- 注意:接口中的抽象方法默认会自动加上public abstract修饰程序员无需自己手写!!
- 按照规范:以后接口中的抽象方法建议不要写上public abstract。因为没有必要啊,默认会加上。
常量
- 在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。
1 | public interface InterF { |
接口和类的关系
类和类的关系
- 继承关系,只能单继承,不能多继承,但是可以多层继承
类和接口的关系
- 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
接口和接口的关系
- 继承关系,可以单继承,也可以多继承
- 细节:如果类实现了最下面的子接口,那么就需要重写所有的抽象方法
注意:star:
关于接口的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
- 当两个接口中存在相同抽象方法的时候,该怎么办?
只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。
- 实现类能不能继承A类的时候,同时实现其他接口呢?
继承的父类,就好比是亲爸爸一样
实现的接口,就好比是干爹一样
可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。
- 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?
实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。
- 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?
处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。
- 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?
可以在接口跟实现类中间,新建一个中间类(适配器类)
让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
新特性
JDK8以后接口新增的方法-默认方法
-
概念
- 允许在接口中定义默认方法,需要使用关键字
default
修饰 - 作用:解决接口升级后,实现类未重写新升级的方法而报错的问题
- 允许在接口中定义默认方法,需要使用关键字
-
格式
1
2
3public default 返回值类型 方法名(参数列表){
} -
注意事项
- 默认方法不是抽象方法,所以不强制被重写,但是如果被重写,重写的时候去掉
default
关键字 - public可以省略,default不能省略
- 如果实现了多个接口,多个接口存在相同名字的默认方法,子类就必须对该方法进行重写
- 默认方法不是抽象方法,所以不强制被重写,但是如果被重写,重写的时候去掉
JDK8以后接口新增的方法-静态方法
-
允许在接口中定义静态方法,需要用static修饰
-
格式
1
2
3public static 返回值类型 方法名(参数列表){
} -
注意事项
- 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
- public可以省略,static不能省略
JDK9以后接口新增的方法-私有方法
-
格式
1
2
3
4
5
6
7
8
9//格式1 --供默认方法使用
private 返回值类型 方法名(参数列表){
}
//格式2 --供静态方法使用
private static 返回值类型 方法名(参数列表){
}
接口的应用
- 接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就行
- 当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态
适配器设计模式
作用
- 解决接口和接口实现类之间的矛盾问题:接口实现类不想重写接口中的所有抽象方法
步骤
- 编写中间类XXXAdapter,实现对应的接口
- 对接口中的抽象方法进行空实现
- 让真正的接口实现类继承中间类,并重写需要用的方法
- 为了避免其他类创建适配器类的对象,中间的适配器类用
abstract
修饰 - 如果真正的实现类有其他的父类,由于java不能多继承故可以让中间类继承真正实现类的父类,达到间接继承
内部类
概述
- 内部类表示的事务是外部类的一部分,内部类单独出现没有任何意义,比如汽车内有一个发动机
访问特点
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象,用内部类对象访问内部类的成员
分类
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
成员内部类
- 写在成员位置的,属于外部类的成员
获取方式
- 方式一
- 当成员内部类被private修饰时,在外部类编写方法,返回内部类对象,达到对外提供内部类对象(类似set/get方法)
- 方式二
- 当成员内部类被非私有修饰时,直接创建对象:
Outer.Inner oi = new Outer().new Inner();
- 当成员内部类被非私有修饰时,直接创建对象:
注意
- 外部类成员变量和内部类成员变量重名时,在内部类如何访问?
Sout(Outer.this.变量名)
- 内部类访问外部类的方法时,要用
Outer.this.方法名()
静态内部类
获取方式
Outer.Inner oi = new Outer.Inner()
调用静态内部类中的方法
- 非静态方法:先创建对象,用对象调用
- 静态方法:外部类名.内部类名.方法名();
注意
- 静态内部类可以直接访问外部类的静态成员。
- 静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。
- 静态内部类中没有
Outer.this
,内外部类变量重名要访问外部类变量使用:外部类.变量名
局部内部类
1 | class 外部类名 { |
匿名内部类:star:
概念
- 隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置
格式
1 | new 类名/接口名() { |
格式的细节
- 包含了继承或实现,方法重写,创建对象。
- 整体就是一个类的子类或者接口的实现类对象
使用场景
- 当方法的参数是接口或者类时,以接口为例,可以传递这个接口的实现类对象,如果实现类只要使用一次,就可以用匿名内部类简化代码
泛型
概述
-
泛型的介绍
泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制
-
泛型的好处
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
-
用于约束集合中存储元素的数据类型
细节
-
泛型中不能写基本数据类型
-
指定泛型的具体类型后,传递数据时,可以传入该类型或者其子类类型
-
如果不写泛型,类型默认是Object,这样多态的弊端就显现出来:不能调用子类特有的功能
-
不能直接在静态方法中使用类的泛型类型参数,但静态方法本身可以声明自己的泛型类型参数
1
2
3
4
5
6
7
8
9
10
11
12
13public class MyClass<T> {
// 静态方法不能直接使用类的泛型参数 T
// public static void print(T value) {
// System.out.println(value);
// }
// 静态方法可以定义自己的泛型参数
public static <U> void print(U value) {
System.out.println(value);
}
}
//因为静态方法属于类本身而不是类的实例。但静态方法可以声明自己的泛型参数,从而实现与泛型相关的功能。
泛型类
-
当定义一个类时,某个变量的类型不确定,就可以使用带有泛型的类
1
2
3修饰符 class 类名<E>{
}
泛型方法
-
当方法中形参类型不确定的时候,可以使用泛型
-
只能在本方法中使用
1
2
3修饰符<E> 返回类型 方法名(参数...){
}
泛型接口
-
当接口中类型不确定的时候,可以使用泛型
1
2
3修饰符 interface 接口名<E>{
}
泛型接口的使用
-
实现类给出具体的接口
1
2
3public class MyList implements List<此处给出具体的类型> {
} -
实现类延续泛型,创建对象时在确定
1
2
3
4
5public class MyList<E> implements List<E> {
}
MyList<此处给出具体的类型> list = new MyList<>{};
泛型的不可继承性和通配符
不可继承性
-
泛型不具备继承性(一旦E的类型确定,便只能传递此类型),但是数据具备继承性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class test {
public static void main(String[] args) {
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
//泛型不具备继承性
method(list1);
method(list2); // 报错,只能传递ArrayList<Ye>
method(list3); // 报错,只能传递ArrayList<Ye>
//数据具备继承性
list1.add(new Fu()); // 可以将子类对象传递至父类集合中
}
public static void method(ArrayList<Ye> list){
}
}
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{} -
那么此时我就是想要用method方法添加ArrayList
和 ArrayList 集合应该怎么办? 1
2
3
4
5
6
7
8//对method方法进行修改
public static<E> void method(ArrayList<E> list){
}
//但是这样有一个弊端,method方法不仅ArrayList<Ye/Fu/Zi>可以接收,而且ArrayList<Student>也可以接收
//那我就是只想接收ArrayList<Ye/Fu/Zi>,应该怎么办呢?
//泛型的通配符 可以解决
泛型的通配符
-
概述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30?也表示不确定的类型,它可以进行类型的限定
? extends E: 表示可以传递E或者E所有的子类类型
? super E: 表示可以传递E或者E所有的父类类型
public class test {
public static void main(String[] args) {
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
ArrayList<Student> list4 = new ArrayList<>();
//泛型不具备继承性
method(list1);
method(list2);
method(list3);
method(list4); // 报错,因为使用通配符后,只能传递Ye和它所有的子类类型
}
//注意:在方法中使用泛型的通配符时,修饰符后面的<E>去掉不用写
public static void method(ArrayList<? extends Ye> list){
}
}
class Student{}
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{} -
应用场景
1
2
31.如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口
2.如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以使用泛型的通配符
关键点:泛型的通配符可以限定类型的范围