四种访问限制符:public、protected、默认、private
public class a {
public static void main(String[] args) {
Person p1 = new Person(); //有括号
}
}
class Person {
int age;
String name;
}
类和内存对象的分配机制:
1. 栈:存放基本数据类型(局部变量)
2. 堆:存放对象(Cat cat、数组等)
3. 方法区:常量池(常量,如字符串)、类加载信息
4. 示意图Cat(name,age,price);
方法
public class a {
public static void main(String[] args) {
Person p1 = new Person(); //有括号
p1.speak();//调用
}
}
class Person {
int age;
String name;
public void speak() {
System.out.println("1");
}
}
可变参数
public class a {
public static void main(String[] args) {
T t = new T();
int nums[] = {1, 2, 3};
System.out.println(t.sum(nums));
}
}
class T {
public int sum(int ...nums) {
System.out.println("接受参数的个数 = " + nums.length);
int res = 0;
for(int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}
}
细节:可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
class T {
public void f1(String str, double ... nums) {
}
}
注意:一个形参列表只能出现一个可变参数
可变参数的本质是数组
可变参数的实参可以是数组
e.g.
//返回姓名和课程成绩
public String shouwScore(String name, double ... scores) {
double res = 0;
for(int i = 0; i < scores.length; i++) sum += scores[i];
return name + "有" + scores.length + "门课总成绩总分为" + res; // return String
}
全局变量:也叫属性,作用域为整个类体
可以不赋值(可以使用默认值)
作用域:可以被本类或其他类使用(通过对象调用,即创一个对象)
可以加修饰符(public、private)
局部变量:一般指在成员方法中定义的变量
必须赋值,否则会出错
不可加修饰符
作用域:只能在本类对应的方法中使用
构造器(完成对象的初始化,并不是创建对象)
1.构造器没有返回值
2.方法名必须和类名相同
3.在创建对象时,系统自动的调用该类的构造方法
4.构造器的修饰符可以是默认也可以是public、protected、private
5.一旦定义了自己的构造器,默认的构造器就被覆盖了
对象创建的流程分析
class Person {
int age = 90;
String name;
Person(String n, int a){
name = n;
age = a;
}
}
Person p = new("小倩", 20);
1.加载Person类信息(Person.class),只会加载一次。
2.在堆中分配空间
3.完成对象初始化(①默认初始化 age = 0 name = null ②显式初始化 age = 90,name = null
③构造器初始化 age = 20, name = 小倩)
4.对象在堆中的地址,返回给p
class Caculator{
double num1;
double num2;
public Caculator(int num1, int num2) {
this.num1 = num1;
this.num2 = num2;
}
public Double div() {
if(num2 == 0) {
System.out.println("num2不能为0");
return null; //null可以返回给Double类但不能返回给double
}
else {
return num1 / num2;
}
}
}
包
包的三大作用:
①区分相同名字的类
②当类很多时,可以很好地管理类
③控制访问范围
包的基本语法:
package com.hspedu;
说明:
1.package关键字,表示打包
2.com.hspedu表示包名
包的命名:
只能包含数字、字母,不能用数字开头,不能是关键字或保留字
一般时com.公司名.项目名.业务模块名
常用的包:
java.lang //lang包是基本包,默认引入,不需要再引入
java.util //系统提供的工具包、工具类,使用Scanner
java.net //网络包,网络开发
java.awt // 做java的界面开发,GUI
import java.util.*;
import java.uitl.Arrays;
Arrays.sort(arr); //排序工具
package的作用是声明当前类所在的包,需要放在类的最上面,之后才import...。
一个类中最多只有一句package
访问限制符
注意:
①修饰符可以修饰类中的属性,成员方法及类
②只有默认和public才能修饰类
继承
基本语法:class 子类 extends 父类 {
}
父类又叫超类、基类
子类又叫派生类
public class a {
String name;
public void testing() {
System.out.println("...");
}
public static void main(String[] args) {
}
}
public class Graduates extends a {
}
注意:
私有属性不能在子类直接访问,要通过公共的方法去访问。
子类必须调用父亲的构造器,完成父类的初始化
当创建子类对象时,不管使用子类的哪个构造器,默认情况下会去调用父类的无参构造器。如果父类没有提供无参构造器,则必须在子类用super去指定使用父类的哪个构造器完成父类的初始化工作,否则,编译不会通过。
public class Sub extends a {
public Sub(String name, int age) {
super("hsp"); //指定调用哪个父类的构造器,是一个形式
System.out.println("子类Sub(String name, int age)构造器被调用...");
//父类a(String name)构造器被调用
}
}
如果希望指定调用父类的某个构造器,则显示的调用:super(参数列表)
super在使用时必须放在构造器第一行,只能在构造器中使用。
super和this都只能放在构造器第一行,因此这两个方法不能放一个构造器中。
java中所有类都是Object类的子类
若想让A继承B和C可以A继承B,B继承C
super关键字
super代表父类的引用,用于访问父类的属性、方法、构造器
基本语法
1.访问父类的属性,但不能访问父类的private属性
super.属性名(super.n1)
package work2;
public class A {
int n1;
public A(int n1) {
this.n1 = n1;
}
}
//
package work2;
public class B extends A{
public B(int n1) {
super(n1);
}
public void hi() {
System.out.println(n1);
}
}
//
package work2;
public class test {
public static void main(String[] args) {
B b = new B(1009);
b.hi();
}
}
//输出1009
2.访问父类的方法,不能访问父类的private方法
super.方法名(参数列表)
super.test1();
super.test2();
3.访问父类的构造器
super(参数列表) 只能放在构造器的第一句,只能出现一句
当子类中有和父类中的成员重名时,为了访问父类的成员,必须通过super,如果没有重名,使用super、this、直接访问是一样的效果。
若 this.cal( );
(1)先找本类,如果有,则调用。
(2)如果没有,则找父类(如果有,并且可以调用,则调用)
(3)如果父类没有,则继续找父亲的父亲...
注意:如果查找方法的过程中,找到了,但不能访问,则报错。
如果查找方法中没有找到,则提示方法不存在。
super从父亲开始找
方法重写/覆盖
子类和父亲的某个方法的名称、返回类型、参数一样,则说子类覆盖了父亲的那个方法
注意:子类的返回类型和父类的返回类型一样,或者是父亲返回类型的子类
父:public Object getInfo(){ }
子:public String getInfo(){ }
子类的方法不能缩小父类的访问权限
public > protected > 默认 > private
方法重写细节:
class AAA {
public AAA m() {
return null;
}
}
class BBB extends AAA {
public BBB m() {
return null;
}
}
其中:BBB是AAA的子类,m是方法重写
多态
函数重载、重写都是多态的体现
对象的多态(核心)
(1)一个 对象的编译类型和运行类型可以不一致
(2)编译类型在定义对象时,就确定了,不能改变
(3)运行类型是可以变化的
(4)编译类型看定义时 = 号的左边,运行类型看=号的右边
Animal animal = new Dog();//animal编译类型是Animal,运行类型是Dog
animal = new Cat(); //animal的运行类型变成了Cat,编译类仍然是Animal
多态的前提是:两个对象存在继承关系
多态的向上转型
(1)本质:父类的引用指向了子类的对象
(2)语法:父类类型 引用名 = new 子类类型();
(3)特点:编译类型看左边,运行类型看右边
可以调用父类中的所有成员(需遵守访问权限)
不能调用子类中特有成员(因为在编译阶段,能调用哪些成员,是由编译类型决定的)
最终运行效果看子类(运行类型)的具体实现,即调用子类(运行类型)开始查找方法
public class PolyDetail {
//向上转型:父类的引用指向了子类的对象
Animal animal = new Cat();
Object obj = new Cat();
}
多态的向下转型
(1)语法:子类类型 引用名 = (子类类型)父类引用 //强制转换
(2)只能强转父类的引用,不能强转父类的对象
(3)要求父类的引用必须指向的是当前目标类型的对象
(4)可以调用子类类型的所有成员
Animal animal = new Cat();
Cat cat = (Cat) animal;
cat.catchMouse();
此时cat的编译类型是Cat,运行类型是Cat
instanceof 判断子类
public class PolyDetail {
public static void main(String[] args) {
Base base = new Sub();
System.out.println(base.count); //看编译类型 10
Sub sub = new Sub();
System.out.println(sub.count); // 20
}
}
class Base{
int count = 10;//属性
}
class Sub extends Base {
int count = 20;
}
属性不能重写,属性的值看编译类型
instanceof 比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的自类型
class Base {
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
public class PolyDetail{
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count); //20
s.display(); //20
Base b = s; //base类型
System.out.println(b == s); //T 判断地址是否相同 二者指向相同
System.out.println(b.count); //b的编译类型为base
b.display();//20
}
}
java的动态绑定机制
1.调用对象时,该方法会和该对象的内存地址/运行类型绑定
2.当调用对象属性时,没有动态绑定机制,哪里声明,哪里调用。
class AA{
public int i = 10;
public int sum() {
return getI() + 10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
class BB extends AA{
public int i = 20;
public int sum() {
return i + 20;
}
public int getI() {
return i;
}
public int sum1() {
return i + 10;
}
}
public class PolyDetail{
public static void main(String[] args) {
AA a = new BB();
System.out.println(a.sum());//40
System.out.println(a.sum1());//30
}
}
注释sum后结果为20,getI找到的i为20
for(int i = 0; i < persons.length; i++) {
System.out.println(persons[i].say());//动态绑定机制
if(persons[i] instanceof Student) {
Student student = (Student)persons[i];//向下转型
student.study();
}
else if(persons[i] instanceof Teacher) {
Teacher teacher = (Teacher)persons[i];
teacher.teach();
}
persons[i].teach();//不能执行,person没有teach
}
object类详解
==
==:既可以判断基本类型,也可以判断引用类型
==判断基本类型,判断值是否相等
==判断引用类型,判断地址是否相等,即判定是不是同一个对象
equals方法
equals只能判断引用类型,默认判断地址是否相等,子类往往重写该方法,比如Integer,String
Integer integer1 = new Integer(1000);
Integer integer2 = new Integer(1000);
System.out.println(integer1 == integer2); //false
String str1 = new String("hsp");
String str2 = new String("hsp");
System.out.println(str1 == str2); //false 看地址
System.out.println(ste1.equals(str2)); //true
重写equals方法
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj instanceof Person) {
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
return false;
}
hashCode方法
(1)提高具有哈希结构的容器的效率
(2)两个引用,如果指向的是同一个对象,则哈希值一样
(3)两个引用,如果指向的是不同对象,则哈希值不一样
(4)哈希值主要是根据地址号来的,但不能完全等价于地址
(5)有需要也可重写
System.out.println("aa.hashCode()="+aa.hashCode());
toString方法
默认返回:全类名+@+哈希值的十六进制
子类往往重写toString方法,用于返回对象的属性信息
当输出一个对象时,toString方法会被默认的调用
重写toString方法,输出对象的属性
快捷键alt + insert ->toString
finalize方法
1.当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作。
2.什么时候被回收,当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize方法。
3.垃圾回收机制的调用,是由系统来决定,也可以通过System.gc()主动出发垃圾回收机制。
断点调试
F7(跳入)、F8(跳过)、Shift+F8(跳出)、F9(resume,执行到下一个断点)
F7:跳入方法内
F8:逐行执行代码
shift + F8:跳出方法
日期格式化
Date date = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH : mm");//用于日期格式化
类变量(静态变量)
static ...就可以在类的所有实例中共享(例如需要计数器时就可以使用 public static int count = 0)
类变量/静态变量可以通过类名访问
static类变量在类加载的时候就生成
定义语法:访问修饰符 static 数据类型 变量名
static 访问修饰符 数据类型 变量名
如何访问:类名.类变量名
对象名.类变量名(静态变量的访问修饰符的访问权限和范围和普通属性是一样的)
实例变量不能通过 类名.类变量名 方式访问
类变量在类加载的时候就初始化了,即使没有创建对象,只要类加载了就可以使用类变量。
类变量的生命周期是随类的加载开始,随着类的消亡而销毁。
实例:求累积学生的学费
class stu{
private String name;
private static double fee = 0;
public stu (String name) {
this.name = name;
}
public addfee(int fee) {
this.fee += stu.fee;
}
}
类方法
访问修饰符 static 数据返回类型 方法名(){ }
类方法的调用:类名.类方法名 或者 对象名.类方法名
类方法使用场景:当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率,如工具类中的方法utils Math类、Arrays类
e.g
class cal{
public static double sum(double n1, double n2) {
return n1 + n1;
}
}
注意
①类方法和普通方法都是随着类的加载而加载,将结构信息储存在方法区
②类方法中无this参数
③类方法可以通过类名调用,也可以通过对象名调用。普通方法需创建对象后调用。
④类方法中不允许使用和对象有关的关键字,比如this和super。普通方法可以
⑤类方法(静态方法)中,只能访问静态变量或静态方法,普通成员方法既可以访问普通变量(方法),也可以访问静态变量(方法)(必须遵守访问权限)
public class Test {
static int count = 9;
public void count() {
System.out.println("count = " + (count ++ ));
}
public static void main(String[] args) {
new Test().count(); // 9;
new Test().count(); //10
System.out.print(Test.count); //11;
}
}
main方法
1.main方法时虚拟机调用
2.java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
3.java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
4.该方法接受String类型的数组参数,该数组中保存执行java命令时传递给所允许的类的参数
5.java 执行的参数 参数1 参数2 参数3 args数组
public class homework02{
public static void main(String[] args) {
for(int i = 0; i < args.length; i++) {
System.out.println("第" + (i + 1) + "个参数=" + args[i]);
}
}
}
提示:
1.在main方法中,可以直接调用main方法所在类的静态方法和静态属性。
2.对于非静态成员,必须创建该类的实例对象后,才能通过这个对象去访问类中的非静态成员。
public class main01 {
private static String name = "stu";
public static void hi() {
System.out.println("main01的hi方法");
}
public static void main(String[] args) {
//可以直接使用name
//静态方法可以访问本类的静态成员
System.out.println("name = " name);
hi();
}
}
代码块
代码块也叫初始化块,属于类的成员,类似于方法,将逻辑语句封装在方法体中,用{}包围起来。
代码块没有方法名,没有返回,没有参数,只有方法体,不能通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
基本语法:
[ 修饰符 ]{
}; //;可省略
注意:
①修饰符可选写,只能写static
②使用static的叫静态代码块,没有static修饰的叫普通代码块
代码块的理解:
①相当于对构造器的补充机制,可以做初始化操作
②如果多个构造器都有重复的语句,可以抽取到初始块中,提高代码的重用性
③在创建对象时,不管调用哪个构造器,都会先调用代码块的内容。
class Movie{
String name;
double price;
{
System.out.println("电影开幕了");
}
public Movie(String name) {
this.name = name;
}
public Movie(String name, double price) {
this.name = name;
this.price = name;
}
}
注意:
1.static代码块,随着类的加载而执行,并且只会执行一次。
普通代码块,每创建一个对象就执行。
2.类什么时候被加载?
①创建对象实例时。
②创建子类对象实例时,父类也会被加载(注意super)。
③使用类的静态成员时(静态属性、静态方法)
3.普通的代码块,在创建对象实例时,会被隐式的调用,被创建一次,就调用一次。
如果只是使用类的静态成员,普通代码块不会执行。
4.创建一个对象时,在一个类 调用顺序:
①调用静态代码块和静态属性的初始化(按顺序)
②调用普通代码块和普通属性的初始化
③调用构造方法
5.创建一个子类(继承关系),调用顺序如下:
①父类的静态代码块和静态属性
②子类的静态代码块和静态属性
③父类的普通代码块和普通属性
④父类的构造方法
⑤子类的普通代码块和普通属性
⑥子类的构造器
注意:静态代码块只能调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
单例设计模式
步骤:
1.构造器私有化
2.类的内部创造对象
3.向外提供一个静态的公共方法,getInstance
4.代码实现
饿汉式:
class GirlFriend {
private String name;
private static GirlFriend gf = new GirlFriend("z");
private GirlFriend(String name) {
this.name = name;
}
public static GirlFriend getInstance() {
return gf;
}
}
懒汉式
class GirlFriend {
//为了能在静态方法中返回gf对象,需要将其修饰为static,static不创建对象情况下使用。
private String name;
private static GirlFriend gf;
private GirlFriend(String name) {
this.name = name;
}
public static GirlFriend getInstance() {
if(gf == null) gf = new GirlFriend("z");
return gf;
}
}
对比:
1.主要区别在于创建对象的时机不同,饿汉在类加载就创建了对象实例,懒汉式在使用时才创建。
2.饿汉式不存在线程安全为,懒汉式存在线程安全问题
3.饿汉式存在浪费资源的可能。
final关键字
可以修饰类、属性、方法、局部变量
使用情况:
①当不希望类被继承
②当不希望父类的某个方法被子类覆盖、重写
③当不希望类的某个属性值被修改
④当不希望某个局部变量被修改
final class A
public final void hi() { }
不希望类的某个属性被修改public final double a = 0.0;
不希望某个局部变量被修改 public double TAX_RATE = 0.08;
注意:
①final修饰的属性又叫常量,必须赋初值,并且以后不能修改,赋值可以加载如下位置之一
1.定义时 2.在构造器中 3.在代码块中
②如果final修饰的属性是静态的,则初始化的位置只能是定义时或在静态代码块内,不能在构造器中赋值。
③final类不能继承,但是可以实例化对象
⑤如果类不是final类,但是含有final方法,则该方法虽然不能被重写,但是可以被继承
⑥一般某个类已经是final类了,就没必要将方法修饰成final方法
⑦final不能修饰构造器
⑧final和static往往搭配使用,不会导致类加载,底层编译器做了优化处理,效率更高。
public final static int num = 1000;
在main输出(XX.num)不会导致XX类的加载。
⑨包装类(Integer,Double,Float,Boolean等都是final),String也是final类
注:
可以public int add(final int x) { x ++;} //错误x不能被修改
抽象类
用abstract关键词修饰一个类时,该类叫抽象类
访问修饰符 abstract 类名{ }
用abstract修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 方法名(参数列表); 不能有方法体
注意:
抽象类不能实例化
抽象类不一定要有抽象方法
一旦有抽象方法,则该类必须为抽象类
抽象类可以有任意成员,如:非抽象方法、构造器、静态属性等
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
抽象方法不能使用private、final、static修饰