数据结构前置知识
在学习Java数据结构前,我们需要一些前置知识:1.集合,2.泛型, 3.包装类,
这篇文章:主要是认识Java中的集合类、包装类、泛型,以及了解复杂度。
文章目录
01集合框架
1.什么是集合框架
Java集合框架Java Collection Framework
,又被称为容器container
,是定义在java.util
包下的一组接口interfaces
和其实现类classes
。
其主要表现为将多个元素element置于一个单元中,用于对这些元素进行快使便捷的存储store
、检索retrieve
、管理manipulate
,即平时我们俗称的增删查改CRUD
。
例如,一副扑克牌(一组牌的集合)、一个邮箱(一组邮件的集合)、一个通讯录一组姓名和电话的映射关系)等等。
其本质就是说:数据结构的种类很多。
那什么是数据结构?
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在任一种或多种特定关系的数据元素的集合。
可以简单理解为:数据+结构-->描述或组织一些数据
下图是类和接口的总览
02包装类
1.介绍
包装类(wrapper):在Java中,由于基本类型不是继承自Object
,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。
基本数据类型和对应的包装类如下表:
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
其中除了int
和char
分别对应Integer
和Character
,其他基本数据类型都是首字母大写。
2.装箱和拆箱
所谓的装箱,可以这样理解:
基本类型->包装类型
反之,即为拆箱。
下面给出示例代码:
public class Test1 {
public static void main(String[] args) {
int a = 10;
Integer b = a;
System.out.println(b);
}
}
我们要了解一个前提:
1.jdk5前是手动装箱和拆箱的。
2.jdk5(含jdk5)是自动装箱和拆箱的。
3.自动装箱和自动拆箱
自动装箱
自动装箱的底层是调用了valueOf
方法,何以见得?我们可以以上面的代码,进入其反汇编语言中分析。
我们可以在IDEA中清晰的看出,在valueOf(int)
中传进来一个参数i ,返回时new
了一个Integer(i)
。
即:
public class Test1 {
public static void main(String[] args) {
int a = 10;
Integer b = a;//自动装箱
System.out.println(b);
Integer c = Integer.valueOf(a);//显示装箱
Integer d = new Integer(a);//显示装箱
System.out.println(c);
System.out.println(d);
}
}
自动拆箱
下面给出示例代码:
public static void main(String[] args) {
Integer a = 10;
int b = a;
System.out.println(b);
}
public static void main(String[] args) {
Integer val = new Integer(10);
int val1 = val;//自动拆箱
System.out.println(val1);
//显示拆箱 拆箱为自己指定的元素
int val2 = val.intValue();
System.out.println(val2);
double val4 = val.doubleValue();
System.out.println(val4);
}
一道经典面试题
请输出下面程序的结果。
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
System.out.println(a == b);
Integer a2 = 128;
Integer b2 = 128;
System.out.println(a2 == b2);
}
是不是很多人第一眼就直接是true true
,其实不然,这道题的结果是true false
。
为什么呢?
这其实就涉及到了Integer
的取值范围,Integer
的取值范围[-128,127]
。
03初识泛型
1.介绍
在《Java编程思想》中,对泛型的介绍是这样的:一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。
泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。
通俗来说:泛型的作用就是在指定的容器中,要持有什么类型的对象,将类型作为参数去传递,需要什么就传什么,让编译器去做检查。
2.引出
这里抛出一个问题:要实现一个类,类中包含一个数组 ,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值,该怎么去实现?
思路
- 在数组中我们只能存放指定类型的元素。
- 但是我们有顶级父类
Object
,那么是否可以将创建一个Object数组呢?
说干就干,我们可以尝试一下,示例代码如下:
class MyArray{
public Object[] array = new Object[10];
public void set(int pos, Object val) {
array[pos] = val;
}
public Object get(int pos){
return array[pos];
}
}
public class Test1 {
public static void main(String[] args) {
MyArray myArray = new MyArray();
myArray.set(0,"123");
myArray.set(1,90);
}
}
这里显然是可以的。
但是这里还是有问题的,如下图
这里的String str = myArray.get(0);
会编译错误,因为get方法
的返回值是Object
,所以这里是需要强制类型转换的,
String str = (String) myArray.get(0);
,这样才 ok,但是这样会很麻烦,那么在这里我们可以引入泛型。
3.语法
泛型的语法如下:
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1,T2,...,Tn>{
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参*/{
// 这里可以使用类型参数
}
class ClassName<T1,T2,...,Tn>extends Parents<T1> {
// 可以只使用部分类型参数
}
interface 接口<T> {
}
泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
那么我们可以将上述的代码进行如下改写:
//<T>:代表当前类是一个泛型类
class MyArray<T>{
public Object[] array = new Object[10];
public void set(int pos, T val) {
array[pos] = val;
}
public T get(int pos){
return (T)array[pos];
}
}
public class Test1 {
public static void main(String[] args) {
MyArray<String> myArray = new MyArray<>();
myArray.set(0, "java");
//myArray.set(1, 90); 这里不能放整型了,90会报错
String str = myArray.get(0);
System.out.println(str);
MyArray<Integer> myArray2 = new MyArray<>();
myArray2.set(0,1);
myArray2.set(1,10);
Integer a = myArray2.get(0);
System.out.println(a);
}
}
4.细节及注意事项
了解
类名后的<T>
代表占位符,表示当前类是一个泛型类
【规范】类型形参一般使用一个大写字母表示,常用的名称有:
E表示Element
K表示Key
V表示Value
N表示Number
T表示Type
S,U,V等等-第二、第三、第四个类型
注意点
- 不允许实例化一个泛型数组。
若是非要的话public T[] arr = (T[]new Object[10]);
但是这样写也不好。没什么必要。
- 泛型只能接受类,所有的基本数据类型必须使用包装类!
- <>当中,只能是引用类型,不能是基本类型
5.了解Raw Type
裸类型是一个泛型类但没有带着类型实参,例如MyArrayList
就是一个裸类型
MyArraylist = new MyArray();
这样子的话,又如同一开始引出泛型时的例子一样了,又得去强制类型转换。
**注意:**我们不要自己去使用裸类型,裸类型是为了兼容老版本的AP保留的机制
下面的类型擦除部分,也会讲到编译器是如何使用裸类型的。
小结:
-
泛型是将数据类型参数化,进行传递
-
使用
<T>
表示当前类是一个泛型类。 -
泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换
6.泛型编译
6.1擦除机制
从反汇编来看,通过命令:javap -c
查看字节码文件,所有的T
都是Object
。
在编译的过程中,将所有的T
替换为Object
这种机制我们称为:擦除机制
运行的时候没有泛型这样的概念–>泛型的擦除机制只是存在于编译期间
Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。
那么这里我们提出问题?
- 那为什么,
T[] arr = new T[10];
是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new Object[5]
吗?
这里其实我们要明确:数组在Java里面是一个非常特殊的存在,在JVM里面其实相当于一种数据类型了。
class MyArray<T>{
public T[] array = (T[])new Object[10];
public void set(int pos, T val) {
array[pos] = val;
}
public T get(int pos){
return (T)array[pos];
}
public T[] getArray() {
return array;
}
}
public class Test1 {
public static void main(String[] args) {
//泛型是如何编译的
MyArray<String> myArray = new MyArray<>();
myArray.set(0, "java");
String [] ret = myArray.getArray();
System.out.println(Arrays.toString(ret));
}
]
类型转换异常
因为整个数组是Object
类型数组,意味着这个数组里面什么都能放,那么我们能证明数组里全部类型都是String
类型吗?显然不行。
对代码进行改写
class MyArray<T>{
// public T[] array = (T[])new Object[10];
public Object[]array = new Object[10];
public void set(int pos, T val) {
array[pos] = val;
}
public T get(int pos){
return (T)array[pos];
}
public T[] getArray() {
return (T[])array;
}
}
public class Test1 {
public static void main(String[] args) {
//泛型是如何编译的
MyArray<String> myArray = new MyArray<>();
myArray.set(0, "java");
Object [] ret = myArray.getArray();
System.out.println(Arrays.toString(ret));
}
}
7.泛型上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
7.1语法
class 泛型类名称<类型形参 extends 类型边界> {
...
}
应用
public class MyArray<E extends Number>{
}
只接受Number的子类型作为E的类型实参
MyArray<Integer>a;//正常,因为Integer是Number的子类型
MyArray<String>b;//编译错误,因为String不是Number的子类型
error:type argument String is not within bounds of type-variable E
MyArrayList<String>b;
Λ
where E is a type-variable:
E extends Number declared in class MyArrayList
了解:没有指定类型边界E,可以视为E extends Object
例题
写一个泛型类 实现一个方法 这个方法球任意类型数组的最大值
为什么会报错呢?
因为两个Object之间是不能比较大小的。
改进:
//T extends Comparable<T> T一定是实现Comparable接口的
class Alg<T extends Comparable<T>>{
public T findMax(T[]array){
T max = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i].compareTo(max) > 0){
max = array[i];
}
}
return max;
}
}
8.泛型方法
如下示例代码:
class Alg2{
//泛型方法
public<T extends Comparable<T>> T findMax(T[]array){
T max = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i].compareTo(max) > 0){
max = array[i];
}
}
return max;
}
}
public class Test1 {
public static void main(String[] args) {
Alg2 alg2 = new Alg2();
Integer[] array = {1,13,53,232,9};
Integer ret = alg2.findMax(array);
System.out.println(ret);
}
}
至此,Java版本数据结构的前置知识就大致介绍完了,接下来将会持续更新数据结构相关博文。