Java中常见问题

join的锁

join是一个线程的方法,一个线程调用join方法,那么它需要先执行完毕。
一个需要重点记住的就是join会放弃锁,这个是重点!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.example.demo;


import java.util.ArrayList;

public class Miv {
public static void main(String[] args) throws Exception {
R r = new R();
r.start();
r.join();//主线程停止,等子线程运行结束再继续(R线程需要先执行完毕)
System.out.println("over");
}
}
class R extends Thread {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("weak up");
}
}

不同包中的权限问题

1
2
3
4
5
6
7
8
9
10
11
12
package library;
public class Api{
static class PackagePrivate{}
static PackagePrivate member = new PackagePrivate();
}
package client;
import library.Api;
class Client{
public static void main(String[] args){
System.out.println(Api.member.hashCode());
}
}

上面代码中,PackagePrivate不是公共的,所以不可以再其他包里面直接使用他的成员方法。

多层次的反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.demo;

import java.lang.reflect.Constructor;

public class Outer {
private Outer(){
System.out.println("Outer");
}
public static void main(String[] args) throws Exception{
new Outer().greetWorld();
}
private void greetWorld()throws Exception {
// Constructor c = Inner.class.getConstructor(Outer.class);
// System.out.println(c.newInstance(Outer.this));
System.out.println( Inner.class.newInstance() ); //错误
System.out.println( new Inner() ); //正确
}
public class Inner{
public String toString(){
return "Hello world";
}
}
}

线程中断的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.example.demo1;

public class SelfInterruption {
public static void main(String[] args) {
Thread.currentThread().interrupt(); //中断线程
if (Thread.interrupted()) { //如果线程中断,返回true,并且清除中断线程的状态
System.out.println("Interrupted: " +
Thread.interrupted());
} else {
System.out.println("Not interrupted: " +
Thread.interrupted());
}
test();
}

public static void test() {

Thread.currentThread().interrupt();
if (Thread.currentThread().isInterrupted()) {//线程是否中断
System.out.println("Interrupted: " +
Thread.currentThread().isInterrupted());
} else {
System.out.println("Not interrupted: " +
Thread.currentThread().isInterrupted());
}

}
}

输出

1
2
Interrupted: false
Interrupted: true

类的惰性初始化

(这个谜团我暂时无法理解)
检查一个类是否被初始化一般有几种状态

  • 这个类尚未被初始化。
  • 这个类正在被当前线程初始化:这是对初始化的递归请求。
  • 这个类正在被其他线程而不是当前线程初始化。
  • 这个类已经被初始化。

当前线程,也就是那个后台线
程,会等待 Class 对象直到初始化完成。遗憾的是,那个正在进行初始化工作的
线程,也就是主线程,正在等待着后台线程运行结束。因为这 2 个线程现在正相
互等待着,该程序就死锁了(deadlock)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Lazy {
private static boolean initialized = false;
static {
Thread t = new Thread(new Runnable() {
public void run() {
initialized = true;
}
});
t.start();
try{
t.join();
}catch (InterruptedException e){
throw new AssertionError(e);
}
}
public static void main(String[] args){
System.out.println(initialized);
}
}

泛型的擦除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.example.demo1;

import java.util.Arrays;
import java.util.List;

public class Pair<T> {
private final T first;
private final T second;

public Pair(T first, T second) {
this.first = first;
this.second = second;
}

public T first() {
return first;
}

public T second() {
return second;
}

public List<String> stringList() {
return Arrays.asList(String.valueOf(first),
String.valueOf(second));
}

public static void main(String[] args) {
Pair p = new Pair<Object>(23, "skidoo");
System.out.println(p.first() + " " + p.second());
for (String s : p.stringList()) //这里出错了需要用Object s
System.out.print(s + " ");
}
}

序列化中的规则问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.example.demo1;

import java.io.*;
import java.util.HashSet;
import java.util.Set;

public class SerialKiller {
public static void main(String[] args) {
Sub sub = new Sub(666);
sub.checkInvariant();
Sub copy = (Sub) deepCopy(sub);
copy.checkInvariant();
}
// Copies its argument via serialization (See Puzzle 80)
static public Object deepCopy(Object obj) {
try {
ByteArrayOutputStream bos =
new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(obj);
ByteArrayInputStream bin =
new ByteArrayInputStream(bos.toByteArray());
return new ObjectInputStream(bin).readObject();
} catch(Exception e) {
throw new IllegalArgumentException(e);
}
}
}

