如何将2个.m文件带有2个.xibs中的音乐和SFX的开/关按钮与单例类的音频连接起来

问题描述

| 我创建了一个singleton类,用于处理我正在创建的益智游戏的背景音乐和SFX。该类在游戏中运行良好。音乐会像SFX一样开始和停止,但是我在将它们连接到主菜单.xib(main menu.m)的选项菜单中的ON / OFF按钮时遇到了问题。 我无法从主menu.xib打开和关闭它们,也无法获取游戏内部的按钮(在game.xib(game.m)上)以对主菜单中的选择做出反应。反之亦然。无法获取主菜单中的按钮来了解游戏中音乐或SFX是否已关闭。 有办法以某种方式将它们连接起来吗? 或其他建议? 附言我也从sharedSoundManager部分得到警告消息。警告说“可能不响应”。我有什么想念的吗? (我从viewDidLoad调用了sharedSoundManager,例如:sharedSoundManager = [SingletonSoundManager sharedSoundManager];)
-(IBAction)SFX{

//ChangeSFX = [[MainMenu alloc] initWithNibName:@\"MainMenu\" bundle: nil];

if(sfx2.hidden){
    NSLog(@\"SFX Stops\");
    sfx1.hidden = YES;
    sfx2.hidden = NO;
    [sharedSoundManager stopSoundWithKey:@\"movingbricks\"];

}
else if(sfx1.hidden){
    NSLog(@\"SFX Starts\");
    sfx1.hidden = NO;
    sfx2.hidden = YES;
    [sharedSoundManager playSoundWithKey:@\"movingbricks\" gain:0.13f pitch:1.0f shouldLoop:NO];

}
}
-(IBAction)MUSIC{

if(music2.hidden){
    NSLog(@\"Music Stops\");
    music1.hidden = YES;
    music2.hidden = NO;
    [sharedSoundManager pausePlayingMusic];
}
else if(music1.hidden){
    NSLog(@\"Music Starts\");
    music1.hidden = NO;
    music2.hidden = YES;
    [sharedSoundManager resumePlayingMusic];
}
} 更新____________________________________________________________ 我的单例声音管理器课程
#import \"SingletonSoundManager.h\"

@interface SingletonSoundManager (Private)
- (BOOL)initOpenAL;
- (NSUInteger) nextAvailableSource;
- (AudioFileID) openAudioFile:(NSString*)theFilePath;
- (UInt32) audioFileSize:(AudioFileID)fileDescriptor;
@end


@implementation SingletonSoundManager

// This var will hold our Singleton class instance that will be handed to anyone who asks for it
static SingletonSoundManager *sharedSoundManager = nil;

// Class method which provides access to the sharedSoundManager var.
+ (SingletonSoundManager *)sharedSoundManager {

    // synchronized is used to lock the object and handle multiple threads accessing this method at
    // the same time
    @synchronized(self) {

        // If the sharedSoundManager var is nil then we need to allocate it.
        if(sharedSoundManager == nil) {
            // Allocate and initialize an instance of this class
            [[self alloc] init];
        }
    }

    // Return the sharedSoundManager
    return sharedSoundManager;
}


/* This is called when you alloc an object.  To protect against instances of this class being
 allocated outside of the sharedSoundManager method,this method checks to make sure 
 that the sharedSoundManager is nil before allocating and initializing it.  If it is not
 nil then nil is returned and the instance would need to be obtained through the sharedSoundManager method
 */
+ (id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedSoundManager == nil) {
            sharedSoundManager = [super allocWithZone:zone];
            return sharedSoundManager;  // assignment and return on first allocation
        }
    }
    return nil; //on subsequent allocation attempts return nil
}


- (id)copyWithZone:(NSZone *)zone {
    return self;
}


/* 
 When the init is called from the sharedSoundManager class method,this method will get called.
 This is where we then initialize the arrays and dictionaries which will store the OpenAL buffers
 create as well as the soundLibrary dictionary
 */
- (id)init {
    if(self = [super init]) {
        soundSources = [[NSMutableArray alloc] init];
        soundLibrary = [[NSMutableDictionary alloc] init];
        musicLibrary = [[NSMutableDictionary alloc] init];

        // Set the default volume for music
        backgroundMusicVolume = 1.0f;

        // Set up the OpenAL
        BOOL result = [self initOpenAL];
        if(!result) return nil;
        return self;
    }
    [self release];
    return nil;
}


/*
 This method is used to initialize OpenAL.  It gets the default device,creates a new context 
 to be used and then preloads the define # sources.  This preloading means we wil be able to play up to
 (max 32) different sounds at the same time
 */
- (BOOL) initOpenAL {
    // Get the device we are going to use for sound.  Using NULL gets the default device
    device = alcOpenDevice(NULL);

    // If a device has been found we then need to create a context,make it current and then
    // preload the OpenAL Sources
    if(device) {
        // Use the device we have now got to create a context \"air\"
        context = alcCreateContext(device,NULL);
        // Make the context we have just created into the active context
        alcMakeContextCurrent(context);
        // Pre-create 32 sound sources which can be dynamically allocated to buffers (sounds)
        NSUInteger sourceID;
        for(int index = 0; index < kMaxSources; index++) {
            // Generate an OpenAL source
            alGenSources(1,&sourceID);
            // Add the generated sourceID to our array of sound sources
            [soundSources addObject:[NSNumber numberWithUnsignedInt:sourceID]];
        }

        // Return YES as we have successfully initialized OpenAL
        return YES;
    }
    // Something went wrong so return NO
    return NO;
}

- (void) shutdownSoundManager {
    @synchronized(self) {
        if(sharedSoundManager != nil) {
            [self dealloc];
        }
    }
}


- (void) loadSoundWithKey:(NSString*)theSoundKey fileName:(NSString*)theFileName fileExt:(NSString*)theFileExt frequency:(NSUInteger)theFrequency {

    // Get the full path of the audio file
    NSString *filePath = [[NSBundle mainBundle] pathForResource:theFileName ofType:theFileExt];

    // Now we need to open the file
    AudioFileID fileID = [self openAudioFile:filePath];

    // Find out how big the actual audio data is
    UInt32 fileSize = [self audioFileSize:fileID];

    // Create a location for the audio data to live temporarily
    unsigned char *outData = malloc(fileSize);

    // Load the byte data from the file into the data buffer
    OSStatus result = noErr;
    result = AudioFileReadBytes(fileID,FALSE,&fileSize,outData);
    AudioFileClose(fileID);

    if(result != 0) {
        NSLog(@\"ERROR SoundEngine: Cannot load sound: %@\",theFileName);
        return;
    }

    NSUInteger bufferID;

    // Generate a buffer within OpenAL for this sound
    alGenBuffers(1,&bufferID);

    // Place the audio data into the new buffer
    alBufferData(bufferID,AL_FORMAT_STEREO16,outData,fileSize,theFrequency);

    // Save the buffer to be used later
    [soundLibrary setObject:[NSNumber numberWithUnsignedInt:bufferID] forKey:theSoundKey];

    // Clean the buffer
    if(outData) {
        free(outData);
        outData = NULL;
    }
}


- (void) loadBackgroundMusicWithKey:(NSString*)theMusicKey fileName:(NSString*)theFileName fileExt:(NSString*)theFileExt {

    NSString *path = [[NSBundle mainBundle] pathForResource:theFileName ofType:theFileExt];
    [musicLibrary setObject:path forKey:theMusicKey];
}


/*
 Used to load an audiofile from the file path which is provided.
 */
- (AudioFileID) openAudioFile:(NSString*)theFilePath {

    AudioFileID outAFID;
    // Create an NSURL which will be used to load the file.  This is slightly easier
    // than using a CFURLRef
    NSURL *afUrl = [NSURL fileURLWithPath:theFilePath];

    // Open the audio file provided
    OSStatus result = AudioFileOpenURL((CFURLRef)afUrl,kAudioFileReadPermission,&outAFID);

    // If we get a result that is not 0 then something has gone wrong.  We report it and 
    // return the out audio file id
    if(result != 0) {
        NSLog(@\"ERROR SoundEngine: Cannot open file: %@\",theFilePath);
        return nil;
    }

    return outAFID;
}


/*
 This helper method returns the file size in bytes for a given AudioFileID
 */
- (UInt32) audioFileSize:(AudioFileID)fileDescriptor {
    UInt64 outDataSize = 0;
    UInt32 thePropSize = sizeof(UInt64);
    OSStatus result = AudioFileGetProperty(fileDescriptor,kAudioFilePropertyAudioDataByteCount,&thePropSize,&outDataSize);
    if(result != 0) NSLog(@\"ERROR: cannot file file size\");
    return (UInt32)outDataSize;
}


/*
 Plays the sound which matches the key provided.  The Gain,pitch and if the sound should loop can
 also be set from the method signature
 */
- (NSUInteger) playSoundWithKey:(NSString*)theSoundKey gain:(ALfloat)theGain pitch:(ALfloat)thePitch shouldLoop:(BOOL)theShouldLoop {

    ALenum err = alGetError(); // clear the error code

    // Find the buffer linked to the key which has been passed in
    NSNumber *numVal = [soundLibrary objectForKey:theSoundKey];
    if(numVal == nil) return 0;
    NSUInteger bufferID = [numVal unsignedIntValue];

    // Find an available source i.e. it is currently not playing anything
    NSUInteger sourceID = [self nextAvailableSource];

    // Make sure that the source is clean by resetting the buffer assigned to the source
    // to 0
    alSourcei(sourceID,AL_BUFFER,0);
    //Attach the buffer we have looked up to the source we have just found
    alSourcei(sourceID,bufferID);

    // Set the pitch and gain of the source
    alSourcef(sourceID,AL_PITCH,thePitch);
    alSourcef(sourceID,AL_GAIN,theGain);

    // Set the looping value
    if(theShouldLoop) {
        alSourcei(sourceID,AL_LOOPING,AL_TRUE);
    } else {
        alSourcei(sourceID,AL_FALSE);
    }

    // Check to see if there were any errors
    err = alGetError();
    if(err != 0) {
        NSLog(@\"ERROR SoundManager: %d\",err);
        return 0;
    }

    // Now play the sound
    alSourcePlay(sourceID);

    // Return the source ID so that loops can be stopped etc
    return sourceID;
}


- (void) stopSoundWithKey:(NSString*)theSoundKey {

    // Reset errors in OpenAL
    ALenum alError = alGetError();
    alError = AL_NO_ERROR;

    // Find the buffer which has been linked to the sound key provided
    NSNumber *numVal = [soundLibrary objectForKey:theSoundKey];

    // If the key is not found log it and finish
    if(numVal == nil) {
        NSLog(@\"WARNING - SoundManager: No sound with key \'%@\' was found so cannot be stopped\",theSoundKey);
        return;
    }

    // Get the buffer number from
    NSUInteger bufferID = [numVal unsignedIntValue];
    NSInteger bufferForSource;
    for(NSNumber *sourceID in soundSources) {

        NSUInteger currentSourceID = [sourceID unsignedIntValue];

        // Grab the buffer currently bound to this source
        alGetSourcei(currentSourceID,&bufferForSource);

        // If the buffer matches the buffer we want to stop then stop the source and unbind it from the buffer
        if(bufferForSource == bufferID) {
            alSourceStop(currentSourceID);
            alSourcei(currentSourceID,0);
        }
    } 

    // Check for any errors
    if((alError = alGetError()) != AL_NO_ERROR)
        NSLog(@\"ERROR - SoundManager: Could not stop sound with key \'%@\' got error %x\",theSoundKey,alError);

}


/*
 Play the background track which matches the key
 */
- (void) playMusicWithKey:(NSString*)theMusicKey timesToRepeat:(NSUInteger)theTimesToRepeat {

    NSError *error;

    NSString *path = [musicLibrary objectForKey:theMusicKey];

    if(!path) {
        NSLog(@\"ERROR SoundEngine: The music key \'%@\' could not be found\",theMusicKey);
        return;
    }

    // Initialize the AVAudioPlayer
    backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];

    // If the backgroundMusicPlayer object is nil then there was an error
    if(!backgroundMusicPlayer) {
        NSLog(@\"ERROR SoundManager: Could not play music for key \'%d\'\",error);
        return;
    }       

    // Set the number of times this music should repeat.  -1 means never stop until its asked to stop
    [backgroundMusicPlayer setNumberOfLoops:theTimesToRepeat];

    // Set the volume of the music
    [backgroundMusicPlayer setVolume:backgroundMusicVolume];

    // Play the music
    [backgroundMusicPlayer play];


}


/*
 Stop playing the currently playing music
 */
- (void) stopPlayingMusic{
    [backgroundMusicPlayer stop];
}

- (void) pausePlayingMusic{
    [backgroundMusicPlayer pause];
}

- (void) resumePlayingMusic{
    [backgroundMusicPlayer play];
}


/*
 Set the volume of the music which is between 0.0 and 1.0
 */
- (void) setBackgroundMusicVolume:(ALfloat)theVolume {

    // Set the volume iVar
    backgroundMusicVolume = theVolume;

    // Check to make sure that the audio player exists and if so set its volume
    if(backgroundMusicPlayer) {
        [backgroundMusicPlayer setVolume:backgroundMusicVolume];

    }
}


/* 
 Search through the max number of sources to find one which is not planning.  If one cannot
 be found that is not playing then the first one which is looping is stopped and used instead.
 If a source still cannot be found then the first source is stopped and used
 */
- (NSUInteger) nextAvailableSource {

    // Holder for the current state of the current source
    NSInteger sourceState;

    // Find a source which is not being used at the moment
    for(NSNumber *sourceNumber in soundSources) {
        alGetSourcei([sourceNumber unsignedIntValue],AL_SOURCE_STATE,&sourceState);
        // If this source is not playing then return it
        if(sourceState != AL_PLAYING) return [sourceNumber unsignedIntValue];
    }

    // If all the sources are being used we look for the first non looping source
    // and use the source associated with that
    NSInteger looping;
    for(NSNumber *sourceNumber in soundSources) {
        alGetSourcei([sourceNumber unsignedIntValue],&looping);
        if(!looping) {
            // We have found a none looping source so return this source and stop checking
            NSUInteger sourceID = [sourceNumber unsignedIntValue];
            alSourceStop(sourceID);
            return sourceID;
        }
    }

    // If there are no looping sources to be found then just use the first sounrce and use that
    NSUInteger sourceID = [[soundSources objectAtIndex:0] unsignedIntegerValue];
    alSourceStop(sourceID);
    return sourceID;
}


- (id)retain {
    return self;
}


- (unsigned)retainCount {
    return UINT_MAX;  //denotes an object that cannot be released
} 


- (void)release {
    //do nothing
}


- (id)autorelease {
    return self;
}

- (void)dealloc {
    // Loop through the OpenAL sources and delete them
    for(NSNumber *numVal in soundSources) {
        NSUInteger sourceID = [numVal unsignedIntValue];
        alDeleteSources(1,&sourceID);
    }

    // Loop through the OpenAL buffers and delete 
    NSEnumerator *enumerator = [soundLibrary keyEnumerator];
    id key;
    while ((key = [enumerator nextObject])) {
        NSNumber *bufferIDVal = [soundLibrary objectForKey:key];
        NSUInteger bufferID = [bufferIDVal unsignedIntValue];
        alDeleteBuffers(1,&bufferID);      
    }

    // Release the arrays and dictionaries we have been using
    [soundLibrary release];
    [soundSources release];
    [musicLibrary release];

    // Disable and then destroy the context
    alcMakeContextCurrent(NULL);
    alcDestroyContext(context);

    // Close the device
    alcCloseDevice(device);

    [super dealloc];
}

@end


//My SFX and Music methods from the game.m class



    -(IBAction)SFX{

    BOOL buttonState = [SingletonSoundManager isSFXOn]; //generated \"may not respond\" warnings. why?
           //BOOL buttonState = [SingletonSoundManager isSFXOn]:
    [SingletonSoundManager setSFXOn:!buttonState]; //generated \"may not respond\" warnings. why?



    if(sfx2.hidden){
        NSLog(@\"SFX Stops\");
        sfx1.hidden = buttonState;
        sfx2.hidden = !buttonState;
        [sharedSoundManager stopSoundWithKey:@\"movingbricks\"]; //generated \"may not respond\" warnings. why?

    }
    else if(sfx1.hidden){
        NSLog(@\"SFX Starts\");
        sfx1.hidden = buttonState;
        sfx2.hidden = !buttonState;
        [sharedSoundManager playSoundWithKey:@\"movingbricks\" gain:0.13f pitch:1.0f shouldLoop:NO]; //generated \"may not respond\" warnings. why?



    }
}

-(IBAction)MUSIC{
    BOOL buttonState = [SingletonSoundManager isMusicOn]; //generated \"may not respond\" warnings. why?
           //BOOL buttonState = [SingletonSoundManager resumePlayingMusic];
    [SingletonSoundManager setMusicOn:!buttonState]; //generated \"may not respond\" warnings. why?
          //[SingletonSoundManager pausePlayingMusic:!buttonState]; 


    if(music2.hidden){
        NSLog(@\"Music Stops\");
        music1.hidden = buttonState;
        music2.hidden = buttonState;
        [sharedSoundManager pausePlayingMusic]; //generated \"may not respond\" warnings. why?


    }
    else if(music1.hidden){
        NSLog(@\"Music Starts\");
        music1.hidden = buttonState;
        music2.hidden = !buttonState;
        [sharedSoundManager resumePlayingMusic]; //generated \"may not respond\" warnings. why?


    }
}

//My SFX and Music methods inside my mainmenu.m class

    //Turn on and off the SFX
-(IBAction)SFXswap{

    BOOL buttonState = [SingletonSoundManager isSFXOn];  //generated \"may not respond\" warnings. why?

    [SingletonSoundManager setSFXOn:!buttonState];  //generated \"may not respond\" warnings. why?



    if(sfx2.hidden){
        NSLog(@\"SFX Stops\");
        sfx1.hidden = buttonState;
        sfx2.hidden = buttonState;
        [sharedSoundManager stopSoundWithKey:@\"movingbricks\"]; //generated \"may not respond\" warnings. why?


    }
    else if(sfx1.hidden){
        NSLog(@\"SFX Starts\");
        sfx1.hidden = buttonState;
        sfx2.hidden = buttonState;
        [sharedSoundManager playSoundWithKey:@\"movingbricks\" gain:0.13f pitch:1.0f shouldLoop:NO]; //generated \"may not respond\" warnings. why?


    }
}

//Turn on and off the backgrund music
-(IBAction)MUSICswap{

    BOOL buttonState = [SingletonSoundManager isMusicOn];  //generated \"may not respond\" warnings. why?
           //BOOL buttonState = [SingletonSoundManager resumePlayingMusic];
    [SingletonSoundManager setMusicOn:!buttonState];  //generated \"may not respond\" warnings. why?
           //[SingletonSoundManager pausePlayingMusic:!buttonState]; 



    if(music2.hidden){
        NSLog(@\"Music Stops\");
        music1.hidden = buttonState;
        music2.hidden = !buttonState;
        [sharedSoundManager pausePlayingMusic]; //generated \"may not respond\" warnings. why?


    }
    else if(music1.hidden){
        NSLog(@\"Music Starts\");
        music1.hidden = buttonState;
        music2.hidden = !buttonState;
        [sharedSoundManager resumePlayingMusic]; //generated \"may not respond\" warnings. why?


    }
}


if this how you meant,Ravin? Please corret me if i´m wrong :)


------------------------------------------------------------------------------------------------------------NEW UPDATE
------------------------------------------------------------------------------------------------------------

    //In singletonSoundManager.h

BOOL isMusicOn; 

- (BOOL) isMusicOn; 
- (void) setMusicOn:(BOOL)boolValue; 


//In singletonSoundManager.m

- (void) playMusicWithKey:(NSString*)theMusicKey timesToRepeat:(NSUInteger)theTimesToRepeat {

    NSError *error;

    NSString *path = [musicLibrary objectForKey:theMusicKey];

    if(!path) {
        NSLog(@\"ERROR SoundEngine: The music key \'%@\' could not be found\",error);
        return;
    }        

    // Set the number of times this music should repeat.  -1 means never stop until its asked to stop
    [backgroundMusicPlayer setNumberOfLoops:theTimesToRepeat];

    // Set the volume of the music
    [backgroundMusicPlayer setVolume:backgroundMusicVolume];

    // Play the music
    [backgroundMusicPlayer play];

    isMusicOn = YES;
}

-(BOOL)isMusicOn 
{ 
    return isMusicOn; 
} 

-(void)setMusicOn:(BOOL)boolValue 
{ 
    isMusicOn = boolValue; 
} 


/*
 Stop playing the currently playing music
 */
- (void) stopPlayingMusic{
    [backgroundMusicPlayer stop];
}

- (void) pausePlayingMusic{
    [backgroundMusicPlayer pause];
    isMusicOn = NO;
}

- (void) resumePlayingMusic{
    [backgroundMusicPlayer play];
    isMusicOn = YES;
}


//In game.m 


-(IBAction)MUSIC{

    BOOL buttonstate = [SingletonSoundManager isMusicOn]; // \"SingletonSoundManager may not respond to isMusicOn\" 
                                                                                       //\" Initialization makes integer from pointer without a cast\"

    [SingletonSoundManager setMusicOn:!buttonstate]; //\"SingletonSoundManager may not respond to setMusicOn\"

    if(music2.hidden){
        NSLog(@\"Music Stops\");
        music1.hidden = buttonstate;
        music2.hidden = !buttonstate;

        [sharedSoundManager pausePlayingMusic]; //\"SingletonSoundManager may not respond to pausePlayingMusic\"

    }
    else if(music1.hidden){
        NSLog(@\"Music Starts\");
        music1.hidden = !buttonstate;
        music2.hidden = buttonstate;

        [sharedSoundManager resumePlayingMusic]; //\"SingletonSoundManager may not respond to resumePlayingMusic\"
    }
}


in mainmenu.m
-(IBAction)MUSICswap{

    BOOL buttonstate = [SingletonSoundManager isMusicOn]; // \"SingletonSoundManager may not respond to isMusicOn\" 
                                                                                       //\" Initialization makes integer from pointer without a cast\"

    [SingletonSoundManager setMusicOn:!buttonstate]; //\"SingletonSoundManager may not respond to setMusicOn\"

    if(music2.hidden){
        NSLog(@\"Music Stops\");
        //music1.hidden = YES;
        //music2.hidden = NO;
        music1.hidden = buttonstate;
        music2.hidden = !buttonstate;
        [sharedSoundManager pausePlayingMusic]; //\"SingletonSoundManager may not respond to pausePlayingMusic\"
    }
    else if(music1.hidden){
        NSLog(@\"Music Starts\");
        //music1.hidden = NO;
        //music2.hidden = YES;
        music1.hidden = !buttonstate;
        music2.hidden = buttonstate;
        [sharedSoundManager resumePlayingMusic]; //\"SingletonSoundManager may not respond to resumePlayingMusic\"
    }
}
    

解决方法

当您使用单例类时,请保持那里的flag(musicOn)可以处理按钮状态(使其成为一个属性,以便您可以设置和获取它)。如果使用的是UIButton,则可以按以下步骤进行操作。
-(IBAction)SFX{

//ChangeSFX = [[MainMenu alloc] initWithNibName:@\"MainMenu\" bundle: nil];
//get object of singletonClass for flag access
BOOL buttonState = [singletonClassObject isMusicOn];
[singletonClassObject setMusicOn:!buttonState];
if(sfx2.hidden){
    NSLog(@\"SFX Stops\");
    sfx1.hidden = buttonState;
    sfx2.hidden = !buttonState;
    [sharedSoundManager stopSoundWithKey:@\"movingbricks\"];

}
else if(sfx1.hidden){
    NSLog(@\"SFX Starts\");
    sfx1.hidden = !buttonState;
    sfx2.hidden = buttonState;
    [sharedSoundManager playSoundWithKey:@\"movingbricks\" gain:0.13f pitch:1.0f shouldLoop:NO];

}
请重新检查隐藏的属性设置,我不确定您希望如何使用它们。 最新更新 在您的单例课程的.h中 在变量声明中添加
BOOL isMusicOn;
在方法声明中添加
+(BOOL) isMusicOn;
+(void) setMusicOn:(BOOL)boolValue;
在单例课程的.m中
+(BOOL)isMusicOn
{
 return isMusicOn;
}

+(void)setMusicOn:(BOOL)boolValue
{
 isMusicOn = boolValue;
}
同样在初始化方法中,请根据需要设置
isMusicOn
变量
YES
NO
。 谢谢,     

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...