JavaRush /Java Blog /Random-KO /메소드를 효과적으로 작성하는 방법(기사 번역)
pena
레벨 41
Москва

메소드를 효과적으로 작성하는 방법(기사 번역)

Random-KO 그룹에 게시되었습니다
원본 기사는 다음 위치에 있습니다: http://www.javacodegeeks.com/2015/09/how-to-write-methods- Effectively.html#download 튜토리얼 게시자: Andrey Redko(Andrey Redko) in Core Java(Java Core) 2015년 9월 18일 이 노트는 아카데미의 고급 Java 과정의 일부입니다. 이 과정은 Java를 보다 효과적으로 사용할 수 있도록 고안되었습니다. 여기에서는 객체 생성, 병렬화, 직렬화, 리플렉션 등과 같은 고급 주제를 논의합니다. 이 지식은 Java 숙달의 정점으로 향하는 여정을 안내할 것입니다.
코스 내용
1. 소개 2. 메소드 서명 3. 메소드 본문 4. 메소드 오버로딩 5. 메소드 오버라이딩 6. 인라인 7. 재귀 8. 메소드 참조 9. 불변성 10. 메소드 문서화 11. 메소드 매개변수 및 반환 값 12. 메소드로서의 메소드 부록 13. 다음 단계의 진입점 14. 소스 코드 다운로드
1. 소개
튜토리얼의 이 섹션에서는 Java에서 메소드를 설계하고 구현하는 것과 관련된 다양한 측면을 논의하는 데 시간을 할애할 것입니다. 튜토리얼의 이전 부분에서 Java로 메소드를 작성하는 것이 매우 쉽다는 것을 알 수 있었지만 메소드를 더 읽기 쉽고 효율적으로 만들 수 있는 방법이 많이 있습니다.
2. 메소드 서명
이미 알고 있듯이 Java는 객체지향 언어입니다. 기본적으로 모든 Java 메소드는 클래스의 일부(또는 통계 메소드의 경우 클래스 자체)에 속합니다. 가시성(또는 접근성) 규칙이 있고 추상 또는 최종으로 선언될 수 있습니다. 그러나 아마도 메소드의 가장 중요한 부분은 메소드의 시그니처일 것입니다. 즉, 반환 유형 및 인수, 그리고 각 메소드 구현에서 발생할 수 있는 확인된 예외 목록입니다(그러나 이 부분은 과거에는 자주 사용되지 않았으며 요즘에는 훨씬 더 자주 사용되지 않습니다). ). 작은 예부터 시작해 보겠습니다. 1 public static void main( String[] args ) { 2 // Some implementation here 3 } 기본 메서드는 문자열 배열을 args 인수로만 사용하고 아무것도 반환하지 않습니다. 모든 메서드를 기본 메서드처럼 간단하게 만드는 것이 매우 좋을 수 있습니다. 그러나 실제로는 메서드 서명을 읽을 수 없게 될 수도 있습니다. 다음 예를 살펴보겠습니다. 1 public void setTitleVisible( int lenght, String title, boolean visible ) { 2 // Some implementation here 3 } 여기서 가장 먼저 눈에 띄는 것은 규칙이 setTitleVisible과 같은 Java 메소드 이름에 기본적으로 사용된다는 것입니다. 이름은 잘 선택되었으며 메소드가 수행해야 하는 작업을 설명하려고 합니다. 둘째, 인수의 이름은 해당 인수의 목적이 무엇인지 알려줍니다(또는 적어도 암시합니다). int i, String s, boolean f 대신 메소드 인수에 대한 정확하고 의미 있는 이름을 찾는 것이 매우 중요합니다(그러나 매우 드문 경우에 이것이 의미가 있음). 셋째, 이 메서드에는 세 개의 인수만 있습니다. Java에는 허용되는 인수 수에 대한 제한이 훨씬 높지만 6개보다 큰 인수 수를 초과하지 않는 것이 좋습니다. 이 제한을 초과하면 서명을 이해하기 어려워집니다. Java 5가 출시되었으므로 메소드는 동일한 유형(varargs라고 함)의 다양한 인수 목록을 가질 수 있으며 특수 구문을 사용할 수 있습니다. 예를 들어, 1 public void find( String … elements ) { 2 // Some implementation here 3 } 내부적으로 Java 컴파일러는 가변 인수를 적절한 유형의 배열로 변환하므로 가변 인수는 다음과 같이 변환될 수 있습니다. 방법을 구현하기 위해 채택되었습니다. 흥미롭게도 Java에서는 일반 매개변수를 사용하여 varargs를 선언할 수도 있습니다. 그러나 인수 유형을 알 수 없기 때문에 Java 컴파일러는 varargs가 올바르게 사용되는지 확인하고 최종 메소드에 @SafeVarargs 주석을 달도록 조언합니다(자세한 내용은 튜토리얼의 5부, 사용 방법 및 시기 참조). Enums 및 Annotations). 우리는 Enumerations 및 Comments를 사용합니다. 예를 들면 다음과 같습니다. 1 @SafeVarargs 2 final public< T > void find( T ... elements ) { 3 // Some implementation here 4 } Другой ближайший путь это использовать @SuppressWarnings комментарии, например 1 @SuppressWarnings( "unchecked" ) 2 public< T > void findSuppressed( T ... elements ) { 3 // Some implementation here 4 } Следующий пример демонстрирует использование проверки исключений How части сигнатуры метода. В недалеком прошлом проверка исключений показала себя не настолько полезной, Howой она предполагалась быть, в результате шаблонный code был использован скорее для записи, чем для решения проблем. 1 public void write( File file ) throws IOException { 2 // Some implementation here 3 } Последнее, но, тем не менее, важное, How правило, рекомендуется (но редко используется), отметить аргументы метода, How final. Это поможет избавиться от практики написания плохого codeа, когда аргументы метода предназначены различным значениям. Кроме того, такие аргументы метода могут быть использованы анонимными классами (подробнее об анонимных классов рассматривается в части 3 учебника, , How to design Classes and Interfaces (Как проектировать Классы и Интерфейсы)), хотя Java 8 облегчила немного это ограничение путем введения эффективных final переменных.
3. Тело метода
Каждый метод имеет свою реализацию и цель существования. Однако, имеется пара общих рекомендаций которые реально помогают написанию ясных и понятных методов. Вероятно, наиболее важный принцип - это принцип единичной ответственности: нужно пытаться реализовать метод таким путем, чтобы каждый единичный метод делал что-то одно, и делал это хорошо. Следуя этому принципу возможно раздувание количества методов класса, и важно найти правильный баланс. Другая важная вещь в процессе codeирования и проектирования - это делать реализуемые методы короткими. Для коротких методов легко понять причину, по которой они сделаны, плюс они обычно умещаются на экран, и таким образом могут быть очень быстро поняты читателем вашего codeа. Последний по порядку (но не по значению) совет связан с использованием return операторов. Если метод возвращает некоторое meaning, пытайтесь минимизировать число мест, где return meaning было бы вызвано (некоторые люди идут даже дальше и рекомендуют использовать лишь единичное return meaning во всех случаях. Чем больше return значений имеет метод, тем более тяжело становится следовать его логике и модифицировать (or оптимизировать) реализацию.
4. Перегрузка метода
Техника перегрузки методов часто используется, чтобы обеспечить специализацию версий метода для различных типов аргументов or их комбинаций. Хотя Name метода одинаковое компьютер выбирает правильную альтернативу, углубляясь в текущие значения аргументов в точке вызова (лучший пример перегрузки это конструкторы Java: Name всегда одинаковое, но аргументы разные) or вызывает ошибку компилятора, если такой вариант метода не найден. Например: 1 public String numberToString( Long number ) { 2 return Long.toString( number ); 3 } 4 5 public String numberToString( BigDecimal number ) { 6 return number.toString(); 7 } Перегрузка метода отчасти близка к дженерикам (больше информации о дженериках можно найти в части 4 учебника How and when to use Generics (Как и когда использовать дженерики)), однако перегрузка используется в случае, где подход с использованием дженериков не работает хорошо и каждый or большинство типов аргументов, которые являются дженериками, требуют своих собственных специализированных реализаций. Тем не менее, комбинируя оба способа дженерики и перегрузку можно быть очень производительным, но часто это невозможно в Java, потому что тип стирается (больше информации в части 4 учебника How and when to use Generics (Как и когда использовать дженерики)). Давайте взглянем на пример: 1 public< T extends Number > String numberToString( T number ) { 2 return number.toString(); 3 } 4 5 public String numberToString( BigDecimal number ) { 6 return number.toPlainString(); 7 } Хотя этот кусок codeа мог быть написан без использования дженериков, это неважно для наших демонстрационных целей. Интересно, что метод numberToString перегружен специальной реализацией BigDecimal и version на дженериках предназначена для всех остальных чисел.
5. Переопределение метода
Мы много говорor о переопределении методов в части 3 учебника (How to design Classes and Interfaces (Как проектировать классы и интерфейсы). В этом разделе, когда мы уже знаем о перегрузке методов, мы собираемся показать, почему использование @Override аннотации так важно. Наш пример продемонстрирует тонкое различие между переопределением метода и перегрузкой метода в простой иерархии классов. 1 public class Parent { 2 public Object toObject( Number number ) { 3 return number.toString(); 4 } 5 } Родительский класс имеет только один метод toObject. Давайте создадим подкласс этого класса и попытаемся придумать версию метода преобразования чисел в строки (instead of необработанных an objectов). 1 public class Child extends Parent { 2 @Override 3 public String toObject( Number number ) { 4 return number.toString(); 5 } 6 } Тем не менее, сигнатура метода toObject в дочернем классе немногим отличается (см Covariant method return types (Ковариантные типы возвращаемые методами) для более подробной информации), и это делает переопределение его из суперкласса в свой класс, при этом компилятор Java не выдает ниHowих ошибок и предупреждений. Теперь, давайте добавим еще один метод к дочернему классу. 1 public class Child extends Parent { 2 public String toObject( Double number ) { 3 return number.toString(); 4 } 5 } Опять же, есть только небольшая разница в сигнатуре метода (Double instead of Number), но то, что в данном случае это перегруженная version метода, не отменяет переопределения метода родителя. То есть, когда подсказка от компилятора Java и @Override аннотации перекрываются: метод с аннотацией из последнего примера с @Override вызовет ошибку компилятора.
6. Встраивание
Встраивание - это оптимизация, осуществляемая с помощью Java JIT (точно в срок) компилятора для того, чтобы устранить конкретный вызов метода и заменить его непосредственно реализацией метода. Использование компилятора JIT эвристики зависит от двух вещей - How часто метод вызывается в настоящее время, а также от того, насколько он большой. Методы, которые слишком велики, не могут быть эффективно встроены. Встраивание может обеспечить значительный прирост производительности codeа и преимущество хранения методов короткими, How мы уже обсуждали в разделе Method body (Тело метода).
7. Рекурсия
Рекурсия в Java - это техника, где метод вызывает сам себя, выполняя расчеты. Например, давайте взглянем на следующий пример, который суммирует число массива: 1 public int sum( int[] numbers ) { 2 if( numbers.length == 0 ) { 3 return 0; 4 } if( numbers.length == 1 ) { 5 return numbers[ 0 ]; 6 } else { 7 return numbers[ 0 ] + sum( Arrays.copyOfRange( numbers, 1, numbers.length ) ); 8 } 9 } Это очень неэффективная реализация, однако она демонстрирует рекурсию достаточно хорошо. Существует одна хорошо известная проблема с рекурсивными методами: в зависимости, насколько глубока цепь вызовов, они могут переполнить стек и вызвать исключение StackOverflowError. Но не все так плохо, How кажется, потому что есть техника, которая может устранить переполнение стека, называемая tail call optimization (оптимизация хвоста вызова). Она может быть применена, если метод с хвостовой рекурсией (методы с хвостовой рекурсией это методы, в которых все рекурсивные вызовы это хвостовые вызовы). Например, давайте перепишем предыдущий алгоритм с использованием в хвостовой рекурсии: 01 public int sum( int initial, int[] numbers ) { 02 if( numbers.length == 0 ) { 03 return initial; 04 } if( numbers.length == 1 ) { 05 return initial + numbers[ 0 ]; 06 } else { 07 return sum( initial + numbers[ 0 ], 08 Arrays.copyOfRange( numbers, 1, numbers.length ) ); 09 } 10 } К сожалению, на данный момент компилятор Java (а также компилятор JVM JIT) не поддерживает tail call optimization хвостовую оптимизация, но все-таки это очень полезная техника, и ее надо знать и принимать во внимание, когда вы пишете рекурсивные алгоритмы в Java.
8. Ссылки методов
В Java 8 сделан огромный шаг вперед, путем введения функциональных понятий в язык Java. Основание, которое трактует методы How данные, понятие, которое не поддерживалось в языке до этого (однако, с тех пор How выпущена Java 7, JVM и стандартная библиотека Java уже были некоторые наработки, чтобы сделать это возможным). К счастью, имея ссылки методов, теперь это возможно. Ссылка статического метода: SomeClass::staticMethodName Ссылка на метод экземпляра конкретного an object: someInstance::instanceMethodName Ссылка на метод экземпляра произвольного an object определенного типа: SomeType::methodName Ссылка на конструктор: SomeClass::new Давайте взглянем на небольшой пример того, How методы могут быть использованы в качестве аргументов других методов. 01 public class MethodReference { 02 public static void println( String s ) { 03 System.out.println( s ); 04 } 05 06 public static void main( String[] args ) { 07 final Collection< String > strings = Arrays.asList( "s1", "s2", "s3" ); 08 strings.stream().forEach( MethodReference::println ); 09 } 10 } В последней строке main метод использует ссылку на println метод чтобы напечатать каждый элемент из коллекции строк в консоль, он передается в качестве аргумента другому методу, forEach.
9. Неизменность
Неизменность обращает на себя много внимания в эти дни, и Java не является исключением. Хорошо известно, что неизменности трудно добиться в Java, но это не значит, что это должно быть проигнорировано. В Java, неизменность - это все знания об изменении внутреннего состояния. В качестве примера, давайте взглянем на спецификации JavaBeans (http://docs.oracle.com/javase/tutorial/javabeans/). В ней говорится, очень ясно, что сеттеры могут изменить состояние an object, что- то до этого содержащего, и это то, что ожидает каждый разработчик Java. Тем не менее, альтернативный подход мог бы не менять состояние, а возвращать новый an object (new) каждый раз. Это не так страшно, How кажется, и новый Java 8 Date/Time API ( разработан под JSR 310: Date and Time API прикрытием) является отличным примером этого. Давайте взглянем на следующий фрагмент codeа: 1 final LocalDateTime now = LocalDateTime.now(); 2 final LocalDateTime tomorrow = now.plusHours( 24 ); 3 4 final LocalDateTime midnight = now 5 .withHour( 0 ) 6 .withMinute( 0 ) 7 .withSecond( 0 ) 8 .withNano( 0 ); Каждый вызов LocalDateTime an object, который должен изменить свое состояние возвращает новый экземпляр LocalDateTime, и держит оригинал без изменений. Это большой сдвиг в парадигме дизайна API по сравнению с старыми Calendar и Date, (которые, мягко говоря, были не очень приятны в использовании и вызвали много головной боли).
10. Документирование метода
В Java, в частности, если вы разрабатываете Howую-то библиотеку or framework, все публичные методы должны быть заdocumentированы с помощью инструмента Javadoc (http://www.oracle.com/technetwork/articles/java/index-jsp-135444.html). Строго говоря, ничего не заставляет вас делать это, но хорошая documentация помогает другим разработчикам понять, что конкретный метод делает, Howие аргументы он требует, Howовы предположения or ограничения его реализации, Howие типы исключений он вызывает и когда они возникают, Howое может быть возвращаемое meaning (если таковые имеются), а также многие другие вещи. Давайте взглянем на следующий пример: 01 /** 02 * The method parses the string argument as a signed decimal integer. 03 * The characters in the string must all be decimal digits, except 04 * that the first character may be a minus sign {@code '-'} or plus 05 * sign {@code '+'}. 06 * 07 *

An exception of type {@code NumberFormatException} is thrown if 08 * string is {@code null} or has length of zero. 09 * 10 *

Examples: 11 *

12	 * parse( "0" ) returns 0
13	 * parse( "+42") returns 42
14	 * parse( "-2" ) returns -2
15	 * parse( "string" ) throws a NumberFormatException
16	 * 
17 * 18 * @param str a {@code String} containing the {@code int} representation to be parsed 19 * @return the integer value represented by the string 20 * @exception NumberFormatException if the string does not contain a valid integer value 21 */ 22 public int parse( String str ) throws NumberFormatException { 23 return Integer.parseInt( str ); 24 }
Это довольно многословная documentация для такого простого метода How parse, но это показывает пару полезных возможностей обеспечиваемых инструментом Javadoc tool, в том числе ссылки на другие классы, образцы фрагментов и продвинутого форматирования. Вот How этот documentация методов отражается в Eclipse, одной из популярных Java IDE. Просто глядя на изображение выше, любой разработчик Java от младшего до старшего уровня может понять цель метода и надлежащим образом использовать ее.
11. Параметры метода и возвращаемые значения
Документирование ваших методов - это великая вещь, но, к сожалению, это не предупреждает случаи, когда метод называют, используя неправильные or неожиданные значения аргументов. Из-за этого, How правило, все публичные методы должны подтвердить свои аргументы и никогда не должны быть уверены, что все время при вызове будут указаны правильные значения (паттерн более известный How sanity checks (санитарная проверка)). Возвращаясь к нашему примеру из предыдущего раздела, метод parse должен выполнить проверку своего единственного аргумента, прежде чем делать что-нибудь с ним: 1 public int parse( String str ) throws NumberFormatException { 2 if( str == null ) { 3 throw new IllegalArgumentException( "String should not be null" ); 4 } 5 6 return Integer.parseInt( str ); 7 } Java имеет другой вариант выполнения проверки и sanity checks, используя assert операторы. Однако, те, которые могли быть выключены во время выполнения и могут быть не выполнены. Предпочтительно, всегда выполнять такие проверки и вызывать соответствующие исключения. Даже имея documentированные методы и проверку их аргументов, хочу сделать еще пару замечаний связанных с возвращаемыми значениями. До того How вышла Java 8, самым простым способом сказать что метод в данное время не имеет значения чтобы его возвратить было просто вернуть нуль. Вот почему Java так плохо получить исключение NullPointerException. Java 8 пытается решить этот вопрос с введением Optional < T > class. Давайте взглянем на этот пример: 1 public< T > Optional< T > find( String id ) { 2 // Some implementation here 3 } Optional < T > предоставляет много полезных методов, и fully устраняет необходимость возвращать в методе null и загрязнять везде ваш code проверками на null. Единственное исключение, вероятно, это коллекции. Всякий раз, когда метод возвращает коллекцию, всегда лучше вернуть null instead of null (и даже Optional < T >), например: 1 public< T > Collection< T > find( String id ) { 2 return Collections.emptyList(); 3 }
12. Метод How точка входа в приложение
귀하가 조직에서 애플리케이션을 작성하는 단순한 개발자이거나 가장 널리 사용되는 Java 프레임워크 또는 라이브러리 중 하나에 기여하는 경우에도 귀하가 내리는 디자인 결정은 코드가 사용되는 방식에 매우 중요한 역할을 합니다. API 디자인 지침은 여러 권의 책만큼 가치가 있지만 튜토리얼의 이 부분에서는 그 중 많은 부분(메소드가 API의 진입점이 되는 방법)을 다루므로 빠른 개요를 보는 것이 매우 도움이 될 것입니다. 서명) 인수 수를 6개 미만으로 유지하십시오(메소드 서명 섹션) • 메소드를 짧고 읽기 쉽게 유지하십시오(메소드 본문 및 인라인 섹션) • 타당하다면 전제 조건 및 예제를 포함하여 공용 메소드를 항상 문서화하십시오(메소드 섹션) 문서) • 항상 인수 검사와 온전성 검사를 수행하십시오(메소드 매개변수 및 반환 값 섹션) • 반환 값으로 null을 피하십시오(메서드 매개변수 및 반환 값 섹션). 내부 상태에 영향을 주지 않음, 불변성 섹션) • 공개되어서는 안 되는 메소드를 숨기기 위해 가시성 및 접근성 규칙을 사용합니다(튜토리얼 3부, 클래스 및 인터페이스를 설계하는 방법).
13. 다음은 무엇입니까
튜토리얼의 이 부분에서는 언어로서의 Java에 대해 조금 덜 설명하고 Java 언어를 효과적으로 사용하는 방법, 특히 읽기 쉽고, 명확하고, 문서화되고 효율적인 메소드를 작성하는 방법에 대해 자세히 설명합니다. 다음 섹션에서는 동일한 기본 아이디어를 이어가며 더 나은 Java 개발자가 되는 데 도움이 되도록 고안된 일반적인 프로그래밍 원칙에 대해 논의할 것입니다.
14. 소스코드 다운로드
이번 수업은 메소드를 효과적으로 작성하는 방법에 관한 것이었습니다. 여기에서 소스 코드를 다운로드할 수 있습니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION