自我参考-Java面向对象(下)

来源:互联网 发布:e订通软件 编辑:程序博客网 时间:2024/06/05 15:31

十一、处理对象
1、打印对象和toString方法

package code;public class PrintObject{    public static void main(String[] args){         Person p = new Person("孙悟空");         System.out.println(p);    }}class Person{    private String name;    public Person(String name){        this.name = name;    }}

toString是一个“自我描述”的方法

package code;class Apple{    private String color;    private double weight;    public void setColor(String color){        this.color = color;    }    public String getColor(){        return color;    }    public void setWeight(double weight){        this.weight = weight;    }    public double getWeight(){        return weight;    }    public Apple(){}    public Apple(String color,double weight){        this.color = color;        this.weight = weight;    }    public String toString(){        return("这个苹果的颜色是:" + color + ",重量是:" + weight);    }}public class ToStringTest{    public static void main(String[]args){        Apple a = new Apple("红色",5.5);        System.out.println(a);    }}

这个苹果的颜色是:红色,重量是:5.5

通过重写Apple类的toString()方法,就可以让系统在打印Apple对象时打印出该对象的“自我描述”信息。

通常将Apple类的toString()方法改为:

public String toString(){        return "Apple[color=" + color + ",weight=" + weight + "]";    }

Apple[color=红色,weight=5.5]

2、 ==和equals方法
对于两个引用类型变量,只有指向同一个对象时,==判断才会返回true。==不可用于比较类型上没有父子关系的两个对象。

