泛型
泛型其实就是一种类型参数,用于指定类型。
为了统计学生成绩,要求设计一个Score对象,包括课程名称、课程号、课程成绩,但是成绩分为两种,一种是以优秀、良好、合格 来作为结果,还有一种就是使用数字分数。现在的问题就是,成绩可能是String类型,也可能是Integer类型,如何才能很好的去存可能出现的两种类型呢?
public class Score<T> { //将Score转变为泛型类<T>
String name;
String id;
T score; //T为泛型,根据用户提供的类型自动变成对应类型
public Score(String name, String id, T score) {
//提供的score类型即为T代表的类型
this.name = name;
this.id = id;
this.score = score; }}
public static void main(String[] args) {
//直接确定Score的类型是字符串类型的成绩
Score<String> score = new Score<String>("数据结构与算法基础", "EP074512", "优秀");
Integer i = score.score;
//编译不通过,因为成员变量score类型被定为String!}
泛型本质上也是一个语法糖(并不是JVM所支持的语法,编译后会转成编译器支持的语法,比如之前的foreach就是),在编译后会被擦除,变回上面的Object类型调用,但是类型转换由编译器帮我们完成,而不是我们自己进行转换(安全)
//反编译后的代码
public static void main(String[] args) {
Score score = new Score("数据结构与算法基础", "EP074512", "优秀");
String i = (String)score.score;
//其实依然会变为强制类型转换,但是这是由编译器帮我们完成的 }
像这样在编译后泛型的内容消失转变为Object的情况称为类型擦除
(重要,需要完全理解),所以泛型只是为了方便我们在编译阶段确定类型的一种语法而已,并不是JVM所支持的。
泛型的使用
泛型类
泛型类就是普通的类多一个类型参数来指定具体的泛型类型。
public class Score<T> {
//将Score转变为泛型类<T>
String name;
String id;
T score;
//T为泛型,根据用户提供的类型自动变成对应类型
public Score(String name, String id, T score) {
//提供的score类型即为T代表的类型
this.name = name;
this.id = id;
this.score = score; }}
在一个普通类型中定义泛型,泛型T称为参数化类型
,在定义泛型类的引用时,需要明确指出类型:
Score<String> score = new Score<String>("数据结构与算法基础", "EP074512", "优秀");
注意,泛型只能用于对象属性,也就是非静态的成员变量才能使用:
泛型无法使用基本类型,如果需要基本类型,只能使用基本类型的包装类进行替换;
类的泛型方法
public T getScore() {
//若方法的返回值类型为泛型,那么编译器会自动进行推断
return score;}
public void setScore(T score) {
//若方法的形式参数为泛型,那么实参只能是定义时的类型
this.score = score;}
Score<String> score = new Score<String>("数据结构与算法基础", "EP074512", "优秀");score.setScore(10);
//编译不通过,因为只接受String类型
同样地,静态方法无法直接使用类定义的泛型(注意是无法直接使用,静态方法可以使用泛型)
自定义泛型方法
那么如果我想在静态方法中使用泛型呢?首先我们要明确之前为什么无法使用泛型,因为之前我们的泛型定义是在类上的,只有明确具体的类型才能开始使用,也就是创建对象时完成类型确定,但是静态方法不需要依附于对象,那么只能在使用时再来确定了,所以静态方法可以使用泛型,但是需要单独定义:
public static <E> void test(E e){
//在方法定义前声明泛型
System.out.println(e);}
同理,成员方法也能自行定义泛型,在实际使用时再进行类型确定:
public <E> void test(E e){ System.out.println(e);}
其实,无论是泛型类还是泛型方法,再使用时一定要能够进行类型推断,明确类型才行。
注意一定要区分类定义的泛型和方法前定义的泛型!
泛型引用
Score<Integer> score; //声明泛型为Integer类型
在定义一个泛型类的引用时,需要在后面指出此类型:
如果不希望指定类型,或是希望此引用类型可以引用任意泛型的Score
类对象,可以使用?
通配符,来表示自动匹配任意的可用类型:
Score<?> score; //score可以引用任意的Score类型对象了!
因为使用了通配符,编译器就无法进行类型推断,所以只能使用原始类型。
Object o = score.getScore(); //只能变为Object
泛型的界限
现在有一个新的需求,现在没有String类型的成绩了,但是成绩依然可能是整数,也可能是小数,这时我们不希望用户将泛型指定为除数字类型外的其他类型,我们就需要使用到泛型的上界定义:
public class Score<T extends Number> {
//设定泛型上界,必须是Number的子类
private final String name;
private final String id;
private T score;
public Score(String name, String id, T score) {
this.name = name;
this.id = id;
this.score = score; }
public T getScore() {
return score; }}
通过extends
关键字进行上界限定,只有指定类型或指定类型的子类才能作为类型参数。并且一旦我们指定了上界后,编译器就将范围从原始类型Object
提升到我们指定的上界Number
,但是依然无法明确具体类型。
钻石运算符
每次创建泛型对象都需要在前后都标明类型,但是实际上后面的类型声明是可以去掉的,因为我们在传入参数时或定义泛型类的引用时,就已经明确了类型,因此JDK1.7提供了钻石运算符来简化代码:
Score<Integer> score = new Score<Integer>("数据结构与算法基础", "EP074512", 10);
//1.7之前
Score<Integer> score = new Score<>("数据结构与算法基础", "EP074512", 10);
//1.7之后
泛型与多态
泛型不仅仅可以可以定义在类上,同时也能定义在接口上:
public interface ScoreInterface<T> {
T getScore();
void setScore(T t);}
public class Score<T> implements ScoreInterface<T>{
//将Score转变为泛型类<T>
private final String name;
private final String id;
private T score;
public Score(String name, String id, T score) {
this.name = name;
this.id = id;
this.score = score; }
public T getScore() {
return score; }
@Override
public void setScore(T score) {
this.score = score; }}
public class StringScore implements ScoreInterface<String>{
//在实现时明确类型
@Override
public String getScore() {
return null; }
@Override
public void setScore(String s) {
}}
数据结构
顺序表
将数据依次存储在连续的整块物理空间中,这种存储结构称为顺序存储结构
,而以这种方式实现的线性表,我们称为顺序表
。表中的每一个个体都被称为元素
,元素左边的元素(上一个元素),称为前驱
,同理,右边的元素(后一个元素)称为后驱
。
链表
每一个结点存放一个元素和一个指向下一个结点的表。
顺序表:
- 访问速度快,随机访问性能高
- 插入和删除的效率低下,极端情况下需要变更整个表
- 不易扩充,需要复制并重新创建数组
链表:
- 插入和删除效率高,只需要改变连接点的指向即可
- 动态扩充容量,无需担心容量问题
- 访问元素需要依次寻找,随机访问元素效率低下
栈
栈遵循先入后出原则,只能在线性表的一端添加和删除元素。。
向栈中插入一个元素时,称为入栈(压栈)
,移除栈顶元素称为出栈。
队列
只能从队尾进从队头出。
。