第十三章 字符串
1. 不可变String
- String对象是不可变的,字符串对象作为方法的参数传递时,实际传递的是引用的一个拷贝.该引用所指的对象其实一直待在单一的物理位置上,从未动过.
- 给String对象赋值本质上是改变该String对象引用的指向.
2. 重载"+"与StringBuilder
- String对象是不可变的,你可以给一个String对象加任意多的别名.因为String对象具有只读特性,所以指向它的任何引用都不可能改变它的值.
- 不可变性会带来一定的效率问题,为String对象重载的"+"操作符就是一个例子.
- String的"+"操作经过编译器优化后是利用的StringBuilder对字符串进行拼接,性能不如直接使用StringBuilder拼接字符串要好.
- 多个String对象的"+"操作有可能会创建多个StringBuilder来拼接.
3. 无意识的递归
- 想打印对象的内存地址,在toString()方法中不能使用this关键字的返回字符串,this关键字会转换为String对象,从而递归调用toString()方法,会抛出异常.
- 解决方案: 使用super.toString()方法打印内存地址.这里的super父类是Object类.
package strings;
import java.util.ArrayList;
import java.util.List;
public class InfiniteRecursion {
@Override
public String toString() {
return " InfiniteRecursion address: " + super.toString() + "\n";
}
public static void main(String[] args) {
List<InfiniteRecursion> v = new ArrayList<>();
for (int i = 0; i < 10; i++) {
v.add(new InfiniteRecursion());
}
System.out.println(v);
}
}
4. String上的操作
- 当需要改变字符串的内容时,String类的方法都会返回一个新的String对象.
- 同时,如果内容没有发生改变,String的方法只是返回指向原对象的引用而已.这节省了存储空间以及避免了额外的花销。
5. 格式化输出
6. System.out.format()
- System.out.format()可以用于PrintStream或PrintWriter对象,其中包括System.out对象.
7. Formatter类
- Formatter类可以将格式化字符串与数据翻译成需要的结果.
- Formatter类格式化抽象语法: %[argument_index][flags][width][.precision]conversion
- 用"-“标志来改变对齐方向(默认右对齐),添加了”-"表示左对齐
- width: 控制一个域的最小尺寸,
- precision: 用来指明最大尺寸,用于String时,它表示打印String时输出字符的最大数量.用于浮点数时,表示小数显示的位数(默认6位),小数过多则舍入,过少则在尾部补零.用于整数时,会出发异常.
-
Formatter转换
-
String.format()
- 是一个static方法,接受与Formatter.format()方法一样的参数,但返回一个String对象.
- String.format()内部,它也是创建一个Formatter对象,然后将你传入的参数转给Formatter.
8. 正则表达式(目前简单了解)
- 在Java中使用正则表达式, \ 的意思是要插入一个正则表达式的反斜线, \\ 是插入一个普通的反斜线
第十四章 类型信息
1. 在运行时识别一个对象类型
- JAVA在运行时 有时要 识别对象和类的信息这个机制叫RTTI。Java提供了两种机制去做这件事。传统的RTTI 和 反射。
- 传统的RTTI 假定编译时就已经知道了所有的类型。
- 反射 允许在运行时发现和使用类型信息
- 传统的RTTI 在编译期通过Class文件识别类型信息,反射在运行期通过Class文件识别类型信息。
- Java类型转换都发生在运行时期。
2. Class对象
- Class对象是由Class这个类产生的,它包含了与某个类有关的信息。
- 每个类都有Class对象,即编译了一个新类就会产生一个该类的Class对象,并且保存在.class文件中。Class对象就是用来产生“常规”对象的。
- Java使用Class对象来执行RTTI。
- 所有类都是第一次使用时动态加载到jvm中。 动态加载就是需要时再加载不使用不加载。
- 只要创建了对一个类的静态成员的引用就会加载该类。new 的时候加载类说明 类的构造器虽然没写static但也是静态方法。
- 一个类的Class对象载入内存后,他就会用来创建该类的对象。
获得Class对象
- Class类的静态方法forName()可以通过传入一个全限定类名(包含包名)返回一个该类的Class类对象引用,此时该类会被加载到内存。
- Class.forName(“thinking14class.Test”)
- 运行时要获得一个类的信息(类型信息)可以通过一个该类的Class对象获得,使用Class.forName()就可以做到,而不必持有该类型的对象通过该对象获得。
- 如果有了一个实例对象可以调用getClass()方法来获得Class对象。
Class类的一些方法
- getName() 获得全限定类名, getCanonicalName()也是获得全限定类名。
- 对于普通类来说,二者没什么区别,只是对于特殊的类型上有点表示差异。
- getSimpleName()只获得类名,没有包名。
isInterface()判断是否为接口。 - getInterfaces() 返回一个Class对象数组,数组元素是该类实现的接口,元素顺序和实现顺序一致。
- getSuperclass() 返回直接基类(不是接口)的Class对象,可以用来发现对象完整的类继续结构。
- newInstance()创建该类实例对象并返回,但该类必须要有默认构造器,这个方法相当于一个虚拟构造器。
3. 类字面常量
- 类名.class 就是字面常量,代表的就是该类的Class对象引用。常量需要赋值给变量
- 简单,安全。 编译期接受检查,不需要像forName一样至于try/catch块中。
- 加载后不会进行初始化,初始化被延迟到静态方法静态域首次使用时。
- 类字面常量可用于 类 接口 数组 基本数据类型
- 基本数据类型也有类字面常量如int.class, 对应的包装器的 Integer.TYPE。
System.out.println(int.class); //int
System.out.println(Integer.TYPE); //int
System.out.println(Integer.class); //class java.lang.Integer
- Integer继承自Number 但 Integer Class 对象不是 Number Class对象的子对象,也就是说Class cn = int.class; 是错误的。
4. 泛化的class引用
- Class引用指向Class对象,该Class对象是Class类的实例,该对象可以制造一个类的实例,并包含一个类的各种信息。
- Class引用表示的就是Class对象的确切类型。
- Class<?> 优于Class 因为Class在编译期不会产生警告,而Class<?>当指向一个非具体的类引用时会产生警告。
- a.newInstance()如果Class引用a不是泛型引用,在编译期就不知道它会返回什么类型那么只能返回Object。
Class a = A.class;
Object t = a.newInstance();
//A t = (A) a.newInstance();//无法确定类型需要强制转换
System.out.println(t.getClass().getName());//thinking14class.A
- a.newInstance() a 是泛型引用并且能确定类型则会返回确切的类型
Class <A> a= A.class;
Class <? extends C> c= C.class; //上界
Class <? super A> d= A.class;
A ta = a.newInstance(); // 可以确定
A tc = c.newInstance(); // 上界至少它是一个C可以确定
- a.newInstance() a 是泛型引用但不能确定类型则只能返回Object。
Class <A> a= A.class;
Class <? extends C> c= C.class; //上界
Class <? super A> d= A.class; //下界
//A ta = a.newInstance(); // 通配符无法确定
A tc = c.newInstance(); // 上界至少它是一个C可以确定
//A td = d.newInstance();// 下界无法确定
- 利用Class类的cast()方法来转换类型
A a = new C();
Class<C> cType = C.class;
C c = cType.cast(a);
c = (C) a; //与上一步一样的效果
5. 类型转换前先做检查
- 传统的类型转换由RTTI确保正确性。
- instanceof关键字(二元操作符) ,返回一个Boolean值,告诉我们对象是不是某个类或该类派生类的实例,他判断的是类型。
if (a instanceof A) //对象a是不是属于A类型,A可能是a父类的父类,如果是这样也返回true
- instanceof 不能比较Class对象,对于Class对象使用isAssignableFrom()判断
if (as.isAssignableFrom(cs))// Class对象cs所在类是不是属于Class对象as所在类或者派生类
- 动态的instanceof :Class对象的isInstance(Object o)方法判断该Class对象是不是o类的(如果o是class对象所在类则返回true,否则返回false哪怕o是所在类的父类)。
if (cs.isInstance(c)) //如果c是class对象所在类则返回true,否则返回false,哪怕c是所在类的父类
6. 反射
- 反射机制:用来检查可用方法,并返回方法名。
- Class类和java.lang.reflect类库对反射提供了支持 点击查看
- reflect包中有Field类,Method类,Constructor类,这些类对象由jvm在运行时创建,用来表示未知类里的字段,方法,构造器。
- 使用Constructor创建新对象,Filed的get() set()方法修改Filed对象关联的字段,Class类的invoke()调用Method关联的方法。
- 调用Class类的getFileds()返回表示字段的Filed数组,getMethods()返回表示方法的Method数组,getConstructor()返回表示构造器的Constructor数组。
通过以上方法一个匿名对象的类信息便可在运行时被确定下来,再在运行时通过.class文件获得相关信息构造该类。 - 没有任何方法可以阻止反射调用那些非公共访问权限的方法,哪怕是private方法或者private域。
7. 动态代理
- 设计模式一种
- Java的动态代理比代理的思想更向前迈进一步,因为它可以动态创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作室揭示调用的类型并确定相应的对策
8. 空对象
- 设计模式一种
- 空对象最有用支出在于它更靠近数据,因为对象表示的是问题空间内的实体。
9. 接口与类型信息
- interface关键字的一种重要目标就是允许程序员隔离构建,进而降低耦合性。如果你编写接口,那么就可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并非是对解耦的一种无懈可击的保障。最简单的解决方式是对实现使用包访问权限。
第十五章
1. 泛型
- “泛型”意思就是适用于许多类型。
- 使用泛型的目的之一: 指定容器持有什么类型,让编译器确保正确性,而不是在运行期发现错误。
- 这个容器可以看成是有其他类型对象作为成员的类,而不单单只是JDK中的容器类。
2. 简单的泛型
元组
- 元组是对象,是一个将多个对象打包存储于其中的单一对象。Java中没有元组这个类库,Python中有元组这一概念。
- 可以通过泛型构建自己的元组类库
class TwoTuple<A,B>{
public final A first;
public final B second;
TwoTuple(A a, B b){
first = a;
second = b;
}
- 元组允许读取元素,但不能插入新元素,不可以修改元素值,因为元素被设置为final。
- 元组可以任意长度,可以存储任何类型对象。
元组泛型的继承
- 父类的泛型同样可以继承给子类,但要显示的写出父类的泛型
class ThreeTuple<A,B,C> extends TwoTuple<A,B>{
public final C three;
public ThreeTuple(A a, B b, C c){
super(a,b);
three = c;
}
- 一个方法只能返回一个对象,但返回一个元组就可以包含多个对象
public static TwoTuple<String,Integer> f(){
return new TwoTuple<String, Integer>("hi",99);
}
模拟堆栈
- 用泛型实现一个自定义堆栈
- 使用内部链式存储机制
public class StackDemo {
public static void main(String[] args) {
LinkedStack<String> lss = new LinkedStack<String>();
lss.push("please");
lss.push("say");
lss.push("good");
String s;
while ((s = lss.pop()) != null)
System.out.println(s);
}
}
class LinkedStack<T> {
//定义栈结点,结点为一个对象
//结点保存元素类型使用泛型45表示
private class Node<U> {
U item; //要保存的数据
Node<U> next; //指向下一结点的引用
Node() { //默认构造器,构造空结点
item = null;
next = null;
}
//该构造器给结点赋值
Node(U item, Node<U> next) {
this.next = next;
this.item = item;
}
//判断栈是否为空
boolean end() {
return item == null && next == null;
}
}
//设置一个末端哨兵,该哨兵结点为空结点
private Node<T> top = new Node<T>();
//压栈 每次push创建一个Node结点
public void push(T item) {
/*第一次压栈将末端哨兵连接到该结点的next,并且top不再是末端哨兵而是第一个结点
第二次压栈将第一个结点连接在第二个结点的next上,top是第二个结点
...
以此类推 整个栈完成
*/
top = new Node<T>(item, top);
}
//弹栈
public T pop() {
T result = top.item; //得到结点数据
if (!top.end()) //如果本结点不是空元素,则丢弃当前top,指向下一top
top = top.next;
return result;
}
}
3. 泛型接口
- 泛型也可以用于接口,例如生成器,生成器是专门负责创建对象类,一般只定义一个方法。
4. 泛型方法
泛型方法定义
- 泛型参数列表至于返回值前 如:
- public void f(){}; 这是泛型方法;
- public int f(T a){};这不是泛型方法,返回值前无泛型。
泛型方法
- 泛型方法所在的类可以是泛型类,也可以不是泛型类,并且泛型标识符可以完全不一样,也就是说泛型方法和泛型类无关。
- 普通static方法无法访问泛型类的类型参数,如果要是使用泛型就要定义成泛型静态方法
public class Gy<T> {
T name;
/* public static T f(T a){ //编译不通过
return a;
}*/
public static <T> T f(T a){
return a;
}
public T g(T b){
return b;
}
}
- 类的泛型要在创建对象时才确定,而类内的静态方法,静态域在类加载时初始化,因此如果使用类的泛型类型则初始化时无法知道具体类型是什么,此时使用泛型方法这样就和类的泛型无关了,这样静态方法初始化时类型只和自身的泛型相关。
- 使用泛型方法时编译期会通过类型参数推断来为我们找出具体类型,而不必自己声明时什么类型。
- 泛型方法返回值是泛型,那么就返回一个泛型,不能是具体类型,反之亦然。
public static <T> Set<T> set(){
// return new HashSet<String>(); //不能返回具体类型
}
显式类型说明
- 泛型就是为了适用于多种类型,而显式类型说明却指定了泛型的具体类型
- 显式类型说明用在调用泛型方法上。
- 泛型方法调用后如果产生一个泛型结果,则不能将泛型结果传入另一个方法,而必须要这么做时就可以使用它显式类型说明。
- 在点操作符和方法名称之间插入<类型> 如果该泛型方法
- 和要传入方法(非静态方法)在同一个类要用 this.<>g()
- 是静态方法要用 类名.<>g()
- 通过对象调用
public static <T> Set<T> set(){
return new HashSet<T>();
}
public static void f(Set<List> stringSet){}
public static void main(String[] args) {
f(Gy.<List>set());
}
可变参数泛型方法
public static void main(String[] args) {
System.out.println(f(1,2,3,4,"juih"));
}
public static <T> List<T> f(T... args){
List<T> result = new ArrayList<T>();
for (T item : args) {
result.add(item);
}
return result;
}
[1, 2, 3, 4, juih]
并且可以准确分辨参数类型
System.out.println(f(1,2,3,4,"juih").get(3).getClass().getName());//java.lang.Integer
System.out.println(f(1,2,3,4,"juih").get(4).getClass().getName());//java.lang.String
生成器Generator思想
5. 泛型用于匿名内部类
6. 构建复杂模型
7. 擦除
- jvm并不认识泛型因此需要将泛型擦除。
- ArrayList 和 ArrayList很容易被认为是不同类型。因为他们有不同的行为,但程序却认为他们是相同的,正是因为擦除的存在。
- 擦除的结果就是把一个对象变为它的原生类
- 泛型只是用来检查类型正确性,泛型部分的代码不会参与运行,这是由于泛型的擦除作用。
- 泛型代码内部无法获得有关泛型参数的类型的信息。
泛型擦除到第一个边界
- 上界 意思就是T 只能为HasF或者其子类。
- 泛型只是在静态类型检查期间出现来验证类型正确性,程序一但运行后泛型都将被擦除,替换成他们的非泛型上界,如List被擦除为List,List被擦除为List, 擦除为
擦除动机
- 擦除使得泛化的代码变得具体,因此泛化客户端可以使用非泛化类库,非泛化客户端也可以使用泛化类库
擦除的代价
- 泛型不能当做一个类去操作,如Foo cat不能代表Cat这个类,因为它会被擦除为Object.
边界处的动作
- 泛型中创建数:Array.newInstance(类<?> componentType, int length) 并且要强制转型为T[]类型。
public class Test<T> {
private Class<T> kind;
T[] create(int size){
return (T[]) Array.newInstance(kind,size);//必须强转T[]
}
List<T> createList(){
return new ArrayList<T>();
}
}
- 边界就是对象进入和离开方法的地方,编译期执行类型检查和插入转型代码就是在边界处。
- 编译期执行了类型检查确保了数据一致性,在离开方法时由编译器为你插入转型代码执行转型,此时转型是安全的。
- 由于擦除kind实际被存储为Class,因此创建数组无法后知道要转型成什么类型,因此必须强转。但创建容器就不需要强转了,编译期可以保证类型的一致性,如果类型不一致不通过编译。
8. 擦除补偿
- 由于擦除存在,所以任何在 运行时 需要知道泛型代表的类型的 确切类型信息 的操作都无法工作。
- 解决办法:引入类型标签
- 给泛型类添加一个标签成员Class kind; 构造器传入类型参数赋值给kind,这样就得到了泛型的类型。
创建类型实例
- 创建泛型的实例 不可以 new T() 一来因为擦除,二来因为不能确定T是否有默认构造器.
- 利用类型标签 可以kind.newInstance()创建对象,但遇到没有默认构造器的类如Integer,运行时就会出错,而编译期无法捕获错误。
- Java解决方案是传入一个显示工厂对象来创建实例,并且限制其类型。
interface Factory<T>{
T create();
}
//创建显式工厂并限制类型为Integer
class IntegerFactory implements Factory<Integer>{
@Override
public Integer create() {
return new Integer(0);
}
}
class Widget{
//创建显式工厂并限制类型为Integer
public static class FactionWidget implements Factory<Widget>{
@Override
public Widget create() {
return new Widget();
}
}
}
class Foo2<T> {
private T x;
public <F extends Factory<T>> Foo2(F factory){
x = factory.create();
}
}
public class Test{
public static void main(String[] args) {
// 创建Foo2对象实例 并且x为泛型的实例对象
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.FactionWidget());
}
}
- 使用模板方法设计模式
泛型数组
- 不能直接创建泛型数组 T[] array = new T[size]
- 可以定义一个泛型数组的引用 T[ ] array ,但无法使用这个引用。
- 解决办法
- 可以使用ArrayList来代替数组达到相同目的。
- 内部创建一个Object[ ] 数组,在需要时将数组内的对象转型为需要的类型,但不能将Object[ ]转型为T[ ],因为没有任何方式可以改变数组底层类型。
public class Test<T> {
private Object[] array={"ji"};
public T get(int index) {
return (T) array[index];
}
public static void main(String[] args) {
Test<String> test = new Test<String>();
String s = test.get(0);// String s = (String)test.get(0);编译器自动插入转型代码
}
}
其实 get内没有任何类型转换 ,T 被擦除成了Object,只是Object转Object了, 创建对象确定T类型后在编译阶段编译器会为你插入转型代码。
9. 边界
- 作用
- 强制泛型可以使用什么类型
- 按边界类型调用方法其方法,无边界的只能调用从Objec继承的方法。
- <T extends A & B & C > A B C 之间没有继承关系
- 多边界擦除到第一个边界A。
10. 通配符
- 通配符可以允许某种类型向上转型,与普通边界对比:
- List first = new ArrayList(); 只能使用T
- List<? extends Fruit> first = new ArrayList(); //可以使用各种Fruit的子类。
- List<? extends Fruit> 读作具有任何从Fruit继承的类型列表。
区别一个概念
- 数组可以向上转型,即导出类型的数组可以赋值给及基本类型数组的引用
- 而导出类型的泛型确不能赋值给基本类型泛型的引用 如: List list = new ArrayList(); 语法错误
- Apple 是 Fruit 的子类,但 Apple所在的List却不是Fruit所在的List的子类,故不能这样转型。
上界和下界
- <? extends Fruit> 上界 ?是Fruit 的子类,但具体是什么不知道,因此当调用get方法时返回的对象可以赋值给Fruit引用,而add添加对象时由于不清楚具体要添加什么子类所以无法使用add方法。
- <? super Apple > 下界 也称 逆变?是Apple的父类,但具体是什么类型不得而知,因此当调用add方法添加对象时可以添加Apple和其子类对象,但调用get方法时无法确定要返回什么类型,因此不能调用get方法返回具体类型,只能返回Object。
无界通配符 <?>
- 表示任何类型,相当于使用原生类 ,他还意味着声明 “我要在这里使用泛型”
- 原生类List实际上是List,表示持有Object对象的原生类,而List<?>是利用泛型给List持有的对象划了一个具体的范围,虽然范围也是Object,但List<?>确不是原生类。
<?>与上下界之间的区别
- 一个方法的参数的类型如是 List ,List<?> ,则可以接收任何形式的List参数,参数是不是泛型无所谓。
- 参数的类型如果是List<? extends/super A > ,则只能接收泛型的List参数.
- 如果参数的类型是 <?> 或者 <? extends A>,则该方法无法调用
- <?>可以向上转型
- 多个泛型参数下只有全为?时编译器无法与原生类区分,但只要有过一个参数不是?就会有所区分如Map<String, ?>必须传入map<String,?>类型的参数,而Map<?,?>可以传入new HashMap();
捕获转换
- 向一个<?>方法传入有个原生类型,编译器可以捕获实际类型参数,这个<? >方法调用其他方法时向其他方法传递被捕获的对象时就会传递确切类型。 如 A a =new A(); 将a传入f(A<?> s)方法,f可以捕获确切类型 即s=A
11. 泛型存在的问题
- 基本数据类型无法作为泛型的类型参数,如 T不能是int 可以使用包装器类
- 自动包装机制不能用于数组int[ ] 不能成为Integer[ ]
- 带有泛型类型参数的转换或者使用instanceof判断类型都没有任何效果
- 被擦除后如果产生相同的方法签名那么不允许编译。
12. 自限定类型
ClassA extens ClassB<A>
- 自限定会强制要求将正在定义的类当做参数传递给基类
13. 动态类型安全
- 以后可以使用Collections工具类中的checkedList(list, type),checkedMap()等方法检查传入容器的类型。
14. 异常
- 类型参数可应用于一个方法的throws子句中
15. 混型
- 混型:混个多个类的能力,产生一个可以表示混型中所有类型的类。
- 与接口混合
- 使用代理方式,每个混入类型中都有相应的域,使用时代理调用方法。
装饰器模式 - 与动态代理结合
16. 潜在类型机制
class Dog:
def speak(self):
print "atf:
def sit(self):
print "Sitting"
def reproduce(self):
pass
class Robot:
def speak(self):
print "atf:
def sit(self):
print "Sitting"
def reproduce(self):
pass
a = Dog()
b = Robot()
perform(a)
perform(b)
- perform(anything) 没有针对任何anything类型,anything包含的接口是潜在的。
- Java不支持潜在类型机制。
17. 对缺乏潜在类型机制的补偿
- 反射
利用反射可以动态地确定所需要的方法并调用它。 - 将方法应用于序列
反射提供了一些有趣的可能,但它将所有的类型检查都转移到了运行时。
尝试一些实现编译时期检查。 - 当你并未拥有正确接口时
编译时期可以检查类型,但是类型被限制在继承层次之内。 - 用适配器仿真潜在类型
适配器模式模仿潜在类型机制,并在编译时期检查类型
/**
* 适配器模式实现潜在类型机制
* @author Administrator
*
* @param <T>
*/
interface Addable<T> { void add(T t);}
class AddableCollectionAdapter<T> implements Addable<T>{
private Collection<T> c;
public AddableCollectionAdapter(Collection<T> c) {
this.c = c;
}
@Override
public void add(T t) {
c.add(t);
}
}
class Adapter{
public static<T> Addable<T> collectionAdapter(Collection<T> c) {
return new AddableCollectionAdapter(c);
}
}
class AddableSimpleQueue<T> extends SimpleQueue<T> implements Addable<T>{
public void add(T t) {
super.add(t);
}
}
public class Fill2 {
public static<T> void fill(Addable<T> addable,
Class<? extends T> classToken,int size) {
for (int i = 0; i < size; i++) {
try {
addable.add(classToken.newInstance());
} catch (Exception e) {
throw new RuntimeException();
}
}
}
public static<T> void fill(Addable<T> addable,
Generator< T> generator,int size) {
for (int i = 0; i < size; i++) {
try {
addable.add(generator.next());
} catch (Exception e) {
throw new RuntimeException();
}
}
}
public static void main(String[] args) {
List<Coffee> coffees = new ArrayList<>();
Fill2.fill(new AddableCollectionAdapter<Coffee>(coffees), Coffee.class, 3);
Fill2.fill(Adapter.collectionAdapter(coffees), Coffee.class, 3);
for (Coffee coffee : coffees) {
System.out.println(coffee);
}
System.out.println("--------------------");
AddableSimpleQueue<Coffee> coffeequeue = new AddableSimpleQueue();
Fill2.fill(coffeequeue, Mocha.class, 5);
Fill2.fill(coffeequeue, Latte.class, 6);
for (Coffee coffee : coffeequeue) {
System.out.println(coffee);
}
}
}