菜鸟学习笔记:Java容器3--泛型、排序

  • 泛型
    • 泛型类
    • 泛型接口
    • 泛型方法
    • 泛型继承
    • 通配符"?"
    • 泛型知识点补充
  • 容器排序
    • Comparable接口与compareTo方法
    • Comparator接口与compare方法
    • TreeSet和TreeMap(了解)

泛型

编成中我们往往会遇到这种情况,在接收参数时无法明确入参的类型,比如我们要接收一个学生的成绩,那么它可能是整数、也可能是小数,还有可能是字符串,早期的Java通常将不能确定的入参定义为Object类型来接收各种类型的参数。在获取时进行强制类型转换。但是这样会带来一个问题,把对象扔进集合中,集合是不知道元素的类型是什么的,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换,如果会出现不能转换的情况会报出ClassCastException异常。泛型正好帮我们解决了这一问题。
泛型的作用就是把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型,说白了就是把参数的类型当作参数一起传递。这样在编译过程中就可以限定入参的类型。我们用例子来说明:

泛型类

使用时确定类型
注意:
1、泛型只能使用引用类型,不能基本类型
2、泛型声明时字母不能使用 静态属性|静态方法上

 // 这里的T1和T2可以是任意合法名称在习惯上K V 分别代表键值中的Key Value。E 代表Element。
public class Student<T1,T2> {private T1 javaScore;private T2 oracleScore;//泛型声明时不能使用 静态属性|静态方法上//private static T1 test;public T1 getJavaScore() {return javaScore;}public void setJavaScore(T1 javaScore) {this.javaScore = javaScore;}public T2 getOracleScore() {return oracleScore;}public void setOracleScore(T2 oracleScore) {this.oracleScore = oracleScore;}public static void main(String[] args) {//使用时指定类型(引用类型),此时T1为String,T2为IntrgerStudent<String,Integer> stu = new Student<String,Integer> ();//1、安全:类型检查,这时如果你设置成非字符串类型在编译时就会报错stu.setJavaScore("优秀");//2、省心:类型转换,不用再像Object类型那样进行强转。int it =stu.getOracleScore(); //自动拆箱}}

泛型接口

接口中 泛型字母只能使用在方法中,不能使用在全局常量中

public interface Comparator<T> {T a;//会报错,因为全局常量是static final类型void compare(T t);
}

泛型方法

泛型方法方法只能访问对象的信息,不能修改信息。

