0%

Java疑难杂症

跟着GitHub上的 27天成为java大神 项目学习Java。

损失精度

1
2
3
4
5
6
面试题:
short s=1, s = s+1;

short s=1, s+=1;

上面两个代码有没有问题,如果有,那里有问题

第一个是存在问题的,java默认类型是int, s = s+1里的1就是int类型的,所以等号右边的s+1会被转化成int类型,而一个int类型的值要赋值给short类型的变量,就会报错。

1
2
3
4
      s += 1; 
//这个是没有问题的。
//因为这个式子等价于:
// s = (s的数据类型)(s + 1);

逻辑右移与算数右移

1
2
3
4
5
>>    //算数右移,最高位补符号位
>>> //逻辑右移,最后高位补零
此处的-1int类型,32
-1>>1 =1111 1111 = -1
-1>>>1=0111 1111 = 2147483647 max
1
2
3
-1 原码:1000 0001
反码:1111 1110
补码:1111 1111

打印数组名

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
int[] arr = new int[3];

System.out.println(arr); //[I@2f92e0f4

System.out.println(arr[0]); //0
System.out.println(arr[1]); //0
System.out.println(arr[2]); //0

}

未初始化的数组被编译器初始化为0,打印arr出现字符串**[I@2f92e0f4**其中 [ 代表该数据为数组,I 代表数据类型是int,后面的是地址。

特殊的,如果打印的是一个char数组命,则会直接打印出数组内容

1
2
3
4
5
6
7
8
class test01 {
public static void main(String[] args) {

char[] arr=new char[]{'a','b','c'};

System.out.println(arr); //abc

}

这是因为方法的重载,public void println(char[] x) 是直接打印出内容,其余几个类型是打印地址。

java中只有值传递

如果想通过方法修改数组可以传递地址,也就是数组名。

this的本质

this的本质:代表方法调用者地址值

float与long

long类型的数据后面要加上L或l,float类ing的数据后面要加上f或F,因为整数类型默认是int,浮点类型默认是double。

Java中的内存分配

程序在运行时需要在内存分配空间为了提高效率,对空间进行了划分。

  • 栈 存储局部变量
  • 堆 存储new出来的东西
  • 方法区
  • 本地方法区
  • 寄存器

static

Java中的静态关键字,类中的变量/方法被static修饰,则可以被所有对象共享。并且被static修饰的成员可以直接通过类名进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student{
public static void show(){
system.out.println("hahahah");
}
}
class Demo{
public static void main(String[] args){
Students s=new Students();
s.show();
//直接通过类名调用
Student.show();
}
}

在静态方法中没有this关键字,因为静态是随着类的加载而加载的(存放在堆中的静态区),优先于对象进入内存,而this是随着对象进入内存的,先进内存的不能访问后进内存的,而后进入内存的可以访问先进入内存的。

静态只能访问静态,静态的成员方法只能访问静态成员变量和静态的成员方法,非静态的成员方法则可以访问所有。

变量的就近原则

使用变量的时候会优先找局部范围,如果想直接使用成员变量可以使用this关键字。及承建的成员变量也符合这个规则,如果想使用父类的成员变量可以使用super关键字。

继承

子类会默认调用父类的无参构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ExtendDemo{
public static void main(String[] args){
int a=666;
Zi z=new Zi(666);
}
}

class Fu{
public Fu(){
System.out.println("father");
}
public Fu(int a){}
}

class Zi extends Fu{
public Zi(int a){
System.out.println(a);
}
}

上述代码的输出是什么?是666吗,不,是father 666,这是为什么呢????因为在调用子类的构造方法输出666之前先调用了父类的无参构造方法,要想只输出666有两种方法,第一种是清空父类的所有构造方法,如下

1
2
3
class Fu{

}

这样父类会有一个默认的Fu{},没有影响,特别注意的是就算Fu类定义成下面这个样子也是不行的,因为编译器检测到存在构造方法,就不会自动添加一个无参构造方法,这样子类在调用父类的构造方法时就会出错。

1
2
3
class Fu{
public Fu(int a){}
}

第二种是使用super关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
class Fu{
public Fu(){
System.out.println("father");
}
public Fu(int a){}
}

class Zi extends Fu{
public Zi(int a){
super(a);
System.out.println(a);
}
}

super(…)关键字可以访问父类的带参构造方法,this(…)访问本类的构造方法。

方法重载与方法重写

方法重写 override 发生在子类与父类之间,当子类与父类方法声明相同时就发生了方法重写,返回值类型、参数列表不可改变。

**方法重载 overload ** 发生在同一个类中,当方法名相同但是参数列表不相同时就发生了重载,重载可以使用不同的返回值类型。

多态

多态可以理解为一个事物的多种形态,比如水,有固体水、液体水、气体水,可以定义一个水类,固液气采用继承的方式来创建新的类,这样既有了水的共性,又有各自的特性。我上面的表达不就是继承吗?哈,这样说似乎也没什么问题,多态的三个必要条件就是:继承、方法重写、父类指向子类对象。继承是多态的前提。

1
2
//父类指向子类对象
Father fa= new Son();

对于多态成员变量:编译、运行看左边

看左边指的就是左边的 fa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//父类
public class Father{
public int age=44;
}

//子类
public class Son extends Father{
public int age=22;
}

public Demo{
public static void main(String[] args){
//父类类型 对象 = new 子类类型 ()
Father fa=new Son();
System.out.println(fa.age);
}
}

运行结果,也就是左边的Father类中的age

1
44

对于多态成员方法:编译看左边,运行看右边。

这里的编译,也就是在运行前,我们写出来,编译器就会检测,如果有错误就不能运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//父类
public class Father{
public void eat() {
System.out.println("爸爸吃饭");
}
}

//子类
public class Son extends Father{
public void eat() {
System.out.println("儿子吃饭");
}
public void play(){
System.out.println("儿子玩游戏");
}
}

public Demo{
public static void main(String[] args){
//父类类型 对象 = new 子类类型 ()
Father fa=new Son();
fa.eat();

//下面一句会报错
//The method paly() is undefined for the type Father Java(67108964)
fa.paly();
}
}

虽然实际运行时使用的是子类的方法,但是在编译阶段,看的左边的父类,父类中没有这个方法,所以直接编译不通过。父类无法调用子类独有的方法,如何解决这个问题?有两种方案,其一是在父类中增加一个空的play方法

1
2
3
4
5
6
7
8
9
//父类
class Father{
public void eat() {
System.out.println("爸爸吃饭");
}
public void play(){

}
}

其二是引用类型转换

引用类型转换

向上转型(自动转换)

子类向父类转型,可以理解为儿子变为父亲。父类引用指向一个子类对象,就是向上转型

1
Fteher fa=new Son();

父类有很多子类,而子类却只有一个父类,所以向上转型是自动的.

向下转型

父类向子类转型,需要强制转换,也就是指定转换类型。

1
2
3
4
5
Father fa=new Son();
//向下转型
//子类类型 子类变量名 =(子类类型) 父类变量名
Son fa2=(Son) fa;
fa2.play();

在完成向下转型后,fa2的变量是子类的,方法是子类与父类的和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//父类
class Father{
int age=44;
public void eat() {
System.out.println("爸爸吃饭");
}
public void fish(){
System.out.println("爸爸钓鱼");
}
}

//子类
class Son extends Father{
int age=22;
public void eat() {
System.out.println("儿子吃饭");
}
public void play(){
System.out.println("儿子玩游戏");
}
}

public class Demo{
public static void main(String[] args){
//父类类型 对象 = new 子类类型 ()
Father fa=new Son();
fa.eat();
//下面一句会报错
Son fa1=(Son) fa;
System.out.println(fa1.age);
fa1.fish();
fa1.play();
}
}
1
2
3
4
爸爸吃饭
22
爸爸钓鱼
儿子玩游戏