【Java基础】Java入门知识与面向对象基本概念:8大基本数据类型、类与对象、封装、数组、String类、内部类、链表

《第一行代码:Java》第1-3章内容读书笔记

【Java基础】《第一行代码:Java》1-3章

第1章、简介

public class

Java中对于类的定义有两种:

  • public class 定义:类名称必须与文件名称保持一致,且类名首字母必须大写。
  • class 定义:类名称可以与文件名不一致,

第2章、程序基本概念

2.3 数据类型划分

数据类型:
  • 基本数据类型:

    • 数值型:byte(1字节)、short、int、long、float、double
    • 字符型:char(2字节)
    • 布尔型:boolean
  • 引用数据类型:

    • 类:class
    • 接口:interface
    • 数组
整型

任何的数字常量都为int型

long num1 = 100;  // 100为int型,但因为没有超过int能表示的范围,所以这里的赋值正确
long num2 = 2147483650L;  // 这个数已经超过int型的最大范围,所以要在它后面加个L才能把它赋值给num2
字符型

Java使用的是十六进制的UNICODE编码,此类编码可以包括任意的文字,为衔接其他编程语言的编码问题(如C++使用的ASCII码),此编码包括了ASCII的部分编码,且编码顺序一致。(如:A在ASCII和UNICODE中的编码都是65)

String数据类型
  • String表示一个字符串,String要求使用双引号 “ ” 声明其内容。

  •   String str = "Hello World!";  // str为字符串变量,"Hello World!"为字符串常量
      str = str + "Hello Siri!";  // +号可以将两个字符串连接起来
    
  •   int num1 = 100;
      double num2 = 99.0;
      String str = "加法结果:" + (num1 + num2);  // 所有的数据类型遇到String的"+"时,都会先把该数据转换为String型数据。
      System.out.println(str);  // 加法结果:199.0
    

2.4 运算符

++、–
  • ++变量、–变量:先进行变量内容的自增或自减,再使用变量进行数学计算
  • 变量++、变量–:先使用变量内容进行计算,再实现自加或自减
int num1 = 10;
int num2 = 10;
int result1 = ++num1 + num2;  // result1 = 21
int result2 = num2++ + num1;  // result2 = 21
&和&&

条件1 & 条件2 和 条件1 && 条件2

  • &:
    • 用于逻辑运算,普通与,即使条件1不满足,也会去验证条件2
    • 用于数值运算(位运算),按位与
  • &&:短路与,如果条件1不满足,则直接跳过,不验证条件2
if ((1 == 2) & (10 / 0 == 0));  // 会报错,因为10/0不存在
if ((1 == 2) && (10 / 0 == 0));  // 不会报错,因为验证完1==2为否之后,就不会再去验证第2个条件了
|和||

条件1 | 条件2 和 条件1 ||条件2

  • |:
    • 普通或,即使条件1满足,也会去验证条件2
    • 用于数值运算(位运算),按位或
  • ||:短路或,如果条件1满足,则直接跳过,不验证条件2
if ((1 == 1) | (10 / 0 == 0));  // 会报错,因为10/0不存在
if ((1 == 1) || (10 / 0 == 0));  // 不会报错,因为验证完1==1为否之后,就不会再去验证第2个条件了
位运算
  • ~:按位非 ,&:按位与 ,|:按位或 ,^:按位异或 ,>>:位右移 ,>>:位左移
continue和break
  • continue:退出本次循环
  • break:退出整个循环

2.6 方法的定义及使用

Java中的方法(Method)即其他语言中的函数(Function)

方法的定义
public static 返回值类型 方法名称(参数类型 参数变量, ……){  // 方法名称首字母小写,之后每个单词首字母大写
    方法体;
    return 返回值;  // 如果返回值类型为void则不需要return
}
方法的重载

方法的重载:虽然方法名相同,但由于传入的参数的个数和类型不同,而执行不同的方法体。

注意:

  • 方法的重载虽然可以返回不同的类型,但从标准开发的角度考虑,建议重载后的方法使用同一种返回值类型

第3章、面向对象基本概念

3.1 面向对象简介

面向对象语言的3个主要特征:封装性、继承性、多态性

多态性

