java面向对象的三大特征:封装、继承、多态
引用变量放在栈内存里,实际的对象放在堆内存里。引用变量里存放的仅仅是一个引用,它指向实际的对象。Java里的引用就是C里的指针。
如果堆内存里的对象没有任何变量指向该对象,那么程序将无法访问该对象,Java的垃圾回收机制会回收该对象,释放内存。如果希望通知垃圾回收机制回收某个对象,只要将引用变量赋值为null即可。
java提供this关键字,指向调用该方法的对象。
如果成员变量和局部变量重名,则成员变量被覆盖,若要访问成员变量,则必须使用this前缀。
static真正的作用就是用于区分成员变量、方法、内部类、初始化块这四种成员是属于类本身还是属于实例。
static修饰的成员不能访问没有static修饰的成员。如果确实要在静态方法中访问普通方法,只能重新创建一个对象。
java语言里,类才是第一公民,方法不能独立定义,要么属于类本身,要么属于该类的一个对象。
java参数传递方式只有一种:值传递。值传递就是将实际参数的副本传入方法内而参数本身不会受到任何影响。
值传递的实质:当系统开始执行方法时,系统为形参执行初始化,就是把实参变量的值赋给方法的形参变量,方法中操作的并不是实际的实参变量。
通过类或对象调用一个方法时,系统会在该方法栈区内为所有的形参分配内存空间,并将实参的值赋给对应的形参,这就完成了形参的初始化。
创建基本类型的时候栈区中会产生变量,但是创建对象的时候,堆内存中保存对象本身,栈内存中保存了引用这个对象的引用变量。
在最后一个形参的类型后加…表明这个形参可以接收多个参数值,当成数组传入。但一个方法最多只有一个个数可变的形参。
方法局部变量和代码块局部变量必须要先指定初始值,不然不能访问他们。
定义局部变量后,系统并未给这个变量分配内存空间,直到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存在这块内存中。
局部变量不属于任何类和实例,因此它总是保存在其所在的方法的栈内存中。
栈内存的变量无须系统垃圾回收。局部变量只保存基本类型的值或者对象的引用,所以局部变量所占的内存区通常比较小。
类相关就用类变量,实例相关就用实例变量。
封装
封装指的是对象状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过类所提供的方法来实现对内部信息的操作和访问。
类里的绝大部分成员变量都应该用private修饰,只有一些static修饰的、类似全局变量的成员变量才能考虑用public修饰。
如果某个类主要做其他类的父类,该类里包含的大部分方法可能仅希望被其子类重写,而不想被外界直接调用,则应该使用protected修饰这些方法。
希望暴露出来给其他类自由调用的方法应该使用public修饰。例如类的构造器。
在一个构造器中可以使用this关键字来调用相应的构造器。
继承
Java类只有一个直接父类。
子类覆盖父类方法之后,子类的对象将无法访问父类被覆盖的方法,但可以在子类方法中调用父类被覆盖的方法,如果是实例方法就用super,如果是类方法就用父类类名。
重载发生在同一个类的多个同名方法之间,重写发生在子类和父类的同名方法之间。
super可以访问父类被隐藏的实例变量。如果子类和父类没有同名的成员变量,则不用显示使用super或者父类名作为调用者。
super调用父类构造器和this调用构造器都必须要放在第一行。
多态
java引用变量有两个类型,一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就会出现多态。
把子类对象直接赋给父类引用变量时,运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征。所以会出现相同类型的变量调用同一个方法呈现多种不同行为特征。这就是对象。
class BaseClass{
public void base(){}
public void test(){}
}
class SubClass{
public void test(){}
public void sub(){}
}
子类引用变量实际上包含子类的方法(可以通过反射来执行这个方法),但是它的编译类型是父类类型,所以编译的时候就不能调用父类没有的方法(sub()),运行时又会表现出子类的方法(SubClass中的test())。
与方法不同的是,对象的实例变量不具备多态性。当引用变量输出实例变量的时候不会输出子类的实例变量。
引用变量只能调用编译时类型的方法,而不能调用它运行时类型的方法,如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型。
向下强制转型时需要注意:
1、基本类型之间的转换只能在数值类型之间进行。
2、引用类型之间的转换只能在具有继承关系的两个类型之间。
向下强制转换的时候最好用instanceof运算符判断时候可以成功转换。
Object obj=new Integer(5);
if(obj instanceof String){
String str = (String)obj;
}
instanceof前一个操作数通常是一个引用常量,后一个操作数通常是一个类或者接口,判断前面的对象是否是后面的类或者其子类、实现类的实例。
Object hello=new String(“hello”);
hello instanceof Object
hello instanceof String
hello instanceof Comparable
不要再父类构造器中调用将要被子类重写的方法。因为这个时候父类会调用被重写的那个方法。
继承是对已有的类做一番改造,以此获得一个特殊的版本,如果两个类有明确的整体、部分的关系,就应该采用组合关系来实现复用。
继承是is_a的关系,组合是has_a的关系。
初始化块只在创建Java对象时隐式执行,而且在执行构造器之前执行。
当Java创建一个对象时,系统先为该对象的所有实例变量分配内存(前提是该类先被加载过),接着程序开始对实例变量执行初始化,其初始化顺序是:先执行初始化块或声明实例变量时指定的初始值,再执行构造器里指定的初始值。
初始化块是构造器的补充,不能接受任何参数。当两个构造器有相同的初始化代码且不接受任何参数,就可以把他们放在初始化块中。
如果希望类加载后对某个类进行初始化操作,就要使用static来修饰代码块。
静态初始化块是类相关的,系统在类初始化阶段执行静态初始化块,而不是在创建对象的时候执行,通常用于对类变量进行初始化处理。静态初始化块不能对实例变量进行初始化处理。
初始化父类的构造器的时候是看子类调用了父类的哪个构造器,如果子类没有调用父类构造器,父类就初始化无参构造器。