Não é possível se referir a uma variável não final dentro de uma classe interna definida em um método diferente


247

Editado: preciso alterar os valores de várias variáveis, pois elas são executadas várias vezes através de um timer. Eu preciso continuar atualizando os valores a cada iteração através do timer. Não consigo definir os valores para final, pois isso me impedirá de atualizar os valores, mas estou recebendo o erro descrito na pergunta inicial abaixo:

Eu escrevi anteriormente o que está abaixo:

Estou recebendo o erro "não pode se referir a uma variável não final dentro de uma classe interna definida em um método diferente".

Isso está acontecendo para o preço chamado duplo e o preço chamado priceObject. Você sabe por que eu recebo esse problema. Não entendo por que preciso ter uma declaração final. Além disso, se você puder ver o que estou tentando fazer, o que preciso fazer para solucionar esse problema.

public static void main(String args[]) {

    int period = 2000;
    int delay = 2000;

    double lastPrice = 0;
    Price priceObject = new Price();
    double price = 0;

    Timer timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {
        public void run() {
            price = priceObject.getNextPrice(lastPrice);
            System.out.println();
            lastPrice = price;
        }
    }, delay, period);
}

O que estou perguntando é: como obtenho uma variável em um timer que posso atualizar continuamente.
Ankur

1
@ Ankur: a resposta simples é "Não". Mas você pode obter o efeito desejado usando uma classe interna; veja a resposta de @ petercardona.
Stephen C

Respostas:


197

Java não suporta fechamentos verdadeiros , embora o uso de uma classe anônima como a que você está usando aqui ( new TimerTask() { ... }) pareça um tipo de fechamento.

editar - Veja os comentários abaixo - o seguinte não é uma explicação correta, como aponta o KeeperOfTheSoul.

É por isso que não funciona:

As variáveis lastPricee o preço são variáveis ​​locais no método main (). O objeto que você cria com a classe anônima pode durar até depois que o main()método retornar.

Quando o main()método retornar, variáveis ​​locais (como lastPricee price) serão limpas da pilha, para que não existam mais após main()retornos.

Mas o objeto de classe anônimo faz referência a essas variáveis. As coisas dariam terrivelmente errado se o objeto de classe anônimo tentasse acessar as variáveis ​​depois que elas fossem limpas.

Ao fazer lastPricee price final, eles não são mais variáveis, mas constantes. O compilador pode apenas substituir o uso de lastPricee pricena classe anônima pelos valores das constantes (em tempo de compilação, é claro), e você não terá mais o problema de acessar variáveis ​​inexistentes.

Outras linguagens de programação que suportam fechamentos o fazem tratando especialmente essas variáveis ​​- garantindo que elas não sejam destruídas quando o método terminar, para que o fechamento ainda possa acessar as variáveis.

@Ankur: Você poderia fazer isso:

public static void main(String args[]) {
    int period = 2000;
    int delay = 2000;

    Timer timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {
        // Variables as member variables instead of local variables in main()
        private double lastPrice = 0;
        private Price priceObject = new Price();
        private double price = 0;

        public void run() {
            price = priceObject.getNextPrice(lastPrice);
            System.out.println();
            lastPrice = price;
        }
    }, delay, period);      
}

34
Não é bem verdade, o Java gera capturas para as variáveis ​​em questão para capturar seus valores em tempo de execução, é apenas o que eles queriam evitar um efeito colateral estranho possível em .Net, onde você captura o valor no delegado, altera o value no método externo e agora o delegado vê o novo valor em stackoverflow.com/questions/271440/c-captured-variable-in-loop para o exemplo C # desse comportamento que o Java pretende evitar.
22630 Chris Chilvers

14
Esse não é um "efeito colateral estranho", é o comportamento normal que as pessoas esperariam - e que o Java não pode oferecer porque não gera capturas. Como solução alternativa, as variáveis ​​locais usadas em uma classe anônima devem ser finais.
22640 Michael Borgwardt

12
Jesper, você provavelmente deve editar as partes incorretas de suas respostas, em vez de apenas receber uma mensagem dizendo que o acima está incorreto.
James McMahon

