Java面向对象

对象和类

引用类型变量java

为了可以对实例化的对象进行访问控制,需一个特殊的变量,即引用。对引用有两点须要说明:
1. 引用类型变量能够存储该类对象的地址信息,一般称为“指向该类的对象”,当一个引用类型变量指向该类的对象,就能够经过这个变量对对象实施访问。
2. 除8种基本类型以外,用类、接口、数组等声明的变量都称为引用类型变量,简称“引用”。

null和NullPointerException程序员

须要注意:当一个引用的值为null的时候,若是经过引用访问对象成员变量或者调用方法是不合逻辑的(因其没有指向某对
象,天然不会有属性和方法)。此时,会产生NullPointerException(空指针异常)。

方法的重载数组

  • 方法的签名安全

    方法的签名包含以下两个方面:方法名和参数列表。
    
      Java语法规定,一个类中不能够有两个方法签名彻底相同的方法,即:一个类中不能够有两个方法的方法
      名和参数列表都彻底相同,可是,若是一个类的两个方法只是方法名相同而参数列表不一样,是能够的。
  • 方法重载及其意义数据结构

    在Java语言中,容许多个方法的名称相同,但参数列表不一样,此种方式称为方法的重载(overload)。函数

构造方法this

  • 构造方法语法结构操作系统

    构造方法是在类中定义的方法, 但不一样于其余的方法,构造方法的定义有以下两点规则:线程

    1. 构造方法的名称必须与类名相同。
    2. 构造方法没有返回值,但也不能写void。
  • 经过构造方法初始化成员变量指针

  • this关键字的使用

    this指代当前对象
  • 默认的构造方法

    JAVA语法规定,任何一个类都必须含有构造方法,假如源程序中没有定义,则编译器在编译时将为其添加
      一个无参的空构造方法(此方法称之为“默认的构造方法”)
  • 构造方法的重载

    不少时候,为了使用的方便,能够对一个类定义多个构造方法,这些构造方法都有相同的名称(类名),只是方法的参数不一样,称之为构造方法的重载。
      在建立对象时,Java编译器会根据不一样的参数调用来不一样构造方法

数组

引用类型数组

  • 数组是对象

    在java中,数组属于引用数据类型,数组对象存放在堆中存储,数组变量属于引用类型,存储数组对象的地址信息,指向数组对象。 而数组的元素能够当作数组对象的成员变量(只不过类型所有相同)

    数组的内存分配

  • 引用类型数组的声明

    刚刚声明的数组为基本类型数组,除了基本类型数组之外,也能够声明引用类型数组。所谓引用类型数组,即数组元素的类型不是基本类型(int,char,float。。) , 而是引用类型,看以下代码:

    Cell [ ]    cells  = new  Cell [ 4 ] ;

    其内存分配以下图

    内存分配图

    从上图示能够看出,new Cell[4]实际是分配了4个空间用于存放4个Cell类型的引用,并赋初始值为null,而并不是是分配了4个Cell类型的对象。

  • 引用类型数组的初始化

    若是但愿每个元素都指向具体的对象,则须要针对每个数组元素进行“new”运算。与基本类型数组同样,也能够采用静态初始化的方式进行初始化操做。以下代码所示:

    Cell[ ]  cells = new  Cell[4];
      cells[0] = new Cell(0,4);
      cells[1] = new Cell(1,3);
      cells[2] = new Cell(1,4);
      cells[3] = new Cell(1,5);

    等价于:

    Cell[ ] cells = new Cell[ ] {
          new Cell(0,4) ,
          new Cell(1,3) ,
          new Cell(1,4) , 
          new Cell(1,5) 
      } ;

    如上数组内存分配图以下图– 6 所示:

    内存分配图

  • 数组的类型是基本类型数组

    int [ ][ ] arr = new int[3][ ];
     arr[0] = new int[2];
     arr[1] = new int[3];
     arr[2] = new int[2];
     arr[1][1] = 100;
    
     分析如上代码能够看出,变量arr指向一个数组,该数组有三个元素,每一个元素都是int类型数组,长度分别为
     2,3,2,arr[1][1]=100表示将arr数组中的第2个元素(数组)的第2个元素赋值为100, 其内存分配如图所示:

    内存分配图

