JAVA笔记-Static与Final详解笔记

时间:2021-05-09 18:24:55

static 成员变量

静态变量属于类的变量,所有类的实例共享的同一个变量。
直接使用类名读写这个变量
应用场景:
案例:
public class Demo04 {
public static void main(String[] args) {
//静态变量属于类的 变量
//直接使用类名读写这个变量
Hoo.z = 10;
int n = Hoo.z;
System.out.println(n);//10
//实例变量x,y是属于对象的变量
//创建对象以后才能使用实例变量
//每个对象都有自己的实例变量
Hoo h1=new Hoo();//(x,y)
Hoo h2=new Hoo();//(x,y)
//使用对象范围对象的实例变量
h1.x = 5;
h1.y = 6;
//测试:h1有自己的属性(5,6)
// h2有自己的属性(0,0)
// h1,h2 共享同一个 Hoo.z
System.out.println(h1);
System.out.println(h2);
//只有所有对象共享的同一个变量,
//才能使用静态修饰
}
}
class Hoo{
int x, y;
static int z;
public String toString(){
return x+”,”+y+”,”+z;
}
}
静态方法

静态方法是属于类的方法,一般使用类名直接调用静态方法。
静态方法与对象方法差别:
· 静态方法中没有隐含参数this(当前对象),不能调用当前对象的属性和方法。
· 非静态方法(对象的方法)在执行期间将调用方法的对象传递给this(当前对象)变量,可以访问当前对象的属性和方法。
使用原则:
如果一个方法的计算过程使用了当前对象的数据,就不能使用静态方法;
相反如果没有使用当前对象的数据,就可以声明为静态方法。
提示:程序中大多使用对象的方法(非静态方法),工具方法和工厂方法使用静态较多。
常用工具方法(静态方法):
Math.sqrt(num)
Math.sin()
Math.cos()
Arrays.toString()
Arrays.sort()
Arrays.fill()
静态方法:
对象方法:
案例代码:
public class Demo05 {
public static void main(String[] args) {
/*
* 静态方法
*/
//静态方法是属于类的方法,用于类名
//调用方法: 类名.方法()
Point p1 = new Point(3, 4);
Point p2 = new Point(6, 8);
//静态方法执行期间没有 this(当前对象)!!!
//静态方法不能访问当前对象的数据!!
double d=Point.distance(p1, p2);
System.out.println(d); //5.0
//在对象上调用非静态方法
//非静态方法有this!!!
//可以访问this(当前对象)中的数据
d = p1.distance(p2);
System.out.println(d); //5.0

    //结论:如果一个方法的计算过程使用
//了当前对象的数据,就不能使用静态
//方法,相反如果没有使用当前对象的
// 数据,就可以声明为静态方法。
}

}
class Point{
int x,y;
public Point(int x, int y) {
this.x=x; this.y=y;
}

/*
* Point类中的非静态方法
* 包含隐含参数this,引用调用方法的对象
*/
public double distance(
/* Point this */ Point p2){
//有一个隐含的引用this,引用调用方法
//当前对象,就可以使用这个对象的数据
int a = this.x-p2.x;
int b = this.y-p2.y;
int c2= a*a+b*b;
return Math.sqrt(c2);
}

/*
* 计算过程只用到参数的数据, 所以定义为
* 静态方法。
* 计算两个点p1 和p2 之间的距离(勾股定理)
*/
public static double distance(
Point p1, Point p2){
int a = p1.x-p2.x;
int b = p1.y-p2.y;
int c2 = a*a+b*b;
return Math.sqrt(c2);
}

}
静态代码块

静态代码块:
是类级别的代码块
在类的加载期间执行,只执行一次
用于处理只初始化一次的数据,如:读取图片数据,等。
案例:
public class Demo06 {
public static void main(String[] args) {
Noo n1 = new Noo();
Noo n2 = new Noo();
}
}
class Noo{
/*
* 类中可以声明“代码块”
* 创建对象时候执行代码块
*/
{
System.out.println(“HI”);
System.out.println(“代码块”);
}

/*
* 静态代码块
* - 是类级别的代码块
* - 在类的加载期间执行,只执行一次
* - 用于处理只初始化一次的数据,如:读取
* 图片数据。
*/
static {
System.out.println("静态代码块");
System.out.println("静态代码块2");
}

}
final 关键字

final变量

final 修饰变量
初始化以后不能“再”改变的变量。
final 修饰局部变量

可以初始化一次!!初始化以后不能“再”改变
案例:
int a = 5;
a = 8;//不是final的变量可以修改值
System.out.println(a);
final int b = 5;//初始化
//b = 8;//编译错误,不能再改变
final int c;
c = 5;//可以初始化一次!!
//c = 8;//编译错,但是不能再次改变值!!
System.out.println(c);
final 修饰的引用变量

可以初始化一次!!初始化以后不能“再”改变。
注意:这里不能改变的是引用变量与对象的引用关系,对象的成员可以任意改变!
案例:
final int[] ary = {3,4,5};
//ary = null;//编译错,但是不能再次改变值!!
ary[0]=5;//可以改变对象内容(数组元素)
System.out.println(ary[0]);//5
final 的方法参数

方法参数也是局部变量,可以使用final声明
方法的参数是在调用方法传递参数时候初始化的,初始化以后在方法内部不能再次改变。
案例:
test(4,5);//调用方法,初始化参数变量

