目录:
- 内部类
- 枚举
- 泛型
- 常用API(一)
内部类
- 是类中的五大成分之一(成员变量,方法,构造器,内部类,代码块),如果一个类定义在另一个类的内部,这个类就是内部类。
- 场景:当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类。
public class Car{
//内部类
public class Engine{
}
}
内部类有四种形式
- 成员内部类
就是类中的一个普通成员,类似前面学过的普通的成员变量、成员方法。
public class Outer{
//成员内部类
public class Inner{
}
}
public static void main(String[] args){
//创建内部类的对象的语法
//外部类名.内部类名 对象名 = new 外部类(...).new 内部类(...);
Outer.Inner in = new.Outer().new Inner();
}
成员内部类中访问其他成员的特点:
- 和前面学过的实例方法一样,成员内部类的实例方法中,同样可以直接访问外部类的实例成员、静态成员。
- 可以在成员内部类的实例方法中,拿到当前外部类对象,格式是:
外部类名.this
。
public class Outer{
private int age = 99;
public class Inner{
private int age = 88;
public void show(){
int age = 77;
//三种变量的访问方法
System.out.println(age);//77
System.out.println(this.age);//88
System.out.println(Outer.this.age);//99
}
}
}
public static void main(String[] args){
//创建内部类的对象的语法
Outer.Inner in = new.Outer().new Inner();
}
- 静态内部类
有static修饰的内部类,属于外部类自己持有。
public class Outer{
//静态内部类
public static class Inner{
}
}
public static void main(String[] args){
//创建内部类的对象的语法
//外部类名.内部类名 对象名 = new 外部类(...).内部类(...);
Outer.Inner in = new.Outer().Inner();
//相比较成员内部类这里内部类不需要new只要new外部类就行了
}
静态内部类中访问外部类成员的特点
- 可以直接访问外部类的静态成员,不可以直接访问外部类的实例成员。
- 局部内部类
局部内部类是定义在方法中、代码块中、构造器等执行体中。
public class Test{
public static void main(String[] args){
}
public static void go(){
class A{
}
abstract class B{
}
interface C{
}
}
}
局部内部类,鸡肋语法,不建议使用。
- 匿名内部类(重点)
就是一种特殊的局部内部类;所谓匿名:指的是程序员不需要为这个类声明名字。
new 类或接口(参数值..){
类体(一般是方法重写);
}
eg:
public static void main(String[] args){
Animal a = new Animal(){
@Override
public void cry(){
System.out.println("猫叫")
}
};
a.cry();
}
abstract class Animal{
public abstract void cry();
}
特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象。
作用:用于更方便的创建一个子类对象。
匿名内部类在开发中的使用场景
- 通常作为一个参数传输给方法。
需求:猫、狗参加游泳比赛。
public interface Swimming{
void swim();
}
public class Test{
public static void main(String[] args){
Swimming s1 = new Swimming(){
@Override
public void swim(){
System.out.println("狗游的飞快");
}
}
go(s1);
go(new Swimming(){
@Override
public void swim(){
System.out.println("猫游的一般");
}
});
}
public static void go(Swimming s){
System.out.println("开始。。。。。");
s.swim();
}
}
# 匿名内部类小结
1. 匿名内部类的书写格式是什么样的?
new 类或接口(参数值...){
类体(一般是方法重写);
}
new Animal(){
@Override
public void cry(){
}
}
2. 匿名内部类有什么特点?
- 匿名内部类本质就是一个子类,并会立即创建出一个子类对象
3. 匿名内部类有什么作用、应用场景?
- 可以更方便的创建出一个子类对象。
- 匿名内部类通常作为一个参数传输给方法。
枚举
枚举是一种特殊类。
枚举类的格式:
修饰符 enum 枚举类名 {
名称1,名称2,...;
其他成员...
}
public enum A{
X,Y,Z;
...
}
注意:
- 枚举类中的第一行,只能写一些合法的标识符(名称),多个名称用逗号隔开。
- 这些名称,本质是常量,每个常量都会记住枚举类的一个对象。
枚举类的特点:
- 枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象。
- 枚举类的构造器都是私有的(写不写都是私有的),因此,枚举类不能创建对象。
- 每句都是最终类,不可以被继承。
- 枚举类中,从第二行开始,可以定义类的其他各种成员。
- 编译器为枚举类新增了几个方法,并且枚举类都是继承java.lang.Enum类的,从Enum类也会继承到一些方法。
public class Test {
public static void main(String[] args) {
A a1 = A.X;
System.out.println(a1);
//枚举类的构造器是私有的,不能对外创建对象
//A a = new A();
//枚举类的第一行都是常量,记住的是枚举类的对象
A a2= A.Y;
//枚举类提供了一些额外的API
A[] as = A.values();
A a3 = A.valueOf("Z");
System.out.println(a3.name());//Z
System.out.println(a3.ordinal());//索引
}
}
public enum A {
//枚举第一行必须罗列的是枚举对象的名字。
X, Y, Z;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
抽象枚举
public enum B {
//因为这个枚举中有抽象方法,所以在枚举对象必须在声明的时候就创建。
X(){
@Override
public void go() {
}
},Y("zhang"){
@Override
public void go() {
System.out.println(getName() + "...");
}
};
private String name;
B() {
}
B(String name) {
this.name = name;
}
public abstract void go();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
B y = B.Y;
y.go();
}
}
因为枚举构造器私有,所以可以直接用枚举写单例。
public enum C{
X;
}
枚举的常见应用场景:
- 用来表示一组信息,然后作为参数进行传输。
选择定义单个的常量来表示一组信息,并作为参数传输。
- 参数值不受约束
选择定义枚举表示一组信息,并作为参数传输
- 代码可读性好,参数值得到了约束,对使用者更友好,建议使用!
public enum Sex {
BOY, GIRL;
}
public class Test {
public static void main(String[] args) {
check(Sex.BOY);
}
//进行分类,使用枚举类限制了传入信息。
public static void check(Sex sex) {
switch (sex) {
case BOY:
System.out.println("针对男性用户操作业务");
break;
case GIRL:
System.out.println("针对女性用户操作业务");
break;
}
}
}
泛型
-
定义类、接口、方法时,同时声明了一个或者多个类型变量(如:
<E>
),称为泛型类、泛型接口,泛型方法,他们统称为泛型。 -
作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换,及其可能出现的异常。
-
泛型的本质:把具体的数据类型作为参数传给类型变量。
泛型类
修饰符 class 类名<类型变量,类型变量, ...>{
}
pubic class ArrayList<E>{
}
泛型接口
修饰符 interface 接口名<类型变量,类型变量, ...>{
}
public interface A<E>{
}
interface Data<E extends People>{
void add(E e);
ArrayList<E> getByName(String name);
}
class StudentData implements Data<Student>{
@Override
public void add(Student student) {
}
@Override
public ArrayList<Student> getByName(String name) {
return null;
}
}
class TeacherData implements Data<Teacher>{
@Override
public void add(Teacher teacher) {
}
@Override
public ArrayList<Teacher> getByName(String name) {
return null;
}
}
注意:类型变量建议用大写的英文字母,常用的有:E,T,K,V等。
泛型方法、泛型通配符、上下限
修饰符 <类型变量,类型变量, ...> 返回值类型 方法名(形参列表){
}
public static <T> void test(T t){
}
泛型通配符
- 就是
?
,可以在“使用泛型”的时候代表一切类型;E T K V是在定义泛型的时候使用的。
// ? 通配符:在使用泛型的时候可以代表一切类型。
public static void go(ArrayList<?> cars){ }
上下限
- 泛型上限:
? extends Car
?
能接收的必须是Car或者其子类 - 泛型上限:
? super Car
?
能接收的必须是Car或者其父类
// ? extends Car (上限)
public static void go2(ArrayList<? extends Car> cars){ }
// ? super Car (下限)
public static void go(ArrayList<? super Car> cars){ }
泛型擦除问题、包装类介绍
泛型的擦除问题和注意事项
- 泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除。
- 泛型不支持基本数据类型,只能支持对象类型(引用数据类型)
- 解决方案使用基本数据类型的包装类(Integer,Double…)
java.lang包下的常用API
Object
Object类是Java中所有类的祖宗类,因此,Java中所有类的对象都可以直接使用Object类中提供的一些方法。
Object类常见的方法
方法名 | 说明 |
---|---|
public String toString() | 返回对象的字符串表示形式。 |
public boolean equals(Object o) | 判断两个对象是否相等。 |
protected Object clone() | 对象克隆 |
# equals的逻辑
1. 判断两个对象是否地址一样,一样直接返回true
2. 判断传入的o是null直接返回false,或者比较者(this)的类型与被比较者的类型不一样,返回false
3. 保证类型一致且不为空,开始逐个比较对象的属性。
toString存在的意义:toString()方法存在的意义就是为了被子类重写,以便返回对象具体的内容。
equals存在的意义:直接比较两个对象的地址是否相同完全可以用“==”替代equals,equals存在的意义就是为了被子类重写,以便子类自己来定制比较规则(比如比较对象内容)。
clone:当某个对象调用这个方法时,这个方法会复制一个一模一样的新对象返回。
概念补充:
浅克隆:拷贝出的新对象,与原对象中的数据一模一样(引用类型拷贝的只是地址)
深拷贝:对象中基本类型的数据直接拷贝。对象中的字符串数据拷贝的还是地址(因为字符串放在了常量池中)。对象中还包含的其他对象,不会拷贝地址,会创建新对象。
Objects
Objects是一个工具类,提供了很多操作对象的静态方法给我们使用。
方法名 | 说明 |
---|---|
public static boolean equals(Object a, Object b) | 先做非空判断,再比较两个对象 |
public static boolean isNull(Object obj) | 判断对象是否为null,为null返回true,反之 |
public static boolean nonNull(Object obj) | 判断对象是否不为null,不为null则返回true,反之 |
Objects.equals源码://这样更加安全
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
包装类
为什么要有包装类?
包装类就是把基本类型的数据包装成对象。
基本数据类型 | 对应的包装类(引用数据类型) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
包装类的使用
Integer a = new Integer(12);//已经舍弃
Integer b = Integer.valueOf(12);
自动装箱和自动拆箱
//自动装箱: 可以自动把基本类型的数据转换成对象
Integer a = 12;
//自动拆箱: 可以自动把包装类型的对象转换成对应的基本数据类型。
int b = a;
包装类存在的意义是什么?
泛型和集合不支持基本数据类型,只能支持引用数据类型。
ArrayList<Integer> list = new ArrayList<>();
list.add(12);
list.add(21);//自动装箱
int res = list.get(1);//自动拆箱
自动装箱:基本数据类型可以自动转换为包装类型。
自动拆箱:包装类型可以自动转换为基本数据类型。
包装类的常见操作:
-
可以把基本数据类型转换成字符串类型
public static String toString(doublic d)
public String toString
-
可以把字符串类型的数值转换成数值本身对应的数据类型。
public static int parseInt(String s)
public static Integer valueOf(String s)
StringBuilder,StringBuffer
- StringBuilder代表可变字符串对象,相当于一个容器,它里面装的字符串可以改变的,就是用来操作字符串的。
- 好处:StringBuilder比String更适合做字符串的修改操作,效率会更高,代码也会更简洁。
构造器 | 说明 |
---|---|
public StringBuilder() | 创建一个空白的可变的字符串对象,不包含任何内容 |
public StringBuilder(String str) | 创建一个指定字符串内容的可变字符串对象。 |
方法名称 | 说明 |
---|---|
puclic StringBuilder append(任意类型) | 添加并返回StringBuilder对象本身 |
puclic StringBuilder reverse() | 将对象的内容反转 |
public int length() | 返回对象内容长度 |
public int toString() | 通过toString()就可以实现把StringBuilder对象转换为String |
为什么操作字符串建议使用StringBuilder,而不是String?
- 对于字符串相关的操作,如果频繁的拼接、修改等,建议使用StringBuilder,效率更高!
- 注意:如果操作字符串较少,或者不需要操作,以及定义字符串变量,还是建议使用String。
StringBuffer又是什么?
- StringBuffer的用法和StringBuilder是一摸一样的。
- 但是StringBuffer是现成安全的,StringBuilder是线程不安全的。
什么是线程安全?
就是在操作某个方法的时候只能有一个线程来操作,一定程度会降低效率。
案例
设计一个方法,用于返回任意整型数组的内容,要求返回的数组内容格式:[11, 22, 33]
分析:
-
方法是否需要接收数据? 需要接受整型数组
-
方法是否需要返回类型? 需要的是返回后的拼接结果。
-
方法内部:遍历数组的数据,把遍历到的数据都拼接起来,使用StringBuilder来完成。
public class Test1 {
public static void main(String[] args) {
int[] arr = new int[5];
arr[0] = 11;
arr[1] = 22;
arr[2] = 33;
arr[3] = 44;
arr[4] = 55;
String info = getArrInfo(arr);
System.out.println(info);
}
public static String getArrInfo(int[] arr) {
//判断为空
if (arr == null) {
return null;
}
StringBuilder sb = new StringBuilder("[");
//遍历添加信息
for (int i = 0; i < arr.length; i++) {
if (i == arr.length - 1) {
sb.append(arr[i]);
} else
sb.append(arr[i]).append(", ");
}
//信息结尾
sb.append("]");
//返回结果
return sb.toString();
}
}
StringJoiner
- JDK8开始才有的,跟StringBulider一样,也是用来操作字符串的,也可以看成一个容器,创建之后里面的内容是可变的。
- 好处:不仅能提高字符串的的操作效率,并在有些场景下使用它操作字符串,代码会更简洁。
构造器 | 说明 |
---|---|
public StringJoiner(间隔符号) | 创建一个StringJoiner对象,指定拼接时的间隔符号。 |
public StringJoiner(间隔符号, 开始符号, 结束符号) | 创建一个StringJoiner对象,指定拼接时的间隔符号、开始符号、结束符号。 |
方法名称 | 说明 |
---|---|
public StringJoiner add(添加的内容) | 添加数据,并返回对象本身 |
public int length() | 返回长度(字符个数) |
public String toString() | 返回一个字符串(该字符串就是拼接之后的结果) |
public class Test1 {
public static void main(String[] args) {
int[] arr = new int[5];
arr[0] = 11;
arr[1] = 22;
arr[2] = 33;
arr[3] = 44;
arr[4] = 55;
System.out.println(getArrInfo2(arr));
}
public static String getArrInfo2(int[] arr) {
//判断为空
if (arr == null) {
return null;
}
StringJoiner sj = new StringJoiner(",", "[", "]");
//遍历添加信息
for (int i = 0; i < arr.length; i++) {
sj.add(arr[i] + "");
}
//返回结果
return sj.toString();
}
}