泛型
- 引出泛型
- 泛型的介绍及使用
- 泛型的使用细节
- 泛型课堂练习题
- 自定义泛型
- Junit
引出泛型
-
现在有这样的一个需求
- 编写一个程序,在ArrayList中,添加三个Dog对象
- Dog对象含有name和age,并输出name和age(要求使用getXxx方法)
用传统的方法编码,代码演示如下
创建Dog类
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; } }
创建测试类Generic01
package com.generic; import java.util.ArrayList; public class Generic01 { public static void main(String[] args) { ArrayList arrayList = new ArrayList(); arrayList.add(new Dog("旺财",10)); arrayList.add(new Dog("小黄",8)); arrayList.add(new Dog("富贵",5)); //使用增强for遍历 for (Object o : arrayList) { //向下转型 Dog dog = (Dog) o; System.out.println(dog.getName()+"--"+dog.getAge()); } } }
用传统方式即可很快完成需求。
假如程序员不小心把一个Cat对象添加到集合里面,会发生什么呢?
arrayList.add(new Cat("小黑",3));
编译运行,发现程序报错
可以发现,倘若我们使用传统的方式来解决需求时,程序会有两个缺点
- 不能对加入到集合ArrayList中的数据类型进行约束(不安全,会发生上面那样的编译错误,而且编译器编译时识别不了)
- 遍历的时候需要进行类型转换,如果集合中的数据量较大时会对效率有影响
而我们使用泛型便可以很好的解决上述两个缺点!
把测试类Generic01中集合的定义改为
ArrayList<Dog> arrayList = new ArrayList<Dog>();
便是使用了泛型,可以规范ArrayList集合中元素的取值。而且当我们使用泛型之后,如果编译器发现添加的类型不满足集合规定的要求,就会报错,可以避免发生异常。
同时,我们遍历集合的时候,不再需要先取出Object类型再进行向下转型了,可以直接获取到Dog类型的数据。
//使用增强for遍历 for (Dog dog : arrayList) { System.out.println(dog.getName()+"--"+dog.getAge()); }
使用泛型的好处
- 编译时,编译器会检查添加元素的类型,提高了安全性
- 减少了类型转换的次数,提高效率
泛型的介绍及语法与使用
-
泛型的介绍(泛型是可以代表一种数据类型的)
-
泛型又称参数化类型,是jdk5.0出现的新特性,解决数据类型的安全性问题
-
在类声明或实例化时只要指定好需要的具体类型即可
-
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时使代码更加简洁、健壮
-
泛型的作用是:可以在类声明时通过一个标识表示类中的某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型(看下面代码理解)。
在编程时,我们新建一个类的属性的时候,有时候可能会不确定该属性的类型,让其由用户来决定,这时候我们便可以使用泛型
class Person<E>{ //E表示数据类型 E s; //E也可以是参数类型 public Person(E s) { this.s = s; } //E做返回类型 public E getS() { return s; } }
我们在Generic02中,可以根据所需来声明E的类型
package com.generic; public class Generic02 { public static void main(String[] args) { //当我们要s为String类型的时候 Person<String> stringPerson = new Person<String>("jack"); //当我们要s为int类型的时候 Person<Integer> person = new Person<Integer>(1); } }
E的数据类型在定义Person时指定,即在编译期间就可以确定E的类型
-
-
泛型的语法
interface 接口 <E>{ } class 类<K,V,E>{ }
其中,K,T,V不代表值,而是表示类型
任意字母都可以。常用T表示
-
泛型的应用实例
- 创建三个学生对象
- 把学生对象放到HashMap中,而且学生名字为key,学生对象为value
- 用两种方式遍历学生对象
代码演示
创建Student类
class Student{ private String name; private int age; public Student(String name,int age) { this.name = name; this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
创建测试类GenericExercise
package com.generic; import java.util.*; public class GenericExercise { public static void main(String[] args) { Student jack = new Student("jack", 10); Student tom = new Student("tom", 13); Student mary = new Student("mary", 15); HashMap<String,Student> hashMap = new HashMap<String,Student>(); hashMap.put(jack.getName(),jack); hashMap.put(tom.getName(),tom); hashMap.put(mary.getName(),mary); Set<String> strings = hashMap.keySet(); Iterator<String> iterator = strings.iterator(); while (iterator.hasNext()) { String next = iterator.next(); System.out.println(next+":"+hashMap.get(next)); } System.out.println("=============="); Set<Map.Entry<String, Student>> entries = hashMap.entrySet(); for (Map.Entry<String, Student> entry : entries) { System.out.println(entry.getKey()+"="+entry.getValue()); } } }
运行结果
泛型的使用细节
-
interface List<T,V>等泛型的声明中,其中T,V只能是引用类型,不能为基本数据类型!
//正确的定义 List <Integer> list1 = new ArrayList<Integer>(); //错误的定义 List <int> list2 = new ArrayList<int>();
-
在给泛型指定的具体类型后,可以传入该类型或该子类类型(继承)。
class Detail{ public static void main(String[] args){ Pig<A> piga = new Pig<A>(new A()); Pig<A> piga = new Pig<A>(new B()); } } class A{ } class B extends A{ } class Pig<E>{ E e; publiu Pig(E e){ this.e = e; } }
-
泛型的使用方式
//传统写法 List <Integer> list1 = new ArrayList<Integer>(); //实际开发中,通常使用简写。因为编译器会进行类型推断,所以后面的可以不写 List <Integer> list1 = new ArrayList<>();//推荐这种写法!
-
如果是这样写 List list1 = new ArrayList(); 也会使用泛型,默认是Object
List list1 = new ArrayList(); //等价于 List <Object> list1 = new ArrayList<>();
泛型课堂练习题
定义Employee类
-
该类包含:private成员变量 name,sal,birthday,其中birthday为Mydate类的对象;
-
为每一个属性定义getter,setter方法;
-
重写toString方法输出name,sal,birthday
-
MyDate类包含:private成员变量month,day,year;并为每一个属性定义getter,setter方法;
-
创建Employee类的三个对象,并把这些对象放入ArrayList集合中(ArrayList需要使用泛型来定义),对集合中的元素进行排序,并遍历输出;
-
排序方式:调用ArrayList的sort方法,传入Comparator对象,先按照name排序,如果name相同,则按照生日日期的先后排序。
创建MyDate类
package com.generic; class MyDate implements Comparable<MyDate>{ private int year; private int month; private int day; public MyDate(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public int getYear() { return year; } public void setYear(int year) { 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; } @Override public String toString() { return "MyDate{" + "year=" + year + ", month=" + month + ", day=" + day + '}'; } //实现Comparable的方法,让Birthday的比较封装在MyDate类中 //封装后代码的维护性和可复用行大大提高! @Override public int compareTo(MyDate o) { //按照year的大小进行排序 int yearMinus = this.getYear() - o.getYear(); //如果year不相同,返回比较值 if (yearMinus != 0){ return yearMinus; } //按照month的大小进行排序 int monthMinus = this.getMonth() - o.getMonth(); if (monthMinus != 0){ return monthMinus; } //如果year与month相同,按照day的大小进行排序 return this.getDay() - o.getDay(); } }
创建Employee类
package com.generic; class Employee{ private String name; private double sal; private MyDate birthday; public Employee(String name, double sal, MyDate birthday) { this.name = name; this.sal = sal; this.birthday = birthday; } public double getSal() { return sal; } public void setSal(double sal) { this.sal = sal; } public MyDate getBirthday() { return birthday; } public void setBirthday(MyDate birthday) { this.birthday = birthday; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "\nEmployee{" + "name='" + name + '\'' + ", sal=" + sal + ", birthday=" + birthday + '}'; } }
创建测试类GenericExercise02
package com.generic; import java.util.ArrayList; import java.util.Comparator; public class GenericExercise02 { public static void main(String[] args) { Employee jack = new Employee("jack", 1000, new MyDate(2000, 03, 20)); Employee tom = new Employee("tom", 2000, new MyDate(1999, 02, 1)); Employee mary = new Employee("jack", 3000, new MyDate(2000, 02, 19)); ArrayList<Employee> employees = new ArrayList<>(); employees.add(jack); employees.add(tom); employees.add(mary); 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; } //按照name排序,字母的先后顺序 int i = emp1.getName().compareTo(emp2.getName()); //i不等于0说明name不相同,即通过名字即可完成排序,所以返回i; if(i != 0){ return i; } //如果name长度相同,便按照birthday进行排序 return emp1.getBirthday().compareTo(emp2.getBirthday()); } }); System.out.println(employees); } }
运行结果
自定义泛型
-
自定义泛型类
-
基本语法
class 类名 <T,V...>{ }
-
注意细节
- 普通成员可以使用泛型(属性、方法)
- 使用泛型的数组,不能初始化(因为数组在创建的时候,不能确定泛型的类型,无法在内存开辟空间)
- 静态方法中不能使用类的泛型(因为static是与类相关的,在类加载时,对象还没有创建,而泛型是在对象创建时才产生的。)
- 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
- 如果在创建对象时,没有指定类型,默认为Object
-
-
自定义泛型接口
-
基本语法
interface 接口名 <T,E,R...>{ }
-
注意细节
-
在接口中,静态成员也不能使用泛型
-
泛型接口的类型,在继承接口或者实现接口时确定
interface IA<T,V>{ T get(V v); } //继承接口时确定泛型的类型 //T->Double,V->String interface IB extends IA<Double,String>{ } //当我们区实现IB接口时,因为IB在继承IA时指定了T为Double,V为String //所以在实现IA的方法时,会自动替换掉T,V class IBImp implements IB{ @Override public Double get(String v){ } }
-
没有指定类型,默认为Object
-
在jdk8中,default方法也能使用泛型
-
-
-
自定义泛型方法
-
基本语法
修饰符 <T,R>返回类型 方法名(参数列表){ }
-
注意细节
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中
- 当泛型方法被调用时,类型会确定
- public void eat(E e){},修饰符后面没有<T,R…> eat方法不是泛型方法,而是使用了泛型
- 泛型方法可以使用类声明的泛型,也可以使用自己声明的泛型
-
-
泛型的继承和通配符
-
泛型不具备继承性
List <Object> list = new ArrayList<String>();//系统会报错
-
JUnit
- 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中
- 如果有多个功能代码测试,就需要来回注销,切换很麻烦
- 而使用JUnit便可以很好的解决上述问题
-
基本介绍
- JUnit是一个Java语言的单元测试框架
- 多数Java的开发环境都已经继承了JUnit作为单元测试的工具
-
使用
在需要测试的文件上面增加一个@Test即可