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
这时,编译的时候编译器就会报错
泛型分为三种,泛型类,泛型接口和泛型方法。
泛型类:
故名思意,在类的定义中,设置一个类型的参数,在调用该类的时候传递进去一个类型。举个例子
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 GenericgenericInteger = 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 FruitGeneratorimplements Generator 4 * 如果不声明泛型,如:class FruitGenerator implements Generator{ ,编译器会报错:"Unknown class" 5 */ 6 class FruitGeneratorimplements Generator { 7 @Override 8 public T next() { 9 return null; 10 } 11 }
下面的例子是定义实现泛型接口的类中传递进去类型的情况
1 /** 2 * 传入泛型实参时: 3 * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator4 * 但是我们可以为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 publicT 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 publicvoid 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 }