640 likes | 778 Views
Reflection & Generics in JDK1.5. 侯捷 / J.J. Hou 資訊教育 / 專欄執筆 / 大學教師 jjhou@jjhou.com, http://www.jjhou.com. Generics. 講題提要. Generics. 自JDK1.3起, Generics 技術以各種 外掛 方式進入Java。JDK1.5正式 內建 此一技術,對 Java 及其 Library 帶來全面的影響與改寫。本講題追蹤該技術全面進入 JDK1.5 的狀況。. Reflection. 講題提要. Reflection.
E N D
Reflection & Generics in JDK1.5 侯捷 / J.J. Hou資訊教育 / 專欄執筆 / 大學教師jjhou@jjhou.com, http://www.jjhou.com
Generics 講題提要.Generics 自JDK1.3起,Generics技術以各種外掛方式進入Java。JDK1.5正式內建此一技術,對 Java 及其 Library 帶來全面的影響與改寫。本講題追蹤該技術全面進入 JDK1.5 的狀況。
Reflection 講題提要.Reflection Reflection 為 Java 帶入靜態語言難以企及的動態應用,廣為人知者便是Component-based programming(例如Javabean)開發環境, deSerialization,以及RMI(Remote Method Invocation)。本講題說明 Reflection 基本觀念及 APIs 運用。
Generics 同質(homogenous)容器與異質(heterogenous) 容器 同質容器:元素型別必須相同。 C++ STL 的容器都是同質容器。 list<int> myList;myList.push_back(3); 異質容器:元素型別可以不同。 Java Collections 都是異質容器。 LinkedList myList = new LinkedList(); myList.add(new Double(4.4));myList.add(new String("jjhou")); 編譯器建立 int版-list 元素型別是Object
Generics 同質容器 in Java Java 借鑑 C++ 語彙符號 "< >",建立同質容器,是謂 Generics in Java: LinkedList<Integer> myList = new LinkedList<Integer>(); myList.add(new Integer(4));myList.add(new Integer(7)); myList.add(new String("hello")); //Error!
Generics 同質容器與多型(Polymorphism)應用 Java Collections 原就充份運用多型: 相當於LinkedList LinkedList<Object> myList = new LinkedList<Object>(); Object 5 11 55 Stroke Rect Circle 18 22 66 2b w:Integeria:ArrayList <Integer> l:Integert:Integerw:Integerh:Integer x:Integery:Integerr:Integer 33 77 Circle 18 44 2c Rect 18 2c Stroke
Generics 同質容器與多型(Polymorphism)應用 為更好地運用多型,可這麼寫: LinkedList<Shape> myList = new LinkedList<Shape>(); Object Shape 5 11 55 18 22 66 2b 33 77 Circle Stroke Rect Circle 18 44 2c w:Integeria:ArrayList <Integer> l:Integert:Integerw:Integerh:Integer x:Integery:Integerr:Integer Rect 18 2c Stroke
Generics 加入 Generics 之前 Java 為保持語言的簡單性,強迫程式員在使用 Collections 時必須記住所擁有的元素型別;取出元素並進一步處理前,必須先轉型,從 Object轉為實際型別。 LinkedList myList = new LinkedList(); myList.add(new Integer(0)); myList.add(new Integer(1)); Integer tempi = (Integer)myList.iterator().next();
Generics 加入 Generics 之後 如果以generic types擴充 Java 語言,就能以更直接的方式表現 collection 的相關資訊,於是編譯器可追蹤記錄實際的元素型別,您也就不再需要轉型。有助於程式的開發與除錯。 LinkedList<Integer> iList = new LinkedList<Integer>(); iList.add(new Integer(0)); iList.add(new Integer(1)); Integer tempi = iList.iterator().next();
Generics Java/C++泛型技術的根本差異 C++ list class list; // int版本 .exe template <typename T,...> class list { ... }; class list; // string版本 膨脹法 class list; // double版本 #include <list> list<int> li; list<string> ls; list<double> ld; list<int> li; list<string> ls; list<double> ld; Java java.util.LinkedList.java public class LinkedList<E> { ... }; public class LinkedList { ... }; .class 擦拭法 import java.util.* LinkedList<Integer> ...; LinkedList<String> ...; LinkedList<Double> ...; import java.util.* LinkedList ...; LinkedList ...; LinkedList ...; .class
Generics Generics Java技術發展歷程 GJ安裝後的路徑 • JDK1.3 + GJ(a Generic Java language extension)以預處理器(%GJClasses%\gjc.Main)+翻新的Collections(%GJClasses%\*.class)提供GP。 • JDK1.4+JSR14(Adding Generics to Java Language)以編譯器套件(%JSR14DISTR%\javac.jar)+翻新的Collections(%JSR14DISTR%\collect.jar)提供GP。 • JDK1.5內建所有必要的GP服務。Java Library全面翻新。 JSR14安裝後的路徑
Generics 學習與觀察Java Generics • 運用Generic Classes 例如 java.util (collections) • 運用Generic Algorithms (methods) 例如 methods in java.util.Collection • 撰寫Generic Classes • 撰寫Generic Algorithms (methods)
Generics 在JDK1.5中運用Generic Classes/Algorithms ArrayList<String> strList = new ArrayList<String>(); strList.add("zero"); strList.add("one"); strList.add("two"); strList.add("five"); System.out.println(strList); // [zero, one, two, five] String str = Collections.max(strList); Collections.sort(strList); static methods
Generics Boxing & UnBoxing 的影響 容器元素必須是 object(不可為純數值) LinkedList<Integer> iList = new LinkedList<Integer>(); iList.add(new Integer(0)); iList.add(new Integer(1)); iList.add(new Integer(5)); iList.add(new Integer(2)); LinkedList<Integer> iList = new LinkedList<Integer>(); iList.add(0); //boxing iList.add(1); iList.add(5); iList.add(2); int i = iList.get(2); //un-boxing
Generics 觀摩 JDK1.5, java.util.Map public interface Map<K,V> { V get(Object key); V put(K key, V value); V remove(Object key); void putAll(Map<? extends K, ? extends V> t); interface Entry<K,V> { … } ... }
Generics 觀摩 JDK1.4, java.util.Map public interface Map { Object get(Object key); Object put(Object key, Object value); Object remove(Object key); void putAll(Map t); interface Entry { … } ... }
Generics 觀摩 JDK1.5, java.util.ArrayList public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private transient E[] elementData; private int size; public ArrayList(Collection<? extends E> c) { size = c.size(); // Allow 10% room for growth elementData = (E[])new Object[ (int)Math.min((size*110L)/100, Integer.MAX_VALUE)]; c.toArray(elementData); } ...
Generics 觀摩 JDK1.4, java.util.ArrayList public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable { private transient Object elementData[]; private int size; public ArrayList(Collection c) { size = c.size(); // Allow 10% room for growth elementData = new Object[ (int)Math.min((size*110L)/100, Integer.MAX_VALUE)]; c.toArray(elementData); } ...
Generics 在JDK1.5中撰寫Generic Classes LinkedList<Shape> sList = new LinkedList<Shape>(); sList.add(new Stroke<Integer,Integer>(…)); sList.add(new Rect<Integer>(…)); sList.add(new Circle<Integer>(…)); Object LinkedList<Shape> sList = new LinkedList<Shape>(); Shape draw() len() compareTo() 5 11 55 18 22 66 2b W,T T T 33 77 Circle<Integer> Stroke Rect Circle 18 44 2c w:T a:ArrayList<T> l:T t:T w:T h:T x:T y:T r:T Rect<Integer> 18 2c Stroke<Integer,Integer>
Generics 在JDK1.5中撰寫Generic Classes 類似設計是 public final class Integer extends Number implements Comparable<Integer> 為了可用於 max() public abstract class Shape implements Comparable<Shape>{ public abstract void draw(); public abstract double len(); public int compareTo(Shape o) { return (this.len() < o.len() ? -1 : (this.len() == o.len() ? 0 : 1)); } } 典型的Template Method 只要class名稱加上 <> 即成為 generic class public class Stroke<W,T> extends Shape implements Serializable { W width; ArrayList<T> a; public double len() { ... } public void draw() { ... } } 實際計算長度時, 恐怕會遭遇麻煩 !
Generics 在 JDK1.5 中撰寫 Generic Classes 只要class名稱之後加上 <>,即成為 generic class 類似的設計是 Class<T> public class Rect<T> extends Shape implements Serializable { T l,t,w,h; public void draw() { ... } public double len() { ... } } public class Circle<T> extends Shape implements Serializable { T x,y,r; public double len() { ... } public void draw() { ... } } 實際計算長度時, 恐怕會遭遇麻煩 !
Generics 在 JDK1.5 中撰寫 Generic Algorithms 只要method名稱之前加上 <>, 即成為 generic method 形式 (1) // in someone class public static <T> TgMethod (List<T> list) { ... } "bounded type parameter" "受到更多約束" 的型別參數 public static <T extends Comparable<T>> TgMethod (List<T> list) { ... } 形式 (2) JDK 1.5允許「不被method真正用到」 的型別參數以符號 '?' 表示 public static List<?>gMethod (List<?> list) { return list; // (本例)原封不動地傳回 } 形式 (3)
Generics 觀摩 JDK1.4, java.util.Collections.max() #001 public class Collections { #002 ... #003 public static #004 //這裡刻意空一行,以利與JDK1.5源碼比較 #005 Objectmax(Collection coll) { #006 Iterator i = coll.iterator(); #007 Comparable candidate = (Comparable)(i.next()); #008 #009 while(i.hasNext()) { #010 Comparable next = (Comparable)(i.next()); #011 if (next.compareTo(candidate) > 0) #012 candidate = next; #013 } #014 return candidate; #015 } #016 ... #017 } // of Collections
Generics 觀摩 JDK1.5, java.util.Collections.max() #001 public class Collections { #002 ... #003 public static #004 <T extends Object & Comparable<? super T>> #005 Tmax(Collection<? extends T> coll) { #006 Iterator<? extends T> i = coll.iterator(); #007 T candidate = i.next(); #008 #009 while(i.hasNext()) { #010 T next = i.next(); #011 if (next.compareTo(candidate) > 0) #012 candidate = next; #013 } #014 return candidate; #015 } #016 ... #017 } // of Collections
Generics 觀摩 JDK1.5, java.util.Collections.max() 3 4 5 6 1 2 1. max() 將接收一個 Collection object 2. 該 Collection object 所含元素必須是 T-derived object 3. T 必須繼承自Object(這倒不必明說。Java必定如此) 4. T 必須實作 Comparable 5. Comparable 所比較的型別必須是 supertype of T 6. max() 傳回 T object
Reflection 所謂 Introspection (內省, 內觀, 反省) 看透("look inside")classes 的能力,我們稱之為 introspection。或說 "the ability of the program to examine itself" Java有兩個作法可以進行 introspection: (1) Static Introspection : .class file inspection (2) Dynamic Introspection : Reflection API
Reflection 所謂 Reflection Reflection 是 Java 被視為動態(或準動態)語言的關鍵,允許程式於執行期透過 Reflection APIs 取得任何已知名稱之 class的內部資訊,包括 package、type parameters、superclass、implementedinterfaces、inner classes, outer class, fields、constructors、methods、modifiers,並可於執行期生成instances、變更 fields 內容或喚起 methods。
Reflection 所謂 Reflection 對於每一個class,JRE都會維護一個對應的不可改動的(immutable)Class object,其中內含該class資訊. Reflection APIs 便是從中取得資訊 virtual machine environment dynamic loading by class loader (a ClassLoader object) Test class definition(Class object for Testclass) Test.class disk
Reflection Reflection APIs 有了Reflection API,我們可以: • 決定(判斷)某個 object 所屬的 class. • 取得 class 的 modifiers, fields, methods, constructors, 和 superclasses 的相關資訊. • 找出 interface 中的constants 和 method declarations. • 為一個執行期才得知名稱的 class 產生實體(物件). • 取得及設定 object's field 值, 即使執行前對它一無所知. • 喚起(invoke)object'method,即使執行期才得知它. • 產生一個新的 array, 其大小和元素型別 (component type) 在執行之前未知。並可更動 array 的元素.
Reflection Reflection APIs Core Reflection API 容納兩類應用。第一類應用需要找出並使用某個 object 的 run-time class 的所有public members。這些應用要求能夠於執行期存取該 object 的所有 public fields, methods, constructors。例如 Java Beans 提供的 services,以及 lightweight tools 如 object inspectors。這些應用程式統是使用 Class所提供的 methods: getField, getMethod, getConstructor, getFields, getMethods, getConstructors來獲得 classes Field, Method, Constructor的 instances。 第二類應用很精巧,需要找出並使用被某個 class 宣告的 members。它們需要能夠於執行期存取「由 class file 供應之 class 實作層級」。例如 development tools 如 interpreters, inspectors, 以及 class browsers, 以及 run-time services 如 Java Object Serialization。這些應用使用 Class所提供的 methods: getDeclaredField, getDeclaredMethod, getDeclaredConstructor, getDeclaredFields, getDeclaredMethods, getDeclaredConstructors來獲得 classes Field, Method, Constructor的 instances。
Reflection Reflection APIs Reflection API 由java.lang.Class和 java.lang.reflect classes: (1)Field,(2)Method,(3)Constructor,(4)Array, (5)Modifier組成。前三者表現 classes /interfaces 的相應 (corresponding) 成員。Array提供 methods 用來建立, 存取, 改動 arrays。Modifier提供 methods 對 modifiers (如 static, public) 解碼(解釋)。
Reflection Object class java.lang.Object java.lang.Class Object class 是所有 Java classes 的 root。 public class Object { public final nativeClass<? extends Object>getClass(); public native int hashCode(); public boolean equals(Object obj) { return (this == obj); } protected native Object clone() throws CloneNotSupportedException; public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } public final native void notify(); public final native void notifyAll(); public final native void wait(long timeout) throws InterruptedException; public final void wait(long timeout, int nanos) throws InterruptedException; public final void wait() throws InterruptedException; protected void finalize() throws Throwable { } }
Reflection Class class java.lang.Object java.lang.Class Class instance 所表現的,是執行中的 Java 程式內的 classes 和 interfaces。enum, annotation, array, primitive Java types (boolean, byte, char, short, int, long, float, double), 以及關鍵字 void 也都可被表現為 Class objects. Class並沒有public constructor,其 objects 係當 classes 被載入或 class loader 的 defineClass() 被呼叫時,由 Java Virtual Machine 自動建構。 以下使用 Class object 列印某個 object 的 class 名稱: void printClassName(Object obj) { System.out.println("The class of " + obj + " is " + obj.getClass().getName()); } 也可對已知型別(或 void),根據其 class literal 取得 Class object,例如: System.out.println("The name of class Foo is: "+Foo.class.getName());
Reflection Class class Class並沒有public constructor。其 objects 係當 classes 被載入 (loadClass(...))或 class loader 的 defineClass(...) 被呼叫時,由 Java Virtual Machine 自動建構。因此,我可以(修改源碼後)測得某個 class 被載入,卻無法測得其對應之 Class object 的誕生。 public final class Class<T> implements java.io.Serializable, java.lang.reflect.GenericDeclaration, java.lang.reflect.Type, java.lang.reflect.AnnotatedElement { private Class() {} ... public String toString() { return ( isInterface() ? "interface " : (isPrimitive() ? "" : "class ")) + getName(); }
Reflection Class class 獲得 java.lang.Class objects 的各種辦法 From getClass(). String str = "abc"; Class c1 = str.getClass(); From the method Class.getSuperclass() Button b = new Button(); Class c1 = b.getClass(); Class c2 = c1.getSuperclass(); BTW getSuperclass() returns null for the class Object. From the static method Class.forName() Class c1 = Class.forName ("java.lang.String"); Class c2 = Class.forName ("java.awt.Button"); Class c3 = Class.forName ("java.util.LinkedList$Entry"); Class c4 = Class.forName ("I"); Class c5 = Class.forName ("[I"); From the .class syntax Class c1 = String.class; Class c2 = java.awt.Button.class; Class c3 = Main.InnerClass.class; Class c4 = int.class; Class c5 = int[].class; From the primitive wrapper classes Class c1 = Boolean.TYPE; Class c2 = Byte.TYPE; Class c3 = Character.TYPE; Class c4 = Short.TYPE; Class c5 = Integer.TYPE; Class c6 = Long.TYPE; Class c7 = Float.TYPE; Class c8 = Double.TYPE; Class c9 = Void.TYPE;
Reflection ClassLoader class 所謂 class loader 是個 object,負責載入 classes。ClassLoader 是個 abstract class。給予它 class 名稱,class loader 便應該嘗試定位 (locate) 或生成 (generate) 某些資料,用以構成一個 class definition。典型策略是將該名稱轉換為檔名,然後讀入對應的 "class file"。 每個Class object 都內含一個getClassLoader(), reference to「定義該 Class object」的那個ClassLoader。 ClassLoader class 運用delegation model 來搜尋 classes 和 resources。其每一個 instance 都有一個 associated parent class loader。一旦請求搜尋一個 class 或 resource,ClassLoader instance 便會將工作委託 (delegate) 給其 parent class loader,在企圖找到 class 或 resource 本身之前。Virtual machine 的內建 class loader 稱為 bootstrap class loader,它本身沒有 parent,但也許用做一個ClassLoader instance 的 parent。 通常,Java virtual machine 以一種 platform-dependent 方式從 local file system 載入 classes。例如在 UNIX 系統上,virtual machine 從環境變數 CLASSPATH 所定義的目錄中載入 classes 。 然而某些 classes 最初或許並非來自檔案,而是來自其他資源,像是網絡,或是被應用程式所建構。method defineClass(String, byte[], int, int)會轉換an array of bytes 成為一個Class instance。這種新定義出來的 class,可使用 Class.newInstance() 產生 instances。
Reflection Class methods Class 涵蓋範圍(局部) Package getPackage() String getName() TypeVariable<Class>[] getTypeParameters() Class getSuperClass() Class[] getInterfaces() Class[] getDeclaredClasses() Class getDeclaringClass() Consructor[] getDeclaredConstructors() Method[] getDeclaredMethods() Field[] getDeclaredFields() but 無法取得 import lib... 1 2 package java.util; public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Queue<E>, Cloneable, java.io.Serializable { private static class Entry<E> { … } public LinkedList() { … } public LinkedList(Collection<? extends E> c) { … } public E getFirst() { … } public E getLast() { … } private transient Entry<E> header = …; private transient int size = 0; } 3 4 5 6 7 8 9
yy (class name part) nm (fully qualified name) key value java.lang.Integer Integer java.lang.Integer 這樣是一個 Hashtable 元素 lastIndexOf(".") Reflection 小工具 tName() static String tName(String nm, Hashtable ht) { String yy; String arr; if (nm.charAt(0) != '[') { int i = nm.lastIndexOf("."); if (i == -1) return nm; // primitive type, ignore it. else { yy = nm.substring(i+1); if (ht != null) ht.put(nm, yy); return yy; } } ...
Reflection 取得 package Package getPackage() 1 package java.util; Class c = null; c = Class.forName(args[0]); Package p; p = c.getPackage(); if (p != null) System.out.println("package "+p.getName()+";"); package java.util;
Reflection Constructor cn[]; Method mm[]; Field ff[]; 取得完整的 import list (1)取得所有fields,將其type記錄於hashtable ff = c.getDeclaredFields(); for (int i = 0; i < ff.length; i++) x = tName(ff[i].getType().getName(), classRef); cn = c.getDeclaredConstructors(); for (int i = 0; i < cn.length; i++) { Class cx[] = cn[i].getParameterTypes(); for (int j = 0; j < cx.length; j++) x = tName(cx[j].getName(), classRef); } mm = c.getDeclaredMethods(); for (int i = 0; i < mm.length; i++) { x = tName(mm[i].getReturnType().getName(), classRef); Class cx[] = mm[i].getParameterTypes(); for (int j = 0; j < cx.length; j++) x = tName(cx[j].getName(), classRef); } classRef.remove(c.getName()); (2)取得所有ctors,將其parameters type 記錄於hashtable (3)取得所有methods,將其return/parameters type 記錄於hashtable (4)從hashtable中移除自己
Reflection 取得完整的 import list import java.lang.Object; import java.util.Collection; import java.util.Set; import java.util.ListIterator; import java.lang.Object; import java.util.LinkedList$Entry; import java.util.Collection; import java.io.ObjectOutputStream; import java.io.ObjectInputStream;
Reflection 取得 class name 及modifier 2 public class LinkedList<E> String getName() public final class Class public class LinkedList c = Class.forName(args[0]); int mod = c.getModifiers(); System.out.print(Modifier.toString(mod)); //整個modifier if (Modifier.isInterface(mod)) System.out.print(" "); //"interface" 已含於modifier else System.out.print(" class "); //關鍵字 "class" System.out.print(tName(c.getName(), null)); //class名稱 public abstract interface Map
Reflection 取得 Type Parameters package java.util public class LinkedList<E> 3 TypeVariable<Class>[] getTypeParameters() TypeVariable<Class>[] tv; tv = c.getTypeParameters(); //warning: unchecked conversion for (int i = 0; i < tv.length; i++) { x = tName(tv[i].getName(), null); //例如 E,K,V... if (i == 0) //第一個 System.out.print("<" + x); else //非第一個 System.out.print("," + x); if (i == tv.length-1) //最後一個 System.out.println(">"); } public class LinkedList<E> public abstract interface Map<K,V>
Reflection 取得 Super Class Class getSuperClass() 4 public class LinkedList<E> extends AbstractSequentialList<E> Class supClass; supClass = c.getSuperclass(); if (supClass != null) //如果有super class System.out.print(" extends" + tName(supClass.getName(),classRef)); public class LinkedList<E> extends AbstractSequentialList, implements List, Queue, Cloneable, Serializable, {
Reflection 取得 Implemented Interfaces public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Queue<E>, Cloneable, java.io.Serializable Class[] getInterfaces() 5 public class LinkedList<E> extends AbstractSequentialList, implements List, Queue, Cloneable, Serializable, { Class cc[]; Class ctmp; //找出所有被實現的interfaces cc = c.getInterfaces(); if (cc.length != 0) System.out.print(", \r\n" + " implements "); for (Class cite : cc) System.out.print(tName(cite.getName(), null)+", "); JDK1.5, for loop 新語法
Reflection 取得 DeclaredClasses (inner classes)和 DeclaringClass (outer class) Class[] getDeclaredClasses() Class getDeclaringClass() public class LinkedList<E> ... { private static class Entry<E> { … } 6 class name: LinkedList$Entry LinkedList$Entry LinkedList$ListItr Map$Entry cc = c.getDeclaredClasses(); //找出inner classes for (Class cite : cc) System.out.println(tName(cite.getName(), null)); ctmp = c.getDeclaringClass(); //找出outer classes if (ctmp != null) System.out.println(ctmp.getName());
Reflection 取得 Declared Constructors (1) public class LinkedList<E> ... { public LinkedList() { … } public LinkedList(Collection<? extends E> c) { … } 7 Consructor[] getDeclaredConstructors()
Reflection 取得 Declared Constructors (2) public java.util.LinkedList(Collection) G: public java.util.LinkedList(java.util.Collection<? extends E>) public java.util.LinkedList() G: public java.util.LinkedList() Constructor cn[]; cn = c.getDeclaredConstructors(); for (int i = 0; i < cn.length; i++) { int md = cn[i].getModifiers(); System.out.print(" " + Modifier.toString(md) + " " + cn[i].getName()); Class cx[] = cn[i].getParameterTypes(); System.out.print("("); for (int j = 0; j < cx.length; j++) { System.out.print(tName(cx[j].getName(), null)); if (j < (cx.length - 1)) System.out.print(", "); } System.out.print(")"); System.out.println("G: " + cn[i].toGenericString()); }
Reflection 取得 Declared Methods (1) public class LinkedList<E> ... { public E getFirst() { … } public E getLast() { … } Method[] getDeclaredMethods() 8
Reflection 取得 Declared Methods (2) public Object get(int) G: public E java.util.LinkedList.get(int) public int size() G: public int java.util.LinkedList.size() Method mm[]; mm = c.getDeclaredMethods(); for (int i = 0; i < mm.length; i++) { int md = mm[i].getModifiers(); System.out.print(" "+Modifier.toString(md)+" "+ tName(mm[i].getReturnType().getName(), null)+" "+ mm[i].getName()); Class cx[] = mm[i].getParameterTypes(); System.out.print("("); for (int j = 0; j < cx.length; j++) { System.out.print(tName(cx[j].getName(), null)); if (j < (cx.length - 1)) System.out.print(", "); } System.out.print(")"); System.out.println("G: " + mm[i].toGenericString()); }