面向对象上篇
前言
面向对象是程序开发中最重要的部分,无论是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
|
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
| #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
#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
| public void methodName(type1 paramName1,type1 paramName2){ }
- (void) methodName:(type1)paramName1 withType2:(type2)paramName2 { }
|
- 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
| Student student = new Student(); student.getName(); student.setName("宇行信");
调用者.方法名(参数...);
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方法。
关于对象的所有权和释放有四个原则:
- 任何你创建的对象你都获得其所有权。(包括 alloc ,new ,copy等关键字获得的对象)
- 通过retain获得对象的所有权。
- 如果你不需要一个对象了,你必须释放所有权。
你不能释放你没有所有权的对象。
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的内存管理机制是非常必要的。后面专题打算细说内存管理。
接着上面说:
- assign:指定对属性只是进行简单赋值,不更改对所赋的值引用计数。如前面提到的基本类型:int、float、double等。
- atomic(nonatomic):指定合成的存取方法是否线程安全。大多说单线程环境我们考虑使用nonatomic来提高存取方法的访问性能。
- copy:当调用setter方法赋值时会被赋值的对象复制一个副本,再将副本赋值给成员变量,copy指示符会将原成员变量所引用的对象的引用计数减1。当成员变量的类型是可变类型,或其子类是可变类型,被赋值的对象有可能在赋值之后被修改,如果程序不需要这种修改影响setter方法设置的成员变量的值,此时就应该考虑使用copy指示符。如:NSString、NSArray、NSSet、NSDictionary、NSData、NSCharacterSet、NSIndexSet。
- getter、setter:用于为合成的getter、setter方法指定自定义方法名,如上面代码示例中针对属性gender。
- readonly、readwrite:一个是只读,所以只合成getter方法,不再合成setter方法,另外就是读写,两者都合成。
- retain:使用retain指示符定义属性时,当把某个对象赋值给该属性时,该属性原来所引用的对象计数-1,被赋值对象的引用计数+1。一般我们在未开启ARC模式的时候,使用这个指示符,一旦启用ARC,这个指示符就不再被使用。
- strong、weak:这个类似Java中强引用和弱引用的区别。开启ARC之后使用起来很方便。
- 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:属性名
setValue:属性值 forKeyPath:属性名路径 valueForKeyPath: 属性名路径
|
- KVO:键值监听,由NSKeyValueObserving协议提供支持,有如下方法:
1 2 3 4
| addObserver:forKeyPath:options:context: removeObserver:forKeyPath: removeObserver:forKeyPath:context observeValueForKeyPath:ofObject:change:context:
|
- 对象初始化:Java通常定义构造函数,如果没有定义则使用默认构造函数,而Object-c我们可以重写父类init方法,但更多情况是定义initWithXXX方法,如上面例子中,当然需要记着调用父类初始化。关于初始化,这里不得不提一下,Java中的静态初始化块,它优先于构造函数执行。
- 对于继承:Java子类定义的变量名如果和父类定义的变量名重复,则会子类覆盖父类,而在Object-c中如果是在定义部分的成员变量重复则会报编译错误,而在实现部分定义的成员变量名称重复会发生覆盖父类,所以注意一下。
- 对于多态:我们都知道,变量类型分编译时类型和运行时类型,编译类型由编译时声明该变量的类型决定,而运行时类型由实际赋值给该变量的对象决定,如果两者类型不一致,就发生了多态。注意多态是建立在继承的基础上,我们知道:编译Object-c代码时,指针变量只能调用声明该变量时所使用的类中的方法,如:NSObject* parent = [SLStudent alloc] init],代码定义了一个变量parent,则这个变量只能调用NSObject中的方法,而不能调用SLStudent中的方法,为了解决这个问题,Object-c中提供了id类型,程序可以对任何对象或任何类型的指针变量赋值为id类型的变量,而且使用id类型的变量可以调用该变量实际所指对象的方法。
- 关于两者对于强制类型转换和向上转型不再多说
- 判断指针变量或者变量的类型:java中的运算符instanceof,对于的Object中的方法是
1 2 3
| - (BOOL) isKindOfClass:clazz - (BOOL) isSubclassOfClazz:clazz - (BOOL) isMemberOfClass:clazz
|
- 关于对象的输出打印:Java中toString方法对于Object-c中的desription方法,两者都是父类Object/NSObject的方法,一般我们打印类信息,重写此方法即可。
- 这里要说一下Java中final这个关键字,它有点类型C#中的sealed关键字,修饰基本类型成员变量不可被重新赋值,修饰引用类型变量保证这个引用所引用的地址不可改变,即一直引用这个对象,但是这个对象完全可以改变;修饰方法则表示不希望子类去覆写这个方法;修饰类时表示该类不可以有子类;
分享到:
新浪微博
微信
QQ空间
QQ好友
豆瓣
有道云笔记
Facebook
Pocket
Google+
Tumblr
Instapaper
Linkedin
取消
移动开发者/技术爱好者/喜欢开源与分享,你也可以关注微信公众号MobDevGroup,移动开发在线分享:MobDevGroup