先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
}
=======================================================================
当使用一个泛型类时 (包括声明变量和创建对象两种情况) , 都应该为这个泛型类传入一个类型实参。如果没有传入类型实际参数 , 编译器就会提出泛型警告 。 假设现在需要定义一个方法 , 该方法里有一个集合形参,集合形参的元素类型是不确定的, 那应该怎样定义呢?
为了表示各种泛型 List 的父类,可以使用类型通配符,类型通配符是一个问号 ( ?) ,将一个问号作为类型实参传给 List 集合 , 写作: List<?> (意思是元素类型未知的 List ) 。 这个问号(?)被称为通配符,它的元素类型可以匹配任何类型 。
public void test(List<?> c){
for (int i = 0 ; i < c . size() ; i++ ){
System . out .println (c.get(i) );
}
}
在使用任何类型的 List 来调用它,程序依然可以访问集合 c 中的元素,其类型是 Object,这永远是安全的,因为不管 List 的真实类型是什么,它包含的都是Object 。
但这种带通配符的 List 仅表示它是各种泛型 List 的父类,并不能把元素加入到其中 。 例如,如下代码将会引起编译错误 :
List<?> c = new ArrayLi st ();
//下丽程序引起编译错误
c . add(new Object()) ;
因为程序无法确定 c 集合中元素的类型,所以不能向其中添加对象 。 根据前面的 List接口定义的代码可以发现 : add()方法有类型参数 E 作为集合的元素类型,所以传给 add 的参数必须是 E 类的对象或者其子类的对象 。 但因为在该例中不知道 E 是什么类型,所以程序无法将任何对象"丢进"该集合 。 唯一的例外是 nulL ——它是所有引用类型的实例 。
另 一方面 , 程序可以调用 get()方法来返回 List<?>集合指定索引处的元素,其返回值是一个未知类型,但可以肯定的是,它总是一个 Object 。 因此,把 get()的返回值赋值给一个 Object 类型的变量,或者放在任何希望是 Object 类型的地方都可以 。
当直接使用 List<?>这种形式时,即表明这个 List 集合可以是任何泛型 List 的父类 。 但还有一种特殊的情形,程序不希望这个 List<?>是任何泛型 List 的父类,只希望它代表某一类泛型 List 的父类。
一个简单的绘图程序,下面先定义三个形状类 :
Shape.java
// 定义一个抽象类Shape
public abstract class Shape
{
public abstract void draw(Canvas c);
}
Circle.java
// 定义Shape的子类Circle
public class Circle extends Shape
{
// 实现画图方法,以打印字符串来模拟画图方法实现
public void draw(Canvas c)
{
System.out.println(“在画布” + c + “上画一个圆”);
}
}
Rectangle.java
// 定义Shape的子类Rectangle
public class Rectangle extends Shape
{
// 实现画图方法,以打印字符串来模拟画图方法实现
public void draw(Canvas c)
{
System.out.println(“把一个矩形画在画布” + c + “上”);
}
}
上面定义了 三个形状类,其中 Shape 是一个抽象父类 , 该抽象父类有两个子类 : Circle 和 Rectangle 。接下来定义一个 Canvas 类 , 该画布类可以画数量不等的形状 (Shape 子类的对象) 。
Canvas.java
public class Canvas
{
// // 同时在画布上绘制多个形状
// public void drawAll(List shapes)
// {
// for (Shape s : shapes)
// {
// s.draw(this);
// }
// }
// 同时在画布上绘制多个形状,使用被限制的泛型通配符
public void drawAll(List<? extends Shape> shapes)
{
for (Shape s : shapes)
{
s.draw(this);
}
}
public static void main(String[] args)
{
List
Canvas c = new Canvas();
// 由于List
// 所以下面代码引发编译错误
c.drawAll(circleList);
}
}
程序中使用了被限制的泛型通配符。
被限制的泛型通配符表示如下 :
//它表示泛型形参必须是 Shape 子类的 List
List<? extends Shape>
List<? extends Shape>是受限制通配符的例子,此处的问号 (?) 代表一个未知的类型,就像前面看到的通配符一样 。 但是此处的这个未知类型一定是 Shape 的子类型(也可以是 Shape 本身),因此可以把 Shape 称为这个通配符的上限 (upper bound) 。
类似地,由于程序无法确定这个受限制的通配符的具体类型,所以不能把 Shape 对象或其子类的对象加入这个泛型集合中 。 例如,下面代码就是错误的:
public void addRectangle(List<? extends Shape> shapes){
//下面代码引起编译错误
shapes .add(O , new Rectangle());
}
简而言之,这种指定通配符上限的集合,只能从集合中取元素(取出的元素总是上限的类型) ,不能向集合中添加元素(因为编译器没法确定集合元素实际是哪种子类型) 。
除可以指定通配符的上限之外, Java 也允许指定通配符的下限,通配符的下限用<? super 类型>的方式来指定,通配符下限的作用与通配符上限的作用恰好相反 。
指定通配符的下限就是为了支持类型型变 。 比如 Foo 是 Bar 的子类,当程序需要一个 A<? super Bar>变量时,程序可以将 A 、 A赋值给 A<? super Bar>类型的变量,这种型变方式被称为逆变。
对于逆变的泛型集合来说,编译器只知道集合元素是下限的父类型,但具体是哪种父类型则不确定。
因此,这种逆变的泛型集合能向其中添加元素(因为实际赋值的集合元素总是逆变声明的父类) ,从集合中取元素时只能被当成 Object 类型处理(编译器无法确定取出的到底是哪个父类的对象)。
假设实现一个工具方法:实现将 src 集合中的元素复制到 dest 集合的功能,因为 dest 集合可以保存 src 集合中的所有元素,所以 dest 集合元素的类型应该是 src 集合元素类型的父类。
对于上面的 copy()方法,可以这样理解两个集合参数之间的依赖关系;不管 src 集合元素的类型是什么,只要 dest 集合元素的类型与前者相同或者是前者的父类即可,此时通配符的下限就有了用武之地 。
下面程序采用通配符下限的方式来实现该 copy()方法 :
MyUtils.java
public class MyUtils
{
// 下面dest集合元素类型必须与src集合元素类型相同,或是其父类
public static T copy(List<? super T> dest
, List src)
{
T last = null;
for (T ele : src)
{
last = ele;
// 逆变的泛型集合添加元素是安全的
dest.add(ele);
}
return last;
}
public static void main(String[] args)
{
List ln = new ArrayList<>();
List li = new ArrayList<>();
li.add(5);
// 此处可准确的知道最后一个被复制的元素是Integer类型
// 与src集合元素的类型相同
Integer last = copy(ln , li); // ①
System.out.println(ln);
}
}
使用这种语句,就可以保证程序的①处调用后推断出最后一个被复制的元素类型是 Integer,而不是笼统的 Number 类型 。
实际上, Java 集合框架中的 TreeSet有一个构造器也用到了这种设定通配符下限的语法,如下所示 :
//下面的 E 是定义 TreeSet 类时的泛型形参
TreeSet(Comparator<? super E> c)
通过这种带下限的通配符的语法 ,可以在创建 TreeSet 对象时灵活地选择合适的 Comparator 。 假定需要创建一个 TreeSet集合,并传入一个可以比较 String 大小 的 Comparator , 这个 Comparator既可以是 Comparator,也可以是 Comparator一一只要尖括号里传入的类型是 String 的父类型(或它本身) 即可。
TreeSetTest.java
public class TreeSetTest
{
public static void main(String[] args)
{
// Comparator的实际类型是TreeSet的元素类型的父类,满足要求
TreeSet ts1 = new TreeSet<>(
new Comparator()
{
public int compare(Object fst, Object snd)
{
-
return hashCode() > snd.hashCode() ? 1
- hashCode() < snd.hashCode() ? -1 : 0;
}
});
ts1.add(“hello”);
ts1.add(“wa”);
// Comparator的实际类型是TreeSet元素的类型,满足要求
TreeSet ts2 = new TreeSet<>(
new Comparator()
{
public int compare(String first, String second)
{
-
return first.length() > second.length() ? -1
- first.length() < second.length() ? 1 : 0;
}
});
ts2.add(“hello”);
ts2.add(“wa”);
System.out.println(ts1);
System.out.println(ts2);
}
}
J ava 泛型不仅允许在使用通配符形参 时设定上限,而且可以在定义泛型形参 时设定 上限 ,用于表示传给该泛型形参的实际类型要么是该上限类型 ,要么是该上限类型的子类。
下面程序示范了这种用法 :
Apple.java
public class Apple
{
T col;
public static void main(String[] args)
{
Apple ai = new Apple<>();
Apple ad = new Apple<>();
// 下面代码将引起编译异常,下面代码试图把String类型传给T形参
// 但String不是Number的子类型,所以引发编译错误
// Apple as = new Apple<>(); // ①
}
}
上面程序定义了 一个 Apple 泛型类 , 该 Apple 类的泛型形参的上限是 Number 类,这表明使用 Apple类时为 T 形参传入的实际类型参数只能是 Number 或 Number 类的子类 。 上面程序在①处将引起编译错误 : 类型 T 的上限是 Number 类型,而此处传入的实际类型是 String 类型 ,既不是 Number 类型,也不
是 Number 类型的子类型,所以将会导致编译错误 。
在一种更极端的情况下,程序需要为泛型形参设定多个上限 (至多有一个父类上限,可以有多个接口上限),表明该泛型形参必须是其父类的子类(是父类本身也行),并且实现多个上限接口。
如下代码所示 :
// 表明 T 类型必须是 Number 类或其子类,并必须实现 java.io.Seria1izab1e 接口
pub1ic c1ass Apple<T extends Number & java. i o . Serializab1e>{
……
}
======================================================================
假设需要实现这样一个方法一一该方法负责将一个 Object 数组的所有元素添加到一个 Collection 集合中 。
考虑采用如下代码来实现该方法:
static void fromArrayToCollection(Object[) a , Collection c){
for (Object 0 : a){
c . add (o);
}
}
上面定义的方法没有任何问题,关键在于方法中的 c 形参,它的数据类型是 Collection。 Collection不是 Collection的子类型一一所以这个方法的功能非常有限,它只能将 Object[]数组的元素复制到元素为 Object ( Object 的子类不行)的 Collection 集合中。
下面代码将引起编译错误 :
String[] strArr = {“a” , "b " };
List strList = new ArrayList<>() ;
/ / Collection对象不能当成 Collection使用,下面代码出现编译错误
fromArrayToCollection(strArr , strList);
为了解决这个问题,可以使用 Java 5 提供的泛型方法 (Generic Method)。所谓泛型方法,就是在声明方法时定义→个或多个泛型形参。
泛型方法的语法格式如下:
修饰符 <T , S> 返回值类型方法名(形参列表){
//方法体 … .
}
采用支持泛型的方法,就可以将上面的fromArrayToCollection 方法改为如下形式:
static void fromArrayToCollection (T[] a , Collection c){
for (T 0 : a){
c.add(o) ;
}
}
下面程序示范了完整的用法 :
GenericMethodTest.java
public class GenericMethodTest
{
// 声明一个泛型方法,该泛型方法中带一个T泛型形参,
static void fromArrayToCollection(T[] a, Collection c)
{
for (T o : a)
{
c.add(o);
}
}
public static void main(String[] args)
{
Object[] oa = new Object[100];
Collection co = new ArrayList<>();
// 下面代码中T代表Object类型
fromArrayToCollection(oa, co);
String[] sa = new String[100];
Collection cs = new ArrayList<>();
// 下面代码中T代表String类型
fromArrayToCollection(sa, cs);
// 下面代码中T代表Object类型
fromArrayToCollection(sa, co);
Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection cn = new ArrayList<>();
// 下面代码中T代表Number类型
fromArrayToCollection(ia, cn);
// 下面代码中T代表Number类型
fromArrayToCollection(fa, cn);
// 下面代码中T代表Number类型
fromArrayToCollection(na, cn);
// 下面代码中T代表Object类型
fromArrayToCollection(na, co);
// 下面代码中T代表String类型,但na是一个Number数组,
// 因为Number既不是String类型,
// 也不是它的子类,所以出现编译错误
// fromArrayToCollection(na, cs);
}
}
大多数时候都可以使用泛型方法来代替类型通配符 。
例如,对于 Java 的 Collection 接口中两个方法定义 :
public interface Collection{
boolean containsAll (Coll ection<?> c);
boolean addAll(Collection<? extends E> c) ;
……
}
上面集合中两个方法的形参都采用了类型通配符的形式,也可以采用 泛型方法的形式, 如下所示 :
public interface Collection{
boolean containsAll(Collection c);
boolean addAll(Col工ection c) ;
……
}
上面方法使用了 泛型形式 , 这时定义泛型形参时设定上限(其中 E 是 Collection 接口里定义的泛型,在该接口里 E 可当成普通类型使用) 。
上面两个方法中泛型形参 T 只使用了 一次,泛型形参 T 产生的唯一效果是可以在不同的调用点传入不同的实际类型 。对于这种情况,应该使用通配符 : 通配符就是被设计用来支持灵活的子类化的 。
技术学习总结
学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。
最后面试分享
大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
boolean addAll(Col工ection c) ;
……
}
上面方法使用了 泛型形式 , 这时定义泛型形参时设定上限(其中 E 是 Collection 接口里定义的泛型,在该接口里 E 可当成普通类型使用) 。
上面两个方法中泛型形参 T 只使用了 一次,泛型形参 T 产生的唯一效果是可以在不同的调用点传入不同的实际类型 。对于这种情况,应该使用通配符 : 通配符就是被设计用来支持灵活的子类化的 。
技术学习总结
学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。
[外链图片转存中…(img-muwneS2W-1713605401662)]
[外链图片转存中…(img-IkhgQbMD-1713605401662)]
[外链图片转存中…(img-VVshIFDK-1713605401663)]
最后面试分享
大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!
[外链图片转存中…(img-OyEWsoN9-1713605401663)]
[外链图片转存中…(img-Hpdvsca0-1713605401664)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-FBlJHZSP-1713605401664)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!