Java += 操作符实质
问题 我之前以为: i += j 等同于 i = i + j; 但假设有:
int i = 5;
long j = 8;
这时 i = i + j 不能编译,但 i += j 却可以编译。这说明两者还是有差别的 这是否意味着,i += j,实际是等同于 i= (type of i) (i + j)呢?
回答 这个问题,其实官方文档中已经解答了。 请看这里 §15.26.2 Compound Assignment Operators
再照搬下官方文档的说明
对复合赋值表达式来说,E1 op= E2 (诸如 i += j; i -= j 等等),其实是等同于 E1 = (T)((E1) op (E2)),其中,T是E1这个元素的类型。
举例来说,如下的代码
short x = 3;
x += 4.6;
等同于
short x = 3;
x = (short)(x + 4.6);
将InputStream转换为String
使用Apache库 不重复造轮子。最靠谱的方法,还是用Apache commons IOUtils 这样简单几行代码就搞定了
StringWriter writer = new StringWriter();
IOUtils.copy(inputStream, writer, encoding);
String theString = writer.toString();
或者 String theString = IOUtils.toString(inputStream, encoding)//这个方法其实封装了上面的方法,减少了一个参数
使用原生库 如果不想引入Apache库,也可以这样做
static String convertStreamToString(java.io.InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
将数组转换为List
问题 假设有数组
Element[] array = {new Element(1),new Element(2),new Element(3)};
如何将其转换为ArrayList
回答1
new ArrayList<Element>(Arrays.asList(array))
回答2
Arrays.asList(array)或者Arrays.asList(new Element(1),new Element(2),new Element(3))
不过,这样做有些坑要注意:
1.这样做生成的list,是定长的。也就是说,如果你对它做add或者remove,都会抛UnsupportedOperationException。
2.如果修改数组的值,list中的对应值也会改变!
Arrays.asList() 返回的是Arrays内部静态类,而不是Java.util.ArrayList的类。这个java.util.Arrays.ArrayList有set(),get(),contains()方法,但是没有任何add() 方法,所以它是固定大小的
如果希望避免这两个坑,请改用这个方式
Collections.addAll(arraylist, array);
HashMap遍历
在Java中有多种遍历HashMAp的方法。让我们回顾一下最常见的方法和它们各自的优缺点。由于所有的Map都实现了Map接口,所以接下来方法适用于所有Map(如:HaspMap,TreeMap,LinkedMap,HashTable,etc)
方法#1 使用For-Each迭代entries
这是最常见的方法,并在大多数情况下更可取的。当你在循环中需要使用Map的键和值时,就可以使用这个方法
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue())
}
注意:For-Each循环是Java5新引入的,所以只能在Java5以上的版本中使用。如果你遍历的map是null的话,For-Each循环会抛出NullPointerException异常,所以在遍历之前你应该判断是否为空引用。
方法#2 使用For-Each迭代keys和values
如果你只需要用到map的keys或values时,你可以遍历KeySet或者values代替entrySet
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//iterating over keys only
for (Integer key : map.keySet()) {
System.out.println("Key = " + key);
}
//iterating over values only
for (Integer value : map.values()) {
System.out.println("Value = " + value);
}
这个方法比entrySet迭代具有轻微的性能优势(大约快10%)并且代码更简洁
方法#3 使用Iterator迭代
使用泛型
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<Integer, Integer> entry = entries.next();
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
不使用泛型
Map map = new HashMap();
Iterator entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
Integer key = (Integer)entry.getKey();
Integer value = (Integer)entry.getValue();
System.out.println("Key = " + key + ", Value = " + value);
}
你可以使用同样的技术迭代keyset或者values
这个似乎有点多余但它具有自己的优势。首先,它是遍历老java版本map的唯一方法。另外一个重要的特性是可以让你在迭代的时候从map中删除entries的(通过调用iterator.remover())唯一方法.如果你试图在For-Each迭代的时候删除entries,你将会得到unpredictable resultes 异常。
从性能方法看,这个方法等价于使用For-Each迭代
方法#4 迭代keys并搜索values(低效的)
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (Integer key : map.keySet()) {
Integer value = map.get(key);
System.out.println("Key = " + key + ", Value = " + value);
}
这个方法看上去比方法#1更简洁,但是实际上它更慢更低效,通过key得到value值更耗时(这个方法在所有实现map接口的map中比方法#1慢20%-200%)。如果你安装了FindBugs,它将检测并警告你这是一个低效的迭代。这个方法应该避免
总结
如果你只需要使用key或者value使用方法#2,如果你坚持使用java的老版本(java 5 以前的版本)或者打算在迭代的时候移除entries,使用方法#3。其他情况请使用#1方法。避免使用#4方法。
Java修饰符:public,protected,private,不加修饰符。有什么区别呢?
如下表所示,Y表示能访问(可见性),N表示不能访问,例如第一行的第3个Y,表示类的变量/方法如果是用public修饰,它的子类能访问这个变量/方法
修饰符 类内部 同个包(package) 子类 其他范围
修饰符 | 类内部 | 同个包(package) | 子类 | 其他范围 |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
无修饰符 | Y | Y | N or Y(见说明) | N |
private | Y | N | N | N |
说明: 需要特别说明“无修饰符”这个情况,子类能否访问父类中无修饰符的变量/方法,取决于子类的位置。如果子类和父类在同一个包中,那么子类可以访问父类中的无修饰符的变量/方法,否则不行。
译注:本来觉得很简单一个问题,没想记录的,但看到答案,才发现自己以前错了。我以前一直以为无修饰符和private是一样的,如果没给变量加修饰符,java就默认为private。
如何测试一个数组是否包含指定的值
指定数组,如:
public static final String[] VALUES = new String[] {“AB”,”BC”,”CD”,”AE”};
现在制定一个值 s,有哪些比较好的方式,判断这个数组 VALUES 是否包含值 s?
简单且优雅的方法:
Arrays.asList(...).contains(...) |
自己写逻辑
问题的本质,其实是一个查找的问题,即查找一个数组是否包含某个值。对于原始类型,若是无序的数组,可以直接写一个 for 循环:
public static boolean useLoop(String[] arr, String targetValue) { |
若是有序的数组,可以考虑二分查找或者其他查找算法:
public static boolean useArraysBinarySearch(String[] arr, String targetValue) { |
若数组里包含的是一个个对象,实际上比较就是引用是否相等(String 类型是判断 值是否相等),本质就是比较 hashcode 和 equal 方法,可以考虑使用 List 或者 Set,如下
public static boolean useList(String[] arr, String targetValue) { |
重写(Override)equals和hashCode方法时应考虑的问题
理论上讲(编程语言、数学层面) equals() 定义了对象的相等关系(自反性、对称性、传递性)(有点抽象,更详细说明,请参考javadoc) 。 另外,它还具有一致性(也就是说,如果一个对象没有修改,那么对象的equals方法,应总是返回相同的值),此外,o.equals(null)应当总是返回false。 hashCode()(javadoc)也必须具备一致性的(也就是说,如果equal的结果没有变,那么hashcode()也应总是返回相同的值)
总的来说,这两个方法的关系:
假如a.equals(b),那么a.hashCode() 应等于b.hashCode()
实践上讲
如果你重写了其中一个方法,那么务必重写另外一个方法
equals()和hashCode()所计算的属性集(set of fields)应当是一样的 如何更快地重写这两个方法呢?
使用Apache Commons Lang library中的EqualsBuilder、HashCodeBuilder
public class Person { |
如果你是用eclipse,可以在代码编辑区右键,然后选择 Source > Generate hashCode() and equals()
另外请记得
当你使用一些基于Hash的 Collection 、 Map,例如HashSet, LinkedHashSet, HashMap, Hashtable, 、WeakHashMap等。在键值对被放到集合中之后,请确保其key值所对应的hashCode()是保持不变的。比较可靠的一个办法,是保持这些key是不可变的,这也能带来不少好处
从一个多层嵌套循环中直接跳出
问题 Java中如何从一个多层嵌套循环中退出,例如下面,有两个循环,break只能退出一个for循环,不能直接跳过第二个for循环
for (Type type : types) { |
回答
可以用break+label的语法,例子如下
public class Test { |
首先在for循环前加标签,如例子中的outerloop,然后在for循环内break label(如本例的outerloop),就会跳出该label指定的for循环。
如何将String转换为Int
有两种方式
Integer x = Integer.valueOf(str); |
这两种方式有一点点不同:
valueOf
返回的是java.lang.Integer
的实例parseInt
返回的是基本数据类型int
Short.valueOf/parseShort,Long.valueOf/parseLong
等也是有类似差别。
int foo; |
如何分割(split)string字符串 使用String#split()方法
如下所示:
String string = "004-034556"; |
需要注意的是,该方法的参数是个正则表达式,要注意对某些字符做转码。例如,.在正则表达式中表示任意字符,因此,如果你要通过.号做分割,需要这样写,split("\\.")
或者split(Pattern.quote("."))
如果只是为了验证字符串中是否包含某个字符,使用String#contains
方法就行。注意该方法的参数,不是正则表达式
在java中如何对比(compare)string
==
对应的是指针相等,也就是他们是否为同一个对象.equals()
对应的是值相等,也就是逻辑相等
因此,如果你想检查两个字符串是否为相同值,那么应该用.equals()
方法
//值是相等的 |
因此, 值的对比,一般都是用equals方法。字符串字面量之间的对比,也可以用==(大家知其所以然即可,但没必要用==)
下面多举个字符串字面量的例子,下面代码中,前四个对比,返回true,最后一个返回false。
public static final String test1 = "test"; |
其他
- 如果你重写了equal方法,记得相对应地修改hashcode方法,否则将会违反这两个方法的对等关系,如果两个对象是相等(equal)的,那么两个对象调用hashCode必须产生相同的整数结果,即:equal为true,hashCode必须为true,equal为false,hashCode也必须为false
- 如果要忽略大小写进行对比,可以用equalsIgnoreCase()方法
Map<Key,Value>基于Value值排序
方法1: 使用TreeMap,可以参考下面的代码
public class Testing { |
译注:如果不自己写Comparator,treemap默认是用key来排序
方法2:
先通过linkedlist排好序,再放到LinkedHashMap中
public class MapUtil |
译注:这两种方法,我简单测试了下,如果map的size在十万级别以上,两者的耗时都是几百毫秒,第二个方法会快一些。否则,第一个方法快一些。因此,如果你处理的map,都是几十万级别以下的大小,两种方式随意使用,看个人喜欢了。
HashMap和Hashtable的区别
问题
在Java中HashMap
和Hashtable
的区别? 哪一个对于多线程应用程序更好?
回答
Hashtable
是同步的,加了synchronized
锁,而HashMap
不是。没有加synchronized
锁的对象,性能通常比加了synchronized
锁的对象要更好一些,因此,如果是非多线程程序,不需要考虑锁、同步等问题,那么使用HashMap
更好。Hashtable
不允许有空的键或值。HashMap
允许空键和空值。HashMap
有一个子类LinkedHashMap
,对这个类对象进行迭代时,它的顺序是有序的(按插入顺序排序)。如有需要,你也能轻易的从LinkedHashMap
转化成HashMap
。Hashtable
就没那么简单了,
总之,如果你无需关心同步(synchronized
)问题,我会建议用HashMap。反之,你可以考虑使用ConcurrentHashMap
如何便捷地将两个数组合到一起
一行代码搞定 Apache Commons Lang library ArrayUtils.addAll(T[], T…)就是专门干这事的
代码:
String[] both = ArrayUtils.addAll(first, second); |
不借助依赖包
非泛型 把下面的Foo替换成你自己的类名
public Foo[] concat(Foo[] a, Foo[] b) { |
泛型
public <T> T[] concatenate (T[] a, T[] b) { |
注意,泛型的方案不适用于基本数据类型(int,boolean……)
Java 是否支持默认的参数值?
在 c++ 中,常见到如下的方法定义(param3 默认为 false):
void MyParameterizedFunction(String param1, int param2, bool param3=false); |
那在 java 中,是否也支持这样的定义方式?
答案是否定的,不过我们可以通过多种方式处理这种参数默认值的情况。
创建者模式
使用创建者模式,你可以设定部分参数是有默认值,部分参数是可选的。如:
Student s1 = new StudentBuilder().name("Eli").buildStudent(); |
方法(构造函数)重载
如:
void foo(String a, Integer b) { |
构造函数重载,对于参数比较少的情况下,比较适合;当参数相对多的时候,可以考虑使用静态工厂方法,或添加一个参数辅助对象。
如果是常规方法重载,可以考虑使用 参数辅助对象,或者重命名多种情况(比如说,有多个开银行卡的重载方法,可以根据需要重命名为 开交行卡,开招行卡 等多种方法)。
null 的传递
当有多个默认参数时,可以考虑传递 null,当参数为 null 时,将参数设为 默认值。如:
void foo(String a, Integer b, Integer c) { |
多参数方式
当有多个参数,且某些参数可以忽略不设置的情况下,可以考虑使用多参数方式。
- 可选的参数类型的一致
void foo(String a, Integer... b) { |
- 可选参数类型不一致
void foo(String a, Object... b) { |
使用 Map 作为方法中的参数
当参数很多,且大部分参数都会使用默认值的情况,可以使用 Map 作为方法中的参数。
void foo(Map<String, Object> parameters) { |
java 产生指定范围的随机数
问题,如何使用 java 产生 010,510 之间的随机数?
Math.random()
Math.random() 可以产生一个 大于等于 0 且 小于 1 的双精度伪随机数,假设需要产生 ”0《= 随机数 <=10” 的随机数,可以这样做:
int num =(int)(Math.random() * 11); |
那如何产生 “5 <= 随机数 <= 10” 的随机数呢?
int num = 5 + (int)(Math.random() * 6); |
生成 “min <= 随机数 <= max ” 的随机数
int num = min + (int)(Math.random() * (max-min+1)); |
java.util.Random
Random 是 java 提供的一个伪随机数生成器。
生成 “ min <= 随机数 <= max ” 的随机数:
import java.util.Random; |
标准库
在实际使用中,没有必要区重新写一次这些随机数的生成规则,可以借助一些标准库完成。如 commons-lang
.
org.apache.commons.lang3.RandomUtils
提供了如下产生指定范围的随机数方法:
// 产生 start <= 随机数 < end 的随机整数 |
org.apache.commons.lang3.RandomStringUtils
提供了生成随机字符串的方法,简单介绍一下:
// 生成指定个数的随机数字串 |
JavaBean 到底是什么?
问题
按照我的理解: “Bean
” 是一个带有属性和getters/setter
方法的Java类。它是不是和C的结构体是相似的呢,对吗? 一个“Bean
“类与普通的类相比是不是语法的不同呢?还是有特殊的定义和接口? 为什么会出现这个术语呢,这让我很困惑? 如果你很好心告诉我一些关于Serializable
接口的信息,对于你的答案那到底是什么意思,我会非常感谢你的。
回答JavaBean
只是一个标准
- 所有的属性是私有的(通过getters/setters处理属性)
- 一个公有的无参数的构造器
- 实现了
序列化(Serializable)
就这些,它只是一个规范。但是很多的类库都是依赖于这些预定。
对于Serializable
,看一下API文档的解释
实现java.io.Serializable接口的类能串行化。 |
换句话说,序列化的对象可以被写入流,文件,对象数据库等。
另外,一个JavaBean
类和一个普通的类没有语法区别,如果遵循上面的标准的话,一个类可以认为成JavaBean
类。
之所以需要JavaBean
,是因为这样预定义了一种类的格式,一些库能依据这个约定的格式,来做一些自动化处理。举个例子,如果一个类库需要通过流来处理你传递的任何对象,它知道它可以正常处理,因为这个对象是可序列化的。(假设这个类库要求你的对象是JavaBeans
)
wait()和sleep()的区别
问题:
在线程里 wait()
和 sleep()
的区别?
我的理解是执行 wait()
语句后,该线程仍是运行态,并且会占用CPU,但是执行 sleep()
后,该线程则不会占用CPU,对吗?
为什么需要 sleep()
和 wait()
两条语句:他们底层是如何实现的?
回答:
线程 在wait
后,可以被另一个拥有相同 synchronized
对象的线程,通过调用 notify
唤醒,而 sleep
不行。wait
和 notify
能正常执行的条件是(否则会抛异常):多个线程的代码,都包在synchronized
块中,并且 synchronized
锁的对象需要是同一个。如下所示:
Object mon = ...; |
上面这个线程调用了 wait
后,会进入等待状态。这时另外一个线程可以这样做:
synchronized (mon) { mon.notify(); } |
可以看到,synchronized
锁对象,都是mon
。因此,当第二个线程调用了 notify() 方法,第一个线程就会唤醒(假设有且仅有一个线程是被包在 synchronized (mon)
中且处于等待状态)。
如果有多个线程在等待(且synchronized
锁对象是同一个,如上例中的mon
),则可以调用 notifyAll
来唤醒。但是,只有其中一个线程能抢到锁并继续执行(因为 wait
的线程都是在 synchronized
块内,需要争夺 synchronized
锁)。其他的线程会被锁住,直到他们依次获得锁。
再补充几点:
wait
方法由Object
对象调用(例如:你可以让synchronized
锁对象调用 wait ,如上面例子的mon.wait()
),而sleep
则由线程调用。wait
之后,可能会伪唤醒(spurious wakeups
)(正在waiting
的线程,无故就被唤醒了,如遇到interrupted, timing out
等情况)。因此,你需要多设置一些检查,如果不满足实际的运行条件,则继续等待,如下:
synchronized { |
当线程调用 sleep
时,并没有释放对象锁,而 wait
则释放了对象锁:
synchronized(LOCK) { |
最后,再小结一下:
sleep()
:“我已经完成了一个时间片,在n微秒前,请不要再给我一个时间片”。这时操作系统不会让这个线程做任何事情,直到sleep
时间结束。wait()
:”我已经完成了一个时间片,在其他线程调用notify()
前,请不要再给我一个时间片)。这时操作系统不会安排这个线程继续运行,直到有人调用了notify()
能否在一个构造器中调用另一个构造器
问题 能否在一个构造器中调用另一个构造器(在同一个类中,不是子类)?如果可以,怎么做? 调用另一个构造器的最好方法是什么(如果有几种方法可以选择的话)?
回答 可以这样做:
public class Foo |
如果你想调用一个特定的父类构造器,而不是本类的构造器,应该使用super,而不是this. 请注意,在构造器中,你只能调用一次其他的构造器。并且调用其他构造器的语句,必须是这个构造器的第一个语句。
finally 代码块总会被执行么
有一个 try/catch 代码块,其中包含一个打印语句。finally代码块总会被执行么?
示例:
try { |
回答
- finally 将会被调用。
只有以下情况 finally 不会被调用:
- 当你使用 System.exit() 后
- 其他线程干扰了现在运行的线程(通过 interrupt 方法)
- JVM 崩溃( crash )了
Answered by Jodonnell, edited by jpaugh.
- //示例代码
class Test |
输出:
finally trumps return. |
如何将String转换为enum
问题
假设定义了如下的enum(枚举):
public enum Blah { |
已知枚举对应的String值,希望得到对应的枚举值。例如,已知”A”,希望得到对应的枚举——Blah.A,应该怎么做?
Enum.valueOf()是否能实现以上目的,如果是,那我如何使用?
答案
是的,Blah.valueOf(“A”) 将会得到 Blah.A
静态方法valueOf() 和 values() 不存在于源码中,而是在编译时创建,我们也可以在JavaDoc查看到它们,比如 Dialog.ModalityTyp 就中出现这两个方法。
其他答案
我有一个挺赞的工具方法:
/** |
你可以这么使用:
public static MyEnum fromString(String name) { |
在java中声明数组
问题描述: 你是如何在Java中声明数组的。
回答: 你可以直接用数组声明,或者通过数组的字面常量(array literal )声明
对于原始类型(primitive types):
int[] myIntArray = new int[3]; |
对于其他类,比如String类,也是相同的:
String[] myStringArray = new String[3]; |
反射(reflection)是什么及其用途?
问题描述 反射是什么,为什么它是有用的? 我特别感兴趣的是java,但我认为任何语言的原理都是相同的。
回答 反射的概念,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。在java中,通过反射,能够在”运行态”动态获得任意一个类的所有属性和方法,动态地调用对象的方法。
举个例子,假设你有一个不知道具体类的对象,并且你想调用它的”dosomething”方法(如果存在的话)。java的静态类型系统只能调用一个已知类对象对应的已知接口,在未指定对象类型时,无法调用它的方法。但是通过反射,你的代码能检查这个未知类对象,并试图找出这个dosomething方法。如果存在这个方法,你可以通过反射调用这个方法。
为了进一步说明,请看下面的例子(下面的对象foo,就是上文提到的,我们不知道它对应的类是什么):
Method method = foo.getClass().getMethod("dosomething",null); |
反射这个特性,经常会用于各种注解中(annotations
)。举个例子,Junit4
将使用反射来遍历你的代码,查找所有加了@test
注解的类方法,之后运行测试单元时就调用这些方法。
最后,其概念在其他支持反射的静态类型语言中也是非常相似的。在动态语言中,无需用到上面说的第一种用法场景——调用未知类的方法(因为动态语言编允许任意对象调用任意方法,如果不存在对应方法,在运行时就会失败),但是第二种情况,查找做了指定标记的方法,这种场景还是很常见的
为什么不能用string类型进行switch判断
问题描述 为什么不能用string类型进行switch判断? 在java的后续版本中,是否会增加这个新特性? 有人能给我一篇文章,解释一下为什么不能这样做,或者进一步说明java中switch语句的运行方式?
回答 在switch语句中用string作为case,这个特性已经在java SE7 中被实现了,距离 这个’bug’ 被提出至少也有16年了。为何迟迟不提供这个特性,原因不明。但可以推测,可能跟性能有关。
Implementtation in JDK 7
在JDK7中,这个特性已经实现了。在编译阶段,以string作为case值的代码,会按照特定的模式,被转换为更加复杂的代码。最终的执行代码将是一些使用了JVM指令的代码。
究竟是如何转换的呢?我们直接看看源码及编译后的代码。源代码:
public class StringInSwitchCase { |
编译后再反编译的代码:
import java.io.PrintStream; |
包含case string的 switch 语句,在编译时会转为为嵌套代码(switch+if)。第一个switch将 case 中的string转为唯一的integer值。这个integer值就是原先string的hashcode值。在case的逻辑中,会加入if语句,这个if语句用于进一步检查string值是否跟原先的case string匹配。这样可以防止hash碰撞,确保代码的健壮。这本质上是一种语法糖,既支持了string作为case值这一特性,又能确保逻辑正确性。
Switchs in the JVM
switch的更多深层技术实现,可以参考JVM规范,compliation of switch statements
。简单概括说,根据使用的常量的多寡,switch会对应到两种不同的JVM指令。JVM指令有所不同,归根结底都是为了代码的效率。
如果常量很多,会将case的int值去掉最低位后作为索引,放到一个指针表中——也就是所谓的tablewitch指令
如果常量相对较少,那么可用二分查找来找到正确的case–也就是所谓的lookupswitch指令
这两种指令,都要求在编译时确保case的对应值是integer常量。在运行时,虽然tableswitchO(1)
的性能通常要好于lookupswitchO(log(n))
的性能。但是前者需要更多的空间开销,因此需要兼顾空间及时间综合考虑性价比。Bill Venners
的文章a great article
有更多深入的分析。
Before JDK 7
在JDK之前,可以用枚举来实现类似的需求。它和在case中使用string有异曲同工之妙。例如如下:
Pill p = Pill.valueOf(str); |
比较java枚举成员使用equal还是==
问题
我知道Java枚举会被编译成一个包含私有构造参数和一堆静态方法的类,当去比较两个枚举的时候,总是使用equals()方法,例如:
public useEnums(SomeEnum a) |
除此之外,我也可以使用 == 替代equals() 方法
public useEnums2(SomeEnum a) |
我有5年以上的java编程经验,并且我想我也懂得 == 和 equals() 之间的区别,但是我仍然觉得很困惑,哪一个操作符才是我该使用的。
答案
二者皆对,如果你看过枚举的源码,你会发现在源码中,equals也仅仅非常简单的 == 。 我使用 == ,因为无论如何,这个左值是可以为 null的
译者补充 java.lang.Enum 中Equals 代码:
public final boolean equals(Object other) { |
额外答案
能在枚举中使用 == 进行判断?
答案是肯定的,因为枚举有着严格的实例化控制,所以你可以用 == 去做比较符,这个用法,在官方文档中也有明确的说明。
JLS 8.9 Enums 一个枚举类型除了定义的那些枚举常量外没有其他实例了。 试图明确地说明一种枚举类型是会导致编译期异常。在枚举中final clone方法确保枚举常量从不会被克隆,而且序列化机制会确保从不会因为反序列化而创造复制的实例。枚举类型的反射实例化也是被禁止的。总之,以上内容确保了除了定义的枚举常量之外,没有枚举类型实例。
因为每个枚举常量只有一个实例,所以如果在比较两个参考值,至少有一个涉及到枚举常量时,允许使用“==”代替equals()。(equals()方法在枚举类中是一个final方法,在参数和返回结果时,很少调用父类的equals()方法,因此是一种恒等的比较。)
什么时候 == 和 equals 不一样?
As a reminder, it needs to be said that generally, == is NOT a viable alternative to equals. When it is, however (such as with enum), there are two important differences to consider: 通常来说 == 不是一个 equals的一个备选方案,无论如何有2个重要的不同处需要考虑:
== 不会抛出 NullPointerException
enum Color { BLACK, WHITE }; |
== 在编译期检测类型兼容性
enum Color { BLACK, WHITE }; |
什么时候使用 == ?
Bloch specifically mentions that immutable classes that have proper control over their instances can guarantee to their clients that == is usable. enum is specifically mentioned to exemplify. 具体来说,那些提供恰当实例控制的不可变类能够保证 == 是可用的,枚举刚好符合这个条件。
考虑静态工厂方法代替构造器 它使得不可变的类可以确保不会存在两个相等的实例,即当且仅当a==b的时候才有a.equals(b)为true。如果类保证了这一点,它的客户端可以使用“==”操作符来代替equals(Object)方法,这样可以提升性能。枚举类型保证了这一点
总而言之,在枚举比较上使用 == , 因为:
- 能正常工作
- 更快
- 运行时是安全的
- 编译期也是安全的
用java怎么创建一个文件并向该文件写文本内容
创建一个文本文件(注意:如果该文件存在,则会覆盖该文件)
PrintWriter writer = new PrintWriter("the-file-name.txt", "UTF-8"); |
创建一个二进制文件(同样会覆盖这文件)
byte data[] = ... |
Java 7+ 用户可以用File
类来写文件 创建一个文本文件
List<String> lines = Arrays.asList("The first line", "The second line"); |
创建一个二进制文件
byte data[] = ... |
其他的答案(1):
在Java 7+中
try (Writer writer = new BufferedWriter(new OutputStreamWriter( |
还有一些实用的方法如下:
FileUtils.writeStringtoFile(..)
来自于commons-io
包Files.write(..)
来自于guava
Note also that you can use a FileWriter, but it uses the default encoding, which is often a bad idea - it’s best to specify the encoding explicitly. 还要注意可以使用FileWriter
,但是它使用的是默认编码,这不是很好的方法,最好是明确指定编码
下面是来自于prior-to-java-7的原始方法
Writer writer = null; |
可以看Reading, Writing, and Creating Files
(包含NIO2)
其他答案(2):
public class Program { |
其他答案(3):
如果已经有想要写到文件中的内容,java.nio.file.Files
作为 Java 7 附加部分的native I/O,提供了简单高效的方法来实现你的目标
基本上创建文件,写文件只需要一行,而且是只需一个方法调用! 下面的例子创建并且写了6个不同的文件来展示是怎么使用的
Charset utf8 = StandardCharsets.UTF_8; |
其他答案(4):
下面是一个小程序来创建和写文件。该版本的代码比较长,但是可以容易理解
|
serialVersionUID 有什么作用?该如何使用?
问题
当一个对象实现 Serializable 接口时,多数 ide 会提示声明一个静态常量 serialVersionUID(版本标识),那 serialVersionUID 到底有什么作用呢?应该如何使用 serialVersionUID ?
回答
serialVersionUID 是实现 Serializable 接口而来的,而 Serializable 则是应用于Java 对象序列化/反序列化。对象的序列化主要有两种用途:
- 把对象序列化成字节码,保存到指定介质上(如磁盘等)
- 用于网络传输
现在反过来说就是,serialVersionUID 会影响到上述所提到的两种行为。那到底会造成什么影响呢?java.io.Serializable doc
文档,给出了一个相对详细解释:serialVersionUID 是 Java 为每个序列化类产生的版本标识,可用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象。如果接收方接收的类的 serialVersionUID 与发送方发送的 serialVersionUID 不一致,进行反序列时会抛出 InvalidClassException。序列化的类可显式声明 serialVersionUID 的值,如下:
` ` `
ANY-ACCESS-MODIFIER static final long serialVersionUID = 1L;
` ` `
当显式定义 serialVersionUID 的值时,Java 根据类的多个方面(具体可参考 Java 序列化规范)动态生成一个默认的 serialVersionUID 。尽管这样,还是建议你在每一个序列化的类中显式指定 serialVersionUID 的值,因为不同的 jdk 编译很可能会生成不同的 serialVersionUID 默认值,进而导致在反序列化时抛出 InvalidClassExceptions 异常。所以,为了保证在不同的 jdk 编译实现中,其 serialVersionUID 的值也一致,可序列化的类必须显式指定 serialVersionUID 的值。另外,serialVersionUID 的修饰符最好是 private,因为 serialVersionUID 不能被继承,所以建议使用 private 修饰 serialVersionUID。
举例说明如下: 现在尝试通过将一个类 Person 序列化到磁盘和反序列化来说明 serialVersionUID 的作用: Person 类如下:
public class Person implements Serializable { |
简单的测试一下:
@Test |
测试发现没有什么问题。有一天,因发展需要, 需要在 Person 中增加了一个字段 email,如下:
public class Person implements Serializable { |
这时我们假设和之前序列化到磁盘的 Person 类是兼容的,便不修改版本标识 serialVersionUID。再次测试如下
@Test |
将以前序列化到磁盘的旧 Person 反序列化到新 Person 类时,没有任何问题。
可当我们增加 email 字段后,不作向后兼容。即放弃原来序列化到磁盘的 Person 类,这时我们可以将版本标识提高,如下:
private static final long serialVersionUID = 2L; |
再次进行反序列化,则会报错,如下:
java.io.InvalidClassException:Person local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2 |
谈到这里,我们大概可以清楚,serialVersionUID 就是控制版本是否兼容的,若我们认为修改的 Person 是向后兼容的,则不修改 serialVersionUID;反之,则提高 serialVersionUID的值。再回到一开始的问题,为什么 ide 会提示声明 serialVersionUID 的值呢?
因为若不显式定义 serialVersionUID 的值,Java 会根据类细节自动生成 serialVersionUID 的值,如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,也有可能会导致不同的serialVersionUID。所以 ide 才会提示声明 serialVersionUID 的值。
附录拓展:
为什么Java的Vector类被认为是过时的或者废弃的
问题
为什么java Vector
类被认为是一个遗留的,过时的或废弃的类?在并发操作时,使用它是无效的吗?
如果我不想手动对对象实现同步,只想用一个线程安全的集合而无需创建底层数组的全新副本(如CopyOnWriteArrayList
一样)。这种情况下,我使用Vector合理吗?
然后就是关于栈的问题,它是Vector
的一个子类,我应该用什么代替它?
回答
Vector中对每一个独立操作都实现了同步,这通常不是我们想要的做法。对单一操作实现同步通常不是线程安全的(举个例子,比如你想遍历一个Vector实例。你仍然需要申明一个锁来防止其他线程在同一时刻修改这个Vector实例。如果不添加锁的话
通常会在遍历实例的这个线程中导致一个ConcurrentModificationException
)同时这个操作也是十分慢的(在创建了一个锁就已经足够的前提下,为什么还需要重复的创建锁)
当然,即使你不需要同步,Vector
也是有锁的资源开销的。
总的来说,在大多数情况下,这种同步方法是存在很大缺陷的。正如Mr Brain Henk指出,你可以通过调用Collections.synchronizedList
来装饰一个集合 -事实上 Vector
将“可变数组”的集合实现与“同步每一个方法”结合起来的做法是另一个糟糕的设计;
各个装饰方法能够更明确的指示其关注的功能实现。
对于Stack这个类-我更乐于使用Deque/ArrayDeque
来实现
Java的foreach循环是如何工作的?
问题
List<String> someList = new ArrayList<String>(); |
如果不用for each语法,等价的循环语句是什么样的?
回答
for(Iterator<String> i = someList.iterator(); i.hasNext(); ) { |
记住,如果需要在循环中使用i.remove;或者以某种方式获取实际的iterator,你不能使用for(:)语法,因为实际的Iterator很难被推断出来。 正如Denis Bueno写的那样,这种代码对任何实现了Iterable接口的对象都奏效。 此外,如果for(:)句法中右侧是一个数组而不是一个可迭代对象,那么内部代码用一个int型的计数器来防止数组越界。详见Java Language Specification
为什么这两个时间(1927年)相减会得到一个奇怪的结果?
问题描述 如果我运行如下的程序,将两个相距一秒的日期解析成字符串并比较他们。
public static void main(String[] args) throws ParseException { |
输出结果为:
353 |
为什么ld4-ld3
不是1
(正如我所期望的那样),而是353
?
如果我把时间改变为之后的一秒:
String str3 = "1927-12-31 23:54:08"; |
时区:
sun.util.calendar.ZoneInfo[id="Asia/Shanghai", |
问题回答
这是因为1927年11月31日上海的时区改变了。 观看此页获得更多关于上海1927年的细节。 这个问题主要是由于在1927年12月31日的午夜,时钟回调了5分钟零52秒。 所以”1927-12-31 23:54:08”这个时间实际上发生了两次,看上去java将这个时间解析为之后的那个瞬间。 因此出现了这种差别。
该什么时候使用 ThreadLocal变量,它是如何工作的?
回答1
一种可能的(也是常见的)使用情形是你不想通过同步方式(synchronized)访问非线程安全的对象(说的就是SimpleDateFormat),而是想给每个线程一个对象实例的时候。 例如
public class Foo |
回答2
因为ThreadLocal是一个既定线程内部的数据引用,你可能在使用线程池的应用服务器上因此引起类加载时候的内存泄漏。你需要使用remove()方法很小心地清理TheadLocal中get()或者set()的变量。 如果程序执行完毕没有清理的话,它持有的任何对类的引用将作为部署的Web应用程序的一部分仍保持在永久堆,永远无法得到回收。重新部署/取消部署也无法清理对应用程序类的引用,因为线程不是被你的应用程序所拥有的。 每次成功部署都会创建一个永远不会被垃圾回收类的实例。
最后将会遇到内存不足的异常-java.lang.java.lang.OutOfMemoryError: PermGen space -XX:MaxPermSize
,在google了很多答案之后你可能只是增加了-XX:MaxPermSize
,而不是修复这个bug。 倘若你的确遇到这种问题,可以通过Eclipse's Memory Analyzer
或根据Frank Kieviet's guide
和 followup
来判断哪些线程和类保留了那些引用。
更新:又发现了Alex Vasseur’s blog entry,它帮助我查清楚了一些ThreadLocal的问题。
如何计算MD5值
问题 Java中有没有方法可以计算一个String的MD5值?
回答 你可以用 MessageDigest
的MD5实例来计算String的MD5值。
使用 MessageDigest
和 String
时,一定要显式声明你的数据编码类型。如果你使用无参的 String.getBytes()
, 它会以当前平台的默认编码来转换数据。不同平台的默认编码可能是不同的,这可能会导致你的数据不一致。
import java.security.*; |
如果你的要计算的数据量很大,你可以循环使用 .update(byte[]) 方法来加载数据。加载完毕后用 .digest() 方法来得到计算出的MD5值。
Java内部类和嵌套静态类
问题
Java 当中的内部类和静态嵌套类有什么主要区别? 在这两者中有什么设计或者实现么?
回答
嵌套类分为两类: 静态和非静态. 用static
装饰的嵌套类叫做静态类, 非静态的嵌套类叫做内部类.
静态嵌套类使用外围类名来访问:
OuterClass.StaticNestedClass |
例如, 实例化一个静态嵌套类的对象就要使用这种语法:
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass(); |
内部类对象的存在需要依靠一个外部类的对象. 看看下面的类:
class OuterClass { |
内部类对象只有当外部类对象存在时才有效, 并且可以直接访问他的包裹对象(外部类对象)的方法以及成员.
因此, 要实例化一个内部类对象, 必须先实例化外部类对象. 然后用这种语法来创建内部类对象:
OuterClass.InnerClass innerObject = outerObject.new InnerClass(); |
提醒一下, 还有一种不用外部类对象来创建内部类对象的方法: inner class without an enclosing
class A { |
在这里, new A() { ... }
是一个定义在静态上下文的内部类对象, 并没有一个外围对象.
如何创建泛型java数组
问题
数组是不能通过泛型创建的,因为我们不能创建不可具体化的类型的数组。如下面的代码:
public class GenSet<E> { |
采纳答案
- 检查:强类型。
GenSet
明确知道数组中包含的类型是什么(例如通过构造器传入Class<E>
,当方法中传入类型不是E将抛出异常)
public class GenSet<E> { |
- 未检查:弱类型。数组内对象不会有任何类型检查,而是作为Object类型传入。
在这种情况下,你可以采取如下写法:
public class GenSet<E> { |
上述代码在编译期能够通过,但因为泛型擦除的缘故,在程序执行过程中,数组的类型有且仅有Object类型存在,这个时候如果我们强制转化为E类型的话,在运行时会有ClassCastException抛出。所以,要确定好泛型的上界,将上边的代码重写一下:
public class GenSet<E extends Foo> { // E has an upper bound of Foo |