Java 基础核心学习笔记,参考书籍《Java核心技术 卷I》

Java 基础核心学习笔记,参考书籍《Java核心技术 卷I》,第1张

Java 基础核心学习笔记

文章目录
  • `Java` 基础核心学习笔记
    • 1、基础语法
      • 1.1、八大数据类型
      • 1.2、变量与常量
      • 1.3、运算符
      • 1.4、字符串
      • 1.5、输入和输出
      • 1.6、流程控制
      • 1.7、大数
      • 1.8、数组
    • 2、面向对象程序设计
      • 2.1、类和对象
      • 2.2、静态修饰符 static
      • 2.3、方法参数
      • 2.4、构造对象
      • 2.5、静态导入
    • 3、继承
      • 3.1、重写
      • 3.2、子类构造器
      • 3.3、多态
      • 3.4、理解一个方法调用的过程
      • 3.5、Object 父类
      • 3.6、包装类、自动装箱、自动拆箱
    • 4、抽象类及接口
      • 4.1、抽象类
      • 4.2、抽象方法
      • 4.3、匿名子类
      • 4.4、接口
      • 4.5、`Java8`接口新特性
      • 4.6、内部类

1、基础语法 1.1、八大数据类型

byte(2字节)、short(2字节)、int(4字节)、long(8字节)、float(4字节)、double(8字节)、char(2字节)、boolean(1位,至少需要一个字节)

在Java中,所有的数值类型占据的字节数与平台无关。

整型数据类型:byte、short、int、long

在Java中没有 unsigned形式来表示一个一个整数类型,如果需要表示一个不可能为负数的的整数值,可以使用对应数据类型对应的类,使用toUnsignedInt()方法,来处理这个数,然后在装换成原来的数据类型。

public static void main(String[] args) {
    byte b = -127;
    System.out.println(b); // 输出-1
    int i = Byte.toUnsignedInt(b); // 装换得到一个 0-255的数值
    // 转换方式:如果是真值,就是本身,如果是负数就是 2最大真值-|原值|
    System.out.println(i); // 输出255
}

浮点类型:float(单精度,有效位6~7)、double(双精度,有效位是双精度的两倍)

三个特殊值:Double.POSITIVE_INFINITY(正无穷大)、Double.NEGATIVE_INFINITY(负无穷大)、NaN(不是一个数字)

public static final double POSITIVE_INFINITY = 1.0 / 0.0;
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;
public static final double NaN = 0.0d / 0.0;
double a = Double.POSITIVE_INFINITY;
double c = Double.NEGATIVE_INFINITY;
double naN = Double.NaN;
System.out.println(naN);  // 输出 NaN
System.out.println(a); // 输出Infinity
System.out.println(c); // 输出-Infinity

如何判断一个浮点数是否为一个数值?Double、Float提供了 isNaN() 来判断一个浮点数是否为一个数值。上面这些内容对于Float也适用。

char类型:表示单个字符

char类型的值可以表示为十六进制,范围是 \u0000~\uFFFF,例如 \u03c0 表示字符 π

System.out.println('\u03c0'); // 输出 π

注意:

  • 注释中的 \u 也可以解析为字符。
// \u000A 你好
System.out.println('\u03c0'); // 输出 π

// 上面代码会报错,因为 \u000A会解析成一个换行符,导致 你好 会换行导致报错
  • \u 后面必须跟一个十六进制数。
// c:\users
System.out.println('\u03c0'); // 输出 π

// 上面也会报错,\u后面跟的是ers,不是一个十六进制数
1.2、变量与常量

变量:初始化、赋值、标识符的命名规则等

千万不要使用未初始化的局部变量,如果局部变量没有初始化直接使用就会报错。但是类的成员变量,在类构造时会初始化属性值。

Java10中,对于局部变量,可以通过变量的初始值来判断变量的类型,可以使用 var 关键字声明变量就无须指定类型。

var a = 10;
var str = "Hello,World!";

常量:使用final关键字来修饰,表示这是个常量,常量只能被赋值一次,一旦赋值就不能改变,常量名通过全部大写。

1.3、运算符

算数运算符:+、-、*、/、%(取模)

注意:整数除以0时会报出异常,而浮点数除以0会得到无穷大。

数学函数 Math

// 两个常量 PI和E
System.out.println(Math.PI);
System.out.println(Math.E);

// 常用方法
public static double abs(double num); // 获取绝对值,有多种重载。
public static double ceil(double num); // 向下取整。
public static double floor(double num); // 向上取整。
public static long round(double num); // 四舍五入。
public static double sqrt(double x); // 求算数平方根
public static double pow(double x,double y); // 求x的y次方
public static int floorMode(int x,int y); // 对于负数求余数,x被除数,y除数,但是如果y是负数也会得到一个负数余数

数据类型转换和强制类型转换

从短字节转换到高字节,可以自动转换,但是整数转换成浮点数类型时可能会发生精度的缺失,浮点数转换成整数也会去除小数。如果从字节转换成低字节,需要进行强制类型转换,这样可能得到错误的结果,强制转换成另一低字节类型时,超出了目标类型的表示范围,就会得到一个错误的数。

二元运算符:+=、++(自增)、–(自减)等

  • +=:例如:x+=3 ,表示 x=x+3,对于其他的数学运算符也适用。
  • ++:自增1,x++,表示x=x+1,自减类似。
int a = 2;
System.out.println(a++); // 输出2,a=3
System.out.println(++a); // 输出4,a=4

// 上面的a++是先赋值后加1,a++这个表达式还是原来的值,但是a后面加1了。
// ++a是先加1后赋值,++a这个表达式的值是a加1后的结果。
// 对于 -- 一样

// 由于运算符是改变变量的值,所以 4++ 这是一个不合法的语句

三元运算符:条件 ? 表达式1 : 表达式2,条件成立输出为表达式1,不成立输出为表达式2。

System.out.println(1>2 ? "1大于2" : "2大于1"); // 输出2大于1

位运算符:&(与)、|(或)、^(异或)、~(非)、>>(右移)、<<(左移)、>>>(无符号右移,高位使用0补位)。位运算是按照二进制的位进行运算。

1.4、字符串

字符串使用 String 这个预定义类来表示,它是一个对象,不属于基本数据类型。

字符串拼接

在Java中,任何一个Java对象都可以转换成字符串,当一个字符串与一个非字符串进行拼接时,后者会转换成字符串。

System.out.println(1+"1你好"); // 输出 11你好
System.out.println(1+1+"你好"); // 输出 2你好 

字符串不可变

String 没有提供任何修改字符串的方法,字符串一旦定义,就不能改变了。如果需要修改字符串,可以通过截取子串,然后通过拼接构建一个新的字符串。

