让java代码更加的简洁

2017-01-11

这篇文章是再说android的java代码更加简洁,当然,做java web开发的也可以阅读。

现在几乎很多公司android项目都是MVP了,OK,我也曾经有这么一篇博客:《挖掘更合适的MVP模式的架构设计》也说到了,架构只是解耦,对代简洁代码帮助并不是特别的大,咳咳,这篇博客在CSDN上被推荐后,24小时内阅读量达到2k+(请允许我装个B!!)。

其实本文的重头戏是精简Model的代码。今天还是分别汇总来说下M 、V、 P每个模块中,真正可以帮助代码更加简洁,减少代码的方案。

先从View开始。

1.View

View这块因为使用了MVP,逻辑本来就已经干掉了80%,所以基本上代码量就少80%了,再配合butterknife这个库又少了一些。
但是,还是有一些可能需要需要手动注册,难免要手动setXXXListener(),难免就要new interface的东西,或者ruannable,timertask,之类的,这样子代码的行数又就变得多了,所以Lambda的话可以很大程度上减轻这个问题,所以就又上了一个台阶,使用lambda代码量就大大减少了,没有用过labmbda可以看我的另外一篇blog《android项目中尝试使用Lambda》。

注:不使用lambda的时候,虽然IDE在你阅读代码时会自动帮你把new 的代码的头部收起来,但是那仅仅限于你第二次在IDE中打开这个类的时候,你第一次写的时候并不会主动帮你收起来,而且收起来之后如果你手动打开了,他并不会再主动合上,要手动合上才可以。

2.Presenter

presenter主要是业务逻辑,因为有大量的业务逻辑和数据处理,所以presenter代码很可能非常多,逻辑稍微写不好,易读性大大降低,所以RxJava问世,流式编程,逻辑像水一样顺流而下,逻辑看起来超级流畅,只是代码行数会变多起来,rxjava并没有减少代码行数,只是帮你整理了逻辑,但是如果再配合lambda,rxjava+lambda来写presenter代码简直完美。

3.Model

觉得model是本文的重头戏的原因是:view和presenter里面的各种方式来优化代码代码逻辑的代码行数的框架很多人都用过了,比如butterknife,ViewInject,rxjava框架等等,唯一觉得可能lambda用的不是特别多吧,其他各种大家都用过,但是到了model这里,优化model代码的框架大家用的真的是很少,因为“中国的互联网公司”都是疯狗式工作法,所以没有人在意你的代码质量,大家只想看到开发成果,在没框架的情况下,model大家写的都很简洁,偷懒。看下面:

比如:Student1

public class Student1 {
    public int age;
    public String name;
}
  • 没有序列化
  • 没有重写equal
  • 没有hashcode

这种写法,还是可以暂时凑合着用,功能仅限于json parse,如果需要一些序列化,或者当我写的健全一些的时候:Student2:

/**
 * Created by wei on 16-12-30.
 * 一个完整的Student类 
 */
public class Student2 implements Parcelable {

    private int age;
    private String name;

    public Student2(int i, String n) {
        age=i;
        name=n;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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


    @Override
    public int hashCode() {
        int hash = 7;
        hash = 31 * hash + this.age;
        hash = 31 * hash + (null == name ? 0 : name.hashCode());
        return hash;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof Student2)) return false;
        Student2 s = (Student2) o;
        if (s.age != s.age) return false;
        return s.name.equals(s.name);
    }


    //如下代码使用 intelij 插件生成
    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.age);
        dest.writeString(this.name);
    }

    protected Student2(Parcel in) {
        this.age = in.readInt();
        this.name = in.readString();
    }

    public static final Parcelable.Creator<Student2> CREATOR = new Parcelable.Creator<Student2>() {
        @Override
        public Student2 createFromParcel(Parcel source) {
            return new Student2(source);
        }

        @Override
        public Student2[] newArray(int size) {
            return new Student2[size];
        }
    };
}

代码行数直接彪到78行,而我只有两个字段啊啊啊啊啊啊!!!!就算没有用Lambda精简interface的代码,也会在第二次在ide中打开时自动隐藏头部,而这个model类里这些代码永远不会自动隐藏啊,他又不是new interface,所以model的代码ide也不能自动隐藏。

所以我需要一个框架去帮我写这么多的代码。

java有个功能是编译时生成,利用注解处理器,在编译时生成这些代码。

谷狗开源的Auto-Value

核心类在于AbstractProcessor注解处理器。
ButterKnife这个库也是用这个东西,在ButterKnife的源代码里ButterKnifeProcessor这个类就是继承于AbstractProcessor。

(这个类的玩法很多,有兴趣的同学可以没事玩玩)

使用了Auto-value之后的这个类这么写:

@AutoValue
public abstract class Student3 implements Parcelable {

    public static Student3 create(int age, String name) {
        return new AutoValue_Student3(age, name);//这里 可能会报错  可能你没有配置apt 的依赖
    }

    public abstract int age();

    public abstract String name();
}

使用之前是78行,之后变为11行。
用的时候:

        //test1
        Student1 s1=new Student1();
        s1.age=1;
        s1.name="wei";

        Student1 s1_c=new Student1();
        s1_c.age=1;
        s1_c.name="wei";

        //test2
        Student2 s2=new Student2(1,"wei");
        Student2 s2_c=new Student2(1,"wei");
        //test3
        Student3 s3=Student3.create(1,"wei");
        Student3 s3_c=Student3.create(1,"wei");


        toast(s1.equals(s1_c)+"");//false
        toast(s2.equals(s2_c)+"");//true
        toast(s3.equals(s3_c)+"");//true

所以这么一个库帮我们减少了很的代码量。
但是,实际代码打包时的代码量可不是这么少,我们看下编译的时候生成的几个java类:AutoValue_Student3 和 基类$AutoValue_Student3:

$AutoValue_Student3:

 abstract class $AutoValue_Student3 extends Student3 {

  private final int age;
  private final String name;

  $AutoValue_Student3(
      int age,
      String name) {
    this.age = age;
    if (name == null) {
      throw new NullPointerException("Null name");
    }
    this.name = name;
  }

  @Override
  public int age() {
    return age;
  }

  @Override
  public String name() {
    return name;
  }

  @Override
  public String toString() {
    return "Student3{"
        + "age=" + age + ", "
        + "name=" + name
        + "}";
  }

  @Override
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }
    if (o instanceof Student3) {
      Student3 that = (Student3) o;
      return (this.age == that.age())
           && (this.name.equals(that.name()));
    }
    return false;
  }

  @Override
  public int hashCode() {
    int h = 1;
    h *= 1000003;
    h ^= this.age;
    h *= 1000003;
    h ^= this.name.hashCode();
    return h;
  }

}

AutoValue_Student3:

import android.os.Parcel;
import android.os.Parcelable;
import java.lang.Override;
import java.lang.String;

final class AutoValue_Student3 extends $AutoValue_Student3 {
  public static final Parcelable.Creator<AutoValue_Student3> CREATOR = new Parcelable.Creator<AutoValue_Student3>() {
    @Override
    public AutoValue_Student3 createFromParcel(Parcel in) {
      return new AutoValue_Student3(
          in.readInt(),
          in.readString()
      );
    }
    @Override
    public AutoValue_Student3[] newArray(int size) {
      return new AutoValue_Student3[size];
    }
  };

  AutoValue_Student3(int age, String name) {
    super(age, name);
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(age());
    dest.writeString(name());
  }

  @Override
  public int describeContents() {
    return 0;
  }
}

可能有人可能不喜欢Atuo-value的用法,那么还有一个库,immutables。

immutables

这个库,跟auto-value一样的实现原理,用法有一点点的区别,优点是定义类的时候代码少了一个create方法。缺点等下再说。

@Value.Immutable
public abstract class Student4 {

    public abstract int age();
    public abstract String name();
}

///////         使用            /////////////////////
        //test1
        Student1 s1 = new Student1();
        s1.age = 1;
        s1.name = "wei";

        Student1 s1_c = new Student1();
        s1_c.age = 1;
        s1_c.name = "wei";

        //test2
        Student2 s2 = new Student2(1, "wei");
        Student2 s2_c = new Student2(1, "wei");
        //test4
        ImmutableStudent4 s4 = ImmutableStudent4.builder()
                .age(1)
                .name("wei")
                .build();


        ImmutableStudent4 s4_c = ImmutableStudent4.builder()
                .age(1)
                .name("wei")
                .build();


        toast(s1.equals(s1_c) + "");//false
        toast(s2.equals(s2_c) + "");//true
        toast(s4.equals(s4_c) + "");//true

实际编译时生成的代码:

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

/**
 * Immutable implementation of {@link Student4}.
 * <p>
 * Use the builder to create immutable instances:
 * {@code ImmutableStudent4.builder()}.
 */
@SuppressWarnings({"all"})
public final class ImmutableStudent4 extends Student4 {
  private final int age;
  private final String name;

  private ImmutableStudent4(int age, String name) {
    this.age = age;
    this.name = name;
  }

  /**
   * @return The value of the {@code age} attribute
   */
  @Override
  public int age() {
    return age;
  }

  /**
   * @return The value of the {@code name} attribute
   */
  @Override
  public String name() {
    return name;
  }

  /**
   * Copy the current immutable object by setting a value for the {@link Student4#age() age} attribute.
   * A value equality check is used to prevent copying of the same value by returning {@code this}.
   * @param value A new value for age
   * @return A modified copy of the {@code this} object
   */
  public final ImmutableStudent4 withAge(int value) {
    if (this.age == value) return this;
    return new ImmutableStudent4(value, this.name);
  }

