经过前面的学习,我们已经了解了面向对象编程的大部分基础内容,这一部分,我们将继续探索面向对象编程过程中一些常用的东西。
Java并不是纯面向对象的语言,虽然Java语言是一个面向对象的语言,但是Java中的基本数据类型却不是面向对象的。Java中的基本类型,如果想通过对象的形式去使用他们,Java提供的基本类型包装类,使得Java能够更好的体现面向对象的思想,同时也使得基本类型能够支持对象操作!
所有的包装类层次结构如下:
其中能够表示数字的基本类型包装类,继承自Number类,对应关系如下表:
我们可以直接使用,这里我们以Integer类为例:
public static void main(String[] args) {
Integer i = new Integer(10); //将10包装为一个Integer类型的变量
}
包装类实际上就是将我们的基本数据类型,封装成一个类(运用了封装的思想)我们可以来看看Integer类中是怎么写的:
private final int value; //类中实际上就靠这个变量在存储包装的值
public Integer(int value) {
this.value = value;
}
包装类型支持自动装箱,我们可以直接将一个对应的基本类型值作为对应包装类型引用变量的值:
public static void main(String[] args) {
Integer i = 10; //将int类型值作为包装类型使用
}
这是怎么做到的?为什么一个对象类型的值可以直接接收一个基本类类型的值?实际上这里就是自动装箱:
public static void main(String[] args) {
Integer i = Integer.valueOf(10); //上面的写法跟这里是等价的
}
这里本质上就是被自动包装成了一个Integer类型的对象,只是语法上为了简单,就支持像这样编写。既然能装箱,也是支持拆箱的:
public static void main(String[] args) {
Integer i = 10;
int a = i;
}
实际上上面的写法本质上就是:
public static void main(String[] args) {
Integer i = 10;
int a = i.intValue(); //通过此方法变成基本类型int值
}
这里就是自动拆箱,得益于包装类型的自动装箱和拆箱机制,我们可以让包装类型轻松地参与到基本类型的运算中:
public static void main(String[] args) {
Integer a = 10, b = 20;
int c = a * b; //直接自动拆箱成基本类型参与到计算中
System.out.println(c);
}
因为包装类是一个类,不是基本类型,所以说两个不同的对象,那么是不相等的:
public static void main(String[] args) {
Integer a = new Integer(10);
Integer b = new Integer(10);
System.out.println(a == b); //虽然a和b的值相同,但是并不是同一个对象,所以说==判断为假
}
那么自动装箱的呢?
public static void main(String[] args) {
Integer a = 10, b = 10;
System.out.println(a == b);
}
我们发现,通过自动装箱转换的Integer对象,如果值相同,得到的会是同一个对象,这是因为:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high) //这里会有一个IntegerCache,如果在范围内,那么会直接返回已经提前创建好的对象
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache会默认缓存-128~127之间的所有值,将这些值提前做成包装类放在数组中存放,虽然我们目前还没有学习数组,但是各位小伙伴只需要知道,我们如果直接让 -128~127之间的值自动装箱为Integer类型的对象,那么始终都会得到同一个对象,这是为了提升效率,因为小的数使用频率非常高,有些时候并不需要创建那么多对象,创建对象越多,内存也会消耗更多。
但是如果超出这个缓存范围的话,就会得到不同的对象了:
public static void main(String[] args) {
Integer a = 128, b = 128;
System.out.println(a == b);
}
这样就不会得到同一个对象了,因为超出了缓存的范围。同样的,Long、Short、Byte类型的包装类也有类似的机制,感兴趣的小伙伴可以自己点进去看看。
我们来看看包装类中提供了哪些其他的方法,包装类支持字符串直接转换:
public static void main(String[] args) {
Integer i = new Integer("666"); //直接将字符串的666,转换为数字666
System.out.println(i);
}
当然,字符串转Integer有多个方法:
public static void main(String[] args) {
Integer i = Integer.valueOf("5555");
//Integer i = Integer.parseInt("5555");
System.out.println(i);
}
我们甚至可以对十六进制和八进制的字符串进行解码,得到对应的int值:
public static void main(String[] args) {
Integer i = Integer.decode("0xA6");
System.out.println(i);
}
也可以将十进制的整数转换为其他进制的字符串:
public static void main(String[] args) {
System.out.println(Integer.toHexString(166));
}
当然,Integer中提供的方法还有很多,这里就不一一列出了。
除了我们上面认识的这几种基本类型包装类之外,还有两个比较特殊的包装类型。
其中第一个是用于计算超大数字的BigInteger,我们知道,即使是最大的long类型,也只能表示64bit的数据,无法表示一个非常大的数,但是BigInteger没有这些限制,我们可以让他等于一个非常大的数字:
public static void main(String[] args) {
BigInteger i = BigInteger.valueOf(Long.MAX_VALUE); //表示Long的最大值,轻轻松松
System.out.println(i);
}
我们可以通过调用类中的方法,进行运算操作:
public static void main(String[] args) {
BigInteger i = BigInteger.valueOf(Long.MAX_VALUE);
i = i.multiply(BigInteger.valueOf(Long.MAX_VALUE)); //即使是long的最大值乘以long的最大值,也能给你算出来
System.out.println(i);
}
我们来看看结果:
可以看到,此时数值已经非常大了,也可以轻松计算出来。咱们来点更刺激的:
public static void main(String[] args) {
BigInteger i = BigInteger.valueOf(Long.MAX_VALUE);
i = i.pow(100); //long的最大值来个100次方吧
System.out.println(i);
}
可以看到,这个数字已经大到一排显示不下了:
一般情况,对于非常大的整数计算,我们就可以使用BigInteger来完成。
我们接着来看第二种,前面我们说了,浮点类型精度有限,对于需要精确计算的场景,就没办法了,而BigDecimal可以实现小数的精确计算。
public static void main(String[] args) {
BigDecimal i = BigDecimal.valueOf(10);
i = i.divide(BigDecimal.valueOf(3), 100, RoundingMode.CEILING);
//计算10/3的结果,精确到小数点后100位
//RoundingMode是舍入模式,就是精确到最后一位时,该怎么处理,这里CEILING表示向上取整
System.out.println(i);
}
可以看到,确实可以精确到这种程度:
但是注意,对于这种结果没有终点的,无限循环的小数,我们必须要限制长度,否则会出现异常。
我们接着来看一个比较特殊的类型,数组。
假设出现一种情况,我们想记录100个数字,要是采用定义100个变量的方式可以吗?是不是有点太累了?这种情况我们就可以使用数组来存放一组相同类型的数据。
数组是相同类型数据的有序集合,数组可以代表任何相同类型的一组内容(包括引用类型和基本类型)其中存放的每一个数据称为数组的一个元素,我们来看看如何去定义一个数组变量:
public static void main(String[] args) {
int[] array; //类型[]就表示这个是一个数组类型
}
注意,数组类型比较特殊,它本身也是类,但是编程不可见(底层C++写的,在运行时动态创建)即使是基本类型的数组,也是以对象的形式存在的,并不是基本数据类型。所以,我们要创建一个数组,同样需要使用new
关键字:
public static void main(String[] args) {
int[] array = new int[10]; //在创建数组时,需要指定数组长度,也就是可以容纳多个int变量的值
Object obj = array; //因为同样是类,肯定是继承自Object的,所以说可以直接向上转型
}
除了上面这种方式之外,我们也可以使用其他方式:
类型[] 变量名称 = new 类型[数组大小];
类型 变量名称[] = new 类型[数组大小]; //支持C语言样式,但不推荐!
类型[] 变量名称 = new 类型[]{...}; //静态初始化(直接指定值和大小)
类型[] 变量名称 = {...}; //同上,但是只能在定义时赋值
创建出来的数组每个位置上都有默认值,如果是引用类型,就是null,如果是基本数据类型,就是0,或者是false,跟对象成员变量的默认值是一样的,要访问数组的某一个元素,我们可以:
public static void main(String[] args) {
int[] array = new int[10];
System.out.println("数组的第一个元素为:"+array[0]); //使用 变量名[下标] 的方式访问
}
注意,数组的下标是从0开始的,不是从1开始的,所以说第一个元素的下标就是0,我们要访问第一个元素,那么直接输入0就行了,但是注意千万别写成负数或是超出范围了,否则会出现异常。
我们也可以使用这种方式为数组的元素赋值:
public static void main(String[] args) {
int[] array = new int[10];
array[0] = 888; //就像使用变量一样,是可以放在赋值运算符左边的,我们可以直接给对应下标位置的元素赋值
System.out.println("数组的第一个元素为:"+array[0]);
}
因为数组本身也是一个对象,数组对象也是具有属性的,比如长度:
public static void main(String[] args) {
int[] array = new int[10];
System.out.println("当前数组长度为:"+array.length); //length属性是int类型的值,表示当前数组长度,长度是在一开始创建数组的时候就确定好的
}
注意,这个length
是在一开始就确定的,而且是final
类型的,不允许进行修改,也就是说数组的长度一旦确定,不能随便进行修改,如果需要使用更大的数组,只能重新创建。
当然,既然是类型,那么肯定也是继承自Object类的:
public static void main(String[] args) {
int[] array = new int[10];
System.out.println(array.toString());
System.out.println(array.equals(array));
}
但是,很遗憾,除了clone()之外,这些方法并没有被重写,也就是说依然是采用的Object中的默认实现:
所以说通过toString()
打印出来的结果,好丑,只不过我们可以发现,数组类型的类名很奇怪,是[
开头的。
因此,如果我们要打印整个数组中所有的元素,得一个一个访问:
public static void main(String[] args) {
int[] array = new int[10];
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
有时候为了方便,我们可以使用简化版的for语句foreach
语法来遍历数组中的每一个元素:
public static void main(String[] args) {
int[] array = new int[10];
for (int i : array) { //int i就是每一个数组中的元素,array就是我们要遍历的数组
System.out.print(i+" "); //每一轮循环,i都会更新成数组中下一个元素
}
}
是不是感觉这种写法更加简洁?只不过这仅仅是语法糖而已,编译之后依然是跟上面一样老老实实在遍历的:
public static void main(String[] args) { //反编译的结果
int[] array = new int[10];
int[] var2 = array;
int var3 = array.length;
for(int var4 = 0; var4 < var3; ++var4) {
int i = var2[var4];
System.out.print(i + " ");
}
}
对于这种普通的数组,其实使用还是挺简单的。这里需要特别说一下,对于基本类型的数组来说,是不支持自动装箱和拆箱的:
public static void main(String[] args) {
int[] arr = new int[10];
Integer[] test = arr;
}
还有,由于基本数据类型和引用类型不同,所以说int类型的数组时不能被Object类型的数组变量接收的:
但是如果是引用类型的话,是可以的:
public static void main(String[] args) {
String[] arr = new String[10];
Object[] array = arr; //数组同样支持向上转型
}
public static void main(String[] args) {
Object[] arr = new Object[10];
String[] array = (String[]) arr; //也支持向下转型
}
前面我们介绍了简单的数组(一维数组)既然数组可以是任何类型的,那么我们能否创建数组类型的数组呢?答案是可以的,套娃嘛,谁不会:
public static void main(String[] args) {
int[][] array = new int[2][10]; //数组类型数组那么就要写两个[]了
}
存放数组的数组,相当于将维度进行了提升,比如上面的就是一个2x10的数组:
这个中数组一共有2个元素,每个元素都是一个存放10个元素的数组,所以说最后看起来就像一个矩阵一样。甚至可以继续套娃,将其变成一个三维数组,也就是存放数组的数组的数组。
public static void main(String[] args) {
int[][] arr = { {1, 2},
{3, 4},
{5, 6}}; //一个三行两列的数组
System.out.println(arr[2][1]); //访问第三行第二列的元素
}
在访问多维数组时,我们需要使用多次[]
运算符来得到对应位置的元素。如果我们要遍历多维数组话,那么就需要多次嵌套循环:
public static void main(String[] args) {
int[][] arr = new int[][]{{1, 2},
{3, 4},
{5, 6}};
for (int i = 0; i < 3; i++) { //要遍历一个二维数组,那么我们得一列一列一行一行地来
for (int j = 0; j < 2; j++) {
System.out.println(arr[i][j]);
}
}
}
我们接着来看数组的延伸应用,实际上我们的方法是支持可变长参数的,什么是可变长参数?
public class Person {
String name;
int age;
String sex;
public void test(String... strings){
}
}
我们在使用时,可以传入0 - N个对应类型的实参:
public static void main(String[] args) {
Person person = new Person();
person.test("1!", "5!", "哥们在这跟你说唱"); //这里我们可以自由传入任意数量的字符串
}
那么我们在方法中怎么才能得到这些传入的参数呢,实际上可变长参数本质就是一个数组:
public void test(String... strings){ //strings这个变量就是一个String[]类型的
for (String string : strings) {
System.out.println(string); //遍历打印数组中每一个元素
}
}
注意,如果同时存在其他参数,那么可变长参数只能放在最后:
public void test(int a, int b, String... strings){
}
这里最后我们再来说一个从开始到现在一直都没有说的东西:
public static void main(String[] args) { //这个String[] args到底是个啥???
}
实际上这个是我们在执行Java程序时,输入的命令行参数,我们可以来打印一下:
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
可以看到,默认情况下直接运行什么都没有,但是如果我们在运行时,添加点内容的话:
java com/test/Main lbwnb aaaa xxxxx #放在包中需要携带主类完整路径才能运行
可以看到,我们在后面随意添加的三个参数,都放到数组中了:
这个东西我们作为新手一般也不会用到,只做了解就行了。
字符串类是一个比较特殊的类,它用于保存字符串。我们知道,基本类型char
可以保存一个2字节的Unicode字符,而字符串则是一系列字符的序列(在C中就是一个字符数组)Java中没有字符串这种基本类型,因此只能使用类来进行定义。注意,字符串中的字符一旦确定,无法进行修改,只能重新创建。
String本身也是一个类,只不过它比较特殊,每个用双引号括起来的字符串,都是String类型的一个实例对象:
public static void main(String[] args) {
String str = "Hello World!";
}
我们也可以象征性地使用一下new关键字:
public static void main(String[] args) {
String str = new String("Hello World!"); //这种方式就是创建一个新的对象
}
注意,如果是直接使用双引号创建的字符串,如果内容相同,为了优化效率,那么始终都是同一个对象:
public static void main(String[] args) {
String str1 = "Hello World";
String str2 = "Hello World";
System.out.println(str1 == str2);
}
但是如果我们使用构造方法主动创建两个新的对象,那么就是不同的对象了:
public static void main(String[] args) {
String str1 = new String("Hello World");
String str2 = new String("Hello World");
System.out.println(str1 == str2);
}
至于为什么会出现这种情况,我们在JVM篇视频教程中会进行详细的介绍,这里各位小伙伴只需要记住就行了。因此,如果我们仅仅是想要判断两个字符串的内容是否相同,不要使用==
,String类重载了equals
方法用于判断和比较内容是否相同:
public static void main(String[] args) {
String str1 = new String("Hello World");
String str2 = new String("Hello World");
System.out.println(str1.equals(str2)); //字符串的内容比较,一定要用equals
}
既然String也是一个类,那么肯定是具有一些方法的,我们可以来看看:
public static void main(String[] args) {
String str = "Hello World";
System.out.println(str.length()); //length方法可以求字符串长度,这个长度是字符的数量
}
因为双引号括起来的字符串本身就是一个实例对象,所以说我们也可以直接用:
public static void main(String[] args) {
System.out.println("Hello World".length()); //虽然看起来挺奇怪的,但是确实支持这种写法
}
字符串类中提供了很多方便我们操作的方法,比如字符串的裁剪、分割操作:
public static void main(String[] args) {
String str = "Hello World";
String sub = str.substring(0, 3); //分割字符串,并返回一个新的子串对象
System.out.println(sub);
}
public static void main(String[] args) {
String str = "Hello World";
String[] strings = str.split(" "); //使用split方法进行字符串分割,比如这里就是通过空格分隔,得到一个字符串数组
for (String string : strings) {
System.out.println(string);
}
}
字符数组和字符串之间是可以快速进行相互转换的:
public static void main(String[] args) {
String str = "Hello World";
char[] chars = str.toCharArray();
System.out.println(chars);
}
public static void main(String[] args) {
char[] chars = new char[]{'奥', '利', '给'};
String str = new String(chars);
System.out.println(str);
}
当然,String类还有很多其他的一些方法,这里就不一一介绍了。
我们在之前的学习中已经了解,字符串支持使用+
和+=
进行拼接操作。
但是拼接字符串实际上底层需要进行很多操作,如果程序中大量进行字符串的拼接似乎不太好,编译器是很聪明的,String的拼接会在编译时进行各种优化:
public static void main(String[] args) {
String str = "杰哥" + "你干嘛"; //我们在写代码时使用的是拼接的形式
System.out.println(str);
}
编译之后就变成这样了:
public static void main(String[] args) {
String str = "杰哥你干嘛";
System.out.println(str);
}
对于变量来说,也有优化,比如下面这种情况:
public static void main(String[] args) {
String str1 = "你看";
String str2 = "这";
String str3 = "汉堡";
String str4 = "做滴";
String str5 = "行不行";
String result = str1 + str2 + str3 + str4 + str5; //5个变量连续加
System.out.println(result);
}
如果直接使用加的话,每次运算都会生成一个新的对象,这里进行4次加法运算,那么中间就需要产生4个字符串对象出来,是不是有点太浪费了?这种情况实际上会被优化为下面的写法:
public static void main(String[] args) {
String str1 = "你看";
String str2 = "这";
String str3 = "汉堡";
String str4 = "做滴";
String str5 = "行不行";
StringBuilder builder = new StringBuilder();
builder.append(str1).append(str2).append(str3).append(str4).append(str5);
System.out.println(builder.toString());
}
这里创建了一个StringBuilder的类型,这个类型是干嘛的呢?实际上它就是专门用于构造字符串的,我们可以使用它来对字符串进行拼接、裁剪等操作,它就像一个字符串编辑器,弥补了字符串不能修改的不足:
public static void main(String[] args) {
StringBuilder builder = new StringBuilder(); //一开始创建时,内部什么都没有
builder.append("AAA"); //我们可以使用append方法来讲字符串拼接到后面
builder.append("BBB");
System.out.println(builder.toString()); //当我们字符串编辑完成之后,就可以使用toString转换为字符串了
}
它还支持裁剪等操作:
public static void main(String[] args) {
StringBuilder builder = new StringBuilder("AAABBB"); //在构造时也可以指定初始字符串
builder.delete(2, 4); //删除2到4这个范围内的字符
System.out.println(builder.toString());
}
当然,StringBuilder类的编辑操作也非常多,这里就不一一列出了。
我们现在想要实现这样一个功能,对于给定的字符串进行判断,如果字符串符合我们的规则,那么就返回真,否则返回假,比如现在我们想要判断字符串是不是邮箱的格式:
public static void main(String[] args) {
String str = "aaaa731341@163.com";
//假设邮箱格式为 数字/字母@数字/字母.com
}
那么现在请你设计一个Java程序用于判断,你该怎么做?是不是感觉很麻烦,但是我们使用正则表达式就可以很轻松解决这种字符串格式匹配问题。
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
我们先来看看下面的这个例子:
public static void main(String[] args) {
String str = "oooo";
//matches方法用于对给定正则表达式进行匹配,匹配成功返回true,否则返回false
System.out.println(str.matches("o+")); //+表示对前面这个字符匹配一次或多次,这里字符串是oooo,正好可以匹配
}
用于规定给定组件必须要出现多少次才能满足匹配的,我们一般称为限定符,限定符表如下:
字符 | 描述 |
---|---|
* | 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。***** 等价于 {0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,zo+ 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。 |
? | 匹配前面的子表达式零次或一次。例如,do(es)? 可以匹配 "do" 、 "does"、 "doxy" 中的 "do" 。? 等价于 {0,1}。 |
{n} | n 是一个非负整数。匹配确定的 n 次。例如,o{2} 不能匹配 "Bob" 中的 o,但是能匹配 "food" 中的两个 o。 |
{n,} | n 是一个非负整数。至少匹配n 次。例如,o{2,} 不能匹配 "Bob" 中的 o,但能匹配 "foooood" 中的所有 o。o{1,} 等价于 o+。o{0,} 则等价于 o*。 |
{n,m} | m 和 n 均为非负整数,其中 n <= m。最少匹配 n 次且最多匹配 m 次。例如,o{1,3} 将匹配 "fooooood" 中的前三个 o。o{0,1} 等价于 o?。请注意在逗号和两个数之间不能有空格。 |
如果我们想要表示一个范围内的字符,可以使用方括号:
public static void main(String[] args) {
String str = "abcabccaa";
System.out.println(str.matches("[abc]*")); //表示abc这几个字符可以出现 0 - N 次
}
对于普通字符来说,我们可以下面的方式实现多种字符匹配:
字符 | 描述 |
---|---|
[ABC] | 匹配 [...] 中的所有字符,例如 [aeiou] 匹配字符串 "google runoob taobao" 中所有的 e o u a 字母。 |
[^ABC] | 匹配除了 [...] 中字符的所有字符,例如 [^aeiou] 匹配字符串 "google runoob taobao" 中除了 e o u a 字母的所有字母。 |
[A-Z] | [A-Z] 表示一个区间,匹配所有大写字母,[a-z] 表示所有小写字母。 |
. | 匹配除换行符(\n、\r)之外的任何单个字符,相等于 \[^\n\r] |
[\s\S] | 匹配所有。\s 是匹配所有空白符,包括换行,\S 非空白符,不包括换行。 |
\w | 匹配字母、数字、下划线。等价于 [A-Za-z0-9_] |
当然,这里仅仅是对正则表达式的简单使用,实际上正则表达式内容非常多,如果需要完整学习正则表达式,可以到:https://www.runoob.com/regexp/regexp-syntax.html
正则表达式并不是只有Java才支持,其他很多语言比如JavaScript、Python等等都是支持正则表达式的。
上一章我们详细介绍了类,我们现在已经知道该如何创建类、使用类了。当然,类的创建其实可以有多种多样的方式,并不仅仅局限于普通的创建。内部类顾名思义,就是创建在内部的类,那么具体是什么的内部呢,我们接着就来讨论一下。
注意: 内部类很多地方都很绕,所以说一定要仔细思考。
我们可以直接在类的内部定义成员内部类:
public class Test {
public class Inner { //内部类也是类,所以说里面也可以有成员变量、方法等,甚至还可以继续套娃一个成员内部类
public void test(){
System.out.println("我是成员内部类!");
}
}
}
成员内部类和成员方法、成员变量一样,是对象所有的,而不是类所有的,如果我们要使用成员内部类,那么就需要:
public static void main(String[] args) {
Test test = new Test(); //我们首先需要创建对象
Test.Inner inner = test.new Inner(); //成员内部类的类型名称就是 外层.内部类名称
}
虽然看着很奇怪,但是确实是这样使用的。我们同样可以使用成员内部类中的方法:
public static void main(String[] args) {
Test test = new Test();
Test.Inner inner = test.new Inner();
inner.test();
}
注意,成员内部类也可以使用访问权限控制,如果我们我们将其权限改为private
,那么就像我们把成员变量访问权限变成私有一样,外部是无法访问到这个内部类的:
可以看到这里直接不认识了。
这里我们需要特别注意一下,在成员内部类中,是可以访问到外层的变量的:
public class Test {
private final String name;
public Test(String name){
this.name = name;
}
public class Inner {
public void test(){
System.out.println("我是成员内部类:"+name);
//成员内部类可以访问到外部的成员变量
//因为成员内部类本身就是某个对象所有的,每个对象都有这样的一个类定义,这里的name是其所依附对象的
}
}
}
每个类可以创建一个对象,每个对象中都有一个单独的类定义,可以通过这个成员内部类又创建出更多对象,套娃了属于是。
所以说我们在使用时:
public static void main(String[] args) {
Test a = new Test("小明");
Test.Inner inner1 = a.new Inner(); //依附于a创建的对象,那么就是a的
inner1.test();
Test b = new Test("小红");
Test.Inner inner2 = b.new Inner(); //依附于b创建的对象,那么就是b的
inner2.test();
}
那现在问大家一个问题,外部能访问内部类里面的成员变量吗?
那么如果内部类中也定义了同名的变量,此时我们怎么去明确要使用的是哪一个呢?
public class Test {
private final String name;
public Test(String name){
this.name = name;
}
public class Inner {
String name;
public void test(String name){
System.out.println("方法参数的name = "+name); //依然是就近原则,最近的是参数,那就是参数了
System.out.println("成员内部类的name = "+this.name); //在内部类中使用this关键字,只能表示内部类对象
System.out.println("成员内部类的name = "+Test.this.name);
//如果需要指定为外部的对象,那么需要在前面添加外部类型名称
}
}
}
包括对方法的调用和super关键字的使用,也是一样的:
public class Inner {
String name;
public void test(String name){
this.toString(); //内部类自己的toString方法
super.toString(); //内部类父类的toString方法
Test.this.toString(); //外部类的toSrting方法
Test.super.toString(); //外部类父类的toString方法
}
}
所以说成员内部类其实在某些情况下使用起来比较麻烦,对于这种成员内部类,我们一般只会在类的内部自己使用。