第6章 访问权限控制
1 访问权限控制的等级,从最大权限到最小权限依次为:public,protected,包访问权限(没有关键字)和private
2 如果想要使用类,可以在import语句中导入该类或使用该类的全名来指定
// 使用ArrayList的一种方法是使用其全名java.util.ArrayList来指定
public static void main(String[] args){
java.util.ArrayList list = new java.util.ArrayList();
}
// 可以使用import导入这个类,而不需要写全名
import java.util.ArrayList
public static void main(String[] args){
ArrayList list = new ArrayList();
}
3 如果使用package语句,它必须是文件中除注释外的第一句程序代码,在文件起始处写 package xxxx ; 表示你声明该编译单元中public类名称是xxxx类库的一部分
4 Java解释器的运行过程:首先,找出环境变量CLASSPATH包含一个或多个目录,用作查看.class文件的根目录。从根目录开始,解释器获取包的名称并将每个句点替换成反斜杆,以从CLASSPATH根中产生一个路径名称(即,package foo.bar.baz变成foo/bar/baz)。得到的路径会与CLASSPATH中的各个不同的项相连接,解释器就在这些目录中查找与你所要创建的类名称相关的.class文件。
5 从java2开始,包名都是小写,如package com.baidu.tt
6 除非要使用包里的所有类,可以使用星号导入全部。最好还是单个导入避免冲突。
7 如果不提供任何访问权限修饰词,即没有public,protected和private , 则意味着它是“包访问权限”。
8 关键字protected处理的是继承的概念。子类可以访问父类的protected成员变量或成员函数
9 代码风格:将public成员前面,后面跟protected,包访问权限和private成员
10 类的访问权限限制:
- 每个编译单元(文件)都只能有一个public类
- public的类名必须完全与该编译单元的文件名一样,包括大小写
- 编译单元内完全不带public类也是可以的,这种不太常见
11 类既不可以是private,也不可以是protected,只能是包访问权限或public
第7章 复用类
1 每个非基本类型的对象都有一个toString()方法。而且当编译器需要一个String而你只有一个对象时,该方法就会被调用。
2 当创建一个类时,就是在继承,如果未明确指出要从其他类中继承,就是在隐式地从Java的标准根类Object进行继承。Object是所有类的父类。
3 继承是用extends关键字,语法为 class A extends B { }
4 可以为每个类都创建一个main()方法,可使每个类的单元测试变得简单易行。
class Soap{
private String s = "Cleanser";
public void append(String a){ s += a;}
public void dilute(){ append(" dilute()");}
public void apply(){ append(" apply()");}
public void scrub(){ append(" scrub()"); }
public String toString(){
return s;
}
public static void main(String[] args){ // main()函数
Cleanser x = new Cleanser();
x.dilute();x.apply();x.scrub();
System.out.println(x);
}
}
public class Chocolate extends Soap {
public void scrub(){
append(" Chocolate.scrub()");
super.scrub();
}
public void foam(){ append(" foam()");}
public static void main(String[] args){ // main()函数
Chocolate c = new Chocolate();
c.dilute();
c.apply();
c.scrub();
c.foam();
System.out.println(c);
System.out.println("Testing base class: ");
Cleanser.main(args);
}
}
补充:即使一个程序中含有多个类,也只有命令行调用的那个类的main( )方法会被调用,如命令行是 java Detergent , 那么Detegent.main( )将会被调用。
5 Java用super关键字表示父类,super.func()表示调用父类的函数func()
6 Java会自动在子类的构造器中先调用父类构造器,找不到会报错。默认的构造器都不会带参数, 子类会自动调用父类的默认构造器,如果父类没有默认构造器,则需要用super显式地调用父类的构造器。
class Art2{
Art2(int i){
System.out.println("Art constructor "+i);
}
}
class Drawing2 extends Art2{
Drawing2(int i){
super(i); // 调用构造器Art2(int i)
System.out.println("Drawing constructor "+i);
}
}
public class Chocolate extends Drawing2 {
Chocolate(){
super(11); // 调用构造器Drawing2(int i)
System.out.println("Chocolate constructor");
}
public static void main(String[] args){
Chocolate c = new Chocolate();
}
}
7 Java中没有析构函数
8 子类对父类的函数的重写,Java SE5 新增了@Override注解,添加了这个注解的函数只能被覆写,如果不小心重载这个函数,编译器会报错
public class Test1 {
@Override // 如果别处有重载该函数,会抛出异常。该函数只能被重写
void doh(int i) {
System.out.println(i);
}
void doh(char i) {
System.out.println(i);
}
public static void main(String[] args) {
Test1 t = new Test1();
t.doh(1);
}
}
class Homer2{
char doh(char c){
System.out.println("doh(char)");
return 'd';
}
float doh(float f){
System.out.println("doh(float)");
return 1.0f;
}
}
class Milhouse{}
class Bart2 extends Homer2{
void doh(Milhouse m){ // 对父类的函数重载
System.out.println("doh(Milhouse)");
}
}
public class Chocolate{
public static void main(String[] args){
Bart2 b = new Bart2();
b.doh(1);
b.doh('x');
b.doh(1.0f);
b.doh(new Milhouse());
}
}
9 “is - a”的关系是用继承来表达,“has - a”的关系是用组合来表达。
10 向上转型,tune( Intrument i )函数将Instrument的子类Chocolate引用转换为Instrument引用,称之为向上引用。
class Instrument{
public void play(){}
static void tune(Instrument i){
i.play();
}
}
public class Chocolate extends Instrument{
public static void main(String[] args){
Chocolate c = new Chocolate();
Instrument.tune(c);
}
}
11 在Java中,编译期常量是指基本数据类型,并且以关键字final表示,定义时必须进行赋值。
12 一个既是static又是final的变量只占据一段不能改变的存储空间,既是static又是final的变量一般用大写表示。
public static int getInt(){
return 12;
}
private Random r = new Random();
private final static int ONE = 1;
private final static int TWO = getInt(); // 可以动态赋值,但必须是static
private final int T = 12;
private final int K = r.nextInt(12);
13 Java允许生成“空白final”,即被声明为final但又未给定初值的成员变量,可以在使用前赋值。
class Poppet{
private int i;
Poppet(int i){
this.i = i;
}
}
public class Chocolate{
private final int i = 0; // Initialized final
// private final int k; // error
private final int j; // Blank final
private final Poppet p; // Blank final
public Chocolate(){
j = 1;
p = new Poppet(1);
}
public Chocolate(int x){
j = x;
p = new Poppet(x);
}
public static void main(String[] args){
new Chocolate();
new Chocolate(4);
}
}
注:类的final成员变量不会被赋默认值,必须要在域的定义处或所有构造器中用表达式对final成员变量进行赋值。Java确保final在使用前必须被初始化。
14 Java允许在参数列表中以声明的方式将参数声明为final,表示在函数中无法修改参数引用所指向的对象
class Gizmo{}
public class Chocolate{
void with(final Gizmo g){
g = new Gizmo(); // error,g为final参数,不能改变
}
public static void main(String[] args){
}
}
15 使用final函数的原因有2个。第一个是把方法锁定,以防任何继承类修改该函数,不会被覆盖。重载是没问题的;第二个原因是效率,编译器对final函数的调用都转为内嵌调用,但只对代码小的函数内嵌调用会比较有用。(第二个原因已渐渐不被使用,因为JVM已足够可以处理效率问题)
16 因为类的private函数,子类是没有权限调用,所以private函数默认都是final。
class WithFinals{
private final void f(){System.out.println("WithFinals.f()");}
private void g(){System.out.println("WithFinals.g()");}
}
class OverridingPrivate extends WithFinals{
private final void f(){ System.out.println("OverringPrivate.f()"); }
private void g(){ System.out.println("OverridingPrivate.g()");}
}
class OverridingPrivate2 extends OverridingPrivate{
public final void f(){ System.out.println("OverringPrivate.f()"); }
public void g(){ System.out.println("OverridingPrivate.g()");}
}
public class Chocolate{
public static void main(String[] args){
OverridingPrivate2 o = new OverridingPrivate2();
o.f();
o.g();
OverridingPrivate o2 = o;
o2.f(); // error
o2.g(); // error
WithFinals wf = o;
wf.f(); // error
wf.g(); // error
}
}
17 当将某个类定义为final时,表示该类不能被继承。final class A { . . . } 。 由于final类不能被继承,所以该类中的所有方法都隐式指定为final,无法覆盖。
18 Java中所有事物都是对象。
19 继承与初始化
class Insect {
private int i = 9;
protected int j;
Insect() {
System.out.println("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 = printInit("static Insect.x1 initialized");
static int printInit(String s) {
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect{
private int k = printInit("Beetle.k initialized");
public Beetle() {
System.out.println("k = "+k);
System.out.println("j = " + j);
}
private static int x2 = printInit("Beetle.x2 initialized");
public static void main(String[] args) {
System.out.println("Beetle constructor");
Beetle b = new Beetle();
}
}
输出
static Insect.x1 initialized
Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
在Beetle上运行java时,先是试图访问Beetle.main( ),这样加载器开始启动并找出Beetle类的编译代码(在Beetle.class文件中),加载时发现有基类,先对基类进行加载。
第8章 多态
1 动态绑定,在运行时才会判断对象的类型。Java中除了static函数和final函数外,其他所有函数都是后期绑定
class A2{
void play(){ System.out.println("A");}
}
class B extends A2{
void play(){ System.out.println("B");}
}
class C extends A2{
void play(){ System.out.println("C");}
}
public class Chocolate{
public static void tune(A2 a){
a.play();
}
public static void main(String[] args){
A2 a = new B();
a.play(); // 动态绑定,调用B的play()
tune(a); // 动态绑定,传入B类引用
}
}
2 private函数无法被覆盖
public class Chocolate{
private void f(){ System.out.println("private f()"); }
public static void main(String[] args){
Chocolate a = new B();
a.f(); // 调用Chocolate的f()函数,因为是private类型,无法被覆盖
}
}
class B extends Chocolate{
public void f(){ System.out.println("public f()"); }
}
3 成员变量和静态方法都不具有多态性。当Sub对象转型为Super引用时,任何成员变量的访问操作都将由编译器解析,因此不会多态。Super.field和Sub.field分配了不同的存储空间,所以Sub实际由两个field变量。静态函数则是跟类相关。
class Super{
public int field = 0;
public int getField(){ return field; }
}
class Sub extends Super{
public int field = 1;
public int getField(){ return field;}
public int getSuperField(){ return super.field;}
}
public class Chocolate{
private void f(){ System.out.println("private f()"); }
public static void main(String[] args){
Super sup = new Sub();
System.out.println("sup.field = "+sup.field + " , sup.getField() = "+sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = "+sub.field + " , sub.getField() = "+sub.getField()+" , sub.getSuperField() = "+sub.getSuperField());
}
}
输出
sup.field = 0 , sup.getField() = 1
sub.field = 1 , sub.getField() = 1 , sub.getSuperField() = 0
class StaticSuper{
public static String staticGet(){
return "Base staticGet()";
}
public String dynamicGet(){
return "Derived dynamicGet()";
}
}
class StaticSub extends StaticSuper{
public static String staticGet(){
return "Derived staticGet()";
}
public String dynamicGet(){
return "Derived dynamicGet()";
}
}
public class Chocolate{
public static void main(String[] args){
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
}
输出
Base staticGet() // 因为是静态函数,没有动态调用子类的函数
Derived dynamicGet()
4 构造器其实是隐式的static函数。
5 所有构造器的调用顺序
- 调用基类构造器
- 按声明顺序初始化成员变量
- 调用自己的构造器主体
class Meal {
private Bread b = new Bread();
Meal(){System.out.println("Meal");}
}
class Bread {
Bread(){System.out.println("Bread");}
}
class Cheese {
Cheese(){System.out.println("Cheese");}
}
class Lettuce {
Lettuce(){System.out.println("Lettuce");}
}
class Lunch extends Meal{
private Lettuce l = new Lettuce();
Lunch(){System.out.println("Lunch");}
}
class ProtableLunch extends Lunch{
ProtableLunch(){System.out.println("ProtableLunch");}
}
public class Chocolate extends ProtableLunch{
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
private Chocolate(){ System.out.println("Chocolate");}
public static void main(String[] args){
new Chocolate();
}
}
输出
Bread
Meal
Lettuce
Lunch
ProtableLunch
Bread
Cheese
Lettuce
Chocolate
6 记住销毁的顺序应该要跟初始化顺序相反。
7 构造器内部有多态函数的问题。如下所示Glyph的draw()会被RoundGlyph的draw()覆盖。初始化的实际过程如下
- 在其他任何事物发生之前,将分配给对象的存储空间初始化为二进制的零
- 调用基类的构造器,调用被覆盖后的draw()函数,但radius值为0
- 按照声明的顺序初始化成员变量
- 调用自身的构造器
class Glyph{
void draw(){ System.out.println("Glyph.draw()");}
Glyph(){
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius = 1;
RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph.RoundGlyph(), radius = "+radius);
}
void draw(){
System.out.println("RoundGlyph.draw(), radius = "+radius);
}
}
public class Chocolate{
public static void main(String[] args){
new RoundGlyph(4);
}
}
输出
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 4
注:编写构造器有一条有效的准则:用尽可能简单的方法使对象进入正常状态,尽量避免调用其他函数。
8 Java SE5添加了协变返回类型,表示子类的被覆盖函数可以返回父类函数返回类型的某种子类类型。
class Grain{
public String toString(){ return "Grain"; }
}
class Wheat extends Grain{
public String toString(){ return "Wheat"; }
}
class Mill{
Grain process(){ return new Grain(); }
}
class WheatMill extends Mill{
Wheat process(){ return new Wheat();}
}
public class Chocolate{
public static void main(String[] args){
Mill m = new Mill();
Grain g = m.process();
System.out.println(g);
m = new WheatMill();
g = m.process();
System.out.println(g);
}
}
输出
Grain
Wheat
9 向上转型会丢失具体的类型信息。可以采用向下转型
class Useful{
public void f(){}
public void g(){}
}
class MoreUseful extends Useful{
public void f(){}
public void g(){}
public void u(){}
public void v(){}
public void w(){}
}
public class Chocolate{
public static void main(String[] args){
Useful[] x = {
new Useful(),
new MoreUseful()
};
x[0].f(); // ok
x[1].g(); // ok
x[1].u(); // error 因为x[1]向上转型,丢失了具体类型信息,无法调用u()函数
((MoreUseful)x[0]).u(); // error,x[0]是Useful类型,不能向下转型为MoreUseful
((MoreUseful)x[1]).u(); // ok,x[1]向下转型为MoreUseful
}
}
第9章 接口
1 只有声明但没有函数体的函数称为抽象函数。如 abstract void f( ) 。 有抽象方法的类即为抽象类,抽象类不能创建对象,会报错。
2 如果一个类继承抽象类,并想创建该类对象,那么该子类必须要为抽象类的所有抽象函数提供函数定义。如果没有全部提供函数定义,该类还是抽象类。
3 抽象类并不要求所有的函数都是抽象函数
abstract class Instrument { // 抽象类
private int i;
public abstract void play(int n);
public String what(){ return "Instrument";}
public abstract void adjust();
}
class Wind extends Instrument{
public void play(int n){
System.out.println("ok");
}
public String what(){ return "Instrument";}
public abstract void adjust(){};
}
4 interface关键字定义一个完全抽象的类,即接口。所有函数只有声明没有函数体。接口被用来建立类与类之间的协议。
5 可以在interface关键字前面加public,仅限于该接口与其同名的文件中被定义。接口的访问权限只能是public或默认包权限。接口也可以包含成员变量,这些成员变量自动是static和final的。成员函数也自动为public。
6 实现接口是用implements。必须要实现所有函数的函数体
interface Instrument{
int VALUE = 5; // static & final
void play(int n); // automatically public
void adjust(); // automatically public
}
class Wind implements Instrument{ // 实现接口必须要定义所有函数的函数体
public void play(int n){ System.out.println("ok");}
public void adjust(){}
}
7 只能继承一个类,但可以实现多个接口。需要将所有的接口名都置于implements关键字之后,用逗号将它们一一隔开。
interface CanFight{
void fight();
}
interface CanSwim{
void swim();
}
interface CanFly{
void fly();
}
class ActionCharacter{
public void fight(){}
}
class Hero extends ActionCharacter implements CanFight,CanSwim,CanFly{
//public void fight(){};
public void swim(){};
public void fly(){};
}
public class ArrayApp {
public static void t(CanFight x){ x.fight(); }
public static void u(CanSwim x){ x.swim(); }
public static void v(CanFly x){ x.fly(); }
public static void w(ActionCharacter x){ x.fight(); }
public static void main(String[] args){
Hero h = new Hero();
t(h);
u(h);
v(h);
w(h);
}
}
Hero类继承了ActionCharacter和实现接口CanFight,CanSwim和CanFly。Hero必须要实现所有接口的函数,但接口CanFight的fight()跟ActionCharacter的fight()一样,所以Hero无须再实现CanFight的fight()函数。t(),u(),v(),w()函数将父类和接口作为参数,所以Hero对象可以向上转型。
8 使用接口的核心原因:为了能够向上转型为多个基类型。第二个原因是防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
9 通过继承来扩展接口。类只能继承一个类,但接口可以继承多个接口
interface Monster{
void menace();
}
interface DangerousMonster extends Monster{
void destroy();
}
interface Lethal{
void kill();
}
interface Vampire extends DangerousMonster,Lethal{ // 接口可以继承多个接口
void drinkBlood();
}
10 接口中的任何变量都自动是static和final。在接口中定义的成员变量不能是空final,但可以被非常量表达式初始化。
interface Monster{
Random RAND = new Random(47);
int RANDOM_INT = RAND.nextInt(12);
long RANDOM_LONG = RAND.nextLong()*10;
float D = 4.32f;
}
11 接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式是工厂方法设计模式。
interface Service{
void method1();
void method2();
}
interface ServiceFactory{
Service getService();
}
class Implementation1 implements Service{
Implementation1(){}
public void method1(){ System.out.println("Implementation1 method1");}
public void method2(){ System.out.println("Implementation1 method2");}
}
class Implementation1Factory implements ServiceFactory{
public Service getService(){
return new Implementation1();
}
}
class Implementation2 implements Service{
Implementation2(){}
public void method1(){ System.out.println("Implementation2 method1");}
public void method2(){ System.out.println("Implementation2 method2");}
}
class Implementation2Factory implements ServiceFactory{
public Service getService(){
return new Implementation2();
}
}
public class ArrayApp {
public static void serviceConsumer(ServiceFactory fact){
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args){
serviceConsumer(new Implementation1Factory());
serviceConsumer(new Implementation2Factory());
}
}