总结Java的语法知识点,相比专栏,这些更加零碎但也非常重要
1. java中的值传递和引用传递
public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
实验观察:
在java中,函数修改数组,和c++一样,是引用传递,直接修改本身,不产生copy;
但是函数修改数字,是值传递,不会修改本身的值;
结论:
参考资料1,2
更正参考资料1中,最后一张图,person 指向了 X03333 不是 X02222
在面试中,一句话总结就是: Java所谓引用传递也就是传递了引用的拷贝,所以本质也是值传递。
在Persontest(Person person)
函数中,实际传入不是main()中的p
,而是在Persontest
的栈帧中,创建了一个变量person,引用copy一份赋值给它了,在里面修改person的引用,让它指向别处,不会影响main中的p指向的class的实际的值。本质还是值传递
public class PassByValue { public static class Person{ private String name; private int age; public String getName(){ return name; } public int getAge(){ return age; } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } } public static void PersonTest(Person person){ System.out.println("传入的person的name:"+person.getName()); person.setName("我是张三"); System.out.println("方法内重新赋值后的name:"+person.getName()); } public static void main(String[] args) { Person p=new Person(); p.setName("我是李四"); p.setAge(45); PersonTest(p); System.out.println("方法执行后的name:"+p.getName()); } }
执行结果:
传入的person的name:我是李四 方法内重新赋值后的name:我是张三 方法执行后的name:我是张三
修改函数:
public static void PersonTest(Person person){ System.out.println("传入的person的name:"+person.getName()); //person = new Person(); person.setName("我是张三"); System.out.println("方法内重新赋值后的name:"+person.getName()); }
执行结果:
传入的person的name:我是李四 方法内重新赋值后的name:我是张三 方法执行后的name:我是李四
2. 自动装箱(autoboxing)与拆箱(unboxing)
非常感谢Chant的博客
自动装箱(autoboxing)是指 Java 编译器自动将基本数据类型值转换成对应的包装类的对象,例如将 int 转换为 Integer 对象,将 boolean 转换问 Boolean 对象。而拆箱(unboxing)则是反过来转换。
2.1 什么时候会自动装箱和拆箱
自动装箱和拆箱会在以下这些情况发生:
- 把基本数据类型赋值给一个声明为其包装类类型的变量时,像
Integer obj = 1
将发生装箱;反之拆箱。 - 把基本数据类型作为函数调用的参数,而该参数的类型是其包装类时,像前面的
list.add(1)
; 将发生装箱;反之拆箱。 - 对包装类进行加减乘除等基本运算时,将发生拆箱。像 Integer a = 0; a += 1; 在执行 += 运算的 + 运算时将发生拆箱。
2.2 可能带来的问题, 值传递
- 对象比较的结果
考虑以下代码public class TestJava { public static void main(String[] args) { Integer number1 = 127; Integer number2 = 127; System.out.println("number3 == number4: " + (number3 == number4)); } }
输出:number1 == number2: true
创建两个不同的对象,注意这里是用==
比较两个 Integer 对象的内存指针,不是比较他们代表的值。number1 和 number2 是两个不同的对象,比较结果应该是 false。其实是因为 Java 虚拟机对 -128到127 的 Integer 对象做了缓存,所以 number1 和 number2 实际上是同一个对象,比较结果自然也就返回 true。
Java 对大整形Integer,-128到127,是 值传递⚠️ - 对象的过多创建
Integer sum = 0; for (int i = 0; i < 1000; i++) { sum = sum + i; }
上面的代码中,由于 sum 是一个 Integer 对象,不能直接进行 + 操作,所以会先执行 sum.intValue() 拆箱,得到 int 类型进行 + 操作。然后再执行 Integer.valueOf(sum) 进行装箱操作,得到一个 Integer 对象赋值给 sum。虽然 Java 虚拟机对 -128 到 127 的 Integer 对象做了缓存,但是从第 128 到 1000 次循环中总共还要创建 873 个对象。这将导致程序的性能降低甚至触发垃圾回收。而简单地把 sum 的类型改为 int 就可解决问题。
3. 为什么弃用Stack类
参考文章
Stack类的源码中有,public class Stack<E> extends Vector<E>
,这就意味着它继承与Vector类,Vector都不要了,它自然也不用了。java官方建立不要使用老旧的 stack类,而使用Deque类去取代它
Deque<TreeNode> stack = new ArrayDeque<>(); // 压入 stack.push(cur); // 弹出 cur = stack.pop(); // 栈顶元素 cur = stack.peek();
4. 重载和重写的区别
◼ 名称 重载英文是overload,重写英文是override。
◼ 作用
- 重载发生在同一个类的多个同名方法之间,让代码便于调用; 具体定义可以参考java基础第一章
- 重写发生在子类与其父类的同名方法之间,让代码易于复用。
父类与子类之间也存在重载,即在子类中定义出与父类名称相同、参数列表不同的方法。
5. super关键字注意点
5.1 super关键字是什么
特别注意,super不是其父类对象的引用!在“java核心技术卷1”原书第10版第五章的5.1.2节注释中提到,super不是对象的引用,只是一个指示编译器调用超类方法的特殊关键字。具体讨论参考这里
public void testTwo() { Base base = super; //无法赋值成功,IDE提示错误,因为super根本不是父类对象引用 Object object = super; //同理,即便是赋值给Object这个顶层超类也不行 int num = super.num; //super可访问父类成员变量(不仅可以访问类成员变量,还可以访问实例成员变量) int methodResultValue = super.baseTest(); //super可访问父类成员方法(不仅可以访问类成员方法,还可以访问实例成员方法) }
5.2 子类构造器执行顺序
在调试含有父类的构造器时发现了,子类的构造器中如果没有特别申明,会按照:
super() -> 初始化成员变量 -> 其他内容
的方式执行。也就是说先调用父类的构造器,再初始化成员变量,最后在执行后面的代码。原因就是后续的代码可能会依赖父类的构造器,因此super()必须在第一行。
5.3 父类无参构造器
创建子类的对象实例时,默认会先调用父类的无参数构造器(默认构造函数)。如果父类没有指明无参数构造器,JVM会创建一个默认的,前提是不存在有参数构造器。
// 父类 public class Person { String name; public Person(){ System.out.println("Init person"); } public Person(String name){ this.name = name; System.out.println("Init person with name"); } } // 子类 public class Driver extends Person{ public Driver(){ // 这里无论写不写super(),都自动会调用super() super(); System.out.println("Init driver"); } public Driver(String name){ // 如果没有调用父类带参数构造器,JVM会自动调用父类无参数构造器 // 这里无论写不写super(),都自动会调用super(),除非调用父类有参数构造器 super(); // 此时不会调用super(),而是直接调用super(name) // super(name); System.out.println("Init driver with name"); } public static void main(String[] args) { Driver d1 = new Driver(); Driver d2 = new Driver("Tom"); } }
因此,当子类中有参数构造器时用super()输出就是:
Init person Init driver Init person Init driver with name
当子类中有参数构造器指定了父类的有参数构造器super(name)输出就是:
Init person Init driver Init person with name Init driver with name
常见的错误就是,父类只定义了有参数构造器,没有构造无参数构造器,此时子类的构造函数就会报错,指出父类无参数构造器没有申明。这是因为,创建类时指定了有参数构造函数后,系统默认不会创建无参数构造函数,需要自己手动创建。
// 父类 public class Person { String name; public Person(String name){ this.name = name; System.out.println("Init person with name"); } } // 子类 public class Driver extends Person{ public Driver(){ // 这里无论写不写super(),都自动会调用super() super(); System.out.println("Init driver"); } public Driver(String name){ super(name); System.out.println("Init driver with name"); } public static void main(String[] args) { // 编译不通过! Driver d1 = new Driver(); Driver d2 = new Driver("Tom"); } }
输出:编译错误,没有 super constructor Person()
解决方法:
A:在父类中加一个无参构造方法
B:通过使用super关键字去显示的调用父类的带参构造方法
C:子类通过this去调用本类的其他构造方法
子类中一定要有一个去访问了父类的构造方法,否则父类数据就没有初始化。
6.equals和==的区别
参考转载自千古壹号,总结得特别好!
一、java当中的数据类型和“==”的含义:
基本数据类型(也称原始数据类型) :
byte,short,char,int,long,float,double,boolean
。他们之间的比较,应用双等号(==)
,比较的是他们的值。引用数据类型:当他们用
(==)
进行比较的时候,比较的是他们在内存中的存放地址(确切的说,是堆内存地址)。
注:对于引用类型,除非是同一个new
出来的对象,他们的比较后的结果为true
,否则比较后结果为false
。因为每new
一次,都会重新开辟堆内存空间。
特别注意如下情况,
Integer number1 = new Integer(127); Integer number2 = new Integer(127); System.out.println("number1 == number2: " + (number1 == number2)); Integer number3 = 127; Integer number4 = 127; System.out.println("number3 == number4: " + (number3 == number4)); String str1 = "hellow"; String str2 = "hellow"; System.out.println("str1 == str2: " + (str1 == str2)); String str3 = new String("hellow"); String str4 = new String("hellow"); System.out.println("str3 == str4: " + (str3 == str4)); /* number1 == number2: false number3 == number4: true 常量池缓存 str1 == str2: true 常量池缓存 str3 == str4: false */
二、equals()方法介绍:
JAVA当中所有的类都是继承于Object这个超类的,在Object类中定义了一个equals的方法,equals的源码是这样写的:
public boolean equals(Object obj) { //this - s1 //obj - s2 return (this == obj); }
可以看到,这个方法的初始默认行为是比较对象的内存地址值,一般来说,意义不大。所以,在一些类库当中这个方法被重写了,如String、Integer、Date
。在这些类当中equals
有其自身的实现(一般都是用来比较对象的成员变量值是否相同),而不再是比较类在堆内存中的存放地址了。
所以说,对于复合数据类型之间进行equals
比较,在没有覆写equals
方法的情况下,他们之间的比较还是内存中的存放位置的地址值,跟双等号(==)
的结果相同;如果被复写,按照复写的要求来。
我们对上面的两段内容做个总结吧:
==
的作用:
基本类型:比较的就是值是否相同
引用类型:比较的就是地址值是否相同equals
的作用:
引用类型:默认情况下,比较的是地址值。但是某些类重写了equals()
方法,比如Interger, String
, 它们比较的是内容是否一样。
此外当我们自定义类时,可以根据情况自己重写equals()
,来比较对象的成员变量值是否相同,比如自定义student
类,定义当年龄,年纪,姓名都一样时是一个学生。
三、String类的equals()方法:
==
:比较的是两个字符串内存地址(堆内存)的数值是否相等,属于数值比较;equals()
:比较的是两个字符串的内容,属于内容比较。
以后进行字符串相等判断的时候都使用equals()
。