当前位置: 首页 > news >正文

从零开始学java--泛型

泛型

目录

泛型

引入

泛型类

泛型与多态

泛型方法

泛型的界限

类型擦除

函数式接口

Supplier供给型函数式接口:

Consumer消费型函数式接口:

Function函数型函数式接口:

Predicate断言式函数式接口:

判空包装


引入

学生成绩可能是数字类型,也可能是字符串类型,如何存放可能出现的两种类型呢:

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.value=value;
    }
}

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

    public static void main(String[] args) {
        Score score=new Score("数学","aa","优秀"); //是String类型的
        
        Integer number=(Integer) score.getValue();
        //获取成绩需要进行强制类型转换,虽然并不是一开始的类型,但是编译不会报错

    }

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

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


泛型类

定义泛型类:


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<>("数学","aa","优秀");
        //使用时跟上<>并在其中填写明确要使用的类型
    }

泛型将数据类型控制在了编译阶段, 在编写代码时就能明确泛型的类型,类型不符合将无法编译通过。

1、因为是具体使用对象时才会明确具体类型,所以说静态方法中不能用。

2、方法中使用待确定类型的变量时,因为不明确类型则会默认这个变量是一个Object类型的变量(即不能使用String等类型中的方法)。可对其进行强制类型转换但没必要。

3、不能通过这个不确定的类型变量直接创建对象和对应的数组。

4、具体类型不同的泛型类变量,不能使用不同的变量进行接收。

5、如果要让某个变量支持引用确定了任意类型的泛型,可以使用?通配符。 


    public static void main(String[] args) {
        Score<String> score=new Score<>("数学","aa","优秀");
        Score<?>score1=score;
    }

 如果使用通配符,由于类型不确定,所以说具体类型同样会变成Object。

6、泛型变量可以定义多个,多个类型变量用,隔开。在使用时需要将这三种类型都进行明确指令。

7、泛型只能确定为一个引用类型,不支持基本类型。

要存放基本数据类型的值,我们只能使用对应的包装类。

如果是基本类型的数组,因为数组本身是引用类型,所以是可以的。


泛型与多态

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


    public static void main(String[] args) {
        Score<String> score=new Score<>("数学","aa","优秀");
        Score<?>score1=score;
    }

当子类实现此接口时,我们可以选择在实现类明确泛型类型:


    public static void main(String[] args) {
        A a=new A();
        Integer i=a.study();
    }
 
    static class A implements Study<Integer>{
//在实现接口或是继承父类时,如果子类是一个普通类,那么可以直接明确对应类型
        @Override
        public Integer study() {
            return 0;
        }
    }

也可以继续使用泛型:


    public static void main(String[] args) {
        A<Integer> a=new A<>();
        Integer i=a.study();
    }
 
    static class A<T> implements Study<T> {
//让子类继续为一个泛型类,那么可以不明确
        @Override
        public T study() {
            return null;
        }
    }

 继承:


    static class A<T>{
    }
 
    static class B extends A<String>{
        
    }

 


泛型方法

泛型变量不仅仅在泛型类中使用,也可以定义泛型方法。

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


    public static void main(String[] args) {
        String str=test("10");
    }
 
    public static <T>T test(T t){ //在返回值类型前添加<>并填写泛型变量表示这是一个泛型方法
        return t;
    }

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

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


    public static void main(String[] args) {
        Integer[] arr = {1, 3, 2, 7, 4, 9, 0};
        //不能比较基本数据类型int
        Arrays.sort(arr, new Comparator<Integer>() {
            //通过创建泛型接口的匿名内部类,来自定义排序规则,因为匿名内部类就是接口的实现类,所以这里就明确了类型
            @Override
            public int compare(Integer o1, Integer o2) {  //这个方法会在执行排序时被调用(别人调用我们的实现)
                //想要让数据从大到小排列:
                return o2-o1;
                //compare方法要求返回一个int来表示两个数的大小关系,大于0表示大于,小于0表示小于
                //如果o2比o1大,那么应该排在前面,所以说返回正数表示大于
            }
        });
        System.out.println(Arrays.toString(arr));
    }

可替换为Lambda表达式:


    public static void main(String[] args) {
        Integer[] arr = {1, 3, 2, 7, 4, 9, 0};
        Arrays.sort(arr, (o1, o2) -> o2-o1);
        System.out.println(Arrays.toString(arr));
    }


泛型的界限

若现在没有String类型的成绩了,但是成绩依然可能是整数或小数,我们不希望将泛型指定为除数字类型外的其他类型,就需要使用到泛型的上界定义。

