Java基础
基础
先来看个代码
1
2
3
4
5
6
7package study;
public class HelloWorld {
public static void main(String[] args) {
// 注:println() 后面的ln代表换行
System.out.println("Hello, world!");
}
}这就是一般的格式了
初识
快捷键
编译器基于IDEA
快捷键 | 作用 |
---|---|
alt + enter | 导入包,自动修正带代码 |
alt + 4 | 运行栏显示隐藏 |
ctrl + y | 删除光标所在行 |
ctrl + alt + l | 格式化代码 |
alt + shift + 上下箭头 | 移动当前代码行 |
shift + F6 | 全选 (当前鼠标选中的) |
数组
认识上与C++相似,格式有所差异
创建数组
数组的使用还是和C++一样的
动态初始化
数据类型[] 数组名称 = new 数据类型[数组长度];
1
2
3
4// 只是举几个例子,代码不全
int[] nums = new int[5];
// 注:字符串类型数据 String 首字母要大写
String[] strings = new String[2];静态初始化
数据类型[] 数组名称 = new 数据类型[]{元素1, 元素2, ...};
也可以使用省略写法
数据类型[] 数组名称 = {元素1, 元素2, ...};
1
2String[] strings = new String[] {"Hello", "world", "java"};
String[] strings = {"Hello", "world", "java"};
注意事项:
- 静态初始化没有直接指定长度,但是仍然会自动推算得到长度
- 静态初始化标准格式可以拆分成为两个步骤,即定义与初始化
- 动态初始化也可以拆分成为两个步骤,同上
- 静态初始化一旦使用省略格式,就不能拆分成为两个步骤了
其它
- 动态初始化时,其中的元素将会自动拥有一个默认值 (0、null、false)
- 直接打印数组名称,得到的是数组对应的内存地址哈希值 (不直接是内存地址)
数组方法
.length()
得到数组长度方法 (导包使用) 作用 Arrays.toString(数组) 将数组转换为字符串 Arrays.sort(数组) 将数组内元素按升序排序
ArrayList类
认知:数组的长度在创建时就是固定的,而通过ArrayList类创建的对象其长度可变,可以实现对数组内数据的操作,弥补这一不足,创建方法为:ArrayList<E> list = new ArrayList<>();
其中
1 | ArrayList<String> list = new ArrayList<>(); |
常用方法:
方法(以list为例) | 作用 |
---|---|
list.add() | 向集合中添加值,返回布尔值 |
list.get(index) | 得到对应的索引值 |
list.remove(index) | 删除对应索引的值并将其返回 |
list.size() | 返回集合长度 |
注意:
1 | 基本类型 包装类(引用类型,包装类都位于java.lang包下,无需导入) |
面向对象
类的定义
1 | // 来个栗子 |
对象的创建
类名称 对象名 = new 类名称()
eg. ClassName stu = new ClassName();
注意事项
与C++一样,类中也有 private 类型的变量,在类外不可调用,访问需定义一对setter/getter方法间接访问
一般类的定义单独在另一个文件中,创建对象时需导入类所在包的路径(格式:
import 包名称.类名称
),若对应的类与当前文件处在同一根目录下则可以省略该步骤类中定义方法时不需要static (很容易犯错)
与C++一样,类中的方法占据单独的内存,对象中的方法存储的是对应的地址
类中的成员变量是有默认值的
当局部变量与成员变量同名时,采用”就近原则“,优先使用局部变量,可以利用this解决该冲突
Setter/Getter方法
作用:类中的成员变量一般定义为private类型(封装性),但又不可避免的需要对成员变量进行赋值等操做,除了利用构造函数 (跟C++一样)赋值外,setter方法也是常见的,而且对于一些需要判断是否合理的数据,在setter方法中可以很好的解决 (利用if-else语句),而getter方法就是对应的取值操作,下面来个栗子:
1 | public class Student { |
小技巧
快速生成 setter/getter等方法:左上角code,点击Generate,选择 Getter and Setter,选择需要生成函数的成员变量(按住shift键可以多选),回车即可 (快捷键alt + lns 可以快速打开Generate面板)
Scanner输入对象
除了java.lang包下的内容不需要导包外,其他的包都需要import导入,该对象所在包在使用时会自动导入
认知: 通过键盘输入数据的对象,与C++中的cin作用相同,但其是对象,使用方法当然也不同,来个栗子:
1 | public static void main(String[] args) { |
Random类
生成随机数的类,既然是类,使用时肯定得创建对象,来个经典的猜数字栗子:
1 | package exercise; |
String字符串类
字符串都已经很熟悉了,来看看下面的代码:
1 | String s1 = new Scanner(System.in).next(); // 输入Hello |
将一个输入的字符串与一样但是是直接定义的字符串相比较时,居然是false,这就有问题了
字符串的比较
了解:对于基本类型来说,’==’是进行数值的比较,而对于引用类型来说,’==’是进行【地址值】的比较,另外,所有new出来的对象都是存储在堆中,而直接定义的字符串 (就是写出来了的)是存储在字符串常量池中。即使两字符串完全相同,但创建方式致使其地址不同,所以直接比较返回的也是false
想要直接比较两字符串的值,可以使用两个方法:
.equals()
比较字符串本身而不是地址.equalsIgnoreCase()
比较字符串时不区分大小写注意:在比较时,若有常量,推荐将常量写在前面,避免空指针异常 (变量若是null,调用比较方法会出现空指针异常错误)
常用方法
方法 作用 .length() 得到字符串的长度 .concat(String) 拼接字符串 (原来的字符串不受影响) .charAt(index) 获取指定索引位置的单个字符 .indexOf(String) 查找参数字符串在字符串中首次出现的位置,若没有,返回-1 .substring(index) 截取字符串 [index, end),包括指定位置的字符 .substring(begin, end) 截取字符串 [begin, end) .toCharArray() 将字符串转换为字符数组形式 .getBytes() 将字符串转换为字节数组形式 .replace(String1, String2) 将字符串中的string1全部替换为string2 有点多哈,不过确实都是常用的,还有个方法:
.split(String)
切割字符串,按照参数的规则将字符串切割为若干部分,返回字符串数组区分:切割不同于截取,指定切割的字符会除掉
1
2
3
4
5
6String str = "xiao xiao shuang";
String[] array = str.split("i");
// array.fori 快捷写法
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}结果为:x
ao x ao shuang
显然,”i“已经没了
Math数学工具类
了解几个常见方法即可
方法 | 作用 |
---|---|
Math.abs(double num) | 获取绝对值 |
Math.ceil(double num) | 向上取整数 |
Math.floor(double num) | 向下取整数 |
Math.round(double num) | 四舍五入 |
另外,Math.PI
代表的是圆周率常量
继承
java语言中是单继承的,即一个子类不能有两个父类
格式
1
2
3public class 子类名称 extends 父类名称 {
// ...
}重名变量的访问
对于类中的方法访问成员变量,若父子类中有相同变量名,方法属于谁,就优先用谁的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 父类
public class Fu {
int num = 10;
}
// 子类
public class Zi extends Fu {
int num = 20;
public void method() {
int num = 30;
System.out.println(num); // 30
System.out.println(this.num); // 20
// 不仅变量,父类中的同名方法也可以利用 super. 调用
System.out.println(super.num); // 10
}
}分析:在方法中,根据就近原则,直接访问
num
为局部变量;this.num
代表的该类中的成员变量;super.num
代表着父类中的成员变量方法覆盖
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 父类
public class Fu {
public void method() {
// ...
}
}
// 子类
public class Zi extends Fu {
public void method() {
// ...
}
}大体与C++相同,就不写详细了,有几点可以了解一下:
@Override
:写在方法前面,可以检测是否为正确的覆盖重写 (可不写,但为了避免小手一抖出错,建议加上)- 子类方法的返回值必须【小于等于】父类方法的返回值范围 (比如:Object > String)
- 子类方法的权限必须【大于等于】父类方法的权限修饰符 (public > protected > (default) > private)
构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 父类
public class Fu {
public Fu(int x) {
System.out.println("父类构造函数调用");
}
}
// 子类
public class Zi extends Fu {
public Zi() {
super(num); // 这必须写在前面哈
System.out.println("子类构造函数调用");
}
}创建子类对象时,肯定是先调用父类的构造函数,父类构造函数利用
super()
调用,有两种情况:- 父类构造函数为无参数的,子类中可以不写,默认会自带
super();
- 父类构造函数包含参数的,子类中必须写,
super(对应参数)
;
- 父类构造函数为无参数的,子类中可以不写,默认会自带
抽象类
1
2
3
4
5
6
7
8
9// 抽象类,可以理解为C++中的虚基类
public abstract class Animal {
// 抽象方法,可以理解为C++中的纯虚函数
public abstract void eat();
// 普通方法
public void normal() {
// ...
}
}注意:
1.抽象类不可创建对象,需要声明
abstract
,且抽象方法必须在抽象类中;2.继承抽象类的子类必须覆盖重写抽象类中的抽象方法
多态
多态的体现
父类引用指向子类对象
父类名称 对象名 = new 子类名称();
接口应用指向实现类对象
接口名称 对象名 = new 实现类名称();
老实说,我觉得这很像C++中的虚函数,动态关联 (解决疑惑)
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法
成员变量/ 方法的使用
使用多态方式创建出来对象obj,要区分
obj.num
和obj.sum()
,以Fu obj = new Zi();
为例:前提:父、子类中均含有
num
,sum()
obj.num
:访问成员变量,优先用Fu
类中的成员变量 (以等号左边优先)obj.sum()
:访问成员方法,看new出来的是谁,优先使用谁的成员方法 (以等号右边优先)转型
分为两种:
向上转型:就是多态写法
向下转型:是一个还原动作,将向上转型的对象还原
格式:
子类名称 对象名 = (子类名称) 父类对象
注:该父类对象必须是
父类名称 对象名 = new 子类名称()
这样定义的
关键字
instanceof
:判断父类引用是否为对应的对象,返回布尔值1
2
3
4
5
6
7
8
9
10
11
12public static void main(String[] args) {
// Animal 为Cat 和 Dog 的父类
Animal animal = new Cat();
// 判断 animal 本来是不是 Cat
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
}
// 判断 animal 本来是不是 Dog
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
}
}这样看不出
instanceof
的意义,但在参数传递时,若形参为Animal animal
,实参为Cat 或 Dog,那在函数体中调用子类对象特有成员时,就必须清楚到底如何向下转型,这时,instanceof
就很有必要了
接口
接口是最高级的抽象类,没有构造函数
首先得清楚:为什么要使用接口 ?
接口的定义
1 | public interface 接口名称 { |
与类的定义很相似,只是将 class 变成了 interface,另外,既然接口是抽象类,那其中定义的方法也应该是抽象方法,实际上也默认是抽象方法,即 public abstract
是可以省略的
实现类
接口需要具体的类去实现 (继承),该类就叫“实现类”,其写法如下:
1 | public class 类名称 implements 接口名称 { |
实现类必须对接口中的抽象方法全部进行覆盖重写,除非实现类是抽象类
默认方法/ 静态方法
前面也说了,接口中定义的应该都是抽象方法,但随着java语言的不断完善,在接口中也可以定义默认/ 静态方法,默认/ 静态方法有函数体
1 | // 定义默认方法 |
虽然都是在接口中定义的,但使用起来可不一样:
默认方法会被实现类继承,可通过实现类的对象调用,也可以覆盖重写
静态方法不能通过实现类的对象调用,而是直接通过接口调用,即:
接口名.静态方法
私有方法
有时接口中的默认/ 静态方法中包含许多相同代码块,为了减少重复代码,可以将相同的代码抽取出来放在新的函数中,直接调用即可,但这个新函数是不希望可以通过同实现类或者接口名调用的,所以就有了私有方法:
1 | // 默认私有方法 |
私有方法只允许在接口中调用
成员变量
接口中的成员变量相当于常量,定义时必须赋值,且赋值后不可修改
声明形式:public static final 数据类型 常量名称 = 数据值;
注意:
public static final
是默认的,可以省略既然有
static
,那常量调用的形式就与静态方法一样,通过接口名调用常量名一般使用全大写字母,可以有下划线
父类与接口
继承父类与多接口
接口是可以与继承一起使用的,前面已经说到,java语言中是单继承,子类不能有两个父类,但接口可以有多个
1
2
3
4public class Zi extends Fu implements InterfaceA, InterfaceB {
// 覆盖重写接口中所有抽象方法
// ...
}注意事项:
- 若实现类所实现的多个接口中,存在重复的抽象方法,则只需要覆盖重写一次即可
- 若实现类所实现的多个接口中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写
- 若一个类所继承父类中的方法,与接口当中的默认方法产生了冲突,优先使用父类中的方法
接口之间的多继承
接口之间也可以继承,而且可以多继承
1
2
3public interface MyInterface extends InterfaceA, InterfaceB {
// ...
}注意事项:
若多个父接口中的抽象方法重复,只会继承一次 (抽象方法没有函数体,不存在冲突)
若多个父接口中的默认方法重复,则子接口中必须覆盖重写
final关键字
代表最终、不可改变的
修饰类
1
2
3public final class 类名称 {
// ...
}使用
final
修饰的类不能有任何子类 (字面意思,最后的类)修饰方法
1
2
3public final void method() {
// ...
}使用
final
修饰的方法为最终方法,即不可被覆盖重写,子类中也不行注意:类与方法中
abstract
和final
是不能同时使用的,两者自相矛盾修饰局部变量
在普通定义的成员变量前加上
final
,代表该局部变量不可再改变注:对于基本类型来说,不可再改变为其中的数据;而对于应用类型来说,不可再改变为其中的地址值
以
String
为例:1
2final String name = new String();
name = new String(); // 报错上面的代码就是错误的,
name
被声明为final
类型,其地址值不可二次赋值 (可以理解为C++中 指向对象的常指针)修饰成员变量
也是在普通定义的成员变量前加上
final
,代表该成员变量不可再改变与局部变量的区别:
- 成员变量是定义在类中的,会有默认值,所以被声明为
final
类型的成员变量必须手动初始化 - 手动初始化有两种方法:1.> 直接在后面写”= …“; 2.> 在所有的构造方法中都进行赋值
- 成员变量是定义在类中的,会有默认值,所以被声明为
四种权限修饰符
Java中有四种权限修饰符访问关系:
public > protected > (default) > private
同一个类(自己) YES YES YES YES
同一个包(邻居) YES YES YES NO
不同包子类(儿子) YES YES NO NO
不同包非子类(陌生人) YES NO NO NO
注意事项:(default)
并不是关键字“default”,而是根本不写
内部类
字面意思,在类的内部包含另一个类
成员内部类
1
2
3
4
5
6
7// 在类中直接包含另一个类称为成员内部类:内部类为外部类的一个成员
public class 外部类名称 {
public class 内部类名称 {
// ...
}
// ...
}注意:内部类可以随意访问外部类中的成员,而外部类需借助内部类对象才能访问内部类成员
使用方法:
间接使用:在外部类的方法中,使用内部类创建对象并调用内部类中的方法,然后在main函数中只调用外部类中的方法;
直接使用:直接在main函数中创建内部类对象,再通过内部类对象调用其方法,但创建对象需按照一定的格式
外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
再来看看熟悉的重名问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Outer {
int num = 10;
public class Inner {
// 内部类中的成员变量
int num = 20;
public void method() {
// 内部类中的局部变量
int num = 30;
System.out.println(num); // 30
System.out.println(this.num); // 20
System.out.println(Outer.this.num); // 10
}
}
// ...
}分析:直接打印
num
,其值还是遵循就近原则;然后this.num
,指该方法所在类中的num
;若想访问外部类中的成员变量,就得强调为Outer.this.num
局部内部类
1
2
3
4
5
6
7
8
9// 在类中的方法中包含另一个类称为局部内部类
public class 外部类名称 {
public void method() {
class 内部类名称 {
// ...
}
}
// ...
}局部内部类只能在其所在的方法中使用。
1 | public class Outer { |
若局部内部类希望访问所在方法中的局部变量,那这个局部变量必须是有效final
,与生命周期有关,不详细说明了
匿名内部类
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。
格式:
接口名称 对象名 = new 接口名称() {}
{…}是匿名内部类的内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43public static void main(String[] args) {
// 使用匿名内部类,但不是匿名对象,对象名称就叫objA
// 此处 MyInterface 为接口名称,注意:new 的是接口,区别下前面所说的的"接口不能 new"
MyInterface objA = new MyInterface() {
public void method1() {
System.out.println("匿名内部类实现了方法!111-A");
}
public void method2() {
System.out.println("匿名内部类实现了方法!222-A");
}
};
objA.method1();
objA.method2();
System.out.println("=================");
// 使用了匿名内部类,而且省略了对象名称,也是匿名对象
new MyInterface() {
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method1();
// 因为匿名对象无法调用第二次方法,所以需要再创建一个匿名内部类的匿名对象
new MyInterface() {
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method2();
}区分:
- 匿名内部类,在【创建对象】的时候,只能使用唯一一次。
- 匿名对象,在【调用方法】的时候,只能调用唯一一次。
- 匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
注意事项:
- 外部类的修饰符只能为
public/ (default)
- 成员内部类的修饰符可以随意
- 局部内部类不能有修饰符
特殊的成员变量
类作为成员变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Weapon {
private String code;
// 构造函数和 Getter/ Setter 此处省略
}
public class Hero {
private String name;
private int age;
// 自定义的类 Weapon
private Weapon weapon;
public void attack() {
System.out.println(age + "岁的" + name + "使用" + weapon.getCode() + "打怪兽");
}
// 构造函数和 Getter/ Setter 此处省略
}与
String
相似,自己定义的类也是可以作为成员变量的,只是赋值时应是: (以类Hero
的对象hero
为例)hero.setWeapon(new Waepon().setCode("太极剑"))
使用实名对象也可以接口作为成员变量
与上述的类是相似的,就不过多描述了