2024年Java最全带你快速看完9(4),京东最新Java面试真题解析

最后

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

image

image

其实面试这一块早在第一个说的25大面试专题就全都有的。以上提及的这些全部的面试+学习的各种笔记资料,我这差不多来回搞了三个多月,收集整理真的很不容易,其中还有很多自己的一些知识总结。正是因为很麻烦,所以对以上这些学习复习资料感兴趣,

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

public enum Operation {

PLUS {

public double apply(double x, double y) {

return x + y;

}

},

MINUS {

public double apply(double x, double y) {

return x - y;

}

},

TIMES {

public double apply(double x, double y) {

return x * y;

}

},

DIVIDE {

public double apply(double x, double y) {

return x / y;

}

};

public abstract double apply(double x, double y);

}

特定于常量的方法实现可以与特定于常量的数据结合使用。toString 方法返回与操作关联的符号:

public enum Operation {

PLUS(“+”) {

public double apply(double x, double y) {

return x + y;

}

},

MINUS(“-”) {

public double apply(double x, double y) {

return x - y;

}

},

TIMES(“*”) {

public double apply(double x, double y) {

return x * y;

}

},

DIVIDE(“/”) {

public double apply(double x, double y) {

return x / y;

}

};

private final String symbol;

Operation(String symbol) {

this.symbol = symbol;

}

@Override

public String toString() {

return symbol;

}

public abstract double apply(double x, double y);

}

上述代码可以很容易地打印算术表达式:

public static void main(String[] args) {

double x = Double.parseDouble(args[0]);

double y = Double.parseDouble(args[1]);

for (Operation op : Operation.values())

System.out.printf(“%f %s %f = %f%n”, x, op, y, op.apply(x, y));

}

2.000000 + 4.000000 = 6.000000

2.000000 - 4.000000 = -2.000000

2.000000 * 4.000000 = 8.000000

2.000000 / 4.000000 = 0.500000


特定于常量的方法实现有一个美中不足的地方,它们使得在枚举常量中共享代码变得更加困难。例如,考虑用一个枚举代表工资包中的工作天数,根据给定的某工人的基本工资(每小时)和当天工作的时间计算当天工人的工资。

enum PayrollDay {

MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY;

private static final int MINS_PER_SHIFT = 8 * 60;

int pay(int minutesWorked, int payRate) {

int basePay = minutesWorked * payRate; // 计算基本工资

// 计算加班工资

int overtimePay;

switch (this) {

case SATURDAY:

case SUNDAY: // Weekend

overtimePay = basePay / 2;

break;

default: // Weekday

overtimePay = minutesWorked <= MINS_PER_SHIFT ?

0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;

}

return basePay + overtimePay;

}

}

假设你给枚举添加了一个元素,可能是一个特殊的值来表示一个假期,但忘记在switch 语句中添加一个相应的case 条件。该程序仍然会编译,但付费方法会将节假日的工资算成工作日的工资,原因是走了上面default里的逻辑。

我们真正想要的是每次添加枚举常量时,就自动选择加班费策略:再定义一个嵌套枚举类PayType,并将PayType实例传递给·PayrollDay·枚举的构造方法里。

public enum PayrollDay {

MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,

SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);

private final PayType payType;

PayrollDay(PayType payType) {

this.payType = payType;

}

PayrollDay() {

this(PayType.WEEKDAY);

} // Default

int pay(int minutesWorked, int payRate) {

return payType.pay(minutesWorked, payRate);

}

// The strategy enum type

private enum PayType {

WEEKDAY {

int overtimePay(int minsWorked, int payRate) {

return minsWorked <= MINS_PER_SHIFT ? 0 :

(minsWorked - MINS_PER_SHIFT) * payRate / 2;

}

},

WEEKEND {

int overtimePay(int minsWorked, int payRate) {

return minsWorked * payRate / 2;

}

};

abstract int overtimePay(int mins, int payRate);

private static final int MINS_PER_SHIFT = 8 * 60;

int pay(int minsWorked, int payRate) {

int basePay = minsWorked * payRate;

return basePay + overtimePay(minsWorked, payRate);

}

}

}

枚举的switch语句适合于给外部的枚举类型增加和常量值对应行为。

假设希望 Operation 枚举有一个实例方法来返回每个相反的操作。