多态是指允许程序中出现重名的现象,Java语言中含有方法重载与对象多态两种形式的多态。

  • 方法重载:见2.6中方法的重载
  • 对象的多态:子类对象可以与父类对象进行相互转换,而且根据其使用的子类不同完成的功能也不同。

3.2 类与对象

对象的声明与实例化:
// 声明并实例化对象
类名称 对象名称 = new 类名称();
// 分步完成
类名称 对象名称 = null;  // 声明对象
对象名称 = new 类名称();  // 实例化对象
堆与栈
  • 堆内存(heap):保存每一个对象的属性内容(数据成员),堆内存需要用关键字 new 才可以开辟,如果一个对象没有对应的堆内存指向,则无法使用。
  • 栈内存(stack):保存的是一块堆内存的地址数值,可以把它想象成一个 int 型变量,每一块栈内存只能存放一块堆内存地址。
  • 即堆内存中保存着对象的真实数据,即对象的属性内容,而栈内存则保存着这些堆内存的地址,可以简单的认为栈内存中保存着对象的名称

从此处就可以看出为什么类在Java中被称为引用数据类型

引用数据的初步分析
  • 引用传递使整个Java中的精髓所在,而引用传递的核心概念也只有一点:一块堆内存空间(保存对象的属性信息)可以同时被多个栈内存共同指向,则每个栈内存都可以修改同一块堆内存空间的属性值。

  • 最关键的还是关键字 new ,一定要注意的是,每一次使用关键字 new 都一定会开辟新的堆内存空间。所以如果再代码里声明两个对象,并且使用了关键字 new 为两个对象分别进行对象的实例化,Name一定是各自占有各自的堆内存空间,且不会相互影响。

Book bookA = new Book() ;		// 声明并实例化第一个对象
Book bookB = null ;				// 声明第二个对象
bookA.title = "Java开发" ;	   // 设置第一个对象的属性内容
bookA.price = 89.8 ;			// 设置第一个对象的属性内容
bookB = bookA ;  				// 引用传递
bookB.price = 69.8 ;			// 利用第二个对象设置属性内容
bookA.getInfo() ;				// 调用类中的方法输出信息
// 执行结果:
//        图书名称:Java开发,加个:69.8
Book bookA = new Book() ;		// 声明并实例化第一个对象
Book bookB = new Book() ;		// 声明并实例化第二个对象
bookA.title = "Java开发" ;	   // 设置第一个对象的属性内容
bookA.price = 89.8 ;			// 设置第一个对象的属性内容
bookB.title = "JSP开发" ;		   // 设置第二个对象的属性内容
bookB.price = 69.8 ;			// 设置第二个对象的属性内容
bookB = bookA ;  				// 引用传递,bookB指向的堆内存变成了bookA指向的地方了
bookB.price = 100.1 ;			// 利用第二个对象设置属性内容
bookA.getInfo() ;				// 调用类中的方法输出信息
// 执行结果:
//        图书名称:Java开发,加个:100.1
GC处理

GC在Java中的核心功能就是堆内存中的对象进行内存的分配与回收。当GC检测到一个堆中的某个对象不再被栈所引用时,就会不定期的对这个堆的内存中保存的对象进行回收。

在Java中针对垃圾收集也提供了多种不同的处理分类。

  • 引用计数:一个实例化对象,如果有程序使用了这个引用对象,引用计数加1,当一个对象使用完毕,引用计数减1,当引用计数为0时,则可以回收。
  • 跟踪收集:从 root set(包括当前正在执行的线程、全局或者静态变量、JVM Handles、JNDI Handles)开始扫描有引用的对象,如果某个对象不可到达,则说明这个对象已经死亡 ( dead ),则GC可以对其进行回收。也就是说:如果A对象引用了B对象的内存,那么虚拟机会记住这个引用路径,而如果一个对象没有在路径图中,则就会被回收。
  • 基于对象跟踪的分代增量收集:所有的对象回收要根据堆内存的结构划分来进行收集

3.3 封装性初步分析

就是将类中的属性进行私有化操作,并且需要加上setter和getter两种方法。

3.5 匿名对象

按照之前的内存关系来讲,对象的名字可以解释为在栈内存中保存,而对象的具体内容在堆内存中保存,这样一来,没有栈内存指向堆内存空间,就是一个匿名对象。