String str = "Hello,World";
str = str.substring(0,3); // substring()方法截取子串
// 这个方法并不是修改了str,而是截取了一个新的子串,然后重新赋值给了str,"Hello,World!"这个字符串依然存在,后面会被GC回收掉

检测字符串相等

可以使用 equals() 方法区分大小写来检测两个字符串是否相等,equalsIgnoreCase()不区分大小写判断字符串是否相等,相等返回true,不相等返回false

System.out.println("Hello".equals("Hello")); // 输出true
System.out.println("test".equalsIgnoreCase("TesT")); // 输出true

注意:在Java中,不能使用 == 来判断两个字符串是否相等, == 只能判断字符串的位置是否在同一个位置上,同一位置的字符串必然是相等的,但是有相同的字符串放在不同的位置上。在Java中,字面量的字符串是共享的。

String s1 = new String("Hello,World!");
System.out.println(s1=="Hello,World!"); // 输出false

空串与null

空串:指一个String对象的长度为0,可以通过 str.length()==0"".equals(str) 来判断。

null串:指这个String对象都不存在,没有任何Java对象与变量有关联。

注意:如果检查字符串是否为空串,需要先检查是否为 null 串。

if(str!=null && str.length()!=0) // 先检查null

字符串 String 常用方法

int comparerTo(String other); // 如果字符串位于other之前返回一个负数,之后返回一个正数,相等返回0
boolean isEmpty();  // 判断字符串长度是否为0,不能判断null
boolean startWith(String pre); // 判断字符串是否以pre开头
boolean endWith(String suffix);
String replace(CharSequence oldStr, CharSequence newStr); // 使用newStr替换字符串中所有的oldStr,CharSequence可以是String和StringBuilder
String substring(int begin,int end); // 截取字符串的子串,从下标begin开始截取,end-1这个下标结束,截取的长度end-begin
String toLowerCase(); // 将字符串的所有大写字母转换成小写
String toUpperCase(); // 将字符串所有小写字母转换成大写
String trim(); // 去掉字符串的头空格和尾空格
static String join(CharSequence s,CharSequence... e); // e是可变参数,将e这些参数,通过s这个分界符连成一个新字符串,可变参数也可以是一个数组的重载方法
String repeat(int count); // 返回字符串重复count次的新字符串,java11

CharSequence是一种接口类型,所有字符串都属于这个接口。

构建字符串

需要多个简单的字符串拼接成一个字符串时,通过字符串的拼接这样浪费空间、时间,这时就可以使用StringBulider类来构建字符串。

StringBuilder builder = new StringBuilder();
builder.append("Hello");
builder.append(",World!");
System.out.println(builder.toString());

下面是StringBuilder类常用方法:

int length(); // 返回字符串构造器中字符串的长度
StringBuilder append(参数); // 将参数追加的构造器中,参数可以是字符串、基本数据类型除byte,也可以是char数组,也可以是一个StringBuilder
StringBuilder insert(int index,参数); // 将参数插入到index下标的位置,参数和上面一样
StringBuilder delete(int begin,int end); // 删除构造器中从begin到end-1这段字符串
String toString(); // 返回构造器内容相同的字符串
String reverse(); // 将构造器中的字符逆序
1.5、输入和输出

输入:在Java中,有很多输入方式,从控制台输入、文件输入等,下面介绍从控制台输入,文件输入在后面I/O流。

方式一:通过Scanner类从控制台读取数据。

Scanner in = new Scanner(System.in);
String s1 = in.next(); // 从控制台读取一串不包含空格、回车、换行的字符串
String s2 = in.nextLine(); // 读取一行包含空格的字符串
int x = in.nextInteger(); // 也可以读取基本数据类型int,double,float,long等

方式二:
通过BufferedReader类从控制台读取数据。

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.println(reader.readLine()); // 读取一行包含空格字符串,会报异常,捕获就好,后面说异常
System.out.println(reader.read()); // 读取单个字符,返回的是ASCII码

输出:格式化输出,按照需要的格式输出内容

Java5中沿用C语言中输出函数printf(),可以使用这个函数来格式化输出。

double x = 3.926;
System.out.printf("%.2f",x); // 保留两位小数输出,自动四舍五入
// % 表示使用对应参数来替换,f表示转换符,下面是其他类型的转换符
转换符类型
d十进制整型
x十六进制整型
o八进制整型
e指数浮点类型
a十六进制浮点类型
s字符串
c字符
b布尔
f浮点类型

也可以使用String类中静态方法format()方法来格式化一个字符串,返回值是一个新字符串。

String result = String.format("x = %.2f",x);
System.out.println(result);

下面是对日期的格式化:

System.out.printf("日期为 ==》 %tF",new Date()); // 输出 日期为 ==》 2022-04-01
// 格式是已%t开头,下面表中任意一个字符结尾

1.6、流程控制

快级代码、条件语句(if、switch)、循环语句(while、for、do…while)、顺序语句。

条件语句

String type = "A";
// 会使用type与下面的case标签进行匹配,匹配成功就执行,不成功就匹配下一个,所有没匹配就执行default
// type只能是byte、short、char、int、枚举、String
// case的类型可以是基本数据类型常量、字符串常量,不能是变量
// 遇到break就跳出,没有break就继续匹配下一个
switch (type){
    case "A":
        System.out.println("a");
        break;
    case "B":
        System.out.println("b");
        break;
    default:
        System.out.println("错误");
}

循环语句

在循环语句中,如果使用浮点数作为结束条件的话,需要小心浮点数精度问题,有可能进入死循环。注意使用break(直接跳出循环)和continue(填过这次循环,进入下一次循环)

1.7、大数

如果基本数据类型精度不能满足要求,可以使用BigInteger(整数)和BigDecimal(浮点数),这两个类可以运算任意长度的数值。

BigInteger

BigInteger bigInteger = BigInteger.valueOf(1000); // 将普通数值装换成bigInteger
System.out.println(bigInteger);
BigInteger bigInteger1 = new BigInteger("10000000000000"); // 将一个字符串转换十进制数值
System.out.println(bigInteger1);

注意:在Java中,对于大数没有四则运算符,不能使用+、-等。只能通过方法来实现。

BigInteger add(BigInteger other); // 两个大数相加,返回一个新大数
BigInteger subtract(BigInteger other); // 减
BigInteger multiply(BigInteger other); // 乘
BigInteger divide(BigInteger other); // 除
BigInteger mod(BigInteger other); // 取余
BigInteger sqrt(BigInteger other); // 开平方根
BigInteger compareTo(BigInteger other); // 比较,小于other返回负数,相等返回0,大于返回正数

