测试开发java面试题

==和equals比较
==:对比的是栈的值,由于不通类型数据存放位置不同,基本数据类型对比的是变量值,对比引用类型时,对比的其实是堆内存的地址
equals:Object中默认的同样是 ==比较,那为什么我们平时使用string类的成员方法equals,对比的是值呢,其实是很多类中重写了Object类中的equals。以String类为例,我们从源码可以看到重写的方法,本质也是对比字符串哪一个字符

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

什么是匿名内部类
匿名内部类可以使你的代码更加简洁,你可以在定义一个类的同时对其进行实例化。它与局部类很相似,不同的是它没有类名,如果某一个类你只需要使用一次,就可以使用匿名内部类

public class HelloWorldAnonymousClasses {

    /**
     * 包含两个方法的HelloWorld接口
     */
    interface HelloWorld {
        public void greet();
        public void greetSomeone(String someone);
    }

    public void sayHello() {

        // 1、局部类EnglishGreeting实现了HelloWorld接口
        class EnglishGreeting implements HelloWorld {
            String name = "world";
            public void greet() {
                greetSomeone("world");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hello " + name);
            }
        }

        HelloWorld englishGreeting = new EnglishGreeting();

        // 2、匿名类实现HelloWorld接口
        HelloWorld frenchGreeting = new HelloWorld() {
            String name = "tout le monde";
            public void greet() {
                greetSomeone("tout le monde");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Salut " + name);
            }
        };

        // 3、匿名类实现HelloWorld接口
        HelloWorld spanishGreeting = new HelloWorld() {
            String name = "mundo";
            public void greet() {
                greetSomeone("mundo");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hola, " + name);
            }
        };

        englishGreeting.greet();
        frenchGreeting.greetSomeone("Fred");
        spanishGreeting.greet();
    }

    public static void main(String... args) {
        HelloWorldAnonymousClasses myApp = new HelloWorldAnonymousClasses();
        myApp.sayHello();
    }
}

string +和append什么区别
使用“+”运算符和使用StringBuilder类的append方法的区别在于它们对字符串的处理方式不同
1、当你使用“+”运算符连接两个字符串时,本质是调用stringbuilder 方法append,每次都返回一个新的字符串对象。

public static void main(String[] args) {
        String str1 = "hello";
        String str2 = str1 + " coisini";
        System.out.println(str2);
    }
    javap -c 
    public class com.lujichao.learn.algorithm.Stringadd {
  public com.lujichao.learn.algorithm.Stringadd();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String hello
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: aload_1
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: ldc           #6                  // String  coisini
      16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;调用append 方法
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_2
      23: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      26: aload_2
      27: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      30: return
}

2、StringBuilder类的append方法则是将指定的字符串附加到原始字符串中,而不会返回新的字符串对象。
在效率方面,如果你只是连接两个小字符串,例如string s = “a” + “b”;那么使用“+”运算符可能会更。然而,当你需要在循环中连接多个字符串时使用+时每次都要new stringbuiled,较为耗时,使用StringBuilder类会更高效。

final关键词的作用?
最终的,可以修饰类,方法和变量
1、修饰类时,当前类不能被继承,大家可以看下我们常用的String、Integer类,这些不能被继承的类,均有final关键词修饰
2、修饰方法时,方法不能被覆盖,但是可以重载
3、修饰基本数据类型变量时,数值一旦被初始化后便不能修改;修饰引用类型变量时,在对其实例化之后便不能让其指向另一个对象,但是引用值是可以变化的

 public static void main(String[] args) {
        final int a=3;
        a=1;//编译器提示:Cannot assign a value to final variable 'a'
        final int[] ints={1,2,3,2};
        ints[0]=9;//合法
        ints=null;//编译器提示:Cannot assign a value to final variable 'a'
        System.out.println(a);
    }

