泛型
-
使用传统方法的问题分析
- 不能对加入到集合ArraysList中的数据类型进行约束(不安全);
- 遍历的时候需要进行类型转换,如果集合中数据量较大,对效率有影响
public class Generic01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//使用传统的方法
ArrayList arrayList = new ArrayList();
arrayList.add(new Dog("旺财",12));
arrayList.add(new Dog("小兰",13));
arrayList.add(new Dog("小天",14));
//对数据类型没有约束,可以添加Cat类
arrayList.add(new Cat("招财",8));
for (Object o: arrayList) {
Dog dog = (Dog) o;
System.out.println(dog.getName()+'-'+dog.getAge());
}
}
}
class Dog{
private String name;
private int age;
public Dog(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;
}
}
使用泛型的好处
- 编译时,检查添加元素的类型,提高了安全性
- 减少了类型转换的次数,提高效率
public class Generic02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//使用泛型
//当我们 ArrayList<Dog> 表示存放到ArrayList集合中的元素是Dog类型
//如果编译器发现添加的类型不满足要求就会报错
ArrayList<Dog> arrayList = new ArrayList<Dog>();
arrayList.add(new Dog("旺财",12));
arrayList.add(new Dog("小兰",13));
arrayList.add(new Dog("小天",14));
// arrayList.add(new Cat("招财",8));
//可以直接取出Dog类型而不需要向下转型
for (Dog dog: arrayList) {
System.out.println(dog.getName()+'-'+dog.getAge());
}
}
}
class Dog{
private String name;
private int age;
public Dog(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;
}
}
class Cat{
private String name;
private int age;
public Cat(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;
}
}
泛型介绍
什么是泛型?
- 泛型又称参数化类型,是jdk5.0出现的新特性,解决数据类型的安全性问题
- 在类声明或实例化时只要指定好需要的具体的类型即可
- java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生
ClassCastException异常 - 泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或是某个方法的返回值的类型或是参数类型。
public class Generic03 {
public static void main(String[] args) {
Person<String> person = new Person<String>("程冠希");
System.out.println(person.getClass());
}
}
//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
// 或是某个方法的返回值的类型或是参数类型。
class Person<E>{
E s;//E表示 s 的数据类型,该数据类型在定义Person对象的时候指定,即在编译期间就确定E是什么类型
public Person(E s) {//E
this.s = s;
}
public E f(){
return s;
}
}
泛型的应用实例
-
泛型的声明
interface接口{}和class类<K,V>{}
比如List,ArrayList
说明:
- 其中,T,K,V不代表值,而是表示类型
- 任意字母都可以,常用T表示,是Type的缩写
-
泛型的实例化
要在类名后面指定类型参数的值(类型)。如:
- List strList = new ArrayList();
- Iterator iterator = customers.iterator();
public class GenericExercise01 {
public static void main(String[] args) {
//1.HashSet
Set<Student> set = new HashSet<Student>();
set.add(new Student("jack",12));
set.add(new Student("jay",12));
set.add(new Student("july",12));
//使用迭代器遍历
Iterator<Student> iterator = set.iterator();
while(iterator.hasNext()){
Student student = iterator.next();
System.out.println(student);
}
HashMap<String,Student> hashMap = new HashMap<String, Student>();
hashMap.put("1",new Student("小红",12));
hashMap.put("2",new Student("小绿",12));
hashMap.put("3",new Student("小蓝",12));
System.out.println(hashMap);
Set<Map.Entry<String, Student>> entries = hashMap.entrySet();
Iterator<Map.Entry<String, Student>> iterator1 = entries.iterator();
while(iterator1.hasNext()){
Map.Entry<String, Student> next = iterator1.next();
System.out.println(next);
}
}
}
class Student{
private String name;
private int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Student(String name, int age){
super();
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;
}
}
一个小案例来实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wPKJy9EL-1665759655760)(E:\学习笔记\图片\Snipaste_2022-06-22_21-01-55.jpg)]
public class GenericExercise02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
employees.add(new Employee("tom",1243,new MyDate(2002,02,13)));
employees.add(new Employee("jack",1224,new MyDate(2002,03,14)));
employees.add(new Employee("tim",1234,new MyDate(2002,04,15)));
employees.add(new Employee("sam",1254,new MyDate(2002,05,17)));
employees.add(new Employee("sam",1254,new MyDate(2002,05,16)));
System.out.println(employees);
System.out.println("====对雇员排序=====");
employees.sort(new Comparator<Employee>(){
@Override
public int compare(Employee emp1, Employee emp2) {
if (!(emp1 instanceof Employee && emp2 instanceof Employee)){
System.out.println("类型不正确");
return 0;
}
int i = emp1.getName().compareTo(emp2.getName());
if(i!=0){
return i;
}
//下面是对birthday的比较,因此我们最好吧这个比较放在MyDate类完成
return emp1.getBirthday().compareTo(emp2.getBirthday());
}
});
System.out.println(employees);
}
}
class Employee{
private String name;
private int sal;
private MyDate birthday;
public Employee(String name, int sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSal() {
return sal;
}
public void setSal(int sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "\n Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
}
class MyDate implements Comparable<MyDate>{
private int year;
private int month;
private int day;
public MyDate( int year,int month, int day) {
this.month = month;
this.day = day;
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
@Override
public int compareTo(MyDate o) {
int yearMinus = year-o.getYear();
if (yearMinus!=0){
return yearMinus;
}
int monthMinus =month-o.getMonth();
if (monthMinus!=0){
return monthMinus;
}
int dateMinus =day-o.getDay();
if (dateMinus!=0){
return dateMinus;
}
return 0;
}
}
自定义泛型
class 类名<T,R…>{//…表示可以有多个泛型
}
注意细节
-
普通成员可以使用泛型(属性、方法)
-
使用泛型的数组,不能初始化
//因为数组在new时 不能确定T的类型,就无法在内存开空间 //java在编译期间所有的泛型信息都会被擦除掉,泛型附加的类型信息对JVM来说是不可见的,而数组是在运行时 //解决方法:使用一个泛型数组包装器,维护一个原始类型的数组, // 通过数组入口方法进行元素编译期的类型安全检测(对应返回值)和强制类型转换(对于运行时不重要),从而保证类型安全。 //T[] ts=new T[3];
-
静态方法中不能使用类的泛型
//因为静态是和类相关的,在类加载时,对象还没有创建 //所以,如果静态方法和属性使用了泛型 JVM就无法完成初始化 //static R r2; //public static void m1(M m){ // }
-
泛型的类型,是在创建对象时确定的(因为创建对象时,需要指定确定的类型)
-
如果再创建对象时,没有指定类型,默认为Object
自定义泛型接口
- 基本语法
interface 接口名 <T,R,…>{
}
-
注意细节
- 接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
- 泛型接口的类型,在继承接口或者实现接口时确定
interface IA extends IUsb<String ,Double>{ } //我们去实现IA接口时,因为IA在继承IUsb接口时,指定了U为String,R为Double //在实现IUsb接口的方法时,使用String替换U,使用Double替换R class AA implements IA{ @Override public Double get(String s) { return null; } @Override public void hi(Double aDouble) { } @Override public void run(Double r1, Double r2, String u1, String u2) { } } interface IUsb<U,R>{ int n = 10; //U name; //普通方法中,可以使用接口泛型 R get(U u); void hi(R r); void run(R r1,R r2,U u1,U u2); //在jdk8中,可以在接口中使用默认方法 default R method(U u){ return null; } }
3.没有指定类型,默认为Object
//建议直接写成IUsb<Object,Object>
class CC implements IUsb{//等价于 class CC implements IUsb<Object,Object>
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi(Object o) {
}
@Override
public void run(Object r1, Object r2, Object u1, Object u2) {
}
}
自定义泛型方法
-
基本语法
修饰符<T,R>返回类型 方法名(参数列表){
}
-
注意细节
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中
- 当泛型方法被调用时,类型会确定
- public void eat(E e){},修饰符后没有<T,R>,eat方法不是泛型方法,而是使用了泛型
public class CustomMethodGeneric {
public static void main(String[] args) {
Car car = new Car();
car.fly("宝马",100);//当调用方法时,传入参数,编译器就会确定类型
car.fly(300,300.1);
Fish<String, ArrayList> stringArrayListFish = new Fish<>();
stringArrayListFish.hello(new ArrayList<>(),11.2f);
}
}
//泛型方法,可以定义在普通类中,也可以定义在泛型类中
class Car{
public void run(){//普通方法
}
//1.<T,R>就是泛型
//2.是提供给fly用的
public <T,R> void fly(T t,R r){//泛型方法
System.out.println(t.getClass());
System.out.println(r.getClass());
}
}
class Fish<T,R>{//泛型类
public void run(){//普通方法
}
public <U,M> void eat(U u,M m){//泛型方法
}
//下面的hi方法不是泛型方法
//是hi方法使用了类声明的泛型
public void hi(T t){
}
//泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型
public <K> void hello(R r,K k){
System.out.println(r.getClass());
System.out.println(k.getClass());
}
}
泛型的继承和通配符
- 泛型不具备继承性
List list = new ArrayList ();//错误 - <?>:支持任意泛型类型
- <? extends A>:支持A类以及A类的子类,规定了泛型的上限
- <? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
public class GenericExtends {
public static void main(String[] args) {
//举例下面三个方法的使用
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AA> list3 = new ArrayList<>();
List<BB> list4 = new ArrayList<>();
List<CC> list5 = new ArrayList<>();
//<?>:支持任意泛型类型
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
// <? extends A>:表示上限可以接受AA和AA的子类
//printCollection2(list1);
//printCollection2(list2);
printCollection2(list3);
printCollection2(list4);
printCollection2(list5);
//<? super A>:表示下限可以接受AA,接收AA和AA类的父类
printCollection3(list1);
//printCollection3(list2);没关系
printCollection3(list3);
// printCollection3(list4);子类
//printCollection3(list5);子类
}
//<?>:支持任意泛型类型
public static void printCollection1(List<?>c) {
for (Object object : c) {
System.out.println(object);
}
}
// <? extends A>:支持A类以及A类的子类,规定了泛型的上限
public static void printCollection2(List<? extends AA>c) {
for (Object object : c) {
System.out.println(object);
}
}
//<? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
public static void printCollection3(List<? super AA >c) {
for (Object object : c) {
System.out.println(object);
}
}
}
JUnit
- 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中
- 如果有多个功能代码测试,就需要来回注销,切换很麻烦
- 如果可以直接运行一个方法就方便很多,并且可以给出相关信息,就好了
-
JUnit是java语言的单元测试框架
-
多数java的开发环境已经集成了JUnit作为单元测试的工具
}
}
// <? extends A>:支持A类以及A类的子类,规定了泛型的上限
public static void printCollection2(List<? extends AA>c) {
for (Object object : c) {
System.out.println(object);
}
}
//<? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
public static void printCollection3(List<? super AA >c) {
for (Object object : c) {
System.out.println(object);
}
}
}
## JUnit
1. 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中
2. 如果有多个功能代码测试,就需要来回注销,切换很麻烦
3. 如果可以直接运行一个方法就方便很多,并且可以给出相关信息,就好了
- JUnit是java语言的单元测试框架
- 多数java的开发环境已经集成了JUnit作为单元测试的工具