文章目录
1.前言
该博客主要分享的是我在学习韩顺平老师的Java基础课教程中整理的泛型知识点笔记,目的是记录自己的学习成果,方便后续的复习,并且在自己进行知识点总结的同时我会将课程中所需代码分享在这篇文章中,帮助各位正在跟着韩顺平老师学习Java的小伙伴们省去跟着老师敲代码的时间,希望能给大家带来一点便利!
2.为什么需要泛型
我们先来看一个具体的需求:
1.请编写程序,在ArrayList中,添加三个Dog对象 2.Dog对象中含有name和age,并输出name和age(要求用getXXX方法输出)
先用传统方法来解决这个需求,代码如下:
public class Generic01 {
public static void main(String[] args) {
ArrayList arrayList=new ArrayList();
arrayList.add(new Dog("小黄",10));
arrayList.add(new Dog("小黑",3));
arrayList.add(new Dog("小白",7));
//遍历
for (Object o :arrayList) {
//向下转型Object->Dog
Dog dog =(Dog) o;
System.out.println(dog.getName()+"-"+dog.getAge()+"岁");
}
}
}
class Dog{
private String name;
private int 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 Dog(String name, int age) {
this.name = name;
this.age = age;
}
}
这串代码的输出结果是:
小黄-10岁
小黑-3岁
小白-7岁
我们的需求在这串代码中看似已经得到了完美的解决,但是我们在这其中实际上还是埋下了一个隐患,并且编译器发现不了,比如:假设我们在添加一个Cat类,并且再给arrayList添加一只猫的话,会发生这样的情形:
public class Generic01 {
public static void main(String[] args) {
ArrayList arrayList=new ArrayList();
arrayList.add(new Dog("小黄",10));
arrayList.add(new Dog("小黑",3));
arrayList.add(new Dog("小白",7));
//程序员不小心加入了一只猫
arrayList.add(new Cat("招财猫",4));
//遍历
for (Object o :arrayList) {
//向下转型Object->Dog
Dog dog =(Dog) o;//加入猫后,在这里会发生类型转换异常
System.out.println(dog.getName()+"-"+dog.getAge()+"岁");
}
}
}
class Dog{
private String name;
private int 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 Dog(String name, int age) {
this.name = name;
this.age = age;
}
}
class Cat{
private String name;
private int 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 Cat(String name, int age) {
this.name = name;
this.age = age;
}
}
通过运行我们可以知道,在添加一只猫进入集合时,编译器并不能及时发现并报错,这样就使得编译器在使用增强for循环遍历时,由于Cat类和Dog类没有继承关系,所有在第四次(前三次传给Object o的都是Dog类)执行Dog dog =(Dog) o;语句时,会抛出类型转换异常。从这里我们就可以分析出使用传统方法解决这类需求的不足:
1.不能对加入到集合ArrayList中的数据类型进行约束(不安全)
2,遍历的时候,需要进行类型转换,如果集合中的数据量较大,由于每一次数据处理都要在向下转型这一步花费一定时间,所有最终会对整个程序的效率有影响
为了解决这个隐患,就需要我们用到泛型(有点类似于C++中的模板)来解决。
3.使用泛型解决需求
先上代码:
public class Generic02 {
public static void main(String[] args) {
ArrayList<Dog> arrayList=new ArrayList<Dog>();
arrayList.add(new Dog("小黄",10));
arrayList.add(new Dog("小黑",3));
arrayList.add(new Dog("小白",7));
//arrayList.add(new Cat("招财猫",4));
//使用泛型后,存放进ArrayList集合中的元素必须是Dog类型,如果不是,编译器会报错
//遍历
for (Dog dog :arrayList) {
System.out.println(dog.getName()+"-"+dog.getAge());
}
}
}
class Dog{
private String name;
private int 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 Dog(String name, int age) {
this.name = name;
this.age = age;
}
}
class Cat{
private String name;
private int 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 Cat(String name, int age) {
this.name = name;
this.age = age;
}
}
下面进行解读:
在ArrayList<Dog> arrayList=new ArrayList<Dog>();这行代码表示我们将能够放入arrayList集合中的数据类型强制约束为了Dog类,因此,当其他类型的元素放入arrayList时,系统会检查一下添加元素的类型是否是Dog类,如果不是,则编译器会报错,为程序员给出提示。
并且,在用增强for循环遍历时,不再强制要求使用Object类,这样我们就可以省去了类型转换的步骤,提高了系统运行效率。
总结:
1.new ArrayList<Dog>();表示放到ArrayList集合中的元素是Dog类型
2.如果编译器发现添加的类型不满足要求,就会报错,泛型相当于加入了一个数据类型约束
3.在遍历的时候可以直接取出Dog类型而不强制要求使用Object类
通过引入泛型,我们的需求就可以得到完美的解决。
3.1泛型的作用
泛型可以在类声明时通过一个标识表示类中某个属性的类型或者是某个方法的返回值的类型或者是参数类型。
下面我们继续通过代码来理解:
public class Generic03 {
public static void main(String[] args) {
Person<String> person=new Person<String>("Java学习进行时");
}
}
class Person<E>{
E s;
public Person(E s) {
this.s = s;
}
public E getS() {
return s;
}
public void setS(E s) {
this.s = s;
}
}
在Person类中
E表示s的数据类型,该数据类型在定义Person对象的时候指定,即在编译期间,才确定E是什么类型
比如Person<String> person=new Person<String>("Java学习进行时");这行代码编译期间,E的类型就被确定为了String类型。
也可以理解为此时的Person类是这样被定义的
class Person{
String s;
public Person(String s) {
this.s = s;
}
public String getS() {
return s;
}
public void setS(E s) {
this.s = s;
}
}
因此,在在传参的时候,只能传递String类型的参数,传递其他类型会报错
3.2泛型的语法
泛型的声明
interface 接口<T>{}和class 类<K,V>{}
1.其中,T,K,V不代表值,而是表示类型
2.任意字母都可以,常用T表示
泛型的实例化
要在类名后面指定类型参数的值(类型),如:
1.List<String> list=new ArrayList<String>();
2.Iterator<Customer> iterator=customers.iterator();
在用迭代器取出一个对象或者集合的时候给他指定一个泛型代表我们取出的这个对象或者集合是跟这个类型相关联的
下面通过一个练习来加深我们对泛型语法的理解
要求如下:
1.创建3个学生对象 2.放入HashSet中学生对象使用 3.放入到HashMap中,要求Key就是String name,Value就是学生对象 4.使用两种方法遍历
代码如下:
public class GenericExercise {
public static void main(String[] args) {
//使用泛型方式给HashSet,放入3个学生对象
HashSet <Student> students=new HashSet<Student>();
students.add(new Student("jack",18));
students.add(new Student("tom",28));
students.add(new Student("mary",19));
//遍历
for (Student student :students) {
System.out.println(student);
}
//使用泛型方式给HashMap放入3个学生对象
HashMap<String,Student>hm=new HashMap<String,Student>();
/*
public class HashMap<K,V>{}
*/
hm.put("milan",new Student("milan",38));
hm.put("smith",new Student("smith",48));
hm.put("hsp",new Student("hsp",28));
//迭代器EntrySet
/*
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
*/
Set<Map.Entry<String,Student>> entries=hm.entrySet();
/*
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
*/
Iterator<Map.Entry<String,Student>> iterator=entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Student> map = iterator.next();
System.out.println(map);
}
}
}
class Student{
private String name;
private int 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 Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
由于此处对于迭代器的分析需要通过调试看源码的方式来分析清楚,仅仅只依靠语言描述清楚可能会花费大量笔墨,因此我将关键几行代码会用到的源码进行了注释,推荐大家去观看韩老师的教程以便更加深入的理解。
3.3泛型的注意事项及细节
先定义好如下类:
class A{}
class B extends A{}
class Pig<E>{
E e;
public Pig(E e) {
this.e = e;
}
}
1.给泛型指向数据类型时,要求是引用类型,不能是基本数据类型
List<Integer> list=new ArrayList<Integer>();
// List<int> list1=new ArrayList<int>();
2.在给泛型指定了具体类型后,可以传入该类型或者其子类类型
Pig<A>aPig=new Pig<A>(new A());
Pig<A>aPig1=new Pig<A>(new B());//传A的子类B
3.泛型使用形式
List<Integer>list1=new ArrayList<Integer>();
实际开发中,我们往往简写
编译器会进行类型推断,推荐使用下面写法
List<Integer>list2=new ArrayList<>();
4.如果我们这样写ArrayList arrayList=new ArrayList();默认给他的泛型就是Object
ArrayList arrayList=new ArrayList();//等价于ArrayList<Object> arrayList=new ArrayList<>();
4.自定义泛型
4.1自定义泛型类
基本语法:
class 类名<T,R...>{//...表示可以有多个泛型
成员
}
注意细节:
1.普通成员可以使用泛型(属性,方法)
2.使用泛型的数组不能初始化
3.静态方法中不能使用类的泛型
4.泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
5.如果在创建对象时,没有指定类型,默认为Object
所有细节将在代码中以注释形式标出
public class CustomGeneric_ {
public static void main(String[] args) {
//T=Double,R=String,M=Integer
Tiger<Double,String,Integer> g=new Tiger<>("john");
g.setT(10.9);//ok
// g.setT("yy");//错误,类型不对
System.out.println(g);
Tiger g2=new Tiger("john~~~");//Ok T,R,M都是Object
g2.setT("yy");//ok 因为T=Object,"yy"=String 是Object子类
System.out.println("g2="+g2);
}
}
/*
1.Tiger后面有泛型,所以我们把Tiger就称为自定义泛型类
2.T,R,M泛型的标识符,一般是单个大写字母
3.泛型标识符可以是多个
4.普通成员可以使用泛型(属性,方法)
5.使用泛型的数组,不能初始化
*/
class Tiger<T,R,M>{
String name;
R r;//属性使用到泛型
M m;
T t;
//数组在new 的时候不能确定T 的类型,就无法在内存中开空间
// T[] ts=new T[8];//报错
//但是可以先定义
T[] ts;
public Tiger(String name, R r, M m, T t) {//构造器使用泛型
this.name = name;
this.r = r;
this.m = m;
this.t = t;
}
//因为静态是和类相关的,在类加载时,对象还没有创建
//所以,如果静态方法和静态属性使用了泛型,JVM就无法初始化
// static R r2;
// public static void m1(M m){}
//方法使用泛型
public String getName() {
return name;
}
public Tiger(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public R getR() {
return r;
}
public void setR(R r) {
this.r = r;
}
public M getM() {
return m;
}
public void setM(M m) {
this.m = m;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
4.2自定义泛型接口
基本语法:
interface 接口名<T,R...>{
}
注意细节:
1.接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
2.泛型接口的类型,在继承接口或者实现接口的确定
3.没有指定类型,则默认为Object
所有细节将在代码中以注释形式标出
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;
}
}
//在继承接口指定泛型接口的类型
interface IA extends IUsb<String,Double>{
//相当于在继承时IA将String指定给了IUsb中U类型,Double指定给了R类型
}
class AA implements IA{
//当AA实现IUsb中所有方法时,我们在IA中为IUsb指定的String和Double会自动填充到相应位置
@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) {}
}
//实现接口时,直接指定泛型接口的类型
//给U指定Integer,给R指定了Float
//所以,当我们实现IUsb方法时,会使用Integer替换U,使用Float替换R
class BB implements IUsb<Integer,Float>{
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float aFloat) {
}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {
}
}
4.3自定义泛型方法
基本语法:
修饰符<T,R...>返回类型 方法名(参数列表){
}
注意细节:
1。泛型方法,可以定义再普通类中,也可以定义在泛型类中
2.当泛型方法被调用时,类型必须确定下来,
3.public void eat(E e){},修饰符后没有<T,R...>eat方法不是泛型类型,而是使用了泛型
所有细节将在代码中以注释形式标出
public class CustomMethodGeneric {
public static void main(String[] args) {
/*
基本语法:
修饰符<T,R...>返回类型 方法名(参数列表){
}
注意细节:
1。泛型方法,可以定义再普通类中,也可以定义在泛型类中
2.当泛型方法被调用时,类型必须确定下来,
3.public void eat(E e){},修饰符后没有<T,R...>eat方法不是泛型类型,而是使用了泛型
*/
Car car=new Car();
car.fly("宝马",100);//当调用方法时,传入参数,编译器就会确定类型
System.out.println("===========");
car.fly(100,100.123);
//测试
Fish<String, ArrayList> fish=new Fish<>();
fish.hello(new ArrayList(),11.3f);
}
}
class Car{
public void run(){
}
//说明泛型方法
//1.<T,R>就是泛型
//2.是提供给fly使用的
public <T,R> void fly(T t,R r){//泛型方法
//这里的T,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){//泛型方法
}
public void hi(T t){
//没有泛型标识符,不是泛型方法
//只是该hi方法使用了类声明的泛型
}
//泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型
public<K> void hello(R r,K k){
//这个类中的R 是使用的泛型类Fish中声明的R,K使用的是自己声明的泛型K
System.out.println("泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型");
System.out.println(r.getClass());
System.out.println(k.getClass());
System.out.println("==============================================");
}
}
5.泛型继承和通配符
1.泛型没有继承性
List<Object> liat=new ArrayList<String>();//不对
2.<?>:支持任意泛型类型
3.<? extends A>:支持A类以及A类的子类,规定了泛型的上限
4.<? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
所有细节将在代码中以注释形式标出
public class GenericExtends {
public static void main(String[] args) {
Object o=new String("xx");
//举例说明下面三个方法的使用
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<>();
System.out.println("如果是List<?> c,则可以接受任意泛型类型");
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
System.out.println("? extends AA表示上限,可以接受AA或者AA子类");
// printCollection2(list1);
// printCollection2(list2);
printCollection2(list3);
printCollection2(list4);
printCollection2(list5);
System.out.println("? super 子类类名AA:支持AA类以及AA类的父类,不限于直接父类");
printCollection3(list1);
// printCollection3(list2);
printCollection3(list3);
// printCollection3(list4);
// printCollection3(list5);
}
//List<?>表示任意的泛型都可接受
public static void printCollection1(List<?> c){
for (Object object :c) {
System.out.println(object);
}
}
//? extends AA表示上限,可以接受AA或者AA子类
public static void printCollection2(List<? extends AA> c){
for (Object object :c) {
System.out.println(object);
}
}
//? super 子类类名AA:支持AA类以及AA类的父类,不限于直接父类
//规定了泛型的下限
public static void printCollection3(List<? super AA> c){
for (Object object :c) {
System.out.println(object);
}
}
}
class AA{}
class BB extends AA{}
class CC extends BB{}
6.后记
跟着韩老师学习Java近一个月的时间了,现在终于将基础部分学完了,但是,为了能够巩固自己的Java基础,我决定将自己所有的学习笔记通过博客的方式记录下来,在方便自己复习的同时,也能帮助各位跟着韩老师学Java的小伙伴节省跟着敲代码的时间(当然,我还是建议跟着敲,只是如果有些内容比较简单的话,可以直接从我这复制粘贴),每篇博客里面,我也会添加一些我自己思考所得的内容(当然,大多数内容还是韩老师所教授的),希望能够对大家有所帮助!