  /**
   * Copy the current immutable object by setting a value for the {@link Student4#name() name} attribute.
   * An equals check used to prevent copying of the same value by returning {@code this}.
   * @param value A new value for name
   * @return A modified copy of the {@code this} object
   */
  public final ImmutableStudent4 withName(String value) {
    if (this.name.equals(value)) return this;
    String newValue = ImmutableStudent4.requireNonNull(value, "name");
    return new ImmutableStudent4(this.age, newValue);
  }

  /**
   * This instance is equal to all instances of {@code ImmutableStudent4} that have equal attribute values.
   * @return {@code true} if {@code this} is equal to {@code another} instance
   */
  @Override
  public boolean equals(Object another) {
    if (this == another) return true;
    return another instanceof ImmutableStudent4
        && equalTo((ImmutableStudent4) another);
  }

  private boolean equalTo(ImmutableStudent4 another) {
    return age == another.age
        && name.equals(another.name);
  }

  /**
   * Computes a hash code from attributes: {@code age}, {@code name}.
   * @return hashCode value
   */
  @Override
  public int hashCode() {
    int h = 31;
    h = h * 17 + age;
    h = h * 17 + name.hashCode();
    return h;
  }

  /**
   * Prints the immutable value {@code Student4} with attribute values.
   * @return A string representation of the value
   */
  @Override
  public String toString() {
    return "Student4{"
        + "age=" + age
        + ", name=" + name
        + "}";
  }

  /**
   * Creates an immutable copy of a {@link Student4} value.
   * Uses accessors to get values to initialize the new immutable instance.
   * If an instance is already immutable, it is returned as is.
   * @param instance The instance to copy
   * @return A copied immutable Student4 instance
   */
  public static ImmutableStudent4 copyOf(Student4 instance) {
    if (instance instanceof ImmutableStudent4) {
      return (ImmutableStudent4) instance;
    }
    return ImmutableStudent4.builder()
        .from(instance)
        .build();
  }

  /**
   * Creates a builder for {@link ImmutableStudent4 ImmutableStudent4}.
   * @return A new ImmutableStudent4 builder
   */
  public static ImmutableStudent4.Builder builder() {
    return new ImmutableStudent4.Builder();
  }

  /**
   * Builds instances of type {@link ImmutableStudent4 ImmutableStudent4}.
   * Initialize attributes and then invoke the {@link #build()} method to create an
   * immutable instance.
   * <p><em>{@code Builder} is not thread-safe and generally should not be stored in a field or collection,
   * but instead used immediately to create instances.</em>
   */
  public static final class Builder {
    private static final long INIT_BIT_AGE = 0x1L;
    private static final long INIT_BIT_NAME = 0x2L;
    private long initBits = 0x3L;

    private int age;
    private String name;

    private Builder() {
    }

    /**
     * Fill a builder with attribute values from the provided {@code Student4} instance.
     * Regular attribute values will be replaced with those from the given instance.
     * Absent optional values will not replace present values.
     * @param instance The instance from which to copy values
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder from(Student4 instance) {
      ImmutableStudent4.requireNonNull(instance, "instance");
      age(instance.age());
      name(instance.name());
      return this;
    }

    /**
     * Initializes the value for the {@link Student4#age() age} attribute.
     * @param age The value for age 
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder age(int age) {
      this.age = age;
      initBits &= ~INIT_BIT_AGE;
      return this;
    }

    /**
     * Initializes the value for the {@link Student4#name() name} attribute.
     * @param name The value for name 
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder name(String name) {
      this.name = ImmutableStudent4.requireNonNull(name, "name");
      initBits &= ~INIT_BIT_NAME;
      return this;
    }

    /**
     * Builds a new {@link ImmutableStudent4 ImmutableStudent4}.
     * @return An immutable instance of Student4
     * @throws java.lang.IllegalStateException if any required attributes are missing
     */
    public ImmutableStudent4 build() {
      if (initBits != 0) {
        throw new IllegalStateException(formatRequiredAttributesMessage());
      }
      return new ImmutableStudent4(age, name);
    }

    private String formatRequiredAttributesMessage() {
      List<String> attributes = new ArrayList<String>();
      if ((initBits & INIT_BIT_AGE) != 0) attributes.add("age");
      if ((initBits & INIT_BIT_NAME) != 0) attributes.add("name");
      return "Cannot build Student4, some of required attributes are not set " + attributes;
    }
  }

  private static <T> T requireNonNull(T object, String message) {
    if (object == null) throw new NullPointerException(message);
    return object;
  }
}