对象内存管理

对象内存管理

  1. 在JAVA中,有java程序、虚拟机、操做系统三个层次,其中java程序与虚拟机交互,而虚拟机与操做系统交互。编译好的java字节码文件运行在JVM中。
  2. 程序中不管代码仍是数据,都须要存储在内存中,而java程序所需内存均由JVM进行管理分配,开发者只需关心JVM是如何管理内存的,而无需关注某种操做系统是如何管理内存的,这就保证了java程序的平台无关性。
  3. JVM会将申请的内存从逻辑上划分为三个区域:堆、栈、方法区。这三个区域分别用于存储不一样的数据。

堆内存

  • 对象存储在堆中

    JVM在其内存空间开辟了一个称为“堆”的存储空间,这部分空间用于存储使用new关键字所建立的对象。请看以下代码:

    Cell   c  = new  Cell ();

    其内存分布如图所示:

    内存分布

    从图中能够看到右侧的堆内存,new Cell()所建立的对象在堆中分配,同时成员变量亦在此分配,并赋初始值为零。引用类型变量c在栈内存中分配,其中保存的数据,为对象在堆内存中的地址信息,假设对象在堆内存的地址为40DF,则c中保存的便是40DF。

  • 成员变量的生命周期

    当声明好对象以后,对该对象(堆中的Cell)的访问须要依靠引用变量(栈中的c),那么当一个对象没有任
      何引用时,该对象被视为废弃的对象,属于被回收的范围,同时该对象中的全部成员变量也随之被回收。
      能够这样认为,成员变量的生命周期为:从对象在堆中建立开始到对象从堆中被回收结束。
    
      Cell   c  =  new  Cell();
      c = null ;
    
      当将c赋值为null时,表示c再也不指向刚刚分配的对象空间,此时成员变量失效。
  • 垃圾回收机制

    1. 垃圾回收器(Garbage Collection,GC)是JVM自带的一个线程(自动运行着的程序),用于回收没有任何引用所指向的对象。
    2. GC线程会从栈中的引用变量开始跟踪,从而断定哪些内存是正在使用的,若GC没法跟踪到某一块堆内存,那么GC就认为这块内存再也不使用了,即为可回收的。可是,java程序员不用担忧内存管理,由于垃圾收集器会自动进行管理。
  • Java程序的内存泄漏问题

    1. 内存泄露是指,再也不被使用的内存没有被及时的回收,严重的内存泄露会因过多的内存占用而致使程序的崩溃。在程序中应该尽可能避免没必要要的内存浪费。
    2. GC线程判断对象是否能够被回收的依据是该对象是否有引用来指向,所以,当肯定该对象再也不使用时,应该及时的将其引用设置为null,这样,该对象即再也不被引用,属于可回收的范围。
  • System.gc()方法

    1. GC的回收对程序员来讲是透明的,并不必定一发现有无引用的对象就当即回收。通常状况下,当咱们须要GC线程即刻回收无用对象时,能够调用System.gc()方法。此方法用于建议JVM立刻调度GC线程回收资源,但具体的实现策略取决于不一样的JVM系统。

