《编写高质量代码(改善Java程序的151个建议)》读书笔记(一)

举报
山河已无恙 发表于 2022/03/11 10:10:40 2022/03/11
【摘要】 第一章,Java开发中通用的方法和准则 1. 不要在常量和变量中出现易混淆的字母,(“1l“,表示一个long型的值,容易看做“11”); 2. 莫让变量脱变成变量(public static final int RAND_CONST = Random().nextInt();) 3. 三元类型操作符的类型必须一致(转化规则,若不可转,返回值Object,为明确类型表达式(变量),正常转化...

第一章,Java开发中通用的方法和准则

1. 不要在常量和变量中出现易混淆的字母,(“1l“,表示一个long型的值,容易看做“11”);
2. 莫让变量脱变成变量(public static final int RAND_CONST = Random().nextInt();)
3. 三元类型操作符的类型必须一致(转化规则,若不可转,返回值Object,为明确类型表达式(变量),正常转化,若为数字与表达式,转换为范围大的,若为字面量数字,类型转化为范围大的)
4. 避免带有变长参数的方法重载(从最简单开始)。
5. 别让null值和空值威胁到重载的变长方法(变长参数N>=0,必须把null定义为具体的类型)。
6. 覆写变长的方法也要循规蹈矩(可访问性一致/公开,参数列表相同(顺序等),返回类型相同/子类,不抛出新异常,或者超过父类的异常类)
7. 警惕自增的陷阱(i =0 ; while(true){i = i++;})
8. 不要让旧语法困扰。
9. 少用静态导入(规则:不使用*通配符,方法名为工具类)。
10. 不要在本类中覆盖静态导入的变量和方法(最短路径原则),一般在原始类重构而不是覆盖。
11. 养成良好的习惯,显示申明UID
public class Person implements Serializable{
//流标识符(Stream Unique Identfier)类的版本定义,可以显示定义可以隐式定义
	private static final long serialVersionUID = 55799L;
	private String name;
	public Person() {
		// TODO Auto-generated constructor stub
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}
public class Producer {

public static void main(String[] args) {
	//序列化
	Person person  =new Person();
	person.setName("李瑞龙");
	SerializationUtils.writeObject(person);
	System.out.println("序列化成功!!");
}
}
public class SerializationUtils {
	private static String FILE_NAME = "C:/LIRUILONG.bin";
	/*
	 * 序列化
	 * 
	 */
	public static void writeObject(Serializable s) {
		try {
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
			oos.writeObject(s);
			oos.close();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	public SerializationUtils() {
		// TODO Auto-generated constructor stub
	}
	/*
	 * 反序列化
	 * @return obj
	 */
public static Object readObject() {
	Object obj  = null;
	try {
		ObjectInput input = new ObjectInputStream(new FileInputStream(FILE_NAME));
		obj = input.readObject();
		input.close();
	}catch(Exception e) {
		e.printStackTrace();
	}
	return obj;
}
}
public class Consumer {
public static void main(String[] args)throws Exception {
	//反序列化
	Person p = (Person)SerializationUtils.readObject();
	System.out.println("name="+p.getName());
}
}

当序列化和反序列化的版本不一致时,反序列化会报一个InvalidClassException异常,原因是类版本发生变化,JVM不能把数据流转换为实例对象,JVM通过SerialVersionUID(流标识符),即类的版本定义的,可以显示定义可以隐式定义(编译器自动申明),JVM反序列化时,会比较数据流中的SerialVersionUID与类中的SerialVersionUID是否相同,不相同抛出异常。依靠显示申明,改变一端的Person后可以运行。即显示申明SerialVersionUID可以避免对象不一致。但尽量不要以这种方式向JVM"撒谎"。

12. 避免用序列化类在构造函数中为不变量赋值,(反序列化时构造函数不会执行)。
13. 避免为final变量复杂赋值:

(保存在磁盘上的对象文件包括两部分:

  • 1,类文件描述信息:包括类路径,继承关系,访问权限,变量描述,变量访问权限,方法签名,返回值,以及变量 的关联关系类信息。
  • 2,非瞬态(trtansient)和非静态(static)的的实例变量值
    反序列化时final变量在一下情况不会被赋值:通过构造函数赋值,通过方法返回值赋值,final修饰的属性不是基本类型
14. 使用序列化类的私有方法巧妙解决部分属性持久化的问题;
private void writeObject(ObjectOutputStream out)throws IOException{
//告诉JVM按照默认的规则写入对象,惯例的写法是写在第一句
		out.defaultWriteObject();
		//写入相应的值
		out.writeInt(salary.getBasePay());
	}
	private void readObject(ObjectInputStream in)throws IOException,ClassNotFoundException{
	//告诉JVM按照默认规则读入对象,也写在第一句
		in.defaultReadObject();
		//独处相应的值
		salary = new Salary(in.readInt(),0);
	}

在序列化类中增加writeObject和readObject两个方法,使用序列化的独有机制,序列化回调,Java调用ObjectOutputStream类把一个对象转换为流数据时,会通过反射(Reflection)检查被序列化的类是否有writeObject方法,并且检查其是否为私有,无返回值的特性,若有,则会委托该方法进行对象序列化,若没有,则由ObjectOutputStream按照默认规则继续序列化,在反序列化的时候也会检查是否有私有方法readObject。如果有会通过该方法读取属性。

15. break 万万不可以忘;
16. 易变业务使用脚本语言编写(特性:灵活,便捷,简单),JCP(Java Community Prosess)提出JSR223规范,JavaScript默认支持,

脚本语言可以随时发布而不用重新部署,即脚本语言改变,也能提供不间断的业务服务。

public class main {

	public main() {
		// TODO Auto-generated constructor stub
	}

	public static void main(String[] args)throws Exception {
		// TODO Auto-generated method stub
	//获取JAVacript的执行引擎(engine)。	
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
    //建立上下文变量
    Bindings bind = engine.createBindings();
    bind.put("factor", 1);
    //绑定上下文,作用域为当前引擎范围。
    engine.setBindings(bind, ScriptContext.ENGINE_SCOPE);
    Scanner input = new Scanner(System.in);
    while(input.hasNextInt()) {
    	int first = input.nextInt();
    	int sec = input.nextInt();
    	System.out.println("输入参数为:"+first+","+sec);
    	//执行js代码
    	engine.eval(new FileReader("C:/model.js"));
    	//是否可调用方法
    	if(engine instanceof Invocable) {
    		Invocable in = (Invocable)engine;
    		Double result = (Double)in.invokeFunction("formula",first,sec);
    		System.out.println("运行结果:"+result.intValue());
    	}
    }
	}
}
 public interface Bindings extends Map<String,Object>
  所有键均为 String 的键/值对映射关系。 
   void put(String key, Object value) 
          设置 ScriptEngine 的状态中的键/值对,它创建一个将在脚本执行中使用或者以其他方式
          使用的 Java Language Binding,具体取决于该键是否被保留      
 public interface ScriptEngine 
 ScriptEngine 是基础接口,该接口的方法在此规范的每个实现中都必须具有完整的功能。
这些方法提供基本的脚本功能。为这个简单接口编写的应用程序应该对每个实现稍做修改就能够运行。
这包括执行脚本的方法,以及设置和获取值的方法。这些值是两种类型的键/值对。组成第一种类型的键/值对中的
键是此规范或个别实现中保留和定义的键。包含保留键的键/值对中的值具有指定的含义。
另一种类型由那些创建 Java 语言 Bindings 的键/值对组成,值通常通过相应键或其装饰的形式用脚本表示。 
 Bindings createBindings() 
          返回一个未初始化的 Bindingsvoid setBindings(Bindings bindings, int scope) 
          设置将由脚本使用的指定值的范围。        
          
 public class ScriptEngineManager extends ObjectScriptEngineManagerScriptEngine 类实现一个发现和实例化机制,还维护一个键/值对集合来存储所有 Manager 创建的引擎所共享的状态。
 此类使用服务提供者机制枚举所有的 ScriptEngineFactory 实现。
   ScriptEngineManager 提供了一个方法,可以返回一个所有工厂实现和基于语言名称、
   文件扩展名和 mime 类型查找工厂的实用方法所组成的数组。 键/值对的 Bindings(即由管理器维护的 "Global Scope")
   对于 ScriptEngineManager 创建的所有 ScriptEngine 实例都是可用的。Bindings 中的值通常公开于所有脚本中。 
ScriptEngine getEngineByName(String shortName) 
          查找并创建一个给定名称的 ScriptEnginepublic interface InvocableScriptEngines 实现的可选接口,该 ScriptEngines 的方法允许在以前执行过的脚本中调用程序。
	Object invokeFunction(String name, Object... args) 
          用于调用脚本中定义的顶层程序和函数。 
  1. 慎用动态编译(注意:在框架中谨慎使用,不要在要求高性能的项目中使用,动态编译要考虑安全问题,记录动态编译过程)
  2. 避免instanceof非预期的结果。(instanceof操作符的左右必须有继承或实现关系)
"String" instanceof String //返回值为true
new String() instanceof String //返回值为true
new Object() instanceof String //false(可以编译)
'A' instanceof Character //编译不通过,A为基本类型,Character为封装类,前边必须为对象。
null instanceof String //false,特殊规则,如果左操作数是null,结果就直接返回false,不在运运算右操作数,
(String)null instanceof String //false,null是一个万用类型,可以说它没有类型,即使类型转换也是null。
new Date() instanceof String //编译不通过,没有继承实现关系。
T(T为泛型String类变量) instanceof Date; //通过,false,T被编译为Object类,传递String类的值,所以 "Object instanceof Date";
19. 断言绝不是鸡肋:
assert <boolean_expression>
assert <boolean_expression><erroe>
当布尔表达式为假时,抛出一个AssertionError错误,是继承自Error的,并附带错误信息,默认不启动。
不可用:在对外的方法中不可用,在执行逻辑代码时不可用。因为生产中断言不会执行。
可用:在私有方法中放置assert作为输入参数校验。流程控制中不可能达到的区域。建立程序探针,即判断程序中不可变的量是否改变。
20. 不要只替换一个类,(发布应用系统时禁止使用类文件替换方式,整体的WAR包发布才是万全之策)

对于final修饰的基本类型和String类型,编译器会认为他是稳定态(Immutable Status),所以编译期间之间把值编译到字节码中,避免运行期引用(Run-time-reference),提高代码执行效率对于final修饰的基本类型和String类型,编译器会认为他是稳定态(Immutable Status),所以编译期间之间把值编译到字节码中,避免运行期引用(Run-time-reference),提高代码执行效率,对于final类来讲编译器认为它是不稳定的,在编译期建立则是引用关系,即到final修饰一个类或实例时,不重新编译也会是最新值。


第二章,基本类型

21. 用于偶判断,不用奇判断,(取模运算,)
i%2 == 1?"奇数":"偶数";//输入-1为偶数
public static int remainder(int dividend,int divisor){
	return dividend - dividend/divisor*divisor;
}
22. 用整数类型处理货币(使用BigDecimal类,与数据库映射方便。使用整形(扩大倍数))
23. 不用让类型默默转换,(java 为先运算后进行类型转化的)当运算结果过界就会从头开始(负值),在运算中加入数据范围大的值,基本类型转换时,使用主动声明方式减少不必要的bug.
24. 边界,边界,还是边界(判断数值范围时,要考虑超过类型边界后为负数)。
25. 不要让四舍五入亏了一方(银行家舍入):
26. 堤防包装类型的null值:包装类型参与运算时,要做null值校验。
27. 谨慎包装类型的大小比较:"=="对于基本类型比较的值,对象比较是否为同一个引用。<>不能比较对象大小。
28. 优先使用整形池:装箱生成的对象,装箱动作通过valueOf实现的,chache是IntegerCatch内部类的静态数组,容纳-128—127之间的Integer对象,不在该范围的int类型通过new生成包装对象。即127这个数字的包装每次都是同一个对象,而128不是同一个对象。整形池存在不仅提高了系统性能,节约内存空间。
public static Integer valueOf(int){
	final int offset= 128;
	if(i>=-128&&i<=127){
	return IntegerCache[i+offset];} 
	return new Ingeger(i);
}
static final Integer cache[] = new Integer[-(-128)+127+1];
static{
		for(int i =0; i < cache.length;i++)
			cache[i] = new Integer(i-128)
}
29. 优先选择基本类型:(自动装箱的重要原则,基本类型可以先加宽,再转变成宽类型的包装类型,但不能直接转换为宽类型的包装类型)
30. 不要随便设置随机种子:

在java中,随机数的产生取决于种子,随机数和种子之间的关系(种子不同,产出的随机数不同,种子相同,即使实例不同也产生相同的随机数)

public static void main(String[] args) {
		//Random r= new Random();默认种子
		Random r= new Random(1000);//设置种子
		for(int i=1;i<4;i++) {
			System.out.println("随机数字为:"+r.nextInt());
		}
	}

获得随机数:Math.random()方法,通过java.util.Random;


第三章,类对象及方法

31. 在接口中不要存在实现代码(接口中声明一个匿名内部类的实例对象的静态常量)。
32. 静态变量一定要先声明后赋值

静态变量是类加载时被分配到数据区(Data Area)的,它在内存中只有一个拷贝,不会被分配多次,其后的所有赋值操作都是值改变,地址则保持不变。JVM初始化变量是先声明空间,然后在赋值(int i= 12;==>int i ; i =12;)
静态变量在类初始化时首先被加载的,JVM会查找类中所有的静态申明,然后分配空间,只是完成地址空间分配,还没有赋值,之后会根据类中的静态赋值(包括静态类型赋值和静态块赋值)的先后顺序执行。变量先申明后使用。

33. 不要(重写)覆写静态方法,覆写是针对非静态方法(实例方法)的,不针对静态方法(类方法),但是可以隐藏静态方法(属于该类,与父类没有关系)。通过实例对象访问静态方法和属性是不好的习惯。

实例对象有两个类型:表面类型(Apparent Type)实际类型(Actual Type),表面类型是声明时的类型,实际类型是对象产生时的类型,对于非静态方法,它是根据对象的实际类型来执行的,对于静态方法来说,不依赖实例对象,通过类名访问,通过对象访问静态方法,JVM会通过对象的表面类型查找到静态方法的入口,然后执行,
在子类中构建与父类相同的方法名,输入参数,输出参数,访问权限(权限可以扩大),并且父类子类都是静态方法,此种行为称之为隐藏(Hide),它与覆写有两点不同。

  • 1,表现形式不同:隐藏用于静态方法,覆写用于非静态,@OVerride可以用于覆写(写上自动检测是否合要求),不能用于隐藏。
  • 2,职责不同:隐藏的目的是为了抛弃父类静态方法,重现子类方法,覆写是为了将父类的行为增强或减弱。
public class Base {
	//父类静态方法
	public static void doSomething() {
		System.out.println("我是父类的静态方法");
	}
	//父类非静态方法
	public void doAnything() {
		System.out.println("我是父类的非静态方法");
	}
}
public class Sub extends Base{
	//子类同名,同参数的静态方法
	public static void doSomething() {
		System.out.println("我是子类的静态方法");
	}
	//覆写父类的非静态方法
	@Override
	public void doAnything() {
		System.out.println("我是子类的非静态方法");
	}
}
public class Client1 {
	public static void main(String[] args) {
		Base base1 = new Sub();
		Sub base = new Sub();
		base.doAnything();
		base1.doSomething();
		base.doSomething();
	}
}
//结果
//我是子类的非静态方法
//我是父类的静态方法
//我是子类的静态方法
34. 构造函数尽量简化。(子类实例化时,会先初始化父类(初始化,不是生成父类对象),就是初始化父类的对象,调用父类的构造函数,然后才会初始化子类的变量,调用子类的构造函数,最后生成一个实例对象)
35. 避免在构造函数中初始化其他类。
36. 使用构造代码精炼程序,

代码块(Code Block):{}包裹的数据体,实现特定算法,一般不能单独运行,要有运行主体,java 中有四种:

  • 1,普通代码块:方法名后面{ }部分。
  • 2,静态代码块:在类中使用static修饰的{ },用于静态变量的初始化和对象创建前的环境初始化。类中的静态块会在整个类加载过程中的初始化阶段执行,而不是在类加载过程中的加载阶段执行初始化阶段是类加载过程中的最后一个阶段,该阶段就是执行类构造器<clinit>方法的过程,<clinit>方法由编译器自动收集类中所有类变量(静态变量)的赋值动作和静态语句块中的语句合并生成 ,一个类一旦进入初始化阶段,必然会执行静态语句块。所以说,静态块一定会在类加载过程中被执行,但不会在加载阶段被执行
  • 3,同步代码块:使用synchronized修饰的{ },表示同一时间只能有一个线程进入到该方法块,一种多线程保护机制。
  • 4,构造代码块:在类中没有人任何前缀和后缀的{ },编译器会把构造代码块插入到构造函数的最前端。
    在通过new关键字生成一个实例时会先执行构造代码块,然后在执行其他构造函数代码,依托于构造函数运行,不是在构造函数之前运行。应用:
  • 1,初始化实例变量(Instance Variable):如果每个构造函数都需要初始化变量,可以通过构造代码块实现。
  • 2,初始化实例环境:当一个对象必须在适的场景才能存在,jee中要产生HTTP Request,必须要建立HTTP session ,可以在创建HTTP Request时通过构造代码块检查HTTP Session是否存在,不存在就创建。
37. 构造代码块会想你所想。(当构造代码块遇到this关键字时(构造函数调用自身其他无参构造函数),则不插入代码块,遇到super时,会放到super方法之后执行)
38. 使用静态内部类提高封装性:

java中的嵌套类(Nesetd Class):分为两种,静态内部类(也叫静态嵌套类,Static Nested Class)和内部类(Inner Class),
静态内部类:加强了类的封装性,提高了代码的可读性。
静态内部类不持有外部类的引用(普通内部类可以访问外部类的方法,属性,即使是private类型也可以访问,静态内部类只可以访问外部类的静态方法和静态属性),静态内部类不依赖外部类(普通内部类与外部类之间是相互依赖的关系,内部类实例不能脱离外部类实例,同生同死,一起声明,一起被拉圾回收器回收,静态内部类可以独立存在,即使外部类消亡,静态内部类还是可以存在),普通内部不能声明static的方法和变量(常量可以修饰,静态内部类没有限制)

39. 使用匿名类的构造函数
//声明一个ArrayList对象。
List la = new ArrayList();
//一个继承了ArrayList的匿名类的声明和赋值,没有任何覆写方法。
List lb = new ArrayList(){};
//在上面的基础上增加了构造函数块。可以有多个。
List lc = new ArrayList(){{}{}};
40. 匿名类的构造函数很特殊:(一般类(具有显示名字的类)的所有构造函数默认都是调用父类的无参构造函数的,而匿名类没有名字,只能有构造代码块代替,它在初始化时直接调用父类的同参构造函数,然后在调用自己的构造代码块)
enum Ops{ADD,SUB }
public class Calculator {
	private int i,j,result;
	
	public Calculator() {
		
	}
	public Calculator(int i,int j) {
		this.i =i;
		this.j = j;
	}
	protected void setOperator(Ops _op) {
		result = _op.equals(Ops.ADD)?i+j:i-j;
	}
	public int getResult() {
		return result;
	}
public static void main(String[] args) {
	//定义一个匿名内部类,使用构造代码块初始化
	Calculator c1 = new Calculator(1,2) {
		{
			setOperator(Ops.ADD);
		}
	};
	System.out.println(c1.getResult());
}
}
41. 让多重继承成为现实(内部类的重要特征,内部类可以继承一个与外部类无关的类,保证了内部类当然独立性)多重继承考虑内部类。
//父亲
public interface Father {
	public int strong();
}
//母亲
public interface Mother {
	public int Kind();
}
//父亲实现类
public class FatherImpl implements Father {
	@Override
	public int strong() {
		return 8;
	}
}
//母亲实现类
public class MotherImpl implements Mother{
	@Override
	public int Kind() {
		return 8;
	}	
}
//儿子
public class Son extends FatherImpl implements Mother{
	@Override
	public int strong() {
		return super.strong()+1;	
	}	
	@Override
	public int Kind() {
		return new MotherSpecial().Kind();
	}
	public class MotherSpecial extends MotherImpl {
		public int kind() {
			return super.Kind() -1;
		}
	}
}
42. 让工具类不可实例化(一般为静态):在构造函数设置私有,抛出异常。
public class UtilsClass{
	private UtilsClass(){
		throw new Error("不要实例化我哎");
		}
	}
43. 避免对象的浅拷贝(浅拷贝是java的一种简单的机制,不便于直接使用)

一个类在实现了Cloneable接口就表示它具备了被拷贝的能力,如果在覆写clone()方法就会完全具备拷贝能力。拷贝在内存中进行,所以在性能方面比直接通过new生成对象要快的多,存在缺陷:浅拷贝(Shadow Clobe,也称影子拷贝)存在对象属性拷贝不彻底的问题。拷贝规则:

  • 1,基本类型拷贝其值,。
  • 2,实例对象,拷贝地址引用,就是说此时新拷贝的对象与原有对象共享该实例变量,不受访问权限的限制。
  • 3,String字符串,拷贝的也是地址,但是在修改时,会从字符串池(String Pool)中重新生成字符串,原有的字符串保持不变
public class Person implements Cloneable{
	//姓名
	private String name;
	//父亲
	private Person father;
	public Person(String name) {
		this.name = name;
	}
	public Person(String name,Person parent) {
		this.name = name;
		this.father = parent;
	}	//getter与setter方法省略
	public Person clone() {
		Person p = null;
		try {
			p = (Person)super.clone();
			//p.setFather(new Person(p.getFather().getName())实现深拷贝
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return p;
	}
}
public class Client2 {
	public static void main(String[] args) {
	Person f = new Person("父亲");
	Person s1 = new Person("大儿子",f);
	Person s2 = s1.clone();
	s2.setName("小儿子");
	//s2.getFather().setName("干爹");
	System.out.println(s1.getName()+"的父亲为:"+s1.getFather().getName());
	System.out.println(s2.getName()+"的父亲为:"+s2.getFather().getName());
}
}
44. 推存使用序列化实现对象的拷贝,

被拷贝的类只要实现Serializable接口,不需要任何实现,需要加上SerialVersionUID常量,使用需要注意:

  • 1,对象的内部属性都是可序列化的。
  • 2,注意方法和属性的特殊修饰符。final,static变量的序列化问题会被 引入到拷贝对象中,瞬态变量(trtansient)不能进行序列化。可一采用Apache下的commons工具包中的SerializationUtils类。
public class CloneUtils {
	//拷贝一个对象
	//@SuppressWarnings。该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默。 
	/*
	 *    关键字         用途 
   deprecation   使用了不赞成使用的类或方法时的警告 
   unchecked     执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。 
   fallthrough   当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。 
   path          在类路径、源文件路径等中有不存在的路径时的警告。  
   serial        当在可序列化的类上缺少 serialVersionUID 定义时的警告。  
   finally       任何 finally 子句不能正常完成时的警告。 
   all           关于以上所有情况的警告。 
 @SuppressWarnings 批注允许您选择性地取消特定代码段(即,类或方法)中的警告。其中的想法是当您看到警告时,
 您将调查它,如果您确定它不是问题,您就可以添加一个 @SuppressWarnings 批注,以使您不会再看到警告。
 虽然它听起来似乎会屏蔽潜在的错误,但实际上它将提高代码安全性,因为它将防止您对警告无动于衷 — 
 您看到的每一个警告都将值得注意。 
	 */
	@SuppressWarnings("unchecked")
	public static <T extends Serializable> T clone(T obj) {
		//拷贝产生的对象
		T clonedObj = null;
		try {
			//读取对象字节数据
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(obj);//写操作
			oos.close();//关闭流
			//分配内存空间,写入原始对象,生成新对象
			ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bais);
			clonedObj = (T)ois.readObject();//读操作
			ois.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
		return clonedObj;	
	}
}
45. 覆写equals方法时不要识别不出自己,equals方法的自反原则(对于任何非空引用x,x.equals(x)应该返回true)
46. equals应该考虑null值情景。对称性原则,对于任何引用x和y的情形,如果x.equals(y)返回true,那么y.equals(x)也应该返回true。
47. 在equals中使用getClass进行类型判断,注意equals的传递性原则,对于实例对象s,y,z,如果如果s.equals(y)为true,y.equals(z)返回true,那么x.equals(z)也返回true。
48. 覆写equals方法必须覆写hashCode方法。

HashMap的底层处理机制是以数组的方式保存Map条目的,链表保存val,依据传入元素的hashCode方法返回的哈希值决定数组下标,如果该位置已有Map条目了,且与传入的键值相等则不要处理,若不相等则则覆盖,如果数组位置没有条目则插入。并加入到Map条目的链表中。即在检查相等时也是由哈希吗确定位置。
哈希码:由Object方法本地生成,确保每一个对象有一个哈希码(哈希算法,输入任意L,通过一定算法f(L),将其转化为非可逆的输出,一对一,多对一成立),重写hashCode方法:

public int hashCode(){
	return new HashCodeBuilder().append(),toHashCode();//HashCodeBuilder哈希码生成工具。
	}
49. 推存覆写toString方法,正常输出:类名+@+hashCode;
50. 使用package-info类为包服务:用于描述和记录包信息的,会被编译,不能声明类,不能继承,没有类关系,用于申明友好类和包内访问常量,为在包上标注一个注解(Annotation)提供方便,即将定义的注解写到这里,提供包的整体注释说明。
51. 不要主动进行垃圾回收。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。