public static Operation inverse(Operation op) {

switch(op) {

case PLUS: return Operation.MINUS;

case MINUS: return Operation.PLUS;

case TIMES: return Operation.DIVIDE;

case DIVIDE: return Operation.TIMES;

default: throw new AssertionError("Unknown op: " + op);

}

}

35 用实际属性代替序数


许多枚举天生就与某个 int 值关联。所以枚举都有一个ordinal方法,返回每个枚举常量在类型中的数字位置。

public enum Ensemble {

SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET;

public int numberOfMusicians() {

return ordinal() + 1;

}

}

这段代码维护起来就是一场噩梦,如果上面常量的顺序变了,所有用到numberOfMusicians的地方都会返回一个不同的值。所以永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例属性中:

public enum Ensemble {

SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),

SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),

NONET(9), DECTET(10), TRIPLE_QUARTET(12);

private final int numberOfMusicians;

Ensemble(int size) {

this.numberOfMusicians = size;

}

public int numberOfMusicians() {

return numberOfMusicians;

}

}

实际上,ordinal的目的是用于基于枚举的通用数据结构,如 EnumSetEnumMap,除了在编写这种数据结构时可以用,其他时候都不要用。

36 使用 EnumSet 替代位属性


EnumSet 类集位属性的简介和性能优势及枚举类型的所有优点于一身。

如果枚举类型的元素主要用于集合中,一般就使用int枚举模式,例如将2的不同倍数设置成常量:

public class Text {

public static final int STYLE_BOLD = 1 << 0; // 1

public static final int STYLE_ITALIC = 1 << 1; // 2

public static final int STYLE_UNDERLINE = 1 << 2; // 4

public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8

// Parameter is bitwise OR of zero or more STYLE_ constants

public void applyStyles(int styles) { … }

}

这种int型表示方式允许使用按位或(or)运算,将几个常量合并到一个称为位属性(bit field)的集合中:

text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

除了使用 OR 运算符之外,位属性表示法还可以支持求交集、异或求加法等很方便的操作,但是它本质上还是int型枚举常量,所以继承了int枚举常量的所有缺点:

1. 打印位属性时,翻译位域要难得多

就好比让你直接用二进制编程,酸爽程度可想而知

2. 没有一个好的方法可以遍历所有位属性表示的元素

3. 写API之前就要确定好需要多少位,选择相应的类型(int、long)


java.util包提供了 EnumSet 类来有效地表示从单个枚举类型中提取的值集合。

在内部具体的实现上,每个EnumSet表示为位矢量。

public class Text {

public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }

// Any Set could be passed in, but EnumSet is clearly best

public void applyStyles(Set

}

EnumSet提供了丰富的静态工厂,可以轻松创建集合:

text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));

因为枚举类型要用在集合中,所以不推荐用位域来表示它,推荐使用EnumSet

37 使用EnumMap 替代序数索引


不要用ordinal来索引数组,要使用EnumMap

有时可能会用到ordinal方法来作为数组的索引,例如创建一个植物类:

public class Plant {

enum LifeCycle {ANNUAL, PERENNIAL, BIENNIAL}

final String name;

final LifeCycle lifeCycle;

Plant(String name, LifeCycle lifeCycle) {

this.name = name;

this.lifeCycle = lifeCycle;

}

@Override

public String toString() {

return name;

}

}

如果有一个植物数组plantsByLifeCycle,按照不同的生长周期(一年生,多年生,或双年生)将花园里的植物garden放入不同的位置:

Set[] plantsByLifeCycle = (Set[]) new Set[Plant.LifeCycle.values().length];

for (int i = 0; i < plantsByLifeCycle.length; i++)

plantsByLifeCycle[i] = new HashSet<>();

for (Plant p : garden)

plantsByLifeCycle[p.lifeCycle.ordinal()].add§;

// Print the results

for (int i = 0; i < plantsByLifeCycle.length; i++) {

System.out.printf(“%s: %s%n”, Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);

}

上面的代码有很多问题:

  1. 数组不能与泛型兼容,编译会报错

  2. 数组不知道它的索引代表什么,比如添加额外的注释来标注这些索引的输出

  3. 当使用以索引顺序为索引的数组时,必须人工保证使用的int值不出差错


使用java.util.EnumMap可以更好地达到上面想要的效果,并规避风险:

Map<Plant.LifeCycle, Set> plantsByLifeCycle = new EnumMap<>(Plant.LifeCycle.class);

