1、主方法中 args 是什么意思?

String[] args:是保存运行 main 函数时输入的参数的字符串数组,当在 cmd 运行时,输入:java test a b c,数组就会将 abc 保存起来:args[0] = a; args[1] = b; args[2]= c; 这些变量在程序中是可以调用的。

2、& 和 && 有什么区别?

(1) & 和 && 都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为 true 时,整个运算结果才为 true,否则,只要有一方为 false,则结果为 false。例如,对于 if(str != null && !str.equals(“”))表达式,当 str 为 null 时,后面的表达式不会执行,所以不会出现 NullPointerException。如果将 && 改为 &,则会抛出NullPointerException 异常。If(x==33 & ++y>0),y 会增长;If(x==33 && ++y>0),y 不会增长。

(2) && 还具有短路的功能,即如果第一个表达式为 false,则不再计算第二个表达式。

(3) & 还可以用作位运算符,当 & 操作符两边的表达式不是 boolean 类型时,& 表示按位与操作,我们通常使用 0x0f 来与一个整数进行 & 运算,来获取该整数的最低 4 个 bit 位,例如,0x01 & 0x0f 的结果为 0x01 。

3、实例变量与类变量

(1) 类变量也叫静态变量,也就是在变量前加了 static 的变量,类变量在创建对象前就已经在内存中存在,随类的创建而创建;实例变量也叫对象变量,即没加 static 的变量。

(2) 类变量是所有对象共有,其中一个对象将它的值改变,其他对象得到的就是改变后的结果。而实例变量则属于对象私有,某一个对象将其值改变,不影响其他对象。

(3) 所有的实例对象都共用一个类变量,内存中只有一处空间是放这个类变量值的。因此,如果一个对象把类变量值改了,另外一个对象再取类变量值就是改过之后的了。在创建实例对象的时候,内存中会为每一个实例对象的每一个非静态成员变量开辟一段内存空间,用来存储这个对象所有的非静态成员变量值,即使两个不同的实例对象是属于同一个 class 类,但是它们的同名非静态成员变量在内存中占用的空间是不同的。

4、== 运算和 equals() 方法

== 和 equals 都是比较的,而前者是运算符,后者则是一个方法,基本数据类型和引用数据类型都可以使用运算符==,而只有引用类型数据才可以使用 equals,下面具体介绍一下两者的用法以及区别:

(1) == 操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用 == 操作符。

(2) 如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如 Objet object = new Object(),变量 object是一个内存,new Object()是另一个内存,此时,变量 object 所对应的内存中存储的数值就是对象占用的那块内存的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用 == 操作符进行比较。

(3) equals 方法是用于比较两个独立对象的内容是否相同,它比较的两个对象是独立的。例如,对于下面的代码:

String a = new String("hello");

String b = new String("hello");

两条 new 语句创建了两个对象,然后用 a,b 这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即 a 和 b 中存储的数值是不相同的,所以,表达式 a==b 将返回 false,而这两个对象中的内容是相同的,所以,表达式 a.equals(b)将返回 true。

equals 本身是一个方法,它是根类 Object 里边的方法,所有类和接口都直接或者间接继承自 Object,所以在所有的类中都有 equals()方法,都是继承来的。

如果一个类没有自己定义 equals 方法,那么它将继承 Object 类的 equals 方法,Object 类的 equals 方法的实现代码如下:

boolean equals(Object o){

}

这说明,如果一个类没有自己定义 equals 方法,它默认的 equals 方法(从 Object 类继承的)就是使用 == 操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用 equals 和使用 == 会得到同样的结果,如果比较的是两个独立的对象则总返回 false。

如果你编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么你必须覆盖equals 方法,由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的。 

/* == 和 equals 的用法以及区别 */
public class TestEquals{
    public static void main(String[] args) {
        String s1 = new String("hello");
        String s2 = new String("hello");
        System.out.println(s1.equals(s2)); // 输出 true,因为 String 类已经重写了 equals
        System.out.println(s1 == s2); // 输出 false,因为两者的地址是不同的

        // 创建三个动物对象
        // a1 和 a2 的 name 和 age 都相同
        Animal a1 = new Animal("Tom", 5);
        Animal a2 = new Animal("Tom", 5);
        //先试一下用 == 比较各个对象
        System.out.println(a1 == a2);
        // 输出 false,两个对象内容相同,但是他们的引用首地址不同
        // 首先将自己写的 equals 方法注释掉,看输出结果是什么
        boolean b1 = a1.equals(a2);
        System.out.println(b1); // 结果为 false,证明是调用的继承来的那个 equals 方法
        // 然后我们调用自己已经重写的 equals,再看下结果
        boolean b2 = a1.equals(a2); // 现在调用的是已经重写后的方法
        System.out.println(b2); // 所以打印的是 true
    }
 }

