Java static与final详细讲解
1、static修饰变量:
2、static修饰方法:
目录
可修饰部分
一、static
3、static和final一块用
final一块用
4、static静态代码块
5.静态导包
6、总结
二、final
1、final修饰类:
2、final修饰方法:
3、 final修饰变量:
4、对于final类型成员变量,
5、final修饰方法参数
可修饰部分
static:成员变量、方法、代码块(静态代码块)、内部类(静态内部类)
final: 类、成员变量、方法、局部变量
一、static
当设计某个class时,其实就是在描述其外观长相以及行为举措。除非以new 来产生对象,否则并不存在任何实质对象。产生对象之际,存储空间才会分配出来,其函数才可供外界使用。
但是有两种情况上述方式无法解决。
第一种:你希望不论产生了多少对象或是不存在任何对象的情形下,那些特定的数据的存储空间都只有一份。
第二种:你希望某个函数不要和class object绑在一起,通过static 关键字可以处理这两种情况。
简言之,static的主要作用是:方便在没有创建对象的情况下来进行调用(方法/变量)。
按照是否静态的对类成员变量进行分类可分两种:
一种是被static修饰的变量,叫静态变量或类变量;
另一种是没有被static修饰的变量,叫实例变量。
两者的区别是:
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
static是静态修饰关键字,可以修饰变量和程序块以及类方法:
当定义一个static的变量的时候jvm会将将其分配在内存堆上,所有程序对它的引用都会指向这一个地址而不会重新分配内存;
当修饰一个程序块的时候(也就是直接将代码写在static{...}中)时候,虚拟机就会优先加载静态块中代码,这主要用于系统初始化;
当修饰一个类方法时候你就可以直接通过类来调用而不需要新建对象。
1、static修饰变量:
无论一个类生成了多少个对象,所有这个对象共用唯一一份静态的成员变量;一个对象对该静态成员变量进行了修改,其他对象的该静态成员变量的值也会随之发生变化。如果一个成员变量是static的,那么我们可以通过类名.成员变量名的方式来使用它(推荐使用这种方式)。
static变量也称作静态变量,静态变量和非静态变量的区别是:
静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。
2、static修饰方法:
static修饰的方法叫做静态方法。对于静态方法来说,可以使用类名.方法名的方式来访问。
static方法可以直接通过类名调用,任何的实例也都可以调用,因此static方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。
static方法只能访问static的变量和方法,因为非static的变量和方法是需要创建一个对象才能访问的,而static的变量/方法不需要创建任何对象。
********
static的数据或方法,属于整个类的而不是属于某个对象的,是不会和类的任何对象实例联系到一起。所以子类和父类之间可以存在同名的static方法名,这里不涉及重载。所以不能把任何方法体内的变量声明为static,例如:
fun() {
static int i=0; //非法。
}
其实理解static是只有一个存储地方,而使用时直接使用,不需要创建对象,就能明白以上的注意事项。
另外,一般的类是没有static的,只有内部类可以加上static来表示嵌套类。
public class StaticTest {public static void main(String[] args) {MyStatic.output();}
}
class MyStatic{public static void output(){System.out.println("output");}
}
内部类
内部类的定义
将一个类定义在另一个给类里面或者方法里面,这样的类就被称为内部类。
内部类可以分为四种:成员内部类、局部内部类、匿名内部类、静态内部类,下面我们逐一介绍这四种内部类。
成员内部类
他定义在另一个类中。一般定义格式如下
class C{class D{}
}
因为类C相对与类D在外面,我们且称类C为外部类。
成员内部类可以无条件访问外部类的属性和方法,但是外部类想要访问内部类属性或方法时,必须要创建一个内部类对象,然后通过该对象访问内部类的属性或方法
成员内部类无条件访问外部类的属性和方法
class C{private String name = "外部类";public void run(){System.out.println("外部类奔跑");}class D{public void say(){System.out.println(name);run();}}
}
外部类访问内部类属性和方法
class C{private String name = "外部类";public void run(){System.out.println("外部类奔跑");}/*使用内部类的属性和方法*/public void eat(){D d = new D();System.out.println(d.value);d.say();}class D{private String value = "DDD";public void say(){System.out.println(name);run();}}
}
外部类属性或方法隐藏
如果成员内部类的属性或者方法与外部类的同名,将导致外部类的这些属性与方法在内部类被隐藏,也可按照该格式调用,外部类.this.属性/方法。
class C{private String name = "外部类";public void run(){System.out.println("外部类奔跑");}/*使用内部类的属性和方法*/public void eat(){D d = new D();System.out.println(d.value);d.say();}class D{private String value = "DDD";private String name = "内部类";public void say(){System.out.println(C.this.name);System.out.println(name);run();}}
}
创建内部类对象
目录
可修饰部分
一、static
1、static修饰变量:
2、static修饰方法:
内部类
内部类的定义
成员内部类
成员内部类无条件访问外部类的属性和方法
外部类访问内部类属性和方法
外部类属性或方法隐藏
创建内部类对象
局部内部类
匿名内部类
静态内部类
内部类的好处
总结
3、static和final一块用
4、static静态代码块
5.静态导包
6、总结
二、final
1、final修饰类:
2、final修饰方法:
3、 final修饰变量:
4、对于final类型成员变量,
5、final修饰方法参数
显然成员内部类是寄生于外部类,创建内部类对象就必须先创造外部类对象。之后创建内部类有两种方式。
public class Test10 {public static void main(String[] args) {/*方式1创建成员内部类对象*/C c = new C();C.D d = c.new D();/*方式2创建成员内部类对象*/C.D d1 = c.getClassD();}
}
class C{private String name = "外部类";public void run(){System.out.println("外部类奔跑");}/*创建一个返回D对象的方法*/public D getClassD(){return new D();}/*使用内部类的属性和方法*/public void eat(){D d = new D();System.out.println(d.value);d.say();}class D{private String value = "DDD";private String name = "内部类";public void say(){System.out.println(C.this.name);System.out.println(name);run();}}
}
成员内部类的访问权限
成员内部类前可加上四种访问修饰符。
private:仅外部类可访问。
protected:同包下或继承类可访问。
default:同包下可访问。
public:所有类可访问。
局部内部类
局部内部类存在于方法中。
他和成员内部类的区别在于局部内部类的访问权限仅限于方法或作用域内。
class K{public void say(){class J{}}
}
注意事项:局部内部类就像局部变量一样,前面不能访问修饰符以及static修饰符。
匿名内部类
下面我们先通过一段代码初步了解一下匿名内部类。
public class Test13 {public static void main(String[] args) {driveCar(new Car(){@Overridepublic void drive() {System.out.println("驾驶着BMW汽车");}});}public static void driveCar(Car car){car.drive();}
}interface Car {void drive();
}
分析以上代码知道静态方法driveCar需要一个Car对象,我们通过实现接口创建一个匿名类对象传递过去。事实上还可以通过继承类来创建一个匿名内部类对象。
注意事项:匿名内部类没有构造方法。也是唯一没有构造方法的内部类。匿名内部类和局部内部类只能访问外部类的final变量。
静态内部类
静态内部类和成员内部类相比多了一个static修饰符。它与类的静态成员变量一般,是不依赖于外部类的。同时静态内部类也有它的特殊性。因为外部类加载时只会加载静态域,所以静态内部类不能使用外部类的非静态变量与方法。
同时可以知道成员内部类里面是不能含静态属性或方法的。
class U {static class I {}
}
内部类的好处
- 完善了Java多继承机制,由于每一个内部类都可以独立的继承接口或类,所以无论外部类是否继承或实现了某个类或接口,对于内部类没有影响。
- 方便写事件驱动程序。
总结
public class Test15 {public static void main(String[] args) {//初始化bean1Test15.Bean1 bean1 = new Test15().new Bean1();bean1.i++;//初始化bean2Test15.Bean2 bean2 = new Test15.Bean2();bean2.j++;//初始化3Bean bean = new Bean();Bean.Bean3 bean3 = bean.new Bean3();bean3.k++;}class Bean1 {public int i = 0;}static class Bean2 {public int j = 0;}
}
class Bean {class Bean3 {public int k = 0;}}
静态内部类对象的创建一般是外部类.内部类 类名 = new 外部类.内部类();
成员内部类对象的创建一般是外部类.内部类 类名 = 外部类对象名.new 内部类();
3、静态方法只能继承,不能重写(Override)。
输出结果:父类
3、static和final一块用
static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!
对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
对于方法,表示不可覆盖,并且可以通过类名直接访问。
特别要注意一个问题:
对于被static和final修饰过的实例常量,实例本身不能再改变了,但对于一些容器类型(比如,ArrayList、HashMap)的实例变量,不可以改变容器变量本身,但可以修改容器中存放的对象,这一点在编程中用到很多。
最后我们需要注意的一点是,同时使用static和final修饰的成员在内存中只占据一段不能改变的存储空间。
看个例子:
import java.io.*;
import java.util.ArrayList;
class test1
{public static void main (String[] args) throws java.lang.Exception{new TestStaticFinal().test(); }
}class TestStaticFinal { private static final String strStaticFinalVar = "aaa"; private static String strStaticVar = null; private final String strFinalVar= null; private static final int intStaticFinalVar = 0; private static final Integer integerStaticFinalVar = new Integer(8); private static final ArrayList<String> alStaticFinalVar = new ArrayList<String>(); void test() { System.out.println("-------------值处理前----------"); System.out.println("strStaticFinalVar=" + strStaticFinalVar + ""); System.out.println("strStaticVar=" + strStaticVar + ""); System.out.println("strFinalVar=" + strFinalVar + ""); System.out.println("intStaticFinalVar=" + intStaticFinalVar + ""); System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + ""); System.out.println("alStaticFinalVar=" + alStaticFinalVar + ""); //strStaticFinalVar="哈哈哈哈";//错误,final表示终态,不可以改变变量本身. strStaticVar = "哈哈哈哈"; //正确,static表示类变量,值可以改变. //strFinalVar="呵呵呵呵"; //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。 //intStaticFinalVar=2; //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。 //integerStaticFinalVar=new Integer(8); //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。 alStaticFinalVar.add("aaa"); //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。 alStaticFinalVar.add("bbb"); //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。 System.out.println("-------------值处理后----------"); System.out.println("strStaticFinalVar=" + strStaticFinalVar + ""); System.out.println("strStaticVar=" + strStaticVar + ""); System.out.println("strFinalVar=" + strFinalVar + ""); System.out.println("intStaticFinalVar=" + intStaticFinalVar + ""); System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + ""); System.out.println("alStaticFinalVar=" + alStaticFinalVar + ""); }
}
运行结果
-------------值处理前----------
strStaticFinalVar=aaa
strStaticVar=null
strFinalVar=null
intStaticFinalVar=0
integerStaticFinalVar=8
alStaticFinalVar=[]
-------------值处理后----------
strStaticFinalVar=aaa
strStaticVar=哈哈哈哈
strFinalVar=null
intStaticFinalVar=0
integerStaticFinalVar=8
alStaticFinalVar=[aaa, bbb]
package com.cy.oauth.testData;import java.util.*;public class userList {//演示数据private final static List<Map<String, Object>> userList = new ArrayList<>();static {Map<String, Object> user = new HashMap<>();user.put("name", "123");user.put("uscc", "1123456");user.put("phone", "13316512676");user.put("password", "123");user.put("smscode", "1");user.put("username", "12312");user.put("idcard", "1231231");user.put("mail", "45612");user.put("userid", "098d4821833b442d83ff4478ed1ee0f9");user.put("status", "0");userList.add(user);}public List<Map<String, Object>> getUserList() {return userList;}public void setUserList(Map<String, Object> userMap) {this.userList.add(userMap);}}
特别注意,静态块要放在声明的变量下面
4、static静态代码块
static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
为什么说静态代码块可以优化效率?看下面两段代码:
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
上面这段代码每次调用isBornBoomer这个方法的时候都会生成startDate 和 endDate 这两个对象,造成空间的浪费。
换成下面这种写法,稍微的会优化一点效率。
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
static静态代码块。静态代码块的作用也是完成一些初始化工作。首先执行静态块,然后执行构造方法。静态代码块在类被加载的时候执行,而构造方法是在生成对象的时候执行;要调用某个类来生成对象,首先需要将类加载到Java虚拟机上(JVM),然后由JVM加载这个类来生成对象。
类的静态代码块只会执行一次,是在类被加载的时候执行的,因为每个类只会加载一个,所以静态代码块也只会被执行一次;而构造方法则不然,每次生成一个对象的时候都会调用类的构造方法,所以new一次就会调用构造方法一次。
如果继承体系既有构造方法,又有静态代码块,那么首先执行最顶层的类的静态代码块,一直执行到最底层类的静态代码块,然后再去执行最顶层类的构造方法,一直执行到最底层的构造方法,注意:静态代码块只会执行一次。
public class StaticTest5 {
public static void main(String[] args) {
new R();
new R();
}
}
class P{
//static静态代码块
static {
System.out.println("static block");
}
public P(){
System.out.println("P constructor");
}
}
class Q extends P{
static {
System.out.println("static block");
}
public Q(){
System.out.println("Q constructor");
}
}
class R extends Q{
static {
System.out.println("static block");
}
public R(){
System.out.println("R constructor");
}
}
输出结果:
static block
static block
static block
P constructor
Q constructor
R constructor
P constructor
Q constructor
R constructor
在说明static关键字的第三个用法时,我们有必要重新梳理一下一个对象的初始化过程。以下面的代码为例:
package com.dotgua.study;class Book{public Book(String msg) {System.out.println(msg);}
}public class Person {Book book1 = new Book("book1成员变量初始化");static Book book2 = new Book("static成员book2成员变量初始化");public Person(String msg) {System.out.println(msg);}Book book3 = new Book("book3成员变量初始化");static Book book4 = new Book("static成员book4成员变量初始化");public static void main(String[] args) {Person p1 = new Person("p1初始化");}/**Output* static成员book2成员变量初始化* static成员book4成员变量初始化* book1成员变量初始化* book3成员变量初始化* p1初始化*///~
}
上面的例子中,Person类中组合了四个Book成员变量,两个是普通成员,两个是static修饰的类成员。我们可以看到,当我们new一个Person对象时,static修饰的成员变量首先被初始化,随后是普通成员,最后调用Person类的构造方法完成初始化。也就是说,在创建对象时,static修饰的成员会首先被初始化,而且我们还可以看到,如果有多个static修饰的成员,那么会按照他们的先后位置进行初始化。
实际上,static修饰的成员的初始化可以更早的进行,请看下面的例子:
class Book{public Book(String msg) {System.out.println(msg);}
}public class Person {Book book1 = new Book("book1成员变量初始化");static Book book2 = new Book("static成员book2成员变量初始化");public Person(String msg) {System.out.println(msg);}Book book3 = new Book("book3成员变量初始化");static Book book4 = new Book("static成员book4成员变量初始化");public static void funStatic() {System.out.println("static修饰的funStatic方法");}public static void main(String[] args) {Person.funStatic();System.out.println("****************");Person p1 = new Person("p1初始化");}/**Output* static成员book2成员变量初始化* static成员book4成员变量初始化* static修饰的funStatic方法* **************** book1成员变量初始化* book3成员变量初始化* p1初始化*///~
}
在上面的例子中我们可以发现两个有意思的地方,第一个是当我们没有创建对象,而是通过类去调用类方法时,尽管该方法没有使用到任何的类成员,类成员还是在方法调用之前就初始化了,这说明,当我们第一次去使用一个类时,就会触发该类的成员初始化。第二个是当我们使用了类方法,完成类的成员的初始化后,再new该类的对象时,static修饰的类成员没有再次初始化,这说明,static修饰的类成员,在程序运行过程中,只需要初始化一次即可,不会进行多次的初始化。
回顾了对象的初始化以后,我们再来看static的第三个作用就非常简单了,那就是当我们初始化static修饰的成员时,可以将他们统一放在一个以static开始,用花括号包裹起来的块状语句中:
class Book{public Book(String msg) {System.out.println(msg);}
}public class Person {Book book1 = new Book("book1成员变量初始化");static Book book2;static {book2 = new Book("static成员book2成员变量初始化");book4 = new Book("static成员book4成员变量初始化");}public Person(String msg) {System.out.println(msg);}Book book3 = new Book("book3成员变量初始化");static Book book4;public static void funStatic() {System.out.println("static修饰的funStatic方法");}public static void main(String[] args) {Person.funStatic();System.out.println("****************");Person p1 = new Person("p1初始化");}/**Output* static成员book2成员变量初始化* static成员book4成员变量初始化* static修饰的funStatic方法* **************** book1成员变量初始化* book3成员变量初始化* p1初始化*///~
}
我们将上一个例子稍微做了一下修改,可以看到,结果没有二致。
5.静态导包
相比于上面的三种用途,第四种用途可能了解的人就比较少了,但是实际上它很简单,而且在调用类方法时会更方便。以上面的“PrintHelper”的例子为例,做一下稍微的变化,即可使用静态导包带给我们的方便:
/* PrintHelper.java文件 */
package com.dotgua.study;public class PrintHelper {public static void print(Object o){System.out.println(o);}
}
/* App.java文件 */import static com.dotgua.study.PrintHelper.*;public class App
{public static void main( String[] args ){print("Hello World!");}/**Output* Hello World!*///~
}
上面的代码来自于两个java文件,其中的PrintHelper很简单,包含了一个用于打印的static方法。而在App.java文件中,我们首先将PrintHelper类导入,这里在导入时,我们使用了static关键字,而且在引入类的最后还加上了“.*”,它的作用就是将PrintHelper类中的所有类方法直接导入。不同于非static导入,采用static导入包后,在不与当前类的方法名冲突的情况下,无需使用“类名.方法名”的方法去调用类方法了,直接可以采用"方法名"去调用类方法,就好像是该类自己的方法一样使用即可。
6、总结
static是java中非常重要的一个关键字,而且它的用法也很丰富,主要有四种用法:
- 用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享;
- 用来修饰成员方法,将其变为类方法,可以直接使用“类名.方法名”的方式调用,常用于工具类;
- 静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键;
- 静态导包用法,将类的方法直接导入到当前类中,从而直接使用“方法名”即可调用类方法,更加方便。
不能在静态方法中访问非静态的成员变量:可以在静态方法中访问静态的成员变量。可以在非静态方法中访问静态的成员变量。
总结:静态的只能访问静态的;非静态的访问一切。
不能在静态方法中使用this、super关键字。
静态内部类:
static final 和 final static
两者没有区别,但是习惯于将static写在前面。
那么static final 和 单纯的final有什么区别呢?
final在一个对象类唯一,static final在多个对象中都唯一;
二、final
根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。
你可能出于两种理解而需要阻止改变:设计或效率。
final类不能被继承,没有子类,final类中的方法默认是final的。
final方法不能被子类的方法覆盖,但可以被继承。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
final不能用于修饰构造方法。
用final修饰的成员变量表示常量,值一旦给定就无法改变!
final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。
从下面的例子中可以看出,一旦给final变量初值后,值就不能再改变了。
另外,final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。
在Java中声明属性、方法和类时,可使用关键字final来修饰。
final变量即为常量,只能赋值一次; final方法不能被子类重写; final类不能被继承。
final关键字:final可以修饰属性、方法、类、成员变量、局部变量
1、final修饰类:
当一个类被final所修饰时,表示该类是一个终态类,即不能被继承。
2、final修饰方法:
当一个方法被final所修饰的时,表示该方法是一个终态方法,即不能被重写(Override)。
如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
使用final方法的原因有二:
第一、把方法锁定,防止任何继承类修改它的意义和实现。
第二、高效。编译器在遇到调用final方法时候会转入内嵌inline机制,大大提高执行效率。
注意,类中所有的private方法都被隐含是final的。由于无法取用private方法,则也无法重载之。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
分析:
覆盖何时发生:
1,子类中出现与父类完全一致的方法
2. 子类可以通过向上转型为父类,并调用父类中的那个方法
若父类中某个方法被声明为final或者private,那么这个方法对子类来说是不可见的,就算在子类中创建了与父类一模一样的方法,这也是一个新的方法,而不是从父类中覆盖的方法。
3、 final修饰变量:
当一个属性被final所修饰时,表示该属性不能被改写。
- 基础类型 用fianl修饰后就变成了一个常量,不可以重新赋值。
- 包装类型 用final修饰后该变量指向的地址不变,但是该地址的的变量完全可以改变。
当final修饰一个原生数据类型时,表示该原生数据类型的值不能发生变化(比如说不能从10变为20);如果final修饰一个引用类型时,表示该引用类型不能再指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。
在编写程序时,我们经常需要说明一个数据是不可变的,我们成为常量。在java中,用final关键字修饰的变量,只能进行一次赋值操作,并且在生存期内不可以改变它的值。更重要的是,final会告诉编译器,这个数据是不会修改的,那么编译器就可能会在编译时期就对该数据进行替换甚至执行计算,这样可以对我们的程序起到一点优化。不过在针对基本类型和引用类型时,final关键字的效果存在细微差别。我们来看下面的例子:
1 class Value {2 int v;3 public Value(int v) {4 this.v = v;5 }6 }7 8 public class FinalTest {9
10 final int f1 = 1;
11 final int f2;
12 public FinalTest() {
13 f2 = 2;
14 }
15
16 public static void main(String[] args) {
17 final int value1 = 1;
18 // value1 = 4;
19 final double value2;
20 value2 = 2.0;
21 final Value value3 = new Value(1);
22 value3.v = 4;
23 }
24 }
上面的例子中,我们先来看一下main方法中的几个final修饰的数据,在给value1赋初始值之后,我们无法再对value1的值进行修改,final关键字起到了常量的作用。从value2我们可以看到,final修饰的变量可以不在声明时赋值,即可以先声明,后赋值。value3时一个引用变量,这里我们可以看到final修饰引用变量时,只是限定了引用变量的引用不可改变,即不能将value3再次引用另一个Value对象,但是引用的对象的值是可以改变的,从内存模型中我们看的更加清晰:
上图中,final修饰的值用粗线条的边框表示它的值是不可改变的,我们知道引用变量的值实际上是它所引用的对象的地址,也就是说该地址的值是不可改变的,从而说明了为什么引用变量不可以改变引用对象。而实际引用的对象实际上是不受final关键字的影响的,所以它的值是可以改变的。
另一方面,我们看到了用final修饰成员变量时的细微差别,因为final修饰的数据的值是不可改变的,所以我们必须确保在使用前就已经对成员变量赋值了。因此对于final修饰的成员变量,我们有且只有两个地方可以给它赋值,一个是声明该成员时赋值,另一个是在构造方法中赋值,在这两个地方我们必须给它们赋初始值。
最后我们需要注意的一点是,同时使用static和final修饰的成员在内存中只占据一段不能改变的存储空间。
原生数据类型案例:
图中的错误是无法为最终变量age分配值
引用类型案例:
图中错误是无法为最终变量address分配值
该引用所指向的对象的内容是可以发生变化的
4、对于final类型成员变量,
其初始化可以在两个地方,有两种赋值方式:
a) 在声明final类型的成员变量时就赋初值
b) 在声明final类型的成员变量时不赋初值,但在类的所有构造方法中都为其赋上初值。
这两个地方只能选其一,要么在定义时给值,要么在构造函数中给值,不能同时既在定义时给了值,又在构造函数中给另外的值。
一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变。
当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
另外方法中的内部类在用到方法中的参变量时,此参变也必须声明为final才可使用。
public class FinalTest {final int a;public FinalTest() {a = 0;}public FinalTest(int a) {this.a = a;}
}
5、final修饰方法参数
前面我们可以看到,如果变量是我们自己创建的,那么使用final修饰表示我们只会给它赋值一次且不会改变变量的值。那么如果变量是作为参数传入的,我们怎么保证它的值不会改变呢?这就用到了final的第二种用法,即在我们编写方法时,可以在参数前面添加final关键字,它表示在整个方法中,我们不会(实际上是不能)改变参数的值:
主要分为两种情况:第一,用final修饰基本数据类型;第二,用final修饰引用数据类型。
第一种情况,修饰基本数据类型,这时参数的值在方法体内是不能被修改的,即不能被重新赋值。否则编译就不通过。
第二种情况,修饰引用类型。这时参数变量所引用的对象是不能被改变的。但是对于引用数据类型,如果修改其属性的话是完全可以的。
所以,final这个关键字,想用的话就用基本数据类型,还是很有作用的
public class FinalTest {/* ... */public void finalFunc(final int i, final Value value) {// i = 5; 不能改变i的值// v = new Value(); 不能改变v的值value.v = 5; // 可以改变引用对象的值}
}
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
分析:
参数被声明为final,若是基本参数,那它就是一个常量,不能被修改;若是一个引用变量,那么它就不能被修改指向另一个对象,但可以修改该引用所指对象的内容。
Java static与final详细讲解相关推荐
- Java字节码的详细讲解-刘宇
Java字节码的详细讲解-刘宇 一.字节码的整体结构 二.字节码范围解析 2.1.魔数 2.2.版本信息 2.3.常量池(constant pool) 2.4.描述符规则 2.5.访问标志(Acces ...
- JAVA MemCache 史无前例的详细讲解 看完包精通MEMCACHE
JAVA MemCache 史无前例的详细讲解!看完包精通MEMCACHE! 分类: 高并发_性能2012-08-22 09:15 5051人阅读 评论(0) 收藏 举报 javastringobje ...
- Java: static,final,代码块 的详解
Java: static,final,代码块 的详解 每博一文案 山本文绪说过这样一句话:哪些决定放弃了的事,就请放弃得干干净净.哪些决定再也不见面的人,就真 的不要再见面了,不要再做背叛自己的事,如 ...
- java回忆录—输入输出流详细讲解(入门经典)
今天我们开始进入学习 java 中比较让人头疼的事, 那就是 I/O 流.多线程.网络编程.这里对 I/O 流的一个详细讲解.希望对大家有点用吧.(不看后悔哦) 一.什么是IO Java中I/O操作主 ...
- java utl_java utl包详细讲解.doc
java utl包详细讲解 java.util包 2006-05-30 10:06:02 来源: JR 网友评论0?条 论坛 ?? 本章介绍Java的实用工具类库java.util包.在这个包中,Ja ...
- Java类与对象详细讲解(上)
目录 一.类与对象的概念 1.1 什么是面向对象 1.2 什么是对象 1.3 什么是类 二.类与对象的定义 2.1 类的创建 2.2 对象的创建 2.3 练习创建两个dog对象,定义其属性 ...
- Java I/O体系详细讲解
注意: 此文侧重讲解Java的IO主要体系,至于具体API功能会在后面的文章详细讲解. I/O以及I/O流的定义: I/O实际上是Input/Output,也就是输入/输出,输入指的是从存储设备中读取 ...
- Java:static和final关键字的区别与比较
文章目录 前言 一.final关键字 二.static关键字 总结 前言 本文记录笔者在复习Java基础期间学习的static和final关键字 一.final关键字 final关键字具有如下特点 f ...
- JAVA继承和多态详细讲解
面向对象编程的重要知识:继承和多态.通过类的继承机制,可以使用已有的类为基础派生出新类,无需编写重复的程序代码,很好地实现程序代码复用.多态是面向对象编程中继封装和继承之后的另一大特征,它具体是指同一 ...
最新文章
- DPU(Data Processing Unit)数据处理器
- python高阶函数
- 腾讯TEG团队打造轻量级数据可视化工具——小马BI【强烈推荐大数据行业学习】
- 山东自考c语言程序设计停考了吗,2018山东自考停考专业有哪些
- 展望Spring野心-Spring资源定位
- 解决从PDF复制文本到word的时候排版问题小技巧
- 盘点国内最具实力的双足仿人机器人研发团队有哪些?
- 专业显卡深度学习_MacOS+AMD-eGPU打造深度学习环境 | 第2期
- matlab 开启并行,Matlab并行(持续更新)
- java中使用jxls导出excel,excel单元格换行,多sheet页导出
- 第5章 域内横向移动分析及防御
- Linux进程间通信——管道通信详解
- android耗电怎么解决方法,Android手机媒体进程耗电严重怎么办
- openCV教程01
- 电商领域知识图谱:常识抽取,表示与应用
- Vue3+vite配置postcss-pxtorem报错[plugin:vite:css] Failed to load PostCss config
- 电脑右键没有“发送到”选项
- Inwook,Kong
- 华为鸿蒙公测尝鲜,华为鸿蒙尝鲜测试正式开始,分三种形式
- win10设置网络打印机