先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
@Override
public String toString() {
return symbol;
}
public double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}
构造方法将Lambda存储在实例属性中,apply
方法将调用转发给Lambda,代码量更少,逻辑也更清晰。
使用Lambda需要注意的地方是:Lambda没有名称和文档;如果计算比较复杂,或者代码量超过几行,就不要用Lambda了。
Lambda表达式一行是最好的,三行是极限!不能再多了
传递给枚举构造方法的参数是在静态环境中计算的。因此,枚举构造方法中的Lambda表达式不能访问枚举的实例成员。如果枚举类型具有难以理解的特殊方法,使用原先的实现方式仍是首选。
匿名类在Lambda时代并未过时,可以依据以下几点来进行选择:
1. 如果想创建抽象类的实例,可以用匿名类来完成
2. 如果一个接口有多个抽象方法,创建实例要用匿名类
3. Lambda无法获得对自身的引用
在Lambda中,关键字this指向外围实例,这通常是我们想要的
不要序列化一个 Lambda 或 匿名类实例,如果想要可序列化的函数对象,如Comparator
,就使用私有静态嵌套类的实例。
如果方法引用看起来更简短更清晰,就用方法引用;否则还是用Lambda
Java提供了一种生成函数对象的方法,比lambda还要简洁:方法引用(method references),就是常说的::
运算符
下面代码是用来保持从任意键到Integer
的映射:
map.merge(key, 1, (count, incr) -> count + incr);
代码使用了Java 8中的 Map 接口中的merge
方法,如果没有给定key的映射,就插入默认值(上面代码里的1);如果映射已经存在,则将函数应用于当前值和指定值,并用结果覆盖当前值。这里的函数是将现有值count递增incr
Integer 类(和所有其他包装数字基本类型)提供了一个静态方法sum
,只传入这个方法的引用也行:
map.merge(key, 1, Integer::sum);
使用了方法引用的代码更简洁
但有时候Lambda会比方法引用更简洁,大多数情况是方法与lambda相同的类中,例如下面的代码发生在GoshThisClassNameIsHumongous
类里:
- 方法引用
service.execute(GoshThisClassNameIsHumongous::action);
- Lambda
service.execute(() -> action());
类似的还有 Function
接口,它用一个静态工厂方法返回 id 函数 Function.identity()
。如果使用等效的lambda内联代码:
x -> x
这样会更简洁
许多方法引用是指静态方法,但有4种方法没有引用静态方法:
| 方法引用类型 | 范例 | Lambda等式 |
| — | — | — |
| 静态 | Integer::parseInt
| str -> Integer.parseInt(str)
|
| 有限制 | Instant.now()::isAfter
| Instant then = Instant.now();
t-> then.isAfter(t)
|
| 无限制 | String::toLowerCase
| str -> str.toLowerCase()
|
| 类构造器 | TreeMap<K, V>::new
| () -> new TreeMap<K, V>
|
| 数组构造器 | int[]::new
| len -> new int[len]
|
无限制的引用经常用在流管道(Stream pipeline)中作为映射和过滤函数;构造器引用是充当工厂对象
如果标准函数接口能满足要求,应该优先使用它,而不是专⻔自己创建新的函数接口。
以LinkedHashMap
为例,可以通过重写其protected removeEldestEntry
方法将此类用作缓存,每次将新的key值加入到map时都会调用该方法。以下代码重写允许map最多保存100个条目,然后在每次添加新key值时删除最老的条目:
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > 100;
}
用Lambda可以做得更好,自己定义一个函数接口如下:
@FunctionalInterface
interface EldestEntryRemovalFunction<K,V>{
boolean remove(Map<K,V> map, Map.Entry<K,V> eldest);
}
这个接口可以正常工作,但是没必要,因为java.util.function
包提供了大量标准函数式接口以供使用。
许多标准函数式接口都提供了有用的默认方法。如Predicate
接口提供了组合判断的方法。标准的BiPredicate<Map<K,V>, Map.Entry<K,V>>
接口应优先于自定义的EldestEntryRemovalFunction
接口的使用。
EldestEntryRemovalFunction
接口使用@FunctionalInterface
注解进行标注的。这个注解类型本质上与@Override
一样,有三个目的:
-
告诉读者这个接口是针对Lambda设计的
-
这个接口不会进行编译,除非他只有一个抽象方法
-
避免后续维护人员不小心给该接口添加抽象方法
始终使用@FunctionalInterface
1注解标注自己写的函数式接口
| 接口 | 方法 | 示例 |
| — | — | — |
| UnaryOperator | T apply(T t) | String::toLowerCase
|
| BinaryOperator | T apply(T t1, T t2) | BigInteger::add
|
| Predicate | boolean test(T t) | Collection::isEmpty
|
| Function<T,R> | R apply(T t) | Arrays::asList
|
| Supplier | T get() | Instant::now
|
| Consumer | void accept(T t) | System.out::println
|
-
这六个基础接口各自还有3种变体(int、long、double),例如
predicate
的变体IntPredicate
-
Function
接口还有9种变体,LongToIntFunction
、DoubleToObjFunction
等 -
这三种基础函数接口还有带两个参数的版本,
BiPredicate <T,U>
、BiFunction <T,U,R>
、BiConsumer <T,U>
-
还有
BiFunction
变体用于返回三个相关的基本类型:ToIntBiFunction<T,U>
,ToLongBiFunction<T,U>
和ToDoubleBiFunction <T,U>
-
Consumer
接口也有带两个参数的变体版本,带一个对象和一个基本类型:ObjDoubleConsumer <T>
,ObjIntConsumer <T>
和ObjLongConsumer <T>
-
还有一个
BooleanSupplier
接口,它是Supplier
的一个变体,返回boolean
注意:不要用带包装类型的基础函数接口来代替基本函数接口。使用装箱基本类型进行批量操作处理,后果可能是致命的。
什么时候应该自己编写接口呢?
答案是:如果没有一个标准的函数接口能够满足需求时
以Comparator <T>
为例,它的结构与ToIntBiFunction <T, T>
接口相同。Comparator
有自己的接口有以下几个原因:
-
每当在API中使用时,其名称提供了良好的文档信息
-
Comparator
接口对于如何构成一个有效的实例,有着严格的条件限制 -
这个接口配置了大量好用的default方法,可以对
Comparator
进行转换和合并
如果所需要的函数接口与Comparator
一样具有以下特征,就需要自己编写专用的函数接口了:
-
通用,并且将受益于描述性的名称
-
具有与其关联的严格的契约
-
将受益于定制的缺省方法
在Java 8中添加了Stream API,以简化串(并)行执行批量操作的任务。
Stream表示有限或无限的数据元素序列,Stream pipeline,表示对这些元素的多级计算。Stream中的元素可以来自集合、数组、文件、正则表达式模式匹配器、伪随机数生成器和其他Stream。数据可以是对象引用或基本类型(int、long、double)。
一个Stream pipeline包含一个 源Stream,几个中间操作,1个终止操作。每个中间操作都以某种方式转换Stream,比如过滤操作。终止操作会对Stream执行一个最终计算,比如返回一个List,打印所有元素等。
- Stream pipeline是lazy的:直到调用终止操作时才会开始计算
没有终止操作的的Stream pipeline是静默的,所以终止操作千万不能忘
- Stream API是fluent的:所有包含pipeline的调用可以链接成一个表达式
介绍完Stream之后,肯定就会有小伙伴们开始思考了,我们应该在什么时候用呢?
其实并没有任何硬性的规定,但可以从以下例子中得到启发:
例一:
读取字典中的单词,打印出单词出现次数大于某值的所有“换位词”
换位词:包含相同字母,但顺序不同的单词
如果换位词一样,这里就认为是同一个单词
public class Anagrams {
public static void main(String[] args) throws IOException {
File dictionary = new File(args[0]);
int minGroupSize = Integer.parseInt(args[1]);
Map<String, Set> groups = new HashMap<>();
try (Scanner s = new Scanner(dictionary)) {
while (s.hasNext()) {
String word = s.next();
groups.computeIfAbsent(alphabetize(word),
(unused) -> new TreeSet<>()).add(word);
}
}
for (Set group : groups.values())
if (group.size() >= minGroupSize)
System.out.println(group.size() + ": " + group);
}
private static String alphabetize(String s) {
char[] a = s.toCharArray();
Arrays.sort(a);
return new String(a);
}
}
将每个单词插入到map中中使用了computeIfAbsent
方法,computeIfAbsent
方法对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hashMap 中。
语法为:
hashmap.computeIfAbsent(K key, Function remappingFunction)
参数说明:
-
key - 键
-
remappingFunction - 重新映射函数,用于重新计算value
例二:
这个例子大量使用了Stream
public class Anagrams {
public static void main(String[] args) throws IOException {
Path dictionary = Paths.get(args[0]);
int minGroupSize = Integer.parseInt(args[1]);
try (Stream words = Files.lines(dictionary)) {
words.collect(
groupingBy(word -> word.chars().sorted()
.collect(StringBuilder::new,
(sb, c) -> sb.append((char) c),
StringBuilder::append).toString()))
.values().stream()
.filter(group -> group.size() >= minGroupSize)
.map(group -> group.size() + ": " + group)
.forEach(System.out::println);
}
}
}
如果你发现这段代码难以阅读,别担心,我也难看懂吗,在工作里面也是不提倡的,所以滥用Stream会使得程序代码难以读懂和维护
例三:
下面的代码和例二的逻辑相同,它没有过度使用Stream,代码可读性很强:
public class Anagrams {
public static void main(String[] args) throws IOException {
Path dictionary = Paths.get(args[0]);
int minGroupSize = Integer.parseInt(args[1]);
try (Stream words = Files.lines(dictionary)) {
words.collect(groupingBy(word -> alphabetize(word)))
.values().stream()
.filter(group -> group.size() >= minGroupSize)
.forEach(g -> System.out.println(g.size() + ": " + g));
}
}
// alphabetize method is the same as in original version
private static String alphabetize(String s) {
char[] a = s.toCharArray();
Arrays.sort(a);
return new String(a);
}
}
它在一个try-with-resources
块中打开文件,获得一个由文件中的所有代码的Stream。Stream中的pipeline没有中间操作,终止操作是将所有单词集合到一个映射中,按照它们的换位词对单词进行分组
values().stream()
打开了一个新的Stream<List<String>>
,这个Stream里的元素都是换位词,filter进行了过滤,忽略大小小于minGroupSize
的所有组,最后由终结操作forEach打印剩下的同位词组。
提高Stream代码的可读性有两个要求:
-
在没有显式类型的情况下,认真命名Lambda参数
-
使用辅助方法(上面的
alphabetize
),因为pipeline缺少显式类型信息和命名临时变量
需要提醒一点,使用Stream处理char类型的数据有风险:
例四:
“Hello world!”.chars().forEach(System.out::print);
发现它打印 721011081081113211911111410810033。这是因为“Hello world!”.chars()
返回的Stream的元素不是char值,而是int,修改方法是加一个强制类型转换:
“Hello world!”.chars().forEach(x -> System.out.print((char) x));
所以应该避免使用Stream来处理char值
综上所述,Stream适合完成下面这些工作:
-
统一转换元素序列
-
过滤元素序列
-
使用单个操作组合元素序列(例如添加、连接或计算最小值)
-
将元素序列累积到一个集合中,可能通过一些公共属性将它们分组
-
在元素序列中搜索满足某些条件的元素
假设Card是一个不变值类,用于封装Rank和Suit,下面代码求他们的笛卡尔积:
private static List newDeck() {
List result = new ArrayList<>();
for (Suit suit : Suit.values())
for (Rank rank : Rank.values())
result.add(new Card(suit, rank));
return result;
}
基于Stream实现的代码如下:
private static List newDeck() {
return Stream.of(Suit.values())
.flatMap(suit ->
Stream.of(Rank.values())
.map(rank -> new Card(suit, rank)))
.collect(toList());
}
其中用到了 flatMap
方法:这个操作将一个Stream中的每个元素都映射到一个Stream中,然后将这些新的Stream全部合并到一个Stream(或展平它们)。
newDeck的两个版本中到底哪一个更好?这就是仁者见仁智者见智的问题了,取决于你的个人喜好 : )
Stream最重要的是把将计算结构构造成一系列变型,其中每个阶段的结果尽可能接近前一阶段结果的纯函数(pure function)。纯函数的结果仅取决于其输入的函数:它不依赖于任何可变状态,也不更新任何状态。为了实现这一点,Stream操作的任何中间操作和终结操作都应该是没有副作用的。
如下代码,将单词出现的频率打印出来:
Map<String, Long> freq = new HashMap<>();
try (Stream words = new Scanner(file).tokens()) {
words.forEach(word -> {
freq.merge(word.toLowerCase(), 1L, Long::sum);
});
}
实际上这根本不是Stream代码,只不过是伪装成Stream的迭代代码。可读性也差,forEach
里面逻辑太多了,正确的应该是这么写:
Map<String, Long> freq;
try (Stream words = new Scanner(file).tokens()) {
freq = words
.collect(groupingBy(String::toLowerCase, counting()));
}
所以forEach 操作应仅用于报告Stream计算的结果,而不是进行计算
对于初学者来说,可以忽略Collector
接口,将其看做是黑盒对象即可,这个黑盒可以将Stream的元素合并到单个集合里。
有三个这样的Collector
:toList()
、toSet()
和 toCollection(collectionFactory)
。基于此,我们可以从频率表中提取排名前10的单词列表:
List topTen = freq.keySet().stream()
.sorted(comparing(freq::get).reversed())
.limit(10)
.collect(toList());
注意上述代码用的是.collect(toList())
,而不是.collect(Collectors.toList())
,这是因为静态导入了Collectors
所有成员,也是一种提高代码可读性的手段。
接下来介绍Collector
中比较重要的三个方法:
1. toMap(keyMapper、valueMapper)
它接受两个函数:一个将Stream元素映射到键,另一个将它映射到值。例如下面将枚举的字符串形式映射到枚举本身:
private static final Map<String, Operation> stringToEnum =
Stream.of(values()).collect(toMap(Object::toString, e -> e));
还有带三个参数的toMap
,假设有一个Stream代表不同艺术家(artists)的专辑(albums),可以得到每个歌唱家最畅销的那一张专辑,用map来存储:
Map<Artist, Album> topHits = albums.collect(toMap(Album::artist, a->a, maxBy(comparing(Album::sales))));
比较器使用静态工厂方法maxBy
,它是从BinaryOperator
import进来的。此方法将Comparator<T>
转换为BinaryOperator<T>
,用于计算指定比较器产生的最大值。
对于`toMap`,阿里巴巴开发规约也专门做了要求:
2. groupingBy
该方法返回Collector
,基于分类函数(classifier function)将元素分类,返回值是一个map,value是存储了每个类别的所有元素的List
words.collect(groupingBy(word -> alphabetize(word)))
上面代码就返回的是collect,key是alphabetize(word)
,value是word列表
还有传入两个参数的groupingBy
,传入counting()
作为下游收集器,这样会生成一个映射,将每个类别与该类别中的元素数量关联起来
Map<String, Long> freq = words.collect(groupingBy(String::toLowerCase, counting()));
最后
小编在这里分享些我自己平时的学习资料,由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
程序员代码面试指南 IT名企算法与数据结构题目最优解
这是” 本程序员面试宝典!书中对IT名企代码面试各类题目的最优解进行了总结,并提供了相关代码实现。针对当前程序员面试缺乏权威题目汇总这一-痛点, 本书选取将近200道真实出现过的经典代码面试题,帮助广“大程序员的面试准备做到万无一失。 “刷”完本书后,你就是“题王”!
《TCP-IP协议组(第4版)》
本书是介绍TCP/IP协议族的经典图书的最新版本。本书自第1版出版以来,就广受读者欢迎。
本书最新版进行」护元,以体境计算机网络技不的最新发展,全书古有七大部分共30草和7个附录:第一部分介绍一些基本概念和基础底层技术:第二部分介绍网络层协议:第三部分介绍运输层协议;第四部分介绍应用层协议:第五部分介绍下一代协议,即IPv6协议:第六部分介绍网络安全问题:第七部分给出了7个附录。
Java开发手册(嵩山版)
这个不用多说了,阿里的开发手册,每次更新我都会看,这是8月初最新更新的**(嵩山版)**
MySQL 8从入门到精通
本书主要内容包括MySQL的安装与配置、数据库的创建、数据表的创建、数据类型和运算符、MySQL 函数、查询数据、数据表的操作(插入、更新与删除数据)、索引、存储过程和函数、视图、触发器、用户管理、数据备份与还原、MySQL 日志、性能优化、MySQL Repl ication、MySQL Workbench、 MySQL Utilities、 MySQL Proxy、PHP操作MySQL数据库和PDO数据库抽象类库等。最后通过3个综合案例的数据库设计,进步讲述 MySQL在实际工作中的应用。
Spring5高级编程(第5版)
本书涵盖Spring 5的所有内容,如果想要充分利用这一领先的企业级 Java应用程序开发框架的强大功能,本书是最全面的Spring参考和实用指南。
本书第5版涵盖核心的Spring及其与其他领先的Java技术(比如Hibemate JPA 2.Tls、Thymeleaf和WebSocket)的集成。本书的重点是介绍如何使用Java配置类、lambda 表达式、Spring Boot以及反应式编程。同时,将与企业级应用程序开发人员分享一些见解和实际经验,包括远程处理、事务、Web 和表示层,等等。
JAVA核心知识点+1000道 互联网Java工程师面试题
企业IT架构转型之道 阿里巴巴中台战略思想与架构实战
本书讲述了阿里巴巴的技术发展史,同时也是-部互联网技 术架构的实践与发展史。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
符、MySQL 函数、查询数据、数据表的操作(插入、更新与删除数据)、索引、存储过程和函数、视图、触发器、用户管理、数据备份与还原、MySQL 日志、性能优化、MySQL Repl ication、MySQL Workbench、 MySQL Utilities、 MySQL Proxy、PHP操作MySQL数据库和PDO数据库抽象类库等。最后通过3个综合案例的数据库设计,进步讲述 MySQL在实际工作中的应用。
[外链图片转存中…(img-zSUI2TPS-1713703034751)]
Spring5高级编程(第5版)
本书涵盖Spring 5的所有内容,如果想要充分利用这一领先的企业级 Java应用程序开发框架的强大功能,本书是最全面的Spring参考和实用指南。
本书第5版涵盖核心的Spring及其与其他领先的Java技术(比如Hibemate JPA 2.Tls、Thymeleaf和WebSocket)的集成。本书的重点是介绍如何使用Java配置类、lambda 表达式、Spring Boot以及反应式编程。同时,将与企业级应用程序开发人员分享一些见解和实际经验,包括远程处理、事务、Web 和表示层,等等。
[外链图片转存中…(img-sWhgXVQe-1713703034751)]
JAVA核心知识点+1000道 互联网Java工程师面试题
[外链图片转存中…(img-VeUPu5HE-1713703034752)]
[外链图片转存中…(img-PFzmXV1N-1713703034752)]
企业IT架构转型之道 阿里巴巴中台战略思想与架构实战
本书讲述了阿里巴巴的技术发展史,同时也是-部互联网技 术架构的实践与发展史。
[外链图片转存中…(img-qg0ITJx7-1713703034753)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-SuBXrTEA-1713703034753)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!