java基础知识易错点总结

1.关于多态编译运行的时候看什么:

class Fu {

    int num = 4;

    void show() {

        System.out.println("Fu show num");

    }

}

class Zi extends Fu {

    int num = 5;

    void show() {

        System.out.println("Zi show num");

    }

}

class Demo {

    public static void main(String[] args) {

        Fu f = new Zi();

        f.num  // 4

        f.show(); //Zi show num

    }

}

得出结论:

①.多态成员变量

当子父类中出现同名的成员变量时,多态调用该变量时:

编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。

运行时期:也是调用引用型变量所属的类中的成员变量。

简单记:编译和运行都参考等号的左边。编译运行看左边。

②.多态成员方法

编译时期:参考引用变量所属的类,如果没有类中没有调用的方法,编译失败。

运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法。

简而言之:编译看左边,运行看右边。

2.关于构造方法

构造方法,如果没有明写构造方法,则默认会有无参构造方法,若出现构造方法,则系统默认的无参构造方法自动省略,如果有子类继承了父类,①.总体来说,子类构造方法第一行必须要super(参数...)(第一行是父类构造方法),若父类只有无参构造方法(默认的,或者是自己重新的),可以不写,省略,默认是存在的。②若父类含有无参构造,则子类可以不写构造方法(默认还是有无参构造方法的),若父类没有无参构造方法,则子类必须写构造方法(可含参数,也可不含参数),且第一行必须写super(参数)。

3.关于代码块的执行顺序:

public class Fu {
    int num =10;
    static{
        System.out.println("开始执行父类静态代码块");
    }
    {
        System.out.println("开始执行父类代码块");
    }
    public Fu(/*int x*/){
        System.out.println("开始执行父类构造方法");
    }
    public Fu(int x,int y){
        System.out.println("两个参数的父类构造方法");
    }
    public void show(){
        {
            int x=1;
            System.out.println("父类局部代码块X:"+x);
        }
        int x =99;
        System.out.println("父类局部代码块外x:"+x);
    }
}
public class Zi extends Fu {
    int num =5;
    static{
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类构造代码块");
    }
    public Zi(){
        System.out.println("子类构造方法");
    }

    @Override
    public void show() {
        {
            int x=1;
            System.out.println("子类局部代码块X:"+x);
        }
        int x =99;
        System.out.println("子类局部代码块外x:"+x);
    }
}
public class CodeBlockTest {
    public static void main(String[] args) {
        Fu f = new Zi();
        System.out.println(f.num);
    }
}

输出结果:

开始执行父类静态代码块
子类静态代码块
开始执行父类代码块
开始执行父类构造方法
子类构造代码块
子类构造方法
10
 

得出结论:

执行顺序:静态代码块>构造代码块>构造方法>局部代码块

如果子类继承父类,则①先执行父类静态代码块②子类静态代码块③父类构造代码块④父类构造方法④子类构造代码块⑤子类构造方法。

3.关于String

字符串是常量,它的值在创建之后不能修改:原码如下,用final修饰不可修改

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence{}

https://www.cnblogs.com/zhangyinhua/p/7689974.html

https://blog.csdn.net/qq_34490018/article/details/82110578 

4.关于数据结构:

堆栈,采用该结构的集合,对元素的存取有如下的特点:

  1. 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
  2. 栈的入口、出口的都是栈的顶端位置
  3. 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
  4. 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。

 

 

队列,采用该结构的集合,对元素的存取有如下的特点:

数组,采用该结构的集合,对元素的存取有如下的特点:

  1. 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,安检。排成一列,每个人依次检查,只有前面的人全部检查完毕后,才能排到当前的人进行检查。
  2. 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。
  1. 查找元素快:通过索引,可以快速访问指定位置的元素
  2. 增删元素慢:
  3. 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图
  4. 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图

 

 

链表,采用该结构的集合,对元素的存取有如下的特点:

  1. 多个节点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
  2. 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
  3. 增删元素快:
  4. 增加元素:操作如左图,只需要修改连接下个元素的地址即可。
  5. 删除元素:操作如右图,只需要修改连接下个元素的地址即可。

哈希表结构(实际上是数组+链表)

①首先会根据存放类对象的hashCode值判断是否有相同的对象,若相同,判断equals方法,若还不相同,将该对象放在和已有类对象hashCode相同的链表上面,若没有相同hashCode对象,则放在下一个数组位置,若相同则重复,不能存放

 

 

5.关于集合

List集合:有索引,可以存放重复的元素,可以用for循环遍历

ArrayList:底层是数组,查询和遍历速度快,增删的时候需要重复创建数组速度较慢,线程不安全。

LinkedList:底层是链表,查询,遍历慢,增删速度快,线程不安全。

Vector:底层是数组,线程安全;

Set集合:没有索引,不可以方重复的元素(需要根据hashCode,和equals方法,判断元素是否重复),只能用迭代器和增强for循环遍历

HashSet:底层数据结构是哈希表相当于数组加链表,没有索引,无序,线程不安全,(要保证元素的唯一需要重写hashCode和equals方法),其实是一个HashMap;构造方法源码如下:

 LinkedHashSet:底层是哈希表+链表,没有索引,有序,线程不安全

Map集合:

HashMap:

  是Map集合的子集合

  底层采用哈希表结构

  HashMap集合中的key不能重复,通过重写hashCode() 与 equals()方法来保证键的唯一。

  不能保证元素存与取的顺序完全一致,线程不安全,可以存放null键,null值

具体数据结构可以参考视频:https://pan.baidu.com/s/1Vs5sQN7IlD_XnbvMrNIq4Q

LinkedHashMap:

   是HashMap集合的子集合

  底层采用哈希表+链表结构

LinkedHashMap集合中的key不能重复,通过重写hashCode() 与 equals()方法来保证键的唯一。

HashTable基本和hashMap相同,区别,既不能方null键,又不能放null值,线程安全。

V put(K key, V value)  把指定的键与指定的值添加到Map集合中:对于put方法,当已经存入相同键的数据的时候,会覆盖,返回被覆盖的value,否则,没有存入相同键的数据的时候,返回null

6.关于可变参数:同一个方法中只能有一个可变参数,并且该可变参数只能放在方法参数的最后一个

//JDK1.5之后写法

public  int add(int...arr){

int sum = 0;

for (int i = 0; i < arr.length; i++) {

sum += arr[i];

}

return sum;

}

6.关于异常,以及try/catch块中各种复杂头疼的地方:请参阅:http://www.cnblogs.com/lulipro/p/7504267.html

7.关于分布式和集群的概念:

多台服务器跑的都是一套完整的代码,这就叫集群。

多台服务器合起来跑的才是一套完整代码,这就叫分布式。

8.关于赋值

// operators/Assignment.java
// Assignment with objects is a bit tricky
class Tank {
    int level;
}

public class Assignment {

    public static void main(String[] args) {
        Tank t1 = new Tank();
        Tank t2 = new Tank();
        t1.level = 9;
        t2.level = 47;
        System.out.println("1: t1.level: " + t1.level +
            ", t2.level: " + t2.level);
        t1 = t2;
        System.out.println("2: t1.level: " + t1.level +
            ", t2.level: " + t2.level);
        t1.level = 27;
        System.out.println("3: t1.level: " + t1.level +
            ", t2.level: " + t2.level);
    }
}

输出结果:

1: t1.level: 9, t2.level: 47
2: t1.level: 47, t2.level: 47
3: t1.level: 27, t2.level: 27

这是一个简单的 Tank 类,在 main() 方法创建了两个实例对象。 两个对象的 level 属性分别被赋予不同的值。 然后,t2 的值被赋予给 t1。在许多编程语言里,预期的结果是 t1 和 t2 的值会一直相对独立。但是,在 Java 中,由于赋予的只是对象的引用,改变 t1 也就改变了 t2。 这是因为 t1 和 t2 此时指向的是堆中同一个对象。(t1 原始对象的引用在 t2 赋值给其时丢失,它引用的对象会在垃圾回收时被清理)。

这种现象通常称为别名(aliasing),这是 Java 处理对象的一种基本方式。但是假若你不想出现这里的别名引起混淆的话,你可以这么做。代码示例:

t1.level = t2.level;

较之前的做法,这样做保留了两个单独的对象,而不是丢弃一个并将 t1 和 t2 绑定到同一个对象。但是这样的操作有点违背 Java 的设计原则。对象的赋值是个需要重视的环节,否则你可能收获意外的“惊喜”。

 

9.关于引用:

传递引用

基本数据类型只是将值传递过去了,非基本数据类型是将地址传过去了,从下面两个例子就可以看出来了

public class Assignment {

    public static void main(String[] args) {
        int x =3;
        f(x);
        System.out.println(x);

    }

    public static void f(int y){
            y=18;
        System.out.println(y);
    }
}

输出:18
           3

当你将引用传递给方法时,它仍指向同一对象。 一个简单的实验演示了这一点:

// references/PassReferences.java
public class PassReferences {
public static void f(PassReferences h) {
        System.out.println("h inside f(): " + h);
    }
    public static void main(String[] args) {
        PassReferences p = new PassReferences();
        System.out.println("p inside main(): " + p);
        f(p);
    }
}
/* Output:
p inside main(): PassReferences@15db9742
h inside f(): PassReferences@15db9742
*/

方法 toString()  在打印语句中自动调用,并且 PassReferences 直接从 Object 继承而无需重新定义 toString() 。 因此,使用的是 Object 的 toString() 版本,它打印出对象的类,然后打印出该对象所在的地址(不是引用,而是实际的对象存储)

10.关于下划线:

下划线

Java 7 中有一个深思熟虑的补充:我们可以在数字字面量中包含下划线 _,以使结果更清晰。这对于大数值的分组特别有用。代码示例:

// operators/Underscores.java
public class Underscores {
    public static void main(String[] args) {
        double d = 341_435_936.445_667;
        System.out.println(d);
        int bin = 0b0010_1111_1010_1111_1010_1111_1010_1111;
        System.out.println(Integer.toBinaryString(bin));
        System.out.printf("%x%n", bin); // [1]
        long hex = 0x7f_e9_b7_aa;
        System.out.printf("%x%n", hex);
    }
}

输出结果:

3.41435936445667E8
101111101011111010111110101111
2fafafaf
7fe9b7aa

下面是合理使用的规则:

  1. 仅限单 _,不能多条相连。
  2. 数值开头和结尾不允许出现 _
  3. FD 和 L的前后禁止出现 _
  4. 二进制前导 b 和 十六进制 x 前后禁止出现 _

11.移位运算符

移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左移位运算符 << 能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 >> 则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。Java 也添加了一种“不分正负”的右移位运算符(>>>),它使用了“零扩展”(zero extension):无论正负,都在高位插入 0。这一运算符是 C/C++ 没有的。

如果移动 charbyte 或 short,则会在移动发生之前将其提升为 int,结果为 int。仅使用右值(rvalue)的 5 个低阶位。这可以防止我们移动超过 int 范围的位数。若对一个 long 值进行处理,最后得到的结果也是 long

移位可以与等号 <<= 或 >>= 或 >>>= 组合使用。左值被替换为其移位运算后的值。但是,问题来了,当无符号右移与赋值相结合时,若将其与 byte 或 short 一起使用的话,则结果错误。取而代之的是,它们被提升为 int 型并右移,但在重新赋值时被截断。在这种情况下,结果为 -1。下面是代码示例:

// operators/URShift.java
// 测试无符号右移
public class URShift {
    public static void main(String[] args) {
        int i = -1;
        System.out.println(Integer.toBinaryString(i));
        i >>>= 10;
        System.out.println(Integer.toBinaryString(i));
        long l = -1;
        System.out.println(Long.toBinaryString(l));
        l >>>= 10;
        System.out.println(Long.toBinaryString(l));
        short s = -1;
        System.out.println(Integer.toBinaryString(s));
        s >>>= 10;
        System.out.println(Integer.toBinaryString(s));
        byte b = -1;
        System.out.println(Integer.toBinaryString(b));
        b >>>= 10;
        System.out.println(Integer.toBinaryString(b));
        b = -1;
        System.out.println(Integer.toBinaryString(b));
        System.out.println(Integer.toBinaryString(b>>>10));
    }
}

输出结果:

11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111

在上例中,结果并未重新赋值给变量 b ,而是直接打印出来,因此一切正常。下面是一个涉及所有位运算符的代码示例:

// operators/BitManipulation.java
// 使用位运算符
import java.util.*;
public class BitManipulation {
    public static void main(String[] args) {
        Random rand = new Random(47);
        int i = rand.nextInt();
        int j = rand.nextInt();
        printBinaryInt("-1", -1);
        printBinaryInt("+1", +1);
        int maxpos = 2147483647;
        printBinaryInt("maxpos", maxpos);
        int maxneg = -2147483648;
        printBinaryInt("maxneg", maxneg);
        printBinaryInt("i", i);
        printBinaryInt("~i", ~i);
        printBinaryInt("-i", -i);
        printBinaryInt("j", j);
        printBinaryInt("i & j", i & j);
        printBinaryInt("i | j", i | j);
        printBinaryInt("i ^ j", i ^ j);
        printBinaryInt("i << 5", i << 5);
        printBinaryInt("i >> 5", i >> 5);
        printBinaryInt("(~i) >> 5", (~i) >> 5);
        printBinaryInt("i >>> 5", i >>> 5);
        printBinaryInt("(~i) >>> 5", (~i) >>> 5);
        long l = rand.nextLong();
        long m = rand.nextLong();
        printBinaryLong("-1L", -1L);
        printBinaryLong("+1L", +1L);
        long ll = 9223372036854775807L;
        printBinaryLong("maxpos", ll);
        long lln = -9223372036854775808L;
        printBinaryLong("maxneg", lln);
        printBinaryLong("l", l);
        printBinaryLong("~l", ~l);
        printBinaryLong("-l", -l);
        printBinaryLong("m", m);
        printBinaryLong("l & m", l & m);
        printBinaryLong("l | m", l | m);
        printBinaryLong("l ^ m", l ^ m);
        printBinaryLong("l << 5", l << 5);
        printBinaryLong("l >> 5", l >> 5);
        printBinaryLong("(~l) >> 5", (~l) >> 5);
        printBinaryLong("l >>> 5", l >>> 5);
        printBinaryLong("(~l) >>> 5", (~l) >>> 5);
    }

    static void printBinaryInt(String s, int i) {
        System.out.println(
        s + ", int: " + i + ", binary:\n " +
        Integer.toBinaryString(i));
    }

    static void printBinaryLong(String s, long l) {
        System.out.println(
        s + ", long: " + l + ", binary:\n " +
        Long.toBinaryString(l));
    }
}

输出结果(前 32 行):

-1, int: -1, binary:
11111111111111111111111111111111
+1, int: 1, binary:
1
maxpos, int: 2147483647, binary:
1111111111111111111111111111111
maxneg, int: -2147483648, binary:
10000000000000000000000000000000
i, int: -1172028779, binary:
10111010001001000100001010010101
~i, int: 1172028778, binary:
 1000101110110111011110101101010
-i, int: 1172028779, binary:
1000101110110111011110101101011
j, int: 1717241110, binary:
1100110010110110000010100010110
i & j, int: 570425364, binary:
100010000000000000000000010100
i | j, int: -25213033, binary:
11111110011111110100011110010111
i ^ j, int: -595638397, binary:
11011100011111110100011110000011
i << 5, int: 1149784736, binary:
1000100100010000101001010100000
i >> 5, int: -36625900, binary:
11111101110100010010001000010100
(~i) >> 5, int: 36625899, binary:
10001011101101110111101011
i >>> 5, int: 97591828, binary:
101110100010010001000010100
(~i) >>> 5, int: 36625899, binary:
10001011101101110111101011
    ...

结尾的两个方法 printBinaryInt() 和 printBinaryLong() 分别操作一个 int 和 long 值,并转换为二进制格式输出,同时附有简要的文字说明。除了演示 int 和 long 的所有位运算符的效果之外,本示例还显示 int 和 long 的最小值、最大值、+1 和 -1 值,以便我们了解它们的形式。注意高位代表符号:0 表示正,1 表示负。上面显示了 int 部分的输出。以上数字的二进制表示形式是带符号的补码(2's complement)。

12.关于标签以及和break,和continue的关系

“标签”是后面跟一个冒号的标识符。代码示例:

label1:

对 Java 来说,唯一用到标签的地方是在循环语句之前。进一步说,它实际需要紧靠在循环语句的前方 —— 在标签和循环之间置入任何语句都是不明智的。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环或者一个开关。这是由于 break 和 continue 关键字通常只中断当前循环,但若搭配标签一起使用,它们就会中断并跳转到标签所在的地方开始执行。代码示例

label1:
outer-iteration { 
  inner-iteration {
  // ...
  break; // [1] 
  // ...
  continue; // [2] 
  // ...
  continue label1; // [3] 
  // ...
  break label1; // [4] 
  } 
}

[1] break 中断内部循环,并在外部循环结束。 [2] continue 移回内部循环的起始处。但在条件 3 中,continue label1 却同时中断内部循环以及外部循环,并移至 label1 处。 [3] 随后,它实际是继续循环,但却从外部循环开始。 [4] break label1 也会中断所有循环,并回到 label1 处,但并不重新进入循环。也就是说,它实际是完全中止了两个循环。

下面是 for 循环的一个例子:

public class LabeledFor<sout> {
    public static void main(String[] args) {
        int i = 0;
        outer: // 此处不允许存在执行语句
        for(; true ;) { // 无限循环
            inner: // 此处不允许存在执行语句
            for(; i < 10; i++) {
                System.out.println("i = " + i);
                if(i == 2) {
                    System.out.println("continue");
                    continue;
                }
                if(i == 3) {
                    System.out.println("break");
                    i++; // 否则 i 永远无法获得自增
                    // 获得自增
                    break;
                }
                if(i == 7) {
                    System.out.println("continue outer");
                    i++;  // 否则 i 永远无法获得自增
                    // 获得自增
                    continue outer;
                }
                if(i == 8) {
                    System.out.println("break outer");
                    break outer;
                }
                for(int k = 0; k < 5; k++) {
                    if(k == 3) {
                        System.out.println("continue inner");
                        continue inner;
                    }
                }
            }
        }
        // 在此处无法 break 或 continue 标签
        int j = 3;
        System.out.println(3+3);
    }

}

输出结果

i = 0
continue inner
i = 1
continue inner
i = 2
continue
i = 3
break
i = 4
continue inner
i = 5
continue inner
i = 6
continue inner
i = 7
continue outer
i = 8
break outer
6
 

注意 break 会中断 for 循环,而且在抵达 for 循环的末尾之前,递增表达式不会执行。由于 break 跳过了递增表达式,所以递增会在 i==3 的情况下直接执行。在 i==7 的情况下,continue outer 语句也会到达循环顶部,而且也会跳过递增,所以它也是直接递增的。

如果没有 break outer 语句,就没有办法在一个内部循环里找到出外部循环的路径。这是由于 break 本身只能中断最内层的循环(对于 continue 同样如此)。 当然,若想在中断循环的同时退出方法,简单地用一个 return 即可。

要记住的重点是:在 Java 里需要使用标签的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中 break 或 continue

break 和 continue 标签在编码中的使用频率相对较低 (此前的语言中很少使用或没有先例),所以我们很少在代码里看到它们。

基础知识可参考:https://blog.csdn.net/Frank_Adam/article/details/79487873https://blog.csdn.net/qq_33824460/article/details/57943966该博客

其中很多地方参考了on java 8

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值