java复习 20

发布于:2025-06-18 ⋅ 阅读:(21) ⋅ 点赞:(0)

学习通上的练习题再仔细过一遍所有的选项,做好整理总结,相信自己,加油!

1 double c = 0.0f;这样写合法的,是小范围转大范围

合法,但可能存在精度损失风险。以下是详细分析:

1. 语法合法性

  • 合法float 可自动转换为 double(小范围 → 大范围,自动类型提升)。

    java

    double c = 0.0f;  // ✅ 合法,float 转 double 无需显式强制类型转换
    
  • 内存表示

    • float(32 位,单精度) → double(64 位,双精度),转换后精度更高。

2. 潜在风险:精度损失(反向操作时)

  • double → float 需强制转换(可能丢失精度):

    java

    float f = 3.1415926;  // ❌ 编译错误:可能损失精度
    float f = (float) 3.1415926;  // ✅ 强制转换,截断为 3.1415927(float 精度限制)
    
  • 示例验证

    java

    double d = 1.1234567890123456789;
    float f = (float) d;  // f 实际值:1.1234568(精度丢失)
    

3. 常见混淆点

(1)字面量后缀问题
  • 0.0 默认是 double,需加 f 显式声明为 float

    java

    float f = 0.0;  // ❌ 编译错误:double 无法自动转为 float
    float f = 0.0f; // ✅ 正确
    
(2)浮点数比较陷阱
  • float 和 double 比较可能不等(即使数值看似相同):

    java

    double d = 0.1;
    float f = 0.1f;
    System.out.println(d == f);  // ❌ 输出 false(二进制表示不同)
    

4. 速记口诀

“小转大自动,大转小强转;float 转 double 稳,double 转 float 截断!”

总结

  • double c = 0.0f; 合法(自动类型提升)。
  • 但需注意:反向转换(double → float)必须强制类型转换,且可能丢失精度。

2 构造方法没有返回类型的,再强化一下,注意!

在 Java 里,构造方法确实没有返回类型,连 void 都不能写 ,这是它的核心特性之一,展开讲讲:

1. 构造方法的定义规则

构造方法是类里特殊的方法,格式上:

  • 方法名:必须和类名完全一致(大小写都要相同,比如类叫 Person,构造方法就叫 Person)。
  • 返回类型没有返回类型声明(既不能写 void,也不能写 intString 这些返回类型 )。
  • 调用时机:创建对象时(new 关键字)自动调用,没法像普通方法一样手动调用。

举个例子:

java

public class Person {
    String name;
    // 构造方法,没有返回类型
    public Person(String n) { 
        name = n;
    }
}

创建对象时会触发构造方法:

java

Person p = new Person("张三"); // new 的时候,调用构造方法初始化

2. 为啥不能有返回类型

构造方法的 “隐藏任务” 是创建并初始化对象,JVM 会隐式返回刚创建好的对象引用。从设计逻辑上:

  • 它的核心是 “初始化对象”,不是 “返回一个值”,所以不需要显式声明返回类型。
  • 如果写了 void 或者其他返回类型,Java 就会把它当成普通方法,而不是构造方法,会引发各种问题(比如创建对象时找不到构造方法)。

3. 和普通方法的关键区别

对比项 构造方法 普通方法
方法名 必须和类名一致 自定义(符合命名规范即可)
返回类型 无(连 void 都不写) 必须声明(void 或具体类型)
调用时机 new 对象时自动调用 对象创建后,手动调用
作用 初始化对象属性 实现业务逻辑、返回数据等

比如下面代码,Person 里的 Person 是构造方法(无返回类型),sayHi 是普通方法(有 void 返回类型):

java

public class Person {
    String name;
    // 构造方法
    public Person(String n) { 
        name = n;
    }
    // 普通方法
    public void sayHi() { 
        System.out.println("你好,我是" + name);
    }
}

4. 特殊情况:编译器自动生成构造方法

  • 如果你没写任何构造方法,编译器会自动生成一个 “无参、空实现” 的构造方法(默认构造方法)。
  • 但只要你写了任意一个构造方法(不管有参还是无参),编译器就不会再自动生成默认构造方法。所以如果需要无参构造,建议手动写上,避免后续调用报错。

