Java基础知识复习

一切都是对象

每种编程语言都有自己的操作内存中元素的方式,Java中,我们认为一切皆对象,我们通过引用来操纵对象,引用可以比作遥控器,对象就是电视机,就算没有电视机,遥控器也可以独立存在。
例如:String s这里我们只是创建了一个引用,并不是对象,如果我们现在向s发送一个信息(调用)就会报一个错误,因为它并没有和任何事物关联
为了简化对象的生命周期,在创建对象时把它放进堆里,通过引用来进行访问,需要注意的是,基本类型是个特例,它一般很小、很简单,还放在堆里不是很有效,所以对于这种类型一般就直接放在堆桟中,同时为了跨平台,大小一般是固定的
Java中数组也是对象,当创建一个数组对象时,就创建了一个引用数组,每个引用会自动进行初始化为某个特定值,该值拥有自己的关键字null,一旦Java看到null就知道这个引用还没有指向一个对象。

关于默认值

当变量作为类的成员使用时,Java才确保给定其默认值,以确保那些基本类型的成员变量得到初始化,防止出现错误。
然而初始化并不适用于局部变量(即并非某个类中的字段),比如某个方法中的变量是不会被默认初始化的。
如果变量没有被初始化,编译器会报一个错误而不像C报一个警告

关于Static静态

静态最大的特点就是:单独分配存储空间,并且在内存中只有一份,多用于数据共享;不依赖于对象,也就是不与任何对象实例关联(不持有引用)。
使用类名是引用static变量的首选方式,如:ClassName.varName它不仅强调了变量的static结构,还在某些情况下为编译器的优化提供了更好的机会。对于静态方法也是如此,可以直接调用而不需要创建实例

静态变量存储于内存中的方法区(也有的叫共享区/数据区)的静态区
什么时候用static呢?

  • 需要访问对象中的特有数据
  • 如果对象中没有成员变量,只有方法,那么就定义成静态

静态代码块中如果有异常不能直接抛,只能先抓,然后在catch里可以new一个异常再抛

参数传递

我们先看一段经典代码:

1
2
3
4
5
6
7
8
9
10
public class Test1 {
public static void main(String[] args) {
int n = 3;
changeData(n);
System.out.println(n);
}
public static void changeData(int data) {
data = 10;
}
}

打印结果当然还是3.
可以理解为传入方法处理的时候将n拷贝了一份赋给变量data,data存在于changeData方法中,生命周期也就伴随着这个方法,当方法执行完毕这个变量也就销毁了。
基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的

特别的,如果传入的是引用类型(对象),那么就是传递的引用,如果改变了这个对象,那就是真的改变了,比如下面的例子,数组也是个对象:

1
2
3
4
5
6
7
8
9
10
11
public class Demo {
public static void main(String[] args) {
int[] ls = {1,2};
updateData(ls);
System.out.println(ls[0]);
}

public static void updateData(int[] data){
data[0] = 6;
}
}

入坑

开始我测试的其实是字符串,毕竟String也是对象嘛~~

1
2
3
4
5
6
7
8
9
10
11
public class Demo {
public static void main(String[] args) {
String ls = "abc";
updateData(ls);
System.out.println(ls[0]);
}

public static void updateData(String data){
data += "d";
}
}

如上,结果还是abc,不变,让我懵逼….
随后才想到,忘记了一个知识点,String其实可以说一旦声明后就是是不可变的(学Py的话应该也会了解到),data += "d";这一句意思就是将data这个对象赋值一份然后追加上d拼成一个新的对象,再把这个对象的引用赋给data,于是最后data随updateData方法的生命周期销毁了….

所以字符串操作应该是尽量少用的,它会在堆中产生大量垃圾,会引起频繁的GC进行回收,卡顿,尽量使用 StringBuffer 和StringBuilder 吧

字符串拼接的效率问题:
使用 + 、使用 StringBuilder 、使用 StringBuffer
在 1.5+ 的 JDK 版本:StringBuilder = + > StringBuffer
在 1.5- 的 JDK 版本:StringBuilder > StringBuffer > +
原因就是 Java 也意识到了使用 + 的问题,所以在编译的时候会自动转换成 StringBuilder ,所以在 1.5+ 的版本它们的效率是一样的,因为要保证多线程同步所以势必会慢一些,但是还是建议使用 StringBuilder

需要说明的是,这里的转换是在 str1 + str2 这种情况下的,当使用 str += "asjdkla"; 这种形式的时候实际上是转换为:str = new StringBuilder().append(str).append("asjdkla").toString();
一眼就能看出创建了太多的 StringBuilder 对象,而且在每次循环过后 str 越来越大,导致每次申请的内存空间越来越大,很多人喜欢把它放到 for 里循环做对比测试。
还有一点就是使用 stringbuilder 的时候,默认它会创建一个长度 16 的容器,当不够了的时候就再 +16,然后把内容拷过去,所以,当大量拼接时,可以根据估计长度来设置这个值:new StringBuilder(24)
PS:此方案不适用于 List ,反而会增加损耗。
使用 String.valueOf() 方法转换字符串能避免 toString 空指针问题;两个字符串拼接直接调用 String.concat() 性能最好
格式化输出可以使用 String.format() 【使用 %1$2s 等占位】或者 Message.format() 【使用 {} 占位】

