Android 基础筑基(一)

249 阅读7分钟

Java基础

1、重载和重写是什么意思,区别是什么?

中文意思容易混淆,用英文记会清晰一些:Overload与Override。

重载(Overload)
在同一个类中,多个方法名称相同,但参数不同(数量或类型不同),这叫做方法重载。

class Calculator {
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }

    int add(int a, int b, int c) {
        return a + b + c;
    }
}

重写(Override)
子类中重新定义父类中的方法(方法名、参数都一样),叫做方法重写,目的是修改或扩展父类的行为。

class Animal {
    void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("Dog barks");
    }
}

2、Java中在传参数时是将值进行传递,还是传递引用?

在 Java 中,参数传递机制统一是:

按值传递(Pass by Value)

但这个“值”是变量的值,可能是基本类型的值,也可能是对象引用的值,这就容易让人混淆。

1. 传递基本类型(int、float、boolean 等)
值的复制,互不影响:

void change(int x) {
    x = 10;
}

int a = 5;
change(a);
System.out.println(a); // 输出:5(没有变)

2. 传递 对象引用类型(比如传一个 List、数组、对象等)

void changeName(Person p) {
    p.name = "Alice";      // 改变对象的内容(有效)
    p = new Person("Bob"); // 改变引用本身(无效)
}

Person p1 = new Person("Tom");
changeName(p1);
System.out.println(p1.name); // 输出:Alice,而不是 Bob

分析:

  • p 是对象引用,传进去时复制了引用的值(地址)
  • p.name = "Alice":通过地址改对象内容,有效
  • p = new Person(...):只是改了 p 的副本指向的地址,对外部 p1 没影响,无效

3、sychronied修饰普通方法和静态方法的区别?

public synchronized void instanceMethod() {
    // 线程安全的实例方法
}

锁的是:当前对象(this)

  • 每个对象有一个对象锁(monitor)
  • 调用该方法时,线程必须先拿到该对象的锁
public static synchronized void staticMethod() {
    // 线程安全的静态方法
}

