static修饰符可以用来修饰类的成员变量、成员方法和代码块。
用static修饰的程序代码表示静态代码块,当Java虚似机加载类时,就会执行该代码块;(且静态代码块,貌似只会执行一次。单例模式的实现方法之一)
被static所修饰的成员变量和成员方法表明归某个类所有,它不依赖于类的特定实例,被类的所有实例共享。
成员变量:定义在类里面、方法外面的变量, 分两种:
静态变量;形式和实例变量类似,在实例变量前面加static关键字;(例如: public static Integer number)
static变量和实例变量的区别:
static变量对于每个类而言在内存中只有一个,能被类的所有实例所共享;实例变量对于每个类的每个实例都有一份,它们之间互不影响;
public class Counter {
public int count1 = 0;
public static int count2 = 0;
public static void main(String[] args) {
Counter counterA = new Counter();
Counter counterB = new Counter();
counterA.count1++;
counterA.count2++;
counterB.count1++;
counterB.count2++;
}
}
// 练习:统计一个类创建实例的个数;
// 解答:设置一个静态变量,每创建一个实例。就将Count++;
成员方法分为静态方法和实例方法。用static修饰的方法叫静态方法,或类方法。静态方法也和静态变量一样,不需要创建类的实例,可以直接通过类名来访问。
public class Sample1 {
public static int add(int x, int y) {
return x + y;
}
}
public class Sample2 {
public void method() {
int result = Sample1.add(1, 2);
System.out.println("result= " + result);
}
}
static方法可以直接访问所属类的实例变量和实例方法,直接访问所属类的静态变量和静态方法;
父类的静态方法不能被子类覆为非静态方法。以下代码编译出错。
public class Base {
public static void method() {
}
}
public class Sub extends Base {
public void method() {
}// 编译出错
}
类中可以包含静态代码块,它不存于任何方法中。在Java虚拟机中加载类时会执行这些静态代码块。如果类中包含多个静态代码块,那么Java虚拟机将按照它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次。
(思考:什么时候JVM对一个类进行类加载? 答: 在进行New操作,也就是分配空间的时候,会进行类加载。)
package com.us.demo.map;
public class Sample {
static int i = 5;
static {//第一个静态代码块
System.out.println("First Static code i="+i++);
}
static {//第二个静态代码块
System.out.println("Second Static code i="+i++);
}
public static void main(String[] args) {
Sample s1 = new Sample();
Sample s2 = new Sample();
System.out.println("At last, i= "+i);
}
}
// 5 6 7
//First Static code i=5
//Second Static code i=6
//At last, i= 7
类的构造方法用于初始化类的实例,而类的静态代码块则可用于初始化类,给类的静态变量赋初始值。
静态代码块与静态方法一样,也不能直接访问类的实例变量和实例方法,而必须通过实例的引用来访问它们。
new一个对象的时候JVM都做了那些事情:
//Student s = new Student();
package com.us.demo.map;
public class Test {
public static void main(String[] args) {
A a = new B();
}
}
class A {
protected String name = "lisi";
static String a = "Fathera";
int numberA = 0;
static {
System.out.println("A静态代码块: " + a);
}
public A() {
System.out.println("父类构造器A");
System.out.println("父类构造器A中调用test方法开始,由于子类重写过test方法所以这里执行子类的test方法");
test();
numberA = 1;
System.out.println("numberA:" + numberA);
System.out.println("父类构造器A中调用test方法结束");
}
public void test() {
}
}
class B extends A {
static String b = "SonB";
static {
System.out.println("A静态代码块: " + b);
}
private String name = "tom";
{
System.out.println("子类匿名代码块中:" + name);
}
public B() {
System.out.println("子类构造器B");
}
public void test() {
System.out.println("test方法中:this.name=" + this.name);
System.out.println("test方法中:super.name=" + super.name);
}
}
结果:
A静态代码块: Fathera
A静态代码块: SonB
父类构造器A
父类构造器A中调用test方法开始,由于子类重写过test方法所以这里执行子类的test方法
test方法中:this.name=null
test方法中:super.name=lisi
numberA:1
父类构造器A中调用test方法结束
子类匿名代码块中:tom
子类构造器B
结论:
练习例子:StaticTest.java StaticTest2.java
静态导入也是JDK5.0引入的新特性。
要使用静态成员(方法和变量)我们必须给出提供这个静态成员的类。使用静态导入可以使被导入类的静态变量和静态方法在当前类中可以直接使用,使用这些静态成员无需再在前面写上他们所属的类名。
import static java.lang.Math.random;
import static java.lang.Math.PI;;
public class Test {
public static void main(String[] args) {
// 之前是需要Math.random()调用的
System.out.println(random());
System.out.println(PI);
}
}
final具有”不可改变的”含义,它可以修饰非抽象类、非抽象成员方法和变量。
用final修饰的变量表示常量,只能被赋一次值;
final不能用来修饰构造方法,因为”方法覆盖”这一概念仅适用于类的成员方法,而不适用于类的构造方法,父类的构造方法和子类的构造方法之间不存在覆盖关系. 因此用final修饰构造方法是无意义的。
??(父类中用private修饰的方法不能被子类的方法覆盖,因此private类型的方法默认是final类型的。)
继承关系的弱点是打破封装,子类能够访问父类的方法,而且能以方法覆盖的方式修改实现细节。在以下情况下,可以考虑把类定义为final类型,使得这个类不能被继承。
在创建对象模型时,确信这个类不会再被扩展;
例如JDK中java.lang.String类被定义为final类型;public final class String
某些情况下,出于安全原因,父类不允许子类覆盖某个方法, 此时可以把这个方法声明为final类型。例如在java.lang.Object类中,getClass()方法为final类型。
final修饰的属性(成员变量)赋值的位置:
非静态的成员变量
静态的成员变量
常量通常这样声明 public final static String variable
static代码块
final可以修饰静态变量、实例变量、局部变量;
package com.us.demo.finalTest;
class Sample {
private final int var1 = 1;
public Sample() {
var1 = 2; //编译出错,不允许改变var1实例变量的值;
}
public void method(final int param) {
final int var2 = 1;
var2++; //编译出错,不允许改变var2局部常量的值
param++; //编译出错,不允许改变final类型参数的值;
}
}
public class Test2 {
}
package com.us.demo.finalTest;
class Sample2 {
final int var1; // 定义var1实例常量
final int var2 = 0; // 定义并初始化var2实例常量
Sample2() {
var1 = 1; // 初始化var1实例常量
}
Sample2(int x) {
var1 = x; // 初始化var1实例常量
}
}
public class Test {
public static void main(String []args){
Sample2 sm2 = new Sample2();
System.out.println(sm2.var1);
}
}
注意: final 对象赋值主要可以在2处: 声明初始化时/构造函数内。
练习 FinalTest.java
可用来修饰类和成员方法。
语法规则;
抽象类不允许实例化:思考原因? 因为其中方法体只有名称,但是无方法体。
package com.us.demo.abstractTest;
//定义一个抽象类
abstract class student{
int a;
// abstract int abstractA;// 报错
//抽象方法
public abstract void study();
//非抽象方法
public void work(){
System.out.println("努力学习");
}
}
class goodstudent extends student{
int abstractA = 1;
//必须要实现抽象方法,否则该类依然是个抽象类
public void study(){
System.out.println("好学生不学习");
}
}
public class Test {
public static void main(String[] args) {
goodstudent s=new goodstudent();
//调用实现的方法
s.study();
//调用从抽象类中继承的非抽象方法
s.work();
}
}
练习 AbstractTest.java
接口使用的目的:解决多重继承问题;例如Fish类继承Animal类,表明Fish是一种动物,但鱼同样也是一种食物,如何表示这种关系呢? 由于Java语言不支持一个类有多个直接的父类,因此无法用继承关系来描述鱼既是一种食物,又是一种动物,为了解决这一问题,Java语言引入接口类型,简称接口。一个类只能有一个直接的父类,但是可以实现多个接口。 采用这种方式,Java语言对多继承提供了有力的支持。
抽象类抽象到极致就是接口,抽象类可存在有方法体的方法,接口中的方法全部为抽象方法;
public interface A {
// private void method3(); // 不合法
// Illegal modifier for the interface method method3; only public & abstract are permitted
void method1(); // 合法,默认为public、abstract类型
public abstract void method2();// 合法,显示声明为public、abstract类型
}
public interface A {
int CONST = 1; // 合法,CONST默认为public, static, final类型
public static final int OPAQUE = 1; // 合法,显示声明为public static final 类型
}
简而言之,抽象类是 可能包含抽象方法的类;接口是 只能包含抽象方法和常量的一种结构类型。
练习:InterfaceTest.java InterfaceTest2.java
面向对象的基本思想之一是封装实现细节并且公开方法。Java语言采用访问控制修饰符来控制类及类的方法和变量的访问权限,从而只向使用者暴露方法,但隐藏实现细节。访问控制分4种级别。
访问级别 | 访问控制修饰符 | 同类 | 同包 | 子类 | 不同的包 |
---|---|---|---|---|---|
公开 | public | y | y | y | y |
受保护 | protected | y | y | y | |
默认 | default | y | y | ||
私有 | private | y |
成员变量、成员方法和构造方法可以处于4个访问级别中的一个;
顶层类只可以处于公开或默认访问级别;
注意:protected和default都有包访问权限(同包下可以访问)
练习:access目录下的测试代码
equals(Object o): Object类中的方法,所以,在每一个java类中,都会有这个方法,因为每一个java类都是直接或者间接的Object类的子类,会继承到这个方法。
如果自己所写的类中已经重写了equals方法,那么就安装用户自定义的方式来比较俩个对象是否相等,如果没有重写过equal方法,那么会调用父类(Object)中的equals方法进行比较,也就是比较地址值。
注意:equals(Object o)方法只能是一个对象来调用,然后参数也是要传一个对象的
所以下面是错误的写法:
int a = 1;
a.equals(1);
因为基本数据类型不是算是对象,不能调用方法
如果是基本数据类型那么就用 == 比较
如果是引用类型的话,想按照自己的方式去比较,就要重写这个类中的equals方法, 如果没有重写,那么equals和==比较的效果是一样的,都是比较引用的地址值
如果是比较字符串,那么直接用equals就可以了,因为String类里面已经重写了equals方法,比较的时候字符串的内容,而不是引用的地址值了
当前用一个引用指向一个对象的时候,比如:Student s = new Student(),然后如果直接打印这个引用s,其实是调用了s.toString()方法,然后就会把这个引用里面的存放的堆区对象的地址值显示出来所以我们会常常在类中重写这个toString()方法,然后让这个类的引用按照我们要求来返回内容。
例如:Person p = new Student()
//会输出:class com.briup.chap06.Student
//说明这个引用p在运行时指向的是Student这个类的对象
//注意这个引用p的类型是Person的(多态)
System.out.println(p.getClass());
// 所以返回的是指向数据空间内 数据结构的 数据类型。
基本数据类型 | 包装数据类型 |
---|---|
boolean | Boolean |
byte | Byte |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
需要注意的是,有一句话说”Java内一切皆对象”。但是,基本数据类型不是对象。
基本数据类型的数据存在栈中,计算更加快速,比较时比较的是数值。
对象数据类型存在堆中,比较时比较的是地址。
如果,需要以其它条件进行比较的话,需要重写compare函数(Comparable接口),或者使用Comparactor比较器。
[1] JAVA 堆栈 堆 方法区 静态区 final static 内存分配 详解
http://blog.csdn.net/peterwin1987/article/details/7571808
[2] Java对象的创建 —— new之后JVM都做了什么?
http://blog.csdn.net/rainnnbow/article/details/52149586
[3] Java类的加载顺序,父类和子类初始化的顺序和重写所遇到的上塑造型
http://blog.csdn.net/u012468263/article/details/50642767
[4] Java中父类和子类初始化顺序
http://blog.csdn.net/yuxin6866/article/details/53107578
[5] java抽象类和抽象方法之间的关系
http://www.cnblogs.com/zxxiaoxia/p/4175768.html