刚接触Java那会儿,我对着控制台输出的"Hello World"兴奋了半天。但真正要写出可维护的工业级代码,光会打印语句可不够。这份笔记记录了我从Java基础语法跨越到面向对象编程的关键知识点,特别适合已经掌握基本语法、正准备深入理解Java编程范式的学习者。我会用实际代码示例展示那些容易被忽略的细节,比如为什么String要设计成不可变对象、多态在框架设计中如何发挥作用。
Java的八大基本类型看似简单,但实际开发中类型转换的坑可不少。比如用浮点数做金额计算时:
java复制double total = 0.1 + 0.2;
System.out.println(total); // 输出0.30000000000000004
这种情况必须使用BigDecimal处理。而包装类型的自动拆装箱也有性能隐患:
java复制Integer sum = 0;
for(int i=0; i<10000; i++){
sum += i; // 发生10000次拆箱/装箱操作
}
关键技巧:在循环体内尽量避免包装类型运算,优先使用基本类型
String的不可变性设计是面试常考点。看这个例子:
java复制String s1 = "java";
String s2 = "java";
System.out.println(s1 == s2); // true,指向常量池同一对象
String s3 = new String("java");
System.out.println(s1 == s3); // false,堆内存新对象
ArrayList和LinkedList的选择不能只看时间复杂度:
| 操作 | ArrayList | LinkedList |
|---|---|---|
| 随机访问 | O(1) | O(n) |
| 头部插入 | O(n) | O(1) |
| 内存占用 | 更小 | 更大 |
实际项目中,当需要频繁在集合中部增删元素时,可以考虑使用CopyOnWriteArrayList。它的写操作通过复制整个数组实现线程安全:
java复制List<String> list = new CopyOnWriteArrayList<>();
list.add("item1"); // 内部创建新数组拷贝元素
这个动物类的继承体系展示了多态的强大:
java复制abstract class Animal {
abstract void makeSound();
}
class Dog extends Animal {
void makeSound() {
System.out.println("Bark!");
}
}
class Cat extends Animal {
void makeSound() {
System.out.println("Meow~");
}
}
// 使用时
Animal myPet = getRandomAnimal(); // 可能是Dog或Cat
myPet.makeSound(); // 动态绑定实际类型的方法
Spring框架的依赖注入正是利用这种特性实现松耦合。我曾见过有人滥用继承导致类层次过深,这时应该考虑用组合替代继承:
java复制// 不好的设计
class Stack extends ArrayList {...}
// 更好的设计
class Stack {
private List elements = new ArrayList();
public void push(Object o) {
elements.add(o);
}
//...
}
接口和抽象类的区别常让人困惑。简单来说:
JDK8之后接口可以有默认方法,这使得它在API演进时更具优势。比如集合框架的Iterable接口:
java复制default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
设计模式中的策略模式就充分利用了接口特性:
java复制interface PaymentStrategy {
void pay(int amount);
}
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("信用卡支付:" + amount);
}
}
class WeChatPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("微信支付:" + amount);
}
}
Java异常体系分为Checked Exception和Unchecked Exception。我总结的处理原则:
正确的异常处理示范:
java复制try {
FileInputStream fis = new FileInputStream("config.properties");
// 使用资源
} catch (FileNotFoundException e) {
log.error("配置文件缺失", e);
throw new ApplicationInitializationException("系统初始化失败", e);
} finally {
// JDK7+可使用try-with-resources
}
别再用System.out.println了!SLF4J+Logback组合是行业标准:
java复制import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
void processData(String input) {
if(input == null) {
logger.warn("接收到空输入参数");
return;
}
logger.debug("开始处理数据:{}", input);
//...
}
日志级别使用建议:
SonarQube静态扫描能发现很多潜在问题。我配置的检查规则包括:
使用JUnit5编写单元测试时要注意:
java复制@Test
@DisplayName("测试数组排序")
void testArraySort() {
int[] input = {3,1,2};
Arrays.sort(input);
assertArrayEquals(new int[]{1,2,3}, input);
}
通过jvisualvm工具观察内存使用情况时,要特别关注:
这个启动参数在我的4核服务器上表现良好:
bash复制java -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
-XX:ParallelGCThreads=4 -jar myapp.jar
遇到内存泄漏时,可以用jmap生成堆转储文件:
bash复制jmap -dump:format=b,file=heap.hprof <pid>
问题1:NoSuchMethodError
问题2:OutOfMemoryError: PermGen space
这个双重检查锁实现有问题:
java复制class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
正确写法应该加volatile:
java复制private static volatile Singleton instance;
掌握这些核心知识后,建议继续深入:
我常看的优质资源:
学习过程中最深的体会是:不要停留在API调用层面,多思考设计者的意图。比如为什么ConcurrentHashMap要放弃分段锁改用CAS+synchronized?这种思考能真正提升编程能力。