文章目录
01 | Java基本概念
-
Java的工作方式
- 编写源代码party.java
- 执行javac来编译源代码,产生party.class
- 启动JVM来运行字节码文件,JVM会将字节码转换成平台能够理解的形式来运行。
-
关于while循环
- 不能使用如下错误代码:
while循环中只能接收boolean型结果,即接收true/false。int i = 1; while(i){ .......... }
- 不能使用如下错误代码:
-
关于类、对象、方法、实例变量
- 由 .java 文件编译出来的 ----------- 类
- 实例变量可以与其他兄弟姐妹不同 -----------对象
- 功能类似模板 ------------ 类
- 喜欢执行工作 ------------ 对象、方法
- 带有很多方法 ------------ 类、对象
- 代表“状态” ------------- 实例变量
- 拥有很多行为 ---------- 对象、类
- 待在对象中 ------------ 方法、实例变量
- 生存于堆上 ------------ 对象
- 被用来创建对象实例 --------- 类
- 状态可以改变 ------------- 对象、实例变量
- 声明方法 -------- 类
- 可以在运行期变化 ---------- 对象、实例变量
02 | primitive主数据类型和引用
-
primitive主数据类型用来保存基本类型的值, 对象引用保存的是对象的引用。
-
primitive主数据类型的声明与赋值声明:
- 编译器不允许将大杯的内容放到小杯中(强制转换有风险),但反过来可以(隐含展开)。
long y = 42; int x = y;//不能通过编译,编译器无法确定long的内容是否可以截成int型 //若要强制编译器通过,可用强制转换,如下: int x = (int) y; long y = 42; short x = (short) y; //可通过编译,但x值为-25534!!! float f = 3.14f; int x = (int) f; //x值为3
- primitive主数据类型共有8种,分别是:
boolean char byte short int long float double
可记忆为:
“Be Careful,Bears shouldn't Ingest Large Furry Dogs ”(小心,熊不应该吃大型毛茸茸的狗)
,
存放数值的有6种如下图: - primitive变量的默认值:
数字的primitive(包含char)的预设为0,boolean的预设为false,对象引用则为null;如: int–0;float–0.0;boolean–false。
int x = 234; byte b = 89; boolean ifFun = true; double d = 34.98; char c = 'f'; long big = 3456789; float f = 32.5f; //不带f默认是double类型
- 编译器不允许将大杯的内容放到小杯中(强制转换有风险),但反过来可以(隐含展开)。
-
Java保留字中易遗忘的:native、strictfp、transient、assert、enum、const
-
变量命名方法:
- 必须以字母、下划线_、或$ 开头,不能用数字开头
- 除首字母外,后面可用数字
- 要避开上述的java保留字
-
primitive主数据类型变量与引用变量:
- 那么问题来了,如果如上图所言,那是否意味着所有的对象引用都具有相同的大小,而不管它实际上所引用的对象大小?
答:是的,对于任意一个Java虚拟机来说,所有的引用大小都一样,但不同的虚拟机间可能会以不同的方式来表示引用,因此某个虚拟机的引用大小可能会大于或小于另一个虚拟机的引用。
- 那么问题来了,如果如上图所言,那是否意味着所有的对象引用都具有相同的大小,而不管它实际上所引用的对象大小?
-
数组也是对象
-
没有引用到任何对象的引用变量的值为null值。
03 | 方法操作实例变量
- 类所描述的是对象知道什么(变量)与执行什么(方法)
- java是通过值传递的(拷贝传递),特别注意,在方法中改变拷贝后的值,对拷贝前的值是没有影响的。(java所传递的所有东西都是值!!!)
- 实例变量与局部变量的区别
- 实例变量声明在类中
- 局部变量声明在方法中
- 局部变量在使用前必须初始化,因为它没有默认值,否则无法编译
- 变量的比较(==与equals)
- 对号入座:实例变量、参数、return、getter、setter、封装、public、private、按值传递、方法
- 一个类可以带很多个------实例变量、方法、getter、setter
- 一种方法只能带有一个-------return
- 可以被隐含的转换------return、参数
- 喜欢private的实例变量--------封装
- 制作一个拷贝-------按值传递
- 只有setter才能更新-------实例变量
- 根据定义返回-------getter
- 不可以以实例变量来运用------public
- 可以有许多个参数 -------- 方法
- 被定义成采用一个参数 ------ setter
- 帮忙创建封装 ------- getter、setter、public、private
- 总是单飞 ---------- return
04 | 编写程序
- Math.random返回一个介于0到小于1之间的数,如下产生0~4之间的整数
int randomNum = (int) (Math.random()*5)
05 | 认识Java的api
- ArrayList与数组的比较
编译器能够自动地将primitive主数据类型包装成Object存放在ArrayList中。 - 除了java.lang这个包里面的类,其他要用到的类都要指定完整名称或者使用import来导入。
06 | 继承与多态
-
继承可传递,即狼is a犬类动物,犬类动物is a 动物,则可推出,狼is a 动物。
-
继承下来的方法可以被覆盖,但实例变量不能被覆盖。
-
如何预防某个类被作出子类:
- 存取控制。就算不能标记为private,也可以不标记为public。非公有的类智能被同一个包的类作出子类。
- 使用final。表示不能被继承。
- 让类只拥有private的构造器。
-
重写与重载:
-
相同点:方法名称相同。(本书认为重载与多态毫无关系P191)
-
不同点:位置、参数列表、权限修饰符、返回值类型、抛出异常不同。
重载(overload):发生在同一个类中,方法名相同,参数列表不同,与权限修饰、返回值类型、抛出异常无关。重写(override): 发生在继承类中,方法名和参数列表相同,权限修饰符大于等于父类、返回值类型小于等于父类、抛出异常小于等于父类。
-
07 | 接口与多态
-
object类是具体的,那怎么会允许有人去创建Object的对象呢?这不就跟Animal对象一样不合理吗?
有时候可能需要一个通用的对象,一个轻量化的对象,它最常见的用途是在线程的同步化上面。 -
使用Object类型的多态应用:
ArrayList<Object> myDogArrayList = new ArrayList<Object>(); Dog adog = new Dog(); myDogArrayList.add(adog); //当把dog对象取出,并赋值给Dog的引用将无法通过编译 Dog d = myDogArrayList.get(0); //上述代码不能通过编译的原因,对ArrayList<Object>调用get()方法会返回Object类型,编译器无法确认它是Dog //注意!任何从ArrayList<Object>取出的东西都会被当做Object类型的引用而不管它原来是什么。
-
多态:
-
对象类型转换:
Object引用变量在没有类型转换的情况下不能复制给其他的类型,若堆上的对象类型与索要转换的类型不兼容,则此转换会在执行期发生异常。例如:Dog d = (Dog) x.getObject(aDog);
//如下代码可编译通过 Object o = al.get(index); Dog d = (Dog) o; d.bark();
-
为什么不能多继承? 会出现“致命方块”的问题
-
如果想要创建出一个具体的子类,但不打算完全地覆盖掉原来的方法,只想加入额外的动作,该如何?
用super关键字,调用父类的方法。abstract class Report{ void runReport(){......} void printReport(){......} } class BuzzReport extends Report{ void runReport(){ super.runReport(); buzzCompliance(); printReport(); } void printReport(){......} }
08 | 构造器与垃圾收集器
-
内存中的两种区域:
- 堆(heap):对象的生存空间
- 栈(stack):方法调用、变量的生存空间
-
实例变量 OR 局部变量
- 实例变量:是被声明在类而不是方法中,存在与所属的对象中。位于堆上。如实例变量是个对对象的引用,则对象与引用都在堆上。primitive默认值:0/0.0/false,引用默认值:null。
public class duck{ int size; }
- 局部变量:声明在方法中,生命周期只限于方法被放在站上的这段时间(方法调用至执行完毕为止)
//x,i,b都为局部变量 public void foo(int x){ int i = x + 3; boolean b = true; }
- 实例变量:是被声明在类而不是方法中,存在与所属的对象中。位于堆上。如实例变量是个对对象的引用,则对象与引用都在堆上。primitive默认值:0/0.0/false,引用默认值:null。
-
方法会被堆在一起:
-
有关对象局部变量:
- 非primitive的变量只是保存对象的引用,而不是对象本身。
- 对象本身只会存在于堆上。
-
实例变量存在于对象所属的堆空间上:
- 若实例变量是primitive类型:则根据具体的int、long等类型留空间;
- 若实例变量是对象:java只会保留引用变量的空间而不是对象本身的空间。如果只声明变量而没有创建对象:
直到引用变量被赋值一个新的Antenna对象才会在堆上占有空间:
构造函数
-
下列代码,看起来是在调用Duck方法,实际上是在调用Duck的构造函数来创建对象。
java Duck myDuck = new Duck();
-
构造函数不是方法,唯一能够调用构造函数的方法:新建一个类。初始化对象时会执行的程序代码在构造函数里。
-
如何分辨构造函数和方法?构造函数没有返回值类型。
-
构造函数不会被继承。
-
如果已经写了一个有参数的构造函数,并且需要一个没有参数的构造函数则必须自己动手写。完全没写构造函数时编译器才会帮你写一个。
-
如果类有一个以上的构造函数,则参数一定要不同。
-
重载: 多个构造函数且参数都不相同(参数的类型 and 顺序)
-
构造函数可以是公有、私有或不指定。
-
在创建新对象时,所有继承下来的构造函数都会执行。且构造函数在执行的时候首先去执行它的父类的构造函数。
-
举个栗子:
-
如何调用父类的构造函数? 调用super,且必须在第一行调用
- 没编写构造函数:自动调用super();
- 有构造函数但没调用super():编译器会帮你对每个重载版本的构造函数加上:super(),编译器只会自动加无参数版本。
public class Duck{ int size; public Duck(int newSize){ super(); //调用父类构造函数 size = newSize; } }
-
某个构造函数调用同一个类的另一个构造函数:(使用this)
- this()只能用在构造函数中,且只能是第一行语句。
- 因为super()和this()都要求在第一行,因此二者不可兼得。
- 举个栗子:
class Mini extends Car{ Color color; public Mini(){ this(Color.Red); } public Mini(Color c){ super("Mini"); color = c; } ======以下为错误示范============== public Mini(int size){ this(Color.Red); super(size); } }
-
对象的生命周期:
- 先看个栗子:
public void doStuff(){ boolean b = true; go(4); } public void go(int x){ int z = x + 24; crazy(); } public void crazy(){ char c = 'a'; }
- 先看个栗子:
-
释放对象的引用(变为可回收垃圾):
- 引用永久性的离开了它的范围:
void go(){ //z会在方法结束时消失 Life z = new Life(); }
- 引用被赋值到其他的对象上
Life z = new Life(); z = new Life();
- 直接将引用设定为null
Life z = new Life(); z = null;
- 引用永久性的离开了它的范围:
09 | 数字与静态
-
静态的方法不能调用非静态的变量:静态方法是通过类的名称来调用的,它不知道有哪些实例变量。
-
静态的方法不能调用非静态的方法。
-
非静态方法可以读取到静态的变量。
-
静态变量是共享的,同一类所有的实例共享一份静态变量。而实例变量是每个实例一个。
-
静态变量在类被加载时初始化,且静态变量在该类的任何静态方法执行之前就初始化。
-
静态的final变量是常数。
- 如下代码:
public: 可供各方读取public static final double PI = 3.141592653589793;
static: 不需要Math的实例
final: 圆周率是不变的 - 常数final变量的初始化:
//方法一: public class Foo{ public static final int X = 25; } //方法二: public class Bar{ public static final Double X ; static{ X = (double)Math.random(); } }
- 如下代码:
-
final:
- final的变量:不能改变它的值
- final的方法:不能覆盖掉该方法
- final的类:不能继承该类
-
Math的方法:
- Math.random(): 返回介于0.0~1.0之间的双精度浮点数(随机数)
- Math.abs(): 返回双精度浮点数类型参数的绝对值
- Math.round(): 返回四舍五入之后的整型或长整型值
- Math.min(): 返回最小值
- Math.max(): 返回最大值
-
primitive主数据类型的包装:
- 当你需要以对象方式来处理primitive数据时,就将其包装起来。
- 装包:
int i = 288; Integer i = new Integer(i);
- 拆包:
int j = i.intValue();
- 编译器的自动拆包装包:
ArrayList<Integer> ln = new ArrayList<Interger>(); //自动装包 ln.add(3); //自动拆包 int num = ln.get(0);
- 举个栗子:
运行失败:空指针异常。public class TestBox{ Integer i; int j; public static void main (String[] args){ Testbox t = new Testbox(); t.go(); } public void go(){ j=i; System.out.println(j); System.out.println(i); } }
-
操作日期:
- 要取得当前的日期时间就用Date,其余功能可以从calendar上面找。
- 创建calendar实例:
Calendar cal = Calendar.getInstance();
- Calendar API的方法
add(int field, int amount) :加减时间值 get(int field):取出指定字段的值 getInstance(): 返回calendar,可指定地区 roll(int field, boolean up): 加减时间值,不进位 set(int field, int value): 设定指定字段的值 set(year, month,day, hour, minute) : 设定完整的时间 setTimeInMillins(long millis) : 以毫秒指定时间
-
做个练习:
正确答案:x x √ x √ x √ √ x x x √ x x
10 | 异常处理
- 受检查与不受检查的异常:
- 编译器不会注意RuntimeException类型的异常,RuntimeException不需要声明或被包在try/catch块中。(要包也行)
- try/catch:
- finally:无论如何都要执行。如果try或者catch块有return指令,finally还是会执行,但是流程会跳到finally然后再回到return指令。
- 一个方法可以抛出多个异常。有多个异常时可直接声明他们的父类,若需单独处理每个异常则要分开catch,并且从小到大catch(把最小的异常写在最上面)。
- Integer.parseInt()方法也有异常,但是不用管。原因:它继承了RuntimeException,大部分这类错误是由于程序逻辑问题,而不是无法预测或防止的问题。
- 如果不想处理异常,可以把他duck(抛掉throws),如果连main()也duck的话可以编译成功,但运行时虚拟机会中断。
- 异常处理规则:
- catch和finally不能没有try;
- try一定要有catch或者finally;
- try和catch之间不能有程序;
- 没有catch时方法必须声明异常。
void go() throws FooException{ try{ x.doStuff(); }finally{} }
- finally不能在没有try块的情形下出现
11 | 内部类
- 内部类:嵌套在一个类内部的类。
- 内部类可以使用外部所有的方法与变量,就算是私用的也一样。
- 内部类可以从外部类中初始化内部类,此内部对象会绑在外部对象上。
12 | 序列化和文件的输入/输出
1.序列化与反序列化
- 序列化(Serializable):将对象状态进行存储,将内存中的东西保存到文件中(转换为字节序列的过程)。
//1.创建出FileOutputStream, 如果文件不存在会被自动创建 FileOutputStream fileStream = new FileOutputStream("MyGame.car"); //2.创建ObjectOutputStream ObjectOutputStream os = new ObjectOutputStream(fileStream); //3.写入对象 os.writeObject(characterOne); os.writeObject(characterTwo); os.writeObject(characterThree); //4.关闭ObjectOutputStream os.close();
- 反序列化(Deserialization):把字节序列恢复为Java对象的过程。
//1.创建出FileInputStream, 如果文件不存在会抛出异常 FileInputStream fileStream = new FileInputStream("MyGame.car"); //2.创建ObjectInputStream ObjectInputStream os = new ObjectInputStream(fileStream); //3.读取对象 Object one = os.readObject(); Object two = os.readObject(); Object three = os.readObject(); //4.转换对象类型 GameCharacter elf = (GameCharacter) one; GameCharacter troll = (GameCharacter) two; GameCharacter magician = (GameCharacter) three; //5.关闭ObjectInputStream os.close();
- 如何序列化?实现Serializable接口。 Serializable接口没有方法需要实现,唯一目的是声明该类能够被序列化。
- 序列化程序会将对象版图上的所有东西存储起来。被对象的实例变量所引用的所有对象都会被序列化。若一个类中包含对另一个类的引用,则另一个类也要实现Serializable接口,否则序列化失败。因此序列化是全有或者全无的。
- 如果某实例变量不能或不应该被序列化,就把他标记为transient(瞬时)的(返回null)。
- 不能序列化的父类可以有可序列化的子类;可序列化的父类的子类会自动可序列化而不用具体声明。
- 静态变量不会被序列化,因为static代表的是每个类一个,当对象被还原时,静态变量会维持类中原本的样子,而不是存储时的样子。
- 读取对象的顺序必须与写入对象的顺序相同。
- readObject()的返回类型是Object,因此反序列化回来的对象还需要转换成原来的原型。
- tip:每当对象被序列化时,该对象会被盖上一个类的版本识别ID(serialVersionUID)。
2. File对象
- file对象代表文件的路径而不是文件本身。
- 可以对File对象做的事:
3.缓冲区
- 使用缓冲区效果更好:
BufferedWriter writer = new BufferedWriter(new FileWriter(afile));
- BufferedWriter可以暂存一堆数据,到满的时候再实际写入磁盘,这样就可以减少对磁盘操作的次数。
- 强制缓冲区立即写入:write.flush();
4.文件的读取
- 代码如下:
13 | 集合与泛型
0.整体结构
- 注意:map并没有继承collection。
1. Set:注重独一无二
- TreeSet:有序,元素唯一,使用该集合存储自定义对象必须实现Comparable接口或者实现Comparator的比较器将其加入TreeSet构造函数的参数列表。调用无参构造函数:用compareTo()来排序。
- HashSet:不保证元素有序性,元素唯一,快速寻找相符元素。
2.Map:用key来搜索的专家
- LinkedHashMap:类似HashMap,但可以记住元素的插入顺序,也可设定成元素上次存取的先后顺序来排序。
- HashMap:key-value结构,key不可重复。
3.List:对付顺序的好帮手
- LinkedList:针对频繁在元素之间插入或删除元素所涉及的高效率集合。(实际还是ArrayList比较实用)
4.排序
- Collection.sort()会把list中的String按照字母排序。
- 那么问题来了,如果不是String是其他对象呢?如何排序?查阅API文档会发现,sort()大量用到了泛型。您肯定要问泛型了吧?那我们来说说泛型。
5.泛型
-
泛型更安全:(几乎只有集合才会真的需要泛型)
-
运用泛型的方法:
-
以泛型的观点来说,extend代表extend或implement。
-
要实现对对象的排序,就必须实现comparable。只有在实现comparable的情况下才能把ArrayList<对象>传给sort()方法。没有为什么,这个方法就是这么声明的。
-
而Comparable只有一个方法需要实现:CompareTo(T o)。此方法的对象必须要辨别在排序位置上他自己是高于、低于或相等于所传入的对象。如果我有自己的排序规则那么CompareTo方法就不是那么适用了。
-
因此,为了满足我们变态的多样性需求,我们可以使用自制的Comparator。
-
总结:排序规则如下:
- 实现Comparable接口。调用sort(List mylist)方法上的**compareTo()**方法来决定顺序。
- 无需实现Comparable接口。调用sort(List mylist,Comparator c)方法,如此,会使用Comparator的**compare()**方法。
即:如果传Comparator给sort()方法,则排序由Comparator来决定。
-
做个习题:
-
tip:如果将方法声明成取用ArrayList, 它只会取用ArrayList参数,ArrayList和ArrayList都不行。
6.对象的等价
-
如果俩对象相等要满足的条件:
- a.equals(b) ==>true
- a.hashCode() == b.hashCode()
-
HashSet()如何检查重复?
当把对象加入HashSet时,会使用hashcode来判断对象加入的位置,如果有相同的hashcode(),则会继续判断equals()方法来检查是否真的相同。 -
两个对象的hashcode()相等,他们不一定相等;但若两个对象相等,则hashcode一定是相等的。原因:hashcode使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。因此,hashcode只能用来缩小寻找成本,最后还是要用equals()才能认定是否是相同对象。
-
equals()的默认行为是测试两个引用是否对heap上的同一个对象(即==)。
-
tip:
- 数组的类型检查:运行期间
- 集合的类型检查:编译期间
7.万用字符:
- ArrayList<? extends 父类>:在使用带有<?>的声明时,只能操作元素,不能新增集合元素。