非堆---栈

  • 栈用于存放方法中的局部变量

    1. JVM在其内存空间开辟一个称为”栈”的存储空间,这部分空间用于存储程序运行时在方法中声明的全部的局部变量,例如,在main方法中有以下代码:

      Cell   c   =   new   Cell ( );
       int num  =  5;

      其内存分配如图所示:

      内存分配

      说明:方法中的变量即为局部变量,是在栈内存中分配,若变量为值类型,则在栈中存储的就是该变量的值。若变量为引用类型,则在栈中存储的是堆中对象的地址。

  • 局部变量的生命周期

    1. 一个运行的Java程序从开始到结束会有屡次方法的调用。JVM会为每个方法的调用在栈中分配一个对应的空间,这个空间称为该方法的栈帧。一个栈帧对应一个正在调用中的方法,栈帧中存储了该方法的参数、局部变量等数据。当某一个方法调用完成后,其对应的栈帧将被清除,局部变量即失效。
  • 成员变量和局部变量

    成员变量与局部变量的差异以下:
      局部变量:
      1) 定义在方法中;
      2) 没有默认值,必须自行设定初始值;
      3) 方法被调用时,存在栈中,方法调用结束时局部变量从栈中清除;
      成员变量:
      1) 定义在类中,方法外;
      2) 由系统设定默认初始值,能够不显式初始化;
      3) 所在类被实例化后,存在堆中,对象被回收时,成员变量失效;

非堆---方法区

  • 方法区用于存放类的信息

    1. 方法区用于存放类的信息,Java程序运行时,首先会经过类装载器载入类文件的字节码信息,通过解析后将其装入方法区。类的各类信息(包括方法)都在方法区存储,看以下代码:

      Cell   c = new  Cell();

      程序在执行这句话时,Cell类首先被装载到JVM的方法区,其中包括类的基本信息和方法定义等,以下图– 3 所示:

      方法区

      经过图示能够看出,在方法区中,包含Cell类的字节码文件,及类的基本信息及方法drop等。

  • 方法只有一份

    1. 当类的信息被加载到方法区时,除了类的类型信息之外,同时类内的方法定义也被加载到方法区;

    2. 类在实例化对象时,多个对象会拥有各自在堆中的空间,但全部实例对象是共用在方法区中的一份方法定义的。意味着,方法只有一份。看以下代码:

      JFrame f1 = new JFrame(); 
       JFrame f2 = new JFrame(); 
       f1.setSize(200, 300);
       f2.setSize(300,400);

      如上的代码中,对象有两个,可是setSize方法只有一份,分别针对f1指向的对象和f2指向的对象调用了两次。

继承的意义

继承

  • 泛化的过程

    1. 前面的案例中定义了T类和J类, 经过分析能够发现, 在这两个类中存在着大量的重复代码,像cells属性、print方法、drop方法、moveLeft方法、moveRight方法,在这两个类中都存在,而且实现上基本也是相同的,本着代码重用的原则,可使用继承的方式来实现。
    2. 首先,构建T类和J类的父类Tetromino类,将公共的(T类和J类公有的)信息存放在父类中, T类和J类继承Tetromino父类。此时,子类便可以共享父类的数据。这个过程就是泛化的过程。
  • extends关键字

    1. 使用继承能够实现代码的重用,在java语言中,须要经过extends关键字实现类的继承。继承完成后,子类(Sub class)能够继承父类(Super class)的成员变量及成员方法,同时子类也能够定义本身的成员变量和成员方法。届时,子类将具备父类的成员及本类的成员。
    2. 须要注意的是,Java语言不支持多重继承,即:一个类只能继承一个父类,但一个父类能够有多个子类。
  • 继承中构造方法

    1. 父类的无参构造方法之因此被执行,是由于java规定,子类在构造以前必须先构造父类。
    2. 事实上,子类的构造方法中是必需要经过super关键字来调用父类的构造方法的,这样才能够保证妥善的初始化继承自父类的成员变量。
    3. 可是看上一个案例中的代码并无super调用父类构造方法,那是由于,若是子类的构造方法中没有调用父类的构造方法,则java编译器会自动的加入对父类无参构造方法的调用。
    4. super();为编译器自动加入的,而且super关键字必须位于子类构造方法的第一行,不然会有编译错误。
    5. 另一点须要注意的是,若父类没有提供无参的构造方法,则会出现编译错误。
    6. 若是在子类构造方法中没有写super调用父类构造方法,这时编译器会默认添加super()来调用父类的无参构造方法,可是父类中又没有定义无参的构造方法,所以会发生编译错误。
  • 父类引用指向子类的对象

    一个子类的对象能够向上造型为父类的类型。

