文档章节

[笔记]改善Java程序的151个建议---第三章 类,对象,方法

jimyao
 jimyao
发布于 2016/01/29 14:47
字数 3176
阅读 4
收藏 0
点赞 1
评论 0

第三章 类,对象,方法
建议31:在接口中不要存在实现代码
接口是一种契约,框架协议,表明它的实现类都是同一种类型,或者具备相似特征的一个集合体。

建议32:静态变量一定要先声明后赋值
静态变量在类初始化时候首先被加载。
public static int i = 1;
static{
     i = 100;
}

建议33:不要覆写静态方法
@Override针对非静态方法
一个实例对象有2个类型,表面类型(Apparent Type),实际类型(Actual Type)。
表面类型是声明时的类型
实际类型是对象产生时候的类型。
非静态方法是根据对象的实际类型执行的。
静态方法特殊,不依赖实例对象,通过类名访问,也可以通过对象访问。对象访问的话,JVM会通过对象的表面类型找到静态方法入口。

在子类中构建与父类相同的静态方法,这种行为叫“隐藏”。

通过实例对象访问静态方法或者属性不是好习惯。

建议34:构造函数尽量简化
构造函数初始化顺序:
子类实例化,首先初始化父类(初始化不是生产父类对象)初始化父类的变量,调用父类的构造函数,然后才会初始化子类的变量,调用子类的构造函数。最后才生产一个实例对象。
构造函数,用于初始化变量,声明实例的上下文。

建议35:避免在构造函数中初始化其他类
不要在构造函数中声明初始化其他类,是良好习惯。
否则会造成StackOverflowError

建议36:使用构造代码块精炼程序
代码块:用大括号把多行代码封装在一起,形成一个独立的数据体,实现特定算法的代码集合。
Java中有4种类型代码块:
(1)普通代码块
方法后面用{},必须通过方法名调用。
(2)静态代码块
static{},用于静态变量初始化,对象创建前的环境初始化。
(3)同步代码块
synchronized{},表示同一时间只能一个线程进入该代码块。一种线程保护机制。
(4)构造代码块
类中没有任何前缀、后缀,用{}括起来。

编译器如果处理构造代码块?编译器会把构造代码块插入到每个构造函数的最前端。构造代码块会在每个构造函数内首先执行。

应用场景:在每个构造函数中运行,在构造函数中首先运行。
初始化实例变量
初始化实例环境

public class Client{
     {
          //构造代码块
          System.out.println("构造代码块");
     }
    
     Client(){
          System.out.println("无参构造函数");
     }
    
     Client(String str){
          System.out.println("有参构造函数");
     }
}

建议37:构造代码块会想你所想
统计一个类实例的数量
构造函数调用自身其他的构造函数(this关键字),则不插入构造代码块。
class Base{
     //对象计数器
     private static int numOfObjects = 0;
    
     {
          //构造代码块
          numOfObject++;
     }
    
     public Base(){}
    
     //有参调用无参构造
     public Base(String str) {
          this();
     }
    
     //有参不调用其它构造
     public Base(int i){
     }
    
     public static int getNumOfObjects(){
          return numOfObjects;
     }
}

public class Client{
     public static void main(String[] args){
          new Base();
          new Base("");
          new Base(0);
          System.out.println(Base.getNumOfObjects());
     }
}

count = 3


建议38:使用静态内部类提高封装性
Java中嵌套类(Nested Class)分为两种:静态内部类(Static Nested Class) 和内部类(Inner Class)。
静态内部类2个优点:加强了类的封装性和提高了代码的可读性。形似内部,神似外部。编译后类文件名也包含外部类,外部类$内部类。他们之间是强关联关系。
静态内部类和普通类的区别:
(1)静态内部类不持有外部类的引用。普通内部类,可以直接访问外部类的属性和方法。静态内部类,只可以访问外部类的静态方法和静态属性,其他不行。
(2)静态内部类不依赖外部类
普通内部类和外部类之间是依赖关系,内部类不能脱离外部类实例,同生共死不能独立存在。静态外部类可以独立存在,即是外部类消亡,其还可以存在。
(3)普通内部内不能声明static变量和方法
普通内部内不能声明static变量和方法,常量(final static)还是可以的,静态内部类没有任何限制。
public class Person{
     private String name;
     private Home home;
    
