IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> java数据结构与算法第二课——泛型 -> 正文阅读

[Java知识库]java数据结构与算法第二课——泛型

?

目录

一:泛型的定义

二:引出泛型

三:泛型

3.1泛型的语法

3.2擦除机制(了解)

3.3泛型的上界

3.3.1泛型的上界的定义?

3.3.2泛型的上界的语法

3.4泛型的静态方法

四:通配符

4.1引出

4.2通配符及其上下界

五:包装类

?5.1装箱

5.2拆箱

5.3一道有趣的题目


一:泛型的定义

????????一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。

????????泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多的类型。从代码上讲,就是对类型实现了参数化。

二:引出泛型

之前我们已经学过数组,在一个数组中,只能存放指定类型的元素。例如:

int[ ] array1 = new int[10];? ? ? ? ? ? ? ?//存放整型数据

String[ ] array2 = new Stirng[10];? ? //存放字符串数据

? ? ? ? 基于泛型的定义,我们希望一个数组可以适用许多许多类型。因为Object类是所有类的父类,那么是否可以把数组创建为Object呢?具体示例如下:

class MyArray{
    public Object[] array = new Object[10];

    /**
     * 获取pos下标的值
     *
     * @param pos
     * @return
     */
    public Object getPos(int pos) {
        return array[pos];
    }

    /**
     * 给pos下标放一个元
     */
    public void setPos(int pos, Object val) {
        array[pos] = val;
    }
}
public class TestDemo {

    public static void main(String[] args) {
        MyArray array = new MyArray();
        array.setPos(0,1);
        array.setPos(1,"hello");

    }
}

? ? ? ? 就目前为止,这段代码似乎没有什么问题,因为数组的元素类型是Object,所以我可以放任意类型的数据,譬如在0下标的位置放入整型1,在1下标的位置放入字符串“hello”。但当我们想通过getPos()函数获取某个下标的元素时,你会发现:

? ? ? ? ?编译器报错了!这是因为数组中元素类型不确定,所以当你获取元素时,编译器无法帮你检测你当前想要获取的元素的类型,所以就会报错。而在实际中,我们很少需要把一个整型数据和一个字符串类型的数据放在同一个数组中,这没什么意义。一般情况下,我们仍希望可以自己指定类型,而非同时持有多种类型。

????????泛型的作用就在此时体现出来:指定当前的容器,要持有什么类型的对象,然后让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。

三:泛型

3.1泛型的语法

class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}

class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}

? ? ? ? 单纯看这些语法可能会让你觉得头大,所以我会对上面的例子进行简单的改写,以帮助我们理解泛型的语法:

package Test;

class MyArray<T>{
    public T[] array = (T[])new Object[10];

    /**
     * 获取pos下标的值
     *
     * @param pos
     * @return
     */
    public T getPos(int pos) {
        return array[pos];
    }

    /**
     * 给pos下标放一个元素
     *
     */
    public void setPos(int pos, T val) {
        array[pos] = val;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyArray<Integer> array = new MyArray<Integer>();
        array.setPos(0,1);
        array.setPos(1,2);
        Integer str = array.getPos(1);
        System.out.println(str);

    }
}

运行结果如下:

? ? ? ? ?也就是说,要将一个类写成“泛性类”,我们需要进行以下操作:

注意:

1.数组中每个元素的类型不再是Object,而变成了T。所以在创建数组,写getPos()和setPos()方法时,都要使用T而不是Object;

2.在创建数组时,要写成public T[] array = (T[])new Object[10],即在右侧new一个Object类型数组并强转为T[]。 即不能直接new泛型类型的数组;

3.类名后的<T> 代表占位符,表示当前类是一个泛型类,【规范】类型形参一般使用一个大写字母表示,常用的名称有:

  • E 表示 Element
  • K 表示 Key
  • V 表示 Value
  • N 表示 Number
  • T 表示 Type
  • S, U, V 等等 - 第二、第三、第四个类型

