Por que System.out.println?

Recentemente comecei a estudar lambda, method references e afins, e acabei me deparando em uma pergunta que fiz quando tive o primeiro contato com o Java: Por que System.out.println() ao invés de println()?

Criei uma classe Objeto para teste e dentro dela contém dois métodos estáticos println(), onde um recebe um Object obj como argumento e o outro String s.

Fiz todas as classes do projeto herdarem Objeto, e realizei dois testes de lambda e um teste de method reference. Os três casos foram satisfatórios, e retornaram exatamente o que queria.

Daí a pergunta: Como toda Classe herda de Object, não seria mais interessante Object conter métodos estáticos chamando System.out.println()?

O método println e outros estão definidos na classe PrintStream.
A classe System é uma classe utilitária, isto é, só possui métodos e atributos estáticos, para interagir com alguns recursos do sistema operacional, seus atributos estáticos são:

Não faz sentido você usar herança para utilizar métodos estáticos.
O que é estático pertence à classe, não às instâncias, então é acessível via NomeDoTipoDeDado.nomeDoMetodo, ou através de import static.
Se usar herança pra isso, estará fazendo um acoplamento forte desnecessariamente.

Faz sentido todo objeto ter métodos para imprimir mensagens?

Tenha em mente que herança serve para fazer polimorfismo e o que é estático não é polimórfico.

Seguindo sua lógica, então o Object do Java poderia estender System para ter acesso implícito aos métodos estáticos de System, isso faz sentido pra você?

Tem uma pergunta que se deve fazer ao pensar em fazer herança:

Um <pretendente_a_classe_filha> é um <pretendente_a_classe_pai>?

Por exemplo:

Um Object é um System?

Não né? Então não faz sentido usar herança.

Mesma coisa como você sugeriu que os métodos print pertencessem à Object.
Faz sentido todo e qualquer objeto querer escrever e/ou ler dados na entrada e saída do sistema operacional?
E mesmo que quisesse, sua sugestão é criar métodos estáticos, então não precisa forçar herança, pois o que é estático não é polimórfico.
:wink:

3 curtidas
public class Objeto {
  public static void println(Object obj) {
    System.out.println(obj);
  }
  public static void println(String s) {
    System.out.println(s);
  }
}

Sim, eu havia entendido o que você sugeriu.

Mas como eu disse, não faz sentido usar herança para acessar métodos estáticos.

Porque eu vou forçar isso?

class MinhaClass extends Objeto { // herança é acoplamento forte

    void meuMetodo() {
        println("Qualquer coisa"); // usando herança pra acessar código estático
    }
}

Se eu posso fazer isso?

class MinhaClass { // não estou estendendo nada, mas poderia estender outra coisa

    void meuMetodo() {
        Objeto.println("Qualquer coisa"); // acessando código estático através de acessso qualificado
    }
}

Ou isso?

import static Objeto.println;

class MinhaClass {

    void meuMetodo() {
        println("Qualquer coisa"); // acessando código estático através de import static
    }
}

Ou isso?

import static java.lang.System.out.*;

class MinhaClass {

    void meuMetodo() {
        println("Qualquer coisa"); // acessando código estático do `System.out` através de import static
    }
}

:man_shrugging:

4 curtidas

Só um detalhe: tem que ser import static (se for static import não compila).

E no caso do System.out, você só consegue importar até o out:

import static java.lang.System.*;

...
out.println("etc");

De resto, concordo com tudo, não faz o menor sentido usar herança nesse caso…

1 curtida

Se está usando herança somente para “digitar menos”, então está usando pelo motivo errado.

Sem contar que herança para acessar métodos estáticos, como já foi dito, é completamente desnecessário - e você ainda “queima” o uso dela, pois em Java cada classe só pode herdar de uma outra classe (então se sua classe precisar estender outra que faz mais sentido, não vai ser possível, porque você já estendeu uma desnecessariamente).

E fazer com que todas as classes herdem o método println acaba violando o Princípio da Responsabilidade Única. System.out é uma instância de PrintStream, que é uma classe que - de forma resumida - “sabe como imprimir coisas”.

No seu caso, como o Objeto só repassa os argumentos para System.out.println, podemos dizer que a responsabilidade de Objeto também é “imprimir coisas na tela”. Ao fazer com que todas as classes do sistema herdem de Objeto, você está dizendo que todas as classes também são responsáveis por isso (ou seja, elas estão “acumulando funções”, pois cada classe já tem sua própria responsabilidade, e você está dando mais uma para elas) - isso tudo, claro, ignorando o fato de que os métodos são estáticos e na verdade não está tendo uma herança de fato (como já foi bem explicado anteriormente pelo @staroski).

Por fim, se der uma olhada na documentação de PrintStream, verá que existem vários métodos para imprimir (print, printf, write, etc), sem contar que print e println possuem várias versões diferentes (que recebem um int, double, array, etc), e isso pode dar diferença, veja:

