4.面向对象上

以下是《疯狂Java讲义》中的一些知识,如有错误,烦请指正。

类和对象

定义类

[修饰符] class 类名 {….类体…..}
  • Java类名通常以大写字母开头,如果类名称由多个单词组成,则每个单词的首字母均应为大写。
  • 类的修饰符可以是public、final、abstract; 或省略这三个。
  • 一个类里可以包含三种最常见的成员:构造器、属性、方法。注意如果没有为一个类编写构造器,该系统会自动为该类提供一个构造器;一旦程序员提供了一个构造器,系统将不再为该类提供构造器。

定义成员变量

[修饰符] 类型  成员变量名[=默认值];
  • 修饰符: 可以是public、protected、private、static、final,其中前三个只能出现其一,并可与后两个组合修饰。
  • 可以是基本类型或者引用类型
  • 成员变量名:采用匈牙利标记法:在以Pascal标记法的变量前附加小写序列说明该变量的类型。在Java我们一般使用匈牙利标记法,基本结构为scope_typeVariableName,它 使用1-3字符前缀来表示数据类型,3个字符的前缀必须小写,前缀后面是由表意性强的一个单词或多个单词组成的名字,而且每个单词的首写字母大写,其它字 母小写,这样保证了对变量名能够进行正确的断句。例如,定义一个整形变量,用来记录文档数量:intDocCount,其中int表明数据类型,后面为表 意的英文名,每个单词首字母大写。这样,在一个变量名就可以反映出变量类型和变量所存储的值的意义两方面内容,这使得代码语句可读性强、更加容易理解。 byte、int、char、long、float、 double、boolean和short。

定义方法

[修饰符] 方法返回值类型 方法名(形参列表) {….方法体….}
  • 修饰符:同成员变量定义
  • 返回值类型:可以是基本类型或者引用类型,需要配合return。无返回值使用void
  • 方法名:方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头。 同变量命名规则。
  • static修饰符修饰的成员属于类本身。类变量、类方法、静态变量、静态方法。静态成员不能直接访问非静态成员。如果静态方法中需要访问普通方法,只能重新创建一个对象。作为调用者调用改方法。

定义构造器

[修饰符] 构造器名(形参列表) {……}
  • 修饰符:可以是public protected private
  • 构造器名必须和类名相同
  • 类的构造器如果定义了返回值类型或者加void,这个构造器将被作为方法处理。
  • 实际上,类的构造器是有返回值的,当使用new关键字来调用构造器时,构造器返回该类的实例。构造器的返回值是隐式的

创建对象

// 定义p变量的同时,为p变量赋值
// 引用型变量p里存放的仅仅是一个引用,它指向实际的对象
// 引用变量存放在栈内存中,实际对象存放在堆内存中
Person p = new Person();
// 多个引用变量指向同一个对象
Person p2=p;

如果堆内存里的对象没有任何变量指向该对象,这个对象就变成了垃圾,Java的垃圾回收机制将回收该对象,释放该对象所占的内存。即切断引用变量和对象之间的关系,将引用变量赋值为null。

调用实例/方法

类.类变量|方法
实例.实例变量|方法

对象的this引用
this 关键字总是指向调用该方法的对象.

  • 构造器中引用该构造器执行初始化的对象
  • 在方法中引用调用该方法的对象

特别的,Java允许允许一个成员直接调用另一个成员,此时可以省略this前缀。一般来说,如果调用static修饰的成员时省略了主调(调用成员、方法的对象),默认使用该类作为主调;如果调用没有用static修饰的成员时省略了主调,默认使用this作为主调.

如果普通方法中有个局部变量和成员变量同名,程序又需要在该方法中访问被覆盖的成员变量,则必须使用this前缀。

方法

Java中方法不能独立存在,必须属于一个类或对象。

方法的参数传递机制
值传递:将实际参数值的副本传入方法内,参数本身不会受到任何影响。
特别注意:引用类型的参数传递仍然是值传递。

