参考:《Java核心技术 卷I》
第1章
主要对java语言有了一个大体的介绍,包括发展史、语言特点等。
C++与java面向对象的高级程序语言,不同点在于:
(1)多继承(java实现多个接口,继承单个父类;C++支持多继承);
(2)Java自动内存管理(JVM垃圾回收机制),C++手动销毁对象(析构函数~ delete对象);
(3)java支持向前引用(所谓向前引用,就是在定义类、接口、方法、变量之前使用它们),C++先声明(头文件)再使用;
(4)goto语句,C++支持(java不支持)
(5)引用与指针。Java不能直接操作对象本身,通过引用作为句柄访问对象,获取成员变量的属性方法。C++中存在引用,指针,都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行加减运算。
第2章
学习了java开发环境(什么是JDK与JRE、JVM?)
JDK,全称Java Development Kit,即Java程序开发工具包;包含了JRE,Java API类库和Jvm;
JRE,全称Java Runtime Environment,即Java运行时环境,包括Java API子集和Jvm;
JVM,全称Java Virtual Machine,即Java虚拟机,java实现跨平台(一次编译,多处运行)的基础;
搭建java开发环境,jdk下载安装-配置环境变量-用txt写个demo,使用javac编译成.class文件,用java指令运行输出结果;
安装IDE:eclipse或IDEA, 打开了下jdk安装路径下的src.zip,浏览了部分jdk类及其源码;
第3章
学习了java的类及方法的使用、方法的定义、主函数的写法、类名命名规范等,都与C++有一定的不同。
java的8个基本数据类型,大小都是固定的,区别于C++(无unsigned修饰,无int32_t、uint32_t之类的类型);
类型 | 大小 |
---|---|
byte | 1 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
char | 1 |
boolean | ? |
每个基本类型对应一个包装类,在java.lang包里面能找到每个基本类的包装类;自动装箱:例如Integer integer = 10,自动拆箱:int i = integer;
计算时的自动转型:Java表达式转型规则由低到高转换(例如int 到 double):
1、所有的byte,short,char型的值将被提升为int型,与小数的计算都会转成double(除非小数后有F/f);
2、如果有一个操作数是long型,计算结果是long型;
3、如果有一个操作数是float型,计算结果是float型;
4、如果有一个操作数是double型,计算结果是double型;
5、被fianl修饰的变量不会自动改变类型,当2个final修饰相操作时,结果会根据左边变量的类型而转化。
关于浮点数的精度,尝试打印:System.out.println(2.0-1.1),输出0.8999999999999999,因为二进制无法精确计算1/10的大小,正如10进制下的1/3。
因此在保证精度的条件下,使用java.math.BigDecimal类;在循环中,因为精度问题,判断两个浮点数的大小关系(是否相等)要格外小心!
第4章
学习了java面向对象的编程思想,方法在类里面定义,通过类的构造器new(实例化)对象,以对象为句柄调用类中定义的方法。方法定义在类内部,和C++的写法不同;
自定义一个类,从静态域(包括:静态块static{...}、静态变量、静态方法,可视为类的属性,不属于某个对象“私有”,可以通过类名或对象名作为句柄访问),构造函数(可以重载,互相调用使用this关键词),成员变量,到每个方法;
与static相关的:单例模式、工厂模式(不使用new对象的方式就可以返回/生成某个类的实例)
面向对象特性:继承、多态、封装;
封装:留一个方法名,集合访问控制符,隐藏类中不必要公用的成员和方法的具体实现,避免其他对象对本类成员变量的机制访问,如使用set/get方法、对变量加private修饰;将方法和属性放在类中,将多个相似或关联性强的类放入同一个包(类似树状管理),达到封装的目的。通过访问控制符控制变量/方法、类/包的访问权限;
访问控制符 | 同一个类 | 同一个包 | 非同包子类 | 非同包非子类 |
---|---|---|---|---|
private | √ | |||
protected | √ | √ | √ | |
(default) | √ | √ | ||
public | √ | √ | √ | √ |
继承/多态:覆盖父类方法/实现父类接口,多定义接口,让子类去实现接口,减少代码之间的耦合,让每一个类有明显的独立的功能,不要把一个类设计的太“臃肿复杂”;在方法的返回类型或方法形参的类型上,使用父类接口,让子类重写/实现父类/接口定义的方法,体现多态,增强代码的灵活性(复用);
重载:同一个类下的方法,方法名相同,传递参数的数量、顺序、类型至少有一个不同;返回类型不同或者访问控制符不同,不能称为重载,会发生编译错误。
关于方法参数传递,值传递和引用传递,对于基本类型,作为形参传入,在方法内修改形参不会影响原有的值(实参);对于参考类型,可以修改对象的属性,但是在方法内,形参=new新对象,修改这个新对象不会影响原有的对象(实参);
初始化块和静态块:eg:
class Root {
// 静态块
static {
System.out.println("Root的静态初始化块");
}
// 初始化块
{
System.out.println("Root的普通初始化块");
}
// 无参构造方法
public Root() {
System.out.println("Root的无参构造函数");
}
}
class Mid extends Root {
// 静态块
static {
System.out.println("Mid的静态初始化块");
}
// 初始化块
{
System.out.println("Mid的普通初始化块");
}
// 无参构造方法
public Mid() {
System.out.println("Mid的无参构造函数");
}
// 有参构造方法
public Mid(String msg) {
this();
System.out.println("Mid的带参构造函数,其参数值为:"+msg);
}
}
class leaf extends Mid {
// 静态块
static {
System.out.println("leaf的静态初始化块");
}
// 初始化块
{
System.out.println("leaf的普通初始化块");
}
// 无参构造方法
public leaf() {
System.out.println("leaf的无参构造函数");
}
}
/**
* 测试类
**/
public class InstanceInitTest {
public static void main(String[] args) { // TODO Auto-generated method stub
new leaf();
System.out.println("-----");
new leaf();
}
}
【输出】:
root的静态初始化块
Mid的静态初始化块
leaf的静态初始化块
root的普通初始化块
root的无参构造函数
Mid的普通初始化块
Mid的无参构造函数
leaf的普通初始化块
leaf的无参构造函数
-----
root的普通初始化块
root的无参构造函数
Mid的普通初始化块
Mid的无参构造函数
leaf的普通初始化块
leaf的无参构造函数
结论:对于static静态块,属于“类的属性”,每个当前类的对象都拥有(共享),类加载机制是从顶层父类层层往下,直到当前类,并且静态块只会初始化一次;初始化块可以看做是与构造器并行的,在构造函数执行前调用,每次new对象都会执行。
this关键字:1.构造器之间的调用,构造器函数名用this;2.作为方法的实参,由于当前类还未完成定义(还未完成定义),用this代替当前类的对象;3.方法内局部变量与成员变量重名,this.+成员变量/方法;
final关键字:1.修饰变量:基本类型为常量,参考类型的引用的地址为常量,且申明时都必须赋初始值;2.修饰类,不能被继承;3.修饰方法,方法不能被重写;
finalize与析构函数:(JVM知识)根据可达性分析,当对象变成(GCRoots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。 一般来说,不需要我们显示的写上.finalize()方法来回收对象,因为java的垃圾回收机制是由JVM自动管理的;但是像一些数据库连接、Socket编程、文件打开等情况,可以覆盖finallize()方法,并在super.finalize()后补上一句close()方法,尽管对象不一定会被回收。
关于java代码的注释,注释是个很好的习惯,在包、类、方法(尤其是方法)需要对变量、方法功能简要的介绍,方便他人的调用(代码维护)。
总结:设计一个类时,首先要确定该类的功能,不要拥有太多的方法和属性,要让一个类负责一个转移的功能,并且保证类的信息(属性、方法)私有;其次,养成对变量初始化的好习惯,对于静态变量,最好做一个注释;不要让一个类和其他类的联系关于紧密(接口隔离原则);类名、变量名、方法名的命名方式一定要参考规范,便于阅读,方法(尤其是涉及算法技巧的)一定要补上一定的代码注释;
第5章
这一章主要讲java中的继承-多态,java不支持多继承,但是可以利用接口实现;
关于继承关系的类型转换,eg:
1.Father f1 = new Son(); // 对,父类引用可以指向子类对象,但是会“丢失方法”,通过该引用不能访问子类中非继承自父类的方法;
2.Son s = (Son)(f1); // 对,属于向下转型,是安全的;
3.Father f2 = new Father(); Son s2 = (Son)(f2); // 错,子类引用不能指向父类对象;
关于继承关系中的方法调用:编译器会对对象的类、方法名以及方法参数匹配,匹配不到或者有多个匹配时,会报错。
动态绑定:现在当前类找该方法,找不到再去找父类,层层往上;JVM的优化,为减少查找开销,JVM有一个专门的“方法表”列出所有方法的签名和实际调用的方法;
final关键字:
1.final修饰的类和方法:阻止继承;
2.final方法(内联方法)会增加方法调用的效率(因为不会再去找他的父类和方法),这一点编译器已经做了优化。
Object类,所有类的父类;
方法:
toString();
equals();
hashCode();
wait();
notify();
notifyAll();
getClass();
clone();
Array与ArrayList区别,数组和Object[],数组长度固定,访问查找速度快;ArrayList长度可变,可以扩容,JDK8中根据grow()方法和Arrays.copyOf()方法,按照1.5倍原来大小扩容;
反射是java中的一个重要知识点,涉及web里面常用的JavaBean、工厂模式等;
相关方法:.getClass()、Class.forName()、.Class()方法;
通过获取类的信息(得到Class类对象),可以生成类的实例化对象;通过判断方法、属性是否可访问,可以调用方法、查看属性等;通过Method的invoke()方法,可以调用该方法(需要传入参数:当前类的一个实例+方法参数数组...)。eg:
package com.Minghao.study;
//父类:Person类
class Person {
/* 成员变量 */
public String name; // 姓名
public int age; // 年龄
/* 构造方法 */
public Person() { super(); }
public Person(String name, int age) { super(); this.name = name; this.age = age; }
/* 打印方法 */
public String showInfo() { return "name=" + name + ", age=" + age; }
}
interface Study { // ...仅为了演示获得接口,就没有写抽象方法 }
//子类:Student类
public class TestReflect_Student extends Person implements Study {
/* 成员变量 */
public String className;
private String address;
/* 构造方法 */
public TestReflect_Student() { super(); }
public TestReflect_Student(String name, int age, String className, String address) {
super(name, age);
this.className = className;
this.address = address;
}
public TestReflect_Student(String className) { this.className = className; }
/* 打印方法 */
public String toString() { return "姓名:" + name + ",年龄:" + age + ",班级:" + className + ",住址:" + address; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
}
【测试】:
package com.Minghao.study;
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method;
public class Test_Reflect_01 {
public static void main(String[] args) throws InstantiationException, ReflectiveOperationException {
Class stu = null;
try {
stu = Class.forName("com.Minghao.study.TestReflect_Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 获取对象的所有公有属性。
Field[] fields = stu.getFields();
for (Field f : fields) {
System.out.println(f);
}
// 获取对象所有属性,但不包含继承的。
Field[] declaredFields = stu.getDeclaredFields();
for (Field ff : declaredFields) {
System.out.println(ff);
}
// 获取对象的所有公共方法
Method[] methods = stu.getMethods();
for (Method m : methods) { System.out.println(m); }
// 获取对象所有的公共构造方法
Constructor[] constructors = stu.getConstructors();
for (Constructor c : constructors) {
System.out.println(c);
}
//获取对象全限定名称
System.out.println(stu.getName());
// 获取包名
System.out.println(stu.getPackage());
// 获取该类实现的所有接口
Class[] interfaces = stu.getInterfaces();
for (Class in : interfaces) { System.out.println(in); }
// 第一种方法,实例化默认构造方法,调用set赋值 TestReflect_Student stu1 = (TestReflect_Student) stu.newInstance(); stu1.setAddress("深圳南山"); System.out.println(stu);
// 第二种方法 取得全部的构造函数 使用构造函数赋值 Constructor<TestReflect_Student> constructor = stu.getConstructor(String.class, int.class, String.class, String.class); TestReflect_Student stu2 = (TestReflect_Student) constructor.newInstance("李四", 18, "七班", "深圳"); System.out.println(stu2);
/* 获取方法并执行方法 */
System.out.println("获取方法并执行方法: ");
Method show = stu.getMethod("showInfo"); //获取showInfo()方法
Object object = show.invoke(stu2); //调用showInfo()方法
System.out.println(object);
}
}
异常,区别于Error,属于Throwable的子类,分为已检查异常和未检查异常,前者编译器会提醒强制加上try-catch捕获异常,后者涉及太多故编译器不检查,可以自己来try-catch;
总结:根据继承的特性,在定义类及其子类的时候,把公共的属性/方法(抽象方法)放在父类;不要“为了继承而继承”,看清楚当前的两个类是否满足“is-a”的关系,不要因为“功能相似”就使用继承;方法的覆盖都是要有意义的,不要对原来的方法做太多的修改(或者可以定义一个新方法);
第 6 章节
这一章主要讲java的高级特性——接口。接口是类抽象出来的,它更像是一种规范,而实现接口的类必须遵守这些规范(继承属性、实现方法)。
接口/抽象类?
特点:abstract/interface-extends/implements;Is A/Like A 而这都不能被实例化;接口比抽象类“更抽象”,抽象类至少包含一个抽象方法(无方法体)或是一个空类(无内容);而接口的方法全是public abstract(隐式)抽象的(实现该方法时候需要显示的声明方法是public的),变量全是public; static final(隐式)的;
注意:JDK8中允许接口中定义完整的static方法,以前的版本,静态方法往往放在接口的伴随类中(如Collection接口/Collections类中有大量的静态方法),而JDK8中就没有专门定义接口的伴随类的必要了。
关于使用:
A.抽象类:子类与子类之间有共同的父类方法,子类选择自己所感兴趣的方法来覆盖/重写,该方法写在抽象类中,避免每个子类再去写一遍;子类与子类之间不同的方法作为抽象方法,在抽象类中定义。一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能(如在模板方法设计模式中,利用hook()钩子方法);
B.接口:类与类之间需要特定的接口进行协调,而不在乎其如何实现(减小类之间的耦合,方便管理个各类直接的关系);作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识,如序列化接口:Serializable;
接口如何使用?
继承接口(可以多继承implements.,...),实现抽象方法;eg:有一个类A的数组A[],为了利用Arrays.sort()方法,则要求class A实现Comparable接口(int compareTo(Object o)方法);
关于是否有必要实现接口的思考?
JDK中定义了很多接口、类、方法,接口的方法只提供了方法名,为了使用JDK的方法,需要当前的类继承某个接口(使用某个方法可能对对象的类型有限制,如上面的Arrays.sort()方法);与其实现接口的方法,为何不自己在定义的类里面自己定义方法呢(可以在自己的方法里面调用JDK方法)?认为是为了方便类的扩展,减少代码的冗余,让代码尽量规范。
默认方法?(JDK8)
以前的接口只能有public static的抽象方法,现在可以有“定义好的方法-有方法体”,但是用default修饰; 这样可以在修改接口时,不影响已经实现接口的子类的编译(否则,需要子类实现新的方法);同时继承父类和实现接口时,若里面有相同的方法(访问控制符,方法名,返回类型,方法参数都一样),则“类优先”;若是同时实现两个接口,有相同的默认方法时,则需要子类重写这个方法(否则编译器不知道要继承哪个方法);同时也不允许一个父接口有default方法而另一个父类只有abstract修饰的相同的方法;
回调方法(函数)?同步/异步回调?
回调(callback)就是Class A通过Class B的对象来调用自身的函数(需要利用接口A_interface):有两个类A、B,一个A的父类接口A_interface;接口A_interface A定义了方法doA-Inf();A的成员变量里面有B的对象(构造器传进去),A的某个方法doA(B b)需要b对象执行方法doB(A_interface A){A.doA-Inf();...};过程就是,A对象执行doA(B b),让b执行doB(this);doB(this)方法内调用了Class A实现接口A_interface的具体方法;
举个例子:假如以买彩票的场景来模拟,我买彩票,调用彩票网,给我返回的结果确定是否中奖,同步回调就是,我买了彩票之后,需要等待彩票网给我返回的结果,这个时候我不能做其他事情,我必须等待这个结果,这就叫同步回调, 同步, 就意味着等待, 我不能去做其他事情, 必须等待, 异步回调就是, 我买了彩票之后, 可以去做其他事情, 然后当彩票网有了结果和消息, 再给我返回消息。
“同步”指的是:A有很多事情,他在调用doA()方法后必须等b对象把事情做完(做完后调用“回调函数”通知A)才能做剩下的事;
“异步”指的是:A告诉b做某件事后,不用等b做完就可以做剩下的事,等b做完时自然会“通知”A;(利用多线程,开启线程去做doA()方法;)
异步调用是为了解决同步调用可能出现阻塞, 导致整个流程卡住而产生的一种调用方式. 类A的方法方法a()通过新起线程的方式调用类B的方法b(), 代码接着直接往下执行, 这样无论方法b()执行时间多久, 都不会阻塞住方法a()的执行.但是这种方式,由于方法a()不等待方法b()的执行完成,在方法a()需要方法b()执行结果的情况下(视具体业务而定,有些业务比如启异步线程发个微信通知、刷新一个缓存这种就没必要), 必须通过一定的方式对方法b()的执行结果进行监听. 在Java中, 可以使用Future+Callable的方式做到这一点。
clone()?浅拷贝和深度拷贝?
clone()是Object下的方法,对于加入一个类A的成员变量有基本类型和参考类型,实现Cloneable接口实现clone()方法(调用 (A)(super.clone())),只能复制基本类型的变量,称为“浅复制”,参考类型变量没有复制;需要在clone()方法自己写上如何复制参考类型变量(或让参考类型变量所在的类也实现Cloneable接口,直到内部不含有参考类型的成员变量),达到“复制对象/深度复制”的效果;
内部类?
定义在类的内部,类似一个类的成员变量一样,作用于在类的内部,访问权限和类的方法一样,new内部类的对象必须通过外部类对象作为句柄调用Out.Inner int = outter.new Inner();
为什么要用内部类?访问权限为本类所有变量(包括private);对同包其他类隐藏;减少代码量(匿名内部类 new 接口名(){实现接口方法...},不用再去完整的定义一个类)。
内部类类型:一般内部类(相当于外部类的成员);静态内部类(类似静态方法,只能访问外部类的静态域);局部内部类(相当于方法,调用的方法的传递参数/方法内变量时,该变量为final(隐式));匿名内部类(实现了接口的某个类的对象,new 接口名(){实现接口方法...});
*接口的应用之一——代理?代理模式(初步学习)?动态代理?
代理就是,产生当前对象的一个“代理对象”,这个代理对象可以过滤对真实对象的方法的访问,或者“丰富”真实对象的方法; 使用到的原理是反射:通过已有对象,得到这个对象所在的类信息(.getClass()),通过类信息“反射”得到当前对象(目标对象)的“代理对象”;或者,也可以根据 类名.class/Class.ForName("")得到目标对象的类信息; 三种代理模式:静态代理;JDK动态代理;CBLIB动态代理(JDK代理需要目标类实现某接口,没有接口的类需要做动态代理的话则只能用CGLIB了,需要导入相应的jar包)