学习笔记 之 JAVA编程思想(Thinking in Java)

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

我要开始读《Java编程思想》这本八百多页的大书了,可能要花两三个月的时间,好好记笔记,加油!!
提示:以下是本篇文章正文内容,下面案例可供参考

第一章 对象导论

1.面向对象语言的五个基本特性:
(1)万物皆为对象(数据 + 操作)
(2)程序是对象的集合,它们通过发送消息来告知彼此所要做的。可以把消息想象为对某个特定方法的调用请求
(3)每个对象都有自己的由其他对象所构成的存储。可以在程序中构建复杂的体系,同时将其复杂性隐藏在对象的简单性背后
(4)每个对象都拥有其类型
(5)某个特定类型的所有对象都可以接收同样的消息。
简单来说,对象具有状态、行为和标识

2.每个对象都有一个接口,每个对象都提供服务。“有一个”说的是组合关系,“是一个”说的是继承关系。在建立新类时,应该首先考虑组合,因为它更加灵活。

3.当继承现有类型时,也就创造了新的类型。这个新的类型不仅包括现有类型的所有成员(尽管private成员被隐藏了起来,并且不可访问),而且更重要的是它复制了基类的接口。也就是说,所有发送给基类对象的消息同时也可以发送给导出类对象。

4.有两种方法可以使基类和导出类产生差异:直接在导出类中添加新方法;覆盖。如果只是覆盖了基类中的方法而没有添加新方法,这就意味着导出类和基类是完全相同的类型,因为它们具有完全相同的接口。结果可以用一个导出类对象来完全替代一个基类对象。称之为替代原则,称之为“是一个”关系。如果在导出类中添加了新的接口元素,即拓展了接口,描述为“像是一个”关系。

5.多态性:一个非面向对象编程的编译器产生的函数调用会引起所谓的“前期绑定”,即编译器将产生对一个具体函数的名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。然而在OOP中,使用了“后期绑定”的概念,程序直到运行时才能够确定代码的地址,当向对象发送消息时,被调用的代码直到运行时才能确定。编译器确保被调用的方法的存在,并对调用参数和返回值执行类型检查(无法提供此类保证的语言被称为是弱类型的),但是并不知道将被执行的确切代码。在某些语言中,必须明确地声明希望某个方法具备后期绑定属性所带来的灵活性(C++ 是使用virtual关键字来实现的).在这些语言中,方法在默认情况下不是动态绑定的。而在Java中,动态绑定是默认行为,不需要添加额外的关键字来实现多态。**把将导出类看做是它的基类的过程称为向上转型,向下转为更为具体的类型称为向下转型。**向上转型是安全的,除非确切知道所要处理的对象的类型,否则向下转型几乎是不安全的。

第二章 一切都是对象

1.Java用引用来操作对象,引用底层就是一个指针。Java中的传递都是“按值传递”的,也就是传递一个引用的副本给方法,这样在方法内部实际上是在对源对象的操作。

2.有五个不同的地方可以存储数据
(1)寄存器,这是最快的存储区,位于CPU内部
(2)堆栈,位于通用RAM(随机访问存储器)中,通过堆栈指针可以从处理器那里获得直接支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时,JAVA系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些Java数据存储在堆栈中——特别是对象引用,但是Java对象并不存储于其中
(3)堆。一种通用的内存池(也位于RAM区),用于存放所有的Java对象。堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活了多长时间,因此在堆里分配存储有很大的灵活性。当然,用堆进行存储分配和清理可能比用堆栈进行存储分配需要更多的时间。
(4)常量存储。常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。
(5)非RAM存储。如果数据完全存活在程序之外,那么它们可以不受程序的任何控制,在程序没有运行时也可以存在。其中两个基本的例子是流对象和持久化对象。在流对象中,对象转化成字节流,通常被发送给另一台机器。在持久化对象中,对象被存放于磁盘上,因此,即使程序终止,它们仍可以保持自己的状态。

3.特例,基本数据类型是存储在堆栈中的。所有数值类型都有正负号。boolean类型所占存储空间的大小没有明确指定,仅定义为能够取字面值true或false。 Java提供了两个用于高精度计算的类:BigInteger和BigDecimal,BigInteger支持任意精度的整数,BigDecimal支持任意精度的定点小数。

4.在C/C++里将一个较大作用域的变量“隐藏”起来的做法,在Java里是不允许的。因为Java设计者认为这样做会导致程序混乱。

5.初始化:类中的基本成员会有其默认值,但是方法中的局部变量可能会是任意值,且未被初始化会发生编译错误

public static void main(String[] args) {
	//显示从运行程序的系统中获取的所有“属性”,提供环境信息
	System.getProperties().list(System.out);
	System.out.println(System.getProperty("user.name"));
	System.out.println(System.getProperty("java.library.path"));
}

第三章 操作符

1.equals()方法的默认行为是比较引用,所以除非在自己的新类中覆盖equals()方法,否则不可能表现出我们希望的行为。大多数Java类库都实现了equals()方法,以方便比较对象的内容,而非比较对象的引用。

2.布尔类型可以执行按位"与"、按位"或" 和 按位“异或”,但是不能执行按位“非”操作。对于布尔值,按位操作符具有与逻辑操作符相同的效果,只是它们不会中途“短路”。此外,针对布尔值进行的按位运算为我们新增了一个“异或”逻辑操作符,它并未包括在“逻辑”操作符的列表中。在移位表达式中,不能使用布尔运算。

3.如果对char、byte、short类型的数值进行移位处理,那么在移位进行之前,它们会被转换为int类型,并且得到的结果也是一个int类型的值。只有数值右端低五位才有用,防止超过int型值所具有的位数。“移位”可与“等号”(<<=或>>=或>>>=)组合使用。此时,操作符左边的值会移动由右边的值指定的位数,再将得到的结果赋给左边的变量。 但在进行“无符号”右移位结合赋值操作时,可能会遇到一个问题:如果对byte或short值进行这样的移位运算,得到的可能不是正确的结果。它们会先被转换成int类型,再进行右移操作,然后被截断,赋值给原来的类型,在这种情况下可能得到-1的结果。

第四章 控制执行流程

带标签的循环:
一般的continue会退回最内层循环的开头(顶部),并继续执行
带标签的continue会到达标签的位置,并重新进入紧接在那个标签后面的循环
一般的break会中断并跳出当前循环
带标签的break会中断并跳出标签所指的循环

public class LabeledFor{
	
	public static void main(String[] args) {
		int i = 0;
		outer:
		for(;true;) {
			inner:
			for(;i < 10; i++) {
				System.out.println("i = " + i);
				if(i == 2) {
					System.out.println("continue");
					continue;
				}
				if(i == 3) {
					System.out.println("break");
					i++;	//再次进入循环时不会执行i++的操作,故在这里加上i++
					break;
				}
				if(i == 7) {
					System.out.println("continue outer");
					i++;
					continue outer;
				}
				if(i == 8) {
					System.out.println("break outer");
					break outer;
				}
				for(int k = 0; k < 5; k++) {
					if(k == 3) {
						System.out.println("continue inner");
						continue inner;
					}
				}
			}			
		}
	}
}
/*
i = 0
continue inner
i = 1
continue inner
i = 2
continue
i = 3
break
i = 4
continue inner
i = 5
continue inner
i = 6
continue inner
i = 7
continue outer
i = 8
break outer
*/

第五章 初始化与清理

1.this关键字的使用:
(1)return this; 可以返回当前对象的引用。
(2)使用this关键字在构造器中调用其他构造器。不过只能调用一次,且必须将构造器调用置于最起始处。除了构造器之外,编译器禁止在其它任何地方调用构造器。

2.static关键字,在static方法内部不能调用非静态方法,反过来倒是可以。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。因为调用非静态方法时不能保证对象已经创建了。

3.finalize()方法:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存,可以用finalize()在垃圾回收时刻做一些重要的清理工作。

4.只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,则随着程序的退出,那些资源也会全部交还给操作系统。

5.使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。无论对象是如何创建的,垃圾回收器都会负责释放对象占据的所有内存,除非在分配内存时采用了类似C语言中的做法,而非Java中的通常做法。这种情况主要发生在使用“本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。本地方法目前只支持C和C++,但它们可以调用其他语言写的代码,所以实际上可以调用任何代码。在非Java代码中,也许会调用C的malloc()函数系列来分配存储空间,而且除非调用了free()函数,否则存储空间将得不到释放,从而造成内存泄漏。当然free()是C和C++中的函数,所以需要在finalize()中用本地方法调用它。

