进阶Java之路
第一章:类与对象
1.1用类制造对象
面向对象的编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据。
类定义了对象长什么样,对象则是按照类的定义所制出来的实体,一个类可以创建很多对象,每个对象都有自己的数据。
- 对象是实体,需要被创建,可以为我们做事情
- 类是规范,根据类的定义来创建对象
1.2定义类
创建对象
- new Vending Machine();
- VendingMachine v = new VendingMachine()
- 对象变量是对象的管理者
- 上面两句语言的意思是我们创建了一个新的类叫做VendingMachine,然后用这个类创建了一个对象,然后用v区管理这个对象。
让对象做事
- .运算符
- v.insertMoney(10);
- v.getFood();
1.3成员变量和成员函数
成员变量
- 类定义了对象中所具有的变量,这些变量称作成员变量。
- 每个对象都有自己的变量,和同一个类的其他对象都是分开的。
函数与成员变量
- 在函数中可以直接写成员变量的名字来访问成员变量。
- 那么究竟访问的是哪个对象呢?
- 函数是 通过对象来调用的
- v.inserMoney()
- 这次调用临时建立了insertMoney()和v之间的关系,让insertMoney()内部的成员变量指的是v的成员变量。
this
this是成员函数的一个特殊的固有的本地变量,它表达了调用这个函数的那个对象
调用函数
- 通过.运算符调用某个对象的函数
- 在成员函数内部可以直接调用自己(this)的其他函数。
本地变量
- 定义在函数内部的变量是本地变量
- 本地变量的生存期和作用域都是函数内部
- 成员变量的生存期是对象的生存期,作用域是类内部的成员函数
1.4对象初始化
成员变量定义初始化
- 成员变量在定义的地方就可以给出初始值
- 没有初始值的成员变量会自动获取0值
- 对象变量的0值表示没有管理任何对象,也可以主动给null值
- 定义初始化可以调用函数,甚至可以使用已经定义的成员变量
构造函数(构造器)
- 如果有一个成员函数的名字和类的名字完全相同,则在创建这个类的每一个对象的时候会自动调用这个函数,这个函数也被称为构造函数
- 构造函数没有返回值
- 构造函数可以有0个,1个或多个参数
- 构造函数总是伴随着new操作一起调用
函数重载
- 一个类可以有多个构造函数,只要他们的参数表不同
- 创建对象的时候给出不同的参数值,就会自动调用不同的构造函数
- 通过this()还可以调用其他的构造函数
- 一个类里的同名但参数表不同的函数构成了重载关系
第二章:对象交互
2.1对象交互
对象和对象之间的联系紧密程度叫做耦合。对象和对象的耦合程度越紧,表现在源代码上,就是它们的代码是互相依赖、互相牵制的。我们理想的模型,是对象和对象之间的耦合要尽可能的松,平行的对象要尽量减少直接联系,让更高层次的对象来提供通信服务。这些就是在我们这个非常简单的数字钟的例子中所体现的。
Display.Java
package clock;
public class Display {
private int value = 0;
private int limit = 0;
public Display(int limit) {
this.limit = limit;
}
public void increase() {
value++;
if(value == limit) {
value = 0;
}
}
public int getValue() {
return value;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
Clock.Java
package clock;
public class Clock {
private Display hour = new Display(24);
private Display minute = new Display(60);
public void start() {
while(true) {
minute.increase();
if(minute.getValue() == 0) {
hour.increase();
}
System.out.printf("%02d:%02d\n", hour.getValue(),minute.getValue());
}
}
public static void main(String[] args) {
Clock clock = new Clock();
clock.start();
}
}
上述中的代码,表示分钟的对象和表示小时的对象没有直接交互。如果想要做直接交互,让分钟的对象在翻转的时候直接调用表示小时的那个对象的那个increase函数,可以用如下代码:
Display.Java
package jinjie;
public class Display {
private int limit = 0;
private int value = 0;
public Display(int limit){
this.limit = limit;
}
public void increase(Display d) {
value++;
if(value==limit) {
value=0;
if(d!=null) {
d.increase(null);
}
}
}
public int getValue() {
return value;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Display d = new Display(24);
Display m = new Display(60);
while (true) {
m.increase(d);
System.out.printf("%02d:%02d\n",d.getValue(),m.getValue());
}
}
}
2.2访问属性
封装
封装,就是把数据和对这些数据的操作放在一起,并且用这些操作把数据掩盖起来,是面向对象的基本概念之一,也是最核心的概念。
我们有一个非常直截了当的手段来保证在类的设计的时候做到封装:
-
所有的成员变量必须是private的,这样就避免别人任意使用你的内部数据;
-
所有public的函数,只是用来实现这个类的对象或类自己要提供的服务的,而不是用来直接访问数据的。除非对数据的访问就是这个类及对象的服务。简单地说,给每个成员变量提供一对用于读写的get/set函数也是不合适的设计。
private -
只有在这个类的内部可以访问
-
类的内部指的是类的成员函数和定义初始化
-
这个限制是对类的而不是对对象的
-
在访问私有的成员变量时需要用到get/set方法。
public -
任何人都可以访问
-
任何人指的是在任何类的函数或定义初始化中可以使用
-
使用指的是调用、访问或定义变量
2.3包
包
当你的程序越来越大的时候,你就会需要有一个机制帮助你管理一个工程中众多的类了。包就是Java的类库管理机制,它借助文件系统的目录来管理类库,一个包就是一个目录,一个包内的所有的类必须放在一个目录下,那个目录的名字必须是包的名字。
作为初学者,你可以忽略不看包,反正一切靠Eclipse。但是作为一个Java程序员,你不能不懂包。要不然,在使用别人的类库和部署你的程序的时候,会遇到不少莫名其妙的麻烦。
2.4类变量
类变量/类函数
类是描述,对象是实体。在类里所描述的成员变量,是位于这个类的每一个对象中的。
而如果某个成员有static关键字做修饰,它就不再属于每一个对象,而是属于整个类的了。
通过每个对象都可以访问到这些类变量和类函数,但是也可以通过类的名字来访问它们。类函数由于不属于任何对象,因此也没有办法建立与调用它们的对象的关系,就不能访问任何非static的成员变量和成员函数了。
静态方法只能访问静态变量,静态变量能够被所有的方法访问。
第三章:对象容器
3.1顺序容器
容器是现代程序设计非常基础而重要的手段。所谓容器,就是“放东西的东西”。数组可以看作是一种容器,但是数组的元素个数一旦确定就无法改变,这在实际使用中是很大的不足。一般意义上的容器,是指具有自动增长容量能力的存放数据的一种数据结构。在面向对象语言中,这种数据结构本身表达为一个对象。所以才有“放东西的东西”的说法。
Java具有丰富的容器,Java的容器具有丰富的功能和良好的性能。熟悉并能充分有效地利用好容器,是现代程序设计的基本能力。首先学习的是顺序容器,即放进容器中的对象是按照指定的顺序(放的顺序)排列起来的,而且允许具有相同值的多个对象存在。
例子:记事本
设计要求:
- 能存储记录
- 不限制能存储的记录的数量
- 能知道已经存储的记录的数量
- 能查看存进去的每一条记录
- 能删除一条记录
- 能列出所有的记录
接口设计 - add(String note);
- getSize();
- getNote(int index);
- removeNote(int index);
- list();
package notebook;
import java.util.ArrayList;
public class NoteBook {
private ArrayList<String> notes = new ArrayList<String>();
public void add(String s) {
notes.add(s);
}
public void add(String s,int location) {
notes.add(location, s);
}
public int grtSize() {
return notes.size();
}
public String getNote(int index) {
return notes.get(index);
}
public void removeNote(int index) {
notes.remove(index);
}
public String[] list(){
String[] a = new String[notes.size()];
notes.toArray(a);
return a;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
NoteBook nb = new NoteBook();
nb.add("first");
nb.add("second");
nb.add("third", 1);
System.out.println(nb.grtSize());
System.out.println(nb.getNote(0));
System.out.println(nb.getNote(1));
nb.removeNote(1);
String[] a = nb.list();
for(String s:a) {
System.out.println(s);
}
}
}
结果:
容器类(数组列表)
在java 中有一个叫做ArrayList的类,具有自动调节数组容量的功能。Arraylist是一个采用类型参数的泛型类,为了指定数组列表保存的元素对象类型,需要用一对尖括号将类名括起来加在后面。
ArrayList<String> notes = new ArrayLiat<String>();
容器类有两个类型:
- 容器的类型
- 元素的类型
在上一句代码中ArrayList指的是容器的类型,而String指的是元素的类型。
使用add方法可以将元素添加到数组列表中。例如:
notes.add(new String(..,..,..));
如果调用add且内部数组已经满了,数组列表就将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中去。如果已经清楚或者能够估计出数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity方法:
notes.ensureCapacity(100);
这个方法调用将分配一个包含100个对象的内部数组。然后调用100次add,而不用重新分配空间。另外还可以把初始容量传递给ArrayList构造器:
ArrayList<String> notes = new ArrayList<String>(100);
size方法将返回数组列表中包含的实际元素书目,例如:
notes.size();
将返回notes数组列表的当前元素数量,它等价于数组a的a.length。一旦能够确认数组列表当前的大小不再发生变化,就可以调用trimToSize方法。这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目。
访问数组列表元素
数组列表自动扩展容量的便利增加了访问元素语法的复杂程度。例如要设置第i个元素,可以使用:
notes.set(i,harry);
它等价于对数组a的元素赋值:a[i] = harry;
使用下列格式获得数组列表的元素:
String s = notes.get(i);
下面这个技巧既可以灵活的扩展数组又可以方便的访问数组元素。首先创建一个数组,并添加所有的元素。
ArrayList<X> list = new ArrayList<X>();
while(...)
{
x = ...;
list.add(x);
}
执行完上述操作后,使用toArray方法将数组元素拷贝到另一个数组中。
X[] a = new X[list.size()];
list.Array(a);
为了插入一个新元素,位于n之后的所有元素都要向后移动一个位置。如果插入新元素后,数组列表的大小超过了容量,数组列表就会被重新分配存储空间。
notes.add(n,e); //n必须介于0到size()-1之间
同样也可以从数组类表中删除一个元素:
String s = notes.remove(n); //n必须介于0到size()-1之间
3.2对象包装器与自动装箱
所有的基本类型都有一个与之对应的类。例如,Integer类对应着基本类型int。通常这些类成为包装器。这些对象包装器类拥有很明显的名字:Integer,Long,Float,Double,Short,Byte,Character,Void,Boolean(前6个类派生于公共超类Number)。对象包装器是不可变的,一旦构造了包装器就不允许更改包装在其中的值。同时,对象包装器还是final,因此不能定义他们的子类。
包装类的构造方法:
基本类型和包装类之间的对应关系:
包装类的常用方法:
假设想定义一个整型数组列表。而尖括号中的参数类型不允许是基本类型,也就是说不允许写成ArrayList。这里就用到了Integer对象包装器类。我们可以声明一个Integer对象的数组列表。
ArrayList<Integer> list = new ArrayList<>();
装箱:把基本类型转换成包装类,使其具有对象的性质,又可分为手动装箱和自动装箱
int i = 10; //定义一个int基本类型
Integer x = new Integer(i); //手动装箱
Integer y = i; //自动装箱
拆箱:和装箱相反,把包装类对象装换成基本类型的值,又可分为手动拆箱和自动拆箱
Integer j = new Integer(8); //定义一个Integer包装类对象,值为8
int m = j.intValue(); //手动拆箱为int类型
int n = j; //自动拆箱为int类型
3.2对象数组
对象数组
当数组的元素的类型是类的时候,数组的每一个元素其实只是对象的管理者而不是对象本身。因此,仅仅创建数组并没有创建其中的每一个对象!
3.3集合容器
集合容器
集合就是数学中的集合的概念:所有的元素都具有唯一的值,元素在其中没有顺序。
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList<String> a = new ArrayList<String>();
a.add("first");
a.add("second");
a.add("third");
System.out.println(a);
System.out.println("---------");
HashSet<String> s = new HashSet<String>();
s.add("first");
s.add("second");
s.add("first");
System.out.println(s);
}
3.4散列表
散列表(Hash)
传统意义上的Hash表,是能以int做值,将数据存放起来的数据结构。Java的Hash表可以以任何实现了hash()函数的类的对象做值来存放对象。
package notebook;
import java.util.HashMap;
import java.util.Scanner;
public class Coin {
private HashMap<Integer, String> coinnames = new HashMap<Integer, String>();
public Coin() {
coinnames.put(1, "peeny");
coinnames.put(10, "dime");
coinnames.put(25, "quarter");
coinnames.put(50, "half-dolor");
coinnames.put(50, "wumao");
System.out.println(coinnames.keySet().size());
System.out.println(coinnames);
for( Integer k : coinnames.keySet()) {
String s = coinnames.get(k);
System.out.println(s);
}
}
public String getName(int amount) {
if (coinnames.containsKey(amount))
return coinnames.get(amount);
else
return "NOT FOUND";
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in = new Scanner(System.in);
int amount = in.nextInt();
Coin coin = new Coin();
String name = coin.getName(amount);
System.out.println(name);
}
}
第四章:继承与多态
4.1继承
基于已有的设计创造新的设计,就是面向对象程序设计中的继承。在继承中,新的类不是凭空产生的,而是基于一个已经存在的类而定义出来的。通过继承,新的类自动获得了基础类中所有的成员,包括成员变量和方法,包括各种访问属性的成员,无论是public还是private。当然,在这之后,程序员还可以加入自己的新的成员,包括变量和方法。
Database.java
package dome;
import java.util.ArrayList;
public class Database {
private ArrayList<CD> listCD = new ArrayList<CD>();
private ArrayList<DVD> listDVD = new ArrayList<DVD>();
public void add (CD cd) {
listCD.add(cd);
}
public void add (DVD dvd) {
listDVD.add(dvd);
}
public void list() {
for(CD cd : listCD) {
cd.print();
}
for(DVD dvd : listDVD) {
dvd.print();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Database db = new Database();
db.add(new CD("abc","abc",4,60,"...."));
db.add(new CD("def","def",4,60,"...."));
db.add(new DVD("xxx","aaa",60,"....."));
db.list();
}
}
CD.java
package dome;
public class CD {
private String title;
private String artist;
private int numofTracks;
private int playingtim;
private boolean gotIt = false;
private String comment;
public CD(String title, String artist, int numofTracks, int playingtim, String comment) {
super();
this.title = title;
this.artist = artist;
this.numofTracks = numofTracks;
this.playingtim = playingtim;
this.comment = comment;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public void print() {
// TODO Auto-generated method stub
System.out.println("CD:"+title+":"+artist);
}
}
DVD.java
package dome;
public class DVD {
private String title;
private String director;
private int playingtim;
private boolean gotIt = false;
private String comment;
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public void print() {
// TODO Auto-generated method stub
System.out.println("DVD:"+title+":"+director);
}
public DVD(String title, String director, int playingtim, String comment) {
super();
this.title = title;
this.director = director;
this.playingtim = playingtim;
this.comment = comment;
}
}
在这里CD和DVD两个类有许多相似的地方,此时就可以利用继承这个特性。在这里创建一个Item的类,让CD和DVD都继承它。代码如下:
Item.java
package dome;
public class Item {
public void print() {
// System.out.println("Item");
}
}
Database.java
package dome;
import java.util.ArrayList;
public class Database {
// private ArrayList<CD> listCD = new ArrayList<CD>();
// private ArrayList<DVD> listDVD = new ArrayList<DVD>();
private ArrayList<Item> listItem = new ArrayList<Item>();
// public void add (CD cd) {
// listCD.add(cd);
// }
// public void add (DVD dvd) {
// listDVD.add(dvd);
// }
public void add(Item item) {
listItem.add(item);
}
public void list() {
// for(CD cd : listCD) {
// cd.print();
// }
// for(DVD dvd : listDVD) {
// dvd.print();
// }
for(Item item : listItem) {
item.print();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Database db = new Database();
db.add(new CD("abc","abc",4,60,"...."));
db.add(new CD("def","def",4,60,"...."));
db.add(new DVD("xxx","aaa",60,"....."));
db.list();
}
}
CD.java
package dome;
public class CD extends Item{
private String title;
private String artist;
private int numofTracks;
private int playingtim;
private boolean gotIt = false;
private String comment;
public CD(String title, String artist, int numofTracks, int playingtim, String comment) {
super();
this.title = title;
this.artist = artist;
this.numofTracks = numofTracks;
this.playingtim = playingtim;
this.comment = comment;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public void print() {
// TODO Auto-generated method stub
System.out.println("CD:"+title+":"+artist);
}
}
DVD.java
package dome;
public class DVD extends Item{
private String title;
private String director;
private int playingtim;
private boolean gotIt = false;
private String comment;
public static void main(String[] args) {
}
public void print() {
// TODO Auto-generated method stub
System.out.println("DVD:"+title+":"+director);
}
public DVD(String title, String director, int playingtim, String comment) {
super();
this.title = title;
this.director = director;
this.playingtim = playingtim;
this.comment = comment;
}
}
我们把用来做基础派生其他类的哪个类叫做父类、超类或者基类,而派生出来的新类叫做子类。
Java用关键字extends表示这种继承/派生关系:
class ThisClass extends SuperClass{
......
}
Java的继承只允许单继承,即一个类只能有一个父类。
4.2子类父类关系
- 对于继承来说,子类从父类那里继承了所有的东西,所有的父类成员,包括变量和方法,都成为了子类的成员,除了构造方法。构造方法是父类所独有的,因为他们的名字就是类的名字,所以父类的构造方法在子类中不存在。除此之外,子类继承得到了父类所有的成员。
- 但是得到不等于可以随便使用。每个成员变量都有自己的访问属性,子类继承得到父类所有的成员,但是不同的访问属性使得子类在使用这些成员时有所不同:有些父类的成员直接成为子类的对外的界面,有些则被深深隐藏起来,即使子类自己也不能直接访问。
- 下表列出了不同访问属性的父类成员在子类中的访问属性:
父类成员访问属性 | 在父类中的含义 | 在子类中的含义 |
---|---|---|
public | 对所有人开放 | 对所人开放 |
protected | 只有包内其他类、自己和子类可以访问 | 只有包内其它类、自己和子类可以访问 |
缺省 | 只有包内其他类可以访问 | 如果子类与父类在同一个包内:只有包内其它类可以访问。否则:相当于private,不能访问 |
private | 只有自己可以访问 | 不能访问 |
- 我们不可以在子类中重新定义继承得到的成员的访问属性。如果我们试图重新定义一个在父类中已经存在的成员变量,那么我们是在定义一个与父类的成员变量完全无关的变量,在子类中我们可以访问这个定义在子类中的变量,在父类的方法中访问父类的那个。尽管它们同名但是互不影响。
- 在构造一个子类的对象时,父类的构造方法也是会被调用的,而且父类的构造方法在子类的构造方法之前被调用。在程序运行过程中,子类对象的一部分空间存放的是父类对象。因为子类从父类得到继承,在子类对象初始化过程中可能会使用到父类的成员。所以父类的空间正是要先被初始化的,然后子类的空间才得到初始化。在这个过程中,如果父类的构造方法需要参数,如何传递参数就很重要了。
super:
- super调用父类的构造方法,必须在构造方法的第一个
- super只能出现在子类的方法或者构造方法中
- super和this不能同时调用构造方法!
super和this的不同:
- 代表的对象不同:this是本身调用者这个对象;super代表父类对象的应用
- 使用前提:this没有继承也能使用;super:只能在继承条件下才可以使用
- 构造方法:this():本类的构造;super():父类得构造!
4.3多态变量和向上造型
子类和子类型
- 类定义了类型,子类定义了子类型
- 子类的对象可以被当做父类的对象来使用
- 可以赋值给父类的变量
- 可以传递给需要父类对象的函数
- 可以放进存放父类对象的容器里
子类型与赋值
子类和参数传递
子类型和容器
多态变量
- 在Java中所有的对象变量都是多态的(就是多种形态,这个时候可以放这种类型的变量,那个时候可以放那种类型的变量),他们能保存不止一种类型的变量
- 它们可以保存的是声明类型的对象,或声明类型的子类的对象
- 当把子类的对象赋给父类的变量的时候,就发生了向上造型
造型Cast
- 子类的对象可以赋值给父类的变量(注意!Java中不存在对象对对象的赋值)
- 父类的对象不能赋值给子类的变量
Vechine v;
Car c = new Car();
v = c; //可以
c =v; //编译错误! - 可以用造型:
c = (Car) v; (只有当v这个变量实际管理的是Car才行)
造型的特点
- 用括号围起类型放在值的前面
- 对象本身并没有发生任何变化,所以不是“类型转换”(int a = (int)10.2;这就是类型转换,在这时候这个值会发生变化)
- 运行时有机制来检查这样的转化是否合理(ClassCastException)
向上造型
- 那一个子类的对象,当做父类的对象来用
- 向上造型是默认的,不需要运算符
- 向上造型总是安全的
假设现有4个类:Person、Teacher、Student和PhDStudent。Teacher 和Student都是Person的子类,PhDStudent是Student的子类。以下的赋值语句哪些是合法的
1 | Person p1 = new Student(); | √ |
---|---|---|
2 | Person p2 = new PhDStudent(); | √ |
3 | PhDStudent phd1 = new Student(); | |
4 | Teacher t1 = new Person(); | |
5 | Student s1 = new PhDStudent(); | √ |
6 | s1 = p1; | |
7 | s1 = p2; | |
8 | p1 = s1; | √ |
9 | t1 = s1; | |
10 | s1 = phd1; | √ |
11 | Phd1 = s1; | |
12 | p1 = p2; | √ |
13 | p2 = p1; | √ |
总结:
- 父类引用指向子类的对象,不可以子类引用指向父类。
- 把子类转换为父类,向上转型;
- 把父类转换为子类,向下转型,强制转换(可能会丢失方法)
- 方便方法的调用,减少重复的代码,简介
测试:
//类型之间的转化 : 父---子
//高 低
Person s1 = new Student();
//高转低可以直接转;低转高,需要强制转
//
Student s2 = (Student) s1;
s2.go();
//或((Student) s1).go();
4.4多态
- 如果子类的方法覆盖了父类的方法,我们也说父类的那个方法在子类有了新的版本或者新的实现。覆盖的新版本具有与老版本相同的方法签名:相同的方法名称和参数表。因此,对于外界来说,子类并没有增加新的方法,仍然是在父类中定义过的那个方法。不同的是,这是一个新版本,所以通过子类的对象调用这个方法,执行的是子类自己的方法。
- 覆盖关系并不说明父类中的方法已经不存在了,而是当通过一个子类的对象调用这个方法时,子类中的方法取代了父类的方法,父类的这个方法被“覆盖”起来而看不见了。而当通过父类的对象调用这个方法时,实际上执行的仍然是父类中的这个方法。注意我们这里说的是对象而不是变量,因为一个类型为父类的变量有可能实际指向的是一个子类的对象。
- 当调用一个方法时,究竟应该调用哪个方法,这件事情叫做绑定。绑定表明了调用一个方法的时候,我们使用的是哪个方法。绑定有两种:一种是早绑定,又称静态绑定,这种绑定在编译的时候就确定了;另一种是晚绑定,即动态绑定。动态绑定在运行的时候根据变量当时实际所指的对象的类型动态决定调用的方法。Java缺省使用动态绑定。
函数调用的绑定 - 当通过对象变量调用函数的时候,调用哪个函数这件事叫做绑定
- 静态绑定:根据变量的声明类型来决定
- 动态绑定:根据变量的动态类型来决定(Java语言中默认所有的绑定都是动态绑定)
- 在成员函数中调用其他成员函数也是通过this这个对象来调用的
测试实例
public class Person {
public void run(){
System.out.println("run");
}
}
public class Student extends Person{
@Override
public void run() {
System.out.println("son");
}
public void eat(){
System.out.println("eat");
}
}
public class Application {
public static void main(String[] args) {
//一个对象的实际类型是确定的
//new Student()
//new Person()
//可以指向的引用类型就不确定了:父亲的引用指向了子类
//student能都调用的方法都是自己的或者继承父类的
Student s1 = new Student();
//Person 父类型,可以指向子类,但是不能调用子类独有的方法
Person s2 = new Student();
Object s3 = new Student();
//对象能执行那些方法,主要看对象左边的类型,和右边的关系不大!
s2.run();//子类重写了父类的方法,执行子类的方法
s1.run();
s1.eat();
s2.eat();
}
}
注意:
- 多态是方法的多态,属性没有多态性
- 父类和子类,有联系才能转换,不然会异常!类型转换异常:ClassCastException
- 存在条件:继承关系,方法需要重写,父类引用指向子类对象!Father f1 = new son();
- 不能重写的方法:
- static 方法,属于类,它不属于实例
- final 常量 ,被final修饰的无法修改,属于常量池
- private 私有方法,不能被重写
方法重写 Override
- 重写都是方法的重写,和属性无关。
- 重写值和非静态方法有关,静态没用,而且只能是 public方法 。
- 需要有继承关系,子类重写父类的方法!
方法名必须相同 参数列表必须相同
修饰符:范围可以扩大,但不能缩小;public > protected > Default >private
抛出的异常: 范围,可以被缩小但不能扩大;ClassNotFoundException <— Exception(大)
重写,子类的方法和父类必须一致,但方法体不同!
为什么需要从写:
父类的功能,子类不一定需要,或者不一定满足!
测试代码:
public class B {
public static void test(){
System.out.println("B->test()");
}
}
public class A extends B{
public static void test(){
System.out.println("A->test()");
}
}
public class Application {
public static void main(String[] args) {
//静态方法:方法的调用智慧和左边的定义的数据类型有关
A a = new A();
a.test();
//父类的引用指向了子类
B b = new A();
b.test();
}
}
输出结果:
public class B {
public void test(){
System.out.println("B->test()");
}
}
public class A extends B{
public void test(){
System.out.println("A->test()");
}
}
public class Application {
public static void main(String[] args) {
//非静态的:重写
A a = new A();
a.test();
//子类重写了父类的方法
B b = new A();
b.test();
}
}
在这里可以看出静态方法和非静态的方法有着很大的区别!
覆盖override
- 子类和父类中存在名称和参数表完全相同的函数,这一对函数构成覆盖关系
- 通过父类的变量调用存在覆盖关系的函数时,会调用变量当时所管理的对象所属的类的函数
- 当父类中的方法不适用于子类时,需要提供一个新的方法来覆盖父类中的方法。但是子类中的方法并不能直接访问父类的私有域,这时候就需要借助于公有接口(共有的get方法),并且需要使用特定的关键字super来指定调用的是父类中的方法。
上述代码最终的修改
CD.java
package dome;
public class CD extends Item{
private String artist;
private int numofTracks;
public CD(String title, String artist, int numofTracks, int playingtime, String comment) {
super(title,playingtime,false,comment);
this.artist = artist;
this.numofTracks = numofTracks;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public void print() {
// TODO Auto-generated method stub
System.out.println("CD:");
super.print();
System.out.println(artist+numofTracks);
}
}
DVD.java
package dome;
public class DVD extends Item{
private String director;
public DVD(String title, String director, int playingtime, String comment) {
super(title,playingtime,false,comment);
this.director = director;
}
public static void main(String[] args) {
DVD dvd = new DVD("a","b",1,"...");
dvd.print();
}
public void print() {
System.out.println("DVD:");
super.print();
System.out.println(director);
}
}
Database.java
package dome;
import java.util.ArrayList;
public class Database {
// private ArrayList<CD> listCD = new ArrayList<CD>();
// private ArrayList<DVD> listDVD = new ArrayList<DVD>();
private ArrayList<Item> listItem = new ArrayList<Item>();
// public void add (CD cd) {
// listCD.add(cd);
// }
// public void add (DVD dvd) {
// listDVD.add(dvd);
// }
public void add(Item item) {
listItem.add(item);
}
public void list() {
// for(CD cd : listCD) {
// cd.print();
// }
// for(DVD dvd : listDVD) {
// dvd.print();
// }
for(Item item : listItem) {
item.print();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Database db = new Database();
db.add(new CD("abc","abc",4,60,"...."));
db.add(new CD("def","def",4,60,"...."));
db.add(new DVD("xxx","aaa",60,"....."));
db.list();
}
}
Item.java
package dome;
public class Item {
private String title;
private int playingtime;
private boolean gotIt = false;
private String comment;
public Item() {
}
public Item(String title, int playingtime, boolean gotIt, String comment) {
super();
this.title = title;
this.playingtime = playingtime;
this.gotIt = gotIt;
this.comment = comment;
}
public void print() {
System.out.println(title);
}
}
4.5类型系统
Object类
单根结构
可以使用Object类型的变量引用任何类型的对象:
Object obi = new Employee(. . .);
阻止继承:final类和方法
不允许扩展的称为final类。假设希望阻止人们定义Executive类的子类,就可以在定义这个类的时候使用final修饰符声明。格式如下:
public final class Excuective extends Managers{
}
类中的特定方法也可以声明为final,这样子类就不能覆盖这个方法。例如:
public class Employee
{
public final String getName()
{
}
}
将方法和类声明为final主要目的是:确保它们不会在子类中改变语义。
强制类型转换
double x = 3.405;
int nx = (int) x;
将表达式x的值转换成整数类型。有时候也需要将某个类的对象引用转换成另外一个类的对象引用。例如:
Manager boss = (Manager) staff[0];
进行类型转换的唯一原因是在暂时忽视对象的实际类型之后,使用对象的全部功能。注意在进行类型转换之前,先查看一下是否能够成功转换,使用instanceof操作符就可以实现。例如:
if (staff[1] instanceof Manager)
{
boss = (Manager) staff[1];
. . .
}
如果这个转换类型不可能成功,编译器就不会进行类型转换。
类型转换要求
- 只能在继承层次内进行类型转换
- 再将父类转换成子类之前,应该使用instanceof进行检查。
hash Code方法
散列码是由对象导出的一个整型值。hashCode方法返回一个整型数值(也可以是负数)。例如Employee类的hashCode方法:
public class Employee
{
public int hashCode
{
return Objects.hash(name,salary,hireDay);
}
}
第五章:抽象与接口
5.1接口(interface)
接口技术主要是用来描述这个类具有什么功能,而并不给出每个功能的具体实现。为了让类实现一个接口,通常需要下面几个步骤:
- 将类声明为实现给定的接口
- 对接口中的所有方法进行定义。
现在假设希望使用Arrays类的sort方法对Employee对象数组进行排序,Employee类就必须实现Comparable接口。
要将类声明为实现某个接口,需要使用关键字implements:
class Employee implements Comparable
当然这里的类需要提供compareTo方法。下面程序给出了对一个Employee类实例数组进行排序的完整代码,用于对一个员工数组排序
package interfaces;
public class Employee implements Comparable<Employee>
{
private String name;
private double salary;
public Employee(String name, double salary)
{
this.name = name;
this.salary = salary;
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent/100;
salary += raise;
}
public int compareTo(Employee other) //这里就用了compareTo方法
{
return Double.compare(salary,other.salary);
}
}
package interfaces;
import java.util.Arrays;
public class EmployeeSortTest {
public static void main(String[] args) {
Employee[] staff = new Employee[3];
staff[0] = new Employee("Harry Hacker",35000);
staff[1] = new Employee("Carl Cracker",75000);
staff[2] = new Employee("Tony Tester",38000);
Arrays.sort(staff);
for (Employee e : staff)
System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
}
}
- int CompareTo(T other)用这个对象与other进行比较。如果这个对象小于other则返回负值;如果相等则返回0;否则返回正值。
接口的特性
接口不是类,尤其不能使用new运算符实例化一个接口:
x = new Comparable; //ERROR
然而,尽管不能构造接口的对象,却能声明接口的变量,而且接口的变量必须引用实现了的接口的对象:
Comparable x;
x = new Employee(...) //OK provided Employee implements Comparable
与可以建立类的继承关系一样,接口也可以被扩展。这里允许存在多条从具有较高通用性的接口到较高专用性的接口的链。例如,假设有一个称为Moveable的接口,可以以它为基础扩展一个叫做Powered的接口:
public interface Moveable
{
void move(double x,double y);
}
public interface Powered extends Moveable
{
double milesPerGallon();
double SPEED_LIMIT = 95;
}
//与接口中的方法都自动地被设置为public一样,接口中的域将被自动设置为public static final。
- 普通类:只有具体实现
- 抽象类:具体实现和规范(抽象方法)都有!
- 接口:只有规范!自己无法写方法,专业的约束!约束和实现分离:面向接口编程~
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是…则必须能…”的思想。“如果你是天使,则必须能飞。如果你是汽车,则必须能跑。”
- OO的精髓,是对对象的抽象,最能体现这一点的就是接口,为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如c++、java、c#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象。
声明类的关键字是class,声明接口的关键字是interface
//interface 接口定义的关键字;接口都需要实现类
public interface UserSerbice {
// public void ss(){ } 报错;接口内不能写方法
//接口中的所有定义其实都是抽象的,默认 public abstract
public abstract void run();
void add(String name);
void delete(String name);
//接口还可以定义变量,所有定义的属性都是静态的常量
public static final int AGE = 99;
int ABC = 99;
}
作用:
- 约束,规范
- 定义一些方法,让不同的人实现。多个人完成共同的工作。
- 接口中所有默认的方法public abstract
- 所有常量默认public static final
- 接口不能被实例化,接口中没有构造方法。
- 可以实现多个接口
- 必须要重写接口中的方法。
- 声明接口interface,实现接口implements,可以实现多个方法
public class Application {
public static void main(String[] args) {
xiaduan sxmz = new xiaduan();
sxmz.mingzi("上下没中");
sxmz.nianling(31);
sxmz.shengao(175.8);
sxmz.zhuzhi("内蒙古");
}
}
/*
//抽象类:extende
//类 可以实现接口 implements 接口
//实现了接口的类,就需要重写接口中的方法
public class xiaduan implements jiekou,jiekou2 {
//利用接口实现多继承(jiekou,jiekou2)
//继承类可以方法的实现,但接口只有方法的定义
@Override//jiekou
public void mingzi(String name) {
System.out.println("名字:"+name);
}
@Override//jiekou
public void nianling(int nl) {
System.out.println("岁数:"+nl);
}
@Override//jiekou
public void shengao(double sg) {
System.out.println("身高:"+sg);
}
@Override//jiekou2
public void zhuzhi(String zz) {
System.out.println("住宅:"+zz);
}
}
*/
5.2抽象
抽象方法充当着占位的角色,它们的具体实现在子类中。
抽象函数\抽象类
- 抽象函数——表达概念而无法实现具体代码的函数
- 抽象类——表达概念而无法构造出实体的类
- 带有abstract修饰符的函数
- 带有抽象函数的类一定是抽象类
- 抽象类不能制造对象,但是可以定义变量
- 任何继承了抽象类的非抽象类的对象可以付给这个变量
实现抽象函数
继承自抽象类的子类必须覆盖父类中的抽象函数,除非自己本身也是抽象的
两种抽象
- 与具体相对的,表示一种概念而非实体
- 与细节相对的,标识在一定程度上忽略细节而着眼大局
5.3数据的表现与分离
程序的业务逻辑与表现无关,表现可以是图形的也可以是文本的,表现可以是当地的也可以是远程的
第六章;控制反转与MVC模式
6.1控制反转
GUI(图形用户界面)给应用程序提供界面,其中包括窗口、菜单、按钮和其他图形组件,这就是今天大多 数人所熟悉的“典型”应用程序界面。借助GUI来介绍两个设计思想:控制反转和MVC设计模式。布局是指如何在屏幕上放置组件。过去,大多数简单的GUI系统让程序员在二维坐标系上 指定每个组件的x和y坐标(以像素点为单位),这对于现代的GUI系统来说太简单了。因为现代的GUI系统还得考虑不同的屏幕分辨率、不同的字体、用户可改变的窗口尺寸,以及许多其他使得布局困难的因素。所以需要有一种能更通用地指定布局的方法,比如,要求“这个部件应该在那个部件的下面“或者”这个部件在窗口改变尺寸时能自动拉伸,但是其他部件保持尺寸不变”。这些可以通过布局管理器(layout manager)来实现。事件处理是用来响应用户输入的技术。创建了部件并且放在屏幕上合适的位置以后,就得 要有办法来处理诸如用户点击按钮这样的事情。Java类库处理这类事情的模型是基于事件的。 如果用户激活了一个部件(比如,点击按钮或者选择菜单项),系统就会产生一个事件。应用 程序可以收到关于这个事件的通知(以程序的一个方法被调用的方式),然后就可以采取程序该做的动作了。
Swing事件机制
(消息机制)ActionListener
- Swing使用一个非常灵活的模型来处理GUI的输入:采用事件监听器的事件处理(event handling)模型。
- Swing框架本身以及大部分部件在发生一些情况时会触发相关的事件,而其他的对象也许会对这些事件感兴趣。不同类型的动作会导致不同类型的事件。当点击一个按钮或选中一个菜单项,部件就会触发动作事件;而当点击或移动鼠标时,会触发鼠标事件;当框架被关闭或最小化时,会触发窗口事件。另外还有许多种其他事件。
- 所有的对象都可以成为任何这些事件的监听器,而一旦成为监听器,就可以得到这些事件触发的通知。
- 实现了众多监听器接口之一的对象就成为一个事件监听器。如果对象实现了恰当的接口, 就可以注册到它想监听的组件上。
6.2内部类
内部类就是指一个类定义在另一个类的内部,从而成为外部类的一个成员。因此一个类中可以有成员变量、方法,还可以有内部类。实际上Java的内部类可以被称为成员类,内部类实际上是它所在类的成员。所以内部类也就具有和成员变量、成员方法相同的性质。比如,成员方法可以访问私有变量,那么成员类也可以访问私有变量了。也就是说,成员类中的成员方法都可以访问成员类所在类的私有变量。内部类最重要的特点就是能够访问外部类的所有成员。
public class Outer {//外部类
private int id = 10;
public void out(){
System.out.println("这是外部类的方法!");
}
public class Inner{//内部类
public void in(){
System.out.println("这是内部类的方法!");
}
}
//内部类可以获得外部类的私有属性
public void getID(){
System.out.println(id);
}
}
public static void main(String[] args) {
//外部类通过 new 获取
Outer outer = new Outer();
//内部类通过 外部类 . new 内部类 获得
Outer.Inner inner = outer.new Inner();
}
使用内部类的原因
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
内部类中声明的所有静态域都必须是final,内部类不能有static方法。
- 成员内部类;类中加类
//先实例化外部类,再用外部类实例化内部类
Outer outer = new Outer();//new外部
Outer.Inner inner = outer.new Inner();//new内部
inner.in();
内部类可以获得外部类的私有属性
-
静态内部类:
不能直接访问非静态的外部类属性(static先于非静态类生成) -
局部内部类
在一个方法中可以定义一个局部类,局部类不能用public和private访问说明符进行声明,它的作用域被限定在声明这个局部类的块中。
局部类有两个优势:- 对外部世界可以完全的隐藏起来,即使外部类中的其它代码也不能访问它。
- 不仅能够访问包含它们的外部类,还可以访问局部变量,不过那些局部变量必须为final。
public void method(){
class inner{
public void in(){
}
}
}
- 匿名类
- 在new对象的时候给出的类的定义形成了匿名类
- 匿名类可以继承某类,也可以实现某接口
- Swing的消息机制广泛使用匿名类
//不起名直接使用
new Apple().eat();
//new接口
class Test{
public static void main(String[] args) {
new UserService(){
};
}
}
interface UserService{
}
第七章:异常处理与输入输出
7.1异常(Exception)
在代码中发现异常时,可将异常放在一个try里面
package field;
import java.util.Scanner;
public class ArrayIndex {
public static void main(String[] args){
int[] a = new int[10];
int index;
Scanner in = new Scanner(System.in);
index = in.nextInt();
a[index] = 10;
System.out.println("hello");
}
}
在这里显示异常
package field;
import java.util.Scanner;
public class ArrayIndex {
public static void main(String[] args){
int[] a = new int[10];
int index;
Scanner in = new Scanner(System.in);
index = in.nextInt();
try {
a[index] = 10;
System.out.println("hello");
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("Caught");
}
}
}
将异常放入try里面,即使运行出现异常还是能接着往下运行。
捕捉异常
try{
//可能产生异常的代码
}catch(Type1 id1){
//处理Type1异常的代码
}catch(Type2 id2){
//处理Type2异常的代码
}catch(Type3 id3){
//处理Type3异常的代码
}
捕捉到异常之后做什么?
- String getMessage();
- String toString();
- void printStackTrace();
- 再度抛出异常使用throw e;
7.2异常机制
- 抛出异常
- 捕获异常
- 异常处理五个关键字
- try、catch、finally、throw、throws
可以当出现异常时,捕获它,防止程序停止。
public static void main(String[] args) {
int a = 1;
int b = 0;
//假设要捕获多个异常:从小到大!不然会报错,提示大异常以及覆盖小异常
try { //try 监控区域
System.out.println(a / b);
} catch (Error e) { //catch(想要捕获的异常类型!)捕获异常
System.out.println("Error");
}catch (Exception e){//“e”,代表异常消息
System.out.println("Exception");
}catch (Throwable t){ //最高级,放在最后面
System.out.println("Throwable");
}finally { //处理善后工作,不管报不报异常,都会执行。
System.out.println("finally");
}
//finally 可以不用,但是catch必须有。finally假设Io,资源,关闭工作
//快捷键:选中需要包裹的代码,ctrl+alt+T
/*
try {
System.out.println(a / b);
} catch (Exception e) {
System.exit(0);//程序结束
e.printStackTrace();//打印错误的栈信息
} finally {
}
*/
}
throw
try {
if (b==0){
throw new ArithmeticException();//主动的抛出异常
}
throws
public static void main(String[] args) {
try {
new linshi().test(1,0);
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
}
}
//假设这个方法中,处理不了这个异常。方法上抛出异常throws,由上一级捕获。
public void test(int a,int b)throws ArithmeticException{
if (b == 0) {
throw new ArithmeticException();//throw 主动的抛出异常,一般在方法内
}
System.out.println(a / b);
}
自定义异常
-
使用 Java 内置的异常类可以描述在编程时出现的大部分异常情况。除此之外用户还可以自定义异常。用户自定义异常类,只需继承
Exception 类即可。 -
在程序中使用自定义异常类,大体可分为以下几个步骤:
- 创建自定义异常类。
- 在方法中通过 throw 关键字抛出异常对象。
- 如果在当前抛出异常的方法中处理异常,可以使用 try-catch 语句捕获 并处理;否则在方法的声明处通过 throws 关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
- 在出现异常方法的调用者中捕获并处理异常。
自定义异常类
//自定义异常类
//假设传递数字>10异常
private int tishi;//创建一个提示信息
public Demo01(int a) {//创建一个构造器传递消息
this.tishi = a;
}
//toString打印信息:异常的打印信息
@Override
public String toString() {
return "异常{" + "tishi=" + tishi + '}';
}
throws抛出方法捕获
//创建一个可能会存在异常的方法
static void test(int a) throws Demo01 {
System.out.println("传递的参数为:"+a);
if (a>10){
throw new Demo01(a);
}
System.out.println("ok");
}
public static void main(String[] args) {
try { //赋值并捕获
test(11);
} catch (Demo01 e) {
// if( ){ } 可以增加一些处理异常的代码块
System.out.println("注意:"+e);
}
}
throw方法内捕获
//创建一个可能会存在异常的方法
static void test(int a) {
System.out.println("传递的参数为:"+a);
if (a>10){
try {
throw new Demo01(a);
} catch (Demo01 e) {
System.out.println("注意:"+e);;
}
}
System.out.println("ok");
}
public static void main(String[] args) {
test(15); //赋值
}
实际应用中的经验总结
- 处理运行时异常,采用逻辑去合理规避同时辅助 try-catch 处理
- 在多重 catch 块后面,可以加一个 catch(Exception)来处理可能被遗漏的异常
- 对于不确定的代码,也可以加上 try-catch ,处理潜在的异常
- 尽量去处理异常,切忌只是简单地调用 printStackTrace()去打印输出
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定
- 尽量添加 finally 语句块去释放占用的资源
如果要读文件,需要做以下事情:
- 打开文件;
- 判断文件大小;
- 分配足够的内存空间;
- 把文件读入内存;
- 关闭文件;
用上异常机制
异常
- 有不寻常的事情发生了
- 当这个事情发生的时候,原本打算要接着做的事情不能再继续做了,必须停下来,让其他地方的某段代码来处理
异常最大的好处就是清晰的分开了正常的业务逻辑代码和遇到情况时的处理代码。
异常声明
如果你的函数可能抛出异常,就必须在函数头部加以声明
void f() throws TooBig, TooSmall,{
// ...
}
catch怎么匹配异常的
- Is-A的关系
- 就是说,抛出的子类异常会被捕捉父类异常的catch给捉到
使用catch(Exception e)可以捕捉任何异常
运行时刻异常
- 像ArrayIndexOutOfBoundsException这样的异常是不需要声明的
- 但是如果没有适当的机制来捕捉,就会最终导致程序终止
异常声明遇到继承关系
- 当覆盖一个函数的时候,子类不能声明抛出比父类的版本更多的异常
- 在子类的构造函数中,必须声明父类可能抛出的全部异常
7.3 流
流的基础类
InputStream和OutputStream构成了输入输出流的基础类
文件流
- FileInputStream
- FileOutputStream
对文件读写操作