05,JavaSE笔记(五)泛型

1,泛型程序设计

在前面我们学习了最重要的类和对象,了解了面向对象编程的思想,注意,非常重要,面向对象是必须要深入理解和掌握的内容,不能草草结束。在本章节,我们还会继续深入了解,从泛型开始,再到反射,最后再开始我们的集合类学习,循序渐进。

1.1 泛型

为了统计学生成绩,要求设计一个Score对象,包括课程名称、课程号、课程成绩,但是成绩分为两种,一种是以优秀、良好、合格 来作为结果,还有一种就是 60.0、75.5、92.5 这样的数字分数,可能高等数学这门课是以数字成绩进行结算,而计算机网络实验这门课是以等级进行结算,这两种分数类型都有可能出现,那么现在该如何去设计这样的一个Score类呢?

现在的问题就是,成绩可能是String类型,也可能是Integer类型,如何才能很好的去存可能出现的两种类型呢?

public class Score {
    String name;
    String id;
    Object value;  //因为Object是所有类型的父类,因此既可以存放Integer也能存放String

  	public Score(String name, String id, Object value) {
        this.name = name;
        this.id = id;
        this.score = value;
    }
}

以上的方法虽然很好地解决了多种类型存储问题,但是Object类型在编译阶段并不具有良好的类型判断能力,很容易出现以下的情况:

public static void main(String[] args) {

    Score score = new Score("数据结构与算法基础", "EP074512", "优秀");  //是String类型的
    Integer number = (Integer) score.score;  //获取成绩需要进行强制类型转换,虽然并不是一开始的类型,但是编译不会报错
}

使用Object类型作为引用,对于使用者来说,由于是Object类型,所以说并不能直接判断存储的类型到底是String还是Integer,取值只能进行强制类型转换,显然无法在编译期确定类型是否安全,项目中代码量非常之大,进行类型比较又会导致额外的开销和增加代码量,如果不经比较就很容易出现类型转换异常,代码的健壮性有所欠缺

所以说这种解决办法虽然可行,但并不是最好的方案。

为了解决以上问题,JDK 5新增了泛型,它能够在编译阶段就检查类型安全,大大提升开发效率。

1.2 泛型类

泛型其实就一个待定类型,我们可以使用一个特殊的名字表示泛型,泛型在定义时并不明确是什么类型,而是需要到使用时才会确定对应的泛型类型。
我们可以将一个类定义为一个泛型类:

public class Score<T> {   //泛型类需要使用<>,我们需要在里面添加1 - N个类型变量
    String name;
    String id;
    T value;   //T会根据使用时提供的类型自动变成对应类型

    public Score(String name, String id, T value) {   //这里T可以是任何类型,但是一旦确定,那么就不能修改了
        this.name = name;
        this.id = id;
        this.value = value;
    }
}

我们来看看这是如何使用的:

public static void main(String[] args) {
    Score<String> score = new Score<String>("计算机网络", "EP074512", "优秀");
  	//因为现在有了类型变量,在使用时同样需要跟上<>并在其中填写明确要使用的类型
  	//这样我们就可以根据不同的类型进行选择了
    String value = score.value;   //一旦类型明确,那么泛型就变成对应的类型了
    System.out.println(value);
}

泛型将数据类型的确定控制在了编译阶段,在编写代码的时候就能明确泛型的类型,如果类型不符合,将无法通过编译!因为是具体使用对象时才会明确具体类型,所以说静态方法中是不能用的:
在这里插入图片描述
只不过这里需要注意一下,我们在方法中使用待确定类型的变量时,因为此时并不明确具体是什么类型,那么默认会认为这个变量是一个Object类型的变量,因为无论具体类型是什么,一定是Object类的子类:

在这里插入图片描述
我们可以对其进行强制类型转换,但是实际上没多大必要:

public void test(T t){
    String str = (String) t;   //都明确要用String了,那这里定义泛型不是多此一举吗
}

因为泛型本身就是对某些待定类型的简单处理,如果都明确要使用什么类型了,那大可不必使用泛型。还有,不能通过这个不确定的类型变量就去直接创建对象和对应的数组:
在这里插入图片描述
注意,具体类型不同的泛型类变量,不能使用不同的变量进行接收:
在这里插入图片描述
如果要让某个变量支持引用确定了任意类型的泛型,那么可以使用?通配符:

public static void main(String[] args) {
    Test<?> test = new Test<Integer>();
    test = new Test<String>();
  	Object o = test.value;    //但是注意,如果使用通配符,那么由于类型不确定,所以说具体类型同样会变成Object
}

当然,泛型变量不止可以只有一个,如果需要使用多个的话,我们也可以定义多个:

public class Test<A, B, C> {   //多个类型变量使用逗号隔开
    public A a;
    public B b;
    public C c;
}

那么在使用时,就需要将这三种类型都进行明确指定:

public static void main(String[] args) {
    Test<String, Integer, Character> test = new Test<>();  //使用钻石运算符可以省略其中的类型
    test.a = "lbwnb";
    test.b = 10;
    test.c = '淦';
}

