【effective java读书笔记】通用程序设计(一)

前言:

从小,我这人读书有个毛病,就是喜欢虎头蛇尾。小说什么的,一直如此。改变这种情况,唯有坚持,才能改变。


正文:

一、第45条,将局部变量的作用域最小化:

1、在第一次使用它的地方声明:

反例:常见有个做法是,变量都定义在顶部。

原因一:

时常在工作中阅读别人的代码就会发现,对阅读者来说,不见得是一个好方法,对此我深有体会。比如,有一个标志位,定义在顶部,如果代码量大,我得回去顶部看标志位是什么再回来,很明显我看完标志位的定义后再找回原来代码使用标志位的地方不容易。多次用到时,各种变化的梳理就更麻烦了。

原因二:

局部变量的生命过长,一个从内存的角度将,变量的创建提前,销毁延后,占用时间长。另一个从安全的角度来讲,如果被意外的调用,例如由于定义变量在外界,导致外界可以使用局部变量,而使得局部变量发生了错误的改变。

2、每个局部变量的声明都包含一个初始化变量表达式:

意味着什么?意味着,声明局部变量的时候,直接对它初始化是最好。如果没有收集到足够初始化的信息,比如初始化一个构造方法的时候的入参,假如暂时还无法获取入参,那么应该延后这个变量的声明。

3、for循环优先于while循环:

书中有这样一个例子:

public class Test {
	public static void main(String[] args) {
		// 获得一个迭代器
		List<Element> c = new ArrayList<Element>();
		for (int i = 0; i < 10; i++) {
			c.add(new Element("bob"+i));	
		}
		Iterator<Element> i = c.iterator();
		while (i.hasNext()) {
			dosomething(i.next());
		}
		
		//	获得一个迭代器
		List<Element> c2 = new ArrayList<Element>();
		for (int i2 = 0; i2 < 15; i2++) {
			c2.add(new Element("heh"+i2));	
		}
		Iterator<Element> i2 = c.iterator();
		while (i.hasNext()) {//BUG
			dosomethingElse(i2.next());
		}
	}

	private static void dosomething(Element next) {
		System.out.println("bob-----"+next.getName());
	}
	
	private static void dosomethingElse(Element next) {
		System.out.println("heh----"+next.getName());
	}
}
执行结果如下:

bob-----bob0

bob-----bob1

bob-----bob2

bob-----bob3

bob-----bob4

bob-----bob5

bob-----bob6

bob-----bob7

bob-----bob8

bob-----bob9

原因在于我标BUG的位置。使用错了变量i。使用错误是人之常情,但是编译器没报错,运行也没有报错,这就很可怕了。

然而,假如使用for循环则可以避免。当然,必须是两个循环都限制变量为自己的局部变量。如果你一定要声明在循环外面的话,那么效果就和while循环一样了。

for (Iterator<Element> i = c.iterator(); i.hasNext();) {
			dosomethingElse(i.next());
		}
		for (Iterator<Element> i2 = c2.iterator(); i.hasNext();) {//编译时错误
			dosomethingElse(i2.next());
		}

二、第46条,for-each循环优先于传统的for循环

先看一个举例:

先写两个枚举如下:

Suit

public enum Suit {CLUB,DIAMOND,HEART,SPADE}
Rank

public enum Rank {ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE,TEN}

写个类用来添加以上两个枚举的属性:

public class Deck {
	private Suit suit;
	private Rank rank;
	public Deck(Suit suit, Rank rank) {
		super();
		this.suit = suit;
		this.rank = rank;
	}
	public Suit getSuit() {
		return suit;
	}
	public void setSuit(Suit suit) {
		this.suit = suit;
	}
	public Rank getRank() {
		return rank;
	}
	public void setRank(Rank rank) {
		this.rank = rank;
	}
	@Override
	public String toString() {
		return "Deck [suit=" + suit + ", rank=" + rank + "]";
	}	
}
写个单元测试类:

public class JunitTest {
	@Test
	public void test() {
		Collection<Suit> suits = Arrays.asList(Suit.values());
		Collection<Rank> ranks = Arrays.asList(Rank.values());
		List<Deck> decks = new ArrayList<Deck>();
		for (Iterator<Suit> iterator = suits.iterator(); iterator.hasNext();) {
			for (Iterator<Rank> iterator2 = ranks.iterator(); iterator2.hasNext();) {
				decks.add(new Deck(iterator.next(), iterator2.next()));
			}
		}
		for (Iterator<Deck> iterator3 = decks.iterator();iterator3.hasNext();) {
			System.out.println(iterator3.next().toString());	
		}
	}
}
看上去以上代码,是为了输出Suit,Rank枚举的所有组合。Suit有4个,Rank有10个,按道理说应该有40种。但是输出结果却抛出异常了。
java.util.NoSuchElementException
at java.util.AbstractList$Itr.next(AbstractList.java:364)
at com.test.JunitTest.test(JunitTest.java:22)

那我来看看单元测试的22行,22行是下面的最后一行的括号回来。这就很奇葩了。