6.finalize()的有趣用法

class Book{
	boolean checkedOut = false;
	Book(boolean checkOut){
		checkedOut = checkOut;
	}
	void checkIn() {
		checkedOut = false;
	}
	protected void finalize() {
		if(checkedOut) {
			System.out.println("Error: checked out");
		}
	}
}
public class TerminationCondition{
	
	public static void main(String[] args) {
		Book novel = new Book(true);
		novel.checkIn();
		new Book(true);
		System.gc();
	}
}
//Error: checked out

本例的终结条件是:所有的Book对象在被当作垃圾回收前都应该被签入(checkIn)。但在main方法中,有一本书未被签入。要是没有finalize()来验证终结条件,将很难发现这种缺陷。
System.gc()用于强制进行终结动作。即使不这么做,通过重复地执行程序(假设程序将分配大量的存储空间而导致垃圾回收动作的执行),最终也能找出错误的Book对象

7.垃圾回收器如何工作
(1)引用计数是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1.当引用离开作用域或被置为null时,引用计数减1.虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0时,就释放其占用的空间(但是,引用记数模式经常会在记数值变为0时立即释放对象)。这种方法有个缺陷,如果对象之间存在循环引用,可能会出现“对象应该回收,但引用计数却不为0”的情况。对垃圾回收器而言,定位这样的交互自引用的对象组所需的工作量极大。引用计数常用来说明垃圾收集的工作方式,但似乎从未被应用于任何一种Java虚拟机实现中
(2)自适应的垃圾回收技术:标记—清扫,停止—复制。思想:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有“活”的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是此对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止。你所访问的对象必须都是“活”的,这就解决了“交互自引用的对象组”的问题——这种现象根本就不会被发现,因此也就被自动回收了。
(3)停止—复制:先暂停程序的运行(所以它不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单、直接地分配新空间了。
当把对象从一处搬到另一处时,所有指向它的那些引用都必须修正。位于堆或静态存储区的引用可以直接被修正,但可能还有其它指向这些对象的引用,它们在遍历的过程中才能被找到。
效率比较低:首先得有两个堆,然后得在这两个分离的堆之间来回倒腾,从而得维护比实际需要多一倍的空间。某些Java虚拟机对此问题的处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。
第二个问题在于复制。程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾。尽管如此,复制式回收器仍然会将所有内存自一处复制到另一处,这很浪费。为了避免这种情形,一些Java虚拟机会进行检查,要是没有新垃圾产生,就会转换到另一种工作模式(即“自适应”)。这种模式称为标记—清扫。
(4)标记—清扫:对一般用途而言,“标记—清扫”方式速度相当慢,但是当你知道只会产生少量垃圾甚至不会产生垃圾时,它的速度就很快了。思路:从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制动作。所以剩下的堆是不连续的,垃圾回收器是希望得到连续空间的话,就得重新整理剩下的对象。同样也必须在程序暂停的情况下才能进行

垃圾收集器:“自适应的、分代的、停止—复制、标记—清扫”式的垃圾收集回收器

8.“即时”(Just-In-Time,JIT)编译器技术
这种技术可以把程序全部或部分翻译成本地机器码(这本该是Java虚拟机的工作),程序运行速度因此得到提升。
当需要装在某个类(通常是在为该类创建第一个对象)时,编译器会先找到其.class文件,然后将该类的字节码装入内存。此时有两种方案可供选择:
(1)让即时编译器编译所有代码。缺陷:一是这种加载动作散落在整个程序生命周期内,累加起来要花更多时间。二是会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这将导致页面调度,从而降低程序的速度。
(2)惰性评估,意思是即时编译器只在必要的时候才编译代码。这样从不会被执行的代码也许压根不会被JIT所编译。新版JDK中的Java HotSpot技术就采用了类似方法,代码每次被执行的时候都会做一些优化,所以执行的次数越多,它的速度就越快

9.静态数据的初始化

class Bowl{
	Bowl(int marker){
		System.out.println("Bowl("+marker+")");
	}
	void f1(int marker) {
		System.out.println("f1("+marker+")");
	}
}

class Table{
	static Bowl bowl1 = new Bowl(1);
	Table(){
		System.out.println("Table()");
		bowl2.f1(1);
	}
	void f2(int marker) {
		System.out.println("f2("+marker+")");
	}
	static Bowl bowl2 = new Bowl(2);
	
}

class Cupboard{
	Bowl bowl3 = new Bowl(3);
	static Bowl bowl4 = new Bowl(4);
	Cupboard(){
		System.out.println("Cupboard");
		bowl4.f1(2);
	}
	void f3(int marker) {
		System.out.println("f3("+marker+")");
	}
	static Bowl bowl5 = new Bowl(5);
}
public class StaticInitialization{
	
	public static void main(String[] args) {
		System.out.println("Creating new Cupboard() in main");
		new Cupboard();
		System.out.println("Creating new Cupboard() in main");
		new Cupboard();
		table.f2(1);
		cupboard.f3(1);
	}
	static Table table = new Table();
	static Cupboard cupboard = new Cupboard();
}
/*
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard
f1(2)
f2(1)
f3(1)
*/

10.可变参数列表

package test;

class A{}

public class NewVarArgs {
	static void printArray(Object... args) {
		for(Object obj : args) {
			System.out.println(obj);
		}
		System.out.println();
	}
	public static void main(String[] args) {
		printArray(new Integer(47),new Float(3.14),new Double(11.11));
		printArray(47,3.17f,11.11);
		printArray("one","two","three");
		printArray(new A(),new A(),new A());
		
		printArray((Object[])new Integer[] {1, 2, 3, 4 });
		printArray();		
	}
}

第六章 访问权限控制

1.重构即重写代码,以使得它更可读、更易理解,并因此更具可维护性。对类库而言尤为重要,该类库的消费者必须依赖他所使用的那部分类库,并且能够知道如果类库出现了新版本,他们并不需要改动代码。从另一方面来说,类库的开发者必须有权限进行修改和改进,并确保客户端代码不会因为这些改动而受到影响。为了解决这一问题,Java提供了访问权限修饰词,以供类库开发人员向客户端程序员指明哪些是可用的,哪些是不可用的。作为一名类库设计人员,你会尽可能将一切方法都定位private,而仅向客户端程序员公开你愿意让他们使用的方法。

2.Java解释器的运行过程如下:首先,找出环境变量CLASSPATH,CLASSPATH包含一个或多个目录,用作查找.class文件的根目录。从根目录开始,解释器获取包的名称并将每个句点替换成反斜杠,以从CLASSPATH根中产生一个路径名称。得到的路径会与CLASSPATH中的各个不同的项相连接,解释器就在这些根目录中查找与你所要创建的类名称相关的.class文件

3.类的创建可以采用将public成员置于开头,后面跟着protected、包访问权限和private成员的形式。这样做的好处是类的使用者可以从头读起,首先阅读对他们而言最为重要的部分(即public成员,因为这样可以从文件外部调用它们),等到遇到作为内部实现细节的非public成员时停止阅读。

4.类的修饰符只能是public、abstract、final或 默认,而不能是private和protected

第七章 复用类

1.复用类的方法: 组合和继承
2.可以为每个类都创建一个main()方法。这种在每个类中都设置一个main()方法的技术可使每个类的单元测试都变得简单易行。而且在完成单元测试之后,也无需删除main(),可以将其留待下次测试。
即使一个程序中含有多个类,也只有命令行所调用的那个类的main()方法会被调用。因此,在此例中,如果命令行是java Detergent,那么Detergent.main()将会被调用。即使Cleanser不是一个public类,如果命令行是java Cleanser ,那么Cleanser.main()仍然会被调用。即使一个类只具有包访问权限,其public main()仍然是可以访问的。

class Cleanser{
	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) {
		Cleanser x = new Cleanser();
		x.dilute(); x.apply(); x.scrub();
		System.out.println(x);
	}
}

public class Detergent extends Cleanser{
	public void scrub() {
		append(" Detergent.scrub()");
		super.scrub();
	}
	
	public void foam() { append(" foam()");}
	
	public static void main(String[] args) {
		Detergent x = new Detergent();
		x.dilute();
		x.apply();
		x.scrub();
		x.foam();
		System.out.println(x);
		System.out.println("Testing base class:");
		Cleanser.main(args);
	}
}
/*
Cleanser dilute() apply() Detergent.scrub() scrub() foam()
Testing base class:
Cleanser dilute() apply() scrub()
*/

3.代理是继承与组合之间的中庸之道,因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。

package test;

class SpaceShipControls{
	void up(int velocity) {}
	void down(int velocity) {}
	void left(int velocity) {}
	void right(int velocity) {}
	void forward(int velocity) {}
	void back(int velocity) {}
	void turboBoost() {}
}
public class SpaceShipDelegation{
	private String name;
	private SpaceShipControls controls = new SpaceShipControls();
	
	public SpaceShipDelegation(String name) {
		this.name = name;
	}
	public void back(int velocity) {
		controls.back(velocity);
	}
	public void down(int velocity) {
		controls.down(velocity);
	}
	public void forward(int velocity) {
		controls.forward(velocity);
	}
	public void left(int velocity) {
		controls.left(velocity);
	}
	public void right(int velocity) {
		controls.right(velocity);
	}
	public void turboBoost() {
		controls.turboBoost();
	}
	public void up(int velocity) {
		controls.up(velocity);
	}
	public static void main(String[] args) {
		SpaceShipDelegation protector = new SpaceShipDelegation("NSEA Protector");
		protector.forward(100);
	}

}

