【effective Java读书笔记】方法(二)

前言:

越发喜欢写读书笔记了,写完之后,后续的工作中,平时写代码,就会比较深刻的记起来,“原来这个地方可以这么用”。

《第40条至41条》

正文:

一、谨慎设计方法签名,其中值得一提的是:避免过长的参数列表。

对了,话说方法签名应该很熟悉吧:参数类型、参数个数、参数顺序。

避免过长的参数列表的三种方法:

1、方法分解成多个方法;

书中举例:在子列表中查找元素的第一个索引和最后一个索引。(java.util.List接口)

public class ListApply {
	public static void main(String[] args) {
		List<String> strs = new ArrayList<>();
		// TODO 提供数据
		for (int i = 0; i < 20; i++) {
			strs.add("test"+i);
		}
		List<String> subStrs= strs.subList(3, 13);
		System.out.println(subStrs.indexOf(subStrs.get(0)));
		System.out.println(subStrs.lastIndexOf(subStrs.get(subStrs.size()-1)));
	}
}

执行结果:

0

9

原本提供这样一个方法需要三个参数,子列表、子列表第一个索引、子列表的最后一个索引。


第8行,List接口提供subList方法,获得子列表;

第9行,根据

subStrs.get(0)


获取第一个对象,通过indexOf方法获得第一个对象出现的第一次的index;

第10行,根据

subStrs.get(subStrs.size()-1))


获取最后一个对象,通过lastIndexOf获取最后一个对象出现的最后一次的index;



2、创建辅助类;

辅助类,顾名思义,就是用来作为辅助工具的类。

举个例子:

public class HelpClass {
	public static class Help{
		public static int add(int x,int y){
			return x+y;
		};
		public static int min(int x,int y){
			return x-y;
		};
	}
	
	public static void main(String[] args) {
		int x =3;
		int y =2;
		System.out.println("3+2=?");
		System.out.println(HelpClass.Help.add(x, y));
		System.out.println("3-2=?");
		System.out.println(HelpClass.Help.min(x, y));
	}
}


执行结果:

3+2=?

5

3-2=?

1


其中:

Help就是辅助类,原本一个这样的计算,需要传递三个入参:第一个数,第二个数,算数操作符。

现在添加辅助类后,只需要添加两个入参。(以上代码用枚举写更佳)


public class HelpClass {
	public static enum Opre{
		ADD("+"){
			@Override
			public int apply(int x, int y) {
				return x+y;
			}
		},
		MIN("-"){
			@Override
			public int apply(int x, int y) {
				return x-y;
			}
		};
		public final String oper;
		Opre(String oper) {
			this.oper = oper;
		}
		public abstract int apply(int x,int y);
	}
	
	public static void main(String[] args) {
		int x =3;
		int y =2;
		System.out.println("3+2=?");
		System.out.println(HelpClass.Opre.ADD.apply(x, y));
		System.out.println("3-2=?");
		System.out.println(HelpClass.Opre.MIN.apply(x, y));
	}
}

3、从对象构建到方法调用都采用Build模式。

直接举例:

public class User {
	private String name;
	private String addr;
	private int age;
	public String getName() {
		return name;
	}
	
	public String getAddr() {
		return addr;
	}
	
	public int getAge() {
		return age;
	}
	
	@Override
	public String toString() {
		return "User [name=" + name + ", addr=" + addr + ", age=" + age + "]";
	}

	public static class Builder{
		private User user = new User();
		public Builder setName(String name){
			user.name = name;
			return this;
		}
		
		public Builder setAddr(String addr){
			user.addr = addr;
			return this;
		}
		
		public Builder setAge(int age){
			user.age = age;
			return this;
		}
		
		public User build(){
			return user;
		}
	}
}


使用如下:


public class Test {
	@org.junit.Test
	public void test() {
		Builder builder = new Builder();
		User user =builder.setName("bear").setAge(14).setAddr("beijing").build();
		System.out.println(user);
	}
}


执行结果如下:


User [name=bear, addr=beijing, age=14]


当然这个方法显著的减少了参数。使用起来也比较方便。



二、慎用重载

关键点出来的就是重载和覆盖的区别:书中提及“对于重载方法的选择是静态的,对于覆盖方法的选择是动态的”。

1、先看一个书中重载的例子:

public class OverRideTest {
	public static String classify(Set<?> s){
		return "Set";
	}
	
	public static String classify(List<?> lst){
		return "List";
	}
	
	public static String classify(Collection<?> c){
		return "Collection";
	}
	
	public static void main(String[] args) {
		Collection<?>[] collections = {new HashSet<String>(),new ArrayList<String>(),new HashMap<String,String>().values()};
		for (Collection<?> c:collections) {
			System.out.println(classify(c));
		}
	}
}


乍看之下,有点懵!这结果是不是应该Set,List,Collection;


而实际结果呢?

Collection

Collection

Collection


原因其实很简单,重载嘛,挑选最合适的。

public static String classify(Collection<?> c){
		return "Collection";
	}

而且重载方法调用是在编译时决定的。那么一旦选定就不能改了。无异议是否?

2、再看一个覆盖的例子:

代码如下

public class Test2 {
	public static void main(String[] args) {
		Wine[] wines = { new Wine(), new RedWine(), new YellowWine() };
		for (Wine wine : wines) {
			System.out.println(wine.name());
		}
	}
}

class Wine {
	String name() {
		return "wine";
	}
}

class RedWine extends Wine {
	String name() {
		return "RedWine";
	}
}

class YellowWine extends Wine {
	String name() {
		return "YellowWine";
	}
}

执行结果:

wine

RedWine

YellowWine


3、慎用重载的例子:

书中这个例子显得很是经典:

public class SetList {
	public static void main(String[] args) {
		Set<Integer> set = new TreeSet<>();
		List<Integer> list = new ArrayList<Integer>();
		for (int i = -3; i < 3; i++) {
			set.add(i);
			list.add(i);
		}
		for (int i = 0; i <3; i++) {
			set.remove(i);
			list.remove(i);
		}
		System.out.println(set + "" + list);
	}
}


看上去以为结果会是

[-3, -2, -1]

[-3, -2, -1]


实际结果却是:

[-3, -2, -1][-2, 0, 2]


原因是什么?第一个Set与我们预期相同。Set添加和删除都会先装箱,再添加删除。

第二个List添加时装箱,但是删除的时候重载了,导致remove第几个位置的,而不是删除某个指定的对象。