Java关键字篇
static关键字
static关键字修饰的变量和方法可通过类名直接访问
1.static 变量
静态变量只会被分配单一的存储空间,其所有实例化对象的此静态变量指向同一存储空间
public class Test {
static int i=47;
public static void main(String[] args){
Test st1 = new Test();
Test st2 = new Test();
i++;
System.out.println(st1.i+"--"+st2.i);
}
}
//输出:
//48--48
2.static 方法
- 静态方法中不能用this和super关键字
因为this表示调用这个函数的当前对象的引用,而静态方法加载调用时,对象还不一定存在的,super同理
- 不能直接访问所属类的实例变量和实例方法(就是静态方法不能访问不带static的成员变量和成员方法)
对于非静态的方法和变量,需要先创建类的实例对象后才可使用,而静态方法在使用前不用创建任何对象
- 因为static方法独立于任何实例,因此static方法必须被实现,不能是抽象的abstract.
抽象类中不一定包含抽象方法,但是包含抽象方法的类一定要被声明为抽象类
- Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。
3.static 代码块
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次
public class Test {
private static int a;
private int b;
static {
Test.a = 3;
System.out.println(a);
Test t = new Test();
t.f();
t.b = 1000;
System.out.println(t.b);
}
static {
Test.a = 4;
System.out.println(a);
}
public static void main(String[] args) {
// TODO 自动生成方法存根
}
static {
Test.a = 5;
System.out.println(a);
}
public void f() {
System.out.println("hhahhahah");
}
}
/*output:
3
hhahhahah
1000
4
5
*/
final关键字
在程序设计中,我们有时可能希望某些数据是不能够改变的,这个时候final就有用武之地了。final是java的关键字,它所表示的是“这部分是无法修改的”。不想被改变的原因有两个:效率、设计。使用到final的有三种情况:数据、方法、类。
1.final变量
有时候数据的恒定不变是很有用的,它能够减轻系统运行时的负担
1、编译期常量,永远不可改变。
2、运行期初始化时,我们希望它不会被改变。
- final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误
- final变量定义的时候,可以先声明,而不给初值,这中变量也称为空白final,无论什么情况,编译器都确保空白final在使用前必须被初始化,但是空白final在关键字final的使用上提供了更大的灵活性,为此一个类中的final变量就可以做到根据对象而有所不同,却保持其恒定不变的特性。
对于基本类型,final使数值恒定不变,而用于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。然而对象自身却是可以被修改的。这一限制也使用数组,它也是对象。
class Person {
private String name;
Person(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class FinalTest {
private final String final_01 = "chenssy"; //编译期常量,必须要进行初始化,且不可更改
private final String final_02; //空白final,必须在构造函数中赋值,在实例化一个对象时被初始化
final ArrayList<String> arry=new ArrayList<String>(); //final数组,数组中的对象可变
private static Random random = new Random();
private final int final_03 = random.nextInt(50); //使用随机数在运行时进行初始化
//引用
public final Person final_04 = new Person("chen_ssy"); //final指向对象引用,引用恒定不变,对象自身可被修改
FinalTest(String final_02){
this.final_02 = final_02;
}
public String toString(){
return "final_01 = " + final_01 +" final_02 = " + final_02 + " final_03 = " + final_03 +
" final_04 = " + final_04.getName();
}
public static void main(String[] args) {
System.out.println("------------第一次创建对象------------");
FinalTest final2 = new FinalTest("zj");
System.out.println(final2);
System.out.println("------------修改引用对象--------------");
final2.final_04.setName("wing");
// final2.final_04 = new Person("sdf");//错误,final_04自身可变,引用指向不能变。
System.out.println(final2);
System.out.println("------------更改数组内容--------------");
final2.arry.add("name");
final2.arry.add("wing");
System.out.println(final2.arry.toString());
}
}
/*
------------第一次创建对象------------
final_01 = chenssy final_02 = zj final_03 = 31 final_04 = chen_ssy
------------修改引用对象--------------
final_01 = chenssy final_02 = zj final_03 = 31 final_04 = wing
------------更改数组内容--------------
[name, wing]
*/
不要以为某些数据是final就可以在编译期知道其值,在这里是使用随机数其进行初始化,他要在运行期才能知道其值。
- 你不能够对final变量再次赋值
- 接口中声明的所有变量本身是public static final的
- 本地变量必须在声明时赋值
2.final方法
- final方法不能被重写,即父类的final方法是不能被子类所覆盖的
- 但是final修饰的方法可以被重载
3.final类
- final类不能被继承。
- final类中的成员变量可以是final或非final的,但是成员方法都隐式指定是final的,因为final类无法被继承,也就无法覆盖重写这些方法。
- final和abstract这两个关键字是反相关的,final类就不可能是abstract的
4.final参数
若某个参数被final修饰了,则代表了该参数是不可改变的
public class Custom {
public void test(final int i){
//i++; ---final参数不可改变
System.out.println(i);
}
public void test(final Person p){
//p = new Person(); --final参数不可变,不可更改参数引用,使其指向另一个对象
p.setName("chenssy"); //对象本身可以改变
}
}
当基本类型的参数被致命为final时,你可以读参数,但无法修改参数。这一特性主要用来向匿名内部类传递数据。匿名内部类如果需要用到外面的局部变量,该变量必须是final类型。
接口中的声明的变量始终都是 publis static final 类型的。
this关键字
理解: Java关键字this只能用于方法体内。
当一个对象创建后,Java虚拟机(JVM)就会给这个对象分配一个引用自身的指针,这个指针的名字就是this。因此,this只能在类中的非静态方法中使用,静态方法和静态的代码块中绝对不能出现this。
并且this只和特定的对象关联,而不和类关联,同一个类的不同对象有不同的this。
实例:
public class Test {
private int number;
private String username;
private String password;
private int x = 100;
public Test(int n) {
number = n; // 这个还可以写为: this.number=n;
}
public Test(int i, String username, String password) {
// 成员变量和参数同名,用"this.成员变量"的方式访问成员变量.
this.username = username;
this.password = password;
}
// 默认不带参数的构造方法
public Test() {
this(0, "未知", "空"); // 通过this调用另一个构造方法
}
public static void main(String args[]) {
Test t1 = new Test();
t1.outinfo(t1);
System.out.println(t1.getSelf().x);
System.out.println(t1.x);
}
private void outinfo(Test t) {
System.out.println("-----------");
System.out.println(t.number);
System.out.println(t.username);
System.out.println(t.password);
f(); // 这个可以写为: this.f();
}
private void f() {
// 局部变量与成员变量同名,用"this.成员变量"的方式访问成员变量.
int x;
x = this.x++;//这里this就表示调用此方法的当前对象,所以必须通过 对象.f()来调用方法,
//也说明this不能用在静态方法中(静态方法可通过类名访问,而不对象)
System.out.println(x);
System.out.println(this.x);
}
//返回当前实例的引用
private Test getSelf() {
return this;
}
}
/*输出
-----------
0
未知
空
100
101
101
101
*/
总结: this是指向当前对象本身的一个指针
- 通过this调用另一个构造方法,用法是this(参数列表),这个仅仅在类的构造方法中,别的地方不能这么用
- 数参数或者函数中的局部变量和成员变量同名的情况下,此时要访问成员变量则需要用“this.成员变量名”的方式来引用成员变量。当然,在没有同名的情况下,可以直接用成员变量的名字,而不用this,用了也不为错。
- 第三、在方法中,需要引用当前对象时候,直接用this。
super关键字
理解 super用在子类中,目的是访问直接父类中被屏蔽的成员(实例中的super.x)
实例
class Father {
public int num=0;
public String x="father";
private int k=0;
public Father() {
num++;
}
public Father(int num){
System.out.println(this.num);
}
public void outinfo(){
System.out.println("Father的方法");
}
}
class Son extends Father{
public int num=100;
public Son() {
super(); //调用超类的构造方法,只能放到第一行.有没有这行,输出结果一样,
//因为子类的对象创建会先调用父类的无参构造函数
int num=50;
/super(num);//错误,只能在第一行
System.out.println(num);//局部变量
System.out.println(this.num);//子类成员变量
System.out.println(super.num);//父类成员变量
// System.out.println(super.k);//错误:super不能访问父类中私有变量和私有方法
System.out.println(x); //若变量未被屏蔽,则可以直接输出父类成员变量x,子类继承而来
System.out.println(super.x);
outinfo();
super.outinfo();
//super(); //错误的,必须放到构造方法体的最前面.
}
public Son(int num){
super(num);
}
//覆盖了超类成员方法outinfo()
public void outinfo(){
System.out.println("Son的方法");
}
public static void main(String[] args) {
new Son();
new Son(8);
}
}
/*output:
50
100
1//无论有没有显示调用super(),结果都是1,
father
father
Son的方法
Father的方法
0
*/
总结:
- 在子类构造方法中要调用父类的构造方法。用“super(参数列表)”的方式调用,参数不是必须的。同时还要注意的一点是:“super(参数列表)”这条语句只能用在子类构造方法体中的第一行。
- 当子类方法中的局部变量或者子类的成员变量与父类成员变量同名时,也就是子类局部变量覆盖父类成员变量时,用“super.成员变量名”来引用父类成员变量。
- 当子类的成员方法覆盖了父类的成员方法时,此时,用“super.方法名(参数列表)”的方式访问父类的方法
finally关键字
finally:通常放在 try…catch 的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要 JVM 不关闭都能执行,可以将释放外部资源的代码写在 finally 块中。
当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。假如我们打开了一个文件,但在处理文件过程中发生异常,这时文件还没有被关闭,此时就会产生资源回收问题。对此,java提供了一种好的解决方案,那就是finally子句,finally子句中的语句是一定会被执行的,所以我们只要把前面说的文件关闭的语句放在finally子句中无论在读写文件中是否遇到异常退出,文件关闭语句都会执行,保证了资源的合理回收。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class Finally {
void fileWith(){
InputStream in = null;
try {
in = new FileInputStream("wang.txt");
//其他操作
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
调用System.exit()可以提前终止程序,而不执行finally的代码
finalize()方法
Object 类中定义的方法,Java 中允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。
finalize方法来自于java.lang.Object,用于回收资源。
可以为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象之前调用。
在实际应用中,不要依赖使用该方法回收任何短缺的资源,这是因为很难知道这个方法什么时候被调用。
class People{
final void output(String name){
System.out.println(name);
}
}
class Stu extends People{
final void output(String name,int id){//可以被重载
System.out.println(name);
}
public void finalize() throws Throwable{
super.finalize();
System.out.println("finalize method was called!");
}
}
public class Main {
public static void main(String[] args){
}
}
通常finalize的目的是在对象被真正回收之前做一些清理工作。例如,一个对象的finalize方法表示输入/输出连接可能在对象被永久回收前执行显式I/O事务来中断连接。
Object类的finalize方法不执行特别的操作,它只是简单地返回。Object子类可以重写这个方法。
Java编程语言不保证对于任何给定的对象哪个线程将调用finalize方法,但是它保证执行finalize的线程在调用finalize方法后不会一直保持任何用户可见的同步锁。如果finalize方法中抛出一个没有catch的异常,这个异常将会被忽略并且对象的finalize将终止。
在启用某个对象的 finalize 方法后,将不会执行进一步操作,直到 Java 虚拟机再次确定尚未终止的任何线程无法再通过任何方法访问此对象,其中包括由准备终止的其他对象或类执行的可能操作,在执行该操作时,对象可能被丢弃。 对于任何给定的对象,finalize最多被Java虚拟机执行一次。
finalize方法抛出的任何异常将导致这个对象的终结操作停止,但也会被忽略。(抛出异常后,该对象还是不可以继续操作,不会影响其他对象,直到被虚拟机回收)
volatile关键字
Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错
锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。
Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束
Volatile的实现中主要有两个步骤:
- 将当前处理器缓存行的数据会写回到系统内存。
- 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。
优点: Volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。
正确使用 volatile 的模式
很多并发性专家事实上往往引导用户远离 volatile 变量,因为使用它们要比使用锁更加容易出错。然而,如果谨慎地遵循一些良好定义的模式,就能够在很多场合内安全地使用 volatile 变量。要始终牢记使用 volatile 的限制 —— 只有在状态真正独立于程序内其他内容时才能使用 volatile —— 这条规则能够避免将这些模式扩展到不安全的用例。
模式 #1:状态标志
也许实现 volatile 变量的规范使用仅仅是使用一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。 很多应用程序包含了一种控制结构,形式为 “在还没有准备好停止程序时再执行一些工作”,如清单 2 所示:
清单 2. 将 volatile 变量作为状态标志使用
volatile boolean shutdownRequested;
...
public void shutdown() { shutdownRequested = true; }
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
很可能会从循环外部调用 shutdown() 方法 —— 即在另一个线程中 —— 因此,需要执行某种同步来确保正确实现 shutdownRequested 变量的可见性。(可能会从 JMX 侦听程序、GUI 事件线程中的操作侦听程序、通过 RMI 、通过一个 Web 服务等调用)。然而,使用 synchronized 块编写循环要比使用清单 2 所示的 volatile 状态标志编写麻烦很多。由于 volatile 简化了编码,并且状态标志并不依赖于程序内任何其他状态,因此此处非常适合使用 volatile。
这种类型的状态标记的一个公共特性是:通常只有一种状态转换;shutdownRequested 标志从 false 转换为 true,然后程序停止。这种模式可以扩展到来回转换的状态标志,但是只有在转换周期不被察觉的情况下才能扩展(从 false 到 true,再转换到 false)。此外,还需要某些原子状态转换机制,例如原子变量。
更多:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
volatile关键字是否能保证线程安全?
答案:不能
解析:volatile 关键字用在多线程同步中,可保证读取的可见性,JVM只是保证从主内存加载到线程工作内存的值是最新的读取值,而非 cache 中。但多个线程对 volatile 的写操作,无法保证线程安全。例如假如线程 1,线程 2 在进行 read,load 操作中,发现主内存中 count 的值都是 5,那么都会加载这个最新的值,在线程 1 堆 count 进行修改之后,会 write 到主内存中,主内存中的 count 变量就会变为 6;线程 2 由于已经进行 read,load 操作,在进行运算之后,也会更新主内存 count 的变量值为 6;导致两个线程及时用 volatile 关键字修改之后,还是会存在并发的情况。
assert关键字
Java在执行的时候默认是不启动断言检查的(这个时候,所有的断言语句都将忽略!),如果要开启断言检查,则需要用开关-enableassertions或-ea来开启。
在程序中右键菜单:Run As---> Run Configuration ---> 选择 Arguments 选项卡 在 VM arguments 文本框中输入: -ea 注意 中间没有空格,如果输入 -da 表示禁止断言。 然后关闭该窗口,提示保存,然后保存就开启了断言。
public static void main(String[] args){
//断言1结果为true,则继续往下执行
assert true;
System.out.println("断言1没有问题,Go!");
System.out.println("\n-----------------\n");
//断言2结果为false,程序终止
assert false : "断言失败,此表达式的信息将会在抛出异常的时候输出!";
System.out.println("断言2没有问题,Go!");
}
输出:
断言1没有问题,Go!
-----------------
Exception in thread "main" java.lang.AssertionError: 断言失败,此表达式的信息将会在抛出异常的时候输出! at java_interview.Main.main(Test.java:70)
陷阱
1、assert关键字需要在运行时候显式开启才能生效,否则你的断言就没有任何意义。而现在主流的Java IDE工具默认都没有开启-ea断言检查功能。这就意味着你如果使用IDE工具编码,调试运行时候会有一定的麻烦。并且,对于Java Web应用,程序代码都是部署在容器里面,你没法直接去控制程序的运行,如果一定要开启-ea的开关,则需要更改Web容器的运行配置参数。这对程序的移植和部署都带来很大的不便。
2、用assert代替if是陷阱之二。assert的判断和if语句差不多,但两者的作用有着本质的区别:assert关键字本意上是为测试调试程序时使用的,但如果不小心用assert来控制了程序的业务流程,那在测试调试结束后去掉assert关键字就意味着修改了程序的正常的逻辑。
3、assert断言失败将面临程序的退出。这在一个生产环境下的应用是绝不能容忍的。一般都是通过异常处理来解决程序中潜在的错误。但是使用断言就很危险,一旦失败系统就挂了。
总结
慎用。
goto关键字
Java语言中goto是保留关键字,没有goto语句,也没有任何使用goto关键字的地方。