文章目录
自动装箱与自动拆箱
把基本数据类型赋值给包装类型叫自动装箱,把包装类型赋值给基本类型叫自动拆箱。
Integer a=3;//自动装箱
int b=a;//自动拆箱
自动装箱:自动装箱底层调用的是Integer.valueOf()
方法,源码如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以看出,当传入的值在 [IntegerCache.low ,IntegerCache.high]之间时,返回的是i+IntegerCache.low,IntegerCache是Integer类内部的一个静态类,源码如下:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
IntegerCache有静态成员变量cache,为一个拥有256个元素的数组:[-128,127]。cache数组对常用的对象进行了缓存,而-128至127是最常用的Integer对象,即当使用的Integer对象大于等于-128并且小于等于127时不用新建一个Integer对象,而是直接使用缓存,这样做一定程度上提高了效率。
Integer aa=100;
Integer bb=100;
System.out.println(aa==bb);//true
int cc=100;
System.out.println(aa==cc);//true,==时自动拆箱,如果+-*/运算,会将基础数据类型装箱
Integer dd=300;
Integer ff=300;
System.out.println(dd==ff);//false
自动拆箱:自动拆箱底层调用的是intValue()方法,源码如下
public int intValue() {
return value;
}
注意:以下代码编译时不会报错,运行时会报java.lang.NullPointerException
Integer integer100 = null;
int int100=integer100;
equals()与hashCode()
equals()与hashCode()的作用:
- equals()方法用来判断对象是否相等,通常判断的是值是否相等。
- hashCode()方法用于返回一个对象的哈希码(散列码),它实际返回一个int整数。哈希码的作用是确定该对象在哈希表中的索引位置。
equals()与hashCode()的关系
- 在集合类HashMap、HashSet、Hashtable等等这些本质是散列表的数据结构中,hashCode()和equals()是有关系的:
- 如果两个对象相等(通过equas()方法比较返回true),那么这两个对象的hashCode值一定相同。如果两个对象通过equals()方法比较返回false,那么它们的hashCode()值也应该不同。
- 如果两个对象hashCode()相等,它们并不一定相等。因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出两个键值对相等。
重写equals()与hashCode()的原则:
- 同一个对象无论何时调用hashCode()方法得到的返回值必须一样。
- hashCode()的返回值相等的对象不一定相等,通过hashCode()和equals()必须能唯一确定一个对象。不相等的对象hashCode()可以相等。
- 一旦重写了equals()函数,就必须重写hashCode()函数。而且hashCode()的生成哈希值的依据应该是equals()中用来比较是否相等的手段。
- 如果两个对象相等(通过equals()方法比较返回true),但这两个对象的hashCode()方法返回不同的hashCode值,这将导致 HashSet/HashMap 把这两个对象保存在Hash表中的两个位置,从而使两个对象都可以添加成功,这就与Set集合不存在重复元素的规则冲突了。
注:如果需要把某个类的对象保存在HashSet集合中,重写这个类的equals()方法和hashCode()方法时,应尽量保证两个对象通过equals()方法返回true()时,它们的hashCode()方法返回值也相同。
Comparable与Comparator接口
- 一个类实现了 Comparable 接口,意味着该类的对象可以直接进行比较(排序),但比较(排序)的方式只有一种,很单一。对象的耦合度高(需要改变对象的内部结构,破坏性大)。实现了Comparable接口的类的对象的
列表
或数组
可以通过Collections.sort()
或Arrays.sort
进行自动排序。 - Comparator比较器,一个类如果想要保持原样,又需要进行不同方式的比较(排序),就可以定制比较器(实现 Comparator接口)。需要比较的对象不需要实现任何接口,只需要在排序的时候传入一个比较器即可,对象本身不用做任何改变。,comparator相当于一通用的比较工具类接口。需要定制一个比较类去实现它,重写里面的compare方法,方法的参数即是需要比较的对象。
- 如果对象的排序需要基于自然顺序,请选择 Comparable,如果需要按照对象的不同属性进行排序,请选择 Comparator。
比较此对象与指定对象的顺序,如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数
//实现类的对象之间比较大小,那么这个类就要实现Comparable接口,然后重写compareTo()方法
public class people implements Comparable<people>{
private String name;
private int age;
private String sex;
//省略getter/setter方法、构造器、toString()
@Override
public int compareTo(people o) {
return this.age - o.age;
}
}
Collections.sort(list,new Comparator<people>(){
@Override
public int compare(people o1, people o2) {
if(o1.age==o2.age&&o1.name==o2.name){
return 0;
}else if(o1.age>o2.age){
return 1;
}else{
return 0;
}
}
});
重载(overload)、重写(overwrite)
重载:一个类里定义多个同名方法,只需要参数列表不同。规则:两同一不同
- 在同一个类中
- 方法名相同
- 参数列表不同
重写:子类继承父类时,定义了和父类同名的方法,就叫重写。规则:两同两小一大
- 方法名相同,参数类型相同
- 子类方法返回类型小于等于父类方法返回类型
- 子类方法抛出异常小于等于父类方法抛出的异常
- 子类方法访问权限大于等于父类方法访问权限
问:为什么方法的返回值不能用于区分重载?
答:Java调用方法时可以忽略返回值。
String、StringBuffer、StringBuilder
在Java中,字符串被作为String类型的对象来处理。String类位于java.lang包中,默认情况下被自动全部导入。
- String类被称为不可变字符序列,原因就是它的底层是由final修饰的字符数组存储的,所以每次重新赋值只是新建了一个对象之后将原来的引用指向它,这样会产生大量的内存垃圾。
- StringBuffer是线程安全的,同时比String类更高效,特别是对字符串进行连接操作时,使用StringBuffer类可以大大提高程序的执行效率。StringBuffer是线程安全的,因为方法大多数都被synchronized修饰了。
- StringBuilder类提供了与StringBuffer类兼容的API,被设计用作StringBuffer的一个简易替换,在单线程环境下,它比StringBuffer类要快。但是在多线程环境下StringBuilder是线程不安全的。使用StringBuilder类处理字符串的方法与StringBuffer类基本一样。
抽象类、抽象方法、接口
抽象方法和抽象类必须使用abstract修饰符来定义,有抽象方法的类只能被定义成抽象类,抽象类里可以没有抽象方法。
抽象类的作用:抽象类主要用于模板模式中:编写一个抽象父类,父类提供多个子类的通用方法,并把一个或多个方法留给子类实现,这就是一种模板模式。
抽象类与抽象方法的规则如下:
- 抽象类不能被实例化,只能当做父类被其他子类继承,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。即使抽象类里不包含抽象方法,这个抽象类也不能创建实例
- 抽象方法不能有方法体
- 抽象类的构造器不能用于创建实例,主要用于被其子类调用
- 含有抽象方法的类只能被定义抽象类
- 当使用abstract修饰类时,表明这个类只能被继承;当使用abstract修饰方法时,表明这个方法必须由子类提供实现(重写)。而final修饰的类不能被继承,final修饰的方法不能被重写,因此final和abstract不能同时使用。
接口:接口是从多个相似类中抽象出来的规范,接口不提供任何实现。接口体现的是规范与实现分离的设计哲学。接口定义的是多个类的公共行为规范。对接口有以下规定:
- 接口中的普通方法都是使用public abstract修饰的,并且方法不能有方法体;但类方法、默认方法都必须有方法体。
- 接口中的变量都是public访问权限,定义接口成员时,可以省略访问权限控制符,如果指定权限,只能是public。
抽象类和接口的区别
- 抽象类中可以构造方法 ,接口没有构造函数
- 类可以实现很多个接口,但是只能继承一个抽象类
- 接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。(Java8 接口可以有实例方法 需要关键字default)
- Java接口中声明的变量默认是public static final(必须赋初始值)。抽象类可以包含非final的变量。
- Java接口中的成员函数默认是public abstract的。抽象类的成员函数可以是private,protected或者是public。
- 接口可继承接口,不能继承类(抽象类和普通类) 抽象类可继承接口也可继承具体类(继承接口时可只实现部分方法)