java基础之内部类
参考博文:
https://blog.csdn.net/xinzhou201/article/details/81950188
https://www.cnblogs.com/dolphin0520/p/3811445.html
概念:
定义在类(包括方法)中的类就叫内部类。
分类:
成员内部类、局部内部类、匿名内部类和静态内部类。
创建方式:
第一种方式:
Outter outter = new Outter(); Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建 第二种方式: Outter.Inner inner1 = outter.getInnerInstance();详解:
成员内部类:
demo:
class Student{ String name = null; public Student(String name) { this.name = name; } class Parent{ //内部类 public void contact() { System.out.println("有事联系我"); } } }
特点:
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以(外部类.this.资源名)的形式进行访问:
在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。
class People{ public People() { } } class Man{ public Man(){ } public People getWoman(){ class Woman extends People{ //局部内部类 int age =0; } return new Woman(); } }
特点:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
匿名内部类:
概念:
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
demo:
scan_bt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub } }); history_bt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub } }); // 这段代码为两个按钮设置监听器,这里面就使用了匿名内部类。这段代码中的: new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub } } //这就是匿名内部类的使用。代码中需要给按钮设置监听器对象,使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用。当然像下面这种写法也是可以的,跟上面使用匿名内部类达到效果相同。 private void setListener() { scan_bt.setOnClickListener(new Listener1()); history_bt.setOnClickListener(new Listener2()); } class Listener1 implements View.OnClickListener{ @Override public void onClick(View v) { // TODO Auto-generated method stub } } class Listener2 implements View.OnClickListener{ @Override public void onClick(View v) { // TODO Auto-generated method stub } }
特点:匿名内部类也是不能有访问修饰符和static修饰符的。
public class Test { public static void main(String[] args) { Outter.Inner inner = new Outter.Inner(); } } class Outter { public Outter() { } static class Inner { public Inner() { } }
特点:静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法。
public class Test { public static void main(String[] args) { } public void test(final int a) { new Thread(){ public void run() { System.out.println(a); }; }.start(); } }
在Test类中有一个test方法,这个方法中a变量的生命周期在方法执行后就结束了,但是test方法中有一个匿名内部类,当test方法执行完后,该内部类的生命周期在可能还没有结束,而在这个内部类中还需要用到a变量,为了解决这个问题,JVM采用了一种复制的方法,讲变量复制到内部类中,如果变量的值在编译期就能确定,则直接在匿名内部类内部创建一个拷贝,如果值不确定,则会将变量的引用传入到构造器中对拷贝进行初始化赋值,所以在run方法中访问的根本就不是原来的a变量,而是复制的那份,但是这样又会产生一个新的问题,就是当变量的值在run方法中发生改变的话,a的值不会跟着变,会造成数据的不一致,所以为了解决这个问题java编译器就限定必须将变量a限制为final变量。
内部类的使用场景:
普通内部类的使用场景:
普通内部类可以访问外部类的非静态成员,内部类可以有成员的四种权限(public,private,protected,default),而普通类只能有public和default。
基于以上两个特点,当类 A 需要使用类 B ,同时 B 需要访问 A 的成员/方法时,可以将 B 作为 A 的成员内部类。demo:
比如安卓开发中常见的在一个 Activity 中有一个 ListView ,我们需要创建一个特定业务的 adapter,在这个 adapter 中需要传入数据,你可以另建一个类,但如果只有当前类需要使用到,完全可以将它创建在 Activity 中: public class VideoListActivity extends AppCompatActivity{ private ListView mVideoListView; private BaseAdapter mListAdapter; private List mVideoInfoData; @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_list); mVideoListView = (ListView) findViewById(R.id.video_list); mVideoInfoData = Collections.EMPTY_LIST; mListAdapter = new VideoListAdapter(); mVideoListView.setAdapter(mListAdapter); } //这里的 private 内部类说明这个 adapter 只能在当前类中使用 private class VideoListAdapter extends BaseAdapter { @Override public int getCount() { return mVideoInfoData.size(); //访问外部类数据 } @Override public Object getItem(final int position) { return mVideoInfoData.get(position); //访问外部类数据 } @Override public long getItemId(final int position) { return 0; } @Override public View getView(final int position, final View convertView, final ViewGroup parent) { return null; } } }
可以利用 private 内部类禁止其他类访问该内部类,从而做到将具体的实现细节完全隐藏。demo:
比如我们有一个 Activity 既可以用作登录也可以用作注册,我们可以这样写: public class MultiplexViewActivity extends AppCompatActivity { public static final String DATA_VIEW_TYPE = "view_type"; public static final int TYPE_LOGIN = 1; public static final int TYPE_REGISTER = 2; private TextView mTitleTv; private ViewController mViewController; @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_multiplex_view); int type = getIntent().getIntExtra(DATA_VIEW_TYPE, TYPE_LOGIN); mViewController = getViewController(type); initView(); } //外界只能拿到基类,具体实现隐藏 public ViewController getViewController(final int type) { switch (type) { case TYPE_REGISTER: return new RegisterViewController(); case TYPE_LOGIN: default: return new LoginViewController(); } } private void initView() { mTitleTv = (TextView) findViewById(R.id.multiplex_title_tv); mViewController.initUi(); } /** * 定义操作规范 */ private interface ViewController { void initUi(); void loadData(); } private class LoginViewController implements ViewController { @Override public void initUi() { mTitleTv.setText("登录"); //显示登录需要的布局 } @Override public void loadData() { //加载登录需要的数据 } } private class RegisterViewController implements ViewController { @Override public void initUi() { mTitleTv.setText("注册"); //显示注册需要的布局 } @Override public void loadData() { //加载注册需要的数据 } } }
/* 解释一下上面的代码,由于要复用这个布局,所以先定义一个布局控制接口 ViewController ,再创建两个内部类实现接口,分别负责登录和注册的布局控制和数据加载。 然后提供一个方法根据参数获取具体的控制器实现 getViewController(final int type) ,这个方法可以是 public 的,外界即使拿到这个 activity 实例,
也只能获取到布局控制器基类,具体的实现被隐藏了,这在后期修改某一个页面时,不用担心会对其他地方造成影响。
有朋友可能会说了:“这 2 个内部类也可以定义成普通类呀”。 确实普通类也同样能满足需求,但是我们希望这 2 个类只是在这个公共支付信息页面才用到,在外界看来是不可见或不可用的状态,这个时候内部类就能满足我们的需求。 这样的场景在 简单工厂模式、迭代器设计模式、命令设计模式都有用到,有兴趣的朋友可以去了解下。 */
静态内部类的使用场景:
静态内部类只能访问外部类的静态成员,但是相对普通内部类它的功能更加完整,也就是可以定义静态成员,普通内部类不能定义静态成员。
基于以上特点,当类 A 需要使用类 B,而 B 不需要直接访问外部类 A 的成员变量和方法时,可以将 B 作为 A 的静态内部类。比较常见的一种使用场景是:
在基类 A 里持有静态内部类 B 的引用,然后在 A 的子类里创建特定业务的 B 的子类,这样就结合多态和静态内部类的优势,既能拓展,又能限制范围 。
demo:
//Android 中经常使用的 LayoutParams 就是静态内部类,由于不同的布局中参数不一样,Android SDK 提供了很多种 LayoutParams: ViewGroup.LayoutParams WindowManager.LayoutParams 继承上一层 RelativeLayout.LayoutParams public interface WindowManager extends ViewManager { //... public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { //... } } 在 View 的 setLayoutParams 中的参数类型是最上层的 ViewGroup.LayoutParams params ,这样子类就可以传入符合自己特性的 LayoutParams 实现: public void setLayoutParams(ViewGroup.LayoutParams params) { if (params == null) { throw new NullPointerException("Layout parameters cannot be null"); } mLayoutParams = params; resolveLayoutParams(); if (mParent instanceof ViewGroup) { ((ViewGroup) mParent).onSetLayoutParams(this, params); } requestLayout(); }
静态内部类还可以用来实现单例模式,好处是:
1.懒加载:类加载时不会创建实例,只有当 getInstance() 方法被调用时才去加载静态内部类以及其中持有的 LocationManager 实例
2.线程安全:JVM 加载类时,可以确保 instance 变量只能初始化一次
demo:
public class LocationManager{ private static class ClassHolder { private static final LocationManager instance = new LocationManager(); } public static LocationManager getInstance() { return ClassHolder.instance; } }
匿名内部类的使用场景:
现阶段我觉得匿名内部类最常见的场景就是设置点击事件,在安卓中很常见:
mButton2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //... } });
匿名内部类没有名字,外部类也就无法持有它的引用,就是说内部类只是用来完成一个特定功能的,所以使用场景可以是:一个方法的返回值是接口,然后根据不同参数返回不同的实现,我们不需要保存引用,直接 new 一个接口实现即可。
demo:
public class GirlFriendMaker { public interface GirlFriend { void sayHi(); } public static GirlFriend giveMeAGirlFriend(final String name) { return new GirlFriend() { //匿名内部类 @Override public void sayHi() { Log.i("来自女朋友的问候", "Hello I'm " + name); } }; } }
局部内部类的使用场景:
限制:
局部内部类类似方法的局部变量,所以在类外或者类的其他方法中不能访问这个内部类。
只能在方法内部,类(局部内部类)定义之后使用,不存在外部可见性问题,因此没有访问修饰符。
不能在局部内部类中使用可变的局部变量。
可以访问外围类的成员变量。如果是static方法,则只能访问static修饰的成员变量。
由于这些限制,局部内部类的使用场景比较少,如果在一个方法中确实需要定义一个类才能实现功能业务的话,可以考虑使用。
总的来说,内部类一般用于两个场景:
- 需要用一个类来解决一个复杂的问题,但是又不希望这个类是公共的
- 需要实现一个接口,但不需要持有它的引用
使用内部类需要注意的问题:
内存泄漏:
四种内部类中除了静态内部类,只要访问外部类的成员/方法,就会持有外部类的引用。当内部类持有外部类的引用,同时生命周期比外部类要长(比如执行耗时任务、被其他长生命周期对象持有),就会导致外部类该被回收时无法被回收,也就是内存泄漏问题。
demo:
//一个 Android 开发中常见的内部类导致内存泄露的例子: public class MainActivity extends AppCompatActivity { public final int LOGIN_SUCCESS = 1; private Context mContext; private boolean isLongTimeNoMsg; @SuppressWarnings("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { isLongTimeNoMsg = false; switch (msg.what) { case LOGIN_SUCCESS: {/ break; } //... } } /*这个 Handler 持有外部类的引用,它发送的 runnable 对象,会被进一步包装为 message 对象,放入消息队列,在被执行、回收之前会一致持有引用,导致无法释放。 解决办法就是使用弱引用或者干脆将 Handler 设计为静态内部类。*/

浙公网安备 33010602011771号