总结一下:构造方法的标志性特点就是 **“与类同名、无返回类型、创建对象时自动调用”** ,记住这几条,遇到相关题目就能快速判断啦~

“超类”(Superclass)就是 “父类”

在 Java 的继承体系里,“超类”(Superclass)就是 “父类”,指被其他类继承的类。理解这个概念对掌握构造方法的执行顺序很关键,帮你快速理清:

1. 超类(父类)的定义

  • 超类:被继承的类,也叫 “父类” 或 “基类”。
  • 子类:继承超类的类,也叫 “派生类”。
  • 语法:用 extends 关键字声明继承关系。

示例

java

// 超类(父类)
class Animal {
    public Animal() {
        System.out.println("Animal 构造方法");
    }
}

// 子类(继承自 Animal)
class Dog extends Animal {
    public Dog() {
        System.out.println("Dog 构造方法");
    }
}

2. 超类构造方法的特性

  • 必须先调用超类构造方法
    子类对象创建时,会自动先调用父类的构造方法,再执行子类自己的构造方法(无论父类构造方法是否为空)。
  • 隐式调用 super()
    子类构造方法的第一行默认有 super()(无参构造),除非显式调用父类的其他构造方法(用 super(参数))。

执行流程

java

Dog dog = new Dog();  // 创建子类对象

// 输出结果:
// Animal 构造方法  ← 先执行父类构造
// Dog 构造方法    ← 再执行子类构造

3. 子类显式调用父类构造方法

如果父类没有无参构造方法,子类必须在构造方法中显式调用父类的有参构造(用 super(参数)):

java

class Animal {
    private String name;
    public Animal(String name) {  // 父类只有有参构造
        this.name = name;
    }
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);  // 显式调用父类有参构造(必须放第一行!)
    }
}

4. 关键规则总结

场景 规则
父类有无参构造 子类构造默认调用 super()(可省略不写)。
父类只有有参构造 子类必须在构造方法中用 super(参数) 显式调用父类构造(否则编译报错)。
子类构造中调用 this() this() 和 super() 不能同时存在,因为都必须放在第一行。

5. 常见面试题:构造方法执行顺序

java

class Grandfather {
    public Grandfather() { System.out.println("爷爷"); }
}

class Father extends Grandfather {
    public Father() { System.out.println("父亲"); }
}

class Son extends Father {
    public Son() { System.out.println("儿子"); }
}

// 执行:
new Son();

// 输出顺序:
// 爷爷 → 父亲 → 儿子  ← 从最顶层父类依次向下执行

总结

  • 超类 = 父类,是被继承的类。
  • 子类构造必调父类构造(通过 super() 或 super(参数)),且必须放在第一行。
  • 执行顺序:从最顶层父类开始,逐层向下执行,最后执行子类自己的构造方法。

记住这个逻辑,考试遇到继承和构造方法的题,直接按 “先父后子” 顺序推导~ 😄

4 写成Animal dog = new Dog ();这个算什么?(多态,向上转型)

这行代码涉及 Java 中的 多态(Polymorphism) 特性,属于向上转型(Upcasting),具体解释如下:

1. 代码本质:多态的体现

java

Animal dog = new Dog();  // 父类引用指向子类对象
  • 类型分析

    • 编译时类型Animal(引用类型为父类)。
    • 运行时类型Dog(实际对象是子类)。
  • 关键规则

    • 只能调用父类声明的方法(编译时检查)。
    • 实际执行子类重写的方法(运行时动态绑定)。

2. 方法调用规则

假设 Animal 和 Dog 定义如下:

java

class Animal {
    public void eat() {
        System.out.println("Animal eats");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {  // 重写父类方法
        System.out.println("Dog eats bones");
    }
    
    public void bark() {  // 子类特有方法
        System.out.println("Dog barks");
    }
}
  • 调用父类方法(正常执行):

    java

    Animal dog = new Dog();
    dog.eat();  // 输出:Dog eats bones  ← 运行时调用子类重写的方法
    
  • 调用子类特有方法(编译错误):

    java