for (Plant.LifeCycle lc : Plant.LifeCycle.values())

plantsByLifeCycle.put(lc, new HashSet<>());

for (Plant p : garden)

plantsByLifeCycle.get(p.lifeCycle).add§;

System.out.println(plantsByLifeCycle);

EnumMap 与序数索引数组的速度相当,其原因正是 EnumMap 内部使用了这样一个数组,但它对程序员的隐藏了这个实现细节

注意EnumMap 构造方法传入一个Class对象:这是一个有限定的类型令牌(bounded type token),它提供运行时的泛型类型信息

通过使用stream可以进一步缩短程序:

System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle)));

这个代码的问题在于它选择了自己的 Map 实现,实际上并不是EnumMap,使用Collectors.groupingBy的三个参数形式的方法,它允许调用者使用mapFactory参数指定map的实现:

System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle,

() -> new EnumMap<>(LifeCycle.class), toSet())));


还有按照序数进行二维数组索引的情况,例如下面代码表示了物理学中物质的状态变化过程(如液体到固体是凝固,液体到气体是沸腾):

public enum Phase {

SOLID, LIQUID, GAS;

public enum Transition {

MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;

// Rows indexed by from-ordinal, cols by to-ordinal

private static final Transition[][] TRANSITIONS = {

{null, MELT, SUBLIME},

{FREEZE, null, BOIL},

{DEPOSIT, CONDENSE, null}

};

// Returns the phase transition from one phase to another

public static Transition from(Phase from, Phase to) {

return TRANSITIONS[from.ordinal()][to.ordinal()];

}

}

}

程序看起来很优雅,但是和上面示例一样,有几个缺陷:

  1. 编译器不知道序数和数组索引之间的关系

  2. 如果在转换表中出错或者在修改Phase 或Phase.Transition枚举类型时忘记更新它,就会报错

按照上面的思路,可以用EnumMap修改,使用Map(起始状态, Map(目标状态, 过渡方式))这种存储格式:

public enum Phase {

SOLID, LIQUID, GAS;

public enum Transition {

MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),

BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),

SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);

private final Phase from;

private final Phase to;

Transition(Phase from, Phase to) {

this.from = from;

this.to = to;

}

// Initialize the phase transition map

private static final Map<Phase, Map<Phase, Transition>> m = Stream.of(values())

.collect(groupingBy(t -> t.from, () -> new EnumMap<>(Phase.class),

toMap(t -> t.to, t -> t,

(x, y) -> y, () -> new EnumMap<>(Phase.class))));

public static Transition from(Phase from, Phase to) {

return m.get(from).get(to);

}

}

}

映射的类型是Map<Phase, Map<Phase, Transition>>,例如Map<液体, Map<固体, 凝固过程>>

第一个集合PhaseTransition进行分组,第二个集合使用从PhaseTransition的映射创建一个EnumMap。第二个收集器((x, y)-> y)) 中的merge方法未使用,只有在我们因为我们想要获得一个EnumMap而定义映射工厂时才需要用到

现在假设想为系统添加一个新阶段:plasma(离子)或电离气体。只有两个Transition与之关联:电离化(ionization),将气体转为离子;和去离子;消电离化(deionization)将离子体转为气体。

要更新层序时,只需将PLASMA添加到Phase中,并将IONIZE(GAS, PLASMA)DEIONIZE(PLASMA, GAS)添加到Transition中:

public enum Phase {

SOLID, LIQUID, GAS, PLASMA;

public enum Transition {

MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),

BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),

SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID),

IONIZE(GAS, PLASMA), DEIONIZE(PLASMA, GAS);

… // Remainder unchanged

}

}

很方便,也很安全!

38 用接口实现可继承的枚举


对于“可继承”的枚举来说,操作码(operation codes, opcodes)是一个经典的例子,操作码是枚举类型,其元素表示某些机器上的操作,例如34中的 Operation 类型,它表示简单计算器上的功能。

有时需要让API来继承枚举,从而实现扩展功能的目的,但这种语法是不支持的,但可以通过接口的形式来巧妙地实现:

public interface Operation {

double apply(double x, double y);

}

