目录
泛型
泛型概述
泛型的优点:
泛型高级
泛型通配符
泛型的定义位置
泛型类
泛型方法
泛型接口
增强For循环
增强for循环概述
静态导入
概述:
可变参数
List集合练习
集合的嵌套遍历
Set接口
Hashset
linkedHashSet
TreeSet
自然排序
比较器排序
泛型
之前说过集合可以存放不同引用数据类型的数据,当集合中既含有String类型的数据,又含有其它类型的数据时,我们在遍历时会出现这样的问题:
代码引例:
import java.util.ArrayList;import java.util.Iterator;public class Test1 { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add("hello"); list.add(20); list.add(12.48); list.add(new Students());// 遍历 Iterator iterator = list.iterator(); while (iterator.hasNext()){ String s=(String)iterator.next(); System.out.println(s); } }}
该集合中存放了四种引用数据类型的数据,当在遍历集合,进行向下转型操作时会报错:
ClassCastException:类型转换异常
这就是由于我们在集合中存放了不仅String类型的数据,还有Integer类型,Double类型和Student类型数据,但在强制类型转换时将遍历到的元素都转换成字符串类型,因此发生异常
解决办法:在存储数据时,将可以存储的数据类型限定为字符串类型,这样在强转时就不会报错
类似于在定义数组String[] arr=new String[3]时就已经将数组限定为只可以存放String类型的数据
Java提供了一种技术实现这种限定:泛型
泛型概述
把明确数据类型的工作,提前到了编译时期,在创建集合的时候明确存储元素的数据类型,有些类似于把数据当作参数一样传递,所以又称之为:参数化类型
泛型语句定义格式:<引用数据类型> 注意:尖括号中只能是引用数据类型
泛型的优点:
1、将我们运行时期出现的问题提前到了编译时期
2、不需要再做强制类型转换
3、优化了代码。消除了不必要的黄色警告线,使代码看起来更加整洁
通过观察API发现:泛型可以出现在类,接口,方法上,类似于
加入泛型后的代码:
import java.util.ArrayList;import java.util.Iterator;public class FanXinTest { public static void main(String[] args) { ArrayList
泛型高级 泛型通配符<?>
任意类型,如果没有明确,那就是Object以及任意的Java类
? extends E
向下限定,E及其子类
? super E
向上限定,E及其父类
代码举例:
在此之前创建三个类:Animal类,Cat类和Dog类,其中Cat类和Dog类继承自Animal类,类中不需要写具体代码。
import java.util.ArrayList;public class GenericMyDemo { public static void main(String[] args) { ArrayList animals1 = new ArrayList<>(); ArrayList
泛型的定义位置 泛型类
就是把泛型定义在类上
定义格式:
public class 类名<泛型类型1,...>
注意:这里的泛型类型可以不止一个,但是泛型类型必须是引用数据类型,这里<>中的内容仅仅表示的是一种参数数据类型,参数类型是一种变量,该变量要符合变量的命名规则,可以是任意符合标识符规则的名字
一般情况下在定义时习惯使用一个大写字母表示。
举例:
//这里是自定义一个泛型类public class GenericDemo1
{ private T obj; public T getObj(){ return obj; } public void setObj(T obj){ this.obj=obj; }} 泛型类的测试类
//这里是泛型类的测试类public class GenericDmeo1Test { public static void main(String[] args) { GenericDemo1
demo1 = new GenericDemo1<>(); demo1.setObj("hello"); String obj = demo1.getObj(); System.out.println(obj); }}
输出结果:
泛型方法
就是把泛型定义在方法上
定义格式:public <泛型类型> 返回类型 方法名(泛型类型 形参)
代码举例:
由于将来不知道需要传入什么类型的数据,因此定义泛型方法
public class Gdemo2 {// 泛型方法 public
void show(R r){ System.out.println(r); }} 泛型方法的测试类
public class Gtest2 { public static void main(String[] args) { Gdemo2 gdemo2 = new Gdemo2();// Stirng类型 gdemo2.show("hello");// Integer类型 gdemo2.show(19);// Double类型 gdemo2.show(15.23); }}
输出结果:
泛型接口
就是把泛型定义在接口上
定义格式:public interface 接口名<泛型类型1,...>
代码举例:
public interface Gdemo3
{ void show(W w);} 该接口的实现类
public class Gdemo3Impl
implements Gdemo3 { @Override public void show(W w) { System.out.println(w); }} 测试类
public class Gtest3{ public static void main(String[] args) { Gdemo3Impl
gdemo3 = new Gdemo3Impl (); gdemo3.show("world");// gdemo3.show(20);已经限定为String类型,不可以使用其他类型 }}
输出结果:
增强For循环
总结:
JDK1.5之后出现的新特性:泛型,增强for循环,包装类,Scanner,枚举
增强for循环概述
增强for循环就是简化数组和Collection集合的遍历
语句定义格式:
for(元素数据类型 变量名(自定义) : 数组或者Collection集合){
使用变量即可,该变量就是元素
}
增强for循环的好处:简化遍历
代码举例:
使用增强for循环遍历数组:
public class ForTest { public static void main(String[] args) {// 数组存储数据遍历 int[] arr={11,232,34,55,6};// 使用普通for循环遍历 for(int i=0;i
输出结果:
使用增强for循环遍历集合:
import java.util.ArrayList;public class ForTest2 { public static void main(String[] args) { // 集合存储对象遍历 ArrayList 如果该集合为null的话,直接使用增强for循环会报错:NullPointerException 空指针异常 所以我们应该在增强for循环之前加入一个判断:判断该集合是否为空即可 import java.util.ArrayList;public class ForTest2 { public static void main(String[] args) { // 集合存储对象遍历 ArrayList 输出结果: 增强for循环是代替迭代器的,如何验证? 迭代器会出现并发修改异常,若增强for循环也会出现并发修改异常,则说明增强for循环是代替迭代器的。 代码同迭代器中代码类似: 代码实现:在增强for循环中加入equals,若相同,则添加元素hadoop到集合中 for (String s : list) { if("hello".equals(s)){ list.add("hadoop"); } } 输出结果:ConcurrentModificationException 并发修改异常 说明增强for循环其实就是替代迭代器的 解决办法:迭代器遍历,迭代器修改;集合遍历,集合修改 这里只给出前一种解决办法: ListIterator 输出结果: 格式:import static 包名...类名.方法名; 可以直接导入到方法级别 注意事项:方法必须是静态的 举例: 在调用Math类中的一些方法时我们需要重复的使用Math.方法名();从其它类中调用方法也是如此,这时只需要使用静态导入即可解决。 注意:当本类中的方法与静态导入的方法名冲突时,调用的是本类中的方法 代码如下: 其他类定义: public class QitaClass { public static void show(String s){ System.out.println(s); } public static void fun(){ System.out.println("这世界很美好!!!"); }} 测试类: import static java.lang.Math.abs;import static java.lang.Math.pow;import static test.Test.src.com.QitaClass.fun;import static test.Test.src.com.QitaClass.show;public class DaoTest { public static void main(String[] args) {// Math类中的方法 System.out.println(Math.abs(-100)); System.out.println(Math.pow(2, 3));// 使用静态导入改进:导入Math类中需要调用的方法:// import static java.lang.Math.abs;// import static java.lang.Math.pow; System.out.println(abs(-23)); System.out.println(pow(2, 4)); fun();//此时调用的时本类中的fun()方法 show("你好世界");// 若仍然想调用其他类中的fun方法,解决办法为:加上前缀调用 test.Test.src.com.QitaClass.fun(); } public static void fun(){ System.out.println("本类中的fun()方法:this world is very good !!!"); }} 输出结果: 格式: 修饰符 返回值类型 方法名(数据类型..、变量名){} 注意:这里的变量其实是一个数组,如果一个方法有可变参数,并且有多个参数,可变参数一定是最后一个参数。 即若使用可变参数定义方法时,有其他数据类型参与的时候,将可变参数的定义放在最后。 Arrays工具类中的一个方法:public static 该方法其实就是将数组转变为一个集合 代码举例: import java.util.Arrays;import java.util.List;public class ChangeDemo { public static void main(String[] args) { int a=1; int b=2; int c=4; sum(a,b); sum(a,b,c); sum("赵云",45,20,30);// Arrays工具类中的一个方法 List 输出结果: 注意:同一个方法定义中,可变参数只能出现一次:即不会出现如下代码行: public static void sum(int..、ints,int..、ints2){} 目前学校有五年级学生和六年级学生。每个年纪有很多的学生,每个学生都是一个对象,可以使用集合表示一个班级的学生 五年级学生:ArrayList 六年级学生:ArrayList 无论是几年级的学生都属于学校的年级,于是学校本身也可以通过集合表示: ArrayList> school 像这种现象就叫做集合嵌套 代码举例: 学生类: public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + ''' + ", age=" + age + '}'; }} 集合嵌套类: import java.util.ArrayList;import java.util.Iterator;public class SchoolDemo { public static void main(String[] args) {// 创建学校对象 ArrayList> school = new ArrayList<>();// 创建年纪对象 ArrayList 输出结果:增强for循环遍历和迭代器遍历结果相同: 普通for循环遍历结果: 获取十个不重复的1到20以内的随机数: import java.util.ArrayList;import java.util.Random;public class RanTest { public static void main(String[] args) { Random random = new Random(); ArrayList 输出结果(每次都会不同): 键盘录入多个数据,以0结束,在控制台输出这些数据中的最大值 import java.util.ArrayList;import java.util.Arrays;import java.util.Scanner;public class MaxDemo { public static void main(String[] args) { Scanner sc = new Scanner(System.in); ArrayList 输出结果: 一个不包含重复元素的Collection Set集合是唯一的,并且元素的顺序是无序的集合 底层结构是哈希表(元素是链表的数组) 哈希表依赖于哈希值存储 代码举例:使用Set集合存储字符串并遍历 import java.util.HashSet;import java.util.Set;public class Test1 { public static void main(String[] args) { Set 输出结果: 存储自定义对象并遍历 import java.util.HashSet;import java.util.Set;public class Test2 { public static void main(String[] args) { Set 输出结果:这里应该不包含重复元素,但是结果包含了重复元素: 造成这样的原因需要去源码中查看add方法的实现源码: 经过查看源码发现该HashSet中的add方法与HashCode和equals方法有关,而Student类中没有重写HashCode和equals方法,因此调用的是Object类中的HashCode和equals方法,故比较的是地址值,对于不同的对象含有不同的地址值,因此无法去重。 解决办法:重写HashCode方法和equals方法即可 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && Objects.equals(name, student.name); } @Override public int hashCode() { return Objects.hash(name, age); } 输出结果: 1、底层数据结构是哈希表和链表 2、哈希表保证元素唯一 3、链表保证了元素的有序(存储和取出的顺序一致) 4、线程不安全,效率高 public class HashSet public class linkedHashSet 代码举例: import java.util.linkedHashSet;public class Test3 { public static void main(String[] args) { linkedHashSet 输出结果: 元素唯一,元素可按照某种规则进行排序 两种排序方式:自然排序 ;比较器排序 代码举例: Treeset存储Integer类型的数据并遍历: import java.util.TreeSet;public class Ttest1 { public static void main(String[] args) { TreeSet 输出结果: TreeSet存储学生对象并遍历: 按照正常的写法,当我们运行的时候,会报错: java.lang.ClassCastException 类转换异常 这是由于在创建TreeSet对象时使用的是无参构造方法,进行的是自然排序,而在这里的底层源码中有向下转型代码:Comparable<? super K> k = (Comparable<? super K>) key; 但是Student类中没有实现Comparable接口,无法向下转型 解决办法:在Student类中实现Comparable接口即可 Student1类: import java.util.Objects;public class Student1 implements Comparable 测试类: import java.util.TreeSet;public class Ttest2 { public static void main(String[] args) { TreeSet 输出结果:这里会发现是按照年龄的大小来进行排序的 需求:使用TreeSet存储学生对象并以姓名的长度来排序 学生类: import java.util.Objects;public class Student3 implements Comparable 测试类: import java.util.TreeSet;public class Ttest3 { public static void main(String[] args) { TreeSet 输出结果: 利用TreeSet创建对象时的带参数的构造方法来进行比较器排序 TreeSet(Comparator<? super E> comparator) 构造一个新的,空的数集,根据指定的比较器进行排序 这里需要实现Comparator接口 代码举例: 实现Comparator接口的实现类ComparatorImpl: import java.util.Comparator;public class ComparatorImpl implements Comparator 这里是具体的测试类 import java.util.TreeSet;public class Ttest4 { public static void main(String[] args) { ComparatorImpl comparator = new ComparatorImpl(); TreeSet 输出结果: 这里还可以使用匿名内部类进行comparator接口的实现: import java.util.Comparator;import java.util.TreeSet;public class Ttest4 { public static void main(String[] args) {// ComparatorImpl comparator = new ComparatorImpl();// TreeSet 输出结果不变 通过查看逻辑来看,这两种排序实现的其实就是按照树的中序遍历方式进行排序的 静态导入
概述:
可变参数
List集合练习
集合的嵌套遍历
Set接口
Hashset
linkedHashSet
TreeSet
自然排序
比较器排序