    dog.bark();  // ❌ 编译错误:Animal 类型没有 bark() 方法
    

3. 向上转型 vs 向下转型

类型转换 语法 特点
向上转型 父类 引用 = new 子类() 自动发生,安全子类是父类的 “一种”)。只能调用父类方法。
向下转型 子类 引用 = (子类) 父类引用 需强制类型转换,可能抛出 ClassCastException(需先通过 instanceof 检查)。

示例:向下转型

java

Animal animal = new Dog();  // 向上转型(自动)
Dog dog = (Dog) animal;     // 向下转型(需强制)
dog.bark();  // ✅ 编译通过,运行正常

错误案例:

java

Animal animal = new Animal();  // 父类对象
Dog dog = (Dog) animal;        // ❌ 运行时抛出 ClassCastException

4. 多态的核心作用

  • 解耦代码:通过父类引用接收不同子类对象,实现方法调用的灵活性。
  • 统一接口:例如:

    java

    List<String> list = new ArrayList<>();  // List 是接口,ArrayList 是实现类
    list.add("hello");  // 调用接口方法,实际执行 ArrayList 的实现
    

总结

  • Animal dog = new Dog(); 是向上转型,属于多态的典型应用。
  • 编译时限制:只能调用父类中定义的方法。
  • 运行时动态绑定:实际执行子类重写的方法(若有)。

记住这个规则,考试遇到类似题直接按 “编译看左边,运行看右边” 的逻辑分析~ 😄

“编译看左边,运行看右边”的详细解释

在 Animal dog = new Dog(); 这种多态场景下,方法调用的规则可以总结为 “编译看左边,运行看右边”,具体到方法的使用规则如下:

1. 编译阶段:由引用类型(左边)决定可调用的方法

  • 引用类型是父类(Animal,因此只能调用父类中声明的方法(包括父类自身的方法和被子类重写的方法)。
  • 子类特有的方法(如 Dog.bark())无法通过父类引用调用,编译时会报错。

示例

java

Animal dog = new Dog();
dog.eat();       // ✅ 父类有 eat() 方法,编译通过
dog.bark();      // ❌ 父类没有 bark() 方法,编译报错

2. 运行阶段:由实际对象类型(右边)决定方法的具体实现

  • 若子类重写了父类方法,运行时优先执行子类的实现;
  • 若子类未重写父类方法,则执行父类的原始方法。

示例

java

class Animal {
    public void eat() { System.out.println("Animal eats"); }
}

class Dog extends Animal {
    @Override
    public void eat() { System.out.println("Dog eats bones"); }
}

Animal dog = new Dog();
dog.eat();  // 输出:Dog eats bones  ← 运行时调用子类重写的方法

3. 如何调用子类特有的方法?(向下转型)

若需要调用子类特有的方法(如 Dog.bark()),需先将父类引用强制转换为子类类型(向下转型),但需注意:

  1. 转换前必须用 instanceof 检查类型,避免 ClassCastException
  2. 仅当父类引用实际指向子类对象时,转换才会成功。

示例

java

Animal animal = new Dog();  // 父类引用指向子类对象

if (animal instanceof Dog) {  // 先检查类型
    Dog dog = (Dog) animal;   // 强制转换为子类类型
    dog.bark();              // ✅ 调用子类特有方法
}

4. 总结:方法调用的完整逻辑

  1. 编译阶段
    • 检查父类(引用类型)是否声明了该方法,未声明则编译报错。
  2. 运行阶段
    • 若子类重写了方法,执行子类实现;
    • 否则,执行父类原始实现。

口诀记忆

  • 可调用的方法看左边(父类)
  • 方法的具体行为看右边(实际对象)

5. 典型场景:多态的应用

多态常用于接口抽象或父类抽象,例如:

java

// 定义形状接口
interface Shape {
    void draw();
}

// 子类实现
class Circle implements Shape {
    @Override public void draw() { System.out.println("画圆"); }
}

class Rectangle implements Shape {
    @Override public void draw() { System.out.println("画矩形"); }
}

// 多态应用:通过接口引用处理不同形状
public void processShape(Shape shape) {
    shape.draw();  // 编译时检查 Shape 接口是否有 draw(),运行时调用具体实现
}

这种方式让代码更灵活,无需关心具体是哪种形状,只需调用统一接口。

核心结论

  • new Dog() 创建的对象可以通过父类引用(Animal)调用父类声明的方法,具体执行子类重写的逻辑(若存在);
  • 子类特有的方法必须通过向下转型为子类类型后才能调用。

掌握 “编译看左边,运行看右边” 的逻辑,就能轻松处理多态场景下的方法调用问题啦~ 😊

6 考试中常考的多态 核心考点总结

一、多态的定义与本质
  1. 定义:同一操作作用于不同对象,产生不同的执行结果(即 “一个接口,多种实现”)。
  2. 本质:通过父类 / 接口引用调用子类重写的方法,运行时动态绑定实际对象类型。
  3. 核心条件需同时满足):
    • 继承 / 实现关系(子类继承父类或实现接口)。
    • 方法重写(子类覆盖父类的方法)。
    • 父类引用指向子类对象(如 Parent obj = new Child();)。