可以看到,上面的方法是如何转递给了底层的controls对象,而其接口由此也就与使用继承得到的接口相同了。但是,我们使用代理时可以拥有更多的控制力,因为我们可以选择只提供在成员对象中的方法的某个子集

4.在组合与继承之间选择
组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。即,在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为新类所定义的接口,而非所嵌入对象的接口。为取得此效果,需要在新类中嵌入一个现有类的private对象。
在继承的时候,使用某个现有类,并开发一个它的特殊版本。通常这意味着你在使用一个通用类,并为了某种特殊需要而将其特殊化。
在面向对象编程中,生成和使用程序代码最有可能采用的办法就是直接将数据和方法包装进一个类中,并使用该类的对象。也可以运用组合技术使用现有类来开发新的类;而继承技术其实时不太常用的。到底是该用组合还是继承,一个最清晰的判断办法就是问一问自己是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的;但如果不需要,则应当好好考虑自己是否需要继承。

5.类中所有的private方法都隐式地指定为是final的。由于无法取用private方法,所以也就无法覆盖它。可以对private方法添加final修饰词,但这并不能给该方法增加任何额外的意义。

6.“覆盖”只有在某方法是基类的接口的一部分时才会出现。即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法是private,它就不是基类的接口的一部分。它仅是一些隐藏于类中的程序代码,只不过是具有相同的名称而已。但如果在导出类中以相同的名称生成一个public、protected或包访问权限的方法的话,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时你并没有覆盖该方法,仅是生成一个新的方法。由于private方法无法触及而且能有效隐藏,所以除了把它看成是因为它所归属的类的组织结构的原因而存在外,其它任何事物都不需要考虑到它。

第八章 多态

1.编译器怎么知道基类引用指向的对象是哪个派生类呢?后期绑定
将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定(如果有的话,由编译器和连接程序实现),叫做前期绑定。它是面向过程的语言中不需要选择就默认的绑定方式。
后期绑定的含义是在运行时根据对象的类型进行绑定。后期绑定也叫做动态绑定或运行时绑定。如果一种语言想实现后期绑定,就必须具有某种机制,以便在运行时能判断对象的类型,从而调用恰当的方法。也就是说,编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。
Java中除了static方法和final方法(private 方法属于final方法)之外,其他所有方法都是后期绑定。这意味着通常情况下,我们不必判定是否应该进行后期绑定——它会自动发生。

2.缺陷:域与静态方法,静态方法是与类,而并非与单个对象相关联的。

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 FieldAccess{
	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("sup.field = " + sub.field + " , sup.getField() = " + sub.getField() + " , sub.getSuperField() = " + sub.getSuperField());
	}
}
/*
sup.field = 0 , sup.getField() = 1
sup.field = 1 , sup.getField() = 1 , sub.getSuperField() = 0
*/

