publicvoid setSex(String sex) {
this.sex = sex;
}
publicvoid setAge(int age) {
this.age = age;
}
publicTeacher() {
}
//方法:获得老师所教授的课程名
publicvoid getClass(String name, String teachClass) {
System.out.println(name + “老师所教授的课程是”+ teachClass);
}
}
//创建老师类的一个具体对象``public class Main {
public static void main(String[] args) {
//new一个具体对象teacherA`` TeacherteacherA = new Teacher();
//具体对象的属性值
teacherA.setName("马冬梅");
teacherA.setSex("女");
teacherA.setAge(22);
//具体对象的行为:马冬梅老师教授的课程是生物
teacherA.getClass(teacherA.getName(), "生物");
}``}
ok,通过以上示例,希望大家对于java的类和对象有了一个初步的概念和了解。
那么,为什么这种所谓的面向对象的编程理念会得到大家的接纳和推崇呢?
因为面向对象程序设计有以下优点:
1. 可重用:它是面向对象软件开发的核心思路,提高了开发效率。面向对象程序设计的抽象、继承、封装和多态四大特点都围绕这个核心。
2. 可扩展:它使面向对象设计脱离了基于模块的设计,便于软件的修改。
接下来就针对Java的继承、封装、多态和泛型 4 个特性进行讲解,来了解一下它们是如何实现代码重用和扩展的。
1.继承
如同现实生活中的子女继承父母的遗产一样,在java中继承指的是子类继承父类的属性和方法。见以下实例:
大家有没有发现语文老师类和数学老师类里的属性和方法大部分是相同的,以上说到java面向对象编程的优点是可重用性和可扩展性,如何通过继承来实现重用和扩展呢?
那就是通过再提炼一个上层父类–老师类来实现,语文老师类和数学老师类再作为子类对其继承使用。
如下:
采用这种向上抽象方式,是为了将多个类的通用属性和方法提取出来,放在它们的父类中,避免同样的代码写多份(即为了实现复用),在子类中只需要定义自己独有的属性和方法,以继承的方式在父类中获取通用属性和方法即可。
//继承代码结构public class Chinese extends Teacher{` `//定义自己独有的属性:工作内容` `Stringcontent;` ` //定义自己独有的方法` `publicvoid writeModels(String name, string content) {` `System.out.println(name + "老师主要工作内容是"+ content);` `}
}
特别说明:继承只能是单继承,即一个子类只能继承一个父类。
2.封装
封装的目的在于保护信息。
Java 提供了私有和公有的访问模式,类的公有接口代表外部的用户应该知道或可以知道的所有信息,私有的方法数据只能通过该类的成员代码来访问,这就可以确保不会发生不希望发生的事情。
封装主要优点如下:
那么,怎么理解封装实现了复用和扩展呢?
读者可以理解为所谓封装其实只是将属性和功能封装成类,并对类里的成员定义了不同的访问权限,最终还是通过与继承机制的结合实现的代码复用和扩展。
3.多态
所谓多态,就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用的到底是哪个类中实现的方法,必须在由程序运行期间才能决定。即不修改程序代码就可以让程序有多个运行状态可以选择,这就是多态性。
上文是根据为啥这个特性叫做多态性的角度给大家解释了一下,如果从实际运用中最终看到的实际效果这个方面来总结,可以理解为,多态是同一个行为具有多个不同表现形式或形态的能力。
举个形象的例子:
现实中,比如我们按下 F1 键这个动作:
如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮助;
在 Windows 下弹出的就是 Windows 帮助和支持。
同一个事件发生在不同的对象上会产生不同的结果,可见,多态实现了很好的扩展性。
4.泛型
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
public class GenericTest{` `// 泛型方法 printArray
publicstatic < E > void printArray( E[] inputArray )
{
// 输出数组元素 for( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
publicstatic void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[]doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { ‘H’, ‘E’, ‘L’, ‘L’, ‘O’ };
System.out.println( “整型数组元素为:” );
printArray( intArray ); // 传递一个整型数组
System.out.println( “\n双精度型数组元素为:” );
printArray(doubleArray ); // 传递一个双精度型数组
System.out.println( “\n字符型数组元素为:” );
printArray( charArray ); // 传递一个字符型数组
}
}
结果如下:
整型数组元素为:``1 2 3 4 5 ``双精度型数组元素为:``1.1 2.2 3.3 4.4 ``字符型数组元素为:``H E L L O
同一个方法,允许传入不同的参数类型,得到不同的运行结果,以此实现了代码的复用和扩展。
2、初始化过程
虽然本文讲解的绝大部分内容偏向应用,但对于初始化过程这种看上去有点涉及到底层原理的内容,考虑再三,还是要稍微提及一下,那是因为在实际工作中,经常发生因为对整个初始化过程的机制不了解而写出不合理代码,从而产生bug的现象。
本段会针对类的各成员的初始化顺序重点强调一下,这也是犯错最多且唯一我们能控制的地方,希望大家务必对此要有所了解。
话不多说,上实例:
class Father{` `static{` `System.out.println("父类静态代码块初始化" );` `}` `{` `System.out.println("父类代码块初始化" );` `}` `private static void s=print();` `public static void print()` `{` `System.out.println("父类静态方法" );` `}` `public Father()` `{` `System.out.println("父类无参构造函数初始化完成" );` `show();` `}` `public void show()` `{` `System.out.println("父类show()方法" );` `}
}class Son extends Father
{
static{
System.out.println(“子类静态代码块初始化” );
}
{
System.out.println(“子类代码块初始化” );
}
private static int i=1;
private String s=“子类私有成员变量” ;
public void show()
{
System.out.println(“子类show()方法:i=” +i);
}
public Son()
{
System.out.println(“子类构造函数初始化完成” );
System.out.println(“子类成员变量初始化完成:s=”+s);
show();
}}
public class TestClassLoadSeq {
public static void main(String[]args)
{
new Son();
}``}
执行顺序:
父类静态代码块初始化
父类静态方法
子类静态代码块初始化
父类代码块初始化
父类无参构造函数初始化完成
子类show()方法:i=1 //因为创建的是son实例,所以父类里的show方法被初始化时,实际调用的是子类show方法
子类代码块初始化
子类构造函数初始化完成
子类成员变量初始化完成:s=子类私有成员变量
子类show()方法:i=1
在遇到初始化失败的相关bug时,通常错误提示不会直接指向有问题的那行。
因此,需要你对类初始化过程有所了解,这样才能快速定位到哪个环节最有可能出问题。
3、常见的集合及方法
在日常的数据分析工作中,常常需要使用到集合来存储和处理数据,因此需要大家对集合的分类和功能有所了解。Java的集合框架分为两部分,分别对应两大接口:Collection接口和Map接口。以下就通过这两大接口开始讲解。
1.Collection接口
Collection接口涉及三种类型的集合:1.Set(规则集) 2.List(线性表) 3.Queue(队列),其层级关系如图:
ps:图片源自网络
这三种类型集合的常用方法及特性总结如下:
集合类型 | 特性 | 常用方法 |
Set | 集合中没有相同的元素 | hashCode() //返回对象存储的物理地址 equals() //判断值是否相等 |
List | 线性表是一个有序允许重复的集合 | add(int index) //指定下标添加元素 addAll(int index, Collection<? extends E> c) //指定下标处添加c中所有元素 get(int index) //返回指定下标元素 lastIndexOf(Object o) //返回相同元素的下标 listIterator() //返回遍历列表的迭代器 listIterator(int startIndex) //返回从startIndex开始的所有元素的迭代器 remove(int index) //删除指定下标的元素 set(int index, E element) //设置指定下标的元素 subList(int fromIndex, int toIndex) //返回从fromIndex到toIndex元素子列表 |
Queue | 是一种先进先出的数据结构 | offer(E e) //添加元素 poll() // 返回并删除队头元素,否则返回null remove() //返回并删除队头元素,否则抛出异常 peek() // 返回队头元素,否则返回null element() //返回队头元素,否则抛出异常 |
2.Map接口
Map接口涉及三种类型的集合:1.HashMap 2.LinkedHashMap 3.TreeMap。其层级关系如下:
Map----
|
|----SortMap----TreeMap
|
|----HashMap----LinkedHashMap
Map的特性为键值不能重复。每个键值对应着一个值,键与值一起存储在集合中。
Map接口中有如下方法:
clear() //删除所有条目
containsKey(Object key) //如果包含指定键值返回true
containsValue(Object value) //如果包含指定值返回true
get(Object key) //获得指定键值对应的值
entrySet() //返回包含条目的规则集
isEmpty() //判断是否空
keySet() //返回包含键值的一个规则集
put(Object key, Object value) //添加键值对
putAll( ) //将指定实例中的键值对添加到当前实例中
remove(Object key) //删除指定键值对应的值
size() //键值对个数
values() //返回包含的集合
4、常用的字符串处理方法
数据分析工作中,最基本的一项工作就是通过hive写类sql语言处理数据,而类sql语法中处理字符串的方法都是通过对java的字符串处理方法进行一层封装得到的,接下来,我们就一起来看下常用Java字符串处理方法有哪些。
字符串查找
String提供了两种查找字符串的方法,即indexOf与lastIndexOf方法。
1、indexOf(String s)
该方法用于返回参数字符串s在指定字符串中首次出现的索引位置,如果没有检索到字符串s,该方法返回-1
String str =“We are students”;``int size = str.indexOf(“a”); // 变量size的值是3
2、lastIndexOf(String str)
该方法用于返回字符串最后一次出现的索引位置。如果没有检索到字符串str,该方法返回-1。如果lastIndexOf方法中的参数是空字符串"" ,则返回的结果与length方法的返回结果相同。
获取指定索引位置的字符
使用charAt()方法可将指定索引处的字符返回。
String str = “hello word”;``char mychar = str.charAt(5); // mychar的结果是w
获取子字符串
通过String类的substring()方法可对字符串进行截取。
1、substring(int beginIndex)
该方法返回的是从指定的索引位置开始截取直到该字符串结尾的子串。
String str = “Hello,word”;``String substr = str.substring(3); //获取字符串,此时substr值为lo,word
2、substring(int beginIndex, int endIndex)
String str = “Hello word”;``String substr = str.substring(0,3); //substr的值为hel
去除空格
trim()方法返回字符串的副本,忽略前导空格和尾部空格。
字符串替换
replace()方法可实现将指定的字符或字符串替换成新的字符或字符串
String str= “address”;``String newstr = str.replace(“a”,“A”);// newstr的值为Address
判断字符串的开始与结尾
startsWith()方法与endsWith()方法分别用于判断字符串是否以指定的内容开始或结束。这两个方法的返回值都为boolean类型。
1、startsWith(Stringprefix)
该方法用于判断当前字符串对象的前缀是否是参数指定的字符串。
2、endsWith(Stringsuffix)
该方法用于判断当前字符串是否以给定的子字符串结束
判断字符串是否相等
1、equals(Stringotherstr)
如果两个字符串具有相同的字符和长度,则使用equals()方法比较时,返回true。同时equals()方法比较时区分大小写。
2、equalsIgnoreCase(Stringotherstr)
equalsIgnoreCase()方法与equals()类似,不过在比较时忽略了大小写。
字母大小写转换
字符串的toLowerCase()方法可将字符串中的所有字符从大写字母改写为小写字母,而toUpperCase()方法可将字符串中的小写字母改写为大写字母。
str.toLowerCase();
str.toUpperCase();
字符串分割
使用split()方法可以使字符串按指定的分隔字符或字符串对内容进行分割,并将分割后的结果存放在字符串数组中。
str.split(‘&’);
str.split(String sign, in limit);
该方法可根据给定的分割符对字符串进行拆分,并限定拆分的次数。
5、常用的日期处理方法
另一个在数据分析工作中,跟字符串处理一样使用较为频繁的就是关于日期的相关处理。
其中最常用到的日期处理类有:java.util.Date、java.util.Calendar、java.text.SimpleDateFormat。
1.java.util.Date的使用
构造函数
-
Date() :分配 Date 对象并用当前时间初始化此对象,以表示分配它的时间(精确到毫秒)。
-
Date(long date) :分配 Date 对象并初始化此对象,以表示自从标准基准时间(即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。
常用方法
2.java.text.SimpleDateFormat的使用
java.text.SimpleDateFormat主要用于格式化日期,需要说明的一点是该类的实例是线程不安全的。
构造函数
public SimpleDateFormat(String pattern):pattern是描述日期和时间格式的模式 如:yyyyMMDD
使用方式
public class TestDateFormat {
public voiddateFormat(){
Date date= new Date(); //创建不同的日期格式` `SimpleDateFormat dt1 = new SimpleDateFormat('YYYY-MM-DD HH:MM:SS');
dt1.format(date);//返回的日期格式如:2019-05-13 04:03:45
SimpleDateFormat dt2 = new SimpleDateFormat(‘YYYY-MM-DD’);
dt2.format(date);//返回的日期格式如:2019-05-13
SimpleDateFormat df3 = new SimpleDateFormat(“yyyy年MM月dd日 hh时mm分ss秒 EE”,Locale.CHINA) ;
dt3.format(date);//返回的日期格式如:2019年05月13日 04时03分45秒 星期一
}``}
3.java.util.Calendar的使用
java.util.Calendar是个抽象类,它可以通过特定的方式设置和读取日期的特定部分,比如年、月、日、时等,并为操作日历字段(例如获得下星期的日期)提供了一些方法。
创建实例
Calendar dt = Calendar.getInstance();
常见操作
//设置时间dt.setTime(new Date());` `//获取年月日时分秒
dt.get(Calendar.YEAR);dt.get(Calendar.MONTH);
dt.get(Calendar.DAY_OF_MONTH);dt.get(Calendar.HOUR);
dt.get(Calendar.MINUTE);dt.get(Calendar.SECOND);` `//获取上午下午
dt.get(Calendar.AM_PM);
//获取一周中的星期几dt.get(Calendar.DAY_OF_WEEK);` `//当前日期基础上加减指定天数
dt.add(Calendar.YEAR, -1);``dt.sub(Calendar.YEAR, -1);
6、json的解析与操作
json的表达能力非常强,一方面拥有常用的数据类型,另一方面可以表达复杂的数据结构。因此,在大数据领域,经常使用json作为信息的载体,将数据封装起来。所以,理解json的结构,对json进行解析与操作,在数据分析工作中非常重要。
下面是几个常用的 JSON 解析类库:
-
Json官方:Douglas Crockford在2001年开始推广使用的数据格式,解析最具有通用性,但是有点小麻烦
-
Gson:谷歌开发的 JSON 库,功能十分全面
-
FastJson:阿里巴巴开发的 JSON 库,性能十分优秀
-
Jackson:社区十分活跃且更新速度很快
下面我们主要通过代码示例来了解下java变量和json格式之间的相互转化以及json对象与字符串的相互转化。
1.编码
从 Java 变量到 JSON 格式的编码过程如下:
public void testJson() {
JSONObject object = new JSONObject();
//string
object.put(“string”,“string”);
//int
object.put(“int”,2);
//boolean
object.put(“boolean”,true);
//array
List integers = Arrays.asList(1,2,3);
object.put(“list”,integers);
//null
object.put(“null”,null);
System.out.println(object);``}
输出结果如下:
{“boolean”:true,“string”:“string”,“list”:[1,2,3],“int”:2}
2.解码
从 JSON 对象到 Java 变量的解码过程如下:
public void testJson2() {
JSONObjectobject = JSONObject
.parseObject("{\"boolean\":true,\"string\":\"string\",\"list\":[1,2,3],\"int\":2}");
//string
String s = object.getString("string");
System.out.println(s);
//int
int i = object.getIntValue("int");
System.out.println(i);
//boolean
boolean b = object.getBooleanValue("boolean");
System.out.println(b);
//list
List<Integer> integers = JSON.parseArray(object.getJSONArray("list").toJSONString(),Integer.class);
integers.forEach(System.out::println);
//null
System.out.println(object.getString("null"));``}
打印结果如下:string2``true``1``2``3``null
3.JSON 对象与字符串的相互转化
//前提说明:JSON是一个抽象类,以下实例中的parseObject(String text)、parseArray(String text)、toJSONString(JSONObject obj)、toJSONString(JSONArray arr)是该抽象类的静态方法 //从字符串解析JSON对象JSONObject obj =JSON.parseObject("{\"runoob\":\"测试实例\"}");
//从字符串解析JSON数组JSONArray arr =JSON.parseArray("[\"测试实例\",\"RUNOOB\"]\n");
//将JSON对象转化为字符串String objStr =JSON.toJSONString(obj);
//将JSON数组转化为字符串``String arrStr =JSON.toJSONString(arr);
7、正则表达式
数据分析过程中,经常需要对字符串进行匹配、替换、提取等操作,而有时简单的字符串处理方法已经无法满足复杂的处理逻辑时,就需要使用到正则表达式来完成。
这部分的内容,在ETL中的数据提取和处理环节应用非常频繁,所以大家要重点掌握。
概念
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
–摘自百度百科
作用
1.匹配:给定的字符串是否符合正则表达式的过滤逻辑
2.**提取:**可以将匹配成功的特定部分提取出来
3.**替换:**针对匹配成功的特定部分,替换为新的字符串
规则
图片摘自云游道士的博客
PS:由于篇幅有限,上述规则只是列出了常用的一些规则,如果在实际工作中发现上述规则不满足业务需求,可以百度一下,寻找其他规则。以下为一些常用的匹配示例。
示例
匹配目标 | 常用正则表达式 |
邮箱 | ^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$ |
手机号码 | ^1[3-9]\d{9}$ |
网页URL | ^(https?://)?[\S]+$ |
IP地址 | ^(((?:[1-9]\d?)|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))[.]){3}((?:[1-9]\d?)|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))$ |
密码(8-16位,只允许字母、数字、指定符号(_+-.?)) | ^[\w\-\.+?]{8,16}$ |
注意事项
1. 使用"|"时,应尽量使用括号来标识边界,否则容易引起歧义
2. 当使用正则匹配是否包含某一字符串时,不需要在前后加模糊匹配,否则会引起性能下降。例如匹配文本(“This is a pretty dog”)中是否包含“pre”开头的单词时,正则表达式应写作“pre[a-z]+”,而不要写作“.*pre[a-z]+.*”
3. 所有的特殊字符在[ ]内都将失去其原有的特殊含义,使用时要特别注意。上面的示例中密码的匹配就是一个字符(+?)含义变化例子
4. 正则表达式中的符号都是英文半角符号
5. 写好正则后,尤其是复杂的表达式,最好可以测试一下。
可以使用在线工具完成测试,如:
https://c.runoob.com/front-end/854
http://tool.oschina.net/regex/
在java中的使用示例
import java.util.regex.*;// 下面只是使用示例代码,不能直接运行
// 目标字符串String url ="https://aa.bb.com/cn/index.html";
// URL匹配规则String regex = "^(https?://)?[\S]+$";
// 编译正则表达式Pattern pattern =Pattern.compile(regex);
// 匹配正则表达式与目标字符串,返回匹配结果对象Matcher matcher =pattern.matcher(url);
// 判断字符串是否与正则表达式匹配成功boolean result =matcher.matches();
System.out.println(result);
PS:在java中还有一种忽略正则表达式内英文字母大小写的写法,意思是在匹配时,忽略目标字符串中英文字母的大小写,都能匹配成功,写法如下所示:
// 忽略大小写的写法``Pattern pattern =Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
8、异常处理
相信大家对异常都不陌生,当程序运行中发生错误时,通常都会抛出异常(Exception)来警醒开发人员,如果不处理就会导致程序直接退出。
数据分析工作中,经常会遇到各种问题,这时异常便提供了找到相应问题诱发原因的重要线索,因此我们要保持良好的编码习惯,避免错过重要提示线索。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)
言尽于此,完结
无论是一个初级的 coder,高级的程序员,还是顶级的系统架构师,应该都有深刻的领会到设计模式的重要性。
- 第一,设计模式能让专业人之间交流方便,如下:
程序员A:这里我用了XXX设计模式
程序员B:那我大致了解你程序的设计思路了
- 第二,易维护
项目经理:今天客户有这样一个需求…
程序员:明白了,这里我使用了XXX设计模式,所以改起来很快
- 第三,设计模式是编程经验的总结
程序员A:B,你怎么想到要这样去构建你的代码
程序员B:在我学习了XXX设计模式之后,好像自然而然就感觉这样写能避免一些问题
- 第四,学习设计模式并不是必须的
程序员A:B,你这段代码使用的是XXX设计模式对吗?
程序员B:不好意思,我没有学习过设计模式,但是我的经验告诉我是这样写的
从设计思想解读开源框架,一步一步到Spring、Spring5、SpringMVC、MyBatis等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了23种设计模式,整理的文件如下图一览无余!
搜集费时费力,能看到此处的都是真爱!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!**
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)
[外链图片转存中…(img-n5v04JLQ-1713799842194)]
言尽于此,完结
无论是一个初级的 coder,高级的程序员,还是顶级的系统架构师,应该都有深刻的领会到设计模式的重要性。
- 第一,设计模式能让专业人之间交流方便,如下:
程序员A:这里我用了XXX设计模式
程序员B:那我大致了解你程序的设计思路了
- 第二,易维护
项目经理:今天客户有这样一个需求…
程序员:明白了,这里我使用了XXX设计模式,所以改起来很快
- 第三,设计模式是编程经验的总结
程序员A:B,你怎么想到要这样去构建你的代码
程序员B:在我学习了XXX设计模式之后,好像自然而然就感觉这样写能避免一些问题
- 第四,学习设计模式并不是必须的
程序员A:B,你这段代码使用的是XXX设计模式对吗?
程序员B:不好意思,我没有学习过设计模式,但是我的经验告诉我是这样写的
[外链图片转存中…(img-bX8k1SPn-1713799842194)]
从设计思想解读开源框架,一步一步到Spring、Spring5、SpringMVC、MyBatis等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了23种设计模式,整理的文件如下图一览无余!
[外链图片转存中…(img-wdCwIAkt-1713799842194)]
搜集费时费力,能看到此处的都是真爱!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!