BigDecimal这个类和BigInteger类似,不详细介绍。

1.8、数组

数组声明、数组初始化,数组一旦声明了就不能改变它的长度,但是可以修改它的单个元素。在Java中允许长度为0的数组,长度为0不代表这个数组为null。初始化化数组但是未赋值,int的数组的初始值为0,boolean的初始化为false,对象数组的初始化为null

for each循环

for(x : collection) {}

它定义一个变量来暂存数组和集合中的每一个元素,collection必须是数组或者是实现了Iterable接口的类对象。但是这种方式没有通过下标来获取数组元素,后面需要下标很麻烦。

数组拷贝

在Java中,允许将一个数组变量赋值给另一个数组变量,这两个数组变量就指向同一个数组,一个数组改变,另一个数组也会发生改变。如果需要拷贝一个数组,但是元数组不发生改变,就需要Arrays.copyOf(),进行数组拷贝。

int[] array = new int[]{1,2,3,4};
// 参数1:拷贝的数组,参数2:新数组的长度;如果新数组长度小于元素组长度,只拷贝前面的,如果大于,其余的元素是默认值
int[] newArray = Arrays.copyOf(array,array.length()*2); // 返回一个新数组,与元数组没有任何关系

数组排序

可以使用Arrays.sort()对一个数组进行排序,从小到大。sort()方法使用的是快速排序法,可以所有数据类型进行排序,包括对象,只需要创建一个Comparator类来定义自己的排序规则即可。

例如下面:

package com.tanke.jichu;

import java.util.Arrays;
import java.util.Comparator;

/**
 * @author tanKe
 * Date on 2022/4/1  15:58
 */
public class Test {



    public static void main(String[] args) {


        Student[] students = new Student[3];
        students[0] = new Student("001","172","135");
        students[1] = new Student("002","173","135");
        students[2] = new Student("003","171","135");
        // 按照身高来排序
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                if (Double.parseDouble(o1.getHeight())>Double.parseDouble(o2.getHeight())){
                    return -1;
                }else if (Double.parseDouble(o1.getHeight())<Double.parseDouble(o2.getHeight())){
                    return 1;
                }
                return 0;
            }
        });
        for (Student student : students) {
            System.out.println(student);
        }
        //Student{id='002', height='173', weight='135'}
		//Student{id='001', height='172', weight='135'}
		//Student{id='003', height='171', weight='135'}
    }
}
class Student{
    private String id;
    private String height;
    private String weight;

    public Student() {
    }

    public Student(String id, String height, String weight) {
        this.id = id;
        this.height = height;
        this.weight = weight;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getHeight() {
        return height;
    }

    public void setHeight(String height) {
        this.height = height;
    }

    public String getWeight() {
        return weight;
    }

    public void setWeight(String weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id='" + id + '\'' +
                ", height='" + height + '\'' +
                ", weight='" + weight + '\'' +
                '}';
    }

}

Arrays数组类

static String toString(T[] a); // 将a数组以字符串形式返回,用逗号隔开
static T[] copyOf(T[] a,int length); // 拷贝一个数组
static T[] copyOfRange(T[] a,int start,int end); // 拷贝一个数组,从start开始拷贝,到end-1,如果end带入a.length()默认值填充
static void sort(T[] a); // 对数组进行排序
static int binarySearch(T[] a,T e); // 进行二分查找数组,如果查找存在e元素,返回e的下标,没有这个元素就返回负值
static void fill(T[] a,T e); // 将数组a的所有元素设置为e
static boolean equals(T[] a,T[] b); // 判断a数组和b数组是否相等,大小和下标相同对应元素是否相等 

多维数组

在Java中本身没有多维数组,可以理解为一个一维数组中的元素也是一个数组,理解为数组的数组。

int[][] array = new int[][]{{1,2,3},{4,5,6}};
// deepToString() 快速输出一个二维数组
System.out.println(Arrays.deepToString(array)); // 输出 [[1, 2, 3], [4, 5, 6]]

注意:在初始化数组的时候,高维必须定义,而低维可以后面定义,这样就可以形成不规则数组。

int[][] array = new int[3][]; // 这个3这个高维必须定义,而后面的低维可以后面定义成其他数据
for(int i=0;i<array.length();i++){
    array[i] = new int[i+1]; // 定义低维
}
2、面向对象程序设计

面向对象程序设计(OOP):程序由对象组成,每一个对象包含公开的特定功能和隐含的部分。

2.1、类和对象

类:是构造对象的模板。

对象:由类构造对象的过程称为创建类的实例,对象有三个特征,对象的行为、状态、标识。每一个对象都有自己的唯一标识。

类之间关系

  • 依赖:一个类中使用了另一个类的方法和属性。
  • 聚合:一个类中包含另一个类作为这个类的属性。
  • 继承:一个类由另一个类继承,拥有另一个类的部分属性和部分方法。

LocalDate类:表示日期的一个类,而Date是一个表示时间点的类。

static LocalDate now(); // 返回当前系统的日期 2022-4-5
static LocalDate of(int year,int month,int day); // 创建一个有参数构建的日期,如果参数不合法会报错
int getYear(); // 返回当前日期的年份
int getMonthValue(); // 返回当前日期的月份
int getDayOfMonth(); // 返回当前日期这个的月的天数
DayOfWeek getDayOfWeek(); // 返回一个DayOfWeek对象,通过这个对象的getValue()方法返回一个1-7的数值,表示当天星期几
LocalDate plusDays(int num); // 返回一个新的LocalDay对象,表示在当前日期后num天
LocalDate minusDays(int num); // 返回一个新的LocalDay对象,表示在当前日期前num天

构造器:用于初始化类数字属性字段。

  • 构造器与类同名。
  • 每一个类都有一个或多个构造器。
  • 构造器有0、1、多个参数。
  • 构造器没有返回值,连void也没有。
  • 构造器总是伴随new来调用的。
/**
 * @author tanKe
 * Date on 2022/4/5  21:09
 */
public class People {
    
    // 字段
    private String name;
    
    // 无参数构造器
    public People(){
        System.out.println("你好");
    }
    
    // 有一个参数构造器
    public People(String name){
        this.name = name;
    }
}

null引用

一个对象包含一个对象的引用,也可以是一个特殊的值null,表示没有引用任何对象,如果一个为null的对象,然后调用它的方法会报NullPointerException错误。所以在以后需要使用一个对象的方法时,需要明确这个对象是否为null。

类的访问权限:可以是使用public修饰类的数据,但是这种方式不安全,导致在任何地方都可以修改类中的数据。通常使用private修改属性,变成私有,但是在一个类,在方法中可以使用任何所属类的对象的私有数据。

class Student{
    private String name;
    
