重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
这篇文章主要介绍“Java中不可或缺的小技巧有哪些”,在日常操作中,相信很多人在Java中不可或缺的小技巧有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中不可或缺的小技巧有哪些”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
成都地区优秀IDC服务器托管提供商(创新互联).为客户提供专业的珉田数据中心,四川各地服务器托管,珉田数据中心、多线服务器托管.托管咨询专线:13518219792
Integer.valueOf(“1”)、Boolean.valueOf(“true”)等。
可读性高(方法名)
性能(不一定创建对象)
灵活性高
下面针对三个优势进行一些解读。
new Point(x,y)和Point.at(x,y)、Point.origin()。构造函数只能看出两个参数,不知其意,后者更易理解。
在某些情况下,可以事先进行实例化一些对象,调用时直接调用即可,不需要进行改变。比如,Boolean。
public final class Boolean implements Serializable, Comparable{ // 预先设置两个对象 public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); public Boolean(boolean var1) { this.value = var1; } public Boolean(String var1) { this(parseBoolean(var1)); } // 工厂方法 public static Boolean valueOf(boolean var0) { return var0?TRUE:FALSE; // 返回预先设置的对象,而不是创建对象 } // 工厂方法 public static Boolean valueOf(String var0) { return parseBoolean(var0)?TRUE:FALSE; } // ... other code }
可根据具体情况,返回子类。相当于更强大的工厂。直接从父类获取到子类。尤其适用于工具类(提供各种API)。例子:Collections。
public class Collections { // 私有,典型工厂 private Collections() { } public static final List EMPTY_LIST = new EmptyList<>(); // 工厂方法 public static finalList emptyList() { return (List ) EMPTY_LIST; } private static class EmptyList extends AbstractList implements RandomAccess, Serializable { // code } // 工厂方法 public static List checkedList(List list, Class type) { // 根据具体情况,获取相应子类 return (list instanceof RandomAccess ? new CheckedRandomAccessList<>(list, type) : new CheckedList<>(list, type)); } // 子类1 static class CheckedRandomAccessList extends CheckedList implements RandomAccess { CheckedRandomAccessList(List list, Class type) { super(list, type); } public List subList(int fromIndex, int toIndex) { return new CheckedRandomAccessList<>( list.subList(fromIndex, toIndex), type); } } // 子类2 static class CheckedList extends CheckedCollection implements List { // code } }
尤其在进行Android开发时,会碰到这种情况。通常是一个对象,具有多个成员变量可能需要初始化,常规方法,需要提供大量构造函数。例如:
// 非Android中的AlertDialog,便于说明问题,举个例子 public class AlertDialog { private int width; private int height; private String title; private String confirmText; private String denyText; private AlertDialog(){} public AlertDialog(int width, int height){ // 空白的警告框 AlertDialog(width,height,null); } // 带标题的警告框 public AlertDialog(int width, int height, String title){ // 带标题的警告框 AlertDialog(width, height, title, "确定"); } // 带标题的警告框,有确定按钮 public AlertDialog(int width, int height, String title, String confirm){ AlertDialog(width, height, title, confirm, null); } // 带标题的警告框,有确定按钮,取消按钮 public AlertDialog(int width, int height, String title, String confirm, String denyText){ // set every thing. } }
有多种样式的警告框,为了调用方便,必须提供多个构造函数。否则用户在调用时,只能使用完整构造函数,容易犯错且无法进行阅读。极不灵活。如果采用另外一种方式,则可以解决,但会花费很多经历处理并发的情况:
// 非Android中的AlertDialog,便于说明问题,举个例子 public class AlertDialog { private int width; private int height; private String title; private String confirmText; private String denyText; public AlertDialog(){}// 空白的构造函数 public void setWidth(int width){ this.width = width; } // 其他set方法 }
调用时,通过调用各个参数的set方法进行设置。问题来了:
并发
无法进行参数校验。例如,只创建了对象,设置了标题,却没有尺寸,相当于创建了一个没有尺寸的警告框。
在Android中,大量的控件都使用了构造器Builder。
// 非Android中的AlertDialog,便于说明问题,举个例子 public class AlertDialog { private int width; private int height; private String title; private String confirmText; private String denyText; // private private AlertDialog(){} // Builder中使用 protected AlertDialog(Builder b){ width = b.width; height = b.height; // ..... if(width==0||height==0) throws new Exception("size must be set"); } // 构造器 public static class Builder { private int width; private int height; private String title; private String confirmText; private String denyText; // 注意:返回的Builder。 public Builder setTitle(String title) { this.title = title; return this; } // 其他set... public AlertDialog build(){ return AlertDialog(this); } } }
于是,可以根据相应需求,进行相应设置,并在AlertDialog真正构造时,进行参数校验。就像这样:
new AlertDialog.Builder().setTitle("提示").build();
上述例子,会成功抛出异常。
Singleton指最多会被实例化一次的类。通常情况下,以前的做法是没有问题的。但是在某些高级情况,通过使用反射的相关知识访问private的构造函数,破坏Singleton。
public class Elvis{ // 注意,公有final对象 public static final Elvis INSTANCE = new Elvis(); private Elvis(){} }
另一种情况,在序列化的过程中,反序列化得到的对象已经不再是以前的对象(破坏了Singleton),这种情况下,可以通过单元素枚举型处理。
public enum Elvis{ INSTANCE; // some methods }
有一些工具类,仅仅是提供一些能力,自己本身不具备任何属性,所以,不适合提供构造函数。然而,缺失构造函数编译器会自动添加上一个无参的构造器。所以,需要提供一个私有化的构造函数。为了防止在类内部误用,再加上一个保护措施和注释。
public class Util{ private Util(){ // 抛出异常,防止内部误调用 throw new AssertionError(); } }
弊端是无法对该类进行继承(子类会调用super())。
对象的重用
昂贵的对象,使用对象池
廉价的对象,慎用对象池。现代JVM对廉价对象的创建和销毁非常快,此时不适于使用对象池。
以下三种情况可能会造成内存泄露:
自己管理的内存(数组长度减小后,pop出的对象容易导致内存泄漏)
缓存
监听和回调
对于自己管理的内存要小心,比如:
public class Stack{ private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack(){ elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e){ ensureCapacity(); elements[size++]=e; // allocate新的堆内存和栈内存 } public Object pop(){ if(size==0) throw new EmptyStackException(); return element[--size]; // pop出element[size],该对象不再有效。内存泄漏原因。 } private void ensureCapacity(){ if(elements.length==size) elements = Arrays.copyOf(elements, 2*size+1); } }
弹出的对象不再有效,但JVM不知道,所以会一直保持该对象,造成内存泄露。
解决:
public Object pop(){ if(size==0) throw new EmptyStackException(); elements[size] = null; // 等待回收 return element[--size]; }
缓存的对象容易被程序员遗忘,需要设置机制来维护缓存,例如不定期回收不再使用的缓存(使用定时器)。某些情况下,使用WeakHashMap可以达到缓存回收的功效。注,只有缓存依赖于外部环境,而不是依赖于值时,WeakHashMap才有效。
使用监听和回调要记住取消注册。确保回收的最好的实现是使用弱引用(weak reference),例如,只将他们保存成WeakHashMap的键。
Java的GC有强大的回收机制,可以简单的记住:不要显示调用finalizer。可以这样理解:
jvm是针对具体的硬件设计的,然而程序却不是针对具体硬件设计的,所以,java代码无法很好的解决gc问题(因为他具有平台差异化)。另外,finalizer的性能开销也非常大,从这个角度上考虑也不应该使用它。
自反性。x.equals(x) == true
对称性。当前仅当y.equals(x)==true时,x.equals(y)==true
传递性。if(x.equals(y)&&y.equals(z)),y.equals(z)==true
一致性。
非空性。x.equals(null)==false
为了保证基于散列的集合使用该类(HashMap、HashSet、HashTable),同时,也是Object.hashCode的通用约定,覆盖equals方法时,必须覆盖hashCode。
Object的toString方法的通用约定是该对象的描述。注意覆盖时,如果有格式,请备注或者严格按照格式返回。
目的是解耦。简单来讲,使用修饰符的优先级从大到小,private>protected>default(缺省)>public。如果在设计之初,设计为private修饰符后,在之后的编码过程如果不得不扩大其作用于,应该先检查是否设计的确如此。
子类覆盖超类,不允许访问级别低于超类的访问级别。(超类的protected,子类覆盖后不能改为default)。
成员变量决不允许是公有的。一旦设置为公有,则放弃了对他处理的能力。这种类并不是线程安全的。即使是final的,也不允许。除非希望通过public static final来暴露常量。成员变量总是需要使用setter和getter来维护。有一个例外:长度非零的数组。这是安全漏洞的一个根源。
// 安全漏洞!此处的数组,并不是不可变的 public static final Thing[] VALUES = {...}
改进:
private static final Thing[] PRIVATE_VALUES = {...} // 此时获取到的才是“常量” public static final ListVALUS = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES))
另一种:
private static final Thing[] PRIVATE_VALUES = {...} // 此时获取到的才是“常量” public static final Thing[] values(){ return PRIVATE_VALUES.clone(); }
继承有利于代码复用,但是尽可能不要进行跨包的继承。包内的继承是优秀的设计方式,一个包里的文件处在同一个程序员的控制之下。但是继承有其局限性:子类依赖于超类。超类一旦发生更改,将可能破坏子类。并且,如果超类是有缺陷的,子类也会得“遗传病”。
复合,即不扩展已有的类,而是在的类中新增一个现有类的。相当于现有类作为一个组建存在于新类中。如此,将只会用到需要用到的东西,而不表现现有类所有的方法和成员变量。新类也可以称为“包装类”,也就是设计模式中的Decorate模式。
函数参数可以传入类似listener的对象,目的是使用listener中的方法。如果使用匿名的参数,每一次调用会创建新的对象。可以将listener声明为成员变量,每次都复用同一个对象,并且可以使用静态域(static变量)。比如String类的CASE_INSENSITIVE_ORDER域。
关注公众号【程序员白楠楠】获取2020年末总结面试资料一套!
嵌套类的目的应该只是为了他的外围类提供服务,如果以后还可能用于其他环境中,则应该设计为顶层类。静态类相当于一个普通的外部类,只是恰好声明在了一个类内部。通常的用户是:Calculator.Operation.PLUS等。和普通类的区别只是,在PLUS前,有了2个前缀,来表明其含义。而非静态类必须存在于外部类对象中。不要手动在外部创建一个内部非静态类对象,创建的过程是:instance.New MemberClass()。这非常奇怪。
如果成员类不需要访问外围类,则需要添加static,是他成为静态成员类,否则每个实例都将包含一个额外指向外围对象的引用。将会影响垃圾回收机制。
例如,应该指定List,而不建议直接使用List。
在使用IDE进行编码时,强大的IDE都会在你编码过程中提示warning,需要尽可能的消除warning,至少,应该小心这些warning。慎用SuppresWarning,如果IDE提示你可以通过添加该注解解决掉warning,请不要那么做。如果实在要使用,请添加注释说明原因。
类比泛型,数组是有一定缺陷的。List和List是没有关系的,而Sub[]是Super[]的子类。
// Fails at runtime Object[] objectArray = new Long[1]; objectArray[0] = "I don't fit in"; // throw exception // won't compile List
从代码中可以看到,使用泛型,会提前发现错误。
PECS,producer-extends,consumer-super。
//public class Stack{ // public Stack(); // public void push(E e); // public E pop(); // public boolean isEmpty(); //} public void pushAll(Iterator extends E> src){ for(E e : src) push(e); } public void popAll(Collection super E> dst){ while(!isEmpty()){ dst.add(pop()); } } // Get and Put Principle
所有comparable和comparator都是消费者(Consumer)。
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH } public enum Orange { NAVEL, TEMPLE, BLOOD }
枚举型在java中非常强大,当需要一组固定常量时,使用enum比int好很多。比如代码可读性,安全性等。
// bad solution public enum Ensemble { SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET; public int numberOfMusicians() { return ordinal() + 1; } } // // improvement public enum Ensemble { SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), SEXTET(6), SEPTET(7), OCTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12); private final int numberOfMusicians; Ensemble(int size) { this.numberOfMusicians = size; } public int numberOfMusicians() { return numberOfMusicians; } }
永远不要像第一种的方式,利用序数访问enum,需要在构造函数中使用参数来初始化。
public class Text{ public static final int STYLE_BOLD = 1 << 0; // 1 public static final int STYLE_ITALIC = 1 << 1; // 2 public static final int STYLE_UNDERLINE = 1 << 2; // 4 public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8 public void applyStyles(int styles){ // ... } } // text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
以上叫做位图法,但是有更好的方案来传递多组常量——EnumSet。
public class Text{ public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH } // 注意此处,使用的是Set而不是EnumSet public void applyStyles(Set