Java基于final修饰数据过程解析

这篇文章主要介绍了Java基于final修饰数据过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

final是Java中的一个重要关键字,它可以修饰数据、方法和类,本篇将从final修饰的数据角度对final做出总结。

final修饰的数据代表着:永远不变。意思是,一旦你用final修饰一块数据,你之后就只能看看它,你想修改它,没门。
我们不希望改变的数据有下面两种情况:

永不改变的编译时常量。

//编译时知道其值
private final int valueOne = 9;

在运行时(不是编译时)被初始化的值,且之后不希望它改变。

//在编译时不能知道其值
private final int i4 = rand.nextInt(20);

设置成常量有啥好处呢?

很简单,让编译器觉得简单,就是最大的好处。比如把PI设置成final,且给定值为3.14,编译器自然会觉得这个东西不会再被修改了,是足够权威的。那么,编译器就会在运行之前(编译时)就把这3.14代入所有的PI中计算,这样在真正运行的时候,速度方面当然会快一点。

有初始值的final域

即声明为final且当场就给定初始值的域。

private final int valueOne = 9;

final+基本数据类型

final修饰的基本数据类型变量存储的数值永恒不变。

/*基本类型变量*/
//带有编译时数值的final基本类型
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
public static final int VALUE_THREE = 39;
//!false:fd1.valueOne++;
//!false:fd1.VALUE_TWO++;
//!false:fd1.VALUE_THREE++;

康康上面醒目的三句false语句,很好地印证了我们之前说的:数值不让改!!!

需要注意的是,按照惯例,下面是定义常量的典型方式:

//典型常量的定义方式
public static final int VALUE_THREE = 39;
  • public修饰符使其可被用于包之外。
  • static使数据只有一份。
  • final表示其无法被更改
  • 名称全为大写英文字母,以下划线隔开。

final+引用数据类型

我们之前说过,基本类型存数值,引用类型存地址值。那么既然final+基本数据类型不让改数值,聪明的我们稍微一联想就明白,final+引用数据类型就是不让你改变量存储实际对象的地址值啦。(也就是不能再让它指向新的对象,很专一)

private Value v1 = new Value(1);
private final Value v2 = new Value(22);
private static final Value V_3 = new Value(333);
//引用变量并不是常量,存储地址可以改变
fd1.v1 = new Value(10);

//v2是引用变量,final修饰之后表示地址不能改变,但是实际对象的值是可以改变的
fd1.v2.i++;
//!false:fd1.v2 = new Value(3);

//V_3与v2类似,是静态和非静态的区别,下面会说明
fd1.V_3.i++;
//!false:fd1.V_3 = new Value(10);
}

通过例子,确实也证明上面所说,一个以final修饰的引用数据类型变量,无法再指向一个新的对象,因为它所存储的地址值已经无法被更改,但是并不影响它指向的实际对象。就拿一个比较典型的引用类型来举例,我们知道数组就是一种典型的引用类型,数组的引用变量存储的是数组再堆中的地址,堆中存放的就是数组每个索引的数值。

/*引用变量之数组*/
private final int[] a = {1,2,3,4,5,6};

引用变量a被指定为final,所以它里面的地址值不能再改,也就无法再让它指向一个新的数组。