public class TestDemo {
    public static void main(String args[]) {
        new Book("Java开发", 69.8).getInfo();			// 匿名对象,只能使用一次,一次之后就会成为垃圾,等待被GC回收
    }
}

3.6 简单Java类

简单Java类是一种在实际开发中使用最多的类的定义形式。对于简单Java类有以下基本开发要求

  • 类名必须存在意义
  • 类中所有属性必须private封装,封装后的属性必须提供setter、getter
  • 类中可以提供任意多个构造方法,但是必须保留一个无参构造方法
  • 类中不允许出现任何输出语句,所有信息输出必须交给被调用处输出
  • 类中需要提供有一个取得对象完整信息的方法,暂定为getinfo(),而且返回String型数据

3.7 数组

数组属于引用型数据,所以在数组的操作过程中,也一定会牵扯到内存的分配问题

数组基本概念

数组的声明并开辟:

数据类型 数组名称[] = new 数据类型[长度];
数据类型[] 数组名称 = new 数据类型[长度];

分步完成:

数据类型 数组名称[] = null;  // 声明数组
数组名称 = new 数据类型[长度];  // 开辟数组

数组的静态初始化

  •   // 格式一:简化格式
      数据类型 数组 名称[] = {值, 值, ...};
      // 格式二:完整格式
      数据类型 数组名称[] = new 数据类型[] {值, 值, ...};
    

虽然数组支持顺序的数据访问操作,但是数组有一个最大的缺点:长度不能被改变,因此在开发中不会直接应用数组,但会使用数组的概念(利用类集框架来解决)

数组操作方法
System.arraycopy

System.arraycopy(源数组名称, 源数组复制开始索引,目标数组名称, 目标数组复制开始索引, 长度):将一个数组的部分内容复制到另一个数组中

int dataA[] = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };			// 定义数组
int dataB[] = new int[] { 11, 22, 33, 44, 55, 66, 77, 88 };	// 定义数组
System.arraycopy(dataA, 4, dataB, 2, 3);					// 数组复制,此时dataB:11,22,5,6,7,66,77,88
java.util.Arrays.sort

java.util.Arrays.sort(数组名称):将数组从小到大进行排序

对象数组
// 对象数组的动态初始化 
// 类名称 对象数组名称[] = new 类名称[长度];
Book books [] = new Book[3] ;					// 开辟了一个3个长度的对象数组,内容为null
books[0] = new Book("Java",79.8) ;				// 对象数组中的每个数据都需要分别实例化
books[1] = new Book("JSP",69.8) ;				// 对象数组中的每个数据都需要分别实例化
books[2] = new Book("Android",89.8) ;			// 对象数组中的每个数据都需要分别实例化
// 对象数组的静态初始化
// 类名称 对象数组名称[] = new 类名称[] {实例化对象, 实例化对象, ...};
Book books[] = new Book[] { 
		new Book("Java", 79.8),
		new Book("JSP", 69.8), 
		new Book("Android", 89.8) }; 		// 开辟了一个3个长度的对象数组

3.8 String类的基本概念

String是字符串的描述类型,String本身属于引用数据类型,所以它可以向其他的引用数据类型一样使用关键字 new 进行实例化操作,但其也可以像基本数据类型那样直接赋值。

String str1 = "www.YOOTK.com"; 				// 直接赋值
System.out.println(str1);					// 输出字符串数据
String str2 = new String("www.YOOTK.com"); 	// 直接赋值
System.out.println(str2);					// 输出字符串数据
字符串的比较1
“ == ”

在Java中,所有的数据类型都可以使用“ == ”来进行比较,但对于基本数据类型来说,是比较他们的值是否相等,对与引用数据类型来说,则是比较他们指向的堆内存的地址是否相等,这种操作往往用来判断两个不同名的对象是否指向同一内存空间。

String stra = "hello";					// 直接赋值定义字符串
String strb = new String("hello");		// 构造方法定义字符串
String strc = strb; 					// 引用传递
System.out.println(stra == strb); 		// 比较结果:false
System.out.println(stra == strc); 		// 比较结果:false
System.out.println(strb == strc); 		// 比较结果:true
equals

str1.equals(str2):比较两String中的内容是否相等