二、多态的表现形式(必考点)
  1. 继承中的方法重写
    • 父类引用调用子类重写的方法,例如:

      java

      Animal animal = new Dog();  // Animal 是父类,Dog 是子类  
      animal.bark();  // 调用 Dog 重写的 bark() 方法,而非 Animal 的方法  
      
  2. 接口与实现类
    • 接口引用指向实现类对象,例如:

      java

      Comparator<Person> comparator = new AgeComparator();  // Comparator 是接口  
      
  3. 抽象类与子类
    • 抽象类引用指向具体子类对象,例如:

      java

      Shape shape = new Circle();  // Shape 是抽象类,Circle 是子类  
      
三、多态的成员访问规则(易混点)
访问类型 编译期行为 运行期行为
方法调用 检查父类是否存在该方法 调用子类重写的方法(动态绑定)
成员变量访问 仅访问父类的成员变量 与编译期一致(不体现多态)
静态方法调用 调用父类的静态方法 与编译期一致(不体现多态)

示例

java

class Parent {
    int num = 10;
    void method() { System.out.println("Parent method"); }
    static void staticMethod() { System.out.println("Parent static"); }
}
class Child extends Parent {
    int num = 20;
    @Override void method() { System.out.println("Child method"); }
    static void staticMethod() { System.out.println("Child static"); }
}
Parent p = new Child();
System.out.println(p.num);      // 输出 10(父类变量)
p.method();                     // 输出 "Child method"(子类方法)
p.staticMethod();               // 输出 "Parent static"(父类静态方法)
四、多态的典型应用场景(简答题高频)
  1. 接口回调:如 Java 的 Comparator 接口用于排序(见前例)。
  2. 策略模式:不同支付方式、算法动态切换(如支付策略选择)。
  3. 模板方法模式:父类定义流程骨架,子类实现细节(如游戏初始化)。
  4. 集合框架:通过接口操作不同实现类(如 List list = new ArrayList();)。
  5. 事件监听:Swing 事件处理、Web 框架的事件回调机制。
五、多态相关的面试 / 笔试真题示例
  1. 代码分析题

    java

    class A { void f() { System.out.println("A"); } }
    class B extends A { void f() { System.out.println("B"); } }
    class C extends B { void f() { System.out.println("C"); } }
    public class Test {
        public static void main(String[] args) {
            A a1 = new B(); A a2 = new C();
            a1.f(); a2.f();
        }
    }
    
     

    输出:B 和 C(多态调用子类重写方法)。

代码解释

  1. 多态机制

    • 变量 a1 和 a2 被声明为 A 类型,但分别指向 B 和 C 的实例。
    • Java 中,方法调用基于对象的实际类型(而非引用类型),因此会调用实际类型中重写的 f() 方法。
  2. 方法重写

    • B 和 C 类均重写了父类 A 的 f() 方法。
    • a1.f() 调用的是 B 类的 f(),输出 B
    • a2.f() 调用的是 C 类的 f(),输出 C

Q:所以说实际创建的都是子类的实例,但为什么类型是父类?

A:(我的理解是编译通过与否是看父类是否存在该方法,实际运行看子类的这个方法里面按照子类的方法体运行),因为编译器只关心引用类型(如A a1)是否包含目标方法(如f())。

在 Java 中,这种 “父类引用指向子类对象” 的机制是多态(Polymorphism)的核心体现,主要有以下原因:

