Java编程思想 - 思考笔记

红色: 问题     紫色: 质疑     绿色: 尚未解决的问题

第六章-访问权限控制

           1. 类库、群组、类文件的关系是什么?

              答: 一个Java可运行程序由若干个类库打包而成。一个类库由若干个群组(package)组合而成。一个群组由若干个类文件组成。一个类文件由一个public类的class文件和任意数量的非public类的class文件组成。

           2. "每一个文件都有一个构件",句中"构件"一词是什么意思?

              答: 拥有自己独立的.java文件与.class文件的类。显然,只有public类才能被称之为"构件",非public类由于与public类共享.java文件,因此不足以被称之为"构件"。

           3. 一组类是如何被打包到一个类库中的?

              答: 首先,Java解释器根据类输出的class文件的路径将类"打包"到一个个类文件中,接着将同一个package下的类文件打包到同一个类库中。

           4. 为什么外部类不能被申明成protected?

              答: 表层原因是外部类的上一层是包(package),只会涉及到以下两种访问权限: 包访问权限(default)和所有包访问权限(public),因此只能使用default或public来修饰外部类。深层原因可以从“其它包“和“同一个包”这两个角度来解释:

                    1. proteced相较于default多了一层"继承"访问权限,方法被定义成protected,如果能构成继承关系,那么在其它包中就能被访问。我们参照这个思想,如果将一个类申明成protected,现在想在其它包中使用这个类,那么参照proctected方法的做法,首先必须继承这个类,但继承的前提是其他包中的类对被protected修饰的类可视,产生逻辑上的死循环,所以类绝对不能被protected修饰。

                   2. 网上许多在谈到第二个角度时,一般都回答的模棱两可,企图借助分析"为什么外部类不能被申明成private"来蒙混过关。实际上,由于protected访问权限位于default之上,提供包访问权限,因此在同一个包下的其它类完全可以对被protected修饰的类可视,也即角度1中无法继承的问题在角度2内不复存在。但在这种场景下,protected能做的事default都能完成,没有必要使用protected,为了不进行重复定义,java不允许类被protected修饰。

扫描二维码关注公众号,回复: 8948603 查看本文章

第七章-复用类

           1. 正常的方法调用与内嵌调用分别是怎样的?(对应书本P143)

               答: 正常的方法调用时,首先编译器会搜索与该方法名相同的所有方法,接着根据传入的参数与方法的参数列表相匹配,找到应当调用的方法,再将整个方法的代码插入到方法调用处,最终由jvm读取字节码文件并将要执行的代码交给解释器,翻译成机器码交由CPU去执行(这里可能有误,我自己反编译从来没看到有方法的代码被插入到方法调用处的情况)。内嵌调用时,编译器不会像正常的方法那样插入方法代码,而是什么都不做,但jvm会创建一个外部方法的副本,直接读取(通过解释器翻译并送到CPU执行)待执行的方法(将参数压入栈,程序计数器跳转至方法代码的行号处并执行,然后跳回方法调用处,并处理返回值。我怀疑这一步会用到类似JIT即时编译机制),得到返回值并填到副本的方法调用处,最终解释器直接执行副本代码即可。

/*
*  外部方法run()
*/
public void run(){
   System.out.print("1");
   System.out.print(waitRun());
   System.out.print("2");
}


/*
*  等待调用的方法waitRun()
*/
public final int waitRun(int i){
    return i + 10;
}

            正常的方法调用

public void run(){
   System.out.print("1");
   System.out.print(1 + 10); //插入程序代码
   System.out.print("2");
}


public final int waitRun(int i){
    return i + 10;
}

           内嵌调用

public void run(){
   System.out.print("1");
   System.out.print(11); //先执行一遍waitRun(),得到返回值填入run()的方法副本后,
                         //再执行run()的副本
   System.out.print("2");
}


public final int waitRun(int i){
    return i + 10;
}

          2. 尝试在子类中"覆盖"一个和基类同方法签名的private方法时,编译器为什么不会报错?

              答: 被private修饰的方法存在的唯一价值就是服务于它所属的那个类,对于其它任何的类而言都毫无价值,因此这个方法绝对不可能被继承,既然不涉及到继承,就更不用谈重写/覆盖的问题了。编译器显然不会报错,因为我们在子类中定义的同名private方法会被编译器看做是子类自己独有的方法而已。

第八章-多态

          1. 众所周知,实现了iterator接口的类都能使用for-each循环来迭代数据,为什么数组没有实现iterator接口,但能使用for-each循环呢?
     
    2. 什么是RTTI?

              答: RTTI是"运行时类型识别"的英文缩写,指的是JVM在运行期间会对对象的具体类型进行检查。

public class Cycle {
    public static void main(String[] args) {
        Cycle[] cycles = new Cycle[]{
                new Unicycle(),
                new Bicycle(),
                new Tricycle()
        };
        ((Unicycle)cycles[3]).balance();  //........语句1   throws ClassCastException
    }
}
class Unicycle extends Cycle {
    public void balance(){
        System.out.println("Unicycle balance()");
    }
}
class Tricycle extends Cycle {

}

                     Compiler期间语句1不会引发任何的报错,但在运行时,jvm首先会查明cycle[3]的具体类型是Tricycle.class,由于Tricycle.class和Unicycle.class不是同一个类型并且类相互之间没有继承关系,因此jvm会抛出运行时异常ClassCastException。