只需要在泛型变量的后面添加extends关键字即可指定上界:


public class Score<T extends Number> { //设定类型参数上界,必须是Number或Number的子类
    String name;
    String id;
    T value;
 
    public Score(String name,String id,T value){
        this.name=name;
        this.id=id;
        this.value=value;
    }
    
    public T getValue() {
        return value;
    }
}

泛型通配符也支持泛型的界限:


    public static void main(String[] args) {
        Score<? extends Integer>score=new Score<>("xm","11",10);
    }

下界只适用于通配符,对于类型变量来说是不支持的。


    public static void main(String[] args) {
        Score<? super Object>score=new Score<>("xm","11",10);
    }

entends定义的只能存放它自己及其子类,super定义的只能存放它自己及其父类。

限定上界后使用这个对象的泛型成员:


    public static void main(String[] args) {
        Score<? extends Number>score=new Score<>("xm","11",10);
        Number o=score.getValue();  //此时虽然使用的是通配符,但是不再是Object类型,而是对应的上界
    }

 限定下界的话,因为还有可能是Object,所以说依然和之前一样:


    public static void main(String[] args) {
        Score<? super Number>score=new Score<>("xm","11",10);
        Object o=score.getValue();
    }

 


类型擦除

实际上在Java中并不是真的有泛型类型,因为所有的对象都是一个普通的类型,一个泛型类型编译之后,实际上会直接使用默认的类型。

在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。

如果我们给类型变量设定了上界,那么会从默认类型变成上界定义的类型。

泛型其实仅仅是在编译阶段进行类型检查,当程序在运行时,并不会真的去检查对应类型,所以哪怕我们不指定类型也可以使用。

擦除机制其实就是为了方便使用后面集合类(否则每次都要强制类型转换)同时为了向下兼容采取的方案,因此泛型的使用会有一些限制:

首先,在进行类型判断时,不允许使用泛型,只能使用原始类型:

    public static void main(String[] args) {
        Test<String> test =new Test<>();
        System.out.println(test instanceof Test);
    }

其次,泛型不支持创建参数化类型数组的:

只不过只是把它当做泛型类型的数组还是可以用的:


函数式接口

@FunctionalInterface 函数式接口都会打上这样的注解

满足Lambda表达式的需求有且仅有一个需要去实现(未实现)的方法。

函数式接口就是JDK1.8专门提供好的用于Lambda表达式的接口,这些接口都可以直接使用Lambda表达式。以下主要介绍四个主要的函数式接口:

Supplier供给型函数式接口:

这个接口是专门用于供给使用的,其中只有一个get方法用于获取需要的对象。

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get(); //实现此方法,实现供给功能
}
    public static void main(String[] args) {
        Supplier<Student> studentSupplier= Student::new;
        studentSupplier.get().hello();
    }

    public static class Student{
        public void hello(){
            System.out.println("我是学生");
        }
    }

Consumer消费型函数式接口:

这个接口专门用于消费某个对象。

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t); //这个方法用于消费,没有返回值


    default Consumer<T> andThen(Consumer<? super T> after) {  //默认实现,这个方法便于我们连续使用此消费接口
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
public class Main {
//专门消费Student对象的Consumer
    private static final Consumer<Student> STUDENT_COMPARATOR=student->System.out.println(student+"看不懂");
    public static void main(String[] args) {
        Student student=new Student();
        STUDENT_COMPARATOR.accept(student);
    }

    public static class Student{
        public void hello(){
            System.out.println("我是学生");
        }
    }
}
    public static void main(String[] args) {
        Student student=new Student();
        STUDENT_COMPARATOR   //可以使用andThen方法继续调用,将消费之后的操作以同样的方式预定好
                .andThen(student1 -> System.out.println("后续操作"))
                .accept(student);
    }

//输出
com.test.Main$Student@404b9385看不懂
后续操作

Function函数型函数式接口:

这个接口消费一个对象,然后会向外供给一个对象(前两个的融合体)

@FunctionalInterface
public interface Function<T, R> {


    R apply(T t);  //这里一共有两个类型参数,其中一个是接受的参数类型,另一个是返回的结果类型


    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }


    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }


    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

apply方法: 

//这里实现了一个简单的功能,将传入的int参数转换为字符串的形式
    private static final Function<Integer,String>INTEGER_STRING_FUNCTION=Objects::toString;
//Integer输入,String输出?
    public static void main(String[] args) {
        String str=INTEGER_STRING_FUNCTION.apply(10);
        System.out.println(str);
    }

