java工程师面试题之java基础

1.switch支持的参数类型?

       java5之前支持char、short、int、byte,java5又支持了enum类型,java7及以后再支持String类型,long类型都不支持

 

int

short

byte

char

enum

String

Long

Java5以前

 

 

 

Java5-java7

 

 

Java7及以后

 

2. 八种基本数据及封装类          

 

基本数据类型

int(32)

short(16)

byte(8)

char(16)

 boolean(2)

float(32)

long(64)

double(64)

封装类

Integer

Short

Byte

Character

Boolean

Float

Long

Double

区别:

  1. 包装类型可以为 null,而基本类型不可以(数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱(将包装类型转为基本类型,比如说把 Integer 对象转换成 int 值),就会抛出 NullPointerException 的异常。)

     2.  包装类型可用于泛型,而基本类型不可以(因为泛型在编译时只保留原始类型,而原始类型只能是 Object 类及其子类——基本类型不是Object及子类,因此会报错。)

     3.   基本类型比包装类型更高效(基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用,需占较多的内存,而且对于多次用到的数值,使用包装类型就显得麻烦)

     4.   两个包装类型的值相同时,却并不相等(两个包装类型指向了不同的地址)

补充:自动拆箱与包装

具体见https://blog.csdn.net/qing_gee/article/details/101670051?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

把基本类型转换成包装类型的过程叫做装箱(boxing)。反之,把包装类型转换成基本类型的过程叫做拆箱(unboxing)

        //输出的是true
        int a = 100;
        Integer b = 100;
        System.out.println(a == b);*/

        //输出的true
        Integer c = 100;
        Integer d = 100;
        System.out.println(c == d);

        //输出的是false
        c = 200;
        d = 200;
        System.out.println(c == d);

第一题直接装箱,肯定是相等;

第二题和第三题的结果可能有点疑问,这里做个解释:java将int转为Integer是用的Integer.valueOf(),而根据源码可知,它对于-128到127的整数转成包装类型时不是去新建一个Integer对象,而是直接去IntegerCache中取出一个Integer对象,这样两个对象相等,所以返回是true,而对于超过这个范围的整数,就是常见的新建Integer对象了,由于对象地址不同,所以返回是true

再补充:new Integer()与Integer.valueOf()的区别

new Integer()是新建一个对象,而Integer.valueOf()会使用缓存池中的对象,多次调用会取得同一个对象的引用,valueOf的实现就是先判断值是否在缓冲池中(java8默认是-128到127),在的话就返回缓冲池里的内容。

 public static void main(String []args){
     Integer x = new Integer(64);
     Integer y = new Integer(64);
     System.out.println(x==y);//false
     x = Integer.valueOf(64);
     y = Integer.valueOf(64);
     System.out.println(x==y);//true
}

编译器会在自动装箱过程调用 valueOf() 方法,因此多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来创建,那么就会引用相同的对象。

 Integer x =127;
 Integer y = 127;
 System.out.println(x==y);

 

 3.equals与==的区别

  1. ==常用于判断基本数据类型是否相等,而equals常用于判断对象相等
  2. 当==与equals都用来判断对象时,如果对象引用地址相同,==返回true,否则会返回false,因此==是来判断对象的内存地址是否相同。而equals返回true或者false主要取决于重写实现,如果没有重写,则单单判断内存地址是否相同,与==一样,当判断String对象,Integer对象时,默认会进行重写,这时只要值相同就会返回true.

 补充:


 String a="abc";
 String b="abc";
 System.out.println(a==b);//返回true
 System.out.println(a.equals(b));//返回true

 String str2 = new String("abc");
 System.out.println(a == str2);//返回false
 System.out.println(a.equals(str2));//返回true

Java的虚拟机在内存中开辟出一块单独的区域,用来存储字符串对象,这块内存区域被称为字符串常量池。当使用 String a = "abc"这样的语句进行定义一个引用的时候,首先会在字符串常量池中查找是否已经相同的对象,如果存在,那么就直接将这个对象的引用返回给a,如果不存在,则需要新建一个值为"abc"的对象,再将新的引用返回a。String a = new String("abc");这样的语句明确告诉JVM想要产生一个新的String对象,并且值为"abc",于是就在堆内存中的某一个小角落开辟了一个新的String对象

