2015年3月28日星期六

Java开发者常犯的十个错误 - liushaobo

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Java开发者常犯的十个错误 - liushaobo  阅读原文»

翻译自:Top 10 Mistakes Java Developers Make

文章列出了Java开发者最常犯的是个错误。

1.将数组转换为ArrayList

为了将数组转换为ArrayList,开发者经常会这样做:

List<String> list = Arrays.asList(arr);

Arrays.asList()会返回一个ArrayList,但这个ArrayListArrays的私有静态类,不是java.util.ArrayListjava.util.Arrays.ArrayListset(), get(), contains()方法,但没有任何能增加元素的方法,所以它的大小是确定的。
为了创建一个真正的ArrayList,应该这样做:

ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));

ArrayList的构造函数能够接收一个Collection类型,而它也是java.util.Arrays.ArrayList的一个祖先类。

2.检查一个数组是否包含某个值

开发者经常这样做:

Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);

这个的代码可以工作,但没必要首先把数组转换为集合,把数组转换为集合需要额外的时间。可以这样做:

Arrays.asList(arr).contains(targetValue);

或者

for(String s: arr){
if(s.equals(targetValue))
return true;
}
return false;

第一个比第二个的可读性更好。

3.在循环里边删除列表的元素

思考下面的代码,该代码在循环里边删除元素

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
for (int i = 0; i < list.size(); i++) {
list.remove(i);
}
System.out.println(list);

输出如下:


上面的方法有一个严重的问题。当一个元素被移除后,列表的大小减小了,索引也就变了。所以希望利用索引在一个循环里边删除多个元素是做不到的。
你可能知道利用迭代器在一个循环里边删除元素是正确的方法,并且知道Java的foreach循环很像一个迭代器,但事实上不是。思考下面的代码:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));

for (String s : list) {
if (s.equals("a"))
list.remove(s);
}

它将会抛出异常ConcurrentModificationException
下面的代码是可以的:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String s = iter.next();

if (s.equals("a")) {
iter.remove();
}
}

.next()方法必须在调用.remove()方法之前调用。在foreach循环里边,编译器会先调用.remove(),再调用.next(),从而导致异常ConcurrentModificationException。你可能想知道ArrayList.iterator()的源代码。

4.HashTable vs HashMap

根据算法的约定,HashTable是这个数据结构的名字,但在Java里边,HashMap是这个数据结构的名字。Hashtable和 HashMap的一个关键性的不同是,HashTable是同步的,而HashMap不是。所以通常不需要HashTable,HashMap用的更多。
HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap
Top 9 questions about Java Maps

5.使用原始集合类型

在Java里边,原始类型和无界通配符类型很容易混合在一起。以Set为例,Set是一个原始类型,而Set< ? >是一个无界通配符类型。
考虑下面使用原始类型List作为参数的代码:

public static void add(List list, Object o){
list.add(o);
}
public static void main(String[] args){
List<String> list = new ArrayList<String>();
add(list, 10);
String s = list.get(0);
}

上面的代码将会抛出异常:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at ...

使用原始集合类型是很危险的,因为原始集合类型跳过了泛型类型检查,是不安全的。SetSet< ? >Set< Object >之间有很大差别。请参考Raw type vs. Unbounded wildcardType Erasure

6.访问级别

开发者经常使用Public作为类的修饰符,这样可以很简单的通过直接引用得到值,但这是一个非常糟糕的设计。根据经验,分配给成员的访问级别应尽可能的低。
public, default, protected, and private

7.ArrayList vs LinkedList

当开发者不知道ArrayList和LinkedList的区别的时候,通常会使用ArrayList,因为它看起来更熟悉。然而,两者之间有很大的性能差异。简单地说,当有大量的插入/删除操作并且没有太多的随机访问操作的时候,应该使用LinkedList。如果对此不太了解,可参考ArrayList vs. LinkedList

8.可变与不可变

不可变对象有许多的优点,比如简单,安全等等。但是对于每一个不同的值都要有一个独立的对象,过多的对象导致垃圾回收的高消耗。当选择可变与不可变时应该有一个权衡。
通常情况下,可变对象可以用来避免产生过多的中间对象。一个经典的实例就是连接大量的字符串,如果使用不可变的字符串,将会产生大量的需要进行垃圾回收的对象。这会浪费CPU大量的时间,使用可变对象才是正确的方案(比如StringBuilder)。

String result="";
for(String s: arr){
result = result + s;
}

在其它的一些情况下也是需要使用可变对象的,比如将可变对象作为参数传入方法可以使你不需要使用很多语句便可以得到多个结果。另外一个例子是排序和 过滤:当然,你可以写一个方法来接收原始的集合,并且返回一个排好序的集合,但是那样对于大的集合就太浪费了。(来自StackOverFlow的dasblinkenlight的答案)。
Why String is Immutable?

9.父类和子类的构造函数


