泛型
概述
泛型是一种把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的安全机制。
优点:将问题提前到了编译期;避免了向下转型;优化了程序设计
泛型可定义在类、接口、方法上
当不使用泛型时,一个集合可以存储各种引用类型数据,取出元素时需要向下转型,如下所示:
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(100);
arrayList.add("aaa");
arrayList.add('b');
Object obj = arrayList.get(1);
String str=(String)obj;
}
}
而使用泛型时,一个集合只能存储指定类型的数据,在取出元素时也无需向下转型。如下所示:
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList();
arrayList.add("aaa");
arrayList.add("bbb");
arrayList.add("ccc");
String str = arrayList.get(1);
//如果使用迭代器遍历集合,迭代器上也要声明泛型才能避免强转
Iterator<String> it=arrayList.iterator();
while (it.hasNext()){
String s=it.next();
System.out.println(s);
}
}
}
注意:如果存储非指定类型的数据,则会报错
泛型的由来:早期的Object类型可以接收任意的对象类型,但是在实际的使用中,会有类型转换的问题。也就存在这隐患,所以Java提供了泛型来解决这个安全问题。
将泛型定义在类上:
将泛型定义在类上时,在创建对象时需要明确类型
class MyClass<E> {
private E e;
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
public class Demo {
public static void main(String[] args) {
MyClas<String> stringMyClass = new MyClass<>();
stringMyClass.setE("abc");
String str = stringMyClass.getE();
System.out.println(str);
}
}
/*
运行结果:
abc
*/
注意:静态方法不可以访问类上定义的泛型,如果静态方法操作的数据类型不确定,可以将泛型定义在方法上。
因为泛型需要在创建对象时才明确,而静态方法随着类的加载而加载。
泛型定义在方法上:
为了让不同方法操作不同类型的对象,且类型不确定,可以将泛型定义在方法上。
public class GenericDemo {
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.show(4);
myClass.show("aaa");
myClass.print(6);
myClass.print("bbb");
}
}
class MyClass{
public <T> void show(T t){
System.out.println("show:"+t);
}
public <E> void print(E e){
System.out.println("print:"+e);
}
}
/*
运行结果:
show:4
show:aaa
print:6
print:bbb
*/
泛型定义在接口上:
明确类型有以下三种情况
interface MyInterface<E> {
public abstract void show(E e);
}
//在创建实现该接口的子类时明确类型
class MyClass implements MyInterface<String>
{
@Override
public void show(String s) {
System.out.println(s);
}
}
public class Demo {
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.show("abc");
}
}
/*
运行结果:
abc
*/
//创建子类时继续使用泛型,创建对象时再明确
class MyClass2<E> implements MyInterface<E>
{
@Override
public void show(E e) {
System.out.println(e);
}
}
public class Demo {
public static void main(String[] args) {
MyClass2<String> myClass2 = new MyClass2<>();
myClass2.show("abc");
}
}
/*
运行结果:
abc
*/
//使用匿名内部类创建该接口子类对象时明确类型
public class Demo3 {
public static void main(String[] args) {
new MyInterface<String>() {
@Override
public void show(String s) {
System.out.println(s);
}
}.show("abc");
}
}
/*
运行结果:
abc
*/
泛型通配符:
类型通配符<?>可以接收任意类型
import java.util.ArrayList;
public class GenericDemo {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
print(list1);
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(5);
list2.add(8);
list2.add(4);
print(list2);
}
//使用<?>可以使方法接收任何类型的集合
public static void print(ArrayList<?> arrayList){
System.out.println(arrayList);
}
}
? extends E:向下限定,E及其子类
现在有Person类及其子类Student,print方法可以输出人的姓名,那么自然可以输出Student类的对象的姓名。
我们可以将泛型设为?,但是这样就可以接受任何类,所以我们可以向下限定。
import java.util.ArrayList;
import java.util.Iterator;
public class GenericDemo3 {
public static void main(String[] args) {
ArrayList<Person> list1 = new ArrayList<>();
list1.add(new Person("aaa"));
list1.add(new Person("bbb"));
list1.add(new Person("ccc"));
print(list1);
ArrayList<Student> list2 = new ArrayList<>();
list2.add(new Student("a"));
list2.add(new Student("b"));
list2.add(new Student("c"));
print(list2);
}
public static void print(ArrayList<? extends Person> arrayList){
Iterator<? extends Person> iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next().getName());
}
}
}
class Person{
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Student extends Person{
public Student(String name) {
super(name);
}
}
同样的,在一些需求中,我们也可以向上限定
? super E:向上限定,E及其父类
增强for循环
增强for循环简化了数组和集合遍历
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
int[] arr={10,20,30,40,50};
/*for (容器中元素的数据类型 元素名称:容器名){
对元素操作的代码;
}*/
for (int each:arr)
{
System.out.println(each);
}
System.out.println("----------");
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(100);
arrayList.add(200);
arrayList.add(300);
arrayList.add(400);
arrayList.add(500);
for (Integer each:arrayList)
{
System.out.println(each);
}
}
}
/*
运行结果:
10
20
30
40
50
----------
100
200
300
400
500
*/
当我们只想使用数组中元素的值时,可以使用foreach循环;但如果要访问或者数组的下标时,使用常规的for循环。
注意:增强for循环底层使用的是迭代器,所以要注意并发修改异常
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(100);
arrayList.add(200);
arrayList.add(300);
arrayList.add(400);
arrayList.add(500);
//在遍历时增删元素会发生并发修改异常
//Exception in thread "main" java.util.ConcurrentModificationException
//其实增强for循环底层使用的是迭代器,所以要注意并发修改异常
for (Integer each:arrayList)
{
if (each.equals(200))
{
arrayList.add(2000);
}
}
}
}
/*
运行结果:
*/
可变参数
定义方法的时候不知道该定义多少个参数,就可以使用可变参数
比如在加法函数,我们不知道要传入多少个参数求和,重载显然不是一个好办法,我们就应该使用可变参数,如下所示:
public class Demo {
public static void main(String[] args) {
int sum1=add(1,2);
int sum2=add(1,2,3);
int sum3=add(1,2,3,4);
System.out.println(sum1);
System.out.println(sum2);
System.out.println(sum3);
}
//可变参数可以接受多个参数
//可变参数实际上就是数组
/*
在一个方法中,如果既有普通参数,又有可变参数,可变参数位于后面,即:
public static int add(int i,int ... num){}
*/
public static int add(int ... num)
{
int sum=0;
for (int each:num)
{
sum=sum+each;
}
return sum;
}
}
/*
运行结果:
3
6
10
*/
事实上,在底层将我们传入的数封装成一个数组,通过数组对方法中的数进行操作
import java.util.Arrays;
public class Demo {
public static void main(String[] args) {
show(2,3,4);
show(4,2,6,7,8,0);
}
public static void show(int...arr){
System.out.println(arr.length);
System.out.println(Arrays.toString(arr));
}
}
/*
运行结果:
3
[2, 3, 4]
6
[4, 2, 6, 7, 8, 0]
*/
Arrays工具类中的asList方法的参数就是可变参数
public static <T> List<T> asList(T... a)
返回一个受指定数组支持的固定大小的列表。
该方法的使用有一些注意事项
1.当传入一个数组,会把数组中的元素取出放到集合中;当传入两个及两个以上数组,会把数组(即地址值)放到集合中。
import java.util.Arrays;
import java.util.List;
public class Demo {
public static void main(String[] args) {
Integer[] integers1={10,20,30};
//集合泛型为Integer
List<Integer> list1 = Arrays.asList(integers1);
Integer i1 = list1.get(1);
Integer[] integers2={10,20,30};
Integer[] integers3={40,50,60};
//集合泛型为Integer[]
List<Integer[]> list2 = Arrays.asList(integers2, integers3);
Integer i2 = list2.get(1)[1];
System.out.println(i1);
System.out.println(i2);
}
}
2.如果传入一个引用类型数组,会将数组中的元素放入集合中;如果传入一个基本类型数组,会将数组放到集合中,因为集合不能存储基本类型数据。
import java.util.Arrays;
import java.util.List;
public class Demo {
public static void main(String[] args) {
Integer[] integers={10,20,30};
int[] arr={10,20,30};
//集合的泛型为Integer
List<Integer> list1 = Arrays.asList(integers);
//集合的泛型为int[]
List<int[]> list2 = Arrays.asList(arr);
}
}
3.通过asList获取的集合存在一个弊端,其长度不可变,所以只能获取修改元素,不能增删元素。
集合嵌套
用一个集合作为容器存储集合。和二维数组类似。
//需求:一个班级有很多学生,一个学习有很多班级
import java.util.ArrayList;
public class Demo6 {
public static void main(String[] args) {
Student s1 = new Student("aa", 21);
Student s2 = new Student("bb", 23);
Student s3 = new Student("cc", 22);
Student s4 = new Student("dd", 21);
Student s5 = new Student("ee", 20);
Student s6 = new Student("ff", 24);
//班级1集合
ArrayList<Student> class1List = new ArrayList<>();
class1List.add(s1);
class1List.add(s2);
class1List.add(s3);
//班级2集合
ArrayList<Student> class2List = new ArrayList<>();
class2List.add(s4);
class2List.add(s5);
class2List.add(s6);
//学校集合
ArrayList<ArrayList<Student>> schoolList = new ArrayList<>();
schoolList.add(class1List);
schoolList.add(class2List);
//两种for循环的遍历方式
for (int i = 0; i < schoolList.size(); i++) {
for (int j = 0; j < schoolList.get(i).size(); j++) {
Student student = schoolList.get(i).get(j);
System.out.println(student);
}
}
System.out.println("---------");
for (ArrayList<Student> each:schoolList)
{
for (Student each1:each)
{
System.out.println(each1);
}
}
}
}
class Student {
private String name;
private int age;
public Student() {
}
public Student(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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/*
运行结果:
Student{name='aa', age=21}
Student{name='bb', age=23}
Student{name='cc', age=22}
Student{name='dd', age=21}
Student{name='ee', age=20}
Student{name='ff', age=24}
---------
Student{name='aa', age=21}
Student{name='bb', age=23}
Student{name='cc', age=22}
Student{name='dd', age=21}
Student{name='ee', age=20}
Student{name='ff', age=24}
*/