4.泛型只能接受类,所有的基本数据类型必须使用包装类!即用Integer而不是int;

5.当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写;

MyArray<Integer> list1 = new MyArray<>(); // 可以推导出实例化需要的类型实参为 Integer

MyArray<String> list2 = new MyArray<>();? ?// 可以推导出实例化需要的类型实参为 String

6.泛型是将数据类型参数化,进行传递;

7.使用<T> 表示当前类是一个泛型类;

8.泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换。

3.2擦除机制(了解)

????????擦除机制就是在编译时,所有的T都被擦除为了Object。通俗讲,就是在编译的过程当中,将所有的T替换为Object的这种机制。

3.3泛型的上界

3.3.1泛型的上界的定义?

????????在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。也就是说,泛型的上界是一种对于传入的类型变量的约束

3.3.2泛型的上界的语法

class 泛型类名称<类型形参 extends 类型边界> {
...
}

具体实例如下:

class Test<E extends Number> {
        
    }

public class TestDemo2 {
    public static void main(String[] args) {
        Test<Number> test = new Test<>();
        Test<Integer> test1 = new Test<Integer>();
        Test<Float> test2 = new Test<Float>();
        Test<Double> test3 = new Test<Double>();
    }
}

? ? ? ? 这段代码说明:传入的类型变量必须是Number类及其子类。

? ? ? ? 根据java文档显示,我们可以传入的类型包括Byte,Double,Float......

复杂示例:??

public class MyArray<E extends Comparable<E>> {
...
}

E必须是实现了Comparable接口的。?具体示例如下:

package Test;

class Student implements Comparable<Student>{
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        return this.age-o.age;
    }
}

class Alg<T extends Comparable<T>> {
    public T findMax(T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if(max.compareTo(array[i]) < 0) {
                max = array[i];
            }
        }
        return max;
    }
}



public class TestDemo1 {
    public static void main(String[] args) {
    Alg<Integer> alg = new Alg<Integer>();
    Integer[] array = {1,4,5,21,8,19};
    Integer max = alg.findMax(array);
    System.out.println("该整型数组中最大的元素为:"+max);

    Alg<Student> alg2 = new Alg<Student>();
    Student[] students = new Student[3];
    students[0] = new Student("wukong",18);
    students[1] = new Student("bajie",99);
    students[2] = new Student("shashidi",5);
    Student student = alg2.findMax(students);
    System.out.println("该学生数组中年龄最大的学生为:"+student);
}
}

运行结果如下:

分析:

? ? ? ? ?我们发现,每次调用findMax()方法时,必须先实例化对象,然后才能调用findMax()方法。那么,有没有可能不实例化对象,直接调用findMax()方法呢?答案是可以的。这就要用到静态方法。

3.4泛型的静态方法

? ? ? ? 明确其语法后,我们将“泛型的上界”加入,即对传入的类型变量做一定的约束:

? ? ? ? 这时候,我们就可以直接使用类名调用findMax()方法,1而不必先实例化对象了。具体示例如下:

class Alg2 {
    public static<T extends Comparable<T> > T findMax(T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if(max.compareTo(array[i]) < 0) {
                max = array[i];
            }
        }
        return max;
    }
}



public class TestDemo1 {

    
    public static void main(String[] args) {
        Integer[] array = {1,4,5,21,8,19};
        Integer max = Alg2.findMax(array);
        System.out.println(max);
    }
}

?运行结果如下:

四:通配符

4.1引出

? ? ? ? 通配符,顾名思义,即适配各种类型。? ? ? ??

????????通配符有什么作用呢?简言之,通配符是用来解决泛型无法协变的问题的,协变指的就是如果Student 是Person 的子类,那么List<Student> 也应该是List<Person> 的子类。但是泛型是不支持这样的父子类关系的。具体实例如下:

? ? ? ? ?如图所示,Student类是Person类的子类,我们认为父类引用引用子类对象应该是没有任何问题的。然而,当我们将想法付诸实践时,发现这样做不合法,这是因为型不支持这样的父子类关系,这就叫做无法协变。而要解决这个问题,就需要用到通配符。具体实例如下:

package Test;


class Message<T> {
    private T message ;

    public T getMessage() {
        return message;
    }

    public void setMessage(T message) {
        this.message = message;
    }

}

public class TestDemo4 {

    public static void function() {
        Message<String> message = new Message<String>() ;
        message.setMessage("仰天大笑出门去,我辈岂是蓬蒿人!");
        fun(message);
    }

    public static void main(String[] args) {
        Message<Integer> message = new Message<Integer>() ;
        message.setMessage(1);
        fun(message);
    }

    public static void fun(Message<?> temp){
        //temp.setMessage(1);是不能往里面加元素的.
        //Message<?> temp  可以接受多种类型
        System.out.println(temp.getMessage());
    }
}

4.2通配符及其上下界

1、通配符,代表未知类型,代表不关心或无法确定实际操作的类型,一般与容器类配合使用。

public void testV(List<?> list) {}

2、<? extends T>,定义上限,期间只有阅读能力。这种方法表明参数化的类型可能是指定的类型或子类型。 可以接受T及其子类。

//t1要么是Test2,要么是Test2的子类

public void testC(Test1<? extends Test2> t1) {

    Test2 value = t1.getValue();

    System.out.println("testC中的:" + value.getT());

}

3、<? super T>,下限定义,有阅读能力和部分写作能力,子类可以写入父类。这种方法表明参数化的类型可以是指定的类型,也可以是父类。可以接收T及其父类,可以写入T及其子类。

//t1要么是Test5,要么是Test5的父类

public void testB(Test1<? super Test5> t1) {

    //子类代替父类

    Test2 value = (Test2) t1.getValue();

    System.out.println(value.getT());

}

? ? ? ? 以上就是通配符及其上界、下界的全部内容了。

五:包装类

在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。

? ? ? ? 如图为各基本类型及其对应的包装类。

? ? ? ? 今天我们要介绍的内容,主要是装箱和拆箱。

?5.1装箱

public class TestDemo6 {
 
    public static void main(String[] args) {
        int a = 10;
        Integer b = a;//自动装箱
        Integer c = Integer.valueOf(a);//手动装箱
        System.out.println(b);
    }
}

????????如何自动装箱?直接将int类型变量赋值给Integer类型的变量。而手动装箱则时调用balueOf()方法。

5.2拆箱

public class TestDemo6 {

    public static void main(String[] args) {
        Integer a = 10;
        int b = a;//自动拆箱
        double d = a;//intValue() 默认是以intValue()拆

        double d2 = a.doubleValue();//手动拆箱

        System.out.println(b);
    }

}

? ? ? ? 在拆箱时,a为Integer类型,那么默认会以intValue()进行拆箱;如何想手动拆箱,则要显式调用相应的方法。

5.3一道有趣的题目

? ? ? ? 对于拆箱和装箱部分,我们已经了解得差不多了。接下来我会介绍一道非常有趣的题目,其中就包含了“装箱”相关的知识。

????????分析代码,为什么会出现这种现象呢?

? ? ? ? 在执行这几行代码时,我们知道发生了装箱。而在底层,装箱默认调用的是valueOf()方法。所以要解决这个问题,我们需要查看valueOf()的原码,看看它究竟是怎样实现装箱的。

?????????为了验证我们的结论,我们采用特殊值法进行验证:

? ? ? ? 结论成立!解决方案也很简单,就是使用equals()方法进行比较。(引用类型的比较)

public class TestDemo6 {
    public static void main(String[] args) {
        Integer a = 128;
        Integer b = 128;
        System.out.println(a.equals(b));
    }
}

? ? ? ? ?运行结果如下:

? ? ? ? ?这告诉我们,在对引用类型进行比较时,一定要使用equals()方法或compareTo()方法。


本课内容结束!

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-01 15:33:33  更:2022-05-01 15:37:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:42:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码