java专项八股知识点(1)

目录

  1. 外部类&内部类
  2. String、StringBuilder和StringBuffer的区别
  3. super和this关键字

  在 Java 中,类可以分为外部类内部类

  • 外部类是最常见的类,定义在 .java 文件的顶层。它可以用修饰符如 publicdefaultabstractfinal 修饰,但不能使用 static 修饰。一个源文件中最多只能有一个 public 外部类,且文件名必须与该类名一致。外部类适合承载完整的业务逻辑,如服务类、控制器类、实体类等。

  • 内部类是定义在另一个类内部的类,主要有三种类型:成员内部类静态内部类匿名内部类。其中,只有静态内部类可以使用 static 修饰。成员内部类和匿名内部类都可以访问外部类的属性和方法,即使这些成员是私有的。静态内部类无法访问外部类的非静态成员。

  从编译结果来看,外部类会被编译为 Outer.class,而内部类会被编译为 Outer$Inner.class 的字节码文件。

  在实际开发中,内部类常用于构建辅助结构,增强封装性。例如:

  • 使用静态内部类实现 Builder 模式;

  • 使用匿名内部类实现按钮点击事件或线程回调;

  • 使用成员内部类管理与外部类高度耦合的数据结构。

(1)成员内部类(非静态)

  成员内部类定义在类的内部,但不是静态的,因此它可以访问外部类的所有成员,包括私有变量。通常用于表达**“整体-部分”关系**,比如学校和学生、公司和员工。

应用场景:

  • 一个类的数据对象高度依赖外部类的上下文(如组织内的成员、设备内的部件)

public class School {
    private String name = "大学";

    // 成员内部类
    public class Student {
        private String studentName;

        public Student(String studentName) {
            this.studentName = studentName;
        }

        public void introduce() {
            // 可以访问外部类成员
            System.out.println("我是 " + studentName + ",来自 " + name);
        }
    }

    public static void main(String[] args) {
        School school = new School();
        Student stu = school.new Student("小明");
        stu.introduce();
    }
}

 (2)静态内部类

  静态内部类是用 static 修饰的内部类。它不能访问外部类的非静态成员,更像一个逻辑上嵌套的独立类,常用于Builder 模式工具类组装等场景。

public class User {
    private String name;
    private int age;

    // 静态内部类 - Builder 模式
    public static class Builder {
        private String name;
        private int age;

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public User build() {
            User user = new User();
            user.name = this.name;
            user.age = this.age;
            return user;
        }
    }

    @Override
    public String toString() {
        return name + " - " + age;
    }

    public static void main(String[] args) {
        User user = new User.Builder()
                .name("张三")
                .age(25)
                .build();
        System.out.println(user);
    }
}

(3)匿名内部类

  匿名内部类是没有名字的内部类,常用于临时实现接口或继承类的逻辑,适用于只用一次的情况,比如线程创建、回调事件处理。

public class Test {
    public static void main(String[] args) {
        // 使用匿名内部类创建线程
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程已启动!");
            }
        });
        t.start();
    }
}

 (1)String(不可变字符串)

   String底层是final char[],它是不可变的,所有字符串拼接、替换、substring等操作,都会创建新对象。不可变性有一些好处:

  • 线程安全(多个线程共享同一个字符串不会有并发问题)
String s = "hello";
Thread t1 = new Thread(() -> {
    System.out.println(s);
});
Thread t2 = new Thread(() -> {
    System.out.println(s);
});
两个线程都在访问s,但因为String是不可变的,所以很安全:
- 内部的char[]是final;
- 无法修改字符串内容,只能重新赋值;
- 无论多少线程读同一个字符串,都不会出现竞态条件。
  • 可以被安全缓存(字符串常量池)

Java 中有个优化机制叫 字符串常量池(String Pool)

String a = "abc";
String b = "abc";
System.out.println(a == b);  // true,指向同一个地址
  • JVM 会自动把 字符串字面量缓存到堆外常量池中

  • 下次用同样的字面量,就不会创建新对象,而是复用;

  • 前提是:对象不可变,否则池里的值就可能被污染!

🤯 如果 String 是可变的,会发生什么?

  String a = "abc"; // 存入常量池
  String b = "abc"; // b 复用了 a 的地址
  a.charAt(0) = 'x'; // 如果可变,那 b 就变成了 "xbc"

 这就相当于你篡改了别人家的数据,整个 JVM 的字符串世界就崩了。所以 Java 强制让 String 不可变,从根本上解决了这个问题。

  • 可以作为 HashMap 的 key(哈希值稳定)
Map<String, Integer> map = new HashMap<>();
map.put("user123", 100);
System.out.println(map.get("user123")); // 100

背后的原理是:

  • HashMap 根据 key.hashCode() 来定位桶;

  • 再用 equals() 来比较内容是否一致;

  • 如果你用的 key 是 可变对象,哈希值可能变了 → 就找不到了!

(2)StringBuilder和StringBuffer

  它们底层都是char[],是可变的,都是继承自AbstractStringBuider,逻辑差不多,最大差别是:

对比项StringBuilderStringBuffer
是否线程安全 ❌ 不安全 线程安全(方法加了 synchronized
性能 🚀 快(无锁) 🐢 慢(加锁)
使用场景 单线程字符串拼接等高性能场景 多线程共享构建字符串时使用

 

public synchronized void append(String str) {
    toStringCache = null;
    super.append(str);
}
在StringBuffer中,大部分方法都被syunchronized修饰,而StringBuilder完全没有。

  StringBuffer中有很多方法来修改字符串内容,如:

  • append(String str):在末尾添加内容
  • insert(int offset, String str):在指定位置插入内容。先把插入点之后的所有字符向后移动,再把新字符串复制进去;
  • delete(int start, int end):删除从 startend - 1 的字符。
  • replace(int start, int end, String str):替换 [start, end) 范围内的字符为 str。等价于:delete(start, end) + insert(start, str),中间可能涉及两次数组移动 + 一次扩容。
  • reverse():将整个字符数组反转。

(3)应用场景

你想做的事推荐用法原因
多线程拼接字符串 StringBuffer 有锁,线程安全
单线程频繁拼接字符串 StringBuilder 无锁,性能更好
不变字符串,作为 key、常量等 String 不可变,哈希值稳定,能进常量池

 

 (1)对象创建的底层流程(从父到子)

当你创建一个子类对象时:new Child(),Java会自动做这些事:

  • 加载类元数据
  • 为对象分配内存
  • 默认初始化成员变量(为0、null等)
  • 执行父类构造器->子类构造器
  • 完成对象创建并返回引用

  关键是:Java 强制要求先执行父类构造器,否则子类可能会访问一个未初始化的父类成员,这样就违背了 Java 的安全模型。

(2)super()的核心作用

super() 是用来在子类构造器中显式调用父类构造器的。

class Parent {
    Parent() {
        System.out.println("Parent构造");
    }
}

class Child extends Parent {
    Child() {
        super(); // ✅ 必须在第一行
        System.out.println("Child构造");
    }
}

  为什么必须在第一行?

  • 因为 Java 编译器要确保父类构造器在子类构造器运行前最先执行

  • 如果不是第一行,可能中间有语句会使用到子类字段/方法,此时父类可能还没准备好 → ❌ 编译失败。

(3)this()的核心作用

  this() 是在构造器中调用本类的其他构造方法,用于构造器重载复用代码

class Book {
    Book() {
        this("default title");
    }

    Book(String title) {
        System.out.println("构造Book:" + title);
    }
}
  • this() 也必须是构造器的第一行;

  • 原因类似于 super(),构造器是对象初始化的一部分,调用必须明确且优先。

(4)this() 和 super() 不能同时出现的根本原因:

  它们都要求“必须是构造器的第一行”,第一行只能有一个语句,它们就“打架”了。

(5)为什么static方法里不能用this

public static void main(String[] args) {
    this.doSomething(); // ❌ 编译失败:Cannot use this in a static context
}

  因为此时你是用 类名来调用方法,没有任何一个对象来充当 this,编译器无法推断出 this 是谁。

你可以把 thissuper 理解成“对象的自我和父亲”:

  • this:我是谁(当前这个对象)

  • super:我爸是谁(当前对象的父类)

但如果你还没出生(new 都没 new),你怎么谈“我是谁”和“我爸是谁”?Java 的 static 方法就是这种“没有对象的静态存在”。

 (1)基本定义

项目ServletCGI(Common Gateway Interface)
本质 Java 提供的动态网页组件,运行在 Web 容器中 一种服务器与脚本程序交互的通用接口协议
类型 Java 类 可执行脚本(如 .py, .pl, .sh)或可执行文件
起源 Java EE 标准,从 1990s 开始用于 Web 开发 早期 Web 开发通用方法,1993 年被广泛采用

 

(3)工作原理

  Servlet 工作流程:

浏览器请求 URL
↓
Web 容器(如 Tomcat)解析 URL → 找到对应 Servlet 类
↓
Servlet 对象在 JVM 内存中已存在(单例)
↓
为当前请求分配一个线程,执行 service/doGet/doPost 方法
↓
Servlet 返回动态 HTML 或 JSON 响应

  CGI 工作流程:

浏览器请求 hello.cgi
↓
服务器检测 .cgi,fork 一个新进程运行该脚本
↓
读取请求数据(通过环境变量或 stdin)
↓
脚本生成 HTML 输出 → stdout 返回结果
↓
进程销毁

(4)优缺点总结

✅ Servlet 优点:

  • 性能高(线程而非进程)

  • 易于维护(面向对象)

  • 功能丰富(原生支持请求解析、Session、Cookie)

  • 高可移植性(只要能跑 JVM 就能跑)

❌ CGI 缺点:

  • 每次请求都创建新进程,性能极差

  • 难以管理用户状态(无内建 Session)

  • 兼容性和安全性差(易受攻击)

  • 不适合高并发场景(很快就把服务器拖垮)

A. d << 2

错误
原因:<< 是位运算符,只能用在整型(int、long 等),不能对 double 使用。


B. d / n

合法
原因:double 和 int 可以做除法,int 会自动转换为 double,结果是浮点数。


C. !d && (n - 3)

错误
原因:! 是逻辑非,只能用在 boolean 上,不能对 double 使用。
并且 (n - 3) 是 int,不能直接参与逻辑运算。


D. (d - 0.2) | n

错误
原因:| 是按位或,只能用于整数,不能用在 double 上。

posted @ 2025-05-27 15:35  筱倩  阅读(43)  评论(0)    收藏  举报