实例
1、list实例化,空值和循环遍历
List<String> list =null;
//list通过不同的赋值,可以有三种状态
//状态1,list尚未实例化
list 就为 null; //这时候list是null
//状态二 list实例化但元素数量为0,
list = new ArrayList<>();
//这时候 list != null 但 list.isEmpty()为空
//可以使用list!=null && list.isEmpty() 为判断
//状态三 list实例化且元素数量不为0,
list = Arrays.asList(arr.split(",")); //arr是数组,有值 这一步是赋值
//即 list!=null && !list.isEmpty()
list != null : null判断是判断有没有对list集合分配内存空间
!list.isEmpty : isEmpty是判断 list 元素是否为空
使用isEmpty()和size()的前提是,list是一个空集合(有实例化),而不是null,否则会抛异常。
像String等也是一样的,这是因为如定义String时:“” 和 new String(),会有占位符,也就是创建了对象,而null的时候,String 不会创建占位符。
————–更通俗的说就是:”” 和new String() 的时候,String是有长度的,而null没有长度。1、所有我们在判断集合不为空的时候常采用:if(list!=null&&!list.isEmpty())的方法去取list里面的值
2、或这种时候判空我们可以使用:
StringUtils和CollectionUtils 等工具类 就不存在这种问题,值为 null 时不会报错, 都可以一并判断
如:List<User> lists
在循环遍历时,可以对list内的元素属性进行操作,如
List<User> lists = new ArrayList<>();
lists.forEach(list -> {
list.setName(list.geName+"123");
})
但不能对元素进行操作,如果要对元素操作,需要使用迭代器,如果要在遍历过程中对集合添加元素,需要使用ListIterator,是List专用
总结:list链表循环时,可以对循环元素属性进行操作,但不能对元素本身进行如添加,删除等操作。
2、Java中逗号运算符的使用
在java中,逗号只能用来分隔方法的参数,或者分割多个变量的声明,或者用于for循环的表达式中。
如:
//反例
for (int i = 0; i < list.size(); i++) {
System.out.println("result");
}
//正例
for (int i = 0, length = list.size(); i < length; i++) {
System.out.println("result");
}
3、java字符串拼接方式性能分析
一、字符串常量(比如"a")或者常量引用(用final修饰的字符串引用,比如final String str = “a”)直接用“+”号进行连接 编译期就会进行优化
如:
String str = “a” + “b” + “c”,编译器会直接优化为String str = “abc”
二、
其它如使用字符串变量拼接的时候,如str += "222";底层会优化成StringBuilder,因此会new一个StringBuilder对象出来
1、一般如果只是使用一次或少数几次的话,还是直接使用 + 号方便 ;
2、在 for 循环这种涉及大量拼接的时候建议使用StringBuilder的append方法,不要使用”+”号操作符进行字符串拼接,原因见下方参考链接
除了上面的方式之外,还有使用StringJoiner类进行拼接。
结论:
在日常的开发过程中,我们怎么选择字符串拼接类呢?
- 简单的字符串拼接,直接使用 + 即可。
- 在 for 循环之类的场景下需要字符串拼接,可以优先考虑使用 StringBuilder 。
- 在使用 Java Stream 和 lambda 的场景下需要字符串拼接,可以优先考虑使用 StringJoiner。
参考:java四种字符串拼接方式性能分析 (参考文章有一部分不对,看评论)
StringJoiner类参考:拼接字符串新姿势—StringJoiner
4、常用工具类
参考:常用的JAVA第三方工具类(实用)
5、JAVA跳出循环的三种方式之 使用标签进行控制
6、Stream的使用
参考:
Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合 (重点)
Java8 关于stream.foreach()和stream.peek()的区别解析
7、Java 8中双冒号的用法
有图有真相:
参考:
8、Assert(断言)关键字的使用
一、是直接在代码中使用 Assert 抽象类即可,它是一个抽象类,里面有许多静态方法,直接调用即可
二、使用 assert 关键字 自行添加判断条件
assert [boolean 表达式]
如:
//obj是对象
Assert.notNull(obj, "对象不能为null");
//如果obj不为空,程序向下执行。为空则报 AssertionError异常,在控制台抛出 “对象不能为null”字符串
//或直接使用assert关键字,添加判断
assert count>=0 : "count is negative !";
参考:
Java中assert(断言)的使用
常用 Assert类 总结
9、枚举类型 Enum
重点参考:我奶奶都能懂java枚举类型
其中遍历枚举类型,可以使用 values()方法;
但是要注意点进去会发现找不到这个方法。这是因为java编译器在编译这个类的时候才会自动插入了一条static的方法values。参考:Java Enum 枚举类的values方法
举例:
public enum EscapeEnum {
//定义枚举项
DOOR("老公没到小区","从正门出去"),
WINDOW("老公上楼了","翻窗子出去"),
CABINET("老公进客厅了","不行了,躲柜子里");
private String location;
private String operation;
// values()的运用
static{
for (EscapeEnum es : values()) {
System.out.println(es.getLocation+":"+es.getOperation);
}
}
//下面是private 构造方法
//下面是get/set方法
}
10、内部类
参考:
11、Java Builder模式和@Builder注解的作用
参考:Java Builder模式(设计模式之Builder模式) 重点
@Builder注解的作用就是代替上面的模式
12、常用的 lombok 注解
@Data :注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 等方法,注解后在编译时会自动加进去
@AllArgsConstructor:注解在类上;为类提供一个全参的构造方法
@NoArgsConstructor:注解在类上;为类提供一个无参的构造方法
@Setter:注解在属性上;为属性提供 setting 方法
@Getter:注解在属性上;为属性提供 getting 方法
@Builder:关于Builder较为复杂一些,Builder的作用之一是为了解决在某个类有很多构造函数的情况,也省去写很多构造函数的麻烦,在设计模式中的思想是:用一个内部类去实例化一个对象,避免一个类出现过多构造函数,
@Log4j :注解在类上;为类提供一个 属性名为log 的 log4j 日志对象
@EqualsAndHashCode:实现equals()方法和hashCode()方法 @ToString:实现toString()方法
@Cleanup:关闭流
@Synchronized:对象同步
@SneakyThrows:抛出异常
13、Java forEach 跳出循环
在forEach中return之后不会退出lambda表达式,而是执行下一个元素操作 类似于continue
要终止循环,可以使用抛出异常,使用stream流等方式
参考:Java Lambda表达式forEach无法跳出循环的解决思路
14、Stream的使用
重点参考(实用):Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合
参考:延迟执行与不可变,系统讲解JavaStream数据处理
归约、分组与分区,深入讲解JavaStream终结操作
15、泛型的使用
16、队列和栈
现在的栈(Stack)已经过时了,推荐使用(Deque)来代替它
栈(Stack):后进先出 ,杯子里放乒乓球 现在不推荐使用了,用Deque代替
队列(Queue ):先进先出,超市收银台
双端队列(Deque): 两头都可以出 ,一根管子
Stack类:栈类 过时 public class Stack<E> extends Vector<E>
Queue:队列类
Deque:双端队列(栈操作建议使用)
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
public interface Deque<E> extends Queue<E> 扩展了java.util.Collection接口
参考:
Java双端队列Deque使用详解(主要参考)
17、java 初始化和实例化的区别?和衍生问题
初始化和实例化的区别:
类的初始化是指:类加载过程中的初始化阶段对类变量按照程序猿的意图进行赋值的过程;
类的实例化是指:在类完全加载到内存中后创建对象的过程。
注意:构造函数只是起对象初始化作用的,而不是起产生对象作用的;
只有 new语句或反射等 才会产生对象作用,生成 类 的对象
如:new子类会实例化父类么?答案是不会。
只是子类一路向上调用了父类的构造函数,一直调用到Object类为止,对对象起初始化作用,没有实例化父类对象。最终产生的对象仍然是当前这一个子类对象准确的说应该就是初始化父类中的变量和执行构造函数
参考:
深入理解Java对象的创建过程:类的初始化与实例化 (重点)
Java中,new子类会实例化父类么? (重点)
18、Java 文档注释:javadoc 标签
如在方法或函数上添加作者名等
参考:javadoc 标签
19、Java8 日期时间 API
java8使用了LocalDateTime和DateTimeFormatter。比之前的Date和Carlendar有所改进。
DateTimeFormatter是线程安全的。DateTimeFormatter中很多属性使用了final修饰。
LocalDate: 只能设置仅含年月日的格式,表示没有时区的日期, LocalDate是不可变并且线程安全的
LocalTime: 只能设置仅含时分秒的格式,表示没有时区的时间, LocalTime是不可变并且线程安全的
LocalDateTime: 可以设置含年月日时分秒的格式 , 表示没有时区的日期时间, LocalDateTime是不可变并且线程安全的ZoneId: 时区ID,用来确定Instant和LocalDateTime互相转换的规则
Instant: 用来表示时间线上的一个点(瞬时)
Clock: 用于访问当前时刻、日期、时间,用到时区
Duration: 用秒和纳秒表示时间的数量(长短),用于计算两个日期的“时间”间隔
Period: 用于计算两个“日期”间隔
java 获取当前周的周一和周日的日期:
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");//设置日期格式
Calendar cld = Calendar.getInstance(Locale.CHINA);
cld.setFirstDayOfWeek(Calendar.MONDAY);//以周一为首日
cld.setTimeInMillis(System.currentTimeMillis());//当前时间
cld.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);//周一
System.out.println(df.format(cld.getTime()));
cld.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);//周日
System.out.println(df.format(cld.getTime()));
使用参考:java8 LocalDateTime - 乐之者v - 博客园
20、JAVA双大括号语法
使用花括号的效果一样,并且看起来更加简洁,根据需要使用
JAVA双大括号语法原理
匿名内部类 + 初始非静态代码块。
第一个大括号是创建一个继承当前对象的匿名内部类。
第二个大括号是在这个匿 名内部类中创建一个非静态初始化代码块,最后new 的操作是得到当前对象的子类 (匿名内部类)然后向上转型为当前对象的引用。
缺陷:
类中每一处双大括号的引用都会产生一个.class文件,导致堆内存会有这些文件的引用,增加类加载器负担。
使用双大括号初始化所创建的匿名内部类会持有当前对象的引用,会把当前对象的实例暴露出去,造出内存泄漏
参考:JAVA双大括号语法_IAmZRH的博客-CSDN博客_java 双大括号
21、java Agent原理和基本使用
JavaAgent 叫Java代理,它可以在加载 main 之前或在运行期间修改已经加载的类的字节码,它内定的方法名叫 premain
Agent分为如下两种:
- 静态Instrument:在main加载之前运行的Agent(JDK 1.5后提供)
- 动态Instrument:在main运行之后运行的Agent(JDK1.6以后提供)。
参考:
一个最简单的javaagent demo实例_席飞剑的博客-CSDN博客这个理解是按照JDK1.5来理解的,只有静态代理。(理解不全面,参考下面的这个)
22、String、JSONObject、JSONArray互转
// 1. String 转 JSONObject
String jsonMessage = "{\"语文\":\"88\",\"数学\":\"78\",\"计算机\":\"99\"}";
JSONObject jsonObject = JSONObject.parseObject(jsonMessage);
// 2. String 转 JSONArray
String jsonMessage = "[{\"语文\":\"88\",\"数学\":\"78\",\"计算机\":\"99\"}]";
JSONArray jsonArray = JSONArray.parseArray(jsonMessage);
// 3. JSONObject 转 String
String jsonMessage = jsonObject.toString();
// 4. JSONObject 转 JSONArray
JSONArray jsonArray = new JSONArray();
jsonArray.add(jsonObject);
// 5. JSONArray 转 String
String jsonMessage = jsonArray.toString();
// 6. JSONArray 转 JSONObject
// 获取 JSONArray 指定 index 的对象
Object o = jsonArray.get(index);
// 转为 List 集合
List<类> list = jsonArray.toJavaList(类.class);
JSONArray遍历取值,因为jsonarray默认是object
JSONArray jsonArray = (JSONArray) object.get("data");
for(int i = 0; i < jsonArray.size(); i++) {
JSONObject obj = jsonArray.getJSONObject(i);
Integer del = (Integer) obj.get("del");
String bookId = (String) obj.get("id");
if (del == 1) {
System.out.println("遍历JSONArray取值成功");
}
23、接口签名校验,AOP自定义注解
签名的概念
目的: 为了确认某个信息确实是由某个发送方发送的,或者某个发布内容确实是由发送方发布的,任何人都不可能伪造消息,并且,发送方也不能抵赖。
方法: 对发布的信息内容,通过某种可靠的加工(比如进行MD5运算),生成签名标识(字符串序列或者证书之类)
验证: 任何人拿到发布的信息内容后,可以通过同样的加工,得出签名标识,如果比对和发布者公布的签名一致,则验证为真。
签名与加密区别: 加密是为了不让别人知道原来的信息,签名是为了保证大家获取到的原来的信息是没有经过改动的。
可以在请求头中加入校验,也可以在对象中加入
一般使用aop校验,也有filter过滤校验
注意使用aop校验,可以使用@Pointcut(XXXXX)定义切点,自定义注解的方式,那样想加在那个接口都可以了。当然你也可以将XXX改为所有Controller下的,这样所有控制层都有了。
参考:
https://www.jianshu.com/p/af2ad6aa24cd Spring-boot-手把手教你使用AOP进行加密解密签名验证(重点)
可能使用到的加密算法
24、jsonArray中按某字段排序
jsonArray中按字段排要重写compare方法
25、string比较大小详解
string比较大小是通过前后两个字符串的ASCII码,是一个一个的比较
如时间格式的字符串
String str1 = "2022-01-02 12:20:30";
String str2 = "2022-03-05 13:12:30";
// 参与比较的两个字符串如果首字符相同,则比较下一个字符,直到有不同的为止,返回该不同的字符的asc码差值,这里就是 到了 1号和3号的不同进行比较
26、java计算两个日期相差多少天,小时,分钟
/**
* 求两个时间相差的时间单位
* @param smallDate 小时间
* @param bigDate 大时间
* @param type 分别为 Calendar.DATE,Calendar.HOUR,Calendar.MINUTE
* @return 相差数量
*/
public long minusTime(Date smallDate,Date bigDate,int type){
if(smallDate==null||bigDate==null){
return Integer.MAX_VALUE;
}
long diffMillis = Math.abs(bigDate.getTime() - smallDate.getTime());
switch (type){
case Calendar.DATE: return TimeUnit.DAYS.convert(diffMillis, TimeUnit.MILLISECONDS);
case Calendar.HOUR:return TimeUnit.HOURS.convert(diffMillis, TimeUnit.MILLISECONDS);
case Calendar.MINUTE:return TimeUnit.MINUTES.convert(diffMillis, TimeUnit.MILLISECONDS);
default:return Integer.MAX_VALUE;
}
}
27、Java中double保留两位小数的四种方法 和 补零的方法
/** 注意:这些方法对于如 12.30 这种最后一位是0的数值,输出时又转换为double,小数后会丢失了一个0,输出变成 12.3(这是因为源码中,(是print函数是将double转为long又转为String)
因此直接使用string输出 或 使用BigDecimal等输出 就不会有这个问题。
及进行 补零操作,最后输出使用string即可,补零操作和保留操作前面一样
**/
一、使用BigDecimal的setScale方法
double one = 5.26419;
BigDecimal two = new BigDecimal(one);
double three = two.setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
// 或直接使用 BigDecimal 输出即可
// toString方法
two.setScale(2,BigDecimal.ROUND_HALF_UP).toString();
二、使用DecimalFormat (也可以进行补零操作)
double one = 5.2;
DecimalFormat format = new DecimalFormat("#.00");
String str = format.format(one); // 5.20
double four = Double.parseDouble(str); //5.2 double将丢失了0
三、使用Sting自带的format方法
double one = 5.26419;
String str = String.format("%.2f",one);
double four = Double.parseDouble(str);
四、使用NumberFormat设置最大小数位数
double one = 5.26419;
NumberFormat format = NumberFormat.getInstance();
format.setMaximumFractionDigits(2);
String str = format.format(one);
double two = Double.parseDouble(str);
参考:浅谈Java中浮点型数据保留两位小数的四种方法 - 编程宝库
28、java 嵌套接口
在Java类中定义接口
public class Test {
interface myInterface {
void show();
}
class Inner implements myInterface {
public void show() {
System.out.println("Welcome to China!!!");
}
}
public static void main(String args[]) {
Inner obj = new Test().new Inner();
obj.show();
}
}
注意:嵌套接口常用于@Validated注解分组校验使用。具体参考注解使用博客。
29、什么时候需要实现Serializable序列化?
全部加上也没有关系,反正不影响
是否实现 Serializable 序列化接口,主要是根据你 是否需要在网络上直接传递JavaBean对象, 如果需要那么就必须实现 Serializable 序列化接口,如果传输的是JavaBean对象里的值,那么就不需要实现 Serializable 序列化接口,转成Json格式的字符串即可
30、java 的节点流和处理流,以及关闭流的顺序
节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader.
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
流的关闭顺序
1.一般情况下是:先打开的后关闭,后打开的先关闭
2.另一种情况:看依赖关系,如果流a依赖流b
例如,处理流a依赖节点流b,应该关闭处理流a,可以不用关闭流b
这是因为处理流a关闭的时候,会调用其内部方法关闭输入的节点流b。(java8 内部实现);当然,如果你要手动关闭b,记得加上判NULL操作。
处理流内部源码:
//BufferedReader.java
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close();//这里的in就是原生流
} finally {
in = null;
cb = null;
}
}
}
注意:
如果将节点流关闭以后再关闭处理流,会抛出IO异常。
如果关闭了处理流,在关闭与之相关的节点流,也可能出现IO异常。(hadoop编程文件流操作中遇到了。)
参考:
31.FilenameUtils类
FilenameUtils包含一些工具类,它基于文件名工作而不是File对象。
这个类旨在于Unix和Windows环境下保持一致,帮助在两个环境下过渡(如从开发环境到生成环境)
参考:Java常用类(六):FilenameUtils类_出处不详,经久不息的博客-CSDN博客_filenameutils
32.Java UUID生成4位8位16位20位24位32位长度的随机字符串
/**
* 获得4个长度的十六进制的UUID
* @return UUID
*/
public static String get4UUID(){
UUID id=UUID.randomUUID();
String[] idd=id.toString().split("-");
return idd[1];
}
/**
* 获得8个长度的十六进制的UUID
* @return UUID
*/
public static String get8UUID(){
UUID id=UUID.randomUUID();
String[] idd=id.toString().split("-");
return idd[0];
}
参考:Java UUID生成4位8位16位20位24位32位长度的随机字符串
但这种可能在大并发的情况下有一定重复概率,为了避免重复率,参考下面的文章
33. list使用stram分页
// name是查询名称
// pageNum pageSize 是 Integer
List<Object> collect = arry.stream().filter(v -> {
StringUtils.isNotBlank(name)? v.getName.contains(name) : true;
}).skip((pageNum - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
int total = StringUtils.isNotBlank(name)? collect.size() : arry.size();
34.StrSubstitutor 替换字符串
参考:java String工具类 StrSubstitutor 替换字符串中的变量使用方法_stringsubstitutor_m0_46325101的博客-CSDN博客
35.Java实现将文件或者文件夹压缩成zip
36.将已有的集合和数据库中集合进行对比,没有的添加,少了的删除
常用于授权用户等操作,列如添加授权的时候添加多个用户,然后比对数据库里面已经存在的权限。如 传入 d e , 数据库存在 a b c d --->更新后数据库应该只有 d e ---> 添加e ,删除 a b c
// 接收前端参数 vo类的属性
/**
* 应用id
*/
private String id;
/**
* 授权用户列表
*/
private List<Auth> authList;
// 关键代码如下
// 过滤授权用户信息
// 传入 d e 已有a b c d 过滤后---> 添加e 删除abc
List<auth> newAuthList = vo.getAuthList();
List<auth> existAuths = selectAuths(vo.getId()).getAuthList();
List<String> existAuthsIds = existAuths.stream().map(auth::getAuthUserId).collect(Collectors.toList());
Iterator<auth> iterator = newAuthList.iterator();
// 双重循环排除数据
while (iterator.hasNext()) {
auth next = iterator.next();
Iterator<auth> exIterator = existAuths.iterator();
while (exIterator.hasNext()) {
auth next2 = exIterator.next();
if (StringUtils.equals(next2.getAuthUserId(), next.getAuthUserId())) {
// 过滤已存在的数据,剩下要删除的数据
exIterator.remove();
}
}
if (existAuthsIds.contains(next.getAuthUserId())) {
// 过滤传入的数据,剩下要添加的数据
iterator.remove();
}
}
if (CollectionUtils.isNotEmpty(existAuths)) {
// 删除不要的
existAuths.forEach(existAuth -> removeAuth(existAuth.getId()));
}
if (CollectionUtils.isNotEmpty(newAuthList)) {
// 添加没有的
newAuthList.forEach(this::saveAuth);
}
37.Java按行读取文件文本内容,或按范围读取
我最终编写使用的代码:
public List<String> queryTxt(Integer startLine, Integer limit) throws IOException {
BufferedReader reader = null;
List<String> list = new ArrayList<>();
try {
reader = new BufferedReader(new FileReader(
"D:\\file\\test\\a\\nohup.out"));
int sLine = startLine;
// 跳过前面n行
for (int i = 0; i < sLine; i++) {
reader.readLine();
}
// 获取固定行数的内容
String line = reader.readLine();
int count = 0;
while (line != null) {
count++;
if (count > limit) {
return returnList;
}
returnList.add(line);
// read next line
line = reader.readLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
reader.close();
}
return list ;
}
参考:
Java按行读取文件文本内容_java怎么读取bsh文件内容_程序员Forlan的博客-CSDN博客
38、Could not extract response: no suitable HttpMessageConverter found for content type [xxx]
主要原因是RestTemplate
把数据从 HttpResponse
转换成Object
的时候找不到合适的HttpMessageConverter,还有可能就是调用的第三方api返回的类型不是json,所以解析不到,即
收到response
的Header
里面的Content-Type
值不是json
参考:
解决:Could not extract response: no suitable HttpMessageConverter found for content type [xxx]_
我的问题是调用的第三方接口返回的是text/html格式,我们使用string接受,然后可能就会导致问题39的出现
39、调用接口时提示Redirecting
问题38的衍生,使用字符串接收时报这个错
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="http://xxx/api/v1/branch_device">http://xxx/api/v1/branch_device</a>. If not click the link.
导致这个问题就是自动跳转;找不到对应的跳转路径,
我的问题最终解决方案:在请求url最后加上/
或者参考下面的链接,不过这个链接的解决方案没解决我的问题
springboot 请求python rest 接口报错_you should be redirected automatically to the targ
40. Stream: 根据对象嵌套排序
// 列表对象里面还有属性对象
list.stream().sorted(Comparator.comparing(v -> v.getUser.getCreatime() )).collect(Collectors.toList());
// v.getUser得到循环列表里面的User对象
其它优化技巧
参考: