[TOC]
Java面向对象初级
一、方法的重载
注意事项和使用细节
- 方法名必须相同;
- 形参列表必须不同(形参类型、个数或顺序至少有一个不同,参数名无要求);
- 返回类型无要求。
可变参数:
public int sum(int ... nums)
表示可接收多个参数。可变参数本质为数组;
参数为0个或多个;
实参可以为数组;
可以与普通参数放在同一形参列表,但必须放在最后;
一个形参列表中只能有一个可变参数。
二、作用域
- 全局变量有默认值,可以不用赋值,局部变量无默认值,需要赋值。
- 属性可以与局部变重名,使用时遵循就近一致原则。
三、构造器
构造器是初始化对象,并非创建对象。
一个类可以定义多个不同的构造器,即构造器重载。
对象创建流程:
对于以下代码:
class Person{ //默认初始化 int age = 90; //显式初始化 String name; // 构造器 Person(String n,int a){ //构造器初始化 name = n; age = a; } } Preson p1 = new Person("小倩",20);
加载Person类的信息(
Person.class
),只加载一次;在堆中分配空间(地址);
完成对象初始化:
默认初始化(age=0,name=null)
显式初始化(age=90,name=null)
构造器初始化(age=20,name=小倩)
把对象在堆中的地址返回给p1(引用)
四、封装
封装的作用
- 提高代码的安全性;
- 提高代码的复用性;
- “高内聚”:封装细节,便于修改内部代码,提高可维护性;
- “低耦合”:简化外部调用,便于调用者使用,便于扩展和写作。
封装主要代码
get()
和set()
函数,如下:
package demo;
import java.util.Scanner;
public class test1 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
person p = new person();
p.setName("小明");
p.setAge(600);
System.out.println(p.getName());
System.out.println(p.getAge());
}
}
class person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
//数据校验
if(name.length() < 2 || name.length() > 6)
{
System.out.println("名字长度不符合要求,默认张三!");
this.name = "张三";
}
else {
this.name = name;
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
//数据校验
if(age > 120)
{
System.out.println("年龄不符合要求,默认18!");
this.age = 18;
}
else {
this.age = age;
}
}
}
封装与构造器
- 构造器可破解封装中的数据校验,所以可进行下列操作:
package demo;
import java.util.Scanner;
public class test1 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
person p = new person();
p.setName("小明");
p.setAge(600);
System.out.println(p.getName());
System.out.println(p.getAge());
System.out.println("**********************");
person p1 = new person("小明",400);
System.out.println(p1.getName());
System.out.println(p1.getAge());
}
}
class person {
private String name;
private int age;
public person() {
}
public person(String name, int age) {
//破解了数据校验
this.name = name;
this.age = age;
//修复(构造器中调用set函数)
setName(name);
setAge(age);
}
public String getName() {
return name;
}
public void setName(String name) {
//数据校验
if(name.length() < 2 || name.length() > 6)
{
System.out.println("名字长度不符合要求,默认张三!");
this.name = "张三";
}
else {
this.name = name;
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
//数据校验
if(age > 120)
{
System.out.println("年龄不符合要求,默认18!");
this.age = 18;
}
else {
this.age = age;
}
}
}
五、继承
创建子类对象时,不论使用子类的那个构造器,默认情况下都会调用父类的无参构造器,若父类没有提供无参构造器,则必须在子类的构造器中用super去指定父类中的构造器(如
super(String name,int age);
)以完成父类的初始化,否则编译不通过。若希望指定去调用父类的某个构造器时,需要显式的调用一下:
super(参数列表)
。super()
在使用时,必须放在构造器第一行(this()
也是),故两方法不能共存于同一个构造器中。super()
和this()
的区别:super()
访问父类中和子类重名的属性,若不重名,则和this()
功能一致。super()
直接访问父类中的属性(方法),this()
先在本类中找属性(方法),再去父类中找属性(方法)。
方法重载
overload
和方法重载override
的比较:- 重载发生在子类,方法名一样,形参类别、个数或顺序至少有一个不同,返回类型和修饰符无要求;
- 重写发生在父子类,方法名一样,形参类别、个数或顺序都相同,子类重写的方法的返回类型和父类返回类型一致或是其子类,子类的权限不能小于父类。
六、多态
方法的多态
- 方法的重载和重写。
对象的多态
- 一个对象的编译类型(
"="左边
)和运行类型("="右边
)可以不一致。
多态的向上转型
本质:父类的引用指向子类的对象;
语法:父类类型 引用名 = new 子类类型();
特点:编译看左边,运行看右边;
不能调用子类特有的成员。
Animal animal = new Cat(); //向上转型
Object obj = new Cat();
animal.eat(); //根据就近原则先去Animal中找相应的方法
多态的向下转型
语法:子类类型 引用名 = (子类类型) 父类引用;
只能强转父类的引用,不能强制父类的对象(对象创建后不再改变);
当向下转型后,可以调用子类类型中的所有成员;
要求父类的当前引用必须指向的是当前目标类型的对象。
Animal animal = new Cat(); //此时animal是指向Cat类型的对象
Cat cat = (Cat) animal;//编译类型和运行类型都是Cat
Dog dog = (Dog) animal;//编译通过,运行报错,违反要求:要求父类的当前引用必须指向的是当前目标类型的对象。
属性的“重写”
- 属性无“重写”之说,属性只看编译类型,即“=”左边。
A a = new B();
System.out.print(a.age); // 10(编译类型为A,找A的属性)
class A{
int age = 10;
}
class B extend A{
int age = 20;
}
instenceof
比较操作符,用于判断对象的运行类型是否为xx类型或xx类型的子类型。
B b = new B();//运行类型为B
System.out.print(b instenceof B); //true
System.out.print(b instenceof A); //true
A a = new B();//运行类型为B
System.out.print(a instenceof B); //true
System.out.print(a instenceof A); //true
Object obj = new Object();//运行类型为Object
System.out.print(obj instenceof A); //false
class A{}
class B extend A{}
动态绑定机制
当调用对象方法时,该方法会与该对象的内存地址/运行类型绑定;
(即若调用的方法子类没有而父类有,则先找到父类方法执行,但此时在父类方法中又需要执行子类和父类都有的方法时,需要看该对象的运行类型是哪个类,就执行那个类中的方法。重名的方法和对象的运行类型动态绑定)
当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
多态数组
- 调用共有方法
Person[] persons = new Person[3];//创建一个Person对象数组
persons[0] = new Person("Jack",20);
persons[1] = new Teacher("Jack",20);
persons[2] = new Student("Jack",20);
//循环遍历多态数组,调用say方法
for(int i = 0;i < persons.length;i++)
{
//此时需要注意动态绑定机制,编译类型都为Person,运行类型根据实际情况看
persons.say();
}
- 调用特有方法(使用类型判断 + 向下转型)
Person[] persons = new Person[3];//创建一个Person对象数组
persons[0] = new Person("Jack",20);
persons[1] = new Teacher("Jack",20);
persons[2] = new Student("Jack",20);
//循环遍历多态数组,调用say方法
for(int i = 0;i < persons.length;i++)
{
if(persons[i] instanceof Student){
Student student = (Student)persons[i];//向下转型
student.study();
//((Student)persons[i]).study();
}
}
多态参数
- 方法定义的形参参数类型为父类类型,实参类型可以为子类类型]
Student student = new Syudent();
getName(student); //实参为student(子类)
void getName(Person p){}//形参为Person(父类)
七、Object类详解
equals
方法
==
和equals
的对比==
既可以判断基本类型,又可以判断引用类型;==
若判断基本类型(如比较数值大小时),判断的是值是否相等;==
若判断引用类型(如比较两对象时),判断的是地址是否相等,即判断是否为同一个对象;equals
是Object类中的方法,只能判断引用类型;equals
默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。
举例:Person类中equals方法的重写
Person类中的equals重写,判断两对象地址或属性是否一样
package demo;
public class test1 {
public static void main(String[] args) {
Person p1 = new Person("a",10);
//Person p2 = p1;
Person p2 = new Person("a",10);
System.out.println(p1.equals(p2));
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//Person类中的equals重写,判断两对象地址或属性是否一样
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if(obj instanceof Person)
{
Person p = (Person) obj;
return p.name.equals(this.name) && p.age == this.age;
}
return false;
}
}
hashCode
hashCode
的几点小结- 提高具有哈希结构的容器的效率;
- 两个引用,如果指向的时同一个对象,则哈希值肯定是一样的,否则不一样;
- 哈希值主要根据地址号来来的,不能完全将哈希值等价于地址;
hashCode
也会重写。
toString
方法
- 默认返回:全类名+@+哈希值的16进制,子类往往重写
toString
方法,用于返回对象的属性信息;
package demo;
public class test1 {
public static void main(String[] args) {
Person p1 = new Person("a",10);
System.out.println(p1.toString()+" "+p1.hashCode());
//输出:demo.Person@49e4cb85 1239731077
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
- 重写
toString
方法,打印对象或者拼接对象时,都会自动调用该对象的toString
形式;
package demo;
public class test1 {
public static void main(String[] args) {
Person p1 = new Person("a",10);
System.out.println(p1.toString()+" "+p1.hashCode());
//输出:Person{name='a', age=10} 1945604815
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//重写toString方法,输出对象的属性
@Override
public String toString() { //重写后,一般输出对象的属性值,当然也可以自己定制
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 当直接输出一个对象时,
toString方
法会被默认调用。(如:System.out.println(Person);
就会默认调用Person.toString()
)
Person p1 = new Person();
System.out.println(p1);
//等价于System.out.println(p1.toString());
Java面向对象高级
一、 类变量和类方法
类变量(静态变量)
定义语法:
访问修饰符 static 数据类型 变量名
(推荐)或static 访问修饰符 数据类型 变量名
;访问方法:
类名.类变量名
(推荐)或者对象名.类变量名
;类变量是被类声明的所有对象共享的,实例对象是对象独有的;
类变量在类加载的时候生成,即对象未创建是也可使用;
jdk8以前类变量存放在方法区,jdk8以后类变量放在堆里;
类方法(静态方法)
定义语法:
访问修饰符 static 数据返回类型 方法名(){}
(推荐)或static 访问修饰符 数据返回类型 方法名(){}
;调用方法:
类名.类方法名
(推荐)或者对象名.类方法名
;使用场景:将一些通用的方法设计成静态类,可以不创建对象就调用相关方法,如
Math.sqrt()
;类方法和普通方法都是随着类的加载而加载,将结构信息储存在方法区,但类方法中无
this()
,普通方法中隐含this()
;类方法不允许使用和对象有关的关键字,如
super()
和this()
,普通方法可以;静态方法只能访问静态成员,而普通成员方法,既可以访问普通变量(方法),也可以访问静态变量(方法)。
二、main
方法语法
解释
main
方法的形式:public static void main(String[] args){}
main
方法是虚拟机调用;- Java虚拟机需要调用类的
main()
方法,所以该方法的访问权限必须是public
; - Java虚拟机在执行
main()
方法时不必创建对象,所以方法必须是static
; - 该方法接收String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数,即
java 执行的程序 参数1 参数2 参数3 ...
。
有以下代码:
public class test1 {
public static void main(String[] args) {
for (int i = 0;i < args.length;i++)
{
System.out.println("第" + (i+1) + "个参数为:" + args[i]);
}
}
}
在命令行编译执行观察参数:
三、代码块
- 代码块相当于另一种形式的构造器(对构造器的补充机制),可做初始化的操作;
- 代码块先于构造器加载;
- 静态代码块只在类加载时执行一次,普通代码块每创建一次对象就执行一次;
- 当只使用某个类的静态成员(未创建该类对象)时,该类的普通代码块不会被执行,但只要加载类时,静态代码块都会执行一次;
- 类什么时候加载:
- 创建对象实例时(new)
- 创建子类对象实例,父类会被加载,即先加载父类(及其代码块或构造器),再加载子类(及其代码块或构造器)
- 使用类的静态成员时(静态属性、静态成员)
package demo;
public class test1 {
public static void main(String[] args) {
A a = new A(12);
System.out.println("=======================");
A b = new A("Hello");
}
}
class A
{
int a;
String b;
//静态代码块
static
{
System.out.println("静态代码块只执行一次!");
}
//普通代码块
{
System.out.println("输出公共代码。");
System.out.println("代码块无论放在哪个位置,都优先加载。");
System.out.println("普通代码块每创建一次对象就执行一次!");
};
//构造器
public A(int a) {
this.a = a;
System.out.println(a);
// System.out.println("输出公共代码。");
}
public A(String b) {
this.b = b;
System.out.println(b);
// System.out.println("输出公共代码。");
}
}
/*
输出:
静态代码块只执行一次!
输出公共代码。
代码块无论放在哪个位置,都优先加载。
普通代码块每创建一次对象就执行一次!
12
=======================
输出公共代码。
代码块无论放在哪个位置,都优先加载。
普通代码块每创建一次对象就执行一次!
Hello
*/
四、类中调用的顺序(重点补充)
- 在继承的类中,构造器的前面其实隐含了
super()
和普通代码块,故调用顺序为父类中的代码块->父类中的构造器->子类中的代码块->子类中的构造器
,示例如下:
package demo;
public class test1 {
public static void main(String[] args) {
B b = new B();
}
}
class A //父类Object
{
//super()
//普通代码块
{
System.out.println("A的代码块被调用。");
}
//构造器
public A()
{
System.out.println("A的构造器被调用。");
}
}
class B extends A
{
//super() 即父类A
//普通代码块
{
System.out.println("B的普通代码块被调用。");
}
//构造器
public B()
{
System.out.println("B的构造器被调用。");
}
}
/*
输出:
A的代码块被调用。
A的构造器被调用。
B的普通代码块被调用。
B的构造器被调用。
*/
创建一个对象时,在一个类中调用的顺序是:
- 调用静态代码块和静态属性初始化(两者优先级相同,若有多个,则按照定义顺序调用)
- 普通代码块和普通属性的初始化(两者优先级相同,若有多个,则按照定义顺序调用)
- 调用构造器方法
创建一个子类对象时(继承关系),调用顺序是:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性(优先级一样,按定义顺序执行)
- 子类的构造方法
补充:静态代码块只能调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员(包括静态成员)。
五、单例设计模式
设计模式:是在大量的实践中总结和理论化后优选的代码结构、编程风格以及解决问题的思考方式。
单例设计模式:采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例模式有两种方式:饿汉式和懒汉式(都要保证对象的唯一性)
饿汉式(线程安全)
- 饿汉式在类加载时创建对象实例;
- 饿汉式是不论是否要用某个对象,先将该对象创建好,故可能会出现多余对象,造成资源的浪费;
- 饿汉式线程安全。
懒汉式(线程不安全)
- 懒汉式在使用对象时才会创建对象实例;
- 懒汉式是先定义唯一的对象,然后在调用对象时再创建对象,不会出现多余对象浪费资源的情况;
- 懒汉式线程不安全。
演示代码SingleTon.java
:
package CodeBase.SingleTon;
//饿汉式
/*
步骤:
1.将构造器私有化
2.在类的内部直接创建对象(该对象是static)
3.提供一个公共的static方法,返回创建好的对象
*/
class SingleTon_1
{
String name;
//先创建一个私有的静态对象
private static SingleTon_1 singleTon_1 = new SingleTon_1("饿汉式");
//构造器私有
private SingleTon_1(String name) {
this.name = name;
}
//公共静态方法返回创建好的对象
public static SingleTon_1 getInstance()
{
return singleTon_1;
}
}
//懒汉式
/*
步骤:
1.构造器私有化
2.先定义一个static对象,但不创建
3.定义一个public的static方法,可以返回一个对象
*/
class SingleTon_2
{
String name;
//先定义一个对象
private static SingleTon_2 singleTon_2;
//构造器私有
private SingleTon_2(String name){
this.name = name;
};
//定义返回对象的方法
public static SingleTon_2 getInstance()
{
if(singleTon_2 == null)
{
singleTon_2 = new SingleTon_2("懒汉式");
}
return singleTon_2;
}
}
public class SingleTon
{
public static void main(String[] args) {
System.out.println("=============正常情况下============");
SingleTon_Test singleTon_test = new SingleTon_Test();
singleTon_test.singleTon1_test();
System.out.println("==========================");
singleTon_test.singleTon2_test();
}
}
//测试对象是否位唯一
class SingleTon_Test {
//测试饿汉式对象
void singleTon1_test()
{
SingleTon_1 test_singleTon_1_1 = SingleTon_1.getInstance();
SingleTon_1 test_singleTon_1_2 = SingleTon_1.getInstance();
System.out.println(test_singleTon_1_1.name);
System.out.println("饿汉式对象一地址:" + test_singleTon_1_1);
System.out.println("饿汉式对象二地址:" + test_singleTon_1_2);
}
//测试懒汉式对象
void singleTon2_test()
{
SingleTon_2 test_singleTon_2_1 = SingleTon_2.getInstance();
SingleTon_2 test_singleTon_2_2 = SingleTon_2.getInstance();
System.out.println(test_singleTon_2_1.name);
System.out.println("懒汉式对象一地址:" + test_singleTon_2_1);
System.out.println("懒汉式对象二地址:" + test_singleTon_2_2);
}
}
/*
输出:
饿汉式
饿汉式对象一地址:CodeBase.SingleTon.SingleTon_1@22f71333
饿汉式对象二地址:CodeBase.SingleTon.SingleTon_1@22f71333
==========================
懒汉式
懒汉式对象一地址:CodeBase.SingleTon.SingleTon_2@6aaa5eb0
懒汉式对象二地址:CodeBase.SingleTon.SingleTon_2@6aaa5eb0
*/
###多线程情况下的饿汉式和懒汉式
未加线程锁演示代码Thread_SingleTon.java
:
package CodeBase.SingleTon;
public class Thread_SingleTon {
public static void main(String[] args) {
System.out.println("=============多线程情况下============");
Thread_SingleTon_Test.Thread_Test_SingleTon_1();
Thread_SingleTon_Test.Thread_Test_SingleTon_2();
}
}
//多线程饿汉式
class Thread_SingleTon_1 implements Runnable
{
@Override
public void run() {
//让线程停一会方便观察
try {
Thread.sleep(20);
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 饿汉式:"+ SingleTon_1.getInstance());
}
}
//多线程懒汉式
class Thread_SingleTon_2 implements Runnable
{
@Override
public void run() {
try {
Thread.sleep(20);
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 懒汉式:" + SingleTon_2.getInstance());
}
}
class Thread_SingleTon_Test
{
//为饿汉式创建2个线程
public static void Thread_Test_SingleTon_1()
{
Thread one = new Thread(new Thread_SingleTon_1());
Thread two = new Thread(new Thread_SingleTon_1());
//开启线程
one.start();;
two.start();
}
//为懒汉式创建2个线程
public static void Thread_Test_SingleTon_2()
{
Thread one = new Thread(new Thread_SingleTon_2());
Thread two = new Thread(new Thread_SingleTon_2());
//开启线程
one.start();;
two.start();
}
}
/*
输出(一种情况):饿汉式为同一个对象,懒汉式为不同的对象
=============多线程情况下============
Thread-1 饿汉式:CodeBase.SingleTon.SingleTon_1@3f58200d
Thread-0 饿汉式:CodeBase.SingleTon.SingleTon_1@3f58200d
Thread-2 懒汉式:CodeBase.SingleTon.SingleTon_2@59355dd6
Thread-3 懒汉式:CodeBase.SingleTon.SingleTon_2@61034766
*/
多线程中懒汉式的改进
- 为每个对象设置一个“互斥锁”,这表明,在每一个时刻只有一个线程持有该互斥锁,而其他线程若要获得该互斥锁,必须等到该线程(持有互斥锁的线程)将其释放。
- 为了使用这个“互斥锁”,在JAVA语言中提供了
synchronized
关键字,这个关键字即可修饰函数,也可以修饰代码,实际上可以将其理解为就是一个锁,当一个线程执行该临界代码的时候,用synchronized
给该线程先上锁,其它线程进不来,当线程代码执行完了的时候有释放该锁,只不过释放锁是隐式的不需要显示的指明,随代码的执行完毕,锁自动的被释放; volatile
关键字可以禁止指令重排。
演示代码:
package CodeBase.SingleTon;
public class Thread_SingleTon {
public static void main(String[] args) {
System.out.println("=============改进懒汉式==============");
Thread_SingleTon_Test.Test_Fix_Thread_SingleTon_2();
}
}
//多线程懒汉式改进
class Fix_SingleTon_2
{
String name;
//先定义一个对象
private volatile static Fix_SingleTon_2 fix_singleTon_2; //第二层锁,volatile关键字禁止指令重排
//构造器私有
private Fix_SingleTon_2(String name){
this.name = name;
};
//定义返回对象的方法
public static Fix_SingleTon_2 getInstance()
{
if(fix_singleTon_2 == null)//第一层检查,检查是否有引用指向对象,高并发情况下会有多个线程同时进入
{
synchronized (Fix_SingleTon_2.class)
{
//双重检查,防止多个线程同时进入第一层检查(因单例模式只允许存在一个对象,故在创建对象之前无引用指向对象,所有线程均可进入第一层检查)
//假设没有第二层检查,那么第一个线程创建完对象释放锁后,后面进入对象也会创建对象,会产生多个对象
if(fix_singleTon_2 == null)
{
fix_singleTon_2 = new Fix_SingleTon_2("多线程懒汉式");
}
}
}
return fix_singleTon_2;
}
}
//多线程改进懒汉式
class Fix_Thread_SingleTon_2 implements Runnable
{
@Override
public void run() {
try {
Thread.sleep(20);
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 改进懒汉式:" + Fix_SingleTon_2.getInstance());
}
}
class Thread_SingleTon_Test
{
//为改进后的懒汉式创建2个线程
public static void Test_Fix_Thread_SingleTon_2()
{
Thread one = new Thread(new Fix_Thread_SingleTon_2());
Thread two = new Thread(new Fix_Thread_SingleTon_2());
//开启线程
one.start();;
two.start();
}
}
//此时对象唯一
六、final
关键字
使用场景
- 不希望类被继承时;
- 不希望父类的某个方法被子类覆盖、重写时;
- 不希望类中某个属性的值被修改时;
- 不希望某个局部变量被修改时。
注意事项及细节讨论
final
修饰的属性又叫常量,一般用XX_XX_XX
命名;final
修饰的属性在定义是赋初值,并且不能再修改,赋值可以在下列位置:- 定义时:如
public final double TAX_RATE=0.08
; - 在构造器中;
- 在代码块中。
- 定义时:如
若
final
修饰的属性时静态的,则初始化的位置只能在定义时和在静态代码块中,不能在构造器中赋值;final
类不能继承,但可以实例化对象;如类不是
final
类,但类里有final
方法,则该方法不能被重写,但类可以被继承;一般来说,若一个类已经是
final
类,则不需要在此类中写final
方法;final
不能修饰构造器;final
往往和static
配合使用,效率更高,底层编译器做了优化:如下代码:
- 直接访问
static final
定义的属性时,编译时就知道了a的值,所以直接访问不会初始化Final
类,即static
代码块不会被加载; - 当
static final
定义的属性需要某个方法来获取时,编译时无法知道b的值,只有运行时才能知道,所以在访问b的值时,需要先初始化类,即static
代码块会被加载。
- 直接访问
package demo;
public class test1 {
public static void main(String[] args) {
System.out.println("直接访问static final定义的属性:"+Final.a);
System.out.println("=======================");
System.out.println("访问static final定义的方法:"+Final.b);
}
}
class Final //父类Object
{
static final int a = 10;
static final int b = b();
static {
System.out.println("Final类被加载。");
}
public static int b()
{
return 20;
}
}
/*
输出:
直接访问static final定义的属性:10
=======================
Final类被加载。
访问static final定义的方法:20
*/
- 包装类(
Integer,Double,Float,Boolean
)等都是final
类,String
也是final
类。
七、抽象类和抽象方法
基本概述
- 形式:
abstract class A{}
和public abstract void B()
; - 当父类方法不确定时,考虑将该方法设计为抽象方法;
- 所谓抽象方法就是没有实现的方法,即没有方法体;
- 当一个类中存在抽象方法时,需要将该类声明为抽象方法;
- 一般来说,抽象类会被继承,由子类实现其抽象方法。
使用细节
- 抽象类不能被实例化;
- 抽象类不一定包含抽象方法。包含抽象方法的类必须为抽象类;
abstract
只能修饰方法和类;- 抽象类可以有任意成员,如非抽象方法、构造器、静态属性等;
- 若一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为抽象类;
- 抽象方法不能用
private,final,static
修饰,因为这些关键字都是和重写相违背的。
八、接口
基本介绍
接口相当于提供了一个规范;
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用时,在实现这些方法。
interface 接口名{
//属性
//方法
}
class 类名 implements 接口{
//自己属性
//自己方法
//必须实现接口的抽象方法
}
- 在jdk7.0以前,接口中所有方法都没有方法体,都是抽象方法;jdk8.0后,接口中可以有静态方法(
static
)和默认方法(default
),即接口中可以有方法的具体实现。
interface A{
//抽象方法可以省略abstract
void a();
//静态方法
public static void b()
{
System.out.println("静态方法。");
}
//默认方法
default static void c()
{
System.out.println("默认方法。");
}
}
使用细节
- 接口不能被实例化;
- 接口中所有方法都是
public
,接口中抽象方法可以不用使用abstract
; - 一个普通类实现接口,必须将接口中所有方法实现;
- 抽象类实现接口时,可以不用实现接口抽象方法;
- 一个类可以同时实现多个接口,逗号间隔两个接口(
class A implements IB,IC
); - 接口中的属性只能是
final
的,而且是public final static
修饰的,如int a = 1;
,实际上是public final static int a = 1;
即必须初始化; - 接口中的属性访问形式:
接口名.属性
(静态属性访问); - 一个接口不能继承其他的类,但能继承其他的接口:
interface A extend B,C{}
; - 接口的修饰符只能是public和默认。
接口VS.继承
- 继承相当于父子关系,子类生来就有(自动拥有)父类的一些属性和方法;而接口相当于师徒关系,某个类若想拥有某个接口的方法,则必须去学习(实现接口中方法)。
- 如子类需要拓展功能,可以通过实现接口的方式,实现接口可以理解为对java单继承机制的一种补充。
- 解决问题不同
- 继承:主要提高代码的复用性和可拓展性;
- 接口:设计、设计好各种规范(方法),让其他人去实现这些方法;
- 接口比继承更加灵活
- 继承满足
is-a
的关系,而接口只需要满足like-a
的关系;
- 继承满足
- 接口在一定程度上实现代码解耦(即:接口规范性+动态绑定),低耦合,高内聚。
接口多态特性
- 多态参数
public class test1 {
public static void main(String[] args) {
//接口的多态体现
//接口类型的变量 if01 可以指向实现了接口的对象实例
IF if01 = new Car();
if01 = new Bar();
//继承的多态体现
//父类类型的变量 a 可以指向继承A的子类的对象实例
A a = new B();
a = new C();
}
}
interface IF{}
class Car implements IF{}
class Bar implements IF{}
class A{}
class B extends A{}
class C extends A{}
- 多态数组
/*
给Usb数组中,存放 Phone 和 Camera 对象,Phone 类还有一个特有的方法 call(),
遍历 Usb 数组,如果是 Phone 对象,除了调用 Usb 接口定义的方法外,还要调用 Phone 特有的方法 call()。
*/
public class test1 {
public static void main(String[] args) {
//多态数组-> 接口类型数组
Usb usb[] = new Usb[2];
usb[0] = new Phone();
usb[1] = new Camera();
for (int i = 0;i < usb.length;i++)
{
usb[i].work(); //动态绑定
//类型判断 (类型的向下转型)
if (usb[i] instanceof Phone) //判断运行类型是否为 Phone
{
((Phone) usb[i]).call(); //向下转型
}
}
}
}
interface Usb{
void work();
}
class Phone implements Usb{
public void call()
{
System.out.println("手机可以打电话。");
}
public void work()
{
System.out.println("手机工作中。");
}
}
class Camera implements Usb{
public void work()
{
System.out.println("相机工作中。");
}
}
/*
输出:
手机工作中。
手机可以打电话。
相机工作中。
*/
接口多态传递
//多态传递演示
public class test1 {
public static void main(String[] args) {
IG ig = new Teacher();
/* 如果 IG 继承了 IH 接口,而 Teacher 实现了 IG 接口,
那么相当于 Teacher 实现了 IH 接口,这就是多态的传递
*/
IH ih = new Teacher();
}
}
interface IH{
void work();
}
interface IG extends IH {}
class Teacher implements IG{//接口可以继承
//当接口IH中有抽象方法时,IG也继承了该方法,故在 Teacher 类中也需要实现方法
public void work()
{
System.out.println("工作中。。。");
}
}
九、内部类
类的五大成员:属性、方法、构造器、代码块、内部类。
内部类最大特点就是可以访问私有属性。
内部类的分类
定义在外部类局部位置上(如方法内)
- 局部内部类(有类名)
- 匿名内部类(无类名)
定义在外部类的成员位置上
- 成员内部类(无
static
修饰) - 静态内部类(有
static
修饰)
- 成员内部类(无
局部内部类
局部内部类定义在方法中或代码块中,其本质仍是一个类;
可以直接访问外部类的所有成员,包括私有的;
不能添加访问修饰符,因为它的地位是一个局部变量,局部变量不能访问修饰符的,但可以使用
final
修饰,以保证其不被继承,因为局部变量也可以使用final
;作用域:仅在定义它的方法或代码块中;
局部内部类访问局部内部类成员:直接访问;
外部类(内部类之外的第一个类)的方法中访问局部内部类成员:创建对象,再访问;
外部其他类(外部类之外的类)不能访问局部内部类;
若外部类和局部内部类的成员重名时,默认遵循就近一致原则,如果向访问外部类的成员,使用
外部类.this.成员
去访问,如下代码演示。
public class test1 { //外部其他类
public static void main(String[] args) {
Outer outer = new Outer();
outer.m1();
System.out.println("outer对象的hashcode:"+outer);
}
}
class Outer //外部类
{
private int n1 = 100;
public void m1() //方法
{
final class Inner
{
private int n1 = 200;
public void f1()
{
System.out.println("n1="+n1); //就近一致原则,访问局部内部类的 n1=200
//此时 Outer.this 相当于一个对象(调用m1方法的对象,此时为outer)
System.out.println("n1="+Outer.this.n1); //访问外部类的 n1=100
System.out.println("Outer.this对象的hashcode:"+Outer.this);
}
}
Inner inner = new Inner();
inner.f1();
}
}
/*
输出:
n1=200
n1=100
Outer.this对象的hashcode:demo.Outer@7a5d012c
outer对象的hashcode:demo.Outer@7a5d012c
*/
匿名内部类
本质详解
有两个需求:
- 需求1:使用IA接口,实现接口内方法
传统方法:单独用类实现接口方法,并创建对象,如tiger1 - 需求2:Tiger类只使用一次,后面不再使用,单独定义一个类浪费资源
解决方法:使用匿名内部类简化,如tiger2
解决代码:
- 需求1:使用IA接口,实现接口内方法
public class test1 {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer //外部类
{
private int n1 = 100;
public void method() //方法
{
//需求1
Tiger tiger1 = new Tiger();
tiger1.cry();
//需求2:使用匿名类
IA tiger2 = new IA()
{
@Override
public void cry() {
System.out.println("(匿名类)老虎叫。。。");
}
};
// IA tiger2 = () -> System.out.println("(匿名类)老虎叫。。。");
tiger2.cry();
}
}
interface IA
{
void cry();
}
//需求1
class Tiger implements IA
{
@Override
public void cry() {
System.out.println("(实现接口)老虎叫。。。");
}
}
/*
输出:
(实现接口)老虎叫。。。
(匿名类)老虎叫。。。
*/
分析:
tiger2的的编译类型是IA,运行内存就是匿名内部类;
内部匿名类的底层:
class Outer$1 implements IA //Outer$1是系统分配的名称 { @Override public void cry() { System.out.println("(匿名类)老虎叫。。。"); } }
Jdk
底层在创建匿名内部类 Outer$1后,立即创建了Outer$1实例,并把地址返回给tiger2;匿名内部类只使用一次就不再使用(对象可以多次使用)。
匿名内部类的使用
基于类的匿名内部类
father
编译类型是Father
,运行类型是Outer$1
(匿名内部类);底层会创建匿名内部类:
同时也直接返回了匿名内部类
Outer$1
的对象。
class Outer$1 extend Father
{
@Override
void speak() {
System.out.println("匿名内部类重写 Father中speak方法");
}
}
如下代码演示:
public class test1 {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer //外部类
{
private int n1 = 100;
public void method() //方法
{
//基于类的匿名内部类
Father father = new Father("Jack"){ //匿名内部类也可使用构造器,Jack传递给构造器
@Override
void speak() {
System.out.println("匿名内部类重写 Father中speak方法");
}
};
System.out.println("father对象的运行类型:"+father.getClass());
father.speak();
//基于抽象类的匿名内部类
Animal animal = new Animal()
{
@Override
void eat() {
System.out.println("动物吃东西。");
}
};
animal.eat();
}
}
class Father
{
String name;
public Father(String name) {
this.name = name;
System.out.println("接收到的名字:"+name);
}
void speak() {}
}
abstract class Animal
{
abstract void eat();
}
/*
输出:
接收到的名字:Jack
father对象的运行类型:class demo.Outer$1
匿名内部类重写 Father中speak方法
动物吃东西。
*/
使用细节
调用可以动态绑定调用,也可以直接调用,内部类本身就会返回对象;
可以访问外部类的所有成员,包括私有成员;
匿名内部类访问外部类成员:直接访问;
外部其他类(外部类之外的类)不能访问局部内部类;
若外部类和局部内部类的成员重名时,默认遵循就近一致原则,如果向访问外部类的成员,使用
外部类.this.成员
去访问。
//动态绑定调用
Animal animal = new Animal()
{
@Override
void eat(String s) {
System.out.println("动物吃"+s);
}
};
animal.eat("草");
//直接调用
new Animal() //返回对象
{
@Override
void eat(String s) {
System.out.println("动物吃"+s);
}
}.eat("草");
应用场景
- 当作实参直接传递,如下代码演示:
代码演示:
/*
需求:实现一个多功能闹钟,可以提醒不同事件
解决方法:1. 接口实现:代码繁杂
2. 内部匿名类当作实参
*/
public class test1 {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer //外部类
{
private int n1 = 100;
public void method() //方法
{
CellPhone cellPhone = new CellPhone();
//内部类直接当实参传递
cellPhone.SmartClock(new Clock() {
@Override
public void ring() {
System.out.println("起床了。");
}
});
cellPhone.SmartClock(new Clock() {
@Override
public void ring() {
System.out.println("上课了。");
}
});
}
}
interface Clock
{
void ring();
}
class CellPhone
{
public void SmartClock(Clock clock)
{
clock.ring();//动态绑定
}
}
/*
输出:
起床了。
上课了。
*/
成员内部类
定义在外部类的成员位置;
可以直接访问外部类的所有成员,包含私有的;
可以添加任意访问修饰符,因为它的地位就是类的一个成员;
作用域:和外部类的其他成员一样,作用域为整个外部类体;
成员内部类访问外部类:直接访问;
外部类访问内部类:创建对象再访问;
若外部类和局部内部类的成员重名时,默认遵循就近一致原则,如果向访问外部类的成员,使用
外部类.this.成员
去访问;外部其他类访问成员内部类:
/*
需求:外部其他类访问成员内部类成员
*/
public class test1 {
public static void main(String[] args) {
//方法1:相当于把new Inner()当作是outer的成员
Outer outer = new Outer();
Outer.Inner inner1 = outer.new Inner();
inner1.say();
//方法2:在外部类中,编写一个方法,可以返回Inner对象
Outer.Inner inner2 = outer.getInner();
inner2.say();
}
}
class Outer //外部类
{
private int n1 = 100;
public class Inner {
void say()
{
System.out.println("成员内部类输出。");
}
}
//方法2中获取对象的方法
public Inner getInner() //返回Inner类型的方法
{
return new Inner();
}
}
/*
输出:
成员内部类输出。
成员内部类输出。
*/
静态内部类
定义在外部类的成员位置,并且有
static
修饰;可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员;
可以添加任意访问修饰符,因为它的地位就是类的一个成员;
作用域:和外部类的其他成员一样,作用域为整个外部类体;
静态内部类访问外部类:直接访问所有静态成员;
外部类访问静态内部类:创建对象再访问;
若外部类和局部内部类的成员重名时,默认遵循就近一致原则,如果向访问外部类的成员,使用
外部类.成员
去访问;外部其他类访问静态内部类:
/*
需求:外部其他类访问静态内部类成员
*/
public class test1 {
public static void main(String[] args) {
//方法1:静态成员可以直接通过 类名.成员 访问(满足访问权限时)
Outer outer = new Outer();
Outer.Inner inner1 = new Outer.Inner();
inner1.say();
//方法2:在外部类中,编写一个方法,可以返回Inner对象实例
Outer.Inner inner2 = outer.getInner();
inner2.say();
//当getInner()为静态时,也可应直接用类名访问,如下
//Outer.getInner().say();
}
}
class Outer //外部类
{
private int n1 = 100;
static class Inner {
void say()
{
System.out.println("静态内部类输出。");
}
}
//方法2中获取对象的方法
public static Inner getInner() //返回Inner类型的方法
{
return new Inner();
}
}
/*
输出:
静态内部类输出。
静态内部类输出。
*/