day03-面向对象-内部类泛型常用API
一、内部类
内部类是类中的五大成分之一(成员变量、方法、构造器、代码块、内部类)
如果一个类定义在另一个类的内部,这个类就是内部类。
场景:当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类
内部类分为四种:
成员内部类[了解]:位于一个类里面成员位置的类
静态内部类[了解]:使用static修饰的成员内部类
局部内部类[了解]:在方法里面定义的类
匿名内部类[重点]:一种特殊的局部内部类
1.1 成员内部类(了解)
成员内部类 定义在一个类中成员位置的类 定义类的格式: public class Outer{ //成员内部类 public class Inner{ } } 创建对象的格式: 外部类名.内部类名 对象名 = new 外部类(..).new 内部类(..); Outer.Inner in = new Outer().new Inner(); 使用特点及注意: 1、成员内部类中可以定义实例成员,静态成员 (注意: 静态成员从JDK16开始支持) 2、成员内部类中的实例方法中,可以直接访问外部类的实例成员,静态成员 3、如果内部和外部类出现了重名的成员,可以通过(外部类名.this.xxx) 强行访问外部类的成员
public class Demo {
public static void main(String[] args) {
//创建对象: 外部类名.内部类名 对象名 = new 外部类(..).new 内部类(..);
Car.Engine engine = new Car().new Engine();
//调用内部类方法
engine.setPower(211);
//访问内部类的静态成员
System.out.println(Car.Engine.price);
engine.showName();
engine.showPrice();
}
}
class Car{
private String name = "宝马";
private int price = 300000;
//成员内部类(与成员变量同级)
public class Engine{
private int power;
//静态成员变量
public static int price = 1000;
//2、成员内部类中的实例方法中,可以直接访问外部类的实例成员,静态成员
public void showName(){
System.out.println("name=" + name);
}
//3、如果内部和外部类出现了重名的成员,可以通过(外部类名.this.xxx) 强行访问外部类的成员
public void showPrice(){
System.out.println("price=" + price);
System.out.println("price=" + Car.this.price);
}
//get/set
public int getPower() {
return power;
}
public void setPower(int power) {
this.power = power;
}
}
}
3.2 静态内部类(了解)
静态内部类 使用static修饰的内部类 定义类的格式: public class Outer{ public static class Inner{ } } 创建对象的格式: 外部类名.内部类名 对象名 = new 外部类(..).内部类(..); Outer.Inner in = new Outer().Inner(); 特点: 1. 可以直接访问外部类的静态成员 2. 不可以直接访问外部类的实例成员
public class Demo {
public static void main(String[] args) {
//创建静态内部类对象
Outer.Inner inner = new Outer.Inner();
//静态内部类,只能访问外部类的静态成员
inner.show();
}
}
class Outer{
private static int num = 100;
private String name = "张三";
public static class Inner{
public void show(){
System.out.println(num);//静态内部类,只能访问外部类的静态成员
//System.out.println(name);//静态内部类不可以直接访问外部类的实例成员
}
}
}
3.3 ⭐匿名内部类⭐
匿名内部类 作用 更方便的创建一个子类对象(简化操作类、接口的代码) 格式 new 类名/接口名(参数值){ 一般都是方法重写; }; 特点 本质是一个子类对象, 编译器会帮我们创建一个子类的对象
public class Demo {
public static void main(String[] args) {
//原始写法:
//需求1: 创建Animal的子类对象,并调用方法
Animal dog1 = new Dog();
dog1.eat();
//需求2: 创建Bird的实现类对象,并调用方法
Bird crow1 = new Crow();
crow1.drink();
// 匿名内部类写法:
//需求3: 创建Animal的子类对象,并调用方法(匿名内部类实现)
Animal dog2 = new Animal() {
@Override
public void eat() {
System.out.println("狗吃肉");
}
};
dog2.eat();
//需求4: 创建Bird的实现类对象,并调用方法(匿名内部类实现)
Bird crow2 = new Bird() {
@Override
public void drink() {
System.out.println("乌鸦喝可乐");
}
};
crow2.drink();
}
}
//抽象类
abstract class Animal {
public abstract void eat();
}
//创建Animal的子类
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
//接口(Bird 鸟 Crow 乌鸦)
interface Bird {
void drink();
}
// 创建Bird的实现类对象
class Crow implements Bird{
@Override
public void drink() {
System.out.println("乌鸦喝水");
}
}
匿名内部类应用场景
匿名内部类应用场景------作为方法的参数进行传递 如果一个方法将一个抽象类/接口做为参数,那我们可以直接传递该抽象类/接口的匿名内部类对象
public class Demo {
public static void main(String[] args) {
//需求1: 调用test1方法, 打印出 狗在吃肉
test1(new Animal() {
@Override
public void eat() {
System.out.println("狗在吃肉");
}
});
//需求2: 调用test2方法, 打印出 乌鸦在喝水
test2(new Bird() {
@Override
public void drink() {
System.out.println("乌鸦在喝水");
}
});
}
public static void test1(Animal animal) {
animal.eat();
}
public static void test2(Bird bird) {
bird.drink();
}
}
//抽象类
abstract class Animal {
public abstract void eat();
}
//接口
interface Bird {
void drink();
}
二、泛型
2.1 认识泛型
定义 定义类、接口、方法时,同时声明的类型变量(如:<E>) ,称为泛型。 作用 泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力! 这样可以避免强制类型转换,及其可能出现的异常。 本质 把具体的数据类型作为参数传给类型变量 分类 泛型类、泛型接口、泛型方法
public class Demo {
public static void main(String[] args) {
//需求1. 定义一个ArrayList不设置泛型, 保存元素, 然后遍历求元素的长度
/* ArrayList list1 = new ArrayList();
list1.add("hello");
list1.add("world");
list1.add(123);
list1.add(true);
for (int i = 0; i < list1.size(); i++) {
Object o = list1.get(i);//多态
//强转
String s = (String) o;
System.out.println(s.length());//运行时异常
}*/
//需求2. 定义一个ArrayList, 设置泛型为String, 保存数据, 然后遍历求元素的长度
ArrayList<String> list = new ArrayList<>();
list.add("莉莉");
list.add("小明");
list.add("hello");
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s.length());
}
}
}
2.2 ⭐泛型类⭐
泛型类 在定义类的时候设置泛型, 然后在后续方法上使用 格式 修饰符 class 类名<类型变量,类型变量,…> { } 注意: 类型变量建议用大写的英文字母,常用的有:E、T、K、V 等
public class Demo {
public static void main(String[] args) {
MyList<String> list = new MyList<>();
list.add("Lihua");
System.out.println(list.get(0));
}
}
//自定义一个泛型类, 模仿ArrayList的add和get功能,只能存放10条数据
class MyList<E>{//E表示数据类型
//存放元素的数组:1.定义一个长度为10的数组
//private E[] arr = (E[]) new Object[10];//写法1
private Object [] arr = new Object[10];//写法2
//2.定义一个索引变量:记录当前数组中存放了多少个元素
private int index = 0;
//add方法:向数组中添加元素
public void add(E e){
arr[index] = e;
index++;
}
//get方法:从数组中获取指定索引位置的元素,并返回元素
public E get(int index){
return (E) arr[index];
}
}
2.3 泛型接口
泛型接口 在定义接口的时候声明泛型 格式 修饰符 interface 类名<类型变量,类型变量,…> { } 注意 类型变量建议用大写的英文字母,常用的有:E、T、K、V 等
public class Demo {
public static void main(String[] args) {
PersonInterface<Teacher> teacherInterface= new TeacherInterfaceImpl();
teacherInterface.findByName("张三");
}
}
//需求: 定义一个接口(接口中拥有两个功能: 保存对象数据和根据名称返回对象)
//谁实现这个接口,谁就需要对两个功能做就提实现
interface PersonInterface<E>{
void add(E e);//保存
E findByName(String name);//查询
}
//实现公共的泛型接口,并设置数据类型
//接口可以变成公共的,实现类不能变成公共的
class TeacherInterfaceImpl implements PersonInterface<Teacher>{
@Override
public void add(Teacher teacher) {
}
@Override
public Teacher findByName(String name) {
System.out.println("根据name查询教师" + name);
return null;
}
}
class StudentInterfaceImpl implements PersonInterface<Student>{
@Override
public void add(Student student) {
}
@Override
public Student findByName(String name) {
return null;
}
}
2.4⭐ 泛型方法⭐
泛型方法 修饰符<类型变量1, 类型交最2> 返回值类型 方法名(参数列表){ } 注意: 类型变量建议使用大写的英文宁母,常见为E、T、K、V等
public class Demo {
public static void main(String[] args) {
//需求: 编写一个将两个相同类型的对象放入一个集合的方法
ArrayList<String> list1 = add("1", "2");
Teacher t1 = new Teacher();
Teacher t2 = new Teacher();
ArrayList<Teacher> list2 = add(t1, t2);
Student s1 = new Student();
Student s2 = new Student();
ArrayList<Student> list3 = add(s1,s2);
System.out.println(list3);
System.out.println(list3.size());
}
//使用泛型方法,代替下面三个方法
public static <E> ArrayList<E> add(E a,E b){
ArrayList<E> arrayList = new ArrayList<>();
arrayList.add(a);
arrayList.add(b);
return arrayList;
}
/*
//将两个字符串放入一个集合,传入两个字符串,构建一个List集合并返回
public static ArrayList<String> add(String a, String b) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add(a);
arrayList.add(b);
return arrayList;
}
//将两个Teacher放入一个集合,传入两个Teacher,构建一个List集合并返回
public static ArrayList<Teacher> add(Teacher a, Teacher b) {
ArrayList<Teacher> arrayList = new ArrayList<>();
arrayList.add(a);
arrayList.add(b);
return arrayList;
}
//将两个Student放入一个集合,传入两个student,构建一个List集合并返回
public static ArrayList<Student> add(Student a, Student b) {
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(a);
arrayList.add(b);
return arrayList;
}
*/
}
class Teacher {
}
class Student {
}
2.5 泛型通配符、上下限
通配符 <?>,可以在使用泛型的时候,代表一切类型 泛型上限 <?extends 类名>,能接收指定类型及其子类型的参数 泛型下限 <?super 类名>,能接收指定类型和父类型的参数
public class Demo {
public static void main(String[] args) {
//数据准备
ArrayList<Animal> animals = new ArrayList();
ArrayList<Dog> dogs = new ArrayList<Dog>();
ArrayList<Person> persons = new ArrayList<Person>();
ArrayList<Teacher> teachers = new ArrayList<Teacher>();
ArrayList<Student> students = new ArrayList<Student>();
//---------------需求1:集合中可以存放任意类型的参数---------------------
m1(animals);
m1(dogs);
m1(persons);
m1(students);
m1(teachers);
//---------------需求2:集合中可以存放Person及其子类型的参数(上限)---------------------
//m2(animals);
//m2(dogs);
m2(persons);
m2(students);
m2(teachers);
//---------------需求3:集合中可以存放Person及其父类型的参数(下限)---------------------
m3(animals);
// m3(dogs);
m3(persons);
// m3(students);
// m3(teachers);
}
//需求1: 定义一个方法m1,参数为一个ArrayList集合,集合中可以存放任意类型的参数
public static void m1(ArrayList<?> list){
System.out.println(list.size());
}
//需求2: 定义一个方法m2,参数为一个ArrayList集合,集合中可以存放Person及其子类型的参数
public static void m2(ArrayList<? extends Person> list){
System.out.println(list.size());
}
//需求3: 定义一个方法m3,参数为一个ArrayList集合,集合中可以存放Person及其父类型的参数
public static void m3(ArrayList<? super Person> list){
System.out.println(list.size());
}
}
//动物
class Animal{
}
//狗
class Dog extends Animal{
}
//人
class Person extends Animal{
}
//老师
class Teacher extends Person{
}
//学生-+
class Student extends Person{
}
2.6 泛型的擦除和注意事项
泛型的注感事项 1、一旦将java文件编译为class文件,泛型就不存在了,称为泛型擦除 2、泛型不支持基本数据类型,只支持引用类型(存基本类型呢?后面学包装类) 包装类:将基本数据类型处理,转化为引用类型
三、常量
常量: 使用了static final修饰的成员变量就被称为常量, 通常用于记录系统的配置信息。 命名规范: 单词全部大写,多个之间使用_连接 优点: 1. 代码可读性更好,可维护性也更好。 2. 程序编译后,出现常量的地方全部会被替换成其记住的字面量,这样可以保证使用常量和直接用字面量的性能是一样的。
public class Demo {
public static void main(String[] args) {
System.out.println("学校名称:" + Constant.SCHOOL_NAME);
//需求2: 保存学生的性别, 为提高性能,经常使用0和1表示性别
System.out.println("性别:" + (Constant.SEX_WOMAN == 0 ? "女":"男"));
}
}
//需求1: 定义一个常量,记录学校的名称
class Constant{
//记录所有的常量信息
public static final String SCHOOL_NAME = "清华大学";
public static final int SEX_WOMAN = 0;
public static final int SEX_MAN = 1;
}
四、枚举
枚举 Java提供的一种用于简洁表示一些固定值的类 枚举类格式 修饰符 enum 校举类名{ 校举顶1,校举项2..; 其他成员.. } 枚举的特点 1、枚举类的第一行只能罗列一些名称,且默认都是常量,每个常量记住的就是枚举类的一个对象 2、枚举类的构造器都是私有的(自己提供也只能是私有的),因此枚举类对外不能创建对象 3、枚举都是最终类,不能被继承 4、枚举类中,从第二行开始,可以定义类的其他成员 5、编译器为枚举类新增了几个方法,并且所有校举类都是java.lang.Enum的子类,所以可以使用父类的方法
public class Demo {
public static void main(String[] args) {
// 需求: 创建Student对象,使用set赋值为:张三,MALE
Student student = new Student();
student.setName("张三");
//3.使用枚举赋值
student.setSex(Sex.MALE);
//4.使用枚举对象调用方法
Sex.MALE.cry();
System.out.println(student);
}
}
//1.定义枚举类
enum Sex{
MALE,FEMALE;//(常量,本质上是字符串类型)
//可以定义成员方法
public void cry(){
System.out.println("cry...");
}
}
class Student {
private String name;
//2.使用枚举定义属性
private Sex sex;//使用枚举表示性别
public void setSex(Sex sex) {
this.sex = sex;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
五、常用API
5.1 Object类
Object类是Java中所有类的祖宗类,类中的方法可以被所有Java类所使用 String toString(): 返回对象的字符串表示形式, 默认打印对象的内存地址, 一般用于子类重写返回对象的指定格式(我们一般让子类重写,以便返回子类对象的内容) boolean equals(Object o): 判断两个对象是否相等, 默认比较两个对象的内存地址, 一般用于子类重写自定义比较规则(我们一般让子类重写,用于比较对象的内容是否相同)
public class Demo {
public static void main(String[] args) {
Student s1 = new Student("小明", 18);
System.out.println(s1);
System.out.println("--------------------------");
Student s2 = new Student("小明", 18);
System.out.println(s2);
System.out.println(s1.equals(s2));//默认比较两个对象的内存地址
}
}
class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
}
5.1.2 对象克隆
Object类常用方法 protected object clone() 对象克隆 浅克隆: 将基本类型数值、引用类型的地址都拷贝一份 (拷贝出的新对象,与原对象中的数据一模一样(引用类型拷贝的只是地址)) 1、子类必须实现cloneable接口(标记接口),否则运行报CloneNotSupportedException 2、子类重写clone方法, 在里面直接调用父类提供的clone方法
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
//1. 创建学生对象
Student student = new Student(1, "张三", "admin", new double[]{20, 30, 50});
System.out.println(student);
System.out.println(student.id);
System.out.println(student.username);
System.out.println(student.password);
System.out.println(student.scores);
//2. 克隆一个学生对象
System.out.println("=====================克隆对象=============================");
Object cloneStudent = student.clone();
Student student1 = (Student) cloneStudent;
System.out.println(student1);
System.out.println(student1.id);
System.out.println(student1.username);
System.out.println(student1.password);
System.out.println(student1.scores);
//修改原始数据
student.scores[0] = 99;
//打印克隆后对象
System.out.println(student1.scores[0]);
}
}
//CloneNotSupportedException : 不支持克隆
/**
- 1、java类实现空接口Cloneable
- 2、重写父类clone方法
*/
class Student implements Cloneable{
int id;
String username;
String password;
double[] scores;
public Student(int id, String username, String password, double[] scores) {
this.id = id;
this.username = username;
this.password = password;
this.scores = scores;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
深克隆: 将基本类型数值、字符串的地址都拷贝一份; 其他引用类型的数据,会创建新对象完成拷贝(拷贝出新的地址) (对象中基本类型的数据直接拷贝。 对象中的字符串数据拷贝的还是地址。 对象中还包含的其他对象,不会拷贝地址,会创建新对象) 1、子类必须实现cloneable接口(标记接口),否则运行报CloneNotSupportedException 2、子类重写clone方法, 在里面直接调用父类提供的clone方法 3、在clone方法中, 将克隆得到的对象中的引用类型重新手动clone一下再复制到对象中
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
//1. 创建学生对象
Student student = new Student(1, "张三", "admin", new double[]{20, 30, 50});
System.out.println(student);
System.out.println(student.id);
System.out.println(student.username);
System.out.println(student.password);
System.out.println(student.scores);
//2. 克隆一个学生对象
System.out.println("=====================克隆对象=============================");
Object cloneStudent = student.clone();
Student student1 = (Student) cloneStudent;
System.out.println(student1);
System.out.println(student1.id);
System.out.println(student1.username);
System.out.println(student1.password);
System.out.println(student1.scores);
//修改原始数据
student.scores[0] = 99;
//打印克隆后对象
System.out.println(student1.scores[0]);
}
}
//CloneNotSupportedException : 不支持克隆
/**
- 1、java类实现空接口Cloneable
- 2、重写父类clone方法
*/
class Student implements Cloneable{
int id;
String username;
String password;
double[] scores;
public Student(int id, String username, String password, double[] scores) {
this.id = id;
this.username = username;
this.password = password;
this.scores = scores;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//深克隆
//1、调用父类的克隆方法,创建一个浅克隆对象
Student student = (Student) super.clone();
//2、对浅克隆对象中存在地址问题的引用类型数据,重新赋值
student.scores = student.scores.clone();
//3、返回
return student;
}
}
5.2 objects
Objects java提供的一个工具类 常见方法 public static boolean equals(Object a, Object b); 先做非空判断,再比较两个参数是否相等 (更严谨安全,避免空指针) public static boolean isNull(Object o); 判断对象是否为null,是返回true public static boolean nonNull(Object o); 判断对象是否不为ull,是返回true
public class Demo {
public static void main(String[] args) {
//1. 定义两个字符串对象
String s1 = null;//NullPointerException 空指针异常:访问空对象的属性或方法都会抛出空指针异常
String s2 = "itheima";
//2. 判断两个对象是否相等
//boolean equals = s1.equals(s2);
boolean equals = Objects.equals(s1, s2);
System.out.println(equals);//false
//3. 判断对象是否为空
boolean aNull = Objects.isNull(s1);
System.out.println(aNull);//true
//4. 判断对象是否不为空
boolean nonNull = Objects.nonNull(s2);
System.out.println(nonNull);//true
}
}
5.3 包装类(8个)
包装类 概述 为了更好的支持面向对象, java为每一种基本类型都提供了一种对应的包装类型 (为了万物皆对象,并且泛型和集合都不支持基本类型,支持包装类) 具体 byte-->Byte short-->Short long-->Long float-->Float double-->Double boolean-->Boolean int-->Integer char-->Character 下面以Integer的角度学习, 其它都是类似的 创建对象(这两个方法比较书面化) pbulic Integer(int value/String value) 构造方法(过时),接收int或string封装成Integer对象 (例如:Integer in = new Integer(5) ) pbulic static Integer valueOf(int i/String value) 替代构造方法,接收int或string封装成Integer对象 (例如:Integer in = Integer.valueOf(5) ) 拆箱和装箱(基本类型和包装类的相互转换) 自动装箱: Java支持将基本数据类型直接赋值给对应的包装类,底层使用的是valueof()方法 (基本数据类型可以自动转换为包装类型) 自动拆箱: java支持将包装类直接赋值给对应基本类型,底层调intValue()方法 (包装类型可以自动转换为基本数据类型) static String toString(int i) 将包装类对象转为String类型 static Integer valueOf(String str) 将字符串转化为包装类对象 static int parseInt(String s) 将字符串数值转为int数值
public class Demo {
public static void main(String[] args) {
//1. 创建对象
// Integer in1 = new Integer(5);//已过时
Integer in2 = Integer.valueOf(5);
System.out.println(in2);
//2. 拆箱和装箱
Integer in3 = 100;//自动装箱:将基本数据类型自动转化为包装类型
int tem1 = in3;//自动拆箱:将包装类型自动转化为基本数据类型
//3.跟字符串的互相转换
//将Integer对象封装的数值转为String类型
String s = in3.toString();
System.out.println(s);
//将字符串转化为包装类对象
String str = "1000";
Integer in4 = Integer.valueOf(str);
System.out.println(in4);
//将字符串数值转为基本类型
int tem2 = Integer.parseInt(str);
System.out.println(tem2);
}
}
//面试题: 输出下面代码的执行原理
//Integer x = 100;//装箱
//x += 200;//先拆箱,再装箱
//System.out.println(x);//300