4.1 类的继承
类的继承
1.Java只支持类的单继承,每个子类只能有一个直接父类
4.1.1 继承的概念
基类和派生类
基类(base class)
▫ 也称超类(superclass)
▫ 是被直接或间接继承的类
派生类(derived-class)
▫ 也称子类 (subclass)
▫ 继承其他类而得到的类
▫ 继承所有祖先的状态和行为
▫ 派生类可以增加变量和方法
▫ 派生类也可以覆盖(override)继承的方法
派生类对象
派生类产生的对象:
从外部来看,它应该包括:
与基类相同的接口
可以具有更多的方法和数据成员
其内包含着一个基类类型的子对象
4.1.2 继承的语法
extends
子类不能直接访问从父类中继承的私有属性及方法,但可使用公有及保护)方法进行访问
继承后,就相当于子类也拥有了父类的属性和方法,可以直接调用
4.1.3 隐藏和覆盖
—属性的隐藏
属性的隐藏
▫ 子类中声明了与父类中相同的成员变量名,则从父类继承的变量将被隐藏
▫ 子类拥有了两个相同名字的变量,一个继承自父类,另一个由自己声明
▫ 当子类执行继承自父类的操作时,处理的是继承自父类的变量,而当子类执行它自己声明的方法时,所操作的就是它自己声明的变量
访问被隐藏的父类属性
1.调用从父类继承的方法,则操作的是从父类继承的属性
2.使用super.属性
操作父类静态属性举例
子类不能继承父类中的静态属性,但可以对父类中的静态属性进行操作。
子类自身不能访问到,但可以return或者进行一些二其他操作
域的隐藏
▫ 子类中声明了与父类中相同的成员变量名,则从父类继承的变量将被隐藏
▫ 隐藏的域:子类方法访问子类中的,父类方法访问父类中的
▫ 方法中总是查找离自己层次最近的同名域来访问(编译器决定)。
▫ 在子类中super.属性可访问父类中的同名属性。
▫ super.super.属性: java不支持,违反封装的原则。
▫ 静态域:由类名或者实例的引用变量类型,编译时决定
方法覆盖
1.如果子类不需使用从父类继承来的方法的功能,则可以声明自己的同名方法,称为方法覆盖
2.覆盖方法的返回类型,方法名称,参数的个数及类型必须和被覆盖的方法一摸一样
3.覆盖方法的访问权限可以比被覆盖的宽松,但是不能更为严格
父类private属性的方法不能被继承,因此也就不能被覆盖。
Parent obj2 = new Child(); ▫ obj2.show(); ▫ 因为创建了Child对象,即使引用变量类型为 Parent, show()执行的也是Child定义的动作。 ▫ 在这种情况下,父类的show()看起来被“覆盖” 了
4.1.4 有继承时的构造方法
有继承时的构造方法遵循以下的原则:
1.子类不能从父类继承构造方法
2.子类的构造方法中调用某一个父类构造方法,调用语句必须出现在子类构造方法的第一行,可使用super关键字
3.如子类构造方法的声明中没有明确调用父类构造方法,则系统在执行子类的构造方法时会自动调用父类的默认构造方法(即无参的构造方法)
4.总之,无论如何,在创建子类对象时,从Object开始,其每一级基类的构造方法都会被执行。
public class Person {
protected String name, phoneNumber, address;
public Person()
{
this("", "", "");
}
public Person(String aName, String aPhoneNumber, String anAddress)
{
name = aName;
phoneNumber = aPhoneNumber;
address = anAddress;
}
}
public class Employee extends Person {
protected int employeeNumber;
protected String workPhoneNumber;
public Employee() {
//此处隐含调用构造方法 Person()
this(0, ""); }
public Employee(int aNumber, String aPhoneNumber) {
//此处隐含调用构造方法 Person()
employeeNumber = aNumber;
workPhoneNumber = aPhoneNumber; } }
public class Professor extends Employee {
protected String research;
public Professor() {
super();
research = ""; }
public Professor(int aNumber, String aPhoneNumber, String
aResearch) {
super(aNumber, aPhoneNumber);
research = aResearch; } }
4.2 Object 类
Java程序中所有类的直接或间接父类,类库中所有类的父类,处在类层次最高点
包含了所有Java类的公共属性,其构造方法是Object( )
包含的主要方法
▫ public final Class getClass() 获取当前对象所属的类信息,返回Class对象 ▫ public String toString() 返回当前对象本身的有关信息,按字符串对象返回 ▫ public boolean equals(Object obj) 比较两个对象是否是同一对象,是则返回true ▫ protected Object clone( ) 生成当前对象的一个拷贝,并返回这个复制对象 ▫ Public int hashCode() 返回该对象的哈希代码值 ▫ protected void finalize() throws Throwable 定义回收当前对象时所需完成的资源释放工作
相等和同一
两个对象具有相同的类型,及相同的属性值,则称二者相等(equal)
如果两个引用变量指向的是同一个对象,则称这两个变量(对象)同一(identical)
比较运算符“ == ” 判断的是这两个对象是否同一
object类默认的equal()方法
public boolean equals(Object x) { return this == x; }
通过重写可以改为判断相等
1.String类中已经重写了Object类的equals方法,可以判别两个字符串是否内容相同
2.重写方法定义头必须与Object类中的equals方法完全相同:public boolean equals(Object x) 尤其是这个object
先判断类型在判断值
public boolean equals(Object obj) { if (obj instanceof Apple) { Apple a = (Apple) obj; return (color.equals(a.getColor()) && (ripe == a.getRipe())); } return false; }
string pool 字符串池:
文字量如果相同,就会分配在相同的地址!! new的话则会分配新的地址
public class TestString { public static void main(String args[]) { String a = "Hello"; String b = "Hello"; String c = new String("Hello"); System.out.println("a==b is "+(a==b)); System.out.println("a==c is "+(a==c)); System.out.println("a.equals(c) is "+a.equals(c)); } } 输出: a==b is true a==c is false a.equals(c) is true public class ShallowCopy { public static void main(String[] args) { String a = new String("hello"); String b = new String("hello"); System.out.println(a==b); } } false
更进一步了解字符串,需要了解字符串的intern方法的原理。
clone方法
1.根据已存在的对象构造一个新的对象在根类Object 中被定义为protected,所以需要覆盖为public
2.实现Cloneable 接口,赋予一个对象被克隆的能力(cloneability)
class MyObject implements Cloneable
{ //…
}
finalize方法
如果要覆盖finalize方法,覆盖方法的最后必须调用super.finalize
getClass方法
final 方法,返回一个Class对象,用来代表对象隶属的类
通过Class 对象,你可以查询Class对象的各种信息:比如它的名字,它的基类,它所实现接口的名字等
class car { static int a =10 ; int b =20 ; public int getA(){ return a; } } class carr extends car{ int a=100; int out(){ return super.a; } } public class ShallowCopy { public static void main(String[] args) { carr a = new carr(); System.out.println(a.getClass()); System.out.println(a.getClass().getName()); } } 输出: class carr carr
notify、notifyAll、wait方法
▫ final方法,不能覆盖
▫ 这三个方法主要用在多线程程序中
4.3 终结类与终结方法
• 终结类与终结方法
1.被final修饰符修饰的类和方法
2.终结类不能被继承
3.终结方法不能被当前类的子类重写
4.3.1 终结类
终结类的特点 不能有派生类 防止黑客
eg:
final class ChessAlgorithm { . . . }
4.3.2 终结方法
不能被派生类覆盖
class Parent { public Parent() { } //构造方法 final int getPI() { return Math.PI; } //终结方法 }
<!--可能出现的判错 : 修改final方法 / 继承final类-->
4.4 抽象类
抽象类
▫ 代表一个抽象概念的类
▫ 没有具体实例对象的类,不能使用new方法进行实例化
▫ 类前需加修饰符abstract
▫ 可包含常规类能够包含的任何东西,例如构造方法,非抽象方法
▫ 也可包含抽象方法,这种方法只有方法的声明,而没有方法的实现
在UML中,抽象类的类名为斜体,以与具体类相区别
abstract class Number {
. . .
}
<!--错误new抽象类-->
4.4.2 抽象方法
抽象方法
▫ 声明的语法形式为public abstract <returnType> <methodName>(...);
▫ 仅有方法头,而没有方法体和操作实现
▫ 具体实现由当前类的不同子类在它们各自的类声明中完成
▫ 抽象类可以包含抽象方法
需注意的问题
一个抽象类的子类如果不是抽象类,则它必须为父类中的所有抽象方法书写方法体,即重写父类中的所有抽象方法
▫ 只有抽象类才能具有抽象方法,即如果一个类中含有抽象方法,则必须将这个类声明为抽象类
▫ 除了抽象方法,抽象类中还可以包括非抽象方法
抽象方法具有强迫性
eg:
abstract class GraphicObject { int x, y; void moveTo(int newX, int newY) { . . . } abstract void draw(); } //重写 class Circle extends GraphicObject { void draw() { . . . } } class Rectangle extends GraphicObject { void draw() { . . . } }
4.5 泛型
其本质是参数化类型,即所操作的数据类型被指定为一个参数
基本思想是类和方法的泛化,是通过参数化实现的,因此泛型又被称为参数化类型
优点:
▫ 编译时的严格类型检查
▫ 消除了绝大多数的类型转换
4.5.1 泛型的概念
泛型可以使用在类、接口以及方法的创建中,分别称为泛型类、泛型方法和泛型接口:
▫ 泛型类:在类名后面加上“<Type>”
▫ 泛型方法:在方法名**前**加上“<Type>”
▫ 泛型接口:第五章介绍接口时讲解
在没有泛型的情况下,通常通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是必须做强制的类型转换
或者程序员需要利用代码检查类型是否正确。instanceof , getClass)
而在使用泛型的情况下,编译器会检查类型是否安全,并且所有的类型转换都是自动和隐式的,可以提高代码的重用率。
一般使用一个大写字母表示参数类型约定如下:
▫ E -Element(Java Collections框架大量使用)
▫ K -Key
▫ N -Number
▫ T -Type
▫ V -Value
▫ S,U,V 等 -第二,第三,第四个类型
eg:
在Java 5之前,为了让类具有通用性,往往将属性类型、函数参数、返回类型定义为Object的类
class GeneralType { Object object; public GeneralType(Object object) { this.object = object; } public Object getObj() { return object; } } public class Tester { public static void main(String args[]){ GeneralType i = new GeneralType(2); // 传递参数为int 类型的2,会自动封箱为Integer类型的对象。 GeneralType d = new GeneralType(0.33); // 传递参数为 double类型的0.33,会自动封箱为Double类型的对象。 System.out.println("i.object=" + (Integer)i.getObj()); System.out.println("i.object=" + (Integer)d.getObj()); // 可以通过编译,但运行时异常 } }
泛型类的使用
class GeneralType <Type> { Type object; public GeneralType(Type object) { this.object = object; } public Type getObj() { return object; } } public class Test { public static void main(String args[]){ GeneralType<Integer> i = new GeneralType<Integer> (2); GeneralType<Double> d = new GeneralType<Double> (0.33); System.out.println("i.object=" + (Integer)i.getObj()); // System.out.println("i.object=" + (Integer)d.getObj()); // 不能通过编译 } } 优点就是在编译阶段就被阻止
泛型方法的使用
class GeneralMethod { <Type> void printClassName(Type object) { System.out.println(object.getClass().getName()); } } public class Test { public static void main(String[] args) { GeneralMethod gm = new GeneralMethod(); gm.printClassName("hello"); gm.printClassName(3); gm.printClassName(3.0f); gm.printClassName(3.0); } }
4.5.2 通配符泛型和有限制的泛型
通配符泛型<?>
class ShowType { public void showType(GeneralType<Object> o) { System.out.println(o.getObj().getClass().getName()); } } public class Test { public static void main(String args[]){ ShowType st = new ShowType(); GeneralType <Integer> i = new GeneralType <Integer> (2); st.showType(i); //这行语句是否合法? } }
上面的代码并不能通过编译,这是因为,不能将General<Integer>类型的变量当做参数传递给General<Object>
事实上,这里能传递的类型只能是General<Object>。因此,在使用泛型时应该注意和继承类的区别。
泛型的使用是严格的
使用通配符泛型 可以让showType函数发挥应有的作用
• “?”代表任意一种类型,它被称为通配符
有限制的泛型
有限制的泛型是指,在参数“Type”后面使用“extends”关键字并加上类名或接口名,表明参数所代表的类型必须是改类的关键字或者实现了该接口
注意,对于实现了某接口的有限制泛型,也是使用extends关键字,而不是implements关键字
eg:
class GeneralType <Type extends Number> { Type object; public GeneralType(Type object) { this.object = object; } public Type getObj() { return object; } } public class Test { public static void main(String args[]){ GeneralType<Integer> i = new GeneralType<Integer> (2); //GeneralType<String> s = new GeneralType<String> ("hello");//非法, //T只能是Number或Number的子类 } }
泛型的类型推断
4.6 类的组合
Java的类中可以有其他类的对象作为成员,这便是类的组合
4.6.1 组合的语法
class Cooker{ // 类的语句 } class Refrigerator{ // 类的语句} class Kitchen{ Cooker myCooker; Refrigerator myRefrigerator; }
public class Point //点类 { private int x, y; //coordinate public Point(int x, int y) { this.x = x; this.y = y;} public int GetX() { return x; } public int GetY() { return y; } } class Line //线段类 { private Point p1,p2; // 两端点 Line(Point a, Point b) { p1 = new Point(a.GetX(),a.GetY()); p2 = new Point(b.GetX(),b.GetY()); } public double Length() { return Math.sqrt(Math.pow(p2.GetX()-p1.GetX(),2) + Math.pow(p2.GetY()-p1.GetY(),2)); } }
注意:在作为属性时,仅仅是声明,?或者是赋初值?
4.6.2 组合与继承的比较
“包含”关系用组合来表达
组合与继承结合举例
面向对象设计原则
• 1. 单一职责原则(**Single Responsibility Principle)**
每一个类应该专注于做一件事情。
• 2. 里氏替换原则(**Liskov Substitution Principle)**
超类存在的地方,子类是可以替换的。
• 3. 依赖倒置原则(**Dependence Inversion Principle)**
实现尽量依赖抽象,不依赖具体实现。(具体的就是依赖
于抽象类或者接口)
• 4. 接口隔离原则(**Interface Segregation Principle)**
应当为客户端提供尽可能小的单独的接口,而不是提供大的总
的接口。
5. 迪米特法则(**Law Of Demeter)**
又叫最少知识原则,一个软件实体应当尽可能少的与其
他实体发生相互作用。
• 6. 开闭原则(**Open Close Principle)**
面向扩展开放,面向修改关闭。
• 7. 组合**/聚合复用原则(Composite/Aggregate**
Reuse Principle CARP**)**
尽量使用合成/聚合达到复用,尽量少用继承。
总之:尽可能做到高内聚、低耦合。
4.7 包的应用
包
为了解决类名冲突,Java提供包来管理类名空间
▫ Java利用包来组织相关的类,并控制访问权限
▫ 包是一种松散的类的集合,利用包来管理类,可实现类的共享与复用
▫ 同一包中的类在默认情况下可以互相访问,通常把需要在一起工作的类放在一个包里
4.7.1 Java 基础类库简介
语言包(java.lang)
语言包java.lang提供了Java语言最基础的类,包括
Object类
数据类型包裹类(the Data Type Wrapper)
字符串类(String、StringBuffer)
数学类(Math)
系统和运行时类(System、Runtime)
类操作类(Class,ClassLoader)
数据类型包裹类
生成数据类型包括类对象的方法:
1.从基本数据类型的变量或常量生成包裹类对象
double x = 1.2;
Double a = new Double(x);
Double b = new Double(-5.25);
2.从字符串生成包裹类对象
Double c = new Double("-2.34");
Integer i = new Integer("1234");
3.已知字符串,可使用valueOf方法将其转换成包裹类对象:
Integer.valueOf("125");
Double.valueOf("5.15");
4.自动装箱
得到基本数据类型数据的方法
1.每一个包裹类都提供相应的方法将包裹类对象转换回基本数据类型的数据
2.Integer、Float、Double、Long、Byte 及Short 类提供了特殊的方法能够将字符串类型的对象直接转换成对应的int、float、double、long、byte或short类型的数据
Integer.parseInt(“234”) // 返回int类型的数据 Float.parseFloat(“234.78”) // 返回float类型的数据
3.自动拆箱
常量字符串类String
String类的常用方法1
变量字符串类StringBuffer
(考or不考)
其对象是可以修改的字符串
该类的方法不能被用于String类的对象
生成StringBuffer类的对象
new StringBuffer();
生成容量为16的空字符串对象
new StringBuffer(int size);
生成容量为size的空字符串对象
new StringBuffer(String aString);
生成aString的一个备份,容量为其长度 +16
方法
已知一个字符串,返回将字符串中的非字母字符都删除后的字符串
public class StringEditor { public static String removeNonLetters(String original) { StringBuffer aBuffer = new StringBuffer(original.length()); char aCharacter; for (int i=0; i<original.length(); i++) { aCharacter = original.charAt(i); if (Character.isLetter(aCharacter)) aBuffer.append(new Character(aCharacter)); } return new String(aBuffer); } }
String, StringBuffer, StringBuilder
StringBuilder不是线程安全的,比StringBuffer效率高,应该尽可能使用。
数学类(Math)
▫ 其中所有的变量和方法都是静态的(static)
▫ 是终结类(final),不能从中派生其他的新类
系统和运行时类(System、Runtime)
System类
▫ 访问系统资源
arraycopy() 复制一个数组
exit() 结束当前运行的程序
currentTimeMillis() 获得系统当前日期和时间等
▫ 访问标准输入输出流
System.in 标准输入,表示键盘
System.out 标准输出,表示显示器
Runtime类
▫ 可直接访问运行时资源
totalMemory() 返回系统内存总量
freeMemory() 返回内存的剩余空间
System.out.println(Runtime.getRuntime().totalMemory());
类操作类(Class、ClassLoader)
Class类
▫ 提供运行时信息,如名字、类型以及父类
▫ Object类中的getClass方法返回当前对象所在的类,返回类型是Class
▫ 它的getName方法返回一个类的名称,返回值是String
▫ 它的getSuperclass方法可以获得当前对象的父类
ClassLoader类
▫ 提供把类装入运行时环境的方法
▫ 构筑比较大型的复杂的系统时,可能会需要定义自己的ClassLoader
实用包(java.util)
Calendar类
StringTokenizer类
生成StringTokenizer类的对象的方法:
▫ new StringTokenizer(String aString);
指定了将被处理的字符串,没有指定分隔符(delimeter),这种情况下默认的分隔符为空格
▫ new StringTokenizer(String aString, String delimiters);
除了指定将被处理的字符串,还指定了分隔符字符串,如分隔符字符串可以为“,:;|_()”
▫ new StringTokenizer(String aString, String delimiters, boolean returnDelimiters);
第三个参数如果为true,则分隔符本身也作为标记返回
文本包(java.text)
提供各种文本或日期格式
包含
▫ Format类
▫ DateFormat类
▫ SimpleDateFormat类
使用已定义的格式对日期对象进行格式化
构造方法 以一指定格式的字符串作为参数
new java.text.SimpleDateFormat(formatString);
format(Date d) 将此种格式应用于给定的日期
aSimpleDateFormat.format(aDate);
4.7.2 自定义包
自定义包
▫ 包是一组类的集合,利用包来管理类,可实现类的共享与复用
▫ 同一包中的类在默认情况下可以互相访问,通常把需要在一起工作的类放在一个包里
▫ 在实际使用中,用户可以将自己的类组织成包结构
包名
通常全部用小写字母
声明之后,会把class文件放入到一个新创建的子文件夹目录下
Package语句
▫ Java源文件的第一条语句, 前面只能有注释或空行
▫ 一个文件中最多只能有一条
▫ 如果源文件中没有,则文件中声明的所有类属于一个默认的无名包
▫ 包声明的语句的完整格式如下:
package pkg1[.pkg2[.pkg3…]];
Java编译器把包对应于文件系统的目录结构
用点来指明目录的层次
编译和生成包
如果在程序Test.java中已声明了包mypackage
编译时采用方式 javac -d destpath Test.java
则编译器会自动在destpath目录下建立子目录mypackage,并将
生成的.class文件都放到destpath/mypackage下。