修饰符:
像其他语言一样,Java可以使用修饰符来修饰类中方法和属性。主要有两类修饰符:
访问修饰符:default, public , protected, private
非访问修饰符:final, abstract, strictfp
修饰符一般使用在一个语句的前端,例:
public void Pig{
int a = 1;
protected String b = "b";
private static final int c = 1;
}
访问修饰符
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java支持4种不同的访问权限。
默认的,也称为 default,在同一包内可见,不使用任何修饰符。
私有的,以 private 修饰符指定,在同一类内可见。
共有的,以 public 修饰符指定,对所有类可见。
受保护的,以 protected 修饰符指定,对同一包内的类和所有子类可见。
Java中类的修饰符有以下几种:private 、default(package)、protect、public,其范围如下表:
范围
private
default
protected
public
同一类
√
√
√
√
同一包中的类
√
√
√
同一包中的类、不同包中的子类
√
√
所有
√
默认访问修饰符-不使用任何关键字
使用默认访问修饰符声明的变量和方法,对同一个包内的类是可见的。接口里的变量都隐式声明为public static final,而接口里的方法默认情况下访问权限为public。
私有访问修饰符-private
私有访问修饰符是最严格的访问级别,所以被声明为private的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为private。
public class Pig{
private int num = 1;
public int getNum(){
return num;
}
public void setNum(int num){
this.num = num;
}
}
公有访问修饰符-public
被声明为public的类、方法、构造方法和接口能够被任何其他类访问。
如果几个相互访问的public类分布在不同的包中,则需要导入相应public类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类继承。
受保护的访问修饰符-protected
被声明为protected的变量、方法和构造器能被同一个包中的任何其他类访问,也能够被不同包中的子类访问。
Protected访问修饰符不能修饰类和接口,方法和成员变量能够声明为protected,但是接口的成员变量和成员方法不能声明为protected。
子类能访问Protected修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。
访问控制和继承
请注意以下方法继承的规则:
父类中声明为public的方法在子类中也必须为public。
父类中声明为protected的方法在子类中要么声明为protected,要么声明为public。不能声明为private。
父类中声明为private的方法,不能够被继承。
非访问修饰符
为了实现一些其他的功能,Java也提供了许多非访问修饰符。
static修饰符,用来创建类方法和类变量。
Final修饰符,用来修饰类、方法和变量,final修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
Abstract修饰符,用来创建抽象类和抽象方法。
Synchronized和volatile修饰符,主要用于线程的编程。
Static修饰符
静态变量:
Static关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被成为类变量。局部变量不能被声明为static变量。
静态方法:
Static关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。(这就是访问权限的问题了)。
被static
修饰符修饰的成员方法和成员变量是独立于该类的任何对象的,可以被所有的类所共享。应为在加载(还没有实例化该类之前),JVM就已经为静态方法和静态变
量分配内存空间。到时候只要使用类名去访问就可以了。被public
修饰的static成员变量和成员方法本质是全局变量或者是全局方法。任何一个类都可以同个类名访问内部和成员变量和成员方法
static代码块
也叫做静态代码块,独立于类成员的代码块。在一个类中可以有多个静态代码块,位置可以任意,它不再任何方法体内,JVM加载类的时候,会自动执行静态代码块(记住:在加载类的时候就已经执行了,不用等待实例化类)。如果是由对各静态代码块,那么JVM就会按照这些静态代码块出现的顺序执行它们。每个静态代码块自会执行一次。(就是这个程序从开始运行到结束只会执行一次)。
public class HellowJava {
static{}
public static void main(String[] arguments) {
System.out.println("main程序");
new StaticTest();
}
}
class StaticTest{
public StaticTest(){
System.out.println("StaticTest");
}
static{
System.out.println("静态代码块0");
}
static{
System.out.println("静态代码块1");
}
}
执行结果:
main静态代码块
main程序
静态代码块0
静态代码块1
StaticTest
解释:程序执行后,先加载 HellowJava 类 ,这时候执行这个类中的静态代码块,加载完毕那个后直接执行 mian() 方法,
按顺序先执行 System.out.println("main程序");
再加载StaticTest 类,这时候执行StaticTest中的静态代码块,并且按顺序执行这些代码块
最后实例化这个StaticTest类(这时候就会调用构造方法)
public class HellowJava {
static{
System.out.println("main静态代码块");
}
public static void main(String[] arguments) {
System.out.println("main程序"+StaticTest.a); //这里的就造成了 StaticTest 类的加载(就是静态代码块会执行)
StaticTest
new StaticTest(); }}
class StaticTest{
public StaticTest(){
System.out.println("StaticTest"); }
static{ a=1; System.out.println("静态代码块0"); }
static{ System.out.println("静态代码块1"); } public static final int a ;
}
结果:
main静态代码块静态代码块0静态代码块1main程序1 StaticTest解释:本来以为static
修饰的代码都是会按顺序直接执行或者分配内存的。看来好像不是这样子。怎么说呢。从最后一行代码可以看到我把静态变量的定义放在了最后面,但是并没有出现
任何报错,说明了JVM记载的时候是首先加载静态变量,再加载静态代码块的。(个人理解,喜欢就喷吧)
static和final一块用表示什么
static final用来修饰成员变量和成员方法,可以理解为“全局常量”。
对于变量 :表示一旦给定值就不可以更改,并且可以通过类名来访问。
对于方法: 表示不可以覆盖,并且可以通过类名来访问。
声明静态方法的限制:
仅能调用其他的static方法;
仅能访问static 数据
不能以任何形式引用this 和super(和继承有关)
Final
Final方法
类中的Final方法可以被子类继承,但是不能被子类修改。
声明final方法的主要目的是防止该方法的内容被修改。
Final类
Final类不能被继承,没有类能够继承final类的任何特性。
Final修饰符
final变量能被显性的初始化,并且只可以初始化一次。被声明为final的对象不能指向不同的对象。但是final中的值是可以改变的。也就是说final中对象的引用不能够改变,但是里边的值可以改变。
例如: final Integer a =10; Integer b =20; 可以这样改变 a =11; 或 a =20; 但是不可以 a =b ;
Final修饰符通常和static修饰符一起使用来创建类常量。
final修饰的成员变量
(1)final修饰的成员变量一旦赋值后,不能被重新赋值。
(2)final修饰的实例Field,要么在定义该Field的时候指定初始值,要么在普通初始化块或构造器中指定初始值。但是如果在普通初始化块中为某个实例Field指定了初始值,则不能再在构造器中指定初始值。
(3)final修饰的类Field,要么在定义该Field的时候指定初始值,要么在静态代码块中定义初始值。
(4)如果在构造器或初始化块中对final成员变量进行初始化,则不要在初始化之前就访问该成员的值。
class finalTest{
final int a = 1; //直接赋值
final String b ;
{
b = "b"; //在代码块中赋值
//这个代码块块只有当实例化这个类的时候优先于静态方法执行,
//和构造方法无关,实例化的时候都会去执行 //比构造函数先执行
}
final boolean c;
public finalTest(){ //在构造函数中赋值
c = false;
}
final static int d = 8; //直接赋值
final static int e;
static{
e = 1; //在静态代码块中赋值
}
}
final 的局部变量
(1)系统不会对局部变量进行初始化。布局变量必须要显示初始化。所以final修饰的局部变量既可以在定义的时候指定默认值,也可以不指定默认值。
(2)final修饰形参的时候,不能赋值
final修饰基本数据类型变量和修饰引用类型变量的区别
final修饰基本数据类型的变量,一旦该变量赋值后,就不能被重新复制赋值了。
对于引用类型的变量,保存的只是引用,final修饰的应用类型的变量只是保证这个对象不改变,但是这个对象的内部内容是可以发生改变的。比如:
final List list = new ArrayList();
list.add("a"); list.add("b");
这里可以看到list在被使用final修饰后还是可以往里边添加内容的,list的内部可以改变。再看:
final int a = 1;
a =2;
这个系统会报错。
final 的宏变量
(1)final 的一个重要的用途就是宏变量,当定义final变量是就制定了初值,这个初值是在编译的时候就加载进来了。编译会把程序中所用到的该变量的地方替换成该变量的值。
public classFinalTest {
public static voidmain(String[] args){
final String name = "小明" + 22.0;
final String name1 = "小明" + String.valueOf(22.0);
System.out.println(name == "小明22.0");
System.out.println(name1 == "小明22.0");
}
}
结果:
true
false
final String name1 = "小明"+String.valueOf(22.0); 中调用了String类的方法,所以在编译的时候无法确定name1的值,所以name1不会当作是宏变量。
packagecn.lsl;
public classFinalTest {
public static voidmain(String[] args){
String s1 = "小明";
String s2 = "小" + "明";
System.out.println(s1 == s2); //true
String str1 = "小";
String str2 = "明";
String s3 = str1 +str2;
System.out.println(s1 == s3); //false
//宏替换
final String str3 = "小";
final String str4 = "明";
String s4 = str3 +str4;
System.out.println(s1 == s4); //true
}
}
(1)Java会使用常量池管理直接使用过的字符串直接量。 String a = "Hellow" ; 那么字符串池会缓存一个字符串 “Hellow”,当执行String b = "Hellow",会直接让b 指向“Hellow” 这个字符串, 所以a == b ;是true
(2)String s3 = str1+str2;在编译时没有办法确定s3的值。
(3)String s4 = str3+str4;应为执行了宏变换,所以在编译的时候就已经确定了s4的值
用final修饰的方法不能被重写。用final修饰的类不能有子类。
不可变类
不可变类是指创建该类的实例后,该实例的Field是不可改变的。如果创建自定义的不可变类,应该遵循如下规则
(1)使用private和final修饰符来修饰该类的Field。
(2)提供带参数的构造器,用于传入参数来初始化类里的Field。
(3)仅为该类的Field提供getter方法,不要为该类的Field提供setter方法。
(4)如果有必要,重写Object类的hashCode和equals方法。
Abstract修饰符
抽象类:
abstract修饰符不能用来实例化对象,声明这个抽象类的唯一目的是为了将来对这个类进行扩充。
一个类不能同时被abstract和final修饰,如果这个类包含抽象方法,那么这个类一定要声明为抽象类,否则出现错误。
抽象类的内部可以包含抽象方法和非抽象方法。
抽象类如下:
abstract class CaraVan{
private double price;
private String model ;
private String year;
public abstract void setName(String name); //抽象方法
public abstract String getName();
}
抽象方法:
抽象方法是一种没有任何实现的方法,具体的实现依靠子类方法实现。该方法也不可以声明为final 和static
任何继承了抽象类的子类必须实现父类的中抽象方法,除非这个子类也是抽象类。
如果一个类中包含抽象方法,那么这个类必须是抽象类,一个抽象类中也可以不包含任何的抽象方法。
例:
abstract class Animal{
public abstract void run(); //抽象方法。
}
class Pig extends Animal{
@Override
public void run() {
// TODO Auto-generated method stub
//do ...
}
}
和接口(Interface)的区别。
1、相同点:
(1)都是抽象类,都是不能实例化。
(2)interface实现类及abstact class 的之类都必须实现已经声明的抽象方法。
2、不同点
(1)interface 的实现使用的是implements ,而abstract class 的实现使用的是extends.
(2)一个类可以实现多个interface但是只可以继承一个abstract class
(3)interface强调功能的实现,而abstract 强调所属的关系。
(4)实现显示不一样,interface的每个方法都是抽象方法,没有方法体,而abstract class 子类可以选择的实现这些方法(其他的可以在abstract class类中直接实现方法体)。
抽象类的这个选择有两点含义:
一是Abastract class中并非所有的方法都是抽象的,只有那些冠有abstract的方法才是抽象的,子类必须实现。那些没有abstract的方法,在Abstrct class中必须定义方法体。
二是abstract class的子类在继承它时,对非抽象方法既可以直接继承,也可以覆盖;而对抽象方法,可以选择实现,也可以通过再次声明其方法为抽象的方式,无需实现,留给其子类来实现,但此类必须也声明为抽象类。既是抽象类,当然也不能实例化。
(5)interface是完全抽象的。只能声明方法,权限也只能声明为public,不能定义方法体,也不可以实例化变量(都是 public static fianl 类型)
详细参考:
Synchronized修饰符
Synchronized关键字声明的方法同一时间只能被一个线程访问。Synchronized修饰符可以应用于四个访问修饰符。
实例:
public synchronized void setName(){
//..
测试代码:
/*静态代码块的测试*/
public class SynchronizedClass implements Runnable {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
SynchronizedClass run = new SynchronizedClass();
Thread t1 = new Thread(run,"aaa");
Thread t2 = new Thread(run,"bbb");
t1.start();
t2.start();
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(this){
for(int i = 0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
/*
* 结果:
aaa 0
aaa 1
aaa 2
bbb 0
bbb 1
bbb 2
* */
/*
* 然而,当一个线程访问object的一个synchronized(this)同步代码块时,
* 另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。*/
//————————————————————————————————————————————————————————————————————————
public class SynchronizedClass2 {
public void test1(){
synchronized(this){
for(int i = 0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
public void tet2(){
for(int i = 0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
final SynchronizedClass2 run = new SynchronizedClass2();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
run.test1();
}
},"a");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
run.tet2();
}
},"b");
t1.start();t2.start();
}
}
/*结果 。每一次的结果可能都是不一样的
* a 0
a 1
b 0
a 2
b 1
b 2*/
//————————————————————————————————————————————————————————————————————
/*
* 当一个线程访问object的一个synchronized(this)同步代码块时,
* 其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞
*
* 每个对象都只有一个锁,只有拿到了这个锁才可以访问这个对象中的同步代码块,
* 是否访问的是同一个同步代码块都需要拿到这个对象锁
* 非同步代码块可以任意访问
*
* 当有一个线程已经拿到这个锁的时候,另一个线程想要去访问这个对象中的不同于前一个线程访问的同步代码块时,
* 也需要等待前一个线程访问完成,释放对象锁才可以访问这个对象的同步代码块*/
public class SynchronizedClass3 {
public void test1(){
synchronized(this){
int i = 5;
while(i-->0){
System.out.println(Thread.currentThread().getName()+" "+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void test2(){
synchronized(this){
int i = 5;
while(i-->0){
System.out.println(Thread.currentThread().getName()+" "+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
final SynchronizedClass3 run = new SynchronizedClass3();
// TODO Auto-generated method stub
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
run.test1();
}
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
run.test2();
}
});
t1.start();
t2.start();
}
}
/*
* 结果:
*
Thread-0 4
Thread-0 3
Thread-0 2
Thread-0 1
Thread-0 0
Thread-1 4
Thread-1 3
Thread-1 2
Thread-1 1
Thread-1 0
* */
//——————————————————————————————————————————————————————————————
/*
* 和实例3一样,同步代码块的原则到同步方法同样是适用的
* 当一个线程访问object的一个synchronized(this)同步代码块时,
* 其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞
*
* 每个对象都只有一个锁,只有拿到了这个锁才可以访问这个对象中的同步代码块,
* 是否访问的是同一个同步代码块都需要拿到这个对象锁
* 非同步代码块可以任意访问
*
* 当有一个线程已经拿到这个锁的时候,另一个线程想要去访问这个对象中的不同于前一个线程访问的同步代码块时,
* 也需要等待前一个线程访问完成,释放对象锁才可以访问这个对象的同步代码块*/
public class SynchronizedClass4 {
public synchronized void test1(){
int i = 5;
while(i-->0){
System.out.println(Thread.currentThread().getName()+" "+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void test2(){
int i = 5;
while(i-->0){
System.out.println(Thread.currentThread().getName()+" "+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
final SynchronizedClass4 run = new SynchronizedClass4();
// TODO Auto-generated method stub
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
run.test1();
}
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
run.test2();
}
});
t1.start();
t2.start();
}
}
/*
* 结果:
*
Thread-0 4
Thread-0 3
Thread-0 2
Thread-0 1
Thread-0 0
Thread-1 4
Thread-1 3
Thread-1 2
Thread-1 1
Thread-1 0
* */
snippet_file_1.txt
1、无论synchronized 关键字作用在方法上还是对象上,如果他作用的对象是非静态的那么它取得的锁是对象;如果作用在静态方法或者或者一个类,那么它取得的锁是类,该类的所有对象同一把锁。
2、每个对象只有一把锁(lock)与之相关联,谁拿到那把锁就可以运行这个对象控制的那段代码。
3、实现同步是要很大的系统开销的,有时候会造成死锁,尽可能避免无谓的同步控制。
在synchronized(this) 表示锁住的是this这个对象。
如果要实现同步,必须要锁同一个对象,就算是两个线程的锁一个是类,一个是类的实例(对象)例如:
final Pig pig = new Pig();
Thread t3 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(pig){
for(int i = 0;i<10;i++){
pig.eat();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
},"c");
Thread t4 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(Pig.class){
for(int i = 0;i<10;i++){
pig.eat();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
},"d");
结果:
d 2
c 2
c 3
d 4
c 5
d 6
d 7
c 8
d 9
c 10
上面的例子中,一个锁是Pig对象,一个锁是Pig类,这样也会出现访问混乱,不能达到同步的效果。
Transient修饰符
实现了Serilizable接口可以将不需要序列化的的属性前面加上 transient ,序列化对象的时候,不会吧这个属性序列化到指定的目的地。
(1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
(2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
(3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
Volatile修饰符
Volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
一个volatile对象引用可能是null。
publicclassMyRunnableimplementsRunnable{privatevolatilebooleanactive;publicvoidrun(){active =true;while(active)// 第一行{// 代码}}publicvoidstop(){active =false;// 第二行}}
通常情况下,在一个线程调用 run() 方法(在 Runnable 开启的线程),在另一个线程调用 stop() 方法。 如果 第一行 中缓冲区的 active 值被使用,那么在第二行 的 active 值为 false 时循环不会停止。
但是以上代码中我们使用了 volatile 修饰 active,所以该循环会停止。
相关测试代码: