предисловие
Я разобрал соответствующие знания внутренних классов, которые являются относительно полными и базовыми.Я надеюсь, что все смогут учиться и прогрессировать вместе.
1. Что такое внутренний класс?
В Java определение одного класса может быть помещено внутри определения другого класса, т.внутренний класс. Сам внутренний класс является атрибутом класса и определяется так же, как и другие атрибуты.
Пример внутреннего класса:
public class Outer {
private int radius = 1;
public static int count = 2;
public Outer() {
}
class inner{
public void visitOuter() {
System.out.println("visit outer private member variable:" + radius);
System.out.println("visit outer static variable:" + count);
}
}
}
Типы внутренних классов
Существует четыре типа внутренних классов:Внутренние классы-члены, локальные внутренние классы, анонимные внутренние классы и статические внутренние классы.
статический внутренний класс
Статический класс, определенный внутри класса, является статическим внутренним классом.
public class Outer {
private static int radius = 1;
static class StaticInner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
}
}
}
Статический внутренний класс может получить доступ ко всем статическим переменным внешнего класса, но не к нестатическим переменным внешнего класса; способ создания статического внутреннего класса,new 外部类.静态内部类(),следующее:
Outer.StaticInner inner = new Outer.StaticInner();
inner.visit();
внутренний класс-член
Нестатический класс, определенный внутри класса и в позиции члена, является внутренним классом-членом.
public class Outer {
private static int radius = 1;
private int count =2;
class Inner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
System.out.println("visit outer variable:" + count);
}
}
}
Члены внутреннего класса могут получить доступ ко всем переменным и методам внешнего класса, включая статические и нестатические, частные и общедоступные. Внутренний класс члена зависит от экземпляра внешнего класса, как он создан外部类实例.new 内部类(),следующее:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.visit();
локальный внутренний класс
Внутренний класс, определенный в методе, является локальным внутренним классом.
public class Outer {
private int out_a = 1;
private static int STATIC_b = 2;
public void testFunctionClass(){
int inner_c =3;
class Inner {
private void fun(){
System.out.println(out_a);
System.out.println(STATIC_b);
System.out.println(inner_c);
}
}
Inner inner = new Inner();
inner.fun();
}
public static void testStaticFunctionClass(){
int d =3;
class Inner {
private void fun(){
// System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
System.out.println(STATIC_b);
System.out.println(d);
}
}
Inner inner = new Inner();
inner.fun();
}
}
Локальный класс, определенный в методе экземпляра, может получить доступ ко всем переменным и методам внешнего класса, а локальный класс, определенный в статическом методе, может получить доступ только к статическим переменным и методам внешнего класса. Как создать локальный внутренний класс в соответствующем методе,new 内部类(),следующее:
public static void testStaticFunctionClass(){
class Inner {
}
Inner inner = new Inner();
}
анонимный внутренний класс
Анонимные внутренние классы — это внутренние классы без имени, которые часто используются в повседневной разработке.
public class Outer {
private void test(final int i) {
new Service() {
public void method() {
for (int j = 0; j < i; j++) {
System.out.println("匿名内部类" );
}
}
}.method();
}
}
//匿名内部类必须继承或实现一个已有的接口
interface Service{
void method();
}
Помимо отсутствия имени, анонимные внутренние классы обладают следующими характеристиками:
- Анонимный внутренний класс должен наследоваться от абстрактного класса или реализовывать интерфейс.
- Анонимные внутренние классы не могут определять статические члены и статические методы.
- Когда формальный параметр метода, в котором он находится, должен использоваться анонимным внутренним классом, он должен быть объявлен окончательным.
- Анонимный внутренний класс не может быть абстрактным, он должен реализовывать все абстрактные методы унаследованного класса или реализованного интерфейса.
Как создать анонимный внутренний класс:
new 类/接口{
//匿名内部类实现部分
}
Преимущества внутренних классов
Почему мы должны использовать внутренние классы? Потому что он имеет следующие преимущества:
- Объект внутреннего класса может получить доступ к содержимому объекта внешнего класса, который его создал, включая частные данные!
- Внутренний класс не виден другим классам в том же пакете и имеет хорошую инкапсуляцию;
- Внутренний класс эффективно реализует «множественное наследование» и оптимизирует дефекты одиночного наследования Java.
- Анонимные внутренние классы могут легко определять обратные вызовы.
Объект внутреннего класса может получить доступ к содержимому объекта внешнего класса, который его создал, включая частные данные!
public class Outer {
private int radius = 1;
protected void test(){
System.out.println("我是外部类方法");
}
class Inner {
public void visit() {
System.out.println("访问外部类变量" + radius);
test();
}
}
}
Мы видим, что внутренний класс Inner может получить доступ к закрытой переменной radius или методу test внешнего класса Outer.
Внутренние классы не видны другим классам в том же пакете и имеют хорошую инкапсуляцию.
Когда внутренний класс украшен приватным, класс скрыт от внешнего мира. Когда внутренний класс реализует интерфейс и выполняет восходящее преобразование, реализация интерфейса скрыта от внешнего мира, что хорошо отражает инкапсуляцию.
//提供的接口
interface IContent{
String getContents();
}
public class Outer {
//私有内部类屏蔽实现细节
private class PContents implements IContent{
@Override
public String getContents() {
System.out.println("获取内部类内容");
return "内部类内容";
}
}
//对外提供方法
public IContent getIContent() {
return new PContents();
}
public static void main(String[] args) {
Outer outer=new Outer();
IContent a1=outer.getIContent();
a1.getContents();
}
}
Мы можем обнаружить, что внешний класс Outer предоставляет метод getIContent внешнему миру, использует внутренний класс для реализации деталей, а затем модифицирует внутренний класс с помощью private, защищая его и полностью выражая инкапсуляцию Java.
Внутренний класс эффективно реализует «множественное наследование» и оптимизирует дефекты одиночного наследования Java.
Мы знаем, что в мире Java у класса может быть только один прямой родительский класс, то есть он существует в режиме единственного наследования. Но внутренние классы делают возможным «множественное наследование»:
- В целом, внутренний класс наследует класс или реализует интерфейс, а также операции кода внутреннего класса создают объекты его окружающих классов. Внутренние классы предоставляют какое-то окно в их прилагаемые классы.
- Каждый внутренний класс может наследоваться от реализации (интерфейса) в команде, поэтому то, унаследовал ли внешний класс реализацию (интерфейса), не влияет на внутренний класс.
- Интерфейсы решают часть проблемы, класс может реализовывать несколько интерфейсов, а внутренние классы позволяют наследовать несколько неинтерфейсных типов (классы или абстрактные классы).
Копия из идей программирования Java, теплота внутренних классов для достижения «множественного наследования» выглядит следующим образом:
class D {}
abstract class E{}
class Z extends D {
E makeE(){ return new E() {}; }
}
public class MultiImplementation {
static void takesD(D d) {}
static void takesE(E e) {}
public static void main(String[] args){
Z z = new Z();
takesD(z);
takesE(z.makeE());
}
}
В коде появляется класс D, абстрактный класс E. Затем наследуйте D с классом Z, и конструкция внутреннего класса возвращает E. Поэтому, когда вы хотите D или E, Z справится, и характеристики «множественного наследования» отлично отображаются.
Анонимные внутренние классы могут легко определять обратные вызовы.
чтоПерезвони? Предположим, что есть два класса A и B, метод b из B вызывается в A, а b вызывает метод c из A во время выполнения, тогда c называется функцией обратного вызова.
//定义一个CallBack接口
public interface CallBack {
void execute();
}
public class TimeTools {
/**
* 测试函数调用时长,通过定义CallBack接口的execute方法
* @param callBack
*/
public void testTime(CallBack callBack) {
long beginTime = System.currentTimeMillis(); //记录起始时间
callBack.execute(); ///进行回调操作
long endTime = System.currentTimeMillis(); //记录结束时间
System.out.println("[use time]:" + (endTime - beginTime)); //打印使用时间
}
public static void main(String[] args) {
TimeTools tool = new TimeTools();
tool.testTime(new CallBack(){
//匿名内部类,定义execute方法
public void execute(){
TestTimeObject testTimeObject = new TestTimeObject();
testTimeObject.testMethod();
}
});
}
}
При вызове testTime() для измерения времени используйте анонимный внутренний класс для реализации метода execute(), делайте что-то в этом методе (выполняйте целевую функцию) и возвращайтесь к методу testTime после выполнения, что является хорошим способом реализовать функцию времени вызова тестовой функции. Очевидно,Анонимные внутренние классы упрощают реализацию обратного вызова.
В-четвертых, нижний слой внутреннего класса
Внутренний маркер
Каждый внутренний класс создает файл .class, содержащий всю информацию о том, как создать объект этого типа. Внутренние классы также должны генерировать файл .class, содержащий информацию об объекте класса. Существуют строгие правила именования файлов внутренних классов: имя внешнего класса + $ + имя внутреннего класса.
Простой пример:
public class Outer {
class Inner{
}
}
После компиляции javac Outer.java сгенерированный файл класса выглядит следующим образом:
Если внутренний класс анонимный, компилятор просто сгенерирует число в качестве его идентификатора. Если внутренние классы вложены в другие внутренние классы (статические внутренние классы), просто добавьте их имена непосредственно после идентификатора окружающего класса и «$».
Почему внутренний класс может получить доступ к членам внешнего класса, включая частные данные?
Из предыдущего раздела мы знаем, что внутренние классы могут получать доступ к членам внешних классов, включая закрытые данные. Так как же это сделать? Ответ будет раскрыт далее.
Давайте посмотрим на этот простой пример:
public class Outer {
private int i = 0;
class Inner{
void method(){
System.out.println(i);
}
}
}
Внешний класс Outer, частное свойство внешнего класса i, внутренний класс Inner, метод метода внутреннего класса. Метод внутреннего класса обращается к свойству внешнего класса i.
Сначала скомпилируйте javac Outer.java и сгенерируйте файл .class следующим образом:
javap -classpath . -v Outer$Inner, декомпилируйте файл Outter$Inner.class, чтобы получить следующую информацию:
Мы видим эту строку, которая является указателем на объект внешнего класса:
final innerclass.Outer this$0;
Хотя компилятор добавляет ссылку на внешний класс при создании внутреннего класса, как эта ссылка назначается?Компилятор добавит параметр в конструктор внутреннего класса для инициализации, тип параметратип внешнего класса,следующее:
innerclass.Outer$Inner(innerclass.Outer);
Указатель Outter this&0 во внутреннем классе-члене указывает на объект внешнего класса, поэтому члены внешнего класса могут быть свободно доступны во внутреннем классе-члене.
Когда локальные внутренние классы и анонимные внутренние классы обращаются к локальным переменным, почему переменные должны быть окончательными?
Когда локальные внутренние классы и анонимные внутренние классы обращаются к локальным переменным, почему переменные должны быть окончательными? Каков его внутренний принцип?
Посмотрите на этот код:
public class Outer {
void outMethod(){
final int a =10;
class Inner {
void innerMethod(){
System.out.println(a);
}
}
}
}
Декомпилируем (Outer$1Inner) получаем следующую информацию
Мы во внутреннем классеinnerMethodметод, вы можете увидеть следующую инструкцию:
3: bipush 10
- Это означает, что константа 10 помещается в стек, а это означает, что используется локальная локальная переменная.
- На самом деле, если значение переменной может быть определено во время компиляции (в демо-версии оно определено как 10), компилятор по умолчанию добавит эквивалентный литерал в пул констант анонимного внутреннего класса (локальный внутренний класс) или напрямую добавьте соответствующий байт-код, встроенный в исполняемый байт-код.
- Sapphire гарантирует, что переменные, используемые локальным внутренним классом, отличаются от внешних локальных переменных, они равны только по значению.
В приведенном выше примере, почему мы должны увеличить его финал? Потому чтоЖизненный цикл несовместитен, Локальные переменные хранятся прямо в стеке, а когда выполнение метода завершается, нефинальные локальные переменные уничтожаются. Однако ссылка локального внутреннего класса на локальную переменную все еще существует, и если локальный внутренний класс захочет вызвать локальную переменную, произойдет ошибка. Добавление final может гарантировать, что переменные, используемые локальным внутренним классом, отличаются от локальных переменных внешнего уровня, что решает эту проблему.
Давайте посмотрим на другой кусок кода, по сути, это перенос переменной a в метод передачи параметров.
public class Outer {
void outMethod(final int a){
class Inner {
void innerMethod(){
System.out.println(a);
}
}
}
}
Доступна декомпиляция
Затем снова возникает новая проблема: поскольку переменная a, к которой обращаются в методе innerMethod, и переменная a в методе outMethod не являются одной и той же переменной, что происходит, когда a изменяется в методе innerMethod? это вызоветНесоответствия данных.
Как это решить? использоватьконечный модификатор, Final измененные переменные ссылочного типа не могут указывать на новые объекты, что решает проблему несогласованности данных.Уведомление:В Java8 локальные переменные, на которые ссылаются локальные внутренние классы, по умолчанию добавляются final, поэтому нет необходимости добавлять ключевое слово final.
В-пятых, сценарии применения внутреннего класса.
В общем, мы его используем и под какую внутреннюю классовую сцену?
Сценарий первый: некоторые мультиалгоритмические ситуации
В некоторых случаях, когда существует много алгоритмов, также могут использоваться внутренние классы, например:
Arrays.sort(emps,new Comparator(){
Public int compare(Object o1,Object o2)
{
return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears();
}
});
Сценарий 2. Решите несколько необъектно-ориентированных блоков операторов.
Если есть много блоков операторов, включая операторы if...else, операторы case и т. д., и их сложно поддерживать и расширять, то это можно решить с помощью внутреннего класса + шаблон проектирования.
Сценарий 3. Надлежащее использование внутренних классов делает код более гибким и расширяемым.
Надлежащее использование внутренних классов может сделать ваш код более гибким и расширяемым. Например, лямбда-выражение JDK использует множество внутренних классов, и его код намного элегантнее. следующее
// JDK8 Lambda表达式写法
new Thread(() -> System.out.println("Thread run()")).start();
Сценарий 4: когда класс больше не используется другими классами, кроме его внешних классов.
Если класс не может быть использован другими классами или по какой-то причине не может ссылаться на него другими классами. Затем мы можем рассмотреть возможность реализации его как внутреннего класса. Пул соединений с базой данных является типичным примером этого.
6. Общие вопросы для внутреннего собеседования
Наконец, давайте взглянем на классический вопрос для внутреннего собеседования.
public class Outer {
private int age = 12;
class Inner {
private int age = 13;
public void print() {
int age = 14;
System.out.println("局部变量:" + age);
System.out.println("内部类变量:" + this.age);
System.out.println("外部类变量:" + Outer.this.age);
}
}
public static void main(String[] args) {
Outer.Inner in = new Outer().new Inner();
in.print();
}
}
результат операции:
Ссылка и спасибо
- «Идеи программирования на Java»
- Внутренние классы (обратные вызовы) в Java
- Java Advanced — Почему необходимо добавлять ключевое слово final для доступа к локальным переменным в локальных внутренних классах
- Подробное объяснение внутренних классов Java
- Использование закулисных героев — четыре сценария применения внутренних классов Java.
Личный публичный аккаунт
- Если вы хороший ребенок, который любит учиться, вы можете подписаться на мой официальный аккаунт, чтобы вместе учиться и обсуждать.
- Если вы считаете, что в этой статье есть какие-либо неточности, вы можете прокомментировать или подписаться на мой официальный аккаунт, пообщаться со мной в частном порядке, и все смогут учиться и прогрессировать вместе.