0
点赞
收藏
分享

微信扫一扫

java易错题锦集二


源码 补码

int i = 5;
int j = 10;
System.out.println(i + ~j);

  • 有个公式,-n=~n+1
  • 另一种解题思路 ~代表对n按位取反 10的源码是: 00000000 00000000 00000000 1010 所以对10按位取反就是 11111111 11111111 11111111 0101 由于计算机中-1表示为 11111111 11111111 11111111 1111 显然,这个码等于-1减1010(即10), 所以~10的值是-11 其实,都不用这么麻烦,就看编码中0的位置,然后把0对应的10进制数加起来,用-1去减就行了。

修饰符

public class Parent {
    private void m1(){}
    void m2(){}
    protected void m3(){}

    public static void m4(){}

}

Parent子类中一定能够继承和覆盖Parent类的m3方法
void m2(){}缺省访问修饰符只在本包中可见,在外包中不可见

访问权限

  • 抽象类默认访问权限 答:default
  • 接口方法默认为public

链接:
抽象类在jdk1.8之前,抽象类的方法默认访问权限为protected,jdk1.8时变成了是default
接口中的方法,在jdk1.8之前是public ,在jdk1.8的时候,也可以是default,到了jdk1.9时也可以是private

字符串替换

String classFile = "com.jd.". replaceAll(".", "/") + "MyClass.class";
        System.out.println(classFile);

答案: Ly8vLy8vL015Q2xhc3MuY2xhc3M= (可以用base64在线解密看结果)
原因:replaceAll 不是replace, replaceAll他的第一个参数是正则表达式

字符串

下面这一段代码输出什么?为什么

public class SystemUtil{
    public static boolean isAdmin(String userId){
        return userId.toLowerCase()=="admin";
    }
    public static void main(String[] args){
        System.out.println(isAdmin("Admin"));
    }
}

答案: false, 原因是toLowerCase方法中是new String();

java易错题锦集二_开发语言

字符串 容易错

下面代码输出什么?
String s1 = "coder";     
String s2 = "coder";     
String s3 = "coder" + s2;     
String s4 = "coder" + "coder";     
String s5 = s1 + s2;            
System.out.println(s3 == s4); 
System.out.println(s3 == s5);    
System.out.println(s4 == "codercoder");

答案:CmZhbHNl77ybZmFsc2XvvJsgdHJ1Ze+8mw== (可以用base64在线解密看结果)
解析:s1,s2都是保存在字符串常量池中的对象;s3,s5是新创建的对象,在堆中;s4 指向的也是字符串常量池中的对象;
所以有s3!=s4,s3!=s5,s4==“codercoder”, s4!=s5。
定义字符串的时候看定义时等号右边拼接字符串时有没有使用到变量, 如果有, 则重新创建一个新的对象

链接:
1、栈区:由编译器自动分配释放,具体方法执行结束后,系统自动释放JVM内存资源。
其作用有保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用。也可以用来保存加载方法时的帧。
2、堆区:一般由程序员分配释放,JVM不定时查看这个对象,如果没有引用指向这个对象就回收。
其作用为用来存放动态产生的数据,包括new出来的实例,字符数组等。
同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法。
3、数据区:用来存放static定义的静态成员。
4、常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中。

线程

下面哪个操作会释放锁
A。sleep()
B。wait()
C。join()
D。yield()

答案: QkM= (可以用base64在线解密看结果)
解析:
1.sleep会使当前线程睡眠指定时间,不释放锁
2.yield会使当前线程重回到可执行状态,等待cpu的调度,不释放锁
3.wait会使当前线程回到线程池中等待,释放锁,当被其他线程使用notify,notifyAll唤醒时进入可执行状态
4.当前线程调用 某线程.join()时会使当前线程等待某线程执行完毕再结束,底层调用了wait,释放锁

ThreadLocal

ThreadLocal类用于创建一个线程本地变量
在Thread中有一个成员变量ThreadLocals,该变量的类型是ThreadLocalMap,也就是一个Map,它的键是threadLocal,值就是变量的副本,ThreadLocal为每一个使用该变量的线程都提供了一个变量值的副本,每一个线程都可以独立地改变自己的副本,是线程隔离的。通过ThreadLocal的get()方法可以获取该线程变量的本地副本,在get方法之前要先set,否则就要重写initialValue()方法。
ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。