4. Object有哪些公用方法?

  1. clone方法:实现对象的浅复制,只有当对象实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里把参数改变,这就需要在类中复写clone方法,如下:
    public class Hello implements Cloneable{
        int x =15;
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        public static void main(String []args) throws CloneNotSupportedException {
            Hello hello = new Hello();
            Hello hello2 = (Hello)hello.clone();
            hello2.x = 20;
            System.out.println(hello.x);//返回15
            System.out.println(hello2.x);//返回20
        }
    }

     

  2. getClass方法:final修饰,不能被重写,获得运行时类型。
    package com.example.demo;
    public class Hello{
        public static void main(String []args) throws CloneNotSupportedException {
            Hello hello = new Hello();
            System.out.println(hello.getClass());//返回com.example.demo.Hello
        }
    }

     

  3. toString方法:该方法用得比较多,一般子类要进行重写,否则返回“类型@HashCode“,如com.example.demo.Hello@23ab930d
  4. finalize方法:该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
  5. equals方法:常用于对象相等比较,子类一般都要重写这个方法,否则和==一样.
  6. hashCode方法:该方法常用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。一般必须满足obj1.equals(obj2)==true,可以推出obj1.hashCode()==obj2.hashCode(),但是hashCode相同不一定就满足equals(有可能equals被重写了)。不过为了提高效率,应该尽量使上面两个条件接近等价。如果不重写hashCode(),在HashSet中添加两个equals的对象,会将两个对象都加入进去。
  7. wait方法:不可被重写,就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。调用该方法后当前线程进入睡眠状态,直到以下事件发生:

            (1)其他线程调用了该对象的notify方法。

            (2)其他线程调用了该对象的notifyAll方法。

            (3)其他线程调用了interrupt中断该线程。

            (4)时间间隔到了。

         此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

      8.notify方法:不可被重写,该方法唤醒在该对象上等待的某个线程。

      9.notifyAll方法该方法唤醒在该对象上等待的所有线程。

 补充:

作者: MoonGeek

出处:https://www.cnblogs.com/moongeek/p/7631447.html

 

java多线程学习之wait、notify/notifyAll 详解

1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。

2、wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。

3、 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程让其获得锁

4、wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒。

5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。

6、notify 和 notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

7、在多线程中要测试某个条件的变化,使用if 还是while?

  要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑;显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while进行等待,直到满足条件才继续往下执行。如下代码:

 public class K {
     //状态锁
    private Object lock;
      //条件变量
    private int now,need;
    public void produce(int num){
         //同步
          synchronized (lock){
             //当前有的不满足需要,进行等待,直到满足条件
             while(now < need){
                 try {
                     //等待阻塞
                     lock.wait();
                 } catch (InterruptedException e) {
                    e.printStackTrace();
                 }
                 System.out.println("我被唤醒了!");
            }
            // 做其他的事情
         }
     }
 }
             

 

显然,只有当前值满足需要值的时候,线程才可以往下执行,所以,必须使用while 循环阻塞。注意,wait() 当被唤醒时候,只是让while循环继续往下走.如果此处用if的话,意味着if继续往下走,会跳出if语句块。

8、实现生产者和消费者问题

  什么是生产者-消费者问题呢?

  如上图,假设有一个公共的容量有限的池子,有两种人,一种是生产者,另一种是消费者。需要满足如下条件:

    1、生产者产生资源往池子里添加,前提是池子没有满,如果池子满了,则生产者暂停生产,直到自己的生成能放下池子。

    2、消费者消耗池子里的资源,前提是池子的资源不为空,否则消费者暂停消耗,进入等待直到池子里有资源数满足自己的需求。

 实现:

      1.先定义一个仓库接口,包含消费和生产方法

1 public interface AbstractStorage {
2     void consume(int num);
3     void produce(int num);
4 }

        2. 仓库接口实现类

 1 import java.util.LinkedList;
 2 
 3 /**
 4  *  生产者和消费者的问题
 5  *  wait、notify/notifyAll() 实现
 6  */
 7 public class Storage1 implements AbstractStorage {
 8     //仓库最大容量
 9     private final int MAX_SIZE = 100;
10     //仓库存储的载体
11     private LinkedList list = new LinkedList();
12 
13     //生产产品
14     public void produce(int num){
15         //同步
16         synchronized (list){
17             //仓库剩余的容量不足以存放即将要生产的数量,暂停生产
18             while(list.size()+num > MAX_SIZE){
19                 System.out.println("【要生产的产品数量】:" + num + "\t【库存量】:"
20                         + list.size() + "\t暂时不能执行生产任务!");
21 
22                 try {
23                     //条件不满足,生产阻塞
24                     list.wait();
25                 } catch (InterruptedException e) {
26                     e.printStackTrace();
27                 }
28             }
29 
30             for(int i=0;i<num;i++){
31                 list.add(new Object());
32             }
33 
34             System.out.println("【已经生产产品数】:" + num + "\t【现仓储量为】:" + list.size());
35 
36             list.notifyAll();
37         }
38     }
39 
40     //消费产品
41     public void consume(int num){
42         synchronized (list){
43 
44             //不满足消费条件
45             while(num > list.size()){
46                 System.out.println("【要消费的产品数量】:" + num + "\t【库存量】:"
47                         + list.size() + "\t暂时不能执行生产任务!");
48 
49                 try {
50                     list.wait();
51                 } catch (InterruptedException e) {
52                     e.printStackTrace();
53                 }
54             }
55 
56             //消费条件满足,开始消费
57             for(int i=0;i<num;i++){
58                 list.remove();
59             }
60 
61             System.out.println("【已经消费产品数】:" + num + "\t【现仓储量为】:" + list.size());
62 
63             list.notifyAll();
64         }
65     }
66 }

   3.实现生产者类

 

