2024年最全阿里面试题系列:工作5年,第一次这么清醒的理解final关键字?,十年开发经验Java架构师

Spring全套教学资料

Spring是Java程序员的《葵花宝典》,其中提供的各种大招,能简化我们的开发,大大提升开发效率!目前99%的公司使用了Spring,大家可以去各大招聘网站看一下,Spring算是必备技能,所以一定要掌握。

目录:

部分内容:

Spring源码

  • 第一部分 Spring 概述
  • 第二部分 核心思想
  • 第三部分 手写实现 IoC 和 AOP(自定义Spring框架)
  • 第四部分 Spring IOC 高级应用
    基础特性
    高级特性
  • 第五部分 Spring IOC源码深度剖析
    设计优雅
    设计模式
    注意:原则、方法和技巧
  • 第六部分 Spring AOP 应用
    声明事务控制
  • 第七部分 Spring AOP源码深度剖析
    必要的笔记、必要的图、通俗易懂的语言化解知识难点

脚手框架:SpringBoot技术

它的目标是简化Spring应用和服务的创建、开发与部署,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用的微服务功能,可以和spring cloud联合部署。

Spring Boot的核心思想是约定大于配置,应用只需要很少的配置即可,简化了应用开发模式。

  • SpringBoot入门
  • 配置文件
  • 日志
  • Web开发
  • Docker
  • SpringBoot与数据访问
  • 启动配置原理
  • 自定义starter

微服务架构:Spring Cloud Alibaba

同 Spring Cloud 一样,Spring Cloud Alibaba 也是一套微服务解决方案,包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

  • 微服务架构介绍
  • Spring Cloud Alibaba介绍
  • 微服务环境搭建
  • 服务治理
  • 服务容错
  • 服务网关
  • 链路追踪
  • ZipKin集成及数据持久化
  • 消息驱动
  • 短信服务
  • Nacos Confifig—服务配置
  • Seata—分布式事务
  • Dubbo—rpc通信

Spring MVC

目录:

部分内容:

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

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

}

}

上述代码在运行时会提示如下错误

java: 变量 name 未在默认构造器中初始化

修改成下面的方式即可。

public class TCCClass {

private final String name=“name”;

}

  1. 在构造方法中赋值

public class TCCClass {

private final String name;

public TCCClass(String name){

this.name=name;

}

}

能够在构造方法中赋值的原因是:对于一个普通成员属性赋值时,必须要先通过构造方法实例化该对象。因此作为该属性唯一的访问入口,JVM允许在构造方法中给final修饰的属性赋值。这个过程并没有违反final的原则。当然如果被修饰final关键字的属性已经初始化了值,是无法再使用构造方法重新赋值的。

反射破坏final规则


基于上述final关键字的基本使用描述,可以知道final修饰的属性是不可变的。

但是,通过反射机制,可以破坏final的规则,代码如下

public class TCCClass {

private final String name=“name”;

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

TCCClass tcc=new TCCClass();

System.out.println(tcc.name);

Field name=tcc.getClass().getDeclaredField(“name”);

name.setAccessible(true);

name.set(tcc,“mic”);

System.out.println(name.get(tcc));

}

}

打印结果如下:

name

mic

知识点扩展

上述代码理论上来说应该是下面这种写法,因为通过反射修改tcc实例对象中的name属性后,应该通过实例对象直接打印出name的结果。

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

TCCClass tcc=new TCCClass();

System.out.println(tcc.name);

Field name=tcc.getClass().getDeclaredField(“name”);

name.setAccessible(true);

name.set(tcc,“mic”);

System.out.println(tcc.name); //here

}

但是实际输出结果后,发现tcc.name打印的结果没有变化?

原因是:JVM在编译时期做的深度优化机制, 就把final类型的String进行了优化, 在编译时期就会把String处理成常量,导致打印结果不会发生变化。

为了避免这种深度优化带来的影响,我们还可以把上述代码修改成下面这种形式

public class TCCClass {

private final String name=(null == null ? "name" : "");
public static void main(String[] args) throws Exception {
    TCCClass tcc=new TCCClass();
    System.out.println(tcc.name);
    Field name=tcc.getClass().getDeclaredField("name");
    name.setAccessible(true);
    name.set(tcc,"mic");
    System.out.println(tcc.name);
}

}

打印结果如下:

name

mic

反射无法修改被final和static同时修饰的变量

把上面的代码修改如下。

public class TCCClass {

private static final String name=(null == null ? “name” : “”);

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

TCCClass tcc=new TCCClass();

System.out.println(tcc.name);

Field name=tcc.getClass().getDeclaredField(“name”);

name.setAccessible(true);

name.set(tcc,“mic”);

System.out.println(tcc.name);

}

}

