目录
一 继承:
class 子类 extends 父类 {
}
- 使用 extends 指定父类.
- Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
- 子类会继承父类的所有 public 的字段和方法.
- 对于父类的 private 的字段和方法, 子类中是无法访问的.
- 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用
1.1 extends关键字
关键字extends用于表示类与类的关系,表明构造的新类派生于一个已经存在的类,其中新类称为: 子类/派生类/孩子类,已存在的类称为: 超类/基类/父类。
1.2 super关键字
super构造器:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
public Cat(String name) {
// 使用 super 调用父类的构造方法.
super(name);
}
}
子类继承父类时,必须先实现父类的构造方法。super()表示父类的构造方法,必须放在子类构造函数的第一行。
super引用:
class Employee {
public double getSalary() {
return 300d;
}
}
class Manager extends Employee {
public double getbonus() {
return super.getSalary(); //调用父类的getSalary()
//return getSalary(); //等价于super.getSalary()
}
}
super与this的关系:
1.3 protected关键字:
- 对于包外的其他类来说, protected 修饰的字段和方法是不能访问的
- 对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的
与其他访问修饰符的区别:
1.4 方法重写
当父类方法不适用与子类方法时,可以重写父类的方法
示例:
class Employee {
public double getSalary() {
return 300d;
}
}
class Manager extends Employee {
public double getSalary() {
//此处不能写成getSalary()+1000;不然会造成死循环
return super.getSalary()+1000;
}
}
- 普通方法可以重写, static 修饰的静态方法不能重写.
- 重写中子类的方法的访问权限不能低于父类的方法访问权限.
- 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)
重载与重写区别:
class B {
public B() {
func();
}
public void func() {
System.out.println("我是B");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("我是D:" + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D(); //执行结果: 我是D:0
}
}
- 构造 D 对象的同时, 会调用 B 的构造方法.
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为0
可协变返回类型示例:
class Employee {
public Employee getBuddy() {
return new Employee();
}
}
class Manager extends Employee {
public Manager getBuddy() {
return new Manager();
}
}
上述例子中, 同名方法getBuddy()构造重写,子类的方法返回类型是父类方法返回类型的子类
1.5 多态
class Employee {
}
class Manager extends Employee {
public static void main(String[] args) {
Employee e = new Manager(); //多态,父类可以引用子类对象
}
}
此时的实例对象是Manager类型,但e是Employee类型的引用,因此只能使用Employee类所拥有的方法。
动态绑定示例:
class Employee {
public double getSalary() {
return 300d;
}
}
class Manager extends Employee {
public double getSalary() {
//此处不能写成getSalary()+1000;不然会造成死循环
return super.getSalary() + 1000;
}
public static void main(String[] args) {
Employee e = new Manager();
System.out.println(e.getSalary()); //输出: 1300.0
}
}
- 一个对象变量(如: 变量e) 可以指示多种实际类型的现象称为多态(polymorphism)。
- 在运行时能够自动选择适当的方法,称为动态绑定。
静态绑定:
如果是private方法、static方法、final方法、构造器,那么编译器可以准确的知道调用那个方法,称为静态绑定。
向下转型示例:
class Employee {
public double getSalary() {
return 300d;
}
}
class Manager extends Employee {
public double getSalary() {
return super.getSalary() + 1000;
}
public double getbond() {
return 1000;
}
public static void main(String[] args) {
Employee e = new Manager();
//e.getbond(); //不能调用getbond()方法
Manager m = (Manager) e; //向下转型
m.getbond();
}
}
- 编译器检查有哪些方法存在, 看的是 Employee 这个类型
- 执行时究竟执行父类的方法还是子类的方法, 看的是 Manager 这个类型(动态绑定)
强制转化:
向下转化时,可以先判断是否能够成功转化, 可以使用 instanceof 操作符。
示例:
class Employee {
}
class Manager extends Employee {
public static void main(String[] args) {
Employee e = new Manager();
Manager m;
if (e instanceof Manager)
m = (Manager) e;
}
}
1.6 fine关键字
- 修饰基本数据类型的变量: 表示常量 (不能修改)。
- 修饰引用变量: 表示引用的对象不能改变
- 修饰类: 表示该类不能被继承。
- 修饰方法: 表示方法不能被重写
二 抽象类
语法规则 :
abstract class Shape {
abstract public void draw();
}
- abstract 关键字修饰的方法是一个抽象方法. 抽象方法没有方法体, 不能执行具体代码。
- 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类。
- 抽象类不能直接实例化。
- 抽象方法不能是 private 的
- 抽象类中可以包含其他的非抽象方法和字段
三 Object类
Object类是Java中所有类的始祖,每个类都默认继承了Object类
示例:
class Manager {
public static void main(String[] args) {
Object o = new Manager();
}
}
3.1 equals方法:
Object原码:
public boolean equals(Object obj) {
return (this == obj);
}
自定义类想要比较内容相等,需要重写equals方法。
示例:
class Employee {
double salary;
String name;
}
class Manager extends Employee {
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (this == obj)
return true;
if (this.getClass() != obj.getClass()) //子类的语义(需要比较的关键字段)不相同时
return false;
// if(!(obj instanceof Employee)) //子类语义相同时,根据实际情况,与上一条if二选一
// return false;
return this.salary == ((Manager) obj).salary //基本数据类型直接用==判断
&& this.name.equals(((Employee) obj).name); //引用类型用equals()判断
}
}
Objects.equals静态方法:
public static boolean equals(Object a, Object b) { //Objects.equals方法原码
return (a == b) || (a != null && a.equals(b));
}
如果两个对象a,b都可能为空,则使用Objects.equals静态方法。
3.2 hashCode方法:
自定义类若没有重写hashCode方法,则默认使用Object类的hashCode方法,从对象的存储地址得散列码。
String类的hashCode方法:
public int hashCode() {
int h = hash; //hash Default to 0
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
3.3 toString方法:
Object原码:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
示例:
class Employee {
public static void main(String[] args) {
Employee e = new Employee();
String str = e+""; //默认e.toString();
System.out.println(e); //默认e.toString();
}
}
数组类的toString()方法:
一维数组: Arrays.toString方法
多维数组: Arrays.deepToString方法
编辑器自动生成toString方法:
1) 鼠标右键
2) 点击Generate
3) 选择toString()
4) 选择ok
四 枚举类
示例:
public enum TestEnum {
RED,BLACK,GREEN;
}
备注: 默认继承Enum类。
4.1 构造器:
public enum TestEnum {
RED("red", 1), BLACK("black", 2), WHITE("white", 3), GREEN("green", 4);
private String name;
private int key;
private TestEnum(String name, int key) {
this.name = name;
this.key = key;
}
public static TestEnum getEnumKey(int key) {
for (TestEnum t : TestEnum.values()) {
if (t.key == key) {
return t;
}
}
return null;
}
public static void main(String[] args) {
System.out.println(getEnumKey(2));
}
}
备注: 枚举的构造方法默认是私有的
在switch中使用:
public enum TestEnum {
RED, BLACK, GREEN, WHITE;
public static void main(String[] args) {
TestEnum testEnum2 = TestEnum.BLACK;
switch (testEnum2) {
case RED:
System.out.println("red");
break;
case BLACK:
System.out.println("black");
break;
case WHITE:
System.out.println("WHITE");
break;
case GREEN:
System.out.println("black");
break;
default:
break;
}
}
}
4.2 常用方法:
方法名称
|
描述
|
values()
|
以数组形式返回枚举类型的所有成员
|
compareTo()
|
比较两个枚举成员在定义时的顺序
|
ordinal()
|
获取枚举成员的索引位置
|
valueOf()
|
将普通字符串转换为枚举实例
|
toString() | 返回枚举常量名 |
4.3 单例模式:
1)普通单例模式:
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) { //进入区域后,再检查一次,如果仍是null,才创建实例
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
静态内部类实现单例模式:
class Singleton {
//私有化构造器
private Singleton() {
}
//对外提供公共的访问方法
public static Singleton getInstance() {
return UserSingletonHolder.INSTANCE;
}
//写一个静态内部类,里面实例化外部类
private static class UserSingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
public class Main {
public static void main(String[] args) {
Singleton u1 = Singleton.getInstance();
Singleton u2 = Singleton.getInstance();
System.out.println("两个实例是否相同:" + (u1 == u2));
}
}
枚举实现单利模式:
public enum TestEnum {
INSTANCE;
public TestEnum getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
TestEnum singleton1 = TestEnum.INSTANCE;
TestEnum singleton2 = TestEnum.INSTANCE;
System.out.println("两个实例是否相同:" + (singleton1 == singleton2));
}
}
枚举实现单例模式是安全的:
- 枚举可以避免反射和序列化问题
五 包装器类
5.1 基本类型的包装器类:
包装器 | 基本类型 |
Integer | int |
Long | long |
Float | float |
Double | double |
Short | short |
Byte | byte |
Character | char |
Boolean | boolean |
注意事项:
- 前6个类派生于公共的超类Number
- 包装器类是不可变的,一旦构造了包装器,就不允许更改包装在其中的值
- 包装器类被final修饰, 不能派生他们的子类
5.2 自动装箱
示例:
public static void main(String[] args) {
Integer a = 3; //等价于a=Integer.valueOf(3)
}
int类型的3赋值给Integer对象a,自动调用Integer.valueOf方法,这个过程叫自动装箱
5.3 自动拆箱
示例:
public static void main(String[] args) {
Integer a = new Integer(3);
int b = a; //等价于b=a.intValue();
}
Integer对象变量的值赋值给int类型的b变量,自动调用intValue()方法,这个过程叫自动拆箱
六 参数可变方法
System.out.printf方法原码:
public PrintStream printf(String format, Object ... args) {
return format(format, args);
}
上述方法的参数列表中, ...是代码一部分,表明这个方法的args参数可以接收任意数量的对象。
自定义参数可变方法:
class Employee {
public static void main(String[] args) {
System.out.println(add()); //输出: 0
System.out.println(add(1)); //输出: 1
System.out.println(add(1, 2)); //输出: 3
System.out.println(add(1, 2, 3));//输出: 6
System.out.println(add(new int[]{1, 2, 3, 4})); //输出: 10
}
public static int add(int... a) {
int sum = 0;
for (int x : a)
sum += x;
return sum;
}
}
七 反射
定义:
Java的反射(reflflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
7.1 反射相关的类
类名
|
用途
|
Class
类
|
代表类的实体,在运行的
Java
应用程序中表示类和接口
|
Field
类
|
代表类的成员变量
/
类的属性
|
Method
类
|
代表类的方法
|
Constructor
类
|
代表类的构造方法
|
7.2 Class类:
Java文件被编译后,生成 .class 文件,由 JVM 解读 .class 文件,解析为一个对象, 这个对象的类型是java.lang.Class . 程序在运行时,每个 java 文件最终变成 Class 类对象的一个 实例。通过Java 的反射机制拿到到这个实例,就可以去 获得甚至去添加改变这个类的属性和动作 ,使得这个类成为一个动态的类。
相关方法:
获得Class对象的三种方式:
Class c3 = null;
try {
c3 = Class.forName("Student"); //注意这里是类的全路径,如果有包需要加包的路径
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class c2 = Student.class;//最为安全可靠,任何一个类都有一个隐含的静态成员变量 class
Student s1 = new Student();
Class c1 = s1.getClass();
7.3 Field类
相关方法:
7.4 Method类
相关方法:
7.5 Constructor类
相关方法:
7.6 反射示例:
class Student {
//私有属性name
private String name = "毛毛";
//公有属性age
public int age = 18;
//不带参数的构造方法
public Student() {
System.out.println("Student()");
}
private Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Student(String,name)");
}
private void function(String str) {
System.out.println(str);
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
class lan {
public static void main(String[] args) {
reflectPrivateConstructor();
reflectPrivateField();
reflectPrivateMethod();
}
// 反射私有的构造方法
public static void reflectPrivateConstructor() {
try {
//获得Class对象
Class<?> classStudent = Class.forName("Cctalk_Demo.Student");
//通过Class对象.获得构造对象
Constructor<?> constructor = classStudent.getDeclaredConstructor(String.class, int.class);
//设置为true后可修改访问权限
constructor.setAccessible(true);
//通过构造器对象的方法创建实例
Object object = constructor.newInstance("毛哥", 16);
Student student = (Student) object;
System.out.println(student);
} catch (Exception e) {
e.printStackTrace();
}
}
// 反射私有属性
public static void reflectPrivateField() {
try {
Class<?> classStudent = Class.forName("Cctalk_Demo.Student");
Field field = classStudent.getDeclaredField("name");
field.setAccessible(true);
//可以修改该属性的值
Student student = (Student) classStudent.newInstance();
field.set(student, "叫我毛毛!");
System.out.println((String) field.get(student));
} catch (Exception e) {
e.printStackTrace();
}
}
// 反射私有方法
public static void reflectPrivateMethod() {
try {
Class<?> classStudent = Class.forName("Cctalk_Demo.Student");
Method method=classStudent.getDeclaredMethod("function",String.class);
method.setAccessible(true);
Student student=(Student) classStudent.newInstance();
method.invoke(student,"我叫吉吉!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
备注:
- 使用反射会有效率问题。会导致程序效率降低。
- 反射技术绕过了源代码的技术,带来维护问题。
- 反射代码比相应的直接代码更复杂
- 枚举类不可反射