1. 多态的实现基础,“父类引用指向子类对象” 的机制

Java 通过动态绑定(Runtime Dynamic Binding)实现方法调用,即:

  • 编译时:检查引用类型(如A a1)是否存在目标方法(如f())。
  • 运行时:根据对象的实际类型(如new B())调用对应的重写方法。

示例

java

A a1 = new B();  // 引用类型为A,实际类型为B
a1.f();          // 编译时检查A类有f()方法,运行时调用B类的f()

2. 为什么需要父类引用?

(1)代码复用与扩展性

通过父类类型作为参数或返回值,可以统一处理不同子类的对象:

java

// 接收父类A的引用,可传入任何A的子类对象
void process(A obj) {
    obj.f();  // 调用实际子类的f()方法
}

process(new B());  // 输出B
process(new C());  // 输出C
(2)接口与抽象类的应用

父类常被设计为抽象类接口,强制子类实现特定方法:

java

interface Shape {
    double area();  // 抽象方法
}

class Circle implements Shape {
    @Override
    public double area() { return Math.PI * r * r; }
}

Shape s = new Circle();  // 接口引用指向实现类
s.area();                // 调用Circle的area()

3. 与静态类型语言的关系

Java 是静态类型语言,变量类型必须在编译时确定。通过父类引用,可以在保证类型安全的前提下,实现运行时的动态行为:

java

A a = new B();  // 合法:B是A的子类
B b = new A();  // 非法:A不是B的子类

总结

引用类型 实际对象 作用
父类(如 A) 子类实例 实现多态,提高代码灵活性与复用性
子类(如 B) 子类实例 访问子类特有方法(但失去多态优势)

关键原则:父类引用只能调用父类中定义的方法(即使实际对象重写了该方法),但具体执行逻辑由实际类型决定。

  1. 概念辨析题

    • “多态是否适用于成员变量?”
      答案:不适用。成员变量访问由编译期类型决定,与对象实际类型无关(见前文表格)。
  2. 简答题

    • “简述多态的作用及实现条件。”
      要点:解耦、可扩展性、代码复用;需满足继承 / 实现、方法重写、父类引用指向子类对象。
六、快速记忆口诀
  • 多态三要素:继承重写加父引,运行动态绑方法。
  • 成员访问规则方法看右边(运行时对象),变量看左边(编译时类型),静态方法也看左。
  • 应用场景:接口回调策略模,模板集合事件多。

掌握这些核心考点,应对考试中的多态题目基本无压力啦! 🚀

7 方法重写(方法覆盖)不可以降低访问权限

核心特点:

  1. 方法签名必须相同

    • 方法名、参数列表、返回类型(Java 5+ 支持协变返回类型,即子类返回类型可以是父类返回类型的子类)必须与父类完全一致。
  2. 访问权限不能更严格(子不能比父严格)

    • 子类方法的访问修饰符(如publicprotected)不能比父类更严格(例如父类是protected,子类不能是private)。
  3. 异常声明范围更小

    • 子类方法抛出的异常必须是父类方法声明异常的子类或不抛出异常。
  4. 只针对实例方法

    • 静态方法static)和私有方法private)不能被覆盖(但子类可以定义同名方法,称为隐藏)。

继承中的成员变量隐藏方法调用规则

关键点分析:

  1. 成员变量隐藏

    • Test类中的private String name与父类Father中的private String name是两个独立的变量。
    • 子类无法直接访问父类的私有变量,因此子类的name变量隐藏了父类的同名变量,但不会影响父类方法的实现。
  2. 方法调用规则

    • test.getName()调用的是父类FathergetName()方法。
    • 父类的getName()方法返回的是父类自身的name变量(值为"father"),而非子类的name变量。

执行流程:

java

Test test = new Test();       // 创建Test对象
System.out.println(test.getName());  // 调用父类的getName()方法
                                     // 返回父类的name变量 → "father"

输出结果:

plaintext

father

为什么不是 String 不可变性?

  • String的不可变性是指字符串对象本身不可修改,但可以通过引用指向新的字符串对象。
  • 在本题中,无论name是否为String类型,结果都一样,因为:
    • 父类方法返回的是父类的变量,与子类变量无关。
    • 即使子类修改了自己的name变量,也不会影响父类的同名变量。

