引出泛型
编写程序,在ArrayList中,添加3个dog对象,dog类含有name和age,使用get方法输出。
java
public class Go {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Dog("大黄", 3));//隐式向上转型Dog放入ArrayList
list.add(new Dog("中黄", 2));
list.add(new Dog("小黄", 1));
for (Object o : list) {//向上转型,将ArrayList放入Object中
Dog dog=(Dog)o;//向下转型,将Object o放入Dog
System.out.println(dog.getName() + "今年" + dog.getAge() + "岁了");
}
}
}
class Dog{
private String name;
private int age;
//略
}
此时如果有一个Cat类对象(与Dog类仅名称不一样),那程序还能允许吗?
java
public class Go {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Dog("大黄", 3));//隐式向上转型Dog放入ArrayList
list.add(new Dog("中黄", 2));
list.add(new Dog("小黄", 1));
list.add(new Cat("狸花", 0));
for (Object o : list) {//向上转型,将ArrayList放入Object中
Dog dog=(Dog)o;//向下转型,将Object o放入Dog
System.out.println(dog.getName() + "今年" + dog.getAge() + "岁了");
}
}
}
class Dog{
private String name;
private int age;
//略
}
class Cat{
private String name;
private int age;
//略
}
编写代码时系统并未报错,但在运行到语句 Dog dog=(Dog)o;时,系统便会报错ClassCastException,因此可见,这段程序只能接受Dog类而不能接受其他类。
同时,系统在运行时需多次进行转型,这在处理大量数据时无疑会拖慢系统的运行速度
这就可见这样编写代码的缺点:
1、未对程序不能处理的数据类型进行约束(不安全)
2、使用增强for循环遍历时必须将对象转型为Object,当数据量较大时,运行速度较慢(效率低)
Dog➡ArrayList➡Object➡Dog
而泛型就可以解决这两个问题
java
ArrayList<Dog> list = new ArrayList<Dog>();
list.add(new Dog("大黄", 3));
list.add(new Dog("中黄", 2));
list.add(new Dog("小黄", 1));
list.add(new Cat("狸花", 0));//报错
for (Dog o : list) {
System.out.println(o.getName() + "今年" + o.getAge() + "岁了");
}
这两处发生了改动
java
//改动前------------------------------------------------------------------------------
ArrayList list = new ArrayList();
list.add(new Cat("狸花", 0));
for (Object o : list) {
Dog dog=(Dog)o;
System.out.println(dog.getName() + "今年" + dog.getAge() + "岁了");
}
//改动后------------------------------------------------------------------------------
ArrayList<Dog> list = new ArrayList<Dog>();
//list.add(new Cat("狸花", 0));报错
for (Dog o : list) {
System.out.println(o.getName() + "今年" + o.getAge() + "岁了");
}
1、编译时,会检查添加元素的类型,提高了安全性
2、使用增强for循环时,不再必须转型为Object,减少了类型转换的次数,提高了效率Dog➡ArrayList➡Dog
3、减少了编译时的警告,且保证了只要在编译时没有警告,运行时就不会产生ClassCastException异常
介绍
泛型又称参数化类型,它提供了编译时类型安全检查的机制,允许在编码时指定集合中对象的类型,这样可以在编译时提供更强的类型检查,可以让我们写出更加通用、可复用的代码,同时减少类型转换以及运行时错误,并提高代码的可读性和重用性。
类、接口、方法都可使用泛型,具体表现为在在类/接口后添加<E>,原本为数据类型的地方也替换为E,而E所代表的数据类型根据创建的实例而确定,代表在该区域相应的位置处,只能使用该数据类型。
java
public class Go {
public static void main(String[] args) {
HashMap<String, student> myMap = new HashMap<>();
myMap.put("万叶",new student(18,"万叶"));
myMap.put("莫娜",new student(15,"莫娜"));
myMap.put("仆人",new student(22,"仆人"));
myMap.put("可莉",new student(8,"可莉"));
Set<Map.Entry<String, student>> set = myMap.entrySet();//myMap.entrySet().var
Iterator<Map.Entry<String, student>> iterator = set.iterator();//set.iterator().var
while (iterator.hasNext()) {
Map.Entry<String, student> next = iterator.next();
System.out.println(next.getKey()+""+next.getValue());
}
}
}
class student{
private int age;
private String name;
//略}
注意:
1、T,E只能是引用类型,而不能是基本数据类型
java
ArrayList<Integer> objects = new ArrayList<Integer>();//正确
ArrayList<int> objects = new ArrayList<int>();//错误
2、在给泛型指定具体类型后,可以传入该类型以及其子类类型
java
public class Go {
public static void main(String[] args) {
HashSet<a> set = new HashSet<a>();
set.add(new a());//正确,传入了指定类
set.add(new b());//正确,传入了指定类的子类
set.add(new c());//错误,传入了其他类
set.add(new top());//错误,传入了指定类的父类
}}
class top{}
class a extends top{}
class b extends a{}
class c{}
3、平时使用泛型时推荐采用简写形式,即第二个<>内不写内容,系统会自动将第一个<>内的内容补充到第二个<>内,
在我们之前创建接口类的实例时,虽然没有写<>泛型,但实际上是隐藏了泛型,相当于指定泛型为Object
java
HashSet hashSet = new HashSet();
//等同于
HashSet<Object> hashSet2 = new HashSet<>();
//等同于
HashSet<Object> hashSet1 = new HashSet<Object>();
例题:
1、定义Employee类,该类包含:private成员变量name、sal、birthday,其中 birthday 为 MyDate 类的对象;
2、补充相应的get、set、构造等方法;
3、MyDate类包含:private成员变量 month、day、year,并补充相关方法
4、添加三个该类对象,并将其放入ArrayList集合中(ArrayList需使用泛型来定义)
5、对集合进行排序并输出,排序规则:调用ArratList的sort方法,传入Comparator对象(使用泛型),先按照name排序,如果name相同则按照生日的年月日先后比较
java
public class Go {
public static void main(String[] args) {
ArrayList<Employee> list = new ArrayList();
list.add(new Employee("banana",8000,new MyDate(12,16,2003)));
list.add(new Employee("apple",9999,new MyDate(03,21,1999)));
list.add(new Employee("pear",3000,new MyDate(05,21,2002)));
list.add(new Employee("apple",3000,new MyDate(03,22,1999)));
list.sort(new Comparator<Employee>() {
@Override
public int compare(Employee employee1, Employee employee2) {
//先调用compareTo方法,name不同则返回正/负值,相同为0,继续下个判断
int namedif= employee1.getName().compareTo(employee2.getName());
if(namedif!=0)
return namedif;
//依次判断年月日的不同
int yeardif=employee1.getBirthday().getYear()-(employee2.getBirthday().getYear());
if(yeardif!=0)
return yeardif;
int monthdif=employee1.getBirthday().getMonth()-(employee2.getBirthday().getMonth());
if(monthdif!=0)
return monthdif;
return employee1.getBirthday().getDay()-(employee2.getBirthday().getDay());}
});
Iterator<Employee> iterator = list.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);}}}
class Employee {
private String name;
private float sal;
private MyDate birthday;
//略}
class MyDate {
private int month;
private int day;
private int year;
//略}
执行结果:
Employee{name='apple', sal=9999.0, birthday=MyDate{month=3, day=21, year=1999}}
Employee{name='apple', sal=3000.0, birthday=MyDate{month=3, day=22, year=1999}}
Employee{name='banana', sal=8000.0, birthday=MyDate{month=12, day=16, year=2003}}
Employee{name='pear', sal=3000.0, birthday=MyDate{month=5, day=21, year=2002}}
但这里的代码还可以精简一下,对年月日大小的判断方法可以封装为MyDate类的一个方法
java
public class Go {
public static void main(String[] args) {
ArrayList<Employee> list = new ArrayList();
list.add(new Employee("banana",8000,new MyDate(12,16,2003)));
list.add(new Employee("apple",9999,new MyDate(03,21,1999)));
list.add(new Employee("pear",3000,new MyDate(05,21,2002)));
list.add(new Employee("apple",3000,new MyDate(03,22,1999)));
list.sort(new Comparator<Employee>() {
@Override
public int compare(Employee employee1, Employee employee2) {
//先调用compareTo方法,name不同则返回正/负值,相同为0,继续下个判断
int namedif= employee1.getName().compareTo(employee2.getName());
if(namedif!=0)
return namedif;
//直接调用MyDate类内部重写的compareTo方法
return employee1.getBirthday().compareTo(employee2.getBirthday());
}
});
Iterator<Employee> iterator = list.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}}}
class Employee {
private String name;
private float sal;
private MyDate birthday;
//略}
class MyDate implements Comparable<MyDate>{//实现接口,并指定泛型
private int month;
private int day;
private int year;
//略
@Override
public int compareTo(MyDate o) {
if (this.year != o.year) {// 比较年份
return this.year - o.year;}
if (this.month != o.month) {// 如果年份相同,比较月份
return this.month - o.month;}
return this.day - o.day;// 如果月份也相同,比较天,相同直接返回0
}
}
泛型类
泛型类是通过类型参数化的类,可以在创建对象的时候指定具体的类型。
java
public class Box<T> { // T代表数据类型,类似于int,可以有多个
private T t;
public void set(T t) {
this.t = t;}
public T get() {
return t;}}
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
注意:
- 普通成员(属性,方法)可以使用泛型。
- 使用泛型的数组不能初始化,因为类型不确定,系统无法确定开辟多大空间,但可以先定义
- 静态属性/方法中不能使用类的泛型,因为静态在类加载时就已执行,但泛型的具体类型需等到创建对象时才确定,所以无法执行
- 泛型类的类型,是在创建对象时确定的,如果在创建对象时未指定泛型,则默认为Object
java
class Box<T> {
private T t;//普通成员(属性,方法)可以使用泛型。
public T getT() {return t;}
int []arr=new int[8];//正确
T []arr1;//正确,可以定义使用泛型的数组
//T []arr2=new T[8];//错误,使用泛型的数组不能初始化
public static void show(){}
public static int num;//正常定义静态方法、属性
public static T show2(){}//错误
public static T num2;//错误,静态方法/属性中不能使用类的泛型}
泛型方法
泛型方法是在调用方法的时候指明具体的类型。泛型方法可以定义在普通类中也可以定义在泛型类中。
java
public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
注意:
-
泛型方法可以定义在普通类中也可以定义在泛型类中
javaclass AAA {//普通类 public void show(){};//普通方法 public <T,R> void show2(){}//泛型方法 } class BBB<T,R>{//泛型类 public void show(){};//普通方法 public <T,R> void show2(){}//泛型方法 }
-
泛型方法被调用时,方法类型会确定
javapublic class Go { public static void main(String[] args) { AAA aaa = new AAA(); aaa.show("aaa",555);//此时T为String,R为Integer aaa.show(66.6,true);//此时T为Double,R为Boolean }} class AAA { public <T,R> void show(T t,R r){ }}
-
没有指定类型,则默认为Object
-
例如public void go(T t,R r){}只是普通方法使用了泛型,而并非泛型方法,且泛型方法可使用泛型类提供的泛型,也可自定义泛型,亦或参杂使用
javaclass AAA <T,R>{//泛型类 public void go(T t,R r){}//普通方法使用泛型 public <T,K>void show(T t,K k){}//泛型方法,泛型类和泛型方法提供的泛型相结合 }
泛型接口
泛型也可以应用于接口。
java
public interface Generator<T> {
T next();
}
注意:
- 接口中,静态成员同样不能使用泛型
- 泛型接口的类型,在继承接口或实现接口时确定
- 没有指定类型,则默认为Object
java
interface usb1<T, R> {
//,错误,静态变量,等同于public static final T num = 10;
T num = 10;
static T show3() {//错误,不能使用静态方法
System.out.println("静态方法的方法体");
return null;}
//正确,抽象方法(无方法体且省略abstract)
T show1();
//正确,普通方法(需加上default关键字)
default public T show2(T t) {
System.out.println("普通方法的方法体");
return t;}}
interface usb2 extends usb1<String,String>{}//在继承接口时确定
class class2 implements usb2{//实现类
@Override
public String show1() {return null;}
@Override
public String show2(String string) {return usb2.super.show2(string);}}
interface usb3<S, D > extends usb1{}//在继承接口时未确定,则需也留下两个泛型
class class3 implements usb3<String,Double>{//实现接口时确定
@Override
public Object show1() {return null;}
@Override
public Object show2(Object o) {return usb3.super.show2(o);}}
java
interface usb1<T,R>{ }//错误
interface usb2 extends usb1{}
class aaa implements usb2<String,Double>{//略
}
继承通配符
1、泛型不具备继承性,之前所学中我们可以将子类对象放到父类的实例中,但在泛型中这并不适用
java
Object str1 = new String();//正确
ArrayList<Object> list = new ArrayList<String>();//错误
2、泛型通配符有三种
1、 <?> - 无界通配符,表示可接收任何类型的泛型
2、<? extends Type> - 上界通配符,表类型参数必须是 Type 或 Type 的子类。
3、<? super Type> - 下界通配符,表类型参数必须是 Type 或 Type 的父类。
类型通配符一般是使用大写字母代替具体的类型参数。
java
public class Go {
// 使用无界通配符的方法,可以接受任何类型的List
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
// 使用上界通配符的方法,可以接受Number或其子类型的List
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number number : list) {
sum += number.doubleValue();
}
return sum;
}
// 使用下界通配符的方法,可以接受Integer或其超类型的List
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i);
}
}
public static void main(String[] args) {
// 创建不同类型的List
List<Integer> integerList = new ArrayList<>(Arrays.asList(1, 2, 3));
List<Double> doubleList = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0));
List<Number> numberList = new ArrayList<>();
// 使用无界通配符方法打印List
System.out.println("Printing integerList:");
printList(integerList);
System.out.println("Printing doubleList:");
printList(doubleList);
// 使用上界通配符方法计算和
System.out.println("Sum of integerList: " + sumOfList(integerList));
System.out.println("Sum of doubleList: " + sumOfList(doubleList));
// 使用下界通配符方法向numberList添加整数
addNumbers(numberList);
System.out.println("Contents of numberList after adding numbers:");
printList(numberList); // 使用无界通配符方法打印结果
}
}
Juit
JUnit 是一个用于 Java 编程语言的开源测试框架,它用于编写和运行可重复的测试。JUnit 提供了一种标准化的方式来编写测试用例,使开发人员能够验证他们的代码是否按预期工作。
使用方法:
首次使用,在想要测试的方法本体前添加"@Test"按Alt+Enter,选择导入5.X资源包到类路径,稍等片刻。之后直接在想要测试的方法本体前添加"@Test"按Alt+Enter,单击左侧运行按钮即可运行单个方法。
例题:定义一个泛型类dao<T>,在其中定义一个Map成员变量,Map的键为String类型,值为T类型。
分别创建以下方法:
- public void save(String id, T t):保存T类型的对象到Map成员变量中
- public T get(String id):从map中获取id对应的对象
- public void update(String id, T t): 替换map中key为指定id的内容,改为指定的t对象
- public List<T> list():返回map中存放的所有T对象
- public void delete(String id, T t):删除指定id对象
定义一个User类,包含id、age、name属性,及其对应的方法。
创建dao类的对象,分别调用五个方法来操作对象。
java
public class Go {
public static void main(String[] args) {
dao<User> D = new dao<User>();
D.save("001",new User(1,18,"top"));
D.save("002",new User(2,19,"mid"));
D.save("003",new User(3,33,"sup"));
D.save("004",new User(4,24,"jug"));
D.save("005",new User(5,21,"adc"));
System.out.println("全部内容------------------------------------------------------------------------------");
for (Object o :D.list()) {
System.out.println(o);}
System.out.println("User内容------------------------------------------------------------------------------");
List<User> users = D.list();
for (Object o :users) {
System.out.println(o);}
D.update("003",new User(3,24,"faker"));
System.out.println("修改后ID为003的选手:"+D.get("003"));
D.delete("005",new User(5,21,"adc"));
System.out.println("删除后内容------------------------------------------------------------------------------");
for (Object o :D.list()) {
System.out.println(o);
}}}
class dao<T> {
private Map<String, T> map = new HashMap<>();
public void save(String id, T t) {
map.put(id, t);}
public T get(String id) {
return map.get(id);}
public void update(String id, T t) {
map.put(id, t);}
public List<T> list() {
return new ArrayList<T>(map.values());}
public void delete(String id, T t) {
map.remove(id, t);}
@Override
public String toString() {
return "dao{" +
"map=" + map +
'}';}
}
class User {
private int id;
private int age;
private String name;
//略
}