泛型
案例引出泛型
按要求写出代码:
在ArrayList中添加3个Dog对象,Dog对象有name和age两个属性,且输出name和age
public class test1 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Dog(10,"小黑"));
list.add(new Dog(3,"小白"));
list.add(new Dog(7,"小黄"));
for (Object o: list) {
Dog d = (Dog) o;
System.out.println(d);
}
}
}
class Dog{
public Dog(int age, String name) {
this.age = age;
this.name = name;
}
int age;
String name;
@Override
public String toString() {
return "Dog{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
以上是正常情况,那么在实际开发中很多时候可能不是那么的顺利,比如程序员不小心放了只猫Cat进去,那么运行到增强for的时候就会报类型转换异常。这样的话就会引出两个问题
1:无法对加入到集合的数据进行约束,不安全
2:遍历的时候,需要进行类型转换,如果数据量很大,就会拉低效率
泛型的定义
泛型是 Java SE5 出现的新特性,泛型的本质是类型参数化或参数化类型,在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。
通俗的说:一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,得借助Object,但是如果要用到方法还得向下转型,这种刻板的限制对代码的束缚就会很大。
泛型的作用:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型,可以有效避免ClassCastException
使用泛型解决开头问题
泛型的简单使用,用符号表示在集合的后面,可以限制加入集合的元素类型只能是x类型或是它的子类
ArrayList < Dog > list = new ArrayList< Dog >();
public class test1 {
public static void main(String[] args) {
ArrayList<Dog> list = new ArrayList<Dog>();
list.add(new Dog(10,"小黑"));
list.add(new Dog(3,"小白"));
list.add(new Dog(7,"小黄"));
// list.add(new Cat());
for (Dog d:list) {
System.out.println(d);
}
}
}
这样就可以约束加入集合的类型,且也不用向下转型了。解决了开头的两个问题
了解泛型
泛(广泛)型(类型),从名字就可以大概知道,泛型表示广泛的类型的意思,有点类似于Object,有点包容万象的意思,只不过泛型不用向下转型。
泛型的声明一般用一个标识表示类中的某个属性,方法返回值或者是参数类型。
一般都会使用< E >来表示泛型(也可以是< C >或者其他字母,但是不能是类名,否则就是直接指定是哪个类了),泛型并不指某个固定的类型,它可以是任何类型
思想解释:
例如:int a = 1,此时a就是1,
a = 2,此时a又变成了2.
泛型就是把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
代码说明:
public class test2 {
public static void main(String[] args) {
Person<String> p = new Person<String>("name");
System.out.println(p.s.getClass());
Person<Integer> i = new Person<Integer>(2);
System.out.println(i.s.getClass());
}
}
class Person<E>{
E s;
public Person(E s) {
this.s = s;
}
public E f(){
return s;
}
}
运行结果:
class java.lang.String
class java.lang.Integer
可以看到,的具体类型是定义的时候决定的,比较类似于形参的思想。代表任何类型,在具体定义和使用时才决定具体是什么类型。同样的一旦确定了是什么类型,那就代表着是那个类型,就不能再当作是任何类型来使用了。
泛型的语法
泛型的声明
interfece接口{}和class类<K,V>{}
其中T,K,V不代表值,而是表示类型,任何字母都可以,常用T表示,是Type的缩写
泛型的实例化
List< String > list = new ArrayList< String >();
在接口和类名后面指定泛型的类型,例如上面就表示,此集合只能加入String类型的
iterator< Student >iterator = set.iterator();
表示迭代器接收的类型只能是Student
泛型的实际使用
创建3个学生对象,分别放到HashMap和HashSet中,HashSet只放学生对象即可
HashMap放String name和学生对象,使用两种方式遍历
public class test3 {
public static void main(String[] args) {
HashSet<Student> hashSet = new HashSet<Student>();
Student s1 = new Student("小明",11);
Student s2 = new Student("小红",12);
Student s3 = new Student("小王",18);
hashSet.add(s1);
hashSet.add(s2);
hashSet.add(s3);
HashMap<String,Student> hashMap = new HashMap<String,Student>();
hashMap.put(s1.name,s1);
hashMap.put(s2.name,s2);
hashMap.put(s3.name,s3);
Iterator<Student> iterator = hashSet.iterator();
while (iterator.hasNext()) {
Student next = iterator.next();
System.out.println(next);
}
Set<Map.Entry<String,Student>>set = hashMap.entrySet();
Iterator<Map.Entry<String,Student>> iterator2 = set.iterator();
while (iterator2.hasNext()) {
Map.Entry<String, Student> next = iterator2.next();
System.out.println(next.getKey()+"--"+next.getValue());
}
}
}
class Student{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
根据实际定义泛型的类型去debug替换方法中的<K,V>即可搞明白。
泛型的使用注意事项
-
在指定泛型类型的时候只能放引用类型,不能放基本类型。
例如 Person< Integer > = Person(),这是可以的, Person< int> = Person(),,这是错误的 -
在指定泛型类型后,传入的类型只能是指定的类型,或者是他的子类型
-
泛型使用格式:
Person< Integer > = Person< Integer >();
Person< Integer > = Person<>();
以上两者都是可以的,但是建议第二种写法,编译器会自动识别右边也是同样的类型 -
如果定义类的时候写了泛型,但是实例化的时候没有添加泛型,那么就等同于指定了< Object >
例如:
//mian方法
Pig p = new Pig();
class Pig< E >{
}
Pig p = new Pig();就等于Pig p< Object > = new Pig< Object >();
泛型小练习
定义Employee类
1)该类包含,private成员变量name,sal,birthday,其中birthday为MyDate类的对象
2)为每个属性定义get,set方法
3)重写toString方法输出三个变量
4)MyDate类包含private成员变量month,day,year并且为每个属性定义get,set方法
5)创建Employee类的3个对象,并且将这三个对象放入ArrayList集合中(使用泛型定义),对集合中的元素进行排序,遍历输出
排序方式:调用ArrayList的sort方法,传入Comparator对象(使用泛型),按照name排序,如果name相同,则按生日的日期先后排序
public class test {
public static void main(String[] args) {
ArrayList<Employee> arrayList = new ArrayList();
Employee e = new Employee();
arrayList.add(new Employee("艾伦",13000,e.new MyDate(5,6,2000)));
arrayList.add(new Employee("黄戈",7000,e.new MyDate(7,3,2008)));
arrayList.add(new Employee("六子",23000,e.new MyDate(25,7,2000)));
arrayList.sort(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
if (o1.getName().length()!=o2.getName().length()){
return o1.getName().length()-o2.getName().length();
}
return o1.getBirthday().compareTo(o2.getBirthday());
}
});
System.out.println(arrayList);
}
}
class Employee{
private String name;
private double sal;
private MyDate birthday;
public Employee() {
}
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
@Override
public String toString() {
return '\n' +"Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
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;
}
class MyDate implements Comparable<MyDate>{//内部类
private int month;
private int day;
private int year;
public MyDate(int day, int month, int year) {
this.month = month;
this.day = day;
this.year = year;
}
@Override
public int compareTo(MyDate o) {
if (this.year!=o.getYear()){
return year-o.getYear();
}
if (this.month!=o.getMonth()){
return month-o.getMonth();
}
if (this.day!=o.getDay()){
return day-o.getDay();
}
return 0;
}
@Override
public String toString() {
return year+"年"+month+"月"+day+"日";
}
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;
}
}
}
自定义泛型
当在定义类或者接口时,在后面加上了泛型,那么这个类就是自定义泛型
自定义泛型类
基本语法
class 类名< T ,R,E …>{
成员
}
注意事项
- 普通成员可以使用泛型(属性,方法)
- 使用泛型定义的数组,不能初始化
- 静态方法中不能使用类的泛型
- 泛型类的类型,是在实例化时确定的
- 如果在实例化时没有指定泛型的类型,那么默认就是Object类型
自定义泛型类使用
public class test2 {
Txt <String,Double,Integer> ttt = new Txt<>("你",23.4,2);
//此时泛型就被指定了类型
}
class Txt <R,T,Y>{
R r;//泛型定义属性
T t;
Y y;
public Txt(R r, T t, Y y) {//构造方法使用类的泛型
this.r = r;
this.t = t;
this.y = y;
}
public T MIN(R r){//方法使用类的泛型
return t;
}
//public static R Dax(){T t}{}//静态方法不能使用类的泛型
}
为什么静态方法不能使用类的泛型
首先一个实例对象创建的过程是 类加载 ----- 静态方法加载 -------创建对象
而泛型的指定类型是在创建对象的时候才指定的,静态方法在此之前就已经加载了,所以它不知道你要的是什么类型,不知道如何加载了,所以不允许在静态方法中使用类的泛型
自定义泛型接口
基本语法
interface 接口名< T ,R >{
}
注意细节
- 接口中,静态成员 也不能使用泛型进行定义(与泛型类的原理是一样的),因为接口中的所有属性默认都是静态的,所以不知直接用泛型修饰属性
- 泛型接口的类型,在继承接口或者实现接口时确定
public class test3 {
}
class Run implements AI{
@Override
public String mi() {
return AI.super.mi();
}
}
interface AI extends Iusb<String,Double>{}
interface Iusb <R,t>{
default R mi(){
return null;
}
}
- 当继承接口或者实现接口时没有指定类型,那么默认就是指定了Object类型
public class test3 {
}
class Run implements AI{
@Override
public Object mi() {
return AI.super.mi();
}
}
interface AI extends Iusb{}//等同于interface AI extends Iusb<Object,Object>
interface Iusb <R,t>{
default R mi(){
return null;
}
}
自定义泛型方法
基本语法
修饰符 < T , R… > 返回类型 方法名(参数列表){}
注意细节
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中
class A <R,E>{
public <F,G> void m(F f){}
}
class B{
public <F,G> void m(F f){}
}
- 当泛型方法被调用时,泛型的类型才会确定
就跟泛型类一样,定义泛型只是给个模板,具体是什么类型是在使用时确定的,如果是作为形参使用,那么在使用泛型方法时编译器就会自动确认是什么类型,如果是方法体中使用就得跟泛型类一样写出来是什么类型
public class test3 {
public static void main(String[] args) {
B b = new B();
b.m("sr",12);
System.out.println("==========");
b.m(21.2,21.33f);
}
}
class B{
public <F,G> void m(F f,G g){
System.out.println(f.getClass());
System.out.println(g.getClass());
}
}
结果:
class java.lang.String
class java.lang.Integer
==========
class java.lang.Double
class java.lang.Float
- 如果修饰符后面没有< T , R >那么这个方法就不是泛型方法,就算形参列表使用了类的泛型,也只是使用了泛型而已。
class B<E>{
public void xx(E e){}//普通方法,非泛型方法
public <F,G> void m(F f,G g){//泛型方法
System.out.println(f.getClass());
System.out.println(g.getClass());
}
}
只有方法自己定义了泛型才是泛型方法
- 泛型方法不仅可以使用自己的泛型,也可以使用类的泛型
泛型其实就是一个模板,为了给类或者方法使用的,类的泛型是提供给整个类使用的,方法的泛型是只提供给自己使用的。
所以泛型方法可以使用类的泛型,而类却不能使用方法的泛型。
public class test3 {
public static void main(String[] args) {
B<Double> b = new B();//定义的时候指定
b.m("String",2.11);
B b2 = new B();
b2.m("String","22");//使用方法的时候指定
}
}
class B<E>{
public <F,G> void m(F f,E e){//泛型方法
System.out.println(f.getClass());
System.out.println(e.getClass());
}
}
class java.lang.String
class java.lang.Double
class java.lang.String
class java.lang.String
上面可以看到,如果泛型方法使用了类的泛型,而类的泛型在实例化时就定义了,那么泛型方法就只能传入对应的类型。
如果类的泛型没有在实例化的时候就定义那么默认就是Object的类型,传啥都可以
自定义泛型练习
- 分析下面代码是否正确,如果不正确,说明为什么
public class test3 {
public static void main(String[] args) {
Apple<String,Integer,Double>apple = new Apple<>();
apple.fly(10);
apple.fly(new Dog());
}
}
class Apple<T,R,M>{
public <E> void fly(E e){
System.out.println(e.getClass().getSimpleName());
}
public void eat(U u){}
public void eat(M m){}
}
class Dog{}
eat方法错误,因为没有定义泛型< U >
输出 Integer 和Dog
泛型的继承和通配符
泛型不具备继承性,例如下面的错误案例
ArrayList< Object > = new ArrayList< String >();
这样是不允许的。
但是可以通过特别的写法来实现与继承的搭配使用,这种写法就是通配符
通过前面的学习,我们知道泛型的主要用途是约束传入对象的类型
通配符有三种写法,对于约束的规则也有所不同
第一种:< ?> ,这种写法允许传入任何类型,上面的学习中就是使用这种
第二种:< ?extends A >.只允许传入A类或A的子类,限制了泛型的上限
第三种:< ?super A >,只允许传入A类或A的父类(不限于直接父类),限制了泛型的下限
举例说明:
public class test4 {
public static void main(String[] args) {
ArrayList<Object> l1 = new ArrayList<>();
ArrayList<String> l2 = new ArrayList<>();
ArrayList<AA> l3 = new ArrayList<>();
ArrayList<CC> l4 = new ArrayList<>();
//第一种<?>,可以传入任何类型,所以4个对象都能传入
f(l1);
f(l2);
f(l3);
f(l4);
//第二种<? extends AA>.只能传入A类或者A的子类,所以l1和l2不能传入
//f2(l1);
//f2(l2);
f2(l3);
f2(l4);
//第三种<? super AA>,只能传入A类或者A的父类,所以l2和l4不能传入
f3(l1);
//f3(l2);
f3(l3);
//f3(l4);
}
public static void f(List<?> c){}
public static void f2(List<? extends AA> c){}
public static void f3(List<? super AA> c){}
}
class AA{}
class BB extends AA{}
class CC extends BB{}
泛型作业
- 定义泛型类DAO< T >,在其中定义一个Map成员变量,Map的键为String 类型,值为T类型
分别创建以下方法:
public void save(String id ,T entity):保存T类型的对象到Map成员变量中
public T get(String id):从map中获取id对应的对象
public void update(String id,T entity):替换map中key为id的内容,改为entity对象
public List< T >list():返回map中存放的所有T对象
public void delete(String id):删除指定id对象
定义一个User类:private int id ,age。String name。
创建DAO类的对象,分别调用save,get,update,list,delete方法来操作User对象
使用JUnit单元测试类进行测试。(IDEA在需要单元测试的方法上添加注释@Test,引入JUnit,可以单独运行方法)
main方法一般是全部程序都在上面跑,这样不利于单独测试某一个功能,所以有了JUnit单元测试
public class HomeWork2 {
public static void main(String[] args) {
}
@Test
public void Test(){
//创建DAO类的对象,分别调用save,get,update,list,delete方法来操作User对象
//使用JUnit单元测试类进行测试。(IDEA在需要单元测试的方法上添加注释@Test,引入JUnit,可以单独运行方法)
DAT<User> dat = new DAT<>();
dat.save("01",new User(1,21,"李三"));
dat.save("02",new User(2,25,"王三"));
dat.save("03",new User(3,22,"赵三"));
dat.get("03");
dat.update("01",new User(1,24,"GHA"));
dat.delete("02");
System.out.println(dat.list());
}
}
class DAT<T>{
Map<String,T> map = new HashMap();;
public void save(String id ,T entity){
map.put(id, entity);
//保存T类型的对象到Map成员变量中
}
public T get(String id){
return map.get(id);
//从map中获取id对应的对象
}
public void update(String id,T entity){
//替换map中key为id的内容,将它的值改为entity
map.put(id, entity);
}
public List< T > list(){
List <T> list = new ArrayList<>();
Set<String> set = map.keySet();
for (String id:set) {
list.add(get(id));
}
return list;
//返回map中存放的所有T对象
}
public void delete(String id){
map.remove(id);
//删除指定id对象
}
}
class User{
private int id;
private int age;
private String name;
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
}