String stra = "hello";						// 直接赋值定义字符串
String strb = new String("hello");			// 构造方法定义字符串
String strc = strb; 						// 引用传递
System.out.println(stra.equals(strb)) ;		// 比较结果:true
System.out.println(stra.equals(strc)) ;		// 比较结果:true
System.out.println(strb.equals(strc)) ;		// 比较结果:true
String类两种实例化方式的区别

String类直接赋值可以实现堆内存的重用,即采用直接赋值的方式实例化,在相同内容的情况下不会开辟新的堆内存空间,而是直接指向已有的堆内存空间。

String stra = "hello";				// 直接赋值实例化
String strb = "hello";				// 直接赋值实例化
String strc = "hello";				// 直接赋值实例化
String strd = "yootk" ;				// 直接赋值实例化,内容不相同
System.out.println(stra == strb); 		// 判断结果:true
System.out.println(stra == strc); 		// 判断结果:true
System.out.println(strb == strc); 		// 判断结果:true
System.out.println(stra == strd); 		// 判断结果:false

String类采用的设计模式为共享设计模式:

在JVM的底层实现实际上会存在一个对象池(不一定只保存String对象),当代码中使用直接赋值的方式定义一个String对象时,会将此字符串对象使用的匿名对象保存在对象池中。如果后续还有其他String类对象也采用了直接赋值的方式,并且设置了同样的内容时,将不会开辟新的堆内存空间,而是使用已有的对象进行引用分配,从而继续使用。

使用构造方法实例化String类对象时,会先开辟一块堆内存空间保存匿名对象,然后再使用关键字new开辟另一块堆内存空间。所以使用构造方法的方式实例化String对象时,实际上会开辟两块空间,其中有一块空间将会成为垃圾。

字符串一旦定义则不可改变

在进行String类对象内容修改时,实际上原始的字符串都没有发生改变,而改变的知识String类对象的引用关系。所以可以得出结论:字符串一旦定义则不可改变

String str1 = "hello";
String str2 = str1;
System.out.println(str1 == str2);  // true
str2 = str2 + " world!";
System.out.println(str1 == str2);  // false

因为字符串一旦定义则不可改变,所以每次修改String类对象的引用关系都会产生垃圾空间,所以对于String的内容不要做过多频繁的修改。

3.9 String类的常用方法

字符与字符串
public String(char[] value);						// 构造,将字符数组value变成String类对象
public String(char[] value, int offset, int count);	// 构造,将部分字符数组value变成String类对象,offset为起始索引,count为需要转换字符的数量
public char charAt(int index);						// 普通,返回指定索引对应的字符信息
public char[] toCharArray();						// 普通,将字符串以字符数组的形式返回

例:

String str1 = "hello";
char[] data = str1.toCharArray();
String str2 = new String(data);
String str3 = new String(data, 1, 3)
System.out.println(str3.charAt(0));  // e
字节与字符串
public String(byte[] bytes);						// 构造,将全部字节数组bytes变成String类对象
public String(byte[] bytes, int offset, int count);	// 构造,将bytes部分字节数组变成String类对象,offset为起始索引,count为需要转换字符的数量
public byte[] getBytes();							// 普通,将字符串变为字节数组
public byte[] getBytes(String charsetName);			// 普通,进行编码转换
字符串的比较2
public boolean equals(String anObject);				// 普通,判断两字符串是否相等,区分大小
public boolean equalsIgnore(String anObject);		// 普通,判断两字符串是否相等,区分大小
public int compareTo(String String anObject);		// 普通,判断两字符串是否相等,按照字符编码比较,从第一个字符依次进行比较,一旦发现不想等,则													// 直接停止比较,并返回比较值
													// =0:表示要比较的两字符串相等
													// >0:表示大于的结果
													// <0:表示小于的结果

例:

