java基本功(非常重要)
java基础知识一(重要)
目录
- java基础知识一(重要)
- 1、基本数据类型
- 1.1 自动装箱与拆箱
- 1.2 相关的面试问题
- 1、以下代码输出结果是什么?
- 3、总结
- 2、== 和 equals的区别是什么?
- 3、hashCode() 与 equals()
- 4、java泛型是什么? 什么是类型擦除?有哪些常见的通配符?
- 2、为什么java中只有值传递?
- 4、深拷贝与浅拷贝
- 5、方法的重载与重写区别
1、基本数据类型
1.1 自动装箱与拆箱
1、什么是装箱与拆箱呢?
装箱:将基本类型转换成包装器类型;
拆箱:将包装器类型转换成对应的基本类型
代码:
public static void main(String[] args){
Integer j = 11; #自动装箱过程
int j = 10; #拆箱过程
}
注意:在java5之前,要生成一个int j=11的变量需要这样:Integer j = new Integer(11);
在java5之后,直接int j = 11;
2、什么是包装器类型呢?
基本类型 对应的包装器类型
byte(1) ——> Byte
boolean(1) ——> Boolean
char(2) ——> Character
short(2) ——> Short
int(4) ——> Integer
float(4) ——> Float
long(8) ——> Long
double(8) ——> Double
3、装箱与拆箱是如何实现的?
# 看字节码文件,如下图
4、装箱有啥好处:
#把基本类型装箱之后(打包成类对象,基本类型并不算对象)可以使用该对象类的封装好的各种操作方法,比较方便!
1.2 相关的面试问题
1、以下代码输出结果是什么?
public class Main {
public static void main(String[] args) {
//int类型
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 150;
Integer i4 = 150;
System.out.println("下面是int类型-------------------");
System.out.println(i1==i2);
# 结果是true,同一个对象地址,因为在内存缓存中有-128~127之间的常量池,如果定义这个区间的数值直接从内存取,所以是同一对象,否则new一个新对象!
System.out.println(i3==i4);
//short类型
Short short1 = 127;
Short short2 = 127;
Short short3 = 128;
Short short4 = 128;
System.out.println("下面是short类型-------------------");
System.out.println(short1 == short2);
System.out.println(short3 == short4);
//character
Character character1 = 'a';
Character character2 = 'a';
Character character3 = '中';
Character character4 = '中';
System.out.println("下面是char类型-------------------");
System.out.println(character3.hashcode());
System.out.println(character1 == character2);
System.out.println(character3 == character4);
//boolean类型
Boolean b1 = false;
Boolean b2 = false;
Boolean b3 = true;
Boolean b4 = true;
System.out.println("下面是boolean类型-------------------");
System.out.println(b1==b2);
System.out.println(b3==b4);
//long类型
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println("下面是long类型-------------------");
System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
System.out.println(g.equals(a+h));
//double类型
Double d1 = 100.0;
Double d2 = 100.0;
Double d3 = 200.0;
Double d4 = 200.0;
System.out.println("下面是double类型-------------------");
System.out.println(d1==d2);
System.out.println(d3==d4);
}
}
输入
下面是int类型-------------------
true
false
下面是short类型-------------------
true
false
下面是char类型-------------------
20013
true
fales
下面是boolean类型-------------------
true
true
下面是long类型-------------------
true
false
true
true
true
false
true
下面是double类型-------------------
false
false
结论:
1、int类型:
1、 在通过Integer.valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象 ,而新的对象的内存地址是不一样的
源码实现:
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
-----------------------------------------------------------------------------------
IntegerCache类的实现
private static class IntegerCache {
static final int high;
static final Integer cache[];
static {
final int low = -128;
// high value may be configured by property
int h = 127;
if (integerCacheHighPropValue != null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
int i = Long.decode(integerCacheHighPropValue).intValue();
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
2、short类型:逻辑与int类型类似,因为源码里short、int 、byte、long、char代码实现逻辑是一样的
3、char类型:
源码实现:
public static Character valueOf(char c){
if (c <= 127){
return CharacherCache.cache[(int)c];
}
return new Characher(c);
}
小结:如果传入的char类型参数小于等于(从编码中转换的对应值)127,则在内存缓存中获取,否则新建一个该类型的对象!
4、boolean类型:
源码实现:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
小结:如果传入参数为true则为true,false则为false;
5、long类型:
1、 a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等
2、 c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较
6、double类型:
# Double、Float的valueOf方法的实现是类似的。
源码实现:
public static Double valueOf(double d) {
return new Double(d);
}
public static Float valueOf(String s) throws NumberFormatEXception {
return new Float(parseFloat(s));
}
小结:对于double和float类型,装箱(我暂时理解为定义一个变量)都是新建一个新的对象
###### 2、谈谈 Integer i = new Integer(xx) 与 Integer i = xxx两种方式的区别?
1、第一种方式不会触发自动装箱的过程;而第二种方式会触发;
2、 在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)
3、总结
1、java的基本数据类型都实现了常量池(缓存)技术,byte、short、int、long等类型包装器默认创建了[-128,127]范围的缓存数值,超过范围就会创建新对象;boolean的常量池只有true or false;
而double、float则没有实现常量池技术,每次装箱都会生成新的对象!
2、为什么常量池的值范围是[-128 ~ 127],因为1byte占8bit位,换算成十进制有2^8=256个数,然后正负各一半128,但是为什么负的多一个数呢?我个人这样理解的:把这些数串起来当成一个链表,为了增强性能,把它连成一个圈,那么头尾两个结点必须要接在一起,那么就少了一个结点,所以256变成255了!
装箱作用:
Intege i = 11; 在java编译时封装成Intege i = Intege.valueOF(40),是直接从常量中获取该对象,效率 极高;
而Intege i = new Intege(11);是要在堆中开辟新空间存储新对象,消耗时间和空间
2、== 和 equals的区别是什么?
# ==:
用于判断两个都对象的地址是否相等,即判断两个对象是否为同一对象!(对于基本类型,比较的是两对象的值是否相同; 对于引用类型,比较的时内存地址值是否相同,因为引用类型变量存的是对象的存储地址值)
# equals:
一般用于判断两个对象内容是否相等,但不能用于比较基本类型的变量,因为该方法继承于超基类object类!源码如下
public boolean equals(Object obj) { return (this == obj);}
# equals()用法如下两种:
没有被重写: 比较的是两个对象的内存地址是否相等(地址值是否相等)
被重写: 重写一般和hashCode一起重写,表示比较两个对象的内容是否相等(值是否相等)
代码:
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
System.out.println();
System.out.println(s3==s4);
System.out.println(s3.equals(s4));
System.out.println();
System.out.println(11==11.000);
}
}
输出:
true
true
false
true
true
3、hashCode() 与 equals()
1、什么是hashCode()?
答: hashcode()是为了获取对象的哈希码,它是一串整数,这串哈希值是作为该对象在哈希表中的一个索引值,其目的就是为了高效查找对象!hashcode继承与object类,所以任何类都有hashcode方法!
2、为什么要有hashcode?
答:当一个对象加入到哈希表时,先根据对象用算法得出一组hash值,拿这个hash值去哈希表中去一一比对。如果没有相同的哈希值,说明表中之前没有存入该对象,然后就把该对象加入进哈希表中; 如果有相同的哈希值,根据equals()方法对当前哈希值相同的两对象进行比较,如果相等则两对象内容相等,就不让该对象加入哈希表中,否则就存到表中其他位置
3、为什么重写equals()必须重写hashCode()?
答: 如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
4、为什么两个对象hashcode值一样,他们也不一定相等?
答: 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。
4、java泛型是什么? 什么是类型擦除?有哪些常见的通配符?
2、为什么java中只有值传递?
# 首先搞清楚方法的值传递和方法的引用传递的概念
*什么是方法的值传递:
指在调用一个有参方法A时,会把实际参数复制一份传递到方法B中,在方法B中如果对参数进行修改,将不会影响到方法A中的实际参数
*什么方法的引用传递:
指在调用一个有参方法A时,会把实际参数的指向地址传递到方法B中,在方法B中如果对参数进行修改,将影响到方法A中的实际参数
# 区分是值传递还是引用传递就看传递的实参有没有被复制一份,不管是引用类型还是基本类型!基本类型就是复制基本类型的具体数值,引用类型就是复制该引用类型对象的地址!
# string类型是引用类型,但它是不可变类型,存在常量池中,修改后会指向新的string对象!
<知识补充>
java不可变类型有: 基本类型(整数:byte、short、int、long; 浮点数:float、double; 布尔:true、false; 字符串: char; )、String,存在常量池中!
java可变类型有: 除上述外都是
1、基本类型值传递
范例:
public static void main(String[] args){
int numb1 = 10;
int numb2 = 11;
change(numb1,numb2);
System.out.println("numb1=" + numb1);
System.out.println("numb2=" + numb2);
}
public static void change(int a,int b){
//a和b互换
int tempNumb = a;
a = b;
b = tempNumb;
System.out.println("a=" + a);
System.out.println("b=" + b);
}
输出:
a=11
b=10
numb1=10
numb2=11
为什么呢?
1、当numb1、numb2传入到change时,其实是numb1和numb2变量的拷贝,所以如何修改都不会改变源变量
2、方法是不能修改基本类型的参数值的
2、引用类型:
范例:
public static void main(String[] args){
int[] array = {1,3,5,7,9};
System.out.println("修改前array[2]:" + array[2]);
change(array);
System.out.println("修改后array[2]:" + array[2]);
}
public static void change(int[] arr){
arr[2] = 10;
}
输出:
修改前array[2]:5
修改前array[2]:10
为什么呢?
1、int[] arr数值在初始化时其实是在拷贝array与源数据(堆中)引用,所以数组int[] arr 和 int[] array同指向一个对象,外部任何一方数组通过引用修改堆中对象的源变量值,都是会同时隐射到其他引用的变量!
3、引用类型:
范例:
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
s1.setName = "小张";
Student s2 = new Student();
s2.setName = "小李";
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
}
}
输出:
x:小李
y:小张
s1:小张
s2:小李
为什么呢?
总结:
1、s1和s2的对象引用并没有被改变,改变的是s1、s2拷贝后的对象引用,所以java不是引用传递,是值传递!
2、基本类型变量是不能通过方法的参数传递来改变其变量值的,因为改变的是拷贝后变量的值
3、引用类型变量是可以通过方法的参数传递来改变引用变量的值
4、不管是基本类型还是引用类型传参,传递的都是对象的拷贝或对象引用的拷贝,不是直接传递对象的引用,所以java是值传递!
4、深拷贝与浅拷贝
1、疑问: 网上一搜一堆的深浅拷贝,那到底为什么探索研究深浅拷贝呢?换言之,有什么好处?
*好处1 (省力):
当你想在程序的上下文中复用一个大对象时,但是该对象的引用太多了,我不想重新创建一个对象并一个个为其赋值,我们就直接可以使用深拷贝,自定义修改其属性!如果该对象的所有引用都是基本类型(含string)话,深浅拷贝都ok,如果不是就得深拷贝
*好处2 (省时):
new一个对象是要在堆和栈中各开辟一块空间的,栈存储对象的引用地址,堆存储该对象值和地址;而拷贝对象是在内存中进行的,所以时效相较于new是更高的!
2、浅拷贝:
*基本类型:拷贝一份一样数值的对象(值传递)
*引用类型:拷贝对象的引用
上代码:
#使用clone()进行浅拷贝
//创建学生类
class Student implements Cloneable{
//类成员属性
private String name;
private Age aage;
private int length;
public Student(String name,Age aage,int length){
this.name = name;
this.aage = aage;
this.length = length;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public Age getAage(){
return aage;
}
public void setAage(Age aage){
this.aage = aage;
}
public int getLength(){
return length;
}
public void setLength(int length){
this.length = length;
}
public Object clone(){
Object obj = null;
try{
obj = super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return obj;
}
}
public class ShallowCopy {
public static void main(String[] args) {
Age a=new Age(20);
Student stu1=new Student("隔壁老王",a,170);
//通过调用重写后的clone方法进行浅拷贝
Student stu2=(Student)stu1.clone();
System.out.println(stu1.getName());
System.out.println(stu1.getAage());
System.out.println(stu1.getLength());
System.out.println(stu2.getName());
System.out.println(stu2.getAage());
System.out.println(stu2.getLength());
//尝试修改stu1中的各属性,观察stu2的属性有没有变化
stu1.setName("隔壁老谢");
//改变age这个引用类型的成员变量的值
a.setAge(30);
//stu1.setaAge(new Age(99)); 使用这种方式修改age属性值的话,stu2是不会跟着改变的。因为创建了一个新的Age类对象而不是改变原对象的实例值
stu1.setLength(180);
System.out.println("--------------------------------");
System.out.println(stu1.getName());
System.out.println(stu1.getAage());
System.out.println(stu1.getLength());
System.out.println(stu2.getName());
System.out.println(stu2.getAage());
System.out.println(stu2.getLength());
}
}
输出:
隔壁老王
20
170
隔壁老王
20
170
--------------------------------
隔壁老谢
30
180
隔壁老王
30
170
分析小结(浅拷贝):
1、name是String类型,但又存在常量池中,属于不可变类型,所以当修改name值时,name会指向新创建的对象“隔壁老谢”,原name还是指向"隔壁老王",所以string也是值传递!
2、length是int基本类型,属于上述的值传递,不改变原值,而指向新值!
3、aage是Age引用类型,是复制对象的地址值后传递给形参,两个对象引用都指向同一个对象地址,所以会修改原值!
3、深拷贝:
*基本类型:拷贝一份一样数值的对象(值传递)
*引用类型:创建一个与原对象一样数值的新对象,和该对象的其他引用,修改这个对象不会影响原对象的值
上代码(深拷贝):
public class DeepCopy {
public static void main(String[] args) {
Age a=new Age(20);
Student stu1=new Student("隔壁老王",a,170);
//通过调用重写后的clone方法进行浅拷贝
Student stu2=(Student)stu1.clone();
System.out.println(stu1.toString());
System.out.println(stu2.toString());
System.out.println();
//尝试修改stu1中的各属性,观察stu2的属性有没有变化
stu1.setName("隔壁老谢");
//改变age这个引用类型的成员变量的值
a.setAge(30);
//stu1.setaAge(new Age(99)); 使用这种方式修改age属性值的话,stu2是不会跟着改变的。因为创建了一个新的Age类对象而不是改变原对象的实例值
stu1.setLength(190);
System.out.println(stu1.toString());
System.out.println(stu2.toString());
}
}
/*
* 创建年龄类
*/
class Age implements Cloneable{
//年龄类的成员变量(属性)
private int age;
//构造方法
public Age(int age) {
this.age=age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return this.age+"";
}
//重写Object的clone方法
public Object clone() {
Object obj=null;
try {
obj=super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
/*
* 创建学生类
*/
class Student implements Cloneable{
//学生类的成员变量(属性),其中一个属性为类的对象
private String name;
private Age aage;
private int length;
//构造方法,其中一个参数为另一个类的对象
public Student(String name,Age a,int length) {
this.name=name;
this.aage=a;
this.length=length;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Age getaAge() {
return this.aage;
}
public void setaAge(Age age) {
this.aage=age;
}
public int getLength() {
return this.length;
}
public void setLength(int length) {
this.length=length;
}
public String toString() {
return "姓名是: "+this.getName()+", 年龄为: "+this.getaAge().toString()+", 长度是: "+this.getLength();
}
//重写Object类的clone方法
public Object clone() {
Object obj=null;
//调用Object类的clone方法——浅拷贝
try {
obj= super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
//调用Age类的clone方法进行深拷贝
//先将obj转化为学生类实例
Student stu=(Student)obj;
//学生类实例的Age对象属性,调用其clone方法进行拷贝
stu.aage=(Age)stu.getaAge().clone();
return obj;
}
}
分析小结(深拷贝):
1、我们结合深浅拷贝的代码输出后,可以得出:对于基本类型(含string),深浅拷贝的结果都是深拷贝,所以我们只要关注引用类型aage的结果
2、aage指向了不同的对象值30和20,两个对象的引用完全隔离互不影响,所以实现了深拷贝!
总结:(深拷贝、浅拷贝与值传递)
基本类型(含String):
深拷贝:
本质: 复制原对象的数值
特点: 通过修改被复制的对象值并不会影响原对象的值 (互不影响)
浅拷贝:
本质: 复制原对象的数值
特点: 通过修改被复制的对象值并不会影响原对象的值 (互不影响)
值传递:
本质: 复制实参的数值,传递给形参
特点: 通过修改实参传递给形参的值,并不影响实参的原值 (互不影响)
引用类型:
深拷贝:
本质: 复制原对象 与 原对象所关联的所有对象创建成新的对象,新对象所关联的所有对象都指向该新对 象
特点: 通过新对象的引用修改原数据不会影响原对象的值,新旧对象隔离开了 (互不影响)
浅拷贝:
本质: 复制原对象的引用a,并指向原对象(通过引用a对原对象进行修改,会直接影响原对象的值)
特点: 通过复制的引用修改对象的值,会直接影响原对象的值 (会影响)
值传递:
本质: 复制实参的存储地址,传递给形参
特点: 通过实参复制给形参的对象地址,修改形参的值会直接影响实参的原值 (会影响)
5、方法的重载与重写区别
#重载:
定义:同一个类里有多个同名的方法,但是其参数列或类型不能相同,同类型形参个数不能一样,同个数的形参但类型不同一样
作用:同一个方法能够兼容不同类型和个数的参数,执行不同的逻辑处理!
#重写:
定义:子类继承父类的相同方法,通过重写父类的公共方法执行过程,得出不同的结果!
作用:可以根据需求自定义父类公共方法的执行过程来满足要求
区别点 | 方法重载 | 方法重写 |
发生范围 | 同一个类中 | 父类的同一个公共方法中 |
参数 | 形参要么类型不同,要么个数不同 | 相同 |
返回类型 | 可以修改 | 子类方法的返回类型范围小于等于父类的返回值类型 |
异常 | 可修改 | 子类方法的异常处理范围小于或等于父类的 |
访问修饰符 | 可修改 | 修饰符作用范围小于等于父类修饰符范围 |
发生阶段 | 编译期 | 运行过程 |
#总结:
> 两同两小一大一注意
两同:相同方法名,形参相同
两小:子类的返回值类型比父类返回值类型小
一大:子类的访问权限大于父类的访问权限
一注意:重写父类方法时,注意如果父类方法返回值是基本数据类型或有void关键字,则不能修改返回值,如果返回值是引用类型,重写时可返回父类返回值引用类型的子类