19
De fato, o Java não suporta fechamentos. Os idiomas que suportam fechamentos o fazem armazenando todo o ambiente local (ou seja, o conjunto de variáveis ​​locais definidas no quadro de pilha atual) como um objeto de heap. O Java não tem suporte para isso (os designers de linguagem queriam implementá-lo, mas o tempo acabou), como solução alternativa, sempre que uma classe local é instanciada, os valores de quaisquer variáveis ​​locais a que se refere são copiados para o heap . No entanto, a JVM não pode manter os valores sincronizados com as variáveis ​​locais, e é por isso que elas precisam ser finais.
Taymon

64
Esta resposta é completamente confusa agora que não há ninguém chamado "KeeperOfTheSoul" que tenha comentado. A resposta deve ser revisada.
Adam Parkin

32

Para evitar efeitos colaterais estranhos com o fechamento de variáveis ​​java referenciadas por um delegado anônimo, deve ser marcado como final; portanto, para se referir lastPricee preço dentro da tarefa de timer, eles precisam ser marcados como finais.

Obviamente, isso não funcionará para você porque você deseja alterá-los; nesse caso, você deve encapsulá-los em uma classe.

public class Foo {
    private PriceObject priceObject;
    private double lastPrice;
    private double price;

    public Foo(PriceObject priceObject) {
        this.priceObject = priceObject;
    }

    public void tick() {
        price = priceObject.getNextPrice(lastPrice);
        lastPrice = price;
    }
}

Agora, basta criar um novo Foo como final e chamar .tick a partir do timer.

public static void main(String args[]){
    int period = 2000;
    int delay = 2000;

    Price priceObject = new Price();
    final Foo foo = new Foo(priceObject);

    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        public void run() {
            foo.tick();
        }
    }, delay, period);
}

1
ou você pode simplesmente liderar o Foo implementar Runnable ..?
vidstige

18

Você só pode acessar variáveis ​​finais da classe que contém quando usar uma classe anônima. Portanto, você precisa declarar as variáveis ​​que estão sendo usadas como finais (o que não é uma opção para você, pois você está alterando lastPrice e price ) ou não usa uma classe anônima.

Portanto, suas opções são criar uma classe interna real, na qual você pode passar as variáveis ​​e usá-las normalmente

ou:

Existe um hack rápido (e na minha opinião feio) para sua variável lastPrice e price , que é declará-la assim

final double lastPrice[1];
final double price[1];

e na sua classe anônima você pode definir o valor como este

price[0] = priceObject.getNextPrice(lastPrice[0]);
System.out.println();
lastPrice[0] = price[0];

14

Boas explicações sobre por que você não pode fazer o que está tentando fazer já foram fornecidas. Como solução, talvez considere:

public class foo
{
    static class priceInfo
    {
        public double lastPrice = 0;
        public double price = 0;
        public Price priceObject = new Price ();
    }

    public static void main ( String args[] )
    {

        int period = 2000;
        int delay = 2000;

        final priceInfo pi = new priceInfo ();
        Timer timer = new Timer ();

        timer.scheduleAtFixedRate ( new TimerTask ()
        {
            public void run ()
            {
                pi.price = pi.priceObject.getNextPrice ( pi.lastPrice );
                System.out.println ();
                pi.lastPrice = pi.price;

            }
        }, delay, period );
    }
}

Parece que provavelmente você poderia fazer um design melhor do que isso, mas a idéia é que você poderia agrupar as variáveis ​​atualizadas dentro de uma referência de classe que não muda.


11

Com classes anônimas, você está realmente declarando uma classe aninhada "sem nome". Para classes aninhadas, o compilador gera uma nova classe pública independente com um construtor que aceita todas as variáveis ​​usadas como argumentos (para classes aninhadas "nomeadas", essa é sempre uma instância da classe original / anexa). Isso é feito porque o ambiente de tempo de execução não tem noção de classes aninhadas, portanto, é necessário haver uma conversão (automática) de uma classe aninhada para uma classe independente.

Veja este código, por exemplo:

public class EnclosingClass {
    public void someMethod() {
        String shared = "hello"; 
        new Thread() {
            public void run() {
                // this is not valid, won't compile
                System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
            }
        }.start();

        // change the reference 'shared' points to, with a new value
        shared = "other hello"; 
        System.out.println(shared);
    }
}