     Person(String name){
          this.name = name;
     }
    
     public static class Home{
          private String address;
          private String tel;
         
          Home(String address, String tel) {
               this.address =addresss;
               this.tel = tel;
          }
     }
}

建议39:使用匿名类的构造函数
List l1 = new ArrayList();
List l2 = new ArrayList(){};
List l3 = new ArrayList(){{}};
List l4 = new ArrayList(){{}{}{}{}{}{}};//也可以是多个
l2是匿名类的声明和赋值,定义了一个继承于ArrayList的匿名类。代码类似于:
class Sub extends ArrayList{
}
List l2 = new Sub();

l3是匿名类的声明和赋值。代码类似于:
class Sub extends ArrayList{
     {
          //初始化块,充当构造函数功能
     }
}
List l3 = new Sub();

建议40:匿名类的构造函数很特殊
一般类(显式名字的类)所有构造函数默认都是调用父类的无参构造。
匿名类构造函数处理机制,匿名类没有名字,只能有构造代码块代替,在其初始化的时候直接调用父类同参数构造,然后再调用自己的构造函数。
enum Ops{ADD, SUB};

class Calculator{
     private int i,j,result;
    
     public Calculator(){};
    
     public Calculator(int i, intj){
          this.i= i;
          this.j = j;
     }
    
     //设置运算符号
     protected  void setOperator(Ops ops){
          result = ops.equals(Ops.ASS) ? i + j : i -  j;
     }
    
     //获得运算结果
     public int getResult(){
          return this.result;
     }
}

public static void main(String[] args){
     Calculator cl = new Calculator(1,2){
          setOperator(Ops.ADD);
     }
     System.out.println(cl.getResult());
}

//上面匿名函数相当于
class Add extends Calculator{
     {
          setOperator(Ops.ADD);
     }
     //覆写父类的构造方法
     public Add(int i, int j){
          super(i, j);
     }
}

建议41:让多重继承成为现实
一个类可以多个接口,但不能继承多个类。有时候确实需要继承多个类,Java提供内部类曲折解决此问题。
interface Father{
     public int strong();
}
interface Mother{
     public int kind();
}

class FatherImpl implements Father{
     public int strong(){
          return 8;
     }
}

class MotherImpl implements Mother{
     public int kind(){
          return 8;
     }
}

class Son extends FatherImpl implements Mother{
     @Override
     public int strong(){
          return super.strong() + 1;
     }
    
     @Override
     public int kind(){
          return new MotherSpecial.kind();
     }
     //内部类
     private class MotherSpecial extends MotherImpl{
          public int kind(){
               return super.kind() - 1;
          }
     }
}


//女儿继承了母亲的kind,同时覆写了父类的strong,这里建立了一个匿名内部类来覆写父类的方法。
class Daughter extends MotherImpl implements Father{
     @Override
     public int strong(){
          return new FatherImpl(){
               @Override
               public int strong(){
                    return super.strong() - 2;
               }
          }.strong();
     }
}

建议42: 让工具类不可实例化
Java工具类的方法和属性都是静态的。由于不希望被初始化,于是设置构造函数为private权限,表示除了类本身外,谁都不能产生一个实例。工具类不要继承。
public class UtilsClass{
     private UtilsClass(){
          throw new Error("不要实例化我");
     }
}

建议43:避免对象的浅拷贝
一个类实现了Cloneable接口表示具备了被拷贝能力,覆写了clone()方法完全具备拷贝能力。
浅拷贝,对象属性拷贝不彻底的问题。

public class Person implements Cloneable{

        //姓名

        private String name ;

        //父亲

        private Person father ;

       

       Person(String name){

               this.name = name ;

       }

       

       Person(String name, Person parent){

               this.name = name ;

               this.father = parent ;

       }


        public String getName() {

               return name ;

       }


        public void setName(String name) {

               this.name = name ;

       }


       

        public Person getFather() {

               return father ;

       }


        public void setFather(Person father) {

               this.father = father ;

       }


        @Override

        public Person clone(){

              Person p = null;

               try {

                      p = (Person) super.clone();

                      p.setFather( new Person(p .getFather().getName()));

              } catch (CloneNotSupportedException e ) {

                      // TODO Auto-generated catch block

                      e.printStackTrace();

              }

               return p ;

              

       }

       

}


public class Client {


