Eu costumava codificar bastante em Python. Agora, por motivos de trabalho, eu codigo em Java. Os projetos que faço são bastante pequenos e, possivelmente, o Python funcionaria melhor, mas há motivos válidos de não engenharia para usar Java (não posso entrar em detalhes).
Sintaxe Java não é problema; é apenas outra língua. Mas, além da sintaxe, o Java possui uma cultura, um conjunto de métodos de desenvolvimento e práticas consideradas "corretas". E, por enquanto, falho completamente em "grok" essa cultura. Então, eu realmente aprecio explicações ou indicações na direção certa.
Um exemplo completo mínimo está disponível em uma pergunta de Estouro de pilha que eu iniciei: https://stackoverflow.com/questions/43619566/returning-a-result-with-several-values-the-java-way/43620339
Eu tenho uma tarefa - analisar (de uma única seqüência de caracteres) e manipular um conjunto de três valores. No Python, é uma linha única (tupla), em Pascal ou C, um registro / estrutura de 5 linhas.
De acordo com as respostas, o equivalente a uma struct está disponível na sintaxe Java e o triplo está disponível em uma biblioteca Apache amplamente usada - mas a maneira "correta" de fazer isso é na verdade criando uma classe separada para o valor, concluída com getters e setters. Alguém foi muito gentil em fornecer um exemplo completo. Eram 47 linhas de código (bem, algumas dessas linhas eram espaços em branco).
Entendo que uma enorme comunidade de desenvolvimento provavelmente não esteja "errada". Portanto, este é um problema com o meu entendimento.
As práticas do Python otimizam a legibilidade (que, nessa filosofia, leva à capacidade de manutenção) e depois a velocidade de desenvolvimento. As práticas de C otimizam para o uso de recursos. Para que as práticas Java são otimizadas? Meu melhor palpite é escalabilidade (tudo deve estar em um estado pronto para um projeto com milhões de LOC), mas é um palpite muito fraco.
fonte
Respostas:
A linguagem Java
Acredito que todas essas respostas estão erradas ao tentar atribuir a intenção à maneira como o Java funciona. A verbosidade do Java não deriva de ser orientada a objetos, pois Python e muitas outras linguagens ainda têm sintaxe de terser. A verbosidade do Java também não provém do suporte a modificadores de acesso. Em vez disso, é simplesmente como o Java foi projetado e evoluiu.
O Java foi originalmente criado como um C ligeiramente melhorado com OO. Como tal, o Java possui uma sintaxe dos anos 70. Além disso, o Java é muito conservador em adicionar recursos para manter a compatibilidade com versões anteriores e permitir que ele resista ao teste do tempo. Se o Java tivesse adicionado recursos modernos, como literais XML, em 2005, quando o XML era a moda, a linguagem teria sido inchada com recursos fantasmas com os quais ninguém se importa e que limitam sua evolução 10 anos depois. Portanto, o Java simplesmente não possui muita sintaxe moderna para expressar conceitos de maneira concisa.
No entanto, não há nada fundamental que impeça o Java de adotar essa sintaxe. Por exemplo, o Java 8 adicionou lambdas e referências a métodos, reduzindo bastante a verbosidade em muitas situações. Da mesma forma, o Java poderia adicionar suporte para declarações compactas de tipos de dados, como as classes de caso da Scala. Mas o Java simplesmente não o fez. Observe que os tipos de valores personalizados estão no horizonte e esse recurso pode introduzir uma nova sintaxe para declará-los. Suponho que vamos ver.
A cultura Java
A história do desenvolvimento Java corporativo nos levou em grande parte à cultura que vemos hoje. No final dos anos 90 / início dos anos 00, o Java se tornou uma linguagem extremamente popular para aplicativos de negócios do lado do servidor. Naquela época, esses aplicativos eram amplamente escritos ad-hoc e incorporavam muitas preocupações complexas, como APIs HTTP, bancos de dados e processamento de feeds XML.
Nos anos 2000, ficou claro que muitos desses aplicativos tinham muito em comum e as estruturas para gerenciar essas preocupações, como o Hibernate ORM, o analisador XML do Xerces, JSPs e a API do servlet e EJB, se tornaram populares. No entanto, embora essas estruturas reduzissem o esforço de trabalhar no domínio específico que elas definiam para automatizar, elas exigiam configuração e coordenação. Na época, por qualquer motivo, era popular escrever estruturas para atender ao caso de uso mais complexo e, portanto, essas bibliotecas eram complicadas de configurar e integrar. E, com o tempo, eles se tornaram cada vez mais complexos à medida que acumulavam recursos. O desenvolvimento corporativo de Java tornou-se cada vez mais cada vez mais vinculado a bibliotecas de terceiros e menos sobre algoritmos de escrita.
Eventualmente, a configuração e o gerenciamento tediosos das ferramentas corporativas tornaram-se dolorosos o suficiente para que as estruturas, principalmente a Spring, surgissem para gerenciar o gerenciamento. Você poderia colocar toda a sua configuração em um só lugar, continuou a teoria, e a ferramenta de configuração configuraria as peças e as uniria. Infelizmente, essas "estruturas de estrutura" adicionaram mais abstração e complexidade em cima de toda a bola de cera.
Nos últimos anos, mais bibliotecas leves cresceram em popularidade. No entanto, toda uma geração de programadores Java atingiu a maioridade durante o crescimento de estruturas corporativas pesadas. Seus modelos, aqueles que desenvolvem as estruturas, escreveram fábricas de fábrica e carregadores de bean de configuração de proxy. Eles tiveram que configurar e integrar essas monstruosidades no dia-a-dia. E, como resultado, a cultura da comunidade como um todo seguiu o exemplo dessas estruturas e tendia a um excesso de engenharia.
fonte
for
loop quando ummap
seria mais apropriado (e legível). Há um meio feliz.Acredito que tenho uma resposta para um dos pontos que você levanta e que não foram levantados por outros.
Java otimiza para detecção de erros de programador em tempo de compilação, nunca fazendo suposições
Em geral, Java tende a inferir apenas fatos sobre o código-fonte depois que o programador já expressou explicitamente sua intenção. O compilador Java nunca faz suposições sobre o código e usará apenas inferência para reduzir o código redundante.
A razão por trás dessa filosofia é que o programador é apenas humano. O que escrevemos nem sempre é o que realmente pretendemos que o programa faça. A linguagem Java tenta mitigar alguns desses problemas, forçando o desenvolvedor a sempre declarar explicitamente seus tipos. Essa é apenas uma maneira de verificar se o código que foi escrito realmente faz o que foi planejado.
Algumas outras linguagens aprimoram ainda mais essa lógica, verificando pré-condições, pós-condições e invariantes (embora não tenha certeza de que o façam em tempo de compilação). Essas são formas ainda mais extremas para o programador fazer com que o compilador verifique novamente seu próprio trabalho.
No seu caso, isso significa que, para que o compilador garanta que você está realmente retornando os tipos que pensa estar retornando, é necessário fornecer essas informações ao compilador.
Em Java, existem duas maneiras de fazer isso:
Use
Triplet<A, B, C>
como tipo de retorno (que realmente deve ser emjava.util
, e eu realmente não posso explicar por que não é. Especialmente desde JDK8 introduzirFunction
,BiFunction
,Consumer
,BiConsumer
, etc ... Parece apenas quePair
eTriplet
, pelo menos, faria sentido. Mas estou divagando )Crie seu próprio tipo de valor para esse fim, em que cada campo é nomeado e digitado corretamente.
Nos dois casos, o compilador pode garantir que sua função retorne os tipos declarados e que o chamador perceba qual é o tipo de cada campo retornado e os use adequadamente.
Alguns idiomas fornecem verificação estática de tipos E inferência de tipos ao mesmo tempo, mas isso deixa a porta aberta para uma classe sutil de problemas de incompatibilidade de tipos. Onde o desenvolvedor pretendeu retornar um valor de um determinado tipo, mas na verdade retorna outro e o compilador AINDA aceita o código, porque acontece que, por co-incidência, a função e o chamador usam apenas métodos que podem ser aplicados tanto ao pretendido e os tipos reais.
Considere algo como isto em Texto datilografado (ou tipo de fluxo) em que a inferência de tipo é usada em vez da digitação explícita.
Esse é um caso trivial bobo, é claro, mas pode ser muito mais sutil e levar a problemas difíceis de depurar, porque o código parece correto. Esse tipo de problema é totalmente evitado em Java, e é para isso que toda a linguagem é projetada.
Observe que, seguindo os princípios orientados a objetos e a modelagem orientada a domínio, a duração da análise de caso na pergunta vinculada também pode ser retornada como um
java.time.Duration
objeto, o que seria muito mais explícito do que os dois casos acima.fonte
Java e Python são as duas linguagens que mais uso, mas estou vindo de outra direção. Ou seja, eu estava no mundo Java antes de começar a usar o Python, para poder ajudar. Eu acho que a resposta para a questão maior de "por que as coisas são tão pesadas" se resume a duas coisas:
yield
.Java 'otimiza' para software de alto valor que será mantido por muitos anos por grandes equipes de pessoas. Eu tive a experiência de escrever coisas em Python e analisá-las um ano depois e ficar perplexo com meu próprio código. Em Java, posso ver pequenos trechos do código de outras pessoas e saber instantaneamente o que ele faz. No Python, você realmente não pode fazer isso. Não é que um seja melhor como você parece perceber, eles apenas têm custos diferentes.
No caso específico que você mencionou, não há tuplas. A solução fácil é criar uma classe com valores públicos. Quando o Java foi lançado, as pessoas faziam isso regularmente. O primeiro problema ao fazer isso é que é uma dor de cabeça de manutenção. Se você precisar adicionar alguma lógica ou segurança de encadeamento ou quiser usar o polimorfismo, precisará, no mínimo, tocar em todas as classes que interagem com o objeto 'tupla-esque'. No Python, existem soluções para isso, como
__getattr__
etc., por isso não é tão terrível.Existem alguns maus hábitos (IMO) em torno disso. Nesse caso, se você deseja uma tupla, questiono por que você a tornaria um objeto mutável. Você só precisa de getters (em uma nota lateral, eu odeio a convenção get / set, mas é o que é.) Eu acho que uma classe simples (mutável ou não) pode ser útil em um contexto privado ou pacote-privado em Java . Ou seja, ao limitar as referências no projeto à classe, você pode refatorar mais tarde, conforme necessário, sem alterar a interface pública da classe. Aqui está um exemplo de como você pode criar um objeto simples e imutável:
Esse é um tipo de padrão que eu uso. Se você não estiver usando um IDE, você deve começar. Ele irá gerar os getters (e setters, se você precisar deles) para você, então isso não é tão doloroso.
Eu me sentiria negligente se não indicasse que já existe um tipo que parece atender a maioria das suas necessidades aqui . Deixando isso de lado, a abordagem que você está usando não é adequada para Java, pois joga com suas fraquezas, não com seus pontos fortes. Aqui está uma melhoria simples:
fonte
Antes de você ficar muito bravo com Java, leia minha resposta em seu outro post .
Uma de suas reclamações é a necessidade de criar uma classe apenas para retornar algum conjunto de valores como resposta. Esta é uma preocupação válida que eu acho que mostra que suas intuições de programação estão no caminho certo! No entanto, acho que outras respostas estão perdendo a marca, mantendo-se com o antipadrão de obsessão primitivo com o qual você se comprometeu. E o Java não tem a mesma facilidade de trabalhar com várias primitivas que o Python, onde você pode retornar vários valores nativamente e atribuí-los a várias variáveis facilmente.
Mas quando você começa a pensar sobre o que um
ApproximateDuration
tipo faz por você, percebe que o escopo não é tão restrito a "apenas uma classe aparentemente desnecessária para retornar três valores". O conceito representado por essa classe é na verdade um dos conceitos principais de negócios do domínio - a necessidade de poder representar os tempos de maneira aproximada e compará-los. Isso precisa fazer parte da linguagem onipresente do núcleo do seu aplicativo, com um bom suporte a objetos e domínios, para que possa ser testado, modular, reutilizável e útil.Seu código que soma durações aproximadas (ou durações com uma margem de erro, no entanto você representa isso) é totalmente processual ou existe algum objeto para ele? Eu proporia que um bom design em torno da soma de durações aproximadas determinaria fazê-lo fora de qualquer código consumidor, dentro de uma classe que possa ser testada. Eu acho que usar esse tipo de objeto de domínio terá um efeito cascata positivo em seu código que ajuda a desviar você das etapas de procedimento linha a linha para realizar uma única tarefa de alto nível (embora com muitas responsabilidades), em direção a classes de responsabilidade única que estão livres de conflitos de preocupações diferentes.
Por exemplo, digamos que você aprenda mais sobre qual precisão ou escala é realmente necessária para que sua soma e comparação de duração funcionem corretamente e você descobre que precisa de um sinalizador intermediário para indicar "erro de aproximadamente 32 milissegundos" (próximo ao quadrado raiz de 1000, portanto, logaritmicamente na metade entre 1 e 1000). Se você se vinculou ao código que usa primitivas para representar isso, precisará encontrar todos os lugares no código em que está
is_in_seconds,is_under_1ms
e alterá-lo parais_in_seconds,is_about_32_ms,is_under_1ms
. Tudo teria que mudar por todo o lado! Fazer uma classe cuja responsabilidade é registrar a margem de erro para que possa ser consumida em outro lugar libera seus consumidores de saberem detalhes de quais margens de erro são importantes ou qualquer coisa sobre como elas se combinam e permite que eles especifiquem apenas a margem de erro relevante. no momento. (Ou seja, nenhum código de consumo cuja margem de erro esteja correta é forçado a mudar quando você adiciona uma nova margem de nível de erro na classe, pois todas as margens de erro antigas ainda são válidas).Declaração de encerramento
A reclamação sobre o peso do Java parece desaparecer à medida que você se aproxima dos princípios do SOLID e GRASP e da engenharia de software mais avançada.
Termo aditivo
Acrescentarei de forma totalmente gratuita e injusta que as propriedades automáticas do C # e a capacidade de atribuir propriedades get-get em construtores ajudam a limpar ainda mais o código um tanto bagunçado que "o modo Java" exigirá (com campos de apoio privados explícitos e funções getter / setter) :
Aqui está uma implementação Java do acima:
Agora isso é bastante limpo. Observe o uso muito importante e intencional da imutabilidade - isso parece crucial para esse tipo específico de classe com valor agregado.
Por outro lado, essa classe também é uma candidata decente por ser um
struct
tipo de valor. Alguns testes mostram se a mudança para uma estrutura tem um benefício de desempenho em tempo de execução (poderia).fonte
Tanto o Python quanto o Java são otimizados para manutenção de acordo com a filosofia de seus designers, mas eles têm idéias muito diferentes sobre como conseguir isso.
Python é uma linguagem de vários paradigmas que otimiza a clareza e a simplicidade do código (fácil de ler e escrever).
Java é (tradicionalmente) uma linguagem OO baseada em classe de paradigma único que otimiza para explicitação e consistência - mesmo à custa de um código mais detalhado.
Uma tupla Python é uma estrutura de dados com um número fixo de campos. A mesma funcionalidade pode ser alcançada por uma classe regular com campos explicitamente declarados. No Python, é natural fornecer tuplas como alternativa às classes, pois isso permite simplificar bastante o código, principalmente devido ao suporte de sintaxe interno para tuplas.
Mas isso realmente não corresponde à cultura Java para fornecer esses atalhos, pois você já pode usar classes declaradas explícitas. Não é necessário introduzir um tipo diferente de estrutura de dados apenas para salvar algumas linhas de código e evitar algumas declarações.
Java prefere um único conceito (classes) aplicado consistentemente com um mínimo de açúcar sintático para casos especiais, enquanto o Python fornece várias ferramentas e muito açúcar sintático para permitir que você escolha o mais conveniente para qualquer finalidade específica.
fonte
Não procure práticas; geralmente é uma péssima idéia, como dito em Best Practices BAD, padrões BOM? . Sei que você não está pedindo melhores práticas, mas ainda acho que você encontrará alguns elementos relevantes.
Procurar uma solução para o seu problema é melhor que uma prática e seu problema não é uma tupla para retornar três valores em Java rapidamente:
Aqui você tem um objeto imutável que contém apenas seus dados e público, para que não haja necessidade de getters. Observe que, no entanto, se você usar algumas ferramentas de serialização ou camada de persistência como um ORM, elas geralmente usam getter / setter (e podem aceitar um parâmetro para usar campos em vez de getter / setter). E é por isso que essas práticas são muito usadas. Portanto, se você quiser saber sobre práticas, é melhor entender por que elas estão aqui para uma melhor utilização delas.
Por fim: uso getters porque uso muitas ferramentas de serialização, mas também não as escrevo; Eu uso o lombok: eu uso os atalhos fornecidos pelo meu IDE.
fonte
Sobre os idiomas Java em geral:
Existem várias razões pelas quais o Java tem classes para tudo. No que diz respeito ao meu conhecimento, o principal motivo é:
Java deve ser fácil de aprender para iniciantes. Quanto mais explícitas, mais difícil é perder detalhes importantes. Acontece menos mágica que seria difícil para os iniciantes entenderem.
Quanto ao seu exemplo específico: a linha de argumento para uma classe separada é a seguinte: se essas três coisas são suficientemente fortes uma para a outra, são retornadas como um valor, vale a pena nomear essa "coisa". E introduzir um nome para um grupo de coisas estruturadas de maneira comum significa definir uma classe.
Você pode reduzir o padrão com ferramentas como Lombok:
fonte
Há muitas coisas que podem ser ditas sobre a cultura Java, mas acho que, no caso de você se deparar agora, há alguns aspectos significativos:
Classes "Struct" com campos
Como outras respostas mencionaram, você pode simplesmente usar uma classe com campos públicos. Se você finalizá-las, obtém uma classe imutável e as inicializa com o construtor:
Obviamente, isso significa que você está vinculado a uma classe específica e qualquer coisa que precise produzir ou consumir um resultado de análise deve usar essa classe. Para algumas aplicações, tudo bem. Para outros, isso pode causar alguma dor. Grande parte do código Java é sobre a definição de contratos, e isso normalmente leva você a interfaces.
Outra armadilha é que, com uma abordagem baseada em classe, você expõe campos e todos esses campos devem ter valores. Por exemplo, isSeconds e millis sempre precisam ter algum valor, mesmo se isLessThanOneMilli for verdadeiro. Qual deve ser a interpretação do valor do campo millis quando isLessThanOneMilli for verdadeiro?
"Estruturas" como interfaces
Com os métodos estáticos permitidos nas interfaces, é realmente relativamente fácil criar tipos imutáveis sem muita sobrecarga sintática. Por exemplo, eu posso implementar o tipo de estrutura de resultados que você está falando como algo assim:
Ainda é um monte de clichê, eu concordo absolutamente, mas há alguns benefícios também, e acho que eles começam a responder algumas de suas principais perguntas.
Com uma estrutura como esse resultado da análise, o contrato do seu analisador é definido com muita clareza. No Python, uma tupla não é realmente diferente de outra. Em Java, a digitação estática está disponível; portanto, já descartamos determinadas classes de erros. Por exemplo, se você está retornando uma tupla em Python e deseja retornar a tupla (millis, isSeconds, isLessThanOneMilli), você pode acidentalmente fazer:
quando você quis dizer:
Com esse tipo de interface Java, você não pode compilar:
em absoluto. Você tem que fazer:
Esse é um benefício das linguagens estaticamente tipadas em geral.
Essa abordagem também começa a lhe dar a capacidade de restringir quais valores você pode obter. Por exemplo, ao chamar getMillis (), você pode verificar se isLessThanOneMilli () é verdadeiro e, se for, lançar uma IllegalStateException (por exemplo), já que não há valor significativo de millis nesse caso.
Tornando difícil fazer a coisa errada
No exemplo de interface acima, você ainda tem o problema de trocar acidentalmente os argumentos isSeconds e isLessThanOneMilli, pois eles têm o mesmo tipo.
Na prática, você pode realmente usar o TimeUnit e a duração, para obter um resultado como:
Isso está ficando muito mais código, mas você só precisa escrevê-lo uma vez e (supondo que tenha documentado as coisas corretamente), as pessoas que acabam usando seu código não precisam adivinhar o que o resultado significa e acidentalmente não pode fazer coisas como
result[0]
quando elas significamresult[1]
. Você ainda pode criar instâncias de maneira bem sucinta, e obter dados delas também não é tão difícil:Observe que você também pode fazer algo assim com a abordagem baseada em classe. Basta especificar construtores para os diferentes casos. No entanto, você ainda tem o problema de inicializar os outros campos e não pode impedir o acesso a eles.
Outra resposta mencionou que a natureza corporativa do Java significa que, na maioria das vezes, você está compondo outras bibliotecas que já existem ou escrevendo bibliotecas para outras pessoas usarem. Sua API pública não deve exigir muito tempo consultando a documentação para decifrar os tipos de resultado, se for possível evitar.
Você escreve essas estruturas apenas uma vez, mas as cria muitas vezes, portanto ainda deseja a criação concisa (que obtém). A digitação estática garante que os dados que você obtém deles sejam o que você espera.
Agora, tudo o que foi dito, ainda existem lugares onde tuplas ou listas simples podem fazer muito sentido. Pode haver menos sobrecarga no retorno de uma matriz de algo, e se esse for o caso (e essa sobrecarga for significativa, o que você determinaria com a criação de perfil), usar um conjunto simples de valores internamente pode fazer muito sentido. Sua API pública provavelmente ainda deve ter tipos claramente definidos.
fonte
originalTimeUnit=DurationTimeUnit.UNDER1MS
o chamador, tentasse ler o valor de milissegundos.O problema é que você compara maçãs com laranjas . Você perguntou como simular o retorno de mais do que um valor único, fornecendo um exemplo rápido e sujo de python com tupla sem tipo e você realmente recebeu a resposta praticamente de uma linha .
A resposta aceita fornece uma solução comercial correta. Nenhuma solução temporária rápida que você precisaria jogar fora e implementar corretamente na primeira vez que precisaria fazer algo prático com o valor retornado, mas uma classe POJO compatível com um grande conjunto de bibliotecas, incluindo persistência, serialização / desserialização, instrumentação e tudo o que for possível.
Isso também não é demorado. A única coisa que você precisa escrever são as definições de campo. Setters, getters, hashCode e iguais podem ser gerados. Portanto, sua pergunta real deve ser: por que os getters e setters não são gerados automaticamente, mas é uma questão de sintaxe (questão sintática do açúcar, diriam alguns) e não a questão cultural.
E, finalmente, você está pensando demais tentando acelerar algo que não é importante. O tempo gasto na criação de classes de DTO é insignificante em comparação com o tempo gasto na manutenção e depuração do sistema. Portanto, ninguém otimiza para menos verbosidade.
fonte
Existem três fatores diferentes que contribuem para o que você está observando.
Tuplas versus campos nomeados
Talvez o mais trivial - nos outros idiomas você usou uma tupla. Debater se as tuplas é uma boa ideia não é realmente o ponto - mas em Java você usou uma estrutura mais pesada, por isso é uma comparação um pouco injusta: você poderia ter usado uma variedade de objetos e algum tipo de conversão.
Sintaxe de idioma
Poderia ser mais fácil declarar a classe? Não estou falando sobre tornar os campos públicos ou usar um mapa, mas algo como as classes de caso do Scala, que fornecem todos os benefícios da configuração que você descreveu, mas são muito mais concisos:
Poderíamos ter isso - mas há um custo: a sintaxe se torna mais complicada. É claro que pode valer a pena para alguns casos, ou mesmo para a maioria dos casos, ou mesmo para a maioria dos casos nos próximos 5 anos - mas precisa ser julgado. A propósito, isso é uma das coisas legais com os idiomas que você pode modificar (ou seja, o lisp) - e observe como isso se torna possível devido à simplicidade da sintaxe. Mesmo que você não modifique o idioma, uma sintaxe simples permite ferramentas mais poderosas; por exemplo, muitas vezes sinto falta de algumas opções de refatoração disponíveis para Java, mas não para Scala.
Filosofia da linguagem
Mas o fator mais importante é que uma linguagem deve permitir uma certa maneira de pensar. Às vezes, pode parecer opressivo (eu sempre desejei suporte para um determinado recurso), mas a remoção de recursos é tão importante quanto tê-los. Você poderia apoiar tudo? Claro, mas você também pode escrever um compilador que compila todos os idiomas. Em outras palavras, você não terá um idioma - terá um superconjunto de idiomas e todo projeto adotaria basicamente um subconjunto.
É claro que é possível escrever um código que vá contra a filosofia da linguagem e, como você observou, os resultados geralmente são feios. Ter uma classe com apenas alguns campos em Java é semelhante a usar um var no Scala, degenerar predicados de prólogo para funções, executar umPerformIO inseguro em haskell etc. As classes Java não devem ser leves - elas não existem para passar dados . Quando algo parece difícil, geralmente é proveitoso dar um passo atrás e ver se há outra maneira. No seu exemplo:
Por que a duração é separada das unidades? Existem muitas bibliotecas de tempo que permitem declarar uma duração - algo como Duração (5 segundos) (a sintaxe varia), que permite fazer o que você quer de uma maneira muito mais robusta. Talvez você queira convertê-lo - por que verificar se o resultado [1] (ou [2]?) É uma 'hora' e se multiplica por 3600? E para o terceiro argumento - qual é o seu propósito? Eu acho que em algum momento você precisará imprimir "menos de 1ms" ou o tempo real - essa é uma lógica que naturalmente pertence aos dados de tempo. Ou seja, você deve ter uma classe como esta:
}
ou o que você realmente deseja fazer com os dados, encapsulando a lógica.
Claro, pode haver um caso em que esse caminho não funcione - não estou dizendo que esse é o algoritmo mágico para converter resultados de tuplas em código Java idiomático! E pode haver casos em que é muito feio e ruim e talvez você deva ter usado um idioma diferente - é por isso que existem tantos afinal!
Mas minha visão sobre por que as classes são "estruturas pesadas" em Java é que você não deve usá-las como contêineres de dados, mas como células da lógica independentes.
fonte
Na minha opinião, os principais motivos são
Isso fornece a você "Você deve escrever uma classe para retornar a qualquer resultado não trivial", que por sua vez é bastante pesado. Se você usou uma classe em vez da interface, pode ter apenas campos e usá-los diretamente, mas isso o vincula a uma implementação específica.
fonte
def f(...): (Int, Int)
é uma funçãof
que retorna um valor que por acaso é uma tupla de números inteiros). Não tenho certeza se os genéricos em Java são o problema; observe que Haskell também digita apagamento, por exemplo. Eu não acho que haja uma razão técnica para o Java não ter tuplas.Eu concordo com JacquesB responder que
Mas explicitação e consistência não são os objetivos finais a serem otimizados. Quando você diz 'python é otimizado para facilitar a leitura', menciona imediatamente que o objetivo final é 'manutenibilidade' e 'velocidade de desenvolvimento'.
O que você alcança quando possui explicitação e consistência, do jeito Java? Minha opinião é que ela evoluiu como uma linguagem que pretende fornecer uma maneira previsível, consistente e uniforme de resolver qualquer problema de software.
Em outras palavras, a cultura Java é otimizada para fazer os gerentes acreditarem que entendem o desenvolvimento de software.
Ou, como um sábio disse há muito tempo ,
fonte
(Esta resposta não é uma explicação para Java em particular, mas aborda a questão geral de “Para o que as práticas [pesadas] podem otimizar?”)
Considere estes dois princípios:
Tentar otimizar um desses objetivos pode atrapalhar o outro (por exemplo , dificultar a execução da coisa errada também pode dificultar a execução da coisa certa ou vice-versa).
Quais tradeoffs são feitos em qualquer caso específico depende do aplicativo, das decisões dos programadores ou da equipe em questão e da cultura (da organização ou da comunidade de idiomas).
Por exemplo, se um bug ou uma interrupção de algumas horas em seu programa resultar em perda de vidas (sistemas médicos, aeronáutica) ou até apenas dinheiro (como milhões de dólares nos sistemas de anúncios do Google), você faria trocas diferentes (não apenas no seu idioma, mas também em outros aspectos da cultura de engenharia) do que você faria em um roteiro único: provavelmente ele se inclina para o lado "pesado".
Outros exemplos que tendem a tornar seu sistema mais "pesado":
Estes são apenas alguns exemplos, para que você tenha uma idéia de casos em que tornar as coisas “pesadas” (e dificultar a escrita rápida de algum código) pode realmente ser intencional. (Pode-se até argumentar que, se escrever código requer muito esforço, isso pode levar você a pensar com mais cuidado antes de escrever código! É claro que essa linha de argumento rapidamente se torna ridícula.)
Um exemplo: o sistema Python interno do Google tende a tornar as coisas "pesadas", de modo que você não pode simplesmente importar o código de outra pessoa, você deve declarar a dependência em um arquivo BUILD , a equipe cujo código você deseja importar precisa ter sua biblioteca declarada como visível para o seu código etc.
Nota : Todas as opções acima são exatamente quando as coisas tendem a ficar "pesadas". Eu absolutamente não afirmo que Java ou Python (as próprias línguas ou suas culturas) fazem trocas ótimas para qualquer caso em particular; é para você pensar. Dois links relacionados a essas compensações:
fonte
A cultura Java evoluiu ao longo do tempo com fortes influências de fontes abertas e de software corporativo - o que é uma mistura estranha se você realmente pensa sobre isso. As soluções corporativas exigem ferramentas pesadas e o código aberto exige simplicidade. O resultado final é que Java está em algum lugar no meio.
Parte do que está influenciando a recomendação é o que é considerado legível e de manutenção em Python e Java é muito diferente.
Menciono apenas C # porque a biblioteca padrão possui um conjunto de classes Tuple <A, B, C, .. n> e é um exemplo perfeito de como as tuplas são difíceis de manusear, se o idioma não as suportar diretamente. Em quase todos os casos, seu código se torna mais legível e de manutenção se você tiver escolhido classes para lidar com o problema. No exemplo específico da sua pergunta de estouro de pilha vinculada, os outros valores seriam facilmente expressos como getters calculados no objeto de retorno.
Uma solução interessante que a plataforma C # fez, que fornece um meio termo feliz, é a idéia de objetos anônimos (lançados no C # 3.0 ) que arranham essa coceira muito bem. Infelizmente, Java ainda não tem um equivalente.
Até que os recursos da linguagem Java sejam alterados, a solução mais legível e de manutenção é ter um objeto dedicado. Isso ocorre devido a restrições na linguagem que datam de 1995. Os autores originais tinham muito mais recursos de linguagem planejados que nunca foram criados, e a compatibilidade com versões anteriores é uma das principais restrições em torno da evolução do Java ao longo do tempo.
fonte
Eu acho que uma das principais coisas sobre o uso de uma classe neste caso é que o que acontece junto deve permanecer junto.
Eu tive essa discussão ao contrário, sobre argumentos de método: Considere um método simples que calcula o IMC:
Nesse caso, eu argumentaria contra esse estilo porque peso e altura estão relacionados. O método "comunica" esses são dois valores separados quando não estão. Quando você calcularia um IMC com o peso de uma pessoa e a altura de outra? Não faria sentido.
Faz muito mais sentido, porque agora você comunica claramente que a altura e o peso vêm da mesma fonte.
O mesmo vale para o retorno de vários valores. Se eles estiverem claramente conectados, retorne um pequeno pacote e use um objeto, se não retornar vários valores.
fonte
Para ser franco, a cultura é que os programadores de Java tendiam originalmente a sair de universidades onde eram ensinados princípios orientados a objetos e princípios de design de software sustentável.
Como ErikE diz em mais palavras em sua resposta, você parece não estar escrevendo código sustentável. O que vejo no seu exemplo é que há uma confusão muito incômoda de preocupações.
Na cultura Java, você tenderá a estar ciente de quais bibliotecas estão disponíveis, e isso permitirá que você obtenha muito mais do que sua programação imediata. Portanto, você trocaria suas idiossincrasias pelos padrões e estilos de design que foram experimentados e testados em ambientes industriais de núcleo duro.
Mas, como você diz, isso não tem desvantagens: hoje, tendo usado Java há mais de 10 anos, costumo usar Node / Javascript ou Go para novos projetos, porque ambos permitem um desenvolvimento mais rápido e, com arquiteturas no estilo de microsserviço, são muitas vezes é suficiente. A julgar pelo fato de o Google ter usado muito Java, mas foi o criador do Go, acho que eles podem estar fazendo o mesmo. Mas, embora agora use Go e Javascript, ainda uso muitas das habilidades de design que adquiri em anos de uso e compreensão de Java.
fonte