String str1 = "ab";
String str2 = "AA";
System.out.println(str1.compareTo(str2));  // 32
字符串查找
public boolean contains(String str);					// 判断子字符串str是否存在,存在返回ture,否则返回false
public int indexOf(String str, int fromIndex);			// 由前向后查到子字符串str的位置,若存在则返回且第一个字符的索引,不存在则返回-1,若有															// 参数fromIndex,则从索引fromIndex处开始查找
public int lastIndexOf(String str, int fromIndex);		// 由后向往查到子字符串str的位置,若存在则返回且第一个字符的索引,不存在则返回-1,若有															// 参数fromIndex,则从索引fromIndex处开始查找
public boolean startsWith(String prefix, int toffset);	// 判断字符串从指定位置toffset开始是否以子字符串prefix开头,无toffset则从头开始
public boolean endsWith(String suffix);					// 判断字符串是否以子字符串suffix结尾
字符串替换
public String replaceAll(String regex, String replacement);		// 用新的字符串regex替换掉全部旧的字符串replacement
public String replaceFirst(String regex, String replacement);	// 用新的字符串regex替换掉第一个旧的字符串replacement
字符串截取
public String substring(int beginIndex);				// 从指定索引beginIndex截取到结尾
public String substring(int beginIndex, int endIndex);	// 截取索引beginIndex到endIndex之间的字符串
字符串拆分
public String[] split(String regex);					// 按照指定的字符串regex将原字符串全部拆分,注:拆分后的字符串中不包含字符串regex
public String[] split(String regex, int limit);			// 按照指定的字符串regex将原字符串拆分成limit份,超过limit份保留不拆

例:

String str = "abcabcabcabc";
String[] result1 = str.split("b", 3);  // a   ca    cabcabc
String[] result2 = str.split("");  // 使用空字符串""表示根据每个字符拆分

注意:对 ”.“ 进行拆分不能直接使用 ”.“ ,而应加上转义符 ”\\“,即使用 ”\\.“ 进行拆分。

字符串其他方法
public String concat(String str);						// 字符串连接,与 "+" 类似
public String toLowerCase();							// 转小写
public String toUpperCase();							// 转大写
public String trim();									// 去掉字符串左右两边的空格,中间的空格保留
public int length();									// 返回字符串的长度
public String intern();									// 将字符串加入对象池,参考  String类两种实例化方式的区别
public boolean isEmpty();								// 返回字符串是否为空(不是 null,而是"",即长度为0)

3.10 this关键字

在Java中this可以完成3件事:调用本类属性,调用本类方法,表示当前对象。

调用本类属性

使用 this.属性 的方式调用本类属性

public Book(String title, double price) {  // 无法正确赋值
    title = title;		
	price = price;		
}
public Book(String title, double price) {  // 可以正确赋值
	this.title = title;	// this.属性表示的是本类属性,这样即使与方法中的参数重名也可以明确定位
    this.price = price;	// this.属性表示的是本类属性,这样即使与方法中的参数重名也可以明确定位
}
调用本类方法
  • 调用本类普通方法:this.方法名称()
  • 调用本类构造方法:this(),
public Book() {
        System.out.println("一个新的Book类对象产生。");	// 把这行语句想象成50行代码
}
public Book(String title) {
    this() ;										// 调用本类无参构造方法,可以不用在写Book()中的50行代码了,减少了重复代码
    this.title = title;
}
public Book(String title, double price) {
    this(title) ;									// 调用本类有一个参数的构造方法,可以不用在写Book()中的50行代码了,减少了重复代码
    this.price = price;
}

注意:

  • 使用 this() 调用构造方法形式的代码只能够放在构造方法的首行

  • 进行构造方法互相调用时,一定要保留调用的出口,以下为错误示范:

    public Book() {
        this("HELLO", 1.1);					 // 调用双参构造
        System.out.println("一个新的Book类对象产生。");
    }
    public Book(String title) {
        this(); 							// 调用本类的无参构造
        this.title = title;
    }
    public Book(String title, double price) {
        this(title); 						// 调用本类的单参构造
        this.price = price;
    }
    
表示当前对象
class Book {
    public void print() {	// 调用print()方法的对象就是当前对象,this就自动与此对象指向同一块内存地址
        System.out.println("this = " + this);	// this就是当前调用方法的对象
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Book booka = new Book();			// 实例化新的Book类对象
        System.out.println("booka = " + booka);	// 主方法中输出Book类对象
        booka.print();					// 调用Book类的print()方法输出,此时booka为当前对象
        System.out.println("---------------------------");
        Book bookb = new Book();			// 实例化新的Book类对象
        System.out.println("bookb = " + bookb);	// 主方法中输出Book类对象
        bookb.print();					// 调用Book类的print()方法输出,此时bookb为当前对象
    }
}
/*
程序执行结果:
    booka = test.Book@1b6d3586
    this = test.Book@1b6d3586
    ---------------------------
    bookb = test.Book@4554617c
    this = test.Book@4554617c
*/

