1.匿名内部类的使用格式和场景是怎样的?
1.匿名内部类的使用格式:
new 父类/接口{
重写方法...
}
匿名内部类的实现一般由:普通类、抽象类、接口,三种形式,案例如下:
package cn.hdc.itWork.d3.匿名内部类;
public class Main {
public static void main(String[] args) {
Animal animal = new Animal() {
@Override
public void run() {
System.out.println("run");
}
};
People people = new People() {
@Override
void say() {
System.out.println("6");
}
};
Car car = new Car() {
@Override
void run() {
System.out.println("run");
}
};
}
}
abstract class Car {
abstract void run();
}
class People {
void say() {
System.out.println("say");
}
}
interface Animal {
void run();
}
2.使用场景
匿名内部类的使用场景通常是在需要创建一个类的实例,但是这个实例只需要在一个地方使用的时候。这种方式可以避免定义额外的类文件,使得代码更加紧凑。
2.泛型的作用是什么?如何定义一个泛型类?
1.作用
类型安全性:
避免 ClassCastException:使用泛型可以确保在编译时就检查类型的安全性,避免运行时出现 ClassCastException。
明确类型:通过泛型,可以在编译时明确指定容器或方法中的具体类型,从而避免类型转换错误。
代码复用性:
通用类型:泛型允许编写通用的类或方法,这些类或方法可以处理多种类型的对象,提高了代码的复用性。
减少冗余代码:使用泛型可以减少为不同类型编写类似代码的冗余。
可读性和维护性:
清晰的类型信息:泛型使代码更清晰,更容易理解,提高了代码的可读性和可维护性。
2.定义
1.在类声明时添加类型参数: 类声明中的类型参数使用尖括号 <> 包围,并且通常使用大写字母来表示类型参数,如 T、E 或者其他有意义的单个字母。
public class CompareUtils<T, E> {}
2.使用类型参数作为类成员的类型: 类的成员变量、方法参数或者返回类型都可以使用这个类型参数。
public T compare(T t, E e) { return (T) null; }
3.创建泛型类的对象时指定实际类型: 当你创建泛型类的实例时,你需要提供具体的类型参数,即类型参数的实际类型。
4.泛型类的约束: 有时你可能希望泛型类只能接受特定类型的子类型。例如,只允许 Number 的子类型。可以通过添加 extends 关键字和一个类名来限定类型参数的上界。public <T extends Number> T compare(T t, E e) { return (T) null; }
5.多重边界: 你还可以为类型参数指定多个边界。
6.注意事项
单一直接上界:Java 中一个类型参数只能有一个直接的类上界,但可以有多个接口上界。
泛型方法的边界:泛型方法也可以有自己的类型参数边界,但同样遵循上述规则。
下界实现:通过显式指定类型参数的具体类型来实现下界效果。
3.说一说你对泛型上限、下限、擦除的理解
1.上限
泛型上限是指在定义泛型时,可以限制类型参数只能是某个特定类型的子类型。这通常用于确保泛型类或方法中的操作具有某些特定的行为或属性。
例如:
<T extends Number>
规定了T只能说Number的子类型
2.下限
泛型下限是指在定义泛型时,可以限制类型参数只能是某个特定类型的超类型。这通常用于确保泛型类或方法中的操作能够处理某种类型的对象。
例如
<T super Double>
规定了T最少是Double类型的超类型,也就是父类或爷爷类甚至更高
3.擦除
泛型擦除:编译时确保类型安全,运行时泛型信息被擦除,只保留原始类型。
擦除案例:
package cn.hdc.itWork.d3_override.d3;
import java.util.ArrayList;
public class t3 {
public static void main(String[] args) {
Integer num1 = Integer.valueOf(5);
Integer num2 = Integer.valueOf(10);
Integer sum = add(num1, num2);
System.out.println("Integer sum: " + sum);
Double num3 = Double.valueOf(5.5);
Double num4 = Double.valueOf(10.5);
Double sum2 = add(num3, num4);
System.out.println("Double sum: " + sum2);
Long num5 = Long.valueOf(768);
Long num6 = Long.valueOf(1232);
Long sum3 = add(num5, num6);
System.out.println("Long sum: " + sum3);
new ArrayList<>();
}
private static <T extends Number> T add(T t1, T t2) {
if (t1 instanceof Double && t2 instanceof Double) {
double d1 = (double) t1;
//为什么double d1 = t1会报错?他不会自动拆箱吗?
/**
* add 方法中,t1 是一个泛型参数 T,并且已经通过 instanceof 判断为 Double 类型。
* 然而,在 double d1 = t1 这一行,编译器无法自动进行拆箱的原因如下:
* 泛型类型擦除:Java 泛型在编译时会被擦除为其对应的原始类型。这意味着 T 实际上会被擦除为 Number 类型。
* 因此,编译器不知道 t1 确切是什么类型,只知道它是 Number 的子类型。在这种情况下,编译器无法自动进行拆箱。
* 类型安全检查:尽管您已经通过 instanceof 检查了 t1 是 Double 类型,编译器在静态类型检查阶段并不知道这一点。
* 因此,它不会自动进行拆箱。
*/
double d2 = (double) t2;
return (T) Double.valueOf(d1 + d2);
//为什么这里需要强转泛型T?
/**
* 在给定的方法中,T 是一个泛型类型,并且它被限制为 Number 类型或其子类的实例。
* 当你在这个方法内部执行加法操作时,你需要确保返回的结果类型与输入参数的类型相匹配。
* 由于 Double 是一个具体的类型而 T 是一个泛型类型,
* 即使你知道结果是一个 Double 值,你也必须将其转换回泛型 T 的形式以便正确地符合方法签名。
*/
} else if (t1 instanceof Integer && t2 instanceof Integer) {
int d1 = (int) t1;
int d2 = (int) t2;
return (T) Integer.valueOf(d1 + d2);
} else if (t1 instanceof Long && t2 instanceof Long) {
long d1 = (long) t1;
long d2 = (long) t2;
return (T) Long.valueOf(d1 + d2);
} else {
return null;
}
}
}
4.Object中的toString和equals方法作用是什么?我们经常怎么用?
1.toString
toString() 方法的主要目的是返回一个表示对象状态的字符串。默认情况下,Object 类中的 toString() 方法返回的是对象的类名加上对象的内存地址(通常是十六进制的哈希码)。实体类中重写该方法后会覆盖 toString() 的默认方法来提供更有意义的字符串表示。
2.equals
equals() 方法用于比较两个对象是否“相等”。在 Object 类中,默认的实现是比较两个对象的引用是否指向同一个内存地址。在大多数情况下,我们需要覆盖 equals() 方法来提供逻辑上的相等性判断。通常重写用于自定义实体类比较规则。
5.为什么要有包装类?什么是自动装箱和拆箱?
1.为什么有包装类
Java 目前支持两种类型的数据:基本数据类型(如 int, double 等)和引用数据类型(类、接口等)。基本数据类型不是对象,不支持继承等面向对象的特性。然而,在 Java 中,许多框架和库都是基于对象设计的,比如集合框架(如 List, Set, Map 等),这就需要将基本数据类型转换为对象类型以便使用这些框架提供的功能。
为了满足这一需求,Java 提供了一套包装类(Wrapper Classes),每个基本数据类型都有对应的包装类。
2.什么是自动装箱和拆箱
自动装箱是指 Java 编译器自动将基本数据类型转换为对应的包装类对象的过程。
自动拆箱是指 Java 编译器自动将包装类对象转换为基本数据类型的过程。
6.写出下面代码的执行原理
Integer x = 100;
x += 200;
System.out.println(x);
包装类Integer x = 100;
x+=200;即:x = x + 200;
x = x + 200;
先运算x + 200,此时自动拆箱把x转为int进行运算,然后自动装箱把int x = 300 包装成Integer
最后输出300