显然,两个库并没有减少打包时的代码,我们打包时,每一行代码都做正常写了,只不过这个库帮我们在编译时生成了。

这个库看上去6-7行代码搞定,明显看上去少写了一个create方法。缺点是:这个库没有一个配合他的parcelable库,如果需要序列化,就比较麻烦,官方在github上issues上的回答是:一个第三方的库来实现,是https://github.com/johncarl81/parceler。

看上去两个库各有个的特色。

说下model定义时,用这两个库的缺点:

1.把类设计成不可变的类

其实项目开发时就应该大量使用不可变类,毕竟这样子涉及优点有 易于设计,实施和使用,
不易出错,更安全,线程安全的,可以自由共享
。所以,我并不觉得你的model类都是不可变类之后,这样子成了一个缺点。
但是如果还是有需求要求他可变的话,immutables也自带了可变的方法,auto-value可能需要借助第三方的库来实现。

2.json parse可能就稍微麻烦一点了。

毕竟不再是可变类,两个库,官方都给了一些方案去叫你处理json. parse。使用复杂度,都在可接受范围内。

3.代码习惯的改变

对于团队大量使用可变类的团队来说,忽然改变使用习惯确实有些难受,可能会有些抵触,或者学习成本在里面。

4.大量使用之后,编译速度可能会有些变化

只是可能,由我自己的使用量,也没有达到不可想象的大。

由于两个库都是编译时生成,那么,这样子可能会带来另外一个问题,就是编译时生成的话,会再编译时做大量的检查处理生成工作,那么必然大量的消耗开发者的电脑的性能,所以这个性能时跑不掉的。那么有的同学会说了“我的电脑是64位,性能好”,但是实际上多种java虚拟机在64位上的表现可不是如此。

在阅读《深入理解java虚拟机》这本书时,发现目前的java虚拟机,在电脑的x64和x86两种cpu下,对比其实x86性能反而更好。原文如下:

Java程序运行在64位虚拟机上需要付出比较大的额外代价:首先是内存问题,由于指针膨胀和各种数据类型对齐补白的原因,运行于64位系统上的Java应用需要消耗更多的内存,通常要比32位系统额外增加10%~30%的内存消耗;其次,多个机构的测试结果显示,64位虚拟机的运行速度在各个测试项中几乎全面落后于32位虚拟机,两者大约有15%左右的性能差距。(节选自《深入理解Java虚拟机》13年出版)
当然了,时间又过去了三年,或许目前x64的机器上java虚拟机表现好了一些吧。

我们之前遇到过一个别的公司项目,大量的使用data-binding,编译速度是20分钟+的时间,简直要炸了,我们给对方一个解决方案就是改项目debug和release两个不同的build配置不同的minsdk,debug的时候或者dev都配置minsdk是21,目前大家手里的测试机器也基本上都是5.0以后的了,所以测试时完全可以用的,等你真正打包上线再改成14或者15。他们改了之后,编译耗时确实有了成倍的提升,但是还是大于60秒。

没办法,data-binding太特殊了,我们目前这种纯java的还好,只要配置内存够,他的操作都是内存中处理,内存操作性能就很好了,但是偏偏data-bing有太多的xml操作,读取和生成,io流太多,编译耗时实在难以提升,60秒对于我们当时公司的团队来说确实承受不了的。毕竟到今天,android studio提供的instant run也是很烂的,感觉像是一个测试版本,各种问题。

关于编译耗时优化的问题,还有很多方案来减少编译耗时,比如配置gradle的jvm内存大小,IDEjvm参数,要修改编译器根目录的配置文件,开启进程守护等等,还有多个Module的时候有个gradle配置来减少编译耗时等等,编译耗时是有很多方案来解决的,我们自己的项目也经历过编译耗时60秒+优化到16秒的情况,所以这个都不是特别大的问题。如果你用这两个库,后期遇到了这种问题,那么是可以解决的。

我有一篇博客《gradle开启multidex之后构建缓慢问题》。

PS:缺点如上了,那么,这么多缺点,和改变平常代码的习惯的这种巨大的对团队的开发效率的影响,综合考虑,还是选择我自己做的一个库。GeneratorX:点下面链接
开发了一个帮我生成java类的库GeneratorX


几个官方github:

https://github.com/google/auto
https://github.com/rharter/auto-value-parcel
https://github.com/immutables/immutables
https://github.com/johncarl81/parceler
上述代码的demo
我自己做的一个库GeneratorX


扫我,我给你讲段子。



注明:本文章属于转载,仅供行业人员学习交流使用,文章版权属于原创作者,在此向原创者致敬,感谢原创作者为大家学习交流提供精品内容。

站方声明:IThao123是为广大互联网从业者免费提供学习交流的平台,如果侵犯了原创著作权,请联系站方删除,给你带来不便,深表歉意。

顶部