锁的是:类对象(Class对象,即 MyClass.class

  • 所有这个类的实例共享一把类锁
  • 和具体哪个对象无关

4、volatile 能否保证线程安全和synchronize有什么区别?

volatile
volatile 是 Java 的一个关键字,用来修饰变量,表示:
当一个线程修改了这个变量的值,其他线程能立即看到最新值(可见性)。
编译器和 CPU 不会重排序它前后的读写操作(有序性)。

总结:

  • 单纯的 volatile 变量的 set(写)和 get(读)操作是线程安全的,能保证可见性和原子性(针对单次赋值)。
  • 多线程读写同一个 volatile 变量,其他线程能立刻看到最新值。
  • 但如果是复合操作(比如 count++),volatile 无法保证原子性,需要用锁或原子类

synchronized
synchronized 是用来给代码块或方法加锁的,表示: 只有获得锁的线程才能执行被保护的代码(互斥访问)
能保证:

  • 原子性(操作不会被中断)
  • 可见性(释放锁前写入的内容,对其他线程可见)
  • 有序性(锁内代码执行有顺序)

5、Android如何判断对象是否可回收,GC会在什么时候触发

Android 如何判断对象是否可回收?
Android(使用 ART 虚拟机)主要采用的是:
可达性分析算法(Reachability Analysis)

判断逻辑:
从一组称为 GC Roots 的对象出发,沿着引用链向下搜索:

  • 如果对象可被 GC Roots 直接或间接引用 → 可达,不可回收
  • 如果对象与 GC Roots 没有引用关系 → 不可达,判定为垃圾,可回收

常见 GC Root 包括:

  • 活跃线程(Thread 对象)
  • 当前方法调用栈中的对象(局部变量)
  • 静态字段引用的对象
  • JNI 中引用的对象(Native 持有 Java 对象)

Android GC 触发时机? GC 并不会一直运行,而是在满足某些条件时被动或主动触发,常见触发条件如下:

场景描述
内存不足分配新对象时,如果没有足够空间,就会触发 GC
阈值触发分配对象数/大小达到阈值
系统空闲时系统空闲(Idle)状态下,后台触发 GC 降低内存
手动调用System.gc() 只是建议 GC,不保证立刻执行
内存分析/调试工具使用内存分析工具时强制触发 GC

6、String、StringBuilder、StringBuffer 有什么区别?

类型定义
String不可变的字符串常量,每次修改都会生成新对象。
StringBuilder可变字符串,适合单线程场景,效率高。
StringBuffer可变字符串,线程安全(方法加了 synchronized),适合多线程。
@Override
@NeverInline
public StringBuilder append(boolean b) {
    super.append(b);
    return this;
}
@Override
synchronized StringBuffer append(AbstractStringBuilder asb) {
    toStringCache = null;
    super.append(asb);
    return this;
}

在大量拼接字符串的场景中的性能:

StringBuilder > StringBuffer >> String

因为:

  • String 每次拼接都会创建新对象(低效)
  • StringBuffer 多了同步锁(比 StringBuilder 慢)
  • StringBuilder 无锁,性能最佳(适合绝大多数情况)

7、try-catch-finally 中 finally 一定会执行吗?return 会影响吗?

绝大多数情况下,finally 块一定会执行,无论:

  • 是否抛出异常
  • 是否有 return
  • 是否有 breakcontinue

8、HashMap 的工作原理

Map<String, String> map = new HashMap<>();
map.put("dog", "旺财");
map.put("cat", "咪咪");
map.put("pig", "佩奇");

内部结构:

table[0]      →  null
table[1]      →  Entry{key="cat", value="咪咪"}
table[2]      →  Entry{key="dog", value="旺财"} → Entry{key="pig", value="佩奇"} (链表)
table[3]      →  null

每个桶(数组索引)可以容纳多个 Entry,形成链表或树。

步骤详解:

  1. 计算 hash 值
    hash("dog") → 得到一个整数 hash 值

  2. 确定数组索引
    index = hash & (table.length - 1) → 落入某个 table[i]

  3. 查看该位置是否已有元素

    • 若无,直接插入新 Entry
    • 若有,Java 7 及以下永久链表,Java 8起同一桶链表长度 > 8 且 table.length ≥ 64,转为红黑树

Get流程

map.get("dog")
  1. 计算 "dog" 的 hash → 得到 index
  2. 找到 table[index]
  3. 遍历链表 / 树,判断 key 是否相等(使用 equals())
  4. 找到返回对应的 value

9、Java中的线程池有哪些

线程池方法特点应用场景
固定线程池Executors.newFixedThreadPool(int n)固定数量线程控制并发线程数,线程可复用
单线程池Executors.newSingleThreadExecutor()单个线程顺序执行任务保证顺序、线程串行化
缓存线程池Executors.newCachedThreadPool()按需创建线程,空闲线程复用,最多 Integer.MAX_VALUE 个线程执行大量短期异步任务
定时线程池Executors.newScheduledThreadPool(int n)可定时或周期性执行任务定时任务、周期调度
工作窃取线程池(Java 8+)Executors.newWorkStealingPool()使用 ForkJoinPool 实现,多个任务队列,自动平衡任务并行计算、多核利用

大部分是使用了ThreadPoolExecutor:

ExecutorService threadPool = new ThreadPoolExecutor(
    corePoolSize,          // 核心线程数
    maximumPoolSize,       // 最大线程数
    keepAliveTime,         // 线程存活时间
    TimeUnit.SECONDS,      // 时间单位
    new LinkedBlockingQueue<>(),  // 队列
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

可以结合源码,看看Executors提供的几个线程池,各个参数的区别。

参数含义
corePoolSize核心线程数,常驻线程
maximumPoolSize最大线程数
keepAliveTime非核心线程闲置多久被回收
workQueue阻塞队列,保存待执行任务
threadFactory创建线程的工厂
handler拒绝策略,如:抛异常、调用者执行、丢弃任务

10、Java 的四种引用类型

特性强引用软引用弱引用虚引用
是否影响 GC不回收低内存时回收可回收可回收
回收条件无引用才回收内存不足时回收GC 扫描时回收GC 后回收并通知
主要用途一般对象持有缓存避免泄漏跟踪回收、资源释放
是否能 get()有值有值有值始终 null
需配合 ReferenceQueue不用不用可选必须

引用的核心作用:
帮助 GC 判断一个对象是否可达,是否应当被回收。

GC 会从一组被称为 “GC Roots” 的对象出发,沿着引用链向下遍历对象图。

  • 如果某个对象能从 GC Roots 通过引用链访问到,说明它不能被回收;
  • 如果无法访问到,则认为对象 “不可达” ,可能被回收。