public class Person{
    private String name = "Person";
    int age=0;
}
public class Child extends Person{
    public String grade;
    public static void main(String[] args){
        Person p = new Child();
        System.out.println(p.name);
    }
}

会输出什么:
A. 输出:Person
B. 没有输出
C. 编译出错
D. 运行出错

解析:
这里是子类访问父类私有属性报错
Java中对字段属性是静态绑定,方法成员是动态绑定,这里错在:在子类中试图访问父类的private字段,所以编译不通过,将private去掉就可访问,不是动态绑定的问题,它本来就属于静态绑定。

静态绑定

  • 静态绑定也叫前期绑定、编译期绑定。
  • 在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法),此时由 编译器或其它连接程序实现。
  • java当中的方法只有final、static、private修饰的的方法和构造方法是静态绑定的。

switch

public static int switchit(int x) {
        int j=1;
        switch (x) {
        case 1:j++;
        case 2:j++;
        case 3:j++;
        case 4:j++;
        case 5:j++;
        default:j++;
        }
        return j+x;
    }

switch除了switch穿透还有在进到case之后,default还是会执行

动态绑定

动态绑定体现了Java的继承与多态,在继承链中,JVM一直沿着继承链动态找到带有该方法的实现.

java易错题锦集二_开发语言_02

并发

下面程序会输出什么:

public static void main(String args[]) {
    Thread t = new Thread() {
        public void run() {
            pong();
        }
    };
    t.run();
    System.out.print("ping");
}
static void pong() {
    System.out.print("pong");
}

答案:cG9uZ3Bpbmc= (可以用base64在线解密看结果)
解析:

