前言
最近荔枝打算再系统学习和梳理一下Java后端开发的相关知识,之前也大致了解过行业招聘要求,其实学习Java后端其实路线很多而且时间很长要求也很高,但基本上都需要从Javase开始学起,然后学J2EE、spring、SSM框架开发,学完后才能学到SpringBoot,当然了如果涉及微服务的话就再加上一个Spring Cloud。除了这些,还需要深入学习数据库比如MySQL,学习一些中间件如redis、kafka等以及各自在实际开发中的使用。在求职的时候其实有些公司对分布式有要求,所以可能还得学k8s、Docker。感觉光是理论知识的学习就需要大半年了哈哈哈,Java后端实在是太卷了。。。在这篇文章中,荔枝就着重梳理一下有关于Javase的知识吧,系统再学一遍。
文章目录
一、包、类和对象
1.1 基本概念
包
为了更好的组织类,Java提供了包机制,用以创建新的命名空间并加以区别类名的命名空间。一般可以通过package ...来声明一个包的创建,import ...来导入包。
类和对象
Java是一门面向对象的语言,对象是类的一个实例,有着独特的属性和行为;类则是一个模板,代表着一类对象的共有特性。在使用类的时候我们需要对类进行实例化并得到一个实例化对象。每个类的内部都有构造方法,如果没有显式定义类构造方法,Java编译器会自动为类隐式定义构造方法。
//类的构造方法
class Test{
//无参构造方法
public Test(){
}
//有参构造方法
public Test(int id){
}
}
public class TestRun{
//实例化对象并调用其有参构造方法
public static void main(String[] args){
Test test = new Test(1);
}
}
1.2 面向对象三大特性
1.2.1 封装性
面向对象中的封装是指将抽象函式接口的实现细节进行封装隐藏,可以是做是在外围多了一层保护膜,要访问该类的话必须要通过一个严格的接口控制,防止该类的代码和数据被外部类定义的代码随机访问。具体的封装实现是对类中的属性进行private关键字修饰,并在对外提供的方法中可能会放开对公共属性的访问和修改。
//类的构造方法
public class Test{
//私有属性
private String name;
private int id;
//对外访问接口
public setName(name){
this.name = name;
}
}
1.2.2 继承性
继承关系是面向对象中的一个最为重要的特性,通过继承和重写方法使得我们的类可以实现功能的延拓。继承的过程主要有几种:子类继承父类、子类继承接口并重写。我们首先来看看子类继承父类并重写父类方法的代码示例。
class Animal{
int age;
String name;
public void setName(String name){
this.name = name;
System.out.println(name);
}
public void move(){
System.out.println("可以移动");
}
}
//继承示例
class Pandas extends Animal{
public void move(){
super.move(); // 应用super类的方法
System.out.println("可以跑和走");
}
public void bark(){
System.out.println("咆哮了");
}
public void myName(String name){
super.setName(name);
System.out.println("I want to paly with "+ name);
}
}
public class test{
public static void main(String args[]){
Pandas b = new Pandas(); // Pandas 对象
b.move(); //执行 Pandas类的方法
b.bark();
b.myName("花花");
}
}
可以看到上面的子类Pandas继承并对myName方法进行重写,同时在myName使用super()继承父类的setName方法并对重写该方法。在Java中子类使用extends关键字后会默认调用父类的非参构造器,但若是想要操作父类的有参构造器或者调用父类被重写方法和属性时必须使用super(),而关键字this可以操作当前类的方法。接着看看继承多个接口类时的代码示例:
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
需要注意的是Java中的继承是不能一个子类继承多个类的,Java中支持单继承和多重继承,但是使用extends是不支持同时继承两个类的。但是,对于接口的继承和重写来说,通过implements关键字却可以实现多继承。
1.2.3 多态性
多态性是指一个模板类通过子类继承的方式使得实例化对象有着多种表现形式。多态的使用可以消除类型之间的耦合关系,同时也使得对象的使用更加灵活。相比起继承,多态相对来说更加抽象,其实堕胎的过程就是一个向上转型的过程,即父类引用指向子类实例对象。对于子类来说,向上转型中,执行了方法重写后,如果不使用super方法的话调用的是子类自己的重写方法。
我们再用上面的例子来改写多态:
class Animal{
int age;
String name;
public void setName(String name){
this.name = name;
System.out.println(name);
}
public void move(){
System.out.println("可以移动");
}
}
//继承示例
class Pandas extends Animal{
public void move(){
super.move(); // 应用super类的方法
System.out.println("可以跑和走");
}
public void bark(){
System.out.println("咆哮了");
}
public void myName(String name){
super.setName(name);
System.out.println("I want to paly with "+ name);
}
}
public class test{
public static void main(String args[]){
Animal b = new Pandas(); // Pandas 对象
b.move();//可以移动、可以跑和走
b.bark();//会报错
}
}
多态存在的三个必要条件:继承、重写、父类引用指向子类对象。
1.3 组合和继承的区分
组合和继承是不同的,组合指的是在一个类中使用另一个类的实例对象,是一种包括的关系;另外一种是继承重写的关系。使用组合的关系能够降低类之间的耦合性。
// Person类
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// Employee类使用Person类的对象作为成员变量,实现组合关系
class Employee {
private Person person;
private String employeeId;
public Employee(String name, String employeeId) {
this.person = new Person(name);
this.employeeId = employeeId;
}
public String getName() {
return person.getName();
}
public String getEmployeeId() {
return employeeId;
}
}
1.4 向上转型和向下转型
向上转型(Upcasting)和向下转型(Downcasting)是Java中多态性的一种体现,它们用于处理对象之间的类型转换。
向上转型:
- 向上转型是将一个子类对象转换为父类类型的过程。也就是说,将子类对象赋值给父类引用变量。
- 向上转型是自动完成的,不需要进行显式的类型转换。
- 向上转型发生在类的继承关系中,它允许子类对象被视为父类对象,只能访问父类中定义的成员。
- 向上转型是安全的,因为子类对象包含了父类的所有特性。
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
public void makeSound() {
System.out.println("狗发出汪汪声");
}
public void fetch() {
System.out.println("狗在玩飞盘");
}
}
public class Main {
public static void main(String[] args) {
// 向上转型
Animal animal = new Dog();
animal.makeSound(); // 输出:"狗发出汪汪声"
// animal.fetch(); // 编译错误,不能访问子类特有的方法
}
}
向下转型:
- 向下转型是将一个父类对象转换为子类类型的过程。也就是说,将父类对象赋值给子类引用变量。
- 向下转型需要进行显式的类型转换,因为编译器不能确定父类对象是否真的是子类对象。
- 向下转型发生在类的继承关系中,它允许父类对象重新被视为子类对象,可以访问子类中定义的特有成员。
- 向下转型是不安全的,如果在运行时出现了不匹配的对象类型,会导致
ClassCastException
异常。
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
public void makeSound() {
System.out.println("狗发出汪汪声");
}
public void fetch() {
System.out.println("狗在玩飞盘");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型
// 向下转型,需要进行显式的类型转换
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch(); // 输出:"狗在玩飞盘"
} else {
System.out.println("对象不是Dog类型");
}
}
}
1.5 抽象类和接口
抽象类(一般用abstract来声明)除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。介绍到这里大家是不是感觉抽象类和接口有点类似?其实二者很类似,接口也是一种抽象类型不能直接被实例化,但是相比之下抽象类只能被一个子类继承并且该子类只能继承一个抽象类;而子类却可以同时继承多个接口,这就是二者最大区别,同时抽象类中可以允许有方法体,但是接口中不被允许出现实现方法的具体功能;抽象类中的成员变量可以是各种类型的但是接口的成员变量就只能是public static final类型。
方法重写和方法重载的区别
方法重写主要发生在子类继承父类的时候使用super对父类构造方法进行继承并对方法进行重写;而方法重载是指在一个类对象中对同一命名的构造方法有不同参数对应的处理方法体。在调用的时候主要是根据传入的参数的类型来判断不同的构造方法。
1.6 枚举类enum
Java中为我们给出了枚举类这种特殊的类,通常表示一组常量比如像颜色的枚举、地点的枚举等等。枚举类既可以在类的外部使用,也可以在类的内部使用。enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。values(), ordinal() 和 valueOf() 方法位于 java.lang.Enum类中
- values() 返回枚举类中所有的值。
- ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
- valueOf()方法返回指定字符串值的枚举常量。
enum Color
{
RED, GREEN, BLUE;
}
public class MyClass {
public static void main(String[] args) {
for (Color myVar : Color.values()) {
System.out.println(myVar);
}
}
}
1.7 内部类
Java中的内部类(Inner Class)是指一个类定义在另一个类的内部。它允许在一个类中定义另一个类,使得这两个类具有一定的关联关系。内部类的定义在外部类的大括号内,可以访问外部类的成员,包括私有成员,而外部类也可以访问内部类的成员。
内部类的分类
- 成员内部类(Member Inner Class):成员内部类是定义在外部类的成员位置(字段、方法)的类。它持有一个对外部类的引用,可以直接访问外部类的成员。
- 静态内部类(Static Inner Class):静态内部类是定义在外部类的成员位置,但使用
static
修饰的类。它与外部类的实例无关,可以直接访问外部类的静态成员,但不能直接访问非静态成员。 - 局部内部类(Local Inner Class):局部内部类是定义在方法内部的类。它只在所在方法中有效,不能有访问修饰符,也不能使用
static
修饰。 - 匿名内部类(Anonymous Inner Class):匿名内部类是没有名字的内部类,它是一种简化的内部类形式。通常在创建接口或抽象类的对象时使用,直接定义并实例化一个没有类名的内部类对象。
主要特点和用途
-
内部类提供了更好的封装性和隐藏性,可以将相关的类组织在一起,使代码结构更加清晰。
-
内部类可以直接访问外部类的私有成员,可以用来实现一些辅助功能。
-
成员内部类和静态内部类可以用来实现多重继承的效果,一个类可以拥有多个内部类。
-
局部内部类和匿名内部类通常用于简化代码,避免定义额外的类。
public class OuterClass {
private int outerField;
public void outerMethod() {
System.out.println("Outer method");
}
// 成员内部类
public class InnerClass {
public void innerMethod() {
outerField = 10; // 可以访问外部类的成员
outerMethod(); // 可以调用外部类的方法
System.out.println("Inner method");
}
}
// 静态内部类
public static class StaticInnerClass {
public void staticInnerMethod() {
System.out.println("Static inner method");
}
}
}
二、泛型和泛型类
在Java中,泛型(Generics)是一种编程机制仅在编译阶段使用,允许在类、接口和方法的定义中使用类型参数,以便在使用时指定具体的类型。泛型的引入使得代码更加通用、安全和可读,同时避免了类型转换的麻烦。
2.1 主要特性和用途
-
类型参数:使用尖括号< >来定义类型参数,可以在类、接口或方法的定义中声明泛型类型。
-
类型安全:通过使用泛型,编译器在编译时会进行类型检查,确保只有兼容的类型被传入泛型类或方法,从而避免运行时的类型转换异常。
-
代码复用:泛型允许编写通用的代码,可以在不同的数据类型上重复使用相同的代码逻辑。
-
避免强制类型转换:使用泛型可以避免在代码中频繁进行强制类型转换,使代码更简洁。
2.2 泛型常见使用场景
- 集合类:Java的集合框架中的
List
、Set
、Map
等接口和实现类都使用了泛型来定义容器中存储的元素类型,使得集合类能够在编译时进行类型检查,避免了在运行时出现类型转换异常。 - 泛型约束:在泛型中可以使用通配符和限定来对类型进行约束,例如使用
extends
关键字限定 - 泛型类型必须是某个类的子类,或者使用
super
关键字限定泛型类型必须是某个类的父类。泛型类继承:可以在泛型类中使用继承关系,实现一个泛型类继承另一个泛型类,以提供更多的功能和灵活性。
泛型通配符:在需要处理未知类型的情况下,可以使用通配符<?>来表示不确定的类型,从而实现更通用的代码。
2.3 泛型类
// 定义一个泛型类
class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class Main {
public static void main(String[] args) {
// 创建一个存储整数的Box对象
Box<Integer> intBox = new Box<>(10);
int value = intBox.getValue();
System.out.println("Value: " + value);
// 创建一个存储字符串的Box对象
Box<String> strBox = new Box<>("Hello, Generics!");
String message = strBox.getValue();
System.out.println("Message: " + message);
}
}
如果写成如下形式实例化对象的时候传入的参数和得到的参数都是一个Object对象。
Box b = new Box();
总的来说,泛型是Java中一种强大的特性,通过类型参数化实现了通用编程,使代码更灵活、类型安全,并提高代码的可读性和复用性。
2.4 泛型接口
定义一个泛型接口
interface IFF<T>{
T test(T t);
}
定义一个接口实现类
class B<T> implements IFF<T>{
@Override
public T test(T t){
return null;
}
}
未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中。如果实现接口时指定接口的泛型的具体数据类型,这个类实现接口所有方法的位置都要泛型替换实际的具体数据类型
public class Testl{
public static void main(String[]args){
Bl<Object> bl = new Bl<Object>();
Bl<String> b2 = new Bl<String>();
B2 b3 = new B2();
}
}
//未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
class B1<T> implements IFF<T>{...}
//如果实现接口时指定接口的泛型的具体数据类型,这个类实现接口所有方法的位置都要泛型替换实际的具体数据类型
class B2 implements IFF<String>{
@overwide
public String test(String t){
return null;
}
}
2.5 泛型方法
class C1<E>{
private E e;
//无返回值的泛型方法
public <T> void test(T s){
T t = s;
//在类上定义的泛型,可以在普通的方法中使用,但是在静态的方法中不能使用类的泛型。
System.out.println(this.e);
}
//有返回值的泛型方法
public <T> T test1(T s){
return s;
}
//可变参数类型的泛型方法
public <T> void test2(T... strs){
for(T s : strs){
System.out.println(s);
}
}
}
需要注意的是:如果泛型方法在调用之前没有固定的数据类型,那么在调用时传入参数是什么类型,就会把泛型改成什么类型,也就是说泛型方法会在调用时确定泛型具体的数据类型。
2.6 泛型通配符
class Dd(
//test方法需要一个1ist集合的参数,不确定1i3t集合到底是存的数的类型是什么
public void test(List<?> 1ist){}
//1i3t参数的元素数据类型是c1及其子类
public void test1(List<? extends c1> 1ist){}
//1ist参数的元素数据类型是c1及其父类
public void test2(List<? super C1> 1ist){}
}
总结
在这篇文章中,荔枝主要把Java中有关类和对象、泛型和泛型类的知识梳理了一下,有些地方感觉文字讲不清楚荔枝也用简单的demo来示例辅助理解,希望能帮助到有需要的小伙伴。不知道现在复习的知识什么时候能用上,什么时候被遗忘,总之尽量保证记得清楚一点吧哈哈哈。最近荔枝确实有点懈怠了,大家不要像荔枝一样噢~~~
今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~ 比心心♥~~~