《第一行代码: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(); // 清空链表