 public static <T> void test(T a){//这里的a无论是什么类型只能访问,不能修改        System.out.println(a);}public static void main(String[] args) {test("a"); //T -->String}

最后需要注意的是泛型没有多态的概念,如果直接使用
A a = new A();会报错。并且规定不能使用instanceof来判断是否为泛型实例。

泛型继承

父类的泛型可以被子类所继承,也可以被子类所擦除,这个特点在普通类、抽象类以及接口中均适用,下面我们通过例子来说明:
假设我们在父类中定义了如下的泛型:

public abstract class Father<T,T1> {T name;public abstract void test(T t);
}

子类在继承时可以直接指定泛型的具体类:

class Child1 extends Father<String,Integer>{//此时name自动变为String类型@Overridepublic void test(String t) {//重写自动变为String t}
}

如果子类是泛型类,父类也指定为泛型,那么泛型结构必须和父类一致或大于父类:

//子类的泛型类必须有T1和T,T1和T顺序可以调换。
class Child2<T1,T,T3> extends Father<T,T1>{T1 t2;@Overridepublic void test(T t) {}
}

如果不指定出父类的泛型,那么该泛型就会变成Object类型,这就是泛型的擦除。所以上例也可以在子类中指定泛型类,父类中不指定,这样泛型就会被擦除,使用Object替换。

class Child3<T1,T2> extends Father{T1 name2;@Overridepublic void test(Object t) {// TODO Auto-generated method stub       }}

也可以子类和父类同时擦除泛型,所有泛型都用Object来替换:

class Child4 extends Father{String name; @Overridepublic void test(Object t) {}
}

但需要注意子类擦除而父类包括泛型的情况是不允许的。
在泛型擦除时需要注意被擦除的泛型相当于Object但不完全等同于Object。比方说:

 public static  void test(Student<Integer> a){}public static  void test1(Student<?> a){//?表示泛型不定}public static void main(String[] args) {Student stu1 = new Student(); Student<Object> stu = new Student<Object>(); test(stu1); //stu1 相当于Object 但是不完全等同Object//test(stu);//会报错,说明类型擦除后,编译时不会类型检查test1(stu1);test1(stu);}

通配符"?"

"?"用在泛型中表示可以接收泛型的任意类型,只能接收和输出,不能进行修改。一般与extends和super关键字搭配使用。
? extends 泛型上线 表示问号可以接受的泛型类必须是泛型上线的子类(可以包括上线)。
? super 泛型下线 表示问号可以接受的泛型必须是泛型下线的父类(可以包括下线)。
下面通过举例来说明:
首先定义一个Fruit类和Apple类:

public class Fruit {}
class Apple extends Fruit{}

在定义一个Student类用于测试:

public class Student<T> {T score;//可接收任意类型public static void test(Student<?> stu){}//Fruit类的子类public static void test2(Student<? extends Fruit> stu){}//指定类型public static void test3(Student<Fruit> stu){}//Fruit类的父类public static void test4(Student<? super Fruit> stu){ }public static void main(String[] args) {//可以?类的泛型指向具体类的对象Student<?> stu = new Student<String>();test(new Student<Integer>());//Apple为Fruit子类test2(new Student<Apple>());//test3(new Student<Apple>()); //泛型没有多态,非Fruit类都会报错       //test4(new Student<Apple>()); //会报错,Apple不是Fruit的父类stu  = new Student<Fruit>();//test4(stu); //stu为Student<?>类型引用变量,与test4接收变量的类型不符,会报错test4(new Student<Object>());//Object是Fruit父类,合法test4(new Student<Fruit>());//extends可以包括自己      }}

泛型知识点补充

  1. 在泛型中还可以使用泛型做嵌套:
public class test <T>{T stu ;public static void main(String[] args) {//泛型的嵌套 test<Student<String>> room =new  test<Student<String>>();//从外到内拆分room.stu = new Student<String>();Student<String> stu = room.stu;String score =stu.score;}
}

没有泛型数组,比如:

test<String>[] arr2 = new test<String>[10];
//test<String>[] arr2可以声明,但不能创建new test<String>[10]

容器排序

Comparable接口与compareTo方法

在可以进行排序的实体类中都实现了java.lang.Comparable接口,此接口中只有一个compareTo方法,该方法通过返回值的正负对this和传入对象作比较,返回0表示两对象相等,返回正数表示this>obj,返回负数表示this<obj,实现了Comparable接口的类通过实现compareTo方法从而确定该类对象的排序方式。
基本类型包装类的比较比较简单,整数和小数以及日期类比较其基本数据类型值的大小,字符比较字符的ASCII码的大小,一般a-z递减,大写字母大于小写字母。
String类的compareTo方法从两个字符串的第一个字符开始进行比较,相等就比较下一个字符,直到一个字符串走完为止,如果走完则比较字符串长度,相等则相等,否则长度大的字符串大,其源码如下:

    public int compareTo(String anotherString) {int len1 = value.length;int len2 = anotherString.value.length;//取int lim = Math.min(len1, len2);char v1[] = value;char v2[] = anotherString.value;int k = 0;while (k < lim) {char c1 = v1[k];char c2 = v2[k];if (c1 != c2) {return c1 - c2;}k++;}return len1 - len2;}

有了compareTo方法,我们就可以对任意类型数组进行排序

 public static <T extends Comparable<T>> void sort(T[] arr){boolean sorted = true;//需进行length-1次冒泡for(int i=0;i<arr.length-1;i++){//该变量用于优化冒泡排序,一次比较中如果未发生交换则排序结束sorted = true;for(int j=0;j<arr.length-1-i;j++){//用Comparable方法判断数组内容大小if(((Comparable)arr[j]).compareTo(arr[j+1])>0){T temp=arr[j];arr[j]=arr[j+1];arr[j+1]=temp;sorted = false;}}if(sorted){break;}}}

Comparator接口与compare方法

与Comparable接口功能相同,java.util.Comparator接口通过重写compare方法也可以实现排序,下面举例说明,这次我们按照字符串的长度来进行排序:
首先来定义排序规则类(也可以不定义这个直接使用匿名内部类):

public class StringComp  implements java.util.Comparator<String>{//按字符串长度排序@Overridepublic int compare(String o1, String o2) {int len1 =o1.length();int len2 =o2.length();        return -(len1-len2);}
}

然后定义一个工具类Utils定义排序方法,接收一个排序规则:

public class Utils{public static <T> void sort(Object[] arr,Comparator<T> com){//从大到小排序 降序boolean sorted= true;int len =arr.length;for(int j=0;j<len-1;j++){ //趟数sorted =true; //假定有序for(int i=0;i<len-1-j;i++){ //次数if(com.compare((T)arr[i], (T)arr[i+1])<0){Object temp = arr[i];arr[i] =arr[i+1];arr[i+1] =temp;sorted =false; //假定失败}}if(sorted){ //减少趟数break;}}}
}

这样我们就可以在调用时通过传入我们的排序规则对对象进行排序:

public static void main(String[] args) {arr2 =new String[]{"a","abcd","abc","def"};Utils.sort(arr2,new StringComp());System.out.println(Arrays.toString(arr2));
}

这样就实现了一个简单的比较类,当然实际工作中不需要我们自己写排序规则Utils,在Java为我们提供了Collections类已经很好的帮我们实现了各种有序容器的排序,使用方法如下:

 public static void main(String[] args) {List<String> list =new ArrayList<String>();list.add("a");list.add("abcd");list.add("abc");list.add("def");//匿名内部类方法实现Collections.sort(list, new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {int len1 =o1.length();int len2 =o2.length();     return -(len1-len2);}});System.out.println(list);}

TreeSet和TreeMap(了解)

Set和Map在是无序不可重复的容器,但Java中也提供了可以对其进行排序的实现类,那就是TreeSet和TreeMap。
TreeSet通过判断compareTo方法或者compare方法的结果是否为0来判断两元素是否相等,所以不需要重写hashcode和equals方法。举例说明:
先定义一个实体类:

public class Person implements Comparable<Person> {private String name;private int age;...//get和set方法public int compareTo(Person o) {return 0;     //当compareTo方法返回0的时候集合中只有一个元素return 1;     //当compareTo方法返回正数的时候集合会怎么存就怎么取return -1;    //当compareTo方法返回负数的时候集合会倒序存储}
}
public static void  main(String[] args) {TreeSet<Person> ts = new TreeSet<>(new Comparator<String> {@Override//其实用compareTo或compare一个方法就可以实现功能,这里综合一下两个比较器的用法public int compare(String s1, String s2) {  int num = s1.length() - s2.length();        return num == 0 ? s1.compareTo(s2) : num;    }});Person p1 = new Person("张三", 11);Person p2 = new Person("李四", 12)Person p3 = new Person("王五", 15)Person p4 = new Person("赵六", 21)ts.add(p1);ts.add(p2);ts.add(p3);ts.add(p4);p1.setAge(13);}

注意TreeSet容器只会对添加的数据排序,如果对元素进行修改中不会影响它的排序位置。比如上例中最后添加p1.setAge(13);不会改变排序结果,张三还是会排在第一位。同样set方法设置后容器还可能出现重复的情况。TreeMap的用法以及带来的问题与TreeSet相似,这里不再赘述。

上一篇:菜鸟学习笔记:Java提升篇2(容器2——Map、Set、迭代器)
下一篇:菜鸟学习笔记:Java提升篇4(容器4——Collections工具类、其他容器)

菜鸟学习笔记:Java提升篇3(容器3——泛型、排序)相关推荐

  1. 决策树算法学习笔记(提升篇)

    声明:本文虽有部分自己理解成分,但是大部分摘自以下链接. 决策树(decision tree)(三)--连续值处理 决策树参数讲解+实例 数据挖掘十大算法 C4.5算法的改进: 用信息增益率来选择属性 ...

  2. 菜鸟学习笔记:Java提升篇4(容器4——Collections工具类、其他容器)

    菜鸟学习笔记:Java容器4--Collections工具类.其他容器 Collections工具类 容器其他知识点 队列Queue Enumeration接口 Hashtable Propertie ...

  3. 菜鸟学习笔记:Java提升篇2(容器2——Map、Set、迭代器)

    菜鸟学习笔记:Java容器2--Map.Set.迭代器 Map容器 HashMap的使用 Hash表讲解 Map实现 Set容器 HashSet的使用 实现 Iterator迭代器 Map容器 Has ...

  4. 菜鸟学习笔记:Java提升篇1(容器1——List)

    菜鸟学习笔记:Java容器1--List容器 容器基本概念 List容器 ArrayList 初始化 add方法 remove方法 LinkList 链表 双向链表 初始化 add方法 remove方 ...

  5. 菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信、聊天室案例)

    菜鸟学习笔记:Java提升篇10(网络2--UDP编程.TCPSocket通信) UDP编程 TCP编程(Socket通信) 单个客户端的连接 多个客户端的连接(聊天室案例) UDP编程 在上一篇中讲 ...

  6. 菜鸟学习笔记:Java提升篇5(IO流1——IO流的概念、字节流、字符流、缓冲流、转换流)

    菜鸟学习笔记:Java IO流1--IO流的概念.字节流.字符流.缓冲流.转换流 IO流的原理及概念 节点流 字节流 文件读取 文件写出 文件拷贝 文件夹拷贝 字符流 文件读取 文件写出 处理流 缓冲 ...

  7. 菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

    菜鸟学习笔记:Java提升篇12(Java动态性2--动态编译.javassist字节码操作) Java的动态编译 通过脚本引擎执行代码 Java字节码操作 JAVAssist的简单使用 常用API ...

  8. 菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)

    Java提升篇11(Java其它高级特性--注解与反射) 注解(Annotation) JDK内置注解 自定义注解 元注解(meta-annotation) 反射(reflection) 动态语言 反 ...

  9. 菜鸟学习笔记:Java提升篇9(网络1——网络基础、Java网络编程)

    菜鸟学习笔记:Java提升篇9(网络1--网络基础.Java网络编程) 网络基础 什么是计算机网络 OS七层模型 Java网络编程 InetAddress InetSocketAddress URL类 ...

最新文章

  1. 02-VTK编译安装
  2. CentOS6.5下做DNS服务器
  3. 图像局部显著性—点特征(SURF)
  4. IEC 6-1131/3的5种标准编程语言
  5. ambari 自定义组件安装
  6. Java—switch case实现两个数的算术运算
  7. Java共现矩阵的构建(用于社交网络结构分析)
  8. oracle 创建用户
  9. 119. PHP 性能问题(2)
  10. Atitit 查询优化器的流程attilax总结
  11. 系统集成项目管理工程师2021年报名时间
  12. 【微信篇】电脑版微信的照片视频文件位置变化
  13. 微信安卓6.5.3以上版本网页上传不了图片的解决方案
  14. 淘宝秒杀半价前N名半价商品
  15. 微信小程序自定义导航栏(带汉堡包菜单)
  16. SSMS证书已被颁发者吊销解决办法
  17. 2018 下半年 Java 后端工程师的书单推荐
  18. PDFbox的head is mandatory问题
  19. 慕课网《Flutter从入门到进阶》学习笔记一
  20. 消防车从红色变为黄绿色 最初原因竟是这

热门文章

  1. mpi4py安装报错error: Cannot compile MPI programs. Check your configuration!!!
  2. matlab busy 如何看进度,matlab solve 之后不出结果不报错,状态一直显示busy
  3. mysql 删除 like_MySQL 定时删除数据
  4. 误差函数拟合优缺点_欠拟合、过拟合及如何防止过拟合
  5. 微信小程序怎么绑定服务器,微信小程序页面表单如何跟图片一起上传服务器
  6. idea创建管理项目
  7. 可用于wpf的图表控件:WPFTookit Chart
  8. hdu 6149 Valley Numer II(01背包套状压dp)
  9. UVAPOJ离散概率与数学期望入门练习[4]
  10. POJ3728 THE MERCHANT LCA RMQ DP