【Java学习—(17)泛型、集合框架、List接口的常用方法】

(点击跳转即可哦)

java学习专栏

LeetCode刷题专栏



泛型的应用

定义泛型对象时,只能使用类

基本类型不能保存到泛型中,必须使用包装类

泛型的引出

看下面这个代码

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]]

要是对大家有所帮助的话,请帮我点个赞吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值