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.关于数据结构:
堆栈,采用该结构的集合,对元素的存取有如下的特点:
- 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
- 栈的入口、出口的都是栈的顶端位置
- 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
- 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
队列,采用该结构的集合,对元素的存取有如下的特点:
数组,采用该结构的集合,对元素的存取有如下的特点:
- 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,安检。排成一列,每个人依次检查,只有前面的人全部检查完毕后,才能排到当前的人进行检查。
- 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。
- 查找元素快:通过索引,可以快速访问指定位置的元素
- 增删元素慢:
- 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图
- 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图
链表,采用该结构的集合,对元素的存取有如下的特点:
- 多个节点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
- 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
- 增删元素快:
- 增加元素:操作如左图,只需要修改连接下个元素的地址即可。
- 删除元素:操作如右图,只需要修改连接下个元素的地址即可。
哈希表结构(实际上是数组+链表)
①首先会根据存放类对象的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
下面是合理使用的规则:
- 仅限单
_
,不能多条相连。 - 数值开头和结尾不允许出现
_
。 F
、D
和L
的前后禁止出现_
。- 二进制前导
b
和 十六进制x
前后禁止出现_
。
11.移位运算符
移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左移位运算符 <<
能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 >>
则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。Java 也添加了一种“不分正负”的右移位运算符(>>>),它使用了“零扩展”(zero extension):无论正负,都在高位插入 0。这一运算符是 C/C++ 没有的。
如果移动 char、byte 或 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/79487873,https://blog.csdn.net/qq_33824460/article/details/57943966该博客
其中很多地方参考了on java 8