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

网友投稿 642 2022-05-29

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

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"撒谎"。

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

1,类文件描述信息:包括类路径,继承关系,访问权限,变量描述,变量访问权限,方法签名,返回值,以及变量 的关联关系类信息。

2,非瞬态(trtansient)和非静态(static)的的实例变量值。

反序列化时final变量在一下情况不会被赋值:通过构造函数赋值,通过方法返回值赋值,final修饰的属性不是基本类型

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。如果有会通过该方法读取属性。

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

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 的键/值对映射关系。 void put(String key, Object value) 设置 ScriptEngine 的状态中的键/值对,它创建一个将在脚本执行中使用或者以其他方式 使用的 Java Language Binding,具体取决于该键是否被保留 public interface ScriptEngine ScriptEngine 是基础接口,该接口的方法在此规范的每个实现中都必须具有完整的功能。 这些方法提供基本的脚本功能。为这个简单接口编写的应用程序应该对每个实现稍做修改就能够运行。 这包括执行脚本的方法,以及设置和获取值的方法。这些值是两种类型的键/值对。组成第一种类型的键/值对中的 键是此规范或个别实现中保留和定义的键。包含保留键的键/值对中的值具有指定的含义。 另一种类型由那些创建 Java 语言 Bindings 的键/值对组成,值通常通过相应键或其装饰的形式用脚本表示。 Bindings createBindings() 返回一个未初始化的 Bindings。 void setBindings(Bindings bindings, int scope) 设置将由脚本使用的指定值的范围。 public class ScriptEngineManager extends ObjectScriptEngineManager 为 ScriptEngine 类实现一个发现和实例化机制,还维护一个键/值对集合来存储所有 Manager 创建的引擎所共享的状态。 此类使用服务提供者机制枚举所有的 ScriptEngineFactory 实现。 ScriptEngineManager 提供了一个方法,可以返回一个所有工厂实现和基于语言名称、 文件扩展名和 mime 类型查找工厂的实用方法所组成的数组。 键/值对的 Bindings(即由管理器维护的 "Global Scope") 对于 ScriptEngineManager 创建的所有 ScriptEngine 实例都是可用的。Bindings 中的值通常公开于所有脚本中。 ScriptEngine getEngineByName(String shortName) 查找并创建一个给定名称的 ScriptEngine。 public interface Invocable 由 ScriptEngines 实现的可选接口,该 ScriptEngines 的方法允许在以前执行过的脚本中调用程序。 Object invokeFunction(String name, Object... args) 用于调用脚本中定义的顶层程序和函数。

慎用动态编译(注意:在框架中谨慎使用,不要在要求高性能的项目中使用,动态编译要考虑安全问题,记录动态编译过程)

避免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";

assert assert 当布尔表达式为假时,抛出一个AssertionError错误,是继承自Error的,并附带错误信息,默认不启动。 不可用:在对外的方法中不可用,在执行逻辑代码时不可用。因为生产中断言不会执行。 可用:在私有方法中放置assert作为输入参数校验。流程控制中不可能达到的区域。建立程序探针,即判断程序中不可变的量是否改变。

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

第二章,基本类型

i%2 == 1?"奇数":"偶数";//输入-1为偶数 public static int remainder(int dividend,int divisor){ return dividend - dividend/divisor*divisor; }

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) }

在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;

第三章,类对象及方法

静态变量是类加载时被分配到数据区(Data Area)的,它在内存中只有一个拷贝,不会被分配多次,其后的所有赋值操作都是值改变,地址则保持不变。JVM初始化变量是先声明空间,然后在赋值(int i= 12;==>int i ; i =12;)

静态变量在类初始化时首先被加载的,JVM会查找类中所有的静态申明,然后分配空间,只是完成地址空间分配,还没有赋值,之后会根据类中的静态赋值(包括静态类型赋值和静态块赋值)的先后顺序执行。变量先申明后使用。

实例对象有两个类型:表面类型(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(); } } //结果 //我是子类的非静态方法 //我是父类的静态方法 //我是子类的静态方法

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

1,普通代码块:方法名后面{ }部分。

2,静态代码块:在类中使用static修饰的{ },用于静态变量的初始化和对象创建前的环境初始化。类中的静态块会在整个类加载过程中的初始化阶段执行,而不是在类加载过程中的加载阶段执行。初始化阶段是类加载过程中的最后一个阶段,该阶段就是执行类构造器方法的过程,方法由编译器自动收集类中所有类变量(静态变量)的赋值动作和静态语句块中的语句合并生成 ,一个类一旦进入初始化阶段,必然会执行静态语句块。所以说,静态块一定会在类加载过程中被执行,但不会在加载阶段被执行

3,同步代码块:使用synchronized修饰的{ },表示同一时间只能有一个线程进入到该方法块,一种多线程保护机制。

4,构造代码块:在类中没有人任何前缀和后缀的{ },编译器会把构造代码块插入到构造函数的最前端。

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

在通过new关键字生成一个实例时会先执行构造代码块,然后在执行其他构造函数代码,依托于构造函数运行,不是在构造函数之前运行。应用:

1,初始化实例变量(Instance Variable):如果每个构造函数都需要初始化变量,可以通过构造代码块实现。

2,初始化实例环境:当一个对象必须在适的场景才能存在,jee中要产生HTTP Request,必须要建立HTTP session ,可以在创建HTTP Request时通过构造代码块检查HTTP Session是否存在,不存在就创建。

java中的嵌套类(Nesetd Class):分为两种,静态内部类(也叫静态嵌套类,Static Nested Class)和内部类(Inner Class),

静态内部类:加强了类的封装性,提高了代码的可读性。

静态内部类不持有外部类的引用(普通内部类可以访问外部类的方法,属性,即使是private类型也可以访问,静态内部类只可以访问外部类的静态方法和静态属性),静态内部类不依赖外部类(普通内部类与外部类之间是相互依赖的关系,内部类实例不能脱离外部类实例,同生同死,一起声明,一起被拉圾回收器回收,静态内部类可以独立存在,即使外部类消亡,静态内部类还是可以存在),普通内部不能声明static的方法和变量(常量可以修饰,静态内部类没有限制)

//声明一个ArrayList对象。 List la = new ArrayList(); //一个继承了ArrayList的匿名类的声明和赋值,没有任何覆写方法。 List lb = new ArrayList(){}; //在上面的基础上增加了构造函数块。可以有多个。 List lc = new ArrayList(){{}{}};

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()); } }

//父亲 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; } } }

public class UtilsClass{ private UtilsClass(){ throw new Error("不要实例化我哎"); } }

一个类在实现了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()); } }

被拷贝的类只要实现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 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; } }

HashMap的底层处理机制是以数组的方式保存Map条目的,链表保存val,依据传入元素的hashCode方法返回的哈希值决定数组下标,如果该位置已有Map条目了,且与传入的键值相等则不要处理,若不相等则则覆盖,如果数组位置没有条目则插入。并加入到Map条目的链表中。即在检查相等时也是由哈希吗确定位置。

哈希码:由Object方法本地生成,确保每一个对象有一个哈希码(哈希算法,输入任意L,通过一定算法f(L),将其转化为非可逆的输出,一对一,多对一成立),重写hashCode方法:

public int hashCode(){ return new HashCodeBuilder().append(),toHashCode();//HashCodeBuilder哈希码生成工具。 }

Java JVM

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:后端开发实践系列——Spring Boot项目模板
下一篇:谢佳标:R语言初学者必须要知道的事儿
相关文章