1 public class Producer extends Thread{
 2     //每次生产的数量
 3     private int num ;
 4 
 5     //所属的仓库
 6     public AbstractStorage abstractStorage;
 7 
 8     public Producer(AbstractStorage abstractStorage){
 9         this.abstractStorage = abstractStorage;
10     }
11 
12     public void setNum(int num){
13         this.num = num;
14     }
15 
16     // 线程run函数
17     @Override
18     public void run()
19     {
20         produce(num);
21     }
22 
23     // 调用仓库Storage的生产函数
24     public void produce(int num)
25     {
26         abstractStorage.produce(num);
27     }
28 }

 4.实现消费者类

 1 public class Consumer extends Thread{
 2     // 每次消费的产品数量
 3     private int num;
 4 
 5     // 所在放置的仓库
 6     private AbstractStorage abstractStorage1;
 7 
 8     // 构造函数,设置仓库
 9     public Consumer(AbstractStorage abstractStorage1)
10     {
11         this.abstractStorage1 = abstractStorage1;
12     }
13 
14     // 线程run函数
15     public void run()
16     {
17         consume(num);
18     }
19 
20     // 调用仓库Storage的生产函数
21     public void consume(int num)
22     {
23         abstractStorage1.consume(num);
24     }
25 
26     public void setNum(int num){
27         this.num = num;
28     }
29 }

 测试

 1 public class Test{
 2     public static void main(String[] args) {
 3         // 仓库对象
 4         AbstractStorage abstractStorage = new Storage1();
 5 
 6         // 生产者对象
 7         Producer p1 = new Producer(abstractStorage);
 8         Producer p2 = new Producer(abstractStorage);
 9         Producer p3 = new Producer(abstractStorage);
10         Producer p4 = new Producer(abstractStorage);
11         Producer p5 = new Producer(abstractStorage);
12         Producer p6 = new Producer(abstractStorage);
13         Producer p7 = new Producer(abstractStorage);
14 
15         // 消费者对象
16         Consumer c1 = new Consumer(abstractStorage);
17         Consumer c2 = new Consumer(abstractStorage);
18         Consumer c3 = new Consumer(abstractStorage);
19 
20         // 设置生产者产品生产数量
21         p1.setNum(10);
22         p2.setNum(10);
23         p3.setNum(10);
24         p4.setNum(10);
25         p5.setNum(10);
26         p6.setNum(10);
27         p7.setNum(80);
28 
29         // 设置消费者产品消费数量
30         c1.setNum(50);
31         c2.setNum(20);
32         c3.setNum(30);
33 
34         // 线程开始执行
35         c1.start();
36         c2.start();
37         c3.start();
38 
39         p1.start();
40         p2.start();
41         p3.start();
42         p4.start();
43         p5.start();
44         p6.start();
45         p7.start();
46     }
47 }

输出 

