ClassLoader是什么
ClassLoader中文類加載器,java編寫出來的是.java文件,然后編譯成.class文件,而ClassLoader就是把class文件加載到jvm內存中;但jvm啟動時,通過不同的類加載器,動態的加載class文件;java比較重要的三類加載器Bootstrap ClassLoader、 Extention ClassLoader、Appclass Loader。
Java中的類加載器
Bootstrap ClassLoader、 Extention ClassLoader、Appclass Loader這三個類加載器分別負責加載不同路徑下的文件
Bootstrap CLassloder
它是最頂級的類加載,主要加載載核心類庫,其路徑為%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等,而這個路徑是可以通過jvm啟動參數-Xbootclasspath來設置
Extention ClassLoader
擴展的類加載器,加載目錄%JRE_HOME%\lib\ext目錄下的jar包和class文件。
AppClassLoader
當前應用的classpath的所有類
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package sun.misc; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.security.AccessControlContext; import java.security.AccessController; import java.security.CodeSource; import java.security.PermissionCollection; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.security.cert.Certificate; import java.util.HashSet; import java.util.StringTokenizer; import java.util.Vector; import sun.net.www.ParseUtil; public class Launcher { private static URLStreamHandlerFactory factory = new Launcher.Factory(); private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader; private static URLStreamHandler fileHandler; public static Launcher getLauncher() { return launcher; } public Launcher() { Launcher.ExtClassLoader var1; try { var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader"); } try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader"); } Thread.currentThread().setContextClassLoader(this.loader); String var2 = System.getProperty("java.security.manager"); if (var2 != null) { SecurityManager var3 = null; if (!"".equals(var2) && !"default".equals(var2)) { try { var3 = (SecurityManager)this.loader.loadClass(var2).newInstance(); } catch (IllegalAccessException var5) { ; } catch (InstantiationException var6) { ; } catch (ClassNotFoundException var7) { ; } catch (ClassCastException var8) { ; } } else { var3 = new SecurityManager(); } if (var3 == null) { throw new InternalError("Could not create SecurityManager: " + var2); } System.setSecurityManager(var3); } } public ClassLoader getClassLoader() { return this.loader; } public static URLClassPath getBootstrapClassPath() { return Launcher.BootClassPathHolder.bcp; }
……………………………… }
從以上源碼可以看到
1. 沒有看到 Bootstrap ClassLoader,只有bootClassPath,這個應該就是頂級類加載器的加載路徑;
2.Launcher 先初始化了Extention ClassLoader ,然后把傳入它實例再初始化AppClassLoader。
static class ExtClassLoader extends URLClassLoader { public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
//獲取加載的文件,進入getExtDirs可以看到加載的文件路徑String var0 = System.getProperty("java.ext.dirs")
final File[] var0 = getExtDirs(); try { return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() { public Launcher.ExtClassLoader run() throws IOException { int var1 = var0.length; for(int var2 = 0; var2 < var1; ++var2) { MetaIndex.registerDirectory(var0[var2]); } //創建類加載 return new Launcher.ExtClassLoader(var0); } }); } catch (PrivilegedActionException var2) { throw (IOException)var2.getException(); } } void addExtURL(URL var1) { super.addURL(var1); } public ExtClassLoader(File[] var1) throws IOException {
//調用父類方法創建,留意到第二個參數為null,這個參數是父加載器,預測ExtClassLoader是沒有父加載器的
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
} private static File[] getExtDirs() { String var0 = System.getProperty("java.ext.dirs"); File[] var1; if (var0 != null) { StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator); int var3 = var2.countTokens(); var1 = new File[var3]; for(int var4 = 0; var4 < var3; ++var4) { var1[var4] = new File(var2.nextToken()); } } else { var1 = new File[0]; } return var1; } private static URL[] getExtURLs(File[] var0) throws IOException { Vector var1 = new Vector(); for(int var2 = 0; var2 < var0.length; ++var2) { String[] var3 = var0[var2].list(); if (var3 != null) { for(int var4 = 0; var4 < var3.length; ++var4) { if (!var3[var4].equals("meta-index")) { File var5 = new File(var0[var2], var3[var4]); var1.add(Launcher.getFileURL(var5)); } } } } URL[] var6 = new URL[var1.size()]; var1.copyInto(var6); return var6; } public String findLibrary(String var1) { var1 = System.mapLibraryName(var1); URL[] var2 = super.getURLs(); File var3 = null; for(int var4 = 0; var4 < var2.length; ++var4) { File var5 = (new File(var2[var4].getPath())).getParentFile(); if (var5 != null && !var5.equals(var3)) { String var6 = VM.getSavedProperty("os.arch"); File var7; if (var6 != null) { var7 = new File(new File(var5, var6), var1); if (var7.exists()) { return var7.getAbsolutePath(); } } var7 = new File(var5, var1); if (var7.exists()) { return var7.getAbsolutePath(); } } var3 = var5; } return null; } private static AccessControlContext getContext(File[] var0) throws IOException { PathPermissions var1 = new PathPermissions(var0); ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1); AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2}); return var3; } static { ClassLoader.registerAsParallelCapable(); } }
看看ExtenTionClassLoader初始化做了什么
從上面可以看到ExtClassLoader extends URLClassLoader,初始化ExtClassLoader先讀取java.ext.dirs路徑(D:\java7\jre\lib\ext;C:\Windows\Sun\Java\lib\ext)下的文件,然后調用URLClassLoader.supper()-->
SecureClassLoader.supper()-->ClassLoader.supper()-->方法創建,因為傳入的parent是null,可以看到ExtClassLoader 沒有父加載器的;簡單說就是加載java.ext.dirs的文件創建了個無父加載器的類加載器。
public static void main(String[] args) { System.out.println(System.getProperty("java.ext.dirs")); } /* D:\java7\jre\lib\ext;C:\Windows\Sun\Java\lib\ext */
/*
URLClassLoader extends SecureClassLoader implements Closeable
*/
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls, factory);
acc = AccessController.getContext();
}
/******************************************************************************/
/*
SecureClassLoader extends ClassLoader
*/
protected SecureClassLoader(ClassLoader parent) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
initialized = true;
}
/******************************************************************************/
/*
ClassLoader類
*/
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
看看APPClassLoader初始化做了什么
static class AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { final String var1 = System.getProperty("java.class.path"); final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1); return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() { public Launcher.AppClassLoader run() { URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2); return new Launcher.AppClassLoader(var1x, var0); } }); } AppClassLoader(URL[] var1, ClassLoader var2) { super(var1, var2, Launcher.factory); } public Class loadClass(String var1, boolean var2) throws ClassNotFoundException { int var3 = var1.lastIndexOf(46); if (var3 != -1) { SecurityManager var4 = System.getSecurityManager(); if (var4 != null) { var4.checkPackageAccess(var1.substring(0, var3)); } } return super.loadClass(var1, var2); } protected PermissionCollection getPermissions(CodeSource var1) { PermissionCollection var2 = super.getPermissions(var1); var2.add(new RuntimePermission("exitVM")); return var2; } private void appendToClassPathForInstrumentation(String var1) { assert Thread.holdsLock(this); super.addURL(Launcher.getFileURL(new File(var1))); } private static AccessControlContext getContext(File[] var0) throws MalformedURLException { PathPermissions var1 = new PathPermissions(var0); ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1); AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2}); return var3; } static { ClassLoader.registerAsParallelCapable(); } }
從上面看APPClassLoader也是繼承URLClassLoader,與ExtClassLoader不同的是,
1.讀取的路徑是java.class.path
2.把ExtClassLoader的實例作為APPClassLoader的父加載器,也就是說APPClassLoader的父類加載器是ExtClassLoader
3.加載順序Bootstrap ClassLoader 到 ExtClassLoader 到 APPClassLoader
父加載器
首先我們先要明確一點是父加載器並不是父類,我們可以通過下面的代碼看到
public class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] repositories) { super(repositories); } public MyClassLoader(URL[] repositories, ClassLoader parent) { super(repositories, parent); } } public static void main(String[] args) { ClassLoader cl = MyClassLoader.class.getClassLoader(); System.out.println("ClassLoader is:"+cl.toString()); System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString()); // System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString()); } ClassLoader is:sun.misc.Launcher$AppClassLoader@31fc6b2 ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@1b2dd1b8
1.MyClassLoader 繼承URLClassLoader,但是可以到店MyClassLoader的父加載器是AppClassLoader
2.AppClassLoader 的父加載器是ExtClassLoader,這個在上面分析初始化的APPClassLoader的時候,就可以看到傳入ExtClassLoader的實例作為它的加載器,那么ExtClassLoader的父加載器上面初始時是null,我們可以驗證一下
public static void main(String[] args) { ClassLoader cl = MyClassLoader.class.getClassLoader(); // System.out.println("ClassLoader is:"+cl.toString()); // System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString()); System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString()); } Exception in thread "main" java.lang.NullPointerException at com.suntek.vdm.gw.util.NotifyHandler.main(NotifyHandler.java:373)
從上面的代碼可以看到,我們的上面是對的ExtClassLoader的父加載器的確是null.
3.那么其他的沒有指定父加載器的ClassLoader的父加載器是什么呢?從上面的代碼我們可以看到MyClassLoader的的是APPClassLoader,那么這是為什么呢?難道沒有指定父加載器的ClassLoader的默認是APP
/* 這ClassLoader是沒有指定父加載器創建方法 */ protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } //這里我們可以看到getSytemClassLoader()這個方法獲取父加載器 private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet<ProtectionDomain>()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; } } //看到return的是scl,而scl賦值是initSystemClassLoader()方法 public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; } //真相就在這里了,就是Launcher的getClassLoader(),而Luancher初始化的前面有看過了,也可以往下看 private static synchronized void initSystemClassLoader() { if (!sclSet) { if (scl != null) throw new IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null) { Throwable oops = null; scl = l.getClassLoader(); try { scl = AccessController.doPrivileged( new SystemClassLoaderAction(scl)); } catch (PrivilegedActionException pae) { oops = pae.getCause(); if (oops instanceof InvocationTargetException) { oops = oops.getCause(); } } if (oops != null) { if (oops instanceof Error) { throw (Error) oops; } else { // wrap the exception throw new Error(oops); } } } sclSet = true; } } //這個是Launcher對的getClassLoader,然后在看看下面的圖,明白了,就是APPClassLoader public ClassLoader getClassLoader() { return this.loader; }
一層層往下最終APPClassLoader作為默認的ClassLoader;
也就是沒有指定的父加載器的默認為是APPClassLoader;
雙親委托
有了父加載器的概念之后,我們就可以開始雙親委托這個玩意了!這部分,發現個哥們寫得不錯 https://blog.csdn.net/briblue/article/details/54973413,於是就復制了!
一個類加載器查找class和resource時,是通過“委托模式”進行的,它首先判斷這個class是不是已經加載成功,如果沒有的話它並不是自己進行查找,而是先通過父加載器,然后遞歸下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果沒有找到,則一級一級返回,最后到達自身去查找這些對象。這種機制就叫做雙親委托。
整個流程可以如下圖所示:
大家可以看到2根箭頭,藍色的代表類加載器向上委托的方向,如果當前的類加載器沒有查詢到這個class對象已經加載就請求父加載器(不一定是父類)進行操作,然后以此類推。直到Bootstrap ClassLoader。如果Bootstrap ClassLoader也沒有加載過此class實例,那么它就會從它指定的路徑中去查找,如果查找成功則返回,如果沒有查找成功則交給子類加載器,也就是ExtClassLoader,這樣類似操作直到終點,也就是我上圖中的紅色箭頭示例。
用序列描述一下:
1. 一個AppClassLoader查找資源時,先看看緩存是否有,緩存有從緩存中獲取,否則委托給父加載器。
2. 遞歸,重復第1部的操作。
3. 如果ExtClassLoader也沒有加載過,則由Bootstrap ClassLoader出面,它首先查找緩存,如果沒有找到的話,就去找自己的規定的路徑下,也就是sun.mic.boot.class
下面的路徑。找到就返回,沒有找到,讓子加載器自己去找。
4. Bootstrap ClassLoader如果沒有查找成功,則ExtClassLoader自己在java.ext.dirs
路徑中去查找,查找成功就返回,查找不成功,再向下讓子加載器找。
5. ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path
路徑下查找。找到就返回。如果沒有找到就讓子類找,如果沒有子類會怎么樣?拋出各種異常。
上面的序列,詳細說明了雙親委托的加載流程。我們可以發現委托是從下向上,然后具體查找過程卻是自上至下。
這里要說一下ExtClassLoader 這個是沒有父加載器,就通過BootStrapClassLoader這個來查找的,下面這個代碼的ClassLoader這個類的方法,
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded
// 首先到緩存尋找
Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) {
//如果父加強是空,往上找 c = parent.loadClass(name, false); } else {
//否則就通過BootStrapClassLoader找,也就應該是ExtClassLoader c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
自定義類加載器
了解我們可以自己寫一個簡單的加載類
public class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] repositories) { super(repositories); } public MyClassLoader(URL[] repositories, ClassLoader parent) { super(repositories, parent); } } public final class MyClassLoaderFactory { public static ClassLoader createClassLoader(File unpacked[], final ClassLoader parent) throws Exception { Set<URL> set = new LinkedHashSet<URL>(); if (unpacked != null) { for (int i = 0; i < unpacked.length; i++) { File file = unpacked[i]; if (!file.exists() || !file.canRead()){ continue; } file = new File(file.getCanonicalPath() + File.separator); URL url = file.toURI().toURL(); set.add(url); } } final URL[] array = set.toArray(new URL[set.size()]); return AccessController.doPrivileged(new PrivilegedAction<MyClassLoader>() { @Override public MyClassLoader run() { if (parent == null){ return new MyClassLoader(array); }else { return new MyClassLoader(array, parent); } } }); } }
private static void startWithClassloader(String path) throws Exception { String classPath= path; File[] packed = new File[] { new File(path) }; ClassLoader catalinaLoader = MyClassLoaderFactory.createClassLoader(null, packed, null);
Thread.currentThread().setContextClassLoader(catalinaLoader); Class<?> startupClass = catalinaLoader.loadClass(MAIN_CLASS); Object startupInstance = startupClass.newInstance(); Method method = startupInstance.getClass().getMethod("start"); method.invoke(startupInstance); }
一步一步來吧,首先自定義一個MyClassLoader類繼承了URlClassLoader,再對比一下AppClassLoader 的創建,可以說基本一樣;
a.獲取路徑的File[] 文件;
b.然后就調用了AccessController 的native <T> T doPrivileged(PrivilegedAction<T> action),而關於這個方法的說法,網友們是這么說的:一個調用者在調用doPrivileged方法時,可被標識為 "特權"。在做訪問控制決策時,如果checkPermission方法遇到一個通過doPrivileged調用而被表示為 "特權"的調用者,並且沒有上下文自變量,checkPermission方法則將終止檢查。如果那個調用者的域具有特定的許可,則不做進一步檢查,checkPermission安靜地返回,表示那個訪問請求是被允許的;如果那個域沒有特定的許可,則象通常一樣,一個異常被拋出
c.調用福利的supper方法創建
Thread.currentThread().setContextClassLoader(catalinaLoader) 這個句有什么用??
a.在java的應用中,每個線程都有一個contextClassLoad,而充init()方法可以看到,默認是父線程的上下文類加載器,當月我們也可以通過Thread.currentThread().setContextClassLoader(catalinaLoader)來設置;
b.這個上下文的加載器的作用簡單的說,就是當這個線程加載某些類時,通過這加載器進行加載;
c.網友說法:
每個運行中的線程都有一個成員contextClassLoader,用來在運行時動態地載入其它類
系統默認的contextClassLoader是systemClassLoader,所以一般而言java程序在執行時可以使用JVM自帶的類、$JAVA_HOME/jre/lib/ext/中的類和$CLASSPATH/中的類
可以使用Thread.currentThread().setContextClassLoader(...);更改當前線程的contextClassLoader,來改變其載入類的行為
ClassLoader被組織成樹形,一般的工作原理是:
1) 線程需要用到某個類,於是contextClassLoader被請求來載入該類
2) contextClassLoader請求它的父ClassLoader來完成該載入請求
3) 如果父ClassLoader無法載入類,則contextClassLoader試圖自己來載入
注意:WebApp?ClassLoader的工作原理和上述有少許不同:
它先試圖自己載入類(在ContextBase?/WEB-INF/...中載入類),如果無法載入,再請求父ClassLoader完成
public class Thread implements Runnable { /* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); } private char name[]; private int priority; private Thread threadQ; private long eetop; /* Whether or not to single_step this thread. */ private boolean single_step; /* Whether or not the thread is a daemon thread. */ private boolean daemon = false; /* JVM state */ private boolean stillborn = false; /* What will be run. */ private Runnable target; /* The group of this thread */ private ThreadGroup group; /* The context ClassLoader for this thread */ private ClassLoader contextClassLoader;
……………… }
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name.toCharArray(); Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
最后通過反射把程序運行起來
一個題目
/** * a.加載->校驗->准備->初始化->使用->卸載(准備和初始化) * b.父類靜態->本身靜態->父類變量和代碼塊->父類構造器->本身變量和代碼塊->本身構造器 * c.靜態只會加載一次,靜態變量一開始會在准備階段賦值默認值,如st = null, flag = false; * c.而st = new StaticTest()這步驟被鑲嵌到static StaticTest st = new StaticTest(); * d.開始執行new StaticTest(),因為靜態變量都已經初始化,因此進入:本身變量和代碼塊->本身構造器 * e.結束new StaticTest(),進入下一個靜態變量初始化System.out.println("2");再下一個static boolean flag = true; * f.執行方法staticFunction(); */ public class StaticTest { public static void main(String[] args) { staticFunction(); } { System.out.println("1"); } static StaticTest st = new StaticTest(); static { System.out.println("2"); } StaticTest() { System.out.println("3"); double temp = 3*0.1; if (temp == a){ System.out.println(a+","+flag); }else { System.out.println(a+","+flag); } } private static void staticFunction(){ if (flag){ System.out.println("4"); }else { System.out.println("5"); } } double a=0.3; static boolean flag = true; }
1
3
0.3,false
2
4