重写

  • 方法的重写

    1. 在java语言中,子类能够重写(覆盖)继承自父类的方法,即方法名和参数列表与父类的方法相同,可是方法的实现不一样。
    2. 当子类重写了父类的方法后,该重写方法被调用时(不管是经过子类的引用调用仍是经过父类的引用调用),运行的都是子类重写后的版本。
  • 重写中使用super关键字

    在子类重写的方法中,能够经过super关键字调用父类的版本

  • 重写和重载的区别

    重载与重写是彻底不一样的语法现象,区别以下所示:
      · 重载: 是指在一个类中定义多个方法名相同但参数列表不一样的方法,在编译时,根据参数的个数和类型来决定绑定哪一个方法。
      · 重写: 是指在子类中定义和父类彻底相同的方法,在程序运行时,根据对象的类型(而不是引用类型)而调用不一样的方法。
    
      分析以下代码的输出结果:
      class Super {
      	public void f() {
      		System.out.println ("super.f()");
      	}
      }
      class Sub extends Super {
      	public void f() {
      		System.out.println ("sub.f()");
      	}
      }
      class Goo {
      	public void g(Super obj) { 
      		System.out.println ("g(Super)");  
      		obj.f();
      	}
      	public void g(Sub obj) {
      		System.out.println ("g(Sub) "); 
      		obj.f();
      	}
      }
      class Test{
          public static void main(String[] args){
              Super obj = new Sub();
      		Goo goo = new Goo();
      		goo.g(obj);
      	}
      }
    
      首先,重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪一个方法, 由于变量
       obj为Super类型引用, 因此,Goo的g(Super)被调用,先输出g(Super)。
      重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法,由于
       obj实际指向的是子类Sub的对象,所以,子类重写后的f方法被调用,即sub.f()。

访问控制

包的概念

  • package语句

    在Java语言中,命名冲突问题是用包(package)的概念来解决的,也就是说,在定义一个类时,除了定义类的名称通常还要指定一个包的名称,定义包名的语法以下所示:

    package 包名;

    须要注意的是,在定义包时,package语句必须写在Java源文件的最开始处,即在类定义以前,以下面的语句将为Point类指定包名为“test”:

    package test;
      	class Point{
      	    ……
      	}

    在命名包名时,包名能够有层次结构,在一个包中能够包含另一个包

  • import语句

    能够经过import语句对类的全称进行声明,import语句的语法以下所示:

    import 类的全局限定名(即包名+类名);

    有时,在import语句中也可使用“*”符号,例如:

    import org.whatisjava.core.*;

访问控制修饰符

  • 封装的意义

    假设有水果店卖水果,分两种方式进行管理,方式一为须要店员,由店员实现取水果、包装、找零等功
      能。方式二为不须要店员,由顾客自行完成取水果、包装、找零等功能。
      那么想想,哪种方式更适合管理呢?通常认为方式一更适合,由于方式二没有人来进行管理,安全性
      较低,除非来的都是活雷锋,彻底靠自觉。而方式一的安全性更高一些,并不是任何人均可以操做水果。
    
      在软件系统中,经常经过封装来解决上面的问题。即:将容易变化的、具体的实现细节(卖水果)封装起
      来,外界不可访问,而对外提供可调用的、稳定的功能(店员),这样的意义在于:
      1. 下降代码出错的可能性,更便于维护。
      2. 当内部实现细节改变时,只要保证对外的功能定义不变,其余的模块不须要更改。
    
      在软件系统中,封装经常须要依靠一些访问控制修饰符来实现。
  • 访问控制修饰符修饰类

    1. 对于类的修饰可使用public和默认方式。 其中,public修饰的类能够被任何一个类使用,而默认访问控制的类只能够被同一个包中的类使用。
    2. 而protected和private访问修饰符是不能够修饰类的,但其能够修饰内部类(后面课程详细介绍)。
  • 访问控制符修饰成员

    4种访问修饰(public、private、protected、默认),均可以修饰成员,其权限以下图所示

    权限

    public 修饰符,在任何地方均可以访问;protected能够在本类、同一包中的类、子类中访问,除此以外的其它类不能够访问;默认方式为能够本类及同一包中的类访问,除此以外其它类不能够访问;private只能够在本类中访问,其它任何类都不能够。

static和final

static关键字

  • static修饰成员变量

    1. static关键字能够修饰成员变量,它所修饰的成员变量不属于对象的数据结构,而是属于类的变量,一般经过类名来引用static成员。
    2. 当建立对象后,成员变量是存储在堆中的,而static成员变量和类的信息一块儿存储在方法区, 而不是在堆中,
    3. 一个类的static成员变量只有“一份”(存储在方法区),不管该类建立了多少对象。
  • static修饰方法

  • static块

    static块为属于类的代码块,在类加载期间执行的代码块,只执行一次,能够用来在软件中加载静态资源(图像、音频等等)。

final关键字

  • final修饰变量

    final关键字修饰变量,意为不可改变。final能够修饰成员变量,也能够修饰局部变量,当final修饰成员变量时,能够有两种初始化方式:

    1. 声明同时初始化
    2. 构造函数中初始化

    final关键字修饰局部变量,在使用以前初始化便可。

  • final修饰方法

    final关键字修饰的方法不能够被重写。使一个方法不能被重写的意义在于:防止子类在定义新方法时形成的“不经意”重写。

  • final修饰类

    final关键字修饰的类不能够被继承。使一个类不能被继承的意义在于:能够保护类不被继承修改,能够控制滥用继承对系统形成的危害。在JDK中的一些基础类库被定义为final的,例如:String、Math、Integer、Double 等等。

  • static final常量

    static final 修饰的成员变量称为常量,必须声明同时初始化,而且不可被改变。常量建议全部字母大写。

    实际应用中应用率较广,由于static final常量是在编译期被替换的,能够节约没必要要的开支,以下代码演示了static final的用法:

    class Foo {
          public static final int NUM = 100;
      }
      class Goo {
          public static void main(String[] args) {
              Sytem.out.println(Foo.NUM);  
              // 代码编译时,会替换为:System.out.println(100);
          }
      }

    说明:static final常量Foo.NUM会在编译时被替换为其常量值(100),在运行Goo类时,Foo类不须要被载入。这样减小了没必要要的开支。

抽象类、接口和内部类

使用抽象类

  • 抽象方法和抽象类

    1. 由abstract修饰的方法为抽象方法,抽象方法即只有方法的定义,没有方法体实现,用一个分号结尾。
    2. 若将抽象方法包含在类中,则该类也应该为抽象的,能够理解为,该类也不完整。抽象类由abstract关键字 声明。
    3. 抽象类是不能实例化对象的,而一个类不能实例化是没有意义的,因此,须要定义类来继承抽象类,而若是 一个类继承了抽象类,则其必须重写其抽象方法(变不完整为完整),除非该类也声明为抽象类。
  • 抽象类不能够实例化

    1. 即便一个类中没有抽象方法,也能够将其定义为抽象类,一样,该类不能够实例化。
    2. 须要注意一点:abstract和final关键字不能够同时用于修饰一个类,由于final关键字使得类不可继承,而abstract修饰的类若是不能够继承将没有任何意义。二者放在一块儿,会起冲突。
  • 继承抽象类

    1. 一个类继承抽象类后,必须实现其抽象方法,不一样的子类能够有不一样的实现。
  • 抽象类的意义

    定义抽象类的意义在于:
      1. 为其子类提供一个公共的类型(父类引用指向子类对象);
      2. 封装子类中的重复内容(成员变量和方法);
      3. 定义有抽象方法,子类虽然有不一样的实现,但该方法的定义是一致的。(子类须要实现此抽象方法)。

