🎜Java 인터페이스의 필드는 자동으로 최종적이고 정적이므로 인터페이스는 상수 그룹을 만드는 데 편리한 도구가 됩니다. 🎜Java SE5🎜 이전에는 이 방법을 사용하여 열거형 효과를 생성했습니다. 🎜Java SE5🎜 이후 Java에는 enum
키워드가 있으므로 🎜인터페이스를 사용하여 상수를 그룹화하는 것은 의미가 없습니다🎜. 🎜🎜🎜🎜🎜10장 내부 클래스 🎜🎜10.1 외부 클래스에 연결(Java 비정적 일반 내부 클래스는 자동으로 외부 클래스의 모든 멤버에 액세스할 수 있습니다). 🎜🎜 Java 일반 내부 클래스는 특별한 조건 없이 🎜포함 객체🎜(포함 객체)의 모든 멤버에 액세스할 수 있습니다. C++ 중첩 클래스의 디자인은 단지 이름 숨기기 메커니즘일 뿐이며 주변 객체와 연결되지 않고 암시적 액세스 권한도 없습니다. Java에서는 클래스가 내부 클래스 객체를 생성할 때 내부 클래스 객체가 해당 외부 클래스의 객체에 대한 참조를 비밀리에 캡처해야 합니다. 그런 다음 이 바깥쪽 클래스의 멤버에 액세스할 때 해당 참조를 사용하여 바깥쪽 클래스의 멤버를 선택합니다. 이러한 세부 사항은 컴파일러에 의해 처리됩니다. Java의 반복자는 이 기능을 재사용합니다. 🎜// Sequence.javainterface Selector{ boolean end();
Object current(); void next();
}public class Sequence{
private Object[] items; private int next = 0; public Sequence(int size){ items = new Object[size]; } public void add(Object x){ if(next != items.length) items[next++] = x;
} private class SequenceSelector implements Selector{
private int i = 0; public boolean end(){ return i == items.length; } public Object current(){ return items[i]; } public void next(){ if (i < items.length) i++; }
} public Selector selector(){ return new SequenceSelector(); } public static void main(String[] args){
Sequence sequence = new Sequence(10); for(int i = 0; i < 10; i++){
sequence.add(Integer.toString(i));
}
Selector selector = sequence.selector(); while(!selector.end()){
System.out.print(selector.current() + " ");
selector.next();
}
System.out.println();
}
}/**Output:
* 0 1 2 3 4 5 6 7 8 9
*/
10.2 .this
与 .new
// DotThis.java// Qualifying access to the outer-class object.public class DotThis{ void f(){ System.out.println("DotThis.f()"); } public class Inner{ public DotThis outer(){ return DotThis.this; // a plain "this" would be Inner's "this"
}
} public Inner inner(){ return new Inner(); } public static void main(String[] args){
DotThis dt = new DotThis();
DotThis.Inner dti = dt.inner();
dti.outer().f();
}
}/*Output:
* DotThis.f()
*/
10.3 匿名内部类
Anonymous Inner Class.
10.4 Java嵌套类
内部类声明为static
时,不再包含外围对象的引用.this
,称为嵌套类(与C++嵌套类大致相似,只不过在C++中那些类不能访问私有成员,而在Java中可以访问)。
- 创建嵌套类,不需要外围对象。
- 不能从嵌套类的对象中访问非静态的外围对象。
10.4.1 接口内部的类
嵌套类可以作为接口的一部分(正常情况下,接口内部不能放置任何代码)。放到接口中的任何类都自动是public和static的。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则
10.4.2 从多层嵌套类中访问外部类的成员
一个内部类被嵌套多少层并不重要——它能透明地访问它所嵌入的外围类的所有成员,如下:
// MultiNestingAccess.javaclass MNA{ private void f(){}
class A{ private void g(){} public class B{ void h(){
g();
f();
}
}
}
}public class MultiNestingAccess{ public static void main(String[] args){
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
10.5 为什么需要内部类
内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。
内部类实现一个接口与外围类实现这个接口有什么区别呢?答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因是:
每个内部类才能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。也就是说,内部类使得Java实现继承多个非接口类型(类或抽象类)。
// MultiInterfaces.java// two ways tha a clas can implement multiple interface.interface A{}interface B{}class X implements A, B {}class Y implements A {
B makeB(){ // Amonymous inner class
return new B() {};
}
}public class MultiInterfaces{
static void takesA(A a){} static void takesB(B b){} public static void main(String[] args){
X x = new X();
Y y = new Y();
takesA(x);
takesB(x);
takesA(y);
takesB(y.makeB());
}
}
10.6 闭包与回调
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用(.this
),在此作用域内,内部类有权操作所有的成员,包括private成员。
回调(callback),通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。Java中没有指针,通过内部类提供的闭包功能可以实现回调。
// Callbacks.java// using inner classes for callbacksinterface Incrementable{
void increment();
}// Very simple to just implement the interface:class Callee1 implements Incrementable{
private int i = 0; public void increment(){
System.out.println(++i);
}
}class MyIncrement{
public void increment(){ System.out.println("Other operation"); } static void f(MyIncrement mi) { mi.increment(); }
}// If your class must implement increment() in some other way, you must use an inner class:class Callee2 extends MyIncrement{
private int i = 0; public void increment(){ super.increment();
System.out.println(++i);
} private class Closure implements Incrementable{
public void increment(){ // Specify outer-class method, otherwise you'd get an infinite recursion:
Callee2.this.increment();
}
}
Incrementable getCallbackReference(){ return new Closure();
}
}class Caller{
private Incrementable callbackReference;
Caller(Incrementable cbh){ callbackReference = cbh; } void go(){ callbackReference.increment(); }
}public class Callbacks {
public static void main(String[] args){
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}/**Uoutput:
* Other operation
* 1
* 1
* 2
* Other operation
* 2
* Other operation
* 3
**/
10.7 Java接口和内部类总结
Java的接口和内部类比其他面向对象的概念更深奥复杂,C++没有这些,将两者结合起来,同样能够解决C++中的用多重继承所能解决的问题。
第11章 持有对象
11.1 迭代器(Iterator)
11.2 ArrayList 和 LinkedList
11.3 Set 不保存重复元素
11.4 Map 将对象映射到其他对象的能力是一种解决编程问题的杀手锏
11.5 Collection 和 Iterator
在Java中,Collection
是描述所有序列容器的共性的根接口,它可能会被 认为是一个“附属接口”,即因为要表示其他若干个接口的共性而出现的接口。而在标准C++类库中并没有其容器的任何公共基类——容器之间的所有共性都是通过迭代器达成的。Java将两种方法绑定到了一起,因为实现Collection
就意味着需要提供iterator()
方法。
11.5 Foreach与迭代器
foreach
语法用于任何实现了Iterable
接口的类。Collection
接口扩展了Iterable
接口,所以所有Collection
对象都适用foreach
语法。
11.6 容器的元素类型
泛型之前的容器不能持有基本类型元素,显然数组是可以的。但是有了泛型,容器就可以指定并检查它们所持有对象的类型,并且有了自动包装机制,容器看起来还能够持有基本类型。
在Java中,任何基本类型都不能作为类型参数。因此不能创建ArrayList<int></int>
或 HashMap<int int></int>
之类的东西。但是可以利用自动包装机制和基本类型的包装器来解决,自动包装机制将自动地实现int
到 Integer
的双向转换:
// ListOfInt.javaimport java.util.*;public class ListOfInt{ public static void main(String[] args){ // 编译错误:意外的类型
// List<int> li = new ArrayList<int>();
// Map<int, Interger> m = new HashMap<int, Integer>();
List<Integer> li = new ArrayList<Integer>(); for(int i = 0; i < 5; i++){
li.add(i); // int --> Integer
} for(int i : li){ // Integer --> int
System.out.print(i + " ");
}
}
}/* Output:
0 1 2 3 4
*/
第12章 通过异常处理错误
12.1 异常
异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。
12.2 终止与恢复
异常处理理论上有两种基本模型。长久以来,尽管程序员们使用的操作系统支持恢复模型的异常处理,但他们最终还是转向使用类似“终止模型”的代码,并且忽略恢复行为。
12.3 创建自定义异常
所有标准异常都有两个构造器:一个是默认构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器。
// FullConstructors.javaclass MyException extends Exception{ public MyException(){} public MyException(String msg){ super(msg); }
}public class FullConstructors{ public static void f() throws MyException{
System.out.println("Throwing MyException form f()"); throw new MyException();
} public static void g() throws MyException{
System.out.println("Throwing MyException form g()"); throw new MyException("Originated in g()");
} public static void main(String[] args){ try{
f();
}catch(MyException e){
e.printStackTrace(System.out);
} try{
g();
}catch(MyException e){
e.printStackTrace(System.out);
}
}
}/*Output:
Throwing MyException form f()
MyException
at FullConstructors.f(FullConstructors.java:11)
at FullConstructors.main(FullConstructors.java:19)
Throwing MyException form g()
MyException: Originated in g()
at FullConstructors.g(FullConstructors.java:15)
at FullConstructors.main(FullConstructors.java:24)
*/
12.4 printStackTrace()
Throwable
类声明了printStackTrace()
方法,它将打印“从方法调用处直到异常抛出处”的方法调用序列。printStackTrace()
方法所提供的信息可以通过getStackTrace()
方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一桢。元素0是栈顶元素,并且是调用序列中的最后一个方法调用(这个Throwable
被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。如下:
// WhoCalled.java// Programmatic access to stack trace informationpublic class WhoCalled{ static void f(){ // Generate an exception to fill in the stack trace
try{ throw new Exception();
}catch(Exception e){ for(StackTraceElement ste : e.getStackTrace()){
System.out.println(ste.getMethodName());
}
}
} static void g(){ f(); } static void h(){ g(); } public static void main(String[] args){
f();
System.out.println("-------------------------------");
g();
System.out.println("-------------------------------");
h();
}
}/*Output:
f
main
-------------------------------
f
g
main
-------------------------------
f
g
h
main
*/
12.5 为异常先占个位子
可以声明方法将抛出异常,实际上却不抛出。这样做的好处是,为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。
在编译时被强制检查的异常称为被检查的异常。
12.6 异常链
常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。JDK1.4以后,所有Throwable的子类在构造器中都可以接受一个cause
对象作为参数。这个cause
就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并了新的异常,也能通过这个异常链追踪到异常最初发生的位置。
在Throwable
的子类中,只有Error
(用于Java虚拟机报告系统错误)、Exception
以及RuntimeException
三种基本的异常提供了带cause
参数的构造器。
12.7 Java标准异常
只能在代码中忽略RuntimeException
(及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException
代表的是编程错误。
12.8 缺憾:异常丢失
用某些特殊的方式使用finally
子句,可能会丢失异常,一种简单的丢失异常的方式是从finally
子句中返回。
12.9 finally子句
在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行finanlly
子句。
当涉及break
和continue
语句的时候,finally
子句也会得到执行。
finally
子句会在执行return
语句前执行,即它总是会执行,所以在一个方法中, 可以从多个点返回,并且可以保证重要的清理工作仍旧会执行。
12.10 异常的限制
当要覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着,当基类使用的代码应用到其派生类对象的时候,一样能够工作。
12.11 构造器
如果在构造器内抛出了异常,清理行为也许就不能正常工作了。
12.12 异常匹配
抛出异常的时候,异常处理系统会按照代码的书写顺序抛出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
12.13 总结
“报告”功能是异常的精髓所在。Java坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过诸如C++这类语言的长处之一,因为在C++这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。
第13章 字符串
13.1 不可变字符串
String对象是不可变的。String类中每个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动。
13.2 重载“+”与 StringBuilder
用于String的“+”与“+=”是Java中仅有的两个重载过的运算符,Java不允许程序员重载任何运算符(但其实Java语言比C++更容易实现运算符的重载)。
String的不可变性带来了一定的效率问题,比如String的“+”运算,每“+”一次都会生成一个新的String对象。Java编译器一般会自动优化,但不同情况下,优化的程度不够。
以下类运行javap -c Concatenation.class
反编译后,可见编译器自动引入了java.lang.StringBuilder
类,帮助了优化。
// Concatenation.javapublic class Concatenation{
public static void main(String[] args){
String mango = "mango";
String s = "abc" + mango + "def" + 47;
System.out.println(s);
}
}/**Output:
* abcmangodef47
**/
Compiled from "Concatenation.java"public class Concatenation { public Concatenation();
Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code: 0: ldc #2 // String mango
2: astore_1 3: new #3 // class java/lang/StringBuilder
6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String abc
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1 16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #7 // String def
21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: bipush 47
26: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
29: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
32: astore_2 33: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_2 37: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: return}
// WhitherStringBuilder.javapublic class WhitherStringBuilder{ public String implicit(String[] fields){
String result = ""; for(int i = 0; i < fields.length; i++){
result += fields[i];
} return result;
} public String explicit(String[] fields){
StringBuilder result = new StringBuilder(); for(int i = 0; i < fields.length; i++){
result.append(fields[i]);
} return result.toString();
}
}
Compiled from "WhitherStringBuilder.java"public class WhitherStringBuilder { public WhitherStringBuilder();
Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public java.lang.String implicit(java.lang.String[]);
Code: 0: ldc #2 // String
2: astore_2 3: iconst_0 4: istore_3 5: iload_3 6: aload_1 7: arraylength 8: if_icmpge 38
11: new #3 // class java/lang/StringBuilder
14: dup 15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: aload_2 19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1 23: iload_3 24: aaload 25: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2 32: iinc 3, 1
35: goto 5
38: aload_2 39: areturn public java.lang.String explicit(java.lang.String[]);
Code: 0: new #3 // class java/lang/StringBuilder
3: dup 4: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
7: astore_2 8: iconst_0 9: istore_3 10: iload_3 11: aload_1 12: arraylength 13: if_icmpge 30
16: aload_2 17: aload_1 18: iload_3 19: aaload 20: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop 24: iinc 3, 1
27: goto 10
30: aload_2 31: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: areturn
}
13.3 无意识的递归
由String对象后面跟着一个“+”,再后面的对象不是String时,编译器会使后面的对象通过toString()
自动类型转换成String
,如果这发生在自定义的类的重写的toString()
方法体内,就有可能发生无限递归,运行时抛出java.lang.StackOverflowError
栈溢出异常。
// InfiniteRecursion.javapublic class InfiniteRecursion{ public String toString(){ //应该调用Object.toString()方法,所以此处应为super.toString()。
return " InfiniteRecursion address: " + this + "\n";
} public static void main(String[] args){
List<InfiniteRecursion> v = new ArrayList<InfiniteRecursion>(); for(int i = 0; i < 10; i++)
v.add(new InfiniteRecursion());
System.out.println(v);
}
}
文末:
这些章节内容算是Java的基础,整理出来作为第一部分,算是温故知新吧。
相关文章:
Java编程思想学习课时(二)第14章-类型信息
Java 프로그래밍 사고 학습 수업(3) 15장 - Generics