第九章-接口

          1. 为什么抽象类允许定义构造器?P170

              答: 抽象类不允许创建对象,但却能够定义构造器,这岂不是矛盾了吗?当然不是。构造器的作用不在于创建对象,而在于初始化对象的属性,比如抽象类总想让导出类继承某几个字段,这些字段的值是固定的,由抽象类的构造器来指定。

          2. 为什么接口中的方法不能被定义成protected?

              答: 被protected修饰的方法只能通过继承的方式去实现,但接口只能被implements(实现),不能被extends(继承),最终导致接口没办法被使用。

          3. 为什么接口中的方法不能被定义成default?

              疑问:default修饰的方法只能在同包下访问,如果我原本就打算只在本包中使用和实现这个接口,那把接口中的方法定义成default也无可厚非吧?凭什么一定要定义成抽象类才行呢?网上有人说是sun公司的规定、接口时要对外开放的云云,这些说法通通都是胡扯,没有任何信服力。

          4. 什么是菱形继承?

第十章-内部类

          1. 内部类是怎样拥有对外部类所有成员访问权的呢?P192

              答: 当某个外部类构造一个内部类时,内部类一定会秘密的捕获一个外部类类型的引用,后续在内部类中所有访问外部类成员的动作,都是由这个外部类引用发出的。

          2. 为什么外部类中可以使用this.xxx来访问私有属性,但在内部类中使用super.xxx反而无法访问父类的私有属性呢?

public class Outer {
    private String name;

    public Outer(String name){
        this.name = name;
    }
    class Inner{
        public String toString(){
            //return super.name;   //报错
            return name;
        }
}

           3. A类和B类不同包且拥有继承关系,A类中含有protected内部类,为什么B类无法初始化A类内部类类型的对象?P195

package com.t1;
public class A{
  protected class Inter(){

  }
  protected int i = 10;
}

package com.t2;
public class B extends A{
  public void test(){
    A a = new A();
    
    //方法1 使用父类的对象来创建内部类
    //A.inter obj = a.Inter new(); 

    //方法2 通过继承获得的Inner类型来创建Inner对象
    //Inter obj = new Inter();
  }
}

             答: 如上图所示,无论方法1和方法2皆报错。众所周知,被protected修饰的成员变量或者方法在基类的导出类中可以访问,看上去在B中创建Inter似乎没问题,难道是编译器出问题了吗?当然不是。创建Inter类型对象的先决条件是调用Inter的构造函数,由于Inter的构造器没有显示的指明,因此会由编译器自动合成一个与该类的访问权限相同的默认构造器(访问修饰符为protected),请注意,B类只是与A类缔结了继承关系,并没有和Inter类产生继承关系,再加上B类和Inter类不处于同一个包下,导致在B类中无法调用A内的构造器,因此无法正常的初始化Inter类型的对象。

                 遇到题干这种情况(不改变内部类Inter的protected访问权限),解决方案有两种,要么在Inter类的内部显示的指定public访问权限的构造器,要么在A类中定义public或protected方法,用于返回内部类Inter的对象。(我个人感觉第二种用的更多,并且内部类往往会被定义成private,仅供外部类使用,防止被其它类篡改。)

        4. 如果希望使类中某些对象的行为与其它对象不同,但不想新建类,此时该怎么做?

            答: 可以使用匿名内部类。

public class A {
    public static void main(String[] args) {
        B b_1 = new B();
        b_1.say(); //输出:  test()

        B b_2 = B.getInstance();
        b_2.say(); //输出:  modify()
    }

}

class B {
    public void say(){
        System.out.println("test()");
    }

    public static B getInstance(){
        return new B(){
            public void say(){
                System.out.println("modify()");
            }
        };
    }
}

第十一章-通过异常处理错误

          1. 异常处理理论中的终止模型和恢复模型分别是什么?P251

             答: 终止模型指的是方法中的错误无法挽回,一旦出现错误就只能停止整个方法并返回,(该方法)不能继续执行下去。恢复模型指的是异常有办法被解决,当异常解决后,jvm将继续执行程序。

/**
*  终止模型
*/
 public void terminateFunc(T t) {
        System.out.print("第一步");
        try{
            t.test();
        }catch(NullPointerException e1){
            System.out.println("对e1异常的处理");
        }
        System.out.print("第二步");
    }

/**
*  恢复模型
*/
public void recoverFunc(T t){
        System.out.print("第一步");
        boolean result = false;
        while(!result){ //不断地循环,直到计算出满意的数值为止
            try{
                result = t.test();
            }catch(NullPointerException e1){
                System.out.println("对e1异常的处理");
            }
        }
        System.out.print("第二步");
}

          2. 如何对System.out与System.err进行重定向?