class Super implements Serializable {
final Set<Super> set = new HashSet<Super>();
}
final class Sub extends Super {
private int id;
public Sub(int id) {
this.id = id;
set.add(this); // Establish invariant
}
public void checkInvariant() {
if (!set.contains(this))
throw new AssertionError("invariant violated");
}
public int hashCode() {
return id;
}
public boolean equals(Object o) {
return (o instanceof Sub) && (id == ((Sub)o).id);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
为了更具体一些,让我们看看程序中在反序列化 Sub 实例的时候发生了什么。首
先,序列化系统会反序列化 Sub 实例中 Super 的域。唯一的这样的域就是 set,
它包含了一个对 HashSet 的引用。在内部,每个 HashSet 实例包含一个对 HashMap
的引用,HashMap 的键是该散列集合的元素。HashSet 类有一个 readObject 方法,
它创建一个空的 HashMap,并且使用 HashMap 的 put 方法,针对集合中的每个元
素在 HashMap 中插入一个键-值对。put 方法会调用键的 hashCode 方法以确定它
所在的单元格(bucket)。在我们的程序中,散列映射表中唯一的键就是 Sub
的实例,而它的 set 域正在被反序列化。这个实例的子类域(subclass field),
即 id,尚未被初始化,所以它的值为 0,即所有 int 域的缺省初始值。不幸的是,
Sub 的 hashCode 方法将返回这个值,而不是最后保存在这个域中的值 666。因为
hashCode 返回了错误的值,相应的键-值对条目将会放入错误的单元格中。当 id
域被初始化为 666 时,一切都太迟了。当 Sub 实例在 HashMap 中的时候,改变这
个域的值就会破坏这个域,进而破坏 HashSet,破坏 Sub 实例。程序检测到了这
个情况,就报告出了相应的错误。

浮点数表示不了一些小数

1
2
3
4
5
public class Change{
public static void main(String args[]){
System.out.println(2.00 - 1.10);
}
}

1.10这个数不可以用浮点数进行表示,造成了误差

使用下面的方法修改

1
2
3
4
5
6
import java.math.BigDecimal;
public class Change1{
public static void main(String args[]){
System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10")));
}
}

不经意的溢出操作

1
final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;

虽然使用long声明计算结果,可是计算过程使用的是int,所以int(结果溢出)->long,这一步操作已经太迟了。
使用下面方法修正:

1
final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;

java中long转int原理

long转为int需要强转都很熟悉,那么介绍一下强转的具体原理。

首先在计算机中,计算加法减法是用补码计算的。

正数的补码为其二进制表示。负数的补码为其模的二进制表示取反加一。

加入为八位二进制加法,如:+3+(-5)

+3的补码为00000011

-5的模为+5,二进制表示为00000101,取反为11111010,加一后为11111011,所以-5的补码为11111011

加法计算公式为:两个数的补码相加,如果是正,则直接输出,如果是负,则除符号位以外取反加一

+3和-5的补码之和为11111110,为负数,去掉符号位第一位 1 ,剩下1111110,取反加一后为10

10为十进制中的2,所以带上符号位为-2。

众所周知,java中long为8字节64位,int为4字节32位。

java如果long强转为int,做的事为:直接取低位32位作为值,但是看做补码。

代码如下:

1
2
3
4

long long= 37444124124l;

System.out.println((int)long);

输出值为-1210581540

首先37444124124 转为二进制为

100010110111110101111111110111011100

后32位为:10110111110101111111110111011100

这个后32位直接当做计算结果输出:符号位为1,则是负数,负数取后31位取反加一

1001000001010000000001000100100

为:1210581540

加上符号位为-1210581540

int 转 long

1
System.out.println(Long.toHexString(0x100000000L + 0xcafebabe));

cafebabe

ffffffffcafebabe

100000000

不加L的十六进制高位默认f

三元表达式中悄悄的转型

1
2
3
4
5
6
7
8
public class DosEquis{
public static void main(String[] args){
char x = 'X';
int i = 0;
System.out.println(true ? x : 0);
System.out.println(false ? i : x);
}
}

i是int类型,char可以转换成int类型

System.out.println(false ? i(int类型) : x(byte、short、char);

会发生变量提升。所以尽量第二操作和第三操作有相同的数据类型

自动转型丢失精度的问题

1
2
3
4
        short x = 0;
int i = 123456;
// x+=i;等于下面的代码
x = (short) (x + i);

请注意括号的使用,优先级问题可能导致一些莫名其妙的问题

1
2
3
4
final String pig = "length: 10";
final String dog = "length: " + pig.length();
System.out. println("Animals are equal: " + pig == dog);
System.out.println(("Animals are equal: " + pig) == dog);

byte转String最好提供一个字符集

1
String str = new String(bytes, "ISO-8859-1");

使用正则表达式的小问题

1
.replaceAll("\\.","/") + ".class");
要想只匹配句点符号,在正则表达式中的句点必须在其前面添加一个反斜杠(\)
进行转义。因为反斜杠字符在字面含义的字符串中具有特殊的含义——它标识转
义字符序列的开始——因此反斜杠自身必须用另一个反斜杠来转义,这样就可以
产生一个转义字符序列,它可以在字面含义的字符串中生成一个反斜杠。

类名的匹配

1
2
System.out.println(Test.class.getName().
replaceAll("\\.",Matcher.quoteReplacement(File.separator)) + ".class");

漂亮的代码

1
2
使用这个代码替代switch case
System.out.println("PGM".charAt(rnd.nextInt(3)) + "ain");

类型不同的数字的比较问题

1
2
3
4
5
6
7
8
public class BigDelight {
public static void main(String[] args) {
for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
if (b == 0x90)
System.out.print("Joy!");
}
}
}

说明(类型提升的锅):

拿一个 byte 与一个 int 进行的比较是一个混合类型比较(mixed-type
comparison)。如果你把 byte 数值想象为苹果,把 int 数值想象成为桔子,那
么该程序就是在拿苹果与桔子比较。请考虑表达式((byte)0x90 == 0x90),尽管
外表看起来是成立的,但是它却等于 false。
为了比较 byte 数值(byte)0x90 和 int 数值 0x90,Java 通过拓宽原始类型转换
将 byte 提升为一个 int[JLS 5.1.2],然后比较这两个 int 数值。因为 byte 是
一个有符号类型,所以这个转换执行的是符号扩展,将负的 byte 数值提升为了
在数字上相等的 int 数值。在本例中,该转换将(byte)0x90 提升为 int 数值-112,
它不等于 int 数值 0x90,即+144。

++的小坑

1
2
3
4
5
j = j++;
等于
int tmp = j;
j = j + 1;
j = tmp?;

循环中最大值的回归

1
2
3
4
5
6
7
8
9
10
public class InTheLoop {
public static final int END = Integer.MAX_VALUE;
public static final int START = END - 100;
public static void main(String[] args) {
int count = 0;
for (int i = START; i <= END; i++)
count++;
System.out.println(count);
}
}

不会输出count,回归到min的值,所以使用

1
2
3
4
int i = START;
do {
count++;
}while (i++ != END)

这些不等于来进行判断比较好

循环中一个很皮的东西(无限循环)

1
2
3
String i = "helll java"
while (i != i + 0) {
}

优柔寡断

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Indecisive {

public static void main(String[] args) {
System.out.println(decision());
}
static boolean decision() {
try {
return true;
} finally {
return false;
}
}
}

最后return false

静态代码块的妙用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UnwelcomeGuest {

public static final long GUEST_USER_ID = -1;
private static final long USER_ID = getUserIdOrGuest;

private static long getUserIdOrGuest {
try {
return getUserIdFromEnvironment();
} catch (IdUnavailableException e) {
System.out.println("Logging in as guest");
return GUEST_USER_ID;
}
}
...// The rest of the program is unchanged
}

使用静态代码块解决final的一次性赋值问题

实例化中堆栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Reluctant {

private Reluctant internalInstance = new Reluctant();

public Reluctant() throws Exception {
throw new Exception("I'm not coming out");
}
public static void main(String[] args) {
try {
Reluctant b = new Reluctant();
System.out.println("Surprise!");
} catch (Exception ex) {
System.out.println("I told you so");
}
}
}

静态方法的调用问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Dog {
public static void bark() {
System.out.print("woof ");
}
}
class Basenji extends Dog {
public static void bark() { }
}
public class Bark {
public static void main(String args[]) {
Dog woofer = new Dog();
Dog nipper = new Basenji();
woofer.bark();
nipper.bark();
}
}
当一个程序调用了一个静态方法时,要被调用的方法都是
在编译时刻被选定的,而这种选定是基于修饰符的编译期类型而做出的,修饰符
的编译期类型就是我们给出的方法调用表达式中圆点左边部分的名字。在本案
中,两个方法调用的修饰符分别是变量 woofer 和 nipper,它们都被声明为 Dog
类型。因为它们具有相同的编译期类型,所以编译器使得它们调用的是相同的方
法:Dog.bark。这也就解释了为什么程序打印出 woof woof。尽管 nipper 的运
行期类型是 Basenji,但是编译器只会考虑其编译器类型。

初始化中的循环问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Elvis {

public static final Elvis INSTANCE = new Elvis();
private final int beltSize;
private static final int CURRENT_YEAR = Calendar.getInstance().get(Calendar.YEAR);

private Elvis() {
beltSize = CURRENT_YEAR - 1930;
}
public int beltSize() {
return beltSize;
}
public static void main(String[] args) {
System.out.println("Elvis wears a size " +
INSTANCE.beltSize() + " belt.");
}
}
  • Elvis 类的初始化是由虚拟机对其 main 方法的调用而触发的。首
    先,其静态域被设置为缺省值[JLS 4.12.5],其中 INSTANCE 域被设置为 null,
    CURRENT_YEAR 被设置为 0。
  • 接下来,静态域初始器按照其出现的顺序执行。第一
    个静态域是 INSTANCE,它的值是通过调用 Elvis()构造器而计算出来的。
    这个构造器会用一个涉及静态域 CURRENT_YEAR 的表达式来初始化 beltSize。通
    常,读取一个静态域是会引起一个类被初始化的事件之一,但是我们已经在初始
    化 Elvis 类了。递归的初始化尝试会直接被忽略掉[JLS 12.4.2, 第 3 步]。因此,
    CURRENT_YEAR 的值仍旧是其缺省值 0。这就是为什么 Elvis 的腰带尺寸变成了
    -1930 的原因。
  • 最后,从构造器返回以完成 Elvis 类的初始化,假设我们是在 2006 年运行该程
    序,那么我们就将静态域 CURRENT_YEAR 初始化成了 2006。遗憾的是,这个域现
    在所具有的正确值对于向 Elvis.INSTANCE.beltSize 的计算施加影响来说已经
    太晚了,beltSize 的值已经是-1930 了。这正是后续所有对
    Elvis.INSTANCE.beltSize()的调用将返回的值。
该程序表明,在 final 类型的静态域被初始化之前,存在着读取它的值的可能,
而此时该静态域包含的还只是其所属类型的缺省值。这是与直觉相违背的,因为
我们通常会将 final 类型的域看作是常量。final 类型的域只有在其初始化表达
式是常量表达式时才是常量[JLS 15.28]。

子类覆盖父类方法在初始化中的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Point {

protected final int x, y;

private final String name; // Cached at construction time

Point(int x, int y) {
this.x = x;
this.y = y;
name = makeName();
}
protected String makeName() {
return "[" + x + "," + y + "]";
}
public final String toString() {
return name;
}
}
public class ColorPoint extends Point {

private final String color;

ColorPoint(int x, int y, String color) {
super(x, y);
this.color = color;
}
protected String makeName() {
return super.makeName() + ":" + color;
}
public static void main(String[] args) {
System.out.println(new ColorPoint(4, 2, "purple"));
}
}

父类构造器->子类的makeName(此时color未完成初始化)
父类调用子类的makeName,是因为子类重新了它这个方法。

累计的玩笑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 class Cache {
static {
initializeIfNecessary();
}
private static int sum;

public static int getSum() {
initializeIfNecessary();
return sum;
}
private static boolean initialized = false;
private static synchronized void initializeIfNecessary() {
if (!initialized) {
for (int i = 0; i < 100; i++)
sum += i;
initialized = true;
}
}
}
public class Client {
public static void main(String[] args) {
System.out.println(Cache.getSum());
}
}

粗心的错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ColorPoint {

private Map<String,String> m = new HashMap<String,String>();
public void ColorPoint() {
m.put("Mickey", "Mouse");
m.put("Mickey", "Mantle");
}
public int size() {
return m.size();
}
public static void main(String args[ ]) {
ColorPoint moreNames = new ColorPoint();
System.out.println(moreNames.size());
}
}

这里的ColorPoint不是构造方法,不会运行。

关于变量覆盖的问题

1
2
3
4
5
6
7
8
9
10
11
class Base {
public String className = "Base";
}
class Derived extends Base {
private String className = "Derived";
}
public class PrivateMatter {
public static void main(String[ ] args) {
System.out.println(new Derived().className);
}
}

变量的隐藏,那么提供比他小的变量,至少一样多。这里继承过来的变量是私有的,所以编译是失败的

不同类中覆盖失败问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

package hack;
import click.CodeTalk;
public class TypeIt {
private static class ClickIt extends CodeTalk {
void printMessage() {
System.out.println("Hack");
}
}
public static void main(String[ ] args) {
ClickIt clickit = new ClickIt();
clickit.doIt();
}
}
package click;
public class CodeTalk {
public void doIt() {
printMessage();
}
void printMessage() {
System.out.println("Click");
}
}

程序中的这两个 twoMessage
方法是无关的,它们仅仅是具有相同的名字而已。当程序在 hack 包内调用
printMessage 方法时,运行的

final变量的覆写(和方法的不同之处)

1
2
3
4
5
6
7
8
9
class Jeopardy {
public static final String PRIZE = "$64,000";
}
public class DoubleJeopardy extends Jeopardy {
public static final String PRIZE = "2 cents";
public static void main(String[ ] args) {
System.out.println(DoubleJeopardy.PRIZE);
}
}

可以证明,final 修饰符对方法和域而言,意味着某些完全不同的事情。对于方
法,final 意味着该方法不能被覆写(对实例方法而言)或者隐藏(对静态方法
而言)[JLS 8.4.3.3]。对于域,final 意味着该域不能被赋值超过一次[JLS
8.3.1.2]。关键字相同,但是其行为却完全不相关。

名字重用的术语表

覆写 (override )


一个实例方法可以覆写( override )在其超类中可访问到的具有相同签名的所有实例方法 [JLS
8.4.8.1] ,从而使能了动态分派( dynamic dispatch );换句话说, VM 将基于实例的运行期类
型来选择要调用的覆写方法 [JLS 15.12.4.4] 。覆写是面向对象编程技术的基础,并且是唯一
没有被普遍劝阻的名字重用形式:

class Base {
public void f() { }
}
class Derived extends Base {
public void f() { } // overrides Base.f()
}

隐藏 (hide )


一个域、静态方法或成员类型可以分别隐藏( hide )在其超类中可访问到的具有相同名字(对
方法而言就是相同的方法签名)的所有域、静态方法或成员类型。隐藏一个成员将阻止其被
继承 [JLS 8.3, 8.4.8.2, 8.5] :

class Base {
public static void f() { }
}
class Derived extends Base {
private static void f() { } // hides Base.f()
}    

重载 (overload )

在某个类中的方法可以重载( overload )另一个方法,只要它们具有相同的名字和不同的签
名。由调用所指定的重载方法是在编译期选定的 [JLS 8.4.9, 15.12.2] :

class CircuitBreaker {
public void f(int i) { } // int overloading
public void f(String s) { } // String overloading
}    

遮蔽 (shadow )

一个变量、方法或类型可以分别遮蔽( shadow )在一个闭合的文本范围内的具有相同名字
的所有变量、方法或类型。如果一个实体被遮蔽了,那么你用它的简单名是无法引用到它的;
根据实体的不同,有时你根本就无法引用到它 [JLS 6.3.1] :

class WhoKnows {
    static String sentence = "I don't know.";
    public static woid main(String[ ] args) {
    String sentence = “I know!”; // shadows static field
    System.out.println(sentence); // prints local variable
    }
}

伪装的多线程程序

1
2
3
4
5
6
7
8
9
10
11
12
public class PingPong{
public static synchronized void main(String[] a){
Thread t = new Thread(){
public void run(){ pong(); }
};
t.run();
System.out.print( "Ping" );
}
static synchronized void pong(){
System.out.print( "Pong" );
}
}

捣乱锁的妖怪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.*;
public class Worker extends Thread {
private volatile boolean quittingTime = false;
public void run() {
while (!quittingTime)
pretendToWork();
System.out.println("Beer is good");
}
private void pretendToWork() {
try {
Thread.sleep(300); // Sleeping on the job?
} catch (InterruptedException ex) { }
}
// It's quitting time, wait for worker - Called by good boss
synchronized void quit() throws InterruptedException {
quittingTime = true;
join();
}
// Rescind quitting time - Called by evil boss
synchronized void keepWorking() {
quittingTime = false;
}
public static void main(String[] args)
throws InterruptedException {
final Worker worker = new Worker();
worker.start();
Timer t = new Timer(true); // Daemon thread
t.schedule(new TimerTask() {
public void run() { worker.keepWorking(); }
}, 500);
Thread.sleep(400);
worker.quit();
}
}

主要需要注意join是会放弃锁的

修改程序的方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private final Object lock = new Object();
// It's quitting time, wait for worker - Called by good boss
void quit() throws InterruptedException{
synchronized (lock){
quittingTime = true;
join();
}
}
// Rescind quitting time - Called by evil boss
void keepWorking(){
synchronized(lock){
quittingTime = false;
}
}

包外访问需要注意类需要是public和成员的限定符为public才可以

1
2
3
4
5
6
7
8
9
10
11
package com.example.demo;

public final class Api {
public static class PackagePrivate{
public void aa(){
System.out.println("aa");
}
}
public static PackagePrivate member = new PackagePrivate();

}
1
2
3
4
5
6
7
8
package com.example.demo1;
import com.example.demo.Api;

public class Client{
public static void main(String[] args) {
Api.member.aa(); //
}
}

如果PackagePrivate不是public 的类那边不可以进行包外访问了

方法重名&遮蔽引起的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

public class Pet{
public final String name;
public final String food;
public final String sound;
public Pet(String name, String food, String sound){
this.name = name;
this.food = food;
this.sound = sound;
}
public void eat(){
System.out.println(name + ": Mmmmm, " + food );
}
public void play(){
System.out.println(name + ": " + sound + " " + sound);
}
public void sleep(){
System.out.println(name + ": Zzzzzzz...");
}
public void live(){
new Thread(){
public void run(){
while(true){
eat();
play();
sleep();
}
}
}.start();
}
public static void main(String[] args){
new Pet("Fido", "beef", "Woof").live();
}
}

改java类会编译错误,因为Thread里面sleep方法找不到,所以需要避免重名。

单例模式的问题

1
2
3
4
5
6
7
public class Dog extends Exception {
public static final Dog INSTANCE = new Dog();
private Dog() {}
public String toString(){
return "Woof";
}
}

而 Exception 实现了 java.io.Serializable。这就意味着 Dog 是可序列化的


改进如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

import java.io.*;
public class CopyDog{ // Not to be confused with copycat
public static void main(String[] args){
Dog newDog = (Dog) deepCopy(Dog.INSTANCE);
System.out.println(newDog == Dog.INSTANCE);
System.out.println(newDog);
}
// This method is very slow and generally a bad idea!
static public Object deepCopy(Object obj){
try{
ByteArrayOutputStream bos =
new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(obj);
ByteArrayInputStream bin =
new ByteArrayInputStream(bos.toByteArray());
return new ObjectInputStream(bin).readObject();
} catch(Exception e) {
throw new IllegalArgumentException(e);
}
}
}

修正的方法:

private Object readResolve(){
// Accept no substitues!
return INSTANCE;
}
认同并鼓励,1元即可。