执行结果,执行之后会报出如下异常, 因为反射无法修改同时被static final修饰的变量:

Exception in thread “main” java.lang.IllegalAccessException: Can not set static final java.lang.String field org.example.cl03.TCCClass.name to java.lang.String

at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)

at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)

at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)

at java.lang.reflect.Field.set(Field.java:764)

at org.example.cl03.TCCClass.main(TCCClass.java:13)

那么被final和static同时修饰的属性,能否被修改呢?答案是可以的!

修改代码如下:

public class TCCClass {

private static final String name=(null == null ? “name” : “”);

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

TCCClass tcc=new TCCClass();

System.out.println(tcc.name);

Field name=tcc.getClass().getDeclaredField(“name”);

name.setAccessible(true);

Field modifiers = name.getClass().getDeclaredField(“modifiers”);

modifiers.setAccessible(true);

modifiers.setInt(name, name.getModifiers() & ~Modifier.FINAL);

name.set(tcc,“mic”);

modifiers.setInt(name, name.getModifiers() & ~Modifier.FINAL);

System.out.println(tcc.name);

}

}

具体思路是,把被修饰了final关键字的name属性,通过反射的方式去掉final关键字,代码实现

Field modifiers = name.getClass().getDeclaredField(“modifiers”);

modifiers.setAccessible(true);

modifiers.setInt(name, name.getModifiers() & ~Modifier.FINAL);

接着通过反射修改name属性,修改成功后,再使用下面代码把final关键字加回来

modifiers.setInt(name, name.getModifiers() & ~Modifier.FINAL);

为什么局部内部类和匿名内部类只能访问final变量


在了解这个问题之前,我们先来看下面这段代码

public static void main(String[] args) {

}

public void test(final int b) {

final int a = 10;

new Thread(){

public void run() {

System.out.println(a);

System.out.println(b);

};

}.start();

}

}

这段代码被编译后,会生成两个文件: FinalExample.class和FinalExample$1.class(匿名内部类)

图片

通过反编译来看一下FinalExample$1.class这个类

class FinalExample$1 extends Thread {

FinalExample$1(FinalExample this$0, int var2, int var3) {

this.this$0 = this$0;

this.val$a = var2;

this.val$b = var3;

}

public void run() {

System.out.println(this.val$a);

System.out.println(this.val$b);

}

}

我们看到匿名内部类FinalExample$1的构造器含有三个参数,一个是指向外部类对象的引用,另外两个是int型变量,很显然,这里是将变量test方法中的形参b,以及常量a以参数的形式传进来,对匿名内部类中的拷贝(变量ab的拷贝)进行赋值初始化。

也就是说,在run方法中访问的变量ab,是局部变量ab的一个副本,为什么这么设计?

test方法中,有可能test方法执行结束且ab的声明周期也结束了,但是Thread这个匿名内部类可能还未执行完,那么在Thread中的run方法中继续使用局部变量ab就会有问题。但是又要实现这样的效果,怎么办呢?所以Java采用了复制的手段来解决这个问题。

但是这样一来,还是存在一个问题,就是test方法中的成员变量与匿名内部类Thread中的成员变量的副本出现数据不一致怎么办?

这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量ab限制为final变量,不允许对变量ab进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

另外,如果我们这么写也是允许的,jvm会隐式给ab增加final关键字。

public void test(int b) {

int a = 10;

new Thread(){

public void run() {

System.out.println(a);

System.out.println(b);

};

}.start();

}

final防止指令重排


最后

总而言之,面试官问来问去,问的那些Redis知识点也就这么多吧,复习的不够到位,知识点掌握不够熟练,所以面试才会卡壳。将这些Redis面试知识解析以及我整理的一些学习笔记分享出来给大家参考学习

还有更多学习笔记面试资料也分享如下:

都是“Redis惹的祸”,害我差点挂在美团三面,真是“虚惊一场”

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

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

式给ab增加final关键字。

public void test(int b) {

int a = 10;

new Thread(){

public void run() {

System.out.println(a);

System.out.println(b);

};

}.start();

}

final防止指令重排


最后

总而言之,面试官问来问去,问的那些Redis知识点也就这么多吧,复习的不够到位,知识点掌握不够熟练,所以面试才会卡壳。将这些Redis面试知识解析以及我整理的一些学习笔记分享出来给大家参考学习

还有更多学习笔记面试资料也分享如下:

[外链图片转存中…(img-fLz8S8QH-1715123688623)]

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

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

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值