一切都是对象
每种编程语言都有自己的操作内存中元素的方式,Java中,我们认为一切皆对象,我们通过引用来操纵对象,引用可以比作遥控器,对象就是电视机,就算没有电视机,遥控器也可以独立存在。
例如:String s
这里我们只是创建了一个引用,并不是对象,如果我们现在向s发送一个信息(调用)就会报一个错误,因为它并没有和任何事物关联
为了简化对象的生命周期,在创建对象时把它放进堆里,通过引用来进行访问,需要注意的是,基本类型是个特例,它一般很小、很简单,还放在堆里不是很有效,所以对于这种类型一般就直接放在堆桟中,同时为了跨平台,大小一般是固定的
Java中数组也是对象,当创建一个数组对象时,就创建了一个引用数组,每个引用会自动进行初始化为某个特定值,该值拥有自己的关键字null,一旦Java看到null就知道这个引用还没有指向一个对象。
关于默认值
当变量作为类的成员使用时,Java才确保给定其默认值,以确保那些基本类型的成员变量得到初始化,防止出现错误。
然而初始化并不适用于局部变量(即并非某个类中的字段),比如某个方法中的变量是不会被默认初始化的。
如果变量没有被初始化,编译器会报一个错误而不像C报一个警告
关于Static静态
静态最大的特点就是:单独分配存储空间,并且在内存中只有一份,多用于数据共享;不依赖于对象,也就是不与任何对象实例关联(不持有引用)。
使用类名是引用static变量的首选方式,如:ClassName.varName
它不仅强调了变量的static结构,还在某些情况下为编译器的优化提供了更好的机会。对于静态方法也是如此,可以直接调用而不需要创建实例
静态变量存储于内存中的方法区(也有的叫共享区/数据区)的静态区
什么时候用static呢?
- 需要访问对象中的特有数据
- 如果对象中没有成员变量,只有方法,那么就定义成静态
静态代码块中如果有异常不能直接抛,只能先抓,然后在catch里可以new一个异常再抛
参数传递
我们先看一段经典代码:
1 | public class Test1 { |
打印结果当然还是3.
可以理解为传入方法处理的时候将n拷贝了一份赋给变量data,data存在于changeData方法中,生命周期也就伴随着这个方法,当方法执行完毕这个变量也就销毁了。
基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的
特别的,如果传入的是引用类型(对象),那么就是传递的引用,如果改变了这个对象,那就是真的改变了,比如下面的例子,数组也是个对象:
1 | public class Demo { |
入坑
开始我测试的其实是字符串,毕竟String也是对象嘛~~
1 | public class Demo { |
如上,结果还是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 | public static void main(String[] args) |
先说结果a为4,b为3;对于a没什么好说的,主要是b,刚开始我所想的是b赋给b,然后b再自增应该是4,但是呢,这里确实不太好理解,我感觉正确的理解应该是这样的:
我们都知道=
运算先执行右边,但是b++
要等到整句执行完才自增才对,程序是不能回头的,咋办?于是java就搞出了个临时变量,先把b(就是等号右边的b)存到临时变量中,执行自增运算,然后进行了=
运算把这个临时变量赋给了b,所以,最后b是3,在C中也是如此
1 | t=b; //存到临时变量 |
直接常量
我们使用“直接常量”的时候,有时候是模棱两可的,如果出现这样的情况要对编译器进行适当的“指导”,比如:
1 | int i1 = 0X2f; |
十六进制数适用于所有的整数数据类型,前缀0x或0X,是数字0,再有,如果是long的类型,在后面最好用大写的L,小写的l和数字1很像
内部类
关于创建的问题,静态内部类可以直接被创建new A.B();
,如果内部类不是静态那就只能这样创建:
1 | A a = new A(); |
静态内部类的创建并不依赖于外围类,也就是不含外围类的引用,毕竟不能用外围类的方法嘛~
广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。关于这个以前的某篇文章貌似也是说过的,哎~写的太乱,分类也乱,我也很无奈啊,先这样吧
重写问题
父类的静态方法不能被子类重写
子类继承父类后,用相同的静态方法和非静态方法,这时非静态方法覆盖父类中的方法(即方法重写),父类的该静态方法被隐藏(如果对象是父类则调用该隐藏的方法),另外子类可继承父类的静态与非静态方法,至于方法重载我觉得它其中一要素就是在同一类中,不能说父类中的什么方法与子类里的什么方法是方法重载的体现
隐藏和覆盖的主要区别是:
如果是覆盖,当子类转换成父类时不能调用父类原本的方法,因为以及被覆盖了
如果是隐藏,当子类变为父类对象时,是可以执行原本父类的方法,而不会去调用子类的
关于权限
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 | String s = "I AM an Object!"; |
String类当然是继承自Object,所以当然返回是True了
instanceof运算符 只被用于对象引用变量,检查左边的被测试对象 是不是 右边类或接口的 实例化。如果被测对象是null值,则测试结果总是false
判断实例和类的方法常用的就是下面的三种,第一种就是上面我们所说:
- instanceOf关键字,用来判断对象是否是类的实例
- isAssignableFrom,用来判断类型间是否存在派生关系
- isInstance方法,用来判断对象是否属于某个类型的实例
注意,后两个方法是class类中的,一般是这样用
1 | ArrayList.class.isAssignableFrom(Object.class); //false |
其他
遇到精度问题,例如经典的 1.0 - 0.9 ,原因就不说了,都知道,就算使用 BigDecimal 依然有精度问题,需要说明的是使用 BigDecimal 的时候不要使用 new 来构造,使用 BigDecimal.valueOf()
来初始化值,运算一律使用方法进行(除法运算除不尽时可能会抛异常)
遇到 if 连续嵌套太深(无 else ,也推荐不要写 else),为了可读性可以考虑反向条件分解成多个独立的 if,这样会比较好阅读
存档系列
把那些复习 Java 时做的笔记大部分转移到了 Github,不占博客的空间了,随着熟练程度的增加那些也没啥用了。。。。
这里提供下索引,集中到这篇笔记中。
- Java快速捡起
基础知识概念,包含:重载、构造函数、继承、多态、封装
现在看着写的还算不错 - Java复习之内存
JVM 内存规划的基础基础,后面看了 深入理解 JVM 表示实写的实在是太简单了 - java中的集合框架
同样是早期学习的产物,现在看看写的太浅了,深入一点的可以参考:
Java基础复习计划二.md) - 抽象类和接口
早期学习产物,基本的对比而已,当时竟然还单独写了一篇,看来当时这是个难点 - 继承,覆盖,抽象类,和多态
早期学习产物,概念的对比,当时自学时被这些概念困扰了好久啊 o( ̄▽ ̄)ゞ)) ̄▽ ̄)o
评论框加载失败,无法访问 Disqus
你可能需要魔法上网~~