        public static void main(String[] args) {

               //定义父亲

              Person f = new Person("父亲" );

               //定义大儿子

              Person s1 = new Person("大儿子" , f );

               //小儿子

              Person s2 = s1 .clone();

              s2.setName("小儿子");

              

               //认干爹

               s1.getFather().setName( "干爹");

              System. out.println(s1 .getName() + " 父亲是: " + s1.getFather().getName());

              System. out.println(s2 .getName() + " 父亲是: " + s2.getFather().getName());

              

       }

}


建议44:推荐使用序列化实现对象拷贝
序列化实现对象拷贝比每一个类都写clone更好的办法
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class CloneUtils {
     //拷贝一个对象
     @SuppressWarnings("unchecked")
     public static <T extends Serializable> T clone(T obj) {
          //拷贝产生的对象
            T cloneObj = 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);
                  //返回新对象并作类型转换
               cloneObj = (T)ois.readObject();
               ois.close();
          }catch(ClassNotFoundException | IOException e) {
               e.printStackTrace();
          }
          return cloneObj;
     }
}

上面那个Person类implements Serializable加上SerialVersionUID就可以实现深拷贝

注意两点:
(1)对象内部的属性都是可以序列化的,如内部属性不可序列化,会抛出异常。
(2)注意方法和属性的特殊修饰符
final,static的问题,transient变量的问题。
更简便的方法是,用Apache commons工具包中的SerializationUtils类

建议45:覆写equals方法时候不要识别不出自己
在写一个JavaBean时,经常会覆写equals方法。根据业务规则判断两个对象是否相等。
equals方法的自反性原则:对于任何非空引用x,   x.equals(x)应该返回true。
不要在p.getName()后面在加上.trim()。

例子见46

建议46:equals应该考虑null值情景
equals对称性原则:对于任何引用x和y的情景,如果x.equals(y)返回true,那么y.equals(x)也应该返回true。

//Object Equals
public class PersonEqual {
     String name;
   
     PersonEqual(String _name){
          this.name = _name;
     }
   
   
     public String getName() {
          return name;
     }


     public void setName(String name) {
          this.name = name;
     }


     public boolean equals(Object obj){
          if(obj != null && obj.getClass() == this.getClass()){
               PersonEqual p = (PersonEqual)obj;
               if(p.getName() == null || this.name == null)
                    return false;
               else
                    return this.name.equalsIgnoreCase(p.getName());
          }
          return false;
     }
}


import java.util.ArrayList;
import java.util.List;


public class Client {
     public static void main(String[] args) {
          PersonEqual pe1 = new PersonEqual("yao");
          PersonEqual pe2 = new PersonEqual("yao ");
        
          List<PersonEqual> list = new ArrayList<>();
          list.add(pe1);
          list.add(pe2);
        
          System.out.println(list.contains(pe1));
          System.out.println(list.contains(pe2));
     }
}

//建议47:在equals中使用getClass进行类型判断
equals传递性原则:对于实例对象x,y,z。如果,x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。
//不是同一个类的2个值不能比较相等
//覆写equals时候,用getClass()代替instanceof判断
public class Emp extends PersonEqual {

     private int id;
     Emp(String _name, int _id) {
          super(_name);
          this.id=_id;
          // TODO Auto-generated constructor stub
     }
     public int getId() {
          return id;
     }
     public void setId(int id) {
          this.id = id;
     }

     public boolean equals(Object obj){
          if(obj != null && obj.getClass() == this.getClass()) {
               Emp emp = (Emp)obj;
               return super.equals(obj) && emp.getId() == this.id;
          }
          return false;
     }
}

import java.util.ArrayList;
import java.util.List;


public class Client {
     public static void main(String[] args) {
          Emp e1 = new Emp("yao", 100);
          Emp e2 = new Emp("yao", 1001);
          PersonEqual pe = new PersonEqual("yao");
          System.out.println(pe.equals(e1));
          System.out.println(pe.equals(e2));

     }
}