1.3 泛型与多态

不只是类,包括接口、抽象类,都是可以支持泛型的:

public interface Study<T> {
    T test();
}

当子类实现此接口时,我们可以选择在实现类明确泛型类型,或是继续使用此泛型让具体创建的对象来确定类型:

public class Main {
    public static void main(String[] args) {
        A a = new A();
        Integer i = a.test();
    }

    static class A implements Study<Integer> {   
      	//在实现接口或是继承父类时,如果子类是一个普通类,那么可以直接明确对应类型
        @Override
        public Integer test() {
            return null;
        }
    }
}

或者是继续摆烂,依然使用泛型:

public class Main {
    public static void main(String[] args) {
        A<String> a = new A<>();
        String i = a.test();
    }

    static class A<T> implements Study<T> {   
      	//让子类继续为一个泛型类,那么可以不用明确
        @Override
        public T test() {
            return null;
        }
    }
}

继承也是同样的:

static class A<T> {
    
}

static class B extends A<String> {

}

1.4 泛型方法

当然,类型变量并不是只能在泛型类中才可以使用,我们也可以定义泛型方法。

当某个方法(无论是是静态方法还是成员方法)需要接受的参数类型并不确定时,我们也可以使用泛型来表示:

public class Main {
    public static void main(String[] args) {
        String str = test("Hello World!");
    }

    private static <T> T test(T t){   //在返回值类型前添加<>并填写泛型变量表示这个是一个泛型方法
        return t;
    }
}

泛型方法会在使用时自动确定泛型类型,比如上我们定义的是类型T作为参数,同样的类型T作为返回值,实际传入的参数是一个字符串类型的值,那么T就会自动变成String类型,因此返回值也是String类型。

public static void main(String[] args) {
    String[] strings = new String[1];
    Main main = new Main();
    main.add(strings, "Hello");
    System.out.println(Arrays.toString(strings));
}

private <T> void add(T[] arr, T t){
    arr[0] = t;
}

实际上泛型方法在很多工具类中也有,比如说Arrays的排序方法:

Integer[] arr = {1, 4, 5, 2, 6, 3, 0, 7, 9, 8};
Arrays.sort(arr, new Comparator<Integer>() {   
  	//通过创建泛型接口的匿名内部类,来自定义排序规则,因为匿名内部类就是接口的实现类,所以说这里就明确了类型
    @Override
    public int compare(Integer o1, Integer o2) {   //这个方法会在执行排序时被调用(别人来调用我们的实现)
        return 0;
    }
});

1.5 泛型的界限

现在有一个新的需求,现在没有String类型的成绩了,但是成绩依然可能是整数,也可能是小数,这时我们不希望用户将泛型指定为除数字类型外的其他类型,我们就需要使用到泛型的上界定义:

public class Score<T extends Number> {   //设定类型参数上界,必须是Number或是Number的子类
    private final String name;
    private final String id;
    private final T value;

    public Score(String name, String id, T value) {
        this.name = name;
        this.id = id;
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

只需要在泛型变量的后面添加extends关键字即可指定上界,使用时,具体类型只能是我们指定的上界类型或是上界类型的子类,不得是其他类型。否则一律报错:
在这里插入图片描述

1.6 判空包装

Java8还新增了一个非常重要的判空包装类Optional,这个类可以很有效的处理空指针问题。

比如对于下面这样一个很简单的方法:

private static void test(String str){   //传入字符串,如果不是空串,那么就打印长度
    if(!str.isEmpty()) {
        System.out.println("字符串长度为:"+str.length());
    }
}

但是如果我们在传入参数时,丢个null进去,直接原地爆炸:

public static void main(String[] args) {
    test(null);
}

private static void test(String str){ 
    if(!str.isEmpty()) {   //此时传入的值为null,调用方法马上得到空指针异常
        System.out.println("字符串长度为:"+str.length());
    }
}

因此我们还需要在使用之前进行判空操作:

private static void test(String str){
    if(str == null) return;   //这样就可以防止null导致的异常了
    if(!str.isEmpty()) {
        System.out.println("字符串长度为:"+str.length());
    }
}

虽然这种方式很好,但是在Java8之后,有了Optional类,它可以更加优雅地处理这种问题,我们来看看如何使用:

private static void test(String str){
    Optional
            .ofNullable(str)   //将传入的对象包装进Optional中
            .ifPresent(s -> System.out.println("字符串长度为:"+s.length()));  
  					//如果不为空,则执行这里的Consumer实现
}

是不是感觉很方便?我们还可以将包装的类型直接转换为另一种类型:

private static void test(String str){
    Integer i = Optional
            .ofNullable(str)
            .map(String::length)   //使用map来进行映射,将当前类型转换为其他类型,或者是进行处理
            .orElse(-1);
    System.out.println(i);
}

当然,Optional的方法比较多,这里就不一一介绍了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值