String、StringBuffer、StringBuilder的区别及使用场景
String类:由于有final关键词修饰,是不可变的,在操作的时候都会产生新的对象,所以同样条件下会更加占用内存
StringBuffer类:由于成员方法均有synchronized修饰,所以其现线程安全,操作时也是在原有对象的基础上操作不会产生新的对象。下面源码我摘取了部分代码,我们可以看到方法均有synchronized修饰

 @Override
    public synchronized int length() {
        return count;
    }

    @Override
    public synchronized int capacity() {
        return value.length;
    }


    @Override
    public synchronized void ensureCapacity(int minimumCapacity) {
        super.ensureCapacity(minimumCapacity);
    }

    /**
     * @since      1.5
     */
    @Override
    public synchronized void trimToSize() {
        super.trimToSize();
    }

    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @see        #length()
     */
    @Override
    public synchronized void setLength(int newLength) {
        toStringCache = null;
        super.setLength(newLength);
    }

    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @see        #length()
     */
    @Override
    public synchronized char charAt(int

StringBuilder类:没有synchronized修饰,线程不安全,操作时也是在原有对象的基础上操作不会产生新的对象
性能对比:string<stringBuffer<stringBuilder
使用场景:在操作字符时我们优先考虑stringbuilder,但是操作多线程共享变量时,因为存在线程安全问题,所以我们应该使用stringbuffer
重载和重写的区别
重载:重载发生在同一个类中,方法名相同,方法的入参不同,可以是顺序不同,类型不同,数量不同。方法的返回类型可以不相同也可以不相同
重写:重写发生在父子类中,方法名和方法入参要相同,返回类型、抛出异常的范围要小于大于父类,修饰符的范围要大于等于父类的修饰符,父类private修饰的方法不能被重写
接口和抽象类的区别
接口设计的目的,是对类的行为的一种约束,要求实现类有什么行为,不要去具体行为如何实现。有些类似于菜单,规定所有餐厅由一道宫保鸡丁,至于怎么做各个餐厅都有不同的做法
1、接口中的方法是没有实现的
2、只能使用public、final、static三种类型的成员变量
3、方法只能使用public abstrct修饰
4、接口能够有多个实现
抽象类:是对类的一种抽奖,解决代码复用的问题。比如有宝马、奥迪、奔驰三个类,这三种品牌的车均拥有发动机,续航,等相同的属性,我们把这些属性抽象出来,为Car类,宝马、奥迪、奔驰三个类继承Car类。公共的方法属性由抽象类实现,特殊个性化的行为属性由子类实现。
1、抽象类可以存在成员方法
2、抽象类中成员变量可以使用各种修饰符修饰
3、抽象类只能继承一个
List和set的区别
我们翻看过源码的话,可以知道list和set均是接口继承于collection接口
List:写入的数据是有序且可以重复,读取数据时可以通过迭代器读取数据,也可以通过get()方法通过下表index取数
有ArrayList、LinkedList 子类实现
Set:写入的数据是无序不能重复,读取数据时只能通过迭代器读取
有hashSet、LinkedHashSet实现类
ArrayList和LinkedList的区别
ArrayList 基于动态数组实现的,LinkedList基于双向链表实现。二者线程均不安全
鉴于二者实现的方式,有这么几点区别
1、ArrayList在查询数据时可以通过index下标获取,效率相较于LinkedList更高
2、新增数据时LinkedList效率要高于ArrayList
3、在新增和删除数据时,新增和删除的数据的位置不同二者的时间复杂度也有所不同。ArrayList新增或删除的位置在尾部时,时间复杂度时O(1),如果在特定位置时由于需要移动其他的元素,时间复杂度时O(n);LinkedList新增或删除的位置在尾部时,时间复杂度时O(1),如果在特定位置新增或删除元素,由于需要遍历元素,时间复杂度时O(N)
4、占用内存空间方面,ArrayList需要多余的空间预留,而LinkedList需要额外的内存空间存储前驱和后继
HashMap和HashTable和HashSet的区别

HashMapHashTableHashSet
线程不安全安全不安全
存放数据key-valuekey-value非key-value
hash值通过key计算hash通过key计算hash通过value计算hash
添加数据方式put方法put方法add方法
实现接口MapMapSet

HashMap、HashTable底层均是有数组+链表+红黑树组成,以添加数据为例
在这里插入图片描述
HashSet底层是有HashMap实现的,add值是在key中存储,value是final修饰的Object对象。我们可以从下面的源码中看出


    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

   
    /**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element <tt>e</tt> to this set if
     * this set contains no element <tt>e2</tt> such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns <tt>false</tt>.
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified
     * element
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

concurrentHashMap原理,jdk1.7和jdk1.8的区别
jdk1.7:
jdk1.7中,concurrentHashMap是由segment数组+HashEntry数组组成。从下面这个图我们更加清楚的看到一个Segment中含有一个HashEntry数组,每一个HashEntry又是一个链表结构
查询元素时需要经过两次hash计算,第一次需要定位segment的位置,再一次hash计算确定hashEntry的位置。
segment继承ReenTrantLock是一个分段锁,添加数据时,会锁着操作的segment,其他的segment不受影响,并发数为segment的个数,数组扩容也不影响其他的segment
在这里插入图片描述
jdk1.8
jdk1.8中,concurrentHashMap由synchronied+cas+node数组+红黑树组成
在这里插入图片描述

相较于HashTable成员方法添加synchronized的保证线程安全,concurrentHashMap是通过原子性和局部添加Synchronized保证线程安全,将能耗降到最低
截取了部分源码我们可以从源码中看到node类中val和next均由volatile修饰,通过volatile的修饰保证多线程中的可见性(这里不理解的可以看前面的文章;https://blog.csdn.net/qq_28039149/article/details/137150623)。
在查找、替换、赋值均使用CAS,当插入数据出现hash冲突时的后续操作,会有Synchronized修饰,这样的锁机制,锁的力度更小,效率更好。

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }
        //添加数据的源码
final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

java的异常体系
在这里插入图片描述
如上图,异常体系中最顶层是Throwable,两个子类,error和exception。
error是程序无法处理的异常,一旦出现程序会被迫停止运行,如我们常见的oom
exception不会导致程序停止。runtimexception发生在运行时如空指针,checkedexception出现在编译时候
gc如何判断对象是否可以被回收
垃圾回收机制有两种算法:1、引用计数法2、可达性分析法
1、引用计数法:每个对象都有一个属性标记引用的数量,新增一个引用加1,减少一个引用数值减1,当应用数量为0时,gc就会进行回收,但存在一个问题:如果A对象引用B对象,B对象引用A对象时,引用值一直为1,gc永远不会回收这个对象
2、可达性分析法:鉴于引用计数法可能存在问题,java使用可达性分析法作为垃圾回收的算法,从gc root开始遍历,只要能够搜索到的对象均不会回收,反之认为对象需要被回收。
那什么时gc root 呢?
(1)、虚拟机栈中引用的对象
(2)、方法区中静态属性引用的对象
(3)、方法区中常量引用的对象
(4)、使用native修饰的对象
线程的生命周期和线程的几种状态
在这里插入图片描述
我们通过上面的流程图我们看到线程的几种状态:new、runnable、waiting、timed_wait、blocked、terminated(结束状态)
sleep()、wait()、join()、yield()的区别
1、从上面的流程图我们不难看出不同点1是wait()等待的线程必须通过notify()或者notifyAll()才能唤醒,而sleep()计时结束后自动唤醒
2、方法的归属不一致,sleep()是Thred类的静态方法,而wait()是object类的成员方法,每个对象都有这个方法
3、wait()方法的调用必须先获取wait对象的锁,而sleep则没有这种限制
wait方法在执行完后会释放对象锁,其他线程可以正常获取该对象锁(我用完了,你们用吧)
sleep在synchronized修饰的代码块中执行时,不回释放对象锁,其他的线程不能获取对象(我睡500ms的时候抱着锁睡觉谁也不别想用)
yield():执行这个方法后,线程直接进入就绪状态,马上释放cpu的执行权,但是会保留cpu的可执行权限。所以cpi下次进行调度时,还会让这个线程继续执行
join():阻塞当前线程,执行完join的线程后,继续执行当前线程逻辑。如下面代码,主线程A,join线程t1后,不管t1线程休眠多久都会先执行t1的逻辑打印2222 再打印111111。如果我们把t1.join()方法注掉后,打印顺序就变为1111111 2222

 public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(2222);
            }
        });
        t1.start();
        t1.join();
        System.out.println(111111);
    }
    输出结果
2222
111111

什么是死锁
所谓死锁,就是多线程运行过程中因抢夺资源造成的一种僵局,没有外力无法解决的情况
在这里插入图片描述
如上所示,死锁产生的四个条件分别是:
1、互斥条件,同一个同一时间只能有一个线程使用
2、请求并保持,线程申请锁收到阻塞时,不会自动释放目前占用的锁
3、不可剥夺,线程持有的锁只能自己使用结束主动释放,不能被别的线程剥夺
4、环路等待,线程A等待线程B持用的锁,线程B等待线程A持用的锁,形成一个闭合环路
那如何解决死锁问题呢?
我们上面说了死锁产生的四个条件,那解决这个问题可以从三个方面入手
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值