    public void test(Student s){
        // 虽然name是私有的,但是在类方法中依然可以使用,但必须是同一个类的所属对象
        return this.name.equals(s.name);
    }
}

final修饰符:可以修饰属性、类,修饰的属性,表示这个属性不能被更改一旦赋值后,修饰的类表示这个类的所有数据都不能修改,也可以表示这是一个最终类,不能被其他类继承。final修饰的属性是在构造方法后初始化的。

final修饰符,对于类型为基本数据类型和不可变类的字段有用,String就是一个不可变类,但是对于可变类会造成混乱。

// 不会指向另外的对象
private final StringBuilder builder;

// 错误
builder = new StringBuilder();
2.2、静态修饰符 static

静态属性:使用static关键字修饰的属性成为静态属性,这个类的所有实例都共享这个属性,静态属性属于类,而不是某一个对象。即使这个对象为null,也可以使用这个类的静态属性。

public class Demo06 {
    public static String name = "张三";

    // 所有这个类的对象实例相当于共享这个name,但是不属于某一个对象
    public static void main(String[] args) {
        Demo06 d1 = null;
        System.out.println(d1.name); // 输出张三
        Demo06 d2 = new Demo06();
        d1.name = "李四"; // 修改name值
        System.out.println(d2.name); // 输出李四
    }
}

静态方法:由static修饰的方法,静态方法不在对象上执行的方法,例如Math的pow()这个静态方法,直接通过Math.pow()使用,没有使用任何的Math对象。静态方法中不能使用非静态属性,但是可以使用静态属性。

  • 方法不需要访问对象状态,参数都是通过显示参数传递时使用静态方法。
  • 方法只需要访问类的静态属性。

静态工厂方法:静态方法还有一种用途就是使用静态工厂方法来创建一个对象。

为什么不使用构造器来实例对象呢?可能存在下面情况,需要静态工厂来实例对象。

  • 构造器的名称必须和类名相同,但是我们有时无法命名构造器。
  • 使用构造器时,无法改变构造对象的类型,而静态工厂方法可以起子类对象,来实现多态。

main方法:他是一个静态方法,所以不需要任何对象进行 *** 作,启动程序时没有任何对象,通过静态main方法来实例对象。

2.3、方法参数

在Java中,总是采用按值传递。方法得到的所有参数值是一个副本。方法不能修改传递给它的任何参数变量的内容。一共有两种类型参数:基本数据类型、引用数据类型。

基本数据类型:

int a = 10;
action(a); // 调用完后a的值没有发生变化
System.out.println(a); // 输出10

public void action(int a){ // a 相当于是一个副本,与数据没有关系
    a = a + 10;
}

引用数据类型:

Student y = new Student("101", "100", "100");
action(y);
System.out.println(y); // 输出id为100

public static void action(Student x){
    // y是x的引用副本,他们同时引用一个Student对象,这个方法结束后x引用就消失了,而y引用还是继续引用
    x.setId("100");
    // 当x修改了x引用的对象,y和x引用的同一个对象就发生改变
}

在Java中,都是按值传递不是按引用调用,例如下面:

public static void swap(Student a,Student b){
    Student temp = a;
    a = b;
    b = temp;
    // 方法结束后,a和b引用都会被丢弃
}

public static void main(Sting[] args){
    Student s1 = new Student("1", "100", "100");
    Student s2 = new Student("2", "100", "100");
    swap(s1,s2); // s1和s2没有交换成功
    System.out.println("s1=="+s1.toString()); // 输出1
    System.out.println("s2=="+s2.toString()); // 输出2
}

面试题一:

public static void main(String[] args) {
    int a = 10;
    int b = 20;
    method(a,b); // 编写method()方法的代码,满足下面的条件
    System.out.println("a = "+a); // 输出 a = 100
    System.out.println("b = "+b); // 输出 b = 200
}

public static void method(int a,int b){
    
    // 方式一:直接在这个方法中输出a = 100, b = 200;然后结束程序,不让程序执行下面的代码
        System.out.println("a = "+100);
        System.out.println("b = "+200);
        System.exit(0); // 结束程序

        // 方式二:通过修改System.out.println()方法
        PrintStream printStream = new PrintStream(System.out){
            // 重写println()方法
            @Override
            public void println(String x) {
                if ("a = 10".equals(x)){
                    x = "a = 100";
                }else if ("b = 20".equals(x)){
                    x = "b = 200";
                }
                // 调用父类的方法
                super.println(x);
            }
        };
        // 重新设置系统输出流
        System.setOut(printStream);
}

注意:不能通过在method()方法中通过修改a、b参数的值来实现,因为是参数是一个副本,方法执行完以后就会被回收。

面试题二:

public static void main(String[] args) {
    int[] ar1 = new int[]{1,2,3};
    char[] ar2 = new char[]{'a','b','c'};
    System.out.println(ar1); // 打印一个地址值
    System.out.println(ar2); // 打印abc
    // println()方法的重载中有char数组的重载
}
2.4、构造对象

重载:一个类中,方法名相同,参数不同,这就是方法的重载,在使用方法的时候,编译器会使用对应具体的方法。

// String类的构造器重载
String();
String(char[] a);
String(int i);

无参构造器:没有参数的构造器,通过常在构造器中初始化属性值,如果没有写构造器,会自动提供一个无参构造器。

有参构造器:带有参数的构造器,也可以初始化属性值。

class Student{
    private String name;
    
    // 无参构造器
    public Student(){
        name = "张三";
    }
    
    // 有参构造器
    public Student(String s){
        name = s;
    }
}

如果在实例化一个对象,在这个类中没有构造器,默认自动生成一个无参构造器,如果只存在有参构造器,会覆盖无参构造器,实例化时必须带参数,没有参数不能实例化。无参构造器和有参构造器可以同时存在。

调用另一个构造方法:在一个类中,有不同的构造方法,可以通过this(参数)来调用类中其它的构造方法,但是不能调用自己这个构造器,也不能回调之前已经调用过的构造器。

class Student{
    private String name;
    
    public Student(){
        System.out.println("无参构造器");
    }
    
    public Student(String name){
        // 必须在构造器的第一行
        this(); // 调用无参构造器
        this.name = name;
        System.out.println("有参构造器");
    }
}

初始化代码块:只要构造对象时,就会执行代码块中的内容,但是在构造方法之前执行的。

class Student{
    private String name;
    // 初始化代码块
    {
        System.out.println("执行初始化代码块");
    }
    public Student(String name){
        this.name = name;
    }
}

构造对象的执行过程:

