从笔者个人角度, 针对Java基础中的一些生僻难点进行了归类整理, 希望可以帮到有需要的兄弟们
1 关键字
1.1 private
类中所有的private方法都隐式地指定为final
1.2 static
static修饰的变量永远不会被序列化
类中被static修饰的内容建议通过类名进行调用
通过静态导入import static导入的静态成员, 可以直接使用, 不需要再通过类名调用
1.3 strictfp
strictfp关键字可以应用于方法/类/接口, 不能应用于抽象方法/变量/构造函数, 确保在每个平台上获得相同的结果
常用于浮点数相关内容, 防止因平台不同导致数据精度不一致
Java 17中解决了浮点指令问题, 已移除此关键字
1.4 transient
对于不想序列化的变量, 使用transient
需要注意以下内容:
只能修饰变量, 不能修饰类和方法
修饰的变量, 在反序列化后变量值将会被置成类型的默认值. 例如, 如果是修饰 int 类型,那么反序列化后结果就是 0
2 基本类型
2.1 8种基本类型介绍
基本类型 位数 字节 默认值
int 32 4 0
short 16 2 0
long 64 8 0L
byte 8 1 0
char 16 2 'u0000'
float 32 4 0f
double 64 8 0d
boolean 1 false
其中boolean依赖于JVM厂商的具体实现, 理论上占1位, 可能出现不同
2.2 精度丢失问题
float和double都会丢失精度, 原因是数据转换为二进制可能会出现无限循环, 超出储存长度
在涉及金额等极为敏感的浮点数据时, 请使用BigDecimal来处理运算
在创建BigDecimal时, 应使用字符串参数或valueOf方法:
// YES!!
new BigDecimal("0.1");
BigDecimal.valueOf(0.1f);
// NO!! 会丢失精度
new BigDecimal(0.1f);
复制代码
2.3 包装类型
基本类型都有对应的包装类型, 其存在的意义在于允许null值含义
举个例子, 学生参加考试但成绩为0和没有参加考试成绩为null是两种情形, 基本类型int没办法体现, 其包装类型Integer则可以
包装类型除Float和Double外都存在常量池, 所以在比较时应使用equals方法而不是==:
// 常量池中存在一个Integer对象, 其值为1
Integer i1 = 1;
Integer i2 = 1;
Integer i3 = new Integer(1);
// 结果为true
i1 == i2;
// 结果为false
i1 == i3;
// 结果为true
i1.equals(i3);
复制代码
3 函数
3.1 函数签名
函数签名是函数在一个类中的唯一标识
内容包括方法名/参数类型/参数名, 不包括返回值
3.2 重载
发生在同一个类中(或者父类和子类之间), 方法名必须相同,参数类型不同/个数不同/顺序不同,方法返回值和访问修饰符可以不同
那可以存在方法名相同/参数相同但返回值不同的重载吗?
答案是不行, 3.1中的函数签名不包括返回值, 所以方法名相同/参数相同但返回值不同的方法会被认为是同一方法, 不能进行重载
3.3 重写
方法名/参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类
如果方法的返回值类型是 void 和基本数据类型,则返回值重写时不可修改
如果方法的返回值类型是引用类型,重写时可以返回该引用类型的子类
3.4 try-catch-finally
当try语句和finally语句中都有return时, finally语句的返回值将会覆盖原始的返回值
换而言之, finally语句一定会执行
// 返回为0
try {
return 1;
} catch (Exception e) {
// ...
} finally {
return 0;
}
复制代码
3.5 try-with-resource
适用于任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象, 会自动调用close方法
4 面向对象
4.1 对象构建顺序
静态代码块
非静态代码块
构造方法
4.2 静态代码块
public class Person {
static {
// 静态代码块
}
}
复制代码
静态代码只会在类的初始化步骤执行一次, 具体在代码中写法为:
new Person()
Class.forName("cn.houtaroy.models.Person")
举个具体的例子:
package cn.houtaroy.models;
public class Person {
static {
System.out.println("静态代码块执行");
}
public static void main(String[] args) {
try {
new Person();
Class.forName("cn.houtaroy.models.Person");
new Person();
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
上述代码只会有一次输出: 静态代码块执行
可以理解为: 静态代码块中定义的是不同对象共性的初始化内容
一个类中的静态代码块可以有多个,会按照它们出现的先后顺序依次执行
public class Person {
static {
// 先执行
}
static {
// 再执行
}
}
复制代码
静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问:
public class Person {
static {
// 可以赋值
age = 0;
// IDE报错, 无法访问
System.out.printf("出生年龄: %d", age);
}
public static Integer age;
}
复制代码
4.3 静态内部类
不需要依赖外围类的创建
不能使用任何外围类的非静态成员变量和方法
静态内部类可用于实现单例模式, 优点是延迟初始化和JVM提供的线程安全支持 :
public class PersonFactory {
// 私有构造方法, 防止在外部调用
private PersonFactory() {
}
// 私有静态内部类, 无法被外部访问
private static class PersonFactoryHolder {
private static final PersonFactory INSTANCE = new PersonFactory();
}
// 静态方法, 调用私有静态内部类获取唯一实例
public static PersonFactory getInstance() {
return PersonFactoryHolder.INSTANCE;
}
}
复制代码
4.4 hashCode与equals
hashCode用于判断对象的hash值是否相同
equals用于判断对象是否相同
在用到hash的地方都会使用hashCode和equals, 例如HashMap. 所以在重写时, 请务必使二者的返回结果保持一致
4.5 值传递
Java中只有值传递, 哪怕是在面向对象, 举个具体的例子:
public class Test {
public static void main(String[] args) {
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
}
}
复制代码
输出结果为:
x:小李
y:小张
s1:小张
s2:小李
复制代码
可以看到, 在函数内部x和y进行了交换, 但外部的s1和s2并没有发生变化
所以这四个变量代表的含义是:
s1: 学生小张对象的引用的值
s2: 学生小李对象的引用的值
x: s1的深拷贝
y: s2的深拷贝
因为x和y是深拷贝, 所以无论它们如何变化, 都不会影响原来的s1和s2
4.6 this/super与静态
两者的概念范畴完全不同:
静态方法是类范畴的概念
this/super是对象范畴的概念
4.7 Objects.equals
在进行比较时(并非类, 全部内容均可)推荐使用Objects.equals:
// 均为false, 且不会抛空指针异常
Objects.equals("Houtaroy", null);
Objects.equals(null, "Houtaroy");
复制代码
5 枚举
5.1 使用枚举相关集合
在将枚举作为元素或键值等使用时, 推荐使用枚举相关集合, 例如EnumSet和EnumMap:
// 创建EnumSet
EnumSet<Gender> set = EnumSet.of(Gender.MAN);
// 创建EnumMap
EnumMap<Gender, String> map = new EnumMap<>(Gender.class);
map.put(Gender.MAN, "男人");
复制代码
5.2 实现设计模式
单例模式
利用枚举可以更简洁/高效/安全的实现单例模式, 且由JVM提供保障
public enum PersonFactory {
INSTANCE;
PersonFactory() {
// 实现人员工厂初始化
person = PersonCheckEntity.builder().id("test").build();
}
private PersonCheckEntity person;
public static PersonFactory getInstance() {
return INSTANCE;
}
public PersonCheckEntity getDeliveryStrategy() {
return this.person;
}
}
复制代码
策略模式
public enum PersonStrategy {
STAND {
@Override
public void advance(Person person) {
System.out.println("向前迈了一步");
}
},
SIT {
@Override
public void advance(Person person) {
System.out.println("向前爬了一截");
}
};
public abstract void advance(Person person);
}
public class Person {
private PersonStrategy status;
public void advance() {
status.advance(this);
}
}
复制代码
状态模式
public enum PersonStrategy {
STAND {
@Override
public void walk(Person person) {
System.out.println("向前迈了一步");
}
},
SIT {
@Override
public void walk(Person person) {
System.out.println("坐着没办法走路, 站起来");
person.setStatus(PersonStrategy.STAND);
person.walk();
}
};
public abstract void walk(Person person);
}
public class Person {
private PersonStrategy status;
public void walk() {
status.walk(this);
}
}
复制代码
6 反射
6.1 什么是反射
反射是框架的灵魂
它赋予了程序在运行过程中分析和使用类概念的能力, 使我们脱离最基本的业务逻辑, 站在更高的维度去处理和思考问题
Java中的利器注解便用到了反射
6.2 获取Class对象的四种方式
Class对象可以理解为类的描述
具体类
Class myClass = Target.class;
复制代码
Class.forName
Class myClass = Class.forName("cn.houtaroy.models.Target");
复制代码
对象实例
Target object = new Target();
Class myClass = object.getClass();
复制代码
类加载器
Class myClass = ClassLoader.loadClass("cn.houtaroy.models.Target");
复制代码
通过类加载器获取的Class不会执行初始化, 意味着不进行包括初始化等一系列步骤,静态块和静态对象不会执行
6.3 具体操作
创建目标类:
public class Target {
private String value;
public void say(String name) {
System.out.printf("I love %s%n", name);
}
private void classValue() {
System.out.printf("value is %s%n", value);
}
}
复制代码
进行反射操作:
public class ExampleUtils {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<?> targetClass = Class.forName("cn.houtaroy.models.Target");
Object targetObject = targetClass.newInstance();
Method[] methods = targetClass.getMethods();
for (Method method : methods) {
System.out.printf("拥有方法: %s%n", method.getName());
}
Field[] fields = targetClass.getFields();
for (Field field : fields) {
System.out.printf("拥有字段: %s%n", field.getName());
}
targetClass.getDeclaredMethod("say", String.class).invoke(targetObject, "Java");
Field valueField = targetClass.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set(targetObject, "test");
Method classValueMethod = targetClass.getDeclaredMethod("classValue");
classValueMethod.setAccessible(true);
classValueMethod.invoke(targetObject);
}
}
复制代码
运行结果为:
拥有方法: say
拥有方法: wait
拥有方法: wait
拥有方法: wait
拥有方法: equals
拥有方法: toString
拥有方法: hashCode
拥有方法: getClass
拥有方法: notify
拥有方法: notifyAll
I love Java
value is test
复制代码
getMethods和getFields方法只会读取public属性
访问私有属性时使用getDeclaredMethod和getDeclaredField, 并调用setAccessible(true)修改其可使用性
从上述内容即可看出, 利用反射会带来一定程度的安全隐患
7 Java中的IO
程序I/O可分解为如下操作:
程序向操作系统发起I/O调用请求
系统内核等待I/O设备准备好数据
系统内核将数据从内核空间拷贝到用户空间
7.1 Blocking I/O
即同步阻塞I/O, 程序会一直等待到I/O操作执行完成
7.2 Non-blocking I/O
即I/O多路复用模型
同步非阻塞I/O使用轮询方式, 会非常消耗CPU资源, 在Web开发中几乎无法使用
I/O多路复用模型利用一个线程来管理, 通过减少无效的系统调用,减少了对 CPU 资源的消耗
7.3 Asynchronous I/O
即异步I/O模型, 通俗点就是系统内核完成后会执行程序指定的回调函数
8 小细节
8.1 StringBuilder与StringBuffer区别
StringBuilder线程不安全
StringBuffer线程安全
8.2 字节流与字符流
字节(Byte)是计量单位,表示数据量多少,是计算机信息技术用于计量存储容量的一种计量单位,通常情况下一字节等于八位
字符(Character)是计算机中使用的字母、数字、字和符号, 在不同的编码中占用的字节数量不同
Java中存在字节流为什么还要提供字符流呢?
因为在不知道字符编码类型时, 使用字节流很容易出现乱码问题
音频文件、图片等媒体文件用字节流较好,如果涉及到字符的话使用字符流较好
好了本文就写到这了那么有需要这方面的资料可以私信我即可