class DataWrap
{
    int a;
    int b;
}
public class ReferenceTransferTest
{
    public static void swap(DataWrap dw)
    {
        // 下面三行代码实现dw的a、b两个成员变量的值交换。
        // 定义一个临时变量来保存dw对象的a成员变量的值
        int tmp = dw.a;
        // 把dw对象的b成员变量值赋给a成员变量
        dw.a = dw.b;
        // 把临时变量tmp的值赋给dw对象的b成员变量
        dw.b = tmp;
        System.out.println("swap方法里,a成员变量的值是"
            + dw.a + ";b成员变量的值是" + dw.b);
        // 把dw直接赋为null,让它不再指向任何有效地址。
        dw = null;
    }
    public static void main(String[] args)
    {
        DataWrap dw = new DataWrap();
        dw.a = 6;
        dw.b = 9;
        swap(dw);
        System.out.println("交换结束后,a成员变量的值是"
            + dw.a + ";b成员变量的值是" + dw.b);//9,6;9,6
    }
}

上面的程序中dw仅仅是一个引用变量,在main栈区会创建dw引用变量,之后再swap栈区会创建其副本,完成对堆内存中的对象的引用。在swap方法中操作堆内存中的DataWrap对象,因此最后在main方法中dw引用的DataWrap成员变量的值交换了。

形参个数可变的方法
如果在定义方法时,在最后一个参数的类型后增加三点…,则表明该形参接受多个参数值,多个参数值被当成数组传入。长度可变的形参只能位于最后一个参数,并一个方法里只能有一个可变长度的参数。

public class Varargs
{
    // 定义了形参个数可变的方法
    public static void test(int a , String... books)
    {
        // books被当成数组处理
        for (String tmp : books)
        {
            System.out.println(tmp);
        }
        // 输出整数变量a的值
        System.out.println(a);
    }
    public static void main(String[] args)
    {
        // 调用test方法
        test(5 , "疯狂Java讲义" , "轻量级Java EE企业应用实战");
    }
}

也可以使用数组形参来定义方法

public static void test(int a , String[] books)

但是调用时必须传入数组:

test(5 , new String[] {"疯狂Java讲义" , "轻量级Java EE企业应用实战"});

递归方法
递归就是在方法中再次调用自己。递归一定要向已知方向递归.

方法重载
Java 允许在一个类里定义多个同名方法,只要形参列表不同即可.

成员变量和局部变量

  • 成员变量指的是在类范围里定义的变量;局部变量指的是在一个方法内定义的变量。
  • 不管是成员变量还是局部变量都遵守相同的命名规则。
  • 局部变量可分为三种:形参、方法局部变量、代码块局部变量,除了形参外,其他局部变量都必须显式地初始化。
  • 成员变量不用显式初始化,只要定义了一个类属性或实例属性,系统会在类的准备阶段或者创建该类实例时,进行默认初始化,成员变量默认初始化是复制规则与数组动态初始化时数组元素赋值规则完全相同。
  • Java允许局部变量和成员变量重名。这样局部变量会覆盖成员变量,可以通过this来调用实例的属性.

成员变量初始化

  1. 当类被加载时,类成员就在内存中分配了一块空间,并指定默认值。
  2. 当对象被创建时,实例成员就在内存中分配了内存空间,并指定初始值。
  3. 为实例变量赋值。
    实例变量与实例共存亡;类变量与类本身共存亡。
Person p1 = new Person();
Person p2 = new Person();
// 为实例变量赋值
p1.name = "张三";
p2.name = "孙悟空";
//为类变量赋值
p1.eyeNum = 2;
p2.eyeNum = 3;

局部变量的初始化
局部变量仅在方法内有效。它总是保存在所在方法的栈内存中。如果局部变量是基本类型的变量,直接把这个变量的值保存在该变量对应的内存中;如果是引用变量,则局部变量保存的是地址,通过该地址引用到该变量实际引用的对象或数组。栈内存中的变量无须系统垃圾回收,当方法执行完成时,局部变量便会自动销毁。

变量使用
局部变量的作用范围越小,它在内存中停留的时间就越短,程序运行性能就越好。

隐藏和封装

访问控制符

  • private 私有的。在同一个类里能被访问。
  • default 默认的,不加任何访问控制符。包访问权限。
  • protected 受保护的。子类中也能访问
  • public 公共的。在任何地方都可以访问

基本原则:

  1. 绝大部分成员变量应该使用private修饰,只有static修饰的、类似全局变量的成员变量才能使用public修饰。工具方法也应该使用private修饰。
  2. 父类的大部分方法可能仅希望被其子类重写,而不希望被外界直接调用,应该使用protected修饰。
  3. 希望暴露出来给其他类自由调用的方法应该使用public修饰。因此类的构造器通过使用public修饰,从而允许在其他地方创建该类的实例。

package
Java允许将一组功能相关的类放在一个package下。

包名应该全部是小写字母,可以使用公司域名倒写来作为包名。

javac -d . Hello.java

以上是在当前路径下编译改文件,结果保存在当前路径新建的包文件夹中。-d用于设置编译生成class文件的保存位置。

java lee.Hello

以上是执行时的命令。注意优先搜索CLASSPATH下的子路径,然后按照与包层次对应的目录结构查找class文件。

当需要导入两个包包含同一个名称的类时,不能使用import,需要使用类的全名

java.sql.Date d = new java.sql.Date();

import 引入包格式。分为两种:

  • 非静态导入,导入的是包下所有的类。如:import package.subpackage.*;
  • 静态导入,导入的是类的静态属性。如:import static package.className.*;

java常用包

  • java.lang.*,系统自动导入该包下所有类
  • java.util.*, 工具类、集合框架
  • java.net.* ,
  • java.io.*,
  • java.text.*,
  • java.sql.*,
  • java.awt.*,
  • java.swing.*.

构造器

构造器是一个特殊的方法,用于在创建对象时执行初始化。注意:当系统执行构造器的执行体之前,系统已经创建了一个对象,只是这个对象还不能被外部程序访问。

构造器重载
构造器的重载和方法的重载一样,都是方法名相同,形参列表不相同。在构造器中可通过this来调用另外一个重载的构造器。

类的继承

Java的继承是单继承,每个子类最多只有一个直接父类。
子类继承父类的语法格式如下:修饰符 class subclass extends superclass {}
子类扩展了父类,将可以获得父类的全部属性和方法,但不能获得父类构造器.

重写父类方法
方法的重写要遵循“两同两小一大”,指的是:方法名相同,形参列表相同。返回值类型更小或相同,抛出的异常更小或相同,访问控制权限要更大。
如果父类方法是private的权限,子类无法重写该方法。

重载与重写?
重载是overload,重写是override。前者是同一个类的多个同名方法之间,后者是发生在子类与父类同名方法之间。当然父类与子类也可能发生重载:子类进程父类方法,并定义了一个与父类方法函数名相同,参数列表不同的方法。

super
通过关键字super来调用父类的被覆盖的实例方法或隐藏属性。没有被覆盖的属性可以直接调用。

调用父类构造器
子类构造器总会调用父类构造器。
如果子类构造器没有显式使用super调用父类构造器,子类构造器默认会调用父类无参数的构造器。
创建一个子类实例时,总会先调用最顶层父类的构造器。

多态

Java 引用变量有两个类型:一个是编译时的类型,一个是运行时的类型,编译时的类型由声明该变量时使用的类型决定,运行时的类型由实际赋给该变量的对象决定。如果编译时类型和运行时的类型不一致,这就有可能出现所谓的多态。
两个相同类型的引用变量,由于它们实际引用的对象的类型不同,当它们调用同名方式时,可能呈现出多种行为特征,这就是多态。