验证代码:

java

public class Test extends Father {
    private String name = "test"; // 隐藏父类的name变量

    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test.getName()); // 输出 "father"
        
        // 子类可以修改自己的name变量,但不影响父类方法
        test.name = "modified";
        System.out.println(test.getName()); // 仍输出 "father"
    }
}

class Father {
    private String name = "father";

    public String getName() {
        return name; // 返回父类的name变量
    }
}

总结:

  • 考点:继承中的变量隐藏和父类方法的作用域。
  • String 不可变性与此题无关,因为本题未涉及字符串修改操作,仅涉及变量访问和方法调用。

接口之间可以多继承(Multiple Inheritance),但是类是单继承的!

是的,在 Java 中,接口之间可以多继承(Multiple Inheritance),即一个接口可以同时继承多个父接口。这与类的单继承(一个类只能继承一个父类)形成对比。

接口多继承的语法:

java

interface InterfaceA {
    void methodA();
}

interface InterfaceB {
    void methodB();
}

// 接口C继承自InterfaceA和InterfaceB(多继承)
interface InterfaceC extends InterfaceA, InterfaceB {
    void methodC();
}

// 实现类必须实现所有继承的方法
class MyClass implements InterfaceC {
    @Override
    public void methodA() { System.out.println("Method A"); }
    
    @Override
    public void methodB() { System.out.println("Method B"); }
    
    @Override
    public void methodC() { System.out.println("Method C"); }
}

关键点:

  1. 接口支持多继承

    • 一个接口可以通过extends关键字同时继承多个父接口,用逗号分隔。
  2. 方法签名合并

    • 如果多个父接口中存在相同方法签名(方法名、参数列表、返回类型均相同),子接口不会报错,而是继承该方法。
    • 如果方法签名相同但返回类型或异常声明冲突,会导致编译错误。
  3. 默认方法冲突

    • 如果多个父接口提供了同名且参数相同的默认方法,子接口必须重写该默认方法以解决冲突:

      java

      interface A {
          default void foo() { System.out.println("A"); }
      }
      
      interface B {
          default void foo() { System.out.println("B"); }
      }
      
      interface C extends A, B {
          @Override
          default void foo() {
              A.super.foo(); // 显式调用A的默认方法
          }
      }
      

为什么类不支持多继承,而接口支持?

  • 类的多继承问题(菱形继承):
    若类C同时继承自AB,而AB有同名方法,C将无法确定使用哪个实现,导致歧义。

  • 接口的多继承安全

    • 接口中的方法默认是抽象的(Java 8 前),不存在方法实现冲突。
    • Java 8 引入默认方法后,通过强制子接口重写冲突的默认方法,避免了歧义。

示例:多继承的应用

java

interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

// 多继承:同时具备飞行和游泳能力
interface AmphibiousVehicle extends Flyable, Swimmable {
    void switchMode();
}

// 实现类需实现所有接口方法
class FlyingBoat implements AmphibiousVehicle {
    @Override
    public void fly() { System.out.println("Flying..."); }
    
    @Override
    public void swim() { System.out.println("Swimming..."); }
    
    @Override
    public void switchMode() { System.out.println("Switching mode..."); }
}

总结:

特性 类(Class) 接口(Interface)
继承限制 单继承(extends 一个类) 多继承(extends 多个接口)
冲突处理 不允许方法实现冲突 默认方法冲突需显式解决
设计目的 定义对象的状态和行为 定义行为规范(契约)

10 java 中二维数组 

结合题目 int a[6][7] 中 a[3][4] 之前的元素个数 进行详细说明:

1. 二维数组的定义与结构

在 Java 中,二维数组是数组的数组,本质上是嵌套结构。

java

int[][] a = new int[6][7];  // 定义一个6行7列的二维数组
  • 行索引范围0 到 5(共 6 行)。
  • 列索引范围0 到 6(每行 7 列)。
  • 存储方式:按行优先存储,即先存储第 0 行的所有元素,再存储第 1 行,依此类推。