  • 如果构造其中第一行中调用了其他构造方法,则执行第二构造器。
  • 没有调用其他构造器,初始化属性的默认值和初始化代码块。
  • 执行构造器中的主体代码。
2.5、静态导入

有一种import语句允许导入静态方法和静态属性,而不只是类。这样就可以省略调用静态方法、静态属性前面的类名。

import static java.lang.Math.*;
// 这样就可以使用Math的静态方法和属性
double sqrt = sqrt(100);
System.out.println(PI);

在Java中,如果在java.lang包下是核心包,如果使用这个包下的类和对象,可以不使用import导入就可以使用,例如String、StringBuilder等。

3、继承

继承:使用extends关键字从一个已经存在的类中派生出一个新类,已存在的类成为父类(超类),新产生的类称为子类(派生类)。子类可以继承父类的所有属性和方法,但是可以存在访问权限的问题,子类可能不能访问父类中private修饰的方法。

class Person {
    String name;
    int age;
}

// 继承至Person类,拥有Person中可以继承的属性方法
class Teacher extends Person{
    
}

通过扩展父类定义子类的时候,只需要指定子类与父类不同的方法,所以在程序设计中应该把最一般的方法放在父类中,特殊的方法放在子类中,将通用的功能抽取到父类中。但是一个子类只能有一个父类。

3.1、重写

​ 子类可以使用从父类中继承的方法,但是如果父类的方法不能满足子类的要求,子类可以重写父类的方法,按照自己的方法执行。

class Person {
    private String name;
    private int age;

    public void eat(){
        System.out.println("我在吃饭!!");
    }
}

// 继承至Person类,拥有Person中可以继承的属性方法
class Teacher extends Person{

    // 重写父类的方法
    @Override
    public void eat(){
        // 调用父类的eat(),方法,不能直接使用eat(),这会让编译器认为是在调用子类中的eat()方法
        super.eat();
        System.out.println("老师在吃饭"); // 编写满足子类的方法
    }

}

继承可以在子类中添加属性、方法或者重写父类方法和属性,但是不能删除任何属性和方法。子类重写父类方法时,子类方法的修饰符权限不能低于父类方法中的权限。

注意:

  • 子类不能重写父类中private修饰的方法,但是子类可以继承父类的静态方法。
  • 如果父类方法的返回值类型是void,则子类重写方法的返回值类型也必须是void。
  • 如果父类方法的返回值类型是A,则子类重写的返回值类型必须是A类型以及A类型的子类。
  • 如果父类方法的返回值类型是基本数据类型,则子类方法的返回值类型也必须相同。
  • 子类重写方法抛出的异常不大于父类方法抛出的异常。
  • static修饰的方法不能被重写。
3.2、子类构造器

​ 在子类中,不能访问父类中private修饰的属性,所以需要通过一个构造器来初始化这些私有字段,可以使用super这个关键字来调用父类的构造器,这句语句必须在子类构造器的第一句话。

class Person {
    private String name;
    private int age;

    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    
    public void eat(){
        System.out.println("我在吃饭!!");
    }
}

// 继承至Person类,拥有Person中可以继承的属性方法
class Teacher extends Person{

    public Teacher(String name, int age) {
        // 调用父类的构造器初始化父类中的私有变量,必须在第一句
        super(name, age);
    }

    // 重写父类的方法
    @Override
    public void eat(){
        // 调用父类的eat(),方法,不能直接使用eat(),这会让编译器认为是在调用子类中的eat()方法
        super.eat();
        System.out.println("老师在吃饭"); // 编写满足子类的方法
    }

}

如果在子类中没有显式的调用父类的构造器,在实例子类的时候会自动调用父类的无参构造器,如果父类中没有无参构造器,编译器会报错。

this和super关键字:

​ this:表示当前这个对象,1、作用隐式参数;2、调用本类中的其它构造器。

​ super:1、调用父类的方法;2、调用父类的构造器。

在调用构造器时,this和super都必须要求在第一行,这就矛盾了,所有this和super,不能在同一个构造器中同时调用构造器。

子类实例化对象:

​ 当我们通过子类实例化对象时,要么显示使用 super(参数) 调用父类的有参构造器,要么默认调用父类的无参构造器,直到调用到Object(所有类的父类)的构造器为止。正是因为加载了所有父类的结构,在子类对象才可以使用对应的结构。虽然调用了父类的构造器,但是不能理解为创建了父类的对象,自始至终只创建了一个你new的子类对象。

3.3、多态

一个对象变量可以应用多种实际类型的现象称为多态。

Person person1 = new Person();
Person person2 = new Person();
Person person3 = new Teacher();
// 上面三个对象都是Person类型的变量,但是前两个引用的是Person对象,第三个引用的是Teacher对象
person1.eat(); // 调用Person的eat方法
person2.eat(); // 调用Person的eat方法
person3.eat(); // 调用Teacher的eat方法
// 注意:person3的类型依然是Person,不能调用Person中不存在方法

即使person3是Person类型,但是虚拟机知道它引用的是Teacher对象,在运行时能够自动的选择适当的方法,这就是动态绑定。

虚拟方法调用:多态中,在编译期只能调用父类中声明的方法,但是在运行期间,实际执行的是子类重写的方法。(编译看左边,执行看右边)

在Java程序中,对象变量是多态的,一个父类类型的变量,既可以引用父类型的对象,也可以引用任何一个子类的对象。但是不能将父类的引用对象给子类类型变量。

注意:

  • 没有继承就没有多态。
  • 需要有方法的重写。
3.4、理解一个方法调用的过程

编译器查看对象的类型和方法名

在同一个类中可能存在多个方法名相同的方法,但是参数不同,以及在父类中名称相同并且可以访问的方法,这样就得到了一个方法列表。

确定参数类型

编译器根据方法调用中的参数类型,在方法列表中匹配一个参数类型完全匹配的一个方法,如果匹配到多个方法就会报错。在参数匹配的过程中存在参数类型的自动转换,例如int–> double,子类–>父类等。

在Java中,方法的名称和参数列表成为方法的签名,这样就可以确定一个不同的方法。

静态绑定

如果方法是private、static、final或者是构造器,编译器就可以很快确定调用的是哪个方法,这个就是静态绑定。

动态绑定

程序运行时,通常使用的是动态绑定,每次调用的时候都要去查询类和父类中方法,这样很消耗资源,所以虚拟机会预先为每一个类计算一个方法表,列出所有方法的签名以及实际调用的方法,这样调用方法时,就可以直接查询这个表即可。

阻止继承:阻止这个类不再派生子类,以及阻止子类不能重写父类中大方法。

前面知道final修饰的属性一旦定义,它的值就不能再修改,使用final来修饰类,表示这个类是终类,不能派生子类。如果final修饰方法,表示这个方法不能被子类重写。

权限修饰符访问范围