/**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

用start方法才能真正启动线程,此时线程会处于就绪状态,一旦得到时间片,则会调用线程的run方法进入运行状态。
而run方法只是普通方法,如果直接调用run方法,程序只会按照顺序执行主线程这一个线程。

注意:

线程地址空间

线程的通信速度更快,切换更快,因为他们共享同一进程的地址空间。
线程之间共享进程获得的数据资源,所以开销小,但不利于资源的管理和保护;而进程执行开销大,但是能够很好的进行资源管理和保护。

进程与线程

子进程得到的是除了代码段是与父进程共享以外,其他所有的都是得到父进程的一个副本,子进程的所有资源都继承父进程,得到父进程资源的副本,子进程可获得父进程的所有堆和栈的数据,但二者并不共享地址空间。两个是单独的进程,继承了以后二者就没有什么关联了,子进程单独运行;进程的线程之间共享由进程获得的资源,但线程拥有属于自己的一小部分资源,就是栈空间,保存其运行状态和局部自动变量的。

sync

public class Test {
    private synchronized void a() {
    }
    private void b() {
        synchronized (this) {
        }
    }
    private synchronized static void c() {
    }
    private void d() {
        synchronized (Test.class) {
        }
    }
}

A. 同一个对象,分别调用方法a和b,锁住的是同一个对象
B. 同一个对象,分别调用方法a和c,锁住的是同一个对象
C. 同一个对象,分别调用方法b和c,锁住的不是同一个对象
D. 同一个对象,分别调用方法a、b、c,锁住的不是同一个对象

答案:QUM= (可以用base64在线解密看结果)
解析:
修饰非静态方法 锁的是this 对象
修饰静态方法 锁的是class对象

链接:

方法a为同步方法,方法b里面的是同步块,同步方法使用的锁是固有对象this,同步块使用的锁可以是任意对象,但是方法b里面的同步块使用的锁是对象this,所以方法a和方法b锁住的是同一个对象。方法 c为静态同步方法,使用的锁是该类的字节码文件,也就是Test.class。方法d里面的也是同步块,只不过使用的锁是Test.class,所以方法c和方法d锁住的是同一个对象。

super

super关键字
目的:
就是显示的告诉子类,当前的属性或方法来源于父类。
作用:
表示当前类父类对象的引用。
用法:
可以调用父类的属性或方法
super.属性名称或者super.方法名称(参数列表);
super可以出现在子类构造方法的第一句代码,就表示子类中的构造方法
明确指明了要调用的父类的构造方法来创建父类的对象

备忘

int b =aa++; // 先赋值,后自增

多态作用

多态的作用

  • 不必编写每一子类的功能调用,可以直接把不同子类当父类看,屏蔽子类间的差异,提高代码的通用率/复用率
  • 父类引用可以调用不同子类的功能,提高了代码的扩充性和可维护性

volatile和synchronized

  • volatile:用来确保将变量的跟新操作通知到其他线程,当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。然而,在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比 synchronized关键字更轻量级的同步机制。
  • synchronized 关键字 : 用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这个段代码。

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。

出于运行速率的考虑,java编译器会把经常经常访问的变量放到缓存(严格讲应该是工作内存)中,读取变量则从缓存中读。但是在多线程编程中,内存中的值和缓存中的值可能会出现不一致。volatile用于限定变量只能从内存中读取,保证对所有线程而言,值都是一致的。但是volatile不能保证原子性,也就不能保证线程安全。

文章详见这里

异常

java易错题锦集二_开发语言_03

java易错题锦集二_System_04

代码执行顺序

  1. 父类静态代码块,父类静态成员变量(同级,按代码顺序执行)
  2. 子类静态代码块,子类静态成员变量(同级,按代码顺序执行)
  3. 父类匿名代码块,父类普通成员变量(同级,按代码顺序执行)
  4. 父类构造方法
  5. 子类匿名代码块,子类普通成员变量(同级,按代码顺序执行)
  6. 子类构造方法

class Father {
    public Father() {
        System.out.println("父类无参构造方法");
    }
    static {
        System.out.println("父类静态代码块1");
    }


    private static int a = Help.fatherStaticMemberVarInit();
    static {
        System.out.println("父类静态代码块2");
    }
    {
        System.out.println("父类普通代码块1---");
    }
    private int b = Help.fatherMemberVarInit();
    {
        System.out.println("父类普通代码块2---");
    }
    public Father(int v) {
        System.out.println("父类带参构造方法");
    }
}

class Son extends Father {
    static {
        System.out.println("子类静态代码块1");
    }
    private static int a = Help.sonStaticMemberVarInit();
    static {
        System.out.println("子类静态代码块2");
    }

    {
        System.out.println("子类普通代码块1");
    }
    private int b = Help.sonMemberVarInit();
    {
        System.out.println("子类普通代码块2");
    }
    public Son() {
        // super(2018);
        System.out.println("子类构造方法");
    }
}

class Help {
    public static int fatherStaticMemberVarInit() {
        System.out.println("父类静态成员变量");
        return 0;
    }
    public static int fatherMemberVarInit() {
        System.out.println("父类普通成员变量");
        return 0;
    }
    public static int sonStaticMemberVarInit() {
        System.out.println("子类静态成员变量");
        return 0;
    }
    public static int sonMemberVarInit() {
        System.out.println("子类普通成员变量");
        return 0;
    }
}

public class Test {
    public static void main(String[] args) {
        Son son1 = new Son();
        System.out.println("===================");
        Son son2 = new Son();
    }
}

注意:

  • 静态内容只在类加载时执行一次,之后不再执行。
  • 默认调用父类的无参构造方法,可以在子类构造方法中利用super指定调用父类的哪个构造方法。

jvm

off-heap叫做堆外内存,将你的对象从堆中脱离出来序列化,然后存储在一大块内存中,这就像它存储到磁盘上一样,但它仍然在RAM中。对象在这种状态下不能直接使用,它们必须首先反序列化,也不受垃圾收集。序列化和反序列化将会影响部分性能(所以可以考虑使用FST-serialization)使用堆外内存能够降低GC导致的暂停。堆外内存不受垃圾收集器管理,也不属于老年代,新生代。

堆外内存意味着把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机)。不属于老年代和新生代

2.新生代 老年代

1)程序计数器

几乎不占有内存。用于取下一条执行的指令。

2)堆

所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成,结构图如下所示:

java易错题锦集二_java_05

新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象。

3)栈
每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。
4)本地方法栈
用于支持native方法的执行,存储了每个native方法调用的状态
5)方法区
存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用永久代(PermanetGeneration)来存放方法区,(在JDK的HotSpot虚拟机中,可以认为方法区就是永久代,但是在其他类型的虚拟机中,没有永久代的概念,有关信息可以看周志明的书)可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。


举报

相关推荐

0 条评论