//!false:fd1.a = new int[]{2,3,4,5,6,7};
for (int i = 0; i < fd1.a.length; i++) {
  fd1.a[i]++;

但是,它指向的数组里的每个元素却可以改动,因为数组中的元素并没有任何的限定。

final与static final

private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
System.out.println(fd1);//fd1: i4 = 15,INT_518

FinalData fd2 = new FinalData("fd2");
System.out.println(fd2);//fd2: i4 = 13,INT_518
FinalData fd3 = new FinalData("fd3");
System.out.println(fd3);//fd3: i4 = 1,INT_5 = 18

上面示例分别创建了三个不同的对象,对其final 和final static 进行测试。

  • 需要明确的是,两者都以final修饰,都不能被改变。
  • 三个对象的i4值,没有用static修饰,不相同且不能改变。
  • 而INT_5的值因为被static修饰,在类加载时已经被初始化,不随对象改变而改变。

空白final域

即声明为final却没有给定初始值的域。

private final String id;//空白final

如果只有上面的这句,编译器会报错,因为它没有初始化。

Variable 'id' might not have been initialized

所以,若定义了空白final域,一定记得在构造器中给它赋值!(必须在域的定义处或者每个构造器中以表达式对final进行赋值,因为系统不会为final域默认初始化)

//在构造器中为空白final域赋初值
public FinalData(){
  id = "空白final默认id";
}
public FinalData(String id){
  this.id = id;
}

不要试图在初始化之前访问域,不然会报错。

final让域可以根据对象的不同而不同,增加灵活性的同时,又保留不被改变的特性。

final修饰的参数

基本数据类型的参数

类似地,就是传入的参数不让改,只让读,这一点很好理解。

public int finalParamTest(final int i){
  //!false:i++;
  //不让改,只让读
  return i+1;
}

但是,我又新增了许多测试,分别定义四种不同的参数传入该方法,发现传入param0和param1编译会报错。(非常疑惑,这部分书上没提,查了许多资料也没有理解清楚,希望大牛可以评论区指点迷津)

/*检测传入参数*/
int param0 = 5;
final int param1 = 10;
static final int PARAM_2 = 15;
static int param3 = 20;
//!false:System.out.println(fd1.finalParamTest(param0));
//!false:System.out.println(fd1.finalParamTest(param1));
//non-static field'param1' cannot be referenced from a static context
System.out.println(fd1.finalParamTest(PARAM_2));
System.out.println(fd1.finalParamTest(param3));
/*为什么形参列表里的参数用final修饰,但是用final修饰的param1无法传进去,
一定要static修饰?*/

引用数据类型的参数

public void finalReferenceTest(final FinalData fd){
  //!false:fd = new FinalData();
  //不能再指向新的对象,存储地址不准变
  fd.param0++;
}

还是类似,不可以让这个引用类型的参数再指向新的对象,但是可以改变其实际指向对象的值。

最后的最后,下面的代码是根据《Thinking in Java》中的示例,结合自己的思想,将各个板块融合而成的超级无敌测试代码,冲冲冲!

package com.my.pac16;

import java.util.Arrays;
import java.util.Random;

/**
 * @auther Summerday
 */

class Value{
  int i;//package access
  public Value(int i){
    this.i =i;
  }

}
/*final域在使用前必须被初始化:定义时,构造器中*/
public class FinalData {
  /*检测传入参数*/
  int param0 = 5;
  final int param1 = 10;
  static final int PARAM_2 = 15;
  static int param3 = 20;
  private static Random rand = new Random(47);
  private final String id;//空白final
  public FinalData(){
    id = "空白final默认id";
  }
  public FinalData(String id){
    this.id = id;
  }
  //带有编译时数值的final基本类型
  private final int valueOne = 9;
  private static final int VALUE_TWO = 99;
  //典型常量的定义方式
  public static final int VALUE_THREE = 39;
  //在编译是不能知道其值
  private final int i4 = rand.nextInt(20);
  static final int INT_5 = rand.nextInt(20);
  private Value v1 = new Value(1);
  private final Value v2 = new Value(22);
  private static final Value V_3 = new Value(333);

  private final int[] a = {1,2,3,4,5,6};
  @Override
  public String toString(){
    return id+": "+"i4 = "+i4+",INT_5 = "+INT_5;
  }
  public int finalParamTest(final int i){
    //!false:i++;
    //不让改,只让读
    return i+1;
  }
  public void finalReferenceTest(final FinalData fd){
    //!false:fd = new FinalData();
    //不能再指向新的对象,存储地址不准变
    fd.param0++;

  }

  public static void main(String[] args) {
    FinalData fd1 = new FinalData("fd1");
    /*基本类型变量*/
    //!false:fd1.valueOne++;
    //!false:fd1.VALUE_TWO++;
    //!false:fd1.VALUE_THREE++;
    /*引用变量*/
    fd1.v1 = new Value(10);
    fd1.v2.i++
    //!false:fd1.v2 = new Value(3);
    System.out.println("fd1.v2.i = [" + fd1.v2.i + "]");

    //!false:fd1.V_3 = new Value(10);
    fd1.V_3.i++;
    System.out.println("fd1.V_3.i = [" + fd1.V_3.i + "]");
    /*引用变量之数组*/
    System.out.println("before:fd1.a[] = " + Arrays.toString(fd1.a));

    /*数组引用变量a是final修饰,
    但是不代表它指向的数据值是final,
    而是a存储的地址值不能改变
     */
    //!false:fd1.a = new int[]{2,3,4,5,6,7};
    for (int i = 0; i < fd1.a.length; i++) {
      fd1.a[i]++;
    }
    System.out.println("after :fd1.a[] = " + Arrays.toString(fd1.a));
    /*final 与static final*/
    //下面示例分别创建了三个不同的对象,对其final 和final static 进行测试
    /*可以发现,三个对象的i4值是随机生成且不能改变的,且不相同,
    而INT_5的值不随对象改变而改变,因为被static修饰,在类加载时已经被初始化*/
    System.out.println(fd1);//fd1: i4 = 15,INT_518

    FinalData fd2 = new FinalData("fd2");
    System.out.println(fd2);//fd2: i4 = 13,INT_518
    FinalData fd3 = new FinalData("fd3");
    System.out.println(fd3);//fd3: i4 = 1,INT_5 = 18

    //!false:System.out.println(fd1.finalParamTest(param0));
    //!false:System.out.println(fd1.finalParamTest(param1));
    //non-static field'param1' cannot be referenced from a static context
    System.out.println(fd1.finalParamTest(PARAM_2));
    System.out.println(fd1.finalParamTest(param3));
    /*为什么形参列表里的参数用final修饰,但是用final修饰的param1无法传进去,
    一定要static修饰?*/

    System.out.println("fd1.param0 = "+fd1.param0);
    fd1.finalReferenceTest(fd1);
    System.out.println("fd1.param0 = "+fd1.param0);
  }

}

文章如有理解错误或叙述不到位,欢迎大家在评论区加以指正。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java异常处理之try...catch...finally详解

    异常处理机制已经成为判断一门编程语言是否成熟的标准之一,其对代码的健壮性有很大影响.一直以来异常处理使用不是很得心应手,今天对异常进行了较为深入的学习,这篇主要是对try-catch-finally的一个总结. 一.java继承体系 Java语言为异常处理提供了丰富的异常类,这些类之间有严格的继承关系.如图: 从图中我们可以看出,所有的类都是继承于Throwable这个父类,java将所有的非正常情况分为两种:Error(错误)和Exception(异常),Error错误一般是于虚拟机相关的问题

  • Java反射如何有效的修改final属性值详解

    前言 以前写过一篇 Java 反射修改 final 属性值,本文将在这里重新温习一下Java反射如何有效的修改final属性值,下面话不多说了,来一起看看详细的介绍: 假设有个类 class Person { public final String name = "Mike"; } 这里声明 name 为非静态的属性只是为了说明反射修改 final 属性无关乎静态不静态,静态只是表现在它是一个类属性,在一个类加载器空间只会有一份拷贝,仅此而已. 创建一个通用方法进行反射修改属性值 pu

  • 详谈Java中Object类中的方法以及finalize函数作用

    Object是所有类的父类,任何类都默认继承Object. 一.Object类中的方法 1.clone方法 保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常. 主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里讲参数改变,这是就需要在类中复写clone方法. 2.getClass方法 final方法,获得运行时类型. 3.toString方法 该方法

  • 浅谈Java之终止继承:Final类和Fianl方法

    前言 关键字Final不仅可以用来修饰变量,而且对类及其方法的继承也有很大的影响,本文将从类与方法两个方面介绍final关键字的功能. Final类 当关键字final用来修饰类时,其含义是该类不能再派生子类,换句话说,任何其他类都不能继承用final修饰的类,即使该类的访问权限为pubilc类型,也不能被继承:否则,将编译报错. 只有当需要确保类中的方法都不被重写时才应该建立最终(final)类,final关键字将会为这些方法提供安全,没有任何人能够重写final类中的方法,因为不能继承. 例

  • Java中final作用于变量、参数、方法及类该如何处理

    Java中方法用final修饰参数的作用 在方法参数前面加final关键字就是为了防止数据在方法体重被修改. 主要分为两种情况:第一,用final修饰基本数据类型:第二,用final修饰引用数据类型. 第一种情况,修饰基本数据类型,这时参数的值在方法体内是不能被修改的,即不能被重新赋值.否则编译就不通过. 第二种情况,修饰引用类型.这时参数变量所引用的对象是不能被改变的.但是对于引用数据类型,如果修改其属性的话是完全可以的. 所以,final这个关键字,想用的话就用基本数据类型,还是很有作用的.

  • Java中finally和return的关系实例解析

    本文研究的主要是Java中finally和return的关系,具体介绍和实例如下所示. finally 和 return 关系的总结 1.try块中有System.exit(0)这样的语句,由于它是终止Java虚拟机JVM的,连JVM都停止了,所有都结束了,当然finally语句也不会被执行到. 2.其它情况下,finally语句都必然会被执行.因此可以在这里执行一些资源的释放操作. (1)finally中的return会覆盖try/catch中的renturn. (2)在finally中写re

  • Java为什么匿名内部类参数引用需要用final进行修饰?

    事实上,除了匿名内部类参数,方法和作用域内的内部类内部使用的外部变量也必须是final 的.原因大致总结一下: 简单解释就是: 方法中的局部变量的生命周期很短,方法结束后变量就要被销毁,加上final是为了延长变量的生命周期. 进一步解释: 内部类通常都含有回调,引用那个匿名内部类的函数执行完了就没了,所以内部类中引用外面的局部变量需要是final的,这样在回调的时候才能找到那个变量,而如果是外围类的成员变量就不需要是final的,因为内部类本身都会含有一个外围了的引用(外围类.this),所以

  • Java中finalize()详解及用法

     Java中finalize()详解 在程序设计中,我们有时可能希望某些数据是不能够改变的,这个时候final就有用武之地了.final是Java的关键字,它所表示的是"这部分是无法修改的".不想被改变的原因有两个:效率.设计.使用到final的有三种情况:数据.方法.类.        一. final数据 有时候数据的恒定不变是很有用的,它能够减轻系统运行时的负担.对于这些恒定不变的数据我可以叫做"常量"."常量"主要应用与以下两个地方: 1

  • java 中JFinal getModel方法和数据库使用出现问题解决办法

    JFinal getModel方法(从页面表单中获取Model对象)+数据库存储问题 一.getmodel方法 1.在JConfig配置类中的数据库映射(存储到数据库时需要此配置) public void configPlugin(Plugins me) { C3p0Plugin cp = null; try { cp = new C3p0Plugin( "jdbc:mysql://localhost:3306/huaxuetang?useUnicode=true&characterEn

  • Java基于final修饰数据过程解析

    这篇文章主要介绍了Java基于final修饰数据过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 final是Java中的一个重要关键字,它可以修饰数据.方法和类,本篇将从final修饰的数据角度对final做出总结. final修饰的数据代表着:永远不变.意思是,一旦你用final修饰一块数据,你之后就只能看看它,你想修改它,没门. 我们不希望改变的数据有下面两种情况: 永不改变的编译时常量. //编译时知道其值 private fin

  • SpringBoot基于数据库实现定时任务过程解析

    这篇文章主要介绍了SpringBoot基于数据库实现定时任务过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在我们平时开发的项目中,定时任务基本属于必不可少的功能,那大家都是怎么做的呢?但我知道的大多都是静态定时任务实现. 基于注解来创建定时任务非常简单,只需几行代码便可完成.实现如下: @Configuration @EnableScheduling public class SimpleScheduleTask { //10秒钟执行

  • Java super关键字调用父类过程解析

    这篇文章主要介绍了Java super关键字调用父类过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 话不多说,直接上代码: package com.my.pac14; /** * @auther Summerday */ public class SuperTest { public static void main(String[] args) { SubClass sb = new SubClass(20); //创建子类的对象,调

  • Java自定义实现equals()方法过程解析

    这篇文章主要介绍了Java自定义实现equals()方法过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 以常见的自定义Date类型为例,没有经验的朋友可能会觉得直接比较年月日即可,从而写出以下的实现 public class MyDate implements Comparable<MyDate> { private final int year; private final int month; private final int

  • Java中final修饰的方法是否可以被重写示例详解

    这是一次阿里面试里被问到的题目,在我的印象中,final修饰的方法是不能被子类重写的.如果在子类中重写final修饰的方法,在编译阶段就会提示Error.但是回答的时候还是有点心虚的,因为final变量就可以用反射的方法进行修改,我也不太确定是否有类似的机制可以绕过编译器的限制.于是面试之后特地上网搜了下这个问题,这里简单记录一下. 首先说一下结论:没有办法能够做到重写一个final修饰的方法,但是有其他的方法可以接近在子类中重新实现final方法并在运行时的动态绑定的效果. 这里需要用到一个a

  • Java输出Hello World完美过程解析

    1. 你会不会输出"Hello World!"? 图1 图 2 当我们学习一门编程语言的时候,我们都会先学如何输出Hello World!

  • java中final修饰符的使用方法

    本文为大家分享了java中final修饰符的使用,供大家参考,具体内容如下 1.final修饰符的用法: final可以修饰变量,被final修饰的变量被赋初始值之后,不能对它重新赋值. final可以修饰方法,被final修饰的方法不能被重写. final可以修饰类,被final修饰的类不能够被继承. 上面的这些"语法口诀"对真正掌握final修饰符的用法依然是不够的. 2.final修饰的变量:被final修饰的实例变量必须显示指定初始值,而且只能在如下三个位置指定初始值: 定义f

  • java中final修饰符实例分析

    final修饰符: final修饰成员变量必须有程序员显示指定初始值. 类的Field:必须在静态初始化块中或声明该Field时指定初始值. 实例Field:必须在非静态初始块中,声明Field或者构造器中指定初始值. final局部变量:必须由程序员显示初始化. final修饰的基本变量和引用类型变量的区别? final修饰的基本变量:不能对基本变量重新赋值. final修饰的引用变量:只保证这个引用类型所引用的地址不会变,即 一直引用同一个对象,但这个对象完全可以发生改变. 复制代码 代码如

  • 通过Java读取xml文件内容过程解析

    这篇文章主要介绍了通过Java读取xml文件内容过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 需要下载jar包dom4j:https://dom4j.github.io/ package com.zyb.xml; import java.io.File; import java.util.Iterator; import org.dom4j.Attribute; import org.dom4j.Document; import or

  • java接口私有方法实现过程解析

    这篇文章主要介绍了java接口私有方法实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 问题描述: 我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题 但是这个共有方法不应该让实现类使用,应该是私有化的. 解决方案: 从java 9开始,接口当中允许定义私有方法. 1.普通私有方法,解决多个默认方法之间重复代码问题 格式: private 返回值类型方法名称(参数列表){ 方法体 } 2.静态私有方法,解决多个静态方法之

随机推荐