【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)

https://www.cnblogs.com/cnb-yuchen/p/18032072

出自【进步*于辰的博客

纯文字阐述,内容比较干。并且,由于考虑到时间长了恐有所遗漏,便即兴记录,并没有对内容进行筛选、排序。因此,大家在阅读时可以直接 Ctrl + F 进行检索。

1、细节积累

  1. 1000 == new Integer(1000)
  2. "CS" + ne w String("DN") = "CSDN"
  3. protected 的限制范围是同体系(继承关系)、同包。
  4. 构造方法无返回值,void属于一种返回值类型。
  5. final、static、private 修饰的方法不能被重写。
  6. 封装的主要作用:对外隐藏内部实现细节、增强程序的安全性。
  7. boolean、byte 类型变量都占一个字节。
  8. 每个类都有各自的常量池,需要时从JVM总常量池中分配;
  9. 装饰模式的原理类似继承,作用是实现读写的扩展。
  10. 表示“空指针”是null,不是NULL
  11. 进入同步代码块、wait()、读取输入流、降低优先级这些方法都可使线程停止。
  12. 接口的方法只能由publicabstractdefaultstatic修饰,不能是private,因为接口不能私有化。
  13. 形参可以声明为final,只是方法内不能修改。
  14. $文数$指正序与倒序相同的数,如:12321
  15. Scanner 类的next()获取输入以“ ”(空格)结尾,nextLine()以“\n”(回车)结尾。
  16. 无论程序多复杂,运行时都、且之会生成一个JVM进程。
  17. default的三个使用场景:switch、默认方法、自定义注解注解元素的默认值。
  18. float 和 double 都是小数,java规定:1.0隐式为1.0d,其中的d是 double 的标志。因此,float f1 = 1.0这条语句编译报错,因为 double 所占字节数大于 float。这就是给float类型变量赋初值时要加f的原因。
  19. 不能作为switch参数的类型有:float、double、long、boolean 和复杂表达式。
  20. java源文件(后缀是.java)编译时默认使用操作系统所设定的编码进行编译。这就是为什么使用记事本编写 java 源代码可以正常编译并在 JVM 运行的原因。
  21. 原始 / 基本数据类型:short、int、long、char、byte、float、double、boolean。
  22. 若仅实例化子类,由于this代表的是当前实例,故当在父类中使用this时,this代表的是子类实例,而非父类实例。
  23. 实例化时,会先从方法区中检查构造方法是否相符(相同),再初始化成员变量和成员方法。
  24. 在多层 for 循环中,有时for前面有一个outer:/inner:,作用是便于控制循环,这只是一个标识,不固定。
  25. boolean 不能为null的原因:(1)、boolean 只有true/false两种取值;(2)、null表示空指针,而 boolean 是基本数据类型。
  26. JVM 方法区用于存放静态资源和类信息,多线程共享(线程安全)。当多线程同时使用一个类时,若此类未加载,则只有一个线程去加载类,其他线程等待。

2、一些类的使用细节

2.1 Object

  1. 因为 Object 类型不能作比较(obj1 > obj2语法不允许),故不能通过 Object 作为“上转类型”来比较不同包装类,需使用 Comparable 类代替 Object。(暂不知原因)
  2. intint[]IntegerInteger[]都可上转为 Object,Integer[]也可上转为Object[],但int[]不可上转为Object[]。(暂不知原因)

2.2 String

为了标记字符串为 unicode 字符,在字符串末尾默认存在一个空格,此空格不存在于字符序列(value)中(即不包含在length中),但使用subString()截取字符串时,开始索引可以是length,能得到一个空格。

2.3 String与字符串缓冲区的区别

String是常量,不可变;字符串缓冲区(StringBuffer/StringBuilder)支持可变的字符串。当需要对字符串进行频繁操作时,两者的性能差异很大。

String 赋值的步骤:

String → StringBuffer/StringBuilder → append() → toString() → String

引用 String 类的 API 中的一张图:

2.4 基本数据类型、包装类与String三者间的转换关系

参考笔记一,P40.7。

2.5 Package