3.11 引用传递

引用传递的基本概念
class Message {
    private String info = "此内容无用" ;				// 定义String类型属性		
    public Message(String info) {					// 利用构造方法设置info属性内容
        this.info = info ;
    }
    public void setInfo(String info) {
        this.info = info ;
    }
    public String getInfo() {
        return this.info ;
    }
}
public class TestDemo {
    public static void main(String args[]) {
        Message msg = new Message("Hello") ;			// 实例化Message类对象
        fun(msg) ;										// 引用传递
        System.out.println(msg.getInfo()) ;				// 输出info属性内容
    }
    public static void fun(Message temp) {				// 接收Message类引用
        temp.setInfo("World") ;							// 修改info属性内容
    }
}
// 程序执行结果:  World

本程序大致内存分析图:

在这里插入图片描述

本程序完整内存分析图:

在这里插入图片描述

3.13 对象的比较

public boolean compare(Book book) {
    if (book == null) {						// 传入数据为null
        return false ;						// 没有必要进行具体的判断
    }

    if (this == book) {						// 内存地址相同
        return true ;						// 避免进行具体细节的比较,节约时间
    }
    if (this.title.equals(book.title) && this.price == book.price) {		// 属性判断
        return true ;
    } else {
        return false ;
    }
}

在进行对象比较时的4个特点:

  • 本类接受自己的引用,在与本类当前对象(this)进行比较
  • 为了避免NullPointerException的产生,应该增加一个null的判断
  • 为了防止浪费性能,可以增加地址数值的判断,因为相同的对象地址相同
  • 进行属性的依次比较,如果属性全不相同,则返回true,否则返回false

3.14 static关键字

static定义属性
  • 如果一个类中的某个属性希望被定义为公共属性(即这个类所有对象公用的属性),则可以在声明属性前加上static关键字。

    static 返回值类型 属性名;  // static属性同样可以在前面加上private进行修饰
    
  • 被static关键字声明的属性一般使用类名进行调用(而非跟普通属性使用对象名调用一样)

    类名.属性名
    
  • 被static关键字声明的属性将被放在全局数据区(普通属性被放在堆内存中),且在没有实例化对象时也可以调用static属性

  • Java中主要的4块内存空间:

    • 栈内存空间:保存所有的对象名称(准确的说是保存引用的堆内存空间的地址)
    • 堆内存空间:保存每个对象的具体属性内容
    • 全局数据区:保存static类型的属性
    • 全局代码区:保存所有的方法代码
static定义方法
  • 使用static声明的方法可以在没有实例化对象时由类名调用

  • static方法不能直接访问非static属性或方法,只能调用static属性或方法

  • 非static方法可以访问static属性和方法,不受任何限制

  • 关于主方法的操作问题:

    • 因为主方法使用了static关键字声明,所以在主方法中直接调用的主类的方法中,只能调用static方法。也即在主方法中调用的主类其他方法都要使用static声明。
  • 类中什么时候定义static方法:

    • 如果一个类中没有属性的产生就,则这个类中所有方法都可以用static声明
主方法

主方法结构含义:

  • public:主方法是程序的开始,所以这个方法对任何操作都一定是可见的,既然是可见的就必须使用public
  • static:证明此方法由类名调用
  • void:主方法是一切执行的开始,既然是开头,就不用返回什么东西
  • main:系统规定好的方法名称,不能修改
  • String[] args:指的程序运行时传递的参数,格式为:“java 类名称 参数 参数 参数”。
public class TestDemo {
    public static void main(String args[]) {  // args参数设置在 Edit configurations 的 program arguments 中
        for (int x = 0; x < args.length; x++) {	// 循环输出参数
            System.out.print(args[x] + "  ");
        }
    }
}
// 程序执行输入:  "hello" "world" "!!!"
// 程序执行结果:	hello  world  !!!

3.15 代码块

