天天看点

Java学习笔记一面向对象一

类和包

定义类

[修饰符] class 类名
{
  零到多个构造器定义
  零到多个成员变量
  零到多个方法
}      

修饰符可以是public、final、abstract,或者完全省略这个修饰符

定义成员变量

[修饰符] 类型 成员变量名 [=默认值];      

修饰符可以省略,也可以是public、protected、private、static、final,其中public、protected、private三个最多只能出现其中之一,可以与static、final组合起来修饰成员变量。

成员变量和局部变量

成员变量分为实例变量(不以static修饰)和类变量(以static修饰),局部变量分为形参(方法签名中定义的变量)、方法局部变量(在方法内定义)和代码块局部变量(在代码块内定义)。

成员变量无需显示初始化,只要为一个类定义了类变量或实例变量,系统就会在这个类的准备阶段或创建该类的实例时进行默认初始化。局部变量除形参之外,都必须显式初始化。这意味着定义局部变量后,系统并为这个变量分配内存空间,直到等到程序为这个变量赋初值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。

定义方法

[修饰符] 方法返回值类型 方法名(形参列表)
{
  //由零到多条可执行语句组成的方法体
}      

修饰符可以省略,也可以是public、protected、private、static、final、abstract,其中public、protected、private三个最多只能出现其中之一;final和abtract最多只能出现其中之一,它们可以与static组合起来修饰方法。

对于static修饰的方法而言,则可以使用类来直接调用该方法,如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象。所以,static修饰的方法中不能使用this引用。由于static修饰的方法不能使用this引用,所以static修饰的方法不能访问不使用static修饰的普通成员,因此Java语法规定:静态成员不能直接访问非静态成员。如果确实需要在静态方法中访问另一个普通方法,则只能重新创建一个对象。使用对象调用static方法和使用类调用该方法一样,这是因为底层依然是使用这些实例所属的类作为调用者。

形参个数可变的方法

从JDK1.5之后,Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三个点(…),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。

// 以可变个数形参来定义方法
public static void test(int a, String... books)
// 以数组形参来定义方法
public static void test(int a, String[] books)      

对于以可变形参形式定义:​

​test(5, "hello", "world")​

​​ 对于以数组形参来定义方法:​

​test(5, new String[]{"hello", "world"})​

​ 个数可变的形参只能处于形参列表的最后,一个方法中最多只能包含一个个数可变的形参。个数可变的形参本质就是一个数组类型的形参,因此调用包含个数可变形参的方法时,该个数可变的形参既可以传入多个参数,也可以传入一个数组。

构造器定义

[修饰符] 构造器名(形参列表)
{
  //由零到多条可执行语句组成的方法体
}      

修饰符可以省略,也可以是public、protected、private其中之一

构造器是创建Java对象的重要途经,通过new关键字调用构造器时,构造器也确实返回了该类的对象,但这个对象并不是完全由构造器负责创建的。实际上,当程序员调用构造器时,系统会先为该对象分配内存空间,并为这个对象执行默认初始化,这个对象已经产生了,这些操作在构造器执行之前就完成了。也就是,当系统开始执行构造器的执行体之前,系统已经创建了一个对象,只是这个对象还不能被外部程序访问,只能在该构造器中通过this来引用。当构造器的执行体执行结束后,这个对象作为构造器的返回值被返回,通常还会赋给另一个引用类型的变量,从而让外部程序可以访问该对象。

复用其他构造器的初始化代码:(通过this来调用复用的重载构造器的初始化代码)

public class Apple
{
  public String name;
  public String color;
  public double weight;
  public Apple() {}
  public Apple(String name, String color)
  {
    this.name = name;
    this.color = color;
  } 
  public Apple(String name, String color, double weight)
  {
    this(name, color);  // <------------- 通过this引用调用构造函数
    this.weight = weight;
  }
}      

使用this调用另一个重载的构造函数只能在构造器中使用,而且必须作为构造器执行体的第一条语句。

定义初始化块

初始化块是Java类里可出现的第4种成员,一个类里可以有多个初始化块,相同类型的初始化块之间有顺序:前面定义的初始化块先执行,后面定义的初始化块后执行。

[修饰符] {
  // 初始化块的可执行代码
}      

初始化块的修饰符只能是static(静态初始化块),或者完全省略这个修饰符。当创建Java对象时,系统总是先调用该类里定义的初始化块,如果一个类里定义了2个普通初始化块,则前面定义的初始化块先执行,后面定义的后执行。初始化块只在创建Java对象时隐式执行,而且在执行构造器之前执行。

public class Person
{
  {
    int a = 6;
    if(a > 4){
      System.out.println("Person初始化块:局部变量a的值大于4");
    }
    System.out.println("Person的初始化块");
  }
  {
    System.out.println("Person的第二个初始化块");
  }
  public Person()
  {
    System.out.println("Person类的无参数构造器");
  }
  public static void main(String[] args)
  {
    new Person();
  }
}
// Person初始化块:局部变量a的值大于4
// Person的初始化块
// Person的第二个初始化块
// Person类的无参数构造器      

普通初始化块、声明实例变量指定的默认值都可认为是对象的初始化代码,它们的执行顺序与源程序中的排列顺序相同。

public class InstanceInitTest
{
  {
    a = 6;
  }
  int a = 9;
  public static void main(String[] args)
  {
    System.out.println(new InstanceInitTest().a); // 输出为9
  }
}      

当Java创建一个对象时,系统先为该对象的所有实例变量分配内存(前提是该类已经被加载过了),接着程序开始对这些实例变量执行初始化,其初始化顺序是:先执行初始化块或声明实例变量时指定的初始值(这两个地方指定初始值的执行允许与它们在源代码中的排列顺序相同),再执行构造器里指定的初始值。

与构造器类似,创建一个Java对象时,不仅会执行该类的普通初始化块和构造器,而且系统会一直上溯到java.lang.Object类,先执行java.lang.Object类的初始化块,开始执行java.lang.Object的构造器,依次向下执行其父类的初始化块,开始执行其父类的构造器…最后才执行该类的初始化块和构造器,返回该类的对象。

实际上初始块是一个假象,使用javac命令编译Java类后,该Java类中的初始化块会消失,初始化块被还原到每个构造器中,且位于构造器所有代码的前面。

如果定义初始化块时使用static修饰符,则变为静态初始化块,也被称为类初始化块,通常用于对类变量执行初始化。系统将在类初始化阶段执行静态初始化块。因此静态初始化块总是比普通初始化块先执行。

静态初始化块属于类的静态成员,同样需要遵循静态成员不能访问非静态成员的规则,因此静态初始化不能访问非静态成员,包括不能访问实例变量和实例方法。静态初始化块和声明静态成员变量时所指定的初始值都是该类的初始化代码,它们的执行顺序与源代码中的排列顺序相同。

与普通初始化块类似的是,系统在类初始化阶段执行静态初始化块时,不仅会执行本类的静态初始化块,而且还会一直上溯到java.lang.Object类(如果它包含静态初始化块),先执行java.lang.Object类的静态初始化(如果有),然后执行其父类的静态初始化块…最后才执行该类的静态初始化块,经过这个过程,才完成了该类的初始化过程。只有当类初始化完成后,才可以在系统中使用这个类,包括访问这个类的类方法、类变量或者这个类来创建实例。

内部类(包括接口、枚举)

在Java类里只能包含成员变量、方法、构造器、初始化块、内部类5种成员。其中static可以修饰成员对于类成员变量、方法、初始化块、内部类。对于访问类变量和类方法,即使某个实例为null,它也可以访问它所属类的类成员。

public class NullAccessStatic
{
  private static void test(){
    System.out.println("static修饰的类方法");
  }
  public static void main(String[] args){
    // 定义一个NullAccessStatic变量,其值为null
    NullAccessStatic nas = null;
    nas.test(); // 使用null对象调用所属类的静态方法
  }
}      

Java允许将一组功能相关的类放在同一个package下,从而组成逻辑上的类库单元。如果希望把一个类放在指定的包结构下,应该在Java源程序的第一个非注释行放置如下格式的代码:​

​package packageName;​

​​位于包中的类,在文件系统中也必须有与包名层次相同的目录结构。如果没有显示指定package语句,则处于默认包下。

父包和子包之间确实表示了某种内在的逻辑关系,例如前面介绍的org.crazyit.elearnging父包和org.crazyit.elearning.student子包,确实可以表明后者是前者的一个模块。但是父包和子包在用法上不存在任何关系,如果父包中的类需要使用子包中的类,则必须使用子包的全名而不能省略父包部分。

// 在lee.HelloTest类中创建lee.sub.Apple类的对象-
lee.sub.Apple a = new lee.sub.Apple()      

import可以向某个Java文件中导入指定包层次下某个类或全部类,import语句应该出现在package语句之后、类定义之前。

import.lee.*; // 导入lee包下所有类,但是lee.sub子包里的类无法导入      

JDK 1.5以后增加了静态导入方法,用于导入指定类的单个静态成员变量、方法或全部静态成员变量、方法:​

​import static package.subpackage...ClassName.fieldName|methodName; 或 import static package.subpackage...ClassName.*;​

​ 使用import可以省略包名,import static连类名都可以省略:

import static java.lang.System.*;
import static java.lang.Math.*;
public class StaticImportTest
{
  public static void main(String[] args)
  {
    // out是java.lang.System类的静态成员变量,代表标准输出
    out.println(PI);
    // 直接调用Math类的sqrt静态方法
    out.println(sqrt(256));
  }
}      

Java默认为所有源文件导入java.lang包下的所有类

封装

访问控制符:​

​private->default->protected->public​

​​ default没有对应的访问控制符,当不使用任何访问控制符来修饰类或类成员,系统默认使用该访问控制级别。

访问控制级别表

private default protected public
同一个类中 ok ok ok ok
同一个包中 ok ok ok
子类中 ok ok
全局范围内 ok

对于类中的成员(包括成员变量、方法和构造器等):

  • private:成员只能在当前类的内部被访问
  • default:default访问控制的成员可以被相同包下的其他类访问
  • protected:成员可以被同一个包中的其他类访问,也可以被不同包中的子类访问
  • public:成员可以被所有类访问

    对于外部类而言,只有两种访问控制级别:public和默认。使用public修饰的外部类可以被所有类使用;不使用任何访问控制符修饰的外部类只能被同一个包中的其他类使用。

继承

Java继承通过extends关键字来实现

修饰符 class SubClass extends SuperClass
{
  // 类定义部分
}      

Java的子类不能获得父类的构造器。如果定义一个Java类并未显示指定这个类的直接父类,则这个类默认扩展java.lang.Object类。因此,java.lang.Object类是所有类的父类,要么是其直接父类,要么是其间接父类。

方法重写override

方法重写:子类包含与父类同名方法,但是要遵循两同两小一大规则,并且覆盖方法和被覆盖方法要么是类方法,要么是实例方法,不能一个是类方法,一个是实例方法。在子类中可以使用super(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)来调用父类中被覆盖的方法。

  • 两同:方法名相同、形参列表相同
  • 两小:子类方法返回值类型应比父类方法返回类型更小或相等,子类方法声明抛出异常应比父类方法声明抛出的异常类更小或相等
  • 一大:子类方法的访问权限应比父类方法的访问权限更大或相等

如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此子类无法访问该方法,也不能重写该方法。即使是定义同样的private访问权限,也无法重写父类的方法。

父类和子类方法可能发生重载,子类获得父类方法,且子类定义了一个与父类方法有相同的方法名但参数列表不同的方法,就会形成父类方法和子类方法的重载。

子类不会获得父类的构造器,但子类构造器里可以使用super调用父类构造器的初始化代码。

class Base
{
  public double size;
  public String name;
  public Base(double size, String name)
  {
    this.size = size;
    this.name = name;
  }
}
public class Sub extends Base
{
  public String color;
  public Sub(double size, String name, String color)
  {  // 通过super调用类调用父类构造的初始化过程
    super(size, name);
    this.color = color;
  }
}      

super调用父类构造器也必须出现在子类构造器执行体的第一行,所以this调用和super调用不会同时出现。不管是否使用super调用来执行父类构造器的初始化代码,子类构造器总会调用父类构造器一次。子类构造器调用父类构造器分如下几种情况:

  • 子类构造器执行体的第一行使用super显式调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器。
  • 子类构造器执行体的第一行代码使用this显式调用本类中重载的构造器,系统将根据this调用里传入的实参列表调用本类中的另一个构造器。执行本类中另一个构造器时即会调用父类构造器。
  • 子类构造器执行中既没有super调用,也没有this调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。

    不管上面哪种情况,当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行;不仅如此,执行父类构造器时,系统会再次上溯执行其父类构造器…依次类推,创建任何Java对象,最先执行的总是java.lang.Object类的构造器。

多态

Java引用变量有两个类型:一个是编译时类型,一个运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。

强制类型转换:(type) variable

Object hello = "Hello";  // 编译时类型为Object
hello instanceof Object  -> true
hello instanceof String  -> true   // Object<->String父子继承关系 hello运行时类型为String
hello instanceof Math    -> false   // Object<->Math父子继承关系,不会出现编译错误  hello运行时类型为String和Math不存在类、其子类和实现类的实例关系
hello instanceof Comparable  -> true //String实现了Comparable接口
String a = "Hello";
a instanceof Math // 编译无法通过