          3. "栈轨迹"中,元素0是栈顶元素,并且是调用序列中的最后一个方法调用。数组中的最后一个元素和栈底是调用序列中的第一个方法调用,通过for-each循环e.getStackTrace( )会返回从最后一个方法名起的所有方法名。这一切看起来非常正常,但为什么普通数据入栈,遍历时的顺序与"栈轨迹"相反呢?

Stack<Integer> stacks = new Stack<>();
stacks.push(5);
stacks.push(10);
stacks.push(4);

for(Integer i : stacks){
	System.out.println(i); //返回 5, 10, 4
}

           答: Stack的底层就是数组,push()实际上就是在add()元素,而for-each在以下标0开始遍历数组,因此序列与数据的插入顺序一致。"栈轨迹"对应的e.getStackTrace( )返回的StackTraceElement[ ]在赋值时,被调整了顺序。 其实可以理解成: 使用fore-each遍历Stack时,Stack就好像被"降级"成数组了。

第二十一章-并发

          1. 21.3.5 章 ExplicitCriticalSection类中 通过显示的Lock对象创建临界区的代码写错了。ExplicitPairManager2类中的increment()使用的锁是ExplicitPairManager2类对象的Lock成员对象(私有),而另一个线程驱动的任务PairChecker调用了getPair()方法上有synchronized修饰,使用的锁是PairManager的派生类——ExplicitPairManager2类的对象锁,由于两个锁不一样,导致并发时,自增方法(ExplicitPairManager2 increment())与检查方法(PairChecker checkStatus())的执行顺序无法控制,极有可能执行完p.incrementX()后,就直接执行了检查方法,最终抛出自定义异常(PairValuesNotEqualException)。解决办法是在PairManager抽象类中增加Lock的定义,并在getPair()与ExplicitPairManager2的increment()方法中都是用显示的Lock对象创建临界区。值得注意的是,lock.lock();并非绝对能获得锁,因此使用lock.tryLock();在返回值为true时执行具体业务代码的方式是最稳妥的。

class ExplicitPairManager2 extends PairManager {
    @Override
    public void increment() {
        Pair temp;
        boolean captured = lock.tryLock();
        try {
            if(captured) {
                p.incrementX();
                p.incrementY();
            }
            temp = getPair();
        }finally {
            if(captured) {
                lock.unlock();
            }
        }
        if(temp != null) {
            store(temp);
        }
    }
}

abstract class PairManager {
    protected ReentrantLock lock = new ReentrantLock();

    AtomicInteger checkCounter = new AtomicInteger(0);
    protected Pair p = new Pair();
    private List<Pair> storage = Collections.synchronizedList(new ArrayList<>());
    public Pair getPair() {
        boolean capture = lock.tryLock();
        System.out.println("capture: " + capture);
        try {
            if(capture) {
                //Make a copy to keep the original safe:
                return new Pair(p.getX(), p.getY());
            }else{
                return null;
            }
        }finally {
            lock.unlock();
        }
    }
    //本方法模拟一系列操作动作
    protected void store(Pair p) {
        storage.add(p);
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public abstract void increment();
}

class PairChecker implements Runnable{
    private PairManager pm;
    public PairChecker(PairManager pm) {
        this.pm = pm;
    }
    @Override
    public void run() {
        while(true) {
            //每次成功执行checkStatus()后,都递增checkCounter
            //System.out.println("执行检测...");
            pm.checkCounter.incrementAndGet();
            Pair pair = pm.getPair();
            if(pair != null) {
                pair.checkStatus();
            }
        }
    }
}

          2. 21.1.5 wait()与notifyAll()一节中,WaxOMatic.java 上蜡和抛光的类中各自的阻塞方法写反了。

/**
 * 上蜡
 */
class WaxOn implements Runnable {
    private Car car;
    public WaxOn(Car c) {
        this.car = c;
    }
    @Override
    public void run() {
        try {
            System.out.println("Enter WaxOn run()");
            while(!Thread.interrupted()) {
                System.out.println("Wax On!   ");
                TimeUnit.MILLISECONDS.sleep(200);
                //涂蜡 唤醒抛光任务
                car.waxed();
                //进入阻塞状态,等待抛光任务完成,重新上蜡
                car.waitForWaxing();
            }
        }catch (InterruptedException e) {
            System.out.println("Exiting via interrupt");
        }
        System.out.println("Ending Wax On task");
    }
}

/**
 * 抛光
 */
class WaxOff implements Runnable{
    private Car car;
    public WaxOff(Car c) {
        this.car = c;
    }
    @Override
    public void run() {
        try {
            System.out.println("Enter WaxOff run()");
            while(!Thread.interrupted()) {
                //抛光任务完成
                car.buffed();
                System.out.println("Wax Off");
                TimeUnit.MILLISECONDS.sleep(200);
                //等待重新抛光
                car.waitForBuffed();
            }
        }catch (InterruptedException e) {
            System.out.println("Exiting via interrupt");
        }
        System.out.println("Ending Wax Off task");
    }
}
发布了45 篇原创文章 · 获赞 13 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/miaomiao19971215/article/details/97621387
今日推荐