**
final关键字的作用
**
相信对于final的用法,大多数人都可以随口说出三句话:
1、被final修饰的类不可以被继承
2、被final修饰的方法不可以被重写
3、被final修饰的变量不可以被改变
重点就是第三句。被final修饰的变量不可以被改变,什么不可以被改变呢,是变量的引用?还是变量里面的内容?还是两者都不可以被改变?写个例子看一下就知道了:
1 public class FinalString
2 {
3 private String str;
4
5 public FinalString(String str)
6 {
7 this.str = str;
8 }
9
10 public String getStr()
11 {
12 return str;
13 }
14
15 public void setStr(String str)
16 {
17 this.str = str;
18 }
19 }
1 public class Test
2 {
3 public static void main(String[] args)
4 {
5 final FinalString fs = new FinalString("1");
6 fs.setStr("2");
7 System.out.println(fs.getStr());
8 }
9 }
运行一下,一点问题都没有。稍微修改一下呢:
1 public static void main(String[] args)
2 {
3 final FinalString fs = new FinalString("1");
4 final FinalString fss = new FinalString("333");
5 fs = fss;
6 }
第5行报错了,“The final local variable fs cannot be assigned”。可见,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的。OK,那final修饰数组呢?
1 public static void main(String[] args)
2 {
3 final String[] strs0 = {"123","234"};
4 final String[] strs1 = {"345","456"};
5 strs1 = strs0;
6 strs1[1] = "333";
7 }
同样,第5行报错了“The final local variable strs1 cannot be assigned”,第6行一点问题都没有。变量和数组一样,都是引用不可变,引用指向的内容可变。实际上如果用过FindBugs插件的应该知道,假如代码里面用final修饰了一个数组,那么改行代码会被作为findBugs的一个bug被查找出来,因为“用final修饰数组是没有意义的”。
接下来,再看一下用final修饰方法参数的场景:
1 public class Test
2 {
3 public static void main(String[] args)
4 {
5 FinalString fs = new FinalString("");
6 A(fs);
7 }
8
9 private static void A(final FinalString fs)
10 {
11 fs.setStr("123");
12 FinalString fss = new FinalString("22");
13 fs = fss;
14 }
15 }
一样,同样是13行报错,11行没有问题,相信大家已经知道原因了。
总结
“引用”是Java中非常重要的一个概念,对于引用的理解不深,很容易犯一些自己都没有意识到的错误。被final修饰的变量,不管变量是在是哪种变量,切记不可变的是变量的引用而非引用指向对象的内容。另外,本文中关于final的作用还有两点没有讲到:
1、被final修饰的方法,JVM会尝试为之寻求内联,这对于提升Java的效率是非常重要的。因此,假如能确定方法不会被继承,那么尽量将方法定义为final的,具体参见运行期优化技术的方法内联部分。
方法内联(代码优化最重要的优化手段之一)的目的主要有两个:去除方法调用的成本(如建立栈帧等)、为其他优化建立了良好的基础,方法内联膨胀之后可以便于在更大范围上采取后续的优化手段。
举个例子:
public final int getA()
{
getA()语句1;
getA()语句2;
getA()语句3;
getA()语句4;
getA()语句5
}
public static void main(String[] args)
{
main语句1;
main语句2;
int i = getA();
main语句3;
main语句4
}
优化之后变为:
public static void main(String[] args)
{
main语句1;
main语句2;
getA()语句1;
getA()语句2;
getA()语句3;
getA()语句4;
getA()语句5;
main语句3;
main语句4
}
从效果上看,无非是把getA()方法中的内容原封不动地拿到main函数中,但这样却少了保护现场、恢复线程、建立栈帧等一系列的工作,并且代码一膨胀,原来方法A有5行代码,方法B有6行代码,方法C有7行代码,对于三个方法各自运行来说可能没什么好优化的,但是三个方法合起来放到main函数之中,就有了很大的优化空间了。
讲到这里,我们是否理解为什么要尽量把方法声明为final?因为Java有多态的存在,运行时调用的是哪个方法可以根据实际的子类来确定,极大地增强了灵活性,但是这样的话,编译期间同样也无法确定应该使用的是哪个版本,所以无法被内联。但是被声明为final的方法不一样,这些方法无法被重写,所以调用类A的B方法,运行时调用的必然是类A的B方法,可以被内联。
2、被final修饰的常量,在编译阶段会存入调用类的常量池中,具体参见类加载机制最后部分和Java内存区域:
3、引用静态常量时,常量在编译阶段会存入类的常量池中,本质上并没有直接引用到定义常量的类
public class ConstClass
{
public static final String HELLOWORLD = "Hello World";
static
{
System.out.println("ConstCLass init");
}
}
public class TestMain
{
public static void main(String[] args)
{
System.out.println(ConstClass.HELLOWORLD);
}
}
运行结果为
Hello World
在编译阶段通过常量传播优化,常量HELLOWORLD的值"Hello World"实际上已经存储到了NotInitialization类的常量池中,以后NotInitialization对常量ConstClass.HELLOWORLD的引用实际上都被转化为NotInitialization类对自身常量池的引用了。也就是说,实际上的NotInitialization的Class文件中并没有ConstClass类的符号引用入口,这两个类在编译成Class之后就不存在任何联系了
Static关键字的作用
static关键字最基本的用法是:
1、被static修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要new出一个类来
2、被static修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要new出一个类来
被static修饰的变量、被static修饰的方法统一属于类的静态资源,是类实例之间共享的,换言之,一处变、处处变。JDK把不同的静态资源放在了不同的类中而不把所有静态资源放在一个类里面,很多人可能想当然认为当然要这么做,但是是否想过为什么要这么做呢?个人认为主要有三个好处:
1、不同的类有自己的静态资源,这可以实现静态资源分类。比如和数学相关的静态资源放在java.lang.Math中,和日历相关的静态资源放在java.util.Calendar中,这样就很清晰了
2、避免重名。不同的类之间有重名的静态变量名、静态方法名也是很正常的,如果所有的都放在一起不可避免的一个问题就是名字重复,这时候怎么办?分类放置就好了。
3、避免静态资源类无限膨胀,这很好理解。
OK,再微微深入一下,也是有些人容易混淆的一个问题:静态方法能不能引用非静态资源?静态方法里面能不能引用静态资源?非静态方法里面能不能引用静态资源?比如就以这段代码为例,是否有错?
public class A
{
static
{
System.out.println("A.static block");
}
public A()
{
System.out.println("A.constructor()");
}
}
public class B extends A
{
static
{
System.out.println("B.static block");
}
public B()
{
System.out.println("B.constructor()");
}
public static void main(String[] args)
{
new B();
new B();
}
}
结果:
A.static block
B.static block
A.constructor()
B.constructor()
A.constructor()
B.constructor()
这个例子得出第三个结论:静态代码块是严格按照父类静态代码块->子类静态代码块的顺序加载的,且只加载一次。
**
Hashcode和equals的区别
**
**
进程 线程 协程
**
**
同步处理 异步处理
**
**
并行 并发
**
并行:指多个任务同时执行。
并发:指在一个时间段内,多个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行(即多个任务在同一处理机上交替执行)。
对单核CPU,因为一个CPU一次只能执行一条指令,是无法做到并行,只能做到并发