final
使用 final
关键字完成以下的操作:
1、使用final关键字声明一个常量
① 变量的值不可变,称为“常量”。
当final
修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化,也就是说,只能被赋值1次。例如:
final int a = 10;
//a = 11;错,a的值不可变
② 使用final修饰成员变量时,需要在定义变量的同时赋予一个初始值,否则会报错如下:
这是因为使用final
关键字修饰成员变量时,Java虚拟机不会对其进行初始化。因此使用final修饰成员变量时,需要在定义变量的同时赋予一个初始值
③ 如果final
修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。例如:
final Point a = new Point(3,4);
//a = null;错,a现在代表的是一个内存地址,是不可变的
//a.x = 30;可以的,不可变的是a,不能给a重新赋值
2、使用final关键字声明一个方法
该方法为最终方法,且只能被子类继承,但是不能被子类重写。
《Java编程思想》第四版
使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用 final 方法进行这些优化了。“
因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为 final
。另外类的 private
方法会隐式地被指定为 final
方法。
3、使用final关键字声明一个类
该类就转变为最终类,没有子类的类,final
修饰的类无法被继承。
4、在方法参数中使用final,在该方法内部不能修改参数的值
static
static 作用
static
关键字的作用:方便在没有创建对象的情况下来进行调用(方法/变量)。例如我们之前用过的开方运算:Math.sqrt();
,如果这个方法不是static
类型,我们就得这么用了:
Math m = new Math();
m.random;
也就是我们使用时还需要创建实例,再调用方法,很不方便。
对于 static
关键字下面进行几点说明:
1、静态属于类,不属于实例
例如我们修改士兵类,(用的是之前文章的例子,传送门:【达内课程】面向对象简介)我们增加一个士兵数量的字段 count:
public class Soldier {
//成员变量
int id;//唯一编号,默认0
int blood = 100;//血量,默认满血
static int count;//士兵数量
其中,id 和 blood 每创建1个 Soldier 实例,会分配一个内存地址,count 只有1个内存地址,是属于类的。如果创建了两个 Soldier 实例 a 和 b,并且count 值被修改,例如
a.count = 5;
b.count = 6;
那么 最终 count 的值是 6。
2、用类名调用静态变量和静态方法
被 static
修饰的变量属于类变量,可以通过类名.变量名
直接引用,而不需要new出一个类来
被 static
修饰的方法属于类方法,可以通过类名.方法名
直接引用,而不需要new出一个类来
非静态的变量和方法,也称为“实例变量”、“实例方法”
3、使用static关键字修饰一个类(内部类),后边会讲到
4、被static
修饰的成员优先加载到内存中,而且会后释放内存,即常驻内存
下面我们通过两个例子再加深下 static
的使用
举例1:static 使用
public class A {
public static int a = 10;
public int b = 20;
public static void main(String[] args) {
System.out.println(a);//正确
//因为main方法本身是一个static方法,很早就进入了内存
//a是static也进入了内存
//b不是,并没有进入内存
//System.out.println(b);//错误
//但以下写法是正确的
//执行了new以后,相应变量就已经加载到内存中了
A ss = new A();
System.out.println(ss.b);
}
}
举例2:士兵类 Soldier 修改
Soldier
public class Soldier {
//成员变量
int id;//唯一编号,默认0
int blood = 100;//血量,默认满血
//静态成员
//属于类,也称为“类变量”
static int count;//士兵数量
//构造方法
public Soldier() {
Log.d("士兵", "执行了士兵的构造方法");
//类名可以省略,所以也可写成 count++;
Soldier.count++;
}
//成员方法
public void go(TextView tv) {
if (blood == 0) {
tv.setText(id + "已阵亡,无法前进" + "\n" + tv.getText());
return;
}
tv.setText(id + "前进" + "\n" + tv.getText());
}
public void attack(TextView tv) {
if (blood == 0) {
tv.setText(id + "已阵亡,无法攻击" + "\n" + tv.getText());
return;
}
tv.setText(+id + "号士兵发起进攻" + "]\n" + tv.getText());
int b = new Random().nextInt(30);
if (blood < b) {
blood = b;
}
blood -= b;
if (blood == 0) {
tv.setText("[" + id + "号士兵阵亡" + "\n" + tv.getText());
} else {
tv.setText("[士兵" + id + "进攻完毕,血量" + blood + "\n" + tv.getText());
}
}
}
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="Create" />
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="Go" />
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="Attack" />
<Button
android:id="@+id/button4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="创建5个士兵实例" />
<Button
android:id="@+id/button5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="5个士兵进攻" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#222222"
android:textSize="20sp" />
</LinearLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
Button button1;
Button button2;
Button button3;
Button button4;
Button button5;
TextView textView;
Soldier soldier;
private Soldier[] a;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = findViewById(R.id.button1);
button2 = findViewById(R.id.button2);
button3 = findViewById(R.id.button3);
button4 = findViewById(R.id.button4);
button5 = findViewById(R.id.button5);
textView = findViewById(R.id.text);
}
public void doClick(View view) {
switch (view.getId()) {
case R.id.button1:
f1(textView);
break;
case R.id.button2:
f2();
break;
case R.id.button3:
f3();
break;
case R.id.button4:
f4();
break;
case R.id.button5:
f5();
break;
}
}
private void f1(TextView textView) {
soldier = new Soldier();
soldier.id = 9527;
//用s1找到士兵对象内存空间
//访问它的属性变量id
textView.setText("士兵9527已创建" + "\n");
}
public void f2() {
soldier.go(textView);
}
public void f3() {
soldier.attack(textView);
textView.setText("士兵" + soldier.id + "血量" + soldier.blood + "\n" + textView.getText());
}
public void f4() {
//新建5个长度的数组赋给a
a = new Soldier[5];
//遍历数组,将士兵实例放入
for (int i = 0; i < a.length; i++) {
a[i] = new Soldier();
a[i].id = i + 1;
}
textView.setText("已新创建5个士兵");
}
public void f5() {
for (int i = 0; i < a.length; i++) {
a[i].attack(textView);
}
textView.setText("士兵数量:" + Soldier.count + "\n" + textView.getText());
}
}
运行结果:
static 方法
声明为 static
的方法有以下几条限制:
1、它们不能以任何方式引用 this 或 super
static
方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有 this 的,因为它不依附于任何对象,既然都没有对象,就谈不上 this 了
2、静态方法仅能调用其他的 static 方法
由于静态方法不依赖于任何对象就可以进行访问,所以由于这个特性,在静态方法中不能
访问类的非静态成员变量
和非静态成员方法
,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用
但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。
static 变量
static
变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响
static 成员变量的初始化顺序按照定义的顺序进行初始化
static 代码块
static
关键字还有一个比较关键的作用就是用来形成静态代码块以优化程序性能。static
块可以置于类中的任何地方,类中可以有多个static
块。在类初次被加载的时候,会按照static
块的顺序来执行每个static
块,并且只会执行一次。
为什么说static
块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static
代码块中进行,例如我们写一个程序,判断一个人是不是 1946-1964 年之间出生的:
import java.sql.Date;
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;
}
}
sBornBoomer 是用来这个人是否是1946-1964年出生的,而每次isBornBoomer 被调用的时候,都会生成 startDate 和 birthDate 两个对象,造成了空间浪费,如果改成这样效率会更好:
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 final
static
修饰的属性强调它们只有一个(节省内存,让其属于类只有1个内存地址),final
修饰的属性表明是一个常数(创建后不能被修改)。
static final
修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。
static final
也可以修饰方法,表示该方法不能重写,可以在不 new 对象的情况下调用
static final
连用当然是二者的结合,可以简单的理解为全局常量。写法如下:
static final 数据类型 常量名 = 值;
注意,这种常量的定义只能写在类的定义语句块中,只有类的属性允许被定义为这种写法,方法体中的这种写法是不被允许的。同时常量名的命名规则是:全大写,单词之间加“_”,例如:
static final int MAX_VALUE = 100;
参考文章列表:
Java中的static关键字解析
java之面向对象详解