第五章 初始化与清理
5.1 用构造器确保初始化
构造器采用与类名称相同的名称,new表达式确实返回了对新建立对象的引用,但构造器并没有任何返回值,构造器不会返回任何东西。
5.2 方法重载
每个重载方法都必须有一个独一无二的参数列表。如果传入的数据类型小于方法中声明的参数类型,传入的数据类型将会被提升,char类型特殊,如果无法找到char类型的参数,会把char提升为int类型,传入的参数类型大于形参类型,就要类型强转。
方法重载只能以名称+参数列表区分,但不包括返回值,编译器无法根据返回值确定方法是否重载了。
class Function{
//Complier Error:Duplicate method f() in type Function
void f(){}
int f(){
return 1;
}
}
5.3 默认构造器
如果类中没有构造器,编译器会自动创建一个无参构造器,但如果已经定义了一个构造器(无论是否有参),编译器就不会自动创建无参构造器。
class Function {
Function(int i) {
System.out.println(i);
}
}
public class Constructor {
public static void main(String[] args) {
Function f = new Function();//The constructor Function() is undefined
}
}
5.4 this关键字
this关键字只能在方法内部使用,表示“调用方法的那个对象“,即当前对象。
返回对当前对象的引用
public class Leaf {
public static int count = 0;
public Leaf increment() {
count++;
return this;
}
public void print(){
System.out.println(count);
}
public static void main(String[] args) {
Leaf leaf = new Leaf();
leaf.increment().increment().increment().increment().print();
}
}
在构造器中使用this,将产生对符合此参数里列表的某个构造器的明确调用。
在构造方法内this只能调用一个构造器,并且必须置于首行。
this调用构造器时能在构造器中调用,不能在其它方法中调用。
5.5 清理:终结处理和垃圾回收
一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象所占用的内存。
1、对象可能不会回收
2、垃圾回收并不等于”析构”
3、垃圾回收只与内存有关
finalize()不是用于进行普通的对象清理工作合适的场所。
只要对象中存在没有被适当清理的部分,程序就存在隐晦的缺陷。finalize()可以用来最终发现这种情况,尽管它并不是总会被调用。
class Book{
boolean checkOut = false;
public Book(boolean checkOut){
this.checkOut = checkOut;
}
public void checkIn(){
this.checkOut = false;
}
@Override
protected void finalize() throws Throwable {
if(checkOut){
System.err.println("Error:checked out");
}
}
}
public class TerminationCondition {
public static void main(String[] args) {
Book b = new Book(true);
b.checkIn();//Proper clean up
new Book(true);//forget to clean up
System.gc();
System.out.println("");
}
}
垃圾回收器如何工作P89
5.6 成员初始化
对于方法的局部变量,Java以编译时的错误形式来贯彻这种保证。
public void f(){
int i;
i++;//Complier Error:The local variable i may not have been initialized
}
如果是类的成员变量,每个类型的变量都会保证有一个初始值。
5.7 构造器初始化
无法阻止自动初始化的进行,它将在构造器被调用之前发生。
public class Leaf {
private int count = 0;
public Leaf(){
count = 9;
}
}
count会先被赋值为0,然后构造器初始化后count为9。这种初始化对于基本类型和对象都适用。
class Window {
public Window(int marker) {
System.out.println("Window(" + marker + ")");
}
}
class House {
Window w1 = new Window(1);//1
House() {
System.out.println("House()");//4
w3 = new Window(33);//5
}
Window w2 = new Window(2);//2
void print(){
System.out.println("print()");//6
}
Window w3 = new Window(3);//3
}
public class OrderOfInitialization {
public static void main(String[] args) {
House h = new House();
h.print();
}
}
/**
Output:
Window(1)
Window(2)
Window(3)
House()
Window(33)
print()
*/
w3会被初始化两次,一次在构造器前,一次在构造器中,构造器前初始化的对象将被丢弃。
无论创建多少个对象,静态数据都只占一份存储区域,static关键字不能应用于局部变量。
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
public void f1(int marker) {
System.out.println("f1(" + marker + ")");
}
}
class Table {
static Bowl b1 = new Bowl(1);
Table() {
System.out.println("Table()");
b2.f1(1);
}
public void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
static Bowl b2 = new Bowl(2);
}
class Cupboard {
Bowl b3 = new Bowl(3);
static Bowl b4 = new Bowl(4);
public Cupboard() {
System.out.println("Cupboard()");
b4.f1(2);
}
public void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl b5 = 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();
}
/**
Output:
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)
*/
只有第一个Table对象被创建时或第一次访问b1、b2时,它们才会被初始化,此后静态对象不会在被初始化。初始化的顺序是先静态对象,然后非静态对象。table、cupboard变量在main()之前就被加载了。
显示静态初始化,静态初始化动作只执行一次。
public class Spoon {
static int i;
static {
i = 47;
System.out.println("static{}");
}
{
//无论调用哪个构造器,这段代码都会被执行,并且多次
System.out.println("....");
}
public static void main(String[] args) {
System.out.println(Spoon.i);
new Spoon();
new Spoon();
new Spoon();
}
}
/**
Output:
static{}
47
....
....
....
*/
5.8 数组初始化
数组只是引用,数组创建是在运行时刻进行的。
int[] a1 = {1,2,3,4};
int[] a2 = new int[10];
Integer[] a3 = {new Integer(1),new Integer(2)};
String[] a4 = new String[]{"a","b","c"};
所有类都继承自Object类,所以Object数组可以接受所有类型的参数
class A {
}
public class VarArgs {
public static void main(String[] args) {
printArray(new Object[] { new Integer(1), new Float(3.2),
new Double(11.11) });
printArray(new String[] { "a", "b" });
printArray(new Object[] { new A(), new A() });
// printArray();
}
public static void printArray(Object[] args) {
for (Object obj : args) {
System.out.print(obj + "\t");
}
System.out.println();
}
}
/**
Output:
1 3.2 11.11
a b
A@525483cd A@2a9931f5
*/
JavaSE5中增加了可变参数,这样就不用显示的编写Object[]语法了,当使可变参数时,编译器会自动的填充为数组。
public class NewVarArgs {
public static void main(String[] args) {
printArray(new Object[] { new Integer(1), new Float(3.2),new Double(11.11) });
printArray(11,12.2,13);
printArray((Object[])new String[] { "a", "b" });
printArray("a","b");
printArray(new Object[] { new A(), new A() });
printArray();//Empty list is OK
}
public static void printArray(Object ... args) {
for (Object obj : args) {
System.out.print(obj + "\t");
}
System.out.println();
}
}
/**
Output:
1 3.2 11.11
11 1.2 13
a b
a b
A@2a9931f5 A@2f9ee1ac
*/
Object[]作为参数时,不能出现没有参数的情况,但可变参数可以。可变参数不依赖于包装器机制,但可以和包装器机制混合在一起,并且有选择的把int类型提升为Integer类型。
public class OverlaodVarargs {
public static void main(String[] args) {
f('a','b');
f(1,2);
f(1L,2L);
f();//The method f(Character[]) is ambiguous for the type OverlaodVarargs
}
public static void f(Character ... args ){
System.out.println("first");
for (Character character : args) {
System.out.println(character+"\t");
}
System.out.println();
}
public static void f(Integer ... args ){
System.out.println("second");
for (Integer integer : args) {
System.out.println(integer+"\t");
}
System.out.println();
}
public static void f(Long ... args ){
System.out.println("third");
}
}
/**
Output:
first
a
b
second
1
2
third
*/
在不是用参数调调f()时,编译器就无法知道应该调用哪个重载的方法。
public static void main(String[] args) {
//The method f(Character[]) is ambiguous for the type OverlaodVarargs
f('a','b');
}
public static void f(Character ... args ){
System.out.println("first");
for (Character character : args) {
System.out.println(character+"\t");
}
System.out.println();
}
public static void f(int i,Character ... args ){
System.out.println("first");
for (Character character : args) {
System.out.println(character+"\t");
}
System.out.println();
}
public enum Spiciness {
NOT,MILD,MEDIUM,HOT,FLAMING
}
public class SimpleEnumUse {
public static void main(String[] args) {
Spiciness howHot = Spiciness.MEDIUM;
System.out.println(howHot);
for (Spiciness s : Spiciness.values()) {
System.out.print(s+"-"+s.ordinal()+"\t");
}
}
}
/**
Output:
MEDIUM
NOT-0 MILD-1 MEDIUM-2 HOT-3 FLAMING-4
*/
枚举也可以在switch语句中使用
第七章 复用类
本章介绍两种复用类的方式:组合、继承
7.1 组合语法
将原有对象的引用置于新类中即可
class WaterSource {
WaterSource() {
System.out.println("WaterSource()");
}
}
public class SprinklerSystem {
private String valve1, valve2, valve3;
//洒水器包含水源
private WaterSource source = new WaterSource();
}
类中的成员变量为基本类型时会获得默认的初始化值,如果为对象初始值为null,使用组合方式创建新类时,类中包含对象实例化的四种方式
1、在定义对象时就初始化,意味着总能在构造器被调用之前初始化对象
public class Batch {
private String s1 = "s1", s2 = "s2";
}
2、构造器中初始化
class Soap {
private String s;
Soap() {
s = "Construted";
}
}
3、在使用对象前初始化,这种创建对象的方式为懒汉式
public class Batch {
private String s3;
public void method() {
if (s1 == null)
s3 = "s3";
}
}
4、实例初始化
class Soap {
private String ss;
{
ss = "ss";
}
Soap() {
}
}
7.2 继承语法
当创建一个类时除非明确指出继承其它类,否则就隐式的继承自Object类,Object类时Java的根类。
public class Animal {
private String name;
public void method(){
System.out.println("Animal method()");
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
public class Cat extends Animal {
}
父类Animal中的方法是public的,这样子类Cat才能访问,如果方法没被任何访问权限修饰,默认是包访问权限,为了继承,一般将父类中的成员变量设置为private,而方法设置为public
继承关系发生时,当子类被初始化时,会在子类的构造器中隐含的加入对父类构造器的调用
class A {
A() {
System.out.println("A()");
}
}
class B extends A {
B() {
System.out.println("B()");
}
}
public class C extends B {
C() {
System.out.println("C()");
}
public static void main(String[] args) {
C c = new C();
}
}
/**
A()
B()
C()
*/
调用有参数的构造器使用super关键字,调用父类构造器必须是在子类构造器的首行
class A {
A(int i){
System.out.println("A(i)="+i);
}
}
class B extends A {
B(int i){
super(i);
System.out.println("B(i)="+i);
}
}
public class C extends B {
C(int i){
super(i);
System.out.println("C(i)="+i);
}
public static void main(String[] args) {
C c = new C(1);
}
}
/**
A(i)=1
B(i)=1
C(i)=1
*/
7.3 代理
将一个成员对象置于所要创建的类中,然后在新类中暴露该成员对象的方法,这种暴露可以是有选择的,这种方式实际上更接近于组合所表达的含义,这是组合与继承间的中庸之道
public class Animal {
public void method() {
System.out.println("Animal method()");
}
public void m1() {
System.out.println("Animal m1()");
}
public void m2() {
System.out.println("Animal m1()");
}
}
public class Proxy {
private Animal animal = new Animal();
public void m1() {
animal.m1();
}
public void m2() {
animal.m2();
}
}
7.4 结合使用组合和继承
1、清理,try{}finaly{}
2、子类中方法重载
3、注解@Override
7.5 在组合与继承之间选择
组合和继承都允许在新类中放置对象,组合是显式的,继承是隐式的,组合通常用于在新类中使用现有类的功能而非它的接口。继承是“is-a”关系,组合是“has-a”关系
7.6 protected关键字
protected对子类和同一包中的类有访问权限,对其它类没有访问权限。
class A {
protected void method(){
System.out.println("class A method()");
}
}
class B extends A {
}
public class C extends B {
public void m(){
method();
}
public static void main(String[] args) {
C c = new C();
c.m();
}
}
7.8 final关键字
对于基本类型数据常量,编译器可以将该常量带入任何一个用到它的计算式中
对于对象类型数据常量,final是引用不变,但对象自身是可以修改
final数据:
java允许“空白final”,是指在声明final但又没有初始化该值的数据,编译期可以确保空白final在使用前被初始化,为final数据初始化的几种方式
public class BlankFinal {
private final int i = 1;// 声明时初始化
private final int j;
{
j = 1;// 实例初始化
}
private static final int x;
static {
x = 1;// 静态块初始化
}
private final int y;
BlankFinal() {
y = 1;// 构造方法初始化
}
}
如果有空白final没有被初始化,会得到编译提示
The blank final field y may not have been initialized
final参数:
java允许在方法的参数列表上将参数声明final,表示在该方法内无法修改该参数的引用(基本类型无法修改值)
public class FinalArguments {
//不能修改
public void method1(final int i){
/**compiler error:
Multiple markers at this line
- Duplicate local variable i
- Syntax error on token "++", delete this token
*/
int i++;
}
//可读
public int method2(final int i) {
return i;
}
//正常参数
public void method3(int i) {
i++;
}
}
final方法:
final方法确保继承类无法修改它的含义,确保在继承类中使用方法的行为不会被改变,不会被覆盖
类中所有private方法都隐式的声明了final,子类无法访问父类中的private方法,所以也无法覆盖它,覆盖只有在方法是父类中的一个public方法时才会发生
final类:
类被定义为final时,不允许被继承,永远不会有子类,final类中的所有的方法都隐式的声明了final,数据可以自行定义
public final class Final {
}
//compiler error:The type Clazz cannot subclass the final class Final
class Clazz extends Final {
}
7.9 初始化及类的加载
当第一此访问类时加载类的*.class文件,包括访问被定义为static的数据、方法,如果第一次访问一个子类时,也会创建起父类,通过extends关键字获得父类信息
第十章 内部类
内部类把一些逻辑相关的类组织在一起,并控制位于内部类的可见性。
10.1 创建内部类
public class Parcel1 {
class Contents {
private int i = 1;
public int value() { return i; }
}
class Destination {
private String lable;
public Destination(String whereTo) {
this.lable = whereTo;
}
public String readLable() { return lable; }
}
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLable());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("BJ");
}
}
这是一个普通的内部类,更典型的情况是在外部类将有一个方法返回一个指向内部类的引用
public class Parcel2 {
class Contents {
private int i = 1;
public int value() { return i; }
}
class Destination {
private String lable;
public Destination(String whereTo) {
this.lable = whereTo;
}
public String readLable() { return lable; }
}
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLable());
}
public Destination to(String dest){
return new Destination(dest);
}
public Contents contents(){
return new Contents();
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
Parcel2.Destination dest = p.to("BJ");
Parcel2.Contents contents = p.contents();
}
}
创建内部类对象的格式:OuterClassName.InnerClassName
10.2 链接到外部类
内部类可以访问外部类的方法或字段,内部类自动拥有对其外部类所有成员的反问权限,当外部类对象创建内部类对象时,此内部类对象会捕获一个指向外部类对象的引用,通过这个引用来访问外部类的成员。
public class Squence {
private Object[] items;
private int next;
public Squence(int length) {
items = new Object[length];
}
public void add(Object obj) {
if (next <items.length) {
items[next++] = obj;
}
}
private class Iterator implements Selector {
private int index = 0;
@Override
public boolean end() {
return index < next;
}
@Override
public Object current() {
if (index < next)
return items[index];
return null;
}
@Override
public void next() {
if (index < items.length)
index++;
}
}
public Iterator iterator() {
return new Iterator();
}
public static void main(String[] args) {
Squence s = new Squence(10);
for (int i = 0; i < 10; i++) {
s.add( "Object-" + i);
}
Squence.Iterator iter = s.iterator();
while (iter.end()) {
System.out.println(iter.current());
iter.next();
}
}
}
10.3 .this和.new
使用对外部类的引用可以使用“外部类名.this”的方式,这种检查在编译期接受检查,没有运行时开销
public class DotThis {
public void f() {
System.out.println("DotThis.f()");
}
public Inner inner() {
return new Inner();
}
class Inner {
public DotThis outer() {
return DotThis.this;
}
}
public static void main(String[] args) {
DotThis dt = new DotThis();
DotThis.Inner inner = dt.inner();
inner.outer().f();
}
}
使用外部类对象直接创建内部类对象,使用“.new 内部类名称()”,必须先有外部类对象,才能有内部类对象,静态内部类是个例外
public class DotNew {
class Ineer {
}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Ineer inner = dn.new Ineer();
}
}
10.4 内部类与向上转型
定义两个接口
public interface Contents {
int value();
}
interface Destination {
String readLabel();
}
使用内部类实现这两个接口
public class Parcel4 {
private class PContents implements Contents {
private int i = 1;
@Override
public int value() {
return i;
}
}
public Contents contens() {
return new PContents();
}
protected class PDestination implements Destination {
private String lable;
private PDestination(String whereTo) {
this.lable = whereTo;
}
@Override
public String readLabel() {
return lable;
}
}
public Destination destination(String whereTo) {
return new PDestination(whereTo);
}
}
PContens使用private修饰,由于不能访问private类,对于客户端来说扩展接口没有价值,private内部类对外部完全隐藏了实现细节
10.5 在方法和作用域内的内部类
在方法内创建内部类
public class Parcel5 {
public Destination destination(String whereTo) {
class PDestination implements Destination {
private String label;
public PDestination(String label) {
this.label = label;
}
@Override
public String readLabel() {
return label;
}
}
return new PDestination(whereTo);
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
System.out.println(p.destination("BJ").readLabel());
}
}
内部类也可以嵌入到if()中,可以出现在任何位置
10.6 匿名内部类
匿名内部类的结构是直接new 类名(){};
public class Parcel7 {
public Contents contents() {
return new Contents() {
private int i = 1;
@Override
public int value() {
return 1;
}
};
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
System.out.println(p.contents().value());
}
}
匿名内部类访问外部定义的对象时,参数必须是final的,不然编译时错误提示
public class Parcel9 {
public Destination destination(final String whereTo) {
return new Destination() {
@Override
public String readLabel() {
return whereTo;
}
};
}
}
在匿名内部类中要实现一些构造器的行为是不可能的,因为匿名类中没有有名称的构造器,可以使用实例初始化完成类似的功能,就是{}代 码块,但是不能重载实例化方法,这也限制了匿名内部类,它可以扩展类或实现接口,但不能两者兼备,继承类就不能实现接口,实现接口就不能继承类
public class Parcel10 {
public Destination destination(final String whereTo, final double price) {
return new Destination() {
private String info;
//类似构造行为
{
info = whereTo + "\t" + price;
}
@Override
public String readLabel() {
return info;
}
};
}
public static void main(String[] args) {
Parcel10 p = new Parcel10();
System.out.println(p.destination("BJ", 11D).readLabel());
}
}
10.6.1 再访工厂方法
10.7 嵌套类
如果不想内部类对象与外部类对象有联系,可以使用静态内部类,差别就是普通内部类对象隐式的保存了一个指向外部类的引用,而静态内部类没有,也就是没有this对外部类的引用,静态内部类不能访问外部类非静态的对象
10.7.1 接口内部类
接口是不允许有任何逻辑代码的,但使用内部类就可以,接口里的任何类都是public static的,原来接口里还能这么写,我也是醉了,如果想要创建一些公共代码,使得可以被所有实现类所用,接口内部类就是最好的选择
public interface ClassInterface {
void howdy();
class Test implements ClassInterface {
@Override
public void howdy() {
System.out.println("howdy!!!");
}
public static void main(String[] args) {
new Test().howdy();
}
}
}
10.7.2 多层嵌套类中访问外部类成员
太TM恶心了
public class MNA {
public void f1(){}
class A{
public void f2(){}
class C{
public void f3(){
f1();
f2();
}
}
}
}
10.8 为什么需要内部类
Java不允许多继承,C++那个多继承也没法用,内部类可以独立继承一个类,解决了多重继承的问题
class A {
}
abstract class B {
}
class C extends A {
B b() {
return new B() {
};
}
}
在一个外部类中可以使用多个内部类以不同的方式实现同一个接口或继承同一个类。
10.8.1 闭包与回调
我还没研究明白
10.8.2 内部类与控制框架
我在研究一下
10.9 内部类的继承
这也是特殊的地方,内部里初始化需要先初始化外部类在初始化内部类,所以继承时也要先得到外部类对象的引用,才能操作内部类
class WithInner {
class Ineer {
}
}
public class InheritInner extends WithInner.Ineer {
//copile-error:
//No enclosing instance of type WithInner is available due to some intermediate constructor invocation
public InheritInner() {
}
public InheritInner(WithInner withInner) {
withInner.super();
}
}
10.10 内部类可以被覆盖吗
内部类不会被覆盖
10.11 局部内部类
局部内部类与匿名内部类唯一的区别就是命名构造器,局部内部类可以对构造器进行重载,而匿名内部类只能用于初始化。
10.12 内部类标识符
Outter$Inner.class//内部类
Outer$1Inner.class//匿名内部类
第十一章 持有对象
11.1 泛型和类型安全的容器
不是用泛型,当在使用get()取数据时得到的只是Object引用,必须要对数据进行类型强转,而且一旦出现java.lang.ClassCastException大部分都是在运行时,不能在编译器检查出来
ArrayList list = new ArrayList();
list.add("String");
list.add(1);
list.add(true);
使用泛型解决容器内数据安全问题,把错误类型数据放在编译器发现
ArrayList<String> list = new ArrayList<String>();
list.add("String");
//compile-time error
//The method add(int, String) in the type ArrayList<String> is not applicable for the arguments (int)
list.add(1);
list.add(true);
泛型应用过程中,面向对象的多态性就体现出来了,泛型可以是接口、抽象类、父类,容器内的数据可以是实现类、子类。
11.2 基本概念
Collection:一个独立元素的序列,一种存放一组对象的方式
Map:一组成对的“键值对”对象,允许使用键查找值
大部分的容器都应该面向接口编程
List<Apple> apples = new ArrayList<Apple>();
这时如果需要修改实现,只需要在创建时换掉实现方式可以了
LinkedList<Apple> apples = new LinkedList()<Apple>();
但如果要使用子类扩展的额外方法,在List接口中不存在,就不能面向接口编程了
Set在add()时只有元素不存在的情况下才会添加,但List不关心数据是否重复,它是把数据放心去
11.3 添加一组元素
Collection<Integer> c = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));
Integer[] moreInts = { 6, 7, 8, 9 };
c.addAll(Arrays.asList(moreInts));
Collections.addAll(c, 10, 11, 12);
Collections.addAll(c,moreInts);
Collection.addAll():没有Collections.addAll()灵活,它只能接收一个集合对象
Collections.addAll():可以接收一个集合对象,也可以使用可以变参数,也可以使用数组
首选是创建一个Collection,然后通过Collections方法增加数据
Arrays.asList():也可以接收对象和可变参数
当使用Arrays.asList()作为输出时,其底层表示的是数组,因此不能调整尺寸大小,不能调用add()、delete()
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//runtime-error: java.lang.UnsupportedOperationException
list.add(1);
对asList()也应该应用于泛型
Arrays.<Integer>asList(1,2,3);
如果使用Arrays.asList()直接返回对象,返回的是对象的引用,如果使用容器的构造方法报一下,才会返回另个新对象的引用。
Collections也会对数据使用泛型检查
class A{}
class AA extends A{}
class B{}
class BB extends B{}
class BBB extends BB{}
List<B> list = new ArrayList<B>();
Collections.addAll(list, new BB(),new BBB());
//compile-error
//The method addAll(Collection<? super T>, T...) in the type Collections is not applicable for the arguments (List<B>, AA)
Collections.addAll(list, new AA());
会根据list变量的泛型对数据进行检查
11.4 容器的打印
Arrays.toString()来打印容器
ArrayList、LinkedList:按照插入的顺序保存元素
HashSet:重复数据值保存一次,无序存放
TreeSet:按比较结果升序排列数据
LinkedHashSet:按照被添加顺序
HashMap:无序存放
LinkedHashMap:按照被添加的顺序
TreeMap:按key的升序排列
通过Hash的方式存储数据,这种方式是最快的获取元素方式
11.5 List
ArrayList:可以通过角标随机访问元素,但是插入和移除元素
LinkedList:插入和删除代价较低,提供优化的顺序访问,但随机访问较慢
这一节介绍List接口所提供的方法,判断数据是否包含、删除的方法,都需要equals()
size()//
isEmpty()//
contains(Object)//某个对象是否在容器中
iterator()//迭代器
toArray()
toArray(T[])//
add(E)
remove(Object)
containsAll(Collection<?>)//判断容器内的数据的是否包含
addAll(Collection<? extends E>)
addAll(int, Collection<? extends E>)
removeAll(Collection<?>)
retainAll(Collection<?>)
clear()//清空容器
equals(Object)
hashCode()
get(int)
set(int, E)//替换,replace名字更适合
add(int, E)
remove(int)
indexOf(Object)//
lastIndexOf(Object)//从后向前索引
listIterator()
listIterator(int)
subList(int, int)
11.6 迭代器
迭代器是一种设计模式,之所以应用它是因为在众多容器的使用中如何将容器容器内容输出成了问题,不同容器的实现有着不同的数据结构,使用迭代器解决了这个问题,每种容器提供自己的迭代器,而客户端不管其具体的实现。
比较迭代器和foreach输出显然使用foreach更加简洁
List<String> strs = new ArrayList<String>();
strs.add("A");
strs.add("B");
strs.add("C");
//迭代器输出
Iterator<String> iter = strs.iterator();
while (iter.hasNext()) {
String str = iter.next();
System.out.println(str);
}
//foreach输出
for (String str : strs) {
System.out.println(str);
}
接受对象容器并传递它,从而在每个对象上都执行操作,这种思想十分强大,并且贯穿于整个Think In Java
public class CrossContainerIteration {
public static void main(String[] args) {
ArrayList<String> strArray = new ArrayList<String>();
strArray.add("A");
strArray.add("B");
LinkedList<String> strLinked = new LinkedList<String>();
strLinked.add("C");
strLinked.add("D");
HashSet<String> strHash = new HashSet<String>();
strHash.add("E");
strHash.add("F");
display(strArray.iterator());
display(strLinked.iterator());
display(strHash.iterator());
}
public static void display(Iterator<String> iterator) {
while (iterator.hasNext()) {
String str = iterator.next();
System.out.print(str + "\t");
}
System.out.println();
}
}
Iterator能够遍历序列的操作与序列底层的结构分离,迭代器统一了对容器的访问方式
ListIterator是一个更加强大的Iterator子类型,但它只能用于各种List容器的访问,Iterator只可以向前移动,但ListIterator可以双向移动
ListIterator直接使用hasPervious()回返会false,因为ListIterator初始化时hasPervious()已经在集合最前端了,所以可以先hasNext()使用hasPervious()指到集合的最后
ArrayList<String> strs = new ArrayList<String>();
strs.add("A");
strs.add("B");
strs.add("C");
strs.add("D");
ListIterator<String> listIterator = strs.listIterator();
while (listIterator.hasNext()) {
listIterator.next();
}
while (listIterator.hasPrevious()) {
String str = listIterator.previous();
System.out.println(str);
}
还可以在ListIterator初始化时告诉其大小
ListIterator<String> listIterator = strs.listIterator(strs.size());
while (listIterator.hasPrevious()) {
String str = listIterator.previous();
System.out.println(str);
}
也可以通过迭代器添加数据
ListIterator<String> listIterator = strs.listIterator();
listIterator.add("A");
listIterator.add("B");
listIterator.add("C");
listIterator.add("D");
while (listIterator.hasPrevious()) {
String str = listIterator.previous();
System.out.println(str);
}
11.7 LinkedList
LinkedList也想ArrayList一样实现了List接口,但在容器中插入和移除时比ArrayList效率高,但随即访问效率低,LinkedList还增加用于栈、队列、双端队列的方法
LinkedList<String> list = new LinkedList<String>(Arrays.<String> asList("A", "B", "C", "D"));
list.add("E");
System.out.println(list);// [A, B, C, D, E]
list.offer("F");
System.out.println(list);// [A, B, C, D, E, F]
list.remove();
System.out.println(list);// [B, C, D, E, F]
System.out.println(list.poll());
System.out.println(list);// [C, D, E, F]
System.out.println(list.peek());
System.out.println(list);// [C, D, E, F]
System.out.println(list.element());
System.out.println(list);// [C, D, E, F]
1、getFirst()、element(),返回列表的头元素,列表为空返回NoSuchElementException,peek()列表为空时返回null
2、removeFirst()、remove(),移除头元素并返回该元素,列表为空返回NoSuchElementException,poll()列表为空时返回null,removeLast()移除返回列表最后一个元素
3、addFirst()、add()、addLast(),将元素插入到列表的尾部
11.8 Stack
后进先出的结构,最后压入栈的元素,第一弹出。可以直接将LinkedList作为栈使用
public class Stack<T> {
private LinkedList<T> storage = new LinkedList<T>();
public void push(T v) {
storage.addFirst(v);
}
public T peek() {
return storage.getFirst();
}
public T pop() {
return storage.removeFirst();
}
public boolean isEmpety() {
return storage.isEmpty();
}
public String toString() {
return storage.toString();
}
}
测试一下
Stack<String> stack = new Stack<String>();
for (String str : "My Dog Has Girlfriend".split(" ")) {
stack.push(str);
}
while (!stack.isEmpety()) {
System.out.println(stack.pop());
}
/* output:
Girgfriend
Has
Dog
My
**/
如果只需要栈的行为,就不太适合使用继承,因为这样会具有LinkedList的其它方法,但JDK1.0就是用了这种糟糕的设计,在java.util包中没有公共的Statck接口
//JDK1.0
public class Stack<E> extends Vector<E> {}
11.9 Set
将多个对象添加到Set时它会阻止重复对象,HashSet的实现对快速查询进行了优化,Set具有与Collection完全一样的接口,没有实现任何扩展。
Set是基于对象的值来确定对象的归属性,0-29之间的10000个随机数被添加到Set中,每一个数肯定重复了许多次
Random random = new Random(System.currentTimeMillis());
Set<Integer> intset = new HashSet<Integer>();
for (int i = 0; i < 10000; i++) {
intset.add(random.nextInt(30));
}
如果相对结果进行排序可以TreeSet,它将元素存储在红黑树上,LinkedHashSet也使用了散列,为了查询速度快,但看起来像是个链表
11.10 Map
Map保存的一对值,key与value对应
检查一下Random类的随机性
Map<Integer, Integer> m = new HashMap<Integer, Integer>();
Random random = new Random(System.currentTimeMillis());
for (int i = 0; i < 10000; i++) {
int r = random.nextInt(20);
Integer value = m.get(r);
m.put(r, value == null ? 1 : value + 1);
}
System.out.println(m);
11.11 Queue
队列规则:在给定一组队列中的元素情况下,确定下一个弹出队列元素的规则
从容器的一端放入对象,从另一端取出,并且对象的放入容器的顺序与取出的顺序一样,LinkedList提供了队列的行为
Queue<Character> queue = new LinkedList<Character>();
for (Character c : "Hello Java Collection Frame".toCharArray()) {
queue.offer(c);
}
while (queue.peek() != null) {
System.out.print(queue.remove());
//output:Hello Java Collection Frame
}
offer()将一个元素插入到队尾
peek()、element()不移除的情况下返回队头寸元素
11.11.1 PriorityQueue
按元素的优先级排列,默认是自然顺序
11.2 Collection和Iterator
Collection 是描述所有序列容器的共性接口,使用接口描述的一个理由是它可以创建更通用的代码,针对与接口而不是实现来编码,但C++比你更没有容器的公共接口或者 类,C++使用迭代器,Java集合框架实现了这两中方式,Collection接口继承了Iterable接口,所以必须也要实现一个返回一个 Iterator的方法
public interface Iterable<T> {
Iterator<T> iterator();
}
public interface Collection<E> extends Iterable<E> {}
通过代码来看这两种方式
public class InterfaceVsIterator {
public static void display(Collection<?> collection) {
for (Object obj : collection) {
System.out.print(obj);
}
System.out.println();
}
public static void display(Iterator<?> iterator) {
while (iterator.hasNext()) {
Object obj = (Object) iterator.next();
System.out.print(obj);
}
System.out.println();
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>(Arrays.<String> asList("A", "B", "C"));
display(list);
display(list.iterator());
Set<String> set = new HashSet<String>(Arrays.<String> asList("A", "B", "C"));
display(set);
display(set.iterator());
Map<String, String> map = new HashMap<String, String>();
map.put("1", "A");
map.put("2", "B");
display(map.keySet());
display(map.values());
display(map.values().iterator());
}
}
接口的方式更简洁些,可以使用foreach输出,如果自己要创建一个容器可以继承AbstractCollection类,或者提供一个可以获得Iterator的方法,
11.13 Foreach与迭代器
JavaSE5引入了Iterable接口,只要实现了Iterable接口的类就可以使用foreach输出,Iterable接口有Iterator()可以返回一Iterator对象,foreach使用它来在序列中移动
public class IterableClass implements Iterable<String> {
private String[] words = "public class IterableClass implements Iterable<String> ".split("");
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < words.length;
}
@Override
public String next() {
return words[index++];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
for (String string : new IterableClass()) {
System.out.print(string);
}
}
}
JavaSE5中大量的Collection实现类都是Iterable类型,但Map并不是Iterable类型,Map有个entrySet()返回一个Map.Entry可以用于foreach
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
数组可以用于foreach但它不是Iterable类型
public class ArrayIsNotIterable {
public static <T> void iterable(Iterable<T> iterable) {
for (T t : iterable) {
System.out.println(t);
}
}
public static void main(String[] args) {
String[] str = "public class ArrayIsNotIterable".split(" ");
// complie-error
// The method iterable(Iterable<T>) in the type ArrayIsNotIterable is not applicable for the arguments (String[])
// iterable(str);
iterable(Arrays.asList(str));
}
}
第十六章 数组
16.1数组为什么特殊
在Java中数组是效率最高的存储和随机访问对象引用序列的方式,数组是一个线型程序。
数组和容器如果越界都会抛出RuntimException异常。
在泛型之前,其它容器类在处理对象时都会当作Object类型,数组之所以优于泛型之前的容器,创建一个数组时就限定了数组持有对象的类型,这意味着可以在编译器进行检查。
数组可以持有基本类型,泛型之前的容器则不能,但有了泛型和自动装箱,其它容器也可以持有基本类型数据,数组仅存的优点就是效率。
public class BerylliumSphere {
public static long count;
public final long id = count++;
@Override
public String toString() {
return "BerylliumSphere [id=" + id + "]";
}
}
public class ContainerComparison {
public static void main(String[] args) {
//数组保存对象
BerylliumSphere[] spheres = new BerylliumSphere[10];
for (int i = 0; i < 5; i++) {
spheres[i] = new BerylliumSphere();
}
System.out.println(Arrays.toString(spheres));
System.out.println(spheres[4]);
//集合保存对象
List<BerylliumSphere> phereList = new ArrayList<BerylliumSphere>();
for (int i = 0; i < 5; i++) {
phereList.add(new BerylliumSphere());
}
System.out.println(phereList);
System.out.println(phereList.get(4));
//数组保存基本类型
int[] integers = {1,2,3,4,5};
System.out.println(Arrays.toString(integers));
System.out.println(integers[4]);
//集合保存基本类型
List<Integer> intList = new ArrayList<Integer>();
for (int i = 0; i < 5; i++) {
intList.add(i);
}
intList.add(97);
System.out.println(intList);
System.out.println(intList.get(4));
}
}
/*
Output:
[BerylliumSphere [id=0], BerylliumSphere [id=1], BerylliumSphere [id=2], BerylliumSphere [id=3], BerylliumSphere [id=4], null, null, null, null, null]
BerylliumSphere [id=4]
[BerylliumSphere [id=5], BerylliumSphere [id=6], BerylliumSphere [id=7], BerylliumSphere [id=8], BerylliumSphere [id=9]]
BerylliumSphere [id=9]
[1, 2, 3, 4, 5]
5
[0, 1, 2, 3, 4, 97]
4
**/
16.2 数组是第一级对象
只读lengh是数组对象唯一可以访问的字段或方法,“[]”是访问数组的唯一方式。
对象数组保存的是引用,基本类型数组直接保存基本类型的值。
public class ArrayOptions {
public static void main(String[] args) {
BerylliumSphere[] b = new BerylliumSphere[5];
System.out.println(Arrays.toString(b));
System.out.println(b.length);
}
}
/*
Output:
[null, null, null, null, null]
5
**/
数组b初始化为BerylliumSphere数组,但仍然可以通过length访问数组的大小,length只表示数组能够容纳多个元素,而不是实际保存元素的个数,这种小缺点使得无法知道数组b中确切的有多少个元素。
对象数组初始化时,其中所有的引用被初始化为null,所以检查其中的引用是否为null,就可以知道数组某个位置是否有对象。
int[] e;
//The local variable e may not have been initialized
System.out.println(e.length);
数组也是对象,为初始化的null对象不访问其对象或属性,编译器就会有错误。
16.3 返回一个数组
public class IceCream {
private static Random random = new Random(47);
private static final String[] FLAVORS = new String[]{"A","B","C","D","E","F","G","H","I","J","K"};
public static String[] flavorSet(int n){
if(n>FLAVORS.length)
throw new IllegalArgumentException("Set too big");
String[] result = new String[n];
boolean[] picked = new boolean[FLAVORS.length];//判断是否重复
for (int i = 0; i < n; i++) {
int index ;
do {
index = random.nextInt(FLAVORS.length);
} while (picked[index]);//false:不重复的 true:重复的,会再次的循环
result[i] = FLAVORS[index];
picked[index] = true;
}
return result;
}
public static void main(String[] args) {
System.out.println(Arrays.toString(flavorSet(3)));
}
}
16.4 多维数组
int[] [] a = {{1,2},{3,4},{5,6}};
System.out.println(Arrays.deepToString(a));
16.5 数组与泛型
class ClassParameter<T> {
public T[] f(T[] arg) {
return arg;
}
}
class MethodParameter {
public static <T> T[] f(T[] arg) {
return arg;
}
}
public class ParameterizedArrayType {
public static void main(String[] args) {
Integer[] arr = {1,2,3};
Integer[] a = new ClassParameter<Integer>().f(arr);
Integer[] b = MethodParameter.f(arr);
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
}
}
这种方式虽可以是数组具有泛型特性,但泛型容器总是更好的选择。
public class ArrayOfGenericType<T> {
T[] array;
public ArrayOfGenericType(int size){
//array = new T[size];//Cannot create a generic array of T
array = (T[]) new Object[size];//Type safety: Unchecked cast from Object[] to T[]
}
}
泛型在类或方法上很有效,但在类或方法内部擦除泛型会带来有问题。
16.6 创建测试数据
Arrays.fill()用同一个值填充各个位置,而针对对象而言,就是复制同一个引用进行填充。
public class FillingArrays {
public static final Integer SIZE = 6;
public static void main(String[] args) {
int[] a1 = new int[SIZE];
Arrays.fill(a1, 1);
System.out.println(Arrays.toString(a1));
String[] a2 = new String[SIZE];
Arrays.fill(a2, "a");
System.out.println(Arrays.toString(a2));
String[] a3 = new String[SIZE];
Arrays.fill(a3, 1,3,"c");
System.out.println(Arrays.toString(a3));
}
}
P443 16.6.2
16.7 Arrays实用功能
数组的复制
public class CopyingArrays {
public static void main(String[] args) {
int[] i = new int[7];
int[] j = new int[10];
Arrays.fill(i, 47);
Arrays.fill(j, 99);
System.out.println("i = "+Arrays.toString(i));
System.out.println("j = "+Arrays.toString(j));
System.arraycopy(i, 0, j, 0, i.length);
System.out.println("j = "+Arrays.toString(j));
Integer[] x = new Integer[7];
Integer[] y = new Integer[10];
Arrays.fill(x, 47);
Arrays.fill(y, 99);
System.out.println("x = "+Arrays.toString(x));
System.out.println("y = "+Arrays.toString(y ));
System.arraycopy(x, 0, y, 0, x.length);
System.out.println("y = "+Arrays.toString(y));
System.arraycopy(i, 0, y, 0, i.length);// java.lang.ArrayStoreException
}
}
/*
Output:
i = [47, 47, 47, 47, 47, 47, 47]
j = [99, 99, 99, 99, 99, 99, 99, 99, 99, 99]
j = [47, 47, 47, 47, 47, 47, 47, 99, 99, 99]
x = [47, 47, 47, 47, 47, 47, 47]
y = [99, 99, 99, 99, 99, 99, 99, 99, 99, 99]
y = [47, 47, 47, 47, 47, 47, 47, 99, 99, 99]
Exception in thread "main" java.lang.ArrayStoreException
at java.lang.System.arraycopy(Native Method)
at rock.lee.c16.CopyingArrays.main(CopyingArrays.java:25)
**/
复制对象数组,只是复制了对象的引用,而不是对象本身的拷贝,被称作“浅复制”。System.arraycopy()不会自动装箱和自动拆箱,所以两个数组成copy时必须具有相同类型,最后把int类型copy到Integer类型是不可以的。
数组的比较
public class ComparingArrays {
public static void main(String[] args) {
int[] a1 = new int[10];
int[] a2 = new int[10];
Arrays.fill(a1, 2);
Arrays.fill(a2, 2);
System.out.println(Arrays.equals(a1, a2));//true
a2[2]= 1;
System.out.println(Arrays.equals(a1, a2));//false
String[] s1 = new String[2];
String[] s2 = new String[]{"a","a"};
Arrays.fill(s1, "a");
System.out.println(Arrays.equals(s1, s2));//true
}
}
数组相等的条件:1、元素个数相等 2、对应位置的元素相等
通过对每一元素使用equals()作为比较判断,基本类型是通过其包装类的equals(),如Integer.equals()。
数组元素的比较
数组的排序
在以排序的数组中查找