java基础语法面经


字符型常量和字符串常量的区别?

1.形式上:形式上,字符型常量是用单引号''引起的一个字符,字符串是用双引号“”引起的零个或多个字符

2.含义上:字符常量相当于一个ASCII值,可以参与运算,字符串常量代表一个地址值(该字符串在内存中保存的地址)

3.占内存大小:一个字符常量占2字节内存,字符串常量占若干个内存

一个字符常量在java中占2字节内存,在Character包装类中有一个成员常量,Character.SIZE,其值为16,单位是bits,已知1byte=8bits,所以字符常量占2字节内存。


java注释

java中的注释是不会被javac编译成.class文件中的字节码的。

java中的注释一共有三种,单行注释,多行注释,文档注释。

单行注释

/           /

多行注释

/*       */

文档注释

/**

*/


标识符和关键字

标识符

标识符由关键字和用户自定义标识符组成。

任何一个语言都离不开标识符,java语言中,自定义标识符指的是用户自定义的方法,类,变量等等的名字。自定义标识符可以有数字,字母,特殊符号,以及 Unicode 字符集中符号大于 0xC0 的所有符号组合构成。

自定义标识符的开头必须是一个$或者_或者字母,其他的符号开头均非法,同时自定义标识符命名的时候也不能命名为java关键字。另外,标识符区分大小写。

关键字

关键字是对编译器有着特殊作用的单词,不能作为自定义标识符来使用,他们有着特定的意义和用途。

一些常见的关键字:

 二者区别

标识符分为自定义标识符和关键字,标识符包含关键字,关键字是对java有着特殊意义和作用的单词,不能作为一般标识符来使用。关键字是被赋予了特殊含义的标识符。


 ==和equals

首先,java是一个只有值传递的语言,因此==比较的是值。对于基本数据类型来说,==比较的就是值,但是对于引用数据类型而言,其传递的值并不是程序员所设定的value,而是本身的地址,所以对于引用数据类型而言,==比较的是地址,也就是二者是否是同一个对象。

对于equals()来说,它不能比较基本数据类型。同时,其源代码比较的也是两个对象是否是同一对象。

public boolean equals(Object obj) {
     return (this == obj);
}

但是,很多类都对equals()进行了覆盖,也就是比较两个对象的value是否相等,而不再是比较二者是否是 同一对象了。

例如String类就对equals()进行了覆盖,使得比较的是两个字符串的值。

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;
}

当然,对于String而言,新建一个String的时候,虚拟机会在常量池里寻找是否有与当前新建String的值相等的对象,如果有就直接引用,没有就新建一个。


hashCode()和equals()

hashCode()介绍

hashCode()的作用是返回哈希码(散列码),它返回的是一个int类型的整数。它定义在Object类中,也就是说每一个类都有这个函数。该方法通常用于将对象的内存地址转化为整数后返回。

散列表存储的是键值对,利用键快速查找到值,这也使用到了哈希码。

请注意,hashCode()只有在散列表中才有用,其他地方没有任何实际用处。它最大的用处就是确定散列表中某个元素的位置。

在散列表中,两个相同对象的hashCode一定要相同,但是两个对象的hashCode相同时,对象不一定相同。

为什么要有hashCode?

散列表的本质是由数组构成的,我们要通过键来获取到数组中的某个位置的元素,进一步说,数组的某个元素的位置,就是键通过散列码得到的。

在hashset中,我们加入某个新元素时就要用到hashCode()来确定它的位置。当加入新的元素时,会先根据hashCode()来确定插入元素所在的位置,如果发现有相同的hashCode(),那么hashset会再调用equals()方法,来判断是否二者是同一对象,如果是同一对象,就不让他进入hashset。如果不是同一元素,那么就放置到别的位置。而不是上来就直接使用equals()。这样能大大减少equals()的次数,增加效率。

为什么两个不同对象可能会有相同的hashCode?