【要消费的产品数量】:50    【库存量】:0    暂时不能执行生产任务!
【要消费的产品数量】:20    【库存量】:0    暂时不能执行生产任务!
【要消费的产品数量】:30    【库存量】:0    暂时不能执行生产任务!
【已经生产产品数】:10    【现仓储量为】:10
【要消费的产品数量】:30    【库存量】:10    暂时不能执行生产任务!
【要消费的产品数量】:20    【库存量】:10    暂时不能执行生产任务!
【要消费的产品数量】:50    【库存量】:10    暂时不能执行生产任务!
【已经生产产品数】:10    【现仓储量为】:20
【已经生产产品数】:10    【现仓储量为】:30
【要消费的产品数量】:50    【库存量】:30    暂时不能执行生产任务!
【已经消费产品数】:20    【现仓储量为】:10
【要消费的产品数量】:30    【库存量】:10    暂时不能执行生产任务!
【已经生产产品数】:10    【现仓储量为】:20
【要消费的产品数量】:50    【库存量】:20    暂时不能执行生产任务!
【要消费的产品数量】:30    【库存量】:20    暂时不能执行生产任务!
【已经生产产品数】:10    【现仓储量为】:30
【已经消费产品数】:30    【现仓储量为】:0
【要消费的产品数量】:50    【库存量】:0    暂时不能执行生产任务!
【已经生产产品数】:10    【现仓储量为】:10
【要消费的产品数量】:50    【库存量】:10    暂时不能执行生产任务!
【已经生产产品数】:80    【现仓储量为】:90
【已经消费产品数】:50    【现仓储量为】:40

Hashcode的作用。

http://c610367182.iteye.com/blog/1930676

以Java.lang.Object来理解,JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次做Object的比较或者取这个对象的时候,它会根据对象的hashcode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。具体过程是这样:

  1. new Object(),JVM根据这个对象的Hashcode值,放入到对应的Hash表对应的Key上,如果不同的对象确产生了相同的hash值,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同hashcode的对象放到这个单链表上去,串在一起。

  2. 比较两个对象的时候,首先根据他们的hashcode去hash表中找他的对象,当两个对象的hashcode相同,那么就是说他们这两个对象放在Hash表中的同一个key上,那么他们一定在这个key上的链表上。那么此时就只能根据Object的equal方法来比较这个对象是否equal。当两个对象的hashcode不同的话,肯定他们不能equal.


String, StringBuffer and StringBuilder

1. 可变性

  • String 不可变
  • StringBuffer 和 StringBuilder 可变

2. 线程安全

  • String 不可变,因此是线程安全的
  • StringBuilder 不是线程安全的
  • StringBuffer 是线程安全的,内部使用 synchronized 进行同步

try catch finally,try里有return,finally还执行么?

会执行,在方法 返回调用者前执行。Java允许在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是纪录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,这会对程序造成很大的困扰,

String是如何保证不可变的?保证不可变有什么好处?

String 被声明为 final,因此它不可被继承。

在 Java 8 中,String 内部使用 char 数组存储数据。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final byte[] value;

    /** The identifier of the encoding used to encode the bytes in {@code value}. */
    private final byte coder;
}
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

String不可变的好处

1. 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key,由于其不可变,因此其hash 值也不可变,这样就只需要进行一次计算。

2. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

 

3. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。

4. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

String Pool

字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。

当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。

下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。

String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);           // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4);           // true

如果是采用 "bbb" 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。

String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6);  // true
 
在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

java中的参数传递

java中是值传递,传对象参数时本质上传的是对象的地址。因此在方法中使指针引用其它对象,那么这两个指针此时指向的是完全不同的对象,在一方改变其所指向对象的内容时对另一方没有影响。

public class Dog {
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private static void change(Dog dog){
        System.out.println(dog.toString());
        dog = new Dog();
        System.out.println(dog.toString());
        dog.setAge(20);
    }

    public static void main(String []args){
         Dog dog = new Dog();
         System.out.println(dog.toString());
         dog.setAge(10);
         change(dog);
         System.out.println(dog.getAge());
    }
}

如果是下面,就是20

    private static void change(Dog dog){
        System.out.println(dog.toString());
        dog.setAge(20);
    }

    public static void main(String []args){
         Dog dog = new Dog();
         System.out.println(dog.toString());
         dog.setAge(10);
         change(dog);
         System.out.println(dog.getAge());//20
    }

float与double:double字面量不能直接赋值float,因此java默认不能隐式向下转型

float f = 1.1   //报错
float f = 1.1f 

 

隐式类型转换

因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型。

short s1 = 1;
// s1 = s1 + 1;

但是使用 += 或者 ++ 运算符可以执行隐式类型转换。

s1 += 1;
// s1++;

上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:

s1 = (short) (s1 + 1);

 

接口和抽象类的比较

相同点:都不能实例化

区别:

  • 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
  • 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
  • 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

 

重写和重载的含义和区别

Overload:它可以表现类的多态性,存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载

Override:在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数,当子类在调用这一函数时自动调用子类的方法,而父类相当于被覆盖(重写)了。重写有以下三个限制:

  • 子类方法的访问权限必须大于等于父类方法;
  • 子类方法的返回类型必须是父类方法返回类型或为其子类型。
  • 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。

