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();
    }
}

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容

  • 第四章 面向对象 面向对象思想 面向对象思想的引入前面我们讲过数组,当有多个数组都需要遍历时,我们可以将遍历的代码...
    chongsheng阅读 548评论 0 0
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,783评论 1 10
  • 123.继承 一个类可以从另外一个类继承方法,属性和其他特征。当一个类继承另外一个类时, 继承类叫子类, 被继承的...
    无沣阅读 1,383评论 2 4
  • 楼下的A姐来我们屋小坐,拿了桔子,桔子装在四方形的一个小纸筐里。 “给你们装桔子皮的。”A姐说。 “自己叠的?” ...
    铅笔芒种阅读 558评论 0 1
  • 今天,我们的科学老师本来是想给我们看一段关于科学的视频的,她发现我们一5班教室的电脑有问题,于是她就给我们看了一...
    严当当阅读 234评论 0 0