类
属性和方法
java是面向对象的编程语言
声明一个类:
class Person {
public String name;
public int age;
}
上面创建了一个Person类,并赋予了两个属性
public表示该属性可以被外界访问
定义好了类后,需要创建实例:
Person ming = new Person();
上面就生成了一个实例
在外部可以对实例的属性进行操作
ming.age = 18;
ming.name = "xiao ming";
但是,外部操作会破坏封装性,我们可以使用private来私有化属性
class Person {
private String name;
private int age;
}
这样外部无法访问这两个属性 不过可以通过提供方法来修改属性
class Person {
private String name;
private int age;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
我们可以在定义方法时对传入的参数进行检查 避免属性被修改
为不合理的值
同样 private可以声明私有方法 这种方法只能在类的内部调用
在方法内部有一个隐含变量 this
它始终指向当前实例 比较类似
于python的self
在没有命名冲突的情况下 this
可以省略
方法可以传参,在定义方法时将需要的参数严格声明
class Person{
public void setNameAndAge(String name,int age){
}
}
可变参数则用类型...
这样的语法来声明,相当于数组类型 在python中的args也是同样的原理
public void setNames(String... names){
///
}
注意 基本类型参数的传递是调用的复制的值
但引用类型的传递则会受到影响 类似于python的浅拷贝
构造方法
在实例生成时就生成好属性 相当于python的__init__
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
构造方法的名称就是类名 只有在使用new时才会调用构造方法 若一个类没有声明构造方法 编译器会自动生成一个空的构造方法
注意 可以有多个构造方法
class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
因此创建实例时 有没有参数都不会报错
没有初始化的字段 引用类型默认为NULL int为0 布尔为False
方法重载指的是声明多个名字相同的方法,传入的参数不同 以应对不同输入的场景
继承
python这一块就是瞎学的,正好复习一下
Java使用extends
来实现继承
class Person {
private String name;
private int age;
public String getName() {...}
public void setName(String name) {...}
public int getAge() {...}
public void setAge(int age) {...}
}
class Student extends Person {
// 不要重复name和age字段/方法,
// 只需要定义新增score字段/方法:
private int score;
public int getScore() { … }
public void setScore(int score) { … }
}
注意 Person类虽然没有写extends 但事实上它是object类的继承 也就是说 只有object没有父类
一个类只能有一个父类
对于private
的字段 子类无法访问
若使用protect
字段修饰 则子类可以访问
super
关键字表示父类 子类可以以此引用父类的属性
class Student extends Person {
public String Hello(){
return "Hello," + super.name;
}
}
注意 在继承中 如果子类没有声明如何调用父类的构造方法 则编译器会自动调用父类的构造方法super();
如果参数存在问题 就会报错
instanceof
用于判断某个实例是否是某个类的(子类)
多态
子类定义了一个与父类完全相同的方法 则为覆写(python中的重写)
举例
父类
class Person {
public void run() {
System.out.println("Person.run");
}
}
子类
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
注意 override 必须是方法名相同 方法参数相同 方法返回值相同
加上@override可以帮助检查是否进行了正确的覆写
那么什么叫做多态呢?我们看下面这种情况
Person p = new Student();
引用类型为Person 实际类型为 Student 那么在调用方法时 生效的是Student的也就是子类的方法
这种情况就称为多态
多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法
举个例子
public void runTwice(Person p) {
p.run();
p.run();
}
传入的参数是Person对象 在调用run方法时则根据传入对象实际的类来调用
因为所有类都继承自 object
对象 因此可以覆写object
的方法toString()
equals()
hashCode()
在子类中 可以通过super 来调用父类没有被覆写的方法
使用final
修饰的父类方法不能被覆写
同理 使用final
修饰的类 不能被继承
使用final
修饰的字段也不能被重新赋值
抽象类
abstract
声明的是一个抽象方法
class Person {
public abstract void run();
}
这样一个抽象方法可以没有执行语句 但可以被继承和覆写
此时 Person类将无法编译 除非声明它是一个抽象类
abstract Person {
public abstract void run();
}
对于一个抽象类 我们无法实例化它 抽象类的价值在于被子类继承 即抽象类相当于规范
接口
使用interface
可以声明一个接口
interface Person {
void run();
String getName();
}
接口的所有方法都是默认抽象的 当一个类想要去实现一个接口时,使用implements
class Student implements Person{
///
}
一个类可以实现多个接口
class Student implements Person,Hello{
///
}
接口可以拓展 使用extends
interface Hello {}
interface Person extends Hello{}
对于每个抽象方法 实现类都需要去覆写 为了避免在接口增加某个新方法后,我们要去所有的实现类里覆写该方法 可以使用
defalut
来声明默认方法 该方法不需要实现类覆写
静态字段、静态方法
public static int number;
静态字段是所有实例对象共享的 即任何一个实例对它修改都相当于修改了类中的该字段
静态方法通过类名就可以调用 不需要实例对象
public static void setNumber();
Person.setNumber();
静态方法属于类而不属于实例 方法内部无法使用this
变量 也不能访问实例字段
静态方法常用于工具类
包
每个类都有一个包名
比如 数组来自于 java.util.Arrays
package ming;
public class Person{
///;
}
使用import
来引用其他包的class
Java编译器最终编译出的.class文件只使用完整类名,因此,在代码中,当编译器遇到一个class名称时:
如果是完整类名,就直接根据完整类名查找这个class;
如果是简单类名,按下面的顺序依次查找:
查找当前package是否存在这个class;
查找import的包是否包含这个class;
查找java.lang包是否包含这个class。
集合
List
Java标准库自带的java.util
包提供了集合类:Collection
,它是除Map
外所有其他集合类的根接口。Java的java.util
包主要提供了以下三种类型的集合:
- List:一种有序列表的集合,例如,按索引排列的Student的List;
- Set:一种保证没有重复元素的集合,例如,所有无重复名称的Student的Set;
- Map:一种通过键值(key-value)查找的映射表集合,例如,根据Student的name查找对应Student的Map。
集合的接口类和实现类是分离的
List<String> list = new ArrayList<>(); // 只能放入String类型
List是有序列表 数组在增删元素时非常不方便 我们更多使用ArrayList
我们考察List<E
>接口,可以看到几个主要的接口方法:
- 在末尾添加一个元素:boolean add(E e)
- 在指定索引添加一个元素:boolean add(int index, E e)
- 删除指定索引的元素:E remove(int index)
- 删除某个元素:boolean remove(Object e)
- 获取指定索引的元素:E get(int index)
- 获取链表大小(包含元素的个数):int size()
另一种List为LinkedList
即链表 指针域指向下一个节点的位置
快速创建List
List<Integer> list = List.of(1, 2, 5);
由于List实现了迭代器接口,因此我们可以使用for each来遍历它
for (String s : list) {
System.out.println(s);
}
List使用 toArray()
方法直接返回数组
常用写法
Integer[] array = list.toArray(new Integer[list.size()]);
List还提供了boolean contains(Object o)
方法来判断List是否包含某个指定元素。此外,int indexOf(Object o)
方法可以返回某个元
素的索引,如果元素不存在,就返回-1。
注意 这种方法要求比较的类是写好了equals方法的 因为查找的对象是一个新对象 如果没有写equals方法 是无法判断list里面有没有的
Map
Map类似于python的字典
HashMap
Map<String, Student> map = new HashMap<>();
map.put("Xiao Ming", s);
Student target = map.get("Xiao Ming");
Map<K, V>
是一种键-值映射表,当我们调用put(K key, V value)
方法时,就把key和value做了映射并放入Map。当我们调用
V get(K key)
时,就可以通过key获取到对应的value。如果key不存在,则返回null。和List类似,Map也是一个接口,最常用的
实现类是HashMap
。
Map.keySet()
获得一个可迭代对象 可以直接遍历
map.entrySet()
则获得键值对集合
for (Map.Entry<String, Integer> entry : map.entrySet())
通过key计算索引的方式就是调用key对象的hashCode()方法,它返回一个int整数。HashMap正是通过这个方法直接定位key对应
的value的索引,继而直接返回value。
因此,正确使用Map必须保证:
- 作为key的对象必须正确覆写
equals()
方法,相等的两个key实例调用equals()
必须返回true; - 作为key的对象还必须正确覆写
hashCode()
方法,且hashCode()
方法要严格遵循以下规范:
如果两个对象相等,则两个对象的hashCode()必须相等;
如果两个对象不相等,则两个对象的hashCode()尽量不要相等。
EnumMap
如果作为key的对象是enum
类型(枚举类),那么,还可以使用Java集合库提供的一种EnumMap
,它在内部以一个非常紧凑的数组存储
value,并且根据enum
类型的key直接定位到内部数组的索引,并不需要计算hashCode()
,不但效率最高,而且没有额外的空间浪
费。
TreeMap
TreeMap是对key排序的map,接口为SortedMap
使用TreeMap
时,放入的Key必须实现Comparable
接口。
如果作为Key的class没有实现Comparable
接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法:
Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
public int compare(Person p1, Person p2) {
return p1.name.compareTo(p2.name);
}
Properties
配置文件的特点是,它的Key-Value一般都是String-String类型的,因此我们完全可以用Map<String, String>
来表示它。
Set
Set和python中的集合一样 存储不重复元素的集合
- 将元素添加进
Set<E>
:boolean add(E e)
- 将元素从
Set<E>
删除:boolean remove(Object e)
- 判断是否包含元素:
boolean contains(Object e)
Set<String> set = new HashSet<>();
Queue
队列和python中的队列相同 他是先进先出的有序表
- int size():获取队列长度;
- boolean add(E)/boolean offer(E):添加元素到队尾;
- E remove()/E poll():获取队首元素并从队列中删除;
- E element()/E peek():获取队首元素但并不从队列中删除。
Queue<String> q = new LinkedList<>();
PriorityQueue
优先级队列在取出时将优先级最高的元素取出
Queue<String> q = new PriorityQueue<>();
// 添加3个元素到队列:
q.offer("apple");
q.offer("pear");
q.offer("banana");
在取出时,banana会在peer之前,因为String的compare接口按照字典序排序
因此要使用优先级队列 需要先定义好compare接口
Deque
双向队列即可以从队首和队尾同时进行的队列
Deque<String> deque = new LinkedList<>();
Deque提供的方法为
addLast(E e) / offerLast(E e)
E removeFirst() / E pollFirst()
E getFirst() / E peekFirst()
addFirst(E e) / offerFirst(E e)
E removeLast() / E pollLast()
E getLast() / E peekLast()
Stack
栈 即先入后出的队列
现版本的java不再使用stack了 使用Deque即可实现stack
Iterator
即python中的迭代器
例如在遍历一个list对象时,实际上调用了list的迭代器对象
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
如果我们自己编写了一个集合类,想要使用for each循环,只需满足以下条件:
- 集合类实现Iterable接口,该接口要求返回一个Iterator对象;
- 用Iterator对象迭代集合内部数据。
Collections
Collections
是JDK提供的工具类,同样位于java.util包中。它提供了一系列静态方法,能更方便地操作各种集合。
Collections提供了一系列方法来创建空集合:
创建空List:List<T> emptyList()
创建空Map:Map<K, V> emptyMap()
创建空Set:Set<T> emptySet()
Collections提供了一系列方法来创建一个单元素集合:
创建一个元素的List:List<T> singletonList(T o)
创建一个元素的Map:Map<K, V> singletonMap(K key, V value)
创建一个元素的Set:Set<T> singleton(T o)
collections还可以进行排序 洗牌
Collections.sort(list);
Collections.shuffle(list);