class Animal{
    private String name;
    private int age;
    public Animal(){}
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public void setAge(int age){
        this.age = age;
    }
    public int getAge(){
        return age;
    }
    // 重写了 equals 方法
    public boolean equals(Object o){
        // 判断两个对象是否为同一个对象,如果是直接返回true
        if (this == o) {
            return true;
        }
        // 判断另一个对象 o 是否是 null,如果是 null 就没有必要再比了,肯定不相等,直接返回false
        if (o == null) {
            return false;
        }
        // 如果前两个都符合要求,再判断对象 o 是否为 Animal 的实例,如果对象 o 是一个别的对象,直接返回 false
        if (o instanceof Animal) {
                // 如果是当前类的实例,那么就强制转换成当前类的实例,再依次比较成员变量是否相等
                Animal animal = (Animal)o;
                // 注意:String 类型的成员变量也可以看做是一个 String 对象,需要用 equals 比较,而不能用==比较
                if (this.getName().equals(animal.name) && this.age == animal.age) {
                    return true;
                }
        }
        return false;
    }
}

5、堆内存与栈内存有什么区别?

(1) heep (堆)是一个可动态申请的内存空间,一般所有创建的对象都放在这里。stack (栈)是一个先进后出的数据结构,通常用于保存方法函数中的参数,局部变量。stack (栈)的空间小,但速度比较快,存放对象的引用,通过栈中的地址索引可以找到堆中的对象。

(2) 栈(Java stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是 Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

(3) 堆(Java Heap)是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

6、可变参数为什么要定义在参数列表的后面?

     void add(int a )

     void add(int a, int b)

     void add (int a, int b, int c)

比如这里的 add 方法就是向集合中添加元素的方法,我们发现这样的重载会使得代码很臃肿,而且复用性也不是很高。

在之前的 Java 版本中是这样设计的:void add(int[] a),就是将接受到的参数类型变成一个数组,在方法中对数组进行遍历,这样数组中有几个元素,就添加几个元素,从而就简化了程序。

后来出现可变参数了,就可以将上边的代码写为:add(int ...is){}虽然形式上发生了改变,但其内部的调用方式是没有变化的,也就是将接受来的个数不确定的参数,装在一个数组中,而且从 int...开始后所有的参数,就会作为数组中的元素装进数组中。

当然有的时候,参数中包含其他参数和可变参数,比如:add(String a, int ...is){},那么从 int...开始之后的参数就是可变参数,而 a 这个参数就不装入到数组中了。那么,如果写成 public void add(int ...is, String a){},根据调用的规则,a 也会作为一个参数添加到数组中去,这当然与我们的程序设计初衷是不相符的。所以,在使用可变参数的时候,要将可变参数定义在参数列表的最后面。

7、创建对象的内存问题?

     class test{ }

     main{ test t = new test(); }

(1) 开辟了栈内存空间:有一个变量 t,存放堆内存地址 new test();

(2) class test{}中包含一个空的构造方法,以及其从 Object 类中继承的所有东西,会分配内存。如果对 new 出来的东西一直没有释放掉对它的引用,Java 的垃圾收集机制无法对其进行回收的,当创建的对象足够多时,会内存溢出。

8、StringBuffer 与StringBuilder 的区别

(1)在执行速度方面:StringBuilder > StringBuffer。

(2)StringBuffer 与 StringBuilder 均为字符串变量,是可改变的对象,每当用它们对字符串做操作时,实际上是在一个对象上操作的,不像 String 一样创建一些对象进行操作,所以速度快。

(3)StringBuilder:线程非安全的。StringBuffer:线程安全的。当在字符串缓冲区被多个线程使用时,JVM 不能保证 StringBuilder 的操作是安全的,虽然速度最快,但是可以保证StringBuffer 是可以正确操作的。大多数情况下是在单线程下进行的操作,所以大多数情况下是建议使用StringBuilder 而不使用 StringBuffer,就是速度的原因。

总结:如果要操作少量的数据用 String;单线程操作字符串缓冲区下操作大量数据用 StringBuilder;多线程操作字符串缓冲区下操作大量数据用StringBuffer。

9、String s = “a” +“b” + “c” + “d” 创建了几个对象


(1)String s = “abc”和 String s = new String(“abc”) 的区别

String s = "abc"; 虚拟机首先会检查 String 池里有没有 "abc" 对象(通过 equals 方法),如果有,直接返回引用,如果没有,会在池里创建一个“abc”对象,并返回引用。

String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);  // result:true

String str = new String("abc"); 不管缓冲池是否有"abc",都会在堆内存创建一个 "abc"对象,返回引用,此时,负责检查并维护缓冲池,其实堆内存的对象是缓冲池中 "abc" 对象的一个拷贝。

String s1 = new String("abc"); 
String s2 = new String("abc"); 
System.out.println(s1 == s2);  // result:false

(2)String s = “a” +“b” + “c” + “d” 创建了几个对象

String s = "a" + "b" +"c" + "d";  java 编译器有个合并已知量的优化功能,在编译阶段就把"a" + "b" + "c" + "d" 合并为“abcd”
String s = "a" + "b" +"c" + "d"; 
// String s = "abcd"; 
System.out.println( s == "abcd");  // result:true

(3)String s1 = “a”  String s2 = “b”  String s3 = s1 + s2;

// String 是常量,不能相加的,java 的实现方式为:
StringBuilder sb = new StringBuidler(s1); 
sb.append(s2); 
s3 = sb.toString(); 
也就是说实际上 s3 是方法返回的 String 对象,凡是方法返回的字符串对象都是在堆内存的。
String s1 = "a"; 
String s2 = "b"; 
String s3 = s1 + s2;  //堆内存的对象 
System.out.println(s3 == "ab");  // result:false