Isso não vai funcionar, porque é isso que o compilador faz sob o capô:

public void someMethod() {
    String shared = "hello"; 
    new EnclosingClass$1(shared).start();

    // change the reference 'shared' points to, with a new value
    shared = "other hello"; 
    System.out.println(shared);
}

A classe anônima original é substituída por alguma classe autônoma que o compilador gera (o código não é exato, mas deve lhe dar uma boa idéia):

public class EnclosingClass$1 extends Thread {
    String shared;
    public EnclosingClass$1(String shared) {
        this.shared = shared;
    }

    public void run() {
        System.out.println(shared);
    }
}

Como você pode ver, a classe autônoma mantém uma referência ao objeto compartilhado; lembre-se de que tudo em java é transmitido por valor; portanto, mesmo que a variável de referência 'compartilhada' em EnclosingClass seja alterada, a instância para a qual ela aponta não será modificada , e todas as outras variáveis ​​de referência que apontam para ele (como a da classe anônima: Incluindo $ 1), não estarão cientes disso. Esse é o principal motivo pelo qual o compilador obriga a declarar essas variáveis ​​'compartilhadas' como finais, para que esse tipo de comportamento não seja inserido no código já em execução.

Agora, é isso que acontece quando você usa uma variável de instância dentro de uma classe anônima (é o que você deve fazer para resolver seu problema, mova sua lógica para um método de "instância" ou para um construtor de uma classe):

public class EnclosingClass {
    String shared = "hello";
    public void someMethod() {
        new Thread() {
            public void run() {
                System.out.println(shared); // this is perfectly valid
            }
        }.start();

        // change the reference 'shared' points to, with a new value
        shared = "other hello"; 
        System.out.println(shared);
    }
}

Isso compila bem, porque o compilador modifica o código, para que a nova classe gerada Enclosing $ 1 mantenha uma referência à instância de EnclosingClass em que foi instanciada (isso é apenas uma representação, mas você deve continuar):

public void someMethod() {
    new EnclosingClass$1(this).start();

    // change the reference 'shared' points to, with a new value
    shared = "other hello"; 
    System.out.println(shared);
}

public class EnclosingClass$1 extends Thread {
    EnclosingClass enclosing;
    public EnclosingClass$1(EnclosingClass enclosing) {
        this.enclosing = enclosing;
    }

    public void run() {
        System.out.println(enclosing.shared);
    }
}

Assim, quando a variável de referência 'compartilhada' em EnclosingClass for reatribuída, e isso acontecer antes da chamada para o Thread # run (), você verá "other hello" impresso duas vezes, porque agora a variável envolvente EnclosingClass $ 1 # manterá uma referência para o objeto da classe em que foi declarado, portanto, as alterações em qualquer atributo desse objeto serão visíveis para as instâncias de EnclosingClass $ 1.

Para mais informações sobre o assunto, você pode ver esta excelente postagem no blog (não escrita por mim): http://kevinboone.net/java_inner.html


E se a variável local 'shared' for um objeto mutável? De acordo com sua explicação, declarar 'final' também não ajudará, certo?
precisa saber é o seguinte

Declarar "compartilhado" como final permitirá modificar o estado do objeto que a variável final faz referência, mas para este exemplo específico que não funcionará porque você não poderá alterar o valor da variável "compartilhada" (que é o que o OP queria), você poderá usá-lo dentro de classes anônimas, mas seu valor não será alterado (porque é declarado final). É importante observar a diferença entre as variáveis ​​e os valores reais que elas possuem (que podem ser primitivas ou referências a objetos no heap).
emerino

>>> mas o valor não muda Eu acho que você está perdendo o ponto, isto é, se a variável de referência final está apontando para um objeto mutável, ela ainda pode ser atualizada, no entanto, a classe anônima cria a cópia superficial, portanto as alterações são refletidas no anônimo classe. Em outras palavras, estado está sincronizado, o que é desejado aqui. Aqui, o OP precisa de capacidade para modificar a variável compartilhada (o tipo primitivo) e para alcançar esse OP precisará agrupar o valor em um objeto mutável e compartilhar esse objeto mutável.
sactiw

