文章目录
- 一、什么是数据结构?
- 二、什么是算法?
- 三、什么是泛型?
-
- [3.1、泛型是如何编译的?------ 擦除机制](#3.1、泛型是如何编译的?—— 擦除机制)
- 3.2、泛型的上界
- 3.3、泛型的通配符
- 3.4、装箱、拆箱
一、什么是数据结构?
数据+结构:描述/组织数据的方式。
数据结构有很多种,比如说 顺序表、栈、堆、二叉树...Java中会把数据结构封装成一个特定的类(程序员可以直接使用,无需自己实现一个数据结构),这样的类叫做集合类,即一个集合类背后对应一种数据结构。
许多的集合类称作集合框架(collection framework)。集合框架里的接口和类的总览:
(1)、Collection:是一个接口,包含了大部分容器常用的一些方法。
(2)、List是一个接口,规范了 ArrayList 和 LinkedList 中要实现的方法,ArrayList 实现了List接口,底层为动态类型顺序表;LinkedList 实现了List接口,底层为双向链表。
(3)、Stack:底层是栈,栈是一种特殊的顺序表。
(4)、Queue:底层是队列,队列是一种特殊的顺序表;Deque 是一个接口
(5)、Set:集合,是一个接口,里面放置的是K模型;HashSet 底层为哈希桶,查询的时间复杂度为O(1);TreeSet 底层为红黑树,查询的时间复杂度为O(log2 N),关于key有序的
(6)、Map:映射,里面存储的是K-V模型的键值对。HashMap 底层为哈希桶,查询时间复杂度为O(1);TreeMap 底层为红黑树,查询的时间复杂度为O(log2N),关于key有序。
学习数据结构需要做到 先熟知背后的数据结构,再学习使用数据结构对应的集合类,即先熟知原理,再实践。
二、什么是算法?
算法定义了良好的计算过程,一般取1个或1组值作为输入,并产生出1个或1组值作为输出。简单来说算法是一系列的计算步骤 ,用来将输入数据转化成输出结果。
2.1、如何衡量一个算法的好坏?
一般通过 时间复杂度(时间率)、空间复杂度(空间率)来衡量。时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间。
2.1.1、时间复杂度
算法的时间复杂度是一个数学函数,算法中基本操作的执行次数,为算法的时间复杂度。
有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为n的数组中搜索一个数据x
最好情况: 1次找到
最坏情况: n次找到
平均情况: n/2次找到
实际中我们一般考虑的都是算法的最坏情况,例如数组搜索数据的最坏情况下为O(n),最好情况为O(1);冒泡排序最坏情况下为O(n^2),最好情况为O(n);二分查找为O(log以2为底的n);
2.1.2、空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。
三、什么是泛型?
一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。JDK1.5后定义出了泛型,泛型表示可以适用于许多许多类型。从代码上讲,就是对类型实现了参数化。
使用Object确实可以传入任意类型的数据,也可以获取到任意类型的数据,但是获取数据时,编译器不会帮我们进行检查,以致于获取数据时明明知道里面是什么数据类型,但是偏偏还需要进行强转,这不太方便。
并且更多情况下,我们还是希望他只能够持有一种数据类型,而不是同时持有这么多类型,太混乱了。所以,泛型就出现了,泛型的主要目的:指定当前的容器,要持有什么类型的对象,让编译器去做检查。 此时,就需要把类型,作为参数传递,需要什么类型,就传入什么类型。
泛型的尖括号 <> 里只能放包装类 ,不能放基本类型!!
3.1、泛型是如何编译的?------ 擦除机制
擦除机制:在编译过程中,将所有T擦除为Object,叫做擦除机制。
泛型到底是怎么编译的? Java的泛型机制是在编译级别实现的,因此运行阶段不存在泛型的概念。
安装可以看代码的字节码插件的步骤:
我们可以看到,其实编译器在编译的时候,将泛型T擦除为了Object,实际上虽然说是T,其实还是Object。
3.2、泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
3.3、泛型的通配符
? 用于在泛型的使用,即为通配符。
通配符是用来解决泛型无法协变的问题的,协变指的就是如果 Student 是 Person 的子类,那么 List 也应该是 List 的子类。但是泛型是不支持这样的父子类关系的。
泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围.
a、通配符上界:? extends 类
java
package com.example.demo.Mytest;
public class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Message<T extends Fruit> { // 设置泛型上界
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
class TestDemo {
public static void main(String[] args) {
Message<Apple> message = new Message<>();
message.setMessage(new Apple());
fun(message);
Message<Banana> message2 = new Message<>();
message2.setMessage(new Banana());
fun(message2);
}
// 此处只要是 Fruit 或者 Fruit 的子类都可以
public static void fun(Message<? extends Fruit> temp) {
// 放的都是 Fruit 或 Fruit 的子类,所以可以直接使用Fruit接收,但是万万不能使用子类的类接收,直接报错
Fruit fruit = temp.getMessage();
// 这里无法确定temp引用的是哪个子类对象,所以即使使用setXX()方法修改,也会报错!
// temp.setMessage(new Apple());
// temp.setMessage(new Banana());
// 所以通常通配符的上界都是用来读取的,因为修改时无法确定到底是修改哪个子类对象
// 也不能用具体的对象接收,因为不知道接收的是哪个对象,只能用Fruit接收
System.out.println(temp.getMessage());
}
}
b、通配符下界:<? super 类 >。即:<? super Integer> :代表可以传入的类型是Integer或者Integer的父类。
java
package com.example.demo.Mytest;
public class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Message<T> {
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
class TestDemo {
public static void main(String[] args) {
Message<Fruit> message = new Message<>();
message.setMessage(new Fruit());
fun(message);
Message<Food> message2 = new Message<>();
message2.setMessage(new Food());
fun(message2);
}
//temp 接收Fruit及其父类的一个Message
public static void fun(Message<? super Fruit> temp) {
// 此时可以修改!!添加的是Fruit 或者Fruit的子类
temp.setMessage(new Apple());//这个是Fruit的子类
temp.setMessage(new Fruit());//这个是Fruit的本身
//Fruit fruit = temp.getMessage(); 不能接收,这里无法确定是哪个父类
System.out.println(temp.getMessage());//只能直接输出
// 所以通配符的下界一般用于修改,不用于接收,用于接收时无法判断是哪个对象,因此会报错
}
}
3.4、装箱、拆箱
装箱:将基本类型变成包装类类型。
拆箱:将包装类类型变成基本类型。