当Sub对象转型为Super引用时,任何域访问操作都将由编译器解析,因此不是多态的。本例中Super.field和Sub.field分配了不同的存储空间。这样Sub实际上包含两个称为field的域;它自己的和他从Super处得到的。然而,在引用Sub的field属性时所产生的默认域并非Supper版本的域。因此,为了得到Super.field,必须显示地指明super.field.

3.构造器并不具有多态性(它们实际上是static方法,只不过该static声明是隐式的)。

4.构造器内部的多态方法的行为

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.RoungGlyph(), radius = " + radius);
	}
	void draw() {
		System.out.println("RoundGlyph.draw(), radius = " + radius);
	}
}

public class PloyConstructors{
	public static void main(String[] args) {
		new RoundGlyph(5);
	}
}
/*
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoungGlyph(), radius = 5
*/

5.协变返回类型。在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型

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 CovariantReturn {
    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);
    }
}

6.用继承进行设计。准则:用继承表达行为间的差异,并用字段表达状态上的变化。下面的例子通过继承得到两个不同的类,用于表达act()方法的差异;而Stage通过运用组合使自己的状态发生变化。在这种情况下,这种状态的改变也就产生了行为的改变。采用了状态设计模式

class Actor{
    public void act(){}
}

class HappyActor extends Actor{
    @Override
    public void act() {
        System.out.println("HappyActor");
    }
}

class SadActor extends Actor{
    @Override
    public void act() {
        System.out.println("SadActor");
    }
}

class Stage{
    private Actor actor = new HappyActor();
    public void change(){actor = new SadActor();}
    public void performPlay(){actor.act();}
}

public class Transmogrify{
    public static void main(String[] args) {
        Stage stage = new Stage();
        stage.performPlay();
        stage.change();
        stage.performPlay();
    }
}

第九章 接口

1.包含抽象方法的类叫做抽象类。如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。
如果从一个抽象类继承,并想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义。如果不这样做(可以选择不做),那么导出类便也是抽象类,且编译器将会强制我们用abstract关键字来限定这个类。

2.interface关键字使抽象概念更向前迈进了一步。abstract关键字允许人们在类中创建一个或多个没有任何定义的方法——提供了接口部分,但是没有提供任何相应的具体实现,这个实现是由此类的继承者创建的。interface这个关键字产生一个完全抽象的类,它根本就没有提供任何具体实现。它允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。接口只提供了形式,而并未提供任何具体实现。
接口中的属性都是static final的,接口中的方法都是public的。

3.策略设计模式。能够根据所传递参数对象的不同而具有不同行为的方法,被称为策略设计模式。

import java.util.Arrays;

class Processor{
    public String name(){
        return getClass().getSimpleName();
    }
    Object process(Object input){ return input;}
}

class Upcase extends Processor{
    String process(Object input){
        return ((String)input).toUpperCase();
    }
}

class Downcase extends Processor{
    Object process(Object input) {
        return ((String)input).toLowerCase();
    }
}

class Splitter extends Processor{
    Object process(Object input) {
        return Arrays.toString(((String)input).split(" "));
    }
}

public class Apply{
    public static void process(Processor p, Object s){
        System.out.println("Using Processor " + p.name());
        System.out.println(p.process(s));
    }
    public static String s = "Disagreement with beliefs is by definition incorrect";

    public static void main(String[] args) {
        process(new Upcase(), s);
        process(new Downcase(), s);
        process(new Splitter(), s);
    }
}
/*
Using Processor Upcase
DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT
Using Processor Downcase
disagreement with beliefs is by definition incorrect
Using Processor Splitter
[Disagreement, with, beliefs, is, by, definition, incorrect]
*/

4.适配器设计模式适配器中的代码将接受你所拥有的接口,并产生你所需要的接口

5.接口没有任何具有实现,也就是说,没有任何与接口相关的存储,因此,也就无法阻止多个接口的组合。

第十章 内部类

第十一章 持有对象

第十二章 通过异常处理错误

第十三章 字符串

第十四章 类型信息

第十五章 泛型

第十六章 数组

第十七章 容器深入研究

第十八章 Java I/O系统

第十九章 枚举类型

第二十章 注解

第二十一章 并发

第二十二章 图形化用户界面

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

该处使用的url网络请求的数据。


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值