1
Obviamente, o OP pode agrupar o valor necessário em um objeto mutável, declarar a variável como final e usá-lo. No entanto, ele pode evitar o uso de um objeto extra declarando a variável como um atributo da classe atual (conforme apontado e explicado na resposta). Forçar objetos mutáveis ​​(como usar matrizes apenas para poder modificar o valor de uma variável compartilhada) não é uma boa ideia.
emerino

7

Quando me deparei com esse problema, apenas passo os objetos para a classe interna através do construtor. Se eu precisar passar objetos primitivos ou imutáveis ​​(como neste caso), será necessária uma classe de wrapper.

Edit: Na verdade, eu não uso uma classe anônima, mas uma subclasse adequada:

public class PriceData {
        private double lastPrice = 0;
        private double price = 0;

        public void setlastPrice(double lastPrice) {
            this.lastPrice = lastPrice;
        }

        public double getLastPrice() {
            return lastPrice;
        }

        public void setPrice(double price) {
            this.price = price;
        }

        public double getPrice() {
            return price;
        }
    }

    public class PriceTimerTask extends TimerTask {
        private PriceData priceData;
        private Price priceObject;

        public PriceTimerTask(PriceData priceData, Price priceObject) {
            this.priceData = priceData;
            this.priceObject = priceObject;
        }

        public void run() {
            priceData.setPrice(priceObject.getNextPrice(lastPrice));
            System.out.println();
            priceData.setLastPrice(priceData.getPrice());

        }
    }

    public static void main(String args[]) {

        int period = 2000;
        int delay = 2000;

        PriceData priceData = new PriceData();
        Price priceObject = new Price();

        Timer timer = new Timer();

        timer.scheduleAtFixedRate(new PriceTimerTask(priceData, priceObject), delay, period);
    }

2

Você não pode se referir a variáveis ​​não finais porque o Java Language Specification diz isso. De 8.1.3:
"Qualquer variável local, parâmetro formal de método ou parâmetro de manipulador de exceção usado, mas não declarado em uma classe interna, deve ser declarado final." Parágrafo inteiro.
Eu posso ver apenas parte do seu código - de acordo comigo, agendar modificações nas variáveis ​​locais é uma ideia estranha. Variáveis ​​locais deixam de existir quando você sai da função. Talvez campos estáticos de uma classe sejam melhores?


2

Acabei de escrever algo para lidar com algo de acordo com a intenção dos autores . Eu descobri que a melhor coisa a fazer era deixar o construtor pegar todos os objetos e, em seu método implementado, usar esses objetos construtores.

No entanto, se você estiver escrevendo uma classe de interface genérica, precisará passar um Objeto, ou melhor, uma lista de Objetos. Isso poderia ser feito por Object [] ou, melhor ainda, Object ... porque é mais fácil chamar.

Veja meu exemplo abaixo.

List<String> lst = new ArrayList<String>();
lst.add("1");
lst.add("2");        

SomeAbstractClass p = new SomeAbstractClass (lst, "another parameter", 20, true) {            

    public void perform( ) {                           
        ArrayList<String> lst = (ArrayList<String>)getArgs()[0];                        
    }

};

public abstract class SomeAbstractClass{    
    private Object[] args;

    public SomeAbstractClass(Object ... args) {
        this.args = args;           
    }      

    public abstract void perform();        

    public Object[] getArgs() {
        return args;
    }

}

Consulte este post sobre fechamentos Java que suporta isso imediatamente : http://mseifed.blogspot.se/2012/09/closure-implementation-for-java-5-6-and.html

A versão 1 suporta a aprovação de encerramentos não finais com autocasting:
https://github.com/MSeifeddo/Closure-implementation-for-Java-5-6-and-7/blob/master/org/mo/closure/v1/ Closure.java

    SortedSet<String> sortedNames = new TreeSet<String>();
    // NOTE! Instead of enforcing final, we pass it through the constructor
    eachLine(randomFile0, new V1<String>(sortedNames) {
        public void call(String line) {
            SortedSet<String> sortedNames = castFirst();  // Read contructor arg zero, and auto cast it
            sortedNames.add(extractName(line));
        }
    });

2

Se você deseja alterar um valor em uma chamada de método dentro de uma classe anônima, esse "valor" é na verdade a Future. Então, se você usa o Guava, pode escrever