因为hashCode使用杂凑算法,可能会造成两个不同对象分配到了相同的值。因此,如上文所提到的,hashset中,只是用hashCode做简单的第一次筛查,而不是直接判定。筛查出具有相同hashCode的元素后还要继续使用equals()进行判别。

hashcode不相同,用equals()方法判断的返回的一定为false。

hashcode相同,equals()方法返回值不能确认,可能为true,可能为false。

为什么重写equals()时一定要重写hashCode()

我们重写equals的目的无非就是判断两个对象的某个val相同的时候,返回true,而不是专注于二者是否是同一对象。那么如果我们不重写hashCode,那么对于equals的重写就无意义。我们想不让两个拥有相同的val的对象在同一个散列表中出现,首先判断的是hashCode是否相同,我们知道两个不一样的对象,即使val相同,它的hashCode也不相同,那么我们在插入这个,已经在散列表中有相同val的元素的时候,先判断了hashCode,发现没有与之相同的hashCode,直接插入,不需要判断equals了,那么这样我们重写equals就没用了,根本走不到equals判断的那一步就被直接插入了。

所以,重写equals的时候一定要重写hashCode()。

举个例子,我在一个散列表里要放学生信息。equals改写为判断学生学号是否相同。相同的话直接覆盖掉原来学生的元素

有一个小王学号是20195456,后来他转专业走了,新来了一个小李,我把小李的学号也分配为20195456来覆盖小王的位置,让小王从我的容器里面消失。

这时候我把小李放进去,假设此时我们没有改写hashCode(),那么小李和小王这两个对象的hashCode非常大的可能性不一样(小概率事件没重写的hashCode会发生碰撞,两个不同对象有相同的hashCode),小李就会直接被塞进去,不判断equals,但是我们希望学号相同的学生都要判断equals,这时候容器里就有小王小李两个20195456了,equals白重写了。

我们需要学号相同的学生的hashCode都要相同,以便于走到判断equals这一步,进而在放进散列表时可以把之前的元素挤出去,所以我们重写hashCode()为

public int hashCode(){

  return 1+ID;
}

这时候,小王和小李的hashCode是一致的,那么我们判断equals,发现返回的是true,那么添加进去小李,就把小王给挤出去了。

这就是为什么重写equals一定要重写hashCode的原因。脱离了散列表,谈equals和hashCode的重写是无意义的。


JAVA泛型

推荐文章:https://blog.csdn.net/s10461/article/details/53941091

什么是java泛型

泛型,也称“参数化类型”,提到参数,我们想到的是函数中传递形参和实参。那么参数化类型,顾名思义就是把原来的类型参数化,在定义时,将类型规定为一个参数,在使用时把具体的类型传递进去。

举个例子就很好理解了

1 List arrayList = new ArrayList();
2 arrayList.add("aaaa");
3 arrayList.add(100);
4 
5 for(int i = 0; i< arrayList.size();i++){
6     String item = (String)arrayList.get(i);
7     Log.d("泛型测试","item = " + item);
8 }

在运行阶段,这段代码一定会报错,因为在同一个arrayList中传递进去了两种类型,但是编译时不会报错

我们使用泛型

 1 List arrayList = new ArrayList(); 2 ... 3 //arrayList.add(100); 在编译阶段,编译器就会报错 

这时,编译的时候编译器就会报错

泛型分为三种,泛型类,泛型接口和泛型方法。

泛型类:

故名思意,在类的定义中,设置一个类型的参数,在调用该类的时候传递进去一个类型。举个例子

 1 //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
 2 //在实例化泛型类时,必须指定T的具体类型
 3 public class Generic{ 
 4     //key这个成员变量的类型为T,T的类型由外部指定  
 5     private T key;
 6 
 7     public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
 8         this.key = key;
 9     }
10 
11     public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
12         return key;
13     }
14 }
1 //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
2 //传入的实参类型需与泛型的类型参数类型相同,即为Integer.
3 Generic genericInteger = new Generic(123456);
4 
5 //传入的实参类型需与泛型的类型参数类型相同,即为String.
6 Generic genericString = new Generic("key_vlaue");

