泛型机制
泛型:是JDK1.5之后引入的一个概念,它的存在是指将类型明确工作推迟到创建对象或调用方法时才明确;
语法格式:<数据类型,数据类型,……>
泛型可以用在类、接口、方法上
泛型的好处:1、避免了向下转型;2、将问题提前到编译期 ;3、提高代码的扩展性
泛型的特点:泛型只在编译期有效,在运行期间就擦除了;
泛型集合
集合都要加入泛型,来限制存储数据类型, 使用get方法
取集合元素时返回也是泛型定义的数据类型,而不是Object对象。
ArrayList<Student> list = new ArrayList<>();
list.add(new Student("scq",12));
list.add(new Student("张家辉", 23));
list.add(new Student("张震岳", 23));
list.add(new Student("张子豪", 23));
//定义一个泛型为Student的迭代器,获取的元素类型为Student类型,
//无需向下转型
Iterator<Student> iterator = list.iterator();
while (iterator.hasNext()){
//定义一个泛型为Student的迭代器,获取的元素类型为Student类型,无需向下转型
Student next = iterator.next();
}
如果集合想要存储各种类型使用Object或者不定义泛型
泛型类
提高扩展性又不需要向下转型:将一个类设计为泛型类
定义格式: public class 类名<泛型类型1,…>
注意事项: 泛型类型必须是引用类型
class Myclass<T,R>{
//定义一个不确定类型通过方法确定类型的私有变量
private T t;
private R r;
// 定义一个不确定类型调用方法才确定返回值类型的方法
public R getR(){return r;}
public void setR(R r){this.r=r;}
public T getT(){return t;}
public void setT(T t){this.t=t;}
}
Myclass<String,Integer> stringIntegerMyclass = new Myclass<>();
stringIntegerMyclass.setT("aaa");
String t = stringIntegerMyclass.getT();
stringIntegerMyclass.setR(100);
Integer r = stringIntegerMyclass.getR();
泛型方法
不知道成员变量类型,使用泛型代替;当使用者创建对象时明确成员变量的具体类型,get、set、方法都会返回已经确定的类型,也不需要向下转型
定义格式: public <泛型类型> 返回类型 方法名(泛型类型 变量名)
test3 t3 = new test3();
//可以定义各种类型数据
t3.show(100);
t3.show("aaa");
//定义泛型方法
public<T> void show(T num) {
System.out.println(num);
}
}
泛型接口
定义格式: public interface 接口名<泛型类型>
//定义泛型接口,子类实现接口时,可以明确接口上的泛型具体是什么类型
interface Myinterface<A,B>{
//定义一个传入参数类型与接口泛型相同的抽象方法
public abstract void test(A a);
public abstract B test1(B b);
}
//子类实现接口时,可以明确接口上的泛型具体是什么类型
class MyA implements Myinterface<Integer,String>{
@Override
public void test(Integer integer) {
System.out.println("Integer");
}
@Override
public String test1(String s) {
return s;
}
}
//采用匿名 内部类的这种方式,在创建接口的子类对象时,可以明确接口上的泛型具体是什么类型。
new Myinterface<Student,String>(){
@Override
public void test(Student student) {
}
@Override
public String test1(String s) {
return s;
}
};
泛型高级之通配符
泛型通配符<?>: 任意类型,如果没有明确,那么就是Object以及任意的Java类了
? extends E: 向下限定,E及其子类
? super E: 向上限定,E及其父类
//泛型通配符,定义一个父类2个子类
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}
ArrayList<?> objects = new ArrayList<Dog>();
ArrayList<?> objects1 = new ArrayList<Cat>();
ArrayList<?> objects2 = new ArrayList<Animal>();
//向上限定 类型为Cat的父类:Cat、Animal或者Object
ArrayList<? super Cat> list1 = new ArrayList<Animal>();
//向上限定,Animal的父类:Animal或者Object
ArrayList<? super Animal> list3 = new ArrayList<Animal>();
ArrayList<? super Animal> list4 = new ArrayList<Object>();
//向下限定 类型为Animal或子类
ArrayList<? extends Animal> list5 = new ArrayList<Cat>();
ArrayList<? extends Animal> list6 = new ArrayList<Animal>();
增强for
新式for循环用于遍历数组和集合、底层用于迭代器实现,因此如果在迭代中添加删除元素,会发生并发修改异常;因此新式for循环遍历途中不可以添加删除元素,只可以遍历,修改元素也可以,只要不改变长度;如果想要添加删除元素使用老式for循环
格式:
for(元素数据类型 变量 : 数组或者Collection集合) {
使用变量即可,该变量就是元素
}
int[] arr={2,3,4,5};
//for(容器中的元素的数据类型 变量名:容器名)
for (int i : arr) {
System.out.println(i);
}
可变参数
定义方法的时候不知道该定义多少个参数时使用。
格式: 修饰符 返回值类型 方法名(数据类型… 变量名){}
注意事项:可变参数用于接收多个同类型的参数,因此如果一个方法有多个参数,可变参数必须放在最后一位用于接收剩余所有的参数
- a: 这里的变量其实是一个数组
- b: 如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个
int sum=add(1,2,3);
//可变参数的方法
private static int add(int a,int...b) {
int sum=a;
for (int i : b) {
sum+=i;
}
return sum;
}
Arrays工具类的asList()
将数组转换成集合
asList():
- 基本数据类型数组转换为集合存入的是地址值;传入引用数据类型数组转换集合存入的是每一个元素;传入多个引用类型数组转换集合存入多个引用类型数组的地址值。
- 此方法得到的集合是Arrays的内部类,没有增删方法不支持这些操作,不能改变长度,只可以修改元素。
//把一个数组转换集合。
int[] arr1={20,30,40};
//传的是一个基本类型的数组,他是把这个数组对象,放到集合中
List<int[]> ints = Arrays.asList(arr1);
//ints是arr1的地址值
System.out.println(ints.get(0)[0]);//20
Integer[] arr2 = {20, 30, 40};
//我传入的是一个包装类型的数组,他是吧数组中的元素,取出来放到集合
List<Integer> integers = Arrays.asList(arr2);
//integers就是一个集合 打印出这个集合
System.out.println(integers);
Integer[] arr3 = {20, 30, 40};
Integer[] arr4 = {20, 30, 400};
//如果你传入多个包装类型的数组,那么他是把多个数组对象,放到集合中。
List<Integer[]> integers1 = Arrays.asList(arr3, arr4);
//获取arr3的第一个元素
Integer integer = integers1.get(0)[0];//20
//也可以这样得到一个集合。
// 注意:通过Arrays.asList() 这种方式的得到集合,不能再次改变集合的长度,也就是说,你不能增删元素。
List<Integer> integers2 = Arrays.asList(20, 30, 40);
System.out.println(integers2);
需求:
我们班有学生,每一个学生是不是一个对象。所以我们可以使用一个集合表示我们班级的学生。ArrayList
但是呢,我们是不是还有班级,每个班级是不是也是一个ArrayList。
而我现在有多个ArrayList。也要用集合存储,怎么办呢 ?
- 集合嵌套之ArrayList嵌套ArrayList
Student s1 = new Student("张三", 23);
Student s2 = new Student("李四", 23);
Student s3 = new Student("王五", 23);
Student s4 = new Student("赵六", 23);
Student s5 = new Student("田七", 23);
Student s6 = new Student("刘八", 23);
ArrayList<Student> Java_class = new ArrayList<>();
Java_class.add(s1);
Java_class.add(s2);
ArrayList<Student> Python_class = new ArrayList<>();
Python_class.add(s3);
Python_class.add(s4);
ArrayList<ArrayList<Student>> school = new ArrayList<>();
school.add(Java_class);
school.add(Python_class);
set集合
元素唯一,不允许重复。
HashSet
- 底层数据结构是哈希表(JDK1.7 数组+链表 JDK1.8 优化 数组+链表+红黑树)
- 元素无序(存取元素的顺序不一致)且唯一(元素不允许重复)
- HashSet集合保证元素的唯一性,是靠元素重写 hashCode和equals() 方法来保证的,如果元素不重写,则无法保证元素的 唯一性。
原因如下:-
首先根据对象的hashcode算出索引值,确定元素放在哈希表的位置;由于new对象的地址值不同,hashcode值不同在哈希表的位置也不同,如果将hashcode值固定为一个数,那么所有对象计算出的在哈希表中的位置是相同的,但是对象的地址值不同,就会在哈希表的同一个位置形成链表结构,那么会大量调用equals方法判断两个元素地址值是否相等,由于new出来的对象地址值都不相同,因此即使是相同的成员变量的对象,均是重复存储;
-
解决方案:通过成员变量值来重写hashcode方法,那么不同对象的成员变量值不同重写的hashcode计算出的在哈希表的位置就不同,就不会形成链式结构;如果不同对象计算出索引值相等,就再次调用重写的equals方法判断成员变量是否相等,不相等就存储;基本上通过第一层重写的hashcode方法就可以区分开不同的对象,无需调用equals方法,只有少机率会出现成员变量值不同单计算的索引值相等的情况,此时才需要调用重写的equals方法继续判断两个对象的成员变量值是否相等。
-
目的: 尽量减少链式结构与equals调用的次数;JDK1.8将链表数超过80会转为红黑树来判断,减少判断次数
-
HashSet<Student> set1 = new HashSet<>();
set1.add(new Student("张三",12));
set1.add(new Student("scq",12));
set1.add(new Student("scq",12));
set1.add(new Student("李四", 24));
set1.add(new Student("王五", 25));
for (Student student : set1) {
System.out.println(student);
}
/* Student{name='李四', age=24}
Student{name='scq', age=12}
Student{name='张三', age=12}
Student{name='王五', age=25}*/
student类如下:
import java.util.Objects;
public class Student
{
private String name;
private int 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;
}
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@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);
}
}
Hashset对于集合的去重
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
list.add(100);
list.add(100);
list.add(100);
list.add(100);
HashSet<Integer> integers = new HashSet<>(list);
System.out.println(integers);//[100]
LinkedHashSet
元素有序,且唯一,底层数据结构是链表+哈希表 链表保证元素有序,哈希表保证元素唯一
LinkedHashSet<Integer> integers1 = new LinkedHashSet<>();
integers1.add(100);
integers1.add(200);
integers1.add(200);
integers1.add(300);
for (Integer integer : integers1) {
System.out.println(integer);
}
/* 100
200
300*/
TreeSet
TreeSet 底层数据结构是二叉树,元素有序唯一。
二叉树存储数据保证元素唯一性原理图:
Treeset对于储Integer类型的元素并遍历
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(20);
treeSet.add(18);
treeSet.add(23);
treeSet.add(22);
treeSet.add(17);
treeSet.add(24);
for (Integer integer : treeSet) {
System.out.println(integer);
}
/*17
18
20
22
23
24 */
给TreeSet类的集合存学生对象的时候,一存就会报错,为什么?
前面说这个集合可以根据你的需要进行排序,但是它在存储学生对象的时候,没有办法进行比较;
排序分为自然排序和比较器排序:因此如果存储学生对象时需要重写排序方法
1、自然排序。如果new一个TreeSet对象的时候采用空参构造,就默认为是自然排序。你给集合当中添加
Integer
类型的数据时,就不会报错,这是因为Integer类当中实现了Comparable接口,重写了这个接口中的compare()。因此自定义的Student类如果想要添加到树集当中,就必须实现接口Comparable,并重写compare()。
2、比较器排序。采用有参构造,你给这个TreeSet对象的构造方法中传递一个比较器,实现接口Comparator,并重写compare()。
compare()的返回值代表元素应该添加到哪,后面添加的元素与根节点相比较,为0说明集合当中存在同样的对象,就存不进去了;因此如果你在代码当中将compare()返回值写死为0,就会出现一种现象,集合中只存进去根节点元素,其余都存不进去。为正数添加到红黑树的左边,为负数添加到红黑树的右边;如果compare()的返回值写成任意正负数,全部的元素都存储在了一个数组当中。
- 自然排序
学生类继承 Comparable接口并重写 compareTo 方法:
public class Student implements Comparable<Student> {
@Override
public int compareTo(Student s) {
/*按照姓名的长度来排序*/
//先比较姓名长度是否相等,返回正负数或0 排序
int num = this.name.length()-s.name.length();
//如果姓名长度相等判断姓名内容是否相等 ,姓名长度不相等说明是不同的对象直接返回num值给num2
int num2=num==0?this.name.compareTo(s.name):num;
//如果姓名长度和内容都相等判断年龄是否相等,否则返回num2
int num3=num2==0?this.age-s.age:num2;
return num3;
//根据年龄大小来排序
/*int num=this.age-s.age;
//如果年龄相同,并不能说明他们是同一个对象,还得比较姓名是否相同。
//compareTo返回一个数值 来作为二叉树的位置
int num2=num==0?this.name.compareTo(s.name):num;
return num2;*/
}
//按照学生的年龄大小来排序
TreeSet<Student> treeSet2 = new TreeSet<>();
treeSet2.add(new Student("张庭aaaaaadfdfdfasdfasdf",23));
treeSet2.add(new Student("张庭2aaa", 23));
treeSet2.add(new Student("张柏芝aaa", 18));
treeSet2.add(new Student("张惠妹aaa", 20));
treeSet2.add(new Student("张惠妹", 20));
treeSet2.add(new Student("张惠妹", 22));
treeSet2.add(new Student("张余生", 20));
treeSet2.add(new Student("张曼玉aaaaaaa", 24));
treeSet2.add(new Student("林青霞aaaaaaaaaa", 26));
for (Student student : treeSet2) {
System.out.println(student.getName()+"=="+student.getAge());
}
/*张余生==20
张惠妹==20
张惠妹==22
张庭2aaa==23
张惠妹aaa==20
张柏芝aaa==18
张曼玉aaaaaaa==24
林青霞aaaaaaaaaa==26
张庭aaaaaadfdfdfasdfasdf==23
*/
- 比较器排序
采用有参构造,给这个TreeSet对象的构造方法中传递一个比较器,实现接口Comparator,并重写compare()。
class MyCompareator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
//安装年龄大小 来排序
int num = s1.getAge() - s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}
}
TreeSet<Student> treeSet1 = new TreeSet<>(new MyCompareator());
treeSet1.add(new Student("张庭aaaaaadfdfdfasdfasdf", 23));
treeSet1.add(new Student("张庭2aaa", 23));
treeSet1.add(new Student("张柏芝aaa", 18));
treeSet1.add(new Student("张惠妹aaa", 20));
treeSet1.add(new Student("张惠妹", 20));
treeSet1.add(new Student("张惠妹", 22));
treeSet1.add(new Student("张余生", 20));
treeSet1.add(new Student("张曼玉aaaaaaa", 24));
treeSet1.add(new Student("林青霞aaaaaaaaaa", 26));
for (Student student : treeSet1) {
System.out.println(student.getName() + "==" + student.getAge());
}
//方法二: 使用匿名内部类来传参
/* TreeSet(Comparator < ? super E > comparator)
构造一个新的空 TreeSet,它根据指定比较器进行排序。
Comparator 比较器*/
TreeSet<Student> treeSet3= new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//安装年龄大小 来排序
int num = s1.getAge() - s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}
});
不光是TreeSet集合有比较器这一说,其它集合也会用到,比如ArrayList里面的sort(),就用到了;
//使用比较器对于Integer类型的ArrayList集合进行排序
ArrayList<Integer> integers2 = new ArrayList<>();
integers2.add(2);
integers2.add(23);
integers2.add(24);
integers2.add(25);
integers2.add(26);
integers2.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
System.out.println(integers2);//[2, 23, 24, 25, 26]
数组的比较器排序:
Integer[] arr={10,2,3,4};
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
System.out.println(Arrays.toString(arr));//[2, 3, 4, 10]