  • private: 只能本类可以使用。
  • public:对外部所有可见。
  • protected:对本包类以及子类可以访问。
  • 缺省:对本包类可以访问。
3.5、Object 父类

​ Object这个类是所有类的祖先,Java中的每一个类都继承了Object类,不需要显示使用extends来继承,默认自动继承。所以可以使用Object类型的变量引用任何类型的对象。当然Object类型的变量只能用于各种类型的一个泛型,如果需要具体使用类型方法和属性,需要将Object类型强制装换成你需要的对象,但是需要你提前知道是什么对象,否则无法强制转换成功。

equals()方法

Object类中的equals()方法用于检查一个对象是否等于另一个对象,就是确定两个对象的引用是否相等。但是如果需要自定义对象相等的条件时,需要重写Object类中equals()方法。

// 如果Student对象的名称相同就判定为相等
@Override
public boolean equals(Object object) {
    // 如果是同一个引用就必然相等
    if(this == object){
        return true;
    }
    if(object == null){
        return false;
    }
    
    // 需要将Object对象转换成自己 *** 作的对象,首先需要判断
    if (object instanceof Student){
        Student s = (Student)object;
        return this.name.equals(s.getName());
    }
    return false;
}

注意:在判断对象属性的是否相等,对于引用数据类型,可能存在null的情况,所以需要改写。通过使用Objects.equals(a,b)来判断。Objects是一个工具类与Object不同。

if (object instanceof Student){
    Student s = (Student)object;
    // Objects.equals()
    // 如果两个参数都为null,就返回true
    // 如果有一个参数为null,就返回false
    // 如果两个参数都不为null,就调用a.equals(b)进行判断
    return Objects.equals(this.name,s.getName( ));
}

重写equals()方法:

  • 显示参数命名为otherObject,后面需要强制转换成名为other变量。
  • 检测 this 和 otherObject 是否相等。
  • 检测otherObject是否为null,为null直接返回false。
  • 选择instanceofgetClass
    • instanceof:检测的对象是否是同一个类型,如果是 父类 instanceof 子类也可以返回true,对于在子类中的比较方式没有改变可以使用。
    • getClass:检测对象是否是同一个类,如果是父类的Class对象和子类的Class对象不同,如果在子类的比较方式和父类不同可以使用这种方式。
  • 将otherObject强制转换成相应的类型变量。
  • 要求对象的属性进行比较,基本数据类型使用 == ,而引用数据类型使用Objects.equals()。
@Override
public boolean equals(Object otherObject) {
    
    if(this == otherObject){
        return true;
    }
    
    if (otherObject == null){
        return false;
    }
    
    // 对于子类定义不同匹配规则
    if (this.getClass() != otherObject.getClass()){
        return false;
    }

    Student s = (Student)otherObject;
    
    return Objects.equals(this.name,s.getName()) && this.age == s.getAge();
}

hashCode()方法:由对象导出的一个整型值,通常不同对象的hashCode值不同。每一个对象都有一个默认的散列码,它由对象的地址值得到的。

System.out.println(student1.hashCode()); // 21685669
System.out.println(student2.hashCode()); // 2133927002

如果需要重新定义一个hashCode()方法,重写父类中的方法,就必须要求用户合理组合对象字段的散列码并返回。但是通产对于null的对象,使用Object.hashCode()方法更加安全,如果为null,就返回0,不为空就调用参数的hashCode()。也可以直接使用Objects.hash()方法,参数是可变参数,通过给定的参数返回一个散列码。

@Override
public int hashCode() {
    // 通过name、age字段返回一个不同的散列码
    return Objects.hash(name, age);
}

注意:equals()方法和hashCode()方法必须兼容,例如x.equals(y)返回true,那么x与y的hashCode()返回值也必须相同。equals()方法定义name属性来区别不同,那么hashCode()也必须通过name属性来获得散列码。

3.6、包装类、自动装箱、自动拆箱

包装类:针对于基本数据类型定义了相应的引用数据类型,这个就是包装类。这样就是用使用包装类的方法来进行 *** 作。

装箱的方式:

// 1、调用包装类的构造器
int a = 10;
Integer a1 = new Integer(a); // 可以使用包装类的方法
System.out.println(a1.toString());

// 2、将一个纯数字的字符串包装成一个包装类
String s = "123";
Integer s2 = new Integer(s);
System.out.println(s2.toString());

// 3、自动装箱:JDK5.0开始,一个基本上类型可以直接赋值给你个对应包装类的变量,过程中发生自动装箱,也可以创建对应的包装类
Integer x = 10; // 但是基本数据类型必须对应自己的包装类,不能将double类型装箱成一个Integer包装类

拆箱的方式:

// 1、使用包装类的xx.XXValue()方法
String s = "123";
Integer s2 = new Integer(s);
int b = s2.intValue();
System.out.println(b);

// 2、自动拆箱:一个包装类可以直接赋值给一个基本数据类型
Integer x = new Integer(10);
int a = x;

自动装箱和自动拆箱是编译器的工作,而不是虚拟机的工作。

基本数据类型、包装类与String类型相互装换:

// 由于有自动装箱和拆箱,我们可以将基本数据类型看成一类

// 1、字符串拼接
String s1 = 10 + "";

// 2、使用String.ValueOf(数据),有多种数据类型的重载
int a = 10;
String s2 = String.ValueOf(a);

// 3、String类型转换成包装类或基本数据类型,采用包装类的xxx.parseXXX(字符串)方法,字符串必须是纯数字
String s2 = "123";
Integer i = Integer.parseInteger(s2);

经典面试题:

Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
// == 比较的是地址,i1和i2是不同对象,就返回false
System.out.println(i1 == i2);  // false


// 下面两个是一个知识点:
/*
	在包装类中有一个静态内部类,这个类中存在一个缓存,值得范围是 -128~127,每一个值都有一个相同的对象,地址也相同
	如果赋值在这个范围,包装类就会在这个范围中返回对应的包装类,所以i3和i4都是同一个对象,而i5和i6超过这个范围,就自己实例化	对应的对象。
*/

Integer i3 = 1;
Integer i4 = 1;
System.out.println(i3 == i4);  // true

Integer i5 = 128;
Integer i6 = 128;
System.out.println(i5 == i6);  // false
Integer i1 = new Integer(1);
Double d1 = new Double(2.0);
System.out.println(true ? i1 : d1); // 输出1.0
// 在这个三元表达式中,i1会自动拆箱成int,然后提升成double,然后在装箱成Double包装类。
// 因为三元运算符 前后条件的类型必须是同一种类型。
// 我们知道Java是按值进行参数传递的,所以不能直接修改形参对应的实参的值,但是包装类是否能够修改对应的值呢?

public static void main(String[] args) {

    Integer i = 10;
    System.out.println(i); // 输出10
    tripe(i);
    System.out.println(i); // 输出10
    // 因为包装类的值是不可以改变的

}
public static void tripe(Integer integer){
    // 形成了一个新的对象
    integer = integer * 2;
}

包装类扩展方法:

static String toString(int i,int radix); // 将i这个十进制参数,返回i对应radix进制的字符串
static int parseInt(String s,int radix); // s字符串是纯数字字符串,radix表示s的进制,返回s对应的十进制int
static Integer ValueOf(String s,int radix); // 与上相同,只是返回的是Integer包装类
4、抽象类及接口 4.1、抽象类

抽象类:随着继承层次的中一个个新子类的定义,类越来越具体,而父类越来越抽象根据有通用性,以至于父类没有具体的实例,使用abstract修饰的类,就是抽象类。

// 抽象类
abstract class Person{

}
// 实现类
class Teacher extends Person{

}

注意事项:

  • 抽象类不能使用 new 关键字来实例化,但是抽象类中有构造器,是提供给子类使用的。
  • 抽象类不能实例化,在开发中通常通过抽象类的子类来实例化。
  • 抽象类可以包含自己的属性和具体的方法。
  • 拥有一个或多个的抽象方法的类必须是抽象类,但是抽象类中可以没有抽象方法。
4.2、抽象方法

抽象方法:在抽象类中使用abstract修饰并且没有方法体的方法称为抽象方法。

// 抽象方法,只有方法的声明,没有方法体
public abstract void say();

注意事项:

  • 抽象方法只有声明,没有方法体。
  • 含有抽象方法的类必定是抽象类,但是抽象类可以不包含抽象方法。
  • 只有子类中重写了父类中的所有抽象方法,子类才可以实例化。
  • 若子类中没有全部实现父类的抽象方法,那么子类也需要修饰成一个抽象类。
  • 抽象方法不能是私有方法,这样继承的子类会继承抽象私有的方法,但是不能重写这个方法。
  • 抽象方法不能是static方法,因为static方法是类方法,方法属于类,子类中可以写这个方法,但是不属于重写。
  • 抽象方法不能是final方法,final方法不能被继承,所有更不可能被重写。

如果子类不能重写父类中所有的抽象方法,子类就不能实例化,所以所有的抽象方法都要可以被子类重写。

4.3、匿名子类

​ 抽象类不能实例化,只有通过子类来事实现抽象类中的方法,有时候我们只需要使用一次子类来实现方法,这就可以使用匿名子类,与之前的匿名对象类似,方便。

public class Demo02 {
    public static void main(String[] args) {
        
        // Person类不能实例化
        Person p = new Person() { // 创建了一个匿名的子类,并实现了父类中的方法
            @Override
            public void say() {
                System.out.println("我是匿名子类对象,我只能使用一次");
            }
        };
    }
}
// 抽象类
abstract class Person{
    // 抽象方法,只有方法的声明,没有方法体
    public abstract void say();

}
4.4、接口

接口:有时候需要从几个类中派生出一个子类,但是Java中不支持多继承,所有产生接口,接口就是定义一些功能,如果你需要实现这些功能,你就可以实现这个接口,然后重写其中的方法,这就是接口。

// 接口:使用interface修饰的类就是接口,在Java中一个类可以实现多个接口
public interface Demo03 {
    // 定义功能
    public String say();
}

接口的结构

  • JDK7.0之前:只能定义全局常量和抽象方法(功能)。
  • JDK8.0及之后:可以定义静态方法和默认方法。
// 全局静态常量,可以直接通过接口直接使用
public static final String MESSAGE = "Hello,World"; // 接口中的静态属性可以被实现类继承
// 可以省略public static final,接口中属性,默认给你加上这三个修饰符
String DATA = "数据";

// 抽象方法:public abstract可以省略不写,不写自动加上
public abstract String say();
void hello();

注意事项:

  • 接口中的静态常量和抽象方法必须的修饰符必须是public,如果是缺省,默认自动给你加上public。
  • 在接口中绝对不能存在实现方法。
  • 接口中不能写构造器,接口也不能实例化。
  • 接口与接口之间是可以存在继承关系,并且是多继承关系。

接口实现:通过implements关键字来实现某个接口,实现类中必须全部实现接口中定义的抽象方法。

public class Demo04 implements Demo03{
    // 实现的方法
    @Override
    public String say() {
        return null;
    }

    @Override
    public void hello() {}
}

匿名实现类:没有创建接口的实现类,直接通过匿名的方式来实现接口的方法。

public class Test{
    public static void main(String[] args){
        // InterfaceTest是一个接口
        InterFAceTest test = new InterFaceTest(){ // 创建了一个匿名的实现类非匿名对象
            // 实现方法
            @Override
            public String say(){ }
        }
    }
}

应用:存在Employee和Manage两个类,Employee是Manage的父类,两个类中有name、id、salary属性,Manage多一个bouns奖金属性,有getSalary()这个方法,现在需要通过工资属性来进行排序。

要实现排序功能需要实现Comparable接口,并重写其中的sort方法,然后通过工具类进行排序。

public class Demo05 {
    public static void main(String[] args) {
        Employee[] employees = new Employee[3];
        employees[0] = new Employee("张三",1,500);
        employees[1] = new Employee("李四",1,700);
        employees[2] = new Manage("王五",2,200,200);
        // 排序,从小到大
        Arrays.sort(employees);
        for (Employee employee : employees) {
            System.out.println(employee.name);
        }
        // 输出王五、张三、李四
    }
}
class Employee implements Comparable{
    String name;
    int id;
    double salary;

    public Employee(){}

    public Employee(String name,int id,double salary){
        this.name = name;
        this.id = id;
        this.salary = salary;
    }
    public double getSalary(){
        return salary;
    }

    @Override
    public int compareTo(Object o) {
        Employee employee = (Employee)o;
        double v = this.getSalary() - employee.getSalary();
        if (v>0){
            return 1;
        } else if ((v < 0)) {
            return -1;
        }else {
            return 0;
        }
    }
}
class Manage extends Employee{
    double bouns;

    public Manage() {
    }
    public Manage(String name, int id, double salary, double bouns) {
        super(name, id, salary);
        this.bouns = bouns;
    }

