从大学课程设计、毕业设计到工作中的一些小功能调整,自己的Java水平一直是会点语法+面向搜索引擎编程级别。想要更加深入地学习Java的东西,但是Java圣经又太厚,自己也不是特别擅长看这些技术书籍的人,在别人的建议下选了《Head First Java》来看。
纵观全书,这本书的重点在Java的基础语法、对象、多态与继承等概念上的讲解(讲得挺好的),对于一些异常处理、线程、IO、网络方面更多是简单的介绍,并没有深入讲解。
原本想通过这本书学习线程方面的概念,为阅读其他更加深入讲解线程方面的文章打下基础,但是似乎无法达到目的。
尽管如此,我还是通过阅读这本书学习的到了一些新的东西。这篇笔记,就是为了整理记录这些新学到的知识,以巩固我对这些知识的掌握。
对象的声明、创建与赋值
当我们声明一个非基本数据类型的变量时,我们通常这样写:
Dog myDog = new Dog();
在这简单的一行代码中,其实包含了三个步骤:
// 1. 声明一个Dog类型的引用变量
Dog myDog
// 2. 创建Dog对象
new Dog
// 3. 将创建的Dog对象,赋值给myDog这个引用变量
Dog myDog = new Dog();
所以,当我们声明对象数据的时候,实际上是声明了该对象的引用变量数据。
对象的生存空间
在Java虚拟机驱动的时候,会从底层操作系统获得一块内存来执行Java程序。在内存中,要关注这两块区域:对象的生存空间堆和方法调用及变量生存的空间栈。
- 堆又被称为可垃圾回收的堆,一旦对象失去了引用,就有可能被回收。
- 实例变量是被声明在类而不是方法中,所以实例变量存在于所属的对象中(堆中)。
- 而局部变量则是被生命在方法中,所以局部变量存在于栈中。
赋值与引用的例子
Book a = new Book();
Book b = new Book(); //这里有两个引用变量,两个对象
Book c = a; //此时有三个引用变量,两个引用变量,c与a指向同一个对象
b = a; // 此时b也与a指向同一个对象,b原本指向的对象失去了引用,处于可回收状态
继承与多态
- 子类会继承父类除了private的所有实例变量和方法。
- 运用多态时,引用类型可以是实际对象类的父类;参数和返回类型都可以多态。
Animal[] animals = new Animal[5];
animals[0] = new Dog();
animals[1] = new Cat();
Class Vet{
public void giveshot(Animal a){
a.makeNoise();
}
Vet vet = new Vet();
v.giveshot(new Dog());
}
- 父类的方法可以在子类中被覆盖,但是标记了final的方法无法被覆盖。
- 一个类只能继承一个父类。但是继承可以是多层继承。B继承A,C继承B,则C也是A的子类。
- 所有对象都是Object的子类,可以用Object来实现多态,但是一般不这样做。
Object o = new Dog();
int i = o.hashCode(); // 可行,因为Object本身有hashCode()方法
o.makeNoise(); // 不可执行,因为此时o的引用类型是Object,Object没有makeNoise()方法,无法执行。
Animal a = (Dog) o;
a.makeNoise(); // 可行,当o赋值给a时进行了类型转换,Dog是Animal的子类,所以可以赋值成功。Animal有makeNoise()方法,所以可以执行
- 当一个类执行构造函数时,会先执行完父类的构造函数。如果存在多层继承,就会一直到最初的父类执行完构造函数,才一层一层向下执行。
- 在子类的构造函数中执行父类的构造函数:
public Dog(String name){
//调用父类的构造函数
super();
}
- 当子类的构造函数中没哟调用super()时,编译器在编译时在我们编写的构造函数中添加super();
- 当我们想重载一个构造函数,但是又调用到这个构造函数时:
Class Dog{
String name;
public Dog(){
// 调用了有一个String参数的构造函数
this("myDog");
}
public Dog(String name){
//调用父类的构造函数
super();
}
}
- 在构造函数中,this()和super()都要存在于构造函数的第一行,不得同时存在。
接口与抽象类
- 抽象类没有实体,抽象类中的方法也没有方法体。
- 接口类是抽象类,抽象类不一定是接口类。
- implements接口后,要实现该接口类的所有方法。
- extends只能extend一个父类,implements可以implement好多个接口。
静态方法
- 非静态方法需要有实例才能调用,静态方法不需要实例,直接以类名就可以调用。
// 非静态方法
Dog d = new Dog();
d.makeNoise();
// 静态方法
Math.min(3,7);
- 静态方法中没有实例变量,也不允许调用非静态的变量。
- 静态方法不允许调用费静态的方法。
- 静态变量是同一个类所有实例共享的。每一个实例都有一个属于自己的实例变量。但静态变量是每个类一个。这里可能会有多个实例对同一个静态变量同时进行修改的问题。
- 静态变量会在该类有任何静态方法执行之前就初始化。
- final的变量代表值无法被改变,final的方法代表无法被覆盖,final的类代表无法被继承。以下例子来自java提高篇(十五)-----关键字final
public class Person {
private String name;
Person(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class FinalTest {
private final String final_01 = "chenssy"; //编译期常量,必须要进行初始化,且不可更改
private final String final_02; //构造器常量,在实例化一个对象时被初始化
private static Random random = new Random();
private final int final_03 = random.nextInt(50); //使用随机数来进行初始化
//引用
public final Person final_04 = new Person("chen_ssy"); //final指向引用数据类型
FinalTest(String final_02){
this.final_02 = final_02;
}
public String toString(){
return "final_01 = " + final_01 +" final_02 = " + final_02 + " final_03 = " + final_03 +
" final_04 = " + final_04.getName();
}
public static void main(String[] args) {
System.out.println("------------第一次创建对象------------");
FinalTest final1 = new FinalTest("cm");
System.out.println(final1);
System.out.println("------------第二次创建对象------------");
FinalTest final2 = new FinalTest("zj");
System.out.println(final2);
System.out.println("------------修改引用对象--------------");
final2.final_04.setName("chenssy");
System.out.println(final2);
}
}
------------------
Output:
------------第一次创建对象------------
final_01 = chenssy final_02 = cm final_03 = 34 final_04 = chen_ssy
------------第二次创建对象------------
final_01 = chenssy final_02 = zj final_03 = 46 final_04 = chen_ssy
------------修改引用对象--------------
final_01 = chenssy final_02 = zj final_03 = 46 final_04 = chenssy
异常处理
- finally块中的代码无论有无异常都会执行。
- 异常也是多态的,Exception是所有异常的父类。
- 异常可以throws 也可以 try/catch。
- 可以为每个不同的异常编写不同的catch块,但是要注意子类要放在父类之前catch
try{
// do somethings
} catch (DogException de){
//deal DogException
} catch (AnimalException ae){
//deal AnimalException
}
// 如果AnimalException的catch块在DogException之前,那么DogException也会被AnimalException的catch块捕获,就不会落到后面的DogException的catch块了。
内部类
- 内部类可以使用外部类的所有方法和变量,包括标记为private的。
- 内部类的实例一定会绑定在外部类的实例上。
- 内部类的其中一个适用场景:一个界面,需要监听多个按钮点击事件并且不同按钮的点击事件有不同的响应。
IO
IO这一章简单地讲了一下文件操作,着重讲了序列化的内容。
其实这章序列化说得不好,建议看这里Java 序列化的高级认识
- 序列化程序会对象相关的所有东西都存储起来,被对象的实例变量所引用的所有对象都会被实例化。
- 如果要让类能够被序列化,必须实现Serializable
- 如果某实例变量不能/不应该被序列化,就把他标记为瞬时的(transient),这样序列化时候,程序会将它跳过。到了解序列化的时候这个引用变量会被置为null。
- 解序列化的时候,所有的类都必须让JVM找到。
- 解序列化时,新的对象会被分配到堆上,但构造函数不会执行。
- 如果对象在继承树上的有不可序列化的祖先类,则该不可序列化类以及在它之上的类的构造函数(就算是可序列化的类)就会执行。从第一个不可序列化的父类开始,之上的类都会回到初始状态。
- 静态文件不会被序列化。
- 序列化标志:如果在解序列化之前,类已经发生了修改,可能会导致解序列化失败。
会损害序列化的修改:
- 删除实例变量
- 改变实例变量的类型
- 将非瞬时的实例变量改为瞬时的
- 改变继承的继承层次
- 将类从可序列化改为不可序列化
- 将实例变量变成静态的
较为安全的修改:
- 加入新的实例变量(还原时取默认值)
- 在继承层次中加入新的类
- 在继承层次中删除类
- 将实例变量从瞬时改为非瞬时
- 一致的序列化ID有利于保证反序列化的成功。
网络通信与多线程
- 服务器与客户端通过socket连接来沟通。
- 当ServerSocket接收到请求时,会在另外的一个端口做一个socket连接来处理客户端的请求。
- 线程代表独立的执行空间。
- 多线程同时执行时,实际上是多个线程随机轮流执行的。
- 线程进入可执行状态时,会在执行中和可执行这两种状态中切换。但是也可能进入堵塞状态。堵塞状态可能是闲置、等待其他县城完成、等待串流数据、等待被占用的对象等原因引起的。
- 构造线程时需要传入一个任务对象,这个任务对象需要实现Runnable接口。
- 并发:不同线程对同一个对象同时进行处理,可能引起问题。
- 锁:要让对象在线程上有足够的安全性,就要对不可分割执行的指令上锁(同步化)。
- 如果线程尝试进入同步化的方法,必须取得对象的钥匙如果钥匙被别的线程拿走了,线程只能等待。
-
如果两个线程互相持有对方正在等待执行的方法的钥匙,就会发生死锁。
集合与排序
- 常见的集合类型,有序的集合中的元素必须是可比较的,Comparable的:
- TreeSet:有序且防止重复的集合。
- HashMap:Key-Value集合,Key不可重复
- HashSet:防止重复的集合,可快速找到相符元素
- LinkedList:针对经常插入或者删除中间元素所涉及的高效率集合(不如ArrayList实用)
- LinkedHashMap:可记住元素插入顺序,可设定依照原宿上次存储先后来排序的HashMap。
- ArrayList中的sort()方法,可进行已实现了Comparable接口的类的排序,也可以使用实现了Comparator接口的内部类来进行排序。
// 实现Comparable接口
class Song inplements Comparable{
public int compareTo(Song s){
return title. compareTo(s.getTitle());
}
}
// 实现Comparator
class Song{
class ArtistCompare implements Comparator{
public int Compare (Song one, Song two){
return one.getArtist().compareTo(two.getArtist());
}
ArtistCompare artistCompare = new ArtistCompare();
Collections.sort(songList, artistCompare);
}
}
- 如果equal()被覆盖过,hashCode()方法也应该相应覆盖。
- equal()默认行为是执行==的比较,即判断两个引用变量是否引用堆中的同一个对象。如果equals()没有被覆盖过,那么两个对象永远不会被视为相等的。
- a.equals(b)必须与a.hashCode()==b.hashCode()等值,但a.hashCode()==b.hashCode() 不一定与a.equals(b)等值。
泛型
//这里的list仅接受ArrayList
public void takeThing(ArrayList list)
//这里的list对象可以接受ArrayList、ArrayList等继承Animal的对象的ArrayList
// 泛型的extends等价于实体类的extends或者implements
public void takeThing(ArrayList list)
//万用字符也可以让ArrayList接受Animal的子类
// 使用万用字符,能够调用list中任何元素的方法,但是不能增加元素。
public void takeAnimals(ArrayList extends Animal> animals){
for(Animal a:animals){
a.eat(); //合法
}
animals.add(new Dog()); //不合法的操作
}
// 第二、第三种写法执行起来是一样的,但是在一般用第二种,因为需要传入多个对象时,第二种方法不需要多次声明
public void takeThing(ArrayList one, ArrayList two)
远端过程调用
远端过程调用的过程:
- 启动RMI registry
- 远程服务被初始化(生成stub和skeleton)
- 远程服务向RMI registry注册
- 客户端查询RMI registry
- 客户端从RMI registry获取stub
- 客户端调用stub上的方法
- stub将方法的调用送到服务器上
- 启动服务前应先启动注册器
- 远程服务的参数和返回都需要做成可序列化
- Jini--adaptive discovery:自动注册、知道接口名称就可以自动适配下发stub
- Jini--self-healing networks:通过续约的方式确定服务的状态,超过续约时间不进行续约就会任务该服务已离线。
碎片知识
- 数据隐藏:将成员变量标记为private,将getters、setters标记为public。
- 实例变量总会有默认值:无论有没有明确赋值或者调用setter,实例变量总会有默认值。
integer --- 0
floating point --- 0.0
boolean --- false
reference --- null
- ==:使用==来比较两个基本数据类型或判断两个引用变量是否指向同一个对象。==可以用来比较任何类型的两个变量,因为它只是比较其中的字节组合。
- equals():使用equals()来判断两个对象是否在意义上向相等
int a = 3;
byte b =3;
if(a == b){
// true
}
Foo c = new Foo();
Foo d = new Foo();
Foo e = c;
if(c == d){
// false
}
if(c == e){
// true
}
if(c.equals(d)){
// true
}
- x++与++x:x++先执行x+1,再执行赋值;++x先执行赋值,再执行x+1
- 长运算符(| &)与短运算符(|| &&):
- 在&&表达式中,左右两边都为true这个表达式返回true,当左边返回false时,JVM不会执行右边的计算就直接返回false;
- 在||表达式中,左右两边都为false这个表达式返回false,当左边返回true时,JVM不会执行右边的计算就直接返回true;
- & 和 | 在boolean表达式会强制JVM执行两边的运算,但一般长表达是用在位运算中。
- 当一个类没有构造方法时,编译器再编译时会默认加上一个无参的构造方法。但是如果一个类已有一个构造方法,则编译器不会再加上无参的构造方法。
-
格式化说明
需要拓展的知识点
- 位运算:移位运算参考:JAVA移位操作符你真的懂吗?
// 位非 ~
int x = 10; //00001010
x = ~x; //11110101
// 位与 & 、位或 |、位异或 ^
int x = 10; //00001010
int x = 6; //00000110
// 位与 &: 两位都是1返回1,否则返回0
int a =x&y; //00000010
// 位或 |: 有一位为1就返回1,否则返回0
int a =x&y; //00001110
// 位异或 ^: 位相同返回1,否则返回0
int a =x&y; //11110010
// 移位运算 左移<<,右移>>,无符号右移>>>,需要结合数据类型来看。
int x = -11; //11111011
// 左移 1位,等于值*2,向左边移动,并且在低位补0.
int a= x<<1; //11110110
// 右移1位,等于值/2,带符号右移,若左操作数是正数,则高位补“0”,若左操作数是负数,则高位补“1”.
int a= x>>1; //1111101
// 无符号右移,无论左操作数是正数还是负数,在高位都补“0”
int a= x>>>1; //0111101
- 不变性:String类型和包装类都是不可变的。创建后就值就不可以改变。JVM中有一个String Pool,不受GC的影响。如果新建的String引用变量的值在String Pool中有相同值的对象,会直接引用这个对象,而不是新建一个String对象。
- 断言:执行时没有特殊设置JVM会自动忽略断言,运行时打开JVM的断言设置,则可以在不影响任意代码的前提下进行除错。
- 静态嵌套类:可以在没有外层实例的情况下使用的类。
public class Outer{
static class Inner{
void do(){
// do somethings
}
}
class Test{
public static void main (String args[]){
Outer.Inner a = new Outer.Inner();
a.do();
}
}
}
- 非静态的嵌套类是通常成为内部类。
- 匿名内部类:
button.addActionListen(new ActionListen{
public void actionPerformed(ActionEvent e){
// do somethings
}
})
- default与protected:default-同一个包内可存取,protected-允许不同包的子类继承它的成员。
-
多维数组:int[][] a2d = new int[4][2]>>这里由5个数组组成。
- 枚举ENUM:Java 枚举(enum) 详解7种常见的用法