七大查找算法:
查找是在大量的信息中寻找一个特定的信息元素,在计算机应用中,查找是常用的基本运算,例如编译程序中符号表的查找。文本简单概括性的介绍了常见的其中查找算法,说是七种,其实是二分查找、插值查找以及斐波那契查找都可以归为一类–插值查找。插值查找和斐波那契查找是在二分查找的基础上的优化查找算法,树表查找和哈希查找。
顺序查找-----略
前提条件:数组中的数据必须是有序的
升序或者降序排列
核心逻辑:每次排除一半的查找范围
1、min和max表示当前要查找的范围
2、mid是在min和max中间的
3、如果要查找的元素在mid的左边,缩小范围时,min不变,max等于mid减1
4、如果要查找的元素在mid的右边,缩小范围,max不变,min等于mid加1
//定义两个变量记录要查找的范围
int min = 0;
int max = arr.length-1;
//利用循环不断地取找要查找的数据
while(true){
if(min>max){
return -1;
}
//找到min和max的中间位置
int mid = (min+max)/2;
//拿着mid指向的元素跟要查找的元素进行比较
//number在mid的右边
//number跟mid指向的元素一样
if(arr[mid]>number){
//number在mid的左边
//min不变,max=mid-1;
max=mid-1;
}else if(arr[mid]<number){
//number在mid的右边
//max不变,min=mid+1;
min=mid+1;
}else{
//number跟mid指向的元素一样
return mid;
}
}
1、二分查找的优势?
提高查找效率
2、二分查找的前提条件?
数据必须是有序的
如果数据是乱的,先排序再用二分查找得到的索引没有实际意义,只能确定当前数字在数组中是否存在,因为排序之后数字的位置就可能发生变化了
3、二分查找的过程
min和max表示当前要查找的范围
mid是在min和max中间的
如果要查找的元素在mid的左边,缩小范围时,min不变,max等于mid-1
如果要查找的元素在mid的右边,缩小范围时,max不变,min等于mid+1
4、二分查找、插值查找、斐波那契查询各自的特点
相同点:
都是通过不断缩小范围来查找对应的数据的
不同点:
计算mid的方式不一样
二分查找:mid每次都是指向范围的中间位置
插值查找:mid尽可能地靠近要查找的数据,但是要求数据尽可能地分布均匀
斐波那契查找:根据黄金分割点来计算mid指向的位置
分块的原则1:前一块中的最大数据,小于后一块中的所有数据(块内无序,块间有序)
分块的原则2:块数数量一般等于数字的个数开根号。比如:16个数字一般分为4个块左右
核心思路:先确定要查找的元素在哪一个块,然后在块中挨个查找
冒泡排序:相邻的数据两两比较,小的放前面,大的放后面
1、相邻的元素两两比较,大的放右边,小的放左边
2、第一轮循环结束,最大值已经找到,,在数组的最右边
3、第二轮循环只要在剩余的元素找最大值就可以了
4、每二轮循环结束,次大值已经确定,第三轮循环继续在剩余数据中循环
1、相邻的元素两两比较,大的放右边,小的放左边
2、第一轮比较完毕之后,最大值就已经确定,第二轮可以少循环一次,后面以此类推
3、如果数组中有n个数据,总共我们只要执行n-1轮的代码就可以
选择排序:从0索引开始,拿着每一个索引上的元素跟后面的元素依次比较,小的放前面,大的放后面,以此类推。
1、从0索引开始,跟后面的元素一一作比较
2、小的放前面,大的放后面
3、第一轮循环结束后,最小的数据已经确定
4、第二轮循环从1索引开始以此类推
5、第三轮循环从2索引开始以此类推
6、第四轮循环从3索引开始以此类推
将0索引的元素到N索引的元素看作是有序的,把N+1索引的元素到最后一个当成是无序的。遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同的数据,插入在后面
//1、找到无序的那一组数组是从哪个索引开始的
int startIndex=-1;
for(int i=0;i<arr.length;i++){
if(arr[i]>arr[i+1]){
startIndex=i+1;
break;
}
}
//2、遍历从startIndex开始到最后一个元素,依次得到无序的那一组数据中的每一个元素
for(int i=startIndex;i<arr.length;i++){
//问题:如何把遍历到的数据,插入到前面有序的这一组当中
//记录当前要插入数据的索引
int j=i;
while(j>0&&arr[j]<arr[j-1]){
//交换位置
int temp=arr[j];
arr[j]=arr[j-1];
arr[j-1]=temp;
j--;
}
printArr(arr);
}
递归指的是方法中调用方法本身的现象
递归的注意点:递归一定要有出口,否则就会出现内存溢出
把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
递归策略只需少量的程序就可描述除解题过程所需要的多次重复计算
写递归的两个核心:
找出口:什么时候不再调用方法
找规则:如何把大问题变成规模较小的问题
方法内部再次调用方法的时候,参数必须要更加的靠近出口
public static int getFactorialRecursion(int number){
if(number==1){
return 1;
}
return number*getFactorialRecursion(number:number-1);
}
第一轮:把0索引的数字作为基准数,确定基准数在数组中正确的位置
比基准数小的全部在左边,比基准数大的全部在右边
public static void quickSort(int[] arr,int i,int j){
//定义两个变量记录要查找的范围
int start = i;
int end = j;
if(start>end){
return;
}
//记录基准数
int baseNumber = arr[i];
while(start!=end){
//利用end,从后往前开始找,找比基准数小的数字
while(true){
if(end<=start||arr[end]<baseNumber){
break;
}
end--;
}
//利用start,从前往后找,找比基准数大的数字
while(true){
if(end<=start||arr[start]>baseNumber){
break;
}
start--;
}
//把end和start指向的元素进行交换
int temp = arr[start];
arr[start]=arr[end];
arr[end]=temp;
}
//当start和end指向了同一个元素的时候,那么上面的循环就会结束
//表示已经找到了基准数在数组中应存入的位置
//基准数归位
//就是拿着这个范围中的第一个数字,跟start指向的元素进行交换
int temp = arr[i];
arr[i] = arr[start];
arr[start]=temp;
quickSort(arr,i,j:start-1);
quickSor(arr,i:start+1,j);
}
1、冒泡排序
相邻的元素两两比较,小的放前面,大的放后面
2、选择排序:
从0索引开始,拿着每一个索引上的元素跟后面的元素依次比较
小的放前面,大的放后面,依次类推
3、插入排序
将数组分为有序和无序两组,遍历无序数组,将元素插入有序序列中即可。
4、快速排序
将排序范围中的第一个数字作为基准数字,再定义两个变量start、end
start从前往后找比基准数大的,end从后往前找比基准数小的
找到之后交换start和end指向的元素,并循环这一过程,直到start和end处于同一个位置,该位置是基准数在数组中应存入的位置,再让基准数归位
归为后的效果:基准数左边的,比基准数小。基准数右边的,比基准数大
操作数组的工具类
public static Stirng toString(数组)----把数组拼接成一个字符串
public static int binarySearch(数组,查找的元素)----二分查找法查找元素
public static int[] copyOf(原数组,新数组长度)----拷贝数组
public static int[] copyOfRange(原数组,起始索引,结束索引)----拷贝数组(指定范围)
public static void fill(数组,元素)----填充数组
public static void sort(数组)----按照默认方式进行数组排序
public static void sort(数组,排序规则)----按照指定的规则排序
binarySearch:二分查找法查找元素
细节1:二分查找的前提:数组中的元素必须是有序,数组中的元素必须是升序的
细节2:如果要查找的元素是存在的,那么返回的是真实的索引
但是,如果要查找的元素是不存在的,返回的是-插入点-1
疑问:为什么要减1呢?
解释:如果此时,我现在要查找数字0,那么如果返回的值是-插入点,就会出现问题了
如果要查找数字0,此时0是不存在的,但是按照上面的规则-插入点,应该就是-0
为了避免这样的情况,Java在这个基础上又减一。
copyOf:拷贝数组
参数一:老数组
参数二:新数组的长度
方法的底层会根据第二个参数来创建新的数组
如果新数组的长度是小于老数组的长度,会部分拷贝
如果新数组的长度是等于老数组的长度,会完全拷贝
如果新数组的长度是大于老数组的长度,会补上默认初始值
copyOfRange:拷贝数组(指定范围)
细节:包头不包尾,包左不包右
sort:排序,默认情况下,给基本数据类型进行升序排列,底层使用的是快速排序
sort(数组,排序规则)按照指定的规则排序
参数一:要排序的数组
参数二:排序的规则
细节:只能给引用数据类型的数组进行排序
如果数组是基本数据类型的,需要变成其对于的包装类
第二个参数是一个接口,所以我们在调用方法的时候,需要传递这个接口的实现类对象,作为排序的规则。
但是这个实现类,我只要使用依次,所以就没有必要单独的去写一个类,直接采取匿名内部类的方式就可以了
底层原理
利用插入排序+二分查找的方式进行排序
默认把0索引的数据当作是有序的序列,索引到最后认为是无序的序列
遍历无需的序列得到里面的每一个元素,假设当前遍历得到的元素是A元素
把A往有序序列中进行插入,在插入的时候,是利用二分查找确定A元素的插入点
拿着A元素,跟插入点的元素进行比较,比较的规则就是compare方法的方法体
如果方法的返回值是负数,拿着A继续跟前面的数据进行比较
如果方法的返回值是正数,拿着A继续跟后面的数据进行比较
如果方法的返回值是0,也拿着A跟后面的数据进行比较
直到能确定A的最终位置为止
compare方法的形式参数:
参数 o1:表示在无序序列中,遍历得到的每一个元素
参数o2:有序序列中的元素
返回值:
负数:表示当前要插入的元素是小的,放在前面
正数:表示当前要插入的元素是大的,放在后面
0:表示当前要插入的元素跟现在的元素比是一样的我们也会放在后面。
简单理解
o1-o2:升序排列
o2-o1:降序排列
函数式编程
函数式编程是一种思想特点
面向对象:先找对象,让对象做事情
函数式编程思想,忽略面向对象的复杂语法,强调做什么,而不是谁去做
而我们要学习的Lambda表达式就是函数式思想的体现
Lambda表达式是JDK8开始后的一种新语法形式
()->{
}
()对应着方法的形参
->固定格式
{}对应着方法的方法体
Lambda表达式可以用来简化匿名内部类的书写
Lambda表达式只能简化函数式接口的匿名内部类的写法
函数式接口:有且仅有一个抽象方法的接口叫做函数式接口,接口上方可以加@FunctionalInterface注解
1、Lambda表达式的基本作用?
简化函数式接口的匿名内部类的写法
2、Lambda表达式有什么使用前提?
必须是接口的匿名内部类,接口中只能有一个抽象方法
3、Lambda的好处?
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码,它可以写出更简洁、更灵活的代码,作为一种更紧凑的代码风格,使Java语言表达能力得到了提升
省略核心:可推导,可省略
省略规则:
1、参数类型可以省略不写
2、如果只有一个参数,参数类型可以省略,同时()也可以省略
3、如果Lambda表达式的方法体只有一行,大括号,分号,return可以省略不写,需要同时省略
Lambda表达式简化Comparator接口的匿名形式
定义数组并存储一些字符串,利用Arrays中的sort方法进行排序
要求:
按照字符串的长度进行排序,短的在前面,长的在后面
(暂时不比较字符串里面的内容)
String[] arr = {"a","aaaa","aaa","aa"};
Arrays.sort(arr,(String o1,String o2)->{
return o1.length()-o2.length();
}
}
String[] arr = {"a","aaaa","aaa","aa"};
Arrays.sort(arr,(o1,o2)->o1.length()-o2.length());
集合体系结构
单列结构:每次添加一个
双列集合:每次添加两个
list系列集合:添加的元素是有序(存和取的顺序一样)、可重复(集合中存储的元素是可重复的)、有索引(可以通过索引值进行获取)
set系列集合:添加的元素是无序(存和取的顺序有可能是不一样的)、不重复(集合中不能存储重复的元素)、无索引(不能通过索引来获取set集合里的元素)
Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的
public boolean add(E e)----把给定的对象添加到当前集合中
public void clear()----清空集合中所有的元素
public boolean remove(E e)----把给定的对象在当前集合中删除
public boolean contains(Object obj)----判断当前集合中是否包含给定的对象
public boolean isEmpty()----判断当前集合是否为空
public int size()----返回集合中元素的个数/集合的长度
注意点:
Collection是一个接口,我们不能直接创建它的对象
所以,现在我们学习他的方法时,只能创建它实现类的对象
目的:为了学习Collection接口里面的方法
自己在做一些练习的时候,还是按照之前的方式去创建对象
Collection<String> coll = new ArrayList<>();
//1、添加元素
//细节1:如果我们要往List系列集合中添加数据,那么方法永远返回true,,因为List系列的是允许元素重复的。
//细节2:如果我们要往Set系列集合中添加数据,如果当前要添加元素不存在,方法返回true,表示添加成功;如果当前添加的元素已经存在,方法返回false,表示添加失败。因为Set系列的集合不允许重复。
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
System.out.println(coll);
//2、清空
coll.clear();
System.out.println(coll);
//3、删除
//细节1:因为Collection里面定义的是共性的方法,所以不能通过索引进行删除,只能通过元素的对象进行删除
//细节2:方法会有一个布尔类型的返回值,删除成功返回true,删除失败返回false
//如果要删除的元素不存在,就会删除失败
System.out.println(coll.remove(o:"aaa"));
System.out.println(coll);
//4、判断元素是否包含
//细节:底层是依赖equals方法进行判断是否存在的
//所以,如果集合中存储的是自定义对象,也想通过contains方法来判断是否包含,那么在javabean类中,一定要重写equals方法
boolean result = coll.contains("aaa");
System.out.println(result);
//5、判断集合是否为空
boolean result2 = coll.isEmpty();
System.out.println(result2);
//6、获取集合的长度
int size = coll.size();
System.out.println(size);
//1、创建集合的对象
Collection coll = new ArrayList<>();
//2、创建三个学生对象
Student s1 = new Student(name:"zhangsan",age:23);
Student s2 = new Student(name:"lisi",age:24);
Student s3 = new Student(name:"wangwu",age:25);
//3、把学生对象添加到集合当中
coll.add(s1);
coll.add(s2);
coll.add(s3);
//4、判断集合中某一个学生对象是否包含
Student s4 = new Student(name:"zhangsan",age:23);
//如果同姓名和同年龄就认为是同一个学生
//因为contains方法在底层依赖equals方法判断对象是否一致的。
//如果存的是自定义对象,没有重写equals方法,那么默认使用Object类中的equals方法进行判断,而Object类中的equals方法,依赖地址值进行判断
//需求:如果同姓名和童年林,就认为是同一个学生
//所以,需要在自定义的Javabean类中,重写equals方法就可以了
迭代器遍历
增强for遍历
Lambda表达式遍历
迭代器不依赖索引
迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式
Collection集合获取迭代器
Iterator< E> iterator()----返回迭代器对象,默认指向当前集合的0索引
Iterator中的常用方法
boolean hasNext()----判断当前位置是否有元素,有元素返回true,没有元素返回false
E next()----获取当前位置的元素,并将迭代器对象移向下一个位置
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}
创建指针
判断是否元素
获取元素;移动·指针
//1、创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");
//2、获取迭代器
//迭代器就好比是一个箭头,默认指向集合的0索引处
Iterator<String> it = coll.iterator();
//3、利用循环不断地去获取集合中的每一个元素
while(it.hasNext)){
//4、next方法的两件事情:获取元素并移动指针
String str = it.next();
System.out.println(str);
}
细节注意点
1、如果在元素结束之后强行获取,会报错NoSuchElementException
2、迭代器遍历完毕,指针不会复位
3、循环中只能用一次next方法(指针不会复位)
4、迭代器遍历时,不能用集合的方法进行增加或者删除
如果我实在要删除,那么可以用迭代器提供的remove方法进行删除,如果我要添加,暂时没有办法
1、迭代器在遍历集合的时候是不依赖索引的
2、迭代器需要掌握三个方法
Iterator<String> it = list.iterator():
while(it.hasNext){
String str = it.next();
System.out.println(str);
}
3、迭代器的四个细节:
如果当前位置没有元素,还要强行获取,会报NoSuchElementException
迭代器遍历完毕,指针不会复位
循环中只能用一次next方法
迭代器遍历时,不能用集合的方法进行增加或者删除
增强for的底层就是迭代器,为了简化迭代器的代码书写的
它是JDK5之后出现的,其内部原理就是一个Iterator迭代器
所有的单列集合和数组才能用增强for进行遍历
快速生成方式
集合的名字+for 回车
Collection<String> coll = new ArrayList<>();
coll.add("zhangsan");
coll.add("lisi");
coll.add("wangwu");
//s其实就是一个第三方变量,在循环的过程中依次表示集合的每一个数据
for(String s:coll){
System.out.println(s);
}
增强for的细节
修改增强for中的变量,不会改变集合中原本的数据(s只是个第三方变量)
得益于JDK8开始的新技术Lambda表达式,,提供了一种更简单、更直接的遍历集合的方式
default void forEach(Consumer super T>action):----结合lambda遍历集合
Collection<String> coll = new ArrayList<>();
coll.add("zhangsan");
coll.add("lisi");
coll.add("wangwu");
//2、利用匿名内部类的形式
//底层原理:
//其实也会自己便利集合,依次得到每一个元素
//把得到的每一个元素,传递给下面的accept方法
//s依次表示集合中的每一个数据
coll.foreach(new Consumer<String>(){
@Override
//s依次表示集合中的每一个数据
public void accept(String s){
System.out.println(s);
}
});
//lambda表达式
//()->()
coll.forEach(s->System.out.println(s));
1、Collection是单列集合的顶层接口,所有方法被List和Set系列集合共享
2、常见成员方法:
add、clear、remove、contains、isEmpty、size
3、三种通用的遍历方式
迭代器:在遍历的过程中需要删除元素,请使用迭代器。
增强for、Lambda:仅仅想遍历,那么使用增强for或Lambda表达式
特点:
有序:存和取的元素顺序一致
有索引:可以通过索引操作元素
可重复:存储的元素可以重复
方法:
Collection的方法List都继承了
List集合因为有索引,所以多了很多索引操作的方法
void add(int index , E element)----在此集合中的指定位置插入指定元素
E remove (int index)----删除指定索引处的元素,返回被删除的元素
E set(int index ,E element)----修改指定索引处的元素,返回被修改的元素
E get(int index)----返回指定索引处的元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//细节:原来索引上的元素会依次往后移
list.add(index:1,element:"QQQ")
//删除指定索引处的元素,返回被删除的元素
String remove = list.remove(index:0);
System.out.println(remove);
//修改指定索引处的元素,返回被修改的元素
String result = list.set(0,"QQQ");
System.out.println(result);
System.out.println(list);
删除元素小细节:
list.add(1);
list.add(2);
list.add(3);
请问:此时删除的是1这个元素,还是1索引上的元素?
为什么?
因为在调用方法的时候,如果方法出现了重载现象
优先调用,实参跟形参类型一致的那个方法。
手动装箱:手动把基本数据类型的1,变成Integer类型
Integer i = Integer.valueOf(1);
迭代器遍历
列表迭代器遍历
增强for遍历
Lambda表达式遍历
普通for循环(因为List集合存在索引)
Iterator< String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}
下面的变量s,其实就是一个第三方的变量而已
在循环的过程中,以此表示集中的每一个元素
for(String s:list){
System.out.println(s);
}
forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
并把每一个元素传递给下面的accept方法
accept方法的形参s,依次表示集合中的每一个元素
list.forEach(new Consumer<String>(){
@Override
public void accept(String s){
System.out.println(s);
}
});
list.forEach(s->System.out.println(s));
size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素
for(int i=0;i<list.size();i++){
String s=list.get(i);
System.out.println(s);
}
获取一个列表迭代器的对象,里面的指针默认也是指向0索引的
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String str = it.next();
if("bbb".equals(str)){
it.add("qqq");
}
}
迭代器遍历:在遍历的过程中需要删除元素,请使用迭代器
列表迭代器:在遍历的过程中需要添加元素,请使用列表迭代器
增强for循环遍历:仅仅想遍历,那么使用增强for或Lambda表达式
Lambda表达式:仅仅想遍历,那么使用增强for或Lambda表达式
普通for:如果遍历的时候想操作索引,可以用普通for
什么是数据结构呢?
计算机存储、组织数据的方式
不同的业务场景要选择不同的数据结构
数据结构的概述
数据结构是计算机底层存储、组织数据的方式
是指数据相互之间是以什么方式排列在一起的
数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择
一般情况下,精心选择的数据结构可以带来更高的运行或者存储效率
1、每种数据结构长什么样子
2、如何添加数据
3、如何删除数据
栈、队列、数组、链表、二叉树、二叉查找树、平衡二叉树、红黑树
栈的特点:后进后出,先进先出
一端开口,栈顶
一端封闭,栈底
数据进入栈模型的过程称为:压/进栈
数据离开栈模型的过程称为:弹/出栈
队列的特点:先进先出,后进后出
一端开口:后端
一段开口:前端
数据从后端进入队列模型的过程称为:入队列
数据从前端离开队列模型的过程称为:出队列
数组是一种查询块,增删慢的模型
查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。元素在内存中是连续存储的
删除效率低:要将原始数据删除,同时后面每个数据前移
添加效率低:添加位置后的每个数据前移,再添加元素
单向链表:
链表中的结点都是独立的对象,在内存中是不连续的,每个节点包含数据值和下一个节点的地址
链表查询慢,无论查询哪个数据都要从头开始找
链表增删相对快
每一个都是独立的节点
节点里面有具体的数据,和下一个数据的地址
创建一个链表
会有一个头head,里面有下一个数据的地址,
双向链表
有前后数据的地址值
栈:后进先出,先进后出
队列:先进先出,后进后出
数组:内存连续区域,查询快,增删慢
链表:元素是游离的,查询慢,首尾操作极快
底层原理:
利用空参创建的集合,在底层创建一个默认长度为0的数组(elementDate)
添加第一个元素时,底层会创建一个新的长度为10的数组
存满时,会扩容1.5倍
如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
底层数据结构是双链表,查询慢,增删快,但是如果操作的是首尾元素,速度也是极快的.
LinkedList本身多了很多直接操作首尾元素的特有API
底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API
public void addFirst(E e)----在列表开头插入指定的元素
public void addLast(E e)----将指定的元素追加到此列表的末尾
public E getFirst()----返回此列表中的第一个元素
public E getLast()----返回此列表中的第一个元素
public E removeFirst()----从此列表中删除并返回第一个元素
public E removeLast()----从此列表中删除并返回最后一个元素
在底层实际上就是创建了一个内部类的对象
这个内部类就表示是ArrayList的迭代器
所以当我们调用多次这个方法的时候,
那么相当于就是创建了多个迭代器的对象
光标:表示是迭代器里面的那个指针,默认指向0索引的位置
结论:在以后如何避免并发修改异常
在使用迭代器或者是增强for遍历集合的过程中,不要使用集合的方法去添加或者删除元素即可
泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
泛型的格式:<数据类型>
注意:泛型只能支持引用数据类型
如果我们没有给集合指定类型,默认认为所有的数据类型都是Object类型
此时可以往集合添加任意的数据类型
带来一个坏处:我们在获取数据的时候,无法使用它的特有行为
此时推出了泛型,可以在添加数据的时候就把类型进行统一
而且我们在获取数据的时候,也省的强转了,非常的方便
泛型的好处:
统一数据类型
把运行时期的问题提前到来了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来
泛型的细节:
泛型中不能写基本数据类型
指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
如果不写泛型,类型默认是Object
泛型可以在很多地方进行定义
写在类后面----泛型类
写在方法上面----泛型方法
写在接口后面----泛型接口
泛型类
使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
public classArrayList<E>{
}
此时E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成T、E、K、V等
当我编写一个类的时候,如果不确定类型,那么这个类就可以定义为泛型类
泛型方法
方法中形参类型不确定时,可以使用类名后面定义的泛型< E>
方法中形参类型不确定时:
1、使用类名后面定义的泛型----所有方法都能用
2、在方法申明上定义自己的泛型----只有本方法能用
修饰符<类型>返回值类型 方法名(类型 变量名){
}
public <T> void show(T t){
}
此处的T可以理解为变量,但是不是用来记录数据的,而是记录类型的
泛型接口
修饰符 interface 接口名<类型>{
}
public interface List<E>{
}
重点:如何使用一个带泛型的接口
方式1:实现类给出具体类型
方法2:实现类延续泛型,创建对象时再确定
泛型的继承和通配符
泛型不具备继承性,但是数据具备继承性
泛型里面写的是什么类型,那么只能传递什么类型的数据
//泛型不具备继承性,但是数据具备继承性
ArrayList<Ye>list1 = new ArrayList<>();
ArrayList<Fu>list2 = new ArrayList<>();
ArrayList<Zi>list3 = new ArrayList<>();
//调用method方法
method(list1);
method(list2);//报错
method(list3);//报错
public static void method(ArrayList<Ye> list){
}
需求:定义一个方法,形参是一个集合,但是集合中的数据类型不确定
ArrayList<Ye>list1 = new ArrayList<>();
ArrayList<Fu>list2 = new ArrayList<>();
ArrayList<Zi>list3 = new ArrayList<>();
method(list1);
method(list2);
method(list3);
public static<E> void (ArrayList<E> list){
}