javase基础
数据类型与变量
-
数据类型分为简单数据类型(基本类型)和复合数据类型(引用类型),String属于引用类型
-
装箱是将基本类型装换成引用类型的过程;拆箱就是将引用类型转换成值类型的过程;
-
自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化成Integer,double转化成Double,等等。反之就是自动拆箱。
-
为什么要进行拆箱和装箱?
Java是一种完全面向对象的语言。因此,包括数字、字符、日期、布尔值等等在内的一切,都是对象。似乎只需要一种方式来对待这些对象就可以了。
对于CPU来说,处理一个完整的对象,需要很多的指令,对于内存来说,又需要很多的内存。如果连整数都是对象,那么性能自然很低。
于是创造了这样一种机制,使得这些基本类型在一般的编程中被当作非对象的简单类型处理,在另一些场合,又允许它们被视作是一个对象。
这就是装箱和拆箱。
作用:为了保证通用性和提高系统性能
一种最普通的场景是调用一个包含类型为Object的参数的函数(方法),该Object可支持任意类型,以便通用。当你需要将一个值类型(比如说int类型)传入容器时,就需要装箱了。
另一种的用法,就是一个泛型 的容器,同样是为了保证通用,而将元素定义为Object类型的,将值类型的值加入该容器时,需要装箱。 -
装箱是用Xxx.valueOf()方法实现的,例如(Integer.valueOf()),所以你会发现不能通过两个Integer直接比较大小,c =5,Integer a=c,Integer b=c ,只有c在-128-127才能相等,因为他们都是引用缓存里面的对象,c不在-128-127,a!=b,因为都是装箱时新创建的对象,所以比较封箱类大小要拆箱去比较Xxx.xxxValue(),比如a.intValue()==b.intValue(),或者用equals()方法
public boolean equals(Object obj) { if (obj instanceof Integer) { //value是int类型,存储拆箱后的值,比较它们拆箱后的值 return value == ((Integer)obj).intValue(); } return false; }
@HotSpotIntrinsicCandidate public static Integer valueOf(int i) { //当i在-128~127,直接返回缓存好的Integer对象 if (i >= IntegerCache.low && i <= IntegerCache.high) //IntegerCache.cache[]-内部类数组,里面的对象是new Integer(-128)到new Integer(127) return IntegerCache.cache[i + (-IntegerCache.low)]; //新创建对象 return new Integer(i); }
public Integer(int value) { this.value = value; }
@HotSpotIntrinsicCandidate public int intValue() { return value; }
-
Integer a = new Integer(3); Integer b = 3; // 将3自动装箱成Integer类型 int c = 3; System.out.println(a == b); // false 两个引用没有引用同一对象a新创建对象,b装箱时是引用缓存 System.out.println(a == c); // true a自动拆箱成int类型再和c比较+
-
7/2=3, 7.0/2.0=3.5,整数相除舍去小数部分,浮点数相除保留
-
b=++a,a先自增再赋值给b
-
b=a++,a先赋值给b,a再自增
-
a=5,a=a++,最后a=5
-
a=5,a=++a,最后a=6
-
a++,不是原子操作,只有赋值基本类型才是原子操作
-
强制转换
短数据类型转长数据类型系统自动转换(可能溢出导致运算错误),否则得采用强制转换 byte a=(byte)new Integer(5)
-
Array和ArrayList的不同点:
Array可以包含基本类型和引用类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。 -
什么是值传递和引用传递
java只有值传递
-
[lambda表达式详解](Lambda表达式详解 - 海向 - 博客园 (cnblogs.com))
-
java语言中的浮点数默认类型是double
-
变量作用域,四种变量区别P70
-
局部变量在定义时系统不会赋初值
-
静态变量在存储上归属类空间,不依赖任何对象
-
为什么静态方法中不可以直接访问非静态方法
静态方法是属于类的,即静态方法是随着类的加载而加载的,在加载类时,程序就会为静态方法分配内存
非静态方法是属于对象的,对象是在类加载之后创建的
静态方法先于对象存在,当你创建一个对象时,程序为其在堆中分配内存,一般是通过this指针来指向该对象。静态方法不依赖于对象的调用,它是通过‘类名.静态方法名’这样的方式来调用的。而对于非静态方法,在对象创建的时候程序才会为其分配内存,然后通过类的对象去访问非静态方法。因此在对象未存在时非静态方法也不存在,而静态方法自然不能调用一个不存在的方法。
-
子类不能直接通过子类对象访问父类私有属性,但是可以通过父类的getXxx()方法间接获取
-
若一个类定义了一个有参构造方法,系统就不会为它自动生成无参构造方法,子类定义构造方法时如果没有用super调用父类构造方法,默认先执行父类无参构造方法,再执行自己的构造方法,当父类没有无参构造方法时会产生错误,所以每个类都最好提供无参构造方法
-
方法重载(overload),即参数多态,方法名一样,但参数不一样,这就是重载(overload)。所谓的参数不一样,主要有两点:第一是参数的个数不一样,第二是参数的类型不一样。
-
子类对父类方法的覆盖(override),得方法名,参数列表,返回类型一样,返回类型可以为之前返回类型子类型,不得改变静态/非静态属性,访问修饰符不能比父类严格
-
子类不能引用父类对象,父类可以引用子类对象,P91,父类引用子类对象时只能访问到父类有的对象和方法,访问到的方法如果子类中覆盖了它,那么访问到的就是子类的那个方法,像子类中覆盖父类的对象属性,静态属性,静态方法都被隐藏,访问到的是父类的对象属性,静态属性,静态方法
-
符号“==”比较的是什么?hsahcode,equals()之间的关系
“对比两个对象基于内存引用,如果两个对象的引用完全相同(指向同一个对象)时,也就是指向内存地址一样时”操作将返回true,否则返回false。”如果两边是基本类型,就是比较数值是否相等。注意,==与hashcode()与equals()没有任何关系
-
哈希码产生的依据:哈希码并不是完全唯一的,它的是由算法生成的一种数据值,不同类的哈希码可以由不同算法生成,是为了排序和查找方便而存在,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。也有相同的情况,看程序员如何写哈希码的算法。
在Java中,哈希码代表对象的特征。
-
以下是关于HashCode的官方文档定义:
hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。 hashCode 的常规协定是: 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。 以下情况不是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。 实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
-
equals()方法是用来判断一个类的两个对象是不是相等,相等的定义是由equals()方法里面的逻辑来判断的
-
两个被equals判定相等的对象hashcode一定要一样,但是哈希码一样的对象不一定相等,这要根据生成哈希码的算法hashcode()来决定
-
比如说对Object来说
public boolean equals(Object obj) { return (this == obj);//即这两个对象指向同一个地址,同一个对象 }
Object类的hashCode是根据返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。
所以保证了当equals判定为false时,即内存地址不一样时哈希码肯定不一样
-
对于String来说
public boolean equals(Object anObject) { //引用对象是不是同一个 if (this == anObject) { return true; } //判断是不是String的实例 if (anObject instanceof String) { String aString = (String)anObject; //判断内容编码是不是一样 if (coder() == aString.coder()) { //value是Byte[], // 先判断是哪个编码,Latin1拉丁还是UTF16 //StringLatin1.equals/StringUTF16.equals方法------------------在下方 return isLatin1() ? StringLatin1.equals(value, aString.value) : StringUTF16.equals(value, aString.value); } } return false; }
@HotSpotIntrinsicCandidate public static boolean equals(byte[] value, byte[] other) { //先比较两个字符串的长度,即value.length的长度。 //若长度不相同,则return false //若长度相同,则按照数组value中的每一位进行比较,不同,则返回false。若每一位都相同,则返回true。 if (value.length == other.length) { for (int i = 0; i < value.length; i++) { if (value[i] != other[i]) { return false; } } return true; } return false; }
所以由代码可得内容一样的String用equals判定为true,即使地址不一样
public int hashCode() {
//private int hash; // Default to 0
//Cache the hash code for the string为字符串缓存哈希码
int h = hash;
if (h == 0 && value.length > 0) {
hash = h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
}
return h;
}
内容一样的String对象的value属性一样,所以哈希码一定一样,但内容不一样的String可能hashcode计算的值一样
public static int hashCode(byte[] value) {
int h = 0;
for (byte v : value) {
h = 31 * h + (v & 0xff);
}
return h;
}
- 对于Integer/Long的equals
public boolean equals(Object obj) {
if (obj instanceof Integer) {
// private final int value;
//The value of the {@code Integer}.
//obj用intValue拆箱后直接与另一个的int值对比
return value == ((Integer)obj).intValue();
}
return false;
}
hashcode()就是它们拆箱后的值,equals也就是它们拆箱后的值的对比,所以equals为true,hashcode一定一样,符合官方文档定义
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,因为要保证equals()为true,hashcode一定要相等的原则,否则就会违反上面提到的第2点;
4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。
再归纳一下就是hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。根据哈希码分成桶,桶里面有不同对象,再用equals()判定是否同一个引用对象,在上方链接里面
-
为什么会出现4.0-3.6=0.40000001这种现象?
浮点数值采用二进制系统表示, 而在二进制系统中无法精确地表示分数 1/10, 就好像十进制无法精确地表示分数 1/3—样。
为什么二进制无法精确表示1/10
其实跟数位表示法有关,比如十进制的情况下:
123,相当于1 * 10^2+2 * 10^1+3 * 10 0(值*进制位置,个位0,十位1,百位2)
同理:1/10由二进制表示小数的时候只能够表示能够用1/(2^n)例如:
0.5能够表示,因为它可以表示成为1/2
0.75也能够表示,因为它可以表示成为1/2+1/(2^2)
0.875也能够表示,因为它可以表示成为1/2+1/(22)+1/(23)
0.9375也能够表示,因为它可以表示成为1/2+1/(22)+1/(23)+1/(2^4)
但是0.1不能够精确表示,因为它不能表示成为1/(2^n)的和的形式 -
final关键字
当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。,通常是为了完成某种标准功能的类,如String,Math,Integer
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。,但可以改变那个对象的属性值
-
String类用于不变字符串的操作,它创建的字符串是不会改变的,没有改变字符串子类的方法,它改变的永远是指向另一个字符串对象,就像
String s="aa"; s=s+"aaa";//字符串拼接后新创建了一个串对象,改变了s的指向,内容为“aa”的串对象不再有引用变量指向它,被垃圾收集程序回收
String a="hello";
String b="hello";
String c=new String("hello");
boolean f=a.equals(b);//a,b,c用equals判定都是True,内容一样
//java对字符串常量的储存有优化,相同字符串常量只储存一份,所以a和b指向同一个字符串,而new String导致创建了一个新字符串对象
f=(a==b);//f=true
f=(a==c)//f=false
-
String不能改变对象中内容,只能创建新串实现串的变化,创建对象过多浪费内存,效率也低,所以要动态改变字符串,通常采用StringBuffer类实现字符串内容添加、修改和删除
-
new OuterOne.ssss().method1();//由于ssss是静态内部类,可以直接通过外层类直接访问内嵌类构造方法,.method1()为非静态方法
-
闭包问题,即匿名内部类重写的方法里面访问到了在定义那个匿名内部类的类文件里面的属性,匿名函数里的变量引用,也叫做变量引用泄露,因为假如变量是匿名内部类外部定义的变量,它可以被在匿名内部类外被改变的话,那么匿名内部类的方法调用会很危险,比如说那个变量突然变成null值,会导致线程安全问题,因此在Java8之前,如果在匿名类内部引用函数局部变量,必须将其声明为final,即不可变对象。java8后在lambda表达式以及匿名类内部,如果引用某局部变量,jvm则直接将其视为final
-
无论异常是否发生,finally里面的代码块 都会执行。即使前面有return语句
-
子类覆盖父类代throws语句的方法时throws抛出的异常不能超过父类的范围,比如父类抛出RuntimeException,子类不能抛出Exception,子类可以不抛出异常
-
list遍历 for(Object obj:list){}