侧边栏壁纸
“技术分享” 共(27)篇
  • Vue 3 中 Watch 侦听器的深入讲解:Immediate 和 Deep 参数 Vue 3 的组合式 API 为我们提供了强大的响应式编程能力,其中 watch 侦听器是一个关键的部分。它允许我们对响应式数据进行更细粒度的观察。在使用 watch 时,我们经常会用到两个非常有用的配置参数:immediate 和 deep。Watch 侦听器的基本用法在介绍 immediate 和 deep 之前,让我们先回顾一下 watch 侦听器的基本用法:import { ref, watch } from 'vue'; export default { setup() { const count = ref(0); watch(count, (newValue, oldValue) => { console.log(`计数器的值从 ${oldValue} 变为 ${newValue}`); }); // ...其他逻辑 } }Immediate 参数immediate 参数用于控制 watch 侦听器是否在初次执行时立即触发回调函数。不使用 Immediate默认情况下,watch 侦听器在创建时不会立即执行回调函数,它只会在侦听的响应式引用发生变化时被触发。使用 Immediate如果我们想要在 watch 创建的同时立即执行一次回调,可以设置 immediate: true。watch(count, (newValue, oldValue) => { // 即使 count 没有变化,这里也会立即执行,并且 oldValue 为 undefined console.log(`计数器的值从 ${oldValue} 变为 ${newValue}`); }, { immediate: true });在开发中,immediate 参数特别有用,例如,当我们需要基于当前的响应式状态立即运行副作用时。Deep 参数deep 参数用于指定 watch 侦听器是否需要深度观察对象内部值的变化。不使用 Deep默认情况下,如果我们的响应式数据是一个对象,watch 只能检测到对象的第一层属性的变化。使用 Deep如果我们需要侦听一个对象内部深层属性的变化,我们可以设置 deep: true。const userProfile = ref({ name: '张三', preferences: { theme: 'dark' } }); watch(userProfile, (newValue, oldValue) => { // 当 userProfile 内部的任何属性改变时,这里都会执行 console.log('用户配置发生变化'); }, { deep: true });在实际应用中,使用 deep 参数可以帮助我们捕捉到嵌套对象的变化,但同时也要注意它可能会引起的性能问题,因为它会监听所有内部属性的变化。总结在 Vue 3 中,watch 侦听器通过 immediate 和 deep 参数提供了高度灵活的配置选项,使我们能够根据具体的场景灵活地处理数据变化。immediate 参数在需要立即反应当前状态时非常有用,而 deep 参数允许我们深入到响应式对象的内部,监视其嵌套属性的变动。恰当地使用这些特性,可以大大增强我们应用的响应能力和用户体验。
    • 1年前
    • 678
    • 0
    • 2
  • 使用Spring Boot实现在线聊天功能 在线聊天功能对于实时通讯的应用程序是必不可少的。在本文中,我们将详细探讨如何使用Spring Boot来实现一个简单的在线聊天接口。技术栈Spring Boot: 用于创建MVC架构的Web应用程序。WebSocket: 提供客户端和服务端之间的双向通信。第一步:创建Spring Boot项目首先,使用Spring Initializr (start.spring.io)创建一个新的Spring Boot项目,添加spring-boot-starter-websocket作为依赖。第二步:配置WebSocket在Spring Boot中配置WebSocket,需要创建一个配置类,如下:import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.*; @EnableWebSocketMessageBroker @Configuration public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 注册一个WebSocket端点,客户端将使用它进行连接 registry.addEndpoint("/chat").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 配置一个简单的消息代理,它将用于将消息从服务器路由到客户端 registry.enableSimpleBroker("/topic"); registry.setApplicationDestinationPrefixes("/app"); } }这里,/chat是连接端点,客户端通过这个URL来建立WebSocket连接。/topic用于定义目的地前缀,客户端将订阅这个路径以接收消息。第三步:创建消息模型创建一个简单的消息模型来承载聊天数据。public class ChatMessage { private String from; private String text; // 构造函数、Getter和Setter }第四步:创建消息处理器定义一个控制器来处理发送到特定路径的消息。import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; @Controller public class ChatController { @MessageMapping("/sendMessage") @SendTo("/topic/public") public ChatMessage sendMessage(ChatMessage chatMessage) { return chatMessage; } }这里的sendMessage方法会处理发送到/app/sendMessage的消息,并将其路由到/topic/public,所有订阅了这个路径的客户端都会收到消息。第五步:前端实现在前端,你可以使用JavaScript和HTML来实现客户端逻辑。<!DOCTYPE html> <html> <head> <title>Chat Room</title> <script src="https://cdn.jsdelivr.net/npm/sockjs-client/dist/sockjs.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/stomp-websocket/lib/stomp.min.js"></script> </head> <body> <div> <input type="text" id="user" placeholder="Enter your name"/> <input type="text" id="messageInput" placeholder="Type a message..."/> <button onclick="sendMessage()">Send</button> </div> <ul id="messageArea"> </ul> <script type="text/javascript"> var stompClient = null; var user = document.querySelector('#user'); var messageInput = document.querySelector('#messageInput'); var messageArea = document.querySelector('#messageArea'); function connect() { var socket = new SockJS('/chat'); stompClient = Stomp.over(socket); stompClient.connect({}, onConnected, onError); } function onConnected() { stompClient.subscribe('/topic/public', onMessageReceived); } function onError(error) { // 处理连接错误 } function sendMessage() { var messageContent = messageInput.value.trim(); if(messageContent && stompClient) { var chatMessage = { from: user.value, text: messageContent }; stompClient.send("/app/sendMessage", {}, JSON.stringify(chatMessage)); messageInput.value = ''; } } function onMessageReceived(payload) { var message = JSON.parse(payload.body); var messageElement = document.createElement('li'); messageElement.innerText = message.from + ': ' + message.text; messageArea.appendChild(messageElement); } connect(); </script> </body> </html>第六步:运行和测试启动Spring Boot应用程序,并在浏览器中打开HTML页面,尝试发送和接收消息。总结以上步骤展示了如何使用Spring Boot和WebSocket实现在线聊天功能。这只是基础实现,生产环境中你还需要考虑安全、错误处理和消息历史等功能。
    • 1年前
    • 412
    • 2
    • 2
  • Vue 3中的选项式API与组合式API对比详解 Vue 3不仅带来了性能的提升,还引入了一个新的组合式API,旨在解决选项式API在复杂组件中遇到的问题。下面,我们将通过具体示例,对比和分析两种API的使用差异,并探讨组合式API的设计理念。数据声明选项式API在选项式API中,我们通过data来声明组件的响应式状态。<script> export default { data() { return { count: 0 }; } }; </script>选项式API通过一个集中的data函数返回所有状态,便于快速查找和管理。组合式API组合式API利用ref或reactive来声明响应式状态。<script setup> import { ref } from 'vue'; const count = ref(0); </script>在组合式API中,<script setup>提供了一种更简洁的方式来定义组件的逻辑。通过ref或reactive,我们可以将状态的声明更紧密地关联到它们被使用的地方,这使得逻辑更加模块化和可重用。方法选项式API选项式API中,方法被定义在methods选项内。<script> export default { methods: { increment() { this.count++; } } }; </script>这种方式使得方法能够通过this访问组件的状态和其他方法,所有方法都被组织在一起。组合式API在组合式API中,方法被定义为局部函数。<script setup> const increment = () => { count.value++; }; </script>使用组合式API时,方法可以直接定义在<script setup>中,更接近原生JavaScript的函数定义,这样做提高了函数的可测试性和可重用性。通过减少对this的依赖,方法的独立性得到加强,也便于与其他逻辑片段共享。计算属性选项式API选项式API中,计算属性被定义在computed选项里。<script> export default { data() { return { firstName: 'John', lastName: 'Doe' }; }, computed: { fullName() { return `${this.firstName} ${this.lastName}`; } } }; </script>通过定义在computed中,Vue为每个计算属性创建了一个响应式的getter,使得它们在依赖变更时自动更新。组合式API在组合式API中,计算属性通过computed函数创建。<script setup> import { ref, computed } from 'vue'; const firstName = ref('John'); const lastName = ref('Doe'); const fullName = computed(() => `${firstName.value} ${lastName.value}`); </script>这里,computed函数使得定义计算属性更加直观,也更容易控制依赖。组合式API允许开发者在逻辑相关的代码附近声明计算属性,提高了代码的可读性。侦听器选项式API选项式API中使用watch选项。<script> export default { watch: { count(newValue) { console.log(`count变化了,现在是${newValue}`); } } }; </script>watch选项允许我们定义一个侦听器,当指定的数据变化时执行回调。组合式API组合式API中使用watch函数。<script setup> import { watch } from 'vue'; watch(count, (newValue) => { console.log(`count变化了,现在是${newValue}`); }); </script>与选项式API不同,watch函数在组合式API中被定义为顶级的API,这使得它更易于从组件逻辑中分离和重用。watch函数的使用提供了更多控制,并且由于是基于函数的,它可以很容易地组合其他响应式状态和逻辑。生命周期钩子选项式API选项式API直接将生命周期钩子作为组件选项。<script> export default { mounted() { console.log('组件挂载完成'); } }; </script>生命周期钩子作为选项,易于识别,但是在处理多个钩子时,代码会分散到不同位置。组合式API组合式API中使用特定的钩子函数。<script setup> import { onMounted } from 'vue'; onMounted(() => { console.log('组件挂载完成'); }); </script>在组合式API中,生命周期钩子被作为函数导入,这样不仅代码更加一致,也更容易将相关的生命周期钩子和逻辑组织在一起,有助于提升代码的清晰度和模块性。
    • 1年前
    • 438
    • 0
    • 0
  • 深入解析ES6:从var到let/const,再到箭头函数 随着ECMAScript 6(ES6)的到来,JavaScript语言引入了许多改革性的新特性,这些新特性不仅使得代码更加简洁,还解决了一些长期存在的问题。在本文中,我们将详细探讨其中的两个显著特性:let/const声明和箭头函数,以及它们是如何改善JavaScript编程的。let和const:新时代的变量声明ES5中仅有var在ES5之前,我们声明变量的唯一方法是使用var。这种方式存在变量提升等问题,容易导致代码中出现错误。var price = 49.99; var price = 29.99; // 重复声明,不会报错 console.log(price); // 输出:29.99ES6带来的变革:let和constES6引入了let和const来优化变量声明。let允许你声明一个作用域受限的变量,而不像var那样经常造成意外的全局变量。let price = 49.99; price = 29.99; // 正常运作 console.log(price); // 输出:29.99const更进一步,它允许你声明一个值不可变的常量。const RATE = 0.1; RATE = 0.2; // TypeError: Assignment to constant variable.这种声明方式确保变量值的不变性,避免了程序中可能出现的错误。箭头函数与this的新维度JavaScript中的this关键字是一个复杂且容易引起混淆的概念。它的值通常取决于函数调用的上下文,这经常导致意外的结果。ES5的this困扰function Team() { this.members = ['John', 'Jane']; this.addMember = function(name) { this.members.push(name); }; this.showMembers = function() { this.members.forEach(function(member) { console.log(this.members); // this指向错误 }); }; } var team = new Team(); team.addMember('Smith'); team.showMembers(); // TypeError or unexpected behaviorES6箭头函数的清晰thisES6的箭头函数提供了一种清晰和简洁的方式来维护this的上下文。function Team() { this.members = ['John', 'Jane']; this.addMember = function(name) { this.members.push(name); }; this.showMembers = () => { this.members.forEach((member) => { console.log(this.members); // this现在指向Team对象 }); }; } var team = new Team(); team.addMember('Smith'); team.showMembers(); // 正确显示成员列表箭头函数没有自己的this,它会捕获其所在上下文的this值,使得在回调函数和方法中的this行为变得可预测和一致。总结let和const的引入,以及箭头函数中的this的改进,是ES6中对JavaScript进行现代化改进的重要步骤。这些新特性不仅使得代码更加可靠和易于维护,也让开发者能够写出更加清晰和简洁的代码。随着ES6特性的广泛采用,JavaScript的强大和易用性正在持续提升,对开发者来说,掌握这些新特性是非常重要的。
    • 1年前
    • 468
    • 0
    • 1
  • 深入浅出:SQL Server数据库设计的三范式 在数据库设计领域,范式(Normalization)是一组关于表结构设计的规则,主要用来避免数据冗余和维护数据的完整性。SQL Server数据库也不例外,它广泛应用三个基本的范式规则来设计数据库。下面,我们将详细介绍这三个范式,并通过实例来帮助大家更好地理解。一、第一范式(1NF):原子性列第一范式是设计数据库的基础,它要求表中的所有列都要有原子性,即列的值不可再分。这意味着数据库表的每一列都应该是不可再分的最小数据单位。例子说明:不符合1NF的订单表:订单编号客户姓名订单商品1张三电脑, 手机2李四电视符合1NF的订单表:订单编号客户姓名商品名称1张三电脑1张三手机2李四电视二、第二范式(2NF):完全函数依赖在满足第一范式的基础上,第二范式要求表中的所有非主属性必须完全依赖于主键。如果存在复合主键,那么非主键的列必须依赖于所有的主键列,而不是部分。例子说明:不符合2NF的订单表(已符合1NF):订单编号商品编号客户姓名商品名称商品价格1001张三电脑50001002张三手机30002003李四电视2500分解后符合2NF的订单商品表和客户表:订单商品表:订单编号商品编号商品名称商品价格1001电脑50001002手机30002003电视2500客户表:订单编号客户姓名1张三2李四三、第三范式(3NF):消除传递依赖第三范式要求在第二范式的基础上,表中的非主键列不能依赖于其他非主键列,即不存在传递依赖。例子说明:不符合3NF的订单客户表(已符合2NF):订单编号客户编号客户姓名客户积分等级11001张三VIP21002李四普通分解后符合3NF的订单表和客户表:订单表:订单编号客户编号1100121002客户信息表:客户编号客户姓名客户积分等级1001张三VIP1002李四普通总结:通过对表结构进行规范化设计,我们可以有效地减少数据冗余,避免数据更新异常,以及提升数据的一致性。在实际的数据库设计过程中,数据库设计师应当根据实际需求,权衡范式的应用与数据库性能之间的关系,做出合理的设计选择。
    • 1年前
    • 498
    • 0
    • 2
  • SpringBoot中获取yml文件数据信息 在Spring Boot中,获取YAML(.yml或.yaml)配置文件中的数据是一个简单而直接的过程。Spring Boot使用了一个非常强大的库Spring Framework,它提供了@Value注解和ConfigurationProperties绑定来实现这个功能。首先,假设我们有一个application.yml文件,内容如下:myapp: name: My Spring Boot App description: This is a sample Spring Boot application. metadata: version: 1.0.0 author: Jane Doe在Spring Boot应用程序中获取这些配置的步骤如下:1. 使用@Value注解@Value注解可以直接在字段上使用,来获取配置文件中的相应值。import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class MyAppProperties { @Value("${myapp.name}") private String name; @Value("${myapp.description}") private String description; // 获取嵌套属性 @Value("${myapp.metadata.version}") private String version; // ... getters and setters }2. 使用ConfigurationProperties绑定另一种方式是创建一个配置属性类,使用@ConfigurationProperties注解,它会自动映射配置文件中的属性到同名的Java字段。import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration @ConfigurationProperties(prefix = "myapp") public class MyAppConfig { private String name; private String description; private Metadata metadata; public static class Metadata { private String version; private String author; // ... getters and setters } // ... getters and setters }然后,你可以在需要的地方注入MyAppConfig类的实例来使用这些配置值。3. @PropertySource与YAML需要注意的是,如果你想要使用@PropertySource注解来指定配置文件的路径,这个注解并不支持YAML格式的文件,它仅仅支持.properties文件。所以,在使用YAML的时候,这个注解通常是不需要的,因为Spring Boot会自动加载application.yml文件。4. 激活特定的配置文件Spring Boot允许你根据环境激活特定的配置文件,比如application-dev.yml或application-prod.yml。你可以通过设置spring.profiles.active属性来激活特定的配置文件。spring: profiles: active: dev或者在运行应用程序时通过命令行参数激活:java -jar myapp.jar --spring.profiles.active=prod这样,Spring Boot会加载相应的application-{profile}.yml文件,并覆盖默认的配置值。结语通过以上步骤,你可以轻松地在Spring Boot应用程序中读取和使用YAML配置文件中的数据。这个功能强大而灵活,非常适合现代应用程序的配置管理需求。记得在实际的编码过程中,你还需要添加相关的依赖项,如spring-boot-configuration-processor,来享受更完善的功能,例如配置属性的自动补全和文档生成。
    • 1年前
    • 414
    • 0
    • 2
  • Spring MVC Web项目配置指南 引言在Java企业级开发中,Spring MVC框架因其强大的功能、灵活的配置方式以及良好的扩展性而被广泛使用。配置Spring MVC通常有两种方式:一种是传统的基于XML的配置,另一种是基于注解和配置类的方式。本文将详细介绍这两种配置方式。传统XML配置方式web.xml配置在基于XML的配置中,web.xml扮演着重要的角色。它是Java EE的标准部署描述文件,用于配置Servlet、Filter等。DispatcherServlet配置 首先,我们需要在web.xml中配置DispatcherServlet,这是Spring MVC框架的核心。通过它来将请求分发到不同的处理器。<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>ContextLoaderListener配置 接着配置ContextLoaderListener,它用于加载Spring的根应用上下文。<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>springmvc.xml配置springmvc.xml是Spring MVC的核心配置文件,在这里我们配置了组件扫描、视图解析器等。组件扫描 首先要配置组件扫描,这样Spring才能找到我们定义的Controller。<context:component-scan base-package="com.example.controller" />视图解析器配置 然后配置视图解析器,它定义了如何将视图名称解析为具体的视图页面。<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean>更多配置 根据需要,我们还可以配置拦截器、多文件上传解析器等。注解与配置类方式随着Spring的发展,基于Java的配置越来越受到欢迎。下面将介绍如何通过注解和配置类来配置Spring MVC。创建配置类我们首先创建一个配置类WebAppConfig,并使用@Configuration注解标识它作为配置类。@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.example.controller") public class WebAppConfig implements WebMvcConfigurer { // 配置视图解析器 @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.jsp("/WEB-INF/views/", ".jsp"); } // 可以继续添加其他配置... }初始化DispatcherServlet为了替代web.xml,我们需要实现WebApplicationInitializer接口来配置DispatcherServlet。public class WebAppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { // 创建WebApplicationContext AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); ac.register(WebAppConfig.class); ac.setServletContext(servletContext); // 注册DispatcherServlet ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ac)); servlet.addMapping("/"); servlet.setLoadOnStartup(1); } }结语以上便是Spring MVC项目在传统XML配置和基于注解与配置类的方式下的配置过程。开发者可以根据项目需求和个人偏好选择适合的配置方式。随着技术的发展,注解与配置类方式由于其简洁性,越来越受到开发者的喜爱。
    • 1年前
    • 305
    • 0
    • 1
  • Java中的ArrayList:深入探索与实践指南 在Java的集合框架中,ArrayList是最常用的数据结构之一。无论你是新手还是经验丰富的开发者,理解ArrayList及其功能都是非常有价值的。在本篇博客中,我们将深入探索ArrayList,了解其如何工作,如何使用,以及注意事项。1. ArrayList简介ArrayList是一个动态数组,它可以自动扩展其大小以适应新增的元素。它实现了List接口,因此它是有序的(你添加元素的顺序会被保留)并且可以包含重复的元素。2. 如何使用ArrayList2.1 创建和初始化你可以这样创建一个ArrayList:ArrayList<String> names = new ArrayList<>();或者在创建时初始化一些元素:ArrayList<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry")); ArrayList<String> moreFruits = new ArrayList<>(fruits);// 使用已有集合初始化ArrayList2.2 添加元素使用add方法添加元素:fruits.add("Date");你也可以在指定位置插入元素:fruits.add(1, "Avocado");2.3 访问元素使用get方法通过索引访问元素:String firstFruit = fruits.get(0); // Apple2.4 修改元素使用set方法:fruits.set(0, "Apricot");2.5 删除元素使用remove方法可以通过索引或直接通过对象删除:fruits.remove(0); // 通过索引删除 fruits.remove("Banana"); // 通过对象删除2.6 其他常用方法int size = fruits.size(); // 获取ArrayList的大小 boolean contains = fruits.contains("Cherry"); // 检查元素是否存在 fruits.clear(); // 清空ArrayList boolean isEmpty = fruits.isEmpty();// 检查ArrayList是否为空3. 遍历ArrayList你可以使用多种方法遍历ArrayList:// 使用传统的for循环 for (int i = 0; i < fruits.size(); i++) { System.out.println(fruits.get(i)); } // 使用增强的for循环 for (String fruit : fruits) { System.out.println(fruit); } // 使用Java 8的forEach和Lambda表达式 fruits.forEach(fruit -> System.out.println(fruit));4. 注意点与建议4.1 性能考虑ArrayList在末尾添加元素非常快,但在列表中间插入或删除元素可能会比较慢,因为需要移动元素。如果你知道最终列表的大小,最好在创建时指定其初始容量,这有助于提高性能。4.2 线程安全性ArrayList不是线程安全的。如果你需要在并发环境中使用,考虑使用Vector或Collections.synchronizedList。4.3 避免ConcurrentModificationException当遍历列表的同时修改它时(例如,在一个for-each循环中删除元素),会抛出ConcurrentModificationException。为了避免这种情况,你可以使用Iterator的remove方法。4.4 动态扩容ArrayList在内部使用数组来存储元素。当添加的元素超过其当前容量时,它会自动增长。但频繁的扩容可能会导致性能问题。如果你知道数据的大致大小,可以在初始化时指定其容量:ArrayList<String> largeList = new ArrayList<>(1000);4.5 null 元素ArrayList允许存储null元素。但在处理ArrayList时要特别小心,以避免NullPointerException。结论ArrayList是Java中非常有用的工具,提供了动态数组的功能和灵活性。无论你是在编写简单的脚本还是复杂的应用程序,都会发现它非常有用。
    • 1年前
    • 370
    • 0
    • 3
  • Java抽象类、抽象方法与接口深度探讨 Java是一门面向对象的编程语言,其中有几个关键概念帮助我们构建灵活和强大的代码,其中最核心的是抽象类、抽象方法和接口。这篇博客将为您展示这些概念的工作方式、用途和注意点。1. 抽象类与抽象方法1.1 定义与示例抽象类是不能被实例化的类,它可能包含一个或多个抽象方法。抽象方法是没有具体实现的方法,只有声明。abstract class Animal { // 抽象方法 abstract void sound(); // 普通方法 void breathe() { System.out.println("呼吸空气"); } } class Dog extends Animal { // 实现父类的抽象方法 void sound() { System.out.println("狗汪汪叫"); } }1.2 用途与好处模板设计:抽象类为子类提供一个通用的模板,确保子类遵循某种规范。代码重用:抽象类可以有具体的实现,减少重复代码。1.3 注意点与缺陷抽象类不能被实例化。如果一个类继承了一个抽象类,则它必须实现该抽象类的所有抽象方法,除非它自己也是一个抽象类。一个类不能继承多个抽象类,因为Java不支持多重继承。2. 接口2.1 定义与示例接口是一个完全抽象的结构,它包含了一组抽象方法。Java 8之后,接口可以包含默认方法和静态方法。interface Swimmable { // 抽象方法 void swim(); // 默认方法 default void breatheInWater() { System.out.println("通过鳃呼吸"); } } class Fish implements Swimmable { @Override public void swim() { System.out.println("鱼在水中游泳"); } }2.2 用途与好处多重继承:Java不允许一个类继承多个类,但一个类可以实现多个接口。灵活性:允许一个类实现任意数量的方法,而不必实现接口中的所有方法。2.3 注意点与缺陷接口中的所有方法默认都是public的,你不能为它们使用其他访问修饰符。在Java 8之前,接口不能包含具体实现,这限制了代码重用。虽然默认方法允许我们在接口中加入新的方法,但可能导致与实现该接口的类的其他方法发生冲突。3. 综合示例abstract class Vehicle { abstract void run(); } interface Flyable { void fly(); } class Airplane extends Vehicle implements Flyable { @Override public void run() { System.out.println("在跑道上跑"); } @Override public void fly() { System.out.println("在空中飞"); } }这里,Airplane是一个既可以在地面跑又可以在空中飞的交通工具,通过结合抽象类和接口,我们可以很好地表示这种复杂的关系。
    • 1年前
    • 391
    • 0
    • 1
  • 深入Java多态性 Java中的多态性是面向对象编程的核心概念之一,它增强了代码的可读性、灵活性和可维护性。但理解它的细节并不总是那么直观。在这篇博客中,我们将通过具体的示例来探讨多态性的工作原理和常见的陷阱。1. 多态的基础首先,让我们从一个简单的示例开始,以理解多态性的基本概念。class Animal { void sound() { System.out.println("动物发出声音"); } } class Dog extends Animal { void sound() { System.out.println("狗汪汪叫"); } } class Cat extends Animal { void sound() { System.out.println("猫喵喵叫"); } } public class TestPolymorphism { public static void main(String[] args) { Animal myDog = new Dog(); Animal myCat = new Cat(); myDog.sound(); // 输出:狗汪汪叫 myCat.sound(); // 输出:猫喵喵叫 } }尽管我们使用的是Animal类型的引用,但在运行时,实际调用的方法是基于对象的真实类型。这就是运行时多态的魔力。2. 字段与多态与方法不同,字段不是多态的。这意味着字段的访问基于引用变量的类型,而不是对象的实际类型。这可以通过以下示例来说明:class Animal { String name = "动物"; } class Dog extends Animal { String name = "狗"; } public class TestFieldPolymorphism { public static void main(String[] args) { Animal myDog = new Dog(); System.out.println(myDog.name); // 输出:动物 } }尽管myDog实际上是一个Dog对象,但输出的仍然是"动物",因为字段的访问不是多态的。3. final、static和private与多态这些修饰符会影响到多态性。为了演示这一点,让我们考虑以下示例:class Animal { final void finalMethod() { System.out.println("这是一个final方法,不能被重写"); } static void staticMethod() { System.out.println("静态方法与类相关,不是多态的"); } private void privateMethod() { System.out.println("私有方法在子类中不可见,不能被重写"); } } class Dog extends Animal { // 下面的代码会报错,因为final方法不能被重写 // void finalMethod() {} // 这不是重写,只是子类中的一个新方法 static void staticMethod() { System.out.println("Dog类的静态方法"); } }4. 类型转换与多态在使用多态时,尤其是在类型转换中,我们需要小心。以下是一个常见的陷阱:Animal animal = new Animal(); Dog dog = (Dog) animal; // 这将抛出一个ClassCastException为避免这种情况,我们应使用instanceof进行检查:if(animal instanceof Dog) { Dog dog = (Dog) animal; }
    • 1年前
    • 392
    • 0
    • 1
  • 人生倒计时
    舔狗日记