2. 数组元素的索引规则

  • 索引从 0 开始a[3][4] 表示第 4 行(索引 3)、第 5 列(索引 4)的元素。
  • 内存布局:二维数组在内存中按一维顺序排列,例如:

    java

    a[0][0], a[0][1], ..., a[0][6],  // 第0行(7个元素)
    a[1][0], a[1][1], ..., a[1][6],  // 第1行(7个元素)
    ...
    a[5][0], a[5][1], ..., a[5][6]   // 第5行(7个元素)
    

3. 计算指定元素前的元素个数

对于 a[3][4],计算其之前的元素个数:

  1. 前 3 行的元素总数
    3 行 × 7 列/行 = 21 个元素
  2. 当前行(第 3 行)中 a[3][4] 之前的元素
    第 3 行的索引范围是 0 到 3(共 4 个元素)。
  3. 总数21 + 4 = 25 个元素。

4. 通用计算公式

对于二维数组 a[m][n],元素 a[i][j] 之前的元素个数为:

java

元素个数 = i × n + j
  • 示例a[3][4] 的元素个数为 3 × 7 + 4 = 25

5. Java 代码验证

java

public class Main {
    public static void main(String[] args) {
        int[][] a = new int[6][7];
        int count = 0;
        int targetRow = 3;
        int targetCol = 4;

        // 遍历前3行的所有元素
        for (int i = 0; i < targetRow; i++) {
            for (int j = 0; j < 7; j++) {
                count++;
            }
        }

        // 遍历当前行(第3行)中a[3][4]之前的元素
        for (int j = 0; j < targetCol; j++) {
            count++;
        }

        System.out.println("a[3][4]之前的元素个数:" + count); // 输出25
    }
}

6. 常见易错点

  1. 索引混淆:行 / 列索引从 0 开始,a[3][4] 是第 4 行第 5 列的元素。
  2. 计算行数:前 3 行的索引是 012(共 3 行),而非 0 到 3(4 行)。
  3. 列元素计算:当前行中 a[i][j] 之前的列索引是 0 到 j-1,共 j 个元素。

总结

通过本题掌握:

  • 二维数组的行优先存储规则。
  • 索引与元素位置的映射关系。
  • 快速计算指定位置前的元素个数(i×n + j)。

11 java声明变量的方括号 [] 前不能直接填数组长度

在 Java 中,方括号 [] 前不能直接填数组长度,这是明确的语法规则。!!!!!

但有以下两种情况可能被混淆,需要区分:

1. 动态初始化数组时,在 new 关键字后指定长度

合法语法

java

int[] a = new int[5];  // 在 new 后指定长度 5,创建数组
  • 关键点
    • 长度必须写在 new 关键字后的方括号内,而非声明变量时的方括号内。
    • 声明变量时,方括号 [] 必须为空(无论放在类型后还是变量名后)。

错误示例

java

int[5] a;         // 错误:声明变量时不能在 [] 中写长度
int a[5] = ...;   // 错误:无论静态/动态初始化,变量后的 [] 都不能写长度

2. 静态初始化数组时,直接通过元素列表隐式指定长度

合法语法

java

int[] a = {1, 2, 3};  // 元素列表隐含长度为 3
  • 关键点
    • 静态初始化(使用 {} 语法)时,绝对不能在任何方括号内写长度。!!!!!!
    • 以下写法均错误:

      java

      int[3] a = {1, 2, 3};    // 错误
      int a[3] = {1, 2, 3};    // 错误
      int[] a = new int[3]{1,2,3};  // 错误:静态初始化不能用 new + 长度
      

总结:唯一合法的 “指定长度” 场景!!!!

只有在 动态初始化 时,才能在 new 关键字后使用 [长度]

java

int[] a;            // 声明变量([] 为空)
a = new int[5];     // 在 new 时指定长度

其他情况下,方括号 [] 前或内部都不能直接写长度。

12 java 集合与泛型考试知识点清单

1. 泛型(Generics)

概念
  • 作用:在编译时检查类型安全,避免类型转换异常(ClassCastException)。
  • 语法:使用<>声明泛型类型,如List<String>Map<Integer, String>
示例代码

java

// 不使用泛型(需手动类型转换,可能出错)
List list = new ArrayList();
list.add("hello");
String str = (String) list.get(0); // 需要强制类型转换