public enum BasicOperation implements Operation {

PLUS(“+”) {

public double apply(double x, double y) { return x + y; }

},

MINUS(“-”) {

public double apply(double x, double y) { return x - y; }

},

TIMES(“*”) {

public double apply(double x, double y) { return x * y; }

},

DIVIDE(“/”) {

public double apply(double x, double y) { return x / y; }

};

private final String symbol;

BasicOperation(String symbol) {

this.symbol = symbol;

}

@Override public String toString() {

return symbol;

}

}

虽然枚举类BasicOperation无法被继承,但接口Operation是可以被继承的。假设想要扩展前面的操作类型,包括指数运算和余数运算。要做的就是编写一个实现 Operation 接口的枚举类型:

public enum ExtendedOperation implements Operation {

EXP(“^”) {

public double apply(double x, double y) {

return Math.pow(x, y);

}

},

REMAINDER(“%”) {

public double apply(double x, double y) {

return x % y;

}

};

private final String symbol;

ExtendedOperation(String symbol) {

this.symbol = symbol;

}

@Override public String toString() {

return symbol;

}

}

测试程序如下:

public static void main(String[] args) {

double x = Double.parseDouble(args[0]);

double y = Double.parseDouble(args[1]);

test(ExtendedOperation.class, x, y);

}

private static <T extends Enum & Operation> void test(

Class opEnumType, double x, double y) {

for (Operation op : opEnumType.getEnumConstants())

System.out.printf(“%f %s %f = %f%n”, x, op, y, op.apply(x, y));

}

注意ExtendedOperation类的字面文字(ExtendedOperation.class)被传递给了test方法,<T extends Enum<T> & Operation> Class<T> 确保了Class 对象既是枚举又是Operation 的子类,这正是遍历元素和执行每个元素相关联的操作时所需要的

另一种方法是传入一个Collection<? extends Operation>,和上面的差异在于这是一个限定通配符类型(⻅第31条),而不是传递了一个class 对象:

public static void main(String[] args) {

double x = Double.parseDouble(args[0]);

double y = Double.parseDouble(args[1]);

test(Arrays.asList(ExtendedOperation.values()), x, y);

}

private static void test(Collection<? extends Operation> opSet, double x, double y) {

for (Operation op : opSet)

System.out.printf(“%f %s %f = %f%n”, x, op, y, op.apply(x, y));

}

上面两个main函数的执行结果全都如下:

4.000000 ^ 2.000000 = 16.000000

4.000000 % 2.000000 = 0.000000

使用接口来实现可扩展枚举的一个小缺点是,无法实现一个枚举类继承另一个枚举类。

Java 平台也借鉴了这种方式来实现java.nio.file.LinkOption枚举类型,它同时实现了 CopyOptionOpenOption 接口。

39 注解优先于命名模式


所有的程序员都应该使用Java平台所提供的预定义的注解类型,既然有了注解,就不要使用命名模式了

使用命名模式(naming patterns)来指示某些程序元素需要通过工具或框架进行特殊处理。例如在 JUnit 4 发布之前,要求程序员必须以test作为测试方法名的开头,这有几个严重缺点:

1. 英语拼写错误会导致运行失败,且没有任何提示

2. 无法实现将测试用于某个程序元素上

意思是,假如将TestSafetyMechanisms类,希望JUnit 3 能够自动测试其所有方法,实际上并不会测试

3. 没有提供将参数值与程序元素相关联的好的方法

例如无法测试只有抛出异常才算成功的代码


注解很好地解决了所有这些问题,JUnit从版本4开始,使用注解来指定简单的测试:

import java.lang.annotation.*;

/**

  • Indicates that the annotated method is a test method.

  • Use only on parameterless static methods.

*/

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Test {

}

Test 本身还被 Retention 和 Target 这两个元注解进行标记,@Retention(RetentionPolicy.RUNTIME)元注解表明Test这个注解在运行时有效,@Target.get(ElementType.METHOD)元注解表明Test 注解只能修饰方法。

在Test 的注释说:“Use only on parameterless static method”(只用于无参的静态方法),实际上编译器并未强制限定这一条。

public class Sample {

@Test

public static void ml() {

} // Test should pass

public static void m2() {

}

@Test

public static void m3() { // Test should fail

throw new RuntimeException (“Boom”);

}

public static void m4() {

}

@Test

public void m5() {

} // INVALID USE: nonstatic method

public static void m6() {

}

@Test

public static void m7() { // Test should fail

throw new RuntimeException(“Crash”);

}

public static void m8() {

}

}

