[RESOLVIDO] Swing funcionamento de Listeners

Bom dia.
Recentemente trabalhando com gráficos no JFreeChart eu precisava de alguma forma mudar em tempo de execução a cor das séries do gráfico, então criei uma aplicação pra isso e coloquei ela no Popup Menu do ChartPanel.
Ela funciona perfeitamente como eu queria, ela exibe para cada série um JLabel com o nome, um JLabel com a cor atual da Série, e um JButton que abre um ColorChooser e retorna a cor desejada. Esta cor é colocada no JLabel de cor da série, também no Renderer da série e é atualizado o gráfico para mostrar a nova cor.
Porém, para funcionar de forma dinâmica de acordo com o número de séries eu faço a criação dos components e a adição do Listener do JButton em um laço de repetição, iterando sobre o número de ítens do DataSet. Segue abaixo o código:

for (int i = 0; i < dataset.getRowCount(); i++) {

		int index = i;

		JLabel lblNome = new JLabel(dataset.getRowKey(i).toString());
		panel.add(lblNome);

		JLabel lblCor = new JLabel();
		lblCor.setOpaque(true);
		lblCor.setBackground((Color) renderer.getSeriesPaint(i));
		lblCor.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		panel.add(lblCor, "width 50!, height 20!");

		JButton button = new JButton("Mudar Cor");
		panel.add(button);
		button.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				Color color = JColorChooser.showDialog(null, "Selecione a Cor Desejada", lblCor.getBackground());

				if (color != null) {
					setColor(lblCor, color, renderer, index);
					chart.fireChartChanged();
				}
			}
		});
	}
private static void setColor(JLabel label, Color color, LineAndShapeRenderer renderer, int index) {

label.setBackground(color);
renderer.setSeriesPaint(index, color);
}

Ele funciona corretamente mas minha dúvida é:
Como o actionPerformed() sabe quais os componentes à serem passados para o setColor() de acordo com a série, mesmo sendo chamado após toda a execução e fora do laço de repetição?

vamos la

vc fez um loop em N elementos.

em casa passo, vc criou objetos e definiu o comportamento deles, no caso via eventos.

um botão vai receber um ActionListener - que é uma Interface. mas vc vez fez new Interface() { ... }

vc criou uma classe anonima, e definiu o comportamento dela ao definir um metodo actionPerformed

nesse caso, o bloco de codigo onde vc define o comportamento desse metodo pode ver as variaveis do bloco lexico exterior. Vc poderia criar uma classe que implementa essa interface e guarda todas essas variaveis como atributos, então chamar o construtor e pimba. mas isso pode ser tedioso: é mais coisa pra escrever.

a regra, salvo engano, é: o bloco do metodo da classe anonima enxerga as variaveis do bloco externo. Desde que vc não altere as variaveis, é sussa:

	public static void main (String[] args) throws java.lang.Exception
	{
		int i = 10;
		ActionListener a = new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				// i++; // não pode fazer isso!
				System.out.println("DURANTE i=" +i);	
			}
		};
		System.out.println("ANTES i=" +i);	
		a.actionPerformed(null);
		System.out.println("DEPOIS i=" +i);	
	}

vc pode ler, no seu caso, o valor de index, pois o escopo de index é um laço de execução, mas vc não pode fazer, por exemplo, index++ pois isso significaria fazer index = ...

porque? pois as ultimas versões de java exigem que as variaveis sejam final dentro desse bloco de codigo, ou effectively final, onde vc le o conteudo mas vc não altera.

mas ai vc vai pensar: “ué mas eu estou alterando a variavel label ao setar um valor…”

sim vc altera o estado interno do objeto mas vc não alterou a variavel para apontar para outro objeto.

a sintaxe de chamar um metodo é util pois os objetos são passados como referencia e o metodo tem acesso ao estado interno do mesmo. vc trabalha com ponteiros mas não esta explicito.

é totalmente diferente vc ter uma variavel inteira i e fazer i++ do que fazer uma variavel do tipo Contador onde esta tem um atributo i e via metodo vc incrementa o mesmo.

dito isso, o bloco de codigo desse metodo definido nesse momento consegue acessar as referencias para o bloco lexico exterior, mas não consegue redefinir as variaveis ( mas le os valores ou invoca metodos ).

se vc quer saber mais, use o aplicativo javap para inspecionar o bytecode gerado.

Olá @peczenyj, entendi! muito obrigado pela resposta!

Se vc ja teve contato com Perl, Python ou Ruby fica mais simples de entender pois o uso de lambdas / funções anonimas é mais frequente. Java demorou pra adicionar algum tipo de lambda nas ultimas versões ( como no caso das interfaces funcionais ) por diversas razões, então esses conceitos não são muito explorados na literatura, principalmente nos livros mais antigos.

Quando vc lida com Threads, ou Swing, vc tem mais oportunidades para praticar, mas perceba que é apenas uma questão de estilo: tudo o que vc faz com uma Classe Anonima, na pratica, vc consegue fazer com uma classe bem definida, com contrutor e metodos. Só que se vc criar classe pra todos os listeners, runnables, etc, vc pode acabar com diversas classes sem necessidade. Por outro lado é mais dificil IMHO de testar codigo que possui classes anonimas pois o conceito de cobertura de codigo fica mais subjetivo.

Conforme vc aprende a programar, vc vai aprendendo a testar o seu codigo via testes unitarios, e pode descobrir que algumas formas rapidas são um parto para testar. Se vc tem uma parte nobre da sua logica de negocios em uma parte do codigo q é dificil de testar, isso pode significar um bug no futuro.

é uma questão de bom senso e experiencia

Entendi, muito obrigado pela informação!