public class Objeto {
    public static void println(Object obj) {
        System.out.println(obj);
    }
    public static void println(String s) {
        System.out.println(s);
    }
}

// dentro do main
char[] c = "abc".toCharArray();
Objeto.println(c);
System.out.println(c);

Apesar de parecer que vai imprimir a mesma coisa duas vezes, na verdade o código acima imprime o seguinte (a primeira linha pode variar o valor, mas será algo parecido):

[C@15db9742
abc

Isso porque println tem uma versão que recebe um array de char, e imprime os caracteres dele. Mas como você não definiu esta versão em Objeto, o que acaba sendo chamado é a primeira versão do método (o Java tenta encontrar algum método cuja assinatura seja compatível, e como não há nenhum que recebe um char [], ele acaba caindo na versão que recebe Object).

E a versão de println que recebe Object acaba chamando String.valueOf, que por sua vez chama o método toString do objeto em questão - e no caso de arrays, o retorno é esse “texto maluco” aí de cima ([C@15db9742 - veja aqui a explicação deste “código”).

Outro caso que dá diferença:

Objeto.println(null); // imprime "null"
System.out.println(null); // não compila!

Claro que para vários casos (int, String's, objetos que implementam toString, etc) vai funcionar, mas se você não incluir todas as versões de println no seu Objeto, podem ter esses corner cases.

Mas tudo isso pode ser evitado se você simplesmente não usar herança onde ela não é adequada :slight_smile:

2 curtidas

É verdade, falta de atenção minha.
Obrigado.
:smiley:

1 curtida

Cada classe responsável por fazer alguma coisa, ok.

A partir da minha dúvida, comecei a fazer testes de herança com as classes Objeto, Usuario e Capitulo6g (o static foi completamente desnecessário). Os testes de impressão, inclusive com lambda e method reference saíram conforme esperado.

Se toda classe herda implicitamente de Object, acredito que seria mais interessante que todas as classes pudessem permitir entrada e saída de dados a partir de Object, deixando a programação em Java mais atrativa. Tendo Python exemplo, você consegue usar input() e print() facilmente.

Acredito que Java poderia melhorar nesse ponto deixando que Object chame System sem quebrar o código existente.

O import static foi desnecessário pois você forçou uma herança desnecessária.

É acúmulo de responsabilidades.
Quantas classes você cria que realmente tem a responsabilidade de solicitar entrada e saída de dados?
Herança não é pra isso.
Recomendo que dê uma estudada em princípios SOLID.

Todas as classes se quiserem podem chamar System.out.println(), então não é acúmulo de responsabilidade deixar a classe Object ter métodos para fazer a mesma tarefa, uma vez que outras linguagens permitem de forma natural chamar o método print().

Só em Java que tem que fazer toda uma cerimônia pra imprimir ou para usar a entrada de dados, poderia resolver com um simples print() e input().

Não é possível que ninguém tenha se perguntado o porquê essa cerimônia toda.

Em Python, print e input são built-ins - funções pré-definidas que não estão atreladas a nenhuma classe (podemos pensar que estão definidas “na própria linguagem”, mesmo não sendo uma descrição tecnicamente acurada). Por isso dá pra chamá-las diretamente, sem precisar referenciar uma classe.

Mas Java não foi definida assim, não existem funções built-in da mesma forma que em Python, e todo e qualquer método tem que pertencer a uma classe.

Podemos até questionar se foi uma decisão “inteligente” e tal, mas acredito que agora é tarde pra mudar isso. Historicamente a linguagem sempre optou pela retrocompatibilidade “a todo custo” (na minha opinião, muitas vezes exagerada até) e acho que permitir built-ins similares aos do Python exigiria uma mudança grande demais na estrutura da linguagem.


É acúmulo de responsabilidade sim, todos os métodos que hoje estão em PrintStream teriam que estar em Object e todas as classes herdariam tudo isso. Ou seja todas as classes teriam a responsabilidade de fazer o que elas foram projetadas para fazer mais a responsabilidade de fazer I/O.

Se levar esse raciocínio ao extremo: veja a lista de todas as funções built-in do Python. Tem de tudo ali, desde I/O (print, input, format, open), passando por funções matemáticas (abs, divmod), manipulação de caracteres (chr, ord), manipulação de listas (map, filter) e muito mais. Por que não colocar tudo isso em Object também? Assim todas as classes saberiam como fazer operações matemáticas, manipular listas, caracteres, converter dados entre diferentes tipos, e até mesmo chamar o sistema de Help. E tudo isso sem precisar importar nada, economizando milhares de caracteres digitados! :slight_smile:

Não dá. Linguagens diferentes possuem ideias e “filosofias” diferentes, são construídas de maneiras diferentes e o que funciona bem para uma pode não funcionar tão bem - ou até ser inviável - em outra.

2 curtidas