tatic修饰的成员不能访问没有static修饰的成员。static是一个特殊的关键字,它可用于修饰方法、属性等成员。Static修饰的成员表明它是属于这个类共有的,而不是属于该类的单个实例,因此通常把static修饰的属性和方法称为类属性、类方法。静态成员不能防伪非静态成员。
对于static修饰的方法,可以使用类直接调用该方法,如果static修饰的方法中使用this关键字,则这个关键字无法指向合适的对象,所以static修饰的方法中不能使用this引用。由于static修饰的方法不能使用this引用,所以static修饰的方法不能访问未使用static修饰的普通成员
static修饰的方法和属性,既可以通过类来调用,也可以通过实例来调用,没有使用static修饰的普通方法和属性,则只能通过实例调用。
java提供了final关键字来修饰变量、方法和类,系统不允许为final变量重新赋值,子类不允许覆盖父类的final方法,final类不能派生之类。
当abstract修饰类时,表明这个类只能被继承,当abstract修饰方法时,表明这个方法必须由子类提供实现,而final修饰的类不能被继承,final修饰的方法不能被重写。因此final与abstract永远不能同时使用。
除此之外,当使用static来修饰一个方法时,表明这个方法属于当前类,即该方法可以通过类来调用,如果该方法被定义成抽象方法,则将导致通过该类来调用该方法是出现错误,因此,static和abstract不能同时修饰某个方法,即没有所谓的类抽象方法。
abstract关键字修饰的方法必须被其子类重写才有意义,否则这个方法将永远不会有方法体,因此abstract方法不能定义为private访问权限,即private和abstract不能同时使用。
无论static还是final字段,都只能存储一个数据,且不得改变。对于基本数据类型,final会将值变成一个常数;但对于对象句柄,final会将句柄编程一个常数,进行声明时,必须将句柄初始化到一个具体的对象,而且永远不能将句柄变成指向另一个对象
final关键字
在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量).
1.修饰类
当用final修饰一个类时,表明这个类不能被继承。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。
2.修饰方法
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final的。
注:类的private方法会隐式地被指定为final方法。
3.修饰变量
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
4.类的final变量和普通变量有什么区别?
当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。
5.被final修饰的引用变量指向的对象内容可变吗?
变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的
相同点:
1.接口和抽象类都不能呗实例化,都位于继承树的顶端,用于被其他类实现和继承
2.接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
不同点:
1.接口里只能包含抽象方法,不包含已经提供实现的方法;抽象类则完全可以包含普通方法。
2.接口里不能定义静态方法;抽象类里可以定义静态方法
3.接口里只能定义静态常量,不能定义普通属性;抽象类里既可以定义普通属性,也可以定义静态常量属性
4.接口不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而让其子类调用这些构造器来完成属于抽象类的初始化操作
5.接口里不能包含初始化模块,但抽象类则完全可以包含初始化模块
6.一个类最多只能由一个父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补java单继承的不足
1.内部类提供更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
2.内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以相互访问,但外部类不能访问内部类的实现细节,例如内部类的属性。
java不允许在非静态内部类中定义静态成员,非静态内部类中不能有静态方法、静态属性、静态初始化模块。
局部内部类不能再外部类以外的地方使用,所有局部成员都不能使用static修饰。不仅如此,因为局部成员的作用域是所在方法,其他程序单元永远也不可能访问另一个方法中的局部成员,所以所有局部成员都不能使用访问控制符修饰。
匿名内部类适合创建那种只需要一次使用的类,不能重复使用。匿名内部类必须继承一个父类或实现一个接口,但最多只能继承一个父类或实现一个接口。
1.匿名内部类不能是抽象类,因为系统在创建匿名内部类的时候,会立即创建匿名内部类的对象。
2.匿名内部类不能定义构造器,因为匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义实例初始化块,通过实例初始化来完成构造器需要完成的事情。
垃圾回收机制
1.垃圾回收机制只负责回收堆内存中对象,不会回收任何物理资源
2.程序无法精确控制垃圾回收的运行,垃圾回收会在合适时候进行。当对象永久性地失去引用后,系统就会在合适时候回收它所占的内存。
3.垃圾回收机制回收任何对象之前,总会先调用它的finalize方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。
finalize方法有如下四个特点:
1.永远不要主动调用某个对象的finalize方法,该方法应该交给垃圾回收机制调用
2.finalize方法何时被调用,是否被调用具有不确定性。不要把finalize方法当成一定会被执行的方法
3.当JVM执行去活对象的finalize方法时,可能使该对象或系统中其他对象重新变成激活状态。
4.当JVM执行finalize方法时出现了异常,垃圾回收机制不会报告异常,程序继续执行。
1.继承不同。
public class Hashtable extends Dictionaryimplements Map
public class HashMap extends AbstractMap implements Map
2.Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
3.Hashtable中,key和value都不允许出现null值。
在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
4.两个遍历方式的内部实现上不同。
Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
5.哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
6.Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
所谓泛型:就是允许在定义类,接口时指定类型形参,这个类型形参将在申明变量、创建对象时确定。
当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。例如为Apple
类在继承时会用到this和super,其使用如下:
this
this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。this的用法在java中大体可以分为3种:
1.普通的直接引用
这种就不用讲了,this相当于是指向当前对象本身。
2.形参与成员名字重名,用this来区分:
class Person {
private int age = 10;
public Person(){
System.out.println("初始化年龄:"+age);
}
public int GetAge(int age){
this.age = age;
return this.age;
}
}
public class test1 {
public static void main(String[] args) {
Person Harry = new Person();
System.out.println("Harry's age is "+Harry.GetAge(12));
}
}
运行结果:
初始化年龄:10
Harry's age is 12
可以看到,这里age是GetAge成员方法的形参,this.age是Person类的成员变量。
3.引用构造函数
这个和super放在一起讲,见下面。
super
super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。super也有三种用法:
1.普通的直接引用
与this类似,super相当于是指向当前对象的父类,这样就可以用super.xxx来引用父类的成员。
2.子类中的成员变量或方法与父类中的成员变量或方法同名
class Country {
String name;
void value() {
name = "China";
}
}
class City extends Country {
String name;
void value() {
name = "Shanghai";
super.value(); //调用父类的方法
System.out.println(name);
System.out.println(super.name);
}
public static void main(String[] args) {
City c=new City();
c.value();
}
}
运行结果:
Shanghai
China
可以看到,这里既调用了父类的方法,也调用了父类的变量。若不调用父类方法value(),只调用父类变量name的话,则父类name值为默认值null。
3.引用构造函数
super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)
class Person {
public static void prt(String s) {
System.out.println(s);
}
Person() {
prt("父类·无参数构造方法:"+"A Person.");
}//构造方法(1)
Person(String name) {
prt("父类·含一个参数的构造方法:"+"A person's name is " + name);
}//构造方法(2)
}
public class Chinese extends Person {
Chinese() {
super(); // 调用父类构造方法(1)
prt("子类·调用父类”无参数构造方法“: "+"A chinese coder.");
}
Chinese(String name) {
super(name);// 调用父类具有相同形参的构造方法(2)
prt("子类·调用父类”含一个参数的构造方法“: "+"his name is " + name);
}
Chinese(String name, int age) {
this(name);// 调用具有相同形参的构造方法(3)
prt("子类:调用子类具有相同形参的构造方法:his age is " + age);
}
public static void main(String[] args) {
Chinese cn = new Chinese();
cn = new Chinese("codersai");
cn = new Chinese("codersai", 18);
}
}
运行结果:
父类·无参数构造方法: APerson.
子类·调用父类”无参数构造方法“: A chinese coder.
父类·含一个参数的构造方法:A person's name is codersai
子类·调用父类”含一个参数的构造方法“: his name is codersai
父类·含一个参数的构造方法:A person's name is codersai
子类·调用父类”含一个参数的构造方法“: his name is codersai
子类:调用子类具有相同形参的构造方法:his age is 18
从本例可以看到,可以用super和this分别调用父类的构造方法和本类中其他形式的构造方法。
例子中Chinese类第三种构造方法调用的是本类中第二种构造方法,而第二种构造方法是调用父类的,因此也要先调用父类的构造方法,再调用本类中第二种,最后是重写第三种构造方法。
super和this的异同:
super(参数):调用基类中的某一个构造函数(应该为构造函数中的第一条语句)
this(参数):调用本类中另一种形成的构造函数(应该为构造函数中的第一条语句)
super:它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
super()和this()类似,区别是,super()从子类中调用父类的构造方法,this()在同一类内调用其它方法。
super()和this()均需放在构造方法内第一行。
尽管可以用this调用一个构造器,但却不能调用两个。
this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
补充:
使用this调用另一个重载的构造器只能在构造器中使用,而且必须作为构造器执行体的第一条语句,使用this调用重载的构造器时,系统会根据this后括号里的实参来调用形参列表与之对应的构造器。
在一个构造器中调用另一个重载的构造器使用this调用来实现,在子类构造器中调用父类构造器使用super调用来实现。
不管是否使用super调用来执行父类构造器的初始化代码,子类构造器总会调用父类的构造器一次,子类构造器调用父类构造器分如下几种情况:
l 子类构造器执行体的第一行使用super显式调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器。
l 子类构造器执行体的第一行代码使用this显式调用本类中重载的构造器,系统将根据this调用里传入的实参列表调用本类另一个构造器,执行本类中另一个构造器时即会调用父类的构造器
l 子类构造器执行体中既没有super调用,也没有this调用,系统将会在执行子类构造器之前,隐式调用父类无参数构造器
不管上面哪种情况,当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行,不仅如此,执行父类构造器时,系统会再次上溯执行其父类的构造器。。。以此类推,创建任何Java对象,最先执行的总是java.lang.Object类的构造器。
v Java引用变量有两个类型:一个是编译时的类型,一个是运行时的类型,编译时的类型由声明该变量时使用的类型决定,运行时的类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就会出现多态。
v 编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际引用对象确实包含该方法,如果需要让这个引用变量来调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转换需要借助于类型转换运算符。
v 强制类型转换不是万能的,当进行强制类型转换时需要注意:
l 基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整数型、字符型和浮点型。但数值型不能喝布尔型之间进行类型转换。
l 引用类型之间的转换只能把一个父类变量转换成子类类型,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则必须这个对象实际上是子类实例才行(即编译时类型为父类类型,而运行时类型为子类类型),否则将在运行时引发ClassCastException异常。
成员变量:作为类的成员而存在,直接存在于类中。
局部变量:作为方法或语句块的成员而存在,存在于方法的参数列表和方法定义中。
局部变量在使用前必须被程序员主动的初始化,和此形成对比,系统中的成员变量则会被系统提供一个默认的初始值。所以在语法上,类的成员变量能够定义后直接使用,而局部变量在定义后先要赋初值,然后才能使用。
java中成员变量和局部变量的区别
1.成员变量在类中,局部变量在方法中。
2.声明成员变量时可以不初始化(被final修饰且没有static的必须显式赋值),而局部变量必须手动初始化。
3.成员变量可以被public,protect,private,static等修饰符修饰,而局部变量不能被控制修饰符及static修饰;两者都可以定义成final型。
4.成员变量存储在堆,局部变量存储在栈。
5.存在时间不同。
在Java中,有两种初始化块:静态初始化块和非静态初始化块。它们都是定义在类中,用大括号{}括起来,静态代码块在大括号外还要加上static关键字。
非静态初始化块(构造代码块):
作用:给对象进行初始化。对象一建立就运行,且优先于构造函数的运行。
与构造函数的区别:非静态初始化块给所有对象进行统一初始化,构造函数只给对应对象初始化。
应用:将所有构造函数共性的东西定义在构造代码块中。
静态初始化块:
作用:给类进行初始化。随着类的加载而执行,且只执行一次
与构造代码块的区别:
1)构造代码块用于初始化对象,每创建一个对象就会被执行一次;静态代码块用于初始化类,随着类的加载而执行,不管创建几个对象,都只执行一次。
2)静态代码块优先于构造代码块的执行
3)都定义在类中,一个带static关键字,一个不带static
构造函数、非静态初始化块、静态代码块都是用于初始化,三者的执行顺序依次是:静态代码块>构造代码块>构造函数。
其实初始化块就是构造器的补充,初始化块是不能接收任何参数的,定义的一些所有对象共有的属性、方法等内容时就可以用初始化块初始化了。
静态初始化块的作用就是当JVM在装载类时,你想让它做一些事情,那么,就可以用静态初始化块。这几者的执行顺序是:
(JVM在装载类时)先装载类的静态成员,再执行静态初始化块(同样,当一个类有继承自某类时,则会先装载该父类,那么,父类的装载或执行顺序,也都如句子所述)。
(在创建类的实例时)先执行实例初始化块,再执行构造方法;但对于一棵继承树中,会先调用父类的构造方法,那么其执行顺序也如句子所述。
例子:
Class A{ int n =0;}
那么你创建对象A a=newA();之后就可以使用a.n属性
Class A{
{
int a=0;
}
}
放在块里面n就不是A的成员,就不能使用a.n属性
前者把n作为对象的成员;后者只是实例化一个对象时声明一个局部变量a。有本质区别。
public class Test {
static {
System.out.println("我是静态块");
}
}
当创建Test类的一个对象的时候,比如new Test() ,是这样,首先是类加载,然后才能new对象,静态块在类加载的时候就执行了,这就说明静态块在new对象之前就会执行,而且一个类在第一次被使用的时候 会被加载,然后在整个应用程序的生命周期当中不会再次被加载了,就加载这一次,所以这就说明,静态块就执行一次,不会执行第二遍!
eg2.
class Root
{
static
{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root()
{
System.out.println("Root的无参数构造器");
}
}
class Mid extends Root
{
static
{
System.out.println("Mid 的静态初始化块");
}
{
System.out.println("Mid 的普通初始化块");
}
public Mid()
{
System.out.println("Mid 的无参数的构造器");
}
public Mid(String msg)
{
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid 的带参数的构造器,其参数值:"+msg);
}
}
class Leaf extends Mid
{
static{
System.out.println("Leaf 的静态初始化块");
}
{
System.out.println("Leaf 的普通初始化块");
}
public Leaf()
{
//通过super调用父类中有一个字符串参数的构造器
super("权威指南");
System.out.println("执行Leaf的构造器");
}
}
public class Test
{
public static void main(String[] args)
{
new Leaf();
new Leaf();
}
}
执行结果:
Root的静态初始化块
Mid的静态初始化
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:权威指南
Leaf的普通初始化块
执行Leaf的构造器
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:权威指南
Leaf的普通初始化块
执行Leaf的构造器
分析:创建了两次Leaf对象。第一次创建一个Leaf对象时,因为系统还不存在Leaf类,因此需要先加载并初始化Leaf类,一旦Leaf类初始化成功后,Leaf类在该虚拟机里巷一直存在,因此当第二次创建Leaf实例时无须再次对Leaf类进行初始化。
String:是对象不是原始类型,为不可变对象,一旦被创建,就不能修改它的值。对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去,String是final类,即不能被继承。
StringBuffer:是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象它只能通过构造函数来建立, 对象被建立以后,在内存中就会分配内存空间,并初始保存一个null.向StringBuffer
中赋值的时候必须通过它的append方法.
StringBuilder:是一个可变对象,和StringBuffer相比,不是线程安全的,一般用在单个线程操作的时候(这种情况很普遍,所以一般优先选用StringBuilder),速度比StringBuffe快很多。
StringBuilder与StringBuffer共同点
StringBuilder和StringBuffer有公共父类 AbstractStringBuilder(抽象类)。
抽象类与接口的其中一个区别是:抽象类中可以定义一些子类的公共方法,子类只需要增加新的功能,不需要重复写已经存在的方法;而接口中只是对方法的申明和常量的定义。
StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer会在方法上加synchronized关键字,进行同步。
最后,如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer。
归纳如下:
1.在执行速度方面的比较:StringBuilder > StringBuffer
2.StringBuffer与StringBuilder,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,不像String一样创建一些对象进行操作,所以速度就快了。
3.StringBuilder:线程非安全的
StringBuffer:线程安全的
当我们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是速度的原因。
对于三者使用的总结:
1.如果要操作少量的数据用 String
2.单线程操作字符串缓冲区下操作大量数据 StringBuilder
3.多线程操作字符串缓冲区下操作大量数据 StringBuffer
classSample
{
Sample(String s)
{
System.out.println(s);
}
Sample()
{
System.out.println("Sample默认构造函数被调用");
}
}
classTest{
static Sample sam=new Sample("静态成员sam初始化");
Sample sam1=new Sample("sam1成员初始化");
static{
System.out.println("static块执行");
if(sam==null)System.out.println("sam isnull");
sam=new Sample("静态块内初始化sam成员变量");
}
Test()
{
System.out.println("Test默认构造函数被调用");
}
}
//主函数
public static void main(String str[])
{
Test a=new Test();
}
输出结果为:
静态成员sam初始化 -----静态成员初始化
static块执行 -----静态块被执行
静态块内初始化sam成员变量 ----静态块执行
sam1成员初始化 -----普通成员初始化
Test默认构造函数被调用 -----构造函数执行
由此可以得出结论:
a 静态成员变量首先初始化(注意,Static块可以看做一个静态成员,其执行顺序和其在类中申明的顺序有关)
b 普通成员初始化
c 执行构造函数。
对于静态成员(static块可以看成普通的一个静态成员,其并不一定在类初始化时首先执行)和普通成员,其初始化顺序只与其在类定义中的顺序有关,和其他因素无关。
例如下面的例子:
classTest{
static{
System.out.println("static 块 1 执行");
}
static Sample staticSam1=newSample("静态成员staticSam1初始化");
Sample sam1=new Sample("sam1成员初始化");
static Sample staticSam2=newSample("静态成员staticSam2初始化");
static{
System.out.println("static 块 2 执行");
}
Test()
{
System.out.println("Test默认构造函数被调用");
}
Sample sam2=new Sample("sam2成员初始化");
}
则结果为:
static 块 1 执行
静态成员staticSam1初始化
静态成员staticSam2初始化
static 块 2 执行
--------静态成员
sam1成员初始化
sam2成员初始化
--------普通成员
Test默认构造函数被调用
--------构造函数
classTest{
static{
System.out.println("父类static 块1 执行");
}
static Sample staticSam1=newSample("父类 静态成员staticSam1初始化");
Sample sam1=new Sample("父类 sam1成员初始化");
static Sample staticSam2=newSample("父类 静态成员staticSam2初始化");
static{
System.out.println("父类 static 块2 执行");
}
Test()
{
System.out.println("父类 Test默认构造函数被调用");
}
Sample sam2=new Sample("父类 sam2成员初始化");
}
classTestSub extends Test
{
static Sample staticSamSub=newSample("子类 静态成员staticSamSub初始化");
TestSub()
{
System.out.println("子类 TestSub 默认构造函数被调用");
}
Sample sam1=new Sample("子类 sam1成员初始化");
static Sample staticSamSub1=newSample("子类 静态成员staticSamSub1初始化");
static{System.out.println("子类 static 块 执行");}
Sample sam2=new Sample("子类 sam2成员初始化");
}
执行结果:
父类 static 块 1 执行
父类静态成员staticSam1初始化
父类静态成员staticSam2初始化
父类 static 块 2 执行
--------父类静态成员初始化
子类静态成员staticSamSub初始化
子类静态成员staticSamSub1初始化
子类 static 块 执行
-------子类静态成员初始化
父类 sam1成员初始化
父类 sam2成员初始化
父类 Test默认构造函数被调用
-------父类普通成员初始化和构造函数执行
子类 sam1成员初始化
子类 sam2成员初始化
子类 TestSub 默认构造函数被调用
-------父类普通成员初始化和构造函数执行
由此得出Java初始化顺序结论:
1 继承体系的所有静态成员初始化(先父类,后子类)
2 父类初始化完成(普通成员的初始化-->构造函数的调用)
3 子类初始化(普通成员-->构造函数)
Java初始化顺序如图:
l 初始化块是Java类里可出现的第四种成员(前面一次有属性、方法和构造器),一个类里可有多个初始化块,相同类型的初始化块之间有顺序:前面定义的初始化块先执行,后面定义的初始化块后执行。
l 从某种程度上来看,初始化块时构造器的补充,初始化块总是在构造器执行之前执行,系统同样可使用初始化块来进行对象的初始化操作。与构造器不同的事,初始化块是一段固定执行的代码,它不能接受任何参数,因此初始化块对同一个类所有对象所进行的初始化处理完全相同
首先,它找到环境变量CLASSPATH(将Java或者具有java解释能力的工具安装到机器中时,通过操作系统进行设定。)CLASSPATH包含一个或多个目录,它们作为一种特殊的“根”使用,从这里展开对.class文件的搜索。从那个根开始,解释器会寻找包名,并将每个点号(句号)替换成一个斜杠,从而生成CLASSPATH根开始的一个路径名(所以package foo.bar.baz会变成 foo\bar\baz或者foo/bar/baz,具体是正斜杠还是反斜杠由操作系统决定)。随后将它们连接到一起,成为CLASSPATN内的各个目录(入口)。以后搜索.class文件时,就可以从这些地方开始查找与准备创建的类名对应的名字。此外,它也会搜索一些标准目录—这些目录与Java解释器驻留的地方有关。
为了保证父类良好的封装性,不会被子类随意改变,设计父类通常应该遵循如下准则:
l 尽量隐藏父类的内部数据,尽量把父类的所有属性都设置成private访问类型,不用让子类直接访问父类的属性
l 不要让子类随意访问、修改父类的方法。父类中哪些仅为辅助其他的工具方法,应该使用private访问控制符修饰,让子类无法访问该方法;如果父类中的方法需要被外部类调用,必须以public修饰,但又不希望子类重写该方法,可以使用final修饰符来修饰该方法;如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,可以使用protected来修饰该方法
l 尽量不要再父类构造器中调用将要被子类重写的方法
Java程序中测试两个变量是否相等有两种方式,一种是利用==运算符,另一种是利用equals方法。当使用==来判断两个变量是否相等时,如果2个变量是基本类型的变量,且都是数值型(不一定要求数据类型严格相同),则只要两个变量的值相等,使用==判断就将返回true。但对于两个引用类型的变量,必须它们指向同一个对象时,==判断才会返回true,==不可比较类型上没有父子关系的两个对象。
String已经重写了Object的equals()方法,String的equals()方法判断两个字符串相等的标准是:只要两个字符串所包含的字符序列相同,通过equals()比较将返回true,否则将返回false。
l 抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体
l 抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例,即使抽象类里不包含抽象方法,这个抽象类也不能创建实例
l 抽象类可以包含属性、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类、枚举类六种成分。抽象类的构造器不能用于创建实例,主要是用于被其它子类调用
l 含有抽象方法的类(包括直接定义了一个抽象方法;继承了一个抽象父类,但没有完全实现父类包含的抽象方法;以及实现了一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义为抽象类