在程序编写中使用 “{}” 来定义代码块。代码块一共可分为4种:普通代码块、构造块、静态块、同步代码块(等待多线程时)

  • 普通代码块:写在方法中的代码块
  • 构造块:写在类中的代码块,在每次实例化对象的时候都会被优先调用(优先于构造方法)
  • 静态块:使用static关键字声明的代码块
    • 在非主类中:只在第一次实例化对象时被优先调用
    • 在主类中:优先于主方法执行

3.16 内部类

内部类是指在一个类的内部定义的类

基本概念
  • 内部类可以直接访问外部类的private属性,外部类也可以通过内部类对象访问内部类的private属性
class Outer {									// 外部类
    private String msg = "Hello World !" ;
    class Inner {								// 定义一个内部类
        private String info = "世界,你好!" ;	// 内部类的私有属性
        public void print() {
            System.out.println(msg) ;			// 直接访问外部类的私有属性
        }
    }
    public void fun() {
        Inner in = new Inner() ;				// 内部类对象
        in.print();	
        System.out.println(in.info) ;			// 直接利用内部类对象访问内部类中定义的私有属性
    }
}
public class TestDemo {
    public static void main(String args[]) {
        Outer out = new Outer() ;				// 实例化外部类对象
        out.fun() ;								// 调用外部类方法
    }
}
/*
程序运行结果:
    Hello World !
    世界,你好!
*/
  • this在内部类中的使用:

    在内部类中访问外部类中的属性,尽量使用this来访问:外部类.this.属性

class Outer {								// 外部类
    private String msg = "Hello World !" ;
    class Inner {							// 定义一个内部类
        public void print() {
            System.out.println(Outer.this.msg) ; 	// 外部类.this = 外部类当前对象
        }
    }
}
  • 在类的外部对内部类进行初始化:
外部类.内部类 对象名称 = new 外部类().new 内部类();
  • 内部类也可以定义为private
static内部类
  • 如果一个内部类使用static定义,那么这个内部类就变成一个“外部类”,并且只能访问外部类中定义的static操作。

  • static内部类的实例化:

    外部类.内部类 对象名称 = new 外部类.内部类();
    
方法中定义内部类

在开发的过程中,在普通方法中定义内部类是最常见的

class Outer {										// 外部类
    private final String msg = "Hello World !" ;  	// 加 final
    public void fun() {								// 外部类普通方法
        class Inner {								// 方法中定义的内部类
            public void print() {
               System.out.println(Outer.this.msg) ;
            }
        }
        new Inner().print() ;						// 内部类实例化对象调用print()输出
    }
}
public class TestDemo {
    public static void main(String args[]) {
        new Outer().fun() ;
    }
}
// 程序执行结果:	Hello World!

注意:为了标准化编写,最好在方法中定义的内部类要访问方法中参数或变量时,要在前面加 “final” 关键字。

3.17 链表

链表是一种根据元素节点逻辑关系排列起来的一种数据结构。

链表的基本形式
  • 链表是一种最为简单的数据结构,它的主要目的是依靠引用关系来实现多个数据的保存。

  • 链表中的每一个节点node主要保存两个属性:数据(data)和下一个节点的引用(next)

在这里插入图片描述

  • Node类的定义:

    class Node { 							// 每一个链表实际上就是由多个节点组成的
        private String data; 					// 要保存的数据
        private Node next; 					// 要保存的下一个节点
    
        public Node(String data) { 				// 必须有数据才有Node
            this.data = data;
        }
        public void setNext(Node next) {
            this.next = next;
        }
        public Node getNext() {
            return this.next;
        }
        public void setData(String data) {
            this.data = data;
        }
        public String getData() {
            return this.data;
        }
    }
    
链表的基本雏形

链表操作类——Link类,专门负责处理节点关系,用户不用担心节点问题,只需要关系Link的处理操作即可。

链表的基本操作特点:

  • 客服端代码不用关注具体的Node以及引用关系的细节,只需要关注Link类中提供的数据操作方法
  • Link类的主要功能是控制Node类对象的产生和根节点
  • Node类主要负责数据的保存以及引用关系的分配