final关键字

 1.修饰数据:对于基本类型,final 使数值不变,即无法修改;

                      对于引用类型,final 使引用不变,也就是不能引用其它对象,但是被引用的对象本身是可以修改的。

2.修饰方法:无法被子类重写

3.修饰类:类不能被继承 

Static关键字 

1.静态变量:类所有的实例都共享静态变量,可以直接通过类名来访问它,其在内存中只存在一份。 

2.静态方法:类加载的时候就存在,它不依赖于任何实例。所以静态方法必须要实现,也就是说它不能是抽象方法,并且只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。

3. 静态语句块:类初始时运行一次

public class A {
    static {
        System.out.println("123");
    }

    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();
    }
}

//只有一个123打印

4.静态内部类:非静态内部类依赖于外部类的实例,而静态内部类不需要。静态内部类不能访问外部类的非静态的变量和方法。

public class OuterClass {

    class InnerClass {
    }

    static class StaticInnerClass {
    }

    public static void main(String[] args) {
        // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
        StaticInnerClass staticInnerClass = new StaticInnerClass();
    }
}

 5.静态导包:在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低

import static com.xxx.ClassName.* 

6.初始化执行顺序:静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。

public class Dog {
    public static String staticField = "静态变量";//1
    static {
        System.out.println("静态语句块");//2
    }
    public String field = "实例变量";//3
    public Dog() {
        System.out.println("构造函数");//5
    }
    {
        System.out.println("普通语句块");//4
    }

    public static void main(String []args){
        Dog dog = new Dog();
    }
}

反射机制

它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性,反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

java 反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 在运行时调用任意一个对象的方法

反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。

Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:

  • Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
  • Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
  • Constructor :可以用 Constructor 创建新的对象。

反射的优点:

  • 可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
  • 类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
  • 调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。

反射的缺点:

尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。

  • 性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。

  • 安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。

  • 内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

 泛型

      1. Java中的泛型是什么 ? 使用泛型的好处是什么?

         泛型,就是允许在定义类、接口、方法时使用类型形参,在声明变量、创建对象、调用方法时再传入实际的类型参数。像List代表了只能存放String类型的对象的List集合。

  如果没有泛型,那么我们很容易在运行时出现ClassCastException,因为本身编译期不报错,泛型防止了那种情况的发生。它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中。

      所以总结起来泛型的好处是:
           • 能够在编译时检查类型安全
           • 所有的强制转换都是自动和隐式的,取出代码后,不用再进行强制类型转换

 2. Java的泛型是如何工作的 ? 什么是类型擦除 ?

  泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List<String>在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。

  3. 什么是泛型中的限定通配符和非限定通配符 ?

  限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。

 4. List<? extends T>和List <? super T>之间有什么区别 ?

  这两个List的声明都是限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。

5. 如何编写一个泛型方法、泛型类?

  编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。

public class GenericClass<F>{

    private F mContent;

     

    public GenericClass(F content){

      mContent = content;

    }

   

    /*

      泛型方法

   */

    public F getContent(){

      return mContent;

    }

     

    public void setContent(F content){

      mcontent = content;

    }

    

    /*

       泛型接口

    */

    public interface GenericInterface<T>{

        void  doSomething(T t);

    }

 

}

7. 你可以把List<String>传递给一个接受List<Object>参数的方法吗?

  如果不用泛型,这样做的话会导致编译错误。

    List<Object> objectList;
    List<String> stringList;
    objectList = stringList;  //compilation error incompatible types

8. Array中可以用泛型吗?

  Array并不支持泛型,而List支持泛型,可以提供编译期的类型安全保证,而Array却不能。

9. 如何阻止Java中的类型未检查的警告?

  如果你把泛型和原始类型混合起来使用,例如下列代码,Java 5的javac编译器会产生类型未检查的警告,例如  

List<String> rawList = new ArrayList(),这种警告可以使用@SuppressWarnings(“unchecked”)注解来屏蔽。

Java 与 C++ 的区别

  • Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。
  • Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台。
  • Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。
  • Java 支持自动垃圾回收,而 C++ 需要手动回收。
  • Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。
  • Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。
  • Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。
  • Java 不支持条件编译,C++ 通过 #ifdef #ifndef 等预处理命令从而实现条件编译。

JRE or JDK

  • JRE 是 一个JVM 程序, Java应用需要运行在JRE.上.
  • JDK 是 JRE的超集, JRE +工具 来发展java程序,比如它提供编译期 "javac"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章