目标读者
:
了解 Java 基本数据类型,权限修饰符,继承,封装,静态。
掌握 Java 输出、for、if 等基本语法。
熟练应用函数,了解函数重载。
抽象类
关键字
abstract
1
2
3
4// 用abstract关键字修饰的类就是抽象类。
public abstract class People {
//...
}抽象类和普通类的区别:
抽象类不能被实例化
抽象类的子类必须实现它的抽象方法,除非这个子类也是抽象类。
抽象类可以拥有抽象方法。普通的类不能拥有抽象方法。
抽象方法:
被
abstract
关键字修饰的方法就是抽象方法。抽象方法需要被子类实现,即重写(Override)。如果子类也是抽象类,那么可以不重写抽象方法,或者重写其中几个。
1
2
3
4
5
6public abstract class People {
// 将会说出的一段话。e.g 我是杨晨
protected abstract String words();
// 做出一个动作。e.g 露出开心的笑容
protected abstract String action();
}
这是一个例子:
- 需求:
我们需要定义老师和学生。他们都是人,都拥有年龄和名字。每个人都可以做一件事:先做一个动作,然后再说一句话。每个人的动作可以不同,说的话也可以不同。
老师需要教一门课程,在老师说话时需要说出自己教的课程名称。
对学生没有特殊要求。
定义抽象类
People
根据需求,可以将老师和同学共同的部分抽象出来,组成抽象类。这样功能相同的代码只需要写一次,老师和学生只要继承
People
即可。 使用抽象类时,如果需求发生变化,我们想去掉年龄,增加性别、学历、户籍等信息,只需要修改People一个类即可。如果不使用继承需要修改
Student
和Teacher
两个类。
1 | package classroom; |
- Teacher类
1 | package classroom; |
- Student类
1 | package classroom; |
- Main 主程序
1 | import java.util.ArrayList; |
- 输出结果
1 | =====================马保国的分割线===================== |
多态
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象
多态的优点
消除类型之间的耦合关系
可替换性
可扩充性
接口性
灵活性
简化性
多态是同一个行为具有多个不同表现形式或形态的能力。如:老师和学生都可以doSomething
,但是他们do的方式不一样。虽然mbg
,yc
,zk
都是People类型的变量,都调用了同一个方法,但是却有不同的表现形式。
1 | public class Main { |
接口
关键字interface
,implements
众所周知,Java的类只能单继承,无论继承的是普通类还是抽象类。但是在庞大的程序中,多继承是不可避免的。为了实现这个功能,Java中引入了接口(Interface)。
那么问题来了,既然还是要有多继承,为什么不直接设计成可以继承多个类呢?
在C++里,类是可以多继承的,但正是因为它的多继承,让程序员们遇到了很多问题。如菱形继承问题:B继承了A,C继承了A,而D同时继承了B和C,这时D中会出现两个同名的父类A,在使用A时会带来一些问题。
接口中可以定义函数和变量
- 接口默认函数是
public abstract
的。而且接口中的函数只能是public的。
1
2
3
4public interface Answerable {
//去掉这里的public abstract 也是一样的效果
public abstract void answerQuestion(String answer);
}- 接口中默认的变量是
public static final
的。
1
2
3
4public interface QAndA extends Answerable,CanAsk {
//去掉这里的public static final 也是一样的效果
public static final String test = "这是一个test";
}- 接口默认函数是
接口可以被类用实现,一个类可以实现多个接口。
implments
1
2
3
4//Answerable.java
public interface Answerable {
public abstract void answerQuestion(String answer);
}1
2
3
4//CanAsk.java
public interface CanAsk {
void askQuestion(Answerable answerable, String question);
}1
2
3
4
5
6//People.java
public abstract class People implements CanAsk, Answerable {
//...
//因为是抽象类,所以即使不实现接口中定义的函数也可以。
}接口可以继承多个接口。
extends
1
2
3
4public interface QAndA extends Answerable,CanAsk {
//QAndA接口中有在Answerable定义的抽象方法 answerQuestion和CanAsk中定义的抽象方法askQuetion
// 可以定义新的方法
}
有了接口也就解决了之前说的多继承的问题。即使不同的接口有同名的函数,因为都没有实现,所以也就不会有菱形继承的问题。
接口与抽象类:
static
与default
在jdk1.8之后,增加了这两个特性。在接口中可以使用
static
和default
来修饰方法,当使用这两个关键字来修饰函数时,函数就不能被abstract
修饰了。接口中可以定义静态函数,使用方式和类没有区别。需要在接口中定义函数实现。
1
2
3
4
5
6
7
8
9public interface InterfaceA {
/**
* 静态方法
*/
static void showStatic() {
System.out.println("InterfaceA++showStatic");
}
}1
2
3
4
5public class Test {
public static void main(String[] args) {
InterfaceA.show();
}
}1
2# 结果
InterfaceA++showStatic在类中,
default
是默认的访问修饰符,不需要写出。但是同时default
也是Java的关键字,在接口中使用它修饰方法,表示该方法需要一个默认的实现。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public interface InterfaceA {
/**
* 静态方法
*/
static void showStatic() {
System.out.println("InterfaceA++showStatic");
}
/**
* 默认方法
*/
default void showDefault() {
System.out.println("InterfaceA ++showDefault");
}
}
public class InterfaceAImpl implements InterfaceA{
}1
2
3
4
5
6
7public class Test {
public static void main(String[] args) {
InterfaceA.showStatic();
new InterfaceAImpl().showDefault();
}
}1
2
3# 结果
InterfaceA++showStatic
InterfaceA ++showDefault子类可以重写接口的default函数
1
2
3
4
5
6
7
8
9
10public class InterfaceAImpl implements InterfaceA
/**
*
* 跟接口default方法一致,但不能再加default修饰符
*/
public void showDefault(){
System.out.println("InterfaceAImpl++ defaultShow");
}
}既然在jdk1.8之后,接口可以有默认的函数实现了,那之前的菱形继承的问题,不就又出现了吗。在Java中提供了解决方案,只要出现了冲突,那么子类就必须要重写这个函数。
他们都不可以被实例化
一个类可以实现多个接口,但只能继承一个类。
匿名内部类与Lambda表达式
匿名内部类
在类的内部,我们依旧可以定义类。这样的类叫做内部类。其中有一种特殊的内部类叫做匿名内部类。
1 | //定义一个接口 |
1 | package anoymous; |
Lambda表达式
当接口只有一个函数时,可以用Lambda表达式替换匿名内部类。
1
2
3
4
5package anoymous;
public interface SingleFunctionInterface {
void function(int a,int b);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package anoymous;
public class AnonymousMain {
public static void main(String[] args) {
// 匿名内部类
SingleFunctionInterface singleFunctionInterface =
new SingleFunctionInterface() {
public int function(int a, int b) {
System.out.println(a + b);
return a + b;
}
};
// Lambda表达式
// ()括号中的参数对应接口中函数的参数列表。
// ->剪头后{}花括号内的内容为实现的方法体
SingleFunctionInterface singleFunctionInterface2 =
(a, b) -> {
System.out.println(a + b);
return a + b;
};
}
}
泛型
Java泛型(generics
)是JDK 5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许程序员在编译时监测非法的类型(编译后与泛型相关的信息会被擦掉,即类型擦除)。使用泛型机制编写的程序代码要比那些杂乱地使用Object
变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList
就是一个无处不在的集合类。
泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。
我们为什么需要泛型?
假如我们需要一个类在使用上和数组的使用几乎无异,但是它可以自动扩展长度.
当添加的元素数量超过数组的长度时,这个类会新建一个长度为原本长度2倍的数组来替换原本的数组.
这时我们需要创建一个类,它拥有一个数组类型的成员变量,像这样.
1
2
3
4
5
6
7
8
9
10
11
12
13 package generic;
public class StringsClass {
private String[] data;
public StringsClass(String[] data) {
this.data = data;
}
public String[] getData() {
return data;
}
}但是数组创建时我们可以任意指定一种类型.如果我们需要整数类型,那就要这样:
1
2
3
4
5
6
7
8
9
10
11
12
13 package generic;
public class IntegersClass {
private Integer[] data;
public IntegersClass(Integer[] data) {
this.data = data;
}
public Integer[] getData() {
return data;
}
}这样不仅代码会高度重复,而且我每自定义一个新的类型,都需要再写一个类,再重复一遍代码。
那不如:
1
2
3
4
5
6
7
8
9
10
11
12
13 package generic;
public class ObjectsClass {
private Object[] data;
public ObjectsClass(Object[] data) {
this.data = data;
}
public Object[] getData() {
return data;
}
}因为Java中,所有类的父类都是Object,这样不管你要什么类型都可以了。我们只需要ObjectsClass一个类即可维护所有类型的数组。
但是这样使用时需要强制类型转换,而且在使用时我也无法得知,某个ObjectsClass类的实例到底存了什么类型的变量。data变量很可能存了不同类型的变量,在实际使用时依旧存在很多问题。
所以我们需要一种机制来解决这个问题,它就是泛型(
generics
)
泛型类
在类名后增加<T>,就是泛型类.其中T是泛型变量。我们也可以指定多个泛型变量,而且可以任意命名(必须符合java变量的命名规范),比如:<T,K,M,O>,<FIRST,SECOND>。
1 | package generic; |
1 | package generic; |
泛型接口
定义泛型接口的方式和定义泛型类很相近。
1 | package generic; |
1 | //继承了泛型接口,但是是普通类 |
泛型方法
定义泛型方法时,尖括号和类型变量需要放在修饰符和返回类型之间。
1 | package generic; |
常用的数据结构
集合(Collection)
Collection是Java中的一个接口,它定义了容器应有的方法,如添加元素、删除元素、遍历元素、清空等等。它有很多不同的实现。容器要容纳某一种类型的元素,所以在使用容器时需要用到泛型。
常用方法
Collection
为所有容器定义了元素操作,常用的有:add(E e)
方法添加元素remove()
方法删除元素clear()
方法清空容器
Collection
有很多不同的实现:
列表(List)
数组相似。但List
是一个接口,不能被实例化,需要使用ArrayList
或LinkedList
等。
集合(Set)
集合中不能有重复的元素。同样Set
也是一个接口,需要使用HashSet
或SortedSet
等。
队列(Queue)
类似列表,但是对容器的操作方式进行了限制,只能将新元素放在最后,只能拿出最早放进去的元素。具有先进先出的性质,像排队一样。Queue
也是一个接口,需要使用ArrayQueue
等。
栈(Stack)
类似队列,但是只能将新元素放在前面,且只能拿出最新放进去的元素。具有后进先出的性质。
映射(Map)
Map
是一个接口,但Map
并不是Collection
的子接口。
Map
中存储的是键值对<KEY,VALUE>,我们可以通过键拿到对应的值,即键和值构成了映射关系。
Map
是接口,我们可以用HashMap
来实例化。
常用的方法
Map实例可以通过:
V put(K k, V v)
向map中存入一个键值对,K
为键,V
为值,返回之前这个键对应的值。V remove(Object key)
根据键移除map中的键值对。V get(Object o)
根据键查找值。Set<K> keySet()
获取map的键的集合Collection<V> values()
获取map的值的集合Set<Map.Entry<K, V>> entrySet()
获取键值对集合V replace(K key, V value)
将key对应的值替换为参数value
一个例子
在之前People的demo基础上,增加一些代码。我们要定义一间教室,教室里有很多人People
,人要可以问问题,还可以回答问题,人们可以在教室里进行讨论。我的帮手小明决定帮我完成Classroom
类的定义,而我去完善人的定义。
但是现在,我们的“人”并不会问问题,也不会回答问题,小明也不知道我将会用什么样的方式实现这个功能。为了能够尽快开始工作,我们协商了两个接口Answerable
和CanAsk
,我们不需要修改什么代码,只要让People
类实现这两个接口就定义好了”人”需要怎么问问题,怎么回答问题。
1 | package classroom; |
1 | package classroom; |
1 | public abstract class People implements CanAsk, Answerable { |
此时,我和小明就可以各自开始干各自的活。他写他的Classroom
,我去完善Teacher
和Student
。而Answerable
和CanAsk
以及People
在我们完工之前不能私自进行任何改动,这三块代码已经成为了我和小明任务对接的接口,它成为了一个不能轻易改变的协议,因为我们在完成各自的任务时都需要使用到这三份代码。
三天后,任务完成了,即使这三天我们没有进行任何沟通,代码也合并得十分完美,Perfect!
完成后的代码:
Classroom.java
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83package classroom;
import java.util.*;
public class Classroom {
// 人不能有重复,所以使用set。重写了people类的equals 和 hashcode函数,集合才可以更合理地查重
private Set<People> peoples = new HashSet<>();
private String name;
// 记录题目到答案的映射。存储<题目,答案>键值对。
private static Map<String, String> question2answer = new HashMap<>();
public Classroom(String name) {
this.name = name;
//添加默认题库
question2answer.put("1+1=?", "2");
question2answer.put("杨晨学姐美不美?", "美!");
}
public String getName() {
return name;
}
/**
* 添加问题
* @param question 问题
* @param answer 答案
* @return 教室
*/
public Classroom addQA(String question,String answer){
question2answer.put(question,answer);
return this;
}
/**
* 给教室添加人
* @param people 人
* @return 教室
*/
public Classroom addPeople(People people) {
peoples.add(people);
return this;
}
public void addPeople(List<People> peoples) {
this.peoples.addAll(peoples);
}
public void addPeople(People... peoples) {
this.peoples.addAll(Arrays.asList(peoples));
}
/**
* 提供答案查询功能
* @param question
* @return
*/
public static String getAnswer(String question) {
return question2answer.get(question);
}
/**
* 开始讨论
*/
public void discuss() {
int askIndex, answerIndex, questionIndex;
// 将人和问题转成数组
People[] peopleArray = new People[peoples.size()];
String[] questionArray = new String[question2answer.keySet().size()];
question2answer.keySet().toArray(questionArray);
peoples.toArray(peopleArray);
for (int k = 0; k < 5; k++) {
//通过Math.random取随机数得到随机的索引值。
askIndex = (int) (Math.random() * peopleArray.length);
answerIndex = (int) (Math.random() * peopleArray.length);
while (answerIndex == askIndex) {
answerIndex = (int) (Math.random() * peopleArray.length);
}
questionIndex = (int) (Math.random() * questionArray.length);
//随机选择一个人随机问另一个人随机一个问题
peopleArray[askIndex].askQuestion(peopleArray[answerIndex], questionArray[questionIndex]);
}
}
}People.java
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64package classroom;
import java.util.Objects;
public abstract class People implements CanAsk, Answerable {
protected int age;
protected String name;
protected String words;
protected String action;
public People(int age, String name, String words, String action) {
this.age = age;
this.name = name;
this.words = words;
this.action = action;
}
// 每个人可以做些什么
public final void doSomething() {
String action = action();
String words = words();
int len = Math.max(action.length(), words.length()) * 2;
StringBuilder sb = new StringBuilder();
String line;
for (int i = 0; i < len / 2; i++) {
sb.append("=");
}
sb.append(name).append("的分割线");
for (int i = 0; i < len / 2; i++) {
sb.append("=");
}
line = sb.toString();
System.out.println(line);
System.out.println(action);
System.out.print("然后说:\"");
System.out.print(words);
System.out.println("。\"");
System.out.println(line);
}
// 将会说出的一段话。e.g 我是杨晨
protected abstract String words();
// 做出一个动作。e.g 露出开心的笑容
protected abstract String action();
//为了Set可以更好的判断people是否相同,重写Object类的equals和hashCode函数
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
People people = (People) o;
return age == people.age &&
Objects.equals(name, people.name) &&
Objects.equals(words, people.words) &&
Objects.equals(action, people.action);
}
public int hashCode() {
return Objects.hash(age, name, words, action);
}
}Student.java
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
43package classroom;
public class Student extends People {
public Student(int age, String name, String words, String expression) {
super(age, name, words, expression);
}
protected String words() {
return words;
}
protected String action() {
return name + "同学" +
action + "。";
}
public void answerQuestion(String answer) {
System.out.println("A--Student " + name + " :");
if(answer==null){
System.out.println("我也不知道");
return;
}
double random = Math.random();
//假设学生有50%的概率知道答案。
if (random >= 0.5) {
System.out.println("答案我知道,是:" + answer);
} else {
System.out.println("答案是...我也不知道");
}
}
public void askQuestion(Answerable answerable, String question) {
System.out.println("Q--Student " + name + " :");
System.out.println("我想问个问题:" + question);
//通知那个人(answerable)该回答问题了。
answerable.answerQuestion(Classroom.getAnswer(question));
}
}Teacher.java
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70package classroom;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
public class Teacher extends People {
protected String subject;
private final Map<String, Integer> answer2count = new HashMap<>();
public Teacher(int age, String name, String words, String expression) {
this(age, name, words, expression, "高数");
}
public Teacher(int age, String name, String words, String expression, String subject) {
super(age, name, words, expression);
this.subject = subject;
}
protected String words() {
return words + "。今天我给大家讲一讲《" +
subject +
"》";
}
protected String action() {
return name + "老师" +
action + "。";
}
public void answerQuestion(String answer) {
System.out.println("A--Teacher " + name + " :");
if (answer == null) {
System.out.println("我也不知道");
return;
}
Integer count = answer2count.get(answer);
if (count != null && count > 1) {
System.out.println("这道题我已经讲过" + count + "次了!");
}
System.out.println("这道题的答案是:" + answer);
answer2count.merge(answer, 1,
//匿名内部类
new BiFunction<Integer, Integer, Integer>() {
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
});
//Lambda表达式
// answer2count.merge(answer, 1,
// (integer, integer2) -> integer + integer2);
//使用相同的函数代替Lambda表达式
// answer2count.merge(answer, 1,
// Integer::sum);
}
public void askQuestion(Answerable answerable, String question) {
System.out.println("Q--Teacher " + name + " :");
System.out.println("问题是:" + question);
//通知那个人(answerable)该回答问题了。
answerable.answerQuestion(Classroom.getAnswer(question));
}
}Main.java
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58import classroom.*;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
People mbg = new Teacher(69, "马保国",
"年轻人不讲武德", "捂着红肿的眼睛",
"武德");
People yc = new Teacher(19, "杨晨",
"我女妆真好看", "露出开心的笑容",
"女妆的一百零八种技巧");
People zk = new Student(21, "郑开",
"杨晨学姐太强了", "拍了拍杨晨");
People robot = new People(0, "1024", "你们肯定猜不到我是机器人", "喝了一口机油") {
protected String words() {
return words;
}
protected String action() {
return name + "机器人" + action + "。";
}
public void answerQuestion(String answer) {
System.out.println("A--Robot " + name + " :");
if (answer == null) {
System.err.println("机器故障:NullPointerException");
return;
}
System.out.println("答案是:" + answer);
}
public void askQuestion(Answerable answerable, String question) {
System.out.println("Q--Robot " + name + " :");
question = "你知道我的名字吗?";
System.out.println("问你个问题:" + question);
answerable.answerQuestion(Classroom.getAnswer(question));
}
};
List<People> peoples = new ArrayList<>();
peoples.add(mbg);
peoples.add(yc);
peoples.add(zk);
peoples.add(robot);
for (People people : peoples) {
people.doSomething();
}
Classroom classroom = new Classroom("twt");
classroom.addPeople(peoples);
classroom.discuss();
}
}运行结果
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=====================马保国的分割线=====================
马保国老师捂着红肿的眼睛。
然后说:"年轻人不讲武德。今天我给大家讲一讲《武德》。"
=====================马保国的分割线=====================
============================杨晨的分割线============================
杨晨老师露出开心的笑容。
然后说:"我女妆真好看。今天我给大家讲一讲《女妆的一百零八种技巧》。"
============================杨晨的分割线============================
==========郑开的分割线==========
郑开同学拍了拍杨晨。
然后说:"杨晨学姐太强了。"
==========郑开的分割线==========
==============1024的分割线==============
1024机器人喝了一口机油。
然后说:"你们肯定猜不到我是机器人。"
==============1024的分割线==============
Q--Student 郑开 :
我想问个问题:1+1=?
A--Teacher 杨晨 :
这道题的答案是:2
Q--Teacher 马保国 :
问题是:1+1=?
A--Teacher 杨晨 :
这道题的答案是:2
Q--Student 郑开 :
我想问个问题:杨晨学姐美不美?
A--Robot 1024 :
答案是:美!
Q--Teacher 马保国 :
问题是:杨晨学姐美不美?
A--Robot 1024 :
答案是:美!
Q--Teacher 马保国 :
问题是:1+1=?
A--Student 郑开 :
答案是...我也不知道
Process finished with exit code 0