值传递与引用传递
值传递
package com.oop.demo01;
public class PassByValue {
public static void main(String[] args) {
int a = 1;
System.out.println(a);
PassByValue.change(a);
System.out.println(a); //1
}
//值传递
public static void change(int a){
a = 10;
}
}
引用传递
package com.oop.demo01;
public class PassByReference {
public static void main(String[] args) {
Person person1 = new Person();
System.out.println(person1.name); //null
PassByReference.change(person1);
System.out.println(person1.name); //BIT0
}
//引用传递 传入参数是Person类中的person1对象,可以改变他的属性!
public static void change(Person person){
person.name = "BIT0";
}
}
class Person{
String name; //null
}
类与对象的构建
创建类
package com.oop.demo02;
//学生类
public class Student {
//属性:字段
String name; //null
int age; //0
//方法
public void study(){
System.out.println(this.name+"在学习");
}
}
创建对象
将类实例化,创建一个对象
package com.oop.demo02;
public class Application {
public static void main(String[] args) {
//类:抽象的,实例化
//类实例化后返回一个自己的对象
//student对象就是一个Student类的具体实例
Student xiaoming = new Student();
Student xh = new Student();
xiaoming.name = "小明";
xiaoming.age = 3;
System.out.println(xiaoming.name);
System.out.println(xiaoming.age);
xiaoming.study();
}
}
构造器
一个类即使什么都不写,他也会存在一个方法
这个方法就是构造方法,也叫构造器,是在创建对象时必须调用的。
构造器特点
1.必须和类的名字相同
2.必须没有返回类型。也不能写void
构造器作用
1.new本质就是在调用构造方法
2.初始化对象中属性的值
注意点:
1.定义有参构造之后,如果还想用无参构造,就必须主动定义一个无参构造器(因为系统不会再自动生成无参了)
2.Alt+Insert 自动生成构造器
package com.oop.demo02;
public class Person {
String name;
//无参构造器 如果不主动定义无参,也不主动定义有参构造的话,就会自动生成
//用来实例化初始值
public Person(){
this.name = "BIT";
}
//有参构造:一旦定义了有参构造,还想用无参的话就必须主动定义
public Person(String Isname){
this.name = Isname;
}
public static void main(String[] args) {
//使用new关键字,本质是在调用构造器
Person person1 = new Person(); //只定义有参,不主动定义无参的话,这里就会报错
Person person2 = new Person("BBIT");
System.out.println(person1.name); //BIT
System.out.println(person2.name); //BBIT
}
}
创建对象内存分析
new所创建对象的实体在堆中,对象名只是引用变量,对象是通过引用来操作的。可以两个引用变量指向一个对象,当一个对象不再有引用变量引用时,就会被抛弃。
封装
利用private将数据隐藏,提供能对数据进行操作的public方法,并在方法中进行过滤操作来保护数据。
封装的优点:
1.提高程序的安全性,保护数据
2.隐藏代码的实现细节
3.统一接口
4.增加系统可维护性
Student类
package com.oop.demo03;
public class Student {
//属性私有
private String name;
private int id;
private char sex;
//提供一些可以操作属性的方法
//get 获得这个数据 可以用Alt+Insert自动生成
public String getName(){
return this.name;
}
//set 给这个数据设置值 可以再此进行判断,避免无意义的值传入
public void setName(String name){
this.name = name;
}
}
主类
package com.oop.demo03;
public class Application {
public static void main(String[] args) {
Student s1 = new Student();
//System.out.println(s1.name); 报错,因为name是私有变量
s1.setName("BIT");
System.out.println(s1.getName()); //输出name
}
}
继承
继承是类和类之间的一种关系。
继承关系的两个类,子类与父类,子类继承父类,使用extends表示
Java中,所有的类都默认直接或间接继承Object类
Java类中只能单继承,没有多继承。(一个子类只能有一个直接父类,但可以有多个间接父类:祖父类)
public class Person{
public void say(){
System.out.println("说了一句话");
}
private int money = 100;
public int getMoney() {
return money;
}
}
//Student 继承 Person,拥有父类public的所有方法与变量
public class Student extends Person{
}
public class Application {
public static void main(String[] args) {
Student student1 = new Student();
student1.say();
//System.out.println(student1.money); //money为私有变量,不被继承
System.out.println(student1.getMoney()); //100
}
}
Super详解
-
super()用于调用父类的构造方法,必须在子类的构造器第一行调用
-
super.方法/属性用于调用父类的方法/属性,但无法调用父类的private方法/属性。
-
super只能出现在子类的方法或构造中。
-
super与this不能同时调用构造方法。
VS this
代表对象不同:
this:本身调用者这个对象
super:代表父类对象的引用
前提:
this:没有继承也可以使用
super:只能在继承条件下使用
构造方法
this:本类的构造
super:父类的构造
例:
父类
package com.oop.demo05;
public class Person {
public Person() {
System.out.println("Person无参执行了");
}
protected String name = "father";
public void print(){
System.out.println("Person");
}
}
子类
package com.oop.demo05;
public class Student extends Person{
public Student() {
//隐藏代码:调用了父类的无参构造
super(); //调用父类的构造器必须在子类构造器的第一行
//this("hello"); //会报错,因为要求调用子类有参构造也要在第一行
System.out.println("Student无参执行了");
}
public Student(String name) {
this.name = name;
}
private String name = "child";
public void print(){
System.out.println("Student");
}
public void test(String name){
System.out.println(name); //myname
System.out.println(this.name); //child
System.out.println(super.name); //father
}
public void test1(){
print(); //Student
this.print(); //Person
super.print(); //Person
}
}
main调用
package com.oop.demo05;
public class Application {
public static void main(String[] args) {
Student stu1 = new Student();
stu1.test("myname");
stu1.test1();
}
}
方法重写
重写是方法的重写,和属性无关。
需要有继承关系,子类重写父类的方法
Alt + Insert Override
1. 方法名必须相同2. 参数列表必须相同3. 方法体必须不同4. 修饰符:范围可以扩大但不能缩小: public >protected>default>privare5. 抛出的异常: 范围可以缩小但不能扩大
为什么需要重写:
父类的功能,子类不一定需要,或不一定满足
静态方法
方法的调用只和左边,定义的数据类型有关,不会被重写
//父类
public class B {
public static void test(){
System.out.println("B=>test");
}
}
//子类
public class A extends B{
public static void test(){
System.out.println("A=>test");
}
}
public class Application {
public static void main(String[] args) {
//方法的调用只和左边,定义的数据类型有关
A a = new A();
a.test(); //A=>test
//父类的引用指向了子类
B b = new A();
b.test(); //B=>test
}
}
非静态方法
父类的方法会被子类重写,仅当父类的引用指向子类时,会使用重写的方法,父类自己创建对象使用时不会受影响。
//父类
public class B {
public void test(){
System.out.println("B=>test");
}
}
//子类
public class A extends B{
//Override 重写
@Override //注解:有功能的注释
public void test() {
System.out.println("A=>test");
}
}
public class Application {
public static void main(String[] args) {
//非静态方法:重写
A a = new A();
a.test(); //A=>test
//父类的引用指向了子类,父类的方法被子类重写
//这就是多态
B b = new A();
b.test(); //A=>test
//父类对象不受影响
B c = new B();
c.test(); //B=>test
}
}
多态
多态是方法的多态,属性没有多态性。
即同一方法可以根据发送对象的不同而采用多种不同的行为方式。
一个对象的实际类型是确定的,但可以指向对象的引用类型有很多(父类,有关系的类)
多态存在的条件:
- 有继承关系
- 子类重写父类的方法
- 父类引用指向子类对象
instanceof
X instanceof Y
- 如果有关系,并且是父子关系 ,true
- 如果有关系,但不是父子(两条分支线),false
- 如果完全无关系(完全两条线),编译报错
//Object > String
//Object > Person > Teacher
//Object > Person > Student
Object object = new Student();
System.out.println(object instanceof Student);//true
System.out.println(object instanceof Person);//true
System.out.println(object instanceof Object);//true
System.out.println(object instanceof Teacher);//false
System.out.println(object instanceof String);//false
System.out.println("================================");
Person person = new Student();
System.out.println(person instanceof Student);//true
System.out.println(person instanceof Person);//true
System.out.println(person instanceof Object);//true
System.out.println(person instanceof Teacher);//false
//System.out.println(person instanceof String);//编译报错!
System.out.println("================================");
Student student = new Student();
System.out.println(student instanceof Student);//true
System.out.println(student instanceof Person);//true
System.out.println(student instanceof Object);//true
//System.out.println(student instanceof Teacher);//编译报错
//System.out.println(student instanceof String);//编译报错
类型转换
-
父类引用指向子类的对象
-
把子类转换为父类,向上转型
Student s1 = new Student(); Person p1 = s1 //直接转换
-
把父类转换为子类,向下转型,子类转换为父类,可能丢失自己本来的方法
Person p1 = new Student(); //必须是父类引用指向子类对象,不能是父类对象Student s1 = (Student) p1; //强制转换
类变量与类方法
类变量
- 静态属性/静态属性/类变量:可以用类名/对象名调用,没有创建对象实例也能访问。是所有对象的共享变量。
class Child{ public static int count = 0;//类变量}
- 非静态属性/实例变量:必须创建对象,并用对象名调用,是每个对象独享的变量。
类方法
-
静态方法/类方法:可通过类名/对象名调用**静态方法只能访问静态变量/方法。**普通方法可以调用静态方法。不允许使用this,super关键字。
-
普通方法:只能通过对象名调用。
小结:静态方法只能访问静态成员;非静态方法可以访问静态与非静态成员。
类方法
public class Student { public static void say(){ System.out.println("学生说话了"); } } public class Demo1{ public static void main(String[] args){ Student.say(); //可直接调用 } }
普通方法
public class Student { public void say(){ System.out.println("学生说话了"); } } public class Demo2{ public static void main(String[] args){ //实例化这个类 new //对象类型 对象名 = 对象值 Student student1 = new Student(); student1.say(); } }
代码块
- 代码块是对构造器的补充机制,可以做初始化操作,每次**创建一个对象(new)**就会执行一次。
{
System.out.println("电影开始放映")
}
- 静态代码块,随着类的加载而执行,且仅执行一次。
static{
System.out.println("静态代码块");
}
类什么时候被加载?
-
创建对象实例时(new)
-
创建子类对象实例,父类也会被加载
-
使用类的静态成员时
创建一个对象时,在一个类调用顺序是:
- 调用静态代码块和静态属性初始化(同优先级,按定义顺序)
- 调用普通代码块和普通属性的初始化(同上)
- 调用构造方法。其中包括super()与调用它的普通代码块。
例:创建子类对象时的调用顺序:(面试题)
- 父类静态代码块与静态属性
- 子类静态代码块与静态属性
- 父类普通代码块与普通属性初始化
- 父类构造方法
- 子类普通代码块与普通属性初始化
- 子类构造方法
静态导入包:导入后,程序中使用该方法无需Math.random,可直接random8
import static java.lang.Math.random
单例设计模式
采取一定方法保证在整个系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
步骤(饿汉式):先创建好对象
- 将构造器私有化(private ClassName(){})
- 在类的内部创建一个对象(静态)
- 在提供一个static方法,返回静态对象
步骤(懒汉式):
- 将构造器私有化
- 定义一个静态属性对象(private static Cat cat;)
- 提供一个public static方法,当属性对象为空时创建对象并返回。
final关键字
-
final可修饰类、属性、方法和局部变量
-
当有以下情形时可以使用final:
- 不希望类被继承
- 不希望父类某个方法被子类重写
- 不希望类的某个属性值被修改
- 不希望某个局部变量被修改
- final修饰的属性又叫常量,一般用XX_XX_XX命名
- final修饰的属性定义时必须赋初值,可在定义时、构造器中、代码块中赋值。静态的不能在构造器中赋值。
- final类不能继承,但可以实例化对象。
- 非final类含有final方法,虽然方法不能重写,但可以被继承。
- 一般来说,final类中,没必要将方法修饰成final方法
- final不能修饰构造器
- final往往与static搭配使用修饰变量,当在用类名直接调用变量时,类不会被加载,只调用变量。
class Demo{
public static final int i = 16;
static{
System.out.println("Demo类被调用")
}
}
System.out.println(Demo.i) //16
- Integer Double Float Boolean String都是final类
抽象类
abstract
将类与方法抽象表达,做一个约束,等继承他的子类去实现抽象方法。
- 抽象类不能被实例化
- 抽象类不一定要包含abstract方法
- 一旦类中包含了abstract方法,则类必须声明为abstract
- abstract只能修饰类和方法
- 抽象类可以有任意成员(非抽象方法 构造器 静态属性)
- 抽象方法不能有方法体(无大括号)
- 如果一个类继承了抽象类则他必须实现抽象类的所有抽象方法,除非自己也是抽象类
- 抽象方法不能使用private、final、static修饰,因为他们与重写相违背。
//抽象类
public abstract class Action {
//约束,等人帮我实现
//抽象方法,只有名字,没有方法实现
public abstract void doSomething();
//不能new抽象类,只能被子类继承
//抽象类中可以写普通方法
}
//继承了抽象类,必须通过重写帮抽象类实现它的抽象方法
public class A extends Action{
@Override
public void doSomething() {
}
}
抽象类实践-模板设计模式
当有多个类,以相同的流程执行不同方法时,可以将流程以及抽象的方法封装到一个抽象类中,当类执行时调用抽象类的流程方法并重写自己特殊方法的抽象方法,实现模板化的效果。 详见老韩例。
接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用时,再根据具体情况把方法写出来。
- 定义接口:interface
public interface UserService { void add(String name); //省略abstract关键字}
- 定义实现类:implements
必须实现接口中的所有抽象方法,可以对接多个接口
public class UserServiceImpl implements UserService,TimeService{
@Override
public void add(String name) {
方法
}
@Override
public void timer() {
方法
}
}
注:接口是更加抽象的抽象类,抽象类中可以有方法体,jdk7.0的接口中所有方法都没有方法体;jdk8.0后接口可以有静态方法,默认方法(default)的具体实现。
- 接口的作用: 统一各类的功能相似的方法的方法名,方便上层调用
注意事项:
- 接口不能被实例化。
- 接口中所有的方法都是public的。
- 普通类实现接口,必须实现给接口的所有抽象方法。alt+enter
- 抽象类实现接口,可以不用实现接口的方法
- 一个类可以实现多个接口
- 接口中的属性,只能是final的,而且是public static final修饰的。如 int a = 1; 实际是public static final int a = 1;(必须初始化)
- 接口中属性的访问形式:接口名.属性名
- 接口不能继承其他的类,但可以继承多个别的接口
- 接口的修饰符只能是public和默认,这点和类的修饰符是一样的
接口VS继承
- 当子类继承父类,就自动拥有父类的功能
- 如果子类需要拓展功能,可以通过实现接口的方式扩展
- 可以理解 实现接口是 对java单继承机制的一种补充
- 继承的价值在于解决代码的复用性和可维护性;接口的价值在于设计好规范方法,让类去实现。
- 接口在一定程度上实现代码的解耦][即:接口规范性+动态绑定机制]
接口的多态特性
多态参数
- 方法的参数可以设为接口类型,用于传入实现了该接口的对象,并在方法中调用他们实现的抽象方法。
public class Computer{
//UsbInterface usbInterface 形参是接口类型 UsbInterface
//接收的是实现了UsbInterface接口的类的对象实例
//可以传入实现了UsbInterface接口中功能的Camera和Phone
public void work(UsbInterface usbInterface){
usbInterface.start();
usbInterface.stop();
}
}
- 接口类型的变量可以指向实现了这个接口类的对象实例
public class InterfacePolyParameter {
public static void main(String[] args) {
//接口的多态体现
//接口类型的变量 if01 可以指向 实现了 IF 接口类的对象实例
IF if01 = new Monster();
if01 = new Car();
//继承体现的多态
//父类类型的变量 a 可以指向 继承 AAA 的子类的对象实例
AAA a = new BBB();
a = new CCC();
}
}
//接口
interface IF {}
class Monster implements IF{}
class Car implements IF{}
//继承
class AAA {}
class BBB extends AAA {}
class CCC extends AAA {}
多态数组
接口类型的数组可以传入实现了该接口的对象作为元素,并进行调用,实现动态绑定。调用某对象的特有方法时,需要向下转型
public class InterfacePolyArr {
public static void main(String[] args) {
//多态数组 -> 接口类型数组
Usb[] usbs = new Usb[2];
usbs[0] = new Phone_();
usbs[1] = new Camera_();
/*
给 Usb 数组中,存放 Phone 和 相机对象,Phone 类还有一个特有的方法 call(),
请遍历 Usb 数组,如果是 Phone 对象,除了调用 Usb 接口定义的方法外,
还需要调用 Phone 特有方法 call
*/
for(int i = 0; i < usb.length; i++){
usbs[i].work();//动态绑定..
//和前面一样,我们仍然需要进行类型的向下转型
if(usbs[i] instanceof Phone_) {//判断他的运行类型是 Phone_
((Phone_) usbs[i]).call();//向下转型
}
}
}
}
interface Usb{
void work();
}
class Phone_ implements Usb {
public void call() {
System.out.println("手机可以打电话...");
}
@Override
public void work() {
System.out.println("手机工作中...");
}
}
class Camera_ implements Usb {
@Override
public void work() {
System.out.println("相机工作中...");
}
}
多态传递
如果接口类1继承了接口类2,则实现该接口1的对象实例也要实现接口2中的抽象方法,同时接口2类型的变量也可以指向此对象。
public class InterfacePolyPass {
public static void main(String[] args) {
//接口类型的变量可以指向,实现了该接口的类的对象实例
IG ig = new Teacher();
//如果 IG 继承了 IH 接口,而 Teacher 类实现了 IG 接口
//那么,实际上就相当于 Teacher 类也实现了 IH 接口.
//这就是所谓的 接口多态传递现象.
IH ih = new Teacher();
}
}
interface IH {
void hi();
}
interface IG extends IH{ }
class Teacher implements IG {
@Override
public void hi() {
}
}
内部类
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类成为内部类,嵌套其他类的类成为外部类。是类的五大成员(属性、方法、构造器、代码块、内部类),内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。注意:内部类是学习的难点重点,底层源码存在大量的内部类。
内部类的分类
- 定义在外部类局部位置上(比如方法内):
- 局部内部类(有类名)
- 匿名内部类(没有类名)重点!!!
- 定义在外部类的成员位置上:
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
局部内部类
- 局部内部类是定义在外部类的局部位置的类,通常在方法中,也可以在代码块中,本质就是一个类。当在方法中时,对象实例调用这个方法时,内部类才存在。
- 可以直接访问外部类的所有成员,包括私有的。
- 不能添加访问修饰符,因为他的地位就是一个局部变量,局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量可以使用final。
- 作用域:仅仅在定义它的方法或代码块中。
- 局部内部类可以直接访问外部类的成员。
- 外部类要访问内部类的成员,必须先创建对象,再访问。
- 外部其他类不能访问局部内部类。
- 当外部类的成员与局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,可以使用[外部类名.this.成员]访问。注:这里的[外部类名.this]就相当于外部类的实例化对象,因为内部类在方法中,哪个对象调用了这个方法,[外部类名.this]就相当于哪个对象。
匿名内部类(重点)
- 本质是类
- 是内部类
- 该类没有名字
- 同时还是一个对象
说明:匿名内部类定义在外部类的局部位置,比如方法中,并且没有类名
匿名内部类基本语法
new 类或接口(参数列表){
类体
};
基于接口的内部类
-
需求:使用某接口,并创建对象,调用方法,但只使用一次
-
传统方式:写一个类,实现该接口,再创建对象
-
简化方式:匿名内部类
接口名 对象名 new 接口名(){ 重写实现接口抽象方法 };
例:
interface IA{ void cry(); } class Outer{ public void method(){ IA tiger new IA(){ //匿名内部类 @Override public void cry(){ System.out.println("老虎叫"); } }; tiger.cry;//老虎叫 } }
-
tiger的编译类型:IA
-
tiger的运行类型:匿名内部类 Outer$1
System.out.println("tiger 的运行类型=" + tiger.getClass());//Outer$1
-
实际上,在底层jdk会创建匿名内部类Outer$1,并立即创建了实例,把地址返回给tiger,底层代码:
class Outer$1 implements IA { @Override public void cry() { System.out.println("老虎叫"); } }
-
底层创建的匿名内部类只能使用这一次,但tiger对象可以多次使用。
基于类/抽象类的匿名内部类
-
创建匿名内部类
类名 对象名 = new 类名(){ 重写方法 }
例:
class Father{ public Father(String name){ System.out.println("接收到name=" + name); } public void test(){} } class Outer{ public void method(){ Father f1 = new Father("jack"){ //匿名内部类 @Override public void test(){ System.out.println("匿名内部类重写了test方法"); } } f1.test;//匿名内部类重写了test方法 } }
-
f1 编译类型:Father
-
f1 运行类型:Outer$2
-
底层也会创建匿名内部类,同时返回匿名内部类的对象,底层代码:
class Outer$2 extends Father{ @Override public void test() { System.out.println("匿名内部类重写了 test 方法"); } }
-
注意(“jack”)参数列表会传递给构造器
匿名内部类的性质
-
匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类的方法可以创建匿名内部类并用一个对象接收,用对象名调用方法,也可以直接在创建匿名内部类的最后[.方法名;]直接调用。
匿名内部类是继承目标类的,因此可以随意调用目标类的原有的方法,super()也指向目标类的成员。
new Person(){ @Override public void ok(String str) { super.ok(str); } }.ok("jack");
-
局部内部类的性质,匿名内部类都有。
匿名内部类的实践
当作实参传递,简洁高效
interface IL {
void show();
}
public class Application{
public static void main(String[] args) {
//当做实参直接传递,简洁高效
f1(new IL() { //匿名内部类
@Override
public void show() {
System.out.println("这是一副名画~~...");
}
});
//传统方法
f1(new Picture());
}
//静态方法,形参是接口类型,接口的多态性
public static void f1(IL il) {
il.show();
}
}
//传统方法
//类->实现 IL => 编程领域 (硬编码)
class Picture implements IL {
@Override
public void show() {
System.out.println("这是一副名画 XX...");
}
}
成员内部类
成员内部类是定义在外部类的成员位置(不在方法或代码块中),并且没有static修饰。
-
可以直接访问外部类的所有成员,包括私有。
-
可以添加任意访问修饰符(public protect 默认 private),因为他的地位就是一个成员。
-
作用域和外部类其他成员一样,为整个类体。
-
内部类访问外部类成员:直接访问。
-
外部类访问内部类成员:创建对象,再访问。
-
其他外部类访问成员内部类:
-
法1:创建对象,再调用
Outer o1 = new Outer(); Outer.Inner i1 = o1.new Inner(); i1.say;
-
法2:在类中写一个get成员内部类的方法,在外部将其传给对象,在调用
class Outer{ public Inner getInner(){ return new Inner(); } } public class Application{ public static void main(String[] args){ Outer o1 = new Outer(); Outer.Inner i2 = o1.getInner(); i2.say; } }
-
-
当外部类的成员与成员内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,可以使用[外部类名.this.成员]访问。
静态内部类
成员内部类是定义在外部类的成员位置,并且有static修饰。
-
可以直接访问外部类的所有静态成员,包括私有的。
-
可以添加任意访问修饰符。
-
作用域:整个类体。
-
内部类访问外部类静态成员:直接访问。
-
外部类访问内部类静态成员:创建对象,再访问。
-
其他外部类访问成员内部类:
//方式 1 //因为静态内部类,是可以通过类名直接访问(前提是满足访问权限) Outer10.Inner10 inner10 = new Outer10.Inner10(); inner10.say(); //方式 2 //编写一个方法,可以返回静态内部类的对象实例. Outer10.Inner10 inner101 = outer10.getInner10(); inner101.say();
-
当外部类的成员与静态内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,可以使用[外部类名.成员]访问。无需this