for (Iterator<Rank> iterator2 = ranks.iterator(); iterator2.hasNext();) {
				decks.add(new Deck(iterator.next(), iterator2.next()));
			}
虽然依然很绝望,但是知道大概位置就是内循环了。我决定在21行也就是上面代码的第2行,add方法这一行改进一下,输出一个日志看看。改进后如下

				Suit suit = iterator.next();
				Rank rank = iterator2.next();
				System.out.println(suit.toString()+"-----"+rank.toString());
				decks.add(new Deck(suit, rank));
发现内循环在第5次添加Deck对象的时候抛出了一个异常。输出日志如下:

CLUB-----ONE

DIAMOND-----TWO

HEART-----THREE

SPADE-----FOUR

这不对呀,理想情况应该是CLUB对应ONE,CLUB对应TWO,以此类推。问题找到了。因为内部使用

new Deck(iterator.next(), iterator2.next())

的时候,每次两个迭代器都会往后走一位。那么如何绕开这个错呢?如下就可以了!

for (Iterator<Suit> iterator = suits.iterator(); iterator.hasNext();) {
			Suit suit = iterator.next();
			for (Iterator<Rank> iterator2 = ranks.iterator(); iterator2.hasNext();) {
				Rank rank = iterator2.next();
				System.out.println(suit.toString()+"-----"+rank.toString());
				decks.add(new Deck(suit, rank));
			}
		}

执行结果:

Deck [suit=CLUB, rank=ONE]

Deck [suit=CLUB, rank=TWO]

Deck [suit=CLUB, rank=THREE]

Deck [suit=CLUB, rank=FOUR]

Deck [suit=CLUB, rank=FIVE]

Deck [suit=CLUB, rank=SIX]

Deck [suit=CLUB, rank=SEVEN]

Deck [suit=CLUB, rank=EIGHT]

Deck [suit=CLUB, rank=NINE]

Deck [suit=CLUB, rank=TEN]

Deck [suit=DIAMOND, rank=ONE]

Deck [suit=DIAMOND, rank=TWO]

Deck [suit=DIAMOND, rank=THREE]

Deck [suit=DIAMOND, rank=FOUR]

Deck [suit=DIAMOND, rank=FIVE]

Deck [suit=DIAMOND, rank=SIX]

Deck [suit=DIAMOND, rank=SEVEN]

Deck [suit=DIAMOND, rank=EIGHT]

Deck [suit=DIAMOND, rank=NINE]

Deck [suit=DIAMOND, rank=TEN]

Deck [suit=HEART, rank=ONE]

Deck [suit=HEART, rank=TWO]

Deck [suit=HEART, rank=THREE]

Deck [suit=HEART, rank=FOUR]

Deck [suit=HEART, rank=FIVE]

Deck [suit=HEART, rank=SIX]

Deck [suit=HEART, rank=SEVEN]

Deck [suit=HEART, rank=EIGHT]

Deck [suit=HEART, rank=NINE]

Deck [suit=HEART, rank=TEN]

Deck [suit=SPADE, rank=ONE]

Deck [suit=SPADE, rank=TWO]

Deck [suit=SPADE, rank=THREE]

Deck [suit=SPADE, rank=FOUR]

Deck [suit=SPADE, rank=FIVE]

Deck [suit=SPADE, rank=SIX]

Deck [suit=SPADE, rank=SEVEN]

Deck [suit=SPADE, rank=EIGHT]

Deck [suit=SPADE, rank=NINE]

Deck [suit=SPADE, rank=TEN]

但是吧,有个更好的办法避免这种情况。将其扼杀在摇篮中。通过foreach,只要实现了iterable的借口就可以通过foreach遍历。

@Test
	public void test2() {
		Collection<Suit> suits = Arrays.asList(Suit.values());
		Collection<Rank> ranks = Arrays.asList(Rank.values());
		List<Deck> decks = new ArrayList<Deck>();
		for (Suit suit:suits) {
			for (Rank rank:ranks) {
				decks.add(new Deck(suit, rank));
			}
		}
		for (Deck deck:decks) {
			System.out.println(deck.toString());	
		}
	}
当然有几种例外情况下是没办法通过foreach遍历的:

1、过滤:例如遍历集合,删除指定元素。

2、转换:遍历列表或者数组,修改或替换某个或者多个元素。

书中以上的两种方法其实都是为了获取数组索引位置才能实现的。也就是需要某个位置的时候,不能通过foreach遍历。

容易错的地方:如果基础不够扎实可能以为我可以这么做:

		for (Suit suit:suits) {
			if (suit.equals(Suit.DIAMOND)) {
				suits.remove(suit);
			}
		}
然而,删除操作是没法在遍历的时候做的。回去好好翻翻数据结构吧。这个时候只能记录当前需要删除的元素的位置。foreach本身没有索引位置。
3、平行迭代:什么是平行迭代,对,就是之前的错误示例的效果,两个迭代器同步增加。












  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值