参考笔记二,P29.6。

  1. Java属跨平台语言,与操作系统无关,Package`是用来组织源文件的一种虚拟文件系统。
  2. import的作用是引入,而不是拷贝,目的是告诉编译器编译时应去哪读取外部文件。
  3. Java的包机制与 IDE 无关。
  4. 同一包下的类可不使用import而直接调用(不是指实例化时用全类名)。

3、关于比较

3.1 ==

==是运算符,只能用于基本数据类型之间、以及相同包装类之间的比较。

3.2 equals()

所有包装类都重写了 Object 类的equals(),但无论equals()底层调用的是toString()intValue()、还是longValue(),结果都是获取值,与地址无关。

当使用equals()比较包装类时,多用于相同包装类之间。之所以不用于不同包装类,是因为所有包装类重写的equals()底层都封装了类型判断,若是不同包装类,则直接返回 false,即无法比较。

4、自动装箱与自动拆箱

所有包装类都具备自动装箱和自动拆箱机制,只是 Double、Float、Boolean 这三个包装类变量的值始终是对象。其中,Double、Float 的常量未存放在 JVM 方法区常量池的原因是浮点数无限。

如果大家想进一步了解自动装箱和自动拆箱机制,可参考博文《[Java]基本数据类型与引用类型赋值的底层分析的小结》的第4.2项。

6、一个关于static容易混淆的细节

大家先看个示例。

int a;

public static void main(String[] args) throws Exception {
C c1 = new C();// C 是类名
int i = 10;
while (i-- > 0) {
new Thread(() -> {
int n = 10000;
while (n-- > 0)
c1.a++;
}).start();
}
Thread t1 = new Thread(() -> {});
t1.start();
t1.join();
System.out.println(c1.a);
}

请问a的打印结果是多少,100000?不是,因为存在多线程并发访问。那如果把a改成类变量,结果是100000吗?仍然不是,依旧存在多线程并发访问。

不是由static修饰吗,为什么不是00000?这就是容易混淆的地方。

  • 类变量属于类,为所有对象共享;
  • 并发问题指线程对同一个数据的更改对其他线程不可见而导致的数据不一致问题。

所以,类变量不是为所有线程共享。

换言之,a必须进行同步处理(synchronized)。至于a定义为类变量、还是成员变量,看具体需求,与多线程无关。(注意下面这一点)

我突发奇想。(上面的线程在跑着)

public static void main(String[] args) throws Exception {
System.out.println(new C().a);
}

请问能获取到a的实时数据吗?当然不能,为什么?因为是两个 JVM。

18、关于数组定义

定义数组的4种格式:

1、int[] arr1 = new int[5];------------A
2、int arr2[] = new int[5];
3、int[] arr3 = {1, 2, 3};-------------B
4、int[] arr4 = new int[]{1, 2, 3};----C // [] 内不能指定元素个数

特例:

public static void main(String[] args) throws Exception {
print({1, 2, 3});// 编译报错
int[] arr = {1, 2, 3};
print(arr);// 打印:[1, 2, 3]
} public static void print(int[] arr) {
System.out.println(Arrays.toString(arr));
}

若形参类型为数组,调用时不能进行数组初始化。

19、实现多接口时的细节

1、方法 A 与 B 参数列表、返回值类型、方法名都相同时。

interface AnimalService {
void print();----------------------A
} interface LifeService {
void print();----------------------B
} class Person implements AnimalService, LifeService {
@Override
public void print() {--------------C // 重写A
}
}

C 重写 A 还是 B,取决于类 Person 实现接口的顺序,故 C 重写A。

2、方法 A 与 B 仅返回值类型不同时。

interface AnimalService {
void print();----------------------A
} interface LifeService {
int print();----------------------B
} class Person implements AnimalService, LifeService {
@Override
public void print() {--------------C // 编译报错
}

@Override
public intC print() {--------------D // 编译报错
}
}

A 与 B 仅返回值类型不同,这种情形不允许,故 C、D 都编译报错。

3、方法 A 与 B 完全不同时。

interface AnimalService {
void print();----------------------A
} interface LifeService {
int print(int age);----------------------B
} class Person implements AnimalService, LifeService {
@Override
public void print() {--------------C // 重写A
} @Override
public int print(int age) {--------D // 重写B
}
}

这就是普遍实现多接口的情况,此时,C、D 也属于重载。

21、关于时间类和时间处理

21.1 Date、Calendar

Date 类倾向于获取 时间,Calendar 类倾向于处理 时间。

示例:获取30分钟后的时间。

Date client = new Date();// 当未指定参数时,获取当前时间
System.out.println(client);// 打印:Wed Apr 12 19:48:10 CST 2023 Calendar handler = Calendar.getInstance();// Calendar是抽象类,故需要通过调用getInstance()创建实例
handler.setTime(client);// 设置初始时间
handler.add(Calendar.MINUTE, 30);// 将初始时间增加30分钟
System.out.println(handler.getTime());// 打印:Wed Apr 12 20:18:10 CST 2023

21.2 SimpleDateFormat

普通的时间格式:yyyy-MM-dd HH:mm:ss

有时候会是“hh”,区别是:前者是24小时制。

22、我误解的一个基础

问题:子类修改父类成员,通过反射获取修改前后父类成员、值未改变。

示例1

待反射类。

class Platform {
String name;
public void initail() {
name = "csdn";
}
}

测试代码。

Platform p1 = new Platform();
Class z1 = p1.getClass();
Field f1 = z1.getDeclaredField("name");
System.out.println(f1.get(p1));// 打印:null
p1.initail();
System.out.println(f1.get(p1));// 打印:csdn

示例2。

待反射类。

class Platform {
String name;
}
class CSDN extends Platform {
public void initail() {
name = "csdn";
}
}

测试代码。

Platform p1 = new Platform();
Class z1 = p1.getClass();
Field f1 = z1.getDeclaredField("name");
System.out.println(f1.get(p1));// 打印:null CSDN c1 = new CSDN();
c1.initail();
System.out.println(f1.get(p1));// 打印:null------A
System.out.println(p1.name);// 打印:null---------B
System.out.println(c1.name);// 打印:csdn

这2个示例的区别在哪?前者是通过父类自身实例修改其成员变量,而后者是通过子类实例修改父类成员变量。

明明已经调用c1.initail(),为何A、B两处的name仍为null

关于子类或父类初始化,详述可参考博文《[Java]知识点》的第5.4项。

原因很简单:假设c1的父类实例是p0(当然,实际上 Platform 类仅进行了实例初始化,未实例化),p0p1是两个实例

上述只是一个基础知识,为何我觉得可作为一个细节?

下述代码是我研究 ArrayList<E> 类的迭代器Itr和子迭代器ListItr时的一个示例。

ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3); Iterator<Integer> it = list.iterator(); Class z1 = Class.forName("java.util.ArrayList$Itr");
Field f1 = z1.getDeclaredField("expectedModCount");
f1.setAccessible(true);
System.out.println(f1.get(it));// 打印:3------A ListIterator<Integer> lit = list.listIterator();
lit.add(4);
System.out.println(f1.get(it));// 打印:3------B
lit.add(5);
System.out.println(f1.get(it));// 打印:3------C it.next();// 抛出:ConcurrentModificationException

关于expectedModCount,见ArrayList<E>的第7.1项。

由于之前调用了3次add(),故A处的值为3。同理,B、C两处的值应分别为45,可实际都是3

PS:我被潜意识误导了,即原因所在。

23、大家可能无意中避过的一个坑

相信大家都写过这么一段代码:

String str;
if (str != null && str.equals("")) {}

并且,可能自己领悟,或者他人指点,一般会这么写:

String str;
if (str != null && "".equals(str)) {}

因为这样可以避免空指针异常

其实,这两种写法并没有什么区别,用第二种纯粹是个人习惯或者是一种规范,我也是如此。

曾经我有一个疑惑:“第一种写法中同样会执行str.equals(),它是如何规避空指针异常的?”

由于这看起来只是一个细节,而且不影响敲代码,也就没关注。然而今天看源码,就碰到了这么一段判断:

if (useCaches &&
reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {}

四个 boolean,这若是无法确定每个 boolean,还如何解析?这就让我想起了开头说的例子。其实,我们并不需要确认所有的 boolean,听我细细道来。。。

这就要涉及运算符&&||的运算规则了,为了研究这个问题,翻了一些资料。果然,这两种运算符实在太基础了,没遇到我需要的,只能自己debug了。。。

看下述测试。

1、测试&&







&&结果为 true,要求每一段都为 true。从左往右逐个执行,若前面为 false,结果则为 false,后面就不会再执行;若前面为 true,则继续判断后面。(这就是开头我说那两种写法并没有区别的原因)

2、测试||







||结果为 true,要求其中一段为 true 即可。从左往右逐个执行,若前面为 true,结果则为 true,后面就不会再执行;若前面为 false,则继续判断后面。

PS:看到这6张不断播放的图片以及这绕口的说明,我自己都有点晕。。。大家可以根据图片自行理解。

补充一点:

String str;
if ("".equals(str)) {}

请问这样可以避免空指针异常吗?答案是不能,因为下述代码会抛出空指针异常:

Object anObject = null;
if(anObject instanceof String) {}

可能大家不明白我的意思,如果大家感兴趣,可以去看看equals()的源码。

直通车 -> String类的第2.14项。

24、关于程序与数据库之间数据类型转换说明(基于JDBC)

24.1 数据存储(DML)

  1. String → DateTime,需使用类java.sql.Timestamp。

24.2 数据查询(select)

  1. DateTime → String,先通过rs.getTimestamp()(rs是结果集ResultSet对象)获取Timestamp对象,再使用SimpleDateFormat类将其转为String。

最后

如果大家想要了解一些Java知识点,可查阅博文《[Java]知识点》。

本文持续更新中。。。

[Java]细节与使用经验的更多相关文章

  1. java细节知识

    代码优化细节 (1)尽量指定类.方法的final修饰符 带有final修饰符的类是不可派生的.在Java核心API中,有许多应用final的例子,例如java.lang.String,整个类都是fin ...

  2. Java细节1--输入关闭

    import java.util.Scanner; public class Demo01 { public static void main(String[] args) { Scanner sc= ...

  3. java细节,细的你想象不到

    一. 构造方法每次都是构造出新的对象,不存在多个线程同时读写同一对象中的属性的问题,所以不需要同步 . 如果父类中的某个方法使用了 synchronized关键字,而子类中也覆盖了这个方法,默认情况下 ...

  4. Java细节

    native关键字用法 native是与C++联合开发的时候用的!java自己开发不用的! 使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL, ...

  5. Java细节整理——数组与内存控制

    重点:使用Java数组之前,必须对数组对象进行初始化. 当数组的所有元素都被分配了合适的内存空间,并指定了初始值时,数组的初始化完成.程序以后将不能重新改变数组对象在内存中的位置和大小. 知识点整理: ...

  6. java细节问题

    保留两位小数:1.234, 1.23, 1.2, 1.0, 1. package test; import java.math.BigDecimal; import java.text.Decimal ...

  7. Java细节----method和function的区别

    面向对象的语言叫方法 面向过程的语言叫函数 在java中没有函数这么一说,只有方法一说.属于概念上的区别. 硬要说区别. Method必须依赖于Object. Function 是独立的,不需要依赖于 ...

  8. 【java细节】Java代码忽略https证书:No subject alternative names present

    https://blog.csdn.net/audioo1/article/details/51746333

  9. java细节篇(==和equals的区别)

    1)当==两边是对象时(String,Integer...),两边比的都是地址2)当==两边是基本类型时(int,float),两边比的都是值3)默认equals比的是对象的地址,但是重写的话可以改变 ...

  10. Java提高配(三七)-----Java集合细节(三):subList的缺陷

    我们经常使用subString方法来对String对象进行分割处理,同时我们也可以使用subList.subMap.subSet来对List.Map.Set进行分割处理,但是这个分割存在某些瑕疵. 一 ...

随机推荐

  1. SpringCloud Alibaba Sentinel实现熔断与限流<2020-9>

    SpringCloud Alibaba Sentinel 1.Sentinel是什么? 1.1.前言说明: 作用:实现熔断与限流 (Hystrix断路器 升级版) 文档直达: 官网中文文档 1.2.S ...

  2. Java并发编程实例--9.使用本地线程变量

    并发程序一个重要方面就是共享数据. 这一点在继承了Thread类或实现了Runnable接口的对象中有着特殊的重要性. 如果你创建了一个实现了Runnable接口的类对象并且用这个对象开启了N个线程对 ...

  3. SQLite 入门教程

    不是 MySQL 用不起,而是 SQLite 更有性价比,绝大多数的 Web 应用 SQLite 都可以满足. SQLite 是一个用 C 语言编写的开源.轻量级.快速.独立且高可靠性的 SQL 数据 ...

  4. ubuntu 20.4安装docker

    ubuntu 20.4 安装docker 目录 查看版本并升级套件 安装必要软件 添加阿里云的GPG密钥,命令设置存储库 执行安装,查看版本 查看版本并升级套件 cat /proc/version L ...

  5. 构建SatelliteRpc:基于Kestrel的RPC框架(整体设计篇)

    背景 之前在.NET 性能优化群内交流时,我们发现很多朋友对于高性能网络框架有需求,需要创建自己的消息服务器.游戏服务器或者物联网网关.但是大多数小伙伴只知道 DotNetty,虽然 DotNetty ...

  6. ASP.NET Core 微信支付(四)【支付结果通知回调(未按照官方步骤) APIV3】

    官方文档 支付通知API 证书和回调报文解密 参考资料 netcore 中没有Request.InputStream 理论实战 对于我来说,这个微信支付结果通知回调有两个难点. 难点一 一开始在想是怎 ...

  7. ubantu18.04使用APT安装go环境指令报错解决方案

    在ubantu下使用sudo apt install golang-go指令安装go环境,安装过程没有报错,在使用时无法识别指令,报错如下: root@sh001:~# go env -w GOPRO ...

  8. SpringBoot与Thymeleaf入门级操作

    使用Thymeleaf 三大理由: 简洁漂亮 容易理解 完美支持HTML5 使用浏览器直接打开页面 不新增标签 只需增强属性 学习目标 快速掌握Thymeleaf的基本使用:五大基础语法,常用内置对象 ...

  9. 01、SECS的基本概念

    最近做的项目跟半导体设备相关,需要学习SECS相关的内容,把自己的学习记录分享出来,如有不足甚至错误的地方,请不吝赐教,十分感谢! 文章内容基本都是SECS协议的内容和参考的资料,只不过是加了自己的理 ...

  10. ABP模块签入GitLab后自动打包并推送到ProGet

    # 1.添加一个名为下划线的解决方案文件夹 # 2.把解决方案根目录下的几个必要的文件添加到上述文件夹下 # 3.修改NuGet.Config,添加私有NuGet服务器的网址,并配置用户名和密码: A ...