class BaseClass
{
    public int book = 6;
    public void base()
    {
        System.out.println("父类的普通方法");
    }
    public void test()
    {
        System.out.println("父类的被覆盖的方法");
    }
}
public class SubClass extends BaseClass
{
    //重新定义一个book实例变量隐藏父类的book实例变量
    public String book = "轻量级Java EE企业应用实战";
    public void test()
    {
        System.out.println("子类的覆盖父类的方法");
    }
    public void sub()
    {
        System.out.println("子类的普通方法");
    }
    public static void main(String[] args)
    {
        // 下面编译时类型和运行时类型完全一样,因此不存在多态
        BaseClass bc = new BaseClass();
        // 输出 6
        System.out.println(bc.book);
        // 下面两次调用将执行BaseClass的方法
        bc.base();
        bc.test();
        // 下面编译时类型和运行时类型完全一样,因此不存在多态
        SubClass sc = new SubClass();
        // 输出"轻量级Java EE企业应用实战"
        System.out.println(sc.book);
        // 下面调用将执行从父类继承到的base()方法
        sc.base();
        // 下面调用将执行从当前类的test()方法
        sc.test();
        // 下面编译时类型和运行时类型不一样,多态发生
        BaseClass ploymophicBc = new SubClass();
        // 输出6 —— 表明访问的是父类对象的实例变量
        System.out.println(ploymophicBc.book);
        // 下面调用将执行从父类继承到的base()方法
        ploymophicBc.base();
        // 下面调用将执行从当前类的test()方法
        ploymophicBc.test();//子类覆盖的父类方法
        // 因为ploymophicBc的编译类型是BaseClass,
        // BaseClass类没有提供sub方法,所以下面代码编译时会出现错误。
        // ploymophicBc.sub();
    }
}

第三个引用变量比较特殊,编译时类型是BaseClass,运行时类型是SubClass。当调用引用变量的Test方法时,实际执行的是SubClass中覆盖后的Test方法。
子类对象赋给父类引用变量,是向上转型,系统自动完成。当调用引用变量的方法时总是表现出子类方法的行为特征。而不是父类。对于子类中新的方法因为编译时为BaseClass就无法调用了,所以运行时无法调用。
实例变量不具备多态,输出BaseClass类的实例变量。官方的话:引用变量访问实例变量时,总是试图访问编译时类型所定义的成员变量。

引用变量的类型转换
强制类型转换: 类型转换运算符是小括号,语法如下(type)variable
注意:

  • 基本类型之间的转换只能在数值类型之间进行。数值类型和布尔类型之间不能进行类型转换
  • 引用类型之间的转换只能在具有继承关系的两个类型之间进行。如果试图将一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行。否则会引起ClassCastException。可以使用instanceof判断是否可以成功转换。

instanceof
前一个操作通常是一个引用类型的变量,后一个操作通常是一个类(也可以是接口)。如果是返回true 否返回false。

public class InstanceofTest
{
    public static void main(String[] args)
    {
        // 声明hello时使用Object类,则hello的编译类型是Object,
        // Object是所有类的父类, 但hello变量的实际类型是String
        Object hello = "Hello";
        // String与Object类存在继承关系,可以进行instanceof运算。返回true。
        System.out.println("字符串是否是Object类的实例:"
            + (hello instanceof Object));
        System.out.println("字符串是否是String类的实例:"
            + (hello instanceof String)); // 返回true。
        // Math与Object类存在继承关系,可以进行instanceof运算。返回false。
        System.out.println("字符串是否是Math类的实例:"
            + (hello instanceof Math));
        // String实现了Comparable接口,所以返回true。
        System.out.println("字符串是否是Comparable接口的实例:"
            + (hello instanceof Comparable));
        String a = "Hello";
//      // String类与Math类没有继承关系,所以下面代码编译无法通过
//      System.out.println("字符串是否是Math类的实例:"
//          + (a instanceof Math));
    }
}

初始化块

格式:[修饰符]{//可执行代码}
修饰符只能是static。static修饰的初始化块称为静态初始化块。
系统总是先调用初始化块,然后是构造器。初始化块只在创建Java对象时隐式执行。

初始化块是构造器的补充,是一段固定执行的代码,不能接受参数。实际上初始化块在使用javac命令编译类之后,初始化块会被还原到每个构造器中,且位于构造器所有代码的前面。

顺序:以此执行父类的初始化块、构造器、初始化块、构造器...

静态初始化块
静态初始化块将在类初始化阶段执行静态初始化块,比普通初始化块优先级更高。同样也需要上溯父类的静态初始化块。

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("疯狂Java讲义");
        System.out.println("执行Leaf的构造器");
    }
}
public class Test
{
    public static void main(String[] args)
    {
        new Leaf();
        new Leaf();
    }
}

静态初始化块与静态成员变量定义的执行顺序与源程序中排列顺序相同。

你可能感兴趣的:(4.面向对象上)