javaobject
类:
注意事项
- 类名首字母大写,驼峰格式
- 类中定义的变量称为成员变量,类中定义的方法成为成员方法。
- 成员变量本身定义时就有默认值,具体和动态数组的元素一样。类中只定义即可,没必要赋初值。
- 一个代码文件可以有多个 class,但只能有一个 public 修饰的 class,且文件名必须和这个用 public 修饰的 class 类名一致。
- 多个变量指向同一个对象数据会相互影响。
- 一个对象如果没有变量指向它,会成为堆内存中的垃圾。java 有自动垃圾回收机制,自动清除垃圾对象。
例子
public class 类名 {
// 类体
String name;
double chinese;
double math;
public void printavg() {
System.out.println("平均成绩:" + (chinese + math) / 2);
}
}对象:
创建对象
student s1 = new student(); //创建对象,student类型的变量s1被new出来。
student s2 = new student(); //s1,s2都是引用变量,存储的是堆内存中对象本体的地址。对象本体内存储方法区中对应的类模板地址。底层原理
public class test { //把这个类加载到内存的方法区
public static void main(String[] args) { //main加载到栈内存
student s1 = new student();
//1. 将student类加载到方法区
//2. 在栈内存开辟存放s1变量的空间
//3. 之后new出来的对象本体存放到堆内存,并且会按照类的结构在这个对象本体中开辟变量空间,变量空间都分别装的默认值,对象本体里面还会创建一个类模板的地址,指向自己是由哪个类创建出来的,这个地址是类在方法区的地址。
//4. 最后再把对象本体在堆内存中的地址赋值给栈内存中的s1,让s1指向这个对象本体。这个地址的结构如 "gml.duixiang.classdemo.student@7344699f",即包名+类名+@+十六进制内存地址。
s1.name = "张三"; //把这些变量赋值给堆内存中对象本体内对应的变量空间。
s1.chinese = 90;
s1.math = 80;
s1.print(); //通过s1存储的地址找到堆内存中的对象实体,再通过对象实体存储的类模板地址找到方法区中对应的类模板,再通过类模板找到对应的方法
}
}this 关键字
- 作用: 是一个变量,可以拿到当前对象在堆内存中的地址,并对这个对象的数据进行实际操作。主要用于解决变量名称冲突问题。
//在类文件中定义这样一个方法
public void printthis() {
System.out.println(this);
}
//在main函数中调用这个方法
student s1 = new student(); //创建一个对象实例
s1.printthis(); //调用这个对象的方法,打印出来的实际上是s1在堆内存中的地址。
System.out.println(s1); //和上面效果一样,都是s1在堆内存中的地址。实际应用:
public class student {
double score;
public void setscore(double score) {
if (this.score < score) { //this.score 表示当前对象的score,第二个score 表示调用这个方法时传入的参数。
System.out.println("及格了" + score);
}
}
//调用时先设置对象实例的score及格分数,再调用setscore方法,传入实际分数,判断是否及格。
}构造器
- 类的一种特殊方法,主要用来初始化对象。分为无参数构造器,和有参数构造器。
- 方法名必须和类名完全相同。
- 创建对象时,对象会自动调用构造器。
- 一个类里可以创建多个构造器,可以有参,也可无参。
- 如果不写构造器,java 会自动为类生成一个无参构造器,如果自己定义了有参构造器就不会自动生成。
- 写完有参后,最好再写一个无参构造器。
例子
//先写一个Student类
public class student {
String name;
double chinese;
//写一个有参构造器
public student(String name ,double chinese) {
this.name = name; //this.name 表示当前对象的name,第二个name 表示调用这个方法时传入的参数。
this.chinese = chinese;
}
}
//在主文件main函数中创建对象并直接初始化
//创建对象实例,会自动调用构造器。具体调用哪个构造器,会根据()括号里面传入的参数判断,如果括号里面没有参数,就调用无参构造器。
student s1 = new student("张三", 90);封装
- 将需要安全的变量数据隐藏,写公开的方法让别人对这个数据进行操作或访问,可以在方法中添加更多验证逻辑,保证数据的安全。
- 封装规范: 合理隐藏,合理暴漏
public class Scoresys {
private double score; // 及格线,不可能让这个变量公有,否则所有人都能胡乱设置及格线。
public void setScore(double score) { // 写一个方法专门设置及格线
// 判断及格线是否合理,如果合理,则设置,否则不设置
if (score > 60 && score < 100) {
this.score = score;
} else {
System.out.println("数据非法");
}
// 其他自定义验证或者校验逻辑
//this.score = score;
}
public double getScore() { // 写一个方法专门获取及格线
// 可以对获取及格线的人进行权限验证逻辑
return score;
}
}javabean
说明
- 特殊形式的类,也叫实体类,类中成员变量都必须私有,且要对外提供每个成员变量的 getxxx 和 setxxx 方法。
- 实体类必须有一个公共的无参构造器
- 实体类一般用于存储数据,而处理数据的业务交给其他类实现,实现业务逻辑的分离。
完整实例
//写一个实体类存储学生姓名
public class Student {
private String name;
public Student(){
}
public Student(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}//再新建一个类,用于处理学生姓名的逻辑
public class StudentName {
private Student s1; //创建一个Student类成员变量
//定义一个构造器,用于接受Student类创建的对象实例,然后把传进来的对象复制一份给 StudentName类的成员变量s1,这样就可以对这个数据进行处理
public StudentName(Student s2) {
this.s1 = s2;
}
//写个方法看看这个学生名字是否合法
public namepass(){
if(s1.getName().length() > 10){ //调用Student对象的方法获取名字
System.out.println("名字太长");
}else{
System.out.println("合法");
}
}
}//在主函数中调用
public static void main(String[] args) {
Student s = new Student();
StudentName s1 = new StudentName(s); 传入s这个对象变量触发StudentName构造器,把s复制一份给s1这个对象的成员变量。
s.setName("张三是个大大大大大大大");
s1.namepass(); //调用StudentName类的方法判断名字是否合法。
}成员变量和局部变量区别
- 成员变量有默认值,定义在类中(方法外),在堆内存,与对象共存亡。
- 局部变量使用前必须赋值,定义在方法中,在栈内存,与方法共存亡。
包
- 同一个包的类可直接互相调用
- 不同包的类调用时,需要先导入包,导包格式:import 包名.类名;
- 要导入多个包的程序,但这些程序名都相同,则只能导入一个包,其他的包要以完整包名进行访问。
//比如有两个类,所在包分别为tz.gml.yx和tz.gml.hm,类名均为demo,则导入时只能导入一个包,另一个包的类名需要加上包名进行访问。
import tz.gml.yx.demo; //导入tz.gml.yx包中的demo类
demo dyx = new demo(); //创建tz.gml.yx包下demo类的对象实例
tz.gml.hm.demo dhm = new tz.gml.hm.demo(); //创建tz.gml.hm包下demo类的对象实例string
创建字符串对象
String name = "csdn";String str = new String("字符串");字符类型数组传给string自动拼接成字符串,char chars[] = {'a','b','c'}; String str2 = new String(chars);- byte 类型数组自动转为字符,再拼接成字符串,如
byte bytes[] = {97,98,99}; String str3 = new String(bytes)打印出来是 abc
String 类方法:
//第一行均为jdk源码中的方法定义格式
//第二行是使用示例
public int length() //返回字符串长度
System.out.println(str.length());
public char charAt(int index) //返回指定索引位置的字符,索引从0开始
System.out.println(str.charAt(2));
public boolean contains(CharSequence s) //判断字符串是否包含了某个字符串
str4.contains("垃圾") //返回布尔值
public boolean startsWith(String prefix) //判断字符串是否以某个字符串开头
str4.startsWith("这个")
public String substring(int beginIndex) //返回从指定索引位置开始到末尾的新的字符串
System.out.println(str3.substring(5));
public String substring(int beginIndex, int endIndex) //返回从指定索引位置开始到指定索引位置结束的新的字符串
System.out.println(str3.substring(0, 5));
public String replace(char oldChar, char newChar) //用新值替换旧值并返回新的字符串
String str2 = str1.replace("垃圾", "**");
public String[] split(String regex) //把字符串按照某个字符内容分割成多个字符串,返回个字符串数组
String str4 = "这个,电影,真是个,垃圾";
String strarr[] = str4.split(",");
public boolean equals(Object anObject) //判断两个字符串是否相同
str1.equals(str2)
public boolean equalsIgnoreCase(String anotherString) //忽略大小写,判断俩字符串是否相同。
str1.equalsIgnoreCase(str2)
public char[] toCharArray() //将当前字符串转换成字符数组并返回
char chararr[] = str.toCharArray();注意事项(底层原理)
- String 是不可变对象,每次更改的本质是指向了新的字符串对象。
//只要是在""创建的字符串对象,都在堆内存的字符串常量池中存储
String str = "hello"; //把堆内存中hello这个字符串的地址赋给str,str指向这个字符串
str += "world"; //world这个字符串也放到常量池中,与hello拼接,产生新的字符串hello world放在堆内存中,然后把hello world这个字符串的地址赋给str,str指向这个新的字符串。
str += "of my"; //在堆内存的字符串常量池存放of my字符串,在让它与当前str的值拼接,再形成一个新的字符串,放到堆内存中,最终把这个地址赋给str变量。
System.out.println(str); //输出hello world of my- 字符串常量池中的字符串都是共享的,相同内容的字符串只有一份。更节约内存。
String str1 = "hello"; //堆内存字符串常量池创建hello,把地址赋给str1
String str2 = "hello"; //发现常量池有这个字符串,把地址赋给str2
System.out.println(str1 == str2); //输出true,str1和str2共享同一个内存地址- new 出来的字符串对象,每次都在堆内存中产生一个新的字符串对象。
char cs[] = {'a','b','c'};
String str1 = new String(cs); //每次都在堆内存创建一个新字符串对象
String str2 = new String(cs);
System.out.println(str1 == str2); //输出falseString str1 = new String("hello"); //这个实际上创建了两个对象,一个是常量池中的字符串"hello"对象,一个是new出来的堆里的字符串对象。但str1指向的是new出来的字符串对象。
String str2 = "hello" //这个实际上没有创建任何对象,因为他发现字符串常量池有hello,直接指向这个。String s1 = "abc";
String s2 = "a"+"b"+"c"; //由于Java的编译优化机制,直接转换成"abc"以提供性能。在对应的.class文件可以看到这个直接被转换成了"abc"
System.out.println(s1 == s2); //输出true
String s3 = s2 + "c"; //和变量运算出来的都是放在堆里面,而不是字符串常量池
System.out.println(s1 == s3); //输出falsestatic
作用: 可以修饰成员变量或成员方法,修饰后的变量或方法既可以通过类访问(推荐),也可以通过实例访问
静态成员变量(类变量)
注意事项
- 成员变量按照有无 static 修饰分为类变量和实例变量
- 类变量会被类的全部对象实例共享.实例变量是属于每个对象的.
- 用 类名.类变量 访问.
底层原理
//Student.java
static String name = "张三";
int age = 18;
//main.java
Student s1 = new Student(); //看到Student,把这个类加载到方法区,检测到这个类有static变量,加载到堆内存,初始值为null.在栈内存开辟s1变量,把堆内存s1的对象实体地址赋给s1,这个对象实体里只有age,没有name,对象实体存放有方法区中类模板的地址.
s1.name = "张三"; //通过栈内存s1找到堆内存的对象实体,通过对象实体内的地址找到方法区中类模板的地址,在通过类模板找到堆内存中static变量,把值赋给它.
Student s2 = new Student(); //再在堆内存创建一个学生对象实体,地址赋值给栈内存中的s2
s2.name = "李华"; // 最终指向的也是堆内存中的static String name.两个对象操作的是同一个变量.举个例子
public class User{
public static int number;
public User(){ //每当类创建一个User对象,都会默认调用一个无参构造函数,这样number就会加1,可以统计一共创建了多少对象.
number++;
// 或User.number++;
}
}静态成员方法(类方法)
- 访问方法
类名.类方法 - main 方法就是一种静态成员方法,可以通过类名直接调用.
- 类方法常用作工具类,每个类方法都是供开发人员完成一个功能的.比如 Java 的 Math 类.
- 比如在一个工具类中开发一个生成随机数的类方法,就可以用类名在其他地方调用,虽然对象方法也可以,但是要创建多个对象,占内存.
- 由于工具类不用创建对象,所以建议将工具类的构造器私有,防止别人给工具类创建对象.
- 类方法可以直接访问类成员,但不能访问对象成员
- 对象方法(即实例方法)可以直接访问类成员和对象成员
- 类方法禁止使用 this 关键字,因为 this 是用来拿到当前对象地址的,而类方法不属于任何对象.
//Myutil.java
public class Myutil{
private Myutil(){ //构造器私有,防止别人创建对象
}
//主函数文件main.java
Myutil myutil = new Myutil(); //报错,因为构造器私有,无法创建对象
Myutil.random(); //调用Myutil类中写好的类方法
}代码块
- 静态代码块
static{},类加载时自动执行,和类一样只会加载一次,用于完成对类变量的初始化
public class Student{
static String name;
static{
name = "张三";
}
}- 实例代码块,在
{}里面直接写,每次创建对象都执行一次,在构造器前执行.和构造器功能一样.实例代码块中定义的是不同对象一样的初始化内容。在类中方法外出现。 - 局部代码块 ,直接在方法中用{}括号括起来,局部代码块里的变量外面访问不了,有利于限制变量生命周期,及早释放。
单例设计模式
解释: 用于确保类只能创建一个对象,比如 java 的 Runtime(java 运行环境),或者 windows 的任务管理器,只需要一个对象即可,不能打开多个任务管理器. 步骤:
饿汉式单例:
拿到对象时,对象早就在类中创建好了
步骤:
- 把类的构造器私有
- 定义一个类变量记录类的一个对象
- 定义一个类方法,返回对象.
// A.java
public class A{
private static A a = new A(); //创建唯一一个对象
private A(){} //构造器私有
public static A getA(){ //类方法,让外部只能通过这个方法得到唯一一个对象.
return a; //无论调用多少次,都是返回的同一个对象a.
}
//主文件主函数main.java中调用
A a1 = A.getA(); //通过类方法获取对象
A a2 = A.getA(); //通过类方法获取对象
System.out.println(a1 == a2); //输出true
}懒汉式单例:
拿对象时,才开始创建对象
步骤:
- 把类的构造器私有
- 定义一个类变量用于将存储对象
- 提供类方法,确保返回的是同一个对象.
//B.java
public class B{
private static B b;
private B(){} //构造器私有
public static B getB(){ //类方法,让外部只能通过这个方法得到唯一一个对象.
if(b == null){ //如果对象为空,才创建对象
b = new B();
}
return b;
}
//main.java
B b1 = B.getB(); //通过类方法获取对象
}继承
tip:
- 当两个类有很多重复属性,可以让这两个类继承一个共同的父类,减少代码量
- 子类实际上获取了父类所有成员,包括似有, 但是没有权限访问私有成员。
- 在父类中生成 get 方法让子类访问私有变量,在定义个方法间接调用父类中的私有方法。因为子类也获取到了这两个方法,可以直接 this.调用,来访问父类私有成员。
- 一个子类只能继承一个父类,java 不支持多继承,但支持多层继承.如儿子继承爸爸,爸爸继承爷爷。
- 所有类都默认继承 object 类,可以调用 object 类的方法,比如 toString(),hashCode(),equals(),getClass(),clone(),finalize()等.即使是一个子类,由于他的父类默认继承了 object,那么这个子类也是他的孙子,也可以调用 object 方法.
继承格式:
public class son extends father{}
//son类是子类(派生类),father类是父类(基类)示例
//father.java 父类
public class father{
private int age;
private void print();
public void printit(){this.print()}; //间接调用私有方法,子类直接用这个printit就可以间接调用print了
public String getName() {return name;}
}
//son.java 子类
public class son extends father{
public void show(){
System.out.println(this.getage()); //调用get方法进行访问父类私有成员变量
this.printit();
}
}
//main.java
son s1 = new son(); //发现s1,检测到son,把son类加载到方法区,检测到父类,把父类也加载到方法区,然后在栈内存创建s1,在堆内存创建s1 son对象实体,通过对象实体里的地址找到方法区的son类,在通过son找到father.
s1.show();访问机制
在子类方法中访问其他成员(成员方法,成员变量),都遵循就近原则,先在子类局部范围找,再在子类成员范围中找,再在父类成员找,父类找不到就报错.
//主文件main.java
B b = new B();
b.printName();
//A.java文件
public class A {
String name = "A";
}
//B.java文件,是A的子类
public class B extends A {
String name = "B";
public void printName() {
String name = "子类B中的一个方法的变量";
System.out.println(name); // 子类B中的一个方法的变量
System.out.println(this.name); // B
// 访问同名的父类成员变量 用 super.属性/方法名
System.out.println(super.name); // A
}
}构造器特性
- 子类的全部构造器,每一个执行前都会先调用父类的构造器,再执行自己,因为在所有子类的构造器里都会默认加上
super();调用父类的无参构造器 - 子类构造器必须默认调用父类的构造器,否则报错。如果想让子类的构造器不调用父类的无参构造器,那么就要在父类手写一个有参构造器,然后在子类的构造器手写 super();方法,在里面加上对应的参数调用父类的有参构造器.
- 子类构造器可以调用父类构造器,把对象中父类这部分的数据先初始化.
//A.java 文件
public A() {
System.out.println("A");
}
//B.java 文件, A是B的父类
public B() {
//super(); //默认调用父类无参构造器
System.out.println("B");
}
public B(int a) {
//super(); //子类有参构造器也会默认调用父类无参构造器
System.out.println("B");
}
//main.java
B b = new B(); //先调用父类A的无参构造器,在调用B自己的无参构造
B b2 = new B(10);//先调用父类A的无参构造器,在调用B自己的有参构造
//最终打印出来的是 ABAB技巧提示
在同一个类中,一个构造器可以用 this()调用同一个类下其他的构造器
public Student(int age,String name){
this(age,name,"逻各斯中央大学");//调用兄弟构造器,这样都是同一个大学的了
//用了this就不能用super,因为下面这个兄弟构造器已经默认有了一个super,一个类不能有两个super.
}
public Student(int age,String name,String schoolName){
this.age = age;
this.XXX //省略
}权限修饰符
四种: public, private,protected,缺省
被修饰的类,方法,变量的访问规则
- public: 任意包下的任意类都可以访问被 public 修饰的东西
- private: 私有的,只能在本类中访问
- protected: 本类,同一个包下其他的类,任意包下的子类
- 缺省: 本类和同一个包下其他的类
方法重写
解释:
子类觉得父类某个方法不好,写一个方法名,参数列表一样的方法,覆盖父类这个方法,至于方法的访问,java 会遵循就近原则.
注意事项:
- 在上方写@Override 注解
@Override //表示重写的方法
public void show(){}- 子类重写父类方法时,重写后的方法的访问权限必须等于或大于父类该方法权限.
- 重写方法返回值类型,必须与被重写的方法一样或者更小
- 私有方法,静态方法不能被重写
多态:(对象多态/行为多态)
- 前提: 有继承关系,存在方法重写,存在父类引用子类对象
- 注意: 多态是对象或行为的多态,成员变量不存在多态,若父类和子类有同名成员变量,通过一个父类类型的子类对象,访问这个变量,输出的是父类的那个成员变量.
- 多态的好处:
- 右边创建的对象可以随时切换,而不用两边都切换,
People p1 = new Teacher(); - 如果一个方法用父类类型的对象作为形参,如
public void static go(People p)那么这个方法可以接受一切继承这个父类的子类对象.
- 右边创建的对象可以随时切换,而不用两边都切换,
例子
//People.java
public void run() {
System.out.println("人可以跑");
}
//Student.java
public class Student extends People {
// 重写父类的方法
@Override
public void run() {
System.out.println("学生跑得快");
}
}
//Teacher.java
public class Teacher extends People {
@Override
public void run() {
System.out.println("老师跑得很慢");
}
}
//main.java
People p1 = new Teacher(); // 对象多态
p1.run(); //行为多态 ,输出老师跑得很慢
People p2 = new Student();
p2.run(); //学生跑得快类型转换:
- 由于子类无法调用自己独有的方法,需要把父类类型的对象强制转换成子类.
- 自动类型转换 : 父类 对象名 = new 子类();
- 强制类型转换 : 子类 对象名 = (子类)父类对象
Teacher t = (Teacher)p转换后就可以调用子类自己独有的方法 - 兄弟类之间不能互转,编译后会报
ClassCastException,类型转换异常.因此强转前用 instanceof 关键字判断当前对象的真实类型,再强制转换.
if(p1 instanceof Student){
Student s2 = (Student) p1;
s2.s2独有方法();
}else{
Teacher t2 = (Teacher) t2;
t2.t2独有方法();
}final
修饰场景:
- 类 :此类无法继续被继承,常用于工具类
- 方法: 方法无法被重写
- 变量: 只被赋值一次,比如圆周率,就是常量
注意事项
- 用 final 修饰的静态成员变量,如 public static final String NAME = ""; 这时候必须赋值,否则报错.
- final 修饰基本类型变量无法被改变,修饰引用类型变量则地址不改变,但地址指向的内容可以被改变.
- 这个东西就是宏,程序用到的在编译的时候会自动进行宏替换.在编译后的.class 文件可以看到.
- 使用常量和字面量性能一样
- 常量格式: public static final 类型 CHANG_LIANG = ""; 常量建议首字母大写,用_下划线连接
抽象类
用途:可以修饰类,成员方法,每个子类要实现的功能不一样,定义抽象类让子类继承并重写父类的抽象方法,实现多态.
public abstract class A{
public abstract void printA(形参列表); //抽象方法不能有{方法体}
}
public class B extends A{
@Override //子类来重写父类的抽象方法
public void printA(形参列表){} //必须重写抽象方法
}注意事项
- 抽象类不一定要有抽象方法,有抽象方法一定是抽象类
- 抽象类也可以有成员变量/方法,构造器
- 最重要的是,抽象类不能创建对象,作为一种特殊的父类,让子类继承并实现.
- 一个类继承抽象类,必须重写完所有抽象类的抽象方法,否则这个类也必须定义成抽象类.
实际应用: 模板方法设计
//抽象类People定义一个模板方法,打印的一三两条语句相同,第二条要求灵活变动
//在第二条那个位置调用抽象方法,然后定义一个抽象方法交给Student和Teacher类重写,用来输出第二条不同的语句
//抽象类People
public final void print(){ //最好用final阻止模板方法被子类重写
sout("两个子类相同的第一个语句") //定义一个模板方法,把相同代码放进去
printdiff(); //调用抽象方法
sout("两个子类相同的第三个语句")
}
public abstract void printdiff();
//Student类,别忘了extends People
public void printdiff(){
@Override
sout("Student类独有的第二个语句")
}
//Teacher类,别忘了extends People
public void printdiff(){
@Override
sout("Teacher类独有的第二个语句")
}
//main.java
Teacher t = new Teacher();
t.print(); //中间那条语句打印出来的会是Teacher类里的接口
注意事项:
- 一个接口可以由多个类实现,一个类可以实现多个接口
- 接口不能创建对象
- 接口只能有常量和抽象方法
示例代码:
//接口A文件
public interface A {
String NAME = "A"; // 默认为常量
void test(); // 默认为抽象方法
}
//创建一个B类,实现接口A,B,C...多个接口
public class B implements A ,B ,C{
@Override
public void test() {实现接口A抽象方法}
}
//main主文件
A b = new B(); //改的时候直接把B改成其他类,因为A这个接口由多个类实现
b.test();接口方法
默认方法(jdk8) 在接口里面实现的默认方法,实现好了那么所有子类对象都可以直接调用这个方法,用 default 修饰,默认是 public
default void test1(){}私有方法(JDK9) 用 private 修饰, 只能在本接口的其他方法中被调用,用来封装一些细节
private void test2(){}静态方法(类方法) 要用接口名来调用,默认 public ,
static void test3(){}
接口多继承
- 接口继承
- 接口可以同时继承多个接口
public interface D extends C,B,A - 实现类要实现这些接口全部的抽象方法
- 接口可以同时继承多个接口
变量访问规则
- 注意事项:(下文所说冲突,比如方法名一样,但返回值不同)
- 如果一个接口要继承多个接口,但多个接口中存在方法名冲突,则此时不能多继承
- 一个类实现多个接口,但多个接口中存在方法名冲突,则此时不能多实现
- 一个类继承了父类,又同时实现了接口,此时如果父类和接口中存在同名的默认方法,则此时只能调用父类的方法
- 一个类实现了多个接口,多个接口存在同名默认方法,可以不冲突,让这个实现类重写该默认方法即可,也就是说这个类将只用自己的这个方法,而不是接口的
内部类
定义在一个类里面的类为内部类,比如汽车里面的发动机就可以写成一个内部类
内部类四种形式:
1. 成员内部类:类中的一个普通成员
public class Car{
private int age = 99;
public class Engine{ //内部类
public int age =88;
public void show(){
//内部类可以访问外部类的成员变量,即使是私有成员
System.out.println(age); //99
}
public void diffshow(){
int age = 66;
sout(age); //函数内部局部变量,66
sout(this.age); //88,Engine这个对象的成员变量
sout(Car.this.age); //99 //调用Car对象里的成员变量
}
}
}
//创建内部类对象格式: 先new外部类,再new内部类
//调用格式: 外部类.内部类,
Car.Engine e = new Car().new Engine();2. 静态内部类
由类持有, 可以访问外部类的静态成员,无法访问实例成员
public class Outer{
private int age = 99;
public static int a;
public static class inner{
public void test(){
sout("age") //报错,因为age是成员变量
sout("a")
}
}
}
//main
Outer.inner in = new Outer.inner();3. 匿名内部类
本质就是个子类,会自动立即创建子类对象 格式: new 类名/接口(参数值){重写抽象方法};
创建匿名内部类示例
//原本是需要单独写一个 cat 类用来继承 Animal 类并重写方法的
//main函数里定义个匿名内部类
Animal cat = new Animal(){ //他会直接创建一个Animal的子类对象
@Override //在这个子类里直接重写他的方法
public void jiao(){
sout("猫叫");
}
};
cat.jiao(); //调用这个重写后的方法简单应用
public static void main(String[] args) {
go(new Swimming() { //定义一个匿名内部类,自动创建对象后直接传递给go()函数作为参数
@Override
public void swim() {
System.out.println("狗开始游泳");
}
});
}
// 接收一切实现Swimming接口的对象
public static void go(Swimming s) {
System.out.println("游泳开始");
s.swim();
}
//定义接口和抽象方法
interface Swimming {
void swim();
}枚举类
优势:
- 提供一组固定常量,避免意外使用一个未定义的值
- 比常量和字符串更易读
- 可以封装行为
- 每个枚举常量都是一个实例,天然支持单例设计模式。
- 天然支持序列化
- 可以直接在 switch 语句中使用枚举
注意事项
- 记住,这些常量 X,Y,Z 的本质就是 A 这个类的的一个对象,而不仅仅是常量
- 第一行必须是列举枚举对象的名字,这些名称都是常量,每个常量记得都是枚举类的一个对象.
- 枚举类的构造器一定都是私有的,默认就是私有,不能创建对象
- 枚举类无法被继承
示例
public enum A {
// 第一行必须是列举枚举对象的名字,这些名称都是常量,每个常量记得都是枚举类的一个对象.
X, Y, Z; //实际就是枚举类A的对象实例
// .class文件里就是
// public static final X = new A();
private String name = "cdsc"; //可以定义变量与方法
}
//main主文件中调用
A a1 = A.X; //调用
System.out.println(a1); //X重写枚举类的抽象方法
public enum A {
X() {
@Override //在枚举对象中重写A这个类的go抽象方法
public void go() {
System.out.println("X");
}
},
Y("张三") { //向Y传入参数,触发有参构造函数
//相当于 public static final X = new A("张三");
@Override
public void go() {
System.out.println(getName()); //调用方法得到name
}
};
public abstract void go(); //定义一个抽象方法
A() {} //无参构造
A(String name) { // 有参构造器
this.name = name;
}
public String getName() {
return name;
}
}常用方法
A a1 = A.X; // 调用
A a2 = A.valueOf("Y"); // 根据枚举常量名得到Y这个枚举对象
A[] values = A.values(); //拿到全部对象 X,Y,Z
System.out.println(a1.name()); // 得到对象的名字,X
System.out.println(a1.ordinal()); // 得到X的索引,0简单应用
public enum Diff{
BOY,GIRL;
}
//main
check(Diff.BOY);
public static void check(Diff sex){
switch (sex){
case BOY:
sout("男");
break;
case GIRL:
sout("女");
break;
}
}泛型
举例:public class ArrayList<E>{}, ArrayList<E>就是种泛型,能约束数据类型,避免强制转换.泛型的本质是把具体数据类型传递给类型变量.
- 泛型运行在编译时,在.class 文件不存在,这是泛型擦除
- 泛型不支持基本数据类型,如 int,double,只支持对象类型.
//需要把基本数据类型换成对象类型,才能传入整数,浮点数等
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<Double> list1 = new ArrayList<>();泛型符号规范:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
S、U、V - 2nd、3rd、4th types自定义泛型类
修饰符 class 类名<类型变量,类型变量,...>{}
泛型类举例
//模拟ArrayList
public class MyArrayList<E> { //创建对象时<>里是啥,所有的E就全都替换成那个数据类型
private Object[] arr = new Object[10];
private int size;
public boolean add(E e) {
arr[size++] = e;
return true;
}
public E get(int index) {
return (E) arr[index];
}
}
//main
Fanxing<String> f = new Fanxing(); //<>里可以限制数据类型,String就会替换掉全部的E
f.add("fsd");
f.add("fsd");
System.out.println(f.get(1));多个泛型参数
//多个泛型参数
public class Myclass<E,T>{
public void input(E e,T t){} //传入多个参数
}
//main
Myclass<String ,int> c2 = new Myclass<>(); //传入对应的两个数据类型
c2.input("fsd",123); //调用函数时也是限制泛型类必须继承一个类
public class Myclass3<E extends Animal>{},要求传入的数据类型必须继承Animal类.自定义泛型接口
修饰符 interface 接口名<类型变量,类型变量 ,...>{}
public interface Data<T>{
void add(T t); //这样重写这个接口的类就可以传入自己的对象的类型了
ArrayList<T> getByName(String name);
}
//实现这个接口
public class Student implements Data<Student>{重写方法}
public class Student implements Data<Teacher>{重写方法}
public interface Data<T extends Animal> //如果接口定义的类型是继承了某个类,那么其他实现这个接口的类就应该传入Animal的对象或者他的子类对象自定义泛型方法
修饰符<类型变量,类型变量,..> 返回值 方法名(形参列表){}
public static <T extends Car> T test(T t){
return t;
}
//所有Car的子类都可以传入通配符
public static <T extends Car> void go(ArrayList<T> cars){}
//改写成
public static void (ArrayList<? extend Car> cars)
// ?是通配符,代表可以传入一切类型,extends Car,则只能传入Car子类
//像这样限制传入参数的上限的称为泛型上限
public static void go(ArrayList<? super Car> cars)
// 只能传入Car或Car父类,