1 基本概念:对象、类、属性和方法
1.对象
真实世界的对象有两个共同的特征:它们都有状态和行为。
识别真实世界对象的状态和行为是从OOP的角度开始思考的好方法。
状态:对象中包含的数据,在Java中,这些是对象的字段
行为:对象支持的操作,在Java中,这些操作称为方法。方法只是面向对象的功能代言,调用方法=调用函数
2.类
每个对象都有一个类,类定义方法和字段。方法和字段统称为成员
类定义了类型和实现,类型≈ 可以使用对象的位置,实现≈ 对象的工作方式
粗略地说,类的方法是其应用程序编程接口(API)——定义用户如何与实例交互
3.类中的静态/非静态变量、方法
类成员变量:与类关联的变量,而不是与类的实例关联的变量。
类成员方法:将方法与类相关联
要引用类变量和方法,请将类的名称和类方法或类变量的名称之间加上句点(‘.’)。(static修饰,如public static int test() test.s;tset.func() )
实例方法和实例变量:不是类方法或类变量的方法和变量
要引用实例方法和变量,必须引用类实例中的方法和变量
总结:
类变量和类方法与一个类关联,并且每个类发生一次。使用它们不需要创建对象。
类的每个实例都会出现一次实例方法和变量。
静态方法不与类的任何特定实例相关联,而实例方法(声明时没有Static关键字)必须在特定对象上调用
2 接口和枚举
1.类与接口:定义和实现ADT
接口之间可以继承与扩展 ,一个类可以实现多个接口(从而具备了多个接口中的方法),一个接口可以有多种实现类。接口:确定ADT规约;类:实现ADT。也可以不需要接口直接使用类作为ADT,既有ADT定义也有ADT实现
实际中更倾向于使用接口来定义变量
2.为什么接口有多种实现
不同的性能:选择最适合使用的实现
不同的行为:选择符合需要的实现。行为必须符合接口规范(“合同”)
通常表现和行为都有所不同:提供功能;性能权衡
示例:哈希集、树集
打破了抽象边界,接口定义中没有包含constructor,也无法保证所有实现类中都包含了同样名字的constructor。 因而,客户端需要知道该接口的某个具体实现类的名字
3.默认方法
在基于抽象的典型设计中,一个接口有一个或多个实现,如果在接口中添加一个或多个方法,接口中的每个方法在所有实现类中都要实现。
默认接口方法是处理此问题的有效方法。通过default方法,在接口中统一实现某些功能,无需在各个类中重复实现它。接口中默认方法的最典型用法是以增量式的为接口增加额外的功能而不破坏已实现的类
4.枚举
有时,类型有一组小而有限的不变值,例如:一年中的月份:1月、2月、…、11月、12月
一周中的几天:周一、周二、…、周六、周日
当值集很小且有限时,将所有值定义为命名常量(称为枚举)是有意义的。Java具有枚举构造。
4 封装和信息隐藏
将设计良好的模块与设计不好的模块区分开来的一个最重要的因素是它对其他模块隐藏内部数据和其他实现细节的程度
设计良好的代码隐藏了所有实现细节,清晰地将API与实现分离,模块仅通过API进行交流
对彼此的内部工作方式视而不见,这样的称为信息隐藏或封装(封装)。
使用接口类型声明变量。客户端仅使用接口中定义的方法,客户端代码无法直接访问属性
但是还存在问题:可以直接访问非接口成员;本质上,这是自愿的信息隐藏。
修改对成员间可见性:public,protected,private
private–只能从声明类访问
protected–可从声明类的子类(和包内)访问
public–可从任何地方访问
仅提供客户要求的功能,所有其他成员应为private。可以随时将private成员公开,而不会破坏客户,但反之亦然。
5 继承与重写
1.重写
可重写方法:允许重新实现的方法。
在Java中,默认情况下方法是可重写的,即没有特殊关键字。
严格继承:子类只能向超类添加新方法,不能覆盖它们。如果在Java程序中无法覆盖某个方法,则必须在其前面加上关键字final。
要注意子类函数的访问修饰权限不能少于父类的。
final字段:防止在初始化后重新分配到该字段
final方法:防止重写该方法
final类:防止类被继承
方法重写要点:
方法重写需满足:
父类中的方法 和 子类中的方法
方法名必须一致
方法参数必须一致
子类的方法的访问修饰符不能缩小访问权限
5.返回值类型要一致(子类的返回值类型 也可以是 父类的返回值类型的子类)
实际执行时调用哪个方法,运行时决定。 如果父类的对象用于调用该方法,则将执行父类中的版本;如果子类的对象用于调用该方法,则将执行子类中的版本。
注:大多数子类可以直接继承父类的这个方法,而少数需要修改这个方法功能的可以进行重写实现自己的setSerialNr方法
当子类包含重写超类方法的方法时,它还可以使用关键字super来调用超类方法。
注:意思是说,子类的这个方法不完全是父类的setSerialNr方法所以需要重写,但其中包括父类的setSerialNr方法内容(也就是在父类setSerialNr方法基础上加内容)那么就可以用super()
第二个构造函数的意思是我们在CheckingAccount c=new CheckingAccountImpl(initialBalance)时,它会调用CheckingAccountImpl的另一构造方法
CheckingAccountImpl(initialBalance,500)
2.抽象类
抽象方法:有签名但没有实现的方法(也称为抽象操作),由关键字abstract定义
抽象类:包含至少一个抽象方法的类称为抽象类
接口:只有抽象方法的抽象类,接口主要用于系统或子系统的规范。实现由子类或其他机制提供。
具体类->抽象类->接口
6 多态、子类型、重载
1.三种形式的多态
A.特殊多态性:当一个功能根据有限的单独指定的类型和组合表示不同的和潜在的异构实现时。许多语言使用方法重载支持特殊多态性。
B.参数多态性:当编写代码时没有提及任何特定类型,因此可以透明地与任何数量的新类型一起使用。在面向对象编程社区中,这通常被称为泛型或泛型编程。
C.子类型(也称为子类型多态性或包含多态性):当一个名称表示由某个公共超类相关的许多不同类的实例时。
总结:
一个方法可以有多个同名的实现(方法重载)
一个类型名字可以代表多个类型(泛型编程)
.一个变量名字可以代表多个类的实例(子类型)
2.特殊多态性和重载
(1)特殊型多态
当一个函数在几个不同的类型上工作(可能不会显示出共同的结构)并且可能以不相关的方式对每个类型进行操作时,就会获得特殊多态性。
(2)重载
1,重载特点
重载方法允许在类中重用相同的方法名称,但使用不同的参数(以及可选地,使用不同的返回类型)。
重载方法通常意味着对调用方法的人要友好一些,因为代码承担了处理不同参数类型的负担,而不是在调用方法之前强制调用方进行转换。
2.重载是一种静态多态
根据参数列表进行最佳匹配
函数调用时进行静态类型检查
在编译阶段时决定要具体执行哪个方法 (static type checking)
与之相反,重写(区分重载与重写)的方法则是在运行时进行动态检查
4.方法重载实例
5.Overload也可以发生在父类和子类之间
要调用的方法的哪个重写版本是在运行时根据对象类型决定的,但要调用的方法的哪个重载版本是根据编译时传递的参数的引用类型决定的。
6.重写重载对比
重写:
1.发生在父类与子类之间
2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
3.访问修饰符的权限一定要大于被重写方法的访问修饰符(public>protected>default>private)
4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
重载:
1.重载Overload是一个类中多态性的一种表现
2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准
当派生类重载原始方法时,它仍然从基类继承原始方法(比如父类有p(int i)而子类是p(double i),子类对象可以同时使用这两种方法。)
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
3.参数多态性与泛型编程
1.定义
参数多态性是指方法针对多种类型时具有同样的行为(这里的多种类型应具有通用结构),此时可使用统一的类型变量表达多种类型。在运行时根据具体指定类型确定具体类型(编译成class文件时,会用指定类型替换类型变量“擦除”)。在JAVA里叫做泛型
泛型编程是一种编程风格,其中数据类型和函数是根据待指定的类型编写的,随后在需要时根据参数提供的特定类型进行实例化。
2.使用泛型变量的三种形式:泛型类、泛型接口和泛型方法
类型变量是非限定标识符。使用菱形运算符<>,帮助声明类型变量。例如
public interface List 泛型接口
public class Entry<KeyType, ValueType> 泛型类
List ints = new ArrayList();
泛型类/接口,是在实例化类的时候指明泛型的具体类型
泛型方法,是在调用方法的时候指明泛型的具体类型
A.泛型类
类中如果声明了一个或多个泛型变量,则为泛型类,这些类型变量称为类的类型参数,泛型类定义了一个或多个用作参数的类型变量。
public class Pair<E> {
private final E first, second;
public Pair(E first, E second) {
this.first = first;
this.second = second;
}
public E first() { return first; }
public E second() { return second; }
}
Client:
Pair<String> p = new Pair<>("Hello", "world");
String result = p.first();
B.泛型接口
如果接口声明一个或多个类型变量,则该接口是泛型的。这些类型变量称为接口的类型参数。定义了一个或多个作为参数的类型变量。所有参数化类型在运行时共享相同的接口。
实例:interface InterfaceName <T,E,…>{
public T methodName();
…
}
※例子:Set
Set是泛型类型的一个示例:一种类型,其规范是根据稍后要填充的占位符类型。
我们没有为Set、Set等编写单独的规范和实现,而是设计和实现了一个Set。
实现Set:
① 泛型接口,非泛型的实现类
public class Charset implements Set
②泛型接口,泛型的实现类
public class HashSet implements Set
*C.泛型方法
实例:public T genericMethod(Class tClass) {
T instance = tClass.newInstance();
return instance;
}
//普通类中的泛型方法
public class GenericTest {
public void normalMethod(…){ …}
public <T> T genericMethod(…){
T var;
….;
} }
//泛型类中的泛型方法
class GenericTest<T>
{
//下面的T同所在类的类型变量一致,show1不是泛型方法
public void show1(T t)
{
System.out.println(t.toString());
}
//下面的E是新的类型变量,只适用于此方法,show2是泛型方法
public <E> void show2(E t)
{
System.out.println(t.toString());
}
//下面的T是新的类型变量,同类的类型变量无关(即使名字一样)
//show3是泛型方法
public <T> void show3(T t)
{
System.out.println(t.toString());
}
}
静态方法不能使用所在泛型类定义的泛型变量
如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
D.关于泛型的一些特点
1.可以有多个类型变量
Map<E, F>, Map<String, Integer>
2.Wildcards 通配符
– List<?> list = new ArrayList();
– List<? extends Animal>
– List<? super Animal>
3.删除泛型类型信息(即仅编译时)
无法使用instanceof检查泛型类型,运行时泛型消失了!4.无法创造泛型数组
– Pair[] foo = new Pair[42]; // won’t compile
4.子类型多态性
子类型多态:
不同类型的对象可以统一的处理而无需区分。每个对象根据其类型进行操作(例如,如果添加新类型的帐户,则客户端代码不会更改)
每个类只能直接扩展一个父类;一个类可以实现多个接口。
在Java中声明子类型(例如,实现接口),则必须确保子类型的规范至少与父类型的规范一样强。
声明一个实现接口的类时,Java编译器会确保a中的每个方法都以兼容的类型签名出现在B中。如果不实现A中声明的所有方法,类B就无法实现接口A。
7.Java中一些重要的对象方法
可以被重写的对象方法:
▪ equals()–如果两个对象“相等”,则为true
▪ hashCode()–用于哈希映射的哈希代码
▪ toString()–可打印的字符串表示法,丑陋且缺乏信息,始终覆盖,除非知道它不会被调用
▪ equals&hashCode–标识语义,如果需要值语义,则必须重写
public class Name {
private final String first, last;
public Name (String first, String last) {
if (first == nu11川last == nu11)
throw new NullPointerException() ;
this.first = first; this.last = last ;
}
public boolean equals (Name o)
{
return first. equals(o. first) && last. equals (o.1ast) ;
}
public int hashCode ()
{
return 31 * first. hashCode() + last. hashCode () ;
public static void main (String[] args) {
Set <Name> s = new HashSet<> () ;
s . add (new Name ( "Mickey","Mouse")) ;
System. out. println (
s. contains (new Name ("Mickey", "Mouse"))) ;
}
//Name重写了哈希代码,但没重写equals!因此,这两个Name实例是不相等的。
//将重载的equals方法替换为重写的equals方法。
@Override
public boolean equals(0bject o)
{
if (!(o instanceof Name))
return false;
Name n = (Name) o;
return n. first . equals(first) && n.last . equals(last);
}
9.设计好的类
1.如何写不可变类
不提供任何变体
▪ 确保不能重写任何方法
▪ 将所有字段设为final字段
▪ 将所有字段设置为private
▪ 确保任何可变组件的安全性(避免重复暴露)(defensive copy)
▪ 实现toString()、hashCode()、clone()、equals()等。
2.不可变类实例
3.什么时候让类可变
1.总是,除非有充分的理由不这样做
▪始终使小的“值类”不可变!示例:颜色、电话号码、单位
–Date和Point都是错误的!
–通常使用long而不是Date
2.类表示状态更改的实体
迭代器、匹配器、集合
–进程类-线程、计时器
3.如果类必须是可变的,则最小化可变性
构造函数应完全初始化实例
避免重新初始化方法