(点击跳转即可哦)
泛型的应用
定义泛型对象时,只能使用类
基本类型不能保存到泛型中,必须使用包装类
泛型的引出
看下面这个代码
class Point{
x;
y;
}
若x = 10, y = 20,
x = 10.1,y = 20.1
x = “你”,y= “我”,在Java中,Java是一个强类型语言,在定义x,y时,必须强制定义变量类型,那此时x,y 该定义为什么类型呢?
应该定义为泛型
泛型,在编译阶段检查类型是否一致的手段。
所谓的泛型指的是在类定义时不明确类型,在使用时明确类型
定义泛型使用"<>"操作符
称为类型参数,
<>中间可以使用任何字符,规范是单个的大写字母
T : 表示任意类型
K : 键值对,key值
V : value 值
E :单个元素
泛型类
public class Point<T>(){
//x,y这两个成员变量的类型不定,在产生Point对象时,明确x,y的类型
T x;
T Y;
public static void main(String[] args){
//此时设置的类型为字符串
Point<String> point = new Point<>();
point.x = "张三";
point.y = "李四";
System.out.println(point.x);
System.out.println(point.y);
}
}
当定义为Point<String>
,此时将T这个类型参数替换为明确的类型String,若在后面的输入时,数据类型不是String类型,就会发生编译报错,不用等到运行就能够看到错误。
一般来说,我们要求把所以的错误都提前暴露在编译阶段,程序还没有跑起来就能够发生错误。
我们发现,引入泛型后,可以在编译阶段检查设置的类型值是否 是指定类型,若不一致,编译报错,取出值的时候,就无需再进行强转。
若泛型类中存在多个类型参数,成员变量的类型不一定一致。
//使用不同的大写字母来指代不同类型
public class Point<T,E>{
T x;
E y;
public static void main(String[] args){
Point<String,String> p1 = new Point<>();
x = "张三";
y = "李四";
Point<String,Integer> p2 = new Point<>();
x = "年龄";
y = 18;
}
}
此时是不会报错的。
在产生对象时,T和E的类型可以相同,也可以不同。
泛型方法
泛型除了可以用在类声明上,也可以单独来定义方法 - 一个类是普通类仍然可以定义泛型方法。
public <T> T test(T t){
System.out.println(t);
return t;
}
<T>
表示该方法是一个泛型方法
T
表示 该方法的返回值类型是 T
(T t)
表示 该方法的参数用了类型参数 T
在泛型类中定义了泛型方法
public class TestMethod<T>{
//使用泛型定义的泛型方法
//public <T> T funMethod(T t){
// return t;
//}
public <E> E funMethod(E e){
return e;
}
}
泛型方法始终以自己的类型参数为准,与泛型类中的T无关,推荐若泛型类中存在了泛型方法,定义为不同的类型参数,不造成异议
泛型声明在接口中
一旦一个接口使用泛型声明,子类在实现接口时就有两种选择。
1 继续保留泛型
//子类在实现接口时,继续保留泛型,子类也不清楚具体的类型
public class MessageImpl1<T> implements IMessage<T>{
@Override
public void print(T t){
System.out.println(t);
}
}
IMessage<String> message = new MessageImpl1<>();
message.print("123");
IMessage<Integer> message1 = new MessageImpl1<>();
message1.print(123);
MessageImpl1 子类仍然是泛型类,因此产生的啥类型,就可以接收啥类型
2 子类明确当前的类型
//子类实现接口时明确清楚当前类型
public class MessageImpl2 implements IMessage<String>{
@Override
public void print(String s){
System.out.println(s);
}
}
//只能接收String 类型
IMessage<String> m2 = new MessageImpl2();
m2.print("234");
IMessage<Integer> m3 = new MessageImpl2();
m3.print(34);
//后两行代码会报错,因为子类已经明确了当前的类型
泛型中的通配符
有泛型之后,使用泛型类或者接口对象方法的参数不好定义
public static void main(String[] args){
IMessage<String> msg = new MessageImpl1();
fun(msg);
IMessage<Integer> m2 = new MessageImpl1();
fun(m2);
}
//fun只能接收String 类型的Message对象
public static void fun(IMessage<String> msg){
msg.print("123");
}
所以就需要通配符
? 通配符
?通配符,只能定义在方法中,表示可以接收所有类型的泛型类。只能调用getter类的方法,无法设置具体值
public static void fun(IMessage<?> msg){//<?>可以接收所有类型的IMessage对象
System.out.println(msg.getMsg());
}
能否在fun方法中调用set类的方法修改值?
不能,此时定义fun方法根本不知道会传入什么类型的msg对象,怎么会设置值
<?extends类>:设置泛型上限
1 可以定义在方法参数和类中,明确此时泛型的上限为具体的某个类
2 设置的父类,不同的子类之间没有直接关系,因此仍然无法设置某个具体的值
在方法声明上
//这个msg 仍然可以接收任何类型,但是必须是Number及其子类
public static void fun(IMessage<? extends Number> msg){
System.out.println(msg.getMsg());
}
只能明确的是当前泛型的天花板,父类为Number,具体传入的是什么类型的子类就未知。
还是不能够调用Setter方法。明确知道父类是什么,现在Number设置也只能设置Number的值,由于不知道传入的子类 是啥,父类的Number类型的值 强制赋值给子类,向下转型不一定成功
在类中
public class TestMethod<T extends Number>{
public void testMethod(T t){
System.out.println(t);
}
public static void main(String[] args){
TestMethod<Integer> cls = new TestMethod<>();
cls.testMethod(123);
//会报错,T必须是Number以及Number的子类
TestMethod<String> cls1 = new TestMethod<>();
cls.testMethod("123");
}
}
<? super 类> 规定泛型下限
<? super Fruit> 只能表示Fruit 及其父类
public static void fun(IMessage<? super String> msg){
System.out.println(msg.getMsg());
}
//此时fun只能接收String类型以及它的父类
此时明确知道的是子类,不管 ? 是啥类型,都一定是String的父类,设置一个String对象,天然都is a 父类
- 由于此时明确知道的是下限,明确的是子类是谁,由于子类天然是父类,可以设置值
- super 下限通配符 只能用在方法内部,不能再类中定义
总结
泛型只是编译阶段的语法糖
泛型类和普通类进入JVM之后,没有任何区别
javac -> *.java 编译为 *.class之后,泛型就没了
类型擦除:泛型信息其实只存在于编译阶段,进入JVM后,会将所有和泛型相关的信息擦除掉
若没有规定泛型上学,则所有泛型信息都擦除为 Object 类型
若规定了泛型上限,则擦除为 相应的泛型上限类型
集合框架初识
所谓的集合就是用来保存和操作数据的一些类。
Collection
:线性表集合,保存单个同类型的元素
List
: 线性结构
Queue
: 队列
Deque
: 双端队列
Map
: 键值对集合
以上都是接口
ArrayList
(动态数组) 和 LinkedList
(双向链表)是 List
的具体实现子类
List
List - 线性表的父接口
常用子类: —ArrayList
(底层是一个动态数组实现的线性表)
— LinkedList
(底层是一个双向链表实现的线性表)
List<Interger> list = new LinkedList<>();
List<Interger> list = new ArrayList<>();
List 接口中定义的方法,子类在实现时,都需要进行覆写,使用时更换子类非常简单,只需要更换new 的子类对象,在使用时没有任何区别,使用方法都是在接口中定义好的。
List定义的方法,
//尾插 e
boolean add(E e)
//将 e 插入到 index 位置
void add(int index, E element)
//尾插 c 中的元素
//boolean addAll(Collection< ? extends E> c)
//删除 index 位置元素
E remove(int index)
//删除遇到的第一个 o
boolean remove(Object o)
//获取下标 index 位置元素
E get(int index)
//将下标 index 位置元素设置为 element
E set(int index, E element)
//清空
void clear()
//判断 o 是否在线性表中
boolean contains(Object o)
//返回第一个 o 所在下标
int indexOf(Object o)
//返回最后一个 o 的下标
int lastIndexOf(Object o)
//截取部分 list
List<E> subList(int fromIndex, int toIndex)
二维数组
//二维数组 int[][] arr = new int[][];
List<List<Integer>> list = new ArrayList<>();
List<Integer> l1 = new ArrayList<>();
l1.add(1);
l1.add(2);
l1.add(3);
List<Integer> l2 = new ArrayList<>();
l2.add(4);
l2.add(5);
l2.add(6);
list.add(l1);
list.add(l2);
System.out.println(list);
输出结果:
[[1,2,3],[4,5,6]]
要是对大家有所帮助的话,请帮我点个赞吧。