public static void test(
final int a, int b){
b = 2;
//a = 5;//初始化以后不能再修改
System.out.println(a+”,”+b);
}
final的属性

1.final 修饰对象的属性,这个属性不能自动初始化,必须手动初始化。
可以直接赋值初始化
也可以使用构造器进行初始化
2.final属性初始化以后就不能再改变了。
3.final 修饰的属性,仍然是实例变量,还是每个都有一个的属性。
4.final修饰属性,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。见文章最后的代码说明。
注意:static 修饰的成员变量,是属于类的变量,是全体共享的同一个变量,与final属性不同。
案例:
//测试:
Roo r1 = new Roo(5);
r1.a = 8;//普通属性可以改变
//r1.b = 7;//编译错误,不能改变final属性
Roo r2 = new Roo(6);
//证明每个对象都有属性b
System.out.println(r1.b+”,”+r2.b);

//类
class Roo{
int a;
final int b;
public Roo(int b) {
this.b = b;
}
}
static final 声明的是常量

对于软件中的不变的常数可以声明为常量
Java 利用“编译擦除”方式替换常量为常数
编译以后原有的常量就被替换为常数
这样可以在运行期间避免到类中读取常量数值,提高程序的运行效率。
案例:
public class Demo09 {
public static void main(String[] args) {
/*
* 测试常量的“编译擦除”技术
* Java 的编译器在编译期间将常量
* K.PI 擦除替换为 3.1415926
* 在运行期间程序无需加载 K 类,
* 也能运行,并且输出 3.1415926
*/
System.out.println(K.PI);
K k = new K();
}
}
class K{
public static final double PI=3.1415926;
static {
System.out.println(“Load K”);
}
}
final方法

final关键字修饰的方法不可以被重写。
使一个方法不能被重写的意义在于:防止子类在定义新方法时造成的“不经意”重写。
注意:在实际开发中是很少使用final修饰方法。
final类

final关键字修饰的类不可以被继承。
final class Foo { }
class Goo extends Foo { }
JDK中的一些基础类库被定义为final的,例如:String、Math、Integer、Double 等等。
使一个类不能被继承的意义在于:可以保护类不被继承修改,可以控制滥用继承对系统造成的危害。
经典题目:
class MyString extends String(){}
答案:编译错误,因为String是final类不能被继承子类!
注意:在实际开发中是很少使用final修饰类

测试:Final修饰的属性会在类初始化时“编译擦除”,会替换成常数。

package demo;
class Price {
// static{
// System.out.println("Static块");
// }
//类成员INSTANCE是Price实例
static Price INSTANCE = new Price(2.8);//1
//默认价格initPrice
static double initPrice = 20;//2
//当前价格
double currentPrice;
//构造函数
public Price(double discount){
//super();


System.out.println("执行了构造函数 initPrice:"+ initPrice);
currentPrice = initPrice - discount;
}
}
public class PriceTest {
public static void main(String[] args) {
System.out.println(Price.INSTANCE.currentPrice);//3
Price p = new Price(2.8);//4
System.out.println(p.currentPrice);
}
}

输出结果如下:

执行了构造函数 initPrice:0.0
-2.8
执行了构造函数 initPrice:20.0
17.2

分析:
在标记3处第一次用到Price类时,程序开始对Price类进行初始化,初始化类分为两个阶段:
第一个阶段:系统为Price的变量分配内存空间并赋予默认值;即INSTANCE = null,initPrice = 0.0,currentPrice=0.0;
第二阶段:按初始化代码的排列顺序对类变量进行初始化;程序依次对他们进行赋值。先对INStANCE赋值(new Price(2,8)),d调用构造器(跳过了正常的初始化顺序),将2.8传给discount局部变量,正常情况下应是先static Price INSTANCE = new Price(2.8);,再执行static double initPrice = 20;,但因有new Price(2.8)调用了构造方法,所以跳过了正常初始化,即static double initPrice = 20;语句并没有执行,也就是说通过new跳过其他的初始化,直接执行了构造器初始化,而此时initPrice并没有初始化,即是默认值0.0,currentPrice=-2.8。接着对inintPrice赋值为20.所以执行标记4处时,两个static变量都已经初始化过了,不会再初始化,此时new Price(2.8)调用构造方法,initPrice = 20.0,所以currentPrice为17.2.

若将标记2处改为:final double initPrice = 20;//2,输出的结果则发生了变化:

执行了构造函数 initPrice:20.0
17.2
执行了构造函数 initPrice:20.0
17.2

这是因为:
**当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。
因为initPrice的值是可以确定的,所以编译器在编译期间将它看做是常量了**

类的final变量和普通变量有什么区别?

  当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。

  那么final变量和普通变量到底有何区别呢?下面请看一个例子:
public class Test {
public static void main(String[] args) {
String a = “hello2”;
final String b = “hello”;
String d = “hello”;
String c = b + 2;
String e = d + 2;
System.out.println((a == c));
System.out.println((a == e));
}
}
  大家可以先想一下这道题的输出结果。为什么第一个比较结果为true,而第二个比较结果为fasle。这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。这种和C语言中的宏替换有点像。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的 值。而对于变量d的访问却需要在运行时通过链接来进行。想必其中的区别大家应该明白了,不过要注意,只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:

public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = getHello();
String c = b + 2;
System.out.println((a == c));

}

public static String getHello() {
return "hello";
}
}

  这段代码的输出结果为false。