建议48:覆写equals方法必须覆写hashCode方法
List中Person类的equals覆写了,不再判断两个类的地址是否相等,而是根据equals中的name判断两个对象是否相等。
HashMap的底层处理机制是数组的方式保存MAP条目(Map Entry),关键是数组下标的处理机制。根据传入元素hashCode方法的返回值决定其数组的下标。
如果,该数组位置上已经有了Map条目,并且与传入的键值相等则不处理。
如果,不想等则覆盖;如果数组位置没有条目,则插入,并加入到Map条目的链表中。
检查键是否存在也是根据哈希码定位的,然后遍历查找键值。
hashCode方法的返回值是什么?是一个对象的哈希码。由Object类的本地方法生成,确保每一个对象有一个哈希码。
重写Person类的hashCode方法。
//Person HashCode
public class PersonHashCode {
     String name;
   
     PersonEqual(String _name){
          this.name = _name;
     }
   
   
     public String getName() {
          return name;
     }


     public void setName(String name) {
          this.name = name;
     }


     public boolean equals(Object obj){
          if(obj != null && obj.getClass() == this.getClass()){
               PersonEqual p = (PersonEqual)obj;
               if(p.getName() == null || this.name == null)
                    return false;
               else
                    return this.name.equalsIgnoreCase(p.getName());
          }
          return false;
     }
    
     @Override
     public int hashCode(){
          return new HashCodeBuilder().append(this.getName).toHashCode();
     }
}

public static void main(String[] args){
     //Person类作为Map的Key
     Map<Person, Object> map = new HashMap<>{
          {
               put(new Person("张三"),new Object());
          }
     };
    
     //Person类的实例作为List的元素
     List<Person> list = new ArrayList<>{
          {
               add(new Person("张三"));
          }
         
     };
    
     boolean b1 = list.contains(new Persion("张三"));
     boolean b2 = map.containsKey(new Person("张三"));
}

建议49:推荐覆写toString方法
Java本身提供的toString方法不友好。
ToStringBuilder    //代替Javabean中的toString()方法

建议50:使用package-info类为包服务
特殊的类:package-info
专门为包服务。描述和记录包的信息。
不能随便被创建,服务的对象特殊,此类不能有实现代码。

作用:
(1)声明友好类和包内访问常量
//这里是类包,声明一个包使用的公共类
class PkgClass{
     public void test(){}
}
//包常量,只允许包内访问
class PkgConst{
     static final String  PACKAGE_CONST = "ABC";
}
(2)为包上提供注解便利
写一个注解标注,放到package-info文件中。
(3)提供包的整体注释说明

建议51:不要主动进行垃圾回收


© 著作权归作者所有

共有 人打赏支持
jimyao
粉丝 17
博文 66
码字总数 27856
作品 0
朝阳
面试中关于Java虚拟机(jvm)的问题看这篇就够了

最近看书的过程中整理了一些面试题,面试题以及答案都在我的文章中有所提到,希望你能在以问题为导向的过程中掌握虚拟机的核心知识。面试毕竟是面试,核心知识我们还是要掌握的,加油~~~ 下面...

snailclimb ⋅ 05/12 ⋅ 0

做几道基础的Java测试题,看看最近有进步吗?欢迎来学习

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

启示录是真的 ⋅ 05/24 ⋅ 0

JVM学习之——Java内存区域

为了加深对Java语言的理解,加深对Java虚拟机工作机制、底层特性的了解和掌握,准备在闲暇时间,抽空对《深入理解Java虚拟机 JVM高级特性与最佳实践》一书进行学习。本文是学习此书第2章时的...

你想要怎样的未来 ⋅ 05/27 ⋅ 0

多线程基础必要知识点!看了学习多线程事半功倍

前言 不小心就鸽了几天没有更新了,这个星期回家咯。在学校的日子要努力一点才行! 只有光头才能变强 回顾前面: 多线程三分钟就可以入个门了! Thread源码剖析 本文章的知识主要参考《Java并...

Java3y ⋅ 04/23 ⋅ 0

【JVM】 java内存区域与内存溢出异常

前言 此系列博客是读《深入理解java虚拟机》所做的笔记整理。 No1. JVM内存管理这堵墙? 对C和C++的开发人员来说,在内存管理领域,他们既拥有每一个对象的“所有权”,也担负着每一个对象生...

binggetong ⋅ 05/07 ⋅ 0

叮!您收到一份超值Java基础入门资料!

摘要:Java语言有什么特点?如何最大效率的学习?深浅拷贝到底有何区别?阿里巴巴高级开发工程师为大家带来Java系统解读,带你掌握Java技术要领,突破重点难点,入门面向对象编程,以详细示例...

