面向对象上篇

前言

面向对象是程序开发中最重要的部分,无论是Java还是Object-c都是一门面向对象的语言,我们这里对比总结这两种语言,体会一下面向对象的思想,慢慢会觉得语言只是工具,思想才是灵魂。

我还是喜欢拿代码开篇,这样好展开总结一些概念性的东西,而不至于理论说多太乏味。说干就干,先上代码:

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Student类
*/
public class Student {
//私有成员变量
private String mName;

protected String age;

public static String grade;

//构造函数
public Student(String name,String age){
this.name = name;
this.age = age;
}

public String getName(){
return mName;
}

public void setName(String name){
this.mName = name;
}

//类方法
public static setGrade(String grade){
grade = grade;
}
}

Object-c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//SLStudent.h文件
#import <Foundation/Foundation.h>

@interface SLStudent : NSObject
{
NSInteger _age;

@private
NSString *_name;
}

- (id) initWithStudent:(NSString *)name age:(NSInteger)age;

- (void) setName : (NSString *) name;

- (NSString *) name;

@property (nonatomic,copy,getter=xxx,setter=yyy:) NSString *gender;
@end

//SLStudent.m文件
#import <Foundation/Foundation.h>
#import "SLStudent.h"

//全局变量模拟类变量,如单例模式使用到
static NSString *grade = nil;
@implementation SLPerson

@synthesize gender = _gender;

- (id) initWithStudent:(NSString *)name age:(NSInteger)age
{
if(self = [super init])
{
_name = name;
_age = age;
}
return self;
}

- (void) setName : (NSString *) name
{
self->_name = name;
}

- (NSString *) name
{
return _name;
}

+ (NSString *) grade
{
return grade;
}

+ (void) setGrade : (NSString *) newGrade
{
grade = newGrade;
}

对比上面的代码做一些说明:

  • 上面分别用Java代码和Object-c实现两个Student类,分别对应的属性有name,age,grade.
  • Java用class这个关键字来定义类,而Obect-c这里和Java不一样的地方,它分为两个文件:.h和.m文件,其中.h是声明文件,.m是类的实现文件,我们一般把方法和成员变量声明在.h文件中,而方法的实现放在.m文件中。我们用@interface作为声明类的开始,@end作为结尾,同样@implementation作为实现类的开始,@end为结束。
  • Java中用static这个关键字修饰方法来表示类方法,修饰变量来表示类变量。否则就是实例变量;而我们在之前的一篇文章中说static关键字修饰变量和方法在C语言中则表示只能在当前文件中使用。所以Object-c中修饰类方法则是用“+”方法来表示,实例方法则用“-”表示。
  • Java中声明私有成员变量建议以m开头,而Object-c中则建议以下划线_开头。
  • 关于方法的定义:
1
2
3
4
5
6
7
8
9
10
//Java
public void methodName(type1 paramName1,type1 paramName2){
//方法体
}
//Object-c实现
- (void) methodName:(type1)paramName1 withType2:(type2)paramName2
{
//方法体
}
//其中methodName方法名字,type类型,paramName形参,这里提到withType2是指形参标签,Object-c中更好的理解形参的作用而引入,且只有从第二个参数开始有。
  • Java中类默认都继承Object,用关键字extends表示,一般省略,而Object-c中都继承NSObject,用:表示,一般不能省略。
  • Object-c中对于没有在.m文件中声明的方法和变量只能在该实现类中使用,只有在.h文件中定义的内容才可以暴漏在外面供用户调用,这也体现了良好的封装意识。
  • 苹果公司建议在类前面加标识,也即这里的SL前缀,cocoa框架的类大多以NS开头,据说是乔布斯当时创立NextStep,把Object-c做为他们的主要开发语言,NS就是来自公司的缩写。
  • 对于类的创建和方法调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
//Java
Student student = new Student();
student.getName();
student.setName("宇行信");
//其语法格式:
调用者.方法名(参数...);

//Object-c
SLStudent *student = [SLStudent alloc] init];
[student name];
[student setName:@"宇行信"];
//其语法格式:
[调用者 方法名:参数 形参标签:参数...];
  • Java中的this关键字和Object-c中的self关键字都是表示调用该方法或者变量的对象。而super关键字代表其父类的实例。
  • Object-c中可以拿id类型作为所有对象的类型,也就是说,任意对象都可以赋值给id类型的变量。
  • 对于方法中的可变形参,两种语言都支持,具体可以脑补一下。
  • Java用static来声明作为类变量,但是在Object-c中不支持类变量,但是可以模拟,如上面变量gender,具体用处我们可以在设计单例模式中用到。
  • 对于封装:
    Java提供了private—->default—->protected—->public,四个的访问级别依次从小到大。