使用compose将指定函数式的结果作为当前函数式的实参:(compose是前置工作)

    public static void main(String[] args) {
        String str=INTEGER_STRING_FUNCTION
                .compose((String s)->s.length())  //将此函数式的返回值作为当前实现的实参
                .apply("aaa");  //传入上面函数式需要的参数
        System.out.println(str);
        //String ->Integer ->String
        // aaa   ->  3     -> "3"
    }

//输出3

andThen可以将当前实现的返回值进行进一步的处理,得到其他类型的值:(后续工作)

    public static void main(String[] args) {
        Boolean str=INTEGER_STRING_FUNCTION
                .andThen(String::isEmpty)  //在执行完后,返回值作为参数执行andThen内的函数式,最后得到的结果就是最终的结果了
                .apply(10);
        System.out.println(str);
    }
//输出false

还提供了一个将传入参数原样返回的实现:

    public static void main(String[] args) {
        Function<String,String>function=Function.identity();
        System.out.println(function.apply("aaaa"));
    }
//输出aaaa

Predicate断言式函数式接口:

接收一个参数,然后进行自定义并返回一个boolean结果。

@FunctionalInterface
public interface Predicate<T> {


    boolean test(T t);  //要实现的方法


    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }


    default Predicate<T> negate() {
        return (t) -> !test(t);
    }


    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }


    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
import java.util.function.Predicate;


public class Main {
    private static final Predicate<Student> STUDENT_PREDICATE=student -> student.score>=60;
    public static void main(String[] args) {
        Student student=new Student();
        student.score=80;
        if (STUDENT_PREDICATE.test(student)){  //test方法的返回值是一个boolean结果
            System.out.println("及格了");
        }
        else {
            System.out.println("不及格");
        }
    }

    public static class Student{
        int score=100;
        public void hello(){
            System.out.println("我是学生");
        }
    }
}


判空包装

判空包装类Optional,这个类可以很有效的处理空指针问题。

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

    public static void test(String str){  //传入字符串,如果不是空串就打印长度
        if(str == null)return; //没有这句若传入null会出现空指针异常错误
        if(!str.isEmpty()){
            System.out.println("长度为"+str.length());
        }
    }

用Optional类处理上述问题:

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

其他的一些使用:

//不为空输出字符串,为空...
    public static void test(String str){
        String s1=Optional
                .ofNullable(str)
                .orElse("为null的备选情况");
        System.out.println(s1);
    }
//将包装的类型直接转换为另一种类型
    public static void main(String[] args) {
        test("aaaaa");
    }

    public static void test(String str){
        Integer i=Optional
                .ofNullable(str)
                .map(s -> s.length())
                .get();
        System.out.println(i);
    }
//输出5

......

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dtcms.com/a/125648.html

相关文章:

  • 倚光科技:以创新之光,雕琢全球领先光学设计公司
  • 【Java集合】LinkedHashSet源码深度分析
  • 理解企业内部信息集成
  • AcWing 166.数独
  • C++基础精讲-04
  • 对称加密与非对称加密与消息摘要算法保证https的数据交互的完整性和保密性
  • <C#>在 C# .NET 6 中,使用IWebHostEnvironment获取Web应用程序的运行信息。
  • 谷歌闭源Android后的生态变局与数据库国产替代的必要性——以金仓数据库为例
  • 出口商品贸易方式企业性质总值数据库
  • ReentrantLock 实现公平锁和非公平锁的原理!
  • swift菜鸟教程6-10(运算符,条件,循环,字符串,字符)
  • 2025年第十八届“认证杯”数学中国数学建模网络挑战赛【BC题】完整版+代码+结果
  • 深入剖析观察者模式:原理、实践与 Spring 源码解读
  • 深度学习总结(8)
  • CSS高级技巧
  • 使用治疗前MR图像预测脑膜瘤Ki-67的多模态深度学习模型
  • 【Qt】QxOrm:下载、安装、使用
  • 界面控件DevExpress WinForms v25.1新功能预览 - 聚焦用户体验升级
  • 如何应对“最后时刻任务堆积”(鼓包现象)
  • 《Vue Router实战教程》5.嵌套路由
  • 二叉树的应用
  • Dubbo、HTTP、RMI之间的区别
  • 递归?递推?
  • Asp.NET Core WebApi 创建带鉴权机制的Api
  • 蓝桥杯 — — 接龙数列
  • swift菜鸟教程14(闭包)
  • Java 解压 rar 文件
  • CSS属性书写顺序
  • wireshark过滤器表达式的规则
  • linux 定时器管理系统设计与实现