使用接口

  • 定义一个接口

    接口能够当作是特殊的抽象类。即只包含抽象方法和常量的抽象类。能够经过interface关键字来定义接口。看以下代码:

    interface Runner { 
          public static int DEFAULT_SPEED = 100
          public void run(); 
      }

    注意,run()方法,此处能够省略public abstract。因其默认就是public abstract的。

  • 实现接口

    1. 与继承不一样,一个类能够实现多个接口,实现的接口直接用逗号分隔。固然,该类须要实现这些接口中定义的全部方法;
    2. 一个类能够经过implements关键字”实现”接口。一个类实现了某个接口后必须实现该接口中定义的全部方法。
    3. 接口能够做为一种类型声明变量,一个接口类型的变量能够引用实现了该接口的类的对象;经过该变量能够调用该接口中定义的方法(具体的实现类提供了方法的实现)。
  • 接口的继承

    接口间能够存在继承关系,一个接口能够经过extends关键字继承另一个接口。子接口继承了父接口中定义的全部方法

  • 接口和抽象类的区别

    1. 一个类只能继承一个抽象类,但能够实现多个接口。
    2. 抽象类中能够包含抽象方法和非抽象方法,而接口中的全部方法均为抽象的。
    3. 子类继承抽象类必须实现抽象类中全部抽象方法,不然子类也必须是抽象类。而子类实现接口则必须实现接口中的全部抽象方法。

多态

  • 多态的意义

    // 前面所讲解的现象就是多态,多态即多种形态,主要有两个方面的表现。
      // 首先,一个类型的引用在指向不一样的对象时会有不一样的实现,看以下的代码:
    
    
      达内职员 emp1 = new 达内讲师(); 
      达内职员 emp2 = new 达内项目经理();
      emp1.完成工做();
      emp2.完成工做();
    
      // 一样是达内职员类型,当指向不一样的对象时,能够有不一样的表现。
      // 其次,一样一个对象,造型成不一样的类型时,会有不一样的功能,看以下代码所示:
    
      达内讲师 teacher = new 达内讲师();
      企业技术顾问 consultant = teacher;
      技术图书做者 author = teacher;
      consultant.培训员工();
      author.编辑稿件();
    
      // 经过上面的代码,能够看出,一样的达内讲师对象,当将其造型为企业技术顾问及技术图书做者时,能够实现不一样的功能。
  • 向上造型

    父类的引用指向子类的对象。这个现象就是下面要给你们介绍的现象,叫作向上造型。

    一个类的对象能够向上造型的类型有:父类的类型及其实现的接口类型。当发生向上造型时,Java编译器会根据类型检查调用方法是否匹配

  • 强制转型

    在实际应用中,还能够经过强制转换将父类型变量转换为子类型变量,前提是该变量指向的对象确实是该子类类型。也可经过强制转换将变量转换为某种接口类型,前提是该变量指向的对象确实实现了该接口。若是在强制转换过程当中出现违背上述两个前提,将会抛出ClassCastException。

  • instanceof关键字

    在强制转型中,为了不出现ClassCastException,能够经过instanceof关键字判断某个引用指向的对象是否为指定类型。

内部类

  • 定义成员内部类

    一个类能够定义在另一个类的内部,定义在类内部的类称之为Inner,其所在的类称之为Outer;Inter
      定义在Outer的内部,一般只服务于Outer,对外不具有可见性,Inter能够直接调用Outer的成员及方法(包括私有的)。
  • 建立内部类对象

    通常状况下,Inner对象会在Outer对象中建立(构造方法或其余方法);Inner对象中会有一个隐式的引用指向建立它的Outer类对象。

  • 定义匿名内部类

    若是在一段程序中须要建立一个类的对象(一般这个类须要实现某个接口或者继承某个类),并且对象建立后,这个类的价值也就不存在了,这个类能够没必要命名,称之为匿名内部类。

面向对象汇总

面向对象三大特征:封装、继承、多态

相关文章
相关标签/搜索