1. 自增运算的使用
int i = 1;
i = i++; //i=1 i++没用
int j = i++; //j=1 i=2
int k = i + ++i * i++; //从左往右算i,k = 2 + 3 * 3 | i=4
输出结果: i=4 j=1 k=11
2. println底层的特殊点
char[]数组的println不是输出地址值,调用的为特定的println(char[] x),此方法会直接将char[]遍历。
int[] arr1 = new int[] {1,2,3};
System.out.println(arr1); //输出的为地址值
char[] arr2 = new char[] {'a','b','c'};
System.out.println(arr2); //输出的为abc 所调用的println()方法不同
3. 数组的遍历
定义一个int[],让数组的每个位置上的值除以首位置的元素,得到的结果作为该位置上的新值。
int[] arr = new int[]{12,22,34,56,43,222,34,30};
for(int i = arr.length - 1;i >= 0;i--){ //应该从后往前遍历,
arr[i] = arr[i]/arr[0]; //如果从前往后遍历,首位置的元素除完一次就改变了
}
4.重写、重载、多态性的易混淆题目
public class PolymorphismExer {
public static void main(String[] args) {
Base base = new Sub(); //多态
base.add(1,2,3); //输出结果为sub_1
Sub sub = new Sub();
sub.add(1,2,3); //输出结果为sub_2
}
}
class Base{
public void add(int a,int ... arr) {
System.out.println("base");
}
}
class Sub extends Base{
public void add(int a,int[] arr) { //这是重写,而不是方法的重载
System.out.println("sub_1"); //因为int[]arr和int...arr不能同时存在
} //所以编译器认为int[]arr和int...arr是一个东西
public void add(int a,int b,int c) {
System.out.println("sub_2");
}
}
5. == 和 equals() 的区别
- == 运算符:比较的是栈中的值,所以①对于基本数据类型而言,比较的是数值;②对于引用数据类型而言,比较的是地址值。
- equals()方法:①只能用于引用数据类型;②Object类中定义的equals()使用的还是 == 运算符,但String、Date、File、包装类等都重写了equals(),使得equals()比较的是属性的相等。
6. 三元运算符易错点
public void test() {
Object o1 = true?Integer.valueOf(1):Double.valueOf(2.0);
System.out.println(o1); //输出1.0,三元运算符会自动类型提升,两个类型要求一样
}
7. 包装类的易错点
Integer类内部通过静态内部类提供了一个缓存池,范围在-128~127之间,如果超过这个范围,Integer值都是new出来的对象。
public void Test() {
Integer i = Integer.valueOf(1);
Integer j = Integer.valueOf(1);
System.out.println(i==j); //true
Integer x = 1;
Integer y = 1;
System.out.println(x==y); //true
Integer a = Integer.valueOf(128);
Integer b = Integer.valueOf(128);
System.out.println(a==b); //false
Integer m = 128; //Integer类内部通过静态内部类提供了一个缓存池,范围在-128~127之间,
Integer n = 128; //如果超过这个范围,Integer值都是new出来的对象
System.out.println(m==n); //false
}
8. 接口和继承在属性上的优先级是平行的
interface A{
int x = 0;
}
class B{
int x = 1;
}
public class C extends B implements A{
public void pX() {
//System.out.println(x); 这样写是错的
System.out.println(super.x); //调用B中的x
System.out.println(A.x); //调用A中的x
}
public static void main(String[] args) {
new C().pX();
}
}
9. 接口的注意点
class Ball implements Rollable {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Ball(String name) { this.name = name; }
@Override //这里的重写即包含了对Playable接口,也包含Bounceable接口的重写
public void play() {
//ball = new Ball("Football"); 写法是错误的,因为ball已经为final
System.out.println(ball.getName());
}
}
interface Playable { void play(); }
interface Bounceable { void play(); }
interface Rollable extends Playable, Bounceable {
Ball ball = new Ball("PingPang"); //这里隐藏了public static final
}
10. 单例模式
//懒汉式实现单例模式
class Singleton{
private Singleton() {}
private static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
//懒汉式单例模式-创建内部类的方式实现
class Singleton{
private Singleton(){}
private static class Inner{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return Inner.INSTANCE;
}
}
//饿汉式单例模式
class Singleton{
private Singleton(){}
public static final Singleton INSTANCE = new Singleton();
}
11. 类和实例初始化的顺序
输出结果:(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
🔹类初始化
①一个类要创建实例需要先加载和初始化该类。
②子类要初始化需要先初始化父类。
③类的初始化执行的是clinit()方法(即class init);
clinit()方法由静态类变量显示赋值代码和静态代码块组成,执行顺序按照代码编写的位置从上到下执行。
🔸实例初始化
①实例初始化是执行init()方法。
②init()方法有多个,有几个构造器就有几个init()方法。
③init()方法由变量、方法和非静态代码块以及构造器代码组成;
其中变量、方法和非静态代码块按照代码编写的位置从上到下顺序执行,对应的构造器代码最后执行。
④init()方法的首行是super(),即对应父类的init()方法。
🔹总结
父类静态()➡子类静态()➡父类变量方法和非静态()➡父类构造器代码➡子类变量方法和非静态()➡子类构造器代码
*说明:按照代码编写的位置从上到下顺序执行
12. 方法的参数传递机制
输出结果:i=1 str=hello num=200 arr=[2,2,3,4] my.a=11
🔸形参是基本数据类型
传递数据值
🔹引用数据类型
传递地址值
特殊的类型:String、包装类具有不可变性
🔸总结
在方法的参数传递中,基本数据类型传递的是值;String、包装类传递的是地址值,但是无法在原地址上直接修改,修改后的数据是存储在新地址上。
13. 有n步台阶,一次只能上一步或者两步,一共有多少种走法
𝑓 ( 𝑛 ) = 𝑓 ( 𝑛 − 2 ) + 𝑓 ( 𝑛 − 1 ) 𝑓(𝑛)=𝑓(𝑛−2)+𝑓(𝑛−1) f(n)=f(n−2)+f(n−1)
//采用递归实现
public static int getFibonacci(int n){
if(n == 1 || n == 2){
return n;
}
return getFibonacci(n-2) + getFibonacci(n-1);
}
//采用循环迭代实现
public static int getFibonacci(int n){
if(n == 1 || n == 2){
return n;
}
int step1 = 1;int step2 = 2;int sum = 0;
for(int i = 3;i <= n;i++){
sum = step1 + step2;
step1 = step2;
step2 = sum;
}
return sum;
}
14. 局部变量和成员变量
运行结果:2,1,5
1,1,5
🔹局部变量:存储在栈中,只在{}内有效,每次执行都是新的生命周期。
🔸成员变量:分为类变量(static修饰)和实例变量
类变量存储在方法区中,随着类的创建而初始化,该类的所有对象的类变量都是共享的;
实例变量存储在堆中,随着对象的创建而初始化,每个对象的实例变量都是独立的。
15. StringBuffer的append
@Test
public void testStringBuffer(){
String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str);
System.out.println(sb.length());//4
System.out.println(sb);//"null"
StringBuffer sb1 = new StringBuffer(str);//运行抛出异常NullPointerException
System.out.println(sb1);
}
16. HashSet添加元素的理解
@Test
public void test1(){
HashSet<Object> hashSet = new HashSet<>();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
hashSet.add(p1);
hashSet.add(p2);
p1.name = "CC"; //修改了p1的属性,但是p1的哈希值并没有改变
hashSet.remove(p1); //使用新的哈希值查找,找不到未更改哈希值的p1
System.out.println(hashSet); //输出(1001,"CC")和(1002,"BB")
hashSet.add(new Person(1001, "CC")); //能够添加成功
System.out.println(hashSet);
hashSet.add(new Person(1001, "AA")); //虽然哈希值与p1相同,但是equals()匹配的内容不同
System.out.println(hashSet); //(1001,"AA")添加成功
}