diff --git a/README.md b/README.md index 35b8f98..5d0d3e4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Todos os exemplos no diretório `notebook` são preparados para o ambiente Jupyt ## Abrir branch específico em uma instância do [binderhub](https://github.com/jupyterhub/binderhub) * Última versão testada e estável: - [![launch @ mybinder.org][badge-jupyterlab-mybinder-org]](https://mybinder.org/v2/gh/santanche/java2learn/v1.0.5?urlpath=lab) + [![launch @ mybinder.org][badge-jupyterlab-mybinder-org]](https://mybinder.org/v2/gh/santanche/java2learn/v1.0.6?urlpath=lab) * Última versão disponível: [![launch @ mybinder.org][badge-jupyterlab-mybinder-org]](https://mybinder.org/v2/gh/santanche/java2learn/master?urlpath=lab) diff --git a/notebooks/pt/c02oo/s11abstrata/s02resolucoes/abstrata-poligono.ipynb b/notebooks/pt/c02oo/s11abstrata/s02resolucoes/abstrata-poligono.ipynb new file mode 100644 index 0000000..188395e --- /dev/null +++ b/notebooks/pt/c02oo/s11abstrata/s02resolucoes/abstrata-poligono.ipynb @@ -0,0 +1,459 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Classe Abstrata\n", + "\n", + "## Retomando o exemplo do Polígono\n", + "\n", + "Estude novamente o exemplo de polígono a seguir, que foi trabalhado na aula de polimorfismo. Observe que na classe `Poligono` o método `getArea()` não faz sentido, já que se trata de uma abstração de polígono que não tem área. Entretanto, ele teve que ser declarado para permitir o polimorfismo desse método, ou seja, para que fosse possível declarar uma referência para `Poligono` e, mesmo assim, se realizar a chamada para o método `getArea()` nas subclasses." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "com.twosigma.beaker.javash.bkr67e631c2.Poligono" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "public class Poligono {\n", + " private int altura;\n", + " private int largura;\n", + "\n", + " \n", + " public Poligono(int altura, int largura) {\n", + " this.altura = altura;\n", + " this.largura = largura;\n", + " }\n", + " \n", + " public int getAltura() {\n", + " return altura;\n", + " }\n", + " \n", + " public int getLargura() {\n", + " return largura;\n", + " }\n", + " \n", + " public float getArea() {\n", + " return 0;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "com.twosigma.beaker.javash.bkr67e631c2.TrianguloRetangulo" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "public class TrianguloRetangulo extends Poligono {\n", + " public TrianguloRetangulo(int altura, int largura) {\n", + " super(altura, largura);\n", + " }\n", + " \n", + " public float getArea() {\n", + " return getAltura() * getLargura() / 2;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "com.twosigma.beaker.javash.bkr67e631c2.Retangulo" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "public class Retangulo extends Poligono {\n", + " public Retangulo(int altura, int largura) {\n", + " super(altura, largura);\n", + " }\n", + " \n", + " public float getArea() {\n", + " return getAltura() * getLargura();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Área do triangulo retângulo: 30.0\n", + "Área do retângulo: 60.0\n" + ] + }, + { + "data": { + "text/plain": [ + "null" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Poligono tr = new TrianguloRetangulo(6, 10);\n", + "Poligono rt = new Retangulo(6, 10);\n", + "\n", + "System.out.println(\"Área do triangulo retângulo: \" + tr.getArea());\n", + "System.out.println(\"Área do retângulo: \" + rt.getArea());" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Usando uma Classes Abstratas e Métodos Abstratos\n", + "\n", + "Essa estratégia de declarar métodos na superclasse que só serão definidos nas subclasses é comum. Nesses casos, a superclasse é tida como uma abstração, que declara interfaces de métodos potenciais, cuja implementação faz sentido nas subclasses.\n", + "\n", + "Para esses casos o Java dispõe do recurso de **Classe Abstrata**.\n", + "\n", + "Uma classe abstrata é declarada usando-se a cláusula `abstract` antes do `class`. No caso do `Poligono` seria:\n", + "~~~java\n", + "public abstract class Poligono\n", + "~~~\n", + "\n", + "Uma classe abstrata ganha a possibilidade de declarar **Métodos Abstratos**, que são métodos apneas com a assinatura mas sem implementação. Esses métodos serão obrigatoriamente implementados pelos herdeiros, a menos que os herdeiros também os declare como abstratos, repassando a responsabilidade para a geração seguinte (nesse caso as respectivas classes herdeiras também serão abstratas).\n", + "\n", + "O método `getArea()` pode se tornar abstrato da seguinte maneira:\n", + "\n", + "~~~java\n", + "public abstract float getArea();\n", + "~~~\n", + "\n", + "Apenas as classes abstratas podem ter métodos abstratos.\n", + "\n", + "Nenhuma classe abstrata pode ser instanciada, ou seja, todos os métodos abstratos terão que ser obrigatoriamente implementados por alguém para que se possa instanciar a respectiva classe.\n", + "\n", + "Veja abaixo como o `Poligono` foi reimplementado:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "com.twosigma.beaker.javash.bkr67e631c2.Poligono" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "public abstract class Poligono {\n", + " private int altura;\n", + " private int largura;\n", + "\n", + " \n", + " public Poligono(int altura, int largura) {\n", + " this.altura = altura;\n", + " this.largura = largura;\n", + " }\n", + " \n", + " public int getAltura() {\n", + " return altura;\n", + " }\n", + " \n", + " public int getLargura() {\n", + " return largura;\n", + " }\n", + " \n", + " public abstract float getArea();\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "com.twosigma.beaker.javash.bkr67e631c2.TrianguloRetangulo" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "public class TrianguloRetangulo extends Poligono {\n", + " public TrianguloRetangulo(int altura, int largura) {\n", + " super(altura, largura);\n", + " }\n", + " \n", + " public float getArea() {\n", + " return getAltura() * getLargura() / 2;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "com.twosigma.beaker.javash.bkr67e631c2.Retangulo" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "public class Retangulo extends Poligono {\n", + " public Retangulo(int altura, int largura) {\n", + " super(altura, largura);\n", + " }\n", + " \n", + " public float getArea() {\n", + " return getAltura() * getLargura();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Área do triangulo retângulo: 30.0\n", + "Área do retângulo: 60.0\n" + ] + }, + { + "data": { + "text/plain": [ + "null" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Poligono tr = new TrianguloRetangulo(6, 10);\n", + "Poligono rt = new Retangulo(6, 10);\n", + "\n", + "System.out.println(\"Área do triangulo retângulo: \" + tr.getArea());\n", + "System.out.println(\"Área do retângulo: \" + rt.getArea());" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementando funcionalidades no nível asbtrato\n", + "\n", + "É possível se pensar em funcionalidades no nível abstrato que se baseiam na previsão do que será implementado nas subclasses.\n", + "\n", + "A seguir é apresentada a classe `ListStr` que representa uma lista de forma abstrata as seguintes duas operações:\n", + "* **`first()`** - se desloca para a primeira posição da lista e retorna o primeiro elemento (retorna nulo se não houver primeiro);\n", + "* **`next()`** - se desloca para a próxima posição da lista e retorna o próximo elemento (retorna nulo se não houver próximo).\n", + "\n", + "Note que foi implementado um método `list()` que parte do pré-suposto de que existe uma implementação de `first()` e `next()`. Isso faz com que você possa implementar a lista de várias maneiras e o método `list()` se adapte automaticamente às implementações dos herdeiros.\n", + "\n", + "## Exercício\n", + "\n", + "Implemente uma classe herdeita de `ListStr` (não abstrata) que armazene uma lista de dados e implemente os métodos `first()` e `next()`. Escreva uma sequência de instruções que use a sua lista." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "com.twosigma.beaker.javash.bkrb3de627e.ListStr" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "public abstract class ListStr {\n", + " \n", + " public abstract void add(String element);\n", + " \n", + " public abstract String first();\n", + " \n", + " public abstract String next();\n", + " \n", + " public void list() {\n", + " String element = first();\n", + " \n", + " while (element != null) {\n", + " System.out.println(element);\n", + " element = next();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "com.twosigma.beaker.javash.bkrb3de627e.List" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "public class List extends ListStr {\n", + " private String content[];\n", + " private int last = -1;\n", + " private int current = 0;\n", + "\n", + " public List(int size) {\n", + " content = new String[size];\n", + " }\n", + "\n", + " public List() {\n", + " this(10);\n", + " }\n", + "\n", + " public void add(String element) {\n", + " if (last < content.length-1) {\n", + " last++;\n", + " content[last] = element;\n", + " }\n", + " }\n", + "\n", + " public String first() {\n", + " current = 0;\n", + " return (current <= last) ? content[current] : null;\n", + " }\n", + "\n", + " public String next() {\n", + " current++;\n", + " return (current <= last) ? content[current] : null;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quincas\n", + "Doriana\n", + "Asdrubal\n", + "Melissa\n", + "Alcebiades\n" + ] + }, + { + "data": { + "text/plain": [ + "null" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ListStr l = new List(5);\n", + "l.add(\"Quincas\");\n", + "l.add(\"Doriana\");\n", + "l.add(\"Asdrubal\");\n", + "l.add(\"Melissa\");\n", + "l.add(\"Alcebiades\");\n", + "l.list();" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Java", + "language": "java", + "name": "java" + }, + "language_info": { + "codemirror_mode": "text/x-java", + "file_extension": ".java", + "mimetype": "", + "name": "Java", + "nbconverter_exporter": "", + "version": "11.0.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/pt/c02oo/s12interface/s01aula-tarefa/interface-poligono.ipynb b/notebooks/pt/c02oo/s12interface/s01aula-tarefa/interface-poligono.ipynb index 5bcb763..fc31fdd 100644 --- a/notebooks/pt/c02oo/s12interface/s01aula-tarefa/interface-poligono.ipynb +++ b/notebooks/pt/c02oo/s12interface/s01aula-tarefa/interface-poligono.ipynb @@ -27,7 +27,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Retangulo" + "com.twosigma.beaker.javash.bkr0a487407.Retangulo" ] }, "execution_count": 1, @@ -71,7 +71,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Circulo" + "com.twosigma.beaker.javash.bkr0a487407.Circulo" ] }, "execution_count": 2, @@ -179,7 +179,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Geometria" + "com.twosigma.beaker.javash.bkr0a487407.Geometria" ] }, "execution_count": 4, @@ -202,7 +202,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Retangulo" + "com.twosigma.beaker.javash.bkr0a487407.Retangulo" ] }, "execution_count": 5, @@ -246,7 +246,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Circulo" + "com.twosigma.beaker.javash.bkr0a487407.Circulo" ] }, "execution_count": 6, @@ -370,7 +370,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.EmprestimoSimples" + "com.twosigma.beaker.javash.bkr0a487407.EmprestimoSimples" ] }, "execution_count": 8, @@ -409,7 +409,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.EmprestimoComposto" + "com.twosigma.beaker.javash.bkr0a487407.EmprestimoComposto" ] }, "execution_count": 9, @@ -522,7 +522,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Retangular" + "com.twosigma.beaker.javash.bkr0a487407.Retangular" ] }, "execution_count": 11, @@ -546,7 +546,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.TrianguloRetangulo" + "com.twosigma.beaker.javash.bkr0a487407.TrianguloRetangulo" ] }, "execution_count": 12, @@ -577,7 +577,7 @@ " }\n", " \n", " public boolean sameProportions(Retangular toCompare) {\n", - " return (largura / altura == toCompare.getLargura() / toCompare.getLargura());\n", + " return (largura / altura == toCompare.getLargura() / toCompare.getAltura());\n", " }\n", "}" ] @@ -590,7 +590,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Retangulo" + "com.twosigma.beaker.javash.bkr0a487407.Retangulo" ] }, "execution_count": 13, @@ -625,7 +625,7 @@ " }\n", " \n", " public boolean sameProportions(Retangular toCompare) {\n", - " return (largura / altura == toCompare.getLargura() / toCompare.getLargura());\n", + " return (largura / altura == toCompare.getLargura() / toCompare.getAltura());\n", " }\n", "}" ] @@ -692,7 +692,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Geometria" + "com.twosigma.beaker.javash.bkr0a487407.Geometria" ] }, "execution_count": 15, @@ -715,7 +715,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Retangular" + "com.twosigma.beaker.javash.bkr0a487407.Retangular" ] }, "execution_count": 16, @@ -739,7 +739,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Retangulo" + "com.twosigma.beaker.javash.bkr0a487407.Retangulo" ] }, "execution_count": 17, @@ -748,7 +748,7 @@ } ], "source": [ - "public class Retangulo implements Geometria, Retangular {\n", + "public class Retangulo implements Retangular {\n", " private int altura;\n", " private int largura;\n", "\n", @@ -774,7 +774,7 @@ " }\n", " \n", " public boolean sameProportions(Retangular toCompare) {\n", - " return (largura / altura == toCompare.getLargura() / toCompare.getLargura());\n", + " return (largura / altura == toCompare.getLargura() / toCompare.getAltura());\n", " }\n", "}" ] @@ -787,7 +787,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.TrianguloRetangulo" + "com.twosigma.beaker.javash.bkr0a487407.TrianguloRetangulo" ] }, "execution_count": 18, @@ -822,7 +822,7 @@ " }\n", " \n", " public boolean sameProportions(Retangular toCompare) {\n", - " return (largura / altura == toCompare.getLargura() / toCompare.getLargura());\n", + " return (largura / altura == toCompare.getLargura() / toCompare.getAltura());\n", " }\n", "}" ] @@ -894,7 +894,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Geometria" + "com.twosigma.beaker.javash.bkr0a487407.Geometria" ] }, "execution_count": 20, @@ -917,7 +917,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Retangular" + "com.twosigma.beaker.javash.bkr0a487407.Retangular" ] }, "execution_count": 21, @@ -941,7 +941,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.GeometriaRetangular" + "com.twosigma.beaker.javash.bkr0a487407.GeometriaRetangular" ] }, "execution_count": 22, @@ -962,7 +962,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Circulo" + "com.twosigma.beaker.javash.bkr0a487407.Circulo" ] }, "execution_count": 23, @@ -1002,7 +1002,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.Retangulo" + "com.twosigma.beaker.javash.bkr0a487407.Retangulo" ] }, "execution_count": 24, @@ -1037,7 +1037,7 @@ " }\n", " \n", " public boolean sameProportions(Retangular toCompare) {\n", - " return (largura / altura == toCompare.getLargura() / toCompare.getLargura());\n", + " return (largura / altura == toCompare.getLargura() / toCompare.getAltura());\n", " }\n", "}" ] @@ -1050,7 +1050,7 @@ { "data": { "text/plain": [ - "com.twosigma.beaker.javash.bkrf6ac97ad.TrianguloRetangulo" + "com.twosigma.beaker.javash.bkr0a487407.TrianguloRetangulo" ] }, "execution_count": 25, @@ -1081,7 +1081,7 @@ " }\n", " \n", " public boolean sameProportions(Retangular toCompare) {\n", - " return (largura / altura == toCompare.getLargura() / toCompare.getLargura());\n", + " return (largura / altura == toCompare.getLargura() / toCompare.getAltura());\n", " }\n", "}" ] diff --git a/src/src/pt/c02oo/s12interface/s03poligono/Retangulo.java b/src/src/pt/c02oo/s12interface/s03poligono/Retangulo.java index 0fbaf29..c4605f0 100644 --- a/src/src/pt/c02oo/s12interface/s03poligono/Retangulo.java +++ b/src/src/pt/c02oo/s12interface/s03poligono/Retangulo.java @@ -26,6 +26,6 @@ public float getArea() { } public boolean sameProportions(Retangular toCompare) { - return (largura / altura == toCompare.getLargura() / toCompare.getLargura()); + return (largura / altura == toCompare.getLargura() / toCompare.getAltura()); } } diff --git a/src/src/pt/c02oo/s12interface/s03poligono/TrianguloRetangulo.java b/src/src/pt/c02oo/s12interface/s03poligono/TrianguloRetangulo.java index d9ec29d..f397eb4 100644 --- a/src/src/pt/c02oo/s12interface/s03poligono/TrianguloRetangulo.java +++ b/src/src/pt/c02oo/s12interface/s03poligono/TrianguloRetangulo.java @@ -22,6 +22,6 @@ public float getArea() { } public boolean sameProportions(Retangular toCompare) { - return (largura / altura == toCompare.getLargura() / toCompare.getLargura()); + return (largura / altura == toCompare.getLargura() / toCompare.getAltura()); } } \ No newline at end of file diff --git a/src/src/pt/c02oo/s12interface/s04poligono/Retangulo.java b/src/src/pt/c02oo/s12interface/s04poligono/Retangulo.java index 209a155..74d8fb1 100644 --- a/src/src/pt/c02oo/s12interface/s04poligono/Retangulo.java +++ b/src/src/pt/c02oo/s12interface/s04poligono/Retangulo.java @@ -26,6 +26,6 @@ public float getArea() { } public boolean sameProportions(Retangular toCompare) { - return (largura / altura == toCompare.getLargura() / toCompare.getLargura()); + return (largura / altura == toCompare.getLargura() / toCompare.getAltura()); } } diff --git a/src/src/pt/c02oo/s12interface/s04poligono/TrianguloRetangulo.java b/src/src/pt/c02oo/s12interface/s04poligono/TrianguloRetangulo.java index 4c78ca0..f7b524c 100644 --- a/src/src/pt/c02oo/s12interface/s04poligono/TrianguloRetangulo.java +++ b/src/src/pt/c02oo/s12interface/s04poligono/TrianguloRetangulo.java @@ -26,6 +26,6 @@ public float getArea() { } public boolean sameProportions(Retangular toCompare) { - return (largura / altura == toCompare.getLargura() / toCompare.getLargura()); + return (largura / altura == toCompare.getLargura() / toCompare.getAltura()); } } \ No newline at end of file diff --git a/src/src/pt/c02oo/s12interface/s05poligono/Retangulo.java b/src/src/pt/c02oo/s12interface/s05poligono/Retangulo.java index c133f25..9600495 100644 --- a/src/src/pt/c02oo/s12interface/s05poligono/Retangulo.java +++ b/src/src/pt/c02oo/s12interface/s05poligono/Retangulo.java @@ -26,6 +26,6 @@ public float getArea() { } public boolean sameProportions(Retangular toCompare) { - return (largura / altura == toCompare.getLargura() / toCompare.getLargura()); + return (largura / altura == toCompare.getLargura() / toCompare.getAltura()); } } diff --git a/src/src/pt/c02oo/s12interface/s05poligono/TrianguloRetangulo.java b/src/src/pt/c02oo/s12interface/s05poligono/TrianguloRetangulo.java index 11617fa..6b81def 100644 --- a/src/src/pt/c02oo/s12interface/s05poligono/TrianguloRetangulo.java +++ b/src/src/pt/c02oo/s12interface/s05poligono/TrianguloRetangulo.java @@ -22,6 +22,6 @@ public float getArea() { } public boolean sameProportions(Retangular toCompare) { - return (largura / altura == toCompare.getLargura() / toCompare.getLargura()); + return (largura / altura == toCompare.getLargura() / toCompare.getAltura()); } } \ No newline at end of file