class Node { 								// 定义一个节点
    private String data; 					// 要保存的数据
    private Node next; 						// 要保存的下一个节点
    public Node(String data) { 				// 每一个Node类对象都必须保存相应的数据
        this.data = data;
    }
    public void setNext(Node next) {
        this.next = next;
    }
    public Node getNext() {
        return this.next;
    }
    public String getData() {
        return this.data;
    } 
    public void addNode(Node newNode) {
        if (this.next == null) { 			// 当前节点的下一个为null
           this.next = newNode; 			// 保存新节点
        } else { 							// 当前节点之后还存在节点
           this.next.addNode(newNode);		// 使用递归,当前节点的下一个节点继续保存
        }
    }
    public void printNode() {
        System.out.println(this.data); 		// 输出当前节点数据
        if (this.next != null) { 			// 还有下一个节点
           this.next.printNode(); 			// 使用递归,找到下一个节点继续输出
        }
    }
}

class Link { 								// 负责数据的设置和输出
    private Node root; 						// 根节点
    public void add(String data) { 
        Node newNode = new Node(data);		// 设置数据的先后关系,所以将data包装在一个Node类对象中
        if (this.root == null) { 			// 一个链表只有一个根节点
           this.root = newNode; 			// 将新的节点设置为根节点
        } else { 							// 根节点已经存在
           this.root.addNode(newNode);		// 交由Node类来进行节点保存
        }
    }
    public void print() { 					// 输出数据
        if (this.root != null) { 			// 存在根节点
           this.root.printNode(); 			// 交给Node类输出
        }
    }
}

public class LinkDemo {
    public static void main(String args[]) {
        Link link = new Link(); 			// 由这个类负责所有的数据操作
        link.add("Hello"); 					// 存放数据
        link.add("MLDN"); 					// 存放数据
        link.add("YOOTK"); 					// 存放数据
        link.add("李兴华");				  // 存放数据
        link.print(); 						// 展示数据
    }
}
  • 注意:由于在使用链表时不想让Node被暴露在外面,所以可以把Node类设计为Link类的private内部类,这样只有Link类可以对他进行使用;同时,也不再需要在Node类中定义setter和getter方法,因为外部类与内部类之间可以方便的访问私有属性

    class Link { 								// 链表类,外部能够看见的只有这一个类
        private class Node { 					// 定义的内部节点类
            private String data; 				// 要保存的数据
            private Node next; 					// 下一个节点引用
            public Node(String data) {			// 每一个Node类对象都必须保存相应的数据
                this.data = data;
            }
        }
        // ===================== 以上为内部类 ===================
        private Node root; 						// 根节点定义
        // ......
    }
    
开发可用链表

前面的代码知识提供了一个基础的开发模型,并不能用于实际的开发中,而如果要想让链表真正可以使用,就应该让链表具备以下9个功能:

public void add(数据类型 变量名);				// 向链表中添加新的元素
public int size();							// 取得链表中保存的元素的个数
public boolean isEmpty();				    // 判断是否为空链表
public boolean contains(数据类型 变量名);		// 判断某个数据是否存在
public 数据类型 get(int index);				  // 根据索引取得数据
public void set(int index, 数据类型 变量名);	// 使用新的内容替换掉指定索引中的旧内容
public void remove(数据类型 变量名);			// 删除指定数据,如果是对象则要进行对象比较
public 数据类型[] toArray();			      // 将链表一对象数组形式返回
public void clear();						// 清空链表

private class Node { // 定义的内部节点类
private String data; // 要保存的数据
private Node next; // 下一个节点引用
public Node(String data) { // 每一个Node类对象都必须保存相应的数据
this.data = data;
}
}
// ===================== 以上为内部类 ===================
private Node root; // 根节点定义
// …
}
~~~

开发可用链表

前面的代码知识提供了一个基础的开发模型,并不能用于实际的开发中,而如果要想让链表真正可以使用,就应该让链表具备以下9个功能:

public void add(数据类型 变量名);				// 向链表中添加新的元素
public int size();							// 取得链表中保存的元素的个数
public boolean isEmpty();				    // 判断是否为空链表
public boolean contains(数据类型 变量名);		// 判断某个数据是否存在
public 数据类型 get(int index);				  // 根据索引取得数据
public void set(int index, 数据类型 变量名);	// 使用新的内容替换掉指定索引中的旧内容
public void remove(数据类型 变量名);			// 删除指定数据,如果是对象则要进行对象比较
public 数据类型[] toArray();			      // 将链表一对象数组形式返回
public void clear();						// 清空链表
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大枫树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值