0
点赞
收藏
分享

微信扫一扫

JAVASE---泛型与集合(1)

颜路在路上 2022-03-11 阅读 84

泛型

泛型其实就是一种类型参数,用于指定类型。

为了统计学生成绩,要求设计一个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) {    
                                            }}

数据结构

顺序表

将数据依次存储在连续的整块物理空间中,这种存储结构称为顺序存储结构,而以这种方式实现的线性表,我们称为顺序表。表中的每一个个体都被称为元素,元素左边的元素(上一个元素),称为前驱,同理,右边的元素(后一个元素)称为后驱线性表链表

每一个结点存放一个元素和一个指向下一个结点的表。

顺序表:

  1. 访问速度快,随机访问性能高
  2. 插入和删除的效率低下,极端情况下需要变更整个表
  3. 不易扩充,需要复制并重新创建数组

链表:

  1. 插入和删除效率高,只需要改变连接点的指向即可
  2. 动态扩充容量,无需担心容量问题
  3. 访问元素需要依次寻找,随机访问元素效率低下

栈遵循先入后出原则,只能在线性表的一端添加和删除元素。计算机一级考栈吗,计算机一二级选择题考点 栈 Day4

 向栈中插入一个元素时,称为入栈(压栈),移除栈顶元素称为出栈。

队列

只能从队尾进从队头出。

举报

相关推荐

0 条评论