private default protected public
同一个类 Y Y Y Y
同一个包 N Y Y Y
子类中 N N Y Y
全局范围内 N N N Y

Object-c同样提供了四个访问控制符:@private、@package、@protected、@public
这里说下@package(与映像访问权限相同):可以在当前类以及当前类实现当前类同一个映像的任意地方访问。对于映像这里表示编译后生成的同一个框架或同一个执行文件。

@private @package @protected @public
同一个类 Y Y Y Y
同一个映像 N Y N Y
子类中 N N Y Y
全局范围内 N N N Y
  • 对于上面提到的面向对象的特征封装,那么如何访问呢,这就是我们接下来说的合成存取方法,在Java中就是getXXX()、setXXX()方法。objec-c从2.0开始支持@property关键字来定义属性,使用该关键字定义的属性无须放在类声明部分的花括号里,而是直接放在@interface和@end之间,同时在类实现部分使用@synthesize指令声明该属性即可(后面这个关键字渐渐不使用了),如上面的gender,这样可以省略get和set方法的编写,其实这是框架帮我们写了,底层还是调用的getter和setter方法。
  • 这里说一下Object-c中@property和类型之间用括号添加的一些额外指示符。除了代码中用到的nonatomic,copy,还有atom、assign、getter、setter。下面单独说这几个:
    在说之前不得不提的就是Object-c中的内存管理,在这之前Object-c一直走的是MRC(手动引用计数),也就是程序员自己去管理内存,申请和释放,Object-c引入了一个引用计数的概念,只要这个变量的引用计数不为0,就不应该被回收,一旦变成0,就要调用release方法进行释放,否则造成内存泄露,而持有对象来增加引用计数的方法有很多,如alloc、new、copy、multablecopy、retain,而释放对象的有release,废弃对象就用dealloc方法。
    关于对象的所有权和释放有四个原则:
  1. 任何你创建的对象你都获得其所有权。(包括 alloc ,new ,copy等关键字获得的对象)
  2. 通过retain获得对象的所有权。
  3. 如果你不需要一个对象了,你必须释放所有权。
  4. 你不能释放你没有所有权的对象。
    Objective-C的内存管理介于C/C++和Java、C#直接,不像C/C++语言内存管理全部需要程序员一手包办,也不像Java C#语言有那么完备的内存垃圾回收器。(Objective-C 2.0有GC机制,不过不支持iOS)。那他是怎么管理内存的呢?通过引用计数进行管理的。PS:在iOS5后增加了Automatic Reference Counting(ARC 自动引用计数)特性,这样程序员不需要自己操心管理内存了,ARC和GC不一样,ARC是编译器的行为。ARC后面再讲。不过熟悉Objective-C的内存管理机制是非常必要的。后面专题打算细说内存管理。

    接着上面说:

    1. assign:指定对属性只是进行简单赋值,不更改对所赋的值引用计数。如前面提到的基本类型:int、float、double等。
    2. atomic(nonatomic):指定合成的存取方法是否线程安全。大多说单线程环境我们考虑使用nonatomic来提高存取方法的访问性能。
    3. copy:当调用setter方法赋值时会被赋值的对象复制一个副本,再将副本赋值给成员变量,copy指示符会将原成员变量所引用的对象的引用计数减1。当成员变量的类型是可变类型,或其子类是可变类型,被赋值的对象有可能在赋值之后被修改,如果程序不需要这种修改影响setter方法设置的成员变量的值,此时就应该考虑使用copy指示符。如:NSString、NSArray、NSSet、NSDictionary、NSData、NSCharacterSet、NSIndexSet。
    4. getter、setter:用于为合成的getter、setter方法指定自定义方法名,如上面代码示例中针对属性gender。
    5. readonly、readwrite:一个是只读,所以只合成getter方法,不再合成setter方法,另外就是读写,两者都合成。
    6. retain:使用retain指示符定义属性时,当把某个对象赋值给该属性时,该属性原来所引用的对象计数-1,被赋值对象的引用计数+1。一般我们在未开启ARC模式的时候,使用这个指示符,一旦启用ARC,这个指示符就不再被使用。
    7. strong、weak:这个类似Java中强引用和弱引用的区别。开启ARC之后使用起来很方便。
    8. unsafe_unretained:这个与上面的weak基本类似,用的比较少。
    • 前面使用@property、@synthesize合成setter和getter方法后,我们可以简化使用点语法来访问属性和对属性赋值。也就是只要该属性有setter和getter方法,都可以通过点语法来设置对象的属性值。
  • KVC:键值编码,由NSKeyValueCoding协议提供支持,有如下方法:
1
2
3
4
5
6
7
8
9
10
11
12
setValue:属性值 forKey:属性名        //指定属性设值。
valueForKey:属性名 //获取指定属性的值。

setValue:属性值 forUndefinedKey:属性名 //找不到对象属性会执行该值。
valueForUndefinedKey:属性名 //找不到属性会执行这个方法。
//一般我们需要重写这两个方法来避免发生异常。

setNilValueForKey:属性名 //为不接受nil的属性设置nil时执行该方法,如int等;

//key路径
setValue:属性值 forKeyPath:属性名路径 //根据key路径设置属性,如[student setValue:@"宇行信" forKeyPath:@"student1.name"];
valueForKeyPath: 属性名路径 //根据key路径获取属性值 [student valueForKeyPath:@"student1.name"];
  • KVO:键值监听,由NSKeyValueObserving协议提供支持,有如下方法:
1
2
3
4
addObserver:forKeyPath:options:context:   //注册一个监听器用于监听指定key路径
removeObserver:forKeyPath: //为key路径删除指定的监听器
removeObserver:forKeyPath:context //为key路径删除指定的监听器,多了一个context参数
observeValueForKeyPath:ofObject:change:context: //当指定key路径属性值发生变化,执行该方法。
  • 对象初始化:Java通常定义构造函数,如果没有定义则使用默认构造函数,而Object-c我们可以重写父类init方法,但更多情况是定义initWithXXX方法,如上面例子中,当然需要记着调用父类初始化。关于初始化,这里不得不提一下,Java中的静态初始化块,它优先于构造函数执行。
1
2
3
static{

}
  • 对于继承:Java子类定义的变量名如果和父类定义的变量名重复,则会子类覆盖父类,而在Object-c中如果是在定义部分的成员变量重复则会报编译错误,而在实现部分定义的成员变量名称重复会发生覆盖父类,所以注意一下。
  • 对于多态:我们都知道,变量类型分编译时类型和运行时类型,编译类型由编译时声明该变量的类型决定,而运行时类型由实际赋值给该变量的对象决定,如果两者类型不一致,就发生了多态。注意多态是建立在继承的基础上,我们知道:编译Object-c代码时,指针变量只能调用声明该变量时所使用的类中的方法,如:NSObject* parent = [SLStudent alloc] init],代码定义了一个变量parent,则这个变量只能调用NSObject中的方法,而不能调用SLStudent中的方法,为了解决这个问题,Object-c中提供了id类型,程序可以对任何对象或任何类型的指针变量赋值为id类型的变量,而且使用id类型的变量可以调用该变量实际所指对象的方法。
  1. 关于两者对于强制类型转换和向上转型不再多说
  2. 判断指针变量或者变量的类型:java中的运算符instanceof,对于的Object中的方法是
1
2
3
- (BOOL) isKindOfClass:clazz   //判断该对象是否为clazz或其子类的实例
- (BOOL) isSubclassOfClazz:clazz //判断该对象是否为clazz子类的实例
- (BOOL) isMemberOfClass:clazz //判断该对象是否为该类的实例
  • 关于对象的输出打印:Java中toString方法对于Object-c中的desription方法,两者都是父类Object/NSObject的方法,一般我们打印类信息,重写此方法即可。
  • 这里要说一下Java中final这个关键字,它有点类型C#中的sealed关键字,修饰基本类型成员变量不可被重新赋值,修饰引用类型变量保证这个引用所引用的地址不可改变,即一直引用这个对象,但是这个对象完全可以改变;修饰方法则表示不希望子类去覆写这个方法;修饰类时表示该类不可以有子类;
分享到:
移动开发者/技术爱好者/喜欢开源与分享,你也可以关注微信公众号MobDevGroup,移动开发在线分享:MobDevGroup