总结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()。

京公网安备 11010502036488号