Sample 类有七个静态方法,其中四个被标注为Test。其中两个,m3 和m7 引发异常,两个m1 和m5 不引发异常。但是没有引发异常的注解方法之一是实例方法,因此它不是注释的有效用法。总之,Sample 包含四个测试:一个会通过,两个会失败,一个是无效的。未使用Test 注解标注的四种方法将被测试工具忽略。

注解永远不会改变被注解代码的语义,但是它可以通过工具进行特殊处理:

import java.lang.reflect.*;

import org.junit.Test;

public class RunTests {

public static void main(String[] args) throws Exception {

int tests = 0;

int passed = 0;

Class<?> testClass = Class.forName(args[0]);

for (Method m : testClass.getDeclaredMethods()) {

if (m.isAnnotationPresent(Test.class)) { //

tests++;

try {

m.invoke(null);

passed++;

} catch (InvocationTargetException wrappedExc) {

Throwable exc = wrappedExc.getCause();

System.out.println(m + " failed: " + exc);

} catch (Exception exc) {

System.out.println("Invalid @Test: " + m);

}

}

}

System.out.printf(“Passed: %d, Failed: %d%n”, passed, tests - passed);

}

}

上述代码通过调用 Method.invoke 来反射地运行所有类标记有Test 注解的方法,运行结果:

在这里插入图片描述

现在添加对 仅在特定异常时 才算成功的测试的支持,添加一个新的注解类型:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface ExceptionTest {

Class<? extends Throwable> value();

}

最后如何让自己一步步成为技术专家

说句实话,如果一个打工人不想提升自己,那便没有工作的意义,毕竟大家也没有到养老的年龄。

当你的技术在一步步贴近阿里p7水平的时候,毫无疑问你的薪资肯定会涨,同时你能学到更多更深的技术,交结到更厉害的大牛。

推荐一份Java架构之路必备的学习笔记,内容相当全面!!!

成年人的世界没有容易二字,前段时间刷抖音看到一个程序员连着加班两星期到半夜2点的视频。在这个行业若想要拿高薪除了提高硬实力别无他法。

你知道吗?现在有的应届生实习薪资都已经赶超开发5年的程序员了,实习薪资26K,30K,你没有紧迫感吗?做了这么多年还不如一个应届生,真的非常尴尬!

进了这个行业就不要把没时间学习当借口,这个行业就是要不断学习,不然就只能被裁员。所以,抓紧时间投资自己,多学点技术,眼前困难,往后轻松!

【关注】+【转发】+【点赞】支持我!创作不易!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

lass.getDeclaredMethods()) {

if (m.isAnnotationPresent(Test.class)) { //

tests++;

try {

m.invoke(null);

passed++;

} catch (InvocationTargetException wrappedExc) {

Throwable exc = wrappedExc.getCause();

System.out.println(m + " failed: " + exc);

} catch (Exception exc) {

System.out.println("Invalid @Test: " + m);

}

}

}

System.out.printf(“Passed: %d, Failed: %d%n”, passed, tests - passed);

}

}

上述代码通过调用 Method.invoke 来反射地运行所有类标记有Test 注解的方法,运行结果:

在这里插入图片描述

现在添加对 仅在特定异常时 才算成功的测试的支持,添加一个新的注解类型:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface ExceptionTest {

Class<? extends Throwable> value();

}

最后如何让自己一步步成为技术专家

说句实话,如果一个打工人不想提升自己,那便没有工作的意义,毕竟大家也没有到养老的年龄。

当你的技术在一步步贴近阿里p7水平的时候,毫无疑问你的薪资肯定会涨,同时你能学到更多更深的技术,交结到更厉害的大牛。

推荐一份Java架构之路必备的学习笔记,内容相当全面!!!

[外链图片转存中…(img-RSkSXkWl-1714887930638)]

成年人的世界没有容易二字,前段时间刷抖音看到一个程序员连着加班两星期到半夜2点的视频。在这个行业若想要拿高薪除了提高硬实力别无他法。

你知道吗?现在有的应届生实习薪资都已经赶超开发5年的程序员了,实习薪资26K,30K,你没有紧迫感吗?做了这么多年还不如一个应届生,真的非常尴尬!

进了这个行业就不要把没时间学习当借口,这个行业就是要不断学习,不然就只能被裁员。所以,抓紧时间投资自己,多学点技术,眼前困难,往后轻松!

【关注】+【转发】+【点赞】支持我!创作不易!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值