因为没有定义父类的默认构造函数,在编译的时候会产生错误。在Java里边,如果一个类没有定义构造函数,编译器将会插入一个无参数的默认构造函数。如果在父类里边定义了一个构造函数,在此例中即Super(String s),编译器将不会插入默认的无参数构造函数。这就是上面示例中父类的情形。
子类的构造函数,不管是没有参数还有有参数,都会调用父类的无参构造函数。因为编译器试图把super()插入到子类的两个构造函数中。但是父类默认的构造函数未定义,编译器就会报出这个错误信息。
要修复这个问题,可以简单的通过1)在父类中添加一个Super()构造方法,就像这样:

public Super(){
System.out.println("Super");
}

或者2)移除自定义的父类构造函数,或者3)在子类的构造函数中调用父类的super(value)。
Constructor of Super and Sub

10.”“还是构造函数?

有两种方式构造字符串:

//1. 利用双引号
String x = "abc";
//2. 利用构造函数
String y = new String("abc");

区别在哪?
下面的例子可以给出一个快速的答案:

String a = "ab
Core Data Stack学习笔记 - 超man  阅读原文»

Entity Entities 实体->数据表
一个实体可以表示一个数据模型

1>通过图形化界面可以建立一个模型关系图,可以指定一对多,多对一,多对多的数据关系

-在数据库开发中,少用多对多关系

2>通过工具能够自动生成对应的模型文件

3>数据保存


NSManagedObject 被管理的对象
-开发中设计的实体,本身都是被管理对象的子类

使用CoreData的一个注意事项:如果开发时,修改过数据模型,最好将沙盒中的数据库文件删除!


使用CoreData开发的步骤

1. 首先创建一个Model,利用图形化界面创建各个实体模型
-创建完成之后,通过工具可以自动生成模型文件
-如果修改模型后,可以再次生成模型文件,会覆盖掉之前生成的文件
-自动生成的模型文件,一般不要进去改!


2. 代码
-实例化模型
-使用模型实例化持久化存储调度器
-指定存储调度器保存数据的类型以及路径
-实例化被管理对象的上下文,指定调度器

以上四步代码,在Xcode6已经封装好,可以直接使用,一般不需要程序员编写!

已经使用单例封装,可以直接调用,代码如下:

单例的.h文件

1 #import <Foundation/Foundation.h>
2 #import <CoreData/CoreData.h>
3
4 @interface CZCoreDataTools : NSObject
5
6 + (instancetype)sharedCoreDataTools;
7
8 /**
9 * 使用模型名称&数据库名称初始化Core Data Stack
10 */
11 - (void)setupCoreDataWithModelName:(NSString *)modelName dbName:(NSString *)dbName;
12
13 /**
14 * 被管理对象的上下文
15 */
16 @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
17
18 /**
19 * 保存上下文
20 */
21 - (void)saveContext;
22
23 @end

单例的.m文件

1 #import "CZCoreDataTools.h"
2
3 @implementation CZCoreDataTools
4
5 + (instancetype)sharedCoreDataTools {
6 static CZCoreDataTools *instance;
7
8 static dispatch_once_t onceToken;
9 dispatch_once(&onceToken, ^{
10 instance = [[self alloc] init];
11 });
12 return instance;
13 }
14
15 // 问题:单例我们希望被广泛使用,不能确定模型的名称,同时数据库的文件名也不确定
16 - (void)setupCoreDataWithModelName:(NSString *)modelName dbName:(NSString *)dbName {
17
18 // 1. 实例化数据模型
19 NSURL *modelURL = [[NSBundle mainBundle] URLForResource:modelName withExtension:@"momd"];
20 NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
21
22 // 2. 实例化持久化存储调度器
23 NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
24
25 // 3. 指定保存的数据库文件,以及类型
26 // 数据库保存的URL
27 NSString *dbPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
28 dbPath = [dbPath stringByAppendingPathComponent:dbName];
29 NSURL *dbURL = [NSURL fileURLWithPath:dbPath];
30
31 [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:dbURL options:nil error:NULL];
32
33 // 4. 被管理对象的上下文
34 _managedObjectContext = [[NSManagedObjectContext alloc] init];
35 // 指定上下文的持久化调度
36 [_managedObjectContext setPersistentStoreCoordinator:psc];
37 }
38
39 - (void)saveContext {
40 [self.managedObjectContext save:NULL];
41 }
42
43
44 @end

// 程序启动时 初始化Core Data Stack

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// 初始化数据库
[[CZCoreDataTools sharedCoreDataTools] setupCoreDataWithModelName:@"Model" dbName:@"my.db"];

return YES;
}

3. NSEntityDescription实体描述对象,用来描述一个实体
使用方法,如果要新建一条记录,需要使用实体描述对象

// 表示要插入一条记录
// 插入Person实体描述的记录
// 插入之后,实体信息在context中维护
// 返回一个指定实体名称对应数据模型
[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.appDelegate.managedObjectContext];

// 设置模型属性

// 通知持久化数据调度器,保存数据
[self.managedObjectContext save:NULL];

提示:如果有多个实体关系,可以直接通过属性设置,就能够实现底层数据表的多表级联
程序员不需要关心底层数据库的任何实现细节

4. 在Core Data中查询数据

所有的查询是由 NSFetchedResultsController 来控制的

-首先指定”要查询的实体”以及排序的属性名称
-指定查询控制器的代理
-实现监?p>阅读更多内容

没有评论:

发表评论