如果了解字符串在堆中的存储结构应该会很好的理解
推荐这篇文章:https://segmentfault.com/a/1190000007099818

自增运算问题

Java简化了运算,用++可以实现自增,但是如果这样写

1
2
3
4
5
6
7
8
public static void main(String[] args)
{
int a = 3,b = 3;
a = ++a;
b = b++;
System.out.println(a);
System.out.println(b);
}

先说结果a为4,b为3;对于a没什么好说的,主要是b,刚开始我所想的是b赋给b,然后b再自增应该是4,但是呢,这里确实不太好理解,我感觉正确的理解应该是这样的:
我们都知道=运算先执行右边,但是b++要等到整句执行完才自增才对,程序是不能回头的,咋办?于是java就搞出了个临时变量,先把b(就是等号右边的b)存到临时变量中,执行自增运算,然后进行了=运算把这个临时变量赋给了b,所以,最后b是3,在C中也是如此

1
2
3
t=b; //存到临时变量
b=b+1; //执行自增,右边运算结束
b=t; //用临时变量t开始左边的“=”运算

直接常量

我们使用“直接常量”的时候,有时候是模棱两可的,如果出现这样的情况要对编译器进行适当的“指导”,比如:

1
2
3
4
5
6
int i1 = 0X2f;
int i2 = 0X2F;
int i3 = 0177;
long n1 = 200L;
float f1 = 12F;
double d1 = 2D;

十六进制数适用于所有的整数数据类型,前缀0x或0X,是数字0,再有,如果是long的类型,在后面最好用大写的L,小写的l和数字1很像

内部类

关于创建的问题,静态内部类可以直接被创建new A.B();,如果内部类不是静态那就只能这样创建:

1
2
A a = new A();
A.B b = a.new B();

静态内部类的创建并不依赖于外围类,也就是不含外围类的引用,毕竟不能用外围类的方法嘛~
广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。关于这个以前的某篇文章貌似也是说过的,哎~写的太乱,分类也乱,我也很无奈啊,先这样吧

重写问题

父类的静态方法不能被子类重写
子类继承父类后,用相同的静态方法和非静态方法,这时非静态方法覆盖父类中的方法(即方法重写),父类的该静态方法被隐藏(如果对象是父类则调用该隐藏的方法),另外子类可继承父类的静态与非静态方法,至于方法重载我觉得它其中一要素就是在同一类中,不能说父类中的什么方法与子类里的什么方法是方法重载的体现

隐藏和覆盖的主要区别是:
如果是覆盖,当子类转换成父类时不能调用父类原本的方法,因为以及被覆盖了
如果是隐藏,当子类变为父类对象时,是可以执行原本父类的方法,而不会去调用子类的

关于权限

Java中有4种权限,至于各个的作用,通过一张表就能明白了,很有规律

公共(public) 保护(protected) 默认(default) 私有(private)
同一类中
同一包中
子类中 - -
不同的包中 - - -

字段和属性

一个类里定义的变量叫做字段,当提供了get/set方法,就称为属性,比如常见的javabean里面的变量都称为属性
属性的多少与变量无关,只与get/set方法的数量有关
这应该是比较正规的叫法

注意,在定义属性的时候命名不要用 mXxx 的这种形式,规范是一方面,还有就是当你生成 get/set 方法时就变成了 getMXxxx ,是不是很恶心,IDE 基本会自动帮你出去开头的 M,所以就变成了 getXxxx,但是返回的还是 mXxxx,这样就更恶心了,所以…..要规范!不要在 java bean 里乱定义名字

instanceof运算符

instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。

1
2
String s = "I AM an Object!";
boolean isObject = s instanceof Object;

String类当然是继承自Object,所以当然返回是True了
instanceof运算符 只被用于对象引用变量,检查左边的被测试对象 是不是 右边类或接口的 实例化。如果被测对象是null值,则测试结果总是false
判断实例和类的方法常用的就是下面的三种,第一种就是上面我们所说:

  1. instanceOf关键字,用来判断对象是否是类的实例
  2. isAssignableFrom,用来判断类型间是否存在派生关系
  3. isInstance方法,用来判断对象是否属于某个类型的实例

注意,后两个方法是class类中的,一般是这样用

1
2
3
4
5
ArrayList.class.isAssignableFrom(Object.class);  //false 
Object.class.isAssignableFrom(ArrayList.class); //true

String s=new String("javaisland");
System.out.println(String.class.isInstance(s)); //true

其他

遇到精度问题,例如经典的 1.0 - 0.9 ,原因就不说了,都知道,就算使用 BigDecimal 依然有精度问题,需要说明的是使用 BigDecimal 的时候不要使用 new 来构造,使用 BigDecimal.valueOf() 来初始化值,运算一律使用方法进行(除法运算除不尽时可能会抛异常)


遇到 if 连续嵌套太深(无 else ,也推荐不要写 else),为了可读性可以考虑反向条件分解成多个独立的 if,这样会比较好阅读

存档系列

把那些复习 Java 时做的笔记大部分转移到了 Github,不占博客的空间了,随着熟练程度的增加那些也没啥用了。。。。

这里提供下索引,集中到这篇笔记中。

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~