    public double getSalary(){
        return super.getSalary()+this.bouns;
    }
}

但是上面的代码存在一点问题,和之前的子类与父类之间判断相等的问题,如果我不比较工资了而比较奖金,这样如果Employee没有奖金的这个属性,就会出现问题。如果子类和父类的比较方式没有什么区别,可以使用这种方式,不同的话需要在比较前前通过getClass()方法判断是否是同一个类,不是同一个类就抛出异常,是同一个类继续比较。

经典面试题:

interface A{
    int x = 1;
}
interface B{
    int x = 2;
}
class C implements A,B{
    public void test(){
        System.out.println(C.x); // x继承至A、B,但是编译器不知道是哪个,直接报错
    }
}
interface A{
    int x = 1;
}
class B{
    int x = 2;
}
class C extends B implements A{
    public void test(){
        System.out.println(this.x); // x继承至A、B,不知道是哪个,直接报错
        // 可以使用下面的方式访问
        System.out.println(super.x); // 父类的x
        System.out.println(A.x); // 接口的x
    }
}
public class Demo08 {
    public static void main(String[] args) {
        D1 d = new D1("张三");
        d.test();
    }
}
interface A1{
    void test();
}
interface B1{
    void test();
}
interface C1 extends A1,B1{
    D1 d1 = new D1("张三");
}
class D1 implements C1{
    String name;

    public D1(String name) {
        this.name = name;
    }
    public void test(){
        d1 = new D1("王五"); // 这里有问题,因为C1中的d1是public static final,常量不能在被修改,编译错误
        System.out.println(d1.name); // 这点没有问题,可以表示C1的实现,而C1的实现可以表示为即使A1的实现也是B1的实现
    }
}
4.5、Java8接口新特性

静态方法:

interface InterfaceTest {
    // public可以省略,但是static不能省略
    public static void test(){
        System.out.println("这是接口中的静态方法!");
    }
}

接口中的静态方法只能由接口来直接调用,实现类不会继承这个静态方法,所以实现类不能使用接口中定义的静态方法。

默认方法:

// default不能省略
default void test1(){
    System.out.println("这是接口的默认方法");
}

接口中的默认方法可以被实现类继承,实现类可以使用接口中的默认方法,也可以直接重写接口中的默认方法,但是不是一定要重写。

// 重写test1()方法
@Override
public void test1() {
    System.out.println("这是实现类重写接口中的默认方法");
}

默认方法冲突问题

  • 当一个实现类实现一个接口,右继承了一个父类,父类和接口中都定义了一个名称、参数相同的默认方法,并且子类没有重写的情况下,那么实现类默认调用的是父类中继承的方法(父类优先),如果重写,就直接使用实现类中重写的方法。
public class Demo01 {
    public static void main(String[] args) {
        A a = new A();
        a.test1();
    }
}
interface InterfaceTest {
    // public可以省略,但是static不能省略
    public static void test(){
        System.out.println("这是接口中的静态方法!");
    }

    // default不能省略
    default void test1(){
        System.out.println("这是接口的默认方法");
    }
}
class SuperClass{
    public void test1(){
        System.out.println("这是父类的方法");
    }
}

class A extends SuperClass implements InterfaceTest{  
    // 重写的方法
    public void test1(){
        // 调用父类中的方法
        super.test1();
        // 调用接口中的方法
        InterfaceTest.super.test1();
    }
}
  • 如果一个实现类实现两个接口,这两个接口中都一个同名同参数的默认方法,这时候就需要通过重写接口中的方法来使用实现类中的方法,如果不重写就会报错。如果存在继承的父类中有这个同名同参数的方法也会使用父类中的方法(父类优先)。
4.6、内部类

内部类:在Java中,允许一个A类声明在一个B类中,则A类就是内部类,而B类是外部内。

内部类分为:成员内部类(静态内部类、非静态内部类)和局部内部类(方法内部类、代码块内部类、构造器内部类)。

public class Demo02 {

    // 成员静态内部类
    static class A{ }

    // 成员非静态内部类
    class B{ }

    {
        // 代码块局部内部类
        class C{ }    
    }
    
    public void method(){
        // 方法局部内部类
        class D{ }
    }
    
    public Demo02(){
        // 构造器局部内部类
        class E{ }
    }
}

为什么要使用内部类呢?

  • 内部类可以对同一个包下的类进行隐藏。
  • 内部类可以使用外部内的属性方法等,包括private修饰的。

成员内部类:分为静态成员内部类和非静态成员内部类。

  • 作为类的成员属性,可以调用外部内的属性、方法等。
  • 作为一个类,可以拥有自己的属性、方法、构造器等。
class B{ 
    public void test(){
        // 可以直接调用外部内的方法,但是如果外部内的方法重名的话,可以使用外部内名.this.method()
        Demo02.this.test2();
    }
}

成员内部类可以使用自己的属性和方法,也可以使用外部类的属性和方法,因为内部类属性对象中总有一个隐式引用,指向外部类对象,这个引用在内部类中不可见。这个应用一般是在内部类的构造器中初始化的,无参构造器会自动产生一个隐式引用。只有内部类的修饰符可以是private,这样这个内部类只能在外部类区域中实例化。

如何实例化一个成员内部类?

public static void main(String[] args) {
    // 对于成员静态内部类
    Demo02.A a = new Demo02.A();
    a.test();
    // 对于成员非静态内部类
    Demo02 d = new Demo02(); // 由于是非静态的,所有首先要创建一个外部内对象
    Demo02.B b = d.new B();
}

局部内部类的使用:通常用余光一个方法,然后返回一个实现某个接口的实现类。

public Comparable<String> method(){
    // 方法局部内部类
    class D implements Comparable<String>{
        @Override
        public int compareTo(String o) {
            return 0;
        }
    }
    return new D();
}

注意事项:

  • 内部类中不能存在静态方法,以及内部类中的所有静态属性必须需要final修饰,且编译的时候赋值一个初始值。
  • 声明局部内部类时,不能有访问修饰符。
  • 局部内部类的作用只局限于这个声明的快中,除了这个快,没有任何方法知道这个内部类存在。
  • 在局部内部类中,如果使用这个方法中的变量,那么这个变量必须是final修饰的变量,JDK8之后可以省略final,但是还是final不能修改。
public Comparable<String> method(){
    final int m = 10; // JDK8之后可以省略final
    // 方法局部内部类
    class D implements Comparable<String>{
        public void test3(){
            System.out.println(m); // 使用了这个方法中的局部变量,这个变量就会变成final
        }
        @Override
        public int compareTo(String o) {
            return 100;
        }
    }
    return new D();
}

静态内部类:如果需要只是简单的将一个内隐藏在,而不需要一个外部的引用,需要将这个内部类使用static修饰,这样就不会有一个外部内的引用。

static class A{
    public void test(){
    }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/741175.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-28
下一篇 2022-04-28

发表评论

登录后才能评论

评论列表(0条)

保存