数据结构前置知识(下)

server/2024/10/11 8:38:36/

1. 包装类

        Java为了让基本数据类型也能够继承Object,因此给每个基本数据类型提供了包装类,

这样就可以和平常的引用数据类型一样使用了,并且也可以应用在泛型上(后续讲)

基本数据类型包装类
byteByte
shortShort
intInterger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

除了 Integer 和 Character , 其余基本类型的包装类都是首字母大写.

        1.1 装箱和拆箱

        装包,装箱: 把基本数据类型给到引用数据类型

// 装包: 把基本数据类型给到引用数据类型public static void main(String[] args) {Integer a = 10;int i = 99;Integer b = i;//这俩个都是自动装包,隐式装包System.out.println(a);System.out.println(b);Integer aa = Integer.valueOf(10);//这个是显示装包,装箱      }
//
10
99

 Interger a = 10;  a是Interger引用数据类型

 int i = 99;  i是基本数据类型

Integer b = i;  我们把基本数据类型变量放在引用数据类型变量里面

以上完成了隐式装包操作

Integer aa = Integer.valueOf(10); 这个是显示装包

总结: 

引用数据类型 变量名 = 基本数据类型变量/传数值; 隐式装包

引用数据类型 变量名 = 引用数据类型.valueOf(基本数据类型变量/传数值); 显示装包

       拆箱,拆包: 把引用数据类型给到基本数据类型

    public static void main(String[] args) {//拆箱Integer a = 10;//隐式拆箱int i = a ;//把引用数据类型->基本数据类型(一般俩种都用到valueOf方法)System.out.println(i);//显示拆箱int aa = a.intValue();double d = a.doubleValue();//显示拆箱System.out.println(aa);System.out.println(d);}
//
10
10
10.0

Integer a = 10; a是引用数据类型

int i = a ; 这个是引用数据类型变量赋值给基本数据类型,完成了拆包过程

上面是隐式拆包

int aa = a.intValue(); 这个是显示拆包

总:

基本数据类型 变量名 = 引用数据类型变量名; 隐式拆包

基本数据类型 变量名 = 引用数据类型变量名.基本数据类型Value() 显示拆包

 再来看看这个代码

    public static void main(String[] args) {Integer a = 100;Integer b = 100;System.out.println(a == b);//[-128,127]Integer a1 = 200;Integer b1 = 200;//源码里面,如果超过127则会new一个新的对象System.out.println(a1 == b1);}
//
true
false

从这个代码我们可以看出,引用数据类型一旦超过了它所表示的范围就会new一个新的对象,比如上面这个例子Integer的范围是[-128,127],因此200超过了它的范围,会new出一个新的对象

2. 泛型

        所谓泛型就是适用于很多类型,从代码来说就是对类型实现了参数化

        2.1 引出泛型

        我们来看这么一个例子:实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?

class Myarray1 {//TODO 这个是尝试的法一public Object[] array = new Object[10];//存放数据public void setValue(int pos,Object val) {array[pos] = val;}//获取数据public Object getValue(int pos) {return array[pos];}
}

既然这个数组可以存放任何类型,那么我们就可以New一个Object类型的数组,我们又需要存放和得到数组里面的 数据,那么我们就要为数组提供set和get方法.这样写虽然感觉没问题,但是当我们在main里面创建这个对象并且调用set和get方法的时候就出现了问题

因为getValue返回的类型是个Object的类型,我们不能直接把父类类型放在子类类型里面,因此我们需要向下转型:

String str = (String) myarray.getValue(1);

 但是,如果每次放进去什么数据,拿出来的时候我们还需要对它进行对应类型的向下转型就太过于麻烦了.所以我们就引出了泛型,把数据类型当成参数传过去,就完成了数据类型不匹配的问题.我们来看以下代码:

class Myarray<T> {//<T>表示当前的类是个泛型类,它只是一个占位符//TODO 这个是法二,用的泛型//泛型表示我可以放进去的类型是我自己指定的类型public Object[] array = new Object[10];
//    public T[] a = new T[10];//泛型是编译时期存在的,当程序运行起来到JVM之后,就没有泛型的概念了,而对于数组而言,创建出来就需要是一个确定的类型//而且,我们无法确保Object类型有没有无参的构造方法//泛型在编译的时候是通过擦除机制,擦成了Object//,2:54:14public void setValue(int pos,T val) {array[pos] = val;}//获取数据public T getValue(int pos) {return (T)array[pos];//把返回的类型,强转为指定类型}}

我们来分析以下这个代码

class Myarray<T> 这里面的<T>表示当前的类是个泛型类,它只是一个占位符

然后里面所有牵扯到值的类型的时候,我们都用这个占位符表示

return (T)array[pos];这个代码就是把Object类型的数组类型的强转为指定类型的值,指定类型就是在main函数里面 <Integer>这里面的类型.然后我们可以看出,在后续我们getValue的时候就不需要强转了,就相当于在创建这个对象的时候我们已经从<Integer>这里晓得了这个就是个Integer类型的对象.我们需要传入什么数据类型的值,我们就创建对象的时候<>在这里面告诉编译器,我们要的是什么类型的数据,后续如果放入其他类型就会报错了.

所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型
 

        2.2 泛型的语法

        class 泛型名称 <类型参数列表> {   }

        class 泛型名称<T1,T2,T3...>

        class 泛型名称<类型参数列表>extends 继承类 {  }

        class 泛型名称<T1,T2,T3...>extends parentClass<T>{}

其中,<T>代表占位符,表示当前的类是一个泛型类

 【规范】类型形参一般使用一个大写字母表示,常用的名称有:(了解即可)

E 表示 Element

K 表示 Key

V 表示 Value

N 表示 Number T 表示 Type

S, U, V 等等 - 第二、第三、第四个类型

 泛型类的使用:

泛型类名<引用数据类型> 对象名 = new 泛型类名<引用数据类型>();

         2.3 泛型的上界

我们通过一个例子来说明一下

class Person {}
class Stundent extends Person  {}
//TODO T 一定是Number 或者Number的子类
class  TestGneric <T extends Number> {}
//一定是Person或者Person的子类
class TestGneric2 <T extends Person> {}
public class Test2 {public static void main(String[] args) {//TODO 泛型的上界TestGneric<Number> testGneric = new TestGneric<>();TestGneric<Integer> testGneric1 = new TestGneric<>();
//        TestGneric<String> testGneric2 = new TestGneric<String>();TestGneric2<Stundent> testGneric2 = new TestGneric2<>();
//一定是Person或者Person的子类TestGneric2<Person> testGneric21 = new TestGneric2<>();}
}

class TestGneric <T extends Number>{}这个类表示的泛型类, T一定是Number或者Number的子类

这个就说明了我们可以通过extends关键字,限制我们使用的泛型的类型,我们在main里面创建它的对象,我们在<>里面的的类型只能是Number或者Number的子类,我们不能放其他引用类型.

然后我们自己实现一个父子类关系来实验,Student继承自Person类,我们限制TestGneric2的泛型类型只能是Person或者是Person的子类 class TestGneric2 <T extends Person>;

不然就会报错.

           2.4 泛型方法

既然我们学会了写泛型类,那么我们也要学会写泛型方法

        我们先看语法:

方法限定符 <类型参数列表> 返回值类型 方法名称(形参列表) {...}

看个例子:

写一个泛型类,求一个数组中的最大值

关于这个代码为什么报错,可以去看看我java分类,类和对象抽象类那一块,我简单解释一下,我们引用数据类型是必须是实现comparable接口才能够指定对什么进行比较,不能够直接进行比较.

因此我们需要进行修改

class Alg2<T extends Comparable<T>> {//把指定的类型放在类上public T findMaxValue(T[] array) {T max = array[0];for (int i = 0; i < array.length; i++) {if (max.compareTo(array[i]) < 0) {max = array[i];}}return max;}
}

在这个代码中,我们把Alg2限制为一个实现了Compareable接口的类或者其子类上

我们来观察一下Integer的源码:

 它是实现了Comparable接口的

Comparable本身是个泛型接口

因此上面代码可以找出最大值:8

但是我们任然用的是泛型类,下面我们来用法二

class Alg3 {public<T extends Comparable<T>> T findMaxValue(T[] array) {//把指定的类型放在方法上T max = array[0];for (int i = 0; i < array.length; i++) {if (max.compareTo(array[i]) < 0) {max = array[i];}}return max;}
}

这个玩意就是我们在普通类里面写一个泛型方法我们限定T是要实现Comparable 接口的类型

访问修饰限定符<T extends 接口<T>> T 方法名 (T 参数) {

}  

 

我们在main里面创建 这个对象,并且创建你想比较的实现了Comparable接口的引用类型数组.然后我们把数组当作参数传入方法里面进行计算.我们根据传进去实参的传值来推导此时的类型,缩写:System.out.println(alg3.findMaxValue(integers));//这个直接根据你传进去的参数来预判出是什么类型,完整写法System.out.println(alg3.<Integer>findMaxValue(integers));

我们写一个不需要实例化对象的静态方法

class Alg4 {public static <T extends Comparable<T>> T findMaxValue(T[] array) {//把指定的类型放在方法上T max = array[0];for (int i = 0; i < array.length; i++) {if (max.compareTo(array[i]) < 0) {max = array[i];}}return max;}
}public class Test {public static void main(String[] args) {Integer[] integers = {1,2,3,4,6,8};System.out.println(Alg4.findMaxValue(integers));System.out.println(Alg4.<Integer>findMaxValue(integers));}
}

静态的方法,我们直接用对象名来进行调用即可,其他用法和上述一样.

                2.5 擦除机制

我们学了这么多泛型的东西,那么我们来聊一下它底层是怎么实现的,

通过命令:javap -c 查看字节码文件,所有的T都是Object

在编译过程中,我们把所有的T替换成Object,这种机制就是擦除机制

然后,我们提出一个问题,我们在创建泛型的时候我们创建的类型为什么是Object类型的,为什么不能直接用泛型创建数组

因为泛型是通过类型擦除实现的,这意味着在运行时候泛型的具体类型会被擦除,编译器无法确定T的具体数据类型,而创建数组,我们是需要知道具体创建类型是什么的.具体硬是要实例化泛型数组就要用到反射机制了(自行了解)


http://www.ppmy.cn/server/130047.html

相关文章

计算机视觉之OpenCV vs YOLO

好多开发者希望搞明白OpenCV 和YOLO区别&#xff0c;实际上&#xff0c;二者在计算机视觉领域都有广泛应用&#xff0c;但它们有很大的不同。 一、OpenCV 概述 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。它…

简易STL实现 | Unordered_set 的实现

std::unordered_set 是一个无序关联容器&#xff0c;其内部基于 哈希表实现 1、基本使用 在 std::set 中&#xff0c;erase 函数的返回值是一个 size_t 类型&#xff0c;表示从集合中删除的元素数量。因为 std::set 中的元素是唯一的&#xff0c;所以返回值要么是 0&#xff…

解决银河麒麟桌面操作系统V10(ARM)中`apt-get update`“正在等待报头”问题

解决银河麒麟桌面操作系统V10&#xff08;ARM&#xff09;中apt-get update“正在等待报头”问题 1、问题描述2、 解决方法步骤一&#xff1a;打开终端步骤二&#xff1a;清理APT缓存步骤三&#xff1a;再次尝试更新软件源 &#x1f496;The Begin&#x1f496;点点关注&#x…

window.location.href和open的区别

window.location.href 是用于在当前浏览器窗口或标签页中导航到一个新的 URL。它直接改变当前页面的地址&#xff0c;不会打开新的窗口或标签页。 // 导航到 Google 主页 window.location.href https://www.google.com; window.open 用于打开一个新的浏览器窗口或标签页&a…

hadoop入门

1.1 hadoop是什么 Hadoop是一个由Apache基金会所开发的分布式系统基础架构&#xff0c;主要是解决海量数据的存储和海量数据的分析计算的问题。通常Hadoop指的是一个更为广泛的概念Hadoop生态圈 1.2 hadoop发展历程 Hadoop创始人Doug Cutting&#xff0c;为了实现与Google类…

(八)Proteus仿真STM32单片机GPIO驱动数码管

1&#xff0c;参考上篇&#xff0c;将LED点阵屏更换成数码管如下图 2&#xff0c;修改驱动函数&#xff0c;数组seg[14]前10个是0-9数字的编码&#xff0c;后四个是空格&#xff0c;点&#xff0c;横线&#xff0c;下划线 char seg_decode(char num)//数字解码 {const char se…

flutter_鸿蒙next(win)环境搭建

第一步 拉取鸿蒙版本flutterSDK仓库 仓库地址&#xff1a;OpenHarmony-SIG/flutter_flutter 第二步 找到拉取的仓库中的README.md 并根据说明配置环境 第三步 配置好环境变量之后 用管理员开启cmd 输入&#xff1a;flutter dcotor 并查看此时flutter所支持的系统 包括&…

【读书笔记】向上生长 - 关于积累、精进、进阶

1.章节概括 当事物积累到一定数量&#xff0c;才可能发生质量的变化&#xff0c;质量不好、高度不够、结果不优&#xff0c;大部分原因是没有踏踏实实做&#xff0c;积累的量不够。 作者还举例一些例子 学习某一种语言或某个工具&#xff0c;想要短时间速成&#xff0c;但练习…