...
final SettableFuture<Integer> myvalue = SettableFuture<Integer>.create();
...
someclass.run(new Runnable(){

    public void run(){
        ...
        myvalue.set(value);
        ...
    }
 }

 return myvalue.get();

2

Uma solução que eu notei não é mencionada (a menos que eu errei, se por favor me corrija), é o uso de uma variável de classe. Corri para este problema de tentar executar um novo segmento dentro de um método: new Thread(){ Do Something }.

Ligar a doSomething()partir do seguinte funcionará. Você não precisa necessariamente declará-lo final, basta alterar o escopo da variável para que ela não seja coletada antes da classe interna. A menos que, é claro, seu processo seja enorme e a alteração do escopo possa criar algum tipo de conflito. Eu não queria fazer minha variável final, pois não era de forma alguma uma final / constante.

public class Test
{

    protected String var1;
    protected String var2;

    public void doSomething()
    {
        new Thread()
        {
            public void run()
            {
                System.out.println("In Thread variable 1: " + var1);
                System.out.println("In Thread variable 2: " + var2);
            }
        }.start();
    }

}

1

Se a variável requerida para ser final, não pode ser, você pode atribuir o valor da variável a outra variável e torná-lo final para que você possa usá-lo.


1

use ClassName.this.variableName para referenciar a variável não final


1

você pode simplesmente declarar a variável fora da classe externa. Depois disso, você poderá editar a variável de dentro da classe interna. Às vezes, enfrento problemas semelhantes ao codificar no Android, então declaro a variável como global e ela funciona para mim.


Isso realmente não responde à pergunta ... É por isso que você está recebendo votos negativos.
Stuart Siegler

0

Você pode fazer lastPrice, priceObjecte pricecampos da classe interna anônima?


0

A principal preocupação é se uma variável dentro da instância de classe anônima pode ser resolvida em tempo de execução. Não é necessário finalizar uma variável, desde que seja garantido que a variável esteja dentro do escopo de tempo de execução. Por exemplo, consulte as duas variáveis ​​_statusMessage e _statusTextView dentro do método updateStatus ().

public class WorkerService extends Service {

Worker _worker;
ExecutorService _executorService;
ScheduledExecutorService _scheduledStopService;

TextView _statusTextView;


@Override
public void onCreate() {
    _worker = new Worker(this);
    _worker.monitorGpsInBackground();

    // To get a thread pool service containing merely one thread
    _executorService = Executors.newSingleThreadExecutor();

    // schedule something to run in the future
    _scheduledStopService = Executors.newSingleThreadScheduledExecutor();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    ServiceRunnable runnable = new ServiceRunnable(this, startId);
    _executorService.execute(runnable);

    // the return value tells what the OS should
    // do if this service is killed for resource reasons
    // 1. START_STICKY: the OS restarts the service when resources become
    // available by passing a null intent to onStartCommand
    // 2. START_REDELIVER_INTENT: the OS restarts the service when resources
    // become available by passing the last intent that was passed to the
    // service before it was killed to onStartCommand
    // 3. START_NOT_STICKY: just wait for next call to startService, no
    // auto-restart
    return Service.START_NOT_STICKY;
}

@Override
public void onDestroy() {
    _worker.stopGpsMonitoring();
}

@Override
public IBinder onBind(Intent intent) {
    return null;
}

class ServiceRunnable implements Runnable {

    WorkerService _theService;
    int _startId;
    String _statusMessage;

    public ServiceRunnable(WorkerService theService, int startId) {
        _theService = theService;
        _startId = startId;
    }

    @Override
    public void run() {

        _statusTextView = MyActivity.getActivityStatusView();

        // get most recently available location as a latitude /
        // longtitude
        Location location = _worker.getLocation();
        updateStatus("Starting");

        // convert lat/lng to a human-readable address
        String address = _worker.reverseGeocode(location);
        updateStatus("Reverse geocoding");

        // Write the location and address out to a file
        _worker.save(location, address, "ResponsiveUx.out");
        updateStatus("Done");

        DelayedStopRequest stopRequest = new DelayedStopRequest(_theService, _startId);

        // schedule a stopRequest after 10 seconds
        _theService._scheduledStopService.schedule(stopRequest, 10, TimeUnit.SECONDS);
    }

    void updateStatus(String message) {
        _statusMessage = message;

        if (_statusTextView != null) {
            _statusTextView.post(new Runnable() {

                @Override
                public void run() {
                    _statusTextView.setText(_statusMessage);

                }

            });
        }
    }

}

0

o que funcionou para mim é apenas definir a variável fora dessa função do seu.

Pouco antes da função principal declarar ie

Double price;
public static void main(String []args(){
--------
--------
}

Isso não vai funcionar, você está declarando uma variável de instância, para usá-la, precisará criar uma instância dentro do seu método principal. Você deve ser mais específico ou simplesmente adicionar o modificador estático à variável 'price'.
Emerino

0

Declare a variável como estática e faça referência a ela no método necessário usando className.variable


Non-static parameter cannot be referenced from a static context
22615 Stephen

@Shweta variáveis ​​locais e parâmetros de método não podem ser declarados 'estáticos'; além disso, é sobre o modo como foi implementado para permitir que as classes dentro dos métodos (classes anônimas locais) continuem o acesso às variáveis ​​locais e aos parâmetros do método, mesmo após o método retornou, ou seja, ele faz suas cópias 'finais' e as usa como variáveis ​​de instância.
sactiw

0

Apenas mais uma explicação. Considere este exemplo abaixo

public class Outer{
     public static void main(String[] args){
         Outer o = new Outer();
         o.m1();        
         o=null;
     }
     public void m1(){
         //int x = 10;
         class Inner{
             Thread t = new Thread(new Runnable(){
                 public void run(){
                     for(int i=0;i<10;i++){
                         try{
                             Thread.sleep(2000);                            
                         }catch(InterruptedException e){
                             //handle InterruptedException e
                         }
                         System.out.println("Thread t running");                             
                     }
                 }
             });
         }
         new Inner().t.start();
         System.out.println("m1 Completes");
    }
}

Aqui a saída será

m1 é concluído

Thread t running

Thread t running

Thread t running

................

Agora o método m1 () é concluído e atribuímos a variável de referência o a null, o Now Outer Class Object é elegível para GC, mas ainda existe o Inner Class Object que possui um relacionamento (Has-A) com o objeto Thread que está sendo executado. Sem o objeto de classe externa existente, não há chance do método m1 () existente e sem o método m1 () existente, não há chance de existir sua variável local, mas se o Inner Class Object usar a variável local do método m1 (), tudo será auto-explicativo. .

Para resolver isso, precisamos criar uma cópia da variável local e, em seguida, copiar no heap com o objeto da classe Inner, o que o java faz apenas para a variável final, porque na verdade não são variáveis, são como constantes (tudo acontece apenas no tempo de compilação não em tempo de execução).


-1

Para resolver o problema acima, idiomas diferentes tomam decisões diferentes.

para Java, a solução é a que vemos neste artigo.

para C #, a solução é permitir efeitos colaterais e capturar por referência é a única opção.

para C ++ 11, a solução é permitir que o programador tome a decisão. Eles podem optar por capturar por valor ou por referência. Se capturar por valor, nenhum efeito colateral ocorreria porque a variável referenciada é realmente diferente. Se capturar por referência, efeitos colaterais podem ocorrer, mas o programador deve perceber.


-2

Porque é confuso se a variável não for final, pois as alterações não serão identificadas na classe anônima.

Basta fazer as variáveis ​​'preço' e 'lastPrice' final.

- Editar

Opa, e você também não precisará atribuir a eles, obviamente, em sua função. Você precisará de novas variáveis ​​locais. De qualquer forma, suspeito que alguém já tenha lhe dado uma resposta melhor.


2
não é apenas confuso - é totalmente incorreto, portanto o compilador não permite.
1919 Chii

Mas então como altero os valores quando preciso?
Ankur

Não apenas porque é confuso; isso ocorre porque o Java não suporta fechamentos. Veja minha resposta abaixo. @ Ankur: Você pode fazer com que as variáveis ​​sejam membros variáveis ​​do objeto de classe anônima, em vez de variáveis ​​locais em main ().
Jesper

Ele os está modificando, para que não possam ser finais.
19489 Robin

Se o preço e o lastPrice fossem finais, as atribuições a eles não seriam compiladas.
187 Greg Gregtes
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.