package code;public class EqualTest{    public static void main(String[] args){        int it = 65;        float fl = 65.0f;        System.out.println("65和65.0f是否相等" + (it==fl));        char ch = 'A';        System.out.println("65和'A'是否相等" + (it == ch));        String str1 = new String("hello");        String str2 = new String("hello");        System.out.println("str1和str2是否相等" + (str1==str2));        System.out.println("str1是否equals str2" + str1.equals(str2));        //由于java.lang.String与EqualTest类没有继承关系,        //所以下面编译错误        //System.out.println("hello" == new EqualTest());    }}

常量池(constant pool)专门用于管理在编译时被确定并保存在已编译的.class文件中的一些数据,它包括关于类,方法,接口中的常量,还包括字符串常量。

JVM常量池保证相同的字符串直接量只有一个,不会产生多个副本,使用new String()创建的字符串对象是运行时创建出来的,他被保存在运行时内存区,不会放入常量池
String已经重写了Object的equals()方法,String()方法判断两个字符串相等的标准是:只要两个字符串所包含的字符序列相同,通过equals()比较返回true,否则返回false

package code;public class OverrideEqualsRight{    public static void main(String [] args){        Person p1 = new Person("孙悟空","12343433433");        Person p2 = new Person("孙行者","12343433433");        Person p3 = new Person("孙悟饭","99933433");        System.out.println("p1和p2是否相等" + p1.equals(p2));        System.out.println("p1和p2是否相等" + p2.equals(p3));    }}
class Person{    private String name;    private String idStr;    public Person(){}    public Person(String name,String idStr){        this.name = name;        this.idStr = idStr;    }    public void setName(String name){        this.name = name;    }    public String getName(){        return name;    }    public void setIdStr(String idStr){        this.idStr = idStr;    }    public String getIdStr(){        return idStr;    }    public boolean equals(Object obj){        //        if(this == obj)             return true;        if(obj != null && obj.getClass() == Person.class){            Person personObj = (Person)obj;            if(this.getIdStr().equals(personObj.getIdStr())){                return true;            }        }        return false;    }}

p1和p2是否相等true
p1和p2是否相等false

上面程序重写了equals()方法,制定了Person对象和其他对象相等的标准,另一个对象必须是Person类的实例,且两个对象的idStr相等,即可判断两个Person对象相等。

对于instanceof运算符而言,当前面对象是后面类的实例或其子类的实例时都返回true,所以重写equals()方法判断两个对象是否为同一个类的实例时使用instanceof是有问题

十二、类成员
1、理解类对象

package code;public class NullAccessStatic{    private static void test(){        System.out.println("static修饰的类方法");    }    public static void main(String []args){        NullAccessStatic nas = null;        //使用null对象调用所属类的静态方法        nas.test();    }}

static修饰的类方法
表明null对象可以访问所属类的类成员, null对象访问实例成员,将会引发NullPointerException异常,

静态初始化块用于执行类初始化动作,在类的初始化阶段,系统会调用该类的静态初始化块来对类进行初始化,一旦该类初始化结束后,静态初始化块将永远不会获得执行的机会 。

对static关键字而言,有一条非常重要的规则,类成员不能访问实例成员。因为类成员是属于类的,类成员的作用域比实例成员的作用域更大,完全可能出现类成员已经初始化完成,而实例成员还没初始化,所以不能访问。

2、 单例类
如果一个类始终只能创建一个实例,这个类被称为单例类 。

为了避免其他类自用创建该类的实例,应该把该类的构造器使用private修饰,从而把该类的所有构造器隐藏起来。

根据良好封装原则,一旦该类的构造器隐藏起来,就需要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰,(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)

而且该类必须缓存已经创建的对象,否则该类无法知道是否曾经创建过对象,也就无法保证只创建一个对象。

package code;class Singleton{    public static Singleton instance;    private Singleton(){}    public static Singleton getInstance(){        if(instance==null){            instance = new Singleton();        }        return instance;    }}public class SingletonTest{    public static void main(String[] args){        Singleton s1 = Singleton.getInstance();        Singleton s2 = Singleton.getInstance();        System.out.println("s1和s2是否相等" + (s1==s2));    }}

s1和s2是否相等true

十三、final 修饰符
final修饰变量时,表示该变量一旦获得了初始值就不可改变,

由于final变量获得初始值之后不能被重新赋值,因此final修饰成员变量和修饰局部变量时有一定的不同。

1、final成员变量
成员变量是随类初始化或对象初始化而初始化的,当类初始化时,系统会为该类的类变量分配内存,并分配默认值;当创建对象时,系统会为该对象的实例变量分配内存,并分配默认值。

java语法规定:final修饰的成员变量必须由程序员显式地指定初始值

归纳: final修饰的类变量,实例变量能指定初始值的地方:

  • 类变量:
    • 在静态初始化块中指定初始值
final static int;static{  i = 10;}

声明该类变量时指定初始值 final static int i = 10;

  • 实例变量:
    • 非静态初始化块
final int i;{  i = 10;}

声明该实例变量
final int i = 10;

构造器中指定初始值

public class FinalVariableTest{  final int i;  public FinalVariableTest(){    i = 10;    //如果在初始化块已经赋值,就不能在构造器中重新赋值  }}

实例变量不能在静态初始化块中指定初始值,因为静态初始化块是静态成员,不可访问实例变量——非静态成员;类变量不能在普通初始化块中指定初始值,因为类变量在类初始化阶段已经被初始化了,普通初始化块不能对其重新赋值。

如果打算在构造器、初始化块中对final成员变量进行初始化,则不要在初始化之前就访问成员变量的值。

package code;public class FinalErrorTest{  final int age;  {    // System.out.println(age);    age = 6;    System.out.println(age);  }  public static void main(String [] args){    new FinalErrorTest();  }}

I:>javac -d . FinalErrorTest.java
FinalErrorTest.java:5: 错误: 可能尚未初始化变量age
System.out.println(age);
^
1 个错误
2、 final局部变量

package code;public class FinalLocalVariable{  public void test(final int a){    // 不能对final修饰的形参赋值,下面语句非法    // a = 5;  }  public static void main(String[] args){    // 定义final局部变量时指定默认值,则str变量无法重新赋值    final String str = "hello";    // str = "java";    final double d;    d = 44.5;    // d = 443.3;  }}

I:>javac -d . FinalLocalVariable.java
FinalLocalVariable.java:10: 错误: 无法为最终变量str分配值
str = “java”;
^
1 个错误
I:>javac -d . FinalLocalVariable.java
FinalLocalVariable.java:5: 错误: 不能分配最终参数a
a = 5;
^
FinalLocalVariable.java:13: 错误: 可能已分配变量d
d = 443.3;
^
2 个错误
因为形参在调用该方法,由系统根据传入的参数来完成初始化,因此使用final修饰的形参不能被赋值。

3、 final修饰基本类型变量和引用类型变量的区别

package code;import java.util.Arrays;class Person{  private int age;  public Person(){}  public Person(int age){    this.age = age;  }  public void setAge(int age){    this.age = age;  }  public int getAge(){    return age;  }}public class FinalReferenceTest{  public static void main(String[]args){    final int[] iArr = {5,6,12,8};    System.out.println(Arrays.toString(iArr));    Arrays.sort(iArr);    System.out.println(Arrays.toString(iArr));    iArr[2] = -8;    System.out.println(Arrays.toString(iArr));    // iArr = null;    final Person p = new Person(45);    p.setAge(21);    System.out.println(p.getAge());    // p = null;  }}

[5, 6, 12, 8]
[5, 6, 8, 12]
[5, 6, -8, 12]
21
使用final修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所引用对象的内容。

4、 可执行“宏替换”的final变量
对一个final变量,不管它是类变量,实例变量,还是局部变量,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量。

  • 使用final修饰符修饰;
  • 在定义该final变量时指定了初始值;
  • 该初始值可以在编译时就被确定下来
package code;public class FinalReplaceTest{  public static void main(String[]args){    final int a = 5+3;    final double b = 1.2/3;    final String str = "疯狂" + "Java";    final String book = "疯狂Java讲义:" + 99.0;    final String book2 = "疯狂Java讲义" + String.valueOf(99.0);    System.out.println(book == "疯狂Java讲义:99.0");    System.out.println(book2 == "疯狂Java讲义:99.0");  }}

true
false

由于定义book2变量时显式将数值99.0转换为字符串,但由于该变量的值需要调用String类的方法,因此编译器无法在编译时确定book2的值,book2不会被当成“宏变量”处理。

package code;public class StringJoinTest{  public static void main(String[] args){    String str1 = "疯狂Java";    String str2 = "疯狂" + "Java";    String str3 = str1 + str2;    System.out.println(str1 == str2);    System.out.println(str1 == str3);  }}

true
false

由于编译器可以在编译阶段就确定str2的值为“疯狂Java”,所以系统会让s2直接指向常量池中缓存的“疯狂Java”字符串,因此str1 = str2. 对于final实例变量而言,只有在定义该变量时指定初始值才会有“宏变量”的效果。

5、 final方法
final修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用final方法。

对于一个private方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法—–如果子类中定义一个与父类private方法有相同方法名,相同形参列表,相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法。

package code;public class PrivateFinalMethodTest{  private final void test(){}}class Sub extends PrivateFinalMethodTest{  public void test(){}}class FinalOverload{  // final修饰的方法只是不能被重写,完全可以被重载  public final void test(){}  public final void test(String agr){}}

6、 final类
final修饰的类不可以有子类,例如java.lang.Math类就是一个final类,它不可以有子类

十四、抽象类
1、抽象方法和抽象类
抽象方法和抽象类的规则:
抽象方法和抽象类必须使用abstract修饰符来定义,有抽象方法的类只能被定义成抽象类,抽象类里可以没有抽象方法,抽象方法不能有方法体。

抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。

抽象类可以包含成员变量,方法,构造器,初始化类,内部类。抽象类不能用于创建实例,主要是用于被其子类调用。

含有抽象方法的类只能被定义成抽象类。

抽象方法和空方法体不是同一个概念。

  • public abstract void test();是一个抽象方法。
  • public void test(){}是一个空方法体。
Shape.javapackage code.shape;public abstract class Shape{  {    System.out.println("执行Shape的初始化块...");  }  private String color;  public abstract double calPerimeter();  public abstract String getType();  public Shape(){}public Shape(String color){  System.out.println("执行Shape的构造器...");  this.color = color;}public void setColor(String color){  this.color = color;}public String getColor(){  return color;}
Triangle.javapackage code.shape;import code.shape.*;public class Triangle extends Shape{  private double a;  private double b;  private double c;  public Triangle(String color,double a,double b,double c){    super(color);    this.setSides(a,b,c);  }  public void setSides(double a,double b,double c){    if(a>=b+c||b>=a+c||c>a+b){      System.out.println("三角形两边之和必须大于等于第三边");      return;    }    this.a = a;    this.b = b;    this.c = c;  }  public double calPerimeter(){    return a+b+c;  }  public String getType(){    return "三角形";  }}
Circle.javapackage code.shape;import java.lang.Math;import code.shape.*;public class Circle extends Shape{  private double radius;  public Circle(String color,double radius){    super(color);    this.radius = radius;  }  public void setRadius(double radius){    this.radius = radius;  }  public double calPerimeter(){    return 2*Math.PI*radius;  }  public String getType(){    return getColor() + "圆形";  }  public static void main(String []args){    Shape s1 = new Triangle("黑色",3,4,5);    Shape s2 = new Circle("红色",4);    System.out.println(s1.getType());    System.out.println(s1.calPerimeter());    System.out.println(s2.getType());    System.out.println(s2.calPerimeter());  }}

执行Shape的初始化块…
执行Shape的构造器…
执行Shape的初始化块…
执行Shape的构造器…
三角形
12.0
红色圆形
25.132741228718345

  • static和abstract并不是绝对互斥,static和abstract不能同时修饰某个方法,但可以同时修饰内部类。

  • abstract方法不能定义为private访问权限。

  • abstract和private不能同时修饰方法。

2、 抽象类的作用
从语义的角度来看,抽象类是从多个具体类中抽象出来的父类,它具有更高层次的抽象。

抽象类体现的就是一种模板模式的设计。

在上述Shape,Circle和Triangle三个类使用了模板模式,父类的普通方法依赖于一个抽象方法,而抽象方法则推迟到子类中实现。

SpeedMeter.javapackage code.speed;public abstract class SpeedMeter{  private double turnRate;  public SpeedMeter(){}  public void setTurnRate(double turnRate){    this.turnRate = turnRate;  }  public abstract double getRadius();  public double getSpeed(){    return java.lang.Math.PI * 2 *getRadius() * turnRate;  }}
carSpeedMeter.javapackage code.speed;public class carSpeedMeter extends SpeedMeter{  public double getRadius(){    return 0.28;  }  public static void main(String[] args){    SpeedMeter sm = new carSpeedMeter();    sm.setTurnRate(15);    System.out.println(sm.getSpeed());  }}

26.389378290154266

规则:

  • 抽象父类可以只定义需要使用的某些方法,把不能实现的部分方法抽象成抽象方法,留给其子类去实现。

  • 父类中可能包含需要调用其他系列方法的方法,这些被调方法既可以由父类实现,也可以由其子类实现。

  • 父类里提供的方法只是定义了一个通用算法,其实现也许并不完全由自身实现,而必须依赖于其子类的辅助。

十五、接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

接口与类相似点:

  • 一个接口可以有多个方法。
  • 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在 .class 结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
  • 接口与类的区别:
  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法。
  • 接口不能包含成员变量,除了 static 和 final 变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承。
  • 接口特性
  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

抽象类和接口的区别

  • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  • 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

十六、内部类
内部类(nested classes),面向对象程序设计中,可以在一个类的内部定义另一个类。嵌套类分为两种,即静态嵌套类和非静态嵌套类。静态嵌套类使用很少,最重要的是非静态嵌套类,也即是被称作为内部类(inner)。内部类是JAVA语言的主要附加部分。内部类几乎可以处于一个类内部任何位置,可以与实例变量处于同一级,或处于方法之内,甚至是一个表达式的一部分。

十七、枚举类

十八、对象与垃圾回收
Java的垃圾回收是Java语言的重要功能之一。当程序创建对象、数组等引用类型实体时,系统都会在堆内存中为之分配一块内存区,对象就保存在这块内存中,当这块内存不在被任何引用变量引用时,这块内存就变成垃圾,等待垃圾回收机制进行回收。垃圾回收机制具有如下特征:

  • 垃圾回收机制只负责回收对内存中的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源)。
  • 程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行。当对象永久性地失去引用后,系统就会在合适的时候回收它所占的内存。
  • 在垃圾回收机制回收任何对象之前,总是先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新应用该对象),从而导致垃圾回收机制取消回收。

对象在内存中的状态
当一个对象在对内存中运行时,根据它被引用变量所引用的状态,可以把它所处的状态分成如下三种:

  • 可达状态:
    • 当一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序可通过引用变量来调用该对象的实例变量和方法。
  • 可恢复状态:
    • 如果程序中某个对象不在有任何引用变量引用它,它就进入了可恢复状态。在这个中状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有可恢复状态对象的finalize()方法进行资源清理。
    • 如果系统在调用finalize()方法时重新让一个引用变量引用该对象,则这个对象会再次变为可达状态;否则该对象将进入不可达状态。
  • 不可达状态
    • 当对象与所有引用变量的关联都被切断,且系统已经调用所有对象的finalize()方法后依然没有使该对象变成可达状态,那么这个对象将永久性地逝去引用,最后变成不可达状态。
    • 只有当一个对象处于不可达状态时,系统才会真正回收该对象所占用的资源。

强制垃圾回收
当一个对象逝去引用后,系统何时调用它的finalize()方法对它进行资源清理,何时它会变成不可达状态,系统何时回收它所占有的内存,对于程序完全透明。程序只能控制一个对象何时不再被任何引用变量引用,决不能控制它何时被回收。

程序无法精确控制Java垃圾回收的时机,但依然可以强制系统进行垃圾回收——这种强制只是通过通知系统进行垃圾回收,但系统是否进行垃圾回收依然不确定。大部分时候,程序强制系统垃圾回收后总会有一些效果。强制系统垃圾回收有如下两种方式:

  • 调用System类的gc()静态方法:System.gc()。
  • 调用Runtimed对象的gc()实例方法:Runtime.getRuntime().gc()。

finalize 方法
在垃圾回收机制回收某个对象所占用的内存之前,通常要求程序调用适当的方法来清理资源,在没有明确指定清理资源的情况下,Java提供了默认机制来清理该对象的资源,这个机制就是finalize()方法。该方法是定义在Object类里的实例方法。

当finalize()方法返回后,对象消失,垃圾回收机制开始执行。方法原型中的throws Throwable表示它可以抛出任何类型的异常。

任何Java类都可以重写Object类的finalize()方法,在该方法中清理该对象所占用的资源。如果程序终止之前始终没有进行垃圾回收,则不会调用失去引用对象的finalize()方法来清理资源。垃圾回收机制何时调用对象的finalize()方法是透明的,只有当程序认为需要更多额外内存时,垃圾回收机制才会进行垃圾回收。因此,完全有可能出现这样一种情形:某个失去引用的对象只占用了少量内存,而且系统没有产生严重的内存需求,因此垃圾回收机制并没有试图回收该对象所占用的资源,所以该对象的finalize()方法也不会得到调用。

finalize()方法具有如下4个特点:

  • 永远不要主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用。
  • finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会被执行的方法。
  • 当JVM执行可恢复对象的finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态。
  • 当JVM执行finalize()方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行。

十九、修饰符的适用范围
Java语言提供了很多修饰符,主要分为以下两类:

  • 访问修饰符;
  • 非访问修饰符

修饰符用来定义类、方法或者变量,通常放在语句的最前端。我们通过下面的例子来说明:

public class className {   // ...}private boolean myFlag;static final double weeks = 9.5;protected static final int BOXWIDTH = 42;public static void main(String[] arguments) {   // 方法体}

访问控制修饰符
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Javav支持 4 种不同的访问权限。

  • default (即缺省,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。

我们可以可以通过以下表来说明访问权限:

修饰符 当前类 同一包内 子孙类 其他包 public Y Y Y Y protected Y Y Y N default Y Y N N private Y N N N

默认访问修饰符-不使用任何关键字

使用默认访问修饰符声明的变量和方法,对同一个包内的类是可见的。接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public。
如下例所示,变量和方法的声明可以不使用任何修饰符。

实例

String version = "1.5.1";boolean processOrder() {   return true;}

私有访问修饰符-private

私有访问修饰符是最严格的访问级别,所以被声明为 private 的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为 private。

声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。

Private 访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。

下面的类使用了私有访问修饰符:

public class Logger {   private String format;   public String getFormat() {      return this.format;   }   public void setFormat(String format) {      this.format = format;   }}

实例中,Logger 类中的 format 变量为私有变量,所以其他类不能直接得到和设置该变量的值。为了使其他类能够操作该变量,定义了两个 public 方法:getFormat() (返回 format的值)和 setFormat(String)(设置 format 的值)

公有访问修饰符-public

被声明为 public 的类、方法、构造方法和接口能够被任何其他类访问。
如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类继承。

以下函数使用了公有访问控制:

public static void main(String[] arguments) {   // ...}

Java 程序的 main() 方法必须设置成公有的,否则,Java 解释器将不能运行该类。

受保护的访问修饰符-protected

被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问,也能够被不同包中的子类访问。

protected 访问修饰符不能修饰类和接口,方法和成员变量能够声明为 protected,但是接口的成员变量和成员方法不能声明为 protected。

子类能访问 protected 修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。

下面的父类使用了 protected 访问修饰符,子类重写了父类的

openSpeaker() 方法。class AudioPlayer {   protected boolean openSpeaker(Speaker sp) {      // 实现细节   }}class StreamingAudioPlayer extends AudioPlayer {   protected boolean openSpeaker(Speaker sp) {      // 实现细节   }}

如果把 openSpeaker() 方法声明为 private,那么除了 AudioPlayer 之外的类将不能访问该方法。

如果把 openSpeaker() 声明为 public,那么所有的类都能够访问该方法。

如果我们只想让该方法对其所在类的子类可见,则将该方法声明为 protected。

访问控制和继承

请注意以下方法继承的规则:

  • 父类中声明为 public 的方法在子类中也必须为 public。
  • 父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。
  • 父类中声明为 private 的方法,不能够被继承。

非访问修饰符
为了实现一些其他的功能,Java 也提供了许多非访问修饰符。

  • static 修饰符,用来修饰类方法和类变量。
  • final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
  • abstract 修饰符,用来创建抽象类和抽象方法。
  • synchronized 和 volatile 修饰符,主要用于线程的编程。

static 修饰符
静态变量:
static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。

静态方法:
static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。

对类变量和方法的访问可以直接使用 classname.variablename 和 classname.methodname 的方式访问。

如下例所示,static修饰符用来创建类方法和类变量。

public class InstanceCounter {   private static int numInstances = 0;   protected static int getCount() {      return numInstances;   }   private static void addInstance() {      numInstances++;   }   InstanceCounter() {      InstanceCounter.addInstance();   }   public static void main(String[] arguments) {      System.out.println("Starting with " +      InstanceCounter.getCount() + " instances");      for (int i = 0; i < 500; ++i){         new InstanceCounter();          }      System.out.println("Created " +      InstanceCounter.getCount() + " instances");   }}

以上实例运行编辑结果如下:

Started with 0 instances
Created 500 instances

final 修饰符

  • final 变量:

    • final 变量能被显式地初始化并且只能初始化一次。被声明为 final 的对象的引用不能指向不同的对象。但是 final 对象里的数据可以被改变。也就是说 final 对象的引用不能改变,但是里面的值可以改变。

    • final 修饰符通常和 static 修饰符一起使用来创建类常量。

实例

public class Test{  final int value = 10;  // 下面是声明常量的实例  public static final int BOXWIDTH = 6;  static final String TITLE = "Manager";  public void changeValue(){     value = 12; //将输出一个错误  }}
  • final 方法

    • 类中的 final 方法可以被子类继承,但是不能被子类修改。
    • 声明 final 方法的主要目的是防止该方法的内容被修改。

如下所示,使用 final 修饰符声明方法。

public class Test{    public final void changeName(){       // 方法体    }}
  • final 类
    f - inal 类不能被继承,没有类能够继承 final 类的任何特性。
    实例
public final class Test {   // 类体}
  • abstract 修饰符
    • 抽象类:
      • 抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
      • 一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。
        抽象类可以包含抽象方法和非抽象方法。

实例

abstract class Caravan{   private double price;   private String model;   private String year;   public abstract void goFast(); //抽象方法   public abstract void changeColor();}
  • 抽象方法
    • 抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。
    • 抽象方法不能被声明成 final 和 static。
    • 任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
    • 如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。
    • 抽象方法的声明以分号结尾,例如:public abstract sample();。

实例

public abstract class SuperClass{    abstract void m(); //抽象方法}class SubClass extends SuperClass{     //实现抽象方法      void m(){          .........      }}
  • synchronized 修饰符
    • synchronized 关键字声明的方法同一时间只能被一个线程访问。
    • synchronized 修饰符可以应用于四个访问修饰符。

实例

public synchronized void showDetails(){.......}
  • transient 修饰符
    • 序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
    • 该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。

实例

public transient int limit = 55;   // 不会持久化public int b; // 持久化
  • volatile 修饰符
    • volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
    • 一个 volatile 对象引用可能是 null。

实例

public class MyRunnable implements Runnable{    private volatile boolean active;    public void run()    {        active = true;        while (active) // 第一行        {            // 代码        }    }    public void stop()    {        active = false; // 第二行    }}

通常情况下,在一个线程调用 run() 方法(在 Runnable 开启的线程),在另一个线程调用 stop() 方法。 如果 第一行 中缓冲区的 active 值被使用,那么在 第二行 的 active 值为 false 时循环不会停止。
但是以上代码中我们使用了 volatile 修饰 active,所以该循环会停止。

原创粉丝点击