Java面向对象(一)
面向对象的基本特征
封装(Encapsulation)
将对象的实现细节隐藏起来,然后通过一些公用方法来暴露该对象的功能。即隐藏类的实现细节,让使用者只能通过事先预定的方法来访问数据,从而可以在方法里面加入控制逻辑,以限制对数据的不合理访问。可进行数据检查,从而保证对象信息的完整性,也便于修改,提高代码的可维护性。
使用访问控制符
访问控制符用于控制一个类的成员是否可以被其他类访问(局部变量不能使用访问控制符).4个访问控制符:private,默认,protected,public.
- private:当前类访问权限,private控制的成员只能在当前类的内部使用;
- 默认(包访问权限):可以被相同包下的其它类访问;
- protected(子类访问权限):可以被同一个包中的其它类访问,也可以被不同包中的子类访问;
- public(公共访问权限):可以被所有类共访问;
外部类只能有两种访问控制级别:public,和默认。
package,import,import static
- package语句必须作为源文件的第一条非注释性语句,一个源文件只能指定一个包,即只能包含一个package语句。如果没有显示指定package语句,则处于默认包下。同一个包中的类可以位于不同的目录中。
- import语句用于向某个java文件中导入指定包层次下某个类或全部类。import语句出现在package语句之后,类定义之前.import语句中可以使用通配符:星号,星号只能代表类,不能代表包.
- import static:静态导入(java1.5),用于导入指定类的某个静态Field,方法或者全部Field,方法。
使用import语句可以省略包名,使用import static语句可以省略类名。java默认所有包中都默认导入java.lang下面的所有类.
继承(Inheritance)
实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法;继承的特点
- 苹果 extends 水果,苹果是子类,水果是父类,苹果(特殊) is a 水果(一般)。
- Java类只能有一个直接父类,可以有无限多个间接父类.java.lang.Object是所有类的超类,要么是直接父类要么是间接父类.
- 扩展和派生:从子类的角度看:子类扩展(extends)了父类,从父类的角度看,父类派生(derive)出了子类.扩展和派生是同一个动作,只是观察的角度不同.
使用继承的注意点
继承可以显示类重用,但是会破坏封装.设计父类时应该遵循的原则:
* 尽量隐藏父类的内部数据;
* 不希望子类访问的方法,使用private修饰;如果某方法不希望被子类重写,但是需要外部类调用,则用public final修饰;如果某方法希望被子类重写,但是不希望外部类调用,则用protected修饰;
* 尽量不要在父类构造器里面调用将要被子类重写的方法;
* 子类需要增加额外的属性或者自己独有的属性时,使用继承.
重写父类的方法
子类包含与父类同名方法(非private修饰的)的现象称为方法重写,也称为方法覆盖.可以说子类重写了父类的方法,也可以说子类覆盖了父类的方法.方法的重写遵循”两同两小一大”:
* 两同:方法名相同,形参列表相同;
* 两小:子类方法的返回值类型要比父类方法的返回值类型更小或相等;子类方法声明抛出的异常类比父类方法声明抛出的异常更小或相等;
* 一大:子类方法的访问权限要比父类方法的访问权限更大或相等。
当子类覆盖父类的方法后,子类的对象将无法访问父类中被覆盖的方法,但可以在子类方法中(使用super关键字)调用父类中被覆盖的方法.
super限定
super用于限定该对象调用它从父类继承得到的Field或方法.
* 在子类方法中调用父类被覆盖的实例方法,使用super.父类实例方法();
* 如果子类定义了与父类同名的Field,例如:name,则使用super.name调用父类的Field;
当系统创建子类对象后,会为子类对象分配两块内存,一块用于保存父类的name,一块用于保存子类的name,但是子类中的name会隐藏父类中的name.
调用父类构造器
子类不会获得父类的构造器,但子类构造器可以(通过super)调用父类构造器的初始化代码,此时super语句必须出现在子类构造器执行体的第一行.
无论是否使用super或者this,系统在执行子类构造器之前总会先调用父类构造器.如果子类没有显示调用父类的构造器,那么子类构造器就会默认调用父类的无参构造器,如果父类没有无参的构造器,则编译器会报错,此时就需要子类显示的调用父类的构造器。
创建任何对象总是从该类所在继承树的最顶层类的构造器开始执行,然后依次向下执行,最后才执行本类的构造器.
多态(Polymorphism)
子类对象可以直接赋给父类变量,但运行时依然表现出子类的行为特征,这意味着同一类型的对象在执行同一个方法时表现出不同的行为特征.Java引用变量有两个类型:一个是编译时类型,一个是运行时类型.编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就有可能出现多态。
多态性
Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,或者被称为向上转型(upcasting),向上转型由系统完成.调用父类引用变量的方法时,其方法行为总是表现出子类方法的行为特征,这就是多态(相同类型的变量,调用同一个方法时呈现出不同的特征,就是多态)。
只有方法才表现出多态,对象的Field则不具备对态性。
Object p=new Person();p只能调用Object类的方法,而不能调用Person类的方法.
引用变量的强制类型转换
引用类型之间的转换只能在有继承关系的两个类型之间进行,且是要将父类实例转换成子类类型时。在进行强制类型转换之前,先用instanceof运算符判断是否可以成功转换.
instanceof运算符
在进行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,例如:
Object hello="hello";
boolean bl1=hello instanceof String;
boolean bl2=hello instanceof Math;
类及其成员
类是抽象的,概念上的东西,它规定了某类对象所共同具有的数据和行为特征.Java通过关键字class来定义类,定义类时可使用Field来描述该类对象的数据,使用方法来描述该类对象的行为特征.是Java最小的程序单元。
Java类成员里面只能含有Field,方法,构造器,初始化块,内部类(包括内部类,接口,枚举)5中成员.static可以修饰Field,方法,初始化块,内部类(包括内部类,接口,枚举),static修饰的是类成员,属于整个类,而不是单个对象.类Field属于整个类,当系统第一次准备使用该类时,系统会为该类Field分配内存空间,类Field开始生效,直到该类被卸载.使用实例来访问类成员时,实际上是委托给该类来访问类成员,即便某个实例为null,也可以访问它所属类的类成员.静态初始化块也是类成员,静态初始化块只在类初始化时才被执行,初始化结束后,静态初始化块就不会再获得执行的机会。
类中的三种最常见的成员:构造器,Field和方法。
构造器
构造器是一个特殊的方法,返回值是隐式的,返回值类型是当前类.
- 语法:[修饰符]构造器名(形参列表){}
- 修饰符:可以省略,也可以是public,private,protected其中之一;
- 构造器名:必须和类名相同;
- 形参列表:和定义方法形参列表的格式完全相同.
构造器最大的作用是在创建对象时执行初始化.如果没有为Java类提供任何构造器,则系统会为这个类提供一个无参数的构造器.如果为Java类提供了构造器,则系统就不会默认再提供构造器.
构造器重载:同一个类里面具有多个构造器,多个构造器的形参列表不同,称为构造器重载.通过this关键字可以在一个构造器里面调用另一个构造器的初始化代码.使用this调用另一个重载的构造器,只能在构造器中使用,且必须作为构造器执行体的第一条语句.
Field
- 语法:[修饰符] Field类型 Field名 [=默认值]
- 修饰符可以省略,也可以是public,private,protected,static,final,其中public,private,protected三个最多只能出现其中之一,也可以与static,final结合修饰Field;
- Field类型可以是引用类型也可以是基本数据类型;
- Field名,一个合法的修饰符;
- 默认值,可以指定一个可选的默认值.
使用static修饰的Field,称为静态Field,静态成员不能访问非静态成员;
成员变量和局部变量
根据定义变量位置的不同,将变量分成两大类:成员变量和局部变量- 成员变量:在类范围内定义的变量,包括类Field(使用static修饰的),实例Field(没有用static修饰的).成员变量无须显示初始化,只要为一个类定义了类Field或实例Field,系统就会在这个类的准备阶段或创建该类的实例时进行默认初始化.
- 局部变量:根据定义形式的不同,可分为以下三种:
- 形参:在定义方法签名时定义的变量,在整个方法内有效;
- 方法局部变量:在方法体内定义的局部变量,从定义该变量的地方生效,到该方法结束时失效;
- 代码块局部变量:在代码块中定义的局部变量,在定义该变量的地方生效,到该代码块结束时失效。
局部变量除了形参之外,都必须显示初始化。
Java允许局部变量和成员变量同名.如果方法里面的局部变量和成员变量同名,则局部变量会覆盖成员变量,如果需要在这个方法里面调用被覆盖的成员变量,则可以使用this或类名作为调用者来限定访问成员变量。
成员变量的初始化和内存中的运行机制
当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间(放置在堆内存中),并在分配内存空间后,自动为成员变量指定初始值。局部变量的初始化和内存中的运行机制
局部变量定义后,系统不会为局部变量执行初始化,也不会为局部变量分配内存空间,直到程序为这个变量显示初始化后,系统才会为这个变量分配内存空间,并保存在其所在方法的桟内存中.桟内存中的变量无须系统垃圾回收,其会随方法或代码块的运行结束而结束。变量的使用规则
成员变量的影响
- 增大了变量的生存时间,这将导致更大的内存开销;
- 扩大了变量的作用域,这不利于提高程序的内聚性;
局部变量
局部变量的作用范围越小,其在内存中的停留时间越少,程序运行性能越好.
方法
- 语法:[修饰符] 方法返回值类型 方法名 (形参列表){}
- 修饰符可以省略,也可以是public,private,protected,static,final,abstract其中public,private,protected三个最多只能出现其中之一,final,abstract只能出现之一,也可以与static结合修饰方法;
- 方法返回值类型:基本数据类型或者引用类型或者void;
- 方法名:一个合法的修饰符;
- 形参列表:零到多个,多个之间用逗号隔开;
- 方法不能独立定义,方法只能在类体里定义;
- 方法要么属于类本身,要么属于该类的一个对象;
- 不能独立执行方法,执行方法必须使用类或对象调用;
使用static修饰的方法,称为静态方法,静态成员不能访问非静态成员;
局部内部类
在一个方法内定义的类,叫做局部内部类,其仅在该方法内有效.局部内部类不能在外部类的方法以外的地方使用,不能使用访问控制符和static修饰;局部内部类示例
public static void main(String[] args) {
//定义局部内部类
class InnerBase{
int a;
}
//定义局部内部类的子类
class InnerSub extends InnerBase{
int b;
}
//创建局部内部类的对象
InnerSub is=new InnerSub();
is.a=5;
is.b=9;
System.out.println(is.a+"======="+is.b);
}
局部内部类的类文件
LocalInnerClass.class,LocalInnerClass$1InnerBase.class,LocalInnerClass$1InnerSub.class,因为同一个类里面可能有两个同名的成员内部类,局部内部类的class文件名中有数字用于区别.
方法的参数传递机制
Java里方法的参数传递方式只有一种:值传递,就是将实际参数的副本(复制品)传入方法内,而参数本身不会受到任何影响。基本数据类型参数的值传递
//swap方法
public static swap(int a,int b){}
//main方法
public static main(String [] args){
int a=9;
int b=6;
swap(a,b);
}
- 执行main方法,在main桟区里面创建两个变量a和b.
- 系统进入swap方法后,系统会建立swap桟区,并在swap桟区里面创建两个变量,并将main桟区里面的a,b变量的值分别赋给swap桟区里面的a,b变量.因此swap方法里面操作的a,b变量和main方法里面的a,b变量不是相同的变量,只是它们的值相等。
引用数据类型的值传递
class DataWrap{
public int a;
public int b;
}
public static swap(DataWrap dw){}
public static main(String[] str){
DataWrap dw=new DataWrap();
dw.a=9;
dw.b=6;
swap(dw);
}
- 执行main方法,创建一个DataWrap对象,在堆内存里面保存这个DataWrap对象,在main桟区里面保存dw这个引用变量;
- 执行swap方法时,系统创建swap桟区,这个桟区里面存放了swap的局部变量dw,系统会将main桟区里面的dw变量的值赋给swap桟区的局部变量。即系统将main桟区里面dw变量的值传递给了swap桟区里面的dw变量,这两个dw变量是两个不同的变量,只是这两个变量都指向了堆内存中的DataWrap对象。
形参个数可变的方法
JDK1.5后,在定义方法时,在最后一个形参的类型后增加三个点,则表明该形参可以接受多个参数值,多个参数值被当成数组传入。
//以可变个数形参来定义方法
public static void test(in a,String... books){};
//等同于下面的采用数组形参来定义方法
public static void test(int a,String[] books){};
//调用
test(5,"book1","book2");
test(5,new String[]{"book1","book2"})
- 个数可变的形参只能位于形参列表的最后,且一个方法中最多只能有一个长度可变的形参.这种形式的方法调用简洁;
- 数组形式的形参可以处于形参列表的任意位置,但是调用起来没有个数可变的形参简洁。
递归方法
一个方法里面调用它自身,称为方法递归.递归一定要向已知方向递归。
方法重载
同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则称为方法重载。
同一个类中,方法名相同,参数列表不同。方法重载与方法的其它部分无关,如:修饰符,返回值。
初始化块
初始化块的修饰符只能是static(也可以不用修饰符),初始化块里面可以包含任何可执行语句,包括定义局部变量,调用其它对象的方法,使用分支,循环语句等.初始化块只在创建Java对象时隐式执行,而且在执行构造器之前执行.如果有多个初始化块,则按照先后顺序执行.
- 普通初始化块:对于普通初始化块(没有使用static修饰的初始化块),系统在创建一个对象时会先一直追溯到java.lang.Object类,执行其初始化块,执行其构造器…,最后才执行本类的初始化块和构造器。
- 静态初始化块:使用static修饰的初始化块,称为静态初始化块,也称为类初始化块.系统会在类的初始化阶段执行静态初始化块(也会追溯到java.lang.Object类的静态初始化块),而不是在创建对象时执行,类初始化块总是比普通初始化块先执行。
初始化顺序
对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块)>(变量、初始化块)>构造器。
int a=9;
{
a=6;
}
//Java在创建一个对象时,系统先为该对象的所有实例Field分配内存,接着对这些实例变量执行初始化,初始化的顺序是:先执行声明Field时指定的初始值,再执行初始化块,再执行构造器里指定的初始值.上例中a的最终值为:6.
内部类(包括内部类,接口,枚举)
定义在其它类内部的类称为内部类(也叫嵌套类),包含内部类的类称为外部类(也叫宿主类)。内部类的作用如下:
* 提供了更好的封装,把内部类隐藏在外部类之中,不允许同一个包中的其它类访问;
* 内部类成员可以直接访问外部类的私有数据,但外部类不能访问内部类的实现细节,如内部类的成员变量;
* 匿名内部类适合于创建那些仅需要一次使用的类。
同一个Java源文件里面可以定义多个类,但如果这些类都是独立的(即彼此都不在彼此的内部),那么这些类不是内部类,它们是相互独立的类.内部类一定要放在另一个类的类体部分(也就是类名后面的花括号部分)。
非静态内部类
定义在类内部的,没有使用static修饰的成员内部类,称为非静态内部类.其是外部类的一个成员,可以使用外部类成员可以使用的修饰符修饰,例如:private,public,protected等修饰. 当在非静态内部类的方法内访问某个变量时,系统会按照下面的顺序查找这个变量:
* 先在该方法内查找是否存在该名字的局部变量;
* 在该方法所在的内部类中查找是否存在该名字的成员变量;
* 在该内部类所在的外部类中查找该名字的成员变量;
如果外部类成员变量、内部类成员变量、与内部类里方法的局部变量同名,则可通过使用this,外部类名.this作为限定区分.
成员内部类的class文件总是这种形式:OuterClass$InnerClass.class.
非静态内部类里面不能定义静态成员,也不能定义静态初始化块,但是可以定义普通初始化块。
静态内部类
使用static修饰的内部类,称为静态内部类,或者类内部类.(static只能修饰内部类,不能修饰外部类)静态内部类可以包含静态成员也可以包含非静态成员.接口里面也能定义内部类,且这个内部类默认使用public static修饰;
使用内部类
- 在外部类内部使用内部类
与定义普通类一样。内部类类名 内部类变量=new 内部类类名(); - 在外部类以外使用非静态内部类
private修饰的内部类(包括静态内部类和非静态内部类)只能在外部类内部使用;
使用其它访问控制符修饰的能在访问修饰符对应的访问权限内使用。
- 省略访问控制符的内部类,只能被与外部类处于同一个包中的其它类所访问;
- 使用protected修饰的内部类,可被与外部类处于同一个包中的其它类和外部类的子类访问;
- 使用public修饰的内部类,可以在任何地方被访问;
- 在外部类以外的地方定义定义内部类(静态内部类和非静态内部类)的方法如下:
- 外部类名.内部类名 变量名=new 外部类名().new 内部类名();
- 外部类名.内部类名 变量名=外部类实例.new 内部类名();
匿名内部类
用于创建只需要使用一次的类,在创建匿名内部类后,系统会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用.匿名内部类必须继承一个父类或者实现一个接口,但最多只能继承一个父类或着实现一个接口.创建匿名内部类时必须实现接口或者抽象父类中的所有抽象方法。如果匿名内部类需要访问外部类的局部变量,则必须使用final修饰符来修饰外部类的局部变量.
通过接口实现的匿名内部类
1).
public class NoNameInnerClass {
interface InProduct{
public String getName();
}
public void test(InProduct pro){
System.out.println("匿名内部类");
}
public static void main(String[] args) {
NoNameInnerClass noNameInnerClass=new NoNameInnerClass();
//new InProduct(){}匿名内部类对象
noNameInnerClass.test(new InProduct(){
@Override
public String getName() {
// TODO Auto-generated method stub
return null;
}
});
}
2)
public class NoNameInnerClass {
interface InProduct{
public String getName();
}
public void test(InProduct pro){
System.out.println("匿名内部类");
}
public static void main(String[] args) {
NoNameInnerClass noNameInnerClass=new NoNameInnerClass();
//in:匿名内部类对像
InProduct in=new InProduct() {
@Override
public String getName() {
// TODO Auto-generated method stub
return null;
}
};
noNameInnerClass.test(in);
}
}
3)
public class NoNameInnerClass {
interface InProduct{
public String getName();
}
public void test(InProduct pro){
System.out.println("匿名内部类");
}
class A implements InProduct{
@Override
public String getName() {
// TODO Auto-generated method stub
return null;
}
}
public static void main(String[] args) {
NoNameInnerClass noNameInnerClass=new NoNameInnerClass();
noNameInnerClass.test(noNameInnerClass.new A());
}
}
通过抽象类实现的匿名类
1.
public class NoNameInnerClass {
abstract class Product{
public Product(){}
private String name;
public Product(String name){
this.name=name;
}
public abstract double getPrice();
}
public void test(Product pro){
System.out.println("匿名内部类");
}
public void tt(){
test(new Product(){
@Override
public double getPrice() {
// TODO Auto-generated method stub
return 0;
}
});
test(new Product("asd"){
@Override
public double getPrice() {
// TODO Auto-generated method stub
return 0;
}
});
}
}
2.
public class NoNameInnerClass {
abstract class Product{
public Product(){}
private String name;
public Product(String name){
this.name=name;
}
public abstract double getPrice();
}
public void test(Product pro){
System.out.println("匿名内部类");
}
public void tt(){
Product product=new Product() {
@Override
public double getPrice() {
// TODO Auto-generated method stub
return 0;
}
};
test(product);
}
}
3.
public class NoNameInnerClass {
abstract class Product{
public Product(){}
private String name;
public Product(String name){
this.name=name;
}
public abstract double getPrice();
}
public void test(Product pro){
System.out.println("匿名内部类");
}
public void tt(){
Product product=new Product() {
{
System.out.println("初始化块");
}
@Override
public double getPrice() {
// TODO Auto-generated method stub
return 0;
}
};
test(product);
}
}
闭包(Closure)和回调
闭包(Closure)是一种能被调用的对象,它保存了创建它的作用域信息.可以把非静态内部类当成面向对象领域的闭包。
回调是允许客户类通过内部类引用来调用其外部类的方法.
面向对象方式的组成
面向对象分析(OOA)
面向对象设计(OOD)
从现实世界中客观存在的事物(即对象)出发来构造软件系统(最小的程序单元是类),并在系统构造中尽可能地运用人类的自然思维方式,强调直接以现实世界中的事物(即对象)为中心来思考、认识问题。