传递进去的一定是类类型,不能是简单的类型,如:是Integer不能是int

那么在泛型类中,一定要传递进去一个类型吗?并不!

如果传递进去了一个类型,那么该泛型类就会起到限制和规范的作用。如果不传递进去类型,那么在该泛型类下面的调用了泛型的那些参数,方法等等的类型,可以为任意类型。

泛型接口:

泛型接口和泛型类的定义基本一致,如

1 //定义一个泛型接口
2 public interface Generator {
3     public T next();
4 }

在定义一个类实现接口的时候,如果没有传递进去一个类型,那么必须要再次声明泛型,如果不声明,就会报错

 1 /**
 2  * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
 3  * 即:class FruitGenerator implements Generator{
 4  * 如果不声明泛型,如:class FruitGenerator implements Generator,编译器会报错:"Unknown class"
 5  */
 6 class FruitGenerator implements Generator{
 7     @Override
 8     public T next() {
 9         return null;
10     }
11 }

下面的例子是定义实现泛型接口的类中传递进去类型的情况

 1 /**
 2  * 传入泛型实参时:
 3  * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator
 4  * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
 5  * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
 6  * 即:Generator,public T next();中的的T都要替换成传入的String类型。
 7  */
 8 public class FruitGenerator implements Generator {
 9 
10     private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
11 
12     @Override
13     public String next() {
14         Random rand = new Random();
15         return fruits[rand.nextInt(3)];
16     }
17 }

泛型方法:

泛型类,是在实现该类的时候指定具体类型。泛型方法,是在调用方法的时候指定具体类型。

注意,在泛型类中实现的方法不叫泛型方法!

下面给出泛型方法的定义:

 1 /**
 2  * 泛型方法的基本介绍
 3  * @param tClass 传入的泛型实参
 4  * @return T 返回值为T类型
 5  * 说明:
 6  *     1)public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。
 7  *     2)只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
 8  *     3)表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
 9  *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
10  */
11 public  T genericMethod(Class tClass)throws InstantiationException ,
12   IllegalAccessException{
13         T instance = tClass.newInstance();
14         return instance;
15 }

特殊情况:在泛型类下使用泛型方法:

 1 public class GenericFruit {
 2     class Fruit{
 3         @Override
 4         public String toString() {
 5             return "fruit";
 6         }
 7     }
 8 
 9     class Apple extends Fruit{
10         @Override
11         public String toString() {
12             return "apple";
13         }
14     }
15 
16     class Person{
17         @Override
18         public String toString() {
19             return "Person";
20         }
21     }
22 
23     class GenerateTest{
24         public void show_1(T t){
25             System.out.println(t.toString());
26         }
27 
28         //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
29         //由于泛型方法在声明的时候会声明泛型,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
30         public  void show_3(E t){
31             System.out.println(t.toString());
32         }
33 
34         //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
35         public  void show_2(T t){
36             System.out.println(t.toString());
37         }
38     }
39 
40     public static void main(String[] args) {
41         Apple apple = new Apple();
42         Person person = new Person();
43 
44         GenerateTest generateTest = new GenerateTest();
45         //apple是Fruit的子类,所以这里可以
46         generateTest.show_1(apple);
47         //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
48         //generateTest.show_1(person);
49 
50         //使用这两个方法都可以成功
51         generateTest.show_2(apple);
52         generateTest.show_2(person);
53 
54         //使用这两个方法也都可以成功
55         generateTest.show_3(apple);
56         generateTest.show_3(person);
57     }
58 }

注意:类中的静态方法不能访问类定义的泛型类型,所以静态方法如果使用了一个不确定的类型,必须在方法前声明泛型!

 1 public class StaticGenerator {
 2     ....
 3     ....
 4     /**
 5      * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
 6      * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
 7      * 如:public static void show(T t){..},此时编译器会提示错误信息:
 8           "StaticGenerator cannot be refrenced from static context"
 9      */
10     public static  void show(T t){
11 
12     }
13 }

泛型通配符

什么是类型擦除