学习通上的练习题再仔细过一遍所有的选项,做好整理总结,相信自己,加油!
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
,也不能写int
、String
这些返回类型 )。 - 调用时机:创建对象时(
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. 特殊情况:编译器自动生成构造方法
- 如果你没写任何构造方法,编译器会自动生成一个 “无参、空实现” 的构造方法(默认构造方法)。
- 但只要你写了任意一个构造方法(不管有参还是无参),编译器就不会再自动生成默认构造方法。所以如果需要无参构造,建议手动写上,避免后续调用报错。
总结一下:构造方法的标志性特点就是 **“与类同名、无返回类型、创建对象时自动调用”** ,记住这几条,遇到相关题目就能快速判断啦~
3 “超类”(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();
是向上转型,属于多态的典型应用。- 编译时限制:只能调用父类中定义的方法。
- 运行时动态绑定:实际执行子类重写的方法(若有)。
记住这个规则,考试遇到类似题直接按 “编译看左边,运行看右边” 的逻辑分析~ 😄
5 “编译看左边,运行看右边”的详细解释
在 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()
),需先将父类引用强制转换为子类类型(向下转型),但需注意:
- 转换前必须用
instanceof
检查类型,避免ClassCastException
; - 仅当父类引用实际指向子类对象时,转换才会成功。
示例:
java
Animal animal = new Dog(); // 父类引用指向子类对象
if (animal instanceof Dog) { // 先检查类型
Dog dog = (Dog) animal; // 强制转换为子类类型
dog.bark(); // ✅ 调用子类特有方法
}
4. 总结:方法调用的完整逻辑
- 编译阶段:
- 检查父类(引用类型)是否声明了该方法,未声明则编译报错。
- 运行阶段:
- 若子类重写了方法,执行子类实现;
- 否则,执行父类原始实现。
口诀记忆:
- 可调用的方法看左边(父类),
- 方法的具体行为看右边(实际对象)。
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 考试中常考的多态 核心考点总结
一、多态的定义与本质
- 定义:同一操作作用于不同对象,产生不同的执行结果(即 “一个接口,多种实现”)。
- 本质:通过父类 / 接口引用调用子类重写的方法,运行时动态绑定实际对象类型。
- 核心条件(需同时满足):
- 继承 / 实现关系(子类继承父类或实现接口)。
- 方法重写(子类覆盖父类的方法)。
- 父类引用指向子类对象(如
Parent obj = new Child();
)。
二、多态的表现形式(必考点)
- 继承中的方法重写
- 父类引用调用子类重写的方法,例如:
java
Animal animal = new Dog(); // Animal 是父类,Dog 是子类 animal.bark(); // 调用 Dog 重写的 bark() 方法,而非 Animal 的方法
- 父类引用调用子类重写的方法,例如:
- 接口与实现类
- 接口引用指向实现类对象,例如:
java
Comparator<Person> comparator = new AgeComparator(); // Comparator 是接口
- 接口引用指向实现类对象,例如:
- 抽象类与子类
- 抽象类引用指向具体子类对象,例如:
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"(父类静态方法)
四、多态的典型应用场景(简答题高频)
- 接口回调:如 Java 的
Comparator
接口用于排序(见前例)。 - 策略模式:不同支付方式、算法动态切换(如支付策略选择)。
- 模板方法模式:父类定义流程骨架,子类实现细节(如游戏初始化)。
- 集合框架:通过接口操作不同实现类(如
List list = new ArrayList();
)。 - 事件监听:Swing 事件处理、Web 框架的事件回调机制。
五、多态相关的面试 / 笔试真题示例
代码分析题:
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(多态调用子类重写方法)。
代码解释
多态机制:
- 变量
a1
和a2
被声明为A
类型,但分别指向B
和C
的实例。 - Java 中,方法调用基于对象的实际类型(而非引用类型),因此会调用实际类型中重写的
f()
方法。
- 变量
方法重写:
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) | 子类实例 | 访问子类特有方法(但失去多态优势) |
关键原则:父类引用只能调用父类中定义的方法(即使实际对象重写了该方法),但具体执行逻辑由实际类型决定。
概念辨析题:
- “多态是否适用于成员变量?”
答案:不适用。成员变量访问由编译期类型决定,与对象实际类型无关(见前文表格)。
- “多态是否适用于成员变量?”
简答题:
- “简述多态的作用及实现条件。”
要点:解耦、可扩展性、代码复用;需满足继承 / 实现、方法重写、父类引用指向子类对象。
- “简述多态的作用及实现条件。”
六、快速记忆口诀
- 多态三要素:继承重写加父引,运行动态绑方法。
- 成员访问规则:方法看右边(运行时对象),变量看左边(编译时类型),静态方法也看左。
- 应用场景:接口回调策略模,模板集合事件多。
掌握这些核心考点,应对考试中的多态题目基本无压力啦! 🚀
7 方法重写(方法覆盖)不可以降低访问权限
核心特点:
方法签名必须相同:
- 方法名、参数列表、返回类型(Java 5+ 支持协变返回类型,即子类返回类型可以是父类返回类型的子类)必须与父类完全一致。
访问权限不能更严格(子不能比父严格):
- 子类方法的访问修饰符(如
public
、protected
)不能比父类更严格(例如父类是protected
,子类不能是private
)。
- 子类方法的访问修饰符(如
异常声明范围更小:
- 子类方法抛出的异常必须是父类方法声明异常的子类或不抛出异常。
只针对实例方法:
- 静态方法(
static
)和私有方法(private
)不能被覆盖(但子类可以定义同名方法,称为隐藏)。
- 静态方法(
8 继承中的成员变量隐藏和方法调用规则
关键点分析:
成员变量隐藏:
Test
类中的private String name
与父类Father
中的private String name
是两个独立的变量。- 子类无法直接访问父类的私有变量,因此子类的
name
变量隐藏了父类的同名变量,但不会影响父类方法的实现。
方法调用规则:
test.getName()
调用的是父类Father
的getName()
方法。- 父类的
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 不可变性与此题无关,因为本题未涉及字符串修改操作,仅涉及变量访问和方法调用。
9 接口之间可以多继承(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"); }
}
关键点:
接口支持多继承:
- 一个接口可以通过
extends
关键字同时继承多个父接口,用逗号分隔。
- 一个接口可以通过
方法签名合并:
- 如果多个父接口中存在相同方法签名(方法名、参数列表、返回类型均相同),子接口不会报错,而是继承该方法。
- 如果方法签名相同但返回类型或异常声明冲突,会导致编译错误。
默认方法冲突:
- 如果多个父接口提供了同名且参数相同的默认方法,子接口必须重写该默认方法以解决冲突:
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
同时继承自A
和B
,而A
和B
有同名方法,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]
,计算其之前的元素个数:
- 前 3 行的元素总数:
3 行 × 7 列/行 = 21 个元素
。 - 当前行(第 3 行)中
a[3][4]
之前的元素:
第 3 行的索引范围是0
到3
(共 4 个元素)。 - 总数:
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. 常见易错点
- 索引混淆:行 / 列索引从
0
开始,a[3][4]
是第 4 行第 5 列的元素。 - 计算行数:前 3 行的索引是
0
、1
、2
(共 3 行),而非0
到3
(4 行)。 - 列元素计算:当前行中
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
(队列)。 - 实现类:
ArrayList
、LinkedList
、HashSet
、TreeSet
、HashMap
、TreeMap
等。
- 顶层接口:
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. 常见面试题
泛型的边界是什么?
- 答:使用
extends
和super
限定泛型类型范围,如<T extends Number>
(T 必须是 Number 或其子类)。
- 答:使用
List
和Set
的区别?- 答:
List
有序可重复,Set
无序唯一。
- 答:
HashMap
和Hashtable
的区别?- 答:
HashMap
非线程安全、允许 null 键值,Hashtable
线程安全、不允许 null。
- 答:
如何遍历
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 集合与泛型的核心内容。