// 使用泛型(编译时检查类型)
List<String> list = new ArrayList<>();
list.add("hello");
String str = list.get(0); // 无需类型转换,编译器保证类型安全

2. 集合框架(Collection Framework)

概念
  • 集合:存储对象的容器,提供统一操作接口。
  • 架构
    • 顶层接口Collection(单列集合)、Map(双列键值对)。
    • 子接口List(有序可重复)、Set(无序唯一)、Queue(队列)。
    • 实现类ArrayListLinkedListHashSetTreeSetHashMapTreeMap等。

3. 常用集合类型及方法

(1)List 接口
  • 特点:有序、可重复,支持索引访问。
  • 实现类ArrayList(动态数组)、LinkedList(双向链表)。

示例代码

java

List<String> list = new ArrayList<>();
list.add("apple");    // 添加元素
list.add("banana");
list.add(0, "cherry"); // 指定位置插入
System.out.println(list.get(1)); // 访问元素:banana
list.remove(0);        // 按索引删除
boolean exists = list.contains("apple"); // 判断元素是否存在
(2)Set 接口
  • 特点:无序、唯一(不允许重复元素)。
  • 实现类HashSet(哈希表)、TreeSet(红黑树,自动排序)。

示例代码

java

Set<Integer> set = new HashSet<>();
set.add(100);
set.add(200);
set.add(100); // 重复元素,添加失败
System.out.println(set.size()); // 输出:2
boolean has100 = set.contains(100); // 判断元素是否存在
(3)Map 接口
  • 特点:存储键值对(Key-Value),键唯一。
  • 实现类HashMap(哈希表)、TreeMap(红黑树,按键排序)。

示例代码

java

Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);    // 添加键值对
map.put("banana", 2);
map.put("apple", 3);    // 键重复,覆盖原值
int value = map.get("apple"); // 获取值:3
map.remove("banana");  // 按键删除
boolean hasKey = map.containsKey("apple"); // 判断键是否存在

4. 迭代器(Iterator)

作用

遍历集合元素,支持remove()操作。

使用方法

java

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

// 使用 Iterator 遍历
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String element = it.next();
    if (element.equals("B")) {
        it.remove(); // 安全删除当前元素
    }
}
System.out.println(list); // 输出:[A, C]

5. 集合的选择场景

集合类型 特点 适用场景
ArrayList 动态数组,随机访问快 频繁查询、少插入删除
LinkedList 双向链表,插入删除快 频繁插入删除、少查询
HashSet 无序唯一,基于哈希表 去重、无需排序
TreeSet 有序唯一,基于红黑树 自动排序(如按字母、数值顺序)
HashMap 键值对,无序,基于哈希表 快速查找、插入删除
TreeMap 键值对,有序,基于红黑树 按键排序的键值对(如按时间戳)

6. 常见面试题

  1. 泛型的边界是什么?

    • 答:使用extendssuper限定泛型类型范围,如<T extends Number>(T 必须是 Number 或其子类)。
  2. ListSet的区别?

    • 答:List有序可重复,Set无序唯一。
  3. HashMapHashtable的区别?

    • 答:HashMap非线程安全、允许 null 键值,Hashtable线程安全、不允许 null。
  4. 如何遍历Map

    • 答:

      java

      // 方法1:遍历键集
      for (String key : map.keySet()) { ... }
      
      // 方法2:遍历键值对(推荐)
      for (Map.Entry<String, Integer> entry : map.entrySet()) {
          String key = entry.getKey();
          Integer value = entry.getValue();
      }
      

7. 实战技巧

  • 初始化集合

    java

    List<String> list = List.of("A", "B", "C"); // 不可变集合(Java 9+)
    Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));
    
  • 集合转数组

    java

    List<String> list = List.of("A", "B", "C");
    String[] array = list.toArray(new String[0]);
    
  • 排序

    java

    List<Integer> list = Arrays.asList(3, 1, 2);
    Collections.sort(list); // 升序排序:[1, 2, 3]
    

通过以上知识点和代码示例,可系统掌握 Java 集合与泛型的核心内容。


网站公告

今日签到

点亮在社区的每一天
去签到