聒小小噪 ⋅ 05/12 ⋅ 0

深入理解java虚拟机阅读笔记(一)————java内存区域

第二章:Java内存区域与内存溢出 2.2 运行时数据区域 2.2.1 程序计数器: (1)、一块较小的内存空间 (2)、可看做当前线程执行的字节码的行号指示器 (3)、字节码解释器工作时通过改变这个...

qq_37468185 ⋅ 05/10 ⋅ 0

Java程序员必读书单,家族又添新成员

点击关注异步图书,置顶公众号 每天与你分享IT好书 技术干货 职场知识 参与文末话题讨论,每日赠送异步图书。 ——异步小编 有些革命出其不意地吸引了全世界的眼球。Twitter、Linux操作系统和...

异步社区 ⋅ 05/09 ⋅ 0

Effective Java 第三版——41.使用标记接口定义类型

Tips 《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8...

M104 ⋅ 05/08 ⋅ 0

java编程新手入门学习的基础语法

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰 ⋅ 05/28 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Qt中的坑--QTreeWidget添加item 不能显示出来

QTreeWidget* pTree = ui.TreeCheckList; QTreeWidgetItem* item = new QTreeWidgetItem(pTree) ;item->setText ( 0, "test" );pTree->addTopLevelItem (item ); 原因是因为创建一个......

k91191 ⋅ 31分钟前 ⋅ 0

使用Guava的RateLimiter做限流

场景: 1. 在日常生活中,我们肯定收到过不少不少这样的短信,“京东最新优惠卷…”,“天猫送您…”。这种类型的短信是属于推广性质的短信。这种短信一般群发量会到千万级别。然而,要完成这...

wind2012 ⋅ 31分钟前 ⋅ 0

QSlider重新enterEvent

#ifndef DIALOG_H#define DIALOG_H#include <QDialog>namespace Ui {class Dialog;}class Dialog : public QDialog{ Q_OBJECTpublic: explicit Dialog(QW......

xxdd ⋅ 32分钟前 ⋅ 0

生产环境redis备份与恢复

生产环境redis备份与恢复 Tyrant0532 0人评论 1563人阅读 2018-02-01 20:34:10 redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。生产中我们主...

rootliu ⋅ 34分钟前 ⋅ 0

nginx中出现403forbidden错误

nginx “403 Forbidden” 错误 出现这个错误一般是因为以下原因: 网站禁止特定的用户访问所有内容,例:网站屏蔽某个ip访问。 访问禁止目录浏览的目录,例:设置autoindex off后访问目录。 ...

河图再现 ⋅ 34分钟前 ⋅ 0

上海云栖:金融政企行业的CDN最佳实践

摘要: 在刚刚结束的上海云栖大会飞天技术汇分论坛上,阿里云视频云产品架构师罗小飞进行了《阿里云CDN——面向金融政企的CDN最佳实践》主题分享,为上海的嘉宾介绍CDN的解决方案与技术服务体...

猫耳m ⋅ 39分钟前 ⋅ 0

docker 基本操作

docker介绍 Docker项目提供了构建在Linux内核功能之上,协同在一起的的高级工具。其目标是帮助开发和运维人员更容易地跨系统跨主机交付应用程序和他们的依赖。Docker通过Docker容器,一个安全...

haoyuehong ⋅ 40分钟前 ⋅ 0

上海云栖:金融政企行业的CDN最佳实践

摘要: 在刚刚结束的上海云栖大会飞天技术汇分论坛上,阿里云视频云产品架构师罗小飞进行了《阿里云CDN——面向金融政企的CDN最佳实践》主题分享,为上海的嘉宾介绍CDN的解决方案与技术服务体...

阿里云云栖社区 ⋅ 43分钟前 ⋅ 0

安装与配置hadoop

一、CentOS7安装 java8,参考centos7.0 安装java1.8,tomcat 二、安装hadoop 版本V3.03 1、下载并解压hadoop # mkdir /usr/local/app# mkdir /usr/local/app/hadoop# cd /usr/local/app/had......

iturtle ⋅ 44分钟前 ⋅ 0

Idea设置Serializable自动生成

File --> Settings --> Editor --> Inspections ->Serialization issues,在该项下找到“Serializable class without 'serialVersionUID' ”并勾选...

Gmupload ⋅ 47分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部